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.IOException; 30 import java.security.Key; 31 import java.security.cert.Certificate; 32 import java.security.cert.CertificateException; 33 import java.security.cert.X509Certificate; 34 import java.util.ArrayList; 35 import java.util.Arrays; 36 import java.util.List; 37 import java.util.Set; 38 39 /** 40 * This class provides the methods to access keystore for certificate management. 41 * 42 * NOTE: This class should only be used from WifiConfigManager! 43 */ 44 public class WifiKeyStore { 45 private static final String TAG = "WifiKeyStore"; 46 47 private boolean mVerboseLoggingEnabled = false; 48 49 private final KeyStore mKeyStore; 50 51 WifiKeyStore(KeyStore keyStore) { 52 mKeyStore = keyStore; 53 } 54 55 /** 56 * Enable verbose logging. 57 */ 58 void enableVerboseLogging(boolean verbose) { 59 mVerboseLoggingEnabled = verbose; 60 } 61 62 // Certificate and private key management for EnterpriseConfig 63 private static boolean needsKeyStore(WifiEnterpriseConfig config) { 64 return (config.getClientCertificate() != null || config.getCaCertificate() != null); 65 } 66 67 private static boolean isHardwareBackedKey(Key key) { 68 return KeyChain.isBoundKeyAlgorithm(key.getAlgorithm()); 69 } 70 71 private static boolean hasHardwareBackedKey(Certificate certificate) { 72 return isHardwareBackedKey(certificate.getPublicKey()); 73 } 74 75 /** 76 * Install keys for given enterprise network. 77 * 78 * @param existingConfig Existing config corresponding to the network already stored in our 79 * database. This maybe null if it's a new network. 80 * @param config Config corresponding to the network. 81 * @return true if successful, false otherwise. 82 */ 83 private boolean installKeys(WifiEnterpriseConfig existingConfig, WifiEnterpriseConfig config, 84 String name) { 85 boolean ret = true; 86 String privKeyName = Credentials.USER_PRIVATE_KEY + name; 87 String userCertName = Credentials.USER_CERTIFICATE + name; 88 Certificate[] clientCertificateChain = config.getClientCertificateChain(); 89 if (clientCertificateChain != null && clientCertificateChain.length != 0) { 90 byte[] privKeyData = config.getClientPrivateKey().getEncoded(); 91 if (mVerboseLoggingEnabled) { 92 if (isHardwareBackedKey(config.getClientPrivateKey())) { 93 Log.d(TAG, "importing keys " + name + " in hardware backed store"); 94 } else { 95 Log.d(TAG, "importing keys " + name + " in software backed store"); 96 } 97 } 98 ret = mKeyStore.importKey(privKeyName, privKeyData, Process.WIFI_UID, 99 KeyStore.FLAG_NONE); 100 101 if (!ret) { 102 return ret; 103 } 104 105 ret = putCertsInKeyStore(userCertName, clientCertificateChain); 106 if (!ret) { 107 // Remove private key installed 108 mKeyStore.delete(privKeyName, Process.WIFI_UID); 109 return ret; 110 } 111 } 112 113 X509Certificate[] caCertificates = config.getCaCertificates(); 114 Set<String> oldCaCertificatesToRemove = new ArraySet<>(); 115 if (existingConfig != null && existingConfig.getCaCertificateAliases() != null) { 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 alias = caCertificates.length == 1 ? name 124 : String.format("%s_%d", name, i); 125 126 oldCaCertificatesToRemove.remove(alias); 127 ret = putCertInKeyStore(Credentials.CA_CERTIFICATE + alias, caCertificates[i]); 128 if (!ret) { 129 // Remove client key+cert 130 if (config.getClientCertificate() != null) { 131 mKeyStore.delete(privKeyName, Process.WIFI_UID); 132 mKeyStore.delete(userCertName, Process.WIFI_UID); 133 } 134 // Remove added CA certs. 135 for (String addedAlias : caCertificateAliases) { 136 mKeyStore.delete(Credentials.CA_CERTIFICATE + addedAlias, Process.WIFI_UID); 137 } 138 return ret; 139 } else { 140 caCertificateAliases.add(alias); 141 } 142 } 143 } 144 // Remove old CA certs. 145 for (String oldAlias : oldCaCertificatesToRemove) { 146 mKeyStore.delete(Credentials.CA_CERTIFICATE + oldAlias, Process.WIFI_UID); 147 } 148 // Set alias names 149 if (config.getClientCertificate() != null) { 150 config.setClientCertificateAlias(name); 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 ret; 160 } 161 162 /** 163 * Install a certificate into the keystore. 164 * 165 * @param name The alias name of the certificate to be installed 166 * @param cert The certificate to be installed 167 * @return true on success 168 */ 169 public boolean putCertInKeyStore(String name, Certificate cert) { 170 return putCertsInKeyStore(name, new Certificate[] {cert}); 171 } 172 173 /** 174 * Install a client certificate chain into the keystore. 175 * 176 * @param name The alias name of the certificate to be installed 177 * @param certs The certificate chain to be installed 178 * @return true on success 179 */ 180 public boolean putCertsInKeyStore(String name, Certificate[] certs) { 181 try { 182 byte[] certData = Credentials.convertToPem(certs); 183 if (mVerboseLoggingEnabled) { 184 Log.d(TAG, "putting " + certs.length + " certificate(s) " 185 + name + " in keystore"); 186 } 187 return mKeyStore.put(name, certData, Process.WIFI_UID, KeyStore.FLAG_NONE); 188 } catch (IOException e1) { 189 return false; 190 } catch (CertificateException e2) { 191 return false; 192 } 193 } 194 195 /** 196 * Install a key into the keystore. 197 * 198 * @param name The alias name of the key to be installed 199 * @param key The key to be installed 200 * @return true on success 201 */ 202 public boolean putKeyInKeyStore(String name, Key key) { 203 byte[] privKeyData = key.getEncoded(); 204 return mKeyStore.importKey(name, privKeyData, Process.WIFI_UID, KeyStore.FLAG_NONE); 205 } 206 207 /** 208 * Remove a certificate or key entry specified by the alias name from the keystore. 209 * 210 * @param name The alias name of the entry to be removed 211 * @return true on success 212 */ 213 public boolean removeEntryFromKeyStore(String name) { 214 return mKeyStore.delete(name, Process.WIFI_UID); 215 } 216 217 /** 218 * Remove enterprise keys from the network config. 219 * 220 * @param config Config corresponding to the network. 221 */ 222 public void removeKeys(WifiEnterpriseConfig config) { 223 String client = config.getClientCertificateAlias(); 224 // a valid client certificate is configured 225 if (!TextUtils.isEmpty(client)) { 226 if (mVerboseLoggingEnabled) Log.d(TAG, "removing client private key and user cert"); 227 mKeyStore.delete(Credentials.USER_PRIVATE_KEY + client, Process.WIFI_UID); 228 mKeyStore.delete(Credentials.USER_CERTIFICATE + client, Process.WIFI_UID); 229 } 230 231 String[] aliases = config.getCaCertificateAliases(); 232 // a valid ca certificate is configured 233 if (aliases != null) { 234 for (String ca : aliases) { 235 if (!TextUtils.isEmpty(ca)) { 236 if (mVerboseLoggingEnabled) Log.d(TAG, "removing CA cert: " + ca); 237 mKeyStore.delete(Credentials.CA_CERTIFICATE + ca, Process.WIFI_UID); 238 } 239 } 240 } 241 } 242 243 /** 244 * Update/Install keys for given enterprise network. 245 * 246 * @param config Config corresponding to the network. 247 * @param existingConfig Existing config corresponding to the network already stored in our 248 * database. This maybe null if it's a new network. 249 * @return true if successful, false otherwise. 250 */ 251 public boolean updateNetworkKeys(WifiConfiguration config, WifiConfiguration existingConfig) { 252 WifiEnterpriseConfig enterpriseConfig = config.enterpriseConfig; 253 if (needsKeyStore(enterpriseConfig)) { 254 try { 255 /* config passed may include only fields being updated. 256 * In order to generate the key id, fetch uninitialized 257 * fields from the currently tracked configuration 258 */ 259 String keyId = config.getKeyIdForCredentials(existingConfig); 260 if (!installKeys(existingConfig != null 261 ? existingConfig.enterpriseConfig : null, enterpriseConfig, keyId)) { 262 Log.e(TAG, config.SSID + ": failed to install keys"); 263 return false; 264 } 265 } catch (IllegalStateException e) { 266 Log.e(TAG, config.SSID + " invalid config for key installation: " + e.getMessage()); 267 return false; 268 } 269 } 270 return true; 271 } 272 273 /** 274 * Checks whether the configuration requires a software backed keystore or not. 275 * @param config WifiEnterprise config instance pointing to the enterprise configuration of the 276 * network. 277 */ 278 public static boolean needsSoftwareBackedKeyStore(WifiEnterpriseConfig config) { 279 String client = config.getClientCertificateAlias(); 280 if (!TextUtils.isEmpty(client)) { 281 // a valid client certificate is configured 282 283 // BUGBUG(b/29578316): keyStore.get() never returns certBytes; because it is not 284 // taking WIFI_UID as a parameter. It always looks for certificate 285 // with SYSTEM_UID, and never finds any Wifi certificates. Assuming that 286 // all certificates need software keystore until we get the get() API 287 // fixed. 288 return true; 289 } 290 return false; 291 } 292 293 } 294