OSDN Git Service

Add RecoverySession importKeyChainSnapshot method
authorRobert Berry <robertberry@google.com>
Mon, 19 Mar 2018 18:00:46 +0000 (18:00 +0000)
committerRobert Berry <robertberry@google.com>
Wed, 21 Mar 2018 10:24:41 +0000 (10:24 +0000)
This imports the keys directly into the keystore of LockSettingsService,
allowing them to be accessed via the RecoveryController getKey method.
This is better as it does not expose raw key material to any app.

Bug: 74345822
Test: runtest frameworks-services -p \
      com.android.server.locksettings.recoverablekeystore

Change-Id: I4991b0cff1d2fa2e5bd0b53a71c096499e93e98b

core/java/android/security/keystore/recovery/RecoveryController.java
core/java/android/security/keystore/recovery/RecoverySession.java
core/java/com/android/internal/widget/ILockSettings.aidl
services/core/java/com/android/server/locksettings/LockSettingsService.java
services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
services/core/java/com/android/server/locksettings/recoverablekeystore/storage/ApplicationKeyStorage.java
services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java

index 7523afd..10c1c9e 100644 (file)
@@ -547,10 +547,7 @@ public class RecoveryController {
             if (grantAlias == null) {
                 throw new InternalRecoveryServiceException("null grant alias");
             }
-            return AndroidKeyStoreProvider.loadAndroidKeyStoreKeyFromKeystore(
-                    mKeyStore,
-                    grantAlias,
-                    KeyStore.UID_SELF);
+            return getKeyFromGrant(grantAlias);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         } catch (UnrecoverableKeyException e) {
@@ -581,10 +578,7 @@ public class RecoveryController {
             if (grantAlias == null) {
                 throw new InternalRecoveryServiceException("Null grant alias");
             }
-            return AndroidKeyStoreProvider.loadAndroidKeyStoreKeyFromKeystore(
-                    mKeyStore,
-                    grantAlias,
-                    KeyStore.UID_SELF);
+            return getKeyFromGrant(alias);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         } catch (UnrecoverableKeyException e) {
@@ -614,10 +608,7 @@ public class RecoveryController {
             if (grantAlias == null) {
                 return null;
             }
-            return AndroidKeyStoreProvider.loadAndroidKeyStoreKeyFromKeystore(
-                    mKeyStore,
-                    grantAlias,
-                    KeyStore.UID_SELF);
+            return getKeyFromGrant(grantAlias);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         } catch (ServiceSpecificException e) {
@@ -626,6 +617,16 @@ public class RecoveryController {
     }
 
     /**
+     * Returns the key with the given {@code grantAlias}.
+     */
+    Key getKeyFromGrant(String grantAlias) throws UnrecoverableKeyException {
+        return AndroidKeyStoreProvider.loadAndroidKeyStoreKeyFromKeystore(
+                mKeyStore,
+                grantAlias,
+                KeyStore.UID_SELF);
+    }
+
+    /**
      * Removes a key called {@code alias} from the recoverable key store.
      *
      * @param alias The key alias.
index 137dd89..b646691 100644 (file)
 
 package android.security.keystore.recovery;
 
+import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
+import android.util.ArrayMap;
 import android.util.Log;
 
+import java.security.Key;
 import java.security.SecureRandom;
+import java.security.UnrecoverableKeyException;
 import java.security.cert.CertPath;
 import java.security.cert.CertificateException;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 
 /**
@@ -187,6 +192,63 @@ public class RecoverySession implements AutoCloseable {
     }
 
     /**
+     * Imports key chain snapshot recovered from a remote vault.
+     *
+     * @param recoveryKeyBlob Recovery blob encrypted by symmetric key generated for this session.
+     * @param applicationKeys Application keys. Key material can be decrypted using recoveryKeyBlob
+     *     and session.
+     * @throws SessionExpiredException if {@code session} has since been closed.
+     * @throws DecryptionFailedException if unable to decrypt the snapshot.
+     * @throws InternalRecoveryServiceException if an error occurs internal to the recovery service.
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.RECOVER_KEYSTORE)
+    public Map<String, Key> recoverKeyChainSnapshot(
+            @NonNull byte[] recoveryKeyBlob,
+            @NonNull List<WrappedApplicationKey> applicationKeys
+    ) throws SessionExpiredException, DecryptionFailedException, InternalRecoveryServiceException {
+        try {
+            Map<String, String> grantAliases = mRecoveryController
+                    .getBinder()
+                    .recoverKeyChainSnapshot(mSessionId, recoveryKeyBlob, applicationKeys);
+            return getKeysFromGrants(grantAliases);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } catch (ServiceSpecificException e) {
+            if (e.errorCode == RecoveryController.ERROR_DECRYPTION_FAILED) {
+                throw new DecryptionFailedException(e.getMessage());
+            }
+            if (e.errorCode == RecoveryController.ERROR_SESSION_EXPIRED) {
+                throw new SessionExpiredException(e.getMessage());
+            }
+            throw mRecoveryController.wrapUnexpectedServiceSpecificException(e);
+        }
+    }
+
+    /** Given a map from alias to grant alias, returns a map from alias to a {@link Key} handle. */
+    private Map<String, Key> getKeysFromGrants(Map<String, String> grantAliases)
+            throws InternalRecoveryServiceException {
+        ArrayMap<String, Key> keysByAlias = new ArrayMap<>(grantAliases.size());
+        for (String alias : grantAliases.keySet()) {
+            String grantAlias = grantAliases.get(alias);
+            Key key;
+            try {
+                key = mRecoveryController.getKeyFromGrant(grantAlias);
+            } catch (UnrecoverableKeyException e) {
+                throw new InternalRecoveryServiceException(
+                        String.format(
+                                Locale.US,
+                                "Failed to get key '%s' from grant '%s'",
+                                alias,
+                                grantAlias), e);
+            }
+            keysByAlias.put(alias, key);
+        }
+        return keysByAlias;
+    }
+
+    /**
      * An internal session ID, used by the framework to match recovery claims to snapshot responses.
      *
      * @hide
index 25e1589..7b7ec8c 100644 (file)
@@ -83,5 +83,9 @@ interface ILockSettings {
             in List<KeyChainProtectionParams> secrets);
     Map/*<String, byte[]>*/ recoverKeys(in String sessionId, in byte[] recoveryKeyBlob,
             in List<WrappedApplicationKey> applicationKeys);
+    Map/*<String, String>*/ recoverKeyChainSnapshot(
+            in String sessionId,
+            in byte[] recoveryKeyBlob,
+            in List<WrappedApplicationKey> applicationKeys);
     void closeSession(in String sessionId);
 }
index 74ebf3e..d7ee1b6 100644 (file)
@@ -2063,11 +2063,19 @@ public class LockSettingsService extends ILockSettings.Stub {
     }
 
     @Override
+    public Map<String, String> recoverKeyChainSnapshot(
+            @NonNull String sessionId,
+            @NonNull byte[] recoveryKeyBlob,
+            @NonNull List<WrappedApplicationKey> applicationKeys) throws RemoteException {
+        return mRecoverableKeyStoreManager.recoverKeyChainSnapshot(
+                sessionId, recoveryKeyBlob, applicationKeys);
+    }
+
+    @Override
     public Map<String, byte[]> recoverKeys(@NonNull String sessionId,
             @NonNull byte[] recoveryKeyBlob, @NonNull List<WrappedApplicationKey> applicationKeys)
             throws RemoteException {
-        return mRecoverableKeyStoreManager.recoverKeys(
-                sessionId, recoveryKeyBlob, applicationKeys);
+        return mRecoverableKeyStoreManager.recoverKeys(sessionId, recoveryKeyBlob, applicationKeys);
     }
 
     @Override
index 20f3403..6dfd208 100644 (file)
@@ -31,6 +31,7 @@ import android.annotation.Nullable;
 import android.app.PendingIntent;
 import android.content.Context;
 import android.os.Binder;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
 import android.os.UserHandle;
@@ -40,6 +41,7 @@ import android.security.keystore.recovery.RecoveryCertPath;
 import android.security.keystore.recovery.RecoveryController;
 import android.security.keystore.recovery.WrappedApplicationKey;
 import android.security.KeyStore;
+import android.util.ArrayMap;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -550,6 +552,78 @@ public class RecoverableKeyStoreManager {
     }
 
     /**
+     * Invoked by a recovery agent after a successful recovery claim is sent to the remote vault
+     * service.
+     *
+     * @param sessionId The session ID used to generate the claim. See
+     *     {@link #startRecoverySession(String, byte[], byte[], byte[], List)}.
+     * @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.
+     * @throws RemoteException if an error occurred recovering the keys.
+     */
+    public Map<String, String> recoverKeyChainSnapshot(
+            @NonNull String sessionId,
+            @NonNull byte[] encryptedRecoveryKey,
+            @NonNull List<WrappedApplicationKey> applicationKeys) throws RemoteException {
+        checkRecoverKeyStorePermission();
+        int userId = UserHandle.getCallingUserId();
+        int uid = Binder.getCallingUid();
+        RecoverySessionStorage.Entry sessionEntry = mRecoverySessionStorage.get(uid, sessionId);
+        if (sessionEntry == null) {
+            throw new ServiceSpecificException(ERROR_SESSION_EXPIRED,
+                    String.format(Locale.US,
+                            "Application uid=%d does not have pending session '%s'",
+                            uid,
+                            sessionId));
+        }
+
+        try {
+            byte[] recoveryKey = decryptRecoveryKey(sessionEntry, encryptedRecoveryKey);
+            Map<String, byte[]> keysByAlias = recoverApplicationKeys(recoveryKey, applicationKeys);
+            return importKeyMaterials(userId, uid, keysByAlias);
+        } catch (KeyStoreException e) {
+            throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
+        } finally {
+            sessionEntry.destroy();
+            mRecoverySessionStorage.remove(uid);
+        }
+    }
+
+    /**
+     * Imports the key materials, returning a map from alias to grant alias for the calling user.
+     *
+     * @param userId The calling user ID.
+     * @param uid The calling uid.
+     * @param keysByAlias The key materials, keyed by alias.
+     * @throws KeyStoreException if an error occurs importing the key or getting the grant.
+     */
+    private Map<String, String> importKeyMaterials(
+            int userId, int uid, Map<String, byte[]> keysByAlias) throws KeyStoreException {
+        ArrayMap<String, String> grantAliasesByAlias = new ArrayMap<>(keysByAlias.size());
+        for (String alias : keysByAlias.keySet()) {
+            mApplicationKeyStorage.setSymmetricKeyEntry(userId, uid, alias, keysByAlias.get(alias));
+            String grantAlias = getAlias(userId, uid, alias);
+            Log.i(TAG, String.format(Locale.US, "Import %s -> %s", alias, grantAlias));
+            grantAliasesByAlias.put(alias, grantAlias);
+        }
+        return grantAliasesByAlias;
+    }
+
+    /**
+     * Returns an alias for the key.
+     *
+     * @param userId The user ID of the calling process.
+     * @param uid The uid of the calling process.
+     * @param alias The alias of the key.
+     * @return The alias in the calling process's keystore.
+     */
+    private String getAlias(int userId, int uid, String alias) {
+        return mApplicationKeyStorage.getGrantAlias(userId, uid, alias);
+    }
+
+    /**
      * Deprecated
      * Generates a key named {@code alias} in the recoverable store for the calling uid. Then
      * returns the raw key material.
@@ -626,7 +700,7 @@ public class RecoverableKeyStoreManager {
             byte[] secretKey =
                     mRecoverableKeyGenerator.generateAndStoreKey(encryptionKey, userId, uid, alias);
             mApplicationKeyStorage.setSymmetricKeyEntry(userId, uid, alias, secretKey);
-            return mApplicationKeyStorage.getGrantAlias(userId, uid, alias);
+            return getAlias(userId, uid, alias);
         } catch (KeyStoreException | InvalidKeyException | RecoverableKeyStorageException e) {
             throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
         }
@@ -677,7 +751,7 @@ public class RecoverableKeyStoreManager {
 
             // Import the key to Android KeyStore and get grant
             mApplicationKeyStorage.setSymmetricKeyEntry(userId, uid, alias, keyBytes);
-            return mApplicationKeyStorage.getGrantAlias(userId, uid, alias);
+            return getAlias(userId, uid, alias);
         } catch (KeyStoreException | InvalidKeyException | RecoverableKeyStorageException e) {
             throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
         }
@@ -691,8 +765,7 @@ public class RecoverableKeyStoreManager {
     public String getKey(@NonNull String alias) throws RemoteException {
         int uid = Binder.getCallingUid();
         int userId = UserHandle.getCallingUserId();
-        String grantAlias = mApplicationKeyStorage.getGrantAlias(userId, uid, alias);
-        return grantAlias;
+        return getAlias(userId, uid, alias);
     }
 
     private byte[] decryptRecoveryKey(
index 3d97623..84ddbf7 100644 (file)
@@ -24,6 +24,7 @@ import android.security.Credentials;
 import android.security.keystore.KeyProperties;
 import android.security.keystore.KeyProtection;
 import android.security.KeyStore;
+import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.locksettings.recoverablekeystore.KeyStoreProxy;
@@ -31,6 +32,8 @@ import com.android.server.locksettings.recoverablekeystore.KeyStoreProxyImpl;
 
 import java.security.KeyStore.SecretKeyEntry;
 import java.security.KeyStoreException;
+import java.util.Locale;
+
 import javax.crypto.spec.SecretKeySpec;
 
 /**
@@ -40,11 +43,13 @@ import javax.crypto.spec.SecretKeySpec;
  * revealing key material
  */
 public class ApplicationKeyStorage {
+    private static final String TAG = "RecoverableAppKeyStore";
+
     private static final String APPLICATION_KEY_ALIAS_PREFIX =
             "com.android.server.locksettings.recoverablekeystore/application/";
 
-    KeyStoreProxy mKeyStore;
-    KeyStore mKeystoreService;
+    private final KeyStoreProxy mKeyStore;
+    private final KeyStore mKeystoreService;
 
     public static ApplicationKeyStorage getInstance(KeyStore keystoreService)
             throws KeyStoreException {
@@ -65,12 +70,15 @@ public class ApplicationKeyStorage {
     public @Nullable String getGrantAlias(int userId, int uid, String alias) {
         // Aliases used by {@link KeyStore} are different than used by public API.
         // {@code USER_PRIVATE_KEY} prefix is used secret keys.
+        Log.i(TAG, String.format(Locale.US, "Get %d/%d/%s", userId, uid, alias));
         String keystoreAlias = Credentials.USER_PRIVATE_KEY + getInternalAlias(userId, uid, alias);
         return mKeystoreService.grant(keystoreAlias, uid);
     }
 
     public void setSymmetricKeyEntry(int userId, int uid, String alias, byte[] secretKey)
             throws KeyStoreException {
+        Log.i(TAG, String.format(Locale.US, "Set %d/%d/%s: %d bytes of key material",
+                userId, uid, alias, secretKey.length));
         try {
             mKeyStore.setEntry(
                 getInternalAlias(userId, uid, alias),
@@ -87,6 +95,7 @@ public class ApplicationKeyStorage {
     }
 
     public void deleteEntry(int userId, int uid, String alias) {
+        Log.i(TAG, String.format(Locale.US, "Del %d/%d/%s", userId, uid, alias));
         try {
             mKeyStore.deleteEntry(getInternalAlias(userId, uid, alias));
         } catch (KeyStoreException e) {
index 0ceb558..464c481 100644 (file)
@@ -40,6 +40,7 @@ import android.os.Binder;
 import android.os.ServiceSpecificException;
 import android.os.UserHandle;
 import android.security.KeyStore;
+import android.security.keystore.AndroidKeyStoreProvider;
 import android.security.keystore.AndroidKeyStoreSecretKey;
 import android.security.keystore.KeyGenParameterSpec;
 import android.security.keystore.KeyProperties;
@@ -68,6 +69,7 @@ import org.mockito.MockitoAnnotations;
 
 import java.io.File;
 import java.nio.charset.StandardCharsets;
+import java.security.UnrecoverableKeyException;
 import java.security.cert.CertPath;
 import java.security.cert.CertificateFactory;
 import java.security.cert.X509Certificate;
@@ -76,8 +78,10 @@ import java.util.concurrent.Executors;
 import java.util.Map;
 import java.util.Random;
 
+import javax.crypto.Cipher;
 import javax.crypto.KeyGenerator;
 import javax.crypto.SecretKey;
+import javax.crypto.spec.GCMParameterSpec;
 import javax.crypto.spec.SecretKeySpec;
 
 @SmallTest
@@ -144,7 +148,6 @@ public class RecoverableKeyStoreManagerTest {
     @Mock private RecoverySnapshotListenersStorage mMockListenersStorage;
     @Mock private KeyguardManager mKeyguardManager;
     @Mock private PlatformKeyManager mPlatformKeyManager;
-    @Mock private KeyStore mKeyStore;
     @Mock private ApplicationKeyStorage mApplicationKeyStorage;
 
     private RecoverableKeyStoreDb mRecoverableKeyStoreDb;
@@ -175,7 +178,7 @@ public class RecoverableKeyStoreManagerTest {
 
         mRecoverableKeyStoreManager = new RecoverableKeyStoreManager(
                 mMockContext,
-                mKeyStore,
+                KeyStore.getInstance(),
                 mRecoverableKeyStoreDb,
                 mRecoverySessionStorage,
                 Executors.newSingleThreadExecutor(),
@@ -219,6 +222,7 @@ public class RecoverableKeyStoreManagerTest {
 
         assertThat(mRecoverableKeyStoreDb.getKey(uid, TEST_ALIAS)).isNotNull();
         assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isTrue();
+        // TODO(76083050) Test the grant mechanism for the keys.
     }
 
     @Test
@@ -671,9 +675,9 @@ public class RecoverableKeyStoreManagerTest {
     }
 
     @Test
-    public void recoverKeys_throwsIfNoSessionIsPresent() throws Exception {
+    public void recoverKeyChainSnapshot_throwsIfNoSessionIsPresent() throws Exception {
         try {
-            mRecoverableKeyStoreManager.recoverKeys(
+            mRecoverableKeyStoreManager.recoverKeyChainSnapshot(
                     TEST_SESSION_ID,
                     /*recoveryKeyBlob=*/ randomBytes(32),
                     /*applicationKeys=*/ ImmutableList.of(
@@ -686,7 +690,7 @@ public class RecoverableKeyStoreManagerTest {
     }
 
     @Test
-    public void recoverKeys_throwsIfRecoveryClaimCannotBeDecrypted() throws Exception {
+    public void recoverKeyChainSnapshot_throwsIfRecoveryClaimCannotBeDecrypted() throws Exception {
         mRecoverableKeyStoreManager.startRecoverySession(
                 TEST_SESSION_ID,
                 TEST_PUBLIC_KEY,
@@ -699,7 +703,7 @@ public class RecoverableKeyStoreManagerTest {
                         TEST_SECRET)));
 
         try {
-            mRecoverableKeyStoreManager.recoverKeys(
+            mRecoverableKeyStoreManager.recoverKeyChainSnapshot(
                     TEST_SESSION_ID,
                     /*encryptedRecoveryKey=*/ randomBytes(60),
                     /*applicationKeys=*/ ImmutableList.of());
@@ -710,7 +714,7 @@ public class RecoverableKeyStoreManagerTest {
     }
 
     @Test
-    public void recoverKeys_throwsIfFailedToDecryptAllApplicationKeys() throws Exception {
+    public void recoverKeyChainSnapshot_throwsIfFailedToDecryptAllApplicationKeys() throws Exception {
         mRecoverableKeyStoreManager.startRecoverySession(
                 TEST_SESSION_ID,
                 TEST_PUBLIC_KEY,
@@ -731,7 +735,7 @@ public class RecoverableKeyStoreManagerTest {
                 encryptedApplicationKey(randomRecoveryKey(), randomBytes(32)));
 
         try {
-            mRecoverableKeyStoreManager.recoverKeys(
+            mRecoverableKeyStoreManager.recoverKeyChainSnapshot(
                     TEST_SESSION_ID,
                     /*encryptedRecoveryKey=*/ encryptedClaimResponse,
                     /*applicationKeys=*/ ImmutableList.of(badApplicationKey));
@@ -742,7 +746,8 @@ public class RecoverableKeyStoreManagerTest {
     }
 
     @Test
-    public void recoverKeys_doesNotThrowIfNoApplicationKeysToBeDecrypted() throws Exception {
+    public void recoverKeyChainSnapshot_doesNotThrowIfNoApplicationKeysToBeDecrypted()
+            throws Exception {
         mRecoverableKeyStoreManager.startRecoverySession(
                 TEST_SESSION_ID,
                 TEST_PUBLIC_KEY,
@@ -759,14 +764,14 @@ public class RecoverableKeyStoreManagerTest {
         byte[] encryptedClaimResponse = encryptClaimResponse(
                 keyClaimant, TEST_SECRET, TEST_VAULT_PARAMS, recoveryKey);
 
-        mRecoverableKeyStoreManager.recoverKeys(
+        mRecoverableKeyStoreManager.recoverKeyChainSnapshot(
                 TEST_SESSION_ID,
                 /*encryptedRecoveryKey=*/ encryptedClaimResponse,
                 /*applicationKeys=*/ ImmutableList.of());
     }
 
     @Test
-    public void recoverKeys_returnsDecryptedKeys() throws Exception {
+    public void recoverKeyChainSnapshot_returnsDecryptedKeys() throws Exception {
         mRecoverableKeyStoreManager.startRecoverySession(
                 TEST_SESSION_ID,
                 TEST_PUBLIC_KEY,
@@ -787,17 +792,18 @@ public class RecoverableKeyStoreManagerTest {
                 TEST_ALIAS,
                 encryptedApplicationKey(recoveryKey, applicationKeyBytes));
 
-        Map<String, byte[]> recoveredKeys = mRecoverableKeyStoreManager.recoverKeys(
+        Map<String, String> recoveredKeys = mRecoverableKeyStoreManager.recoverKeyChainSnapshot(
                 TEST_SESSION_ID,
                 encryptedClaimResponse,
                 ImmutableList.of(applicationKey));
 
         assertThat(recoveredKeys).hasSize(1);
-        assertThat(recoveredKeys.get(TEST_ALIAS)).isEqualTo(applicationKeyBytes);
+        assertThat(recoveredKeys).containsKey(TEST_ALIAS);
+        // TODO(76083050) Test the grant mechanism for the keys.
     }
 
     @Test
-    public void recoverKeys_worksOnOtherApplicationKeysIfOneDecryptionFails() throws Exception {
+    public void recoverKeyChainSnapshot_worksOnOtherApplicationKeysIfOneDecryptionFails() throws Exception {
         mRecoverableKeyStoreManager.startRecoverySession(
                 TEST_SESSION_ID,
                 TEST_PUBLIC_KEY,
@@ -825,13 +831,14 @@ public class RecoverableKeyStoreManagerTest {
                 TEST_ALIAS2,
                 encryptedApplicationKey(recoveryKey, applicationKeyBytes2));
 
-        Map<String, byte[]> recoveredKeys = mRecoverableKeyStoreManager.recoverKeys(
+        Map<String, String> recoveredKeys = mRecoverableKeyStoreManager.recoverKeyChainSnapshot(
                 TEST_SESSION_ID,
                 encryptedClaimResponse,
                 ImmutableList.of(applicationKey1, applicationKey2));
 
         assertThat(recoveredKeys).hasSize(1);
-        assertThat(recoveredKeys.get(TEST_ALIAS2)).isEqualTo(applicationKeyBytes2);
+        assertThat(recoveredKeys).containsKey(TEST_ALIAS2);
+        // TODO(76083050) Test the grant mechanism for the keys.
     }
 
     @Test