/*
 * 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.KeyProperties.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.UserManager;
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.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<Integer> 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<String> getExistingKeyAliases() {
            final List<String> keyStoreAliases = new ArrayList<>();
            try {
                final Enumeration<String> aliases = mKeyStore.aliases();
                while (aliases.hasMoreElements()) {
                    final String alias = aliases.nextElement();
                    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<Certificate> 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.KeyProperties.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. Only admin users are allowed to pass {@code Process.WIFI_UID} to
         *            the KeyChain service.
         * @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;
            }

            UserManager userManager = mContext.getSystemService(UserManager.class);
            if (uid == Process.WIFI_UID && !userManager.isAdminUser()) {
                Log.e(TAG,
                    "Installation into the WiFi Keystore should be called from the admin user");
                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<Certificate> 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<X509Certificate> parseCertificates(byte[] bytes)
                throws CertificateException {
            final CertificateFactory cf = CertificateFactory.getInstance("X.509");
            return (Collection<X509Certificate>)
                    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);
            Preconditions.checkArgument(containsKeyPair(alias),
                    "Alias not associated with a key.");
            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<String>(
                        mTrustedCertificateStore.userAliases()));
            }
        }

        @Override
        public StringParceledListSlice getSystemCaAliases() {
            synchronized (mTrustedCertificateStore) {
                return new StringParceledListSlice(new ArrayList<String>(
                        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<String> getCaCertificateChainAliases(String rootAlias,
                boolean includeDeletedSystem) {
            synchronized (mTrustedCertificateStore) {
                X509Certificate root = (X509Certificate) mTrustedCertificateStore.getCertificate(
                        rootAlias, includeDeletedSystem);
                try {
                    List<X509Certificate> chain = mTrustedCertificateStore.getCertificateChain(
                            root);
                    List<String> aliases = new ArrayList<String>(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<String> existingAliases = mCredentialManagementApp.getAuthenticationPolicy()
                    .getAliases();
            Set<String> 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<Uri, String> 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");
        }
    }
}
