* version. Version zero is used, if no snapshots were created for the account.
*
* @return Map from recovery agent accounts to snapshot versions.
- * @see KeyStoreRecoveryData.getSnapshotVersion
+ * @see KeyStoreRecoveryData#getSnapshotVersion
* @hide
*/
public @NonNull Map<byte[], Integer> getRecoverySnapshotVersions()
/**
* Server parameters used to generate new recovery key blobs. This value will be included in
* {@code KeyStoreRecoveryData.getEncryptedRecoveryKeyBlob()}. The same value must be included
- * in vaultParams {@link startRecoverySession}
+ * in vaultParams {@link #startRecoverySession}
*
* @param serverParameters included in recovery key blob.
* @see #getRecoveryData
public void setServerParameters(long serverParameters, int userId) throws RemoteException {
checkRecoverKeyStorePermission();
- throw new UnsupportedOperationException();
+ mDatabase.setServerParameters(userId, Binder.getCallingUid(), serverParameters);
}
/**
import com.android.server.locksettings.recoverablekeystore.WrappedKey;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.KeysEntry;
-import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.RecoveryServicePublicKeyEntry;
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.RecoveryServiceMetadataEntry;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.UserMetadataEntry;
}
/**
- * Inserts or updates the public key of the recovery service into the database.
+ * Updates the public key of the recovery service into the database.
*
* @param userId The uid of the profile the application is running under.
* @param uid The uid of the application to whom the key belongs.
public long setRecoveryServicePublicKey(int userId, int uid, PublicKey publicKey) {
SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
ContentValues values = new ContentValues();
- values.put(RecoveryServicePublicKeyEntry.COLUMN_NAME_USER_ID, userId);
- values.put(RecoveryServicePublicKeyEntry.COLUMN_NAME_UID, uid);
- values.put(RecoveryServicePublicKeyEntry.COLUMN_NAME_PUBLIC_KEY, publicKey.getEncoded());
- return db.replace(RecoveryServicePublicKeyEntry.TABLE_NAME, /*nullColumnHack=*/ null,
- values);
+ values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY, publicKey.getEncoded());
+ String selection =
+ RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND "
+ + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?";
+ String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)};
+
+ ensureRecoveryServiceMetadataEntryExists(userId, uid);
+ return db.update(
+ RecoveryServiceMetadataEntry.TABLE_NAME, values, selection, selectionArguments);
}
/**
SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
String[] projection = {
- RecoveryServicePublicKeyEntry._ID,
- RecoveryServicePublicKeyEntry.COLUMN_NAME_USER_ID,
- RecoveryServicePublicKeyEntry.COLUMN_NAME_UID,
- RecoveryServicePublicKeyEntry.COLUMN_NAME_PUBLIC_KEY};
+ RecoveryServiceMetadataEntry._ID,
+ RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID,
+ RecoveryServiceMetadataEntry.COLUMN_NAME_UID,
+ RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY};
String selection =
- RecoveryServicePublicKeyEntry.COLUMN_NAME_USER_ID + " = ? AND "
- + RecoveryServicePublicKeyEntry.COLUMN_NAME_UID + " = ?";
- String[] selectionArguments = { Integer.toString(userId), Integer.toString(uid)};
+ RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND "
+ + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?";
+ String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)};
try (
Cursor cursor = db.query(
- RecoveryServicePublicKeyEntry.TABLE_NAME,
+ RecoveryServiceMetadataEntry.TABLE_NAME,
projection,
selection,
selectionArguments,
return null;
}
cursor.moveToFirst();
- byte[] keyBytes = cursor.getBlob(cursor.getColumnIndexOrThrow(
- RecoveryServicePublicKeyEntry.COLUMN_NAME_PUBLIC_KEY));
+ int idx = cursor.getColumnIndexOrThrow(
+ RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY);
+ if (cursor.isNull(idx)) {
+ return null;
+ }
+ byte[] keyBytes = cursor.getBlob(idx);
X509EncodedKeySpec pkSpec = new X509EncodedKeySpec(keyBytes);
try {
return KeyFactory.getInstance("EC").generatePublic(pkSpec);
}
/**
+ * Updates the server parameters given by the application initializing the local recovery
+ * components.
+ *
+ * @param userId The uid of the profile the application is running under.
+ * @param uid The uid of the application.
+ * @param serverParameters The server parameters.
+ * @return The primary key of the inserted row, or -1 if failed.
+ *
+ * @hide
+ */
+ public long setServerParameters(int userId, int uid, long serverParameters) {
+ SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
+ ContentValues values = new ContentValues();
+ values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_SERVER_PARAMETERS, serverParameters);
+ String selection =
+ RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND "
+ + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?";
+ String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)};
+
+ ensureRecoveryServiceMetadataEntryExists(userId, uid);
+ return db.update(
+ RecoveryServiceMetadataEntry.TABLE_NAME, values, selection, selectionArguments);
+ }
+
+ /**
+ * Returns the server paramters that was previously set by the application who initialized the
+ * local recovery service components.
+ *
+ * @param userId The uid of the profile the application is running under.
+ * @param uid The uid of the application who initialized the local recovery components.
+ * @return The server parameters that were previously set, or null if there's none.
+ *
+ * @hide
+ */
+ public Long getServerParameters(int userId, int uid) {
+ SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
+
+ String[] projection = {
+ RecoveryServiceMetadataEntry._ID,
+ RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID,
+ RecoveryServiceMetadataEntry.COLUMN_NAME_UID,
+ RecoveryServiceMetadataEntry.COLUMN_NAME_SERVER_PARAMETERS};
+ 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_SERVER_PARAMETERS);
+ if (cursor.isNull(idx)) {
+ return null;
+ } else {
+ return cursor.getLong(idx);
+ }
+ }
+ }
+
+ /**
+ * 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.
+ */
+ private void ensureRecoveryServiceMetadataEntryExists(int userId, int uid) {
+ SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
+ ContentValues values = new ContentValues();
+ values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID, userId);
+ values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_UID, uid);
+ db.insertWithOnConflict(RecoveryServiceMetadataEntry.TABLE_NAME, /*nullColumnHack=*/ null,
+ values, SQLiteDatabase.CONFLICT_IGNORE);
+ }
+
+ /**
* Closes all open connections to the database.
*/
public void close() {
}
/**
- * Table holding public keys of the recovery service.
+ * Table holding metadata of the recovery service.
*/
- static class RecoveryServicePublicKeyEntry implements BaseColumns {
- static final String TABLE_NAME = "recovery_service_public_keys";
+ static class RecoveryServiceMetadataEntry implements BaseColumns {
+ static final String TABLE_NAME = "recovery_service_metadata";
/**
* The user id of the profile the application is running under.
* The public key of the recovery service.
*/
static final String COLUMN_NAME_PUBLIC_KEY = "public_key";
+
+ /**
+ * The server parameters of the recovery service.
+ */
+ static final String COLUMN_NAME_SERVER_PARAMETERS = "server_parameters";
}
}
import android.database.sqlite.SQLiteOpenHelper;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.KeysEntry;
-import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.RecoveryServicePublicKeyEntry;
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.RecoveryServiceMetadataEntry;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.UserMetadataEntry;
/**
+ UserMetadataEntry.COLUMN_NAME_PLATFORM_KEY_GENERATION_ID + " INTEGER)";
private static final String SQL_CREATE_RECOVERY_SERVICE_PUBLIC_KEY_ENTRY =
- "CREATE TABLE " + RecoveryServicePublicKeyEntry.TABLE_NAME + " ("
- + RecoveryServicePublicKeyEntry._ID + " INTEGER PRIMARY KEY,"
- + RecoveryServicePublicKeyEntry.COLUMN_NAME_USER_ID + " INTEGER,"
- + RecoveryServicePublicKeyEntry.COLUMN_NAME_UID + " INTEGER,"
- + RecoveryServicePublicKeyEntry.COLUMN_NAME_PUBLIC_KEY + " BLOB,"
+ "CREATE TABLE " + RecoveryServiceMetadataEntry.TABLE_NAME + " ("
+ + RecoveryServiceMetadataEntry._ID + " INTEGER PRIMARY KEY,"
+ + RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " INTEGER,"
+ + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " INTEGER,"
+ + RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY + " BLOB,"
+ + RecoveryServiceMetadataEntry.COLUMN_NAME_SERVER_PARAMETERS + " INTEGER,"
+ "UNIQUE("
- + RecoveryServicePublicKeyEntry.COLUMN_NAME_USER_ID + ","
- + RecoveryServicePublicKeyEntry.COLUMN_NAME_UID + "))";
+ + RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + ","
+ + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + "))";
private static final String SQL_DELETE_KEYS_ENTRY =
"DROP TABLE IF EXISTS " + KeysEntry.TABLE_NAME;
"DROP TABLE IF EXISTS " + UserMetadataEntry.TABLE_NAME;
private static final String SQL_DELETE_RECOVERY_SERVICE_PUBLIC_KEY_ENTRY =
- "DROP TABLE IF EXISTS " + RecoveryServicePublicKeyEntry.TABLE_NAME;
+ "DROP TABLE IF EXISTS " + RecoveryServiceMetadataEntry.TABLE_NAME;
RecoverableKeyStoreDbHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
import android.app.KeyguardManager;
import android.content.Context;
-import android.content.SharedPreferences;
import android.security.keystore.KeyProperties;
import android.security.keystore.KeyProtection;
import android.support.test.InstrumentationRegistry;
import org.junit.runner.RunWith;
import android.content.Context;
+import android.content.SharedPreferences;
import android.security.recoverablekeystore.RecoverableKeyStoreLoader;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
}
@Test
- public void setRecoveryServicePublicKey_allowsTwoUsersToHaveTheSameUidAndKey()
- throws Exception {
- int userId1 = 12;
- int userId2 = 23;
+ public void getRecoveryServicePublicKey_returnsNullIfNoKey() throws Exception {
+ int userId = 12;
+ int uid = 10009;
+ assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isNull();
+
+ long serverParams = 123456L;
+ mRecoverableKeyStoreDb.setServerParameters(userId, uid, serverParams);
+ assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isNull();
+ }
+
+ @Test
+ public void getRecoveryServicePublicKey_returnsInsertedKey() throws Exception {
+ int userId = 12;
int uid = 10009;
PublicKey pubkey = genRandomPublicKey();
- mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId1, uid, pubkey);
- mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId2, uid, pubkey);
- assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId1, uid)).isEqualTo(
- pubkey);
- assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId2, uid)).isEqualTo(
+ mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, pubkey);
+ assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isEqualTo(
pubkey);
}
@Test
- public void setRecoveryServicePublicKey_allowsAUserToHaveTwoUids() throws Exception {
+ public void setServerParameters_replaceOldValue() throws Exception {
+ int userId = 12;
+ int uid = 10009;
+ long serverParams1 = 111L;
+ long serverParams2 = 222L;
+ mRecoverableKeyStoreDb.setServerParameters(userId, uid, serverParams1);
+ mRecoverableKeyStoreDb.setServerParameters(userId, uid, serverParams2);
+ assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isEqualTo(
+ serverParams2);
+ }
+
+ @Test
+ public void getServerParameters_returnsNullIfNoValue() throws Exception {
+ int userId = 12;
+ int uid = 10009;
+ assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isNull();
+
+ PublicKey pubkey = genRandomPublicKey();
+ mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, pubkey);
+ assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isNull();
+ }
+
+ @Test
+ public void getServerParameters_returnsInsertedValue() throws Exception {
+ int userId = 12;
+ int uid = 10009;
+ long serverParams = 123456L;
+ mRecoverableKeyStoreDb.setServerParameters(userId, uid, serverParams);
+ assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isEqualTo(serverParams);
+ }
+
+ @Test
+ public void setRecoveryServiceMetadataEntry_allowsAUserToHaveTwoUids() throws Exception {
int userId = 12;
int uid1 = 10009;
int uid2 = 20009;
}
@Test
- public void getRecoveryServicePublicKey_returnsNullIfNoKey() throws Exception {
- int userId = 12;
+ public void setRecoveryServiceMetadataEntry_allowsTwoUsersToHaveTheSameUid() throws Exception {
+ int userId1 = 12;
+ int userId2 = 23;
int uid = 10009;
- assertNull(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid));
+ PublicKey pubkey = genRandomPublicKey();
+ mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId1, uid, pubkey);
+ mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId2, uid, pubkey);
+ assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId1, uid)).isEqualTo(
+ pubkey);
+ assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId2, uid)).isEqualTo(
+ pubkey);
}
@Test
- public void getRecoveryServicePublicKey_returnsInsertedKey() throws Exception {
+ public void setRecoveryServiceMetadataEntry_updatesColumnsSeparately() throws Exception {
int userId = 12;
int uid = 10009;
- PublicKey pubkey = genRandomPublicKey();
- mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, pubkey);
+ PublicKey pubkey1 = genRandomPublicKey();
+ PublicKey pubkey2 = genRandomPublicKey();
+ long serverParams = 123456L;
+
+ mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, pubkey1);
assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isEqualTo(
- pubkey);
+ pubkey1);
+ assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isNull();
+
+ mRecoverableKeyStoreDb.setServerParameters(userId, uid, serverParams);
+ assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isEqualTo(
+ pubkey1);
+ assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isEqualTo(serverParams);
+
+ mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, pubkey2);
+ assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isEqualTo(
+ pubkey2);
+ assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isEqualTo(serverParams);
}
private static byte[] getUtf8Bytes(String s) {