diff options
| author | 2017-12-27 11:58:45 -0800 | |
|---|---|---|
| committer | 2017-12-27 15:57:49 -0800 | |
| commit | bdfdf53d08618ed34358b6ba66e1893bd35a4623 (patch) | |
| tree | bdcef0a0e4f03e6d1d0ff14e45e2ac11f673d8d5 | |
| parent | 6015c072476cb17b0683f53614fa5b03a18d2d34 (diff) | |
Implement RecoverableKeyStore API to set/get recovery secret types.
Bug: 66499222
Test: adb shell am instrument -w -e package \
com.android.server.locksettings.recoverablekeystore \
com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
Change-Id: If29f22f24438a9d050fabebf970b9ae56b0df805
6 files changed, 221 insertions, 4 deletions
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java index 11f4e9c113b0..fe1cad4b18ac 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java @@ -229,7 +229,8 @@ public class RecoverableKeyStoreManager { @NonNull @KeyStoreRecoveryMetadata.UserSecretType int[] secretTypes, int userId) throws RemoteException { checkRecoverKeyStorePermission(); - throw new UnsupportedOperationException(); + mDatabase.setRecoverySecretTypes(UserHandle.getCallingUserId(), Binder.getCallingUid(), + secretTypes); } /** @@ -240,7 +241,8 @@ public class RecoverableKeyStoreManager { */ public @NonNull int[] getRecoverySecretTypes(int userId) throws RemoteException { checkRecoverKeyStorePermission(); - throw new UnsupportedOperationException(); + return mDatabase.getRecoverySecretTypes(UserHandle.getCallingUserId(), + Binder.getCallingUid()); } /** 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 156d5ba355aa..e6efad5fb4b1 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 @@ -23,6 +23,7 @@ import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.security.recoverablekeystore.RecoverableKeyStoreLoader; +import android.text.TextUtils; import android.util.Log; import com.android.server.locksettings.recoverablekeystore.WrappedKey; @@ -30,18 +31,18 @@ import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKe import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.RecoveryServiceMetadataEntry; import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.UserMetadataEntry; - - import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.PublicKey; import java.security.spec.InvalidKeySpecException; import java.security.spec.X509EncodedKeySpec; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.StringJoiner; /** * Database of recoverable key information. @@ -426,6 +427,99 @@ public class RecoverableKeyStoreDb { } /** + * Updates the list of user secret types used for end-to-end encryption. + * If no secret types are set, recovery snapshot will not be created. + * See {@code KeyStoreRecoveryMetadata} + * + * @param userId The uid of the profile the application is running under. + * @param uid The uid of the application. + * @param secretTypes list of secret types + * @return The primary key of the updated row, or -1 if failed. + * + * @hide + */ + public long setRecoverySecretTypes(int userId, int uid, int[] secretTypes) { + SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase(); + ContentValues values = new ContentValues(); + StringJoiner joiner = new StringJoiner(","); + Arrays.stream(secretTypes).forEach(i -> joiner.add(Integer.toString(i))); + String typesAsCsv = joiner.toString(); + values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_SECRET_TYPES, typesAsCsv); + String selection = + RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND " + + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?"; + ensureRecoveryServiceMetadataEntryExists(userId, uid); + return db.update(RecoveryServiceMetadataEntry.TABLE_NAME, values, selection, + new String[] {String.valueOf(userId), String.valueOf(uid)}); + } + + /** + * Returns the list of secret types used for end-to-end encryption. + * + * @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 Secret types or empty array, if types were not set. + * + * @hide + */ + public @NonNull int[] getRecoverySecretTypes(int userId, int uid) { + SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase(); + + String[] projection = { + RecoveryServiceMetadataEntry._ID, + RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID, + RecoveryServiceMetadataEntry.COLUMN_NAME_UID, + RecoveryServiceMetadataEntry.COLUMN_NAME_SECRET_TYPES}; + 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 new int[]{}; + } + 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 new int[]{}; + } + cursor.moveToFirst(); + int idx = cursor.getColumnIndexOrThrow( + RecoveryServiceMetadataEntry.COLUMN_NAME_SECRET_TYPES); + if (cursor.isNull(idx)) { + return new int[]{}; + } + String csv = cursor.getString(idx); + if (TextUtils.isEmpty(csv)) { + return new int[]{}; + } + String[] types = csv.split(","); + int[] result = new int[types.length]; + for (int i = 0; i < types.length; i++) { + try { + result[i] = Integer.parseInt(types[i]); + } catch (NumberFormatException e) { + Log.wtf(TAG, "String format error " + e); + } + } + return result; + } + } + + /** * Updates the server parameters given by the application initializing the local recovery * components. * 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 a232771d241a..8f773ddd4598 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 @@ -109,6 +109,11 @@ class RecoverableKeyStoreDbContract { static final String COLUMN_NAME_PUBLIC_KEY = "public_key"; /** + * Secret types used for end-to-end encryption. + */ + static final String COLUMN_NAME_SECRET_TYPES = "secret_types"; + + /** * The server parameters of the recovery service. */ static final String COLUMN_NAME_SERVER_PARAMETERS = "server_parameters"; 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 c87812d2c3e2..5b07f3e28e83 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 @@ -57,6 +57,7 @@ class RecoverableKeyStoreDbHelper extends SQLiteOpenHelper { + RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " INTEGER," + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " INTEGER," + RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY + " BLOB," + + RecoveryServiceMetadataEntry.COLUMN_NAME_SECRET_TYPES + " TEXT," + RecoveryServiceMetadataEntry.COLUMN_NAME_SERVER_PARAMETERS + " INTEGER," + "UNIQUE(" + RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + "," diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java index 2c9b35699a47..88df62bae3df 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java @@ -362,6 +362,26 @@ public class RecoverableKeyStoreManagerTest { } @Test + public void setRecoverySecretTypes() throws Exception { + int userId = UserHandle.getCallingUserId(); + int[] types1 = new int[]{11, 2000}; + int[] types2 = new int[]{1, 2, 3}; + int[] types3 = new int[]{}; + + mRecoverableKeyStoreManager.setRecoverySecretTypes(types1, userId); + assertThat(mRecoverableKeyStoreManager.getRecoverySecretTypes(userId)).isEqualTo( + types1); + + mRecoverableKeyStoreManager.setRecoverySecretTypes(types2, userId); + assertThat(mRecoverableKeyStoreManager.getRecoverySecretTypes(userId)).isEqualTo( + types2); + + mRecoverableKeyStoreManager.setRecoverySecretTypes(types3, userId); + assertThat(mRecoverableKeyStoreManager.getRecoverySecretTypes(userId)).isEqualTo( + types3); + } + + @Test public void setRecoveryStatus_forOneAlias() throws Exception { int userId = UserHandle.getCallingUserId(); int uid = Binder.getCallingUid(); 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 373a7bcccafa..a5b67af11562 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 @@ -328,6 +328,7 @@ public class RecoverableKeyStoreDbTest { } @Test + public void getRecoveryAgentUid_returnsUidIfSet() throws Exception { int userId = 12; int uid = 190992; @@ -341,6 +342,100 @@ public class RecoverableKeyStoreDbTest { assertThat(mRecoverableKeyStoreDb.getRecoveryAgentUid(12)).isEqualTo(-1); } + public void setRecoverySecretTypes_emptyDefaultValue() throws Exception { + int userId = 12; + int uid = 10009; + assertThat(mRecoverableKeyStoreDb.getRecoverySecretTypes(userId, uid)).isEqualTo( + new int[]{}); // default + } + + @Test + public void setRecoverySecretTypes_updateValue() throws Exception { + int userId = 12; + int uid = 10009; + int[] types1 = new int[]{1}; + int[] types2 = new int[]{2}; + + mRecoverableKeyStoreDb.setRecoverySecretTypes(userId, uid, types1); + assertThat(mRecoverableKeyStoreDb.getRecoverySecretTypes(userId, uid)).isEqualTo( + types1); + mRecoverableKeyStoreDb.setRecoverySecretTypes(userId, uid, types2); + assertThat(mRecoverableKeyStoreDb.getRecoverySecretTypes(userId, uid)).isEqualTo( + types2); + } + + @Test + public void setRecoverySecretTypes_withMultiElementArrays() throws Exception { + int userId = 12; + int uid = 10009; + int[] types1 = new int[]{11, 2000}; + int[] types2 = new int[]{1, 2, 3}; + int[] types3 = new int[]{}; + + mRecoverableKeyStoreDb.setRecoverySecretTypes(userId, uid, types1); + assertThat(mRecoverableKeyStoreDb.getRecoverySecretTypes(userId, uid)).isEqualTo( + types1); + mRecoverableKeyStoreDb.setRecoverySecretTypes(userId, uid, types2); + assertThat(mRecoverableKeyStoreDb.getRecoverySecretTypes(userId, uid)).isEqualTo( + types2); + mRecoverableKeyStoreDb.setRecoverySecretTypes(userId, uid, types3); + assertThat(mRecoverableKeyStoreDb.getRecoverySecretTypes(userId, uid)).isEqualTo( + types3); + } + + @Test + public void setRecoverySecretTypes_withDifferentUid() throws Exception { + int userId = 12; + int uid1 = 10011; + int uid2 = 10012; + int[] types1 = new int[]{1}; + int[] types2 = new int[]{2}; + + mRecoverableKeyStoreDb.setRecoverySecretTypes(userId, uid1, types1); + mRecoverableKeyStoreDb.setRecoverySecretTypes(userId, uid2, types2); + assertThat(mRecoverableKeyStoreDb.getRecoverySecretTypes(userId, uid1)).isEqualTo( + types1); + assertThat(mRecoverableKeyStoreDb.getRecoverySecretTypes(userId, uid2)).isEqualTo( + types2); + } + + @Test + public void setRecoveryServiceMetadataMethods() throws Exception { + int userId = 12; + int uid = 10009; + + PublicKey pubkey1 = genRandomPublicKey(); + int[] types1 = new int[]{1}; + long serverParams1 = 111L; + + PublicKey pubkey2 = genRandomPublicKey(); + int[] types2 = new int[]{2}; + long serverParams2 = 222L; + + mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, pubkey1); + mRecoverableKeyStoreDb.setRecoverySecretTypes(userId, uid, types1); + mRecoverableKeyStoreDb.setServerParameters(userId, uid, serverParams1); + + assertThat(mRecoverableKeyStoreDb.getRecoverySecretTypes(userId, uid)).isEqualTo( + types1); + assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isEqualTo( + serverParams1); + assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isEqualTo( + pubkey1); + + // Check that the methods don't interfere with each other. + mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, pubkey2); + mRecoverableKeyStoreDb.setRecoverySecretTypes(userId, uid, types2); + mRecoverableKeyStoreDb.setServerParameters(userId, uid, serverParams2); + + assertThat(mRecoverableKeyStoreDb.getRecoverySecretTypes(userId, uid)).isEqualTo( + types2); + assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isEqualTo( + serverParams2); + assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isEqualTo( + pubkey2); + } + @Test public void setServerParameters_replaceOldValue() throws Exception { int userId = 12; |