This is an automated email from the ASF dual-hosted git repository.

hubgeter pushed a commit to branch 
task4-geode-10509-certificatebuilder-bouncycastle
in repository https://gitbox.apache.org/repos/asf/geode.git

commit 7275e8c6fc04147062577650d87152ae9ada04c5
Author: daidai <[email protected]>
AuthorDate: Fri Jun 5 15:05:00 2026 +0800

    GEODE-10509: Replaced sun.security.x509.*  usages with Bouncy Castle 
(`bcpkix-jdk18on`) public APIs
---
 .../gradle/plugins/DependencyConstraints.groovy    |   2 +
 .../scripts/src/main/groovy/geode-test.gradle      |   1 -
 geode-junit/build.gradle                           |  13 +-
 .../apache/geode/cache/ssl/CertificateBuilder.java | 157 +++++++--------------
 .../ssl/CertificateBuilderExtensionsTest.java      | 116 +++++++++++++++
 5 files changed, 175 insertions(+), 114 deletions(-)

diff --git 
a/build-tools/geode-dependency-management/src/main/groovy/org/apache/geode/gradle/plugins/DependencyConstraints.groovy
 
b/build-tools/geode-dependency-management/src/main/groovy/org/apache/geode/gradle/plugins/DependencyConstraints.groovy
index d71e6717ba..e7ea889164 100644
--- 
a/build-tools/geode-dependency-management/src/main/groovy/org/apache/geode/gradle/plugins/DependencyConstraints.groovy
+++ 
b/build-tools/geode-dependency-management/src/main/groovy/org/apache/geode/gradle/plugins/DependencyConstraints.groovy
@@ -183,6 +183,8 @@ class DependencyConstraints {
         api(group: 'org.apache.shiro', name: 'shiro-core', version: 
get('shiro.version'))
         // GEODE-10583: Pin Bouncy Castle provider (pulled in via 
shiro-crypto-hash) to 1.84
         api(group: 'org.bouncycastle', name: 'bcprov-jdk18on', version: 
get('bouncycastle.version'))
+        // GEODE-10509: Bouncy Castle PKIX for X.509 certificate building in 
test utilities
+        api(group: 'org.bouncycastle', name: 'bcpkix-jdk18on', version: 
get('bouncycastle.version'))
         api(group: 'org.assertj', name: 'assertj-core', version: '3.22.0')
         api(group: 'org.awaitility', name: 'awaitility', version: '4.2.0')
         api(group: 'org.buildobjects', name: 'jproc', version: '2.8.0')
diff --git a/build-tools/scripts/src/main/groovy/geode-test.gradle 
b/build-tools/scripts/src/main/groovy/geode-test.gradle
index 73a56dfd2f..073ec43e79 100644
--- a/build-tools/scripts/src/main/groovy/geode-test.gradle
+++ b/build-tools/scripts/src/main/groovy/geode-test.gradle
@@ -249,7 +249,6 @@ gradle.taskGraph.whenReady({ graph ->
                 "--add-opens=java.xml/jdk.xml.internal=ALL-UNNAMED",
                 
"--add-opens=jdk.management/com.sun.management.internal=ALL-UNNAMED",
 
-                "--add-exports=java.base/sun.security.x509=ALL-UNNAMED",
                 
"--add-exports=java.management/com.sun.jmx.remote.security=ALL-UNNAMED",
         ]
 
diff --git a/geode-junit/build.gradle b/geode-junit/build.gradle
index f2c19d86b6..02e135c5b0 100755
--- a/geode-junit/build.gradle
+++ b/geode-junit/build.gradle
@@ -20,18 +20,8 @@ plugins {
   id 'geode-publish-java'
 }
 
-compileJava {
-  // -Xlint:-sunapi flag removed as it doesn't exist in Java 17
-  // Added --add-exports for sun.security packages needed for 
CertificateBuilder
-  options.compilerArgs << '-XDenableSunApiLintControl'
-  options.compilerArgs << 
'--add-exports=java.base/sun.security.x509=ALL-UNNAMED'
-  options.compilerArgs << 
'--add-exports=java.base/sun.security.util=ALL-UNNAMED'
-}
-
 javadoc {
-  // Exclude classes that use internal sun.security packages to avoid javadoc 
errors
   options.addBooleanOption('Xdoclint:none', true)
-  exclude '**/CertificateBuilder.java'
   exclude '**/HybridCATestFixture.java'
 }
 
@@ -68,6 +58,9 @@ dependencies {
   api('org.apache.commons:commons-lang3')
   api('org.apache.logging.log4j:log4j-api')
 
+  // GEODE-10509: X.509 certificate building for the CertificateBuilder test 
utility
+  implementation('org.bouncycastle:bcpkix-jdk18on')
+
   api('org.awaitility:awaitility')
   api('org.hamcrest:hamcrest')
   api('io.micrometer:micrometer-core')
diff --git 
a/geode-junit/src/main/java/org/apache/geode/cache/ssl/CertificateBuilder.java 
b/geode-junit/src/main/java/org/apache/geode/cache/ssl/CertificateBuilder.java
index 66cec02670..6a5f17704d 100644
--- 
a/geode-junit/src/main/java/org/apache/geode/cache/ssl/CertificateBuilder.java
+++ 
b/geode-junit/src/main/java/org/apache/geode/cache/ssl/CertificateBuilder.java
@@ -14,8 +14,6 @@
  */
 package org.apache.geode.cache.ssl;
 
-import java.io.IOException;
-import java.io.UncheckedIOException;
 import java.math.BigInteger;
 import java.net.InetAddress;
 import java.net.UnknownHostException;
@@ -30,28 +28,21 @@ import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
 
-import sun.security.util.ObjectIdentifier;
-import sun.security.x509.AlgorithmId;
-import sun.security.x509.BasicConstraintsExtension;
-import sun.security.x509.CertificateAlgorithmId;
-import sun.security.x509.CertificateExtensions;
-import sun.security.x509.CertificateSerialNumber;
-import sun.security.x509.CertificateValidity;
-import sun.security.x509.CertificateVersion;
-import sun.security.x509.CertificateX509Key;
-import sun.security.x509.DNSName;
-import sun.security.x509.ExtendedKeyUsageExtension;
-import sun.security.x509.GeneralName;
-import sun.security.x509.GeneralNames;
-import sun.security.x509.IPAddressName;
-import sun.security.x509.KeyIdentifier;
-import sun.security.x509.KeyUsageExtension;
-import sun.security.x509.SubjectAlternativeNameExtension;
-import sun.security.x509.SubjectKeyIdentifierExtension;
-import sun.security.x509.X500Name;
-import sun.security.x509.X509CertImpl;
-import sun.security.x509.X509CertInfo;
-
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.BasicConstraints;
+import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.GeneralNames;
+import org.bouncycastle.asn1.x509.KeyPurposeId;
+import org.bouncycastle.asn1.x509.KeyUsage;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
+import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
 
 /**
  * Class which allows easily building certificates. It can also be used to 
build
@@ -66,7 +57,7 @@ public class CertificateBuilder {
   private final List<InetAddress> ipAddresses;
   private boolean isCA;
   private CertificateMaterial issuer;
-  private final List<ObjectIdentifier> extendedKeyUsages;
+  private final List<ASN1ObjectIdentifier> extendedKeyUsages;
 
   public CertificateBuilder() {
     this(30, "SHA256withRSA");
@@ -81,27 +72,15 @@ public class CertificateBuilder {
   }
 
   private static GeneralName dnsGeneralName(String name) {
-    try {
-      return new GeneralName(new DNSName(name));
-    } catch (IOException ex) {
-      throw new UncheckedIOException(ex);
-    }
+    return new GeneralName(GeneralName.dNSName, name);
   }
 
   private static GeneralName ipGeneralName(InetAddress hostAddress) {
-    try {
-      return new GeneralName(new IPAddressName(hostAddress.getAddress()));
-    } catch (IOException ex) {
-      throw new UncheckedIOException(ex);
-    }
+    return new GeneralName(GeneralName.iPAddress, 
hostAddress.getHostAddress());
   }
 
   public CertificateBuilder commonName(String cn) {
-    try {
-      name = new X500Name("O=Geode, CN=" + cn);
-    } catch (IOException ex) {
-      throw new UncheckedIOException(ex);
-    }
+    name = new X500Name("O=Geode, CN=" + cn);
     return this;
   }
 
@@ -142,12 +121,8 @@ public class CertificateBuilder {
    * - "1.3.6.1.5.5.7.3.3" = codeSigning
    */
   public CertificateBuilder extendedKeyUsage(String... oids) {
-    try {
-      for (String oid : oids) {
-        extendedKeyUsages.add(ObjectIdentifier.of(oid));
-      }
-    } catch (IOException ex) {
-      throw new UncheckedIOException(ex);
+    for (String oid : oids) {
+      extendedKeyUsages.add(new ASN1ObjectIdentifier(oid));
     }
     return this;
   }
@@ -166,17 +141,17 @@ public class CertificateBuilder {
     return extendedKeyUsage("1.3.6.1.5.5.7.3.1");
   }
 
-  private GeneralNames san() throws IOException {
-    GeneralNames names = new GeneralNames();
-    for (String name : dnsNames) {
-      names.add(CertificateBuilder.dnsGeneralName(name));
+  private GeneralNames subjectAlternativeNames() {
+    List<GeneralName> names = new ArrayList<>();
+    for (String dnsName : dnsNames) {
+      names.add(CertificateBuilder.dnsGeneralName(dnsName));
     }
 
     for (InetAddress address : ipAddresses) {
       names.add(CertificateBuilder.ipGeneralName(address));
     }
 
-    return names;
+    return new GeneralNames(names.toArray(new GeneralName[0]));
   }
 
   public CertificateMaterial generate() {
@@ -202,71 +177,47 @@ public class CertificateBuilder {
   private X509Certificate generate(PublicKey publicKey, PrivateKey privateKey) 
{
     Date from = new Date();
     Date to = new Date(from.getTime() + days * 86_400_000L);
+    BigInteger serialNumber = new BigInteger(64, new SecureRandom());
 
-    CertificateValidity interval = new CertificateValidity(from, to);
-    BigInteger sn = new BigInteger(64, new SecureRandom());
-
-    X509CertInfo info = new X509CertInfo();
+    X500Name issuerName;
+    if (issuer == null) {
+      // This is a self-signed certificate
+      issuerName = name;
+    } else {
+      issuerName =
+          
X500Name.getInstance(issuer.getCertificate().getSubjectX500Principal().getEncoded());
+    }
 
     try {
-      info.set(X509CertInfo.VALIDITY, interval);
-      info.set(X509CertInfo.SERIAL_NUMBER, new CertificateSerialNumber(sn));
-      info.set(X509CertInfo.SUBJECT, name);
-      info.set(X509CertInfo.KEY, new CertificateX509Key(publicKey));
-      info.set(X509CertInfo.VERSION, new 
CertificateVersion(CertificateVersion.V3));
-      AlgorithmId algo = AlgorithmId.get("MD5withRSA");
-      info.set(X509CertInfo.ALGORITHM_ID, new CertificateAlgorithmId(algo));
-
-      if (issuer == null) {
-        // This is a self-signed certificate
-        info.set(X509CertInfo.ISSUER, name);
-      } else {
-        info.set(X509CertInfo.ISSUER, issuer.getCertificate().getSubjectDN());
-      }
-
-      CertificateExtensions extensions = new CertificateExtensions();
+      JcaX509v3CertificateBuilder certBuilder =
+          new JcaX509v3CertificateBuilder(issuerName, serialNumber, from, to, 
name, publicKey);
 
-      byte[] keyIdBytes = new KeyIdentifier(publicKey).getIdentifier();
-      SubjectKeyIdentifierExtension keyIdentifier = new 
SubjectKeyIdentifierExtension(keyIdBytes);
-      extensions.set(SubjectKeyIdentifierExtension.NAME, keyIdentifier);
+      JcaX509ExtensionUtils extensionUtils = new JcaX509ExtensionUtils();
+      certBuilder.addExtension(Extension.subjectKeyIdentifier, false,
+          extensionUtils.createSubjectKeyIdentifier(publicKey));
 
-      GeneralNames subjectAltNames = san();
-      if (!subjectAltNames.isEmpty()) {
-        SubjectAlternativeNameExtension altNames =
-            new SubjectAlternativeNameExtension(subjectAltNames);
-        extensions.set(SubjectAlternativeNameExtension.NAME, altNames);
+      GeneralNames subjectAltNames = subjectAlternativeNames();
+      if (subjectAltNames.getNames().length > 0) {
+        certBuilder.addExtension(Extension.subjectAlternativeName, false, 
subjectAltNames);
       }
 
       if (isCA) {
-        KeyUsageExtension usageExtension = new KeyUsageExtension();
-        usageExtension.set(KeyUsageExtension.KEY_CERTSIGN, true);
-        extensions.set(KeyUsageExtension.NAME, usageExtension);
-
-        BasicConstraintsExtension basicConstraints = new 
BasicConstraintsExtension(true, 0);
-        extensions.set(BasicConstraintsExtension.NAME, basicConstraints);
+        certBuilder.addExtension(Extension.keyUsage, true, new 
KeyUsage(KeyUsage.keyCertSign));
+        certBuilder.addExtension(Extension.basicConstraints, true, new 
BasicConstraints(0));
       }
 
       if (!extendedKeyUsages.isEmpty()) {
-        ExtendedKeyUsageExtension ekuExtension =
-            new ExtendedKeyUsageExtension(new 
java.util.Vector<>(extendedKeyUsages));
-        extensions.set(ExtendedKeyUsageExtension.NAME, ekuExtension);
-      }
-
-      if (!extensions.getAllExtensions().isEmpty()) {
-        info.set(X509CertInfo.EXTENSIONS, extensions);
+        KeyPurposeId[] keyPurposeIds = new 
KeyPurposeId[extendedKeyUsages.size()];
+        for (int i = 0; i < extendedKeyUsages.size(); i++) {
+          keyPurposeIds[i] = 
KeyPurposeId.getInstance(extendedKeyUsages.get(i));
+        }
+        certBuilder.addExtension(Extension.extendedKeyUsage, false,
+            new ExtendedKeyUsage(keyPurposeIds));
       }
 
-      // Sign the cert to identify the algorithm that's used.
-      X509CertImpl cert = new X509CertImpl(info);
-      cert.sign(privateKey, algorithm);
-
-      // Update the algorithm, and resign.
-      algo = (AlgorithmId) cert.get(X509CertImpl.SIG_ALG);
-      info.set(CertificateAlgorithmId.NAME + "." + 
CertificateAlgorithmId.ALGORITHM, algo);
-      cert = new X509CertImpl(info);
-      cert.sign(privateKey, algorithm);
-
-      return cert;
+      ContentSigner signer = new 
JcaContentSignerBuilder(algorithm).build(privateKey);
+      X509CertificateHolder certHolder = certBuilder.build(signer);
+      return new JcaX509CertificateConverter().getCertificate(certHolder);
     } catch (Exception ex) {
       throw new RuntimeException("Unable to create certificate", ex);
     }
diff --git 
a/geode-junit/src/test/java/org/apache/geode/cache/ssl/CertificateBuilderExtensionsTest.java
 
b/geode-junit/src/test/java/org/apache/geode/cache/ssl/CertificateBuilderExtensionsTest.java
new file mode 100644
index 0000000000..c082c31160
--- /dev/null
+++ 
b/geode-junit/src/test/java/org/apache/geode/cache/ssl/CertificateBuilderExtensionsTest.java
@@ -0,0 +1,116 @@
+/*
+ * 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.geode.cache.ssl;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+
+import java.net.InetAddress;
+import java.security.cert.X509Certificate;
+import java.util.Collection;
+import java.util.List;
+
+import org.junit.Test;
+
+/**
+ * Verifies that the certificates produced by {@link CertificateBuilder} carry 
the expected X.509
+ * extensions, so that the Bouncy Castle implementation is functionally 
equivalent to the previous
+ * {@code sun.security.x509}-based one (GEODE-10509).
+ */
+public class CertificateBuilderExtensionsTest {
+
+  // X509Certificate.getSubjectAlternativeNames() general-name type tags (RFC 
5280)
+  private static final int SAN_DNS = 2;
+  private static final int SAN_IP = 7;
+
+  @Test
+  public void subjectIsSetFromCommonName() {
+    X509Certificate cert = new 
CertificateBuilder().commonName("test-host").generate()
+        .getCertificate();
+
+    assertThat(cert.getSubjectX500Principal().getName())
+        .contains("CN=test-host")
+        .contains("O=Geode");
+    assertThat(cert.getVersion()).isEqualTo(3);
+  }
+
+  @Test
+  public void subjectAlternativeNamesContainDnsAndIp() throws Exception {
+    X509Certificate cert = new CertificateBuilder()
+        .commonName("test-host")
+        .sanDnsName("example.com")
+        .sanIpAddress(InetAddress.getByName("127.0.0.1"))
+        .generate()
+        .getCertificate();
+
+    Collection<List<?>> sans = cert.getSubjectAlternativeNames();
+    assertThat(sans).isNotNull();
+    assertThat(sans).anySatisfy(san -> {
+      assertThat(san.get(0)).isEqualTo(SAN_DNS);
+      assertThat(san.get(1)).isEqualTo("example.com");
+    });
+    assertThat(sans).anySatisfy(san -> {
+      assertThat(san.get(0)).isEqualTo(SAN_IP);
+      assertThat(san.get(1)).isEqualTo("127.0.0.1");
+    });
+  }
+
+  @Test
+  public void caCertificateHasBasicConstraintsAndKeyCertSign() {
+    X509Certificate ca = new CertificateBuilder().commonName("my 
ca").isCA().generate()
+        .getCertificate();
+
+    // getBasicConstraints() returns the path length (>= 0) for a CA, or -1 
for a non-CA.
+    assertThat(ca.getBasicConstraints()).isGreaterThanOrEqualTo(0);
+    // KeyUsage bit 5 is keyCertSign.
+    assertThat(ca.getKeyUsage()).isNotNull();
+    assertThat(ca.getKeyUsage()[5]).isTrue();
+  }
+
+  @Test
+  public void nonCaCertificateHasNoBasicConstraints() {
+    X509Certificate cert = new 
CertificateBuilder().commonName("leaf").generate().getCertificate();
+
+    assertThat(cert.getBasicConstraints()).isEqualTo(-1);
+  }
+
+  @Test
+  public void extendedKeyUsageContainsServerAndClientAuth() throws Exception {
+    X509Certificate cert = new CertificateBuilder()
+        .commonName("svc")
+        .serverAuthEKU()
+        .clientAuthEKU()
+        .generate()
+        .getCertificate();
+
+    assertThat(cert.getExtendedKeyUsage())
+        .contains("1.3.6.1.5.5.7.3.1", "1.3.6.1.5.5.7.3.2");
+  }
+
+  @Test
+  public void issuedCertificateIsSignedByAndChainsToTheIssuer() {
+    CertificateMaterial ca = new CertificateBuilder().commonName("my 
ca").isCA().generate();
+    X509Certificate leaf = new CertificateBuilder()
+        .commonName("leaf")
+        .issuedBy(ca)
+        .generate()
+        .getCertificate();
+
+    assertThat(leaf.getIssuerX500Principal())
+        .isEqualTo(ca.getCertificate().getSubjectX500Principal());
+    // The leaf's signature must verify against the issuer's public key.
+    assertThatCode(() -> 
leaf.verify(ca.getCertificate().getPublicKey())).doesNotThrowAnyException();
+  }
+}

Reply via email to