diff options
author | 2019-08-20 15:43:30 +0100 | |
---|---|---|
committer | 2019-09-06 11:12:23 +0100 | |
commit | c949517f4d7f29778e201875e57fc900b57f3cf3 (patch) | |
tree | 56fba2662aaeaef67e1d52be8baae4d7b58fb3d7 | |
parent | ad52c6bc3a56f8ec7b7c32e38fe28659cbba7109 (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
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); + } +} |