Loading services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java +101 −7 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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. Loading Loading @@ -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(); } Loading Loading @@ -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 */ 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. */ 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[] 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 Loading services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java +23 −1 Original line number Diff line number Diff line Loading @@ -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. * Loading @@ -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."); } } services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java +27 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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); } Loading Loading
services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java +101 −7 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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. Loading Loading @@ -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(); } Loading Loading @@ -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 */ 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. */ 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[] 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 Loading
services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java +23 −1 Original line number Diff line number Diff line Loading @@ -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. * Loading @@ -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."); } }
services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java +27 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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); } Loading