summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--service/java/com/android/server/wifi/hotspot2/OsuServerConnection.java202
-rw-r--r--service/java/com/android/server/wifi/hotspot2/PasspointProvisioner.java284
-rw-r--r--service/java/com/android/server/wifi/hotspot2/ServiceProviderVerifier.java (renamed from service/java/com/android/server/wifi/hotspot2/ASN1SubjectAltNamesParser.java)55
-rw-r--r--service/java/com/android/server/wifi/hotspot2/soap/HttpsServiceConnection.java4
-rw-r--r--tests/wifitests/src/com/android/server/wifi/hotspot2/OsuServerConnectionTest.java152
-rw-r--r--tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointProvisionerTest.java118
-rw-r--r--tests/wifitests/src/com/android/server/wifi/hotspot2/ServiceProviderVerifierTest.java (renamed from tests/wifitests/src/com/android/server/wifi/hotspot2/ASN1SubjectAltNamesParserTest.java)78
-rw-r--r--tests/wifitests/src/com/android/server/wifi/hotspot2/soap/HttpsTransportTest.java6
8 files changed, 815 insertions, 84 deletions
diff --git a/service/java/com/android/server/wifi/hotspot2/OsuServerConnection.java b/service/java/com/android/server/wifi/hotspot2/OsuServerConnection.java
index 9952ce43b..08281cb17 100644
--- a/service/java/com/android/server/wifi/hotspot2/OsuServerConnection.java
+++ b/service/java/com/android/server/wifi/hotspot2/OsuServerConnection.java
@@ -32,19 +32,28 @@ import com.android.server.wifi.hotspot2.soap.HttpsTransport;
import com.android.server.wifi.hotspot2.soap.SoapParser;
import com.android.server.wifi.hotspot2.soap.SppResponseMessage;
+import org.ksoap2.HeaderProperty;
import org.ksoap2.serialization.AttributeInfo;
import org.ksoap2.serialization.SoapObject;
import org.ksoap2.serialization.SoapSerializationEnvelope;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
+import java.util.ArrayList;
import java.util.Collection;
+import java.util.HashMap;
import java.util.List;
import java.util.Locale;
+import java.util.Map;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
@@ -76,6 +85,10 @@ public class OsuServerConnection {
private boolean mVerboseLoggingEnabled = false;
private Looper mLooper;
+ public static final int TRUST_CERT_TYPE_AAA = 1;
+ public static final int TRUST_CERT_TYPE_REMEDIATION = 2;
+ public static final int TRUST_CERT_TYPE_POLICY = 3;
+
@VisibleForTesting
/* package */ OsuServerConnection(Looper looper) {
mLooper = looper;
@@ -91,9 +104,9 @@ public class OsuServerConnection {
}
/**
- * Initialize socket factory for server connection using HTTPS
+ * Initializes socket factory for server connection using HTTPS
*
- * @param tlsContext SSLContext that will be used for HTTPS connection
+ * @param tlsContext SSLContext that will be used for HTTPS connection
* @param trustManagerImpl TrustManagerImpl delegate to validate certs
*/
public void init(SSLContext tlsContext, TrustManagerImpl trustManagerImpl) {
@@ -102,7 +115,7 @@ public class OsuServerConnection {
}
try {
mTrustManager = new WFATrustManager(trustManagerImpl);
- tlsContext.init(null, new TrustManager[] { mTrustManager }, null);
+ tlsContext.init(null, new TrustManager[]{mTrustManager}, null);
mSocketFactory = tlsContext.getSocketFactory();
} catch (KeyManagementException e) {
Log.w(TAG, "Initialization failed");
@@ -141,7 +154,7 @@ public class OsuServerConnection {
/**
* Connect to the OSU server
*
- * @param url Osu Server's URL
+ * @param url Osu Server's URL
* @param network current network connection
* @return boolean value, true if connection was successful
*
@@ -155,6 +168,8 @@ public class OsuServerConnection {
try {
urlConnection = (HttpsURLConnection) mNetwork.openConnection(mUrl);
urlConnection.setSSLSocketFactory(mSocketFactory);
+ urlConnection.setConnectTimeout(HttpsServiceConnection.DEFAULT_TIMEOUT_MS);
+ urlConnection.setReadTimeout(HttpsServiceConnection.DEFAULT_TIMEOUT_MS);
urlConnection.connect();
} catch (IOException e) {
Log.e(TAG, "Unable to establish a URL connection");
@@ -183,7 +198,7 @@ public class OsuServerConnection {
return false;
}
- for (Pair<Locale, String> identity : ASN1SubjectAltNamesParser.getProviderNames(
+ for (Pair<Locale, String> identity : ServiceProviderVerifier.getProviderNames(
mTrustManager.getProviderCert())) {
if (identity.first == null) continue;
@@ -227,6 +242,36 @@ public class OsuServerConnection {
return true;
}
+ /**
+ * Retrieves Trust Root CA certificates for AAA, Remediation, Policy Server
+ *
+ * @param trustCertsInfo trust cert information for each type (AAA,Remediation and Policy).
+ * {@code Key} is the cert type.
+ * {@code Value} is the map that has a key for certUrl and a value for
+ * fingerprint of the certificate.
+ * @return {@code true} if {@link Network} is valid and {@code trustCertsInfo} is not null,
+ * {@code false} otherwise.
+ */
+ public boolean retrieveTrustRootCerts(
+ @NonNull Map<Integer, Map<String, byte[]>> trustCertsInfo) {
+ if (mNetwork == null) {
+ Log.e(TAG, "Network is not established");
+ return false;
+ }
+
+ if (mUrlConnection == null) {
+ Log.e(TAG, "Server certificate is not validated");
+ return false;
+ }
+
+ if (trustCertsInfo == null || trustCertsInfo.isEmpty()) {
+ Log.e(TAG, "TrustCertsInfo is not valid");
+ return false;
+ }
+ mHandler.post(() -> performRetrievingTrustRootCerts(trustCertsInfo));
+ return true;
+ }
+
private void performSoapMessageExchange(@NonNull SoapSerializationEnvelope soapEnvelope) {
if (mServiceConnection != null) {
mServiceConnection.disconnect();
@@ -241,7 +286,7 @@ public class OsuServerConnection {
return;
}
- SppResponseMessage sppResponse = null;
+ SppResponseMessage sppResponse;
try {
// Sending the SOAP message
mHttpsTransport.call("", soapEnvelope);
@@ -294,8 +339,149 @@ public class OsuServerConnection {
}
}
+ private void performRetrievingTrustRootCerts(
+ @NonNull Map<Integer, Map<String, byte[]>> trustCertsInfo) {
+ // Key: CERT_TYPE (AAA, REMEDIATION, POLICY), Value: a list of X509Certificate retrieved for
+ // the type.
+ Map<Integer, List<X509Certificate>> trustRootCertificates = new HashMap<>();
+
+ for (Map.Entry<Integer, Map<String, byte[]>> certInfoPerType : trustCertsInfo.entrySet()) {
+ List<X509Certificate> certificates = new ArrayList<>();
+
+ // Iterates certInfo to get a cert with a url provided in certInfo.key().
+ // Key: Cert url, Value: SHA-256 hash bytes to match the fingerprint of a
+ // certificates retrieved from server.
+ for (Map.Entry<String, byte[]> certInfo : certInfoPerType.getValue().entrySet()) {
+ if (certInfo.getValue() == null) {
+ // clear all of retrieved CA certs so that PasspointProvisioner aborts
+ // current flow.
+ trustRootCertificates.clear();
+ break;
+ }
+ X509Certificate certificate = getCert(certInfo.getKey());
+
+ if (certificate == null || !ServiceProviderVerifier.verifyCertFingerprint(
+ certificate, certInfo.getValue())) {
+ // If any failure happens, clear all of retrieved CA certs so that
+ // PasspointProvisioner aborts current flow.
+ trustRootCertificates.clear();
+ break;
+ }
+ certificates.add(certificate);
+ }
+ if (!certificates.isEmpty()) {
+ trustRootCertificates.put(certInfoPerType.getKey(), certificates);
+ }
+ }
+
+ if (mOsuServerCallbacks != null) {
+ // If it passes empty trustRootCertificates here, PasspointProvisioner will abort
+ // current flow because it indicates that client device doesn't get any trust root
+ // certificates from server.
+ mOsuServerCallbacks.onReceivedTrustRootCertificates(mOsuServerCallbacks.getSessionId(),
+ trustRootCertificates);
+ }
+ }
+
+ /**
+ * Retrieves a X.509 Certificate from server.
+ *
+ * @param certUrl url to retrieve a X.509 Certificate
+ * @return {@link X509Certificate} in success, {@code null} otherwise.
+ */
+ private X509Certificate getCert(@NonNull String certUrl) {
+ if (certUrl == null || !certUrl.toLowerCase(Locale.US).startsWith("https://")) {
+ Log.e(TAG, "invalid certUrl provided");
+ return null;
+ }
+
+ try {
+ URL serverUrl = new URL(certUrl);
+ CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
+ if (mServiceConnection != null) {
+ mServiceConnection.disconnect();
+ }
+ mServiceConnection = getServiceConnection(serverUrl, mNetwork);
+ mServiceConnection.setRequestMethod("GET");
+ mServiceConnection.setRequestProperty("Accept-Encoding", "gzip");
+
+ if (mServiceConnection.getResponseCode() != HttpURLConnection.HTTP_OK) {
+ Log.e(TAG, "The response code of the HTTPS GET to " + certUrl
+ + " is not OK, but " + mServiceConnection.getResponseCode());
+ return null;
+ }
+ boolean bPkcs7 = false;
+ boolean bBase64 = false;
+ List<HeaderProperty> properties = mServiceConnection.getResponseProperties();
+ for (HeaderProperty property : properties) {
+ if (property == null || property.getKey() == null || property.getValue() == null) {
+ continue;
+ }
+ if (property.getKey().equalsIgnoreCase("Content-Type")) {
+ if (property.getValue().equals("application/pkcs7-mime")
+ || property.getValue().equals("application/x-x509-ca-cert")) {
+ // application/x-x509-ca-cert : File content is a DER encoded X.509
+ // certificate
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "a certificate found in a HTTPS response from " + certUrl);
+ }
+
+ // ca cert
+ bPkcs7 = true;
+ }
+ }
+ if (property.getKey().equalsIgnoreCase("Content-Transfer-Encoding")
+ && property.getValue().equalsIgnoreCase("base64")) {
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG,
+ "base64 encoding content in a HTTP response from " + certUrl);
+ }
+ bBase64 = true;
+ }
+ }
+ if (!bPkcs7) {
+ Log.e(TAG, "no X509Certificate found in the HTTPS response");
+ return null;
+ }
+ InputStream in = mServiceConnection.openInputStream();
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ byte[] buf = new byte[8192];
+ while (true) {
+ int rd = in.read(buf, 0, 8192);
+ if (rd == -1) {
+ break;
+ }
+ bos.write(buf, 0, rd);
+ }
+ in.close();
+ bos.flush();
+ byte[] byteArray = bos.toByteArray();
+ if (bBase64) {
+ String s = new String(byteArray);
+ byteArray = android.util.Base64.decode(s, android.util.Base64.DEFAULT);
+ }
+
+ X509Certificate certificate = (X509Certificate) certFactory.generateCertificate(
+ new ByteArrayInputStream(byteArray));
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "cert : " + certificate.getSubjectDN());
+ }
+ return certificate;
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to get the data from " + certUrl + ": " + e);
+ } catch (CertificateException e) {
+ Log.e(TAG, "Failed to get instance for CertificateFactory " + e);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Failed to decode the data: " + e);
+ } finally {
+ mServiceConnection.disconnect();
+ mServiceConnection = null;
+ }
+ return null;
+ }
+
/**
- * Get the HTTPS service connection used for SOAP message exchange.
+ * Gets the HTTPS service connection used for SOAP message exchange.
*
* @return {@link HttpsServiceConnection}
*/
@@ -317,7 +503,7 @@ public class OsuServerConnection {
}
/**
- * Clean up
+ * Cleans up
*/
public void cleanup() {
if (mUrlConnection != null) {
diff --git a/service/java/com/android/server/wifi/hotspot2/PasspointProvisioner.java b/service/java/com/android/server/wifi/hotspot2/PasspointProvisioner.java
index 25073506e..0dc3428cc 100644
--- a/service/java/com/android/server/wifi/hotspot2/PasspointProvisioner.java
+++ b/service/java/com/android/server/wifi/hotspot2/PasspointProvisioner.java
@@ -47,7 +47,11 @@ import com.android.server.wifi.hotspot2.soap.command.SppCommand;
import java.net.MalformedURLException;
import java.net.URL;
+import java.security.cert.X509Certificate;
+import java.util.HashMap;
+import java.util.List;
import java.util.Locale;
+import java.util.Map;
/**
* Provides methods to carry out provisioning flow
@@ -75,6 +79,7 @@ public class PasspointProvisioner {
private int mCurrentSessionId = 0;
private int mCallingUid;
private boolean mVerboseLoggingEnabled = false;
+ private WifiManager mWifiManager;
PasspointProvisioner(Context context, WifiNative wifiNative,
PasspointObjectFactory objectFactory) {
@@ -90,9 +95,11 @@ public class PasspointProvisioner {
/**
* Sets up for provisioning
+ *
* @param looper Looper on which the Provisioning state machine will run
*/
public void init(Looper looper) {
+ mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
mProvisioningStateMachine.start(new Handler(looper));
mOsuNetworkConnection.init(mProvisioningStateMachine.getHandler());
// Offload the heavy load job to another thread
@@ -106,6 +113,7 @@ public class PasspointProvisioner {
/**
* Enable verbose logging to help debug failures
+ *
* @param level integer indicating verbose logging enabled if > 0
*/
public void enableVerboseLogging(int level) {
@@ -116,9 +124,10 @@ public class PasspointProvisioner {
/**
* Start provisioning flow with a given provider.
+ *
* @param callingUid calling uid.
- * @param provider {@link OsuProvider} to provision with.
- * @param callback {@link IProvisioningCallback} to provide provisioning status.
+ * @param provider {@link OsuProvider} to provision with.
+ * @param callback {@link IProvisioningCallback} to provide provisioning status.
* @return boolean value, true if provisioning was started, false otherwise.
*
* Implements HS2.0 provisioning flow with a given HS2.0 provider.
@@ -154,6 +163,7 @@ public class PasspointProvisioner {
static final int STATE_WAITING_FOR_REDIRECT_RESPONSE = 6;
static final int STATE_WAITING_FOR_SECOND_SOAP_RESPONSE = 7;
static final int STATE_WAITING_FOR_THIRD_SOAP_RESPONSE = 8;
+ static final int STATE_WAITING_FOR_TRUST_ROOT_CERTS = 9;
private OsuProvider mOsuProvider;
private IProvisioningCallback mProvisioningCallback;
@@ -163,6 +173,7 @@ public class PasspointProvisioner {
private Network mNetwork;
private String mSessionId;
private String mWebUrl;
+ private PasspointConfiguration mPasspointConfiguration;
/**
* Initializes and starts the state machine with a handler to handle incoming events
@@ -195,12 +206,13 @@ public class PasspointProvisioner {
if (mVerboseLoggingEnabled) {
Log.v(TAG, "State Machine needs to be reset before starting provisioning");
}
- resetStateMachine(ProvisioningCallback.OSU_FAILURE_PROVISIONING_ABORTED);
+ resetStateMachineForFailure(ProvisioningCallback.OSU_FAILURE_PROVISIONING_ABORTED);
}
if (!mOsuServerConnection.canValidateServer()) {
Log.w(TAG, "Provisioning is not possible");
mProvisioningCallback = callback;
- resetStateMachine(ProvisioningCallback.OSU_FAILURE_PROVISIONING_NOT_AVAILABLE);
+ resetStateMachineForFailure(
+ ProvisioningCallback.OSU_FAILURE_PROVISIONING_NOT_AVAILABLE);
return;
}
URL serverUrl;
@@ -209,7 +221,7 @@ public class PasspointProvisioner {
} catch (MalformedURLException e) {
Log.e(TAG, "Invalid Server URL");
mProvisioningCallback = callback;
- resetStateMachine(ProvisioningCallback.OSU_FAILURE_SERVER_URL_INVALID);
+ resetStateMachineForFailure(ProvisioningCallback.OSU_FAILURE_SERVER_URL_INVALID);
return;
}
mServerUrl = serverUrl;
@@ -224,7 +236,7 @@ public class PasspointProvisioner {
if (!mOsuNetworkConnection.connect(mOsuProvider.getOsuSsid(),
mOsuProvider.getNetworkAccessIdentifier())) {
- resetStateMachine(ProvisioningCallback.OSU_FAILURE_AP_CONNECTION);
+ resetStateMachineForFailure(ProvisioningCallback.OSU_FAILURE_AP_CONNECTION);
return;
}
invokeProvisioningCallback(PROVISIONING_STATUS,
@@ -245,7 +257,7 @@ public class PasspointProvisioner {
Log.w(TAG, "Wifi Disable unhandled in state=" + mState);
return;
}
- resetStateMachine(ProvisioningCallback.OSU_FAILURE_AP_CONNECTION);
+ resetStateMachineForFailure(ProvisioningCallback.OSU_FAILURE_AP_CONNECTION);
}
/**
@@ -266,7 +278,7 @@ public class PasspointProvisioner {
Log.wtf(TAG, "Server Validation Failure unhandled in mState=" + mState);
return;
}
- resetStateMachine(ProvisioningCallback.OSU_FAILURE_SERVER_VALIDATION);
+ resetStateMachineForFailure(ProvisioningCallback.OSU_FAILURE_SERVER_VALIDATION);
}
/**
@@ -300,7 +312,7 @@ public class PasspointProvisioner {
public void handleRedirectResponse() {
if (mState != STATE_WAITING_FOR_REDIRECT_RESPONSE) {
Log.e(TAG, "Received redirect request in wrong state=" + mState);
- resetStateMachine(ProvisioningCallback.OSU_FAILURE_PROVISIONING_ABORTED);
+ resetStateMachineForFailure(ProvisioningCallback.OSU_FAILURE_PROVISIONING_ABORTED);
return;
}
@@ -322,11 +334,12 @@ public class PasspointProvisioner {
if (mState != STATE_WAITING_FOR_REDIRECT_RESPONSE) {
Log.e(TAG, "Received timeout error for HTTP redirect response in wrong state="
+ mState);
- resetStateMachine(ProvisioningCallback.OSU_FAILURE_PROVISIONING_ABORTED);
+ resetStateMachineForFailure(ProvisioningCallback.OSU_FAILURE_PROVISIONING_ABORTED);
return;
}
mRedirectListener.stopServer();
- resetStateMachine(ProvisioningCallback.OSU_FAILURE_TIMED_OUT_REDIRECT_LISTENER);
+ resetStateMachineForFailure(
+ ProvisioningCallback.OSU_FAILURE_TIMED_OUT_REDIRECT_LISTENER);
}
/**
@@ -353,7 +366,7 @@ public class PasspointProvisioner {
/**
* Handles SOAP message response sent by server
*
- * @param sessionId indicating current session ID
+ * @param sessionId indicating current session ID
* @param responseMessage SOAP SPP response, or {@code null} in any failure.
* Note: Called on main thread (WifiService thread).
*/
@@ -367,7 +380,7 @@ public class PasspointProvisioner {
if (responseMessage == null) {
Log.e(TAG, "failed to send the sppPostDevData message");
- resetStateMachine(ProvisioningCallback.OSU_FAILURE_SOAP_MESSAGE_EXCHANGE);
+ resetStateMachineForFailure(ProvisioningCallback.OSU_FAILURE_SOAP_MESSAGE_EXCHANGE);
return;
}
@@ -376,7 +389,7 @@ public class PasspointProvisioner {
!= SppResponseMessage.MessageType.POST_DEV_DATA_RESPONSE) {
Log.e(TAG, "Expected a PostDevDataResponse, but got "
+ responseMessage.getMessageType());
- resetStateMachine(
+ resetStateMachineForFailure(
ProvisioningCallback.OSU_FAILURE_UNEXPECTED_SOAP_MESSAGE_TYPE);
return;
}
@@ -387,7 +400,8 @@ public class PasspointProvisioner {
!= SppCommand.ExecCommandId.BROWSER) {
Log.e(TAG, "Expected a launchBrowser command, but got "
+ devDataResponse.getSppCommand().getExecCommandId());
- resetStateMachine(ProvisioningCallback.OSU_FAILURE_UNEXPECTED_COMMAND_TYPE);
+ resetStateMachineForFailure(
+ ProvisioningCallback.OSU_FAILURE_UNEXPECTED_COMMAND_TYPE);
return;
}
@@ -397,13 +411,15 @@ public class PasspointProvisioner {
mWebUrl = ((BrowserUri) devDataResponse.getSppCommand().getCommandData()).getUri();
if (mWebUrl == null) {
Log.e(TAG, "No Web-Url");
- resetStateMachine(ProvisioningCallback.OSU_FAILURE_INVALID_SERVER_URL);
+ resetStateMachineForFailure(
+ ProvisioningCallback.OSU_FAILURE_INVALID_SERVER_URL);
return;
}
if (!mWebUrl.toLowerCase(Locale.US).contains(mSessionId.toLowerCase(Locale.US))) {
Log.e(TAG, "Bad or Missing session ID in webUrl");
- resetStateMachine(ProvisioningCallback.OSU_FAILURE_INVALID_SERVER_URL);
+ resetStateMachineForFailure(
+ ProvisioningCallback.OSU_FAILURE_INVALID_SERVER_URL);
return;
}
launchOsuWebView();
@@ -412,7 +428,7 @@ public class PasspointProvisioner {
!= SppResponseMessage.MessageType.POST_DEV_DATA_RESPONSE) {
Log.e(TAG, "Expected a PostDevDataResponse, but got "
+ responseMessage.getMessageType());
- resetStateMachine(
+ resetStateMachineForFailure(
ProvisioningCallback.OSU_FAILURE_UNEXPECTED_SOAP_MESSAGE_TYPE);
return;
}
@@ -424,21 +440,20 @@ public class PasspointProvisioner {
Log.e(TAG, "Expected a ADD_MO command, but got " + (
(devDataResponse.getSppCommand() == null) ? "null"
: devDataResponse.getSppCommand().getSppCommandId()));
- resetStateMachine(
+ resetStateMachineForFailure(
ProvisioningCallback.OSU_FAILURE_UNEXPECTED_COMMAND_TYPE);
return;
}
- PasspointConfiguration passpointConfig = buildPasspointConfiguration(
- (PpsMoData) devDataResponse.getSppCommand().getCommandData());
-
- thirdSoapExchange(passpointConfig == null);
+ mPasspointConfiguration = buildPasspointConfiguration(
+ (PpsMoData) devDataResponse.getSppCommand().getCommandData());
+ thirdSoapExchange(mPasspointConfiguration == null);
} else if (mState == STATE_WAITING_FOR_THIRD_SOAP_RESPONSE) {
if (responseMessage.getMessageType()
!= SppResponseMessage.MessageType.EXCHANGE_COMPLETE) {
Log.e(TAG, "Expected a ExchangeCompleteMessage, but got "
+ responseMessage.getMessageType());
- resetStateMachine(
+ resetStateMachineForFailure(
ProvisioningCallback.OSU_FAILURE_UNEXPECTED_SOAP_MESSAGE_TYPE);
return;
}
@@ -449,7 +464,7 @@ public class PasspointProvisioner {
!= SppConstants.SppStatus.EXCHANGE_COMPLETE) {
Log.e(TAG, "Expected a ExchangeCompleteMessage Status, but got "
+ exchangeCompleteMessage.getStatus());
- resetStateMachine(
+ resetStateMachineForFailure(
ProvisioningCallback.OSU_FAILURE_UNEXPECTED_SOAP_MESSAGE_STATUS);
return;
}
@@ -458,11 +473,16 @@ public class PasspointProvisioner {
Log.e(TAG,
"In the SppExchangeComplete, got error "
+ exchangeCompleteMessage.getError());
- resetStateMachine(ProvisioningCallback.OSU_FAILURE_PROVISIONING_ABORTED);
+ resetStateMachineForFailure(
+ ProvisioningCallback.OSU_FAILURE_PROVISIONING_ABORTED);
return;
}
-
- // TODO(b/74244324): Implement a routine to get CAs for AAA, Remediation, Policy.
+ if (mPasspointConfiguration == null) {
+ Log.e(TAG, "No PPS MO to use for retrieving TrustCerts");
+ resetStateMachineForFailure(ProvisioningCallback.OSU_FAILURE_NO_PPS_MO);
+ return;
+ }
+ retrieveTrustRootCerts(mPasspointConfiguration);
} else {
if (mVerboseLoggingEnabled) {
Log.v(TAG, "Received an unexpected SOAP message in state=" + mState);
@@ -471,6 +491,67 @@ public class PasspointProvisioner {
}
/**
+ * Installs the trust root CA certificates for AAA, Remediation and Policy Server
+ *
+ * @param sessionId indicating current session ID
+ * @param trustRootCertificates trust root CA certificates to be installed.
+ */
+ public void installTrustRootCertificates(int sessionId,
+ @NonNull Map<Integer, List<X509Certificate>> trustRootCertificates) {
+ if (sessionId != mCurrentSessionId) {
+ Log.w(TAG, "Expected TrustRootCertificates callback for currentSessionId="
+ + mCurrentSessionId);
+ return;
+ }
+ if (mState != STATE_WAITING_FOR_TRUST_ROOT_CERTS) {
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "Received an unexpected TrustRootCertificates in state=" + mState);
+ }
+ return;
+ }
+
+ if (trustRootCertificates.isEmpty()) {
+ Log.e(TAG, "fails to retrieve trust root certificates");
+ resetStateMachineForFailure(
+ ProvisioningCallback.OSU_FAILURE_RETRIEVE_TRUST_ROOT_CERTIFICATES);
+ return;
+ }
+
+ List<X509Certificate> certificates = trustRootCertificates.get(
+ OsuServerConnection.TRUST_CERT_TYPE_AAA);
+ if (certificates == null || certificates.isEmpty()) {
+ Log.e(TAG, "fails to retrieve trust root certificate for AAA server");
+ resetStateMachineForFailure(
+ ProvisioningCallback.OSU_FAILURE_NO_AAA_TRUST_ROOT_CERTIFICATE);
+ return;
+ }
+
+ // TODO(117717842) : Currently PasspointConfiguration is only allowed to save a single
+ // trust CA certificate for AAA server. So, add a routine in PasspointConfiguration
+ // to store multiple trust CA certificates for AAA server.
+ mPasspointConfiguration.getCredential().setCaCertificate(
+ certificates.get(0));
+
+ // TODO(b/116346527): Implement a routine to store trust CA certificates for
+ // remediation and policy server.
+ try {
+ mWifiManager.addOrUpdatePasspointConfiguration(mPasspointConfiguration);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "fails to add a new PasspointConfiguration: " + e);
+ resetStateMachineForFailure(
+ ProvisioningCallback.OSU_FAILURE_ADD_PASSPOINT_CONFIGURATION);
+ return;
+ }
+
+ invokeProvisioningCompleteCallback();
+ if (mVerboseLoggingEnabled) {
+ Log.i(TAG, "Provisioning is complete for "
+ + mPasspointConfiguration.getHomeSp().getFqdn());
+ }
+ resetStateMachine();
+ }
+
+ /**
* Disconnect event received
*
* Note: Called on main thread (WifiService thread).
@@ -484,19 +565,24 @@ public class PasspointProvisioner {
return;
}
mNetwork = null;
- resetStateMachine(ProvisioningCallback.OSU_FAILURE_AP_CONNECTION);
+ resetStateMachineForFailure(ProvisioningCallback.OSU_FAILURE_AP_CONNECTION);
}
+ /**
+ * Establishes TLS session to the server(OSU Server, Remediation or Policy Server).
+ *
+ * @param network current {@link Network} associated with the target AP.
+ */
private void initiateServerConnection(Network network) {
if (mVerboseLoggingEnabled) {
Log.v(TAG, "Initiating server connection in state=" + mState);
}
if (mState != STATE_OSU_AP_CONNECTED) {
- Log.wtf(TAG , "Initiating server connection aborted in invalid state=" + mState);
+ Log.wtf(TAG, "Initiating server connection aborted in invalid state=" + mState);
return;
}
if (!mOsuServerConnection.connect(mServerUrl, network)) {
- resetStateMachine(ProvisioningCallback.OSU_FAILURE_SERVER_CONNECTION);
+ resetStateMachineForFailure(ProvisioningCallback.OSU_FAILURE_SERVER_CONNECTION);
return;
}
mNetwork = network;
@@ -523,6 +609,18 @@ public class PasspointProvisioner {
}
}
+ private void invokeProvisioningCompleteCallback() {
+ if (mProvisioningCallback == null) {
+ Log.e(TAG, "No provisioning complete callback registered");
+ return;
+ }
+ try {
+ mProvisioningCallback.onProvisioningComplete();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Remote Exception while posting provisioning complete");
+ }
+ }
+
/**
* Validate the OSU Server certificate based on the procedure in 7.3.2.2 of Hotspot2.0
* rel2 spec.
@@ -534,7 +632,8 @@ public class PasspointProvisioner {
}
if (!mOsuServerConnection.validateProvider(
Locale.getDefault(), mOsuProvider.getFriendlyName())) {
- resetStateMachine(ProvisioningCallback.OSU_FAILURE_SERVICE_PROVIDER_VERIFICATION);
+ resetStateMachineForFailure(
+ ProvisioningCallback.OSU_FAILURE_SERVICE_PROVIDER_VERIFICATION);
return;
}
invokeProvisioningCallback(PROVISIONING_STATUS,
@@ -552,7 +651,7 @@ public class PasspointProvisioner {
if (mState != STATE_OSU_SERVER_CONNECTED) {
Log.e(TAG, "Initiates soap message exchange in wrong state=" + mState);
- resetStateMachine(ProvisioningCallback.OSU_FAILURE_PROVISIONING_ABORTED);
+ resetStateMachineForFailure(ProvisioningCallback.OSU_FAILURE_PROVISIONING_ABORTED);
return;
}
@@ -570,11 +669,14 @@ public class PasspointProvisioner {
changeState(STATE_WAITING_FOR_FIRST_SOAP_RESPONSE);
} else {
Log.e(TAG, "HttpsConnection is not established for soap message exchange");
- resetStateMachine(ProvisioningCallback.OSU_FAILURE_SOAP_MESSAGE_EXCHANGE);
+ resetStateMachineForFailure(ProvisioningCallback.OSU_FAILURE_SOAP_MESSAGE_EXCHANGE);
return;
}
}
+ /**
+ * Launches OsuLogin Application for users to register a new subscription.
+ */
private void launchOsuWebView() {
if (mVerboseLoggingEnabled) {
Log.v(TAG, "launch Osu webview in state =" + mState);
@@ -582,7 +684,7 @@ public class PasspointProvisioner {
if (mState != STATE_WAITING_FOR_FIRST_SOAP_RESPONSE) {
Log.e(TAG, "launch Osu webview in wrong state =" + mState);
- resetStateMachine(ProvisioningCallback.OSU_FAILURE_PROVISIONING_ABORTED);
+ resetStateMachineForFailure(ProvisioningCallback.OSU_FAILURE_PROVISIONING_ABORTED);
return;
}
@@ -608,7 +710,8 @@ public class PasspointProvisioner {
}
})) {
Log.e(TAG, "fails to start redirect listener");
- resetStateMachine(ProvisioningCallback.OSU_FAILURE_START_REDIRECT_LISTENER);
+ resetStateMachineForFailure(
+ ProvisioningCallback.OSU_FAILURE_START_REDIRECT_LISTENER);
return;
}
@@ -628,7 +731,7 @@ public class PasspointProvisioner {
changeState(STATE_WAITING_FOR_REDIRECT_RESPONSE);
} else {
Log.e(TAG, "can't resolve the activity for the intent");
- resetStateMachine(ProvisioningCallback.OSU_FAILURE_NO_OSU_ACTIVITY_FOUND);
+ resetStateMachineForFailure(ProvisioningCallback.OSU_FAILURE_NO_OSU_ACTIVITY_FOUND);
return;
}
}
@@ -643,7 +746,7 @@ public class PasspointProvisioner {
if (mState != STATE_WAITING_FOR_REDIRECT_RESPONSE) {
Log.e(TAG, "Initiates the second soap message exchange in wrong state=" + mState);
- resetStateMachine(ProvisioningCallback.OSU_FAILURE_PROVISIONING_ABORTED);
+ resetStateMachineForFailure(ProvisioningCallback.OSU_FAILURE_PROVISIONING_ABORTED);
return;
}
@@ -657,7 +760,7 @@ public class PasspointProvisioner {
changeState(STATE_WAITING_FOR_SECOND_SOAP_RESPONSE);
} else {
Log.e(TAG, "HttpsConnection is not established for soap message exchange");
- resetStateMachine(ProvisioningCallback.OSU_FAILURE_SOAP_MESSAGE_EXCHANGE);
+ resetStateMachineForFailure(ProvisioningCallback.OSU_FAILURE_SOAP_MESSAGE_EXCHANGE);
return;
}
}
@@ -672,7 +775,7 @@ public class PasspointProvisioner {
if (mState != STATE_WAITING_FOR_SECOND_SOAP_RESPONSE) {
Log.e(TAG, "Initiates the third soap message exchange in wrong state=" + mState);
- resetStateMachine(ProvisioningCallback.OSU_FAILURE_PROVISIONING_ABORTED);
+ resetStateMachineForFailure(ProvisioningCallback.OSU_FAILURE_PROVISIONING_ABORTED);
return;
}
@@ -684,11 +787,15 @@ public class PasspointProvisioner {
changeState(STATE_WAITING_FOR_THIRD_SOAP_RESPONSE);
} else {
Log.e(TAG, "HttpsConnection is not established for soap message exchange");
- resetStateMachine(ProvisioningCallback.OSU_FAILURE_SOAP_MESSAGE_EXCHANGE);
+ resetStateMachineForFailure(ProvisioningCallback.OSU_FAILURE_SOAP_MESSAGE_EXCHANGE);
return;
}
}
+ /**
+ * Builds {@link PasspointConfiguration} object from PPS(PerProviderSubscription)
+ * MO(Management Object).
+ */
private PasspointConfiguration buildPasspointConfiguration(@NonNull PpsMoData moData) {
String moTree = moData.getPpsMoTree();
@@ -703,6 +810,72 @@ public class PasspointProvisioner {
return passpointConfiguration;
}
+ /**
+ * Retrieves Trust Root CA Certificates from server url defined in PPS
+ * (PerProviderSubscription) MO(Management Object).
+ */
+ private void retrieveTrustRootCerts(@NonNull PasspointConfiguration passpointConfig) {
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "Initiates retrieving trust root certs in state =" + mState);
+ }
+
+ Map<String, byte[]> trustCertInfo = passpointConfig.getTrustRootCertList();
+ if (trustCertInfo == null || trustCertInfo.isEmpty()) {
+ Log.e(TAG, "no AAATrustRoot Node found");
+ resetStateMachineForFailure(
+ ProvisioningCallback.OSU_FAILURE_NO_AAA_SERVER_TRUST_ROOT_NODE);
+ return;
+ }
+ Map<Integer, Map<String, byte[]>> allTrustCerts = new HashMap<>();
+ allTrustCerts.put(OsuServerConnection.TRUST_CERT_TYPE_AAA, trustCertInfo);
+
+ // SubscriptionUpdate is a required node.
+ if (passpointConfig.getSubscriptionUpdate() != null
+ && passpointConfig.getSubscriptionUpdate().getTrustRootCertUrl() != null) {
+ trustCertInfo = new HashMap<>();
+ trustCertInfo.put(
+ passpointConfig.getSubscriptionUpdate().getTrustRootCertUrl(),
+ passpointConfig.getSubscriptionUpdate()
+ .getTrustRootCertSha256Fingerprint());
+ allTrustCerts.put(OsuServerConnection.TRUST_CERT_TYPE_REMEDIATION, trustCertInfo);
+ } else {
+ Log.e(TAG, "no TrustRoot Node for remediation server found");
+ resetStateMachineForFailure(
+ ProvisioningCallback.OSU_FAILURE_NO_REMEDIATION_SERVER_TRUST_ROOT_NODE);
+ return;
+ }
+
+ // Policy is an optional node
+ if (passpointConfig.getPolicy() != null) {
+ if (passpointConfig.getPolicy().getPolicyUpdate() != null
+ && passpointConfig.getPolicy().getPolicyUpdate().getTrustRootCertUrl()
+ != null) {
+ trustCertInfo = new HashMap<>();
+ trustCertInfo.put(
+ passpointConfig.getPolicy().getPolicyUpdate()
+ .getTrustRootCertUrl(),
+ passpointConfig.getPolicy().getPolicyUpdate()
+ .getTrustRootCertSha256Fingerprint());
+ allTrustCerts.put(OsuServerConnection.TRUST_CERT_TYPE_POLICY, trustCertInfo);
+ } else {
+ Log.e(TAG, "no TrustRoot Node for policy server found");
+ resetStateMachineForFailure(
+ ProvisioningCallback.OSU_FAILURE_NO_POLICY_SERVER_TRUST_ROOT_NODE);
+ return;
+ }
+ }
+
+ if (mOsuServerConnection.retrieveTrustRootCerts(allTrustCerts)) {
+ invokeProvisioningCallback(PROVISIONING_STATUS,
+ ProvisioningCallback.OSU_STATUS_RETRIEVING_TRUST_ROOT_CERTS);
+ changeState(STATE_WAITING_FOR_TRUST_ROOT_CERTS);
+ } else {
+ Log.e(TAG, "HttpsConnection is not established for retrieving trust root certs");
+ resetStateMachineForFailure(ProvisioningCallback.OSU_FAILURE_SERVER_CONNECTION);
+ return;
+ }
+ }
+
private void changeState(int nextState) {
if (nextState != mState) {
if (mVerboseLoggingEnabled) {
@@ -712,13 +885,18 @@ public class PasspointProvisioner {
}
}
- private void resetStateMachine(int failureCode) {
+ private void resetStateMachineForFailure(int failureCode) {
invokeProvisioningCallback(PROVISIONING_FAILURE, failureCode);
+ resetStateMachine();
+ }
+
+ private void resetStateMachine() {
mRedirectListener.stopServer();
mOsuNetworkConnection.setEventCallback(null);
mOsuNetworkConnection.disconnectIfNeeded();
mOsuServerConnection.setEventCallback(null);
mOsuServerConnection.cleanup();
+ mPasspointConfiguration = null;
changeState(STATE_INIT);
}
}
@@ -730,8 +908,6 @@ public class PasspointProvisioner {
*/
class OsuNetworkCallbacks implements OsuNetworkConnection.Callbacks {
- OsuNetworkCallbacks() {}
-
@Override
public void onConnected(Network network) {
if (mVerboseLoggingEnabled) {
@@ -821,7 +997,7 @@ public class PasspointProvisioner {
/**
* Callback when soap message is received from server.
*
- * @param sessionId indicating current session ID
+ * @param sessionId indicating current session ID
* @param responseMessage SOAP SPP response parsed or {@code null} in any failure
* Note: Called on different thread (OsuServer Thread)!
*/
@@ -834,6 +1010,22 @@ public class PasspointProvisioner {
mProvisioningStateMachine.handleSoapMessageResponse(sessionId,
responseMessage));
}
+
+ /**
+ * Callback when trust root certificates are retrieved from server.
+ *
+ * @param sessionId indicating current session ID
+ * @param trustRootCertificates trust root CA certificates retrieved from server
+ * Note: Called on different thread (OsuServer Thread)!
+ */
+ public void onReceivedTrustRootCertificates(int sessionId,
+ @NonNull Map<Integer, List<X509Certificate>> trustRootCertificates) {
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "onReceivedTrustRootCertificates with sessionId=" + sessionId);
+ }
+ mProvisioningStateMachine.getHandler().post(() ->
+ mProvisioningStateMachine.installTrustRootCertificates(sessionId,
+ trustRootCertificates));
+ }
}
}
-
diff --git a/service/java/com/android/server/wifi/hotspot2/ASN1SubjectAltNamesParser.java b/service/java/com/android/server/wifi/hotspot2/ServiceProviderVerifier.java
index 33e865b78..e09ac178a 100644
--- a/service/java/com/android/server/wifi/hotspot2/ASN1SubjectAltNamesParser.java
+++ b/service/java/com/android/server/wifi/hotspot2/ServiceProviderVerifier.java
@@ -16,7 +16,9 @@
package com.android.server.wifi.hotspot2;
+import android.annotation.NonNull;
import android.text.TextUtils;
+import android.util.Log;
import android.util.Pair;
import com.android.internal.annotations.VisibleForTesting;
@@ -27,16 +29,21 @@ import com.android.org.bouncycastle.asn1.ASN1Sequence;
import com.android.org.bouncycastle.asn1.DERTaggedObject;
import com.android.org.bouncycastle.asn1.DERUTF8String;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
/**
- * Utility to provide parsing of SubjectAltNames extensions from X509Certificate
+ * Utility class to validate a server X.509 Certificate of a service provider.
*/
-public class ASN1SubjectAltNamesParser {
+public class ServiceProviderVerifier {
+ private static final String TAG = "ServiceProviderVerifier";
+
private static final int OTHER_NAME = 0;
private static final int ENTRY_COUNT = 2;
private static final int LANGUAGE_CODE_LENGTH = 3;
@@ -165,7 +172,48 @@ public class ASN1SubjectAltNamesParser {
}
/**
- * Extract the language code and friendly Name from the alternativeName.
+ * Verifies a SHA-256 fingerprint of a X.509 Certificate.
+ *
+ * The SHA-256 fingerprint is calculated over the X.509 ASN.1 DER encoded certificate.
+ * @param x509Cert a server X.509 Certificate to verify
+ * @param certSHA256Fingerprint a SHA-256 hash value stored in PPS(PerProviderSubscription)
+ * MO(Management Object)
+ * SubscriptionUpdate/TrustRoot/CertSHA256Fingerprint for
+ * remediation server
+ * AAAServerTrustRoot/CertSHA256Fingerprint for AAA server
+ * PolicyUpdate/TrustRoot/CertSHA256Fingerprint for Policy Server
+ *
+ * @return {@code true} if the fingerprint of {@code x509Cert} is equal to {@code
+ * certSHA256Fingerprint}, {@code false} otherwise.
+ */
+ public static boolean verifyCertFingerprint(@NonNull X509Certificate x509Cert,
+ @NonNull byte[] certSHA256Fingerprint) {
+ try {
+ byte[] fingerPrintSha256 = computeHash(x509Cert.getEncoded());
+ if (fingerPrintSha256 == null) return false;
+ if (Arrays.equals(fingerPrintSha256, certSHA256Fingerprint)) {
+ return true;
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "verifyCertFingerprint err:" + e);
+ }
+ return false;
+ }
+
+ /**
+ * Computes a hash with SHA-256 algorithm for the input.
+ */
+ private static byte[] computeHash(byte[] input) {
+ try {
+ MessageDigest digest = MessageDigest.getInstance("SHA-256");
+ return digest.digest(input);
+ } catch (NoSuchAlgorithmException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Extracts the language code and friendly Name from the alternativeName.
*/
private static Pair<Locale, String> getFriendlyName(String alternativeName) {
@@ -189,4 +237,3 @@ public class ASN1SubjectAltNamesParser {
return Pair.create(locale, friendlyName);
}
}
-
diff --git a/service/java/com/android/server/wifi/hotspot2/soap/HttpsServiceConnection.java b/service/java/com/android/server/wifi/hotspot2/soap/HttpsServiceConnection.java
index 8f22589e8..99f2f21a6 100644
--- a/service/java/com/android/server/wifi/hotspot2/soap/HttpsServiceConnection.java
+++ b/service/java/com/android/server/wifi/hotspot2/soap/HttpsServiceConnection.java
@@ -39,10 +39,14 @@ import javax.net.ssl.SSLSocketFactory;
* https connection for SOAP message.
*/
public class HttpsServiceConnection implements ServiceConnection {
+ // TODO(117906601): find an optimal value for a connection timeout
+ public static final int DEFAULT_TIMEOUT_MS = 5000; // 5 seconds
private HttpsURLConnection mConnection;
public HttpsServiceConnection(Network network, URL url) throws IOException {
mConnection = (HttpsURLConnection) network.openConnection(url);
+ mConnection.setConnectTimeout(DEFAULT_TIMEOUT_MS);
+ mConnection.setReadTimeout(DEFAULT_TIMEOUT_MS);
}
@Override
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/OsuServerConnectionTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/OsuServerConnectionTest.java
index f0a72efce..39ef15e5d 100644
--- a/tests/wifitests/src/com/android/server/wifi/hotspot2/OsuServerConnectionTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/OsuServerConnectionTest.java
@@ -16,6 +16,7 @@
package com.android.server.wifi.hotspot2;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyString;
@@ -41,24 +42,31 @@ import com.android.server.wifi.hotspot2.soap.SppResponseMessage;
import org.junit.Before;
import org.junit.Test;
+import org.ksoap2.HeaderProperty;
import org.ksoap2.SoapEnvelope;
import org.ksoap2.serialization.SoapObject;
import org.ksoap2.serialization.SoapSerializationEnvelope;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.MockitoSession;
import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
import java.net.Socket;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
import java.util.Locale;
+import java.util.Map;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
@@ -66,7 +74,7 @@ import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
/**
- * Unit tests for {@link com.android.server.wifi.hotspot2.OsuServerConnection}.
+ * Unit tests for {@link OsuServerConnection}.
*/
@SmallTest
public class OsuServerConnectionTest {
@@ -84,6 +92,8 @@ public class OsuServerConnectionTest {
private ArgumentCaptor<TrustManager[]> mTrustManagerCaptor =
ArgumentCaptor.forClass(TrustManager[].class);
+ private Map<Integer, Map<String, byte[]>> mTrustCertsInfo = new HashMap<>();
+
@Mock PasspointProvisioner.OsuServerCallbacks mOsuServerCallbacks;
@Mock Network mNetwork;
@Mock HttpsURLConnection mUrlConnection;
@@ -118,9 +128,9 @@ public class OsuServerConnectionTest {
public void verifyInitAndConnect() throws Exception {
// static mocking
MockitoSession session = ExtendedMockito.mockitoSession().mockStatic(
- ASN1SubjectAltNamesParser.class).startMocking();
+ ServiceProviderVerifier.class).startMocking();
try {
- when(ASN1SubjectAltNamesParser.getProviderNames(any(X509Certificate.class))).thenReturn(
+ when(ServiceProviderVerifier.getProviderNames(any(X509Certificate.class))).thenReturn(
mProviderIdentities);
mOsuServerConnection.init(mTlsContext, mDelegate);
@@ -224,9 +234,9 @@ public class OsuServerConnectionTest {
public void verifyInitAndConnectInvalidProviderIdentity() throws Exception {
// static mocking
MockitoSession session = ExtendedMockito.mockitoSession().mockStatic(
- ASN1SubjectAltNamesParser.class).startMocking();
+ ServiceProviderVerifier.class).startMocking();
try {
- when(ASN1SubjectAltNamesParser.getProviderNames(any(X509Certificate.class))).thenReturn(
+ when(ServiceProviderVerifier.getProviderNames(any(X509Certificate.class))).thenReturn(
mProviderIdentities);
mOsuServerConnection.init(mTlsContext, mDelegate);
@@ -263,10 +273,7 @@ public class OsuServerConnectionTest {
*/
@Test
public void verifyExchangeSoapMessageWithInvalidArgument() {
- mOsuServerConnection.init(mTlsContext, mDelegate);
- mOsuServerConnection.setEventCallback(mOsuServerCallbacks);
-
- assertTrue(mOsuServerConnection.connect(mValidServerUrl, mNetwork));
+ establishServerConnection();
assertFalse(mOsuServerConnection.exchangeSoapMessage(null));
}
@@ -307,9 +314,7 @@ public class OsuServerConnectionTest {
MockitoSession session = ExtendedMockito.mockitoSession().mockStatic(
HttpsTransport.class).mockStatic(SoapParser.class).startMocking();
try {
- mOsuServerConnection.init(mTlsContext, mDelegate);
- mOsuServerConnection.setEventCallback(mOsuServerCallbacks);
- assertTrue(mOsuServerConnection.connect(mValidServerUrl, mNetwork));
+ establishServerConnection();
SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(SoapEnvelope.VER12);
envelope.bodyIn = new SoapObject();
@@ -325,4 +330,127 @@ public class OsuServerConnectionTest {
session.finishMocking();
}
}
+
+ /**
+ * Verifies {@code retrieveTrustRootCerts} should return {@code false} if there is no
+ * connection.
+ */
+ @Test
+ public void verifyRetrieveTrustRootCertsWithoutConnection() {
+ assertFalse(mOsuServerConnection.retrieveTrustRootCerts(mTrustCertsInfo));
+ }
+
+ /**
+ * Verifies {@code retrieveTrustRootCerts} should return {@code false} if {@code
+ * mTrustCertsInfo} is empty.
+ */
+ @Test
+ public void verifyRetrieveTrustRootCertsWithEmptyOfTrustCertsInfo() {
+ mOsuServerConnection.init(mTlsContext, mDelegate);
+ mOsuServerConnection.setEventCallback(mOsuServerCallbacks);
+ assertFalse(mOsuServerConnection.retrieveTrustRootCerts(mTrustCertsInfo));
+ }
+
+ /**
+ * Verifies it should return an empty collection of CA certificates if HTTPS response from
+ * server to get root CA certificate is not HTTP OK.
+ */
+ @Test
+ public void verifyRetrieveTrustRootCertsWithErrorInHTTPSResponse() throws IOException {
+ // static mocking
+ MockitoSession session = ExtendedMockito.mockitoSession().mockStatic(
+ HttpsTransport.class).startMocking();
+ try {
+ when(HttpsTransport.createInstance(any(Network.class), any(URL.class))).thenReturn(
+ mHttpsTransport);
+ when(mHttpsServiceConnection.getResponseCode()).thenReturn(
+ HttpURLConnection.HTTP_NO_CONTENT);
+ ArgumentCaptor<Map<Integer, List<X509Certificate>>> argumentCaptor =
+ ArgumentCaptor.forClass(Map.class);
+
+ // Test Data
+ Map<String, byte[]> certInfo = new HashMap<>();
+ certInfo.put("https://test.com/trustroot", "testData".getBytes());
+ certInfo.put("https://test2.com/trustroot", "testData2".getBytes());
+ mTrustCertsInfo.put(OsuServerConnection.TRUST_CERT_TYPE_AAA, certInfo);
+
+ establishServerConnection();
+
+ assertTrue(mOsuServerConnection.retrieveTrustRootCerts(mTrustCertsInfo));
+
+ mLooper.dispatchAll();
+
+ verify(mOsuServerCallbacks).onReceivedTrustRootCertificates(anyInt(),
+ argumentCaptor.capture());
+ assertTrue(argumentCaptor.getValue().isEmpty());
+ } finally {
+ session.finishMocking();
+ }
+ }
+
+ /**
+ * Verifies it should return a collection of CA certificates if there is no error while
+ * downloading root CA certificate from each {@code URL} provided
+ */
+ @Test
+ public void verifyRetrieveTrustRootCertsWithoutError() throws IOException,
+ CertificateException {
+ // static mocking
+ MockitoSession session = ExtendedMockito.mockitoSession().mockStatic(
+ HttpsTransport.class).mockStatic(CertificateFactory.class).mockStatic(
+ ServiceProviderVerifier.class).startMocking();
+ try {
+ X509Certificate certificate = Mockito.mock(X509Certificate.class);
+ InputStream inputStream = Mockito.mock(InputStream.class);
+
+ // To avoid infinite loop in OsuServerConnection.getCert.
+ when(inputStream.read(any(byte[].class), anyInt(), anyInt())).thenReturn(-1);
+
+ CertificateFactory certificateFactory = Mockito.mock(CertificateFactory.class);
+ when(certificateFactory.generateCertificate(any(InputStream.class))).thenReturn(
+ certificate);
+ when(CertificateFactory.getInstance(anyString())).thenReturn(certificateFactory);
+ when(HttpsTransport.createInstance(any(Network.class), any(URL.class))).thenReturn(
+ mHttpsTransport);
+ when(mHttpsServiceConnection.getResponseCode()).thenReturn(
+ HttpURLConnection.HTTP_OK);
+ when(mHttpsServiceConnection.openInputStream()).thenReturn(inputStream);
+ ArgumentCaptor<Map<Integer, List<X509Certificate>>> argumentCaptor =
+ ArgumentCaptor.forClass(Map.class);
+ when(ServiceProviderVerifier.verifyCertFingerprint(any(X509Certificate.class),
+ any(byte[].class))).thenReturn(true);
+
+ // Test Data
+ Map<String, byte[]> certInfo = new HashMap<>();
+ certInfo.put("https://test.com/trustroot", "testData".getBytes());
+ mTrustCertsInfo.put(OsuServerConnection.TRUST_CERT_TYPE_AAA, certInfo);
+
+ List<HeaderProperty> properties = new ArrayList<>();
+
+ // Indicates that X.509 CA certificate is included.
+ properties.add(new HeaderProperty("Content-Type", "application/x-x509-ca-cert"));
+ when(mHttpsServiceConnection.getResponseProperties()).thenReturn(properties);
+
+ establishServerConnection();
+
+ assertTrue(mOsuServerConnection.retrieveTrustRootCerts(mTrustCertsInfo));
+
+ mLooper.dispatchAll();
+
+ verify(mOsuServerCallbacks).onReceivedTrustRootCertificates(anyInt(),
+ argumentCaptor.capture());
+ assertEquals(1, argumentCaptor.getValue().size());
+ assertEquals(certificate,
+ argumentCaptor.getValue().get(OsuServerConnection.TRUST_CERT_TYPE_AAA).get(0));
+ } finally {
+ session.finishMocking();
+ }
+ }
+
+ private void establishServerConnection() {
+ mOsuServerConnection.init(mTlsContext, mDelegate);
+ mOsuServerConnection.setEventCallback(mOsuServerCallbacks);
+
+ assertTrue(mOsuServerConnection.connect(mValidServerUrl, mNetwork));
+ }
}
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointProvisionerTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointProvisionerTest.java
index 2214d53d3..3607a0326 100644
--- a/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointProvisionerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointProvisionerTest.java
@@ -18,10 +18,14 @@ package com.android.server.wifi.hotspot2;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyMap;
+import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -39,7 +43,12 @@ import android.net.wifi.WifiManager;
import android.net.wifi.WifiSsid;
import android.net.wifi.hotspot2.IProvisioningCallback;
import android.net.wifi.hotspot2.OsuProvider;
+import android.net.wifi.hotspot2.PasspointConfiguration;
import android.net.wifi.hotspot2.ProvisioningCallback;
+import android.net.wifi.hotspot2.omadm.PpsMoParser;
+import android.net.wifi.hotspot2.pps.Credential;
+import android.net.wifi.hotspot2.pps.HomeSp;
+import android.net.wifi.hotspot2.pps.UpdateParameter;
import android.os.Build;
import android.os.Handler;
import android.os.RemoteException;
@@ -70,12 +79,17 @@ import org.mockito.MockitoSession;
import java.net.URL;
import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
import java.util.Locale;
+import java.util.Map;
import javax.net.ssl.SSLContext;
/**
- * Unit tests for {@link com.android.server.wifi.hotspot2.PasspointProvisioner}.
+ * Unit tests for {@link PasspointProvisioner}.
*/
@SmallTest
public class PasspointProvisionerTest {
@@ -86,6 +100,7 @@ public class PasspointProvisionerTest {
private static final int STEP_WAIT_FOR_REDIRECT_RESPONSE = 3;
private static final int STEP_WAIT_FOR_SECOND_SOAP_RESPONSE = 4;
private static final int STEP_WAIT_FOR_THIRD_SOAP_RESPONSE = 5;
+ private static final int STEP_WAIT_FOR_TRUST_ROOT_CERTS = 6;
private static final String TEST_DEV_ID = "12312341";
private static final String TEST_MANUFACTURER = Build.MANUFACTURER;
@@ -141,13 +156,15 @@ public class PasspointProvisionerTest {
@Mock PpsMoData mPpsMoData;
@Mock RedirectListener mRedirectListener;
@Mock PackageManager mPackageManager;
+ @Mock PasspointConfiguration mPasspointConfiguration;
+ @Mock X509Certificate mX509Certificate;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mTestUrl = new URL(TEST_REDIRECT_URL);
mSession = ExtendedMockito.mockitoSession().mockStatic(
- RedirectListener.class).startMocking();
+ RedirectListener.class).mockStatic(PpsMoParser.class).startMocking();
when(RedirectListener.createInstance(mLooper.getLooper())).thenReturn(
mRedirectListener);
@@ -212,6 +229,21 @@ public class PasspointProvisionerTest {
resolveInfo.activityInfo.applicationInfo.packageName = OSU_APP_PACKAGE;
when(mPackageManager.resolveActivity(any(Intent.class),
eq(PackageManager.MATCH_DEFAULT_ONLY))).thenReturn(resolveInfo);
+
+ Map<String, byte[]> trustCertInfo = new HashMap<>();
+ trustCertInfo.put("https://testurl.com", "testData".getBytes());
+ when(mPasspointConfiguration.getTrustRootCertList()).thenReturn(trustCertInfo);
+ when(mPasspointConfiguration.getCredential()).thenReturn(new Credential());
+ HomeSp homeSp = new HomeSp();
+ homeSp.setFqdn("test.com");
+ when(mPasspointConfiguration.getHomeSp()).thenReturn(homeSp);
+
+ UpdateParameter updateParameter = new UpdateParameter();
+ updateParameter.setTrustRootCertUrl("https://testurl.com");
+ updateParameter.setTrustRootCertSha256Fingerprint("testData".getBytes());
+ when(mPasspointConfiguration.getSubscriptionUpdate()).thenReturn(updateParameter);
+ when(mOsuServerConnection.retrieveTrustRootCerts(anyMap())).thenReturn(true);
+ lenient().when(PpsMoParser.parseMoText(isNull())).thenReturn(mPasspointConfiguration);
}
@After
@@ -309,6 +341,20 @@ public class PasspointProvisionerTest {
mOsuServerCallbacks.onReceivedSoapMessage(mOsuServerCallbacks.getSessionId(),
mExchangeCompleteMessage);
mLooper.dispatchAll();
+ } else if (step == STEP_WAIT_FOR_TRUST_ROOT_CERTS) {
+ verify(mCallback).onProvisioningStatus(
+ ProvisioningCallback.OSU_STATUS_RETRIEVING_TRUST_ROOT_CERTS);
+
+ Map<Integer, List<X509Certificate>> trustRootCertificates = new HashMap<>();
+ List<X509Certificate> certificates = new ArrayList<>();
+ certificates.add(mX509Certificate);
+ trustRootCertificates.put(OsuServerConnection.TRUST_CERT_TYPE_AAA, certificates);
+
+ // Received trust root CA certificates
+ mOsuServerCallbacks.onReceivedTrustRootCertificates(
+ mOsuServerCallbacks.getSessionId(), trustRootCertificates);
+ mLooper.dispatchAll();
+ verify(mCallback).onProvisioningComplete();
}
}
}
@@ -646,14 +692,78 @@ public class PasspointProvisionerTest {
}
/**
+ * Verifies that the right provisioning callbacks are invoked when failing to call {@link
+ * OsuServerConnection#retrieveTrustRootCerts(Map)}.
+ */
+ @Test
+ public void verifyHandlingErrorForCallingRetrieveTrustRootCerts()
+ throws RemoteException {
+ when(mOsuServerConnection.retrieveTrustRootCerts(anyMap())).thenReturn(false);
+ stopAfterStep(STEP_WAIT_FOR_THIRD_SOAP_RESPONSE);
+
+ verify(mCallback).onProvisioningFailure(
+ ProvisioningCallback.OSU_FAILURE_SERVER_CONNECTION);
+ }
+
+ /**
+ * Verifies that the right provisioning callbacks are invoked when a new {@link
+ * PasspointConfiguration} is failed to add.
+ */
+ @Test
+ public void verifyHandlingErrorForAddingPasspointConfiguration() throws RemoteException {
+ doThrow(IllegalArgumentException.class).when(
+ mWifiManager).addOrUpdatePasspointConfiguration(any(PasspointConfiguration.class));
+ stopAfterStep(STEP_WAIT_FOR_THIRD_SOAP_RESPONSE);
+ verify(mCallback).onProvisioningStatus(
+ ProvisioningCallback.OSU_STATUS_RETRIEVING_TRUST_ROOT_CERTS);
+
+ Map<Integer, List<X509Certificate>> trustRootCertificates = new HashMap<>();
+ List<X509Certificate> certificates = new ArrayList<>();
+ certificates.add(mX509Certificate);
+ trustRootCertificates.put(OsuServerConnection.TRUST_CERT_TYPE_AAA, certificates);
+
+ // Received trust root CA certificates
+ mOsuServerCallbacks.onReceivedTrustRootCertificates(
+ mOsuServerCallbacks.getSessionId(), trustRootCertificates);
+ mLooper.dispatchAll();
+
+ verify(mCallback).onProvisioningFailure(
+ ProvisioningCallback.OSU_FAILURE_ADD_PASSPOINT_CONFIGURATION);
+ }
+
+ /**
+ * Verifies that the right provisioning callbacks are invoked when it is failed to retrieve
+ * trust root certificates from the URLs provided.
+ */
+ @Test
+ public void verifyHandlingEmptyTrustRootCertificateRetrieved() throws RemoteException {
+ doThrow(IllegalArgumentException.class).when(
+ mWifiManager).addOrUpdatePasspointConfiguration(any(PasspointConfiguration.class));
+ stopAfterStep(STEP_WAIT_FOR_THIRD_SOAP_RESPONSE);
+ verify(mCallback).onProvisioningStatus(
+ ProvisioningCallback.OSU_STATUS_RETRIEVING_TRUST_ROOT_CERTS);
+
+ // Empty trust root certificates.
+ Map<Integer, List<X509Certificate>> trustRootCertificates = new HashMap<>();
+
+ // Received trust root CA certificates
+ mOsuServerCallbacks.onReceivedTrustRootCertificates(
+ mOsuServerCallbacks.getSessionId(), trustRootCertificates);
+ mLooper.dispatchAll();
+
+ verify(mCallback).onProvisioningFailure(
+ ProvisioningCallback.OSU_FAILURE_RETRIEVE_TRUST_ROOT_CERTIFICATES);
+ }
+
+ /**
* Verifies that the right provisioning callbacks are invoked as the provisioner progresses
* to the end as successful case.
*/
@Test
public void verifyProvisioningFlowForSuccessfulCase() throws RemoteException {
- stopAfterStep(STEP_WAIT_FOR_THIRD_SOAP_RESPONSE);
+ stopAfterStep(STEP_WAIT_FOR_TRUST_ROOT_CERTS);
- // No further runnables posted
+ // No further runnable posted
verifyNoMoreInteractions(mCallback);
}
}
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/ASN1SubjectAltNamesParserTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/ServiceProviderVerifierTest.java
index 59711fa67..ee0ac896f 100644
--- a/tests/wifitests/src/com/android/server/wifi/hotspot2/ASN1SubjectAltNamesParserTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/ServiceProviderVerifierTest.java
@@ -16,11 +16,12 @@
package com.android.server.wifi.hotspot2;
-import static com.android.server.wifi.hotspot2.ASN1SubjectAltNamesParser
+import static com.android.server.wifi.hotspot2.ServiceProviderVerifier
.ID_WFA_OID_HOTSPOT_FRIENDLYNAME;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.doThrow;
@@ -50,10 +51,10 @@ import java.util.List;
import java.util.Locale;
/**
- * Unit tests for {@link com.android.server.wifi.hotspot2.ASN1SubjectAltNamesParser}.
+ * Unit tests for {@link ServiceProviderVerifier}.
*/
@SmallTest
-public class ASN1SubjectAltNamesParserTest {
+public class ServiceProviderVerifierTest {
private List<List<?>> mNewNames;
private static final String LOCAL_HOST_NAME = "localhost";
private static final byte[] LOCAL_HOST_ADDRESS = {127, 0, 0, 1};
@@ -80,7 +81,7 @@ public class ASN1SubjectAltNamesParserTest {
*/
@Test
public void testNullForProviderCertShouldReturnEmptyList() {
- assertTrue(ASN1SubjectAltNamesParser.getProviderNames(null).isEmpty());
+ assertTrue(ServiceProviderVerifier.getProviderNames(null).isEmpty());
}
/**
@@ -90,7 +91,7 @@ public class ASN1SubjectAltNamesParserTest {
@Test
public void testNullFromgetSubjectAlternativeNamesShouldReturnEmptyList() throws Exception {
when(mX509Certificate.getSubjectAlternativeNames()).thenReturn(null);
- assertTrue(ASN1SubjectAltNamesParser.getProviderNames(mX509Certificate).isEmpty());
+ assertTrue(ServiceProviderVerifier.getProviderNames(mX509Certificate).isEmpty());
}
/**
@@ -101,7 +102,7 @@ public class ASN1SubjectAltNamesParserTest {
public void testEmptyListFromGetSubjectAlternativeNamesShouldReturnEmptyList()
throws Exception {
when(mX509Certificate.getSubjectAlternativeNames()).thenReturn(Collections.emptySet());
- assertTrue(ASN1SubjectAltNamesParser.getProviderNames(mX509Certificate).isEmpty());
+ assertTrue(ServiceProviderVerifier.getProviderNames(mX509Certificate).isEmpty());
}
/**
@@ -114,7 +115,7 @@ public class ASN1SubjectAltNamesParserTest {
doThrow(new CertificateParsingException()).when(
mX509Certificate).getSubjectAlternativeNames();
- assertTrue(ASN1SubjectAltNamesParser.getProviderNames(mX509Certificate).isEmpty());
+ assertTrue(ServiceProviderVerifier.getProviderNames(mX509Certificate).isEmpty());
}
/**
@@ -131,7 +132,7 @@ public class ASN1SubjectAltNamesParserTest {
when(mX509Certificate.getSubjectAlternativeNames()).thenReturn(
Collections.unmodifiableCollection(mNewNames));
- assertTrue(ASN1SubjectAltNamesParser.getProviderNames(mX509Certificate).isEmpty());
+ assertTrue(ServiceProviderVerifier.getProviderNames(mX509Certificate).isEmpty());
}
/**
@@ -149,7 +150,7 @@ public class ASN1SubjectAltNamesParserTest {
when(mX509Certificate.getSubjectAlternativeNames()).thenReturn(
Collections.unmodifiableCollection(mNewNames));
- assertTrue(ASN1SubjectAltNamesParser.getProviderNames(mX509Certificate).isEmpty());
+ assertTrue(ServiceProviderVerifier.getProviderNames(mX509Certificate).isEmpty());
}
/**
@@ -169,7 +170,7 @@ public class ASN1SubjectAltNamesParserTest {
when(mX509Certificate.getSubjectAlternativeNames()).thenReturn(
Collections.unmodifiableCollection(mNewNames));
- List<Pair<Locale, String>> result = ASN1SubjectAltNamesParser.getProviderNames(
+ List<Pair<Locale, String>> result = ServiceProviderVerifier.getProviderNames(
mX509Certificate);
assertThat(result.size(), is(1));
@@ -177,6 +178,36 @@ public class ASN1SubjectAltNamesParserTest {
}
/**
+ * Verify that verifyCertFingerPrint should return {@code true} when a fingerprint of {@link
+ * X509Certificate} is same with a value of hash provided.
+ */
+ @Test
+ public void testVerifyFingerPrintOfCertificateWithSameFingerPrintValueReturnTrue()
+ throws Exception {
+ String testData = "testData";
+ String testHash = "ba477a0ac57e10dd90bb5bf0289c5990fe839c619b26fde7c2aac62f526d4113";
+ when(mX509Certificate.getEncoded()).thenReturn(testData.getBytes());
+
+ assertTrue(ServiceProviderVerifier.verifyCertFingerprint(mX509Certificate,
+ hexToBytes(testHash)));
+ }
+
+ /**
+ * Verify that verifyCertFingerPrint should return {@code false} when a fingerprint of {@link
+ * X509Certificate} is different with a value of hash provided.
+ */
+ @Test
+ public void testVerifyFingerPrintOfCertificateWithDifferentFingerPrintValueReturnFalse()
+ throws Exception {
+ String testData = "differentData";
+ String testHash = "ba477a0ac57e10dd90bb5bf0289c5990fe839c619b26fde7c2aac62f526d4113";
+ when(mX509Certificate.getEncoded()).thenReturn(testData.getBytes());
+
+ assertFalse(ServiceProviderVerifier.verifyCertFingerprint(mX509Certificate,
+ hexToBytes(testHash)));
+ }
+
+ /**
* Helper function to create an entry complying with the format returned
* {@link X509Certificate#getSubjectAlternativeNames()}
*/
@@ -187,4 +218,31 @@ public class ASN1SubjectAltNamesParserTest {
return nameEntry;
}
+
+ /**
+ * Converts a hex string to an array of bytes. The {@code hex} should have an even length. If
+ * not, the last character will be ignored.
+ */
+ private byte[] hexToBytes(String hex) {
+ byte[] output = new byte[hex.length() / 2];
+ for (int i = 0, j = 0; i + 1 < hex.length(); i += 2, j++) {
+ output[j] = (byte) (charToByte(hex.charAt(i)) << 4 | charToByte(hex.charAt(i + 1)));
+ }
+ return output;
+ }
+
+ /**
+ * Converts a character of [0-9a-aA-F] to its hex value in a byte. If the character is not a
+ * hex number, 0 will be returned.
+ */
+ private byte charToByte(char c) {
+ if (c >= 0x30 && c <= 0x39) {
+ return (byte) (c - 0x30);
+ } else if (c >= 0x41 && c <= 0x46) {
+ return (byte) (c - 0x37);
+ } else if (c >= 0x61 && c <= 0x66) {
+ return (byte) (c - 0x57);
+ }
+ return 0;
+ }
}
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/soap/HttpsTransportTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/soap/HttpsTransportTest.java
index 7248c3823..0f189242f 100644
--- a/tests/wifitests/src/com/android/server/wifi/hotspot2/soap/HttpsTransportTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/soap/HttpsTransportTest.java
@@ -17,6 +17,8 @@
package com.android.server.wifi.hotspot2.soap;
import static org.junit.Assert.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks;
import android.net.Network;
@@ -29,6 +31,8 @@ import org.mockito.Mock;
import java.io.IOException;
import java.net.URL;
+import javax.net.ssl.HttpsURLConnection;
+
/**
* Unit tests for {@link HttpsTransport}.
*/
@@ -39,6 +43,7 @@ public class HttpsTransportTest {
private HttpsTransport mHttpsTransport;
@Mock Network mNetwork;
+ @Mock HttpsURLConnection mHttpsURLConnection;
/**
* Sets up test.
@@ -47,6 +52,7 @@ public class HttpsTransportTest {
public void setUp() throws Exception {
initMocks(this);
mUrl = new URL(TEST_URL);
+ when(mNetwork.openConnection(any(URL.class))).thenReturn(mHttpsURLConnection);
mHttpsTransport = HttpsTransport.createInstance(mNetwork, mUrl);
}