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:
Robert Berry
2017-12-28 12:08:30 +00:00
parent 4158a67a32
commit 94ea4e4caf
5 changed files with 153 additions and 7 deletions

View File

@@ -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 {

View File

@@ -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}.
*/

View File

@@ -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();

View File

@@ -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);

View File

@@ -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);