• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.settings.wifi.dpp;
18 
19 import android.net.wifi.WifiConfiguration;
20 import android.text.TextUtils;
21 
22 import androidx.annotation.VisibleForTesting;
23 
24 import java.util.Arrays;
25 import java.util.List;
26 import java.util.regex.Pattern;
27 
28 /**
29  * Supports to parse 2 types of QR code
30  *
31  *     1. Standard Wi-Fi DPP bootstrapping information or
32  *     2. ZXing reader library's Wi-Fi Network config format described in
33  *     https://github.com/zxing/zxing/wiki/Barcode-Contents#wi-fi-network-config-android-ios-11
34  *
35  * ZXing reader library's Wi-Fi Network config format example:
36  *
37  *     WIFI:T:WPA;S:mynetwork;P:mypass;;
38  *
39  * parameter Example    Description
40  * T         WPA        Authentication type; can be WEP or WPA, or 'nopass' for no password. Or,
41  *                      omit for no password.
42  * S         mynetwork  Network SSID. Required. Enclose in double quotes if it is an ASCII name,
43  *                      but could be interpreted as hex (i.e. "ABCD")
44  * P         mypass     Password, ignored if T is "nopass" (in which case it may be omitted).
45  *                      Enclose in double quotes if it is an ASCII name, but could be interpreted as
46  *                      hex (i.e. "ABCD")
47  * H         true       Optional. True if the network SSID is hidden.
48  *
49  */
50 public class WifiQrCode {
51     static final String SCHEME_DPP = "DPP";
52     static final String SCHEME_ZXING_WIFI_NETWORK_CONFIG = "WIFI";
53     static final String PREFIX_DPP = "DPP:";
54     static final String PREFIX_ZXING_WIFI_NETWORK_CONFIG = "WIFI:";
55 
56     static final String PREFIX_DPP_PUBLIC_KEY = "K:";
57     static final String PREFIX_DPP_INFORMATION = "I:";
58 
59     static final String PREFIX_ZXING_SECURITY = "T:";
60     static final String PREFIX_ZXING_SSID = "S:";
61     static final String PREFIX_ZXING_PASSWORD = "P:";
62     static final String PREFIX_ZXING_HIDDEN_SSID = "H:";
63 
64     static final String DELIMITER_QR_CODE = ";";
65 
66     // Ignores password if security is SECURITY_NO_PASSWORD or absent
67     static final String SECURITY_NO_PASSWORD = "nopass"; //open network or OWE
68     static final String SECURITY_WEP = "WEP";
69     static final String SECURITY_WPA_PSK = "WPA";
70     static final String SECURITY_SAE = "SAE";
71 
72     private String mQrCode;
73 
74     /**
75      * SCHEME_DPP for standard Wi-Fi device provision protocol; SCHEME_ZXING_WIFI_NETWORK_CONFIG
76      * for ZXing reader library' Wi-Fi Network config format
77      */
78     private String mScheme;
79 
80     // Data from parsed Wi-Fi DPP QR code
81     private String mPublicKey;
82     private String mInformation;
83 
84     // Data from parsed ZXing reader library's Wi-Fi Network config format
85     private WifiNetworkConfig mWifiNetworkConfig;
86 
WifiQrCode(String qrCode)87     public WifiQrCode(String qrCode) throws IllegalArgumentException {
88         if (TextUtils.isEmpty(qrCode)) {
89             throw new IllegalArgumentException("Empty QR code");
90         }
91 
92         mQrCode = qrCode;
93 
94         if (qrCode.startsWith(PREFIX_DPP)) {
95             mScheme = SCHEME_DPP;
96             parseWifiDppQrCode(qrCode);
97         } else if (qrCode.startsWith(PREFIX_ZXING_WIFI_NETWORK_CONFIG)) {
98             mScheme = SCHEME_ZXING_WIFI_NETWORK_CONFIG;
99             parseZxingWifiQrCode(qrCode);
100         } else {
101             throw new IllegalArgumentException("Invalid scheme");
102         }
103     }
104 
105     /** Parses Wi-Fi DPP QR code string */
parseWifiDppQrCode(String qrCode)106     private void parseWifiDppQrCode(String qrCode) throws IllegalArgumentException {
107         List<String> keyValueList = getKeyValueList(qrCode, PREFIX_DPP, DELIMITER_QR_CODE);
108 
109         String publicKey = getValueOrNull(keyValueList, PREFIX_DPP_PUBLIC_KEY);
110         if (TextUtils.isEmpty(publicKey)) {
111             throw new IllegalArgumentException("Invalid format");
112         }
113         mPublicKey = publicKey;
114 
115         mInformation = getValueOrNull(keyValueList, PREFIX_DPP_INFORMATION);
116     }
117 
118     /** Parses ZXing reader library's Wi-Fi Network config format */
parseZxingWifiQrCode(String qrCode)119     private void parseZxingWifiQrCode(String qrCode) throws IllegalArgumentException {
120         List<String> keyValueList = getKeyValueList(qrCode, PREFIX_ZXING_WIFI_NETWORK_CONFIG,
121                 DELIMITER_QR_CODE);
122 
123         String security = getValueOrNull(keyValueList, PREFIX_ZXING_SECURITY);
124         String ssid = getValueOrNull(keyValueList, PREFIX_ZXING_SSID);
125         String password = getValueOrNull(keyValueList, PREFIX_ZXING_PASSWORD);
126         String hiddenSsidString = getValueOrNull(keyValueList, PREFIX_ZXING_HIDDEN_SSID);
127 
128         boolean hiddenSsid = "true".equalsIgnoreCase(hiddenSsidString);
129 
130         //"\", ";", "," and ":" are escaped with a backslash "\", should remove at first
131         security = removeBackSlash(security);
132         ssid = removeBackSlash(ssid);
133         password = removeBackSlash(password);
134 
135         mWifiNetworkConfig = WifiNetworkConfig.getValidConfigOrNull(security, ssid, password,
136                 hiddenSsid, WifiConfiguration.INVALID_NETWORK_ID, /* isHotspot */ false);
137 
138         if (mWifiNetworkConfig == null) {
139             throw new IllegalArgumentException("Invalid format");
140         }
141     }
142 
143     /**
144      * Splits key/value pairs from qrCode
145      *
146      * @param qrCode the QR code raw string
147      * @param prefixQrCode the string before all key/value pairs in qrCode
148      * @param delimiter the string to split key/value pairs, can't contain a backslash
149      * @return a list contains string of key/value (e.g. K:key1)
150      */
getKeyValueList(String qrCode, String prefixQrCode, String delimiter)151     private List<String> getKeyValueList(String qrCode, String prefixQrCode,
152                 String delimiter) {
153         String keyValueString = qrCode.substring(prefixQrCode.length());
154 
155         // Should not treat \delimiter as a delimiter
156         String regex = "(?<!\\\\)" + Pattern.quote(delimiter);
157 
158         return Arrays.asList(keyValueString.split(regex));
159     }
160 
getValueOrNull(List<String> keyValueList, String prefix)161     private String getValueOrNull(List<String> keyValueList, String prefix) {
162         for (String keyValue : keyValueList) {
163             if (keyValue.startsWith(prefix)) {
164                 return  keyValue.substring(prefix.length());
165             }
166         }
167 
168         return null;
169     }
170 
171     @VisibleForTesting
removeBackSlash(String input)172     String removeBackSlash(String input) {
173         if (input == null) {
174             return null;
175         }
176 
177         StringBuilder sb = new StringBuilder();
178         boolean backSlash = false;
179         for (char ch : input.toCharArray()) {
180             if (ch != '\\') {
181                 sb.append(ch);
182                 backSlash = false;
183             } else {
184                 if (backSlash) {
185                     sb.append(ch);
186                     backSlash = false;
187                     continue;
188                 }
189 
190                 backSlash = true;
191             }
192         }
193 
194         return sb.toString();
195     }
196 
getQrCode()197     String getQrCode() {
198         return mQrCode;
199     }
200 
201     /**
202      * Uses to check type of QR code
203      *
204      * SCHEME_DPP for standard Wi-Fi device provision protocol; SCHEME_ZXING_WIFI_NETWORK_CONFIG
205      * for ZXing reader library' Wi-Fi Network config format
206      */
getScheme()207     public String getScheme() {
208         return mScheme;
209     }
210 
211     /** Available when {@code getScheme()} returns SCHEME_DPP */
212     @VisibleForTesting
getPublicKey()213     String getPublicKey() {
214         return mPublicKey;
215     }
216 
217     /** May be available when {@code getScheme()} returns SCHEME_DPP */
getInformation()218     public String getInformation() {
219         return mInformation;
220     }
221 
222     /** Available when {@code getScheme()} returns SCHEME_ZXING_WIFI_NETWORK_CONFIG */
getWifiNetworkConfig()223     WifiNetworkConfig getWifiNetworkConfig() {
224         if (mWifiNetworkConfig == null) {
225             return null;
226         }
227 
228         return new WifiNetworkConfig(mWifiNetworkConfig);
229     }
230 
getValidWifiDppQrCodeOrNull(String qrCode)231     static WifiQrCode getValidWifiDppQrCodeOrNull(String qrCode) {
232         WifiQrCode wifiQrCode;
233         try {
234             wifiQrCode = new WifiQrCode(qrCode);
235         } catch(IllegalArgumentException e) {
236             return null;
237         }
238 
239         if (SCHEME_DPP.equals(wifiQrCode.getScheme())) {
240             return wifiQrCode;
241         }
242 
243         return null;
244     }
245 }
246