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