1 // Copyright 2020 Google LLC
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 //     https://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 package com.google.security.cryptauth.lib.securegcm;
16 
17 import com.google.protobuf.ByteString;
18 import com.google.security.cryptauth.lib.securegcm.Ukey2Handshake.AlertException;
19 import com.google.security.cryptauth.lib.securegcm.Ukey2Handshake.HandshakeCipher;
20 import com.google.security.cryptauth.lib.securegcm.Ukey2Handshake.State;
21 import com.google.security.cryptauth.lib.securegcm.UkeyProto.Ukey2ClientFinished;
22 import com.google.security.cryptauth.lib.securegcm.UkeyProto.Ukey2ClientInit;
23 import com.google.security.cryptauth.lib.securegcm.UkeyProto.Ukey2ClientInit.CipherCommitment;
24 import com.google.security.cryptauth.lib.securegcm.UkeyProto.Ukey2Message;
25 import com.google.security.cryptauth.lib.securegcm.UkeyProto.Ukey2ServerInit;
26 import java.util.Arrays;
27 import java.util.HashSet;
28 import java.util.Set;
29 import junit.framework.TestCase;
30 import org.junit.Assert;
31 
32 /**
33  * Android compatible tests for the {@link Ukey2Handshake} class.
34  */
35 public class Ukey2HandshakeTest extends TestCase {
36 
37   private static final int MAX_AUTH_STRING_LENGTH = 32;
38 
39   @Override
setUp()40   protected void setUp() throws Exception {
41     KeyEncodingTest.installSunEcSecurityProviderIfNecessary();
42     super.setUp();
43   }
44 
45   /**
46    * Tests correct use
47    */
testHandshake()48   public void testHandshake() throws Exception {
49     if (KeyEncoding.isLegacyCryptoRequired()) {
50       // this means we're running on an old SDK, which doesn't support the
51       // necessary crypto. Let's not test anything in this case.
52       return;
53     }
54 
55     Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
56     Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
57     byte[] handshakeMessage;
58 
59     assertEquals(State.IN_PROGRESS, client.getHandshakeState());
60     assertEquals(State.IN_PROGRESS, server.getHandshakeState());
61 
62     // Message 1 (Client Init)
63     handshakeMessage = client.getNextHandshakeMessage();
64     server.parseHandshakeMessage(handshakeMessage);
65     assertEquals(State.IN_PROGRESS, client.getHandshakeState());
66     assertEquals(State.IN_PROGRESS, server.getHandshakeState());
67 
68     // Message 2 (Server Init)
69     handshakeMessage = server.getNextHandshakeMessage();
70     client.parseHandshakeMessage(handshakeMessage);
71     assertEquals(State.IN_PROGRESS, client.getHandshakeState());
72     assertEquals(State.IN_PROGRESS, server.getHandshakeState());
73 
74     // Message 3 (Client Finish)
75     handshakeMessage = client.getNextHandshakeMessage();
76     server.parseHandshakeMessage(handshakeMessage);
77     assertEquals(State.VERIFICATION_NEEDED, client.getHandshakeState());
78     assertEquals(State.VERIFICATION_NEEDED, server.getHandshakeState());
79 
80     // Get the auth string
81     byte[] clientAuthString = client.getVerificationString(MAX_AUTH_STRING_LENGTH);
82     byte[] serverAuthString = server.getVerificationString(MAX_AUTH_STRING_LENGTH);
83     Assert.assertArrayEquals(clientAuthString, serverAuthString);
84     assertEquals(State.VERIFICATION_IN_PROGRESS, client.getHandshakeState());
85     assertEquals(State.VERIFICATION_IN_PROGRESS, server.getHandshakeState());
86 
87     // Verify the auth string
88     client.verifyHandshake();
89     server.verifyHandshake();
90     assertEquals(State.FINISHED, client.getHandshakeState());
91     assertEquals(State.FINISHED, server.getHandshakeState());
92 
93     // Make a context
94     D2DConnectionContext clientContext = client.toConnectionContext();
95     D2DConnectionContext serverContext = server.toConnectionContext();
96     assertContextsCompatible(clientContext, serverContext);
97     assertEquals(State.ALREADY_USED, client.getHandshakeState());
98     assertEquals(State.ALREADY_USED, server.getHandshakeState());
99   }
100 
101   /**
102    * Verify enums for ciphers match the proto values
103    */
testCipherEnumValuesCorrect()104   public void testCipherEnumValuesCorrect() {
105     assertEquals(
106         "You added a cipher, but forgot to change the test", 1, HandshakeCipher.values().length);
107 
108     assertEquals(UkeyProto.Ukey2HandshakeCipher.P256_SHA512,
109         HandshakeCipher.P256_SHA512.getValue());
110   }
111 
112   /**
113    * Tests incorrect use by callers (client and servers accidentally sending the wrong message at
114    * the wrong time)
115    */
testHandshakeClientAndServerSendRepeatedOutOfOrderMessages()116   public void testHandshakeClientAndServerSendRepeatedOutOfOrderMessages() throws Exception {
117     if (KeyEncoding.isLegacyCryptoRequired()) {
118       // this means we're running on an old SDK, which doesn't support the
119       // necessary crypto. Let's not test anything in this case.
120       return;
121     }
122 
123     // Client sends ClientInit (again) instead of ClientFinished
124     Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
125     Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
126     byte[] handshakeMessage = client.getNextHandshakeMessage();
127     server.parseHandshakeMessage(handshakeMessage);
128     server.getNextHandshakeMessage(); // do this to avoid illegal state
129     try {
130       server.parseHandshakeMessage(handshakeMessage);
131       fail("Expected Alert for client sending ClientInit twice");
132     } catch (HandshakeException e) {
133       // success
134     }
135     assertEquals(State.ERROR, server.getHandshakeState());
136 
137     // Server sends ClientInit back to client instead of ServerInit
138     client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
139     server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
140     handshakeMessage = client.getNextHandshakeMessage();
141     try {
142       client.parseHandshakeMessage(handshakeMessage);
143       fail("Expected Alert for server sending ClientInit back to client");
144     } catch (AlertException e) {
145       // success
146     }
147     assertEquals(State.ERROR, client.getHandshakeState());
148 
149     // Clients sends ServerInit back to client instead of ClientFinished
150     client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
151     server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
152     handshakeMessage = client.getNextHandshakeMessage();
153     server.parseHandshakeMessage(handshakeMessage);
154     handshakeMessage = server.getNextHandshakeMessage();
155     try {
156       server.parseHandshakeMessage(handshakeMessage);
157       fail("Expected Alert for client sending ServerInit back to server");
158     } catch (HandshakeException e) {
159       // success
160     }
161     assertEquals(State.ERROR, server.getHandshakeState());
162   }
163 
164   /**
165    * Tests that verification codes are different for different handshake runs. Also tests a full
166    * man-in-the-middle attack.
167    */
testVerificationCodeUniqueToSession()168   public void testVerificationCodeUniqueToSession() throws Exception {
169     if (KeyEncoding.isLegacyCryptoRequired()) {
170       // this means we're running on an old SDK, which doesn't support the
171       // necessary crypto. Let's not test anything in this case.
172       return;
173     }
174 
175     // Client 1 and Server 1
176     Ukey2Handshake client1 = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
177     Ukey2Handshake server1 = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
178     byte[] handshakeMessage = client1.getNextHandshakeMessage();
179     server1.parseHandshakeMessage(handshakeMessage);
180     handshakeMessage = server1.getNextHandshakeMessage();
181     client1.parseHandshakeMessage(handshakeMessage);
182     handshakeMessage = client1.getNextHandshakeMessage();
183     server1.parseHandshakeMessage(handshakeMessage);
184     byte[] client1AuthString = client1.getVerificationString(MAX_AUTH_STRING_LENGTH);
185     byte[] server1AuthString = server1.getVerificationString(MAX_AUTH_STRING_LENGTH);
186     Assert.assertArrayEquals(client1AuthString, server1AuthString);
187 
188     // Client 2 and Server 2
189     Ukey2Handshake client2 = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
190     Ukey2Handshake server2 = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
191     handshakeMessage = client2.getNextHandshakeMessage();
192     server2.parseHandshakeMessage(handshakeMessage);
193     handshakeMessage = server2.getNextHandshakeMessage();
194     client2.parseHandshakeMessage(handshakeMessage);
195     handshakeMessage = client2.getNextHandshakeMessage();
196     server2.parseHandshakeMessage(handshakeMessage);
197     byte[] client2AuthString = client2.getVerificationString(MAX_AUTH_STRING_LENGTH);
198     byte[] server2AuthString = server2.getVerificationString(MAX_AUTH_STRING_LENGTH);
199     Assert.assertArrayEquals(client2AuthString, server2AuthString);
200 
201     // Make sure the verification strings differ
202     assertFalse(Arrays.equals(client1AuthString, client2AuthString));
203   }
204 
205   /**
206    * Test an attack where the adversary swaps out the public key in the final message (i.e.,
207    * commitment doesn't match public key)
208    */
testPublicKeyDoesntMatchCommitment()209   public void testPublicKeyDoesntMatchCommitment() throws Exception {
210     if (KeyEncoding.isLegacyCryptoRequired()) {
211       // this means we're running on an old SDK, which doesn't support the
212       // necessary crypto. Let's not test anything in this case.
213       return;
214     }
215 
216     // Run handshake as usual, but stop before sending client finished
217     Ukey2Handshake client1 = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
218     Ukey2Handshake server1 = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
219     byte[] handshakeMessage = client1.getNextHandshakeMessage();
220     server1.parseHandshakeMessage(handshakeMessage);
221     handshakeMessage = server1.getNextHandshakeMessage();
222 
223     // Run another handshake and get the final client finished
224     Ukey2Handshake client2 = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
225     Ukey2Handshake server2 = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
226     handshakeMessage = client2.getNextHandshakeMessage();
227     server2.parseHandshakeMessage(handshakeMessage);
228     handshakeMessage = server2.getNextHandshakeMessage();
229     client2.parseHandshakeMessage(handshakeMessage);
230     handshakeMessage = client2.getNextHandshakeMessage();
231 
232     // Now use the client finished from second handshake in first handshake (simulates where an
233     // attacker switches out the last message).
234     try {
235       server1.parseHandshakeMessage(handshakeMessage);
236       fail("Expected server to catch mismatched ClientFinished");
237     } catch (HandshakeException e) {
238       // success
239     }
240     assertEquals(State.ERROR, server1.getHandshakeState());
241 
242     // Make sure caller can't actually do anything with the server now that an error has occurred
243     try {
244       server1.getVerificationString(MAX_AUTH_STRING_LENGTH);
245       fail("Server allows operations post error");
246     } catch (IllegalStateException e) {
247       // success
248     }
249     try {
250       server1.verifyHandshake();
251       fail("Server allows operations post error");
252     } catch (IllegalStateException e) {
253       // success
254     }
255     try {
256       server1.toConnectionContext();
257       fail("Server allows operations post error");
258     } catch (IllegalStateException e) {
259       // success
260     }
261   }
262 
263   /**
264    * Test commitment having unsupported version
265    */
testClientInitUnsupportedVersion()266   public void testClientInitUnsupportedVersion() throws Exception {
267     if (KeyEncoding.isLegacyCryptoRequired()) {
268       // this means we're running on an old SDK, which doesn't support the
269       // necessary crypto. Let's not test anything in this case.
270       return;
271     }
272 
273     // Get ClientInit and modify the version to be too big
274     Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
275     Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
276     byte[] handshakeMessage = client.getNextHandshakeMessage();
277 
278     Ukey2Message.Builder message = Ukey2Message.newBuilder(
279         Ukey2Message.parseFrom(handshakeMessage));
280     Ukey2ClientInit.Builder clientInit =
281         Ukey2ClientInit.newBuilder(Ukey2ClientInit.parseFrom(message.getMessageData()));
282     clientInit.setVersion(Ukey2Handshake.VERSION + 1);
283     message.setMessageData(ByteString.copyFrom(clientInit.build().toByteArray()));
284     handshakeMessage = message.build().toByteArray();
285 
286     try {
287       server.parseHandshakeMessage(handshakeMessage);
288       fail("Server did not catch unsupported version (too big) in ClientInit");
289     } catch (AlertException e) {
290       // success
291     }
292     assertEquals(State.ERROR, server.getHandshakeState());
293 
294     // Get ClientInit and modify the version to be too big
295     client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
296     server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
297     handshakeMessage = client.getNextHandshakeMessage();
298 
299     message = Ukey2Message.newBuilder(
300         Ukey2Message.parseFrom(handshakeMessage));
301     clientInit = Ukey2ClientInit.newBuilder(Ukey2ClientInit.parseFrom(message.getMessageData()));
302     clientInit.setVersion(0 /* minimum version is 1 */);
303     message.setMessageData(ByteString.copyFrom(clientInit.build().toByteArray()));
304     handshakeMessage = message.build().toByteArray();
305 
306     try {
307       server.parseHandshakeMessage(handshakeMessage);
308       fail("Server did not catch unsupported version (too small) in ClientInit");
309     } catch (AlertException e) {
310       // success
311     }
312     assertEquals(State.ERROR, server.getHandshakeState());
313   }
314 
315   /**
316    * Tests that server catches wrong number of random bytes in ClientInit
317    */
testWrongNonceLengthInClientInit()318   public void testWrongNonceLengthInClientInit() throws Exception {
319     if (KeyEncoding.isLegacyCryptoRequired()) {
320       // this means we're running on an old SDK, which doesn't support the
321       // necessary crypto. Let's not test anything in this case.
322       return;
323     }
324 
325     // Get ClientInit and modify the nonce
326     Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
327     Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
328     byte[] handshakeMessage = client.getNextHandshakeMessage();
329 
330     Ukey2Message.Builder message = Ukey2Message.newBuilder(
331         Ukey2Message.parseFrom(handshakeMessage));
332     Ukey2ClientInit.Builder clientInit =
333         Ukey2ClientInit.newBuilder(Ukey2ClientInit.parseFrom(message.getMessageData()));
334     clientInit.setRandom(
335         ByteString.copyFrom(
336             Arrays.copyOf(
337                 clientInit.getRandom().toByteArray(),
338                 31 /* as per go/ukey2, nonces must be 32 bytes long */)));
339     message.setMessageData(ByteString.copyFrom(clientInit.build().toByteArray()));
340     handshakeMessage = message.build().toByteArray();
341 
342     try {
343       server.parseHandshakeMessage(handshakeMessage);
344       fail("Server did not catch nonce being too short in ClientInit");
345     } catch (AlertException e) {
346       // success
347     }
348     assertEquals(State.ERROR, server.getHandshakeState());
349   }
350 
351   /**
352    * Test that server catches missing commitment in ClientInit message
353    */
testServerCatchesMissingCommitmentInClientInit()354   public void testServerCatchesMissingCommitmentInClientInit() throws Exception {
355     if (KeyEncoding.isLegacyCryptoRequired()) {
356       // this means we're running on an old SDK, which doesn't support the
357       // necessary crypto. Let's not test anything in this case.
358       return;
359     }
360 
361     // Get ClientInit and modify the commitment
362     Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
363     Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
364     byte[] handshakeMessage = client.getNextHandshakeMessage();
365 
366     Ukey2Message.Builder message = Ukey2Message.newBuilder(
367         Ukey2Message.parseFrom(handshakeMessage));
368     Ukey2ClientInit clientInit =
369         Ukey2ClientInit.newBuilder(Ukey2ClientInit.parseFrom(message.getMessageData()))
370         .build();
371     Ukey2ClientInit.Builder badClientInit = Ukey2ClientInit.newBuilder()
372         .setVersion(clientInit.getVersion())
373         .setRandom(clientInit.getRandom());
374     for (CipherCommitment commitment : clientInit.getCipherCommitmentsList()) {
375       badClientInit.addCipherCommitments(commitment);
376     }
377 
378     message.setMessageData(ByteString.copyFrom(badClientInit.build().toByteArray()));
379     handshakeMessage = message.build().toByteArray();
380 
381     try {
382       server.parseHandshakeMessage(handshakeMessage);
383       fail("Server did not catch missing commitment in ClientInit");
384     } catch (AlertException e) {
385       // success
386     }
387   }
388 
389   /**
390    * Test that client catches invalid version in ServerInit
391    */
testServerInitUnsupportedVersion()392   public void testServerInitUnsupportedVersion() throws Exception {
393     if (KeyEncoding.isLegacyCryptoRequired()) {
394       // this means we're running on an old SDK, which doesn't support the
395       // necessary crypto. Let's not test anything in this case.
396       return;
397     }
398 
399     // Get ServerInit and modify the version to be too big
400     Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
401     Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
402     byte[] handshakeMessage = client.getNextHandshakeMessage();
403     server.parseHandshakeMessage(handshakeMessage);
404     handshakeMessage = server.getNextHandshakeMessage();
405 
406     Ukey2Message.Builder message = Ukey2Message.newBuilder(
407         Ukey2Message.parseFrom(handshakeMessage));
408     Ukey2ServerInit serverInit =
409         Ukey2ServerInit.newBuilder(Ukey2ServerInit.parseFrom(message.getMessageData()))
410             .setVersion(Ukey2Handshake.VERSION + 1)
411             .build();
412     message.setMessageData(ByteString.copyFrom(serverInit.toByteArray()));
413     handshakeMessage = message.build().toByteArray();
414 
415     try {
416       client.parseHandshakeMessage(handshakeMessage);
417       fail("Client did not catch unsupported version (too big) in ServerInit");
418     } catch (AlertException e) {
419       // success
420     }
421     assertEquals(State.ERROR, client.getHandshakeState());
422 
423     // Get ServerInit and modify the version to be too big
424     client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
425     server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
426     handshakeMessage = client.getNextHandshakeMessage();
427     server.parseHandshakeMessage(handshakeMessage);
428     handshakeMessage = server.getNextHandshakeMessage();
429 
430     message = Ukey2Message.newBuilder(Ukey2Message.parseFrom(handshakeMessage));
431     serverInit =
432         Ukey2ServerInit.newBuilder(Ukey2ServerInit.parseFrom(message.getMessageData()))
433             .setVersion(0 /* minimum version is 1 */)
434             .build();
435     message.setMessageData(ByteString.copyFrom(serverInit.toByteArray()));
436     handshakeMessage = message.build().toByteArray();
437 
438     try {
439       client.parseHandshakeMessage(handshakeMessage);
440       fail("Client did not catch unsupported version (too small) in ServerInit");
441     } catch (AlertException e) {
442       // success
443     }
444     assertEquals(State.ERROR, client.getHandshakeState());
445   }
446 
447   /**
448    * Tests that client catches wrong number of random bytes in ServerInit
449    */
testWrongNonceLengthInServerInit()450   public void testWrongNonceLengthInServerInit() throws Exception {
451     if (KeyEncoding.isLegacyCryptoRequired()) {
452       // this means we're running on an old SDK, which doesn't support the
453       // necessary crypto. Let's not test anything in this case.
454       return;
455     }
456 
457     // Get ServerInit and modify the nonce
458     Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
459     Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
460     byte[] handshakeMessage = client.getNextHandshakeMessage();
461     server.parseHandshakeMessage(handshakeMessage);
462     handshakeMessage = server.getNextHandshakeMessage();
463 
464     Ukey2Message.Builder message = Ukey2Message.newBuilder(
465         Ukey2Message.parseFrom(handshakeMessage));
466     Ukey2ServerInit.Builder serverInitBuilder =
467         Ukey2ServerInit.newBuilder(Ukey2ServerInit.parseFrom(message.getMessageData()));
468     Ukey2ServerInit serverInit = serverInitBuilder.setRandom(ByteString.copyFrom(Arrays.copyOf(
469             serverInitBuilder.getRandom().toByteArray(),
470             31 /* as per go/ukey2, nonces must be 32 bytes long */)))
471         .build();
472     message.setMessageData(ByteString.copyFrom(serverInit.toByteArray()));
473     handshakeMessage = message.build().toByteArray();
474 
475     try {
476       client.parseHandshakeMessage(handshakeMessage);
477       fail("Client did not catch nonce being too short in ServerInit");
478     } catch (AlertException e) {
479       // success
480     }
481     assertEquals(State.ERROR, client.getHandshakeState());
482   }
483 
484   /**
485    * Test that client catches missing or incorrect handshake cipher in serverInit
486    */
testMissingOrIncorrectHandshakeCipherInServerInit()487   public void testMissingOrIncorrectHandshakeCipherInServerInit() throws Exception {
488     if (KeyEncoding.isLegacyCryptoRequired()) {
489       // this means we're running on an old SDK, which doesn't support the
490       // necessary crypto. Let's not test anything in this case.
491       return;
492     }
493 
494     // Get ServerInit
495     Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
496     Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
497     byte[] handshakeMessage = client.getNextHandshakeMessage();
498     server.parseHandshakeMessage(handshakeMessage);
499     handshakeMessage = server.getNextHandshakeMessage();
500     Ukey2Message.Builder message = Ukey2Message.newBuilder(
501         Ukey2Message.parseFrom(handshakeMessage));
502     Ukey2ServerInit serverInit = Ukey2ServerInit.parseFrom(message.getMessageData());
503 
504     // remove handshake cipher
505     Ukey2ServerInit badServerInit = Ukey2ServerInit.newBuilder()
506         .setPublicKey(serverInit.getPublicKey())
507         .setRandom(serverInit.getRandom())
508         .setVersion(serverInit.getVersion())
509         .build();
510 
511     message.setMessageData(ByteString.copyFrom(badServerInit.toByteArray()));
512     handshakeMessage = message.build().toByteArray();
513 
514     try {
515       client.parseHandshakeMessage(handshakeMessage);
516       fail("Client did not catch missing handshake cipher in ServerInit");
517     } catch (AlertException e) {
518       // success
519     }
520     assertEquals(State.ERROR, client.getHandshakeState());
521 
522     // Get ServerInit
523     client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
524     server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
525     handshakeMessage = client.getNextHandshakeMessage();
526     server.parseHandshakeMessage(handshakeMessage);
527     handshakeMessage = server.getNextHandshakeMessage();
528     message = Ukey2Message.newBuilder(Ukey2Message.parseFrom(handshakeMessage));
529     serverInit = Ukey2ServerInit.parseFrom(message.getMessageData());
530 
531     // put in a bad handshake cipher
532     badServerInit = Ukey2ServerInit.newBuilder()
533         .setPublicKey(serverInit.getPublicKey())
534         .setRandom(serverInit.getRandom())
535         .setVersion(serverInit.getVersion())
536         .setHandshakeCipher(UkeyProto.Ukey2HandshakeCipher.RESERVED)
537         .build();
538 
539     message.setMessageData(ByteString.copyFrom(badServerInit.toByteArray()));
540     handshakeMessage = message.build().toByteArray();
541 
542     try {
543       client.parseHandshakeMessage(handshakeMessage);
544       fail("Client did not catch bad handshake cipher in ServerInit");
545     } catch (AlertException e) {
546       // success
547     }
548     assertEquals(State.ERROR, client.getHandshakeState());
549   }
550 
551   /**
552    * Test that client catches missing or incorrect public key in serverInit
553    */
testMissingOrIncorrectPublicKeyInServerInit()554   public void testMissingOrIncorrectPublicKeyInServerInit() throws Exception {
555     if (KeyEncoding.isLegacyCryptoRequired()) {
556       // this means we're running on an old SDK, which doesn't support the
557       // necessary crypto. Let's not test anything in this case.
558       return;
559     }
560 
561     // Get ServerInit
562     Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
563     Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
564     byte[] handshakeMessage = client.getNextHandshakeMessage();
565     server.parseHandshakeMessage(handshakeMessage);
566     handshakeMessage = server.getNextHandshakeMessage();
567     Ukey2Message.Builder message = Ukey2Message.newBuilder(
568         Ukey2Message.parseFrom(handshakeMessage));
569     Ukey2ServerInit serverInit = Ukey2ServerInit.parseFrom(message.getMessageData());
570 
571     // remove public key
572     Ukey2ServerInit badServerInit = Ukey2ServerInit.newBuilder()
573         .setRandom(serverInit.getRandom())
574         .setVersion(serverInit.getVersion())
575         .setHandshakeCipher(serverInit.getHandshakeCipher())
576         .build();
577 
578     message.setMessageData(ByteString.copyFrom(badServerInit.toByteArray()));
579     handshakeMessage = message.build().toByteArray();
580 
581     try {
582       client.parseHandshakeMessage(handshakeMessage);
583       fail("Client did not catch missing public key in ServerInit");
584     } catch (AlertException e) {
585       // success
586     }
587     assertEquals(State.ERROR, client.getHandshakeState());
588 
589     // Get ServerInit
590     client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
591     server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
592     handshakeMessage = client.getNextHandshakeMessage();
593     server.parseHandshakeMessage(handshakeMessage);
594     handshakeMessage = server.getNextHandshakeMessage();
595     message = Ukey2Message.newBuilder(
596         Ukey2Message.parseFrom(handshakeMessage));
597     serverInit = Ukey2ServerInit.parseFrom(message.getMessageData());
598 
599     // put in a bad public key
600     badServerInit = Ukey2ServerInit.newBuilder()
601         .setPublicKey(ByteString.copyFrom(new byte[] {42, 12, 1}))
602         .setRandom(serverInit.getRandom())
603         .setVersion(serverInit.getVersion())
604         .setHandshakeCipher(serverInit.getHandshakeCipher())
605         .build();
606 
607     message.setMessageData(ByteString.copyFrom(badServerInit.toByteArray()));
608     handshakeMessage = message.build().toByteArray();
609 
610     try {
611       client.parseHandshakeMessage(handshakeMessage);
612       fail("Client did not catch bad public key in ServerInit");
613     } catch (AlertException e) {
614       // success
615     }
616     assertEquals(State.ERROR, client.getHandshakeState());
617   }
618 
619   /**
620    * Test that client catches missing or incorrect public key in clientFinished
621    */
testMissingOrIncorrectPublicKeyInClientFinished()622   public void testMissingOrIncorrectPublicKeyInClientFinished() throws Exception {
623     if (KeyEncoding.isLegacyCryptoRequired()) {
624       // this means we're running on an old SDK, which doesn't support the
625       // necessary crypto. Let's not test anything in this case.
626       return;
627     }
628 
629     // Get ClientFinished
630     Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
631     Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
632     byte[] handshakeMessage = client.getNextHandshakeMessage();
633     server.parseHandshakeMessage(handshakeMessage);
634     handshakeMessage = server.getNextHandshakeMessage();
635     client.parseHandshakeMessage(handshakeMessage);
636     handshakeMessage = client.getNextHandshakeMessage();
637     Ukey2Message.Builder message = Ukey2Message.newBuilder(
638         Ukey2Message.parseFrom(handshakeMessage));
639 
640     // remove public key
641     Ukey2ClientFinished.Builder badClientFinished = Ukey2ClientFinished.newBuilder();
642 
643     message.setMessageData(ByteString.copyFrom(badClientFinished.build().toByteArray()));
644     handshakeMessage = message.build().toByteArray();
645 
646     try {
647       server.parseHandshakeMessage(handshakeMessage);
648       fail("Server did not catch missing public key in ClientFinished");
649     } catch (HandshakeException e) {
650       // success
651     }
652     assertEquals(State.ERROR, server.getHandshakeState());
653 
654     // Get ClientFinished
655     client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
656     server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
657     handshakeMessage = client.getNextHandshakeMessage();
658     server.parseHandshakeMessage(handshakeMessage);
659     handshakeMessage = server.getNextHandshakeMessage();
660     client.parseHandshakeMessage(handshakeMessage);
661     handshakeMessage = client.getNextHandshakeMessage();
662     message = Ukey2Message.newBuilder(Ukey2Message.parseFrom(handshakeMessage));
663 
664     // remove public key
665     badClientFinished = Ukey2ClientFinished.newBuilder()
666         .setPublicKey(ByteString.copyFrom(new byte[] {42, 12, 1}));
667 
668     message.setMessageData(ByteString.copyFrom(badClientFinished.build().toByteArray()));
669     handshakeMessage = message.build().toByteArray();
670 
671     try {
672       server.parseHandshakeMessage(handshakeMessage);
673       fail("Server did not catch bad public key in ClientFinished");
674     } catch (HandshakeException e) {
675       // success
676     }
677     assertEquals(State.ERROR, server.getHandshakeState());
678   }
679 
680   /**
681    * Tests that items (nonces, commitments, public keys) that should be random are at least
682    * different on every run.
683    */
testRandomItemsDifferentOnEveryRun()684   public void testRandomItemsDifferentOnEveryRun() throws Exception {
685     if (KeyEncoding.isLegacyCryptoRequired()) {
686       // this means we're running on an old SDK, which doesn't support the
687       // necessary crypto. Let's not test anything in this case.
688       return;
689     }
690 
691     int numberOfRuns = 50;
692 
693     // Search for collisions
694     Set<Integer> commitments = new HashSet<>(numberOfRuns);
695     Set<Integer> clientNonces = new HashSet<>(numberOfRuns);
696     Set<Integer> serverNonces = new HashSet<>(numberOfRuns);
697     Set<Integer> serverPublicKeys = new HashSet<>(numberOfRuns);
698     Set<Integer> clientPublicKeys = new HashSet<>(numberOfRuns);
699 
700     for (int i = 0; i < numberOfRuns; i++) {
701       Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
702       Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
703       byte[] handshakeMessage = client.getNextHandshakeMessage();
704       Ukey2Message message = Ukey2Message.parseFrom(handshakeMessage);
705       Ukey2ClientInit clientInit = Ukey2ClientInit.parseFrom(message.getMessageData());
706 
707       server.parseHandshakeMessage(handshakeMessage);
708       handshakeMessage = server.getNextHandshakeMessage();
709       message = Ukey2Message.parseFrom(handshakeMessage);
710       Ukey2ServerInit serverInit = Ukey2ServerInit.parseFrom(message.getMessageData());
711 
712       client.parseHandshakeMessage(handshakeMessage);
713       handshakeMessage = client.getNextHandshakeMessage();
714       message = Ukey2Message.parseFrom(handshakeMessage);
715       Ukey2ClientFinished clientFinished = Ukey2ClientFinished.parseFrom(message.getMessageData());
716 
717       // Clean up to save some memory (b/32054837)
718       client = null;
719       server = null;
720       handshakeMessage = null;
721       message = null;
722       System.gc();
723 
724       // ClientInit randomness
725       Integer nonceHash = Integer.valueOf(Arrays.hashCode(clientInit.getRandom().toByteArray()));
726       if (clientNonces.contains(nonceHash) || serverNonces.contains(nonceHash)) {
727         fail("Nonce in ClientINit has repeated!");
728       }
729       clientNonces.add(nonceHash);
730 
731       Integer commitmentHash = 0;
732       for (CipherCommitment commitement : clientInit.getCipherCommitmentsList()) {
733         commitmentHash += Arrays.hashCode(commitement.toByteArray());
734       }
735       if (commitments.contains(nonceHash)) {
736         fail("Commitment has repeated!");
737       }
738       commitments.add(commitmentHash);
739 
740       // ServerInit randomness
741       nonceHash = Integer.valueOf(Arrays.hashCode(serverInit.getRandom().toByteArray()));
742       if (serverNonces.contains(nonceHash) || clientNonces.contains(nonceHash)) {
743         fail("Nonce in ServerInit repeated!");
744       }
745       serverNonces.add(nonceHash);
746 
747       Integer publicKeyHash =
748           Integer.valueOf(Arrays.hashCode(serverInit.getPublicKey().toByteArray()));
749       if (serverPublicKeys.contains(publicKeyHash) || clientPublicKeys.contains(publicKeyHash)) {
750         fail("Public Key in ServerInit repeated!");
751       }
752       serverPublicKeys.add(publicKeyHash);
753 
754       // Client Finished randomness
755       publicKeyHash = Integer.valueOf(Arrays.hashCode(clientFinished.getPublicKey().toByteArray()));
756       if (serverPublicKeys.contains(publicKeyHash) || clientPublicKeys.contains(publicKeyHash)) {
757         fail("Public Key in ClientFinished repeated!");
758       }
759       clientPublicKeys.add(publicKeyHash);
760     }
761   }
762 
763   /**
764    * Tests that {@link Ukey2Handshake#getVerificationString(int)} enforces sane verification string
765    * lengths.
766    */
testGetVerificationEnforcesSaneLengths()767   public void testGetVerificationEnforcesSaneLengths() throws Exception {
768     if (KeyEncoding.isLegacyCryptoRequired()) {
769       // this means we're running on an old SDK, which doesn't support the
770       // necessary crypto. Let's not test anything in this case.
771       return;
772     }
773 
774     // Run the protocol
775     Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
776     Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
777     byte[] handshakeMessage = client.getNextHandshakeMessage();
778     server.parseHandshakeMessage(handshakeMessage);
779     handshakeMessage = server.getNextHandshakeMessage();
780     client.parseHandshakeMessage(handshakeMessage);
781     handshakeMessage = client.getNextHandshakeMessage();
782     server.parseHandshakeMessage(handshakeMessage);
783 
784     // Try to get too short verification string
785     try {
786       client.getVerificationString(0);
787       fail("Too short verification string allowed");
788     } catch (IllegalArgumentException e) {
789       // success
790     }
791 
792     // Try to get too long verification string
793     try {
794       server.getVerificationString(MAX_AUTH_STRING_LENGTH + 1);
795       fail("Too long verification string allowed");
796     } catch (IllegalArgumentException e) {
797       // success
798     }
799   }
800 
801   /**
802    * Asserts that the given client and server contexts are compatible
803    */
assertContextsCompatible( D2DConnectionContext clientContext, D2DConnectionContext serverContext)804   private void assertContextsCompatible(
805       D2DConnectionContext clientContext, D2DConnectionContext serverContext) {
806     assertNotNull(clientContext);
807     assertNotNull(serverContext);
808     assertEquals(D2DConnectionContextV1.PROTOCOL_VERSION, clientContext.getProtocolVersion());
809     assertEquals(D2DConnectionContextV1.PROTOCOL_VERSION, serverContext.getProtocolVersion());
810     assertEquals(clientContext.getEncodeKey(), serverContext.getDecodeKey());
811     assertEquals(clientContext.getDecodeKey(), serverContext.getEncodeKey());
812     assertFalse(clientContext.getEncodeKey().equals(clientContext.getDecodeKey()));
813     assertEquals(0, clientContext.getSequenceNumberForEncoding());
814     assertEquals(0, clientContext.getSequenceNumberForDecoding());
815     assertEquals(0, serverContext.getSequenceNumberForEncoding());
816     assertEquals(0, serverContext.getSequenceNumberForDecoding());
817   }
818 }
819