A. Boudoux
A. Boudoux

Reputation: 133

Using ssl certificate with feign

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

Answers (6)

Samuel Olufemi
Samuel Olufemi

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

meridbt
meridbt

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

hugoalexandremf
hugoalexandremf

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

M Aqib Naeem
M Aqib Naeem

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

Bertolt
Bertolt

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

A. Boudoux
A. Boudoux

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

Related Questions