blob: b667fd4c97bc6c100381fa78cf589c3d4db8bd59 [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.wifi;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiEnterpriseConfig;
import android.os.Process;
import android.security.Credentials;
import android.security.KeyChain;
import android.security.KeyStore;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
import java.io.IOException;
import java.security.Key;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
/**
* This class provides the methods to access keystore for certificate management.
*
* NOTE: This class should only be used from WifiConfigManager!
*/
public class WifiKeyStore {
private static final String TAG = "WifiKeyStore";
private boolean mVerboseLoggingEnabled = false;
private final KeyStore mKeyStore;
WifiKeyStore(KeyStore keyStore) {
mKeyStore = keyStore;
}
/**
* Enable verbose logging.
*/
void enableVerboseLogging(boolean verbose) {
mVerboseLoggingEnabled = verbose;
}
// Certificate and private key management for EnterpriseConfig
private static boolean needsKeyStore(WifiEnterpriseConfig config) {
return (config.getClientCertificate() != null || config.getCaCertificate() != null);
}
private static boolean isHardwareBackedKey(Key key) {
return KeyChain.isBoundKeyAlgorithm(key.getAlgorithm());
}
private static boolean hasHardwareBackedKey(Certificate certificate) {
return isHardwareBackedKey(certificate.getPublicKey());
}
/**
* Install keys for given enterprise network.
*
* @param existingConfig Existing config corresponding to the network already stored in our
* database. This maybe null if it's a new network.
* @param config Config corresponding to the network.
* @return true if successful, false otherwise.
*/
private boolean installKeys(WifiEnterpriseConfig existingConfig, WifiEnterpriseConfig config,
String name) {
boolean ret = true;
String privKeyName = Credentials.USER_PRIVATE_KEY + name;
String userCertName = Credentials.USER_CERTIFICATE + name;
if (config.getClientCertificate() != null) {
byte[] privKeyData = config.getClientPrivateKey().getEncoded();
if (mVerboseLoggingEnabled) {
if (isHardwareBackedKey(config.getClientPrivateKey())) {
Log.d(TAG, "importing keys " + name + " in hardware backed store");
} else {
Log.d(TAG, "importing keys " + name + " in software backed store");
}
}
ret = mKeyStore.importKey(privKeyName, privKeyData, Process.WIFI_UID,
KeyStore.FLAG_NONE);
if (!ret) {
return ret;
}
ret = putCertInKeyStore(userCertName, config.getClientCertificate());
if (!ret) {
// Remove private key installed
mKeyStore.delete(privKeyName, Process.WIFI_UID);
return ret;
}
}
X509Certificate[] caCertificates = config.getCaCertificates();
Set<String> oldCaCertificatesToRemove = new ArraySet<>();
if (existingConfig != null && existingConfig.getCaCertificateAliases() != null) {
oldCaCertificatesToRemove.addAll(
Arrays.asList(existingConfig.getCaCertificateAliases()));
}
List<String> caCertificateAliases = null;
if (caCertificates != null) {
caCertificateAliases = new ArrayList<>();
for (int i = 0; i < caCertificates.length; i++) {
String alias = caCertificates.length == 1 ? name
: String.format("%s_%d", name, i);
oldCaCertificatesToRemove.remove(alias);
ret = putCertInKeyStore(Credentials.CA_CERTIFICATE + alias, caCertificates[i]);
if (!ret) {
// Remove client key+cert
if (config.getClientCertificate() != null) {
mKeyStore.delete(privKeyName, Process.WIFI_UID);
mKeyStore.delete(userCertName, Process.WIFI_UID);
}
// Remove added CA certs.
for (String addedAlias : caCertificateAliases) {
mKeyStore.delete(Credentials.CA_CERTIFICATE + addedAlias, Process.WIFI_UID);
}
return ret;
} else {
caCertificateAliases.add(alias);
}
}
}
// Remove old CA certs.
for (String oldAlias : oldCaCertificatesToRemove) {
mKeyStore.delete(Credentials.CA_CERTIFICATE + oldAlias, Process.WIFI_UID);
}
// Set alias names
if (config.getClientCertificate() != null) {
config.setClientCertificateAlias(name);
config.resetClientKeyEntry();
}
if (caCertificates != null) {
config.setCaCertificateAliases(
caCertificateAliases.toArray(new String[caCertificateAliases.size()]));
config.resetCaCertificate();
}
return ret;
}
/**
* Install a certificate into the keystore.
*
* @param name The alias name of the certificate to be installed
* @param cert The certificate to be installed
* @return true on success
*/
public boolean putCertInKeyStore(String name, Certificate cert) {
try {
byte[] certData = Credentials.convertToPem(cert);
if (mVerboseLoggingEnabled) Log.d(TAG, "putting certificate " + name + " in keystore");
return mKeyStore.put(name, certData, Process.WIFI_UID, KeyStore.FLAG_NONE);
} catch (IOException e1) {
return false;
} catch (CertificateException e2) {
return false;
}
}
/**
* Install a key into the keystore.
*
* @param name The alias name of the key to be installed
* @param key The key to be installed
* @return true on success
*/
public boolean putKeyInKeyStore(String name, Key key) {
byte[] privKeyData = key.getEncoded();
return mKeyStore.importKey(name, privKeyData, Process.WIFI_UID, KeyStore.FLAG_NONE);
}
/**
* Remove a certificate or key entry specified by the alias name from the keystore.
*
* @param name The alias name of the entry to be removed
* @return true on success
*/
public boolean removeEntryFromKeyStore(String name) {
return mKeyStore.delete(name, Process.WIFI_UID);
}
/**
* Remove enterprise keys from the network config.
*
* @param config Config corresponding to the network.
*/
public void removeKeys(WifiEnterpriseConfig config) {
String client = config.getClientCertificateAlias();
// a valid client certificate is configured
if (!TextUtils.isEmpty(client)) {
if (mVerboseLoggingEnabled) Log.d(TAG, "removing client private key and user cert");
mKeyStore.delete(Credentials.USER_PRIVATE_KEY + client, Process.WIFI_UID);
mKeyStore.delete(Credentials.USER_CERTIFICATE + client, Process.WIFI_UID);
}
String[] aliases = config.getCaCertificateAliases();
// a valid ca certificate is configured
if (aliases != null) {
for (String ca : aliases) {
if (!TextUtils.isEmpty(ca)) {
if (mVerboseLoggingEnabled) Log.d(TAG, "removing CA cert: " + ca);
mKeyStore.delete(Credentials.CA_CERTIFICATE + ca, Process.WIFI_UID);
}
}
}
}
/**
* Update/Install keys for given enterprise network.
*
* @param config Config corresponding to the network.
* @param existingConfig Existing config corresponding to the network already stored in our
* database. This maybe null if it's a new network.
* @return true if successful, false otherwise.
*/
public boolean updateNetworkKeys(WifiConfiguration config, WifiConfiguration existingConfig) {
WifiEnterpriseConfig enterpriseConfig = config.enterpriseConfig;
if (needsKeyStore(enterpriseConfig)) {
try {
/* config passed may include only fields being updated.
* In order to generate the key id, fetch uninitialized
* fields from the currently tracked configuration
*/
String keyId = config.getKeyIdForCredentials(existingConfig);
if (!installKeys(existingConfig != null
? existingConfig.enterpriseConfig : null, enterpriseConfig, keyId)) {
Log.e(TAG, config.SSID + ": failed to install keys");
return false;
}
} catch (IllegalStateException e) {
Log.e(TAG, config.SSID + " invalid config for key installation: " + e.getMessage());
return false;
}
}
return true;
}
/**
* Checks whether the configuration requires a software backed keystore or not.
* @param config WifiEnterprise config instance pointing to the enterprise configuration of the
* network.
*/
public static boolean needsSoftwareBackedKeyStore(WifiEnterpriseConfig config) {
String client = config.getClientCertificateAlias();
if (!TextUtils.isEmpty(client)) {
// a valid client certificate is configured
// BUGBUG(b/29578316): keyStore.get() never returns certBytes; because it is not
// taking WIFI_UID as a parameter. It always looks for certificate
// with SYSTEM_UID, and never finds any Wifi certificates. Assuming that
// all certificates need software keystore until we get the get() API
// fixed.
return true;
}
return false;
}
}