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.content.Context;
21 import android.net.wifi.WifiConfiguration;
22 import android.net.wifi.WifiEnterpriseConfig;
23 import android.os.UserHandle;
24 import android.security.KeyChain;
25 import android.text.TextUtils;
26 import android.util.ArraySet;
27 import android.util.Log;
28 
29 import com.android.internal.util.Preconditions;
30 import com.android.modules.utils.build.SdkLevel;
31 import com.android.server.wifi.util.ArrayUtils;
32 
33 import java.security.Key;
34 import java.security.KeyStore;
35 import java.security.KeyStoreException;
36 import java.security.Principal;
37 import java.security.cert.Certificate;
38 import java.security.cert.X509Certificate;
39 import java.security.interfaces.ECPublicKey;
40 import java.security.interfaces.RSAPublicKey;
41 import java.security.spec.ECParameterSpec;
42 import java.util.ArrayList;
43 import java.util.Arrays;
44 import java.util.List;
45 import java.util.Set;
46 
47 /**
48  * This class provides the methods to access keystore for certificate management.
49  *
50  * NOTE: This class should only be used from WifiConfigManager!
51  */
52 public class WifiKeyStore {
53     private static final String TAG = "WifiKeyStore";
54 
55     private boolean mVerboseLoggingEnabled = false;
56 
57     @Nullable private final KeyStore mKeyStore;
58     private final Context mContext;
59     private final FrameworkFacade mFrameworkFacade;
60 
WifiKeyStore(Context context, @Nullable KeyStore keyStore, FrameworkFacade frameworkFacade)61     WifiKeyStore(Context context, @Nullable KeyStore keyStore, FrameworkFacade frameworkFacade) {
62         mKeyStore = keyStore;
63         if (mKeyStore == null) {
64             Log.e(TAG, "Unable to retrieve keystore, all key operations will fail");
65         }
66         mContext = context;
67         mFrameworkFacade = frameworkFacade;
68     }
69 
70     /**
71      * Enable verbose logging.
72      */
enableVerboseLogging(boolean verbose)73     void enableVerboseLogging(boolean verbose) {
74         mVerboseLoggingEnabled = verbose;
75     }
76 
77     // Certificate and private key management for EnterpriseConfig
needsKeyStore(WifiEnterpriseConfig config)78     private static boolean needsKeyStore(WifiEnterpriseConfig config) {
79         return (config.getClientCertificate() != null || config.getCaCertificate() != null
80                 || config.getCaCertificateAlias() != null
81                 || config.getClientCertificateAlias() != null);
82     }
83 
isHardwareBackedKey(Key key)84     private static boolean isHardwareBackedKey(Key key) {
85         return KeyChain.isBoundKeyAlgorithm(key.getAlgorithm());
86     }
87 
hasHardwareBackedKey(Certificate certificate)88     private static boolean hasHardwareBackedKey(Certificate certificate) {
89         return isHardwareBackedKey(certificate.getPublicKey());
90     }
91 
92     /**
93      * Install keys for given enterprise network.
94      *
95      * @param existingConfig Existing config corresponding to the network already stored in our
96      *                       database. This maybe null if it's a new network.
97      * @param config         Config corresponding to the network.
98      * @param existingAlias  Alias for all the existing key store data stored.
99      * @param alias          Alias for all the key store data to store.
100      * @return true if successful, false otherwise.
101      */
installKeys(WifiEnterpriseConfig existingConfig, WifiEnterpriseConfig config, String existingAlias, String alias)102     private boolean installKeys(WifiEnterpriseConfig existingConfig, WifiEnterpriseConfig config,
103             String existingAlias, String alias) {
104         Preconditions.checkNotNull(mKeyStore);
105         Certificate[] clientCertificateChain = config.getClientCertificateChain();
106         if (!ArrayUtils.isEmpty(clientCertificateChain)) {
107             if (!putUserPrivKeyAndCertsInKeyStore(alias, config.getClientPrivateKey(),
108                     clientCertificateChain)) {
109                 return false;
110             }
111         }
112         X509Certificate[] caCertificates = config.getCaCertificates();
113         Set<String> oldCaCertificatesToRemove = new ArraySet<>();
114 
115         // Create a list of old Root CA certificate aliases from the existing configuration.
116         // Note that when updating from Settings, caCertificates is empty, therefore, all
117         // certificates must be kept. This happens because the certificate material is already
118         // stored in KeyStore and the only reference left is the alias.
119         if (existingConfig != null && existingConfig.getCaCertificateAliases() != null
120                 && existingConfig.isAppInstalledCaCert() && caCertificates != null) {
121             oldCaCertificatesToRemove.addAll(
122                     Arrays.asList(existingConfig.getCaCertificateAliases()));
123         }
124         List<String> caCertificateAliases = null;
125         if (caCertificates != null) {
126             caCertificateAliases = new ArrayList<>();
127             for (int i = 0; i < caCertificates.length; i++) {
128                 String caAlias = alias + "_" + i;
129 
130                 oldCaCertificatesToRemove.remove(caAlias);
131                 if (!putCaCertInKeyStore(caAlias, caCertificates[i])) {
132                     // cleanup everything on failure.
133                     removeEntryFromKeyStore(alias);
134                     for (String addedAlias : caCertificateAliases) {
135                         removeEntryFromKeyStore(addedAlias);
136                     }
137                     return false;
138                 }
139                 caCertificateAliases.add(caAlias);
140             }
141         }
142         // If alias changed, remove the old one.
143         if (!TextUtils.equals(alias, existingAlias)) {
144             if (existingConfig != null && existingConfig.isAppInstalledDeviceKeyAndCert()) {
145                 // Remove old private keys.
146                 removeEntryFromKeyStore(existingAlias);
147             }
148         }
149         // Remove any old CA certs.
150         for (String oldAlias : oldCaCertificatesToRemove) {
151             removeEntryFromKeyStore(oldAlias);
152         }
153         // Set alias names
154         if (config.getClientCertificate() != null) {
155             config.setClientCertificateAlias(alias);
156             config.resetClientKeyEntry();
157         }
158 
159         if (caCertificates != null) {
160             config.setCaCertificateAliases(
161                     caCertificateAliases.toArray(new String[caCertificateAliases.size()]));
162             config.resetCaCertificate();
163         }
164         return true;
165     }
166 
167     /**
168      * Install a CA certificate into the keystore.
169      *
170      * @param alias The alias name of the CA certificate to be installed
171      * @param cert The CA certificate to be installed
172      * @return true on success
173      */
putCaCertInKeyStore(String alias, Certificate cert)174     public boolean putCaCertInKeyStore(String alias, Certificate cert) {
175         try {
176             mKeyStore.setCertificateEntry(alias, cert);
177             return true;
178         } catch (KeyStoreException e) {
179             Log.e(TAG, "Failed to put CA certificate in keystore: " + e.getMessage());
180             return false;
181         }
182     }
183 
184     /**
185      * Install a private key + user certificate into the keystore.
186      *
187      * @param alias The alias name of the key to be installed
188      * @param key The private key to be installed
189      * @param certs User Certificate chain.
190      * @return true on success
191      */
putUserPrivKeyAndCertsInKeyStore(String alias, Key key, Certificate[] certs)192     public boolean putUserPrivKeyAndCertsInKeyStore(String alias, Key key, Certificate[] certs) {
193         try {
194             mKeyStore.setKeyEntry(alias, key, null, certs);
195             return true;
196         } catch (KeyStoreException e) {
197             Log.e(TAG, "Failed to put private key or certificate in keystore: " + e.getMessage());
198             return false;
199         }
200     }
201 
202     /**
203      * Remove a certificate or key entry specified by the alias name from the keystore.
204      *
205      * @param alias The alias name of the entry to be removed
206      * @return true on success
207      */
removeEntryFromKeyStore(String alias)208     public boolean removeEntryFromKeyStore(String alias) {
209         Preconditions.checkNotNull(mKeyStore);
210         try {
211             mKeyStore.deleteEntry(alias);
212             return true;
213         } catch (KeyStoreException e) {
214             return false;
215         }
216     }
217 
218     /**
219      * Remove enterprise keys from the network config.
220      *
221      * @param config Config corresponding to the network.
222      * @param forceRemove remove keys regardless of the key installer.
223      */
removeKeys(WifiEnterpriseConfig config, boolean forceRemove)224     public void removeKeys(WifiEnterpriseConfig config, boolean forceRemove) {
225         Preconditions.checkNotNull(mKeyStore);
226         // Do not remove keys that were manually installed by the user
227         if (forceRemove || 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, user cert and CA cert)");
233                 }
234                 // if there is only a single CA certificate, then that is also stored with
235                 // the same alias, hence will be removed here.
236                 removeEntryFromKeyStore(client);
237             }
238         }
239 
240         // Do not remove CA certs that were manually installed by the user
241         if (forceRemove || config.isAppInstalledCaCert()) {
242             String[] aliases = config.getCaCertificateAliases();
243             if (aliases == null || aliases.length == 0) {
244                 return;
245             }
246             // Remove all CA certificate.
247             for (String ca : aliases) {
248                 if (!TextUtils.isEmpty(ca)) {
249                     if (mVerboseLoggingEnabled) {
250                         Log.d(TAG, "removing CA cert: " + ca);
251                     }
252                     removeEntryFromKeyStore(ca);
253                 }
254             }
255         }
256     }
257 
258     /**
259      * Update/Install keys for given enterprise network.
260      *
261      * @param config         Config corresponding to the network.
262      * @param existingConfig Existing config corresponding to the network already stored in our
263      *                       database. This maybe null if it's a new network.
264      * @return true if successful, false otherwise.
265      */
updateNetworkKeys(WifiConfiguration config, WifiConfiguration existingConfig)266     public boolean updateNetworkKeys(WifiConfiguration config, WifiConfiguration existingConfig) {
267         Preconditions.checkNotNull(mKeyStore);
268         Preconditions.checkNotNull(config.enterpriseConfig);
269         WifiEnterpriseConfig enterpriseConfig = config.enterpriseConfig;
270         /* config passed may include only fields being updated.
271          * In order to generate the key id, fetch uninitialized
272          * fields from the currently tracked configuration
273          */
274         String keyId = config.getKeyIdForCredentials(existingConfig);
275         WifiEnterpriseConfig existingEnterpriseConfig = null;
276         String existingKeyId = null;
277         if (existingConfig != null) {
278             Preconditions.checkNotNull(existingConfig.enterpriseConfig);
279             existingEnterpriseConfig = existingConfig.enterpriseConfig;
280             existingKeyId = existingConfig.getKeyIdForCredentials(existingConfig);
281         }
282 
283         if (SdkLevel.isAtLeastS()) {
284             // If client key is in KeyChain, convert KeyChain alias into a grant string that can be
285             // used by the supplicant like a normal alias.
286             final String keyChainAlias = enterpriseConfig.getClientKeyPairAliasInternal();
287             if (keyChainAlias != null) {
288                 final String grantString = mFrameworkFacade.getWifiKeyGrantAsUser(
289                         mContext, UserHandle.getUserHandleForUid(config.creatorUid), keyChainAlias);
290                 if (grantString == null) {
291                     // The key is not granted to Wifi uid or the alias is invalid.
292                     Log.e(TAG, "Unable to get key grant");
293                     return false;
294                 }
295                 enterpriseConfig.setClientCertificateAlias(grantString);
296             }
297         }
298 
299         if (!needsKeyStore(enterpriseConfig)) {
300             return true;
301         }
302 
303         try {
304             if (!installKeys(existingEnterpriseConfig, enterpriseConfig, existingKeyId, keyId)) {
305                 Log.e(TAG, config.SSID + ": failed to install keys");
306                 return false;
307             }
308         } catch (IllegalStateException e) {
309             Log.e(TAG, config.SSID + " invalid config for key installation: " + e.getMessage());
310             return false;
311         }
312 
313         // For WPA3-Enterprise 192-bit networks, set the SuiteBCipher field based on the
314         // CA certificate type. Suite-B requires SHA384, reject other certs.
315         if (config.isSecurityType(WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT)) {
316             // Read the CA certificates, and initialize
317             String[] caAliases = config.enterpriseConfig.getCaCertificateAliases();
318             int caCertType = -1;
319 
320             // In TOFU mode, configure the security mode based on the user certificate only.
321             if (!config.enterpriseConfig.isTrustOnFirstUseEnabled()) {
322                 if (caAliases == null || caAliases.length == 0) {
323                     Log.e(TAG, "No CA aliases in profile");
324                     return false;
325                 }
326 
327                 int prevCaCertType = -1;
328                 for (String caAlias : caAliases) {
329                     Certificate caCert = null;
330                     try {
331                         caCert = mKeyStore.getCertificate(caAlias);
332                     } catch (KeyStoreException e) {
333                         Log.e(TAG, "Failed to get Suite-B certificate", e);
334                     }
335                     if (caCert == null || !(caCert instanceof X509Certificate)) {
336                         Log.e(TAG, "Failed reading CA certificate for Suite-B");
337                         return false;
338                     }
339 
340                     // Confirm that the CA certificate is compatible with Suite-B requirements
341                     caCertType = getSuiteBCipherFromCert((X509Certificate) caCert);
342                     if (caCertType < 0) {
343                         return false;
344                     }
345                     if (prevCaCertType != -1) {
346                         if (prevCaCertType != caCertType) {
347                             Log.e(TAG, "Incompatible CA certificates");
348                             return false;
349                         }
350                     }
351                     prevCaCertType = caCertType;
352                 }
353             }
354 
355             Certificate clientCert = null;
356             try {
357                 clientCert = mKeyStore.getCertificate(config.enterpriseConfig
358                         .getClientCertificateAlias());
359             } catch (KeyStoreException e) {
360                 Log.e(TAG, "Failed to get Suite-B client certificate", e);
361             }
362             if (clientCert == null || !(clientCert instanceof X509Certificate)) {
363                 Log.e(TAG, "Failed reading client certificate for Suite-B");
364                 return false;
365             }
366 
367             int clientCertType = getSuiteBCipherFromCert((X509Certificate) clientCert);
368             if (clientCertType < 0) {
369                 return false;
370             }
371 
372             if (clientCertType == caCertType
373                     || config.enterpriseConfig.isTrustOnFirstUseEnabled()) {
374                 config.enableSuiteBCiphers(
375                         clientCertType == WifiConfiguration.SuiteBCipher.ECDHE_ECDSA,
376                         clientCertType == WifiConfiguration.SuiteBCipher.ECDHE_RSA);
377             } else {
378                 Log.e(TAG, "Client certificate for Suite-B is incompatible with the CA "
379                         + "certificate");
380                 return false;
381             }
382         }
383         return true;
384     }
385 
386     /**
387      * Get the Suite-B cipher from the certificate
388      *
389      * @param x509Certificate Certificate to process
390      * @return WifiConfiguration.SuiteBCipher.ECDHE_RSA if the certificate OID matches the Suite-B
391      * requirements for RSA certificates, WifiConfiguration.SuiteBCipher.ECDHE_ECDSA if the
392      * certificate OID matches the Suite-B requirements for ECDSA certificates, or -1 otherwise.
393      */
getSuiteBCipherFromCert(X509Certificate x509Certificate)394     private int getSuiteBCipherFromCert(X509Certificate x509Certificate) {
395         String sigAlgOid = x509Certificate.getSigAlgOID();
396         if (mVerboseLoggingEnabled) {
397             Principal p = x509Certificate.getSubjectX500Principal();
398             if (p != null && !TextUtils.isEmpty(p.getName())) {
399                 Log.d(TAG, "Checking cert " + p.getName());
400             }
401         }
402         int bitLength = 0;
403 
404         // Wi-Fi alliance requires the use of both ECDSA secp384r1 and RSA 3072 certificates
405         // in WPA3-Enterprise 192-bit security networks, which are also known as Suite-B-192
406         // networks, even though NSA Suite-B-192 mandates ECDSA only. The use of the term
407         // Suite-B was already coined in the IEEE 802.11-2016 specification for
408         // AKM 00-0F-AC but the test plan for WPA3-Enterprise 192-bit for APs mandates
409         // support for both RSA and ECDSA, and for STAs it mandates ECDSA and optionally
410         // RSA. In order to be compatible with all WPA3-Enterprise 192-bit deployments,
411         // we are supporting both types here.
412         if (TextUtils.equals(sigAlgOid, "1.2.840.113549.1.1.12")) {
413             // sha384WithRSAEncryption
414             if (x509Certificate.getPublicKey() instanceof RSAPublicKey) {
415                 final RSAPublicKey rsaPublicKey = (RSAPublicKey) x509Certificate.getPublicKey();
416                 if (rsaPublicKey.getModulus() != null) {
417                     bitLength = rsaPublicKey.getModulus().bitLength();
418                     if (bitLength >= 3072) {
419                         if (mVerboseLoggingEnabled) {
420                             Log.d(TAG, "Found Suite-B RSA certificate");
421                         }
422                         return WifiConfiguration.SuiteBCipher.ECDHE_RSA;
423                     }
424                 }
425             }
426         } else if (TextUtils.equals(sigAlgOid, "1.2.840.10045.4.3.3")) {
427             // ecdsa-with-SHA384
428             if (x509Certificate.getPublicKey() instanceof ECPublicKey) {
429                 final ECPublicKey ecPublicKey = (ECPublicKey) x509Certificate.getPublicKey();
430                 final ECParameterSpec ecParameterSpec = ecPublicKey.getParams();
431                 if (ecParameterSpec != null && ecParameterSpec.getOrder() != null) {
432                     bitLength = ecParameterSpec.getOrder().bitLength();
433                     if (bitLength >= 384) {
434                         if (mVerboseLoggingEnabled) {
435                             Log.d(TAG, "Found Suite-B ECDSA certificate");
436                         }
437                         return WifiConfiguration.SuiteBCipher.ECDHE_ECDSA;
438                     }
439                 }
440             }
441         }
442         Log.e(TAG, "Invalid certificate type for Suite-B: " + sigAlgOid + " or insufficient"
443                 + " bit length: " + bitLength);
444         return -1;
445     }
446 
447     /**
448      * Requests a grant from KeyChain and populates client certificate alias with it.
449      *
450      * @return true if no problems encountered.
451      */
validateKeyChainAlias(String alias, int uid)452     public boolean validateKeyChainAlias(String alias, int uid) {
453         if (TextUtils.isEmpty(alias)) {
454             Log.e(TAG, "Alias cannot be empty");
455             return false;
456         }
457 
458         if (!SdkLevel.isAtLeastS()) {
459             Log.w(TAG, "Attempt to use a KeyChain key on pre-S device");
460             return false;
461         }
462 
463         return mFrameworkFacade.hasWifiKeyGrantAsUser(
464                 mContext, UserHandle.getUserHandleForUid(uid), alias);
465     }
466 }
467