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