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