Loading services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java 0 → 100644 +185 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.locksettings.recoverablekeystore.storage; import android.annotation.Nullable; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.util.Log; import com.android.server.locksettings.recoverablekeystore.WrappedKey; import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.KeysEntry; import java.util.HashMap; import java.util.Locale; import java.util.Map; /** * Database of recoverable key information. * * @hide */ public class RecoverableKeyStoreDb { private static final String TAG = "RecoverableKeyStoreDb"; private static final int IDLE_TIMEOUT_SECONDS = 30; private final RecoverableKeyStoreDbHelper mKeyStoreDbHelper; /** * A new instance, storing the database in the user directory of {@code context}. * * @hide */ public static RecoverableKeyStoreDb newInstance(Context context) { RecoverableKeyStoreDbHelper helper = new RecoverableKeyStoreDbHelper(context); helper.setWriteAheadLoggingEnabled(true); helper.setIdleConnectionTimeout(IDLE_TIMEOUT_SECONDS); return new RecoverableKeyStoreDb(helper); } private RecoverableKeyStoreDb(RecoverableKeyStoreDbHelper keyStoreDbHelper) { this.mKeyStoreDbHelper = keyStoreDbHelper; } /** * Inserts a key into the database. * * @param uid Uid of the application to whom the key belongs. * @param alias The alias of the key in the AndroidKeyStore. * @param wrappedKey The wrapped bytes of the key. * @param generationId The generation ID of the platform key that wrapped the key. * @return The primary key of the inserted row, or -1 if failed. * * @hide */ public long insertKey(int uid, String alias, WrappedKey wrappedKey, int generationId) { SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase(); ContentValues values = new ContentValues(); values.put(KeysEntry.COLUMN_NAME_UID, uid); values.put(KeysEntry.COLUMN_NAME_ALIAS, alias); values.put(KeysEntry.COLUMN_NAME_NONCE, wrappedKey.getNonce()); values.put(KeysEntry.COLUMN_NAME_WRAPPED_KEY, wrappedKey.getKeyMaterial()); values.put(KeysEntry.COLUMN_NAME_LAST_SYNCED_AT, -1); values.put(KeysEntry.COLUMN_NAME_GENERATION_ID, generationId); return db.replace(KeysEntry.TABLE_NAME, /*nullColumnHack=*/ null, values); } /** * Gets the key with {@code alias} for the app with {@code uid}. * * @hide */ @Nullable public WrappedKey getKey(int uid, String alias) { SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase(); String[] projection = { KeysEntry._ID, KeysEntry.COLUMN_NAME_NONCE, KeysEntry.COLUMN_NAME_WRAPPED_KEY, KeysEntry.COLUMN_NAME_GENERATION_ID}; String selection = KeysEntry.COLUMN_NAME_UID + " = ? AND " + KeysEntry.COLUMN_NAME_ALIAS + " = ?"; String[] selectionArguments = { Integer.toString(uid), alias }; try ( Cursor cursor = db.query( KeysEntry.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 WrappedKey entries found for uid=%d alias='%s'. " + "Should only ever be 0 or 1.", count, uid, alias)); return null; } cursor.moveToFirst(); byte[] nonce = cursor.getBlob( cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_NONCE)); byte[] keyMaterial = cursor.getBlob( cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_WRAPPED_KEY)); return new WrappedKey(nonce, keyMaterial); } } /** * Returns all keys for the given {@code uid} and {@code platformKeyGenerationId}. * * @param uid User id of the profile to which all the keys are associated. * @param platformKeyGenerationId The generation ID of the platform key that wrapped these keys. * (i.e., this should be the most recent generation ID, as older platform keys are not * usable.) * * @hide */ public Map<String, WrappedKey> getAllKeys(int uid, int platformKeyGenerationId) { SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase(); String[] projection = { KeysEntry._ID, KeysEntry.COLUMN_NAME_NONCE, KeysEntry.COLUMN_NAME_WRAPPED_KEY, KeysEntry.COLUMN_NAME_ALIAS}; String selection = KeysEntry.COLUMN_NAME_UID + " = ? AND " + KeysEntry.COLUMN_NAME_GENERATION_ID + " = ?"; String[] selectionArguments = { Integer.toString(uid), Integer.toString(platformKeyGenerationId) }; try ( Cursor cursor = db.query( KeysEntry.TABLE_NAME, projection, selection, selectionArguments, /*groupBy=*/ null, /*having=*/ null, /*orderBy=*/ null) ) { HashMap<String, WrappedKey> keys = new HashMap<>(); while (cursor.moveToNext()) { byte[] nonce = cursor.getBlob( cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_NONCE)); byte[] keyMaterial = cursor.getBlob( cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_WRAPPED_KEY)); String alias = cursor.getString( cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_ALIAS)); keys.put(alias, new WrappedKey(nonce, keyMaterial)); } return keys; } } /** * Closes all open connections to the database. */ public void close() { mKeyStoreDbHelper.close(); } // TODO: Add method for updating the 'last synced' time. } services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java 0 → 100644 +61 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.locksettings.recoverablekeystore.storage; import android.provider.BaseColumns; /** * Contract for recoverable key database. Describes the tables present. */ class RecoverableKeyStoreDbContract { /** * Table holding wrapped keys, and information about when they were last synced. */ static class KeysEntry implements BaseColumns { static final String TABLE_NAME = "keys"; /** * The uid of the application that generated the key. */ static final String COLUMN_NAME_UID = "uid"; /** * The alias of the key, as set in AndroidKeyStore. */ static final String COLUMN_NAME_ALIAS = "alias"; /** * Nonce with which the key was encrypted. */ static final String COLUMN_NAME_NONCE = "nonce"; /** * Encrypted bytes of the key. */ static final String COLUMN_NAME_WRAPPED_KEY = "wrapped_key"; /** * Generation ID of the platform key that was used to encrypt this key. */ static final String COLUMN_NAME_GENERATION_ID = "platform_key_generation_id"; /** * Timestamp of when this key was last synced with remote storage, or -1 if never synced. */ static final String COLUMN_NAME_LAST_SYNCED_AT = "last_synced_at"; } } services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java 0 → 100644 +43 −0 Original line number Diff line number Diff line package com.android.server.locksettings.recoverablekeystore.storage; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.KeysEntry; /** * Helper for creating the recoverable key database. */ 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 = "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_NONCE + " BLOB," + KeysEntry.COLUMN_NAME_WRAPPED_KEY + " BLOB," + KeysEntry.COLUMN_NAME_GENERATION_ID + " INTEGER," + KeysEntry.COLUMN_NAME_LAST_SYNCED_AT + " INTEGER)"; private static final String SQL_DELETE_ENTRIES = "DROP TABLE IF EXISTS " + KeysEntry.TABLE_NAME; RecoverableKeyStoreDbHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL(SQL_CREATE_ENTRIES); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL(SQL_DELETE_ENTRIES); onCreate(db); } } services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java 0 → 100644 +155 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.locksettings.recoverablekeystore.storage; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; 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; import java.nio.charset.StandardCharsets; import java.util.Map; @SmallTest @RunWith(AndroidJUnit4.class) public class RecoverableKeyStoreDbTest { private static final String DATABASE_FILE_NAME = "recoverablekeystore.db"; private RecoverableKeyStoreDb mRecoverableKeyStoreDb; private File mDatabaseFile; @Before public void setUp() { Context context = InstrumentationRegistry.getTargetContext(); mDatabaseFile = context.getDatabasePath(DATABASE_FILE_NAME); mRecoverableKeyStoreDb = RecoverableKeyStoreDb.newInstance(context); } @After public void tearDown() { mRecoverableKeyStoreDb.close(); mDatabaseFile.delete(); } @Test public void insertKey_replacesOldKey() { int userId = 12; String alias = "test"; WrappedKey oldWrappedKey = new WrappedKey( getUtf8Bytes("nonce1"), getUtf8Bytes("keymaterial1")); mRecoverableKeyStoreDb.insertKey( userId, alias, oldWrappedKey, /*generationId=*/ 1); byte[] nonce = getUtf8Bytes("nonce2"); byte[] keyMaterial = getUtf8Bytes("keymaterial2"); WrappedKey newWrappedKey = new WrappedKey(nonce, keyMaterial); mRecoverableKeyStoreDb.insertKey( userId, alias, newWrappedKey, /*generationId=*/ 2); WrappedKey retrievedKey = mRecoverableKeyStoreDb.getKey(userId, alias); assertArrayEquals(nonce, retrievedKey.getNonce()); assertArrayEquals(keyMaterial, retrievedKey.getKeyMaterial()); } @Test public void getKey_returnsNullIfNoKey() { WrappedKey key = mRecoverableKeyStoreDb.getKey( /*userId=*/ 1, /*alias=*/ "hello"); assertNull(key); } @Test public void getKey_returnsInsertedKey() { int userId = 12; int generationId = 6; String alias = "test"; byte[] nonce = getUtf8Bytes("nonce"); byte[] keyMaterial = getUtf8Bytes("keymaterial"); WrappedKey wrappedKey = new WrappedKey(nonce, keyMaterial); mRecoverableKeyStoreDb.insertKey(userId, alias, wrappedKey, generationId); WrappedKey retrievedKey = mRecoverableKeyStoreDb.getKey(userId, alias); assertArrayEquals(nonce, retrievedKey.getNonce()); assertArrayEquals(keyMaterial, retrievedKey.getKeyMaterial()); } @Test public void getAllKeys_getsKeysWithUserIdAndGenerationId() { int userId = 12; int generationId = 6; String alias = "test"; byte[] nonce = getUtf8Bytes("nonce"); byte[] keyMaterial = getUtf8Bytes("keymaterial"); WrappedKey wrappedKey = new WrappedKey(nonce, keyMaterial); mRecoverableKeyStoreDb.insertKey(userId, alias, wrappedKey, generationId); Map<String, WrappedKey> keys = mRecoverableKeyStoreDb.getAllKeys(userId, generationId); assertEquals(1, keys.size()); assertTrue(keys.containsKey(alias)); WrappedKey retrievedKey = keys.get(alias); assertArrayEquals(nonce, retrievedKey.getNonce()); assertArrayEquals(keyMaterial, retrievedKey.getKeyMaterial()); } @Test public void getAllKeys_doesNotReturnKeysWithBadGenerationId() { int userId = 12; WrappedKey wrappedKey = new WrappedKey( getUtf8Bytes("nonce"), getUtf8Bytes("keymaterial")); mRecoverableKeyStoreDb.insertKey( userId, /*alias=*/ "test", wrappedKey, /*generationId=*/ 5); Map<String, WrappedKey> keys = mRecoverableKeyStoreDb.getAllKeys( userId, /*generationId=*/ 7); assertTrue(keys.isEmpty()); } @Test public void getAllKeys_doesNotReturnKeysWithBadUserId() { int generationId = 12; WrappedKey wrappedKey = new WrappedKey( getUtf8Bytes("nonce"), getUtf8Bytes("keymaterial")); mRecoverableKeyStoreDb.insertKey( /*userId=*/ 1, /*alias=*/ "test", wrappedKey, generationId); Map<String, WrappedKey> keys = mRecoverableKeyStoreDb.getAllKeys( /*userId=*/ 2, generationId); assertTrue(keys.isEmpty()); } private static byte[] getUtf8Bytes(String s) { return s.getBytes(StandardCharsets.UTF_8); } } Loading
services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java 0 → 100644 +185 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.locksettings.recoverablekeystore.storage; import android.annotation.Nullable; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.util.Log; import com.android.server.locksettings.recoverablekeystore.WrappedKey; import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.KeysEntry; import java.util.HashMap; import java.util.Locale; import java.util.Map; /** * Database of recoverable key information. * * @hide */ public class RecoverableKeyStoreDb { private static final String TAG = "RecoverableKeyStoreDb"; private static final int IDLE_TIMEOUT_SECONDS = 30; private final RecoverableKeyStoreDbHelper mKeyStoreDbHelper; /** * A new instance, storing the database in the user directory of {@code context}. * * @hide */ public static RecoverableKeyStoreDb newInstance(Context context) { RecoverableKeyStoreDbHelper helper = new RecoverableKeyStoreDbHelper(context); helper.setWriteAheadLoggingEnabled(true); helper.setIdleConnectionTimeout(IDLE_TIMEOUT_SECONDS); return new RecoverableKeyStoreDb(helper); } private RecoverableKeyStoreDb(RecoverableKeyStoreDbHelper keyStoreDbHelper) { this.mKeyStoreDbHelper = keyStoreDbHelper; } /** * Inserts a key into the database. * * @param uid Uid of the application to whom the key belongs. * @param alias The alias of the key in the AndroidKeyStore. * @param wrappedKey The wrapped bytes of the key. * @param generationId The generation ID of the platform key that wrapped the key. * @return The primary key of the inserted row, or -1 if failed. * * @hide */ public long insertKey(int uid, String alias, WrappedKey wrappedKey, int generationId) { SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase(); ContentValues values = new ContentValues(); values.put(KeysEntry.COLUMN_NAME_UID, uid); values.put(KeysEntry.COLUMN_NAME_ALIAS, alias); values.put(KeysEntry.COLUMN_NAME_NONCE, wrappedKey.getNonce()); values.put(KeysEntry.COLUMN_NAME_WRAPPED_KEY, wrappedKey.getKeyMaterial()); values.put(KeysEntry.COLUMN_NAME_LAST_SYNCED_AT, -1); values.put(KeysEntry.COLUMN_NAME_GENERATION_ID, generationId); return db.replace(KeysEntry.TABLE_NAME, /*nullColumnHack=*/ null, values); } /** * Gets the key with {@code alias} for the app with {@code uid}. * * @hide */ @Nullable public WrappedKey getKey(int uid, String alias) { SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase(); String[] projection = { KeysEntry._ID, KeysEntry.COLUMN_NAME_NONCE, KeysEntry.COLUMN_NAME_WRAPPED_KEY, KeysEntry.COLUMN_NAME_GENERATION_ID}; String selection = KeysEntry.COLUMN_NAME_UID + " = ? AND " + KeysEntry.COLUMN_NAME_ALIAS + " = ?"; String[] selectionArguments = { Integer.toString(uid), alias }; try ( Cursor cursor = db.query( KeysEntry.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 WrappedKey entries found for uid=%d alias='%s'. " + "Should only ever be 0 or 1.", count, uid, alias)); return null; } cursor.moveToFirst(); byte[] nonce = cursor.getBlob( cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_NONCE)); byte[] keyMaterial = cursor.getBlob( cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_WRAPPED_KEY)); return new WrappedKey(nonce, keyMaterial); } } /** * Returns all keys for the given {@code uid} and {@code platformKeyGenerationId}. * * @param uid User id of the profile to which all the keys are associated. * @param platformKeyGenerationId The generation ID of the platform key that wrapped these keys. * (i.e., this should be the most recent generation ID, as older platform keys are not * usable.) * * @hide */ public Map<String, WrappedKey> getAllKeys(int uid, int platformKeyGenerationId) { SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase(); String[] projection = { KeysEntry._ID, KeysEntry.COLUMN_NAME_NONCE, KeysEntry.COLUMN_NAME_WRAPPED_KEY, KeysEntry.COLUMN_NAME_ALIAS}; String selection = KeysEntry.COLUMN_NAME_UID + " = ? AND " + KeysEntry.COLUMN_NAME_GENERATION_ID + " = ?"; String[] selectionArguments = { Integer.toString(uid), Integer.toString(platformKeyGenerationId) }; try ( Cursor cursor = db.query( KeysEntry.TABLE_NAME, projection, selection, selectionArguments, /*groupBy=*/ null, /*having=*/ null, /*orderBy=*/ null) ) { HashMap<String, WrappedKey> keys = new HashMap<>(); while (cursor.moveToNext()) { byte[] nonce = cursor.getBlob( cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_NONCE)); byte[] keyMaterial = cursor.getBlob( cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_WRAPPED_KEY)); String alias = cursor.getString( cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_ALIAS)); keys.put(alias, new WrappedKey(nonce, keyMaterial)); } return keys; } } /** * Closes all open connections to the database. */ public void close() { mKeyStoreDbHelper.close(); } // TODO: Add method for updating the 'last synced' time. }
services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java 0 → 100644 +61 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.locksettings.recoverablekeystore.storage; import android.provider.BaseColumns; /** * Contract for recoverable key database. Describes the tables present. */ class RecoverableKeyStoreDbContract { /** * Table holding wrapped keys, and information about when they were last synced. */ static class KeysEntry implements BaseColumns { static final String TABLE_NAME = "keys"; /** * The uid of the application that generated the key. */ static final String COLUMN_NAME_UID = "uid"; /** * The alias of the key, as set in AndroidKeyStore. */ static final String COLUMN_NAME_ALIAS = "alias"; /** * Nonce with which the key was encrypted. */ static final String COLUMN_NAME_NONCE = "nonce"; /** * Encrypted bytes of the key. */ static final String COLUMN_NAME_WRAPPED_KEY = "wrapped_key"; /** * Generation ID of the platform key that was used to encrypt this key. */ static final String COLUMN_NAME_GENERATION_ID = "platform_key_generation_id"; /** * Timestamp of when this key was last synced with remote storage, or -1 if never synced. */ static final String COLUMN_NAME_LAST_SYNCED_AT = "last_synced_at"; } }
services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java 0 → 100644 +43 −0 Original line number Diff line number Diff line package com.android.server.locksettings.recoverablekeystore.storage; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.KeysEntry; /** * Helper for creating the recoverable key database. */ 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 = "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_NONCE + " BLOB," + KeysEntry.COLUMN_NAME_WRAPPED_KEY + " BLOB," + KeysEntry.COLUMN_NAME_GENERATION_ID + " INTEGER," + KeysEntry.COLUMN_NAME_LAST_SYNCED_AT + " INTEGER)"; private static final String SQL_DELETE_ENTRIES = "DROP TABLE IF EXISTS " + KeysEntry.TABLE_NAME; RecoverableKeyStoreDbHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL(SQL_CREATE_ENTRIES); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL(SQL_DELETE_ENTRIES); onCreate(db); } }
services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java 0 → 100644 +155 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.locksettings.recoverablekeystore.storage; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; 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; import java.nio.charset.StandardCharsets; import java.util.Map; @SmallTest @RunWith(AndroidJUnit4.class) public class RecoverableKeyStoreDbTest { private static final String DATABASE_FILE_NAME = "recoverablekeystore.db"; private RecoverableKeyStoreDb mRecoverableKeyStoreDb; private File mDatabaseFile; @Before public void setUp() { Context context = InstrumentationRegistry.getTargetContext(); mDatabaseFile = context.getDatabasePath(DATABASE_FILE_NAME); mRecoverableKeyStoreDb = RecoverableKeyStoreDb.newInstance(context); } @After public void tearDown() { mRecoverableKeyStoreDb.close(); mDatabaseFile.delete(); } @Test public void insertKey_replacesOldKey() { int userId = 12; String alias = "test"; WrappedKey oldWrappedKey = new WrappedKey( getUtf8Bytes("nonce1"), getUtf8Bytes("keymaterial1")); mRecoverableKeyStoreDb.insertKey( userId, alias, oldWrappedKey, /*generationId=*/ 1); byte[] nonce = getUtf8Bytes("nonce2"); byte[] keyMaterial = getUtf8Bytes("keymaterial2"); WrappedKey newWrappedKey = new WrappedKey(nonce, keyMaterial); mRecoverableKeyStoreDb.insertKey( userId, alias, newWrappedKey, /*generationId=*/ 2); WrappedKey retrievedKey = mRecoverableKeyStoreDb.getKey(userId, alias); assertArrayEquals(nonce, retrievedKey.getNonce()); assertArrayEquals(keyMaterial, retrievedKey.getKeyMaterial()); } @Test public void getKey_returnsNullIfNoKey() { WrappedKey key = mRecoverableKeyStoreDb.getKey( /*userId=*/ 1, /*alias=*/ "hello"); assertNull(key); } @Test public void getKey_returnsInsertedKey() { int userId = 12; int generationId = 6; String alias = "test"; byte[] nonce = getUtf8Bytes("nonce"); byte[] keyMaterial = getUtf8Bytes("keymaterial"); WrappedKey wrappedKey = new WrappedKey(nonce, keyMaterial); mRecoverableKeyStoreDb.insertKey(userId, alias, wrappedKey, generationId); WrappedKey retrievedKey = mRecoverableKeyStoreDb.getKey(userId, alias); assertArrayEquals(nonce, retrievedKey.getNonce()); assertArrayEquals(keyMaterial, retrievedKey.getKeyMaterial()); } @Test public void getAllKeys_getsKeysWithUserIdAndGenerationId() { int userId = 12; int generationId = 6; String alias = "test"; byte[] nonce = getUtf8Bytes("nonce"); byte[] keyMaterial = getUtf8Bytes("keymaterial"); WrappedKey wrappedKey = new WrappedKey(nonce, keyMaterial); mRecoverableKeyStoreDb.insertKey(userId, alias, wrappedKey, generationId); Map<String, WrappedKey> keys = mRecoverableKeyStoreDb.getAllKeys(userId, generationId); assertEquals(1, keys.size()); assertTrue(keys.containsKey(alias)); WrappedKey retrievedKey = keys.get(alias); assertArrayEquals(nonce, retrievedKey.getNonce()); assertArrayEquals(keyMaterial, retrievedKey.getKeyMaterial()); } @Test public void getAllKeys_doesNotReturnKeysWithBadGenerationId() { int userId = 12; WrappedKey wrappedKey = new WrappedKey( getUtf8Bytes("nonce"), getUtf8Bytes("keymaterial")); mRecoverableKeyStoreDb.insertKey( userId, /*alias=*/ "test", wrappedKey, /*generationId=*/ 5); Map<String, WrappedKey> keys = mRecoverableKeyStoreDb.getAllKeys( userId, /*generationId=*/ 7); assertTrue(keys.isEmpty()); } @Test public void getAllKeys_doesNotReturnKeysWithBadUserId() { int generationId = 12; WrappedKey wrappedKey = new WrappedKey( getUtf8Bytes("nonce"), getUtf8Bytes("keymaterial")); mRecoverableKeyStoreDb.insertKey( /*userId=*/ 1, /*alias=*/ "test", wrappedKey, generationId); Map<String, WrappedKey> keys = mRecoverableKeyStoreDb.getAllKeys( /*userId=*/ 2, generationId); assertTrue(keys.isEmpty()); } private static byte[] getUtf8Bytes(String s) { return s.getBytes(StandardCharsets.UTF_8); } }