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
This commit is contained in:
@@ -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"],
|
||||
}
|
||||
|
||||
52
packages/BackupEncryption/proto/wrapped_key.proto
Normal file
52
packages/BackupEncryption/proto/wrapped_key.proto
Normal file
@@ -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;
|
||||
}
|
||||
@@ -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() {}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ android_robolectric_test {
|
||||
],
|
||||
java_resource_dirs: ["config"],
|
||||
libs: [
|
||||
"backup-encryption-protos",
|
||||
"platform-test-annotations",
|
||||
"testng",
|
||||
],
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user