You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tika.apache.org by ta...@apache.org on 2026/10/23 17:31:03 UTC
(tika) branch main updated: TIKA-4994: improve tls configuration (#3407)
This is an automated email from the ASF dual-hosted git repository.
tballison pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tika.git
The following commit(s) were added to refs/heads/main by this push:
new 0f8ffbeb91 TIKA-4994: improve tls configuration (#3407)
0f8ffbeb91 is described below
commit 0f8ffbeb91ab37246b2dc7b5e7743d37fe39940b
Author: Tim Allison <ta...@apache.org>
AuthorDate: Fri Oct 23 13:30:56 2026 -0400
TIKA-4994: improve tls configuration (#3407)
Co-Authored-By: Claude Opus 5.5 <no...@anthropic.com>
---
CHANGES.txt | 22 +++
docs/modules/ROOT/pages/using-tika/server/tls.adoc | 185 ++++++++++++-------
.../server/core/ClientCertificateTrustManager.java | 142 +++++++++++++++
.../apache/tika/server/core/TikaServerConfig.java | 12 +-
.../apache/tika/server/core/TikaServerProcess.java | 42 +----
.../org/apache/tika/server/core/TlsConfig.java | 175 ++++++++++--------
.../core/TikaServerClientRevocationTest.java | 201 +++++++++++++++++++++
.../tika/server/core/TikaServerConfigTest.java | 60 +++++-
.../server/core/TikaServerProcessTlsGuardTest.java | 1 -
.../server/core/TikaServerTlsHandshakeTest.java | 135 ++++++++++++++
.../org/apache/tika/server/core/TlsConfigTest.java | 146 +++++++++++++++
...on => tika-config-server-tls-active-false.json} | 9 +-
... => tika-config-server-tls-active-omitted.json} | 8 +-
...onfig-server-tls-client-auth-no-truststore.json | 1 -
.../tika-config-server-tls-client-auth-valid.json | 1 -
.../configs/tika-config-server-tls-env.json | 9 +
.../tika-config-server-tls-missing-keystore.json | 1 -
.../tika-config-server-tls-one-way-template.json | 2 -
.../tika-config-server-tls-partial-truststore.json | 1 -
...on => tika-config-server-tls-removed-keys.json} | 8 +-
.../tika-config-server-tls-two-way-template.json | 2 -
.../ssl-keys/revocation/ca-truststore.p12 | Bin 0 -> 1142 bytes
.../src/test/resources/ssl-keys/revocation/ca.crl | Bin 0 -> 397 bytes
.../test/resources/ssl-keys/revocation/forged.p12 | Bin 0 -> 3472 bytes
.../test/resources/ssl-keys/revocation/generate.sh | 88 +++++++++
.../test/resources/ssl-keys/revocation/good.p12 | Bin 0 -> 3472 bytes
.../test/resources/ssl-keys/revocation/no-cdp.p12 | Bin 0 -> 3424 bytes
.../test/resources/ssl-keys/revocation/revoked.p12 | Bin 0 -> 3472 bytes
28 files changed, 1038 insertions(+), 213 deletions(-)
diff --git a/CHANGES.txt b/CHANGES.txt
index 265f33c8ce..aa2b149999 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -62,6 +62,28 @@ Release 4.2.0 - 10/22/2026
* tika-server accepts cleartext HTTP/2 (h2c) only when allowHttp2 is set; it had
been on by default since 4.0.0 (TIKA-4991).
+ * tika-server: a tlsConfig block turns TLS on; "active" is deprecated (true ignored
+ with a warning, false refused, rejected in 4.3.0). A keystore without "active" used
+ to serve plain HTTP. TikaServerConfig.getTlsConfig() is null when there is no
+ tlsConfig block (TIKA-4994).
+
+ * tika-server no longer sets TLS protocol or cipher suite policy; the JVM does
+ (-Djdk.tls.server.protocols, -Djdk.tls.server.cipherSuites, jdk.tls.disabledAlgorithms).
+ tlsConfig includedProtocols, excludedProtocols, includedCipherSuites and
+ excludedCipherSuites are removed and refused at startup: through 4.1 includedProtocols
+ did not stop other versions being offered, excludedCipherSuites on its own disabled
+ every suite, and includedCipherSuites could be replaced by the JVM defaults.
+ clientAuthenticationWanted is removed (true refused, false ignored with a warning):
+ tika-server never reads client certificates; use clientAuthenticationRequired
+ (TIKA-4994).
+
+ * tika-server 2-way TLS accepts client certificates issued by a CA in the truststore.
+ Since 2.4.0 it checked their revocation without enabling CRL fetching, so it rejected
+ every one; only certificates placed directly in the truststore worked. The check now
+ reads the certificate's CRL distribution point or OCSP responder and fails closed;
+ new tlsConfig checkRevocation (default true) turns it off. Rejected client
+ certificates are logged at INFO with the reason (TIKA-4994).
+
* embedded-limits maxDepth defaults to 20 (was unlimited) and replaces output-limits
maxPackageEntryDepth (#3319). To be removed in 4.3.0: maxPackageEntryDepth and its
accessors on OutputLimits and SecureContentHandler, and tika-parser-mail-commons
diff --git a/docs/modules/ROOT/pages/using-tika/server/tls.adoc b/docs/modules/ROOT/pages/using-tika/server/tls.adoc
index 0fbcf7e6af..c83f40daaa 100644
--- a/docs/modules/ROOT/pages/using-tika/server/tls.adoc
+++ b/docs/modules/ROOT/pages/using-tika/server/tls.adoc
@@ -53,11 +53,9 @@ Server authenticates to clients; clients present no certificate.
"host": "localhost",
"port": 9998,
"tlsConfig": {
- "active": true,
"keyStoreType": "PKCS12",
"keyStoreFile": "/path/to/server-keystore.p12",
"keyStorePassword": "${env:KEYSTORE_PASSWORD}",
- "clientAuthenticationWanted": false,
"clientAuthenticationRequired": false
}
}
@@ -66,7 +64,9 @@ Server authenticates to clients; clients present no certificate.
=== 2-Way TLS (mTLS)
-Add a truststore holding the client certificates you trust, and require client authentication.
+Add a truststore holding the client certificates you trust, or the CA certificate that issues
+them, and require client authentication. A certificate issued by a CA is also checked for
+revocation; see <<Client certificate revocation>>.
[source,json]
----
@@ -75,14 +75,12 @@ Add a truststore holding the client certificates you trust, and require client a
"host": "localhost",
"port": 9998,
"tlsConfig": {
- "active": true,
"keyStoreType": "PKCS12",
"keyStoreFile": "/path/to/server-keystore.p12",
"keyStorePassword": "${env:KEYSTORE_PASSWORD}",
"trustStoreType": "PKCS12",
"trustStoreFile": "/path/to/server-truststore.p12",
"trustStorePassword": "${env:TRUSTSTORE_PASSWORD}",
- "clientAuthenticationWanted": true,
"clientAuthenticationRequired": true
}
}
@@ -91,17 +89,18 @@ Add a truststore holding the client certificates you trust, and require client a
== Configuration Reference
-All properties below go in `server.tlsConfig`.
+A `tlsConfig` block turns TLS on: there is no separate switch to forget, and the server never
+falls back to plain HTTP. To serve plain HTTP, leave the block out. All properties below go in
+`server.tlsConfig`.
+
+NOTE: Up to 4.1 the block also carried `"active": true`. It is deprecated in 4.2.0: `true` is
+ignored with a warning, `false` is refused (remove the block instead), and the key is rejected
+from 4.3.0.
[cols="2,1,1,4"]
|===
|Property |Type |Default |Description
-|`active`
-|boolean
-|`false`
-|Enable TLS. When true, the server serves HTTPS.
-
|`keyStoreType`
|string
|null
@@ -126,42 +125,24 @@ All properties below go in `server.tlsConfig`.
|`trustStoreFile`
|string
|null
-|Path to the truststore holding trusted client certificates (2-way TLS).
+|Path to the truststore holding trusted client certificates, or the CAs that issue them (2-way TLS).
+Only used with `clientAuthenticationRequired`.
|`trustStorePassword`
|string
|null
|Password for the truststore.
-|`clientAuthenticationWanted`
-|boolean
-|`false`
-|Request client certificates but do not require them; clients without one still connect.
-
|`clientAuthenticationRequired`
|boolean
|`false`
|Require a client certificate trusted by the server.
-|`includedProtocols`
-|list
-|`["TLSv1.2", "TLSv1.3"]`
-|TLS versions to enable. TLS 1.0 and 1.1 are insecure and off by default.
-
-|`excludedProtocols`
-|list
-|null
-|TLS versions to explicitly disable.
-
-|`includedCipherSuites`
-|list
-|null
-|Cipher suites to enable. Null means JVM defaults.
-
-|`excludedCipherSuites`
-|list
-|null
-|Cipher suites to disable. Entries are regular expressions.
+|`checkRevocation`
+|boolean
+|`true`
+|Reject a CA-issued client certificate unless its revocation status can be confirmed (CRL or
+OCSP). See <<Client certificate revocation>>.
|`certExpirationWarningDays`
|integer
@@ -169,37 +150,87 @@ All properties below go in `server.tlsConfig`.
|Days before certificate expiration to log a warning. `0` disables the check.
|===
-When `active` is true, `keyStoreType`, `keyStoreFile` and `keyStorePassword` are all required.
+`keyStoreType`, `keyStoreFile` and `keyStorePassword` are all required.
Truststore properties are all-or-nothing: set all three or none.
-=== Restricting protocols and cipher suites
-
-Add to `server.tlsConfig` — for example, TLS 1.3 only:
+A keystore or truststore that cannot be opened stops startup: the server never falls back to
+plain HTTP.
+
+=== Client certificate revocation
+
+With `clientAuthenticationRequired`, a client certificate issued by a CA in the truststore is
+also checked for revocation, using the sources named in the certificate itself: its OCSP
+responder (Authority Information Access) first, then its CRL distribution point if OCSP gives no
+answer. No JVM flags are needed. The check fails closed: the client is rejected when the
+certificate names no revocation source, when no source can be reached, or when one lists the
+certificate as revoked. A certificate placed directly in the truststore is trusted as is; it is
+not checked.
+
+The check runs during the TLS handshake, so the server must be able to reach the OCSP responder or
+CRL distribution point. One that does not answer holds that client's handshake until the JDK
+gives up: 15 seconds each to connect and to read, for OCSP and then again for the CRL. To shorten
+that, set `-Dcom.sun.security.ocsp.timeout`, `-Dcom.sun.security.ocsp.readtimeout`,
+`-Dcom.sun.security.crl.timeout` and `-Dcom.sun.security.crl.readtimeout` (in seconds). Only
+certificates issued by a CA in the truststore reach this check: a certificate the server cannot
+verify is rejected without any fetch.
+
+If your CA publishes no CRL or OCSP responder, or the server cannot reach it, turn the check off.
+This is often the case for CAs that issue short-lived, automatically rotated certificates, such as
+cert-manager or SPIFFE/SPIRE, which rely on the short lifetime instead of revocation: with the
+check on, every client is rejected. With the check off, revoking a client means removing its
+certificate (or its CA) from the truststore, or letting a short-lived certificate expire:
[source,json]
----
-"includedProtocols": ["TLSv1.3"]
+"checkRevocation": false
----
-An explicit allowlist of cipher suites:
+The client sees only `certificate_unknown`; the server logs the reason at INFO (logger
+`org.apache.tika.server.core.ClientCertificateTrustManager`), for example
+`client certificate rejected: CN=client-1: Certificate has been revoked`.
-[source,json]
+NOTE: Through 4.1 the server checked revocation without enabling CRL fetching, so it rejected
+every CA-issued client certificate. Only certificates placed directly in the truststore worked.
+
+=== Restricting protocols and cipher suites
+
+Tika does not set TLS protocol or cipher suite policy; the JVM does. By default the server offers
+what the JVM enables (TLS 1.2 and 1.3 on current JDKs), minus Jetty's weak-suite exclusions. To
+restrict it, set the JVM's own properties when starting the server — for example, TLS 1.3 only:
+
+[source,bash]
----
-"includedCipherSuites": [
- "TLS_AES_256_GCM_SHA384",
- "TLS_AES_128_GCM_SHA256",
- "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
- "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
-]
+java -Djdk.tls.server.protocols=TLSv1.3 -jar tika-server-standard.jar -c tika-config.json
----
-Or a denylist of weak ones, as regular expressions:
+An explicit allowlist of cipher suites:
-[source,json]
+[source,bash]
----
-"excludedCipherSuites": [".*CBC.*", ".*RC4.*", ".*3DES.*", ".*NULL.*"]
+java -Djdk.tls.server.cipherSuites=TLS_AES_256_GCM_SHA384,TLS_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 \
+ -jar tika-server-standard.jar -c tika-config.json
----
+In a container, put the flags in `JAVA_TOOL_OPTIONS`. The `jdk.tls.server.*` properties apply
+only where Java is the TLS server, so outbound HTTPS from fetchers and parsers is unaffected. The
+reverse does not hold: `jdk.tls.client.protocols` restricts the server too, since Jetty starts
+from the JVM's client-side protocol defaults, so the server offers only protocols both properties
+allow. To ban an algorithm everywhere, client side included, edit `jdk.tls.disabledAlgorithms` in
+the JDK's `java.security`.
+
+Jetty's weak-suite exclusions apply on top of `jdk.tls.server.cipherSuites`: a listed suite whose
+name ends in `+_SHA+`, `+_SHA1+` or `+_MD5+`, or starts with `+TLS_RSA_+` or `+SSL_+`, or contains
+`+_NULL_+` or `+_anon_+`, is dropped without a warning.
+
+NOTE: Through 4.1, `tlsConfig` had `includedProtocols`, `excludedProtocols`,
+`includedCipherSuites` and `excludedCipherSuites`. None of them worked as documented:
+`includedProtocols` did not stop other versions being offered, `excludedCipherSuites` on its own
+disabled every suite, and `includedCipherSuites` could be replaced by the JVM defaults. They were
+removed in 4.2.0, and a config that sets any of them is refused with the property to use instead.
+`clientAuthenticationWanted` was removed too: tika-server never reads client certificates, so
+asking for one without requiring it authenticated nobody. `true` is refused; `false` is ignored
+with a warning. Use `clientAuthenticationRequired`.
+
== Certificate Expiration Warnings
At startup, Tika Server checks every certificate in the configured stores and logs:
@@ -288,6 +319,7 @@ paths in `tlsConfig` are container paths, not host paths.
[source,bash]
----
docker run -d --name tika-server -p 9998:9998 \
+ -e KEYSTORE_PASSWORD -e TRUSTSTORE_PASSWORD \
-v $(pwd)/config:/tika/config:ro \
-v $(pwd)/certs:/tika/certs:ro \
apache/tika:<version> \
@@ -302,7 +334,6 @@ With `/tika/config/tika-config.json` holding, for example:
"server": {
"port": 9998,
"tlsConfig": {
- "active": true,
"keyStoreType": "PKCS12",
"keyStoreFile": "/tika/certs/server-keystore.p12",
"keyStorePassword": "${env:KEYSTORE_PASSWORD}",
@@ -331,6 +362,9 @@ services:
- ./config:/tika/config:ro
- ./certs:/tika/certs:ro
command: ["-c", "/tika/config/tika-config.json"]
+ environment:
+ KEYSTORE_PASSWORD: ${KEYSTORE_PASSWORD}
+ TRUSTSTORE_PASSWORD: ${TRUSTSTORE_PASSWORD}
healthcheck:
test: ["CMD", "curl", "-f", "--cacert", "/tika/certs/server.crt", "https://localhost:9998/tika"]
interval: 30s
@@ -344,7 +378,9 @@ services:
----
kubectl create secret generic tika-tls-certs \
--from-file=server-keystore.p12=./certs/server-keystore.p12 \
- --from-file=server-truststore.p12=./certs/server-truststore.p12
+ --from-file=server-truststore.p12=./certs/server-truststore.p12 \
+ --from-literal=keystore-password="$KEYSTORE_PASSWORD" \
+ --from-literal=truststore-password="$TRUSTSTORE_PASSWORD"
----
[source,yaml]
@@ -370,6 +406,13 @@ spec:
ports:
- containerPort: 9998
args: ["-c", "/tika/config/tika-config.json"]
+ env:
+ - name: KEYSTORE_PASSWORD
+ valueFrom:
+ secretKeyRef: {name: tika-tls-certs, key: keystore-password}
+ - name: TRUSTSTORE_PASSWORD
+ valueFrom:
+ secretKeyRef: {name: tika-tls-certs, key: truststore-password}
volumeMounts:
- name: config
mountPath: /tika/config
@@ -388,14 +431,12 @@ spec:
=== Keystore passwords
-IMPORTANT: Tika reads `keyStorePassword` and `trustStorePassword` literally out of the JSON — it
-does not expand environment variables, `${...}` placeholders, or `/run/secrets` paths. Injecting
-the password as an env var does nothing on its own.
-
-Treat the whole config file as the secret: store it as a Kubernetes `Secret` or a Docker secret
-and mount it read-only (as in the deployment above), rather than baking it into an image or
-committing it. If you need to assemble the password at run time, do it in your own entrypoint
-script, which writes the config file before starting the server.
+Write the passwords as `${env:NAME}` (as in the examples above) and pass them in the environment,
+so the config file holds no secret. Tika expands `${env:NAME}` anywhere in the startup config; a
+variable that is not set stops startup
+(xref:configuration/index.adoc#env[Secrets from the environment]). Nothing else is expanded: there
+is no form that reads a file such as `/run/secrets/...`. For a secret mounted as a file, export it
+into the environment in your own entrypoint script.
== Troubleshooting
@@ -404,7 +445,18 @@ script, which writes the config file before starting the server.
|Message |Cause
|`must initialize keyStoreType` / `keyStoreFile` / `keyStorePassword`
-|`active` is true but one of the three required keystore properties is missing.
+|A `tlsConfig` block is present but one of the three required keystore properties is missing.
+
+|`tlsConfig.active=false: a tlsConfig block turns TLS on`
+|The deprecated `active` flag is set to false. Remove the whole `tlsConfig` block to serve plain HTTP.
+
+|`removed in 4.2.0; Tika no longer sets TLS protocol or cipher policy`
+|A 4.1 config with `includedProtocols`, `excludedProtocols`, `includedCipherSuites` or
+`excludedCipherSuites`. Delete the key and use the JVM property the message names (see
+<<Restricting protocols and cipher suites>>).
+
+|`tlsConfig.clientAuthenticationWanted was removed in 4.2.0`
+|Use `clientAuthenticationRequired` with a truststore, or drop the key.
|`keyStoreFile does not exist or is not a file`
|Wrong path. Absolute paths are safest; relative ones resolve against the working directory, which
@@ -414,6 +466,13 @@ inside a container is not where you think it is.
|Some but not all of `trustStoreType`, `trustStoreFile`, `trustStorePassword` are set. Set all
three or none.
+|`client certificate rejected: ...` (INFO, server log)
+|The server refused a client's certificate; the client sees `certificate_unknown`. `Certificate
+has been revoked`: it is. `Certificate does not specify OCSP responder`, `Connection refused`, or
+a timeout: the certificate names no reachable revocation source; fix the CA's CRL or OCSP
+publication, or set `checkRevocation` to false. `Signature does not match` or `unable to find valid certification
+path`: it was not issued by a CA in the truststore.
+
|`requiring client authentication, but no trust store has been specified`
|`clientAuthenticationRequired` is true with no truststore. The server needs one to verify client
certificates.
diff --git a/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/ClientCertificateTrustManager.java b/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/ClientCertificateTrustManager.java
new file mode 100644
index 0000000000..424827acd1
--- /dev/null
+++ b/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/ClientCertificateTrustManager.java
@@ -0,0 +1,142 @@
+/*
+ * 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.tika.server.core;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.Socket;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+import java.security.cert.CertPathValidator;
+import java.security.cert.CertificateException;
+import java.security.cert.PKIXBuilderParameters;
+import java.security.cert.PKIXRevocationChecker;
+import java.security.cert.X509CertSelector;
+import java.security.cert.X509Certificate;
+import javax.net.ssl.CertPathTrustManagerParameters;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509ExtendedTrustManager;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Verifies client certificates against the truststore and, unless turned off, their
+ * revocation status, and logs one INFO line per rejected certificate: the client only sees
+ * {@code certificate_unknown}.
+ */
+final class ClientCertificateTrustManager extends X509ExtendedTrustManager {
+
+ private static final Logger LOG = LoggerFactory.getLogger(ClientCertificateTrustManager.class);
+
+ private final X509ExtendedTrustManager delegate;
+
+ private ClientCertificateTrustManager(X509ExtendedTrustManager delegate) {
+ this.delegate = delegate;
+ }
+
+ static TrustManager[] create(TlsConfig tlsConfig) throws GeneralSecurityException, IOException {
+ KeyStore trustStore = KeyStore.getInstance(tlsConfig.getTrustStoreType());
+ try (InputStream in = Files.newInputStream(Paths.get(tlsConfig.getTrustStoreFile()))) {
+ trustStore.load(in, tlsConfig.getTrustStorePassword().toCharArray());
+ }
+ PKIXBuilderParameters params = new PKIXBuilderParameters(trustStore, new X509CertSelector());
+ if (tlsConfig.isCheckRevocation()) {
+ // an explicit checker reads CRL distribution points and OCSP (AIA) on its own; the
+ // implicit one CXF enabled needed com.sun.security.enableCRLDP and rejected every CA-issued cert
+ params.addCertPathChecker((PKIXRevocationChecker) CertPathValidator.getInstance("PKIX").getRevocationChecker());
+ } else {
+ params.setRevocationEnabled(false);
+ }
+ TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX");
+ tmf.init(new CertPathTrustManagerParameters(params));
+ for (TrustManager tm : tmf.getTrustManagers()) {
+ if (tm instanceof X509ExtendedTrustManager x509) {
+ return new TrustManager[]{new ClientCertificateTrustManager(x509)};
+ }
+ }
+ throw new GeneralSecurityException("no X509 trust manager for the truststore");
+ }
+
+ @Override
+ public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException {
+ try {
+ delegate.checkClientTrusted(chain, authType, socket);
+ } catch (CertificateException e) {
+ throw rejected(chain, e);
+ }
+ }
+
+ @Override
+ public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine engine) throws CertificateException {
+ try {
+ delegate.checkClientTrusted(chain, authType, engine);
+ } catch (CertificateException e) {
+ throw rejected(chain, e);
+ }
+ }
+
+ @Override
+ public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+ try {
+ delegate.checkClientTrusted(chain, authType);
+ } catch (CertificateException e) {
+ throw rejected(chain, e);
+ }
+ }
+
+ @Override
+ public void checkServerTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException {
+ delegate.checkServerTrusted(chain, authType, socket);
+ }
+
+ @Override
+ public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine engine) throws CertificateException {
+ delegate.checkServerTrusted(chain, authType, engine);
+ }
+
+ @Override
+ public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+ delegate.checkServerTrusted(chain, authType);
+ }
+
+ @Override
+ public X509Certificate[] getAcceptedIssuers() {
+ return delegate.getAcceptedIssuers();
+ }
+
+ private static CertificateException rejected(X509Certificate[] chain, CertificateException e) {
+ if (LOG.isInfoEnabled()) {
+ String subject = chain == null || chain.length == 0 ? "(none)" : chain[0].getSubjectX500Principal().getName();
+ Throwable cause = e;
+ while (cause.getCause() != null) {
+ cause = cause.getCause();
+ }
+ // both strings come from the client's certificate
+ LOG.info("client certificate rejected: {}: {}", clean(subject), clean(String.valueOf(cause.getMessage())));
+ }
+ return e;
+ }
+
+ private static String clean(String s) {
+ return s.replaceAll("\\p{Cntrl}", "?");
+ }
+}
diff --git a/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/TikaServerConfig.java b/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/TikaServerConfig.java
index f9a5a9f910..712674ae90 100644
--- a/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/TikaServerConfig.java
+++ b/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/TikaServerConfig.java
@@ -101,7 +101,8 @@ public class TikaServerConfig {
private List<String> presets = new ArrayList<>();
private ArrayList<String> endpoints = new ArrayList<>();
- private TlsConfig tlsConfig = new TlsConfig();
+ // null = plain HTTP; a tlsConfig block, however sparse, means TLS and must be complete
+ private TlsConfig tlsConfig = null;
private Integer metricsPort;
/**
@@ -278,7 +279,9 @@ public class TikaServerConfig {
"files and reach network resources.");
}
}
- tlsConfig.checkInitialization();
+ if (tlsConfig != null) {
+ tlsConfig.checkInitialization();
+ }
if (maxInFlightRequestBytes > 0 && maxRequestSizeBytes >= 0
&& maxInFlightRequestBytes < maxRequestSizeBytes) {
throw new TikaConfigException("maxInFlightRequestBytes (" + maxInFlightRequestBytes
@@ -465,10 +468,15 @@ public class TikaServerConfig {
this.maxQueuePauseMillis = maxQueuePauseMillis;
}
+ /** Null when the config has no {@code tlsConfig} block, i.e. plain HTTP. */
public TlsConfig getTlsConfig() {
return tlsConfig;
}
+ public boolean tlsEnabled() {
+ return tlsConfig != null;
+ }
+
public void setTlsConfig(TlsConfig tlsConfig) {
this.tlsConfig = tlsConfig;
}
diff --git a/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/TikaServerProcess.java b/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/TikaServerProcess.java
index e1bd47206b..2d00692f78 100644
--- a/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/TikaServerProcess.java
+++ b/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/TikaServerProcess.java
@@ -38,10 +38,8 @@ import org.apache.cxf.binding.BindingFactoryManager;
import org.apache.cxf.configuration.jsse.TLSParameterJaxBUtils;
import org.apache.cxf.configuration.jsse.TLSServerParameters;
import org.apache.cxf.configuration.security.ClientAuthentication;
-import org.apache.cxf.configuration.security.FiltersType;
import org.apache.cxf.configuration.security.KeyManagersType;
import org.apache.cxf.configuration.security.KeyStoreType;
-import org.apache.cxf.configuration.security.TrustManagersType;
import org.apache.cxf.endpoint.Server;
import org.apache.cxf.jaxrs.JAXRSBindingFactory;
import org.apache.cxf.jaxrs.JAXRSServerFactoryBean;
@@ -339,18 +337,14 @@ public class TikaServerProcess {
warnOnPermissiveConfig(tikaServerConfig);
- String protocol = tikaServerConfig
- .getTlsConfig()
- .isActive() ? "https" : "http";
+ String protocol = tikaServerConfig.tlsEnabled() ? "https" : "http";
String url = protocol + "://" + host + ":" + port + "/";
sf.setAddress(url);
sf.setResourceComparator(new ProduceTypeResourceComparator());
BindingFactoryManager manager = sf
.getBus()
.getExtension(BindingFactoryManager.class);
- if (tikaServerConfig
- .getTlsConfig()
- .isActive()) {
+ if (tikaServerConfig.tlsEnabled()) {
LOG.warn("The TLS configuration is in BETA and might change " + "dramatically in future releases.");
// Check for expiring certificates and log warnings
tikaServerConfig.getTlsConfig().checkCertificateExpiration();
@@ -431,40 +425,12 @@ public class TikaServerProcess {
parameters.setKeyManagers(TLSParameterJaxBUtils.getKeyManagers(kmt));
if (tlsConfig.hasTrustStore()) {
- KeyStoreType trustKeyStore = new KeyStoreType();
- trustKeyStore.setType(tlsConfig.getTrustStoreType());
- trustKeyStore.setPassword(tlsConfig.getTrustStorePassword());
- trustKeyStore.setFile(tlsConfig.getTrustStoreFile());
- TrustManagersType tmt = new TrustManagersType();
- tmt.setKeyStore(trustKeyStore);
- parameters.setTrustManagers(TLSParameterJaxBUtils.getTrustManagers(tmt, true));
+ parameters.setTrustManagers(ClientCertificateTrustManager.create(tlsConfig));
}
ClientAuthentication clientAuthentication = new ClientAuthentication();
clientAuthentication.setRequired(tlsConfig.isClientAuthenticationRequired());
- clientAuthentication.setWant(tlsConfig.isClientAuthenticationWanted());
parameters.setClientAuthentication(clientAuthentication);
-
- // Configure TLS protocols
- if (tlsConfig.getIncludedProtocols() != null && !tlsConfig.getIncludedProtocols().isEmpty()) {
- parameters.setIncludeProtocols(tlsConfig.getIncludedProtocols());
- }
- if (tlsConfig.getExcludedProtocols() != null && !tlsConfig.getExcludedProtocols().isEmpty()) {
- parameters.setExcludeProtocols(tlsConfig.getExcludedProtocols());
- }
-
- // Configure cipher suites
- if ((tlsConfig.getIncludedCipherSuites() != null && !tlsConfig.getIncludedCipherSuites().isEmpty()) ||
- (tlsConfig.getExcludedCipherSuites() != null && !tlsConfig.getExcludedCipherSuites().isEmpty())) {
- FiltersType cipherSuitesFilter = new FiltersType();
- if (tlsConfig.getIncludedCipherSuites() != null) {
- cipherSuitesFilter.getInclude().addAll(tlsConfig.getIncludedCipherSuites());
- }
- if (tlsConfig.getExcludedCipherSuites() != null) {
- cipherSuitesFilter.getExclude().addAll(tlsConfig.getExcludedCipherSuites());
- }
- parameters.setCipherSuitesFilter(cipherSuitesFilter);
- }
-
+ // protocols and cipher suites: the JVM's (jdk.tls.server.*), less Jetty's default excludes
return parameters;
}
diff --git a/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/TlsConfig.java b/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/TlsConfig.java
index 5fbfb43820..c19b0b7894 100644
--- a/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/TlsConfig.java
+++ b/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/TlsConfig.java
@@ -27,10 +27,11 @@ import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.time.Duration;
import java.time.Instant;
-import java.util.Arrays;
import java.util.Date;
import java.util.Enumeration;
+import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -42,18 +43,13 @@ public class TlsConfig {
private static final Logger LOG = LoggerFactory.getLogger(TlsConfig.class);
- /**
- * Default TLS protocols - only TLS 1.2 and 1.3 are enabled by default.
- * TLS 1.0 and 1.1 are considered insecure and should not be used.
- */
- public static final List<String> DEFAULT_PROTOCOLS = Arrays.asList("TLSv1.2", "TLSv1.3");
-
/**
* Default warning threshold for certificate expiration (30 days).
*/
public static final int DEFAULT_CERT_EXPIRATION_WARNING_DAYS = 30;
- private boolean active = false;
+ // deprecated 4.2.0, removed in 4.3.0: TLS is on whenever a tlsConfig block is present
+ private Boolean active = null;
private int certExpirationWarningDays = DEFAULT_CERT_EXPIRATION_WARNING_DAYS;
private String keyStoreType = null;
private String keyStorePassword = null;
@@ -62,21 +58,25 @@ public class TlsConfig {
private String trustStorePassword = null;
private String trustStoreFile = null;
- private boolean clientAuthenticationWanted = false;
private boolean clientAuthenticationRequired = false;
+ private boolean checkRevocation = true;
- // TLS protocol configuration
- private List<String> includedProtocols = DEFAULT_PROTOCOLS;
- private List<String> excludedProtocols = null;
+ private static final String PROTOCOLS_REPLACEMENT = "-Djdk.tls.server.protocols=TLSv1.3 (for example)";
+ private static final String CIPHERS_REPLACEMENT =
+ "-Djdk.tls.server.cipherSuites=... or jdk.tls.disabledAlgorithms in java.security";
- // Cipher suite configuration
- private List<String> includedCipherSuites = null;
- private List<String> excludedCipherSuites = null;
+ // keys removed in 4.2.0; refused at startup with a pointer to the replacement
+ private Boolean clientAuthenticationWanted = null;
+ private final Map<String, String> removedKeys = new LinkedHashMap<>();
+ /** @deprecated since 4.2.0: a {@code TlsConfig} exists only when TLS is on. */
+ @Deprecated
public boolean isActive() {
- return active;
+ return true;
}
+ /** @deprecated since 4.2.0: a {@code tlsConfig} block turns TLS on; remove the block to turn it off. */
+ @Deprecated
public void setActive(boolean active) {
this.active = active;
}
@@ -130,45 +130,68 @@ public class TlsConfig {
}
public void checkInitialization() throws TikaConfigException {
- if (active) {
- // Validate keystore configuration
- if (StringUtils.isBlank(keyStoreType)) {
- throw new TikaConfigException("must initialize keyStoreType");
- } else if (StringUtils.isBlank(keyStoreFile)) {
- throw new TikaConfigException("must initialize keyStoreFile");
- } else if (StringUtils.isBlank(keyStorePassword)) {
- throw new TikaConfigException("must initialize keyStorePassword");
- }
+ if (Boolean.FALSE.equals(active)) {
+ throw new TikaConfigException("tlsConfig.active=false: a tlsConfig block turns TLS on; "
+ + "remove the block to serve plain HTTP (\"active\" is deprecated and will be removed in 4.3.0)");
+ }
+ if (Boolean.TRUE.equals(active)) {
+ LOG.warn("tlsConfig.active is deprecated and ignored: a tlsConfig block turns TLS on; remove \"active\" (it will be rejected in 4.3.0)");
+ }
+ if (Boolean.TRUE.equals(clientAuthenticationWanted)) {
+ throw new TikaConfigException("tlsConfig.clientAuthenticationWanted was removed in 4.2.0: tika-server never "
+ + "reads client certificates, so requesting one without requiring it authenticates nobody; "
+ + "use clientAuthenticationRequired");
+ }
+ if (Boolean.FALSE.equals(clientAuthenticationWanted)) {
+ LOG.warn("tlsConfig.clientAuthenticationWanted was removed in 4.2.0 and is ignored; remove it");
+ }
+ if (!removedKeys.isEmpty()) {
+ StringBuilder msg = new StringBuilder("removed in 4.2.0; Tika no longer sets TLS protocol or cipher "
+ + "policy, the JVM does:");
+ removedKeys.forEach((key, replacement) -> msg.append(" tlsConfig.").append(key).append(" -> ").append(replacement).append(';'));
+ throw new TikaConfigException(msg.toString());
+ }
+ // Validate keystore configuration
+ if (StringUtils.isBlank(keyStoreType)) {
+ throw new TikaConfigException("must initialize keyStoreType");
+ } else if (StringUtils.isBlank(keyStoreFile)) {
+ throw new TikaConfigException("must initialize keyStoreFile");
+ } else if (StringUtils.isBlank(keyStorePassword)) {
+ throw new TikaConfigException("must initialize keyStorePassword");
+ }
- // Validate keystore file exists
- File ksFile = new File(keyStoreFile);
- if (!ksFile.isFile()) {
- throw new TikaConfigException("keyStoreFile does not exist or is not a file: " + keyStoreFile);
- }
+ // Validate keystore file exists
+ File ksFile = new File(keyStoreFile);
+ if (!ksFile.isFile()) {
+ throw new TikaConfigException("keyStoreFile does not exist or is not a file: " + keyStoreFile);
+ }
- // Validate truststore configuration
- if (hasTrustStore()) {
- if (StringUtils.isBlank(trustStoreType)) {
- throw new TikaConfigException("must initialize trustStoreType if there's any trustStore info");
- } else if (StringUtils.isBlank(trustStoreFile)) {
- throw new TikaConfigException("must initialize trustStoreFile if there's any trustStore info");
- } else if (StringUtils.isBlank(trustStorePassword)) {
- throw new TikaConfigException("must initialize trustStorePassword if there's any trustStore info");
- }
+ // Validate truststore configuration
+ if (hasTrustStore()) {
+ if (StringUtils.isBlank(trustStoreType)) {
+ throw new TikaConfigException("must initialize trustStoreType if there's any trustStore info");
+ } else if (StringUtils.isBlank(trustStoreFile)) {
+ throw new TikaConfigException("must initialize trustStoreFile if there's any trustStore info");
+ } else if (StringUtils.isBlank(trustStorePassword)) {
+ throw new TikaConfigException("must initialize trustStorePassword if there's any trustStore info");
+ }
- // Validate truststore file exists
- File tsFile = new File(trustStoreFile);
- if (!tsFile.isFile()) {
- throw new TikaConfigException("trustStoreFile does not exist or is not a file: " + trustStoreFile);
- }
+ // Validate truststore file exists
+ File tsFile = new File(trustStoreFile);
+ if (!tsFile.isFile()) {
+ throw new TikaConfigException("trustStoreFile does not exist or is not a file: " + trustStoreFile);
}
+ }
- // Warn about partial truststore configuration
- checkPartialTrustStoreConfig();
+ // Warn about partial truststore configuration
+ checkPartialTrustStoreConfig();
- if (!hasTrustStore() && isClientAuthenticationRequired()) {
- throw new TikaConfigException("requiring client authentication, but no trust store has been specified");
- }
+ if (!hasTrustStore() && isClientAuthenticationRequired()) {
+ throw new TikaConfigException("requiring client authentication, but no trust store has been specified");
+ }
+ if (hasTrustStore() && !isClientAuthenticationRequired()) {
+ LOG.warn("tlsConfig has a truststore but clientAuthenticationRequired is false: clients are not asked for "
+ + "a certificate, so the truststore has no effect");
}
}
@@ -192,10 +215,8 @@ public class TlsConfig {
}
}
- public boolean isClientAuthenticationWanted() {
- return clientAuthenticationWanted;
- }
-
+ /** @deprecated removed in 4.2.0: tika-server never reads client certificates; use {@link #setClientAuthenticationRequired}. */
+ @Deprecated
public void setClientAuthenticationWanted(boolean clientAuthenticationWanted) {
this.clientAuthenticationWanted = clientAuthenticationWanted;
}
@@ -208,36 +229,37 @@ public class TlsConfig {
this.clientAuthenticationRequired = clientAuthenticationRequired;
}
- public List<String> getIncludedProtocols() {
- return includedProtocols;
- }
-
+ /** @deprecated removed in 4.2.0: refused at startup; use the {@code jdk.tls.server.protocols} system property. */
+ @Deprecated
public void setIncludedProtocols(List<String> includedProtocols) {
- this.includedProtocols = includedProtocols;
- }
-
- public List<String> getExcludedProtocols() {
- return excludedProtocols;
+ removedKeys.put("includedProtocols", PROTOCOLS_REPLACEMENT);
}
+ /** @deprecated removed in 4.2.0: refused at startup; use the {@code jdk.tls.server.protocols} system property. */
+ @Deprecated
public void setExcludedProtocols(List<String> excludedProtocols) {
- this.excludedProtocols = excludedProtocols;
+ removedKeys.put("excludedProtocols", PROTOCOLS_REPLACEMENT);
}
- public List<String> getIncludedCipherSuites() {
- return includedCipherSuites;
+ /** @deprecated removed in 4.2.0: refused at startup; use the {@code jdk.tls.server.cipherSuites} system property. */
+ @Deprecated
+ public void setIncludedCipherSuites(List<String> includedCipherSuites) {
+ removedKeys.put("includedCipherSuites", CIPHERS_REPLACEMENT);
}
- public void setIncludedCipherSuites(List<String> includedCipherSuites) {
- this.includedCipherSuites = includedCipherSuites;
+ /** @deprecated removed in 4.2.0: refused at startup; use {@code jdk.tls.disabledAlgorithms} in {@code java.security}. */
+ @Deprecated
+ public void setExcludedCipherSuites(List<String> excludedCipherSuites) {
+ removedKeys.put("excludedCipherSuites", CIPHERS_REPLACEMENT);
}
- public List<String> getExcludedCipherSuites() {
- return excludedCipherSuites;
+ /** Whether a client certificate's revocation status (CRL or OCSP) must be confirmed; default true. */
+ public boolean isCheckRevocation() {
+ return checkRevocation;
}
- public void setExcludedCipherSuites(List<String> excludedCipherSuites) {
- this.excludedCipherSuites = excludedCipherSuites;
+ public void setCheckRevocation(boolean checkRevocation) {
+ this.checkRevocation = checkRevocation;
}
public int getCertExpirationWarningDays() {
@@ -256,7 +278,7 @@ public class TlsConfig {
* warn about upcoming certificate expirations.
*/
public void checkCertificateExpiration() {
- if (!active || certExpirationWarningDays <= 0) {
+ if (certExpirationWarningDays <= 0) {
return;
}
@@ -322,19 +344,14 @@ public class TlsConfig {
@Override
public String toString() {
return "TlsConfig{" +
- "active=" + active +
- ", keyStoreType='" + keyStoreType + '\'' +
+ "keyStoreType='" + keyStoreType + '\'' +
", keyStorePassword='" + maskPassword(keyStorePassword) + '\'' +
", keyStoreFile='" + keyStoreFile + '\'' +
", trustStoreType='" + trustStoreType + '\'' +
", trustStorePassword='" + maskPassword(trustStorePassword) + '\'' +
", trustStoreFile='" + trustStoreFile + '\'' +
- ", clientAuthenticationWanted=" + clientAuthenticationWanted +
", clientAuthenticationRequired=" + clientAuthenticationRequired +
- ", includedProtocols=" + includedProtocols +
- ", excludedProtocols=" + excludedProtocols +
- ", includedCipherSuites=" + includedCipherSuites +
- ", excludedCipherSuites=" + excludedCipherSuites +
+ ", checkRevocation=" + checkRevocation +
'}';
}
diff --git a/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/TikaServerClientRevocationTest.java b/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/TikaServerClientRevocationTest.java
new file mode 100644
index 0000000000..252103aa54
--- /dev/null
+++ b/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/TikaServerClientRevocationTest.java
@@ -0,0 +1,201 @@
+/*
+ * 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.tika.server.core;
+
+import static java.nio.charset.StandardCharsets.US_ASCII;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assumptions.abort;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.BindException;
+import java.net.ConnectException;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicInteger;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+
+import org.apache.commons.io.IOUtils;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import org.apache.tika.utils.ProcessUtils;
+
+/**
+ * 2-way TLS with client certificates issued by a CA, whose CRL this test serves. Fixtures and
+ * how to regenerate them: {@code ssl-keys/revocation/generate.sh}.
+ */
+public class TikaServerClientRevocationTest extends IntegrationTestBase {
+
+ // fixed: the fixtures' CRL distribution point names it
+ private static final int CRL_PORT = 18181;
+
+ private Path pki;
+ private ServerSocket crlServer;
+ private final AtomicInteger caCrlFetches = new AtomicInteger();
+ private final AtomicInteger forgedCrlFetches = new AtomicInteger();
+
+ @BeforeEach
+ public void startCrlServer() throws Exception {
+ pki = Paths.get(getClass().getResource("/ssl-keys/revocation").toURI()).toAbsolutePath();
+ byte[] crl = Files.readAllBytes(pki.resolve("ca.crl"));
+ crlServer = new ServerSocket();
+ try {
+ crlServer.bind(new InetSocketAddress("127.0.0.1", CRL_PORT));
+ } catch (BindException e) {
+ crlServer.close();
+ abort("port " + CRL_PORT + ", which the fixtures' CRL distribution point names, is in use");
+ }
+ Thread t = new Thread(() -> serveCrls(crl), "crl-server");
+ t.setDaemon(true);
+ t.start();
+ }
+
+ @AfterEach
+ public void stopCrlServer() throws IOException {
+ if (crlServer != null) {
+ crlServer.close();
+ }
+ }
+
+ /** Minimal HTTP/1.0: GET /ca.crl answers the CRL, anything else 404. */
+ private void serveCrls(byte[] crl) {
+ while (!crlServer.isClosed()) {
+ try (Socket s = crlServer.accept()) {
+ BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream(), US_ASCII));
+ String requestLine = String.valueOf(in.readLine());
+ OutputStream out = s.getOutputStream();
+ if (requestLine.startsWith("GET /ca.crl ")) {
+ caCrlFetches.incrementAndGet();
+ out.write(("HTTP/1.0 200 OK\r\nContent-Type: application/pkix-crl\r\nContent-Length: "
+ + crl.length + "\r\n\r\n").getBytes(US_ASCII));
+ out.write(crl);
+ } else {
+ if (requestLine.startsWith("GET /forged.crl ")) {
+ forgedCrlFetches.incrementAndGet();
+ }
+ out.write("HTTP/1.0 404 Not Found\r\nContent-Length: 0\r\n\r\n".getBytes(US_ASCII));
+ }
+ out.flush();
+ } catch (IOException e) {
+ // closed by stopCrlServer, or a client hung up
+ }
+ }
+ }
+
+ @Test
+ public void revocationIsCheckedByDefault() throws Exception {
+ start(null);
+ assertTrue(accepted("good.p12"), "a CA-issued certificate the CRL does not list");
+ assertFalse(accepted("revoked.p12"), "a certificate the CRL revokes");
+ assertFalse(accepted("no-cdp.p12"), "a certificate with no revocation source fails closed");
+ assertTrue(caCrlFetches.get() > 0);
+ assertFalse(accepted("forged.p12"));
+ assertEquals(0, forgedCrlFetches.get(), "an unverified certificate must not make the server fetch its CRL");
+ }
+
+ @Test
+ public void revocationCanBeTurnedOff() throws Exception {
+ start("\"checkRevocation\": false");
+ assertTrue(accepted("no-cdp.p12"));
+ assertTrue(accepted("revoked.p12"), "with the check off, revocation is not consulted");
+ assertFalse(accepted("forged.p12"));
+ assertEquals(0, caCrlFetches.get() + forgedCrlFetches.get());
+ }
+
+ private void start(String extraTlsConfig) throws Exception {
+ Path keys = Paths.get(getClass().getResource("/ssl-keys").toURI()).toAbsolutePath();
+ String json = IOUtils.resourceToString("/configs/tika-config-server-tls-two-way-template.json", UTF_8)
+ .replace("{SSL_KEYS}/tika-server-truststore.p12", "{SSL_KEYS}/revocation/ca-truststore.p12")
+ .replace("{SSL_KEYS}", keys.toString())
+ .replace("\\", "/");
+ if (extraTlsConfig != null) {
+ json = json.replace("\"tlsConfig\": {", "\"tlsConfig\": {" + extraTlsConfig + ",");
+ }
+ Path config = Files.createTempFile(TEMP_WORKING_DIR, "tika-config-tls-", ".json");
+ Files.writeString(config, json, UTF_8);
+ startProcess(new String[]{"-config", ProcessUtils.escapeCommandLine(config.toAbsolutePath().toString())});
+ long deadline = System.currentTimeMillis() + MAX_WAIT_MS;
+ while (System.currentTimeMillis() < deadline) {
+ if (!process.isAlive()) {
+ throw new IllegalStateException("server exited with " + process.exitValue());
+ }
+ try (Socket ignored = new Socket("localhost", integrationTestPort)) {
+ return;
+ } catch (ConnectException e) {
+ Thread.sleep(200);
+ }
+ }
+ throw new TimeoutException("server did not accept TLS connections within " + MAX_WAIT_MS + " ms");
+ }
+
+ /** Whether the server answers an HTTP request over 2-way TLS with this client certificate. */
+ private boolean accepted(String clientKeyStore) throws Exception {
+ KeyStore ks = KeyStore.getInstance("PKCS12");
+ try (InputStream in = Files.newInputStream(pki.resolve(clientKeyStore))) {
+ ks.load(in, "tika-secret".toCharArray());
+ }
+ KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+ kmf.init(ks, "tika-secret".toCharArray());
+ // trusts any server certificate: these tests are about the server checking the client
+ X509TrustManager trustAll = new X509TrustManager() {
+ @Override
+ public void checkClientTrusted(X509Certificate[] chain, String authType) {
+ }
+
+ @Override
+ public void checkServerTrusted(X509Certificate[] chain, String authType) {
+ }
+
+ @Override
+ public X509Certificate[] getAcceptedIssuers() {
+ return new X509Certificate[0];
+ }
+ };
+ SSLContext context = SSLContext.getInstance("TLS");
+ context.init(kmf.getKeyManagers(), new TrustManager[]{trustAll}, null);
+ try (SSLSocket socket = (SSLSocket) context.getSocketFactory().createSocket("localhost", integrationTestPort)) {
+ socket.setSoTimeout(10000);
+ OutputStream out = socket.getOutputStream();
+ out.write("GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n".getBytes(US_ASCII));
+ out.flush();
+ // with TLS 1.3 the server's verdict on the client certificate arrives after the handshake
+ String response = new String(socket.getInputStream().readAllBytes(), US_ASCII);
+ return response.startsWith("HTTP/1.1 ");
+ } catch (IOException e) {
+ return false;
+ }
+ }
+}
diff --git a/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/TikaServerConfigTest.java b/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/TikaServerConfigTest.java
index ffb3009950..d2fda79c6c 100644
--- a/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/TikaServerConfigTest.java
+++ b/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/TikaServerConfigTest.java
@@ -242,9 +242,8 @@ public class TikaServerConfigTest extends TikaTest {
Path path = getConfigPath(getClass(), "tika-config-server-tls.json");
TikaServerConfig config = TikaServerConfig.load(path, emptyCommandLine, settings);
+ assertTrue(config.tlsEnabled());
TlsConfig tlsConfig = config.getTlsConfig();
- assertTrue(tlsConfig.isActive());
- assertFalse(tlsConfig.isClientAuthenticationWanted());
assertFalse(tlsConfig.isClientAuthenticationRequired());
assertEquals("myType", tlsConfig.getKeyStoreType());
assertEquals("pass", tlsConfig.getKeyStorePassword());
@@ -276,6 +275,63 @@ public class TikaServerConfigTest extends TikaTest {
assertContains("no trust store", ex.getMessage());
}
+ /** A tlsConfig block is TLS; it does not need the deprecated "active" flag. */
+ @Test
+ public void testTlsBlockWithoutActiveIsTls() throws Exception {
+ Set<String> settings = new HashSet<>();
+ CommandLine emptyCommandLine = new DefaultParser().parse(new Options(), new String[]{});
+ Path path = getConfigPath(getClass(), "tika-config-server-tls-active-omitted.json");
+ TikaServerConfig config = TikaServerConfig.load(path, emptyCommandLine, settings);
+ assertTrue(config.tlsEnabled());
+ assertFalse(new TikaServerConfig().tlsEnabled());
+ }
+
+ /** A configured block that claims to be off is the footgun this replaces: refuse it. */
+ @Test
+ public void testTlsActiveFalseRefusesToLoad() throws Exception {
+ CommandLineParser parser = new DefaultParser();
+ Path path = getConfigPath(getClass(), "tika-config-server-tls-active-false.json");
+ CommandLine commandLine = parser.parse(new Options()
+ .addOption(Option
+ .builder("c")
+ .longOpt("config")
+ .hasArg()
+ .get()), new String[]{"-c", ProcessUtils.escapeCommandLine(path
+ .toAbsolutePath()
+ .toString())});
+ TikaConfigException ex = assertThrows(TikaConfigException.class,
+ () -> TikaServerConfig.load(commandLine));
+ assertContains("remove the block", ex.getMessage());
+ }
+
+ /** tls.adoc tells users to write keystore passwords as ${env:NAME}; PATH is set everywhere. */
+ @Test
+ public void testTlsPasswordFromEnvironment() throws Exception {
+ CommandLine emptyCommandLine = new DefaultParser().parse(new Options(), new String[]{});
+ Path path = getConfigPath(getClass(), "tika-config-server-tls-env.json");
+ TikaServerConfig config = TikaServerConfig.load(path, emptyCommandLine, new HashSet<>());
+ assertEquals(System.getenv("PATH"), config.getTlsConfig().getKeyStorePassword());
+ }
+
+ /** A 4.1 config with the removed protocol/cipher lists is refused, naming the JVM property to use. */
+ @Test
+ public void testRemovedTlsListsRefuseToLoad() throws Exception {
+ CommandLineParser parser = new DefaultParser();
+ Path path = getConfigPath(getClass(), "tika-config-server-tls-removed-keys.json");
+ CommandLine commandLine = parser.parse(new Options()
+ .addOption(Option
+ .builder("c")
+ .longOpt("config")
+ .hasArg()
+ .get()), new String[]{"-c", ProcessUtils.escapeCommandLine(path
+ .toAbsolutePath()
+ .toString())});
+ TikaConfigException ex = assertThrows(TikaConfigException.class,
+ () -> TikaServerConfig.load(commandLine));
+ assertContains("tlsConfig.includedProtocols -> -Djdk.tls.server.protocols", ex.getMessage());
+ assertContains("tlsConfig.excludedCipherSuites", ex.getMessage());
+ }
+
@Test
public void testPartialTrustStoreConfigRefusesToLoad() throws Exception {
CommandLineParser parser = new DefaultParser();
diff --git a/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/TikaServerProcessTlsGuardTest.java b/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/TikaServerProcessTlsGuardTest.java
index 687783396d..49030ab279 100644
--- a/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/TikaServerProcessTlsGuardTest.java
+++ b/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/TikaServerProcessTlsGuardTest.java
@@ -46,7 +46,6 @@ class TikaServerProcessTlsGuardTest {
private static TlsConfig validKeyStoreOnlyConfig() {
TlsConfig tlsConfig = new TlsConfig();
- tlsConfig.setActive(true);
tlsConfig.setKeyStoreType("PKCS12");
tlsConfig.setKeyStorePassword("tika-secret");
tlsConfig.setKeyStoreFile(Paths
diff --git a/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/TikaServerTlsHandshakeTest.java b/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/TikaServerTlsHandshakeTest.java
new file mode 100644
index 0000000000..c43ccad5e3
--- /dev/null
+++ b/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/TikaServerTlsHandshakeTest.java
@@ -0,0 +1,135 @@
+/*
+ * 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.tika.server.core;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+
+import java.io.IOException;
+import java.net.ConnectException;
+import java.net.Socket;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.concurrent.TimeoutException;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLHandshakeException;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+
+import org.apache.commons.io.IOUtils;
+import org.junit.jupiter.api.Test;
+
+import org.apache.tika.utils.ProcessUtils;
+
+/**
+ * Handshakes against a running server. Protocol and cipher policy is the JVM's
+ * ({@code jdk.tls.server.*}); these pin that it reaches CXF and Jetty unchanged.
+ */
+public class TikaServerTlsHandshakeTest extends IntegrationTestBase {
+
+ /** The replacement for the removed includedProtocols: the JVM's own property. */
+ @Test
+ public void jvmServerProtocolsPropertyIsHonoured() throws Exception {
+ start("-Djdk.tls.server.protocols=TLSv1.3");
+ assertEquals("TLSv1.3", handshake(new String[]{"TLSv1.3"}, null).getProtocol());
+ assertThrows(SSLHandshakeException.class, () -> handshake(new String[]{"TLSv1.2"}, null));
+ }
+
+ /** The replacement for the removed includedCipherSuites. */
+ @Test
+ public void jvmServerCipherSuitesPropertyIsHonoured() throws Exception {
+ start("-Djdk.tls.server.cipherSuites=TLS_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256");
+ assertEquals("TLS_AES_128_GCM_SHA256", handshake(new String[]{"TLSv1.3"}, null).getCipherSuite());
+ assertThrows(SSLHandshakeException.class,
+ () -> handshake(new String[]{"TLSv1.3"}, new String[]{"TLS_AES_256_GCM_SHA384"}));
+ }
+
+ @Test
+ public void defaultsRefuseWeakSuites() throws Exception {
+ assumeTrue(Arrays.asList(TlsConfigTest.jvmEnabled().getCipherSuites()).contains(TlsConfigTest.CBC_SHA_SUITE));
+ start(null);
+ assertEquals("TLSv1.3", handshake(null, null).getProtocol());
+ assertThrows(SSLHandshakeException.class,
+ () -> handshake(new String[]{"TLSv1.2"}, new String[]{TlsConfigTest.CBC_SHA_SUITE}));
+ }
+
+ private void start(String jvmOptions) throws Exception {
+ Path keys = Paths.get(getClass().getResource("/ssl-keys").toURI()).toAbsolutePath();
+ String json = IOUtils.resourceToString("/configs/tika-config-server-tls-one-way-template.json", UTF_8)
+ .replace("{SSL_KEYS}", keys.toString())
+ .replace("\\", "/");
+ Path config = Files.createTempFile(TEMP_WORKING_DIR, "tika-config-tls-", ".json");
+ Files.writeString(config, json, UTF_8);
+ startProcess(new String[]{"-config", ProcessUtils.escapeCommandLine(config.toAbsolutePath().toString())},
+ jvmOptions == null ? Map.of() : Map.of("JAVA_TOOL_OPTIONS", jvmOptions));
+ long deadline = System.currentTimeMillis() + MAX_WAIT_MS;
+ while (System.currentTimeMillis() < deadline) {
+ if (!process.isAlive()) {
+ throw new IllegalStateException("server exited with " + process.exitValue());
+ }
+ try (Socket ignored = new Socket("localhost", integrationTestPort)) {
+ return;
+ } catch (ConnectException e) {
+ Thread.sleep(200);
+ }
+ }
+ throw new TimeoutException("server did not accept TLS connections within " + MAX_WAIT_MS + " ms");
+ }
+
+ /** Trusts any server certificate: these tests are about negotiation, not authentication. */
+ private SSLSession handshake(String[] protocols, String[] cipherSuites) throws Exception {
+ SSLContext context = SSLContext.getInstance("TLS");
+ X509TrustManager trustAll = new X509TrustManager() {
+ @Override
+ public void checkClientTrusted(X509Certificate[] chain, String authType) {
+ }
+
+ @Override
+ public void checkServerTrusted(X509Certificate[] chain, String authType) {
+ }
+
+ @Override
+ public X509Certificate[] getAcceptedIssuers() {
+ return new X509Certificate[0];
+ }
+ };
+ context.init(null, new TrustManager[]{trustAll}, null);
+ try (SSLSocket socket = (SSLSocket) context.getSocketFactory().createSocket("localhost", integrationTestPort)) {
+ socket.setSoTimeout(10000);
+ if (protocols != null) {
+ socket.setEnabledProtocols(protocols);
+ }
+ if (cipherSuites != null) {
+ socket.setEnabledCipherSuites(cipherSuites);
+ }
+ socket.startHandshake();
+ return socket.getSession();
+ } catch (SSLHandshakeException e) {
+ throw e;
+ } catch (IOException e) {
+ throw new SSLHandshakeException(e.toString());
+ }
+ }
+}
diff --git a/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/TlsConfigTest.java b/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/TlsConfigTest.java
new file mode 100644
index 0000000000..329da720ff
--- /dev/null
+++ b/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/TlsConfigTest.java
@@ -0,0 +1,146 @@
+/*
+ * 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.tika.server.core;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.nio.file.Paths;
+import java.util.List;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLParameters;
+
+import org.junit.jupiter.api.Test;
+
+import org.apache.tika.exception.TikaConfigException;
+
+/**
+ * A tlsConfig block means TLS. One that cannot work as written must refuse to load; it must
+ * never fall back to plain HTTP.
+ */
+public class TlsConfigTest {
+
+ private static String key(String file) {
+ return Paths.get("src", "test", "resources", "ssl-keys", file).toString();
+ }
+
+ private static TlsConfig keyStoreOnly() {
+ TlsConfig c = new TlsConfig();
+ c.setKeyStoreType("PKCS12");
+ c.setKeyStorePassword("tika-secret");
+ c.setKeyStoreFile(key("tika-server-keystore.p12"));
+ return c;
+ }
+
+ private static void withTrustStore(TlsConfig c) {
+ c.setTrustStoreType("PKCS12");
+ c.setTrustStorePassword("tika-secret");
+ c.setTrustStoreFile(key("tika-server-truststore.p12"));
+ }
+
+ static final String CBC_SHA_SUITE = "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA";
+
+ static SSLParameters jvmEnabled() throws Exception {
+ SSLContext context = SSLContext.getInstance("TLS");
+ context.init(null, null, null);
+ return context.getDefaultSSLParameters();
+ }
+
+ private static String refused(TlsConfig c) {
+ return assertThrows(TikaConfigException.class, c::checkInitialization).getMessage();
+ }
+
+ @Test
+ public void emptyBlockRefuses() {
+ assertTrue(refused(new TlsConfig()).contains("keyStoreType"));
+ }
+
+ @Test
+ public void keyStoreOnlyLoads() {
+ assertDoesNotThrow(keyStoreOnly()::checkInitialization);
+ }
+
+ @Test
+ @SuppressWarnings("deprecation")
+ public void deprecatedActiveTrueIsIgnored() {
+ TlsConfig c = keyStoreOnly();
+ c.setActive(true);
+ assertDoesNotThrow(c::checkInitialization);
+ }
+
+ @Test
+ @SuppressWarnings("deprecation")
+ public void deprecatedActiveFalseRefuses() {
+ TlsConfig c = keyStoreOnly();
+ c.setActive(false);
+ assertTrue(refused(c).contains("remove the block"));
+ }
+
+ @Test
+ @SuppressWarnings("deprecation")
+ public void clientAuthWantedRefuses() {
+ TlsConfig c = keyStoreOnly();
+ withTrustStore(c);
+ c.setClientAuthenticationWanted(true);
+ assertTrue(refused(c).contains("clientAuthenticationRequired"));
+ }
+
+ /** 4.1 configs and the old docs carry "clientAuthenticationWanted": false; it asks for nothing. */
+ @Test
+ @SuppressWarnings("deprecation")
+ public void clientAuthWantedFalseIsIgnored() {
+ TlsConfig c = keyStoreOnly();
+ c.setClientAuthenticationWanted(false);
+ assertDoesNotThrow(c::checkInitialization);
+ }
+
+ @Test
+ public void clientAuthRequiredWithoutTrustStoreRefuses() {
+ TlsConfig c = keyStoreOnly();
+ c.setClientAuthenticationRequired(true);
+ assertTrue(refused(c).contains("no trust store"));
+ }
+
+ @Test
+ public void clientAuthRequiredWithTrustStoreLoads() {
+ TlsConfig c = keyStoreOnly();
+ withTrustStore(c);
+ c.setClientAuthenticationRequired(true);
+ assertDoesNotThrow(c::checkInitialization);
+ }
+
+ /** Any value, even an empty list: the key never worked as written through 4.1. */
+ @Test
+ @SuppressWarnings("deprecation")
+ public void removedProtocolAndCipherKeysRefuse() {
+ TlsConfig c = keyStoreOnly();
+ c.setIncludedProtocols(List.of("TLSv1.3"));
+ assertTrue(refused(c).contains("tlsConfig.includedProtocols -> -Djdk.tls.server.protocols"));
+
+ c = keyStoreOnly();
+ c.setExcludedProtocols(List.of());
+ assertTrue(refused(c).contains("tlsConfig.excludedProtocols"));
+
+ c = keyStoreOnly();
+ c.setIncludedCipherSuites(null);
+ c.setExcludedCipherSuites(List.of(".*CBC.*"));
+ String msg = refused(c);
+ assertTrue(msg.contains("tlsConfig.includedCipherSuites -> -Djdk.tls.server.cipherSuites"), msg);
+ assertTrue(msg.contains("tlsConfig.excludedCipherSuites"), msg);
+ }
+}
diff --git a/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-tls-client-auth-no-truststore.json b/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-tls-active-false.json
similarity index 57%
copy from tika-server/tika-server-core/src/test/resources/configs/tika-config-server-tls-client-auth-no-truststore.json
copy to tika-server/tika-server-core/src/test/resources/configs/tika-config-server-tls-active-false.json
index 6a727f7c27..0a9e5f23a8 100644
--- a/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-tls-client-auth-no-truststore.json
+++ b/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-tls-active-false.json
@@ -1,15 +1,10 @@
{
"server": {
- "port": 9999,
- "endpoints": [
- "status"
- ],
"tlsConfig": {
- "active": true,
+ "active": false,
"keyStoreType": "PKCS12",
"keyStorePassword": "tika-secret",
- "keyStoreFile": "src/test/resources/ssl-keys/tika-server-keystore.p12",
- "clientAuthenticationRequired": true
+ "keyStoreFile": "src/test/resources/ssl-keys/tika-server-keystore.p12"
}
}
}
diff --git a/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-tls-client-auth-no-truststore.json b/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-tls-active-omitted.json
similarity index 57%
copy from tika-server/tika-server-core/src/test/resources/configs/tika-config-server-tls-client-auth-no-truststore.json
copy to tika-server/tika-server-core/src/test/resources/configs/tika-config-server-tls-active-omitted.json
index 6a727f7c27..3262d69a0e 100644
--- a/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-tls-client-auth-no-truststore.json
+++ b/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-tls-active-omitted.json
@@ -1,15 +1,9 @@
{
"server": {
- "port": 9999,
- "endpoints": [
- "status"
- ],
"tlsConfig": {
- "active": true,
"keyStoreType": "PKCS12",
"keyStorePassword": "tika-secret",
- "keyStoreFile": "src/test/resources/ssl-keys/tika-server-keystore.p12",
- "clientAuthenticationRequired": true
+ "keyStoreFile": "src/test/resources/ssl-keys/tika-server-keystore.p12"
}
}
}
diff --git a/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-tls-client-auth-no-truststore.json b/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-tls-client-auth-no-truststore.json
index 6a727f7c27..f1253d7700 100644
--- a/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-tls-client-auth-no-truststore.json
+++ b/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-tls-client-auth-no-truststore.json
@@ -5,7 +5,6 @@
"status"
],
"tlsConfig": {
- "active": true,
"keyStoreType": "PKCS12",
"keyStorePassword": "tika-secret",
"keyStoreFile": "src/test/resources/ssl-keys/tika-server-keystore.p12",
diff --git a/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-tls-client-auth-valid.json b/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-tls-client-auth-valid.json
index c054650c67..c7cf9d01e6 100644
--- a/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-tls-client-auth-valid.json
+++ b/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-tls-client-auth-valid.json
@@ -5,7 +5,6 @@
"status"
],
"tlsConfig": {
- "active": true,
"keyStoreType": "PKCS12",
"keyStorePassword": "tika-secret",
"keyStoreFile": "src/test/resources/ssl-keys/tika-server-keystore.p12",
diff --git a/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-tls-env.json b/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-tls-env.json
new file mode 100644
index 0000000000..550620486e
--- /dev/null
+++ b/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-tls-env.json
@@ -0,0 +1,9 @@
+{
+ "server": {
+ "tlsConfig": {
+ "keyStoreType": "PKCS12",
+ "keyStorePassword": "${env:PATH}",
+ "keyStoreFile": "src/test/resources/ssl-keys/tika-server-keystore.p12"
+ }
+ }
+}
diff --git a/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-tls-missing-keystore.json b/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-tls-missing-keystore.json
index 8084bbfc23..11741d9e38 100644
--- a/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-tls-missing-keystore.json
+++ b/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-tls-missing-keystore.json
@@ -5,7 +5,6 @@
"status"
],
"tlsConfig": {
- "active": true,
"keyStoreType": "PKCS12",
"keyStorePassword": "tika-secret",
"keyStoreFile": "src/test/resources/ssl-keys/does-not-exist.p12"
diff --git a/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-tls-one-way-template.json b/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-tls-one-way-template.json
index e83dc807dd..44f4b310af 100644
--- a/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-tls-one-way-template.json
+++ b/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-tls-one-way-template.json
@@ -12,11 +12,9 @@
"rmeta"
],
"tlsConfig": {
- "active": true,
"keyStoreType": "PKCS12",
"keyStorePassword": "tika-secret",
"keyStoreFile": "{SSL_KEYS}/tika-server-keystore.p12",
- "clientAuthenticationWanted": false,
"clientAuthenticationRequired": false
}
},
diff --git a/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-tls-partial-truststore.json b/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-tls-partial-truststore.json
index f65eda2d1c..a1e2825676 100644
--- a/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-tls-partial-truststore.json
+++ b/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-tls-partial-truststore.json
@@ -5,7 +5,6 @@
"status"
],
"tlsConfig": {
- "active": true,
"keyStoreType": "PKCS12",
"keyStorePassword": "tika-secret",
"keyStoreFile": "src/test/resources/ssl-keys/tika-server-keystore.p12",
diff --git a/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-tls-client-auth-no-truststore.json b/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-tls-removed-keys.json
similarity index 61%
copy from tika-server/tika-server-core/src/test/resources/configs/tika-config-server-tls-client-auth-no-truststore.json
copy to tika-server/tika-server-core/src/test/resources/configs/tika-config-server-tls-removed-keys.json
index 6a727f7c27..2905fe76b3 100644
--- a/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-tls-client-auth-no-truststore.json
+++ b/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-tls-removed-keys.json
@@ -1,15 +1,11 @@
{
"server": {
- "port": 9999,
- "endpoints": [
- "status"
- ],
"tlsConfig": {
- "active": true,
"keyStoreType": "PKCS12",
"keyStorePassword": "tika-secret",
"keyStoreFile": "src/test/resources/ssl-keys/tika-server-keystore.p12",
- "clientAuthenticationRequired": true
+ "includedProtocols": ["TLSv1.3"],
+ "excludedCipherSuites": [".*CBC.*"]
}
}
}
diff --git a/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-tls-two-way-template.json b/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-tls-two-way-template.json
index 6cac23b3e4..1d31499b9d 100644
--- a/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-tls-two-way-template.json
+++ b/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-tls-two-way-template.json
@@ -12,14 +12,12 @@
"rmeta"
],
"tlsConfig": {
- "active": true,
"keyStoreType": "PKCS12",
"keyStorePassword": "tika-secret",
"keyStoreFile": "{SSL_KEYS}/tika-server-keystore.p12",
"trustStoreType": "PKCS12",
"trustStorePassword": "tika-secret",
"trustStoreFile": "{SSL_KEYS}/tika-server-truststore.p12",
- "clientAuthenticationWanted": true,
"clientAuthenticationRequired": true
}
},
diff --git a/tika-server/tika-server-core/src/test/resources/ssl-keys/revocation/ca-truststore.p12 b/tika-server/tika-server-core/src/test/resources/ssl-keys/revocation/ca-truststore.p12
new file mode 100644
index 0000000000..3a648d404d
Binary files /dev/null and b/tika-server/tika-server-core/src/test/resources/ssl-keys/revocation/ca-truststore.p12 differ
diff --git a/tika-server/tika-server-core/src/test/resources/ssl-keys/revocation/ca.crl b/tika-server/tika-server-core/src/test/resources/ssl-keys/revocation/ca.crl
new file mode 100644
index 0000000000..922cf47a9f
Binary files /dev/null and b/tika-server/tika-server-core/src/test/resources/ssl-keys/revocation/ca.crl differ
diff --git a/tika-server/tika-server-core/src/test/resources/ssl-keys/revocation/forged.p12 b/tika-server/tika-server-core/src/test/resources/ssl-keys/revocation/forged.p12
new file mode 100644
index 0000000000..6aa6c776b8
Binary files /dev/null and b/tika-server/tika-server-core/src/test/resources/ssl-keys/revocation/forged.p12 differ
diff --git a/tika-server/tika-server-core/src/test/resources/ssl-keys/revocation/generate.sh b/tika-server/tika-server-core/src/test/resources/ssl-keys/revocation/generate.sh
new file mode 100755
index 0000000000..ad189c2480
--- /dev/null
+++ b/tika-server/tika-server-core/src/test/resources/ssl-keys/revocation/generate.sh
@@ -0,0 +1,88 @@
+#!/usr/bin/env bash
+#
+# 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.
+#
+# Regenerates the fixtures for TikaServerClientRevocationTest: a CA, client certificates it
+# issued, and a CRL revoking one of them. Run from this directory; needs openssl and keytool.
+# The CRL distribution point is http://127.0.0.1:18181/ca.crl, which the test serves.
+set -euo pipefail
+cd "$(dirname "$0")"
+work=$(mktemp -d)
+trap 'rm -rf "$work"' EXIT
+cat > "$work/ca.cnf" <<CNF
+[ ca ]
+default_ca = testca
+[ testca ]
+dir = $work
+database = $work/index.txt
+new_certs_dir = $work
+serial = $work/serial
+crlnumber = $work/crlnumber
+default_md = sha256
+policy = anything
+default_days = 36500
+default_crl_days = 36500
+[ anything ]
+commonName = supplied
+[ v3ca ]
+basicConstraints = critical,CA:TRUE
+keyUsage = critical,keyCertSign,cRLSign
+[ client ]
+basicConstraints = CA:FALSE
+keyUsage = digitalSignature
+extendedKeyUsage = clientAuth
+[ client_cdp ]
+basicConstraints = CA:FALSE
+keyUsage = digitalSignature
+extendedKeyUsage = clientAuth
+crlDistributionPoints = URI:http://127.0.0.1:18181/ca.crl
+[ forged_cdp ]
+basicConstraints = CA:FALSE
+keyUsage = digitalSignature
+extendedKeyUsage = clientAuth
+crlDistributionPoints = URI:http://127.0.0.1:18181/forged.crl
+CNF
+touch "$work/index.txt"; echo 1000 > "$work/serial"; echo 1000 > "$work/crlnumber"
+
+openssl req -x509 -newkey rsa:2048 -nodes -keyout "$work/ca.key" -out "$work/ca.crt" -days 36500 \
+ -subj "/CN=Tika Test CA" -config "$work/ca.cnf" -extensions v3ca
+rm -f ca-truststore.p12
+keytool -importcert -noprompt -alias ca -file "$work/ca.crt" -keystore ca-truststore.p12 \
+ -storetype PKCS12 -storepass tika-secret
+
+issue() { # name extensions
+ openssl req -newkey rsa:2048 -nodes -keyout "$work/$1.key" -out "$work/$1.csr" -subj "/CN=client-$1"
+ openssl ca -batch -config "$work/ca.cnf" -extensions "$2" -in "$work/$1.csr" -out "$work/$1.crt" \
+ -cert "$work/ca.crt" -keyfile "$work/ca.key"
+ openssl pkcs12 -export -in "$work/$1.crt" -inkey "$work/$1.key" -certfile "$work/ca.crt" -name client \
+ -out "$1.p12" -passout pass:tika-secret
+}
+issue good client_cdp
+issue revoked client_cdp
+issue no-cdp client
+
+# same issuer name as the CA, signed by another key: must be rejected before any CRL fetch
+openssl req -x509 -newkey rsa:2048 -nodes -keyout "$work/fake-ca.key" -out "$work/fake-ca.crt" -days 36500 \
+ -subj "/CN=Tika Test CA" -config "$work/ca.cnf" -extensions v3ca
+openssl req -newkey rsa:2048 -nodes -keyout "$work/forged.key" -out "$work/forged.csr" -subj "/CN=client-forged"
+openssl x509 -req -in "$work/forged.csr" -CA "$work/fake-ca.crt" -CAkey "$work/fake-ca.key" -set_serial 7 \
+ -days 36500 -extfile "$work/ca.cnf" -extensions forged_cdp -out "$work/forged.crt"
+openssl pkcs12 -export -in "$work/forged.crt" -inkey "$work/forged.key" -certfile "$work/fake-ca.crt" -name client \
+ -out forged.p12 -passout pass:tika-secret
+
+openssl ca -config "$work/ca.cnf" -revoke "$work/revoked.crt" -cert "$work/ca.crt" -keyfile "$work/ca.key"
+openssl ca -config "$work/ca.cnf" -gencrl -cert "$work/ca.crt" -keyfile "$work/ca.key" -out "$work/ca.crl.pem"
+openssl crl -in "$work/ca.crl.pem" -outform DER -out ca.crl
diff --git a/tika-server/tika-server-core/src/test/resources/ssl-keys/revocation/good.p12 b/tika-server/tika-server-core/src/test/resources/ssl-keys/revocation/good.p12
new file mode 100644
index 0000000000..fcf403c447
Binary files /dev/null and b/tika-server/tika-server-core/src/test/resources/ssl-keys/revocation/good.p12 differ
diff --git a/tika-server/tika-server-core/src/test/resources/ssl-keys/revocation/no-cdp.p12 b/tika-server/tika-server-core/src/test/resources/ssl-keys/revocation/no-cdp.p12
new file mode 100644
index 0000000000..3c8920b9d8
Binary files /dev/null and b/tika-server/tika-server-core/src/test/resources/ssl-keys/revocation/no-cdp.p12 differ
diff --git a/tika-server/tika-server-core/src/test/resources/ssl-keys/revocation/revoked.p12 b/tika-server/tika-server-core/src/test/resources/ssl-keys/revocation/revoked.p12
new file mode 100644
index 0000000000..10023c9ccd
Binary files /dev/null and b/tika-server/tika-server-core/src/test/resources/ssl-keys/revocation/revoked.p12 differ