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

Commit 9fa18c62 authored by Robert Berry's avatar Robert Berry
Browse files

Add platform key generation ID to WrappedKey instances

This is so that when we persist them, we can tell that they were wrapped
with a specific version of the platform key. This will be useful for us
to provide error messages to the users of recoverable keys. (i.e., in
the case where the user had an application key that was wrapped with a
platform key that is no longer valid, they MUST rotate key.)

Test: adb shell am instrument -w -e package com.android.server.locksettings.recoverablekeystore com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
Change-Id: I91569bcaf23b49d89a9caa9d313d9c93952b620d
parent 13dbdde4
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 −4
Original line number Diff line number Diff line
@@ -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;
+12 −8
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;
    }

    /**
@@ -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;
    }

    /**
+4 −4
Original line number Diff line number Diff line
@@ -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<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);
+28 −17
Original line number Diff line number Diff line
@@ -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<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,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<String, WrappedKey> keysByAlias = new HashMap<>();
        keysByAlias.put(alias, wrappedKey);

        Map<String, SecretKey> 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<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(
@@ -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());
    }
}