diff options
4 files changed, 212 insertions, 31 deletions
diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpClientConfiguration.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpClientConfiguration.java index f978fa4c..5ecbe1ec 100644 --- a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpClientConfiguration.java +++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpClientConfiguration.java @@ -22,6 +22,7 @@ import lombok.extern.slf4j.Slf4j; public class HttpClientConfiguration { private static final String MSG_KEYSTORE_NAME = "KeyStore for httpClient: {0}"; + private static final String MSG_TRUSTSTORE_NAME = "TrustStore for httpClient: {0}"; private static final String ERROR_00 = "internal.httpclient.00"; private static final String ERROR_01 = "internal.httpclient.01"; @@ -64,6 +65,9 @@ public class HttpClientConfiguration { private String sslKeyPassword; @Setter + private KeyStoreConfiguration trustStoreConfig; + + @Setter private boolean followHttpRedirects = true; @Setter @@ -160,6 +164,14 @@ public class HttpClientConfiguration { } } + // validate SSL TrustStore if it was set + if (this.trustStoreConfig != null) { + log.trace("Validating TrustStore: {} for http-client: {} ...", + this.trustStoreConfig.getFriendlyName(), this.friendlyName); + this.trustStoreConfig.validate(); + + } + } /** @@ -183,6 +195,35 @@ public class HttpClientConfiguration { } + /** + * Build a {@link KeyStoreConfiguration} object from trustStore configuration + * parameters. + * + * @param keyStoreType String based KeyStore type + * @param keyStorePath Path to KeyStore in case of a software based KeyStore + * @param keyStorePassword Password in case of a software based KeyStore + * @param keyStoreName Name of the KeyStore in case of a named KeyStore like + * HSM-Facade + * @throws EaafConfigurationException In case of a configuration error + */ + public void buildTrustStoreConfig(String keyStoreType, String keyStorePath, + String keyStorePassword, String keyStoreName) throws EaafConfigurationException { + if (StringUtils.isNotEmpty(keyStoreType)) { + log.debug("Injecting custom TrustStore configuration for: {} ... ", friendlyName); + final KeyStoreConfiguration config = new KeyStoreConfiguration(); + config.setFriendlyName(MessageFormat.format(MSG_TRUSTSTORE_NAME, friendlyName)); + config.setKeyStoreType(keyStoreType); + config.setSoftKeyStoreFilePath(keyStorePath); + config.setSoftKeyStorePassword(keyStorePassword); + config.setKeyStoreName(keyStoreName); + this.trustStoreConfig = config; + + } else { + log.debug("No TrustStoreType. Skipping TrustStore configuration for: {} ... ", friendlyName); + + } + } + public enum ClientAuthMode { NONE("none"), PASSWORD("password"), SSL("ssl"); diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpClientFactory.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpClientFactory.java index 7a71bfab..ceffe26c 100644 --- a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpClientFactory.java +++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpClientFactory.java @@ -47,6 +47,7 @@ import at.gv.egiz.eaaf.core.api.idp.IConfiguration; import at.gv.egiz.eaaf.core.exceptions.EaafConfigurationException; import at.gv.egiz.eaaf.core.exceptions.EaafException; import at.gv.egiz.eaaf.core.impl.credential.EaafKeyStoreFactory; +import at.gv.egiz.eaaf.core.impl.credential.KeyStoreConfiguration; import at.gv.egiz.eaaf.core.impl.credential.KeyStoreConfiguration.KeyStoreType; import at.gv.egiz.eaaf.core.impl.data.Pair; import at.gv.egiz.eaaf.core.impl.http.interceptor.PreemptiveAuthInterceptor; @@ -83,8 +84,6 @@ public class HttpClientFactory implements IHttpClientFactory { "client.http.connection.retry.count"; public static final String PROP_CONFIG_CLIENT_HTTP_CONNECTION_RETRY_POST = "client.http.connection.retry.post"; - public static final String PROP_CONFIG_CLIENT_HTTP_SSL_HOSTNAMEVERIFIER_TRUSTALL = - "client.http.ssl.hostnameverifier.trustall"; public static final String PROP_CONFIG_CLIENT_HTTP_PROXY_HOST_SSL = "client.http.connection.proxy.host.ssl"; @@ -104,17 +103,28 @@ public class HttpClientFactory implements IHttpClientFactory { public static final String PROP_CONFIG_CLIENT_AUTH_HTTP_USERNAME = "client.auth.http.username"; public static final String PROP_CONFIG_CLIENT_AUTH_HTTP_PASSORD = "client.auth.http.password"; public static final String PROP_CONFIG_CLIENT_AUTH_SSL_KEYSTORE_PATH = - "client.auth.ssl.keystore.path"; + "client.http.auth.ssl.keystore.path"; public static final String PROP_CONFIG_CLIENT_AUTH_SSL_KEYSTORE_PASSORD = - "client.auth.ssl.keystore.password"; + "client.http.auth.ssl.keystore.password"; private static final String PROP_CONFIG_CLIENT_AUTH_SSL_KEYSTORE_NAME = - "client.auth.ssl.keystore.name"; + "client.http.auth.ssl.keystore.name"; public static final String PROP_CONFIG_CLIENT_AUTH_SSL_KEYSTORE_TYPE = - "client.auth.ssl.keystore.type"; + "client.http.auth.ssl.keystore.type"; public static final String PROP_CONFIG_CLIENT_AUTH_SSL_KEY_ALIAS = - "client.auth.ssl.key.alias"; + "client.http.auth.ssl.key.alias"; public static final String PROP_CONFIG_CLIENT_AUTH_SSL_KEY_PASSWORD = - "client.auth.ssl.key.password"; + "client.http.auth.ssl.key.password"; + + public static final String PROP_CONFIG_CLIENT_AUTH_SSL_TRUSTSTORE_PATH = + "client.http.ssl.truststore.path"; + public static final String PROP_CONFIG_CLIENT_AUTH_SSL_TRUSTSTORE_PASSORD = + "client.http.ssl.truststore.password"; + public static final String PROP_CONFIG_CLIENT_AUTH_SSL_TRUSTSTORE_NAME = + "client.http.ssl.truststore.name"; + public static final String PROP_CONFIG_CLIENT_AUTH_SSL_TRUSTSTORE_TYPE = + "client.http.ssl.truststore.type"; + public static final String PROP_CONFIG_CLIENT_HTTP_SSL_HOSTNAMEVERIFIER_TRUSTALL = + "client.http.ssl.hostnameverifier.trustall"; // default configuration values public static final String DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_SOCKET = "15"; @@ -306,6 +316,17 @@ public class HttpClientFactory implements IHttpClientFactory { config.setSslKeyPassword( basicConfig.getBasicConfiguration(PROP_CONFIG_CLIENT_AUTH_SSL_KEY_PASSWORD)); + // set SSL TrustStore if available + config.buildTrustStoreConfig( + basicConfig.getBasicConfiguration( + PROP_CONFIG_CLIENT_AUTH_SSL_TRUSTSTORE_TYPE), + basicConfig.getBasicConfiguration( + PROP_CONFIG_CLIENT_AUTH_SSL_TRUSTSTORE_PATH, StringUtils.EMPTY), + basicConfig.getBasicConfiguration( + PROP_CONFIG_CLIENT_AUTH_SSL_TRUSTSTORE_PASSORD, StringUtils.EMPTY), + basicConfig.getBasicConfiguration( + PROP_CONFIG_CLIENT_AUTH_SSL_TRUSTSTORE_NAME, StringUtils.EMPTY)); + config.setDisableHostnameValidation(basicConfig.getBasicConfigurationBoolean( PROP_CONFIG_CLIENT_HTTP_SSL_HOSTNAMEVERIFIER_TRUSTALL, false)); @@ -369,11 +390,14 @@ public class HttpClientFactory implements IHttpClientFactory { log.trace("Injecting SSL client-authentication into http client ... "); sslContext = HttpUtils.buildSslContextWithSslClientAuthentication(keyStore, httpClientConfig.getSslKeyAlias(), httpClientConfig.getSslKeyPassword(), + buildCustomTrustStore(httpClientConfig.getTrustStoreConfig()), httpClientConfig.isDisableTlsHostCertificateValidation(), httpClientConfig.getFriendlyName()); } else { log.trace("Initializing default SSL Context ... "); - sslContext = HttpUtils.buildSslContext(httpClientConfig.isDisableTlsHostCertificateValidation(), + sslContext = HttpUtils.buildSslContext( + buildCustomTrustStore(httpClientConfig.getTrustStoreConfig()), + httpClientConfig.isDisableTlsHostCertificateValidation(), httpClientConfig.getFriendlyName()); } @@ -393,6 +417,18 @@ public class HttpClientFactory implements IHttpClientFactory { } + private KeyStore buildCustomTrustStore(KeyStoreConfiguration trustStoreConfig) throws EaafException { + if (trustStoreConfig != null) { + log.debug("Open trustStore with type: {}", trustStoreConfig.getKeyStoreType()); + final Pair<KeyStore, Provider> trustStore = keyStoreFactory.buildNewKeyStore(trustStoreConfig); + return trustStore.getFirst(); + + } else { + return null; + + } + } + @Nonnull private HttpClientConnectionManager injectConnectionManager( HttpClientBuilder builder, final LayeredConnectionSocketFactory sslConnectionFactory) diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpUtils.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpUtils.java index 0faa94f4..61076dfa 100644 --- a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpUtils.java +++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpUtils.java @@ -30,8 +30,6 @@ import java.security.NoSuchAlgorithmException; import java.security.Provider; import java.security.UnrecoverableKeyException; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import javax.net.ssl.SSLContext; import org.apache.commons.lang3.StringUtils; @@ -53,6 +51,8 @@ import at.gv.egiz.eaaf.core.exceptions.EaafConfigurationException; import at.gv.egiz.eaaf.core.exceptions.EaafFactoryException; import at.gv.egiz.eaaf.core.impl.data.Pair; import at.gv.egiz.eaaf.core.impl.data.Triple; +import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; import jakarta.servlet.http.HttpServletRequest; import lombok.NonNull; import lombok.extern.slf4j.Slf4j; @@ -272,10 +272,31 @@ public class HttpUtils { public static SSLContext buildSslContext( boolean trustAllServerCertificates, @Nonnull String friendlyName) throws EaafConfigurationException, EaafFactoryException { + return buildSslContext(null, trustAllServerCertificates, friendlyName); + + } + + /** + * Initialize a {@link SSLContext}. + * + * @param trustStore Custom SSL TrustStore, or <code>null</code> + * if default truststore should be used + * @param trustAllServerCertificates Deactivate SSL server-certificate + * validation + * @param friendlyName FriendlyName of the http client for logging + * purposes + * @return {@link SSLContext} with X509 client authentication + * @throws EaafConfigurationException In case of a configuration error + * @throws EaafFactoryException In case of a {@link SSLContext} + * initialization error + */ + public static SSLContext buildSslContext(@Nullable final KeyStore trustStore, + boolean trustAllServerCertificates, @Nonnull String friendlyName) + throws EaafConfigurationException, EaafFactoryException { try { EaafSslContextBuilder sslContextBuilder = EaafSslContextBuilder.create(); - injectTrustStore(sslContextBuilder, null, trustAllServerCertificates, friendlyName); + injectTrustStore(sslContextBuilder, trustStore, trustAllServerCertificates, friendlyName); return sslContextBuilder.build(); @@ -308,20 +329,8 @@ public class HttpUtils { @Nullable String keyAlias, @Nullable String keyPasswordString, boolean trustAllServerCertificates, @Nonnull String friendlyName) throws EaafConfigurationException, EaafFactoryException { - try { - EaafSslContextBuilder sslContextBuilder = EaafSslContextBuilder.create(); - - injectKeyStore(sslContextBuilder, keyStore, keyAlias, keyPasswordString, friendlyName); - - injectTrustStore(sslContextBuilder, null, trustAllServerCertificates, friendlyName); - - return sslContextBuilder.build(); - - } catch (NoSuchAlgorithmException | KeyManagementException | UnrecoverableKeyException - | KeyStoreException e) { - throw new EaafFactoryException(ERROR_03, new Object[] { friendlyName, e.getMessage() }, e); - - } + return buildSslContextWithSslClientAuthentication(keyStore, keyAlias, keyPasswordString, + null, trustAllServerCertificates, friendlyName); } /** @@ -346,7 +355,7 @@ public class HttpUtils { */ public static SSLContext buildSslContextWithSslClientAuthentication(@Nonnull final Pair<KeyStore, Provider> keyStore, @Nullable String keyAlias, @Nullable String keyPasswordString, - @Nullable final Pair<KeyStore, Provider> trustStore, boolean trustAllServerCertificates, + @Nullable final KeyStore trustStore, boolean trustAllServerCertificates, @Nonnull String friendlyName) throws EaafConfigurationException, EaafFactoryException { try { @@ -366,7 +375,7 @@ public class HttpUtils { } private static void injectTrustStore(EaafSslContextBuilder sslContextBuilder, - Pair<KeyStore, Provider> trustStore, boolean trustAllServerCertificates, String friendlyName) + KeyStore trustStore, boolean trustAllServerCertificates, String friendlyName) throws NoSuchAlgorithmException, KeyStoreException { TrustStrategy trustStrategy = null; @@ -376,14 +385,12 @@ public class HttpUtils { } - KeyStore trustStoreImpl = null; if (trustStore != null) { log.info("Http-client: {} uses custom TrustStore.", friendlyName); - trustStoreImpl = trustStore.getFirst(); } - sslContextBuilder.loadTrustMaterial(trustStoreImpl, trustStrategy); + sslContextBuilder.loadTrustMaterial(trustStore, trustStrategy); } diff --git a/eaaf_core_utils/src/test/java/at/gv/egiz/eaaf/core/test/http/HttpClientFactoryTest.java b/eaaf_core_utils/src/test/java/at/gv/egiz/eaaf/core/test/http/HttpClientFactoryTest.java index c566380e..33ba96e2 100644 --- a/eaaf_core_utils/src/test/java/at/gv/egiz/eaaf/core/test/http/HttpClientFactoryTest.java +++ b/eaaf_core_utils/src/test/java/at/gv/egiz/eaaf/core/test/http/HttpClientFactoryTest.java @@ -327,6 +327,13 @@ public class HttpClientFactoryTest { config.setUsername("jUnit"); config.setPassword("password"); + final String current = new java.io.File(".").getCanonicalPath(); + System.setProperty("javax.net.ssl.trustStoreType", "jks"); + System.setProperty("javax.net.ssl.trustStore", + current + "/src/test/resources/data/ssL_truststore.jks"); + System.setProperty("javax.net.ssl.trustStorePassword", + "password"); + final CloseableHttpClient client = httpClientFactory.getHttpClient(config); Assert.assertNotNull("httpClient", client); @@ -394,6 +401,96 @@ public class HttpClientFactoryTest { } @Test + public void withCustomTrustStore() throws EaafException, ClientProtocolException, + IOException, KeyStoreException { + final HttpClientConfiguration config = new HttpClientConfiguration("jUnit"); + config.setEnablePreEmptiveHttpBasicAuth(false); + config.setAuthMode("password"); + config.setUsername("jUnit"); + config.setPassword("password"); + + final String current = new java.io.File(".").getCanonicalPath(); + config.buildTrustStoreConfig("jks", "file:" + current + "/src/test/resources/data/ssL_truststore.jks", + "password", null); + + final CloseableHttpClient client = httpClientFactory.getHttpClient(config); + Assert.assertNotNull("httpClient", client); + + // set-up mock-up web-server with SSL client authentication + final String localhost = InetAddress.getByName("localhost").getCanonicalHostName(); + final HeldCertificate localhostCertificate = new HeldCertificate.Builder() + .addSubjectAlternativeName(localhost) + .build(); + final HandshakeCertificates serverCertificates = new HandshakeCertificates.Builder() + .heldCertificate(localhostCertificate) + .build(); + mockWebServer = new MockWebServer(); + mockWebServer.useHttps(serverCertificates.sslSocketFactory(), false); + mockWebServer.enqueue(new MockResponse().setResponseCode(200) + .setBody("Successful auth!")); + mockServerUrl = mockWebServer.url("/sp/junit"); + + // perform test request + final HttpUriRequest httpGet2 = new HttpGet(mockServerUrl.url().toString()); + assertThrows(Exception.class, () -> client.execute(httpGet2)); + + } + + @Test + public void withWrongCustomTrustStore() throws EaafException, ClientProtocolException, + IOException, KeyStoreException { + final HttpClientConfiguration config = new HttpClientConfiguration("jUnit"); + config.setEnablePreEmptiveHttpBasicAuth(false); + config.setAuthMode("password"); + config.setUsername("jUnit"); + config.setPassword("password"); + + final String current = new java.io.File(".").getCanonicalPath(); + config.buildTrustStoreConfig("jks", "file:" + current + "/src/test/resources/data/ssL_truststore.jks", + "password", null); + + final CloseableHttpClient client = httpClientFactory.getHttpClient(config); + Assert.assertNotNull("httpClient", client); + + // set-up mock-up web-server with SSL client authentication + final String localhost = InetAddress.getByName("localhost").getCanonicalHostName(); + final HeldCertificate localhostCertificate = new HeldCertificate.Builder() + .addSubjectAlternativeName(localhost) + .build(); + final HandshakeCertificates serverCertificates = new HandshakeCertificates.Builder() + .heldCertificate(localhostCertificate) + .build(); + mockWebServer = new MockWebServer(); + mockWebServer.useHttps(serverCertificates.sslSocketFactory(), false); + mockWebServer.enqueue(new MockResponse().setResponseCode(200) + .setBody("Successful auth!")); + mockServerUrl = mockWebServer.url("/sp/junit"); + + // perform test request + final HttpUriRequest httpGet2 = new HttpGet(mockServerUrl.url().toString()); + assertThrows(Exception.class, () -> client.execute(httpGet2)); + + } + + @Test + public void withWrongConfigCustomTrustStore() throws EaafException, ClientProtocolException, + IOException, KeyStoreException { + final HttpClientConfiguration config = new HttpClientConfiguration("jUnit"); + config.setEnablePreEmptiveHttpBasicAuth(false); + config.setAuthMode("password"); + config.setUsername("jUnit"); + config.setPassword("password"); + + final String current = new java.io.File(".").getCanonicalPath(); + config.buildTrustStoreConfig("jks", "file:" + current + "/src/test/resources/data/ssL_truststore.jks", + "wrongPassword", null); + + EaafException error = assertThrows(EaafException.class, () -> httpClientFactory.getHttpClient(config)); + Assert.assertEquals("wrong errorCode", "internal.keystore.06", error.getErrorId()); + + } + + @Test public void testHttpClientRetryOneTime() throws EaafException, InterruptedException, ClientProtocolException, IOException { final HttpClientConfiguration config = |