From b9a220b9b50ef8d0e19d619721209233b3253c2c Mon Sep 17 00:00:00 2001 From: Robert Berry Date: Thu, 21 Dec 2017 12:41:01 +0000 Subject: [PATCH] Implement recoverKeys This implements all of recoverKeys, except for loading keys into the AndroidKeyStore. Also omitting re-enrolling keys into the recoverable store for now, as it is not clear whether the user will have a lock screen set at this point. If they do not have a lock screen set, we cannot re-enroll keys, as the platform-decrypt key is bound to the lock screen. Also modifies SecureBox to throw AEADBadTagException for any issues with the encrypted payload. IllegalArgumentException is a runtime exception, so would be unexpected, but might occur if the encrypted payload is for some reason garbage. Also, throw NPE if the payload is null, as that is a programmer error - not something that should ever occur at runtime. Test: adb shell am instrument -w -e package com.android.server.locksettings.recoverablekeystore com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner Change-Id: I4f0be412c3044f3472a6aed514f1caf54b7ee41f --- .../recoverablekeystore/KeySyncUtils.java | 24 ++++ .../RecoverableKeyStoreManager.java | 102 +++++++++++++- .../recoverablekeystore/SecureBox.java | 13 +- .../storage/RecoverySessionStorage.java | 19 ++- .../recoverablekeystore/KeySyncUtilsTest.java | 38 ++++++ .../RecoverableKeyStoreManagerTest.java | 149 +++++++++++++++++++++ .../recoverablekeystore/SecureBoxTest.java | 4 +- .../storage/RecoverySessionStorageTest.java | 17 ++- 8 files changed, 344 insertions(+), 22 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 b390fe8194a3..4597fad9a234 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java @@ -54,6 +54,8 @@ public class KeySyncUtils { "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[] RECOVERY_RESPONSE_HEADER = + "V1 reencrypted_recovery_key".getBytes(StandardCharsets.UTF_8); private static final byte[] THM_KF_HASH_PREFIX = "THM_KF_hash".getBytes(StandardCharsets.UTF_8); @@ -204,6 +206,28 @@ public class KeySyncUtils { } /** + * Decrypts response from recovery claim, returning the locally encrypted key. + * + * @param keyClaimant The key claimant, used by the remote service to encrypt the response. + * @param vaultParams Vault params associated with the claim. + * @param encryptedResponse The encrypted response. + * @return The locally encrypted recovery key. + * @throws NoSuchAlgorithmException if any SecureBox algorithm is not present. + * @throws InvalidKeyException if the {@code keyClaimant} could not be used to decrypt. + * @throws AEADBadTagException if the message has been tampered with or was encrypted with a + * different key. + */ + public static byte[] decryptRecoveryClaimResponse( + byte[] keyClaimant, byte[] vaultParams, byte[] encryptedResponse) + throws NoSuchAlgorithmException, InvalidKeyException, AEADBadTagException { + return SecureBox.decrypt( + /*ourPrivateKey=*/ null, + /*sharedSecret=*/ keyClaimant, + /*header=*/ concat(RECOVERY_RESPONSE_HEADER, vaultParams), + /*encryptedPayload=*/ encryptedResponse); + } + + /** * Decrypts a recovery key, after having retrieved it from a remote server. * * @param lskfHash The lock screen hash associated with the key. diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java index c089b40f3024..cfeaaf8ec8d9 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java @@ -35,6 +35,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb; import com.android.server.locksettings.recoverablekeystore.storage.RecoverySessionStorage; +import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; @@ -42,10 +43,13 @@ import java.security.PublicKey; import java.security.spec.InvalidKeySpecException; import java.util.ArrayList; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import javax.crypto.AEADBadTagException; + /** * Class with {@link RecoverableKeyStoreLoader} API implementation and internal methods to interact * with {@code LockSettingsService}. @@ -225,7 +229,7 @@ public class RecoverableKeyStoreManager { * @param verifierPublicKey X509-encoded public key. * @param vaultParams Additional params associated with vault. * @param vaultChallenge Challenge issued by vault service. - * @param secrets Lock-screen hashes. Should have a single element. TODO: why is this a list? + * @param secrets Lock-screen hashes. For now only a single secret is supported. * @return Encrypted bytes of recovery claim. This can then be issued to the vault service. * * @hide @@ -248,7 +252,8 @@ public class RecoverableKeyStoreManager { byte[] keyClaimant = KeySyncUtils.generateKeyClaimant(); byte[] kfHash = secrets.get(0).getSecret(); mRecoverySessionStorage.add( - userId, new RecoverySessionStorage.Entry(sessionId, kfHash, keyClaimant)); + userId, + new RecoverySessionStorage.Entry(sessionId, kfHash, keyClaimant, vaultParams)); try { byte[] thmKfHash = KeySyncUtils.calculateThmKfHash(kfHash); @@ -275,14 +280,101 @@ public class RecoverableKeyStoreManager { } } + /** + * Invoked by a recovery agent after a successful recovery claim is sent to the remote vault + * service. + * + *

TODO: should also load into AndroidKeyStore. + * + * @param sessionId The session ID used to generate the claim. See + * {@link #startRecoverySession(String, byte[], byte[], byte[], List, int)}. + * @param encryptedRecoveryKey The encrypted recovery key blob returned by the remote vault + * service. + * @param applicationKeys The encrypted key blobs returned by the remote vault service. These + * were wrapped with the recovery key. + * @param uid The uid of the recovery agent. + * @throws RemoteException if an error occurred recovering the keys. + */ public void recoverKeys( @NonNull String sessionId, - @NonNull byte[] recoveryKeyBlob, + @NonNull byte[] encryptedRecoveryKey, @NonNull List applicationKeys, - int userId) + int uid) throws RemoteException { checkRecoverKeyStorePermission(); - throw new UnsupportedOperationException(); + + RecoverySessionStorage.Entry sessionEntry = mRecoverySessionStorage.get(uid, sessionId); + if (sessionEntry == null) { + throw new RemoteException(String.format(Locale.US, + "User %d does not have pending session '%s'", uid, sessionId)); + } + + try { + byte[] recoveryKey = decryptRecoveryKey(sessionEntry, encryptedRecoveryKey); + recoverApplicationKeys(recoveryKey, applicationKeys); + } finally { + sessionEntry.destroy(); + mRecoverySessionStorage.remove(uid); + } + } + + private byte[] decryptRecoveryKey( + RecoverySessionStorage.Entry sessionEntry, byte[] encryptedClaimResponse) + throws RemoteException { + try { + byte[] locallyEncryptedKey = KeySyncUtils.decryptRecoveryClaimResponse( + sessionEntry.getKeyClaimant(), + sessionEntry.getVaultParams(), + encryptedClaimResponse); + return KeySyncUtils.decryptRecoveryKey(sessionEntry.getLskfHash(), locallyEncryptedKey); + } catch (InvalidKeyException | AEADBadTagException e) { + throw new RemoteException( + "Failed to decrypt recovery key", + e, + /*enableSuppression=*/ true, + /*writeableStackTrace=*/ true); + } catch (NoSuchAlgorithmException e) { + // Should never happen: all the algorithms used are required by AOSP implementations + throw new RemoteException( + "Missing required algorithm", + e, + /*enableSuppression=*/ true, + /*writeableStackTrace=*/ true); + } + } + + /** + * Uses {@code recoveryKey} to decrypt {@code applicationKeys}. + * + *

TODO: and load them into store? + * + * @throws RemoteException if an error occurred decrypting the keys. + */ + private void recoverApplicationKeys( + @NonNull byte[] recoveryKey, + @NonNull List applicationKeys) throws RemoteException { + for (KeyEntryRecoveryData applicationKey : applicationKeys) { + String alias = new String(applicationKey.getAlias(), StandardCharsets.UTF_8); + byte[] encryptedKeyMaterial = applicationKey.getEncryptedKeyMaterial(); + + try { + // TODO: put decrypted key material in appropriate AndroidKeyStore + KeySyncUtils.decryptApplicationKey(recoveryKey, encryptedKeyMaterial); + } catch (NoSuchAlgorithmException e) { + // Should never happen: all the algorithms used are required by AOSP implementations + throw new RemoteException( + "Missing required algorithm", + e, + /*enableSuppression=*/ true, + /*writeableStackTrace=*/ true); + } catch (InvalidKeyException | AEADBadTagException e) { + throw new RemoteException( + "Failed to recover key with alias '" + alias + "'", + e, + /*enableSuppression=*/ true, + /*writeableStackTrace=*/ true); + } + } } /** 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 d8a2d31f6703..801d4de3cddd 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java @@ -230,7 +230,7 @@ public class SecureBox { * @throws NoSuchAlgorithmException if any underlying crypto algorithm is not supported * @throws InvalidKeyException if the provided key is invalid for underlying crypto algorithms * @throws AEADBadTagException if the authentication tag contained in {@code encryptedPayload} - * cannot be validated + * cannot be validated, or if the payload is not a valid SecureBox V2 payload. * @hide */ public static byte[] decrypt( @@ -244,12 +244,14 @@ public class SecureBox { throw new IllegalArgumentException("Both the private key and shared secret are empty"); } header = emptyByteArrayIfNull(header); - encryptedPayload = emptyByteArrayIfNull(encryptedPayload); + if (encryptedPayload == null) { + throw new NullPointerException("Encrypted payload must not be null."); + } ByteBuffer ciphertextBuffer = ByteBuffer.wrap(encryptedPayload); byte[] version = readEncryptedPayload(ciphertextBuffer, VERSION.length); if (!Arrays.equals(version, VERSION)) { - throw new IllegalArgumentException("The payload was not encrypted by SecureBox v2"); + throw new AEADBadTagException("The payload was not encrypted by SecureBox v2"); } byte[] senderPublicKeyBytes; @@ -271,12 +273,13 @@ public class SecureBox { return aesGcmDecrypt(decryptionKey, randNonce, ciphertext, header); } - private static byte[] readEncryptedPayload(ByteBuffer buffer, int length) { + private static byte[] readEncryptedPayload(ByteBuffer buffer, int length) + throws AEADBadTagException { byte[] output = new byte[length]; try { buffer.get(output); } catch (BufferUnderflowException ex) { - throw new IllegalArgumentException("The encrypted payload is too short"); + throw new AEADBadTagException("The encrypted payload is too short"); } return output; } diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorage.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorage.java index bc56ae1dc181..f7633e4cee8d 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorage.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorage.java @@ -129,15 +129,17 @@ public class RecoverySessionStorage implements Destroyable { public static class Entry implements Destroyable { private final byte[] mLskfHash; private final byte[] mKeyClaimant; + private final byte[] mVaultParams; private final String mSessionId; /** * @hide */ - public Entry(String sessionId, byte[] lskfHash, byte[] keyClaimant) { - this.mLskfHash = lskfHash; - this.mSessionId = sessionId; - this.mKeyClaimant = keyClaimant; + public Entry(String sessionId, byte[] lskfHash, byte[] keyClaimant, byte[] vaultParams) { + mLskfHash = lskfHash; + mSessionId = sessionId; + mKeyClaimant = keyClaimant; + mVaultParams = vaultParams; } /** @@ -160,6 +162,15 @@ public class RecoverySessionStorage implements Destroyable { } /** + * Returns the vault params associated with the session. + * + * @hide + */ + public byte[] getVaultParams() { + return mVaultParams; + } + + /** * Overwrites the memory for the lskf hash and key claimant. * * @hide 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 c328dda68389..6254d528c44b 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 @@ -55,6 +55,8 @@ public class KeySyncUtilsTest { utf8Bytes("snQzsbvclkSsG6PwasAp1oFLzbq3KtFe"); private static final byte[] RECOVERY_CLAIM_HEADER = "V1 KF_claim".getBytes(StandardCharsets.UTF_8); + private static final byte[] RECOVERY_RESPONSE_HEADER = + "V1 reencrypted_recovery_key".getBytes(StandardCharsets.UTF_8); @Test public void calculateThmKfHash_isShaOfLockScreenHashWithPrefix() throws Exception { @@ -173,6 +175,42 @@ public class KeySyncUtilsTest { } @Test + public void decryptRecoveryClaimResponse_decryptsAValidResponse() throws Exception { + byte[] keyClaimant = KeySyncUtils.generateKeyClaimant(); + byte[] vaultParams = randomBytes(100); + byte[] recoveryKey = randomBytes(32); + byte[] encryptedPayload = SecureBox.encrypt( + /*theirPublicKey=*/ null, + /*sharedSecret=*/ keyClaimant, + /*header=*/ KeySyncUtils.concat(RECOVERY_RESPONSE_HEADER, vaultParams), + /*payload=*/ recoveryKey); + + byte[] decrypted = KeySyncUtils.decryptRecoveryClaimResponse( + keyClaimant, vaultParams, encryptedPayload); + + assertArrayEquals(recoveryKey, decrypted); + } + + @Test + public void decryptRecoveryClaimResponse_throwsIfCannotDecrypt() throws Exception { + byte[] vaultParams = randomBytes(100); + byte[] recoveryKey = randomBytes(32); + byte[] encryptedPayload = SecureBox.encrypt( + /*theirPublicKey=*/ null, + /*sharedSecret=*/ KeySyncUtils.generateKeyClaimant(), + /*header=*/ KeySyncUtils.concat(RECOVERY_RESPONSE_HEADER, vaultParams), + /*payload=*/ recoveryKey); + + try { + KeySyncUtils.decryptRecoveryClaimResponse( + KeySyncUtils.generateKeyClaimant(), vaultParams, encryptedPayload); + fail("Did not throw decrypting with bad keyClaimant"); + } catch (AEADBadTagException error) { + // expected + } + } + + @Test public void encryptRecoveryClaim_encryptsLockScreenAndKeyClaimant() throws Exception { KeyPair keyPair = SecureBox.genKeyPair(); byte[] keyClaimant = KeySyncUtils.generateKeyClaimant(); diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java index 56a23de9b897..fb2d3413907f 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java @@ -21,6 +21,7 @@ import static android.security.recoverablekeystore.KeyStoreRecoveryMetadata.TYPE import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.times; @@ -29,6 +30,7 @@ import static org.mockito.Mockito.verify; import android.content.Context; import android.os.RemoteException; import android.security.recoverablekeystore.KeyDerivationParameters; +import android.security.recoverablekeystore.KeyEntryRecoveryData; import android.security.recoverablekeystore.KeyStoreRecoveryMetadata; import android.security.recoverablekeystore.RecoverableKeyStoreLoader; import android.support.test.InstrumentationRegistry; @@ -39,6 +41,7 @@ import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKe import com.android.server.locksettings.recoverablekeystore.storage.RecoverySessionStorage; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import org.junit.After; import org.junit.Before; @@ -50,6 +53,10 @@ import org.mockito.MockitoAnnotations; import java.io.File; import java.nio.charset.StandardCharsets; import java.util.concurrent.Executors; +import java.util.Random; + +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; @SmallTest @RunWith(AndroidJUnit4.class) @@ -77,6 +84,9 @@ public class RecoverableKeyStoreManagerTest { private static final byte[] TEST_VAULT_PARAMS = getUtf8Bytes("vault_params"); private static final int TEST_USER_ID = 10009; private static final int KEY_CLAIMANT_LENGTH_BYTES = 16; + private static final byte[] RECOVERY_RESPONSE_HEADER = + "V1 reencrypted_recovery_key".getBytes(StandardCharsets.UTF_8); + private static final String TEST_ALIAS = "nick"; @Mock private Context mMockContext; @@ -156,6 +166,7 @@ public class RecoverableKeyStoreManagerTest { TEST_VAULT_CHALLENGE, ImmutableList.of(), TEST_USER_ID); + fail("should have thrown"); } catch (RemoteException e) { assertEquals("Only a single KeyStoreRecoveryMetadata is supported", e.getMessage()); @@ -176,13 +187,151 @@ public class RecoverableKeyStoreManagerTest { KeyDerivationParameters.createSHA256Parameters(TEST_SALT), TEST_SECRET)), TEST_USER_ID); + fail("should have thrown"); } catch (RemoteException e) { assertEquals("Not a valid X509 key", e.getMessage()); } } + @Test + public void recoverKeys_throwsIfNoSessionIsPresent() throws Exception { + try { + mRecoverableKeyStoreManager.recoverKeys( + TEST_SESSION_ID, + /*recoveryKeyBlob=*/ randomBytes(32), + /*applicationKeys=*/ ImmutableList.of( + new KeyEntryRecoveryData(getUtf8Bytes("alias"), randomBytes(32)) + ), + TEST_USER_ID); + fail("should have thrown"); + } catch (RemoteException e) { + assertEquals("User 10009 does not have pending session 'karlin'", + e.getMessage()); + } + } + + @Test + public void recoverKeys_throwsIfRecoveryClaimCannotBeDecrypted() throws Exception { + mRecoverableKeyStoreManager.startRecoverySession( + TEST_SESSION_ID, + TEST_PUBLIC_KEY, + TEST_VAULT_PARAMS, + TEST_VAULT_CHALLENGE, + ImmutableList.of(new KeyStoreRecoveryMetadata( + TYPE_LOCKSCREEN, + TYPE_PASSWORD, + KeyDerivationParameters.createSHA256Parameters(TEST_SALT), + TEST_SECRET)), + TEST_USER_ID); + + try { + mRecoverableKeyStoreManager.recoverKeys( + TEST_SESSION_ID, + /*encryptedRecoveryKey=*/ randomBytes(60), + /*applicationKeys=*/ ImmutableList.of(), + /*uid=*/ TEST_USER_ID); + fail("should have thrown"); + } catch (RemoteException e) { + assertEquals("Failed to decrypt recovery key", e.getMessage()); + } + } + + @Test + public void recoverKeys_throwsIfFailedToDecryptAnApplicationKey() throws Exception { + mRecoverableKeyStoreManager.startRecoverySession( + TEST_SESSION_ID, + TEST_PUBLIC_KEY, + TEST_VAULT_PARAMS, + TEST_VAULT_CHALLENGE, + ImmutableList.of(new KeyStoreRecoveryMetadata( + TYPE_LOCKSCREEN, + TYPE_PASSWORD, + KeyDerivationParameters.createSHA256Parameters(TEST_SALT), + TEST_SECRET)), + TEST_USER_ID); + byte[] keyClaimant = mRecoverySessionStorage.get(TEST_USER_ID, TEST_SESSION_ID) + .getKeyClaimant(); + SecretKey recoveryKey = randomRecoveryKey(); + byte[] encryptedClaimResponse = encryptClaimResponse( + keyClaimant, TEST_SECRET, TEST_VAULT_PARAMS, recoveryKey); + KeyEntryRecoveryData badApplicationKey = new KeyEntryRecoveryData( + TEST_ALIAS.getBytes(StandardCharsets.UTF_8), + randomBytes(32)); + + try { + mRecoverableKeyStoreManager.recoverKeys( + TEST_SESSION_ID, + /*encryptedRecoveryKey=*/ encryptedClaimResponse, + /*applicationKeys=*/ ImmutableList.of(badApplicationKey), + /*uid=*/ TEST_USER_ID); + fail("should have thrown"); + } catch (RemoteException e) { + assertEquals("Failed to recover key with alias 'nick'", e.getMessage()); + } + } + + @Test + public void recoverKeys_doesNotThrowIfAllIsOk() throws Exception { + mRecoverableKeyStoreManager.startRecoverySession( + TEST_SESSION_ID, + TEST_PUBLIC_KEY, + TEST_VAULT_PARAMS, + TEST_VAULT_CHALLENGE, + ImmutableList.of(new KeyStoreRecoveryMetadata( + TYPE_LOCKSCREEN, + TYPE_PASSWORD, + KeyDerivationParameters.createSHA256Parameters(TEST_SALT), + TEST_SECRET)), + TEST_USER_ID); + byte[] keyClaimant = mRecoverySessionStorage.get(TEST_USER_ID, TEST_SESSION_ID) + .getKeyClaimant(); + SecretKey recoveryKey = randomRecoveryKey(); + byte[] encryptedClaimResponse = encryptClaimResponse( + keyClaimant, TEST_SECRET, TEST_VAULT_PARAMS, recoveryKey); + KeyEntryRecoveryData applicationKey = new KeyEntryRecoveryData( + TEST_ALIAS.getBytes(StandardCharsets.UTF_8), + randomEncryptedApplicationKey(recoveryKey) + ); + + mRecoverableKeyStoreManager.recoverKeys( + TEST_SESSION_ID, + encryptedClaimResponse, + ImmutableList.of(applicationKey), + TEST_USER_ID); + } + + private static byte[] randomEncryptedApplicationKey(SecretKey recoveryKey) throws Exception { + return KeySyncUtils.encryptKeysWithRecoveryKey(recoveryKey, ImmutableMap.of( + "alias", new SecretKeySpec(randomBytes(32), "AES") + )).get("alias"); + } + + private static byte[] encryptClaimResponse( + byte[] keyClaimant, + byte[] lskfHash, + byte[] vaultParams, + SecretKey recoveryKey) throws Exception { + byte[] locallyEncryptedRecoveryKey = KeySyncUtils.locallyEncryptRecoveryKey( + lskfHash, recoveryKey); + return SecureBox.encrypt( + /*theirPublicKey=*/ null, + /*sharedSecret=*/ keyClaimant, + /*header=*/ KeySyncUtils.concat(RECOVERY_RESPONSE_HEADER, vaultParams), + /*payload=*/ locallyEncryptedRecoveryKey); + } + + private static SecretKey randomRecoveryKey() { + return new SecretKeySpec(randomBytes(32), "AES"); + } + private static byte[] getUtf8Bytes(String s) { return s.getBytes(StandardCharsets.UTF_8); } + + private static byte[] randomBytes(int n) { + byte[] bytes = new byte[n]; + new Random().nextBytes(bytes); + return bytes; + } } diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/SecureBoxTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/SecureBoxTest.java index 72b69f046b33..35ec23b2ee6b 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/SecureBoxTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/SecureBoxTest.java @@ -274,9 +274,9 @@ public class SecureBoxTest { @Test public void decrypt_nullEncryptedPayload() throws Exception { - IllegalArgumentException expected = + NullPointerException expected = expectThrows( - IllegalArgumentException.class, + NullPointerException.class, () -> SecureBox.decrypt( THM_PRIVATE_KEY, diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorageTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorageTest.java index 6aeff2892b9c..6f93fe409e48 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorageTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorageTest.java @@ -37,6 +37,7 @@ public class RecoverySessionStorageTest { private static final int TEST_USER_ID = 696; private static final byte[] TEST_LSKF_HASH = getUtf8Bytes("lskf"); private static final byte[] TEST_KEY_CLAIMANT = getUtf8Bytes("0000111122223333"); + private static final byte[] TEST_VAULT_PARAMS = getUtf8Bytes("vault params vault params"); @Test public void size_isZeroForEmpty() { @@ -47,7 +48,7 @@ public class RecoverySessionStorageTest { public void size_incrementsAfterAdd() { RecoverySessionStorage storage = new RecoverySessionStorage(); storage.add(TEST_USER_ID, new RecoverySessionStorage.Entry( - TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture())); + TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture(), vaultParamsFixture())); assertEquals(1, storage.size()); } @@ -56,7 +57,7 @@ public class RecoverySessionStorageTest { public void size_decrementsAfterRemove() { RecoverySessionStorage storage = new RecoverySessionStorage(); storage.add(TEST_USER_ID, new RecoverySessionStorage.Entry( - TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture())); + TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture(), vaultParamsFixture())); storage.remove(TEST_USER_ID); assertEquals(0, storage.size()); @@ -66,7 +67,7 @@ public class RecoverySessionStorageTest { public void remove_overwritesLskfHashMemory() { RecoverySessionStorage storage = new RecoverySessionStorage(); RecoverySessionStorage.Entry entry = new RecoverySessionStorage.Entry( - TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture()); + TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture(), vaultParamsFixture()); storage.add(TEST_USER_ID, entry); storage.remove(TEST_USER_ID); @@ -78,7 +79,7 @@ public class RecoverySessionStorageTest { public void remove_overwritesKeyClaimantMemory() { RecoverySessionStorage storage = new RecoverySessionStorage(); RecoverySessionStorage.Entry entry = new RecoverySessionStorage.Entry( - TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture()); + TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture(), vaultParamsFixture()); storage.add(TEST_USER_ID, entry); storage.remove(TEST_USER_ID); @@ -90,7 +91,7 @@ public class RecoverySessionStorageTest { public void destroy_overwritesLskfHashMemory() { RecoverySessionStorage storage = new RecoverySessionStorage(); RecoverySessionStorage.Entry entry = new RecoverySessionStorage.Entry( - TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture()); + TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture(), vaultParamsFixture()); storage.add(TEST_USER_ID, entry); storage.destroy(); @@ -102,7 +103,7 @@ public class RecoverySessionStorageTest { public void destroy_overwritesKeyClaimantMemory() { RecoverySessionStorage storage = new RecoverySessionStorage(); RecoverySessionStorage.Entry entry = new RecoverySessionStorage.Entry( - TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture()); + TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture(), vaultParamsFixture()); storage.add(TEST_USER_ID, entry); storage.destroy(); @@ -126,6 +127,10 @@ public class RecoverySessionStorageTest { return Arrays.copyOf(TEST_KEY_CLAIMANT, TEST_KEY_CLAIMANT.length); } + private static byte[] vaultParamsFixture() { + return Arrays.copyOf(TEST_VAULT_PARAMS, TEST_VAULT_PARAMS.length); + } + private static byte[] getUtf8Bytes(String s) { return s.getBytes(StandardCharsets.UTF_8); } -- 2.11.0