diff options
3 files changed, 151 insertions, 8 deletions
| diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java index e4d2b953b61a..37aeb3af051e 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java @@ -16,6 +16,8 @@  package com.android.server.locksettings.recoverablekeystore; +import com.android.internal.annotations.VisibleForTesting; +  import java.nio.charset.StandardCharsets;  import java.security.InvalidKeyException;  import java.security.MessageDigest; @@ -25,6 +27,7 @@ import java.security.SecureRandom;  import java.util.HashMap;  import java.util.Map; +import javax.crypto.AEADBadTagException;  import javax.crypto.KeyGenerator;  import javax.crypto.SecretKey; @@ -45,9 +48,13 @@ public class KeySyncUtils {              "V1 locally_encrypted_recovery_key".getBytes(StandardCharsets.UTF_8);      private static final byte[] ENCRYPTED_APPLICATION_KEY_HEADER =              "V1 encrypted_application_key".getBytes(StandardCharsets.UTF_8); +    private static final byte[] RECOVERY_CLAIM_HEADER = +            "V1 KF_claim".getBytes(StandardCharsets.UTF_8);      private static final byte[] THM_KF_HASH_PREFIX = "THM_KF_hash".getBytes(StandardCharsets.UTF_8); +    private static final int KEY_CLAIMANT_LENGTH_BYTES = 16; +      /**       * Encrypts the recovery key using both the lock screen hash and the remote storage's public       * key. @@ -121,7 +128,7 @@ public class KeySyncUtils {       */      public static SecretKey generateRecoveryKey() throws NoSuchAlgorithmException {          KeyGenerator keyGenerator = KeyGenerator.getInstance(RECOVERY_KEY_ALGORITHM); -        keyGenerator.init(RECOVERY_KEY_SIZE_BITS, SecureRandom.getInstanceStrong()); +        keyGenerator.init(RECOVERY_KEY_SIZE_BITS, new SecureRandom());          return keyGenerator.generateKey();      } @@ -153,13 +160,100 @@ public class KeySyncUtils {      }      /** -     * Returns a new array, the contents of which are the concatenation of {@code a} and {@code b}. +     * Returns a random 16-byte key claimant. +     * +     * @hide +     */ +    public static byte[] generateKeyClaimant() { +        SecureRandom secureRandom = new SecureRandom(); +        byte[] key = new byte[KEY_CLAIMANT_LENGTH_BYTES]; +        secureRandom.nextBytes(key); +        return key; +    } + +    /** +     * Encrypts a claim to recover a remote recovery key. +     * +     * @param publicKey The public key of the remote server. +     * @param vaultParams Associated vault parameters. +     * @param challenge The challenge issued by the server. +     * @param thmKfHash The THM hash of the lock screen. +     * @param keyClaimant The random key claimant. +     * @return The encrypted recovery claim, to be sent to the remote server. +     * @throws NoSuchAlgorithmException if any SecureBox algorithm is not present. +     * @throws InvalidKeyException if the {@code publicKey} could not be used to encrypt. +     * +     * @hide       */ -    private static byte[] concat(byte[] a, byte[] b) { -        byte[] result = new byte[a.length + b.length]; -        System.arraycopy(a, 0, result, 0, a.length); -        System.arraycopy(b, 0, result, a.length, b.length); -        return result; +    public static byte[] encryptRecoveryClaim( +            PublicKey publicKey, +            byte[] vaultParams, +            byte[] challenge, +            byte[] thmKfHash, +            byte[] keyClaimant) throws NoSuchAlgorithmException, InvalidKeyException { +        return SecureBox.encrypt( +                publicKey, +                /*sharedSecret=*/ null, +                /*header=*/ concat(RECOVERY_CLAIM_HEADER, vaultParams, challenge), +                /*payload=*/ concat(thmKfHash, keyClaimant)); +    } + +    /** +     * Decrypts a recovery key, after having retrieved it from a remote server. +     * +     * @param lskfHash The lock screen hash associated with the key. +     * @param encryptedRecoveryKey The encrypted key. +     * @return The raw key material. +     * @throws NoSuchAlgorithmException if any SecureBox algorithm is unavailable. +     * @throws AEADBadTagException if the message has been tampered with or was encrypted with a +     *     different key. +     */ +    public static byte[] decryptRecoveryKey(byte[] lskfHash, byte[] encryptedRecoveryKey) +            throws NoSuchAlgorithmException, InvalidKeyException, AEADBadTagException { +        return SecureBox.decrypt( +                /*ourPrivateKey=*/ null, +                /*sharedSecret=*/ lskfHash, +                /*header=*/ LOCALLY_ENCRYPTED_RECOVERY_KEY_HEADER, +                /*encryptedPayload=*/ encryptedRecoveryKey); +    } + +    /** +     * Decrypts an application key, using the recovery key. +     * +     * @param recoveryKey The recovery key - used to wrap all application keys. +     * @param encryptedApplicationKey The application key to unwrap. +     * @return The raw key material of the application key. +     * @throws NoSuchAlgorithmException if any SecureBox algorithm is unavailable. +     * @throws AEADBadTagException if the message has been tampered with or was encrypted with a +     *     different key. +     */ +    public static byte[] decryptApplicationKey(byte[] recoveryKey, byte[] encryptedApplicationKey) +            throws NoSuchAlgorithmException, InvalidKeyException, AEADBadTagException { +        return SecureBox.decrypt( +                /*ourPrivateKey=*/ null, +                /*sharedSecret=*/ recoveryKey, +                /*header=*/ ENCRYPTED_APPLICATION_KEY_HEADER, +                /*encryptedPayload=*/ encryptedApplicationKey); +    } + +    /** +     * Returns the concatenation of all the given {@code arrays}. +     */ +    @VisibleForTesting +    static byte[] concat(byte[]... arrays) { +        int length = 0; +        for (byte[] array : arrays) { +            length += array.length; +        } + +        byte[] concatenated = new byte[length]; +        int pos = 0; +        for (byte[] array : arrays) { +            System.arraycopy(array, /*srcPos=*/ 0, concatenated, pos, array.length); +            pos += array.length; +        } + +        return concatenated;      }      // Statics only diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java index 457fdc14c7f6..742cb4591864 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java @@ -16,10 +16,15 @@  package com.android.server.locksettings.recoverablekeystore; +import android.annotation.Nullable; +  import java.security.InvalidKeyException;  import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey;  import java.security.PublicKey; +import javax.crypto.AEADBadTagException; +  /**   * TODO(b/69056040) Add implementation of SecureBox. This is a placeholder so KeySyncUtils compiles.   * @@ -32,8 +37,25 @@ public class SecureBox {       * @hide       */      public static byte[] encrypt( -            PublicKey theirPublicKey, byte[] sharedSecret, byte[] header, byte[] payload) +            @Nullable PublicKey theirPublicKey, +            @Nullable byte[] sharedSecret, +            @Nullable byte[] header, +            @Nullable byte[] payload)              throws NoSuchAlgorithmException, InvalidKeyException {          throw new UnsupportedOperationException("Needs to be implemented.");      } + +    /** +     * TODO(b/69056040) Add implementation of decrypt. +     * +     * @hide +     */ +    public static byte[] decrypt( +            @Nullable PrivateKey ourPrivateKey, +            @Nullable byte[] sharedSecret, +            @Nullable byte[] header, +            byte[] encryptedPayload) +            throws NoSuchAlgorithmException, InvalidKeyException, AEADBadTagException { +        throw new UnsupportedOperationException("Needs to be implemented."); +    }  } diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java index c918e8c7899d..ac3abedba7f3 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java @@ -37,6 +37,7 @@ import javax.crypto.SecretKey;  public class KeySyncUtilsTest {      private static final int RECOVERY_KEY_LENGTH_BITS = 256;      private static final int THM_KF_HASH_SIZE = 256; +    private static final int KEY_CLAIMANT_LENGTH_BYTES = 16;      private static final String SHA_256_ALGORITHM = "SHA-256";      @Test @@ -70,6 +71,32 @@ public class KeySyncUtilsTest {          assertFalse(Arrays.equals(a.getEncoded(), b.getEncoded()));      } +    @Test +    public void generateKeyClaimant_returns16Bytes() throws Exception { +        byte[] keyClaimant = KeySyncUtils.generateKeyClaimant(); + +        assertEquals(KEY_CLAIMANT_LENGTH_BYTES, keyClaimant.length); +    } + +    @Test +    public void generateKeyClaimant_generatesANewClaimantEachTime() { +        byte[] a = KeySyncUtils.generateKeyClaimant(); +        byte[] b = KeySyncUtils.generateKeyClaimant(); + +        assertFalse(Arrays.equals(a, b)); +    } + +    @Test +    public void concat_concatenatesArrays() { +        assertArrayEquals( +                utf8Bytes("hello, world!"), +                KeySyncUtils.concat( +                        utf8Bytes("hello"), +                        utf8Bytes(", "), +                        utf8Bytes("world"), +                        utf8Bytes("!"))); +    } +      private static byte[] utf8Bytes(String s) {          return s.getBytes(StandardCharsets.UTF_8);      } |