From bc08840440d5121035244d8fd45a857becf3b7bb Mon Sep 17 00:00:00 2001 From: Robert Berry Date: Mon, 18 Dec 2017 13:10:41 +0000 Subject: [PATCH] Add storage for platform key IDs to SQLite db Also fix UNIQUE constraint for keys table and add test. Test: adb shell am instrument -w -e package com.android.server.locksettings.recoverablekeystore com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner Change-Id: I868cc4385b6557135ef1d40b39f23c0383453ca3 --- .../storage/RecoverableKeyStoreDb.java | 47 ++++++++++++++++++ .../RecoverableKeyStoreDbContract.java | 18 +++++++ .../storage/RecoverableKeyStoreDbHelper.java | 28 ++++++++--- .../storage/RecoverableKeyStoreDbTest.java | 49 ++++++++++++++++++- 4 files changed, 133 insertions(+), 9 deletions(-) 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 79865337d4c15..3644d368a7f36 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 @@ -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; @@ -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. */ 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 c54d0a6f4f774..b6c168f410785 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 @@ -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"; + } } 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 e3783c496ee2f..686820323241b 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 @@ -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. @@ -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); } } 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 3d5b958170a91..d5ad9597bf1cd 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 @@ -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; @@ -65,7 +64,7 @@ public class RecoverableKeyStoreDbTest { WrappedKey oldWrappedKey = new WrappedKey( getUtf8Bytes("nonce1"), getUtf8Bytes("keymaterial1"), - /*platformKeyGenerationId=*/1); + /*platformKeyGenerationId=*/ 1); mRecoverableKeyStoreDb.insertKey( userId, alias, oldWrappedKey); byte[] nonce = getUtf8Bytes("nonce2"); @@ -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( @@ -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); }