1package com.android.hotspot2;
2
3import android.content.Context;
4import android.content.Intent;
5import android.net.CaptivePortal;
6import android.net.ConnectivityManager;
7import android.net.ICaptivePortal;
8import android.net.Network;
9import android.net.wifi.WifiConfiguration;
10import android.net.wifi.WifiEnterpriseConfig;
11import android.net.wifi.WifiInfo;
12import android.net.wifi.WifiManager;
13import android.util.Log;
14
15import com.android.configparse.ConfigBuilder;
16import com.android.hotspot2.omadm.MOManager;
17import com.android.hotspot2.omadm.MOTree;
18import com.android.hotspot2.omadm.OMAConstants;
19import com.android.hotspot2.omadm.OMAException;
20import com.android.hotspot2.omadm.OMAParser;
21import com.android.hotspot2.osu.OSUCertType;
22import com.android.hotspot2.osu.OSUInfo;
23import com.android.hotspot2.osu.OSUManager;
24import com.android.hotspot2.osu.commands.MOData;
25import com.android.hotspot2.pps.HomeSP;
26
27import org.xml.sax.SAXException;
28
29import java.io.IOException;
30import java.net.URL;
31import java.security.GeneralSecurityException;
32import java.security.PrivateKey;
33import java.security.cert.X509Certificate;
34import java.util.ArrayList;
35import java.util.Collection;
36import java.util.HashMap;
37import java.util.List;
38import java.util.Map;
39
40public 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();
137        wifiManager.addPasspointManagementObject(xml);
138        return MOManager.buildSP(xml);
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}
389