diff options
| author | Thomas Lenz <thomas.lenz@egiz.gv.at> | 2020-03-11 12:46:45 +0100 | 
|---|---|---|
| committer | Thomas Lenz <thomas.lenz@egiz.gv.at> | 2020-03-11 12:46:45 +0100 | 
| commit | f95a1fb3982395ccbc7e139cb5bd8a1c106bbb48 (patch) | |
| tree | e2fd57efe89ded06cfe998d712b68bd2ed4b1de2 /eaaf_core_utils | |
| parent | 19bc544de503af5992d045a699a1f2bcc1eaf505 (diff) | |
| download | EAAF-Components-f95a1fb3982395ccbc7e139cb5bd8a1c106bbb48.tar.gz EAAF-Components-f95a1fb3982395ccbc7e139cb5bd8a1c106bbb48.tar.bz2 EAAF-Components-f95a1fb3982395ccbc7e139cb5bd8a1c106bbb48.zip | |
refactor HttpClientFactory.java to build HTTP clients with different authentication mechanisms
Diffstat (limited to 'eaaf_core_utils')
10 files changed, 872 insertions, 205 deletions
| diff --git a/eaaf_core_utils/pom.xml b/eaaf_core_utils/pom.xml index 1f5467ab..681152f7 100644 --- a/eaaf_core_utils/pom.xml +++ b/eaaf_core_utils/pom.xml @@ -100,6 +100,16 @@        <artifactId>guava</artifactId>        <scope>test</scope>      </dependency> +    <dependency> +      <groupId>com.squareup.okhttp3</groupId> +      <artifactId>mockwebserver</artifactId> +      <scope>test</scope> +    </dependency> +    <dependency> +      <groupId>com.squareup.okhttp3</groupId> +      <artifactId>okhttp-tls</artifactId> +      <scope>test</scope> +    </dependency>    </dependencies> diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/EaafSslKeySelectionStrategy.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/EaafSslKeySelectionStrategy.java new file mode 100644 index 00000000..1e1e2137 --- /dev/null +++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/EaafSslKeySelectionStrategy.java @@ -0,0 +1,50 @@ +package at.gv.egiz.eaaf.core.impl.http; + +import java.net.Socket; +import java.util.Map; + +import org.apache.commons.lang3.StringUtils; +import org.apache.http.ssl.PrivateKeyDetails; +import org.apache.http.ssl.PrivateKeyStrategy; + +import lombok.extern.slf4j.Slf4j; + +/** + * Private Key selection implementation for Apache HTTP clients. + * + * @author tlenz + * + */ +@Slf4j +public class EaafSslKeySelectionStrategy implements PrivateKeyStrategy { + +  private final String keyAlias; + +  /** +   * Private Key selection implementation for Apache HTTP clients. +   * +   * @param alias Alias of the Key that should be used for SSL client authentication. +   */ +  public EaafSslKeySelectionStrategy(String alias) { +    this.keyAlias = alias; + +  } + +  @Override +  public String chooseAlias(Map<String, PrivateKeyDetails> aliases, Socket socket) { +    log.trace("Selection SSL client-auth key for alias: {}", keyAlias); +    final PrivateKeyDetails selected = aliases.get(keyAlias); +    if (selected != null) { +      log.trace("Select SL client-auth key with type:", selected.getType()); +      return keyAlias; + +    } else { +      log.warn("KeyStore contains NO key with alias: {}. Using first key from keystore", keyAlias); +      log.info("Available aliases: {}", StringUtils.join(aliases.keySet(), ", ")); +      return aliases.keySet().iterator().next(); + +    } + +  } + +} 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 new file mode 100644 index 00000000..582ad545 --- /dev/null +++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpClientConfiguration.java @@ -0,0 +1,191 @@ +package at.gv.egiz.eaaf.core.impl.http; + +import java.text.MessageFormat; +import java.util.UUID; + +import javax.annotation.Nonnull; + +import at.gv.egiz.eaaf.core.exceptions.EaafConfigurationException; +import at.gv.egiz.eaaf.core.impl.credential.KeyStoreConfiguration; + +import org.apache.commons.lang3.StringUtils; + +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Getter +public class HttpClientConfiguration { + +  private static final String MSG_KEYSTORE_NAME = "KeyStore for httpClient: {0}"; + +  private static final String ERROR_00 = "internal.httpclient.00"; +  private static final String ERROR_01 = "internal.httpclient.01"; +  private static final String ERROR_02 = "internal.httpclient.02"; + +  @Nonnull +  private final String friendlyName; + +  @Nonnull +  private final String uuid; + +  @Nonnull +  private ClientAuthMode authMode = ClientAuthMode.NONE; + +  @Setter +  private String username; + +  @Setter +  private String password; + +  @Setter +  boolean disableHostnameValidation = false; + +  @Setter +  boolean disableTlsHostCertificateValidation = false; + + +  private KeyStoreConfiguration keyStoreConfig; + +  @Setter +  private String sslKeyAlias; + +  @Setter +  private String sslKeyPassword; + +  @Setter +  private boolean followHttpRedirects = true; + +  /** +   * Get a new HTTP-client configuration object. +   * +   * @param name FriendlyName of this http client for logging purposes. +   */ +  public HttpClientConfiguration(String name) { +    this.friendlyName = name; +    this.uuid = UUID.randomUUID().toString(); + +  } + +  /** +   * Set Client authentication-mode from configuration property. +   * +   * <p>If the mode is unknown than the {@link ClientAuthMode} is set to <code>NONE</code> </p> +   * +   * @param authModeString Modes from {@link ClientAuthMode} +   */ +  public void setAuthMode(String authModeString) { +    final ClientAuthMode configAuthMode = HttpClientConfiguration.ClientAuthMode.fromString(authModeString); +    if (configAuthMode != null) { +      authMode = configAuthMode; + +    } else { +      log.warn("Can Not parse ClientAuthMode for client: {}! Set mode to default value", +          friendlyName); + +    } +  } + + +  /** +   * Validate the internal state of this configuration object. +   * +   * @throws EaafConfigurationException In case of a configuration error +   */ +  public void validate() throws EaafConfigurationException { +    log.trace("Validating http-client: {}", this.friendlyName); +    if (this.authMode.equals(ClientAuthMode.PASSWORD)) { +      if (StringUtils.isEmpty(this.username)) { +        throw new EaafConfigurationException(ERROR_00, new Object[] {this.friendlyName}); + +      } + +      if (StringUtils.isEmpty(this.password)) { +        log.warn("Http basic authentication was activated but NOT username was set!"); + +      } + +    } else if (this.authMode.equals(ClientAuthMode.SSL)) { +      if (this.keyStoreConfig == null) { +        throw new EaafConfigurationException(ERROR_01, new Object[] {this.friendlyName}); + +      } else { +        log.trace("Validating KeyStore: {} for http-client: {} ...", +            this.keyStoreConfig.getFriendlyName(), this.friendlyName); +        this.keyStoreConfig.validate(); + +      } + +      if (StringUtils.isEmpty(this.sslKeyPassword)) { +        throw new EaafConfigurationException(ERROR_02, new Object[] { +            this.friendlyName, this.keyStoreConfig.getFriendlyName()}); + +      } +    } + +  } + +  /** +   * Build a {@link KeyStoreConfiguration} object from 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 buildKeyStoreConfig(String keyStoreType, String keyStorePath, +      String keyStorePassword, String keyStoreName) throws EaafConfigurationException { +    final KeyStoreConfiguration config = new KeyStoreConfiguration(); +    config.setKeyStoreType(keyStoreType); +    config.setFriendlyName(MessageFormat.format(MSG_KEYSTORE_NAME, friendlyName)); +    config.setSoftKeyStoreFilePath(keyStorePath); +    config.setSoftKeyStorePassword(keyStorePassword); +    config.setKeyStoreName(keyStoreName); +    this.keyStoreConfig = config; + +  } + +  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(); + +    } + +  } + +} diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/HttpClientFactory.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpClientFactory.java index e681e705..b6e660da 100644 --- a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/HttpClientFactory.java +++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpClientFactory.java @@ -1,11 +1,11 @@ -package at.gv.egiz.eaaf.core.impl.utils; +package at.gv.egiz.eaaf.core.impl.http; -import java.security.KeyManagementException;  import java.security.KeyStore; -import java.security.KeyStoreException;  import java.security.NoSuchAlgorithmException; -import java.security.UnrecoverableKeyException; +import java.util.HashMap; +import java.util.Map; +import javax.annotation.Nonnull;  import javax.annotation.PostConstruct;  import javax.net.ssl.HostnameVerifier;  import javax.net.ssl.SSLContext; @@ -13,8 +13,8 @@ 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.exceptions.EaafFactoryException;  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; @@ -41,22 +41,19 @@ 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) +  @Autowired    private IConfiguration basicConfig; +  @Autowired +  private EaafKeyStoreFactory keyStoreFactory; -  @Autowired(required = true) -  ResourceLoader resourceLoader; - -  @Autowired private EaafKeyStoreFactory keyStoreFactory; +  private static final String ERROR_03 = "internal.httpclient.03";    public static final String PROP_CONFIG_CLIENT_HTTP_CONNECTION_POOL_USE =        "client.http.connection.pool.use"; @@ -84,9 +81,10 @@ public class HttpClientFactory implements IHttpClientFactory {        "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_ALIAS = +      "client.auth.ssl.key.alias";    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"; @@ -95,48 +93,8 @@ public class HttpClientFactory implements IHttpClientFactory {    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; +  private String defaultConfigurationId = null; +  private final Map<String, HttpClientBuilder> availableBuilders = new HashMap<>();    /*     * (non-Javadoc) @@ -151,151 +109,145 @@ public class HttpClientFactory implements IHttpClientFactory {    @Override    public CloseableHttpClient getHttpClient(final boolean followRedirects) { -    RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); -    if (!followRedirects) { -      redirectStrategy = new RedirectStrategy() { +    return availableBuilders.get(defaultConfigurationId).setRedirectStrategy( +        buildRedirectStrategy(followRedirects)).build(); -        @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; -        } -      }; -    } +  @Override +  public CloseableHttpClient getHttpClient(@Nonnull HttpClientConfiguration config) throws EaafException { +    log.trace("Build http client for: {}", config.getFriendlyName()); +    HttpClientBuilder builder = null; +    if (availableBuilders.containsKey(config.getUuid())) { +      builder = availableBuilders.get(config.getUuid()); -    return httpClientBuilder.setRedirectStrategy(redirectStrategy).build(); +    } else { +      log.debug("Initialize new http-client builder for: {}", config.getFriendlyName()); -  } +      //validate configuration object +      config.validate(); -  @PostConstruct -  private void initalize() { -    // initialize http client -    log.trace("Initializing HTTP Client-builder ... "); -    httpClientBuilder = HttpClients.custom(); +      builder = HttpClients.custom(); +      builder.setDefaultRequestConfig(buildDefaultRequestConfig()); -    // 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); +      //inject basic authentication infos +      injectBasicAuthenticationIfRequired(builder, config); -    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 authentication if required +      final LayeredConnectionSocketFactory sslConnectionFactory = getSslContext(config); -    } +      // set pool connection if required +      injectDefaultConnectionPoolIfRequired(builder, sslConnectionFactory); -    // inject basic http authentication if required -    log.info("Client authentication-mode is set to: {}", clientAuthMode); -    injectBasicAuthenticationIfRequired(clientAuthMode); +      availableBuilders.put(config.getUuid(), builder); -    // inject authentication if required -    final LayeredConnectionSocketFactory sslConnectionFactory = getSslContext(clientAuthMode); +    } -    // set pool connection if required -    injectConnectionPoolIfRequired(sslConnectionFactory); +    return builder.setRedirectStrategy( +        buildRedirectStrategy(config.isFollowHttpRedirects())).build();    } -  private void injectBasicAuthenticationIfRequired(final ClientAuthMode clientAuthMode) { -    if (clientAuthMode.equals(ClientAuthMode.PASSWORD)) { -      final CredentialsProvider provider = new BasicCredentialsProvider(); +  @PostConstruct +  private void initalize() throws EaafException { +    final HttpClientConfiguration defaultHttpClientConfig = buildDefaultHttpClientConfiguration(); -      final String username = -          basicConfig.getBasicConfiguration(PROP_CONFIG_CLIENT_AUTH_HTTP_USERNAME); -      final String password = -          basicConfig.getBasicConfiguration(PROP_CONFIG_CLIENT_AUTH_HTTP_PASSORD); +    // initialize http client +    log.trace("Initializing default HTTP-Client builder ... "); +    final HttpClientBuilder defaultHttpClientBuilder = HttpClients.custom(); -      if (StringUtils.isEmpty(username)) { -        log.warn("Http basic authentication was activated but NOT username was set!"); +    // set default request configuration +    defaultHttpClientBuilder.setDefaultRequestConfig(buildDefaultRequestConfig()); -      } +    //inject http basic authentication +    injectBasicAuthenticationIfRequired(defaultHttpClientBuilder, defaultHttpClientConfig); -      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); +    // inject authentication if required +    final LayeredConnectionSocketFactory sslConnectionFactory = +        getSslContext(defaultHttpClientConfig); -    } else { -      log.trace("Injection of Http Basic authentication was skipped"); +    // set pool connection if required +    injectDefaultConnectionPoolIfRequired(defaultHttpClientBuilder, sslConnectionFactory); -    } +    //set default http client builder +    defaultConfigurationId = defaultHttpClientConfig.getUuid(); +    availableBuilders.put(defaultConfigurationId, defaultHttpClientBuilder);    } -  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 HttpClientConfiguration buildDefaultHttpClientConfiguration() throws EaafConfigurationException { +    final HttpClientConfiguration config = new HttpClientConfiguration("Default"); +    // inject basic http authentication if required +    config.setAuthMode(basicConfig.getBasicConfiguration(PROP_CONFIG_CLIENT_MODE, +        HttpClientConfiguration.ClientAuthMode.NONE.getMode())); +    log.info("Default client authentication-mode is set to: {}", config.getAuthMode()); + +    // set Username and Password if available +    config.setUsername(basicConfig.getBasicConfiguration(PROP_CONFIG_CLIENT_AUTH_HTTP_USERNAME)); +    config.setPassword(basicConfig.getBasicConfiguration(PROP_CONFIG_CLIENT_AUTH_HTTP_PASSORD)); + +    // set SSL Client auth. informations if available +    config.buildKeyStoreConfig( +        basicConfig.getBasicConfiguration( +            PROP_CONFIG_CLIENT_AUTH_SSL_KEYSTORE_TYPE, KeyStoreType.PKCS12.getKeyStoreType()), +        basicConfig.getBasicConfiguration( +            PROP_CONFIG_CLIENT_AUTH_SSL_KEYSTORE_PATH, StringUtils.EMPTY), +        basicConfig.getBasicConfiguration( +            PROP_CONFIG_CLIENT_AUTH_SSL_KEYSTORE_PASSORD, StringUtils.EMPTY), +        basicConfig.getBasicConfiguration( +            PROP_CONFIG_CLIENT_AUTH_SSL_KEYSTORE_NAME, StringUtils.EMPTY)); + +    config.setSslKeyAlias( +        basicConfig.getBasicConfiguration(PROP_CONFIG_CLIENT_AUTH_SSL_KEY_ALIAS)); +    config.setSslKeyPassword( +        basicConfig.getBasicConfiguration(PROP_CONFIG_CLIENT_AUTH_SSL_KEY_PASSWORD)); + +    config.setDisableHostnameValidation(basicConfig.getBasicConfigurationBoolean( +        PROP_CONFIG_CLIENT_HTTP_SSL_HOSTNAMEVERIFIER_TRUSTALL, false)); + +    // validate configuration object +    config.validate(); + +    return config;    } -  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(); +  private void injectBasicAuthenticationIfRequired(HttpClientBuilder builder, +      final HttpClientConfiguration httpClientConfig) { +    if (httpClientConfig.getAuthMode().equals(HttpClientConfiguration.ClientAuthMode.PASSWORD)) { +      final CredentialsProvider provider = new BasicCredentialsProvider(); +      log.trace("Injecting basic authentication with username: {} and password: {}", +          httpClientConfig.getUsername(), httpClientConfig.getPassword()); +      final UsernamePasswordCredentials credentials = new UsernamePasswordCredentials( +          httpClientConfig.getUsername(), httpClientConfig.getPassword()); -      return keyStore; +      final AuthScope scope = new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT, AuthScope.ANY_REALM); +      provider.setCredentials(scope, credentials); +      builder.setDefaultCredentialsProvider(provider); +      log.info("Basic http authentication was injected with username: {}", +          httpClientConfig.getUsername()); -    } 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); +    } else { +      log.trace("Injection of Http Basic authentication was skipped");      }    } -  private LayeredConnectionSocketFactory getSslContext(final ClientAuthMode clientAuthMode) { +  @Nonnull +  private LayeredConnectionSocketFactory getSslContext(final HttpClientConfiguration httpClientConfig) +      throws EaafException {      SSLContext sslContext = null;      try { -      if (clientAuthMode.equals(ClientAuthMode.SSL)) { -        sslContext = buildSslContextWithSslClientAuthentication(); +      if (httpClientConfig.getAuthMode().equals(HttpClientConfiguration.ClientAuthMode.SSL)) { +        log.debug("Open keyStore with type: {}", httpClientConfig.getKeyStoreConfig().getKeyStoreType()); +        final KeyStore keyStore = keyStoreFactory.buildNewKeyStore(httpClientConfig.getKeyStoreConfig()) +            .getFirst(); + +        log.trace("Injecting SSL client-authentication into http client ... "); +        sslContext = HttpUtils.buildSslContextWithSslClientAuthentication(keyStore, +            httpClientConfig.getSslKeyAlias(), httpClientConfig.getSslKeyPassword(), +            httpClientConfig.isDisableTlsHostCertificateValidation(), httpClientConfig.getFriendlyName());        } else {          log.trace("Initializing default SSL Context ... "); @@ -305,8 +257,7 @@ public class HttpClientFactory implements IHttpClientFactory {        // set hostname verifier        HostnameVerifier hostnameVerifier = null; -      if (basicConfig.getBasicConfigurationBoolean( -          PROP_CONFIG_CLIENT_HTTP_SSL_HOSTNAMEVERIFIER_TRUSTALL, false)) { +      if (httpClientConfig.isDisableHostnameValidation()) {          hostnameVerifier = new NoopHostnameVerifier();          log.warn("HTTP client-builder deactivates SSL Host-name verification!"); @@ -314,22 +265,20 @@ public class HttpClientFactory implements IHttpClientFactory {        final LayeredConnectionSocketFactory sslSocketFactory =            new SSLConnectionSocketFactory(sslContext, hostnameVerifier); - +      log.debug("HTTP client-builder successfuly initialized");        return sslSocketFactory; -    } catch (final NoSuchAlgorithmException | KeyManagementException | UnrecoverableKeyException -        | KeyStoreException | EaafConfigurationException e) { +    } catch (final NoSuchAlgorithmException e) {        log.warn("HTTP client-builder can NOT initialze SSL-Context", e); +      throw new EaafFactoryException(ERROR_03, new Object[] { +          httpClientConfig.getFriendlyName(), e.getMessage()}, e);      } -    log.info("HTTP client-builder successfuly initialized"); -    return null; -    } -  private void injectConnectionPoolIfRequired( -      final LayeredConnectionSocketFactory sslConnectionFactory) { +  private void injectDefaultConnectionPoolIfRequired( +      HttpClientBuilder builder, final LayeredConnectionSocketFactory sslConnectionFactory) {      if (basicConfig.getBasicConfigurationBoolean(PROP_CONFIG_CLIENT_HTTP_CONNECTION_POOL_USE,          true)) {        PoolingHttpClientConnectionManager pool; @@ -355,15 +304,56 @@ public class HttpClientFactory implements IHttpClientFactory {            basicConfig.getBasicConfiguration(PROP_CONFIG_CLIENT_HTTP_CONNECTION_POOL_MAXTOTAL,                DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_POOL_MAXTOTAL))); -      httpClientBuilder.setConnectionManager(pool); +      builder.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); +      builder.setSSLSocketFactory(sslConnectionFactory); + +    } + +  } + +  private RequestConfig buildDefaultRequestConfig() { +    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(); +    return requestConfig; + +  } + +  private static RedirectStrategy buildRedirectStrategy(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 redirectStrategy;    } diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/HttpUtils.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpUtils.java index 66356ba0..2d514912 100644 --- a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/HttpUtils.java +++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpUtils.java @@ -16,14 +16,35 @@   * works that you distribute must include a readable copy of the "NOTICE" text file.  */ -package at.gv.egiz.eaaf.core.impl.utils; +package at.gv.egiz.eaaf.core.impl.http; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.net.ssl.SSLContext;  import javax.servlet.http.HttpServletRequest; +import at.gv.egiz.eaaf.core.exceptions.EaafConfigurationException; +import at.gv.egiz.eaaf.core.exceptions.EaafFactoryException; +  import org.apache.commons.lang3.StringUtils; +import org.apache.http.conn.ssl.TrustAllStrategy; +import org.apache.http.ssl.SSLContextBuilder; +import org.apache.http.ssl.SSLContexts; +import org.apache.http.ssl.TrustStrategy; + +import lombok.extern.slf4j.Slf4j; +@Slf4j  public class HttpUtils { +  private static final String ERROR_03 = "internal.httpclient.03"; +    /**     * Helper method to retrieve server URL including context path.     * @@ -115,4 +136,59 @@ public class HttpUtils {      }    } +  /** +   * Initialize a {@link SSLContext} with a {@link KeyStore} that uses X509 Client +   * authentication. +   * +   * @param keyStore                   KeyStore with private keys that should be +   *                                   used +   * @param keyAlias                   Alias of the key that should be used. If +   *                                   the alias is null, than the first key that +   *                                   is found will be selected. +   * @param keyPasswordString          Password of the Key in this keystore +   * @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 buildSslContextWithSslClientAuthentication(@Nonnull final KeyStore keyStore, +      @Nullable String keyAlias, @Nullable String keyPasswordString, +      boolean trustAllServerCertificates, @Nonnull String friendlyName) +      throws EaafConfigurationException, EaafFactoryException { +    try { +      log.trace("Open SSL Client-Auth keystore with password: {}", keyPasswordString); +      final char[] keyPassword = keyPasswordString == null ? StringUtils.EMPTY.toCharArray() +          : keyPasswordString.toCharArray(); + +      SSLContextBuilder sslContextBuilder = SSLContexts.custom(); +      if (StringUtils.isNotEmpty(keyAlias)) { +        sslContextBuilder = sslContextBuilder +            .loadKeyMaterial(keyStore, keyPassword, new EaafSslKeySelectionStrategy(keyAlias)); + +      } else { +        sslContextBuilder = sslContextBuilder +            .loadKeyMaterial(keyStore, keyPassword); + +      } + +      if (trustAllServerCertificates) { +        log.warn("Http-client:{} trusts ALL TLS server-certificates!"); +        final TrustStrategy trustStrategy = new TrustAllStrategy(); +        sslContextBuilder = sslContextBuilder.loadTrustMaterial(trustStrategy); + +      } + +      return sslContextBuilder.build(); + +    } catch (NoSuchAlgorithmException | KeyManagementException | UnrecoverableKeyException +        | KeyStoreException e) { +      throw new EaafFactoryException(ERROR_03, new Object[] { friendlyName, e.getMessage() }, e); + +    } +  } +  } diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/IHttpClientFactory.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/IHttpClientFactory.java new file mode 100644 index 00000000..7ec58d46 --- /dev/null +++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/IHttpClientFactory.java @@ -0,0 +1,43 @@ +package at.gv.egiz.eaaf.core.impl.http; + +import javax.annotation.Nonnull; + +import at.gv.egiz.eaaf.core.exceptions.EaafException; + +import org.apache.http.impl.client.CloseableHttpClient; + +public interface IHttpClientFactory { + +  /** +   * Return an instance of a Apache HTTP client that uses +   * default configuration properties from {@link IHttpClientFactory} implementation +   * and follows http redirects automatically. +   * +   * @return http client +   */ +  @Nonnull +  CloseableHttpClient getHttpClient(); + +  /** +   * Return an instance of a Apache HTTP client that uses +   * default configuration properties from {@link IHttpClientFactory} implementation. +   * +   * @param followRedirects if <code>false</code>, the client does not flow 30x +   *                        http redirects +   * @return http client +   */ +  @Nonnull +  CloseableHttpClient getHttpClient(boolean followRedirects); + +  /** +   * Return an instance of a Apache HTTP client based in {@link HttpClientConfiguration}. +   * +   * @param config Configuration object for this http client +   * @return http client +   * @throws EaafException In case of a http-client initialization problem +   */ +  @Nonnull +  CloseableHttpClient getHttpClient(@Nonnull HttpClientConfiguration config) +      throws EaafException; + +} diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/IHttpClientFactory.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/IHttpClientFactory.java deleted file mode 100644 index f922e1ac..00000000 --- a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/IHttpClientFactory.java +++ /dev/null @@ -1,24 +0,0 @@ -package at.gv.egiz.eaaf.core.impl.utils; - -import org.apache.http.impl.client.CloseableHttpClient; - -public interface IHttpClientFactory { - -  /** -   * Return an instance of a Apache HTTP client that follows http redirects -   * automatically. -   * -   * @return -   */ -  CloseableHttpClient getHttpClient(); - -  /** -   * Return an instance of a Apache HTTP client. -   * -   * @param followRedirects if <code>false</code>, the client does not flow 30x -   *                        http redirects -   * @return -   */ -  CloseableHttpClient getHttpClient(boolean followRedirects); - -} diff --git a/eaaf_core_utils/src/main/resources/messages/eaaf_utils_message.properties b/eaaf_core_utils/src/main/resources/messages/eaaf_utils_message.properties index f531e02d..b20c5f63 100644 --- a/eaaf_core_utils/src/main/resources/messages/eaaf_utils_message.properties +++ b/eaaf_core_utils/src/main/resources/messages/eaaf_utils_message.properties @@ -12,4 +12,7 @@ internal.keystore.07=Validation of KeyStore: {0} failed. Reason: {1}  internal.keystore.08=Can not access Key: {1} in KeyStore: {0}  internal.keystore.09=Can not access Key: {1} in KeyStore: {0} Reason: {2} - +internal.httpclient.00=HttpClient:{0} uses http Basic-Auth, but 'Username' is NOT set +internal.httpclient.01=HttpClient:{0} uses X509 client-auth, but 'KeyStoreConfig' is NOT set +internal.httpclient.02=HttpClient:{0} uses KeyStore:{1}, but 'keyPassword' is NOT set +internal.httpclient.03=Can not initialize SSLContext for HttpClient:{0} Reason:{1} 
\ No newline at end of file diff --git a/eaaf_core_utils/src/main/resources/spring/eaaf_utils.beans.xml b/eaaf_core_utils/src/main/resources/spring/eaaf_utils.beans.xml index ab631e34..aa5a50de 100644 --- a/eaaf_core_utils/src/main/resources/spring/eaaf_utils.beans.xml +++ b/eaaf_core_utils/src/main/resources/spring/eaaf_utils.beans.xml @@ -11,8 +11,8 @@      http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd" >    <bean id="eaafHttpClientFactory" -        class="at.gv.egiz.eaaf.core.impl.utils.HttpClientFactory" />         -         +        class="at.gv.egiz.eaaf.core.impl.http.HttpClientFactory" />         +                          <bean id="eaafKeyStoreFactory"          class="at.gv.egiz.eaaf.core.impl.credential.EaafKeyStoreFactory" /> 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 new file mode 100644 index 00000000..b2f0f80e --- /dev/null +++ b/eaaf_core_utils/src/test/java/at/gv/egiz/eaaf/core/test/http/HttpClientFactoryTest.java @@ -0,0 +1,328 @@ +package at.gv.egiz.eaaf.core.test.http; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.InetAddress; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.Provider; +import java.security.cert.X509Certificate; + +import at.gv.egiz.eaaf.core.exceptions.EaafException; +import at.gv.egiz.eaaf.core.impl.credential.EaafKeyStoreFactory; +import at.gv.egiz.eaaf.core.impl.data.Pair; +import at.gv.egiz.eaaf.core.impl.http.HttpClientConfiguration; +import at.gv.egiz.eaaf.core.impl.http.IHttpClientFactory; + +import org.apache.commons.lang3.RandomStringUtils; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.impl.client.CloseableHttpClient; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import okhttp3.HttpUrl; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; +import okhttp3.tls.HandshakeCertificates; +import okhttp3.tls.HeldCertificate; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration("/spring/test_eaaf_pvp_not_lazy.beans.xml") +public class HttpClientFactoryTest { + +  @Autowired private EaafKeyStoreFactory keyStoreFactory; +  @Autowired private IHttpClientFactory httpClientFactory; + +  private MockWebServer mockWebServer = null; +  private HttpUrl mockServerUrl; + +  /** +   * JUnit test set-up. +   * +   */ +  @Before +  public void setup() { + +  } + +  /** +   * jUnit test shutdown. +   * +   * @throws IOException In case of an mockWebServer error +   */ +  @After +  public void shutdown() throws IOException { +    if (mockWebServer != null) { +      mockWebServer.shutdown(); +      mockWebServer = null; + +    } + +  } + +  @Test +  public void getDefaultClient() { +    final CloseableHttpClient client = httpClientFactory.getHttpClient(); +    Assert.assertNotNull("httpClient", client); + +  } + +  @Test +  public void getDefaultClientNoRedirect() { +    final CloseableHttpClient client = httpClientFactory.getHttpClient(false); +    Assert.assertNotNull("httpClient", client); + +  } + +  @Test +  public void getCustomClientsDefault() throws EaafException { +    final HttpClientConfiguration config = new HttpClientConfiguration("jUnit"); +    Assert.assertFalse("Wrong default config - Hostnamevalidation", +        config.isDisableHostnameValidation()); +    Assert.assertFalse("Wrong default config - TLS Server-certs", +        config.isDisableTlsHostCertificateValidation()); + +    final CloseableHttpClient client1 = httpClientFactory.getHttpClient(config); +    Assert.assertNotNull("first http client", client1); + +    final CloseableHttpClient client2 = httpClientFactory.getHttpClient(config); +    Assert.assertNotNull("second http client", client2); + +  } + +  @Test +  public void getCustomClientUnknownAuthMethod() throws EaafException { +    final HttpClientConfiguration config = new HttpClientConfiguration("jUnit"); +    config.setAuthMode(RandomStringUtils.randomAlphabetic(5)); +    final CloseableHttpClient client = httpClientFactory.getHttpClient(config); +    Assert.assertNotNull("httpClient", client); + +  } + +  @Test +  public void getCustomClientBasicAuth() throws EaafException, ClientProtocolException, IOException, InterruptedException { +    final HttpClientConfiguration config = new HttpClientConfiguration("jUnit"); +    config.setAuthMode("password"); +    config.setUsername("jUnit"); +    config.setPassword("password"); + +    final CloseableHttpClient client = httpClientFactory.getHttpClient(config); +    Assert.assertNotNull("httpClient", client); + +    //setup test webserver that requestes http Basic authentication +    mockWebServer = new MockWebServer(); +    mockServerUrl = mockWebServer.url("/sp/junit"); +    mockWebServer.enqueue(new MockResponse() +        .setResponseCode(HttpURLConnection.HTTP_UNAUTHORIZED) +        .addHeader("www-authenticate: Basic realm=\"protected area\"") +        .setBody("Please authenticate.")); +    mockWebServer.enqueue(new MockResponse().setResponseCode(200) +        .setBody("Successful auth!")); + +    //request webservice +    final HttpUriRequest httpGet2 = new HttpGet(mockServerUrl.url().toString()); +    final CloseableHttpResponse httpResp2 = client.execute(httpGet2); +    Assert.assertEquals("http statusCode", 200, httpResp2.getStatusLine().getStatusCode()); + +    //check request contains basic authentication after authentication was requested +    final RecordedRequest httpReq1 = mockWebServer.takeRequest(); +    final RecordedRequest httpReq2 = mockWebServer.takeRequest(); +    Assert.assertNull("wrong BasicAuthHeader", httpReq1.getHeader("Authorization")); +    Assert.assertNotNull("missing BasicAuthHeader", httpReq2.getHeader("Authorization")); + +  } + +  @Test +  public void getCustomClientBasicAuthNoUsername(){ +    final HttpClientConfiguration config = new HttpClientConfiguration("jUnit"); +    config.setAuthMode("password"); +    try { +      httpClientFactory.getHttpClient(config); +      Assert.fail("Wrong config not detected"); + +    } catch (final EaafException e) { +      Assert.assertEquals("Wrong errorCode", "internal.httpclient.00", e.getErrorId()); + +    } +  } + +  @Test +  public void getCustomClientBasicAuthNoPassword() throws EaafException { +    final HttpClientConfiguration config = new HttpClientConfiguration("jUnit"); +    config.setAuthMode("password"); +    config.setUsername(RandomStringUtils.randomAlphabetic(5)); + +    final CloseableHttpClient client = httpClientFactory.getHttpClient(config); +    Assert.assertNotNull("httpClient", client); + +  } + +  @Test +  public void getCustomClientX509AuthNoKeyStoreConfig(){ +    final HttpClientConfiguration config = new HttpClientConfiguration("jUnit"); +    config.setAuthMode("ssl"); +    try { +      httpClientFactory.getHttpClient(config); +      Assert.fail("Wrong config not detected"); + +    } catch (final EaafException e) { +      Assert.assertEquals("Wrong errorCode", "internal.httpclient.01", e.getErrorId()); + +    } +  } + +  @Test +  public void getCustomClientX509AuthNoKeyPassword() throws EaafException{ +    final HttpClientConfiguration config = new HttpClientConfiguration("jUnit"); +    config.setAuthMode("ssl"); +    config.buildKeyStoreConfig( +        "jks", +        "src/test/resources/data/junit.jks", +        "password", +        null); + +    try { +      httpClientFactory.getHttpClient(config); +      Assert.fail("Wrong config not detected"); + +    } catch (final EaafException e) { +      Assert.assertEquals("Wrong errorCode", "internal.httpclient.02", e.getErrorId()); + +    } +  } + +  @Test +  public void getCustomClientX509Auth() throws EaafException { +    final HttpClientConfiguration config = new HttpClientConfiguration("jUnit"); +    config.setAuthMode("ssl"); +    config.buildKeyStoreConfig( +        "jks", +        "src/test/resources/data/junit.jks", +        "password", +        null); +    config.setSslKeyPassword("password"); + +    final CloseableHttpClient client = httpClientFactory.getHttpClient(config); +    Assert.assertNotNull("httpClient", client); + +  } + +  @Test +  public void getCustomClientX509AuthWithAlias() throws EaafException, ClientProtocolException, +      IOException, KeyStoreException { +    final HttpClientConfiguration config = new HttpClientConfiguration("jUnit"); +    config.setAuthMode("ssl"); +    config.buildKeyStoreConfig( +        "jks", +        "src/test/resources/data/junit.jks", +        "password", +        null); +    config.setSslKeyPassword("password"); +    config.setSslKeyAlias("sig"); +    config.setDisableTlsHostCertificateValidation(true); + +    final CloseableHttpClient client = httpClientFactory.getHttpClient(config); +    Assert.assertNotNull("httpClient", client); + +    //set-up mock-up web-server with SSL client authentication +    final Pair<KeyStore, Provider> sslClientKeyStore = +        keyStoreFactory.buildNewKeyStore(config.getKeyStoreConfig()); +    final String localhost = InetAddress.getByName("localhost").getCanonicalHostName(); +    final HeldCertificate localhostCertificate = new HeldCertificate.Builder() +        .addSubjectAlternativeName(localhost) +        .build(); +    final HandshakeCertificates serverCertificates = new HandshakeCertificates.Builder() +        .addTrustedCertificate( +            (X509Certificate) sslClientKeyStore.getFirst().getCertificate(config.getSslKeyAlias())) +        .heldCertificate(localhostCertificate) +        .build(); +    mockWebServer = new MockWebServer(); +    mockWebServer.useHttps(serverCertificates.sslSocketFactory(), false); +    mockWebServer.requireClientAuth(); +    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()); +    final CloseableHttpResponse httpResp2 = client.execute(httpGet2); +    Assert.assertEquals("http statusCode", 200, httpResp2.getStatusLine().getStatusCode()); + +  } + +  @Test +  public void getCustomClientX509AuthWrongKeyPassword() throws EaafException{ +    final HttpClientConfiguration config = new HttpClientConfiguration("jUnit"); +    config.setAuthMode("ssl"); +    config.buildKeyStoreConfig( +        "jks", +        "src/test/resources/data/junit.jks", +        "password", +        null); +    config.setSslKeyPassword(RandomStringUtils.randomAlphanumeric(5)); +    config.setSslKeyAlias("sig"); + +    try { +      httpClientFactory.getHttpClient(config); +      Assert.fail("Wrong key password not detected"); + +    } catch (final EaafException e) { +      Assert.assertEquals("Wrong errorCode", "internal.httpclient.03", e.getErrorId()); + +    } +  } + +  @Test +  public void getCustomClientX509AuthWithWrongAlias() throws EaafException, KeyStoreException, ClientProtocolException, IOException { +    final HttpClientConfiguration config = new HttpClientConfiguration("jUnit"); +    config.setAuthMode("ssl"); +    config.buildKeyStoreConfig( +        "jks", +        "src/test/resources/data/junit.jks", +        "password", +        null); +    config.setSslKeyPassword("password"); +    config.setSslKeyAlias(RandomStringUtils.randomAlphabetic(5)); +    config.setDisableHostnameValidation(true); +    config.setFollowHttpRedirects(false); +    config.setDisableTlsHostCertificateValidation(true); + +    final CloseableHttpClient client = httpClientFactory.getHttpClient(config); +    Assert.assertNotNull("httpClient", client); + +    //set-up mock-up web-server with SSL client authentication +    final Pair<KeyStore, Provider> sslClientKeyStore = +        keyStoreFactory.buildNewKeyStore(config.getKeyStoreConfig()); +    final String localhost = InetAddress.getByName("localhost").getCanonicalHostName(); +    final HeldCertificate localhostCertificate = new HeldCertificate.Builder() +        .addSubjectAlternativeName(localhost) +        .build(); +    final HandshakeCertificates serverCertificates = new HandshakeCertificates.Builder() +        .addTrustedCertificate( +            (X509Certificate) sslClientKeyStore.getFirst().getCertificate("meta")) +        .heldCertificate(localhostCertificate) +        .build(); +    mockWebServer = new MockWebServer(); +    mockWebServer.useHttps(serverCertificates.sslSocketFactory(), false); +    mockWebServer.requireClientAuth(); +    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()); +    final CloseableHttpResponse httpResp2 = client.execute(httpGet2); +    Assert.assertEquals("http statusCode", 200, httpResp2.getStatusLine().getStatusCode()); + +  } +} | 
