OSDN Git Service

Add PlatformEncryptionKey (again)
authorRobert Berry <robertberry@google.com>
Mon, 18 Dec 2017 19:26:22 +0000 (19:26 +0000)
committerRobert Berry <robertberry@google.com>
Mon, 18 Dec 2017 19:28:18 +0000 (19:28 +0000)
Version 2 of this. The other change had to be reverted due to breaking
the build. This is almost identical, just with some additional fixes for
the database api.

Test: adb shell am instrument -w -e package com.android.server.locksettings.recoverablekeystore com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
Change-Id: I02928a9351739673bdffec55013c6ee7789edc1c

services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformEncryptionKey.java [new file with mode: 0644]
services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGenerator.java
services/core/java/com/android/server/locksettings/recoverablekeystore/WrappedKey.java
services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java
services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java
services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/WrappedKeyTest.java
services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.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 (file)
index 0000000..38f5b45
--- /dev/null
@@ -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;
+    }
+}
index 40c7889..54deec2 100644 (file)
@@ -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;
index f18e796..dfa173c 100644 (file)
@@ -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;
     }
 
     /**
index 79bf5aa..7986533 100644 (file)
@@ -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;
         }
index 298a988..12dbdb3 100644 (file)
@@ -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);
index 8371fe5..56122a7 100644 (file)
@@ -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(
index 5cb88dd..3d5b958 100644 (file)
@@ -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);