DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • Improving Java Application Reliability with Dynatrace AI Engine
  • A Systematic Approach for Java Software Upgrades
  • Building a Simple RAG Application With Java and Quarkus
  • Dust Actors and Large Language Models: An Application

Trending

  • From APIs to Actions: Rethinking Back-End Design for Agents
  • Product-Led Software Delivery: Intelligent Platforms for DevOps at Scale
  • Zero-Downtime Deployments for Java Apps on Kubernetes
  • Docker Hardened Images Are Free Now — Here's What You Still Need to Build
  1. DZone
  2. Coding
  3. Java
  4. How to Change Certificates At Runtime Without Downtime

How to Change Certificates At Runtime Without Downtime

In this article, see how to change certificates during runtime without taking the application down

By 
Hakan Altındağ user avatar
Hakan Altındağ
·
Alireza C user avatar
Alireza C
·
Updated Oct. 23, 21 · Tutorial
Likes (12)
Comment
Save
Tweet
Share
37.4K Views

Join the DZone community and get the full member experience.

Join For Free

Certificates Are Always a Pain in the Production Environment!  

Security is one the most important part of any application these days especially the fact that most of the applications are running on a public cloud provider puts the security part on a higher priority. One of the ways applications are used to keep communications secure is through the certificate. The certificate is one of the concepts that is not as easy as other parts of software development. First, you should understand how a certificate plays in the security part to figure out how to incorporate it into your application security. Moreover, you need to know how to generate/issue a new certificate for your application. 

Unfortunately, certificate generation is not a one-time job and it has an expiration date. So, it means a new certificate should be replaced with the current certificate before the expiration date comes. In most cases, the certificate information is used in configurations of a deployed application on production. Therefore, you need to generate a new certificate and redeploy your application on production. This creates difficulties for software teams to see how they can handle this issue and justify the downtime in production if there is no rolling update mechanism. Lack of knowledge and documentation in projects often makes this operation highly error prune. Therefore, there is a high chance that even after a new certificate something fails on production unexpectedly because of misconfiguration. In this article, we are going to see how we can solve this issue without having downtime on production while using a single server and without any changes on the application level.

How to Update the Certificate on the Server With Zero Downtime

This example demonstrates a Java-based server with the widely used Spring-Boot, however, the configuration can also be applied to other servers.

Maven Dependencies

The following Maven dependencies will be used for the examples:

Client

XML
 




xxxxxxxxxx
1
14


 
1
<dependency>
2
    <groupId>io.github.hakky54</groupId>
3
    <artifactId>sslcontext-kickstart</artifactId>
4
</dependency>
5

          
6
<dependency>
7
    <groupId>org.slf4j</groupId>
8
    <artifactId>slf4j-simple</artifactId>
9
</dependency>
10

          
11
<dependency>
12
    <groupId>com.fasterxml.jackson.core</groupId>
13
    <artifactId>jackson-databind</artifactId>
14
</dependency>



Server

XML
 




xxxxxxxxxx
1
22


 
1
<dependency>
2
    <groupId>org.springframework.boot</groupId>
3
    <artifactId>spring-boot</artifactId>
4
</dependency>
5

          
6
<dependency>
7
    <groupId>org.springframework.boot</groupId>
8
    <artifactId>spring-boot-starter-web</artifactId>
9
    <exclusions>
10
        <exclusion>
11
            <groupId>org.springframework.boot</groupId>
12
            <artifactId>spring-boot-starter-tomcat</artifactId>
13
        </exclusion>
14
    </exclusions>
15
</dependency>
16

          
17
<dependency>
18
    <groupId>org.springframework.boot</groupId>
19
    <artifactId>spring-boot-starter-jetty</artifactId>
20
</dependency>
21

          
22
<dependency>
23
    <groupId>io.github.hakky54</groupId>
24
    <artifactId>sslcontext-kickstart-for-jetty</artifactId>
25
</dependency>



Spring Boot has an embedded Tomcat server that doesn't support the configuration which will be demonstrated within this example. The Tomcat server will be replaced by Jetty which has the ability to accept a custom SSL configuration object. 

Server Configuration

In this tutorial, we will demonstrate updating the server certificates through an HTTP call. It is also possible to change the certificates when the keystores are getting updates on the file system with a file listener, see here for a reference implementation: FilesBasedSslUpdateService

Let's start with the REST implementation by adding a basic rest controller to access the server:

Java
 




xxxxxxxxxx
1


 
1
@Controller
2
public class HelloWorldController {
3

          
4
    @GetMapping(value = "/api/hello", produces = MediaType.TEXT_PLAIN_VALUE)
5
    public ResponseEntity<String> hello() {
6
        return ResponseEntity.ok("Hello");
7
    }
8

          
9
}



The server is currently accessible on port 8080 with HTTP protocol. To secure it we need to adjust the server configuration. This can be done by creating an instance of ConfigurableServletWebServerFactory. It needs an SslContextFactory and a custom port. In this case, 8443 will be used.

Java
 




xxxxxxxxxx
1
13


 
1
@Bean
2
public ConfigurableServletWebServerFactory webServerFactory(SslContextFactory.Server sslContextFactory) {
3
    JettyServletWebServerFactory factory = new JettyServletWebServerFactory();
4

          
5
    JettyServerCustomizer jettyServerCustomizer = server -> {
6
        ServerConnector serverConnector = new ServerConnector(server, sslContextFactory);
7
        serverConnector.setPort(8443);
8
        server.setConnectors(new Connector[]{serverConnector});
9
    };
10
    factory.setServerCustomizers(Collections.singletonList(jettyServerCustomizer));
11

          
12
    return factory;
13
}



SslContextFactory of Jetty will be constructed with the following snippet:

Java
 




xxxxxxxxxx
1
16


 
1
@Bean
2
public SSLFactory sslFactory() {
3
    return SSLFactory.builder()
4
            .withSwappableIdentityMaterial()
5
            .withIdentityMaterial("/path/to/identity.jks", "secret".toCharArray())
6
            .withSwappableTrustMaterial()
7
            .withTrustMaterial("/path/to/truststore.jks", "secret".toCharArray())
8
            .withNeedClientAuthentication()
9
            .build();
10
}
11

          
12
@Bean
13
public SslContextFactory.Server sslContextFactory(SSLFactory sslFactory) {
14
    return JettySslUtils.forServer(sslFactory);
15
}



The SSLFactory will first use the identity.jks, which contains the server key pair, to create a KeyManager. It will also use the truststore.jks, which contains trusted certificates, to create a TrustManager. By default, it is not possible to update the server certificates at runtime without taking the server down and restarting it. However, this feature can be enabled by using the withSwappableIdentityMaterial and withSwappableTrustMaterial. These two options will wrap the actual KeyManager and TrustManager with a wrapper of the same type. This special wrapper class has the ability to swap the underlying KeyManager and TrustManager with a new one whenever needed. Most of the servers and also HTTP clients are using the base SSL configuration, such as SSLContext, SSLSocketFactory, SSLServerSocketFactory, and SSLEngine which will only hold the reference to the highest level of KeyManager and TrustManager, and therefore it is possible to hot-swap the internal one.

The next step is to make the KeyManager, TrustManager, and SSLSession available for an admin service that has the responsibility of taking new SSL material and swapping it with the existing SSL configuration.

Java
 




xxxxxxxxxx
1
14


 
1
@Bean
2
public X509ExtendedKeyManager keyManager(SSLFactory sslFactory) {
3
    return sslFactory.getKeyManager().orElseThrow();
4
}
5

          
6
@Bean
7
public X509ExtendedTrustManager trustManager(SSLFactory sslFactory) {
8
    return sslFactory.getTrustManager().orElseThrow();
9
}
10

          
11
@Bean
12
public SSLSessionContext serverSessionContext(SSLFactory sslFactory) {
13
    return sslFactory.getSslContext().getServerSessionContext();
14
}



Java
 




xxxxxxxxxx
1
23


 
1
@Service
2
public class SwappableSslService {
3

          
4
    private final SSLSessionContext sslSessionContext;
5
    private final X509ExtendedKeyManager swappableKeyManager;
6
    private final X509ExtendedTrustManager swappableTrustManager;
7

          
8
    public SwappableSslService(SSLSessionContext sslSessionContext,
9
                               X509ExtendedKeyManager swappableKeyManager,
10
                               X509ExtendedTrustManager swappableTrustManager) {
11

          
12
        this.sslSessionContext = sslSessionContext;
13
        this.swappableKeyManager = swappableKeyManager;
14
        this.swappableTrustManager = swappableTrustManager;
15
    }
16

          
17
    public void updateSslMaterials(X509ExtendedKeyManager keyManager, X509ExtendedTrustManager trustManager) {
18
        KeyManagerUtils.swapKeyManager(swappableKeyManager, keyManager);
19
        TrustManagerUtils.swapTrustManager(swappableTrustManager, trustManager);
20
        SSLSessionUtils.invalidateCaches(sslSessionContext);
21
    }
22

          
23
}



The SwappableSslService will take a new KeyManager and a new TrustManager. It will inject it into the existing SSL configuration which we have created in the previous snippet and the old one will be overridden. By cleaning the SSL session all new requests will use the new certificates. The next step would be an entry point to get new certificates, this might be a file listener or just a rest controller. In this example, we will use a basic rest controller which accepts an SSLUpdateRequest. We hide this endpoint behind the admin path.

Java
 




x


 
1
@RestController
2
public class AdminController {
3

          
4
    private final SwappableSslService sslService;
5

          
6
    public AdminController(SwappableSslService sslService) {
7
        this.sslService = sslService;
8
    }
9

          
10
    @PostMapping(value = "/admin/ssl", consumes = MediaType.APPLICATION_JSON_VALUE)
11
    public void updateKeyManager(@RequestBody SSLUpdateRequest request) throws IOException {
12
        try (InputStream keyStoreStream = new ByteArrayInputStream(request.getKeyStore());
13
             InputStream trustStoreStream = new ByteArrayInputStream(request.getTrustStore())) {
14

          
15
            KeyStore keyStore = KeyStoreUtils.loadKeyStore(keyStoreStream, request.getKeyStorePassword());
16
            X509ExtendedKeyManager keyManager = KeyManagerUtils.createKeyManager(keyStore, request.getKeyStorePassword());
17

          
18
            KeyStore trustStore = KeyStoreUtils.loadKeyStore(trustStoreStream, request.getTrustStorePassword());
19
            X509ExtendedTrustManager trustManager = TrustManagerUtils.createTrustManager(trustStore);
20

          
21
            sslService.updateSslMaterials(keyManager, trustManager);
22
        }
23
    }
24

          
25
}
49
}



Java
 




xxxxxxxxxx
1
23


 
1
public class SSLUpdateRequest {
2

          
3
    private byte[] keyStore;
4
    private char[] keyStorePassword;
5
    private byte[] trustStore;
6
    private char[] trustStorePassword;
7

          
8
    public SSLUpdateRequest() {}
9

          
10
    public SSLUpdateRequest(byte[] keyStore,
11
                            char[] keyStorePassword,
12
                            byte[] trustStore,
13
                            char[] trustStorePassword) {
14
        
15
        this.keyStore = keyStore;
16
        this.keyStorePassword = keyStorePassword;
17
        this.trustStore = trustStore;
18
        this.trustStorePassword = trustStorePassword;
19
    }
20
    
21
    // Getters and Setters
22

          
23
}



This basic admin controller has now the capability of receiving a new keyStore as a new server identity and a new trustStore as a byte array. It is able to translate these byte arrays into a KeyStore object and it will forward it to the SwappableSslService. The Server setup is done and when accessed through the browser at https://localhost:8443/api/hello we will get the server certificate as shown below. It will expire on the 28th of April 2021, so we need to update it!

Certificate Expiration Date Screenshot

Client Configuration

The client can be any type of application that is able to send an HTTP post request to the admin controller. It expects a JSON request which has the structure of the SSLUpdateRequest as shown earlier. In our case, we will use the default HTTP Client which is available from Java 11 onwards. The client also needs his own certificates to be able to communicate with the server as the server requires any client to authenticate based on their certificates. This is also called mutual authentication, a.k.a., two-way-ssl. If a client is not trusted by the server it cannot communicate with it. The admin SSL material will be loaded and it will be supplied to the HTTP Client. Next, the new server identity.jks and truststore will be loaded and it will be sent to the admin endpoint of the server.

Java
 




xxxxxxxxxx
1
33


 
1
public class App {
2

          
3
    private static final ObjectMapper objectMapper = new ObjectMapper();
4

          
5
    public static void main(String[] args) throws IOException, InterruptedException {
6
        char[] keyStorePassword = "secret".toCharArray();
7

          
8
        SSLFactory sslFactory = SSLFactory.builder()
9
                .withIdentityMaterial("keystores/admin/identity.jks", keyStorePassword)
10
                .withTrustMaterial("keystores/admin/truststore.jks", keyStorePassword)
11
                .build();
12

          
13
        HttpClient httpClient = HttpClient.newBuilder()
14
                .sslParameters(sslFactory.getSslParameters())
15
                .sslContext(sslFactory.getSslContext())
16
                .build();
17

          
18
        byte[] identity = App.class.getClassLoader().getResourceAsStream("keystores/server/identity.jks").readAllBytes();
19
        byte[] truststore = App.class.getClassLoader().getResourceAsStream("keystores/server/truststore.jks").readAllBytes();
20
        SSLUpdateRequest sslUpdateRequest = new SSLUpdateRequest(identity, keyStorePassword, truststore, keyStorePassword);
21

          
22
        String body = objectMapper.writeValueAsString(sslUpdateRequest);
23

          
24
        HttpRequest request = HttpRequest.newBuilder().POST(HttpRequest.BodyPublishers.ofString(body))
25
                .header("Content-Type", "application/json")
26
                .uri(URI.create("https://localhost:8443/admin/ssl"))
27
                .build();
28

          
29
        HttpResponse<Void> response = httpClient.send(request, HttpResponse.BodyHandlers.discarding());
30
        System.out.println(response.statusCode());
31
    }
32

          
33
}



After running the client it will print a 200 as a status code which means, I did it and it looks like everything went smooth! When we refresh our browser page we will see that the server certificate has changed, it still has the signed Root-CA but the new certificate is valid for another 10 years. It will expire on the 26th of January 2031!

If you haven't watched the video which was in the beginning, there is a demo in the video in which you can see how this solution works. 

Benefits of This Solution

This solution is helping to decouple the certificate implementation from the application layer and that can be controlled separately from the actual development pipeline. This means whenever any changes needed to be done on the part of the certificate, you only change that part without being waiting for a release to add that change. Most importantly with this solution, there will no downtime and end users would not notice the certificate replacement. This will be a huge achievement for applications where you do not have flexible slots for these kinds of changes.

Although this solution has been implemented with Java, you should be able to implement this concept in any programming language.

As usual, you'll find the sources over on GitHub. You can give it a try and we are looking forward to hearing your feedback!

application Java (programming language)

Opinions expressed by DZone contributors are their own.

Related

  • Improving Java Application Reliability with Dynatrace AI Engine
  • A Systematic Approach for Java Software Upgrades
  • Building a Simple RAG Application With Java and Quarkus
  • Dust Actors and Large Language Models: An Application

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook