diff --git a/src/com/android/settings/wifi/dpp/WifiDppUtils.java b/src/com/android/settings/wifi/dpp/WifiDppUtils.java index 3275695c307..cc75d441dce 100644 --- a/src/com/android/settings/wifi/dpp/WifiDppUtils.java +++ b/src/com/android/settings/wifi/dpp/WifiDppUtils.java @@ -58,25 +58,7 @@ public class WifiDppUtils { /** The data corresponding to {@code WifiConfiguration} hiddenSSID */ public static final String EXTRA_WIFI_HIDDEN_SSID = "hiddenSsid"; - /** - * Acceptable QR code string may be a standard W-Fi DPP bootstrapping information or the Wi-Fi - * Network config format described in - * https://github.com/zxing/zxing/wiki/Barcode-Contents#wi-fi-network-config-android-ios-11 - * - * Wi-Fi Network config format example: - * - * WIFI:T:WPA;S:mynetwork;P:mypass;; - * - * parameter Example Description - * T WPA Authentication type; can be WEP or WPA, or 'nopass' for no password. Or, - * omit for no password. - * S mynetwork Network SSID. Required. Enclose in double quotes if it is an ASCII name, - * but could be interpreted as hex (i.e. "ABCD") - * P mypass Password, ignored if T is "nopass" (in which case it may be omitted). - * Enclose in double quotes if it is an ASCII name, but could be interpreted as - * hex (i.e. "ABCD") - * H true Optional. True if the network SSID is hidden. - */ + /** @see WifiQrCode */ public static final String EXTRA_QR_CODE = "qrCode"; /** diff --git a/src/com/android/settings/wifi/dpp/WifiNetworkConfig.java b/src/com/android/settings/wifi/dpp/WifiNetworkConfig.java index 439de988c0f..bb64e050532 100644 --- a/src/com/android/settings/wifi/dpp/WifiNetworkConfig.java +++ b/src/com/android/settings/wifi/dpp/WifiNetworkConfig.java @@ -19,15 +19,20 @@ package com.android.settings.wifi.dpp; import android.content.Intent; import android.text.TextUtils; +import androidx.annotation.Keep; + /** - * Contains the Wi-Fi Network config parameters described in - * https://github.com/zxing/zxing/wiki/Barcode-Contents#wi-fi-network-config-android-ios-11 + * Wraps the parameters of ZXing reader library's Wi-Fi Network config format. + * Please check {@code WifiQrCode} for detail of the format. * * Checks below members of {@code WifiDppUtils} for more information. * EXTRA_WIFI_SECURITY / EXTRA_WIFI_SSID / EXTRA_WIFI_PRE_SHARED_KEY / EXTRA_WIFI_HIDDEN_SSID / * EXTRA_QR_CODE */ public class WifiNetworkConfig { + // Ignores password if security is NO_PASSWORD or absent + public static final String NO_PASSWORD = "nopass"; + private String mSecurity; private String mSsid; private String mPreSharedKey; @@ -42,9 +47,18 @@ public class WifiNetworkConfig { } public WifiNetworkConfig(WifiNetworkConfig config) { - mSecurity = new String(config.mSecurity); - mSsid = new String(config.mSsid); - mPreSharedKey = new String(config.mPreSharedKey); + if (config.mSecurity != null) { + mSecurity = new String(config.mSecurity); + } + + if (config.mSsid != null) { + mSsid = new String(config.mSsid); + } + + if (config.mPreSharedKey != null) { + mPreSharedKey = new String(config.mPreSharedKey); + } + mHiddenSsid = config.mHiddenSsid; } @@ -69,12 +83,13 @@ public class WifiNetworkConfig { String preSharedKey = intent.getStringExtra(WifiDppUtils.EXTRA_WIFI_PRE_SHARED_KEY); boolean hiddenSsid = intent.getBooleanExtra(WifiDppUtils.EXTRA_WIFI_HIDDEN_SSID, false); - if (!isValidConfig(security, ssid, hiddenSsid)) { - return null; - } + return getValidConfigOrNull(security, ssid, preSharedKey, hiddenSsid); + } - if (ssid == null) { - ssid = ""; + public static WifiNetworkConfig getValidConfigOrNull(String security, String ssid, + String preSharedKey, boolean hiddenSsid) { + if (!isValidConfig(security, ssid, preSharedKey, hiddenSsid)) { + return null; } return new WifiNetworkConfig(security, ssid, preSharedKey, hiddenSsid); @@ -84,13 +99,17 @@ public class WifiNetworkConfig { if (config == null) { return false; } else { - return isValidConfig(config.mSecurity, config.mSsid, config.mHiddenSsid); + return isValidConfig(config.mSecurity, config.mSsid, config.mPreSharedKey, + config.mHiddenSsid); } } - public static boolean isValidConfig(String security, String ssid, boolean hiddenSsid) { - if (TextUtils.isEmpty(security)) { - return false; + public static boolean isValidConfig(String security, String ssid, String preSharedKey, + boolean hiddenSsid) { + if (!TextUtils.isEmpty(security) && !NO_PASSWORD.equals(security)) { + if (TextUtils.isEmpty(preSharedKey)) { + return false; + } } if (!hiddenSsid && TextUtils.isEmpty(ssid)) { @@ -100,18 +119,22 @@ public class WifiNetworkConfig { return true; } + @Keep public String getSecurity() { - return new String(mSecurity); + return mSecurity; } + @Keep public String getSsid() { - return new String(mSsid); + return mSsid; } + @Keep public String getPreSharedKey() { - return new String(mPreSharedKey); + return mPreSharedKey; } + @Keep public boolean getHiddenSsid() { return mHiddenSsid; } diff --git a/src/com/android/settings/wifi/dpp/WifiQrCode.java b/src/com/android/settings/wifi/dpp/WifiQrCode.java new file mode 100644 index 00000000000..ebc39c371cb --- /dev/null +++ b/src/com/android/settings/wifi/dpp/WifiQrCode.java @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.wifi.dpp; + +import android.content.Intent; +import android.text.TextUtils; + +import androidx.annotation.Keep; +import androidx.annotation.VisibleForTesting; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Supports to parse 2 types of QR code + * + * 1. Standard Wi-Fi DPP bootstrapping information or + * 2. ZXing reader library's Wi-Fi Network config format described in + * https://github.com/zxing/zxing/wiki/Barcode-Contents#wi-fi-network-config-android-ios-11 + * + * ZXing reader library's Wi-Fi Network config format example: + * + * WIFI:T:WPA;S:mynetwork;P:mypass;; + * + * parameter Example Description + * T WPA Authentication type; can be WEP or WPA, or 'nopass' for no password. Or, + * omit for no password. + * S mynetwork Network SSID. Required. Enclose in double quotes if it is an ASCII name, + * but could be interpreted as hex (i.e. "ABCD") + * P mypass Password, ignored if T is "nopass" (in which case it may be omitted). + * Enclose in double quotes if it is an ASCII name, but could be interpreted as + * hex (i.e. "ABCD") + * H true Optional. True if the network SSID is hidden. + * + */ +@Keep +public class WifiQrCode { + public static final String SCHEME_DPP = "DPP"; + public static final String SCHEME_ZXING_WIFI_NETWORK_CONFIG = "WIFI"; + public static final String PREFIX_DPP = "DPP:"; + public static final String PREFIX_ZXING_WIFI_NETWORK_CONFIG = "WIFI:"; + + public static final String PREFIX_DPP_PUBLIC_KEY = "K:"; + public static final String PREFIX_DPP_INFORMATION = "I:"; + + public static final String PREFIX_ZXING_SECURITY = "T:"; + public static final String PREFIX_ZXING_SSID = "S:"; + public static final String PREFIX_ZXING_PASSWORD = "P:"; + public static final String PREFIX_ZXING_HIDDEN_SSID = "H:"; + + public static final String SUFFIX_QR_CODE = ";"; + + private String mQrCode; + + /** + * SCHEME_DPP for standard Wi-Fi device provision protocol; SCHEME_ZXING_WIFI_NETWORK_CONFIG + * for ZXing reader library' Wi-Fi Network config format + */ + private String mScheme; + + // Data from parsed Wi-Fi DPP QR code + private String mPublicKey; + private String mInformation; + + // Data from parsed ZXing reader library's Wi-Fi Network config format + private WifiNetworkConfig mWifiNetworkConfig; + + @Keep + public WifiQrCode(String qrCode) throws IllegalArgumentException { + if (TextUtils.isEmpty(qrCode)) { + throw new IllegalArgumentException("Empty QR code"); + } + + mQrCode = qrCode; + + if (qrCode.startsWith(PREFIX_DPP)) { + mScheme = SCHEME_DPP; + parseWifiDppQrCode(qrCode); + } else if (qrCode.startsWith(PREFIX_ZXING_WIFI_NETWORK_CONFIG)) { + mScheme = SCHEME_ZXING_WIFI_NETWORK_CONFIG; + parseZxingWifiQrCode(qrCode); + } else { + throw new IllegalArgumentException("Invalid scheme"); + } + } + + /** Parses Wi-Fi DPP QR code string */ + private void parseWifiDppQrCode(String qrCode) throws IllegalArgumentException { + String publicKey = getSubStringOrNull(qrCode, PREFIX_DPP_PUBLIC_KEY, SUFFIX_QR_CODE); + if (TextUtils.isEmpty(publicKey)) { + throw new IllegalArgumentException("Invalid format"); + } + mPublicKey = publicKey; + + mInformation = getSubStringOrNull(qrCode, PREFIX_DPP_INFORMATION, SUFFIX_QR_CODE); + } + + /** Parses ZXing reader library's Wi-Fi Network config format */ + private void parseZxingWifiQrCode(String qrCode) throws IllegalArgumentException { + String security = getSubStringOrNull(qrCode, PREFIX_ZXING_SECURITY, SUFFIX_QR_CODE); + String ssid = getSubStringOrNull(qrCode, PREFIX_ZXING_SSID, SUFFIX_QR_CODE); + String password = getSubStringOrNull(qrCode, PREFIX_ZXING_PASSWORD, SUFFIX_QR_CODE); + String hiddenSsidString = getSubStringOrNull(qrCode, PREFIX_ZXING_HIDDEN_SSID, + SUFFIX_QR_CODE); + boolean hiddenSsid = "true".equalsIgnoreCase(hiddenSsidString); + + //"\", ";", "," and ":" are escaped with a backslash "\", should remove at first + security = removeBackSlash(security); + ssid = removeBackSlash(ssid); + password = removeBackSlash(password); + + mWifiNetworkConfig = WifiNetworkConfig.getValidConfigOrNull(security, ssid, password, + hiddenSsid); + + if (mWifiNetworkConfig == null) { + throw new IllegalArgumentException("Invalid format"); + } + } + + /** + * Gets the substring between prefix & suffix from input. + * + * @param prefix the string before the returned substring + * @param suffix the string after the returned substring + * @return null if not exists, non-null otherwise + */ + private static String getSubStringOrNull(String input, String prefix, String suffix) { + StringBuilder sb = new StringBuilder(); + String regex = sb.append(prefix).append("(.*?)").append(suffix).toString(); + Pattern pattern = Pattern.compile(regex); + Matcher matcher = pattern.matcher(input); + + if (!matcher.find()) { + return null; + } + + String target = matcher.group(1); + if (TextUtils.isEmpty(target)) { + return null; + } + + return target; + } + + @Keep + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + protected static String removeBackSlash(String input) { + if (input == null) { + return null; + } + + StringBuilder sb = new StringBuilder(); + boolean backSlash = false; + for (char ch : input.toCharArray()) { + if (ch != '\\') { + sb.append(ch); + backSlash = false; + } else { + if (backSlash) { + sb.append(ch); + backSlash = false; + continue; + } + + backSlash = true; + } + } + + return sb.toString(); + } + + @Keep + public String getQrCode() { + return mQrCode; + } + + /** + * Uses to check type of QR code + * + * SCHEME_DPP for standard Wi-Fi device provision protocol; SCHEME_ZXING_WIFI_NETWORK_CONFIG + * for ZXing reader library' Wi-Fi Network config format + */ + @Keep + public String getScheme() { + return mScheme; + } + + /** Available when {@code getScheme()} returns SCHEME_DPP */ + @Keep + public String getPublicKey() { + return mPublicKey; + } + + /** May be available when {@code getScheme()} returns SCHEME_DPP */ + @Keep + public String getInformation() { + return mInformation; + } + + /** Available when {@code getScheme()} returns SCHEME_ZXING_WIFI_NETWORK_CONFIG */ + @Keep + public WifiNetworkConfig getWifiNetworkConfig() { + if (mWifiNetworkConfig == null) { + return null; + } + + return new WifiNetworkConfig(mWifiNetworkConfig); + } +} diff --git a/tests/unit/src/com/android/settings/wifi/dpp/WifiQrCodetest.java b/tests/unit/src/com/android/settings/wifi/dpp/WifiQrCodetest.java new file mode 100644 index 00000000000..775ca489ffe --- /dev/null +++ b/tests/unit/src/com/android/settings/wifi/dpp/WifiQrCodetest.java @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.wifi.dpp; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class WifiQrCodetest { + // Valid Wi-Fi DPP QR code & it's parameters + private static final String VALID_WIFI_DPP_QR_CODE = "DPP:I:SN=4774LH2b4044;M:010203040506;K:" + + "MDkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDIgADURzxmttZoIRIPWGoQMV00XHWCAQIhXruVWOz0NjlkIA=;;"; + + private static final String PUBLIC_KEY_OF_VALID_WIFI_DPP_QR_CODE = + "MDkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDIgADURzxmttZoIRIPWGoQMV00XHWCAQIhXruVWOz0NjlkIA="; + + private static final String INFORMATION_OF_VALID_WIFI_DPP_QR_CODE = + "SN=4774LH2b4044"; + + // Valid ZXing reader library's Wi-Fi Network config format & it's parameters + private static final String VALID_ZXING_WIFI_QR_CODE = + "WIFI:T:WPA;S:mynetwork;P:mypass;H:true;;"; + + // Valid ZXing reader library's Wi-Fi Network config format - security type nopass and no password + private static final String VALID_ZXING_WIFI_QR_CODE_NOPASS_AND_NO_PASSWORD = + "WIFI:T:nopass;S:mynetwork;;"; + + // Valid ZXing reader library's Wi-Fi Network config format - no security and no password + private static final String VALID_ZXING_WIFI_QR_CODE_NO_SECURITY_AND_NO_PASSWORD = + "WIFI:T:;S:mynetwork;P:;H:false;;"; + + private static final String SECURITY_OF_VALID_ZXING_WIFI_QR_CODE = "WPA"; + private static final String SECURITY_OF_VALID_ZXING_WIFI_QR_CODE_NOPASS = "nopass"; + private static final String SSID_OF_VALID_ZXING_WIFI_QR_CODE = "mynetwork"; + private static final String PASSWORD_OF_VALID_ZXING_WIFI_QR_CODE = "mypass"; + + // Invalid scheme QR code + private static final String INVALID_SCHEME_QR_CODE = "BT:T:WPA;S:mynetwork;P:mypass;H:true;;"; + + // Invalid Wi-Fi DPP QR code - no public key - case 1 + private static final String INVALID_WIFI_DPP_QR_CODE_NO_PUBLIC_KEY_1 = + "DPP:I:SN=4774LH2b4044;M:010203040506;K:;;"; + + // Invalid Wi-Fi DPP QR code - no public key - case 2 + private static final String INVALID_WIFI_DPP_QR_CODE_NO_PUBLIC_KEY_2 = + "DPP:I:SN=4774LH2b4044;M:010203040506;;"; + + // Invalid ZXing reader library's Wi-Fi Network config format - no password + private static final String INVALID_ZXING_WIFI_QR_CODE_NO_PASSWORD = + "WIFI:T:WPA;S:mynetwork;P:;;"; + + // Invalid ZXing reader library's Wi-Fi Network config format - no SSID + private static final String INVALID_ZXING_WIFI_QR_CODE_NO_SSID = + "WIFI:T:WPA;P:mypass;;"; + + @Test + public void parseValidWifiDppQrCode() { + WifiQrCode wifiQrCode = new WifiQrCode(VALID_WIFI_DPP_QR_CODE); + + assertEquals(WifiQrCode.SCHEME_DPP, wifiQrCode.getScheme()); + assertEquals(PUBLIC_KEY_OF_VALID_WIFI_DPP_QR_CODE, wifiQrCode.getPublicKey()); + assertEquals(INFORMATION_OF_VALID_WIFI_DPP_QR_CODE, wifiQrCode.getInformation()); + } + + @Test + public void parseValidZxingWifiQrCode() { + WifiQrCode wifiQrCode = new WifiQrCode(VALID_ZXING_WIFI_QR_CODE); + WifiNetworkConfig config = wifiQrCode.getWifiNetworkConfig(); + + assertEquals(WifiQrCode.SCHEME_ZXING_WIFI_NETWORK_CONFIG, wifiQrCode.getScheme()); + assertNotNull(config); + assertEquals(SECURITY_OF_VALID_ZXING_WIFI_QR_CODE, config.getSecurity()); + assertEquals(SSID_OF_VALID_ZXING_WIFI_QR_CODE, config.getSsid()); + assertEquals(PASSWORD_OF_VALID_ZXING_WIFI_QR_CODE, config.getPreSharedKey()); + assertEquals(true, config.getHiddenSsid()); + } + + @Test + public void parseValidZxingWifiQrCode_noPass_and_no_password() { + WifiQrCode wifiQrCode = new WifiQrCode(VALID_ZXING_WIFI_QR_CODE_NOPASS_AND_NO_PASSWORD); + WifiNetworkConfig config = wifiQrCode.getWifiNetworkConfig(); + + assertEquals(WifiQrCode.SCHEME_ZXING_WIFI_NETWORK_CONFIG, wifiQrCode.getScheme()); + assertNotNull(config); + assertEquals(SECURITY_OF_VALID_ZXING_WIFI_QR_CODE_NOPASS, config.getSecurity()); + assertEquals(SSID_OF_VALID_ZXING_WIFI_QR_CODE, config.getSsid()); + assertNull(config.getPreSharedKey()); + assertEquals(false, config.getHiddenSsid()); + } + + @Test + public void parseValidZxingWifiQrCode_no_security_and_no_password() { + WifiQrCode wifiQrCode = new WifiQrCode(VALID_ZXING_WIFI_QR_CODE_NO_SECURITY_AND_NO_PASSWORD); + WifiNetworkConfig config = wifiQrCode.getWifiNetworkConfig(); + + assertEquals(WifiQrCode.SCHEME_ZXING_WIFI_NETWORK_CONFIG, wifiQrCode.getScheme()); + assertNotNull(config); + assertNull(config.getSecurity()); + assertEquals(SSID_OF_VALID_ZXING_WIFI_QR_CODE, config.getSsid()); + assertNull(config.getPreSharedKey()); + assertEquals(false, config.getHiddenSsid()); + } + + @Test + public void testRemoveBackSlash() { + assertEquals("\\", WifiQrCode.removeBackSlash("\\\\")); + assertEquals("ab", WifiQrCode.removeBackSlash("a\\b")); + assertEquals("a", WifiQrCode.removeBackSlash("\\a")); + assertEquals("\\b", WifiQrCode.removeBackSlash("\\\\b")); + assertEquals("c\\", WifiQrCode.removeBackSlash("c\\\\")); + } + + @Test + public void parseEmptyQrCode_shouldThrowIllegalArgumentException() { + try { + new WifiQrCode(null); + fail("Null QR code"); + } catch (IllegalArgumentException e) { + // Do nothing + } + + try { + new WifiQrCode(""); + fail("Empty string QR code"); + } catch (IllegalArgumentException e) { + // Do nothing + } + + try { + new WifiQrCode("DPP:;"); + fail("Empty content WIFI DPP QR code"); + } catch (IllegalArgumentException e) { + // Do nothing + } + + try { + new WifiQrCode("WIFI:;"); + fail("Empty content ZXing WIFI QR code"); + } catch (IllegalArgumentException e) { + // Do nothing + } + } + + @Test + public void parseInvalidSchemeQrCode_shouldThrowIllegalArgumentException() { + try { + new WifiQrCode(INVALID_SCHEME_QR_CODE); + fail("Invalid scheme"); + } catch (IllegalArgumentException e) { + // Do nothing + } + } + + @Test + public void parseInvalidWifiDppQrCode_noPublicKey_shouldThrowIllegalArgumentException() { + try { + new WifiQrCode(INVALID_WIFI_DPP_QR_CODE_NO_PUBLIC_KEY_1); + fail("No public key case 1"); + } catch (IllegalArgumentException e) { + // Do nothing + } + + try { + new WifiQrCode(INVALID_WIFI_DPP_QR_CODE_NO_PUBLIC_KEY_2); + fail("No public key case 2"); + } catch (IllegalArgumentException e) { + // Do nothing + } + } + + @Test + public void parseInvalidZxingWifiQrCode_noPassword_shouldThrowIllegalArgumentException() { + try { + new WifiQrCode(INVALID_ZXING_WIFI_QR_CODE_NO_PASSWORD); + fail("No password"); + } catch (IllegalArgumentException e) { + // Do nothing + } + } + + @Test + public void parseInvalidZxingWifiQrCode_noSsid_shouldThrowIllegalArgumentException() { + try { + new WifiQrCode(INVALID_ZXING_WIFI_QR_CODE_NO_SSID); + fail("No SSID"); + } catch (IllegalArgumentException e) { + // Do nothing + } + } +}