This is an automated email from the ASF dual-hosted git repository.
markt-asf pushed a commit to branch 11.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/11.0.x by this push:
new 3ffad06df0 First pass at TLS 1.3 support
3ffad06df0 is described below
commit 3ffad06df0ead3a5562010a6e94c8e46d34ce6a6
Author: Mark Thomas <[email protected]>
AuthorDate: Thu Sep 24 18:18:24 2026 +0100
First pass at TLS 1.3 support
Co-authored-by: GitHub Copilot (GPT-5.6 Sol) <[email protected]>
---
.../apache/catalina/tribes/group/GroupChannel.java | 22 ++++-
.../catalina/tribes/group/GroupChannelMBean.java | 4 +
.../catalina/tribes/group/TribesSslContext.java | 60 ++++++++----
.../catalina/tribes/transport/nio/TlsChannel.java | 9 +-
.../tomcat/util/net/openssl/OpenSSLContext.java | 4 +-
.../tomcat/util/net/openssl/OpenSSLEngine.java | 3 +-
.../net/openssl/OpenSSLPreSharedKeySelector.java | 30 +++++-
.../util/net/openssl/panama/OpenSSLContext.java | 101 ++++++++++++++++++++-
.../util/net/openssl/panama/OpenSSLEngine.java | 3 +-
.../util/openssl/SSL_psk_use_session_cb_func.java | 64 +++++++++++++
java/org/apache/tomcat/util/openssl/openssl_h.java | 38 ++++++++
.../catalina/tribes/group/TestGroupChannelTls.java | 9 +-
.../tribes/transport/nio/TestTlsChannel.java | 58 +++++++++---
webapps/docs/changelog.xml | 2 +-
webapps/docs/config/cluster-channel.xml | 16 +++-
15 files changed, 380 insertions(+), 43 deletions(-)
diff --git a/java/org/apache/catalina/tribes/group/GroupChannel.java
b/java/org/apache/catalina/tribes/group/GroupChannel.java
index 840559a48b..15896f5ad5 100644
--- a/java/org/apache/catalina/tribes/group/GroupChannel.java
+++ b/java/org/apache/catalina/tribes/group/GroupChannel.java
@@ -125,8 +125,10 @@ public class GroupChannel extends ChannelInterceptorBase
implements ManagedChann
protected boolean optionCheck = false;
private boolean secure;
+ private String pskDigest = "SHA256";
private String pskIdentity;
private String pskKey;
+ private String pskProtocol = "TLSv1.3";
private volatile TribesSslContext sslContext;
/**
@@ -461,7 +463,7 @@ public class GroupChannel extends ChannelInterceptorBase
implements ManagedChann
if (sslContext == null) {
if (pskKey != null && pskIdentity != null) {
try {
- sslContext = new TribesSslContext(pskIdentity, pskKey);
+ sslContext = new TribesSslContext(pskIdentity, pskKey,
pskDigest, pskProtocol);
} catch (Exception e) {
if (secure) {
throw new
ChannelException(sm.getString("groupChannel.tlsUnavailable"), e);
@@ -677,6 +679,15 @@ public class GroupChannel extends ChannelInterceptorBase
implements ManagedChann
this.secure = secure;
}
+ @Override
+ public String getPskDigest() {
+ return pskDigest;
+ }
+
+ public void setPskDigest(String pskDigest) {
+ this.pskDigest = pskDigest;
+ }
+
@Override
public String getPskIdentity() {
return pskIdentity;
@@ -690,6 +701,15 @@ public class GroupChannel extends ChannelInterceptorBase
implements ManagedChann
this.pskKey = pskKey;
}
+ @Override
+ public String getPskProtocol() {
+ return pskProtocol;
+ }
+
+ public void setPskProtocol(String pskProtocol) {
+ this.pskProtocol = pskProtocol;
+ }
+
public TribesSslContext getSslContext() {
return sslContext;
}
diff --git a/java/org/apache/catalina/tribes/group/GroupChannelMBean.java
b/java/org/apache/catalina/tribes/group/GroupChannelMBean.java
index bd63930a9c..45e5491312 100644
--- a/java/org/apache/catalina/tribes/group/GroupChannelMBean.java
+++ b/java/org/apache/catalina/tribes/group/GroupChannelMBean.java
@@ -54,8 +54,12 @@ public interface GroupChannelMBean {
boolean getSecure();
+ String getPskDigest();
+
String getPskIdentity();
+ String getPskProtocol();
+
// Operations
/**
* Starts the channel with the given service type.
diff --git a/java/org/apache/catalina/tribes/group/TribesSslContext.java
b/java/org/apache/catalina/tribes/group/TribesSslContext.java
index 4ab59fbd80..527e6e1453 100644
--- a/java/org/apache/catalina/tribes/group/TribesSslContext.java
+++ b/java/org/apache/catalina/tribes/group/TribesSslContext.java
@@ -18,6 +18,7 @@ package org.apache.catalina.tribes.group;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
import java.util.Set;
import javax.net.ssl.SSLEngine;
@@ -34,12 +35,12 @@ public class TribesSslContext implements AutoCloseable {
private final Object serverContext;
private final boolean ffm;
- public TribesSslContext(String identity, String key) throws Exception {
- Object[] contexts = createFfmContexts(identity, key);
+ public TribesSslContext(String identity, String key, String digest, String
protocol) throws Exception {
+ Object[] contexts = createFfmContexts(identity, key, digest, protocol);
if (contexts != null) {
ffm = true;
} else {
- contexts = createNativeContexts(identity, key);
+ contexts = createNativeContexts(identity, key, digest, protocol);
ffm = false;
}
if (contexts == null) {
@@ -57,7 +58,8 @@ public class TribesSslContext implements AutoCloseable {
return createEngine(serverContext, false);
}
- private static Object[] createFfmContexts(String identity, String key)
throws Exception {
+ private static Object[] createFfmContexts(String identity, String key,
String digest, String protocol)
+ throws Exception {
/*
* Tribes may be used stand-alone so don't assume there is an
OpenSSLLifecycleListener that Tomcat is already
* using but do use the library in a manner that is compatible if
Tomcat is using such an instance.
@@ -74,28 +76,47 @@ public class TribesSslContext implements AutoCloseable {
library.getMethod("destroy").invoke(null);
return null;
}
- return createContexts(FFM_CONTEXT, identity, key);
+ return createContexts(FFM_CONTEXT, identity, key, digest, protocol);
}
- private static Object[] createNativeContexts(String identity, String key)
throws Exception {
+ private static Object[] createNativeContexts(String identity, String key,
String digest, String protocol)
+ throws Exception {
/*
* Tribes may be used stand-alone so don't assume there is an
AprLifecycleListener that Tomcat is already using
* but do use the library in a manner that is compatible if Tomcat is
using such an instance.
*/
- Class<?> listener =
Class.forName("org.apache.catalina.core.AprLifecycleListener");
- listener.getConstructor().newInstance();
- if (!((Boolean)
listener.getMethod("isAprAvailable").invoke(null)).booleanValue()) {
+ Class<?> listenerClass =
Class.forName("org.apache.catalina.core.AprLifecycleListener");
+ Object listener = listenerClass.getConstructor().newInstance();
+ if (!((Boolean)
listenerClass.getMethod("isAprAvailable").invoke(null)).booleanValue()) {
return null;
}
- return createContexts(NATIVE_CONTEXT, identity, key);
+ /*
+ * isAprAvailable() only performs the lightweight native library load
check. The OpenSSL-specific
+ * initialization (including the native ex_data index set up by
SSL.initialize(), which the PSK callbacks
+ * rely on to recover their SSL_CTX-specific state) only happens in
response to a Lifecycle.BEFORE_INIT_EVENT,
+ * which a standalone Tribes channel never fires. Fire it here so
OpenSSL is fully initialized before any
+ * SSL_CTX/SSL objects are created.
+ */
+ Class<?> lifecycleClass =
Class.forName("org.apache.catalina.Lifecycle");
+ Object lifecycleProxy =
Proxy.newProxyInstance(lifecycleClass.getClassLoader(),
+ new Class<?>[] { lifecycleClass }, (proxy, method, args) ->
null);
+ Class<?> lifecycleEventClass =
Class.forName("org.apache.catalina.LifecycleEvent");
+ Constructor<?> eventConstructor =
+ lifecycleEventClass.getConstructor(lifecycleClass,
String.class, Object.class);
+ Object event = eventConstructor.newInstance(lifecycleProxy,
+ lifecycleClass.getField("BEFORE_INIT_EVENT").get(null), null);
+ Class<?> listenerInterface =
Class.forName("org.apache.catalina.LifecycleListener");
+ listenerInterface.getMethod("lifecycleEvent",
lifecycleEventClass).invoke(listener, event);
+ return createContexts(NATIVE_CONTEXT, identity, key, digest, protocol);
}
- private static Object[] createContexts(String className, String identity,
String key) throws Exception {
+ private static Object[] createContexts(String className, String identity,
String key, String digest, String protocol)
+ throws Exception {
Class<?> clazz = Class.forName(className);
Class<?> certificateClass =
Class.forName("org.apache.tomcat.util.net.SSLHostConfigCertificate");
Constructor<?> constructor = clazz.getConstructor(certificateClass,
java.util.List.class, boolean.class);
- Object client = constructor.newInstance(createCertificate(identity,
key), null, Boolean.TRUE);
- Object server = constructor.newInstance(createCertificate(identity,
key), null, Boolean.FALSE);
+ Object client = constructor.newInstance(createCertificate(identity,
key, digest, protocol), null, Boolean.TRUE);
+ Object server = constructor.newInstance(createCertificate(identity,
key, digest, protocol), null, Boolean.FALSE);
Method init = clazz.getMethod("init",
javax.net.ssl.KeyManager[].class, javax.net.ssl.TrustManager[].class,
java.security.SecureRandom.class);
init.invoke(client, null, null, null);
@@ -103,15 +124,22 @@ public class TribesSslContext implements AutoCloseable {
return new Object[] { client, server };
}
- private static Object createCertificate(String identity, String key)
throws Exception {
+ private static Object createCertificate(String identity, String key,
String digest, String protocol)
+ throws Exception {
Class<?> configClass =
Class.forName("org.apache.tomcat.util.net.SSLHostConfig");
Object config = configClass.getConstructor().newInstance();
- configClass.getMethod("setProtocols", String.class).invoke(config,
"TLSv1.2");
+ configClass.getMethod("setProtocols", String.class).invoke(config,
protocol);
configClass.getMethod("setEnabledProtocols",
String[].class).invoke(config,
- (Object) new String[] { "TLSv1.2" });
+ (Object) new String[] { protocol });
configClass.getMethod("setCiphers", String.class).invoke(config,
"PSK-AES128-GCM-SHA256");
+ if ("TLSv1.3".equals(protocol)) {
+ String cipherSuites =
"SHA384".equalsIgnoreCase(digest.replace("-", "")) ? "TLS_AES_256_GCM_SHA384" :
+ "TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256";
+ configClass.getMethod("setCipherSuites",
String.class).invoke(config, cipherSuites);
+ }
Class<?> pskClass =
Class.forName("org.apache.tomcat.util.net.SSLHostConfigPreSharedKey");
Object psk = pskClass.getConstructor(configClass).newInstance(config);
+ pskClass.getMethod("setDigest", String.class).invoke(psk, digest);
pskClass.getMethod("setIdentity", String.class).invoke(psk, identity);
pskClass.getMethod("setKey", String.class).invoke(psk, key);
configClass.getMethod("addPreSharedKey", pskClass).invoke(config, psk);
diff --git a/java/org/apache/catalina/tribes/transport/nio/TlsChannel.java
b/java/org/apache/catalina/tribes/transport/nio/TlsChannel.java
index 6db5243194..f351b72a72 100644
--- a/java/org/apache/catalina/tribes/transport/nio/TlsChannel.java
+++ b/java/org/apache/catalina/tribes/transport/nio/TlsChannel.java
@@ -68,9 +68,16 @@ final class TlsChannel implements ByteChannel {
case NEED_WRAP -> {
networkOutput.clear();
SSLEngineResult result = engine.wrap(EMPTY, networkOutput);
- status = result.getHandshakeStatus();
+ if (result.getStatus() == Status.BUFFER_OVERFLOW) {
+ networkOutput = expand(networkOutput,
engine.getSession().getPacketBufferSize());
+ continue;
+ }
networkOutput.flip();
writeFully(networkOutput);
+ if (result.getStatus() == Status.CLOSED) {
+ throw new EOFException();
+ }
+ status = result.getHandshakeStatus();
continue;
}
case NEED_UNWRAP, NEED_UNWRAP_AGAIN -> {
diff --git a/java/org/apache/tomcat/util/net/openssl/OpenSSLContext.java
b/java/org/apache/tomcat/util/net/openssl/OpenSSLContext.java
index e2774e1d9c..436b4d1bd1 100644
--- a/java/org/apache/tomcat/util/net/openssl/OpenSSLContext.java
+++ b/java/org/apache/tomcat/util/net/openssl/OpenSSLContext.java
@@ -459,7 +459,9 @@ public class OpenSSLContext implements
org.apache.tomcat.util.net.SSLContext {
for (String protocol : sslHostConfig.getEnabledProtocols()) {
if (Constants.SSL_PROTO_TLSv1_2.equals(protocol) &&
clientMode) {
SSLContext.setPskClientCallback(state.ctx, selector);
- } else if (Constants.SSL_PROTO_TLSv1_2.equals(protocol)) {
+ } else if (Constants.SSL_PROTO_TLSv1_3.equals(protocol) &&
clientMode) {
+ SSLContext.setPskUseSessionCallback(state.ctx,
selector);
+ } else if (Constants.SSL_PROTO_TLSv1_2.equals(protocol) &&
!clientMode) {
SSLContext.setPskServerCallback(state.ctx, selector);
} else if (Constants.SSL_PROTO_TLSv1_3.equals(protocol) &&
!clientMode) {
SSLContext.setPskFindSessionCallback(state.ctx,
selector);
diff --git a/java/org/apache/tomcat/util/net/openssl/OpenSSLEngine.java
b/java/org/apache/tomcat/util/net/openssl/OpenSSLEngine.java
index 4fc7a1a56a..33bf8c15eb 100644
--- a/java/org/apache/tomcat/util/net/openssl/OpenSSLEngine.java
+++ b/java/org/apache/tomcat/util/net/openssl/OpenSSLEngine.java
@@ -1073,7 +1073,8 @@ public final class OpenSSLEngine extends SSLEngine
implements SSLUtil.ProtocolIn
// No pending data to be sent to the peer
// Check to see if we have finished handshaking
int handshakeCount = SSL.getHandshakeCount(state.ssl);
- if (handshakeCount != currentHandshake &&
SSL.renegotiatePending(state.ssl) == 0 &&
+ if (handshakeCount != currentHandshake && SSL.isInInit(state.ssl)
== 0 &&
+ SSL.renegotiatePending(state.ssl) == 0 &&
(SSL.getPostHandshakeAuthInProgress(state.ssl) == 0)) {
if (alpn) {
selectedProtocol = SSL.getAlpnSelected(state.ssl);
diff --git
a/java/org/apache/tomcat/util/net/openssl/OpenSSLPreSharedKeySelector.java
b/java/org/apache/tomcat/util/net/openssl/OpenSSLPreSharedKeySelector.java
index bbc0bd41e6..b7ad9802a3 100644
--- a/java/org/apache/tomcat/util/net/openssl/OpenSSLPreSharedKeySelector.java
+++ b/java/org/apache/tomcat/util/net/openssl/OpenSSLPreSharedKeySelector.java
@@ -76,15 +76,37 @@ public class OpenSSLPreSharedKeySelector implements
PreSharedKeySelector {
if (psk == null) {
return null;
}
+ if (!setCipherSuite(psk, cipherSuite)) {
+ return null;
+ }
+ // Need to limit keys to 48 bytes for TLS 1.3
+ return truncateToLength(identityString, psk.getKeyInternal(), 48);
+ }
+
+ @Override
+ public byte[] selectClient(long ssl, String[] identity, int[] cipherSuite)
{
+ if (identityToKeyMap.isEmpty()) {
+ return null;
+ }
+ SSLHostConfigPreSharedKey psk =
identityToKeyMap.values().iterator().next();
+ if (!setCipherSuite(psk, cipherSuite)) {
+ return null;
+ }
+ identity[0] = psk.getIdentity();
+ // Need to limit keys to 48 bytes for TLS 1.3
+ return truncateToLength(psk.getIdentity(), psk.getKeyInternal(), 48);
+ }
+
+ private static boolean setCipherSuite(SSLHostConfigPreSharedKey psk, int[]
cipherSuite) {
switch (psk.getDigestInternal()) {
case SHA256:
// Any TLS ciphersuite that OpenSSL recognises that uses
SHA256 works here
cipherSuite[0] = 0x1301;
- break;
+ return true;
case SHA384:
// Any TLS ciphersuite that OpenSSL recognises that uses
SHA384 works here
cipherSuite[0] = 0x1302;
- break;
+ return true;
case AEAD:
case GOST89MAC:
case GOST94:
@@ -92,10 +114,8 @@ public class OpenSSLPreSharedKeySelector implements
PreSharedKeySelector {
case SHA1:
default:
// Unsupported digest
- return null;
+ return false;
}
- // Need to limit keys to 48 bytes for TLS 1.3
- return truncateToLength(identityString, psk.getKeyInternal(), 48);
}
diff --git a/java/org/apache/tomcat/util/net/openssl/panama/OpenSSLContext.java
b/java/org/apache/tomcat/util/net/openssl/panama/OpenSSLContext.java
index 36c82320d5..bdba3ecb4a 100644
--- a/java/org/apache/tomcat/util/net/openssl/panama/OpenSSLContext.java
+++ b/java/org/apache/tomcat/util/net/openssl/panama/OpenSSLContext.java
@@ -36,7 +36,9 @@ import java.util.Arrays;
import java.util.Base64;
import java.util.Iterator;
import java.util.List;
+import java.util.Map;
import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLEngine;
@@ -76,6 +78,7 @@ import
org.apache.tomcat.util.openssl.SSL_CTX_set_verify$callback;
import org.apache.tomcat.util.openssl.SSL_psk_client_cb_func;
import org.apache.tomcat.util.openssl.SSL_psk_find_session_cb_func;
import org.apache.tomcat.util.openssl.SSL_psk_server_cb_func;
+import org.apache.tomcat.util.openssl.SSL_psk_use_session_cb_func;
import org.apache.tomcat.util.openssl.openssl_h;
import org.apache.tomcat.util.openssl.openssl_h_Compatibility;
import org.apache.tomcat.util.openssl.pem_password_cb;
@@ -664,7 +667,13 @@ public class OpenSSLContext implements
org.apache.tomcat.util.net.SSLContext {
if (Constants.SSL_PROTO_TLSv1_2.equals(protocol) &&
clientMode) {
SSL_CTX_set_psk_client_callback(state.sslCtx,
SSL_psk_client_cb_func.allocate(new
PskClientCallback(selector), contextArena));
- } else if (Constants.SSL_PROTO_TLSv1_2.equals(protocol)) {
+ } else if (Constants.SSL_PROTO_TLSv1_3.equals(protocol) &&
clientMode) {
+ if (openssl_h_Compatibility.LIBRESSL) {
+ throw new
SSLException(sm.getString("openssl.pskTls13Unsupported"));
+ }
+ SSL_CTX_set_psk_use_session_callback(state.sslCtx,
SSL_psk_use_session_cb_func
+ .allocate(new PskUseSessionCallback(selector,
contextArena), contextArena));
+ } else if (Constants.SSL_PROTO_TLSv1_2.equals(protocol) &&
!clientMode) {
SSL_CTX_set_psk_server_callback(state.sslCtx,
SSL_psk_server_cb_func
.allocate(new PskServerCallback(selector),
contextArena));
} else if (Constants.SSL_PROTO_TLSv1_3.equals(protocol) &&
!clientMode) {
@@ -931,14 +940,94 @@ public class OpenSSLContext implements
org.apache.tomcat.util.net.SSLContext {
try {
MemorySegment keySegment =
localArena.allocateFrom(ValueLayout.JAVA_BYTE, key);
try {
+ MemorySegment sidCtxSegment =
+ localArena.allocateFrom(ValueLayout.JAVA_BYTE,
DEFAULT_SESSION_ID_CONTEXT);
+ if (SSL_SESSION_set1_master_key(session, keySegment,
key.length) == 0 ||
+ SSL_SESSION_set_cipher(session, cipher) == 0 ||
+ SSL_SESSION_set_protocol_version(session,
TLS1_3_VERSION()) == 0 ||
+ SSL_SESSION_set1_id_context(session,
sidCtxSegment,
+ DEFAULT_SESSION_ID_CONTEXT.length) ==
0) {
+ return 0;
+ }
+ } finally {
+ keySegment.fill((byte) 0);
+ }
+ sessionPointerSegment.set(ValueLayout.ADDRESS, 0, session);
+ success = true;
+ return 1;
+ } finally {
+ if (!success) {
+ SSL_SESSION_free(session);
+ }
+ }
+ }
+ }
+ }
+
+ private static class PskUseSessionCallback implements
SSL_psk_use_session_cb_func.Function {
+
+ private final OpenSSLPreSharedKeySelector selector;
+ private final Arena contextArena;
+ private final Map<String,ClientIdentity> identities = new
ConcurrentHashMap<>();
+
+ PskUseSessionCallback(OpenSSLPreSharedKeySelector selector, Arena
contextArena) {
+ this.selector = selector;
+ this.contextArena = contextArena;
+ }
+
+ @Override
+ public int apply(MemorySegment ssl, MemorySegment md, MemorySegment
identity, MemorySegment identityLength,
+ MemorySegment sessionPointer) {
+ try (var localArena = Arena.ofConfined()) {
+ MemorySegment sessionPointerSegment =
+
sessionPointer.reinterpret(ValueLayout.ADDRESS.byteSize(), localArena, null);
+ sessionPointerSegment.set(ValueLayout.ADDRESS, 0,
MemorySegment.NULL);
+
+ String[] selectedIdentity = new String[1];
+ int[] cipherSuite = new int[1];
+ byte[] key = selector.selectClient(ssl.address(),
selectedIdentity, cipherSuite);
+ if (key == null || selectedIdentity[0] == null) {
+ return 1;
+ }
+ if (key.length == 0 || cipherSuite[0] <= 0 || cipherSuite[0] >
0xFFFF) {
+ return 0;
+ }
+
+ byte[] cipherId = new byte[] { (byte) (cipherSuite[0] >> 8),
(byte) cipherSuite[0] };
+ MemorySegment cipher =
+ SSL_CIPHER_find(ssl,
localArena.allocateFrom(ValueLayout.JAVA_BYTE, cipherId));
+ if (MemorySegment.NULL.equals(cipher)
+ ||
!Constants.SSL_PROTO_TLSv1_3.equals(SSL_CIPHER_get_version(cipher).getString(0)))
{
+ return 0;
+ }
+
+ MemorySegment session = SSL_SESSION_new();
+ if (MemorySegment.NULL.equals(session)) {
+ return 0;
+ }
+ boolean success = false;
+ try {
+ MemorySegment keySegment =
localArena.allocateFrom(ValueLayout.JAVA_BYTE, key);
+ try {
+ MemorySegment sidCtxSegment =
+ localArena.allocateFrom(ValueLayout.JAVA_BYTE,
DEFAULT_SESSION_ID_CONTEXT);
if (SSL_SESSION_set1_master_key(session, keySegment,
key.length) == 0 ||
SSL_SESSION_set_cipher(session, cipher) == 0 ||
- SSL_SESSION_set_protocol_version(session,
TLS1_3_VERSION()) == 0) {
+ SSL_SESSION_set_protocol_version(session,
TLS1_3_VERSION()) == 0 ||
+ SSL_SESSION_set1_id_context(session,
sidCtxSegment,
+ DEFAULT_SESSION_ID_CONTEXT.length) ==
0) {
return 0;
}
} finally {
keySegment.fill((byte) 0);
}
+
+ ClientIdentity clientIdentity =
+ identities.computeIfAbsent(selectedIdentity[0],
this::createIdentity);
+ identity.reinterpret(ValueLayout.ADDRESS.byteSize(),
localArena, null)
+ .set(ValueLayout.ADDRESS, 0,
clientIdentity.value());
+
identityLength.reinterpret(ValueLayout.JAVA_LONG.byteSize(), localArena, null)
+ .set(ValueLayout.JAVA_LONG, 0,
clientIdentity.length());
sessionPointerSegment.set(ValueLayout.ADDRESS, 0, session);
success = true;
return 1;
@@ -949,6 +1038,14 @@ public class OpenSSLContext implements
org.apache.tomcat.util.net.SSLContext {
}
}
}
+
+ private ClientIdentity createIdentity(String identity) {
+ byte[] bytes = identity.getBytes(StandardCharsets.UTF_8);
+ return new ClientIdentity(contextArena.allocateFrom(identity),
bytes.length);
+ }
+ }
+
+ private record ClientIdentity(MemorySegment value, long length) {
}
diff --git a/java/org/apache/tomcat/util/net/openssl/panama/OpenSSLEngine.java
b/java/org/apache/tomcat/util/net/openssl/panama/OpenSSLEngine.java
index b58f667d90..a3526446cd 100644
--- a/java/org/apache/tomcat/util/net/openssl/panama/OpenSSLEngine.java
+++ b/java/org/apache/tomcat/util/net/openssl/panama/OpenSSLEngine.java
@@ -1036,7 +1036,8 @@ public final class OpenSSLEngine extends SSLEngine
implements SSLUtil.ProtocolIn
// No pending data to be sent to the peer
// Check to see if we have finished handshaking
- if (state.handshakeCount != currentHandshake &&
SSL_renegotiate_pending(state.ssl) == 0 &&
+ if (state.handshakeCount != currentHandshake &&
SSL_in_init(state.ssl) == 0 &&
+ SSL_renegotiate_pending(state.ssl) == 0 &&
(state.phaState != PHAState.START)) {
if (alpn) {
selectedProtocol = getProtocolNegotiated();
diff --git
a/java/org/apache/tomcat/util/openssl/SSL_psk_use_session_cb_func.java
b/java/org/apache/tomcat/util/openssl/SSL_psk_use_session_cb_func.java
new file mode 100644
index 0000000000..78a56e276f
--- /dev/null
+++ b/java/org/apache/tomcat/util/openssl/SSL_psk_use_session_cb_func.java
@@ -0,0 +1,64 @@
+/*
+ * 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.
+ */
+
+// Generated by jextract
+
+package org.apache.tomcat.util.openssl;
+
+import java.lang.foreign.Arena;
+import java.lang.foreign.FunctionDescriptor;
+import java.lang.foreign.Linker;
+import java.lang.foreign.MemorySegment;
+import java.lang.invoke.MethodHandle;
+
+/**
+ * {@snippet lang = c : * SSL_psk_use_session_cb_func cb
+ * }
+ */
+@SuppressWarnings("javadoc")
+public class SSL_psk_use_session_cb_func {
+
+ public interface Function {
+ int apply(MemorySegment ssl, MemorySegment md, MemorySegment identity,
MemorySegment identityLength,
+ MemorySegment session);
+ }
+
+ private static final FunctionDescriptor $DESC =
FunctionDescriptor.of(openssl_h.C_INT, openssl_h.C_POINTER,
+ openssl_h.C_POINTER, openssl_h.C_POINTER, openssl_h.C_POINTER,
openssl_h.C_POINTER);
+
+ public static FunctionDescriptor descriptor() {
+ return $DESC;
+ }
+
+ private static final MethodHandle UP$MH =
+ openssl_h.upcallHandle(SSL_psk_use_session_cb_func.Function.class,
"apply", $DESC);
+
+ public static MemorySegment allocate(SSL_psk_use_session_cb_func.Function
fi, Arena scope) {
+ return Linker.nativeLinker().upcallStub(UP$MH.bindTo(fi), $DESC,
scope);
+ }
+
+ private static final MethodHandle DOWN$MH =
Linker.nativeLinker().downcallHandle($DESC);
+
+ public static int invoke(MemorySegment funcPtr, MemorySegment ssl,
MemorySegment md, MemorySegment identity,
+ MemorySegment identityLength, MemorySegment session) {
+ try {
+ return (int) DOWN$MH.invokeExact(funcPtr, ssl, md, identity,
identityLength, session);
+ } catch (Throwable ex$) {
+ throw new AssertionError("should not reach here", ex$);
+ }
+ }
+}
diff --git a/java/org/apache/tomcat/util/openssl/openssl_h.java
b/java/org/apache/tomcat/util/openssl/openssl_h.java
index 4533664eed..6cc1de6c71 100644
--- a/java/org/apache/tomcat/util/openssl/openssl_h.java
+++ b/java/org/apache/tomcat/util/openssl/openssl_h.java
@@ -10373,6 +10373,25 @@ public class openssl_h {
}
}
+ private static class SSL_CTX_set_psk_use_session_callback {
+ public static final FunctionDescriptor DESC =
+ FunctionDescriptor.ofVoid(openssl_h.C_POINTER,
openssl_h.C_POINTER);
+ public static final MemorySegment ADDR =
openssl_h.findOrThrow("SSL_CTX_set_psk_use_session_callback");
+ public static final MethodHandle HANDLE =
Linker.nativeLinker().downcallHandle(ADDR, DESC);
+ }
+
+ public static void SSL_CTX_set_psk_use_session_callback(MemorySegment ctx,
MemorySegment callback) {
+ var mh$ = SSL_CTX_set_psk_use_session_callback.HANDLE;
+ try {
+ if (TRACE_DOWNCALLS) {
+ traceDowncall("SSL_CTX_set_psk_use_session_callback", ctx,
callback);
+ }
+ mh$.invokeExact(ctx, callback);
+ } catch (Throwable ex$) {
+ throw new AssertionError("should not reach here", ex$);
+ }
+ }
+
private static class SSL_SESSION_new {
public static final FunctionDescriptor DESC =
FunctionDescriptor.of(openssl_h.C_POINTER);
public static final MemorySegment ADDR =
openssl_h.findOrThrow("SSL_SESSION_new");
@@ -10428,6 +10447,25 @@ public class openssl_h {
}
}
+ private static class SSL_SESSION_set1_id_context {
+ public static final FunctionDescriptor DESC =
FunctionDescriptor.of(openssl_h.C_INT, openssl_h.C_POINTER,
+ openssl_h.C_POINTER, openssl_h.C_INT);
+ public static final MemorySegment ADDR =
openssl_h.findOrThrow("SSL_SESSION_set1_id_context");
+ public static final MethodHandle HANDLE =
Linker.nativeLinker().downcallHandle(ADDR, DESC);
+ }
+
+ public static int SSL_SESSION_set1_id_context(MemorySegment session,
MemorySegment sidCtx, int sidCtxLength) {
+ var mh$ = SSL_SESSION_set1_id_context.HANDLE;
+ try {
+ if (TRACE_DOWNCALLS) {
+ traceDowncall("SSL_SESSION_set1_id_context", session, sidCtx,
sidCtxLength);
+ }
+ return (int) mh$.invokeExact(session, sidCtx, sidCtxLength);
+ } catch (Throwable ex$) {
+ throw new AssertionError("should not reach here", ex$);
+ }
+ }
+
private static class SSL_SESSION_set_cipher {
public static final FunctionDescriptor DESC =
FunctionDescriptor.of(openssl_h.C_INT, openssl_h.C_POINTER,
openssl_h.C_POINTER);
diff --git a/test/org/apache/catalina/tribes/group/TestGroupChannelTls.java
b/test/org/apache/catalina/tribes/group/TestGroupChannelTls.java
index 01df48af57..8f2f35375e 100644
--- a/test/org/apache/catalina/tribes/group/TestGroupChannelTls.java
+++ b/test/org/apache/catalina/tribes/group/TestGroupChannelTls.java
@@ -32,10 +32,17 @@ import org.apache.catalina.tribes.transport.ReceiverBase;
public class TestGroupChannelTls {
+ @Test
+ public void testPskDefaults() {
+ GroupChannel channel = new GroupChannel();
+ Assert.assertEquals("SHA256", channel.getPskDigest());
+ Assert.assertEquals("TLSv1.3", channel.getPskProtocol());
+ }
+
@Test
public void testSecureMessage() throws Exception {
try (@SuppressWarnings("unused") TribesSslContext ignored =
- new TribesSslContext("tribes-test",
"000102030405060708090a0b0c0d0e0f")) {
+ new TribesSslContext("tribes-test",
"000102030405060708090a0b0c0d0e0f", "TLSv1.3", "SHA256")) {
// Verify that a supported TLS provider is available before
creating the channels.
} catch (Exception e) {
Assume.assumeNoException(e);
diff --git a/test/org/apache/catalina/tribes/transport/nio/TestTlsChannel.java
b/test/org/apache/catalina/tribes/transport/nio/TestTlsChannel.java
index acf1032d8c..50e0290f5f 100644
--- a/test/org/apache/catalina/tribes/transport/nio/TestTlsChannel.java
+++ b/test/org/apache/catalina/tribes/transport/nio/TestTlsChannel.java
@@ -22,6 +22,9 @@ import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeUnit;
+
+import javax.net.ssl.SSLEngine;
import org.junit.Assert;
import org.junit.Assume;
@@ -33,12 +36,27 @@ public class TestTlsChannel {
@Test
public void testPskRoundTrip() throws Exception {
- try (TribesSslContext context = createContext();
+ doTestPskRoundTrip(createContext("SHA256", "TLSv1.3"), "TLSv1.3");
+ }
+
+ @Test
+ public void testPskRoundTripTls13Sha384() throws Exception {
+ doTestPskRoundTrip(createContext("SHA384", "TLSv1.3"), "TLSv1.3");
+ }
+
+ @Test
+ public void testPskRoundTripTls12() throws Exception {
+ doTestPskRoundTrip(createContext("SHA256", "TLSv1.2"), "TLSv1.2");
+ }
+
+ private static void doTestPskRoundTrip(TribesSslContext context, String
protocol) throws Exception {
+ try (context;
ServerSocketChannel server = ServerSocketChannel.open()) {
server.bind(new InetSocketAddress("localhost", 0));
FutureTask<Void> serverTask = new FutureTask<>(() -> {
- try (SocketChannel socket = server.accept();
- TlsChannel tls = new TlsChannel(socket.socket(),
context.createServerEngine())) {
+ SSLEngine engine = context.createServerEngine();
+ try (SocketChannel socket = server.accept(); TlsChannel tls =
new TlsChannel(socket.socket(), engine)) {
+
Assert.assertTrue(engine.getSession().getProtocol().startsWith(protocol));
Assert.assertEquals("request", read(tls));
tls.write(ByteBuffer.wrap("response".getBytes(StandardCharsets.UTF_8)));
}
@@ -46,12 +64,23 @@ public class TestTlsChannel {
});
Thread serverThread = new Thread(serverTask);
serverThread.start();
- try (SocketChannel socket =
SocketChannel.open(server.getLocalAddress());
- TlsChannel tls = new TlsChannel(socket.socket(),
context.createClientEngine())) {
-
tls.write(ByteBuffer.wrap("request".getBytes(StandardCharsets.UTF_8)));
- Assert.assertEquals("response", read(tls));
+ SSLEngine engine = context.createClientEngine();
+ try {
+ try (SocketChannel socket =
SocketChannel.open(server.getLocalAddress());
+ TlsChannel tls = new TlsChannel(socket.socket(),
engine)) {
+
Assert.assertTrue(engine.getSession().getProtocol().startsWith(protocol));
+
tls.write(ByteBuffer.wrap("request".getBytes(StandardCharsets.UTF_8)));
+ Assert.assertEquals("response", read(tls));
+ }
+ serverTask.get();
+ } catch (Exception e) {
+ try {
+ serverTask.get(5, TimeUnit.SECONDS);
+ } catch (Exception serverException) {
+ e.addSuppressed(serverException);
+ }
+ throw e;
}
- serverTask.get();
}
}
@@ -87,8 +116,13 @@ public class TestTlsChannel {
}
private static TribesSslContext createContext() {
+ return createContext("SHA256", "TLSv1.3");
+ }
+
+ private static TribesSslContext createContext(String digest, String
protocol) {
try {
- return new TribesSslContext("tribes-test",
"000102030405060708090a0b0c0d0e0f");
+ return new TribesSslContext("tribes-test",
+
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", digest,
protocol);
} catch (Exception e) {
Assume.assumeNoException(e);
return null;
@@ -97,7 +131,9 @@ public class TestTlsChannel {
private static String read(TlsChannel channel) throws Exception {
ByteBuffer buffer = ByteBuffer.allocate(32);
- channel.read(buffer);
+ while (buffer.position() == 0) {
+ Assert.assertTrue(channel.read(buffer) >= 0);
+ }
buffer.flip();
return StandardCharsets.UTF_8.decode(buffer).toString();
}
@@ -105,7 +141,7 @@ public class TestTlsChannel {
private static byte[] read(TlsChannel channel, int length) throws
Exception {
ByteBuffer buffer = ByteBuffer.allocate(length);
while (buffer.hasRemaining()) {
- Assert.assertTrue(channel.read(buffer) > 0);
+ Assert.assertTrue(channel.read(buffer) >= 0);
}
return buffer.array();
}
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index b7bc41b5f6..51123290ed 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -667,7 +667,7 @@
other cluster members. (remm)
</fix>
<add>
- Add TLS pre-shared key support to Apache Tribes. (markt)
+ Add TLS 1.2 and TLS 1.3 pre-shared key support to Apache Tribes.
(markt)
</add>
</changelog>
</subsection>
diff --git a/webapps/docs/config/cluster-channel.xml
b/webapps/docs/config/cluster-channel.xml
index 146e123e4b..e080a7830d 100644
--- a/webapps/docs/config/cluster-channel.xml
+++ b/webapps/docs/config/cluster-channel.xml
@@ -134,12 +134,24 @@
<code>pskKey</code> to enable TLS.
</attribute>
+ <attribute name="pskDigest" required="false">
+ The message digest algorithm associated with the pre-shared key for
+ TLS 1.3, either <code>SHA256</code> or <code>SHA384</code>. The default
+ is <code>SHA256</code>. This attribute is ignored for TLS 1.2.
+ </attribute>
+
<attribute name="pskKey" required="false">
The TLS pre-shared key encoded as hexadecimal characters. This must be
configured together with <code>pskIdentity</code>. The key is not
exposed through JMX. It should be no longer than 512 bytes for TLS 1.2.
- If the value is truely random then 32 bytes are sufficient for a
256-bit
- cipher suite.
+ For TLS 1.3, it must be no longer than 48 bytes. If the value is truely
+ random then 32 bytes are sufficient for a 256-bit cipher suite.
+ </attribute>
+
+ <attribute name="pskProtocol" required="false">
+ The TLS protocol to use with the pre-shared key. The permitted values
+ are <code>TLSv1.2</code> and <code>TLSv1.3</code>. The default is
+ <code>TLSv1.3</code>.
</attribute>
<attribute name="jmxEnabled" required="false">
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]