Loading services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java +13 −4 Original line number Diff line number Diff line Loading @@ -63,6 +63,7 @@ public class KeySyncTask implements Runnable { private static final int SALT_LENGTH_BYTES = 16; private static final int LENGTH_PREFIX_BYTES = Integer.BYTES; private static final String LOCK_SCREEN_HASH_ALGORITHM = "SHA-256"; private static final int TRUSTED_HARDWARE_MAX_ATTEMPTS = 10; private final RecoverableKeyStoreDb mRecoverableKeyStoreDb; private final int mUserId; Loading Loading @@ -140,19 +141,23 @@ public class KeySyncTask implements Runnable { Log.w(TAG, "No recovery agent initialized for user " + mUserId); return; } if (!mSnapshotListenersStorage.hasListener(recoveryAgentUid)) { Log.w(TAG, "No pending intent registered for recovery agent " + recoveryAgentUid); return; } PublicKey publicKey = getVaultPublicKey(); if (publicKey == null) { Log.w(TAG, "Not initialized for KeySync: no public key set. Cancelling task."); return; } Long deviceId = mRecoverableKeyStoreDb.getServerParameters(mUserId, recoveryAgentUid); if (deviceId == null) { Log.w(TAG, "No device ID set for user " + mUserId); return; } byte[] salt = generateSalt(); byte[] localLskfHash = hashCredentials(salt, mCredential); Loading Loading @@ -191,8 +196,12 @@ public class KeySyncTask implements Runnable { return; } // TODO: construct vault params and vault metadata byte[] vaultParams = {}; // TODO: where do we get counter_id from here? byte[] vaultParams = KeySyncUtils.packVaultParams( publicKey, /*counterId=*/ 1, TRUSTED_HARDWARE_MAX_ATTEMPTS, deviceId); byte[] encryptedRecoveryKey; try { Loading services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java +23 −0 Original line number Diff line number Diff line Loading @@ -18,6 +18,8 @@ package com.android.server.locksettings.recoverablekeystore; import com.android.internal.annotations.VisibleForTesting; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.KeyFactory; Loading Loading @@ -60,6 +62,7 @@ public class KeySyncUtils { private static final byte[] THM_KF_HASH_PREFIX = "THM_KF_hash".getBytes(StandardCharsets.UTF_8); private static final int KEY_CLAIMANT_LENGTH_BYTES = 16; private static final int VAULT_PARAMS_LENGTH_BYTES = 85; /** * Encrypts the recovery key using both the lock screen hash and the remote storage's public Loading Loading @@ -280,6 +283,26 @@ public class KeySyncUtils { return keyFactory.generatePublic(publicKeySpec); } /** * Packs vault params into a binary format. * * @param thmPublicKey Public key of the trusted hardware module. * @param counterId ID referring to the specific counter in the hardware module. * @param maxAttempts Maximum allowed guesses before trusted hardware wipes key. * @param deviceId ID of the device. * @return The binary vault params, ready for sync. */ public static byte[] packVaultParams( PublicKey thmPublicKey, long counterId, int maxAttempts, long deviceId) { return ByteBuffer.allocate(VAULT_PARAMS_LENGTH_BYTES) .order(ByteOrder.LITTLE_ENDIAN) .put(SecureBox.encodePublicKey(thmPublicKey)) .putLong(counterId) .putInt(maxAttempts) .putLong(deviceId) .array(); } /** * Returns the concatenation of all the given {@code arrays}. */ Loading services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java +7 −1 Original line number Diff line number Diff line Loading @@ -372,7 +372,13 @@ public class SecureBox { } } @VisibleForTesting /** * Encodes public key in format expected by the secure hardware module. This is used as part * of the vault params. * * @param publicKey The public key. * @return The key packed into a 65-byte array. */ static byte[] encodePublicKey(PublicKey publicKey) { ECPoint point = ((ECPublicKey) publicKey).getW(); byte[] x = point.getAffineX().toByteArray(); Loading services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java +29 −2 Original line number Diff line number Diff line Loading @@ -72,6 +72,7 @@ public class KeySyncTaskTest { private static final int TEST_USER_ID = 1000; private static final int TEST_APP_UID = 10009; private static final int TEST_RECOVERY_AGENT_UID = 90873; private static final long TEST_DEVICE_ID = 13295035643L; private static final String TEST_APP_KEY_ALIAS = "rcleaver"; private static final int TEST_GENERATION_ID = 2; private static final int TEST_CREDENTIAL_TYPE = CREDENTIAL_TYPE_PASSWORD; Loading Loading @@ -223,6 +224,8 @@ public class KeySyncTaskTest { @Test public void run_doesNotSendAnythingIfNoRecoveryAgentPendingIntentRegistered() throws Exception { SecretKey applicationKey = generateKey(); mRecoverableKeyStoreDb.setServerParameters( TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_DEVICE_ID); mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID); mRecoverableKeyStoreDb.insertKey( TEST_USER_ID, Loading @@ -237,9 +240,29 @@ public class KeySyncTaskTest { assertNull(mRecoverySnapshotStorage.get(TEST_USER_ID)); } @Test public void run_doesNotSendAnythingIfNoDeviceIdIsSet() throws Exception { SecretKey applicationKey = generateKey(); mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID); mRecoverableKeyStoreDb.insertKey( TEST_USER_ID, TEST_APP_UID, TEST_APP_KEY_ALIAS, WrappedKey.fromSecretKey(mEncryptKey, applicationKey)); mRecoverableKeyStoreDb.setRecoveryServicePublicKey( TEST_USER_ID, TEST_RECOVERY_AGENT_UID, mKeyPair.getPublic()); when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true); mKeySyncTask.run(); assertNull(mRecoverySnapshotStorage.get(TEST_USER_ID)); } @Test public void run_sendsEncryptedKeysIfAvailableToSync() throws Exception { SecretKey applicationKey = generateKey(); mRecoverableKeyStoreDb.setServerParameters( TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_DEVICE_ID); mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID); mRecoverableKeyStoreDb.insertKey( TEST_USER_ID, Loading @@ -261,11 +284,15 @@ public class KeySyncTaskTest { byte[] lockScreenHash = KeySyncTask.hashCredentials( keyDerivationParameters.getSalt(), TEST_CREDENTIAL); // TODO: what should vault params be here? // TODO: what should counter_id be here? byte[] recoveryKey = decryptThmEncryptedKey( lockScreenHash, recoveryData.getEncryptedRecoveryKeyBlob(), /*vaultParams=*/ new byte[0]); /*vaultParams=*/ KeySyncUtils.packVaultParams( mKeyPair.getPublic(), /*counterId=*/ 1, /*maxAttempts=*/ 10, TEST_DEVICE_ID)); List<KeyEntryRecoveryData> applicationKeys = recoveryData.getApplicationKeyBlobs(); assertEquals(1, applicationKeys.size()); KeyEntryRecoveryData keyData = applicationKeys.get(0); Loading services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java +81 −0 Original line number Diff line number Diff line Loading @@ -30,9 +30,12 @@ import com.google.common.collect.ImmutableMap; import org.junit.Test; import org.junit.runner.RunWith; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.charset.StandardCharsets; import java.security.KeyPair; import java.security.MessageDigest; import java.security.PublicKey; import java.util.Arrays; import java.util.Map; import java.util.Random; Loading @@ -57,6 +60,8 @@ public class KeySyncUtilsTest { "V1 KF_claim".getBytes(StandardCharsets.UTF_8); private static final byte[] RECOVERY_RESPONSE_HEADER = "V1 reencrypted_recovery_key".getBytes(StandardCharsets.UTF_8); private static final int PUBLIC_KEY_LENGTH_BYTES = 65; private static final int VAULT_PARAMS_LENGTH_BYTES = 85; @Test public void calculateThmKfHash_isShaOfLockScreenHashWithPrefix() throws Exception { Loading Loading @@ -336,6 +341,82 @@ public class KeySyncUtilsTest { } } @Test public void packVaultParams_returns85Bytes() throws Exception { PublicKey thmPublicKey = SecureBox.genKeyPair().getPublic(); byte[] packedForm = KeySyncUtils.packVaultParams( thmPublicKey, /*counterId=*/ 1001L, /*maxAttempts=*/ 10, /*deviceId=*/ 1L); assertEquals(VAULT_PARAMS_LENGTH_BYTES, packedForm.length); } @Test public void packVaultParams_encodesPublicKeyInFirst65Bytes() throws Exception { PublicKey thmPublicKey = SecureBox.genKeyPair().getPublic(); byte[] packedForm = KeySyncUtils.packVaultParams( thmPublicKey, /*counterId=*/ 1001L, /*maxAttempts=*/ 10, /*deviceId=*/ 1L); assertArrayEquals( SecureBox.encodePublicKey(thmPublicKey), Arrays.copyOf(packedForm, PUBLIC_KEY_LENGTH_BYTES)); } @Test public void packVaultParams_encodesCounterIdAsSecondParam() throws Exception { long counterId = 103502L; byte[] packedForm = KeySyncUtils.packVaultParams( SecureBox.genKeyPair().getPublic(), counterId, /*maxAttempts=*/ 10, /*deviceId=*/ 1L); ByteBuffer byteBuffer = ByteBuffer.wrap(packedForm) .order(ByteOrder.LITTLE_ENDIAN); byteBuffer.position(PUBLIC_KEY_LENGTH_BYTES); assertEquals(counterId, byteBuffer.getLong()); } @Test public void packVaultParams_encodesMaxAttemptsAsThirdParam() throws Exception { int maxAttempts = 10; byte[] packedForm = KeySyncUtils.packVaultParams( SecureBox.genKeyPair().getPublic(), /*counterId=*/ 1001L, maxAttempts, /*deviceId=*/ 1L); ByteBuffer byteBuffer = ByteBuffer.wrap(packedForm) .order(ByteOrder.LITTLE_ENDIAN); byteBuffer.position(PUBLIC_KEY_LENGTH_BYTES + Long.BYTES); assertEquals(maxAttempts, byteBuffer.getInt()); } @Test public void packVaultParams_encodesDeviceIdAsLastParam() throws Exception { long deviceId = 102942158152L; byte[] packedForm = KeySyncUtils.packVaultParams( SecureBox.genKeyPair().getPublic(), /*counterId=*/ 10021L, /*maxAttempts=*/ 10, deviceId); ByteBuffer byteBuffer = ByteBuffer.wrap(packedForm) .order(ByteOrder.LITTLE_ENDIAN); byteBuffer.position(PUBLIC_KEY_LENGTH_BYTES + Long.BYTES + Integer.BYTES); assertEquals(deviceId, byteBuffer.getLong()); } private static byte[] randomBytes(int n) { byte[] bytes = new byte[n]; new Random().nextBytes(bytes); Loading Loading
services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java +13 −4 Original line number Diff line number Diff line Loading @@ -63,6 +63,7 @@ public class KeySyncTask implements Runnable { private static final int SALT_LENGTH_BYTES = 16; private static final int LENGTH_PREFIX_BYTES = Integer.BYTES; private static final String LOCK_SCREEN_HASH_ALGORITHM = "SHA-256"; private static final int TRUSTED_HARDWARE_MAX_ATTEMPTS = 10; private final RecoverableKeyStoreDb mRecoverableKeyStoreDb; private final int mUserId; Loading Loading @@ -140,19 +141,23 @@ public class KeySyncTask implements Runnable { Log.w(TAG, "No recovery agent initialized for user " + mUserId); return; } if (!mSnapshotListenersStorage.hasListener(recoveryAgentUid)) { Log.w(TAG, "No pending intent registered for recovery agent " + recoveryAgentUid); return; } PublicKey publicKey = getVaultPublicKey(); if (publicKey == null) { Log.w(TAG, "Not initialized for KeySync: no public key set. Cancelling task."); return; } Long deviceId = mRecoverableKeyStoreDb.getServerParameters(mUserId, recoveryAgentUid); if (deviceId == null) { Log.w(TAG, "No device ID set for user " + mUserId); return; } byte[] salt = generateSalt(); byte[] localLskfHash = hashCredentials(salt, mCredential); Loading Loading @@ -191,8 +196,12 @@ public class KeySyncTask implements Runnable { return; } // TODO: construct vault params and vault metadata byte[] vaultParams = {}; // TODO: where do we get counter_id from here? byte[] vaultParams = KeySyncUtils.packVaultParams( publicKey, /*counterId=*/ 1, TRUSTED_HARDWARE_MAX_ATTEMPTS, deviceId); byte[] encryptedRecoveryKey; try { Loading
services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java +23 −0 Original line number Diff line number Diff line Loading @@ -18,6 +18,8 @@ package com.android.server.locksettings.recoverablekeystore; import com.android.internal.annotations.VisibleForTesting; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.KeyFactory; Loading Loading @@ -60,6 +62,7 @@ public class KeySyncUtils { private static final byte[] THM_KF_HASH_PREFIX = "THM_KF_hash".getBytes(StandardCharsets.UTF_8); private static final int KEY_CLAIMANT_LENGTH_BYTES = 16; private static final int VAULT_PARAMS_LENGTH_BYTES = 85; /** * Encrypts the recovery key using both the lock screen hash and the remote storage's public Loading Loading @@ -280,6 +283,26 @@ public class KeySyncUtils { return keyFactory.generatePublic(publicKeySpec); } /** * Packs vault params into a binary format. * * @param thmPublicKey Public key of the trusted hardware module. * @param counterId ID referring to the specific counter in the hardware module. * @param maxAttempts Maximum allowed guesses before trusted hardware wipes key. * @param deviceId ID of the device. * @return The binary vault params, ready for sync. */ public static byte[] packVaultParams( PublicKey thmPublicKey, long counterId, int maxAttempts, long deviceId) { return ByteBuffer.allocate(VAULT_PARAMS_LENGTH_BYTES) .order(ByteOrder.LITTLE_ENDIAN) .put(SecureBox.encodePublicKey(thmPublicKey)) .putLong(counterId) .putInt(maxAttempts) .putLong(deviceId) .array(); } /** * Returns the concatenation of all the given {@code arrays}. */ Loading
services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java +7 −1 Original line number Diff line number Diff line Loading @@ -372,7 +372,13 @@ public class SecureBox { } } @VisibleForTesting /** * Encodes public key in format expected by the secure hardware module. This is used as part * of the vault params. * * @param publicKey The public key. * @return The key packed into a 65-byte array. */ static byte[] encodePublicKey(PublicKey publicKey) { ECPoint point = ((ECPublicKey) publicKey).getW(); byte[] x = point.getAffineX().toByteArray(); Loading
services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java +29 −2 Original line number Diff line number Diff line Loading @@ -72,6 +72,7 @@ public class KeySyncTaskTest { private static final int TEST_USER_ID = 1000; private static final int TEST_APP_UID = 10009; private static final int TEST_RECOVERY_AGENT_UID = 90873; private static final long TEST_DEVICE_ID = 13295035643L; private static final String TEST_APP_KEY_ALIAS = "rcleaver"; private static final int TEST_GENERATION_ID = 2; private static final int TEST_CREDENTIAL_TYPE = CREDENTIAL_TYPE_PASSWORD; Loading Loading @@ -223,6 +224,8 @@ public class KeySyncTaskTest { @Test public void run_doesNotSendAnythingIfNoRecoveryAgentPendingIntentRegistered() throws Exception { SecretKey applicationKey = generateKey(); mRecoverableKeyStoreDb.setServerParameters( TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_DEVICE_ID); mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID); mRecoverableKeyStoreDb.insertKey( TEST_USER_ID, Loading @@ -237,9 +240,29 @@ public class KeySyncTaskTest { assertNull(mRecoverySnapshotStorage.get(TEST_USER_ID)); } @Test public void run_doesNotSendAnythingIfNoDeviceIdIsSet() throws Exception { SecretKey applicationKey = generateKey(); mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID); mRecoverableKeyStoreDb.insertKey( TEST_USER_ID, TEST_APP_UID, TEST_APP_KEY_ALIAS, WrappedKey.fromSecretKey(mEncryptKey, applicationKey)); mRecoverableKeyStoreDb.setRecoveryServicePublicKey( TEST_USER_ID, TEST_RECOVERY_AGENT_UID, mKeyPair.getPublic()); when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true); mKeySyncTask.run(); assertNull(mRecoverySnapshotStorage.get(TEST_USER_ID)); } @Test public void run_sendsEncryptedKeysIfAvailableToSync() throws Exception { SecretKey applicationKey = generateKey(); mRecoverableKeyStoreDb.setServerParameters( TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_DEVICE_ID); mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID); mRecoverableKeyStoreDb.insertKey( TEST_USER_ID, Loading @@ -261,11 +284,15 @@ public class KeySyncTaskTest { byte[] lockScreenHash = KeySyncTask.hashCredentials( keyDerivationParameters.getSalt(), TEST_CREDENTIAL); // TODO: what should vault params be here? // TODO: what should counter_id be here? byte[] recoveryKey = decryptThmEncryptedKey( lockScreenHash, recoveryData.getEncryptedRecoveryKeyBlob(), /*vaultParams=*/ new byte[0]); /*vaultParams=*/ KeySyncUtils.packVaultParams( mKeyPair.getPublic(), /*counterId=*/ 1, /*maxAttempts=*/ 10, TEST_DEVICE_ID)); List<KeyEntryRecoveryData> applicationKeys = recoveryData.getApplicationKeyBlobs(); assertEquals(1, applicationKeys.size()); KeyEntryRecoveryData keyData = applicationKeys.get(0); Loading
services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java +81 −0 Original line number Diff line number Diff line Loading @@ -30,9 +30,12 @@ import com.google.common.collect.ImmutableMap; import org.junit.Test; import org.junit.runner.RunWith; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.charset.StandardCharsets; import java.security.KeyPair; import java.security.MessageDigest; import java.security.PublicKey; import java.util.Arrays; import java.util.Map; import java.util.Random; Loading @@ -57,6 +60,8 @@ public class KeySyncUtilsTest { "V1 KF_claim".getBytes(StandardCharsets.UTF_8); private static final byte[] RECOVERY_RESPONSE_HEADER = "V1 reencrypted_recovery_key".getBytes(StandardCharsets.UTF_8); private static final int PUBLIC_KEY_LENGTH_BYTES = 65; private static final int VAULT_PARAMS_LENGTH_BYTES = 85; @Test public void calculateThmKfHash_isShaOfLockScreenHashWithPrefix() throws Exception { Loading Loading @@ -336,6 +341,82 @@ public class KeySyncUtilsTest { } } @Test public void packVaultParams_returns85Bytes() throws Exception { PublicKey thmPublicKey = SecureBox.genKeyPair().getPublic(); byte[] packedForm = KeySyncUtils.packVaultParams( thmPublicKey, /*counterId=*/ 1001L, /*maxAttempts=*/ 10, /*deviceId=*/ 1L); assertEquals(VAULT_PARAMS_LENGTH_BYTES, packedForm.length); } @Test public void packVaultParams_encodesPublicKeyInFirst65Bytes() throws Exception { PublicKey thmPublicKey = SecureBox.genKeyPair().getPublic(); byte[] packedForm = KeySyncUtils.packVaultParams( thmPublicKey, /*counterId=*/ 1001L, /*maxAttempts=*/ 10, /*deviceId=*/ 1L); assertArrayEquals( SecureBox.encodePublicKey(thmPublicKey), Arrays.copyOf(packedForm, PUBLIC_KEY_LENGTH_BYTES)); } @Test public void packVaultParams_encodesCounterIdAsSecondParam() throws Exception { long counterId = 103502L; byte[] packedForm = KeySyncUtils.packVaultParams( SecureBox.genKeyPair().getPublic(), counterId, /*maxAttempts=*/ 10, /*deviceId=*/ 1L); ByteBuffer byteBuffer = ByteBuffer.wrap(packedForm) .order(ByteOrder.LITTLE_ENDIAN); byteBuffer.position(PUBLIC_KEY_LENGTH_BYTES); assertEquals(counterId, byteBuffer.getLong()); } @Test public void packVaultParams_encodesMaxAttemptsAsThirdParam() throws Exception { int maxAttempts = 10; byte[] packedForm = KeySyncUtils.packVaultParams( SecureBox.genKeyPair().getPublic(), /*counterId=*/ 1001L, maxAttempts, /*deviceId=*/ 1L); ByteBuffer byteBuffer = ByteBuffer.wrap(packedForm) .order(ByteOrder.LITTLE_ENDIAN); byteBuffer.position(PUBLIC_KEY_LENGTH_BYTES + Long.BYTES); assertEquals(maxAttempts, byteBuffer.getInt()); } @Test public void packVaultParams_encodesDeviceIdAsLastParam() throws Exception { long deviceId = 102942158152L; byte[] packedForm = KeySyncUtils.packVaultParams( SecureBox.genKeyPair().getPublic(), /*counterId=*/ 10021L, /*maxAttempts=*/ 10, deviceId); ByteBuffer byteBuffer = ByteBuffer.wrap(packedForm) .order(ByteOrder.LITTLE_ENDIAN); byteBuffer.position(PUBLIC_KEY_LENGTH_BYTES + Long.BYTES + Integer.BYTES); assertEquals(deviceId, byteBuffer.getLong()); } private static byte[] randomBytes(int n) { byte[] bytes = new byte[n]; new Random().nextBytes(bytes); Loading