I've been tasked with implementing functionality in a Spring Boot REST API to contact another API (XML webservice). The outside API uses two-way SSL authentication. I've been given the correct certificate to implement on our side, and I've implemented the Java code. But whenever I run the code I get "Received fatal alert: handshake_failure". I've loaded the jks keystore into the SSLContext like this:
FileInputStream truststoreFile = new FileInputStream("/Users/myUser/Desktop/myProject/myProjectName/src/main/resources/keystore-name.jks");
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
KeyStore truststore = KeyStore.getInstance(KeyStore.getDefaultType());
char[] trustorePassword = "keyStorePassword".toCharArray();
truststore.load(truststoreFile, trustorePassword);
trustManagerFactory.init(truststore);
SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
KeyManager[] keyManagers = {};//if you have key managers;
sslContext.init(keyManagers, trustManagerFactory.getTrustManagers(), new SecureRandom());
Would I actually have to configure anything else to enable mutual two-way SSL from our API, acting like I client in this scenario? I thought I could just like the cert keystore and go. But maybe I need to do something else to enable this?
You are using the file shared with you in the wrong context. That file is a Keystore containing the client certificate and corresponding key.
TrustStore - Tells which CAs should be trusted by the client (you).
Keystore - Tells the server about the client (you).
In order for the mutual TLS handshake to pass through, you need to load the Keystore and set it in KeyManager like below.
// Load the Keystore
KeyStore keyStore = KeyStore.getInstance("JKS");
InputStream keystoreStream = new FileInputStream(pathToJKSFile);
keyStore.load(keystoreStream, keystorePassword.toCharArray());
// Add Keystore to KeyManager
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, keystorePassword.toCharArray());
// Create SSLContext with KeyManager and TrustManager
SSLContext context = SSLContext.getInstance("TLS");
context.init(keyManagerFactory.getKeyManagers(), null, new SecureRandom());
SSLSocketFactory sslSocketFactory = context.getSocketFactory();
// Now, use this SSLSocketFactory while making the HTTPS request
Related
I am trying to implement secured Socket connections between a Spring Server and an Android Client but I found some design problems.
At the beginning I implemented the solution provided in this tutorial (it's in Spanish but easily understandable with Google Traductor). This tutorial secures Socket connections with a key in each side (one for server and one for client) and uses a trustedKeys.jks to store trusted keys in each side.
That means that for every client I should:
Create a new keystore for each new client
Add this new client key to trusted keys on server side
Add server keystore to each new client
This seemed unrealistic to me considering the growth of the number of clients.
I found another approach that suits better my requirements, it uses the server certificate as a public key (I think) and ciphers the data with it, that will be deciphered by the server keystore:
Server Code
public SSLServerSocket getSSLServerSocket(int port) throws IOException, ...
{
KeyStore keyStore = KeyStore.getInstance("JKS");
keyStore.load(resourceKeyFile.getInputStream(), keystorePassword.toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keystore, keystorePassword.toCharArray());
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(kmf.getKeyManagers(), null, null);
SSLServerSocketFactory sslServerSocketfactory = sc.getServerSocketFactory();
return (SSLServerSocket) sslServerSocketfactory.createServerSocket(port);
}
Here is my function for creating a SSLServerSocket. In the KeyManagerFactory initialization kmf.init(keyStore, keystorePassword.toCharArray()); I set the server keystore keystore with its password keystorePassword which are read with #Value Spring Annotation outside the function. This function will return the SSLServerSocket which will accept client connections in a new Thread like this:
while(true) {
SSLSocket clientSocket = (SSLSocket) serverSocket.accept();
}
Client code
SSLContext context = socketUtil.createSSLContext();
SSLSocketFactory sf = context.getSocketFactory();
SSLSocket socket = (SSLSocket) sf.createSocket(serverUrl, port);
This code creates the context I want to usem which is presented below, and creates the Socket to an address serverUrl and a port port:
public final SSLContext createSSLContext()
throws Exception {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
InputStream in = getClass().getResourceAsStream("serverCert.pem");
KeyStore trustStore = KeyStore.getInstance("JKS");
trustStore.load(null);
try {
X509Certificate cacert = (X509Certificate) cf.generateCertificate(in);
trustStore.setCertificateEntry("serverKey", cacert);
} finally {
IOUtils.closeQuietly(in);
}
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(trustStore);
SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, tmf.getTrustManagers(), new SecureRandom());
return sslContext;
}
Here, I load the Server certificate serverCert.pem which I generated from the keystore with the keytool utility. Then other parameters are setted, such as the algorithm or the protocol. Thus, the socket will trust connections from the issuer of the certificate.
The code is working just nice and smooth, but I'm concerned about security and good practices. So once again, is this a good approach to solve my problem?
Any clarifications about the process, code or misunderstandings are welcome. Thank you so much!
How can I set the Security Provider for a SSL Context?
My Code is like
Security.insertProviderAt(new XXX(), 1);///XXX is a Security Provider
KeyStore clientStore = KeyStore.getInstance("PKCS12");
clientStore.load(new FileInputStream(p12FileName), p12FilePass.toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(clientStore, p12FilePass.toCharArray());
KeyManager[] kms = kmf.getKeyManagers();
sslContext = SSLContext.getInstance("TLS");
sslContext.init(kms, null, new SecureRandom());
If I remove the Provider XXX or set SunEC as the priority provider, everything works fine. But I can't do both due to some reason. Is there any way I can set the provider as SunEC in the SSL Context for the REST call without changing the first line Security.insertProviderAt(new XXX(), 1)
I need to perform a rest call by attaching the local ssl certificate.
I do not have any info about KeyStore. I just know there is a Certificate installed in my PC and I have to use the certificate based on details of certificate like "Serial number", "Issuer" etc which i can see in the certificate details in the personal certificate store.
I need to create SSLConnectionSocketFactory object which can be attached to rest call.
My question is how to create the SSLContext object?
SSLContext sslContext;// How to create this object and pass it to sslSocketFactory.
HostnameVerifier hostnameVerifier = NoopHostnameVerifier.INSTANCE;
SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext, hostnameVerifier);
You can create the SSLContext instance using this code snippet.
// Load Certificate
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
Certificate certificate = certificateFactory.generateCertificate(new FileInputStream(new File("CERTIFICATE_LOCATION")));
// Create TrustStore
KeyStore trustStoreContainingTheCertificate = KeyStore.getInstance("JKS");
trustStoreContainingTheCertificate.load(null, null);
trustStoreContainingTheCertificate.setCertificateEntry("ANY_CERTIFICATE_ALIAS", certificate);
// Create SSLContext
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(trustStoreContainingTheCertificate);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
System.out.println(sslSocketFactory);
I'm creating SSLContext in standard way:
take .p12 certificate file,
create KeyStore and load certificate into it,
create KeyManagerFactory, init it with KeyStore, and get KeyManagers,
create TrustManagerFactory, init it with null, and get TrustManagers.
create SSLContext and init it with KeyManagers and TrustManagers.
The question is - how can I extract KeyStore and certificate data back from SSLContext? The task is to obtain fingerprint hash from certficate.
Is it even possible or I have to get it separately, reading certificate from file?
It can be done if you have a custom TrustManager. You can refer to this link for that custom class. Look for the private SavingTrustManager static class.
And the place where you are using the java's default TrustManager, use this class so that you can retrieve the certificate that the server sent.
SSLContext context = SSLContext.getInstance("TLS");
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(dummyTrustStore);
X509TrustManager defaultTrustManager = (X509TrustManager) tmf.getTrustManagers()[0];
SavingTrustManager savingTrustManager = new SavingTrustManager(defaultTrustManager);
context.init(null, new TrustManager[] { savingTrustManager }, null);
SSLSocketFactory factory = context.getSocketFactory();
And after you have started the handshake, you can get the certificates from the SavingTrustManager from the static member variable chain, like:
savingTrustManager.chain
I would like to create an embedded ActiveMQ broker that listens on SSL protocol using client authentication mechanism (TLS).
Here's my code that expects to do so :
//loading keystore from file
KeyStore keystore = KeyStore.getInstance("pkcs12");
File ksfile = new File("/home/me/client1.pkcs12");
FileInputStream ksfis = new FileInputStream(ksfile);
keystore.load(ksfis, "password".toCharArray());
//loading truststore from file
KeyStore truststore = KeyStore.getInstance("jks");
truststore.load(new FileInputStream(new File("/home/me/client1.truststore")), "password"
.toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory
.getDefaultAlgorithm());
kmf.init(keystore, "password".toCharArray());
TrustManagerFactory tmf = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(truststore);
//broker definition
String cfURI = "ssl://localhost:2032";
BrokerService brokerService = new BrokerService();
brokerService.addConnector(cfURI);
//configure ssl context for the broker
SslContext sslContext = new SslContext(kmf.getKeyManagers(),tmf.getTrustManagers(), null);
//need client authentication
sslContext.getSSLContext().getDefaultSSLParameters().setNeedClientAuth(true);
sslContext.getSSLContext().getDefaultSSLParameters().setWantClientAuth(true);
brokerService.setSslContext(sslContext);
brokerService.start();
When i execute the previous code in a main program, i get the following error :
GRAVE: Could not accept connection : javax.net.ssl.SSLException: No available certificate or key corresponds to the SSL cipher suites which are enabled.
Any suggestions are wellcome !
Thanks for reading.
Has your client set the certificate from the broker in its truststore? I'm afraid thats the problem you are running into.
Other than that, it would probably help if you paste the client code as well
I got this error by using ActiveMQConnectionFactory instead of ActiveMQSslConnectionFactory when connecting from the client