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