From f34fc7e18c2a2ec5cff0bd9d96397311745fbef4 Mon Sep 17 00:00:00 2001 From: Dmitry Dementyev Date: Mon, 26 Mar 2018 17:31:29 -0700 Subject: [PATCH] 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 --- .../recoverablekeystore/KeySyncTask.java | 30 +- .../RecoverableKeyStoreManager.java | 49 ++- .../storage/RecoverableKeyStoreDb.java | 291 +++++++++++++++++- .../RecoverableKeyStoreDbContract.java | 40 +++ .../storage/RecoverableKeyStoreDbHelper.java | 61 +++- .../recoverablekeystore/KeySyncTaskTest.java | 36 ++- .../RecoverableKeyStoreManagerTest.java | 48 +-- .../RecoverableKeyStoreDbHelperTest.java | 32 ++ .../storage/RecoverableKeyStoreDbTest.java | 108 +++++-- 9 files changed, 609 insertions(+), 86 deletions(-) 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