blob: 6d120902a25aac9b2777e22fe903797e3b711369 [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.hotspot2;
import android.net.wifi.hotspot2.PasspointConfiguration;
import android.security.Credentials;
import android.util.Log;
import com.android.server.wifi.WifiKeyStore;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
/**
* Abstraction for Passpoint service provider. This class contains the both static
* Passpoint configuration data and the runtime data (e.g. blacklisted SSIDs, statistics).
*/
public class PasspointProvider {
private static final String TAG = "PasspointProvider";
// Prefix for certificates and keys aliases.
private static final String ALIAS_PREFIX = "HS2_";
private final PasspointConfiguration mConfig;
private final WifiKeyStore mKeyStore;
// Unique identifier for this provider. Used as part of the alias names for identifying
// certificates and keys installed on the keystore.
private final long mProviderId;
// Aliases for the private keys and certificates installed in the keystore.
private String mCaCertificateAlias;
private String mClientPrivateKeyAlias;
private String mClientCertificateAlias;
public PasspointProvider(PasspointConfiguration config, WifiKeyStore keyStore,
long providerId) {
// Maintain a copy of the configuration to avoid it being updated by others.
mConfig = new PasspointConfiguration(config);
mKeyStore = keyStore;
mProviderId = providerId;
}
public PasspointConfiguration getConfig() {
// Return a copy of the configuration to avoid it being updated by others.
return new PasspointConfiguration(mConfig);
}
public String getCaCertificateAlias() {
return mCaCertificateAlias;
}
public String getClientPrivateKeyAlias() {
return mClientPrivateKeyAlias;
}
public String getClientCertificateAlias() {
return mClientCertificateAlias;
}
/**
* Install certificates and key based on current configuration.
* Note: the certificates and keys in the configuration will get cleared once
* they're installed in the keystore.
*
* @return true on success
*/
public boolean installCertsAndKeys() {
// Install CA certificate.
if (mConfig.credential.caCertificate != null) {
String alias = formatAliasName(Credentials.CA_CERTIFICATE, mProviderId);
if (!mKeyStore.putCertInKeyStore(alias, mConfig.credential.caCertificate)) {
Log.e(TAG, "Failed to install CA Certificate");
uninstallCertsAndKeys();
return false;
}
mCaCertificateAlias = alias;
}
// Install the client private key.
if (mConfig.credential.clientPrivateKey != null) {
String alias = formatAliasName(Credentials.USER_PRIVATE_KEY, mProviderId);
if (!mKeyStore.putKeyInKeyStore(alias, mConfig.credential.clientPrivateKey)) {
Log.e(TAG, "Failed to install client private key");
uninstallCertsAndKeys();
return false;
}
mClientPrivateKeyAlias = alias;
}
// Install the client certificate.
if (mConfig.credential.clientCertificateChain != null) {
X509Certificate clientCert =
getClientCertificate(mConfig.credential.clientCertificateChain,
mConfig.credential.certCredential.certSha256FingerPrint);
if (clientCert == null) {
Log.e(TAG, "Failed to locate client certificate");
uninstallCertsAndKeys();
return false;
}
String alias = formatAliasName(Credentials.USER_CERTIFICATE, mProviderId);
if (!mKeyStore.putCertInKeyStore(alias, clientCert)) {
Log.e(TAG, "Failed to install client certificate");
uninstallCertsAndKeys();
return false;
}
mClientCertificateAlias = alias;
}
// Clear the keys and certificates in the configuration.
mConfig.credential.caCertificate = null;
mConfig.credential.clientPrivateKey = null;
mConfig.credential.clientCertificateChain = null;
return true;
}
/**
* Remove any installed certificates and key.
*/
public void uninstallCertsAndKeys() {
if (mCaCertificateAlias != null) {
if (!mKeyStore.removeEntryFromKeyStore(mCaCertificateAlias)) {
Log.e(TAG, "Failed to remove entry: " + mCaCertificateAlias);
}
mCaCertificateAlias = null;
}
if (mClientPrivateKeyAlias != null) {
if (!mKeyStore.removeEntryFromKeyStore(mClientPrivateKeyAlias)) {
Log.e(TAG, "Failed to remove entry: " + mClientPrivateKeyAlias);
}
mClientPrivateKeyAlias = null;
}
if (mClientCertificateAlias != null) {
if (!mKeyStore.removeEntryFromKeyStore(mClientCertificateAlias)) {
Log.e(TAG, "Failed to remove entry: " + mClientCertificateAlias);
}
mClientCertificateAlias = null;
}
}
/**
* Create and return a certificate or key alias name based on the given prefix and uid.
*
* @param type The key or certificate type string
* @param uid The UID of the alias
* @return String
*/
private static String formatAliasName(String type, long uid) {
return type + ALIAS_PREFIX + uid;
}
/**
* Retrieve the client certificate from the certificates chain. The certificate
* with the matching SHA256 digest is the client certificate.
*
* @param certChain The client certificates chain
* @param expectedSha256Fingerprint The expected SHA256 digest of the client certificate
* @return {@link java.security.cert.X509Certificate}
*/
private static X509Certificate getClientCertificate(X509Certificate[] certChain,
byte[] expectedSha256Fingerprint) {
if (certChain == null) {
return null;
}
try {
MessageDigest digester = MessageDigest.getInstance("SHA-256");
for (X509Certificate certificate : certChain) {
digester.reset();
byte[] fingerprint = digester.digest(certificate.getEncoded());
if (Arrays.equals(expectedSha256Fingerprint, fingerprint)) {
return certificate;
}
}
} catch (CertificateEncodingException | NoSuchAlgorithmException e) {
return null;
}
return null;
}
}