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