I'm trying to make a request to a server with a client certificate authentication with this code:
try {
/*** CA Certificate ***/
CertificateFactory cf = CertificateFactory.getInstance("X.509");
InputStream caInput = getResources().openRawResource(R.raw.caserver);
Certificate ca = cf.generateCertificate(caInput);
System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN());
// Create a KeyStore containing our trusted CAs
String keyStoreType = KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
keyStore.setCertificateEntry("ca", ca);
System.out.println(keyStoreType);
// Create a TrustManager that trusts the CAs in our KeyStore
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(keyStore);
/*** Client Certificate ***/
KeyStore keyStore12 = KeyStore.getInstance("PKCS12");
InputStream certInput12 = getResources().openRawResource(R.raw.p12client);
keyStore12.load(certInput12, "123456key".toCharArray());
// Create a KeyManager that uses our client cert
String algorithm = KeyManagerFactory.getDefaultAlgorithm();
KeyManagerFactory kmf = KeyManagerFactory.getInstance(algorithm);
kmf.init(keyStore12, null);
/*** SSL Connection ***/
// Create an SSLContext that uses our TrustManager and our KeyManager
SSLContext context = SSLContext.getInstance("TLS");
context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
URL url = new URL("https://myurl/test.json");
HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
urlConnection.setSSLSocketFactory(context.getSocketFactory());
System.out.println("Weeeeeeeeeee");
InputStream in = urlConnection.getInputStream(); // this throw exception
}
catch (Exception e) {
e.printStackTrace();
}
I obtain the next exception when the execution reach the last line InputStream in = urlConnection.getInputStream();.
System.err: javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
I have spent lots of hours trying to fix this error but I can't find any information. When I make the same request using a web browser with the client certificate, all is ok.
Any help? Thanks in advance.
Edit
I follow this steps to generate certificates:
> openssl req -config openssl.cnf -new -x509 -extensions v3_ca -days 3650 -keyout private/caserver.key -out certs/caserver.crt
> openssl req -config openssl.cnf -new -nodes -keyout private/client.key -out client.csr -days 1095
> openssl ca -config openssl.cnf -cert certs/caserver.crt -policy policy_anything -out certs/client.crt -infiles csr/client.csr
> openssl pkcs12 -export -clcerts -in certs/client.crt -inkey private/client.key -out p12client.p12
In my code I use caserver.crt and p12client.p12.
I don't know why input stream unable to read certificate from Assets folder. I had the same problem. To overcome , i have put certificate in raw folder and access it through
InputStream caInput = getResources().openRawResource(R.raw.mycertificate);
and worked well !
You appear to be focusing on the client certificate and possible problems there, but I think the error is related to the server certificate.
You have InputStream caInput = getResources().openRawResource(R.raw.caserver); which takes as input, a CA certificate of a CA that can verify that the server certificate is valid (caserver may be a DER file, for example). As your code is saying you want to trust that CA.
So, the problem may be that this file is not a correct certificate for that CA.
Or, it may really be the certificate for that CA. But that CA might not have signed your server certificate directly. Often, there is a chain of trust, where one CA might sign, then that CA is trusted by another CA, and so on, all the way up to a root CA or other CA that you trust.
So, why does the same web site, with same server certificate, work from the browser? Your browser may have a larger set of CAs that it trusts, so is able to authenticate the server. Whereas your android app may not trust one or more intermediate CAs in the chain of trust. Therefore, "Trust anchor for certification path not found."
What can you do about it? See google's guide on what to do with cases of unknown CA or missing intermediate CA, etc.
Related
I am trying to understand how to configure the Web client. What I have is a working curl that I am not able to convert into a valid HTTPS request through (any) Java HTTP client.
The curl is:
curl -s --cert $CERTIFICATE --key $KEY https.url
where $CERTIFICATE is a .crt file containing:
----BEGIN CERTIFICATE----
....
----END CERTIFICATE-----
And the $KEY is a .key file containing:
-----BEGIN RSA PRIVATE KEY-----
...
-----END RSA PRIVATE KEY-----
I want to convert this curl into a valid JAVA request. Currently, I am configuring a Spring WebClient in this way:
private WebClient getWebClient() throws SSLException {
SslContext sslContext = SslContextBuilder.forClient().keyManager(
Paths.get(properties.getCrtFile()).toFile(),
Paths.get(properties.getKeyFile()).toFile(),
properties.getCertKeyPassword()).build();
HttpClient httpClient = HttpClient.create().secure(t -> t.sslContext(sslContext));
return WebClient
.builder()
.clientConnector(new ReactorClientHttpConnector(httpClient)).build();
}
But when I use the webclient to make a request it returns an error:
exception: File does not contain valid private key:
Any idea where is the error?
This is how I solved the problem:
Verify that .cert and .key files are valid:
openssl x509 -noout -modulus -in certFile.crt | openssl md5
#> (stdin)= 7f1a9c4d13aead7fd4a0f241a6ce8
and
openssl rsa -noout -modulus -in certKey.key | openssl md5
#> (stdin)= 7f1a9c4d13aead7fd4a0f241a6ce8
Convert my .cert and .key files into a PCKS12 that Java can understand. (Keep in mind that my cert and key files are in PEM format as explained in the question). I used the following command:
openssl pkcs12 -export -in certFile.crt -inkey keyFile.key -out cert.p12
This step will prompt you to enter a password. We will use this password when reading the certificate into a KeyStore.
Create an SSLContext by reading the certificate:
private SslContext getSSLContext() {
try (FileInputStream keyStoreFileInputStream = new
FileInputStream("pathTop12CertificateFile")) {
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(keyStoreFileInputStream,"password".toCharArray());
KeyManagerFactory keyManagerFactory =
KeyManagerFactory.getInstance("SunX509");
keyManagerFactory.init(keyStore, "password".toCharArray());
return SslContextBuilder.forClient()
.keyManager(keyManagerFactory)
.build();
} catch (Exception e) {
log.error("An error has occurred: ", e);
}
return null;
}
Build a Spring WebClient using this SSLContext:
private WebClient getWebClient() {
HttpClient httpClient = HttpClient.create().secure(sslSpec -> sslSpec.sslContext(getSSLContext()));
ClientHttpConnector clientHttpConnector = new ReactorClientHttpConnector(httpClient);
return WebClient
.builder()
.clientConnector(clientHttpConnector)
.build();
}
Now we can use WebClient to make our HTTP Requests.
From the details you provided, I understand that the SslContextBuilder can't understand your key type.
Try to convert a non-encrypted key to PKCS8 using the following command.
openssl pkcs8 -topk8 -nocrypt -in pkcs1_key_file -out pkcs8_key.pem
Also, see the examples at https://netty.io/4.0/api/io/netty/handler/ssl/util/InsecureTrustManagerFactory.html that accepts without verification all X.509 certificates, including those that are self-signed.
Also discussed at https://groups.google.com/forum/#!topic/grpc-io/5uAK5c9rTHw
I have an application server running some utility commands, which is programmed in C.
I have to connect to the server through Java client program using Java SSL socket with
client authentication.
The key on the server side was created using:
openssl req -new -text -out ser.req
openssl rsa -in privkey.pem -out ser.key
openssl req -x509 -in ser.req -text -key ser.key -out ser.crt
I have been provided the server key and certificate. I have combined the key and certificate
into a PKCS12 format file:
openssl pkcs12 -inkey ser.key -in ser.crt -export -out ser.pkcs12
Then loading the resulting PKCS12 file into a JSSE keystore with keytool:
keytool -importkeystore -srckeystore ser.pkcs12 -srcstoretype PKCS12 -destkeystore ser.keystore
But when I try to connect, I get the following error:
javax.net.ssl.SSLHandshakeException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at sun.security.ssl.Alert.createSSLException(Alert.java:131)
at sun.security.ssl.TransportContext.fatal(TransportContext.java:324)
at sun.security.ssl.TransportContext.fatal(TransportContext.java:267)
at sun.security.ssl.TransportContext.fatal(TransportContext.java:262)
at sun.security.ssl.CertificateMessage$T12CertificateConsumer.checkServerCerts(CertificateMessage.java:654)
at sun.security.ssl.CertificateMessage$T12CertificateConsumer.onCertificate(CertificateMessage.java:473)
at sun.security.ssl.CertificateMessage$T12CertificateConsumer.consume(CertificateMessage.java:369)
at sun.security.ssl.SSLHandshake.consume(SSLHandshake.java:377)
at sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:444)
at sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:422)
at sun.security.ssl.TransportContext.dispatch(TransportContext.java:182)
at sun.security.ssl.SSLTransport.decode(SSLTransport.java:149)
at sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1143)
at sun.security.ssl.SSLSocketImpl.readHandshakeRecord(SSLSocketImpl.java:1054)
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:394)
at SSLSocketClient.main(SSLSocketClient.java:67)
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:456)
at sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:323)
at sun.security.validator.Validator.validate(Validator.java:271)
at sun.security.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:315)
at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:223)
at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:129)
at sun.security.ssl.CertificateMessage$T12CertificateConsumer.checkServerCerts(CertificateMessage.java:638)
... 11 more
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at sun.security.provider.certpath.SunCertPathBuilder.build(SunCertPathBuilder.java:141)
at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:126)
at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:280)
at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:451)
... 17 more
On the server side log:
SSL open_server: could not accept SSL connection: sslv3 alert certificate unknown
Running command:
java -Djavax.net.ssl.keyStore=/path/to/ser.keystore -Djavax.net.ssl.keyStorePassword=passwd SSLSocketClient <server-ip> <port>
Does anyone know the cause of this problem?
Updated the client source code:
import java.net.*;
import java.io.*;
import javax.net.ssl.*;
import java.security.cert.CertificateFactory;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.security.KeyStore;
import java.security.SecureRandom;
import javax.net.SocketFactory;
public class SSLSocketClient {
public static void main(String [] args) throws Exception {
String serverName = args[0];
int port = Integer.parseInt(args[1]);
try {
SSLSocketFactory sf =
(SSLSocketFactory)SSLSocketFactory.getDefault();
Socket client = new Socket(serverName, port);
System.out.println("Connected to " + client.getRemoteSocketAddress());
OutputStream outToServer = client.getOutputStream();
DataOutputStream out = new DataOutputStream(new BufferedOutputStream(outToServer));
writeData(out);
out.flush();
InputStream inFromServer = client.getInputStream();
DataInputStream in = new DataInputStream(inFromServer);
readData(in);
outToServer = client.getOutputStream();
out = new DataOutputStream(new BufferedOutputStream(outToServer));
writeData2(out);
out.flush();
Socket newClient = sf.createSocket(client, serverName, port, false);
client.close();
} catch (IOException e) {
e.printStackTrace();
}
}
private static void writeData(DataOutputStream out) throws IOException {
char CMD_CHAR_U = 'U';
byte b = (byte) (0x00ff & CMD_CHAR_U);
out.writeByte(b); // <U>
}
private static void writeData2(DataOutputStream out) throws IOException {
char CMD_CHAR_S = 'S';
byte b = (byte) (0x00ff & CMD_CHAR_S);
out.writeByte(b); // <S>
}
private static void readData(DataInputStream in) throws IOException {
char sChar = (char) in.readByte();
System.out.println("<S>\t\t" + sChar);
}
}
Now creating the truststore as shown in the link:
https://jdbc.postgresql.org/documentation/head/ssl-client.html
Steps to create:
openssl x509 -in server.crt -out server.crt.der -outform der
keytool -keystore mystore -alias clientstore -import -file server.crt.der
java -Djavax.net.ssl.trustStore=mystore -Djavax.net.ssl.trustStorePassword=mypassword com.mycompany.MyApp
Note - The server side is using TLSv1 protocol
But still not able to make it through. What am I doing wrong?
What I want is the server to authenticate the crt of the client.
The login protocol with server; the SSL we use is only to authenticate
not to secure the transmission:
-------------------------------------------------------------
client server
-------------------------------------------------------------
sock = connect() sock = accept()
<U><LOGIN_SSL=501>
--------------------------------->
'S'|'E'
<---------------------------------
'S'
--------------------------------->
SSL_connect(sock) SSL_accept(sock)
<R><LOGIN_SSL>
<---------------------------------
I think you have several problems with your setup.
To configure properly the SSL connection with JSSE you need several things depending if you need to authenticate the server, the client, or to perform mutual authentication.
Let's suppose the later and more complete use case of mutual authentication.
The objective is to configure a SSLSocketFactory that you can use to contact your server.
To configure a SSLSocketFactory, you need a SSLContext.
This element in turn with require at least two elements for the mutual authentication use case, a KeyManagerFactory, required for client side SSL authentication, i.e., the server to trust the client, and TrustManagerFactory, required for configuring the client to trust the server.
Both KeyManagerFactory and TrustManagerFactory require a properly configured keystore with the necessary cryptographic material.
So, the first step will consist on generating this cryptographic material.
You already created a keystore with the server certificate:
keytool -keystore serverpublic.keystore -alias clientstore -import -file server.crt.der -storepass yourserverpublickeystorepassword
Please, be aware that, in a similar way as in the server case, you also need to create a public and private key pair for your client, of course, different than the server one.
The related code you provided with OpenSSL and keytool looks appropriate. Please, repeat the process for the client side:
openssl req -new -text -out client.csr
openssl rsa -in clientpriv.pem -out client.key
openssl req -x509 -in client.csr -text -key client.key -out client.crt
// You can use PKCS12 also with Java but it is also ok on this way
openssl pkcs12 -inkey client.key -in client.crt -export -out client.pkcs12
// Do not bother yourself and, in this use case, use always the same password for the key and keystore
keytool -importkeystore -srckeystore client.pkcs12 -srcstoretype PKCS12 -destkeystore client.keystore -storepass "yourclientkeystorepassword"
With the right keystores in place, try something like the following to interact with your server:
// First, let's configure the SSL for client authentication
KeyStore clientKeyStore = KeyStore.getInstance("JKS");
clientKeyStore.load(
new FileInputStream("/path/to/client.keystore"),
"yourclientkeystorepassword".toCharArray()
);
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); // SunX509
kmf.init(clientKeyStore, "yourclientkeystorepassword".toCharArray());
KeyManager[] keyManagers = kmf.getKeyManagers();
// Now, let's configure the client to trust the server
KeyStore serverKeyStore = KeyStore.getInstance("JKS");
serverKeyStore.load(
new FileInputStream("/path/to/serverpublic.keystore"),
"yourserverpublickeystorepassword".toCharArray()
);
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); // SunX509
tmf.init(serverKeyStore);
TrustManager[] trustManagers = tmf.getTrustManagers();
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagers, trustManagers, null); // You can provide SecureRandom also if you wish
// Create the SSL socket factory and establish the connection
SSLSocketFactory sf = sslContext.getSocketFactory();
SSLSocket socket = (SSLSocket)sf.createSocket(serverName, port);
// Interact with your server. Place your code here
// Please, consider the following link for alternatives approaches on how to
// interchange information with the server:
// https://web.mit.edu/java_v1.5.0_22/distrib/share/docs/guide/security/jsse/samples/sockets/client/SSLSocketClient.java
// It also suggest the use of startHandshake explicitly if your are using PrintWriter for the reason explained in the example an in the docs:
// https://docs.oracle.com/en/java/javase/11/docs/api/java.base/javax/net/ssl/SSLSocket.html
//...
// Close the socket
socket.close();
The described approach can be extended to use, instead of sockets, higher level of abstraction components like HttpsURLConnection and HTTP clients - with the exception of Apache HttpClient that handles SSL differently - like OkHttp which, under the hood, use SSLSocketFactory and related stuff.
Please, also consider review this great article from IBM's DeveloperWorks, in addition to explain many of the point aforementioned will provide you great guidance with the generation of keystores for your client an server if necessary.
Please, also be aware that, depending on your server code, you may need to configure it to trust the provided client certificate.
According to your comments you are using a server side code similar to the one provided by Postgresql 8.1. Please, see the relevant documentation for configuring SSL in that database, if you are using some similar server side code it maybe could be of help.
Probably the best approach will be to generate a client certificate derived from the root certificate trusted by your server instead of using a self signed one.
I think that it will be also relevant for your server side SSL certificate an associated private key: first, create a root self signed certificate, your CA certificate, configure your server side C code to trust it, and then derive both client and server side SSL cryptographic material from that CA: probably it will simplify your setup and make everything work properly.
I would like to implement a ssl connection which the RSA key pair are kept in the Hardware Security Module (HSM) in Java.
By using openssl pkcs11 engine, I achieved the TLS connection with the following engine configuration.
[openssl_def]
engines = engine_section
[engine_section]
pkcs11 = pkcs11_section
[pkcs11_section]
engine_id = pkcs11
MODULE_PATH = /home/ubuntu/Desktop/vendorlib.so
PIN = "123456"
init = 0
[req]
distinguished_name = test_name
[req_distinguished_name]
and then I create a certificate using this engine with the following command where ssl_key is kept in HSM device.
OPENSSL_CONF=engine.conf openssl req -new -x509 -days 365 -subj '/CN=test/' -sha256 -config engine.conf -engine pkcs11 -keyform engine -key slot_2-label_ssl_key -out cert.pem
I can confirm the certificate and key pair with the following openssl server and client commands.
For the server:
OPENSSL_CONF=engine.conf openssl s_server -engine pkcs11 -keyform engine -key slot_2-label_ssl_key -cert cert.pem -accept 44330
For the client:
OPENSSL_CONF=engine.conf openssl s_client -connect localhost:44330 -engine pkcs11
However, I need this connection to be established in Vertx(3.81) Java 8 connection.
When I look at the documentation of the vertx, there is an OpenSSLEngineOptions which can be set as serverOptions but I couldn't figure where to put parameters such as key name, slot number and engine id etc.
In fact, OpenSSLEngineOptions has a constructor which takes JsonObject, but I couldn't find any sample for this JsonObject.
Here is a code snipped where I instantiate the Vertx ssl option
HttpServerOptions serverOptions = jerseyServerOptions.getServerOptions();
OpenSSLEngineOptions openSSLOptions = new OpenSSLEngineOptions();
serverOptions.setOpenSslEngineOptions(openSSLOptions);
serverOptions.setPort(1234);
serverOptions.setSsl(true);
String certCopy = "-----BEGIN CERTIFICATE-----
MIICMzCCAZygAwIBAgIJALiPnVsvq8dsMA0GCSqGSIb3DQEBBQUAMFMxCzAJBgNV
BAYTAlVTMQwwCgYDVQQIEwNmb28xDDAKBgNVBAcTA2ZvbzEMMAoGA1UEChMDZm9v
MQwwCgYDVQQLEwNmb28xDDAKBgNVBAMTA2ZvbzAeFw0xMzAzMTkxNTQwMTlaFw0x
ODAzMTgxNTQwMTlaMFMxCzAJBgNVBAYTAlVTMQwwCgYDVQQIEwNmb28xDDAKBgNV
BAcTA2ZvbzEMMAoGA1UEChMDZm9vMQwwCgYDVQQLEwNmb28xDDAKBgNVBAMTA2Zv
bzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAzdGfxi9CNbMf1UUcvDQh7MYB
OveIHyc0E0KIbhjK5FkCBU4CiZrbfHagaW7ZEcN0tt3EvpbOMxxc/ZQU2WN/s/wP
xph0pSfsfFsTKM4RhTWD2v4fgk+xZiKd1p0+L4hTtpwnEw0uXRVd0ki6muwV5y/P
+5FHUeldq+pgTcgzuK8CAwEAAaMPMA0wCwYDVR0PBAQDAgLkMA0GCSqGSIb3DQEB
BQUAA4GBAJiDAAtY0mQQeuxWdzLRzXmjvdSuL9GoyT3BF/jSnpxz5/58dba8pWen
v3pj4P3w5DoOso0rzkZy2jEsEitlVM2mLSbQpMM+MUVQCQoiG6W9xuCFuxSrwPIS
pAqEAuV4DNoxQKKWmhVv+J0ptMWD25Pnpxeq5sXzghfJnslJlQND
-----END CERTIFICATE-----";
PemKeyCertOptions pemKeyCertOptions = new PemKeyCertOptions().setCertValue(Buffer.buffer(certValue));
serverOptions.setPemKeyCertOptions(pemKeyCertOptions);
TLDR;
How can we establish a SSL connection in Vertx where the private key stored in only HSM and could't be extracted?
Edit:
I found the parser of the jsonObject in the constructor of OpenSSLEngineOptions here. Unfortunately, it only reads the sessionCacheEnabled option.
I came up with a solution using Security Provider and Java Keystore. Here is the sample code:
/*
pkcs11.cfg example:
name=PKCS11
library=vendor provided library absolute path
slot=0
*/
String propertyPath = "/root/IdeaProjects/ssl/pkcs11.cfg;
char[] pin = "1234".toCharArray();
Provider p = new SunPKCS11(propertyPath);
Security.removeProvider("IAIK");
Security.addProvider(p);
KeyStore ks = KeyStore.getInstance("PKCS11",p);
ks.load(null,pin);
After the initialization of KeyStore, you can direct your SSLContext to KeyStore. But according to Java 8 PKCS11 Documentation, the CKA_ID attribute of the certificate and the private key should match. If pkcs11 engine matches the pair of certificate and key, it should demonstrate as alias.
//Showing aliases
Enumeration<String> aliases = ks.aliases();
for (; aliases.hasMoreElements(); ){
System.out.println(aliases.nextElement());
}
After verifying aliases, we'll use SSLContext to bind our KeyManager.
SSLContext ctx = SSLContext.getInstane("TLS");
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(ks,pin);
ctx.init(keyManagerFactory.getKeyManagers(), null, null);
I have
A self-signed server certificate (from a third-party organization I need to communicate with)
My client certificate, containing the secret key, signed by this server certificate.
Now I need to send a POST request via HTTPS using these certificates.
I managed to test the connection over https in Internet Explorer after I installed them in browser:
server cert - into the trusted CA
client cert - into the personal certs.
In java until now I used the code, given in SO: Java client certificates over HTTPS/SSL in the answer by neu242, i.e. accepted any certificate. But now the server side does accept this, i.e. I get SSL-handshake failure.
Thanks to SO: X509TrustManager Override without allowing ALL certs? I tried to return the server certificate in getAcceptedIssuers, but in vain. It throws
javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
right after getAcceptedIssuers returns.
public X509Certificate[] getAcceptedIssuers() {
try {
X509Certificate scert;
try (InputStream inStream = new FileInputStream("..\\server.crt")) {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
scert = (X509Certificate) cf.generateCertificate(inStream);
}
return new X509Certificate[]{scert};
} catch (Exception ex) {
writeLogFile(ex.getMessage());
return new X509Certificate[]{};
}
}
I guess I should specify the client certificate somehow, but cannot find any way to.
I may be wrong of course.
Hope someone can lead me the right direction.
In the end I managed to make it work.
As the server certificate is self-signed, I had to place it into a truststore. To avoid adding it to common JRE cacerts, I placed it into a truststore in a separate file with the following command (thanks to Common SSL issues in Java:
keytool -import -v -trustcacerts
-file servercert.crt -keystore server.jks
-keypass mypwd -storepass mypwd
Then I used the obtained truststore and the client certificate containing the secret key to initialize key stores and specify them to the SSL context (thanks to sql.ru: Received fatal alert: handshake_failure):
String pwd = "mypwd";
InputStream keyStoreUrl = new FileInputStream("client.p12");
InputStream trustStoreUrl = new FileInputStream("server.jks");
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(keyStoreUrl, pwd.toCharArray());
KeyManagerFactory keyManagerFactory =
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, pwd.toCharArray());
KeyStore trustStore = KeyStore.getInstance("JKS");
trustStore.load(trustStoreUrl, pwd.toCharArray());
TrustManagerFactory trustManagerFactory =
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(trustStore);
final SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(keyManagerFactory.getKeyManagers(),
trustManagerFactory.getTrustManagers(),
new SecureRandom());
SSLContext.setDefault(sslContext);
Also I had to specify HostnameVerifier, as there were some inconsistency with the server certificate and this server's url:
HostnameVerifier allHostsValid = new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
//...
}
};
HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
And that is it. Further on it was as simple as:
url = new URL(targetURL);
connection = (HttpsURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type",
"application/x-www-form-urlencoded;charset=utf-8");
connection.setRequestProperty("Accept", "application/x-www-form-urlencoded");
connection.setRequestProperty("Accept-Charset", "UTF-8");
connection.setRequestProperty("Content-Length",
Integer.toString(Data.getBytes("utf8").length));
connection.setDoInput(true);
connection.setDoOutput(true);
connection.getOutputStream().write(Data.getBytes("utf8"));
// read response...
A module I'm adding to our large Java application has to converse with another company's SSL-secured website. The problem is that the site uses a self-signed certificate. I have a copy of the certificate to verify that I'm not encountering a man-in-the-middle attack, and I need to incorporate this certificate into our code in such a way that the connection to the server will be successful.
Here's the basic code:
void sendRequest(String dataPacket) {
String urlStr = "https://host.example.com/";
URL url = new URL(urlStr);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setMethod("POST");
conn.setRequestProperty("Content-Length", data.length());
conn.setDoOutput(true);
OutputStreamWriter o = new OutputStreamWriter(conn.getOutputStream());
o.write(data);
o.flush();
}
Without any additional handling in place for the self-signed certificate, this dies at conn.getOutputStream() with the following exception:
Exception in thread "main" 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
....
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
....
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
Ideally, my code needs to teach Java to accept this one self-signed certificate, for this one spot in the application, and nowhere else.
I know that I can import the certificate into the JRE's certificate authority store, and that will allow Java to accept it. That's not an approach I want to take if I can help; it seems very invasive to do on all of our customer's machines for one module they may not use; it would affect all other Java applications using the same JRE, and I don't like that even though the odds of any other Java application ever accessing this site are nil. It's also not a trivial operation: on UNIX I have to obtain access rights to modify the JRE in this way.
I've also seen that I can create a TrustManager instance that does some custom checking. It looks like I might even be able to create a TrustManager that delegates to the real TrustManager in all instances except this one certificate. But it looks like that TrustManager gets installed globally, and I presume would affect all other connections from our application, and that doesn't smell quite right to me, either.
What is the preferred, standard, or best way to set up a Java application to accept a self-signed certificate? Can I accomplish all of the goals I have in mind above, or am I going to have to compromise? Is there an option involving files and directories and configuration settings, and little-to-no code?
Create an SSLSocket factory yourself, and set it on the HttpsURLConnection before connecting.
...
HttpsURLConnection conn = (HttpsURLConnection)url.openConnection();
conn.setSSLSocketFactory(sslFactory);
conn.setMethod("POST");
...
You'll want to create one SSLSocketFactory and keep it around. Here's a sketch of how to initialize it:
/* Load the keyStore that includes self-signed cert as a "trusted" entry. */
KeyStore keyStore = ...
TrustManagerFactory tmf =
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(keyStore);
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(null, tmf.getTrustManagers(), null);
sslFactory = ctx.getSocketFactory();
If you need help creating the key store, please comment.
Here's an example of loading the key store:
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(trustStore, trustStorePassword);
trustStore.close();
To create the key store with a PEM format certificate, you can write your own code using CertificateFactory, or just import it with keytool from the JDK (keytool won't work for a "key entry", but is just fine for a "trusted entry").
keytool -import -file selfsigned.pem -alias server -keystore server.jks
I read through LOTS of places online to solve this thing.
This is the code I wrote to make it work:
ByteArrayInputStream derInputStream = new ByteArrayInputStream(app.certificateString.getBytes());
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate) certificateFactory.generateCertificate(derInputStream);
String alias = "alias";//cert.getSubjectX500Principal().getName();
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustStore.load(null);
trustStore.setCertificateEntry(alias, cert);
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(trustStore, null);
KeyManager[] keyManagers = kmf.getKeyManagers();
TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
tmf.init(trustStore);
TrustManager[] trustManagers = tmf.getTrustManagers();
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagers, trustManagers, null);
URL url = new URL(someURL);
conn = (HttpsURLConnection) url.openConnection();
conn.setSSLSocketFactory(sslContext.getSocketFactory());
app.certificateString is a String that contains the Certificate, for example:
static public String certificateString=
"-----BEGIN CERTIFICATE-----\n" +
"MIIGQTCCBSmgAwIBAgIHBcg1dAivUzANBgkqhkiG9w0BAQsFADCBjDELMAkGA1UE" +
"BhMCSUwxFjAUBgNVBAoTDVN0YXJ0Q29tIEx0ZC4xKzApBgNVBAsTIlNlY3VyZSBE" +
... a bunch of characters...
"5126sfeEJMRV4Fl2E5W1gDHoOd6V==\n" +
"-----END CERTIFICATE-----";
I have tested that you can put any characters in the certificate string, if it is self signed, as long as you keep the exact structure above. I obtained the certificate string with my laptop's Terminal command line.
If creating a SSLSocketFactory is not an option, just import the key into the JVM
Retrieve the public key:
$openssl s_client -connect dev-server:443, then create a file dev-server.pem that looks like
-----BEGIN CERTIFICATE-----
lklkkkllklklklklllkllklkl
lklkkkllklklklklllkllklkl
lklkkkllklk....
-----END CERTIFICATE-----
Import the key: #keytool -import -alias dev-server -keystore $JAVA_HOME/jre/lib/security/cacerts -file dev-server.pem.
Password: changeit
Restart JVM
Source: How to solve javax.net.ssl.SSLHandshakeException?
We copy the JRE's truststore and add our custom certificates to that truststore, then tell the application to use the custom truststore with a system property. This way we leave the default JRE truststore alone.
The downside is that when you update the JRE you don't get its new truststore automatically merged with your custom one.
You could maybe handle this scenario by having an installer or startup routine that verifies the truststore/jdk and checks for a mismatch or automatically updates the truststore. I don't know what happens if you update the truststore while the application is running.
This solution isn't 100% elegant or foolproof but it's simple, works, and requires no code.
I've had to do something like this when using commons-httpclient to access an internal https server with a self-signed certificate. Yes, our solution was to create a custom TrustManager that simply passed everything (logging a debug message).
This comes down to having our own SSLSocketFactory that creates SSL sockets from our local SSLContext, which is set up to have only our local TrustManager associated with it. You don't need to go near a keystore/certstore at all.
So this is in our LocalSSLSocketFactory:
static {
try {
SSL_CONTEXT = SSLContext.getInstance("SSL");
SSL_CONTEXT.init(null, new TrustManager[] { new LocalSSLTrustManager() }, null);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Unable to initialise SSL context", e);
} catch (KeyManagementException e) {
throw new RuntimeException("Unable to initialise SSL context", e);
}
}
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
LOG.trace("createSocket(host => {}, port => {})", new Object[] { host, new Integer(port) });
return SSL_CONTEXT.getSocketFactory().createSocket(host, port);
}
Along with other methods implementing SecureProtocolSocketFactory. LocalSSLTrustManager is the aforementioned dummy trust manager implementation.