1 /* 2 * Copyright (C) 2018 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; 18 19 import static android.net.wifi.WifiManager.EASY_CONNECT_NETWORK_ROLE_AP; 20 21 import android.content.Context; 22 import android.hardware.wifi.supplicant.V1_2.DppAkm; 23 import android.hardware.wifi.supplicant.V1_2.DppFailureCode; 24 import android.hardware.wifi.supplicant.V1_2.DppNetRole; 25 import android.hardware.wifi.supplicant.V1_2.DppProgressCode; 26 import android.net.wifi.EasyConnectStatusCallback; 27 import android.net.wifi.IDppCallback; 28 import android.net.wifi.WifiConfiguration; 29 import android.net.wifi.WifiManager; 30 import android.os.Handler; 31 import android.os.IBinder; 32 import android.os.Looper; 33 import android.os.RemoteException; 34 import android.util.Log; 35 36 import com.android.internal.annotations.VisibleForTesting; 37 import com.android.internal.util.WakeupMessage; 38 import com.android.server.wifi.WifiNative.DppEventCallback; 39 40 /** 41 * DPP Manager class 42 * Implements the DPP Initiator APIs and callbacks 43 */ 44 public class DppManager { 45 private static final String TAG = "DppManager"; 46 public Handler mHandler; 47 48 private DppRequestInfo mDppRequestInfo = null; 49 private final WifiNative mWifiNative; 50 private String mClientIfaceName; 51 private boolean mVerboseLoggingEnabled; 52 WifiConfigManager mWifiConfigManager; 53 private final Context mContext; 54 @VisibleForTesting 55 public WakeupMessage mDppTimeoutMessage = null; 56 private final Clock mClock; 57 private static final String DPP_TIMEOUT_TAG = TAG + " Request Timeout"; 58 private static final int DPP_TIMEOUT_MS = 40_000; // 40 seconds 59 private final DppMetrics mDppMetrics; 60 61 private final DppEventCallback mDppEventCallback = new DppEventCallback() { 62 @Override 63 public void onSuccessConfigReceived(WifiConfiguration newWifiConfiguration) { 64 mHandler.post(() -> { 65 DppManager.this.onSuccessConfigReceived(newWifiConfiguration); 66 }); 67 } 68 69 @Override 70 public void onSuccessConfigSent() { 71 mHandler.post(() -> { 72 DppManager.this.onSuccessConfigSent(); 73 }); 74 } 75 76 @Override 77 public void onProgress(int dppStatusCode) { 78 mHandler.post(() -> { 79 DppManager.this.onProgress(dppStatusCode); 80 }); 81 } 82 83 @Override 84 public void onFailure(int dppStatusCode) { 85 mHandler.post(() -> { 86 DppManager.this.onFailure(dppStatusCode); 87 }); 88 } 89 }; 90 DppManager(Looper looper, WifiNative wifiNative, WifiConfigManager wifiConfigManager, Context context, DppMetrics dppMetrics)91 DppManager(Looper looper, WifiNative wifiNative, WifiConfigManager wifiConfigManager, 92 Context context, DppMetrics dppMetrics) { 93 mHandler = new Handler(looper); 94 mWifiNative = wifiNative; 95 mWifiConfigManager = wifiConfigManager; 96 mWifiNative.registerDppEventCallback(mDppEventCallback); 97 mContext = context; 98 mClock = new Clock(); 99 mDppMetrics = dppMetrics; 100 101 // Setup timer 102 mDppTimeoutMessage = new WakeupMessage(mContext, mHandler, 103 DPP_TIMEOUT_TAG, () -> { 104 timeoutDppRequest(); 105 }); 106 } 107 encodeStringToHex(String str)108 private static String encodeStringToHex(String str) { 109 if ((str.length() > 1) && (str.charAt(0) == '"') && (str.charAt(str.length() - 1) == '"')) { 110 // Remove the surrounding quotes 111 str = str.substring(1, str.length() - 1); 112 113 // Convert to Hex 114 char[] charsArray = str.toCharArray(); 115 StringBuffer hexBuffer = new StringBuffer(); 116 for (int i = 0; i < charsArray.length; i++) { 117 hexBuffer.append(Integer.toHexString((int) charsArray[i])); 118 } 119 return hexBuffer.toString(); 120 } 121 return str; 122 } 123 timeoutDppRequest()124 private void timeoutDppRequest() { 125 logd("DPP timeout"); 126 127 if (mDppRequestInfo == null) { 128 Log.e(TAG, "DPP timeout with no request info"); 129 return; 130 } 131 132 // Clean up supplicant resources 133 if (!mWifiNative.stopDppInitiator(mClientIfaceName)) { 134 Log.e(TAG, "Failed to stop DPP Initiator"); 135 } 136 137 // Clean up resources and let the caller know about the timeout 138 onFailure(DppFailureCode.TIMEOUT); 139 } 140 141 /** 142 * Start DPP request in Configurator-Initiator mode. The goal of this call is to send the 143 * selected Wi-Fi configuration to a remote peer so it could join that network. 144 * 145 * @param uid User ID 146 * @param binder Binder object 147 * @param enrolleeUri The Enrollee URI, scanned externally (e.g. via QR code) 148 * @param selectedNetworkId The selected Wi-Fi network ID to be sent 149 * @param enrolleeNetworkRole Network role of remote enrollee: STA or AP 150 * @param callback DPP Callback object 151 */ startDppAsConfiguratorInitiator(int uid, IBinder binder, String enrolleeUri, int selectedNetworkId, @WifiManager.EasyConnectNetworkRole int enrolleeNetworkRole, IDppCallback callback)152 public void startDppAsConfiguratorInitiator(int uid, IBinder binder, 153 String enrolleeUri, int selectedNetworkId, 154 @WifiManager.EasyConnectNetworkRole int enrolleeNetworkRole, IDppCallback callback) { 155 mDppMetrics.updateDppConfiguratorInitiatorRequests(); 156 if (mDppRequestInfo != null) { 157 try { 158 Log.e(TAG, "DPP request already in progress"); 159 Log.e(TAG, "Ongoing request UID: " + mDppRequestInfo.uid + ", new UID: " 160 + uid); 161 162 mDppMetrics.updateDppFailure(EasyConnectStatusCallback 163 .EASY_CONNECT_EVENT_FAILURE_BUSY); 164 // On going DPP. Call the failure callback directly 165 callback.onFailure(EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_BUSY); 166 } catch (RemoteException e) { 167 // Empty 168 } 169 return; 170 } 171 172 mClientIfaceName = mWifiNative.getClientInterfaceName(); 173 if (mClientIfaceName == null) { 174 try { 175 Log.e(TAG, "Wi-Fi client interface does not exist"); 176 // On going DPP. Call the failure callback directly 177 mDppMetrics.updateDppFailure(EasyConnectStatusCallback 178 .EASY_CONNECT_EVENT_FAILURE_GENERIC); 179 callback.onFailure(EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_GENERIC); 180 } catch (RemoteException e) { 181 // Empty 182 } 183 return; 184 } 185 186 WifiConfiguration selectedNetwork = mWifiConfigManager 187 .getConfiguredNetworkWithoutMasking(selectedNetworkId); 188 189 if (selectedNetwork == null) { 190 try { 191 Log.e(TAG, "Selected network is null"); 192 // On going DPP. Call the failure callback directly 193 mDppMetrics.updateDppFailure(EasyConnectStatusCallback 194 .EASY_CONNECT_EVENT_FAILURE_INVALID_NETWORK); 195 callback.onFailure(EasyConnectStatusCallback 196 .EASY_CONNECT_EVENT_FAILURE_INVALID_NETWORK); 197 } catch (RemoteException e) { 198 // Empty 199 } 200 return; 201 } 202 203 String password = null; 204 String psk = null; 205 int securityAkm; 206 207 // Currently support either SAE mode or PSK mode 208 if (selectedNetwork.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SAE)) { 209 // SAE 210 password = selectedNetwork.preSharedKey; 211 securityAkm = DppAkm.SAE; 212 } else if (selectedNetwork.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)) { 213 if (selectedNetwork.preSharedKey.matches(String.format("[0-9A-Fa-f]{%d}", 64))) { 214 // PSK 215 psk = selectedNetwork.preSharedKey; 216 } else { 217 // Passphrase 218 password = selectedNetwork.preSharedKey; 219 } 220 securityAkm = DppAkm.PSK; 221 } else { 222 try { 223 // Key management must be either PSK or SAE 224 Log.e(TAG, "Key management must be either PSK or SAE"); 225 mDppMetrics.updateDppFailure(EasyConnectStatusCallback 226 .EASY_CONNECT_EVENT_FAILURE_INVALID_NETWORK); 227 callback.onFailure( 228 EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_INVALID_NETWORK); 229 } catch (RemoteException e) { 230 // Empty 231 } 232 return; 233 } 234 235 mDppRequestInfo = new DppRequestInfo(); 236 mDppRequestInfo.uid = uid; 237 mDppRequestInfo.binder = binder; 238 mDppRequestInfo.callback = callback; 239 240 if (!linkToDeath(mDppRequestInfo)) { 241 // Notify failure and clean up 242 onFailure(EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_GENERIC); 243 return; 244 } 245 246 logd("Interface " + mClientIfaceName + ": Initializing URI: " + enrolleeUri); 247 248 mDppRequestInfo.startTime = mClock.getElapsedSinceBootMillis(); 249 mDppTimeoutMessage.schedule(mDppRequestInfo.startTime + DPP_TIMEOUT_MS); 250 251 // Send Enrollee URI and get a peer ID 252 int peerId = mWifiNative.addDppPeerUri(mClientIfaceName, enrolleeUri); 253 254 if (peerId < 0) { 255 Log.e(TAG, "DPP add URI failure"); 256 257 // Notify failure and clean up 258 onFailure(DppFailureCode.INVALID_URI); 259 return; 260 } 261 mDppRequestInfo.peerId = peerId; 262 263 // Auth init 264 logd("Authenticating"); 265 266 String ssidEncoded = encodeStringToHex(selectedNetwork.SSID); 267 String passwordEncoded = null; 268 269 if (password != null) { 270 passwordEncoded = encodeStringToHex(selectedNetwork.preSharedKey); 271 } 272 273 if (!mWifiNative.startDppConfiguratorInitiator(mClientIfaceName, 274 mDppRequestInfo.peerId, 0, ssidEncoded, passwordEncoded, psk, 275 enrolleeNetworkRole == EASY_CONNECT_NETWORK_ROLE_AP ? DppNetRole.AP 276 : DppNetRole.STA, 277 securityAkm)) { 278 Log.e(TAG, "DPP Start Configurator Initiator failure"); 279 280 // Notify failure and clean up 281 onFailure(DppFailureCode.FAILURE); 282 return; 283 } 284 285 logd("Success: Started DPP Initiator with peer ID " 286 + mDppRequestInfo.peerId); 287 } 288 289 /** 290 * Start DPP request in Enrollee-Initiator mode. The goal of this call is to receive a 291 * Wi-Fi configuration object from the peer configurator in order to join a network. 292 * 293 * @param uid User ID 294 * @param binder Binder object 295 * @param configuratorUri The Configurator URI, scanned externally (e.g. via QR code) 296 * @param callback DPP Callback object 297 */ startDppAsEnrolleeInitiator(int uid, IBinder binder, String configuratorUri, IDppCallback callback)298 public void startDppAsEnrolleeInitiator(int uid, IBinder binder, 299 String configuratorUri, IDppCallback callback) { 300 mDppMetrics.updateDppEnrolleeInitiatorRequests(); 301 if (mDppRequestInfo != null) { 302 try { 303 Log.e(TAG, "DPP request already in progress"); 304 Log.e(TAG, "Ongoing request UID: " + mDppRequestInfo.uid + ", new UID: " 305 + uid); 306 307 mDppMetrics.updateDppFailure(EasyConnectStatusCallback 308 .EASY_CONNECT_EVENT_FAILURE_BUSY); 309 // On going DPP. Call the failure callback directly 310 callback.onFailure(EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_BUSY); 311 } catch (RemoteException e) { 312 // Empty 313 } 314 return; 315 } 316 317 mDppRequestInfo = new DppRequestInfo(); 318 mDppRequestInfo.uid = uid; 319 mDppRequestInfo.binder = binder; 320 mDppRequestInfo.callback = callback; 321 322 if (!linkToDeath(mDppRequestInfo)) { 323 // Notify failure and clean up 324 onFailure(DppFailureCode.FAILURE); 325 return; 326 } 327 328 mDppRequestInfo.startTime = mClock.getElapsedSinceBootMillis(); 329 mDppTimeoutMessage.schedule(mDppRequestInfo.startTime + DPP_TIMEOUT_MS); 330 331 mClientIfaceName = mWifiNative.getClientInterfaceName(); 332 logd("Interface " + mClientIfaceName + ": Initializing URI: " + configuratorUri); 333 334 // Send Configurator URI and get a peer ID 335 int peerId = mWifiNative.addDppPeerUri(mClientIfaceName, configuratorUri); 336 337 if (peerId < 0) { 338 Log.e(TAG, "DPP add URI failure"); 339 onFailure(DppFailureCode.INVALID_URI); 340 return; 341 } 342 mDppRequestInfo.peerId = peerId; 343 344 // Auth init 345 logd("Authenticating"); 346 347 if (!mWifiNative.startDppEnrolleeInitiator(mClientIfaceName, mDppRequestInfo.peerId, 348 0)) { 349 Log.e(TAG, "DPP Start Enrollee Initiator failure"); 350 351 // Notify failure and clean up 352 onFailure(DppFailureCode.FAILURE); 353 return; 354 } 355 356 logd("Success: Started DPP Initiator with peer ID " 357 + mDppRequestInfo.peerId); 358 } 359 360 /** 361 * Stop a current DPP session 362 * 363 * @param uid User ID 364 */ stopDppSession(int uid)365 public void stopDppSession(int uid) { 366 if (mDppRequestInfo == null) { 367 logd("UID " + uid + " called stop DPP session with no active DPP session"); 368 return; 369 } 370 371 if (mDppRequestInfo.uid != uid) { 372 Log.e(TAG, "UID " + uid + " called stop DPP session but UID " + mDppRequestInfo.uid 373 + " has started it"); 374 return; 375 } 376 377 // Clean up supplicant resources 378 if (!mWifiNative.stopDppInitiator(mClientIfaceName)) { 379 Log.e(TAG, "Failed to stop DPP Initiator"); 380 } 381 382 cleanupDppResources(); 383 384 logd("Success: Stopped DPP Initiator"); 385 } 386 cleanupDppResources()387 private void cleanupDppResources() { 388 logd("DPP clean up resources"); 389 if (mDppRequestInfo == null) { 390 return; 391 } 392 393 // Cancel pending timeout 394 mDppTimeoutMessage.cancel(); 395 396 // Remove the URI from the supplicant list 397 if (!mWifiNative.removeDppUri(mClientIfaceName, mDppRequestInfo.peerId)) { 398 Log.e(TAG, "Failed to remove DPP URI ID " + mDppRequestInfo.peerId); 399 } 400 401 mDppRequestInfo.binder.unlinkToDeath(mDppRequestInfo.dr, 0); 402 403 mDppRequestInfo = null; 404 } 405 406 private static class DppRequestInfo { 407 public int uid; 408 public IBinder binder; 409 public IBinder.DeathRecipient dr; 410 public int peerId; 411 public IDppCallback callback; 412 public long startTime; 413 414 @Override toString()415 public String toString() { 416 return new StringBuilder("DppRequestInfo: uid=").append(uid).append(", binder=").append( 417 binder).append(", dr=").append(dr) 418 .append(", callback=").append(callback) 419 .append(", peerId=").append(peerId).toString(); 420 } 421 } 422 423 /** 424 * Enable vervose logging from DppManager 425 * 426 * @param verbose 0 to disable verbose logging, or any other value to enable. 427 */ enableVerboseLogging(int verbose)428 public void enableVerboseLogging(int verbose) { 429 mVerboseLoggingEnabled = verbose != 0 ? true : false; 430 } 431 onSuccessConfigReceived(WifiConfiguration newWifiConfiguration)432 private void onSuccessConfigReceived(WifiConfiguration newWifiConfiguration) { 433 try { 434 logd("onSuccessConfigReceived"); 435 436 if (mDppRequestInfo != null) { 437 long now = mClock.getElapsedSinceBootMillis(); 438 mDppMetrics.updateDppOperationTime((int) (now - mDppRequestInfo.startTime)); 439 440 NetworkUpdateResult networkUpdateResult = mWifiConfigManager 441 .addOrUpdateNetwork(newWifiConfiguration, mDppRequestInfo.uid); 442 443 if (networkUpdateResult.isSuccess()) { 444 mDppMetrics.updateDppEnrolleeSuccess(); 445 mDppRequestInfo.callback.onSuccessConfigReceived( 446 networkUpdateResult.getNetworkId()); 447 } else { 448 Log.e(TAG, "DPP configuration received, but failed to update network"); 449 mDppMetrics.updateDppFailure(EasyConnectStatusCallback 450 .EASY_CONNECT_EVENT_FAILURE_CONFIGURATION); 451 mDppRequestInfo.callback.onFailure(EasyConnectStatusCallback 452 .EASY_CONNECT_EVENT_FAILURE_CONFIGURATION); 453 } 454 } else { 455 Log.e(TAG, "Unexpected null Wi-Fi configuration object"); 456 } 457 } catch (RemoteException e) { 458 Log.e(TAG, "Callback failure"); 459 } 460 461 // Success, DPP is complete. Clear the DPP session automatically 462 cleanupDppResources(); 463 } 464 onSuccessConfigSent()465 private void onSuccessConfigSent() { 466 try { 467 if (mDppRequestInfo == null) { 468 Log.e(TAG, "onDppSuccessConfigSent event without a request information object"); 469 return; 470 } 471 472 logd("onSuccessConfigSent"); 473 474 long now = mClock.getElapsedSinceBootMillis(); 475 mDppMetrics.updateDppOperationTime((int) (now - mDppRequestInfo.startTime)); 476 477 mDppMetrics.updateDppConfiguratorSuccess( 478 EasyConnectStatusCallback.EASY_CONNECT_EVENT_SUCCESS_CONFIGURATION_SENT); 479 mDppRequestInfo.callback.onSuccess( 480 EasyConnectStatusCallback.EASY_CONNECT_EVENT_SUCCESS_CONFIGURATION_SENT); 481 482 } catch (RemoteException e) { 483 Log.e(TAG, "Callback failure"); 484 } 485 486 // Success, DPP is complete. Clear the DPP session automatically 487 cleanupDppResources(); 488 } 489 onProgress(int dppStatusCode)490 private void onProgress(int dppStatusCode) { 491 try { 492 if (mDppRequestInfo == null) { 493 Log.e(TAG, "onProgress event without a request information object"); 494 return; 495 } 496 497 logd("onProgress: " + dppStatusCode); 498 499 int dppProgressCode; 500 501 // Convert from HAL codes to WifiManager/user codes 502 switch (dppStatusCode) { 503 case DppProgressCode.AUTHENTICATION_SUCCESS: 504 dppProgressCode = EasyConnectStatusCallback 505 .EASY_CONNECT_EVENT_PROGRESS_AUTHENTICATION_SUCCESS; 506 break; 507 508 case DppProgressCode.RESPONSE_PENDING: 509 dppProgressCode = EasyConnectStatusCallback 510 .EASY_CONNECT_EVENT_PROGRESS_RESPONSE_PENDING; 511 break; 512 513 default: 514 Log.e(TAG, "onProgress: unknown code " + dppStatusCode); 515 return; 516 } 517 518 mDppRequestInfo.callback.onProgress(dppProgressCode); 519 520 } catch (RemoteException e) { 521 Log.e(TAG, "Callback failure"); 522 } 523 } 524 onFailure(int dppStatusCode)525 private void onFailure(int dppStatusCode) { 526 try { 527 if (mDppRequestInfo == null) { 528 Log.e(TAG, "onFailure event without a request information object"); 529 return; 530 } 531 532 logd("OnFailure: " + dppStatusCode); 533 534 long now = mClock.getElapsedSinceBootMillis(); 535 mDppMetrics.updateDppOperationTime((int) (now - mDppRequestInfo.startTime)); 536 537 int dppFailureCode; 538 539 // Convert from HAL codes to WifiManager/user codes 540 switch (dppStatusCode) { 541 case DppFailureCode.INVALID_URI: 542 dppFailureCode = 543 EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_INVALID_URI; 544 break; 545 546 case DppFailureCode.AUTHENTICATION: 547 dppFailureCode = 548 EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_AUTHENTICATION; 549 break; 550 551 case DppFailureCode.NOT_COMPATIBLE: 552 dppFailureCode = 553 EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_NOT_COMPATIBLE; 554 break; 555 556 case DppFailureCode.CONFIGURATION: 557 dppFailureCode = 558 EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_CONFIGURATION; 559 break; 560 561 case DppFailureCode.BUSY: 562 dppFailureCode = EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_BUSY; 563 break; 564 565 case DppFailureCode.TIMEOUT: 566 dppFailureCode = EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_TIMEOUT; 567 break; 568 569 case DppFailureCode.NOT_SUPPORTED: 570 dppFailureCode = 571 EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_NOT_SUPPORTED; 572 break; 573 574 case DppFailureCode.FAILURE: 575 default: 576 dppFailureCode = EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_GENERIC; 577 break; 578 } 579 580 mDppMetrics.updateDppFailure(dppFailureCode); 581 mDppRequestInfo.callback.onFailure(dppFailureCode); 582 583 } catch (RemoteException e) { 584 Log.e(TAG, "Callback failure"); 585 } 586 587 // All failures are fatal, clear the DPP session 588 cleanupDppResources(); 589 } 590 logd(String message)591 private void logd(String message) { 592 if (mVerboseLoggingEnabled) { 593 Log.d(TAG, message); 594 } 595 } 596 linkToDeath(DppRequestInfo dppRequestInfo)597 private boolean linkToDeath(DppRequestInfo dppRequestInfo) { 598 // register for binder death 599 dppRequestInfo.dr = new IBinder.DeathRecipient() { 600 @Override 601 public void binderDied() { 602 if (dppRequestInfo == null) { 603 return; 604 } 605 606 logd("binderDied: uid=" + dppRequestInfo.uid); 607 608 mHandler.post(() -> { 609 cleanupDppResources(); 610 }); 611 } 612 }; 613 614 try { 615 dppRequestInfo.binder.linkToDeath(dppRequestInfo.dr, 0); 616 } catch (RemoteException e) { 617 Log.e(TAG, "Error on linkToDeath - " + e); 618 dppRequestInfo.dr = null; 619 return false; 620 } 621 622 return true; 623 } 624 } 625