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 public static final String SCHEME_DPP = "DPP"; 52 public static final String SCHEME_ZXING_WIFI_NETWORK_CONFIG = "WIFI"; 53 public static final String PREFIX_DPP = "DPP:"; 54 public static final String PREFIX_ZXING_WIFI_NETWORK_CONFIG = "WIFI:"; 55 56 public static final String PREFIX_DPP_PUBLIC_KEY = "K:"; 57 public static final String PREFIX_DPP_INFORMATION = "I:"; 58 59 public static final String PREFIX_ZXING_SECURITY = "T:"; 60 public static final String PREFIX_ZXING_SSID = "S:"; 61 public static final String PREFIX_ZXING_PASSWORD = "P:"; 62 public static final String PREFIX_ZXING_HIDDEN_SSID = "H:"; 63 64 public static final String DELIMITER_QR_CODE = ";"; 65 66 // Ignores password if security is SECURITY_NO_PASSWORD or absent 67 public static final String SECURITY_NO_PASSWORD = "nopass"; //open network or OWE 68 public static final String SECURITY_WEP = "WEP"; 69 public static final String SECURITY_WPA_PSK = "WPA"; 70 public static final String SECURITY_SAE = "SAE"; 71 // Adb QR code pairing is in the following format: 72 // WIFI:T:ADB;S:myname;P:mypass;; 73 public static final String SECURITY_ADB = "ADB"; 74 75 private String mQrCode; 76 77 /** 78 * SCHEME_DPP for standard Wi-Fi device provision protocol; SCHEME_ZXING_WIFI_NETWORK_CONFIG 79 * for ZXing reader library' Wi-Fi Network config format 80 */ 81 private String mScheme; 82 83 // Data from parsed Wi-Fi DPP QR code 84 private String mPublicKey; 85 private String mInformation; 86 87 // Data from parsed ZXing reader library's Wi-Fi Network config format 88 private WifiNetworkConfig mWifiNetworkConfig; 89 WifiQrCode(String qrCode)90 public WifiQrCode(String qrCode) throws IllegalArgumentException { 91 if (TextUtils.isEmpty(qrCode)) { 92 throw new IllegalArgumentException("Empty QR code"); 93 } 94 95 mQrCode = qrCode; 96 97 if (qrCode.startsWith(PREFIX_DPP)) { 98 mScheme = SCHEME_DPP; 99 parseWifiDppQrCode(qrCode); 100 } else if (qrCode.startsWith(PREFIX_ZXING_WIFI_NETWORK_CONFIG)) { 101 mScheme = SCHEME_ZXING_WIFI_NETWORK_CONFIG; 102 parseZxingWifiQrCode(qrCode); 103 } else { 104 throw new IllegalArgumentException("Invalid scheme"); 105 } 106 } 107 108 /** Parses Wi-Fi DPP QR code string */ parseWifiDppQrCode(String qrCode)109 private void parseWifiDppQrCode(String qrCode) throws IllegalArgumentException { 110 List keyValueList = getKeyValueList(qrCode, PREFIX_DPP, DELIMITER_QR_CODE); 111 112 String publicKey = getValueOrNull(keyValueList, PREFIX_DPP_PUBLIC_KEY); 113 if (TextUtils.isEmpty(publicKey)) { 114 throw new IllegalArgumentException("Invalid format"); 115 } 116 mPublicKey = publicKey; 117 118 mInformation = getValueOrNull(keyValueList, PREFIX_DPP_INFORMATION); 119 } 120 121 /** Parses ZXing reader library's Wi-Fi Network config format */ parseZxingWifiQrCode(String qrCode)122 private void parseZxingWifiQrCode(String qrCode) throws IllegalArgumentException { 123 List keyValueList = getKeyValueList(qrCode, PREFIX_ZXING_WIFI_NETWORK_CONFIG, 124 DELIMITER_QR_CODE); 125 126 String security = getValueOrNull(keyValueList, PREFIX_ZXING_SECURITY); 127 String ssid = getValueOrNull(keyValueList, PREFIX_ZXING_SSID); 128 String password = getValueOrNull(keyValueList, PREFIX_ZXING_PASSWORD); 129 String hiddenSsidString = getValueOrNull(keyValueList, PREFIX_ZXING_HIDDEN_SSID); 130 131 boolean hiddenSsid = "true".equalsIgnoreCase(hiddenSsidString); 132 133 //"\", ";", "," and ":" are escaped with a backslash "\", should remove at first 134 security = removeBackSlash(security); 135 ssid = removeBackSlash(ssid); 136 password = removeBackSlash(password); 137 138 mWifiNetworkConfig = WifiNetworkConfig.getValidConfigOrNull(security, ssid, password, 139 hiddenSsid, WifiConfiguration.INVALID_NETWORK_ID, /* isHotspot */ false); 140 141 if (mWifiNetworkConfig == null) { 142 throw new IllegalArgumentException("Invalid format"); 143 } 144 } 145 146 /** 147 * Splits key/value pairs from qrCode 148 * 149 * @param qrCode the QR code raw string 150 * @param prefixQrCode the string before all key/value pairs in qrCode 151 * @param delimiter the string to split key/value pairs, can't contain a backslash 152 * @return a list contains string of key/value (e.g. K:key1) 153 */ getKeyValueList(String qrCode, String prefixQrCode, String delimiter)154 private List<String> getKeyValueList(String qrCode, String prefixQrCode, 155 String delimiter) { 156 String keyValueString = qrCode.substring(prefixQrCode.length()); 157 158 // Should not treat \delimiter as a delimiter 159 String regex = "(?<!\\\\)" + Pattern.quote(delimiter); 160 161 List<String> result = Arrays.asList(keyValueString.split(regex)); 162 return result; 163 } 164 getValueOrNull(List<String> keyValueList, String prefix)165 private String getValueOrNull(List<String> keyValueList, String prefix) { 166 for (String keyValue : keyValueList) { 167 if (keyValue.startsWith(prefix)) { 168 return keyValue.substring(prefix.length()); 169 } 170 } 171 172 return null; 173 } 174 175 @VisibleForTesting removeBackSlash(String input)176 String removeBackSlash(String input) { 177 if (input == null) { 178 return null; 179 } 180 181 StringBuilder sb = new StringBuilder(); 182 boolean backSlash = false; 183 for (char ch : input.toCharArray()) { 184 if (ch != '\\') { 185 sb.append(ch); 186 backSlash = false; 187 } else { 188 if (backSlash) { 189 sb.append(ch); 190 backSlash = false; 191 continue; 192 } 193 194 backSlash = true; 195 } 196 } 197 198 return sb.toString(); 199 } 200 getQrCode()201 public String getQrCode() { 202 return mQrCode; 203 } 204 205 /** 206 * Uses to check type of QR code 207 * 208 * SCHEME_DPP for standard Wi-Fi device provision protocol; SCHEME_ZXING_WIFI_NETWORK_CONFIG 209 * for ZXing reader library' Wi-Fi Network config format 210 */ getScheme()211 public String getScheme() { 212 return mScheme; 213 } 214 215 /** Available when {@code getScheme()} returns SCHEME_DPP */ 216 @VisibleForTesting getPublicKey()217 String getPublicKey() { 218 return mPublicKey; 219 } 220 221 /** May be available when {@code getScheme()} returns SCHEME_DPP */ getInformation()222 public String getInformation() { 223 return mInformation; 224 } 225 226 /** Available when {@code getScheme()} returns SCHEME_ZXING_WIFI_NETWORK_CONFIG */ getWifiNetworkConfig()227 public WifiNetworkConfig getWifiNetworkConfig() { 228 if (mWifiNetworkConfig == null) { 229 return null; 230 } 231 232 return new WifiNetworkConfig(mWifiNetworkConfig); 233 } 234 getValidWifiDppQrCodeOrNull(String qrCode)235 public static WifiQrCode getValidWifiDppQrCodeOrNull(String qrCode) { 236 WifiQrCode wifiQrCode; 237 try { 238 wifiQrCode = new WifiQrCode(qrCode); 239 } catch(IllegalArgumentException e) { 240 return null; 241 } 242 243 if (SCHEME_DPP.equals(wifiQrCode.getScheme())) { 244 return wifiQrCode; 245 } 246 247 return null; 248 } 249 } 250