• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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