This is an automated email from the ASF dual-hosted git repository.

rcordier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit dece20343d3b84329da6f11314d1b8a7a4aca06b
Author: Benoit TELLIER <[email protected]>
AuthorDate: Sat Mar 28 16:28:25 2026 +0100

    JAMES-4193 Configurable SSL session cache
---
 docs/modules/servers/partials/configure/ssl.adoc   | 26 ++++++++++++++++++++++
 .../apache/james/protocols/netty/SslConfig.java    | 25 ++++++++++++++++++---
 .../protocols/lib/LegacyJavaEncryptionFactory.java |  4 ++++
 3 files changed, 52 insertions(+), 3 deletions(-)

diff --git a/docs/modules/servers/partials/configure/ssl.adoc 
b/docs/modules/servers/partials/configure/ssl.adoc
index df740c26bb..5cb4f9470c 100644
--- a/docs/modules/servers/partials/configure/ssl.adoc
+++ b/docs/modules/servers/partials/configure/ssl.adoc
@@ -105,6 +105,32 @@ Please note `JKS` keystore format is also supported 
(default value if no keystor
 ....
 
 
+=== TLS session cache
+
+TLS session resumption allows clients to reconnect without performing a full 
TLS handshake, reducing CPU usage and connection latency.
+
+James caches completed TLS sessions server-side using the JDK 
`SSLSessionContext`. The cache size can be configured with `sessionCacheSize`:
+
+[source,xml]
+....
+<tls socketTLS="false" startTLS="true">
+  <keystore>file://conf/keystore</keystore>
+  <keystoreType>PKCS12</keystoreType>
+  <secret>yoursecret</secret>
+
+ <sessionCache>
+  <size>1024</size>
+  <timeout>600s</timeout>
+ </sessionCache>
+</tls>
+....
+
+When a cached session is found, the client and server skip the asymmetric key 
exchange and certificate verification,
+reusing the previously negotiated symmetric keys. This is particularly 
effective for mail clients that open many
+short-lived connections (e.g. mobile push sync).
+
+The `timeout` is expressed in seconds (`s`), minutes (`m`), or hours (`h`). If 
`sessionCache` is omitted, the JDK defaults are used.
+
 === Client authentication via certificates
 
 When you enable TLS, you may also configure the server to require a client 
certificate for authentication:
diff --git 
a/protocols/netty/src/main/java/org/apache/james/protocols/netty/SslConfig.java 
b/protocols/netty/src/main/java/org/apache/james/protocols/netty/SslConfig.java
index f185f43395..f9811f999c 100644
--- 
a/protocols/netty/src/main/java/org/apache/james/protocols/netty/SslConfig.java
+++ 
b/protocols/netty/src/main/java/org/apache/james/protocols/netty/SslConfig.java
@@ -19,10 +19,14 @@
 
 package org.apache.james.protocols.netty;
 
+import java.time.Duration;
+import java.util.Optional;
+
 import org.apache.commons.configuration2.HierarchicalConfiguration;
 import org.apache.commons.configuration2.ex.ConfigurationException;
 import org.apache.commons.configuration2.tree.ImmutableNode;
 import org.apache.james.protocols.api.ClientAuth;
+import org.apache.james.util.DurationParser;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -60,6 +64,9 @@ public class SslConfig {
             String truststoreType = 
config.getString("tls.clientAuth.truststoreType", "JKS");
             char[] truststoreSecret = 
config.getString("tls.clientAuth.truststoreSecret", "").toCharArray();
             boolean enableOCSPCRLChecks = 
config.getBoolean("tls.enableOCSPCRLChecks", false);
+            Optional<Integer> sessionCacheSize = 
Optional.ofNullable(config.getInteger("tls.sessionCache.size", null));
+            Optional<Duration> sessionCacheTimeout = 
Optional.ofNullable(config.getString("tls.sessionCache.timeout", null))
+                .map(DurationParser::parse);
 
             if (useSSL) {
                 LOGGER.info("SSL enabled with keystore({}) at {}, certificates 
{}", keystoreType, keystore, certificates);
@@ -67,9 +74,9 @@ public class SslConfig {
                 LOGGER.info("TLS enabled with auth {} using truststore {}", 
clientAuth, truststore);
             }
 
-            return new SslConfig(useStartTLS, useSSL, clientAuth, keystore, 
keystoreType, privateKey, certificates, secret, truststore, truststoreType, 
enabledCipherSuites, enabledProtocols, truststoreSecret, enableOCSPCRLChecks);
+            return new SslConfig(useStartTLS, useSSL, clientAuth, keystore, 
keystoreType, privateKey, certificates, secret, truststore, truststoreType, 
enabledCipherSuites, enabledProtocols, truststoreSecret, enableOCSPCRLChecks, 
sessionCacheSize, sessionCacheTimeout);
         } else {
-            return new SslConfig(useStartTLS, useSSL, clientAuth, null, null, 
null, null, null, null, null, null, null, null, false);
+            return new SslConfig(useStartTLS, useSSL, clientAuth, null, null, 
null, null, null, null, null, null, null, null, false, Optional.empty(), 
Optional.empty());
         }
     }
 
@@ -87,10 +94,12 @@ public class SslConfig {
     private final String[] enabledProtocols;
     private final char[] truststoreSecret;
     private final boolean enableOCSPCRLChecks;
+    private final Optional<Integer> sessionCacheSize;
+    private final Optional<Duration> sessionCacheTimeout;
 
     public SslConfig(boolean useStartTLS, boolean useSSL, ClientAuth 
clientAuth, String keystore, String keystoreType, String privateKey,
                      String certificates, String secret, String truststore, 
String truststoreType, String[] enabledCipherSuites, String[] enabledProtocols,
-                     char[] truststoreSecret, boolean enableOCSPCRLChecks) {
+                     char[] truststoreSecret, boolean enableOCSPCRLChecks, 
Optional<Integer> sessionCacheSize, Optional<Duration> sessionCacheTimeout) {
         this.useStartTLS = useStartTLS;
         this.useSSL = useSSL;
         this.clientAuth = clientAuth;
@@ -105,6 +114,8 @@ public class SslConfig {
         this.enabledProtocols = enabledProtocols;
         this.truststoreSecret = truststoreSecret;
         this.enableOCSPCRLChecks = enableOCSPCRLChecks;
+        this.sessionCacheSize = sessionCacheSize;
+        this.sessionCacheTimeout = sessionCacheTimeout;
     }
 
     public ClientAuth getClientAuth() {
@@ -162,4 +173,12 @@ public class SslConfig {
     public boolean ocspCRLChecksEnabled() {
         return enableOCSPCRLChecks;
     }
+
+    public Optional<Integer> getSessionCacheSize() {
+        return sessionCacheSize;
+    }
+
+    public Optional<Duration> getSessionCacheTimeout() {
+        return sessionCacheTimeout;
+    }
 }
diff --git 
a/server/protocols/protocols-library/src/main/java/org/apache/james/protocols/lib/LegacyJavaEncryptionFactory.java
 
b/server/protocols/protocols-library/src/main/java/org/apache/james/protocols/lib/LegacyJavaEncryptionFactory.java
index 07a2754e2f..d31dd586b2 100644
--- 
a/server/protocols/protocols-library/src/main/java/org/apache/james/protocols/lib/LegacyJavaEncryptionFactory.java
+++ 
b/server/protocols/protocols-library/src/main/java/org/apache/james/protocols/lib/LegacyJavaEncryptionFactory.java
@@ -24,6 +24,7 @@ import java.security.cert.CertPathBuilder;
 import java.security.cert.PKIXBuilderParameters;
 import java.security.cert.PKIXRevocationChecker;
 import java.security.cert.X509CertSelector;
+import java.time.Duration;
 import java.util.EnumSet;
 import java.util.Optional;
 
@@ -97,6 +98,9 @@ public class LegacyJavaEncryptionFactory implements 
Encryption.Factory {
 
         SSLContext context = sslFactoryBuilder.build().getSslContext();
 
+        sslConfig.getSessionCacheSize().ifPresent(size -> 
context.getServerSessionContext().setSessionCacheSize(size));
+        
sslConfig.getSessionCacheTimeout().map(Duration::toSeconds).map(Math::toIntExact).ifPresent(timeout
 -> context.getServerSessionContext().setSessionTimeout(timeout));
+
         if (sslConfig.useStartTLS()) {
             return Encryption.createStartTls(context, 
sslConfig.getEnabledCipherSuites(), sslConfig.getEnabledProtocols(), 
sslConfig.getClientAuth());
         } else {


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to