I am developing a Java application that queries a REST API on a remote server over HTTP. For security reasons this communication should be switched to HTTPS.
Now that Let's Encrypt started their public beta, I'd like to know if Java currently works (or is confirmed to be working in the future) with their certificates by default.
Let's Encrypt got their intermediate cross-signed by IdenTrust, which should be good news. However, I cannot find any of these two in the output of this command:
keytool -keystore "..\lib\security\cacerts" -storepass changeit -list
I know that trusted CAs can be added manually on each machine, but since my application should be free to download and executable without any further configuration, I am looking for solutions that work "out of the box". Do you have good news for me?
[Update 2016-06-08: According to https://bugs.openjdk.java.net/browse/JDK-8154757 the IdenTrust CA will be included in Oracle Java 8u101.]
[Update 2016-08-05: Java 8u101 has been released and does indeed include the IdenTrust CA: release notes]
Does Java support Let's Encrypt certificates?
Yes. The Let's Encrypt certificate is just a regular public key certificate. Java supports it (according to Let's Encrypt Certificate Compatibility, for Java 7 >= 7u111 and Java 8 >= 8u101).
Does Java trust Let's Encrypt certificates out of the box?
No / it depends on the JVM. The truststore of Oracle JDK/JRE up to 8u66 contains neither the Let's Encrypt CA specifically nor the IdenTrust CA that cross signed it. new URL("https://letsencrypt.org/").openConnection().connect(); for example results in javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException.
You can however provide your own validator / define a custom keystore that contains the required root CA or import the certificate into the JVM truststore.
https://community.letsencrypt.org/t/will-the-cross-root-cover-trust-by-the-default-list-in-the-jdk-jre/134/10 discusses the topic as well.
Here is some example code that shows how to add a certificate to the default truststore at runtime. You'll just need to add the certificate (exported from firefox as .der and put in classpath)
Based on How can I get a list of trusted root certificates in Java? and http://developer.android.com/training/articles/security-ssl.html#UnknownCa
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.KeyStore;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.PKIXParameters;
import java.security.cert.TrustAnchor;
import java.security.cert.X509Certificate;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.TrustManagerFactory;
public class SSLExample {
// BEGIN ------- ADDME
static {
try {
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
Path ksPath = Paths.get(System.getProperty("java.home"),
"lib", "security", "cacerts");
keyStore.load(Files.newInputStream(ksPath),
"changeit".toCharArray());
CertificateFactory cf = CertificateFactory.getInstance("X.509");
try (InputStream caInput = new BufferedInputStream(
// this files is shipped with the application
SSLExample.class.getResourceAsStream("DSTRootCAX3.der"))) {
Certificate crt = cf.generateCertificate(caInput);
System.out.println("Added Cert for " + ((X509Certificate) crt)
.getSubjectDN());
keyStore.setCertificateEntry("DSTRootCAX3", crt);
}
if (false) { // enable to see
System.out.println("Truststore now trusting: ");
PKIXParameters params = new PKIXParameters(keyStore);
params.getTrustAnchors().stream()
.map(TrustAnchor::getTrustedCert)
.map(X509Certificate::getSubjectDN)
.forEach(System.out::println);
System.out.println();
}
TrustManagerFactory tmf = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(keyStore);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, tmf.getTrustManagers(), null);
SSLContext.setDefault(sslContext);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
// END ---------- ADDME
public static void main(String[] args) throws IOException {
// signed by default trusted CAs.
testUrl(new URL("https://google.com"));
testUrl(new URL("https://www.thawte.com"));
// signed by letsencrypt
testUrl(new URL("https://helloworld.letsencrypt.org"));
// signed by LE's cross-sign CA
testUrl(new URL("https://letsencrypt.org"));
// expired
testUrl(new URL("https://tv.eurosport.com/"));
// self-signed
testUrl(new URL("https://www.pcwebshop.co.uk/"));
}
static void testUrl(URL url) throws IOException {
URLConnection connection = url.openConnection();
try {
connection.connect();
System.out.println("Headers of " + url + " => "
+ connection.getHeaderFields());
} catch (SSLHandshakeException e) {
System.out.println("Untrusted: " + url);
}
}
}
I know the OP asked for a solution without local configuration changes, but in case you want to add the trust chain to the keystore permanently:
$ keytool -trustcacerts \
-keystore $JAVA_HOME/jre/lib/security/cacerts \
-storepass changeit \
-noprompt \
-importcert \
-file /etc/letsencrypt/live/hostname.com/chain.pem
source: https://community.letsencrypt.org/t/will-the-cross-root-cover-trust-by-the-default-list-in-the-jdk-jre/134/13
Detailed answer for those of us willing to make local config changes that includes backing up the config file:
1. Test if it is working before the changes
If you don't have a test program already, you can use my java SSLPing ping program which tests the TLS handshake (will work with any SSL/TLS port, not just HTTPS). I'll use the prebuilt SSLPing.jar, but reading the code and building it yourself is a quick and easy task:
$ git clone https://github.com/dimalinux/SSLPing.git
Cloning into 'SSLPing'...
[... output snipped ...]
Since my Java version is earlier than 1.8.0_101 (not released at the time of this writing), a Let's Encrypt certificate will not verify by default. Let's see what failure looks like before applying the fix:
$ java -jar SSLPing/dist/SSLPing.jar helloworld.letsencrypt.org 443
About to connect to 'helloworld.letsencrypt.org' on port 443
javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
[... output snipped ...]
2. Import the certificate
I'm on Mac OS X with the JAVA_HOME environment variable set. Later commands will assume this variable is set for the java installation you are modifying:
$ echo $JAVA_HOME
/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/
Make a backup of the cacerts file we will be modifying so you can back out any change without reinstalling the JDK:
$ sudo cp -a $JAVA_HOME/jre/lib/security/cacerts $JAVA_HOME/jre/lib/security/cacerts.orig
Download the signing certificate we need to import:
$ wget https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.der
Perform the import:
$ sudo keytool -trustcacerts -keystore $JAVA_HOME/jre/lib/security/cacerts -storepass changeit -noprompt -importcert -alias lets-encrypt-x3-cross-signed -file lets-encrypt-x3-cross-signed.der
Certificate was added to keystore
3. Verify that it is working after the changes
Verify that Java is now happy connecting to the SSL port:
$ java -jar SSLPing/dist/SSLPing.jar helloworld.letsencrypt.org 443
About to connect to 'helloworld.letsencrypt.org' on port 443
Successfully connected
For JDK which do not support Let's Encrypt certificates yet, you can add those to the JDK cacerts following this process (thanks to this).
Download all the certificates on https://letsencrypt.org/certificates/ (choose the der format) and add them one by one with this kind of command (example for letsencryptauthorityx1.der):
keytool -import -keystore PATH_TO_JDK\jre\lib\security\cacerts -storepass changeit -noprompt -trustcacerts -alias letsencryptauthorityx1 -file PATH_TO_DOWNLOADS\letsencryptauthorityx1.der
Related
Can GraphDB Free be configured in any way to allow content to be served over HTTPS?
I'm creating a simple front-end web application using Angular2 that makes HTTP requests to the GraphDB SPARQL endpoint.
I'm using Windows IIS and the server has been setup not to trust any content this is not served over HTTPS.
Any insights would be helpful.
According the instructions in GraphDB's configuration file $GDB_HOME/conf/graphdb.properties, to enable HTTPS uncomment the following lines:
# Enable SSL (uncomment to enable)
graphdb.connector.SSLEnabled = true
graphdb.connector.scheme = https
graphdb.connector.secure = true
# GraphDB uses the Java implementation of SSL, which requires a configured key in the Java keystore.
# To setup keystore uncomment the following properties and set keystorePass and keyPass to the actual values.
graphdb.connector.keyFile = <path to the keystore file if different from ${graphdb.home}/keystore>
graphdb.connector.keystorePass = <secret>
graphdb.connector.keyAlias = graphdb
graphdb.connector.keyPass = <secret>
If you use a self-signed certificate add it to the keystore with:
keytool -genkey -alias graphdb -keyalg RSA
If you have a third-party trusted OpenSSL certificate, you would need to convert it to PKCS12 key and then import to the Java keystore with:
keytool -importkeystore -deststorepass MYPASS -srckeystore mypkcs12.p12 -srcstoretype PKCS12
For additional information on how to deal with the third party trusted certificates check this excellent link explaining in great details how to convert import private key and certificate into java keystore
Edited
To locate the $GDB_HOME directory for the desktop installation, check the GraphDB documentation.
I am passing certificates in RestTemplate request header. I get 403 forbidden error. How can I display the certificate information at my end before I make a call to the web service? I am using below code to pass certificate in request header.
What is the right way to send a client certificate with every request made by the resttemplate in spring?
Not sure about 403 as I'm newbie on certs & stuff, but for your other questions(I know this is late but might help someone who come across to this page) :
Java Keytool Commands for Checking :
keytool comes with JDK
If you need to check the information within a certificate, or Java keystore, use these commands.
Check a stand-alone certificate :
keytool -printcert -v -file mydomain.crt
Check which certificates are in a Java keystore :
keytool -list -v -keystore keystore.jks
Check a particular keystore entry using an alias :
keytool -list -v -keystore keystore.jks -alias mydomain
First, I would suggest to verify in your rest client whether you're able to access the rest endpoint or not by importing cert & key files:
https://www.getpostman.com/docs/v6/postman/sending_api_requests/certificates
Helpful links:
https://www.sslshopper.com/article-most-common-java-keytool-keystore-commands.html
I have a java web project and I have used few certificates to contact a URL. Now I have deployed project as a WAR file in a Unix server and my project has a certificate issue.
I have the certificates in my local store and I need to put them in the Unix server and need to add them to the keytool in Tomcat. How do i do that.
Can I get an example how do I do that with keytool?
There are at least 3 ways of handling this problem:
You can import the certificate into the JRE's default truststore (often in $JAVA_HOME/lib/security/cacerts). This will affect all the applications using that JRE (unless they override the default settings). You'll need to have write permissions on that cacerts file to do this too.
You can import the certificate into a local keystore that you will configure to be Tomcat's default truststore. Typically, you could make a copy of the default cacerts file and import your certificate into this copy, or you can create a new keystore and import only the certificates you know you need (keytool -import -keystore ... will create the keystore file if it doesn't exist). This can be done in tomcat by setting an additional system property in catalina.sh (or .bat): in JAVA_OPTS, you can add -Djavax.net.ssl.trustStore=/path/to/local/truststore.jks for example (and other related properties).
You can make that certificate be used only by certain connections in your application (or set the default SSLContext programmatically). For this, you'll need to alter your application so that it loads the keystore, uses it to initialise a TrustManagerFactory, in passed into an SSLContext. Then, how that SSLContext can be used depends on the client library you're using. There is an example in this answer.
Either way, you can import your cert (be it a CA cert or a specific server cert) into the truststore of your choice using:
keytool -import -file cert.pem -alias "some name" -keystore truststore.jks
(If using the programming route, you can also create your keystore in memory and load the certificate file dynamically, as shown in this answer. Using keystores might be easier, it's up to you to assess the pros and cons of the deployment you want to use.)
I got the answer for this. We need to find which java file the tomcat is using and we need to add the keytool to that particular thing.
this is where the tomcat has its cacerts .ie. the java which is used by the tomcat.
etc/pki/java/cacerts
Keytool command:
keytool -import -alias ttg-lys-cm1 -file /var/lib/certificates/ttg-lys-cm1.cer -keystore "/etc/pki/java/cacerts"
Once you do this restart tomcat and it works fine.
Thanks
pradeepa
When trying to run tests with arquillian with adminHttps option set to true, got an exception:
Could not connect to DAS on: (...) | java.security.cert.CertificateException: No subject alternative names present
org.jboss.arquillian.container.spi.client.container.LifecycleException: Could not connect to DAS on: (...) | java.security.cert.CertificateException: No subject alternative names present
at org.jboss.arquillian.container.glassfish.CommonGlassFishManager.start(CommonGlassFishManager.java:77)
at org.jboss.arquillian.container.glassfish.remote_3_1.GlassFishRestDeployableContainer.start(GlassFishRestDeployableContainer.java:59)
at org.jboss.arquillian.container.impl.ContainerImpl.start(ContainerImpl.java:199)
The examples which I found were without ssl turned on.
I think that I should import a certificate, but can't find how or if it's realy that problem. Any idea? Thanks for sugestions.
Whilst the accepted answer shows that this problem was due to the use of the containers self signed certificate, it doesn't explain how the problem can be overcome. Hopefully this answer will help anyone having the same problem.
You can import the containers self signed certificate into a keystore using the following command:
keytool -import -alias glassfish -file mycert.cer -keystore truststore.jks -storepass changeit
Change mycert.cer to the location of the certificate file
Change truststore.jks to the location of the keystore you want to use to store the certificate. This can either be a new keystore or an existing one. The storepass parameter is the password for the keystore. If you are using an existing keystore then you will need to specify the password associated with the keystore.
Once you have imported the self signed certificate into the keystore you need to tell the JVM running Arquillian to use the keystore. This can be done by setting the system parameter javax.net.ssl.trustStore to the location of the keystore. For example javax.net.ssl.trustStore=truststore.jks
Problem was caused by certificate. When deploying to another server you should import certificate from that server into current machine (when usins self-signed certificates)
I'm trying to request data from a web service which requires a client certificate to be presented by a client. The server uses SSL for all communications, and uses a self-signed certificate. I gave Netbeans the service's WSDL file and it generated client code with wsimport.
I have no issues when my client code is written in a regular Java application; I set the trust store to the cacerts file containing the server's certificate, set the key store to be a file provided by the server admin in JKS format containing 2 keys - a client private key and the server's public key, build the request object, and send the request.
The problem comes when I move it to an enterprise Java environment. Requirements dictate that the code must be an Enterprise JavaBean inside an Enterprise Archive running on the Glassfish application server. It appears that Glassfish has its own security settings which override the JVM's settings. When the EJB method containing the web service call runs, the SSL negotiation fails: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target. I don't know how to set Glassfish's security settings up like my JVM's settings, can anyone explain Glassfish's security settings? The research I have done has only shown how to set up Glassfish as a web service server, not as a web service client.
I have a .cer certificate file for the server which I added it to my trust store by using Java's keytool to add it to the default cacerts file.
Would it be better to modify cacerts file with InstallCert to include the self-signed certificate, following the steps at http://blog.johnryding.com/post/1548502059/acquire-an-ssl-certificate-for-your-java-programs-in-win?
I have the trust store file, key store file, along with a .cer certificate file and a .p12 browser certificate, stored in $JAVA_HOME/jre/lib/security and $JAVA_HOME/lib/security.
I'm using Netbeans 6.9.1 and Glassfish 3.1 Final. The relevant piece of code is below, copied from my EJB. The exception occurs at the last line.
System.setProperty("javax.net.ssl.trustStore", "C:\\jssecacerts");
System.setProperty("javax.net.ssl.trustStorePassword", "changeit");
System.setProperty("javax.net.ssl.keyStore", "C:\\userCertificate.jks");
System.setProperty("javax.net.ssl.keyStorePassword", "password");
RequestObject request = new RequestObject;
request.setQuery("some data");
request.setUsername("user");
request.setPassword("pass");
Service service = new Service();
Endpoint port = service.getWebServicePort();
Result result = port.specificWebServiceMethod(request);
I faced the same exception that Jacques Pritchard described above:
javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
I solved it importing the root certificate in both cacerts.jks and keystore.jks, using the following commands:
/usr/java/jdk1.6.0_25/bin/keytool -import -trustcacerts -file root_ca.cer -alias rootca -keystore cacerts.jks
/usr/java/jdk1.6.0_25/bin/keytool -import -trustcacerts -file root_ca.cer -alias rootca -keystore keystore.jks
It's important to say that the alias rootca is a name I defined myself to label the certificate. You can choose any name also.
Instead of using the global system properties, you should create a separate SSLContext for your client. Whether or not it runs within a Glassfish server doesn't really matter, then.
Here is a question that should be relevant (about client-certificates for WS): Choosing SSL client certificate in Java
I had this exact problem (with Glassfish 3.0.1).
Here are the exact steps we took to resolve this.
a. Use java keytool command to view the keystore to see what's in it. This is helpful later on to see if there are any changes. The command goes something like
keytool -list -keystore MyKeyStore.jks
b. Convert the pfx to a pem using openssl. Note that I used the correct password for the input pfx and the same password as my java keystore for the pem file output.
openssl pkcs12 -in MyPfxFile.pfx -out MyPemFile.pem
Convert the pem file to a p12 which can easily be imported into a java keystore. Note that I used the same password from my java keystore as I did on the input and output files.
openssl pkcs12 -export -in MyPemFile.pem -out MyP12File.p12
Now I finally import the p12 into my java keystore. Note that I used java 6, java 5 keytool doesn't have support for the -importkeystore argument.
keytool -importkeystore -deststorepass MyPassword -destkeystore PathToMyKeystore/keystore.jks -srckeystore MyP12File.p12 -srcstoretype PKCS12 -srcstorepass MyPassword
You can list the keystore contents here, something like this keytool -list -keystore keystore.jks just to ensure that your new key was imported correctly.
If you're lucky like I am you'll find that starting up your app server at this point will be of no use. You'll see errors like something about pkix path or something about HTTP 403 Forbidden.
The steps used above worked perfectly for Sun Application Server 9.1_1 but not for Oracle Glassfish 3.0.1. I'm thinking this has something to do with the version of JSSE used in ogs 3 compared to Sun App Server or jdk versions. Adding the jvm option below to your ogs 3 domain.xml file should resolve the issue if simply adding the client cert to the keystore didn't.
<jvm-options>-Djava.protocol.handler.pkgs=com.sun.net.ssl.internal.www.protocol</jvm-options>
I did notice that someone said to not use the jvm options statement above but it's part of the fix, don't use it and see if it works, I'll bet it won't. Maybe just changing the handlers is why it works?
Here's where I found the details: http://onjava.com/pub/a/onjava/2001/05/03/java_security.html?page=4
I also stumbled across a final issue (only for ogs 3), if you get fails every now and again I'd suggest finding the InstallCert app (its out there) and give it the following command line parameters:
I was getting these PKIX errors every third attempt at calling the web service.
Hopefully this helped someone else out. These kind of issues really make me want to rip my hair out :)
I finally got it.
Removed all the certs from my keytool.
Command Example : keytool -list -v -keystore keystore.jks -alias mydomain
I converted the cert response from server to bas64 DER and copied them into one file a .PEM, and I uploaded the .PEM into my keytool:
Command Example : keytool -importcert -keystore keystore.jks -alias mydomain -file my.pem
Then I loaded the keystore:
KeyStore myStore = KeyStore.getInstance("JKS");
InputStream keyInputx = new FileInputStream("C:\\myStore.jks");
myStore.load(keyInputx, "xxx".toCharArray());
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
keyInputx.close();
/*Enumeration enumeration = myStore.aliases();
while (enumeration.hasMoreElements()) {
String alias = (String) enumeration.nextElement();
System.out.println("alias name: " + alias);
Certificate certificate = myStore.getCertificate(alias);
System.out.println(certificate.toString());
}*/
keyManagerFactory.init(myStore, "xxx".toCharArray());
SSLContext context = SSLContext.getInstance("TLS");
context.init(keyManagerFactory.getKeyManagers(), null, new SecureRandom());
SSLSocketFactory sockFact = context.getSocketFactory();
Lots of references around so be happy to use.