/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.keychain; import static android.app.admin.SecurityLog.TAG_CERT_AUTHORITY_INSTALLED; import static android.app.admin.SecurityLog.TAG_CERT_AUTHORITY_REMOVED; import static android.security.KeyStore.UID_SELF; import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.AppOpsManager; import android.app.BroadcastOptions; import android.app.IntentService; import android.app.admin.SecurityLog; import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.StringParceledListSlice; import android.hardware.security.keymint.ErrorCode; import android.net.Uri; import android.os.Binder; import android.os.Build; import android.os.IBinder; import android.os.Process; import android.os.UserHandle; import android.security.AppUriAuthenticationPolicy; import android.security.CredentialManagementApp; import android.security.IKeyChainService; import android.security.KeyChain; import android.security.KeyStore2; import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.KeyProperties; import android.security.keystore.ParcelableKeyGenParameterSpec; import android.security.keystore.StrongBoxUnavailableException; import android.security.keystore2.AndroidKeyStoreLoadStoreParameter; import android.system.keystore2.Domain; import android.system.keystore2.KeyDescriptor; import android.system.keystore2.KeyPermission; import android.text.TextUtils; import android.util.Base64; import android.util.Log; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; import com.android.internal.widget.LockPatternUtils; import com.android.keychain.internal.ExistingKeysProvider; import com.android.keychain.internal.GrantsDatabase; import com.android.org.conscrypt.TrustedCertificateStore; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.security.InvalidAlgorithmParameterException; import java.security.Key; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.PrivateKey; import java.security.ProviderException; import java.security.UnrecoverableKeyException; import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.security.auth.x500.X500Principal; public class KeyChainService extends IntentService { private static final String TAG = "KeyChain"; private static final String CERT_INSTALLER_PACKAGE = "com.android.certinstaller"; private final Set ALLOWED_UIDS = Collections.unmodifiableSet( new HashSet(Arrays.asList(UID_SELF, Process.WIFI_UID))); private static final String MSG_NOT_SYSTEM = "Not system package"; private static final String MSG_NOT_SYSTEM_OR_CERT_INSTALLER = "Not system or cert installer package"; private static final String MSG_NOT_SYSTEM_OR_CRED_MNG_APP = "Not system or credential management app package"; /** created in onCreate(), closed in onDestroy() */ private GrantsDatabase mGrantsDb; private Injector mInjector; private KeyStore mKeyStore; private KeyChainStateStorage mStateStorage; private Object mCredentialManagementAppLock = new Object(); @Nullable @GuardedBy("mCredentialManagementAppLock") private CredentialManagementApp mCredentialManagementApp; public KeyChainService() { super(KeyChainService.class.getSimpleName()); mInjector = new Injector(); } private KeyStore getKeyStore() { try { final KeyStore keystore = mInjector.getKeyStoreInstance(); keystore.load(null); return keystore; } catch (KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException e) { Log.e(TAG, "Error opening AndroidKeyStore.", e); throw new RuntimeException("Error opening AndroidKeyStore.", e); } } private KeyStore getKeyStore(boolean useWifiNamespace) { if (!useWifiNamespace) { return mKeyStore; } try { final KeyStore keystore = mInjector.getKeyStoreInstance(); keystore.load( new AndroidKeyStoreLoadStoreParameter( KeyProperties.NAMESPACE_WIFI)); return keystore; } catch (IOException | CertificateException | KeyStoreException | NoSuchAlgorithmException e) { Log.e(TAG, "Failed to open AndroidKeyStore for WI-FI namespace.", e); return null; } } @Override public void onCreate() { super.onCreate(); mKeyStore = getKeyStore(); mGrantsDb = new GrantsDatabase(this, new KeyStoreAliasesProvider(mKeyStore)); mStateStorage = new KeyChainStateStorage(getDataDir()); synchronized (mCredentialManagementAppLock) { mCredentialManagementApp = mStateStorage.loadCredentialManagementApp(); } } @Override public void onDestroy() { super.onDestroy(); mGrantsDb.destroy(); mGrantsDb = null; } private static class KeyStoreAliasesProvider implements ExistingKeysProvider { private final KeyStore mKeyStore; KeyStoreAliasesProvider(KeyStore keyStore) { mKeyStore = keyStore; } @Override public List getExistingKeyAliases() { final List keyStoreAliases = new ArrayList<>(); try { final Enumeration aliases = mKeyStore.aliases(); while (aliases.hasMoreElements()) { final String alias = aliases.nextElement(); if (!alias.startsWith(LockPatternUtils.SYNTHETIC_PASSWORD_KEY_PREFIX)) { if (mKeyStore.isKeyEntry(alias)) { keyStoreAliases.add(alias); } } } } catch (KeyStoreException e) { Log.e(TAG, "Error while loading entries from keystore. " + "List may be empty or incomplete."); } return keyStoreAliases; } } private KeyDescriptor makeKeyDescriptor(String alias) { final KeyDescriptor key = new KeyDescriptor(); key.domain = Domain.APP; key.nspace = KeyProperties.NAMESPACE_APPLICATION; key.alias = alias; key.blob = null; return key; } private final IKeyChainService.Stub mIKeyChainService = new IKeyChainService.Stub() { private final TrustedCertificateStore mTrustedCertificateStore = new TrustedCertificateStore(); private final Context mContext = KeyChainService.this; @Override public String requestPrivateKey(String alias) { final CallerIdentity caller = getCaller(); if (!hasGrant(alias, caller)) { return null; } final int granteeUid = caller.mUid; try { final KeyStore2 keyStore2 = KeyStore2.getInstance(); KeyDescriptor grant = keyStore2.grant(makeKeyDescriptor(alias), granteeUid, KeyPermission.USE | KeyPermission.GET_INFO); return KeyChain.getGrantString(grant); } catch (android.security.KeyStoreException e) { Log.e(TAG, "Failed to grant " + alias + " to uid: " + granteeUid, e); return null; } } @Override public String getWifiKeyGrantAsUser(String alias) { Preconditions.checkCallAuthorization(isSystemUid(getCaller()), MSG_NOT_SYSTEM); if (!hasGrant(alias, Process.WIFI_UID)) { return null; } KeyStore2 keyStore2 = KeyStore2.getInstance(); try { KeyDescriptor grant = keyStore2.grant(makeKeyDescriptor(alias), Process.WIFI_UID, KeyPermission.USE | KeyPermission.GET_INFO); return KeyStore2.makeKeystoreEngineGrantString(grant.nspace); } catch (android.security.KeyStoreException e) { Log.e(TAG, "Failed to grant " + alias + " to uid: " + Process.WIFI_UID, e); return null; } } @Override public byte[] getCertificate(String alias) { final CallerIdentity caller = getCaller(); if (!hasGrant(alias, caller) && !isSystemUid(caller)) { return null; } try { if (!mKeyStore.isCertificateEntry(alias)) { final Certificate cert = mKeyStore.getCertificate(alias); if (cert == null) return null; return cert.getEncoded(); } else { return null; } } catch (KeyStoreException | CertificateEncodingException e) { Log.e(TAG, "Failed to retrieve certificate.", e); return null; } } @Override public byte[] getCaCertificates(String alias) { final CallerIdentity caller = getCaller(); if (!hasGrant(alias, caller) && !isSystemUid(caller)) { return null; } try { if (mKeyStore.isCertificateEntry(alias)) { return mKeyStore.getCertificate(alias).getEncoded(); } else { final Certificate[] certs = mKeyStore.getCertificateChain(alias); if (certs == null || certs.length <= 1) return null; ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); for (int i = 1; i < certs.length; ++i) { outputStream.write(certs[i].getEncoded()); } return outputStream.toByteArray(); } } catch (KeyStoreException | CertificateEncodingException | IOException e) { Log.e(TAG, "Failed to retrieve certificate(s) from AndroidKeyStore.", e); return null; } } @Override public boolean isUserSelectable(String alias) { validateAlias(alias); return mGrantsDb.isUserSelectable(alias); } @Override public void setUserSelectable(String alias, boolean isUserSelectable) { validateAlias(alias); Preconditions.checkCallAuthorization(isSystemUid(getCaller()), MSG_NOT_SYSTEM); Log.i(TAG, String.format("Marking certificate %s as user-selectable: %b", alias, isUserSelectable)); mGrantsDb.setIsUserSelectable(alias, isUserSelectable); } @Override public int generateKeyPair( String algorithm, ParcelableKeyGenParameterSpec parcelableSpec) { Preconditions.checkCallAuthorization(isSystemUid(getCaller()), MSG_NOT_SYSTEM); final KeyGenParameterSpec spec = parcelableSpec.getSpec(); final String alias = spec.getKeystoreAlias(); Log.i(TAG, String.format("About to generate key with alias %s, algorithm %s", alias, algorithm)); if (KeyChain.KEY_ALIAS_SELECTION_DENIED.equals(alias)) { throw new IllegalArgumentException("The alias specified for the key denotes " + "a reserved value and cannot be used to name a key"); } // Validate the alias here to avoid relying on KeyGenParameterSpec c'tor preventing // the creation of a KeyGenParameterSpec instance with a non-empty alias. if (TextUtils.isEmpty(alias) || spec.getUid() != UID_SELF) { Log.e(TAG, "Cannot generate key pair with empty alias or specified uid."); return KeyChain.KEY_GEN_MISSING_ALIAS; } try { KeyPairGenerator generator = KeyPairGenerator.getInstance( algorithm, "AndroidKeyStore"); // Do not prepend USER_PRIVATE_KEY to the alias because // AndroidKeyStoreKeyPairGeneratorSpi will helpfully prepend that in // generateKeyPair. generator.initialize(spec); KeyPair kp = generator.generateKeyPair(); if (kp == null) { Log.e(TAG, "Key generation failed."); return KeyChain.KEY_GEN_FAILURE; } return KeyChain.KEY_GEN_SUCCESS; } catch (NoSuchAlgorithmException e) { Log.e(TAG, "Invalid algorithm requested", e); return KeyChain.KEY_GEN_NO_SUCH_ALGORITHM; } catch (InvalidAlgorithmParameterException e) { Log.e(TAG, "Invalid algorithm params", e); return KeyChain.KEY_GEN_INVALID_ALGORITHM_PARAMETERS; } catch (NoSuchProviderException e) { Log.e(TAG, "Could not find Keystore.", e); return KeyChain.KEY_GEN_NO_KEYSTORE_PROVIDER; } catch (StrongBoxUnavailableException e) { Log.e(TAG, "StrongBox unavailable.", e); return KeyChain.KEY_GEN_STRONGBOX_UNAVAILABLE; } catch (ProviderException e) { Throwable t = e.getCause(); if (t instanceof android.security.KeyStoreException) { if (((android.security.KeyStoreException) t).getErrorCode() == ErrorCode.CANNOT_ATTEST_IDS) { return KeyChain.KEY_ATTESTATION_CANNOT_ATTEST_IDS; } } Log.e(TAG, "KeyStore error.", e); return KeyChain.KEY_GEN_FAILURE; } } @Override public boolean setKeyPairCertificate(String alias, byte[] userCertificate, byte[] userCertificateChain) { Preconditions.checkCallAuthorization(isSystemUid(getCaller()), MSG_NOT_SYSTEM); final PrivateKey privateKey; try { final Key key = mKeyStore.getKey(alias, null); if (! (key instanceof PrivateKey)) { return false; } privateKey = (PrivateKey) key; } catch (KeyStoreException | NoSuchAlgorithmException | UnrecoverableKeyException e) { Log.e(TAG, "Failed to get private key entry.", e); return false; } final ArrayList certs = new ArrayList<>(); try { if (userCertificate != null) { certs.add(parseCertificate(userCertificate)); } if (userCertificateChain != null) { certs.addAll(parseCertificates(userCertificateChain)); } } catch (CertificateException e) { Log.e(TAG, "Failed to parse user certificate.", e); return false; } final Certificate[] certsArray = certs.toArray(new Certificate[0]); try { // setKeyEntry with a private key loaded from AndroidKeyStore replaces // the certificate components without touching the private key if // the alias is the same as that of the private key. mKeyStore.setKeyEntry(alias, privateKey, null, certsArray); } catch (KeyStoreException e) { Log.e(TAG, "Failed update key certificates.", e); return false; } if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, String.format("Set certificate for key alias %s : user %s CA chain: %s", alias, emptyOrBase64Encoded(userCertificate), emptyOrBase64Encoded(userCertificateChain))); } broadcastKeychainChange(); broadcastLegacyStorageChange(); return true; } private void validateAlias(String alias) { if (alias == null) { throw new NullPointerException("alias == null"); } } private boolean hasGrant(String alias, CallerIdentity caller) { return hasGrant(alias, caller.mUid); } private boolean hasGrant(String alias, int targetUid) { validateAlias(alias); if (!mGrantsDb.hasGrant(targetUid, alias)) { Log.w(TAG, String.format( "uid %d doesn't have permission to access the requested alias %s", targetUid, alias)); return false; } return true; } @Override public String installCaCertificate(byte[] caCertificate) { final CallerIdentity caller = getCaller(); Preconditions.checkCallAuthorization(isSystemUid(caller) || isCertInstaller(caller), MSG_NOT_SYSTEM_OR_CERT_INSTALLER); final String alias; String subject = null; final boolean isSecurityLoggingEnabled = mInjector.isSecurityLoggingEnabled(); final X509Certificate cert; try { cert = parseCertificate(caCertificate); final boolean isDebugLoggable = Log.isLoggable(TAG, Log.DEBUG); subject = cert.getSubjectX500Principal().getName(X500Principal.CANONICAL); if (isDebugLoggable) { Log.d(TAG, String.format("Installing CA certificate: %s", subject)); } synchronized (mTrustedCertificateStore) { mTrustedCertificateStore.installCertificate(cert); alias = mTrustedCertificateStore.getCertificateAlias(cert); } } catch (IOException | CertificateException e) { Log.w(TAG, "Failed installing CA certificate", e); if (isSecurityLoggingEnabled && subject != null) { mInjector.writeSecurityEvent( TAG_CERT_AUTHORITY_INSTALLED, 0 /*result*/, subject, UserHandle.myUserId()); } throw new IllegalStateException(e); } if (isSecurityLoggingEnabled && subject != null) { mInjector.writeSecurityEvent( TAG_CERT_AUTHORITY_INSTALLED, 1 /*result*/, subject, UserHandle.myUserId()); } // If the caller is the cert installer, install the CA certificate into KeyStore. // This is a temporary solution to enable CA certificates to be used as VPN trust // anchors. Ultimately, the user should explicitly choose to install the VPN trust // anchor separately and independently of CA certificates, at which point this code // should be removed. if (CERT_INSTALLER_PACKAGE.equals(caller.mPackageName)) { try { mKeyStore.setCertificateEntry(String.format("%s %s", subject, alias), cert); } catch(KeyStoreException e) { Log.e(TAG, String.format( "Attempted installing %s (subject: %s) to KeyStore. Failed", alias, subject), e); } } broadcastLegacyStorageChange(); broadcastTrustStoreChange(); return alias; } /** * Install a key pair to the keystore. * * @param privateKey The private key associated with the client certificate * @param userCertificate The client certificate to be installed * @param userCertificateChain The rest of the chain for the client certificate * @param alias The alias under which the key pair is installed. It is invalid to pass * {@code KeyChain.KEY_ALIAS_SELECTION_DENIED}. * @param uid Can be only one of two values: Either * {@code android.security.KeyStore.UID_SELF} to indicate installation into the * current user's system Keystore instance, or {@code Process.WIFI_UID} to * indicate installation into the main user's WiFi Keystore instance. It is only * valid to pass {@code Process.WIFI_UID} to the KeyChain service on user 0. * @return Whether the operation succeeded or not. */ @Override public boolean installKeyPair(@Nullable byte[] privateKey, @Nullable byte[] userCertificate, @Nullable byte[] userCertificateChain, String alias, int uid) { final CallerIdentity caller = getCaller(); Preconditions.checkCallAuthorization(isSystemUid(caller) || isCertInstaller(caller), MSG_NOT_SYSTEM_OR_CERT_INSTALLER); if (KeyChain.KEY_ALIAS_SELECTION_DENIED.equals(alias)) { throw new IllegalArgumentException("The alias specified for the key denotes " + "a reserved value and cannot be used to name a key"); } if (!ALLOWED_UIDS.contains(uid)) { Log.e(TAG, String.format("Installing alias %s as UID %d is now allowed.", alias, uid)); return false; } if (privateKey == null && userCertificate == null && userCertificateChain == null) { Log.e(TAG, String.format("Nothing to install for alias %s", alias)); return false; } if (uid == Process.WIFI_UID && UserHandle.myUserId() != UserHandle.USER_SYSTEM) { Log.e(TAG, String.format( "Installation into the WiFi Keystore should be called from the primary " + "user, not user %d", UserHandle.myUserId())); return false; } if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, String.format("Installing certificate and key to alias %s to uid %d: " + "user cert %s CA chain: %s", alias, uid, emptyOrBase64Encoded(userCertificate), emptyOrBase64Encoded(userCertificateChain))); } final ArrayList certs = new ArrayList<>(); try { if (userCertificate != null) { certs.add(parseCertificate(userCertificate)); } if (userCertificateChain != null) { certs.addAll(parseCertificates(userCertificateChain)); } } catch (CertificateException e) { Log.e(TAG, "Failed to parse user certificate.", e); return false; } if (certs.isEmpty()) { Log.e(TAG, "Cannot install private key without public certificate."); return false; } final Certificate[] certificates = certs.toArray(new Certificate[0]); final PrivateKey privateKey1; try { if (privateKey != null) { final KeyFactory keyFactory = KeyFactory.getInstance(certificates[0].getPublicKey().getAlgorithm()); privateKey1 = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(privateKey)); } else { privateKey1 = null; } } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { Log.e(TAG, "Failed to parse private key.", e); return false; } KeyStore keystore = getKeyStore(uid == Process.WIFI_UID); if (keystore == null) { return false; } try { if (privateKey != null) { keystore.setKeyEntry(alias, privateKey1, null, certificates); } else { if (certificates.length > 1) { Log.e(TAG, "Cannot install key certificate chain without private key."); return false; } keystore.setCertificateEntry(alias, certificates[0]); } } catch (KeyStoreException e) { Log.e(TAG, "Failed to install key pair.", e); } broadcastKeychainChange(); broadcastLegacyStorageChange(); return true; } @Override public boolean removeKeyPair(String alias) { final CallerIdentity caller = getCaller(); Preconditions.checkCallAuthorization(isSystemUid(caller) || isCertInstaller(caller), MSG_NOT_SYSTEM_OR_CERT_INSTALLER); return removeKeyPairInternal(alias); } private boolean removeKeyPairInternal(String alias) { try { mKeyStore.deleteEntry(alias); } catch (KeyStoreException e) { Log.e(TAG, String.format( "Failed not remove keystore entry with alias %s", alias)); return false; } Log.w(TAG, String.format( "WARNING: Removing alias %s, existing grants will be revoked.", alias)); mGrantsDb.removeAliasInformation(alias); broadcastKeychainChange(); broadcastLegacyStorageChange(); return true; } @Override public boolean containsKeyPair(String alias) { Preconditions.checkCallAuthorization(isSystemUid(getCaller()), MSG_NOT_SYSTEM); try { final Key key = mKeyStore.getKey(alias, null); return key instanceof PrivateKey; } catch (UnrecoverableKeyException | NoSuchAlgorithmException | KeyStoreException e) { Log.w("Error while trying to check for key presence.", e); return false; } } private X509Certificate parseCertificate(byte[] bytes) throws CertificateException { CertificateFactory cf = CertificateFactory.getInstance("X.509"); return (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(bytes)); } private Collection parseCertificates(byte[] bytes) throws CertificateException { final CertificateFactory cf = CertificateFactory.getInstance("X.509"); return (Collection) cf.generateCertificates(new ByteArrayInputStream(bytes)); } @Override public boolean reset() { // only Settings should be able to reset Preconditions.checkCallAuthorization(isSystemUid(getCaller()), MSG_NOT_SYSTEM); mGrantsDb.removeAllAliasesInformation(); boolean ok = true; synchronized (mTrustedCertificateStore) { // delete user-installed CA certs for (String alias : mTrustedCertificateStore.aliases()) { if (TrustedCertificateStore.isUser(alias)) { if (!deleteCertificateEntry(alias)) { ok = false; } } } } broadcastTrustStoreChange(); broadcastKeychainChange(); broadcastLegacyStorageChange(); return ok; } @Override public boolean deleteCaCertificate(String alias) { // only Settings should be able to delete Preconditions.checkCallAuthorization(isSystemUid(getCaller()), MSG_NOT_SYSTEM); boolean ok = true; Log.i(TAG, String.format("Deleting CA certificate %s", alias)); synchronized (mTrustedCertificateStore) { ok = deleteCertificateEntry(alias); } broadcastTrustStoreChange(); broadcastLegacyStorageChange(); return ok; } private boolean deleteCertificateEntry(String alias) { String subjectForAudit = null; if (mInjector.isSecurityLoggingEnabled()) { final Certificate cert = mTrustedCertificateStore.getCertificate(alias); if (cert instanceof X509Certificate) { subjectForAudit = ((X509Certificate) cert) .getSubjectX500Principal().getName(X500Principal.CANONICAL); } } try { mTrustedCertificateStore.deleteCertificateEntry(alias); if (subjectForAudit != null) { mInjector.writeSecurityEvent( TAG_CERT_AUTHORITY_REMOVED, 1 /*result*/, subjectForAudit, UserHandle.myUserId()); } return true; } catch (IOException | CertificateException e) { Log.w(TAG, "Problem removing CA certificate " + alias, e); if (subjectForAudit != null) { mInjector.writeSecurityEvent( TAG_CERT_AUTHORITY_REMOVED, 0 /*result*/, subjectForAudit, UserHandle.myUserId()); } return false; } } private boolean hasManageCredentialManagementAppPermission(CallerIdentity caller) { return mContext.checkPermission(Manifest.permission.MANAGE_CREDENTIAL_MANAGEMENT_APP, caller.mPid, caller.mUid) == PackageManager.PERMISSION_GRANTED; } private boolean isCertInstaller(CallerIdentity caller) { return caller.mPackageName != null && CERT_INSTALLER_PACKAGE.equals(caller.mPackageName); } private boolean isCredentialManagementApp(CallerIdentity caller) { synchronized (mCredentialManagementAppLock) { return mCredentialManagementApp != null && caller.mPackageName != null && caller.mPackageName.equals(mCredentialManagementApp.getPackageName()); } } private boolean isSystemUid(CallerIdentity caller) { return UserHandle.isSameApp(caller.mUid, Process.SYSTEM_UID); } @Override public boolean hasGrant(int uid, String alias) { Preconditions.checkCallAuthorization(isSystemUid(getCaller()), MSG_NOT_SYSTEM); return mGrantsDb.hasGrant(uid, alias); } @Override public boolean setGrant(int uid, String alias, boolean granted) { Preconditions.checkCallAuthorization(isSystemUid(getCaller()), MSG_NOT_SYSTEM); mGrantsDb.setGrant(uid, alias, granted); if (!granted) { try { KeyStore2.getInstance().ungrant(makeKeyDescriptor(alias), uid); } catch (android.security.KeyStoreException e) { Log.e(TAG, "Failed to ungrant " + alias + " to uid: " + uid, e); return false; } } broadcastPermissionChange(uid, alias, granted); broadcastLegacyStorageChange(); return true; } @Override public int[] getGrants(String alias) { Preconditions.checkCallAuthorization(isSystemUid(getCaller()), MSG_NOT_SYSTEM); try { if (mKeyStore.isKeyEntry(alias)) { return mGrantsDb.getGrants(alias); } } catch (KeyStoreException e) { Log.w(TAG, "Error while checking if key exists.", e); } throw new IllegalArgumentException("Alias not found: " + alias); } @Override public StringParceledListSlice getUserCaAliases() { synchronized (mTrustedCertificateStore) { return new StringParceledListSlice(new ArrayList( mTrustedCertificateStore.userAliases())); } } @Override public StringParceledListSlice getSystemCaAliases() { synchronized (mTrustedCertificateStore) { return new StringParceledListSlice(new ArrayList( mTrustedCertificateStore.allSystemAliases())); } } @Override public boolean containsCaAlias(String alias) { return mTrustedCertificateStore.containsAlias(alias); } @Override public byte[] getEncodedCaCertificate(String alias, boolean includeDeletedSystem) { synchronized (mTrustedCertificateStore) { X509Certificate certificate = (X509Certificate) mTrustedCertificateStore .getCertificate(alias, includeDeletedSystem); if (certificate == null) { Log.w(TAG, "Could not find CA certificate " + alias); return null; } try { return certificate.getEncoded(); } catch (CertificateEncodingException e) { Log.w(TAG, "Error while encoding CA certificate " + alias); return null; } } } @Override public List getCaCertificateChainAliases(String rootAlias, boolean includeDeletedSystem) { synchronized (mTrustedCertificateStore) { X509Certificate root = (X509Certificate) mTrustedCertificateStore.getCertificate( rootAlias, includeDeletedSystem); try { List chain = mTrustedCertificateStore.getCertificateChain( root); List aliases = new ArrayList(chain.size()); final int n = chain.size(); for (int i = 0; i < n; ++i) { String alias = mTrustedCertificateStore.getCertificateAlias(chain.get(i), true); if (alias != null) { aliases.add(alias); } } return aliases; } catch (CertificateException e) { Log.w(TAG, "Error retrieving cert chain for root " + rootAlias); return Collections.emptyList(); } } } @Override public void setCredentialManagementApp(@NonNull String packageName, @NonNull AppUriAuthenticationPolicy authenticationPolicy) { final CallerIdentity caller = getCaller(); Preconditions.checkCallAuthorization(isSystemUid(caller) || hasManageCredentialManagementAppPermission(caller), MSG_NOT_SYSTEM); checkValidAuthenticationPolicy(authenticationPolicy); synchronized (mCredentialManagementAppLock) { if (mCredentialManagementApp != null) { final String existingPackage = mCredentialManagementApp.getPackageName(); if (existingPackage.equals(packageName)) { // Update existing credential management app's policy removeOrphanedKeyPairs(authenticationPolicy); } else { // Replace existing credential management app removeOrphanedKeyPairs(null); setManageCredentialsAppOps(existingPackage, false); } } setManageCredentialsAppOps(packageName, true); mCredentialManagementApp = new CredentialManagementApp(packageName, authenticationPolicy); mStateStorage.saveCredentialManagementApp(mCredentialManagementApp); } } private void setManageCredentialsAppOps(String packageName, boolean allowed) { try { int mode = allowed ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_DEFAULT; ApplicationInfo appInfo = getPackageManager().getApplicationInfo(packageName, 0); getSystemService(AppOpsManager.class).setMode(AppOpsManager.OP_MANAGE_CREDENTIALS, appInfo.uid, packageName, mode); } catch (PackageManager.NameNotFoundException e) { Log.e(TAG, "Unable to find info for package: " + packageName); } } private void removeOrphanedKeyPairs( @Nullable AppUriAuthenticationPolicy newPolicy) { Set existingAliases = mCredentialManagementApp.getAuthenticationPolicy() .getAliases(); Set newAliases = newPolicy != null ? newPolicy.getAliases() : new HashSet<>(); // Uninstall all certificates that are no longer included in the new // authentication policy for (String existingAlias : existingAliases) { if (!newAliases.contains(existingAlias)) { removeKeyPairInternal(existingAlias); } } } private void checkValidAuthenticationPolicy( @NonNull AppUriAuthenticationPolicy authenticationPolicy) { if (authenticationPolicy == null || authenticationPolicy.getAppAndUriMappings().isEmpty()) { throw new IllegalArgumentException("The authentication policy is null or empty"); } // Check whether any of the aliases in the policy already exist for (String alias : authenticationPolicy.getAliases()) { if (requestPrivateKey(alias) != null) { throw new IllegalArgumentException(String.format("The authentication policy " + "contains an installed alias: %s", alias)); } } } @Override public boolean hasCredentialManagementApp() { Preconditions.checkCallAuthorization(isSystemUid(getCaller()), MSG_NOT_SYSTEM); synchronized (mCredentialManagementAppLock) { return mCredentialManagementApp != null; } } @Nullable @Override public String getCredentialManagementAppPackageName() { Preconditions.checkCallAuthorization(isSystemUid(getCaller()), MSG_NOT_SYSTEM); synchronized (mCredentialManagementAppLock) { return mCredentialManagementApp != null ? mCredentialManagementApp.getPackageName() : null; } } @Nullable @Override public AppUriAuthenticationPolicy getCredentialManagementAppPolicy() { final CallerIdentity caller = getCaller(); Preconditions.checkCallAuthorization(isSystemUid(caller) || isCredentialManagementApp(caller), MSG_NOT_SYSTEM_OR_CRED_MNG_APP); synchronized (mCredentialManagementAppLock) { return mCredentialManagementApp != null ? mCredentialManagementApp.getAuthenticationPolicy() : null; } } @Nullable @Override public String getPredefinedAliasForPackageAndUri(@NonNull String packageName, @Nullable Uri uri) { Preconditions.checkCallAuthorization(isSystemUid(getCaller()), MSG_NOT_SYSTEM); synchronized (mCredentialManagementAppLock) { if (mCredentialManagementApp == null || uri == null) { return null; } Map urisToAliases = mCredentialManagementApp.getAuthenticationPolicy() .getAppAndUriMappings().get(packageName); return urisToAliases != null ? urisToAliases.get(uri) : null; } } @Override public void removeCredentialManagementApp() { final CallerIdentity caller = getCaller(); Preconditions.checkCallAuthorization(isSystemUid(caller) || isCredentialManagementApp(caller) || hasManageCredentialManagementAppPermission(caller), MSG_NOT_SYSTEM_OR_CRED_MNG_APP); synchronized (mCredentialManagementAppLock) { if (mCredentialManagementApp != null) { // Remove all certificates removeOrphanedKeyPairs(null); setManageCredentialsAppOps(mCredentialManagementApp.getPackageName(), false); } mCredentialManagementApp = null; mStateStorage.saveCredentialManagementApp(mCredentialManagementApp); } } @Override public boolean isCredentialManagementApp(@NonNull String packageName) { final CallerIdentity caller = getCaller(); Preconditions.checkCallAuthorization(isSystemUid(caller) || isCredentialManagementApp(caller), MSG_NOT_SYSTEM_OR_CRED_MNG_APP); synchronized (mCredentialManagementAppLock) { return packageName.equals(mCredentialManagementApp.getPackageName()); } } }; @Override public IBinder onBind(Intent intent) { if (IKeyChainService.class.getName().equals(intent.getAction())) { return mIKeyChainService; } return null; } @Override protected void onHandleIntent(final Intent intent) { if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) { mGrantsDb.purgeOldGrants(getPackageManager()); } } private void broadcastLegacyStorageChange() { Intent intent = new Intent(KeyChain.ACTION_STORAGE_CHANGED); BroadcastOptions opts = BroadcastOptions.makeBasic(); opts.setMaxManifestReceiverApiLevel(Build.VERSION_CODES.N_MR1); sendBroadcastAsUser(intent, UserHandle.of(UserHandle.myUserId()), null, opts.toBundle()); } private void broadcastKeychainChange() { Intent intent = new Intent(KeyChain.ACTION_KEYCHAIN_CHANGED); sendBroadcastAsUser(intent, UserHandle.of(UserHandle.myUserId())); } private void broadcastTrustStoreChange() { Intent intent = new Intent(KeyChain.ACTION_TRUST_STORE_CHANGED); sendBroadcastAsUser(intent, UserHandle.of(UserHandle.myUserId())); } private void broadcastPermissionChange(int uid, String alias, boolean access) { // Since the permission change only impacts one uid only send to that uid's packages. final PackageManager packageManager = getPackageManager(); String[] packages = packageManager.getPackagesForUid(uid); if (packages == null) { return; } for (String pckg : packages) { Intent intent = new Intent(KeyChain.ACTION_KEY_ACCESS_CHANGED); intent.putExtra(KeyChain.EXTRA_KEY_ALIAS, alias); intent.putExtra(KeyChain.EXTRA_KEY_ACCESSIBLE, access); intent.setPackage(pckg); sendBroadcastAsUser(intent, UserHandle.of(UserHandle.myUserId())); } } private static String emptyOrBase64Encoded(byte[] cert) { if (cert == null) { return ""; } return Base64.encodeToString(cert, Base64.NO_WRAP); } private final class CallerIdentity { final int mUid; final int mPid; final String mPackageName; CallerIdentity() { mUid = mInjector.getCallingUid(); mPid = Binder.getCallingPid(); mPackageName = getPackageManager().getNameForUid(mUid); } } private CallerIdentity getCaller() { return new CallerIdentity(); } @VisibleForTesting void setInjector(Injector injector) { mInjector = injector; } /** * Injector for mocking out dependencies in tests. */ @VisibleForTesting static class Injector { public boolean isSecurityLoggingEnabled() { return SecurityLog.isLoggingEnabled(); } public void writeSecurityEvent(int tag, Object... payload) { SecurityLog.writeEvent(tag, payload); } public int getCallingUid() { return Binder.getCallingUid(); } public KeyStore getKeyStoreInstance() throws KeyStoreException { return KeyStore.getInstance("AndroidKeyStore"); } } }