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
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workkloads.

Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • A Systematic Approach for Java Software Upgrades
  • Building a Simple RAG Application With Java and Quarkus
  • Dust Actors and Large Language Models: An Application
  • Five Java Developer Must-Haves for Ultra-Fast Startup Solutions

Trending

  • The 4 R’s of Pipeline Reliability: Designing Data Systems That Last
  • The Modern Data Stack Is Overrated — Here’s What Works
  • Rethinking Recruitment: A Journey Through Hiring Practices
  • Segmentation Violation and How Rust Helps Overcome It
  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
DZone Core CORE ·
Updated Oct. 23, 21 · Tutorial
Likes (12)
Comment
Save
Tweet
Share
36.0K 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

  • A Systematic Approach for Java Software Upgrades
  • Building a Simple RAG Application With Java and Quarkus
  • Dust Actors and Large Language Models: An Application
  • Five Java Developer Must-Haves for Ultra-Fast Startup Solutions

Partner Resources

×

Comments
Oops! Something Went Wrong

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

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

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 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!