From e443168b481bb88fecbad73084147e7e8c882908 Mon Sep 17 00:00:00 2001 From: Thomas Lenz Date: Tue, 10 Dec 2019 07:39:27 +0100 Subject: refactoring to new EGIZ code requirements --- .../modules/auth/eidas/v2/szr/SzrClient.java | 408 +++++++++++++++++++++ 1 file changed, 408 insertions(+) create mode 100644 eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/szr/SzrClient.java (limited to 'eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/szr/SzrClient.java') diff --git a/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/szr/SzrClient.java b/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/szr/SzrClient.java new file mode 100644 index 00000000..763d8dab --- /dev/null +++ b/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/szr/SzrClient.java @@ -0,0 +1,408 @@ +/* + * Copyright 2018 A-SIT Plus GmbH + * AT-specific eIDAS Connector has been developed in a cooperation between EGIZ, + * A-SIT Plus GmbH, A-SIT, and Graz University of Technology. + * + * Licensed under the EUPL, Version 1.2 or - as soon they will be approved by + * the European Commission - subsequent versions of the EUPL (the "License"); + * You may not use this work except in compliance with the License. + * You may obtain a copy of the License at: + * https://joinup.ec.europa.eu/news/understanding-eupl-v12 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This product combines work with different licenses. See the "NOTICE" text + * file for details on the various modules and licenses. + * The "NOTICE" text file is part of the distribution. Any derivative works + * that you distribute must include a readable copy of the "NOTICE" text file. + */ + +package at.asitplus.eidas.specific.modules.auth.eidas.v2.szr; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.net.URL; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.UnrecoverableKeyException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import javax.annotation.PostConstruct; +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.Marshaller; +import javax.xml.namespace.QName; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; +import javax.xml.ws.BindingProvider; +import javax.xml.ws.Dispatch; +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.xpath.XPathAPI; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import at.asitplus.eidas.specific.modules.auth.eidas.v2.Constants; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.SzrCommunicationException; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.utils.LoggingHandler; +import at.gv.egiz.eaaf.core.api.data.XmlNamespaceConstants; +import at.gv.egiz.eaaf.core.api.idp.IConfiguration; +import at.gv.egiz.eaaf.core.impl.utils.DomUtils; +import at.gv.egiz.eaaf.core.impl.utils.FileUtils; +import at.gv.egiz.eaaf.core.impl.utils.KeyStoreUtils; +import szrservices.GetBPK; +import szrservices.GetBPKResponse; +import szrservices.GetIdentityLinkEidas; +import szrservices.GetIdentityLinkEidasResponse; +import szrservices.IdentityLinkType; +import szrservices.ObjectFactory; +import szrservices.PersonInfoType; +import szrservices.SZR; +import szrservices.SZRException_Exception; + +@Service("SZRClientForeIDAS") +public class SzrClient { + private static final Logger log = LoggerFactory.getLogger(SzrClient.class); + + private static final String CLIENT_DEFAULT = "DefaultClient"; + private static final String CLIENT_RAW = "RawClient"; + + @Autowired + private IConfiguration basicConfig; + + // client for anything, without identitylink + private SZR szr = null; + + // RAW client is needed for identitylink + private Dispatch dispatch = null; + + private SzrService szrService = null; + private String szrUrl = null; + private QName qname = null; + + /** + * Get IdentityLink of a person. + * + * @param personInfo Person identification information + * @return IdentityLink + * @throws SzrCommunicationException In case of a SZR error + */ + public IdentityLinkType getIdentityLinkInRawMode(PersonInfoType personInfo) + throws SzrCommunicationException { + try { + final GetIdentityLinkEidas getIdl = new GetIdentityLinkEidas(); + getIdl.setPersonInfo(personInfo); + + final JAXBContext jaxbContext = JAXBContext.newInstance(ObjectFactory.class); + final Marshaller jaxbMarshaller = jaxbContext.createMarshaller(); + + final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + jaxbMarshaller.marshal(getIdl, outputStream); + outputStream.flush(); + + final Source source = new StreamSource(new ByteArrayInputStream(outputStream.toByteArray())); + outputStream.close(); + + log.trace("Requesting SZR ... "); + final Source response = dispatch.invoke(source); + log.trace("Receive RAW response from SZR"); + + final byte[] szrResponse = sourceToByteArray(response); + final GetIdentityLinkEidasResponse jaxbElement = (GetIdentityLinkEidasResponse) jaxbContext + .createUnmarshaller().unmarshal(new ByteArrayInputStream(szrResponse)); + + // build response + log.trace(new String(szrResponse, "UTF-8")); + + // ok, we have success + final Document doc = DomUtils.parseDocument( + new ByteArrayInputStream(szrResponse), + true, + XmlNamespaceConstants.ALL_SCHEMA_LOCATIONS + " " + Constants.SZR_SCHEMA_LOCATIONS, + null, null); + final String xpathExpression = "//saml:Assertion"; + final Element nsNode = doc.createElementNS("urn:oasis:names:tc:SAML:1.0:assertion", "saml:NSNode"); + + log.trace("Selecting signed doc " + xpathExpression); + final Element documentNode = (Element) XPathAPI.selectSingleNode(doc, + xpathExpression, nsNode); + log.trace("Signed document: " + DomUtils.serializeNode(documentNode)); + + final IdentityLinkType idl = new IdentityLinkType(); + idl.setAssertion(documentNode); + idl.setPersonInfo(jaxbElement.getGetIdentityLinkReturn().getPersonInfo()); + + return idl; + + } catch (final Exception e) { + log.warn("SZR communication FAILED. Reason: " + e.getMessage(), e); + throw new SzrCommunicationException("ernb.02", new Object[] { e.getMessage() }, e); + + } + + } + + /** + * Get bPK of person. + * + * @param personInfo Person identification information + * @param target requested bPK target + * @param vkz Verfahrenskennzeichen + * @return bPK for this person + * @throws SzrCommunicationException In case of a SZR error + */ + public String getBpk(PersonInfoType personInfo, String target, String vkz) + throws SzrCommunicationException { + try { + final GetBPK parameters = new GetBPK(); + parameters.setPersonInfo(personInfo); + parameters.setBereichsKennung(target); + parameters.setVKZ(vkz); + final GetBPKResponse result = this.szr.getBPK(parameters); + + return result.getGetBPKReturn(); + + } catch (final SZRException_Exception e) { + log.warn("SZR communication FAILED. Reason: " + e.getMessage(), e); + throw new SzrCommunicationException("ernb.02", new Object[] { e.getMessage() }, e); + + } + + } + + @PostConstruct + private void initialize() { + log.info("Starting SZR-Client initialization .... "); + final URL url = SzrClient.class.getResource("/szr_client/SZR-1.1.WSDL"); + + final boolean useTestSzr = basicConfig.getBasicConfigurationBoolean( + Constants.CONIG_PROPS_EIDAS_SZRCLIENT_USETESTSERVICE, + true); + + if (useTestSzr) { + log.debug("Initializing SZR test environment configuration."); + qname = SzrService.SZRTestumgebung; + szrService = new SzrService(url, new QName("urn:SZRServices", "SZRService")); + szr = szrService.getSzrTestumgebung(); + szrUrl = basicConfig.getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_SZRCLIENT_ENDPOINT_TEST); + + } else { + log.debug("Initializing SZR productive configuration."); + qname = SzrService.SZRProduktionsumgebung; + szrService = new SzrService(url, new QName("urn:SZRServices", "SZRService")); + szr = szrService.getSzrProduktionsumgebung(); + szrUrl = basicConfig.getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_SZRCLIENT_ENDPOINT_PROD); + + } + + // create raw client; + dispatch = szrService.createDispatch(qname, Source.class, javax.xml.ws.Service.Mode.PAYLOAD); + + if (StringUtils.isEmpty(szrUrl)) { + log.error("No SZR service-URL found. SZR-Client initalisiation failed."); + throw new RuntimeException("No SZR service URL found. SZR-Client initalisiation failed."); + + } + + // check if Clients can be initialized + if (szr == null) { + log.error("SZR " + CLIENT_DEFAULT + " is 'NULL'. Something goes wrong"); + throw new RuntimeException("SZR " + CLIENT_DEFAULT + " is 'NULL'. Something goes wrong"); + + } + if (dispatch == null) { + log.error("SZR " + CLIENT_RAW + " is 'NULL'. Something goes wrong"); + throw new RuntimeException("SZR " + CLIENT_RAW + " is 'NULL'. Something goes wrong"); + + } + + // inject handler + log.info("Use SZR service-URL: " + szrUrl); + injectBindingProvider((BindingProvider) szr, CLIENT_DEFAULT); + injectBindingProvider(dispatch, CLIENT_RAW); + + // inject http parameters and SSL context + log.debug("Inject HTTP client settings ... "); + injectHttpClient(szr, CLIENT_DEFAULT); + injectHttpClient(dispatch, CLIENT_RAW); + + log.info("SZR-Client initialization successfull"); + } + + private void injectHttpClient(Object raw, String clientType) { + // extract client from implementation + Client client = null; + 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(basicConfig.getBasicConfiguration( + Constants.CONIG_PROPS_EIDAS_SZRCLIENT_TIMEOUT_CONNECTION, + Constants.HTTP_CLIENT_DEFAULT_TIMEOUT_CONNECTION)) * 1000); + httpClientPolicy.setReceiveTimeout( + Integer.parseInt(basicConfig.getBasicConfiguration( + Constants.CONIG_PROPS_EIDAS_SZRCLIENT_TIMEOUT_RESPONSE, + Constants.HTTP_CLIENT_DEFAULT_TIMEOUT_RESPONSE)) * 1000); + http.setClient(httpClientPolicy); + + // inject SSL context in case of https + if (szrUrl.toLowerCase().startsWith("https")) { + log.debug("Adding SSLContext to client: " + clientType + " ... "); + final TLSClientParameters tlsParams = new TLSClientParameters(); + tlsParams.setSSLSocketFactory(createSslContext(clientType).getSocketFactory()); + http.setTlsClientParameters(tlsParams); + log.info("SSLContext initialized for client: " + clientType); + + } + + } + + private void injectBindingProvider(BindingProvider bindingProvider, String clientType) { + 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<>(); + bindingProvider.getBinding().setHandlerChain(handlerList); + + } + + // add logging handler to trace messages if required + if (basicConfig.getBasicConfigurationBoolean( + Constants.CONIG_PROPS_EIDAS_SZRCLIENT_DEBUG_TRACEMESSAGES, + false)) { + final LoggingHandler loggingHandler = new LoggingHandler(); + handlerList.add(loggingHandler); + + } + bindingProvider.getBinding().setHandlerChain(handlerList); + } + + private SSLContext createSslContext(String clientType) { + try { + final SSLContext context = SSLContext.getInstance("TLS"); + + // initialize key-mangager for SSL client-authentication + KeyManager[] keyManager = null; + final String keyStorePath = basicConfig.getBasicConfiguration( + Constants.CONIG_PROPS_EIDAS_SZRCLIENT_SSL_KEYSTORE_PATH); + final String keyStorePassword = basicConfig.getBasicConfiguration( + Constants.CONIG_PROPS_EIDAS_SZRCLIENT_SSL_KEYSTORE_PASSWORD); + if (StringUtils.isNotEmpty(keyStorePath)) { + log.trace("Find keyStore path: " + keyStorePath + " Injecting SSL client certificate ... "); + try { + final KeyStore keyStore = KeyStoreUtils.loadKeyStore( + FileUtils.makeAbsoluteUrl(keyStorePath, basicConfig.getConfigurationRootDirectory()), + keyStorePassword); + + final KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); + kmf.init(keyStore, keyStorePassword.toCharArray()); + keyManager = kmf.getKeyManagers(); + log.debug("SSL client certificate injected to client: " + clientType); + + } catch (KeyStoreException | IOException | UnrecoverableKeyException e) { + log.error("Can NOT load SSL client certificate from path: " + keyStorePath); + throw new RuntimeException("Can NOT load SSL client certificate from path: " + keyStorePath, e); + + } + } else { + log.debug( + "No KeyStore for SSL Client Auth. found. Initializing SSLContext without authentication ... "); + + } + + // initialize SSL TrustStore + TrustManager[] trustManager = null; + final String trustStorePath = basicConfig.getBasicConfiguration( + Constants.CONIG_PROPS_EIDAS_SZRCLIENT_SSL_TRUSTSTORE_PATH); + final String trustStorePassword = basicConfig.getBasicConfiguration( + Constants.CONIG_PROPS_EIDAS_SZRCLIENT_SSL_TRUSTSTORE_PASSWORD); + if (StringUtils.isNotEmpty(trustStorePath)) { + log.trace("Find trustStore path: " + trustStorePath + " Injecting SSL TrustStore ... "); + try { + final KeyStore trustStore = KeyStoreUtils.loadKeyStore( + FileUtils.makeAbsoluteUrl(trustStorePath, basicConfig.getConfigurationRootDirectory()), + trustStorePassword); + + final TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509"); + tmf.init(trustStore); + trustManager = tmf.getTrustManagers(); + log.debug("SSL TrustStore injected to client: " + clientType); + + } catch (KeyStoreException | IOException e) { + log.error("Can NOT open SSL TrustStore from path: " + trustStorePath); + throw new RuntimeException("Can NOT open SSL TrustStore from path: " + trustStorePath, e); + + } + + } else { + log.debug("No custom SSL TrustStore found. Initializing SSLContext with JVM default truststore ... "); + + } + + context.init(keyManager, trustManager, new SecureRandom()); + return context; + + } catch (NoSuchAlgorithmException | KeyManagementException e) { + log.error("SSLContext initialization FAILED.", e); + throw new RuntimeException("SSLContext initialization FAILED.", e); + + } + + } + + private byte[] sourceToByteArray(Source result) throws TransformerException { + final TransformerFactory factory = TransformerFactory.newInstance(); + final Transformer transformer = factory.newTransformer(); + transformer.setOutputProperty("omit-xml-declaration", "yes"); + transformer.setOutputProperty("method", "xml"); + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + final StreamResult streamResult = new StreamResult(); + streamResult.setOutputStream(out); + transformer.transform(result, streamResult); + return out.toByteArray(); + } + +} -- cgit v1.2.3