hotspot2: install Passpoint certificates and keys in keystore

When adding a Passpoint provider, install the certificates and
keys specified in the configuration to the keystore.

While there, move the object creation for Passpoint related
objects out of the WifiInjector and into newly created
PasspointObjectFactory.

Bug: 32619189
Test: frameworks/opt/net/wifi/tests/wifitests/runtests.sh
Change-Id: I42ee22a31d30e2c9fa05ece8713b95ebea71256e
diff --git a/service/java/com/android/server/wifi/WifiInjector.java b/service/java/com/android/server/wifi/WifiInjector.java
index 3d13564..fffac5d 100644
--- a/service/java/com/android/server/wifi/WifiInjector.java
+++ b/service/java/com/android/server/wifi/WifiInjector.java
@@ -39,8 +39,8 @@
 import com.android.server.am.BatteryStatsService;
 import com.android.server.net.DelayedDiskWrite;
 import com.android.server.net.IpConfigStore;
-import com.android.server.wifi.hotspot2.PasspointEventHandler;
 import com.android.server.wifi.hotspot2.PasspointManager;
+import com.android.server.wifi.hotspot2.PasspointObjectFactory;
 import com.android.server.wifi.util.WifiPermissionsUtil;
 import com.android.server.wifi.util.WifiPermissionsWrapper;
 
@@ -170,7 +170,8 @@
         mWifiPermissionsUtil = new WifiPermissionsUtil(mWifiPermissionsWrapper, mContext,
                 mSettingsStore, UserManager.get(mContext), new NetworkScorerAppManager(mContext));
         mSimAccessor = new SIMAccessor(mContext);
-        mPasspointManager = new PasspointManager(mContext, this, mSimAccessor);
+        mPasspointManager = new PasspointManager(mContext, mWifiNative, mWifiKeyStore, mClock,
+                mSimAccessor, new PasspointObjectFactory());
     }
 
     /**
@@ -331,14 +332,6 @@
     }
 
     /**
-     * Create a PasspointEventHandler instance with the given callbacks.
-     */
-    public PasspointEventHandler makePasspointEventHandler(
-            PasspointEventHandler.Callbacks callbacks) {
-        return new PasspointEventHandler(mWifiNative, callbacks);
-    }
-
-    /**
      * Obtain an instance of WifiScanner.
      * If it was not already created, then obtain an instance.  Note, this must be done lazily since
      * WifiScannerService is separate and created later.
diff --git a/service/java/com/android/server/wifi/WifiKeyStore.java b/service/java/com/android/server/wifi/WifiKeyStore.java
index f921988..b667fd4 100644
--- a/service/java/com/android/server/wifi/WifiKeyStore.java
+++ b/service/java/com/android/server/wifi/WifiKeyStore.java
@@ -158,7 +158,14 @@
         return ret;
     }
 
-    private boolean putCertInKeyStore(String name, Certificate cert) {
+    /**
+     * Install a certificate into the keystore.
+     *
+     * @param name The alias name of the certificate to be installed
+     * @param cert The certificate to be installed
+     * @return true on success
+     */
+    public boolean putCertInKeyStore(String name, Certificate cert) {
         try {
             byte[] certData = Credentials.convertToPem(cert);
             if (mVerboseLoggingEnabled) Log.d(TAG, "putting certificate " + name + " in keystore");
@@ -171,6 +178,28 @@
     }
 
     /**
+     * Install a key into the keystore.
+     *
+     * @param name The alias name of the key to be installed
+     * @param key The key to be installed
+     * @return true on success
+     */
+    public boolean putKeyInKeyStore(String name, Key key) {
+        byte[] privKeyData = key.getEncoded();
+        return mKeyStore.importKey(name, privKeyData, Process.WIFI_UID, KeyStore.FLAG_NONE);
+    }
+
+    /**
+     * Remove a certificate or key entry specified by the alias name from the keystore.
+     *
+     * @param name The alias name of the entry to be removed
+     * @return true on success
+     */
+    public boolean removeEntryFromKeyStore(String name) {
+        return mKeyStore.delete(name, Process.WIFI_UID);
+    }
+
+    /**
      * Remove enterprise keys from the network config.
      *
      * @param config Config corresponding to the network.
diff --git a/service/java/com/android/server/wifi/hotspot2/PasspointManager.java b/service/java/com/android/server/wifi/hotspot2/PasspointManager.java
index 3e668a7..d130e01 100644
--- a/service/java/com/android/server/wifi/hotspot2/PasspointManager.java
+++ b/service/java/com/android/server/wifi/hotspot2/PasspointManager.java
@@ -34,9 +34,11 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import com.android.server.wifi.Clock;
 import com.android.server.wifi.IMSIParameter;
 import com.android.server.wifi.SIMAccessor;
-import com.android.server.wifi.WifiInjector;
+import com.android.server.wifi.WifiKeyStore;
+import com.android.server.wifi.WifiNative;
 import com.android.server.wifi.anqp.ANQPElement;
 import com.android.server.wifi.anqp.Constants;
 
@@ -55,6 +57,9 @@
 
     private final PasspointEventHandler mHandler;
     private final SIMAccessor mSimAccessor;
+    private final WifiKeyStore mKeyStore;
+    private final Clock mClock;
+    private final PasspointObjectFactory mObjectFactory;
     private final Map<String, PasspointProvider> mProviders;
 
     private class CallbackHandler implements PasspointEventHandler.Callbacks {
@@ -104,9 +109,14 @@
         }
     }
 
-    public PasspointManager(Context context, WifiInjector wifiInjector, SIMAccessor simAccessor) {
-        mHandler = wifiInjector.makePasspointEventHandler(new CallbackHandler(context));
+    public PasspointManager(Context context, WifiNative wifiNative, WifiKeyStore keyStore,
+            Clock clock, SIMAccessor simAccessor, PasspointObjectFactory objectFactory) {
+        mHandler = objectFactory.makePasspointEventHandler(wifiNative,
+                new CallbackHandler(context));
+        mKeyStore = keyStore;
+        mClock = clock;
         mSimAccessor = simAccessor;
+        mObjectFactory = objectFactory;
         mProviders = new HashMap<>();
         // TODO(zqiu): load providers from the persistent storage.
     }
@@ -144,19 +154,25 @@
             }
         }
 
-        // TODO(b/32619189): install new key and certificates to the keystore.
+        // Create a provider and install the necessary certificates and keys.
+        PasspointProvider newProvider = mObjectFactory.makePasspointProvider(
+                config, mKeyStore, mClock.getWallClockMillis());
+
+        if (!newProvider.installCertsAndKeys()) {
+            Log.e(TAG, "Failed to install certificates and keys to keystore");
+            return false;
+        }
 
         // Detect existing configuration in the same base domain.
         PasspointProvider existingProvider = findProviderInSameBaseDomain(config.homeSp.fqdn);
         if (existingProvider != null) {
             Log.d(TAG, "Replacing configuration for " + existingProvider.getConfig().homeSp.fqdn
                     + " with " + config.homeSp.fqdn);
-            // TODO(b/32619189): Remove existing key and certificates from the keystore.
-
+            existingProvider.uninstallCertsAndKeys();
             mProviders.remove(existingProvider.getConfig().homeSp.fqdn);
         }
 
-        mProviders.put(config.homeSp.fqdn, new PasspointProvider(config));
+        mProviders.put(config.homeSp.fqdn, newProvider);
 
         // TODO(b/31065385): Persist updated providers configuration to the persistent storage.
 
@@ -175,8 +191,7 @@
             return false;
         }
 
-        // TODO(b/32619189): Remove key and certificates from the keystore.
-
+        mProviders.get(fqdn).uninstallCertsAndKeys();
         mProviders.remove(fqdn);
         return true;
     }
diff --git a/service/java/com/android/server/wifi/hotspot2/PasspointObjectFactory.java b/service/java/com/android/server/wifi/hotspot2/PasspointObjectFactory.java
new file mode 100644
index 0000000..41ec9fa
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/PasspointObjectFactory.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2016 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.server.wifi.hotspot2;
+
+import android.net.wifi.hotspot2.PasspointConfiguration;
+
+import com.android.server.wifi.WifiKeyStore;
+import com.android.server.wifi.WifiNative;
+
+/**
+ * Factory class for creating Passpoint related objects. Useful for mocking object creations
+ * in the unit tests.
+ */
+public class PasspointObjectFactory{
+    /**
+     * Create a PasspointEventHandler instance.
+     *
+     * @param wifiNative Instance of {@link WifiNative}
+     * @param callbacks Instance of {@link PasspointEventHandler.Callbacks}
+     * @return {@link PasspointEventHandler}
+     */
+    public PasspointEventHandler makePasspointEventHandler(WifiNative wifiNative,
+            PasspointEventHandler.Callbacks callbacks) {
+        return new PasspointEventHandler(wifiNative, callbacks);
+    }
+
+    /**
+     * Create a PasspointProvider instance.
+     *
+     * @param keyStore Instance of {@link WifiKeyStore}
+     * @param config Configuration for the provider
+     * @param providerId Unique identifier for the provider
+     * @return {@link PasspointProvider}
+     */
+    public PasspointProvider makePasspointProvider(PasspointConfiguration config,
+            WifiKeyStore keyStore, long providerId) {
+        return new PasspointProvider(config, keyStore, providerId);
+    }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/PasspointProvider.java b/service/java/com/android/server/wifi/hotspot2/PasspointProvider.java
index 9c38ac7..6d12090 100644
--- a/service/java/com/android/server/wifi/hotspot2/PasspointProvider.java
+++ b/service/java/com/android/server/wifi/hotspot2/PasspointProvider.java
@@ -17,21 +17,181 @@
 package com.android.server.wifi.hotspot2;
 
 import android.net.wifi.hotspot2.PasspointConfiguration;
+import android.security.Credentials;
+import android.util.Log;
+
+import com.android.server.wifi.WifiKeyStore;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
 
 /**
  * Abstraction for Passpoint service provider.  This class contains the both static
  * Passpoint configuration data and the runtime data (e.g. blacklisted SSIDs, statistics).
  */
 public class PasspointProvider {
-    private final PasspointConfiguration mConfig;
+    private static final String TAG = "PasspointProvider";
 
-    public PasspointProvider(PasspointConfiguration config) {
+    // Prefix for certificates and keys aliases.
+    private static final String ALIAS_PREFIX = "HS2_";
+
+    private final PasspointConfiguration mConfig;
+    private final WifiKeyStore mKeyStore;
+
+    // Unique identifier for this provider. Used as part of the alias names for identifying
+    // certificates and keys installed on the keystore.
+    private final long mProviderId;
+
+    // Aliases for the private keys and certificates installed in the keystore.
+    private String mCaCertificateAlias;
+    private String mClientPrivateKeyAlias;
+    private String mClientCertificateAlias;
+
+    public PasspointProvider(PasspointConfiguration config, WifiKeyStore keyStore,
+            long providerId) {
         // Maintain a copy of the configuration to avoid it being updated by others.
         mConfig = new PasspointConfiguration(config);
+        mKeyStore = keyStore;
+        mProviderId = providerId;
     }
 
     public PasspointConfiguration getConfig() {
         // Return a copy of the configuration to avoid it being updated by others.
         return new PasspointConfiguration(mConfig);
     }
+
+    public String getCaCertificateAlias() {
+        return mCaCertificateAlias;
+    }
+
+    public String getClientPrivateKeyAlias() {
+        return mClientPrivateKeyAlias;
+    }
+
+    public String getClientCertificateAlias() {
+        return mClientCertificateAlias;
+    }
+
+    /**
+     * Install certificates and key based on current configuration.
+     * Note: the certificates and keys in the configuration will get cleared once
+     * they're installed in the keystore.
+     *
+     * @return true on success
+     */
+    public boolean installCertsAndKeys() {
+        // Install CA certificate.
+        if (mConfig.credential.caCertificate != null) {
+            String alias = formatAliasName(Credentials.CA_CERTIFICATE, mProviderId);
+            if (!mKeyStore.putCertInKeyStore(alias, mConfig.credential.caCertificate)) {
+                Log.e(TAG, "Failed to install CA Certificate");
+                uninstallCertsAndKeys();
+                return false;
+            }
+            mCaCertificateAlias = alias;
+        }
+
+        // Install the client private key.
+        if (mConfig.credential.clientPrivateKey != null) {
+            String alias = formatAliasName(Credentials.USER_PRIVATE_KEY, mProviderId);
+            if (!mKeyStore.putKeyInKeyStore(alias, mConfig.credential.clientPrivateKey)) {
+                Log.e(TAG, "Failed to install client private key");
+                uninstallCertsAndKeys();
+                return false;
+            }
+            mClientPrivateKeyAlias = alias;
+        }
+
+        // Install the client certificate.
+        if (mConfig.credential.clientCertificateChain != null) {
+            X509Certificate clientCert =
+                    getClientCertificate(mConfig.credential.clientCertificateChain,
+                                         mConfig.credential.certCredential.certSha256FingerPrint);
+            if (clientCert == null) {
+                Log.e(TAG, "Failed to locate client certificate");
+                uninstallCertsAndKeys();
+                return false;
+            }
+            String alias = formatAliasName(Credentials.USER_CERTIFICATE, mProviderId);
+            if (!mKeyStore.putCertInKeyStore(alias, clientCert)) {
+                Log.e(TAG, "Failed to install client certificate");
+                uninstallCertsAndKeys();
+                return false;
+            }
+            mClientCertificateAlias = alias;
+        }
+
+        // Clear the keys and certificates in the configuration.
+        mConfig.credential.caCertificate = null;
+        mConfig.credential.clientPrivateKey = null;
+        mConfig.credential.clientCertificateChain = null;
+        return true;
+    }
+
+    /**
+     * Remove any installed certificates and key.
+     */
+    public void uninstallCertsAndKeys() {
+        if (mCaCertificateAlias != null) {
+            if (!mKeyStore.removeEntryFromKeyStore(mCaCertificateAlias)) {
+                Log.e(TAG, "Failed to remove entry: " + mCaCertificateAlias);
+            }
+            mCaCertificateAlias = null;
+        }
+        if (mClientPrivateKeyAlias != null) {
+            if (!mKeyStore.removeEntryFromKeyStore(mClientPrivateKeyAlias)) {
+                Log.e(TAG, "Failed to remove entry: " + mClientPrivateKeyAlias);
+            }
+            mClientPrivateKeyAlias = null;
+        }
+        if (mClientCertificateAlias != null) {
+            if (!mKeyStore.removeEntryFromKeyStore(mClientCertificateAlias)) {
+                Log.e(TAG, "Failed to remove entry: " + mClientCertificateAlias);
+            }
+            mClientCertificateAlias = null;
+        }
+    }
+
+    /**
+     * Create and return a certificate or key alias name based on the given prefix and uid.
+     *
+     * @param type The key or certificate type string
+     * @param uid The UID of the alias
+     * @return String
+     */
+    private static String formatAliasName(String type, long uid) {
+        return type + ALIAS_PREFIX + uid;
+    }
+
+    /**
+     * Retrieve the client certificate from the certificates chain.  The certificate
+     * with the matching SHA256 digest is the client certificate.
+     *
+     * @param certChain The client certificates chain
+     * @param expectedSha256Fingerprint The expected SHA256 digest of the client certificate
+     * @return {@link java.security.cert.X509Certificate}
+     */
+    private static X509Certificate getClientCertificate(X509Certificate[] certChain,
+            byte[] expectedSha256Fingerprint) {
+        if (certChain == null) {
+            return null;
+        }
+        try {
+            MessageDigest digester = MessageDigest.getInstance("SHA-256");
+            for (X509Certificate certificate : certChain) {
+                digester.reset();
+                byte[] fingerprint = digester.digest(certificate.getEncoded());
+                if (Arrays.equals(expectedSha256Fingerprint, fingerprint)) {
+                    return certificate;
+                }
+            }
+        } catch (CertificateEncodingException | NoSuchAlgorithmException e) {
+            return null;
+        }
+
+        return null;
+    }
 }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/FakeKeys.java b/service/tests/wifitests/src/com/android/server/wifi/FakeKeys.java
index e8694b4..99ce196 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/FakeKeys.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/FakeKeys.java
@@ -3,8 +3,13 @@
 import java.io.ByteArrayInputStream;
 import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
 import java.security.cert.CertificateFactory;
 import java.security.cert.X509Certificate;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
 
 /**
  * A class containing test certificates.
@@ -52,6 +57,146 @@
             "-----END CERTIFICATE-----\n";
     public static final X509Certificate CA_CERT1 = loadCertificate(CA_CERT1_STRING);
 
+    private static final String CLIENT_CERT_STR = "-----BEGIN CERTIFICATE-----\n" +
+            "MIIE/DCCAuQCAQEwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UEBhMCVVMxCzAJBgNV\n" +
+            "BAgMAkNBMRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MRAwDgYDVQQKDAdUZXN0aW5n\n" +
+            "MB4XDTE2MDkzMDIwNTQyOFoXDTE3MDkzMDIwNTQyOFowRDELMAkGA1UEBhMCVVMx\n" +
+            "CzAJBgNVBAgMAkNBMRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MRAwDgYDVQQKDAdU\n" +
+            "ZXN0aW5nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAnpmcbuaeHfnJ\n" +
+            "k+2QNvxmdVFTawyFMNk0USCq5sexscwmxbewG/Rb8YnixwJWS44v2XkSujB67z5C\n" +
+            "s2qudFEhRXKdEuC6idbAuA97KjipHh0AAniWMsyv61fvbgsUC0b0canx3LiDq81p\n" +
+            "y28NNGmAvoazLZUZ4AhBRiwYZY6FKk723gmZoGbEIeG7J1dlXPusc1662rIjz4eU\n" +
+            "zlmmlvqyHfNqnNk8L14Vug6Xh+lOEGN85xhu1YHAEKGrS89kZxs5rum/cZU8KH2V\n" +
+            "v6eKnY03kxjiVLQtnLpm/7VUEoCMGHyruRj+p3my4+DgqMsmsH52RZCBsjyGlpbU\n" +
+            "NOwOTIX6xh+Rqloduz4AnrMYYIiIw2s8g+2zJM7VbcVKx0fGS26BKdrxgrXWfmNE\n" +
+            "nR0/REQ5AxDGw0jfTUvtdTkXAf+K4MDjcNLEZ+MA4rHfAfQWZtUR5BkHCQYxNpJk\n" +
+            "pA0gyk+BpKdC4WdzI14NSWsu5sRCmBCFqH6BTOSEq/V1cNorBxNwLSSTwFFqUDqx\n" +
+            "Y5nQLXygkJf9WHZWtSKeSjtOYgilz7UKzC2s3CsjmIyGFe+SwpuHJnuE4Uc8Z5Cb\n" +
+            "bjNGHPzqL6XnmzZHJp7RF8kBdKdjGC7dCUltzOfICZeKlzOOq+Kw42T/nXjuXvpb\n" +
+            "nkXNxg741Nwd6RecykXJbseFwm3EYxkCAwEAATANBgkqhkiG9w0BAQsFAAOCAgEA\n" +
+            "Ga1mGwI9aXkL2fTPXO9YkAPzoGeX8aeuVYSQaSkNq+5vnogYCyAt3YDHjRG+ewTT\n" +
+            "WbnPA991xRAPac+biJeXWmwvgGj0YuT7e79phAiGkTTnbAjFHGfYnBy/tI/v7btO\n" +
+            "hRNElA5yTJ1m2fVbBEKXzMR83jrT9iyI+YLRN86zUZIaC86xxSbqnrdWN2jOK6MX\n" +
+            "dS8Arp9tPQjC/4gW+2Ilxv68jiYh+5auWHQZVjppWVY//iu4mAbkq1pTwQEhZ8F8\n" +
+            "Zrmh9DHh60hLFcfSuhIAwf/NMzppwdkjy1ruKVrpijhGKGp4OWu8nvOUgHSzxc7F\n" +
+            "PwpVZ5N2Ku4L8MLO6BG2VasRJK7l17TzDXlfLZHJjkuryOFxVaQKt8ZNFgTOaCXS\n" +
+            "E+gpTLksKU7riYckoiP4+H1sn9qcis0e8s4o/uf1UVc8GSdDw61ReGM5oZEDm1u8\n" +
+            "H9x20QU6igLqzyBpqvCKv7JNgU1uB2PAODHH78zJiUfnKd1y+o+J1iWzaGj3EFji\n" +
+            "T8AXksbTP733FeFXfggXju2dyBH+Z1S5BBTEOd1brWgXlHSAZGm97MKZ94r6/tkX\n" +
+            "qfv3fCos0DKz0oV7qBxYS8wiYhzrRVxG6ITAoH8uuUVVQaZF+G4nJ2jEqNbfuKyX\n" +
+            "ATQsVNjNNlDA0J33GobPMjT326wa4YAWMx8PI5PJZ3g=\n" +
+            "-----END CERTIFICATE-----\n";
+    public static final X509Certificate CLIENT_CERT = loadCertificate(CLIENT_CERT_STR);
+
+    private static final byte[] FAKE_RSA_KEY_1 = new byte[] {
+            (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0x78, (byte) 0x02, (byte) 0x01,
+            (byte) 0x00, (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a,
+            (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01,
+            (byte) 0x01, (byte) 0x01, (byte) 0x05, (byte) 0x00, (byte) 0x04, (byte) 0x82,
+            (byte) 0x02, (byte) 0x62, (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0x5e,
+            (byte) 0x02, (byte) 0x01, (byte) 0x00, (byte) 0x02, (byte) 0x81, (byte) 0x81,
+            (byte) 0x00, (byte) 0xce, (byte) 0x29, (byte) 0xeb, (byte) 0xf6, (byte) 0x5b,
+            (byte) 0x25, (byte) 0xdc, (byte) 0xa1, (byte) 0xa6, (byte) 0x2c, (byte) 0x66,
+            (byte) 0xcb, (byte) 0x20, (byte) 0x90, (byte) 0x27, (byte) 0x86, (byte) 0x8a,
+            (byte) 0x44, (byte) 0x71, (byte) 0x50, (byte) 0xda, (byte) 0xd3, (byte) 0x02,
+            (byte) 0x77, (byte) 0x55, (byte) 0xe9, (byte) 0xe8, (byte) 0x08, (byte) 0xf3,
+            (byte) 0x36, (byte) 0x9a, (byte) 0xae, (byte) 0xab, (byte) 0x04, (byte) 0x6d,
+            (byte) 0x00, (byte) 0x99, (byte) 0xbf, (byte) 0x7d, (byte) 0x0f, (byte) 0x67,
+            (byte) 0x8b, (byte) 0x1d, (byte) 0xd4, (byte) 0x2b, (byte) 0x7c, (byte) 0xcb,
+            (byte) 0xcd, (byte) 0x33, (byte) 0xc7, (byte) 0x84, (byte) 0x30, (byte) 0xe2,
+            (byte) 0x45, (byte) 0x21, (byte) 0xb3, (byte) 0x75, (byte) 0xf5, (byte) 0x79,
+            (byte) 0x02, (byte) 0xda, (byte) 0x50, (byte) 0xa3, (byte) 0x8b, (byte) 0xce,
+            (byte) 0xc3, (byte) 0x8e, (byte) 0x0f, (byte) 0x25, (byte) 0xeb, (byte) 0x08,
+            (byte) 0x2c, (byte) 0xdd, (byte) 0x1c, (byte) 0xcf, (byte) 0xff, (byte) 0x3b,
+            (byte) 0xde, (byte) 0xb6, (byte) 0xaa, (byte) 0x2a, (byte) 0xa9, (byte) 0xc4,
+            (byte) 0x8a, (byte) 0x24, (byte) 0x24, (byte) 0xe6, (byte) 0x29, (byte) 0x0d,
+            (byte) 0x98, (byte) 0x4c, (byte) 0x32, (byte) 0xa1, (byte) 0x7b, (byte) 0x23,
+            (byte) 0x2b, (byte) 0x42, (byte) 0x30, (byte) 0xee, (byte) 0x78, (byte) 0x08,
+            (byte) 0x47, (byte) 0xad, (byte) 0xf2, (byte) 0x96, (byte) 0xd5, (byte) 0xf1,
+            (byte) 0x62, (byte) 0x42, (byte) 0x2d, (byte) 0x35, (byte) 0x19, (byte) 0xb4,
+            (byte) 0x3c, (byte) 0xc9, (byte) 0xc3, (byte) 0x5f, (byte) 0x03, (byte) 0x16,
+            (byte) 0x3a, (byte) 0x23, (byte) 0xac, (byte) 0xcb, (byte) 0xce, (byte) 0x9e,
+            (byte) 0x51, (byte) 0x2e, (byte) 0x6d, (byte) 0x02, (byte) 0x03, (byte) 0x01,
+            (byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x81, (byte) 0x80, (byte) 0x16,
+            (byte) 0x59, (byte) 0xc3, (byte) 0x24, (byte) 0x1d, (byte) 0x33, (byte) 0x98,
+            (byte) 0x9c, (byte) 0xc9, (byte) 0xc8, (byte) 0x2c, (byte) 0x88, (byte) 0xbf,
+            (byte) 0x0a, (byte) 0x01, (byte) 0xce, (byte) 0xfb, (byte) 0x34, (byte) 0x7a,
+            (byte) 0x58, (byte) 0x7a, (byte) 0xb0, (byte) 0xbf, (byte) 0xa6, (byte) 0xb2,
+            (byte) 0x60, (byte) 0xbe, (byte) 0x70, (byte) 0x21, (byte) 0xf5, (byte) 0xfc,
+            (byte) 0x85, (byte) 0x0d, (byte) 0x33, (byte) 0x58, (byte) 0xa1, (byte) 0xe5,
+            (byte) 0x09, (byte) 0x36, (byte) 0x84, (byte) 0xb2, (byte) 0x04, (byte) 0x0a,
+            (byte) 0x02, (byte) 0xd3, (byte) 0x88, (byte) 0x1f, (byte) 0x0c, (byte) 0x2b,
+            (byte) 0x1d, (byte) 0xe9, (byte) 0x3d, (byte) 0xe7, (byte) 0x79, (byte) 0xf9,
+            (byte) 0x32, (byte) 0x5c, (byte) 0x8a, (byte) 0x75, (byte) 0x49, (byte) 0x12,
+            (byte) 0xe4, (byte) 0x05, (byte) 0x26, (byte) 0xd4, (byte) 0x2e, (byte) 0x9e,
+            (byte) 0x1f, (byte) 0xcc, (byte) 0x54, (byte) 0xad, (byte) 0x33, (byte) 0x8d,
+            (byte) 0x99, (byte) 0x00, (byte) 0xdc, (byte) 0xf5, (byte) 0xb4, (byte) 0xa2,
+            (byte) 0x2f, (byte) 0xba, (byte) 0xe5, (byte) 0x62, (byte) 0x30, (byte) 0x6d,
+            (byte) 0xe6, (byte) 0x3d, (byte) 0xeb, (byte) 0x24, (byte) 0xc2, (byte) 0xdc,
+            (byte) 0x5f, (byte) 0xb7, (byte) 0x16, (byte) 0x35, (byte) 0xa3, (byte) 0x98,
+            (byte) 0x98, (byte) 0xa8, (byte) 0xef, (byte) 0xe8, (byte) 0xc4, (byte) 0x96,
+            (byte) 0x6d, (byte) 0x38, (byte) 0xab, (byte) 0x26, (byte) 0x6d, (byte) 0x30,
+            (byte) 0xc2, (byte) 0xa0, (byte) 0x44, (byte) 0xe4, (byte) 0xff, (byte) 0x7e,
+            (byte) 0xbe, (byte) 0x7c, (byte) 0x33, (byte) 0xa5, (byte) 0x10, (byte) 0xad,
+            (byte) 0xd7, (byte) 0x1e, (byte) 0x13, (byte) 0x20, (byte) 0xb3, (byte) 0x1f,
+            (byte) 0x41, (byte) 0x02, (byte) 0x41, (byte) 0x00, (byte) 0xf1, (byte) 0x89,
+            (byte) 0x07, (byte) 0x0f, (byte) 0xe8, (byte) 0xcf, (byte) 0xab, (byte) 0x13,
+            (byte) 0x2a, (byte) 0x8f, (byte) 0x88, (byte) 0x80, (byte) 0x11, (byte) 0x9a,
+            (byte) 0x79, (byte) 0xb6, (byte) 0x59, (byte) 0x3a, (byte) 0x50, (byte) 0x6e,
+            (byte) 0x57, (byte) 0x37, (byte) 0xab, (byte) 0x2a, (byte) 0xd2, (byte) 0xaa,
+            (byte) 0xd9, (byte) 0x72, (byte) 0x73, (byte) 0xff, (byte) 0x8b, (byte) 0x47,
+            (byte) 0x76, (byte) 0xdd, (byte) 0xdc, (byte) 0xf5, (byte) 0x97, (byte) 0x44,
+            (byte) 0x3a, (byte) 0x78, (byte) 0xbe, (byte) 0x17, (byte) 0xb4, (byte) 0x22,
+            (byte) 0x6f, (byte) 0xe5, (byte) 0x23, (byte) 0x70, (byte) 0x1d, (byte) 0x10,
+            (byte) 0x5d, (byte) 0xba, (byte) 0x16, (byte) 0x81, (byte) 0xf1, (byte) 0x45,
+            (byte) 0xce, (byte) 0x30, (byte) 0xb4, (byte) 0xab, (byte) 0x80, (byte) 0xe4,
+            (byte) 0x98, (byte) 0x31, (byte) 0x02, (byte) 0x41, (byte) 0x00, (byte) 0xda,
+            (byte) 0x82, (byte) 0x9d, (byte) 0x3f, (byte) 0xca, (byte) 0x2f, (byte) 0xe1,
+            (byte) 0xd4, (byte) 0x86, (byte) 0x77, (byte) 0x48, (byte) 0xa6, (byte) 0xab,
+            (byte) 0xab, (byte) 0x1c, (byte) 0x42, (byte) 0x5c, (byte) 0xd5, (byte) 0xc7,
+            (byte) 0x46, (byte) 0x59, (byte) 0x91, (byte) 0x3f, (byte) 0xfc, (byte) 0xcc,
+            (byte) 0xec, (byte) 0xc2, (byte) 0x40, (byte) 0x12, (byte) 0x2c, (byte) 0x8d,
+            (byte) 0x1f, (byte) 0xa2, (byte) 0x18, (byte) 0x88, (byte) 0xee, (byte) 0x82,
+            (byte) 0x4a, (byte) 0x5a, (byte) 0x5e, (byte) 0x88, (byte) 0x20, (byte) 0xe3,
+            (byte) 0x7b, (byte) 0xe0, (byte) 0xd8, (byte) 0x3a, (byte) 0x52, (byte) 0x9a,
+            (byte) 0x26, (byte) 0x6a, (byte) 0x04, (byte) 0xec, (byte) 0xe8, (byte) 0xb9,
+            (byte) 0x48, (byte) 0x40, (byte) 0xe1, (byte) 0xe1, (byte) 0x83, (byte) 0xa6,
+            (byte) 0x67, (byte) 0xa6, (byte) 0xfd, (byte) 0x02, (byte) 0x41, (byte) 0x00,
+            (byte) 0x89, (byte) 0x72, (byte) 0x3e, (byte) 0xb0, (byte) 0x90, (byte) 0xfd,
+            (byte) 0x4c, (byte) 0x0e, (byte) 0xd6, (byte) 0x13, (byte) 0x63, (byte) 0xcb,
+            (byte) 0xed, (byte) 0x38, (byte) 0x88, (byte) 0xb6, (byte) 0x79, (byte) 0xc4,
+            (byte) 0x33, (byte) 0x6c, (byte) 0xf6, (byte) 0xf8, (byte) 0xd8, (byte) 0xd0,
+            (byte) 0xbf, (byte) 0x9d, (byte) 0x35, (byte) 0xac, (byte) 0x69, (byte) 0xd2,
+            (byte) 0x2b, (byte) 0xc1, (byte) 0xf9, (byte) 0x24, (byte) 0x7b, (byte) 0xce,
+            (byte) 0xcd, (byte) 0xcb, (byte) 0xa7, (byte) 0xb2, (byte) 0x7a, (byte) 0x0a,
+            (byte) 0x27, (byte) 0x19, (byte) 0xc9, (byte) 0xaf, (byte) 0x0d, (byte) 0x21,
+            (byte) 0x89, (byte) 0x88, (byte) 0x7c, (byte) 0xad, (byte) 0x9e, (byte) 0x8d,
+            (byte) 0x47, (byte) 0x6d, (byte) 0x3f, (byte) 0xce, (byte) 0x7b, (byte) 0xa1,
+            (byte) 0x74, (byte) 0xf1, (byte) 0xa0, (byte) 0xa1, (byte) 0x02, (byte) 0x41,
+            (byte) 0x00, (byte) 0xd9, (byte) 0xa8, (byte) 0xf5, (byte) 0xfe, (byte) 0xce,
+            (byte) 0xe6, (byte) 0x77, (byte) 0x6b, (byte) 0xfe, (byte) 0x2d, (byte) 0xe0,
+            (byte) 0x1e, (byte) 0xb6, (byte) 0x2e, (byte) 0x12, (byte) 0x4e, (byte) 0x40,
+            (byte) 0xaf, (byte) 0x6a, (byte) 0x7b, (byte) 0x37, (byte) 0x49, (byte) 0x2a,
+            (byte) 0x96, (byte) 0x25, (byte) 0x83, (byte) 0x49, (byte) 0xd4, (byte) 0x0c,
+            (byte) 0xc6, (byte) 0x78, (byte) 0x25, (byte) 0x24, (byte) 0x90, (byte) 0x90,
+            (byte) 0x06, (byte) 0x15, (byte) 0x9e, (byte) 0xfe, (byte) 0xf9, (byte) 0xdf,
+            (byte) 0x5b, (byte) 0xf3, (byte) 0x7e, (byte) 0x38, (byte) 0x70, (byte) 0xeb,
+            (byte) 0x57, (byte) 0xd0, (byte) 0xd9, (byte) 0xa7, (byte) 0x0e, (byte) 0x14,
+            (byte) 0xf7, (byte) 0x95, (byte) 0x68, (byte) 0xd5, (byte) 0xc8, (byte) 0xab,
+            (byte) 0x9d, (byte) 0x3a, (byte) 0x2b, (byte) 0x51, (byte) 0xf9, (byte) 0x02,
+            (byte) 0x41, (byte) 0x00, (byte) 0x96, (byte) 0xdf, (byte) 0xe9, (byte) 0x67,
+            (byte) 0x6c, (byte) 0xdc, (byte) 0x90, (byte) 0x14, (byte) 0xb4, (byte) 0x1d,
+            (byte) 0x22, (byte) 0x33, (byte) 0x4a, (byte) 0x31, (byte) 0xc1, (byte) 0x9d,
+            (byte) 0x2e, (byte) 0xff, (byte) 0x9a, (byte) 0x2a, (byte) 0x95, (byte) 0x4b,
+            (byte) 0x27, (byte) 0x74, (byte) 0xcb, (byte) 0x21, (byte) 0xc3, (byte) 0xd2,
+            (byte) 0x0b, (byte) 0xb2, (byte) 0x46, (byte) 0x87, (byte) 0xf8, (byte) 0x28,
+            (byte) 0x01, (byte) 0x8b, (byte) 0xd8, (byte) 0xb9, (byte) 0x4b, (byte) 0xcd,
+            (byte) 0x9a, (byte) 0x96, (byte) 0x41, (byte) 0x0e, (byte) 0x36, (byte) 0x6d,
+            (byte) 0x40, (byte) 0x42, (byte) 0xbc, (byte) 0xd9, (byte) 0xd3, (byte) 0x7b,
+            (byte) 0xbc, (byte) 0xa7, (byte) 0x92, (byte) 0x90, (byte) 0xdd, (byte) 0xa1,
+            (byte) 0x9c, (byte) 0xce, (byte) 0xa1, (byte) 0x87, (byte) 0x11, (byte) 0x51
+    };
+    public static final PrivateKey RSA_KEY1 = loadPrivateRSAKey(FAKE_RSA_KEY_1);
 
     private static X509Certificate loadCertificate(String blob) {
         try {
@@ -64,4 +209,13 @@
             return null;
         }
     }
+
+    private static PrivateKey loadPrivateRSAKey(byte[] fakeKey) {
+        try {
+            KeyFactory kf = KeyFactory.getInstance("RSA");
+            return kf.generatePrivate(new PKCS8EncodedKeySpec(fakeKey));
+        } catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
+            return null;
+        }
+    }
 }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointManagerTest.java b/service/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointManagerTest.java
index 1d9f937..69d7b8b 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointManagerTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointManagerTest.java
@@ -24,7 +24,9 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 import static org.mockito.MockitoAnnotations.initMocks;
@@ -38,10 +40,13 @@
 import android.os.UserHandle;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import com.android.server.wifi.Clock;
 import com.android.server.wifi.FakeKeys;
 import com.android.server.wifi.IMSIParameter;
 import com.android.server.wifi.SIMAccessor;
 import com.android.server.wifi.WifiInjector;
+import com.android.server.wifi.WifiKeyStore;
+import com.android.server.wifi.WifiNative;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -63,21 +68,27 @@
     private static final String TEST_FRIENDLY_NAME = "friendly name";
     private static final String TEST_REALM = "realm.test.com";
     private static final String TEST_IMSI = "1234*";
+    private static final long PROVIDER_ID = 1L;
 
     @Mock Context mContext;
-    @Mock WifiInjector mWifiInjector;
-    @Mock PasspointEventHandler.Callbacks mCallbacks;
+    @Mock WifiNative mWifiNative;
+    @Mock WifiKeyStore mWifiKeyStore;
+    @Mock Clock mClock;
     @Mock SIMAccessor mSimAccessor;
+    @Mock PasspointObjectFactory mObjectFactory;
+    @Mock PasspointEventHandler.Callbacks mCallbacks;
     PasspointManager mManager;
 
     /** Sets up test. */
     @Before
     public void setUp() throws Exception {
         initMocks(this);
-        mManager = new PasspointManager(mContext, mWifiInjector, mSimAccessor);
+        mManager = new PasspointManager(mContext, mWifiNative, mWifiKeyStore, mClock,
+                mSimAccessor, mObjectFactory);
         ArgumentCaptor<PasspointEventHandler.Callbacks> callbacks =
                 ArgumentCaptor.forClass(PasspointEventHandler.Callbacks.class);
-        verify(mWifiInjector).makePasspointEventHandler(callbacks.capture());
+        verify(mObjectFactory).makePasspointEventHandler(any(WifiNative.class),
+                                                         callbacks.capture());
         mCallbacks = callbacks.getValue();
     }
 
@@ -115,6 +126,19 @@
     }
 
     /**
+     * Create a mock PasspointProvider with default expectations.
+     *
+     * @param config The configuration associated with the provider
+     * @return {@link com.android.server.wifi.hotspot2.PasspointProvider}
+     */
+    private PasspointProvider createMockProvider(PasspointConfiguration config) {
+        PasspointProvider provider = mock(PasspointProvider.class);
+        when(provider.installCertsAndKeys()).thenReturn(true);
+        when(provider.getConfig()).thenReturn(config);
+        return provider;
+    }
+
+    /**
      * Validate the broadcast intent when icon file retrieval succeeded.
      *
      * @throws Exception
@@ -200,11 +224,16 @@
         config.credential.userCredential.password = "password";
         config.credential.userCredential.eapType = EAPConstants.EAP_TTLS;
         config.credential.userCredential.nonEapInnerMethod = "MS-CHAP";
+        PasspointProvider provider = createMockProvider(config);
+        when(mClock.getWallClockMillis()).thenReturn(PROVIDER_ID);
+        when(mObjectFactory.makePasspointProvider(config, mWifiKeyStore, PROVIDER_ID))
+                .thenReturn(provider);
         assertTrue(mManager.addProvider(config));
         verifyInstalledConfig(config);
 
         // Remove the provider.
         assertTrue(mManager.removeProvider(TEST_FQDN));
+        verify(provider).uninstallCertsAndKeys();
         assertEquals(null, mManager.getProviderConfigs());
     }
 
@@ -226,11 +255,16 @@
         config.credential.simCredential.eapType = EAPConstants.EAP_SIM;
         when(mSimAccessor.getMatchingImsis(new IMSIParameter(TEST_IMSI)))
                 .thenReturn(new ArrayList<String>());
+        PasspointProvider provider = createMockProvider(config);
+        when(mClock.getWallClockMillis()).thenReturn(PROVIDER_ID);
+        when(mObjectFactory.makePasspointProvider(config, mWifiKeyStore, PROVIDER_ID))
+                .thenReturn(provider);
         assertTrue(mManager.addProvider(config));
         verifyInstalledConfig(config);
 
         // Remove the provider.
         assertTrue(mManager.removeProvider(TEST_FQDN));
+        verify(provider).uninstallCertsAndKeys();
         assertEquals(null, mManager.getProviderConfigs());
     }
 
@@ -276,6 +310,10 @@
         origConfig.credential.simCredential.eapType = EAPConstants.EAP_SIM;
         when(mSimAccessor.getMatchingImsis(new IMSIParameter(TEST_IMSI)))
                 .thenReturn(new ArrayList<String>());
+        PasspointProvider origProvider = createMockProvider(origConfig);
+        when(mClock.getWallClockMillis()).thenReturn(PROVIDER_ID);
+        when(mObjectFactory.makePasspointProvider(origConfig, mWifiKeyStore, PROVIDER_ID))
+                .thenReturn(origProvider);
         assertTrue(mManager.addProvider(origConfig));
         verifyInstalledConfig(origConfig);
 
@@ -293,7 +331,49 @@
         newConfig.credential.userCredential.password = "password";
         newConfig.credential.userCredential.eapType = EAPConstants.EAP_TTLS;
         newConfig.credential.userCredential.nonEapInnerMethod = "MS-CHAP";
+        PasspointProvider newProvider = createMockProvider(newConfig);
+        when(mClock.getWallClockMillis()).thenReturn(PROVIDER_ID);
+        when(mObjectFactory.makePasspointProvider(newConfig, mWifiKeyStore, PROVIDER_ID))
+                .thenReturn(newProvider);
         assertTrue(mManager.addProvider(newConfig));
         verifyInstalledConfig(newConfig);
     }
+
+    /**
+     * Verify that adding a provider will fail when failing to install certificates and
+     * key to the keystore.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void addProviderOnKeyInstallationFailiure() throws Exception {
+        PasspointConfiguration config = new PasspointConfiguration();
+        config.homeSp = new HomeSP();
+        config.homeSp.fqdn = TEST_FQDN;
+        config.homeSp.friendlyName = TEST_FRIENDLY_NAME;
+        config.credential = new Credential();
+        config.credential.realm = TEST_REALM;
+        config.credential.caCertificate = FakeKeys.CA_CERT0;
+        config.credential.userCredential = new Credential.UserCredential();
+        config.credential.userCredential.username = "username";
+        config.credential.userCredential.password = "password";
+        config.credential.userCredential.eapType = EAPConstants.EAP_TTLS;
+        config.credential.userCredential.nonEapInnerMethod = "MS-CHAP";
+        PasspointProvider provider = mock(PasspointProvider.class);
+        when(provider.installCertsAndKeys()).thenReturn(false);
+        when(mClock.getWallClockMillis()).thenReturn(PROVIDER_ID);
+        when(mObjectFactory.makePasspointProvider(config, mWifiKeyStore, PROVIDER_ID))
+                .thenReturn(provider);
+        assertFalse(mManager.addProvider(config));
+    }
+
+    /**
+     * Verify that removing a non-existing provider will fail.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void removeNonExistingProvider() throws Exception {
+        assertFalse(mManager.removeProvider(TEST_FQDN));
+    }
 }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointProviderTest.java b/service/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointProviderTest.java
index 3ff01bc..db8a43a 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointProviderTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointProviderTest.java
@@ -18,20 +18,54 @@
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.MockitoAnnotations.initMocks;
 
 import android.net.wifi.hotspot2.PasspointConfiguration;
+import android.net.wifi.hotspot2.pps.Credential;
 import android.net.wifi.hotspot2.pps.HomeSP;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import com.android.server.wifi.FakeKeys;
+import com.android.server.wifi.WifiKeyStore;
+
+import org.junit.Before;
 import org.junit.Test;
+import org.mockito.Mock;
+
+import java.security.MessageDigest;
+import java.security.cert.X509Certificate;
 
 /**
  * Unit tests for {@link com.android.server.wifi.hotspot2.PasspointProvider}.
  */
 @SmallTest
 public class PasspointProviderTest {
+    private static final long PROVIDER_ID = 12L;
+    private static final String CA_CERTIFICATE_ALIAS = "CACERT_HS2_12";
+    private static final String CLIENT_CERTIFICATE_ALIAS = "USRCERT_HS2_12";
+    private static final String CLIENT_PRIVATE_KEY_ALIAS = "USRPKEY_HS2_12";
+
+    @Mock WifiKeyStore mKeyStore;
     PasspointProvider mProvider;
 
+    /** Sets up test. */
+    @Before
+    public void setUp() throws Exception {
+        initMocks(this);
+    }
+
+    /**
+     * Helper function for creating a provider instance for testing.
+     *
+     * @param config The configuration associated with the provider
+     * @return {@link com.android.server.wifi.hotspot2.PasspointProvider}
+     */
+    private PasspointProvider createProvider(PasspointConfiguration config) {
+        return new PasspointProvider(config, mKeyStore, PROVIDER_ID);
+    }
+
     /**
      * Verify that the configuration associated with the provider is the same or not the same
      * as the expected configuration.
@@ -60,7 +94,7 @@
         PasspointConfiguration config = new PasspointConfiguration();
         config.homeSp = new HomeSP();
         config.homeSp.fqdn = "test1";
-        mProvider = new PasspointProvider(config);
+        mProvider = createProvider(config);
         verifyInstalledConfig(config, true);
 
         // Modify the original configuration, the configuration maintained by the provider
@@ -81,7 +115,7 @@
         PasspointConfiguration config = new PasspointConfiguration();
         config.homeSp = new HomeSP();
         config.homeSp.fqdn = "test1";
-        mProvider = new PasspointProvider(config);
+        mProvider = createProvider(config);
         verifyInstalledConfig(config, true);
 
         // Modify the retrieved configuration, verify the configuration maintained by the
@@ -90,4 +124,118 @@
         retrievedConfig.homeSp.fqdn = "test2";
         verifyInstalledConfig(retrievedConfig, false);
     }
+
+    /**
+     * Verify a successful installation of certificates and key.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void installCertsAndKeysSuccess() throws Exception {
+        // Create a dummy configuration with certificate credential.
+        PasspointConfiguration config = new PasspointConfiguration();
+        config.credential = new Credential();
+        config.credential.certCredential = new Credential.CertificateCredential();
+        config.credential.certCredential.certSha256FingerPrint =
+                MessageDigest.getInstance("SHA-256").digest(FakeKeys.CLIENT_CERT.getEncoded());
+        config.credential.caCertificate = FakeKeys.CA_CERT0;
+        config.credential.clientPrivateKey = FakeKeys.RSA_KEY1;
+        config.credential.clientCertificateChain = new X509Certificate[] {FakeKeys.CLIENT_CERT};
+        mProvider = createProvider(config);
+
+        // Install client certificate and key to the keystore successfully.
+        when(mKeyStore.putCertInKeyStore(CA_CERTIFICATE_ALIAS, FakeKeys.CA_CERT0))
+                .thenReturn(true);
+        when(mKeyStore.putKeyInKeyStore(CLIENT_PRIVATE_KEY_ALIAS, FakeKeys.RSA_KEY1))
+                .thenReturn(true);
+        when(mKeyStore.putCertInKeyStore(CLIENT_CERTIFICATE_ALIAS, FakeKeys.CLIENT_CERT))
+                .thenReturn(true);
+        assertTrue(mProvider.installCertsAndKeys());
+
+        // Verify client certificate and key in the configuration gets cleared and aliases
+        // are set correctly.
+        PasspointConfiguration curConfig = mProvider.getConfig();
+        assertTrue(curConfig.credential.caCertificate == null);
+        assertTrue(curConfig.credential.clientPrivateKey == null);
+        assertTrue(curConfig.credential.clientCertificateChain == null);
+        assertTrue(mProvider.getCaCertificateAlias().equals(CA_CERTIFICATE_ALIAS));
+        assertTrue(mProvider.getClientPrivateKeyAlias().equals(CLIENT_PRIVATE_KEY_ALIAS));
+        assertTrue(mProvider.getClientCertificateAlias().equals(CLIENT_CERTIFICATE_ALIAS));
+    }
+
+    /**
+     * Verify a failure installation of certificates and key.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void installCertsAndKeysFailure() throws Exception {
+        // Create a dummy configuration with certificate credential.
+        PasspointConfiguration config = new PasspointConfiguration();
+        config.credential = new Credential();
+        config.credential.certCredential = new Credential.CertificateCredential();
+        config.credential.certCredential.certSha256FingerPrint =
+                MessageDigest.getInstance("SHA-256").digest(FakeKeys.CLIENT_CERT.getEncoded());
+        config.credential.caCertificate = FakeKeys.CA_CERT0;
+        config.credential.clientPrivateKey = FakeKeys.RSA_KEY1;
+        config.credential.clientCertificateChain = new X509Certificate[] {FakeKeys.CLIENT_CERT};
+        mProvider = createProvider(config);
+
+        // Failed to install client certificate to the keystore.
+        when(mKeyStore.putCertInKeyStore(CA_CERTIFICATE_ALIAS, FakeKeys.CA_CERT0))
+                .thenReturn(true);
+        when(mKeyStore.putKeyInKeyStore(CLIENT_PRIVATE_KEY_ALIAS, FakeKeys.RSA_KEY1))
+                .thenReturn(true);
+        when(mKeyStore.putCertInKeyStore(CLIENT_CERTIFICATE_ALIAS, FakeKeys.CLIENT_CERT))
+                .thenReturn(false);
+        assertFalse(mProvider.installCertsAndKeys());
+
+        // Verify certificates and key in the configuration are not cleared and aliases
+        // are not set.
+        PasspointConfiguration curConfig = mProvider.getConfig();
+        assertTrue(curConfig.credential.caCertificate != null);
+        assertTrue(curConfig.credential.clientCertificateChain != null);
+        assertTrue(curConfig.credential.clientPrivateKey != null);
+        assertTrue(mProvider.getCaCertificateAlias() == null);
+        assertTrue(mProvider.getClientPrivateKeyAlias() == null);
+        assertTrue(mProvider.getClientCertificateAlias() == null);
+    }
+
+    /**
+     * Verify a successful uninstallation of certificates and key.
+     */
+    @Test
+    public void uninstallCertsAndKeys() throws Exception {
+        // Create a dummy configuration with certificate credential.
+        PasspointConfiguration config = new PasspointConfiguration();
+        config.credential = new Credential();
+        config.credential.certCredential = new Credential.CertificateCredential();
+        config.credential.certCredential.certSha256FingerPrint =
+                MessageDigest.getInstance("SHA-256").digest(FakeKeys.CLIENT_CERT.getEncoded());
+        config.credential.caCertificate = FakeKeys.CA_CERT0;
+        config.credential.clientPrivateKey = FakeKeys.RSA_KEY1;
+        config.credential.clientCertificateChain = new X509Certificate[] {FakeKeys.CLIENT_CERT};
+        mProvider = createProvider(config);
+
+        // Install client certificate and key to the keystore successfully.
+        when(mKeyStore.putCertInKeyStore(CA_CERTIFICATE_ALIAS, FakeKeys.CA_CERT0))
+                .thenReturn(true);
+        when(mKeyStore.putKeyInKeyStore(CLIENT_PRIVATE_KEY_ALIAS, FakeKeys.RSA_KEY1))
+                .thenReturn(true);
+        when(mKeyStore.putCertInKeyStore(CLIENT_CERTIFICATE_ALIAS, FakeKeys.CLIENT_CERT))
+                .thenReturn(true);
+        assertTrue(mProvider.installCertsAndKeys());
+        assertTrue(mProvider.getCaCertificateAlias().equals(CA_CERTIFICATE_ALIAS));
+        assertTrue(mProvider.getClientPrivateKeyAlias().equals(CLIENT_PRIVATE_KEY_ALIAS));
+        assertTrue(mProvider.getClientCertificateAlias().equals(CLIENT_CERTIFICATE_ALIAS));
+
+        // Uninstall certificates and key from the keystore.
+        mProvider.uninstallCertsAndKeys();
+        verify(mKeyStore).removeEntryFromKeyStore(CA_CERTIFICATE_ALIAS);
+        verify(mKeyStore).removeEntryFromKeyStore(CLIENT_CERTIFICATE_ALIAS);
+        verify(mKeyStore).removeEntryFromKeyStore(CLIENT_PRIVATE_KEY_ALIAS);
+        assertTrue(mProvider.getCaCertificateAlias() == null);
+        assertTrue(mProvider.getClientPrivateKeyAlias() == null);
+        assertTrue(mProvider.getClientCertificateAlias() == null);
+    }
 }