You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ozone.apache.org by ad...@apache.org on 2026/10/24 08:25:32 UTC
(ozone) branch master updated: HDDS-17353. Register BouncyCastle provider only when it is looked up by name (#11725)
This is an automated email from the ASF dual-hosted git repository.
adoroszlai pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ozone.git
The following commit(s) were added to refs/heads/master by this push:
new 69d77beff60 HDDS-17353. Register BouncyCastle provider only when it is looked up by name (#11725)
69d77beff60 is described below
commit 69d77beff60b9106e2517c36a7a1b2cc726af568
Author: Andrey Yarovoy <11...@users.noreply.github.com>
AuthorDate: Sat Oct 24 04:25:25 2026 -0400
HDDS-17353. Register BouncyCastle provider only when it is looked up by name (#11725)
Co-authored-by: Claude Code (Claude Opus 5.5)
---
.../hadoop/hdds/security/SecurityConfig.java | 23 +++++--
.../x509/certificate/utils/CertificateCodec.java | 5 +-
.../org/apache/hadoop/ozone/OzoneSecurityUtil.java | 2 -
.../TestCertificateCodecProviderRegistration.java | 53 ++++++++++++++
.../security/TestSecurityProviderRegistration.java | 80 ++++++++++++++++++++++
5 files changed, 155 insertions(+), 8 deletions(-)
diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/SecurityConfig.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/SecurityConfig.java
index 2d38da89f67..70bacd89fe4 100644
--- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/SecurityConfig.java
+++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/SecurityConfig.java
@@ -167,7 +167,7 @@ public SecurityConfig(ConfigurationSource configuration) {
this.size = configuration.getInt(HDDS_KEY_LEN, HDDS_DEFAULT_KEY_LEN);
this.keyAlgo = configuration.get(HDDS_KEY_ALGORITHM,
HDDS_DEFAULT_KEY_ALGORITHM);
- initSecurityProvider(configuration);
+ // The provider is registered by getProvider(), when something needs it: most clients never do.
this.providerString = configuration.get(HDDS_SECURITY_PROVIDER, HDDS_DEFAULT_SECURITY_PROVIDER);
// Please Note: To make it easy for our customers we will attempt to read
@@ -304,10 +304,22 @@ public SecurityConfig(ConfigurationSource configuration) {
: Collections.unmodifiableList(Arrays.asList(configuration.getTrimmedStrings(HDDS_GRPC_TLS_CIPHERS)));
}
- public static synchronized void initSecurityProvider(ConfigurationSource configuration) {
+ public static void initSecurityProvider(ConfigurationSource configuration) {
+ initSecurityProvider(configuration.get(HDDS_SECURITY_PROVIDER, HDDS_DEFAULT_SECURITY_PROVIDER));
+ }
+
+ /**
+ * Registers the security provider, BouncyCastle for "BC", if no provider has been resolved yet.
+ */
+ public static void initSecurityProvider(String providerString) {
+ if (SecurityConfig.provider == null) {
+ initSecurityProviderSynchronized(providerString);
+ }
+ }
+
+ private static synchronized void initSecurityProviderSynchronized(String providerString) {
// First Startup -- if the provider is null, check for the provider.
if (SecurityConfig.provider == null) {
- String providerString = configuration.get(HDDS_SECURITY_PROVIDER, HDDS_DEFAULT_SECURITY_PROVIDER);
provider = Security.getProvider(providerString);
if (SecurityConfig.provider == null) {
// Provider not found, let us try to Dynamically initialize the
@@ -504,12 +516,13 @@ public int getSize() {
}
/**
- * Returns the Provider name. SCM defaults to using Bouncy Castle and will
- * return "BC".
+ * Returns the Provider name, after registering the provider so that it can be looked up by that name.
+ * SCM defaults to using Bouncy Castle and will return "BC".
*
* @return String Provider name.
*/
public String getProvider() {
+ initSecurityProvider(providerString);
return providerString;
}
diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/utils/CertificateCodec.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/utils/CertificateCodec.java
index f737e785ade..3089b20bd0c 100644
--- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/utils/CertificateCodec.java
+++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/utils/CertificateCodec.java
@@ -164,7 +164,8 @@ public static X509Certificate getX509Certificate(byte[] pemEncoded)
}
public static X509Certificate readX509Certificate(InputStream input) throws CertificateException {
- final Certificate cert = getCertFactory().generateCertificate(input);
+ // The JDK's X.509 factory: a client reading CA certificates must not need the BouncyCastle provider.
+ final Certificate cert = CertificateFactory.getInstance("X.509").generateCertificate(input);
if (cert instanceof X509Certificate) {
return (X509Certificate) cert;
}
@@ -184,6 +185,8 @@ public static X509Certificate firstCertificateFrom(CertPath certificatePath) {
}
public static CertificateFactory getCertFactory() throws CertificateException {
+ // BouncyCastle, for the "PEM" cert path encoding only it supports
+ SecurityConfig.initSecurityProvider("BC");
try {
return CertificateFactory.getInstance("X.509", "BC");
} catch (NoSuchProviderException e) {
diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneSecurityUtil.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneSecurityUtil.java
index 6e9da83b2bb..438cdcee7d4 100644
--- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneSecurityUtil.java
+++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneSecurityUtil.java
@@ -35,7 +35,6 @@
import org.apache.hadoop.hdds.annotation.InterfaceAudience;
import org.apache.hadoop.hdds.annotation.InterfaceStability;
import org.apache.hadoop.hdds.conf.ConfigurationSource;
-import org.apache.hadoop.hdds.security.SecurityConfig;
import org.apache.hadoop.hdds.security.x509.certificate.utils.CertificateCodec;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -54,7 +53,6 @@ private OzoneSecurityUtil() {
}
public static boolean isSecurityEnabled(ConfigurationSource conf) {
- SecurityConfig.initSecurityProvider(conf);
return conf.getBoolean(OZONE_SECURITY_ENABLED_KEY, OZONE_SECURITY_ENABLED_DEFAULT);
}
diff --git a/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/security/TestCertificateCodecProviderRegistration.java b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/security/TestCertificateCodecProviderRegistration.java
new file mode 100644
index 00000000000..7fa83ee6361
--- /dev/null
+++ b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/security/TestCertificateCodecProviderRegistration.java
@@ -0,0 +1,53 @@
+/*
+ * 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.hadoop.hdds.security;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.Security;
+import java.security.cert.CertPath;
+import java.security.cert.X509Certificate;
+import org.apache.hadoop.hdds.security.x509.CertificateTestUtils;
+import org.apache.hadoop.hdds.security.x509.certificate.utils.CertificateCodec;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests that {@link CertificateCodec#getCertFactory()} registers BouncyCastle when nothing has registered it yet.
+ * Registration is JVM-wide, so this runs in its own class (and so its own JVM).
+ */
+class TestCertificateCodecProviderRegistration {
+
+ @Test
+ void certFactoryRegistersBouncyCastle() throws Exception {
+ assertNull(Security.getProvider("BC"), "BouncyCastle registered before the test");
+ KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
+ generator.initialize(2048);
+ KeyPair keys = generator.generateKeyPair();
+ X509Certificate cert = CertificateTestUtils.createSelfSignedCert(keys, "ca");
+
+ // the "PEM" cert path encoding is BouncyCastle's
+ CertPath path = CertificateCodec.getCertPathFromPemEncodedString(CertificateCodec.getPEMEncodedString(cert));
+
+ assertEquals(cert, CertificateCodec.firstCertificateFrom(path));
+ assertNotNull(Security.getProvider("BC"));
+ }
+}
diff --git a/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/security/TestSecurityProviderRegistration.java b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/security/TestSecurityProviderRegistration.java
new file mode 100644
index 00000000000..1ee52877c89
--- /dev/null
+++ b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/security/TestSecurityProviderRegistration.java
@@ -0,0 +1,80 @@
+/*
+ * 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.hadoop.hdds.security;
+
+import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_SECURITY_ENABLED_KEY;
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.Security;
+import java.security.cert.X509Certificate;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.hdds.security.x509.CertificateTestUtils;
+import org.apache.hadoop.hdds.security.x509.certificate.utils.CertificateCodec;
+import org.apache.hadoop.ozone.OzoneSecurityUtil;
+import org.junit.jupiter.api.MethodOrderer;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestMethodOrder;
+
+/**
+ * Tests that the client path does not register the BouncyCastle provider, and that code which names the provider
+ * gets it registered. Registration is JVM-wide, so the tests run in order, in this class's own JVM.
+ */
+@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
+class TestSecurityProviderRegistration {
+
+ private static final String BC = "BC";
+
+ @Test
+ @Order(1)
+ void clientPathDoesNotRegisterBouncyCastle() throws Exception {
+ assertNull(Security.getProvider(BC), "BouncyCastle registered before the test");
+ OzoneConfiguration conf = new OzoneConfiguration();
+ conf.setBoolean(OZONE_SECURITY_ENABLED_KEY, true);
+
+ new SecurityConfig(conf);
+ assertTrue(OzoneSecurityUtil.isSecurityEnabled(conf));
+
+ // a client reading a CA certificate, as from ServiceInfoEx.getCaCertificates()
+ KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
+ generator.initialize(2048);
+ KeyPair keys = generator.generateKeyPair();
+ X509Certificate original = CertificateTestUtils.createSelfSignedCert(keys, "ca");
+ X509Certificate parsed = CertificateCodec.getX509Certificate(CertificateCodec.getPEMEncodedString(original));
+ assertArrayEquals(original.getEncoded(), parsed.getEncoded());
+ assertEquals(original, parsed);
+
+ assertNull(Security.getProvider(BC), "The client path must not register BouncyCastle");
+ }
+
+ @Test
+ @Order(2)
+ void getProviderRegistersBouncyCastle() {
+ SecurityConfig securityConfig = new SecurityConfig(new OzoneConfiguration());
+ assertNull(Security.getProvider(BC));
+
+ assertEquals(BC, securityConfig.getProvider());
+ assertNotNull(Security.getProvider(BC), "getProvider() must register the provider it names");
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@ozone.apache.org
For additional commands, e-mail: commits-help@ozone.apache.org