PasspointProvider.java revision 2aa7cc7c13207c3afa3ef428ec1c8cdc1893c50c
1/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.wifi.hotspot2;
18
19import android.net.wifi.EAPConstants;
20import android.net.wifi.WifiConfiguration;
21import android.net.wifi.WifiEnterpriseConfig;
22import android.net.wifi.hotspot2.PasspointConfiguration;
23import android.net.wifi.hotspot2.pps.Credential;
24import android.net.wifi.hotspot2.pps.Credential.SimCredential;
25import android.net.wifi.hotspot2.pps.Credential.UserCredential;
26import android.net.wifi.hotspot2.pps.HomeSp;
27import android.security.Credentials;
28import android.text.TextUtils;
29import android.util.Base64;
30import android.util.Log;
31
32import com.android.server.wifi.IMSIParameter;
33import com.android.server.wifi.SIMAccessor;
34import com.android.server.wifi.WifiKeyStore;
35import com.android.server.wifi.hotspot2.anqp.ANQPElement;
36import com.android.server.wifi.hotspot2.anqp.Constants.ANQPElementType;
37import com.android.server.wifi.hotspot2.anqp.DomainNameElement;
38import com.android.server.wifi.hotspot2.anqp.NAIRealmElement;
39import com.android.server.wifi.hotspot2.anqp.RoamingConsortiumElement;
40import com.android.server.wifi.hotspot2.anqp.ThreeGPPNetworkElement;
41import com.android.server.wifi.hotspot2.anqp.eap.AuthParam;
42import com.android.server.wifi.hotspot2.anqp.eap.NonEAPInnerAuth;
43
44import java.nio.charset.StandardCharsets;
45import java.security.MessageDigest;
46import java.security.NoSuchAlgorithmException;
47import java.security.cert.CertificateEncodingException;
48import java.security.cert.X509Certificate;
49import java.util.Arrays;
50import java.util.List;
51import java.util.Map;
52import java.util.Objects;
53
54/**
55 * Abstraction for Passpoint service provider.  This class contains the both static
56 * Passpoint configuration data and the runtime data (e.g. blacklisted SSIDs, statistics).
57 */
58public class PasspointProvider {
59    private static final String TAG = "PasspointProvider";
60
61    /**
62     * Used as part of alias string for certificates and keys.  The alias string is in the format
63     * of: [KEY_TYPE]_HS2_[ProviderID]
64     * For example: "CACERT_HS2_0", "USRCERT_HS2_0", "USRPKEY_HS2_0"
65     */
66    private static final String ALIAS_HS_TYPE = "HS2_";
67
68    private final PasspointConfiguration mConfig;
69    private final WifiKeyStore mKeyStore;
70
71    /**
72     * Aliases for the private keys and certificates installed in the keystore.  Each alias
73     * is a suffix of the actual certificate or key name installed in the keystore.  The
74     * certificate or key name in the keystore is consist of |Type|_|alias|.
75     * This will be consistent with the usage of the term "alias" in {@link WifiEnterpriseConfig}.
76     */
77    private String mCaCertificateAlias;
78    private String mClientPrivateKeyAlias;
79    private String mClientCertificateAlias;
80
81    private final long mProviderId;
82
83    private final IMSIParameter mImsiParameter;
84    private final List<String> mMatchingSIMImsiList;
85
86    private final int mEAPMethodID;
87    private final AuthParam mAuthParam;
88
89    public PasspointProvider(PasspointConfiguration config, WifiKeyStore keyStore,
90            SIMAccessor simAccessor, long providerId) {
91        this(config, keyStore, simAccessor, providerId, null, null, null);
92    }
93
94    public PasspointProvider(PasspointConfiguration config, WifiKeyStore keyStore,
95            SIMAccessor simAccessor, long providerId, String caCertificateAlias,
96            String clientCertificateAlias, String clientPrivateKeyAlias) {
97        // Maintain a copy of the configuration to avoid it being updated by others.
98        mConfig = new PasspointConfiguration(config);
99        mKeyStore = keyStore;
100        mProviderId = providerId;
101        mCaCertificateAlias = caCertificateAlias;
102        mClientCertificateAlias = clientCertificateAlias;
103        mClientPrivateKeyAlias = clientPrivateKeyAlias;
104
105        // Setup EAP method and authentication parameter based on the credential.
106        if (mConfig.getCredential().getUserCredential() != null) {
107            mEAPMethodID = EAPConstants.EAP_TTLS;
108            mAuthParam = new NonEAPInnerAuth(NonEAPInnerAuth.getAuthTypeID(
109                    mConfig.getCredential().getUserCredential().getNonEapInnerMethod()));
110            mImsiParameter = null;
111            mMatchingSIMImsiList = null;
112        } else if (mConfig.getCredential().getCertCredential() != null) {
113            mEAPMethodID = EAPConstants.EAP_TLS;
114            mAuthParam = null;
115            mImsiParameter = null;
116            mMatchingSIMImsiList = null;
117        } else {
118            mEAPMethodID = mConfig.getCredential().getSimCredential().getEapType();
119            mAuthParam = null;
120            mImsiParameter = IMSIParameter.build(
121                    mConfig.getCredential().getSimCredential().getImsi());
122            mMatchingSIMImsiList = simAccessor.getMatchingImsis(mImsiParameter);
123        }
124    }
125
126    public PasspointConfiguration getConfig() {
127        // Return a copy of the configuration to avoid it being updated by others.
128        return new PasspointConfiguration(mConfig);
129    }
130
131    public String getCaCertificateAlias() {
132        return mCaCertificateAlias;
133    }
134
135    public String getClientPrivateKeyAlias() {
136        return mClientPrivateKeyAlias;
137    }
138
139    public String getClientCertificateAlias() {
140        return mClientCertificateAlias;
141    }
142
143    public long getProviderId() {
144        return mProviderId;
145    }
146
147    /**
148     * Install certificates and key based on current configuration.
149     * Note: the certificates and keys in the configuration will get cleared once
150     * they're installed in the keystore.
151     *
152     * @return true on success
153     */
154    public boolean installCertsAndKeys() {
155        // Install CA certificate.
156        if (mConfig.getCredential().getCaCertificate() != null) {
157            String certName = Credentials.CA_CERTIFICATE + ALIAS_HS_TYPE + mProviderId;
158            if (!mKeyStore.putCertInKeyStore(certName,
159                    mConfig.getCredential().getCaCertificate())) {
160                Log.e(TAG, "Failed to install CA Certificate");
161                uninstallCertsAndKeys();
162                return false;
163            }
164            mCaCertificateAlias = ALIAS_HS_TYPE + mProviderId;
165        }
166
167        // Install the client private key.
168        if (mConfig.getCredential().getClientPrivateKey() != null) {
169            String keyName = Credentials.USER_PRIVATE_KEY + ALIAS_HS_TYPE + mProviderId;
170            if (!mKeyStore.putKeyInKeyStore(keyName,
171                    mConfig.getCredential().getClientPrivateKey())) {
172                Log.e(TAG, "Failed to install client private key");
173                uninstallCertsAndKeys();
174                return false;
175            }
176            mClientPrivateKeyAlias = ALIAS_HS_TYPE + mProviderId;
177        }
178
179        // Install the client certificate.
180        if (mConfig.getCredential().getClientCertificateChain() != null) {
181            X509Certificate clientCert = getClientCertificate(
182                    mConfig.getCredential().getClientCertificateChain(),
183                    mConfig.getCredential().getCertCredential().getCertSha256Fingerprint());
184            if (clientCert == null) {
185                Log.e(TAG, "Failed to locate client certificate");
186                uninstallCertsAndKeys();
187                return false;
188            }
189            String certName = Credentials.USER_CERTIFICATE + ALIAS_HS_TYPE + mProviderId;
190            if (!mKeyStore.putCertInKeyStore(certName, clientCert)) {
191                Log.e(TAG, "Failed to install client certificate");
192                uninstallCertsAndKeys();
193                return false;
194            }
195            mClientCertificateAlias = ALIAS_HS_TYPE + mProviderId;
196        }
197
198        // Clear the keys and certificates in the configuration.
199        mConfig.getCredential().setCaCertificate(null);
200        mConfig.getCredential().setClientPrivateKey(null);
201        mConfig.getCredential().setClientCertificateChain(null);
202        return true;
203    }
204
205    /**
206     * Remove any installed certificates and key.
207     */
208    public void uninstallCertsAndKeys() {
209        if (mCaCertificateAlias != null) {
210            if (!mKeyStore.removeEntryFromKeyStore(
211                    Credentials.CA_CERTIFICATE + mCaCertificateAlias)) {
212                Log.e(TAG, "Failed to remove entry: " + mCaCertificateAlias);
213            }
214            mCaCertificateAlias = null;
215        }
216        if (mClientPrivateKeyAlias != null) {
217            if (!mKeyStore.removeEntryFromKeyStore(
218                    Credentials.USER_PRIVATE_KEY + mClientPrivateKeyAlias)) {
219                Log.e(TAG, "Failed to remove entry: " + mClientPrivateKeyAlias);
220            }
221            mClientPrivateKeyAlias = null;
222        }
223        if (mClientCertificateAlias != null) {
224            if (!mKeyStore.removeEntryFromKeyStore(
225                    Credentials.USER_CERTIFICATE + mClientCertificateAlias)) {
226                Log.e(TAG, "Failed to remove entry: " + mClientCertificateAlias);
227            }
228            mClientCertificateAlias = null;
229        }
230    }
231
232    /**
233     * Return the matching status with the given AP, based on the ANQP elements from the AP.
234     *
235     * @param anqpElements ANQP elements from the AP
236     * @return {@link PasspointMatch}
237     */
238    public PasspointMatch match(Map<ANQPElementType, ANQPElement> anqpElements) {
239        PasspointMatch providerMatch = matchProvider(anqpElements);
240
241        // Perform authentication match against the NAI Realm.
242        int authMatch = ANQPMatcher.matchNAIRealm(
243                (NAIRealmElement) anqpElements.get(ANQPElementType.ANQPNAIRealm),
244                mConfig.getCredential().getRealm(), mEAPMethodID, mAuthParam);
245
246        // Auth mismatch, demote provider match.
247        if (authMatch == AuthMatch.NONE) {
248            return PasspointMatch.None;
249        }
250
251        // No realm match, return provider match as is.
252        if ((authMatch & AuthMatch.REALM) == 0) {
253            return providerMatch;
254        }
255
256        // Realm match, promote provider match to roaming if no other provider match is found.
257        return providerMatch == PasspointMatch.None ? PasspointMatch.RoamingProvider
258                : providerMatch;
259    }
260
261    /**
262     * Generate a WifiConfiguration based on the provider's configuration.  The generated
263     * WifiConfiguration will include all the necessary credentials for network connection except
264     * the SSID, which should be added by the caller when the config is being used for network
265     * connection.
266     *
267     * @return {@link WifiConfiguration}
268     */
269    public WifiConfiguration getWifiConfig() {
270        WifiConfiguration wifiConfig = new WifiConfiguration();
271        wifiConfig.FQDN = mConfig.getHomeSp().getFqdn();
272        if (mConfig.getHomeSp().getRoamingConsortiumOis() != null) {
273            wifiConfig.roamingConsortiumIds = Arrays.copyOf(
274                    mConfig.getHomeSp().getRoamingConsortiumOis(),
275                    mConfig.getHomeSp().getRoamingConsortiumOis().length);
276        }
277        wifiConfig.providerFriendlyName = mConfig.getHomeSp().getFriendlyName();
278        wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
279        wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.IEEE8021X);
280
281        WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
282        enterpriseConfig.setRealm(mConfig.getCredential().getRealm());
283        enterpriseConfig.setDomainSuffixMatch(mConfig.getHomeSp().getFqdn());
284        if (mConfig.getCredential().getUserCredential() != null) {
285            buildEnterpriseConfigForUserCredential(enterpriseConfig,
286                    mConfig.getCredential().getUserCredential());
287            setAnonymousIdentityToNaiRealm(enterpriseConfig, mConfig.getCredential().getRealm());
288        } else if (mConfig.getCredential().getCertCredential() != null) {
289            buildEnterpriseConfigForCertCredential(enterpriseConfig);
290            setAnonymousIdentityToNaiRealm(enterpriseConfig, mConfig.getCredential().getRealm());
291        } else {
292            buildEnterpriseConfigForSimCredential(enterpriseConfig,
293                    mConfig.getCredential().getSimCredential());
294        }
295        wifiConfig.enterpriseConfig = enterpriseConfig;
296        return wifiConfig;
297    }
298
299    /**
300     * Convert a legacy {@link WifiConfiguration} representation of a Passpoint configuration to
301     * a {@link PasspointConfiguration}.  This is used for migrating legacy Passpoint
302     * configuration (release N and older).
303     *
304     * @param wifiConfig The {@link WifiConfiguration} to convert
305     * @return {@link PasspointConfiguration}
306     */
307    public static PasspointConfiguration convertFromWifiConfig(WifiConfiguration wifiConfig) {
308        PasspointConfiguration passpointConfig = new PasspointConfiguration();
309
310        // Setup HomeSP.
311        HomeSp homeSp = new HomeSp();
312        if (TextUtils.isEmpty(wifiConfig.FQDN)) {
313            Log.e(TAG, "Missing FQDN");
314            return null;
315        }
316        homeSp.setFqdn(wifiConfig.FQDN);
317        homeSp.setFriendlyName(wifiConfig.providerFriendlyName);
318        if (wifiConfig.roamingConsortiumIds != null) {
319            homeSp.setRoamingConsortiumOis(Arrays.copyOf(
320                    wifiConfig.roamingConsortiumIds, wifiConfig.roamingConsortiumIds.length));
321        }
322        passpointConfig.setHomeSp(homeSp);
323
324        // Setup Credential.
325        Credential credential = new Credential();
326        credential.setRealm(wifiConfig.enterpriseConfig.getRealm());
327        switch (wifiConfig.enterpriseConfig.getEapMethod()) {
328            case WifiEnterpriseConfig.Eap.TTLS:
329                credential.setUserCredential(buildUserCredentialFromEnterpriseConfig(
330                        wifiConfig.enterpriseConfig));
331                break;
332            case WifiEnterpriseConfig.Eap.TLS:
333                Credential.CertificateCredential certCred = new Credential.CertificateCredential();
334                certCred.setCertType(Credential.CertificateCredential.CERT_TYPE_X509V3);
335                credential.setCertCredential(certCred);
336                break;
337            case WifiEnterpriseConfig.Eap.SIM:
338                credential.setSimCredential(buildSimCredentialFromEnterpriseConfig(
339                        EAPConstants.EAP_SIM, wifiConfig.enterpriseConfig));
340                break;
341            case WifiEnterpriseConfig.Eap.AKA:
342                credential.setSimCredential(buildSimCredentialFromEnterpriseConfig(
343                        EAPConstants.EAP_AKA, wifiConfig.enterpriseConfig));
344                break;
345            case WifiEnterpriseConfig.Eap.AKA_PRIME:
346                credential.setSimCredential(buildSimCredentialFromEnterpriseConfig(
347                        EAPConstants.EAP_AKA_PRIME, wifiConfig.enterpriseConfig));
348                break;
349            default:
350                Log.e(TAG, "Unsupport EAP method: " + wifiConfig.enterpriseConfig.getEapMethod());
351                return null;
352        }
353        if (credential.getUserCredential() == null && credential.getCertCredential() == null
354                && credential.getSimCredential() == null) {
355            Log.e(TAG, "Missing credential");
356            return null;
357        }
358        passpointConfig.setCredential(credential);
359
360        return passpointConfig;
361    }
362
363    @Override
364    public boolean equals(Object thatObject) {
365        if (this == thatObject) {
366            return true;
367        }
368        if (!(thatObject instanceof PasspointProvider)) {
369            return false;
370        }
371        PasspointProvider that = (PasspointProvider) thatObject;
372        return mProviderId == that.mProviderId
373                && TextUtils.equals(mCaCertificateAlias, that.mCaCertificateAlias)
374                && TextUtils.equals(mClientCertificateAlias, that.mClientCertificateAlias)
375                && TextUtils.equals(mClientPrivateKeyAlias, that.mClientPrivateKeyAlias)
376                && (mConfig == null ? that.mConfig == null : mConfig.equals(that.mConfig));
377    }
378
379    @Override
380    public int hashCode() {
381        return Objects.hash(mProviderId, mCaCertificateAlias, mClientCertificateAlias,
382                mClientPrivateKeyAlias, mConfig);
383    }
384
385    @Override
386    public String toString() {
387        StringBuilder builder = new StringBuilder();
388        builder.append("ProviderId: ").append(mProviderId).append("\n");
389        builder.append("Configuration Begin ---\n");
390        builder.append(mConfig);
391        builder.append("Configuration End ---\n");
392        return builder.toString();
393    }
394
395    /**
396     * Retrieve the client certificate from the certificates chain.  The certificate
397     * with the matching SHA256 digest is the client certificate.
398     *
399     * @param certChain The client certificates chain
400     * @param expectedSha256Fingerprint The expected SHA256 digest of the client certificate
401     * @return {@link java.security.cert.X509Certificate}
402     */
403    private static X509Certificate getClientCertificate(X509Certificate[] certChain,
404            byte[] expectedSha256Fingerprint) {
405        if (certChain == null) {
406            return null;
407        }
408        try {
409            MessageDigest digester = MessageDigest.getInstance("SHA-256");
410            for (X509Certificate certificate : certChain) {
411                digester.reset();
412                byte[] fingerprint = digester.digest(certificate.getEncoded());
413                if (Arrays.equals(expectedSha256Fingerprint, fingerprint)) {
414                    return certificate;
415                }
416            }
417        } catch (CertificateEncodingException | NoSuchAlgorithmException e) {
418            return null;
419        }
420
421        return null;
422    }
423
424    /**
425     * Perform a provider match based on the given ANQP elements.
426     *
427     * @param anqpElements List of ANQP elements
428     * @return {@link PasspointMatch}
429     */
430    private PasspointMatch matchProvider(Map<ANQPElementType, ANQPElement> anqpElements) {
431        // Domain name matching.
432        if (ANQPMatcher.matchDomainName(
433                (DomainNameElement) anqpElements.get(ANQPElementType.ANQPDomName),
434                mConfig.getHomeSp().getFqdn(), mImsiParameter, mMatchingSIMImsiList)) {
435            return PasspointMatch.HomeProvider;
436        }
437
438        // Roaming Consortium OI matching.
439        if (ANQPMatcher.matchRoamingConsortium(
440                (RoamingConsortiumElement) anqpElements.get(ANQPElementType.ANQPRoamingConsortium),
441                mConfig.getHomeSp().getRoamingConsortiumOis())) {
442            return PasspointMatch.RoamingProvider;
443        }
444
445        // 3GPP Network matching.
446        if (ANQPMatcher.matchThreeGPPNetwork(
447                (ThreeGPPNetworkElement) anqpElements.get(ANQPElementType.ANQP3GPPNetwork),
448                mImsiParameter, mMatchingSIMImsiList)) {
449            return PasspointMatch.RoamingProvider;
450        }
451        return PasspointMatch.None;
452    }
453
454    /**
455     * Fill in WifiEnterpriseConfig with information from an user credential.
456     *
457     * @param config Instance of {@link WifiEnterpriseConfig}
458     * @param credential Instance of {@link UserCredential}
459     */
460    private void buildEnterpriseConfigForUserCredential(WifiEnterpriseConfig config,
461            Credential.UserCredential credential) {
462        byte[] pwOctets = Base64.decode(credential.getPassword(), Base64.DEFAULT);
463        String decodedPassword = new String(pwOctets, StandardCharsets.UTF_8);
464        config.setEapMethod(WifiEnterpriseConfig.Eap.TTLS);
465        config.setIdentity(credential.getUsername());
466        config.setPassword(decodedPassword);
467        config.setCaCertificateAlias(mCaCertificateAlias);
468        int phase2Method = WifiEnterpriseConfig.Phase2.NONE;
469        switch (credential.getNonEapInnerMethod()) {
470            case Credential.UserCredential.AUTH_METHOD_PAP:
471                phase2Method = WifiEnterpriseConfig.Phase2.PAP;
472                break;
473            case Credential.UserCredential.AUTH_METHOD_MSCHAP:
474                phase2Method = WifiEnterpriseConfig.Phase2.MSCHAP;
475                break;
476            case Credential.UserCredential.AUTH_METHOD_MSCHAPV2:
477                phase2Method = WifiEnterpriseConfig.Phase2.MSCHAPV2;
478                break;
479            default:
480                // Should never happen since this is already validated when the provider is
481                // added.
482                Log.wtf(TAG, "Unsupported Auth: " + credential.getNonEapInnerMethod());
483                break;
484        }
485        config.setPhase2Method(phase2Method);
486    }
487
488    /**
489     * Fill in WifiEnterpriseConfig with information from a certificate credential.
490     *
491     * @param config Instance of {@link WifiEnterpriseConfig}
492     */
493    private void buildEnterpriseConfigForCertCredential(WifiEnterpriseConfig config) {
494        config.setEapMethod(WifiEnterpriseConfig.Eap.TLS);
495        config.setClientCertificateAlias(mClientCertificateAlias);
496        config.setCaCertificateAlias(mCaCertificateAlias);
497    }
498
499    /**
500     * Fill in WifiEnterpriseConfig with information from a SIM credential.
501     *
502     * @param config Instance of {@link WifiEnterpriseConfig}
503     * @param credential Instance of {@link SimCredential}
504     */
505    private void buildEnterpriseConfigForSimCredential(WifiEnterpriseConfig config,
506            Credential.SimCredential credential) {
507        int eapMethod = WifiEnterpriseConfig.Eap.NONE;
508        switch(credential.getEapType()) {
509            case EAPConstants.EAP_SIM:
510                eapMethod = WifiEnterpriseConfig.Eap.SIM;
511                break;
512            case EAPConstants.EAP_AKA:
513                eapMethod = WifiEnterpriseConfig.Eap.AKA;
514                break;
515            case EAPConstants.EAP_AKA_PRIME:
516                eapMethod = WifiEnterpriseConfig.Eap.AKA_PRIME;
517                break;
518            default:
519                // Should never happen since this is already validated when the provider is
520                // added.
521                Log.wtf(TAG, "Unsupported EAP Method: " + credential.getEapType());
522                break;
523        }
524        config.setEapMethod(eapMethod);
525        config.setPlmn(credential.getImsi());
526    }
527
528    private static void setAnonymousIdentityToNaiRealm(WifiEnterpriseConfig config, String realm) {
529        /**
530         * Set WPA supplicant's anonymous identity field to a string containing the NAI realm, so
531         * that this value will be sent to the EAP server as part of the EAP-Response/ Identity
532         * packet. WPA supplicant will reset this field after using it for the EAP-Response/Identity
533         * packet, and revert to using the (real) identity field for subsequent transactions that
534         * request an identity (e.g. in EAP-TTLS).
535         *
536         * This NAI realm value (the portion of the identity after the '@') is used to tell the
537         * AAA server which AAA/H to forward packets to. The hardcoded username, "anonymous", is a
538         * placeholder that is not used--it is set to this value by convention. See Section 5.1 of
539         * RFC3748 for more details.
540         *
541         * NOTE: we do not set this value for EAP-SIM/AKA/AKA', since the EAP server expects the
542         * EAP-Response/Identity packet to contain an actual, IMSI-based identity, in order to
543         * identify the device.
544         */
545        config.setAnonymousIdentity("anonymous@" + realm);
546    }
547
548    /**
549     * Helper function for creating a
550     * {@link android.net.wifi.hotspot2.pps.Credential.UserCredential} from the given
551     * {@link WifiEnterpriseConfig}
552     *
553     * @param config The enterprise configuration containing the credential
554     * @return {@link android.net.wifi.hotspot2.pps.Credential.UserCredential}
555     */
556    private static Credential.UserCredential buildUserCredentialFromEnterpriseConfig(
557            WifiEnterpriseConfig config) {
558        Credential.UserCredential userCredential = new Credential.UserCredential();
559        userCredential.setEapType(EAPConstants.EAP_TTLS);
560
561        if (TextUtils.isEmpty(config.getIdentity())) {
562            Log.e(TAG, "Missing username for user credential");
563            return null;
564        }
565        userCredential.setUsername(config.getIdentity());
566
567        if (TextUtils.isEmpty(config.getPassword())) {
568            Log.e(TAG, "Missing password for user credential");
569            return null;
570        }
571        String encodedPassword =
572                new String(Base64.encode(config.getPassword().getBytes(StandardCharsets.UTF_8),
573                        Base64.DEFAULT), StandardCharsets.UTF_8);
574        userCredential.setPassword(encodedPassword);
575
576        switch(config.getPhase2Method()) {
577            case WifiEnterpriseConfig.Phase2.PAP:
578                userCredential.setNonEapInnerMethod(Credential.UserCredential.AUTH_METHOD_PAP);
579                break;
580            case WifiEnterpriseConfig.Phase2.MSCHAP:
581                userCredential.setNonEapInnerMethod(Credential.UserCredential.AUTH_METHOD_MSCHAP);
582                break;
583            case WifiEnterpriseConfig.Phase2.MSCHAPV2:
584                userCredential.setNonEapInnerMethod(Credential.UserCredential.AUTH_METHOD_MSCHAPV2);
585                break;
586            default:
587                Log.e(TAG, "Unsupported phase2 method for TTLS: " + config.getPhase2Method());
588                return null;
589        }
590        return userCredential;
591    }
592
593    /**
594     * Helper function for creating a
595     * {@link android.net.wifi.hotspot2.pps.Credential.SimCredential} from the given
596     * {@link WifiEnterpriseConfig}
597     *
598     * @param eapType The EAP type of the SIM credential
599     * @param config The enterprise configuration containing the credential
600     * @return {@link android.net.wifi.hotspot2.pps.Credential.SimCredential}
601     */
602    private static Credential.SimCredential buildSimCredentialFromEnterpriseConfig(
603            int eapType, WifiEnterpriseConfig config) {
604        Credential.SimCredential simCredential = new Credential.SimCredential();
605        if (TextUtils.isEmpty(config.getPlmn())) {
606            Log.e(TAG, "Missing IMSI for SIM credential");
607            return null;
608        }
609        simCredential.setImsi(config.getPlmn());
610        simCredential.setEapType(eapType);
611        return simCredential;
612    }
613}
614