This is an automated email from the ASF dual-hosted git repository.

dsoumis pushed a commit to branch 11.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git

commit c2f69f5e13a3186d38106f48e6c964f957060aa5
Author: Dimitris Soumis <[email protected]>
AuthorDate: Wed Oct 28 15:23:07 2026 +0200

    Use the certificate actually served after Windows-MY alias resolution
    
    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   | 125 +++++++++++++++++++--
 .../TestSSLUtilBaseConfiguredAliasSelection.java   |  59 ++++++++++
 9 files changed, 268 insertions(+), 29 deletions(-)

diff --git a/java/org/apache/catalina/manager/ManagerServlet.java 
b/java/org/apache/catalina/manager/ManagerServlet.java
index 71cdb510cb..8d899467dc 100644
--- a/java/org/apache/catalina/manager/ManagerServlet.java
+++ b/java/org/apache/catalina/manager/ManagerServlet.java
@@ -75,7 +75,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;
 
@@ -1690,14 +1689,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 18037a6fef..0c8e93fa16 100644
--- a/java/org/apache/tomcat/util/net/AbstractEndpoint.java
+++ b/java/org/apache/tomcat/util/net/AbstractEndpoint.java
@@ -613,6 +613,7 @@ public abstract class AbstractEndpoint<S, U> {
      */
     protected void logCertificate(SSLHostConfigCertificate certificate) {
         SSLHostConfig sslHostConfig = certificate.getSSLHostConfig();
+        SSLContext sslContext = certificate.getSslContext();
 
         String certificateInfo;
 
@@ -647,13 +648,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 ca9335dc43..b6fee25844 100644
--- a/java/org/apache/tomcat/util/net/LocalStrings.properties
+++ b/java/org/apache/tomcat/util/net/LocalStrings.properties
@@ -132,6 +132,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 df0125ce86..e306cb8c62 100644
--- a/java/org/apache/tomcat/util/net/SSLHostConfig.java
+++ b/java/org/apache/tomcat/util/net/SSLHostConfig.java
@@ -1615,11 +1615,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 b7d67010bd..3e59bc541f 100644
--- a/java/org/apache/tomcat/util/net/SSLHostConfigCertificate.java
+++ b/java/org/apache/tomcat/util/net/SSLHostConfigCertificate.java
@@ -245,6 +245,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..9679da9a1a 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 x509KeyManager) {
+                    String alias = 
certificate.chooseServerAlias(x509KeyManager);
+                    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 8a9f9be7b6..c549e4603c 100644
--- 
a/test/org/apache/tomcat/util/net/SSLUtilBaseCertificateSelectionTestBase.java
+++ 
b/test/org/apache/tomcat/util/net/SSLUtilBaseCertificateSelectionTestBase.java
@@ -19,14 +19,18 @@ package org.apache.tomcat.util.net;
 
 import java.io.File;
 import java.io.Serial;
+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.Collection;
 import java.util.Collections;
+import java.util.Date;
 import java.util.List;
+import java.util.Set;
 import java.util.stream.Stream;
 
 import javax.net.ssl.X509KeyManager;
@@ -140,11 +144,68 @@ 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(List.of());
+        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(List.of());
+        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") {
-            @Serial
-            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);
     }
 
 
@@ -152,12 +213,20 @@ public abstract class 
SSLUtilBaseCertificateSelectionTestBase extends TomcatBase
         return orderedKeystore(new Provider("SUN", "1.0", "test") {
             @Serial
             private static final long serialVersionUID = 4434321038151233913L;
-        }, "JKS", null, entries);
+        }, "JKS", true, null, entries);
     }
 
 
-    protected static KeyStore orderedKeystore(Provider provider, String type, 
String malformedAlias,
-            KeystoreEntry... entries) throws Exception {
+    private static Provider sunMSCAPIProvider() {
+        return new Provider("SunMSCAPI", "1.0", "test") {
+            @Serial
+            private static final long serialVersionUID = -4141665633237722962L;
+        };
+    }
+
+
+    protected static KeyStore orderedKeystore(Provider provider, String type, 
boolean exportableKeys,
+            String malformedAlias, KeystoreEntry... entries) throws Exception {
         File file = TesterKeystoreGenerator.generateKeystore(List.of(entries));
         KeyStore delegate = KeyStore.getInstance(file, 
TesterSupport.JKS_PASS.toCharArray());
         List<String> aliases = 
Stream.of(entries).map(KeystoreEntry::alias).toList();
@@ -180,11 +249,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);
@@ -192,6 +264,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 privateKey)) {
+            return key;
+        }
+        return new PrivateKey() {
+            @Serial
+            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]

Reply via email to