• 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.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         if (existingConfig != null && existingConfig.getCaCertificateAliases() != null
115                 && existingConfig.isAppInstalledCaCert()) {
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 caAlias = String.format("%s_%d", alias, i);
124 
125                 oldCaCertificatesToRemove.remove(caAlias);
126                 if (!putCaCertInKeyStore(caAlias, caCertificates[i])) {
127                     // cleanup everything on failure.
128                     removeEntryFromKeyStore(alias);
129                     for (String addedAlias : caCertificateAliases) {
130                         removeEntryFromKeyStore(addedAlias);
131                     }
132                     return false;
133                 }
134                 caCertificateAliases.add(caAlias);
135             }
136         }
137         // If alias changed, remove the old one.
138         if (!alias.equals(existingAlias)) {
139             if (existingConfig != null && existingConfig.isAppInstalledDeviceKeyAndCert()) {
140                 // Remove old private keys.
141                 removeEntryFromKeyStore(existingAlias);
142             }
143         }
144         // Remove any old CA certs.
145         for (String oldAlias : oldCaCertificatesToRemove) {
146             removeEntryFromKeyStore(oldAlias);
147         }
148         // Set alias names
149         if (config.getClientCertificate() != null) {
150             config.setClientCertificateAlias(alias);
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 true;
160     }
161 
162     /**
163      * Install a CA certificate into the keystore.
164      *
165      * @param alias The alias name of the CA certificate to be installed
166      * @param cert The CA certificate to be installed
167      * @return true on success
168      */
putCaCertInKeyStore(String alias, Certificate cert)169     public boolean putCaCertInKeyStore(String alias, Certificate cert) {
170         try {
171             mKeyStore.setCertificateEntry(alias, cert);
172             return true;
173         } catch (KeyStoreException e) {
174             Log.e(TAG, "Failed to put CA certificate in keystore: " + e.getMessage());
175             return false;
176         }
177     }
178 
179     /**
180      * Install a private key + user certificate into the keystore.
181      *
182      * @param alias The alias name of the key to be installed
183      * @param key The private key to be installed
184      * @param certs User Certificate chain.
185      * @return true on success
186      */
putUserPrivKeyAndCertsInKeyStore(String alias, Key key, Certificate[] certs)187     public boolean putUserPrivKeyAndCertsInKeyStore(String alias, Key key, Certificate[] certs) {
188         try {
189             mKeyStore.setKeyEntry(alias, key, null, certs);
190             return true;
191         } catch (KeyStoreException e) {
192             Log.e(TAG, "Failed to put private key or certificate in keystore: " + e.getMessage());
193             return false;
194         }
195     }
196 
197     /**
198      * Remove a certificate or key entry specified by the alias name from the keystore.
199      *
200      * @param alias The alias name of the entry to be removed
201      * @return true on success
202      */
removeEntryFromKeyStore(String alias)203     public boolean removeEntryFromKeyStore(String alias) {
204         Preconditions.checkNotNull(mKeyStore);
205         try {
206             mKeyStore.deleteEntry(alias);
207             return true;
208         } catch (KeyStoreException e) {
209             return false;
210         }
211     }
212 
213     /**
214      * Remove enterprise keys from the network config.
215      *
216      * @param config Config corresponding to the network.
217      */
removeKeys(WifiEnterpriseConfig config)218     public void removeKeys(WifiEnterpriseConfig config) {
219         Preconditions.checkNotNull(mKeyStore);
220         // Do not remove keys that were manually installed by the user
221         if (config.isAppInstalledDeviceKeyAndCert()) {
222             String client = config.getClientCertificateAlias();
223             // a valid client certificate is configured
224             if (!TextUtils.isEmpty(client)) {
225                 if (mVerboseLoggingEnabled) {
226                     Log.d(TAG, "removing client private key, user cert and CA cert)");
227                 }
228                 // if there is only a single CA certificate, then that is also stored with
229                 // the same alias, hence will be removed here.
230                 removeEntryFromKeyStore(client);
231             }
232         }
233 
234         // Do not remove CA certs that were manually installed by the user
235         if (config.isAppInstalledCaCert()) {
236             String[] aliases = config.getCaCertificateAliases();
237             if (aliases == null || aliases.length == 0) {
238                 return;
239             }
240             // Remove all CA certificate.
241             for (String ca : aliases) {
242                 if (!TextUtils.isEmpty(ca)) {
243                     if (mVerboseLoggingEnabled) {
244                         Log.d(TAG, "removing CA cert: " + ca);
245                     }
246                     removeEntryFromKeyStore(ca);
247                 }
248             }
249         }
250     }
251 
252     /**
253      * Update/Install keys for given enterprise network.
254      *
255      * @param config         Config corresponding to the network.
256      * @param existingConfig Existing config corresponding to the network already stored in our
257      *                       database. This maybe null if it's a new network.
258      * @return true if successful, false otherwise.
259      */
updateNetworkKeys(WifiConfiguration config, WifiConfiguration existingConfig)260     public boolean updateNetworkKeys(WifiConfiguration config, WifiConfiguration existingConfig) {
261         Preconditions.checkNotNull(mKeyStore);
262         Preconditions.checkNotNull(config.enterpriseConfig);
263         WifiEnterpriseConfig enterpriseConfig = config.enterpriseConfig;
264         /* config passed may include only fields being updated.
265          * In order to generate the key id, fetch uninitialized
266          * fields from the currently tracked configuration
267          */
268         String keyId = config.getKeyIdForCredentials(existingConfig);
269         WifiEnterpriseConfig existingEnterpriseConfig = null;
270         String existingKeyId = null;
271         if (existingConfig != null) {
272             Preconditions.checkNotNull(existingConfig.enterpriseConfig);
273             existingEnterpriseConfig = existingConfig.enterpriseConfig;
274             existingKeyId = existingConfig.getKeyIdForCredentials(existingConfig);
275         }
276 
277         if (SdkLevel.isAtLeastS()) {
278             // If client key is in KeyChain, convert KeyChain alias into a grant string that can be
279             // used by the supplicant like a normal alias.
280             final String keyChainAlias = enterpriseConfig.getClientKeyPairAliasInternal();
281             if (keyChainAlias != null) {
282                 final String grantString = mFrameworkFacade.getWifiKeyGrantAsUser(
283                         mContext, UserHandle.getUserHandleForUid(config.creatorUid), keyChainAlias);
284                 if (grantString == null) {
285                     // The key is not granted to Wifi uid or the alias is invalid.
286                     Log.e(TAG, "Unable to get key grant");
287                     return false;
288                 }
289                 enterpriseConfig.setClientCertificateAlias(grantString);
290             }
291         }
292 
293         if (!needsKeyStore(enterpriseConfig)) {
294             return true;
295         }
296 
297         try {
298             if (!installKeys(existingEnterpriseConfig, enterpriseConfig, existingKeyId, keyId)) {
299                 Log.e(TAG, config.SSID + ": failed to install keys");
300                 return false;
301             }
302         } catch (IllegalStateException e) {
303             Log.e(TAG, config.SSID + " invalid config for key installation: " + e.getMessage());
304             return false;
305         }
306 
307         // For WPA3-Enterprise 192-bit networks, set the SuiteBCipher field based on the
308         // CA certificate type. Suite-B requires SHA384, reject other certs.
309         if (config.isSecurityType(WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT)) {
310             // Read the CA certificates, and initialize
311             String[] caAliases = config.enterpriseConfig.getCaCertificateAliases();
312 
313             if (caAliases == null || caAliases.length == 0) {
314                 Log.e(TAG, "No CA aliases in profile");
315                 return false;
316             }
317 
318             int caCertType = -1;
319             int prevCaCertType = -1;
320             for (String caAlias : caAliases) {
321                 Certificate caCert = null;
322                 try {
323                     caCert = mKeyStore.getCertificate(caAlias);
324                 } catch (KeyStoreException e) {
325                     Log.e(TAG, "Failed to get Suite-B certificate", e);
326                 }
327                 if (caCert == null || !(caCert instanceof X509Certificate)) {
328                     Log.e(TAG, "Failed reading CA certificate for Suite-B");
329                     return false;
330                 }
331 
332                 // Confirm that the CA certificate is compatible with Suite-B requirements
333                 caCertType = getSuiteBCipherFromCert((X509Certificate) caCert);
334                 if (caCertType < 0) {
335                     return false;
336                 }
337                 if (prevCaCertType != -1) {
338                     if (prevCaCertType != caCertType) {
339                         Log.e(TAG, "Incompatible CA certificates");
340                         return false;
341                     }
342                 }
343                 prevCaCertType = caCertType;
344             }
345 
346             Certificate clientCert = null;
347             try {
348                 clientCert = mKeyStore.getCertificate(config.enterpriseConfig
349                         .getClientCertificateAlias());
350             } catch (KeyStoreException e) {
351                 Log.e(TAG, "Failed to get Suite-B client certificate", e);
352             }
353             if (clientCert == null || !(clientCert instanceof X509Certificate)) {
354                 Log.e(TAG, "Failed reading client certificate for Suite-B");
355                 return false;
356             }
357 
358             int clientCertType = getSuiteBCipherFromCert((X509Certificate) clientCert);
359             if (clientCertType < 0) {
360                 return false;
361             }
362 
363             if (clientCertType == caCertType) {
364                 config.enableSuiteBCiphers(
365                         clientCertType == WifiConfiguration.SuiteBCipher.ECDHE_ECDSA,
366                         clientCertType == WifiConfiguration.SuiteBCipher.ECDHE_RSA);
367             } else {
368                 Log.e(TAG, "Client certificate for Suite-B is incompatible with the CA "
369                         + "certificate");
370                 return false;
371             }
372         }
373         return true;
374     }
375 
376     /**
377      * Get the Suite-B cipher from the certificate
378      *
379      * @param x509Certificate Certificate to process
380      * @return WifiConfiguration.SuiteBCipher.ECDHE_RSA if the certificate OID matches the Suite-B
381      * requirements for RSA certificates, WifiConfiguration.SuiteBCipher.ECDHE_ECDSA if the
382      * certificate OID matches the Suite-B requirements for ECDSA certificates, or -1 otherwise.
383      */
getSuiteBCipherFromCert(X509Certificate x509Certificate)384     private int getSuiteBCipherFromCert(X509Certificate x509Certificate) {
385         String sigAlgOid = x509Certificate.getSigAlgOID();
386         if (mVerboseLoggingEnabled) {
387             Principal p = x509Certificate.getSubjectX500Principal();
388             if (p != null && !TextUtils.isEmpty(p.getName())) {
389                 Log.d(TAG, "Checking cert " + p.getName());
390             }
391         }
392         int bitLength = 0;
393 
394         // Wi-Fi alliance requires the use of both ECDSA secp384r1 and RSA 3072 certificates
395         // in WPA3-Enterprise 192-bit security networks, which are also known as Suite-B-192
396         // networks, even though NSA Suite-B-192 mandates ECDSA only. The use of the term
397         // Suite-B was already coined in the IEEE 802.11-2016 specification for
398         // AKM 00-0F-AC but the test plan for WPA3-Enterprise 192-bit for APs mandates
399         // support for both RSA and ECDSA, and for STAs it mandates ECDSA and optionally
400         // RSA. In order to be compatible with all WPA3-Enterprise 192-bit deployments,
401         // we are supporting both types here.
402         if (sigAlgOid.equals("1.2.840.113549.1.1.12")) {
403             // sha384WithRSAEncryption
404             if (x509Certificate.getPublicKey() instanceof RSAPublicKey) {
405                 final RSAPublicKey rsaPublicKey = (RSAPublicKey) x509Certificate.getPublicKey();
406                 if (rsaPublicKey.getModulus() != null) {
407                     bitLength = rsaPublicKey.getModulus().bitLength();
408                     if (bitLength >= 3072) {
409                         if (mVerboseLoggingEnabled) {
410                             Log.d(TAG, "Found Suite-B RSA certificate");
411                         }
412                         return WifiConfiguration.SuiteBCipher.ECDHE_RSA;
413                     }
414                 }
415             }
416         } else if (sigAlgOid.equals("1.2.840.10045.4.3.3")) {
417             // ecdsa-with-SHA384
418             if (x509Certificate.getPublicKey() instanceof ECPublicKey) {
419                 final ECPublicKey ecPublicKey = (ECPublicKey) x509Certificate.getPublicKey();
420                 final ECParameterSpec ecParameterSpec = ecPublicKey.getParams();
421                 if (ecParameterSpec != null && ecParameterSpec.getOrder() != null) {
422                     bitLength = ecParameterSpec.getOrder().bitLength();
423                     if (bitLength >= 384) {
424                         if (mVerboseLoggingEnabled) {
425                             Log.d(TAG, "Found Suite-B ECDSA certificate");
426                         }
427                         return WifiConfiguration.SuiteBCipher.ECDHE_ECDSA;
428                     }
429                 }
430             }
431         }
432         Log.e(TAG, "Invalid certificate type for Suite-B: " + sigAlgOid + " or insufficient"
433                 + " bit length: " + bitLength);
434         return -1;
435     }
436 
437     /**
438      * Requests a grant from KeyChain and populates client certificate alias with it.
439      *
440      * @return true if no problems encountered.
441      */
validateKeyChainAlias(String alias, int uid)442     public boolean validateKeyChainAlias(String alias, int uid) {
443         if (TextUtils.isEmpty(alias)) {
444             Log.e(TAG, "Alias cannot be empty");
445             return false;
446         }
447 
448         if (!SdkLevel.isAtLeastS()) {
449             Log.w(TAG, "Attempt to use a KeyChain key on pre-S device");
450             return false;
451         }
452 
453         return mFrameworkFacade.hasWifiKeyGrantAsUser(
454                 mContext, UserHandle.getUserHandleForUid(uid), alias);
455     }
456 }
457