From: Robert Berry Date: Mon, 19 Mar 2018 18:00:46 +0000 (+0000) Subject: Add RecoverySession importKeyChainSnapshot method X-Git-Tag: android-x86-9.0-r1~166^2~60^2 X-Git-Url: http://git.osdn.net/view?a=commitdiff_plain;h=4a5c87def075c805d4fcae7ff01dd2e78ec27b1a;p=android-x86%2Fframeworks-base.git Add RecoverySession importKeyChainSnapshot method 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 --- diff --git a/core/java/android/security/keystore/recovery/RecoveryController.java b/core/java/android/security/keystore/recovery/RecoveryController.java index 7523afdf8041..10c1c9ee1b90 100644 --- a/core/java/android/security/keystore/recovery/RecoveryController.java +++ b/core/java/android/security/keystore/recovery/RecoveryController.java @@ -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. diff --git a/core/java/android/security/keystore/recovery/RecoverySession.java b/core/java/android/security/keystore/recovery/RecoverySession.java index 137dd8946c9a..b646691baf3c 100644 --- a/core/java/android/security/keystore/recovery/RecoverySession.java +++ b/core/java/android/security/keystore/recovery/RecoverySession.java @@ -16,17 +16,22 @@ 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 recoverKeyChainSnapshot( + @NonNull byte[] recoveryKeyBlob, + @NonNull List applicationKeys + ) throws SessionExpiredException, DecryptionFailedException, InternalRecoveryServiceException { + try { + Map 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 getKeysFromGrants(Map grantAliases) + throws InternalRecoveryServiceException { + ArrayMap 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 diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl index 25e1589db74e..7b7ec8cb1407 100644 --- a/core/java/com/android/internal/widget/ILockSettings.aidl +++ b/core/java/com/android/internal/widget/ILockSettings.aidl @@ -83,5 +83,9 @@ interface ILockSettings { in List secrets); Map/**/ recoverKeys(in String sessionId, in byte[] recoveryKeyBlob, in List applicationKeys); + Map/**/ recoverKeyChainSnapshot( + in String sessionId, + in byte[] recoveryKeyBlob, + in List applicationKeys); void closeSession(in String sessionId); } diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index 74ebf3e44616..d7ee1b6be50b 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -2063,11 +2063,19 @@ public class LockSettingsService extends ILockSettings.Stub { } @Override + public Map recoverKeyChainSnapshot( + @NonNull String sessionId, + @NonNull byte[] recoveryKeyBlob, + @NonNull List applicationKeys) throws RemoteException { + return mRecoverableKeyStoreManager.recoverKeyChainSnapshot( + sessionId, recoveryKeyBlob, applicationKeys); + } + + @Override public Map recoverKeys(@NonNull String sessionId, @NonNull byte[] recoveryKeyBlob, @NonNull List applicationKeys) throws RemoteException { - return mRecoverableKeyStoreManager.recoverKeys( - sessionId, recoveryKeyBlob, applicationKeys); + return mRecoverableKeyStoreManager.recoverKeys(sessionId, recoveryKeyBlob, applicationKeys); } @Override 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 20f3403c56c5..6dfd208e8464 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java @@ -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 recoverKeyChainSnapshot( + @NonNull String sessionId, + @NonNull byte[] encryptedRecoveryKey, + @NonNull List 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 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 importKeyMaterials( + int userId, int uid, Map keysByAlias) throws KeyStoreException { + ArrayMap 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( diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/ApplicationKeyStorage.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/ApplicationKeyStorage.java index 3d9762337312..84ddbf778c70 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/ApplicationKeyStorage.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/ApplicationKeyStorage.java @@ -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) { 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 0ceb55862738..464c4819363e 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 @@ -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 recoveredKeys = mRecoverableKeyStoreManager.recoverKeys( + Map 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 recoveredKeys = mRecoverableKeyStoreManager.recoverKeys( + Map 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