/* * 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 com.android.settings.wifi.dpp.WifiQrCode.SECURITY_NO_PASSWORD; import static com.android.settings.wifi.dpp.WifiQrCode.SECURITY_SAE; import static com.android.settings.wifi.dpp.WifiQrCode.SECURITY_WEP; import static com.android.settings.wifi.dpp.WifiQrCode.SECURITY_WPA_PSK; import android.content.Context; import android.content.Intent; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiConfiguration.KeyMgmt; import android.net.wifi.WifiManager; import android.text.TextUtils; import android.util.Log; import androidx.annotation.VisibleForTesting; import java.util.ArrayList; import java.util.List; /** * 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 { static final String FAKE_SSID = "fake network"; static final String FAKE_PASSWORD = "password"; private static final String TAG = "WifiNetworkConfig"; private String mSecurity; private String mSsid; private String mPreSharedKey; private boolean mHiddenSsid; private int mNetworkId; private boolean mIsHotspot; @VisibleForTesting WifiNetworkConfig(String security, String ssid, String preSharedKey, boolean hiddenSsid, int networkId, boolean isHotspot) { mSecurity = security; mSsid = ssid; mPreSharedKey = preSharedKey; mHiddenSsid = hiddenSsid; mNetworkId = networkId; mIsHotspot = isHotspot; } public WifiNetworkConfig(WifiNetworkConfig config) { mSecurity = config.mSecurity; mSsid = config.mSsid; mPreSharedKey = config.mPreSharedKey; mHiddenSsid = config.mHiddenSsid; mNetworkId = config.mNetworkId; mIsHotspot = config.mIsHotspot; } /** * Wi-Fi DPP activities should implement this interface for fragments to retrieve the * WifiNetworkConfig for configuration */ public interface Retriever { WifiNetworkConfig getWifiNetworkConfig(); } /** * Retrieve WifiNetworkConfig from below 2 intents * * android.settings.WIFI_DPP_CONFIGURATOR_QR_CODE_GENERATOR * android.settings.WIFI_DPP_CONFIGURATOR_QR_CODE_SCANNER */ static WifiNetworkConfig getValidConfigOrNull(Intent intent) { final String security = intent.getStringExtra(WifiDppUtils.EXTRA_WIFI_SECURITY); final String ssid = intent.getStringExtra(WifiDppUtils.EXTRA_WIFI_SSID); final String preSharedKey = intent.getStringExtra(WifiDppUtils.EXTRA_WIFI_PRE_SHARED_KEY); final boolean hiddenSsid = intent.getBooleanExtra(WifiDppUtils.EXTRA_WIFI_HIDDEN_SSID, false); final int networkId = intent.getIntExtra(WifiDppUtils.EXTRA_WIFI_NETWORK_ID, WifiConfiguration.INVALID_NETWORK_ID); final boolean isHotspot = intent.getBooleanExtra(WifiDppUtils.EXTRA_IS_HOTSPOT, false); return getValidConfigOrNull(security, ssid, preSharedKey, hiddenSsid, networkId, isHotspot); } static WifiNetworkConfig getValidConfigOrNull(String security, String ssid, String preSharedKey, boolean hiddenSsid, int networkId, boolean isHotspot) { if (!isValidConfig(security, ssid, preSharedKey, hiddenSsid)) { return null; } return new WifiNetworkConfig(security, ssid, preSharedKey, hiddenSsid, networkId, isHotspot); } static boolean isValidConfig(WifiNetworkConfig config) { if (config == null) { return false; } else { return isValidConfig(config.mSecurity, config.mSsid, config.mPreSharedKey, config.mHiddenSsid); } } static boolean isValidConfig(String security, String ssid, String preSharedKey, boolean hiddenSsid) { if (!TextUtils.isEmpty(security) && !SECURITY_NO_PASSWORD.equals(security)) { if (TextUtils.isEmpty(preSharedKey)) { return false; } } if (!hiddenSsid && TextUtils.isEmpty(ssid)) { return false; } return true; } /** * Escaped special characters "\", ";", ":", "," with a backslash * See https://github.com/zxing/zxing/wiki/Barcode-Contents */ private String escapeSpecialCharacters(String str) { if (TextUtils.isEmpty(str)) { return str; } StringBuilder buf = new StringBuilder(); for (int i = 0; i < str.length(); i++) { char ch = str.charAt(i); if (ch =='\\' || ch == ',' || ch == ';' || ch == ':') { buf.append('\\'); } buf.append(ch); } return buf.toString(); } /** * Construct a barcode string for WiFi network login. * See https://en.wikipedia.org/wiki/QR_code#WiFi_network_login */ String getQrCode() { final String empty = ""; return new StringBuilder("WIFI:") .append("S:") .append(escapeSpecialCharacters(mSsid)) .append(";") .append("T:") .append(TextUtils.isEmpty(mSecurity) ? empty : mSecurity) .append(";") .append("P:") .append(TextUtils.isEmpty(mPreSharedKey) ? empty : escapeSpecialCharacters(mPreSharedKey)) .append(";") .append("H:") .append(mHiddenSsid) .append(";;") .toString(); } public String getSecurity() { return mSecurity; } public String getSsid() { return mSsid; } public String getPreSharedKey() { return mPreSharedKey; } public boolean getHiddenSsid() { return mHiddenSsid; } public int getNetworkId() { return mNetworkId; } public boolean isHotspot() { return mIsHotspot; } public boolean isSupportWifiDpp(Context context) { if (!WifiDppUtils.isWifiDppEnabled(context)) { return false; } if (TextUtils.isEmpty(mSecurity)) { return false; } // DPP 1.0 only supports SAE and PSK. final WifiManager wifiManager = context.getSystemService(WifiManager.class); switch (mSecurity) { case SECURITY_SAE: if (wifiManager.isWpa3SaeSupported()) { return true; } break; case SECURITY_WPA_PSK: return true; default: } return false; } /** * This is a simplified method from {@code WifiConfigController.getConfig()} * * @return When it's a open network, returns 2 WifiConfiguration in the List, the 1st is * open network and the 2nd is enhanced open network. Returns 1 WifiConfiguration in the * List for all other supported Wi-Fi securities. */ List getWifiConfigurations() { final List wifiConfigurations = new ArrayList<>(); if (!isValidConfig(this)) { return wifiConfigurations; } if (TextUtils.isEmpty(mSecurity) || SECURITY_NO_PASSWORD.equals(mSecurity)) { // TODO (b/129835824): we add both open network and enhanced open network to WifiManager // for android Q, should improve it in the future. final WifiConfiguration openNetworkWifiConfiguration = getBasicWifiConfiguration(); openNetworkWifiConfiguration.allowedKeyManagement.set(KeyMgmt.NONE); wifiConfigurations.add(openNetworkWifiConfiguration); final WifiConfiguration enhancedOpenNetworkWifiConfiguration = getBasicWifiConfiguration(); enhancedOpenNetworkWifiConfiguration .setSecurityParams(WifiConfiguration.SECURITY_TYPE_OWE); wifiConfigurations.add(enhancedOpenNetworkWifiConfiguration); return wifiConfigurations; } final WifiConfiguration wifiConfiguration = getBasicWifiConfiguration(); if (mSecurity.startsWith(SECURITY_WEP)) { wifiConfiguration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_WEP); // WEP-40, WEP-104, and 256-bit WEP (WEP-232?) final int length = mPreSharedKey.length(); if ((length == 10 || length == 26 || length == 58) && mPreSharedKey.matches("[0-9A-Fa-f]*")) { wifiConfiguration.wepKeys[0] = mPreSharedKey; } else { wifiConfiguration.wepKeys[0] = addQuotationIfNeeded(mPreSharedKey); } } else if (mSecurity.startsWith(SECURITY_WPA_PSK)) { wifiConfiguration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK); if (mPreSharedKey.matches("[0-9A-Fa-f]{64}")) { wifiConfiguration.preSharedKey = mPreSharedKey; } else { wifiConfiguration.preSharedKey = addQuotationIfNeeded(mPreSharedKey); } } else if (mSecurity.startsWith(SECURITY_SAE)) { wifiConfiguration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_SAE); if (mPreSharedKey.length() != 0) { wifiConfiguration.preSharedKey = addQuotationIfNeeded(mPreSharedKey); } } else { Log.w(TAG, "Unsupported security"); return wifiConfigurations; } wifiConfigurations.add(wifiConfiguration); return wifiConfigurations; } private WifiConfiguration getBasicWifiConfiguration() { final WifiConfiguration wifiConfiguration = new WifiConfiguration(); wifiConfiguration.SSID = addQuotationIfNeeded(mSsid); wifiConfiguration.hiddenSSID = mHiddenSsid; wifiConfiguration.networkId = mNetworkId; return wifiConfiguration; } private String addQuotationIfNeeded(String input) { if (TextUtils.isEmpty(input)) { return ""; } if (input.length() >= 2 && input.startsWith("\"") && input.endsWith("\"")) { return input; } StringBuilder sb = new StringBuilder(); sb.append("\"").append(input).append("\""); return sb.toString(); } }