1 /* 2 * Copyright 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.wifi.hotspot2; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.net.Network; 24 import android.net.wifi.ScanResult; 25 import android.net.wifi.WifiManager; 26 import android.net.wifi.hotspot2.IProvisioningCallback; 27 import android.net.wifi.hotspot2.OsuProvider; 28 import android.net.wifi.hotspot2.PasspointConfiguration; 29 import android.net.wifi.hotspot2.ProvisioningCallback; 30 import android.net.wifi.hotspot2.omadm.PpsMoParser; 31 import android.os.Handler; 32 import android.os.HandlerThread; 33 import android.os.Looper; 34 import android.os.RemoteException; 35 import android.os.UserHandle; 36 import android.util.Log; 37 38 import com.android.internal.annotations.VisibleForTesting; 39 import com.android.server.wifi.WifiMetrics; 40 import com.android.server.wifi.WifiNative; 41 import com.android.server.wifi.hotspot2.anqp.ANQPElement; 42 import com.android.server.wifi.hotspot2.anqp.Constants; 43 import com.android.server.wifi.hotspot2.anqp.HSOsuProvidersElement; 44 import com.android.server.wifi.hotspot2.anqp.OsuProviderInfo; 45 import com.android.server.wifi.hotspot2.soap.ExchangeCompleteMessage; 46 import com.android.server.wifi.hotspot2.soap.PostDevDataMessage; 47 import com.android.server.wifi.hotspot2.soap.PostDevDataResponse; 48 import com.android.server.wifi.hotspot2.soap.RedirectListener; 49 import com.android.server.wifi.hotspot2.soap.SppConstants; 50 import com.android.server.wifi.hotspot2.soap.SppResponseMessage; 51 import com.android.server.wifi.hotspot2.soap.UpdateResponseMessage; 52 import com.android.server.wifi.hotspot2.soap.command.BrowserUri; 53 import com.android.server.wifi.hotspot2.soap.command.PpsMoData; 54 import com.android.server.wifi.hotspot2.soap.command.SppCommand; 55 56 import java.net.MalformedURLException; 57 import java.net.URL; 58 import java.security.cert.X509Certificate; 59 import java.util.HashMap; 60 import java.util.List; 61 import java.util.Locale; 62 import java.util.Map; 63 import java.util.stream.Collectors; 64 65 /** 66 * Provides methods to carry out provisioning flow 67 */ 68 public class PasspointProvisioner { 69 private static final String TAG = "PasspointProvisioner"; 70 71 // Indicates callback type for caller initiating provisioning 72 private static final int PROVISIONING_STATUS = 0; 73 private static final int PROVISIONING_FAILURE = 1; 74 75 // TLS version to be used for HTTPS connection with OSU server 76 private static final String TLS_VERSION = "TLSv1"; 77 private static final String OSU_APP_PACKAGE = "com.android.hotspot2"; 78 79 private final Context mContext; 80 private final ProvisioningStateMachine mProvisioningStateMachine; 81 private final OsuNetworkCallbacks mOsuNetworkCallbacks; 82 private final OsuNetworkConnection mOsuNetworkConnection; 83 private final OsuServerConnection mOsuServerConnection; 84 private final WfaKeyStore mWfaKeyStore; 85 private final PasspointObjectFactory mObjectFactory; 86 private final SystemInfo mSystemInfo; 87 private int mCurrentSessionId = 0; 88 private int mCallingUid; 89 private boolean mVerboseLoggingEnabled = false; 90 private WifiManager mWifiManager; 91 private PasspointManager mPasspointManager; 92 private Looper mLooper; 93 private final WifiMetrics mWifiMetrics; 94 95 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) PasspointProvisioner(Context context, WifiNative wifiNative, PasspointObjectFactory objectFactory, PasspointManager passpointManager, WifiMetrics wifiMetrics)96 public PasspointProvisioner(Context context, WifiNative wifiNative, 97 PasspointObjectFactory objectFactory, PasspointManager passpointManager, 98 WifiMetrics wifiMetrics) { 99 mContext = context; 100 mOsuNetworkConnection = objectFactory.makeOsuNetworkConnection(context); 101 mProvisioningStateMachine = new ProvisioningStateMachine(); 102 mOsuNetworkCallbacks = new OsuNetworkCallbacks(); 103 mOsuServerConnection = objectFactory.makeOsuServerConnection(); 104 mWfaKeyStore = objectFactory.makeWfaKeyStore(); 105 mSystemInfo = objectFactory.getSystemInfo(context, wifiNative); 106 mObjectFactory = objectFactory; 107 mPasspointManager = passpointManager; 108 mWifiMetrics = wifiMetrics; 109 } 110 111 /** 112 * Sets up for provisioning 113 * 114 * @param looper Looper on which the Provisioning state machine will run 115 */ init(Looper looper)116 public void init(Looper looper) { 117 mLooper = looper; 118 mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE); 119 mProvisioningStateMachine.start(new Handler(mLooper)); 120 mOsuNetworkConnection.init(mProvisioningStateMachine.getHandler()); 121 // Offload the heavy load job to another thread 122 mProvisioningStateMachine.getHandler().post(() -> { 123 mWfaKeyStore.load(); 124 mOsuServerConnection.init(mObjectFactory.getSSLContext(TLS_VERSION), 125 mObjectFactory.getTrustManagerImpl(mWfaKeyStore.get())); 126 }); 127 } 128 129 /** 130 * Enable verbose logging to help debug failures 131 * 132 * @param level integer indicating verbose logging enabled if > 0 133 */ enableVerboseLogging(int level)134 public void enableVerboseLogging(int level) { 135 mVerboseLoggingEnabled = (level > 0) ? true : false; 136 mOsuNetworkConnection.enableVerboseLogging(level); 137 mOsuServerConnection.enableVerboseLogging(level); 138 } 139 140 /** 141 * Start provisioning flow with a given provider. 142 * 143 * @param callingUid calling uid. 144 * @param provider {@link OsuProvider} to provision with. 145 * @param callback {@link IProvisioningCallback} to provide provisioning status. 146 * @return boolean value, true if provisioning was started, false otherwise. 147 * 148 * Implements HS2.0 provisioning flow with a given HS2.0 provider. 149 */ startSubscriptionProvisioning(int callingUid, OsuProvider provider, IProvisioningCallback callback)150 public boolean startSubscriptionProvisioning(int callingUid, OsuProvider provider, 151 IProvisioningCallback callback) { 152 mCallingUid = callingUid; 153 154 Log.v(TAG, "Provisioning started with " + provider.toString()); 155 156 mProvisioningStateMachine.getHandler().post(() -> { 157 mProvisioningStateMachine.startProvisioning(provider, callback); 158 }); 159 160 return true; 161 } 162 163 /** 164 * Handles the provisioning flow state transitions 165 */ 166 class ProvisioningStateMachine { 167 private static final String TAG = "PasspointProvisioningStateMachine"; 168 169 static final int STATE_INIT = 1; 170 static final int STATE_AP_CONNECTING = 2; 171 static final int STATE_OSU_SERVER_CONNECTING = 3; 172 static final int STATE_WAITING_FOR_FIRST_SOAP_RESPONSE = 4; 173 static final int STATE_WAITING_FOR_REDIRECT_RESPONSE = 5; 174 static final int STATE_WAITING_FOR_SECOND_SOAP_RESPONSE = 6; 175 static final int STATE_WAITING_FOR_THIRD_SOAP_RESPONSE = 7; 176 static final int STATE_WAITING_FOR_TRUST_ROOT_CERTS = 8; 177 178 private OsuProvider mOsuProvider; 179 private IProvisioningCallback mProvisioningCallback; 180 private int mState = STATE_INIT; 181 private Handler mHandler; 182 private URL mServerUrl; 183 private Network mNetwork; 184 private String mSessionId; 185 private String mWebUrl; 186 private PasspointConfiguration mPasspointConfiguration; 187 private RedirectListener mRedirectListener; 188 private HandlerThread mRedirectHandlerThread; 189 private Handler mRedirectStartStopHandler; 190 191 /** 192 * Initializes and starts the state machine with a handler to handle incoming events 193 */ start(Handler handler)194 public void start(Handler handler) { 195 mHandler = handler; 196 if (mRedirectHandlerThread == null) { 197 mRedirectHandlerThread = new HandlerThread("RedirectListenerHandler"); 198 mRedirectHandlerThread.start(); 199 mRedirectStartStopHandler = new Handler(mRedirectHandlerThread.getLooper()); 200 } 201 } 202 203 /** 204 * Returns the handler on which a runnable can be posted 205 * 206 * @return Handler State Machine's handler 207 */ getHandler()208 public Handler getHandler() { 209 return mHandler; 210 } 211 212 /** 213 * Start Provisioning with the Osuprovider and invoke callbacks 214 * 215 * @param provider OsuProvider to provision with 216 * @param callback IProvisioningCallback to invoke callbacks on 217 * Note: Called on main thread (WifiService thread). 218 */ startProvisioning(OsuProvider provider, IProvisioningCallback callback)219 public void startProvisioning(OsuProvider provider, IProvisioningCallback callback) { 220 if (mVerboseLoggingEnabled) { 221 Log.v(TAG, "startProvisioning received in state=" + mState); 222 } 223 224 if (mState != STATE_INIT) { 225 if (mVerboseLoggingEnabled) { 226 Log.v(TAG, "State Machine needs to be reset before starting provisioning"); 227 } 228 resetStateMachineForFailure(ProvisioningCallback.OSU_FAILURE_PROVISIONING_ABORTED); 229 } 230 mProvisioningCallback = callback; 231 mRedirectListener = RedirectListener.createInstance(mLooper); 232 233 if (mRedirectListener == null) { 234 resetStateMachineForFailure( 235 ProvisioningCallback.OSU_FAILURE_START_REDIRECT_LISTENER); 236 return; 237 } 238 239 if (!mOsuServerConnection.canValidateServer()) { 240 Log.w(TAG, "Provisioning is not possible"); 241 resetStateMachineForFailure( 242 ProvisioningCallback.OSU_FAILURE_PROVISIONING_NOT_AVAILABLE); 243 return; 244 } 245 URL serverUrl; 246 try { 247 serverUrl = new URL(provider.getServerUri().toString()); 248 } catch (MalformedURLException e) { 249 Log.e(TAG, "Invalid Server URL"); 250 resetStateMachineForFailure(ProvisioningCallback.OSU_FAILURE_SERVER_URL_INVALID); 251 return; 252 } 253 mServerUrl = serverUrl; 254 mOsuProvider = provider; 255 if (mOsuProvider.getOsuSsid() == null) { 256 // Find a best matching OsuProvider that has an OSU SSID from current scanResults 257 List<ScanResult> scanResults = mWifiManager.getScanResults(); 258 mOsuProvider = getBestMatchingOsuProvider(scanResults, mOsuProvider); 259 if (mOsuProvider == null) { 260 resetStateMachineForFailure( 261 ProvisioningCallback.OSU_FAILURE_OSU_PROVIDER_NOT_FOUND); 262 return; 263 } 264 } 265 266 // Register for network and wifi state events during provisioning flow 267 mOsuNetworkConnection.setEventCallback(mOsuNetworkCallbacks); 268 269 // Register for OSU server callbacks 270 mOsuServerConnection.setEventCallback(new OsuServerCallbacks(++mCurrentSessionId)); 271 272 if (!mOsuNetworkConnection.connect(mOsuProvider.getOsuSsid(), 273 mOsuProvider.getNetworkAccessIdentifier(), mOsuProvider.getFriendlyName())) { 274 resetStateMachineForFailure(ProvisioningCallback.OSU_FAILURE_AP_CONNECTION); 275 return; 276 } 277 invokeProvisioningCallback(PROVISIONING_STATUS, 278 ProvisioningCallback.OSU_STATUS_AP_CONNECTING); 279 changeState(STATE_AP_CONNECTING); 280 } 281 282 /** 283 * Handles Wifi Disable event 284 * 285 * Note: Called on main thread (WifiService thread). 286 */ handleWifiDisabled()287 public void handleWifiDisabled() { 288 if (mVerboseLoggingEnabled) { 289 Log.v(TAG, "Wifi Disabled in state=" + mState); 290 } 291 if (mState == STATE_INIT) { 292 Log.w(TAG, "Wifi Disable unhandled in state=" + mState); 293 return; 294 } 295 resetStateMachineForFailure(ProvisioningCallback.OSU_FAILURE_AP_CONNECTION); 296 } 297 298 /** 299 * Handles server connection status 300 * 301 * @param sessionId indicating current session ID 302 * @param succeeded boolean indicating success/failure of server connection 303 * Note: Called on main thread (WifiService thread). 304 */ handleServerConnectionStatus(int sessionId, boolean succeeded)305 public void handleServerConnectionStatus(int sessionId, boolean succeeded) { 306 if (mVerboseLoggingEnabled) { 307 Log.v(TAG, "Server Connection status received in " + mState); 308 } 309 if (sessionId != mCurrentSessionId) { 310 Log.w(TAG, "Expected server connection failure callback for currentSessionId=" 311 + mCurrentSessionId); 312 return; 313 } 314 if (mState != STATE_OSU_SERVER_CONNECTING) { 315 Log.wtf(TAG, "Server Validation Failure unhandled in mState=" + mState); 316 return; 317 } 318 if (!succeeded) { 319 resetStateMachineForFailure(ProvisioningCallback.OSU_FAILURE_SERVER_CONNECTION); 320 return; 321 } 322 invokeProvisioningCallback(PROVISIONING_STATUS, 323 ProvisioningCallback.OSU_STATUS_SERVER_CONNECTED); 324 mProvisioningStateMachine.getHandler().post(() -> initSoapExchange()); 325 } 326 327 /** 328 * Handles server validation failure 329 * 330 * @param sessionId indicating current session ID 331 * Note: Called on main thread (WifiService thread). 332 */ handleServerValidationFailure(int sessionId)333 public void handleServerValidationFailure(int sessionId) { 334 if (mVerboseLoggingEnabled) { 335 Log.v(TAG, "Server Validation failure received in " + mState); 336 } 337 if (sessionId != mCurrentSessionId) { 338 Log.w(TAG, "Expected server validation callback for currentSessionId=" 339 + mCurrentSessionId); 340 return; 341 } 342 if (mState != STATE_OSU_SERVER_CONNECTING) { 343 Log.wtf(TAG, "Server Validation Failure unhandled in mState=" + mState); 344 return; 345 } 346 resetStateMachineForFailure(ProvisioningCallback.OSU_FAILURE_SERVER_VALIDATION); 347 } 348 349 /** 350 * Handles status of server validation success 351 * 352 * @param sessionId indicating current session ID 353 * Note: Called on main thread (WifiService thread). 354 */ handleServerValidationSuccess(int sessionId)355 public void handleServerValidationSuccess(int sessionId) { 356 if (mVerboseLoggingEnabled) { 357 Log.v(TAG, "Server Validation Success received in " + mState); 358 } 359 if (sessionId != mCurrentSessionId) { 360 Log.w(TAG, "Expected server validation callback for currentSessionId=" 361 + mCurrentSessionId); 362 return; 363 } 364 if (mState != STATE_OSU_SERVER_CONNECTING) { 365 Log.wtf(TAG, "Server validation success event unhandled in state=" + mState); 366 return; 367 } 368 if (!mOsuServerConnection.validateProvider( 369 mOsuProvider.getFriendlyNameList())) { 370 Log.e(TAG, 371 "OSU Server certificate does not have the one matched with the selected " 372 + "Service Name: " 373 + mOsuProvider.getFriendlyName()); 374 resetStateMachineForFailure( 375 ProvisioningCallback.OSU_FAILURE_SERVICE_PROVIDER_VERIFICATION); 376 return; 377 } 378 invokeProvisioningCallback(PROVISIONING_STATUS, 379 ProvisioningCallback.OSU_STATUS_SERVER_VALIDATED); 380 } 381 382 /** 383 * Handles next step once receiving a HTTP redirect response. 384 * 385 * Note: Called on main thread (WifiService thread). 386 */ handleRedirectResponse()387 public void handleRedirectResponse() { 388 if (mState != STATE_WAITING_FOR_REDIRECT_RESPONSE) { 389 Log.e(TAG, "Received redirect request in wrong state=" + mState); 390 resetStateMachineForFailure(ProvisioningCallback.OSU_FAILURE_PROVISIONING_ABORTED); 391 return; 392 } 393 394 invokeProvisioningCallback(PROVISIONING_STATUS, 395 ProvisioningCallback.OSU_STATUS_REDIRECT_RESPONSE_RECEIVED); 396 mRedirectListener.stopServer(mRedirectStartStopHandler); 397 secondSoapExchange(); 398 } 399 400 /** 401 * Handles next step when timeout occurs because {@link RedirectListener} doesn't 402 * receive a HTTP redirect response. 403 * 404 * Note: Called on main thread (WifiService thread). 405 */ handleTimeOutForRedirectResponse()406 public void handleTimeOutForRedirectResponse() { 407 Log.e(TAG, "Timed out for HTTP redirect response"); 408 409 if (mState != STATE_WAITING_FOR_REDIRECT_RESPONSE) { 410 Log.e(TAG, "Received timeout error for HTTP redirect response in wrong state=" 411 + mState); 412 resetStateMachineForFailure(ProvisioningCallback.OSU_FAILURE_PROVISIONING_ABORTED); 413 return; 414 } 415 mRedirectListener.stopServer(mRedirectStartStopHandler); 416 resetStateMachineForFailure( 417 ProvisioningCallback.OSU_FAILURE_TIMED_OUT_REDIRECT_LISTENER); 418 } 419 420 /** 421 * Connected event received 422 * 423 * @param network Network object for this connection 424 * Note: Called on main thread (WifiService thread). 425 */ handleConnectedEvent(Network network)426 public void handleConnectedEvent(Network network) { 427 if (mVerboseLoggingEnabled) { 428 Log.v(TAG, "Connected event received in state=" + mState); 429 } 430 if (mState != STATE_AP_CONNECTING) { 431 // Not waiting for a connection 432 Log.wtf(TAG, "Connection event unhandled in state=" + mState); 433 return; 434 } 435 invokeProvisioningCallback(PROVISIONING_STATUS, 436 ProvisioningCallback.OSU_STATUS_AP_CONNECTED); 437 initiateServerConnection(network); 438 } 439 440 /** 441 * Handles SOAP message response sent by server 442 * 443 * @param sessionId indicating current session ID 444 * @param responseMessage SOAP SPP response, or {@code null} in any failure. 445 * Note: Called on main thread (WifiService thread). 446 */ handleSoapMessageResponse(int sessionId, @Nullable SppResponseMessage responseMessage)447 public void handleSoapMessageResponse(int sessionId, 448 @Nullable SppResponseMessage responseMessage) { 449 if (sessionId != mCurrentSessionId) { 450 Log.w(TAG, "Expected soapMessageResponse callback for currentSessionId=" 451 + mCurrentSessionId); 452 return; 453 } 454 455 if (responseMessage == null) { 456 Log.e(TAG, "failed to send the sppPostDevData message"); 457 resetStateMachineForFailure(ProvisioningCallback.OSU_FAILURE_SOAP_MESSAGE_EXCHANGE); 458 return; 459 } 460 461 if (mState == STATE_WAITING_FOR_FIRST_SOAP_RESPONSE) { 462 if (responseMessage.getMessageType() 463 != SppResponseMessage.MessageType.POST_DEV_DATA_RESPONSE) { 464 Log.e(TAG, "Expected a PostDevDataResponse, but got " 465 + responseMessage.getMessageType()); 466 resetStateMachineForFailure( 467 ProvisioningCallback.OSU_FAILURE_UNEXPECTED_SOAP_MESSAGE_TYPE); 468 return; 469 } 470 471 PostDevDataResponse devDataResponse = (PostDevDataResponse) responseMessage; 472 mSessionId = devDataResponse.getSessionID(); 473 if (devDataResponse.getSppCommand().getExecCommandId() 474 != SppCommand.ExecCommandId.BROWSER) { 475 Log.e(TAG, "Expected a launchBrowser command, but got " 476 + devDataResponse.getSppCommand().getExecCommandId()); 477 resetStateMachineForFailure( 478 ProvisioningCallback.OSU_FAILURE_UNEXPECTED_COMMAND_TYPE); 479 return; 480 } 481 482 Log.d(TAG, "Exec: " + devDataResponse.getSppCommand().getExecCommandId() + ", for '" 483 + devDataResponse.getSppCommand().getCommandData() + "'"); 484 485 mWebUrl = ((BrowserUri) devDataResponse.getSppCommand().getCommandData()).getUri(); 486 if (mWebUrl == null) { 487 Log.e(TAG, "No Web-Url"); 488 resetStateMachineForFailure( 489 ProvisioningCallback.OSU_FAILURE_INVALID_URL_FORMAT_FOR_OSU); 490 return; 491 } 492 493 if (!mWebUrl.toLowerCase(Locale.US).contains(mSessionId.toLowerCase(Locale.US))) { 494 Log.e(TAG, "Bad or Missing session ID in webUrl"); 495 resetStateMachineForFailure( 496 ProvisioningCallback.OSU_FAILURE_INVALID_URL_FORMAT_FOR_OSU); 497 return; 498 } 499 launchOsuWebView(); 500 } else if (mState == STATE_WAITING_FOR_SECOND_SOAP_RESPONSE) { 501 if (responseMessage.getMessageType() 502 != SppResponseMessage.MessageType.POST_DEV_DATA_RESPONSE) { 503 Log.e(TAG, "Expected a PostDevDataResponse, but got " 504 + responseMessage.getMessageType()); 505 resetStateMachineForFailure( 506 ProvisioningCallback.OSU_FAILURE_UNEXPECTED_SOAP_MESSAGE_TYPE); 507 return; 508 } 509 510 PostDevDataResponse devDataResponse = (PostDevDataResponse) responseMessage; 511 if (devDataResponse.getSppCommand() == null 512 || devDataResponse.getSppCommand().getSppCommandId() 513 != SppCommand.CommandId.ADD_MO) { 514 Log.e(TAG, "Expected a ADD_MO command, but got " + ( 515 (devDataResponse.getSppCommand() == null) ? "null" 516 : devDataResponse.getSppCommand().getSppCommandId())); 517 resetStateMachineForFailure( 518 ProvisioningCallback.OSU_FAILURE_UNEXPECTED_COMMAND_TYPE); 519 return; 520 } 521 522 mPasspointConfiguration = buildPasspointConfiguration( 523 (PpsMoData) devDataResponse.getSppCommand().getCommandData()); 524 thirdSoapExchange(mPasspointConfiguration == null); 525 } else if (mState == STATE_WAITING_FOR_THIRD_SOAP_RESPONSE) { 526 if (responseMessage.getMessageType() 527 != SppResponseMessage.MessageType.EXCHANGE_COMPLETE) { 528 Log.e(TAG, "Expected a ExchangeCompleteMessage, but got " 529 + responseMessage.getMessageType()); 530 resetStateMachineForFailure( 531 ProvisioningCallback.OSU_FAILURE_UNEXPECTED_SOAP_MESSAGE_TYPE); 532 return; 533 } 534 535 ExchangeCompleteMessage exchangeCompleteMessage = 536 (ExchangeCompleteMessage) responseMessage; 537 if (exchangeCompleteMessage.getStatus() 538 != SppConstants.SppStatus.EXCHANGE_COMPLETE) { 539 Log.e(TAG, "Expected a ExchangeCompleteMessage Status, but got " 540 + exchangeCompleteMessage.getStatus()); 541 resetStateMachineForFailure( 542 ProvisioningCallback.OSU_FAILURE_UNEXPECTED_SOAP_MESSAGE_STATUS); 543 return; 544 } 545 546 if (exchangeCompleteMessage.getError() != SppConstants.INVALID_SPP_CONSTANT) { 547 Log.e(TAG, 548 "In the SppExchangeComplete, got error " 549 + exchangeCompleteMessage.getError()); 550 resetStateMachineForFailure( 551 ProvisioningCallback.OSU_FAILURE_PROVISIONING_ABORTED); 552 return; 553 } 554 if (mPasspointConfiguration == null) { 555 Log.e(TAG, "No PPS MO to use for retrieving TrustCerts"); 556 resetStateMachineForFailure(ProvisioningCallback.OSU_FAILURE_NO_PPS_MO); 557 return; 558 } 559 retrieveTrustRootCerts(mPasspointConfiguration); 560 } else { 561 if (mVerboseLoggingEnabled) { 562 Log.v(TAG, "Received an unexpected SOAP message in state=" + mState); 563 } 564 } 565 } 566 567 /** 568 * Installs the trust root CA certificates for AAA, Remediation and Policy Server 569 * 570 * @param sessionId indicating current session ID 571 * @param trustRootCertificates trust root CA certificates to be installed. 572 */ installTrustRootCertificates(int sessionId, @NonNull Map<Integer, List<X509Certificate>> trustRootCertificates)573 public void installTrustRootCertificates(int sessionId, 574 @NonNull Map<Integer, List<X509Certificate>> trustRootCertificates) { 575 if (sessionId != mCurrentSessionId) { 576 Log.w(TAG, "Expected TrustRootCertificates callback for currentSessionId=" 577 + mCurrentSessionId); 578 return; 579 } 580 if (mState != STATE_WAITING_FOR_TRUST_ROOT_CERTS) { 581 if (mVerboseLoggingEnabled) { 582 Log.v(TAG, "Received an unexpected TrustRootCertificates in state=" + mState); 583 } 584 return; 585 } 586 587 if (trustRootCertificates.isEmpty()) { 588 Log.e(TAG, "fails to retrieve trust root certificates"); 589 resetStateMachineForFailure( 590 ProvisioningCallback.OSU_FAILURE_RETRIEVE_TRUST_ROOT_CERTIFICATES); 591 return; 592 } 593 594 List<X509Certificate> certificates = trustRootCertificates.get( 595 OsuServerConnection.TRUST_CERT_TYPE_AAA); 596 if (certificates == null || certificates.isEmpty()) { 597 Log.e(TAG, "fails to retrieve trust root certificate for AAA server"); 598 resetStateMachineForFailure( 599 ProvisioningCallback.OSU_FAILURE_NO_AAA_TRUST_ROOT_CERTIFICATE); 600 return; 601 } 602 603 // Save the service friendly names from OsuProvider to keep this in the profile. 604 mPasspointConfiguration.setServiceFriendlyNames(mOsuProvider.getFriendlyNameList()); 605 606 mPasspointConfiguration.getCredential().setCaCertificates( 607 certificates.toArray(new X509Certificate[0])); 608 609 certificates = trustRootCertificates.get( 610 OsuServerConnection.TRUST_CERT_TYPE_REMEDIATION); 611 if (certificates == null || certificates.isEmpty()) { 612 Log.e(TAG, "fails to retrieve trust root certificate for Remediation"); 613 resetStateMachineForFailure( 614 ProvisioningCallback.OSU_FAILURE_RETRIEVE_TRUST_ROOT_CERTIFICATES); 615 return; 616 } 617 618 if (mPasspointConfiguration.getSubscriptionUpdate() != null) { 619 mPasspointConfiguration.getSubscriptionUpdate().setCaCertificate( 620 certificates.get(0)); 621 } 622 623 try { 624 mWifiManager.addOrUpdatePasspointConfiguration(mPasspointConfiguration); 625 } catch (IllegalArgumentException e) { 626 Log.e(TAG, "fails to add a new PasspointConfiguration: " + e); 627 resetStateMachineForFailure( 628 ProvisioningCallback.OSU_FAILURE_ADD_PASSPOINT_CONFIGURATION); 629 return; 630 } 631 632 invokeProvisioningCompleteCallback(); 633 if (mVerboseLoggingEnabled) { 634 Log.i(TAG, "Provisioning is complete for " 635 + mPasspointConfiguration.getHomeSp().getFqdn()); 636 } 637 resetStateMachine(); 638 } 639 640 /** 641 * Disconnect event received 642 * 643 * Note: Called on main thread (WifiService thread). 644 */ handleDisconnect()645 public void handleDisconnect() { 646 if (mVerboseLoggingEnabled) { 647 Log.v(TAG, "Connection failed in state=" + mState); 648 } 649 if (mState == STATE_INIT) { 650 Log.w(TAG, "Disconnect event unhandled in state=" + mState); 651 return; 652 } 653 mNetwork = null; 654 resetStateMachineForFailure(ProvisioningCallback.OSU_FAILURE_AP_CONNECTION); 655 } 656 657 /** 658 * Establishes TLS session to the server(OSU Server, Remediation or Policy Server). 659 * 660 * @param network current {@link Network} associated with the target AP. 661 */ initiateServerConnection(Network network)662 private void initiateServerConnection(Network network) { 663 if (mVerboseLoggingEnabled) { 664 Log.v(TAG, "Initiating server connection in state=" + mState); 665 } 666 667 if (!mOsuServerConnection.connect(mServerUrl, network)) { 668 resetStateMachineForFailure(ProvisioningCallback.OSU_FAILURE_SERVER_CONNECTION); 669 return; 670 } 671 mNetwork = network; 672 changeState(STATE_OSU_SERVER_CONNECTING); 673 invokeProvisioningCallback(PROVISIONING_STATUS, 674 ProvisioningCallback.OSU_STATUS_SERVER_CONNECTING); 675 } 676 invokeProvisioningCallback(int callbackType, int status)677 private void invokeProvisioningCallback(int callbackType, int status) { 678 if (mProvisioningCallback == null) { 679 Log.e(TAG, "Provisioning callback " + callbackType + " with status " + status 680 + " not invoked"); 681 return; 682 } 683 try { 684 if (callbackType == PROVISIONING_STATUS) { 685 mProvisioningCallback.onProvisioningStatus(status); 686 } else { 687 mProvisioningCallback.onProvisioningFailure(status); 688 } 689 } catch (RemoteException e) { 690 Log.e(TAG, "Remote Exception while posting callback type=" + callbackType 691 + " status=" + status); 692 } 693 } 694 invokeProvisioningCompleteCallback()695 private void invokeProvisioningCompleteCallback() { 696 mWifiMetrics.incrementPasspointProvisionSuccess(); 697 if (mProvisioningCallback == null) { 698 Log.e(TAG, "No provisioning complete callback registered"); 699 return; 700 } 701 try { 702 mProvisioningCallback.onProvisioningComplete(); 703 } catch (RemoteException e) { 704 Log.e(TAG, "Remote Exception while posting provisioning complete"); 705 } 706 } 707 708 /** 709 * Initiates the SOAP message exchange with sending the sppPostDevData message. 710 */ initSoapExchange()711 private void initSoapExchange() { 712 if (mVerboseLoggingEnabled) { 713 Log.v(TAG, "Initiates soap message exchange in state =" + mState); 714 } 715 716 if (mState != STATE_OSU_SERVER_CONNECTING) { 717 Log.e(TAG, "Initiates soap message exchange in wrong state=" + mState); 718 resetStateMachineForFailure(ProvisioningCallback.OSU_FAILURE_PROVISIONING_ABORTED); 719 return; 720 } 721 722 // Redirect uri used for signal of completion for registration process. 723 final URL redirectUri = mRedirectListener.getServerUrl(); 724 725 // Sending the first sppPostDevDataRequest message. 726 if (mOsuServerConnection.exchangeSoapMessage( 727 PostDevDataMessage.serializeToSoapEnvelope(mContext, mSystemInfo, 728 redirectUri.toString(), 729 SppConstants.SppReason.SUBSCRIPTION_REGISTRATION, null))) { 730 invokeProvisioningCallback(PROVISIONING_STATUS, 731 ProvisioningCallback.OSU_STATUS_INIT_SOAP_EXCHANGE); 732 // Move to initiate soap exchange 733 changeState(STATE_WAITING_FOR_FIRST_SOAP_RESPONSE); 734 } else { 735 Log.e(TAG, "HttpsConnection is not established for soap message exchange"); 736 resetStateMachineForFailure(ProvisioningCallback.OSU_FAILURE_SOAP_MESSAGE_EXCHANGE); 737 return; 738 } 739 } 740 741 /** 742 * Launches OsuLogin Application for users to register a new subscription. 743 */ launchOsuWebView()744 private void launchOsuWebView() { 745 if (mVerboseLoggingEnabled) { 746 Log.v(TAG, "launch Osu webview in state =" + mState); 747 } 748 749 if (mState != STATE_WAITING_FOR_FIRST_SOAP_RESPONSE) { 750 Log.e(TAG, "launch Osu webview in wrong state =" + mState); 751 resetStateMachineForFailure(ProvisioningCallback.OSU_FAILURE_PROVISIONING_ABORTED); 752 return; 753 } 754 755 // Start the redirect server to listen the HTTP redirect response from server 756 // as completion of user input. 757 if (!mRedirectListener.startServer(new RedirectListener.RedirectCallback() { 758 /** Called on different thread (RedirectListener thread). */ 759 @Override 760 public void onRedirectReceived() { 761 if (mVerboseLoggingEnabled) { 762 Log.v(TAG, "Received HTTP redirect response"); 763 } 764 mProvisioningStateMachine.getHandler().post(() -> handleRedirectResponse()); 765 } 766 767 /** Called on main thread (WifiService thread). */ 768 @Override 769 public void onRedirectTimedOut() { 770 if (mVerboseLoggingEnabled) { 771 Log.v(TAG, "Timed out to receive a HTTP redirect response"); 772 } 773 mProvisioningStateMachine.handleTimeOutForRedirectResponse(); 774 } 775 }, mRedirectStartStopHandler)) { 776 Log.e(TAG, "fails to start redirect listener"); 777 resetStateMachineForFailure( 778 ProvisioningCallback.OSU_FAILURE_START_REDIRECT_LISTENER); 779 return; 780 } 781 782 Intent intent = new Intent(WifiManager.ACTION_PASSPOINT_LAUNCH_OSU_VIEW); 783 intent.setPackage(OSU_APP_PACKAGE); 784 intent.putExtra(WifiManager.EXTRA_OSU_NETWORK, mNetwork); 785 intent.putExtra(WifiManager.EXTRA_URL, mWebUrl); 786 787 intent.setFlags( 788 Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK); 789 790 // Verify that the intent will resolve to an activity 791 if (intent.resolveActivity(mContext.getPackageManager()) != null) { 792 mContext.startActivityAsUser(intent, UserHandle.CURRENT); 793 invokeProvisioningCallback(PROVISIONING_STATUS, 794 ProvisioningCallback.OSU_STATUS_WAITING_FOR_REDIRECT_RESPONSE); 795 changeState(STATE_WAITING_FOR_REDIRECT_RESPONSE); 796 } else { 797 Log.e(TAG, "can't resolve the activity for the intent"); 798 resetStateMachineForFailure(ProvisioningCallback.OSU_FAILURE_NO_OSU_ACTIVITY_FOUND); 799 return; 800 } 801 } 802 803 /** 804 * Initiates the second SOAP message exchange with sending the sppPostDevData message. 805 */ secondSoapExchange()806 private void secondSoapExchange() { 807 if (mVerboseLoggingEnabled) { 808 Log.v(TAG, "Initiates the second soap message exchange in state =" + mState); 809 } 810 811 if (mState != STATE_WAITING_FOR_REDIRECT_RESPONSE) { 812 Log.e(TAG, "Initiates the second soap message exchange in wrong state=" + mState); 813 resetStateMachineForFailure(ProvisioningCallback.OSU_FAILURE_PROVISIONING_ABORTED); 814 return; 815 } 816 817 // Sending the second sppPostDevDataRequest message. 818 if (mOsuServerConnection.exchangeSoapMessage( 819 PostDevDataMessage.serializeToSoapEnvelope(mContext, mSystemInfo, 820 mRedirectListener.getServerUrl().toString(), 821 SppConstants.SppReason.USER_INPUT_COMPLETED, mSessionId))) { 822 invokeProvisioningCallback(PROVISIONING_STATUS, 823 ProvisioningCallback.OSU_STATUS_SECOND_SOAP_EXCHANGE); 824 changeState(STATE_WAITING_FOR_SECOND_SOAP_RESPONSE); 825 } else { 826 Log.e(TAG, "HttpsConnection is not established for soap message exchange"); 827 resetStateMachineForFailure(ProvisioningCallback.OSU_FAILURE_SOAP_MESSAGE_EXCHANGE); 828 return; 829 } 830 } 831 832 /** 833 * Initiates the third SOAP message exchange with sending the sppUpdateResponse message. 834 */ thirdSoapExchange(boolean isError)835 private void thirdSoapExchange(boolean isError) { 836 if (mVerboseLoggingEnabled) { 837 Log.v(TAG, "Initiates the third soap message exchange in state =" + mState); 838 } 839 840 if (mState != STATE_WAITING_FOR_SECOND_SOAP_RESPONSE) { 841 Log.e(TAG, "Initiates the third soap message exchange in wrong state=" + mState); 842 resetStateMachineForFailure(ProvisioningCallback.OSU_FAILURE_PROVISIONING_ABORTED); 843 return; 844 } 845 846 // Sending the sppUpdateResponse message. 847 if (mOsuServerConnection.exchangeSoapMessage( 848 UpdateResponseMessage.serializeToSoapEnvelope(mSessionId, isError))) { 849 invokeProvisioningCallback(PROVISIONING_STATUS, 850 ProvisioningCallback.OSU_STATUS_THIRD_SOAP_EXCHANGE); 851 changeState(STATE_WAITING_FOR_THIRD_SOAP_RESPONSE); 852 } else { 853 Log.e(TAG, "HttpsConnection is not established for soap message exchange"); 854 resetStateMachineForFailure(ProvisioningCallback.OSU_FAILURE_SOAP_MESSAGE_EXCHANGE); 855 return; 856 } 857 } 858 859 /** 860 * Builds {@link PasspointConfiguration} object from PPS(PerProviderSubscription) 861 * MO(Management Object). 862 */ buildPasspointConfiguration(@onNull PpsMoData moData)863 private PasspointConfiguration buildPasspointConfiguration(@NonNull PpsMoData moData) { 864 String moTree = moData.getPpsMoTree(); 865 866 PasspointConfiguration passpointConfiguration = PpsMoParser.parseMoText(moTree); 867 if (passpointConfiguration == null) { 868 Log.e(TAG, "fails to parse the MoTree"); 869 return null; 870 } 871 872 if (!passpointConfiguration.validateForR2()) { 873 Log.e(TAG, "PPS MO received is invalid: " + passpointConfiguration); 874 return null; 875 } 876 877 if (mVerboseLoggingEnabled) { 878 Log.d(TAG, "The parsed PasspointConfiguration: " + passpointConfiguration); 879 } 880 881 return passpointConfiguration; 882 } 883 884 /** 885 * Retrieves Trust Root CA Certificates from server url defined in PPS 886 * (PerProviderSubscription) MO(Management Object). 887 */ retrieveTrustRootCerts(@onNull PasspointConfiguration passpointConfig)888 private void retrieveTrustRootCerts(@NonNull PasspointConfiguration passpointConfig) { 889 if (mVerboseLoggingEnabled) { 890 Log.v(TAG, "Initiates retrieving trust root certs in state =" + mState); 891 } 892 893 Map<String, byte[]> trustCertInfo = passpointConfig.getTrustRootCertList(); 894 if (trustCertInfo == null || trustCertInfo.isEmpty()) { 895 Log.e(TAG, "no AAATrustRoot Node found"); 896 resetStateMachineForFailure( 897 ProvisioningCallback.OSU_FAILURE_NO_AAA_SERVER_TRUST_ROOT_NODE); 898 return; 899 } 900 Map<Integer, Map<String, byte[]>> allTrustCerts = new HashMap<>(); 901 allTrustCerts.put(OsuServerConnection.TRUST_CERT_TYPE_AAA, trustCertInfo); 902 903 // SubscriptionUpdate is a required node. 904 if (passpointConfig.getSubscriptionUpdate() != null 905 && passpointConfig.getSubscriptionUpdate().getTrustRootCertUrl() != null) { 906 trustCertInfo = new HashMap<>(); 907 trustCertInfo.put( 908 passpointConfig.getSubscriptionUpdate().getTrustRootCertUrl(), 909 passpointConfig.getSubscriptionUpdate() 910 .getTrustRootCertSha256Fingerprint()); 911 allTrustCerts.put(OsuServerConnection.TRUST_CERT_TYPE_REMEDIATION, trustCertInfo); 912 } else { 913 Log.e(TAG, "no TrustRoot Node for remediation server found"); 914 resetStateMachineForFailure( 915 ProvisioningCallback.OSU_FAILURE_NO_REMEDIATION_SERVER_TRUST_ROOT_NODE); 916 return; 917 } 918 919 // Policy is an optional node 920 if (passpointConfig.getPolicy() != null) { 921 if (passpointConfig.getPolicy().getPolicyUpdate() != null 922 && passpointConfig.getPolicy().getPolicyUpdate().getTrustRootCertUrl() 923 != null) { 924 trustCertInfo = new HashMap<>(); 925 trustCertInfo.put( 926 passpointConfig.getPolicy().getPolicyUpdate() 927 .getTrustRootCertUrl(), 928 passpointConfig.getPolicy().getPolicyUpdate() 929 .getTrustRootCertSha256Fingerprint()); 930 allTrustCerts.put(OsuServerConnection.TRUST_CERT_TYPE_POLICY, trustCertInfo); 931 } else { 932 Log.e(TAG, "no TrustRoot Node for policy server found"); 933 resetStateMachineForFailure( 934 ProvisioningCallback.OSU_FAILURE_NO_POLICY_SERVER_TRUST_ROOT_NODE); 935 return; 936 } 937 } 938 939 if (mOsuServerConnection.retrieveTrustRootCerts(allTrustCerts)) { 940 invokeProvisioningCallback(PROVISIONING_STATUS, 941 ProvisioningCallback.OSU_STATUS_RETRIEVING_TRUST_ROOT_CERTS); 942 changeState(STATE_WAITING_FOR_TRUST_ROOT_CERTS); 943 } else { 944 Log.e(TAG, "HttpsConnection is not established for retrieving trust root certs"); 945 resetStateMachineForFailure(ProvisioningCallback.OSU_FAILURE_SERVER_CONNECTION); 946 return; 947 } 948 } 949 changeState(int nextState)950 private void changeState(int nextState) { 951 if (nextState != mState) { 952 if (mVerboseLoggingEnabled) { 953 Log.v(TAG, "Changing state from " + mState + " -> " + nextState); 954 } 955 mState = nextState; 956 } 957 } 958 resetStateMachineForFailure(int failureCode)959 private void resetStateMachineForFailure(int failureCode) { 960 mWifiMetrics.incrementPasspointProvisionFailure(failureCode); 961 invokeProvisioningCallback(PROVISIONING_FAILURE, failureCode); 962 resetStateMachine(); 963 } 964 resetStateMachine()965 private void resetStateMachine() { 966 if (mRedirectListener != null) { 967 mRedirectListener.stopServer(mRedirectStartStopHandler); 968 } 969 mOsuNetworkConnection.setEventCallback(null); 970 mOsuNetworkConnection.disconnectIfNeeded(); 971 mOsuServerConnection.setEventCallback(null); 972 mOsuServerConnection.cleanup(); 973 mPasspointConfiguration = null; 974 mProvisioningCallback = null; 975 changeState(STATE_INIT); 976 } 977 978 /** 979 * Get a best matching osuProvider from scanResults with provided osuProvider 980 * 981 * @param scanResults a list of {@link ScanResult} to find a best osuProvider 982 * @param osuProvider an instance of {@link OsuProvider} used to match with scanResults 983 * @return a best matching {@link OsuProvider}, {@code null} when an invalid scanResults are 984 * provided or no match is found. 985 */ getBestMatchingOsuProvider( List<ScanResult> scanResults, OsuProvider osuProvider)986 private OsuProvider getBestMatchingOsuProvider( 987 List<ScanResult> scanResults, 988 OsuProvider osuProvider) { 989 if (scanResults == null) { 990 Log.e(TAG, "Attempt to retrieve OSU providers for a null ScanResult"); 991 return null; 992 } 993 994 if (osuProvider == null) { 995 Log.e(TAG, "Attempt to retrieve best OSU provider for a null osuProvider"); 996 return null; 997 } 998 999 // Clear the OSU SSID to compare it with other OsuProviders only about service 1000 // provider information. 1001 osuProvider.setOsuSsid(null); 1002 1003 // Filter non-Passpoint AP out and sort it by descending order of signal strength. 1004 scanResults = scanResults.stream() 1005 .filter((scanResult) -> scanResult.isPasspointNetwork()) 1006 .sorted((sr1, sr2) -> sr2.level - sr1.level) 1007 .collect(Collectors.toList()); 1008 1009 for (ScanResult scanResult : scanResults) { 1010 // Lookup OSU Providers ANQP element by ANQPNetworkKey. 1011 // It might have same ANQP element with another one which has same ANQP domain id. 1012 Map<Constants.ANQPElementType, ANQPElement> anqpElements = 1013 mPasspointManager.getANQPElements( 1014 scanResult); 1015 HSOsuProvidersElement element = 1016 (HSOsuProvidersElement) anqpElements.get( 1017 Constants.ANQPElementType.HSOSUProviders); 1018 if (element == null) continue; 1019 for (OsuProviderInfo info : element.getProviders()) { 1020 OsuProvider candidate = new OsuProvider(null, info.getFriendlyNames(), 1021 info.getServiceDescription(), info.getServerUri(), 1022 info.getNetworkAccessIdentifier(), info.getMethodList(), null); 1023 if (candidate.equals(osuProvider)) { 1024 // Found a matching candidate and then set OSU SSID for the OSU provider. 1025 candidate.setOsuSsid(element.getOsuSsid()); 1026 return candidate; 1027 } 1028 } 1029 } 1030 return null; 1031 } 1032 } 1033 1034 /** 1035 * Callbacks for network and wifi events 1036 * 1037 * Note: Called on main thread (WifiService thread). 1038 */ 1039 class OsuNetworkCallbacks implements OsuNetworkConnection.Callbacks { 1040 1041 @Override onConnected(Network network)1042 public void onConnected(Network network) { 1043 if (mVerboseLoggingEnabled) { 1044 Log.v(TAG, "onConnected to " + network); 1045 } 1046 if (network == null) { 1047 mProvisioningStateMachine.handleDisconnect(); 1048 } else { 1049 mProvisioningStateMachine.handleConnectedEvent(network); 1050 } 1051 } 1052 1053 @Override onDisconnected()1054 public void onDisconnected() { 1055 if (mVerboseLoggingEnabled) { 1056 Log.v(TAG, "onDisconnected"); 1057 } 1058 mProvisioningStateMachine.handleDisconnect(); 1059 } 1060 1061 @Override onTimeOut()1062 public void onTimeOut() { 1063 if (mVerboseLoggingEnabled) { 1064 Log.v(TAG, "Timed out waiting for connection to OSU AP"); 1065 } 1066 mProvisioningStateMachine.handleDisconnect(); 1067 } 1068 1069 @Override onWifiEnabled()1070 public void onWifiEnabled() { 1071 if (mVerboseLoggingEnabled) { 1072 Log.v(TAG, "onWifiEnabled"); 1073 } 1074 } 1075 1076 @Override onWifiDisabled()1077 public void onWifiDisabled() { 1078 if (mVerboseLoggingEnabled) { 1079 Log.v(TAG, "onWifiDisabled"); 1080 } 1081 mProvisioningStateMachine.handleWifiDisabled(); 1082 } 1083 } 1084 1085 /** 1086 * Defines the callbacks expected from OsuServerConnection 1087 * 1088 * Note: Called on main thread (WifiService thread). 1089 */ 1090 public class OsuServerCallbacks { 1091 private final int mSessionId; 1092 OsuServerCallbacks(int sessionId)1093 OsuServerCallbacks(int sessionId) { 1094 mSessionId = sessionId; 1095 } 1096 1097 /** 1098 * Returns the session ID corresponding to this callback 1099 * 1100 * @return int sessionID 1101 */ getSessionId()1102 public int getSessionId() { 1103 return mSessionId; 1104 } 1105 1106 /** 1107 * Callback when a TLS connection to the server is failed. 1108 * 1109 * @param sessionId indicating current session ID 1110 * @param succeeded boolean indicating success/failure of server connection 1111 */ onServerConnectionStatus(int sessionId, boolean succeeded)1112 public void onServerConnectionStatus(int sessionId, boolean succeeded) { 1113 if (mVerboseLoggingEnabled) { 1114 Log.v(TAG, "OSU Server connection status=" + succeeded + " sessionId=" + sessionId); 1115 } 1116 mProvisioningStateMachine.getHandler().post(() -> 1117 mProvisioningStateMachine.handleServerConnectionStatus(sessionId, succeeded)); 1118 } 1119 1120 /** 1121 * Provides a server validation status for the session ID 1122 * 1123 * @param sessionId integer indicating current session ID 1124 * @param succeeded boolean indicating success/failure of server validation 1125 */ onServerValidationStatus(int sessionId, boolean succeeded)1126 public void onServerValidationStatus(int sessionId, boolean succeeded) { 1127 if (mVerboseLoggingEnabled) { 1128 Log.v(TAG, "OSU Server Validation status=" + succeeded + " sessionId=" + sessionId); 1129 } 1130 if (succeeded) { 1131 mProvisioningStateMachine.getHandler().post(() -> { 1132 mProvisioningStateMachine.handleServerValidationSuccess(sessionId); 1133 }); 1134 } else { 1135 mProvisioningStateMachine.getHandler().post(() -> { 1136 mProvisioningStateMachine.handleServerValidationFailure(sessionId); 1137 }); 1138 } 1139 } 1140 1141 /** 1142 * Callback when soap message is received from server. 1143 * 1144 * @param sessionId indicating current session ID 1145 * @param responseMessage SOAP SPP response parsed or {@code null} in any failure 1146 * Note: Called on different thread (OsuServer Thread)! 1147 */ onReceivedSoapMessage(int sessionId, @Nullable SppResponseMessage responseMessage)1148 public void onReceivedSoapMessage(int sessionId, 1149 @Nullable SppResponseMessage responseMessage) { 1150 if (mVerboseLoggingEnabled) { 1151 Log.v(TAG, "onReceivedSoapMessage with sessionId=" + sessionId); 1152 } 1153 mProvisioningStateMachine.getHandler().post(() -> 1154 mProvisioningStateMachine.handleSoapMessageResponse(sessionId, 1155 responseMessage)); 1156 } 1157 1158 /** 1159 * Callback when trust root certificates are retrieved from server. 1160 * 1161 * @param sessionId indicating current session ID 1162 * @param trustRootCertificates trust root CA certificates retrieved from server 1163 * Note: Called on different thread (OsuServer Thread)! 1164 */ onReceivedTrustRootCertificates(int sessionId, @NonNull Map<Integer, List<X509Certificate>> trustRootCertificates)1165 public void onReceivedTrustRootCertificates(int sessionId, 1166 @NonNull Map<Integer, List<X509Certificate>> trustRootCertificates) { 1167 if (mVerboseLoggingEnabled) { 1168 Log.v(TAG, "onReceivedTrustRootCertificates with sessionId=" + sessionId); 1169 } 1170 mProvisioningStateMachine.getHandler().post(() -> 1171 mProvisioningStateMachine.installTrustRootCertificates(sessionId, 1172 trustRootCertificates)); 1173 } 1174 } 1175 } 1176