blob: 38802e4062e61ac62a7e05b9bb73f160e31afd2c [file] [log] [blame]
/*
* Copyright (C) 2019 The Android Open Source Project
*
* 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
*
* http://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 android.car.encryptionrunner;
import static com.google.common.truth.Truth.assertThat;
import android.util.Log;
import junit.framework.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
public class EncryptionRunnerTest {
private Key mClientKey;
private Key mServerKey;
private byte[] mData = "testData".getBytes();
private interface RunnerFactory {
EncryptionRunner newRunner();
}
private interface HandshakeVerifier {
void verifyHandshake(EncryptionRunner clientRunner, EncryptionRunner serverRunner)
throws Exception;
}
@Test
public void happyFlow_dummyRunner() throws Exception {
verifyRunners(EncryptionRunnerFactory::newDummyRunner,
EncryptionRunnerTest::verifyHandshake);
}
@Test
public void happyFlow_ukey2Runner() throws Exception {
verifyRunners(EncryptionRunnerTest::newRunner, EncryptionRunnerTest::verifyHandshake);
}
@Test
public void happyFlow_oobUkey2Runner() throws Exception {
verifyRunners(EncryptionRunnerTest::newOobRunner, EncryptionRunnerTest::verifyOobHandshake);
}
@Test
public void happyFlow_dummyRunner_reconnect() throws Exception {
setUpFirstConnection(EncryptionRunnerFactory::newDummyRunner,
EncryptionRunnerTest::verifyHandshake);
verifyRunnersReconnect(EncryptionRunnerFactory::newDummyRunner);
}
@Test
public void happyFlow_uKey2Runner_reconnect() throws Exception {
setUpFirstConnection(EncryptionRunnerTest::newRunner,
EncryptionRunnerTest::verifyHandshake);
verifyRunnersReconnect(EncryptionRunnerTest::newRunner);
}
@Test
public void happyFlow_oobUey2Runner_reconnect() throws Exception {
setUpFirstConnection(EncryptionRunnerTest::newOobRunner,
EncryptionRunnerTest::verifyOobHandshake);
verifyRunnersReconnect(EncryptionRunnerTest::newOobRunner);
}
@Test
public void uKey2Runner_reconnect_encrypt_and_decrypt() throws Exception {
setUpFirstConnection(EncryptionRunnerTest::newRunner,
EncryptionRunnerTest::verifyHandshake);
setUpReconnection(EncryptionRunnerTest::newRunner, EncryptionRunnerTest::verifyHandshake);
assertThat(mClientKey.decryptData(mServerKey.encryptData(mData))).isEqualTo(mData);
}
@Test
public void dummyRunner_reconnect_encrypt_and_decrypt() throws Exception {
setUpFirstConnection(EncryptionRunnerFactory::newDummyRunner,
EncryptionRunnerTest::verifyHandshake);
setUpReconnection(EncryptionRunnerFactory::newDummyRunner,
EncryptionRunnerTest::verifyHandshake);
assertThat(mClientKey.decryptData(mServerKey.encryptData(mData))).isEqualTo(mData);
}
@Test
public void oobUkey2Runner_reconnect_encrypt_and_decrypt() throws Exception {
setUpFirstConnection(EncryptionRunnerTest::newOobRunner,
EncryptionRunnerTest::verifyOobHandshake);
setUpReconnection(EncryptionRunnerTest::newOobRunner,
EncryptionRunnerTest::verifyOobHandshake);
assertThat(mClientKey.decryptData(mServerKey.encryptData(mData))).isEqualTo(mData);
}
private static EncryptionRunner newRunner() {
return EncryptionRunnerFactory.newRunner(
EncryptionRunnerFactory.EncryptionRunnerType.UKEY2);
}
private static EncryptionRunner newOobRunner() {
return EncryptionRunnerFactory.newRunner(
EncryptionRunnerFactory.EncryptionRunnerType.OOB_UKEY2);
}
private void setUpFirstConnection(RunnerFactory runnerFactory,
HandshakeVerifier handshakeVerifier) throws Exception {
EncryptionRunner clientRunner = runnerFactory.newRunner();
EncryptionRunner serverRunner = runnerFactory.newRunner();
handshakeVerifier.verifyHandshake(clientRunner, serverRunner);
HandshakeMessage finalServerMessage = serverRunner.verifyPin();
HandshakeMessage finalClientMessage = clientRunner.verifyPin();
mServerKey = finalServerMessage.getKey();
mClientKey = finalClientMessage.getKey();
}
private void setUpReconnection(RunnerFactory runnerFactory, HandshakeVerifier handshakeVerifier)
throws Exception {
setUpFirstConnection(runnerFactory, handshakeVerifier);
EncryptionRunner clientRunner = runnerFactory.newRunner();
EncryptionRunner serverRunner = runnerFactory.newRunner();
verifyHandshakeReconnect(clientRunner, serverRunner);
HandshakeMessage nextClientMessage =
clientRunner.initReconnectAuthentication(mClientKey.asBytes());
HandshakeMessage finalServerMessage = serverRunner.authenticateReconnection(
nextClientMessage.getNextMessage(), mServerKey.asBytes());
HandshakeMessage finalClientMessage = clientRunner.authenticateReconnection(
finalServerMessage.getNextMessage(), mServerKey.asBytes());
mServerKey = finalServerMessage.getKey();
mClientKey = finalClientMessage.getKey();
}
/**
* Runs through a happy flow of encryption runners and verifies that they behave as expected.
* Some * of the test is implementation specific because the interface doesn't specify how many
* round * trips may be needed but this test makes assumptions( i.e. white box testing).
*/
private void verifyRunners(RunnerFactory runnerFactory, HandshakeVerifier handshakeVerifier)
throws Exception {
EncryptionRunner clientRunner = runnerFactory.newRunner();
EncryptionRunner serverRunner = runnerFactory.newRunner();
handshakeVerifier.verifyHandshake(clientRunner, serverRunner);
HandshakeMessage finalServerMessage = serverRunner.verifyPin();
assertThat(finalServerMessage.getHandshakeState())
.isEqualTo(HandshakeMessage.HandshakeState.FINISHED);
assertThat(finalServerMessage.getKey()).isNotNull();
assertThat(finalServerMessage.getNextMessage()).isNull();
HandshakeMessage finalClientMessage = clientRunner.verifyPin();
assertThat(finalClientMessage.getHandshakeState())
.isEqualTo(HandshakeMessage.HandshakeState.FINISHED);
assertThat(finalClientMessage.getKey()).isNotNull();
assertThat(finalClientMessage.getNextMessage()).isNull();
assertThat(finalServerMessage.getKey()
.decryptData(finalClientMessage.getKey().encryptData(mData)))
.isEqualTo(mData);
assertThat(finalClientMessage.getKey()
.decryptData(finalServerMessage.getKey().encryptData(mData)))
.isEqualTo(mData);
}
private void verifyRunnersReconnect(RunnerFactory runnerFactory) throws Exception {
EncryptionRunner clientRunner = runnerFactory.newRunner();
EncryptionRunner serverRunner = runnerFactory.newRunner();
verifyHandshakeReconnect(clientRunner, serverRunner);
HandshakeMessage nextClientMessage =
clientRunner.initReconnectAuthentication(mClientKey.asBytes());
assertThat(nextClientMessage.getHandshakeState())
.isEqualTo(HandshakeMessage.HandshakeState.RESUMING_SESSION);
assertThat(nextClientMessage.getKey()).isNull();
assertThat(nextClientMessage.getNextMessage()).isNotNull();
HandshakeMessage finalServerMessage = serverRunner.authenticateReconnection(
nextClientMessage.getNextMessage(), mServerKey.asBytes());
assertThat(finalServerMessage.getHandshakeState())
.isEqualTo(HandshakeMessage.HandshakeState.FINISHED);
assertThat(finalServerMessage.getKey()).isNotNull();
assertThat(finalServerMessage.getNextMessage()).isNotNull();
HandshakeMessage finalClientMessage = clientRunner.authenticateReconnection(
finalServerMessage.getNextMessage(), mServerKey.asBytes());
assertThat(finalClientMessage.getHandshakeState())
.isEqualTo(HandshakeMessage.HandshakeState.FINISHED);
assertThat(finalClientMessage.getKey()).isNotNull();
assertThat(finalClientMessage.getNextMessage()).isNull();
}
private static void verifyHandshake(EncryptionRunner clientRunner,
EncryptionRunner serverRunner)
throws Exception {
HandshakeMessage initialClientMessage = clientRunner.initHandshake();
assertThat(initialClientMessage.getHandshakeState())
.isEqualTo(HandshakeMessage.HandshakeState.IN_PROGRESS);
assertThat(initialClientMessage.getKey()).isNull();
assertThat(initialClientMessage.getNextMessage()).isNotNull();
// This and the following similar log statements are useful when running this test to
// find the payload sizes.
Log.i(EncryptionRunner.TAG,
"initial client size:" + initialClientMessage.getNextMessage().length);
HandshakeMessage initialServerMessage =
serverRunner.respondToInitRequest(initialClientMessage.getNextMessage());
assertThat(initialServerMessage.getHandshakeState())
.isEqualTo(HandshakeMessage.HandshakeState.IN_PROGRESS);
assertThat(initialServerMessage.getKey()).isNull();
assertThat(initialServerMessage.getNextMessage()).isNotNull();
Log.i(EncryptionRunner.TAG,
"initial server message size:" + initialServerMessage.getNextMessage().length);
HandshakeMessage clientMessage =
clientRunner.continueHandshake(initialServerMessage.getNextMessage());
assertThat(clientMessage.getHandshakeState())
.isEqualTo(HandshakeMessage.HandshakeState.VERIFICATION_NEEDED);
assertThat(clientMessage.getKey()).isNull();
assertThat(clientMessage.getVerificationCode()).isNotEmpty();
assertThat(clientMessage.getNextMessage()).isNotNull();
Log.i(EncryptionRunner.TAG,
"second client message size:" + clientMessage.getNextMessage().length);
HandshakeMessage serverMessage =
serverRunner.continueHandshake(clientMessage.getNextMessage());
assertThat(serverMessage.getHandshakeState())
.isEqualTo(HandshakeMessage.HandshakeState.VERIFICATION_NEEDED);
assertThat(serverMessage.getKey()).isNull();
assertThat(serverMessage.getNextMessage()).isNull();
Log.i(EncryptionRunner.TAG,
"last server message size:" + clientMessage.getNextMessage().length);
}
private static void verifyOobHandshake(
EncryptionRunner clientRunner, EncryptionRunner serverRunner) throws Exception {
HandshakeMessage initialClientMessage = clientRunner.initHandshake();
assertThat(initialClientMessage.getHandshakeState())
.isEqualTo(HandshakeMessage.HandshakeState.IN_PROGRESS);
assertThat(initialClientMessage.getKey()).isNull();
assertThat(initialClientMessage.getNextMessage()).isNotNull();
HandshakeMessage initialServerMessage =
serverRunner.respondToInitRequest(initialClientMessage.getNextMessage());
assertThat(initialServerMessage.getHandshakeState())
.isEqualTo(HandshakeMessage.HandshakeState.IN_PROGRESS);
assertThat(initialServerMessage.getKey()).isNull();
assertThat(initialServerMessage.getNextMessage()).isNotNull();
HandshakeMessage clientMessage =
clientRunner.continueHandshake(initialServerMessage.getNextMessage());
assertThat(clientMessage.getHandshakeState())
.isEqualTo(HandshakeMessage.HandshakeState.OOB_VERIFICATION_NEEDED);
assertThat(clientMessage.getKey()).isNull();
assertThat(clientMessage.getOobVerificationCode()).isNotEmpty();
assertThat(clientMessage.getNextMessage()).isNotNull();
HandshakeMessage serverMessage = serverRunner.continueHandshake(
clientMessage.getNextMessage());
assertThat(serverMessage.getHandshakeState())
.isEqualTo(HandshakeMessage.HandshakeState.OOB_VERIFICATION_NEEDED);
assertThat(serverMessage.getKey()).isNull();
assertThat(serverMessage.getNextMessage()).isNull();
}
private void verifyHandshakeReconnect(
EncryptionRunner clientRunner, EncryptionRunner serverRunner)
throws HandshakeException {
clientRunner.setIsReconnect(true);
serverRunner.setIsReconnect(true);
HandshakeMessage initialClientMessage = clientRunner.initHandshake();
assertThat(initialClientMessage.getHandshakeState())
.isEqualTo(HandshakeMessage.HandshakeState.IN_PROGRESS);
assertThat(initialClientMessage.getKey()).isNull();
assertThat(initialClientMessage.getNextMessage()).isNotNull();
// This and the following similar log statements are useful when running this test to
// find the payload sizes.
Log.i(EncryptionRunner.TAG,
"initial client size:" + initialClientMessage.getNextMessage().length);
HandshakeMessage initialServerMessage =
serverRunner.respondToInitRequest(initialClientMessage.getNextMessage());
assertThat(initialServerMessage.getHandshakeState())
.isEqualTo(HandshakeMessage.HandshakeState.IN_PROGRESS);
assertThat(initialServerMessage.getKey()).isNull();
assertThat(initialServerMessage.getNextMessage()).isNotNull();
Log.i(EncryptionRunner.TAG,
"initial server message size:" + initialServerMessage.getNextMessage().length);
HandshakeMessage clientMessage =
clientRunner.continueHandshake(initialServerMessage.getNextMessage());
assertThat(clientMessage.getHandshakeState())
.isEqualTo(HandshakeMessage.HandshakeState.RESUMING_SESSION);
assertThat(clientMessage.getKey()).isNull();
assertThat(clientMessage.getNextMessage()).isNotNull();
HandshakeMessage serverMessage =
serverRunner.continueHandshake(clientMessage.getNextMessage());
assertThat(serverMessage.getHandshakeState())
.isEqualTo(HandshakeMessage.HandshakeState.RESUMING_SESSION);
assertThat(serverMessage.getKey()).isNull();
}
@Test
public void invalidPin_ukey2() throws Exception {
invalidPinTest(EncryptionRunnerTest::newRunner, EncryptionRunnerTest::verifyHandshake);
}
@Test
public void invalidPin_dummy() throws Exception {
invalidPinTest(EncryptionRunnerFactory::newDummyRunner,
EncryptionRunnerTest::verifyHandshake);
}
@Test
public void invalidPin_oobUkey2() throws Exception {
invalidPinTest(EncryptionRunnerTest::newOobRunner,
EncryptionRunnerTest::verifyOobHandshake);
}
private void invalidPinTest(RunnerFactory runnerFactory, HandshakeVerifier handshakeVerifier)
throws Exception {
EncryptionRunner clientRunner = runnerFactory.newRunner();
EncryptionRunner serverRunner = runnerFactory.newRunner();
handshakeVerifier.verifyHandshake(clientRunner, serverRunner);
clientRunner.invalidPin();
serverRunner.invalidPin();
try {
clientRunner.verifyPin();
Assert.fail();
} catch (Exception ignored) {
// pass
}
try {
serverRunner.verifyPin();
Assert.fail();
} catch (Exception ignored) {
// pass
}
}
}