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

>     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