Loading services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java +22 −1 Original line number Diff line number Diff line Loading @@ -273,7 +273,7 @@ public class KeySyncTask implements Runnable { } boolean useScryptToHashCredential = shouldUseScryptToHashCredential(); byte[] salt = generateSalt(); byte[] salt = generateNewSaltIfNecessary(); byte[] localLskfHash; if (useScryptToHashCredential) { localLskfHash = hashCredentialsByScrypt(salt, mCredential.getCredential()); Loading Loading @@ -480,6 +480,27 @@ public class KeySyncTask implements Runnable { } } /** * Reads salt from the database if LSKF is the same as in the previous snapshot. * * @return The salt. */ private byte[] generateNewSaltIfNecessary() { if (mCredentialUpdated) { byte[] newSalt = generateSalt(); mRecoverableKeyStoreDb.setLskfSalt(mUserId, newSalt); return newSalt; } byte[] storedSalt = mRecoverableKeyStoreDb.getLskfSalt(mUserId); if (storedSalt != null && storedSalt.length == SALT_LENGTH_BYTES) { return storedSalt; } else { byte[] newSalt = generateSalt(); mRecoverableKeyStoreDb.setLskfSalt(mUserId, newSalt); return newSalt; } } /** * Generates a salt to include with the lock screen hash. * Loading services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java +48 −0 Original line number Diff line number Diff line Loading @@ -440,6 +440,54 @@ public class RecoverableKeyStoreDb { } } /** * Sets the {@code lskfSalt} for the user {@code userId}. * * @return The number of updated rows. */ public long setLskfSalt(int userId, byte[] lskfSalt) { SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase(); ContentValues values = new ContentValues(); values.put(UserMetadataEntry.COLUMN_NAME_USER_ID, userId); values.put(UserMetadataEntry.COLUMN_NAME_LSKF_SALT, lskfSalt); String selection = UserMetadataEntry.COLUMN_NAME_USER_ID + " = ?"; String[] selectionArguments = new String[] {String.valueOf(userId)}; ensureUserMetadataEntryExists(userId); return db.update(UserMetadataEntry.TABLE_NAME, values, selection, selectionArguments); } /** * Returns salt used to create previous snapshot for user {@code userId}. */ public @Nullable byte[] getLskfSalt(int userId) { SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase(); String[] projection = { UserMetadataEntry.COLUMN_NAME_LSKF_SALT}; String selection = UserMetadataEntry.COLUMN_NAME_USER_ID + " = ?"; String[] selectionArguments = { Integer.toString(userId)}; try (Cursor cursor = db.query( UserMetadataEntry.TABLE_NAME, projection, selection, selectionArguments, /*groupBy=*/ null, /*having=*/ null, /*orderBy=*/ null) ) { if (cursor.getCount() == 0) { return null; // previous value was not stored. } cursor.moveToFirst(); return cursor.getBlob( cursor.getColumnIndexOrThrow( UserMetadataEntry.COLUMN_NAME_LSKF_SALT)); } } /** * Updates status of old keys to {@code RecoveryController.RECOVERY_STATUS_PERMANENT_FAILURE}. */ Loading services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java +5 −0 Original line number Diff line number Diff line Loading @@ -103,6 +103,11 @@ class RecoverableKeyStoreDbContract { * Number of invalid lockscreen credentials guess from a remote device. */ static final String COLUMN_NAME_BAD_REMOTE_GUESS_COUNTER = "bad_remote_guess_counter"; /** * Salt used to create previous keychain snapshot. */ static final String COLUMN_NAME_LSKF_SALT = "lskf_salt"; } /** Loading services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java +26 −3 Original line number Diff line number Diff line Loading @@ -35,7 +35,9 @@ class RecoverableKeyStoreDbHelper extends SQLiteOpenHelper { // v6 - added user id serial number. // v7 - added bad guess counter for remote LSKF check; // v8 - added field to store LSKF salt. static final int DATABASE_VERSION_7 = 7; static final int DATABASE_VERSION_8 = 8; private static final String DATABASE_NAME = "recoverablekeystore.db"; private static final String SQL_CREATE_KEYS_ENTRY = Loading Loading @@ -70,6 +72,16 @@ class RecoverableKeyStoreDbHelper extends SQLiteOpenHelper { + UserMetadataEntry.COLUMN_NAME_BAD_REMOTE_GUESS_COUNTER + " INTEGER DEFAULT 0)"; private static final String SQL_CREATE_USER_METADATA_ENTRY_FOR_V8 = "CREATE TABLE " + UserMetadataEntry.TABLE_NAME + "( " + UserMetadataEntry._ID + " INTEGER PRIMARY KEY," + UserMetadataEntry.COLUMN_NAME_USER_ID + " INTEGER UNIQUE," + UserMetadataEntry.COLUMN_NAME_PLATFORM_KEY_GENERATION_ID + " INTEGER," + UserMetadataEntry.COLUMN_NAME_USER_SERIAL_NUMBER + " INTEGER DEFAULT -1," + UserMetadataEntry.COLUMN_NAME_BAD_REMOTE_GUESS_COUNTER + " INTEGER DEFAULT 0," + UserMetadataEntry.COLUMN_NAME_LSKF_SALT + " BLOB)"; private static final String SQL_CREATE_RECOVERY_SERVICE_METADATA_ENTRY = "CREATE TABLE " + RecoveryServiceMetadataEntry.TABLE_NAME + " (" + RecoveryServiceMetadataEntry._ID + " INTEGER PRIMARY KEY," Loading Loading @@ -118,13 +130,13 @@ class RecoverableKeyStoreDbHelper extends SQLiteOpenHelper { } private static int getDbVersion(Context context) { return DATABASE_VERSION_7; return DATABASE_VERSION_8; } @Override public void onCreate(SQLiteDatabase db) { db.execSQL(SQL_CREATE_KEYS_ENTRY); db.execSQL(SQL_CREATE_USER_METADATA_ENTRY_FOR_V7); db.execSQL(SQL_CREATE_USER_METADATA_ENTRY_FOR_V8); db.execSQL(SQL_CREATE_RECOVERY_SERVICE_METADATA_ENTRY); db.execSQL(SQL_CREATE_ROOT_OF_TRUST_ENTRY); } Loading Loading @@ -173,8 +185,13 @@ class RecoverableKeyStoreDbHelper extends SQLiteOpenHelper { } oldVersion = 7; } if (oldVersion < 8 && newVersion >= 8) { upgradeDbForVersion8(db); oldVersion = 8; } } catch (SQLiteException e) { Log.e(TAG, "Recreating recoverablekeystore after unexpected upgrade error.", e); Log.e(TAG, "Recreating recoverablekeystore database after unexpected upgrade error.", e); dropAllKnownTables(db); // Wipe database. onCreate(db); return; Loading Loading @@ -234,6 +251,12 @@ class RecoverableKeyStoreDbHelper extends SQLiteOpenHelper { /*defaultStr=*/ null); } private void upgradeDbForVersion8(SQLiteDatabase db) { Log.d(TAG, "Updating recoverable keystore database to version 8"); addColumnToTable(db, UserMetadataEntry.TABLE_NAME, UserMetadataEntry.COLUMN_NAME_LSKF_SALT, "BLOB", /*defaultStr=*/ null); } private static void addColumnToTable( SQLiteDatabase db, String tableName, String column, String columnType, String defaultStr) { Loading services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java +36 −0 Original line number Diff line number Diff line Loading @@ -95,6 +95,8 @@ public class KeySyncTaskTest { private static final int TEST_RECOVERY_AGENT_UID2 = 10010; private static final byte[] TEST_VAULT_HANDLE = new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}; private static final byte[] LSKF_SALT = new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, -16}; private static final String TEST_APP_KEY_ALIAS = "rcleaver"; private static final byte[] TEST_APP_KEY_METADATA_NULL = null; private static final byte[] TEST_APP_KEY_METADATA_NON_NULL = Loading Loading @@ -862,6 +864,40 @@ public class KeySyncTaskTest { assertThat(mRecoverableKeyStoreDb.getBadRemoteGuessCounter(TEST_USER_ID_2)).isEqualTo(4); } @Test public void run_storesLskfSalt() throws Exception { LockscreenCredential shortPassword = LockscreenCredential.createPassword("e2e4"); mKeySyncTask = new KeySyncTask( mRecoverableKeyStoreDb, mRecoverySnapshotStorage, mSnapshotListenersStorage, TEST_USER_ID, shortPassword, /*credentialUpdated=*/ false, mPlatformKeyManager, mTestOnlyInsecureCertificateHelper, mMockScrypt); mRecoverableKeyStoreDb.setServerParams( TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_VAULT_HANDLE); 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, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1); mRecoverableKeyStoreDb.setLskfSalt(TEST_USER_ID, LSKF_SALT); setExpectedScryptArgument(shortPassword); mKeySyncTask.run(); KeyChainSnapshot keyChainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID); assertThat(keyChainSnapshot.getKeyChainProtectionParams().get(0).getLockScreenUiFormat()) .isEqualTo(UI_FORMAT_PASSWORD); KeyDerivationParams keyDerivationParams = keyChainSnapshot.getKeyChainProtectionParams().get(0).getKeyDerivationParams(); byte[] storedSalt = mRecoverableKeyStoreDb.getLskfSalt(TEST_USER_ID); assertThat(storedSalt).isEqualTo(keyDerivationParams.getSalt()); assertThat(storedSalt).isEqualTo(LSKF_SALT); } private SecretKey addApplicationKey(int userId, int recoveryAgentUid, String alias) throws Exception{ return addApplicationKey(userId, recoveryAgentUid, alias, TEST_APP_KEY_METADATA_NULL); Loading Loading
services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java +22 −1 Original line number Diff line number Diff line Loading @@ -273,7 +273,7 @@ public class KeySyncTask implements Runnable { } boolean useScryptToHashCredential = shouldUseScryptToHashCredential(); byte[] salt = generateSalt(); byte[] salt = generateNewSaltIfNecessary(); byte[] localLskfHash; if (useScryptToHashCredential) { localLskfHash = hashCredentialsByScrypt(salt, mCredential.getCredential()); Loading Loading @@ -480,6 +480,27 @@ public class KeySyncTask implements Runnable { } } /** * Reads salt from the database if LSKF is the same as in the previous snapshot. * * @return The salt. */ private byte[] generateNewSaltIfNecessary() { if (mCredentialUpdated) { byte[] newSalt = generateSalt(); mRecoverableKeyStoreDb.setLskfSalt(mUserId, newSalt); return newSalt; } byte[] storedSalt = mRecoverableKeyStoreDb.getLskfSalt(mUserId); if (storedSalt != null && storedSalt.length == SALT_LENGTH_BYTES) { return storedSalt; } else { byte[] newSalt = generateSalt(); mRecoverableKeyStoreDb.setLskfSalt(mUserId, newSalt); return newSalt; } } /** * Generates a salt to include with the lock screen hash. * Loading
services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java +48 −0 Original line number Diff line number Diff line Loading @@ -440,6 +440,54 @@ public class RecoverableKeyStoreDb { } } /** * Sets the {@code lskfSalt} for the user {@code userId}. * * @return The number of updated rows. */ public long setLskfSalt(int userId, byte[] lskfSalt) { SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase(); ContentValues values = new ContentValues(); values.put(UserMetadataEntry.COLUMN_NAME_USER_ID, userId); values.put(UserMetadataEntry.COLUMN_NAME_LSKF_SALT, lskfSalt); String selection = UserMetadataEntry.COLUMN_NAME_USER_ID + " = ?"; String[] selectionArguments = new String[] {String.valueOf(userId)}; ensureUserMetadataEntryExists(userId); return db.update(UserMetadataEntry.TABLE_NAME, values, selection, selectionArguments); } /** * Returns salt used to create previous snapshot for user {@code userId}. */ public @Nullable byte[] getLskfSalt(int userId) { SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase(); String[] projection = { UserMetadataEntry.COLUMN_NAME_LSKF_SALT}; String selection = UserMetadataEntry.COLUMN_NAME_USER_ID + " = ?"; String[] selectionArguments = { Integer.toString(userId)}; try (Cursor cursor = db.query( UserMetadataEntry.TABLE_NAME, projection, selection, selectionArguments, /*groupBy=*/ null, /*having=*/ null, /*orderBy=*/ null) ) { if (cursor.getCount() == 0) { return null; // previous value was not stored. } cursor.moveToFirst(); return cursor.getBlob( cursor.getColumnIndexOrThrow( UserMetadataEntry.COLUMN_NAME_LSKF_SALT)); } } /** * Updates status of old keys to {@code RecoveryController.RECOVERY_STATUS_PERMANENT_FAILURE}. */ Loading
services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java +5 −0 Original line number Diff line number Diff line Loading @@ -103,6 +103,11 @@ class RecoverableKeyStoreDbContract { * Number of invalid lockscreen credentials guess from a remote device. */ static final String COLUMN_NAME_BAD_REMOTE_GUESS_COUNTER = "bad_remote_guess_counter"; /** * Salt used to create previous keychain snapshot. */ static final String COLUMN_NAME_LSKF_SALT = "lskf_salt"; } /** Loading
services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java +26 −3 Original line number Diff line number Diff line Loading @@ -35,7 +35,9 @@ class RecoverableKeyStoreDbHelper extends SQLiteOpenHelper { // v6 - added user id serial number. // v7 - added bad guess counter for remote LSKF check; // v8 - added field to store LSKF salt. static final int DATABASE_VERSION_7 = 7; static final int DATABASE_VERSION_8 = 8; private static final String DATABASE_NAME = "recoverablekeystore.db"; private static final String SQL_CREATE_KEYS_ENTRY = Loading Loading @@ -70,6 +72,16 @@ class RecoverableKeyStoreDbHelper extends SQLiteOpenHelper { + UserMetadataEntry.COLUMN_NAME_BAD_REMOTE_GUESS_COUNTER + " INTEGER DEFAULT 0)"; private static final String SQL_CREATE_USER_METADATA_ENTRY_FOR_V8 = "CREATE TABLE " + UserMetadataEntry.TABLE_NAME + "( " + UserMetadataEntry._ID + " INTEGER PRIMARY KEY," + UserMetadataEntry.COLUMN_NAME_USER_ID + " INTEGER UNIQUE," + UserMetadataEntry.COLUMN_NAME_PLATFORM_KEY_GENERATION_ID + " INTEGER," + UserMetadataEntry.COLUMN_NAME_USER_SERIAL_NUMBER + " INTEGER DEFAULT -1," + UserMetadataEntry.COLUMN_NAME_BAD_REMOTE_GUESS_COUNTER + " INTEGER DEFAULT 0," + UserMetadataEntry.COLUMN_NAME_LSKF_SALT + " BLOB)"; private static final String SQL_CREATE_RECOVERY_SERVICE_METADATA_ENTRY = "CREATE TABLE " + RecoveryServiceMetadataEntry.TABLE_NAME + " (" + RecoveryServiceMetadataEntry._ID + " INTEGER PRIMARY KEY," Loading Loading @@ -118,13 +130,13 @@ class RecoverableKeyStoreDbHelper extends SQLiteOpenHelper { } private static int getDbVersion(Context context) { return DATABASE_VERSION_7; return DATABASE_VERSION_8; } @Override public void onCreate(SQLiteDatabase db) { db.execSQL(SQL_CREATE_KEYS_ENTRY); db.execSQL(SQL_CREATE_USER_METADATA_ENTRY_FOR_V7); db.execSQL(SQL_CREATE_USER_METADATA_ENTRY_FOR_V8); db.execSQL(SQL_CREATE_RECOVERY_SERVICE_METADATA_ENTRY); db.execSQL(SQL_CREATE_ROOT_OF_TRUST_ENTRY); } Loading Loading @@ -173,8 +185,13 @@ class RecoverableKeyStoreDbHelper extends SQLiteOpenHelper { } oldVersion = 7; } if (oldVersion < 8 && newVersion >= 8) { upgradeDbForVersion8(db); oldVersion = 8; } } catch (SQLiteException e) { Log.e(TAG, "Recreating recoverablekeystore after unexpected upgrade error.", e); Log.e(TAG, "Recreating recoverablekeystore database after unexpected upgrade error.", e); dropAllKnownTables(db); // Wipe database. onCreate(db); return; Loading Loading @@ -234,6 +251,12 @@ class RecoverableKeyStoreDbHelper extends SQLiteOpenHelper { /*defaultStr=*/ null); } private void upgradeDbForVersion8(SQLiteDatabase db) { Log.d(TAG, "Updating recoverable keystore database to version 8"); addColumnToTable(db, UserMetadataEntry.TABLE_NAME, UserMetadataEntry.COLUMN_NAME_LSKF_SALT, "BLOB", /*defaultStr=*/ null); } private static void addColumnToTable( SQLiteDatabase db, String tableName, String column, String columnType, String defaultStr) { Loading
services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java +36 −0 Original line number Diff line number Diff line Loading @@ -95,6 +95,8 @@ public class KeySyncTaskTest { private static final int TEST_RECOVERY_AGENT_UID2 = 10010; private static final byte[] TEST_VAULT_HANDLE = new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}; private static final byte[] LSKF_SALT = new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, -16}; private static final String TEST_APP_KEY_ALIAS = "rcleaver"; private static final byte[] TEST_APP_KEY_METADATA_NULL = null; private static final byte[] TEST_APP_KEY_METADATA_NON_NULL = Loading Loading @@ -862,6 +864,40 @@ public class KeySyncTaskTest { assertThat(mRecoverableKeyStoreDb.getBadRemoteGuessCounter(TEST_USER_ID_2)).isEqualTo(4); } @Test public void run_storesLskfSalt() throws Exception { LockscreenCredential shortPassword = LockscreenCredential.createPassword("e2e4"); mKeySyncTask = new KeySyncTask( mRecoverableKeyStoreDb, mRecoverySnapshotStorage, mSnapshotListenersStorage, TEST_USER_ID, shortPassword, /*credentialUpdated=*/ false, mPlatformKeyManager, mTestOnlyInsecureCertificateHelper, mMockScrypt); mRecoverableKeyStoreDb.setServerParams( TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_VAULT_HANDLE); 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, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1); mRecoverableKeyStoreDb.setLskfSalt(TEST_USER_ID, LSKF_SALT); setExpectedScryptArgument(shortPassword); mKeySyncTask.run(); KeyChainSnapshot keyChainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID); assertThat(keyChainSnapshot.getKeyChainProtectionParams().get(0).getLockScreenUiFormat()) .isEqualTo(UI_FORMAT_PASSWORD); KeyDerivationParams keyDerivationParams = keyChainSnapshot.getKeyChainProtectionParams().get(0).getKeyDerivationParams(); byte[] storedSalt = mRecoverableKeyStoreDb.getLskfSalt(TEST_USER_ID); assertThat(storedSalt).isEqualTo(keyDerivationParams.getSalt()); assertThat(storedSalt).isEqualTo(LSKF_SALT); } private SecretKey addApplicationKey(int userId, int recoveryAgentUid, String alias) throws Exception{ return addApplicationKey(userId, recoveryAgentUid, alias, TEST_APP_KEY_METADATA_NULL); Loading