From b8b030bdbb1aa20d74bedce3ca6f302be5421a86 Mon Sep 17 00:00:00 2001 From: Dmitry Dementyev Date: Tue, 19 Dec 2017 11:02:54 -0800 Subject: [PATCH] Add more methods to RecoverableKeyStoreLoader. 1) Methods to get key status. 2) Register pending intent to get notification about new recovery snapshots. Test: none Bug: 66499222 Change-Id: I4d5f8c1a6581b5e08f4589e19961d93c499689e1 --- .../RecoverableKeyStoreLoader.java | 101 +++++++++++++++++- .../internal/widget/ILockSettings.aidl | 6 ++ .../locksettings/LockSettingsService.java | 13 +++ .../RecoverableKeyStoreManager.java | 43 +++++++- 4 files changed, 155 insertions(+), 8 deletions(-) diff --git a/core/java/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java b/core/java/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java index f2f225df7f930..f88768b184fdd 100644 --- a/core/java/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java +++ b/core/java/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java @@ -18,6 +18,7 @@ package android.security.recoverablekeystore; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.PendingIntent; import android.content.pm.PackageManager.NameNotFoundException; import android.os.RemoteException; import android.os.ServiceManager; @@ -29,6 +30,7 @@ import android.util.AndroidException; import com.android.internal.widget.ILockSettings; import java.util.List; +import java.util.Map; /** * A wrapper around KeyStore which lets key be exported to trusted hardware on server side and @@ -43,9 +45,23 @@ public class RecoverableKeyStoreLoader { public static final int NO_ERROR = KeyStore.NO_ERROR; public static final int SYSTEM_ERROR = KeyStore.SYSTEM_ERROR; public static final int UNINITIALIZED_RECOVERY_PUBLIC_KEY = 20; - // Too many updates to recovery public key or server parameters. + /** + * Rate limit is enforced to prevent using too many trusted remote devices, since each device + * can have its own number of user secret guesses allowed. + * + * @hide + */ public static final int RATE_LIMIT_EXCEEDED = 21; + /** Key has been successfully synced. */ + public static final int RECOVERY_STATUS_SYNCED = 0; + /** Waiting for recovery agent to sync the key. */ + public static final int RECOVERY_STATUS_SYNC_IN_PROGRESS = 1; + /** Recovery account is not available. */ + public static final int RECOVERY_STATUS_MISSING_ACCOUNT = 2; + /** Key cannot be synced. */ + public static final int RECOVERY_STATUS_PERMANENT_FAILURE = 3; + private final ILockSettings mBinder; private RecoverableKeyStoreLoader(ILockSettings binder) { @@ -155,7 +171,7 @@ public class RecoverableKeyStoreLoader { * @return Data necessary to recover keystore. * @hide */ - public KeyStoreRecoveryData getRecoveryData(@NonNull byte[] account) + public @NonNull KeyStoreRecoveryData getRecoveryData(@NonNull byte[] account) throws RecoverableKeyStoreLoaderException { try { KeyStoreRecoveryData recoveryData = @@ -168,6 +184,50 @@ public class RecoverableKeyStoreLoader { } } + /** + * Sets a listener which notifies recovery agent that new recovery snapshot is available. {@link + * #getRecoveryData} can be used to get the snapshot. Note that every recovery agent can have at + * most one registered listener at any time. + * + * @param intent triggered when new snapshot is available. Unregisters listener if the value is + * {@code null}. + * @hide + */ + public void setSnapshotCreatedPendingIntent(@Nullable PendingIntent intent) + throws RecoverableKeyStoreLoaderException { + try { + mBinder.setSnapshotCreatedPendingIntent(intent, UserHandle.getCallingUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (ServiceSpecificException e) { + throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e); + } + } + + /** + * Returns a map from recovery agent accounts to corresponding KeyStore recovery snapshot + * version. Version zero is used, if no snapshots were created for the account. + * + * @return Map from recovery agent accounts to snapshot versions. + * @see KeyStoreRecoveryData.getSnapshotVersion + * @hide + */ + public @NonNull Map getRecoverySnapshotVersions() + throws RecoverableKeyStoreLoaderException { + try { + // IPC doesn't support generic Maps. + @SuppressWarnings("unchecked") + Map result = + (Map) + mBinder.getRecoverySnapshotVersions(UserHandle.getCallingUserId()); + return result; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (ServiceSpecificException e) { + throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e); + } + } + /** * Server parameters used to generate new recovery key blobs. This value will be included in * {@code KeyStoreRecoveryData.getEncryptedRecoveryKeyBlob()}. The same value must be included @@ -191,8 +251,8 @@ public class RecoverableKeyStoreLoader { /** * Updates recovery status for given keys. It is used to notify keystore that key was - * successfully stored on the server or there were an error. Returned as a part of KeyInfo data - * structure. + * successfully stored on the server or there were an error. Application can check this value + * using {@code getRecoveyStatus}. * * @param packageName Application whose recoverable keys' statuses are to be updated. * @param aliases List of application-specific key aliases. If the array is empty, updates the @@ -211,6 +271,39 @@ public class RecoverableKeyStoreLoader { } } + /** + * Returns a {@code Map} from Application's KeyStore key aliases to their recovery status. + * Negative status values are reserved for recovery agent specific codes. List of common codes: + * + *
    + *
  • {@link #RECOVERY_STATUS_SYNCED} + *
  • {@link #RECOVERY_STATUS_SYNC_IN_PROGRESS} + *
  • {@link #RECOVERY_STATUS_MISSING_ACCOUNT} + *
  • {@link #RECOVERY_STATUS_PERMANENT_FAILURE} + *
+ * + * @param packageName Application whose recoverable keys' statuses are to be retrieved. if + * {@code null} caller's package will be used. + * @return {@code Map} from KeyStore alias to recovery status. + * @see #setRecoveryStatus + * @hide + */ + public Map getRecoveryStatus(@Nullable String packageName) + throws RecoverableKeyStoreLoaderException { + try { + // IPC doesn't support generic Maps. + @SuppressWarnings("unchecked") + Map result = + (Map) + mBinder.getRecoveryStatus(packageName, UserHandle.getCallingUserId()); + return result; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (ServiceSpecificException e) { + throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e); + } + } + /** * Specifies a set of secret types used for end-to-end keystore encryption. Knowing all of them * is necessary to recover data. diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl index 164a74509ed77..43536a522a47b 100644 --- a/core/java/com/android/internal/widget/ILockSettings.aidl +++ b/core/java/com/android/internal/widget/ILockSettings.aidl @@ -16,6 +16,7 @@ package com.android.internal.widget; +import android.app.PendingIntent; import android.app.trust.IStrongAuthTracker; import android.os.Bundle; import android.security.recoverablekeystore.KeyEntryRecoveryData; @@ -24,6 +25,8 @@ import android.security.recoverablekeystore.KeyStoreRecoveryMetadata; import com.android.internal.widget.ICheckCredentialProgressCallback; import com.android.internal.widget.VerifyCredentialResponse; +import java.util.Map; + /** {@hide} */ interface ILockSettings { void setBoolean(in String key, in boolean value, in int userId); @@ -63,8 +66,11 @@ interface ILockSettings { void initRecoveryService(in String rootCertificateAlias, in byte[] signedPublicKeyList, int userId); KeyStoreRecoveryData getRecoveryData(in byte[] account, int userId); + void setSnapshotCreatedPendingIntent(in PendingIntent intent, int userId); + Map getRecoverySnapshotVersions(int userId); void setServerParameters(long serverParameters, int userId); void setRecoveryStatus(in String packageName, in String[] aliases, int status, int userId); + Map getRecoveryStatus(in String packageName, int userId); void setRecoverySecretTypes(in int[] secretTypes, int userId); int[] getRecoverySecretTypes(int userId); int[] getPendingRecoverySecretTypes(int userId); diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index aa55930697ba6..0481dab772eeb 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -1943,6 +1943,15 @@ public class LockSettingsService extends ILockSettings.Stub { return mRecoverableKeyStoreManager.getRecoveryData(account, userId); } + public void setSnapshotCreatedPendingIntent(@Nullable PendingIntent intent, int userId) + throws RemoteException { + mRecoverableKeyStoreManager.setSnapshotCreatedPendingIntent(intent, userId); + } + + public Map getRecoverySnapshotVersions(int userId) throws RemoteException { + return mRecoverableKeyStoreManager.getRecoverySnapshotVersions(userId); + } + @Override public void setServerParameters(long serverParameters, int userId) throws RemoteException { mRecoverableKeyStoreManager.setServerParameters(serverParameters, userId); @@ -1954,6 +1963,10 @@ public class LockSettingsService extends ILockSettings.Stub { mRecoverableKeyStoreManager.setRecoveryStatus(packageName, aliases, status, userId); } + public Map getRecoveryStatus(@Nullable String packageName, int userId) throws RemoteException { + return mRecoverableKeyStoreManager.getRecoveryStatus(packageName, userId); + } + @Override public void setRecoverySecretTypes(@NonNull @KeyStoreRecoveryMetadata.UserSecretType int[] secretTypes, int userId) throws RemoteException { 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 e459f288b4c0e..f87f7fa1e885a 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java @@ -18,6 +18,7 @@ package com.android.server.locksettings.recoverablekeystore; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.PendingIntent; import android.content.Context; import android.os.Binder; import android.os.RemoteException; @@ -33,6 +34,7 @@ import com.android.internal.annotations.VisibleForTesting; import java.util.ArrayList; import java.util.List; +import java.util.Map; /** * Class with {@link RecoverableKeyStoreLoader} API implementation and internal methods to interact @@ -77,7 +79,7 @@ public class RecoverableKeyStoreManager { * @return recovery data * @hide */ - public KeyStoreRecoveryData getRecoveryData(@NonNull byte[] account, int userId) + public @NonNull KeyStoreRecoveryData getRecoveryData(@NonNull byte[] account, int userId) throws RemoteException { checkRecoverKeyStorePermission(); final int callingUid = Binder.getCallingUid(); // Recovery agent uid. @@ -100,6 +102,24 @@ public class RecoverableKeyStoreManager { RecoverableKeyStoreLoader.UNINITIALIZED_RECOVERY_PUBLIC_KEY); } + public void setSnapshotCreatedPendingIntent(@Nullable PendingIntent intent, int userId) + throws RemoteException { + checkRecoverKeyStorePermission(); + throw new UnsupportedOperationException(); + } + + /** + * Gets recovery snapshot versions for all accounts. Note that snapshot may have 0 application + * keys, but it still needs to be synced, if previous versions were not empty. + * + * @return Map from Recovery agent account to snapshot version. + */ + public @NonNull Map getRecoverySnapshotVersions(int userId) + throws RemoteException { + checkRecoverKeyStorePermission(); + throw new UnsupportedOperationException(); + } + public void setServerParameters(long serverParameters, int userId) throws RemoteException { checkRecoverKeyStorePermission(); throw new UnsupportedOperationException(); @@ -112,6 +132,21 @@ public class RecoverableKeyStoreManager { throw new UnsupportedOperationException(); } + /** + * Gets recovery status for keys {@code packageName}. + * + * @param packageName which recoverable keys statuses will be returned + * @return Map from KeyStore alias to recovery status + */ + public @NonNull Map getRecoveryStatus(@Nullable String packageName, int userId) + throws RemoteException { + // Any application should be able to check status for its own keys. + // If caller is a recovery agent it can check statuses for other packages, but + // only for recoverable keys it manages. + checkRecoverKeyStorePermission(); + throw new UnsupportedOperationException(); + } + /** * Sets recovery secrets list used by all recovery agents for given {@code userId} * @@ -130,7 +165,7 @@ public class RecoverableKeyStoreManager { * @return secret types * @hide */ - public int[] getRecoverySecretTypes(int userId) throws RemoteException { + public @NonNull int[] getRecoverySecretTypes(int userId) throws RemoteException { checkRecoverKeyStorePermission(); throw new UnsupportedOperationException(); } @@ -141,7 +176,7 @@ public class RecoverableKeyStoreManager { * @return secret types * @hide */ - public int[] getPendingRecoverySecretTypes(int userId) throws RemoteException { + public @NonNull int[] getPendingRecoverySecretTypes(int userId) throws RemoteException { checkRecoverKeyStorePermission(); throw new UnsupportedOperationException(); } @@ -164,7 +199,7 @@ public class RecoverableKeyStoreManager { * @return recovery claim * @hide */ - public byte[] startRecoverySession( + public @NonNull byte[] startRecoverySession( @NonNull String sessionId, @NonNull byte[] verifierPublicKey, @NonNull byte[] vaultParams,