WifiEnterpriseConfig.java revision 8629390701efe0ed74730a06ab2fc868a7ca265d
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 */
16package android.net.wifi;
17
18import android.annotation.Nullable;
19import android.os.Parcel;
20import android.os.Parcelable;
21import android.security.Credentials;
22import android.text.TextUtils;
23
24import java.io.ByteArrayInputStream;
25import java.nio.charset.StandardCharsets;
26import java.security.KeyFactory;
27import java.security.NoSuchAlgorithmException;
28import java.security.PrivateKey;
29import java.security.cert.CertificateEncodingException;
30import java.security.cert.CertificateException;
31import java.security.cert.CertificateFactory;
32import java.security.cert.X509Certificate;
33import java.security.spec.InvalidKeySpecException;
34import java.security.spec.PKCS8EncodedKeySpec;
35import java.util.HashMap;
36import java.util.Map;
37
38/**
39 * Enterprise configuration details for Wi-Fi. Stores details about the EAP method
40 * and any associated credentials.
41 */
42public class WifiEnterpriseConfig implements Parcelable {
43
44    /** @hide */
45    public static final String EMPTY_VALUE         = "NULL";
46    /** @hide */
47    public static final String EAP_KEY             = "eap";
48    /** @hide */
49    public static final String PHASE2_KEY          = "phase2";
50    /** @hide */
51    public static final String IDENTITY_KEY        = "identity";
52    /** @hide */
53    public static final String ANON_IDENTITY_KEY   = "anonymous_identity";
54    /** @hide */
55    public static final String PASSWORD_KEY        = "password";
56    /** @hide */
57    public static final String SUBJECT_MATCH_KEY   = "subject_match";
58    /** @hide */
59    public static final String ALTSUBJECT_MATCH_KEY = "altsubject_match";
60    /** @hide */
61    public static final String DOM_SUFFIX_MATCH_KEY = "domain_suffix_match";
62    /** @hide */
63    public static final String OPP_KEY_CACHING     = "proactive_key_caching";
64    /**
65     * String representing the keystore OpenSSL ENGINE's ID.
66     * @hide
67     */
68    public static final String ENGINE_ID_KEYSTORE = "keystore";
69
70    /**
71     * String representing the keystore URI used for wpa_supplicant.
72     * @hide
73     */
74    public static final String KEYSTORE_URI = "keystore://";
75
76    /**
77     * String representing the keystore URI used for wpa_supplicant,
78     * Unlike #KEYSTORE_URI, this supports a list of space-delimited aliases
79     * @hide
80     */
81    public static final String KEYSTORES_URI = "keystores://";
82
83    /**
84     * String to set the engine value to when it should be enabled.
85     * @hide
86     */
87    public static final String ENGINE_ENABLE = "1";
88
89    /**
90     * String to set the engine value to when it should be disabled.
91     * @hide
92     */
93    public static final String ENGINE_DISABLE = "0";
94
95    /** @hide */
96    public static final String CA_CERT_PREFIX = KEYSTORE_URI + Credentials.CA_CERTIFICATE;
97    /** @hide */
98    public static final String CLIENT_CERT_PREFIX = KEYSTORE_URI + Credentials.USER_CERTIFICATE;
99    /** @hide */
100    public static final String CLIENT_CERT_KEY     = "client_cert";
101    /** @hide */
102    public static final String CA_CERT_KEY         = "ca_cert";
103    /** @hide */
104    public static final String CA_PATH_KEY         = "ca_path";
105    /** @hide */
106    public static final String ENGINE_KEY          = "engine";
107    /** @hide */
108    public static final String ENGINE_ID_KEY       = "engine_id";
109    /** @hide */
110    public static final String PRIVATE_KEY_ID_KEY  = "key_id";
111    /** @hide */
112    public static final String REALM_KEY           = "realm";
113    /** @hide */
114    public static final String PLMN_KEY            = "plmn";
115    /** @hide */
116    public static final String PHASE1_KEY          = "phase1";
117    /** @hide */
118    public static final String CA_CERT_ALIAS_DELIMITER = " ";
119
120    /** {@hide} */
121    public static final String ENABLE_TLS_1_2 = "\"tls_disable_tlsv1_2=0\"";
122    /** {@hide} */
123    public static final String DISABLE_TLS_1_2 = "\"tls_disable_tlsv1_2=1\"";
124
125    private HashMap<String, String> mFields = new HashMap<String, String>();
126    //By default, we enable TLS1.2. However, due to a known bug on some radius, we may disable it to
127    // fall back to TLS 1.1.
128    private boolean mTls12Enable =  true;
129    private X509Certificate[] mCaCerts;
130    private PrivateKey mClientPrivateKey;
131    private X509Certificate mClientCertificate;
132
133    public WifiEnterpriseConfig() {
134        // Do not set defaults so that the enterprise fields that are not changed
135        // by API are not changed underneath
136        // This is essential because an app may not have all fields like password
137        // available. It allows modification of subset of fields.
138
139    }
140
141    /** Copy constructor */
142    public WifiEnterpriseConfig(WifiEnterpriseConfig source) {
143        for (String key : source.mFields.keySet()) {
144            mFields.put(key, source.mFields.get(key));
145        }
146    }
147
148    @Override
149    public int describeContents() {
150        return 0;
151    }
152
153    @Override
154    public void writeToParcel(Parcel dest, int flags) {
155        dest.writeInt(mFields.size());
156        for (Map.Entry<String, String> entry : mFields.entrySet()) {
157            dest.writeString(entry.getKey());
158            dest.writeString(entry.getValue());
159        }
160
161        writeCertificates(dest, mCaCerts);
162
163        if (mClientPrivateKey != null) {
164            String algorithm = mClientPrivateKey.getAlgorithm();
165            byte[] userKeyBytes = mClientPrivateKey.getEncoded();
166            dest.writeInt(userKeyBytes.length);
167            dest.writeByteArray(userKeyBytes);
168            dest.writeString(algorithm);
169        } else {
170            dest.writeInt(0);
171        }
172
173        writeCertificate(dest, mClientCertificate);
174        dest.writeInt(mTls12Enable ? 1: 0);
175    }
176
177    private void writeCertificates(Parcel dest, X509Certificate[] cert) {
178        if (cert != null && cert.length != 0) {
179            dest.writeInt(cert.length);
180            for (int i = 0; i < cert.length; i++) {
181                writeCertificate(dest, cert[i]);
182            }
183        } else {
184            dest.writeInt(0);
185        }
186    }
187
188    private void writeCertificate(Parcel dest, X509Certificate cert) {
189        if (cert != null) {
190            try {
191                byte[] certBytes = cert.getEncoded();
192                dest.writeInt(certBytes.length);
193                dest.writeByteArray(certBytes);
194            } catch (CertificateEncodingException e) {
195                dest.writeInt(0);
196            }
197        } else {
198            dest.writeInt(0);
199        }
200    }
201
202    public static final Creator<WifiEnterpriseConfig> CREATOR =
203            new Creator<WifiEnterpriseConfig>() {
204                public WifiEnterpriseConfig createFromParcel(Parcel in) {
205                    WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
206                    int count = in.readInt();
207                    for (int i = 0; i < count; i++) {
208                        String key = in.readString();
209                        String value = in.readString();
210                        enterpriseConfig.mFields.put(key, value);
211                    }
212
213                    enterpriseConfig.mCaCerts = readCertificates(in);
214
215                    PrivateKey userKey = null;
216                    int len = in.readInt();
217                    if (len > 0) {
218                        try {
219                            byte[] bytes = new byte[len];
220                            in.readByteArray(bytes);
221                            String algorithm = in.readString();
222                            KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
223                            userKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(bytes));
224                        } catch (NoSuchAlgorithmException e) {
225                            userKey = null;
226                        } catch (InvalidKeySpecException e) {
227                            userKey = null;
228                        }
229                    }
230
231                    enterpriseConfig.mClientPrivateKey = userKey;
232                    enterpriseConfig.mClientCertificate = readCertificate(in);
233                    enterpriseConfig.mTls12Enable = (in.readInt() == 1);
234                    return enterpriseConfig;
235                }
236
237                private X509Certificate[] readCertificates(Parcel in) {
238                    X509Certificate[] certs = null;
239                    int len = in.readInt();
240                    if (len > 0) {
241                        certs = new X509Certificate[len];
242                        for (int i = 0; i < len; i++) {
243                            certs[i] = readCertificate(in);
244                        }
245                    }
246                    return certs;
247                }
248
249                private X509Certificate readCertificate(Parcel in) {
250                    X509Certificate cert = null;
251                    int len = in.readInt();
252                    if (len > 0) {
253                        try {
254                            byte[] bytes = new byte[len];
255                            in.readByteArray(bytes);
256                            CertificateFactory cFactory = CertificateFactory.getInstance("X.509");
257                            cert = (X509Certificate) cFactory
258                                    .generateCertificate(new ByteArrayInputStream(bytes));
259                        } catch (CertificateException e) {
260                            cert = null;
261                        }
262                    }
263                    return cert;
264                }
265
266                public WifiEnterpriseConfig[] newArray(int size) {
267                    return new WifiEnterpriseConfig[size];
268                }
269            };
270
271    /** The Extensible Authentication Protocol method used */
272    public static final class Eap {
273        /** No EAP method used. Represents an empty config */
274        public static final int NONE    = -1;
275        /** Protected EAP */
276        public static final int PEAP    = 0;
277        /** EAP-Transport Layer Security */
278        public static final int TLS     = 1;
279        /** EAP-Tunneled Transport Layer Security */
280        public static final int TTLS    = 2;
281        /** EAP-Password */
282        public static final int PWD     = 3;
283        /** EAP-Subscriber Identity Module */
284        public static final int SIM     = 4;
285        /** EAP-Authentication and Key Agreement */
286        public static final int AKA     = 5;
287        /** EAP-Authentication and Key Agreement Prime */
288        public static final int AKA_PRIME = 6;
289        /** Hotspot 2.0 r2 OSEN */
290        public static final int UNAUTH_TLS = 7;
291        /** @hide */
292        public static final String[] strings =
293                { "PEAP", "TLS", "TTLS", "PWD", "SIM", "AKA", "AKA'", "WFA-UNAUTH-TLS" };
294
295        /** Prevent initialization */
296        private Eap() {}
297    }
298
299    /** The inner authentication method used */
300    public static final class Phase2 {
301        public static final int NONE        = 0;
302        /** Password Authentication Protocol */
303        public static final int PAP         = 1;
304        /** Microsoft Challenge Handshake Authentication Protocol */
305        public static final int MSCHAP      = 2;
306        /** Microsoft Challenge Handshake Authentication Protocol v2 */
307        public static final int MSCHAPV2    = 3;
308        /** Generic Token Card */
309        public static final int GTC         = 4;
310        private static final String PREFIX = "auth=";
311        /** @hide */
312        public static final String[] strings = {EMPTY_VALUE, "PAP", "MSCHAP",
313                "MSCHAPV2", "GTC" };
314
315        /** Prevent initialization */
316        private Phase2() {}
317    }
318
319    /** Internal use only
320     * @hide
321     */
322    public HashMap<String, String> getFields() {
323        return mFields;
324    }
325
326    /**
327     * Set the EAP authentication method.
328     * @param  eapMethod is one {@link Eap#PEAP}, {@link Eap#TLS}, {@link Eap#TTLS} or
329     *                   {@link Eap#PWD}
330     * @throws IllegalArgumentException on an invalid eap method
331     */
332    public void setEapMethod(int eapMethod) {
333        switch (eapMethod) {
334            /** Valid methods */
335            case Eap.TLS:
336                setPhase2Method(Phase2.NONE);
337                /* fall through */
338            case Eap.PEAP:
339            case Eap.PWD:
340            case Eap.TTLS:
341            case Eap.SIM:
342            case Eap.AKA:
343            case Eap.AKA_PRIME:
344                mFields.put(EAP_KEY, Eap.strings[eapMethod]);
345                mFields.put(OPP_KEY_CACHING, "1");
346                break;
347            default:
348                throw new IllegalArgumentException("Unknown EAP method");
349        }
350    }
351
352    /**
353     * Set the TLS version
354     * @param enable: true -- enable TLS1.2  false -- disable TLS1.2
355     * @hide
356     */
357    public void setTls12Enable(boolean enable) {
358        mTls12Enable = enable;
359        mFields.put(PHASE1_KEY,
360                enable ? ENABLE_TLS_1_2 : DISABLE_TLS_1_2);
361    }
362
363    /**
364     * Get the TLS1.2 enabled or not
365     * @return eap method configured
366     * @hide
367     */
368    public boolean getTls12Enable() {
369        return mTls12Enable;
370    }
371
372    /**
373     * Get the eap method.
374     * @return eap method configured
375     */
376    public int getEapMethod() {
377        String eapMethod  = mFields.get(EAP_KEY);
378        return getStringIndex(Eap.strings, eapMethod, Eap.NONE);
379    }
380
381    /**
382     * Set Phase 2 authentication method. Sets the inner authentication method to be used in
383     * phase 2 after setting up a secure channel
384     * @param phase2Method is the inner authentication method and can be one of {@link Phase2#NONE},
385     *                     {@link Phase2#PAP}, {@link Phase2#MSCHAP}, {@link Phase2#MSCHAPV2},
386     *                     {@link Phase2#GTC}
387     * @throws IllegalArgumentException on an invalid phase2 method
388     *
389     */
390    public void setPhase2Method(int phase2Method) {
391        switch (phase2Method) {
392            case Phase2.NONE:
393                mFields.put(PHASE2_KEY, EMPTY_VALUE);
394                break;
395            /** Valid methods */
396            case Phase2.PAP:
397            case Phase2.MSCHAP:
398            case Phase2.MSCHAPV2:
399            case Phase2.GTC:
400                mFields.put(PHASE2_KEY, convertToQuotedString(
401                        Phase2.PREFIX + Phase2.strings[phase2Method]));
402                break;
403            default:
404                throw new IllegalArgumentException("Unknown Phase 2 method");
405        }
406    }
407
408    /**
409     * Get the phase 2 authentication method.
410     * @return a phase 2 method defined at {@link Phase2}
411     * */
412    public int getPhase2Method() {
413        String phase2Method = removeDoubleQuotes(mFields.get(PHASE2_KEY));
414        // Remove auth= prefix
415        if (phase2Method.startsWith(Phase2.PREFIX)) {
416            phase2Method = phase2Method.substring(Phase2.PREFIX.length());
417        }
418        return getStringIndex(Phase2.strings, phase2Method, Phase2.NONE);
419    }
420
421    /**
422     * Set the identity
423     * @param identity
424     */
425    public void setIdentity(String identity) {
426        setFieldValue(IDENTITY_KEY, identity, "");
427    }
428
429    /**
430     * Get the identity
431     * @return the identity
432     */
433    public String getIdentity() {
434        return getFieldValue(IDENTITY_KEY, "");
435    }
436
437    /**
438     * Set anonymous identity. This is used as the unencrypted identity with
439     * certain EAP types
440     * @param anonymousIdentity the anonymous identity
441     */
442    public void setAnonymousIdentity(String anonymousIdentity) {
443        setFieldValue(ANON_IDENTITY_KEY, anonymousIdentity, "");
444    }
445
446    /** Get the anonymous identity
447     * @return anonymous identity
448     */
449    public String getAnonymousIdentity() {
450        return getFieldValue(ANON_IDENTITY_KEY, "");
451    }
452
453    /**
454     * Set the password.
455     * @param password the password
456     */
457    public void setPassword(String password) {
458        setFieldValue(PASSWORD_KEY, password, "");
459    }
460
461    /**
462     * Get the password.
463     *
464     * Returns locally set password value. For networks fetched from
465     * framework, returns "*".
466     */
467    public String getPassword() {
468        return getFieldValue(PASSWORD_KEY, "");
469    }
470
471    /**
472     * Encode a CA certificate alias so it does not contain illegal character.
473     * @hide
474     */
475    public static String encodeCaCertificateAlias(String alias) {
476        byte[] bytes = alias.getBytes(StandardCharsets.UTF_8);
477        StringBuilder sb = new StringBuilder(bytes.length * 2);
478        for (byte o : bytes) {
479            sb.append(String.format("%02x", o & 0xFF));
480        }
481        return sb.toString();
482    }
483
484    /**
485     * Decode a previously-encoded CA certificate alias.
486     * @hide
487     */
488    public static String decodeCaCertificateAlias(String alias) {
489        byte[] data = new byte[alias.length() >> 1];
490        for (int n = 0, position = 0; n < alias.length(); n += 2, position++) {
491            data[position] = (byte) Integer.parseInt(alias.substring(n,  n + 2), 16);
492        }
493        try {
494            return new String(data, StandardCharsets.UTF_8);
495        } catch (NumberFormatException e) {
496            e.printStackTrace();
497            return alias;
498        }
499    }
500
501    /**
502     * Set CA certificate alias.
503     *
504     * <p> See the {@link android.security.KeyChain} for details on installing or choosing
505     * a certificate
506     * </p>
507     * @param alias identifies the certificate
508     * @hide
509     */
510    public void setCaCertificateAlias(String alias) {
511        setFieldValue(CA_CERT_KEY, alias, CA_CERT_PREFIX);
512    }
513
514    /**
515     * Set CA certificate aliases. When creating installing the corresponding certificate to
516     * the keystore, please use alias encoded by {@link #encodeCaCertificateAlias(String)}.
517     *
518     * <p> See the {@link android.security.KeyChain} for details on installing or choosing
519     * a certificate.
520     * </p>
521     * @param aliases identifies the certificate
522     * @hide
523     */
524    public void setCaCertificateAliases(@Nullable String[] aliases) {
525        if (aliases == null) {
526            setFieldValue(CA_CERT_KEY, null, CA_CERT_PREFIX);
527        } else if (aliases.length == 1) {
528            // Backwards compatibility: use the original cert prefix if setting only one alias.
529            setCaCertificateAlias(aliases[0]);
530        } else {
531            // Use KEYSTORES_URI which supports multiple aliases.
532            StringBuilder sb = new StringBuilder();
533            for (int i = 0; i < aliases.length; i++) {
534                if (i > 0) {
535                    sb.append(CA_CERT_ALIAS_DELIMITER);
536                }
537                sb.append(encodeCaCertificateAlias(Credentials.CA_CERTIFICATE + aliases[i]));
538            }
539            setFieldValue(CA_CERT_KEY, sb.toString(), KEYSTORES_URI);
540        }
541    }
542
543    /**
544     * Get CA certificate alias
545     * @return alias to the CA certificate
546     * @hide
547     */
548    public String getCaCertificateAlias() {
549        return getFieldValue(CA_CERT_KEY, CA_CERT_PREFIX);
550    }
551
552    /**
553     * Get CA certificate aliases
554     * @return alias to the CA certificate
555     * @hide
556     */
557    @Nullable public String[] getCaCertificateAliases() {
558        String value = getFieldValue(CA_CERT_KEY, "");
559        if (value.startsWith(CA_CERT_PREFIX)) {
560            // Backwards compatibility: parse the original alias prefix.
561            return new String[] {getFieldValue(CA_CERT_KEY, CA_CERT_PREFIX)};
562        } else if (value.startsWith(KEYSTORES_URI)) {
563            String values = value.substring(KEYSTORES_URI.length());
564
565            String[] aliases = TextUtils.split(values, CA_CERT_ALIAS_DELIMITER);
566            for (int i = 0; i < aliases.length; i++) {
567                aliases[i] = decodeCaCertificateAlias(aliases[i]);
568                if (aliases[i].startsWith(Credentials.CA_CERTIFICATE)) {
569                    aliases[i] = aliases[i].substring(Credentials.CA_CERTIFICATE.length());
570                }
571            }
572            return aliases.length != 0 ? aliases : null;
573        } else {
574            return TextUtils.isEmpty(value) ? null : new String[] {value};
575        }
576    }
577
578    /**
579     * Specify a X.509 certificate that identifies the server.
580     *
581     * <p>A default name is automatically assigned to the certificate and used
582     * with this configuration. The framework takes care of installing the
583     * certificate when the config is saved and removing the certificate when
584     * the config is removed.
585     *
586     * @param cert X.509 CA certificate
587     * @throws IllegalArgumentException if not a CA certificate
588     */
589    public void setCaCertificate(@Nullable X509Certificate cert) {
590        if (cert != null) {
591            if (cert.getBasicConstraints() >= 0) {
592                mCaCerts = new X509Certificate[] {cert};
593            } else {
594                throw new IllegalArgumentException("Not a CA certificate");
595            }
596        } else {
597            mCaCerts = null;
598        }
599    }
600
601    /**
602     * Get CA certificate. If multiple CA certificates are configured previously,
603     * return the first one.
604     * @return X.509 CA certificate
605     */
606    @Nullable public X509Certificate getCaCertificate() {
607        if (mCaCerts != null && mCaCerts.length > 0) {
608            return mCaCerts[0];
609        } else {
610            return null;
611        }
612    }
613
614    /**
615     * Specify a list of X.509 certificates that identifies the server. The validation
616     * passes if the CA of server certificate matches one of the given certificates.
617
618     * <p>Default names are automatically assigned to the certificates and used
619     * with this configuration. The framework takes care of installing the
620     * certificates when the config is saved and removing the certificates when
621     * the config is removed.
622     *
623     * @param certs X.509 CA certificates
624     * @throws IllegalArgumentException if any of the provided certificates is
625     *     not a CA certificate
626     */
627    public void setCaCertificates(@Nullable X509Certificate[] certs) {
628        if (certs != null) {
629            X509Certificate[] newCerts = new X509Certificate[certs.length];
630            for (int i = 0; i < certs.length; i++) {
631                if (certs[i].getBasicConstraints() >= 0) {
632                    newCerts[i] = certs[i];
633                } else {
634                    throw new IllegalArgumentException("Not a CA certificate");
635                }
636            }
637            mCaCerts = newCerts;
638        } else {
639            mCaCerts = null;
640        }
641    }
642
643    /**
644     * Get CA certificates.
645     */
646    @Nullable public X509Certificate[] getCaCertificates() {
647        if (mCaCerts != null || mCaCerts.length > 0) {
648            return mCaCerts;
649        } else {
650            return null;
651        }
652    }
653
654    /**
655     * @hide
656     */
657    public void resetCaCertificate() {
658        mCaCerts = null;
659    }
660
661    /**
662     * Set the ca_path directive on wpa_supplicant.
663     *
664     * From wpa_supplicant documentation:
665     *
666     * Directory path for CA certificate files (PEM). This path may contain
667     * multiple CA certificates in OpenSSL format. Common use for this is to
668     * point to system trusted CA list which is often installed into directory
669     * like /etc/ssl/certs. If configured, these certificates are added to the
670     * list of trusted CAs. ca_cert may also be included in that case, but it is
671     * not required.
672     * @param domain The path for CA certificate files
673     * @hide
674     */
675    public void setCaPath(String path) {
676        setFieldValue(CA_PATH_KEY, path);
677    }
678
679    /**
680     * Get the domain_suffix_match value. See setDomSuffixMatch.
681     * @return The path for CA certificate files.
682     * @hide
683     */
684    public String getCaPath() {
685        return getFieldValue(CA_PATH_KEY, "");
686    }
687
688    /** Set Client certificate alias.
689     *
690     * <p> See the {@link android.security.KeyChain} for details on installing or choosing
691     * a certificate
692     * </p>
693     * @param alias identifies the certificate
694     * @hide
695     */
696    public void setClientCertificateAlias(String alias) {
697        setFieldValue(CLIENT_CERT_KEY, alias, CLIENT_CERT_PREFIX);
698        setFieldValue(PRIVATE_KEY_ID_KEY, alias, Credentials.USER_PRIVATE_KEY);
699        // Also, set engine parameters
700        if (TextUtils.isEmpty(alias)) {
701            mFields.put(ENGINE_KEY, ENGINE_DISABLE);
702            mFields.put(ENGINE_ID_KEY, EMPTY_VALUE);
703        } else {
704            mFields.put(ENGINE_KEY, ENGINE_ENABLE);
705            mFields.put(ENGINE_ID_KEY, convertToQuotedString(ENGINE_ID_KEYSTORE));
706        }
707    }
708
709    /**
710     * Get client certificate alias
711     * @return alias to the client certificate
712     * @hide
713     */
714    public String getClientCertificateAlias() {
715        return getFieldValue(CLIENT_CERT_KEY, CLIENT_CERT_PREFIX);
716    }
717
718    /**
719     * Specify a private key and client certificate for client authorization.
720     *
721     * <p>A default name is automatically assigned to the key entry and used
722     * with this configuration.  The framework takes care of installing the
723     * key entry when the config is saved and removing the key entry when
724     * the config is removed.
725
726     * @param privateKey
727     * @param clientCertificate
728     * @throws IllegalArgumentException for an invalid key or certificate.
729     */
730    public void setClientKeyEntry(PrivateKey privateKey, X509Certificate clientCertificate) {
731        if (clientCertificate != null) {
732            if (clientCertificate.getBasicConstraints() != -1) {
733                throw new IllegalArgumentException("Cannot be a CA certificate");
734            }
735            if (privateKey == null) {
736                throw new IllegalArgumentException("Client cert without a private key");
737            }
738            if (privateKey.getEncoded() == null) {
739                throw new IllegalArgumentException("Private key cannot be encoded");
740            }
741        }
742
743        mClientPrivateKey = privateKey;
744        mClientCertificate = clientCertificate;
745    }
746
747    /**
748     * Get client certificate
749     *
750     * @return X.509 client certificate
751     */
752    public X509Certificate getClientCertificate() {
753        return mClientCertificate;
754    }
755
756    /**
757     * @hide
758     */
759    public void resetClientKeyEntry() {
760        mClientPrivateKey = null;
761        mClientCertificate = null;
762    }
763
764    /**
765     * @hide
766     */
767    public PrivateKey getClientPrivateKey() {
768        return mClientPrivateKey;
769    }
770
771    /**
772     * Set subject match (deprecated). This is the substring to be matched against the subject of
773     * the authentication server certificate.
774     * @param subjectMatch substring to be matched
775     * @deprecated in favor of altSubjectMatch
776     */
777    public void setSubjectMatch(String subjectMatch) {
778        setFieldValue(SUBJECT_MATCH_KEY, subjectMatch, "");
779    }
780
781    /**
782     * Get subject match (deprecated)
783     * @return the subject match string
784     * @deprecated in favor of altSubjectMatch
785     */
786    public String getSubjectMatch() {
787        return getFieldValue(SUBJECT_MATCH_KEY, "");
788    }
789
790    /**
791     * Set alternate subject match. This is the substring to be matched against the
792     * alternate subject of the authentication server certificate.
793     * @param altSubjectMatch substring to be matched, for example
794     *                     DNS:server.example.com;EMAIL:server@example.com
795     */
796    public void setAltSubjectMatch(String altSubjectMatch) {
797        setFieldValue(ALTSUBJECT_MATCH_KEY, altSubjectMatch, "");
798    }
799
800    /**
801     * Get alternate subject match
802     * @return the alternate subject match string
803     */
804    public String getAltSubjectMatch() {
805        return getFieldValue(ALTSUBJECT_MATCH_KEY, "");
806    }
807
808    /**
809     * Set the domain_suffix_match directive on wpa_supplicant. This is the parameter to use
810     * for Hotspot 2.0 defined matching of AAA server certs per WFA HS2.0 spec, section 7.3.3.2,
811     * second paragraph.
812     *
813     * From wpa_supplicant documentation:
814     * Constraint for server domain name. If set, this FQDN is used as a suffix match requirement
815     * for the AAAserver certificate in SubjectAltName dNSName element(s). If a matching dNSName is
816     * found, this constraint is met. If no dNSName values are present, this constraint is matched
817     * against SubjectName CN using same suffix match comparison.
818     * Suffix match here means that the host/domain name is compared one label at a time starting
819     * from the top-level domain and all the labels in domain_suffix_match shall be included in the
820     * certificate. The certificate may include additional sub-level labels in addition to the
821     * required labels.
822     * For example, domain_suffix_match=example.com would match test.example.com but would not
823     * match test-example.com.
824     * @param domain The domain value
825     */
826    public void setDomainSuffixMatch(String domain) {
827        setFieldValue(DOM_SUFFIX_MATCH_KEY, domain);
828    }
829
830    /**
831     * Get the domain_suffix_match value. See setDomSuffixMatch.
832     * @return The domain value.
833     */
834    public String getDomainSuffixMatch() {
835        return getFieldValue(DOM_SUFFIX_MATCH_KEY, "");
836    }
837
838    /**
839     * Set realm for passpoint credential; realm identifies a set of networks where your
840     * passpoint credential can be used
841     * @param realm the realm
842     */
843    public void setRealm(String realm) {
844        setFieldValue(REALM_KEY, realm, "");
845    }
846
847    /**
848     * Get realm for passpoint credential; see {@link #setRealm(String)} for more information
849     * @return the realm
850     */
851    public String getRealm() {
852        return getFieldValue(REALM_KEY, "");
853    }
854
855    /**
856     * Set plmn (Public Land Mobile Network) of the provider of passpoint credential
857     * @param plmn the plmn value derived from mcc (mobile country code) & mnc (mobile network code)
858     */
859    public void setPlmn(String plmn) {
860        setFieldValue(PLMN_KEY, plmn, "");
861    }
862
863    /**
864     * Get plmn (Public Land Mobile Network) for passpoint credential; see {@link #setPlmn
865     * (String)} for more information
866     * @return the plmn
867     */
868    public String getPlmn() {
869        return getFieldValue(PLMN_KEY, "");
870    }
871
872    /** See {@link WifiConfiguration#getKeyIdForCredentials} @hide */
873    String getKeyId(WifiEnterpriseConfig current) {
874        String eap = mFields.get(EAP_KEY);
875        String phase2 = mFields.get(PHASE2_KEY);
876
877        // If either eap or phase2 are not initialized, use current config details
878        if (TextUtils.isEmpty((eap))) {
879            eap = current.mFields.get(EAP_KEY);
880        }
881        if (TextUtils.isEmpty(phase2)) {
882            phase2 = current.mFields.get(PHASE2_KEY);
883        }
884        return eap + "_" + phase2;
885    }
886
887    private String removeDoubleQuotes(String string) {
888        if (TextUtils.isEmpty(string)) return "";
889        int length = string.length();
890        if ((length > 1) && (string.charAt(0) == '"')
891                && (string.charAt(length - 1) == '"')) {
892            return string.substring(1, length - 1);
893        }
894        return string;
895    }
896
897    private String convertToQuotedString(String string) {
898        return "\"" + string + "\"";
899    }
900
901    /** Returns the index at which the toBeFound string is found in the array.
902     * @param arr array of strings
903     * @param toBeFound string to be found
904     * @param defaultIndex default index to be returned when string is not found
905     * @return the index into array
906     */
907    private int getStringIndex(String arr[], String toBeFound, int defaultIndex) {
908        if (TextUtils.isEmpty(toBeFound)) return defaultIndex;
909        for (int i = 0; i < arr.length; i++) {
910            if (toBeFound.equals(arr[i])) return i;
911        }
912        return defaultIndex;
913    }
914
915    /** Returns the field value for the key.
916     * @param key into the hash
917     * @param prefix is the prefix that the value may have
918     * @return value
919     * @hide
920     */
921    public String getFieldValue(String key, String prefix) {
922        String value = mFields.get(key);
923        // Uninitialized or known to be empty after reading from supplicant
924        if (TextUtils.isEmpty(value) || EMPTY_VALUE.equals(value)) return "";
925
926        value = removeDoubleQuotes(value);
927        if (value.startsWith(prefix)) {
928            return value.substring(prefix.length());
929        } else {
930            return value;
931        }
932    }
933
934    /** Set a value with an optional prefix at key
935     * @param key into the hash
936     * @param value to be set
937     * @param prefix an optional value to be prefixed to actual value
938     * @hide
939     */
940    public void setFieldValue(String key, String value, String prefix) {
941        if (TextUtils.isEmpty(value)) {
942            mFields.put(key, EMPTY_VALUE);
943        } else {
944            mFields.put(key, convertToQuotedString(prefix + value));
945        }
946    }
947
948
949    /** Set a value with an optional prefix at key
950     * @param key into the hash
951     * @param value to be set
952     * @param prefix an optional value to be prefixed to actual value
953     * @hide
954     */
955    public void setFieldValue(String key, String value) {
956        if (TextUtils.isEmpty(value)) {
957           mFields.put(key, EMPTY_VALUE);
958        } else {
959            mFields.put(key, convertToQuotedString(value));
960        }
961    }
962
963    @Override
964    public String toString() {
965        StringBuffer sb = new StringBuffer();
966        for (String key : mFields.keySet()) {
967            sb.append(key).append(" ").append(mFields.get(key)).append("\n");
968        }
969        return sb.toString();
970    }
971}
972