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

Commit 57019387 authored by Robert Berry's avatar Robert Berry
Browse files

Throw on attempt to unwrap a WrappedKey with old PlatformKey

Also brings the decrypt key inline with the representation in
ag/3362855. When getting the latest decrypt/encrypt key we will
always want to know the generation ID, so that we can either
persist that information with the WrappedKey, or check against
WrappedKeys we're attempting to decrypt. As such it makes sense
to have methods return a class that wraps the key and ID, as they
always belong together.

Test: adb shell am instrument -w -e package com.android.server.locksettings.recoverablekeystore com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
Change-Id: I2c7e97af9ed87216ff2f133a1e3efd546431ab7e
parent 418875d9
Loading
Loading
Loading
Loading
+35 −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;

/**
 * Error thrown when the {@link PlatformDecryptionKey} instance has a different generation ID from
 * the {@link WrappedKey} instance.
 *
 * @hide
 */
public class BadPlatformKeyException extends Exception {

    /**
     * A new instance with {@code message}.
     *
     * @hide
     */
    public BadPlatformKeyException(String message) {
        super(message);
    }
}
+65 −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;

/**
 * Used to unwrap recoverable keys before syncing them with remote storage.
 *
 * <p>This is a private key stored in AndroidKeyStore. Has an associated generation ID, which is
 * stored with wrapped keys, allowing us to ensure the wrapped key has the same version as the
 * platform key.
 *
 * @hide
 */
public class PlatformDecryptionKey {

    private final int mGenerationId;
    private final AndroidKeyStoreSecretKey mKey;

    /**
     * A new instance.
     *
     * @param generationId The generation ID of the platform key.
     * @param key The key handle in AndroidKeyStore.
     *
     * @hide
     */
    public PlatformDecryptionKey(int generationId, AndroidKeyStoreSecretKey key) {
        mGenerationId = generationId;
        mKey = key;
    }

    /**
     * Returns the generation ID.
     *
     * @hide
     */
    public int getGenerationId() {
        return mGenerationId;
    }

    /**
     * Returns the actual key, which can be used to decrypt.
     *
     * @hide
     */
    public AndroidKeyStoreSecretKey getKey() {
        return mKey;
    }
}
+27 −3
Original line number Diff line number Diff line
@@ -124,27 +124,51 @@ 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;
    }

    /**
     * Unwraps the {@code wrappedKeys} with the {@code platformKey}.
     *
     * @return The unwrapped keys, indexed by alias.
     * @throws NoSuchAlgorithmException if AES/GCM/NoPadding Cipher or AES key type is unavailable.
     * @throws BadPlatformKeyException if the {@code platformKey} has a different generation ID to
     *     any of the {@code wrappedKeys}.
     *
     * @hide
     */
    public static Map<String, SecretKey> unwrapKeys(
            SecretKey platformKey,
            PlatformDecryptionKey platformKey,
            Map<String, WrappedKey> wrappedKeys)
            throws NoSuchAlgorithmException, NoSuchPaddingException {
            throws NoSuchAlgorithmException, NoSuchPaddingException, BadPlatformKeyException {
        HashMap<String, SecretKey> unwrappedKeys = new HashMap<>();
        Cipher cipher = Cipher.getInstance(KEY_WRAP_CIPHER_ALGORITHM);
        int platformKeyGenerationId = platformKey.getGenerationId();

        for (String alias : wrappedKeys.keySet()) {
            WrappedKey wrappedKey = wrappedKeys.get(alias);
            if (wrappedKey.getPlatformKeyGenerationId() != platformKeyGenerationId) {
                throw new BadPlatformKeyException(String.format(
                        Locale.US,
                        "WrappedKey with alias '%s' was wrapped with platform key %d, not "
                                + "platform key %d",
                        alias,
                        wrappedKey.getPlatformKeyGenerationId(),
                        platformKey.getGenerationId()));
            }

            try {
                cipher.init(
                        Cipher.UNWRAP_MODE,
                        platformKey,
                        platformKey.getKey(),
                        new GCMParameterSpec(GCM_TAG_LENGTH_BITS, wrappedKey.getNonce()));
            } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
                Log.e(TAG,
+31 −3
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.server.locksettings.recoverablekeystore;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import android.security.keystore.AndroidKeyStoreSecretKey;
import android.security.keystore.KeyGenParameterSpec;
@@ -46,6 +47,7 @@ public class WrappedKeyTest {
    private static final String KEY_ALGORITHM = "AES";
    private static final String CIPHER_ALGORITHM = "AES/GCM/NoPadding";
    private static final String WRAPPING_KEY_ALIAS = "WrappedKeyTestWrappingKeyAlias";
    private static final int GENERATION_ID = 1;
    private static final int GCM_TAG_LENGTH_BYTES = 16;
    private static final int BITS_PER_BYTE = 8;
    private static final int GCM_TAG_LENGTH_BITS = GCM_TAG_LENGTH_BYTES * BITS_PER_BYTE;
@@ -77,9 +79,9 @@ public class WrappedKeyTest {
    @Test
    public void decryptWrappedKeys_decryptsWrappedKeys() throws Exception {
        String alias = "karlin";
        SecretKey platformKey = generateAndroidKeyStoreKey();
        PlatformDecryptionKey platformKey = generatePlatformDecryptionKey();
        SecretKey appKey = generateKey();
        WrappedKey wrappedKey = WrappedKey.fromSecretKey(platformKey, appKey);
        WrappedKey wrappedKey = WrappedKey.fromSecretKey(platformKey.getKey(), appKey);
        HashMap<String, WrappedKey> keysByAlias = new HashMap<>();
        keysByAlias.put(alias, wrappedKey);

@@ -99,11 +101,29 @@ public class WrappedKeyTest {
        keysByAlias.put(alias, wrappedKey);

        Map<String, SecretKey> unwrappedKeys = WrappedKey.unwrapKeys(
                generateAndroidKeyStoreKey(), keysByAlias);
                generatePlatformDecryptionKey(), keysByAlias);

        assertEquals(0, unwrappedKeys.size());
    }

    @Test
    public void decryptWrappedKeys_throwsIfPlatformKeyGenerationIdDoesNotMatch() throws Exception {
        WrappedKey wrappedKey = WrappedKey.fromSecretKey(generateKey(), generateKey());
        HashMap<String, WrappedKey> keysByAlias = new HashMap<>();
        keysByAlias.put("benji", wrappedKey);

        try {
            WrappedKey.unwrapKeys(
                    generatePlatformDecryptionKey(/*generationId=*/ 2), keysByAlias);
            fail("Should have thrown.");
        } catch (BadPlatformKeyException e) {
            assertEquals(
                    "WrappedKey with alias 'benji' was wrapped with platform key 1,"
                            + " not platform key 2",
                    e.getMessage());
        }
    }

    private SecretKey generateKey() throws Exception {
        KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM);
        keyGenerator.init(/*keySize=*/ 256);
@@ -122,4 +142,12 @@ 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());
    }
}