package at.gv.egiz.eaaf.core.impl.utils; import java.security.KeyManagementException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.UnrecoverableKeyException; import javax.annotation.PostConstruct; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; 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 org.apache.commons.lang3.StringUtils; import org.apache.http.HttpRequest; import org.apache.http.HttpResponse; import org.apache.http.ProtocolException; import org.apache.http.auth.AuthScope; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.CredentialsProvider; import org.apache.http.client.RedirectStrategy; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.config.Registry; import org.apache.http.config.RegistryBuilder; import org.apache.http.conn.socket.ConnectionSocketFactory; import org.apache.http.conn.socket.LayeredConnectionSocketFactory; import org.apache.http.conn.socket.PlainConnectionSocketFactory; import org.apache.http.conn.ssl.NoopHostnameVerifier; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.impl.client.BasicCredentialsProvider; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.DefaultRedirectStrategy; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.protocol.HttpContext; import org.apache.http.ssl.SSLContexts; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.ResourceLoader; import lombok.extern.slf4j.Slf4j; @Slf4j public class HttpClientFactory implements IHttpClientFactory { @Autowired(required = true) private IConfiguration basicConfig; @Autowired(required = true) ResourceLoader resourceLoader; @Autowired private EaafKeyStoreFactory keyStoreFactory; public static final String PROP_CONFIG_CLIENT_HTTP_CONNECTION_POOL_USE = "client.http.connection.pool.use"; public static final String PROP_CONFIG_CLIENT_HTTP_CONNECTION_POOL_MAXTOTAL = "client.http.connection.pool.maxtotal"; public static final String PROP_CONFIG_CLIENT_HTTP_CONNECTION_POOL_MAXPERROUTE = "client.http.connection.pool.maxperroute"; public static final String PROP_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_SOCKET = "client.http.connection.timeout.socket"; public static final String PROP_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_CONNECTION = "client.http.connection.timeout.connection"; public static final String PROP_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_REQUEST = "client.http.connection.timeout.request"; public static final String PROP_CONFIG_CLIENT_HTTP_SSL_HOSTNAMEVERIFIER_TRUSTALL = "client.http.ssl.hostnameverifier.trustall"; public static final String PROP_CONFIG_CLIENT_MODE = "client.authmode"; 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"; public static final String PROP_CONFIG_CLIENT_AUTH_SSL_KEYSTORE_PASSORD = "client.auth.ssl.keystore.password"; private static final String PROP_CONFIG_CLIENT_AUTH_SSL_KEYSTORE_NAME = "client.auth.ssl.keystore.name"; public static final String PROP_CONFIG_CLIENT_AUTH_SSL_KEYSTORE_TYPE = "client.auth.ssl.keystore.type"; public static final String PROP_CONFIG_CLIENT_AUTH_SSL_KEY_PASSWORD = "client.auth.ssl.key.password"; public static final String PROP_CONFIG_CLIENT_AUTH_SSL_KEY_ALIAS = "client.auth.ssl.key.alias"; // default configuration values public static final String DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_SOCKET = "15"; public static final String DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_CONNECTION = "15"; public static final String DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_REQUEST = "30"; public static final String DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_POOL_MAXTOTAL = "500"; public static final String DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_POOL_MAXPERROUTE = "100"; public enum ClientAuthMode { NONE("none"), PASSWORD("password"), SSL("ssl"); private final String mode; ClientAuthMode(final String mode) { this.mode = mode; } /** * Get the PVP mode. * * @return */ public String getMode() { return this.mode; } /** * Get http-client authentication mode from String representation. * * @param s Config parameter * @return */ public static ClientAuthMode fromString(final String s) { try { return ClientAuthMode.valueOf(s.toUpperCase()); } catch (IllegalArgumentException | NullPointerException e) { return null; } } @Override public String toString() { return getMode(); } } private HttpClientBuilder httpClientBuilder = null; /* * (non-Javadoc) * * @see at.gv.egiz.eaaf.core.impl.utils.IHttpClientFactory#getHttpClient() */ @Override public CloseableHttpClient getHttpClient() { return getHttpClient(true); } @Override public CloseableHttpClient getHttpClient(final boolean followRedirects) { RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); if (!followRedirects) { redirectStrategy = new RedirectStrategy() { @Override public boolean isRedirected(final HttpRequest request, final HttpResponse response, final HttpContext context) throws ProtocolException { return false; } @Override public HttpUriRequest getRedirect(final HttpRequest request, final HttpResponse response, final HttpContext context) throws ProtocolException { return null; } }; } return httpClientBuilder.setRedirectStrategy(redirectStrategy).build(); } @PostConstruct private void initalize() { // initialize http client log.trace("Initializing HTTP Client-builder ... "); httpClientBuilder = HttpClients.custom(); // set default request configuration final RequestConfig requestConfig = RequestConfig.custom() .setConnectTimeout( Integer.parseInt(basicConfig.getBasicConfiguration( PROP_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_CONNECTION, DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_CONNECTION)) * 1000) .setConnectionRequestTimeout(Integer.parseInt(basicConfig.getBasicConfiguration( PROP_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_REQUEST, DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_REQUEST)) * 1000) .setSocketTimeout(Integer.parseInt( basicConfig.getBasicConfiguration(PROP_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_SOCKET, DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_SOCKET)) * 1000) .build(); httpClientBuilder.setDefaultRequestConfig(requestConfig); ClientAuthMode clientAuthMode = ClientAuthMode.fromString( basicConfig.getBasicConfiguration(PROP_CONFIG_CLIENT_MODE, ClientAuthMode.NONE.getMode())); if (clientAuthMode == null) { log.warn("Can Not parse ClientAuthMode! Set mode to default value"); clientAuthMode = ClientAuthMode.NONE; } // inject basic http authentication if required log.info("Client authentication-mode is set to: {}", clientAuthMode); injectBasicAuthenticationIfRequired(clientAuthMode); // inject authentication if required final LayeredConnectionSocketFactory sslConnectionFactory = getSslContext(clientAuthMode); // set pool connection if required injectConnectionPoolIfRequired(sslConnectionFactory); } private void injectBasicAuthenticationIfRequired(final ClientAuthMode clientAuthMode) { if (clientAuthMode.equals(ClientAuthMode.PASSWORD)) { final CredentialsProvider provider = new BasicCredentialsProvider(); final String username = basicConfig.getBasicConfiguration(PROP_CONFIG_CLIENT_AUTH_HTTP_USERNAME); final String password = basicConfig.getBasicConfiguration(PROP_CONFIG_CLIENT_AUTH_HTTP_PASSORD); if (StringUtils.isEmpty(username)) { log.warn("Http basic authentication was activated but NOT username was set!"); } log.trace("Injecting basic authentication with username: {} and password: {}", username, password); final UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(username, password); provider.setCredentials(AuthScope.ANY, credentials); httpClientBuilder.setDefaultCredentialsProvider(provider); log.info("Basic http authentication was injected with username: {}", username); } else { log.trace("Injection of Http Basic authentication was skipped"); } } private SSLContext buildSslContextWithSslClientAuthentication() throws KeyManagementException, UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException, EaafConfigurationException { log.trace("Injecting SSL client-authentication into http client ... "); final KeyStore keystore = getSslAuthKeyStore(); final String keyPasswordString = basicConfig.getBasicConfiguration(PROP_CONFIG_CLIENT_AUTH_SSL_KEY_PASSWORD); log.trace("Open SSL Client-Auth keystore with password: {}", keyPasswordString); final char[] keyPassword = keyPasswordString == null ? StringUtils.EMPTY.toCharArray() : keyPasswordString.toCharArray(); return SSLContexts.custom().loadKeyMaterial(keystore, keyPassword).build(); } private KeyStore getSslAuthKeyStore() throws EaafConfigurationException { final String keyStoreType = basicConfig.getBasicConfiguration( PROP_CONFIG_CLIENT_AUTH_SSL_KEYSTORE_TYPE, KeyStoreType.PKCS12.getKeyStoreType()); final String localKeyStorePath = basicConfig .getBasicConfiguration(PROP_CONFIG_CLIENT_AUTH_SSL_KEYSTORE_PATH, StringUtils.EMPTY); final String keyStorePassword = basicConfig .getBasicConfiguration(PROP_CONFIG_CLIENT_AUTH_SSL_KEYSTORE_PASSORD, StringUtils.EMPTY); final String keyStoreName = basicConfig .getBasicConfiguration(PROP_CONFIG_CLIENT_AUTH_SSL_KEYSTORE_NAME, StringUtils.EMPTY); try { final KeyStoreConfiguration keyStoreConfig = new KeyStoreConfiguration(); keyStoreConfig.setKeyStoreType(keyStoreType); keyStoreConfig.setFriendlyName("HttpClient Keystore"); keyStoreConfig.setSoftKeyStoreFilePath(localKeyStorePath); keyStoreConfig.setSoftKeyStorePassword(keyStorePassword); keyStoreConfig.setKeyStoreName(keyStoreName); log.debug("Open keyStore with type: {}", keyStoreType); final KeyStore keyStore = keyStoreFactory.buildNewKeyStore(keyStoreConfig).getFirst(); return keyStore; } catch (final EaafException e) { log.warn("Can NOT read keyStore: {} from filesystem", localKeyStorePath, null, e); throw new EaafConfigurationException("Can NOT read keyStore: {} from filesystem", new Object[] { localKeyStorePath }, e); } } private LayeredConnectionSocketFactory getSslContext(final ClientAuthMode clientAuthMode) { SSLContext sslContext = null; try { if (clientAuthMode.equals(ClientAuthMode.SSL)) { sslContext = buildSslContextWithSslClientAuthentication(); } else { log.trace("Initializing default SSL Context ... "); sslContext = SSLContext.getDefault(); } // set hostname verifier HostnameVerifier hostnameVerifier = null; if (basicConfig.getBasicConfigurationBoolean( PROP_CONFIG_CLIENT_HTTP_SSL_HOSTNAMEVERIFIER_TRUSTALL, false)) { hostnameVerifier = new NoopHostnameVerifier(); log.warn("HTTP client-builder deactivates SSL Host-name verification!"); } final LayeredConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext, hostnameVerifier); return sslSocketFactory; } catch (final NoSuchAlgorithmException | KeyManagementException | UnrecoverableKeyException | KeyStoreException | EaafConfigurationException e) { log.warn("HTTP client-builder can NOT initialze SSL-Context", e); } log.info("HTTP client-builder successfuly initialized"); return null; } private void injectConnectionPoolIfRequired( final LayeredConnectionSocketFactory sslConnectionFactory) { if (basicConfig.getBasicConfigurationBoolean(PROP_CONFIG_CLIENT_HTTP_CONNECTION_POOL_USE, true)) { PoolingHttpClientConnectionManager pool; // set socketFactoryRegistry if SSLConnectionFactory is Set if (sslConnectionFactory != null) { final Registry socketFactoryRegistry = RegistryBuilder.create() .register("http", PlainConnectionSocketFactory.getSocketFactory()) .register("https", sslConnectionFactory).build(); log.trace("Inject SSLSocketFactory into pooled connection"); pool = new PoolingHttpClientConnectionManager(socketFactoryRegistry); } else { pool = new PoolingHttpClientConnectionManager(); } pool.setDefaultMaxPerRoute(Integer.parseInt( basicConfig.getBasicConfiguration(PROP_CONFIG_CLIENT_HTTP_CONNECTION_POOL_MAXPERROUTE, DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_POOL_MAXPERROUTE))); pool.setMaxTotal(Integer.parseInt( basicConfig.getBasicConfiguration(PROP_CONFIG_CLIENT_HTTP_CONNECTION_POOL_MAXTOTAL, DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_POOL_MAXTOTAL))); httpClientBuilder.setConnectionManager(pool); log.debug("Initalize http-client pool with, maxTotal: {} maxPerRoute: {}", pool.getMaxTotal(), pool.getDefaultMaxPerRoute()); } else if (sslConnectionFactory != null) { log.trace("Inject SSLSocketFactory without connection pool"); httpClientBuilder.setSSLSocketFactory(sslConnectionFactory); } } }