Merge "Throw on attempt to unwrap a WrappedKey with old PlatformKey"

This commit is contained in:
Robert Berry
2017-12-16 22:54:17 +00:00
committed by Android (Google) Code Review
4 changed files with 158 additions and 6 deletions

View File

@@ -0,0 +1,35 @@
/*
* Copyright (C) 2017 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.locksettings.recoverablekeystore;
/**
* Error thrown when the {@link PlatformDecryptionKey} instance has a different generation ID from
* the {@link WrappedKey} instance.
*
* @hide
*/
public class BadPlatformKeyException extends Exception {
/**
* A new instance with {@code message}.
*
* @hide
*/
public BadPlatformKeyException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright (C) 2017 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.locksettings.recoverablekeystore;
import android.security.keystore.AndroidKeyStoreSecretKey;
/**
* Used to unwrap recoverable keys before syncing them with remote storage.
*
* <p>This is a private key stored in AndroidKeyStore. Has an associated generation ID, which is
* stored with wrapped keys, allowing us to ensure the wrapped key has the same version as the
* platform key.
*
* @hide
*/
public class PlatformDecryptionKey {
private final int mGenerationId;
private final AndroidKeyStoreSecretKey mKey;
/**
* A new instance.
*
* @param generationId The generation ID of the platform key.
* @param key The key handle in AndroidKeyStore.
*
* @hide
*/
public PlatformDecryptionKey(int generationId, AndroidKeyStoreSecretKey key) {
mGenerationId = generationId;
mKey = key;
}
/**
* Returns the generation ID.
*
* @hide
*/
public int getGenerationId() {
return mGenerationId;
}
/**
* Returns the actual key, which can be used to decrypt.
*
* @hide
*/
public AndroidKeyStoreSecretKey getKey() {
return mKey;
}
}

View File

@@ -124,27 +124,51 @@ public class WrappedKey {
return mKeyMaterial;
}
/**
* Returns the generation ID of the platform key, with which this key was wrapped.
*
* @hide
*/
public int getPlatformKeyGenerationId() {
// TODO(robertberry) Implement. See ag/3362855.
return 1;
}
/**
* Unwraps the {@code wrappedKeys} with the {@code platformKey}.
*
* @return The unwrapped keys, indexed by alias.
* @throws NoSuchAlgorithmException if AES/GCM/NoPadding Cipher or AES key type is unavailable.
* @throws BadPlatformKeyException if the {@code platformKey} has a different generation ID to
* any of the {@code wrappedKeys}.
*
* @hide
*/
public static Map<String, SecretKey> unwrapKeys(
SecretKey platformKey,
PlatformDecryptionKey platformKey,
Map<String, WrappedKey> wrappedKeys)
throws NoSuchAlgorithmException, NoSuchPaddingException {
throws NoSuchAlgorithmException, NoSuchPaddingException, BadPlatformKeyException {
HashMap<String, SecretKey> unwrappedKeys = new HashMap<>();
Cipher cipher = Cipher.getInstance(KEY_WRAP_CIPHER_ALGORITHM);
int platformKeyGenerationId = platformKey.getGenerationId();
for (String alias : wrappedKeys.keySet()) {
WrappedKey wrappedKey = wrappedKeys.get(alias);
if (wrappedKey.getPlatformKeyGenerationId() != platformKeyGenerationId) {
throw new BadPlatformKeyException(String.format(
Locale.US,
"WrappedKey with alias '%s' was wrapped with platform key %d, not "
+ "platform key %d",
alias,
wrappedKey.getPlatformKeyGenerationId(),
platformKey.getGenerationId()));
}
try {
cipher.init(
Cipher.UNWRAP_MODE,
platformKey,
platformKey.getKey(),
new GCMParameterSpec(GCM_TAG_LENGTH_BITS, wrappedKey.getNonce()));
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
Log.e(TAG,

View File

@@ -19,6 +19,7 @@ package com.android.server.locksettings.recoverablekeystore;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.security.keystore.AndroidKeyStoreSecretKey;
import android.security.keystore.KeyGenParameterSpec;
@@ -46,6 +47,7 @@ public class WrappedKeyTest {
private static final String KEY_ALGORITHM = "AES";
private static final String CIPHER_ALGORITHM = "AES/GCM/NoPadding";
private static final String WRAPPING_KEY_ALIAS = "WrappedKeyTestWrappingKeyAlias";
private static final int GENERATION_ID = 1;
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;
@@ -77,9 +79,9 @@ public class WrappedKeyTest {
@Test
public void decryptWrappedKeys_decryptsWrappedKeys() throws Exception {
String alias = "karlin";
SecretKey platformKey = generateAndroidKeyStoreKey();
PlatformDecryptionKey platformKey = generatePlatformDecryptionKey();
SecretKey appKey = generateKey();
WrappedKey wrappedKey = WrappedKey.fromSecretKey(platformKey, appKey);
WrappedKey wrappedKey = WrappedKey.fromSecretKey(platformKey.getKey(), appKey);
HashMap<String, WrappedKey> keysByAlias = new HashMap<>();
keysByAlias.put(alias, wrappedKey);
@@ -99,11 +101,29 @@ public class WrappedKeyTest {
keysByAlias.put(alias, wrappedKey);
Map<String, SecretKey> unwrappedKeys = WrappedKey.unwrapKeys(
generateAndroidKeyStoreKey(), keysByAlias);
generatePlatformDecryptionKey(), keysByAlias);
assertEquals(0, unwrappedKeys.size());
}
@Test
public void decryptWrappedKeys_throwsIfPlatformKeyGenerationIdDoesNotMatch() throws Exception {
WrappedKey wrappedKey = WrappedKey.fromSecretKey(generateKey(), generateKey());
HashMap<String, WrappedKey> keysByAlias = new HashMap<>();
keysByAlias.put("benji", wrappedKey);
try {
WrappedKey.unwrapKeys(
generatePlatformDecryptionKey(/*generationId=*/ 2), keysByAlias);
fail("Should have thrown.");
} catch (BadPlatformKeyException e) {
assertEquals(
"WrappedKey with alias 'benji' was wrapped with platform key 1,"
+ " not platform key 2",
e.getMessage());
}
}
private SecretKey generateKey() throws Exception {
KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM);
keyGenerator.init(/*keySize=*/ 256);
@@ -122,4 +142,12 @@ public class WrappedKeyTest {
.build());
return (AndroidKeyStoreSecretKey) keyGenerator.generateKey();
}
private PlatformDecryptionKey generatePlatformDecryptionKey() throws Exception {
return generatePlatformDecryptionKey(GENERATION_ID);
}
private PlatformDecryptionKey generatePlatformDecryptionKey(int generationId) throws Exception {
return new PlatformDecryptionKey(generationId, generateAndroidKeyStoreKey());
}
}