summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Al Sutton <alsutton@google.com> 2019-08-20 15:43:30 +0100
committer Al Sutton <alsutton@google.com> 2019-09-06 11:12:23 +0100
commitc949517f4d7f29778e201875e57fc900b57f3cf3 (patch)
tree56fba2662aaeaef67e1d52be8baae4d7b58fb3d7
parentad52c6bc3a56f8ec7b7c32e38fe28659cbba7109 (diff)
Migrate KeyWrapUtils
Bring KeyWrapUtils in from GMSCore. This class relies heavily on a set of protobufs, so this CL includes the creation of the protobuf target support it and the inclusion of that target in the tests. Bug: 111386661 Test: atest BackupFrameworksServicesRoboTests Change-Id: I89e0c68a449f784b132780410d9de32824bb674a
-rw-r--r--packages/BackupEncryption/Android.bp9
-rw-r--r--packages/BackupEncryption/proto/wrapped_key.proto52
-rw-r--r--packages/BackupEncryption/src/com/android/server/backup/encryption/keys/KeyWrapUtils.java132
-rw-r--r--packages/BackupEncryption/test/robolectric/Android.bp1
-rw-r--r--packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/KeyWrapUtilsTest.java158
5 files changed, 351 insertions, 1 deletions
diff --git a/packages/BackupEncryption/Android.bp b/packages/BackupEncryption/Android.bp
index 50dbcdb1b4c7..9bcd677538eb 100644
--- a/packages/BackupEncryption/Android.bp
+++ b/packages/BackupEncryption/Android.bp
@@ -17,8 +17,15 @@
android_app {
name: "BackupEncryption",
srcs: ["src/**/*.java"],
+ libs: ["backup-encryption-protos"],
optimize: { enabled: false },
platform_apis: true,
certificate: "platform",
privileged: true,
-} \ No newline at end of file
+}
+
+java_library {
+ name: "backup-encryption-protos",
+ proto: { type: "nano" },
+ srcs: ["proto/**/*.proto"],
+}
diff --git a/packages/BackupEncryption/proto/wrapped_key.proto b/packages/BackupEncryption/proto/wrapped_key.proto
new file mode 100644
index 000000000000..817b7b40d606
--- /dev/null
+++ b/packages/BackupEncryption/proto/wrapped_key.proto
@@ -0,0 +1,52 @@
+syntax = "proto2";
+
+package android_backup_crypto;
+
+option java_package = "com.android.server.backup.encryption.protos";
+option java_outer_classname = "WrappedKeyProto";
+
+// Metadata associated with a tertiary key.
+message KeyMetadata {
+ // Type of Cipher algorithm the key is used for.
+ enum Type {
+ UNKNOWN = 0;
+ // No padding. Uses 12-byte nonce. Tag length 16 bytes.
+ AES_256_GCM = 1;
+ }
+
+ // What kind of Cipher algorithm the key is used for. We assume at the moment
+ // that this will always be AES_256_GCM and throw if this is not the case.
+ // Provided here for forwards compatibility in case at some point we need to
+ // change Cipher algorithm.
+ optional Type type = 1;
+}
+
+// An encrypted tertiary key.
+message WrappedKey {
+ // The Cipher with which the key was encrypted.
+ enum WrapAlgorithm {
+ UNKNOWN = 0;
+ // No padding. Uses 16-byte nonce (see nonce field). Tag length 16 bytes.
+ // The nonce is 16-bytes as this is wrapped with a key in AndroidKeyStore.
+ // AndroidKeyStore requires that it generates the IV, and it generates a
+ // 16-byte IV for you. You CANNOT provide your own IV.
+ AES_256_GCM = 1;
+ }
+
+ // Cipher algorithm used to wrap the key. We assume at the moment that this
+ // is always AES_256_GC and throw if this is not the case. Provided here for
+ // forwards compatibility if at some point we need to change Cipher algorithm.
+ optional WrapAlgorithm wrap_algorithm = 1;
+
+ // The nonce used to initialize the Cipher in AES/256/GCM mode.
+ optional bytes nonce = 2;
+
+ // The encrypted bytes of the key material.
+ optional bytes key = 3;
+
+ // Associated key metadata.
+ optional KeyMetadata metadata = 4;
+
+ // Deprecated field; Do not use
+ reserved 5;
+}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/KeyWrapUtils.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/KeyWrapUtils.java
new file mode 100644
index 000000000000..a043c1fe687f
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/KeyWrapUtils.java
@@ -0,0 +1,132 @@
+/*
+ * 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 com.android.server.backup.encryption.keys;
+
+import com.android.server.backup.encryption.protos.nano.WrappedKeyProto;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.util.Locale;
+
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.GCMParameterSpec;
+
+/** Utility functions for wrapping and unwrapping tertiary keys. */
+public class KeyWrapUtils {
+ private static final String AES_GCM_MODE = "AES/GCM/NoPadding";
+ private static final int GCM_TAG_LENGTH_BYTES = 16;
+ private static final int BITS_PER_BYTE = 8;
+ private static final int GCM_TAG_LENGTH_BITS = GCM_TAG_LENGTH_BYTES * BITS_PER_BYTE;
+ private static final String KEY_ALGORITHM = "AES";
+
+ /**
+ * Uses the secondary key to unwrap the wrapped tertiary key.
+ *
+ * @param secondaryKey The secondary key used to wrap the tertiary key.
+ * @param wrappedKey The wrapped tertiary key.
+ * @return The unwrapped tertiary key.
+ * @throws InvalidKeyException if the provided secondary key cannot unwrap the tertiary key.
+ */
+ public static SecretKey unwrap(SecretKey secondaryKey, WrappedKeyProto.WrappedKey wrappedKey)
+ throws InvalidKeyException, NoSuchAlgorithmException,
+ InvalidAlgorithmParameterException, NoSuchPaddingException {
+ if (wrappedKey.wrapAlgorithm != WrappedKeyProto.WrappedKey.AES_256_GCM) {
+ throw new InvalidKeyException(
+ String.format(
+ Locale.US,
+ "Could not unwrap key wrapped with %s algorithm",
+ wrappedKey.wrapAlgorithm));
+ }
+
+ if (wrappedKey.metadata == null) {
+ throw new InvalidKeyException("Metadata missing from wrapped tertiary key.");
+ }
+
+ if (wrappedKey.metadata.type != WrappedKeyProto.KeyMetadata.AES_256_GCM) {
+ throw new InvalidKeyException(
+ String.format(
+ Locale.US,
+ "Wrapped key was unexpected %s algorithm. Only support"
+ + " AES/GCM/NoPadding.",
+ wrappedKey.metadata.type));
+ }
+
+ Cipher cipher = getCipher();
+
+ cipher.init(
+ Cipher.UNWRAP_MODE,
+ secondaryKey,
+ new GCMParameterSpec(GCM_TAG_LENGTH_BITS, wrappedKey.nonce));
+
+ return (SecretKey) cipher.unwrap(wrappedKey.key, KEY_ALGORITHM, Cipher.SECRET_KEY);
+ }
+
+ /**
+ * Wraps the tertiary key with the secondary key.
+ *
+ * @param secondaryKey The secondary key to use for wrapping.
+ * @param tertiaryKey The key to wrap.
+ * @return The wrapped key.
+ * @throws InvalidKeyException if the key is not good for wrapping.
+ * @throws IllegalBlockSizeException if there is an issue wrapping.
+ */
+ public static WrappedKeyProto.WrappedKey wrap(SecretKey secondaryKey, SecretKey tertiaryKey)
+ throws InvalidKeyException, IllegalBlockSizeException, NoSuchAlgorithmException,
+ NoSuchPaddingException {
+ Cipher cipher = getCipher();
+ cipher.init(Cipher.WRAP_MODE, secondaryKey);
+
+ WrappedKeyProto.WrappedKey wrappedKey = new WrappedKeyProto.WrappedKey();
+ wrappedKey.key = cipher.wrap(tertiaryKey);
+ wrappedKey.nonce = cipher.getIV();
+ wrappedKey.wrapAlgorithm = WrappedKeyProto.WrappedKey.AES_256_GCM;
+ wrappedKey.metadata = new WrappedKeyProto.KeyMetadata();
+ wrappedKey.metadata.type = WrappedKeyProto.KeyMetadata.AES_256_GCM;
+ return wrappedKey;
+ }
+
+ /**
+ * Rewraps a tertiary key with a new secondary key.
+ *
+ * @param oldSecondaryKey The old secondary key, used to unwrap the tertiary key.
+ * @param newSecondaryKey The new secondary key, used to rewrap the tertiary key.
+ * @param tertiaryKey The tertiary key, wrapped by {@code oldSecondaryKey}.
+ * @return The tertiary key, wrapped by {@code newSecondaryKey}.
+ * @throws InvalidKeyException if the key is not good for wrapping or unwrapping.
+ * @throws IllegalBlockSizeException if there is an issue wrapping.
+ */
+ public static WrappedKeyProto.WrappedKey rewrap(
+ SecretKey oldSecondaryKey,
+ SecretKey newSecondaryKey,
+ WrappedKeyProto.WrappedKey tertiaryKey)
+ throws InvalidKeyException, IllegalBlockSizeException,
+ InvalidAlgorithmParameterException, NoSuchAlgorithmException,
+ NoSuchPaddingException {
+ return wrap(newSecondaryKey, unwrap(oldSecondaryKey, tertiaryKey));
+ }
+
+ private static Cipher getCipher() throws NoSuchPaddingException, NoSuchAlgorithmException {
+ return Cipher.getInstance(AES_GCM_MODE);
+ }
+
+ // Statics only
+ private KeyWrapUtils() {}
+}
diff --git a/packages/BackupEncryption/test/robolectric/Android.bp b/packages/BackupEncryption/test/robolectric/Android.bp
index 6d1abbb61a8e..3376ec97e02f 100644
--- a/packages/BackupEncryption/test/robolectric/Android.bp
+++ b/packages/BackupEncryption/test/robolectric/Android.bp
@@ -20,6 +20,7 @@ android_robolectric_test {
],
java_resource_dirs: ["config"],
libs: [
+ "backup-encryption-protos",
"platform-test-annotations",
"testng",
],
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/KeyWrapUtilsTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/KeyWrapUtilsTest.java
new file mode 100644
index 000000000000..b60740421ad5
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/KeyWrapUtilsTest.java
@@ -0,0 +1,158 @@
+/*
+ * 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 com.android.server.backup.encryption.keys;
+
+import static com.android.server.backup.testing.CryptoTestUtils.generateAesKey;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.platform.test.annotations.Presubmit;
+
+import com.android.server.backup.encryption.protos.nano.WrappedKeyProto;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import java.security.InvalidKeyException;
+
+import javax.crypto.SecretKey;
+
+/** Key wrapping tests */
+@RunWith(RobolectricTestRunner.class)
+@Presubmit
+public class KeyWrapUtilsTest {
+ private static final int KEY_SIZE_BITS = 256;
+ private static final int BITS_PER_BYTE = 8;
+ private static final int GCM_NONCE_LENGTH_BYTES = 16;
+ private static final int GCM_TAG_LENGTH_BYTES = 16;
+
+ /** Test a wrapped key has metadata */
+ @Test
+ public void wrap_addsMetadata() throws Exception {
+ WrappedKeyProto.WrappedKey wrappedKey =
+ KeyWrapUtils.wrap(
+ /*secondaryKey=*/ generateAesKey(), /*tertiaryKey=*/ generateAesKey());
+ assertThat(wrappedKey.metadata).isNotNull();
+ assertThat(wrappedKey.metadata.type).isEqualTo(WrappedKeyProto.KeyMetadata.AES_256_GCM);
+ }
+
+ /** Test a wrapped key has an algorithm specified */
+ @Test
+ public void wrap_addsWrapAlgorithm() throws Exception {
+ WrappedKeyProto.WrappedKey wrappedKey =
+ KeyWrapUtils.wrap(
+ /*secondaryKey=*/ generateAesKey(), /*tertiaryKey=*/ generateAesKey());
+ assertThat(wrappedKey.wrapAlgorithm).isEqualTo(WrappedKeyProto.WrappedKey.AES_256_GCM);
+ }
+
+ /** Test a wrapped key haas an nonce of the right length */
+ @Test
+ public void wrap_addsNonceOfAppropriateLength() throws Exception {
+ WrappedKeyProto.WrappedKey wrappedKey =
+ KeyWrapUtils.wrap(
+ /*secondaryKey=*/ generateAesKey(), /*tertiaryKey=*/ generateAesKey());
+ assertThat(wrappedKey.nonce).hasLength(GCM_NONCE_LENGTH_BYTES);
+ }
+
+ /** Test a wrapped key has a key of the right length */
+ @Test
+ public void wrap_addsTagOfAppropriateLength() throws Exception {
+ WrappedKeyProto.WrappedKey wrappedKey =
+ KeyWrapUtils.wrap(
+ /*secondaryKey=*/ generateAesKey(), /*tertiaryKey=*/ generateAesKey());
+ assertThat(wrappedKey.key).hasLength(KEY_SIZE_BITS / BITS_PER_BYTE + GCM_TAG_LENGTH_BYTES);
+ }
+
+ /** Ensure a key can be wrapped and unwrapped again */
+ @Test
+ public void unwrap_unwrapsEncryptedKey() throws Exception {
+ SecretKey secondaryKey = generateAesKey();
+ SecretKey tertiaryKey = generateAesKey();
+ WrappedKeyProto.WrappedKey wrappedKey = KeyWrapUtils.wrap(secondaryKey, tertiaryKey);
+ SecretKey unwrappedKey = KeyWrapUtils.unwrap(secondaryKey, wrappedKey);
+ assertThat(unwrappedKey).isEqualTo(tertiaryKey);
+ }
+
+ /** Ensure the unwrap method rejects keys with bad algorithms */
+ @Test(expected = InvalidKeyException.class)
+ public void unwrap_throwsForBadWrapAlgorithm() throws Exception {
+ SecretKey secondaryKey = generateAesKey();
+ WrappedKeyProto.WrappedKey wrappedKey = KeyWrapUtils.wrap(secondaryKey, generateAesKey());
+ wrappedKey.wrapAlgorithm = WrappedKeyProto.WrappedKey.UNKNOWN;
+
+ KeyWrapUtils.unwrap(secondaryKey, wrappedKey);
+ }
+
+ /** Ensure the unwrap method rejects metadata indicating the encryption type is unknown */
+ @Test(expected = InvalidKeyException.class)
+ public void unwrap_throwsForBadKeyAlgorithm() throws Exception {
+ SecretKey secondaryKey = generateAesKey();
+ WrappedKeyProto.WrappedKey wrappedKey = KeyWrapUtils.wrap(secondaryKey, generateAesKey());
+ wrappedKey.metadata.type = WrappedKeyProto.KeyMetadata.UNKNOWN;
+
+ KeyWrapUtils.unwrap(secondaryKey, wrappedKey);
+ }
+
+ /** Ensure the unwrap method rejects wrapped keys missing the metadata */
+ @Test(expected = InvalidKeyException.class)
+ public void unwrap_throwsForMissingMetadata() throws Exception {
+ SecretKey secondaryKey = generateAesKey();
+ WrappedKeyProto.WrappedKey wrappedKey = KeyWrapUtils.wrap(secondaryKey, generateAesKey());
+ wrappedKey.metadata = null;
+
+ KeyWrapUtils.unwrap(secondaryKey, wrappedKey);
+ }
+
+ /** Ensure unwrap rejects invalid secondary keys */
+ @Test(expected = InvalidKeyException.class)
+ public void unwrap_throwsForBadSecondaryKey() throws Exception {
+ WrappedKeyProto.WrappedKey wrappedKey =
+ KeyWrapUtils.wrap(
+ /*secondaryKey=*/ generateAesKey(), /*tertiaryKey=*/ generateAesKey());
+
+ KeyWrapUtils.unwrap(generateAesKey(), wrappedKey);
+ }
+
+ /** Ensure rewrap can rewrap keys */
+ @Test
+ public void rewrap_canBeUnwrappedWithNewSecondaryKey() throws Exception {
+ SecretKey tertiaryKey = generateAesKey();
+ SecretKey oldSecondaryKey = generateAesKey();
+ SecretKey newSecondaryKey = generateAesKey();
+ WrappedKeyProto.WrappedKey wrappedWithOld = KeyWrapUtils.wrap(oldSecondaryKey, tertiaryKey);
+
+ WrappedKeyProto.WrappedKey wrappedWithNew =
+ KeyWrapUtils.rewrap(oldSecondaryKey, newSecondaryKey, wrappedWithOld);
+
+ assertThat(KeyWrapUtils.unwrap(newSecondaryKey, wrappedWithNew)).isEqualTo(tertiaryKey);
+ }
+
+ /** Ensure rewrap doesn't create something decryptable by an old key */
+ @Test(expected = InvalidKeyException.class)
+ public void rewrap_cannotBeUnwrappedWithOldSecondaryKey() throws Exception {
+ SecretKey tertiaryKey = generateAesKey();
+ SecretKey oldSecondaryKey = generateAesKey();
+ SecretKey newSecondaryKey = generateAesKey();
+ WrappedKeyProto.WrappedKey wrappedWithOld = KeyWrapUtils.wrap(oldSecondaryKey, tertiaryKey);
+
+ WrappedKeyProto.WrappedKey wrappedWithNew =
+ KeyWrapUtils.rewrap(oldSecondaryKey, newSecondaryKey, wrappedWithOld);
+
+ KeyWrapUtils.unwrap(oldSecondaryKey, wrappedWithNew);
+ }
+}