My Spring Boot App was working fine with KeyCloak using a public certificate. But since my Keycloak has changed to a private certificate I get the following error:
"An I/O error occurred while reading from the JWK Set source: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target"
I've already got the private certificate but haven't figure out a successful way to set this up; If I make a curl to the Keycloack endpoint passing the certificate as parameter works fine.
curl --cacert mycertificate.crt -X GET \ 130 ↵
https://keycloak.address.bla/auth/realms/my-app/protocol/openid-connect/certs
I've tried to tweak my ResourceServerConfiguration class to use the certificate by generating a keystore.jks using keytool from my private certificate but I had no success; When I try the code bellow I get following error: "Cannot load keys from store: class path resource [keystore.jks]"
#Bean
#Primary
public JwtAccessTokenConverter createJwtAccessTokenConverter() {
var jwtAccessTokenConverter = new JwtAccessTokenConverter();
KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("/keystore.jks"), "changeit".toCharArray());
jwtAccessTokenConverter.setKeyPair(keyStoreKeyFactory.getKeyPair("myAlias"));
jwtAccessTokenConverter.setAccessTokenConverter(keycloakAccessTokenConverter);
return jwtAccessTokenConverter;
}
My application.properties
security.oauth2.resource.id=account
security.oauth2.resource.jwk.key-set-uri=${app.keycloak.api}/protocol/openid-connect/certs
The problem was solved by just importing the private certificate to my JVM. After importing the ".crt" file all my request were successful;
The command to import .crt file to JVM is as follow: keytool -importcert -file my_certificate.crt -noprompt -alias certificate_alias -storepass changeit -keystore $JAVA_HOME/lib/security/cacerts
Do you try setting the classloader?
KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("keystore.jks",this.getClass().getClassLoader()), "changeit".toCharArray());
Or you can use #Resource
#Value("classpath:keystore.jks")
Resource keystore;
...
KeyStoreKeyFactory key = new KeyStoreKeyFactory(this.keystore, passphrase);
Related
I want to connect to redis by using SSL. I set up host, port etc. but when i'm setting...
spring.redis.ssl=true
and when i run the application i got following error:
org.springframework.data.redis.RedisConnectionFailureException:
Unable to connect to Redis; nested exception is io.lettuce.core.RedisConnectionException: Unable to connect to
XXX:XXX at
org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory$ExceptionTranslatingConnectionProvider.translateException(LettuceConnectionFactory.java:1689)
~[spring-data-redis-2.5.7.jar:2.5.7]
Caused by: javax.net.ssl.SSLHandshakeException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException:
unable to find valid certification path to requested target at
java.base/sun.security.ssl.Alert.createSSLException(Alert.java:131)
~[na:na] at
java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:349)
~[na:na]
Actually my certificate (test.pem) is in resources folder in spring boot apllication project.
Where should i put the certificate file or how to set the path to this file?
I want to set it by application.yml or by java code.
This configuration works in my case:
#Configuration
#RequiredArgsConstructor
public class RedisSSLConfiguration {
#Value("${spring.redis.host}")
private String host;
#Value("${spring.redis.port}")
private int port;
#Value("${spring.redis.password}")
private String password;
#Value("${spring.redis.ssl:false}")
private boolean sslEnabled;
private final ResourceLoader resourceLoader;
#Bean
RedisConnectionFactory redisConnectionFactory() throws IOException {
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
redisStandaloneConfiguration.setHostName(host);
redisStandaloneConfiguration.setPort(port);
redisStandaloneConfiguration.setPassword(password);
LettuceClientConfiguration.LettuceClientConfigurationBuilder lettuceClientConfigurationBuilder =
LettuceClientConfiguration.builder();
if (sslEnabled){
SslOptions sslOptions = SslOptions.builder()
.trustManager(resourceLoader.getResource("classpath:redis.pem").getFile())
.build();
ClientOptions clientOptions = ClientOptions
.builder()
.sslOptions(sslOptions)
.protocolVersion(ProtocolVersion.RESP3)
.build();
lettuceClientConfigurationBuilder
.clientOptions(clientOptions)
.useSsl();
}
LettuceClientConfiguration lettuceClientConfiguration = lettuceClientConfigurationBuilder.build();
return new LettuceConnectionFactory(redisStandaloneConfiguration, lettuceClientConfiguration);
}
}
I have a springboot server running with https (for websocket connections), with following config:
application.properties
server.port: 9080
server.ssl.key-store: classpath:keystore.p12
server.ssl.key-store-password: XXXXXX
server.ssl.keyStoreType: PKCS12
server.ssl.keyAlias: XXXXXX
and web config
#Slf4j
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic", "/queue");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/greeting").setHandshakeHandler(new CustomHandshakeHandler())
.setAllowedOrigins("https://192.168.0.101:9000", "chrome-extension://ggnhohnkfcpcanfekomdkjffnfcjnjam").withSockJS();
}
}
The UI is a vue dev server running on port 9000, with following config:
vue.config.js
var fs = require('fs');
module.exports = {
"devServer": {
"port": 9000,
"https": {
"key": fs.readFileSync('./certs/privkey.pem'),
"cert": fs.readFileSync('./certs/cert.pem')
},
"proxy": {
"/": {
"target": "https://192.168.0.101:9080",
"ws": true,
"secure": false,
"changeOrigin": true
}
},
"overlay": {
"warnings": true,
"errors": true
}
},
"transpileDependencies": [
"vuetify"
],
publicPath: "./"
}
certs folder is at same level as src folder, and the key and certificates were generated using following steps:
Generate keystore:
keytool -genkey -alias tomcat -storetype PKCS12 -keyalg RSA -keysize 2048 -keystore keystore.p12 -validity 3650
Generate certificate in pem format from keystore
openssl pkcs12 -in keystore.p12 -nokeys -out cert.pem
Generate private key in pem format from keystore:
openssl pkcs12 -in keystore.p12 -nodes -nocerts -out privkey.pem
and in my vue file I am creating new connection using:
import SockJS from "sockjs-client";
import Stomp from "webstomp-client";
...
var socket = new SockJS("/greeting");
this.ws = Stomp.over(socket);
...
this.ws.send("/app/message", JSON.stringify({"id":12, "name":"dev"}), {});
My problem was even after adding configuration for https in both frontend and backend, I was getting net::ERR_CONNECTION_REFUSED on the UI side
The mistake I did was, in my web-config in setAllowedOrigins() I was still using http instead of https for the url of the ui dev server.
I know this isn't a question per say, but hopefully all these steps helps somebody
I have a Spring Boot Microservice where I am trying to invoke an external server which exposes an HTTPS REST Endpoint (TLS v1.2). I have been provided the server side certificate in .pem format.
I would like to implement this call using RestTemplate and use the provided certificate and verify the host name during the call.
I have tried to Google this and all the search results are trying to ignore the certificate and host name.
Can I have an example code snippet to implement this properly?
After some digging with different blogs and stackoverflow threads, following worked for me:
Create Rest Template:
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(new FileInputStream(ResourceUtils.getFile(clientKeyPath)), "".toCharArray());
SSLContext sslContext = SSLContextBuilder
.create()
.loadKeyMaterial(keyStore, null)
.loadTrustMaterial(ResourceUtils.getFile(keystorePath), keystorePassword.toCharArray())
.build();
SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext, new CustomHostnameVerifier());
HttpClient client = HttpClients
.custom()
.setSSLSocketFactory(sslConnectionSocketFactory)
.build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(client);
RestTemplate sslRestTemplate = new RestTemplate(requestFactory);
Implementation of CustomHostnameVerifier:
#Component
public class CustomHostnameVerifier implements HostnameVerifier {
#Value("${dns.name}")
private String dnsName;
#Override
public boolean verify(String hostname, SSLSession session) {
return hostname.equals(dnsName);
}
}
I wrote a 2-way authetication restful service cleint to consume a secured restful web service on port 8443 over https. Here are parts of application.properties:
trust.store=classpath:truststore.jks
trust.store.password=xyz123
key.store=classpath:keystore.jks
key.store.password=xyz123
Below are two ways to configure RestTemplate
#Configuration
public class SSLTemplate {
#Value("${key.store}")
private String keyStore;
#Value("${key.store.password}")
private String keyStorePassword;
#Value("${trust.store}")
private String trustStore;
#Value("${trust.store.password}")
private String trustStorePassword;
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) throws Exception {
SSLContext sslContext = SSLContextBuilder.create()
.loadKeyMaterial(ResourceUtils.getFile(keyStore), keyStorePassword.toCharArray(), keyStorePassword.toCharArray())
.loadTrustMaterial(ResourceUtils.getFile(trustStoreF), trustStorePassword.toCharArray())
.build();
...
}
#Configuration
public class SSLTemplate {
#Value("${key.store}")
private Resource keyStoreR;
#Value("${key.store.password}")
private String keyStorePassword;
#Value("${trust.store}")
private Resource trustStoreR;
#Value("${trust.store.password}")
private String trustStorePassword;
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) throws Exception {
SSLContext sslContext = SSLContextBuilder.create()
.loadKeyMaterial(keyStoreR.getURL(), keyStorePassword.toCharArray(), keyStorePassword.toCharArray())
.loadTrustMaterial(trustStoreR.getURL(), trustStorePassword.toCharArray())
.build();
...
}
When I run the app via bootRun or within Eclipse, both of them work.
But when I use jar launcer
java -jar app.jar
I got below exception.
Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiation
Exception: Failed to instantiate [org.springframework.web.client.RestTemplate]: Factory method 'restTemplate' threw exception;
nested exception is java.io.FileNotFoundException: URL cannot be resolved to absolute file path because it does not reside in the file system: jar:file:/C:/build/libs/app.jar!/BOOT-INF/classes!/truststore.jks
I have also tried
java -Djavax.net.ssl.trustStore=truststore.jks -Djavax.net.ssl.trustStorePassword=xyz123 -Djavax.net.ssl.keyStore=keystore.jks -Djavax.net.ssl.keyStorePassword=xyz123 -jar app.jar
and got the same exception. Any help will be really appreciated.
Try using resource.getInputStream() instead of resource.getFile() as, resource.getFile() in Spring tries to access a file system path but it can not access a path in your JAR
This link has a rich content and do take a look at Andy's answer here
Sample example:
Resource resource = resourceLoader.getResource("classpath:GeoLite2-Country.mmdb");
InputStream dbAsStream = resource.getInputStream();
and do you have use full path
-Djavax.net.ssl.trustStore=something/like/this/truststore.jks
I have created keys using below command
keytool -genkey -keyalg RSA -alias selfsigned -keystore keystore.jks -storepass password -validity 360 -keysize 2048
After the i have exposed HTTPS connetion using camel end points
public class HTTPSCamelEndPoint {
public Endpoint httpsConfig(CamelContext context) throws Exception
{
KeyStoreParameters ksp = new KeyStoreParameters();
ksp.setResource("C:\\Users\\sithamparamd\\keystore.jks");
ksp.setPassword("123456");
KeyManagersParameters kmp = new KeyManagersParameters();
kmp.setKeyStore(ksp);
kmp.setKeyPassword("password");
SSLContextParameters scp = new SSLContextParameters();
scp.setKeyManagers(kmp);
JettyHttpComponent jettyComponent =context.getComponent("jetty", JettyHttpComponent.class);
jettyComponent.setSslContextParameters(scp);
//jettyComponent.createEndpoint("jetty:https://192.168.16.98:4443/myservice");
return jettyComponent.createEndpoint("jetty:https://192.168.16.98:4443/myservice");
}
public static void main(String[] args) throws Exception {
HTTPSCamelEndPoint httpsCamelEndPoint= new HTTPSCamelEndPoint();
CamelContext camelContext=httpsCamelEndPoint.getContext();
final Endpoint endpoint=httpsCamelEndPoint.httpsConfig(camelContext);
System.out.println(endpoint);
camelContext.addRoutes(new RouteBuilder() {
#Override
public void configure() throws Exception {
// TODO Auto-generated method stub
from(endpoint).process(new Processor() {
public void process(Exchange arg0) throws Exception {
// TODO Auto-generated method stub
System.out.println("GOT THE MSG !!!!");
}
});
}
});
camelContext.start();
}
public CamelContext getContext()
{
CamelContext camelContext=new DefaultCamelContext();
JettyHttpComponent httpComponent=new JettyHttpComponent();
camelContext.addComponent("jetty", httpComponent);
return camelContext;
}
}
but when i access through the URL its showing as invalided certificate. Please tel me the reason for this and give the solution for over come this.
It's a warning, since you are using self-signed certificate that you generated is not trusted by the browser.
The warning will not occur when you use CA Certificate What are CA Certificates
You can suppress the warning by adding the certificate to the trusted root CA store Example
A self-signed certificate would not be recognised by the browser. Only CA signed certificate could be recognised.
You can set up a free trusted certificate with the Let's Encrypt project, this is the how-to tutorial.
And this is a wiki of CA.