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