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 
17 package com.android.server.wifi;
18 
19 import android.net.wifi.WifiConfiguration;
20 import android.net.wifi.WifiEnterpriseConfig;
21 import android.os.Process;
22 import android.security.Credentials;
23 import android.security.KeyChain;
24 import android.security.KeyStore;
25 import android.text.TextUtils;
26 import android.util.ArraySet;
27 import android.util.Log;
28 
29 import java.io.ByteArrayInputStream;
30 import java.io.IOException;
31 import java.io.InputStream;
32 import java.security.Key;
33 import java.security.cert.Certificate;
34 import java.security.cert.CertificateException;
35 import java.security.cert.CertificateFactory;
36 import java.security.cert.X509Certificate;
37 import java.util.ArrayList;
38 import java.util.Arrays;
39 import java.util.List;
40 import java.util.Set;
41 
42 /**
43  * This class provides the methods to access keystore for certificate management.
44  *
45  * NOTE: This class should only be used from WifiConfigManager!
46  */
47 public class WifiKeyStore {
48     private static final String TAG = "WifiKeyStore";
49 
50     private boolean mVerboseLoggingEnabled = false;
51 
52     private final KeyStore mKeyStore;
53 
WifiKeyStore(KeyStore keyStore)54     WifiKeyStore(KeyStore keyStore) {
55         mKeyStore = keyStore;
56     }
57 
58     /**
59      * Enable verbose logging.
60      */
enableVerboseLogging(boolean verbose)61     void enableVerboseLogging(boolean verbose) {
62         mVerboseLoggingEnabled = verbose;
63     }
64 
65     // Certificate and private key management for EnterpriseConfig
needsKeyStore(WifiEnterpriseConfig config)66     private static boolean needsKeyStore(WifiEnterpriseConfig config) {
67         return (config.getClientCertificate() != null || config.getCaCertificate() != null);
68     }
69 
isHardwareBackedKey(Key key)70     private static boolean isHardwareBackedKey(Key key) {
71         return KeyChain.isBoundKeyAlgorithm(key.getAlgorithm());
72     }
73 
hasHardwareBackedKey(Certificate certificate)74     private static boolean hasHardwareBackedKey(Certificate certificate) {
75         return isHardwareBackedKey(certificate.getPublicKey());
76     }
77 
78     /**
79      * Install keys for given enterprise network.
80      *
81      * @param existingConfig Existing config corresponding to the network already stored in our
82      *                       database. This maybe null if it's a new network.
83      * @param config         Config corresponding to the network.
84      * @return true if successful, false otherwise.
85      */
installKeys(WifiEnterpriseConfig existingConfig, WifiEnterpriseConfig config, String name)86     private boolean installKeys(WifiEnterpriseConfig existingConfig, WifiEnterpriseConfig config,
87             String name) {
88         boolean ret = true;
89         String privKeyName = Credentials.USER_PRIVATE_KEY + name;
90         String userCertName = Credentials.USER_CERTIFICATE + name;
91         Certificate[] clientCertificateChain = config.getClientCertificateChain();
92         if (clientCertificateChain != null && clientCertificateChain.length != 0) {
93             byte[] privKeyData = config.getClientPrivateKey().getEncoded();
94             if (mVerboseLoggingEnabled) {
95                 if (isHardwareBackedKey(config.getClientPrivateKey())) {
96                     Log.d(TAG, "importing keys " + name + " in hardware backed store");
97                 } else {
98                     Log.d(TAG, "importing keys " + name + " in software backed store");
99                 }
100             }
101             ret = mKeyStore.importKey(privKeyName, privKeyData, Process.WIFI_UID,
102                     KeyStore.FLAG_NONE);
103 
104             if (!ret) {
105                 return ret;
106             }
107 
108             ret = putCertsInKeyStore(userCertName, clientCertificateChain);
109             if (!ret) {
110                 // Remove private key installed
111                 mKeyStore.delete(privKeyName, Process.WIFI_UID);
112                 return ret;
113             }
114         }
115 
116         X509Certificate[] caCertificates = config.getCaCertificates();
117         Set<String> oldCaCertificatesToRemove = new ArraySet<>();
118         if (existingConfig != null && existingConfig.getCaCertificateAliases() != null) {
119             oldCaCertificatesToRemove.addAll(
120                     Arrays.asList(existingConfig.getCaCertificateAliases()));
121         }
122         List<String> caCertificateAliases = null;
123         if (caCertificates != null) {
124             caCertificateAliases = new ArrayList<>();
125             for (int i = 0; i < caCertificates.length; i++) {
126                 String alias = caCertificates.length == 1 ? name
127                         : String.format("%s_%d", name, i);
128 
129                 oldCaCertificatesToRemove.remove(alias);
130                 ret = putCertInKeyStore(Credentials.CA_CERTIFICATE + alias, caCertificates[i]);
131                 if (!ret) {
132                     // Remove client key+cert
133                     if (config.getClientCertificate() != null) {
134                         mKeyStore.delete(privKeyName, Process.WIFI_UID);
135                         mKeyStore.delete(userCertName, Process.WIFI_UID);
136                     }
137                     // Remove added CA certs.
138                     for (String addedAlias : caCertificateAliases) {
139                         mKeyStore.delete(Credentials.CA_CERTIFICATE + addedAlias, Process.WIFI_UID);
140                     }
141                     return ret;
142                 } else {
143                     caCertificateAliases.add(alias);
144                 }
145             }
146         }
147         // Remove old CA certs.
148         for (String oldAlias : oldCaCertificatesToRemove) {
149             mKeyStore.delete(Credentials.CA_CERTIFICATE + oldAlias, Process.WIFI_UID);
150         }
151         // Set alias names
152         if (config.getClientCertificate() != null) {
153             config.setClientCertificateAlias(name);
154             config.resetClientKeyEntry();
155         }
156 
157         if (caCertificates != null) {
158             config.setCaCertificateAliases(
159                     caCertificateAliases.toArray(new String[caCertificateAliases.size()]));
160             config.resetCaCertificate();
161         }
162         return ret;
163     }
164 
165     /**
166      * Install a certificate into the keystore.
167      *
168      * @param name The alias name of the certificate to be installed
169      * @param cert The certificate to be installed
170      * @return true on success
171      */
putCertInKeyStore(String name, Certificate cert)172     public boolean putCertInKeyStore(String name, Certificate cert) {
173         return putCertsInKeyStore(name, new Certificate[] {cert});
174     }
175 
176     /**
177      * Install a client certificate chain into the keystore.
178      *
179      * @param name The alias name of the certificate to be installed
180      * @param certs The certificate chain to be installed
181      * @return true on success
182      */
putCertsInKeyStore(String name, Certificate[] certs)183     public boolean putCertsInKeyStore(String name, Certificate[] certs) {
184         try {
185             byte[] certData = Credentials.convertToPem(certs);
186             if (mVerboseLoggingEnabled) {
187                 Log.d(TAG, "putting " + certs.length + " certificate(s) "
188                         + name + " in keystore");
189             }
190             return mKeyStore.put(name, certData, Process.WIFI_UID, KeyStore.FLAG_NONE);
191         } catch (IOException e1) {
192             return false;
193         } catch (CertificateException e2) {
194             return false;
195         }
196     }
197 
198     /**
199      * Install a key into the keystore.
200      *
201      * @param name The alias name of the key to be installed
202      * @param key The key to be installed
203      * @return true on success
204      */
putKeyInKeyStore(String name, Key key)205     public boolean putKeyInKeyStore(String name, Key key) {
206         byte[] privKeyData = key.getEncoded();
207         return mKeyStore.importKey(name, privKeyData, Process.WIFI_UID, KeyStore.FLAG_NONE);
208     }
209 
210     /**
211      * Remove a certificate or key entry specified by the alias name from the keystore.
212      *
213      * @param name The alias name of the entry to be removed
214      * @return true on success
215      */
removeEntryFromKeyStore(String name)216     public boolean removeEntryFromKeyStore(String name) {
217         return mKeyStore.delete(name, Process.WIFI_UID);
218     }
219 
220     /**
221      * Remove enterprise keys from the network config.
222      *
223      * @param config Config corresponding to the network.
224      */
removeKeys(WifiEnterpriseConfig config)225     public void removeKeys(WifiEnterpriseConfig config) {
226         // Do not remove keys that were manually installed by the user
227         if (config.isAppInstalledDeviceKeyAndCert()) {
228             String client = config.getClientCertificateAlias();
229             // a valid client certificate is configured
230             if (!TextUtils.isEmpty(client)) {
231                 if (mVerboseLoggingEnabled) {
232                     Log.d(TAG, "removing client private key and user cert");
233                 }
234                 mKeyStore.delete(Credentials.USER_PRIVATE_KEY + client, Process.WIFI_UID);
235                 mKeyStore.delete(Credentials.USER_CERTIFICATE + client, Process.WIFI_UID);
236             }
237         }
238 
239         // Do not remove CA certs that were manually installed by the user
240         if (config.isAppInstalledCaCert()) {
241             String[] aliases = config.getCaCertificateAliases();
242             // a valid ca certificate is configured
243             if (aliases != null) {
244                 for (String ca : aliases) {
245                     if (!TextUtils.isEmpty(ca)) {
246                         if (mVerboseLoggingEnabled) {
247                             Log.d(TAG, "removing CA cert: " + ca);
248                         }
249                         mKeyStore.delete(Credentials.CA_CERTIFICATE + ca, Process.WIFI_UID);
250                     }
251                 }
252             }
253         }
254     }
255 
256 
257     /**
258      * @param certData byte array of the certificate
259      */
buildCACertificate(byte[] certData)260     private X509Certificate buildCACertificate(byte[] certData) {
261         try {
262             CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
263             InputStream inputStream = new ByteArrayInputStream(certData);
264             X509Certificate caCertificateX509 = (X509Certificate) certificateFactory
265                     .generateCertificate(inputStream);
266             return caCertificateX509;
267         } catch (CertificateException e) {
268             return null;
269         }
270     }
271     /**
272      * Update/Install keys for given enterprise network.
273      *
274      * @param config         Config corresponding to the network.
275      * @param existingConfig Existing config corresponding to the network already stored in our
276      *                       database. This maybe null if it's a new network.
277      * @return true if successful, false otherwise.
278      */
updateNetworkKeys(WifiConfiguration config, WifiConfiguration existingConfig)279     public boolean updateNetworkKeys(WifiConfiguration config, WifiConfiguration existingConfig) {
280         WifiEnterpriseConfig enterpriseConfig = config.enterpriseConfig;
281         if (!needsKeyStore(enterpriseConfig)) {
282             return true;
283         }
284 
285         try {
286             /* config passed may include only fields being updated.
287              * In order to generate the key id, fetch uninitialized
288              * fields from the currently tracked configuration
289              */
290             String keyId = config.getKeyIdForCredentials(existingConfig);
291             if (!installKeys(existingConfig != null
292                     ? existingConfig.enterpriseConfig : null, enterpriseConfig, keyId)) {
293                 Log.e(TAG, config.SSID + ": failed to install keys");
294                 return false;
295             }
296         } catch (IllegalStateException e) {
297             Log.e(TAG, config.SSID + " invalid config for key installation: " + e.getMessage());
298             return false;
299         }
300 
301         // For WPA3-Enterprise 192-bit networks, set the SuiteBCipher field based on the
302         // CA certificate type. Suite-B requires SHA384, reject other certs.
303         if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SUITE_B_192)) {
304             // Read the first CA certificate, and initialize
305             byte[] certData = mKeyStore.get(
306                     Credentials.CA_CERTIFICATE + config.enterpriseConfig.getCaCertificateAlias(),
307                     android.os.Process.WIFI_UID);
308 
309             if (certData == null) {
310                 Log.e(TAG, "Failed reading CA certificate for Suite-B");
311                 return false;
312             }
313 
314             X509Certificate x509CaCert = buildCACertificate(certData);
315 
316             if (x509CaCert != null) {
317                 String sigAlgOid = x509CaCert.getSigAlgOID();
318                 if (mVerboseLoggingEnabled) {
319                     Log.d(TAG, "Signature algorithm: " + sigAlgOid);
320                 }
321                 config.allowedSuiteBCiphers.clear();
322 
323                 // Wi-Fi alliance requires the use of both ECDSA secp384r1 and RSA 3072 certificates
324                 // in WPA3-Enterprise 192-bit security networks, which are also known as Suite-B-192
325                 // networks, even though NSA Suite-B-192 mandates ECDSA only. The use of the term
326                 // Suite-B was already coined in the IEEE 802.11-2016 specification for
327                 // AKM 00-0F-AC but the test plan for WPA3-Enterprise 192-bit for APs mandates
328                 // support for both RSA and ECDSA, and for STAs it mandates ECDSA and optionally
329                 // RSA. In order to be compatible with all WPA3-Enterprise 192-bit deployments,
330                 // we are supporting both types here.
331                 if (sigAlgOid.equals("1.2.840.113549.1.1.12")) {
332                     // sha384WithRSAEncryption
333                     config.allowedSuiteBCiphers.set(
334                             WifiConfiguration.SuiteBCipher.ECDHE_RSA);
335                     if (mVerboseLoggingEnabled) {
336                         Log.d(TAG, "Selecting Suite-B RSA");
337                     }
338                 } else if (sigAlgOid.equals("1.2.840.10045.4.3.3")) {
339                     // ecdsa-with-SHA384
340                     config.allowedSuiteBCiphers.set(
341                             WifiConfiguration.SuiteBCipher.ECDHE_ECDSA);
342                     if (mVerboseLoggingEnabled) {
343                         Log.d(TAG, "Selecting Suite-B ECDSA");
344                     }
345                 } else {
346                     Log.e(TAG, "Invalid CA certificate type for Suite-B: "
347                             + sigAlgOid);
348                     return false;
349                 }
350             } else {
351                 Log.e(TAG, "Invalid CA certificate for Suite-B");
352                 return false;
353             }
354         }
355         return true;
356     }
357 }
358