blob: 49e6f30294fae27b594c95fb4d9e02fb30461d1c [file] [log] [blame]
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.security.cryptauth.lib.securegcm;
import com.google.protobuf.ByteString;
import com.google.security.cryptauth.lib.securegcm.Ukey2Handshake.AlertException;
import com.google.security.cryptauth.lib.securegcm.Ukey2Handshake.HandshakeCipher;
import com.google.security.cryptauth.lib.securegcm.Ukey2Handshake.State;
import com.google.security.cryptauth.lib.securegcm.UkeyProto.Ukey2ClientFinished;
import com.google.security.cryptauth.lib.securegcm.UkeyProto.Ukey2ClientInit;
import com.google.security.cryptauth.lib.securegcm.UkeyProto.Ukey2ClientInit.CipherCommitment;
import com.google.security.cryptauth.lib.securegcm.UkeyProto.Ukey2Message;
import com.google.security.cryptauth.lib.securegcm.UkeyProto.Ukey2ServerInit;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import junit.framework.TestCase;
import org.junit.Assert;
/**
* Android compatible tests for the {@link Ukey2Handshake} class.
*/
public class Ukey2HandshakeTest extends TestCase {
private static final int MAX_AUTH_STRING_LENGTH = 32;
@Override
protected void setUp() throws Exception {
KeyEncodingTest.installSunEcSecurityProviderIfNecessary();
super.setUp();
}
/**
* Tests correct use
*/
public void testHandshake() throws Exception {
if (KeyEncoding.isLegacyCryptoRequired()) {
// this means we're running on an old SDK, which doesn't support the
// necessary crypto. Let's not test anything in this case.
return;
}
Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
byte[] handshakeMessage;
assertEquals(State.IN_PROGRESS, client.getHandshakeState());
assertEquals(State.IN_PROGRESS, server.getHandshakeState());
// Message 1 (Client Init)
handshakeMessage = client.getNextHandshakeMessage();
server.parseHandshakeMessage(handshakeMessage);
assertEquals(State.IN_PROGRESS, client.getHandshakeState());
assertEquals(State.IN_PROGRESS, server.getHandshakeState());
// Message 2 (Server Init)
handshakeMessage = server.getNextHandshakeMessage();
client.parseHandshakeMessage(handshakeMessage);
assertEquals(State.IN_PROGRESS, client.getHandshakeState());
assertEquals(State.IN_PROGRESS, server.getHandshakeState());
// Message 3 (Client Finish)
handshakeMessage = client.getNextHandshakeMessage();
server.parseHandshakeMessage(handshakeMessage);
assertEquals(State.VERIFICATION_NEEDED, client.getHandshakeState());
assertEquals(State.VERIFICATION_NEEDED, server.getHandshakeState());
// Get the auth string
byte[] clientAuthString = client.getVerificationString(MAX_AUTH_STRING_LENGTH);
byte[] serverAuthString = server.getVerificationString(MAX_AUTH_STRING_LENGTH);
Assert.assertArrayEquals(clientAuthString, serverAuthString);
assertEquals(State.VERIFICATION_IN_PROGRESS, client.getHandshakeState());
assertEquals(State.VERIFICATION_IN_PROGRESS, server.getHandshakeState());
// Verify the auth string
client.verifyHandshake();
server.verifyHandshake();
assertEquals(State.FINISHED, client.getHandshakeState());
assertEquals(State.FINISHED, server.getHandshakeState());
// Make a context
D2DConnectionContext clientContext = client.toConnectionContext();
D2DConnectionContext serverContext = server.toConnectionContext();
assertContextsCompatible(clientContext, serverContext);
assertEquals(State.ALREADY_USED, client.getHandshakeState());
assertEquals(State.ALREADY_USED, server.getHandshakeState());
}
/**
* Verify enums for ciphers match the proto values
*/
public void testCipherEnumValuesCorrect() {
assertEquals(
"You added a cipher, but forgot to change the test", 1, HandshakeCipher.values().length);
assertEquals(UkeyProto.Ukey2HandshakeCipher.P256_SHA512,
HandshakeCipher.P256_SHA512.getValue());
}
/**
* Tests incorrect use by callers (client and servers accidentally sending the wrong message at
* the wrong time)
*/
public void testHandshakeClientAndServerSendRepeatedOutOfOrderMessages() throws Exception {
if (KeyEncoding.isLegacyCryptoRequired()) {
// this means we're running on an old SDK, which doesn't support the
// necessary crypto. Let's not test anything in this case.
return;
}
// Client sends ClientInit (again) instead of ClientFinished
Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
byte[] handshakeMessage = client.getNextHandshakeMessage();
server.parseHandshakeMessage(handshakeMessage);
server.getNextHandshakeMessage(); // do this to avoid illegal state
try {
server.parseHandshakeMessage(handshakeMessage);
fail("Expected Alert for client sending ClientInit twice");
} catch (HandshakeException e) {
// success
}
assertEquals(State.ERROR, server.getHandshakeState());
// Server sends ClientInit back to client instead of ServerInit
client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
handshakeMessage = client.getNextHandshakeMessage();
try {
client.parseHandshakeMessage(handshakeMessage);
fail("Expected Alert for server sending ClientInit back to client");
} catch (AlertException e) {
// success
}
assertEquals(State.ERROR, client.getHandshakeState());
// Clients sends ServerInit back to client instead of ClientFinished
client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
handshakeMessage = client.getNextHandshakeMessage();
server.parseHandshakeMessage(handshakeMessage);
handshakeMessage = server.getNextHandshakeMessage();
try {
server.parseHandshakeMessage(handshakeMessage);
fail("Expected Alert for client sending ServerInit back to server");
} catch (HandshakeException e) {
// success
}
assertEquals(State.ERROR, server.getHandshakeState());
}
/**
* Tests that verification codes are different for different handshake runs. Also tests a full
* on-path attack.
*/
public void testVerificationCodeUniqueToSession() throws Exception {
if (KeyEncoding.isLegacyCryptoRequired()) {
// this means we're running on an old SDK, which doesn't support the
// necessary crypto. Let's not test anything in this case.
return;
}
// Client 1 and Server 1
Ukey2Handshake client1 = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
Ukey2Handshake server1 = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
byte[] handshakeMessage = client1.getNextHandshakeMessage();
server1.parseHandshakeMessage(handshakeMessage);
handshakeMessage = server1.getNextHandshakeMessage();
client1.parseHandshakeMessage(handshakeMessage);
handshakeMessage = client1.getNextHandshakeMessage();
server1.parseHandshakeMessage(handshakeMessage);
byte[] client1AuthString = client1.getVerificationString(MAX_AUTH_STRING_LENGTH);
byte[] server1AuthString = server1.getVerificationString(MAX_AUTH_STRING_LENGTH);
Assert.assertArrayEquals(client1AuthString, server1AuthString);
// Client 2 and Server 2
Ukey2Handshake client2 = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
Ukey2Handshake server2 = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
handshakeMessage = client2.getNextHandshakeMessage();
server2.parseHandshakeMessage(handshakeMessage);
handshakeMessage = server2.getNextHandshakeMessage();
client2.parseHandshakeMessage(handshakeMessage);
handshakeMessage = client2.getNextHandshakeMessage();
server2.parseHandshakeMessage(handshakeMessage);
byte[] client2AuthString = client2.getVerificationString(MAX_AUTH_STRING_LENGTH);
byte[] server2AuthString = server2.getVerificationString(MAX_AUTH_STRING_LENGTH);
Assert.assertArrayEquals(client2AuthString, server2AuthString);
// Make sure the verification strings differ
assertFalse(Arrays.equals(client1AuthString, client2AuthString));
}
/**
* Test an attack where the adversary swaps out the public key in the final message (i.e.,
* commitment doesn't match public key)
*/
public void testPublicKeyDoesntMatchCommitment() throws Exception {
if (KeyEncoding.isLegacyCryptoRequired()) {
// this means we're running on an old SDK, which doesn't support the
// necessary crypto. Let's not test anything in this case.
return;
}
// Run handshake as usual, but stop before sending client finished
Ukey2Handshake client1 = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
Ukey2Handshake server1 = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
byte[] handshakeMessage = client1.getNextHandshakeMessage();
server1.parseHandshakeMessage(handshakeMessage);
handshakeMessage = server1.getNextHandshakeMessage();
// Run another handshake and get the final client finished
Ukey2Handshake client2 = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
Ukey2Handshake server2 = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
handshakeMessage = client2.getNextHandshakeMessage();
server2.parseHandshakeMessage(handshakeMessage);
handshakeMessage = server2.getNextHandshakeMessage();
client2.parseHandshakeMessage(handshakeMessage);
handshakeMessage = client2.getNextHandshakeMessage();
// Now use the client finished from second handshake in first handshake (simulates where an
// attacker switches out the last message).
try {
server1.parseHandshakeMessage(handshakeMessage);
fail("Expected server to catch mismatched ClientFinished");
} catch (HandshakeException e) {
// success
}
assertEquals(State.ERROR, server1.getHandshakeState());
// Make sure caller can't actually do anything with the server now that an error has occurred
try {
server1.getVerificationString(MAX_AUTH_STRING_LENGTH);
fail("Server allows operations post error");
} catch (IllegalStateException e) {
// success
}
try {
server1.verifyHandshake();
fail("Server allows operations post error");
} catch (IllegalStateException e) {
// success
}
try {
server1.toConnectionContext();
fail("Server allows operations post error");
} catch (IllegalStateException e) {
// success
}
}
/**
* Test commitment having unsupported version
*/
public void testClientInitUnsupportedVersion() throws Exception {
if (KeyEncoding.isLegacyCryptoRequired()) {
// this means we're running on an old SDK, which doesn't support the
// necessary crypto. Let's not test anything in this case.
return;
}
// Get ClientInit and modify the version to be too big
Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
byte[] handshakeMessage = client.getNextHandshakeMessage();
Ukey2Message.Builder message = Ukey2Message.newBuilder(
Ukey2Message.parseFrom(handshakeMessage));
Ukey2ClientInit.Builder clientInit =
Ukey2ClientInit.newBuilder(Ukey2ClientInit.parseFrom(message.getMessageData()));
clientInit.setVersion(Ukey2Handshake.VERSION + 1);
message.setMessageData(ByteString.copyFrom(clientInit.build().toByteArray()));
handshakeMessage = message.build().toByteArray();
try {
server.parseHandshakeMessage(handshakeMessage);
fail("Server did not catch unsupported version (too big) in ClientInit");
} catch (AlertException e) {
// success
}
assertEquals(State.ERROR, server.getHandshakeState());
// Get ClientInit and modify the version to be too big
client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
handshakeMessage = client.getNextHandshakeMessage();
message = Ukey2Message.newBuilder(
Ukey2Message.parseFrom(handshakeMessage));
clientInit = Ukey2ClientInit.newBuilder(Ukey2ClientInit.parseFrom(message.getMessageData()));
clientInit.setVersion(0 /* minimum version is 1 */);
message.setMessageData(ByteString.copyFrom(clientInit.build().toByteArray()));
handshakeMessage = message.build().toByteArray();
try {
server.parseHandshakeMessage(handshakeMessage);
fail("Server did not catch unsupported version (too small) in ClientInit");
} catch (AlertException e) {
// success
}
assertEquals(State.ERROR, server.getHandshakeState());
}
/**
* Tests that server catches wrong number of random bytes in ClientInit
*/
public void testWrongNonceLengthInClientInit() throws Exception {
if (KeyEncoding.isLegacyCryptoRequired()) {
// this means we're running on an old SDK, which doesn't support the
// necessary crypto. Let's not test anything in this case.
return;
}
// Get ClientInit and modify the nonce
Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
byte[] handshakeMessage = client.getNextHandshakeMessage();
Ukey2Message.Builder message = Ukey2Message.newBuilder(
Ukey2Message.parseFrom(handshakeMessage));
Ukey2ClientInit.Builder clientInit =
Ukey2ClientInit.newBuilder(Ukey2ClientInit.parseFrom(message.getMessageData()));
clientInit.setRandom(
ByteString.copyFrom(
Arrays.copyOf(
clientInit.getRandom().toByteArray(),
31 /* as per go/ukey2, nonces must be 32 bytes long */)));
message.setMessageData(ByteString.copyFrom(clientInit.build().toByteArray()));
handshakeMessage = message.build().toByteArray();
try {
server.parseHandshakeMessage(handshakeMessage);
fail("Server did not catch nonce being too short in ClientInit");
} catch (AlertException e) {
// success
}
assertEquals(State.ERROR, server.getHandshakeState());
}
/**
* Test that server catches missing commitment in ClientInit message
*/
public void testServerCatchesMissingCommitmentInClientInit() throws Exception {
if (KeyEncoding.isLegacyCryptoRequired()) {
// this means we're running on an old SDK, which doesn't support the
// necessary crypto. Let's not test anything in this case.
return;
}
// Get ClientInit and modify the commitment
Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
byte[] handshakeMessage = client.getNextHandshakeMessage();
Ukey2Message.Builder message = Ukey2Message.newBuilder(
Ukey2Message.parseFrom(handshakeMessage));
Ukey2ClientInit clientInit =
Ukey2ClientInit.newBuilder(Ukey2ClientInit.parseFrom(message.getMessageData()))
.build();
Ukey2ClientInit.Builder badClientInit = Ukey2ClientInit.newBuilder()
.setVersion(clientInit.getVersion())
.setRandom(clientInit.getRandom());
for (CipherCommitment commitment : clientInit.getCipherCommitmentsList()) {
badClientInit.addCipherCommitments(commitment);
}
message.setMessageData(ByteString.copyFrom(badClientInit.build().toByteArray()));
handshakeMessage = message.build().toByteArray();
try {
server.parseHandshakeMessage(handshakeMessage);
fail("Server did not catch missing commitment in ClientInit");
} catch (AlertException e) {
// success
}
}
/**
* Test that client catches invalid version in ServerInit
*/
public void testServerInitUnsupportedVersion() throws Exception {
if (KeyEncoding.isLegacyCryptoRequired()) {
// this means we're running on an old SDK, which doesn't support the
// necessary crypto. Let's not test anything in this case.
return;
}
// Get ServerInit and modify the version to be too big
Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
byte[] handshakeMessage = client.getNextHandshakeMessage();
server.parseHandshakeMessage(handshakeMessage);
handshakeMessage = server.getNextHandshakeMessage();
Ukey2Message.Builder message = Ukey2Message.newBuilder(
Ukey2Message.parseFrom(handshakeMessage));
Ukey2ServerInit serverInit =
Ukey2ServerInit.newBuilder(Ukey2ServerInit.parseFrom(message.getMessageData()))
.setVersion(Ukey2Handshake.VERSION + 1)
.build();
message.setMessageData(ByteString.copyFrom(serverInit.toByteArray()));
handshakeMessage = message.build().toByteArray();
try {
client.parseHandshakeMessage(handshakeMessage);
fail("Client did not catch unsupported version (too big) in ServerInit");
} catch (AlertException e) {
// success
}
assertEquals(State.ERROR, client.getHandshakeState());
// Get ServerInit and modify the version to be too big
client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
handshakeMessage = client.getNextHandshakeMessage();
server.parseHandshakeMessage(handshakeMessage);
handshakeMessage = server.getNextHandshakeMessage();
message = Ukey2Message.newBuilder(Ukey2Message.parseFrom(handshakeMessage));
serverInit =
Ukey2ServerInit.newBuilder(Ukey2ServerInit.parseFrom(message.getMessageData()))
.setVersion(0 /* minimum version is 1 */)
.build();
message.setMessageData(ByteString.copyFrom(serverInit.toByteArray()));
handshakeMessage = message.build().toByteArray();
try {
client.parseHandshakeMessage(handshakeMessage);
fail("Client did not catch unsupported version (too small) in ServerInit");
} catch (AlertException e) {
// success
}
assertEquals(State.ERROR, client.getHandshakeState());
}
/**
* Tests that client catches wrong number of random bytes in ServerInit
*/
public void testWrongNonceLengthInServerInit() throws Exception {
if (KeyEncoding.isLegacyCryptoRequired()) {
// this means we're running on an old SDK, which doesn't support the
// necessary crypto. Let's not test anything in this case.
return;
}
// Get ServerInit and modify the nonce
Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
byte[] handshakeMessage = client.getNextHandshakeMessage();
server.parseHandshakeMessage(handshakeMessage);
handshakeMessage = server.getNextHandshakeMessage();
Ukey2Message.Builder message = Ukey2Message.newBuilder(
Ukey2Message.parseFrom(handshakeMessage));
Ukey2ServerInit.Builder serverInitBuilder =
Ukey2ServerInit.newBuilder(Ukey2ServerInit.parseFrom(message.getMessageData()));
Ukey2ServerInit serverInit = serverInitBuilder.setRandom(ByteString.copyFrom(Arrays.copyOf(
serverInitBuilder.getRandom().toByteArray(),
31 /* as per go/ukey2, nonces must be 32 bytes long */)))
.build();
message.setMessageData(ByteString.copyFrom(serverInit.toByteArray()));
handshakeMessage = message.build().toByteArray();
try {
client.parseHandshakeMessage(handshakeMessage);
fail("Client did not catch nonce being too short in ServerInit");
} catch (AlertException e) {
// success
}
assertEquals(State.ERROR, client.getHandshakeState());
}
/**
* Test that client catches missing or incorrect handshake cipher in serverInit
*/
public void testMissingOrIncorrectHandshakeCipherInServerInit() throws Exception {
if (KeyEncoding.isLegacyCryptoRequired()) {
// this means we're running on an old SDK, which doesn't support the
// necessary crypto. Let's not test anything in this case.
return;
}
// Get ServerInit
Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
byte[] handshakeMessage = client.getNextHandshakeMessage();
server.parseHandshakeMessage(handshakeMessage);
handshakeMessage = server.getNextHandshakeMessage();
Ukey2Message.Builder message = Ukey2Message.newBuilder(
Ukey2Message.parseFrom(handshakeMessage));
Ukey2ServerInit serverInit = Ukey2ServerInit.parseFrom(message.getMessageData());
// remove handshake cipher
Ukey2ServerInit badServerInit = Ukey2ServerInit.newBuilder()
.setPublicKey(serverInit.getPublicKey())
.setRandom(serverInit.getRandom())
.setVersion(serverInit.getVersion())
.build();
message.setMessageData(ByteString.copyFrom(badServerInit.toByteArray()));
handshakeMessage = message.build().toByteArray();
try {
client.parseHandshakeMessage(handshakeMessage);
fail("Client did not catch missing handshake cipher in ServerInit");
} catch (AlertException e) {
// success
}
assertEquals(State.ERROR, client.getHandshakeState());
// Get ServerInit
client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
handshakeMessage = client.getNextHandshakeMessage();
server.parseHandshakeMessage(handshakeMessage);
handshakeMessage = server.getNextHandshakeMessage();
message = Ukey2Message.newBuilder(Ukey2Message.parseFrom(handshakeMessage));
serverInit = Ukey2ServerInit.parseFrom(message.getMessageData());
// put in a bad handshake cipher
badServerInit = Ukey2ServerInit.newBuilder()
.setPublicKey(serverInit.getPublicKey())
.setRandom(serverInit.getRandom())
.setVersion(serverInit.getVersion())
.setHandshakeCipher(UkeyProto.Ukey2HandshakeCipher.RESERVED)
.build();
message.setMessageData(ByteString.copyFrom(badServerInit.toByteArray()));
handshakeMessage = message.build().toByteArray();
try {
client.parseHandshakeMessage(handshakeMessage);
fail("Client did not catch bad handshake cipher in ServerInit");
} catch (AlertException e) {
// success
}
assertEquals(State.ERROR, client.getHandshakeState());
}
/**
* Test that client catches missing or incorrect public key in serverInit
*/
public void testMissingOrIncorrectPublicKeyInServerInit() throws Exception {
if (KeyEncoding.isLegacyCryptoRequired()) {
// this means we're running on an old SDK, which doesn't support the
// necessary crypto. Let's not test anything in this case.
return;
}
// Get ServerInit
Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
byte[] handshakeMessage = client.getNextHandshakeMessage();
server.parseHandshakeMessage(handshakeMessage);
handshakeMessage = server.getNextHandshakeMessage();
Ukey2Message.Builder message = Ukey2Message.newBuilder(
Ukey2Message.parseFrom(handshakeMessage));
Ukey2ServerInit serverInit = Ukey2ServerInit.parseFrom(message.getMessageData());
// remove public key
Ukey2ServerInit badServerInit = Ukey2ServerInit.newBuilder()
.setRandom(serverInit.getRandom())
.setVersion(serverInit.getVersion())
.setHandshakeCipher(serverInit.getHandshakeCipher())
.build();
message.setMessageData(ByteString.copyFrom(badServerInit.toByteArray()));
handshakeMessage = message.build().toByteArray();
try {
client.parseHandshakeMessage(handshakeMessage);
fail("Client did not catch missing public key in ServerInit");
} catch (AlertException e) {
// success
}
assertEquals(State.ERROR, client.getHandshakeState());
// Get ServerInit
client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
handshakeMessage = client.getNextHandshakeMessage();
server.parseHandshakeMessage(handshakeMessage);
handshakeMessage = server.getNextHandshakeMessage();
message = Ukey2Message.newBuilder(
Ukey2Message.parseFrom(handshakeMessage));
serverInit = Ukey2ServerInit.parseFrom(message.getMessageData());
// put in a bad public key
badServerInit = Ukey2ServerInit.newBuilder()
.setPublicKey(ByteString.copyFrom(new byte[] {42, 12, 1}))
.setRandom(serverInit.getRandom())
.setVersion(serverInit.getVersion())
.setHandshakeCipher(serverInit.getHandshakeCipher())
.build();
message.setMessageData(ByteString.copyFrom(badServerInit.toByteArray()));
handshakeMessage = message.build().toByteArray();
try {
client.parseHandshakeMessage(handshakeMessage);
fail("Client did not catch bad public key in ServerInit");
} catch (AlertException e) {
// success
}
assertEquals(State.ERROR, client.getHandshakeState());
}
/**
* Test that client catches missing or incorrect public key in clientFinished
*/
public void testMissingOrIncorrectPublicKeyInClientFinished() throws Exception {
if (KeyEncoding.isLegacyCryptoRequired()) {
// this means we're running on an old SDK, which doesn't support the
// necessary crypto. Let's not test anything in this case.
return;
}
// Get ClientFinished
Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
byte[] handshakeMessage = client.getNextHandshakeMessage();
server.parseHandshakeMessage(handshakeMessage);
handshakeMessage = server.getNextHandshakeMessage();
client.parseHandshakeMessage(handshakeMessage);
handshakeMessage = client.getNextHandshakeMessage();
Ukey2Message.Builder message = Ukey2Message.newBuilder(
Ukey2Message.parseFrom(handshakeMessage));
// remove public key
Ukey2ClientFinished.Builder badClientFinished = Ukey2ClientFinished.newBuilder();
message.setMessageData(ByteString.copyFrom(badClientFinished.build().toByteArray()));
handshakeMessage = message.build().toByteArray();
try {
server.parseHandshakeMessage(handshakeMessage);
fail("Server did not catch missing public key in ClientFinished");
} catch (HandshakeException e) {
// success
}
assertEquals(State.ERROR, server.getHandshakeState());
// Get ClientFinished
client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
handshakeMessage = client.getNextHandshakeMessage();
server.parseHandshakeMessage(handshakeMessage);
handshakeMessage = server.getNextHandshakeMessage();
client.parseHandshakeMessage(handshakeMessage);
handshakeMessage = client.getNextHandshakeMessage();
message = Ukey2Message.newBuilder(Ukey2Message.parseFrom(handshakeMessage));
// remove public key
badClientFinished = Ukey2ClientFinished.newBuilder()
.setPublicKey(ByteString.copyFrom(new byte[] {42, 12, 1}));
message.setMessageData(ByteString.copyFrom(badClientFinished.build().toByteArray()));
handshakeMessage = message.build().toByteArray();
try {
server.parseHandshakeMessage(handshakeMessage);
fail("Server did not catch bad public key in ClientFinished");
} catch (HandshakeException e) {
// success
}
assertEquals(State.ERROR, server.getHandshakeState());
}
/**
* Tests that items (nonces, commitments, public keys) that should be random are at least
* different on every run.
*/
public void testRandomItemsDifferentOnEveryRun() throws Exception {
if (KeyEncoding.isLegacyCryptoRequired()) {
// this means we're running on an old SDK, which doesn't support the
// necessary crypto. Let's not test anything in this case.
return;
}
int numberOfRuns = 50;
// Search for collisions
Set<Integer> commitments = new HashSet<>(numberOfRuns);
Set<Integer> clientNonces = new HashSet<>(numberOfRuns);
Set<Integer> serverNonces = new HashSet<>(numberOfRuns);
Set<Integer> serverPublicKeys = new HashSet<>(numberOfRuns);
Set<Integer> clientPublicKeys = new HashSet<>(numberOfRuns);
for (int i = 0; i < numberOfRuns; i++) {
Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
byte[] handshakeMessage = client.getNextHandshakeMessage();
Ukey2Message message = Ukey2Message.parseFrom(handshakeMessage);
Ukey2ClientInit clientInit = Ukey2ClientInit.parseFrom(message.getMessageData());
server.parseHandshakeMessage(handshakeMessage);
handshakeMessage = server.getNextHandshakeMessage();
message = Ukey2Message.parseFrom(handshakeMessage);
Ukey2ServerInit serverInit = Ukey2ServerInit.parseFrom(message.getMessageData());
client.parseHandshakeMessage(handshakeMessage);
handshakeMessage = client.getNextHandshakeMessage();
message = Ukey2Message.parseFrom(handshakeMessage);
Ukey2ClientFinished clientFinished = Ukey2ClientFinished.parseFrom(message.getMessageData());
// Clean up to save some memory (b/32054837)
client = null;
server = null;
handshakeMessage = null;
message = null;
System.gc();
// ClientInit randomness
Integer nonceHash = Integer.valueOf(Arrays.hashCode(clientInit.getRandom().toByteArray()));
if (clientNonces.contains(nonceHash) || serverNonces.contains(nonceHash)) {
fail("Nonce in ClientINit has repeated!");
}
clientNonces.add(nonceHash);
Integer commitmentHash = 0;
for (CipherCommitment commitement : clientInit.getCipherCommitmentsList()) {
commitmentHash += Arrays.hashCode(commitement.toByteArray());
}
if (commitments.contains(nonceHash)) {
fail("Commitment has repeated!");
}
commitments.add(commitmentHash);
// ServerInit randomness
nonceHash = Integer.valueOf(Arrays.hashCode(serverInit.getRandom().toByteArray()));
if (serverNonces.contains(nonceHash) || clientNonces.contains(nonceHash)) {
fail("Nonce in ServerInit repeated!");
}
serverNonces.add(nonceHash);
Integer publicKeyHash =
Integer.valueOf(Arrays.hashCode(serverInit.getPublicKey().toByteArray()));
if (serverPublicKeys.contains(publicKeyHash) || clientPublicKeys.contains(publicKeyHash)) {
fail("Public Key in ServerInit repeated!");
}
serverPublicKeys.add(publicKeyHash);
// Client Finished randomness
publicKeyHash = Integer.valueOf(Arrays.hashCode(clientFinished.getPublicKey().toByteArray()));
if (serverPublicKeys.contains(publicKeyHash) || clientPublicKeys.contains(publicKeyHash)) {
fail("Public Key in ClientFinished repeated!");
}
clientPublicKeys.add(publicKeyHash);
}
}
/**
* Tests that {@link Ukey2Handshake#getVerificationString(int)} enforces sane verification string
* lengths.
*/
public void testGetVerificationEnforcesSaneLengths() throws Exception {
if (KeyEncoding.isLegacyCryptoRequired()) {
// this means we're running on an old SDK, which doesn't support the
// necessary crypto. Let's not test anything in this case.
return;
}
// Run the protocol
Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
byte[] handshakeMessage = client.getNextHandshakeMessage();
server.parseHandshakeMessage(handshakeMessage);
handshakeMessage = server.getNextHandshakeMessage();
client.parseHandshakeMessage(handshakeMessage);
handshakeMessage = client.getNextHandshakeMessage();
server.parseHandshakeMessage(handshakeMessage);
// Try to get too short verification string
try {
client.getVerificationString(0);
fail("Too short verification string allowed");
} catch (IllegalArgumentException e) {
// success
}
// Try to get too long verification string
try {
server.getVerificationString(MAX_AUTH_STRING_LENGTH + 1);
fail("Too long verification string allowed");
} catch (IllegalArgumentException e) {
// success
}
}
/**
* Asserts that the given client and server contexts are compatible
*/
private void assertContextsCompatible(
D2DConnectionContext clientContext, D2DConnectionContext serverContext) {
assertNotNull(clientContext);
assertNotNull(serverContext);
assertEquals(D2DConnectionContextV1.PROTOCOL_VERSION, clientContext.getProtocolVersion());
assertEquals(D2DConnectionContextV1.PROTOCOL_VERSION, serverContext.getProtocolVersion());
assertEquals(clientContext.getEncodeKey(), serverContext.getDecodeKey());
assertEquals(clientContext.getDecodeKey(), serverContext.getEncodeKey());
assertFalse(clientContext.getEncodeKey().equals(clientContext.getDecodeKey()));
assertEquals(0, clientContext.getSequenceNumberForEncoding());
assertEquals(0, clientContext.getSequenceNumberForDecoding());
assertEquals(0, serverContext.getSequenceNumberForEncoding());
assertEquals(0, serverContext.getSequenceNumberForDecoding());
}
}