1 /* 2 * Copyright (C) 2020 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 package com.android.settings.wifi.dpp; 17 18 import android.content.Context; 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 * Extension of WifiQrCode to support ADB QR code format. 30 * It will be based on the ZXing format: 31 * 32 * WIFI:T:ADB;S:myname;P:mypassword;; 33 */ 34 public class AdbQrCode { 35 static final String SECURITY_ADB = "ADB"; 36 static final String SCHEME_DPP = "DPP"; 37 static final String SCHEME_ZXING_WIFI_NETWORK_CONFIG = "WIFI"; 38 static final String PREFIX_DPP = "DPP:"; 39 static final String PREFIX_ZXING_WIFI_NETWORK_CONFIG = "WIFI:"; 40 41 static final String PREFIX_DPP_PUBLIC_KEY = "K:"; 42 static final String PREFIX_DPP_INFORMATION = "I:"; 43 44 static final String PREFIX_ZXING_SECURITY = "T:"; 45 static final String PREFIX_ZXING_SSID = "S:"; 46 static final String PREFIX_ZXING_PASSWORD = "P:"; 47 static final String PREFIX_ZXING_HIDDEN_SSID = "H:"; 48 static final String DELIMITER_QR_CODE = ";"; 49 // Ignores password if security is SECURITY_NO_PASSWORD or absent 50 static final String SECURITY_NO_PASSWORD = "nopass"; //open network or OWE 51 static final String SECURITY_WEP = "WEP"; 52 static final String SECURITY_WPA_PSK = "WPA"; 53 static final String SECURITY_SAE = "SAE"; 54 private String mQrCode; 55 /** 56 * SCHEME_DPP for standard Wi-Fi device provision protocol; SCHEME_ZXING_WIFI_NETWORK_CONFIG 57 * for ZXing reader library' Wi-Fi Network config format 58 */ 59 private String mScheme; 60 // Data from parsed Wi-Fi DPP QR code 61 private String mPublicKey; 62 private String mInformation; 63 // Data from parsed ZXing reader library's Wi-Fi Network config format 64 private WifiNetworkConfig mAdbConfig; 65 AdbQrCode(String qrCode)66 public AdbQrCode(String qrCode) throws IllegalArgumentException { 67 if (TextUtils.isEmpty(qrCode)) { 68 throw new IllegalArgumentException("Empty QR code"); 69 } 70 71 mQrCode = qrCode; 72 if (qrCode.startsWith(PREFIX_DPP)) { 73 mScheme = SCHEME_DPP; 74 parseWifiDppQrCode(qrCode); 75 } else if (qrCode.startsWith(PREFIX_ZXING_WIFI_NETWORK_CONFIG)) { 76 mScheme = SCHEME_ZXING_WIFI_NETWORK_CONFIG; 77 parseZxingWifiQrCode(qrCode); 78 } else { 79 throw new IllegalArgumentException("Invalid scheme"); 80 } 81 82 // Only accept the zxing format. 83 if (!SCHEME_ZXING_WIFI_NETWORK_CONFIG.equals(getScheme())) { 84 throw new IllegalArgumentException("DPP format not supported for ADB QR code"); 85 } 86 mAdbConfig = getWifiNetworkConfig(); 87 88 if (!SECURITY_ADB.equals(mAdbConfig.getSecurity())) { 89 throw new IllegalArgumentException("Invalid security type"); 90 } 91 92 if (TextUtils.isEmpty(mAdbConfig.getSsid())) { 93 throw new IllegalArgumentException("Empty service name"); 94 } 95 96 if (TextUtils.isEmpty(mAdbConfig.getPreSharedKey())) { 97 throw new IllegalArgumentException("Empty password"); 98 } 99 } 100 getAdbNetworkConfig()101 public WifiNetworkConfig getAdbNetworkConfig() { 102 return mAdbConfig; 103 } 104 105 /** 106 * Triggers a vibration to notify of a valid QR code. 107 * 108 * @param context The context to use 109 */ triggerVibrationForQrCodeRecognition(Context context)110 public static void triggerVibrationForQrCodeRecognition(Context context) { 111 WifiDppUtils.triggerVibrationForQrCodeRecognition(context); 112 } 113 114 /** Parses Wi-Fi DPP QR code string */ parseWifiDppQrCode(String qrCode)115 private void parseWifiDppQrCode(String qrCode) throws IllegalArgumentException { 116 List<String> keyValueList = getKeyValueList(qrCode, PREFIX_DPP, DELIMITER_QR_CODE); 117 String publicKey = getValueOrNull(keyValueList, PREFIX_DPP_PUBLIC_KEY); 118 if (TextUtils.isEmpty(publicKey)) { 119 throw new IllegalArgumentException("Invalid format"); 120 } 121 mPublicKey = publicKey; 122 mInformation = getValueOrNull(keyValueList, PREFIX_DPP_INFORMATION); 123 } 124 125 /** Parses ZXing reader library's Wi-Fi Network config format */ parseZxingWifiQrCode(String qrCode)126 private void parseZxingWifiQrCode(String qrCode) throws IllegalArgumentException { 127 List<String> keyValueList = getKeyValueList(qrCode, PREFIX_ZXING_WIFI_NETWORK_CONFIG, 128 DELIMITER_QR_CODE); 129 String security = getValueOrNull(keyValueList, PREFIX_ZXING_SECURITY); 130 String ssid = getValueOrNull(keyValueList, PREFIX_ZXING_SSID); 131 String password = getValueOrNull(keyValueList, PREFIX_ZXING_PASSWORD); 132 String hiddenSsidString = getValueOrNull(keyValueList, PREFIX_ZXING_HIDDEN_SSID); 133 boolean hiddenSsid = "true".equalsIgnoreCase(hiddenSsidString); 134 //"\", ";", "," and ":" are escaped with a backslash "\", should remove at first 135 security = removeBackSlash(security); 136 ssid = removeBackSlash(ssid); 137 password = removeBackSlash(password); 138 mAdbConfig = WifiNetworkConfig.getValidConfigOrNull(security, ssid, password, 139 hiddenSsid, WifiConfiguration.INVALID_NETWORK_ID, /* isHotspot */ false); 140 if (mAdbConfig == null) { 141 throw new IllegalArgumentException("Invalid format"); 142 } 143 } 144 145 /** 146 * Splits key/value pairs from qrCode 147 * 148 * @param qrCode the QR code raw string 149 * @param prefixQrCode the string before all key/value pairs in qrCode 150 * @param delimiter the string to split key/value pairs, can't contain a backslash 151 * @return a list contains string of key/value (e.g. K:key1) 152 */ getKeyValueList(String qrCode, String prefixQrCode, String delimiter)153 private List<String> getKeyValueList(String qrCode, String prefixQrCode, 154 String delimiter) { 155 String keyValueString = qrCode.substring(prefixQrCode.length()); 156 // Should not treat \delimiter as a delimiter 157 String regex = "(?<!\\\\)" + Pattern.quote(delimiter); 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 String strippedKeyValue = keyValue.stripLeading(); 164 if (strippedKeyValue.startsWith(prefix)) { 165 return strippedKeyValue.substring(prefix.length()); 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 StringBuilder sb = new StringBuilder(); 177 boolean backSlash = false; 178 for (char ch : input.toCharArray()) { 179 if (ch != '\\') { 180 sb.append(ch); 181 backSlash = false; 182 } else { 183 if (backSlash) { 184 sb.append(ch); 185 backSlash = false; 186 continue; 187 } 188 backSlash = true; 189 } 190 } 191 return sb.toString(); 192 } 193 getQrCode()194 String getQrCode() { 195 return mQrCode; 196 } 197 198 /** 199 * Uses to check type of QR code 200 * 201 * SCHEME_DPP for standard Wi-Fi device provision protocol; SCHEME_ZXING_WIFI_NETWORK_CONFIG 202 * for ZXing reader library' Wi-Fi Network config format 203 */ getScheme()204 public String getScheme() { 205 return mScheme; 206 } 207 208 /** Available when {@code getScheme()} returns SCHEME_DPP */ 209 @VisibleForTesting getPublicKey()210 String getPublicKey() { 211 return mPublicKey; 212 } 213 214 /** May be available when {@code getScheme()} returns SCHEME_DPP */ getInformation()215 public String getInformation() { 216 return mInformation; 217 } 218 219 /** Available when {@code getScheme()} returns SCHEME_ZXING_WIFI_NETWORK_CONFIG */ getWifiNetworkConfig()220 WifiNetworkConfig getWifiNetworkConfig() { 221 if (mAdbConfig == null) { 222 return null; 223 } 224 return new WifiNetworkConfig(mAdbConfig); 225 } 226 } 227