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 603e56c72184..1a6e6f032e63 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 f2c19d86b668..09456745fa38 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 66cec0267032..d4a54b55f569 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.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 ipAddresses; private boolean isCA; private CertificateMaterial issuer; - private final List extendedKeyUsages; + private final List extendedKeyUsages; public CertificateBuilder() { this(30, "SHA256withRSA"); @@ -81,27 +75,15 @@ public CertificateBuilder(int days, String algorithm) { } 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 CertificateBuilder issuedBy(CertificateMaterial issuer) { * - "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 CertificateBuilder serverAuthEKU() { return extendedKeyUsage("1.3.6.1.5.5.7.3.1"); } - private GeneralNames san() throws IOException { - GeneralNames names = new GeneralNames(); + private GeneralNames san() { + List names = new ArrayList<>(); for (String name : dnsNames) { names.add(CertificateBuilder.dnsGeneralName(name)); } @@ -176,7 +154,7 @@ private GeneralNames san() throws IOException { names.add(CertificateBuilder.ipGeneralName(address)); } - return names; + return new GeneralNames(names.toArray(new GeneralName[0])); } public CertificateMaterial generate() { @@ -203,70 +181,41 @@ private X509Certificate generate(PublicKey publicKey, PrivateKey privateKey) { 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 000000000000..0bf76c4f34af --- /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 87dbcf8aa1c1..3533e1559227 100644 --- a/geode-junit/src/test/resources/expected-pom.xml +++ b/geode-junit/src/test/resources/expected-pom.xml @@ -384,5 +384,20 @@ + + org.bouncycastle + bcpkix-jdk18on + runtime + + + log4j-to-slf4j + org.apache.logging.log4j + + + * + ch.qos.logback + + +