Loading services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java +4 −3 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ import static android.security.keystore.recovery.KeyChainProtectionParams.TYPE_L import android.annotation.Nullable; import android.content.Context; import android.os.RemoteException; import android.security.Scrypt; import android.security.keystore.recovery.KeyChainProtectionParams; import android.security.keystore.recovery.KeyChainSnapshot; Loading Loading @@ -162,7 +163,7 @@ public class KeySyncTask implements Runnable { } } private void syncKeys() { private void syncKeys() throws RemoteException { if (mCredentialType == LockPatternUtils.CREDENTIAL_TYPE_NONE) { // Application keys for the user will not be available for sync. Log.w(TAG, "Credentials are not set for user " + mUserId); Loading Loading @@ -195,7 +196,7 @@ public class KeySyncTask implements Runnable { && mCredentialType != LockPatternUtils.CREDENTIAL_TYPE_PASSWORD; } private void syncKeysForAgent(int recoveryAgentUid) throws IOException { private void syncKeysForAgent(int recoveryAgentUid) throws IOException, RemoteException { boolean shouldRecreateCurrentVersion = false; if (!shouldCreateSnapshot(recoveryAgentUid)) { shouldRecreateCurrentVersion = Loading Loading @@ -412,7 +413,7 @@ public class KeySyncTask implements Runnable { private Map<String, Pair<SecretKey, byte[]>> getKeysToSync(int recoveryAgentUid) throws InsecureUserException, KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException, NoSuchPaddingException, BadPlatformKeyException, InvalidKeyException, InvalidAlgorithmParameterException, IOException { InvalidKeyException, InvalidAlgorithmParameterException, IOException, RemoteException { PlatformDecryptionKey decryptKey = mPlatformKeyManager.getDecryptKey(mUserId);; Map<String, WrappedKey> wrappedKeys = mRecoverableKeyStoreDb.getAllKeys( mUserId, recoveryAgentUid, decryptKey.getGenerationId()); Loading services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java +68 −10 Original line number Diff line number Diff line Loading @@ -18,15 +18,21 @@ package com.android.server.locksettings.recoverablekeystore; import android.app.KeyguardManager; import android.content.Context; import android.os.RemoteException; import android.security.GateKeeper; import android.security.keystore.AndroidKeyStoreSecretKey; import android.security.keystore.KeyPermanentlyInvalidatedException; import android.security.keystore.KeyProperties; import android.security.keystore.KeyProtection; import android.service.gatekeeper.IGateKeeperService; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb; import java.io.IOException; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; Loading @@ -34,9 +40,11 @@ import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; import java.util.Locale; import javax.crypto.Cipher; import javax.crypto.KeyGenerator; import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; import javax.security.auth.DestroyFailedException; import javax.crypto.spec.GCMParameterSpec; /** * Manages creating and checking the validity of the platform key. Loading Loading @@ -67,6 +75,10 @@ public class PlatformKeyManager { private static final String ENCRYPT_KEY_ALIAS_SUFFIX = "encrypt"; private static final String DECRYPT_KEY_ALIAS_SUFFIX = "decrypt"; private static final int USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS = 15; private static final String KEY_WRAP_CIPHER_ALGORITHM = "AES/GCM/NoPadding"; private static final int GCM_TAG_LENGTH_BITS = 128; // Only used for checking if a key is usable private static final byte[] GCM_INSECURE_NONCE_BYTES = new byte[12]; private final Context mContext; private final KeyStoreProxy mKeyStore; Loading Loading @@ -158,12 +170,14 @@ public class PlatformKeyManager { * @throws KeyStoreException if there is an error in AndroidKeyStore. * @throws InsecureUserException if the user does not have a lock screen set. * @throws IOException if there was an issue with local database update. * @throws RemoteException if there was an issue communicating with {@link IGateKeeperService}. * * @hide */ @VisibleForTesting void regenerate(int userId) throws NoSuchAlgorithmException, KeyStoreException, InsecureUserException, IOException { throws NoSuchAlgorithmException, KeyStoreException, InsecureUserException, IOException, RemoteException { if (!isAvailable(userId)) { throw new InsecureUserException(String.format( Locale.US, "%d does not have a lock screen set.", userId)); Loading @@ -190,11 +204,13 @@ public class PlatformKeyManager { * @throws NoSuchAlgorithmException if AES is unavailable - should never occur. * @throws InsecureUserException if the user does not have a lock screen set. * @throws IOException if there was an issue with local database update. * @throws RemoteException if there was an issue communicating with {@link IGateKeeperService}. * * @hide */ public PlatformEncryptionKey getEncryptKey(int userId) throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException, IOException { public PlatformEncryptionKey getEncryptKey(int userId) throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException, IOException, RemoteException { init(userId); try { // Try to see if the decryption key is still accessible before using the encryption key. Loading Loading @@ -243,14 +259,18 @@ public class PlatformKeyManager { * @throws NoSuchAlgorithmException if AES is unavailable - should never occur. * @throws InsecureUserException if the user does not have a lock screen set. * @throws IOException if there was an issue with local database update. * @throws RemoteException if there was an issue communicating with {@link IGateKeeperService}. * * @hide */ public PlatformDecryptionKey getDecryptKey(int userId) throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException, IOException { public PlatformDecryptionKey getDecryptKey(int userId) throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException, IOException, RemoteException { init(userId); try { return getDecryptKeyInternal(userId); PlatformDecryptionKey decryptionKey = getDecryptKeyInternal(userId); ensureDecryptionKeyIsValid(userId, decryptionKey); return decryptionKey; } catch (UnrecoverableKeyException e) { Log.i(TAG, String.format(Locale.US, "Regenerating permanently invalid Platform key for user %d.", Loading Loading @@ -283,6 +303,29 @@ public class PlatformKeyManager { return new PlatformDecryptionKey(generationId, key); } /** * Tries to use the decryption key to make sure it is not permanently invalidated. The exception * {@code KeyPermanentlyInvalidatedException} is thrown only when the key is in use. * * <p>Note that we ignore all other InvalidKeyException exceptions, because such an exception * may be thrown for auth-bound keys if there's no recent unlock event. */ private void ensureDecryptionKeyIsValid(int userId, PlatformDecryptionKey decryptionKey) throws UnrecoverableKeyException { try { Cipher.getInstance(KEY_WRAP_CIPHER_ALGORITHM).init(Cipher.UNWRAP_MODE, decryptionKey.getKey(), new GCMParameterSpec(GCM_TAG_LENGTH_BITS, GCM_INSECURE_NONCE_BYTES)); } catch (KeyPermanentlyInvalidatedException e) { Log.e(TAG, String.format(Locale.US, "The platform key for user %d became invalid.", userId)); throw new UnrecoverableKeyException(e.getMessage()); } catch (NoSuchAlgorithmException | InvalidKeyException | InvalidAlgorithmParameterException | NoSuchPaddingException e) { // Ignore all other exceptions } } /** * Initializes the class. If there is no current platform key, and the user has a lock screen * set, will create the platform key and set the generation ID. Loading @@ -291,11 +334,13 @@ public class PlatformKeyManager { * @throws KeyStoreException if there was an error in AndroidKeyStore. * @throws NoSuchAlgorithmException if AES is unavailable - should never happen. * @throws IOException if there was an issue with local database update. * @throws RemoteException if there was an issue communicating with {@link IGateKeeperService}. * * @hide */ void init(int userId) throws KeyStoreException, NoSuchAlgorithmException, InsecureUserException, IOException { throws KeyStoreException, NoSuchAlgorithmException, InsecureUserException, IOException, RemoteException { if (!isAvailable(userId)) { throw new InsecureUserException(String.format( Locale.US, "%d does not have a lock screen set.", userId)); Loading Loading @@ -372,6 +417,11 @@ public class PlatformKeyManager { && mKeyStore.containsAlias(getDecryptAlias(userId, generationId)); } @VisibleForTesting IGateKeeperService getGateKeeperService() { return GateKeeper.getService(); } /** * Generates a new 256-bit AES key, and loads it into AndroidKeyStore with the given * {@code generationId} determining its aliases. Loading @@ -380,15 +430,23 @@ public class PlatformKeyManager { * available since API version 1. * @throws KeyStoreException if there was an issue loading the keys into AndroidKeyStore. * @throws IOException if there was an issue with local database update. * @throws RemoteException if there was an issue communicating with {@link IGateKeeperService}. */ private void generateAndLoadKey(int userId, int generationId) throws NoSuchAlgorithmException, KeyStoreException, IOException { throws NoSuchAlgorithmException, KeyStoreException, IOException, RemoteException { String encryptAlias = getEncryptAlias(userId, generationId); String decryptAlias = getDecryptAlias(userId, generationId); // SecretKey implementation doesn't provide reliable way to destroy the secret // so it may live in memory for some time. SecretKey secretKey = generateAesKey(); long secureUserId = getGateKeeperService().getSecureUserId(userId); // TODO(b/124095438): Propagate this failure instead of silently failing. if (secureUserId == GateKeeper.INVALID_SECURE_USER_ID) { Log.e(TAG, "No SID available for user " + userId); return; } // Store decryption key first since it is more likely to fail. mKeyStore.setEntry( decryptAlias, Loading @@ -399,7 +457,7 @@ public class PlatformKeyManager { USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS) .setBlockModes(KeyProperties.BLOCK_MODE_GCM) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) .setBoundToSpecificSecureUserId(userId) .setBoundToSpecificSecureUserId(secureUserId) .build()); mKeyStore.setEntry( encryptAlias, Loading services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java +83 −4 Original line number Diff line number Diff line Loading @@ -24,14 +24,21 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.expectThrows; import android.app.KeyguardManager; import android.content.Context; import android.os.RemoteException; import android.security.GateKeeper; import android.security.keystore.AndroidKeyStoreSecretKey; import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.KeyProperties; import android.security.keystore.KeyProtection; import android.service.gatekeeper.IGateKeeperService; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; Loading @@ -53,6 +60,8 @@ import java.security.KeyStore; import java.security.UnrecoverableKeyException; import java.util.List; import javax.crypto.KeyGenerator; @SmallTest @RunWith(AndroidJUnit4.class) public class PlatformKeyManagerTest { Loading @@ -60,10 +69,15 @@ public class PlatformKeyManagerTest { private static final String DATABASE_FILE_NAME = "recoverablekeystore.db"; private static final int USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS = 15; private static final int USER_ID_FIXTURE = 42; private static final long USER_SID = 4200L; private static final String KEY_ALGORITHM = "AES"; private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore"; private static final String TESTING_KEYSTORE_KEY_ALIAS = "testing-key-store-key-alias"; @Mock private Context mContext; @Mock private KeyStoreProxy mKeyStoreProxy; @Mock private KeyguardManager mKeyguardManager; @Mock private IGateKeeperService mGateKeeperService; @Captor private ArgumentCaptor<KeyStore.ProtectionParameter> mProtectionParameterCaptor; @Captor private ArgumentCaptor<KeyStore.Entry> mEntryArgumentCaptor; Loading @@ -74,18 +88,19 @@ public class PlatformKeyManagerTest { private PlatformKeyManager mPlatformKeyManager; @Before public void setUp() { public void setUp() throws Exception { MockitoAnnotations.initMocks(this); Context context = InstrumentationRegistry.getTargetContext(); mDatabaseFile = context.getDatabasePath(DATABASE_FILE_NAME); mRecoverableKeyStoreDb = RecoverableKeyStoreDb.newInstance(context); mPlatformKeyManager = new PlatformKeyManager( mContext, mKeyStoreProxy, mRecoverableKeyStoreDb); mPlatformKeyManager = new PlatformKeyManagerTestable( mContext, mKeyStoreProxy, mRecoverableKeyStoreDb, mGateKeeperService); when(mContext.getSystemService(anyString())).thenReturn(mKeyguardManager); when(mContext.getSystemServiceName(any())).thenReturn("test"); when(mKeyguardManager.isDeviceSecure(USER_ID_FIXTURE)).thenReturn(true); when(mGateKeeperService.getSecureUserId(USER_ID_FIXTURE)).thenReturn(USER_SID); } @After Loading Loading @@ -192,10 +207,35 @@ public class PlatformKeyManagerTest { mPlatformKeyManager.init(USER_ID_FIXTURE); assertEquals( USER_ID_FIXTURE, USER_SID, getDecryptKeyProtection().getBoundToSpecificSecureUserId()); } @Test public void init_doesNotCreateDecryptKeyIfNoSid() throws Exception { when(mGateKeeperService.getSecureUserId(USER_ID_FIXTURE)) .thenReturn(GateKeeper.INVALID_SECURE_USER_ID); mPlatformKeyManager.init(USER_ID_FIXTURE); verify(mKeyStoreProxy, never()).setEntry( eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"), any(), any()); } @Test public void init_doesNotCreateDecryptKeyOnGateKeeperException() throws Exception { when(mGateKeeperService.getSecureUserId(USER_ID_FIXTURE)).thenThrow(new RemoteException()); expectThrows(RemoteException.class, () -> mPlatformKeyManager.init(USER_ID_FIXTURE)); verify(mKeyStoreProxy, never()).setEntry( eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"), any(), any()); } @Test public void init_createsBothKeysWithSameMaterial() throws Exception { mPlatformKeyManager.init(USER_ID_FIXTURE); Loading Loading @@ -259,6 +299,9 @@ public class PlatformKeyManagerTest { when(mKeyStoreProxy .containsAlias("com.android.server.locksettings.recoverablekeystore/" + "platform/42/1/encrypt")).thenReturn(true); when(mKeyStoreProxy.getKey( eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"), any())).thenReturn(generateAndroidKeyStoreKey()); mPlatformKeyManager.getDecryptKey(USER_ID_FIXTURE); Loading @@ -281,6 +324,9 @@ public class PlatformKeyManagerTest { when(mKeyStoreProxy .containsAlias("com.android.server.locksettings.recoverablekeystore/" + "platform/42/2/decrypt")).thenReturn(true); when(mKeyStoreProxy.getKey( eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"), any())).thenReturn(generateAndroidKeyStoreKey()); mPlatformKeyManager.getDecryptKey(USER_ID_FIXTURE); Loading Loading @@ -352,6 +398,9 @@ public class PlatformKeyManagerTest { doThrow(new UnrecoverableKeyException()).when(mKeyStoreProxy).getKey( eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"), any()); when(mKeyStoreProxy.getKey( eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"), any())).thenReturn(generateAndroidKeyStoreKey()); when(mKeyStoreProxy .containsAlias("com.android.server.locksettings.recoverablekeystore/" Loading Loading @@ -536,4 +585,34 @@ public class PlatformKeyManagerTest { mProtectionParameterCaptor.capture()); return (KeyProtection) mProtectionParameterCaptor.getValue(); } private AndroidKeyStoreSecretKey generateAndroidKeyStoreKey() throws Exception { KeyGenerator keyGenerator = KeyGenerator.getInstance( KEY_ALGORITHM, ANDROID_KEY_STORE_PROVIDER); keyGenerator.init(new KeyGenParameterSpec.Builder(TESTING_KEYSTORE_KEY_ALIAS, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) .setBlockModes(KeyProperties.BLOCK_MODE_GCM) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) .build()); return (AndroidKeyStoreSecretKey) keyGenerator.generateKey(); } class PlatformKeyManagerTestable extends PlatformKeyManager { private IGateKeeperService mGateKeeperService; PlatformKeyManagerTestable( Context context, KeyStoreProxy keyStoreProxy, RecoverableKeyStoreDb database, IGateKeeperService gateKeeperService) { super(context, keyStoreProxy, database); mGateKeeperService = gateKeeperService; } @Override IGateKeeperService getGateKeeperService() { return mGateKeeperService; } } } Loading
services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java +4 −3 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ import static android.security.keystore.recovery.KeyChainProtectionParams.TYPE_L import android.annotation.Nullable; import android.content.Context; import android.os.RemoteException; import android.security.Scrypt; import android.security.keystore.recovery.KeyChainProtectionParams; import android.security.keystore.recovery.KeyChainSnapshot; Loading Loading @@ -162,7 +163,7 @@ public class KeySyncTask implements Runnable { } } private void syncKeys() { private void syncKeys() throws RemoteException { if (mCredentialType == LockPatternUtils.CREDENTIAL_TYPE_NONE) { // Application keys for the user will not be available for sync. Log.w(TAG, "Credentials are not set for user " + mUserId); Loading Loading @@ -195,7 +196,7 @@ public class KeySyncTask implements Runnable { && mCredentialType != LockPatternUtils.CREDENTIAL_TYPE_PASSWORD; } private void syncKeysForAgent(int recoveryAgentUid) throws IOException { private void syncKeysForAgent(int recoveryAgentUid) throws IOException, RemoteException { boolean shouldRecreateCurrentVersion = false; if (!shouldCreateSnapshot(recoveryAgentUid)) { shouldRecreateCurrentVersion = Loading Loading @@ -412,7 +413,7 @@ public class KeySyncTask implements Runnable { private Map<String, Pair<SecretKey, byte[]>> getKeysToSync(int recoveryAgentUid) throws InsecureUserException, KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException, NoSuchPaddingException, BadPlatformKeyException, InvalidKeyException, InvalidAlgorithmParameterException, IOException { InvalidKeyException, InvalidAlgorithmParameterException, IOException, RemoteException { PlatformDecryptionKey decryptKey = mPlatformKeyManager.getDecryptKey(mUserId);; Map<String, WrappedKey> wrappedKeys = mRecoverableKeyStoreDb.getAllKeys( mUserId, recoveryAgentUid, decryptKey.getGenerationId()); Loading
services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java +68 −10 Original line number Diff line number Diff line Loading @@ -18,15 +18,21 @@ package com.android.server.locksettings.recoverablekeystore; import android.app.KeyguardManager; import android.content.Context; import android.os.RemoteException; import android.security.GateKeeper; import android.security.keystore.AndroidKeyStoreSecretKey; import android.security.keystore.KeyPermanentlyInvalidatedException; import android.security.keystore.KeyProperties; import android.security.keystore.KeyProtection; import android.service.gatekeeper.IGateKeeperService; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb; import java.io.IOException; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; Loading @@ -34,9 +40,11 @@ import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; import java.util.Locale; import javax.crypto.Cipher; import javax.crypto.KeyGenerator; import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; import javax.security.auth.DestroyFailedException; import javax.crypto.spec.GCMParameterSpec; /** * Manages creating and checking the validity of the platform key. Loading Loading @@ -67,6 +75,10 @@ public class PlatformKeyManager { private static final String ENCRYPT_KEY_ALIAS_SUFFIX = "encrypt"; private static final String DECRYPT_KEY_ALIAS_SUFFIX = "decrypt"; private static final int USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS = 15; private static final String KEY_WRAP_CIPHER_ALGORITHM = "AES/GCM/NoPadding"; private static final int GCM_TAG_LENGTH_BITS = 128; // Only used for checking if a key is usable private static final byte[] GCM_INSECURE_NONCE_BYTES = new byte[12]; private final Context mContext; private final KeyStoreProxy mKeyStore; Loading Loading @@ -158,12 +170,14 @@ public class PlatformKeyManager { * @throws KeyStoreException if there is an error in AndroidKeyStore. * @throws InsecureUserException if the user does not have a lock screen set. * @throws IOException if there was an issue with local database update. * @throws RemoteException if there was an issue communicating with {@link IGateKeeperService}. * * @hide */ @VisibleForTesting void regenerate(int userId) throws NoSuchAlgorithmException, KeyStoreException, InsecureUserException, IOException { throws NoSuchAlgorithmException, KeyStoreException, InsecureUserException, IOException, RemoteException { if (!isAvailable(userId)) { throw new InsecureUserException(String.format( Locale.US, "%d does not have a lock screen set.", userId)); Loading @@ -190,11 +204,13 @@ public class PlatformKeyManager { * @throws NoSuchAlgorithmException if AES is unavailable - should never occur. * @throws InsecureUserException if the user does not have a lock screen set. * @throws IOException if there was an issue with local database update. * @throws RemoteException if there was an issue communicating with {@link IGateKeeperService}. * * @hide */ public PlatformEncryptionKey getEncryptKey(int userId) throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException, IOException { public PlatformEncryptionKey getEncryptKey(int userId) throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException, IOException, RemoteException { init(userId); try { // Try to see if the decryption key is still accessible before using the encryption key. Loading Loading @@ -243,14 +259,18 @@ public class PlatformKeyManager { * @throws NoSuchAlgorithmException if AES is unavailable - should never occur. * @throws InsecureUserException if the user does not have a lock screen set. * @throws IOException if there was an issue with local database update. * @throws RemoteException if there was an issue communicating with {@link IGateKeeperService}. * * @hide */ public PlatformDecryptionKey getDecryptKey(int userId) throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException, IOException { public PlatformDecryptionKey getDecryptKey(int userId) throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException, IOException, RemoteException { init(userId); try { return getDecryptKeyInternal(userId); PlatformDecryptionKey decryptionKey = getDecryptKeyInternal(userId); ensureDecryptionKeyIsValid(userId, decryptionKey); return decryptionKey; } catch (UnrecoverableKeyException e) { Log.i(TAG, String.format(Locale.US, "Regenerating permanently invalid Platform key for user %d.", Loading Loading @@ -283,6 +303,29 @@ public class PlatformKeyManager { return new PlatformDecryptionKey(generationId, key); } /** * Tries to use the decryption key to make sure it is not permanently invalidated. The exception * {@code KeyPermanentlyInvalidatedException} is thrown only when the key is in use. * * <p>Note that we ignore all other InvalidKeyException exceptions, because such an exception * may be thrown for auth-bound keys if there's no recent unlock event. */ private void ensureDecryptionKeyIsValid(int userId, PlatformDecryptionKey decryptionKey) throws UnrecoverableKeyException { try { Cipher.getInstance(KEY_WRAP_CIPHER_ALGORITHM).init(Cipher.UNWRAP_MODE, decryptionKey.getKey(), new GCMParameterSpec(GCM_TAG_LENGTH_BITS, GCM_INSECURE_NONCE_BYTES)); } catch (KeyPermanentlyInvalidatedException e) { Log.e(TAG, String.format(Locale.US, "The platform key for user %d became invalid.", userId)); throw new UnrecoverableKeyException(e.getMessage()); } catch (NoSuchAlgorithmException | InvalidKeyException | InvalidAlgorithmParameterException | NoSuchPaddingException e) { // Ignore all other exceptions } } /** * Initializes the class. If there is no current platform key, and the user has a lock screen * set, will create the platform key and set the generation ID. Loading @@ -291,11 +334,13 @@ public class PlatformKeyManager { * @throws KeyStoreException if there was an error in AndroidKeyStore. * @throws NoSuchAlgorithmException if AES is unavailable - should never happen. * @throws IOException if there was an issue with local database update. * @throws RemoteException if there was an issue communicating with {@link IGateKeeperService}. * * @hide */ void init(int userId) throws KeyStoreException, NoSuchAlgorithmException, InsecureUserException, IOException { throws KeyStoreException, NoSuchAlgorithmException, InsecureUserException, IOException, RemoteException { if (!isAvailable(userId)) { throw new InsecureUserException(String.format( Locale.US, "%d does not have a lock screen set.", userId)); Loading Loading @@ -372,6 +417,11 @@ public class PlatformKeyManager { && mKeyStore.containsAlias(getDecryptAlias(userId, generationId)); } @VisibleForTesting IGateKeeperService getGateKeeperService() { return GateKeeper.getService(); } /** * Generates a new 256-bit AES key, and loads it into AndroidKeyStore with the given * {@code generationId} determining its aliases. Loading @@ -380,15 +430,23 @@ public class PlatformKeyManager { * available since API version 1. * @throws KeyStoreException if there was an issue loading the keys into AndroidKeyStore. * @throws IOException if there was an issue with local database update. * @throws RemoteException if there was an issue communicating with {@link IGateKeeperService}. */ private void generateAndLoadKey(int userId, int generationId) throws NoSuchAlgorithmException, KeyStoreException, IOException { throws NoSuchAlgorithmException, KeyStoreException, IOException, RemoteException { String encryptAlias = getEncryptAlias(userId, generationId); String decryptAlias = getDecryptAlias(userId, generationId); // SecretKey implementation doesn't provide reliable way to destroy the secret // so it may live in memory for some time. SecretKey secretKey = generateAesKey(); long secureUserId = getGateKeeperService().getSecureUserId(userId); // TODO(b/124095438): Propagate this failure instead of silently failing. if (secureUserId == GateKeeper.INVALID_SECURE_USER_ID) { Log.e(TAG, "No SID available for user " + userId); return; } // Store decryption key first since it is more likely to fail. mKeyStore.setEntry( decryptAlias, Loading @@ -399,7 +457,7 @@ public class PlatformKeyManager { USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS) .setBlockModes(KeyProperties.BLOCK_MODE_GCM) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) .setBoundToSpecificSecureUserId(userId) .setBoundToSpecificSecureUserId(secureUserId) .build()); mKeyStore.setEntry( encryptAlias, Loading
services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java +83 −4 Original line number Diff line number Diff line Loading @@ -24,14 +24,21 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.expectThrows; import android.app.KeyguardManager; import android.content.Context; import android.os.RemoteException; import android.security.GateKeeper; import android.security.keystore.AndroidKeyStoreSecretKey; import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.KeyProperties; import android.security.keystore.KeyProtection; import android.service.gatekeeper.IGateKeeperService; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; Loading @@ -53,6 +60,8 @@ import java.security.KeyStore; import java.security.UnrecoverableKeyException; import java.util.List; import javax.crypto.KeyGenerator; @SmallTest @RunWith(AndroidJUnit4.class) public class PlatformKeyManagerTest { Loading @@ -60,10 +69,15 @@ public class PlatformKeyManagerTest { private static final String DATABASE_FILE_NAME = "recoverablekeystore.db"; private static final int USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS = 15; private static final int USER_ID_FIXTURE = 42; private static final long USER_SID = 4200L; private static final String KEY_ALGORITHM = "AES"; private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore"; private static final String TESTING_KEYSTORE_KEY_ALIAS = "testing-key-store-key-alias"; @Mock private Context mContext; @Mock private KeyStoreProxy mKeyStoreProxy; @Mock private KeyguardManager mKeyguardManager; @Mock private IGateKeeperService mGateKeeperService; @Captor private ArgumentCaptor<KeyStore.ProtectionParameter> mProtectionParameterCaptor; @Captor private ArgumentCaptor<KeyStore.Entry> mEntryArgumentCaptor; Loading @@ -74,18 +88,19 @@ public class PlatformKeyManagerTest { private PlatformKeyManager mPlatformKeyManager; @Before public void setUp() { public void setUp() throws Exception { MockitoAnnotations.initMocks(this); Context context = InstrumentationRegistry.getTargetContext(); mDatabaseFile = context.getDatabasePath(DATABASE_FILE_NAME); mRecoverableKeyStoreDb = RecoverableKeyStoreDb.newInstance(context); mPlatformKeyManager = new PlatformKeyManager( mContext, mKeyStoreProxy, mRecoverableKeyStoreDb); mPlatformKeyManager = new PlatformKeyManagerTestable( mContext, mKeyStoreProxy, mRecoverableKeyStoreDb, mGateKeeperService); when(mContext.getSystemService(anyString())).thenReturn(mKeyguardManager); when(mContext.getSystemServiceName(any())).thenReturn("test"); when(mKeyguardManager.isDeviceSecure(USER_ID_FIXTURE)).thenReturn(true); when(mGateKeeperService.getSecureUserId(USER_ID_FIXTURE)).thenReturn(USER_SID); } @After Loading Loading @@ -192,10 +207,35 @@ public class PlatformKeyManagerTest { mPlatformKeyManager.init(USER_ID_FIXTURE); assertEquals( USER_ID_FIXTURE, USER_SID, getDecryptKeyProtection().getBoundToSpecificSecureUserId()); } @Test public void init_doesNotCreateDecryptKeyIfNoSid() throws Exception { when(mGateKeeperService.getSecureUserId(USER_ID_FIXTURE)) .thenReturn(GateKeeper.INVALID_SECURE_USER_ID); mPlatformKeyManager.init(USER_ID_FIXTURE); verify(mKeyStoreProxy, never()).setEntry( eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"), any(), any()); } @Test public void init_doesNotCreateDecryptKeyOnGateKeeperException() throws Exception { when(mGateKeeperService.getSecureUserId(USER_ID_FIXTURE)).thenThrow(new RemoteException()); expectThrows(RemoteException.class, () -> mPlatformKeyManager.init(USER_ID_FIXTURE)); verify(mKeyStoreProxy, never()).setEntry( eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"), any(), any()); } @Test public void init_createsBothKeysWithSameMaterial() throws Exception { mPlatformKeyManager.init(USER_ID_FIXTURE); Loading Loading @@ -259,6 +299,9 @@ public class PlatformKeyManagerTest { when(mKeyStoreProxy .containsAlias("com.android.server.locksettings.recoverablekeystore/" + "platform/42/1/encrypt")).thenReturn(true); when(mKeyStoreProxy.getKey( eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"), any())).thenReturn(generateAndroidKeyStoreKey()); mPlatformKeyManager.getDecryptKey(USER_ID_FIXTURE); Loading @@ -281,6 +324,9 @@ public class PlatformKeyManagerTest { when(mKeyStoreProxy .containsAlias("com.android.server.locksettings.recoverablekeystore/" + "platform/42/2/decrypt")).thenReturn(true); when(mKeyStoreProxy.getKey( eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"), any())).thenReturn(generateAndroidKeyStoreKey()); mPlatformKeyManager.getDecryptKey(USER_ID_FIXTURE); Loading Loading @@ -352,6 +398,9 @@ public class PlatformKeyManagerTest { doThrow(new UnrecoverableKeyException()).when(mKeyStoreProxy).getKey( eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"), any()); when(mKeyStoreProxy.getKey( eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"), any())).thenReturn(generateAndroidKeyStoreKey()); when(mKeyStoreProxy .containsAlias("com.android.server.locksettings.recoverablekeystore/" Loading Loading @@ -536,4 +585,34 @@ public class PlatformKeyManagerTest { mProtectionParameterCaptor.capture()); return (KeyProtection) mProtectionParameterCaptor.getValue(); } private AndroidKeyStoreSecretKey generateAndroidKeyStoreKey() throws Exception { KeyGenerator keyGenerator = KeyGenerator.getInstance( KEY_ALGORITHM, ANDROID_KEY_STORE_PROVIDER); keyGenerator.init(new KeyGenParameterSpec.Builder(TESTING_KEYSTORE_KEY_ALIAS, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) .setBlockModes(KeyProperties.BLOCK_MODE_GCM) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) .build()); return (AndroidKeyStoreSecretKey) keyGenerator.generateKey(); } class PlatformKeyManagerTestable extends PlatformKeyManager { private IGateKeeperService mGateKeeperService; PlatformKeyManagerTestable( Context context, KeyStoreProxy keyStoreProxy, RecoverableKeyStoreDb database, IGateKeeperService gateKeeperService) { super(context, keyStoreProxy, database); mGateKeeperService = gateKeeperService; } @Override IGateKeeperService getGateKeeperService() { return mGateKeeperService; } } }