aboutsummaryrefslogtreecommitdiffstats
path: root/security-utils/src/main/java/com/yahoo/security/AutoReloadingX509KeyManager.java
blob: be1940441d4b7c95722f9fcf8a14f4057197518b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.security;

import javax.net.ssl.SSLEngine;
import javax.net.ssl.X509ExtendedKeyManager;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.KeyStore;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.time.Duration;
import java.util.Arrays;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * A {@link X509ExtendedKeyManager} that reloads the certificate and private key from file regularly.
 *
 * @author bjorncs
 */
public class AutoReloadingX509KeyManager extends X509ExtendedKeyManager implements AutoCloseable {

    public static final String CERTIFICATE_ALIAS = "default";

    private static final Duration UPDATE_PERIOD = Duration.ofHours(1);

    private static final Logger log = Logger.getLogger(AutoReloadingX509KeyManager.class.getName());

    private final MutableX509KeyManager mutableX509KeyManager;
    private final ScheduledExecutorService scheduler;
    private final Path privateKeyFile;
    private final Path certificatesFile;

    private AutoReloadingX509KeyManager(Path privateKeyFile, Path certificatesFile) {
        this(privateKeyFile, certificatesFile, createDefaultScheduler());
    }

    AutoReloadingX509KeyManager(Path privateKeyFile, Path certificatesFile, ScheduledExecutorService scheduler) {
            this.privateKeyFile = privateKeyFile;
            this.certificatesFile = certificatesFile;
            this.scheduler = scheduler;
            this.mutableX509KeyManager = new MutableX509KeyManager(createKeystore(privateKeyFile, certificatesFile), new char[0]);
            scheduler.scheduleAtFixedRate(
                    new KeyManagerReloader(), UPDATE_PERIOD.getSeconds()/*initial delay*/, UPDATE_PERIOD.getSeconds(), TimeUnit.SECONDS);
        }

    public static AutoReloadingX509KeyManager fromPemFiles(Path privateKeyFile, Path certificatesFile) {
        return new AutoReloadingX509KeyManager(privateKeyFile, certificatesFile);
    }

    public X509CertificateWithKey getCurrentCertificateWithKey() {
        X509ExtendedKeyManager manager = mutableX509KeyManager.currentManager();
        X509Certificate[] certificateChain = manager.getCertificateChain(CERTIFICATE_ALIAS);
        PrivateKey privateKey = manager.getPrivateKey(CERTIFICATE_ALIAS);
        return new X509CertificateWithKey(Arrays.asList(certificateChain), privateKey);
    }

    private static KeyStore createKeystore(Path privateKey, Path certificateChain) {
        try {
            return KeyStoreBuilder.withType(KeyStoreType.PKCS12)
                    .withKeyEntry(
                            CERTIFICATE_ALIAS,
                            KeyUtils.fromPemEncodedPrivateKey(new String(Files.readAllBytes(privateKey), StandardCharsets.UTF_8)),
                            X509CertificateUtils.certificateListFromPem(new String(Files.readAllBytes(certificateChain), StandardCharsets.UTF_8)))
                    .build();
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
    }

    private static ScheduledExecutorService createDefaultScheduler() {
        return Executors.newSingleThreadScheduledExecutor(runnable -> {
            Thread thread = new Thread(runnable, "auto-reloading-x509-key-manager");
            thread.setDaemon(true);
            return thread;
        });
    }

    private class KeyManagerReloader implements Runnable {
        @Override
        public void run() {
            try {
                log.log(Level.FINE, () -> String.format("Reloading key and certificate chain (private-key='%s', certificates='%s')", privateKeyFile, certificatesFile));
                mutableX509KeyManager.updateKeystore(createKeystore(privateKeyFile, certificatesFile), new char[0]);
            } catch (Throwable t) {
                log.log(Level.SEVERE,
                        String.format("Failed to load X509 key manager (private-key='%s', certificates='%s'): %s",
                                      privateKeyFile, certificatesFile, t.getMessage()),
                        t);
            }
        }
    }

    @Override
    public void close() {
        try {
            scheduler.shutdownNow();
            scheduler.awaitTermination(5, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    //
    // Methods from X509ExtendedKeyManager
    //

    @Override
    public String[] getServerAliases(String keyType, Principal[] issuers) {
        return mutableX509KeyManager.getServerAliases(keyType, issuers);
    }

    @Override
    public String[] getClientAliases(String keyType, Principal[] issuers) {
        return mutableX509KeyManager.getClientAliases(keyType, issuers);
    }

    @Override
    public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
        return mutableX509KeyManager.chooseServerAlias(keyType, issuers, socket);
    }

    @Override
    public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) {
        return mutableX509KeyManager.chooseClientAlias(keyType, issuers, socket);
    }

    @Override
    public String chooseEngineServerAlias(String keyType, Principal[] issuers, SSLEngine engine) {
        return mutableX509KeyManager.chooseEngineServerAlias(keyType, issuers, engine);
    }

    @Override
    public String chooseEngineClientAlias(String[] keyType, Principal[] issuers, SSLEngine engine) {
        return mutableX509KeyManager.chooseEngineClientAlias(keyType, issuers, engine);
    }

    @Override
    public X509Certificate[] getCertificateChain(String alias) {
        return mutableX509KeyManager.getCertificateChain(alias);
    }

    @Override
    public PrivateKey getPrivateKey(String alias) {
        return mutableX509KeyManager.getPrivateKey(alias);
    }

}