Loading services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java +24 −0 Original line number Diff line number Diff line Loading @@ -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); Loading Loading @@ -203,6 +205,28 @@ public class KeySyncUtils { /*payload=*/ concat(thmKfHash, keyClaimant)); } /** * 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. * Loading services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java +97 −5 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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}. Loading Loading @@ -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 Loading @@ -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); Loading @@ -275,14 +280,101 @@ public class RecoverableKeyStoreManager { } } /** * Invoked by a recovery agent after a successful recovery claim is sent to the remote vault * service. * * <p>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<KeyEntryRecoveryData> 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}. * * <p>TODO: and load them into store? * * @throws RemoteException if an error occurred decrypting the keys. */ private void recoverApplicationKeys( @NonNull byte[] recoveryKey, @NonNull List<KeyEntryRecoveryData> 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); } } } /** Loading services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java +8 −5 Original line number Diff line number Diff line Loading @@ -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( Loading @@ -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; Loading @@ -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; } Loading services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorage.java +15 −4 Original line number Diff line number Diff line Loading @@ -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; } /** Loading @@ -159,6 +161,15 @@ public class RecoverySessionStorage implements Destroyable { return mKeyClaimant; } /** * Returns the vault params associated with the session. * * @hide */ public byte[] getVaultParams() { return mVaultParams; } /** * Overwrites the memory for the lskf hash and key claimant. * Loading services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java +38 −0 Original line number Diff line number Diff line Loading @@ -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 { Loading Loading @@ -172,6 +174,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(); Loading Loading
services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java +24 −0 Original line number Diff line number Diff line Loading @@ -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); Loading Loading @@ -203,6 +205,28 @@ public class KeySyncUtils { /*payload=*/ concat(thmKfHash, keyClaimant)); } /** * 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. * Loading
services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java +97 −5 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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}. Loading Loading @@ -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 Loading @@ -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); Loading @@ -275,14 +280,101 @@ public class RecoverableKeyStoreManager { } } /** * Invoked by a recovery agent after a successful recovery claim is sent to the remote vault * service. * * <p>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<KeyEntryRecoveryData> 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}. * * <p>TODO: and load them into store? * * @throws RemoteException if an error occurred decrypting the keys. */ private void recoverApplicationKeys( @NonNull byte[] recoveryKey, @NonNull List<KeyEntryRecoveryData> 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); } } } /** Loading
services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java +8 −5 Original line number Diff line number Diff line Loading @@ -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( Loading @@ -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; Loading @@ -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; } Loading
services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorage.java +15 −4 Original line number Diff line number Diff line Loading @@ -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; } /** Loading @@ -159,6 +161,15 @@ public class RecoverySessionStorage implements Destroyable { return mKeyClaimant; } /** * Returns the vault params associated with the session. * * @hide */ public byte[] getVaultParams() { return mVaultParams; } /** * Overwrites the memory for the lskf hash and key claimant. * Loading
services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java +38 −0 Original line number Diff line number Diff line Loading @@ -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 { Loading Loading @@ -172,6 +174,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(); Loading