Encode vault params in key sync task
Still not sure how we're getting counter_id here, though? Test: adb shell am instrument -w -e package com.android.server.locksettings.recoverablekeystore com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner Change-Id: Ic473fff4a19c1d044a6381a1459eca0835a55697
This commit is contained in:
@@ -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;
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
@@ -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
|
||||
@@ -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}.
|
||||
*/
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
@@ -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,
|
||||
@@ -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,
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
@@ -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 {
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user