Loading core/java/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java +18 −21 Original line number Diff line number Diff line Loading @@ -23,7 +23,6 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.os.RemoteException; import android.os.ServiceManager; import android.os.ServiceSpecificException; import android.os.UserHandle; import android.security.KeyStore; import android.util.AndroidException; Loading @@ -44,8 +43,10 @@ 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; public static final int NO_SNAPSHOT_PENDING_ERROR = 21; public static final int ERROR_UNINITIALIZED_RECOVERY_PUBLIC_KEY = 20; public static final int ERROR_NO_SNAPSHOT_PENDING = 21; public static final int ERROR_KEYSTORE_INTERNAL_ERROR = 22; public static final int ERROR_INSECURE_USER = 24; /** * Rate limit is enforced to prevent using too many trusted remote devices, since each device Loading Loading @@ -124,7 +125,7 @@ public class RecoverableKeyStoreLoader { return "OK"; case SYSTEM_ERROR: return "System error"; case UNINITIALIZED_RECOVERY_PUBLIC_KEY: case ERROR_UNINITIALIZED_RECOVERY_PUBLIC_KEY: return "Recovery service is not initialized"; case RATE_LIMIT_EXCEEDED: return "Rate limit exceeded"; Loading Loading @@ -156,8 +157,7 @@ public class RecoverableKeyStoreLoader { @NonNull String rootCertificateAlias, @NonNull byte[] signedPublicKeyList) throws RecoverableKeyStoreLoaderException { try { mBinder.initRecoveryService( rootCertificateAlias, signedPublicKeyList, UserHandle.getCallingUserId()); mBinder.initRecoveryService(rootCertificateAlias, signedPublicKeyList); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } catch (ServiceSpecificException e) { Loading @@ -176,8 +176,7 @@ public class RecoverableKeyStoreLoader { public @NonNull KeyStoreRecoveryData getRecoveryData(@NonNull byte[] account) throws RecoverableKeyStoreLoaderException { try { KeyStoreRecoveryData recoveryData = mBinder.getRecoveryData(account, UserHandle.getCallingUserId()); KeyStoreRecoveryData recoveryData = mBinder.getRecoveryData(account); return recoveryData; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); Loading @@ -198,7 +197,7 @@ public class RecoverableKeyStoreLoader { public void setSnapshotCreatedPendingIntent(@Nullable PendingIntent intent) throws RecoverableKeyStoreLoaderException { try { mBinder.setSnapshotCreatedPendingIntent(intent, UserHandle.getCallingUserId()); mBinder.setSnapshotCreatedPendingIntent(intent); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } catch (ServiceSpecificException e) { Loading @@ -220,8 +219,7 @@ public class RecoverableKeyStoreLoader { // IPC doesn't support generic Maps. @SuppressWarnings("unchecked") Map<byte[], Integer> result = (Map<byte[], Integer>) mBinder.getRecoverySnapshotVersions(UserHandle.getCallingUserId()); (Map<byte[], Integer>) mBinder.getRecoverySnapshotVersions(); return result; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); Loading @@ -243,7 +241,7 @@ public class RecoverableKeyStoreLoader { public void setServerParameters(long serverParameters) throws RecoverableKeyStoreLoaderException { try { mBinder.setServerParameters(serverParameters, UserHandle.getCallingUserId()); mBinder.setServerParameters(serverParameters); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } catch (ServiceSpecificException e) { Loading @@ -265,7 +263,7 @@ public class RecoverableKeyStoreLoader { @NonNull String packageName, @Nullable String[] aliases, int status) throws NameNotFoundException, RecoverableKeyStoreLoaderException { try { mBinder.setRecoveryStatus(packageName, aliases, status, UserHandle.getCallingUserId()); mBinder.setRecoveryStatus(packageName, aliases, status); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } catch (ServiceSpecificException e) { Loading Loading @@ -297,7 +295,7 @@ public class RecoverableKeyStoreLoader { @SuppressWarnings("unchecked") Map<String, Integer> result = (Map<String, Integer>) mBinder.getRecoveryStatus(packageName, UserHandle.getCallingUserId()); mBinder.getRecoveryStatus(packageName); return result; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); Loading @@ -317,7 +315,7 @@ public class RecoverableKeyStoreLoader { @NonNull @KeyStoreRecoveryMetadata.UserSecretType int[] secretTypes) throws RecoverableKeyStoreLoaderException { try { mBinder.setRecoverySecretTypes(secretTypes, UserHandle.getCallingUserId()); mBinder.setRecoverySecretTypes(secretTypes); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } catch (ServiceSpecificException e) { Loading @@ -335,7 +333,7 @@ public class RecoverableKeyStoreLoader { public @NonNull @KeyStoreRecoveryMetadata.UserSecretType int[] getRecoverySecretTypes() throws RecoverableKeyStoreLoaderException { try { return mBinder.getRecoverySecretTypes(UserHandle.getCallingUserId()); return mBinder.getRecoverySecretTypes(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } catch (ServiceSpecificException e) { Loading @@ -353,7 +351,7 @@ public class RecoverableKeyStoreLoader { public @NonNull @KeyStoreRecoveryMetadata.UserSecretType int[] getPendingRecoverySecretTypes() throws RecoverableKeyStoreLoaderException { try { return mBinder.getPendingRecoverySecretTypes(UserHandle.getCallingUserId()); return mBinder.getPendingRecoverySecretTypes(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } catch (ServiceSpecificException e) { Loading @@ -373,7 +371,7 @@ public class RecoverableKeyStoreLoader { public void recoverySecretAvailable(@NonNull KeyStoreRecoveryMetadata recoverySecret) throws RecoverableKeyStoreLoaderException { try { mBinder.recoverySecretAvailable(recoverySecret, UserHandle.getCallingUserId()); mBinder.recoverySecretAvailable(recoverySecret); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } catch (ServiceSpecificException e) { Loading Loading @@ -412,8 +410,7 @@ public class RecoverableKeyStoreLoader { verifierPublicKey, vaultParams, vaultChallenge, secrets, UserHandle.getCallingUserId()); secrets); return recoveryClaim; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); Loading @@ -440,7 +437,7 @@ public class RecoverableKeyStoreLoader { throws RecoverableKeyStoreLoaderException { try { return (Map<String, byte[]>) mBinder.recoverKeys( sessionId, recoveryKeyBlob, applicationKeys, UserHandle.getCallingUserId()); sessionId, recoveryKeyBlob, applicationKeys); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } catch (ServiceSpecificException e) { Loading core/java/com/android/internal/widget/ILockSettings.aidl +13 −14 Original line number Diff line number Diff line Loading @@ -63,22 +63,21 @@ interface ILockSettings { // RecoverableKeyStoreLoader methods. // {@code ServiceSpecificException} may be thrown to signal an error, which caller can // convert to {@code RecoverableKeyStoreLoader}. void initRecoveryService(in String rootCertificateAlias, in byte[] signedPublicKeyList, int userId); KeyStoreRecoveryData getRecoveryData(in byte[] account, int userId); void initRecoveryService(in String rootCertificateAlias, in byte[] signedPublicKeyList); KeyStoreRecoveryData getRecoveryData(in byte[] account); byte[] generateAndStoreKey(String alias); 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); void recoverySecretAvailable(in KeyStoreRecoveryMetadata recoverySecret, int userId); void setSnapshotCreatedPendingIntent(in PendingIntent intent); Map getRecoverySnapshotVersions(); void setServerParameters(long serverParameters); void setRecoveryStatus(in String packageName, in String[] aliases, int status); Map getRecoveryStatus(in String packageName); void setRecoverySecretTypes(in int[] secretTypes); int[] getRecoverySecretTypes(); int[] getPendingRecoverySecretTypes(); void recoverySecretAvailable(in KeyStoreRecoveryMetadata recoverySecret); byte[] startRecoverySession(in String sessionId, in byte[] verifierPublicKey, in byte[] vaultParams, in byte[] vaultChallenge, in List<KeyStoreRecoveryMetadata> secrets, int userId); in List<KeyStoreRecoveryMetadata> secrets); Map/*<String, byte[]>*/ recoverKeys(in String sessionId, in byte[] recoveryKeyBlob, in List<KeyEntryRecoveryData> applicationKeys, int userId); in List<KeyEntryRecoveryData> applicationKeys); } services/core/java/com/android/server/locksettings/LockSettingsService.java +27 −31 Original line number Diff line number Diff line Loading @@ -1956,82 +1956,78 @@ public class LockSettingsService extends ILockSettings.Stub { @Override public void initRecoveryService(@NonNull String rootCertificateAlias, @NonNull byte[] signedPublicKeyList, @UserIdInt int userId) throws RemoteException { @NonNull byte[] signedPublicKeyList) throws RemoteException { mRecoverableKeyStoreManager.initRecoveryService(rootCertificateAlias, signedPublicKeyList, userId); signedPublicKeyList); } @Override public KeyStoreRecoveryData getRecoveryData(@NonNull byte[] account, @UserIdInt int userId) throws RemoteException { return mRecoverableKeyStoreManager.getRecoveryData(account, userId); public KeyStoreRecoveryData getRecoveryData(@NonNull byte[] account) throws RemoteException { return mRecoverableKeyStoreManager.getRecoveryData(account); } public void setSnapshotCreatedPendingIntent(@Nullable PendingIntent intent, int userId) public void setSnapshotCreatedPendingIntent(@Nullable PendingIntent intent) throws RemoteException { mRecoverableKeyStoreManager.setSnapshotCreatedPendingIntent(intent, userId); mRecoverableKeyStoreManager.setSnapshotCreatedPendingIntent(intent); } public Map getRecoverySnapshotVersions(int userId) throws RemoteException { return mRecoverableKeyStoreManager.getRecoverySnapshotVersions(userId); public Map getRecoverySnapshotVersions() throws RemoteException { return mRecoverableKeyStoreManager.getRecoverySnapshotVersions(); } @Override public void setServerParameters(long serverParameters, @UserIdInt int userId) throws RemoteException { mRecoverableKeyStoreManager.setServerParameters(serverParameters, userId); public void setServerParameters(long serverParameters) throws RemoteException { mRecoverableKeyStoreManager.setServerParameters(serverParameters); } @Override public void setRecoveryStatus(@NonNull String packageName, @Nullable String[] aliases, int status, @UserIdInt int userId) throws RemoteException { mRecoverableKeyStoreManager.setRecoveryStatus(packageName, aliases, status, userId); int status) throws RemoteException { mRecoverableKeyStoreManager.setRecoveryStatus(packageName, aliases, status); } public Map getRecoveryStatus(@Nullable String packageName, int userId) throws RemoteException { return mRecoverableKeyStoreManager.getRecoveryStatus(packageName, userId); public Map getRecoveryStatus(@Nullable String packageName) throws RemoteException { return mRecoverableKeyStoreManager.getRecoveryStatus(packageName); } @Override public void setRecoverySecretTypes(@NonNull @KeyStoreRecoveryMetadata.UserSecretType int[] secretTypes, @UserIdInt int userId) throws RemoteException { mRecoverableKeyStoreManager.setRecoverySecretTypes(secretTypes, userId); int[] secretTypes) throws RemoteException { mRecoverableKeyStoreManager.setRecoverySecretTypes(secretTypes); } @Override public int[] getRecoverySecretTypes(@UserIdInt int userId) throws RemoteException { return mRecoverableKeyStoreManager.getRecoverySecretTypes(userId); public int[] getRecoverySecretTypes() throws RemoteException { return mRecoverableKeyStoreManager.getRecoverySecretTypes(); } @Override public int[] getPendingRecoverySecretTypes(@UserIdInt int userId) throws RemoteException { public int[] getPendingRecoverySecretTypes() throws RemoteException { throw new SecurityException("Not implemented"); } @Override public void recoverySecretAvailable(@NonNull KeyStoreRecoveryMetadata recoverySecret, @UserIdInt int userId) throws RemoteException { mRecoverableKeyStoreManager.recoverySecretAvailable(recoverySecret, userId); public void recoverySecretAvailable(@NonNull KeyStoreRecoveryMetadata recoverySecret) throws RemoteException { mRecoverableKeyStoreManager.recoverySecretAvailable(recoverySecret); } @Override public byte[] startRecoverySession(@NonNull String sessionId, @NonNull byte[] verifierPublicKey, @NonNull byte[] vaultParams, @NonNull byte[] vaultChallenge, @NonNull List<KeyStoreRecoveryMetadata> secrets, @UserIdInt int userId) throws RemoteException { @NonNull byte[] vaultChallenge, @NonNull List<KeyStoreRecoveryMetadata> secrets) throws RemoteException { return mRecoverableKeyStoreManager.startRecoverySession(sessionId, verifierPublicKey, vaultParams, vaultChallenge, secrets, userId); vaultParams, vaultChallenge, secrets); } @Override public Map<String, byte[]> recoverKeys(@NonNull String sessionId, @NonNull byte[] recoveryKeyBlob, @NonNull List<KeyEntryRecoveryData> applicationKeys, @UserIdInt int userId) @NonNull byte[] recoveryKeyBlob, @NonNull List<KeyEntryRecoveryData> applicationKeys) throws RemoteException { return mRecoverableKeyStoreManager.recoverKeys( sessionId, recoveryKeyBlob, applicationKeys, userId); sessionId, recoveryKeyBlob, applicationKeys); } @Override Loading services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java +37 −40 Original line number Diff line number Diff line Loading @@ -63,9 +63,11 @@ import javax.crypto.AEADBadTagException; public class RecoverableKeyStoreManager { private static final String TAG = "RecoverableKeyStoreMgr"; private static final int ERROR_INSECURE_USER = 1; private static final int ERROR_KEYSTORE_INTERNAL_ERROR = 2; private static final int ERROR_DATABASE_ERROR = 3; // TODO: move error codes to RecoverableKeyStoreLoader. private static int ERROR_INSECURE_USER = 1; private static int ERROR_KEYSTORE_INTERNAL_ERROR = 2; private static int ERROR_DATABASE_ERROR = 3; private static int ERROR_RECOVERY_SESSION_NOT_FOUND = 4; private static RecoverableKeyStoreManager mInstance; Loading Loading @@ -119,9 +121,10 @@ public class RecoverableKeyStoreManager { } public void initRecoveryService( @NonNull String rootCertificateAlias, @NonNull byte[] signedPublicKeyList, int userId) @NonNull String rootCertificateAlias, @NonNull byte[] signedPublicKeyList) throws RemoteException { checkRecoverKeyStorePermission(); int userId = UserHandle.getCallingUserId(); // TODO: open /system/etc/security/... cert file, and check the signature on the public keys PublicKey publicKey; try { Loading @@ -144,22 +147,22 @@ public class RecoverableKeyStoreManager { * @return recovery data * @hide */ public @NonNull KeyStoreRecoveryData getRecoveryData(@NonNull byte[] account, int userId) public @NonNull KeyStoreRecoveryData getRecoveryData(@NonNull byte[] account) throws RemoteException { checkRecoverKeyStorePermission(); KeyStoreRecoveryData snapshot = mSnapshotStorage.get(UserHandle.getCallingUserId()); if (snapshot == null) { throw new ServiceSpecificException(RecoverableKeyStoreLoader.NO_SNAPSHOT_PENDING_ERROR); throw new ServiceSpecificException(RecoverableKeyStoreLoader.ERROR_NO_SNAPSHOT_PENDING); } return snapshot; } public void setSnapshotCreatedPendingIntent(@Nullable PendingIntent intent, int userId) public void setSnapshotCreatedPendingIntent(@Nullable PendingIntent intent) throws RemoteException { checkRecoverKeyStorePermission(); final int recoveryAgentUid = Binder.getCallingUid(); mListenersStorage.setSnapshotListener(recoveryAgentUid, intent); int uid = Binder.getCallingUid(); mListenersStorage.setSnapshotListener(uid, intent); } /** Loading @@ -168,14 +171,15 @@ public class RecoverableKeyStoreManager { * * @return Map from Recovery agent account to snapshot version. */ public @NonNull Map<byte[], Integer> getRecoverySnapshotVersions(int userId) public @NonNull Map<byte[], Integer> getRecoverySnapshotVersions() throws RemoteException { checkRecoverKeyStorePermission(); throw new UnsupportedOperationException(); } public void setServerParameters(long serverParameters, int userId) throws RemoteException { public void setServerParameters(long serverParameters) throws RemoteException { checkRecoverKeyStorePermission(); int userId = UserHandle.getCallingUserId(); mDatabase.setServerParameters(userId, Binder.getCallingUid(), serverParameters); } Loading @@ -187,7 +191,7 @@ public class RecoverableKeyStoreManager { * @param status - new status */ public void setRecoveryStatus( @NonNull String packageName, @Nullable String[] aliases, int status, int userId) @NonNull String packageName, @Nullable String[] aliases, int status) throws RemoteException { checkRecoverKeyStorePermission(); int uid = Binder.getCallingUid(); Loading @@ -211,7 +215,7 @@ public class RecoverableKeyStoreManager { * * @return {@code Map} from KeyStore alias to recovery status. */ public @NonNull Map<String, Integer> getRecoveryStatus(@Nullable String packageName, int userId) public @NonNull Map<String, Integer> getRecoveryStatus(@Nullable String packageName) 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 Loading @@ -225,7 +229,7 @@ public class RecoverableKeyStoreManager { * @hide */ public void setRecoverySecretTypes( @NonNull @KeyStoreRecoveryMetadata.UserSecretType int[] secretTypes, int userId) @NonNull @KeyStoreRecoveryMetadata.UserSecretType int[] secretTypes) throws RemoteException { checkRecoverKeyStorePermission(); mDatabase.setRecoverySecretTypes(UserHandle.getCallingUserId(), Binder.getCallingUid(), Loading @@ -238,7 +242,7 @@ public class RecoverableKeyStoreManager { * @return secret types * @hide */ public @NonNull int[] getRecoverySecretTypes(int userId) throws RemoteException { public @NonNull int[] getRecoverySecretTypes() throws RemoteException { checkRecoverKeyStorePermission(); return mDatabase.getRecoverySecretTypes(UserHandle.getCallingUserId(), Binder.getCallingUid()); Loading @@ -250,17 +254,17 @@ public class RecoverableKeyStoreManager { * @return secret types * @hide */ public @NonNull int[] getPendingRecoverySecretTypes(int userId) throws RemoteException { public @NonNull int[] getPendingRecoverySecretTypes() throws RemoteException { checkRecoverKeyStorePermission(); throw new UnsupportedOperationException(); } public void recoverySecretAvailable( @NonNull KeyStoreRecoveryMetadata recoverySecret, int userId) throws RemoteException { final int callingUid = Binder.getCallingUid(); // Recovery agent uid. @NonNull KeyStoreRecoveryMetadata recoverySecret) throws RemoteException { int uid = Binder.getCallingUid(); if (recoverySecret.getLockScreenUiFormat() == KeyStoreRecoveryMetadata.TYPE_LOCKSCREEN) { throw new SecurityException( "Caller " + callingUid + "is not allowed to set lock screen secret"); "Caller " + uid + " is not allowed to set lock screen secret"); } checkRecoverKeyStorePermission(); // TODO: add hook from LockSettingsService to set lock screen secret. Loading @@ -284,10 +288,10 @@ public class RecoverableKeyStoreManager { @NonNull byte[] verifierPublicKey, @NonNull byte[] vaultParams, @NonNull byte[] vaultChallenge, @NonNull List<KeyStoreRecoveryMetadata> secrets, int userId) @NonNull List<KeyStoreRecoveryMetadata> secrets) throws RemoteException { checkRecoverKeyStorePermission(); int uid = Binder.getCallingUid(); if (secrets.size() != 1) { // TODO: support multiple secrets Loading @@ -297,7 +301,7 @@ public class RecoverableKeyStoreManager { byte[] keyClaimant = KeySyncUtils.generateKeyClaimant(); byte[] kfHash = secrets.get(0).getSecret(); mRecoverySessionStorage.add( userId, uid, new RecoverySessionStorage.Entry(sessionId, kfHash, keyClaimant, vaultParams)); try { Loading Loading @@ -335,22 +339,21 @@ public class RecoverableKeyStoreManager { * 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. * @return Map from alias to raw key material. * @throws RemoteException if an error occurred recovering the keys. */ public Map<String, byte[]> recoverKeys( @NonNull String sessionId, @NonNull byte[] encryptedRecoveryKey, @NonNull List<KeyEntryRecoveryData> applicationKeys, int uid) @NonNull List<KeyEntryRecoveryData> applicationKeys) throws RemoteException { checkRecoverKeyStorePermission(); int uid = Binder.getCallingUid(); 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)); throw new ServiceSpecificException(ERROR_KEYSTORE_INTERNAL_ERROR, String.format(Locale.US, "Application uid=%d does not have pending session '%s'", uid, sessionId)); } try { Loading @@ -372,7 +375,7 @@ public class RecoverableKeyStoreManager { */ public byte[] generateAndStoreKey(@NonNull String alias) throws RemoteException { int uid = Binder.getCallingUid(); int userId = Binder.getCallingUserHandle().getIdentifier(); int userId = UserHandle.getCallingUserId(); PlatformEncryptionKey encryptionKey; Loading Loading @@ -400,7 +403,7 @@ public class RecoverableKeyStoreManager { private byte[] decryptRecoveryKey( RecoverySessionStorage.Entry sessionEntry, byte[] encryptedClaimResponse) throws RemoteException { throws RemoteException, ServiceSpecificException { try { byte[] locallyEncryptedKey = KeySyncUtils.decryptRecoveryClaimResponse( sessionEntry.getKeyClaimant(), Loading @@ -408,18 +411,12 @@ public class RecoverableKeyStoreManager { encryptedClaimResponse); return KeySyncUtils.decryptRecoveryKey(sessionEntry.getLskfHash(), locallyEncryptedKey); } catch (InvalidKeyException | AEADBadTagException e) { throw new RemoteException( "Failed to decrypt recovery key", e, /*enableSuppression=*/ true, /*writeableStackTrace=*/ true); throw new ServiceSpecificException(ERROR_KEYSTORE_INTERNAL_ERROR, "Failed to decrypt recovery key " + e.getMessage()); } 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); throw new ServiceSpecificException(ERROR_KEYSTORE_INTERNAL_ERROR, e.getMessage()); } } Loading services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java +37 −49 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
core/java/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java +18 −21 Original line number Diff line number Diff line Loading @@ -23,7 +23,6 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.os.RemoteException; import android.os.ServiceManager; import android.os.ServiceSpecificException; import android.os.UserHandle; import android.security.KeyStore; import android.util.AndroidException; Loading @@ -44,8 +43,10 @@ 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; public static final int NO_SNAPSHOT_PENDING_ERROR = 21; public static final int ERROR_UNINITIALIZED_RECOVERY_PUBLIC_KEY = 20; public static final int ERROR_NO_SNAPSHOT_PENDING = 21; public static final int ERROR_KEYSTORE_INTERNAL_ERROR = 22; public static final int ERROR_INSECURE_USER = 24; /** * Rate limit is enforced to prevent using too many trusted remote devices, since each device Loading Loading @@ -124,7 +125,7 @@ public class RecoverableKeyStoreLoader { return "OK"; case SYSTEM_ERROR: return "System error"; case UNINITIALIZED_RECOVERY_PUBLIC_KEY: case ERROR_UNINITIALIZED_RECOVERY_PUBLIC_KEY: return "Recovery service is not initialized"; case RATE_LIMIT_EXCEEDED: return "Rate limit exceeded"; Loading Loading @@ -156,8 +157,7 @@ public class RecoverableKeyStoreLoader { @NonNull String rootCertificateAlias, @NonNull byte[] signedPublicKeyList) throws RecoverableKeyStoreLoaderException { try { mBinder.initRecoveryService( rootCertificateAlias, signedPublicKeyList, UserHandle.getCallingUserId()); mBinder.initRecoveryService(rootCertificateAlias, signedPublicKeyList); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } catch (ServiceSpecificException e) { Loading @@ -176,8 +176,7 @@ public class RecoverableKeyStoreLoader { public @NonNull KeyStoreRecoveryData getRecoveryData(@NonNull byte[] account) throws RecoverableKeyStoreLoaderException { try { KeyStoreRecoveryData recoveryData = mBinder.getRecoveryData(account, UserHandle.getCallingUserId()); KeyStoreRecoveryData recoveryData = mBinder.getRecoveryData(account); return recoveryData; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); Loading @@ -198,7 +197,7 @@ public class RecoverableKeyStoreLoader { public void setSnapshotCreatedPendingIntent(@Nullable PendingIntent intent) throws RecoverableKeyStoreLoaderException { try { mBinder.setSnapshotCreatedPendingIntent(intent, UserHandle.getCallingUserId()); mBinder.setSnapshotCreatedPendingIntent(intent); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } catch (ServiceSpecificException e) { Loading @@ -220,8 +219,7 @@ public class RecoverableKeyStoreLoader { // IPC doesn't support generic Maps. @SuppressWarnings("unchecked") Map<byte[], Integer> result = (Map<byte[], Integer>) mBinder.getRecoverySnapshotVersions(UserHandle.getCallingUserId()); (Map<byte[], Integer>) mBinder.getRecoverySnapshotVersions(); return result; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); Loading @@ -243,7 +241,7 @@ public class RecoverableKeyStoreLoader { public void setServerParameters(long serverParameters) throws RecoverableKeyStoreLoaderException { try { mBinder.setServerParameters(serverParameters, UserHandle.getCallingUserId()); mBinder.setServerParameters(serverParameters); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } catch (ServiceSpecificException e) { Loading @@ -265,7 +263,7 @@ public class RecoverableKeyStoreLoader { @NonNull String packageName, @Nullable String[] aliases, int status) throws NameNotFoundException, RecoverableKeyStoreLoaderException { try { mBinder.setRecoveryStatus(packageName, aliases, status, UserHandle.getCallingUserId()); mBinder.setRecoveryStatus(packageName, aliases, status); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } catch (ServiceSpecificException e) { Loading Loading @@ -297,7 +295,7 @@ public class RecoverableKeyStoreLoader { @SuppressWarnings("unchecked") Map<String, Integer> result = (Map<String, Integer>) mBinder.getRecoveryStatus(packageName, UserHandle.getCallingUserId()); mBinder.getRecoveryStatus(packageName); return result; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); Loading @@ -317,7 +315,7 @@ public class RecoverableKeyStoreLoader { @NonNull @KeyStoreRecoveryMetadata.UserSecretType int[] secretTypes) throws RecoverableKeyStoreLoaderException { try { mBinder.setRecoverySecretTypes(secretTypes, UserHandle.getCallingUserId()); mBinder.setRecoverySecretTypes(secretTypes); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } catch (ServiceSpecificException e) { Loading @@ -335,7 +333,7 @@ public class RecoverableKeyStoreLoader { public @NonNull @KeyStoreRecoveryMetadata.UserSecretType int[] getRecoverySecretTypes() throws RecoverableKeyStoreLoaderException { try { return mBinder.getRecoverySecretTypes(UserHandle.getCallingUserId()); return mBinder.getRecoverySecretTypes(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } catch (ServiceSpecificException e) { Loading @@ -353,7 +351,7 @@ public class RecoverableKeyStoreLoader { public @NonNull @KeyStoreRecoveryMetadata.UserSecretType int[] getPendingRecoverySecretTypes() throws RecoverableKeyStoreLoaderException { try { return mBinder.getPendingRecoverySecretTypes(UserHandle.getCallingUserId()); return mBinder.getPendingRecoverySecretTypes(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } catch (ServiceSpecificException e) { Loading @@ -373,7 +371,7 @@ public class RecoverableKeyStoreLoader { public void recoverySecretAvailable(@NonNull KeyStoreRecoveryMetadata recoverySecret) throws RecoverableKeyStoreLoaderException { try { mBinder.recoverySecretAvailable(recoverySecret, UserHandle.getCallingUserId()); mBinder.recoverySecretAvailable(recoverySecret); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } catch (ServiceSpecificException e) { Loading Loading @@ -412,8 +410,7 @@ public class RecoverableKeyStoreLoader { verifierPublicKey, vaultParams, vaultChallenge, secrets, UserHandle.getCallingUserId()); secrets); return recoveryClaim; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); Loading @@ -440,7 +437,7 @@ public class RecoverableKeyStoreLoader { throws RecoverableKeyStoreLoaderException { try { return (Map<String, byte[]>) mBinder.recoverKeys( sessionId, recoveryKeyBlob, applicationKeys, UserHandle.getCallingUserId()); sessionId, recoveryKeyBlob, applicationKeys); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } catch (ServiceSpecificException e) { Loading
core/java/com/android/internal/widget/ILockSettings.aidl +13 −14 Original line number Diff line number Diff line Loading @@ -63,22 +63,21 @@ interface ILockSettings { // RecoverableKeyStoreLoader methods. // {@code ServiceSpecificException} may be thrown to signal an error, which caller can // convert to {@code RecoverableKeyStoreLoader}. void initRecoveryService(in String rootCertificateAlias, in byte[] signedPublicKeyList, int userId); KeyStoreRecoveryData getRecoveryData(in byte[] account, int userId); void initRecoveryService(in String rootCertificateAlias, in byte[] signedPublicKeyList); KeyStoreRecoveryData getRecoveryData(in byte[] account); byte[] generateAndStoreKey(String alias); 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); void recoverySecretAvailable(in KeyStoreRecoveryMetadata recoverySecret, int userId); void setSnapshotCreatedPendingIntent(in PendingIntent intent); Map getRecoverySnapshotVersions(); void setServerParameters(long serverParameters); void setRecoveryStatus(in String packageName, in String[] aliases, int status); Map getRecoveryStatus(in String packageName); void setRecoverySecretTypes(in int[] secretTypes); int[] getRecoverySecretTypes(); int[] getPendingRecoverySecretTypes(); void recoverySecretAvailable(in KeyStoreRecoveryMetadata recoverySecret); byte[] startRecoverySession(in String sessionId, in byte[] verifierPublicKey, in byte[] vaultParams, in byte[] vaultChallenge, in List<KeyStoreRecoveryMetadata> secrets, int userId); in List<KeyStoreRecoveryMetadata> secrets); Map/*<String, byte[]>*/ recoverKeys(in String sessionId, in byte[] recoveryKeyBlob, in List<KeyEntryRecoveryData> applicationKeys, int userId); in List<KeyEntryRecoveryData> applicationKeys); }
services/core/java/com/android/server/locksettings/LockSettingsService.java +27 −31 Original line number Diff line number Diff line Loading @@ -1956,82 +1956,78 @@ public class LockSettingsService extends ILockSettings.Stub { @Override public void initRecoveryService(@NonNull String rootCertificateAlias, @NonNull byte[] signedPublicKeyList, @UserIdInt int userId) throws RemoteException { @NonNull byte[] signedPublicKeyList) throws RemoteException { mRecoverableKeyStoreManager.initRecoveryService(rootCertificateAlias, signedPublicKeyList, userId); signedPublicKeyList); } @Override public KeyStoreRecoveryData getRecoveryData(@NonNull byte[] account, @UserIdInt int userId) throws RemoteException { return mRecoverableKeyStoreManager.getRecoveryData(account, userId); public KeyStoreRecoveryData getRecoveryData(@NonNull byte[] account) throws RemoteException { return mRecoverableKeyStoreManager.getRecoveryData(account); } public void setSnapshotCreatedPendingIntent(@Nullable PendingIntent intent, int userId) public void setSnapshotCreatedPendingIntent(@Nullable PendingIntent intent) throws RemoteException { mRecoverableKeyStoreManager.setSnapshotCreatedPendingIntent(intent, userId); mRecoverableKeyStoreManager.setSnapshotCreatedPendingIntent(intent); } public Map getRecoverySnapshotVersions(int userId) throws RemoteException { return mRecoverableKeyStoreManager.getRecoverySnapshotVersions(userId); public Map getRecoverySnapshotVersions() throws RemoteException { return mRecoverableKeyStoreManager.getRecoverySnapshotVersions(); } @Override public void setServerParameters(long serverParameters, @UserIdInt int userId) throws RemoteException { mRecoverableKeyStoreManager.setServerParameters(serverParameters, userId); public void setServerParameters(long serverParameters) throws RemoteException { mRecoverableKeyStoreManager.setServerParameters(serverParameters); } @Override public void setRecoveryStatus(@NonNull String packageName, @Nullable String[] aliases, int status, @UserIdInt int userId) throws RemoteException { mRecoverableKeyStoreManager.setRecoveryStatus(packageName, aliases, status, userId); int status) throws RemoteException { mRecoverableKeyStoreManager.setRecoveryStatus(packageName, aliases, status); } public Map getRecoveryStatus(@Nullable String packageName, int userId) throws RemoteException { return mRecoverableKeyStoreManager.getRecoveryStatus(packageName, userId); public Map getRecoveryStatus(@Nullable String packageName) throws RemoteException { return mRecoverableKeyStoreManager.getRecoveryStatus(packageName); } @Override public void setRecoverySecretTypes(@NonNull @KeyStoreRecoveryMetadata.UserSecretType int[] secretTypes, @UserIdInt int userId) throws RemoteException { mRecoverableKeyStoreManager.setRecoverySecretTypes(secretTypes, userId); int[] secretTypes) throws RemoteException { mRecoverableKeyStoreManager.setRecoverySecretTypes(secretTypes); } @Override public int[] getRecoverySecretTypes(@UserIdInt int userId) throws RemoteException { return mRecoverableKeyStoreManager.getRecoverySecretTypes(userId); public int[] getRecoverySecretTypes() throws RemoteException { return mRecoverableKeyStoreManager.getRecoverySecretTypes(); } @Override public int[] getPendingRecoverySecretTypes(@UserIdInt int userId) throws RemoteException { public int[] getPendingRecoverySecretTypes() throws RemoteException { throw new SecurityException("Not implemented"); } @Override public void recoverySecretAvailable(@NonNull KeyStoreRecoveryMetadata recoverySecret, @UserIdInt int userId) throws RemoteException { mRecoverableKeyStoreManager.recoverySecretAvailable(recoverySecret, userId); public void recoverySecretAvailable(@NonNull KeyStoreRecoveryMetadata recoverySecret) throws RemoteException { mRecoverableKeyStoreManager.recoverySecretAvailable(recoverySecret); } @Override public byte[] startRecoverySession(@NonNull String sessionId, @NonNull byte[] verifierPublicKey, @NonNull byte[] vaultParams, @NonNull byte[] vaultChallenge, @NonNull List<KeyStoreRecoveryMetadata> secrets, @UserIdInt int userId) throws RemoteException { @NonNull byte[] vaultChallenge, @NonNull List<KeyStoreRecoveryMetadata> secrets) throws RemoteException { return mRecoverableKeyStoreManager.startRecoverySession(sessionId, verifierPublicKey, vaultParams, vaultChallenge, secrets, userId); vaultParams, vaultChallenge, secrets); } @Override public Map<String, byte[]> recoverKeys(@NonNull String sessionId, @NonNull byte[] recoveryKeyBlob, @NonNull List<KeyEntryRecoveryData> applicationKeys, @UserIdInt int userId) @NonNull byte[] recoveryKeyBlob, @NonNull List<KeyEntryRecoveryData> applicationKeys) throws RemoteException { return mRecoverableKeyStoreManager.recoverKeys( sessionId, recoveryKeyBlob, applicationKeys, userId); sessionId, recoveryKeyBlob, applicationKeys); } @Override Loading
services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java +37 −40 Original line number Diff line number Diff line Loading @@ -63,9 +63,11 @@ import javax.crypto.AEADBadTagException; public class RecoverableKeyStoreManager { private static final String TAG = "RecoverableKeyStoreMgr"; private static final int ERROR_INSECURE_USER = 1; private static final int ERROR_KEYSTORE_INTERNAL_ERROR = 2; private static final int ERROR_DATABASE_ERROR = 3; // TODO: move error codes to RecoverableKeyStoreLoader. private static int ERROR_INSECURE_USER = 1; private static int ERROR_KEYSTORE_INTERNAL_ERROR = 2; private static int ERROR_DATABASE_ERROR = 3; private static int ERROR_RECOVERY_SESSION_NOT_FOUND = 4; private static RecoverableKeyStoreManager mInstance; Loading Loading @@ -119,9 +121,10 @@ public class RecoverableKeyStoreManager { } public void initRecoveryService( @NonNull String rootCertificateAlias, @NonNull byte[] signedPublicKeyList, int userId) @NonNull String rootCertificateAlias, @NonNull byte[] signedPublicKeyList) throws RemoteException { checkRecoverKeyStorePermission(); int userId = UserHandle.getCallingUserId(); // TODO: open /system/etc/security/... cert file, and check the signature on the public keys PublicKey publicKey; try { Loading @@ -144,22 +147,22 @@ public class RecoverableKeyStoreManager { * @return recovery data * @hide */ public @NonNull KeyStoreRecoveryData getRecoveryData(@NonNull byte[] account, int userId) public @NonNull KeyStoreRecoveryData getRecoveryData(@NonNull byte[] account) throws RemoteException { checkRecoverKeyStorePermission(); KeyStoreRecoveryData snapshot = mSnapshotStorage.get(UserHandle.getCallingUserId()); if (snapshot == null) { throw new ServiceSpecificException(RecoverableKeyStoreLoader.NO_SNAPSHOT_PENDING_ERROR); throw new ServiceSpecificException(RecoverableKeyStoreLoader.ERROR_NO_SNAPSHOT_PENDING); } return snapshot; } public void setSnapshotCreatedPendingIntent(@Nullable PendingIntent intent, int userId) public void setSnapshotCreatedPendingIntent(@Nullable PendingIntent intent) throws RemoteException { checkRecoverKeyStorePermission(); final int recoveryAgentUid = Binder.getCallingUid(); mListenersStorage.setSnapshotListener(recoveryAgentUid, intent); int uid = Binder.getCallingUid(); mListenersStorage.setSnapshotListener(uid, intent); } /** Loading @@ -168,14 +171,15 @@ public class RecoverableKeyStoreManager { * * @return Map from Recovery agent account to snapshot version. */ public @NonNull Map<byte[], Integer> getRecoverySnapshotVersions(int userId) public @NonNull Map<byte[], Integer> getRecoverySnapshotVersions() throws RemoteException { checkRecoverKeyStorePermission(); throw new UnsupportedOperationException(); } public void setServerParameters(long serverParameters, int userId) throws RemoteException { public void setServerParameters(long serverParameters) throws RemoteException { checkRecoverKeyStorePermission(); int userId = UserHandle.getCallingUserId(); mDatabase.setServerParameters(userId, Binder.getCallingUid(), serverParameters); } Loading @@ -187,7 +191,7 @@ public class RecoverableKeyStoreManager { * @param status - new status */ public void setRecoveryStatus( @NonNull String packageName, @Nullable String[] aliases, int status, int userId) @NonNull String packageName, @Nullable String[] aliases, int status) throws RemoteException { checkRecoverKeyStorePermission(); int uid = Binder.getCallingUid(); Loading @@ -211,7 +215,7 @@ public class RecoverableKeyStoreManager { * * @return {@code Map} from KeyStore alias to recovery status. */ public @NonNull Map<String, Integer> getRecoveryStatus(@Nullable String packageName, int userId) public @NonNull Map<String, Integer> getRecoveryStatus(@Nullable String packageName) 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 Loading @@ -225,7 +229,7 @@ public class RecoverableKeyStoreManager { * @hide */ public void setRecoverySecretTypes( @NonNull @KeyStoreRecoveryMetadata.UserSecretType int[] secretTypes, int userId) @NonNull @KeyStoreRecoveryMetadata.UserSecretType int[] secretTypes) throws RemoteException { checkRecoverKeyStorePermission(); mDatabase.setRecoverySecretTypes(UserHandle.getCallingUserId(), Binder.getCallingUid(), Loading @@ -238,7 +242,7 @@ public class RecoverableKeyStoreManager { * @return secret types * @hide */ public @NonNull int[] getRecoverySecretTypes(int userId) throws RemoteException { public @NonNull int[] getRecoverySecretTypes() throws RemoteException { checkRecoverKeyStorePermission(); return mDatabase.getRecoverySecretTypes(UserHandle.getCallingUserId(), Binder.getCallingUid()); Loading @@ -250,17 +254,17 @@ public class RecoverableKeyStoreManager { * @return secret types * @hide */ public @NonNull int[] getPendingRecoverySecretTypes(int userId) throws RemoteException { public @NonNull int[] getPendingRecoverySecretTypes() throws RemoteException { checkRecoverKeyStorePermission(); throw new UnsupportedOperationException(); } public void recoverySecretAvailable( @NonNull KeyStoreRecoveryMetadata recoverySecret, int userId) throws RemoteException { final int callingUid = Binder.getCallingUid(); // Recovery agent uid. @NonNull KeyStoreRecoveryMetadata recoverySecret) throws RemoteException { int uid = Binder.getCallingUid(); if (recoverySecret.getLockScreenUiFormat() == KeyStoreRecoveryMetadata.TYPE_LOCKSCREEN) { throw new SecurityException( "Caller " + callingUid + "is not allowed to set lock screen secret"); "Caller " + uid + " is not allowed to set lock screen secret"); } checkRecoverKeyStorePermission(); // TODO: add hook from LockSettingsService to set lock screen secret. Loading @@ -284,10 +288,10 @@ public class RecoverableKeyStoreManager { @NonNull byte[] verifierPublicKey, @NonNull byte[] vaultParams, @NonNull byte[] vaultChallenge, @NonNull List<KeyStoreRecoveryMetadata> secrets, int userId) @NonNull List<KeyStoreRecoveryMetadata> secrets) throws RemoteException { checkRecoverKeyStorePermission(); int uid = Binder.getCallingUid(); if (secrets.size() != 1) { // TODO: support multiple secrets Loading @@ -297,7 +301,7 @@ public class RecoverableKeyStoreManager { byte[] keyClaimant = KeySyncUtils.generateKeyClaimant(); byte[] kfHash = secrets.get(0).getSecret(); mRecoverySessionStorage.add( userId, uid, new RecoverySessionStorage.Entry(sessionId, kfHash, keyClaimant, vaultParams)); try { Loading Loading @@ -335,22 +339,21 @@ public class RecoverableKeyStoreManager { * 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. * @return Map from alias to raw key material. * @throws RemoteException if an error occurred recovering the keys. */ public Map<String, byte[]> recoverKeys( @NonNull String sessionId, @NonNull byte[] encryptedRecoveryKey, @NonNull List<KeyEntryRecoveryData> applicationKeys, int uid) @NonNull List<KeyEntryRecoveryData> applicationKeys) throws RemoteException { checkRecoverKeyStorePermission(); int uid = Binder.getCallingUid(); 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)); throw new ServiceSpecificException(ERROR_KEYSTORE_INTERNAL_ERROR, String.format(Locale.US, "Application uid=%d does not have pending session '%s'", uid, sessionId)); } try { Loading @@ -372,7 +375,7 @@ public class RecoverableKeyStoreManager { */ public byte[] generateAndStoreKey(@NonNull String alias) throws RemoteException { int uid = Binder.getCallingUid(); int userId = Binder.getCallingUserHandle().getIdentifier(); int userId = UserHandle.getCallingUserId(); PlatformEncryptionKey encryptionKey; Loading Loading @@ -400,7 +403,7 @@ public class RecoverableKeyStoreManager { private byte[] decryptRecoveryKey( RecoverySessionStorage.Entry sessionEntry, byte[] encryptedClaimResponse) throws RemoteException { throws RemoteException, ServiceSpecificException { try { byte[] locallyEncryptedKey = KeySyncUtils.decryptRecoveryClaimResponse( sessionEntry.getKeyClaimant(), Loading @@ -408,18 +411,12 @@ public class RecoverableKeyStoreManager { encryptedClaimResponse); return KeySyncUtils.decryptRecoveryKey(sessionEntry.getLskfHash(), locallyEncryptedKey); } catch (InvalidKeyException | AEADBadTagException e) { throw new RemoteException( "Failed to decrypt recovery key", e, /*enableSuppression=*/ true, /*writeableStackTrace=*/ true); throw new ServiceSpecificException(ERROR_KEYSTORE_INTERNAL_ERROR, "Failed to decrypt recovery key " + e.getMessage()); } 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); throw new ServiceSpecificException(ERROR_KEYSTORE_INTERNAL_ERROR, e.getMessage()); } } Loading
services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java +37 −49 File changed.Preview size limit exceeded, changes collapsed. Show changes