[go: nahoru, domu]

1package com.android.server.wifi.configparse;
2
3import android.content.Context;
4import android.net.Uri;
5import android.net.wifi.WifiConfiguration;
6import android.net.wifi.WifiEnterpriseConfig;
7import android.provider.DocumentsContract;
8import android.util.Base64;
9import android.util.Log;
10
11import com.android.server.wifi.IMSIParameter;
12import com.android.server.wifi.anqp.eap.AuthParam;
13import com.android.server.wifi.anqp.eap.EAP;
14import com.android.server.wifi.anqp.eap.EAPMethod;
15import com.android.server.wifi.anqp.eap.NonEAPInnerAuth;
16import com.android.server.wifi.hotspot2.omadm.PasspointManagementObjectManager;
17import com.android.server.wifi.hotspot2.pps.Credential;
18import com.android.server.wifi.hotspot2.pps.HomeSP;
19
20import org.xml.sax.SAXException;
21
22import java.io.ByteArrayInputStream;
23import java.io.IOException;
24import java.io.InputStreamReader;
25import java.io.LineNumberReader;
26import java.nio.charset.StandardCharsets;
27import java.security.GeneralSecurityException;
28import java.security.KeyStore;
29import java.security.MessageDigest;
30import java.security.PrivateKey;
31import java.security.cert.Certificate;
32import java.security.cert.CertificateFactory;
33import java.security.cert.X509Certificate;
34import java.util.ArrayList;
35import java.util.Arrays;
36import java.util.Enumeration;
37import java.util.HashSet;
38import java.util.List;
39
40public class ConfigBuilder {
41    public static final String WifiConfigType = "application/x-wifi-config";
42    private static final String ProfileTag = "application/x-passpoint-profile";
43    private static final String KeyTag = "application/x-pkcs12";
44    private static final String CATag = "application/x-x509-ca-cert";
45
46    private static final String X509 = "X.509";
47
48    private static final String TAG = "WCFG";
49
50    public static WifiConfiguration buildConfig(String uriString, byte[] data, Context context)
51            throws IOException, GeneralSecurityException, SAXException {
52        Log.d(TAG, "Content: " + (data != null ? data.length : -1));
53
54        byte[] b64 = Base64.decode(new String(data, StandardCharsets.ISO_8859_1), Base64.DEFAULT);
55        Log.d(TAG, "Decoded: " + b64.length + " bytes.");
56
57        dropFile(Uri.parse(uriString), context);
58
59        MIMEContainer mimeContainer = new
60                MIMEContainer(new LineNumberReader(
61                new InputStreamReader(new ByteArrayInputStream(b64), StandardCharsets.ISO_8859_1)),
62                null);
63        if (!mimeContainer.isBase64()) {
64            throw new IOException("Encoding for " +
65                    mimeContainer.getContentType() + " is not base64");
66        }
67        MIMEContainer inner;
68        if (mimeContainer.getContentType().equals(WifiConfigType)) {
69            byte[] wrappedContent = Base64.decode(mimeContainer.getText(), Base64.DEFAULT);
70            Log.d(TAG, "Building container from '" +
71                    new String(wrappedContent, StandardCharsets.ISO_8859_1) + "'");
72            inner = new MIMEContainer(new LineNumberReader(
73                    new InputStreamReader(new ByteArrayInputStream(wrappedContent),
74                            StandardCharsets.ISO_8859_1)), null);
75        }
76        else {
77            inner = mimeContainer;
78        }
79        return parse(inner);
80    }
81
82    private static void dropFile(Uri uri, Context context) {
83        if (DocumentsContract.isDocumentUri(context, uri)) {
84            DocumentsContract.deleteDocument(context.getContentResolver(), uri);
85        } else {
86            context.getContentResolver().delete(uri, null, null);
87        }
88    }
89
90    private static WifiConfiguration parse(MIMEContainer root)
91            throws IOException, GeneralSecurityException, SAXException {
92
93        if (root.getMimeContainers() == null) {
94            throw new IOException("Malformed MIME content: not multipart");
95        }
96
97        String moText = null;
98        X509Certificate caCert = null;
99        PrivateKey clientKey = null;
100        List<X509Certificate> clientChain = null;
101
102        for (MIMEContainer subContainer : root.getMimeContainers()) {
103            Log.d(TAG, " + Content Type: " + subContainer.getContentType());
104            switch (subContainer.getContentType()) {
105                case ProfileTag:
106                    if (subContainer.isBase64()) {
107                        byte[] octets = Base64.decode(subContainer.getText(), Base64.DEFAULT);
108                        moText = new String(octets, StandardCharsets.UTF_8);
109                    } else {
110                        moText = subContainer.getText();
111                    }
112                    Log.d(TAG, "OMA: " + moText);
113                    break;
114                case CATag: {
115                    if (!subContainer.isBase64()) {
116                        throw new IOException("Can't read non base64 encoded cert");
117                    }
118
119                    byte[] octets = Base64.decode(subContainer.getText(), Base64.DEFAULT);
120                    CertificateFactory factory = CertificateFactory.getInstance(X509);
121                    caCert = (X509Certificate) factory.generateCertificate(
122                            new ByteArrayInputStream(octets));
123                    Log.d(TAG, "Cert subject " + caCert.getSubjectX500Principal());
124                    Log.d(TAG, "Full Cert: " + caCert);
125                    break;
126                }
127                case KeyTag: {
128                    if (!subContainer.isBase64()) {
129                        throw new IOException("Can't read non base64 encoded key");
130                    }
131
132                    byte[] octets = Base64.decode(subContainer.getText(), Base64.DEFAULT);
133
134                    KeyStore ks = KeyStore.getInstance("PKCS12");
135                    ByteArrayInputStream in = new ByteArrayInputStream(octets);
136                    ks.load(in, new char[0]);
137                    in.close();
138                    Log.d(TAG, "---- Start PKCS12 info " + octets.length + ", size " + ks.size());
139                    Enumeration<String> aliases = ks.aliases();
140                    while (aliases.hasMoreElements()) {
141                        String alias = aliases.nextElement();
142                        clientKey = (PrivateKey) ks.getKey(alias, null);
143                        Log.d(TAG, "Key: " + clientKey.getFormat());
144                        Certificate[] chain = ks.getCertificateChain(alias);
145                        if (chain != null) {
146                            clientChain = new ArrayList<>();
147                            for (Certificate certificate : chain) {
148                                if (!(certificate instanceof X509Certificate)) {
149                                    Log.w(TAG, "Element in cert chain is not an X509Certificate: " +
150                                            certificate.getClass());
151                                }
152                                clientChain.add((X509Certificate) certificate);
153                            }
154                            Log.d(TAG, "Chain: " + clientChain.size());
155                        }
156                    }
157                    Log.d(TAG, "---- End PKCS12 info.");
158                    break;
159                }
160            }
161        }
162
163        if (moText == null) {
164            throw new IOException("Missing profile");
165        }
166
167        HomeSP homeSP = PasspointManagementObjectManager.buildSP(moText);
168
169        return buildConfig(homeSP, caCert, clientChain, clientKey);
170    }
171
172    private static WifiConfiguration buildConfig(HomeSP homeSP, X509Certificate caCert,
173                                                 List<X509Certificate> clientChain, PrivateKey key)
174            throws IOException, GeneralSecurityException {
175
176        Credential credential = homeSP.getCredential();
177
178        WifiConfiguration config;
179
180        EAP.EAPMethodID eapMethodID = credential.getEAPMethod().getEAPMethodID();
181        switch (eapMethodID) {
182            case EAP_TTLS:
183                if (key != null || clientChain != null) {
184                    Log.w(TAG, "Client cert and/or key included with EAP-TTLS profile");
185                }
186                config = buildTTLSConfig(homeSP);
187                break;
188            case EAP_TLS:
189                config = buildTLSConfig(homeSP, clientChain, key);
190                break;
191            case EAP_AKA:
192            case EAP_AKAPrim:
193            case EAP_SIM:
194                if (key != null || clientChain != null || caCert != null) {
195                    Log.i(TAG, "Client/CA cert and/or key included with " +
196                            eapMethodID + " profile");
197                }
198                config = buildSIMConfig(homeSP);
199                break;
200            default:
201                throw new IOException("Unsupported EAP Method: " + eapMethodID);
202        }
203
204        WifiEnterpriseConfig enterpriseConfig = config.enterpriseConfig;
205
206        enterpriseConfig.setCaCertificate(caCert);
207        enterpriseConfig.setAnonymousIdentity("anonymous@" + credential.getRealm());
208
209        return config;
210    }
211
212    // Retain for debugging purposes
213    /*
214    private static void xIterateCerts(KeyStore ks, X509Certificate caCert)
215            throws GeneralSecurityException {
216        Enumeration<String> aliases = ks.aliases();
217        while (aliases.hasMoreElements()) {
218            String alias = aliases.nextElement();
219            Certificate cert = ks.getCertificate(alias);
220            Log.d("HS2J", "Checking " + alias);
221            if (cert instanceof X509Certificate) {
222                X509Certificate x509Certificate = (X509Certificate) cert;
223                boolean sm = x509Certificate.getSubjectX500Principal().equals(
224                        caCert.getSubjectX500Principal());
225                boolean eq = false;
226                if (sm) {
227                    eq = Arrays.equals(x509Certificate.getEncoded(), caCert.getEncoded());
228                }
229                Log.d("HS2J", "Subject: " + x509Certificate.getSubjectX500Principal() +
230                        ": " + sm + "/" + eq);
231            }
232        }
233    }
234    */
235
236    private static WifiConfiguration buildTTLSConfig(HomeSP homeSP)
237            throws IOException {
238        Credential credential = homeSP.getCredential();
239
240        if (credential.getUserName() == null || credential.getPassword() == null) {
241            throw new IOException("EAP-TTLS provisioned without user name or password");
242        }
243
244        EAPMethod eapMethod = credential.getEAPMethod();
245
246        AuthParam authParam = eapMethod.getAuthParam();
247        if (authParam == null ||
248                authParam.getAuthInfoID() != EAP.AuthInfoID.NonEAPInnerAuthType) {
249            throw new IOException("Bad auth parameter for EAP-TTLS: " + authParam);
250        }
251
252        WifiConfiguration config = buildBaseConfiguration(homeSP);
253        NonEAPInnerAuth ttlsParam = (NonEAPInnerAuth) authParam;
254        WifiEnterpriseConfig enterpriseConfig = config.enterpriseConfig;
255        enterpriseConfig.setPhase2Method(remapInnerMethod(ttlsParam.getType()));
256        enterpriseConfig.setIdentity(credential.getUserName());
257        enterpriseConfig.setPassword(credential.getPassword());
258
259        return config;
260    }
261
262    private static WifiConfiguration buildTLSConfig(HomeSP homeSP,
263                                                    List<X509Certificate> clientChain,
264                                                    PrivateKey clientKey)
265            throws IOException, GeneralSecurityException {
266
267        Credential credential = homeSP.getCredential();
268
269        X509Certificate clientCertificate = null;
270
271        if (clientKey == null || clientChain == null) {
272            throw new IOException("No key and/or cert passed for EAP-TLS");
273        }
274        if (credential.getCertType() != Credential.CertType.x509v3) {
275            throw new IOException("Invalid certificate type for TLS: " +
276                    credential.getCertType());
277        }
278
279        byte[] reference = credential.getFingerPrint();
280        MessageDigest digester = MessageDigest.getInstance("SHA-256");
281        for (X509Certificate certificate : clientChain) {
282            digester.reset();
283            byte[] fingerprint = digester.digest(certificate.getEncoded());
284            if (Arrays.equals(reference, fingerprint)) {
285                clientCertificate = certificate;
286                break;
287            }
288        }
289        if (clientCertificate == null) {
290            throw new IOException("No certificate in chain matches supplied fingerprint");
291        }
292
293        String alias = Base64.encodeToString(reference, Base64.DEFAULT);
294
295        WifiConfiguration config = buildBaseConfiguration(homeSP);
296        WifiEnterpriseConfig enterpriseConfig = config.enterpriseConfig;
297        enterpriseConfig.setClientCertificateAlias(alias);
298        enterpriseConfig.setClientKeyEntry(clientKey, clientCertificate);
299
300        return config;
301    }
302
303    private static WifiConfiguration buildSIMConfig(HomeSP homeSP)
304            throws IOException {
305
306        Credential credential = homeSP.getCredential();
307        IMSIParameter credImsi = credential.getImsi();
308
309        /*
310         * Uncomment to enforce strict IMSI matching with currently installed SIM cards.
311         *
312        TelephonyManager tm = TelephonyManager.from(context);
313        SubscriptionManager sub = SubscriptionManager.from(context);
314        boolean match = false;
315
316        for (int subId : sub.getActiveSubscriptionIdList()) {
317            String imsi = tm.getSubscriberId(subId);
318            if (credImsi.matches(imsi)) {
319                match = true;
320                break;
321            }
322        }
323        if (!match) {
324            throw new IOException("Supplied IMSI does not match any SIM card");
325        }
326        */
327
328        WifiConfiguration config = buildBaseConfiguration(homeSP);
329        config.enterpriseConfig.setPlmn(credImsi.toString());
330        return config;
331    }
332
333    private static WifiConfiguration buildBaseConfiguration(HomeSP homeSP) throws IOException {
334        EAP.EAPMethodID eapMethodID = homeSP.getCredential().getEAPMethod().getEAPMethodID();
335
336        WifiConfiguration config = new WifiConfiguration();
337
338        config.FQDN = homeSP.getFQDN();
339
340        HashSet<Long> roamingConsortiumIds = homeSP.getRoamingConsortiums();
341        config.roamingConsortiumIds = new long[roamingConsortiumIds.size()];
342        int i = 0;
343        for (long id : roamingConsortiumIds) {
344            config.roamingConsortiumIds[i] = id;
345            i++;
346        }
347        config.providerFriendlyName = homeSP.getFriendlyName();
348
349        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
350        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.IEEE8021X);
351
352        WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
353        enterpriseConfig.setEapMethod(remapEAPMethod(eapMethodID));
354        enterpriseConfig.setRealm(homeSP.getCredential().getRealm());
355        config.enterpriseConfig = enterpriseConfig;
356        // The framework based config builder only ever builds r1 configs:
357        config.updateIdentifier = null;
358
359        return config;
360    }
361
362    private static int remapEAPMethod(EAP.EAPMethodID eapMethodID) throws IOException {
363        switch (eapMethodID) {
364            case EAP_TTLS:
365                return WifiEnterpriseConfig.Eap.TTLS;
366            case EAP_TLS:
367                return WifiEnterpriseConfig.Eap.TLS;
368            case EAP_SIM:
369                return WifiEnterpriseConfig.Eap.SIM;
370            case EAP_AKA:
371                return WifiEnterpriseConfig.Eap.AKA;
372            case EAP_AKAPrim:
373                return WifiEnterpriseConfig.Eap.AKA_PRIME;
374            default:
375                throw new IOException("Bad EAP method: " + eapMethodID);
376        }
377    }
378
379    private static int remapInnerMethod(NonEAPInnerAuth.NonEAPType type) throws IOException {
380        switch (type) {
381            case PAP:
382                return WifiEnterpriseConfig.Phase2.PAP;
383            case MSCHAP:
384                return WifiEnterpriseConfig.Phase2.MSCHAP;
385            case MSCHAPv2:
386                return WifiEnterpriseConfig.Phase2.MSCHAPV2;
387            case CHAP:
388            default:
389                throw new IOException("Inner method " + type + " not supported");
390        }
391    }
392}
393