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 a87adbde31e6a..050c1f4a6141c 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 @@ 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; + } } 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 6d2bec8b43839..30125f8199dee 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 @@ 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 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, 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 2676ee8897af4..38834ac6c1f55 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 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. */ 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 2c3d3ab5be4d5..1eff2d4721448 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 @@ class RecoverableKeyStoreDbContract { /** * 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 @@ class RecoverableKeyStoreDbContract { * 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 8a89f2d4faa96..43efe9cc0cbb1 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 android.util.Log; 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 @@ import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKe 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 @@ class RecoverableKeyStoreDbHelper extends SQLiteOpenHelper { + 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 @@ class RecoverableKeyStoreDbHelper extends SQLiteOpenHelper { + 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 @@ class RecoverableKeyStoreDbHelper extends SQLiteOpenHelper { 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 @@ class RecoverableKeyStoreDbHelper extends SQLiteOpenHelper { 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 @@ class RecoverableKeyStoreDbHelper extends SQLiteOpenHelper { 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 0ea231773aaf5..9ae45ea15ca2e 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 @@ import javax.crypto.SecretKey; 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 @@ public class KeySyncTaskTest { 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 @@ public class KeySyncTaskTest { 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 @@ public class KeySyncTaskTest { 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 @@ public class KeySyncTaskTest { @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 @@ public class KeySyncTaskTest { @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 @@ public class KeySyncTaskTest { @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 @@ public class KeySyncTaskTest { @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 @@ public class KeySyncTaskTest { 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 @@ public class KeySyncTaskTest { 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 @@ public class KeySyncTaskTest { 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 @@ public class KeySyncTaskTest { @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 @@ public class KeySyncTaskTest { 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 @@ public class KeySyncTaskTest { @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 8db2537b9433f..f5f5027da7711 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.AndroidKeyStoreProvider; 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 @@ public class RecoverableKeyStoreManagerTest { 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 @@ public class RecoverableKeyStoreManagerTest { 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 @@ public class RecoverableKeyStoreManagerTest { 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 @@ public class RecoverableKeyStoreManagerTest { 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 @@ public class RecoverableKeyStoreManagerTest { 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 @@ public class RecoverableKeyStoreManagerTest { 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 @@ public class RecoverableKeyStoreManagerTest { 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 @@ public class RecoverableKeyStoreManagerTest { 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 class RecoverableKeyStoreManagerTest { 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 class RecoverableKeyStoreManagerTest { 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 @@ public class RecoverableKeyStoreManagerTest { 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 @@ public class RecoverableKeyStoreManagerTest { 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 @@ public class RecoverableKeyStoreManagerTest { 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 @@ public class RecoverableKeyStoreManagerTest { 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 37482a37003ca..9b09dd1ae6ad1 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 android.support.test.runner.AndroidJUnit4; 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 @@ public class RecoverableKeyStoreDbHelperTest { 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 @@ public class RecoverableKeyStoreDbHelperTest { 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 @@ public class RecoverableKeyStoreDbHelperTest { 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 @@ public class RecoverableKeyStoreDbHelperTest { 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 8b01d972f7e55..940745e955e46 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 @@ import java.util.Map; 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 @@ public class RecoverableKeyStoreDbTest { Map 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 class RecoverableKeyStoreDbTest { 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 @@ public class RecoverableKeyStoreDbTest { 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 @@ -479,6 +535,24 @@ public class RecoverableKeyStoreDbTest { assertThat(agents).contains(uid2); } + @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; @@ -495,11 +569,9 @@ public class RecoverableKeyStoreDbTest { 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