Use rootAlias to index chosen cert and its version.

Added new column  to store active alias for given recovery agent.
Added new table with chosen certififcate and cert list serial number indexed
by recovery agent and root of trust.

Bug: 76433465
Test: adb shell am instrument -w -e package
com.android.server.locksettings.recoverablekeystore
com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner

Change-Id: Iae8b84312805400bf1acd4db242efeb6d167c000
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
index a87adbd..050c1f4 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
@@ -19,10 +19,12 @@
 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 @@
         }
 
         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 @@
             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 @@
             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 @@
         }
         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;
+    }
 }
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
index 6d2bec8..30125f8 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
@@ -176,6 +176,20 @@
         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 @@
 
         // 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 @@
                     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 @@
             @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 @@
             @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 @@
     }
 
     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 @@
         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,
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java
index 2676ee8..38834ac 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java
@@ -29,6 +29,7 @@
 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 @@
      *
      * @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 @@
      *
      * @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 @@
      *
      * @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 @@
      *
      * @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));
     }
 
@@ -608,6 +617,85 @@
     }
 
     /**
+     * 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
      *
      * @param userId The userId of the profile the application is running under.
@@ -874,7 +962,6 @@
      *
      * @hide
      */
-
     private long setBytes(int userId, int uid, String key, byte[] value) {
         SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
         ContentValues values = new ContentValues();
@@ -890,6 +977,176 @@
     }
 
     /**
+     * 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.
      */
@@ -903,6 +1160,20 @@
     }
 
     /**
+     * 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.
      */
     public void close() {
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java
index 2c3d3ab..1eff2d4 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java
@@ -230,16 +230,19 @@
 
         /**
          * The public key of the recovery service.
+         * Deprecated.
          */
         static final String COLUMN_NAME_PUBLIC_KEY = "public_key";
 
         /**
          * The certificate path of the recovery service.
+         * Deprecated.
          */
         static final String COLUMN_NAME_CERT_PATH = "cert_path";
 
         /**
          * The serial number contained in the certificate XML file of the recovery service.
+         * Deprecated.
          */
         static final String COLUMN_NAME_CERT_SERIAL = "cert_serial";
 
@@ -257,5 +260,42 @@
          * The server parameters of the recovery service.
          */
         static final String COLUMN_NAME_SERVER_PARAMS = "server_params";
+
+        /**
+         * Active root of trust
+         */
+        static final String COLUMN_NAME_ACTIVE_ROOT_OF_TRUST = "active_root_of_trust";
+    }
+
+    /**
+     * Table data for given recovery agent and root of trust pair.
+     */
+    static class RootOfTrustEntry implements BaseColumns {
+        static final String TABLE_NAME = "root_of_trust";
+
+        /**
+         * The user id of the profile the application is running under.
+         */
+        static final String COLUMN_NAME_USER_ID = "user_id";
+
+        /**
+         * The uid of the application that initializes the local recovery components.
+         */
+        static final String COLUMN_NAME_UID = "uid";
+
+        /**
+         * Root of trust alias
+         */
+        static final String COLUMN_NAME_ROOT_ALIAS = "root_alias";
+
+        /**
+         * The certificate path of the recovery service.
+         */
+        static final String COLUMN_NAME_CERT_PATH = "cert_path";
+
+        /**
+         * The serial number contained in the certificate XML file of the recovery service.
+         */
+        static final String COLUMN_NAME_CERT_SERIAL = "cert_serial";
     }
 }
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java
index 8a89f2d..43efe9c 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java
@@ -23,6 +23,7 @@
 
 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;
 
 /**
@@ -31,7 +32,7 @@
 class RecoverableKeyStoreDbHelper extends SQLiteOpenHelper {
     private static final String TAG = "RecoverableKeyStoreDbHp";
 
-    static final int DATABASE_VERSION = 3;
+    static final int DATABASE_VERSION = 4;
     private static final String DATABASE_NAME = "recoverablekeystore.db";
 
     private static final String SQL_CREATE_KEYS_ENTRY =
@@ -61,6 +62,7 @@
                     + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " INTEGER,"
                     + RecoveryServiceMetadataEntry.COLUMN_NAME_SNAPSHOT_VERSION + " INTEGER,"
                     + RecoveryServiceMetadataEntry.COLUMN_NAME_SHOULD_CREATE_SNAPSHOT + " INTEGER,"
+                    + RecoveryServiceMetadataEntry.COLUMN_NAME_ACTIVE_ROOT_OF_TRUST + " TEXT,"
                     + RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY + " BLOB,"
                     + RecoveryServiceMetadataEntry.COLUMN_NAME_CERT_PATH + " BLOB,"
                     + RecoveryServiceMetadataEntry.COLUMN_NAME_CERT_SERIAL + " INTEGER,"
@@ -71,6 +73,19 @@
                     + RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID  + ","
                     + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + "))";
 
+    private static final String SQL_CREATE_ROOT_OF_TRUST_ENTRY =
+            "CREATE TABLE " + RootOfTrustEntry.TABLE_NAME + " ("
+                    + RootOfTrustEntry._ID + " INTEGER PRIMARY KEY,"
+                    + RootOfTrustEntry.COLUMN_NAME_USER_ID + " INTEGER,"
+                    + RootOfTrustEntry.COLUMN_NAME_UID + " INTEGER,"
+                    + RootOfTrustEntry.COLUMN_NAME_ROOT_ALIAS + " TEST,"
+                    + RootOfTrustEntry.COLUMN_NAME_CERT_PATH + " BLOB,"
+                    + RootOfTrustEntry.COLUMN_NAME_CERT_SERIAL + " INTEGER,"
+                    + "UNIQUE("
+                    + RootOfTrustEntry.COLUMN_NAME_USER_ID  + ","
+                    + RootOfTrustEntry.COLUMN_NAME_UID  + ","
+                    + RootOfTrustEntry.COLUMN_NAME_ROOT_ALIAS + "))";
+
     private static final String SQL_DELETE_KEYS_ENTRY =
             "DROP TABLE IF EXISTS " + KeysEntry.TABLE_NAME;
 
@@ -80,6 +95,9 @@
     private static final String SQL_DELETE_RECOVERY_SERVICE_METADATA_ENTRY =
             "DROP TABLE IF EXISTS " + RecoveryServiceMetadataEntry.TABLE_NAME;
 
+    private static final String SQL_DELETE_ROOT_OF_TRUST_ENTRY =
+            "DROP TABLE IF EXISTS " + RootOfTrustEntry.TABLE_NAME;
+
     RecoverableKeyStoreDbHelper(Context context) {
         super(context, DATABASE_NAME, null, DATABASE_VERSION);
     }
@@ -89,21 +107,44 @@
         db.execSQL(SQL_CREATE_KEYS_ENTRY);
         db.execSQL(SQL_CREATE_USER_METADATA_ENTRY);
         db.execSQL(SQL_CREATE_RECOVERY_SERVICE_METADATA_ENTRY);
+        db.execSQL(SQL_CREATE_ROOT_OF_TRUST_ENTRY);
+    }
+
+    @Override
+    public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+        Log.e(TAG, "Recreating recoverablekeystore after unexpected version downgrade.");
+        dropAllKnownTables(db); // Wipe database.
+        onCreate(db);
     }
 
     @Override
     public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
         if (oldVersion < 2) {
-            db.execSQL(SQL_DELETE_KEYS_ENTRY);
-            db.execSQL(SQL_DELETE_USER_METADATA_ENTRY);
-            db.execSQL(SQL_DELETE_RECOVERY_SERVICE_METADATA_ENTRY);
+            dropAllKnownTables(db); // Wipe database.
             onCreate(db);
             return;
         }
 
-        if (oldVersion < 3) {
+        if (oldVersion < 3 && newVersion >= 3) {
             upgradeDbForVersion3(db);
+            oldVersion = 3;
         }
+
+        if (oldVersion < 4 && newVersion >= 4) {
+            upgradeDbForVersion4(db);
+            oldVersion = 4;
+        }
+
+        if (oldVersion != newVersion) {
+            Log.e(TAG, "Failed to update recoverablekeystore database to the most recent version");
+        }
+    }
+
+    private void dropAllKnownTables(SQLiteDatabase db) {
+            db.execSQL(SQL_DELETE_KEYS_ENTRY);
+            db.execSQL(SQL_DELETE_USER_METADATA_ENTRY);
+            db.execSQL(SQL_DELETE_RECOVERY_SERVICE_METADATA_ENTRY);
+            db.execSQL(SQL_DELETE_ROOT_OF_TRUST_ENTRY);
     }
 
     private void upgradeDbForVersion3(SQLiteDatabase db) {
@@ -115,6 +156,16 @@
                 null);
     }
 
+    private void upgradeDbForVersion4(SQLiteDatabase db) {
+        Log.d(TAG, "Updating recoverable keystore database to version 4");
+        // Add new table with two columns for cert path and cert serial number.
+        db.execSQL(SQL_CREATE_ROOT_OF_TRUST_ENTRY);
+        // adds column to store root of trust currently used by the recovery agent
+        addColumnToTable(db, RecoveryServiceMetadataEntry.TABLE_NAME,
+                RecoveryServiceMetadataEntry.COLUMN_NAME_ACTIVE_ROOT_OF_TRUST, "TEXT",
+                /*defaultStr=*/ null);
+    }
+
     private static void addColumnToTable(
             SQLiteDatabase db, String tableName, String column, String columnType,
             String defaultStr) {
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
index 0ea2317..9ae45ea 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
@@ -74,6 +74,7 @@
 public class KeySyncTaskTest {
     private static final String KEY_ALGORITHM = "AES";
     private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore";
+    private static final String TEST_ROOT_CERT_ALIAS = "trusted_root";
     private static final String WRAPPING_KEY_ALIAS = "KeySyncTaskTest/WrappingKey";
     private static final String DATABASE_FILE_NAME = "recoverablekeystore.db";
     private static final int TEST_USER_ID = 1000;
@@ -111,6 +112,11 @@
                 new int[] {TYPE_LOCKSCREEN});
         mRecoverableKeyStoreDb.setRecoverySecretTypes(TEST_USER_ID, TEST_RECOVERY_AGENT_UID2,
                 new int[] {TYPE_LOCKSCREEN});
+
+        mRecoverableKeyStoreDb.setActiveRootOfTrust(TEST_USER_ID, TEST_RECOVERY_AGENT_UID,
+                TEST_ROOT_CERT_ALIAS);
+        mRecoverableKeyStoreDb.setActiveRootOfTrust(TEST_USER_ID, TEST_RECOVERY_AGENT_UID2,
+                TEST_ROOT_CERT_ALIAS);
         mRecoverySnapshotStorage = new RecoverySnapshotStorage();
 
         mKeySyncTask = new KeySyncTask(
@@ -247,7 +253,7 @@
                 TEST_APP_KEY_ALIAS,
                 WrappedKey.fromSecretKey(mEncryptKey, applicationKey));
         mRecoverableKeyStoreDb.setRecoveryServiceCertPath(
-                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TestData.CERT_PATH_1);
+                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1);
         when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);
 
         mKeySyncTask.run();
@@ -263,7 +269,7 @@
         mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID);
         addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS);
         mRecoverableKeyStoreDb.setRecoveryServiceCertPath(
-                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TestData.CERT_PATH_1);
+                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1);
 
         mKeySyncTask.run();
 
@@ -273,7 +279,7 @@
     @Test
     public void run_sendsEncryptedKeysIfAvailableToSync_withRawPublicKey() throws Exception {
         mRecoverableKeyStoreDb.setRecoveryServiceCertPath(
-                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TestData.CERT_PATH_1);
+                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1);
 
         mRecoverableKeyStoreDb.setServerParams(
                 TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_VAULT_HANDLE);
@@ -320,7 +326,7 @@
     @Test
     public void run_sendsEncryptedKeysIfAvailableToSync_withCertPath() throws Exception {
         mRecoverableKeyStoreDb.setRecoveryServiceCertPath(
-                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TestData.CERT_PATH_1);
+                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1);
         mRecoverableKeyStoreDb.setServerParams(
                 TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_VAULT_HANDLE);
         when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);
@@ -339,7 +345,7 @@
     @Test
     public void run_setsCorrectSnapshotVersion() throws Exception {
         mRecoverableKeyStoreDb.setRecoveryServiceCertPath(
-                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TestData.CERT_PATH_1);
+                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1);
         when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);
         addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS);
 
@@ -358,7 +364,7 @@
     @Test
     public void run_recreatesMissingSnapshot() throws Exception {
         mRecoverableKeyStoreDb.setRecoveryServiceCertPath(
-                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TestData.CERT_PATH_1);
+                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1);
         when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);
         addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS);
 
@@ -388,7 +394,7 @@
                 mPlatformKeyManager);
 
         mRecoverableKeyStoreDb.setRecoveryServiceCertPath(
-                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TestData.CERT_PATH_1);
+                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1);
         when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);
         SecretKey applicationKey =
                 addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS);
@@ -414,7 +420,7 @@
                 mPlatformKeyManager);
 
         mRecoverableKeyStoreDb.setRecoveryServiceCertPath(
-                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TestData.CERT_PATH_1);
+                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1);
         when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);
         SecretKey applicationKey =
                 addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS);
@@ -441,7 +447,7 @@
                 mPlatformKeyManager);
 
         mRecoverableKeyStoreDb.setRecoveryServiceCertPath(
-                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TestData.CERT_PATH_1);
+                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1);
         when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);
         SecretKey applicationKey =
                 addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS);
@@ -457,9 +463,9 @@
     @Test
     public void run_sendsEncryptedKeysWithTwoRegisteredAgents() throws Exception {
         mRecoverableKeyStoreDb.setRecoveryServiceCertPath(
-                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TestData.CERT_PATH_1);
+                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1);
         mRecoverableKeyStoreDb.setRecoveryServiceCertPath(
-                TEST_USER_ID, TEST_RECOVERY_AGENT_UID2, TestData.CERT_PATH_1);
+                TEST_USER_ID, TEST_RECOVERY_AGENT_UID2, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1);
         when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);
         when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID2)).thenReturn(true);
         addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS);
@@ -479,9 +485,9 @@
                 new int[] {1000});
 
         mRecoverableKeyStoreDb.setRecoveryServiceCertPath(
-                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TestData.CERT_PATH_1);
+                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1);
         mRecoverableKeyStoreDb.setRecoveryServiceCertPath(
-                TEST_USER_ID, TEST_RECOVERY_AGENT_UID2, TestData.CERT_PATH_1);
+                TEST_USER_ID, TEST_RECOVERY_AGENT_UID2, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1);
         when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);
         when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID2)).thenReturn(true);
         addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS);
@@ -496,9 +502,9 @@
     @Test
     public void run_notifiesNonregisteredAgent() throws Exception {
         mRecoverableKeyStoreDb.setRecoveryServiceCertPath(
-                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TestData.CERT_PATH_1);
+                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1);
         mRecoverableKeyStoreDb.setRecoveryServiceCertPath(
-                TEST_USER_ID, TEST_RECOVERY_AGENT_UID2, TestData.CERT_PATH_1);
+                TEST_USER_ID, TEST_RECOVERY_AGENT_UID2, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1);
         when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);
         when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID2)).thenReturn(false);
         addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
index 8db2537..f5f5027 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
@@ -44,9 +44,10 @@
 import android.security.keystore.AndroidKeyStoreSecretKey;
 import android.security.keystore.KeyGenParameterSpec;
 import android.security.keystore.KeyProperties;
-import android.security.keystore.recovery.KeyDerivationParams;
 import android.security.keystore.recovery.KeyChainProtectionParams;
+import android.security.keystore.recovery.KeyDerivationParams;
 import android.security.keystore.recovery.RecoveryCertPath;
+import android.security.keystore.recovery.TrustedRootCertificates;
 import android.security.keystore.recovery.WrappedApplicationKey;
 import android.support.test.filters.SmallTest;
 import android.support.test.InstrumentationRegistry;
@@ -90,6 +91,8 @@
     private static final String DATABASE_FILE_NAME = "recoverablekeystore.db";
 
     private static final String ROOT_CERTIFICATE_ALIAS = "";
+    private static final String DEFAULT_ROOT_CERT_ALIAS =
+            TrustedRootCertificates.GOOGLE_CLOUD_KEY_VAULT_SERVICE_V1_ALIAS;
     private static final String TEST_SESSION_ID = "karlin";
     private static final byte[] TEST_PUBLIC_KEY = new byte[] {
         (byte) 0x30, (byte) 0x59, (byte) 0x30, (byte) 0x13, (byte) 0x06, (byte) 0x07, (byte) 0x2a,
@@ -143,7 +146,7 @@
     private static final String KEY_ALGORITHM = "AES";
     private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore";
     private static final String WRAPPING_KEY_ALIAS = "RecoverableKeyStoreManagerTest/WrappingKey";
-    private static final String TEST_ROOT_CERT_ALIAS = "";
+    private static final String TEST_DEFAULT_ROOT_CERT_ALIAS = "";
     private static final KeyChainProtectionParams TEST_PROTECTION_PARAMS =
     new KeyChainProtectionParams.Builder()
             .setUserSecretType(TYPE_LOCKSCREEN)
@@ -299,10 +302,10 @@
                 TestData.getCertXmlWithSerial(certSerial));
 
         assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isFalse();
-        assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertPath(userId, uid)).isEqualTo(
-                TestData.CERT_PATH_1);
-        assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertSerial(userId, uid)).isEqualTo(
-                certSerial);
+        assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertPath(userId, uid,
+                DEFAULT_ROOT_CERT_ALIAS)).isEqualTo(TestData.CERT_PATH_1);
+        assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertSerial(userId, uid,
+                DEFAULT_ROOT_CERT_ALIAS)).isEqualTo(certSerial);
         assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isNull();
     }
 
@@ -328,7 +331,8 @@
         byte[] modifiedCertXml = TestData.getCertXml();
         modifiedCertXml[modifiedCertXml.length - 50] ^= 1;  // Flip a bit in the certificate
         try {
-            mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS, modifiedCertXml);
+            mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS,
+                    modifiedCertXml);
             fail("should have thrown");
         } catch (ServiceSpecificException e) {
             assertThat(e.getMessage()).contains("validate cert");
@@ -346,8 +350,8 @@
         mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS,
                 TestData.getCertXmlWithSerial(certSerial + 1));
 
-        assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertSerial(userId, uid))
-                .isEqualTo(certSerial + 1);
+        assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertSerial(userId, uid,
+                DEFAULT_ROOT_CERT_ALIAS)).isEqualTo(certSerial + 1);
         assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isTrue();
     }
 
@@ -362,8 +366,8 @@
         mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS,
                 TestData.getCertXmlWithSerial(certSerial - 1));
 
-        assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertSerial(userId, uid))
-                .isEqualTo(certSerial);
+        assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertSerial(userId, uid,
+                DEFAULT_ROOT_CERT_ALIAS)).isEqualTo(certSerial);
         assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isFalse();
     }
 
@@ -391,8 +395,10 @@
         mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS, TEST_PUBLIC_KEY);
 
         assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isTrue();
-        assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertPath(userId, uid)).isNull();
-        assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertSerial(userId, uid)).isNull();
+        assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertPath(userId, uid,
+                DEFAULT_ROOT_CERT_ALIAS)).isNull();
+        assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertSerial(userId, uid,
+                DEFAULT_ROOT_CERT_ALIAS)).isNull();
         assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isNotNull();
     }
 
@@ -406,8 +412,8 @@
                 ROOT_CERTIFICATE_ALIAS, TestData.getCertXml(), TestData.getSigXml());
 
         assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isFalse();
-        assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertPath(userId, uid)).isEqualTo(
-                TestData.CERT_PATH_1);
+        assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertPath(userId, uid,
+                DEFAULT_ROOT_CERT_ALIAS)).isEqualTo(TestData.CERT_PATH_1);
         assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isNull();
     }
 
@@ -480,7 +486,7 @@
     public void startRecoverySessionWithCertPath_storesTheSessionInfo() throws Exception {
         mRecoverableKeyStoreManager.startRecoverySessionWithCertPath(
                 TEST_SESSION_ID,
-                TEST_ROOT_CERT_ALIAS,
+                TEST_DEFAULT_ROOT_CERT_ALIAS,
                 RecoveryCertPath.createRecoveryCertPath(TestData.CERT_PATH_1),
                 TEST_VAULT_PARAMS,
                 TEST_VAULT_CHALLENGE,
@@ -497,7 +503,7 @@
     public void startRecoverySessionWithCertPath_checksPermissionFirst() throws Exception {
         mRecoverableKeyStoreManager.startRecoverySessionWithCertPath(
                 TEST_SESSION_ID,
-                TEST_ROOT_CERT_ALIAS,
+                TEST_DEFAULT_ROOT_CERT_ALIAS,
                 RecoveryCertPath.createRecoveryCertPath(TestData.CERT_PATH_1),
                 TEST_VAULT_PARAMS,
                 TEST_VAULT_CHALLENGE,
@@ -601,7 +607,7 @@
         try {
             mRecoverableKeyStoreManager.startRecoverySessionWithCertPath(
                     TEST_SESSION_ID,
-                    TEST_ROOT_CERT_ALIAS,
+                    TEST_DEFAULT_ROOT_CERT_ALIAS,
                     RecoveryCertPath.createRecoveryCertPath(TestData.CERT_PATH_1),
                     TEST_VAULT_PARAMS,
                     TEST_VAULT_CHALLENGE,
@@ -620,7 +626,7 @@
         try {
             mRecoverableKeyStoreManager.startRecoverySessionWithCertPath(
                     TEST_SESSION_ID,
-                    TEST_ROOT_CERT_ALIAS,
+                    TEST_DEFAULT_ROOT_CERT_ALIAS,
                     RecoveryCertPath.createRecoveryCertPath(TestData.CERT_PATH_1),
                     vaultParams,
                     TEST_VAULT_CHALLENGE,
@@ -638,7 +644,7 @@
         try {
             mRecoverableKeyStoreManager.startRecoverySessionWithCertPath(
                     TEST_SESSION_ID,
-                    TEST_ROOT_CERT_ALIAS,
+                    TEST_DEFAULT_ROOT_CERT_ALIAS,
                     RecoveryCertPath.createRecoveryCertPath(emptyCertPath),
                     TEST_VAULT_PARAMS,
                     TEST_VAULT_CHALLENGE,
@@ -658,7 +664,7 @@
         try {
             mRecoverableKeyStoreManager.startRecoverySessionWithCertPath(
                     TEST_SESSION_ID,
-                    TEST_ROOT_CERT_ALIAS,
+                    TEST_DEFAULT_ROOT_CERT_ALIAS,
                     RecoveryCertPath.createRecoveryCertPath(shortCertPath),
                     TEST_VAULT_PARAMS,
                     TEST_VAULT_CHALLENGE,
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelperTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelperTest.java
index 37482a3..9b09dd1a 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelperTest.java
@@ -34,6 +34,7 @@
 
 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;
 
 @SmallTest
@@ -55,6 +56,7 @@
     private static final String TEST_SECRET_TYPES = "test-secret-types";
     private static final long TEST_COUNTER_ID = -3981205205038476415L;
     private static final byte[] TEST_SERVER_PARAMS = "test-server-params".getBytes(UTF_8);
+    private static final String TEST_ROOT_ALIAS = "root_cert_alias";
     private static final byte[] TEST_CERT_PATH = "test-cert-path".getBytes(UTF_8);
     private static final long TEST_CERT_SERIAL = 1000L;
 
@@ -135,6 +137,32 @@
         checkAllColumns();
     }
 
+    @Test
+    public void onUpgrade_v2_to_v3_to_v4() throws Exception {
+        createV2Tables();
+
+        assertThat(isRootOfTrustTableAvailable()).isFalse(); // V2 doesn't have the table;
+
+        mDatabaseHelper.onUpgrade(mDatabase, /*oldVersion=*/ 2, /*newVersion=*/ 3);
+
+        assertThat(isRootOfTrustTableAvailable()).isFalse(); // V3 doesn't have the table;
+
+        mDatabaseHelper.onUpgrade(mDatabase, /*oldVersion=*/ 3,
+                RecoverableKeyStoreDbHelper.DATABASE_VERSION);
+        checkAllColumns();
+    }
+
+    private boolean isRootOfTrustTableAvailable() {
+        ContentValues values = new ContentValues();
+        values.put(RootOfTrustEntry.COLUMN_NAME_USER_ID, TEST_USER_ID);
+        values.put(RootOfTrustEntry.COLUMN_NAME_UID, TEST_UID);
+        values.put(RootOfTrustEntry.COLUMN_NAME_ROOT_ALIAS, TEST_ROOT_ALIAS);
+        values.put(RootOfTrustEntry.COLUMN_NAME_CERT_PATH, TEST_CERT_PATH);
+        values.put(RootOfTrustEntry.COLUMN_NAME_CERT_SERIAL, TEST_CERT_SERIAL);
+        return mDatabase.insert(RootOfTrustEntry.TABLE_NAME, /*nullColumnHack=*/ null, values)
+                > -1;
+    }
+
     private void checkAllColumns() throws Exception {
         // Check the table containing encrypted application keys
         ContentValues values = new ContentValues();
@@ -165,6 +193,7 @@
                 TEST_SNAPSHOT_VERSION);
         values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_SHOULD_CREATE_SNAPSHOT,
                 TEST_SHOULD_CREATE_SNAPSHOT);
+        values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_ACTIVE_ROOT_OF_TRUST, TEST_ROOT_ALIAS);
         values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY, TEST_PUBLIC_KEY);
         values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_SECRET_TYPES, TEST_SECRET_TYPES);
         values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_COUNTER_ID, TEST_COUNTER_ID);
@@ -175,5 +204,8 @@
                 mDatabase.insert(RecoveryServiceMetadataEntry.TABLE_NAME, /*nullColumnHack=*/ null,
                         values))
                 .isGreaterThan(-1L);
+
+        // Check the table about recovery service and root of trust data introduced in V4
+        assertThat(isRootOfTrustTableAvailable()).isTrue();
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java
index 8b01d97..940745e 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java
@@ -49,6 +49,9 @@
 public class RecoverableKeyStoreDbTest {
     private static final String DATABASE_FILE_NAME = "recoverablekeystore.db";
 
+    private static final String TEST_ROOT_CERT_ALIAS = "trusted_root";
+    private static final String TEST_ROOT_CERT_ALIAS2 = "another_trusted_root";
+
     private RecoverableKeyStoreDb mRecoverableKeyStoreDb;
     private File mDatabaseFile;
 
@@ -284,7 +287,8 @@
 
         Map<String, Integer> statuses = mRecoverableKeyStoreDb.getStatusForAllKeys(uid);
         assertThat(statuses).hasSize(3);
-        assertThat(statuses).containsEntry(alias, RecoveryController.RECOVERY_STATUS_SYNC_IN_PROGRESS);
+        assertThat(statuses).containsEntry(alias,
+                RecoveryController.RECOVERY_STATUS_SYNC_IN_PROGRESS);
         assertThat(statuses).containsEntry(alias2, status);
         assertThat(statuses).containsEntry(alias3, status);
 
@@ -401,26 +405,53 @@
     public void setRecoveryServiceCertPath_replaceOldValue() throws Exception {
         int userId = 12;
         int uid = 10009;
-        mRecoverableKeyStoreDb.setRecoveryServiceCertPath(userId, uid, TestData.CERT_PATH_1);
-        mRecoverableKeyStoreDb.setRecoveryServiceCertPath(userId, uid, TestData.CERT_PATH_2);
-        assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertPath(userId, uid)).isEqualTo(
+        mRecoverableKeyStoreDb.setRecoveryServiceCertPath(userId, uid, TEST_ROOT_CERT_ALIAS,
+                TestData.CERT_PATH_1);
+        mRecoverableKeyStoreDb.setRecoveryServiceCertPath(userId, uid, TEST_ROOT_CERT_ALIAS,
                 TestData.CERT_PATH_2);
+        assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertPath(userId, uid,
+                TEST_ROOT_CERT_ALIAS)).isEqualTo(TestData.CERT_PATH_2);
+    }
+
+    @Test
+    public void setRecoveryServiceCertPath_updateValuesForCorrectRootCert() throws Exception {
+        int userId = 12;
+        int uid = 10009;
+        mRecoverableKeyStoreDb.setRecoveryServiceCertPath(userId, uid, TEST_ROOT_CERT_ALIAS,
+                TestData.CERT_PATH_1);
+        mRecoverableKeyStoreDb.setRecoveryServiceCertPath(userId, uid, TEST_ROOT_CERT_ALIAS2,
+                TestData.CERT_PATH_1);
+
+        assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertPath(userId, uid,
+                TEST_ROOT_CERT_ALIAS)).isEqualTo(TestData.CERT_PATH_1);
+        assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertPath(userId, uid,
+                TEST_ROOT_CERT_ALIAS2)).isEqualTo(TestData.CERT_PATH_1);
+
+        mRecoverableKeyStoreDb.setRecoveryServiceCertPath(userId, uid, TEST_ROOT_CERT_ALIAS2,
+                TestData.CERT_PATH_2);
+
+        assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertPath(userId, uid,
+                TEST_ROOT_CERT_ALIAS)).isEqualTo(TestData.CERT_PATH_1);
+        assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertPath(userId, uid,
+                TEST_ROOT_CERT_ALIAS2)).isEqualTo(TestData.CERT_PATH_2);
     }
 
     @Test
     public void getRecoveryServiceCertPath_returnsNullIfNoValue() throws Exception {
         int userId = 12;
         int uid = 10009;
-        assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertPath(userId, uid)).isNull();
+        assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertPath(userId, uid,
+                TEST_ROOT_CERT_ALIAS)).isNull();
     }
 
     @Test
     public void getRecoveryServiceCertPath_returnsInsertedValue() throws Exception {
         int userId = 12;
         int uid = 10009;
-        mRecoverableKeyStoreDb.setRecoveryServiceCertPath(userId, uid, TestData.CERT_PATH_1);
-        assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertPath(userId, uid)).isEqualTo(
+        mRecoverableKeyStoreDb.setRecoveryServiceCertPath(userId, uid, TEST_ROOT_CERT_ALIAS,
                 TestData.CERT_PATH_1);
+        assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertPath(userId, uid,
+                TEST_ROOT_CERT_ALIAS)).isEqualTo(TestData.CERT_PATH_1);
     }
 
     @Test
@@ -428,25 +459,50 @@
         int userId = 12;
         int uid = 10009;
 
-        mRecoverableKeyStoreDb.setRecoveryServiceCertSerial(userId, uid, 1L);
-        mRecoverableKeyStoreDb.setRecoveryServiceCertSerial(userId, uid, 3L);
-        assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertSerial(userId, uid)).isEqualTo(3L);
+        mRecoverableKeyStoreDb.setRecoveryServiceCertSerial(userId, uid, TEST_ROOT_CERT_ALIAS, 1L);
+        mRecoverableKeyStoreDb.setRecoveryServiceCertSerial(userId, uid, TEST_ROOT_CERT_ALIAS, 3L);
+        assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertSerial(userId, uid,
+                TEST_ROOT_CERT_ALIAS)).isEqualTo(3L);
+    }
+
+    @Test
+    public void setRecoveryServiceCertSerial_updateValuesForCorrectRootCert() throws Exception {
+        int userId = 12;
+        int uid = 10009;
+        mRecoverableKeyStoreDb.setRecoveryServiceCertSerial(userId, uid, TEST_ROOT_CERT_ALIAS, 1L);
+        mRecoverableKeyStoreDb.setRecoveryServiceCertSerial(userId, uid, TEST_ROOT_CERT_ALIAS2, 1L);
+
+        assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertSerial(userId, uid,
+                TEST_ROOT_CERT_ALIAS)).isEqualTo(1L);
+        assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertSerial(userId, uid,
+                TEST_ROOT_CERT_ALIAS2)).isEqualTo(1L);
+
+        mRecoverableKeyStoreDb.setRecoveryServiceCertSerial(userId, uid, TEST_ROOT_CERT_ALIAS2, 3L);
+
+        assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertSerial(userId, uid,
+                TEST_ROOT_CERT_ALIAS)).isEqualTo(1L);
+        assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertSerial(userId, uid,
+                TEST_ROOT_CERT_ALIAS2)).isEqualTo(3L);
     }
 
     @Test
     public void getRecoveryServiceCertSerial_returnsNullIfNoValue() throws Exception {
         int userId = 12;
         int uid = 10009;
-        assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertSerial(userId, uid)).isNull();
+        assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertSerial(userId, uid,
+                TEST_ROOT_CERT_ALIAS)).isNull();
+        assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertSerial(userId, uid,
+                TEST_ROOT_CERT_ALIAS2)).isNull();
     }
 
     @Test
     public void getRecoveryServiceCertSerial_returnsInsertedValue() throws Exception {
         int userId = 12;
         int uid = 10009;
-        mRecoverableKeyStoreDb.setRecoveryServiceCertSerial(userId, uid, 1234L);
-        assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertSerial(userId, uid)).isEqualTo(
-                1234L);
+        mRecoverableKeyStoreDb.setRecoveryServiceCertSerial(userId, uid,
+                TEST_ROOT_CERT_ALIAS, 1234L);
+        assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertSerial(userId, uid,
+                TEST_ROOT_CERT_ALIAS)).isEqualTo(1234L);
     }
 
     @Test
@@ -480,6 +536,24 @@
     }
 
     @Test
+    public void setActiveRootOfTrust_emptyDefaultValue() throws Exception {
+        int userId = 12;
+        int uid = 10009;
+        assertThat(mRecoverableKeyStoreDb.getActiveRootOfTrust(userId, uid)).isEqualTo(null);
+    }
+
+    @Test
+    public void setActiveRootOfTrust_updateValue() throws Exception {
+        int userId = 12;
+        int uid = 10009;
+        mRecoverableKeyStoreDb.setActiveRootOfTrust(userId, uid, "root");
+        assertThat(mRecoverableKeyStoreDb.getActiveRootOfTrust(userId, uid)).isEqualTo("root");
+
+        mRecoverableKeyStoreDb.setActiveRootOfTrust(userId, uid, "root2");
+        assertThat(mRecoverableKeyStoreDb.getActiveRootOfTrust(userId, uid)).isEqualTo("root2");
+    }
+
+    @Test
     public void setRecoverySecretTypes_emptyDefaultValue() throws Exception {
         int userId = 12;
         int uid = 10009;
@@ -495,11 +569,9 @@
         int[] types2 = new int[]{2};
 
         mRecoverableKeyStoreDb.setRecoverySecretTypes(userId, uid, types1);
-        assertThat(mRecoverableKeyStoreDb.getRecoverySecretTypes(userId, uid)).isEqualTo(
-                types1);
+        assertThat(mRecoverableKeyStoreDb.getRecoverySecretTypes(userId, uid)).isEqualTo(types1);
         mRecoverableKeyStoreDb.setRecoverySecretTypes(userId, uid, types2);
-        assertThat(mRecoverableKeyStoreDb.getRecoverySecretTypes(userId, uid)).isEqualTo(
-                types2);
+        assertThat(mRecoverableKeyStoreDb.getRecoverySecretTypes(userId, uid)).isEqualTo(types2);
     }
 
     @Test