Add encryption runner binary.
Test: atest EncryptionRunnerTest
Bug: 120617459
Change-Id: Ia604ec55f586d27c1b77053c03f1a9c1919a3fd9
diff --git a/EncryptionRunner/Android.bp b/EncryptionRunner/Android.bp
new file mode 100644
index 0000000..b02e6de
--- /dev/null
+++ b/EncryptionRunner/Android.bp
@@ -0,0 +1,49 @@
+// 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.
+
+android_library {
+ name: "EncryptionRunner",
+ min_sdk_version: "23",
+ product_variables: {
+ pdk: {
+ enabled: false,
+ },
+ },
+ srcs: [
+ "src/**/*.java",
+ ],
+}
+
+android_test {
+ name: "EncryptionRunnerTest",
+ min_sdk_version: "23",
+ srcs: [
+ "test/**/*.java",
+ ],
+ product_variables: {
+ pdk: {
+ enabled: false,
+ },
+ },
+ libs: [
+ "android.test.base",
+ "android.test.runner",
+ ],
+ static_libs: [
+ "androidx.test.rules",
+ "EncryptionRunner",
+ "junit",
+ "truth-prebuilt",
+ ],
+}
diff --git a/EncryptionRunner/AndroidManifest.xml b/EncryptionRunner/AndroidManifest.xml
new file mode 100644
index 0000000..8d7643e
--- /dev/null
+++ b/EncryptionRunner/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ package="android.car.encryptionrunner" >
+ <uses-sdk android:minSdkVersion="23" android:targetSdkVersion="23" />
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.car.encryptionrunner"
+ android:label="Encryption Runner Tests" />
+</manifest>
diff --git a/EncryptionRunner/AndroidTest.xml b/EncryptionRunner/AndroidTest.xml
new file mode 100644
index 0000000..b6cbbfa
--- /dev/null
+++ b/EncryptionRunner/AndroidTest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Runs Tests for EncryptionRunner.">
+ <option name="test-tag" value="EncryptionRunnerTest" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="EncryptionRunnerTest.apk" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.car.encryptionrunner" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="hidden-api-checks" value="false"/>
+ </test>
+</configuration>
diff --git a/EncryptionRunner/src/android/car/encryptionrunner/DummyEncryptionRunner.java b/EncryptionRunner/src/android/car/encryptionrunner/DummyEncryptionRunner.java
new file mode 100644
index 0000000..b08c985
--- /dev/null
+++ b/EncryptionRunner/src/android/car/encryptionrunner/DummyEncryptionRunner.java
@@ -0,0 +1,138 @@
+/*
+ * 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 android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * An ecnryption runnner that doesn't actually do encryption. Useful for debugging. Do not use in
+ * production environments.
+ */
+class DummyEncryptionRunner implements EncryptionRunner {
+
+ private static final String KEY = "key";
+ private static final String INIT = "init";
+ private static final String INIT_RESPONSE = "initResponse";
+ private static final String CLIENT_RESPONSE = "clientResponse";
+ public static final String PIN = "1234";
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({Mode.UNKNOWN, Mode.CLIENT, Mode.SERVER})
+ private @interface Mode {
+
+ int UNKNOWN = 0;
+ int CLIENT = 1;
+ int SERVER = 2;
+ }
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({State.UNKNOWN, State.WAITING_FOR_RESPONSE, State.FINISHED})
+ private @interface State {
+
+ int UNKNOWN = 0;
+ int WAITING_FOR_RESPONSE = 1;
+ int FINISHED = 2;
+ }
+
+ @Mode
+ private int mMode;
+ @State
+ private int mState;
+
+ @Override
+ public HandshakeMessage initHandshake() {
+ mMode = Mode.CLIENT;
+ mState = State.WAITING_FOR_RESPONSE;
+ return HandshakeMessage.newBuilder()
+ .setNextMessage(INIT.getBytes())
+ .build();
+ }
+
+ @Override
+ public HandshakeMessage respondToInitRequest(byte[] initializationRequest)
+ throws HandshakeException {
+ mMode = Mode.SERVER;
+ if (!new String(initializationRequest).equals(INIT)) {
+ throw new HandshakeException("Unexpected initialization request");
+ }
+ mState = State.WAITING_FOR_RESPONSE;
+ return HandshakeMessage.newBuilder()
+ .setNextMessage(INIT_RESPONSE.getBytes())
+ .build();
+ }
+
+ @Override
+ public HandshakeMessage continueHandshake(byte[] response) throws HandshakeException {
+ if (mState != State.WAITING_FOR_RESPONSE) {
+ throw new HandshakeException("not waiting for response but got one");
+ }
+ switch(mMode) {
+ case Mode.SERVER:
+ if (!CLIENT_RESPONSE.equals(new String(response))) {
+ throw new HandshakeException("unexpected response: " + new String(response));
+ }
+ mState = State.FINISHED;
+ return HandshakeMessage.newBuilder()
+ .setHandshakeComplete(true)
+ .setKey(new DummyKey())
+ .build();
+ case Mode.CLIENT:
+ if (!INIT_RESPONSE.equals(new String(response))) {
+ throw new HandshakeException("unexpected response: " + new String(response));
+ }
+ mState = State.FINISHED;
+ return HandshakeMessage.newBuilder()
+ .setHandshakeComplete(true)
+ .setKey(new DummyKey())
+ .setNextMessage(CLIENT_RESPONSE.getBytes())
+ .build();
+ default:
+ throw new IllegalStateException();
+ }
+ }
+
+ @Override
+ public Key keyOf(byte[] serialized) {
+ return new DummyKey();
+ }
+
+ @Override
+ public String getPin() {
+ return PIN;
+ }
+
+ @Override
+ public byte[] encryptData(Key key, byte[] data) {
+ return data;
+ }
+
+ @Override
+ public byte[] decryptData(Key key, byte[] encryptedData) {
+ return encryptedData;
+ }
+
+ private class DummyKey implements Key {
+
+ @Override
+ public byte[] asBytes() {
+ return KEY.getBytes();
+ }
+ }
+}
diff --git a/EncryptionRunner/src/android/car/encryptionrunner/EncryptionRunner.java b/EncryptionRunner/src/android/car/encryptionrunner/EncryptionRunner.java
new file mode 100644
index 0000000..7e7bd3e
--- /dev/null
+++ b/EncryptionRunner/src/android/car/encryptionrunner/EncryptionRunner.java
@@ -0,0 +1,87 @@
+/*
+ * 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 android.annotation.NonNull;
+
+/**
+ * A generalized interface that allows for generating shared secrets as well as encrypting
+ * messages.
+ */
+public interface EncryptionRunner {
+
+ /**
+ * Starts an encryption handshake.
+ *
+ * @return A handshake message with information about the handshake that is started.
+ */
+ HandshakeMessage initHandshake();
+
+ /**
+ * Starts an encryption handshake where the device that is being communicated with already
+ * initiated the request.
+ *
+ * @param initializationRequest the bytes that the other device sent over.
+ * @return a handshake message with information about the handshake.
+ * @throws HandshakeException if initialization request is invalid.
+ */
+ HandshakeMessage respondToInitRequest(@NonNull byte[] initializationRequest)
+ throws HandshakeException;
+
+ /**
+ * Continues a handshake after receiving another response from the connected device.
+ *
+ * @param response the response from the other device.
+ * @return a message that can be used to continue the handshake.
+ * @throws HandshakeException if unexpected bytes in response.
+ */
+ HandshakeMessage continueHandshake(@NonNull byte[] response) throws HandshakeException;
+
+ /**
+ * De seriliazes a previously serilized key generated by an instance of this encryption runner.
+ *
+ * @param serialized the serialized bytes of the key.
+ * @return the Key object used for encryption.
+ */
+ Key keyOf(@NonNull byte[] serialized);
+
+ /**
+ * A user visible shared pin. This pin can be used to verify that both devices that are
+ * communicating have agreed to the same key and will be shown to a user.
+ *
+ * @return the user visible pin.
+ */
+ String getPin();
+
+ /**
+ * Encrypts data using an encryption key.
+ *
+ * @param key the key used to encrypt the data.
+ * @param data the data to be encrypted
+ * @return the encrypted data.
+ */
+ byte[] encryptData(@NonNull Key key, @NonNull byte[] data);
+
+ /**
+ * Decrypts data using a specified key.
+ *
+ * @param key The key used to decrypt the data.
+ * @param encryptedData The encrypted data.
+ * @return decrypted data.
+ */
+ byte[] decryptData(@NonNull Key key, @NonNull byte[] encryptedData);
+}
diff --git a/EncryptionRunner/src/android/car/encryptionrunner/EncryptionRunnerFactory.java b/EncryptionRunner/src/android/car/encryptionrunner/EncryptionRunnerFactory.java
new file mode 100644
index 0000000..d975835
--- /dev/null
+++ b/EncryptionRunner/src/android/car/encryptionrunner/EncryptionRunnerFactory.java
@@ -0,0 +1,31 @@
+/*
+ * 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;
+
+/**
+ * Factory that creates encryption runner.
+ */
+public class EncryptionRunnerFactory {
+
+ /**
+ * Creates a new {@link EncryptionRunner} one that doesn't actually do encryption but is useful
+ * for testing.
+ */
+ static EncryptionRunner newDummyRunner() {
+ return new DummyEncryptionRunner();
+ }
+}
diff --git a/EncryptionRunner/src/android/car/encryptionrunner/HandshakeException.java b/EncryptionRunner/src/android/car/encryptionrunner/HandshakeException.java
new file mode 100644
index 0000000..02c873c
--- /dev/null
+++ b/EncryptionRunner/src/android/car/encryptionrunner/HandshakeException.java
@@ -0,0 +1,27 @@
+/*
+ * 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;
+
+/**
+ * Exception indicating an error during a Handshake of EncryptionRunner.
+ */
+public class HandshakeException extends Exception {
+
+ public HandshakeException(String message) {
+ super(message);
+ }
+}
diff --git a/EncryptionRunner/src/android/car/encryptionrunner/HandshakeMessage.java b/EncryptionRunner/src/android/car/encryptionrunner/HandshakeMessage.java
new file mode 100644
index 0000000..5286770
--- /dev/null
+++ b/EncryptionRunner/src/android/car/encryptionrunner/HandshakeMessage.java
@@ -0,0 +1,97 @@
+/*
+ * 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 android.annotation.Nullable;
+
+/**
+ * During an {@link EncryptionRunner} handshake process, these are the messages returned as part
+ * of each step.
+ */
+public class HandshakeMessage {
+
+ private final boolean mHandShakeComplete;
+ private final Key mKey;
+ private final byte[] mNextMessage;
+
+ /**
+ * @return Returns a builder for {@link HandshakeMessage}.
+ */
+ public static Builder newBuilder() {
+ return new Builder();
+ }
+
+ /**
+ * Use the builder;
+ */
+ private HandshakeMessage(
+ boolean handShakeComplete,
+ @Nullable Key key,
+ @Nullable byte[] nextMessage) {
+ mHandShakeComplete = handShakeComplete;
+ mKey = key;
+ mNextMessage = nextMessage;
+ }
+
+ /**
+ * Returns the next message to send in a handshake.
+ */
+ @Nullable
+ public byte[] getNextMessage() {
+ return mNextMessage == null ? null : mNextMessage.clone();
+ }
+
+ /**
+ * Returns true if the handshake is complete.
+ */
+ public boolean isHandShakeComplete() {
+ return mHandShakeComplete;
+ }
+
+ /**
+ * Returns the encryption key that can be used to encrypt data.
+ */
+ @Nullable
+ public Key getKey() {
+ return mKey;
+ }
+
+ static class Builder {
+ boolean mHandshakeComplete;
+ Key mKey;
+ byte[] mNextMessage;
+
+ Builder setHandshakeComplete(boolean handshakeComplete) {
+ mHandshakeComplete = handshakeComplete;
+ return this;
+ }
+
+ Builder setKey(Key key) {
+ mKey = key;
+ return this;
+ }
+
+ Builder setNextMessage(byte[] nextMessage) {
+ mNextMessage = nextMessage == null ? null : nextMessage.clone();
+ return this;
+ }
+
+ HandshakeMessage build() {
+ return new HandshakeMessage(mHandshakeComplete, mKey, mNextMessage);
+ }
+ }
+}
diff --git a/EncryptionRunner/src/android/car/encryptionrunner/Key.java b/EncryptionRunner/src/android/car/encryptionrunner/Key.java
new file mode 100644
index 0000000..97dd362
--- /dev/null
+++ b/EncryptionRunner/src/android/car/encryptionrunner/Key.java
@@ -0,0 +1,29 @@
+/*
+ * 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 android.annotation.NonNull;
+
+/**
+ * Represents a serializable encryption key.
+ */
+public interface Key {
+ /**
+ * Returns a serialized encryption key.
+ */
+ @NonNull byte[] asBytes();
+}
diff --git a/EncryptionRunner/test/android/car/encryptionrunner/EncryptionRunnerTest.java b/EncryptionRunner/test/android/car/encryptionrunner/EncryptionRunnerTest.java
new file mode 100644
index 0000000..d08b37a
--- /dev/null
+++ b/EncryptionRunner/test/android/car/encryptionrunner/EncryptionRunnerTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2018 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 androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class EncryptionRunnerTest {
+
+ private static final byte[] sTestData = "test data".getBytes();
+
+ @Test
+ public void happyFlow() throws Exception {
+ // This performs a handshake and then sends an "encrypted" message back and forth.
+ // Any encryption runner should be able to do this.
+ // Right now just using the dummy runner, when we have a real runner we can extract this
+ // method or just have the factory create a real runner.
+ EncryptionRunner clientRunner = EncryptionRunnerFactory.newDummyRunner();
+ EncryptionRunner serverRunner = EncryptionRunnerFactory.newDummyRunner();
+ HandshakeMessage initialClientMessage = clientRunner.initHandshake();
+
+ assertThat(initialClientMessage.isHandShakeComplete()).isFalse();
+ assertThat(initialClientMessage.getKey()).isNull();
+ assertThat(initialClientMessage.getNextMessage()).isNotNull();
+
+ HandshakeMessage initialServerMessage =
+ serverRunner.respondToInitRequest(initialClientMessage.getNextMessage());
+
+ assertThat(initialServerMessage.isHandShakeComplete()).isFalse();
+ assertThat(initialServerMessage.getKey()).isNull();
+ assertThat(initialServerMessage.getNextMessage()).isNotNull();
+
+ HandshakeMessage clientMessage =
+ clientRunner.continueHandshake(initialServerMessage.getNextMessage());
+
+ assertThat(clientMessage.isHandShakeComplete()).isTrue();
+ assertThat(clientMessage.getKey()).isNotNull();
+ assertThat(clientMessage.getNextMessage()).isNotNull();
+
+ HandshakeMessage serverMessage =
+ serverRunner.continueHandshake(clientMessage.getNextMessage());
+
+ assertThat(serverMessage.isHandShakeComplete()).isTrue();
+ assertThat(serverMessage.getKey()).isNotNull();
+ assertThat(serverMessage.getNextMessage()).isNull();
+
+ assertThat(serverRunner.decryptData(
+ serverMessage.getKey(),
+ clientRunner.encryptData(clientMessage.getKey(), sTestData))).isEqualTo(sTestData);
+ assertThat(clientRunner.decryptData(
+ clientMessage.getKey(),
+ serverRunner.encryptData(serverMessage.getKey(), sTestData))).isEqualTo(sTestData);
+ }
+
+}