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