Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 94ea4e4c authored by Robert Berry's avatar Robert Berry
Browse files

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
parent 4158a67a
Loading
Loading
Loading
Loading
+13 −4
Original line number Diff line number Diff line
@@ -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 {
+23 −0
Original line number Diff line number Diff line
@@ -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}.
     */
+7 −1
Original line number Diff line number Diff line
@@ -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();
+29 −2
Original line number Diff line number Diff line
@@ -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);
+81 −0
Original line number Diff line number Diff line
@@ -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);