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