Keystore 2.0: Update credential settings to use public Keystore API.

Test: N/A
Bug: 171305607
Bug: 171305388
Change-Id: I377115aca6b2df8052ae118f986c2f713535b6ec
This commit is contained in:
Janis Danisevskis
2021-01-25 14:54:48 -08:00
parent f69f3ed7dc
commit a05bd65cb6
3 changed files with 114 additions and 63 deletions

View File

@@ -34,9 +34,9 @@ import android.security.Credentials;
import android.security.IKeyChainService; import android.security.IKeyChainService;
import android.security.KeyChain; import android.security.KeyChain;
import android.security.KeyChain.KeyChainConnection; import android.security.KeyChain.KeyChainConnection;
import android.security.KeyStore; import android.security.keystore.AndroidKeyStoreProvider;
import android.security.keymaster.KeyCharacteristics; import android.security.keystore.KeyProperties;
import android.security.keymaster.KeymasterDefs; import android.security.keystore2.AndroidKeyStoreLoadStoreParameter;
import android.util.Log; import android.util.Log;
import android.util.SparseArray; import android.util.SparseArray;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@@ -55,13 +55,21 @@ import com.android.settingslib.RestrictedLockUtils;
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
import com.android.settingslib.RestrictedLockUtilsInternal; import com.android.settingslib.RestrictedLockUtilsInternal;
import java.security.Key;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException; import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.Enumeration;
import java.util.List; import java.util.List;
import java.util.SortedMap; import java.util.SortedMap;
import java.util.TreeMap; import java.util.TreeMap;
import javax.crypto.SecretKey;
public class UserCredentialsSettings extends SettingsPreferenceFragment public class UserCredentialsSettings extends SettingsPreferenceFragment
implements View.OnClickListener { implements View.OnClickListener {
private static final String TAG = "UserCredentialsSettings"; private static final String TAG = "UserCredentialsSettings";
@@ -201,21 +209,19 @@ public class UserCredentialsSettings extends SettingsPreferenceFragment
} }
private void deleteWifiCredential(final Credential credential) { private void deleteWifiCredential(final Credential credential) {
final KeyStore keyStore = KeyStore.getInstance(); try {
final EnumSet<Credential.Type> storedTypes = credential.getStoredTypes(); KeyStore keyStore = null;
if (AndroidKeyStoreProvider.isKeystore2Enabled()) {
// Remove all Wi-Fi credentials keyStore = KeyStore.getInstance("AndroidKeyStore");
if (storedTypes.contains(Credential.Type.USER_KEY)) { keyStore.load(
keyStore.delete(Credentials.USER_PRIVATE_KEY + credential.getAlias(), new AndroidKeyStoreLoadStoreParameter(
Process.WIFI_UID); KeyProperties.NAMESPACE_WIFI));
} } else {
if (storedTypes.contains(Credential.Type.USER_CERTIFICATE)) { keyStore = AndroidKeyStoreProvider.getKeyStoreForUid(Process.WIFI_UID);
keyStore.delete(Credentials.USER_CERTIFICATE + credential.getAlias(), }
Process.WIFI_UID); keyStore.deleteEntry(credential.getAlias());
} } catch (Exception e) {
if (storedTypes.contains(Credential.Type.CA_CERTIFICATE)) { throw new RuntimeException("Failed to delete keys from keystore.");
keyStore.delete(Credentials.CA_CERTIFICATE + credential.getAlias(),
Process.WIFI_UID);
} }
} }
@@ -266,73 +272,103 @@ public class UserCredentialsSettings extends SettingsPreferenceFragment
*/ */
@Override @Override
protected List<Credential> doInBackground(Void... params) { protected List<Credential> doInBackground(Void... params) {
final KeyStore keyStore = KeyStore.getInstance();
// Certificates can be installed into SYSTEM_UID or WIFI_UID through CertInstaller. // Certificates can be installed into SYSTEM_UID or WIFI_UID through CertInstaller.
final int myUserId = UserHandle.myUserId(); final int myUserId = UserHandle.myUserId();
final int systemUid = UserHandle.getUid(myUserId, Process.SYSTEM_UID); final int systemUid = UserHandle.getUid(myUserId, Process.SYSTEM_UID);
final int wifiUid = UserHandle.getUid(myUserId, Process.WIFI_UID); final int wifiUid = UserHandle.getUid(myUserId, Process.WIFI_UID);
List<Credential> credentials = new ArrayList<>(); try {
credentials.addAll(getCredentialsForUid(keyStore, systemUid).values()); KeyStore processKeystore = KeyStore.getInstance("AndroidKeyStore");
credentials.addAll(getCredentialsForUid(keyStore, wifiUid).values()); processKeystore.load(null);
return credentials; KeyStore wifiKeystore = null;
} if (myUserId == 0) {
// Only the primary user may see wifi configurations.
if (AndroidKeyStoreProvider.isKeystore2Enabled()) {
wifiKeystore = KeyStore.getInstance("AndroidKeyStore");
wifiKeystore.load(new AndroidKeyStoreLoadStoreParameter(
KeyProperties.NAMESPACE_WIFI));
} else {
wifiKeystore = AndroidKeyStoreProvider.getKeyStoreForUid(Process.WIFI_UID);
}
}
private boolean isAsymmetric(KeyStore keyStore, String alias, int uid) List<Credential> credentials = new ArrayList<>();
throws UnrecoverableKeyException { credentials.addAll(getCredentialsForUid(processKeystore, systemUid).values());
KeyCharacteristics keyCharacteristics = new KeyCharacteristics(); if (wifiKeystore != null) {
int errorCode = keyStore.getKeyCharacteristics(alias, null, null, uid, credentials.addAll(getCredentialsForUid(wifiKeystore, wifiUid).values());
keyCharacteristics);
if (errorCode != KeyStore.NO_ERROR) {
throw (UnrecoverableKeyException)
new UnrecoverableKeyException("Failed to obtain information about key")
.initCause(KeyStore.getKeyStoreException(errorCode));
} }
Integer keymasterAlgorithm = keyCharacteristics.getEnum( return credentials;
KeymasterDefs.KM_TAG_ALGORITHM); } catch (Exception e) {
if (keymasterAlgorithm == null) { throw new RuntimeException("Failed to load credentials from Keystore.", e);
throw new UnrecoverableKeyException("Key algorithm unknown"); }
}
return keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_RSA ||
keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_EC;
} }
private SortedMap<String, Credential> getCredentialsForUid(KeyStore keyStore, int uid) { private SortedMap<String, Credential> getCredentialsForUid(KeyStore keyStore, int uid) {
final SortedMap<String, Credential> aliasMap = new TreeMap<>(); try {
for (final Credential.Type type : Credential.Type.values()) { final SortedMap<String, Credential> aliasMap = new TreeMap<>();
for (final String prefix : type.prefix) { boolean isSystem = UserHandle.getAppId(uid) == Process.SYSTEM_UID;
for (final String alias : keyStore.list(prefix, uid)) { Enumeration<String> aliases = keyStore.aliases();
if (UserHandle.getAppId(uid) == Process.SYSTEM_UID) { while (aliases.hasMoreElements()) {
String alias = aliases.nextElement();
Credential c = new Credential(alias, uid);
Key key = null;
try {
key = keyStore.getKey(alias, null);
} catch (NoSuchAlgorithmException | UnrecoverableKeyException e) {
Log.e(TAG, "Error tying to retrieve key: " + alias, e);
continue;
}
if (key != null) {
// So we have a key
if (key instanceof SecretKey) {
// We don't display any symmetric key entries.
continue;
}
if (isSystem) {
// Do not show work profile keys in user credentials // Do not show work profile keys in user credentials
if (alias.startsWith(LockPatternUtils.PROFILE_KEY_NAME_ENCRYPT) || if (alias.startsWith(LockPatternUtils.PROFILE_KEY_NAME_ENCRYPT) ||
alias.startsWith(LockPatternUtils.PROFILE_KEY_NAME_DECRYPT)) { alias.startsWith(LockPatternUtils.PROFILE_KEY_NAME_DECRYPT)) {
continue; continue;
} }
// Do not show synthetic password keys in user credential // Do not show synthetic password keys in user credential
// We should never reach this point because the synthetic password key
// is symmetric.
if (alias.startsWith(LockPatternUtils.SYNTHETIC_PASSWORD_KEY_PREFIX)) { if (alias.startsWith(LockPatternUtils.SYNTHETIC_PASSWORD_KEY_PREFIX)) {
continue; continue;
} }
} }
try { // At this point we have determined that we have an asymmetric key.
if (type == Credential.Type.USER_KEY && // so we have at least a USER_KEY and USER_CERTIFICATE.
!isAsymmetric(keyStore, prefix + alias, uid)) { c.storedTypes.add(Credential.Type.USER_KEY);
continue;
Certificate[] certs = keyStore.getCertificateChain(alias);
if (certs != null) {
c.storedTypes.add(Credential.Type.USER_CERTIFICATE);
if (certs.length > 1) {
c.storedTypes.add(Credential.Type.CA_CERTIFICATE);
} }
} catch (UnrecoverableKeyException e) {
Log.e(TAG, "Unable to determine algorithm of key: " + prefix + alias, e);
continue;
} }
Credential c = aliasMap.get(alias); } else {
if (c == null) { // So there is no key but we have an alias. This must mean that we have
c = new Credential(alias, uid); // some certificate.
aliasMap.put(alias, c); if (keyStore.isCertificateEntry(alias)) {
c.storedTypes.add(Credential.Type.CA_CERTIFICATE);
} else {
// This is a weired inconsistent case that should not exist.
// Pure trusted certificate entries should be stored in CA_CERTIFICATE,
// but if isCErtificateEntry returns null this means that only the
// USER_CERTIFICATE is populated which should never be the case without
// a private key. It can still be retrieved with
// keystore.getCertificate().
c.storedTypes.add(Credential.Type.USER_CERTIFICATE);
} }
c.storedTypes.add(type);
} }
aliasMap.put(alias, c);
} }
return aliasMap;
} catch (KeyStoreException e) {
throw new RuntimeException("Failed to load credential from Android Keystore.", e);
} }
return aliasMap;
} }
@Override @Override

View File

@@ -63,7 +63,6 @@ public final class CredentialStorage extends FragmentActivity {
private static final int CONFIRM_CLEAR_SYSTEM_CREDENTIAL_REQUEST = 1; private static final int CONFIRM_CLEAR_SYSTEM_CREDENTIAL_REQUEST = 1;
private final KeyStore mKeyStore = KeyStore.getInstance();
private LockPatternUtils mUtils; private LockPatternUtils mUtils;
/** /**

View File

@@ -18,7 +18,6 @@ package com.android.settings.security;
import android.content.Context; import android.content.Context;
import android.os.UserManager; import android.os.UserManager;
import android.security.KeyStore;
import androidx.preference.PreferenceScreen; import androidx.preference.PreferenceScreen;
@@ -27,6 +26,9 @@ import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnResume; import com.android.settingslib.core.lifecycle.events.OnResume;
import java.security.KeyStore;
import java.security.KeyStoreException;
public class ResetCredentialsPreferenceController extends RestrictedEncryptionPreferenceController public class ResetCredentialsPreferenceController extends RestrictedEncryptionPreferenceController
implements LifecycleObserver, OnResume { implements LifecycleObserver, OnResume {
@@ -38,7 +40,13 @@ public class ResetCredentialsPreferenceController extends RestrictedEncryptionPr
public ResetCredentialsPreferenceController(Context context, Lifecycle lifecycle) { public ResetCredentialsPreferenceController(Context context, Lifecycle lifecycle) {
super(context, UserManager.DISALLOW_CONFIG_CREDENTIALS); super(context, UserManager.DISALLOW_CONFIG_CREDENTIALS);
mKeyStore = KeyStore.getInstance(); KeyStore keyStore = null;
try {
keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
} catch (Exception e) {
}
mKeyStore = keyStore;
if (lifecycle != null) { if (lifecycle != null) {
lifecycle.addObserver(this); lifecycle.addObserver(this);
} }
@@ -58,7 +66,15 @@ public class ResetCredentialsPreferenceController extends RestrictedEncryptionPr
@Override @Override
public void onResume() { public void onResume() {
if (mPreference != null && !mPreference.isDisabledByAdmin()) { if (mPreference != null && !mPreference.isDisabledByAdmin()) {
mPreference.setEnabled(!mKeyStore.isEmpty()); boolean isEnabled = false;
try {
if (mKeyStore != null) {
isEnabled = mKeyStore.aliases().hasMoreElements();
}
} catch (KeyStoreException e) {
// If access to keystore fails, treat as disabled.
}
mPreference.setEnabled(isEnabled);
} }
} }
} }