Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 67b228c4 authored by Robert Berry's avatar Robert Berry
Browse files

Add PlatformEncryptionKey (again)

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
parent 53b2d749
Loading
Loading
Loading
Loading
+62 −0
Original line number Diff line number Diff line
/*
 * 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;
    }
}
+3 −3
Original line number Diff line number Diff line
@@ -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;
+12 −7
Original line number Diff line number Diff line
@@ -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;
    }

    /**
+7 −6
Original line number Diff line number Diff line
@@ -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;
        }
+3 −2
Original line number Diff line number Diff line
@@ -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);
Loading