You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@geode.apache.org by ji...@apache.org on 2026/09/13 16:35:16 UTC

(geode) branch develop updated: [GEODE-10641] Fix CertificateBuilder (#8070)

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

JinwooHwang pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/geode.git


The following commit(s) were added to refs/heads/develop by this push:
     new 8295390505 [GEODE-10641] Fix CertificateBuilder (#8070)
8295390505 is described below

commit 8295390505a7af56c9c49a34e777fda1e452fbec
Author: Jinwoo Hwang <92...@users.noreply.github.com>
AuthorDate: Sun Sep 13 12:35:10 2026 -0400

    [GEODE-10641] Fix CertificateBuilder (#8070)
    
    * [GEODE-10641] Fix CertificateBuilder
    
    * [GEODE-10641] Add CertificateBuilderJUnitTest
---
 .../gradle/plugins/DependencyConstraints.groovy    |   1 +
 geode-junit/build.gradle                           |  16 +--
 .../apache/geode/cache/ssl/CertificateBuilder.java | 135 +++++++--------------
 .../cache/ssl/CertificateBuilderJUnitTest.java     |  56 +++++++++
 geode-junit/src/test/resources/expected-pom.xml    |  15 +++
 5 files changed, 115 insertions(+), 108 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 603e56c721..1a6e6f032e 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
@@ -190,6 +190,7 @@ 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'))
+        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/geode-junit/build.gradle b/geode-junit/build.gradle
index f2c19d86b6..09456745fa 100755
--- a/geode-junit/build.gradle
+++ b/geode-junit/build.gradle
@@ -20,21 +20,6 @@ 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'
-}
-
 dependencies {
   api(platform(project(':boms:geode-all-bom')))
 
@@ -78,6 +63,7 @@ dependencies {
   implementation('org.junit.vintage:junit-vintage-engine')
 
   implementation('pl.pragmatists:JUnitParams')
+  implementation('org.bouncycastle:bcpkix-jdk18on')
 
   integrationTestImplementation(project(':geode-core'))
   integrationTestImplementation(project(':geode-serialization'))
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..d4a54b55f5 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,27 +28,23 @@ 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 javax.security.auth.x500.X500Principal;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DEROctetString;
+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.X509v3CertificateBuilder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
+import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
 
 
 /**
@@ -66,7 +60,7 @@ public class CertificateBuilder {
   private final List<InetAddress> ipAddresses;
   private boolean isCA;
   private CertificateMaterial issuer;
-  private final List<ObjectIdentifier> extendedKeyUsages;
+  private final List<KeyPurposeId> extendedKeyUsages;
 
   public CertificateBuilder() {
     this(30, "SHA256withRSA");
@@ -81,27 +75,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, new DEROctetString(hostAddress.getAddress()));
   }
 
   public CertificateBuilder commonName(String cn) {
-    try {
-      name = new X500Name("O=Geode, CN=" + cn);
-    } catch (IOException ex) {
-      throw new UncheckedIOException(ex);
-    }
+    name = X500Name.getInstance(new X500Principal("O=Geode, CN=" + cn).getEncoded());
     return this;
   }
 
@@ -142,12 +124,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(KeyPurposeId.getInstance(new ASN1ObjectIdentifier(oid)));
     }
     return this;
   }
@@ -166,8 +144,8 @@ public class CertificateBuilder {
     return extendedKeyUsage("1.3.6.1.5.5.7.3.1");
   }
 
-  private GeneralNames san() throws IOException {
-    GeneralNames names = new GeneralNames();
+  private GeneralNames san() {
+    List<GeneralName> names = new ArrayList<>();
     for (String name : dnsNames) {
       names.add(CertificateBuilder.dnsGeneralName(name));
     }
@@ -176,7 +154,7 @@ public class CertificateBuilder {
       names.add(CertificateBuilder.ipGeneralName(address));
     }
 
-    return names;
+    return new GeneralNames(names.toArray(new GeneralName[0]));
   }
 
   public CertificateMaterial generate() {
@@ -203,70 +181,41 @@ public class CertificateBuilder {
     Date from = new Date();
     Date to = new Date(from.getTime() + days * 86_400_000L);
 
-    CertificateValidity interval = new CertificateValidity(from, to);
     BigInteger sn = new BigInteger(64, new SecureRandom());
 
-    X509CertInfo info = new X509CertInfo();
-
     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));
-
+      X500Name issuerName;
       if (issuer == null) {
         // This is a self-signed certificate
-        info.set(X509CertInfo.ISSUER, name);
+        issuerName = name;
       } else {
-        info.set(X509CertInfo.ISSUER, issuer.getCertificate().getSubjectDN());
+        issuerName =
+            X500Name.getInstance(issuer.getCertificate().getSubjectX500Principal().getEncoded());
       }
 
-      CertificateExtensions extensions = new CertificateExtensions();
+      X509v3CertificateBuilder builder =
+          new JcaX509v3CertificateBuilder(issuerName, sn, from, to, name, publicKey);
 
-      byte[] keyIdBytes = new KeyIdentifier(publicKey).getIdentifier();
-      SubjectKeyIdentifierExtension keyIdentifier = new SubjectKeyIdentifierExtension(keyIdBytes);
-      extensions.set(SubjectKeyIdentifierExtension.NAME, keyIdentifier);
+      builder.addExtension(Extension.subjectKeyIdentifier, false,
+          new JcaX509ExtensionUtils().createSubjectKeyIdentifier(publicKey));
 
       GeneralNames subjectAltNames = san();
-      if (!subjectAltNames.isEmpty()) {
-        SubjectAlternativeNameExtension altNames =
-            new SubjectAlternativeNameExtension(subjectAltNames);
-        extensions.set(SubjectAlternativeNameExtension.NAME, altNames);
+      if (subjectAltNames.getNames().length > 0) {
+        builder.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);
+        builder.addExtension(Extension.keyUsage, true, new KeyUsage(KeyUsage.keyCertSign));
+        builder.addExtension(Extension.basicConstraints, true, new BasicConstraints(0));
       }
 
       if (!extendedKeyUsages.isEmpty()) {
-        ExtendedKeyUsageExtension ekuExtension =
-            new ExtendedKeyUsageExtension(new java.util.Vector<>(extendedKeyUsages));
-        extensions.set(ExtendedKeyUsageExtension.NAME, ekuExtension);
+        builder.addExtension(Extension.extendedKeyUsage, false,
+            new ExtendedKeyUsage(extendedKeyUsages.toArray(new KeyPurposeId[0])));
       }
 
-      if (!extensions.getAllExtensions().isEmpty()) {
-        info.set(X509CertInfo.EXTENSIONS, extensions);
-      }
-
-      // 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;
+      return new JcaX509CertificateConverter()
+          .getCertificate(builder.build(new JcaContentSignerBuilder(algorithm).build(privateKey)));
     } catch (Exception ex) {
       throw new RuntimeException("Unable to create certificate", ex);
     }
diff --git a/geode-junit/src/test/java/org/apache/geode/cache/ssl/CertificateBuilderJUnitTest.java b/geode-junit/src/test/java/org/apache/geode/cache/ssl/CertificateBuilderJUnitTest.java
new file mode 100644
index 0000000000..0bf76c4f34
--- /dev/null
+++ b/geode-junit/src/test/java/org/apache/geode/cache/ssl/CertificateBuilderJUnitTest.java
@@ -0,0 +1,56 @@
+/*
+ * 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.security.cert.X509Certificate;
+
+import org.junit.Test;
+
+public class CertificateBuilderJUnitTest {
+
+  private static final int DNS_NAME = 2;
+  private static final int IP_ADDRESS = 7;
+  private static final String SERVER_AUTH = "1.3.6.1.5.5.7.3.1";
+
+  @Test
+  public void generatedCertificateContainsRequestedExtensions() throws Exception {
+    CertificateMaterial issuer = new CertificateBuilder()
+        .commonName("issuer")
+        .isCA()
+        .generate();
+
+    CertificateMaterial material = new CertificateBuilder()
+        .commonName("server")
+        .sanDnsName("localhost")
+        .sanIpAddress("127.0.0.1")
+        .isCA()
+        .serverAuthEKU()
+        .issuedBy(issuer)
+        .generate();
+
+    X509Certificate certificate = material.getCertificate();
+
+    assertThat(certificate.getSubjectAlternativeNames())
+        .extracting(name -> name.get(0) + ":" + name.get(1))
+        .containsExactlyInAnyOrder(DNS_NAME + ":localhost", IP_ADDRESS + ":127.0.0.1");
+    assertThat(certificate.getExtendedKeyUsage()).containsExactly(SERVER_AUTH);
+    assertThat(certificate.getBasicConstraints()).isNotEqualTo(-1);
+    assertThatCode(() -> certificate.verify(issuer.getPublicKey()))
+        .doesNotThrowAnyException();
+  }
+}
diff --git a/geode-junit/src/test/resources/expected-pom.xml b/geode-junit/src/test/resources/expected-pom.xml
index 87dbcf8aa1..3533e15592 100644
--- a/geode-junit/src/test/resources/expected-pom.xml
+++ b/geode-junit/src/test/resources/expected-pom.xml
@@ -384,5 +384,20 @@
         </exclusion>
       </exclusions>
     </dependency>
+    <dependency>
+      <groupId>org.bouncycastle</groupId>
+      <artifactId>bcpkix-jdk18on</artifactId>
+      <scope>runtime</scope>
+      <exclusions>
+        <exclusion>
+          <artifactId>log4j-to-slf4j</artifactId>
+          <groupId>org.apache.logging.log4j</groupId>
+        </exclusion>
+        <exclusion>
+          <artifactId>*</artifactId>
+          <groupId>ch.qos.logback</groupId>
+        </exclusion>
+      </exclusions>
+    </dependency>
   </dependencies>
 </project>