Loading core/java/android/security/keystore/recovery/RecoveryController.java +43 −0 Original line number Diff line number Diff line Loading @@ -113,6 +113,14 @@ public class RecoveryController { */ public static final int ERROR_DECRYPTION_FAILED = 26; /** * Error thrown if the format of a given key is invalid. This might be because the key has a * wrong length, invalid content, etc. * * @hide */ public static final int ERROR_INVALID_KEY_FORMAT = 27; private final ILockSettings mBinder; private final KeyStore mKeyStore; Loading Loading @@ -461,6 +469,7 @@ public class RecoveryController { } } // TODO: Unhide the following APIs, generateKey(), importKey(), and getKey() /** * @deprecated Use {@link #generateKey(String)}. * @removed Loading Loading @@ -502,6 +511,40 @@ public class RecoveryController { } } /** * Imports a 256-bit recoverable AES key with the given {@code alias} and the raw bytes {@code * keyBytes}. * * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery * service. * @throws LockScreenRequiredException if the user does not have a lock screen set. A lock * screen is required to generate recoverable keys. * * @hide */ public Key importKey(@NonNull String alias, byte[] keyBytes) throws InternalRecoveryServiceException, LockScreenRequiredException { try { String grantAlias = mBinder.importKey(alias, keyBytes); if (grantAlias == null) { throw new InternalRecoveryServiceException("Null grant alias"); } return AndroidKeyStoreProvider.loadAndroidKeyStoreKeyFromKeystore( mKeyStore, grantAlias, KeyStore.UID_SELF); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } catch (UnrecoverableKeyException e) { throw new InternalRecoveryServiceException("Failed to get key from keystore", e); } catch (ServiceSpecificException e) { if (e.errorCode == ERROR_INSECURE_USER) { throw new LockScreenRequiredException(e.getMessage()); } throw wrapUnexpectedServiceSpecificException(e); } } /** * Gets a key called {@code alias} from the recoverable key store. * Loading core/java/com/android/internal/widget/ILockSettings.aidl +1 −0 Original line number Diff line number Diff line Loading @@ -68,6 +68,7 @@ interface ILockSettings { KeyChainSnapshot getKeyChainSnapshot(); byte[] generateAndStoreKey(String alias); String generateKey(String alias); String importKey(String alias, in byte[] keyBytes); String getKey(String alias); void removeKey(String alias); void setSnapshotCreatedPendingIntent(in PendingIntent intent); Loading services/core/java/com/android/server/locksettings/LockSettingsService.java +5 −0 Original line number Diff line number Diff line Loading @@ -2078,6 +2078,11 @@ public class LockSettingsService extends ILockSettings.Stub { return mRecoverableKeyStoreManager.generateKey(alias); } @Override public String importKey(@NonNull String alias, byte[] keyBytes) throws RemoteException { return mRecoverableKeyStoreManager.importKey(alias, keyBytes); } @Override public String getKey(@NonNull String alias) throws RemoteException { return mRecoverableKeyStoreManager.getKey(alias); Loading services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGenerator.java +48 −5 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.server.locksettings.recoverablekeystore; import android.annotation.NonNull; import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb; import java.security.InvalidKeyException; Loading @@ -25,20 +27,24 @@ import java.util.Locale; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; // TODO: Rename RecoverableKeyGenerator to RecoverableKeyManager as it can import a key too now /** * Generates keys and stores them both in AndroidKeyStore and on disk, in wrapped form. * Generates/imports keys and stores them both in AndroidKeyStore and on disk, in wrapped form. * * <p>Generates 256-bit AES keys, which can be used for encrypt / decrypt with AES/GCM/NoPadding. * <p>Generates/imports 256-bit AES keys, which can be used for encrypt and decrypt with AES-GCM. * They are synced to disk wrapped by a platform key. This allows them to be exported to a remote * service. * * @hide */ public class RecoverableKeyGenerator { private static final int RESULT_CANNOT_INSERT_ROW = -1; private static final String KEY_GENERATOR_ALGORITHM = "AES"; private static final int KEY_SIZE_BITS = 256; private static final String SECRET_KEY_ALGORITHM = "AES"; static final int KEY_SIZE_BITS = 256; /** * A new {@link RecoverableKeyGenerator} instance. Loading @@ -52,7 +58,7 @@ public class RecoverableKeyGenerator { throws NoSuchAlgorithmException { // NB: This cannot use AndroidKeyStore as the provider, as we need access to the raw key // material, so that it can be synced to disk in encrypted form. KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_GENERATOR_ALGORITHM); KeyGenerator keyGenerator = KeyGenerator.getInstance(SECRET_KEY_ALGORITHM); return new RecoverableKeyGenerator(keyGenerator, database); } Loading Loading @@ -102,4 +108,41 @@ public class RecoverableKeyGenerator { mDatabase.setShouldCreateSnapshot(userId, uid, true); return key.getEncoded(); } /** * Imports an AES key with the given alias. * * <p>Stores in the AndroidKeyStore, as well as persisting in wrapped form to disk. It is * persisted to disk so that it can be synced remotely, and then recovered on another device. * The generated key allows encrypt/decrypt only using AES/GCM/NoPadding. * * @param platformKey The user's platform key, with which to wrap the generated key. * @param userId The user ID of the profile to which the calling app belongs. * @param uid The uid of the application that will own the key. * @param alias The alias by which the key will be known in the recoverable key store. * @param keyBytes The raw bytes of the AES key to be imported. * @throws RecoverableKeyStorageException if there is some error persisting the key either to * the database. * @throws KeyStoreException if there is a KeyStore error wrapping the generated key. * @throws InvalidKeyException if the platform key cannot be used to wrap keys. * * @hide */ public void importKey( @NonNull PlatformEncryptionKey platformKey, int userId, int uid, @NonNull String alias, @NonNull byte[] keyBytes) throws RecoverableKeyStorageException, KeyStoreException, InvalidKeyException { SecretKey key = new SecretKeySpec(keyBytes, SECRET_KEY_ALGORITHM); WrappedKey wrappedKey = WrappedKey.fromSecretKey(platformKey, key); long result = mDatabase.insertKey(userId, uid, alias, wrappedKey); if (result == RESULT_CANNOT_INSERT_ROW) { throw new RecoverableKeyStorageException( String.format( Locale.US, "Failed writing (%d, %s) to database.", uid, alias)); } mDatabase.setShouldCreateSnapshot(userId, uid, true); } } services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java +53 −8 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.server.locksettings.recoverablekeystore; import static android.security.keystore.recovery.RecoveryController.ERROR_BAD_CERTIFICATE_FORMAT; import static android.security.keystore.recovery.RecoveryController.ERROR_DECRYPTION_FAILED; import static android.security.keystore.recovery.RecoveryController.ERROR_INSECURE_USER; import static android.security.keystore.recovery.RecoveryController.ERROR_INVALID_KEY_FORMAT; import static android.security.keystore.recovery.RecoveryController.ERROR_NO_SNAPSHOT_PENDING; import static android.security.keystore.recovery.RecoveryController.ERROR_SERVICE_INTERNAL_ERROR; import static android.security.keystore.recovery.RecoveryController.ERROR_SESSION_EXPIRED; Loading Loading @@ -505,6 +506,7 @@ public class RecoverableKeyStoreManager { * * <p>TODO: Once AndroidKeyStore has added move api, do not return raw bytes. * * @deprecated * @hide */ public byte[] generateAndStoreKey(@NonNull String alias) throws RemoteException { Loading Loading @@ -580,6 +582,57 @@ public class RecoverableKeyStoreManager { } } /** * Imports a 256-bit AES-GCM key named {@code alias}. The key is stored in system service * keystore namespace. * * @param alias the alias provided by caller as a reference to the key. * @param keyBytes the raw bytes of the 256-bit AES key. * @return grant alias, which caller can use to access the key. * @throws RemoteException if the given key is invalid or some internal errors occur. * * @hide */ public String importKey(@NonNull String alias, @NonNull byte[] keyBytes) throws RemoteException { if (keyBytes == null || keyBytes.length != RecoverableKeyGenerator.KEY_SIZE_BITS / Byte.SIZE) { Log.e(TAG, "The given key for import doesn't have the required length " + RecoverableKeyGenerator.KEY_SIZE_BITS); throw new ServiceSpecificException(ERROR_INVALID_KEY_FORMAT, "The given key does not contain " + RecoverableKeyGenerator.KEY_SIZE_BITS + " bits."); } int uid = Binder.getCallingUid(); int userId = UserHandle.getCallingUserId(); // TODO: Refactor RecoverableKeyGenerator to wrap the PlatformKey logic PlatformEncryptionKey encryptionKey; try { encryptionKey = mPlatformKeyManager.getEncryptKey(userId); } catch (NoSuchAlgorithmException e) { // Impossible: all algorithms must be supported by AOSP throw new RuntimeException(e); } catch (KeyStoreException | UnrecoverableKeyException e) { throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage()); } catch (InsecureUserException e) { throw new ServiceSpecificException(ERROR_INSECURE_USER, e.getMessage()); } try { // Wrap the key by the platform key and store the wrapped key locally mRecoverableKeyGenerator.importKey(encryptionKey, userId, uid, alias, keyBytes); // Import the key to Android KeyStore and get grant mApplicationKeyStorage.setSymmetricKeyEntry(userId, uid, alias, keyBytes); return mApplicationKeyStorage.getGrantAlias(userId, uid, alias); } catch (KeyStoreException | InvalidKeyException | RecoverableKeyStorageException e) { throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage()); } } /** * Gets a key named {@code alias} in caller's namespace. * Loading Loading @@ -630,14 +683,6 @@ public class RecoverableKeyStoreManager { } } private String constructLoggingMessage(String key, byte[] value) { if (value == null) { return key + " is null"; } else { return key + ": " + HexDump.toHexString(value); } } /** * Uses {@code recoveryKey} to decrypt {@code applicationKeys}. * Loading Loading
core/java/android/security/keystore/recovery/RecoveryController.java +43 −0 Original line number Diff line number Diff line Loading @@ -113,6 +113,14 @@ public class RecoveryController { */ public static final int ERROR_DECRYPTION_FAILED = 26; /** * Error thrown if the format of a given key is invalid. This might be because the key has a * wrong length, invalid content, etc. * * @hide */ public static final int ERROR_INVALID_KEY_FORMAT = 27; private final ILockSettings mBinder; private final KeyStore mKeyStore; Loading Loading @@ -461,6 +469,7 @@ public class RecoveryController { } } // TODO: Unhide the following APIs, generateKey(), importKey(), and getKey() /** * @deprecated Use {@link #generateKey(String)}. * @removed Loading Loading @@ -502,6 +511,40 @@ public class RecoveryController { } } /** * Imports a 256-bit recoverable AES key with the given {@code alias} and the raw bytes {@code * keyBytes}. * * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery * service. * @throws LockScreenRequiredException if the user does not have a lock screen set. A lock * screen is required to generate recoverable keys. * * @hide */ public Key importKey(@NonNull String alias, byte[] keyBytes) throws InternalRecoveryServiceException, LockScreenRequiredException { try { String grantAlias = mBinder.importKey(alias, keyBytes); if (grantAlias == null) { throw new InternalRecoveryServiceException("Null grant alias"); } return AndroidKeyStoreProvider.loadAndroidKeyStoreKeyFromKeystore( mKeyStore, grantAlias, KeyStore.UID_SELF); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } catch (UnrecoverableKeyException e) { throw new InternalRecoveryServiceException("Failed to get key from keystore", e); } catch (ServiceSpecificException e) { if (e.errorCode == ERROR_INSECURE_USER) { throw new LockScreenRequiredException(e.getMessage()); } throw wrapUnexpectedServiceSpecificException(e); } } /** * Gets a key called {@code alias} from the recoverable key store. * Loading
core/java/com/android/internal/widget/ILockSettings.aidl +1 −0 Original line number Diff line number Diff line Loading @@ -68,6 +68,7 @@ interface ILockSettings { KeyChainSnapshot getKeyChainSnapshot(); byte[] generateAndStoreKey(String alias); String generateKey(String alias); String importKey(String alias, in byte[] keyBytes); String getKey(String alias); void removeKey(String alias); void setSnapshotCreatedPendingIntent(in PendingIntent intent); Loading
services/core/java/com/android/server/locksettings/LockSettingsService.java +5 −0 Original line number Diff line number Diff line Loading @@ -2078,6 +2078,11 @@ public class LockSettingsService extends ILockSettings.Stub { return mRecoverableKeyStoreManager.generateKey(alias); } @Override public String importKey(@NonNull String alias, byte[] keyBytes) throws RemoteException { return mRecoverableKeyStoreManager.importKey(alias, keyBytes); } @Override public String getKey(@NonNull String alias) throws RemoteException { return mRecoverableKeyStoreManager.getKey(alias); Loading
services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGenerator.java +48 −5 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.server.locksettings.recoverablekeystore; import android.annotation.NonNull; import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb; import java.security.InvalidKeyException; Loading @@ -25,20 +27,24 @@ import java.util.Locale; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; // TODO: Rename RecoverableKeyGenerator to RecoverableKeyManager as it can import a key too now /** * Generates keys and stores them both in AndroidKeyStore and on disk, in wrapped form. * Generates/imports keys and stores them both in AndroidKeyStore and on disk, in wrapped form. * * <p>Generates 256-bit AES keys, which can be used for encrypt / decrypt with AES/GCM/NoPadding. * <p>Generates/imports 256-bit AES keys, which can be used for encrypt and decrypt with AES-GCM. * They are synced to disk wrapped by a platform key. This allows them to be exported to a remote * service. * * @hide */ public class RecoverableKeyGenerator { private static final int RESULT_CANNOT_INSERT_ROW = -1; private static final String KEY_GENERATOR_ALGORITHM = "AES"; private static final int KEY_SIZE_BITS = 256; private static final String SECRET_KEY_ALGORITHM = "AES"; static final int KEY_SIZE_BITS = 256; /** * A new {@link RecoverableKeyGenerator} instance. Loading @@ -52,7 +58,7 @@ public class RecoverableKeyGenerator { throws NoSuchAlgorithmException { // NB: This cannot use AndroidKeyStore as the provider, as we need access to the raw key // material, so that it can be synced to disk in encrypted form. KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_GENERATOR_ALGORITHM); KeyGenerator keyGenerator = KeyGenerator.getInstance(SECRET_KEY_ALGORITHM); return new RecoverableKeyGenerator(keyGenerator, database); } Loading Loading @@ -102,4 +108,41 @@ public class RecoverableKeyGenerator { mDatabase.setShouldCreateSnapshot(userId, uid, true); return key.getEncoded(); } /** * Imports an AES key with the given alias. * * <p>Stores in the AndroidKeyStore, as well as persisting in wrapped form to disk. It is * persisted to disk so that it can be synced remotely, and then recovered on another device. * The generated key allows encrypt/decrypt only using AES/GCM/NoPadding. * * @param platformKey The user's platform key, with which to wrap the generated key. * @param userId The user ID of the profile to which the calling app belongs. * @param uid The uid of the application that will own the key. * @param alias The alias by which the key will be known in the recoverable key store. * @param keyBytes The raw bytes of the AES key to be imported. * @throws RecoverableKeyStorageException if there is some error persisting the key either to * the database. * @throws KeyStoreException if there is a KeyStore error wrapping the generated key. * @throws InvalidKeyException if the platform key cannot be used to wrap keys. * * @hide */ public void importKey( @NonNull PlatformEncryptionKey platformKey, int userId, int uid, @NonNull String alias, @NonNull byte[] keyBytes) throws RecoverableKeyStorageException, KeyStoreException, InvalidKeyException { SecretKey key = new SecretKeySpec(keyBytes, SECRET_KEY_ALGORITHM); WrappedKey wrappedKey = WrappedKey.fromSecretKey(platformKey, key); long result = mDatabase.insertKey(userId, uid, alias, wrappedKey); if (result == RESULT_CANNOT_INSERT_ROW) { throw new RecoverableKeyStorageException( String.format( Locale.US, "Failed writing (%d, %s) to database.", uid, alias)); } mDatabase.setShouldCreateSnapshot(userId, uid, true); } }
services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java +53 −8 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.server.locksettings.recoverablekeystore; import static android.security.keystore.recovery.RecoveryController.ERROR_BAD_CERTIFICATE_FORMAT; import static android.security.keystore.recovery.RecoveryController.ERROR_DECRYPTION_FAILED; import static android.security.keystore.recovery.RecoveryController.ERROR_INSECURE_USER; import static android.security.keystore.recovery.RecoveryController.ERROR_INVALID_KEY_FORMAT; import static android.security.keystore.recovery.RecoveryController.ERROR_NO_SNAPSHOT_PENDING; import static android.security.keystore.recovery.RecoveryController.ERROR_SERVICE_INTERNAL_ERROR; import static android.security.keystore.recovery.RecoveryController.ERROR_SESSION_EXPIRED; Loading Loading @@ -505,6 +506,7 @@ public class RecoverableKeyStoreManager { * * <p>TODO: Once AndroidKeyStore has added move api, do not return raw bytes. * * @deprecated * @hide */ public byte[] generateAndStoreKey(@NonNull String alias) throws RemoteException { Loading Loading @@ -580,6 +582,57 @@ public class RecoverableKeyStoreManager { } } /** * Imports a 256-bit AES-GCM key named {@code alias}. The key is stored in system service * keystore namespace. * * @param alias the alias provided by caller as a reference to the key. * @param keyBytes the raw bytes of the 256-bit AES key. * @return grant alias, which caller can use to access the key. * @throws RemoteException if the given key is invalid or some internal errors occur. * * @hide */ public String importKey(@NonNull String alias, @NonNull byte[] keyBytes) throws RemoteException { if (keyBytes == null || keyBytes.length != RecoverableKeyGenerator.KEY_SIZE_BITS / Byte.SIZE) { Log.e(TAG, "The given key for import doesn't have the required length " + RecoverableKeyGenerator.KEY_SIZE_BITS); throw new ServiceSpecificException(ERROR_INVALID_KEY_FORMAT, "The given key does not contain " + RecoverableKeyGenerator.KEY_SIZE_BITS + " bits."); } int uid = Binder.getCallingUid(); int userId = UserHandle.getCallingUserId(); // TODO: Refactor RecoverableKeyGenerator to wrap the PlatformKey logic PlatformEncryptionKey encryptionKey; try { encryptionKey = mPlatformKeyManager.getEncryptKey(userId); } catch (NoSuchAlgorithmException e) { // Impossible: all algorithms must be supported by AOSP throw new RuntimeException(e); } catch (KeyStoreException | UnrecoverableKeyException e) { throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage()); } catch (InsecureUserException e) { throw new ServiceSpecificException(ERROR_INSECURE_USER, e.getMessage()); } try { // Wrap the key by the platform key and store the wrapped key locally mRecoverableKeyGenerator.importKey(encryptionKey, userId, uid, alias, keyBytes); // Import the key to Android KeyStore and get grant mApplicationKeyStorage.setSymmetricKeyEntry(userId, uid, alias, keyBytes); return mApplicationKeyStorage.getGrantAlias(userId, uid, alias); } catch (KeyStoreException | InvalidKeyException | RecoverableKeyStorageException e) { throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage()); } } /** * Gets a key named {@code alias} in caller's namespace. * Loading Loading @@ -630,14 +683,6 @@ public class RecoverableKeyStoreManager { } } private String constructLoggingMessage(String key, byte[] value) { if (value == null) { return key + " is null"; } else { return key + ": " + HexDump.toHexString(value); } } /** * Uses {@code recoveryKey} to decrypt {@code applicationKeys}. * Loading