1package com.android.configparse;
2
3import android.content.Context;
4import android.net.Uri;
5import android.net.wifi.WifiConfiguration;
6import android.net.wifi.WifiEnterpriseConfig;
7import android.util.Base64;
8import android.util.Log;
9
10import com.android.anqp.eap.AuthParam;
11import com.android.anqp.eap.EAP;
12import com.android.anqp.eap.EAPMethod;
13import com.android.anqp.eap.NonEAPInnerAuth;
14import com.android.hotspot2.IMSIParameter;
15import com.android.hotspot2.pps.Credential;
16import com.android.hotspot2.pps.HomeSP;
17
18import java.io.IOException;
19import java.security.GeneralSecurityException;
20import java.security.MessageDigest;
21import java.security.PrivateKey;
22import java.security.cert.X509Certificate;
23import java.util.Arrays;
24import java.util.HashSet;
25import java.util.List;
26
27public class ConfigBuilder {
28    private static final String TAG = "WCFG";
29
30    private static void dropFile(Uri uri, Context context) {
31        context.getContentResolver().delete(uri, null, null);
32    }
33
34    public static WifiConfiguration buildConfig(HomeSP homeSP, X509Certificate caCert,
35                                                 List<X509Certificate> clientChain, PrivateKey key)
36            throws IOException, GeneralSecurityException {
37
38        Credential credential = homeSP.getCredential();
39
40        WifiConfiguration config;
41
42        EAP.EAPMethodID eapMethodID = credential.getEAPMethod().getEAPMethodID();
43        switch (eapMethodID) {
44            case EAP_TTLS:
45                if (key != null || clientChain != null) {
46                    Log.w(TAG, "Client cert and/or key included with EAP-TTLS profile");
47                }
48                config = buildTTLSConfig(homeSP);
49                break;
50            case EAP_TLS:
51                config = buildTLSConfig(homeSP, clientChain, key);
52                break;
53            case EAP_AKA:
54            case EAP_AKAPrim:
55            case EAP_SIM:
56                if (key != null || clientChain != null || caCert != null) {
57                    Log.i(TAG, "Client/CA cert and/or key included with " +
58                            eapMethodID + " profile");
59                }
60                config = buildSIMConfig(homeSP);
61                break;
62            default:
63                throw new IOException("Unsupported EAP Method: " + eapMethodID);
64        }
65
66        WifiEnterpriseConfig enterpriseConfig = config.enterpriseConfig;
67
68        enterpriseConfig.setCaCertificate(caCert);
69        enterpriseConfig.setAnonymousIdentity("anonymous@" + credential.getRealm());
70
71        return config;
72    }
73
74    // Retain for debugging purposes
75    /*
76    private static void xIterateCerts(KeyStore ks, X509Certificate caCert)
77            throws GeneralSecurityException {
78        Enumeration<String> aliases = ks.aliases();
79        while (aliases.hasMoreElements()) {
80            String alias = aliases.nextElement();
81            Certificate cert = ks.getCertificate(alias);
82            Log.d("HS2J", "Checking " + alias);
83            if (cert instanceof X509Certificate) {
84                X509Certificate x509Certificate = (X509Certificate) cert;
85                boolean sm = x509Certificate.getSubjectX500Principal().equals(
86                        caCert.getSubjectX500Principal());
87                boolean eq = false;
88                if (sm) {
89                    eq = Arrays.equals(x509Certificate.getEncoded(), caCert.getEncoded());
90                }
91                Log.d("HS2J", "Subject: " + x509Certificate.getSubjectX500Principal() +
92                        ": " + sm + "/" + eq);
93            }
94        }
95    }
96    */
97
98    private static WifiConfiguration buildTTLSConfig(HomeSP homeSP)
99            throws IOException {
100        Credential credential = homeSP.getCredential();
101
102        if (credential.getUserName() == null || credential.getPassword() == null) {
103            throw new IOException("EAP-TTLS provisioned without user name or password");
104        }
105
106        EAPMethod eapMethod = credential.getEAPMethod();
107
108        AuthParam authParam = eapMethod.getAuthParam();
109        if (authParam == null ||
110                authParam.getAuthInfoID() != EAP.AuthInfoID.NonEAPInnerAuthType) {
111            throw new IOException("Bad auth parameter for EAP-TTLS: " + authParam);
112        }
113
114        WifiConfiguration config = buildBaseConfiguration(homeSP);
115        NonEAPInnerAuth ttlsParam = (NonEAPInnerAuth) authParam;
116        WifiEnterpriseConfig enterpriseConfig = config.enterpriseConfig;
117        enterpriseConfig.setPhase2Method(remapInnerMethod(ttlsParam.getType()));
118        enterpriseConfig.setIdentity(credential.getUserName());
119        enterpriseConfig.setPassword(credential.getPassword());
120
121        return config;
122    }
123
124    private static WifiConfiguration buildTLSConfig(HomeSP homeSP,
125                                                    List<X509Certificate> clientChain,
126                                                    PrivateKey clientKey)
127            throws IOException, GeneralSecurityException {
128
129        Credential credential = homeSP.getCredential();
130
131        X509Certificate clientCertificate = null;
132
133        if (clientKey == null || clientChain == null) {
134            throw new IOException("No key and/or cert passed for EAP-TLS");
135        }
136        if (credential.getCertType() != Credential.CertType.x509v3) {
137            throw new IOException("Invalid certificate type for TLS: " +
138                    credential.getCertType());
139        }
140
141        byte[] reference = credential.getFingerPrint();
142        MessageDigest digester = MessageDigest.getInstance("SHA-256");
143        for (X509Certificate certificate : clientChain) {
144            digester.reset();
145            byte[] fingerprint = digester.digest(certificate.getEncoded());
146            if (Arrays.equals(reference, fingerprint)) {
147                clientCertificate = certificate;
148                break;
149            }
150        }
151        if (clientCertificate == null) {
152            throw new IOException("No certificate in chain matches supplied fingerprint");
153        }
154
155        String alias = Base64.encodeToString(reference, Base64.DEFAULT);
156
157        WifiConfiguration config = buildBaseConfiguration(homeSP);
158        WifiEnterpriseConfig enterpriseConfig = config.enterpriseConfig;
159        enterpriseConfig.setClientCertificateAlias(alias);
160        enterpriseConfig.setClientKeyEntry(clientKey, clientCertificate);
161
162        return config;
163    }
164
165    private static WifiConfiguration buildSIMConfig(HomeSP homeSP)
166            throws IOException {
167
168        Credential credential = homeSP.getCredential();
169        IMSIParameter credImsi = credential.getImsi();
170
171        /*
172         * Uncomment to enforce strict IMSI matching with currently installed SIM cards.
173         *
174        TelephonyManager tm = TelephonyManager.from(context);
175        SubscriptionManager sub = SubscriptionManager.from(context);
176        boolean match = false;
177
178        for (int subId : sub.getActiveSubscriptionIdList()) {
179            String imsi = tm.getSubscriberId(subId);
180            if (credImsi.matches(imsi)) {
181                match = true;
182                break;
183            }
184        }
185        if (!match) {
186            throw new IOException("Supplied IMSI does not match any SIM card");
187        }
188        */
189
190        WifiConfiguration config = buildBaseConfiguration(homeSP);
191        config.enterpriseConfig.setPlmn(credImsi.toString());
192        return config;
193    }
194
195    private static WifiConfiguration buildBaseConfiguration(HomeSP homeSP) throws IOException {
196        EAP.EAPMethodID eapMethodID = homeSP.getCredential().getEAPMethod().getEAPMethodID();
197
198        WifiConfiguration config = new WifiConfiguration();
199
200        config.FQDN = homeSP.getFQDN();
201
202        HashSet<Long> roamingConsortiumIds = homeSP.getRoamingConsortiums();
203        config.roamingConsortiumIds = new long[roamingConsortiumIds.size()];
204        int i = 0;
205        for (long id : roamingConsortiumIds) {
206            config.roamingConsortiumIds[i] = id;
207            i++;
208        }
209        config.providerFriendlyName = homeSP.getFriendlyName();
210
211        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
212        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.IEEE8021X);
213
214        WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
215        enterpriseConfig.setEapMethod(remapEAPMethod(eapMethodID));
216        enterpriseConfig.setRealm(homeSP.getCredential().getRealm());
217        if (homeSP.getUpdateIdentifier() >= 0) {
218            config.updateIdentifier = Integer.toString(homeSP.getUpdateIdentifier());
219        }
220        config.enterpriseConfig = enterpriseConfig;
221        if (homeSP.getUpdateIdentifier() >= 0) {
222            config.updateIdentifier = Integer.toString(homeSP.getUpdateIdentifier());
223        }
224
225        return config;
226    }
227
228    private static int remapEAPMethod(EAP.EAPMethodID eapMethodID) throws IOException {
229        switch (eapMethodID) {
230            case EAP_TTLS:
231                return WifiEnterpriseConfig.Eap.TTLS;
232            case EAP_TLS:
233                return WifiEnterpriseConfig.Eap.TLS;
234            case EAP_SIM:
235                return WifiEnterpriseConfig.Eap.SIM;
236            case EAP_AKA:
237                return WifiEnterpriseConfig.Eap.AKA;
238            case EAP_AKAPrim:
239                return WifiEnterpriseConfig.Eap.AKA_PRIME;
240            default:
241                throw new IOException("Bad EAP method: " + eapMethodID);
242        }
243    }
244
245    private static int remapInnerMethod(NonEAPInnerAuth.NonEAPType type) throws IOException {
246        switch (type) {
247            case PAP:
248                return WifiEnterpriseConfig.Phase2.PAP;
249            case MSCHAP:
250                return WifiEnterpriseConfig.Phase2.MSCHAP;
251            case MSCHAPv2:
252                return WifiEnterpriseConfig.Phase2.MSCHAPV2;
253            case CHAP:
254            default:
255                throw new IOException("Inner method " + type + " not supported");
256        }
257    }
258}
259