Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use p12 client certificate with spring feign client

I have a Spring Boot application that calls a remote service.

This remote web service provided me a p12 file that should authenticate my application.

How do I configure my feign client to use the p12 certificate ?


I've tried settings these properties:

-Djavax.net.ssl.keyStore=path_to_cert.p12 -Djavax.net.ssl.keyStorePassword=xxx -Djavax.net.ssl.keyStoreType=PKCS12

But it doesn't change anything, I still get this error:

sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
like image 309
BiAiB Avatar asked Mar 04 '23 09:03

BiAiB


2 Answers

I could finally manage to do it with a lot of blind trial and error.

The problem is, by default, the feign builder builds feign clients with null SSLSocketFactory:

org.springframework.cloud.openfeign.FeignClientsConfiguration#feignBuilder:

@Bean
@Scope("prototype")
@ConditionalOnMissingBean
public Feign.Builder feignBuilder(Retryer retryer) {
    return Feign.builder().retryer(retryer);
}

feign.Feign.Builder:

  public static class Builder {
    // ...
    private Client client = new Client.Default(null, null);

So, I had to define this bean in a @Configuration:

@Bean
@Profile({"prod", "docker"})
public Feign.Builder feignBuilder() {
    return Feign.builder()
        .retryer(Retryer.NEVER_RETRY)
        .client(new Client.Default(getSSLSocketFactory(), null));

with this method: (can't remember source)

SSLSocketFactory getSSLSocketFactory() {
    char[] allPassword = keyStorePassword.toCharArray();
    SSLContext sslContext = null;
    try {
        sslContext = SSLContextBuilder
            .create()
            .setKeyStoreType(keyStoreType)
            .loadKeyMaterial(ResourceUtils.getFile(keyStore), allPassword, allPassword)
            .build();
    } catch (Exception e) { /* *** */ }
    return sslContext.getSocketFactory();
}

Now, it works for me, I debugged though the feign client calls and the sslSocketFactory is correctly passed to the underlying connection.

like image 120
BiAiB Avatar answered Mar 05 '23 21:03

BiAiB


In case you wish to achieve the above effect programmatically without using keytool, you can do the following:

class CustomFeignConfiguration {

    private val log = Logger.getLogger(this.javaClass.name)

    @Value("\${client_p12_base64_encoded_string}")
    private val clientP12: String = ""

    @Value("\${client_p12_password}")
    private val clientP12Pass: String = ""

    @Bean
    fun feignClient(): Client {
        val sslSocketFactory= getSSLSocketFactory()
        log.info("CUSTOM FEIGN CLIENT CALLED")
        return Client.Default(sslSocketFactory, DefaultHostnameVerifier())
    }

    private fun getSSLSocketFactory(): SSLSocketFactory {
        val decoder = java.util.Base64.getDecoder()
        val p12 = decoder.decode(clientP12)
        val p12File = File("clientCer.p12")
        p12File.writeBytes(p12)

        try {
            val sslContext = SSLContexts
                .custom()
                .loadKeyMaterial(p12File, clientP12Pass.toCharArray(), clientP12Pass.toCharArray())
                .build()
            return sslContext.socketFactory
        } catch (exception: Exception) {
            throw RuntimeException(exception)
        }

    }
}

The FeignClient interface that is using the configuration has to load this specifically

@FeignClient(name = "client", configuration = [CustomFeignConfiguration::class], url = "\${url}")
interface Client {
  ....
  ....
}

The SSLContexts library can only use p12 certificates and we have to convert the certificates and keys in PEM format to the P12 format.

Create a p12 certificate from your PEM certificate and key using the following SSL command:

openssl pkcs12 -export -inkey domain.key -in domain.crt -out domain.p12

Please record the password that you enter after you run this command.

Convert this p12 certificate to a base64 string using the following command

base64 domain.p12 > domain.p12.base64

Convert this multiline string to a single line string using the following command:

tr -d "\n\r" < domain.p12.base64 > domain.p12.base64.singleline

Use the single line string from this command and the password that you recorded earlier in your application.properties.

like image 24
ksinkar Avatar answered Mar 05 '23 21:03

ksinkar