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>