package at.asitplus.eidas.specific.modules.auth.eidas.v2.clients;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.net.ssl.SSLContext;
import javax.xml.ws.BindingProvider;
import javax.xml.ws.handler.Handler;
import org.apache.commons.lang3.StringUtils;
import org.apache.cxf.configuration.jsse.TLSClientParameters;
import org.apache.cxf.endpoint.Client;
import org.apache.cxf.frontend.ClientProxy;
import org.apache.cxf.jaxws.DispatchImpl;
import org.apache.cxf.transport.http.HTTPConduit;
import org.apache.cxf.transports.http.configuration.HTTPClientPolicy;
import org.apache.http.ssl.SSLContextBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.Nullable;
import at.asitplus.eidas.specific.modules.auth.eidas.v2.utils.LoggingHandler;
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.HttpUtils;
import lombok.Builder;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class AbstractSoapClient {
@Autowired
protected IConfiguration basicConfig;
@Autowired
EaafKeyStoreFactory keyStoreFactory;
@Builder
@Getter
public static class HttpClientConfig {
private final String clientName;
private final String clientUrl;
private final String clientType;
private final String connectionTimeout;
private final String responseTimeout;
private final KeyStoreConfiguration keyStoreConfig;
private final String keyAlias;
private final String keyPassword;
private final KeyStoreConfiguration trustStoreConfig;
@Builder.Default
private final boolean trustAll = false;
}
/**
* Build a validated KeyStore Configuration-Object from configuration keys.
*
* @param keyStoreTypeKey Configuration key for type
* @param keyStorePathKey Configuration key for path
* @param keyStorePasswordKey Configuration key for password
* @param keyStoreNameKey Configuration key for name
* @param friendlyName Friendlyname for logging and errorhandling
* @return Valid KeyStore configuration or null
if no type was
* defined
* @throws EaafConfigurationException In case of validation error
*/
@Nullable
protected KeyStoreConfiguration buildKeyStoreConfiguration(String keyStoreTypeKey, String keyStorePathKey,
String keyStorePasswordKey, String keyStoreNameKey, String friendlyName)
throws EaafConfigurationException {
if (StringUtils.isNotEmpty(basicConfig.getBasicConfiguration(keyStoreTypeKey))) {
log.debug("Starting configuration of: {} ... ", friendlyName);
final KeyStoreConfiguration config = new KeyStoreConfiguration();
config.setFriendlyName(friendlyName);
config.setKeyStoreType(basicConfig.getBasicConfiguration(keyStoreTypeKey, KeyStoreType.PKCS12.name()));
config.setKeyStoreName(basicConfig.getBasicConfiguration(keyStoreNameKey));
config.setSoftKeyStoreFilePath(basicConfig.getBasicConfiguration(keyStorePathKey));
config.setSoftKeyStorePassword(basicConfig.getBasicConfiguration(keyStorePasswordKey));
// validate keystore configuration
config.validate();
return config;
} else {
log.info("Skipping configuration of: {}", friendlyName);
return null;
}
}
protected void injectHttpClient(Object raw, HttpClientConfig config) {
// extract client from implementation
Client client;
if (raw instanceof DispatchImpl>) {
client = ((DispatchImpl>) raw).getClient();
} else if (raw instanceof Client) {
client = ClientProxy.getClient(raw);
} else {
throw new RuntimeException("SOAP Client for SZR connection is of UNSUPPORTED type: " + raw.getClass()
.getName());
}
// set basic connection policies
final HTTPConduit http = (HTTPConduit) client.getConduit();
// set timeout policy
final HTTPClientPolicy httpClientPolicy = new HTTPClientPolicy();
httpClientPolicy.setConnectionTimeout(Integer.parseInt(config.getConnectionTimeout()) * 1000L);
httpClientPolicy.setReceiveTimeout(Integer.parseInt(config.getResponseTimeout()) * 1000L);
http.setClient(httpClientPolicy);
// inject SSL context in case of https
if (config.getClientUrl().toLowerCase().startsWith("https")) {
try {
log.debug("Adding SSLContext to client: " + config.getClientType() + " ... ");
final TLSClientParameters tlsParams = new TLSClientParameters();
if (config.getKeyStoreConfig() != null) {
final SSLContext sslContext = HttpUtils.buildSslContextWithSslClientAuthentication(
keyStoreFactory.buildNewKeyStore(config.getKeyStoreConfig()),
config.getKeyAlias(),
config.getKeyPassword(),
loadTrustStore(config.getTrustStoreConfig(), config.getClientName()),
config.isTrustAll(),
config.getClientName());
tlsParams.setSSLSocketFactory(sslContext.getSocketFactory());
} else {
log.debug(
"No KeyStore for SSL Client Auth. found. Initializing SSLContext for: {} without authentication ... ",
config.getClientName());
tlsParams.setSSLSocketFactory(SSLContextBuilder.create().build().getSocketFactory());
}
http.setTlsClientParameters(tlsParams);
log.info("SSLContext initialized for client: " + config.getClientType());
} catch (EaafException | KeyManagementException | NoSuchAlgorithmException e) {
log.error("SSLContext initialization FAILED.", e);
throw new RuntimeException("SSLContext initialization FAILED.", e);
}
}
}
private Pair loadTrustStore(KeyStoreConfiguration trustStoreConfig, String friendlyName)
throws EaafException {
if (trustStoreConfig != null) {
log.info("Build custom SSL truststore for: {}", friendlyName);
return keyStoreFactory.buildNewKeyStore(trustStoreConfig);
} else {
log.info("Use default SSL truststore for: {}", friendlyName);
return null;
}
}
protected void injectBindingProvider(BindingProvider bindingProvider, String clientType, String szrUrl,
boolean enableTraceLogging) {
final Map requestContext = bindingProvider.getRequestContext();
requestContext.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, szrUrl);
log.trace("Adding JAX-WS request/response trace handler to client: " + clientType);
List handlerList = bindingProvider.getBinding().getHandlerChain();
if (handlerList == null) {
handlerList = new ArrayList<>();
}
// add unique TransactionId into SOAP header
handlerList.add(new BmiSoapTransactionHeaderInterceptor(clientType));
// add logging handler to trace messages if required
if (enableTraceLogging) {
final LoggingHandler loggingHandler = new LoggingHandler();
handlerList.add(loggingHandler);
}
bindingProvider.getBinding().setHandlerChain(handlerList);
}
}