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]
