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

Commit 5d50100e authored by Robert Berry's avatar Robert Berry Committed by Android (Google) Code Review
Browse files

Merge "Use RecoverableKeyStoreDb in RecoverableKeyGenerator"

parents 5bebd1e9 a244b2ed
Loading
Loading
Loading
Loading
+34 −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.AndroidKeyStoreProvider;

import java.security.KeyStoreException;
import java.security.NoSuchProviderException;

public interface AndroidKeyStoreFactory {
    KeyStoreProxy getKeyStoreForUid(int uid) throws KeyStoreException, NoSuchProviderException;

    class Impl implements AndroidKeyStoreFactory {
        @Override
        public KeyStoreProxy getKeyStoreForUid(int uid)
                throws KeyStoreException, NoSuchProviderException {
            return new KeyStoreProxyImpl(AndroidKeyStoreProvider.getKeyStoreForUid(uid));
        }
    }
}
+3 −0
Original line number Diff line number Diff line
@@ -40,4 +40,7 @@ public interface KeyStoreProxy {
    /** @see KeyStore#setEntry(String, KeyStore.Entry, KeyStore.ProtectionParameter) */
    void setEntry(String alias, KeyStore.Entry entry, KeyStore.ProtectionParameter protParam)
            throws KeyStoreException;

    /** @see KeyStore#deleteEntry(String) */
    void deleteEntry(String alias) throws KeyStoreException;
}
+5 −0
Original line number Diff line number Diff line
@@ -52,4 +52,9 @@ public class KeyStoreProxyImpl implements KeyStoreProxy {
            throws KeyStoreException {
        mKeyStore.setEntry(alias, entry, protParam);
    }

    @Override
    public void deleteEntry(String alias) throws KeyStoreException {
        mKeyStore.deleteEntry(alias);
    }
}
+63 −41
Original line number Diff line number Diff line
@@ -16,16 +16,18 @@

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;

import java.io.IOException;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;

import java.security.InvalidKeyException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableEntryException;
import java.security.NoSuchProviderException;
import java.util.Locale;

import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
@@ -42,39 +44,39 @@ import javax.security.auth.DestroyFailedException;
 */
public class RecoverableKeyGenerator {
    private static final String TAG = "RecoverableKeyGenerator";

    private static final int RESULT_CANNOT_INSERT_ROW = -1;
    private static final String KEY_GENERATOR_ALGORITHM = "AES";
    private static final int KEY_SIZE_BITS = 256;

    /**
     * A new {@link RecoverableKeyGenerator} instance.
     *
     * @param platformKey Secret key used to wrap generated keys before persisting to disk.
     * @param recoverableKeyStorage Class that manages persisting wrapped keys to disk.
     * @throws NoSuchAlgorithmException if "AES" key generation or "AES/GCM/NoPadding" cipher is
     *     unavailable. Should never happen.
     *
     * @hide
     */
    public static RecoverableKeyGenerator newInstance(
            PlatformEncryptionKey platformKey, RecoverableKeyStorage recoverableKeyStorage)
    public static RecoverableKeyGenerator newInstance(RecoverableKeyStoreDb database)
            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.
        KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_GENERATOR_ALGORITHM);
        return new RecoverableKeyGenerator(keyGenerator, platformKey, recoverableKeyStorage);
        return new RecoverableKeyGenerator(
                keyGenerator, database, new AndroidKeyStoreFactory.Impl());
    }

    private final KeyGenerator mKeyGenerator;
    private final RecoverableKeyStorage mRecoverableKeyStorage;
    private final PlatformEncryptionKey mPlatformKey;
    private final RecoverableKeyStoreDb mDatabase;
    private final AndroidKeyStoreFactory mAndroidKeyStoreFactory;

    private RecoverableKeyGenerator(
            KeyGenerator keyGenerator,
            PlatformEncryptionKey platformKey,
            RecoverableKeyStorage recoverableKeyStorage) {
            RecoverableKeyStoreDb recoverableKeyStoreDb,
            AndroidKeyStoreFactory androidKeyStoreFactory) {
        mKeyGenerator = keyGenerator;
        mRecoverableKeyStorage = recoverableKeyStorage;
        mPlatformKey = platformKey;
        mAndroidKeyStoreFactory = androidKeyStoreFactory;
        mDatabase = recoverableKeyStoreDb;
    }

    /**
@@ -84,50 +86,70 @@ public class RecoverableKeyGenerator {
     * persisted to disk so that it can be synced remotely, and then recovered on another device.
     * The generated key allows encrypt/decrypt only using AES/GCM/NoPadding.
     *
     * <p>The key handle returned to the caller is a reference to the AndroidKeyStore key,
     * meaning that the caller is never able to access the raw, unencrypted key.
     *
     * @param platformKey The user's platform key, with which to wrap the generated key.
     * @param uid The uid of the application that will own the key.
     * @param alias The alias by which the key will be known in AndroidKeyStore.
     * @throws RecoverableKeyStorageException if there is some error persisting the key either to
     *     the AndroidKeyStore or the database.
     * @throws KeyStoreException if there is a KeyStore error wrapping the generated key.
     * @throws InvalidKeyException if the platform key cannot be used to wrap keys.
     * @throws IOException if there was an issue writing the wrapped key to the wrapped key store.
     * @throws UnrecoverableEntryException if could not retrieve key after putting it in
     *     AndroidKeyStore. This should not happen.
     * @return A handle to the AndroidKeyStore key.
     *
     * @hide
     */
    public SecretKey generateAndStoreKey(String alias) throws KeyStoreException,
            InvalidKeyException, IOException, UnrecoverableEntryException {
    public void generateAndStoreKey(PlatformEncryptionKey platformKey, int uid, String alias)
            throws RecoverableKeyStorageException, KeyStoreException, InvalidKeyException {
        mKeyGenerator.init(KEY_SIZE_BITS);
        SecretKey key = mKeyGenerator.generateKey();

        mRecoverableKeyStorage.importIntoAndroidKeyStore(
        KeyStoreProxy keyStore;

        try {
            keyStore = mAndroidKeyStoreFactory.getKeyStoreForUid(uid);
        } catch (NoSuchProviderException e) {
            throw new RecoverableKeyStorageException(
                    "Impossible: AndroidKeyStore provider did not exist", e);
        } catch (KeyStoreException e) {
            throw new RecoverableKeyStorageException(
                    "Could not load AndroidKeyStore for " + uid, e);
        }

        try {
            keyStore.setEntry(
                    alias,
                key,
                    new KeyStore.SecretKeyEntry(key),
                    new KeyProtection.Builder(
                            KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
                            .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
                            .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
                            .build());
        WrappedKey wrappedKey = WrappedKey.fromSecretKey(mPlatformKey, key);
        } catch (KeyStoreException e) {
            throw new RecoverableKeyStorageException(
                    "Failed to load (%d, %s) into AndroidKeyStore", e);
        }

        WrappedKey wrappedKey = WrappedKey.fromSecretKey(platformKey, key);
        try {
            // Keep raw key material in memory for minimum possible time.
            key.destroy();
        } catch (DestroyFailedException e) {
            Log.w(TAG, "Could not destroy SecretKey.");
        }
        long result = mDatabase.insertKey(uid, alias, wrappedKey);

        mRecoverableKeyStorage.persistToDisk(alias, wrappedKey);

        if (result == RESULT_CANNOT_INSERT_ROW) {
            // Attempt to clean up
            try {
            // Reload from the keystore, so that the caller is only provided with the handle of the
            // key, not the raw key material.
            return mRecoverableKeyStorage.loadFromAndroidKeyStore(alias);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(
                    "Impossible: NoSuchAlgorithmException when attempting to retrieve a key "
                            + "that has only just been stored in AndroidKeyStore.", e);
                keyStore.deleteEntry(alias);
            } catch (KeyStoreException e) {
                Log.e(TAG, String.format(Locale.US,
                        "Could not delete recoverable key (%d, %s) from "
                                + "AndroidKeyStore after error writing to database.", uid, alias),
                        e);
            }

            throw new RecoverableKeyStorageException(
                    String.format(
                            Locale.US, "Failed writing (%d, %s) to database.", uid, alias));
        }
    }
}
+0 −80
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.KeyProtection;

import java.io.IOException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableEntryException;

import javax.crypto.SecretKey;

/**
 * Stores wrapped keys to disk, so they can be synced on the next screen unlock event.
 *
 * @hide
 */
public interface RecoverableKeyStorage {

    /**
     * Writes {@code wrappedKey} to disk, keyed by the application's uid and the {@code alias}.
     *
     * @throws IOException if an error occurred writing to disk.
     *
     * @hide
     */
    void persistToDisk(String alias, WrappedKey wrappedKey) throws IOException;

    /**
     * Imports {@code key} into AndroidKeyStore, keyed by the application's uid and
     * the {@code alias}.
     *
     * @param alias The alias of the key.
     * @param key The key.
     * @param keyProtection Protection params denoting what the key can be used for. (e.g., what
     *                      Cipher modes, whether for encrpyt/decrypt or signing, etc.)
     * @throws KeyStoreException if an error occurred loading the key into the AndroidKeyStore.
     *
     * @hide
     */
    void importIntoAndroidKeyStore(String alias, SecretKey key, KeyProtection keyProtection) throws
            KeyStoreException;

    /**
     * Loads a key handle from AndroidKeyStore.
     *
     * @param alias Alias of the key to load.
     * @return The key handle.
     * @throws KeyStoreException if an error occurred loading the key from AndroidKeyStore.
     *
     * @hide
     */
    SecretKey loadFromAndroidKeyStore(String alias) throws KeyStoreException,
            NoSuchAlgorithmException,
            UnrecoverableEntryException;

    /**
     * Removes the entry with the given {@code alias} from AndroidKeyStore.
     *
     * @throws KeyStoreException if an error occurred deleting the key from AndroidKeyStore.
     *
     * @hide
     */
    void removeFromAndroidKeyStore(String alias) throws KeyStoreException;
}
Loading