content: /*
   * Copyright (C) 2016 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package com.android.server.wifi;
  
  import android.net.wifi.WifiConfiguration;
  import android.net.wifi.WifiEnterpriseConfig;
  import android.os.Process;
  import android.security.Credentials;
  import android.security.KeyChain;
  import android.security.KeyStore;
  import android.text.TextUtils;
  import android.util.ArraySet;
  import android.util.Log;
  
  import java.io.ByteArrayInputStream;
  import java.io.IOException;
  import java.io.InputStream;
  import java.security.Key;
  import java.security.cert.Certificate;
  import java.security.cert.CertificateException;
  import java.security.cert.CertificateFactory;
  import java.security.cert.X509Certificate;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.List;
  import java.util.Set;
  
  /**
   * This class provides the methods to access keystore for certificate management.
   *
   * NOTE: This class should only be used from WifiConfigManager!
   */
  public class WifiKeyStore {
      private static final String TAG = "WifiKeyStore";
  
      private boolean mVerboseLoggingEnabled = false;
  
      private final KeyStore mKeyStore;
  
      WifiKeyStore(KeyStore keyStore) {
          mKeyStore = keyStore;
      }
  
      /**
       * Enable verbose logging.
       */
      void enableVerboseLogging(boolean verbose) {
          mVerboseLoggingEnabled = verbose;
      }
  
      // Certificate and private key management for EnterpriseConfig
      private static boolean needsKeyStore(WifiEnterpriseConfig config) {
          return (config.getClientCertificate() != null || config.getCaCertificate() != null);
      }
  
      private static boolean isHardwareBackedKey(Key key) {
          return KeyChain.isBoundKeyAlgorithm(key.getAlgorithm());
      }
  
      private static boolean hasHardwareBackedKey(Certificate certificate) {
          return isHardwareBackedKey(certificate.getPublicKey());
      }
  
      /**
       * Install keys for given enterprise network.
       *
       * @param existingConfig Existing config corresponding to the network already stored in our
       *                       database. This maybe null if it's a new network.
       * @param config         Config corresponding to the network.
       * @return true if successful, false otherwise.
       */
      private boolean installKeys(WifiEnterpriseConfig existingConfig, WifiEnterpriseConfig config,
              String name) {
          boolean ret = true;
          String privKeyName = Credentials.USER_PRIVATE_KEY + name;
          String userCertName = Credentials.USER_CERTIFICATE + name;
          Certificate[] clientCertificateChain = config.getClientCertificateChain();
          if (clientCertificateChain != null && clientCertificateChain.length != 0) {
              byte[] privKeyData = config.getClientPrivateKey().getEncoded();
              if (mVerboseLoggingEnabled) {
                  if (isHardwareBackedKey(config.getClientPrivateKey())) {
                      Log.d(TAG, "importing keys " + name + " in hardware backed store");
                  } else {
                      Log.d(TAG, "importing keys " + name + " in software backed store");
                  }
              }
              ret = mKeyStore.importKey(privKeyName, privKeyData, Process.WIFI_UID,
                      KeyStore.FLAG_NONE);
  
              if (!ret) {
                  return ret;
              }
  
              ret = putCertsInKeyStore(userCertName, clientCertificateChain);
              if (!ret) {
                  // Remove private key installed
                  mKeyStore.delete(privKeyName, Process.WIFI_UID);
                  return ret;
              }
          }
  
          X509Certificate[] caCertificates = config.getCaCertificates();
          Set<String> oldCaCertificatesToRemove = new ArraySet<>();
          if (existingConfig != null && existingConfig.getCaCertificateAliases() != null) {
              oldCaCertificatesToRemove.addAll(
                      Arrays.asList(existingConfig.getCaCertificateAliases()));
          }
          List<String> caCertificateAliases = null;
          if (caCertificates != null) {
              caCertificateAliases = new ArrayList<>();
              for (int i = 0; i < caCertificates.length; i++) {
                  String alias = caCertificates.length == 1 ? name
                          : String.format("%s_%d", name, i);
  
                  oldCaCertificatesToRemove.remove(alias);
                  ret = putCertInKeyStore(Credentials.CA_CERTIFICATE + alias, caCertificates[i]);
                  if (!ret) {
                      // Remove client key+cert
                      if (config.getClientCertificate() != null) {
                          mKeyStore.delete(privKeyName, Process.WIFI_UID);
                          mKeyStore.delete(userCertName, Process.WIFI_UID);
                      }
                      // Remove added CA certs.
                      for (String addedAlias : caCertificateAliases) {
                          mKeyStore.delete(Credentials.CA_CERTIFICATE + addedAlias, Process.WIFI_UID);
                      }
                      return ret;
                  } else {
                      caCertificateAliases.add(alias);
                  }
              }
          }
          // Remove old CA certs.
          for (String oldAlias : oldCaCertificatesToRemove) {
              mKeyStore.delete(Credentials.CA_CERTIFICATE + oldAlias, Process.WIFI_UID);
          }
          // Set alias names
          if (config.getClientCertificate() != null) {
              config.setClientCertificateAlias(name);
              config.resetClientKeyEntry();
          }
  
          if (caCertificates != null) {
              config.setCaCertificateAliases(
                      caCertificateAliases.toArray(new String[caCertificateAliases.size()]));
              config.resetCaCertificate();
          }
          return ret;
      }
  
      /**
       * Install a certificate into the keystore.
       *
       * @param name The alias name of the certificate to be installed
       * @param cert The certificate to be installed
       * @return true on success
       */
      public boolean putCertInKeyStore(String name, Certificate cert) {
          return putCertsInKeyStore(name, new Certificate[] {cert});
      }
  
      /**
       * Install a client certificate chain into the keystore.
       *
       * @param name The alias name of the certificate to be installed
       * @param certs The certificate chain to be installed
       * @return true on success
       */
      public boolean putCertsInKeyStore(String name, Certificate[] certs) {
          try {
              byte[] certData = Credentials.convertToPem(certs);
              if (mVerboseLoggingEnabled) {
                  Log.d(TAG, "putting " + certs.length + " certificate(s) "
                          + name + " in keystore");
              }
              return mKeyStore.put(name, certData, Process.WIFI_UID, KeyStore.FLAG_NONE);
          } catch (IOException e1) {
              return false;
          } catch (CertificateException e2) {
              return false;
          }
      }
  
      /**
       * Install a key into the keystore.
       *
       * @param name The alias name of the key to be installed
       * @param key The key to be installed
       * @return true on success
       */
      public boolean putKeyInKeyStore(String name, Key key) {
          byte[] privKeyData = key.getEncoded();
          return mKeyStore.importKey(name, privKeyData, Process.WIFI_UID, KeyStore.FLAG_NONE);
      }
  
      /**
       * Remove a certificate or key entry specified by the alias name from the keystore.
       *
       * @param name The alias name of the entry to be removed
       * @return true on success
       */
      public boolean removeEntryFromKeyStore(String name) {
          return mKeyStore.delete(name, Process.WIFI_UID);
      }
  
      /**
       * Remove enterprise keys from the network config.
       *
       * @param config Config corresponding to the network.
       */
      public void removeKeys(WifiEnterpriseConfig config) {
          // Do not remove keys that were manually installed by the user
          if (config.isAppInstalledDeviceKeyAndCert()) {
              String client = config.getClientCertificateAlias();
              // a valid client certificate is configured
              if (!TextUtils.isEmpty(client)) {
                  if (mVerboseLoggingEnabled) {
                      Log.d(TAG, "removing client private key and user cert");
                  }
                  mKeyStore.delete(Credentials.USER_PRIVATE_KEY + client, Process.WIFI_UID);
                  mKeyStore.delete(Credentials.USER_CERTIFICATE + client, Process.WIFI_UID);
              }
          }
  
          // Do not remove CA certs that were manually installed by the user
          if (config.isAppInstalledCaCert()) {
              String[] aliases = config.getCaCertificateAliases();
              // a valid ca certificate is configured
              if (aliases != null) {
                  for (String ca : aliases) {
                      if (!TextUtils.isEmpty(ca)) {
                          if (mVerboseLoggingEnabled) {
                              Log.d(TAG, "removing CA cert: " + ca);
                          }
                          mKeyStore.delete(Credentials.CA_CERTIFICATE + ca, Process.WIFI_UID);
                      }
                  }
              }
          }
      }
  
  
      /**
       * @param certData byte array of the certificate
       */
      private X509Certificate buildCACertificate(byte[] certData) {
          try {
              CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
              InputStream inputStream = new ByteArrayInputStream(certData);
              X509Certificate caCertificateX509 = (X509Certificate) certificateFactory
                      .generateCertificate(inputStream);
              return caCertificateX509;
          } catch (CertificateException e) {
              return null;
          }
      }
      /**
       * Update/Install keys for given enterprise network.
       *
       * @param config         Config corresponding to the network.
       * @param existingConfig Existing config corresponding to the network already stored in our
       *                       database. This maybe null if it's a new network.
       * @return true if successful, false otherwise.
       */
      public boolean updateNetworkKeys(WifiConfiguration config, WifiConfiguration existingConfig) {
          WifiEnterpriseConfig enterpriseConfig = config.enterpriseConfig;
          if (!needsKeyStore(enterpriseConfig)) {
              return true;
          }
  
          try {
              /* config passed may include only fields being updated.
               * In order to generate the key id, fetch uninitialized
               * fields from the currently tracked configuration
               */
              String keyId = config.getKeyIdForCredentials(existingConfig);
              if (!installKeys(existingConfig != null
                      ? existingConfig.enterpriseConfig : null, enterpriseConfig, keyId)) {
                  Log.e(TAG, config.SSID + ": failed to install keys");
                  return false;
              }
          } catch (IllegalStateException e) {
              Log.e(TAG, config.SSID + " invalid config for key installation: " + e.getMessage());
              return false;
          }
  
          // For WPA3-Enterprise 192-bit networks, set the SuiteBCipher field based on the
          // CA certificate type. Suite-B requires SHA384, reject other certs.
          if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SUITE_B_192)) {
              // Read the first CA certificate, and initialize
              byte[] certData = mKeyStore.get(
                      Credentials.CA_CERTIFICATE + config.enterpriseConfig.getCaCertificateAlias(),
                      android.os.Process.WIFI_UID);
  
              if (certData == null) {
                  Log.e(TAG, "Failed reading CA certificate for Suite-B");
                  return false;
              }
  
              X509Certificate x509CaCert = buildCACertificate(certData);
  
              if (x509CaCert != null) {
                  String sigAlgOid = x509CaCert.getSigAlgOID();
                  if (mVerboseLoggingEnabled) {
                      Log.d(TAG, "Signature algorithm: " + sigAlgOid);
                  }
                  config.allowedSuiteBCiphers.clear();
  
                  // Wi-Fi alliance requires the use of both ECDSA secp384r1 and RSA 3072 certificates
                  // in WPA3-Enterprise 192-bit security networks, which are also known as Suite-B-192
                  // networks, even though NSA Suite-B-192 mandates ECDSA only. The use of the term
                  // Suite-B was already coined in the IEEE 802.11-2016 specification for
                  // AKM 00-0F-AC but the test plan for WPA3-Enterprise 192-bit for APs mandates
                  // support for both RSA and ECDSA, and for STAs it mandates ECDSA and optionally
                  // RSA. In order to be compatible with all WPA3-Enterprise 192-bit deployments,
                  // we are supporting both types here.
                  if (sigAlgOid.equals("1.2.840.113549.1.1.12")) {
                      // sha384WithRSAEncryption
                      config.allowedSuiteBCiphers.set(
                              WifiConfiguration.SuiteBCipher.ECDHE_RSA);
                      if (mVerboseLoggingEnabled) {
                          Log.d(TAG, "Selecting Suite-B RSA");
                      }
                  } else if (sigAlgOid.equals("1.2.840.10045.4.3.3")) {
                      // ecdsa-with-SHA384
                      config.allowedSuiteBCiphers.set(
                              WifiConfiguration.SuiteBCipher.ECDHE_ECDSA);
                      if (mVerboseLoggingEnabled) {
                          Log.d(TAG, "Selecting Suite-B ECDSA");
                      }
                  } else {
                      Log.e(TAG, "Invalid CA certificate type for Suite-B: "
                              + sigAlgOid);
                      return false;
                  }
              } else {
                  Log.e(TAG, "Invalid CA certificate for Suite-B");
                  return false;
              }
          }
          return true;
      }
  }
  