/* * 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.clients.szr; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.annotation.PostConstruct; import javax.xml.XMLConstants; 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 org.apache.commons.lang3.StringUtils; import org.apache.xpath.XPathAPI; import org.springframework.stereotype.Service; import org.w3c.dom.Document; import org.w3c.dom.Element; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import at.asitplus.eidas.specific.modules.auth.eidas.v2.Constants; import at.asitplus.eidas.specific.modules.auth.eidas.v2.clients.AbstractSoapClient; import at.asitplus.eidas.specific.modules.auth.eidas.v2.clients.AbstractSoapClient.HttpClientConfig.HttpClientConfigBuilder; import at.asitplus.eidas.specific.modules.auth.eidas.v2.dao.MatchedPersonResult; import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.SzrCommunicationException; import at.gv.e_government.reference.namespace.persondata._20020228.IdentificationType; import at.gv.e_government.reference.namespace.persondata._20020228.PersonNameType; import at.gv.e_government.reference.namespace.persondata._20020228.PhysicalPersonType; import at.gv.egiz.eaaf.core.api.data.EaafConstants; import at.gv.egiz.eaaf.core.api.data.PvpAttributeDefinitions; import at.gv.egiz.eaaf.core.api.data.XmlNamespaceConstants; import at.gv.egiz.eaaf.core.exceptions.EaafConfigurationException; import at.gv.egiz.eaaf.core.impl.utils.DomUtils; import lombok.extern.slf4j.Slf4j; import szrservices.GetBPK; import szrservices.GetBPKResponse; import szrservices.GetIdentityLinkEidas; import szrservices.GetIdentityLinkEidasResponse; import szrservices.IdentityLinkType; import szrservices.JwsHeaderParam; import szrservices.ObjectFactory; import szrservices.PersonInfoType; import szrservices.SZR; import szrservices.SZRException_Exception; import szrservices.SignContent; import szrservices.SignContentEntry; import szrservices.SignContentResponseType; @Slf4j @Service("SZRClientForeIDAS") public class SzrClient extends AbstractSoapClient { private static final String CLIENT_DEFAULT = "DefaultClient"; private static final String CLIENT_RAW = "RawClient"; private static final String ATTR_NAME_VSZ = "urn:eidgvat:attributes.vsz.value"; private static final String ATTR_NAME_PUBKEYS = "urn:eidgvat:attributes.user.pubkeys"; private static final String ATTR_NAME_STATUS = "urn:eidgvat:attributes.eid.status"; private static final String KEY_BC_BIND = "bcBindReq"; private static final String JOSE_HEADER_USERCERTPINNING_TYPE = "urn:at.gv.eid:bindtype"; private static final String JOSE_HEADER_USERCERTPINNING_EIDASBIND = "urn:at.gv.eid:eidasBind"; public static final String ATTR_NAME_MDS = "urn:eidgvat:mds"; // client for anything, without identitylink private SZR szr = null; // RAW client is needed for identitylink private Dispatch dispatch = null; final ObjectMapper mapper = new ObjectMapper(); /** * Get IdentityLink of a person. * * * @param matchedPersonData eID information of an already matched person. * @return IdentityLink * @throws SzrCommunicationException In case of a SZR error */ public IdentityLinkType getIdentityLinkInRawMode(MatchedPersonResult matchedPersonData) throws SzrCommunicationException { try { final GetIdentityLinkEidas getIdl = new GetIdentityLinkEidas(); getIdl.setPersonInfo(generateSzrRequest(matchedPersonData)); return getIdentityLinkGeneric(getIdl); } catch (final Exception e) { log.warn("SZR communication FAILED for operation: {} Reason: {}", "GetIdentityLinkEidas", e.getMessage(), e); throw new SzrCommunicationException("ernb.02", new Object[]{e.getMessage()}, e); } } /** * Get bPK of person. * * * @param matchedPersonData Minimum dataset of person * @param target requested bPK target * @param vkz Verfahrenskennzeichen * @return bPK for this person * @throws SzrCommunicationException In case of a SZR error */ public List getBpk(MatchedPersonResult matchedPersonData, String target, String vkz) throws SzrCommunicationException { try { final GetBPK parameters = new GetBPK(); parameters.setPersonInfo(generateSzrRequest(matchedPersonData)); parameters.getBereichsKennung().add(target); parameters.setVKZ(vkz); final GetBPKResponse result = this.szr.getBPK(parameters); return result.getGetBPKReturn(); } catch (final SZRException_Exception e) { log.warn("SZR communication FAILED for operation: {} Reason: {}", "GetBPK", e.getMessage(), e); throw new SzrCommunicationException("ernb.02", new Object[]{e.getMessage()}, e); } } /** * Request a encrypted baseId from SZR. * * @param matchedPersonData eID information of an already matched person. * @return encrypted baseId * @throws SzrCommunicationException In case of a SZR error */ public String getEncryptedStammzahl(MatchedPersonResult matchedPersonData) throws SzrCommunicationException { final String resp; try { resp = this.szr.getStammzahlEncrypted(generateSzrRequest(matchedPersonData), false); } catch (SZRException_Exception e) { throw new SzrCommunicationException("ernb.02", new Object[]{e.getMessage()}, e); } if (StringUtils.isEmpty(resp)) { throw new SzrCommunicationException("ernb.01", new Object[]{"Stammzahl response empty"}); // TODO error handling } return resp; } /** * Sign an eidasBind data-structure that combines vsz with user's pubKey and E-ID status. * * @param vsz encrypted baseId * @param bindingPubKey binding PublicKey as PKCS1# (ASN.1) container * @param eidStatus Status of the E-ID * @param eidData eID information that was used for ERnP registration * @return bPK for this person * @throws SzrCommunicationException In case of a SZR error */ public String getEidasBind(final String vsz, final String bindingPubKey, final String eidStatus, MatchedPersonResult eidData)throws SzrCommunicationException { final Map eidsaBindMap = new HashMap<>(); eidsaBindMap.put(ATTR_NAME_VSZ, vsz); eidsaBindMap.put(ATTR_NAME_STATUS, eidStatus); eidsaBindMap.put(ATTR_NAME_PUBKEYS, Collections.singletonList(bindingPubKey)); eidsaBindMap.put(PvpAttributeDefinitions.EID_ISSUING_NATION_NAME, eidData.getCountryCode()); injectMdsIfAvailableAndActive(eidsaBindMap, eidData); try { final String serializedEidasBind = mapper.writeValueAsString(eidsaBindMap); final SignContent req = new SignContent(); final SignContentEntry eidasBindInfo = new SignContentEntry(); eidasBindInfo.setKey(KEY_BC_BIND); eidasBindInfo.setValue(serializedEidasBind); req.getIn().add(eidasBindInfo); req.setAppendCert(false); final JwsHeaderParam eidasBindJoseHeader = new JwsHeaderParam(); eidasBindJoseHeader.setKey(JOSE_HEADER_USERCERTPINNING_TYPE); eidasBindJoseHeader.setValue(JOSE_HEADER_USERCERTPINNING_EIDASBIND); req.getJWSHeaderParam().add(eidasBindJoseHeader); log.trace("Requesting SZR to sign bcBind datastructure ... "); final SignContentResponseType resp = szr.signContent(req.isAppendCert(), req.getJWSHeaderParam(), req.getIn()); log.trace("Receive SZR response on bcBind siging operation "); if (resp == null || resp.getOut() == null || resp.getOut().isEmpty() || StringUtils.isEmpty(resp.getOut().get(0).getValue())) { throw new SzrCommunicationException("ernb.01", new Object[]{"BcBind response empty"}); } return resp.getOut().get(0).getValue(); } catch (final JsonProcessingException | SZRException_Exception e) { log.warn("SZR communication FAILED for operation: {} Reason: {}", "SignContent", e.getMessage(), e); throw new SzrCommunicationException("ernb.02", new Object[]{e.getMessage()}, e); } } private PersonInfoType generateSzrRequest(MatchedPersonResult matchedPersonData) { log.trace("Starting connecting SZR Gateway"); final PersonInfoType personInfo = new PersonInfoType(); final PersonNameType personName = new PersonNameType(); final PhysicalPersonType naturalPerson = new PhysicalPersonType(); IdentificationType bpk = new IdentificationType(); naturalPerson.setName(personName); personInfo.setPerson(naturalPerson); naturalPerson.setIdentification(bpk); // person information personName.setFamilyName(matchedPersonData.getFamilyName()); personName.setGivenName(matchedPersonData.getGivenName()); naturalPerson.setDateOfBirth(matchedPersonData.getDateOfBirth()); bpk.setValue(matchedPersonData.getBpk()); bpk.setType(EaafConstants.URN_PREFIX_CDID + "ZP"); return personInfo; } private IdentityLinkType getIdentityLinkGeneric(GetIdentityLinkEidas getIdl) throws Exception { 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, StandardCharsets.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; } @PostConstruct private void initialize() throws EaafConfigurationException { log.info("Starting SZR-Client initialization .... "); final URL url = SzrClient.class.getResource("/wsdl/szr_client/SZR_v4.0.wsdl"); final boolean useTestSzr = basicConfig.getBasicConfigurationBoolean( Constants.CONIG_PROPS_EIDAS_SZRCLIENT_USETESTSERVICE, true); SzrService szrService; QName qname; String szrUrl; 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, szrUrl, basicConfig.getBasicConfigurationBoolean(Constants.CONIG_PROPS_EIDAS_SZRCLIENT_DEBUG_TRACEMESSAGES, false)); injectBindingProvider(dispatch, CLIENT_RAW, szrUrl, basicConfig.getBasicConfigurationBoolean(Constants.CONIG_PROPS_EIDAS_SZRCLIENT_DEBUG_TRACEMESSAGES, false)); // inject http parameters and SSL context log.debug("Inject HTTP client settings ... "); HttpClientConfigBuilder httpClientBuilder = HttpClientConfig.builder() .clientName("SZR Client") .clientUrl(szrUrl) .connectionTimeout(basicConfig.getBasicConfiguration( Constants.CONIG_PROPS_EIDAS_SZRCLIENT_TIMEOUT_CONNECTION, Constants.HTTP_CLIENT_DEFAULT_TIMEOUT_CONNECTION)) .responseTimeout(basicConfig.getBasicConfiguration( Constants.CONIG_PROPS_EIDAS_SZRCLIENT_TIMEOUT_RESPONSE, Constants.HTTP_CLIENT_DEFAULT_TIMEOUT_RESPONSE)) .keyStoreConfig(buildKeyStoreConfiguration( Constants.CONIG_PROPS_EIDAS_SZRCLIENT_SSL_KEYSTORE_TYPE, Constants.CONIG_PROPS_EIDAS_SZRCLIENT_SSL_KEYSTORE_PATH, Constants.CONIG_PROPS_EIDAS_SZRCLIENT_SSL_KEYSTORE_PASSWORD, Constants.CONIG_PROPS_EIDAS_SZRCLIENT_SSL_KEYSTORE_NAME, "SZR SSL Client-Authentication KeyStore")) .keyAlias(basicConfig.getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_SZRCLIENT_SSL_KEYS_ALIAS)) .keyPassword(basicConfig.getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_SZRCLIENT_SSL_KEY_PASSWORD)) .trustAll(false) .trustStoreConfig(buildKeyStoreConfiguration( Constants.CONIG_PROPS_EIDAS_SZRCLIENT_SSL_TRUSTSTORE_TYPE, Constants.CONIG_PROPS_EIDAS_SZRCLIENT_SSL_TRUSTSTORE_PATH, Constants.CONIG_PROPS_EIDAS_SZRCLIENT_SSL_TRUSTSTORE_PASSWORD, Constants.CONIG_PROPS_EIDAS_SZRCLIENT_SSL_TRUSTSTORE_NAME, "SZR SSL Client-Authentication KeyStore")); injectHttpClient(szr, httpClientBuilder.clientType(CLIENT_DEFAULT).build()); injectHttpClient(dispatch, httpClientBuilder.clientType(CLIENT_RAW).build()); log.info("SZR-Client initialization successfull"); } private void injectMdsIfAvailableAndActive(Map eidsaBindMap, MatchedPersonResult eidData) { if (basicConfig.getBasicConfigurationBoolean( Constants.CONIG_PROPS_EIDAS_SZRCLIENT_SET_MDS_TO_EIDASBIND, false)) { log.info("Injecting MDS into eidasBind ... "); final Map mds = new HashMap<>(); mds.put(PvpAttributeDefinitions.PRINCIPAL_NAME_NAME, eidData.getFamilyName()); mds.put(PvpAttributeDefinitions.GIVEN_NAME_NAME, eidData.getGivenName()); mds.put(PvpAttributeDefinitions.BIRTHDATE_NAME, eidData.getDateOfBirth()); eidsaBindMap.put(ATTR_NAME_MDS, mds); } } private byte[] sourceToByteArray(Source result) throws TransformerException { final TransformerFactory factory = TransformerFactory.newInstance(); factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); 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(); } }