diff options
author | Bjørn Christian Seime <bjorncs@verizonmedia.com> | 2019-02-07 15:45:43 +0100 |
---|---|---|
committer | Bjørn Christian Seime <bjorncs@verizonmedia.com> | 2019-02-14 16:25:11 +0100 |
commit | 9534bd26aeb71de647367ff466824b755e7c1747 (patch) | |
tree | f2648f224b2669e949a16156c50308c8a3d45993 | |
parent | fbfdd5d9515f4e244dc5f930ef5748b5df66cdcb (diff) |
Misc changes to TlsContext and its implementations
- Add methods to retrieve underlying SSLContext and SSLParameters
- Add createSslEngine() overload with peer host and port
- Remove constructor DefaultTlsContext constructor taking path to config file.
- Resolve valid ciphers and protcols in constructor.
- Use mutual x509 key/trust manager in ReloadingTlsContext
3 files changed, 147 insertions, 49 deletions
diff --git a/security-utils/src/main/java/com/yahoo/security/tls/DefaultTlsContext.java b/security-utils/src/main/java/com/yahoo/security/tls/DefaultTlsContext.java index 473e50bc128..c9c326df9ed 100644 --- a/security-utils/src/main/java/com/yahoo/security/tls/DefaultTlsContext.java +++ b/security-utils/src/main/java/com/yahoo/security/tls/DefaultTlsContext.java @@ -7,7 +7,7 @@ import com.yahoo.security.tls.policy.AuthorizedPeers; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; -import java.nio.file.Path; +import javax.net.ssl.SSLParameters; import java.security.PrivateKey; import java.security.cert.X509Certificate; import java.util.Arrays; @@ -38,7 +38,8 @@ public class DefaultTlsContext implements TlsContext { private static final Logger log = Logger.getLogger(DefaultTlsContext.class.getName()); private final SSLContext sslContext; - private final List<String> acceptedCiphers; + private final String[] validCiphers; + private final String[] validProtocols; public DefaultTlsContext(List<X509Certificate> certificates, PrivateKey privateKey, @@ -46,50 +47,77 @@ public class DefaultTlsContext implements TlsContext { AuthorizedPeers authorizedPeers, AuthorizationMode mode, List<String> acceptedCiphers) { - this.sslContext = createSslContext(certificates, privateKey, caCertificates, authorizedPeers, mode); - this.acceptedCiphers = acceptedCiphers; + this(createSslContext(certificates, privateKey, caCertificates, authorizedPeers, mode), + acceptedCiphers); } - public DefaultTlsContext(Path tlsOptionsConfigFile, AuthorizationMode mode) { - TransportSecurityOptions options = TransportSecurityOptions.fromJsonFile(tlsOptionsConfigFile); - this.sslContext = createSslContext(options, mode); - this.acceptedCiphers = options.getAcceptedCiphers(); - } - @Override - public SSLEngine createSslEngine() { - SSLEngine sslEngine = sslContext.createSSLEngine(); - restrictSetOfEnabledCiphers(sslEngine, acceptedCiphers); - restrictTlsProtocols(sslEngine); - sslEngine.setNeedClientAuth(true); - return sslEngine; + public DefaultTlsContext(SSLContext sslContext, List<String> acceptedCiphers) { + this.sslContext = sslContext; + this.validCiphers = getAllowedCiphers(sslContext, acceptedCiphers); + this.validProtocols = getAllowedProtocols(sslContext); } - private static void restrictSetOfEnabledCiphers(SSLEngine sslEngine, List<String> acceptedCiphers) { - String[] validCipherSuites = Arrays.stream(sslEngine.getSupportedCipherSuites()) + + private static String[] getAllowedCiphers(SSLContext sslContext, List<String> acceptedCiphers) { + String[] supportedCipherSuites = sslContext.getSupportedSSLParameters().getCipherSuites(); + String[] validCipherSuites = Arrays.stream(supportedCipherSuites) .filter(suite -> ALLOWED_CIPHER_SUITES.contains(suite) && (acceptedCiphers.isEmpty() || acceptedCiphers.contains(suite))) .toArray(String[]::new); if (validCipherSuites.length == 0) { throw new IllegalStateException( String.format("None of the allowed cipher suites are supported " + "(allowed-cipher-suites=%s, supported-cipher-suites=%s, accepted-cipher-suites=%s)", - ALLOWED_CIPHER_SUITES, List.of(sslEngine.getSupportedCipherSuites()), acceptedCiphers)); + ALLOWED_CIPHER_SUITES, List.of(supportedCipherSuites), acceptedCiphers)); } - log.log(Level.FINE, () -> String.format("Allowed cipher suites that are supported: %s", Arrays.toString(validCipherSuites))); - sslEngine.setEnabledCipherSuites(validCipherSuites); + log.log(Level.FINE, () -> String.format("Allowed cipher suites that are supported: %s", List.of(validCipherSuites))); + return validCipherSuites; } - private static void restrictTlsProtocols(SSLEngine sslEngine) { - String[] validProtocols = Arrays.stream(sslEngine.getSupportedProtocols()) + private static String[] getAllowedProtocols(SSLContext sslContext) { + String[] supportedProtocols = sslContext.getSupportedSSLParameters().getProtocols(); + String[] validProtocols = Arrays.stream(supportedProtocols) .filter(ALLOWED_PROTOCOLS::contains) .toArray(String[]::new); if (validProtocols.length == 0) { throw new IllegalArgumentException( String.format("None of the allowed protocols are supported (allowed-protocols=%s, supported-protocols=%s)", - ALLOWED_PROTOCOLS, Arrays.toString(sslEngine.getSupportedProtocols()))); + ALLOWED_PROTOCOLS, List.of(supportedProtocols))); } - log.log(Level.FINE, () -> String.format("Allowed protocols that are supported: %s", Arrays.toString(validProtocols))); - sslEngine.setEnabledProtocols(validProtocols); + log.log(Level.FINE, () -> String.format("Allowed protocols that are supported: %s", List.of(validProtocols))); + return validProtocols; + } + + @Override + public SSLContext context() { + return sslContext; + } + + @Override + public SSLParameters parameters() { + return createSslParameters(); + } + + @Override + public SSLEngine createSslEngine() { + SSLEngine sslEngine = sslContext.createSSLEngine(); + sslEngine.setSSLParameters(createSslParameters()); + return sslEngine; + } + + @Override + public SSLEngine createSslEngine(String peerHost, int peerPort) { + SSLEngine sslEngine = sslContext.createSSLEngine(peerHost, peerPort); + sslEngine.setSSLParameters(createSslParameters()); + return sslEngine; + } + + private SSLParameters createSslParameters() { + SSLParameters newParameters = sslContext.getDefaultSSLParameters(); + newParameters.setCipherSuites(validCiphers); + newParameters.setProtocols(validProtocols); + newParameters.setNeedClientAuth(true); + return newParameters; } private static SSLContext createSslContext(List<X509Certificate> certificates, @@ -110,16 +138,5 @@ public class DefaultTlsContext implements TlsContext { return builder.build(); } - private static SSLContext createSslContext(TransportSecurityOptions options, AuthorizationMode mode) { - SslContextBuilder builder = new SslContextBuilder(); - options.getCertificatesFile() - .ifPresent(certificates -> builder.withKeyStore(options.getPrivateKeyFile().get(), certificates)); - options.getCaCertificatesFile().ifPresent(builder::withTrustStore); - if (mode != AuthorizationMode.DISABLE) { - options.getAuthorizedPeers().ifPresent( - authorizedPeers -> builder.withTrustManagerFactory(new PeerAuthorizerTrustManagersFactory(authorizedPeers, mode))); - } - return builder.build(); - } } diff --git a/security-utils/src/main/java/com/yahoo/security/tls/ReloadingTlsContext.java b/security-utils/src/main/java/com/yahoo/security/tls/ReloadingTlsContext.java index 5add13e067d..ed5d893f6dc 100644 --- a/security-utils/src/main/java/com/yahoo/security/tls/ReloadingTlsContext.java +++ b/security-utils/src/main/java/com/yahoo/security/tls/ReloadingTlsContext.java @@ -1,13 +1,28 @@ // Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.security.tls; +import com.yahoo.security.KeyStoreBuilder; +import com.yahoo.security.KeyStoreType; +import com.yahoo.security.KeyUtils; +import com.yahoo.security.SslContextBuilder; +import com.yahoo.security.X509CertificateUtils; +import com.yahoo.security.tls.authz.PeerAuthorizerTrustManager; + +import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.X509ExtendedTrustManager; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; import java.nio.file.Path; +import java.security.KeyStore; +import java.security.cert.X509Certificate; import java.time.Duration; +import java.util.List; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; import java.util.logging.Level; import java.util.logging.Logger; @@ -23,8 +38,9 @@ public class ReloadingTlsContext implements TlsContext { private static final Logger log = Logger.getLogger(ReloadingTlsContext.class.getName()); private final Path tlsOptionsConfigFile; - private final AuthorizationMode mode; - private final AtomicReference<TlsContext> currentTlsContext; + private final TlsContext tlsContext; + private final MutableX509TrustManager trustManager = new MutableX509TrustManager(); + private final MutableX509KeyManager keyManager = new MutableX509KeyManager(); private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(runnable -> { Thread thread = new Thread(runnable, "tls-context-reloader"); @@ -34,19 +50,77 @@ public class ReloadingTlsContext implements TlsContext { public ReloadingTlsContext(Path tlsOptionsConfigFile, AuthorizationMode mode) { this.tlsOptionsConfigFile = tlsOptionsConfigFile; - this.mode = mode; - this.currentTlsContext = new AtomicReference<>(new DefaultTlsContext(tlsOptionsConfigFile, mode)); - this.scheduler.scheduleAtFixedRate(new SslContextReloader(), + TransportSecurityOptions options = TransportSecurityOptions.fromJsonFile(tlsOptionsConfigFile); + reloadCryptoMaterial(options, trustManager, keyManager); + this.tlsContext = createDefaultTlsContext(options, mode, trustManager, keyManager); + this.scheduler.scheduleAtFixedRate(new CryptoMaterialReloader(), UPDATE_PERIOD.getSeconds()/*initial delay*/, UPDATE_PERIOD.getSeconds(), TimeUnit.SECONDS); } - @Override - public SSLEngine createSslEngine() { - return currentTlsContext.get().createSslEngine(); + private static void reloadCryptoMaterial(TransportSecurityOptions options, + MutableX509TrustManager trustManager, + MutableX509KeyManager keyManager) { + if (options.getCaCertificatesFile().isPresent()) { + trustManager.updateTruststore(loadTruststore(options.getCaCertificatesFile().get())); + } else { + trustManager.useDefaultTruststore(); + } + + if (options.getPrivateKeyFile().isPresent() && options.getCertificatesFile().isPresent()) { + keyManager.updateKeystore(loadKeystore(options.getPrivateKeyFile().get(), options.getCertificatesFile().get()), new char[0]); + } else { + keyManager.useDefaultKeystore(); + } } + private static KeyStore loadTruststore(Path caCertificateFile) { + try { + List<X509Certificate> caCertificates = X509CertificateUtils.certificateListFromPem(Files.readString(caCertificateFile)); + KeyStoreBuilder trustStoreBuilder = KeyStoreBuilder.withType(KeyStoreType.PKCS12); + for (int i = 0; i < caCertificates.size(); i++) { + trustStoreBuilder.withCertificateEntry("cert-" + i, caCertificates.get(i)); + } + return trustStoreBuilder.build(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + private static KeyStore loadKeystore(Path privateKeyFile, Path certificatesFile) { + try { + return KeyStoreBuilder.withType(KeyStoreType.PKCS12) + .withKeyEntry( + "default", + KeyUtils.fromPemEncodedPrivateKey(Files.readString(privateKeyFile)), + X509CertificateUtils.certificateListFromPem(Files.readString(certificatesFile))) + .build(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + private static DefaultTlsContext createDefaultTlsContext(TransportSecurityOptions options, + AuthorizationMode mode, + MutableX509TrustManager mutableTrustManager, + MutableX509KeyManager mutableKeyManager) { + SSLContext sslContext = new SslContextBuilder() + .withKeyManagerFactory((ignoredKeystore, ignoredPassword) -> mutableKeyManager) + .withTrustManagerFactory( + ignoredTruststore -> options.getAuthorizedPeers() + .map(authorizedPeers -> (X509ExtendedTrustManager) new PeerAuthorizerTrustManager(authorizedPeers, mode, mutableTrustManager)) + .orElse(mutableTrustManager)) + .build(); + return new DefaultTlsContext(sslContext, options.getAcceptedCiphers()); + } + + // Wrapped methods from TlsContext + @Override public SSLContext context() { return tlsContext.context(); } + @Override public SSLParameters parameters() { return tlsContext.parameters(); } + @Override public SSLEngine createSslEngine() { return tlsContext.createSslEngine(); } + @Override public SSLEngine createSslEngine(String peerHost, int peerPort) { return tlsContext.createSslEngine(peerHost, peerPort); } + @Override public void close() { try { @@ -57,13 +131,13 @@ public class ReloadingTlsContext implements TlsContext { } } - private class SslContextReloader implements Runnable { + private class CryptoMaterialReloader implements Runnable { @Override public void run() { try { - currentTlsContext.set(new DefaultTlsContext(tlsOptionsConfigFile, mode)); + reloadCryptoMaterial(TransportSecurityOptions.fromJsonFile(tlsOptionsConfigFile), trustManager, keyManager); } catch (Throwable t) { - log.log(Level.SEVERE, String.format("Failed to load SSLContext (path='%s'): %s", tlsOptionsConfigFile, t.getMessage()), t); + log.log(Level.SEVERE, String.format("Failed reload crypto material (path='%s'): %s", tlsOptionsConfigFile, t.getMessage()), t); } } } diff --git a/security-utils/src/main/java/com/yahoo/security/tls/TlsContext.java b/security-utils/src/main/java/com/yahoo/security/tls/TlsContext.java index 58687a0ba8f..b315dd00b31 100644 --- a/security-utils/src/main/java/com/yahoo/security/tls/TlsContext.java +++ b/security-utils/src/main/java/com/yahoo/security/tls/TlsContext.java @@ -3,6 +3,7 @@ package com.yahoo.security.tls; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLParameters; /** * A simplified version of {@link SSLContext} modelled as an interface. @@ -11,8 +12,14 @@ import javax.net.ssl.SSLEngine; */ public interface TlsContext extends AutoCloseable { + SSLContext context(); + + SSLParameters parameters(); + SSLEngine createSslEngine(); + SSLEngine createSslEngine(String peerHost, int peerPort); + @Override default void close() {} } |