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

Commit 4120a4d0 authored by Dmitry Dementyev's avatar Dmitry Dementyev Committed by android-build-merger
Browse files

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

am: 52bc5f2a

Change-Id: Id3926848ae5fdad1851b4193319a08652642d1e6
parents 581ae181 52bc5f2a
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