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.net.wifi.WifiConfiguration; 20 import android.net.wifi.WifiEnterpriseConfig; 21 import android.os.Process; 22 import android.security.Credentials; 23 import android.security.KeyChain; 24 import android.security.KeyStore; 25 import android.text.TextUtils; 26 import android.util.ArraySet; 27 import android.util.Log; 28 29 import java.io.ByteArrayInputStream; 30 import java.io.IOException; 31 import java.io.InputStream; 32 import java.security.Key; 33 import java.security.cert.Certificate; 34 import java.security.cert.CertificateException; 35 import java.security.cert.CertificateFactory; 36 import java.security.cert.X509Certificate; 37 import java.util.ArrayList; 38 import java.util.Arrays; 39 import java.util.List; 40 import java.util.Set; 41 42 /** 43 * This class provides the methods to access keystore for certificate management. 44 * 45 * NOTE: This class should only be used from WifiConfigManager! 46 */ 47 public class WifiKeyStore { 48 private static final String TAG = "WifiKeyStore"; 49 50 private boolean mVerboseLoggingEnabled = false; 51 52 private final KeyStore mKeyStore; 53 WifiKeyStore(KeyStore keyStore)54 WifiKeyStore(KeyStore keyStore) { 55 mKeyStore = keyStore; 56 } 57 58 /** 59 * Enable verbose logging. 60 */ enableVerboseLogging(boolean verbose)61 void enableVerboseLogging(boolean verbose) { 62 mVerboseLoggingEnabled = verbose; 63 } 64 65 // Certificate and private key management for EnterpriseConfig needsKeyStore(WifiEnterpriseConfig config)66 private static boolean needsKeyStore(WifiEnterpriseConfig config) { 67 return (config.getClientCertificate() != null || config.getCaCertificate() != null); 68 } 69 isHardwareBackedKey(Key key)70 private static boolean isHardwareBackedKey(Key key) { 71 return KeyChain.isBoundKeyAlgorithm(key.getAlgorithm()); 72 } 73 hasHardwareBackedKey(Certificate certificate)74 private static boolean hasHardwareBackedKey(Certificate certificate) { 75 return isHardwareBackedKey(certificate.getPublicKey()); 76 } 77 78 /** 79 * Install keys for given enterprise network. 80 * 81 * @param existingConfig Existing config corresponding to the network already stored in our 82 * database. This maybe null if it's a new network. 83 * @param config Config corresponding to the network. 84 * @return true if successful, false otherwise. 85 */ installKeys(WifiEnterpriseConfig existingConfig, WifiEnterpriseConfig config, String name)86 private boolean installKeys(WifiEnterpriseConfig existingConfig, WifiEnterpriseConfig config, 87 String name) { 88 boolean ret = true; 89 String privKeyName = Credentials.USER_PRIVATE_KEY + name; 90 String userCertName = Credentials.USER_CERTIFICATE + name; 91 Certificate[] clientCertificateChain = config.getClientCertificateChain(); 92 if (clientCertificateChain != null && clientCertificateChain.length != 0) { 93 byte[] privKeyData = config.getClientPrivateKey().getEncoded(); 94 if (mVerboseLoggingEnabled) { 95 if (isHardwareBackedKey(config.getClientPrivateKey())) { 96 Log.d(TAG, "importing keys " + name + " in hardware backed store"); 97 } else { 98 Log.d(TAG, "importing keys " + name + " in software backed store"); 99 } 100 } 101 ret = mKeyStore.importKey(privKeyName, privKeyData, Process.WIFI_UID, 102 KeyStore.FLAG_NONE); 103 104 if (!ret) { 105 return ret; 106 } 107 108 ret = putCertsInKeyStore(userCertName, clientCertificateChain); 109 if (!ret) { 110 // Remove private key installed 111 mKeyStore.delete(privKeyName, Process.WIFI_UID); 112 return ret; 113 } 114 } 115 116 X509Certificate[] caCertificates = config.getCaCertificates(); 117 Set<String> oldCaCertificatesToRemove = new ArraySet<>(); 118 if (existingConfig != null && existingConfig.getCaCertificateAliases() != null) { 119 oldCaCertificatesToRemove.addAll( 120 Arrays.asList(existingConfig.getCaCertificateAliases())); 121 } 122 List<String> caCertificateAliases = null; 123 if (caCertificates != null) { 124 caCertificateAliases = new ArrayList<>(); 125 for (int i = 0; i < caCertificates.length; i++) { 126 String alias = caCertificates.length == 1 ? name 127 : String.format("%s_%d", name, i); 128 129 oldCaCertificatesToRemove.remove(alias); 130 ret = putCertInKeyStore(Credentials.CA_CERTIFICATE + alias, caCertificates[i]); 131 if (!ret) { 132 // Remove client key+cert 133 if (config.getClientCertificate() != null) { 134 mKeyStore.delete(privKeyName, Process.WIFI_UID); 135 mKeyStore.delete(userCertName, Process.WIFI_UID); 136 } 137 // Remove added CA certs. 138 for (String addedAlias : caCertificateAliases) { 139 mKeyStore.delete(Credentials.CA_CERTIFICATE + addedAlias, Process.WIFI_UID); 140 } 141 return ret; 142 } else { 143 caCertificateAliases.add(alias); 144 } 145 } 146 } 147 // Remove old CA certs. 148 for (String oldAlias : oldCaCertificatesToRemove) { 149 mKeyStore.delete(Credentials.CA_CERTIFICATE + oldAlias, Process.WIFI_UID); 150 } 151 // Set alias names 152 if (config.getClientCertificate() != null) { 153 config.setClientCertificateAlias(name); 154 config.resetClientKeyEntry(); 155 } 156 157 if (caCertificates != null) { 158 config.setCaCertificateAliases( 159 caCertificateAliases.toArray(new String[caCertificateAliases.size()])); 160 config.resetCaCertificate(); 161 } 162 return ret; 163 } 164 165 /** 166 * Install a certificate into the keystore. 167 * 168 * @param name The alias name of the certificate to be installed 169 * @param cert The certificate to be installed 170 * @return true on success 171 */ putCertInKeyStore(String name, Certificate cert)172 public boolean putCertInKeyStore(String name, Certificate cert) { 173 return putCertsInKeyStore(name, new Certificate[] {cert}); 174 } 175 176 /** 177 * Install a client certificate chain into the keystore. 178 * 179 * @param name The alias name of the certificate to be installed 180 * @param certs The certificate chain to be installed 181 * @return true on success 182 */ putCertsInKeyStore(String name, Certificate[] certs)183 public boolean putCertsInKeyStore(String name, Certificate[] certs) { 184 try { 185 byte[] certData = Credentials.convertToPem(certs); 186 if (mVerboseLoggingEnabled) { 187 Log.d(TAG, "putting " + certs.length + " certificate(s) " 188 + name + " in keystore"); 189 } 190 return mKeyStore.put(name, certData, Process.WIFI_UID, KeyStore.FLAG_NONE); 191 } catch (IOException e1) { 192 return false; 193 } catch (CertificateException e2) { 194 return false; 195 } 196 } 197 198 /** 199 * Install a key into the keystore. 200 * 201 * @param name The alias name of the key to be installed 202 * @param key The key to be installed 203 * @return true on success 204 */ putKeyInKeyStore(String name, Key key)205 public boolean putKeyInKeyStore(String name, Key key) { 206 byte[] privKeyData = key.getEncoded(); 207 return mKeyStore.importKey(name, privKeyData, Process.WIFI_UID, KeyStore.FLAG_NONE); 208 } 209 210 /** 211 * Remove a certificate or key entry specified by the alias name from the keystore. 212 * 213 * @param name The alias name of the entry to be removed 214 * @return true on success 215 */ removeEntryFromKeyStore(String name)216 public boolean removeEntryFromKeyStore(String name) { 217 return mKeyStore.delete(name, Process.WIFI_UID); 218 } 219 220 /** 221 * Remove enterprise keys from the network config. 222 * 223 * @param config Config corresponding to the network. 224 */ removeKeys(WifiEnterpriseConfig config)225 public void removeKeys(WifiEnterpriseConfig config) { 226 // Do not remove keys that were manually installed by the user 227 if (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 and user cert"); 233 } 234 mKeyStore.delete(Credentials.USER_PRIVATE_KEY + client, Process.WIFI_UID); 235 mKeyStore.delete(Credentials.USER_CERTIFICATE + client, Process.WIFI_UID); 236 } 237 } 238 239 // Do not remove CA certs that were manually installed by the user 240 if (config.isAppInstalledCaCert()) { 241 String[] aliases = config.getCaCertificateAliases(); 242 // a valid ca certificate is configured 243 if (aliases != null) { 244 for (String ca : aliases) { 245 if (!TextUtils.isEmpty(ca)) { 246 if (mVerboseLoggingEnabled) { 247 Log.d(TAG, "removing CA cert: " + ca); 248 } 249 mKeyStore.delete(Credentials.CA_CERTIFICATE + ca, Process.WIFI_UID); 250 } 251 } 252 } 253 } 254 } 255 256 257 /** 258 * @param certData byte array of the certificate 259 */ buildCACertificate(byte[] certData)260 private X509Certificate buildCACertificate(byte[] certData) { 261 try { 262 CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); 263 InputStream inputStream = new ByteArrayInputStream(certData); 264 X509Certificate caCertificateX509 = (X509Certificate) certificateFactory 265 .generateCertificate(inputStream); 266 return caCertificateX509; 267 } catch (CertificateException e) { 268 return null; 269 } 270 } 271 /** 272 * Update/Install keys for given enterprise network. 273 * 274 * @param config Config corresponding to the network. 275 * @param existingConfig Existing config corresponding to the network already stored in our 276 * database. This maybe null if it's a new network. 277 * @return true if successful, false otherwise. 278 */ updateNetworkKeys(WifiConfiguration config, WifiConfiguration existingConfig)279 public boolean updateNetworkKeys(WifiConfiguration config, WifiConfiguration existingConfig) { 280 WifiEnterpriseConfig enterpriseConfig = config.enterpriseConfig; 281 if (!needsKeyStore(enterpriseConfig)) { 282 return true; 283 } 284 285 try { 286 /* config passed may include only fields being updated. 287 * In order to generate the key id, fetch uninitialized 288 * fields from the currently tracked configuration 289 */ 290 String keyId = config.getKeyIdForCredentials(existingConfig); 291 if (!installKeys(existingConfig != null 292 ? existingConfig.enterpriseConfig : null, enterpriseConfig, keyId)) { 293 Log.e(TAG, config.SSID + ": failed to install keys"); 294 return false; 295 } 296 } catch (IllegalStateException e) { 297 Log.e(TAG, config.SSID + " invalid config for key installation: " + e.getMessage()); 298 return false; 299 } 300 301 // For WPA3-Enterprise 192-bit networks, set the SuiteBCipher field based on the 302 // CA certificate type. Suite-B requires SHA384, reject other certs. 303 if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SUITE_B_192)) { 304 // Read the first CA certificate, and initialize 305 byte[] certData = mKeyStore.get( 306 Credentials.CA_CERTIFICATE + config.enterpriseConfig.getCaCertificateAlias(), 307 android.os.Process.WIFI_UID); 308 309 if (certData == null) { 310 Log.e(TAG, "Failed reading CA certificate for Suite-B"); 311 return false; 312 } 313 314 X509Certificate x509CaCert = buildCACertificate(certData); 315 316 if (x509CaCert != null) { 317 String sigAlgOid = x509CaCert.getSigAlgOID(); 318 if (mVerboseLoggingEnabled) { 319 Log.d(TAG, "Signature algorithm: " + sigAlgOid); 320 } 321 config.allowedSuiteBCiphers.clear(); 322 323 // Wi-Fi alliance requires the use of both ECDSA secp384r1 and RSA 3072 certificates 324 // in WPA3-Enterprise 192-bit security networks, which are also known as Suite-B-192 325 // networks, even though NSA Suite-B-192 mandates ECDSA only. The use of the term 326 // Suite-B was already coined in the IEEE 802.11-2016 specification for 327 // AKM 00-0F-AC but the test plan for WPA3-Enterprise 192-bit for APs mandates 328 // support for both RSA and ECDSA, and for STAs it mandates ECDSA and optionally 329 // RSA. In order to be compatible with all WPA3-Enterprise 192-bit deployments, 330 // we are supporting both types here. 331 if (sigAlgOid.equals("1.2.840.113549.1.1.12")) { 332 // sha384WithRSAEncryption 333 config.allowedSuiteBCiphers.set( 334 WifiConfiguration.SuiteBCipher.ECDHE_RSA); 335 if (mVerboseLoggingEnabled) { 336 Log.d(TAG, "Selecting Suite-B RSA"); 337 } 338 } else if (sigAlgOid.equals("1.2.840.10045.4.3.3")) { 339 // ecdsa-with-SHA384 340 config.allowedSuiteBCiphers.set( 341 WifiConfiguration.SuiteBCipher.ECDHE_ECDSA); 342 if (mVerboseLoggingEnabled) { 343 Log.d(TAG, "Selecting Suite-B ECDSA"); 344 } 345 } else { 346 Log.e(TAG, "Invalid CA certificate type for Suite-B: " 347 + sigAlgOid); 348 return false; 349 } 350 } else { 351 Log.e(TAG, "Invalid CA certificate for Suite-B"); 352 return false; 353 } 354 } 355 return true; 356 } 357 } 358