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