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