Jan Nordqvist | ee699a6 | 2016-02-05 15:30:26 -0800 | [diff] [blame] | 1 | package com.android.hotspot2; |
| 2 | |
| 3 | import android.content.Context; |
| 4 | import android.content.Intent; |
| 5 | import android.net.CaptivePortal; |
| 6 | import android.net.ConnectivityManager; |
| 7 | import android.net.ICaptivePortal; |
| 8 | import android.net.Network; |
| 9 | import android.net.wifi.WifiConfiguration; |
| 10 | import android.net.wifi.WifiEnterpriseConfig; |
| 11 | import android.net.wifi.WifiInfo; |
| 12 | import android.net.wifi.WifiManager; |
| 13 | import android.util.Log; |
| 14 | |
| 15 | import com.android.configparse.ConfigBuilder; |
| 16 | import com.android.hotspot2.omadm.MOManager; |
| 17 | import com.android.hotspot2.omadm.MOTree; |
| 18 | import com.android.hotspot2.omadm.OMAConstants; |
| 19 | import com.android.hotspot2.omadm.OMAException; |
| 20 | import com.android.hotspot2.omadm.OMAParser; |
| 21 | import com.android.hotspot2.osu.OSUCertType; |
| 22 | import com.android.hotspot2.osu.OSUInfo; |
| 23 | import com.android.hotspot2.osu.OSUManager; |
| 24 | import com.android.hotspot2.osu.commands.MOData; |
| 25 | import com.android.hotspot2.pps.HomeSP; |
| 26 | |
| 27 | import org.xml.sax.SAXException; |
| 28 | |
| 29 | import java.io.IOException; |
| 30 | import java.net.URL; |
| 31 | import java.security.GeneralSecurityException; |
| 32 | import java.security.PrivateKey; |
| 33 | import java.security.cert.X509Certificate; |
| 34 | import java.util.ArrayList; |
| 35 | import java.util.Collection; |
| 36 | import java.util.HashMap; |
| 37 | import java.util.List; |
| 38 | import java.util.Map; |
| 39 | |
| 40 | public class WifiNetworkAdapter { |
| 41 | private final Context mContext; |
| 42 | private final OSUManager mOSUManager; |
| 43 | private final Map<String, PasspointConfig> mPasspointConfigs = new HashMap<>(); |
| 44 | |
| 45 | private static class PasspointConfig { |
| 46 | private final WifiConfiguration mWifiConfiguration; |
| 47 | private final MOTree mMOTree; |
| 48 | private final HomeSP mHomeSP; |
| 49 | |
| 50 | private PasspointConfig(WifiConfiguration config) throws IOException, SAXException { |
| 51 | mWifiConfiguration = config; |
| 52 | OMAParser omaParser = new OMAParser(); |
| 53 | mMOTree = omaParser.parse(config.getMoTree(), OMAConstants.PPS_URN); |
| 54 | List<HomeSP> spList = MOManager.buildSPs(mMOTree); |
| 55 | if (spList.size() != 1) { |
| 56 | throw new OMAException("Expected exactly one HomeSP, got " + spList.size()); |
| 57 | } |
| 58 | mHomeSP = spList.iterator().next(); |
| 59 | } |
| 60 | |
| 61 | public WifiConfiguration getWifiConfiguration() { |
| 62 | return mWifiConfiguration; |
| 63 | } |
| 64 | |
| 65 | public HomeSP getHomeSP() { |
| 66 | return mHomeSP; |
| 67 | } |
| 68 | |
| 69 | public MOTree getmMOTree() { |
| 70 | return mMOTree; |
| 71 | } |
| 72 | } |
| 73 | |
| 74 | public WifiNetworkAdapter(Context context, OSUManager osuManager) { |
| 75 | mOSUManager = osuManager; |
| 76 | mContext = context; |
| 77 | } |
| 78 | |
| 79 | public void initialize() { |
| 80 | loadAllSps(); |
| 81 | } |
| 82 | |
| 83 | public void networkConfigChange(WifiConfiguration configuration) { |
| 84 | loadAllSps(); |
| 85 | } |
| 86 | |
| 87 | private void loadAllSps() { |
| 88 | Log.d(OSUManager.TAG, "Loading all SPs"); |
| 89 | WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE); |
| 90 | for (WifiConfiguration config : wifiManager.getPrivilegedConfiguredNetworks()) { |
| 91 | String moTree = config.getMoTree(); |
| 92 | if (moTree != null) { |
| 93 | try { |
| 94 | mPasspointConfigs.put(config.FQDN, new PasspointConfig(config)); |
| 95 | } catch (IOException | SAXException e) { |
| 96 | Log.w(OSUManager.TAG, "Failed to parse MO: " + e); |
| 97 | } |
| 98 | } |
| 99 | } |
| 100 | } |
| 101 | |
| 102 | public Collection<HomeSP> getLoadedSPs() { |
| 103 | List<HomeSP> homeSPs = new ArrayList<>(); |
| 104 | for (PasspointConfig config : mPasspointConfigs.values()) { |
| 105 | homeSPs.add(config.getHomeSP()); |
| 106 | } |
| 107 | return homeSPs; |
| 108 | } |
| 109 | |
| 110 | public MOTree getMOTree(HomeSP homeSP) { |
| 111 | PasspointConfig config = mPasspointConfigs.get(homeSP.getFQDN()); |
| 112 | return config != null ? config.getmMOTree() : null; |
| 113 | } |
| 114 | |
| 115 | public void launchBrowser(URL target, Network network, URL endRedirect) { |
| 116 | Log.d(OSUManager.TAG, "Browser to " + target + ", land at " + endRedirect); |
| 117 | |
| 118 | final Intent intent = new Intent( |
| 119 | ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN); |
| 120 | intent.putExtra(ConnectivityManager.EXTRA_NETWORK, network); |
| 121 | intent.putExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL, |
| 122 | new CaptivePortal(new ICaptivePortal.Stub() { |
| 123 | @Override |
| 124 | public void appResponse(int response) { |
| 125 | } |
| 126 | })); |
| 127 | //intent.setData(Uri.parse(target.toString())); !!! Doesn't work! |
| 128 | intent.putExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_URL, target.toString()); |
| 129 | intent.setFlags( |
| 130 | Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK); |
| 131 | mContext.startActivity(intent); |
| 132 | } |
| 133 | |
| 134 | public HomeSP addSP(MOTree instanceTree) throws IOException, SAXException { |
| 135 | WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE); |
| 136 | String xml = instanceTree.toXml(); |
Peter Qiu | 28a3d445 | 2016-11-14 15:18:09 -0800 | [diff] [blame] | 137 | // TODO(b/32883320): use the new API for adding Passpoint configuration. |
| 138 | return null; |
Jan Nordqvist | ee699a6 | 2016-02-05 15:30:26 -0800 | [diff] [blame] | 139 | } |
| 140 | |
| 141 | public void removeSP(String fqdn) throws IOException { |
| 142 | WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE); |
| 143 | } |
| 144 | |
| 145 | public HomeSP modifySP(HomeSP homeSP, Collection<MOData> mods) |
| 146 | throws IOException { |
| 147 | WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE); |
| 148 | return null; |
| 149 | } |
| 150 | |
| 151 | public Network getCurrentNetwork() { |
| 152 | WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE); |
| 153 | return wifiManager.getCurrentNetwork(); |
| 154 | } |
| 155 | |
| 156 | public WifiConfiguration getActiveWifiConfig() { |
| 157 | WifiInfo wifiInfo = getConnectionInfo(); |
| 158 | if (wifiInfo == null) { |
| 159 | return null; |
| 160 | } |
| 161 | WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE); |
| 162 | for (WifiConfiguration config : wifiManager.getConfiguredNetworks()) { |
| 163 | if (config.networkId == wifiInfo.getNetworkId()) { |
| 164 | return config; |
| 165 | } |
| 166 | } |
| 167 | return null; |
| 168 | } |
| 169 | |
| 170 | public WifiInfo getConnectionInfo() { |
| 171 | WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE); |
| 172 | return wifiManager.getConnectionInfo(); |
| 173 | } |
| 174 | |
| 175 | public PasspointMatch matchProviderWithCurrentNetwork(String fqdn) { |
| 176 | WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE); |
| 177 | int ordinal = wifiManager.matchProviderWithCurrentNetwork(fqdn); |
| 178 | return ordinal >= 0 && ordinal < PasspointMatch.values().length ? |
| 179 | PasspointMatch.values()[ordinal] : null; |
| 180 | } |
| 181 | |
| 182 | public WifiConfiguration getWifiConfig(HomeSP homeSP) { |
| 183 | PasspointConfig passpointConfig = mPasspointConfigs.get(homeSP.getFQDN()); |
| 184 | return passpointConfig != null ? passpointConfig.getWifiConfiguration() : null; |
| 185 | } |
| 186 | |
| 187 | public WifiConfiguration getActivePasspointNetwork() { |
| 188 | PasspointConfig passpointConfig = getActivePasspointConfig(); |
| 189 | return passpointConfig != null ? passpointConfig.getWifiConfiguration() : null; |
| 190 | } |
| 191 | |
| 192 | private PasspointConfig getActivePasspointConfig() { |
| 193 | WifiInfo wifiInfo = getConnectionInfo(); |
| 194 | if (wifiInfo == null) { |
| 195 | return null; |
| 196 | } |
| 197 | |
| 198 | for (PasspointConfig passpointConfig : mPasspointConfigs.values()) { |
| 199 | if (passpointConfig.getWifiConfiguration().networkId == wifiInfo.getNetworkId()) { |
| 200 | return passpointConfig; |
| 201 | } |
| 202 | } |
| 203 | return null; |
| 204 | } |
| 205 | |
| 206 | public HomeSP getCurrentSP() { |
| 207 | PasspointConfig passpointConfig = getActivePasspointConfig(); |
| 208 | return passpointConfig != null ? passpointConfig.getHomeSP() : null; |
| 209 | } |
| 210 | |
| 211 | public void doIconQuery(long bssid, String fileName) { |
| 212 | WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE); |
| 213 | Log.d("ZXZ", String.format("Icon query for %012x '%s'", bssid, fileName)); |
| 214 | wifiManager.queryPasspointIcon(bssid, fileName); |
| 215 | } |
| 216 | |
| 217 | public Integer addNetwork(HomeSP homeSP, Map<OSUCertType, List<X509Certificate>> certs, |
| 218 | PrivateKey privateKey, Network osuNetwork) |
| 219 | throws IOException, GeneralSecurityException { |
| 220 | |
| 221 | List<X509Certificate> aaaTrust = certs.get(OSUCertType.AAA); |
| 222 | if (aaaTrust.isEmpty()) { |
| 223 | aaaTrust = certs.get(OSUCertType.CA); // Get the CAs from the EST flow. |
| 224 | } |
| 225 | |
| 226 | WifiConfiguration config = ConfigBuilder.buildConfig(homeSP, |
| 227 | aaaTrust.iterator().next(), |
| 228 | certs.get(OSUCertType.Client), privateKey); |
| 229 | |
| 230 | WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE); |
| 231 | int nwkId = wifiManager.addNetwork(config); |
| 232 | boolean saved = false; |
| 233 | if (nwkId >= 0) { |
| 234 | saved = wifiManager.saveConfiguration(); |
| 235 | } |
| 236 | Log.d(OSUManager.TAG, "Wifi configuration " + nwkId + |
| 237 | " " + (saved ? "saved" : "not saved")); |
| 238 | |
| 239 | if (saved) { |
| 240 | reconnect(osuNetwork, nwkId); |
| 241 | return nwkId; |
| 242 | } else { |
| 243 | return null; |
| 244 | } |
| 245 | } |
| 246 | |
| 247 | public void updateNetwork(HomeSP homeSP, X509Certificate caCert, |
| 248 | List<X509Certificate> clientCerts, PrivateKey privateKey) |
| 249 | throws IOException, GeneralSecurityException { |
| 250 | |
| 251 | WifiConfiguration config = getWifiConfig(homeSP); |
| 252 | if (config == null) { |
| 253 | throw new IOException("Failed to find matching network config"); |
| 254 | } |
| 255 | Log.d(OSUManager.TAG, "Found matching config " + config.networkId + ", updating"); |
| 256 | |
| 257 | WifiEnterpriseConfig enterpriseConfig = config.enterpriseConfig; |
| 258 | WifiConfiguration newConfig = ConfigBuilder.buildConfig(homeSP, |
| 259 | caCert != null ? caCert : enterpriseConfig.getCaCertificate(), |
| 260 | clientCerts, privateKey); |
| 261 | newConfig.networkId = config.networkId; |
| 262 | |
| 263 | WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE); |
| 264 | wifiManager.save(newConfig, null); |
| 265 | wifiManager.saveConfiguration(); |
| 266 | } |
| 267 | |
| 268 | /** |
| 269 | * Connect to an OSU provisioning network. The connection should not bring down other existing |
| 270 | * connection and the network should not be made the default network since the connection |
| 271 | * is solely for sign up and is neither intended for nor likely provides access to any |
| 272 | * generic resources. |
| 273 | * |
| 274 | * @param osuInfo The OSU info object that defines the parameters for the network. An OSU |
| 275 | * network is either an open network, or, if the OSU NAI is set, an "OSEN" |
| 276 | * network, which is an anonymous EAP-TLS network with special keys. |
| 277 | * @param info An opaque string that is passed on to any user notification. The string is used |
| 278 | * for the name of the service provider. |
| 279 | * @return an Integer holding the network-id of the just added network configuration, or null |
| 280 | * if the network existed prior to this call (was not added by the OSU infrastructure). |
| 281 | * The value will be used at the end of the OSU flow to delete the network as applicable. |
| 282 | * @throws IOException Issues: |
| 283 | * 1. The network id is not returned. addNetwork cannot be called from here since the method |
| 284 | * runs in the context of the app and doesn't have the appropriate permission. |
| 285 | * 2. The connection is not immediately usable if the network was not previously selected |
| 286 | * manually. |
| 287 | */ |
| 288 | public Integer connect(OSUInfo osuInfo, final String info) throws IOException { |
| 289 | WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE); |
| 290 | |
| 291 | WifiConfiguration config = new WifiConfiguration(); |
| 292 | config.SSID = '"' + osuInfo.getSSID() + '"'; |
| 293 | if (osuInfo.getOSUBssid() != 0) { |
| 294 | config.BSSID = Utils.macToString(osuInfo.getOSUBssid()); |
| 295 | Log.d(OSUManager.TAG, String.format("Setting BSSID of '%s' to %012x", |
| 296 | osuInfo.getSSID(), osuInfo.getOSUBssid())); |
| 297 | } |
| 298 | |
| 299 | if (osuInfo.getOSUProvider().getOsuNai() == null) { |
| 300 | config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); |
| 301 | } else { |
| 302 | config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.OSEN); |
| 303 | config.allowedProtocols.set(WifiConfiguration.Protocol.OSEN); |
| 304 | config.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP); |
| 305 | config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.GTK_NOT_USED); |
| 306 | config.enterpriseConfig = new WifiEnterpriseConfig(); |
| 307 | config.enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.UNAUTH_TLS); |
| 308 | config.enterpriseConfig.setIdentity(osuInfo.getOSUProvider().getOsuNai()); |
| 309 | // !!! OSEN CA Cert??? |
| 310 | } |
| 311 | |
| 312 | int networkId = wifiManager.addNetwork(config); |
| 313 | if (wifiManager.enableNetwork(networkId, true)) { |
| 314 | return networkId; |
| 315 | } else { |
| 316 | return null; |
| 317 | } |
| 318 | |
| 319 | /* sequence of addNetwork(), enableNetwork(), saveConfiguration() and reconnect() |
| 320 | wifiManager.connect(config, new WifiManager.ActionListener() { |
| 321 | @Override |
| 322 | public void onSuccess() { |
| 323 | // Connection event comes from network change intent registered in initialize |
| 324 | } |
| 325 | |
| 326 | @Override |
| 327 | public void onFailure(int reason) { |
| 328 | mOSUManager.notifyUser(OSUOperationStatus.ProvisioningFailure, |
| 329 | "Cannot connect to OSU network: " + reason, info); |
| 330 | } |
| 331 | }); |
| 332 | return null; |
| 333 | |
| 334 | /* |
| 335 | try { |
| 336 | int nwkID = wifiManager.addOrUpdateOSUNetwork(config); |
| 337 | if (nwkID == WifiConfiguration.INVALID_NETWORK_ID) { |
| 338 | throw new IOException("Failed to add OSU network"); |
| 339 | } |
| 340 | wifiManager.enableNetwork(nwkID, false); |
| 341 | wifiManager.reconnect(); |
| 342 | return nwkID; |
| 343 | } |
| 344 | catch (SecurityException se) { |
| 345 | Log.d("ZXZ", "Blah: " + se, se); |
| 346 | wifiManager.connect(config, new WifiManager.ActionListener() { |
| 347 | @Override |
| 348 | public void onSuccess() { |
| 349 | // Connection event comes from network change intent registered in initialize |
| 350 | } |
| 351 | |
| 352 | @Override |
| 353 | public void onFailure(int reason) { |
| 354 | mOSUManager.notifyUser(OSUOperationStatus.ProvisioningFailure, |
| 355 | "Cannot connect to OSU network: " + reason, info); |
| 356 | } |
| 357 | }); |
| 358 | return null; |
| 359 | } |
| 360 | */ |
| 361 | } |
| 362 | |
| 363 | private void reconnect(Network osuNetwork, int newNwkId) { |
| 364 | WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE); |
| 365 | if (osuNetwork != null) { |
| 366 | wifiManager.disableNetwork(osuNetwork.netId); |
| 367 | } |
| 368 | if (newNwkId != WifiConfiguration.INVALID_NETWORK_ID) { |
| 369 | wifiManager.enableNetwork(newNwkId, true); |
| 370 | } |
| 371 | } |
| 372 | |
| 373 | public void deleteNetwork(int id) { |
| 374 | WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE); |
| 375 | wifiManager.disableNetwork(id); |
| 376 | wifiManager.forget(id, null); |
| 377 | } |
| 378 | |
| 379 | /** |
| 380 | * Set the re-authentication hold off time for the current network |
| 381 | * |
| 382 | * @param holdoff hold off time in milliseconds |
| 383 | * @param ess set if the hold off pertains to an ESS rather than a BSS |
| 384 | */ |
| 385 | public void setHoldoffTime(long holdoff, boolean ess) { |
| 386 | |
| 387 | } |
| 388 | } |