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 0000000000000..38f5b45ea1907 --- /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 40c788997ba5a..b22ba4ec8bded 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 f18e7961de5f1..a0e34c35b3147 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 298a98822caa1..c13c779f29345 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 8371fe5e376ca..c056160e7f11b 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,23 +70,36 @@ 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); assertEquals(rawKey, unwrappedKey); } + @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()); - } }