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 
17 package com.android.server.wifi.hotspot2;
18 
19 import android.annotation.Nullable;
20 import android.net.wifi.EAPConstants;
21 import android.net.wifi.WifiConfiguration;
22 import android.net.wifi.WifiEnterpriseConfig;
23 import android.net.wifi.hotspot2.PasspointConfiguration;
24 import android.net.wifi.hotspot2.pps.Credential;
25 import android.net.wifi.hotspot2.pps.Credential.SimCredential;
26 import android.net.wifi.hotspot2.pps.Credential.UserCredential;
27 import android.net.wifi.hotspot2.pps.HomeSp;
28 import android.security.Credentials;
29 import android.text.TextUtils;
30 import android.util.Base64;
31 import android.util.Log;
32 
33 import com.android.server.wifi.IMSIParameter;
34 import com.android.server.wifi.SIMAccessor;
35 import com.android.server.wifi.WifiKeyStore;
36 import com.android.server.wifi.hotspot2.anqp.ANQPElement;
37 import com.android.server.wifi.hotspot2.anqp.Constants.ANQPElementType;
38 import com.android.server.wifi.hotspot2.anqp.DomainNameElement;
39 import com.android.server.wifi.hotspot2.anqp.NAIRealmElement;
40 import com.android.server.wifi.hotspot2.anqp.RoamingConsortiumElement;
41 import com.android.server.wifi.hotspot2.anqp.ThreeGPPNetworkElement;
42 import com.android.server.wifi.hotspot2.anqp.eap.AuthParam;
43 import com.android.server.wifi.hotspot2.anqp.eap.NonEAPInnerAuth;
44 import com.android.server.wifi.util.InformationElementUtil.RoamingConsortium;
45 
46 import java.nio.charset.StandardCharsets;
47 import java.security.MessageDigest;
48 import java.security.NoSuchAlgorithmException;
49 import java.security.cert.CertificateEncodingException;
50 import java.security.cert.X509Certificate;
51 import java.util.ArrayList;
52 import java.util.Arrays;
53 import java.util.List;
54 import java.util.Map;
55 import java.util.Objects;
56 
57 /**
58  * Abstraction for Passpoint service provider.  This class contains the both static
59  * Passpoint configuration data and the runtime data (e.g. blacklisted SSIDs, statistics).
60  */
61 public class PasspointProvider {
62     private static final String TAG = "PasspointProvider";
63 
64     /**
65      * Used as part of alias string for certificates and keys.  The alias string is in the format
66      * of: [KEY_TYPE]_HS2_[ProviderID]
67      * For example: "CACERT_HS2_0", "USRCERT_HS2_0", "USRPKEY_HS2_0", "CACERT_HS2_REMEDIATION_0"
68      */
69     private static final String ALIAS_HS_TYPE = "HS2_";
70     private static final String ALIAS_ALIAS_REMEDIATION_TYPE = "REMEDIATION_";
71 
72     private final PasspointConfiguration mConfig;
73     private final WifiKeyStore mKeyStore;
74 
75     /**
76      * Aliases for the private keys and certificates installed in the keystore.  Each alias
77      * is a suffix of the actual certificate or key name installed in the keystore.  The
78      * certificate or key name in the keystore is consist of |Type|_|alias|.
79      * This will be consistent with the usage of the term "alias" in {@link WifiEnterpriseConfig}.
80      */
81     private List<String> mCaCertificateAliases;
82     private String mClientPrivateKeyAlias;
83     private String mClientCertificateAlias;
84     private String mRemediationCaCertificateAlias;
85 
86     private final long mProviderId;
87     private final int mCreatorUid;
88     private final String mPackageName;
89 
90     private final IMSIParameter mImsiParameter;
91     private final List<String> mMatchingSIMImsiList;
92 
93     private final int mEAPMethodID;
94     private final AuthParam mAuthParam;
95 
96     private boolean mHasEverConnected;
97     private boolean mIsShared;
98 
99     /**
100      * This is a flag to indicate if the Provider is created temporarily.
101      * Thus, it is not saved permanently unlike normal Passpoint profile.
102      */
103     private boolean mIsEphemeral = false;
104 
PasspointProvider(PasspointConfiguration config, WifiKeyStore keyStore, SIMAccessor simAccessor, long providerId, int creatorUid, String packageName)105     public PasspointProvider(PasspointConfiguration config, WifiKeyStore keyStore,
106             SIMAccessor simAccessor, long providerId, int creatorUid, String packageName) {
107         this(config, keyStore, simAccessor, providerId, creatorUid, packageName, null, null, null,
108                 null, false, false);
109     }
110 
PasspointProvider(PasspointConfiguration config, WifiKeyStore keyStore, SIMAccessor simAccessor, long providerId, int creatorUid, String packageName, List<String> caCertificateAliases, String clientCertificateAlias, String clientPrivateKeyAlias, String remediationCaCertificateAlias, boolean hasEverConnected, boolean isShared)111     public PasspointProvider(PasspointConfiguration config, WifiKeyStore keyStore,
112             SIMAccessor simAccessor, long providerId, int creatorUid, String packageName,
113             List<String> caCertificateAliases,
114             String clientCertificateAlias, String clientPrivateKeyAlias,
115             String remediationCaCertificateAlias,
116             boolean hasEverConnected, boolean isShared) {
117         // Maintain a copy of the configuration to avoid it being updated by others.
118         mConfig = new PasspointConfiguration(config);
119         mKeyStore = keyStore;
120         mProviderId = providerId;
121         mCreatorUid = creatorUid;
122         mPackageName = packageName;
123         mCaCertificateAliases = caCertificateAliases;
124         mClientCertificateAlias = clientCertificateAlias;
125         mClientPrivateKeyAlias = clientPrivateKeyAlias;
126         mRemediationCaCertificateAlias = remediationCaCertificateAlias;
127         mHasEverConnected = hasEverConnected;
128         mIsShared = isShared;
129 
130         // Setup EAP method and authentication parameter based on the credential.
131         if (mConfig.getCredential().getUserCredential() != null) {
132             mEAPMethodID = EAPConstants.EAP_TTLS;
133             mAuthParam = new NonEAPInnerAuth(NonEAPInnerAuth.getAuthTypeID(
134                     mConfig.getCredential().getUserCredential().getNonEapInnerMethod()));
135             mImsiParameter = null;
136             mMatchingSIMImsiList = null;
137         } else if (mConfig.getCredential().getCertCredential() != null) {
138             mEAPMethodID = EAPConstants.EAP_TLS;
139             mAuthParam = null;
140             mImsiParameter = null;
141             mMatchingSIMImsiList = null;
142         } else {
143             mEAPMethodID = mConfig.getCredential().getSimCredential().getEapType();
144             mAuthParam = null;
145             mImsiParameter = IMSIParameter.build(
146                     mConfig.getCredential().getSimCredential().getImsi());
147             mMatchingSIMImsiList = simAccessor.getMatchingImsis(mImsiParameter);
148         }
149     }
150 
getConfig()151     public PasspointConfiguration getConfig() {
152         // Return a copy of the configuration to avoid it being updated by others.
153         return new PasspointConfiguration(mConfig);
154     }
155 
getCaCertificateAliases()156     public List<String> getCaCertificateAliases() {
157         return mCaCertificateAliases;
158     }
159 
getClientPrivateKeyAlias()160     public String getClientPrivateKeyAlias() {
161         return mClientPrivateKeyAlias;
162     }
163 
getClientCertificateAlias()164     public String getClientCertificateAlias() {
165         return mClientCertificateAlias;
166     }
167 
getRemediationCaCertificateAlias()168     public String getRemediationCaCertificateAlias() {
169         return mRemediationCaCertificateAlias;
170     }
171 
getProviderId()172     public long getProviderId() {
173         return mProviderId;
174     }
175 
getCreatorUid()176     public int getCreatorUid() {
177         return mCreatorUid;
178     }
179 
180     @Nullable
getPackageName()181     public String getPackageName() {
182         return mPackageName;
183     }
184 
getHasEverConnected()185     public boolean getHasEverConnected() {
186         return mHasEverConnected;
187     }
188 
setHasEverConnected(boolean hasEverConnected)189     public void setHasEverConnected(boolean hasEverConnected) {
190         mHasEverConnected = hasEverConnected;
191     }
192 
isEphemeral()193     public boolean isEphemeral() {
194         return mIsEphemeral;
195     }
196 
setEphemeral(boolean isEphemeral)197     public void setEphemeral(boolean isEphemeral) {
198         mIsEphemeral = isEphemeral;
199     }
200 
getImsiParameter()201     public IMSIParameter getImsiParameter() {
202         return mImsiParameter;
203     }
204 
205     /**
206      * Install certificates and key based on current configuration.
207      * Note: the certificates and keys in the configuration will get cleared once
208      * they're installed in the keystore.
209      *
210      * @return true on success
211      */
installCertsAndKeys()212     public boolean installCertsAndKeys() {
213         // Install CA certificate.
214         X509Certificate[] x509Certificates = mConfig.getCredential().getCaCertificates();
215         if (x509Certificates != null) {
216             mCaCertificateAliases = new ArrayList<>();
217             for (int i = 0; i < x509Certificates.length; i++) {
218                 String alias = String.format("%s%s_%d", ALIAS_HS_TYPE, mProviderId, i);
219                 if (!mKeyStore.putCertInKeyStore(Credentials.CA_CERTIFICATE + alias,
220                         x509Certificates[i])) {
221                     Log.e(TAG, "Failed to install CA Certificate");
222                     uninstallCertsAndKeys();
223                     return false;
224                 } else {
225                     mCaCertificateAliases.add(alias);
226                 }
227             }
228         }
229 
230         // Install the client private key.
231         if (mConfig.getCredential().getClientPrivateKey() != null) {
232             String keyName = Credentials.USER_PRIVATE_KEY + ALIAS_HS_TYPE + mProviderId;
233             if (!mKeyStore.putKeyInKeyStore(keyName,
234                     mConfig.getCredential().getClientPrivateKey())) {
235                 Log.e(TAG, "Failed to install client private key");
236                 uninstallCertsAndKeys();
237                 return false;
238             }
239             mClientPrivateKeyAlias = ALIAS_HS_TYPE + mProviderId;
240         }
241 
242         // Install the client certificate.
243         if (mConfig.getCredential().getClientCertificateChain() != null) {
244             X509Certificate clientCert = getClientCertificate(
245                     mConfig.getCredential().getClientCertificateChain(),
246                     mConfig.getCredential().getCertCredential().getCertSha256Fingerprint());
247             if (clientCert == null) {
248                 Log.e(TAG, "Failed to locate client certificate");
249                 uninstallCertsAndKeys();
250                 return false;
251             }
252             String certName = Credentials.USER_CERTIFICATE + ALIAS_HS_TYPE + mProviderId;
253             if (!mKeyStore.putCertInKeyStore(certName, clientCert)) {
254                 Log.e(TAG, "Failed to install client certificate");
255                 uninstallCertsAndKeys();
256                 return false;
257             }
258             mClientCertificateAlias = ALIAS_HS_TYPE + mProviderId;
259         }
260 
261         if (mConfig.getSubscriptionUpdate() != null) {
262             X509Certificate certificate = mConfig.getSubscriptionUpdate().getCaCertificate();
263             if (certificate == null) {
264                 Log.e(TAG, "Failed to locate CA certificate for remediation");
265                 uninstallCertsAndKeys();
266                 return false;
267             }
268             mRemediationCaCertificateAlias =
269                     ALIAS_HS_TYPE + ALIAS_ALIAS_REMEDIATION_TYPE + mProviderId;
270             String certName = Credentials.CA_CERTIFICATE + mRemediationCaCertificateAlias;
271             if (!mKeyStore.putCertInKeyStore(certName, certificate)) {
272                 Log.e(TAG, "Failed to install CA certificate for remediation");
273                 mRemediationCaCertificateAlias = null;
274                 uninstallCertsAndKeys();
275                 return false;
276             }
277         }
278 
279         // Clear the keys and certificates in the configuration.
280         mConfig.getCredential().setCaCertificates(null);
281         mConfig.getCredential().setClientPrivateKey(null);
282         mConfig.getCredential().setClientCertificateChain(null);
283         if (mConfig.getSubscriptionUpdate() != null) {
284             mConfig.getSubscriptionUpdate().setCaCertificate(null);
285         }
286         return true;
287     }
288 
289     /**
290      * Remove any installed certificates and key.
291      */
uninstallCertsAndKeys()292     public void uninstallCertsAndKeys() {
293         if (mCaCertificateAliases != null) {
294             for (String certificateAlias : mCaCertificateAliases) {
295                 if (!mKeyStore.removeEntryFromKeyStore(
296                         Credentials.CA_CERTIFICATE + certificateAlias)) {
297                     Log.e(TAG, "Failed to remove entry: " + certificateAlias);
298                 }
299             }
300             mCaCertificateAliases = null;
301         }
302         if (mClientPrivateKeyAlias != null) {
303             if (!mKeyStore.removeEntryFromKeyStore(
304                     Credentials.USER_PRIVATE_KEY + mClientPrivateKeyAlias)) {
305                 Log.e(TAG, "Failed to remove entry: " + mClientPrivateKeyAlias);
306             }
307             mClientPrivateKeyAlias = null;
308         }
309         if (mClientCertificateAlias != null) {
310             if (!mKeyStore.removeEntryFromKeyStore(
311                     Credentials.USER_CERTIFICATE + mClientCertificateAlias)) {
312                 Log.e(TAG, "Failed to remove entry: " + mClientCertificateAlias);
313             }
314             mClientCertificateAlias = null;
315         }
316 
317         if (mRemediationCaCertificateAlias != null) {
318             if (!mKeyStore.removeEntryFromKeyStore(
319                     Credentials.CA_CERTIFICATE + mRemediationCaCertificateAlias)) {
320                 Log.e(TAG, "Failed to remove entry: " + mRemediationCaCertificateAlias);
321             }
322             mRemediationCaCertificateAlias = null;
323         }
324     }
325 
326     /**
327      * Return the matching status with the given AP, based on the ANQP elements from the AP.
328      *
329      * @param anqpElements ANQP elements from the AP
330      * @param roamingConsortium Roaming Consortium information element from the AP
331      * @return {@link PasspointMatch}
332      */
match(Map<ANQPElementType, ANQPElement> anqpElements, RoamingConsortium roamingConsortium)333     public PasspointMatch match(Map<ANQPElementType, ANQPElement> anqpElements,
334             RoamingConsortium roamingConsortium) {
335         PasspointMatch providerMatch = matchProviderExceptFor3GPP(anqpElements, roamingConsortium);
336 
337         // 3GPP Network matching.
338         if (providerMatch == PasspointMatch.None && ANQPMatcher.matchThreeGPPNetwork(
339                 (ThreeGPPNetworkElement) anqpElements.get(ANQPElementType.ANQP3GPPNetwork),
340                 mImsiParameter, mMatchingSIMImsiList)) {
341             return PasspointMatch.RoamingProvider;
342         }
343 
344         // Perform authentication match against the NAI Realm.
345         int authMatch = ANQPMatcher.matchNAIRealm(
346                 (NAIRealmElement) anqpElements.get(ANQPElementType.ANQPNAIRealm),
347                 mConfig.getCredential().getRealm(), mEAPMethodID, mAuthParam);
348 
349         // In case of Auth mismatch, demote provider match.
350         if (authMatch == AuthMatch.NONE) {
351             return PasspointMatch.None;
352         }
353 
354         // In case of no realm match, return provider match as is.
355         if ((authMatch & AuthMatch.REALM) == 0) {
356             return providerMatch;
357         }
358 
359         // Promote the provider match to roaming provider if provider match is not found, but NAI
360         // realm is matched.
361         return providerMatch == PasspointMatch.None ? PasspointMatch.RoamingProvider
362                 : providerMatch;
363     }
364 
365     /**
366      * Generate a WifiConfiguration based on the provider's configuration.  The generated
367      * WifiConfiguration will include all the necessary credentials for network connection except
368      * the SSID, which should be added by the caller when the config is being used for network
369      * connection.
370      *
371      * @return {@link WifiConfiguration}
372      */
getWifiConfig()373     public WifiConfiguration getWifiConfig() {
374         WifiConfiguration wifiConfig = new WifiConfiguration();
375         wifiConfig.FQDN = mConfig.getHomeSp().getFqdn();
376         if (mConfig.getHomeSp().getRoamingConsortiumOis() != null) {
377             wifiConfig.roamingConsortiumIds = Arrays.copyOf(
378                     mConfig.getHomeSp().getRoamingConsortiumOis(),
379                     mConfig.getHomeSp().getRoamingConsortiumOis().length);
380         }
381         if (mConfig.getUpdateIdentifier() != Integer.MIN_VALUE) {
382             // R2 profile, it needs to set updateIdentifier HS2.0 Indication element as PPS MO
383             // ID in Association Request.
384             wifiConfig.updateIdentifier = Integer.toString(mConfig.getUpdateIdentifier());
385             if (isMeteredNetwork(mConfig)) {
386                 wifiConfig.meteredOverride = WifiConfiguration.METERED_OVERRIDE_METERED;
387             }
388         }
389         wifiConfig.providerFriendlyName = mConfig.getHomeSp().getFriendlyName();
390         wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
391         wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.IEEE8021X);
392 
393         // Set RSN only to tell wpa_supplicant that this network is for Passpoint.
394         wifiConfig.allowedProtocols.set(WifiConfiguration.Protocol.RSN);
395 
396         WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
397         enterpriseConfig.setRealm(mConfig.getCredential().getRealm());
398         enterpriseConfig.setDomainSuffixMatch(mConfig.getHomeSp().getFqdn());
399         if (mConfig.getCredential().getUserCredential() != null) {
400             buildEnterpriseConfigForUserCredential(enterpriseConfig,
401                     mConfig.getCredential().getUserCredential());
402             setAnonymousIdentityToNaiRealm(enterpriseConfig, mConfig.getCredential().getRealm());
403         } else if (mConfig.getCredential().getCertCredential() != null) {
404             buildEnterpriseConfigForCertCredential(enterpriseConfig);
405             setAnonymousIdentityToNaiRealm(enterpriseConfig, mConfig.getCredential().getRealm());
406         } else {
407             buildEnterpriseConfigForSimCredential(enterpriseConfig,
408                     mConfig.getCredential().getSimCredential());
409         }
410         wifiConfig.enterpriseConfig = enterpriseConfig;
411         wifiConfig.shared = mIsShared;
412         return wifiConfig;
413     }
414 
415     /**
416      * @return true if provider is backed by a SIM credential.
417      */
isSimCredential()418     public boolean isSimCredential() {
419         return mConfig.getCredential().getSimCredential() != null;
420     }
421 
422     /**
423      * Convert a legacy {@link WifiConfiguration} representation of a Passpoint configuration to
424      * a {@link PasspointConfiguration}.  This is used for migrating legacy Passpoint
425      * configuration (release N and older).
426      *
427      * @param wifiConfig The {@link WifiConfiguration} to convert
428      * @return {@link PasspointConfiguration}
429      */
convertFromWifiConfig(WifiConfiguration wifiConfig)430     public static PasspointConfiguration convertFromWifiConfig(WifiConfiguration wifiConfig) {
431         PasspointConfiguration passpointConfig = new PasspointConfiguration();
432 
433         // Setup HomeSP.
434         HomeSp homeSp = new HomeSp();
435         if (TextUtils.isEmpty(wifiConfig.FQDN)) {
436             Log.e(TAG, "Missing FQDN");
437             return null;
438         }
439         homeSp.setFqdn(wifiConfig.FQDN);
440         homeSp.setFriendlyName(wifiConfig.providerFriendlyName);
441         if (wifiConfig.roamingConsortiumIds != null) {
442             homeSp.setRoamingConsortiumOis(Arrays.copyOf(
443                     wifiConfig.roamingConsortiumIds, wifiConfig.roamingConsortiumIds.length));
444         }
445         passpointConfig.setHomeSp(homeSp);
446 
447         // Setup Credential.
448         Credential credential = new Credential();
449         credential.setRealm(wifiConfig.enterpriseConfig.getRealm());
450         switch (wifiConfig.enterpriseConfig.getEapMethod()) {
451             case WifiEnterpriseConfig.Eap.TTLS:
452                 credential.setUserCredential(buildUserCredentialFromEnterpriseConfig(
453                         wifiConfig.enterpriseConfig));
454                 break;
455             case WifiEnterpriseConfig.Eap.TLS:
456                 Credential.CertificateCredential certCred = new Credential.CertificateCredential();
457                 certCred.setCertType(Credential.CertificateCredential.CERT_TYPE_X509V3);
458                 credential.setCertCredential(certCred);
459                 break;
460             case WifiEnterpriseConfig.Eap.SIM:
461                 credential.setSimCredential(buildSimCredentialFromEnterpriseConfig(
462                         EAPConstants.EAP_SIM, wifiConfig.enterpriseConfig));
463                 break;
464             case WifiEnterpriseConfig.Eap.AKA:
465                 credential.setSimCredential(buildSimCredentialFromEnterpriseConfig(
466                         EAPConstants.EAP_AKA, wifiConfig.enterpriseConfig));
467                 break;
468             case WifiEnterpriseConfig.Eap.AKA_PRIME:
469                 credential.setSimCredential(buildSimCredentialFromEnterpriseConfig(
470                         EAPConstants.EAP_AKA_PRIME, wifiConfig.enterpriseConfig));
471                 break;
472             default:
473                 Log.e(TAG, "Unsupport EAP method: " + wifiConfig.enterpriseConfig.getEapMethod());
474                 return null;
475         }
476         if (credential.getUserCredential() == null && credential.getCertCredential() == null
477                 && credential.getSimCredential() == null) {
478             Log.e(TAG, "Missing credential");
479             return null;
480         }
481         passpointConfig.setCredential(credential);
482 
483         return passpointConfig;
484     }
485 
486     @Override
equals(Object thatObject)487     public boolean equals(Object thatObject) {
488         if (this == thatObject) {
489             return true;
490         }
491         if (!(thatObject instanceof PasspointProvider)) {
492             return false;
493         }
494         PasspointProvider that = (PasspointProvider) thatObject;
495         return mProviderId == that.mProviderId
496                 && (mCaCertificateAliases == null ? that.mCaCertificateAliases == null
497                 : mCaCertificateAliases.equals(that.mCaCertificateAliases))
498                 && TextUtils.equals(mClientCertificateAlias, that.mClientCertificateAlias)
499                 && TextUtils.equals(mClientPrivateKeyAlias, that.mClientPrivateKeyAlias)
500                 && (mConfig == null ? that.mConfig == null : mConfig.equals(that.mConfig))
501                 && TextUtils.equals(mRemediationCaCertificateAlias,
502                 that.mRemediationCaCertificateAlias);
503     }
504 
505     @Override
hashCode()506     public int hashCode() {
507         return Objects.hash(mProviderId, mCaCertificateAliases, mClientCertificateAlias,
508                 mClientPrivateKeyAlias, mConfig, mRemediationCaCertificateAlias);
509     }
510 
511     @Override
toString()512     public String toString() {
513         StringBuilder builder = new StringBuilder();
514         builder.append("ProviderId: ").append(mProviderId).append("\n");
515         builder.append("CreatorUID: ").append(mCreatorUid).append("\n");
516         if (mPackageName != null) {
517             builder.append("PackageName: ").append(mPackageName).append("\n");
518         }
519         builder.append("Configuration Begin ---\n");
520         builder.append(mConfig);
521         builder.append("Configuration End ---\n");
522         return builder.toString();
523     }
524 
525     /**
526      * Retrieve the client certificate from the certificates chain.  The certificate
527      * with the matching SHA256 digest is the client certificate.
528      *
529      * @param certChain The client certificates chain
530      * @param expectedSha256Fingerprint The expected SHA256 digest of the client certificate
531      * @return {@link java.security.cert.X509Certificate}
532      */
getClientCertificate(X509Certificate[] certChain, byte[] expectedSha256Fingerprint)533     private static X509Certificate getClientCertificate(X509Certificate[] certChain,
534             byte[] expectedSha256Fingerprint) {
535         if (certChain == null) {
536             return null;
537         }
538         try {
539             MessageDigest digester = MessageDigest.getInstance("SHA-256");
540             for (X509Certificate certificate : certChain) {
541                 digester.reset();
542                 byte[] fingerprint = digester.digest(certificate.getEncoded());
543                 if (Arrays.equals(expectedSha256Fingerprint, fingerprint)) {
544                     return certificate;
545                 }
546             }
547         } catch (CertificateEncodingException | NoSuchAlgorithmException e) {
548             return null;
549         }
550 
551         return null;
552     }
553 
554     /**
555      * Determines the Passpoint network is a metered network.
556      *
557      * Expiration date -> non-metered
558      * Data limit -> metered
559      * Time usage limit -> metered
560      * @param passpointConfig instance of {@link PasspointConfiguration}
561      * @return {@code true} if the network is a metered network, {@code false} otherwise.
562      */
isMeteredNetwork(PasspointConfiguration passpointConfig)563     private boolean isMeteredNetwork(PasspointConfiguration passpointConfig) {
564         if (passpointConfig == null) return false;
565 
566         // If DataLimit is zero, there is unlimited data usage for the account.
567         // If TimeLimit is zero, there is unlimited time usage for the account.
568         return passpointConfig.getUsageLimitDataLimit() > 0
569                 || passpointConfig.getUsageLimitTimeLimitInMinutes() > 0;
570     }
571 
572     /**
573      * Perform a provider match based on the given ANQP elements except for matching 3GPP Network.
574      *
575      * @param anqpElements List of ANQP elements
576      * @param roamingConsortium Roaming Consortium information element from the AP
577      * @return {@link PasspointMatch}
578      */
matchProviderExceptFor3GPP( Map<ANQPElementType, ANQPElement> anqpElements, RoamingConsortium roamingConsortium)579     private PasspointMatch matchProviderExceptFor3GPP(
580             Map<ANQPElementType, ANQPElement> anqpElements,
581             RoamingConsortium roamingConsortium) {
582         // Domain name matching.
583         if (ANQPMatcher.matchDomainName(
584                 (DomainNameElement) anqpElements.get(ANQPElementType.ANQPDomName),
585                 mConfig.getHomeSp().getFqdn(), mImsiParameter, mMatchingSIMImsiList)) {
586             return PasspointMatch.HomeProvider;
587         }
588 
589         // ANQP Roaming Consortium OI matching.
590         long[] providerOIs = mConfig.getHomeSp().getRoamingConsortiumOis();
591         if (ANQPMatcher.matchRoamingConsortium(
592                 (RoamingConsortiumElement) anqpElements.get(ANQPElementType.ANQPRoamingConsortium),
593                 providerOIs)) {
594             return PasspointMatch.RoamingProvider;
595         }
596 
597         long[] roamingConsortiums = roamingConsortium.getRoamingConsortiums();
598         // Roaming Consortium OI information element matching.
599         if (roamingConsortiums != null && providerOIs != null) {
600             for (long sta_oi: roamingConsortiums) {
601                 for (long ap_oi: providerOIs) {
602                     if (sta_oi == ap_oi) {
603                         return PasspointMatch.RoamingProvider;
604                     }
605                 }
606             }
607         }
608 
609         return PasspointMatch.None;
610     }
611 
612     /**
613      * Fill in WifiEnterpriseConfig with information from an user credential.
614      *
615      * @param config Instance of {@link WifiEnterpriseConfig}
616      * @param credential Instance of {@link UserCredential}
617      */
buildEnterpriseConfigForUserCredential(WifiEnterpriseConfig config, Credential.UserCredential credential)618     private void buildEnterpriseConfigForUserCredential(WifiEnterpriseConfig config,
619             Credential.UserCredential credential) {
620         byte[] pwOctets = Base64.decode(credential.getPassword(), Base64.DEFAULT);
621         String decodedPassword = new String(pwOctets, StandardCharsets.UTF_8);
622         config.setEapMethod(WifiEnterpriseConfig.Eap.TTLS);
623         config.setIdentity(credential.getUsername());
624         config.setPassword(decodedPassword);
625         config.setCaCertificateAliases(mCaCertificateAliases.toArray(new String[0]));
626         int phase2Method = WifiEnterpriseConfig.Phase2.NONE;
627         switch (credential.getNonEapInnerMethod()) {
628             case Credential.UserCredential.AUTH_METHOD_PAP:
629                 phase2Method = WifiEnterpriseConfig.Phase2.PAP;
630                 break;
631             case Credential.UserCredential.AUTH_METHOD_MSCHAP:
632                 phase2Method = WifiEnterpriseConfig.Phase2.MSCHAP;
633                 break;
634             case Credential.UserCredential.AUTH_METHOD_MSCHAPV2:
635                 phase2Method = WifiEnterpriseConfig.Phase2.MSCHAPV2;
636                 break;
637             default:
638                 // Should never happen since this is already validated when the provider is
639                 // added.
640                 Log.wtf(TAG, "Unsupported Auth: " + credential.getNonEapInnerMethod());
641                 break;
642         }
643         config.setPhase2Method(phase2Method);
644     }
645 
646     /**
647      * Fill in WifiEnterpriseConfig with information from a certificate credential.
648      *
649      * @param config Instance of {@link WifiEnterpriseConfig}
650      */
buildEnterpriseConfigForCertCredential(WifiEnterpriseConfig config)651     private void buildEnterpriseConfigForCertCredential(WifiEnterpriseConfig config) {
652         config.setEapMethod(WifiEnterpriseConfig.Eap.TLS);
653         config.setClientCertificateAlias(mClientCertificateAlias);
654         config.setCaCertificateAliases(mCaCertificateAliases.toArray(new String[0]));
655     }
656 
657     /**
658      * Fill in WifiEnterpriseConfig with information from a SIM credential.
659      *
660      * @param config Instance of {@link WifiEnterpriseConfig}
661      * @param credential Instance of {@link SimCredential}
662      */
buildEnterpriseConfigForSimCredential(WifiEnterpriseConfig config, Credential.SimCredential credential)663     private void buildEnterpriseConfigForSimCredential(WifiEnterpriseConfig config,
664             Credential.SimCredential credential) {
665         int eapMethod = WifiEnterpriseConfig.Eap.NONE;
666         switch(credential.getEapType()) {
667             case EAPConstants.EAP_SIM:
668                 eapMethod = WifiEnterpriseConfig.Eap.SIM;
669                 break;
670             case EAPConstants.EAP_AKA:
671                 eapMethod = WifiEnterpriseConfig.Eap.AKA;
672                 break;
673             case EAPConstants.EAP_AKA_PRIME:
674                 eapMethod = WifiEnterpriseConfig.Eap.AKA_PRIME;
675                 break;
676             default:
677                 // Should never happen since this is already validated when the provider is
678                 // added.
679                 Log.wtf(TAG, "Unsupported EAP Method: " + credential.getEapType());
680                 break;
681         }
682         config.setEapMethod(eapMethod);
683         config.setPlmn(credential.getImsi());
684     }
685 
setAnonymousIdentityToNaiRealm(WifiEnterpriseConfig config, String realm)686     private static void setAnonymousIdentityToNaiRealm(WifiEnterpriseConfig config, String realm) {
687         /**
688          * Set WPA supplicant's anonymous identity field to a string containing the NAI realm, so
689          * that this value will be sent to the EAP server as part of the EAP-Response/ Identity
690          * packet. WPA supplicant will reset this field after using it for the EAP-Response/Identity
691          * packet, and revert to using the (real) identity field for subsequent transactions that
692          * request an identity (e.g. in EAP-TTLS).
693          *
694          * This NAI realm value (the portion of the identity after the '@') is used to tell the
695          * AAA server which AAA/H to forward packets to. The hardcoded username, "anonymous", is a
696          * placeholder that is not used--it is set to this value by convention. See Section 5.1 of
697          * RFC3748 for more details.
698          *
699          * NOTE: we do not set this value for EAP-SIM/AKA/AKA', since the EAP server expects the
700          * EAP-Response/Identity packet to contain an actual, IMSI-based identity, in order to
701          * identify the device.
702          */
703         config.setAnonymousIdentity("anonymous@" + realm);
704     }
705 
706     /**
707      * Helper function for creating a
708      * {@link android.net.wifi.hotspot2.pps.Credential.UserCredential} from the given
709      * {@link WifiEnterpriseConfig}
710      *
711      * @param config The enterprise configuration containing the credential
712      * @return {@link android.net.wifi.hotspot2.pps.Credential.UserCredential}
713      */
buildUserCredentialFromEnterpriseConfig( WifiEnterpriseConfig config)714     private static Credential.UserCredential buildUserCredentialFromEnterpriseConfig(
715             WifiEnterpriseConfig config) {
716         Credential.UserCredential userCredential = new Credential.UserCredential();
717         userCredential.setEapType(EAPConstants.EAP_TTLS);
718 
719         if (TextUtils.isEmpty(config.getIdentity())) {
720             Log.e(TAG, "Missing username for user credential");
721             return null;
722         }
723         userCredential.setUsername(config.getIdentity());
724 
725         if (TextUtils.isEmpty(config.getPassword())) {
726             Log.e(TAG, "Missing password for user credential");
727             return null;
728         }
729         String encodedPassword =
730                 new String(Base64.encode(config.getPassword().getBytes(StandardCharsets.UTF_8),
731                         Base64.DEFAULT), StandardCharsets.UTF_8);
732         userCredential.setPassword(encodedPassword);
733 
734         switch(config.getPhase2Method()) {
735             case WifiEnterpriseConfig.Phase2.PAP:
736                 userCredential.setNonEapInnerMethod(Credential.UserCredential.AUTH_METHOD_PAP);
737                 break;
738             case WifiEnterpriseConfig.Phase2.MSCHAP:
739                 userCredential.setNonEapInnerMethod(Credential.UserCredential.AUTH_METHOD_MSCHAP);
740                 break;
741             case WifiEnterpriseConfig.Phase2.MSCHAPV2:
742                 userCredential.setNonEapInnerMethod(Credential.UserCredential.AUTH_METHOD_MSCHAPV2);
743                 break;
744             default:
745                 Log.e(TAG, "Unsupported phase2 method for TTLS: " + config.getPhase2Method());
746                 return null;
747         }
748         return userCredential;
749     }
750 
751     /**
752      * Helper function for creating a
753      * {@link android.net.wifi.hotspot2.pps.Credential.SimCredential} from the given
754      * {@link WifiEnterpriseConfig}
755      *
756      * @param eapType The EAP type of the SIM credential
757      * @param config The enterprise configuration containing the credential
758      * @return {@link android.net.wifi.hotspot2.pps.Credential.SimCredential}
759      */
buildSimCredentialFromEnterpriseConfig( int eapType, WifiEnterpriseConfig config)760     private static Credential.SimCredential buildSimCredentialFromEnterpriseConfig(
761             int eapType, WifiEnterpriseConfig config) {
762         Credential.SimCredential simCredential = new Credential.SimCredential();
763         if (TextUtils.isEmpty(config.getPlmn())) {
764             Log.e(TAG, "Missing IMSI for SIM credential");
765             return null;
766         }
767         simCredential.setImsi(config.getPlmn());
768         simCredential.setEapType(eapType);
769         return simCredential;
770     }
771 }
772