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