Home | History | Annotate | Download | only in wifi
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.server.wifi;
     18 
     19 import android.net.wifi.WifiConfiguration;
     20 import android.net.wifi.WifiEnterpriseConfig;
     21 import android.os.Process;
     22 import android.security.Credentials;
     23 import android.security.KeyChain;
     24 import android.security.KeyStore;
     25 import android.text.TextUtils;
     26 import android.util.ArraySet;
     27 import android.util.Log;
     28 
     29 import java.io.IOException;
     30 import java.security.Key;
     31 import java.security.cert.Certificate;
     32 import java.security.cert.CertificateException;
     33 import java.security.cert.X509Certificate;
     34 import java.util.ArrayList;
     35 import java.util.Arrays;
     36 import java.util.List;
     37 import java.util.Set;
     38 
     39 /**
     40  * This class provides the methods to access keystore for certificate management.
     41  *
     42  * NOTE: This class should only be used from WifiConfigManager!
     43  */
     44 public class WifiKeyStore {
     45     private static final String TAG = "WifiKeyStore";
     46 
     47     private boolean mVerboseLoggingEnabled = false;
     48 
     49     private final KeyStore mKeyStore;
     50 
     51     WifiKeyStore(KeyStore keyStore) {
     52         mKeyStore = keyStore;
     53     }
     54 
     55     /**
     56      * Enable verbose logging.
     57      */
     58     void enableVerboseLogging(boolean verbose) {
     59         mVerboseLoggingEnabled = verbose;
     60     }
     61 
     62     // Certificate and private key management for EnterpriseConfig
     63     private static boolean needsKeyStore(WifiEnterpriseConfig config) {
     64         return (config.getClientCertificate() != null || config.getCaCertificate() != null);
     65     }
     66 
     67     private static boolean isHardwareBackedKey(Key key) {
     68         return KeyChain.isBoundKeyAlgorithm(key.getAlgorithm());
     69     }
     70 
     71     private static boolean hasHardwareBackedKey(Certificate certificate) {
     72         return isHardwareBackedKey(certificate.getPublicKey());
     73     }
     74 
     75     /**
     76      * Install keys for given enterprise network.
     77      *
     78      * @param existingConfig Existing config corresponding to the network already stored in our
     79      *                       database. This maybe null if it's a new network.
     80      * @param config         Config corresponding to the network.
     81      * @return true if successful, false otherwise.
     82      */
     83     private boolean installKeys(WifiEnterpriseConfig existingConfig, WifiEnterpriseConfig config,
     84             String name) {
     85         boolean ret = true;
     86         String privKeyName = Credentials.USER_PRIVATE_KEY + name;
     87         String userCertName = Credentials.USER_CERTIFICATE + name;
     88         Certificate[] clientCertificateChain = config.getClientCertificateChain();
     89         if (clientCertificateChain != null && clientCertificateChain.length != 0) {
     90             byte[] privKeyData = config.getClientPrivateKey().getEncoded();
     91             if (mVerboseLoggingEnabled) {
     92                 if (isHardwareBackedKey(config.getClientPrivateKey())) {
     93                     Log.d(TAG, "importing keys " + name + " in hardware backed store");
     94                 } else {
     95                     Log.d(TAG, "importing keys " + name + " in software backed store");
     96                 }
     97             }
     98             ret = mKeyStore.importKey(privKeyName, privKeyData, Process.WIFI_UID,
     99                     KeyStore.FLAG_NONE);
    100 
    101             if (!ret) {
    102                 return ret;
    103             }
    104 
    105             ret = putCertsInKeyStore(userCertName, clientCertificateChain);
    106             if (!ret) {
    107                 // Remove private key installed
    108                 mKeyStore.delete(privKeyName, Process.WIFI_UID);
    109                 return ret;
    110             }
    111         }
    112 
    113         X509Certificate[] caCertificates = config.getCaCertificates();
    114         Set<String> oldCaCertificatesToRemove = new ArraySet<>();
    115         if (existingConfig != null && existingConfig.getCaCertificateAliases() != null) {
    116             oldCaCertificatesToRemove.addAll(
    117                     Arrays.asList(existingConfig.getCaCertificateAliases()));
    118         }
    119         List<String> caCertificateAliases = null;
    120         if (caCertificates != null) {
    121             caCertificateAliases = new ArrayList<>();
    122             for (int i = 0; i < caCertificates.length; i++) {
    123                 String alias = caCertificates.length == 1 ? name
    124                         : String.format("%s_%d", name, i);
    125 
    126                 oldCaCertificatesToRemove.remove(alias);
    127                 ret = putCertInKeyStore(Credentials.CA_CERTIFICATE + alias, caCertificates[i]);
    128                 if (!ret) {
    129                     // Remove client key+cert
    130                     if (config.getClientCertificate() != null) {
    131                         mKeyStore.delete(privKeyName, Process.WIFI_UID);
    132                         mKeyStore.delete(userCertName, Process.WIFI_UID);
    133                     }
    134                     // Remove added CA certs.
    135                     for (String addedAlias : caCertificateAliases) {
    136                         mKeyStore.delete(Credentials.CA_CERTIFICATE + addedAlias, Process.WIFI_UID);
    137                     }
    138                     return ret;
    139                 } else {
    140                     caCertificateAliases.add(alias);
    141                 }
    142             }
    143         }
    144         // Remove old CA certs.
    145         for (String oldAlias : oldCaCertificatesToRemove) {
    146             mKeyStore.delete(Credentials.CA_CERTIFICATE + oldAlias, Process.WIFI_UID);
    147         }
    148         // Set alias names
    149         if (config.getClientCertificate() != null) {
    150             config.setClientCertificateAlias(name);
    151             config.resetClientKeyEntry();
    152         }
    153 
    154         if (caCertificates != null) {
    155             config.setCaCertificateAliases(
    156                     caCertificateAliases.toArray(new String[caCertificateAliases.size()]));
    157             config.resetCaCertificate();
    158         }
    159         return ret;
    160     }
    161 
    162     /**
    163      * Install a certificate into the keystore.
    164      *
    165      * @param name The alias name of the certificate to be installed
    166      * @param cert The certificate to be installed
    167      * @return true on success
    168      */
    169     public boolean putCertInKeyStore(String name, Certificate cert) {
    170         return putCertsInKeyStore(name, new Certificate[] {cert});
    171     }
    172 
    173     /**
    174      * Install a client certificate chain into the keystore.
    175      *
    176      * @param name The alias name of the certificate to be installed
    177      * @param certs The certificate chain to be installed
    178      * @return true on success
    179      */
    180     public boolean putCertsInKeyStore(String name, Certificate[] certs) {
    181         try {
    182             byte[] certData = Credentials.convertToPem(certs);
    183             if (mVerboseLoggingEnabled) {
    184                 Log.d(TAG, "putting " + certs.length + " certificate(s) "
    185                         + name + " in keystore");
    186             }
    187             return mKeyStore.put(name, certData, Process.WIFI_UID, KeyStore.FLAG_NONE);
    188         } catch (IOException e1) {
    189             return false;
    190         } catch (CertificateException e2) {
    191             return false;
    192         }
    193     }
    194 
    195     /**
    196      * Install a key into the keystore.
    197      *
    198      * @param name The alias name of the key to be installed
    199      * @param key The key to be installed
    200      * @return true on success
    201      */
    202     public boolean putKeyInKeyStore(String name, Key key) {
    203         byte[] privKeyData = key.getEncoded();
    204         return mKeyStore.importKey(name, privKeyData, Process.WIFI_UID, KeyStore.FLAG_NONE);
    205     }
    206 
    207     /**
    208      * Remove a certificate or key entry specified by the alias name from the keystore.
    209      *
    210      * @param name The alias name of the entry to be removed
    211      * @return true on success
    212      */
    213     public boolean removeEntryFromKeyStore(String name) {
    214         return mKeyStore.delete(name, Process.WIFI_UID);
    215     }
    216 
    217     /**
    218      * Remove enterprise keys from the network config.
    219      *
    220      * @param config Config corresponding to the network.
    221      */
    222     public void removeKeys(WifiEnterpriseConfig config) {
    223         String client = config.getClientCertificateAlias();
    224         // a valid client certificate is configured
    225         if (!TextUtils.isEmpty(client)) {
    226             if (mVerboseLoggingEnabled) Log.d(TAG, "removing client private key and user cert");
    227             mKeyStore.delete(Credentials.USER_PRIVATE_KEY + client, Process.WIFI_UID);
    228             mKeyStore.delete(Credentials.USER_CERTIFICATE + client, Process.WIFI_UID);
    229         }
    230 
    231         String[] aliases = config.getCaCertificateAliases();
    232         // a valid ca certificate is configured
    233         if (aliases != null) {
    234             for (String ca : aliases) {
    235                 if (!TextUtils.isEmpty(ca)) {
    236                     if (mVerboseLoggingEnabled) Log.d(TAG, "removing CA cert: " + ca);
    237                     mKeyStore.delete(Credentials.CA_CERTIFICATE + ca, Process.WIFI_UID);
    238                 }
    239             }
    240         }
    241     }
    242 
    243     /**
    244      * Update/Install keys for given enterprise network.
    245      *
    246      * @param config         Config corresponding to the network.
    247      * @param existingConfig Existing config corresponding to the network already stored in our
    248      *                       database. This maybe null if it's a new network.
    249      * @return true if successful, false otherwise.
    250      */
    251     public boolean updateNetworkKeys(WifiConfiguration config, WifiConfiguration existingConfig) {
    252         WifiEnterpriseConfig enterpriseConfig = config.enterpriseConfig;
    253         if (needsKeyStore(enterpriseConfig)) {
    254             try {
    255                 /* config passed may include only fields being updated.
    256                  * In order to generate the key id, fetch uninitialized
    257                  * fields from the currently tracked configuration
    258                  */
    259                 String keyId = config.getKeyIdForCredentials(existingConfig);
    260                 if (!installKeys(existingConfig != null
    261                         ? existingConfig.enterpriseConfig : null, enterpriseConfig, keyId)) {
    262                     Log.e(TAG, config.SSID + ": failed to install keys");
    263                     return false;
    264                 }
    265             } catch (IllegalStateException e) {
    266                 Log.e(TAG, config.SSID + " invalid config for key installation: " + e.getMessage());
    267                 return false;
    268             }
    269         }
    270         return true;
    271     }
    272 
    273     /**
    274      * Checks whether the configuration requires a software backed keystore or not.
    275      * @param config WifiEnterprise config instance pointing to the enterprise configuration of the
    276      *               network.
    277      */
    278     public static boolean needsSoftwareBackedKeyStore(WifiEnterpriseConfig config) {
    279         String client = config.getClientCertificateAlias();
    280         if (!TextUtils.isEmpty(client)) {
    281             // a valid client certificate is configured
    282 
    283             // BUGBUG(b/29578316): keyStore.get() never returns certBytes; because it is not
    284             // taking WIFI_UID as a parameter. It always looks for certificate
    285             // with SYSTEM_UID, and never finds any Wifi certificates. Assuming that
    286             // all certificates need software keystore until we get the get() API
    287             // fixed.
    288             return true;
    289         }
    290         return false;
    291     }
    292 
    293 }
    294