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