1/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.wifi;
18
19import android.net.wifi.WifiConfiguration;
20import android.net.wifi.WifiEnterpriseConfig;
21import android.os.Process;
22import android.security.Credentials;
23import android.security.KeyChain;
24import android.security.KeyStore;
25import android.text.TextUtils;
26import android.util.ArraySet;
27import android.util.Log;
28
29import java.io.IOException;
30import java.security.Key;
31import java.security.cert.Certificate;
32import java.security.cert.CertificateException;
33import java.security.cert.X509Certificate;
34import java.util.ArrayList;
35import java.util.Arrays;
36import java.util.List;
37import java.util.Set;
38
39/**
40 * This class provides the methods to access keystore for certificate management.
41 *
42 * NOTE: This class should only be used from WifiConfigManager!
43 */
44public class WifiKeyStore {
45    private static final String TAG = "WifiKeyStore";
46
47    private boolean mVerboseLoggingEnabled = false;
48
49    private final KeyStore mKeyStore;
50
51    WifiKeyStore(KeyStore keyStore) {
52        mKeyStore = keyStore;
53    }
54
55    /**
56     * Enable verbose logging.
57     */
58    void enableVerboseLogging(boolean verbose) {
59        mVerboseLoggingEnabled = verbose;
60    }
61
62    // Certificate and private key management for EnterpriseConfig
63    private static boolean needsKeyStore(WifiEnterpriseConfig config) {
64        return (config.getClientCertificate() != null || config.getCaCertificate() != null);
65    }
66
67    private static boolean isHardwareBackedKey(Key key) {
68        return KeyChain.isBoundKeyAlgorithm(key.getAlgorithm());
69    }
70
71    private static boolean hasHardwareBackedKey(Certificate certificate) {
72        return isHardwareBackedKey(certificate.getPublicKey());
73    }
74
75    /**
76     * Install keys for given enterprise network.
77     *
78     * @param existingConfig Existing config corresponding to the network already stored in our
79     *                       database. This maybe null if it's a new network.
80     * @param config         Config corresponding to the network.
81     * @return true if successful, false otherwise.
82     */
83    private boolean installKeys(WifiEnterpriseConfig existingConfig, WifiEnterpriseConfig config,
84            String name) {
85        boolean ret = true;
86        String privKeyName = Credentials.USER_PRIVATE_KEY + name;
87        String userCertName = Credentials.USER_CERTIFICATE + name;
88        Certificate[] clientCertificateChain = config.getClientCertificateChain();
89        if (clientCertificateChain != null && clientCertificateChain.length != 0) {
90            byte[] privKeyData = config.getClientPrivateKey().getEncoded();
91            if (mVerboseLoggingEnabled) {
92                if (isHardwareBackedKey(config.getClientPrivateKey())) {
93                    Log.d(TAG, "importing keys " + name + " in hardware backed store");
94                } else {
95                    Log.d(TAG, "importing keys " + name + " in software backed store");
96                }
97            }
98            ret = mKeyStore.importKey(privKeyName, privKeyData, Process.WIFI_UID,
99                    KeyStore.FLAG_NONE);
100
101            if (!ret) {
102                return ret;
103            }
104
105            ret = putCertsInKeyStore(userCertName, clientCertificateChain);
106            if (!ret) {
107                // Remove private key installed
108                mKeyStore.delete(privKeyName, Process.WIFI_UID);
109                return ret;
110            }
111        }
112
113        X509Certificate[] caCertificates = config.getCaCertificates();
114        Set<String> oldCaCertificatesToRemove = new ArraySet<>();
115        if (existingConfig != null && existingConfig.getCaCertificateAliases() != null) {
116            oldCaCertificatesToRemove.addAll(
117                    Arrays.asList(existingConfig.getCaCertificateAliases()));
118        }
119        List<String> caCertificateAliases = null;
120        if (caCertificates != null) {
121            caCertificateAliases = new ArrayList<>();
122            for (int i = 0; i < caCertificates.length; i++) {
123                String alias = caCertificates.length == 1 ? name
124                        : String.format("%s_%d", name, i);
125
126                oldCaCertificatesToRemove.remove(alias);
127                ret = putCertInKeyStore(Credentials.CA_CERTIFICATE + alias, caCertificates[i]);
128                if (!ret) {
129                    // Remove client key+cert
130                    if (config.getClientCertificate() != null) {
131                        mKeyStore.delete(privKeyName, Process.WIFI_UID);
132                        mKeyStore.delete(userCertName, Process.WIFI_UID);
133                    }
134                    // Remove added CA certs.
135                    for (String addedAlias : caCertificateAliases) {
136                        mKeyStore.delete(Credentials.CA_CERTIFICATE + addedAlias, Process.WIFI_UID);
137                    }
138                    return ret;
139                } else {
140                    caCertificateAliases.add(alias);
141                }
142            }
143        }
144        // Remove old CA certs.
145        for (String oldAlias : oldCaCertificatesToRemove) {
146            mKeyStore.delete(Credentials.CA_CERTIFICATE + oldAlias, Process.WIFI_UID);
147        }
148        // Set alias names
149        if (config.getClientCertificate() != null) {
150            config.setClientCertificateAlias(name);
151            config.resetClientKeyEntry();
152        }
153
154        if (caCertificates != null) {
155            config.setCaCertificateAliases(
156                    caCertificateAliases.toArray(new String[caCertificateAliases.size()]));
157            config.resetCaCertificate();
158        }
159        return ret;
160    }
161
162    /**
163     * Install a certificate into the keystore.
164     *
165     * @param name The alias name of the certificate to be installed
166     * @param cert The certificate to be installed
167     * @return true on success
168     */
169    public boolean putCertInKeyStore(String name, Certificate cert) {
170        return putCertsInKeyStore(name, new Certificate[] {cert});
171    }
172
173    /**
174     * Install a client certificate chain into the keystore.
175     *
176     * @param name The alias name of the certificate to be installed
177     * @param certs The certificate chain to be installed
178     * @return true on success
179     */
180    public boolean putCertsInKeyStore(String name, Certificate[] certs) {
181        try {
182            byte[] certData = Credentials.convertToPem(certs);
183            if (mVerboseLoggingEnabled) {
184                Log.d(TAG, "putting " + certs.length + " certificate(s) "
185                        + name + " in keystore");
186            }
187            return mKeyStore.put(name, certData, Process.WIFI_UID, KeyStore.FLAG_NONE);
188        } catch (IOException e1) {
189            return false;
190        } catch (CertificateException e2) {
191            return false;
192        }
193    }
194
195    /**
196     * Install a key into the keystore.
197     *
198     * @param name The alias name of the key to be installed
199     * @param key The key to be installed
200     * @return true on success
201     */
202    public boolean putKeyInKeyStore(String name, Key key) {
203        byte[] privKeyData = key.getEncoded();
204        return mKeyStore.importKey(name, privKeyData, Process.WIFI_UID, KeyStore.FLAG_NONE);
205    }
206
207    /**
208     * Remove a certificate or key entry specified by the alias name from the keystore.
209     *
210     * @param name The alias name of the entry to be removed
211     * @return true on success
212     */
213    public boolean removeEntryFromKeyStore(String name) {
214        return mKeyStore.delete(name, Process.WIFI_UID);
215    }
216
217    /**
218     * Remove enterprise keys from the network config.
219     *
220     * @param config Config corresponding to the network.
221     */
222    public void removeKeys(WifiEnterpriseConfig config) {
223        String client = config.getClientCertificateAlias();
224        // a valid client certificate is configured
225        if (!TextUtils.isEmpty(client)) {
226            if (mVerboseLoggingEnabled) Log.d(TAG, "removing client private key and user cert");
227            mKeyStore.delete(Credentials.USER_PRIVATE_KEY + client, Process.WIFI_UID);
228            mKeyStore.delete(Credentials.USER_CERTIFICATE + client, Process.WIFI_UID);
229        }
230
231        String[] aliases = config.getCaCertificateAliases();
232        // a valid ca certificate is configured
233        if (aliases != null) {
234            for (String ca : aliases) {
235                if (!TextUtils.isEmpty(ca)) {
236                    if (mVerboseLoggingEnabled) Log.d(TAG, "removing CA cert: " + ca);
237                    mKeyStore.delete(Credentials.CA_CERTIFICATE + ca, Process.WIFI_UID);
238                }
239            }
240        }
241    }
242
243    /**
244     * Update/Install keys for given enterprise network.
245     *
246     * @param config         Config corresponding to the network.
247     * @param existingConfig Existing config corresponding to the network already stored in our
248     *                       database. This maybe null if it's a new network.
249     * @return true if successful, false otherwise.
250     */
251    public boolean updateNetworkKeys(WifiConfiguration config, WifiConfiguration existingConfig) {
252        WifiEnterpriseConfig enterpriseConfig = config.enterpriseConfig;
253        if (needsKeyStore(enterpriseConfig)) {
254            try {
255                /* config passed may include only fields being updated.
256                 * In order to generate the key id, fetch uninitialized
257                 * fields from the currently tracked configuration
258                 */
259                String keyId = config.getKeyIdForCredentials(existingConfig);
260                if (!installKeys(existingConfig != null
261                        ? existingConfig.enterpriseConfig : null, enterpriseConfig, keyId)) {
262                    Log.e(TAG, config.SSID + ": failed to install keys");
263                    return false;
264                }
265            } catch (IllegalStateException e) {
266                Log.e(TAG, config.SSID + " invalid config for key installation: " + e.getMessage());
267                return false;
268            }
269        }
270        return true;
271    }
272
273    /**
274     * Checks whether the configuration requires a software backed keystore or not.
275     * @param config WifiEnterprise config instance pointing to the enterprise configuration of the
276     *               network.
277     */
278    public static boolean needsSoftwareBackedKeyStore(WifiEnterpriseConfig config) {
279        String client = config.getClientCertificateAlias();
280        if (!TextUtils.isEmpty(client)) {
281            // a valid client certificate is configured
282
283            // BUGBUG(b/29578316): keyStore.get() never returns certBytes; because it is not
284            // taking WIFI_UID as a parameter. It always looks for certificate
285            // with SYSTEM_UID, and never finds any Wifi certificates. Assuming that
286            // all certificates need software keystore until we get the get() API
287            // fixed.
288            return true;
289        }
290        return false;
291    }
292
293}
294