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