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

Commit 3415e4ac authored by Bo Zhu's avatar Bo Zhu Committed by Android (Google) Code Review
Browse files

Merge "Add the actual implementation changes for the new metadata field for recoverable key"

parents c9c272bb 7ebcd66d
Loading
Loading
Loading
Loading
+13 −7
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import android.security.keystore.recovery.KeyChainSnapshot;
import android.security.keystore.recovery.KeyDerivationParams;
import android.security.keystore.recovery.WrappedApplicationKey;
import android.util.Log;
import android.util.Pair;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
@@ -258,9 +259,9 @@ public class KeySyncTask implements Runnable {
            localLskfHash = hashCredentialsBySaltedSha256(salt, mCredential);
        }

        Map<String, SecretKey> rawKeys;
        Map<String, Pair<SecretKey, byte[]>> rawKeysWithMetadata;
        try {
            rawKeys = getKeysToSync(recoveryAgentUid);
            rawKeysWithMetadata = getKeysToSync(recoveryAgentUid);
        } catch (GeneralSecurityException e) {
            Log.e(TAG, "Failed to load recoverable keys for sync", e);
            return;
@@ -278,7 +279,9 @@ public class KeySyncTask implements Runnable {
        }
        // Only include insecure key material for test
        if (mTestOnlyInsecureCertificateHelper.isTestOnlyCertificateAlias(rootCertAlias)) {
            rawKeys = mTestOnlyInsecureCertificateHelper.keepOnlyWhitelistedInsecureKeys(rawKeys);
            rawKeysWithMetadata =
                    mTestOnlyInsecureCertificateHelper.keepOnlyWhitelistedInsecureKeys(
                            rawKeysWithMetadata);
        }

        SecretKey recoveryKey;
@@ -292,7 +295,7 @@ public class KeySyncTask implements Runnable {
        Map<String, byte[]> encryptedApplicationKeys;
        try {
            encryptedApplicationKeys = KeySyncUtils.encryptKeysWithRecoveryKey(
                    recoveryKey, rawKeys);
                    recoveryKey, rawKeysWithMetadata);
        } catch (InvalidKeyException | NoSuchAlgorithmException e) {
            Log.wtf(TAG,
                    "Should be impossible: could not encrypt application keys with random key",
@@ -356,7 +359,8 @@ public class KeySyncTask implements Runnable {
                .setCounterId(counterId)
                .setServerParams(vaultHandle)
                .setKeyChainProtectionParams(metadataList)
                .setWrappedApplicationKeys(createApplicationKeyEntries(encryptedApplicationKeys))
                .setWrappedApplicationKeys(
                        createApplicationKeyEntries(encryptedApplicationKeys, rawKeysWithMetadata))
                .setEncryptedRecoveryKeyBlob(encryptedRecoveryKey);
        try {
            keyChainSnapshotBuilder.setTrustedHardwareCertPath(certPath);
@@ -405,7 +409,7 @@ public class KeySyncTask implements Runnable {
    /**
     * Returns all of the recoverable keys for the user.
     */
    private Map<String, SecretKey> getKeysToSync(int recoveryAgentUid)
    private Map<String, Pair<SecretKey, byte[]>> getKeysToSync(int recoveryAgentUid)
            throws InsecureUserException, KeyStoreException, UnrecoverableKeyException,
            NoSuchAlgorithmException, NoSuchPaddingException, BadPlatformKeyException,
            InvalidKeyException, InvalidAlgorithmParameterException, IOException {
@@ -521,12 +525,14 @@ public class KeySyncTask implements Runnable {
    }

    private static List<WrappedApplicationKey> createApplicationKeyEntries(
            Map<String, byte[]> encryptedApplicationKeys) {
            Map<String, byte[]> encryptedApplicationKeys,
            Map<String, Pair<SecretKey, byte[]>> originalKeysWithMetadata) {
        ArrayList<WrappedApplicationKey> keyEntries = new ArrayList<>();
        for (String alias : encryptedApplicationKeys.keySet()) {
            keyEntries.add(new WrappedApplicationKey.Builder()
                    .setAlias(alias)
                    .setEncryptedKeyMaterial(encryptedApplicationKeys.get(alias))
                    .setMetadata(originalKeysWithMetadata.get(alias).second)
                    .build());
        }
        return keyEntries;
+28 −5
Original line number Diff line number Diff line
@@ -16,6 +16,9 @@

package com.android.server.locksettings.recoverablekeystore;

import android.annotation.Nullable;
import android.util.Pair;

import com.android.internal.annotations.VisibleForTesting;

import java.nio.ByteBuffer;
@@ -152,15 +155,28 @@ public class KeySyncUtils {
     * @hide
     */
    public static Map<String, byte[]> encryptKeysWithRecoveryKey(
            SecretKey recoveryKey, Map<String, SecretKey> keys)
            SecretKey recoveryKey, Map<String, Pair<SecretKey, byte[]>> keys)
            throws NoSuchAlgorithmException, InvalidKeyException {
        HashMap<String, byte[]> encryptedKeys = new HashMap<>();
        for (String alias : keys.keySet()) {
            SecretKey key = keys.get(alias);
            SecretKey key = keys.get(alias).first;
            byte[] metadata = keys.get(alias).second;
            byte[] header;
            if (metadata == null) {
                header = ENCRYPTED_APPLICATION_KEY_HEADER;
            } else {
                // The provided metadata, if non-empty, will be bound to the authenticated
                // encryption process of the key material. As a result, the ciphertext cannot be
                // decrypted if a wrong metadata is provided during the recovery/decryption process.
                // Note that Android P devices do not have the API to provide the optional metadata,
                // so all the keys with non-empty metadata stored on Android Q+ devices cannot be
                // recovered on Android P devices.
                header = concat(ENCRYPTED_APPLICATION_KEY_HEADER, metadata);
            }
            byte[] encryptedKey = SecureBox.encrypt(
                    /*theirPublicKey=*/ null,
                    /*sharedSecret=*/ recoveryKey.getEncoded(),
                    /*header=*/ ENCRYPTED_APPLICATION_KEY_HEADER,
                    /*header=*/ header,
                    /*payload=*/ key.getEncoded());
            encryptedKeys.put(alias, encryptedKey);
        }
@@ -257,12 +273,19 @@ public class KeySyncUtils {
     * @throws AEADBadTagException if the message has been tampered with or was encrypted with a
     *     different key.
     */
    public static byte[] decryptApplicationKey(byte[] recoveryKey, byte[] encryptedApplicationKey)
    public static byte[] decryptApplicationKey(byte[] recoveryKey, byte[] encryptedApplicationKey,
            @Nullable byte[] applicationKeyMetadata)
            throws NoSuchAlgorithmException, InvalidKeyException, AEADBadTagException {
        byte[] header;
        if (applicationKeyMetadata == null) {
            header = ENCRYPTED_APPLICATION_KEY_HEADER;
        } else {
            header = concat(ENCRYPTED_APPLICATION_KEY_HEADER, applicationKeyMetadata);
        }
        return SecureBox.decrypt(
                /*ourPrivateKey=*/ null,
                /*sharedSecret=*/ recoveryKey,
                /*header=*/ ENCRYPTED_APPLICATION_KEY_HEADER,
                /*header=*/ header,
                /*encryptedPayload=*/ encryptedApplicationKey);
    }

+11 −5
Original line number Diff line number Diff line
@@ -17,6 +17,8 @@
package com.android.server.locksettings.recoverablekeystore;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.util.Log;

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

@@ -24,7 +26,6 @@ import java.security.InvalidKeyException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.Locale;
import android.util.Log;

import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
@@ -84,6 +85,8 @@ public class RecoverableKeyGenerator {
     * @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 metadata The optional metadata that will be authenticated (but unencrypted) together
     *     with the key material when the key is uploaded to cloud.
     * @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.
@@ -92,12 +95,13 @@ public class RecoverableKeyGenerator {
     * @hide
     */
    public byte[] generateAndStoreKey(
            PlatformEncryptionKey platformKey, int userId, int uid, String alias)
            PlatformEncryptionKey platformKey, int userId, int uid, String alias,
            @Nullable byte[] metadata)
            throws RecoverableKeyStorageException, KeyStoreException, InvalidKeyException {
        mKeyGenerator.init(KEY_SIZE_BITS);
        SecretKey key = mKeyGenerator.generateKey();

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

        if (result == RESULT_CANNOT_INSERT_ROW) {
@@ -126,6 +130,8 @@ public class RecoverableKeyGenerator {
     * @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.
     * @param metadata The optional metadata that will be authenticated (but unencrypted) together
     *     with the key material when the key is uploaded to cloud.
     * @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.
@@ -135,11 +141,11 @@ public class RecoverableKeyGenerator {
     */
    public void importKey(
            @NonNull PlatformEncryptionKey platformKey, int userId, int uid, @NonNull String alias,
            @NonNull byte[] keyBytes)
            @NonNull byte[] keyBytes, @Nullable byte[] metadata)
            throws RecoverableKeyStorageException, KeyStoreException, InvalidKeyException {
        SecretKey key = new SecretKeySpec(keyBytes, SECRET_KEY_ALGORITHM);

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

        if (result == RESULT_CANNOT_INSERT_ROW) {
+12 −13
Original line number Diff line number Diff line
@@ -604,7 +604,8 @@ public class RecoverableKeyStoreManager {

        try {
            byte[] recoveryKey = decryptRecoveryKey(sessionEntry, encryptedRecoveryKey);
            Map<String, byte[]> keysByAlias = recoverApplicationKeys(recoveryKey, applicationKeys);
            Map<String, byte[]> keysByAlias = recoverApplicationKeys(recoveryKey,
                    applicationKeys);
            return importKeyMaterials(userId, uid, keysByAlias);
        } catch (KeyStoreException e) {
            throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
@@ -623,7 +624,8 @@ public class RecoverableKeyStoreManager {
     * @throws KeyStoreException if an error occurs importing the key or getting the grant.
     */
    private @NonNull Map<String, String> importKeyMaterials(
            int userId, int uid, Map<String, byte[]> keysByAlias) throws KeyStoreException {
            int userId, int uid, Map<String, byte[]> keysByAlias)
            throws KeyStoreException {
        ArrayMap<String, String> grantAliasesByAlias = new ArrayMap<>(keysByAlias.size());
        for (String alias : keysByAlias.keySet()) {
            mApplicationKeyStorage.setSymmetricKeyEntry(userId, uid, alias, keysByAlias.get(alias));
@@ -700,8 +702,6 @@ public class RecoverableKeyStoreManager {
        int uid = Binder.getCallingUid();
        int userId = UserHandle.getCallingUserId();

        // TODO: Include metadata in the processes of authentication and storage

        PlatformEncryptionKey encryptionKey;
        try {
            encryptionKey = mPlatformKeyManager.getEncryptKey(userId);
@@ -715,8 +715,8 @@ public class RecoverableKeyStoreManager {
        }

        try {
            byte[] secretKey =
                    mRecoverableKeyGenerator.generateAndStoreKey(encryptionKey, userId, uid, alias);
            byte[] secretKey = mRecoverableKeyGenerator.generateAndStoreKey(encryptionKey, userId,
                    uid, alias, metadata);
            mApplicationKeyStorage.setSymmetricKeyEntry(userId, uid, alias, secretKey);
            return getAlias(userId, uid, alias);
        } catch (KeyStoreException | InvalidKeyException | RecoverableKeyStorageException e) {
@@ -768,8 +768,6 @@ public class RecoverableKeyStoreManager {
                            + " bits.");
        }

        // TODO: Include metadata in the processes of authentication and storage

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

@@ -787,7 +785,8 @@ public class RecoverableKeyStoreManager {

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

            // Import the key to Android KeyStore and get grant
            mApplicationKeyStorage.setSymmetricKeyEntry(userId, uid, alias, keyBytes);
@@ -854,17 +853,17 @@ public class RecoverableKeyStoreManager {
     * @return Map from alias to raw key material.
     * @throws RemoteException if an error occurred decrypting the keys.
     */
    private @NonNull Map<String, byte[]> recoverApplicationKeys(
            @NonNull byte[] recoveryKey,
    private @NonNull Map<String, byte[]> recoverApplicationKeys(@NonNull byte[] recoveryKey,
            @NonNull List<WrappedApplicationKey> applicationKeys) throws RemoteException {
        HashMap<String, byte[]> keyMaterialByAlias = new HashMap<>();
        for (WrappedApplicationKey applicationKey : applicationKeys) {
            String alias = applicationKey.getAlias();
            byte[] encryptedKeyMaterial = applicationKey.getEncryptedKeyMaterial();
            byte[] keyMetadata = applicationKey.getMetadata();

            try {
                byte[] keyMaterial =
                        KeySyncUtils.decryptApplicationKey(recoveryKey, encryptedKeyMaterial);
                byte[] keyMaterial = KeySyncUtils.decryptApplicationKey(recoveryKey,
                        encryptedKeyMaterial, keyMetadata);
                keyMaterialByAlias.put(alias, keyMaterial);
            } catch (NoSuchAlgorithmException e) {
                Log.wtf(TAG, "Missing SecureBox algorithm. AOSP required to support this.", e);
+12 −7
Original line number Diff line number Diff line
@@ -18,17 +18,20 @@ package com.android.server.locksettings.recoverablekeystore;

import static android.security.keystore.recovery.RecoveryController.ERROR_INVALID_CERTIFICATE;

import com.android.internal.widget.LockPatternUtils;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.security.keystore.recovery.TrustedRootCertificates;
import android.util.Log;
import android.util.Pair;

import com.android.internal.widget.LockPatternUtils;

import java.util.HashMap;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Map;

import javax.crypto.SecretKey;

/**
@@ -84,22 +87,24 @@ public class TestOnlyInsecureCertificateHelper {
                || isTestOnlyCertificateAlias(rootCertificateAlias);
    }

    public boolean doesCredentialSupportInsecureMode(int credentialType, String credential) {
    boolean doesCredentialSupportInsecureMode(int credentialType, String credential) {
        return (credentialType == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD)
            && (credential != null)
            && credential.startsWith(TrustedRootCertificates.INSECURE_PASSWORD_PREFIX);
    }

    public Map<String, SecretKey> keepOnlyWhitelistedInsecureKeys(Map<String, SecretKey> rawKeys) {
    Map<String, Pair<SecretKey, byte[]>> keepOnlyWhitelistedInsecureKeys(
            Map<String, Pair<SecretKey, byte[]>> rawKeys) {
        if (rawKeys == null) {
            return null;
        }
        Map<String, SecretKey> filteredKeys = new HashMap<>();
        for (Map.Entry<String, SecretKey> entry : rawKeys.entrySet()) {
        Map<String, Pair<SecretKey, byte[]>> filteredKeys = new HashMap<>();
        for (Map.Entry<String, Pair<SecretKey, byte[]>> entry : rawKeys.entrySet()) {
            String alias = entry.getKey();
            if (alias != null
                    && alias.startsWith(TrustedRootCertificates.INSECURE_KEY_ALIAS_PREFIX)) {
                filteredKeys.put(entry.getKey(), entry.getValue());
                filteredKeys.put(entry.getKey(),
                        Pair.create(entry.getValue().first, entry.getValue().second));
                Log.d(TAG, "adding key with insecure alias " + alias + " to the recovery snapshot");
            }
        }
Loading