/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.apache.hadoop.security.ssl;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.security.alias.CredentialProvider;
import org.apache.hadoop.security.alias.CredentialProviderFactory;
import org.apache.hadoop.security.alias.JavaKeyStoreProvider;
import org.apache.hadoop.test.GenericTestUtils;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.math.BigInteger;
import java.net.Socket;
import java.net.URL;
import java.security.GeneralSecurityException;
import java.security.Key;
import java.security.KeyManagementException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.SecureRandom;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import java.security.InvalidKeyException;
import java.security.NoSuchProviderException;
import java.security.SignatureException;
import java.security.cert.CertificateEncodingException;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509KeyManager;
import javax.net.ssl.X509TrustManager;
import javax.security.auth.x500.X500Principal;

import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.bouncycastle.x509.X509V1CertificateGenerator;

public class KeyStoreTestUtil {

  public final static String SERVER_KEY_STORE_PASSWORD_DEFAULT = "serverP";
  public final static String CLIENT_KEY_STORE_PASSWORD_DEFAULT = "clientP";
  public final static String TRUST_STORE_PASSWORD_DEFAULT = "trustP";

  public static String getClasspathDir(Class klass) throws Exception {
    String file = klass.getName();
    file = file.replace('.', '/') + ".class";
    URL url = Thread.currentThread().getContextClassLoader().getResource(file);
    String baseDir = url.toURI().getPath();
    baseDir = baseDir.substring(0, baseDir.length() - file.length() - 1);
    return baseDir;
  }

  @SuppressWarnings("deprecation")
  /**
   * Create a self-signed X.509 Certificate.
   *
   * @param dn the X.509 Distinguished Name, eg "CN=Test, L=London, C=GB"
   * @param pair the KeyPair
   * @param days how many days from now the Certificate is valid for
   * @param algorithm the signing algorithm, eg "SHA1withRSA"
   * @return the self-signed certificate
   */
  public static X509Certificate generateCertificate(String dn, KeyPair pair, int days, String algorithm)
      throws CertificateEncodingException,
             InvalidKeyException,
             IllegalStateException,
             NoSuchProviderException, NoSuchAlgorithmException, SignatureException{

    Date from = new Date();
    Date to = new Date(from.getTime() + days * 86400000l);
    BigInteger sn = new BigInteger(64, new SecureRandom());
    KeyPair keyPair = pair;
    X509V1CertificateGenerator certGen = new X509V1CertificateGenerator();
    X500Principal  dnName = new X500Principal(dn);

    certGen.setSerialNumber(sn);
    certGen.setIssuerDN(dnName);
    certGen.setNotBefore(from);
    certGen.setNotAfter(to);
    certGen.setSubjectDN(dnName);
    certGen.setPublicKey(keyPair.getPublic());
    certGen.setSignatureAlgorithm(algorithm);

    X509Certificate cert = certGen.generate(pair.getPrivate());
    return cert;
  }

  public static KeyPair generateKeyPair(String algorithm)
      throws NoSuchAlgorithmException {
    KeyPairGenerator keyGen = KeyPairGenerator.getInstance(algorithm);
    keyGen.initialize(1024);
    return keyGen.genKeyPair();
  }

  private static KeyStore createEmptyKeyStore()
      throws GeneralSecurityException, IOException {
    KeyStore ks = KeyStore.getInstance("JKS");
    ks.load(null, null); // initialize
    return ks;
  }

  private static void saveKeyStore(KeyStore ks, String filename,
      String password)
      throws GeneralSecurityException, IOException {
    FileOutputStream out = new FileOutputStream(filename);
    try {
      ks.store(out, password.toCharArray());
    } finally {
      out.close();
    }
  }

  public static void createKeyStore(String filename,
      String password, String alias,
      Key privateKey, Certificate cert)
      throws GeneralSecurityException, IOException {
    createKeyStore(filename, password, alias, privateKey,
        new Certificate[]{cert});
  }

  public static void createKeyStore(String filename,
      String password, String alias,
      Key privateKey, Certificate[] certs)
      throws GeneralSecurityException, IOException {
    KeyStore ks = createEmptyKeyStore();
    ks.setKeyEntry(alias, privateKey, password.toCharArray(), certs);
    saveKeyStore(ks, filename, password);
  }

  /**
   * Creates a keystore with a single key and saves it to a file.
   *
   * @param filename String file to save
   * @param password String store password to set on keystore
   * @param keyPassword String key password to set on key
   * @param alias String alias to use for the key
   * @param privateKey Key to save in keystore
   * @param cert Certificate to use as certificate chain associated to key
   * @throws GeneralSecurityException for any error with the security APIs
   * @throws IOException if there is an I/O error saving the file
   */
  public static void createKeyStore(String filename,
      String password, String keyPassword, String alias,
      Key privateKey, Certificate cert)
      throws GeneralSecurityException, IOException {
    KeyStore ks = createEmptyKeyStore();
    ks.setKeyEntry(alias, privateKey, keyPassword.toCharArray(),
        new Certificate[]{cert});
    saveKeyStore(ks, filename, password);
  }

  public static void createTrustStore(String filename,
      String password, String alias,
      Certificate cert)
      throws GeneralSecurityException, IOException {
    KeyStore ks = createEmptyKeyStore();
    ks.setCertificateEntry(alias, cert);
    saveKeyStore(ks, filename, password);
  }

  public static <T extends Certificate> void createTrustStore(
      String filename, String password, Map<String, T> certs)
      throws GeneralSecurityException, IOException {
    KeyStore ks = createEmptyKeyStore();
    for (Map.Entry<String, T> cert : certs.entrySet()) {
      ks.setCertificateEntry(cert.getKey(), cert.getValue());
    }
    saveKeyStore(ks, filename, password);
  }

  public static KeyStore bytesToKeyStore(byte[] bytes, String password)
      throws GeneralSecurityException, IOException {
    KeyStore keyStore = createEmptyKeyStore();
    ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
    keyStore.load(bais, password.toCharArray());
    return keyStore;
  }

  public static void cleanupSSLConfig(String keystoresDir, String sslConfDir)
      throws Exception {
    File f = new File(keystoresDir + "/clientKS.jks");
    f.delete();
    f = new File(keystoresDir + "/serverKS.jks");
    f.delete();
    f = new File(keystoresDir + "/trustKS.jks");
    f.delete();
    f = new File(sslConfDir + "/ssl-client.xml");
    f.delete();
    f = new File(sslConfDir + "/ssl-server.xml");
    f.delete();
  }

  /**
   * Performs complete setup of SSL configuration in preparation for testing an
   * SSLFactory.  This includes keys, certs, keystores, truststores, the server
   * SSL configuration file, the client SSL configuration file, and the master
   * configuration file read by the SSLFactory.
   *
   * @param keystoresDir String directory to save keystores
   * @param sslConfDir String directory to save SSL configuration files
   * @param conf Configuration master configuration to be used by an SSLFactory,
   * which will be mutated by this method
   * @param useClientCert boolean true to make the client present a cert in the
   * SSL handshake
   */
  public static void setupSSLConfig(String keystoresDir, String sslConfDir,
      Configuration conf, boolean useClientCert) throws Exception {
    setupSSLConfig(keystoresDir, sslConfDir, conf, useClientCert, true);
  }

  /**
   * Performs complete setup of SSL configuration in preparation for testing an
   * SSLFactory.  This includes keys, certs, keystores, truststores, the server
   * SSL configuration file, the client SSL configuration file, and the master
   * configuration file read by the SSLFactory.
   *
   * @param keystoresDir String directory to save keystores
   * @param sslConfDir String directory to save SSL configuration files
   * @param conf Configuration master configuration to be used by an SSLFactory,
   * which will be mutated by this method
   * @param useClientCert boolean true to make the client present a cert in the
   * SSL handshake
   * @param trustStore boolean true to create truststore, false not to create it
   * @throws java.lang.Exception
   */
  public static void setupSSLConfig(String keystoresDir, String sslConfDir,
                                    Configuration conf, boolean useClientCert,
      boolean trustStore)
    throws Exception {
    setupSSLConfig(keystoresDir, sslConfDir, conf, useClientCert, true,"");
  }

  /**
   * Performs complete setup of SSL configuration in preparation for testing an
   * SSLFactory.  This includes keys, certs, keystores, truststores, the server
   * SSL configuration file, the client SSL configuration file, and the master
   * configuration file read by the SSLFactory.
   *
   * @param keystoresDir
   * @param sslConfDir
   * @param conf
   * @param useClientCert
   * @param trustStore
   * @param excludeCiphers
   * @throws Exception
   */
  public static void setupSSLConfig(String keystoresDir, String sslConfDir,
      Configuration conf, boolean useClientCert, boolean trustStore,
      String excludeCiphers) throws Exception {
    setupSSLConfig(keystoresDir, sslConfDir, conf, useClientCert, trustStore,
        excludeCiphers, SERVER_KEY_STORE_PASSWORD_DEFAULT,
        CLIENT_KEY_STORE_PASSWORD_DEFAULT, TRUST_STORE_PASSWORD_DEFAULT);
  }


  /**
   * Performs complete setup of SSL configuration in preparation for testing an
   * SSLFactory.  This includes keys, certs, keystores, truststores, the server
   * SSL configuration file, the client SSL configuration file, and the master
   * configuration file read by the SSLFactory and the passwords required to
   * access the keyStores (Server and Client KeyStore Passwords and
   * TrustStore Password).
   *
   * @param keystoresDir
   * @param sslConfDir
   * @param conf
   * @param useClientCert
   * @param trustStore
   * @param excludeCiphers
   * @param serverPassword
   * @param clientPassword
   * @param trustPassword
   * @throws Exception
   */
  @SuppressWarnings("checkstyle:parameternumber")
  public static void setupSSLConfig(String keystoresDir, String sslConfDir,
      Configuration conf, boolean useClientCert, boolean trustStore,
      String excludeCiphers, String serverPassword, String clientPassword,
      String trustPassword) throws Exception {

    String clientKS = keystoresDir + "/clientKS.jks";
    String serverKS = keystoresDir + "/serverKS.jks";
    String trustKS = null;

    File sslClientConfFile = new File(sslConfDir, getClientSSLConfigFileName());
    File sslServerConfFile = new File(sslConfDir, getServerSSLConfigFileName());

    Map<String, X509Certificate> certs = new HashMap<String, X509Certificate>();

    if (useClientCert) {
      KeyPair cKP = KeyStoreTestUtil.generateKeyPair("RSA");
      X509Certificate cCert =
        KeyStoreTestUtil.generateCertificate("CN=localhost, O=client", cKP, 30,
                                             "SHA1withRSA");
      KeyStoreTestUtil.createKeyStore(clientKS, clientPassword, "client",
                                      cKP.getPrivate(), cCert);
      certs.put("client", cCert);
    }

    KeyPair sKP = KeyStoreTestUtil.generateKeyPair("RSA");
    X509Certificate sCert =
      KeyStoreTestUtil.generateCertificate("CN=localhost, O=server", sKP, 30,
                                           "SHA1withRSA");
    KeyStoreTestUtil.createKeyStore(serverKS, serverPassword, "server",
                                    sKP.getPrivate(), sCert);
    certs.put("server", sCert);

    if (trustStore) {
      trustKS = keystoresDir + "/trustKS.jks";
      KeyStoreTestUtil.createTrustStore(trustKS, trustPassword, certs);
    }

    Configuration clientSSLConf = createClientSSLConfig(clientKS,
        clientPassword, clientPassword, trustKS, trustPassword, excludeCiphers);
    Configuration serverSSLConf = createServerSSLConfig(serverKS,
        serverPassword, serverPassword, trustKS, trustPassword, excludeCiphers);

    saveConfig(sslClientConfFile, clientSSLConf);
    saveConfig(sslServerConfFile, serverSSLConf);

    conf.set(SSLFactory.SSL_HOSTNAME_VERIFIER_KEY, "ALLOW_ALL");
    conf.set(SSLFactory.SSL_CLIENT_CONF_KEY, sslClientConfFile.getName());
    conf.set(SSLFactory.SSL_SERVER_CONF_KEY, sslServerConfFile.getName());
    conf.setBoolean(SSLFactory.SSL_REQUIRE_CLIENT_CERT_KEY, useClientCert);
  }

  /**
   * Creates SSL configuration for a client.
   * 
   * @param clientKS String client keystore file
   * @param password String store password, or null to avoid setting store
   *   password
   * @param keyPassword String key password, or null to avoid setting key
   *   password
   * @param trustKS String truststore file
   * @return Configuration for client SSL
   */
  public static Configuration createClientSSLConfig(String clientKS,
      String password, String keyPassword, String trustKS,
      String trustPassword) {
    return createSSLConfig(SSLFactory.Mode.CLIENT,
      clientKS, password, keyPassword, trustKS, trustPassword, "");
  }

  /**
   * Creates SSL configuration for a client.
   *
   * @param clientKS String client keystore file
   * @param password String store password, or null to avoid setting store
   *   password
   * @param keyPassword String key password, or null to avoid setting key
   *   password
   * @param trustKS String truststore file
   * @param excludeCiphers String comma separated ciphers to exclude
   * @return Configuration for client SSL
   */
  public static Configuration createClientSSLConfig(String clientKS,
      String password, String keyPassword, String trustKS,
      String trustPassword, String excludeCiphers) {
    return createSSLConfig(SSLFactory.Mode.CLIENT,
      clientKS, password, keyPassword, trustKS, trustPassword, excludeCiphers);
  }

  /**
   * Creates SSL configuration for a server.
   * 
   * @param serverKS String server keystore file
   * @param password String store password, or null to avoid setting store
   *   password
   * @param keyPassword String key password, or null to avoid setting key
   *   password
   * @param trustKS String truststore file
   * @return Configuration for server SSL
   * @throws java.io.IOException
   */
  public static Configuration createServerSSLConfig(String serverKS,
      String password, String keyPassword, String trustKS, String trustPassword)
      throws IOException {
    return createSSLConfig(SSLFactory.Mode.SERVER,
      serverKS, password, keyPassword, trustKS, trustPassword, "");
  }

  /**
   * Creates SSL configuration for a server.
   *
   * @param serverKS String server keystore file
   * @param password String store password, or null to avoid setting store
   * password
   * @param keyPassword String key password, or null to avoid setting key
   * password
   * @param trustKS String truststore file
   * @param excludeCiphers String comma separated ciphers to exclude
   * @return
   * @throws IOException
   */
  public static Configuration createServerSSLConfig(String serverKS,
      String password, String keyPassword, String trustKS, String trustPassword,
      String excludeCiphers) throws IOException {
    return createSSLConfig(SSLFactory.Mode.SERVER,
      serverKS, password, keyPassword, trustKS, trustPassword, excludeCiphers);
  }

  /**
   * Returns the client SSL configuration file name.  Under parallel test
   * execution, this file name is parameterized by a unique ID to ensure that
   * concurrent tests don't collide on an SSL configuration file.
   *
   * @return client SSL configuration file name
   */
  public static String getClientSSLConfigFileName() {
    return getSSLConfigFileName("ssl-client");
  }

  /**
   * Returns the server SSL configuration file name.  Under parallel test
   * execution, this file name is parameterized by a unique ID to ensure that
   * concurrent tests don't collide on an SSL configuration file.
   *
   * @return client SSL configuration file name
   */
  public static String getServerSSLConfigFileName() {
    return getSSLConfigFileName("ssl-server");
  }

  /**
   * Returns an SSL configuration file name.  Under parallel test
   * execution, this file name is parameterized by a unique ID to ensure that
   * concurrent tests don't collide on an SSL configuration file.
   *
   * @param base the base of the file name
   * @return SSL configuration file name for base
   */
  private static String getSSLConfigFileName(String base) {
    String testUniqueForkId = System.getProperty("test.unique.fork.id");
    String fileSuffix = testUniqueForkId != null ? "-" + testUniqueForkId : "";
    return base + fileSuffix + ".xml";
  }

  /**
   * Creates SSL configuration.
   *
   * @param mode SSLFactory.Mode mode to configure
   * @param keystore String keystore file
   * @param password String store password, or null to avoid setting store
   *   password
   * @param keyPassword String key password, or null to avoid setting key
   *   password
   * @param trustKS String truststore file
   * @return Configuration for SSL
   */
  private static Configuration createSSLConfig(SSLFactory.Mode mode,
      String keystore, String password, String keyPassword, String trustKS,
      String trustStorePwd, String excludeCiphers) {

    Configuration sslConf = new Configuration(false);
    if (keystore != null) {
      sslConf.set(FileBasedKeyStoresFactory.resolvePropertyName(mode,
        FileBasedKeyStoresFactory.SSL_KEYSTORE_LOCATION_TPL_KEY), keystore);
    }
    if (password != null) {
      sslConf.set(FileBasedKeyStoresFactory.resolvePropertyName(mode,
        FileBasedKeyStoresFactory.SSL_KEYSTORE_PASSWORD_TPL_KEY), password);
    }
    if (keyPassword != null) {
      sslConf.set(FileBasedKeyStoresFactory.resolvePropertyName(mode,
        FileBasedKeyStoresFactory.SSL_KEYSTORE_KEYPASSWORD_TPL_KEY),
        keyPassword);
    }
    if (trustKS != null) {
      sslConf.set(FileBasedKeyStoresFactory.resolvePropertyName(mode,
        FileBasedKeyStoresFactory.SSL_TRUSTSTORE_LOCATION_TPL_KEY), trustKS);
    }
    if (trustStorePwd != null) {
      sslConf.set(FileBasedKeyStoresFactory.resolvePropertyName(mode,
        FileBasedKeyStoresFactory.SSL_TRUSTSTORE_PASSWORD_TPL_KEY),
          trustStorePwd);
    }
    if(null != excludeCiphers && !excludeCiphers.isEmpty()) {
      sslConf.set(FileBasedKeyStoresFactory.resolvePropertyName(mode,
      FileBasedKeyStoresFactory.SSL_EXCLUDE_CIPHER_LIST),
        excludeCiphers);
    }
    sslConf.set(FileBasedKeyStoresFactory.resolvePropertyName(mode,
      FileBasedKeyStoresFactory.SSL_TRUSTSTORE_RELOAD_INTERVAL_TPL_KEY), "1000");

    return sslConf;
  }

  /**
   * Saves configuration to a file.
   * 
   * @param file File to save
   * @param conf Configuration contents to write to file
   * @throws IOException if there is an I/O error saving the file
   */
  public static void saveConfig(File file, Configuration conf)
      throws IOException {
    Writer writer = new FileWriter(file);
    try {
      conf.writeXml(writer);
    } finally {
      writer.close();
    }
  }

  public static void provisionPasswordsToCredentialProvider() throws Exception {
    File testDir = GenericTestUtils.getTestDir();

    Configuration conf = new Configuration();
    final Path jksPath = new Path(testDir.toString(), "test.jks");
    final String ourUrl =
    JavaKeyStoreProvider.SCHEME_NAME + "://file" + jksPath.toUri();

    File file = new File(testDir, "test.jks");
    file.delete();
    conf.set(CredentialProviderFactory.CREDENTIAL_PROVIDER_PATH, ourUrl);

    CredentialProvider provider =
        CredentialProviderFactory.getProviders(conf).get(0);
    char[] keypass = {'k', 'e', 'y', 'p', 'a', 's', 's'};
    char[] storepass = {'s', 't', 'o', 'r', 'e', 'p', 'a', 's', 's'};

    // create new aliases
    try {
      provider.createCredentialEntry(
          FileBasedKeyStoresFactory.resolvePropertyName(SSLFactory.Mode.SERVER,
              FileBasedKeyStoresFactory.SSL_KEYSTORE_PASSWORD_TPL_KEY),
              storepass);

      provider.createCredentialEntry(
          FileBasedKeyStoresFactory.resolvePropertyName(SSLFactory.Mode.SERVER,
              FileBasedKeyStoresFactory.SSL_KEYSTORE_KEYPASSWORD_TPL_KEY),
              keypass);

      // write out so that it can be found in checks
      provider.flush();
    } catch (Exception e) {
      e.printStackTrace();
      throw e;
    }
  }

  /**
   * Get the SSL configuration
   * @return {@link Configuration} instance with ssl configs loaded
   */
  public static Configuration getSslConfig(){
    Configuration sslConf = new Configuration(false);
    String sslServerConfFile = KeyStoreTestUtil.getServerSSLConfigFileName();
    String sslClientConfFile = KeyStoreTestUtil.getClientSSLConfigFileName();
    sslConf.addResource(sslServerConfFile);
    sslConf.addResource(sslClientConfFile);
    sslConf.set(SSLFactory.SSL_SERVER_CONF_KEY, sslServerConfFile);
    sslConf.set(SSLFactory.SSL_CLIENT_CONF_KEY, sslClientConfFile);
    return sslConf;
  }

  /**
   * Configures the passed in {@link HttpsURLConnection} to allow all SSL
   * certificates.
   *
   * @param httpsConn The HttpsURLConnection to configure
   * @throws KeyManagementException
   * @throws NoSuchAlgorithmException
   */
  public static void setAllowAllSSL(HttpsURLConnection httpsConn)
      throws KeyManagementException, NoSuchAlgorithmException {
    setAllowAllSSL(httpsConn, null);
  }

  /**
   * Configures the passed in {@link HttpsURLConnection} to allow all SSL
   * certificates.  Also presents a client certificate.
   *
   * @param httpsConn The HttpsURLConnection to configure
   * @param clientCert The client certificate to present
   * @param clientKeyPair The KeyPair for the client certificate
   * @throws KeyManagementException
   * @throws NoSuchAlgorithmException
   */
  public static void setAllowAllSSL(HttpsURLConnection httpsConn,
      X509Certificate clientCert, KeyPair clientKeyPair)
      throws KeyManagementException, NoSuchAlgorithmException {
    X509KeyManager km = new X509KeyManager() {
      @Override
      public String[] getClientAliases(String s, Principal[] principals) {
        return new String[]{"client"};
      }

      @Override
      public String chooseClientAlias(String[] strings,
          Principal[] principals, Socket socket) {
        return "client";
      }

      @Override
      public String[] getServerAliases(String s, Principal[] principals) {
        return null;
      }

      @Override
      public String chooseServerAlias(String s, Principal[] principals,
          Socket socket) {
        return null;
      }

      @Override
      public X509Certificate[] getCertificateChain(String s) {
        return new X509Certificate[]{clientCert};
      }

      @Override
      public PrivateKey getPrivateKey(String s) {
        return clientKeyPair.getPrivate();
      }
    };
    setAllowAllSSL(httpsConn, km);
  }

  private static void setAllowAllSSL(HttpsURLConnection httpsConn,
      KeyManager km) throws KeyManagementException, NoSuchAlgorithmException {
    // Create a TrustManager that trusts anything
    TrustManager[] trustAllCerts = new TrustManager[] {
        new X509TrustManager() {
          @Override
          public java.security.cert.X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[]{};
          }

          @Override
          public void checkClientTrusted(
              java.security.cert.X509Certificate[] certs, String authType)
              throws CertificateException {
          }

          @Override
          public void checkServerTrusted(
              java.security.cert.X509Certificate[] certs, String authType)
              throws CertificateException {
          }
        }
    };
    KeyManager[] kms = (km == null) ? null : new KeyManager[]{km};
    SSLContext sc = SSLContext.getInstance("SSL");
    sc.init(kms, trustAllCerts, new SecureRandom());
    httpsConn.setSSLSocketFactory(sc.getSocketFactory());
    // Don't check the hostname
    httpsConn.setHostnameVerifier(new NoopHostnameVerifier());
  }
}
