1 // Copyright 2017 Google Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 // 15 //////////////////////////////////////////////////////////////////////////////// 16 17 package com.google.crypto.tink.subtle; 18 19 import static com.google.common.truth.Truth.assertThat; 20 import static org.junit.Assert.assertEquals; 21 import static org.junit.Assert.assertThrows; 22 23 import com.google.crypto.tink.testing.WycheproofTestUtil; 24 import com.google.gson.JsonArray; 25 import com.google.gson.JsonObject; 26 import java.security.GeneralSecurityException; 27 import java.security.InvalidKeyException; 28 import java.util.ArrayList; 29 import org.junit.Test; 30 import org.junit.runner.RunWith; 31 import org.junit.runners.JUnit4; 32 33 /** Unit tests for {@link X25519}. */ 34 @RunWith(JUnit4.class) 35 public final class X25519Test { 36 /** Iteration test in Section 5.2 of RFC 7748. https://tools.ietf.org/html/rfc7748 */ 37 @Test testComputeSharedSecretWithRfcIteration()38 public void testComputeSharedSecretWithRfcIteration() throws Exception { 39 byte[] k = new byte[32]; 40 k[0] = 9; 41 byte[] prevK = k; 42 k = X25519.computeSharedSecret(k, prevK); 43 assertEquals("422c8e7a6227d7bca1350b3e2bb7279f7897b87bb6854b783c60e80311ae3079", Hex.encode(k)); 44 for (int i = 0; i < 999; i++) { 45 byte[] tmp = k; 46 k = X25519.computeSharedSecret(k, prevK); 47 prevK = tmp; 48 } 49 assertEquals("684cf59ba83309552800ef566f2f4d3c1c3887c49360e3875f2eb94d99532c51", Hex.encode(k)); 50 // Omitting 1M iteration to limit the test runtime. 51 } 52 53 @Test computeSharedSecret_ignoresMostSignificantBitInPublicKey()54 public void computeSharedSecret_ignoresMostSignificantBitInPublicKey() throws Exception { 55 // first iteration of test in Section 5.2 of RFC 7748 with MSB set 56 byte[] k = new byte[32]; 57 k[0] = 9; 58 59 byte[] kWithMsb = new byte[32]; 60 kWithMsb[0] = 9; 61 kWithMsb[31] = (byte) 0x80; // set MSB 62 63 assertThat(Hex.encode(X25519.computeSharedSecret(k, kWithMsb))) 64 .isEqualTo("422c8e7a6227d7bca1350b3e2bb7279f7897b87bb6854b783c60e80311ae3079"); 65 } 66 67 @Test computeSharedSecret_withBannedPublicKey_throws()68 public void computeSharedSecret_withBannedPublicKey_throws() throws Exception { 69 byte[] privateKey = new byte[32]; 70 privateKey[0] = 9; 71 72 // List of banned public keys from Curve25519.java. 73 byte[][] bannedPublicKeys = 74 new byte[][] { 75 // 0 76 new byte[] { 77 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 78 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 79 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 80 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 81 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 82 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 83 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 84 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 85 }, 86 // 1 87 new byte[] { 88 (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, 89 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 90 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 91 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 92 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 93 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 94 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 95 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 96 }, 97 // 325606250916557431795983626356110631294008115727848805560023387167927233504 98 new byte[] { 99 (byte) 0xe0, (byte) 0xeb, (byte) 0x7a, (byte) 0x7c, 100 (byte) 0x3b, (byte) 0x41, (byte) 0xb8, (byte) 0xae, 101 (byte) 0x16, (byte) 0x56, (byte) 0xe3, (byte) 0xfa, 102 (byte) 0xf1, (byte) 0x9f, (byte) 0xc4, (byte) 0x6a, 103 (byte) 0xda, (byte) 0x09, (byte) 0x8d, (byte) 0xeb, 104 (byte) 0x9c, (byte) 0x32, (byte) 0xb1, (byte) 0xfd, 105 (byte) 0x86, (byte) 0x62, (byte) 0x05, (byte) 0x16, 106 (byte) 0x5f, (byte) 0x49, (byte) 0xb8, (byte) 0x00, 107 }, 108 // 39382357235489614581723060781553021112529911719440698176882885853963445705823 109 new byte[] { 110 (byte) 0x5f, (byte) 0x9c, (byte) 0x95, (byte) 0xbc, 111 (byte) 0xa3, (byte) 0x50, (byte) 0x8c, (byte) 0x24, 112 (byte) 0xb1, (byte) 0xd0, (byte) 0xb1, (byte) 0x55, 113 (byte) 0x9c, (byte) 0x83, (byte) 0xef, (byte) 0x5b, 114 (byte) 0x04, (byte) 0x44, (byte) 0x5c, (byte) 0xc4, 115 (byte) 0x58, (byte) 0x1c, (byte) 0x8e, (byte) 0x86, 116 (byte) 0xd8, (byte) 0x22, (byte) 0x4e, (byte) 0xdd, 117 (byte) 0xd0, (byte) 0x9f, (byte) 0x11, (byte) 0x57 118 }, 119 // 2^255 - 19 - 1 120 new byte[] { 121 (byte) 0xec, (byte) 0xff, (byte) 0xff, (byte) 0xff, 122 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, 123 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, 124 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, 125 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, 126 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, 127 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, 128 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f, 129 }, 130 // 2^255 - 19 131 new byte[] { 132 (byte) 0xed, (byte) 0xff, (byte) 0xff, (byte) 0xff, 133 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, 134 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, 135 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, 136 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, 137 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, 138 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, 139 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f 140 }, 141 // 2^255 - 19 + 1 142 new byte[] { 143 (byte) 0xee, (byte) 0xff, (byte) 0xff, (byte) 0xff, 144 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, 145 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, 146 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, 147 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, 148 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, 149 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, 150 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f 151 } 152 }; 153 for (byte[] bannedPublicKey : bannedPublicKeys) { 154 assertThrows( 155 InvalidKeyException.class, () -> X25519.computeSharedSecret(privateKey, bannedPublicKey)); 156 } 157 } 158 159 /** 160 * Tests against the test vectors in Section 6.1 of RFC 7748. https://tools.ietf.org/html/rfc7748 161 */ 162 @Test testPublicFromPrivateWithRfcTestVectors()163 public void testPublicFromPrivateWithRfcTestVectors() throws Exception { 164 byte[] out = 165 X25519.publicFromPrivate( 166 Hex.decode("77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a")); 167 assertEquals( 168 "8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a", Hex.encode(out)); 169 170 out = 171 X25519.publicFromPrivate( 172 Hex.decode("5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb")); 173 assertEquals( 174 "de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f", Hex.encode(out)); 175 } 176 177 @Test testGeneratePrivateKeyReturnsIntentionallyMalformedKeys()178 public void testGeneratePrivateKeyReturnsIntentionallyMalformedKeys() { 179 byte[] privateKey = X25519.generatePrivateKey(); 180 assertEquals(7, privateKey[0] & 7); 181 assertEquals(128, privateKey[31] & 192); 182 } 183 184 @Test testX25519ThrowsIllegalArgExceptionWhenPrivateKeySizeIsLessThan32Bytes()185 public void testX25519ThrowsIllegalArgExceptionWhenPrivateKeySizeIsLessThan32Bytes() 186 throws Exception { 187 byte[] privateKey = new byte[31]; 188 byte[] base = new byte[32]; 189 base[0] = 9; 190 assertThrows(InvalidKeyException.class, () -> X25519.computeSharedSecret(privateKey, base)); 191 } 192 193 @Test testX25519ThrowsIllegalArgExceptionWhenPrivateKeySizeIsGreaterThan32Bytes()194 public void testX25519ThrowsIllegalArgExceptionWhenPrivateKeySizeIsGreaterThan32Bytes() 195 throws Exception { 196 byte[] privateKey = new byte[33]; 197 byte[] base = new byte[32]; 198 base[0] = 9; 199 assertThrows(InvalidKeyException.class, () -> X25519.computeSharedSecret(privateKey, base)); 200 } 201 202 @Test testX25519ThrowsIllegalArgExceptionWhenPeersPublicValueIsLessThan32Bytes()203 public void testX25519ThrowsIllegalArgExceptionWhenPeersPublicValueIsLessThan32Bytes() 204 throws Exception { 205 byte[] privateKey = new byte[32]; 206 byte[] base = new byte[31]; 207 base[0] = 9; 208 assertThrows(InvalidKeyException.class, () -> X25519.computeSharedSecret(privateKey, base)); 209 } 210 211 @Test testX25519ThrowsIllegalArgExceptionWhenPeersPublicValueIsGreaterThan32Bytes()212 public void testX25519ThrowsIllegalArgExceptionWhenPeersPublicValueIsGreaterThan32Bytes() 213 throws Exception { 214 byte[] privateKey = new byte[32]; 215 byte[] base = new byte[33]; 216 base[0] = 9; 217 assertThrows(InvalidKeyException.class, () -> X25519.computeSharedSecret(privateKey, base)); 218 } 219 220 @Test testX25519PublicFromPrivateThrowsIllegalArgExWhenPrivateKeyIsLessThan32Bytes()221 public void testX25519PublicFromPrivateThrowsIllegalArgExWhenPrivateKeyIsLessThan32Bytes() { 222 byte[] privateKey = new byte[31]; 223 assertThrows(InvalidKeyException.class, () -> X25519.publicFromPrivate(privateKey)); 224 } 225 226 @Test testX25519PublicFromPrivateThrowsIllegalArgExWhenPrivateKeyIsGreaterThan32Bytes()227 public void testX25519PublicFromPrivateThrowsIllegalArgExWhenPrivateKeyIsGreaterThan32Bytes() { 228 byte[] privateKey = new byte[33]; 229 assertThrows(InvalidKeyException.class, () -> X25519.publicFromPrivate(privateKey)); 230 } 231 232 @Test testComputeSharedSecretWithWycheproofVectors()233 public void testComputeSharedSecretWithWycheproofVectors() throws Exception { 234 JsonObject json = 235 WycheproofTestUtil.readJson("../wycheproof/testvectors/x25519_test.json"); 236 ArrayList<String> errors = new ArrayList<>(); 237 JsonArray testGroups = json.getAsJsonArray("testGroups"); 238 for (int i = 0; i < testGroups.size(); i++) { 239 JsonObject group = testGroups.get(i).getAsJsonObject(); 240 JsonArray tests = group.getAsJsonArray("tests"); 241 String curve = group.get("curve").getAsString(); 242 for (int j = 0; j < tests.size(); j++) { 243 JsonObject testcase = tests.get(j).getAsJsonObject(); 244 String tcId = 245 String.format("testcase %d (%s)", 246 testcase.get("tcId").getAsInt(), testcase.get("comment").getAsString()); 247 String result = testcase.get("result").getAsString(); 248 String hexPubKey = testcase.get("public").getAsString(); 249 String hexPrivKey = testcase.get("private").getAsString(); 250 String expectedSharedSecret = testcase.get("shared").getAsString(); 251 assertThat(curve).isEqualTo("curve25519"); 252 try { 253 String sharedSecret = 254 Hex.encode(X25519.computeSharedSecret(Hex.decode(hexPrivKey), Hex.decode(hexPubKey))); 255 if (result.equals("invalid")) { 256 errors.add( 257 "FAIL " + tcId + ": accepting invalid parameters, shared secret: " + sharedSecret); 258 } else if (!expectedSharedSecret.equals(sharedSecret)) { 259 errors.add( 260 "FAIL " 261 + tcId 262 + ": incorrect shared secret, computed: " 263 + sharedSecret 264 + ", expected: " 265 + expectedSharedSecret); 266 } 267 } catch (GeneralSecurityException ex) { 268 if (result.equals("valid")) { 269 errors.add("FAIL " + tcId + ": exception: " + ex.getMessage()); 270 } 271 } 272 } 273 } 274 assertThat(errors).isEmpty(); 275 } 276 } 277