Can I have Mutual/2 Way SSL without having https connection? - spring-boot

I am very new to this SSL configurations. I am working on adding 2 way ssl configuration on a Springboot application. I have gone through lot of code snippets online on this implementation.
My destination server end point - https://endpoint.com/path/to/service is secured to which I make a REST call.
If I set my application.yml:
server:
port: 9001
ssl:
enabled: true
client-auth: need
key-store: classpath:KEYSTORE.pfx
key-store-password: password
key-alias: aliasname
key-store-type: PKCS12
trust-store: classpath:TRUSTSTORE.p12
trust-store-password: password
trust-store-type: PKCS12
My application runs on https://localhost:9001.
TRUSTSTORE.p12 contains the server's public certificates and CA certificates.
With this setup, I'm able to get response from the server using RestTemplate configurations.
When I give another TrustStore which doesn't have my Server's public Certificate, it fails to establish a connection - Expected behaviour.
But when I disable the SSL:
server:
port: 9001
ssl:
enabled: false
My application runs on HTTP//:localhost:9001.
Will I still be able to make a Mutual authentication with the truststore and keystore available at my SSL Context?
Can I Still validate the server's public certificate with the above yml configuration and the below code?
// KeyStore and TrustStore
keyStore = KeyStore.getInstance("pkcs12");
ClassPathResource classPathResource = new ClassPathResource("KEYSTORE.pfx");
InputStream inputStream = classPathResource.getInputStream();
keyStore.load(inputStream, "password".toCharArray());
TS = KeyStore.getInstance("pkcs12");
ClassPathResource classPathResource1 = new ClassPathResource("TRUSTSTORE.p12");
InputStream inputStream1 = classPathResource1.getInputStream();
TS.load(inputStream1, "password".toCharArray());
// configuring the SSLContext
SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(
new SSLContextBuilder().loadTrustMaterial(TS, new TrustSelfSignedStrategy())
.loadKeyMaterial(keyStore, "password".toCharArray()).build());
NOTE: With ssl set to FALSE other applications would call my endpoint at http//:localhost:9001. And I would make a REST call the server on https://endpoint.com/path/to/service.

You cannot have mutual SSL authentication without having SSL. While one might probably construct some kind of mutual authentication method which works with plain HTTP instead of HTTPS there is no standard for this. The usual certificate based mutual authentication which is implemented in the browsers works only with HTTPS since it is actually part of the SSL layer.

Related

Spring Boot WebCLient accept Self Signed Certificate, but not with InsecureTrustManagerFactory

I'm trying to build a REST client using Spring Boot and utilizing WebClient, however I'm conflicted when trying to config HTTPS call to a REST API.
When using RestTemplate, I was able to get self signed certificate working by using TrustSelfSignedStrategy(), thus even when the certificate is self signed, it is still being validated for its hostname, expiry date, etc.
In WebClient, so far I only found the way self signed certificate is by utilizing InsecureTrustManagerFactory, however this will cause the whole validation to be skipped as well, effectively void the purpose of using HTTPS in the first place.
As quoted from Netty documentations:
An insecure TrustManagerFactory that trusts all X.509 certificates without any verification.
NOTE: Never use this TrustManagerFactory in production. It is purely for testing purposes, and thus it is very insecure.
Is there any way I can use self signed certificate in WebClient without having to dismantle all the verification?
Yes, you can use a self signed certificate. What you need to do is add the self signed certificate into a java keystore and load it into your application by getting the keystore and transforming it into a TrustManager. Afterword you can supply the TrustManager to the SslContextBuilder which is needed for configuring the WebClient based on Netty. See below for an example:
Path truststorePath = Paths.get(/path/to/your/truststore)
InputStream truststoreInputStream = Files.newInputStream(truststorePath, StandardOpenOption.READ)
KeyStore truststore = KeyStore.getInstance(KeyStore.getDefaultType());
truststore.load(truststoreInputStream, truststorePassword);
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(truststore);
SslContext sslContext = SslContextBuilder.forClient()
.trustManager(trustManagerFactory)
.build()
HttpClient httpClient = HttpClient.create()
.secure(sslSpec -> sslSpec.sslContext(sslContext));
WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(httpClient))
.build()

What type of certificate spring-boot application needs to have HTTPS connection?

I would like to make clients require HTTPS protocol to connect to my spring-boot backend application but I have no idea what kind of certificate I need for this.
Any certificate is good for this? Or I need to "turn on" some configurations?
Well, best you can do is to generate self-signed certificate.
There are many examples.
Also, you have to configure your backend.
Add dependency:
implementation 'org.apache.httpcomponents:httpclient:4.5'
Provide RestTemplate bean:
#Bean
private RestTemplate restTemplate() {
SSLContext sslContext = buildSslContext();
SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslContext);
HttpClient httpClient = HttpClients.custom()
.setSSLSocketFactory(socketFactory)
.build();
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
return new RestTemplate(factory);
}
private SSLContext buildSslContext() {
try {
char[] keyStorePassword = sslProperties.getKeyStorePassword();
return new SSLContextBuilder()
.loadKeyMaterial(
KeyStore.getInstance(new File(sslProperties.getKeyStore()), keyStorePassword),
keyStorePassword
).build();
} catch (Exception ex) {
throw new IllegalStateException("Unable to instantiate SSL context", ex);
} finally {
sslProperties.setKeyStorePassword(null);
sslProperties.setTrustStorePassword(null);
}
}
Provide required SSL properties in your application.properties or application.yaml file:
server:
ssl:
enabled: true
key-store: /path/to/key.keystore
key-store-password: password
key-alias: alias
trust-store: /path/to/truststore
trust-store-password: password
That's it. Now you can see your Tomcat is starting on 8080 (or another port) (https).
Alternatively, you can use my spring boot starter
1, You need a certificate for the application
there is two kind of certificates: self-signed certificate and ca-issued certificate
self-signed certificate
you can sign a certificate yourself use keytool
self-signed certificate has some disadvantage, browers will show a red alert page when access self-sign certificate site, and client need import the corresponding self-sign ca root certificate to communicate with server
ca-issued certificate
you can get a ca-issued certificate from let's encrypt
ca-issued certificate do not have all those disadvantage mentioned above ,it just work
2, You also need setting some spring boot configurations, see Spring Boot Docs - How to configure ssl

How to configure HermesJMS to use a specific client certificate when connecting via SSL?

I have a Spring Boot Application that I've created instantiates an instance of ActiveMqSslBroker. I am attempting to connect to this broker using HermesJMS as a client.
I've configured the connection factory in Hermes as follows:
Class: org.apache.activemq.ActiveMQSslConnectionFactory
brokerURL: ssl://localhost:61616
keyStore: /path/to/client-keystore-containing-client-cert.ks
keyStoreKeyPassword: *****
keyStoreType: PKCS12
trustStore: /path/to/trust-store-containing-broker-cert.ts
trustStorePassword: ****
trustStoreType: PKCS12
The broker is configured in my spring-boot application as follows:
SSL Connector:
brokerUrl: ssl://localhost:61616
KeyManagers:
returned from KeyManagerFactory.getKeyManagers()
KeyStore: /path/to/key-store-containing-broker-cert.ks
TrustManagers:
returned from TrustManagerFactory.getTrustManagers()
TrustStore: /path/to/trust-store-containing-client-cert.ks
The broker is rejecting the connection requests from Hermes with the following error:
javax.net.ssl.SSLHandshakeException: Received fatal alert: certificate_unknown
So apparently HermesJMS is not sending the client certificate that is contained in its configured keyStore. Does the key have to have a specific alias to be picked up and used by Hermes? Is there a property I can set to specify the alias from the keyStore to use?
Turns out it was user error. I had a couple of different session configured, and while flipping back and forth between my IDE and Hermes, I somehow ended up testing on a session that was using the wrong connection factory.
After switching to the right session, things started to work.
For completeness here is how I got things working:
In my Spring-Boot application I defined my BrokerService bean as follows:
#Bean
public BrokerService broker(
#Value("${spring.activemq.broker-url}") String brokerUrl,
#Qualifier("brokerTrustManagerFactory") TrustManagerFactory trustManagerFactory,
#Qualifier("brokerKeyManagerFactory") KeyManagerFactory keyManagerFactory,
#Qualifier("secureRandom") SecureRandom secureRandom
){
SslBrokerService brokerService = new SslBrokerService();
brokerService.addSslConnector(
brokerUrl,
keyManagerFactory.getKeyManagers(),
trustManagerFactory.getTrustManagers(),
secureRandom
);
return brokerService;
}
Here is how a connection factory could be configured in a client application:
#Bean
ConnectionFactory connectionFactory(
#Value("${spring.activemq.broker-url}") String brokerUrl,
#Value("${spring.activemq.trustStorePath}") String trustStorePath,
#Value("${spring.activemq.trustStorePass}") String trustStorePass,
#Value("${spring.activemq.keyStorePath}") String keyStorePath,
#Value("${spring.activemq.keyStorePass}") String keyStorePass,
#Value("${client.key.pass}") String clientKeyPass
) {
ActiveMQSslConnectionFactory connectionFactory =
new ActiveMQSslConnectionFactory(brokerUrl);
connectionFactory.setTrustStore(trustStorePath);
connectionFactory.setTrustStorePassword(trustStorePass);
connectionFactory.setTrustStoreType("PKCS12");
connectionFactory.setKeyStore(keyStorePath);
connectionFactory.setKeyStorePassword(keyStorePass);
connectionFactory.setKeyStoreKeyPassword(clientKeyPass);
connectionFactory.setKeyStoreType("PKCS12");
return connectionFactory;
}
Hopefully someone will find this answer useful. Please note, the "spring.activemq.*" property names are not official property names recognized by Spring-Boot. They're just names that seemed to be used by a lot of the spring-boot activemq tutorials on the web.
Thanks,
Dave

Certificate Based authentication, client share certificate to server

I have a requirement from a server application to share a SSL certificate. step i did:
I generated a self signed certificate against the Domain IP address(don't have domain name) where my application is deployed.
i shared the certificate to the server. they will keep the certificate in their trust store.
server is validating the request for the IP address. if the request are not coming from the IP address they are stopping them.
My question:
i have a spring boot application. do i need to make any change in my code for the certificate i have generated. if yes then what is the change.
Yes you need to make changes in your code. You need to load your keystore(with keypair) and if required also load your truststore into your http client. Most of the http clients require a SSLContext, so this would be sufficient for you:
KeyStore keyStore = ...;
TrustStore trustStore = ...;
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, keyStorePassword);
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(trustStore);
SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);
// Spring provides by default RestTemplate as HTTP Client, this client is an Apache HTTP Client wrapper
// The setup would be:
HttpClient httpClient = HttpClient.newBuilder();
.sslContext(sslFactory.getSslContext());
.build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
RestTemplate restTemplate = new RestTemplate(requestFactory)

Apache HttpClient for RestTemplate default behaviour if not setting up SSLSocketFactory and SSLContext

I am using Spring RestTemplate (config using HttpClient) to make a restful service call for a URL with https.
I explicitly setup SSL using following code:
SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial().setProtocol().build
return new SSLConnectionSocketFactory(sslContext)
It works without any problem.
What would be the default behaviour if I don't setup SSLSocketFactory and SSLContext for HttpClient? Will it look for the cacerts under the installed JDK or it will disable the ssl?
I say Yes If your https website can be passed by your browser, or your certificate file is coming from the Certificate Authority such as GlobalSign, Verisign
we can see the SSLContext and SSLSocketFactory will be created automatically, so you can visit https without doing anything
if your certificate file is not trusted, and import to jdk cacerts still can't solve this, because HttpClient download the certificate file via SSLSocket, not from the jdk cacerts

Resources