Merge "LockSettingsService: migrate to its own keystore namespace"

This commit is contained in:
Treehugger Robot
2021-04-29 22:35:52 +00:00
committed by Gerrit Code Review
5 changed files with 151 additions and 47 deletions

View File

@@ -29,6 +29,8 @@ import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSW
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN;
import static com.android.internal.widget.LockPatternUtils.EscrowTokenStateChangeCallback;
import static com.android.internal.widget.LockPatternUtils.PROFILE_KEY_NAME_DECRYPT;
import static com.android.internal.widget.LockPatternUtils.PROFILE_KEY_NAME_ENCRYPT;
import static com.android.internal.widget.LockPatternUtils.SYNTHETIC_PASSWORD_HANDLE_KEY;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE;
@@ -99,6 +101,7 @@ import android.security.keystore.recovery.KeyChainProtectionParams;
import android.security.keystore.recovery.KeyChainSnapshot;
import android.security.keystore.recovery.RecoveryCertPath;
import android.security.keystore.recovery.WrappedApplicationKey;
import android.security.keystore2.AndroidKeyStoreLoadStoreParameter;
import android.security.keystore2.AndroidKeyStoreProvider;
import android.service.gatekeeper.GateKeeperResponse;
import android.service.gatekeeper.IGateKeeperService;
@@ -153,6 +156,7 @@ import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
@@ -225,6 +229,7 @@ public class LockSettingsService extends ILockSettings.Stub {
private final SyntheticPasswordManager mSpManager;
private final KeyStore mKeyStore;
private final java.security.KeyStore mJavaKeyStore;
private final RecoverableKeyStoreManager mRecoverableKeyStoreManager;
private ManagedProfilePasswordCache mManagedProfilePasswordCache;
@@ -543,16 +548,22 @@ public class LockSettingsService extends ILockSettings.Stub {
return Settings.Secure.getIntForUser(contentResolver, keyName, defaultValue, userId);
}
public @NonNull ManagedProfilePasswordCache getManagedProfilePasswordCache() {
public java.security.KeyStore getJavaKeyStore() {
try {
java.security.KeyStore ks = java.security.KeyStore.getInstance(
SyntheticPasswordCrypto.androidKeystoreProviderName());
ks.load(null);
return new ManagedProfilePasswordCache(ks, getUserManager());
ks.load(new AndroidKeyStoreLoadStoreParameter(
SyntheticPasswordCrypto.keyNamespace()));
return ks;
} catch (Exception e) {
throw new IllegalStateException("Cannot load keystore", e);
}
}
public @NonNull ManagedProfilePasswordCache getManagedProfilePasswordCache(
java.security.KeyStore ks) {
return new ManagedProfilePasswordCache(ks, getUserManager());
}
}
public LockSettingsService(Context context) {
@@ -564,6 +575,7 @@ public class LockSettingsService extends ILockSettings.Stub {
mInjector = injector;
mContext = injector.getContext();
mKeyStore = injector.getKeyStore();
mJavaKeyStore = injector.getJavaKeyStore();
mRecoverableKeyStoreManager = injector.getRecoverableKeyStoreManager();
mHandler = injector.getHandler(injector.getServiceThread());
mStrongAuth = injector.getStrongAuth();
@@ -586,7 +598,7 @@ public class LockSettingsService extends ILockSettings.Stub {
mStrongAuthTracker.register(mStrongAuth);
mSpManager = injector.getSyntheticPasswordManager(mStorage);
mManagedProfilePasswordCache = injector.getManagedProfilePasswordCache();
mManagedProfilePasswordCache = injector.getManagedProfilePasswordCache(mJavaKeyStore);
mRebootEscrowManager = injector.getRebootEscrowManager(new RebootEscrowCallbacks(),
mStorage);
@@ -959,6 +971,21 @@ public class LockSettingsService extends ILockSettings.Stub {
setString("migrated_wear_lockscreen_disabled", "true", 0);
Slog.i(TAG, "Migrated lockscreen_disabled for Wear devices");
}
if (getString("migrated_keystore_namespace", null, 0) == null) {
boolean success = true;
synchronized (mSpManager) {
success &= mSpManager.migrateKeyNamespace();
}
success &= migrateProfileLockKeys();
if (success) {
setString("migrated_keystore_namespace", "true", 0);
Slog.i(TAG, "Migrated keys to LSS namespace");
} else {
Slog.w(TAG, "Failed to migrate keys to LSS namespace");
}
}
}
private void migrateOldDataAfterSystemReady() {
@@ -999,6 +1026,22 @@ public class LockSettingsService extends ILockSettings.Stub {
}
}
private boolean migrateProfileLockKeys() {
boolean success = true;
final List<UserInfo> users = mUserManager.getUsers();
final int userCount = users.size();
for (int i = 0; i < userCount; i++) {
UserInfo user = users.get(i);
if (user.isManagedProfile() && !getSeparateProfileChallengeEnabledInternal(user.id)) {
success &= SyntheticPasswordCrypto.migrateLockSettingsKey(
PROFILE_KEY_NAME_ENCRYPT + user.id);
success &= SyntheticPasswordCrypto.migrateLockSettingsKey(
PROFILE_KEY_NAME_DECRYPT + user.id);
}
}
return success;
}
/**
* Returns the lowest password quality that still presents the same UI for entering it.
*
@@ -1284,11 +1327,8 @@ public class LockSettingsService extends ILockSettings.Stub {
byte[] encryptedPassword = Arrays.copyOfRange(storedData, PROFILE_KEY_IV_SIZE,
storedData.length);
byte[] decryptionResult;
java.security.KeyStore keyStore = java.security.KeyStore.getInstance(
SyntheticPasswordCrypto.androidKeystoreProviderName());
keyStore.load(null);
SecretKey decryptionKey = (SecretKey) keyStore.getKey(
LockPatternUtils.PROFILE_KEY_NAME_DECRYPT + userId, null);
SecretKey decryptionKey = (SecretKey) mJavaKeyStore.getKey(
PROFILE_KEY_NAME_DECRYPT + userId, null);
Cipher cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
+ KeyProperties.BLOCK_MODE_GCM + "/" + KeyProperties.ENCRYPTION_PADDING_NONE);
@@ -1744,30 +1784,26 @@ public class LockSettingsService extends ILockSettings.Stub {
KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES);
keyGenerator.init(new SecureRandom());
SecretKey secretKey = keyGenerator.generateKey();
java.security.KeyStore keyStore = java.security.KeyStore.getInstance(
SyntheticPasswordCrypto.androidKeystoreProviderName());
keyStore.load(null);
try {
keyStore.setEntry(
LockPatternUtils.PROFILE_KEY_NAME_ENCRYPT + userId,
mJavaKeyStore.setEntry(
PROFILE_KEY_NAME_ENCRYPT + userId,
new java.security.KeyStore.SecretKeyEntry(secretKey),
new KeyProtection.Builder(KeyProperties.PURPOSE_ENCRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.build());
keyStore.setEntry(
LockPatternUtils.PROFILE_KEY_NAME_DECRYPT + userId,
mJavaKeyStore.setEntry(
PROFILE_KEY_NAME_DECRYPT + userId,
new java.security.KeyStore.SecretKeyEntry(secretKey),
new KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setUserAuthenticationRequired(true)
.setUserAuthenticationValidityDurationSeconds(30)
.setCriticalToDeviceEncryption(true)
.build());
// Key imported, obtain a reference to it.
SecretKey keyStoreEncryptionKey = (SecretKey) keyStore.getKey(
LockPatternUtils.PROFILE_KEY_NAME_ENCRYPT + userId, null);
SecretKey keyStoreEncryptionKey = (SecretKey) mJavaKeyStore.getKey(
PROFILE_KEY_NAME_ENCRYPT + userId, null);
Cipher cipher = Cipher.getInstance(
KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_GCM + "/"
+ KeyProperties.ENCRYPTION_PADDING_NONE);
@@ -1776,10 +1812,10 @@ public class LockSettingsService extends ILockSettings.Stub {
iv = cipher.getIV();
} finally {
// The original key can now be discarded.
keyStore.deleteEntry(LockPatternUtils.PROFILE_KEY_NAME_ENCRYPT + userId);
mJavaKeyStore.deleteEntry(PROFILE_KEY_NAME_ENCRYPT + userId);
}
} catch (CertificateException | UnrecoverableKeyException
| IOException | BadPaddingException | IllegalBlockSizeException | KeyStoreException
} catch (UnrecoverableKeyException
| BadPaddingException | IllegalBlockSizeException | KeyStoreException
| NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException e) {
throw new IllegalStateException("Failed to encrypt key", e);
}
@@ -2300,13 +2336,9 @@ public class LockSettingsService extends ILockSettings.Stub {
private void removeKeystoreProfileKey(int targetUserId) {
Slog.i(TAG, "Remove keystore profile key for user: " + targetUserId);
try {
java.security.KeyStore keyStore = java.security.KeyStore.getInstance(
SyntheticPasswordCrypto.androidKeystoreProviderName());
keyStore.load(null);
keyStore.deleteEntry(LockPatternUtils.PROFILE_KEY_NAME_ENCRYPT + targetUserId);
keyStore.deleteEntry(LockPatternUtils.PROFILE_KEY_NAME_DECRYPT + targetUserId);
} catch (KeyStoreException | NoSuchAlgorithmException | CertificateException
| IOException e) {
mJavaKeyStore.deleteEntry(PROFILE_KEY_NAME_ENCRYPT + targetUserId);
mJavaKeyStore.deleteEntry(PROFILE_KEY_NAME_DECRYPT + targetUserId);
} catch (KeyStoreException e) {
// We have tried our best to remove all keys
Slog.e(TAG, "Unable to remove keystore profile key for user:" + targetUserId, e);
}
@@ -3257,6 +3289,12 @@ public class LockSettingsService extends ILockSettings.Stub {
pw.println();
pw.decreaseIndent();
pw.println("Keys in namespace:");
pw.increaseIndent();
dumpKeystoreKeys(pw);
pw.println();
pw.decreaseIndent();
pw.println("Storage:");
pw.increaseIndent();
mStorage.dump(pw);
@@ -3276,6 +3314,18 @@ public class LockSettingsService extends ILockSettings.Stub {
pw.decreaseIndent();
}
private void dumpKeystoreKeys(IndentingPrintWriter pw) {
try {
final Enumeration<String> aliases = mJavaKeyStore.aliases();
while (aliases.hasMoreElements()) {
pw.println(aliases.nextElement());
}
} catch (KeyStoreException e) {
pw.println("Unable to get keys: " + e.toString());
Slog.d(TAG, "Dump error", e);
}
}
/**
* Cryptographically disable escrow token support for the current user, if the user is not
* managed (either user has a profile owner, or if device is managed). Do not disable

View File

@@ -23,7 +23,6 @@ import android.os.UserManager;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.security.keystore.UserNotAuthenticatedException;
import android.security.keystore2.AndroidKeyStoreSpi;
import android.util.Slog;
import android.util.SparseArray;
@@ -95,11 +94,12 @@ public class ManagedProfilePasswordCache {
SecretKey key;
try {
generator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES,
AndroidKeyStoreSpi.NAME);
mKeyStore.getProvider());
generator.init(new KeyGenParameterSpec.Builder(
keyName, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setKeySize(KEY_LENGTH)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setNamespace(SyntheticPasswordCrypto.keyNamespace())
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
// Generate auth-bound key to user 0 (since we the caller is user 0)
.setUserAuthenticationRequired(true)

View File

@@ -16,8 +16,12 @@
package com.android.server.locksettings;
import android.security.AndroidKeyStoreMaintenance;
import android.security.keystore.KeyProperties;
import android.security.keystore.KeyProtection;
import android.security.keystore2.AndroidKeyStoreLoadStoreParameter;
import android.system.keystore2.Domain;
import android.system.keystore2.KeyDescriptor;
import android.util.Slog;
import java.io.ByteArrayOutputStream;
@@ -125,9 +129,7 @@ public class SyntheticPasswordCrypto {
public static byte[] decryptBlobV1(String keyAlias, byte[] blob, byte[] applicationId) {
try {
KeyStore keyStore = KeyStore.getInstance(androidKeystoreProviderName());
keyStore.load(null);
KeyStore keyStore = getKeyStore();
SecretKey decryptionKey = (SecretKey) keyStore.getKey(keyAlias, null);
if (decryptionKey == null) {
throw new IllegalStateException("SP key is missing: " + keyAlias);
@@ -144,10 +146,20 @@ public class SyntheticPasswordCrypto {
return "AndroidKeyStore";
}
static int keyNamespace() {
return KeyProperties.NAMESPACE_LOCKSETTINGS;
}
private static KeyStore getKeyStore()
throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException {
KeyStore keyStore = KeyStore.getInstance(androidKeystoreProviderName());
keyStore.load(new AndroidKeyStoreLoadStoreParameter(keyNamespace()));
return keyStore;
}
public static byte[] decryptBlob(String keyAlias, byte[] blob, byte[] applicationId) {
try {
KeyStore keyStore = KeyStore.getInstance(androidKeystoreProviderName());
keyStore.load(null);
final KeyStore keyStore = getKeyStore();
SecretKey decryptionKey = (SecretKey) keyStore.getKey(keyAlias, null);
if (decryptionKey == null) {
@@ -170,8 +182,7 @@ public class SyntheticPasswordCrypto {
KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES);
keyGenerator.init(AES_KEY_LENGTH * 8, new SecureRandom());
SecretKey secretKey = keyGenerator.generateKey();
KeyStore keyStore = KeyStore.getInstance(androidKeystoreProviderName());
keyStore.load(null);
final KeyStore keyStore = getKeyStore();
KeyProtection.Builder builder = new KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
@@ -200,8 +211,7 @@ public class SyntheticPasswordCrypto {
public static void destroyBlobKey(String keyAlias) {
KeyStore keyStore;
try {
keyStore = KeyStore.getInstance(androidKeystoreProviderName());
keyStore.load(null);
keyStore = getKeyStore();
keyStore.deleteEntry(keyAlias);
Slog.i(TAG, "SP key deleted: " + keyAlias);
} catch (KeyStoreException | NoSuchAlgorithmException | CertificateException
@@ -229,4 +239,32 @@ public class SyntheticPasswordCrypto {
throw new IllegalStateException("NoSuchAlgorithmException for SHA-512", e);
}
}
static boolean migrateLockSettingsKey(String alias) {
final KeyDescriptor legacyKey = new KeyDescriptor();
legacyKey.domain = Domain.APP;
legacyKey.nspace = KeyProperties.NAMESPACE_APPLICATION;
legacyKey.alias = alias;
final KeyDescriptor newKey = new KeyDescriptor();
newKey.domain = Domain.SELINUX;
newKey.nspace = SyntheticPasswordCrypto.keyNamespace();
newKey.alias = alias;
Slog.i(TAG, "Migrating key " + alias);
int err = AndroidKeyStoreMaintenance.migrateKeyNamespace(legacyKey, newKey);
if (err == 0) {
return true;
} else if (err == AndroidKeyStoreMaintenance.KEY_NOT_FOUND) {
Slog.i(TAG, "Key does not exist");
// Treat this as a success so we don't migrate again.
return true;
} else if (err == AndroidKeyStoreMaintenance.INVALID_ARGUMENT) {
Slog.i(TAG, "Key already exists");
// Treat this as a success so we don't migrate again.
return true;
} else {
Slog.e(TAG, String.format("Failed to migrate key: %d", err));
return false;
}
}
}

View File

@@ -519,7 +519,7 @@ public class SyntheticPasswordManager {
public void removeUser(int userId) {
for (long handle : mStorage.listSyntheticPasswordHandlesForUser(SP_BLOB_NAME, userId)) {
destroyWeaverSlot(handle, userId);
destroySPBlobKey(getHandleName(handle));
destroySPBlobKey(getKeyName(handle));
}
}
@@ -955,7 +955,7 @@ public class SyntheticPasswordManager {
} else {
secret = authToken.getSyntheticPassword();
}
byte[] content = createSPBlob(getHandleName(handle), secret, applicationId, sid);
byte[] content = createSPBlob(getKeyName(handle), secret, applicationId, sid);
byte[] blob = new byte[content.length + 1 + 1];
/*
* We can upgrade from v1 to v2 because that's just a change in the way that
@@ -1137,10 +1137,10 @@ public class SyntheticPasswordManager {
}
final byte[] secret;
if (version == SYNTHETIC_PASSWORD_VERSION_V1) {
secret = SyntheticPasswordCrypto.decryptBlobV1(getHandleName(handle),
secret = SyntheticPasswordCrypto.decryptBlobV1(getKeyName(handle),
Arrays.copyOfRange(blob, 2, blob.length), applicationId);
} else {
secret = decryptSPBlob(getHandleName(handle),
secret = decryptSPBlob(getKeyName(handle),
Arrays.copyOfRange(blob, 2, blob.length), applicationId);
}
if (secret == null) {
@@ -1235,7 +1235,7 @@ public class SyntheticPasswordManager {
private void destroySyntheticPassword(long handle, int userId) {
destroyState(SP_BLOB_NAME, handle, userId);
destroySPBlobKey(getHandleName(handle));
destroySPBlobKey(getKeyName(handle));
if (hasState(WEAVER_SLOT_NAME, handle, userId)) {
destroyWeaverSlot(handle, userId);
}
@@ -1351,7 +1351,7 @@ public class SyntheticPasswordManager {
}
}
private String getHandleName(long handle) {
private String getKeyName(long handle) {
return String.format("%s%x", LockPatternUtils.SYNTHETIC_PASSWORD_KEY_PREFIX, handle);
}
@@ -1412,4 +1412,19 @@ public class SyntheticPasswordManager {
}
return hexBytes;
}
/**
* Migrate all existing SP keystore keys from uid 1000 app domain to LSS selinux domain
*/
public boolean migrateKeyNamespace() {
boolean success = true;
final Map<Integer, List<Long>> allHandles =
mStorage.listSyntheticPasswordHandlesForAllUsers(SP_BLOB_NAME);
for (List<Long> userHandles : allHandles.values()) {
for (long handle : userHandles) {
success &= SyntheticPasswordCrypto.migrateLockSettingsKey(getKeyName(handle));
}
}
return success;
}
}

View File

@@ -155,7 +155,8 @@ public class LockSettingsServiceTestable extends LockSettingsService {
}
@Override
public ManagedProfilePasswordCache getManagedProfilePasswordCache() {
public ManagedProfilePasswordCache getManagedProfilePasswordCache(
java.security.KeyStore ks) {
return mock(ManagedProfilePasswordCache.class);
}