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