diff --git a/bookkeeper-server/src/main/java/org/apache/bookkeeper/tls/TLSContextFactory.java b/bookkeeper-server/src/main/java/org/apache/bookkeeper/tls/TLSContextFactory.java index 29dbd143a53..fdb2d019c34 100644 --- a/bookkeeper-server/src/main/java/org/apache/bookkeeper/tls/TLSContextFactory.java +++ b/bookkeeper-server/src/main/java/org/apache/bookkeeper/tls/TLSContextFactory.java @@ -37,6 +37,8 @@ import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; +import java.security.Provider; +import java.security.Security; import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; import java.security.spec.InvalidKeySpecException; @@ -46,21 +48,78 @@ import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.TrustManagerFactory; +import lombok.extern.slf4j.Slf4j; import org.apache.bookkeeper.conf.AbstractConfiguration; import org.apache.bookkeeper.conf.ClientConfiguration; import org.apache.bookkeeper.conf.ServerConfiguration; import org.apache.commons.io.FileUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * A factory to manage TLS contexts. */ +@Slf4j public class TLSContextFactory implements SecurityHandlerFactory { - static { - // Fixes loading PKCS8Key file: https://stackoverflow.com/a/18912362 - java.security.Security.addProvider(new org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider()); + public static final Provider BC_PROVIDER = getProvider(); + public static final String BC_FIPS_PROVIDER_CLASS = "org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider"; + public static final String BC_NON_FIPS_PROVIDER_CLASS = "org.bouncycastle.jce.provider.BouncyCastleProvider"; + + // Security.getProvider("BC") / Security.getProvider("BCFIPS"). + // also used to get Factories. e.g. CertificateFactory.getInstance("X.509", "BCFIPS") + public static final String BC_FIPS = "BCFIPS"; + public static final String BC = "BC"; + + /** + * Get Bouncy Castle provider, and call Security.addProvider(provider) if success. + */ + public static Provider getProvider() { + boolean isProviderInstalled = + Security.getProvider(BC) != null || Security.getProvider(BC_FIPS) != null; + + if (isProviderInstalled) { + Provider provider = Security.getProvider(BC) != null + ? Security.getProvider(BC) + : Security.getProvider(BC_FIPS); + if (log.isDebugEnabled()) { + log.debug("Already instantiated Bouncy Castle provider {}", provider.getName()); + } + return provider; + } + + // Not installed, try load from class path + try { + return getBCProviderFromClassPath(); + } catch (Exception e) { + log.warn("Not able to get Bouncy Castle provider for both FIPS and Non-FIPS from class path:", e); + throw new RuntimeException(e); + } + } + + /** + * Get Bouncy Castle provider from classpath, and call Security.addProvider. + * Throw Exception if failed. + */ + public static Provider getBCProviderFromClassPath() throws Exception { + Class clazz; + try { + clazz = Class.forName(BC_FIPS_PROVIDER_CLASS); + } catch (ClassNotFoundException cnf) { + if (log.isDebugEnabled()) { + log.debug("Not able to get Bouncy Castle provider: {}, try to get FIPS provider {}", + BC_NON_FIPS_PROVIDER_CLASS, BC_FIPS_PROVIDER_CLASS); + } + // attempt to use the NON_FIPS provider. + clazz = Class.forName(BC_NON_FIPS_PROVIDER_CLASS); + + } + + @SuppressWarnings("unchecked") + Provider provider = (Provider) clazz.getDeclaredConstructor().newInstance(); + Security.addProvider(provider); + if (log.isDebugEnabled()) { + log.debug("Found and Instantiated Bouncy Castle provider in classpath {}", provider.getName()); + } + return provider; } /** @@ -83,7 +142,6 @@ public String toString() { } } - private static final Logger LOG = LoggerFactory.getLogger(TLSContextFactory.class); private static final String TLSCONTEXT_HANDLER_NAME = "tls"; private String[] protocols; private String[] ciphers; @@ -130,7 +188,7 @@ private KeyManagerFactory initKeyManagerFactory(String keyStoreType, String keyS KeyManagerFactory kmf = null; if (Strings.isNullOrEmpty(keyStoreLocation)) { - LOG.error("Key store location cannot be empty when Mutual Authentication is enabled!"); + log.error("Key store location cannot be empty when Mutual Authentication is enabled!"); throw new SecurityException("Key store location cannot be empty when Mutual Authentication is enabled!"); } @@ -153,7 +211,7 @@ private TrustManagerFactory initTrustManagerFactory(String trustStoreType, Strin TrustManagerFactory tmf; if (Strings.isNullOrEmpty(trustStoreLocation)) { - LOG.error("Trust Store location cannot be empty!"); + log.error("Trust Store location cannot be empty!"); throw new SecurityException("Trust Store location cannot be empty!"); } @@ -173,18 +231,18 @@ private TrustManagerFactory initTrustManagerFactory(String trustStoreType, Strin private SslProvider getTLSProvider(String sslProvider) { if (sslProvider.trim().equalsIgnoreCase("OpenSSL")) { if (OpenSsl.isAvailable()) { - LOG.info("Security provider - OpenSSL"); + log.info("Security provider - OpenSSL"); return SslProvider.OPENSSL; } Throwable causeUnavailable = OpenSsl.unavailabilityCause(); - LOG.warn("OpenSSL Unavailable: ", causeUnavailable); + log.warn("OpenSSL Unavailable: ", causeUnavailable); - LOG.info("Security provider - JDK"); + log.info("Security provider - JDK"); return SslProvider.JDK; } - LOG.info("Security provider - JDK"); + log.info("Security provider - JDK"); return SslProvider.JDK; } @@ -306,7 +364,7 @@ private synchronized SslContext getSSLContext() { || tlsKeyStorePasswordFilePath.checkAndRefresh() || tlsTrustStoreFilePath.checkAndRefresh() || tlsTrustStorePasswordFilePath.checkAndRefresh()) { try { - LOG.info("Updating tls certs certFile={}, keyStoreFile={}, trustStoreFile={}", + log.info("Updating tls certs certFile={}, keyStoreFile={}, trustStoreFile={}", tlsCertificateFilePath.getFileName(), tlsKeyStoreFilePath.getFileName(), tlsTrustStoreFilePath.getFileName()); if (isServerCtx) { @@ -315,7 +373,7 @@ private synchronized SslContext getSSLContext() { updateClientContext(); } } catch (Exception e) { - LOG.info("Failed to refresh tls certs", e); + log.info("Failed to refresh tls certs", e); } } } @@ -469,15 +527,15 @@ public SslHandler newTLSHandler() { if (protocols != null && protocols.length != 0) { sslHandler.engine().setEnabledProtocols(protocols); } - if (LOG.isDebugEnabled()) { - LOG.debug("Enabled cipher protocols: {} ", Arrays.toString(sslHandler.engine().getEnabledProtocols())); + if (log.isDebugEnabled()) { + log.debug("Enabled cipher protocols: {} ", Arrays.toString(sslHandler.engine().getEnabledProtocols())); } if (ciphers != null && ciphers.length != 0) { sslHandler.engine().setEnabledCipherSuites(ciphers); } - if (LOG.isDebugEnabled()) { - LOG.debug("Enabled cipher suites: {} ", Arrays.toString(sslHandler.engine().getEnabledCipherSuites())); + if (log.isDebugEnabled()) { + log.debug("Enabled cipher suites: {} ", Arrays.toString(sslHandler.engine().getEnabledCipherSuites())); } return sslHandler; diff --git a/bookkeeper-server/src/test/java/org/apache/bookkeeper/tls/TestTLS.java b/bookkeeper-server/src/test/java/org/apache/bookkeeper/tls/TestTLS.java index f4b59d8568c..d2c420ce4ca 100644 --- a/bookkeeper-server/src/test/java/org/apache/bookkeeper/tls/TestTLS.java +++ b/bookkeeper-server/src/test/java/org/apache/bookkeeper/tls/TestTLS.java @@ -233,6 +233,15 @@ public void tearDown() throws Exception { super.tearDown(); } + /** + * Verify the BouncyCastleProvider Name is expected. + */ + @Test + public void testGetBouncyCastleProviderName() throws Exception { + String bcName = TLSContextFactory.getProvider().getName(); + Assert.assertEquals(bcName, TLSContextFactory.BC_FIPS); + } + /** * Verify that a server will not start if tls is enabled but no cert is specified. */ diff --git a/tests/backward-compat/bc-non-fips/pom.xml b/tests/backward-compat/bc-non-fips/pom.xml new file mode 100644 index 00000000000..eba5b1d7219 --- /dev/null +++ b/tests/backward-compat/bc-non-fips/pom.xml @@ -0,0 +1,79 @@ + + + + 4.0.0 + + org.apache.bookkeeper.tests + backward-compat + 4.15.0-SNAPSHOT + .. + + + org.apache.bookkeeper.tests.backward-compat + bc-non-fips + jar + Apache BookKeeper :: Tests :: Backward Compatibility :: Test Bouncy Castle Provider load non FIPS version + + 1.68 + + + + + junit + junit + ${junit.version} + + + + org.apache.bookkeeper + bookkeeper-server + ${project.version} + + + org.bouncycastle + * + + + test + + + + org.bouncycastle + bcpkix-jdk15on + ${bc-non-fips.version} + + + + org.bouncycastle + bcprov-ext-jdk15on + ${bc-non-fips.version} + + + + + + + org.apache.maven.plugins + maven-deploy-plugin + + true + + + + + diff --git a/tests/backward-compat/bc-non-fips/src/test/java/org/apache/bookkeeper/tls/TestBCNonFips.java b/tests/backward-compat/bc-non-fips/src/test/java/org/apache/bookkeeper/tls/TestBCNonFips.java new file mode 100644 index 00000000000..eded70b0a59 --- /dev/null +++ b/tests/backward-compat/bc-non-fips/src/test/java/org/apache/bookkeeper/tls/TestBCNonFips.java @@ -0,0 +1,36 @@ +/** + * 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.bookkeeper.tls; + +import org.junit.Assert; +import org.junit.Test; + +/** + * Test Bouncy Castle Provider load non FIPS version. + */ +public class TestBCNonFips { + + /** + * Verify the BouncyCastleProvider Name is expected. + */ + @Test + public void testGetBouncyCastleProviderName() { + String bcName = TLSContextFactory.getProvider().getName(); + Assert.assertEquals(bcName, TLSContextFactory.BC); + } +} diff --git a/tests/backward-compat/pom.xml b/tests/backward-compat/pom.xml index af9dbfa2814..396840bb8f4 100644 --- a/tests/backward-compat/pom.xml +++ b/tests/backward-compat/pom.xml @@ -36,5 +36,6 @@ old-cookie-new-cluster current-server-old-clients yahoo-custom-version + bc-non-fips