Loading services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java +47 −0 Original line number Diff line number Diff line Loading @@ -25,6 +25,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.UserMetadataEntry; import java.util.HashMap; import java.util.Locale; Loading Loading @@ -175,6 +176,52 @@ public class RecoverableKeyStoreDb { } } /** * Sets the {@code generationId} of the platform key for the account owned by {@code userId}. * * @return The primary key ID of the relation. */ public long setPlatformKeyGenerationId(int userId, int generationId) { SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase(); ContentValues values = new ContentValues(); values.put(UserMetadataEntry.COLUMN_NAME_USER_ID, userId); values.put(UserMetadataEntry.COLUMN_NAME_PLATFORM_KEY_GENERATION_ID, generationId); return db.replace( UserMetadataEntry.TABLE_NAME, /*nullColumnHack=*/ null, values); } /** * Returns the generation ID associated with the platform key of the user with {@code userId}. */ public int getPlatformKeyGenerationId(int userId) { SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase(); String[] projection = { UserMetadataEntry.COLUMN_NAME_PLATFORM_KEY_GENERATION_ID}; 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 -1; } cursor.moveToFirst(); return cursor.getInt( cursor.getColumnIndexOrThrow( UserMetadataEntry.COLUMN_NAME_PLATFORM_KEY_GENERATION_ID)); } } /** * Closes all open connections to the database. */ Loading services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java +18 −0 Original line number Diff line number Diff line Loading @@ -58,4 +58,22 @@ class RecoverableKeyStoreDbContract { */ static final String COLUMN_NAME_LAST_SYNCED_AT = "last_synced_at"; } /** * Recoverable KeyStore metadata for a specific user profile. */ static class UserMetadataEntry implements BaseColumns { static final String TABLE_NAME = "user_metadata"; /** * User ID of the profile. */ static final String COLUMN_NAME_USER_ID = "user_id"; /** * Every time a new platform key is generated for a user, this increments. The platform key * is used to wrap recoverable keys on disk. */ static final String COLUMN_NAME_PLATFORM_KEY_GENERATION_ID = "platform_key_generation_id"; } } services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java +21 −7 Original line number Diff line number Diff line Loading @@ -5,6 +5,7 @@ import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.KeysEntry; import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.UserMetadataEntry; /** * Helper for creating the recoverable key database. Loading @@ -13,31 +14,44 @@ class RecoverableKeyStoreDbHelper extends SQLiteOpenHelper { private static final int DATABASE_VERSION = 1; private static final String DATABASE_NAME = "recoverablekeystore.db"; private static final String SQL_CREATE_ENTRIES = private static final String SQL_CREATE_KEYS_ENTRY = "CREATE TABLE " + KeysEntry.TABLE_NAME + "( " + KeysEntry._ID + " INTEGER PRIMARY KEY," + KeysEntry.COLUMN_NAME_UID + " INTEGER UNIQUE," + KeysEntry.COLUMN_NAME_ALIAS + " TEXT UNIQUE," + KeysEntry.COLUMN_NAME_UID + " INTEGER," + KeysEntry.COLUMN_NAME_ALIAS + " TEXT," + KeysEntry.COLUMN_NAME_NONCE + " BLOB," + KeysEntry.COLUMN_NAME_WRAPPED_KEY + " BLOB," + KeysEntry.COLUMN_NAME_GENERATION_ID + " INTEGER," + KeysEntry.COLUMN_NAME_LAST_SYNCED_AT + " INTEGER)"; + KeysEntry.COLUMN_NAME_LAST_SYNCED_AT + " INTEGER," + "UNIQUE(" + KeysEntry.COLUMN_NAME_UID + "," + KeysEntry.COLUMN_NAME_ALIAS + "))"; private static final String SQL_DELETE_ENTRIES = private static final String SQL_CREATE_USER_METADATA_ENTRY = "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)"; private static final String SQL_DELETE_KEYS_ENTRY = "DROP TABLE IF EXISTS " + KeysEntry.TABLE_NAME; private static final String SQL_DELETE_USER_METADATA_ENTRY = "DROP TABLE IF EXISTS " + UserMetadataEntry.TABLE_NAME; RecoverableKeyStoreDbHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL(SQL_CREATE_ENTRIES); db.execSQL(SQL_CREATE_KEYS_ENTRY); db.execSQL(SQL_CREATE_USER_METADATA_ENTRY); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL(SQL_DELETE_ENTRIES); db.execSQL(SQL_DELETE_KEYS_ENTRY); db.execSQL(SQL_DELETE_USER_METADATA_ENTRY); onCreate(db); } } services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java +47 −2 Original line number Diff line number Diff line Loading @@ -30,7 +30,6 @@ import android.content.Context; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import com.android.server.locksettings.recoverablekeystore.WrappedKey; import java.io.File; Loading Loading @@ -82,6 +81,29 @@ public class RecoverableKeyStoreDbTest { assertEquals(2, retrievedKey.getPlatformKeyGenerationId()); } @Test public void insertKey_allowsTwoUidsToHaveSameAlias() { String alias = "pcoulton"; WrappedKey key1 = new WrappedKey( getUtf8Bytes("nonce1"), getUtf8Bytes("key1"), /*platformKeyGenerationId=*/ 1); WrappedKey key2 = new WrappedKey( getUtf8Bytes("nonce2"), getUtf8Bytes("key2"), /*platformKeyGenerationId=*/ 1); mRecoverableKeyStoreDb.insertKey(/*uid=*/ 1, alias, key1); mRecoverableKeyStoreDb.insertKey(/*uid=*/ 2, alias, key2); assertArrayEquals( getUtf8Bytes("nonce1"), mRecoverableKeyStoreDb.getKey(1, alias).getNonce()); assertArrayEquals( getUtf8Bytes("nonce2"), mRecoverableKeyStoreDb.getKey(2, alias).getNonce()); } @Test public void getKey_returnsNullIfNoKey() { WrappedKey key = mRecoverableKeyStoreDb.getKey( Loading Loading @@ -157,6 +179,29 @@ public class RecoverableKeyStoreDbTest { assertTrue(keys.isEmpty()); } @Test public void getPlatformKeyGenerationId_returnsGenerationId() { int userId = 42; int generationId = 24; mRecoverableKeyStoreDb.setPlatformKeyGenerationId(userId, generationId); assertEquals(generationId, mRecoverableKeyStoreDb.getPlatformKeyGenerationId(userId)); } @Test public void getPlatformKeyGenerationId_returnsMinusOneIfNoEntry() { assertEquals(-1, mRecoverableKeyStoreDb.getPlatformKeyGenerationId(42)); } @Test public void setPlatformKeyGenerationId_replacesOldEntry() { int userId = 42; mRecoverableKeyStoreDb.setPlatformKeyGenerationId(userId, 1); mRecoverableKeyStoreDb.setPlatformKeyGenerationId(userId, 2); assertEquals(2, mRecoverableKeyStoreDb.getPlatformKeyGenerationId(userId)); } private static byte[] getUtf8Bytes(String s) { return s.getBytes(StandardCharsets.UTF_8); } Loading Loading
services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java +47 −0 Original line number Diff line number Diff line Loading @@ -25,6 +25,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.UserMetadataEntry; import java.util.HashMap; import java.util.Locale; Loading Loading @@ -175,6 +176,52 @@ public class RecoverableKeyStoreDb { } } /** * Sets the {@code generationId} of the platform key for the account owned by {@code userId}. * * @return The primary key ID of the relation. */ public long setPlatformKeyGenerationId(int userId, int generationId) { SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase(); ContentValues values = new ContentValues(); values.put(UserMetadataEntry.COLUMN_NAME_USER_ID, userId); values.put(UserMetadataEntry.COLUMN_NAME_PLATFORM_KEY_GENERATION_ID, generationId); return db.replace( UserMetadataEntry.TABLE_NAME, /*nullColumnHack=*/ null, values); } /** * Returns the generation ID associated with the platform key of the user with {@code userId}. */ public int getPlatformKeyGenerationId(int userId) { SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase(); String[] projection = { UserMetadataEntry.COLUMN_NAME_PLATFORM_KEY_GENERATION_ID}; 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 -1; } cursor.moveToFirst(); return cursor.getInt( cursor.getColumnIndexOrThrow( UserMetadataEntry.COLUMN_NAME_PLATFORM_KEY_GENERATION_ID)); } } /** * Closes all open connections to the database. */ Loading
services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java +18 −0 Original line number Diff line number Diff line Loading @@ -58,4 +58,22 @@ class RecoverableKeyStoreDbContract { */ static final String COLUMN_NAME_LAST_SYNCED_AT = "last_synced_at"; } /** * Recoverable KeyStore metadata for a specific user profile. */ static class UserMetadataEntry implements BaseColumns { static final String TABLE_NAME = "user_metadata"; /** * User ID of the profile. */ static final String COLUMN_NAME_USER_ID = "user_id"; /** * Every time a new platform key is generated for a user, this increments. The platform key * is used to wrap recoverable keys on disk. */ static final String COLUMN_NAME_PLATFORM_KEY_GENERATION_ID = "platform_key_generation_id"; } }
services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java +21 −7 Original line number Diff line number Diff line Loading @@ -5,6 +5,7 @@ import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.KeysEntry; import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.UserMetadataEntry; /** * Helper for creating the recoverable key database. Loading @@ -13,31 +14,44 @@ class RecoverableKeyStoreDbHelper extends SQLiteOpenHelper { private static final int DATABASE_VERSION = 1; private static final String DATABASE_NAME = "recoverablekeystore.db"; private static final String SQL_CREATE_ENTRIES = private static final String SQL_CREATE_KEYS_ENTRY = "CREATE TABLE " + KeysEntry.TABLE_NAME + "( " + KeysEntry._ID + " INTEGER PRIMARY KEY," + KeysEntry.COLUMN_NAME_UID + " INTEGER UNIQUE," + KeysEntry.COLUMN_NAME_ALIAS + " TEXT UNIQUE," + KeysEntry.COLUMN_NAME_UID + " INTEGER," + KeysEntry.COLUMN_NAME_ALIAS + " TEXT," + KeysEntry.COLUMN_NAME_NONCE + " BLOB," + KeysEntry.COLUMN_NAME_WRAPPED_KEY + " BLOB," + KeysEntry.COLUMN_NAME_GENERATION_ID + " INTEGER," + KeysEntry.COLUMN_NAME_LAST_SYNCED_AT + " INTEGER)"; + KeysEntry.COLUMN_NAME_LAST_SYNCED_AT + " INTEGER," + "UNIQUE(" + KeysEntry.COLUMN_NAME_UID + "," + KeysEntry.COLUMN_NAME_ALIAS + "))"; private static final String SQL_DELETE_ENTRIES = private static final String SQL_CREATE_USER_METADATA_ENTRY = "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)"; private static final String SQL_DELETE_KEYS_ENTRY = "DROP TABLE IF EXISTS " + KeysEntry.TABLE_NAME; private static final String SQL_DELETE_USER_METADATA_ENTRY = "DROP TABLE IF EXISTS " + UserMetadataEntry.TABLE_NAME; RecoverableKeyStoreDbHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL(SQL_CREATE_ENTRIES); db.execSQL(SQL_CREATE_KEYS_ENTRY); db.execSQL(SQL_CREATE_USER_METADATA_ENTRY); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL(SQL_DELETE_ENTRIES); db.execSQL(SQL_DELETE_KEYS_ENTRY); db.execSQL(SQL_DELETE_USER_METADATA_ENTRY); onCreate(db); } }
services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java +47 −2 Original line number Diff line number Diff line Loading @@ -30,7 +30,6 @@ import android.content.Context; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import com.android.server.locksettings.recoverablekeystore.WrappedKey; import java.io.File; Loading Loading @@ -82,6 +81,29 @@ public class RecoverableKeyStoreDbTest { assertEquals(2, retrievedKey.getPlatformKeyGenerationId()); } @Test public void insertKey_allowsTwoUidsToHaveSameAlias() { String alias = "pcoulton"; WrappedKey key1 = new WrappedKey( getUtf8Bytes("nonce1"), getUtf8Bytes("key1"), /*platformKeyGenerationId=*/ 1); WrappedKey key2 = new WrappedKey( getUtf8Bytes("nonce2"), getUtf8Bytes("key2"), /*platformKeyGenerationId=*/ 1); mRecoverableKeyStoreDb.insertKey(/*uid=*/ 1, alias, key1); mRecoverableKeyStoreDb.insertKey(/*uid=*/ 2, alias, key2); assertArrayEquals( getUtf8Bytes("nonce1"), mRecoverableKeyStoreDb.getKey(1, alias).getNonce()); assertArrayEquals( getUtf8Bytes("nonce2"), mRecoverableKeyStoreDb.getKey(2, alias).getNonce()); } @Test public void getKey_returnsNullIfNoKey() { WrappedKey key = mRecoverableKeyStoreDb.getKey( Loading Loading @@ -157,6 +179,29 @@ public class RecoverableKeyStoreDbTest { assertTrue(keys.isEmpty()); } @Test public void getPlatformKeyGenerationId_returnsGenerationId() { int userId = 42; int generationId = 24; mRecoverableKeyStoreDb.setPlatformKeyGenerationId(userId, generationId); assertEquals(generationId, mRecoverableKeyStoreDb.getPlatformKeyGenerationId(userId)); } @Test public void getPlatformKeyGenerationId_returnsMinusOneIfNoEntry() { assertEquals(-1, mRecoverableKeyStoreDb.getPlatformKeyGenerationId(42)); } @Test public void setPlatformKeyGenerationId_replacesOldEntry() { int userId = 42; mRecoverableKeyStoreDb.setPlatformKeyGenerationId(userId, 1); mRecoverableKeyStoreDb.setPlatformKeyGenerationId(userId, 2); assertEquals(2, mRecoverableKeyStoreDb.getPlatformKeyGenerationId(userId)); } private static byte[] getUtf8Bytes(String s) { return s.getBytes(StandardCharsets.UTF_8); } Loading