How to use Spring WebSocketClient with SSL? - spring

I connect with my websocket client to non-SSL endpoint without any problem. But I cannot find any way how to connect to wss (SSL) endpoint. Where can I define the SSL factory etc. No object seem to have related set method.
WebSocketClient transport = new StandardWebSocketClient();
WebSocketStompClient stompClient = new WebSocketStompClient(transport);
stompClient.setMessageConverter(new MappingJackson2MessageConverter());
String url = cfg.getWebsocketEndpoint();
StompSessionHandler handler = new MySessionHandler();
WebSocketHttpHeaders headers = new WebSocketHttpHeaders();
stompClient.connect(url, handler);
I am using wss:// url and on the other side I have a server with self-signed certificate. However, this code does not throw any exception while connecting, but the session is not established.
EDIT: After enabling tracing for web.* I got a standard error, with
sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
It occurs when connecting to server with self-signed certificate. However, for RestTemplate I already updated SSLContext with this code and REST calls are fine now, but I do not know why, StandardWebSocketClient is IGNORING the SSLContext. Why?
String keystoreType = "JKS";
InputStream keystoreLocation = new FileInputStream("src/main/resources/aaa.jks");
char [] keystorePassword = "zzz".toCharArray();
char [] keyPassword = "zzz".toCharArray();
KeyStore keystore = KeyStore.getInstance(keystoreType);
keystore.load(keystoreLocation, keystorePassword);
KeyManagerFactory kmfactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmfactory.init(keystore, keyPassword);
InputStream truststoreLocation = new FileInputStream("src/main/resources/aaa.jks");
char [] truststorePassword = "zzz".toCharArray();
String truststoreType = "JKS";
KeyStore truststore = KeyStore.getInstance(truststoreType);
truststore.load(truststoreLocation, truststorePassword);
TrustManagerFactory tmfactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmfactory.init(truststore);
KeyManager[] keymanagers = kmfactory.getKeyManagers();
TrustManager[] trustmanagers = tmfactory.getTrustManagers();
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keymanagers, trustmanagers, new SecureRandom());
SSLContext.setDefault(sslContext);
UPDATE: Unfortunately, I did not managed to do this with custom truststore. I installed the certificate with InstallCert.java.

I think that each websocket container implementation provides ways to do this.
You have to set this configuration using the StandardWebSocketClient .setUserProperties. All those properties are internally set in the ClientEndpointConfig used by the client.
Here's an example with Tomcat as a provider:
StandardWebSocketClient wsClient = //...;
SSLContext sslContext = //...;
wsClient.setUserProperties(WsWebSocketContainer.SSL_CONTEXT_PROPERTY, sslContext);
In any case you should refer to your provider reference documentation to know which configuration keys you should use.

I've faced a similar issue, where I need to use a different ssl context instead of the default one and I could not use the Tomcat provider version solution. I've met a lot of resources and examples about this solution which does not fit my case, and for sure I landed here too on this question.
When you need to use a specific/custom SSLContext to set and you don't need the 'tomcat version', you can go for the Jetty version because it allows you to set which trust store and/or key store you want to use. In this case my Spring application was on the 'client' side and not the server one, but Jetty SslContextFactory provides the same functionalities for the 'Server' case.
The short example code below is for the client side, but it shares methods and signatures with the server side (check the jetty documentation about)
final SslContextFactory.Client factory = new SslContextFactory.Client();
factory.setSslContext(sslContext); // a different loaded java SslContext instead of the default one
//Create the web socket client with jetty client factory
final JettyWebSocketClient client =
new JettyWebSocketClient(new WebSocketClient(new HttpClient(factory)));
client.start();
final WebSocketStompClient stomp = new WebSocketStompClient(client);
There're also method for setting the trust store or key store instead a fully loaded SslContext.

Related

Get certificates from Azure KeyVault to Keystore in Kotlin/SpringBoot for outgoing requests

For some API requests I need in my backend a certificate. Currently the .p12 is versioned in the repository and loaded into the WebClient when its initialized like this:
private fun getWebClient(): WebClient {
val ks: KeyStore = KeyStore.getInstance("PKCS12")
ks.load(ClassPathResource("keystore.p12").inputStream, config.trustStorePassword.toCharArray())
val kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm())
kmf.init(ks, config.trustStorePassword.toCharArray())
val sslContext = SslContextBuilder
.forClient()
.keyManager(kmf)
.build()
val httpClient: HttpClient = HttpClient.create().secure { sslSpec -> sslSpec.sslContext(sslContext) }
return WebClient
.builder()
.baseUrl(config.BaseUrl)
.clientConnector(ReactorClientHttpConnector(httpClient))
.build()
}
I want to change it since the backend is deployed to Azure AppService. I already created a KeyVault and imported the certificate and granted access via a managed identity to the AppService.
I currently struggle to load the keystore in Spring Boot from the KeyVault. For reference I am trying to follow https://learn.microsoft.com/en-us/azure/developer/java/spring-framework/configure-spring-boot-starter-java-app-with-azure-key-vault-certificates.
It uses the azure.keyvault.uri property which is apparently deprecated, so I am using spring.cloud.azure.keyvault.certificate.endpoint.
Also documentation states:
KeyStore azureKeyVaultKeyStore = KeyStore.getInstance("AzureKeyVault");
KeyVaultLoadStoreParameter parameter = new KeyVaultLoadStoreParameter(
System.getProperty("azure.keyvault.uri"));
azureKeyVaultKeyStore.load(parameter);
SSLContext sslContext = SSLContexts.custom()
.loadTrustMaterial(azureKeyVaultKeyStore, null)
.build();
However, I am not able to resolve the class KeyVautLoadStoreParameter.
I am using spring boot 2.7.7 and
implementation("com.azure.spring:spring-cloud-azure-starter:4.5.0")
implementation("com.azure.spring:spring-cloud-azure-starter-keyvault:4.5.0")
Any help towards loading the certificates and configuring the WebClient would be greatly appreciated.
What you can do is create an app registration in azure AD and then grant access to this app registration through access policy in the azure keyvault.
Now collect the clientId , Tenant Id and client Secret from the app registration
Create the secret in certificates&secret tab and click on new registration.
Now create the application.yaml file in the following format
server:
ssl:
key-alias: TestCertificate
key-store-type: AzureKeyVault
trust-store-type: AzureKeyVault
port: 8443
azure:
keyvault:
uri: <KEYVAULT URL>
client-id: <CLIENT_ID FROM APP REGISTRATIOM>
client-secret: <CLIENT_SECRET FROM APP REGISTRATIOM>
enabled: true
tenant-id: <TENANT_ID FROM APP REGISTRATIOM>
Now you can import all the credentials from the azure key vault using the following program.
KeyStore azureKeyVaultKeyStore = KeyStore.getInstance("AzureKeyVault");
KeyVaultLoadStoreParameter parameter = new KeyVaultLoadStoreParameter(
System.getProperty("azure.keyvault.uri"),
System.getProperty("azure.keyvault.tenant-id"),
System.getProperty("azure.keyvault.client-id"),
System.getProperty("azure.keyvault.client-secret")
);
azureKeyVaultKeyStore.load(parameter);
SSLContext sslContext = SSLContexts.custom()
.loadTrustMaterial(azureKeyVaultKeyStore, null)
.build();
// Here I am cross check wether we have imported the certificate name " TestCertificate"
if(azureKeyVaultKeyStore.containsAlias("TestCertificate"))
{
System.out.println("The Certificate Exists ");
}
else {
System.out.println("Error Has Occured");
}
Output:

SSL context in mongo:client attribute using spring xml config

How to add SSL key store and trust store file path and password in mongo:client options using spring xml to connect mongo db on TLS . Also need to know how to add ssl invalid host name allowed in mongo:client attribute in xml. I am using spring data mongo db 2.2.3 .
This is not XML solution, but via Bean.
This is what I did for 2.2.5.RELEASE. Note that for 2.3.0, there is no MongoClientOptions
#Value("classpath:truststore/mongoserver-truststore.p12")
private Resource trustStore;
#Value("${ssl.truststore.mongodb.password}")
private String mongoTrustStorePassword;
#Bean
public MongoClientOptions mongoClientOptions() throws Exception {
TrustManagerFactory trustManagerFactory = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(trustStore.getInputStream(), mongoTrustStorePassword.toCharArray());
trustManagerFactory.init(keyStore);
SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
return MongoClientOptions.builder()
.sslEnabled(true)
.sslContext(sslContext)
.sslInvalidHostNameAllowed(true)
.build();
}

How to force the Application deployed in WAS servers to use default trust store trust.p12 (Node) rather than refer to java cercets

My Rest Template is referring to java_1.8_192/jre/lib/security/cacerts rather than it is not referring to WAS default trust store. Below are the code snippet i am using. How to force the code to check default WAS node level trust store.
SSLContext sslContext = SSLContexts.custom().build();
SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory( sslContext, new String[] { "TLSv1.2", "TLSv1.1" },null, SSLConnectionSocketFactory.getDefaultHostnameVerifier() );
CloseableHttpClient client = HttpClients.custom().setSSLSocketFactory( sslConnectionSocketFactory ).build();
ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory( client );

Handling multiple RestTemplate instances for multiple hosts

I am building out a web service which proxies and does some slight manipulation of HTTP requests. I'm handling requests going to multiple hosts of the same type but of which I don't know of until run time (I consume a web service that gives the host IPs). Each host that I interact with has different credentials (Basic-Auth, fetched from a non-local database, credentials change periodically). The way I handle things today is pretty naive. For every request, I am constructing a new RestTemplate like so:
public static RestOperations getRestOperations(int timeout, String username, String password)
{
RequestConfig requestConfig = RequestConfig.custom().setConnectionRequestTimeout(timeout).setConnectTimeout(timeout).setSocketTimeout(timeout).build();
CredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, password));
CloseableHttpClient httpclient = HttpClients.custom()
.setDefaultCredentialsProvider(credsProvider)
.setDefaultRequestConfig(requestConfig)
.build();
ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(
httpclient);
return new RestTemplate(requestFactory);
}
So each Controller method always starts out with:
UsernamePassword userPass = credentialService.getCredentials(request.getRemoteHost())
RestOperations restOps = getRestOperations(userPass.getUser(), userPass.getPass(), TIMEOUT_IN_MILLIS);
It seems to me that since I'm constructing a new RestTemplate with each request that any previous connections that have been made between my server and the host are not being reused.
Is this the case? If so, then it seems I will need some sort of RestTemplateFactory which can cache RestTemplate instances based on the host IP address so that connections can be reused. However if I do that, then I need some mechanism that makes sure that the credentials haven't changed and to check and update credentials if they do change. Is there a better solution?

Proxy settings in Spring Boot

My application needs to fetch an XML file from the web, as follows:
#Bean
public HTTPMetadataProvider metadataProvider()
throws MetadataProviderException {
String metadataURL = "http://idp.ssocircle.com/idp-meta.xml";
final Timer backgroundTaskTimer = new Timer(true);
HTTPMetadataProvider provider =
new HTTPMetadataProvider(backgroundTaskTimer, httpClient(), metadataURL);
provider.setParserPool(parserPool());
return provider;
}
I'm working by using a filtered network, thus the app is unable to retrieve that file.
There is a way to setup an HTTP Proxy (e.g. myproxy.eu:8080) in Spring Boot?
Alternatively, I could retrieve the XML file by using the HTTPS protocol, but I should properly setup the metadata provider in order to support an encrypted connection... How?
This is not something you can configure in spring boot, HttpClient is not using java variables.
Therefor you need to set the proxy on the httpClient manually:
HostConfiguration hostConfig = new HostConfiguration();
hostConfig.setProxyHost(new ProxyHost("your.proxy.host", 8080));
httpClient.setHostConfiguration(hostConfig);

Resources