From 57019387b8caf16e1af33d444d0b955e440af0de Mon Sep 17 00:00:00 2001 From: Robert Berry Date: Fri, 15 Dec 2017 15:25:01 +0000 Subject: [PATCH] Throw on attempt to unwrap a WrappedKey with old PlatformKey Also brings the decrypt key inline with the representation in ag/3362855. When getting the latest decrypt/encrypt key we will always want to know the generation ID, so that we can either persist that information with the WrappedKey, or check against WrappedKeys we're attempting to decrypt. As such it makes sense to have methods return a class that wraps the key and ID, as they always belong together. Test: adb shell am instrument -w -e package com.android.server.locksettings.recoverablekeystore com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner Change-Id: I2c7e97af9ed87216ff2f133a1e3efd546431ab7e --- .../BadPlatformKeyException.java | 35 ++++++++++ .../PlatformDecryptionKey.java | 65 +++++++++++++++++++ .../recoverablekeystore/WrappedKey.java | 30 ++++++++- .../recoverablekeystore/WrappedKeyTest.java | 34 +++++++++- 4 files changed, 158 insertions(+), 6 deletions(-) create mode 100644 services/core/java/com/android/server/locksettings/recoverablekeystore/BadPlatformKeyException.java create mode 100644 services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformDecryptionKey.java diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/BadPlatformKeyException.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/BadPlatformKeyException.java new file mode 100644 index 0000000000000..c7a98b6672ec4 --- /dev/null +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/BadPlatformKeyException.java @@ -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); + } +} diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformDecryptionKey.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformDecryptionKey.java new file mode 100644 index 0000000000000..35571f1f27280 --- /dev/null +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformDecryptionKey.java @@ -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. + * + *

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; + } +} diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/WrappedKey.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/WrappedKey.java index 9002292174ba8..f18e7961de5f1 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/WrappedKey.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/WrappedKey.java @@ -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 unwrapKeys( - SecretKey platformKey, + PlatformDecryptionKey platformKey, Map wrappedKeys) - throws NoSuchAlgorithmException, NoSuchPaddingException { + throws NoSuchAlgorithmException, NoSuchPaddingException, BadPlatformKeyException { HashMap 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, diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/WrappedKeyTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/WrappedKeyTest.java index fa73722ea060e..8371fe5e376ca 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/WrappedKeyTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/WrappedKeyTest.java @@ -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 keysByAlias = new HashMap<>(); keysByAlias.put(alias, wrappedKey); @@ -99,11 +101,29 @@ public class WrappedKeyTest { keysByAlias.put(alias, wrappedKey); Map 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 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()); + } }