diff options
7 files changed, 136 insertions, 39 deletions
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. + * + * <p>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. + * + * <p>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..54deec27f423 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGenerator.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGenerator.java @@ -56,7 +56,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 +66,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..dfa173c8d463 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; } /** @@ -131,8 +137,7 @@ public class WrappedKey { * @hide */ public int getPlatformKeyGenerationId() { - // TODO(robertberry) Implement. See ag/3362855. - return 1; + return mPlatformKeyGenerationId; } /** diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java index 79bf5aad3fbf..79865337d4c1 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java @@ -62,13 +62,12 @@ public class RecoverableKeyStoreDb { * * @param uid Uid of the application to whom the key belongs. * @param alias The alias of the key in the AndroidKeyStore. - * @param wrappedKey The wrapped bytes of the key. - * @param generationId The generation ID of the platform key that wrapped the key. + * @param wrappedKey The wrapped key. * @return The primary key of the inserted row, or -1 if failed. * * @hide */ - public long insertKey(int uid, String alias, WrappedKey wrappedKey, int generationId) { + public long insertKey(int uid, String alias, WrappedKey wrappedKey) { SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase(); ContentValues values = new ContentValues(); values.put(KeysEntry.COLUMN_NAME_UID, uid); @@ -76,7 +75,7 @@ public class RecoverableKeyStoreDb { values.put(KeysEntry.COLUMN_NAME_NONCE, wrappedKey.getNonce()); values.put(KeysEntry.COLUMN_NAME_WRAPPED_KEY, wrappedKey.getKeyMaterial()); values.put(KeysEntry.COLUMN_NAME_LAST_SYNCED_AT, -1); - values.put(KeysEntry.COLUMN_NAME_GENERATION_ID, generationId); + values.put(KeysEntry.COLUMN_NAME_GENERATION_ID, wrappedKey.getPlatformKeyGenerationId()); return db.replace(KeysEntry.TABLE_NAME, /*nullColumnHack=*/ null, values); } @@ -123,7 +122,9 @@ public class RecoverableKeyStoreDb { cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_NONCE)); byte[] keyMaterial = cursor.getBlob( cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_WRAPPED_KEY)); - return new WrappedKey(nonce, keyMaterial); + int generationId = cursor.getInt( + cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_GENERATION_ID)); + return new WrappedKey(nonce, keyMaterial, generationId); } } @@ -168,7 +169,7 @@ public class RecoverableKeyStoreDb { cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_WRAPPED_KEY)); String alias = cursor.getString( cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_ALIAS)); - keys.put(alias, new WrappedKey(nonce, keyMaterial)); + keys.put(alias, new WrappedKey(nonce, keyMaterial, platformKeyGenerationId)); } return keys; } 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..12dbdb3fecb4 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,6 +48,7 @@ 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"; @@ -58,14 +59,14 @@ public class RecoverableKeyGeneratorTest { @Captor ArgumentCaptor<KeyProtection> 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..56122a7d6d4c 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<String, WrappedKey> keysByAlias = new HashMap<>(); keysByAlias.put(alias, wrappedKey); - Map<String, SecretKey> unwrappedKeys = WrappedKey.unwrapKeys(platformKey, keysByAlias); + Map<String, SecretKey> unwrappedKeys = WrappedKey.unwrapKeys( + new PlatformDecryptionKey(GENERATION_ID, platformKey), keysByAlias); assertEquals(1, unwrappedKeys.size()); assertTrue(unwrappedKeys.containsKey(alias)); @@ -95,26 +109,32 @@ 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<String, WrappedKey> keysByAlias = new HashMap<>(); keysByAlias.put(alias, wrappedKey); Map<String, SecretKey> unwrappedKeys = WrappedKey.unwrapKeys( - generatePlatformDecryptionKey(), keysByAlias); + new PlatformDecryptionKey(GENERATION_ID, generateAndroidKeyStoreKey()), + 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<String, WrappedKey> 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( diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java index 5cb88dd073ec..3d5b958170a9 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java @@ -63,19 +63,23 @@ public class RecoverableKeyStoreDbTest { int userId = 12; String alias = "test"; WrappedKey oldWrappedKey = new WrappedKey( - getUtf8Bytes("nonce1"), getUtf8Bytes("keymaterial1")); + getUtf8Bytes("nonce1"), + getUtf8Bytes("keymaterial1"), + /*platformKeyGenerationId=*/1); mRecoverableKeyStoreDb.insertKey( - userId, alias, oldWrappedKey, /*generationId=*/ 1); + userId, alias, oldWrappedKey); byte[] nonce = getUtf8Bytes("nonce2"); byte[] keyMaterial = getUtf8Bytes("keymaterial2"); - WrappedKey newWrappedKey = new WrappedKey(nonce, keyMaterial); + WrappedKey newWrappedKey = new WrappedKey( + nonce, keyMaterial, /*platformKeyGenerationId=*/2); mRecoverableKeyStoreDb.insertKey( - userId, alias, newWrappedKey, /*generationId=*/ 2); + userId, alias, newWrappedKey); WrappedKey retrievedKey = mRecoverableKeyStoreDb.getKey(userId, alias); assertArrayEquals(nonce, retrievedKey.getNonce()); assertArrayEquals(keyMaterial, retrievedKey.getKeyMaterial()); + assertEquals(2, retrievedKey.getPlatformKeyGenerationId()); } @Test @@ -93,13 +97,14 @@ public class RecoverableKeyStoreDbTest { String alias = "test"; byte[] nonce = getUtf8Bytes("nonce"); byte[] keyMaterial = getUtf8Bytes("keymaterial"); - WrappedKey wrappedKey = new WrappedKey(nonce, keyMaterial); - mRecoverableKeyStoreDb.insertKey(userId, alias, wrappedKey, generationId); + WrappedKey wrappedKey = new WrappedKey(nonce, keyMaterial, generationId); + mRecoverableKeyStoreDb.insertKey(userId, alias, wrappedKey); WrappedKey retrievedKey = mRecoverableKeyStoreDb.getKey(userId, alias); assertArrayEquals(nonce, retrievedKey.getNonce()); assertArrayEquals(keyMaterial, retrievedKey.getKeyMaterial()); + assertEquals(generationId, retrievedKey.getPlatformKeyGenerationId()); } @Test @@ -109,8 +114,8 @@ public class RecoverableKeyStoreDbTest { String alias = "test"; byte[] nonce = getUtf8Bytes("nonce"); byte[] keyMaterial = getUtf8Bytes("keymaterial"); - WrappedKey wrappedKey = new WrappedKey(nonce, keyMaterial); - mRecoverableKeyStoreDb.insertKey(userId, alias, wrappedKey, generationId); + WrappedKey wrappedKey = new WrappedKey(nonce, keyMaterial, generationId); + mRecoverableKeyStoreDb.insertKey(userId, alias, wrappedKey); Map<String, WrappedKey> keys = mRecoverableKeyStoreDb.getAllKeys(userId, generationId); @@ -119,15 +124,18 @@ public class RecoverableKeyStoreDbTest { WrappedKey retrievedKey = keys.get(alias); assertArrayEquals(nonce, retrievedKey.getNonce()); assertArrayEquals(keyMaterial, retrievedKey.getKeyMaterial()); + assertEquals(generationId, retrievedKey.getPlatformKeyGenerationId()); } @Test public void getAllKeys_doesNotReturnKeysWithBadGenerationId() { int userId = 12; WrappedKey wrappedKey = new WrappedKey( - getUtf8Bytes("nonce"), getUtf8Bytes("keymaterial")); + getUtf8Bytes("nonce"), + getUtf8Bytes("keymaterial"), + /*platformKeyGenerationId=*/ 5); mRecoverableKeyStoreDb.insertKey( - userId, /*alias=*/ "test", wrappedKey, /*generationId=*/ 5); + userId, /*alias=*/ "test", wrappedKey); Map<String, WrappedKey> keys = mRecoverableKeyStoreDb.getAllKeys( userId, /*generationId=*/ 7); @@ -139,9 +147,9 @@ public class RecoverableKeyStoreDbTest { public void getAllKeys_doesNotReturnKeysWithBadUserId() { int generationId = 12; WrappedKey wrappedKey = new WrappedKey( - getUtf8Bytes("nonce"), getUtf8Bytes("keymaterial")); + getUtf8Bytes("nonce"), getUtf8Bytes("keymaterial"), generationId); mRecoverableKeyStoreDb.insertKey( - /*userId=*/ 1, /*alias=*/ "test", wrappedKey, generationId); + /*userId=*/ 1, /*alias=*/ "test", wrappedKey); Map<String, WrappedKey> keys = mRecoverableKeyStoreDb.getAllKeys( /*userId=*/ 2, generationId); |