From 9fa18c621e82d4a6e2b647fc3268ddc89e64b73c Mon Sep 17 00:00:00 2001 From: Robert Berry Date: Thu, 14 Dec 2017 17:04:07 +0000 Subject: [PATCH] Add platform key generation ID to WrappedKey instances This is so that when we persist them, we can tell that they were wrapped with a specific version of the platform key. This will be useful for us to provide error messages to the users of recoverable keys. (i.e., in the case where the user had an application key that was wrapped with a platform key that is no longer valid, they MUST rotate key.) Test: adb shell am instrument -w -e package com.android.server.locksettings.recoverablekeystore com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner Change-Id: I91569bcaf23b49d89a9caa9d313d9c93952b620d --- .../recoverablekeystore/PlatformEncryptionKey.java | 62 ++++++++++++++++++++++ .../RecoverableKeyGenerator.java | 7 ++- .../recoverablekeystore/WrappedKey.java | 20 ++++--- .../RecoverableKeyGeneratorTest.java | 8 +-- .../recoverablekeystore/WrappedKeyTest.java | 45 ++++++++++------ 5 files changed, 109 insertions(+), 33 deletions(-) create mode 100644 services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformEncryptionKey.java diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformEncryptionKey.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformEncryptionKey.java new file mode 100644 index 000000000000..38f5b45ea190 --- /dev/null +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformEncryptionKey.java @@ -0,0 +1,62 @@ +/* + * 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; + +/** + * Private key stored in AndroidKeyStore. Used to wrap recoverable keys before writing them to disk. + * + *

Identified by a generation ID, which increments whenever a new platform key is generated. A + * new key must be generated whenever the user disables their lock screen, as the decryption key is + * tied to lock-screen authentication. + * + *

One current platform key exists per profile on the device. (As each must be tied to a + * different user's lock screen.) + * + * @hide + */ +public class PlatformEncryptionKey { + + private final int mGenerationId; + private final AndroidKeyStoreSecretKey mKey; + + /** + * A new instance. + * + * @param generationId The generation ID of the key. + * @param key The secret key handle. Can be used to encrypt WITHOUT requiring screen unlock. + */ + public PlatformEncryptionKey(int generationId, AndroidKeyStoreSecretKey key) { + mGenerationId = generationId; + mKey = key; + } + + /** + * Returns the generation ID of the key. + */ + public int getGenerationId() { + return mGenerationId; + } + + /** + * Returns the actual key, which can only be used to encrypt. + */ + public AndroidKeyStoreSecretKey getKey() { + return mKey; + } +} diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGenerator.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGenerator.java index 40c788997ba5..b22ba4ec8bde 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGenerator.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGenerator.java @@ -16,7 +16,6 @@ package com.android.server.locksettings.recoverablekeystore; -import android.security.keystore.AndroidKeyStoreSecretKey; import android.security.keystore.KeyProperties; import android.security.keystore.KeyProtection; import android.util.Log; @@ -56,7 +55,7 @@ public class RecoverableKeyGenerator { * @hide */ public static RecoverableKeyGenerator newInstance( - AndroidKeyStoreSecretKey platformKey, RecoverableKeyStorage recoverableKeyStorage) + PlatformEncryptionKey platformKey, RecoverableKeyStorage recoverableKeyStorage) throws NoSuchAlgorithmException { // NB: This cannot use AndroidKeyStore as the provider, as we need access to the raw key // material, so that it can be synced to disk in encrypted form. @@ -66,11 +65,11 @@ public class RecoverableKeyGenerator { private final KeyGenerator mKeyGenerator; private final RecoverableKeyStorage mRecoverableKeyStorage; - private final AndroidKeyStoreSecretKey mPlatformKey; + private final PlatformEncryptionKey mPlatformKey; private RecoverableKeyGenerator( KeyGenerator keyGenerator, - AndroidKeyStoreSecretKey platformKey, + PlatformEncryptionKey platformKey, RecoverableKeyStorage recoverableKeyStorage) { mKeyGenerator = keyGenerator; mRecoverableKeyStorage = recoverableKeyStorage; 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 f18e7961de5f..a0e34c35b314 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/WrappedKey.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/WrappedKey.java @@ -44,6 +44,7 @@ public class WrappedKey { private static final String APPLICATION_KEY_ALGORITHM = "AES"; private static final int GCM_TAG_LENGTH_BITS = 128; + private final int mPlatformKeyGenerationId; private final byte[] mNonce; private final byte[] mKeyMaterial; @@ -55,8 +56,8 @@ public class WrappedKey { * {@link android.security.keystore.AndroidKeyStoreKey} for an example of a key that does * not expose its key material. */ - public static WrappedKey fromSecretKey( - SecretKey wrappingKey, SecretKey key) throws InvalidKeyException, KeyStoreException { + public static WrappedKey fromSecretKey(PlatformEncryptionKey wrappingKey, SecretKey key) + throws InvalidKeyException, KeyStoreException { if (key.getEncoded() == null) { throw new InvalidKeyException( "key does not expose encoded material. It cannot be wrapped."); @@ -70,7 +71,7 @@ public class WrappedKey { "Android does not support AES/GCM/NoPadding. This should never happen."); } - cipher.init(Cipher.WRAP_MODE, wrappingKey); + cipher.init(Cipher.WRAP_MODE, wrappingKey.getKey()); byte[] encryptedKeyMaterial; try { encryptedKeyMaterial = cipher.wrap(key); @@ -90,7 +91,10 @@ public class WrappedKey { } } - return new WrappedKey(/*mNonce=*/ cipher.getIV(), /*mKeyMaterial=*/ encryptedKeyMaterial); + return new WrappedKey( + /*nonce=*/ cipher.getIV(), + /*keyMaterial=*/ encryptedKeyMaterial, + /*platformKeyGenerationId=*/ wrappingKey.getGenerationId()); } /** @@ -98,12 +102,14 @@ public class WrappedKey { * * @param nonce The nonce with which the key material was encrypted. * @param keyMaterial The encrypted bytes of the key material. + * @param platformKeyGenerationId The generation ID of the key used to wrap this key. * * @hide */ - public WrappedKey(byte[] nonce, byte[] keyMaterial) { + public WrappedKey(byte[] nonce, byte[] keyMaterial, int platformKeyGenerationId) { mNonce = nonce; mKeyMaterial = keyMaterial; + mPlatformKeyGenerationId = platformKeyGenerationId; } /** @@ -124,15 +130,13 @@ 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; + return mPlatformKeyGenerationId; } /** diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java index 298a98822caa..c13c779f2934 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java @@ -48,24 +48,24 @@ import javax.crypto.SecretKey; @SmallTest @RunWith(AndroidJUnit4.class) public class RecoverableKeyGeneratorTest { + private static final int TEST_GENERATION_ID = 3; private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore"; private static final String KEY_ALGORITHM = "AES"; private static final String TEST_ALIAS = "karlin"; private static final String WRAPPING_KEY_ALIAS = "RecoverableKeyGeneratorTestWrappingKey"; - @Mock - RecoverableKeyStorage mRecoverableKeyStorage; + @Mock RecoverableKeyStorage mRecoverableKeyStorage; @Captor ArgumentCaptor mKeyProtectionArgumentCaptor; - private AndroidKeyStoreSecretKey mPlatformKey; + private PlatformEncryptionKey mPlatformKey; private SecretKey mKeyHandle; private RecoverableKeyGenerator mRecoverableKeyGenerator; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - mPlatformKey = generateAndroidKeyStoreKey(); + mPlatformKey = new PlatformEncryptionKey(TEST_GENERATION_ID, generateAndroidKeyStoreKey()); mKeyHandle = generateKey(); mRecoverableKeyGenerator = RecoverableKeyGenerator.newInstance( mPlatformKey, mRecoverableKeyStorage); 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 8371fe5e376c..c056160e7f11 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 @@ -61,7 +61,8 @@ public class WrappedKeyTest { @Test public void fromSecretKey_createsWrappedKeyThatCanBeUnwrapped() throws Exception { - SecretKey wrappingKey = generateAndroidKeyStoreKey(); + PlatformEncryptionKey wrappingKey = new PlatformEncryptionKey( + GENERATION_ID, generateAndroidKeyStoreKey()); SecretKey rawKey = generateKey(); WrappedKey wrappedKey = WrappedKey.fromSecretKey(wrappingKey, rawKey); @@ -69,7 +70,7 @@ public class WrappedKeyTest { Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); cipher.init( Cipher.UNWRAP_MODE, - wrappingKey, + wrappingKey.getKey(), new GCMParameterSpec(GCM_TAG_LENGTH_BITS, wrappedKey.getNonce())); SecretKey unwrappedKey = (SecretKey) cipher.unwrap( wrappedKey.getKeyMaterial(), KEY_ALGORITHM, Cipher.SECRET_KEY); @@ -77,15 +78,28 @@ public class WrappedKeyTest { } @Test + public void fromSecretKey_returnsAKeyWithTheGenerationIdOfTheWrappingKey() throws Exception { + PlatformEncryptionKey wrappingKey = new PlatformEncryptionKey( + GENERATION_ID, generateAndroidKeyStoreKey()); + SecretKey rawKey = generateKey(); + + WrappedKey wrappedKey = WrappedKey.fromSecretKey(wrappingKey, rawKey); + + assertEquals(GENERATION_ID, wrappedKey.getPlatformKeyGenerationId()); + } + + @Test public void decryptWrappedKeys_decryptsWrappedKeys() throws Exception { String alias = "karlin"; - PlatformDecryptionKey platformKey = generatePlatformDecryptionKey(); + AndroidKeyStoreSecretKey platformKey = generateAndroidKeyStoreKey(); SecretKey appKey = generateKey(); - WrappedKey wrappedKey = WrappedKey.fromSecretKey(platformKey.getKey(), appKey); + WrappedKey wrappedKey = WrappedKey.fromSecretKey( + new PlatformEncryptionKey(GENERATION_ID, platformKey), appKey); HashMap keysByAlias = new HashMap<>(); keysByAlias.put(alias, wrappedKey); - Map unwrappedKeys = WrappedKey.unwrapKeys(platformKey, keysByAlias); + Map unwrappedKeys = WrappedKey.unwrapKeys( + new PlatformDecryptionKey(GENERATION_ID, platformKey), keysByAlias); assertEquals(1, unwrappedKeys.size()); assertTrue(unwrappedKeys.containsKey(alias)); @@ -95,26 +109,31 @@ public class WrappedKeyTest { @Test public void decryptWrappedKeys_doesNotDieIfSomeKeysAreUnwrappable() throws Exception { String alias = "karlin"; + AndroidKeyStoreSecretKey platformKey = generateAndroidKeyStoreKey(); SecretKey appKey = generateKey(); - WrappedKey wrappedKey = WrappedKey.fromSecretKey(generateKey(), appKey); + WrappedKey wrappedKey = WrappedKey.fromSecretKey( + new PlatformEncryptionKey(GENERATION_ID, platformKey), appKey); HashMap keysByAlias = new HashMap<>(); keysByAlias.put(alias, wrappedKey); Map unwrappedKeys = WrappedKey.unwrapKeys( - generatePlatformDecryptionKey(), keysByAlias); + new PlatformDecryptionKey(GENERATION_ID, platformKey), keysByAlias); assertEquals(0, unwrappedKeys.size()); } @Test public void decryptWrappedKeys_throwsIfPlatformKeyGenerationIdDoesNotMatch() throws Exception { - WrappedKey wrappedKey = WrappedKey.fromSecretKey(generateKey(), generateKey()); + AndroidKeyStoreSecretKey platformKey = generateAndroidKeyStoreKey(); + WrappedKey wrappedKey = WrappedKey.fromSecretKey( + new PlatformEncryptionKey(GENERATION_ID, platformKey), generateKey()); HashMap keysByAlias = new HashMap<>(); keysByAlias.put("benji", wrappedKey); try { WrappedKey.unwrapKeys( - generatePlatformDecryptionKey(/*generationId=*/ 2), keysByAlias); + new PlatformDecryptionKey(/*generationId=*/ 2, platformKey), + keysByAlias); fail("Should have thrown."); } catch (BadPlatformKeyException e) { assertEquals( @@ -142,12 +161,4 @@ 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()); - } } -- 2.11.0