Java HTTPS client certificate authentication
Asked Answered
R

9

275

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'm writing a Java client that needs to do a simple POST of data to a particular URL. That part works fine, the only problem is it's supposed to be done over HTTPS. The HTTPS part is fairly easy to handle (either with HTTPclient or using Java's built-in HTTPS support), but I'm stuck on authenticating with client certificates. I've noticed there's already a very similar question on here, which I haven't tried out with my code yet (will do so soon enough). My current issue is that - whatever I do - the Java client never sends along the certificate (I can check this with PCAP dumps).

I would like to know what exactly the client is supposed to present to the server when authenticating with certificates (specifically for Java - if that matters at all)? Is this a JKS file, or PKCS#12? What's supposed to be in them; just the client certificate, or a key? If so, which key? There's quite a bit of confusion about all the different kinds of files, certificate types and such.

As I've said before I'm new to HTTPS/SSL/TLS so I would appreciate some background information as well (doesn't have to be an essay; I'll settle for links to good articles).

Rupture answered 3/11, 2009 at 8:51 Comment(1)
i have given two certificate from client how to identify which one needs to add in keystore and truststore could you please help identify this issue as you have already gone through similar kind of issue , this issue i have raised actually not getting clue what to do #61374776Mogerly
R
275

Finally managed to solve all the issues, so I'll answer my own question. These are the settings/files I've used to manage to get my particular problem(s) solved;

The client's keystore is a PKCS#12 format file containing

  1. The client's public certificate (in this instance signed by a self-signed CA)
  2. The client's private key

To generate it I used OpenSSL's pkcs12 command, for example;

openssl pkcs12 -export -in client.crt -inkey client.key -out client.p12 -name "Whatever"

Tip: make sure you get the latest OpenSSL, not version 0.9.8h because that seems to suffer from a bug which doesn't allow you to properly generate PKCS#12 files.

This PKCS#12 file will be used by the Java client to present the client certificate to the server when the server has explicitly requested the client to authenticate. See the Wikipedia article on TLS for an overview of how the protocol for client certificate authentication actually works (also explains why we need the client's private key here).

The client's truststore is a straight forward JKS format file containing the root or intermediate CA certificates. These CA certificates will determine which endpoints you will be allowed to communicate with, in this case it will allow your client to connect to whichever server presents a certificate which was signed by one of the truststore's CA's.

To generate it you can use the standard Java keytool, for example;

keytool -genkey -dname "cn=CLIENT" -alias truststorekey -keyalg RSA -keystore ./client-truststore.jks -keypass whatever -storepass whatever
keytool -import -keystore ./client-truststore.jks -file myca.crt -alias myca

Using this truststore, your client will try to do a complete SSL handshake with all servers who present a certificate signed by the CA identified by myca.crt.

The files above are strictly for the client only. When you want to set-up a server as well, the server needs its own key- and truststore files. A great walk-through for setting up a fully working example for both a Java client and server (using Tomcat) can be found on this website.

Issues/Remarks/Tips

  1. Client certificate authentication can only be enforced by the server.
  2. (Important!) When the server requests a client certificate (as part of the TLS handshake), it will also provide a list of trusted CA's as part of the certificate request. When the client certificate you wish to present for authentication is not signed by one of these CA's, it won't be presented at all (in my opinion, this is weird behaviour, but I'm sure there's a reason for it). This was the main cause of my issues, as the other party had not configured their server properly to accept my self-signed client certificate and we assumed that the problem was at my end for not properly providing the client certificate in the request.
  3. Get Wireshark. It has great SSL/HTTPS packet analysis and will be a tremendous help debugging and finding the problem. It's similar to -Djavax.net.debug=ssl but is more structured and (arguably) easier to interpret if you're uncomfortable with the Java SSL debug output.
  4. It's perfectly possible to use the Apache httpclient library. If you want to use httpclient, just replace the destination URL with the HTTPS equivalent and add the following JVM arguments (which are the same for any other client, regardless of the library you want to use to send/receive data over HTTP/HTTPS):

    -Djavax.net.debug=ssl
    -Djavax.net.ssl.keyStoreType=pkcs12
    -Djavax.net.ssl.keyStore=client.p12
    -Djavax.net.ssl.keyStorePassword=whatever
    -Djavax.net.ssl.trustStoreType=jks
    -Djavax.net.ssl.trustStore=client-truststore.jks
    -Djavax.net.ssl.trustStorePassword=whatever
Rupture answered 10/11, 2009 at 19:27 Comment(5)
"When the client certificate you wish to present for authentication is not signed by one of these CA's, it won't be presented at all". The certificates aren't presented because the client knows they won't be accepted by the server. Also, your certificate can be signed by an intermediate CA "ICA", and the server can present your client with the root CA "RCA", and your web browser will still let you pick your certificate even though it's signed by ICA not RCA.Cruel
As an example of the above comment, consider a situation where you have one root CA (RCA1) and two intermediate CAs (ICA1 and ICA2). On Apache Tomcat if you import RCA1 into the trust store, your web browser will present ALL certificates signed by ICA1 and ICA2, even though they aren't in your trust store. This is because it's the chain that matters not individual certs.Cruel
"in my opinion, this is weird behaviour, but I'm sure there's a reason for it". The reason for it is that that's what it says in RFC 2246. Nothing weird about it. Allowing clients to present certificates that won't be accepted by the server is what would be weird, and a complete waste of time and space.Kinakinabalu
I'd highly recommend against using a single, JVM-wide keystore (and truststore!) as you have in your example above. Customizing a single connection is safer and more flexible, but it does require that you do write a bit more code. You have to customize the SSLContext as in @Magnus's answer.Genic
@Kinakinabalu can you say more about why it would never be appropriate for a client to send a cert not issued by a CA the server requests? I read through RFC 2246 and found nothing stating that a client can't offer a cert that isn't in the list of acceptable CA names from the server. Both curl and openssl s_client will happily send a client certificate that isn't in the list presented by the server, and in my experience many servers will accept client certificates that are not issued by a "trusted CA" but have been added to an allow list w/ their SPKI fingerprint or otherwise.Clytemnestra
Q
107

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"));
Quitt answered 10/9, 2015 at 23:54 Comment(7)
I had to use sslContext = SSLContexts.custom().loadTrustMaterial(keyFile, PASSWORD).build();. I couldn't get it working with loadKeyMaterial(...).Orling
@ConorSvensson Trust material is for the client trusting the remote server certificate, key material is for the server trusting the client.Quitt
I really like this concise and on-point answer. In case people are interested, I provide a worked example here with build instructions. https://mcmap.net/q/103114/-how-do-i-pass-the-client-certificate-with-http-clientRaffo
You saved my day! Just one extra change i had to do, send the password in loadKeyMaterial(keystore, keyPassphrase.toCharArray()) code' as well!Calcic
Isn't this answer specific to Apache HttpClient?Assemblyman
@Assemblyman Yes it is specific to Apache http. Every HTTP library will have it's own way of being configured, but most of them should somehow use an SSLContext.Quitt
I had 2 files that I had to load into the keyStore. I found that I had to replace the null with the password of the 2nd one when doing ".loadKeyMaterial(keyStore, null)"Elson
C
35

They JKS file is just a container for certificates and key pairs. In a client-side authentication scenario, the various parts of the keys will be located here:

  • The client's store will contain the client's private and public key pair. It is called a keystore.
  • The server's store will contain the client's public key. It is called a truststore.

The separation of truststore and keystore is not mandatory but recommended. They can be the same physical file.

To set the filesystem locations of the two stores, use the following system properties:

-Djavax.net.ssl.keyStore=clientsidestore.jks

and on the server:

-Djavax.net.ssl.trustStore=serversidestore.jks

To export the client's certificate (public key) to a file, so you can copy it to the server, use

keytool -export -alias MYKEY -file publicclientkey.cer -store clientsidestore.jks

To import the client's public key into the server's keystore, use (as the the poster mentioned, this has already been done by the server admins)

keytool -import -file publicclientkey.cer -store serversidestore.jks
Clemenciaclemency answered 3/11, 2009 at 8:51 Comment(7)
I should probably mention that I have no control over the server. The server has imported our public certificate. I've been told by the admins of that system that I need to explicitly provide the certificate so that it can be sent along during the handshake (their server explicitly requests this).Rupture
You'll need public and private keys for your public certificate (that one that's known to the server) as a JKS file.Tice
Thanks for the example code. In the code above, what is "mykey-public.cer" exactly? Is this the client public certificate (we use self-signed certificates)?Rupture
Yes it is, i've renamed the files in the code snippets accordingly. I hope this makes it clear.Clemenciaclemency
Righto, thanks. I keep getting confused because apparently "key" and "certificate" are used interchangeably.Rupture
(Sorry to drag this out like this) I've added the ssl debugging JVM parameter, along with my JKS keystore (which needs to have a public/private key pair in it as well next to the certificate) and it doesn't work, but I get a lot more information now. In the debug information; in the section "*** CertificateRequest Cert Types: RSA, DSS, Cert Authorities:" I don't see our self-signed CA certificate, even though it is included in the JRE cacerts keystore. The subsequent "*** Certificate chain" section is empty as well, which is I guess the root cause.Rupture
The server's truststore will contain the client's certificate, or that of one of its signers. Your answer conflates public keys with certificates at several points. They are not the same thing,Kinakinabalu
D
14

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;
}

}
Deliadelian answered 10/3, 2015 at 15:37 Comment(4)
If you would rather have the certificate available to all applications that use a particular JVM installation, follow this answer instead.Upthrust
is the method configureRequest() for setting the proxy of the client project right?Chickaree
yes, it is http client configuration and it is proxy config in this caseDeliadelian
maybe I have a dumb question, but how do I create a file cacert.jks? I have exception Exception in thread "main" java.io.FileNotFoundException: .\cacert.jks (The system cannot find the file specified)Allcot
D
10

Given a p12 file with both the certificate and the private key (generated by openssl, for example), the following code will use that for a specific HttpsURLConnection:

    KeyStore keyStore = KeyStore.getInstance("pkcs12");
    keyStore.load(new FileInputStream(keyStorePath), keystorePassword.toCharArray());
    KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
    kmf.init(keyStore, keystorePassword.toCharArray());
    SSLContext ctx = SSLContext.getInstance("TLS");
    ctx.init(kmf.getKeyManagers(), null, null);
    SSLSocketFactory sslSocketFactory = ctx.getSocketFactory();

    HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
    connection.setSSLSocketFactory(sslSocketFactory);

The SSLContext takes some time to initialize, so you might want to cache it.

Dozen answered 25/3, 2020 at 8:19 Comment(0)
P
6

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.

Proverbs answered 2/5, 2017 at 12:52 Comment(1)
Think you have typo in server CSR generation step: should use server.key not client.keyActin
B
4

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 to bank

  1. Receive 2 certificate: my client root certificate user.pem and bank root certificate: bank.crt

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

    openssl pkcs12 -export -in user.pem -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.

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

    keytool -import -alias bankca -file bank.crt -keystore keystore.p12 -storepass MY_PASS
    

    Check keystore certificates by:

    keytool -list -keystore keystore.p12
    
  4. 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;
    }
    
Bushore answered 13/12, 2019 at 7:22 Comment(2)
You can do it all with the keytool. There is no need forOpenSSL in this at all.Kinakinabalu
This answer is Gold !!!. Thanks a lot :)Forestaysail
H
3

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);
  }
    }

} 
Heatstroke answered 28/7, 2020 at 20:16 Comment(1)
Since i am a fan of making lazy people actually read and modify code , if you want to download the entire chain (which you absolutely want to do to make ssl auth work from behind corporate proxies) all you need to do is add a loop in above code , to loop over the chain and write it out to cacerts or jssecacerts or whatever (you figure it out)Heatstroke
D
-1

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.

Dorathydorca answered 23/9, 2015 at 17:48 Comment(2)
PKCS12 format was traditionally used for privatekey-AND-cert, but Java since 8 in 2014 (more than a year before this answer) has supported PKCS12 containing cert(s) without privatekey(s). Regardless of the keystore format, client auth does require privatekey-AND-cert. I don't understand your second sentence, but Java client can automatically select a client cert-and-key if at least one suitable entry is available or a keymanager can be configured to use a specified one.Liftoff
Your second sentence is totally incorrect. The server provides its trusted signers, and the client either dies or doesn't provide a certificate that satisifes that constraint. Automatically, not via 'in your code'. That is the server's 'way of knowing it is representing a client'.Kinakinabalu

© 2022 - 2024 — McMap. All rights reserved.