Track keys per config and allow cert push from apps

Allow configuring keys for a configuration and update/delete
from wificonfigstore.

Change-Id: I79b43bf7ca58f7efc95f7dcc125fc84d7aa8c795
This commit is contained in:
Irfan Sheriff
2013-02-05 09:44:12 -08:00
committed by Irfan Sheriff
parent b07526fb1c
commit b815edf3ab
3 changed files with 343 additions and 32 deletions

View File

@@ -36,6 +36,7 @@ import android.os.Message;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.UserHandle;
import android.security.KeyStore;
import android.text.TextUtils;
import android.util.Log;
@@ -143,6 +144,7 @@ class WifiConfigStore {
private static final String EOS = "eos";
private WifiNative mWifiNative;
private final KeyStore mKeyStore = KeyStore.getInstance();
WifiConfigStore(Context c, WifiNative wn) {
mContext = c;
@@ -294,16 +296,7 @@ class WifiConfigStore {
boolean forgetNetwork(int netId) {
if (mWifiNative.removeNetwork(netId)) {
mWifiNative.saveConfig();
WifiConfiguration target = null;
WifiConfiguration config = mConfiguredNetworks.get(netId);
if (config != null) {
target = mConfiguredNetworks.remove(netId);
mNetworkIds.remove(configKey(config));
}
if (target != null) {
writeIpAndProxyConfigurations();
sendConfiguredNetworksChangedBroadcast(target, WifiManager.CHANGE_REASON_REMOVED);
}
removeConfigAndSendBroadcastIfNeeded(netId);
return true;
} else {
loge("Failed to remove network " + netId);
@@ -341,20 +334,27 @@ class WifiConfigStore {
*/
boolean removeNetwork(int netId) {
boolean ret = mWifiNative.removeNetwork(netId);
WifiConfiguration config = null;
if (ret) {
config = mConfiguredNetworks.get(netId);
if (config != null) {
config = mConfiguredNetworks.remove(netId);
mNetworkIds.remove(configKey(config));
}
}
if (config != null) {
sendConfiguredNetworksChangedBroadcast(config, WifiManager.CHANGE_REASON_REMOVED);
removeConfigAndSendBroadcastIfNeeded(netId);
}
return ret;
}
private void removeConfigAndSendBroadcastIfNeeded(int netId) {
WifiConfiguration config = mConfiguredNetworks.get(netId);
if (config != null) {
// Remove any associated keys
if (config.enterpriseConfig != null) {
config.enterpriseConfig.removeKeys(mKeyStore);
}
mConfiguredNetworks.remove(netId);
mNetworkIds.remove(configKey(config));
writeIpAndProxyConfigurations();
sendConfiguredNetworksChangedBroadcast(config, WifiManager.CHANGE_REASON_REMOVED);
}
}
/**
* Enable a network. Note that there is no saveConfig operation.
* This function is retained for compatibility with the public
@@ -1122,13 +1122,48 @@ class WifiConfigStore {
}
if (config.enterpriseConfig != null) {
HashMap<String, String> enterpriseFields = config.enterpriseConfig.getFields();
WifiEnterpriseConfig enterpriseConfig = config.enterpriseConfig;
if (enterpriseConfig.needsKeyStore()) {
/**
* Keyguard settings may eventually be controlled by device policy.
* We check here if keystore is unlocked before installing
* credentials.
* TODO: Figure a way to store these credentials for wifi alone
* TODO: Do we need a dialog here ?
*/
if (mKeyStore.state() != KeyStore.State.UNLOCKED) {
loge(config.SSID + ": key store is locked");
break setVariables;
}
try {
/* config passed may include only fields being updated.
* In order to generate the key id, fetch uninitialized
* fields from the currently tracked configuration
*/
WifiConfiguration currentConfig = mConfiguredNetworks.get(netId);
String keyId = config.getKeyIdForCredentials(currentConfig);
if (!enterpriseConfig.installKeys(mKeyStore, keyId)) {
loge(config.SSID + ": failed to install keys");
break setVariables;
}
} catch (IllegalStateException e) {
loge(config.SSID + " invalid config for key installation");
break setVariables;
}
}
HashMap<String, String> enterpriseFields = enterpriseConfig.getFields();
for (String key : enterpriseFields.keySet()) {
String value = enterpriseFields.get(key);
if (!mWifiNative.setNetworkVariable(
netId,
key,
value)) {
enterpriseConfig.removeKeys(mKeyStore);
loge(config.SSID + ": failed to set " + key +
": " + value);
break setVariables;
@@ -1136,7 +1171,7 @@ class WifiConfigStore {
}
}
updateFailed = false;
}
} //end of setVariables
if (updateFailed) {
if (newNetwork) {

View File

@@ -19,6 +19,7 @@ package android.net.wifi;
import android.net.LinkProperties;
import android.os.Parcelable;
import android.os.Parcel;
import android.text.TextUtils;
import java.util.BitSet;
@@ -274,7 +275,8 @@ public class WifiConfiguration implements Parcelable {
*/
public BitSet allowedGroupCiphers;
/**
* The enterprise configuration details
* The enterprise configuration details specifying the EAP method,
* certificates and other settings associated with the EAP.
* @hide
*/
public WifiEnterpriseConfig enterpriseConfig;
@@ -462,6 +464,47 @@ public class WifiConfiguration implements Parcelable {
return SSID;
}
/**
* Get an identifier for associating credentials with this config
* @param current configuration contains values for additional fields
* that are not part of this configuration. Used
* when a config with some fields is passed by an application.
* @throws IllegalStateException if config is invalid for key id generation
* @hide
*/
String getKeyIdForCredentials(WifiConfiguration current) {
String keyMgmt = null;
try {
// Get current config details for fields that are not initialized
if (TextUtils.isEmpty(SSID)) SSID = current.SSID;
if (allowedKeyManagement.cardinality() == 0) {
allowedKeyManagement = current.allowedKeyManagement;
}
if (allowedKeyManagement.get(KeyMgmt.WPA_EAP)) {
keyMgmt = KeyMgmt.strings[KeyMgmt.WPA_EAP];
}
if (allowedKeyManagement.get(KeyMgmt.IEEE8021X)) {
keyMgmt += KeyMgmt.strings[KeyMgmt.IEEE8021X];
}
if (TextUtils.isEmpty(keyMgmt)) {
throw new IllegalStateException("Not an EAP network");
}
return trimStringForKeyId(SSID) + "_" + keyMgmt + "_" +
trimStringForKeyId(enterpriseConfig.getKeyId(current != null ?
current.enterpriseConfig : null));
} catch (NullPointerException e) {
throw new IllegalStateException("Invalid config details");
}
}
private String trimStringForKeyId(String string) {
// Remove quotes and spaces
return string.replace("\"", "").replace(" ", "");
}
private static BitSet readBitSet(Parcel src) {
int cardinality = src.readInt();
@@ -485,6 +528,9 @@ public class WifiConfiguration implements Parcelable {
/** @hide */
public int getAuthType() {
if (allowedKeyManagement.cardinality() > 1) {
throw new IllegalStateException("More than one auth type set");
}
if (allowedKeyManagement.get(KeyMgmt.WPA_PSK)) {
return KeyMgmt.WPA_PSK;
} else if (allowedKeyManagement.get(KeyMgmt.WPA2_PSK)) {

View File

@@ -19,7 +19,26 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.security.Credentials;
import android.text.TextUtils;
import android.util.Log;
import com.android.org.bouncycastle.asn1.ASN1InputStream;
import com.android.org.bouncycastle.asn1.ASN1Sequence;
import com.android.org.bouncycastle.asn1.DEROctetString;
import com.android.org.bouncycastle.asn1.x509.BasicConstraints;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;
@@ -70,6 +89,9 @@ public class WifiEnterpriseConfig implements Parcelable {
private static final String PRIVATE_KEY_ID_KEY = "key_id";
private HashMap<String, String> mFields = new HashMap<String, String>();
private X509Certificate mCaCert;
private PrivateKey mClientPrivateKey;
private X509Certificate mClientCertificate;
/** This represents an empty value of an enterprise field.
* NULL is used at wpa_supplicant to indicate an empty value
@@ -103,6 +125,34 @@ public class WifiEnterpriseConfig implements Parcelable {
dest.writeString(entry.getKey());
dest.writeString(entry.getValue());
}
writeCertificate(dest, mCaCert);
if (mClientPrivateKey != null) {
String algorithm = mClientPrivateKey.getAlgorithm();
byte[] userKeyBytes = mClientPrivateKey.getEncoded();
dest.writeInt(userKeyBytes.length);
dest.writeByteArray(userKeyBytes);
dest.writeString(algorithm);
} else {
dest.writeInt(0);
}
writeCertificate(dest, mClientCertificate);
}
private void writeCertificate(Parcel dest, X509Certificate cert) {
if (cert != null) {
try {
byte[] certBytes = cert.getEncoded();
dest.writeInt(certBytes.length);
dest.writeByteArray(certBytes);
} catch (CertificateEncodingException e) {
dest.writeInt(0);
}
} else {
dest.writeInt(0);
}
}
public static final Creator<WifiEnterpriseConfig> CREATOR =
@@ -115,9 +165,47 @@ public class WifiEnterpriseConfig implements Parcelable {
String value = in.readString();
enterpriseConfig.mFields.put(key, value);
}
enterpriseConfig.mCaCert = readCertificate(in);
PrivateKey userKey = null;
int len = in.readInt();
if (len > 0) {
try {
byte[] bytes = new byte[len];
in.readByteArray(bytes);
String algorithm = in.readString();
KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
userKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(bytes));
} catch (NoSuchAlgorithmException e) {
userKey = null;
} catch (InvalidKeySpecException e) {
userKey = null;
}
}
enterpriseConfig.mClientPrivateKey = userKey;
enterpriseConfig.mClientCertificate = readCertificate(in);
return enterpriseConfig;
}
private X509Certificate readCertificate(Parcel in) {
X509Certificate cert = null;
int len = in.readInt();
if (len > 0) {
try {
byte[] bytes = new byte[len];
in.readByteArray(bytes);
CertificateFactory cFactory = CertificateFactory.getInstance("X.509");
cert = (X509Certificate) cFactory
.generateCertificate(new ByteArrayInputStream(bytes));
} catch (CertificateException e) {
cert = null;
}
}
return cert;
}
public WifiEnterpriseConfig[] newArray(int size) {
return new WifiEnterpriseConfig[size];
}
@@ -145,13 +233,13 @@ public class WifiEnterpriseConfig implements Parcelable {
public static final String[] strings = {EMPTY_VALUE, "PAP", "MSCHAP", "MSCHAPV2", "GTC" };
}
/** Internal use only @hide */
public HashMap<String, String> getFields() {
/** Internal use only */
HashMap<String, String> getFields() {
return mFields;
}
/** Internal use only @hide */
public static String[] getSupplicantKeys() {
/** Internal use only */
static String[] getSupplicantKeys() {
return new String[] { EAP_KEY, PHASE2_KEY, IDENTITY_KEY, ANON_IDENTITY_KEY, PASSWORD_KEY,
CLIENT_CERT_KEY, CA_CERT_KEY, SUBJECT_MATCH_KEY, ENGINE_KEY, ENGINE_ID_KEY,
PRIVATE_KEY_ID_KEY };
@@ -271,19 +359,37 @@ public class WifiEnterpriseConfig implements Parcelable {
* a certificate
* </p>
* @param alias identifies the certificate
* @hide
*/
public void setCaCertificate(String alias) {
public void setCaCertificateAlias(String alias) {
setFieldValue(CA_CERT_KEY, alias, CA_CERT_PREFIX);
}
/**
* Get CA certificate alias
* @return alias to the CA certificate
* @hide
*/
public String getCaCertificate() {
public String getCaCertificateAlias() {
return getFieldValue(CA_CERT_KEY, CA_CERT_PREFIX);
}
/**
* Specify a X.509 certificate that identifies the server.
*
* <p>A default name is automatically assigned to the certificate and used
* with this configuration.
* @param cert X.509 CA certificate
* @throws IllegalArgumentException if not a CA certificate
*/
public void setCaCertificate(X509Certificate cert) {
if (cert.getBasicConstraints() >= 0) {
mCaCert = cert;
} else {
throw new IllegalArgumentException("Not a CA certificate");
}
}
/**
* Set Client certificate alias.
*
@@ -291,8 +397,9 @@ public class WifiEnterpriseConfig implements Parcelable {
* a certificate
* </p>
* @param alias identifies the certificate
* @hide
*/
public void setClientCertificate(String alias) {
public void setClientCertificateAlias(String alias) {
setFieldValue(CLIENT_CERT_KEY, alias, CLIENT_CERT_PREFIX);
setFieldValue(PRIVATE_KEY_ID_KEY, alias, Credentials.USER_PRIVATE_KEY);
// Also, set engine parameters
@@ -308,11 +415,117 @@ public class WifiEnterpriseConfig implements Parcelable {
/**
* Get client certificate alias
* @return alias to the client certificate
* @hide
*/
public String getClientCertificate() {
public String getClientCertificateAlias() {
return getFieldValue(CLIENT_CERT_KEY, CLIENT_CERT_PREFIX);
}
/**
* Specify a private key and client certificate for client authorization.
*
* <p>A default name is automatically assigned to the key entry and used
* with this configuration.
* @param privateKey
* @param clientCertificate
*/
public void setClientKeyEntry(PrivateKey privateKey, X509Certificate clientCertificate) {
if (clientCertificate != null) {
if (clientCertificate.getBasicConstraints() != -1) {
throw new IllegalArgumentException("Cannot be a CA certificate");
}
if (privateKey == null) {
throw new IllegalArgumentException("Client cert without a private key");
}
if (privateKey.getEncoded() == null) {
throw new IllegalArgumentException("Private key cannot be encoded");
}
}
mClientPrivateKey = privateKey;
mClientCertificate = clientCertificate;
}
boolean needsKeyStore() {
// Has no keys to be installed
if (mClientCertificate == null && mCaCert == null) return false;
return true;
}
boolean installKeys(android.security.KeyStore keyStore, String name) {
boolean ret = true;
String privKeyName = Credentials.USER_PRIVATE_KEY + name;
String userCertName = Credentials.USER_CERTIFICATE + name;
String caCertName = Credentials.CA_CERTIFICATE + name;
if (mClientCertificate != null) {
byte[] privKeyData = mClientPrivateKey.getEncoded();
ret = keyStore.importKey(privKeyName, privKeyData);
if (ret == false) {
return ret;
}
ret = putCertInKeyStore(keyStore, userCertName, mClientCertificate);
if (ret == false) {
// Remove private key installed
keyStore.delKey(privKeyName);
return ret;
}
}
if (mCaCert != null) {
ret = putCertInKeyStore(keyStore, caCertName, mCaCert);
if (ret == false) {
if (mClientCertificate != null) {
// Remove client key+cert
keyStore.delKey(privKeyName);
keyStore.delete(userCertName);
}
return ret;
}
}
// Set alias names
if (mClientCertificate != null) {
setClientCertificateAlias(name);
mClientPrivateKey = null;
mClientCertificate = null;
}
if (mCaCert != null) {
setCaCertificateAlias(name);
mCaCert = null;
}
return ret;
}
private boolean putCertInKeyStore(android.security.KeyStore keyStore, String name,
Certificate cert) {
try {
byte[] certData = Credentials.convertToPem(cert);
return keyStore.put(name, certData);
} catch (IOException e1) {
return false;
} catch (CertificateException e2) {
return false;
}
}
void removeKeys(android.security.KeyStore keyStore) {
String client = getFieldValue(CLIENT_CERT_KEY, CLIENT_CERT_PREFIX);
// a valid client certificate is configured
if (!TextUtils.isEmpty(client)) {
keyStore.delKey(Credentials.USER_PRIVATE_KEY + client);
keyStore.delete(Credentials.USER_CERTIFICATE + client);
}
String ca = getFieldValue(CA_CERT_KEY, CA_CERT_PREFIX);
// a valid ca certificate is configured
if (!TextUtils.isEmpty(ca)) {
keyStore.delete(Credentials.CA_CERTIFICATE + ca);
}
}
/**
* Set subject match. This is the substring to be matched against the subject of the
* authentication server certificate.
@@ -330,13 +543,28 @@ public class WifiEnterpriseConfig implements Parcelable {
return getFieldValue(SUBJECT_MATCH_KEY, "");
}
/** See {@link WifiConfiguration#getKeyIdForCredentials} @hide */
String getKeyId(WifiEnterpriseConfig current) {
String eap = mFields.get(EAP_KEY);
String phase2 = mFields.get(PHASE2_KEY);
// If either eap or phase2 are not initialized, use current config details
if (TextUtils.isEmpty((eap))) {
eap = current.mFields.get(EAP_KEY);
}
if (TextUtils.isEmpty(phase2)) {
phase2 = current.mFields.get(PHASE2_KEY);
}
return eap + "_" + phase2;
}
/** Migrates the old style TLS config to the new config style. This should only be used
* when restoring an old wpa_supplicant.conf or upgrading from a previous
* platform version.
* @return true if the config was updated
* @hide
*/
public boolean migrateOldEapTlsNative(WifiNative wifiNative, int netId) {
boolean migrateOldEapTlsNative(WifiNative wifiNative, int netId) {
String oldPrivateKey = wifiNative.getNetworkVariable(netId, OLD_PRIVATE_KEY_NAME);
/*
* If the old configuration value is not present, then there is nothing
@@ -395,6 +623,7 @@ public class WifiEnterpriseConfig implements Parcelable {
* @return the index into array
*/
private int getStringIndex(String arr[], String toBeFound, int defaultIndex) {
if (TextUtils.isEmpty(toBeFound)) return defaultIndex;
for (int i = 0; i < arr.length; i++) {
if (toBeFound.equals(arr[i])) return i;
}
@@ -408,7 +637,8 @@ public class WifiEnterpriseConfig implements Parcelable {
*/
private String getFieldValue(String key, String prefix) {
String value = mFields.get(key);
if (EMPTY_VALUE.equals(value)) return "";
// Uninitialized or known to be empty after reading from supplicant
if (TextUtils.isEmpty(value) || EMPTY_VALUE.equals(value)) return "";
return removeDoubleQuotes(value).substring(prefix.length());
}