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 <[email protected]>
AuthorDate: Fri Oct 23 13:30:56 2026 -0400

    TIKA-4994: improve tls configuration (#3407)
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
 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

Reply via email to