Java HTTPS client certificate authentication - java

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).

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
The client's public certificate (in this instance signed by a self-signed CA)
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
Client certificate authentication can only be enforced by the server.
(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.
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.
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

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

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

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

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.

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.

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:
Save code below to InstallCert.java
Open command line and execute: javac InstallCert.java
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);
}
}
}

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):
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
Receive 2 certificate: my client root certificate user.pem and bank root certificate: bank.crt
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.
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
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;
}

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.

Related

Java SSL-Server bad certificate Error when trying to use SSL-Connection [duplicate]

I am trying to setup a SSL Socket connection (and am doing the following on the client)
I generate a Certificte Signing Request to obtain a signed client certificate
Now I have a private key (used during the CSR), a signed client certificate and root certificate (obtained out of band).
I add the private key and signed client certificate to a cert chain and add that to the key manager. and the root cert to the trust manager.
But I get a bad certificate error.
I am pretty sure I am using the right certs. Should I add the signed client cert to the trust manager as well? Tried that, no luck still.
//I add the private key and the client cert to KeyStore ks
FileInputStream certificateStream = new FileInputStream(clientCertFile);
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
java.security.cert.Certificate[] chain = {};
chain = certificateFactory.generateCertificates(certificateStream).toArray(chain);
certificateStream.close();
String privateKeyEntryPassword = "123";
ks.setEntry("abc", new KeyStore.PrivateKeyEntry(privateKey, chain),
new KeyStore.PasswordProtection(privateKeyEntryPassword.toCharArray()));
//Add the root certificate to keystore jks
FileInputStream is = new FileInputStream(new File(filename));
CertificateFactory cf = CertificateFactory.getInstance("X.509");
java.security.cert.X509Certificate cert = (X509Certificate) cf.generateCertificate(is);
System.out.println("Certificate Information: ");
System.out.println(cert.getSubjectDN().toString());
jks.setCertificateEntry(cert.getSubjectDN().toString(), cert);
//Initialize the keymanager and trustmanager and add them to the SSL context
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ks, "123".toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(jks);
Is there some sort of certificate chain that I need to create here?
I had a p12 with these components as well and upon using pretty similar code, adding the private key to the keymanager and the root cert from p12 to the trust manager I could make it work. But now I need to make it work without the p12.
EDIT: Stack trace was requested. Hope this should suffice. (NOTE: I masked the filenames)
Caused by: javax.net.ssl.SSLHandshakeException: Received fatal alert: bad_certificate
at com.sun.net.ssl.internal.ssl.Alerts.getSSLException(Alerts.java:174)
at com.sun.net.ssl.internal.ssl.Alerts.getSSLException(Alerts.java:136)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.recvAlert(SSLSocketImpl.java:1720)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:954)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1138)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1165)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1149)
at client.abc2.openSocketConnection(abc2.java:33)
at client.abc1.runClient(abc1.java:63)
at screens.app.abc.validateLogin(abc.java:197)
... 32 more
You need to add the root cert to the keystore as well.
I got this error when I removed these 2 lines. If you know your keystore has the right certs, make sure your code is looking at the right keystore.
System.setProperty("javax.net.ssl.keyStore", <keystorePath>));
System.setProperty("javax.net.ssl.keyStorePassword",<keystorePassword>));
I also needed this VM argument:
-Djavax.net.ssl.trustStore=/app/certs/keystore.jk
See here for more details:
https://stackoverflow.com/a/34311797/1308453
Provided that the server certificate is signed and valid, you only need to open the connection as usual:
import java.net.*;
import java.io.*;
public class URLConnectionReader {
public static void main(String[] args) throws Exception {
URL google = new URL("https://www.google.com/");
URLConnection yc = google.openConnection();
BufferedReader in = new BufferedReader(new InputStreamReader(
yc.getInputStream()));
String inputLine;
while ((inputLine = in.readLine()) != null)
System.out.println(inputLine);
in.close();
}
}
Note that the URL has the HTTPS schema to indicate the use of SSL.
If the server's certificate is signed but you are accessing using a different IP address/domain name than the one in the certificate, you can bypass hostname verification with this:
HostnameVerifier hv = new HostnameVerifier() {
public boolean verify(String urlHostName,SSLSession session) {
return true;
}
};
HttpsURLConnection.setDefaultHostnameVerifier(hv);
If the certificate is not signed then you need to add it to the keystore used by the JVM (useful commands).

CertPathBuilderException using netty to communicate with server - loading cert in truststore issue?

I've setup syslog server using logstash, and secured it with ssl as described in this excellent document: http://www.logstashbook.com/TheLogstashBook_sample.pdf
On the server I created a key and cert as follows:
openssl genrsa -out server.key 2048
openssl req -new -key server.key -batch -out server.csr
openssl x509 -req -days 3650 -in server.csr -signkey server.key -out server.crt
I copied server.crt to my client system, and am trying to use it to send a syslog message to my server, but it's failing with:
Caused by: java.security.cert.CertPathBuilderException: unable to find
valid certification path to requested target at
com.ibm.security.cert.PKIXCertPathBuilderImpl.buildCertPath(PKIXCertPathBuilderImpl.java:642)
at
com.ibm.security.cert.PKIXCertPathBuilderImpl.engineBuild(PKIXCertPathBuilderImpl.java:356)
at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:256)
at com.ibm.jsse2.util.h.a(h.java:37)
Below is my code:
EventLoopGroup group = null;
Bootstrap bootstrap = null;
Channel channel = null;
group = new NioEventLoopGroup();
bootstrap = new Bootstrap();
bootstrap.group(group);
SSLContext s=SSLContext.getInstance("TLS");
s.init(null, null,null);
String[] suites = s.getSocketFactory().getSupportedCipherSuites();
List<String> ciphers = new ArrayList<String>();
for (int i = 0; i < suites.length; i++) {
ciphers.add(suites[i]);
}
SslContextBuilder ctxBuilder = SslContextBuilder.forClient();
ctxBuilder.ciphers(ciphers);
// get cert
FileInputStream ksfis = new FileInputStream("server.crt");
BufferedInputStream ksbufin = new BufferedInputStream(ksfis);
X509Certificate certificate = (X509Certificate)
CertificateFactory.getInstance("X.509").generateCertificate(ksbufin);
// add cert to keystore
KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
keystore.load(null, "password".toCharArray());
keystore.setCertificateEntry("alias", certificate);
System.setProperty("javax.net.ssl.trustStore", "server.crt");
ctxBuilder.trustManager(certificate);
SslContext sslCtx = ctxBuilder.build();
bootstrap.channel(NioSocketChannel.class)
.option(ChannelOption.SO_KEEPALIVE, true)
.handler(new TcpSyslogEventEncoder());
try {
ChannelFuture future = bootstrap.connect(new InetSocketAddress(hostname, 5000));
channel = future.syncUninterruptibly().channel();
channel.pipeline().addLast("ssl", sslCtx.newHandler(channel.alloc(), hostname, 5000));
}
catch (Exception e) {
System.out.println("Unable to connect to host. Cause is " + e.toString());
}
SyslogEvent event = new SyslogEvent("Dec 23 12:11:43 louis postfix/smtpd[31499]: da a tu cuerpo alegria macarena[95.75.93.154]");
channel.writeAndFlush(event);
System.out.println("Got to end");

Restlet javax.net.ssl.SSLHandshakeException: null cert chain

I am testing SSL communication between client and server locally.
So I generated certificate using OpenSSL commands. Added this certificate in cacert file. Also generated .p12 file.
I am using the same .p12 file in server and client.
This is the server code
Server server = component.getServers().add(Protocol.HTTPS, port);
Series<Parameter> params = server.getContext().getParameters();
params.add("keystorePath", ".p12 file path");
params.add("keystoreType", "PKCS12");
params.add("needClientAuthentication","true");
component.getDefaultHost().attach("", "/AA"), new AAClass());
component.start();
And this is client code:
Client client = trustAllCerts();
clientResource = new ClientResource(url);
clientResource.setNext(client);
try{
clientText = clientResource.post"");
}
catch(ResourceException e){
e.printStackTrace();
}
public Client trustAllCerts() {
Client client = null;
try {
client = new Client(new Context(), Protocol.HTTPS);
Context context = client.getContext();
final SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
context.getAttributes().put("sslContextFactory", new SslContextFactory() {
public void init(Series<Parameter> parameters) {
}
public SSLContext createSslContext() {
return sslContext;
}
});
TrustManager tm = new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public X509Certificate[] getAcceptedIssuers() {
return null;
}
};
context.getAttributes().put("hostnameVerifier", new HostnameVerifier() {
#Override
public boolean verify(String arg0, SSLSession arg1) {
return true;
}
});
sslContext.init(null, new TrustManager[] { tm }, null);
} catch (KeyManagementException e) {
LOGGER.error("Exception in Key Management" + e);
} catch (NoSuchAlgorithmException e) {
LOGGER.error("Exception in Algorithm Used" + e);
}
return client;
}
I am getting following exception:
Restlet-1299242, fatal error: 42: null cert chain
javax.net.ssl.SSLHandshakeException: null cert chain
%% Invalidated: [Session-25, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256]
Restlet-1299242, SEND TLSv1.2 ALERT: fatal, description = bad_certificate
Restlet-1299242, WRITE: TLSv1.2 Alert, length = 2
Restlet-1299242, fatal: engine already closed. Rethrowing javax.net.ssl.SSLHandshakeException: null cert chain
Restlet-1299242, called closeInbound()
Restlet-1299242, fatal: engine already closed. Rethrowing javax.net.ssl.SSLException: Inbound closed before receiving peer's close_notify: possible truncation attack?
Restlet-1299242, called closeOutbound()
Restlet-1299242, closeOutboundInternal()
I tried to add keystore and truststore using System.setProperty() but it didn't work.
Please help. Thanks in advance.
First, lets create a JKS formatted keystore. PKCS12 is usually used in browser and on default java applications uses JKS (as far as I know). Java also supports PKCS12 but I do not know exact parameters for it.
Preparing JKS File
Lets look in our PKCS12 file and get the certificate aliases that we want to extract our JKS file.
keytool -list \
-keystore [*.p12 file] \
-storepass [password] \
-storetype PKCS12 \
-v
Note the aliases you want to export. And now lets create a JKS file.
keytool -keystore [*.jks file path] -genkey -alias client
This will ask bunch of questions. You can fill them as you like. Now, you can export your aliases from *.p12 file to *.jks file.
keytool -importkeystore \
-srckeystore [*.p12 file path] \
-srcstoretype pkcs12 \
-srcalias [alias from first command] \
-destkeystore [*.jks file path] \
-deststoretype jks \
-deststorepass [*.jks file password] \
-destalias [new alias]
If you do not have any PKCS12 file, or your certificates are in CER, DER or PEM format you can add your certificates to your keystore using the command below.
keytool -import \
-alias [new alias] \
-keystore [*.jks file path] \
-file [*.DER file path]
And please be sure that you imported, your certificate, your certificate provider's certificate (intermediate certificate) and root certificate.
Now you can check that your JKS file contains all the certificates you are needed.
keytool -list \
-keystore [*.jks file path] \
-storepass [password] \
-storetype jks \
-v
Setting up Server
You can use your JKS file both on client and server side. According to Restlet documentation you can use JKS file like this to provide HTTPS connection.
Server server = component.getServers().add(Protocol.HTTPS, port);
Series<Parameter> parameters = server.getContext().getParameters();
parameters.add("sslContextFactory","org.restlet.engine.ssl.DefaultSslContextFactory");
parameters.add("keyStorePath", "*.jks file");
parameters.add("keyStorePassword", "password");
parameters.add("keyPassword", "password");
parameters.add("keyStoreType", "JKS");
After that if you check your port from browser you must see a secure sign. Or you can use some online tool(like this one) to check your certificate.
Setting up Client
Now lets look at client side. Since you are developing both side of the application you can use already created JKS file.
Context con = new Context();
Series<Parameter> clParameters = con.getParameters();
clParameters.add("truststorePath", "*.jks file");
clParameters.add("truststorePassword", "password");
clParameters.add("truststoreType", "JKS");
Client restletClient = new Client(con, Protocol.HTTPS);
While testing or in other circumstances, your certificate hostname and your actual hostname may not match. In order to disable hostname checks you can add this block to your application.
static{
javax.net.ssl.HttpsURLConnection.setDefaultHostnameVerifier(
new javax.net.ssl.HostnameVerifier(){
public boolean verify(String hostname,
javax.net.ssl.SSLSession sslSession ) {
return true ;
}
});
}
Some Thoughts
Since I cannot test it on my locale, I am not exactly sure that your client and server JKS file must be the same. You may only need to add your own certificate to your server.jks. SSL and certificates are always tricky for me. I usually get it work after some trial and error. I hope this will help you.
Also, You may also want to consider, using a reverse proxy kind of web server like Apache2 or Nginx. If you want to use them, you must merge your certificates to a single file. If you look at your certificate file you see that each file (your own certificate, intermediate certificate and root certificate) is like this
-----BEGIN CERTIFICATE-----
MIIDfTCCAuagAwIBAgIDErvmMA0GCSqGSIb3DQEBBQUA...
....
-----END CERTIFICATE-----
You need to simply add one to other to create a merged certificate. And than use that certificate to end SSL on Apache2 or Nginx. This is what I usually do. But on client side you still need to create JKS files.
I am using the same .p12 file in server and client
This is already a mistake. The client and the server are different identities and should not have the same private key, public key, or certificate.
I suggest you ditch all the OpenSSL stuff and start again with the keytool as follows:
At the server, generate a keypair, and a certificate request; get it signed; import the signer's certificate chain with the -trustcacerts option; and import the signed certificate using the same alias you used when creating the keypair and CSR.
At the client, ditto, but using (of course) a different keystore file.
You're done. Forget about
OpenSSL
PKCS#12
self-signed certificates
all forms of trustAllCerts, custom TrustManagers, and custom code of any kind whatsoever
using the same keypair/certificate for the server and client
importing the server certificate to the client, and vice versa
any system properties other than those that identify the javax.net.ssl.keyStore and javax.net.ssl.keyStorePassword
setting a password on the keypair or the imported signed certificate.
Steps (1) and (2) are how it is intended to be done. Depart from those and you are in for trouble and strife.
One option is to read the p12/pfx-file, get the certificates and use them to programmatically construct KeyStores and TrustStores.
If the input is one pfx-file containing a CA root certificate and a related client certificate,
the methods shown in the class SslUtils below will let you do that.
There is one caveat though: the default Restlet server (version 2.3.4) will not pickup the certificates send by the client.
I did manage to work-around this issue (it is not pretty though), see my answer on this question.
I will focus on configuring the secure connections here, but all source code and a working example is available in the
restlet-clientcert Github project.
The Github project is a result of me thinking I know what I'm doing, having no luck and no experience with Restlet,
but biting the bullet anyway so I can feel a little bit better knowing that I could get this basic stuff to work.
On the server side, use a custom ServerSslContextFactory that programmatically configures the used SSLContext.
Register the custom factory with:
ServerSslContextFactory sslCtx = new ServerSslContextFactory();
sslCtx.init(certFileName, certFilePwd);
ConcurrentMap<String, Object> attribs = server.getContext().getAttributes();
attribs.put("sslContextFactory", sslCtx);
and attach a "guard" to extract the client certificate info:
CertificateAuthenticator guard = new CertificateAuthenticator(server.getContext());
guard.setNext(MyRestlet.class);
component.getDefaultHost().attachDefault(guard);
The ServerSslContextFactory:
public class ServerSslContextFactory extends DefaultSslContextFactory {
private static final Logger log = LoggerFactory.getLogger(ServerSslContextFactory.class);
protected DefaultSslContext wrappedCtx;
public void init(String certFileName, char[] certFilePwd) throws Exception {
if (log.isDebugEnabled()) {
log.debug("Loading certificates from [" + certFileName + "] and using "
+ (certFilePwd != null && certFilePwd.length > 0 ? "a" : "no") + " password.");
}
Path certFilePath = Paths.get(Thread.currentThread().getContextClassLoader().getResource(certFileName).toURI());
KeyManagerFactory kmf = SslUtils.loadKeyStore(certFilePath, certFilePwd);
KeyManager[] kms = kmf.getKeyManagers();
List<X509Certificate> certs = SslUtils.getClientCaCerts(kms);
TrustManagerFactory tmf = SslUtils.createTrustStore(Constants.CERT_CA_ALIAS, certs.get(0));
TrustManager[] tms = tmf.getTrustManagers();
super.setNeedClientAuthentication(true);
SSLContext ctx = SSLContext.getInstance(SslUtils.DEFAULT_SSL_PROTOCOL);
ctx.init(kms, tms, null);
wrappedCtx = (DefaultSslContext) createWrapper(ctx);
}
#Override
public void init(Series<Parameter> parameters) {
log.debug("Not using parameters to initialize server SSL Context factory.");
}
#Override
public SSLContext createSslContext() throws Exception {
return wrappedCtx;
}
#Override
public boolean isNeedClientAuthentication() {
if (log.isDebugEnabled()) {
//log.debug("Needing client auth: " + super.isNeedClientAuthentication(), new RuntimeException("trace"));
log.debug("Needing client auth: " + super.isNeedClientAuthentication());
}
return super.isNeedClientAuthentication();
}
}
On the client side, a similar thing:
ClientSslContextFactory sslCtx = new ClientSslContextFactory();
sslCtx.init(certFileName, certFilePwd);
attribs.put("sslContextFactory", sslCtx);
Also set a hostnameVerifier (as shown in your question) to not verify hostnames.
The ClientSslContextFactory:
public class ClientSslContextFactory extends SslContextFactory {
private static final Logger log = LoggerFactory.getLogger(ClientSslContextFactory.class);
protected KeyManager[] kms;
protected TrustManager[] tms;
public void init(String certFileName, char[] certFilePwd) throws Exception {
log.debug("Loading certificates from [" + certFileName + "] and using "
+ (certFilePwd != null && certFilePwd.length > 0 ? "a" : "no") + " password.");
Path certFilePath = Paths.get(Thread.currentThread().getContextClassLoader().getResource(certFileName).toURI());
KeyManagerFactory kmf = SslUtils.loadKeyStore(certFilePath, certFilePwd);
kms = kmf.getKeyManagers();
/*
List<X509Certificate> certs = SslUtils.getClientCaCerts(kms);
TrustManagerFactory tmf = SslUtils.createTrustStore(Constants.CERT_CA_ALIAS, certs.get(0));
tms = tmf.getTrustManagers();
*/
tms = new TrustManager[1];
tms[0] = new TrustServerCertAlways();
}
#Override
public void init(Series<Parameter> parameters) {
log.debug("Not using parameters to initialize client SSL Context factory.");
}
#Override
public SSLContext createSslContext() throws Exception {
SSLContext ctx = SSLContext.getInstance(SslUtils.DEFAULT_SSL_PROTOCOL);
ctx.init(kms, tms, null);
return ctx;
}
static class TrustServerCertAlways implements X509TrustManager {
#Override public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
log.debug("Trusting all client certificates.");
}
#Override public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
log.debug("Trusting all server certificates.");
}
#Override public X509Certificate[] getAcceptedIssuers() {
log.debug("No accepted issuers.");
return null;
}
}
}
And finally the SslUtils class containing the "read and reconstruct" methods
(full version including "get email-address from certificate" methods is available in the previously mentioned Github project):
import java.io.InputStream;
import java.net.Authenticator;
import java.net.PasswordAuthentication;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.KeyStore;
import java.security.KeyStore.LoadStoreParameter;
import java.security.cert.X509Certificate;
import java.util.*;
import javax.net.ssl.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SslUtils {
private static final Logger log = LoggerFactory.getLogger(SslUtils.class);
/**
* List of SSL protocols (SSLv3, TLSv1.2, etc.). See also {#link SslUtils#DEFAULT_SSL_PROTOCOL}.
* <br>Documented at http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#SSLContext
*/
public static final String[] SSL_PROTOCOLS = new String[] { "SSL", "SSLv2", "SSLv3", "TLS", "TLSv1", "TLSv1.1", "TLSv1.2" };
/**
* Default SSL protocol to use ("TLSv1.2").
*/
public static final String DEFAULT_SSL_PROTOCOL = "TLSv1.2";
/**
* Creates a default SSL context with an empty key-store and the default JRE trust-store.
*/
public static SSLContext createDefaultSslContext() throws Exception {
return createSslContext(null, null, null, null);
}
/**
* Creates a default SSL socket factory.
* <br>All system properties related to trust/key-stores are ignored, eveything is done programmatically.
* This is because the Sun implementation reads the system-properties once and then caches the values.
* Among other things, this fails the unit tests.
* <br>For reference, the system properties (again, NOT USED):
* <br> - javax.net.ssl.trustStore (default cacerts.jks)
* <br> - javax.net.ssl.trustStorePassword
* <br>and for client certificate:
* <br> - javax.net.ssl.keyStore (set to "agent-cert.p12")
* <br> - javax.net.ssl.keyStoreType (set to "pkcs12")
* <br> - javax.net.ssl.keyStorePassword
* <br>See for a discussion:
* https://stackoverflow.com/questions/6340918/trust-store-vs-key-store-creating-with-keytool
* <br>See for client certificates in Java:
* https://stackoverflow.com/questions/1666052/java-https-client-certificate-authentication
* #param keyStoreFileName The name (ending with pfx) of the file with client certificates.
* #param trustStoreFileName The name (ending with jks) of the Java KeyStore with trusted (root) certificates.
* #return null or the SSLContext.
*/
public static SSLContext createSslContext(Path keyStoreFile, String keyStorePwd,
Path trustStoreFile, String trustStorePwd) throws Exception {
return createSslContext(keyStoreFile, keyStorePwd, trustStoreFile, trustStorePwd, DEFAULT_SSL_PROTOCOL);
}
/**
* See {#link #createSslContext(Path, String, Path, String)}.
* #param sslProtocol a value from {#link #SSL_PROTOCOLS}.
*/
public static SSLContext createSslContext(Path keyStoreFile, String keyStorePwd,
Path trustStoreFile, String trustStorePwd, String sslProtocol) throws Exception {
KeyManagerFactory kmf = loadKeyStore(keyStoreFile, keyStorePwd == null ? null : keyStorePwd.toCharArray());
TrustManagerFactory tmf = loadTrustStore(trustStoreFile, trustStorePwd == null ? null : trustStorePwd.toCharArray());
//set an Authenticator to generate username and password
SSLContext ctx = SSLContext.getInstance(sslProtocol);
ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
return ctx;
}
/**
* Calls {#link #createSslContextFromClientKeyStore(Path, String, Path, String)} with the {#link #DEFAULT_SSL_PROTOCOL}.
*/
public static SSLContext createSslContextFromClientKeyStore(Path keyStoreFile, String keyStorePwd,
String caAlias) throws Exception {
return createSslContextFromClientKeyStore(keyStoreFile, keyStorePwd, caAlias, DEFAULT_SSL_PROTOCOL);
}
/**
* Creates a SSL context from the given key-store containing a client certificate and a (CA) root certificate.
* The root certificate is set in the trust-store of the SSL context.
* #param keyStoreFileName key-store file name (ending with .pfx).
* #param keyStorePwd key-store password
* #param caAlias the alias to use for the CA (root) certificate (e.g. "mycaroot").
* #param sslProtocol the ssl-protocol (e.g. {#link #DEFAULT_SSL_PROTOCOL}).
*/
public static SSLContext createSslContextFromClientKeyStore(Path keyStoreFile, String keyStorePwd,
String caAlias, String sslProtocol) throws Exception {
KeyManagerFactory kmf = loadKeyStore(keyStoreFile, keyStorePwd == null ? null : keyStorePwd.toCharArray());
List<X509Certificate> certs = getClientCaCerts(kmf.getKeyManagers());
if (certs.size() < 1) {
throw new Exception("Cannot find CA (root) certificate in key-managers from key store " + keyStoreFile.getFileName());
}
TrustManagerFactory tmf = createTrustStore(caAlias, certs.get(0));
SSLContext ctx = SSLContext.getInstance(sslProtocol);
ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
return ctx;
}
public static KeyManagerFactory loadKeyStore(Path storeFile) throws Exception {
return loadKeyStore(storeFile, null);
}
public static KeyManagerFactory loadKeyStore(Path storeFile, char[] storePwd) throws Exception {
return loadKeyStore(storeFile, storePwd, null, null);
}
public static KeyManagerFactory loadKeyStore(Path storeFile, char[] storePwd,
String storeType, String algorithm) throws Exception {
KeyManagerFactory kmf = null;
if (storeFile == null) {
kmf = loadKeyStore((InputStream)null, storePwd, storeType, algorithm);
} else {
try (InputStream storeIn = Files.newInputStream(storeFile)) {
kmf = loadKeyStore(storeIn, storePwd, storeType, algorithm);
log.info("Initialized certificate key-store from [" + storeFile.getFileName() + "]");
}
}
return kmf;
}
public static KeyManagerFactory loadKeyStore(InputStream storeIn, char[] storePwd,
String storeType, String algorithm) throws Exception {
if (storePwd == null && storeIn != null) {
storePwd = "changeit".toCharArray();
log.debug("Using default key store password.");
}
if (storeType == null) {
storeType = "pkcs12";
log.debug("Using default key store type " + storeType);
}
if (algorithm == null) {
algorithm = KeyManagerFactory.getDefaultAlgorithm(); // "SunX509"
log.debug("Using default key store algorithm " + algorithm);
}
KeyManagerFactory kmf = null;
KeyStore keyStore = loadStore(storeIn, storePwd, storeType);
kmf = KeyManagerFactory.getInstance(algorithm);
kmf.init(keyStore, storePwd);
if (storeIn == null) {
log.info("Initialized a default certificate key-store");
}
return kmf;
}
/**
* Creates a trust-store with the given CA (root) certificate.
* #param certAlias the alias for the certificate (e.g. "mycaroot")
* #param caCert the CA (root) certificate
* #return an initialized trust manager factory.
*/
public static TrustManagerFactory createTrustStore(String certAlias, X509Certificate caCert) throws Exception {
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load((LoadStoreParameter)null); // must initialize the key-store
ks.setCertificateEntry(certAlias, caCert);
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ks);
return tmf;
}
public static TrustManagerFactory loadTrustStore(Path storeFile) throws Exception {
return loadTrustStore(storeFile, null);
}
public static TrustManagerFactory loadTrustStore(Path storeFile, char[] storePwd) throws Exception {
return loadTrustStore(storeFile, storePwd, null, null);
}
public static TrustManagerFactory loadTrustStore(Path storeFile, char[] storePwd,
String storeType, String algorithm) throws Exception {
TrustManagerFactory tmf = null;
if (storeFile == null) {
tmf = loadTrustStore((InputStream)null, storePwd, storeType, algorithm);
} else {
try (InputStream storeIn = Files.newInputStream(storeFile)) {
tmf = loadTrustStore(storeIn, storePwd, storeType, algorithm);
}
log.info("Initialized certificate trust-store from [" + storeFile.getFileName() + "]");
}
return tmf;
}
public static TrustManagerFactory loadTrustStore(InputStream storeIn, char[] storePwd,
String storeType, String algorithm) throws Exception {
if (storePwd == null && storeIn != null) {
storePwd = "changeit".toCharArray();
log.debug("Using default trust store password.");
}
if (storeType == null) {
storeType = KeyStore.getDefaultType();
log.debug("Using default trust store type " + storeType);
}
if (algorithm == null) {
algorithm = TrustManagerFactory.getDefaultAlgorithm();
log.debug("Using default trust store algorithm " + algorithm);
}
TrustManagerFactory tmf = null;
KeyStore trustStore = loadStore(storeIn, storePwd, storeType);
tmf = TrustManagerFactory.getInstance(algorithm);
tmf.init(trustStore);
if (storeIn == null) {
log.info("Initialized a default certificate trust-store");
}
return tmf;
}
/**
* Creates a default trust store containing the JRE certificates in {#code JAVA_HOME\lib\security\cacerts.jks}
* <br>To view loaded certificates call
* <br>{#code System.setProperty("javax.net.debug", "ssl,trustmanager");}
* <br>before calling this method.
*/
public static TrustManagerFactory createDefaultTrustStore() throws Exception {
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init((KeyStore)null);
return tmf;
}
/**
* #param in if null, null is returned.
*/
public static KeyStore loadStore(InputStream in, char[] pwd, String type) throws Exception {
if (in == null) {
return null;
}
KeyStore ks = KeyStore.getInstance(type);
ks.load(in, pwd);
return ks;
}
/**
* Finds any CA (root) certificates present in client certificate chains.
* <br>Uses {#link #getClientAliases(KeyManager)}
* #param kms key-managers (from a key-store).
* #return an empty list or a list containing CA (root) certificates.
*/
public static List<X509Certificate> getClientCaCerts(KeyManager[] kms) {
List<X509Certificate> caCerts = new LinkedList<X509Certificate>();
for (int i = 0; i < kms.length; i++) {
if (!(kms[i] instanceof X509KeyManager)) {
continue;
}
X509KeyManager km = (X509KeyManager) kms[i];
List<String> aliases = getClientAliases(km);
for (String alias: aliases) {
X509Certificate[] cchain = km.getCertificateChain(alias);
if (cchain == null || cchain.length < 2) {
continue;
}
// first certificate in chain is the user certificate
// last certificate is the CA (root certificate).
caCerts.add(cchain[cchain.length-1]);
if (log.isDebugEnabled()) {
log.debug("Found 1 root certificate from client certificate alias " + alias);
}
}
}
return caCerts;
}
/**
* List of key types for client certificate aliases, used in {#link #getAliases(KeyManager)}
* <br>List is documented at
* http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#jssenames
*/
public static final String[] KEY_TYPES = new String[] {"RSA", "DSA", "DH_RSA", "DH_DSA", "EC", "EC_EC", "EC_RSA" };
/**
* Searches for client aliases in the given key-manager.
* Does nothing when the given key-manager is not an instance of {#link X509KeyManager}.
* #return an empty list or a list containing client aliases found in the key-manager.
*/
public static List<String> getClientAliases(KeyManager keyManager) {
List<String> aliases = new LinkedList<String>();
if (keyManager instanceof X509KeyManager) {
X509KeyManager km = (X509KeyManager) keyManager;
for (String keyType: KEY_TYPES) {
String[] kmAliases = km.getClientAliases(keyType, null);
if (kmAliases != null) {
for (String alias: kmAliases) {
if (!isEmpty(alias)) {
aliases.add(alias);
}
}
}
} // for keytypes
}
return aliases;
}
/**
* Sets the default authenticator which can be used for example with http-request that require basic authoriation.
* <br>See also {#link Authenticator#setDefault(Authenticator)}.
*/
public static void setDefaultAuthenticator(final String userName, final char[] pwd) throws Exception {
Authenticator auth = new Authenticator() {
#Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(userName, pwd);
}
};
Authenticator.setDefault(auth);
}
/**
* #return true if s is not null and not empty after trimming, false otherwise.
*/
public static boolean isEmpty(String s) { return (s == null || s.trim().isEmpty()); }
}
On a side-node: Java is transitioning the default keystore type from JKS to PKCS12 (see JEP 229).
You probably didn't add the full certificate chain in your keystore, and just included the keypair itself. In that case, the client just receives the public key, but it cannot validate if that key can be trusted. The certificate chain is there to be able to check if the signatures on the public key match, and lead up to a trusted certificate authority.
See e.g: Adding certificate chain to p12(pfx) certificate
openssl pkcs12 -in certificate.p12 -out clientcert.pem -nodes -clcerts
openssl x509 -in trusted_ca.cer -inform DER -out trusted_ca.pem
openssl x509 -in root_ca.cer -inform DER -out root_ca.pem
cat clientcert.pem trusted_ca.pem root_ca.pem >> clientcertchain.pem
openssl pkcs12 -export -in clientcertchain.pem -out clientcertchain.pfx
You can do it the java way, too, using e.g. portecle: http://portecle.sourceforge.net/import-ca-reply.html, but you also need to combine the certificate chain in one file to import. Just copy-paste all certificates after one another, starting from your own, and ending with the root CA.
This way, the resulting pfx file can be used on the server to return the certificate chain to the client.

Public key verification always returns "Signature does not match"

I am trying to verify the public key of a certificate. The certificate has been imported into a keystore using this command:
keytool -importcert -file cert.cer -keystore kstore.jks -alias mycert -storepass changeit
This is the java code I use to verify the public key:
File keyStore = new File("kstore.jks");
String keyStorePassword = "changeit";
KeyStore ks = null;
try {
ks = KeyStore.getInstance("jks");
ks.load(keyStore.toURI().toURL().openStream(), keyStorePassword.toCharArray());
} catch (Exception e) {
e.printStackTrace();
}
try {
Certificate cert = ks.getCertificate("mycert");
PublicKey pk = cert.getPublicKey();
cert.verify(pk);
//cert.verify(pk, "SunRsaSign");
System.out.println("Keys verified");
} catch (Exception e) {
e.printStackTrace();
}
The exception I get is:
java.security.SignatureException: Signature does not match.
at sun.security.x509.X509CertImpl.verify(X509CertImpl.java:446)
at sun.security.x509.X509CertImpl.verify(X509CertImpl.java:389)
at VerifyEBXMLSignature.runIt3(VerifyEBXMLSignature.java:62)
at VerifyEBXMLSignature.main(VerifyEBXMLSignature.java:41)
The certificate contains a public key and I do not have access to the private key.
Is it at all possible to verify the public key against this certificate that I import into a keystore? The public key comes from the certificate itself, so it should be correct.
What more should I look for with the certificate?
I just got some more iformation about the certificate: It is exported from the private key. Is there anything in that process that may have be done wrong?
You shouldn't be passing in the public key that you extracted from the certificate. You should be passing in the public key of the issuer's certificate to verify the signature.
So, as Robert pointed out in comments, your above code only works if it's a self-signed certificate (the certificate is signed with itself).
The public key verify method internally uses X509 Certificate implementation.
So it can only verify those certificates which are generated as per X509 standards.
For more info Visit http://en.wikipedia.org/wiki/X.509

Java trustmanager behavior on expired certificates

Does java's TrustManager implementation ignore if a certificate has expired?
I tried the following:
- Using keytool and parameter -startdate "1970/01/01 00:00:00" I created a P12 keystore with an expired certificate.
- I exported the certificate:
Keystore type: PKCS12
Keystore provider: SunJSSE
Your keystore contains 1 entry
Alias name: fake
Creation date: 5 ╠ά± 2011
Entry type: PrivateKeyEntry
Certificate chain length: 1
Certificate[1]:
Owner: CN=Malicious, OU=Mal, O=Mal, L=Fake, ST=GR, C=GR
Issuer: CN=Malicious, OU=Mal, O=Mal, L=Fake, ST=GR, C=GR
Serial number: -1c20
Valid from: Thu Jan 01 00:00:00 EET 1970 until: Fri Jan 02 00:00:00 EET 1970
Certificate fingerprints:
MD5: A9:BE:3A:3D:45:24:1B:4F:3C:9B:2E:02:E3:57:86:11
SHA1: 21:9D:E1:04:09:CF:10:58:73:C4:62:3C:46:4C:76:A3:81:56:88:4D
Signature algorithm name: SHA1withRSA
Version: 3
*******************************************
I used this certificate as server certificate for Tomcat.
Then using an apache httpClient I connected to tomcat, but first I added the expired certificate to the client's trust-store (using a TrustManager
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
and loading the expired certificate).
I was expecting the connection to fail.
Instead the connection succeeds.
Using System.setProperty("javax.net.debug", "ssl");
I see:
***
Found trusted certificate:
[
[
Version: V3
Subject: CN=Malicious, OU=Mal, O=Mal, L=Fake, ST=GR, C=GR
Signature Algorithm: SHA1withRSA, OID = 1.2.840.113549.1.1.5
Key: Sun RSA public key, 1024 bits
modulus: 10350555024148635338735220482157687267055139906998169922552357357346372886164908067983097037540922519808845662295379579697361784480052371935565129553860304254832565723373586277732296157572040989796830623403187557540749531267846797324326299709274902019299
public exponent: 65537
Validity: [From: Thu Jan 01 00:00:00 EET 1970,
To: Fri Jan 02 00:00:00 EET 1970]
Issuer: CN=Malicious, OU=Mal, O=Mal, L=Fake, ST=GR, C=GR
SerialNumber: [ -1c20]
]
I see that in TLS handshake the expired certificate is send by Tomcat connector.
But the client (i.e. the TrustManager) does not reject the connection.
Is this the default behavior?
Am I suppose to configure the trustmanager somehow to check for expiration?
UPDATE:
I found that the actual TrustManager used is X509TrustManagerImpl. Here X509TrustManagerImpl says that this class has a minimal logic.May be I am using the wrong TrustManager?
UPDATE2:
From the javadoc X509TrustManager it is not clear if it checks for certificate expiration
void checkServerTrusted(X509Certificate[] chain,String authType)
throws CertificateException
Given the partial or complete
certificate chain provided by the
peer, build a certificate path to a
trusted root and return if it can be
validated and is trusted for server
SSL authentication based on the
authentication type.The authentication
type is the key exchange algorithm
portion of the cipher suites
represented as a String, such as
"RSA", "DHE_DSS". Note: for some
exportable cipher suites, the key
exchange algorithm is determined at
run time during the handshake. For
instance, for
TLS_RSA_EXPORT_WITH_RC4_40_MD5, the
authType should be RSA_EXPORT when an
ephemeral RSA key is used for the key
exchange, and RSA when the key from
the server certificate is used.
Checking is case-sensitive.
Thanks
I've just had a similar issue myself while overriding checkServerTrusted.
Turns out that if you need to check expiration you can call X509Certificate.checkValidity() and it will throw either a CertificateExpiredException or a CertificateNotYetValidException. Both of these extend CertificateException so they can be happily thrown by checkServerTrusted.
To solve your problem you could implement a new X509TrustManager which creates your original instance in its constructor, implements all methods as calls to the original instance, and adds a call to checkValidity for each certificate in certs[] inside checkServerTrusted.
I did not try your example, but I now I regularly have to regenerate my server certificates (for our development server) since their certificates have quite short validity times.
In our case the client does not have the server certificates themselves in the truststore, but only the certificate of our CA (with longer validity), and when the client tries to connect to the server, both sides get a SSLException (which may be wrapped in another exception in your case).
I guess that the trust manager assumes something like "if you give me expired certificates to trust in, I'll do it".
Try our approach instead (it also saves you to update the client each time the server certificate expires).
I believe IBM's JSSE checks for expiry while Sun's does not.
Very old thread but I thought I'd share some code that implements Matt Lyons' suggestion above (I think!). I looked at a lot of questions on this but didn't find any actual code examples. AFAIK, this code is ok for Java 6, 7, 8 (not sure about earlier/later versions).
This is where I create the HTTPS URL connection that checks SSL certs against a local truststore (but also checks for cert expiry)
public HttpURLConnection getHttpsUrlConnection(URL url) {
HttpURLConnection connection = null;
try {
// Create the connection
connection = (HttpsURLConnection) url.openConnection();
((HttpsURLConnection) connection).setSSLSocketFactory(getSslSocketFactoryThatChecksCertsAgainstLocalKeystore());
} catch (IOException e) {
e.printStackTrace();
}
return connection;
}
private SSLSocketFactory getSslSocketFactoryThatChecksCertsAgainstLocalKeystore() {
try {
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
// I'm using a custom keystore here (useful tips about working with keystores here: https://www.baeldung.com/java-keystore)
// I think you can use the default keystore (JRE cacerts file) by passing in null here (cast to KeyStore)
trustManagerFactory.init(getLocalKeystore());
// Get the trust managers (I think there's normally only one)
TrustManager trustManagers[] = trustManagerFactory.getTrustManagers();
// Create an array of MyX509TrustManager objects to extend/replace trustManagers
MyX509TrustManager myTrustManagers[] = new MyX509TrustManager[trustManagers.length];
for (int i = 0; i < myTrustManagers.length; i++) {
if (trustManagers[i] instanceof X509TrustManager) {
// For each trust manager, create a new MyX509TrustManager (which will check for expired certs!)
myTrustManagers[i] = new MyX509TrustManager((X509TrustManager) trustManagers[i]);
}
}
// Create a ssl socket factory using my trust manager(s) that will include expired cert checking
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, myTrustManagers, null);
return sslContext.getSocketFactory();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
This is the class that implements X509TrustManager (and contains a reference to a "real" X509TrustManager which does almost all of the processing).
import javax.net.ssl.X509TrustManager;
import java.security.cert.CertificateException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.X509Certificate;
public class MyX509TrustManager implements X509TrustManager {
private X509TrustManager impl;
public MyX509TrustManager(X509TrustManager impl) {
this.impl = impl;
}
#Override
public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
impl.checkClientTrusted(x509Certificates, s);
}
#Override
public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
impl.checkServerTrusted(x509Certificates, s);
checkCertExpiry(x509Certificates);
}
#Override
public X509Certificate[] getAcceptedIssuers() {
return impl.getAcceptedIssuers();
}
private void checkCertExpiry(X509Certificate[] x509Certificates) throws CertificateException {
long currentTime = System.currentTimeMillis();
for (int i = 0; i < x509Certificates.length; i++) {
if (currentTime > x509Certificates[i].getNotAfter().getTime()) {
throw new CertificateExpiredException("Cert expired on " + x509Certificates[i].getNotAfter());
} else if (currentTime < x509Certificates[i].getNotBefore().getTime()) {
throw new CertificateExpiredException("Cert will not be valid until " + x509Certificates[i].getNotBefore());
}
}
}
}
There is probably a more elegant way to do this. Suggestions welcome!

Categories