From 4607f8bc2fb20da1681fede9094fe0af25ef0917 Mon Sep 17 00:00:00 2001 From: Madhura Bhave Date: Fri, 10 Mar 2017 12:09:49 -0800 Subject: [PATCH] Determine key from configured alias for undertow Closes gh-8245 --- ...dertowEmbeddedServletContainerFactory.java | 69 +++++++++++++++++- ...tEmbeddedServletContainerFactoryTests.java | 52 +++++++++++++ spring-boot/src/test/resources/test.jks | Bin 3173 -> 4464 bytes 3 files changed, 120 insertions(+), 1 deletion(-) diff --git a/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainerFactory.java b/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainerFactory.java index 39e3e53fe63..16d0822aa1f 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainerFactory.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainerFactory.java @@ -19,12 +19,16 @@ package org.springframework.boot.context.embedded.undertow; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; +import java.net.Socket; import java.net.URL; import java.net.URLConnection; import java.nio.charset.Charset; import java.security.KeyManagementException; import java.security.KeyStore; import java.security.NoSuchAlgorithmException; +import java.security.Principal; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -37,8 +41,10 @@ import java.util.Set; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509ExtendedKeyManager; import javax.servlet.ServletContainerInitializer; import javax.servlet.ServletContext; import javax.servlet.ServletException; @@ -311,13 +317,23 @@ public class UndertowEmbeddedServletContainerFactory keyPassword = ssl.getKeyStorePassword().toCharArray(); } keyManagerFactory.init(keyStore, keyPassword); - return keyManagerFactory.getKeyManagers(); + return getConfigurableAliasKeyManagers(ssl, keyManagerFactory.getKeyManagers()); } catch (Exception ex) { throw new IllegalStateException(ex); } } + private KeyManager[] getConfigurableAliasKeyManagers(Ssl ssl, KeyManager[] keyManagers) { + for (int i = 0; i < keyManagers.length; i++) { + if (keyManagers[i] instanceof X509ExtendedKeyManager) { + keyManagers[i] = new ConfigurableAliasKeyManager((X509ExtendedKeyManager) keyManagers[i], + ssl.getKeyAlias()); + } + } + return keyManagers; + } + private KeyStore getKeyStore() throws Exception { if (getSslStoreProvider() != null) { return getSslStoreProvider().getKeyStore(); @@ -693,6 +709,57 @@ public class UndertowEmbeddedServletContainerFactory initializer.onStartup(servletContext); } } + } + + private static class ConfigurableAliasKeyManager extends X509ExtendedKeyManager { + + private final X509ExtendedKeyManager sourceKeyManager; + + private final String alias; + + ConfigurableAliasKeyManager(X509ExtendedKeyManager keyManager, String alias) { + this.sourceKeyManager = keyManager; + this.alias = alias; + } + + @Override + public String chooseEngineClientAlias(String[] strings, Principal[] principals, SSLEngine sslEngine) { + return this.sourceKeyManager.chooseEngineClientAlias(strings, principals, sslEngine); + } + + @Override + public String chooseEngineServerAlias(String s, Principal[] principals, SSLEngine sslEngine) { + if (this.alias == null) { + return this.sourceKeyManager.chooseEngineServerAlias(s, principals, sslEngine); + } + return this.alias; + } + + public String chooseClientAlias(String[] keyType, Principal[] issuers, + Socket socket) { + return this.sourceKeyManager.chooseClientAlias(keyType, issuers, socket); + } + + public String chooseServerAlias(String keyType, Principal[] issuers, + Socket socket) { + return this.sourceKeyManager.chooseServerAlias(keyType, issuers, socket); + } + + public X509Certificate[] getCertificateChain(String alias) { + return this.sourceKeyManager.getCertificateChain(alias); + } + + public String[] getClientAliases(String keyType, Principal[] issuers) { + return this.sourceKeyManager.getClientAliases(keyType, issuers); + } + + public PrivateKey getPrivateKey(String alias) { + return this.sourceKeyManager.getPrivateKey(alias); + } + + public String[] getServerAliases(String keyType, Principal[] issuers) { + return this.sourceKeyManager.getServerAliases(keyType, issuers); + } } diff --git a/spring-boot/src/test/java/org/springframework/boot/context/embedded/AbstractEmbeddedServletContainerFactoryTests.java b/spring-boot/src/test/java/org/springframework/boot/context/embedded/AbstractEmbeddedServletContainerFactoryTests.java index 14bce615933..4b21689073c 100644 --- a/spring-boot/src/test/java/org/springframework/boot/context/embedded/AbstractEmbeddedServletContainerFactoryTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/context/embedded/AbstractEmbeddedServletContainerFactoryTests.java @@ -34,6 +34,7 @@ import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -441,6 +442,24 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests { .contains("scheme=https"); } + @Test + public void sslKeyAlias() throws Exception { + AbstractEmbeddedServletContainerFactory factory = getFactory(); + factory.setSsl(getSsl(null, "password", "test-alias", "src/test/resources/test.jks")); + this.container = factory.getEmbeddedServletContainer( + new ServletRegistrationBean(new ExampleServlet(true, false), "/hello")); + this.container.start(); + SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory( + new SSLContextBuilder() + .loadTrustMaterial(null, new SerialNumberValidatingTrustSelfSignedStrategy("77e7c302")).build()); + HttpClient httpClient = HttpClients.custom().setSSLSocketFactory(socketFactory) + .build(); + HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory( + httpClient); + assertThat(getResponse(getLocalUrl("https", "/hello"), requestFactory)) + .contains("scheme=https"); + } + @Test public void serverHeaderIsDisabledByDefaultWhenUsingSsl() throws Exception { AbstractEmbeddedServletContainerFactory factory = getFactory(); @@ -654,13 +673,25 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests { return getSsl(clientAuth, keyPassword, keyStore, null, null, null); } + private Ssl getSsl(ClientAuth clientAuth, String keyPassword, String keyAlias, String keyStore) { + return getSsl(clientAuth, keyPassword, keyAlias, keyStore, null, null, null); + } + private Ssl getSsl(ClientAuth clientAuth, String keyPassword, String keyStore, String trustStore, String[] supportedProtocols, String[] ciphers) { + return getSsl(clientAuth, keyPassword, null, keyStore, trustStore, supportedProtocols, ciphers); + } + + private Ssl getSsl(ClientAuth clientAuth, String keyPassword, String keyAlias, String keyStore, + String trustStore, String[] supportedProtocols, String[] ciphers) { Ssl ssl = new Ssl(); ssl.setClientAuth(clientAuth); if (keyPassword != null) { ssl.setKeyPassword(keyPassword); } + if (keyAlias != null) { + ssl.setKeyAlias(keyAlias); + } if (keyStore != null) { ssl.setKeyStore(keyStore); ssl.setKeyStorePassword("secret"); @@ -1247,4 +1278,25 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests { } + /** + * {@link TrustSelfSignedStrategy} that also validates certificate serial + * number. + */ + private static final class SerialNumberValidatingTrustSelfSignedStrategy extends TrustSelfSignedStrategy { + + private final String serialNumber; + + private SerialNumberValidatingTrustSelfSignedStrategy(String serialNumber) { + this.serialNumber = serialNumber; + } + + @Override + public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException { + String hexSerialNumber = chain[0].getSerialNumber().toString(16); + boolean isMatch = hexSerialNumber.equals(this.serialNumber); + return super.isTrusted(chain, authType) && isMatch; + } + + } + } diff --git a/spring-boot/src/test/resources/test.jks b/spring-boot/src/test/resources/test.jks index b10103d0d9dbe5505c895bad6dff9b2d8b52fa4b..1bce90bba66f697b2051dc839377de1f016a563d 100644 GIT binary patch delta 1307 zcmaDV@j;2_-`jt085kItfS7qB&uWf(Mh32u)Z!A|#GK5;Vg?4rsGWP|76a*igC@pq z13oq`Z8k<0MlB{mMn+Z!mL|sX%SscjY;{eXz9D7)Z8;^U#%gWe_&p!>i`9C6PE+OF zA+FG0d&c>b#$S(bsXEtp?3Rnzouh2O&(=g%iQy5iYGse{++6nAQ)Y7U{ahDa_Ot$0 z^U3M?Z`gBh`#yE^(cEx+g1TGzyw#FFW(ujNw3+Ak*gSqIvfpf)u-mjxt2TN}VO+m- z$Ae{-Tef>x?4GwpT|`oEm~Q*3DR+le z?}`04Ph{H>{*LQ=c5j_Oy+iM+-jc7o*$W)i5@!Y4+U;NbZuQotP7U2_c0D&*!rnSP zyxo3#_ZPKi^7R$Zrq>!uR!u#a&{KBZrC|C&fsU8#ZK7qNtqlKj(jNC)GaPS`>WP-U zcF_1!ArR%uI|-Of2Qk4>K8Xv$1Qnd7QIgVP<78s4x^V z5MW~tWnmNM%*jtq%*n_vE`f=0K*StT1lh4DPKBaHDLgkY><*uQ_^e zo;?31xIM_`y$i!(Is1lffohqqu?|XszawpXBYy2a`gNDVx6`vjbFMd9H^`dge@aW( zo;@>Q>5}>LU$9gQC>`?Oz4H3M?Z>2lScgA9wqltmKg;jtZ+HZ6MH!u=sa<}1i;-ybe$y*AtS z-swH7W6XAh@2s4%e)lh{S4}^|eYkDeo6-X#!~1q!iS&O~$S%;>u*FlXQ2B+AhM4g7 z^PEOKCzd_55v%GBsp-{tuy~!EcKzmMr4#cO*K_|;G)?;WJE@9M#rn*WkNZ{xPUu|G zeE(t8)A?+Zy3^tsszX&%7=5h&o%hsYZfs#-YHVOS6d)4EzD9lG=1GU7jvvknKj7cM z6#a5m-TXIt4_ve6UQ3@lRZKPImT8F1kq49f@BVohyFM((TVYXBq364&ADibf{P%rT zug>doCTg`fi|Vn<%RavQU?s`>`+v^S_viQjcUlw3@@v(rQ|yZ+)Y`f0@9TbQ4nJ_} z{G40o