This is an automated email from the ASF dual-hosted git repository.
markt pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/main by this push:
new 4c1f613b0b Replace OpenSSL OCSP responder with an in-process
BouncyCastle solution
4c1f613b0b is described below
commit 4c1f613b0bd2ff67f4fefa8609f036340f14ba4e
Author: Mark Thomas <[email protected]>
AuthorDate: Fri Mar 6 08:59:01 2026 +0000
Replace OpenSSL OCSP responder with an in-process BouncyCastle solution
---
build.properties.default | 20 ++
build.xml | 33 +++
res/ide-support/eclipse/eclipse.classpath | 3 +
res/ide-support/idea/tomcat.iml | 27 +++
.../netbeans/nb-tomcat-build.properties | 2 +-
res/ide-support/netbeans/project.xml | 2 +-
.../tomcat/security/TestSecurity2017Ocsp.java | 4 +-
.../tomcat/util/net/ocsp/TestOcspEnabled.java | 5 +-
.../tomcat/util/net/ocsp/TesterOcspResponder.java | 139 +++++-------
.../util/net/ocsp/TesterOcspResponderServlet.java | 238 +++++++++++++++++++++
webapps/docs/changelog.xml | 4 +
11 files changed, 385 insertions(+), 92 deletions(-)
diff --git a/build.properties.default b/build.properties.default
index ee293d5600..d1be9797a7 100644
--- a/build.properties.default
+++ b/build.properties.default
@@ -349,3 +349,23 @@
derby-shared.jar=${derby.home}/derby-shared-${derby.version}.jar
derby-shared.loc=${base-maven.loc}/org/apache/derby/derbyshared/${derby.version}/derbyshared-${derby.version}.jar
derby-tools.jar=${derby.home}/derby-tools-${derby.version}.jar
derby-tools.loc=${base-maven.loc}/org/apache/derby/derbytools/${derby.version}/derbytools-${derby.version}.jar
+
+# ----- Bouncy Castle, used by unit tests ----
+bouncycastle.version=1.83
+bouncycastle-provider.checksum.enabled=true
+bouncycastle-provider.checksum.algorithm=SHA-512
+bouncycastle-provider.checksum.value=e51cc843ca130ad4a15ff667360063bbb583af3f22e14193840a734da6665b470ba1855ce975f88a94ddd6419aa020d3a9966980bc1deb7514f92a7215d6e229
+bouncycastle-pkix.checksum.enabled=true
+bouncycastle-pkix.checksum.algorithm=SHA-512
+bouncycastle-pkix.checksum.value=9c67d990a56a5c448f9bb9edbb8b99dc15971e16de7e6f10c3eab129a1389eef4882c5a5d910ac4ddc27d44f7bc9fa4054c7f56dc031154c131bccc50ebd67b9
+bouncycastle-util.checksum.enabled=true
+bouncycastle-util.checksum.algorithm=SHA-512
+bouncycastle-util.checksum.value=e19831d4afc0a709fd57694f33bfe3a8e881cba287c34fb076a44ef436e56e6bdf49299ca9525028a4de9bf5fadeaa254654d0e527855f1f5659c5a7be538576
+
+bouncycastle.home=${base.path}/bouncycastle-${bouncycastle.version}
+bouncycastle-provider.jar=${bouncycastle.home}/bouncycastle-provider-${bouncycastle.version}.jar
+bouncycastle-provider.loc=${base-maven.loc}/org/bouncycastle/bcprov-jdk18on/${bouncycastle.version}/bcprov-jdk18on-${bouncycastle.version}.jar
+bouncycastle-pkix.jar=${bouncycastle.home}/bouncycastle-pkix-${bouncycastle.version}.jar
+bouncycastle-pkix.loc=${base-maven.loc}/org/bouncycastle/bcpkix-jdk18on/${bouncycastle.version}/bcpkix-jdk18on-${bouncycastle.version}.jar
+bouncycastle-util.jar=${bouncycastle.home}/bouncycastle-util-${bouncycastle.version}.jar
+bouncycastle-util.loc=${base-maven.loc}/org/bouncycastle/bcutil-jdk18on/${bouncycastle.version}/bcutil-jdk18on-${bouncycastle.version}.jar
diff --git a/build.xml b/build.xml
index 31fe59cb9a..6c26f11dc9 100644
--- a/build.xml
+++ b/build.xml
@@ -257,6 +257,9 @@
<pathelement location="${derby.jar}"/>
<pathelement location="${derby-shared.jar}"/>
<pathelement location="${derby-tools.jar}"/>
+ <pathelement location="${bouncycastle-provider.jar}"/>
+ <pathelement location="${bouncycastle-pkix.jar}"/>
+ <pathelement location="${bouncycastle-util.jar}"/>
<path refid="compile.classpath" />
<path refid="tomcat.classpath" />
</path>
@@ -273,6 +276,9 @@
<filter token="MIGRATION_JAR" value="${migration-lib.jar}"/>
<filter token="UNBOUNDID_JAR" value="${unboundid.jar}"/>
<filter token="JUNIT_JAR" value="${junit.jar}"/>
+ <filter token="BC_PROVIDER_JAR" value="${bouncycastle-provider.jar}"/>
+ <filter token="BC_PKIX_JAR" value="${bouncycastle-pkix.jar}"/>
+ <filter token="BC_UTIL_JAR" value="${bouncycastle-util.jar}"/>
<filter token="OUTPUT_DIR" value="${tomcat.output}"/>
</filterset>
@@ -3753,6 +3759,33 @@ Configured for ${release.asfusername} to release Tomcat
${version.major}.${versi
<param name="checksum.value" value="${derby-tools.checksum.value}"/>
</antcall>
+ <antcall target="downloadfile">
+ <param name="sourcefile" value="${bouncycastle-provider.loc}"/>
+ <param name="destfile" value="${bouncycastle-provider.jar}"/>
+ <param name="destdir" value="${bouncycastle.home}"/>
+ <param name="checksum.enabled"
value="${bouncycastle-provider.checksum.enabled}"/>
+ <param name="checksum.algorithm"
value="${bouncycastle-provider.checksum.algorithm}"/>
+ <param name="checksum.value"
value="${bouncycastle-provider.checksum.value}"/>
+ </antcall>
+
+ <antcall target="downloadfile">
+ <param name="sourcefile" value="${bouncycastle-pkix.loc}"/>
+ <param name="destfile" value="${bouncycastle-pkix.jar}"/>
+ <param name="destdir" value="${bouncycastle.home}"/>
+ <param name="checksum.enabled"
value="${bouncycastle-pkix.checksum.enabled}"/>
+ <param name="checksum.algorithm"
value="${bouncycastle-pkix.checksum.algorithm}"/>
+ <param name="checksum.value"
value="${bouncycastle-pkix.checksum.value}"/>
+ </antcall>
+
+ <antcall target="downloadfile">
+ <param name="sourcefile" value="${bouncycastle-util.loc}"/>
+ <param name="destfile" value="${bouncycastle-util.jar}"/>
+ <param name="destdir" value="${bouncycastle.home}"/>
+ <param name="checksum.enabled"
value="${bouncycastle-util.checksum.enabled}"/>
+ <param name="checksum.algorithm"
value="${bouncycastle-util.checksum.algorithm}"/>
+ <param name="checksum.value"
value="${bouncycastle-util.checksum.value}"/>
+ </antcall>
+
</target>
<target name="download-jacoco"
diff --git a/res/ide-support/eclipse/eclipse.classpath
b/res/ide-support/eclipse/eclipse.classpath
index f4059ab458..fbba3531e2 100644
--- a/res/ide-support/eclipse/eclipse.classpath
+++ b/res/ide-support/eclipse/eclipse.classpath
@@ -38,5 +38,8 @@
<classpathentry kind="lib" path="@BND_JAR@"/>
<classpathentry kind="lib" path="@MIGRATION_JAR@"/>
<classpathentry kind="lib" path="@UNBOUNDID_JAR@"/>
+ <classpathentry kind="lib" path="@BC_PROVIDER_JAR@"/>
+ <classpathentry kind="lib" path="@BC_PKIX_JAR@"/>
+ <classpathentry kind="lib" path="@BC_UTIL_JAR@"/>
<classpathentry kind="output" path=".settings/output"/>
</classpath>
diff --git a/res/ide-support/idea/tomcat.iml b/res/ide-support/idea/tomcat.iml
index 354b60eeea..419b561a2f 100644
--- a/res/ide-support/idea/tomcat.iml
+++ b/res/ide-support/idea/tomcat.iml
@@ -126,6 +126,33 @@
<SOURCES />
</library>
</orderEntry>
+ <orderEntry type="module-library">
+ <library>
+ <CLASSES>
+ <root url="jar://@BC_PROVIDER_JAR@!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library">
+ <library>
+ <CLASSES>
+ <root url="jar://@BC_PKIX_JAR@!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library">
+ <library>
+ <CLASSES>
+ <root url="jar://@BC_UTIL_JAR@!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
<orderEntry type="module-library">
<library>
diff --git a/res/ide-support/netbeans/nb-tomcat-build.properties
b/res/ide-support/netbeans/nb-tomcat-build.properties
index 0f9c96d049..a97609bf18 100644
--- a/res/ide-support/netbeans/nb-tomcat-build.properties
+++ b/res/ide-support/netbeans/nb-tomcat-build.properties
@@ -37,7 +37,7 @@ nb-test.io-method=org.apache.coyote.http11.Http11NioProtocol
# it is not possible to retrieve the classpaths from the build to
# use in the NetBeans targets, so they must be explicitly declared
-nb-test.classpath=${test.classes}:${tomcat.build}/webapps/examples/WEB-INF/classes:@JUNIT_JAR@:@EASYMOCK_JAR@:@BYTEBUDDY_JAR@:@OBJENESIS_JAR@:@HAMCREST_JAR@:@ECJ_JAR@:@UNBOUNDID_JAR@:${tomcat.classes}
+nb-test.classpath=${test.classes}:${tomcat.build}/webapps/examples/WEB-INF/classes:@JUNIT_JAR@:@EASYMOCK_JAR@:@BYTEBUDDY_JAR@:@OBJENESIS_JAR@:@HAMCREST_JAR@:@ECJ_JAR@:@UNBOUNDID_JAR@:@BC_PROVIDER_JAR@:@BC_PKIX_JAR@:@BC_UTIL_JAR@:${tomcat.classes}
# Extra properties used by the Tomcat project additional NetBeans targets.
diff --git a/res/ide-support/netbeans/project.xml
b/res/ide-support/netbeans/project.xml
index 42a3aa6a3c..8315b50bf0 100644
--- a/res/ide-support/netbeans/project.xml
+++ b/res/ide-support/netbeans/project.xml
@@ -189,7 +189,7 @@
<compilation-unit>
<package-root>test</package-root>
<unit-tests/>
- <classpath
mode="compile">output/classes:output/testclasses:output/build/webapps/examples/WEB-INF/classes:@JUNIT_JAR@:@EASYMOCK_JAR@:@BYTEBUDDY_JAR@:@OBJENESIS_JAR@:@HAMCREST_JAR@:@UNBOUNDID_JAR@</classpath>
+ <classpath
mode="compile">output/classes:output/testclasses:output/build/webapps/examples/WEB-INF/classes:@JUNIT_JAR@:@EASYMOCK_JAR@:@BYTEBUDDY_JAR@:@OBJENESIS_JAR@:@HAMCREST_JAR@:@UNBOUNDID_JAR@:@BC_PROVIDER_JAR@:@BC_PKIX_JAR@:@BC_UTIL_JAR@</classpath>
<source-level>@BUILD_JAVA_VERSION@</source-level>
</compilation-unit>
</java-data>
diff --git a/test/org/apache/tomcat/security/TestSecurity2017Ocsp.java
b/test/org/apache/tomcat/security/TestSecurity2017Ocsp.java
index e6cb01e0ae..570d7510ff 100644
--- a/test/org/apache/tomcat/security/TestSecurity2017Ocsp.java
+++ b/test/org/apache/tomcat/security/TestSecurity2017Ocsp.java
@@ -16,8 +16,6 @@
*/
package org.apache.tomcat.security;
-import java.io.IOException;
-
import javax.net.ssl.SSLHandshakeException;
import jakarta.servlet.http.HttpServletResponse;
@@ -50,7 +48,7 @@ public class TestSecurity2017Ocsp extends OcspBaseTest {
ocspResponder = new TesterOcspResponder();
try {
ocspResponder.start();
- } catch (IOException ioe) {
+ } catch (Exception e) {
ocspResponder = null;
}
}
diff --git a/test/org/apache/tomcat/util/net/ocsp/TestOcspEnabled.java
b/test/org/apache/tomcat/util/net/ocsp/TestOcspEnabled.java
index e47d06eb38..df96e97e0a 100644
--- a/test/org/apache/tomcat/util/net/ocsp/TestOcspEnabled.java
+++ b/test/org/apache/tomcat/util/net/ocsp/TestOcspEnabled.java
@@ -16,7 +16,6 @@
*/
package org.apache.tomcat.util.net.ocsp;
-import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@@ -43,8 +42,8 @@ public class TestOcspEnabled extends OcspBaseTest {
ocspResponder = new TesterOcspResponder();
try {
ocspResponder.start();
- } catch (IOException ioe) {
- ocspResponder = null;
+ } catch (Exception e) {
+ e.printStackTrace();
}
}
diff --git a/test/org/apache/tomcat/util/net/ocsp/TesterOcspResponder.java
b/test/org/apache/tomcat/util/net/ocsp/TesterOcspResponder.java
index c6aa974b2c..ac069c3052 100644
--- a/test/org/apache/tomcat/util/net/ocsp/TesterOcspResponder.java
+++ b/test/org/apache/tomcat/util/net/ocsp/TesterOcspResponder.java
@@ -16,108 +16,79 @@
*/
package org.apache.tomcat.util.net.ocsp;
-import java.io.IOException;
-import java.io.PrintStream;
-import java.io.Reader;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
+import java.io.File;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
-import org.junit.Assert;
+import org.apache.catalina.Context;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.connector.Connector;
+import org.apache.catalina.startup.ExpandWar;
+import org.apache.catalina.startup.Tomcat;
-import org.apache.tomcat.util.net.TesterSupport;
-
-/*
- * The OpenSSL ocsp tool is great, but it does generate a lot of output. That
output needs to be swallowed, else the
- * process will freeze when the output buffers (stdout and stderr) are full.
- *
- * There is a command line option to redirect stdout (which could be
redirected to /dev/null), but there is no option to
- * redirect stderr. Therefore, this class uses a couple of dedicated threads
to read stdout and stderr. By default, the
- * output is ignored, but it can be dumped to Java's stdout/stderr if required
for debugging purposes.
- */
public class TesterOcspResponder {
- private static List<String> ocspArgs = Arrays.asList("ocsp", "-port",
"8888", "-text", "-index",
- TesterSupport.DB_INDEX, "-CA", TesterSupport.CA_CERT_PEM, "-rkey",
TesterSupport.OCSP_RESPONDER_RSA_KEY,
- "-rsigner", TesterSupport.OCSP_RESPONDER_RSA_CERT, "-nmin", "60");
+ private File catalinaBase;
+ private Tomcat ocspResponder;
- private Process p;
+ public void start() throws Exception {
+ ocspResponder = new Tomcat();
- public void start() throws IOException {
- if (p != null) {
- throw new IllegalStateException("Already started");
- }
+ Connector connector = new Connector("HTTP/1.1");
+ connector.setPort(8888);
+ connector.setThrowOnFailure(true);
+ connector.setEncodedSolidusHandling("passthrough");
+ ocspResponder.getService().addConnector(connector);
- String openSSLPath = System.getProperty("tomcat.test.openssl.path");
- String openSSLLibPath = null;
- if (openSSLPath == null || openSSLPath.length() == 0) {
- openSSLPath = "openssl";
- } else {
- // Explicit OpenSSL path may also need explicit lib path
- // (e.g. Gump needs this)
- openSSLLibPath = openSSLPath.substring(0,
openSSLPath.lastIndexOf('/'));
- openSSLLibPath = openSSLLibPath + "/../:" + openSSLLibPath +
"/../lib:" + openSSLLibPath + "/../lib64";
+ // Create a temporary directory structure for the OCSP responder
+ File tempBase = new File(System.getProperty("tomcat.test.temp",
"output/tmp"));
+ if (!tempBase.mkdirs() && !tempBase.isDirectory()) {
+ throw new IllegalStateException("Unable to create tempBase");
}
- List<String> cmd = new ArrayList<>();
- cmd.add(openSSLPath);
- cmd.addAll(ocspArgs);
- ProcessBuilder pb = new ProcessBuilder(cmd.toArray(new String[0]));
-
- if (openSSLLibPath != null) {
- Map<String,String> env = pb.environment();
- String libraryPath = env.get("LD_LIBRARY_PATH");
- if (libraryPath == null) {
- libraryPath = openSSLLibPath;
- } else {
- libraryPath = libraryPath + ":" + openSSLLibPath;
- }
- env.put("LD_LIBRARY_PATH", libraryPath);
+ // Create and configure CATALINA_BASE
+ Path tempBasePath =
FileSystems.getDefault().getPath(tempBase.getAbsolutePath());
+ catalinaBase = Files.createTempDirectory(tempBasePath,
"ocsp").toFile();
+ if (!catalinaBase.isDirectory()) {
+ throw new IllegalStateException("Unable to create CATALINA_BASE
for OCSP responder");
}
+ ocspResponder.setBaseDir(catalinaBase.getAbsolutePath());
- p = pb.start();
+ // Create and configure a web apps directory
+ File appBase = new File(catalinaBase, "webapps");
+ if (!appBase.exists() && !appBase.mkdir()) {
+ throw new IllegalStateException("Unable to create appBase for OCSP
responder");
+ }
+ ocspResponder.getHost().setAppBase(appBase.getAbsolutePath());
- redirect(p.inputReader(), System.out, true);
- redirect(p.errorReader(), System.err, true);
+ // Configure the ROOT web application
+ // No file system docBase required
+ Context ctx = ocspResponder.addContext("", null);
+ Tomcat.addServlet(ctx, "responder", new TesterOcspResponderServlet());
+ ctx.addServletMappingDecoded("/", "responder");
- Assert.assertTrue(p.isAlive());
+ // Start the responder
+ ocspResponder.start();
}
public void stop() {
- if (p == null) {
- throw new IllegalStateException("Not started");
- }
- p.destroy();
-
- try {
- if (!p.waitFor(30, TimeUnit.SECONDS)) {
- throw new IllegalStateException("Failed to stop");
+ if (ocspResponder != null) {
+ try {
+ ocspResponder.stop();
+ } catch (LifecycleException e) {
+ // Good enough for testing
+ e.printStackTrace();
}
- } catch (InterruptedException e) {
- throw new IllegalStateException("Interrupted while waiting to
stop", e);
- }
- }
-
-
- private void redirect(final Reader r, final PrintStream os, final boolean
swallow) {
- /*
- * InputStream will close when process ends. Thread will exit once
stream closes.
- */
- new Thread( () -> {
- char[] cbuf = new char[1024];
try {
- int read;
- while ((read = r.read(cbuf)) > 0) {
- if (!swallow) {
- os.print(new String(cbuf, 0, read));
- }
- }
- } catch (IOException ignore) {
- // Ignore
+ ocspResponder.destroy();
+ } catch (LifecycleException e) {
+ // Good enough for testing
+ e.printStackTrace();
}
-
- }).start();
+ }
+ if (catalinaBase != null) {
+ ExpandWar.deleteDir(catalinaBase);
+ }
}
}
diff --git
a/test/org/apache/tomcat/util/net/ocsp/TesterOcspResponderServlet.java
b/test/org/apache/tomcat/util/net/ocsp/TesterOcspResponderServlet.java
new file mode 100644
index 0000000000..a61de5210b
--- /dev/null
+++ b/test/org/apache/tomcat/util/net/ocsp/TesterOcspResponderServlet.java
@@ -0,0 +1,238 @@
+/*
+ * 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.tomcat.util.net.ocsp;
+
+import java.io.FileReader;
+import java.io.IOException;
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
+import java.security.PrivateKey;
+import java.security.Provider;
+import java.security.Security;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.Base64;
+import java.util.Date;
+
+import jakarta.servlet.ServletConfig;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.ServletOutputStream;
+import jakarta.servlet.http.HttpServlet;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+import org.apache.catalina.util.IOTools;
+import org.apache.tomcat.util.http.fileupload.ByteArrayOutputStream;
+import org.apache.tomcat.util.net.TesterSupport;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
+import org.bouncycastle.cert.ocsp.BasicOCSPResp;
+import org.bouncycastle.cert.ocsp.BasicOCSPRespBuilder;
+import org.bouncycastle.cert.ocsp.CertificateID;
+import org.bouncycastle.cert.ocsp.CertificateStatus;
+import org.bouncycastle.cert.ocsp.OCSPException;
+import org.bouncycastle.cert.ocsp.OCSPReq;
+import org.bouncycastle.cert.ocsp.OCSPResp;
+import org.bouncycastle.cert.ocsp.OCSPRespBuilder;
+import org.bouncycastle.cert.ocsp.Req;
+import org.bouncycastle.cert.ocsp.RespID;
+import org.bouncycastle.cert.ocsp.RevokedStatus;
+import org.bouncycastle.cert.ocsp.UnknownStatus;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.openssl.PEMParser;
+import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.DigestCalculatorProvider;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
+
+/*
+ * Based on https://github.com/wdawson/revoker - ALv2 licensed
+ */
+public class TesterOcspResponderServlet extends HttpServlet {
+
+ private static final long serialVersionUID = 1L;
+
+ private DigestCalculatorProvider digestCalculatorProvider;
+ private X509CertificateHolder[] responderCertificateChain;
+ private RespID responderID;
+ private ContentSigner contentSigner;
+
+
+ @Override
+ public void init(ServletConfig config) throws ServletException {
+ // Enable the Bouncy Castle Provider
+ Provider provider = new BouncyCastleProvider();
+ Security.addProvider(provider);
+
+ // Create the digest provider
+ try {
+ this.digestCalculatorProvider = new
JcaDigestCalculatorProviderBuilder().setProvider("BC").build();
+ } catch (OperatorCreationException e) {
+ throw new ServletException(e);
+ }
+
+ // Parse the OCSP responder cert
+ X509Certificate responderCert;
+ try (PEMParser pemParser = new PEMParser(new
FileReader(TesterSupport.OCSP_RESPONDER_RSA_CERT))) {
+ JcaX509CertificateConverter x509Converter = new
JcaX509CertificateConverter().setProvider(provider);
+ responderCert =
x509Converter.getCertificate((X509CertificateHolder) pemParser.readObject());
+ } catch (IOException | CertificateException e) {
+ throw new ServletException(e);
+ }
+
+ // Parse the OCSP responder issuer certificate
+ X509Certificate issuerCert;
+ try (PEMParser pemParser = new PEMParser(new
FileReader(TesterSupport.CA_CERT_PEM))) {
+ JcaX509CertificateConverter x509Converter = new
JcaX509CertificateConverter().setProvider(provider);
+ issuerCert = x509Converter.getCertificate((X509CertificateHolder)
pemParser.readObject());
+ } catch (IOException | CertificateException e) {
+ throw new ServletException(e);
+ }
+
+ // Create the responder certificate chain
+ try {
+ responderCertificateChain = new X509CertificateHolder[] { new
JcaX509CertificateHolder(responderCert),
+ new JcaX509CertificateHolder(issuerCert) };
+ } catch (CertificateEncodingException e) {
+ throw new ServletException(e);
+ }
+
+ // Create the responder ID
+ SubjectPublicKeyInfo publicKeyInfo =
+
SubjectPublicKeyInfo.getInstance(responderCert.getPublicKey().getEncoded());
+
+ try {
+ // Only SHA-1 supported
+ responderID = new RespID(publicKeyInfo,
+ digestCalculatorProvider.get(new
DefaultDigestAlgorithmIdentifierFinder().find("SHA-1")));
+ } catch (OperatorCreationException | OCSPException e) {
+ throw new ServletException(e);
+ }
+
+ // Parse the private key
+ PrivateKey responderKey;
+ try (PEMParser pemParser = new PEMParser(new
FileReader(TesterSupport.OCSP_RESPONDER_RSA_KEY))) {
+ PrivateKeyInfo privateKeyInfo =
PrivateKeyInfo.getInstance(pemParser.readObject());
+ JcaPEMKeyConverter converter = new JcaPEMKeyConverter();
+ responderKey = converter.getPrivateKey(privateKeyInfo);
+ } catch (IOException e) {
+ throw new ServletException(e);
+ }
+
+ // Create the content signer
+ try {
+ contentSigner = new
JcaContentSignerBuilder("SHA256withRSA").setProvider("BC").build(responderKey);
+ } catch (OperatorCreationException e) {
+ throw new ServletException(e);
+ }
+ }
+
+
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
+
+ // The request is base64 encoded and passed as the path (less the
leading '/')
+ String urlEncoded = req.getRequestURI().substring(1);
+
+ // Handle longer URI used for TestSecurity2017Ocsp
+ if (urlEncoded.startsWith("xxxxxxxx")) {
+ urlEncoded = urlEncoded.substring(urlEncoded.indexOf("/") + 1);
+ }
+ String base64 = URLDecoder.decode(urlEncoded,
StandardCharsets.US_ASCII);
+ byte[] derEncodeOCSPRequest = Base64.getDecoder().decode(base64);
+
+ // Process the OCSP request
+ OCSPResp ocspResponse = processOscpRequest(derEncodeOCSPRequest);
+
+ // Write the OCSP response
+ ServletOutputStream sos = resp.getOutputStream();
+ sos.write(ocspResponse.getEncoded());
+ }
+
+
+ @Override
+ protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
+ // The request is passed in the request body
+
+ // Determine request content length (or start with a reasonable
default)
+ int contentLength = req.getContentLength();
+ if (contentLength == -1) {
+ // OCSP requests are small. 1k should be plenty and it can expand
if necessary.
+ contentLength = 1024;
+ }
+
+ // Read the body into a byte array
+ ByteArrayOutputStream baos = new ByteArrayOutputStream(contentLength);
+ IOTools.flow(req.getInputStream(), baos);
+
+ // Process the OCSP request
+ OCSPResp ocspResponse = processOscpRequest(baos.toByteArray());
+
+ // Write the OCSP response
+ ServletOutputStream sos = resp.getOutputStream();
+ sos.write(ocspResponse.getEncoded());
+ }
+
+
+ private OCSPResp processOscpRequest(byte[] derEncodeOCSPRequest) throws
ServletException, IOException {
+
+ OCSPReq ocspReq = new OCSPReq(derEncodeOCSPRequest);
+
+ // For the tests as currently written it is safe to assume the request
is valid
+
+ // Set the responses for each certificate
+ BasicOCSPRespBuilder responseBuilder = new
BasicOCSPRespBuilder(responderID);
+ Req[] requests = ocspReq.getRequestList();
+ for (Req request : requests) {
+ CertificateID certificateID = request.getCertID();
+ switch (certificateID.getSerialNumber().intValue()) {
+ // TODO read index.db rather than hard-code certificate serial
numbers
+ case 4096:
+ case 4098:
+ case 4100:
+ case 4101:
+ responseBuilder.addResponse(certificateID,
CertificateStatus.GOOD);
+ break;
+ case 4097:
+ case 4099:
+ case 4102:
+ responseBuilder.addResponse(certificateID, new
RevokedStatus(new Date(0)));
+ break;
+ default:
+ responseBuilder.addResponse(certificateID, new
UnknownStatus());
+ }
+ }
+
+ // Build and sign the response
+ OCSPResp ocspResponse;
+ try {
+ BasicOCSPResp basicResponse = responseBuilder.build(contentSigner,
responderCertificateChain, new Date());
+ ocspResponse = new
OCSPRespBuilder().build(OCSPRespBuilder.SUCCESSFUL, basicResponse);
+ } catch (OCSPException e) {
+ throw new ServletException(e);
+ }
+
+ return ocspResponse;
+ }
+}
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index b1e79bfa6d..c905cdcb7d 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -284,6 +284,10 @@
<code>Integer</code> rather than a <code>Long</code> to be consistent
with how port is exposed in the Servlet API. (markt)
</fix>
+ <scode>
+ Replace the external OpenSSL based OCSP responder used during unit
tests
+ with a Bouncy Castle based, in-process Java OCSP responder. (markt)
+ </scode>
</changelog>
</subsection>
<subsection name="Jasper">
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]