1

I have a java keystore with which I can connect to a protected https third-party service. I use this keystore explicitely in my code when I initialize my web client:

// Solution #1
String password = "changeit";
KeyStore keyStore = KeyStore.getInstance(new File("src/main/resources/keystore.jks"), password.toCharArray());

SSLContext sslContext = new SSLContextBuilder()
    .loadKeyMaterial(keyStore, password.toCharArray())
    .build();

SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslContext, (hostname, session) -> true);

HttpClient httpClient = HttpClients.custom()
    .setSSLSocketFactory(socketFactory)
    .build();

With this approach, everything works fine.

But I also know that there is a possibility to specify the system variables javax.net.ssl.keyStore and javax.net.ssl.keyStorePassword. So I was expecting an alternative solution to the code above will also work:

// Solution #2
System.setProperty( "javax.net.ssl.keyStore", "src/main/resources/keystore.jks");
System.setProperty( "javax.net.ssl.keyStorePassword", "changeit"); 
HttpClient httpClient = HttpClients.createDefault();

where I create a default web client without constructing SSLContext with my keystore explicitly. I have expected that the default web client will take somehow the keystore automatically from javax.net.ssl.keyStore. But it seems it did not take and this solution did not work for me.

So I wonder what is the purpose of the use of system property javax.net.ssl.keyStore? How it can be useful? What is the best practice here?

Mikhail Geyer
  • 881
  • 2
  • 9
  • 27

3 Answers3

0

You can do something like the first solution, but you have to remove the password as a String, the password should be either in your properties file that is loaded at runtime, or specify it as an environment variable when you start the JVM.

I would say that the main point is that the password should not be in the code.

For the second solution you might want to check the trustStore environment variables.

https://docs.oracle.com/javadb/10.8.3.0/adminguide/cadminsslserver.html

Léo Schneider
  • 2,085
  • 3
  • 15
  • 28
0

Set the truststore javax.net.ssl.trustStore for the public cert to provide to the external service. javax.net.ssl.keyStore is for storing your private cert when running on https.

// Your private cert location
System.setProperty("javax.net.ssl.keyStoreType", "pkcs12");
System.setProperty("javax.net.ssl.keyStore", env.getProperty(SSL_KEYSTORE));
System.setProperty("javax.net.ssl.keyStorePassword", env.getProperty(SSL_KEYSTORE_PASS));
// Public cert location
System.setProperty("javax.net.ssl.trustStoreType", "pkcs12");
System.setProperty("javax.net.ssl.trustStore", env.getProperty(SSL_TRUSTSTORE));
System.setProperty("javax.net.ssl.trustStorePassword", env.getProperty(SSL_TRUSTSTORE_PASS));

Also, I would recommend loading the properties (especially passwords) from a separate properties file, as mentioned by Léo Schneider.

UPDATE To your question regarding usefulness of javax.net.ssl properties, these properties are an alternative to define the truststore and keystore properties. It is useful b/c not all libraries allow for SSLContext as input where it may be needed (for example legacy libraries that don't support ssl). Furthermore, these properties can also be defined directly from the command line, increasing usability.

gagarwa
  • 1,426
  • 1
  • 15
  • 28
  • Thank you for your answer. But it is still not clear for me - what for `javax.net.ssl.keyStore` argument is from a practical point of view? In any case, I should always create a ssl context in web client. How `javax.net.ssl.keyStore` helps me here? I can just use a direct path to my Keystore as I did in `Solution #1` (password out of question). – Mikhail Geyer Sep 03 '20 at 09:27
  • `javax.net.ssl` properties are an alternative to define the truststore and keystore properties. It is useful b/c not all libraries allow for SSLContext as input where it may be needed. Furthermore, these properties can also be defined directly from the command line. – gagarwa Sep 03 '20 at 10:01
  • Ok, I ran my app with `-Djavax.net.ssl.keyStorePassword=changeit -Djavax.net.ssl.keyStore=keystore.p12 -Djavax.net.ssl.keyStoreType=pkcs12` but my requests stay unauthorized until I define custom `sslContext` and put it explicitly in `httpClient` (as I did in `Solution #1`). I was hoping that with these properties I can escape the custom definition of `sslContext`, but it seems that it is impossible. So these properties do not really helpful, I will just use them only to get values and then do all the work of defining `sslContext` by myself. Or I miss something? – Mikhail Geyer Sep 07 '20 at 15:13
  • As I mentioned in the first line, you need to use `javax.net.ssl.trustStore` properties, NOT `javax.net.ssl.keyStore`. Keystore is for storing YOUR certificates, so that the server can verify your identity....Sorry if I wasn't clear. – gagarwa Sep 08 '20 at 12:20
  • No, I need exactly `keyStore` as my request to the remote server should be formed with the identity certificate and private key (from them I have generated my `keyStore`). The remote server has only my `public` key, so my requests can be authenticated. – Mikhail Geyer Sep 08 '20 at 12:27
  • Identity Certificate & Private Key? They should be the same (identity cert = private key). Also, you can't send private key (that would be unsafe) Just FYI. Some other notes: 1) If you are connecting to https, you need to get their public key and also provide the `javax.net.ssl.trustStore` props, or enable accept all connections by default (unsafe, not recommended). 2) Make sure the keystore location is correct...you are providing two different values in your post vs. comments. – gagarwa Sep 08 '20 at 14:51
  • Also, I mentioned `pkcs12`, but please double check your keystore type. – gagarwa Sep 08 '20 at 15:03
  • Please check out my answer where I summarize my findings. – Mikhail Geyer Sep 08 '20 at 15:47
0

Accordingly to the answer of @Bruno in How to acess jvm default KeyStore? there is no default KeyStore in java. That means that if you run the app with

-Djavax.net.ssl.keyStorePassword=changeit -Djavax.net.ssl.keyStore=/opt/app/certificates/keyStore.jks

this will also require to parse them in your code like

  private static final String filePath = System.getProperty("javax.net.ssl.keyStore");
  private static final String password = System.getProperty("javax.net.ssl.keyStorePassword");

and then use explicitly in your HttpClient (like in Solution #1). In other words, just specifying the properties for keyStore is useless if you will not parse them manually and not use them for your HttpClient. This is what I was trying to understand when I had posted my question.

This is an important difference from system properties for TrustStore like

-Djavax.net.ssl.trustStorePassword=changeit -Djavax.net.ssl.trustStore=/opt/app/certificates/cacert

Specifying these properties does not require any extra code. As there is a default TustStore which will be created automatically by JVM from the properties. An then httpClient will just use that default TrustStore without any efforts from a developer.

Mikhail Geyer
  • 881
  • 2
  • 9
  • 27
  • There is something missing here. I know for certain that defining these properties are necessary for IBM MQ, as there is no alternative. So either MQ is getting these properties explicitly or there is some misunderstanding here. I asked Bruno to clarify. – gagarwa Sep 09 '20 at 17:01
  • I particularly was interested in `io.netty` webClient. Here is a similar discussion on this topic https://github.com/reactor/reactor-netty/issues/640. – Mikhail Geyer Sep 11 '20 at 11:58
  • The solution mentioned by **violetagg** in the discussion should work. – gagarwa Sep 11 '20 at 15:32
  • I tried it and it did not work for me - WebClient still did not use the default JVM SSLContext with KeyStore. – Mikhail Geyer Sep 14 '20 at 07:22