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.os.Parcel;
19import android.os.Parcelable;
20import android.os.Process;
21import android.security.Credentials;
22import android.security.KeyChain;
23import android.security.KeyStore;
24import android.text.TextUtils;
25import android.util.Slog;
26
27import java.io.ByteArrayInputStream;
28import java.io.IOException;
29import java.security.KeyFactory;
30import java.security.NoSuchAlgorithmException;
31import java.security.PrivateKey;
32import java.security.cert.Certificate;
33import java.security.cert.CertificateEncodingException;
34import java.security.cert.CertificateException;
35import java.security.cert.CertificateFactory;
36import java.security.cert.X509Certificate;
37import java.security.spec.InvalidKeySpecException;
38import java.security.spec.PKCS8EncodedKeySpec;
39import java.util.HashMap;
40import java.util.Map;
41
42/**
43 * Enterprise configuration details for Wi-Fi. Stores details about the EAP method
44 * and any associated credentials.
45 */
46public class WifiEnterpriseConfig implements Parcelable {
47    private static final String TAG = "WifiEnterpriseConfig";
48    private static final boolean DBG = false;
49    /**
50     * In old configurations, the "private_key" field was used. However, newer
51     * configurations use the key_id field with the engine_id set to "keystore".
52     * If this field is found in the configuration, the migration code is
53     * triggered.
54     */
55    private static final String OLD_PRIVATE_KEY_NAME = "private_key";
56
57    /**
58     * String representing the keystore OpenSSL ENGINE's ID.
59     */
60    private static final String ENGINE_ID_KEYSTORE = "keystore";
61
62    /**
63     * String representing the keystore URI used for wpa_supplicant.
64     */
65    private static final String KEYSTORE_URI = "keystore://";
66
67    /**
68     * String to set the engine value to when it should be enabled.
69     */
70    private static final String ENGINE_ENABLE = "1";
71
72    /**
73     * String to set the engine value to when it should be disabled.
74     */
75    private static final String ENGINE_DISABLE = "0";
76
77    private static final String CA_CERT_PREFIX = KEYSTORE_URI + Credentials.CA_CERTIFICATE;
78    private static final String CLIENT_CERT_PREFIX = KEYSTORE_URI + Credentials.USER_CERTIFICATE;
79
80    private static final String EAP_KEY             = "eap";
81    private static final String PHASE2_KEY          = "phase2";
82    private static final String IDENTITY_KEY        = "identity";
83    private static final String ANON_IDENTITY_KEY   = "anonymous_identity";
84    private static final String PASSWORD_KEY        = "password";
85    private static final String CLIENT_CERT_KEY     = "client_cert";
86    private static final String CA_CERT_KEY         = "ca_cert";
87    private static final String SUBJECT_MATCH_KEY   = "subject_match";
88    private static final String ENGINE_KEY          = "engine";
89    private static final String ENGINE_ID_KEY       = "engine_id";
90    private static final String PRIVATE_KEY_ID_KEY  = "key_id";
91    private static final String OPP_KEY_CACHING     = "proactive_key_caching";
92
93    private HashMap<String, String> mFields = new HashMap<String, String>();
94    private X509Certificate mCaCert;
95    private PrivateKey mClientPrivateKey;
96    private X509Certificate mClientCertificate;
97    private boolean mNeedsSoftwareKeystore = false;
98
99    /** This represents an empty value of an enterprise field.
100     * NULL is used at wpa_supplicant to indicate an empty value
101     */
102    static final String EMPTY_VALUE = "NULL";
103
104    public WifiEnterpriseConfig() {
105        // Do not set defaults so that the enterprise fields that are not changed
106        // by API are not changed underneath
107        // This is essential because an app may not have all fields like password
108        // available. It allows modification of subset of fields.
109
110    }
111
112    /** Copy constructor */
113    public WifiEnterpriseConfig(WifiEnterpriseConfig source) {
114        for (String key : source.mFields.keySet()) {
115            mFields.put(key, source.mFields.get(key));
116        }
117    }
118
119    @Override
120    public int describeContents() {
121        return 0;
122    }
123
124    @Override
125    public void writeToParcel(Parcel dest, int flags) {
126        dest.writeInt(mFields.size());
127        for (Map.Entry<String, String> entry : mFields.entrySet()) {
128            dest.writeString(entry.getKey());
129            dest.writeString(entry.getValue());
130        }
131
132        writeCertificate(dest, mCaCert);
133
134        if (mClientPrivateKey != null) {
135            String algorithm = mClientPrivateKey.getAlgorithm();
136            byte[] userKeyBytes = mClientPrivateKey.getEncoded();
137            dest.writeInt(userKeyBytes.length);
138            dest.writeByteArray(userKeyBytes);
139            dest.writeString(algorithm);
140        } else {
141            dest.writeInt(0);
142        }
143
144        writeCertificate(dest, mClientCertificate);
145    }
146
147    private void writeCertificate(Parcel dest, X509Certificate cert) {
148        if (cert != null) {
149            try {
150                byte[] certBytes = cert.getEncoded();
151                dest.writeInt(certBytes.length);
152                dest.writeByteArray(certBytes);
153            } catch (CertificateEncodingException e) {
154                dest.writeInt(0);
155            }
156        } else {
157            dest.writeInt(0);
158        }
159    }
160
161    public static final Creator<WifiEnterpriseConfig> CREATOR =
162            new Creator<WifiEnterpriseConfig>() {
163                public WifiEnterpriseConfig createFromParcel(Parcel in) {
164                    WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
165                    int count = in.readInt();
166                    for (int i = 0; i < count; i++) {
167                        String key = in.readString();
168                        String value = in.readString();
169                        enterpriseConfig.mFields.put(key, value);
170                    }
171
172                    enterpriseConfig.mCaCert = readCertificate(in);
173
174                    PrivateKey userKey = null;
175                    int len = in.readInt();
176                    if (len > 0) {
177                        try {
178                            byte[] bytes = new byte[len];
179                            in.readByteArray(bytes);
180                            String algorithm = in.readString();
181                            KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
182                            userKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(bytes));
183                        } catch (NoSuchAlgorithmException e) {
184                            userKey = null;
185                        } catch (InvalidKeySpecException e) {
186                            userKey = null;
187                        }
188                    }
189
190                    enterpriseConfig.mClientPrivateKey = userKey;
191                    enterpriseConfig.mClientCertificate = readCertificate(in);
192                    return enterpriseConfig;
193                }
194
195                private X509Certificate readCertificate(Parcel in) {
196                    X509Certificate cert = null;
197                    int len = in.readInt();
198                    if (len > 0) {
199                        try {
200                            byte[] bytes = new byte[len];
201                            in.readByteArray(bytes);
202                            CertificateFactory cFactory = CertificateFactory.getInstance("X.509");
203                            cert = (X509Certificate) cFactory
204                                    .generateCertificate(new ByteArrayInputStream(bytes));
205                        } catch (CertificateException e) {
206                            cert = null;
207                        }
208                    }
209                    return cert;
210                }
211
212                public WifiEnterpriseConfig[] newArray(int size) {
213                    return new WifiEnterpriseConfig[size];
214                }
215            };
216
217    /** The Extensible Authentication Protocol method used */
218    public static final class Eap {
219        /** No EAP method used. Represents an empty config */
220        public static final int NONE    = -1;
221        /** Protected EAP */
222        public static final int PEAP    = 0;
223        /** EAP-Transport Layer Security */
224        public static final int TLS     = 1;
225        /** EAP-Tunneled Transport Layer Security */
226        public static final int TTLS    = 2;
227        /** EAP-Password */
228        public static final int PWD     = 3;
229        /** @hide */
230        public static final String[] strings = { "PEAP", "TLS", "TTLS", "PWD" };
231
232        /** Prevent initialization */
233        private Eap() {}
234    }
235
236    /** The inner authentication method used */
237    public static final class Phase2 {
238        public static final int NONE        = 0;
239        /** Password Authentication Protocol */
240        public static final int PAP         = 1;
241        /** Microsoft Challenge Handshake Authentication Protocol */
242        public static final int MSCHAP      = 2;
243        /** Microsoft Challenge Handshake Authentication Protocol v2 */
244        public static final int MSCHAPV2    = 3;
245        /** Generic Token Card */
246        public static final int GTC         = 4;
247        private static final String PREFIX = "auth=";
248        /** @hide */
249        public static final String[] strings = {EMPTY_VALUE, "PAP", "MSCHAP", "MSCHAPV2", "GTC" };
250
251        /** Prevent initialization */
252        private Phase2() {}
253    }
254
255    /** Internal use only */
256    HashMap<String, String> getFields() {
257        return mFields;
258    }
259
260    /** Internal use only */
261    static String[] getSupplicantKeys() {
262        return new String[] { EAP_KEY, PHASE2_KEY, IDENTITY_KEY, ANON_IDENTITY_KEY, PASSWORD_KEY,
263                CLIENT_CERT_KEY, CA_CERT_KEY, SUBJECT_MATCH_KEY, ENGINE_KEY, ENGINE_ID_KEY,
264                PRIVATE_KEY_ID_KEY };
265    }
266
267    /**
268     * Set the EAP authentication method.
269     * @param  eapMethod is one {@link Eap#PEAP}, {@link Eap#TLS}, {@link Eap#TTLS} or
270     *                   {@link Eap#PWD}
271     * @throws IllegalArgumentException on an invalid eap method
272     */
273    public void setEapMethod(int eapMethod) {
274        switch (eapMethod) {
275            /** Valid methods */
276            case Eap.PEAP:
277            case Eap.PWD:
278            case Eap.TLS:
279            case Eap.TTLS:
280                mFields.put(EAP_KEY, Eap.strings[eapMethod]);
281                mFields.put(OPP_KEY_CACHING, "1");
282                break;
283            default:
284                throw new IllegalArgumentException("Unknown EAP method");
285        }
286    }
287
288    /**
289     * Get the eap method.
290     * @return eap method configured
291     */
292    public int getEapMethod() {
293        String eapMethod  = mFields.get(EAP_KEY);
294        return getStringIndex(Eap.strings, eapMethod, Eap.NONE);
295    }
296
297    /**
298     * Set Phase 2 authentication method. Sets the inner authentication method to be used in
299     * phase 2 after setting up a secure channel
300     * @param phase2Method is the inner authentication method and can be one of {@link Phase2#NONE},
301     *                     {@link Phase2#PAP}, {@link Phase2#MSCHAP}, {@link Phase2#MSCHAPV2},
302     *                     {@link Phase2#GTC}
303     * @throws IllegalArgumentException on an invalid phase2 method
304     *
305     */
306    public void setPhase2Method(int phase2Method) {
307        switch (phase2Method) {
308            case Phase2.NONE:
309                mFields.put(PHASE2_KEY, EMPTY_VALUE);
310                break;
311            /** Valid methods */
312            case Phase2.PAP:
313            case Phase2.MSCHAP:
314            case Phase2.MSCHAPV2:
315            case Phase2.GTC:
316                mFields.put(PHASE2_KEY, convertToQuotedString(
317                        Phase2.PREFIX + Phase2.strings[phase2Method]));
318                break;
319            default:
320                throw new IllegalArgumentException("Unknown Phase 2 method");
321        }
322    }
323
324    /**
325     * Get the phase 2 authentication method.
326     * @return a phase 2 method defined at {@link Phase2}
327     * */
328    public int getPhase2Method() {
329        String phase2Method = removeDoubleQuotes(mFields.get(PHASE2_KEY));
330        // Remove auth= prefix
331        if (phase2Method.startsWith(Phase2.PREFIX)) {
332            phase2Method = phase2Method.substring(Phase2.PREFIX.length());
333        }
334        return getStringIndex(Phase2.strings, phase2Method, Phase2.NONE);
335    }
336
337    /**
338     * Set the identity
339     * @param identity
340     */
341    public void setIdentity(String identity) {
342        setFieldValue(IDENTITY_KEY, identity, "");
343    }
344
345    /**
346     * Get the identity
347     * @return the identity
348     */
349    public String getIdentity() {
350        return getFieldValue(IDENTITY_KEY, "");
351    }
352
353    /**
354     * Set anonymous identity. This is used as the unencrypted identity with
355     * certain EAP types
356     * @param anonymousIdentity the anonymous identity
357     */
358    public void setAnonymousIdentity(String anonymousIdentity) {
359        setFieldValue(ANON_IDENTITY_KEY, anonymousIdentity, "");
360    }
361
362    /** Get the anonymous identity
363     * @return anonymous identity
364     */
365    public String getAnonymousIdentity() {
366        return getFieldValue(ANON_IDENTITY_KEY, "");
367    }
368
369    /**
370     * Set the password.
371     * @param password the password
372     */
373    public void setPassword(String password) {
374        setFieldValue(PASSWORD_KEY, password, "");
375    }
376
377    /**
378     * Get the password.
379     *
380     * Returns locally set password value. For networks fetched from
381     * framework, returns "*".
382     */
383    public String getPassword() {
384        return getFieldValue(PASSWORD_KEY, "");
385    }
386
387    /**
388     * Set CA certificate alias.
389     *
390     * <p> See the {@link android.security.KeyChain} for details on installing or choosing
391     * a certificate
392     * </p>
393     * @param alias identifies the certificate
394     * @hide
395     */
396    public void setCaCertificateAlias(String alias) {
397        setFieldValue(CA_CERT_KEY, alias, CA_CERT_PREFIX);
398    }
399
400    /**
401     * Get CA certificate alias
402     * @return alias to the CA certificate
403     * @hide
404     */
405    public String getCaCertificateAlias() {
406        return getFieldValue(CA_CERT_KEY, CA_CERT_PREFIX);
407    }
408
409    /**
410     * Specify a X.509 certificate that identifies the server.
411     *
412     * <p>A default name is automatically assigned to the certificate and used
413     * with this configuration. The framework takes care of installing the
414     * certificate when the config is saved and removing the certificate when
415     * the config is removed.
416     *
417     * @param cert X.509 CA certificate
418     * @throws IllegalArgumentException if not a CA certificate
419     */
420    public void setCaCertificate(X509Certificate cert) {
421        if (cert != null) {
422            if (cert.getBasicConstraints() >= 0) {
423                mCaCert = cert;
424            } else {
425                throw new IllegalArgumentException("Not a CA certificate");
426            }
427        } else {
428            mCaCert = null;
429        }
430    }
431
432    /**
433     * Get CA certificate
434     *
435     * @return X.509 CA certificate
436     */
437    public X509Certificate getCaCertificate() {
438        return mCaCert;
439    }
440
441    /**
442     * Set Client certificate alias.
443     *
444     * <p> See the {@link android.security.KeyChain} for details on installing or choosing
445     * a certificate
446     * </p>
447     * @param alias identifies the certificate
448     * @hide
449     */
450    public void setClientCertificateAlias(String alias) {
451        setFieldValue(CLIENT_CERT_KEY, alias, CLIENT_CERT_PREFIX);
452        setFieldValue(PRIVATE_KEY_ID_KEY, alias, Credentials.USER_PRIVATE_KEY);
453        // Also, set engine parameters
454        if (TextUtils.isEmpty(alias)) {
455            mFields.put(ENGINE_KEY, ENGINE_DISABLE);
456            mFields.put(ENGINE_ID_KEY, EMPTY_VALUE);
457        } else {
458            mFields.put(ENGINE_KEY, ENGINE_ENABLE);
459            mFields.put(ENGINE_ID_KEY, convertToQuotedString(ENGINE_ID_KEYSTORE));
460        }
461    }
462
463    /**
464     * Get client certificate alias
465     * @return alias to the client certificate
466     * @hide
467     */
468    public String getClientCertificateAlias() {
469        return getFieldValue(CLIENT_CERT_KEY, CLIENT_CERT_PREFIX);
470    }
471
472    /**
473     * Specify a private key and client certificate for client authorization.
474     *
475     * <p>A default name is automatically assigned to the key entry and used
476     * with this configuration.  The framework takes care of installing the
477     * key entry when the config is saved and removing the key entry when
478     * the config is removed.
479
480     * @param privateKey
481     * @param clientCertificate
482     * @throws IllegalArgumentException for an invalid key or certificate.
483     */
484    public void setClientKeyEntry(PrivateKey privateKey, X509Certificate clientCertificate) {
485        if (clientCertificate != null) {
486            if (clientCertificate.getBasicConstraints() != -1) {
487                throw new IllegalArgumentException("Cannot be a CA certificate");
488            }
489            if (privateKey == null) {
490                throw new IllegalArgumentException("Client cert without a private key");
491            }
492            if (privateKey.getEncoded() == null) {
493                throw new IllegalArgumentException("Private key cannot be encoded");
494            }
495        }
496
497        mClientPrivateKey = privateKey;
498        mClientCertificate = clientCertificate;
499    }
500
501    /**
502     * Get client certificate
503     *
504     * @return X.509 client certificate
505     */
506    public X509Certificate getClientCertificate() {
507        return mClientCertificate;
508    }
509
510    boolean needsKeyStore() {
511        // Has no keys to be installed
512        if (mClientCertificate == null && mCaCert == null) return false;
513        return true;
514    }
515
516    static boolean isHardwareBackedKey(PrivateKey key) {
517        return KeyChain.isBoundKeyAlgorithm(key.getAlgorithm());
518    }
519
520    static boolean hasHardwareBackedKey(Certificate certificate) {
521        return KeyChain.isBoundKeyAlgorithm(certificate.getPublicKey().getAlgorithm());
522    }
523
524    boolean needsSoftwareBackedKeyStore() {
525        return mNeedsSoftwareKeystore;
526    }
527
528    boolean installKeys(android.security.KeyStore keyStore, String name) {
529        boolean ret = true;
530        String privKeyName = Credentials.USER_PRIVATE_KEY + name;
531        String userCertName = Credentials.USER_CERTIFICATE + name;
532        String caCertName = Credentials.CA_CERTIFICATE + name;
533        if (mClientCertificate != null) {
534            byte[] privKeyData = mClientPrivateKey.getEncoded();
535            if (isHardwareBackedKey(mClientPrivateKey)) {
536                // Hardware backed key store is secure enough to store keys un-encrypted, this
537                // removes the need for user to punch a PIN to get access to these keys
538                if (DBG) Slog.d(TAG, "importing keys " + name + " in hardware backed " +
539                        "store");
540                ret = keyStore.importKey(privKeyName, privKeyData, Process.WIFI_UID,
541                                KeyStore.FLAG_NONE);
542            } else {
543                // Software backed key store is NOT secure enough to store keys un-encrypted.
544                // Save keys encrypted so they are protected with user's PIN. User will
545                // have to unlock phone before being able to use these keys and connect to
546                // networks.
547                if (DBG) Slog.d(TAG, "importing keys " + name + " in software backed store");
548                ret = keyStore.importKey(privKeyName, privKeyData, Process.WIFI_UID,
549                        KeyStore.FLAG_ENCRYPTED);
550                mNeedsSoftwareKeystore = true;
551            }
552            if (ret == false) {
553                return ret;
554            }
555
556            ret = putCertInKeyStore(keyStore, userCertName, mClientCertificate);
557            if (ret == false) {
558                // Remove private key installed
559                keyStore.delKey(privKeyName, Process.WIFI_UID);
560                return ret;
561            }
562        }
563
564        if (mCaCert != null) {
565            ret = putCertInKeyStore(keyStore, caCertName, mCaCert);
566            if (ret == false) {
567                if (mClientCertificate != null) {
568                    // Remove client key+cert
569                    keyStore.delKey(privKeyName, Process.WIFI_UID);
570                    keyStore.delete(userCertName, Process.WIFI_UID);
571                }
572                return ret;
573            }
574        }
575
576        // Set alias names
577        if (mClientCertificate != null) {
578            setClientCertificateAlias(name);
579            mClientPrivateKey = null;
580            mClientCertificate = null;
581        }
582
583        if (mCaCert != null) {
584            setCaCertificateAlias(name);
585            mCaCert = null;
586        }
587
588        return ret;
589    }
590
591    private boolean putCertInKeyStore(android.security.KeyStore keyStore, String name,
592            Certificate cert) {
593        try {
594            byte[] certData = Credentials.convertToPem(cert);
595            if (DBG) Slog.d(TAG, "putting certificate " + name + " in keystore");
596            return keyStore.put(name, certData, Process.WIFI_UID, KeyStore.FLAG_NONE);
597
598        } catch (IOException e1) {
599            return false;
600        } catch (CertificateException e2) {
601            return false;
602        }
603    }
604
605    void removeKeys(KeyStore keyStore) {
606        String client = getFieldValue(CLIENT_CERT_KEY, CLIENT_CERT_PREFIX);
607        // a valid client certificate is configured
608        if (!TextUtils.isEmpty(client)) {
609            if (DBG) Slog.d(TAG, "removing client private key and user cert");
610            keyStore.delKey(Credentials.USER_PRIVATE_KEY + client, Process.WIFI_UID);
611            keyStore.delete(Credentials.USER_CERTIFICATE + client, Process.WIFI_UID);
612        }
613
614        String ca = getFieldValue(CA_CERT_KEY, CA_CERT_PREFIX);
615        // a valid ca certificate is configured
616        if (!TextUtils.isEmpty(ca)) {
617            if (DBG) Slog.d(TAG, "removing CA cert");
618            keyStore.delete(Credentials.CA_CERTIFICATE + ca, Process.WIFI_UID);
619        }
620    }
621
622    /**
623     * Set subject match. This is the substring to be matched against the subject of the
624     * authentication server certificate.
625     * @param subjectMatch substring to be matched
626     */
627    public void setSubjectMatch(String subjectMatch) {
628        setFieldValue(SUBJECT_MATCH_KEY, subjectMatch, "");
629    }
630
631    /**
632     * Get subject match
633     * @return the subject match string
634     */
635    public String getSubjectMatch() {
636        return getFieldValue(SUBJECT_MATCH_KEY, "");
637    }
638
639    /** See {@link WifiConfiguration#getKeyIdForCredentials} @hide */
640    String getKeyId(WifiEnterpriseConfig current) {
641        String eap = mFields.get(EAP_KEY);
642        String phase2 = mFields.get(PHASE2_KEY);
643
644        // If either eap or phase2 are not initialized, use current config details
645        if (TextUtils.isEmpty((eap))) {
646            eap = current.mFields.get(EAP_KEY);
647        }
648        if (TextUtils.isEmpty(phase2)) {
649            phase2 = current.mFields.get(PHASE2_KEY);
650        }
651        return eap + "_" + phase2;
652    }
653
654    /** Migrates the old style TLS config to the new config style. This should only be used
655     * when restoring an old wpa_supplicant.conf or upgrading from a previous
656     * platform version.
657     * @return true if the config was updated
658     * @hide
659     */
660    boolean migrateOldEapTlsNative(WifiNative wifiNative, int netId) {
661        String oldPrivateKey = wifiNative.getNetworkVariable(netId, OLD_PRIVATE_KEY_NAME);
662        /*
663         * If the old configuration value is not present, then there is nothing
664         * to do.
665         */
666        if (TextUtils.isEmpty(oldPrivateKey)) {
667            return false;
668        } else {
669            // Also ignore it if it's empty quotes.
670            oldPrivateKey = removeDoubleQuotes(oldPrivateKey);
671            if (TextUtils.isEmpty(oldPrivateKey)) {
672                return false;
673            }
674        }
675
676        mFields.put(ENGINE_KEY, ENGINE_ENABLE);
677        mFields.put(ENGINE_ID_KEY, convertToQuotedString(ENGINE_ID_KEYSTORE));
678
679        /*
680        * The old key started with the keystore:// URI prefix, but we don't
681        * need that anymore. Trim it off if it exists.
682        */
683        final String keyName;
684        if (oldPrivateKey.startsWith(KEYSTORE_URI)) {
685            keyName = new String(oldPrivateKey.substring(KEYSTORE_URI.length()));
686        } else {
687            keyName = oldPrivateKey;
688        }
689        mFields.put(PRIVATE_KEY_ID_KEY, convertToQuotedString(keyName));
690
691        wifiNative.setNetworkVariable(netId, ENGINE_KEY, mFields.get(ENGINE_KEY));
692        wifiNative.setNetworkVariable(netId, ENGINE_ID_KEY, mFields.get(ENGINE_ID_KEY));
693        wifiNative.setNetworkVariable(netId, PRIVATE_KEY_ID_KEY, mFields.get(PRIVATE_KEY_ID_KEY));
694        // Remove old private_key string so we don't run this again.
695        wifiNative.setNetworkVariable(netId, OLD_PRIVATE_KEY_NAME, EMPTY_VALUE);
696        return true;
697    }
698
699    /** Migrate certs from global pool to wifi UID if not already done */
700    void migrateCerts(android.security.KeyStore keyStore) {
701        String client = getFieldValue(CLIENT_CERT_KEY, CLIENT_CERT_PREFIX);
702        // a valid client certificate is configured
703        if (!TextUtils.isEmpty(client)) {
704            if (!keyStore.contains(Credentials.USER_PRIVATE_KEY + client, Process.WIFI_UID)) {
705                keyStore.duplicate(Credentials.USER_PRIVATE_KEY + client, -1,
706                        Credentials.USER_PRIVATE_KEY + client, Process.WIFI_UID);
707                keyStore.duplicate(Credentials.USER_CERTIFICATE + client, -1,
708                        Credentials.USER_CERTIFICATE + client, Process.WIFI_UID);
709            }
710        }
711
712        String ca = getFieldValue(CA_CERT_KEY, CA_CERT_PREFIX);
713        // a valid ca certificate is configured
714        if (!TextUtils.isEmpty(ca)) {
715            if (!keyStore.contains(Credentials.CA_CERTIFICATE + ca, Process.WIFI_UID)) {
716                keyStore.duplicate(Credentials.CA_CERTIFICATE + ca, -1,
717                        Credentials.CA_CERTIFICATE + ca, Process.WIFI_UID);
718            }
719        }
720    }
721
722    void initializeSoftwareKeystoreFlag(android.security.KeyStore keyStore) {
723        String client = getFieldValue(CLIENT_CERT_KEY, CLIENT_CERT_PREFIX);
724        if (!TextUtils.isEmpty(client)) {
725            // a valid client certificate is configured
726
727            // BUGBUG: keyStore.get() never returns certBytes; because it is not
728            // taking WIFI_UID as a parameter. It always looks for certificate
729            // with SYSTEM_UID, and never finds any Wifi certificates. Assuming that
730            // all certificates need software keystore until we get the get() API
731            // fixed.
732
733            mNeedsSoftwareKeystore = true;
734
735            /*
736            try {
737
738                if (DBG) Slog.d(TAG, "Loading client certificate " + Credentials
739                        .USER_CERTIFICATE + client);
740
741                CertificateFactory factory = CertificateFactory.getInstance("X.509");
742                if (factory == null) {
743                    Slog.e(TAG, "Error getting certificate factory");
744                    return;
745                }
746
747                byte[] certBytes = keyStore.get(Credentials.USER_CERTIFICATE + client);
748                if (certBytes != null) {
749                    Certificate cert = (X509Certificate) factory.generateCertificate(
750                            new ByteArrayInputStream(certBytes));
751
752                    if (cert != null) {
753                        mNeedsSoftwareKeystore = hasHardwareBackedKey(cert);
754
755                        if (DBG) Slog.d(TAG, "Loaded client certificate " + Credentials
756                                .USER_CERTIFICATE + client);
757                        if (DBG) Slog.d(TAG, "It " + (mNeedsSoftwareKeystore ? "needs" :
758                                "does not need" ) + " software key store");
759                    } else {
760                        Slog.d(TAG, "could not generate certificate");
761                    }
762                } else {
763                    Slog.e(TAG, "Could not load client certificate " + Credentials
764                            .USER_CERTIFICATE + client);
765                    mNeedsSoftwareKeystore = true;
766                }
767
768            } catch(CertificateException e) {
769                Slog.e(TAG, "Could not read certificates");
770                mCaCert = null;
771                mClientCertificate = null;
772            }
773            */
774        }
775    }
776
777    private String removeDoubleQuotes(String string) {
778        if (TextUtils.isEmpty(string)) return "";
779        int length = string.length();
780        if ((length > 1) && (string.charAt(0) == '"')
781                && (string.charAt(length - 1) == '"')) {
782            return string.substring(1, length - 1);
783        }
784        return string;
785    }
786
787    private String convertToQuotedString(String string) {
788        return "\"" + string + "\"";
789    }
790
791    /** Returns the index at which the toBeFound string is found in the array.
792     * @param arr array of strings
793     * @param toBeFound string to be found
794     * @param defaultIndex default index to be returned when string is not found
795     * @return the index into array
796     */
797    private int getStringIndex(String arr[], String toBeFound, int defaultIndex) {
798        if (TextUtils.isEmpty(toBeFound)) return defaultIndex;
799        for (int i = 0; i < arr.length; i++) {
800            if (toBeFound.equals(arr[i])) return i;
801        }
802        return defaultIndex;
803    }
804
805    /** Returns the field value for the key.
806     * @param key into the hash
807     * @param prefix is the prefix that the value may have
808     * @return value
809     */
810    private String getFieldValue(String key, String prefix) {
811        String value = mFields.get(key);
812        // Uninitialized or known to be empty after reading from supplicant
813        if (TextUtils.isEmpty(value) || EMPTY_VALUE.equals(value)) return "";
814
815        value = removeDoubleQuotes(value);
816        if (value.startsWith(prefix)) {
817            return value.substring(prefix.length());
818        } else {
819            return value;
820        }
821    }
822
823    /** Set a value with an optional prefix at key
824     * @param key into the hash
825     * @param value to be set
826     * @param prefix an optional value to be prefixed to actual value
827     */
828    private void setFieldValue(String key, String value, String prefix) {
829        if (TextUtils.isEmpty(value)) {
830            mFields.put(key, EMPTY_VALUE);
831        } else {
832            mFields.put(key, convertToQuotedString(prefix + value));
833        }
834    }
835
836    @Override
837    public String toString() {
838        StringBuffer sb = new StringBuffer();
839        for (String key : mFields.keySet()) {
840            sb.append(key).append(" ").append(mFields.get(key)).append("\n");
841        }
842        return sb.toString();
843    }
844}
845