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