Spring Boot | How to update Truststore dynamically?

孤人 提交于 2019-12-22 05:23:38

问题


I have currently implemented mutual TLS in my Spring Boot application and I am doing it programatically, like so:

@Bean
public ServletWebServerFactory servContainer() {
    TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
    TomcatConnectorCustomizer tomcatConnectorCustomizer = new TomcatConnectorCustomizer() {
        @Override
        public void customize(Connector connector) {
            connector.setPort(8443);
            connector.setScheme("https");
            connector.setSecure(true);
            Http11NioProtocol protocol = (Http11NioProtocol) connector.getProtocolHandler();

            protocol.setSSLEnabled(true);
            protocol.setKeystoreType("PKCS12");
            protocol.setKeystoreFile(keystorePath);
            protocol.setKeystorePass(keystorePass);
            //client must be authenticated (the cert he sends should be in our trust store)
            protocol.setSSLVerifyClient(Boolean.toString(true));
            protocol.setTruststoreFile(truststorePath);
            protocol.setTruststorePass(truststorePass);
            protocol.setKeyAlias("APP");
        }
    };
    tomcat.addConnectorCustomizers(tomcatConnectorCustomizer);
    return tomcat;
}

This is working fine and as expected. But I have a requirement where I need to update the trust store during run time (For Ex. when a @getmapping endpoint is invoked).

Specifically, I need to add a new certificate to the TrustStore without stopping/restarting the application. So I will have to somehow modify the in-memory trust store of my application.

How can I do this?

Update:

I tried to add a bean dynamically which adds a new Trust Manager to the SslContext, but this does not work.

@GetMapping("/register")
public String Register() throws Exception {
    ConfigurableApplicationContext configContext = (ConfigurableApplicationContext) appContext;
    ConfigurableListableBeanFactory beanRegistry = configContext.getBeanFactory();
    SSLContext sslContext = getSSLContext();
    beanRegistry.registerSingleton("sslContext", sslContext);
    return "okay";
}


public  SSLContext getSSLContext() throws Exception {
    TrustManager[] trustManagers = new TrustManager[] {
            new ReloadableX509TrustManager(truststoreNewPath)
    };
    SSLContext sslContext = SSLContext.getInstance("SSL");
    sslContext.init(null, trustManagers, null);
    SSLContext.setDefault(sslContext);
    return sslContext;
}

I also tried to invoke the above getSSLContext() as a @bean. Which did not work too.

Update 2 :

My current solutions are based on these links, which are for Java but I'm not sure how to implement them in my Spring Application.

  • https://jcalcote.wordpress.com/2010/06/22/managing-a-dynamic-java-trust-store/

I'm almost there:

I have found a solution which describes exactly how to have a dynamic trust store. But I am not able to figure out how to reload the trust store during run time. Say for example when a GET endpoint is invoked.

Client Certificate authentication without local truststore I have a list of Certificates, I just need to know how to invoke the ReloadableX509TrustManager's addCertificates() method.


回答1:


First, make your ReloadableX509TrustManager a managed bean - eg annotate with @Component

@Component
class ReloadableX509TrustManager 
    implements X509TrustManager {
.....
    public ReloadableX509TrustManager(@Value("someValueFromAppConfig")String tspath){....}
.....

Second, use it in your controller instead of creating new one eg

@GetMapping("/register")
public String Register() throws Exception {
    ConfigurableApplicationContext configContext = (ConfigurableApplicationContext) appContext;
    ConfigurableListableBeanFactory beanRegistry = configContext.getBeanFactory();
    SSLContext sslContext = getSSLContext();
    beanRegistry.registerSingleton("sslContext", sslContext);
    return "okay";
}

@Autowired private ReloadableX509TrustManager reloadableManager;
public  SSLContext getSSLContext() throws Exception {
    TrustManager[] trustManagers = new TrustManager[] {
            reloadableManager
    };
    SSLContext sslContext = SSLContext.getInstance("SSL");
    sslContext.init(null, trustManagers, null);
    SSLContext.setDefault(sslContext);
    return sslContext;
}

Thrid, follow article to get to know how to "reload" that trust manager. It can be done by changing most of methods to package protected and invoke it from some sort of sertificate service - or make it public and call directly. The choice is yours.




回答2:


Update 2 :

My current solutions are based on these links, which are for Java but I'm not sure how to implement them in my Spring Application.

https://jcalcote.wordpress.com/2010/06/22/managing-a-dynamic-java-trust-store/

Spring or not, your main goal doesn't require constraints to the applicative technology in a broad sense, the important thing is that your application (Client) it will be able to load new certificates into the client at any point during runtime and make changes on-the-fly. Let's say that to make what has been said, we could implement our logic in one of our class (TrustManager) implementing X509TrustManager.

Implementing the trust manager in a Client

The goal here is to tell the runtime that you want to use this new class to verify certificates. We have to instantiate a new SSLContext with our TrustManager and use this to specify a new SSLSocketFactory.

try {
    String trustStorePath = "path to a truststore that you have";
    String trustStorePassword = "password of trustStore";
    String defaultTrustStore = "path to default truststore";

    // Initialize the new trustManager with the default trust store
    TrustManager trustManager = new TrustManager(defaultTrustStore);

    // Load the new Keystore and decrypt it
    KeyStore ks = KeyStore.getInstance("JKS");
    ks.load(new FileInputStream(trustStorePath), trustStorePassword.toCharArray());

    // Add all of the certficates in the truststore and add them to the trust manager
    Enumeration<String> enumerator = ks.aliases();
    ArrayList<Certificate> certs = new ArrayList<>();
    while (enumerator.hasMoreElements()) {
        String currentAlias = enumerator.nextElement();
        certs.add(ks.getCertificate(currentAlias));
    }
    trustManager.addCertificates(certs);

    // Initialize the SSLContext and add it to the client conduit.
    SSLContext sc = SSLContext.getInstance("SSL");
    sc.init(null, new TrustManager[] {trustManager}, null);

    // Set the new TrustManager in the client.
    HTTPConduit httpConduit = (HTTPConduit) ClientProxy.getClient(service).getConduit();
    TLSClientParameters tlsCP = new TLSClientParameters(); 
    httpConduit.setTlsClientParameters(tlsCP);
    tlsCP.setSSLSocketFactory(sc.getSocketFactory());
} catch (Exception e) {
    e.printStackTrace();
}

If you need of a reference, here you'll find it.




回答3:


I also came through the same scenario where I have to update the TrustManager of my spring Boot App on the fly.

  1. I uploaded the certificate to my default cacert file(i.e Default SSL Context TrustManager) - This will take care of the connections establishing through default SSLContext.
  2. But I also have two different REST Template Beans, One for establishing 1way/Mutual SSL Connection and another for 2Way SSL Connection. Since these Beans got initialized during the startup
    • changing the default TrustManager will not update the TrustManager of REST Template.
    • For this, I updated the RestTemplate Bean with the new Request Factory dynamically which solved my issue.


来源:https://stackoverflow.com/questions/57143673/spring-boot-how-to-update-truststore-dynamically

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!