Reputation: 133
I'm trying to acess a apllication secured by https, i have a p12 certificate (already imported as .cer into cacerts folder of my jdk).
I already tried this tutorial to no success: https://dzone.com/articles/ssl-based-feignclient-example-in-java-microcervice
And also i'm using part of this solution: How to use p12 client certificate with spring feign client
Debuging the ssl connection i get the following error:
javax.net.ssl|ERROR|25|http-nio-auto-1-exec-1|2021-01-26 16:56:34.789 BRT|TransportContext.java:317|Fatal (HANDSHAKE_FAILURE): Received fatal alert: handshake_failure
My current feign config class
@Bean
@ConditionalOnMissingBean
public Feign.Builder feignBuilder(Retryer retryer) {
return Feign.builder().retryer(retryer);
}
@Bean
public Feign.Builder feignBuilder() {
return Feign.builder()
.retryer(Retryer.NEVER_RETRY)
.client(new Client.Default(getSSLSocketFactory(), null));
}
private SSLSocketFactory getSSLSocketFactory() {
String keyStorePassword = "myPassword";
char[] allPassword = keyStorePassword.toCharArray();
SSLContext sslContext = null;
try {
sslContext = SSLContextBuilder
.create()
.setKeyStoreType("PKCS12")
.loadKeyMaterial(ResourceUtils.getFile("keypath"), allPassword, allPassword)
.build();
} catch (Exception e) { }
return sslContext.getSocketFactory();
}
In the debbuging section of the code i can see my certificate is there, but still my java is getting the handshake error. I'm new to ssl concept and possible did some config wrong.
One last note, when in the feign config class and set the trust store and password by System
System.setProperty("javax.net.ssl.trustStorePassword", "pass");
System.setProperty("javax.net.ssl.trustStore", "pathtocerth.p12");
The error change to this:
javax.net.ssl|ERROR|25|http-nio-auto-1-exec-1|2021-01-26 16:48:58.551 BRT|TransportContext.java:317|Fatal (CERTIFICATE_UNKNOWN): PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
Upvotes: 9
Views: 35917
Reputation: 1055
This solution worked for me :
The bean loads the certificate into the application context and makes the certificate available to the feign client at run time
I leveraged on this libraby https://github.com/Hakky54/sslcontext-kickstart and x509certificate
so I added this to my pom file
<dependency> <groupId>io.github.hakky54</groupId> <artifactId>sslcontext-kickstart</artifactId> <version>9.0.0</version> </dependency> <dependency> <dependency> <groupId>org.wso2.carbon.extension.identity.x509certificate</groupId> <artifactId>x509certificate-validation-feature</artifactId> <version>1.0.3</version> <type>pom</type> </dependency>
import okhttp3.OkHttpClient;
public OkHttpClient sslClient() throws Exception {
return new OkHttpClient.Builder()
.sslSocketFactory(createSSLSocketFactory(), getTrustManager(KeyStore.getInstance("PKCS12")))
.hostnameVerifier((hostname, session) -> true)
.build();
}
private X509TrustManager getTrustManager(KeyStore keyStore)
throws NoSuchAlgorithmException, KeyStoreException {
TrustManagerFactory tmf =
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(keyStore);
return (X509TrustManager) tmf.getTrustManagers()[0];
}
the usual createSSLSocketFactory implementation in the question will work.
Upvotes: 2
Reputation: 405
Hello there from 2024!
Spring Boot 3.1 introduces convenient approach of configuring SSL context using Bundles.
An elementary example for feign client:
// MyClient.java
@FeignClient(name = "my-client", url = "${my-client.url}",
configuration = MyClientConfig.class)
public interface MyClient { /* methods */ }
// MyClientConfig.java
public class MyClientConfig {
@Bean
public Client client(SslBundles sslBundles) {
SSLContext sslContext = sslBundles.getBundle("my-client").createSslContext();
return new Client.Default(sslContext.getSocketFactory(), HttpsURLConnection.getDefaultHostnameVerifier());
}
}
# application.yaml
spring:
ssl:
bundle:
jks:
my-client:
keystore:
location: /path-to-keystore/keystore.p12
password: secret
type: PKCS12
truststore:
location: /path-to-truststore/truststore.p12
password: secret
type: PKCS12
Bundles support both store-based (JKS or PKCS12) under jks
node and key-based configuration under pem
node
See details in this Spring blog post
Upvotes: 3
Reputation: 326
This issue has to do with the fact that this specific cert is not included in the trusted certificates store. For development purposes we can create an accepting TrustStrategy like this:
TrustStrategy acceptingTrustStrategy = (cert, authType) -> true;
and then using it like this directly in the SSLContextBuilder:
.loadTrustMaterial(null, acceptingTrustStrategy)
Upvotes: 0
Reputation: 197
In case someone is facing the same issue in 2023. I configured the Client as a Bean in Feign Configurations and set SSL Socket Factory details there.
Gradle Imports:
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign:3.1.6'
implementation 'io.github.openfeign:feign-httpclient:12.3'
Client Bean in Feign Configuration:
@Bean
public Client feignClient() throws Exception {
log.info("Configuring SSL Context for Feign Client");
return new Client.Default(createSSLContext(), SSLConnectionSocketFactory.getDefaultHostnameVerifier());
}
and created SSL Socket Factory for from resource files as follow:
private SSLSocketFactory createSSLContext() throws Exception {
String trustStorePath = "classpath:cacerts"
String keyStorePath = "classpath:client-key.pfx"
log.info("Trust Store for Feign Client: " + trustStorePath);
log.info("Key Store for Feign Client: " + keyStorePath);
KeyStore keyStore = KeyStore.getInstance("PKCS12"); // PKCS12 for PFX files. Change this to 'JKS' if you are using java keystore
keyStore.load(new FileInputStream(ResourceUtils.getFile(keyStorePath)), keyStorePassword.toCharArray());
SSLContext context = SSLContextBuilder.create()
.loadTrustMaterial(ResourceUtils.getFile(trustStorePath), trustStorePassword.toCharArray())
.loadKeyMaterial(keyStore, keyStorePassword.toCharArray())
.build();
return context.getSocketFactory();
}
Upvotes: 5
Reputation: 1045
You can add the key (and additional trust store optionally) directly via application properties in Spring.
server:
ssl:
#trust-store: path_to_your_truststore
#trust-store-password: changeit
#trust-store-type: JKS
#trust-store-provider: SUN
key-store: path_to_your_keystore
key-store-password: changeit
key-alias: 1
key-store-type: PKCS12
key-store-provider: SUN
key-password: changeit
protocol: TLS
To identify the key-alias, key-store-type and key-store-provider, you can use the following command:
keytool -list -keystore path_to_keystore
But if you just want to turn off hostname verification, then you don't need above. You can configure it for feign httpclient by adding these properties:
feign.httpclient.disableSslValidation=true
and this maven dependency:
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
No need for okhttp.
Upvotes: 0
Reputation: 133
I'm answering myself since i found out the problem. Case someone face the same issue the solution is quite simple.
Inside application properties you need to add these properties:
feign.httpclient.disableSslValidation=true
feign.httpclient.enabled=false
feign.okhttp.enabled=true
From
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
<version>9.4.0</version>
</dependency>
Set the feign configuration class
@Configuration
public class CustomFeignConfiguration {
@Bean
public void Config() {
System.setProperty("javax.net.ssl.keyStoreType", "PKCS12");
System.setProperty("javax.net.ssl.keyStore", "path to p12");
System.setProperty("javax.net.ssl.keyStorePassword", "key password");
}
And use the feign config in the feign request
@FeignClient(name = "foo", url = "https://foo/foo",
configuration = CustomFeignConfiguration.class)
public interface IFeingRequest {
request here
}
With this solution I did NOT need to convert the certificate and store it into java trust store.
Upvotes: 1