/* * 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(); } }