Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
81dc1a1
Document Windows keystores and TLS certificate reloading
dsoumis Oct 22, 2026
a87c327
Select a key store certificate valid for TLS server authentication
dsoumis Oct 22, 2026
6ca553a
Select a server-auth certificate for a configured Windows-MY alias
dsoumis Oct 28, 2026
11adba4
Use the certificate actually served after Windows-MY alias resolution
dsoumis Oct 28, 2026
5789950
Fix typos
dsoumis Oct 29, 2026
0bef422
Scan only the contiguous run of Windows-MY numbered duplicates
dsoumis Oct 29, 2026
6ac4f04
Reuse SSLHostConfigCertificate.chooseServerAlias in the OpenSSL contexts
dsoumis Oct 29, 2026
2486144
Share served-alias resolution across all SSLContext engines
dsoumis Oct 29, 2026
29057c0
Ignore alias case when reporting a resolved certificate alias.
dsoumis Oct 29, 2026
00f5777
Simplify by removing redundant method.
dsoumis Oct 29, 2026
48d0fed
Resolve the served alias once when logging a certificate
dsoumis Oct 29, 2026
a70a422
Enhance docs
dsoumis Oct 29, 2026
bf6d7d5
Skip key entries without a server-auth X.509 certificate when auto-se…
dsoumis Oct 29, 2026
d2bd9ac
Replace runtime certificate generation with pre-generated fixtures
dsoumis Oct 29, 2026
430ead8
Skip an entry only when its cert is provably unusable. Absent/non-X.5…
dsoumis Oct 29, 2026
5a0b9eb
The wrapped SSLContext and its key manager are supplied pre-configure…
dsoumis Oct 29, 2026
2d3d882
Non-exportable Windows keys require JSSE
dsoumis Oct 29, 2026
ed549e1
Add TestSSLContextWrapper
dsoumis Oct 29, 2026
2584315
Catch RuntimeException in case of weird provider
dsoumis Oct 29, 2026
bb33136
Align behavior with Panama
dsoumis Oct 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions java/org/apache/catalina/manager/ManagerServlet.java
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@
import org.apache.tomcat.util.net.SSLContext;
import org.apache.tomcat.util.net.SSLHostConfig;
import org.apache.tomcat.util.net.SSLHostConfigCertificate;
import org.apache.tomcat.util.net.SSLUtilBase;
import org.apache.tomcat.util.res.StringManager;
import org.apache.tomcat.util.security.Escape;

Expand Down Expand Up @@ -1690,14 +1689,11 @@ protected Map<String,List<String>> getConnectorCerts(StringManager smClient) {
sslHostConfigCert.getType();
List<String> certList = new ArrayList<>();
SSLContext sslContext = sslHostConfigCert.getSslContext();
String alias = sslHostConfigCert.getCertificateKeyAlias();
if (alias == null) {
alias = SSLUtilBase.DEFAULT_KEY_ALIAS;
}
if (sslContext == null) {
certList.add(smClient.getString("managerServlet.certsNotLoaded"));
} else {
X509Certificate[] certs = sslContext.getCertificateChain(alias);
X509Certificate[] certs = sslContext.getCertificateChain(
sslContext.getServedAlias(sslHostConfigCert));
if (certs == null) {
certList.add(smClient.getString("managerServlet.certsNotAvailable"));
} else {
Expand Down
28 changes: 18 additions & 10 deletions java/org/apache/tomcat/util/net/AbstractEndpoint.java
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,7 @@ protected void createSSLContext(SSLHostConfig sslHostConfig) throws IllegalArgum
*/
protected void logCertificate(SSLHostConfigCertificate certificate) {
SSLHostConfig sslHostConfig = certificate.getSSLHostConfig();
SSLContext sslContext = certificate.getSslContext();

String certificateInfo;

Expand Down Expand Up @@ -647,17 +648,24 @@ protected void logCertificate(SSLHostConfigCertificate certificate) {
getLogCertificate().info(sm.getString("endpoint.tls.info", getName(), sslHostConfig.getHostName(),
certificate.getType(), certificateInfo, trustStoreSource));

SSLContext sslContext = certificate.getSslContext();
if (getLogCertificate().isDebugEnabled() && sslContext != null) {
String alias = certificate.getCertificateKeyAlias();
if (alias == null) {
alias = SSLUtilBase.DEFAULT_KEY_ALIAS;
if (sslContext != null) {
String servedAlias = sslContext.getServedAlias(certificate);

// When a configured alias was resolved to a different alias than is actually served (e.g. a SunMSCAPI
// Windows-MY numbered duplicate), report it so the alias logged above is not mistaken for the one in use.
String configuredAlias = certificate.getCertificateKeyAlias();
if (configuredAlias != null && servedAlias != null && !servedAlias.equalsIgnoreCase(configuredAlias)) {
getLogCertificate()
.info(sm.getString("endpoint.tls.info.cert.aliasResolved", configuredAlias, servedAlias));
}
X509Certificate[] x509Certificates = sslContext.getCertificateChain(alias);
if (x509Certificates != null && x509Certificates.length > 0) {
getLogCertificate().debug(generateCertificateDebug(x509Certificates[0]));
} else {
getLogCertificate().debug(sm.getString("endpoint.tls.cert.noCerts"));

if (getLogCertificate().isDebugEnabled()) {
X509Certificate[] x509Certificates = sslContext.getCertificateChain(servedAlias);
if (x509Certificates != null && x509Certificates.length > 0) {
getLogCertificate().debug(generateCertificateDebug(x509Certificates[0]));
} else {
getLogCertificate().debug(sm.getString("endpoint.tls.cert.noCerts"));
}
}
}
}
Expand Down
18 changes: 18 additions & 0 deletions java/org/apache/tomcat/util/net/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,24 @@ public class Constants {
*/
public static final String SSL_PROTO_SSLv2Hello = "SSLv2Hello";

/**
* EKU OIDs.
*/
public static final String SERVER_AUTH_EXTENDED_KEY_USAGE_OID = "1.3.6.1.5.5.7.3.1";
public static final String ANY_EXTENDED_KEY_USAGE_OID = "2.5.29.37.0";

/**
* SunMSCAPI provider name.
*/
public static final String SUNMSCAPI_PROVIDER_NAME = "SunMSCAPI";

/**
* Windows-MY key store types.
*/
public static final String WINDOWS_MY_KEYSTORE_TYPE = "WINDOWS-MY";
public static final String WINDOWS_MY_CURRENTUSER_KEYSTORE_TYPE = "WINDOWS-MY-CURRENTUSER";
public static final String WINDOWS_MY_LOCALMACHINE_KEYSTORE_TYPE = "WINDOWS-MY-LOCALMACHINE";

/**
* Private constructor to prevent instantiation.
*/
Expand Down
4 changes: 4 additions & 0 deletions java/org/apache/tomcat/util/net/LocalStrings.properties
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ endpoint.tls.cert.encodingError=Certificate fingerprints not available
endpoint.tls.cert.noCerts=Certificate details not available as the certificate chain returned from the SSLContext was empty
endpoint.tls.enabledGroups=Configured named groups {0} for the TLS connector
endpoint.tls.info=Connector [{0}], TLS virtual host [{1}], certificate type [{2}] configured from {3} with trust store [{4}]
endpoint.tls.info.cert.aliasResolved=The configured certificate alias [{0}] was resolved to alias [{1}] which is valid for TLS server authentication
endpoint.tls.info.cert.keystore=keystore [{0}] using alias [{1}]
endpoint.tls.info.cert.keystore.direct=Set directly - location unknown
endpoint.tls.info.cert.pem=key [{0}], certificate [{1}] and certificate chain [{2}]
Expand Down Expand Up @@ -178,9 +179,12 @@ sslUtilBase.keystore_load_failed=Failed to load keystore type [{0}] with path [{
sslUtilBase.noCertFile=SSLHostConfig attribute certificateFile must be defined when using an SSL connector
sslUtilBase.noCrlSupport=The truststoreProvider [{0}] does not support the certificateRevocationFile configuration option
sslUtilBase.noKeys=No aliases for private keys found in key store
sslUtilBase.noServerAuthKey=The key store has private key entries but none has a certificate that is valid for TLS server authentication. Enable debug logging to see the skipped aliases, use a certificate whose Extended Key Usage permits server authentication, or configure certificateKeyAlias explicitly.
sslUtilBase.noVerificationDepth=The truststoreProvider [{0}] does not support the certificateVerificationDepth configuration option
sslUtilBase.noneSupported=None of the [{0}] specified are supported by the SSL engine : [{1}]
sslUtilBase.selectedServerAuthCert=Selected the certificate with alias [{0}] for TLS server authentication as the configured alias [{1}] is not valid for that purpose and the key store contains more than one certificate under that name
sslUtilBase.skipped=Tomcat interprets the [{0}] attribute in a manner consistent with the latest OpenSSL development branch. Some of the specified [{0}] are not supported by the configured SSL engine for this connector (which may use JSSE or an older OpenSSL version) and have been skipped: [{1}]
sslUtilBase.skippedKeyUsage=Skipping certificate with alias [{0}] as it is not valid for TLS server authentication
sslUtilBase.ssl3=SSLv3 has been explicitly enabled. This protocol is known to be insecure.
sslUtilBase.tls13.auth=The JSSE TLS 1.3 implementation does not support post handshake authentication (PHA) and is therefore incompatible with optional certificate authentication
sslUtilBase.trustedCertNotChecked=The validity dates of the trusted certificate with alias [{0}] were not checked as the certificate was of an unknown type
Expand Down
42 changes: 42 additions & 0 deletions java/org/apache/tomcat/util/net/SSLContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSessionContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509KeyManager;

/**
* This interface is needed to override the default SSLContext class to allow SSL implementation pluggability without
Expand Down Expand Up @@ -93,10 +94,51 @@ default SSLEngine createSSLEngine(boolean clientMode) {
* Returns the certificate chain for the given alias.
*
* @param alias The alias
*
* @return The certificate chain
*/
X509Certificate[] getCertificateChain(String alias);


/**
* Returns the key managers that may serve the given certificate configuration, in preference order. Used by
* {@link #getServedAlias} to resolve the alias actually served. The default returns an empty array (no key manager
* known); implementations backed by one or more key managers override it to expose them.
*
* @param certificate The certificate configuration
*
* @return The key managers that may serve the certificate, possibly empty, never {@code null}
*/
default X509KeyManager[] getServerKeyManagers(SSLHostConfigCertificate certificate) {
return new X509KeyManager[0];
}


/**
* Returns the alias the key manager would select to serve for the given certificate configuration during a
* handshake. This can differ from {@link SSLHostConfigCertificate#getCertificateKeyAlias()}, for example after
* SunMSCAPI Windows-MY numbered-duplicate resolution. Each key manager from {@link #getServerKeyManagers} is asked
* in turn; if none selects an alias the configured alias is used (or {@link SSLUtilBase#DEFAULT_KEY_ALIAS} when
* none is configured).
*
* @param certificate The certificate configuration
*
* @return The served alias, never {@code null}
*/
default String getServedAlias(SSLHostConfigCertificate certificate) {
for (X509KeyManager keyManager : getServerKeyManagers(certificate)) {
if (keyManager != null) {
String alias = certificate.chooseServerAlias(keyManager);
if (alias != null) {
return alias;
}
}
}
String configuredAlias = certificate.getCertificateKeyAlias();
return configuredAlias == null ? SSLUtilBase.DEFAULT_KEY_ALIAS : configuredAlias;
}


/**
* Returns the accepted issuers.
*
Expand Down
16 changes: 16 additions & 0 deletions java/org/apache/tomcat/util/net/SSLContextWrapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,22 @@ public X509Certificate[] getCertificateChain(String alias) {
return keyManager.getCertificateChain(alias);
}


/**
* {@inheritDoc}
* <p>
* The wrapped {@code javax.net.ssl.SSLContext} and its key manager are supplied pre-configured, so Tomcat cannot
* reliably predict which alias a handshake will serve: the socket-based {@link X509KeyManager#chooseServerAlias}
* callback used elsewhere to probe the served alias may disagree with the {@code chooseEngineServerAlias} callback
* that an {@code SSLEngine} handshake actually uses. The alias the operator configured is therefore the only
* dependable answer for certificate monitoring.
*/
@Override
public String getServedAlias(SSLHostConfigCertificate certificate) {
String configuredAlias = certificate.getCertificateKeyAlias();
return configuredAlias == null ? SSLUtilBase.DEFAULT_KEY_ALIAS : configuredAlias;
}

@Override
public X509Certificate[] getAcceptedIssuers() {
return trustManager.getAcceptedIssuers();
Expand Down
7 changes: 2 additions & 5 deletions java/org/apache/tomcat/util/net/SSLHostConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -1587,11 +1587,8 @@ public Set<X509Certificate> certificatesExpiringBefore(Date date) {
for (SSLHostConfigCertificate sslHostConfigCertificate : sslHostConfigCertificates) {
SSLContext sslContext = sslHostConfigCertificate.getSslContext();
if (sslContext != null) {
String alias = sslHostConfigCertificate.getCertificateKeyAlias();
if (alias == null) {
alias = SSLUtilBase.DEFAULT_KEY_ALIAS;
}
X509Certificate[] certificates = sslContext.getCertificateChain(alias);
X509Certificate[] certificates = sslContext.getCertificateChain(
sslContext.getServedAlias(sslHostConfigCertificate));
if (certificates != null && certificates.length > 0) {
X509Certificate certificate = certificates[0];
Date expirationDate = certificate.getNotAfter();
Expand Down
25 changes: 25 additions & 0 deletions java/org/apache/tomcat/util/net/SSLHostConfigCertificate.java
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,31 @@ public Type getType() {
}


/**
* Returns the alias the given key manager would choose to serve for this certificate, or {@code null} if it has no
* suitable entry. When {@link #getType()} is {@link Type#UNDEFINED} every key type is tried. This reflects the
* alias actually used in a handshake, which can differ from {@link #getCertificateKeyAlias()} - for example after
* the SunMSCAPI Windows-MY numbered-duplicate resolution performed by SSLUtilBase#getKeyManagers().
*
* @param keyManager the key manager that will serve this certificate
*
* @return the served alias, or {@code null} if the key manager has no suitable entry
*/
public String chooseServerAlias(X509KeyManager keyManager) {
Type[] candidateTypes = Type.UNDEFINED.equals(type) ? Type.values() : new Type[] { type };
for (Type candidateType : candidateTypes) {
if (candidateType == Type.UNDEFINED) {
continue;
}
String alias = keyManager.chooseServerAlias(candidateType.getKeyType(), null, null);
if (alias != null) {
return alias;
}
}
return null;
}

Comment thread
dsoumis marked this conversation as resolved.
Resolved

/**
* Returns the password for the certificate's private key.
*
Expand Down
Loading
Loading