On Thu, Nov 5, 2026 at 2:24 PM Rémy Maucherat via dev <[email protected]>
wrote:

> On Thu, Nov 5, 2026 at 1:10 PM dsoumis.apache.org via dev
> <[email protected]> wrote:
> >
> > This is an automated email from the ASF dual-hosted git repository.
> >
> > dsoumis pushed a commit to branch 9.0.x
> > in repository https://gitbox.apache.org/repos/asf/tomcat.git
> >
> > commit 24d49b8a81971ac56989d4a6d6283b56cd5c2517
> > Author: Dimitris Soumis <[email protected]>
> > AuthorDate: Wed Oct 28 15:23:07 2026 +0200
> >
> >     Use the certificate actually served after Windows-MY alias resolution
>
> At least when I am spamming, it's not about Windows stuff ...
>
> Rémy
>

I kept the commits separate rather than squashing them, so the individual
changes and their rationale remain clear in the history.
I think this is valuable, even if it generates some additional traffic on
the list.

Dimitris

>
> >     The configured certificateKeyAlias can differ from the certificate
> actually
> >     served once the SunMSCAPI Windows-MY numbered-duplicate resolution
> has selected
> >     a different alias. Consumers that looked the certificate up by the
> configured
> >     alias therefore reported or monitored the wrong certificate.
> > ---
> >  .../apache/catalina/manager/ManagerServlet.java    |   7 +-
> >  .../apache/tomcat/util/net/AbstractEndpoint.java   |  19 +++-
> >  .../apache/tomcat/util/net/LocalStrings.properties |   1 +
> >  java/org/apache/tomcat/util/net/SSLContext.java    |  38 ++++++-
> >  java/org/apache/tomcat/util/net/SSLHostConfig.java |   6 +-
> >  .../tomcat/util/net/SSLHostConfigCertificate.java  |  25 +++++
> >  .../tomcat/util/net/jsse/JSSESSLContext.java       |  17 +++
> >  .../SSLUtilBaseCertificateSelectionTestBase.java   | 123
> +++++++++++++++++++--
> >  .../TestSSLUtilBaseConfiguredAliasSelection.java   |  59 ++++++++++
> >  9 files changed, 267 insertions(+), 28 deletions(-)
> >
> > diff --git a/java/org/apache/catalina/manager/ManagerServlet.java
> b/java/org/apache/catalina/manager/ManagerServlet.java
> > index 4300098f56..cde65b296a 100644
> > --- a/java/org/apache/catalina/manager/ManagerServlet.java
> > +++ b/java/org/apache/catalina/manager/ManagerServlet.java
> > @@ -73,7 +73,6 @@ import org.apache.tomcat.util.modeler.Registry;
> >  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;
> >
> > @@ -1746,14 +1745,10 @@ public class ManagerServlet extends HttpServlet
> implements ContainerServlet {
> >                                  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(sslHostConfigCert);
> >                              if (certs == null) {
> >
> certList.add(smClient.getString("managerServlet.certsNotAvailable"));
> >                              } else {
> > diff --git a/java/org/apache/tomcat/util/net/AbstractEndpoint.java
> b/java/org/apache/tomcat/util/net/AbstractEndpoint.java
> > index a8627e8eec..dea7c8b9a5 100644
> > --- a/java/org/apache/tomcat/util/net/AbstractEndpoint.java
> > +++ b/java/org/apache/tomcat/util/net/AbstractEndpoint.java
> > @@ -517,6 +517,7 @@ public abstract class AbstractEndpoint<S, U> {
> >       */
> >      protected void logCertificate(SSLHostConfigCertificate certificate)
> {
> >          SSLHostConfig sslHostConfig = certificate.getSSLHostConfig();
> > +        SSLContext sslContext = certificate.getSslContext();
> >
> >          String certificateInfo;
> >
> > @@ -551,13 +552,19 @@ public abstract class AbstractEndpoint<S, U> {
> >          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;
> > +        // When a configured alias was resolved to a different alias
> that 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 (sslContext != null && configuredAlias != null) {
> > +            String servedAlias = sslContext.getServedAlias(certificate);
> > +            if (servedAlias != null &&
> !servedAlias.equals(configuredAlias)) {
> > +                getLogCertificate()
> > +
> .info(sm.getString("endpoint.tls.info.cert.aliasResolved", configuredAlias,
> servedAlias));
> >              }
> > -            X509Certificate[] x509Certificates =
> sslContext.getCertificateChain(alias);
> > +        }
> > +
> > +        if (getLogCertificate().isDebugEnabled() && sslContext != null)
> {
> > +            X509Certificate[] x509Certificates =
> sslContext.getCertificateChain(certificate);
> >              if (x509Certificates != null && x509Certificates.length >
> 0) {
> >
> getLogCertificate().debug(generateCertificateDebug(x509Certificates[0]));
> >              } else {
> > diff --git a/java/org/apache/tomcat/util/net/LocalStrings.properties
> b/java/org/apache/tomcat/util/net/LocalStrings.properties
> > index 5821a88c6d..591ac94273 100644
> > --- a/java/org/apache/tomcat/util/net/LocalStrings.properties
> > +++ b/java/org/apache/tomcat/util/net/LocalStrings.properties
> > @@ -150,6 +150,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}]
> > diff --git a/java/org/apache/tomcat/util/net/SSLContext.java
> b/java/org/apache/tomcat/util/net/SSLContext.java
> > index f51287aa80..25adf54622 100644
> > --- a/java/org/apache/tomcat/util/net/SSLContext.java
> > +++ b/java/org/apache/tomcat/util/net/SSLContext.java
> > @@ -90,13 +90,49 @@ public interface SSLContext {
> >      SSLParameters getSupportedSSLParameters();
> >
> >      /**
> > -     * Returns the certificate chain for the given alias.
> > +     * Returns the certificate chain for the given key store alias.
> This is a low-level lookup by exact alias; callers
> > +     * that want the certificate actually served for a configured
> {@link SSLHostConfigCertificate} should use
> > +     * {@link #getCertificateChain(SSLHostConfigCertificate)}, which
> resolves the served alias (it can differ from the
> > +     * configured alias, for example after SunMSCAPI Windows-MY
> numbered-duplicate resolution).
> >       *
> >       * @param alias The alias
> > +     *
> >       * @return The certificate chain
> >       */
> >      X509Certificate[] getCertificateChain(String alias);
> >
> > +
> > +    /**
> > +     * 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. The default
> returns the configured alias (or
> > +     * {@link SSLUtilBase#DEFAULT_KEY_ALIAS} when none is configured);
> implementations backed by a key manager override
> > +     * it to resolve the alias actually served.
> > +     *
> > +     * @param certificate The certificate configuration
> > +     *
> > +     * @return The served alias, never {@code null}
> > +     */
> > +    default String getServedAlias(SSLHostConfigCertificate certificate)
> {
> > +        String alias = certificate.getCertificateKeyAlias();
> > +        return alias == null ? SSLUtilBase.DEFAULT_KEY_ALIAS : alias;
> > +    }
> > +
> > +
> > +    /**
> > +     * Returns the certificate chain actually served for the given
> certificate configuration. Unlike
> > +     * {@link #getCertificateChain(String)}, which looks up a fixed
> alias, this uses {@link #getServedAlias}, so callers
> > +     * such as the certificate reload check observe the certificate
> that is really in use. This can differ from the
> > +     * configured alias, for example after SunMSCAPI Windows-MY
> duplicate resolution.
> > +     *
> > +     * @param certificate The certificate configuration
> > +     *
> > +     * @return The served certificate chain, or {@code null} if none
> can be determined
> > +     */
> > +    default X509Certificate[]
> getCertificateChain(SSLHostConfigCertificate certificate) {
> > +        return getCertificateChain(getServedAlias(certificate));
> > +    }
> > +
> >      /**
> >       * Returns the accepted issuers.
> >       *
> > diff --git a/java/org/apache/tomcat/util/net/SSLHostConfig.java
> b/java/org/apache/tomcat/util/net/SSLHostConfig.java
> > index 2b16861c07..137393210d 100644
> > --- a/java/org/apache/tomcat/util/net/SSLHostConfig.java
> > +++ b/java/org/apache/tomcat/util/net/SSLHostConfig.java
> > @@ -1873,11 +1873,7 @@ public class SSLHostConfig implements
> Serializable {
> >          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(sslHostConfigCertificate);
> >                  if (certificates != null && certificates.length > 0) {
> >                      X509Certificate certificate = certificates[0];
> >                      Date expirationDate = certificate.getNotAfter();
> > diff --git
> a/java/org/apache/tomcat/util/net/SSLHostConfigCertificate.java
> b/java/org/apache/tomcat/util/net/SSLHostConfigCertificate.java
> > index 395a1c6dcc..b413f90e13 100644
> > --- a/java/org/apache/tomcat/util/net/SSLHostConfigCertificate.java
> > +++ b/java/org/apache/tomcat/util/net/SSLHostConfigCertificate.java
> > @@ -243,6 +243,31 @@ public class SSLHostConfigCertificate implements
> Serializable {
> >      }
> >
> >
> > +    /**
> > +     * 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;
> > +    }
> > +
> > +
> >      /**
> >       * Returns the password for the certificate's private key.
> >       *
> > diff --git a/java/org/apache/tomcat/util/net/jsse/JSSESSLContext.java
> b/java/org/apache/tomcat/util/net/jsse/JSSESSLContext.java
> > index 088257110b..eada0c2381 100644
> > --- a/java/org/apache/tomcat/util/net/jsse/JSSESSLContext.java
> > +++ b/java/org/apache/tomcat/util/net/jsse/JSSESSLContext.java
> > @@ -34,6 +34,7 @@ import javax.net.ssl.X509KeyManager;
> >  import javax.net.ssl.X509TrustManager;
> >
> >  import org.apache.tomcat.util.net.SSLContext;
> > +import org.apache.tomcat.util.net.SSLHostConfigCertificate;
> >
> >  class JSSESSLContext implements SSLContext {
> >
> > @@ -89,6 +90,22 @@ class JSSESSLContext implements SSLContext {
> >          return result;
> >      }
> >
> > +
> > +    @Override
> > +    public String getServedAlias(SSLHostConfigCertificate certificate) {
> > +        if (kms != null) {
> > +            for (KeyManager km : kms) {
> > +                if (km instanceof X509KeyManager) {
> > +                    String alias =
> certificate.chooseServerAlias((X509KeyManager) km);
> > +                    if (alias != null) {
> > +                        return alias;
> > +                    }
> > +                }
> > +            }
> > +        }
> > +        return SSLContext.super.getServedAlias(certificate);
> > +    }
> > +
> >      @Override
> >      public X509Certificate[] getAcceptedIssuers() {
> >          Set<X509Certificate> certs = new HashSet<>();
> > diff --git
> a/test/org/apache/tomcat/util/net/SSLUtilBaseCertificateSelectionTestBase.java
> b/test/org/apache/tomcat/util/net/SSLUtilBaseCertificateSelectionTestBase.java
> > index 5e28cbbc8a..83ceeb0827 100644
> > ---
> a/test/org/apache/tomcat/util/net/SSLUtilBaseCertificateSelectionTestBase.java
> > +++
> b/test/org/apache/tomcat/util/net/SSLUtilBaseCertificateSelectionTestBase.java
> > @@ -20,15 +20,19 @@ package org.apache.tomcat.util.net;
> >  import java.io.File;
> >  import java.io.FileInputStream;
> >  import java.io.InputStream;
> > +import java.security.Key;
> >  import java.security.KeyStore;
> >  import java.security.KeyStoreSpi;
> > +import java.security.PrivateKey;
> >  import java.security.Provider;
> >  import java.security.cert.CertificateParsingException;
> >  import java.security.cert.X509Certificate;
> >  import java.util.Arrays;
> >  import java.util.Collection;
> >  import java.util.Collections;
> > +import java.util.Date;
> >  import java.util.List;
> > +import java.util.Set;
> >  import java.util.stream.Collectors;
> >  import java.util.stream.Stream;
> >
> > @@ -143,22 +147,87 @@ public abstract class
> SSLUtilBaseCertificateSelectionTestBase extends TomcatBase
> >      }
> >
> >
> > +    /*
> > +     * Builds and registers an SSLContext for the given configured
> alias and key store, then returns the certificates
> > +     * that SSLHostConfig.certificatesExpiringBefore() - the input to
> the TLS certificate reload listener - would flag.
> > +     * The cut-off date is far enough in the future that every
> certificate qualifies, so the result is exactly the set
> > +     * of certificates the listener monitors for reload.
> > +     */
> > +    protected Set<X509Certificate>
> certificatesMonitoredForReload(String alias, KeyStore keystore) throws
> Exception {
> > +        SSLHostConfigCertificate certificate =
> registeredCertificate(alias, keystore);
> > +        SSLContext context =
> sslUtil(certificate).createSSLContext(Collections.emptyList());
> > +        try {
> > +            certificate.setSslContext(context);
> > +            Date farFuture = new Date(System.currentTimeMillis() +
> 3650L * 24 * 60 * 60 * 1000);
> > +            return
> certificate.getSSLHostConfig().certificatesExpiringBefore(farFuture);
> > +        } finally {
> > +            context.destroy();
> > +        }
> > +    }
> > +
> > +
> > +    /*
> > +     * Builds and registers an SSLContext for the given configured
> alias and key store, then returns the alias the
> > +     * context reports as actually served - which is what the
> certificate logging and reload paths resolve.
> > +     */
> > +    protected String servedAlias(String alias, KeyStore keystore)
> throws Exception {
> > +        SSLHostConfigCertificate certificate =
> registeredCertificate(alias, keystore);
> > +        SSLContext context =
> sslUtil(certificate).createSSLContext(Collections.emptyList());
> > +        try {
> > +            certificate.setSslContext(context);
> > +            return context.getServedAlias(certificate);
> > +        } finally {
> > +            context.destroy();
> > +        }
> > +    }
> > +
> > +
> > +    private static SSLHostConfigCertificate
> registeredCertificate(String alias, KeyStore keystore) {
> > +        SSLHostConfig hostConfig = new SSLHostConfig();
> > +        SSLHostConfigCertificate certificate = new
> SSLHostConfigCertificate(hostConfig, Type.UNDEFINED);
> > +        certificate.setCertificateKeystore(keystore);
> > +        certificate.setCertificateKeystoreType("JKS");
> > +
> certificate.setCertificateKeystorePassword(TesterSupport.JKS_PASS);
> > +        if (alias != null) {
> > +            certificate.setCertificateKeyAlias(alias);
> > +        }
> > +        hostConfig.addCertificate(certificate);
> > +        return certificate;
> > +    }
> > +
> > +
> >      protected static KeyStore orderedKeystore(String malformedAlias,
> KeystoreEntry... entries) throws Exception {
> > -        return orderedKeystore(new Provider("SunMSCAPI", 1.0, "test") {
> > -            private static final long serialVersionUID =
> -4141665633237722962L;
> > -        }, "Windows-MY", malformedAlias, entries);
> > +        return orderedKeystore(sunMSCAPIProvider(), "Windows-MY", true,
> malformedAlias, entries);
> > +    }
> > +
> > +
> > +    /*
> > +     * As orderedKeystore(String, KeystoreEntry...) but the private
> keys report a null format, like a non-exportable
> > +     * SunMSCAPI key. This routes SSLUtilBase.getKeyManagers() through
> its production path that keeps the original key
> > +     * store (ksUsed == ks) and wraps each key manager, rather than the
> in-memory-copy branch the exportable JKS-backed
> > +     * mock otherwise takes.
> > +     */
> > +    protected static KeyStore nonExportableKeystore(KeystoreEntry...
> entries) throws Exception {
> > +        return orderedKeystore(sunMSCAPIProvider(), "Windows-MY",
> false, null, entries);
> >      }
> >
> >
> >      protected static KeyStore nonSunMSCAPIKeystore(KeystoreEntry...
> entries) throws Exception {
> >          return orderedKeystore(new Provider("SUN", 1.0, "test") {
> >              private static final long serialVersionUID =
> 4434321038151233913L;
> > -        }, "JKS", null, entries);
> > +        }, "JKS", true, null, entries);
> > +    }
> > +
> > +
> > +    private static Provider sunMSCAPIProvider() {
> > +        return new Provider("SunMSCAPI", 1.0, "test") {
> > +            private static final long serialVersionUID =
> -4141665633237722962L;
> > +        };
> >      }
> >
> >
> > -    protected static KeyStore orderedKeystore(Provider provider, String
> type, String malformedAlias,
> > -            KeystoreEntry... entries) throws Exception {
> > +    protected static KeyStore orderedKeystore(Provider provider, String
> type, boolean exportableKeys,
> > +            String malformedAlias, KeystoreEntry... entries) throws
> Exception {
> >          File file =
> TesterKeystoreGenerator.generateKeystore(Arrays.asList(entries));
> >          KeyStore delegate = KeyStore.getInstance("JKS");
> >          try (InputStream is = new FileInputStream(file)) {
> > @@ -184,11 +253,14 @@ public abstract class
> SSLUtilBaseCertificateSelectionTestBase extends TomcatBase
> >          });
> >
> EasyMock.expect(spi.engineGetCertificateChain(EasyMock.anyString()))
> >                  .andStubAnswer(() ->
> delegate.getCertificateChain(EasyMock.getCurrentArgument(0)));
> > -        EasyMock.expect(spi.engineGetKey(EasyMock.anyString(),
> EasyMock.anyObject(char[].class)))
> > -                .andStubAnswer(() ->
> delegate.getKey(EasyMock.getCurrentArgument(0),
> EasyMock.getCurrentArgument(1)));
> > +        EasyMock.expect(spi.engineGetKey(EasyMock.anyString(),
> EasyMock.anyObject(char[].class))).andStubAnswer(() -> {
> > +            Key key = delegate.getKey(EasyMock.getCurrentArgument(0),
> EasyMock.getCurrentArgument(1));
> > +            return exportableKeys ? key : nonExportableView(key);
> > +        });
> >          EasyMock.replay(spi);
> > -        // A real JKS backs the mock, so keys report PKCS#8 and
> getKeyManagers() takes its in-memory-copy branch
> > -        // rather than SunMSCAPI's non-exportable-key path; the
> provider name and store type are overridden only so
> > +        // A real JKS backs the mock. With exportableKeys the keys
> report PKCS#8, so getKeyManagers() takes its
> > +        // in-memory-copy branch; with exportableKeys == false they
> report a null format (see nonExportableView), so it
> > +        // takes SunMSCAPI's non-exportable-key path instead. The
> provider name and store type are overridden only so
> >          // isSunMSCAPIWindowsMY() can be exercised off Windows.
> >          KeyStore result = new KeyStore(spi, provider, type) { };
> >          result.load(null, null);
> > @@ -196,6 +268,37 @@ public abstract class
> SSLUtilBaseCertificateSelectionTestBase extends TomcatBase
> >      }
> >
> >
> > +    /*
> > +     * A view of a private key that reports a null format, as a
> non-exportable SunMSCAPI key does, while staying usable
> > +     * by the key manager. getEncoded() returns null for the same
> reason; these selection tests never sign, so the key
> > +     * material itself is never needed.
> > +     */
> > +    private static Key nonExportableView(Key key) {
> > +        if (!(key instanceof PrivateKey)) {
> > +            return key;
> > +        }
> > +        final PrivateKey privateKey = (PrivateKey) key;
> > +        return new PrivateKey() {
> > +            private static final long serialVersionUID = 1L;
> > +
> > +            @Override
> > +            public String getAlgorithm() {
> > +                return privateKey.getAlgorithm();
> > +            }
> > +
> > +            @Override
> > +            public String getFormat() {
> > +                return null;
> > +            }
> > +
> > +            @Override
> > +            public byte[] getEncoded() {
> > +                return null;
> > +            }
> > +        };
> > +    }
> > +
> > +
> >      protected static void assertEku(X509Certificate certificate, String
> oid) throws Exception {
> >          List<String> extendedKeyUsage =
> certificate.getExtendedKeyUsage();
> >          Assert.assertNotNull("Presented certificate has no Extended Key
> Usage", extendedKeyUsage);
> > diff --git
> a/test/org/apache/tomcat/util/net/TestSSLUtilBaseConfiguredAliasSelection.java
> b/test/org/apache/tomcat/util/net/TestSSLUtilBaseConfiguredAliasSelection.java
> > index 911180f643..05189fc0ef 100644
> > ---
> a/test/org/apache/tomcat/util/net/TestSSLUtilBaseConfiguredAliasSelection.java
> > +++
> b/test/org/apache/tomcat/util/net/TestSSLUtilBaseConfiguredAliasSelection.java
> > @@ -20,8 +20,10 @@ package org.apache.tomcat.util.net;
> >  import java.io.IOException;
> >  import java.security.KeyStore;
> >  import java.security.cert.X509Certificate;
> > +import java.util.Set;
> >
> >  import org.junit.Assert;
> > +import org.junit.Assume;
> >  import org.junit.Test;
> >  import org.junit.runner.RunWith;
> >  import org.junit.runners.Parameterized;
> > @@ -160,6 +162,27 @@ public class
> TestSSLUtilBaseConfiguredAliasSelection extends SSLUtilBaseCertific
> >      }
> >
> >
> > +    /*
> > +     * After the SunMSCAPI numbered-duplicate swap, the certificate
> served to clients is a numbered duplicate of the
> > +     * configured alias. The TLS certificate reload listener (via
> SSLHostConfig.certificatesExpiringBefore()) must
> > +     * monitor that served certificate, not the certificate at the
> configured alias. Non-exportable keys route
> > +     * getKeyManagers() through the production ksUsed == ks path a real
> SunMSCAPI store uses. Scoped to JSSE: for
> > +     * OpenSSL the served certificate is held in the native layer and
> resolved by a separate code path.
> > +     */
> > +    @Test
> > +    public void testReloadListenerMonitorsTheServedCertificate() throws
> Exception {
> > +        Assume.assumeTrue("JSSE".equals(connectorName));
> > +        KeyStore keystore = nonExportableKeystore(key(HOST,
> CLIENT_AUTH), key(HOST + " (1)", SERVER_AUTH));
> > +
> > +        Set<X509Certificate> monitored =
> certificatesMonitoredForReload(HOST, keystore);
> > +
> > +        X509Certificate served = (X509Certificate)
> keystore.getCertificateChain(HOST + " (1)")[0];
> > +        X509Certificate notServed = (X509Certificate)
> keystore.getCertificateChain(HOST)[0];
> > +        Assert.assertTrue("The served certificate must be monitored for
> reload", monitored.contains(served));
> > +        Assert.assertFalse("The unserved configured certificate must
> not be monitored", monitored.contains(notServed));
> > +    }
> > +
> > +
> >      /*
> >       * In a SunMSCAPI Windows-MY store a certificate without a private
> key can take the bare friendly name while the
> >       * key-bearing certificate of the same name is exposed as a
> numbered duplicate. The configured alias then names a
> > @@ -187,4 +210,40 @@ public class
> TestSSLUtilBaseConfiguredAliasSelection extends SSLUtilBaseCertific
> >                  key("tomcat (2)", SERVER_AUTH));
> >          assertEku(presented,
> Constants.SERVER_AUTH_EXTENDED_KEY_USAGE_OID);
> >      }
> > +
> > +
> > +    /*
> > +     * For an OpenSSL connector the served certificate is loaded into
> the native layer, but the reload check must still
> > +     * report it. With exportable keys getKeyManagers() copies only the
> resolved key into an in-memory store, so
> > +     * OpenSSLContext.getCertificateChain() finds nothing under the
> configured alias and already falls back to the served
> > +     * alias - no OpenSSL-specific change is needed. (Non-exportable
> SunMSCAPI keys cannot be loaded by OpenSSL at all,
> > +     * so this exportable case is the only one that runs.)
> > +     */
> > +    @Test
> > +    public void testReloadResolvesServedCertificateWithOpenSSL() throws
> Exception {
> > +        Assume.assumeTrue(useOpenSSL);
> > +        KeyStore keystore = orderedKeystore(null, key(HOST,
> CLIENT_AUTH), key(HOST + " (1)", SERVER_AUTH));
> > +
> > +        Set<X509Certificate> monitored =
> certificatesMonitoredForReload(HOST, keystore);
> > +
> > +        X509Certificate served = (X509Certificate)
> keystore.getCertificateChain(HOST + " (1)")[0];
> > +        X509Certificate notServed = (X509Certificate)
> keystore.getCertificateChain(HOST)[0];
> > +        Assert.assertTrue("OpenSSL reload must resolve the served
> certificate", monitored.contains(served));
> > +        Assert.assertFalse("OpenSSL reload must not report the unserved
> configured certificate",
> > +                monitored.contains(notServed));
> > +    }
> > +
> > +
> > +    /*
> > +     * After the SunMSCAPI numbered-duplicate swap the served alias is
> the numbered duplicate, not the configured alias.
> > +     * This is the alias that logCertificate reports and the reload
> check resolves. Non-exportable keys route
> > +     * getKeyManagers() through the production ksUsed == ks path.
> Scoped to JSSE: for OpenSSL the served alias is
> > +     * resolved from the native layer by a separate path.
> > +     */
> > +    @Test
> > +    public void testServedAliasIsTheNumberedDuplicate() throws
> Exception {
> > +        Assume.assumeTrue("JSSE".equals(connectorName));
> > +        KeyStore keystore = nonExportableKeystore(key(HOST,
> CLIENT_AUTH), key(HOST + " (1)", SERVER_AUTH));
> > +        Assert.assertEquals(HOST + " (1)", servedAlias(HOST, keystore));
> > +    }
> >  }
> >
> >
> > ---------------------------------------------------------------------
> > To unsubscribe, e-mail: [email protected]
> > For additional commands, e-mail: [email protected]
> >
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: [email protected]
> For additional commands, e-mail: [email protected]
>
>

Reply via email to