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 15b4f20504 Add support for pre-shared keys with Tomcat Native TLS
connections.
15b4f20504 is described below
commit 15b4f205042308a52ee738e716dc62b390ca71f6
Author: Mark Thomas <[email protected]>
AuthorDate: Mon Sep 21 20:40:53 2026 +0100
Add support for pre-shared keys with Tomcat Native TLS connections.
Adds a new sub-component SSLHostConfigPreSharedKey that is configured
via a new <PreSharedKey ... /> element in server.xml along with
storeconfig support.
Supports pre-shared key only (no cert) configuration for a TLS
connector.
Co-authored-by: GitHub Copilot (GPT-5.6 Sol) <[email protected]>
---
java/org/apache/catalina/startup/Catalina.java | 5 +
.../catalina/startup/PreSharedKeyCreateRule.java | 56 ++++++++
.../catalina/storeconfig/SSLHostConfigSF.java | 14 +-
.../catalina/storeconfig/server-registry.xml | 9 +-
.../apache/tomcat/util/net/AbstractEndpoint.java | 5 +-
java/org/apache/tomcat/util/net/SSLHostConfig.java | 38 ++++++
.../tomcat/util/net/SSLHostConfigPreSharedKey.java | 141 +++++++++++++++++++++
java/org/apache/tomcat/util/net/jsse/JSSEUtil.java | 4 +
.../tomcat/util/net/jsse/LocalStrings.properties | 1 +
.../util/net/openssl/LocalStrings.properties | 3 +
.../tomcat/util/net/openssl/OpenSSLContext.java | 28 +++-
.../net/openssl/OpenSSLPreSharedKeySelector.java | 102 +++++++++++++++
.../tomcat/util/net/openssl/OpenSSLUtil.java | 3 +
.../catalina/storeconfig/TestStoreConfig.java | 8 ++
.../apache/tomcat/util/net/TestSSLHostConfig.java | 68 ++++++++++
webapps/docs/changelog.xml | 4 +
webapps/docs/config/http.xml | 32 ++++-
17 files changed, 509 insertions(+), 12 deletions(-)
diff --git a/java/org/apache/catalina/startup/Catalina.java
b/java/org/apache/catalina/startup/Catalina.java
index 5d514d9d0e..fbbaf01ad3 100644
--- a/java/org/apache/catalina/startup/Catalina.java
+++ b/java/org/apache/catalina/startup/Catalina.java
@@ -556,6 +556,11 @@ public class Catalina {
digester.addSetNext("Server/Service/Connector/SSLHostConfig/Certificate",
"addCertificate",
"org.apache.tomcat.util.net.SSLHostConfigCertificate");
+
digester.addRule("Server/Service/Connector/SSLHostConfig/PreSharedKey", new
PreSharedKeyCreateRule());
+
digester.addSetProperties("Server/Service/Connector/SSLHostConfig/PreSharedKey");
+
digester.addSetNext("Server/Service/Connector/SSLHostConfig/PreSharedKey",
"addPreSharedKey",
+ "org.apache.tomcat.util.net.SSLHostConfigPreSharedKey");
+
digester.addObjectCreate("Server/Service/Connector/SSLHostConfig/OpenSSLConf",
"org.apache.tomcat.util.net.openssl.OpenSSLConf");
digester.addSetProperties("Server/Service/Connector/SSLHostConfig/OpenSSLConf");
diff --git a/java/org/apache/catalina/startup/PreSharedKeyCreateRule.java
b/java/org/apache/catalina/startup/PreSharedKeyCreateRule.java
new file mode 100644
index 0000000000..24fd7fce8f
--- /dev/null
+++ b/java/org/apache/catalina/startup/PreSharedKeyCreateRule.java
@@ -0,0 +1,56 @@
+/*
+ * 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.catalina.startup;
+
+import org.apache.tomcat.util.digester.Rule;
+import org.apache.tomcat.util.net.SSLHostConfig;
+import org.apache.tomcat.util.net.SSLHostConfigPreSharedKey;
+import org.xml.sax.Attributes;
+
+/**
+ * Rule implementation that creates an SSLHostConfigPreSharedKey.
+ */
+public class PreSharedKeyCreateRule extends Rule {
+
+ /**
+ * Default constructor.
+ */
+ public PreSharedKeyCreateRule() {
+ }
+
+ @Override
+ public void begin(String namespace, String name, Attributes attributes)
throws Exception {
+ SSLHostConfig sslHostConfig = (SSLHostConfig) digester.peek();
+ SSLHostConfigPreSharedKey preSharedKey = new
SSLHostConfigPreSharedKey(sslHostConfig);
+
+ digester.push(preSharedKey);
+
+ StringBuilder code = digester.getGeneratedCode();
+ if (code != null) {
+ code.append(SSLHostConfigPreSharedKey.class.getName()).append(' ')
+ .append(digester.toVariableName(preSharedKey));
+ code.append(" = new
").append(SSLHostConfigPreSharedKey.class.getName());
+
code.append('(').append(digester.toVariableName(sslHostConfig)).append(");");
+ code.append(System.lineSeparator());
+ }
+ }
+
+ @Override
+ public void end(String namespace, String name) throws Exception {
+ digester.pop();
+ }
+}
diff --git a/java/org/apache/catalina/storeconfig/SSLHostConfigSF.java
b/java/org/apache/catalina/storeconfig/SSLHostConfigSF.java
index df245691ec..f9213f0eeb 100644
--- a/java/org/apache/catalina/storeconfig/SSLHostConfigSF.java
+++ b/java/org/apache/catalina/storeconfig/SSLHostConfigSF.java
@@ -23,6 +23,7 @@ import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.net.SSLHostConfig;
import org.apache.tomcat.util.net.SSLHostConfigCertificate;
+import org.apache.tomcat.util.net.SSLHostConfigPreSharedKey;
import org.apache.tomcat.util.net.openssl.OpenSSLConf;
import org.apache.tomcat.util.net.openssl.OpenSSLConfCmd;
@@ -84,8 +85,13 @@ public class SSLHostConfigSF extends StoreFactoryBase {
throws Exception {
if (aSSLHostConfig instanceof SSLHostConfig sslHostConfig) {
// Store nested <SSLHostConfigCertificate> elements
- SSLHostConfigCertificate[] hostConfigsCertificates =
- sslHostConfig.getCertificates().toArray(new
SSLHostConfigCertificate[0]);
+ SSLHostConfigCertificate[] hostConfigsCertificates;
+ if (sslHostConfig.isPreSharedKeyOnly()) {
+ hostConfigsCertificates = new SSLHostConfigCertificate[0];
+ } else {
+ hostConfigsCertificates =
+ sslHostConfig.getCertificates().toArray(new
SSLHostConfigCertificate[0]);
+ }
// Remove the runtime placeholder certificate that was created when
// no certificate was configured
SSLHostConfigCertificate defaultCertificate =
sslHostConfig.getDefaultCertificate();
@@ -99,6 +105,10 @@ public class SSLHostConfigSF extends StoreFactoryBase {
hostConfigsCertificates = certificates.toArray(new
SSLHostConfigCertificate[0]);
}
storeElementArray(aWriter, indent, hostConfigsCertificates);
+ // Store nested <PreSharedKey> elements
+ SSLHostConfigPreSharedKey[] preSharedKeys =
+ sslHostConfig.getPreSharedKeys().toArray(new
SSLHostConfigPreSharedKey[0]);
+ storeElementArray(aWriter, indent, preSharedKeys);
// Store nested <OpenSSLConf> element
OpenSSLConf openSslConf = sslHostConfig.getOpenSslConf();
openSslConf = removeRuntimeCommands(openSslConf, sslHostConfig);
diff --git a/java/org/apache/catalina/storeconfig/server-registry.xml
b/java/org/apache/catalina/storeconfig/server-registry.xml
index b728402000..eb5c46ff37 100644
--- a/java/org/apache/catalina/storeconfig/server-registry.xml
+++ b/java/org/apache/catalina/storeconfig/server-registry.xml
@@ -132,6 +132,14 @@
storeFactoryClass="org.apache.catalina.storeconfig.StoreFactoryBase"
storeAppenderClass="org.apache.catalina.storeconfig.CertificateStoreAppender">
</Description>
+ <Description
+ tag="PreSharedKey"
+ standard="true"
+ default="true"
+ tagClass="org.apache.tomcat.util.net.SSLHostConfigPreSharedKey"
+ children="false"
+ storeFactoryClass="org.apache.catalina.storeconfig.StoreFactoryBase">
+ </Description>
<Description
tag="OpenSSLConf"
standard="true"
@@ -495,4 +503,3 @@
storeFactoryClass="org.apache.catalina.storeconfig.StoreFactoryBase">
</Description>
</Registry>
-
diff --git a/java/org/apache/tomcat/util/net/AbstractEndpoint.java
b/java/org/apache/tomcat/util/net/AbstractEndpoint.java
index 9af6762bd2..05c368ba04 100644
--- a/java/org/apache/tomcat/util/net/AbstractEndpoint.java
+++ b/java/org/apache/tomcat/util/net/AbstractEndpoint.java
@@ -594,7 +594,9 @@ public abstract class AbstractEndpoint<S, U> {
certificate.setSslContextGenerated(sslContext);
}
- logCertificate(certificate);
+ if (!sslHostConfig.isPreSharedKeyOnly()) {
+ logCertificate(certificate);
+ }
}
}
@@ -2572,4 +2574,3 @@ public abstract class AbstractEndpoint<S, U> {
*/
protected abstract void destroySocket(U socket);
}
-
diff --git a/java/org/apache/tomcat/util/net/SSLHostConfig.java
b/java/org/apache/tomcat/util/net/SSLHostConfig.java
index bc3630294e..612d4df9e5 100644
--- a/java/org/apache/tomcat/util/net/SSLHostConfig.java
+++ b/java/org/apache/tomcat/util/net/SSLHostConfig.java
@@ -153,6 +153,10 @@ public class SSLHostConfig implements Serializable {
* The set of certificate configurations.
*/
private final Set<SSLHostConfigCertificate> certificates = new
LinkedHashSet<>(4);
+ /**
+ * The set of pre-shared key configurations.
+ */
+ private final Set<SSLHostConfigPreSharedKey> preSharedKeys = new
LinkedHashSet<>();
// Common
/**
* The path to the certificate revocation list file.
@@ -587,6 +591,40 @@ public class SSLHostConfig implements Serializable {
}
+ /**
+ * Adds a pre-shared key to this SSL host configuration.
+ *
+ * @param preSharedKey the pre-shared key to add
+ */
+ public void addPreSharedKey(SSLHostConfigPreSharedKey preSharedKey) {
+ preSharedKeys.add(preSharedKey);
+ }
+
+
+ /**
+ * Returns the set of pre-shared keys.
+ *
+ * @return the pre-shared keys
+ */
+ public Set<SSLHostConfigPreSharedKey> getPreSharedKeys() {
+ return preSharedKeys;
+ }
+
+
+ /**
+ * Determines whether this configuration uses pre-shared keys without a
certificate.
+ *
+ * @return {@code true} if pre-shared keys are configured and no
certificate was explicitly configured
+ */
+ public boolean isPreSharedKeyOnly() {
+ if (preSharedKeys.isEmpty()) {
+ return false;
+ }
+ return certificates.isEmpty() ||
+ certificates.size() == 1 &&
certificates.contains(defaultCertificate);
+ }
+
+
// ----------------------------------------- Common configuration
properties
/**
diff --git a/java/org/apache/tomcat/util/net/SSLHostConfigPreSharedKey.java
b/java/org/apache/tomcat/util/net/SSLHostConfigPreSharedKey.java
new file mode 100644
index 0000000000..7660490631
--- /dev/null
+++ b/java/org/apache/tomcat/util/net/SSLHostConfigPreSharedKey.java
@@ -0,0 +1,141 @@
+/*
+ * 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.tomcat.util.net;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Locale;
+
+import org.apache.tomcat.util.buf.HexUtils;
+import org.apache.tomcat.util.net.openssl.ciphers.MessageDigest;
+
+/**
+ * Represents a pre-shared key configuration for a virtual host.
+ */
+public class SSLHostConfigPreSharedKey implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ private final SSLHostConfig sslHostConfig;
+
+ private String identity;
+ private byte[] key;
+ private MessageDigest digest = MessageDigest.SHA256;
+
+ /**
+ * Creates a new pre-shared key configuration for the given host.
+ *
+ * @param sslHostConfig the parent SSL host configuration
+ */
+ public SSLHostConfigPreSharedKey(SSLHostConfig sslHostConfig) {
+ this.sslHostConfig = sslHostConfig;
+ }
+
+ /**
+ * Returns the SSLHostConfig that owns this pre-shared key configuration.
+ *
+ * @return the parent SSLHostConfig
+ */
+ public SSLHostConfig getSSLHostConfig() {
+ return sslHostConfig;
+ }
+
+ /**
+ * Returns the identity associated with the pre-shared key.
+ *
+ * @return the pre-shared key identity
+ */
+ public String getIdentity() {
+ return identity;
+ }
+
+ /**
+ * Sets the identity associated with the pre-shared key.
+ *
+ * @param identity the pre-shared key identity
+ */
+ public void setIdentity(String identity) {
+ this.identity = identity;
+ }
+
+ /**
+ * Returns the pre-shared key as a hexadecimal string.
+ *
+ * @return the hexadecimal pre-shared key
+ */
+ public String getKey() {
+ return HexUtils.toHexString(key);
+ }
+
+ /**
+ * Sets the pre-shared key from a hexadecimal string.
+ *
+ * @param key the hexadecimal pre-shared key
+ *
+ * @throws IllegalArgumentException if the key is not valid hexadecimal
+ */
+ public void setKey(String key) {
+ this.key = HexUtils.fromHexString(key);
+ }
+
+ /**
+ * Returns the pre-shared key.
+ *
+ * @return the pre-shared key
+ */
+ public byte[] getKeyInternal() {
+ return key;
+ }
+
+ /**
+ * Returns the message digest algorithm name.
+ *
+ * @return the message digest algorithm name
+ */
+ public String getDigest() {
+ if (digest == null) {
+ return null;
+ }
+ return digest.name();
+ }
+
+ /**
+ * Sets the message digest algorithm.
+ *
+ * @param digest the message digest name
+ *
+ * @throws IllegalArgumentException if the message digest is not recognized
+ */
+ public void setDigest(String digest) {
+ if (digest == null) {
+ this.digest = null;
+ return;
+ }
+ // Remove all "-". Handles SHA-256 as well as odd variations like
Sh-A256 but there is no harm in that.
+ this.digest = MessageDigest.valueOf(digest.replaceAll("-",
"").toUpperCase(Locale.ENGLISH));
+ }
+
+ /**
+ * Returns the message digest.
+ *
+ * @return the message digest
+ */
+ public MessageDigest getDigestInternal() {
+ return digest;
+ }
+}
diff --git a/java/org/apache/tomcat/util/net/jsse/JSSEUtil.java
b/java/org/apache/tomcat/util/net/jsse/JSSEUtil.java
index 62215b6ddf..5d830db28e 100644
--- a/java/org/apache/tomcat/util/net/jsse/JSSEUtil.java
+++ b/java/org/apache/tomcat/util/net/jsse/JSSEUtil.java
@@ -158,6 +158,10 @@ public class JSSEUtil extends SSLUtilBase {
log.warn(sm.getString("jsseUtil.opensslconf.present"));
}
+ if (!sslHostConfig.getPreSharedKeys().isEmpty()) {
+ log.warn(sm.getString("jsseUtil.psk.present"));
+ }
+
initialized = true;
}
}
diff --git a/java/org/apache/tomcat/util/net/jsse/LocalStrings.properties
b/java/org/apache/tomcat/util/net/jsse/LocalStrings.properties
index 85fc27deca..42fcc34036 100644
--- a/java/org/apache/tomcat/util/net/jsse/LocalStrings.properties
+++ b/java/org/apache/tomcat/util/net/jsse/LocalStrings.properties
@@ -22,6 +22,7 @@ jsseSupport.clientCertError=Error trying to obtain a
certificate from the client
jsseUtil.excludeProtocol=The SSL protocol [{0}] which is supported in this JRE
was excluded from the protocols available to Tomcat
jsseUtil.noDefaultProtocols=Unable to determine a default for
sslEnabledProtocols. Set an explicit value to ensure the connector can start.
jsseUtil.opensslconf.present=A connector is configured to use a JSSE TLS
implementation with OpenSSL specific OpenSSLConf configuration elements. The
OpenSSLConf configuration elements will be ignored.
+jsseUtil.psk.present=A connector is configured to use a JSSE TLS
implementation with OpenSSL specific pre-shared key configuration elements. The
pre-shared key configuration elements will be ignored.
pemFile.boundaryTypeMismatch=The end boundary [{2}] of the PEM file [{0}] does
not match the type [{1}] of the start boundary that opened that part
pemFile.encryption.broken=The PEM file [{0}] is using [{1}] which is
considered broken because a brute force attack is trivial with current hardware
diff --git a/java/org/apache/tomcat/util/net/openssl/LocalStrings.properties
b/java/org/apache/tomcat/util/net/openssl/LocalStrings.properties
index e6b3eee170..86efb9c01a 100644
--- a/java/org/apache/tomcat/util/net/openssl/LocalStrings.properties
+++ b/java/org/apache/tomcat/util/net/openssl/LocalStrings.properties
@@ -60,6 +60,9 @@ openssl.trustManagerMissing=No trust manager found
opensslImplementation.notAvailable=A suitable Tomcat Native implementation is
not available
+opensslPreSharedKeySelector.identity.duplicate=The identity [{0}] has been
defined multiple times for SSL virtual host [{1}]
+opensslPreSharedKeySelector.truncate=The PSK for identity [{0}] has been
truncated for [{1}] bytes for compatibility with OpenSSL.
+
opensslconf.applyCommand=OpenSSLConf applying command (name [{0}], value [{1}])
opensslconf.applyFailed=Failure while applying OpenSSLConf to SSL context
opensslconf.checkCommand=OpenSSLConf checking command (name [{0}], value [{1}])
diff --git a/java/org/apache/tomcat/util/net/openssl/OpenSSLContext.java
b/java/org/apache/tomcat/util/net/openssl/OpenSSLContext.java
index d8383db463..7d65b98948 100644
--- a/java/org/apache/tomcat/util/net/openssl/OpenSSLContext.java
+++ b/java/org/apache/tomcat/util/net/openssl/OpenSSLContext.java
@@ -33,6 +33,7 @@ import java.util.Arrays;
import java.util.Base64;
import java.util.Iterator;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.locks.Lock;
import javax.net.ssl.KeyManager;
@@ -49,6 +50,7 @@ import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.jni.AprStatus;
import org.apache.tomcat.jni.Pool;
+import org.apache.tomcat.jni.PreSharedKeySelector;
import org.apache.tomcat.jni.SSL;
import org.apache.tomcat.jni.SSLConf;
import org.apache.tomcat.jni.SSLContext;
@@ -57,6 +59,7 @@ import org.apache.tomcat.util.net.SSLHostConfig;
import org.apache.tomcat.util.net.SSLHostConfig.CertificateVerification;
import org.apache.tomcat.util.net.SSLHostConfigCertificate;
import org.apache.tomcat.util.net.SSLHostConfigCertificate.Type;
+import org.apache.tomcat.util.net.SSLHostConfigPreSharedKey;
import org.apache.tomcat.util.net.SSLUtilBase;
import org.apache.tomcat.util.res.StringManager;
@@ -372,12 +375,14 @@ public class OpenSSLContext implements
org.apache.tomcat.util.net.SSLContext {
SSLContext.setCipherSuite(state.ctx, sslHostConfig.getCiphers());
SSLContext.setCipherSuitesEx(state.ctx,
sslHostConfig.getCipherSuites());
- // If there is no certificate file must be using a KeyStore so a
KeyManager is required.
- // If there is a certificate file a KeyManager is helpful but not
strictly necessary.
- certificate.setCertificateKeyManager(
- OpenSSLUtil.chooseKeyManager(kms,
certificate.getCertificateFile() == null));
+ if (!sslHostConfig.isPreSharedKeyOnly()) {
+ // If there is no certificate file must be using a KeyStore so
a KeyManager is required.
+ // If there is a certificate file a KeyManager is helpful but
not strictly necessary.
+ certificate.setCertificateKeyManager(
+ OpenSSLUtil.chooseKeyManager(kms,
certificate.getCertificateFile() == null));
- addCertificate(certificate);
+ addCertificate(certificate);
+ }
// Client certificate verification
int value = switch (sslHostConfig.getCertificateVerification()) {
@@ -440,6 +445,19 @@ public class OpenSSLContext implements
org.apache.tomcat.util.net.SSLContext {
}
}
+ Set<SSLHostConfigPreSharedKey> psks =
sslHostConfig.getPreSharedKeys();
+ if (!psks.isEmpty()) {
+ PreSharedKeySelector selector = new
OpenSSLPreSharedKeySelector(psks);
+
+ for (String protocol : sslHostConfig.getEnabledProtocols()) {
+ if (Constants.SSL_PROTO_TLSv1_2.equals(protocol)) {
+ SSLContext.setPskServerCallback(state.ctx, selector);
+ } else if (Constants.SSL_PROTO_TLSv1_3.equals(protocol)) {
+ SSLContext.setPskFindSessionCallback(state.ctx,
selector);
+ }
+ }
+ }
+
if (negotiableProtocols != null && !negotiableProtocols.isEmpty())
{
List<String> protocols = new ArrayList<>(negotiableProtocols);
protocols.add("http/1.1");
diff --git
a/java/org/apache/tomcat/util/net/openssl/OpenSSLPreSharedKeySelector.java
b/java/org/apache/tomcat/util/net/openssl/OpenSSLPreSharedKeySelector.java
new file mode 100644
index 0000000000..33208edbfc
--- /dev/null
+++ b/java/org/apache/tomcat/util/net/openssl/OpenSSLPreSharedKeySelector.java
@@ -0,0 +1,102 @@
+/*
+ * 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.tomcat.util.net.openssl;
+
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.jni.PreSharedKeySelector;
+import org.apache.tomcat.util.net.SSLHostConfigPreSharedKey;
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+ * Callback implementation that Tomcat uses to select a pre-shared key for a
given connection based on the identity
+ * provided by the client.
+ */
+public class OpenSSLPreSharedKeySelector implements PreSharedKeySelector {
+
+ private static final Log log =
LogFactory.getLog(OpenSSLPreSharedKeySelector.class);
+ private static final StringManager sm =
StringManager.getManager(OpenSSLPreSharedKeySelector.class);
+
+ private final Map<String,SSLHostConfigPreSharedKey> identityToKeyMap = new
HashMap<>();
+
+ public OpenSSLPreSharedKeySelector(Set<SSLHostConfigPreSharedKey> psks) {
+ for (SSLHostConfigPreSharedKey psk : psks) {
+ SSLHostConfigPreSharedKey old =
identityToKeyMap.put(psk.getIdentity(), psk);
+ if (old != null) {
+ throw new
IllegalArgumentException(sm.getString("opensslPreSharedKeySelector.identity.duplicate",
+ psk.getIdentity(),
psk.getSSLHostConfig().getHostName()));
+ }
+ }
+ }
+
+ @Override
+ public byte[] select(long ssl, String identity) {
+ SSLHostConfigPreSharedKey psk = identityToKeyMap.get(identity);
+ if (psk == null) {
+ return null;
+ }
+ // Need to limit keys to 48 bytes for TLS 1.3
+ return truncateToLength(identity, psk.getKeyInternal(), 512);
+ }
+
+ @Override
+ public byte[] select(long ssl, byte[] identity, int[] cipherSuite) {
+ String identityString = new String(identity, StandardCharsets.UTF_8);
+ SSLHostConfigPreSharedKey psk = identityToKeyMap.get(identityString);
+ if (psk == null) {
+ return null;
+ }
+ switch (psk.getDigestInternal()) {
+ case SHA256:
+ // Any TLS ciphersuite that OpenSSL recognises that uses
SHA256 works here
+ cipherSuite[0] = 0x1301;
+ break;
+ case SHA384:
+ // Any TLS ciphersuite that OpenSSL recognises that uses
SHA384 works here
+ cipherSuite[0] = 0x1302;
+ break;
+ case AEAD:
+ case GOST89MAC:
+ case GOST94:
+ case MD5:
+ case SHA1:
+ default:
+ // Unsupported digest
+ return null;
+ }
+ // Need to limit keys to 48 bytes for TLS 1.3
+ return truncateToLength(identityString, psk.getKeyInternal(), 48);
+ }
+
+
+ private byte[] truncateToLength(String identity, byte[] input, int length)
{
+ byte[] result;
+ if (input.length > length) {
+ result = new byte[length];
+ System.arraycopy(input, 0, result, 0, length);
+ log.warn(sm.getString("opensslPreSharedKeySelector.truncate",
identity, Integer.toString(length)));
+ } else {
+ result = input;
+ }
+ return result;
+ }
+}
diff --git a/java/org/apache/tomcat/util/net/openssl/OpenSSLUtil.java
b/java/org/apache/tomcat/util/net/openssl/OpenSSLUtil.java
index 3ddae0bc90..5749cfe679 100644
--- a/java/org/apache/tomcat/util/net/openssl/OpenSSLUtil.java
+++ b/java/org/apache/tomcat/util/net/openssl/OpenSSLUtil.java
@@ -117,6 +117,9 @@ public class OpenSSLUtil extends SSLUtilBase {
@Override
public KeyManager[] getKeyManagers() throws Exception {
+ if (sslHostConfig.isPreSharedKeyOnly()) {
+ return null;
+ }
try {
return super.getKeyManagers();
} catch (IllegalArgumentException e) {
diff --git a/test/org/apache/catalina/storeconfig/TestStoreConfig.java
b/test/org/apache/catalina/storeconfig/TestStoreConfig.java
index ae50169652..d8443dd861 100644
--- a/test/org/apache/catalina/storeconfig/TestStoreConfig.java
+++ b/test/org/apache/catalina/storeconfig/TestStoreConfig.java
@@ -44,6 +44,7 @@ import org.apache.catalina.util.SessionIdGeneratorBase;
import org.apache.catalina.valves.AccessLogValve;
import org.apache.tomcat.util.net.SSLHostConfig;
import org.apache.tomcat.util.net.SSLHostConfigCertificate;
+import org.apache.tomcat.util.net.SSLHostConfigPreSharedKey;
import org.apache.tomcat.util.net.openssl.OpenSSLConf;
import org.apache.tomcat.util.net.openssl.OpenSSLConfCmd;
import org.xml.sax.InputSource;
@@ -95,6 +96,11 @@ public class TestStoreConfig extends TomcatBaseTest {
tc_SSLHostConfigCertificate_23.setCertificateKeystoreFile("conf/localhost-rsa.jks");
tc_SSLHostConfigCertificate_23.setCertificateKeystorePassword("mypassword");
tc_SSLHostConfig_22.addCertificate(tc_SSLHostConfigCertificate_23);
+ SSLHostConfigPreSharedKey preSharedKey = new
SSLHostConfigPreSharedKey(tc_SSLHostConfig_22);
+ preSharedKey.setIdentity("test");
+ preSharedKey.setKey("000102030405060708090a0b0c0d0e0f");
+ preSharedKey.setDigest("SHA256");
+ tc_SSLHostConfig_22.addPreSharedKey(preSharedKey);
tomcat.getConnector().addSslHostConfig(tc_SSLHostConfig_22);
org.apache.catalina.ha.tcp.SimpleTcpCluster tc_SimpleTcpCluster_51 =
new org.apache.catalina.ha.tcp.SimpleTcpCluster();
@@ -159,6 +165,8 @@ public class TestStoreConfig extends TomcatBaseTest {
Assert.assertTrue(serverXmlDump.contains("channelStartOptions"));
Assert.assertTrue(serverXmlDump.contains("SecretKeyCredentialHandler"));
Assert.assertTrue(serverXmlDump.contains("certificateKeystorePassword="));
+ Assert.assertTrue(serverXmlDump.contains(
+ "<PreSharedKey digest=\"SHA256\" identity=\"test\"
key=\"000102030405060708090a0b0c0d0e0f\""));
Assert.assertTrue(serverXmlDump.contains("+TLSv1.1"));
SAXParserFactory.newInstance().newSAXParser().getXMLReader().parse(new
InputSource(new StringReader(serverXmlDump)));
diff --git a/test/org/apache/tomcat/util/net/TestSSLHostConfig.java
b/test/org/apache/tomcat/util/net/TestSSLHostConfig.java
index a258fbc4d4..9a9941d9b7 100644
--- a/test/org/apache/tomcat/util/net/TestSSLHostConfig.java
+++ b/test/org/apache/tomcat/util/net/TestSSLHostConfig.java
@@ -29,6 +29,7 @@ import org.junit.Test;
import org.apache.tomcat.util.net.openssl.OpenSSLConf;
import org.apache.tomcat.util.net.openssl.OpenSSLConfCmd;
import org.apache.tomcat.util.net.openssl.ciphers.Cipher;
+import org.apache.tomcat.util.net.openssl.ciphers.MessageDigest;
public class TestSSLHostConfig {
@@ -191,6 +192,61 @@ public class TestSSLHostConfig {
}
+ @Test
+ public void testPreSharedKey() {
+ SSLHostConfig sslHostConfig = new SSLHostConfig();
+ SSLHostConfigPreSharedKey preSharedKey = new
SSLHostConfigPreSharedKey(sslHostConfig);
+ Assert.assertEquals("SHA256", preSharedKey.getDigest());
+ Assert.assertEquals(MessageDigest.SHA256,
preSharedKey.getDigestInternal());
+ preSharedKey.setIdentity("test");
+ preSharedKey.setKey("00010203");
+ preSharedKey.setDigest("SHA256");
+ sslHostConfig.addPreSharedKey(preSharedKey);
+
+ Assert.assertSame(sslHostConfig, preSharedKey.getSSLHostConfig());
+ Assert.assertEquals("test", preSharedKey.getIdentity());
+ Assert.assertEquals("00010203", preSharedKey.getKey());
+ Assert.assertArrayEquals(new byte[] { 0, 1, 2, 3 },
preSharedKey.getKeyInternal());
+ Assert.assertEquals("SHA256", preSharedKey.getDigest());
+ Assert.assertEquals(MessageDigest.SHA256,
preSharedKey.getDigestInternal());
+ Assert.assertSame(preSharedKey,
sslHostConfig.getPreSharedKeys().iterator().next());
+ }
+
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testPreSharedKeyInvalidKey() {
+ SSLHostConfigPreSharedKey preSharedKey = new
SSLHostConfigPreSharedKey(null);
+ preSharedKey.setKey("invalid");
+ }
+
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testPreSharedKeyInvalidDigest() {
+ SSLHostConfigPreSharedKey preSharedKey = new
SSLHostConfigPreSharedKey(null);
+ preSharedKey.setDigest("invalid");
+ }
+
+
+ @Test
+ public void testPreSharedKeyOnly() {
+ SSLHostConfig sslHostConfig = new SSLHostConfig();
+ SSLHostConfigPreSharedKey preSharedKey = new
SSLHostConfigPreSharedKey(sslHostConfig);
+ sslHostConfig.addPreSharedKey(preSharedKey);
+
+ Assert.assertTrue(sslHostConfig.isPreSharedKeyOnly());
+
+ // Creating the default certificate used to hold the SSLContext must
not change the result.
+ sslHostConfig.getCertificates(true);
+ Assert.assertTrue(sslHostConfig.isPreSharedKeyOnly());
+
+ SSLHostConfig withCertificate = new SSLHostConfig();
+ withCertificate.addPreSharedKey(new
SSLHostConfigPreSharedKey(withCertificate));
+ withCertificate.addCertificate(
+ new SSLHostConfigCertificate(withCertificate,
SSLHostConfigCertificate.Type.UNDEFINED));
+ Assert.assertFalse(withCertificate.isPreSharedKeyOnly());
+ }
+
+
@Test
public void testSerialization() throws IOException, ClassNotFoundException
{
// Dummy OpenSSL command name/value pair
@@ -205,6 +261,11 @@ public class TestSSLHostConfig {
openSSLConfCmd.setValue(value);
openSSLConf.addCmd(openSSLConfCmd);
sslHostConfig.setOpenSslConf(openSSLConf);
+ SSLHostConfigPreSharedKey preSharedKey = new
SSLHostConfigPreSharedKey(sslHostConfig);
+ preSharedKey.setIdentity("test");
+ preSharedKey.setKey("00010203");
+ preSharedKey.setDigest("SHA256");
+ sslHostConfig.addPreSharedKey(preSharedKey);
// Serialize
ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -223,5 +284,12 @@ public class TestSSLHostConfig {
OpenSSLConfCmd command = commands.get(0);
Assert.assertEquals(name, command.getName());
Assert.assertEquals(value, command.getValue());
+ SSLHostConfigPreSharedKey outputPreSharedKey =
output.getPreSharedKeys().iterator().next();
+ Assert.assertSame(output, outputPreSharedKey.getSSLHostConfig());
+ Assert.assertEquals("test", outputPreSharedKey.getIdentity());
+ Assert.assertEquals("00010203", outputPreSharedKey.getKey());
+ Assert.assertEquals("SHA256", outputPreSharedKey.getDigest());
+ Assert.assertArrayEquals(new byte[] { 0, 1, 2, 3 },
outputPreSharedKey.getKeyInternal());
+ Assert.assertEquals(MessageDigest.SHA256,
outputPreSharedKey.getDigestInternal());
}
}
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index afc7c04bf6..534b7dbce5 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -407,6 +407,10 @@
directly via the configured trust store) is not subject to OCSP
checking. (markt)
</fix>
+ <add>
+ Add support for configuring TLS pre-shared keys under an
+ <code>SSLHostConfig</code>. (markt)
+ </add>
</changelog>
</subsection>
<subsection name="Jasper">
diff --git a/webapps/docs/config/http.xml b/webapps/docs/config/http.xml
index acffcc5d7a..bd1b15b4f0 100644
--- a/webapps/docs/config/http.xml
+++ b/webapps/docs/config/http.xml
@@ -1327,8 +1327,10 @@
<strong>Connector</strong>.</p>
<p>Each <strong>SSLHostConfig</strong> must in turn define at least one
- <strong>Certificate</strong>. The types of the <strong>Certificate</strong>s
- must be unique.</p>
+ <strong>Certificate</strong> or <strong>PreSharedKey</strong>. A
configuration
+ with no <strong>Certificate</strong> requires the Tomcat Native OpenSSL
+ implementation. The types of the <strong>Certificate</strong>s must be
+ unique.</p>
<p>In addition to the standard TLS related request attributes defined in
section 3.10 of the Servlet specification, Tomcat supports a number of
@@ -1663,6 +1665,32 @@
</subsection>
+ <subsection name="SSL Support - PreSharedKey">
+
+ <p>A <strong>PreSharedKey</strong> element represents a pre-shared key that
+ may be used by an OpenSSL-based connector. It must be nested in an
+ <strong>SSLHostConfig</strong> element.</p>
+
+ <attributes>
+
+ <attribute name="identity" required="true">
+ <p>The identity associated with this pre-shared key.</p>
+ </attribute>
+
+ <attribute name="key" required="true">
+ <p>The pre-shared key encoded as hexadecimal characters.</p>
+ </attribute>
+
+ <attribute name="digest" required="false">
+ <p>The message digest algorithm associated with this pre-shared key for
+ TLS 1.3, for example <code>SHA256</code> or <code>SHA384</code>. If not
+ specified, the default is <code>SHA256</code>.</p>
+ </attribute>
+
+ </attributes>
+
+ </subsection>
+
<subsection name="SSL Support - Certificate">
<p></p>
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]