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]