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