• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2013 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 package android.net.wifi;
17 
18 import android.annotation.IntDef;
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.annotation.SystemApi;
22 import android.compat.annotation.UnsupportedAppUsage;
23 import android.os.Build;
24 import android.os.Parcel;
25 import android.os.Parcelable;
26 import android.text.TextUtils;
27 import android.util.Log;
28 
29 import androidx.annotation.RequiresApi;
30 
31 import com.android.modules.utils.build.SdkLevel;
32 
33 import java.lang.annotation.Retention;
34 import java.lang.annotation.RetentionPolicy;
35 import java.nio.charset.StandardCharsets;
36 import java.security.PrivateKey;
37 import java.security.cert.X509Certificate;
38 import java.security.interfaces.ECPublicKey;
39 import java.security.interfaces.RSAPublicKey;
40 import java.security.spec.ECParameterSpec;
41 import java.util.Arrays;
42 import java.util.HashMap;
43 import java.util.List;
44 import java.util.Map;
45 
46 /**
47  * Enterprise configuration details for Wi-Fi. Stores details about the EAP method
48  * and any associated credentials.
49  */
50 public class WifiEnterpriseConfig implements Parcelable {
51 
52     /** Key prefix for WAPI AS certificates. */
53     public static final String WAPI_AS_CERTIFICATE = "WAPIAS_";
54 
55     /** Key prefix for WAPI user certificates. */
56     public static final String WAPI_USER_CERTIFICATE = "WAPIUSR_";
57 
58     /**
59      * Intent extra: name for WAPI AS certificates
60      */
61     public static final String EXTRA_WAPI_AS_CERTIFICATE_NAME =
62             "android.net.wifi.extra.WAPI_AS_CERTIFICATE_NAME";
63 
64     /**
65      * Intent extra: data for WAPI AS certificates
66      */
67     public static final String EXTRA_WAPI_AS_CERTIFICATE_DATA =
68             "android.net.wifi.extra.WAPI_AS_CERTIFICATE_DATA";
69 
70     /**
71      * Intent extra: name for WAPI USER certificates
72      */
73     public static final String EXTRA_WAPI_USER_CERTIFICATE_NAME =
74             "android.net.wifi.extra.WAPI_USER_CERTIFICATE_NAME";
75 
76     /**
77      * Intent extra: data for WAPI USER certificates
78      */
79     public static final String EXTRA_WAPI_USER_CERTIFICATE_DATA =
80             "android.net.wifi.extra.WAPI_USER_CERTIFICATE_DATA";
81 
82     /** @hide */
83     public static final String EMPTY_VALUE         = "NULL";
84     /** @hide */
85     public static final String EAP_KEY             = "eap";
86     /** @hide */
87     public static final String PHASE2_KEY          = "phase2";
88     /** @hide */
89     public static final String IDENTITY_KEY        = "identity";
90     /** @hide */
91     public static final String ANON_IDENTITY_KEY   = "anonymous_identity";
92     /** @hide */
93     public static final String PASSWORD_KEY        = "password";
94     /** @hide */
95     public static final String SUBJECT_MATCH_KEY   = "subject_match";
96     /** @hide */
97     public static final String ALTSUBJECT_MATCH_KEY = "altsubject_match";
98     /** @hide */
99     public static final String DOM_SUFFIX_MATCH_KEY = "domain_suffix_match";
100     /** @hide */
101     public static final String OPP_KEY_CACHING     = "proactive_key_caching";
102     /** @hide */
103     public static final String EAP_ERP             = "eap_erp";
104     /** @hide */
105     public static final String OCSP                = "ocsp";
106     /** @hide */
107     public static final String DECORATED_IDENTITY_PREFIX_KEY = "decorated_username_prefix";
108 
109     /**
110      * String representing the keystore OpenSSL ENGINE's ID.
111      * @hide
112      */
113     public static final String ENGINE_ID_KEYSTORE = "keystore";
114 
115     /**
116      * String representing the keystore URI used for wpa_supplicant.
117      * @hide
118      */
119     public static final String KEYSTORE_URI = "keystore://";
120 
121     /**
122      * String representing the keystore URI used for wpa_supplicant,
123      * Unlike #KEYSTORE_URI, this supports a list of space-delimited aliases
124      * @hide
125      */
126     public static final String KEYSTORES_URI = "keystores://";
127 
128     /**
129      * String to set the engine value to when it should be enabled.
130      * @hide
131      */
132     public static final String ENGINE_ENABLE = "1";
133 
134     /**
135      * String to set the engine value to when it should be disabled.
136      * @hide
137      */
138     public static final String ENGINE_DISABLE = "0";
139 
140     /**
141      * Key prefix for CA certificates.
142      * Note: copied from {@link android.security.Credentials#CA_CERTIFICATE} since it is @hide.
143      */
144     private static final String CA_CERTIFICATE = "CACERT_";
145     /**
146      * Key prefix for user certificates.
147      * Note: copied from {@link android.security.Credentials#USER_CERTIFICATE} since it is @hide.
148      */
149     private static final String USER_CERTIFICATE = "USRCERT_";
150     /**
151      * Key prefix for user private and secret keys.
152      * Note: copied from {@link android.security.Credentials#USER_PRIVATE_KEY} since it is @hide.
153      */
154     private static final String USER_PRIVATE_KEY = "USRPKEY_";
155 
156     /** @hide */
157     public static final String CA_CERT_PREFIX = KEYSTORE_URI + CA_CERTIFICATE;
158     /** @hide */
159     public static final String CLIENT_CERT_PREFIX = KEYSTORE_URI + USER_CERTIFICATE;
160     /** @hide */
161     public static final String CLIENT_CERT_KEY     = "client_cert";
162     /** @hide */
163     public static final String CA_CERT_KEY         = "ca_cert";
164     /** @hide */
165     public static final String CA_PATH_KEY         = "ca_path";
166     /** @hide */
167     public static final String ENGINE_KEY          = "engine";
168     /** @hide */
169     public static final String ENGINE_ID_KEY       = "engine_id";
170     /** @hide */
171     public static final String PRIVATE_KEY_ID_KEY  = "key_id";
172     /** @hide */
173     public static final String REALM_KEY           = "realm";
174     /** @hide */
175     public static final String PLMN_KEY            = "plmn";
176     /** @hide */
177     public static final String CA_CERT_ALIAS_DELIMITER = " ";
178     /** @hide */
179     public static final String WAPI_CERT_SUITE_KEY = "wapi_cert_suite";
180 
181     /**
182      * Do not use OCSP stapling (TLS certificate status extension)
183      * @hide
184      */
185     @SystemApi
186     public static final int OCSP_NONE = 0;
187 
188     /**
189      * Try to use OCSP stapling, but not require response
190      * @hide
191      */
192     @SystemApi
193     public static final int OCSP_REQUEST_CERT_STATUS = 1;
194 
195     /**
196      * Require valid OCSP stapling response
197      * @hide
198      */
199     @SystemApi
200     public static final int OCSP_REQUIRE_CERT_STATUS = 2;
201 
202     /**
203      * Require valid OCSP stapling response for all not-trusted certificates in the server
204      * certificate chain.
205      * @apiNote This option is not supported by most SSL libraries and should not be used.
206      * Specifying this option will most likely cause connection failures.
207      * @hide
208      */
209     @SystemApi
210     public static final int OCSP_REQUIRE_ALL_NON_TRUSTED_CERTS_STATUS = 3;
211 
212     /** @hide */
213     @IntDef(prefix = {"OCSP_"}, value = {
214             OCSP_NONE,
215             OCSP_REQUEST_CERT_STATUS,
216             OCSP_REQUIRE_CERT_STATUS,
217             OCSP_REQUIRE_ALL_NON_TRUSTED_CERTS_STATUS
218     })
219     @Retention(RetentionPolicy.SOURCE)
220     public @interface Ocsp {}
221 
222     /**
223      * Whether to use/require OCSP (Online Certificate Status Protocol) to check server certificate.
224      * @hide
225      */
226     private @Ocsp int mOcsp = OCSP_NONE;
227 
228     // Fields to copy verbatim from wpa_supplicant.
229     private static final String[] SUPPLICANT_CONFIG_KEYS = new String[] {
230             IDENTITY_KEY,
231             ANON_IDENTITY_KEY,
232             PASSWORD_KEY,
233             CLIENT_CERT_KEY,
234             CA_CERT_KEY,
235             SUBJECT_MATCH_KEY,
236             ENGINE_KEY,
237             ENGINE_ID_KEY,
238             PRIVATE_KEY_ID_KEY,
239             ALTSUBJECT_MATCH_KEY,
240             DOM_SUFFIX_MATCH_KEY,
241             CA_PATH_KEY
242     };
243 
244     /**
245      * Fields that have unquoted values in {@link #mFields}.
246      */
247     private static final List<String> UNQUOTED_KEYS = Arrays.asList(ENGINE_KEY, OPP_KEY_CACHING,
248                                                                     EAP_ERP);
249 
250     @UnsupportedAppUsage
251     private HashMap<String, String> mFields = new HashMap<String, String>();
252     private X509Certificate[] mCaCerts;
253     private PrivateKey mClientPrivateKey;
254     private X509Certificate[] mClientCertificateChain;
255     private int mEapMethod = Eap.NONE;
256     private int mPhase2Method = Phase2.NONE;
257     private boolean mIsAppInstalledDeviceKeyAndCert = false;
258     private boolean mIsAppInstalledCaCert = false;
259     private String mKeyChainAlias;
260 
261     private static final String TAG = "WifiEnterpriseConfig";
262 
WifiEnterpriseConfig()263     public WifiEnterpriseConfig() {
264         // Do not set defaults so that the enterprise fields that are not changed
265         // by API are not changed underneath
266         // This is essential because an app may not have all fields like password
267         // available. It allows modification of subset of fields.
268 
269     }
270 
271     /**
272      * Copy over the contents of the source WifiEnterpriseConfig object over to this object.
273      *
274      * @param source Source WifiEnterpriseConfig object.
275      * @param ignoreMaskedPassword Set to true to ignore masked password field, false otherwise.
276      * @param mask if |ignoreMaskedPassword| is set, check if the incoming password field is set
277      *             to this value.
278      */
copyFrom(WifiEnterpriseConfig source, boolean ignoreMaskedPassword, String mask)279     private void copyFrom(WifiEnterpriseConfig source, boolean ignoreMaskedPassword, String mask) {
280         for (String key : source.mFields.keySet()) {
281             if (ignoreMaskedPassword && key.equals(PASSWORD_KEY)
282                     && TextUtils.equals(source.mFields.get(key), mask)) {
283                 continue;
284             }
285             mFields.put(key, source.mFields.get(key));
286         }
287         if (source.mCaCerts != null) {
288             mCaCerts = Arrays.copyOf(source.mCaCerts, source.mCaCerts.length);
289         } else {
290             mCaCerts = null;
291         }
292         mClientPrivateKey = source.mClientPrivateKey;
293         if (source.mClientCertificateChain != null) {
294             mClientCertificateChain = Arrays.copyOf(
295                     source.mClientCertificateChain,
296                     source.mClientCertificateChain.length);
297         } else {
298             mClientCertificateChain = null;
299         }
300         mKeyChainAlias = source.mKeyChainAlias;
301         mEapMethod = source.mEapMethod;
302         mPhase2Method = source.mPhase2Method;
303         mIsAppInstalledDeviceKeyAndCert = source.mIsAppInstalledDeviceKeyAndCert;
304         mIsAppInstalledCaCert = source.mIsAppInstalledCaCert;
305         mOcsp = source.mOcsp;
306     }
307 
308     /**
309      * Copy constructor.
310      * This copies over all the fields verbatim (does not ignore masked password fields).
311      *
312      * @param source Source WifiEnterpriseConfig object.
313      */
WifiEnterpriseConfig(WifiEnterpriseConfig source)314     public WifiEnterpriseConfig(WifiEnterpriseConfig source) {
315         copyFrom(source, false, "");
316     }
317 
318     /**
319      * Copy fields from the provided external WifiEnterpriseConfig.
320      * This is needed to handle the WifiEnterpriseConfig objects which were sent by apps with the
321      * password field masked.
322      *
323      * @param externalConfig External WifiEnterpriseConfig object.
324      * @param mask String mask to compare against.
325      * @hide
326      */
copyFromExternal(WifiEnterpriseConfig externalConfig, String mask)327     public void copyFromExternal(WifiEnterpriseConfig externalConfig, String mask) {
328         copyFrom(externalConfig, true, convertToQuotedString(mask));
329     }
330 
331     @Override
describeContents()332     public int describeContents() {
333         return 0;
334     }
335 
336     @Override
writeToParcel(Parcel dest, int flags)337     public void writeToParcel(Parcel dest, int flags) {
338         dest.writeInt(mFields.size());
339         for (Map.Entry<String, String> entry : mFields.entrySet()) {
340             dest.writeString(entry.getKey());
341             dest.writeString(entry.getValue());
342         }
343 
344         dest.writeInt(mEapMethod);
345         dest.writeInt(mPhase2Method);
346         ParcelUtil.writeCertificates(dest, mCaCerts);
347         ParcelUtil.writePrivateKey(dest, mClientPrivateKey);
348         ParcelUtil.writeCertificates(dest, mClientCertificateChain);
349         dest.writeString(mKeyChainAlias);
350         dest.writeBoolean(mIsAppInstalledDeviceKeyAndCert);
351         dest.writeBoolean(mIsAppInstalledCaCert);
352         dest.writeInt(mOcsp);
353     }
354 
355     public static final @android.annotation.NonNull Creator<WifiEnterpriseConfig> CREATOR =
356             new Creator<WifiEnterpriseConfig>() {
357                 @Override
358                 public WifiEnterpriseConfig createFromParcel(Parcel in) {
359                     WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
360                     int count = in.readInt();
361                     for (int i = 0; i < count; i++) {
362                         String key = in.readString();
363                         String value = in.readString();
364                         enterpriseConfig.mFields.put(key, value);
365                     }
366 
367                     enterpriseConfig.mEapMethod = in.readInt();
368                     enterpriseConfig.mPhase2Method = in.readInt();
369                     enterpriseConfig.mCaCerts = ParcelUtil.readCertificates(in);
370                     enterpriseConfig.mClientPrivateKey = ParcelUtil.readPrivateKey(in);
371                     enterpriseConfig.mClientCertificateChain = ParcelUtil.readCertificates(in);
372                     enterpriseConfig.mKeyChainAlias = in.readString();
373                     enterpriseConfig.mIsAppInstalledDeviceKeyAndCert = in.readBoolean();
374                     enterpriseConfig.mIsAppInstalledCaCert = in.readBoolean();
375                     enterpriseConfig.mOcsp = in.readInt();
376                     return enterpriseConfig;
377                 }
378 
379                 @Override
380                 public WifiEnterpriseConfig[] newArray(int size) {
381                     return new WifiEnterpriseConfig[size];
382                 }
383             };
384 
385     /** The Extensible Authentication Protocol method used */
386     public static final class Eap {
387         /** No EAP method used. Represents an empty config */
388         public static final int NONE    = -1;
389         /** Protected EAP */
390         public static final int PEAP    = 0;
391         /** EAP-Transport Layer Security */
392         public static final int TLS     = 1;
393         /** EAP-Tunneled Transport Layer Security */
394         public static final int TTLS    = 2;
395         /** EAP-Password */
396         public static final int PWD     = 3;
397         /** EAP-Subscriber Identity Module [RFC-4186] */
398         public static final int SIM     = 4;
399         /** EAP-Authentication and Key Agreement [RFC-4187] */
400         public static final int AKA     = 5;
401         /** EAP-Authentication and Key Agreement Prime [RFC-5448] */
402         public static final int AKA_PRIME = 6;
403         /** Hotspot 2.0 r2 OSEN */
404         public static final int UNAUTH_TLS = 7;
405         /** WAPI Certificate */
406         public static final int WAPI_CERT = 8;
407         /** @hide */
408         public static final String[] strings =
409                 { "PEAP", "TLS", "TTLS", "PWD", "SIM", "AKA", "AKA'", "WFA-UNAUTH-TLS",
410                         "WAPI_CERT" };
411 
412         /** Prevent initialization */
Eap()413         private Eap() {}
414     }
415 
416     /** The inner authentication method used */
417     public static final class Phase2 {
418         public static final int NONE        = 0;
419         /** Password Authentication Protocol */
420         public static final int PAP         = 1;
421         /** Microsoft Challenge Handshake Authentication Protocol */
422         public static final int MSCHAP      = 2;
423         /** Microsoft Challenge Handshake Authentication Protocol v2 */
424         public static final int MSCHAPV2    = 3;
425         /** Generic Token Card */
426         public static final int GTC         = 4;
427         /** EAP-Subscriber Identity Module [RFC-4186] */
428         public static final int SIM         = 5;
429         /** EAP-Authentication and Key Agreement [RFC-4187] */
430         public static final int AKA         = 6;
431         /** EAP-Authentication and Key Agreement Prime [RFC-5448] */
432         public static final int AKA_PRIME   = 7;
433         private static final String AUTH_PREFIX = "auth=";
434         private static final String AUTHEAP_PREFIX = "autheap=";
435         /** @hide */
436         public static final String[] strings = {EMPTY_VALUE, "PAP", "MSCHAP",
437                 "MSCHAPV2", "GTC", "SIM", "AKA", "AKA'" };
438 
439         /** Prevent initialization */
Phase2()440         private Phase2() {}
441     }
442 
443     // Loader and saver interfaces for exchanging data with wpa_supplicant.
444     // TODO: Decouple this object (which is just a placeholder of the configuration)
445     // from the implementation that knows what wpa_supplicant wants.
446     /**
447      * Interface used for retrieving supplicant configuration from WifiEnterpriseConfig
448      * @hide
449      */
450     public interface SupplicantSaver {
451         /**
452          * Set a value within wpa_supplicant configuration
453          * @param key index to set within wpa_supplciant
454          * @param value the value for the key
455          * @return true if successful; false otherwise
456          */
saveValue(String key, String value)457         boolean saveValue(String key, String value);
458     }
459 
460     /**
461      * Interface used for populating a WifiEnterpriseConfig from supplicant configuration
462      * @hide
463      */
464     public interface SupplicantLoader {
465         /**
466          * Returns a value within wpa_supplicant configuration
467          * @param key index to set within wpa_supplciant
468          * @return string value if successful; null otherwise
469          */
loadValue(String key)470         String loadValue(String key);
471     }
472 
473     /**
474      * Internal use only; supply field values to wpa_supplicant config.  The configuration
475      * process aborts on the first failed call on {@code saver}.
476      * @param saver proxy for setting configuration in wpa_supplciant
477      * @return whether the save succeeded on all attempts
478      * @hide
479      */
saveToSupplicant(SupplicantSaver saver)480     public boolean saveToSupplicant(SupplicantSaver saver) {
481         if (!isEapMethodValid()) {
482             return false;
483         }
484 
485         // wpa_supplicant can update the anonymous identity for these kinds of networks after
486         // framework reads them, so make sure the framework doesn't try to overwrite them.
487         boolean shouldNotWriteAnonIdentity = mEapMethod == WifiEnterpriseConfig.Eap.SIM
488                 || mEapMethod == WifiEnterpriseConfig.Eap.AKA
489                 || mEapMethod == WifiEnterpriseConfig.Eap.AKA_PRIME;
490         for (String key : mFields.keySet()) {
491             if (shouldNotWriteAnonIdentity && ANON_IDENTITY_KEY.equals(key)) {
492                 continue;
493             }
494             if (!saver.saveValue(key, mFields.get(key))) {
495                 return false;
496             }
497         }
498 
499         if (!saver.saveValue(EAP_KEY, Eap.strings[mEapMethod])) {
500             return false;
501         }
502 
503         if (mEapMethod != Eap.TLS && mEapMethod != Eap.UNAUTH_TLS && mPhase2Method != Phase2.NONE) {
504             boolean is_autheap = mEapMethod == Eap.TTLS && mPhase2Method == Phase2.GTC;
505             String prefix = is_autheap ? Phase2.AUTHEAP_PREFIX : Phase2.AUTH_PREFIX;
506             String value = convertToQuotedString(prefix + Phase2.strings[mPhase2Method]);
507             return saver.saveValue(PHASE2_KEY, value);
508         } else if (mPhase2Method == Phase2.NONE) {
509             // By default, send a null phase 2 to clear old configuration values.
510             return saver.saveValue(PHASE2_KEY, null);
511         } else {
512             Log.e(TAG, "WiFi enterprise configuration is invalid as it supplies a "
513                     + "phase 2 method but the phase1 method does not support it.");
514             return false;
515         }
516     }
517 
518     /**
519      * Internal use only; retrieve configuration from wpa_supplicant config.
520      * @param loader proxy for retrieving configuration keys from wpa_supplicant
521      * @hide
522      */
loadFromSupplicant(SupplicantLoader loader)523     public void loadFromSupplicant(SupplicantLoader loader) {
524         for (String key : SUPPLICANT_CONFIG_KEYS) {
525             String value = loader.loadValue(key);
526             if (value == null) {
527                 mFields.put(key, EMPTY_VALUE);
528             } else {
529                 mFields.put(key, value);
530             }
531         }
532         String eapMethod  = loader.loadValue(EAP_KEY);
533         mEapMethod = getStringIndex(Eap.strings, eapMethod, Eap.NONE);
534 
535         String phase2Method = removeDoubleQuotes(loader.loadValue(PHASE2_KEY));
536         // Remove "auth=" or "autheap=" prefix.
537         if (phase2Method.startsWith(Phase2.AUTH_PREFIX)) {
538             phase2Method = phase2Method.substring(Phase2.AUTH_PREFIX.length());
539         } else if (phase2Method.startsWith(Phase2.AUTHEAP_PREFIX)) {
540             phase2Method = phase2Method.substring(Phase2.AUTHEAP_PREFIX.length());
541         }
542         mPhase2Method = getStringIndex(Phase2.strings, phase2Method, Phase2.NONE);
543     }
544 
545     /**
546      * Set the EAP authentication method.
547      * @param  eapMethod is one of {@link Eap}, except for {@link Eap#NONE}
548      * @throws IllegalArgumentException on an invalid eap method
549      */
setEapMethod(int eapMethod)550     public void setEapMethod(int eapMethod) {
551         switch (eapMethod) {
552             /** Valid methods */
553             case Eap.WAPI_CERT:
554                 mEapMethod = eapMethod;
555                 setPhase2Method(Phase2.NONE);
556                 break;
557             case Eap.TLS:
558             case Eap.UNAUTH_TLS:
559                 setPhase2Method(Phase2.NONE);
560                 /* fall through */
561             case Eap.PEAP:
562             case Eap.PWD:
563             case Eap.TTLS:
564             case Eap.SIM:
565             case Eap.AKA:
566             case Eap.AKA_PRIME:
567                 mEapMethod = eapMethod;
568                 setFieldValue(OPP_KEY_CACHING, "1");
569                 break;
570             default:
571                 throw new IllegalArgumentException("Unknown EAP method");
572         }
573     }
574 
575     /**
576      * Get the eap method.
577      * @return eap method configured
578      */
getEapMethod()579     public int getEapMethod() {
580         return mEapMethod;
581     }
582 
583     /**
584      * Set Phase 2 authentication method. Sets the inner authentication method to be used in
585      * phase 2 after setting up a secure channel
586      * @param phase2Method is the inner authentication method and can be one of {@link Phase2}
587      * @throws IllegalArgumentException on an invalid phase2 method
588      */
setPhase2Method(int phase2Method)589     public void setPhase2Method(int phase2Method) {
590         switch (phase2Method) {
591             case Phase2.NONE:
592             case Phase2.PAP:
593             case Phase2.MSCHAP:
594             case Phase2.MSCHAPV2:
595             case Phase2.GTC:
596             case Phase2.SIM:
597             case Phase2.AKA:
598             case Phase2.AKA_PRIME:
599                 mPhase2Method = phase2Method;
600                 break;
601             default:
602                 throw new IllegalArgumentException("Unknown Phase 2 method");
603         }
604     }
605 
606     /**
607      * Get the phase 2 authentication method.
608      * @return a phase 2 method defined at {@link Phase2}
609      * */
getPhase2Method()610     public int getPhase2Method() {
611         return mPhase2Method;
612     }
613 
614     /**
615      * Set the identity
616      * @param identity
617      */
setIdentity(String identity)618     public void setIdentity(String identity) {
619         setFieldValue(IDENTITY_KEY, identity, "");
620     }
621 
622     /**
623      * Get the identity
624      * @return the identity
625      */
getIdentity()626     public String getIdentity() {
627         return getFieldValue(IDENTITY_KEY);
628     }
629 
630     /**
631      * Set anonymous identity. This is used as the unencrypted identity with
632      * certain EAP types
633      * @param anonymousIdentity the anonymous identity
634      */
setAnonymousIdentity(String anonymousIdentity)635     public void setAnonymousIdentity(String anonymousIdentity) {
636         setFieldValue(ANON_IDENTITY_KEY, anonymousIdentity);
637     }
638 
639     /**
640      * Get the anonymous identity
641      * @return anonymous identity
642      */
getAnonymousIdentity()643     public String getAnonymousIdentity() {
644         return getFieldValue(ANON_IDENTITY_KEY);
645     }
646 
647     /**
648      * Set the password.
649      * @param password the password
650      */
setPassword(String password)651     public void setPassword(String password) {
652         setFieldValue(PASSWORD_KEY, password);
653     }
654 
655     /**
656      * Get the password.
657      *
658      * Returns locally set password value. For networks fetched from
659      * framework, returns "*".
660      */
getPassword()661     public String getPassword() {
662         return getFieldValue(PASSWORD_KEY);
663     }
664 
665     /**
666      * Encode a CA certificate alias so it does not contain illegal character.
667      * @hide
668      */
encodeCaCertificateAlias(String alias)669     public static String encodeCaCertificateAlias(String alias) {
670         byte[] bytes = alias.getBytes(StandardCharsets.UTF_8);
671         StringBuilder sb = new StringBuilder(bytes.length * 2);
672         for (byte o : bytes) {
673             sb.append(String.format("%02x", o & 0xFF));
674         }
675         return sb.toString();
676     }
677 
678     /**
679      * Decode a previously-encoded CA certificate alias.
680      * @hide
681      */
decodeCaCertificateAlias(String alias)682     public static String decodeCaCertificateAlias(String alias) {
683         byte[] data = new byte[alias.length() >> 1];
684         for (int n = 0, position = 0; n < alias.length(); n += 2, position++) {
685             data[position] = (byte) Integer.parseInt(alias.substring(n,  n + 2), 16);
686         }
687         try {
688             return new String(data, StandardCharsets.UTF_8);
689         } catch (NumberFormatException e) {
690             e.printStackTrace();
691             return alias;
692         }
693     }
694 
695     /**
696      * Set CA certificate alias.
697      *
698      * <p> See the {@link android.security.KeyChain} for details on installing or choosing
699      * a certificate
700      * </p>
701      * @param alias identifies the certificate
702      * @hide
703      */
704     @UnsupportedAppUsage
setCaCertificateAlias(String alias)705     public void setCaCertificateAlias(String alias) {
706         setFieldValue(CA_CERT_KEY, alias, CA_CERT_PREFIX);
707     }
708 
709     /**
710      * Set CA certificate aliases. When creating installing the corresponding certificate to
711      * the keystore, please use alias encoded by {@link #encodeCaCertificateAlias(String)}.
712      *
713      * <p> See the {@link android.security.KeyChain} for details on installing or choosing
714      * a certificate.
715      * </p>
716      * @param aliases identifies the certificate. Can be null to indicate the absence of a
717      *                certificate.
718      * @hide
719      */
720     @SystemApi
setCaCertificateAliases(@ullable String[] aliases)721     public void setCaCertificateAliases(@Nullable String[] aliases) {
722         if (aliases == null) {
723             setFieldValue(CA_CERT_KEY, null, CA_CERT_PREFIX);
724         } else if (aliases.length == 1) {
725             // Backwards compatibility: use the original cert prefix if setting only one alias.
726             setCaCertificateAlias(aliases[0]);
727         } else {
728             // Use KEYSTORES_URI which supports multiple aliases.
729             StringBuilder sb = new StringBuilder();
730             for (int i = 0; i < aliases.length; i++) {
731                 if (i > 0) {
732                     sb.append(CA_CERT_ALIAS_DELIMITER);
733                 }
734                 sb.append(encodeCaCertificateAlias(CA_CERTIFICATE + aliases[i]));
735             }
736             setFieldValue(CA_CERT_KEY, sb.toString(), KEYSTORES_URI);
737         }
738     }
739 
740     /**
741      * Get CA certificate alias
742      * @return alias to the CA certificate
743      * @hide
744      */
745     @UnsupportedAppUsage
getCaCertificateAlias()746     public String getCaCertificateAlias() {
747         return getFieldValue(CA_CERT_KEY, CA_CERT_PREFIX);
748     }
749 
750     /**
751      * Get CA certificate aliases.
752      * @return alias to the CA certificate, or null if unset.
753      * @hide
754      */
755     @Nullable
756     @SystemApi
getCaCertificateAliases()757     public String[] getCaCertificateAliases() {
758         String value = getFieldValue(CA_CERT_KEY);
759         if (value.startsWith(CA_CERT_PREFIX)) {
760             // Backwards compatibility: parse the original alias prefix.
761             return new String[] {getFieldValue(CA_CERT_KEY, CA_CERT_PREFIX)};
762         } else if (value.startsWith(KEYSTORES_URI)) {
763             String values = value.substring(KEYSTORES_URI.length());
764 
765             String[] aliases = TextUtils.split(values, CA_CERT_ALIAS_DELIMITER);
766             for (int i = 0; i < aliases.length; i++) {
767                 aliases[i] = decodeCaCertificateAlias(aliases[i]);
768                 if (aliases[i].startsWith(CA_CERTIFICATE)) {
769                     aliases[i] = aliases[i].substring(CA_CERTIFICATE.length());
770                 }
771             }
772             return aliases.length != 0 ? aliases : null;
773         } else {
774             return TextUtils.isEmpty(value) ? null : new String[] {value};
775         }
776     }
777 
778     /**
779      * Specify a X.509 certificate that identifies the server.
780      *
781      * <p>A default name is automatically assigned to the certificate and used
782      * with this configuration. The framework takes care of installing the
783      * certificate when the config is saved and removing the certificate when
784      * the config is removed.
785      *
786      * Note: If no certificate is set for an Enterprise configuration, either by not calling this
787      * API (or the {@link #setCaCertificates(X509Certificate[])}, or by calling it with null, then
788      * the server certificate validation is skipped - which means that the connection is not secure.
789      *
790      * @param cert X.509 CA certificate
791      * @throws IllegalArgumentException if not a CA certificate
792      */
setCaCertificate(@ullable X509Certificate cert)793     public void setCaCertificate(@Nullable X509Certificate cert) {
794         if (cert != null) {
795             if (cert.getBasicConstraints() >= 0) {
796                 mIsAppInstalledCaCert = true;
797                 mCaCerts = new X509Certificate[] {cert};
798             } else {
799                 mCaCerts = null;
800                 throw new IllegalArgumentException("Not a CA certificate");
801             }
802         } else {
803             mCaCerts = null;
804         }
805     }
806 
807     /**
808      * Get CA certificate. If multiple CA certificates are configured previously,
809      * return the first one.
810      * @return X.509 CA certificate
811      */
getCaCertificate()812     @Nullable public X509Certificate getCaCertificate() {
813         if (mCaCerts != null && mCaCerts.length > 0) {
814             return mCaCerts[0];
815         } else {
816             return null;
817         }
818     }
819 
820     /**
821      * Specify a list of X.509 certificates that identifies the server. The validation
822      * passes if the CA of server certificate matches one of the given certificates.
823 
824      * <p>Default names are automatically assigned to the certificates and used
825      * with this configuration. The framework takes care of installing the
826      * certificates when the config is saved and removing the certificates when
827      * the config is removed.
828      *
829      * Note: If no certificates are set for an Enterprise configuration, either by not calling this
830      * API (or the {@link #setCaCertificate(X509Certificate)}, or by calling it with null, then the
831      * server certificate validation is skipped - which means that the
832      * connection is not secure.
833      *
834      * @param certs X.509 CA certificates
835      * @throws IllegalArgumentException if any of the provided certificates is
836      *     not a CA certificate
837      */
setCaCertificates(@ullable X509Certificate[] certs)838     public void setCaCertificates(@Nullable X509Certificate[] certs) {
839         if (certs != null) {
840             X509Certificate[] newCerts = new X509Certificate[certs.length];
841             for (int i = 0; i < certs.length; i++) {
842                 if (certs[i].getBasicConstraints() >= 0) {
843                     newCerts[i] = certs[i];
844                 } else {
845                     mCaCerts = null;
846                     throw new IllegalArgumentException("Not a CA certificate");
847                 }
848             }
849             mCaCerts = newCerts;
850             mIsAppInstalledCaCert = true;
851         } else {
852             mCaCerts = null;
853         }
854     }
855 
856     /**
857      * Get CA certificates.
858      */
getCaCertificates()859     @Nullable public X509Certificate[] getCaCertificates() {
860         if (mCaCerts != null && mCaCerts.length > 0) {
861             return mCaCerts;
862         } else {
863             return null;
864         }
865     }
866 
867     /**
868      * @hide
869      */
resetCaCertificate()870     public void resetCaCertificate() {
871         mCaCerts = null;
872     }
873 
874     /**
875      * Set the ca_path directive on wpa_supplicant.
876      *
877      * From wpa_supplicant documentation:
878      *
879      * Directory path for CA certificate files (PEM). This path may contain
880      * multiple CA certificates in OpenSSL format. Common use for this is to
881      * point to system trusted CA list which is often installed into directory
882      * like /etc/ssl/certs. If configured, these certificates are added to the
883      * list of trusted CAs. ca_cert may also be included in that case, but it is
884      * not required.
885      *
886      * Note: If no certificate path is set for an Enterprise configuration, either by not calling
887      * this API, or by calling it with null, and no certificate is set by
888      * {@link #setCaCertificate(X509Certificate)} or {@link #setCaCertificates(X509Certificate[])},
889      * then the server certificate validation is skipped - which means that the connection is not
890      * secure.
891      *
892      * @param path The path for CA certificate files, or empty string to clear.
893      * @hide
894      */
895     @SystemApi
setCaPath(@onNull String path)896     public void setCaPath(@NonNull String path) {
897         setFieldValue(CA_PATH_KEY, path);
898     }
899 
900     /**
901      * Get the ca_path directive from wpa_supplicant.
902      * @return The path for CA certificate files, or an empty string if unset.
903      * @hide
904      */
905     @NonNull
906     @SystemApi
getCaPath()907     public String getCaPath() {
908         return getFieldValue(CA_PATH_KEY);
909     }
910 
911     /**
912      * Set Client certificate alias.
913      *
914      * <p> See the {@link android.security.KeyChain} for details on installing or choosing
915      * a certificate
916      * </p>
917      * @param alias identifies the certificate, or empty string to clear.
918      * @hide
919      */
920     @SystemApi
setClientCertificateAlias(@onNull String alias)921     public void setClientCertificateAlias(@NonNull String alias) {
922         setFieldValue(CLIENT_CERT_KEY, alias, CLIENT_CERT_PREFIX);
923         setFieldValue(PRIVATE_KEY_ID_KEY, alias, USER_PRIVATE_KEY);
924         // Also, set engine parameters
925         if (TextUtils.isEmpty(alias)) {
926             setFieldValue(ENGINE_KEY, ENGINE_DISABLE);
927             setFieldValue(ENGINE_ID_KEY, "");
928         } else {
929             setFieldValue(ENGINE_KEY, ENGINE_ENABLE);
930             setFieldValue(ENGINE_ID_KEY, ENGINE_ID_KEYSTORE);
931         }
932     }
933 
934     /**
935      * Get client certificate alias.
936      * @return alias to the client certificate, or an empty string if unset.
937      * @hide
938      */
939     @NonNull
940     @SystemApi
getClientCertificateAlias()941     public String getClientCertificateAlias() {
942         return getFieldValue(CLIENT_CERT_KEY, CLIENT_CERT_PREFIX);
943     }
944 
945     /**
946      * Specify a private key and client certificate for client authorization.
947      *
948      * <p>A default name is automatically assigned to the key entry and used
949      * with this configuration.  The framework takes care of installing the
950      * key entry when the config is saved and removing the key entry when
951      * the config is removed.
952 
953      * @param privateKey a PrivateKey instance for the end certificate.
954      * @param clientCertificate an X509Certificate representing the end certificate.
955      * @throws IllegalArgumentException for an invalid key or certificate.
956      */
setClientKeyEntry(PrivateKey privateKey, X509Certificate clientCertificate)957     public void setClientKeyEntry(PrivateKey privateKey, X509Certificate clientCertificate) {
958         X509Certificate[] clientCertificates = null;
959         if (clientCertificate != null) {
960             clientCertificates = new X509Certificate[] {clientCertificate};
961         }
962         setClientKeyEntryWithCertificateChain(privateKey, clientCertificates);
963     }
964 
965     /**
966      * Specify a private key and client certificate chain for client authorization.
967      *
968      * <p>A default name is automatically assigned to the key entry and used
969      * with this configuration.  The framework takes care of installing the
970      * key entry when the config is saved and removing the key entry when
971      * the config is removed.
972      *
973      * @param privateKey a PrivateKey instance for the end certificate.
974      * @param clientCertificateChain an array of X509Certificate instances which starts with
975      *         end certificate and continues with additional CA certificates necessary to
976      *         link the end certificate with some root certificate known by the authenticator.
977      * @throws IllegalArgumentException for an invalid key or certificate.
978      */
setClientKeyEntryWithCertificateChain(PrivateKey privateKey, X509Certificate[] clientCertificateChain)979     public void setClientKeyEntryWithCertificateChain(PrivateKey privateKey,
980             X509Certificate[] clientCertificateChain) {
981         X509Certificate[] newCerts = null;
982         if (clientCertificateChain != null && clientCertificateChain.length > 0) {
983             // We validate that this is a well formed chain that starts
984             // with an end-certificate and is followed by CA certificates.
985             // We don't validate that each following certificate verifies
986             // the previous. https://en.wikipedia.org/wiki/Chain_of_trust
987             //
988             // Basic constraints is an X.509 extension type that defines
989             // whether a given certificate is allowed to sign additional
990             // certificates and what path length restrictions may exist.
991             // We use this to judge whether the certificate is an end
992             // certificate or a CA certificate.
993             // https://cryptography.io/en/latest/x509/reference/
994             if (clientCertificateChain[0].getBasicConstraints() != -1) {
995                 throw new IllegalArgumentException(
996                         "First certificate in the chain must be a client end certificate");
997             }
998 
999             for (int i = 1; i < clientCertificateChain.length; i++) {
1000                 if (clientCertificateChain[i].getBasicConstraints() == -1) {
1001                     throw new IllegalArgumentException(
1002                             "All certificates following the first must be CA certificates");
1003                 }
1004             }
1005             newCerts = Arrays.copyOf(clientCertificateChain,
1006                     clientCertificateChain.length);
1007 
1008             if (privateKey == null) {
1009                 throw new IllegalArgumentException("Client cert without a private key");
1010             }
1011             if (privateKey.getEncoded() == null) {
1012                 throw new IllegalArgumentException("Private key cannot be encoded");
1013             }
1014         }
1015 
1016         mClientPrivateKey = privateKey;
1017         mClientCertificateChain = newCerts;
1018         mIsAppInstalledDeviceKeyAndCert = true;
1019     }
1020 
1021     /**
1022      * Specify a key pair via KeyChain alias for client authentication.
1023      *
1024      * The alias should refer to a key pair in KeyChain that is allowed for WiFi authentication.
1025      *
1026      * @param alias key pair alias
1027      * @see android.app.admin.DevicePolicyManager#grantKeyPairToWifiAuth(String)
1028      */
1029     @RequiresApi(Build.VERSION_CODES.S)
setClientKeyPairAlias(@onNull String alias)1030     public void setClientKeyPairAlias(@NonNull String alias) {
1031         if (!SdkLevel.isAtLeastS()) {
1032             throw new UnsupportedOperationException();
1033         }
1034         mKeyChainAlias = alias;
1035     }
1036 
1037     /**
1038      * Get KeyChain alias to use for client authentication.
1039      */
1040     @RequiresApi(Build.VERSION_CODES.S)
getClientKeyPairAlias()1041     public @Nullable String getClientKeyPairAlias() {
1042         if (!SdkLevel.isAtLeastS()) {
1043             throw new UnsupportedOperationException();
1044         }
1045         return mKeyChainAlias;
1046     }
1047 
1048     /**
1049      * Get KeyChain alias to use for client authentication without SDK check.
1050      * @hide
1051      */
getClientKeyPairAliasInternal()1052     public @Nullable String getClientKeyPairAliasInternal() {
1053         return mKeyChainAlias;
1054     }
1055 
1056     /**
1057      * Get client certificate
1058      *
1059      * @return X.509 client certificate
1060      */
getClientCertificate()1061     public X509Certificate getClientCertificate() {
1062         if (mClientCertificateChain != null && mClientCertificateChain.length > 0) {
1063             return mClientCertificateChain[0];
1064         } else {
1065             return null;
1066         }
1067     }
1068 
1069     /**
1070      * Get the complete client certificate chain in the same order as it was last supplied.
1071      *
1072      * <p>If the chain was last supplied by a call to
1073      * {@link #setClientKeyEntry(java.security.PrivateKey, java.security.cert.X509Certificate)}
1074      * with a non-null * certificate instance, a single-element array containing the certificate
1075      * will be * returned. If {@link #setClientKeyEntryWithCertificateChain(
1076      * java.security.PrivateKey, java.security.cert.X509Certificate[])} was last called with a
1077      * non-empty array, this array will be returned in the same order as it was supplied.
1078      * Otherwise, {@code null} will be returned.
1079      *
1080      * @return X.509 client certificates
1081      */
getClientCertificateChain()1082     @Nullable public X509Certificate[] getClientCertificateChain() {
1083         if (mClientCertificateChain != null && mClientCertificateChain.length > 0) {
1084             return mClientCertificateChain;
1085         } else {
1086             return null;
1087         }
1088     }
1089 
1090     /**
1091      * @hide
1092      */
resetClientKeyEntry()1093     public void resetClientKeyEntry() {
1094         mClientPrivateKey = null;
1095         mClientCertificateChain = null;
1096     }
1097 
1098     /**
1099      * Get the client private key as supplied in {@link #setClientKeyEntryWithCertificateChain}, or
1100      * null if unset.
1101      */
1102     @Nullable
getClientPrivateKey()1103     public PrivateKey getClientPrivateKey() {
1104         return mClientPrivateKey;
1105     }
1106 
1107     /**
1108      * Set subject match (deprecated). This is the substring to be matched against the subject of
1109      * the authentication server certificate.
1110      * @param subjectMatch substring to be matched
1111      * @deprecated in favor of altSubjectMatch
1112      */
setSubjectMatch(String subjectMatch)1113     public void setSubjectMatch(String subjectMatch) {
1114         setFieldValue(SUBJECT_MATCH_KEY, subjectMatch);
1115     }
1116 
1117     /**
1118      * Get subject match (deprecated)
1119      * @return the subject match string
1120      * @deprecated in favor of altSubjectMatch
1121      */
getSubjectMatch()1122     public String getSubjectMatch() {
1123         return getFieldValue(SUBJECT_MATCH_KEY);
1124     }
1125 
1126     /**
1127      * Set alternate subject match. This is the substring to be matched against the
1128      * alternate subject of the authentication server certificate.
1129      *
1130      * Note: If no alternate subject is set for an Enterprise configuration, either by not calling
1131      * this API, or by calling it with null, or not setting domain suffix match using the
1132      * {@link #setDomainSuffixMatch(String)}, then the server certificate validation is incomplete -
1133      * which means that the connection is not secure.
1134      *
1135      * @param altSubjectMatch substring to be matched, for example
1136      *                     DNS:server.example.com;EMAIL:server@example.com
1137      */
setAltSubjectMatch(String altSubjectMatch)1138     public void setAltSubjectMatch(String altSubjectMatch) {
1139         setFieldValue(ALTSUBJECT_MATCH_KEY, altSubjectMatch);
1140     }
1141 
1142     /**
1143      * Get alternate subject match
1144      * @return the alternate subject match string
1145      */
getAltSubjectMatch()1146     public String getAltSubjectMatch() {
1147         return getFieldValue(ALTSUBJECT_MATCH_KEY);
1148     }
1149 
1150     /**
1151      * Set the domain_suffix_match directive on wpa_supplicant. This is the parameter to use
1152      * for Hotspot 2.0 defined matching of AAA server certs per WFA HS2.0 spec, section 7.3.3.2,
1153      * second paragraph.
1154      *
1155      * <p>From wpa_supplicant documentation:
1156      * <p>Constraint for server domain name. If set, this FQDN is used as a suffix match requirement
1157      * for the AAAserver certificate in SubjectAltName dNSName element(s). If a matching dNSName is
1158      * found, this constraint is met.
1159      * <p>Suffix match here means that the host/domain name is compared one label at a time starting
1160      * from the top-level domain and all the labels in domain_suffix_match shall be included in the
1161      * certificate. The certificate may include additional sub-level labels in addition to the
1162      * required labels.
1163      * <p>More than one match string can be provided by using semicolons to separate the strings
1164      * (e.g., example.org;example.com). When multiple strings are specified, a match with any one of
1165      * the values is considered a sufficient match for the certificate, i.e., the conditions are
1166      * ORed ogether.
1167      * <p>For example, domain_suffix_match=example.com would match test.example.com but would not
1168      * match test-example.com.
1169      *
1170      * Note: If no domain suffix is set for an Enterprise configuration, either by not calling this
1171      * API, or by calling it with null, or not setting alternate subject match using the
1172      * {@link #setAltSubjectMatch(String)}, then the server certificate
1173      * validation is incomplete - which means that the connection is not secure.
1174      *
1175      * @param domain The domain value
1176      */
setDomainSuffixMatch(String domain)1177     public void setDomainSuffixMatch(String domain) {
1178         setFieldValue(DOM_SUFFIX_MATCH_KEY, domain);
1179     }
1180 
1181     /**
1182      * Get the domain_suffix_match value. See setDomSuffixMatch.
1183      * @return The domain value.
1184      */
getDomainSuffixMatch()1185     public String getDomainSuffixMatch() {
1186         return getFieldValue(DOM_SUFFIX_MATCH_KEY);
1187     }
1188 
1189     /**
1190      * Set realm for Passpoint credential; realm identifies a set of networks where your
1191      * Passpoint credential can be used
1192      * @param realm the realm
1193      */
setRealm(String realm)1194     public void setRealm(String realm) {
1195         setFieldValue(REALM_KEY, realm);
1196     }
1197 
1198     /**
1199      * Get realm for Passpoint credential; see {@link #setRealm(String)} for more information
1200      * @return the realm
1201      */
getRealm()1202     public String getRealm() {
1203         return getFieldValue(REALM_KEY);
1204     }
1205 
1206     /**
1207      * Set plmn (Public Land Mobile Network) of the provider of Passpoint credential
1208      * @param plmn the plmn value derived from mcc (mobile country code) & mnc (mobile network code)
1209      */
setPlmn(String plmn)1210     public void setPlmn(String plmn) {
1211         setFieldValue(PLMN_KEY, plmn);
1212     }
1213 
1214     /**
1215      * Get plmn (Public Land Mobile Network) for Passpoint credential; see {@link #setPlmn
1216      * (String)} for more information
1217      * @return the plmn
1218      */
getPlmn()1219     public String getPlmn() {
1220         return getFieldValue(PLMN_KEY);
1221     }
1222 
1223     /** See {@link WifiConfiguration#getKeyIdForCredentials} @hide */
getKeyId(WifiEnterpriseConfig current)1224     public String getKeyId(WifiEnterpriseConfig current) {
1225         // If EAP method is not initialized, use current config details
1226         if (mEapMethod == Eap.NONE) {
1227             return (current != null) ? current.getKeyId(null) : EMPTY_VALUE;
1228         }
1229         if (!isEapMethodValid()) {
1230             return EMPTY_VALUE;
1231         }
1232         return Eap.strings[mEapMethod] + "_" + Phase2.strings[mPhase2Method];
1233     }
1234 
removeDoubleQuotes(String string)1235     private String removeDoubleQuotes(String string) {
1236         if (TextUtils.isEmpty(string)) return "";
1237         int length = string.length();
1238         if ((length > 1) && (string.charAt(0) == '"')
1239                 && (string.charAt(length - 1) == '"')) {
1240             return string.substring(1, length - 1);
1241         }
1242         return string;
1243     }
1244 
convertToQuotedString(String string)1245     private String convertToQuotedString(String string) {
1246         return "\"" + string + "\"";
1247     }
1248 
1249     /**
1250      * Returns the index at which the toBeFound string is found in the array.
1251      * @param arr array of strings
1252      * @param toBeFound string to be found
1253      * @param defaultIndex default index to be returned when string is not found
1254      * @return the index into array
1255      */
getStringIndex(String arr[], String toBeFound, int defaultIndex)1256     private int getStringIndex(String arr[], String toBeFound, int defaultIndex) {
1257         if (TextUtils.isEmpty(toBeFound)) return defaultIndex;
1258         for (int i = 0; i < arr.length; i++) {
1259             if (toBeFound.equals(arr[i])) return i;
1260         }
1261         return defaultIndex;
1262     }
1263 
1264     /**
1265      * Returns the field value for the key with prefix removed.
1266      * @param key into the hash
1267      * @param prefix is the prefix that the value may have
1268      * @return value
1269      * @hide
1270      */
getFieldValue(String key, String prefix)1271     private String getFieldValue(String key, String prefix) {
1272         // TODO: Should raise an exception if |key| is EAP_KEY or PHASE2_KEY since
1273         // neither of these keys should be retrieved in this manner.
1274         String value = mFields.get(key);
1275         // Uninitialized or known to be empty after reading from supplicant
1276         if (TextUtils.isEmpty(value) || EMPTY_VALUE.equals(value)) return "";
1277 
1278         value = removeDoubleQuotes(value);
1279         if (value.startsWith(prefix)) {
1280             return value.substring(prefix.length());
1281         } else {
1282             return value;
1283         }
1284     }
1285 
1286     /**
1287      * Returns the field value for the key.
1288      * @param key into the hash
1289      * @return value
1290      * @hide
1291      */
getFieldValue(String key)1292     public String getFieldValue(String key) {
1293         return getFieldValue(key, "");
1294     }
1295 
1296     /**
1297      * Set a value with an optional prefix at key
1298      * @param key into the hash
1299      * @param value to be set
1300      * @param prefix an optional value to be prefixed to actual value
1301      * @hide
1302      */
setFieldValue(String key, String value, String prefix)1303     private void setFieldValue(String key, String value, String prefix) {
1304         // TODO: Should raise an exception if |key| is EAP_KEY or PHASE2_KEY since
1305         // neither of these keys should be set in this manner.
1306         if (TextUtils.isEmpty(value)) {
1307             mFields.put(key, EMPTY_VALUE);
1308         } else {
1309             String valueToSet;
1310             if (!UNQUOTED_KEYS.contains(key)) {
1311                 valueToSet = convertToQuotedString(prefix + value);
1312             } else {
1313                 valueToSet = prefix + value;
1314             }
1315             mFields.put(key, valueToSet);
1316         }
1317     }
1318 
1319     /**
1320      * Set a value at key
1321      * @param key into the hash
1322      * @param value to be set
1323      * @hide
1324      */
setFieldValue(String key, String value)1325     public void setFieldValue(String key, String value) {
1326         setFieldValue(key, value, "");
1327     }
1328 
1329     @Override
toString()1330     public String toString() {
1331         StringBuffer sb = new StringBuffer();
1332         for (String key : mFields.keySet()) {
1333             // Don't display password in toString().
1334             String value = PASSWORD_KEY.equals(key) ? "<removed>" : mFields.get(key);
1335             sb.append(key).append(" ").append(value).append("\n");
1336         }
1337         if (mEapMethod >= 0 && mEapMethod < Eap.strings.length) {
1338             sb.append("eap_method: ").append(Eap.strings[mEapMethod]).append("\n");
1339         }
1340         if (mPhase2Method > 0 && mPhase2Method < Phase2.strings.length) {
1341             sb.append("phase2_method: ").append(Phase2.strings[mPhase2Method]).append("\n");
1342         }
1343         sb.append(" ocsp: ").append(mOcsp).append("\n");
1344         return sb.toString();
1345     }
1346 
1347     /**
1348      * Returns whether the EAP method data is valid, i.e., whether mEapMethod and mPhase2Method
1349      * are valid indices into {@code Eap.strings[]} and {@code Phase2.strings[]} respectively.
1350      */
isEapMethodValid()1351     private boolean isEapMethodValid() {
1352         if (mEapMethod == Eap.NONE) {
1353             Log.e(TAG, "WiFi enterprise configuration is invalid as it supplies no EAP method.");
1354             return false;
1355         }
1356         if (mEapMethod < 0 || mEapMethod >= Eap.strings.length) {
1357             Log.e(TAG, "mEapMethod is invald for WiFi enterprise configuration: " + mEapMethod);
1358             return false;
1359         }
1360         if (mPhase2Method < 0 || mPhase2Method >= Phase2.strings.length) {
1361             Log.e(TAG, "mPhase2Method is invald for WiFi enterprise configuration: "
1362                     + mPhase2Method);
1363             return false;
1364         }
1365         return true;
1366     }
1367 
1368     /**
1369      * Check if certificate was installed by an app, or manually (not by an app). If true,
1370      * certificate and keys will be removed from key storage when this network is removed. If not,
1371      * then certificates and keys remain persistent until the user manually removes them.
1372      *
1373      * @return true if certificate was installed by an app, false if certificate was installed
1374      * manually by the user.
1375      * @hide
1376      */
isAppInstalledDeviceKeyAndCert()1377     public boolean isAppInstalledDeviceKeyAndCert() {
1378         return mIsAppInstalledDeviceKeyAndCert;
1379     }
1380 
1381     /**
1382      * Initialize the value of the app installed device key and cert flag.
1383      *
1384      * @param isAppInstalledDeviceKeyAndCert true or false
1385      * @hide
1386      */
initIsAppInstalledDeviceKeyAndCert(boolean isAppInstalledDeviceKeyAndCert)1387     public void initIsAppInstalledDeviceKeyAndCert(boolean isAppInstalledDeviceKeyAndCert) {
1388         mIsAppInstalledDeviceKeyAndCert = isAppInstalledDeviceKeyAndCert;
1389     }
1390 
1391     /**
1392      * Check if CA certificate was installed by an app, or manually (not by an app). If true,
1393      * CA certificate will be removed from key storage when this network is removed. If not,
1394      * then certificates and keys remain persistent until the user manually removes them.
1395      *
1396      * @return true if CA certificate was installed by an app, false if CA certificate was installed
1397      * manually by the user.
1398      * @hide
1399      */
isAppInstalledCaCert()1400     public boolean isAppInstalledCaCert() {
1401         return mIsAppInstalledCaCert;
1402     }
1403 
1404     /**
1405      * Initialize the value of the app installed root CA cert flag.
1406      *
1407      * @param isAppInstalledCaCert true or false
1408      * @hide
1409      */
initIsAppInstalledCaCert(boolean isAppInstalledCaCert)1410     public void initIsAppInstalledCaCert(boolean isAppInstalledCaCert) {
1411         mIsAppInstalledCaCert = isAppInstalledCaCert;
1412     }
1413 
1414     /**
1415      * Set the OCSP type.
1416      * @param ocsp is one of {@link ##OCSP_NONE}, {@link #OCSP_REQUEST_CERT_STATUS},
1417      *                   {@link #OCSP_REQUIRE_CERT_STATUS} or
1418      *                   {@link #OCSP_REQUIRE_ALL_NON_TRUSTED_CERTS_STATUS}
1419      * @throws IllegalArgumentException if the OCSP type is invalid
1420      * @hide
1421      */
1422     @SystemApi
setOcsp(@csp int ocsp)1423     public void setOcsp(@Ocsp int ocsp) {
1424         if (ocsp >= OCSP_NONE && ocsp <= OCSP_REQUIRE_ALL_NON_TRUSTED_CERTS_STATUS) {
1425             mOcsp = ocsp;
1426         } else {
1427             throw new IllegalArgumentException("Invalid OCSP type.");
1428         }
1429     }
1430 
1431     /**
1432      * Get the OCSP type.
1433      * @hide
1434      */
1435     @SystemApi
getOcsp()1436     public @Ocsp int getOcsp() {
1437         return mOcsp;
1438     }
1439 
1440     /**
1441      * Utility method to determine whether the configuration's authentication method is SIM-based.
1442      *
1443      * @return true if the credential information requires SIM card for current authentication
1444      * method, otherwise it returns false.
1445      */
isAuthenticationSimBased()1446     public boolean isAuthenticationSimBased() {
1447         if (mEapMethod == Eap.SIM || mEapMethod == Eap.AKA || mEapMethod == Eap.AKA_PRIME) {
1448             return true;
1449         }
1450         if (mEapMethod == Eap.PEAP) {
1451             return mPhase2Method == Phase2.SIM || mPhase2Method == Phase2.AKA
1452                     || mPhase2Method == Phase2.AKA_PRIME;
1453         }
1454         return false;
1455     }
1456 
1457     /**
1458      * Set the WAPI certificate suite name on wpa_supplicant.
1459      *
1460      * If this field is not specified, WAPI-CERT uses ASU ID from WAI packet
1461      * as the certificate suite name automatically.
1462      *
1463      * @param wapiCertSuite The name for WAPI certificate suite, or empty string to clear.
1464      * @hide
1465      */
1466     @SystemApi
setWapiCertSuite(@onNull String wapiCertSuite)1467     public void setWapiCertSuite(@NonNull String wapiCertSuite) {
1468         setFieldValue(WAPI_CERT_SUITE_KEY, wapiCertSuite);
1469     }
1470 
1471     /**
1472      * Get the WAPI certificate suite name
1473      * @return the certificate suite name
1474      * @hide
1475      */
1476     @NonNull
1477     @SystemApi
getWapiCertSuite()1478     public String getWapiCertSuite() {
1479         return getFieldValue(WAPI_CERT_SUITE_KEY);
1480     }
1481 
1482     /**
1483      * Determines whether an Enterprise configuration's EAP method requires a Root CA certification
1484      * to validate the authentication server i.e. PEAP, TLS, or TTLS.
1485      * @return True if configuration requires a CA certification, false otherwise.
1486      */
isEapMethodServerCertUsed()1487     public boolean isEapMethodServerCertUsed() {
1488         return mEapMethod == Eap.PEAP || mEapMethod == Eap.TLS || mEapMethod == Eap.TTLS;
1489     }
1490     /**
1491      * Determines whether an Enterprise configuration enables server certificate validation.
1492      * <p>
1493      * The caller can determine, along with {@link #isEapMethodServerCertUsed()}, if an
1494      * Enterprise configuration enables server certificate validation, which is a mandatory
1495      * requirement for networks that use TLS based EAP methods. A configuration that does not
1496      * enable server certificate validation will be ignored and will not be considered for
1497      * network selection. A network suggestion with such a configuration will cause an
1498      * IllegalArgumentException to be thrown when suggested.
1499      * Server validation is achieved by the following:
1500      * - Either certificate or CA path is configured.
1501      * - Either alternative subject match or domain suffix match is set.
1502      * @return True for server certificate validation is enabled, false otherwise.
1503      * @throws IllegalStateException on configuration which doesn't use server certificate.
1504      * @see #isEapMethodServerCertUsed()
1505      */
isServerCertValidationEnabled()1506     public boolean isServerCertValidationEnabled() {
1507         if (!isEapMethodServerCertUsed()) {
1508             throw new IllegalStateException("Configuration doesn't use server certificates for "
1509                     + "authentication");
1510         }
1511         return isMandatoryParameterSetForServerCertValidation();
1512     }
1513 
1514     /**
1515      * Helper method to check if mandatory parameter for server cert validation is set.
1516      * @hide
1517      */
isMandatoryParameterSetForServerCertValidation()1518     public boolean isMandatoryParameterSetForServerCertValidation() {
1519         if (TextUtils.isEmpty(getAltSubjectMatch())
1520                 && TextUtils.isEmpty(getDomainSuffixMatch())) {
1521             // Both subject and domain match are not set, validation is not enabled.
1522             return false;
1523         }
1524         if (mIsAppInstalledCaCert) {
1525             // CA certificate is installed by App, validation is enabled.
1526             return true;
1527         }
1528         if (getCaCertificateAliases() != null) {
1529             // CA certificate alias from keyStore is set, validation is enabled.
1530             return true;
1531         }
1532         return !TextUtils.isEmpty(getCaPath());
1533     }
1534 
1535     /**
1536      * Check if a given certificate Get the Suite-B cipher from the certificate
1537      *
1538      * @param x509Certificate Certificate to process
1539      * @return true if the certificate OID matches the Suite-B requirements for RSA or ECDSA
1540      * certificates, or false otherwise.
1541      * @hide
1542      */
isSuiteBCipherCert(@ullable X509Certificate x509Certificate)1543     public static boolean isSuiteBCipherCert(@Nullable X509Certificate x509Certificate) {
1544         if (x509Certificate == null) {
1545             return false;
1546         }
1547         final String sigAlgOid = x509Certificate.getSigAlgOID();
1548 
1549         // Wi-Fi alliance requires the use of both ECDSA secp384r1 and RSA 3072 certificates
1550         // in WPA3-Enterprise 192-bit security networks, which are also known as Suite-B-192
1551         // networks, even though NSA Suite-B-192 mandates ECDSA only. The use of the term
1552         // Suite-B was already coined in the IEEE 802.11-2016 specification for
1553         // AKM 00-0F-AC but the test plan for WPA3-Enterprise 192-bit for APs mandates
1554         // support for both RSA and ECDSA, and for STAs it mandates ECDSA and optionally
1555         // RSA. In order to be compatible with all WPA3-Enterprise 192-bit deployments,
1556         // we are supporting both types here.
1557         if (sigAlgOid.equals("1.2.840.113549.1.1.12")) {
1558             // sha384WithRSAEncryption
1559             if (x509Certificate.getPublicKey() instanceof RSAPublicKey) {
1560                 final RSAPublicKey rsaPublicKey = (RSAPublicKey) x509Certificate.getPublicKey();
1561                 if (rsaPublicKey.getModulus() != null
1562                         && rsaPublicKey.getModulus().bitLength() >= 3072) {
1563                     return true;
1564                 }
1565             }
1566         } else if (sigAlgOid.equals("1.2.840.10045.4.3.3")) {
1567             // ecdsa-with-SHA384
1568             if (x509Certificate.getPublicKey() instanceof ECPublicKey) {
1569                 final ECPublicKey ecPublicKey = (ECPublicKey) x509Certificate.getPublicKey();
1570                 final ECParameterSpec ecParameterSpec = ecPublicKey.getParams();
1571 
1572                 if (ecParameterSpec != null && ecParameterSpec.getOrder() != null
1573                         && ecParameterSpec.getOrder().bitLength() >= 384) {
1574                     return true;
1575                 }
1576             }
1577         }
1578         return false;
1579     }
1580 
1581     /**
1582      * Set a prefix for a decorated identity as per RFC 7542.
1583      * This prefix must contain a list of realms (could be a list of 1) delimited by a '!'
1584      * character. e.g. homerealm.example.org! or proxyrealm.example.net!homerealm.example.org!
1585      * A prefix of "homerealm.example.org!" will generate a decorated identity that
1586      * looks like: homerealm.example.org!user@otherrealm.example.net
1587      * Calling with a null parameter will clear the decorated prefix.
1588      * Note: Caller must verify that the device supports this feature by calling
1589      * {@link WifiManager#isDecoratedIdentitySupported()}
1590      *
1591      * @param decoratedIdentityPrefix The prefix to add to the outer/anonymous identity
1592      */
1593     @RequiresApi(Build.VERSION_CODES.S)
setDecoratedIdentityPrefix(@ullable String decoratedIdentityPrefix)1594     public void setDecoratedIdentityPrefix(@Nullable String decoratedIdentityPrefix) {
1595         if (!SdkLevel.isAtLeastS()) {
1596             throw new UnsupportedOperationException();
1597         }
1598         if (!TextUtils.isEmpty(decoratedIdentityPrefix) && !decoratedIdentityPrefix.endsWith("!")) {
1599             throw new IllegalArgumentException(
1600                     "Decorated identity prefix must be delimited by '!'");
1601         }
1602         setFieldValue(DECORATED_IDENTITY_PREFIX_KEY, decoratedIdentityPrefix);
1603     }
1604 
1605     /**
1606      * Get the decorated identity prefix.
1607      *
1608      * @return The decorated identity prefix
1609      */
1610     @RequiresApi(Build.VERSION_CODES.S)
getDecoratedIdentityPrefix()1611     public @Nullable String getDecoratedIdentityPrefix() {
1612         if (!SdkLevel.isAtLeastS()) {
1613             throw new UnsupportedOperationException();
1614         }
1615         final String decoratedId = getFieldValue(DECORATED_IDENTITY_PREFIX_KEY);
1616         return decoratedId.isEmpty() ? null : decoratedId;
1617     }
1618 }
1619