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

Commit e2a95e5c authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Add a new API to import a key provided by the caller, such that this...

Merge "Add a new API to import a key provided by the caller, such that this key can also be synced to the remote service"
parents eaf5a4a1 2c8e5383
Loading
Loading
Loading
Loading
+43 −0
Original line number Diff line number Diff line
@@ -113,6 +113,14 @@ public class RecoveryController {
     */
    public static final int ERROR_DECRYPTION_FAILED = 26;

    /**
     * Error thrown if the format of a given key is invalid. This might be because the key has a
     * wrong length, invalid content, etc.
     *
     * @hide
     */
    public static final int ERROR_INVALID_KEY_FORMAT = 27;


    private final ILockSettings mBinder;
    private final KeyStore mKeyStore;
@@ -461,6 +469,7 @@ public class RecoveryController {
        }
    }

    // TODO: Unhide the following APIs, generateKey(), importKey(), and getKey()
    /**
     * @deprecated Use {@link #generateKey(String)}.
     * @removed
@@ -502,6 +511,40 @@ public class RecoveryController {
        }
    }

    /**
     * Imports a 256-bit recoverable AES key with the given {@code alias} and the raw bytes {@code
     * keyBytes}.
     *
     * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
     *     service.
     * @throws LockScreenRequiredException if the user does not have a lock screen set. A lock
     *     screen is required to generate recoverable keys.
     *
     * @hide
     */
    public Key importKey(@NonNull String alias, byte[] keyBytes)
            throws InternalRecoveryServiceException, LockScreenRequiredException {
        try {
            String grantAlias = mBinder.importKey(alias, keyBytes);
            if (grantAlias == null) {
                throw new InternalRecoveryServiceException("Null grant alias");
            }
            return AndroidKeyStoreProvider.loadAndroidKeyStoreKeyFromKeystore(
                    mKeyStore,
                    grantAlias,
                    KeyStore.UID_SELF);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        } catch (UnrecoverableKeyException e) {
            throw new InternalRecoveryServiceException("Failed to get key from keystore", e);
        } catch (ServiceSpecificException e) {
            if (e.errorCode == ERROR_INSECURE_USER) {
                throw new LockScreenRequiredException(e.getMessage());
            }
            throw wrapUnexpectedServiceSpecificException(e);
        }
    }

    /**
     * Gets a key called {@code alias} from the recoverable key store.
     *
+1 −0
Original line number Diff line number Diff line
@@ -68,6 +68,7 @@ interface ILockSettings {
    KeyChainSnapshot getKeyChainSnapshot();
    byte[] generateAndStoreKey(String alias);
    String generateKey(String alias);
    String importKey(String alias, in byte[] keyBytes);
    String getKey(String alias);
    void removeKey(String alias);
    void setSnapshotCreatedPendingIntent(in PendingIntent intent);
+5 −0
Original line number Diff line number Diff line
@@ -2078,6 +2078,11 @@ public class LockSettingsService extends ILockSettings.Stub {
        return mRecoverableKeyStoreManager.generateKey(alias);
    }

    @Override
    public String importKey(@NonNull String alias, byte[] keyBytes) throws RemoteException {
        return mRecoverableKeyStoreManager.importKey(alias, keyBytes);
    }

    @Override
    public String getKey(@NonNull String alias) throws RemoteException {
        return mRecoverableKeyStoreManager.getKey(alias);
+48 −5
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package com.android.server.locksettings.recoverablekeystore;

import android.annotation.NonNull;

import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;

import java.security.InvalidKeyException;
@@ -25,20 +27,24 @@ import java.util.Locale;

import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

// TODO: Rename RecoverableKeyGenerator to RecoverableKeyManager as it can import a key too now
/**
 * Generates keys and stores them both in AndroidKeyStore and on disk, in wrapped form.
 * Generates/imports keys and stores them both in AndroidKeyStore and on disk, in wrapped form.
 *
 * <p>Generates 256-bit AES keys, which can be used for encrypt / decrypt with AES/GCM/NoPadding.
 * <p>Generates/imports 256-bit AES keys, which can be used for encrypt and decrypt with AES-GCM.
 * They are synced to disk wrapped by a platform key. This allows them to be exported to a remote
 * service.
 *
 * @hide
 */
public class 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;
    private static final String SECRET_KEY_ALGORITHM = "AES";

    static final int KEY_SIZE_BITS = 256;

    /**
     * A new {@link RecoverableKeyGenerator} instance.
@@ -52,7 +58,7 @@ public class RecoverableKeyGenerator {
            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);
        KeyGenerator keyGenerator = KeyGenerator.getInstance(SECRET_KEY_ALGORITHM);
        return new RecoverableKeyGenerator(keyGenerator, database);
    }

@@ -102,4 +108,41 @@ public class RecoverableKeyGenerator {
        mDatabase.setShouldCreateSnapshot(userId, uid, true);
        return key.getEncoded();
    }

    /**
     * Imports an AES key with the given alias.
     *
     * <p>Stores in the AndroidKeyStore, as well as persisting in wrapped form to disk. It is
     * 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.
     *
     * @param platformKey The user's platform key, with which to wrap the generated key.
     * @param userId The user ID of the profile to which the calling app belongs.
     * @param uid The uid of the application that will own the key.
     * @param alias The alias by which the key will be known in the recoverable key store.
     * @param keyBytes The raw bytes of the AES key to be imported.
     * @throws RecoverableKeyStorageException if there is some error persisting the key either to
     *     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.
     *
     * @hide
     */
    public void importKey(
            @NonNull PlatformEncryptionKey platformKey, int userId, int uid, @NonNull String alias,
            @NonNull byte[] keyBytes)
            throws RecoverableKeyStorageException, KeyStoreException, InvalidKeyException {
        SecretKey key = new SecretKeySpec(keyBytes, SECRET_KEY_ALGORITHM);

        WrappedKey wrappedKey = WrappedKey.fromSecretKey(platformKey, key);
        long result = mDatabase.insertKey(userId, uid, alias, wrappedKey);

        if (result == RESULT_CANNOT_INSERT_ROW) {
            throw new RecoverableKeyStorageException(
                    String.format(
                            Locale.US, "Failed writing (%d, %s) to database.", uid, alias));
        }

        mDatabase.setShouldCreateSnapshot(userId, uid, true);
    }
}
+53 −8
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.server.locksettings.recoverablekeystore;
import static android.security.keystore.recovery.RecoveryController.ERROR_BAD_CERTIFICATE_FORMAT;
import static android.security.keystore.recovery.RecoveryController.ERROR_DECRYPTION_FAILED;
import static android.security.keystore.recovery.RecoveryController.ERROR_INSECURE_USER;
import static android.security.keystore.recovery.RecoveryController.ERROR_INVALID_KEY_FORMAT;
import static android.security.keystore.recovery.RecoveryController.ERROR_NO_SNAPSHOT_PENDING;
import static android.security.keystore.recovery.RecoveryController.ERROR_SERVICE_INTERNAL_ERROR;
import static android.security.keystore.recovery.RecoveryController.ERROR_SESSION_EXPIRED;
@@ -505,6 +506,7 @@ public class RecoverableKeyStoreManager {
     *
     * <p>TODO: Once AndroidKeyStore has added move api, do not return raw bytes.
     *
     * @deprecated
     * @hide
     */
    public byte[] generateAndStoreKey(@NonNull String alias) throws RemoteException {
@@ -580,6 +582,57 @@ public class RecoverableKeyStoreManager {
        }
    }

    /**
     * Imports a 256-bit AES-GCM key named {@code alias}. The key is stored in system service
     * keystore namespace.
     *
     * @param alias the alias provided by caller as a reference to the key.
     * @param keyBytes the raw bytes of the 256-bit AES key.
     * @return grant alias, which caller can use to access the key.
     * @throws RemoteException if the given key is invalid or some internal errors occur.
     *
     * @hide
     */
    public String importKey(@NonNull String alias, @NonNull byte[] keyBytes)
            throws RemoteException {
        if (keyBytes == null ||
                keyBytes.length != RecoverableKeyGenerator.KEY_SIZE_BITS / Byte.SIZE) {
            Log.e(TAG, "The given key for import doesn't have the required length "
                    + RecoverableKeyGenerator.KEY_SIZE_BITS);
            throw new ServiceSpecificException(ERROR_INVALID_KEY_FORMAT,
                    "The given key does not contain " + RecoverableKeyGenerator.KEY_SIZE_BITS
                            + " bits.");
        }

        int uid = Binder.getCallingUid();
        int userId = UserHandle.getCallingUserId();

        // TODO: Refactor RecoverableKeyGenerator to wrap the PlatformKey logic

        PlatformEncryptionKey encryptionKey;
        try {
            encryptionKey = mPlatformKeyManager.getEncryptKey(userId);
        } catch (NoSuchAlgorithmException e) {
            // Impossible: all algorithms must be supported by AOSP
            throw new RuntimeException(e);
        } catch (KeyStoreException | UnrecoverableKeyException e) {
            throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
        } catch (InsecureUserException e) {
            throw new ServiceSpecificException(ERROR_INSECURE_USER, e.getMessage());
        }

        try {
            // Wrap the key by the platform key and store the wrapped key locally
            mRecoverableKeyGenerator.importKey(encryptionKey, userId, uid, alias, keyBytes);

            // Import the key to Android KeyStore and get grant
            mApplicationKeyStorage.setSymmetricKeyEntry(userId, uid, alias, keyBytes);
            return mApplicationKeyStorage.getGrantAlias(userId, uid, alias);
        } catch (KeyStoreException | InvalidKeyException | RecoverableKeyStorageException e) {
            throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
        }
    }

    /**
     * Gets a key named {@code alias} in caller's namespace.
     *
@@ -630,14 +683,6 @@ public class RecoverableKeyStoreManager {
        }
    }

    private String constructLoggingMessage(String key, byte[] value) {
        if (value == null) {
            return key + " is null";
        } else {
            return key + ": " + HexDump.toHexString(value);
        }
    }

    /**
     * Uses {@code recoveryKey} to decrypt {@code applicationKeys}.
     *
Loading