From e2e77ed55687cb92c6f5a273995daf64dedef848 Mon Sep 17 00:00:00 2001 From: Christof Rabensteiner Date: Wed, 26 Jun 2019 08:47:58 +0200 Subject: Protect MsgClient via SSL (ink Client Authentication) - Add Component to create SSLContexts with own Key- and trust store. - Inject SSLContext into HTTP Client. - Add EAAF-Components Core Dependency, which is needed by SSLContextCreator (KeyStoreUtils). Schema Changes in mzs:DeliveryRequest/Config: - Got Rid of mzs:DeliveryRequest/Config/Server. In mzs 1.4.1, Server replaces the result of zkopf query person request. Since this zkopf interface does not exist anymore, Server was removed. - Add ClientType, which holds all parameters needed to connect to a service (Url, SSL params, a.o.). Configuration: - Add default parameters for SSL Clients in application.yaml. - Merge default parameters into incoming mzs:DeliveryRequests. MoaZSException Fixes: - Remove "Extends throwable" from Builder. - Add convenient shorthand init method (message, throwable). Refactor: - Put "determinePath" to FileUtils. - Put string related utility functions into StringUtils. --- src/main/java/at/gv/egiz/moazs/MoaZSException.java | 6 +- .../java/at/gv/egiz/moazs/config/MoaSigConfig.java | 19 +-- src/main/java/at/gv/egiz/moazs/msg/MsgClient.java | 64 +++++-- .../at/gv/egiz/moazs/msg/MsgClientFactory.java | 51 +++++- .../moazs/pipeline/SameThreadDeliveryPipeline.java | 4 +- .../moazs/preprocess/ConfigProfileGenerator.java | 19 +-- .../at/gv/egiz/moazs/preprocess/ConfigUtil.java | 183 ++++++++++++++++++--- .../moazs/preprocess/DeliveryRequestAugmenter.java | 6 +- src/main/java/at/gv/egiz/moazs/util/FileUtils.java | 22 +++ .../at/gv/egiz/moazs/util/SSLContextCreator.java | 82 +++++++++ .../java/at/gv/egiz/moazs/util/StringUtils.java | 17 ++ src/main/resources/application.yaml | 29 ++-- src/main/resources/mzs/app2mzs.xsd | 34 +++- 13 files changed, 441 insertions(+), 95 deletions(-) create mode 100644 src/main/java/at/gv/egiz/moazs/util/FileUtils.java create mode 100644 src/main/java/at/gv/egiz/moazs/util/SSLContextCreator.java create mode 100644 src/main/java/at/gv/egiz/moazs/util/StringUtils.java (limited to 'src/main') diff --git a/src/main/java/at/gv/egiz/moazs/MoaZSException.java b/src/main/java/at/gv/egiz/moazs/MoaZSException.java index db04241..847d9c1 100644 --- a/src/main/java/at/gv/egiz/moazs/MoaZSException.java +++ b/src/main/java/at/gv/egiz/moazs/MoaZSException.java @@ -32,6 +32,10 @@ public class MoaZSException extends RuntimeException { this.mzsRequest = mzsRequest; } + public static MoaZSException moaZSException(String message, Throwable cause) { + return moaZSExceptionBuilder(message).withCause(cause).build(); + } + public static MoaZSException moaZSException(String formatString, Object... objects) { return moaZSExceptionBuilder(formatString, objects).build(); } @@ -73,7 +77,7 @@ public class MoaZSException extends RuntimeException { return msgRequest; } - public static class Builder extends Throwable { + public static class Builder { private String message; private Throwable cause; diff --git a/src/main/java/at/gv/egiz/moazs/config/MoaSigConfig.java b/src/main/java/at/gv/egiz/moazs/config/MoaSigConfig.java index 05ecac1..0b7bdc7 100644 --- a/src/main/java/at/gv/egiz/moazs/config/MoaSigConfig.java +++ b/src/main/java/at/gv/egiz/moazs/config/MoaSigConfig.java @@ -2,10 +2,12 @@ package at.gv.egiz.moazs.config; import at.gv.egiz.eid.authhandler.modules.sigverify.moasig.api.ISignatureVerificationService; import at.gv.egiz.eid.authhandler.modules.sigverify.moasig.impl.SignatureVerificationService; +import at.gv.egiz.moazs.util.FileUtils; import at.gv.egiz.moazs.verify.MoaSPSSSignatureVerifier; import at.gv.egiz.moazs.verify.SignatureVerifier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -28,15 +30,18 @@ public class MoaSigConfig { private final String keyStoreType; private final String defaultTrustProfile; private final String spssConfigFilePath; + private final FileUtils fileUtils; public MoaSigConfig(@Value("${javax.net.ssl.trustStoreType}") String trustStoreType, @Value("${javax.net.ssl.keyStoreType}") String keyStoreType, @Value("${moa.spss.server.default-trustprofile}") String defaultTrustProfile, - @Value("${moa.spss.server.configuration}") String spssConfigFilePath) throws FileNotFoundException { + @Value("${moa.spss.server.configuration}") String spssConfigFilePath, + @Autowired FileUtils fileUtils) throws FileNotFoundException { this.trustStoreType = trustStoreType; this.keyStoreType = keyStoreType; this.defaultTrustProfile = defaultTrustProfile; this.spssConfigFilePath = spssConfigFilePath; + this.fileUtils = fileUtils; fallBackToSpringEnvForMoaSPSSConfigProperty(); fallBackToSpringEnvForJavaxNetSSLStoreTypeProperty(); } @@ -45,7 +50,7 @@ public class MoaSigConfig { log.info("value of spssConfigFilePath is {}", spssConfigFilePath); if(System.getProperty(MOA_SPSS_CONFIG_FILE_PROPERTY) == null) { - var realPath = determinePath(spssConfigFilePath); + var realPath = fileUtils.determinePath(spssConfigFilePath); var realFile = new File(realPath); if(realFile.exists() && realFile.canRead()) { @@ -57,15 +62,7 @@ public class MoaSigConfig { } } - private String determinePath(String abstractPath) { - if (new File(abstractPath).isAbsolute()) { - return abstractPath; - } else { - //resolve relative path as classpath resource - //java.lang.Class needs relative resources to start with "/" - return this.getClass().getResource("/" + abstractPath).getFile(); - } - } + private void fallBackToSpringEnvForJavaxNetSSLStoreTypeProperty() { if (System.getProperty(JAVAX_SSL_TRUSTSTORE_TYPE_PROPERTY) == null) { diff --git a/src/main/java/at/gv/egiz/moazs/msg/MsgClient.java b/src/main/java/at/gv/egiz/moazs/msg/MsgClient.java index 82f172d..84a7801 100644 --- a/src/main/java/at/gv/egiz/moazs/msg/MsgClient.java +++ b/src/main/java/at/gv/egiz/moazs/msg/MsgClient.java @@ -1,46 +1,80 @@ package at.gv.egiz.moazs.msg; -import at.gv.zustellung.app2mzs.xsd.ConfigType; import at.gv.zustellung.msg.xsd.App2ZusePort; +import at.gv.zustellung.msg.xsd.App2ZusePortService; import at.gv.zustellung.msg.xsd.DeliveryRequestStatusType; import at.gv.zustellung.msg.xsd.DeliveryRequestType; +import org.apache.cxf.configuration.jsse.TLSClientParameters; +import org.apache.cxf.endpoint.Client; +import org.apache.cxf.frontend.ClientProxy; import org.apache.cxf.jaxws.JaxWsClientFactoryBean; import org.apache.cxf.jaxws.JaxWsProxyFactoryBean; import org.apache.cxf.message.Message; import org.apache.cxf.phase.PhaseInterceptor; +import org.apache.cxf.transport.http.HTTPConduit; +import org.apache.cxf.transports.http.configuration.HTTPClientPolicy; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.lang.Nullable; +import javax.net.ssl.SSLContext; + +//TODO: Rethink design. could this entire class be replaced? +// Because everything the send() method does could be initialized in +// the MsgClientFactory as well. public class MsgClient { - private final DeliveryRequestType msgRequest; - private final ConfigType config; + private static final Logger log = LoggerFactory.getLogger(MsgClient.class); + private final PhaseInterceptor interceptor; - public MsgClient(DeliveryRequestType msgRequest, ConfigType config, PhaseInterceptor interceptor) { - this.msgRequest = msgRequest; - this.config = config; + private final String address; + + //TODO: make configurable + private final int connectionTimeout = 60; + private final int receiveTimeout = 60; + + @Nullable + private final SSLContext sslContext; + + public MsgClient(PhaseInterceptor interceptor, + String address, + @Nullable SSLContext sslContext) { this.interceptor = interceptor; + this.address = address; + this.sslContext = sslContext; } /** * Send {@code msgRequest} to {@code Config/Server/ZUSEUrlID} and run {@code interceptor} on response. * @return */ - public DeliveryRequestStatusType send() { - var proxy = connect(config); - return proxy.delivery(msgRequest); - } - - private App2ZusePort connect(ConfigType config) { - - var address = config.getServer().getZUSEUrlID(); + public DeliveryRequestStatusType send(DeliveryRequestType msgRequest) { var factory = new JaxWsClientFactoryBean(); + factory.setServiceClass(App2ZusePort.class); factory.setAddress(address); factory.getInInterceptors().add(interceptor); var proxy = new JaxWsProxyFactoryBean(factory).create(); - return (App2ZusePort) proxy; + Client client = ClientProxy.getClient(proxy); + HTTPConduit http = (HTTPConduit) client.getConduit(); + + var httpClientPolicy = new HTTPClientPolicy(); + httpClientPolicy.setConnectionTimeout(connectionTimeout); + httpClientPolicy.setReceiveTimeout(receiveTimeout); + http.setClient(httpClientPolicy); + + if (sslContext != null) { + var tlsParams = new TLSClientParameters(); + tlsParams.setSSLSocketFactory(sslContext.getSocketFactory()); + http.setTlsClientParameters(tlsParams); + log.info("SSLContext initialized. "); + } + + return ((App2ZusePort)proxy).delivery(msgRequest); } + } diff --git a/src/main/java/at/gv/egiz/moazs/msg/MsgClientFactory.java b/src/main/java/at/gv/egiz/moazs/msg/MsgClientFactory.java index c2cf34f..d4cc9f1 100644 --- a/src/main/java/at/gv/egiz/moazs/msg/MsgClientFactory.java +++ b/src/main/java/at/gv/egiz/moazs/msg/MsgClientFactory.java @@ -1,14 +1,57 @@ package at.gv.egiz.moazs.msg; -import at.gv.zustellung.app2mzs.xsd.ConfigType; -import at.gv.zustellung.msg.xsd.DeliveryRequestType; +import at.gv.egiz.moazs.util.FileUtils; +import at.gv.egiz.moazs.util.SSLContextCreator; +import at.gv.zustellung.app2mzs.xsd.ClientType; +import at.gv.zustellung.app2mzs.xsd.KeyStoreType; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import javax.net.ssl.SSLContext; + +import static at.gv.zustellung.app2mzs.xsd.KeyStoreType.keyStoreTypeBuilder; + @Component public class MsgClientFactory { - public MsgClient create(DeliveryRequestType msgRequest, ConfigType config, StoreSOAPBodyBinaryInRepositoryInterceptor storeResponseInterceptor) { - return new MsgClient(msgRequest, config, storeResponseInterceptor); + private final StoreSOAPBodyBinaryInRepositoryInterceptor storeResponseInterceptor; + private final SSLContextCreator sslContextCreator; + private final FileUtils fileUtils; + + + @Autowired + public MsgClientFactory(StoreSOAPBodyBinaryInRepositoryInterceptor storeResponseInterceptor, SSLContextCreator creator, FileUtils fileUtils) { + this.storeResponseInterceptor = storeResponseInterceptor; + this.sslContextCreator = creator; + this.fileUtils = fileUtils; + } + + + /** + * Creates a client that communicates with a msg service. + * + * @param params for the client, such as service url and ssl parameters. + * @return the msg client + */ + //TODO evaluate and honor laxhostnameverification and trustall parameter! + public MsgClient create(ClientType params) { + + SSLContext sslContext = null; + + if (params.getURL().startsWith("https")) { + var keystore = resolveKeyStorePath(params.getSSL().getKeyStore()); + var truststore = resolveKeyStorePath(params.getSSL().getTrustStore()); + sslContext = sslContextCreator.createSSLContext(keystore, truststore); + } + + return new MsgClient(storeResponseInterceptor, params.getURL(), sslContext); + } + + private KeyStoreType resolveKeyStorePath(KeyStoreType store) { + return store == null ? null + : keyStoreTypeBuilder(store) + .withFileName(fileUtils.determinePath(store.getFileName())) + .build(); } } diff --git a/src/main/java/at/gv/egiz/moazs/pipeline/SameThreadDeliveryPipeline.java b/src/main/java/at/gv/egiz/moazs/pipeline/SameThreadDeliveryPipeline.java index ae8286f..20320c4 100644 --- a/src/main/java/at/gv/egiz/moazs/pipeline/SameThreadDeliveryPipeline.java +++ b/src/main/java/at/gv/egiz/moazs/pipeline/SameThreadDeliveryPipeline.java @@ -80,7 +80,7 @@ public class SameThreadDeliveryPipeline implements DeliveryPipeline { exceptionBuilder.withMsgRequest(msgRequest); - var status = msgClientFactory.create(msgRequest, mzsRequest.getConfig(), interceptor).send(); + var status = msgClientFactory.create(mzsRequest.getConfig().getMSGClient()).send(msgRequest); exceptionBuilder.withMsgResult(status); verifySignedStatus(appDeliveryId, exceptionBuilder); @@ -128,7 +128,7 @@ public class SameThreadDeliveryPipeline implements DeliveryPipeline { .withAppDeliveryID(appDeliveryId); if (exception.getMzsRequest() != null) { - errorBuilder.withDeliverySystem(exception.getMzsRequest().getConfig().getServer().getZUSEUrlID()); + errorBuilder.withDeliverySystem(exception.getMzsRequest().getConfig().getMSGClient().getURL()); } if (exception.getTnvzResult() != null && exception.getTnvzResult().getError() != null) { diff --git a/src/main/java/at/gv/egiz/moazs/preprocess/ConfigProfileGenerator.java b/src/main/java/at/gv/egiz/moazs/preprocess/ConfigProfileGenerator.java index be14852..fa1ccd6 100644 --- a/src/main/java/at/gv/egiz/moazs/preprocess/ConfigProfileGenerator.java +++ b/src/main/java/at/gv/egiz/moazs/preprocess/ConfigProfileGenerator.java @@ -1,6 +1,7 @@ package at.gv.egiz.moazs.preprocess; import at.gv.egiz.moazs.MoaZSException; +import at.gv.egiz.moazs.util.StringUtils; import at.gv.zustellung.app2mzs.xsd.ConfigType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -58,9 +59,9 @@ public class ConfigProfileGenerator { var groupedKeys = properties.getPropertyNames() .filter(this::isConfigurationProfileProperty) - .map(this::removePrefix) - .filter(this::hasPrefix) - .collect(groupingBy(this::keepPrefix, mapping(this::removePrefix, toSet()))); + .map(StringUtils::removePrefix) + .filter(StringUtils::hasPrefix) + .collect(groupingBy(StringUtils::keepPrefix, mapping(StringUtils::removePrefix, toSet()))); var profiles = groupedKeys.entrySet().stream() .collect(toUnmodifiableMap(Entry::getKey, this::createConfigFromEnv)); @@ -78,22 +79,10 @@ public class ConfigProfileGenerator { return defaultProfile == null ? profiles : mergeProfiles(profiles, defaultProfile); } - private boolean hasPrefix(String name) { - return name.indexOf('.') != -1; - } - private boolean isConfigurationProfileProperty(String propName) { return propName.startsWith(profilePrefix + "."); } - private String keepPrefix(String name) { - return name.substring(0, name.indexOf('.')); - } - - private String removePrefix(String name) { - return name.substring(name.indexOf('.') + 1); - } - private ConfigType createConfigFromEnv(Entry> entry) { var profile = entry.getKey(); diff --git a/src/main/java/at/gv/egiz/moazs/preprocess/ConfigUtil.java b/src/main/java/at/gv/egiz/moazs/preprocess/ConfigUtil.java index 3fef4bd..1befd1d 100644 --- a/src/main/java/at/gv/egiz/moazs/preprocess/ConfigUtil.java +++ b/src/main/java/at/gv/egiz/moazs/preprocess/ConfigUtil.java @@ -1,20 +1,37 @@ package at.gv.egiz.moazs.preprocess; +import at.gv.egiz.moazs.util.StringUtils; +import at.gv.zustellung.app2mzs.xsd.ClientType; import at.gv.zustellung.app2mzs.xsd.ConfigType; -import at.gv.zustellung.app2mzs.xsd.ServerType; +import at.gv.zustellung.app2mzs.xsd.KeyStoreType; +import at.gv.zustellung.app2mzs.xsd.SSLType; import org.springframework.lang.Nullable; import org.springframework.stereotype.Component; import java.util.Map; +import static at.gv.zustellung.app2mzs.xsd.ClientType.clientTypeBuilder; import static at.gv.zustellung.app2mzs.xsd.ConfigType.configTypeBuilder; -import static at.gv.zustellung.app2mzs.xsd.ServerType.serverTypeBuilder; +import static at.gv.zustellung.app2mzs.xsd.KeyStoreType.keyStoreTypeBuilder; +import static at.gv.zustellung.app2mzs.xsd.SSLType.SSLTypeBuilder; +import static java.util.stream.Collectors.toMap; @Component public class ConfigUtil { - private static final String TNVZ_REQUEST_KEY = "perform-query-person-request"; - private static final String MSG_URL_KEY = "msg.url"; + public static final String TNVZ_REQUEST_KEY = "perform-query-person-request"; + public static final String MSG_CLIENT_KEY = "msg-client"; + public static final String TNVZ_CLIENT_KEY = "tnvz-client"; + public static final String URL_KEY = "url"; + public static final String SSL_KEY = "ssl"; + public static final String TRUST_ALL_KEY = "trust-all"; + public static final String LAX_HOSTNAME_VERIFICATION_KEY = "lax-hostname-verification"; + public static final String KEYSTORE_KEY = "keystore"; + public static final String TRUSTSTORE_KEY = "truststore"; + public static final String FILENAME_KEY = "filename"; + public static final String FILETYPE_KEY = "filetype"; + public static final String PASSWORD_KEY = "password"; + /** * Convert a map into a Config object. @@ -23,16 +40,74 @@ public class ConfigUtil { * @return Config */ public ConfigType convert(Map values) { - var server = serverTypeBuilder() - .withZUSEUrlID(values.get(MSG_URL_KEY)) - .build(); - Boolean performQueryPersonRequest = values.get(TNVZ_REQUEST_KEY) == null ? null : Boolean.getBoolean(values.get(TNVZ_REQUEST_KEY)); + var msgClientParams = filterMapByPrefix(values, MSG_CLIENT_KEY); + ClientType msgClient = msgClientParams.isEmpty() + ? null : buildClient(msgClientParams); + + var tnvzClientParams = filterMapByPrefix(values, TNVZ_CLIENT_KEY); + ClientType tnvzClient = tnvzClientParams.isEmpty() + ? null : buildClient(tnvzClientParams); + return ConfigType.configTypeBuilder() .withPerformQueryPersonRequest(performQueryPersonRequest) - .withServer(server) + .withMSGClient(msgClient) + .withTNVZClient(tnvzClient) + .build(); + } + + private Map filterMapByPrefix(Map values, String prefix) { + return values.entrySet().stream() + .filter(entry -> entry.getKey().startsWith(prefix)) + .collect(toMap(e -> StringUtils.removePrefix(e.getKey()), Map.Entry::getValue)); + } + + + private ClientType buildClient(Map clientParams) { + + var url = clientParams.get(URL_KEY); + + var sslParams = filterMapByPrefix(clientParams, SSL_KEY); + SSLType ssl = sslParams.isEmpty() + ? null : buildSSL(sslParams); + + return clientTypeBuilder().withURL(url).withSSL(ssl).build(); + + } + + private SSLType buildSSL(Map sslParams) { + + var keyStoreParams = filterMapByPrefix(sslParams, KEYSTORE_KEY); + KeyStoreType keyStore = keyStoreParams.isEmpty() + ? null : buildKeyStore(keyStoreParams); + + var trustStoreParams = filterMapByPrefix(sslParams, TRUSTSTORE_KEY); + KeyStoreType trustStore = trustStoreParams.isEmpty() + ? null : buildKeyStore(trustStoreParams); + + var trustAll = sslParams.get(TRUST_ALL_KEY) == null + ? null : Boolean.getBoolean(sslParams.get(TRUST_ALL_KEY)); + + var laxHostNameVerification = sslParams.get(LAX_HOSTNAME_VERIFICATION_KEY) == null + ? null : Boolean.getBoolean(sslParams.get(LAX_HOSTNAME_VERIFICATION_KEY)); + + return SSLTypeBuilder() + .withKeyStore(keyStore) + .withTrustStore(trustStore) + .withTrustAll(trustAll) + .withLaxHostNameVerification(laxHostNameVerification) + .build(); + + } + + private KeyStoreType buildKeyStore(Map params) { + + return keyStoreTypeBuilder() + .withFileName(params.get(FILENAME_KEY)) + .withFileType(params.get(FILETYPE_KEY)) + .withPassword(params.get(PASSWORD_KEY)) .build(); } @@ -47,32 +122,65 @@ public class ConfigUtil { var builder = configTypeBuilder(fallback); - if(primary.getServer() != null) { - builder.withServer(merge(primary.getServer(), fallback.getServer())); + if (primary.isPerformQueryPersonRequest() != null) { + builder.withPerformQueryPersonRequest(primary.isPerformQueryPersonRequest()); } - if(primary.isPerformQueryPersonRequest() != null) { - builder.withPerformQueryPersonRequest(primary.isPerformQueryPersonRequest()); + if (primary.getMSGClient() != null) { + builder.withMSGClient(merge(primary.getMSGClient(), fallback.getMSGClient())); + } + + if (primary.getTNVZClient() != null) { + builder.withMSGClient(merge(primary.getTNVZClient(), fallback.getTNVZClient())); } return builder.build(); + } + + private ClientType merge(ClientType primary, ClientType fallback) { + var builder = clientTypeBuilder(fallback); + + if (primary.getURL() != null) { + builder.withURL(primary.getURL()); + } + + if (primary.getSSL() != null) { + builder.withSSL(merge(primary.getSSL(), fallback.getSSL())); + } + return builder.build(); } - private ServerType merge(ServerType primary, ServerType fallback) { + private SSLType merge(SSLType primary, SSLType fallback) { + var builder = SSLTypeBuilder(fallback); - if (fallback == null) { - return primary; + if (primary.getKeyStore() != null) { + builder.withKeyStore(merge(primary.getKeyStore(), fallback.getKeyStore())); } - var builder = serverTypeBuilder(fallback); + if (primary.getTrustStore() != null) { + builder.withKeyStore(merge(primary.getTrustStore(), fallback.getTrustStore())); + } - if (primary.getX509() != null) builder.withX509 (primary.getX509() ); - if (primary.getZUSEUrlID() != null) builder.withZUSEUrlID(primary.getZUSEUrlID()); + if (primary.isLaxHostNameVerification() != null) { + builder.withLaxHostNameVerification(primary.isLaxHostNameVerification()); + } + if (primary.isTrustAll() != null) { + builder.withLaxHostNameVerification(primary.isTrustAll()); + } return builder.build(); } + private KeyStoreType merge(KeyStoreType primary, KeyStoreType fallback) { + + if (primary.getFileName() != null && primary.getFileType() != null && primary.getPassword() != null) + return primary; + + return fallback; + + } + /** * Check if all mandatory fields are set. * @@ -80,11 +188,42 @@ public class ConfigUtil { * @return true if all mandatory fields are set */ public boolean isComplete(@Nullable ConfigType profile) { - //TODO: add check fo x509 certificate return profile != null && profile.isPerformQueryPersonRequest() != null - && profile.getServer() != null - && profile.getServer().getZUSEUrlID() != null; + && isTVNZClientConfigured(profile.getTNVZClient(), profile.isPerformQueryPersonRequest()) + && isMSGClientConfigured(profile.getMSGClient()); + } + + private boolean isTVNZClientConfigured(ClientType tnvzClient, Boolean isPerformQueryPersonRequest) { + return (tnvzClient != null + && tnvzClient.getURL() != null + && isSSLConfigured(tnvzClient)) + || isPerformQueryPersonRequest == false; } + private boolean isMSGClientConfigured(ClientType msgClient) { + return msgClient != null + && msgClient.getURL() != null + && isSSLConfigured(msgClient); + } + + private boolean isSSLConfigured(ClientType params) { + return (params.getURL().startsWith("https") + && params.getSSL() != null + && params.getSSL().isTrustAll() != null + && params.getSSL().isLaxHostNameVerification() != null + && isKeyStoreConfigured(params.getSSL().getKeyStore()) + && isKeyStoreConfigured(params.getSSL().getTrustStore())) + || !params.getURL().startsWith("https"); + } + + private boolean isKeyStoreConfigured(KeyStoreType keyStore) { + return (keyStore != null + && keyStore.getPassword() != null + && keyStore.getFileType() != null + && keyStore.getFileName() != null) + || keyStore == null; + } + + } diff --git a/src/main/java/at/gv/egiz/moazs/preprocess/DeliveryRequestAugmenter.java b/src/main/java/at/gv/egiz/moazs/preprocess/DeliveryRequestAugmenter.java index 057c3d4..d3891e4 100644 --- a/src/main/java/at/gv/egiz/moazs/preprocess/DeliveryRequestAugmenter.java +++ b/src/main/java/at/gv/egiz/moazs/preprocess/DeliveryRequestAugmenter.java @@ -37,8 +37,8 @@ public class DeliveryRequestAugmenter { public DeliveryRequestType augment(DeliveryRequestType request) { var requestConfig = request.getConfig(); - var profileId = determineProfileIdFrom(requestConfig); - var fallbackConfig = configs.get(profileId); + var fallbackProfileId = determineProfileIdFrom(requestConfig); + var fallbackConfig = configs.get(fallbackProfileId); if (fallbackConfig == null) { @@ -58,7 +58,7 @@ public class DeliveryRequestAugmenter { .withConfig(mergedConfig) .build(); } else { - throw moaZSException(INCOMPLETE_MERGED_CONFIG_ERROR_MESSAGE, profileId); + throw moaZSException(INCOMPLETE_MERGED_CONFIG_ERROR_MESSAGE, fallbackProfileId); } } } diff --git a/src/main/java/at/gv/egiz/moazs/util/FileUtils.java b/src/main/java/at/gv/egiz/moazs/util/FileUtils.java new file mode 100644 index 0000000..7e7723d --- /dev/null +++ b/src/main/java/at/gv/egiz/moazs/util/FileUtils.java @@ -0,0 +1,22 @@ +package at.gv.egiz.moazs.util; + +import org.springframework.stereotype.Component; + +import java.io.File; + +@Component +public class FileUtils { + + /** + * If path is relative, resolve path as classpath resource. If path is absolute, + * leave as-is. + */ + public String determinePath(String abstractPath) { + if (new File(abstractPath).isAbsolute()) { + return abstractPath; + } else { + //java.lang.Class needs relative resources to start with "/" + return this.getClass().getResource("/" + abstractPath).getFile(); + } + } +} diff --git a/src/main/java/at/gv/egiz/moazs/util/SSLContextCreator.java b/src/main/java/at/gv/egiz/moazs/util/SSLContextCreator.java new file mode 100644 index 0000000..b4d66d1 --- /dev/null +++ b/src/main/java/at/gv/egiz/moazs/util/SSLContextCreator.java @@ -0,0 +1,82 @@ +package at.gv.egiz.moazs.util; + +import at.gv.egiz.eaaf.core.impl.utils.KeyStoreUtils; +import at.gv.zustellung.app2mzs.xsd.KeyStoreType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.lang.Nullable; +import org.springframework.stereotype.Component; + +import javax.net.ssl.*; +import java.io.IOException; +import java.security.*; + +import static at.gv.egiz.moazs.MoaZSException.moaZSException; +import static java.lang.String.format; + +@Component +public class SSLContextCreator { + + private static final Logger log = LoggerFactory.getLogger(SSLContextCreator.class); + + /** + * Creates an SSL Context. + * Adapted from at.asitplus.eidas.specific.modules.authmodule_eIDASv2.szr.SZRClient + * + * @param keystore (if null, use no key store) + * @param truststore (if null, use default trust store) + * @throws at.gv.egiz.moazs.MoaZSException + */ + public SSLContext createSSLContext(@Nullable KeyStoreType keystore, @Nullable KeyStoreType truststore) { + try { + SSLContext context = SSLContext.getInstance("TLS"); + KeyManager[] keyManager = initKeyManager(keystore); + TrustManager[] trustManager = initTrustManager(truststore); + context.init(keyManager, trustManager, new SecureRandom()); + return context; + } catch (NoSuchAlgorithmException | KeyManagementException e) { + throw moaZSException("SSLContext initialization FAILED.", e); + } + } + + private KeyManager[] initKeyManager(KeyStoreType keystore) { + if (keystore == null) { + log.trace("No keystore path provided. NOT using SSL client authentication. "); + return null; + } else { + log.trace("Find keystore path: {}. Injecting SSL client certificate... ", keystore.getFileName()); + try { + KeyStore keyStore = KeyStoreUtils.loadKeyStore( + keystore.getFileType(), keystore.getFileName(), keystore.getPassword()); + KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); + kmf.init(keyStore, keystore.getPassword().toCharArray()); + log.trace("SSL client certificate injected."); + return kmf.getKeyManagers(); + } catch (IOException | GeneralSecurityException e) { + throw moaZSException(format("Can NOT load SSL client certificate from path: %s.", + keystore.getFileName()), e); + } + } + } + + private TrustManager[] initTrustManager(KeyStoreType truststore) { + if (truststore == null) { + log.trace("Using default truststore. "); + return null; + } else { + log.trace("Find truststore path: {}. Injecting SSL truststore... ", truststore.getFileName()); + try { + KeyStore trustStore = KeyStoreUtils.loadKeyStore( + truststore.getFileType(), truststore.getFileName(), truststore.getPassword()); + TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509"); + tmf.init(trustStore); + log.trace("SSL TrustStore injected to client. "); + return tmf.getTrustManagers(); + } catch (GeneralSecurityException | IOException e) { + throw moaZSException(format("Can NOT open SSL TrustStore from path: %s.", + truststore.getFileName()), e); + } + } + } + +} diff --git a/src/main/java/at/gv/egiz/moazs/util/StringUtils.java b/src/main/java/at/gv/egiz/moazs/util/StringUtils.java new file mode 100644 index 0000000..fd40f6e --- /dev/null +++ b/src/main/java/at/gv/egiz/moazs/util/StringUtils.java @@ -0,0 +1,17 @@ +package at.gv.egiz.moazs.util; + +public class StringUtils { + + public static boolean hasPrefix(String name) { + return name.indexOf('.') != -1; + } + + public static String keepPrefix(String name) { + return name.substring(0, name.indexOf('.')); + } + + public static String removePrefix(String name) { + return name.substring(name.indexOf('.') + 1); + } + +} diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 9ce1158..a0040ca 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -13,32 +13,34 @@ spring: # Order: DeliveryRequest/Config > [chosen-profile] > default delivery-request-configuration-profiles: default: + + perform-query-person-request: false + ## All parameters for MSG client. - msg: + msg-client: - ## How to reach url: http://localhost:8081/services/DeliveryRequest + ssl: + ## Boolean; if true, app will trust all server certificates; + ## if false, server certificate needs to be in truststore. + trust-all: false + + ## Boolean; if true, app ignores mismatches between server's host name and + ## Certificate's common name / alternative subject name. + lax-hostname-verification: false + ## Parameters for ssl client auth keystore: ## Absolute path to file - filename: + filename: ssl/client.jks ## Password to unlock key store. password: 1233 ## JKS or PKCS12 type: JKS - ## Boolean; if true, app will trust all server certificates; - ## if false, server certificate needs to be in truststore. - trustall: false - ## Boolean; if true, app ignores mismatches between server's host name and - ## Certificate's common name / alternative subject name. - laxhostnameverification: false - - - perform-query-person-request: false app-profile-1: msg: @@ -49,9 +51,6 @@ delivery-request-configuration-profiles: msg: url: https://msg-url2.com -key-store-profiles: - msg-key-store: - ## If set to false, moa zs ignores an incomplete default DeliveryRequest-configuration ## profile and continues startup. See 'delivery-request-configuration-profiles'. ## Default value: true diff --git a/src/main/resources/mzs/app2mzs.xsd b/src/main/resources/mzs/app2mzs.xsd index 05a9ea4..956cd31 100644 --- a/src/main/resources/mzs/app2mzs.xsd +++ b/src/main/resources/mzs/app2mzs.xsd @@ -81,19 +81,39 @@ - + + - - + + + - - + + + + + + + + + + + + + + + + + + + + - + @@ -179,7 +199,7 @@ - + -- cgit v1.2.3