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:
@@ -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
|
||||||
|
@@ -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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user