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