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
Related
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
I took the following example and moved code around so it is better simulating an actual client and actual a server where
the server only has access to the trust store file
the client only has access to the client keystore file
At least in TLS1v2, that is how it worked. After I rework the code so there are two SSL contexts(one server side and one client side), it blows up and does not work
javax.net.ssl.SSLHandshakeException: No available authentication scheme
The code I reworked now reads like this
public SSLEngineSimpleDemo() throws Exception {
File baseWorkingDir = FileFactory.getBaseWorkingDir();
File keyStoreFile = FileFactory.newFile(baseWorkingDir, "src/test/resources/client2.keystore");
char[] passphrase = "123456".toCharArray();
KeyStore ks = KeyStore.getInstance("JKS");
ks.load(new FileInputStream(keyStoreFile), passphrase);
clientCtx = SSLContext.getInstance("TLS");
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ks, passphrase);
clientCtx.init(kmf.getKeyManagers(), null, null);
File trustStoreFile = FileFactory.newFile(baseWorkingDir, "src/test/resources/server2.keystore");
KeyStore ts = KeyStore.getInstance("JKS");
ts.load(new FileInputStream(trustStoreFile), passphrase);
serverCtx = SSLContext.getInstance("TLS");
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(ts);
serverCtx.init(null, tmf.getTrustManagers(), null);
}
I have code like this that works on TLS1v2 so I am not sure why in TLS1v3, this is not working anymore.
What do I have wrong here?
Is my assumption correct in that the trustStoreFile is my private server key?
Is my assumption correct in that the clientStoreFile is my public key?
Is my assumption correct in that the server only needs the private key?
Is my assumption correct in that the client only needs the public key?
Java version: /Library/Java/JavaVirtualMachines/jdk-11.0.5.jdk
OMG, I am an idiot. The issue was my key generation script naming the first thing client2.keystore(Which was the server2.keystore).
once I fix script to generate the private key/public key par into server2.keystore(instead of the mistake of client2.keystore), export, import public key into client2.keystore, it all works.
I should have provided that script :(.
The exception javax.net.ssl.SSLHandshakeException: No available authentication scheme happens when the operating system running your server doesn't support the authentication method the JVM is looking for.
Additionally, TLSv1.3 can be explicitly specified using when instantiating an SSL context.
Change your clientCtx = SSLContext.getInstance("TLS"); to clientCtx = SSLContext.getInstance("TLSv1.3");
and
serverCtx = SSLContext.getInstance("TLS"); to serverCtx = SSLContext.getInstance("TLSv1.3");
Note: SSLContext supports more options such as
SSLv3,TLSv1,TLSv1.1,TLSv1.2
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!
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 trying to establish a secure connection between two Java projects but I'm getting a SSLHandshakeException (no cipher suites in common). This are the methods to create sockets in both sides:
Client:
private SSLSocket getSocketConnection() throws SSLConnectionException {
try {
/* Load properties */
String keystore = properties.getProperty("controller.keystore");
String passphrase = properties.getProperty("controller.passphrase");
String host = properties.getProperty("controller.host");
int port = Integer.parseInt(properties
.getProperty("controller.port"));
/* Create keystore */
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(new FileInputStream(keystore), passphrase.toCharArray());
/* Get factory for the given keystore */
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(keyStore);
SSLContext ctx = SSLContext.getInstance("SSL");
ctx.init(null, tmf.getTrustManagers(), null);
SSLSocketFactory factory = ctx.getSocketFactory();
return (SSLSocket) factory.createSocket(host, port);
} catch (Exception e) {
throw new SSLConnectionException(
"Problem connecting with remote controller: "
+ e.getMessage(), e.getCause());
}
}
Server:
private SSLServerSocket getServerSocket() throws SSLConnectionException {
try {
/* Load properties */
Properties properties = getProperties("controller.properties");
String keystore = properties.getProperty("controller.keystore");
String passphrase = properties.getProperty("controller.passphrase");
int port = Integer.parseInt(properties
.getProperty("controller.port"));
/* Create keystore */
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(new FileInputStream(keystore), passphrase.toCharArray());
/* Get factory for the given keystore */
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(keyStore);
SSLContext ctx = SSLContext.getInstance("SSL");
ctx.init(null, tmf.getTrustManagers(), null);
SSLServerSocketFactory factory = ctx.getServerSocketFactory();
return (SSLServerSocket) factory.createServerSocket(port);
} catch (Exception e) {
throw new SSLConnectionException(
"Problem starting auth server: "
+ e.getMessage(), e.getCause());
}
}
I have a RSA key generated with keytool. This code load it from disk.
What I'm doing wrong?
UPDATE:
I added the a call to setEnabledCipherSuites in both sides with this array:
String enableThese[] =
{
"SSL_RSA_WITH_3DES_EDE_CBC_SHA",
"SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA",
"SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA"
};
I get the same result.
On the server side, you're not initialising the keystore/keymanagers, only the truststore/trustmanagers: ctx.init(null, tmf.getTrustManagers(), null).
On the server, initialising the keymanager is always necessary to configure the server certificate. Initialising the truststore is only necessary when you want to use client-certificate authentication. (There are more details in this question for the difference between keymanager and trustmanager.)
Without any keymanager configured, there is no RSA or DSA based certificate available, so no cipher suite that rely on a certificate for authentication (all the ones enabled by default are) are available. Hence, you get no cipher suites in common between the client and the server.
You'd need something like this:
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keystore, password.toCharArray()); // That's the key's password, if different.
// ...
ctx.init(kmf.getKeyManagers(), null, null);
It's not clear from your example, but you shouldn't of course use the same keystore on the client (as a truststore) and on the server side (as a keystore): the private key should only be known to the server, and doesn't need to be in the client's trust store.
EDIT: (I'll try to re-explain in a different way, since it wasn't clear for everyone. Perhaps it might help.)
The following code initialises the SSLContext with a null array of key managers (the first argument): ctx.init(null, tmf.getTrustManagers(), null). (There is no default key manager.)
The key manager is what manages your (private) keys and certificates, on the side where the code is running. On the server, the key manager is what's responsible for handling the server certificate and its private key. The key manager is itself usually initialised by the "keystore keystore". "keystore" in Java can have multiple meanings. One of the meaning of keystore is the entity into which keys and certificates can be stored, typically a file. Such a keystore can be used to initialise a trust manager1 (in which case it's referred to as the truststore) or a key manager (in which case it's referred to as a keystore). Sorry, not my choice of names, but that's the way the system properties are called.
When the server is configured with a null key manager, it is configured without any certificate and associated private key. Therefore, it doesn't have any RSA or DSA certificate. Therefore, it won't be able to use any of the *_RSA_* or *_DSS_* cipher suites, whether they've been explicitly enabled or not (they will be disabled automatically by lack of certificate to use with them). This effectively discards any cipher suite enabled by default (or any such cipher suite enabled explicitly anyway). Hence, there is "no cipher suite in common".
In short, an SSLContext on the server side needs to be configured with a certificate and its private key2. This is done by configuring its key manager. In turn, this is often done by using a keystore with a KeyManagerFactory (not a TrustManagerFactory).
1: The trust manager uses local trust anchors (e.g. trusted CA certificates) to evaluate trust in a remote party (i.e. a server trusting a client certificate or a client trusting a server certificate).
2: Some cipher suites supported by the JSSE don't need certificates, but they're either anonymous cipher suites (insecure) or Kerberos cipher suites (which need to be set up differently altogether). Both are disabled by default.