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

Commit 52bc5f2a authored by Dmitry Dementyev's avatar Dmitry Dementyev Committed by Android (Google) Code Review
Browse files

Merge "Use rootAlias to index chosen cert and its version." into pi-dev

parents a21c304b f34fc7e1
Loading
Loading
Loading
Loading
+28 −2
Original line number Diff line number Diff line
@@ -19,10 +19,12 @@ package com.android.server.locksettings.recoverablekeystore;
import static android.security.keystore.recovery.KeyChainProtectionParams.TYPE_LOCKSCREEN;

import android.annotation.Nullable;
import android.annotation.NonNull;
import android.content.Context;
import android.security.keystore.recovery.KeyDerivationParams;
import android.security.keystore.recovery.KeyChainProtectionParams;
import android.security.keystore.recovery.KeyChainSnapshot;
import android.security.keystore.recovery.KeyDerivationParams;
import android.security.keystore.recovery.TrustedRootCertificates;
import android.security.keystore.recovery.WrappedApplicationKey;
import android.util.Log;

@@ -185,8 +187,12 @@ public class KeySyncTask implements Runnable {
        }

        PublicKey publicKey;
        String rootCertAlias =
                mRecoverableKeyStoreDb.getActiveRootOfTrust(mUserId, recoveryAgentUid);

        rootCertAlias = replaceEmptyValueWithSecureDefault(rootCertAlias);
        CertPath certPath = mRecoverableKeyStoreDb.getRecoveryServiceCertPath(mUserId,
                recoveryAgentUid);
                recoveryAgentUid, rootCertAlias);
        if (certPath != null) {
            Log.d(TAG, "Using the public key in stored CertPath for syncing");
            publicKey = certPath.getCertificates().get(0).getPublicKey();
@@ -206,6 +212,14 @@ public class KeySyncTask implements Runnable {
            return;
        }

        // The only place in this class which uses credential value
        if (!TrustedRootCertificates.GOOGLE_CLOUD_KEY_VAULT_SERVICE_V1_ALIAS.equals(
                rootCertAlias)) {
            // TODO: allow only whitelisted LSKF usage
            Log.w(TAG, "Untrusted root certificate is used by recovery agent "
                    + recoveryAgentUid);
        }

        byte[] salt = generateSalt();
        byte[] localLskfHash = hashCredentials(salt, mCredential);

@@ -225,6 +239,8 @@ public class KeySyncTask implements Runnable {
            return;
        }

        // TODO: filter raw keys based on the root of trust.
        // It is the only place in the class where raw key material is used.
        SecretKey recoveryKey;
        try {
            recoveryKey = generateRecoveryKey();
@@ -451,4 +467,14 @@ public class KeySyncTask implements Runnable {
        }
        return keyEntries;
    }

    private @NonNull String replaceEmptyValueWithSecureDefault(
            @Nullable String rootCertificateAlias) {
        if (rootCertificateAlias == null || rootCertificateAlias.isEmpty()) {
            Log.e(TAG, "rootCertificateAlias is null or empty");
            // Use the default Google Key Vault Service CA certificate if the alias is not provided
            rootCertificateAlias = TrustedRootCertificates.GOOGLE_CLOUD_KEY_VAULT_SERVICE_V1_ALIAS;
        }
        return rootCertificateAlias;
    }
}
+34 −15
Original line number Diff line number Diff line
@@ -176,6 +176,20 @@ public class RecoverableKeyStoreManager {
        checkRecoverKeyStorePermission();
        int userId = UserHandle.getCallingUserId();
        int uid = Binder.getCallingUid();
        rootCertificateAlias = replaceEmptyValueWithSecureDefault(rootCertificateAlias);

        // Always set active alias to the argument of the last call to initRecoveryService method,
        // even if cert file is incorrect.
        String activeRootAlias = mDatabase.getActiveRootOfTrust(userId, uid);
        if (activeRootAlias == null) {
            Log.d(TAG, "Root of trust for recovery agent + " + uid
                + " is assigned for the first time to " + rootCertificateAlias);
            mDatabase.setActiveRootOfTrust(userId, uid, rootCertificateAlias);
        } else if (!activeRootAlias.equals(rootCertificateAlias)) {
            Log.i(TAG, "Root of trust for recovery agent " + uid + " is changed to "
                    + rootCertificateAlias + " from  " + activeRootAlias);
            mDatabase.setActiveRootOfTrust(userId, uid, rootCertificateAlias);
        }

        CertXml certXml;
        try {
@@ -194,7 +208,7 @@ public class RecoverableKeyStoreManager {

        // Check serial number
        long newSerial = certXml.getSerial();
        Long oldSerial = mDatabase.getRecoveryServiceCertSerial(userId, uid);
        Long oldSerial = mDatabase.getRecoveryServiceCertSerial(userId, uid, rootCertificateAlias);
        if (oldSerial != null && oldSerial >= newSerial) {
            if (oldSerial == newSerial) {
                Log.i(TAG, "The cert file serial number is the same, so skip updating.");
@@ -217,13 +231,16 @@ public class RecoverableKeyStoreManager {
                    ERROR_INVALID_CERTIFICATE, "Failed to validate certificate.");
        }

        boolean wasInitialized = mDatabase.getRecoveryServiceCertPath(userId, uid) != null;
        boolean wasInitialized = mDatabase.getRecoveryServiceCertPath(userId, uid,
                rootCertificateAlias) != null;

        // Save the chosen and validated certificate into database
        try {
            Log.d(TAG, "Saving the randomly chosen endpoint certificate to database");
            if (mDatabase.setRecoveryServiceCertPath(userId, uid, certPath) > 0) {
                mDatabase.setRecoveryServiceCertSerial(userId, uid, newSerial);
            if (mDatabase.setRecoveryServiceCertPath(userId, uid, rootCertificateAlias,
                    certPath) > 0) {
                mDatabase.setRecoveryServiceCertSerial(userId, uid, rootCertificateAlias,
                        newSerial);
                if (wasInitialized) {
                    Log.i(TAG, "This is a certificate change. Snapshot pending.");
                    mDatabase.setShouldCreateSnapshot(userId, uid, true);
@@ -253,9 +270,7 @@ public class RecoverableKeyStoreManager {
            @NonNull byte[] recoveryServiceSigFile)
            throws RemoteException {
        checkRecoverKeyStorePermission();
        if (rootCertificateAlias == null) {
            Log.e(TAG, "rootCertificateAlias is null");
        }
        rootCertificateAlias = replaceEmptyValueWithSecureDefault(rootCertificateAlias);
        Preconditions.checkNotNull(recoveryServiceCertFile, "recoveryServiceCertFile is null");
        Preconditions.checkNotNull(recoveryServiceSigFile, "recoveryServiceSigFile is null");

@@ -509,9 +524,7 @@ public class RecoverableKeyStoreManager {
            @NonNull List<KeyChainProtectionParams> secrets)
            throws RemoteException {
        checkRecoverKeyStorePermission();
        if (rootCertificateAlias == null) {
            Log.e(TAG, "rootCertificateAlias is null");
        }
        rootCertificateAlias = replaceEmptyValueWithSecureDefault(rootCertificateAlias);
        Preconditions.checkNotNull(sessionId, "invalid session");
        Preconditions.checkNotNull(verifierCertPath, "verifierCertPath is null");
        Preconditions.checkNotNull(vaultParams, "vaultParams is null");
@@ -953,11 +966,7 @@ public class RecoverableKeyStoreManager {
    }

    private X509Certificate getRootCertificate(String rootCertificateAlias) throws RemoteException {
        if (rootCertificateAlias == null || rootCertificateAlias.isEmpty()) {
            // Use the default Google Key Vault Service CA certificate if the alias is not provided
            rootCertificateAlias = TrustedRootCertificates.GOOGLE_CLOUD_KEY_VAULT_SERVICE_V1_ALIAS;
        }

        rootCertificateAlias = replaceEmptyValueWithSecureDefault(rootCertificateAlias);
        X509Certificate rootCertificate =
                TrustedRootCertificates.getRootCertificate(rootCertificateAlias);
        if (rootCertificate == null) {
@@ -967,6 +976,16 @@ public class RecoverableKeyStoreManager {
        return rootCertificate;
    }

    private @NonNull String replaceEmptyValueWithSecureDefault(
            @Nullable String rootCertificateAlias) {
        if (rootCertificateAlias == null || rootCertificateAlias.isEmpty()) {
            Log.e(TAG, "rootCertificateAlias is null or empty");
            // Use the default Google Key Vault Service CA certificate if the alias is not provided
            rootCertificateAlias = TrustedRootCertificates.GOOGLE_CLOUD_KEY_VAULT_SERVICE_V1_ALIAS;
        }
        return rootCertificateAlias;
    }

    private void checkRecoverKeyStorePermission() {
        mContext.enforceCallingOrSelfPermission(
                Manifest.permission.RECOVER_KEYSTORE,
+281 −10
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@ import android.util.Log;
import com.android.server.locksettings.recoverablekeystore.WrappedKey;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.KeysEntry;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.RecoveryServiceMetadataEntry;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.RootOfTrustEntry;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.UserMetadataEntry;

import java.io.ByteArrayInputStream;
@@ -385,13 +386,15 @@ public class RecoverableKeyStoreDb {
     *
     * @param userId The userId of the profile the application is running under.
     * @param uid The uid of the application who initializes the local recovery components.
     * @param rootAlias The root of trust alias.
     * @return The value that were previously set, or null if there's none.
     *
     * @hide
     */
    @Nullable
    public Long getRecoveryServiceCertSerial(int userId, int uid) {
        return getLong(userId, uid, RecoveryServiceMetadataEntry.COLUMN_NAME_CERT_SERIAL);
    public Long getRecoveryServiceCertSerial(int userId, int uid, @NonNull String rootAlias) {
        return getLong(userId, uid, rootAlias,
                RecoveryServiceMetadataEntry.COLUMN_NAME_CERT_SERIAL);
    }

    /**
@@ -399,13 +402,16 @@ public class RecoverableKeyStoreDb {
     *
     * @param userId The userId of the profile the application is running under.
     * @param uid The uid of the application who initializes the local recovery components.
     * @param rootAlias The root of trust alias.
     * @param serial The serial number contained in the XML file for recovery service certificates.
     * @return The primary key of the inserted row, or -1 if failed.
     *
     * @hide
     */
    public long setRecoveryServiceCertSerial(int userId, int uid, long serial) {
        return setLong(userId, uid, RecoveryServiceMetadataEntry.COLUMN_NAME_CERT_SERIAL, serial);
    public long setRecoveryServiceCertSerial(int userId, int uid, @NonNull String rootAlias,
            long serial) {
        return setLong(userId, uid, rootAlias, RecoveryServiceMetadataEntry.COLUMN_NAME_CERT_SERIAL,
                serial);
    }

    /**
@@ -413,13 +419,15 @@ public class RecoverableKeyStoreDb {
     *
     * @param userId The userId of the profile the application is running under.
     * @param uid The uid of the application who initializes the local recovery components.
     * @param rootAlias The root of trust alias.
     * @return The value that were previously set, or null if there's none.
     *
     * @hide
     */
    @Nullable
    public CertPath getRecoveryServiceCertPath(int userId, int uid) {
        byte[] bytes = getBytes(userId, uid, RecoveryServiceMetadataEntry.COLUMN_NAME_CERT_PATH);
    public CertPath getRecoveryServiceCertPath(int userId, int uid, @NonNull String rootAlias) {
        byte[] bytes = getBytes(userId, uid, rootAlias,
                RecoveryServiceMetadataEntry.COLUMN_NAME_CERT_PATH);
        if (bytes == null) {
            return null;
        }
@@ -440,16 +448,17 @@ public class RecoverableKeyStoreDb {
     *
     * @param userId The userId of the profile the application is running under.
     * @param uid The uid of the application who initializes the local recovery components.
     * @param rootAlias The root of trust alias.
     * @param certPath The certificate path of the recovery service.
     * @return The primary key of the inserted row, or -1 if failed.
     * @hide
     */
    public long setRecoveryServiceCertPath(int userId, int uid, CertPath certPath) throws
            CertificateEncodingException {
    public long setRecoveryServiceCertPath(int userId, int uid, @NonNull String rootAlias,
            CertPath certPath) throws CertificateEncodingException {
        if (certPath.getCertificates().size() == 0) {
            throw new CertificateEncodingException("No certificate contained in the cert path.");
        }
        return setBytes(userId, uid, RecoveryServiceMetadataEntry.COLUMN_NAME_CERT_PATH,
        return setBytes(userId, uid, rootAlias, RecoveryServiceMetadataEntry.COLUMN_NAME_CERT_PATH,
                certPath.getEncoded(CERT_PATH_ENCODING));
    }

@@ -607,6 +616,85 @@ public class RecoverableKeyStoreDb {
        }
    }

    /**
     * Active root of trust for the recovery agent.
     *
     * @param userId The userId of the profile the application is running under.
     * @param uid The uid of the application.
     * @param rootAlias The root of trust alias.
     * @return The primary key of the updated row, or -1 if failed.
     *
     * @hide
     */
    public long setActiveRootOfTrust(int userId, int uid, @Nullable String rootAlias) {
        SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
        ContentValues values = new ContentValues();
        values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_ACTIVE_ROOT_OF_TRUST, rootAlias);
        String selection =
                RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND "
                + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?";
        ensureRecoveryServiceMetadataEntryExists(userId, uid);
        return db.update(RecoveryServiceMetadataEntry.TABLE_NAME, values,
            selection, new String[] {String.valueOf(userId), String.valueOf(uid)});
    }

    /**
     * Active root of trust for the recovery agent.
     *
     * @param userId The userId of the profile the application is running under.
     * @param uid The uid of the application who initialized the local recovery components.
     * @return Active root of trust alias of null if it was not set
     *
     * @hide
     */
    public @Nullable String getActiveRootOfTrust(int userId, int uid) {
        SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();

        String[] projection = {
                RecoveryServiceMetadataEntry._ID,
                RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID,
                RecoveryServiceMetadataEntry.COLUMN_NAME_UID,
                RecoveryServiceMetadataEntry.COLUMN_NAME_ACTIVE_ROOT_OF_TRUST};
        String selection =
                RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND "
                        + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?";
        String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)};

        try (
                Cursor cursor = db.query(
                        RecoveryServiceMetadataEntry.TABLE_NAME,
                        projection,
                        selection,
                        selectionArguments,
                        /*groupBy=*/ null,
                        /*having=*/ null,
                        /*orderBy=*/ null)
        ) {
            int count = cursor.getCount();
            if (count == 0) {
                return null;
            }
            if (count > 1) {
                Log.wtf(TAG,
                        String.format(Locale.US,
                                "%d deviceId entries found for userId=%d uid=%d. "
                                        + "Should only ever be 0 or 1.", count, userId, uid));
                return null;
            }
            cursor.moveToFirst();
            int idx = cursor.getColumnIndexOrThrow(
                    RecoveryServiceMetadataEntry.COLUMN_NAME_ACTIVE_ROOT_OF_TRUST);
            if (cursor.isNull(idx)) {
                return null;
            }
            String result = cursor.getString(idx);
            if (TextUtils.isEmpty(result)) {
                return null;
            }
            return result;
        }
    }

    /**
     * Updates the counterId
     *
@@ -874,7 +962,6 @@ public class RecoverableKeyStoreDb {
     *
     * @hide
     */

    private long setBytes(int userId, int uid, String key, byte[] value) {
        SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
        ContentValues values = new ContentValues();
@@ -889,6 +976,176 @@ public class RecoverableKeyStoreDb {
                RecoveryServiceMetadataEntry.TABLE_NAME, values, selection, selectionArguments);
    }

    /**
     * Returns given binary value from the database.
     *
     * @param userId The userId of the profile the application is running under.
     * @param uid The uid of the application who initialized the local recovery components.
     * @param rootAlias The root of trust alias.
     * @param key from {@code RootOfTrustEntry}
     * @return The value that were previously set, or null if there's none.
     *
     * @hide
     */
    private byte[] getBytes(int userId, int uid, String rootAlias, String key) {
        SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();

        String[] projection = {
                RootOfTrustEntry._ID,
                RootOfTrustEntry.COLUMN_NAME_USER_ID,
                RootOfTrustEntry.COLUMN_NAME_UID,
                RootOfTrustEntry.COLUMN_NAME_ROOT_ALIAS,
                key};
        String selection =
                RootOfTrustEntry.COLUMN_NAME_USER_ID + " = ? AND "
                        + RootOfTrustEntry.COLUMN_NAME_UID + " = ? AND "
                        + RootOfTrustEntry.COLUMN_NAME_ROOT_ALIAS + " = ?";
        String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid), rootAlias};

        try (
            Cursor cursor = db.query(
                    RootOfTrustEntry.TABLE_NAME,
                    projection,
                    selection,
                    selectionArguments,
                    /*groupBy=*/ null,
                    /*having=*/ null,
                    /*orderBy=*/ null)
        ) {
            int count = cursor.getCount();
            if (count == 0) {
                return null;
            }
            if (count > 1) {
                Log.wtf(TAG,
                        String.format(Locale.US,
                                "%d entries found for userId=%d uid=%d. "
                                        + "Should only ever be 0 or 1.", count, userId, uid));
                return null;
            }
            cursor.moveToFirst();
            int idx = cursor.getColumnIndexOrThrow(key);
            if (cursor.isNull(idx)) {
                return null;
            } else {
                return cursor.getBlob(idx);
            }
        }
    }

    /**
     * Sets a binary value in the database.
     *
     * @param userId The userId of the profile the application is running under.
     * @param uid The uid of the application who initialized the local recovery components.
     * @param rootAlias The root of trust alias.
     * @param key defined in {@code RootOfTrustEntry}
     * @param value new value.
     * @return The primary key of the inserted row, or -1 if failed.
     *
     * @hide
     */
    private long setBytes(int userId, int uid, String rootAlias, String key, byte[] value) {
        SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
        ContentValues values = new ContentValues();
        values.put(key, value);
        String selection =
                RootOfTrustEntry.COLUMN_NAME_USER_ID + " = ? AND "
                        + RootOfTrustEntry.COLUMN_NAME_UID + " = ? AND "
                        + RootOfTrustEntry.COLUMN_NAME_ROOT_ALIAS + " = ?";
        String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid), rootAlias};

        ensureRootOfTrustEntryExists(userId, uid, rootAlias);
        return db.update(
                RootOfTrustEntry.TABLE_NAME, values, selection, selectionArguments);
    }

    /**
     * Returns given long value from the database.
     *
     * @param userId The userId of the profile the application is running under.
     * @param uid The uid of the application who initialized the local recovery components.
     * @param rootAlias The root of trust alias.
     * @param key from {@code RootOfTrustEntry}
     * @return The value that were previously set, or null if there's none.
     *
     * @hide
     */
    private Long getLong(int userId, int uid, String rootAlias, String key) {
        SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();

        String[] projection = {
                RootOfTrustEntry._ID,
                RootOfTrustEntry.COLUMN_NAME_USER_ID,
                RootOfTrustEntry.COLUMN_NAME_UID,
                RootOfTrustEntry.COLUMN_NAME_ROOT_ALIAS,
                key};
        String selection =
                RootOfTrustEntry.COLUMN_NAME_USER_ID + " = ? AND "
                        + RootOfTrustEntry.COLUMN_NAME_UID + " = ? AND "
                        + RootOfTrustEntry.COLUMN_NAME_ROOT_ALIAS + " = ?";
        String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid), rootAlias};

        try (
            Cursor cursor = db.query(
                    RootOfTrustEntry.TABLE_NAME,
                    projection,
                    selection,
                    selectionArguments,
                    /*groupBy=*/ null,
                    /*having=*/ null,
                    /*orderBy=*/ null)
        ) {
            int count = cursor.getCount();
            if (count == 0) {
                return null;
            }
            if (count > 1) {
                Log.wtf(TAG,
                        String.format(Locale.US,
                                "%d entries found for userId=%d uid=%d. "
                                        + "Should only ever be 0 or 1.", count, userId, uid));
                return null;
            }
            cursor.moveToFirst();
            int idx = cursor.getColumnIndexOrThrow(key);
            if (cursor.isNull(idx)) {
                return null;
            } else {
                return cursor.getLong(idx);
            }
        }
    }

    /**
     * Sets a long value in the database.
     *
     * @param userId The userId of the profile the application is running under.
     * @param uid The uid of the application who initialized the local recovery components.
     * @param rootAlias The root of trust alias.
     * @param key defined in {@code RootOfTrustEntry}
     * @param value new value.
     * @return The primary key of the inserted row, or -1 if failed.
     *
     * @hide
     */

    private long setLong(int userId, int uid, String rootAlias, String key, long value) {
        SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
        ContentValues values = new ContentValues();
        values.put(key, value);
        String selection =
                RootOfTrustEntry.COLUMN_NAME_USER_ID + " = ? AND "
                        + RootOfTrustEntry.COLUMN_NAME_UID + " = ? AND "
                        + RootOfTrustEntry.COLUMN_NAME_ROOT_ALIAS + " = ?";
        String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid), rootAlias};

        ensureRootOfTrustEntryExists(userId, uid, rootAlias);
        return db.update(
                RootOfTrustEntry.TABLE_NAME, values, selection, selectionArguments);
    }


    /**
     * Creates an empty row in the recovery service metadata table if such a row doesn't exist for
     * the given userId and uid, so db.update will succeed.
@@ -902,6 +1159,20 @@ public class RecoverableKeyStoreDb {
                values, SQLiteDatabase.CONFLICT_IGNORE);
    }

    /**
     * Creates an empty row in the root of trust table if such a row doesn't exist for
     * the given userId and uid, so db.update will succeed.
     */
    private void ensureRootOfTrustEntryExists(int userId, int uid, String rootAlias) {
        SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
        ContentValues values = new ContentValues();
        values.put(RootOfTrustEntry.COLUMN_NAME_USER_ID, userId);
        values.put(RootOfTrustEntry.COLUMN_NAME_UID, uid);
        values.put(RootOfTrustEntry.COLUMN_NAME_ROOT_ALIAS, rootAlias);
        db.insertWithOnConflict(RootOfTrustEntry.TABLE_NAME, /*nullColumnHack=*/ null,
                values, SQLiteDatabase.CONFLICT_IGNORE);
    }

    /**
     * Closes all open connections to the database.
     */
+40 −0

File changed.

Preview size limit exceeded, changes collapsed.

+56 −5

File changed.

Preview size limit exceeded, changes collapsed.

Loading