Java HTTPS client certificate authentication

后端 未结 9 1363
情深已故
情深已故 2020-11-22 07:42

I\'m fairly new to HTTPS/SSL/TLS and I\'m a bit confused over what exactly the clients are supposed to present when authenticating with certificates.

I\

相关标签:
9条回答
  • 2020-11-22 07:49

    I've connected to bank with two-way SSL (client and server certificate) with Spring Boot. So describe here all my steps, hope it helps someone (simplest working solution, I've found):

    1. Generate sertificate request:

      • Generate private key:

        openssl genrsa -des3 -passout pass:MY_PASSWORD -out user.key 2048
        
      • Generate certificate request:

        openssl req -new -key user.key -out user.csr -passin pass:MY_PASSWORD
        

      Keep user.key (and password) and send certificate request user.csr to bank for my sertificate

    2. Receive 2 certificate: my client root certificate clientId.crt and bank root certificate: bank.crt

    3. Create Java keystore (enter key password and set keystore password):

      openssl pkcs12 -export -in clientId.crt -inkey user.key -out keystore.p12 -name clientId -CAfile ca.crt -caname root
      

      Don't pay attention on output: unable to write 'random state'. Java PKCS12 keystore.p12 created.

    4. Add into keystore bank.crt (for simplicity I've used one keystore):

      keytool -import -alias banktestca -file banktestca.crt -keystore keystore.p12 -storepass javaops
      

      Check keystore certificates by:

      keytool -list -keystore keystore.p12
      
    5. Ready for Java code:) I've used Spring Boot RestTemplate with add org.apache.httpcomponents.httpcore dependency:

      @Bean("sslRestTemplate")
      public RestTemplate sslRestTemplate() throws Exception {
        char[] storePassword = appProperties.getSslStorePassword().toCharArray();
        URL keyStore = new URL(appProperties.getSslStore());
      
        SSLContext sslContext = new SSLContextBuilder()
              .loadTrustMaterial(keyStore, storePassword)
        // use storePassword twice (with key password do not work)!!
              .loadKeyMaterial(keyStore, storePassword, storePassword) 
              .build();
      
        // Solve "Certificate doesn't match any of the subject alternative names"
        SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
      
        CloseableHttpClient client = HttpClients.custom().setSSLSocketFactory(socketFactory).build();
        HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(client);
        RestTemplate restTemplate = new RestTemplate(factory);
        // restTemplate.setMessageConverters(List.of(new Jaxb2RootElementHttpMessageConverter()));
        return restTemplate;
      }
      
    0 讨论(0)
  • 2020-11-22 07:51

    For those of you who simply want to set up a two-way authentication (server and client certificates), a combination of these two links will get you there :

    Two-way auth setup:

    https://linuxconfig.org/apache-web-server-ssl-authentication

    You don't need to use the openssl config file that they mention; just use

    • $ openssl genrsa -des3 -out ca.key 4096

    • $ openssl req -new -x509 -days 365 -key ca.key -out ca.crt

    to generate your own CA certificate, and then generate and sign the server and client keys via:

    • $ openssl genrsa -des3 -out server.key 4096

    • $ openssl req -new -key server.key -out server.csr

    • $ openssl x509 -req -days 365 -in server.csr -CA ca.crt -CAkey ca.key -set_serial 100 -out server.crt

    and

    • $ openssl genrsa -des3 -out client.key 4096

    • $ openssl req -new -key client.key -out client.csr

    • $ openssl x509 -req -days 365 -in client.csr -CA ca.crt -CAkey ca.key -set_serial 101 -out client.crt

    For the rest follow the steps in the link. Managing the certificates for Chrome works the same as in the example for firefox that is mentioned.

    Next, setup the server via:

    https://www.digitalocean.com/community/tutorials/how-to-create-a-ssl-certificate-on-apache-for-ubuntu-14-04

    Note that you have already created the server .crt and .key so you don't have to do that step anymore.

    0 讨论(0)
  • 2020-11-22 07:52

    Other answers show how to globally configure client certificates. However if you want to programmatically define the client key for one particular connection, rather than globally define it across every application running on your JVM, then you can configure your own SSLContext like so:

    String keyPassphrase = "";
    
    KeyStore keyStore = KeyStore.getInstance("PKCS12");
    keyStore.load(new FileInputStream("cert-key-pair.pfx"), keyPassphrase.toCharArray());
    
    SSLContext sslContext = SSLContexts.custom()
            .loadKeyMaterial(keyStore, null)
            .build();
    
    HttpClient httpClient = HttpClients.custom().setSSLContext(sslContext).build();
    HttpResponse response = httpClient.execute(new HttpGet("https://example.com"));
    
    0 讨论(0)
  • 2020-11-22 07:57

    I think the fix here was the keystore type, pkcs12(pfx) always have private key and JKS type can exist without private key. Unless you specify in your code or select a certificate thru browser, the server have no way of knowing it is representing a client on the other end.

    0 讨论(0)
  • 2020-11-22 08:02

    There is a better way than having to manually navigate to https://url , knowing what button to click in what browser, knowing where and how to save the "certificate" file and finally knowing the magic incantation for the keytool to install it locally.

    Just do this:

    1. Save code below to InstallCert.java
    2. Open command line and execute: javac InstallCert.java
    3. Run like: java InstallCert <host>[:port] [passphrase] (port and passphrase are optional)

    Here is the code for InstallCert, note the year in header, will need to modify some parts for "later" versions of java:

    /*
     * Copyright 2006 Sun Microsystems, Inc.  All Rights Reserved.
     *
     * Redistribution and use in source and binary forms, with or without
     * modification, are permitted provided that the following conditions
     * are met:
     *
     *   - Redistributions of source code must retain the above copyright
     *     notice, this list of conditions and the following disclaimer.
     *
     *   - Redistributions in binary form must reproduce the above copyright
     *     notice, this list of conditions and the following disclaimer in the
     *     documentation and/or other materials provided with the distribution.
     *
     *   - Neither the name of Sun Microsystems nor the names of its
     *     contributors may be used to endorse or promote products derived
     *     from this software without specific prior written permission.
     *
     * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
     * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
     * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
     * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
     * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
     * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
     * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     */
    
    import java.io.*;
    import java.net.URL;
    
    import java.security.*;
    import java.security.cert.*;
    
    import javax.net.ssl.*;
    
    public class InstallCert {
    
        public static void main(String[] args) throws Exception {
      String host;
      int port;
      char[] passphrase;
      if ((args.length == 1) || (args.length == 2)) {
          String[] c = args[0].split(":");
          host = c[0];
          port = (c.length == 1) ? 443 : Integer.parseInt(c[1]);
          String p = (args.length == 1) ? "changeit" : args[1];
          passphrase = p.toCharArray();
      } else {
          System.out.println("Usage: java InstallCert <host>[:port] [passphrase]");
          return;
      }
    
      File file = new File("jssecacerts");
      if (file.isFile() == false) {
          char SEP = File.separatorChar;
          File dir = new File(System.getProperty("java.home") + SEP
            + "lib" + SEP + "security");
          file = new File(dir, "jssecacerts");
          if (file.isFile() == false) {
        file = new File(dir, "cacerts");
          }
      }
      System.out.println("Loading KeyStore " + file + "...");
      InputStream in = new FileInputStream(file);
      KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
      ks.load(in, passphrase);
      in.close();
    
      SSLContext context = SSLContext.getInstance("TLS");
      TrustManagerFactory tmf =
          TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
      tmf.init(ks);
      X509TrustManager defaultTrustManager = (X509TrustManager)tmf.getTrustManagers()[0];
      SavingTrustManager tm = new SavingTrustManager(defaultTrustManager);
      context.init(null, new TrustManager[] {tm}, null);
      SSLSocketFactory factory = context.getSocketFactory();
    
      System.out.println("Opening connection to " + host + ":" + port + "...");
      SSLSocket socket = (SSLSocket)factory.createSocket(host, port);
      socket.setSoTimeout(10000);
      try {
          System.out.println("Starting SSL handshake...");
          socket.startHandshake();
          socket.close();
          System.out.println();
          System.out.println("No errors, certificate is already trusted");
      } catch (SSLException e) {
          System.out.println();
          e.printStackTrace(System.out);
      }
    
      X509Certificate[] chain = tm.chain;
      if (chain == null) {
          System.out.println("Could not obtain server certificate chain");
          return;
      }
    
      BufferedReader reader =
        new BufferedReader(new InputStreamReader(System.in));
    
      System.out.println();
      System.out.println("Server sent " + chain.length + " certificate(s):");
      System.out.println();
      MessageDigest sha1 = MessageDigest.getInstance("SHA1");
      MessageDigest md5 = MessageDigest.getInstance("MD5");
      for (int i = 0; i < chain.length; i++) {
          X509Certificate cert = chain[i];
          System.out.println
            (" " + (i + 1) + " Subject " + cert.getSubjectDN());
          System.out.println("   Issuer  " + cert.getIssuerDN());
          sha1.update(cert.getEncoded());
          System.out.println("   sha1    " + toHexString(sha1.digest()));
          md5.update(cert.getEncoded());
          System.out.println("   md5     " + toHexString(md5.digest()));
          System.out.println();
      }
    
      System.out.println("Enter certificate to add to trusted keystore or 'q' to quit: [1]");
      String line = reader.readLine().trim();
      int k;
      try {
          k = (line.length() == 0) ? 0 : Integer.parseInt(line) - 1;
      } catch (NumberFormatException e) {
          System.out.println("KeyStore not changed");
          return;
      }
    
      X509Certificate cert = chain[k];
      String alias = host + "-" + (k + 1);
      ks.setCertificateEntry(alias, cert);
    
      OutputStream out = new FileOutputStream("jssecacerts");
      ks.store(out, passphrase);
      out.close();
    
      System.out.println();
      System.out.println(cert);
      System.out.println();
      System.out.println
        ("Added certificate to keystore 'jssecacerts' using alias '"
        + alias + "'");
        }
    
        private static final char[] HEXDIGITS = "0123456789abcdef".toCharArray();
    
        private static String toHexString(byte[] bytes) {
      StringBuilder sb = new StringBuilder(bytes.length * 3);
      for (int b : bytes) {
          b &= 0xff;
          sb.append(HEXDIGITS[b >> 4]);
          sb.append(HEXDIGITS[b & 15]);
          sb.append(' ');
      }
      return sb.toString();
        }
    
        private static class SavingTrustManager implements X509TrustManager {
    
      private final X509TrustManager tm;
      private X509Certificate[] chain;
    
      SavingTrustManager(X509TrustManager tm) {
          this.tm = tm;
      }
    
      public X509Certificate[] getAcceptedIssuers() {
          throw new UnsupportedOperationException();
      }
    
      public void checkClientTrusted(X509Certificate[] chain, String authType)
        throws CertificateException {
          throw new UnsupportedOperationException();
      }
    
      public void checkServerTrusted(X509Certificate[] chain, String authType)
        throws CertificateException {
          this.chain = chain;
          tm.checkServerTrusted(chain, authType);
      }
        }
    
    } 
    
    0 讨论(0)
  • 2020-11-22 08:04

    Maven pom.xml:

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <groupId>some.examples</groupId>
        <artifactId>sslcliauth</artifactId>
        <version>1.0-SNAPSHOT</version>
        <packaging>jar</packaging>
        <name>sslcliauth</name>
        <dependencies>
            <dependency>
                <groupId>org.apache.httpcomponents</groupId>
                <artifactId>httpclient</artifactId>
                <version>4.4</version>
            </dependency>
        </dependencies>
    </project>
    

    Java code:

    package some.examples;
    
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.security.KeyManagementException;
    import java.security.KeyStore;
    import java.security.KeyStoreException;
    import java.security.NoSuchAlgorithmException;
    import java.security.UnrecoverableKeyException;
    import java.security.cert.CertificateException;
    import java.util.logging.Level;
    import java.util.logging.Logger;
    import javax.net.ssl.SSLContext;
    import org.apache.http.HttpEntity;
    import org.apache.http.HttpHost;
    import org.apache.http.client.config.RequestConfig;
    import org.apache.http.client.methods.CloseableHttpResponse;
    import org.apache.http.client.methods.HttpPost;
    import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
    import org.apache.http.ssl.SSLContexts;
    import org.apache.http.impl.client.CloseableHttpClient;
    import org.apache.http.impl.client.HttpClients;
    import org.apache.http.util.EntityUtils;
    import org.apache.http.entity.InputStreamEntity;
    
    public class SSLCliAuthExample {
    
    private static final Logger LOG = Logger.getLogger(SSLCliAuthExample.class.getName());
    
    private static final String CA_KEYSTORE_TYPE = KeyStore.getDefaultType(); //"JKS";
    private static final String CA_KEYSTORE_PATH = "./cacert.jks";
    private static final String CA_KEYSTORE_PASS = "changeit";
    
    private static final String CLIENT_KEYSTORE_TYPE = "PKCS12";
    private static final String CLIENT_KEYSTORE_PATH = "./client.p12";
    private static final String CLIENT_KEYSTORE_PASS = "changeit";
    
    public static void main(String[] args) throws Exception {
        requestTimestamp();
    }
    
    public final static void requestTimestamp() throws Exception {
        SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(
                createSslCustomContext(),
                new String[]{"TLSv1"}, // Allow TLSv1 protocol only
                null,
                SSLConnectionSocketFactory.getDefaultHostnameVerifier());
        try (CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(csf).build()) {
            HttpPost req = new HttpPost("https://changeit.com/changeit");
            req.setConfig(configureRequest());
            HttpEntity ent = new InputStreamEntity(new FileInputStream("./bytes.bin"));
            req.setEntity(ent);
            try (CloseableHttpResponse response = httpclient.execute(req)) {
                HttpEntity entity = response.getEntity();
                LOG.log(Level.INFO, "*** Reponse status: {0}", response.getStatusLine());
                EntityUtils.consume(entity);
                LOG.log(Level.INFO, "*** Response entity: {0}", entity.toString());
            }
        }
    }
    
    public static RequestConfig configureRequest() {
        HttpHost proxy = new HttpHost("changeit.local", 8080, "http");
        RequestConfig config = RequestConfig.custom()
                .setProxy(proxy)
                .build();
        return config;
    }
    
    public static SSLContext createSslCustomContext() throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException, KeyManagementException, UnrecoverableKeyException {
        // Trusted CA keystore
        KeyStore tks = KeyStore.getInstance(CA_KEYSTORE_TYPE);
        tks.load(new FileInputStream(CA_KEYSTORE_PATH), CA_KEYSTORE_PASS.toCharArray());
    
        // Client keystore
        KeyStore cks = KeyStore.getInstance(CLIENT_KEYSTORE_TYPE);
        cks.load(new FileInputStream(CLIENT_KEYSTORE_PATH), CLIENT_KEYSTORE_PASS.toCharArray());
    
        SSLContext sslcontext = SSLContexts.custom()
                //.loadTrustMaterial(tks, new TrustSelfSignedStrategy()) // use it to customize
                .loadKeyMaterial(cks, CLIENT_KEYSTORE_PASS.toCharArray()) // load client certificate
                .build();
        return sslcontext;
    }
    
    }
    
    0 讨论(0)
提交回复
热议问题