| /** |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.pulsar.common.util.keystoretls; |
| |
| import static org.apache.pulsar.common.util.SecurityUtility.getProvider; |
| import com.google.common.base.Strings; |
| import io.netty.handler.ssl.util.InsecureTrustManagerFactory; |
| import java.io.FileInputStream; |
| import java.io.IOException; |
| import java.security.GeneralSecurityException; |
| import java.security.KeyStore; |
| import java.security.Provider; |
| import java.security.SecureRandom; |
| import java.util.Arrays; |
| import java.util.HashSet; |
| import java.util.Set; |
| import javax.net.ssl.KeyManager; |
| import javax.net.ssl.KeyManagerFactory; |
| import javax.net.ssl.SSLContext; |
| import javax.net.ssl.SSLEngine; |
| import javax.net.ssl.TrustManagerFactory; |
| import lombok.Getter; |
| import lombok.extern.slf4j.Slf4j; |
| import org.apache.pulsar.common.util.SecurityUtility; |
| import org.eclipse.jetty.util.ssl.SslContextFactory; |
| |
| /** |
| * KeyStoreSSLContext that mainly wrap a SSLContext to provide SSL context for both webservice and netty. |
| */ |
| @Slf4j |
| public class KeyStoreSSLContext { |
| public static final String DEFAULT_KEYSTORE_TYPE = "JKS"; |
| public static final String DEFAULT_SSL_PROTOCOL = "TLS"; |
| public static final String DEFAULT_SSL_ENABLED_PROTOCOLS = "TLSv1.3,TLSv1.2"; |
| public static final String DEFAULT_SSL_KEYMANGER_ALGORITHM = KeyManagerFactory.getDefaultAlgorithm(); |
| public static final String DEFAULT_SSL_TRUSTMANAGER_ALGORITHM = TrustManagerFactory.getDefaultAlgorithm(); |
| |
| public static final Provider BC_PROVIDER = getProvider(); |
| |
| /** |
| * Connection Mode for TLS. |
| */ |
| public enum Mode { |
| CLIENT, |
| SERVER |
| } |
| |
| @Getter |
| private final Mode mode; |
| |
| private final String sslProviderString; |
| private final String keyStoreTypeString; |
| private final String keyStorePath; |
| private final String keyStorePassword; |
| private final boolean allowInsecureConnection; |
| private final String trustStoreTypeString; |
| private final String trustStorePath; |
| private final String trustStorePassword; |
| private final boolean needClientAuth; |
| private final Set<String> ciphers; |
| private final Set<String> protocols; |
| private SSLContext sslContext; |
| |
| private final String protocol = DEFAULT_SSL_PROTOCOL; |
| private final String kmfAlgorithm = DEFAULT_SSL_KEYMANGER_ALGORITHM; |
| private final String tmfAlgorithm = DEFAULT_SSL_TRUSTMANAGER_ALGORITHM; |
| |
| // only init vars, before using it, need to call createSSLContext to create ssl context. |
| public KeyStoreSSLContext(Mode mode, |
| String sslProviderString, |
| String keyStoreTypeString, |
| String keyStorePath, |
| String keyStorePassword, |
| boolean allowInsecureConnection, |
| String trustStoreTypeString, |
| String trustStorePath, |
| String trustStorePassword, |
| boolean requireTrustedClientCertOnConnect, |
| Set<String> ciphers, |
| Set<String> protocols) { |
| this.mode = mode; |
| this.sslProviderString = sslProviderString; |
| this.keyStoreTypeString = Strings.isNullOrEmpty(keyStoreTypeString) |
| ? DEFAULT_KEYSTORE_TYPE |
| : keyStoreTypeString; |
| this.keyStorePath = keyStorePath; |
| this.keyStorePassword = keyStorePassword; |
| this.trustStoreTypeString = Strings.isNullOrEmpty(trustStoreTypeString) |
| ? DEFAULT_KEYSTORE_TYPE |
| : trustStoreTypeString; |
| this.trustStorePath = trustStorePath; |
| if (trustStorePassword == null) { |
| this.trustStorePassword = ""; |
| } else { |
| this.trustStorePassword = trustStorePassword; |
| } |
| this.needClientAuth = requireTrustedClientCertOnConnect; |
| |
| if (protocols != null && protocols.size() > 0) { |
| this.protocols = protocols; |
| } else { |
| this.protocols = new HashSet<>(Arrays.asList(DEFAULT_SSL_ENABLED_PROTOCOLS.split("\\s*,\\s*"))); |
| } |
| |
| if (ciphers != null && ciphers.size() > 0) { |
| this.ciphers = ciphers; |
| } else { |
| this.ciphers = null; |
| } |
| |
| this.allowInsecureConnection = allowInsecureConnection; |
| } |
| |
| public SSLContext createSSLContext() throws GeneralSecurityException, IOException { |
| SSLContext sslContext; |
| if (sslProviderString != null) { |
| sslContext = SSLContext.getInstance(protocol, sslProviderString); |
| } else { |
| sslContext = SSLContext.getInstance(protocol); |
| } |
| |
| // key store |
| KeyManager[] keyManagers = null; |
| if (!Strings.isNullOrEmpty(keyStorePath)) { |
| KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(kmfAlgorithm); |
| KeyStore keyStore = KeyStore.getInstance(keyStoreTypeString); |
| char[] passwordChars = keyStorePassword.toCharArray(); |
| try (FileInputStream inputStream = new FileInputStream(keyStorePath)) { |
| keyStore.load(inputStream, passwordChars); |
| } |
| keyManagerFactory.init(keyStore, passwordChars); |
| keyManagers = keyManagerFactory.getKeyManagers(); |
| } |
| |
| // trust store |
| TrustManagerFactory trustManagerFactory; |
| if (this.allowInsecureConnection) { |
| trustManagerFactory = InsecureTrustManagerFactory.INSTANCE; |
| } else { |
| trustManagerFactory = sslProviderString != null |
| ? TrustManagerFactory.getInstance(tmfAlgorithm, sslProviderString) |
| : TrustManagerFactory.getInstance(tmfAlgorithm); |
| KeyStore trustStore = KeyStore.getInstance(trustStoreTypeString); |
| char[] passwordChars = trustStorePassword.toCharArray(); |
| try (FileInputStream inputStream = new FileInputStream(trustStorePath)) { |
| trustStore.load(inputStream, passwordChars); |
| } |
| trustManagerFactory.init(trustStore); |
| } |
| |
| // init |
| sslContext.init(keyManagers, SecurityUtility |
| .processConscryptTrustManagers(trustManagerFactory.getTrustManagers()), |
| new SecureRandom()); |
| this.sslContext = sslContext; |
| return sslContext; |
| } |
| |
| public SSLContext getSslContext() { |
| if (sslContext == null) { |
| throw new IllegalStateException("createSSLContext hasn't been called."); |
| } |
| return sslContext; |
| } |
| |
| public SSLEngine createSSLEngine() { |
| return configureSSLEngine(getSslContext().createSSLEngine()); |
| } |
| |
| public SSLEngine createSSLEngine(String peerHost, int peerPort) { |
| return configureSSLEngine(getSslContext().createSSLEngine(peerHost, peerPort)); |
| } |
| |
| private SSLEngine configureSSLEngine(SSLEngine sslEngine) { |
| sslEngine.setEnabledProtocols(protocols.toArray(new String[0])); |
| if (this.ciphers != null) { |
| sslEngine.setEnabledCipherSuites(this.ciphers.toArray(new String[0])); |
| } |
| |
| if (this.mode == Mode.SERVER) { |
| sslEngine.setNeedClientAuth(this.needClientAuth); |
| sslEngine.setUseClientMode(false); |
| } else { |
| sslEngine.setUseClientMode(true); |
| } |
| return sslEngine; |
| } |
| |
| public static KeyStoreSSLContext createClientKeyStoreSslContext(String sslProviderString, |
| String keyStoreTypeString, |
| String keyStorePath, |
| String keyStorePassword, |
| boolean allowInsecureConnection, |
| String trustStoreTypeString, |
| String trustStorePath, |
| String trustStorePassword, |
| Set<String> ciphers, |
| Set<String> protocols) |
| throws GeneralSecurityException, IOException { |
| KeyStoreSSLContext keyStoreSSLContext = new KeyStoreSSLContext(Mode.CLIENT, |
| sslProviderString, |
| keyStoreTypeString, |
| keyStorePath, |
| keyStorePassword, |
| allowInsecureConnection, |
| trustStoreTypeString, |
| trustStorePath, |
| trustStorePassword, |
| false, |
| ciphers, |
| protocols); |
| |
| keyStoreSSLContext.createSSLContext(); |
| return keyStoreSSLContext; |
| } |
| |
| |
| public static KeyStoreSSLContext createServerKeyStoreSslContext(String sslProviderString, |
| String keyStoreTypeString, |
| String keyStorePath, |
| String keyStorePassword, |
| boolean allowInsecureConnection, |
| String trustStoreTypeString, |
| String trustStorePath, |
| String trustStorePassword, |
| boolean requireTrustedClientCertOnConnect, |
| Set<String> ciphers, |
| Set<String> protocols) |
| throws GeneralSecurityException, IOException { |
| KeyStoreSSLContext keyStoreSSLContext = new KeyStoreSSLContext(Mode.SERVER, |
| sslProviderString, |
| keyStoreTypeString, |
| keyStorePath, |
| keyStorePassword, |
| allowInsecureConnection, |
| trustStoreTypeString, |
| trustStorePath, |
| trustStorePassword, |
| requireTrustedClientCertOnConnect, |
| ciphers, |
| protocols); |
| |
| keyStoreSSLContext.createSSLContext(); |
| return keyStoreSSLContext; |
| } |
| |
| // for web server use case, no need ciphers and protocols |
| public static SSLContext createServerSslContext(String sslProviderString, |
| String keyStoreTypeString, |
| String keyStorePath, |
| String keyStorePassword, |
| boolean allowInsecureConnection, |
| String trustStoreTypeString, |
| String trustStorePath, |
| String trustStorePassword, |
| boolean requireTrustedClientCertOnConnect) |
| throws GeneralSecurityException, IOException { |
| |
| return createServerKeyStoreSslContext( |
| sslProviderString, |
| keyStoreTypeString, |
| keyStorePath, |
| keyStorePassword, |
| allowInsecureConnection, |
| trustStoreTypeString, |
| trustStorePath, |
| trustStorePassword, |
| requireTrustedClientCertOnConnect, |
| null, |
| null).getSslContext(); |
| } |
| |
| // for web client |
| public static SSLContext createClientSslContext(String sslProviderString, |
| String keyStoreTypeString, |
| String keyStorePath, |
| String keyStorePassword, |
| boolean allowInsecureConnection, |
| String trustStoreTypeString, |
| String trustStorePath, |
| String trustStorePassword, |
| Set<String> ciphers, |
| Set<String> protocol) |
| throws GeneralSecurityException, IOException { |
| KeyStoreSSLContext keyStoreSSLContext = new KeyStoreSSLContext(Mode.CLIENT, |
| sslProviderString, |
| keyStoreTypeString, |
| keyStorePath, |
| keyStorePassword, |
| allowInsecureConnection, |
| trustStoreTypeString, |
| trustStorePath, |
| trustStorePassword, |
| false, |
| ciphers, |
| protocol); |
| |
| return keyStoreSSLContext.createSSLContext(); |
| } |
| |
| // for web client |
| public static SSLContext createClientSslContext(String keyStoreTypeString, |
| String keyStorePath, |
| String keyStorePassword, |
| String trustStoreTypeString, |
| String trustStorePath, |
| String trustStorePassword) |
| throws GeneralSecurityException, IOException { |
| KeyStoreSSLContext keyStoreSSLContext = new KeyStoreSSLContext(Mode.CLIENT, |
| null, |
| keyStoreTypeString, |
| keyStorePath, |
| keyStorePassword, |
| false, |
| trustStoreTypeString, |
| trustStorePath, |
| trustStorePassword, |
| false, |
| null, |
| null); |
| |
| return keyStoreSSLContext.createSSLContext(); |
| } |
| |
| // for web server. autoRefresh is default true. |
| public static SslContextFactory createSslContextFactory(String sslProviderString, |
| String keyStoreTypeString, |
| String keyStore, |
| String keyStorePassword, |
| boolean allowInsecureConnection, |
| String trustStoreTypeString, |
| String trustStore, |
| String trustStorePassword, |
| boolean requireTrustedClientCertOnConnect, |
| long certRefreshInSec) |
| throws GeneralSecurityException, IOException { |
| SslContextFactory sslCtxFactory; |
| |
| if (sslProviderString == null) { |
| Provider provider = SecurityUtility.CONSCRYPT_PROVIDER; |
| if (provider != null) { |
| sslProviderString = provider.getName(); |
| } |
| } |
| |
| sslCtxFactory = new SslContextFactoryWithAutoRefresh( |
| sslProviderString, |
| keyStoreTypeString, |
| keyStore, |
| keyStorePassword, |
| allowInsecureConnection, |
| trustStoreTypeString, |
| trustStore, |
| trustStorePassword, |
| requireTrustedClientCertOnConnect, |
| certRefreshInSec); |
| |
| if (requireTrustedClientCertOnConnect) { |
| sslCtxFactory.setNeedClientAuth(true); |
| } else { |
| sslCtxFactory.setWantClientAuth(true); |
| } |
| sslCtxFactory.setTrustAll(true); |
| |
| return sslCtxFactory; |
| } |
| } |
| |