Loading packages/BackupEncryption/Android.bp +8 −1 Original line number Diff line number Diff line Loading @@ -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, } java_library { name: "backup-encryption-protos", proto: { type: "nano" }, srcs: ["proto/**/*.proto"], } packages/BackupEncryption/proto/wrapped_key.proto 0 → 100644 +52 −0 Original line number Diff line number Diff line 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; } packages/BackupEncryption/src/com/android/server/backup/encryption/keys/KeyWrapUtils.java 0 → 100644 +132 −0 Original line number Diff line number Diff line /* * 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() {} } packages/BackupEncryption/test/robolectric/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ android_robolectric_test { ], java_resource_dirs: ["config"], libs: [ "backup-encryption-protos", "platform-test-annotations", "testng", ], Loading packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/KeyWrapUtilsTest.java 0 → 100644 +158 −0 Original line number Diff line number Diff line /* * 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); } } Loading
packages/BackupEncryption/Android.bp +8 −1 Original line number Diff line number Diff line Loading @@ -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, } java_library { name: "backup-encryption-protos", proto: { type: "nano" }, srcs: ["proto/**/*.proto"], }
packages/BackupEncryption/proto/wrapped_key.proto 0 → 100644 +52 −0 Original line number Diff line number Diff line 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; }
packages/BackupEncryption/src/com/android/server/backup/encryption/keys/KeyWrapUtils.java 0 → 100644 +132 −0 Original line number Diff line number Diff line /* * 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() {} }
packages/BackupEncryption/test/robolectric/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ android_robolectric_test { ], java_resource_dirs: ["config"], libs: [ "backup-encryption-protos", "platform-test-annotations", "testng", ], Loading
packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/KeyWrapUtilsTest.java 0 → 100644 +158 −0 Original line number Diff line number Diff line /* * 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); } }