summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformEncryptionKey.java62
-rw-r--r--services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGenerator.java6
-rw-r--r--services/core/java/com/android/server/locksettings/recoverablekeystore/WrappedKey.java19
-rw-r--r--services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java13
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/WrappedKeyTest.java38
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java32
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);