From f696b4085fd10b3b9d627437f439b222903e13e1 Mon Sep 17 00:00:00 2001 From: lalber Date: Tue, 20 Oct 2020 07:47:44 +0200 Subject: stuck on how to test --- .../modules/auth/eidas/v2/szr/SzrClient.java | 119 ++++++-- .../eidas/v2/tasks/CreateIdentityLinkTask.java | 141 ++++++---- .../modules/auth/eidas/v2/utils/JoseUtils.java | 305 +++++++++++++++++++++ 3 files changed, 492 insertions(+), 73 deletions(-) create mode 100644 eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/utils/JoseUtils.java (limited to 'eidas_modules/authmodule-eIDAS-v2/src/main/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 index 763d8dab..5a551649 100644 --- 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 @@ -33,9 +33,7 @@ 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 java.util.*; import javax.annotation.PostConstruct; import javax.net.ssl.KeyManager; @@ -56,7 +54,10 @@ import javax.xml.ws.BindingProvider; import javax.xml.ws.Dispatch; import javax.xml.ws.handler.Handler; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.time.StopWatch; import org.apache.cxf.configuration.jsse.TLSClientParameters; import org.apache.cxf.endpoint.Client; import org.apache.cxf.frontend.ClientProxy; @@ -79,15 +80,7 @@ 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; +import szrservices.*; @Service("SZRClientForeIDAS") public class SzrClient { @@ -109,9 +102,11 @@ public class SzrClient { private String szrUrl = null; private QName qname = null; + final ObjectMapper mapper = new ObjectMapper(); + /** * Get IdentityLink of a person. - * + * * @param personInfo Person identification information * @return IdentityLink * @throws SzrCommunicationException In case of a SZR error @@ -165,7 +160,7 @@ public class SzrClient { } catch (final Exception e) { log.warn("SZR communication FAILED. Reason: " + e.getMessage(), e); - throw new SzrCommunicationException("ernb.02", new Object[] { e.getMessage() }, e); + throw new SzrCommunicationException("ernb.02", new Object[]{e.getMessage()}, e); } @@ -173,19 +168,19 @@ public class SzrClient { /** * Get bPK of person. - * + * * @param personInfo Person identification information - * @param target requested bPK target - * @param vkz Verfahrenskennzeichen + * @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) + public List getBpk(PersonInfoType personInfo, String target, String vkz) throws SzrCommunicationException { try { final GetBPK parameters = new GetBPK(); parameters.setPersonInfo(personInfo); - parameters.setBereichsKennung(target); + parameters.getBereichsKennung().add(target); parameters.setVKZ(vkz); final GetBPKResponse result = this.szr.getBPK(parameters); @@ -193,16 +188,98 @@ public class SzrClient { } catch (final SZRException_Exception e) { log.warn("SZR communication FAILED. Reason: " + e.getMessage(), e); - throw new SzrCommunicationException("ernb.02", new Object[] { e.getMessage() }, e); + throw new SzrCommunicationException("ernb.02", new Object[]{e.getMessage()}, e); } } + /** + * Request a encryped baseId from SRZ. + * + * @param personInfo Minimum dataset of person + * @return encrypted baseId + * @throws SzrCommunicationException In case of a SZR error + */ + public String getEncryptedStammzahl(final PersonInfoType personInfo) + throws SzrCommunicationException { + + final String resp; + try { + resp = this.szr.getStammzahlEncrypted(personInfo, false); + } catch (SZRException_Exception e) { + throw new SzrCommunicationException("ernb.02", new Object[]{e.getMessage()}, e); + } + + if (resp == null || StringUtils.isEmpty(resp)) { + throw new SzrCommunicationException("ernb.01", new Object[]{"Stammzahl response empty"}); // TODO error handling + } + + return resp; + + } + + + /** + * Signs content. + * + * @param vsz ? TODO + * @param bindingPubKey binding PublikKey as PKCS1# (ASN.1) container + * @param eidStatus Status of the E-ID + * @return bPK for this person + * @throws SzrCommunicationException In case of a SZR error + */ + public String getBcBind(final String vsz, final String bindingPubKey, final String eidStatus) + throws SzrCommunicationException { + + final String ATTR_NAME_VSZ = "urn:eidgvat:attributes.vsz.value"; + final String ATTR_NAME_PUBKEYS = "urn:eidgvat:attributes.user.pubkeys"; + final String ATTR_NAME_STATUS = "urn:eidgvat:attributes.eid.status"; + final String KEY_BC_BIND = "bcBindReq"; + final String JOSE_HEADER_USERCERTPINNING_TYPE = "urn:at.gv.eid:bindtype"; + final String JOSE_HEADER_USERCERTPINNING_EIDASBIND = "urn:at.gv.eid:eidasBind"; + + final Map bcBindMap = new HashMap<>(); + bcBindMap.put(ATTR_NAME_VSZ, vsz); + bcBindMap.put(ATTR_NAME_STATUS, eidStatus); + bcBindMap.put(ATTR_NAME_PUBKEYS, Arrays.asList(bindingPubKey)); + + try { + final String serializedBcBind = mapper.writeValueAsString(bcBindMap); + final SignContent req = new SignContent(); + final SignContentEntry bcBindInfo = new SignContentEntry(); + bcBindInfo.setKey(KEY_BC_BIND); + bcBindInfo.setValue(serializedBcBind); + req.getIn().add(bcBindInfo); + req.setAppendCert(false); + final JwsHeaderParam bcBindJoseHeader = new JwsHeaderParam(); + bcBindJoseHeader.setKey(JOSE_HEADER_USERCERTPINNING_TYPE); + bcBindJoseHeader.setValue(JOSE_HEADER_USERCERTPINNING_EIDASBIND); + req.getJWSHeaderParam().add(bcBindJoseHeader); + + 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().isEmpty() + || resp.getOut().get(0).getValue() == null) { + throw new SzrCommunicationException("ernb.01", new Object[]{"BcBind response empty"}); //TODO check error handling + } + + return resp.getOut().get(0).getValue(); + + } catch (final JsonProcessingException | SZRException_Exception e) { + log.warn("Requesting bcBind by using SZR FAILED. Reason: {}", e.getMessage(), null, 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 URL url = SzrClient.class.getResource("/szr_client/SZR_v4.0.wsdl"); final boolean useTestSzr = basicConfig.getBasicConfigurationBoolean( Constants.CONIG_PROPS_EIDAS_SZRCLIENT_USETESTSERVICE, diff --git a/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/CreateIdentityLinkTask.java b/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/CreateIdentityLinkTask.java index 88c3515b..e4a22cbc 100644 --- a/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/CreateIdentityLinkTask.java +++ b/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/CreateIdentityLinkTask.java @@ -19,28 +19,10 @@ * 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.tasks; -import java.io.InputStream; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.apache.commons.lang3.StringUtils; -import org.joda.time.DateTime; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; -import org.w3c.dom.Element; -import org.w3c.dom.Node; - -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; - import at.asitplus.eidas.specific.connector.MsConnectorEventCodes; import at.asitplus.eidas.specific.connector.MsEidasNodeConstants; import at.asitplus.eidas.specific.modules.auth.eidas.v2.Constants; @@ -60,6 +42,8 @@ import at.gv.egiz.eaaf.core.api.idp.auth.data.IIdentityLink; import at.gv.egiz.eaaf.core.api.idp.process.ExecutionContext; import at.gv.egiz.eaaf.core.exceptions.EaafException; import at.gv.egiz.eaaf.core.exceptions.TaskExecutionException; +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.data.Pair; import at.gv.egiz.eaaf.core.impl.idp.auth.builder.BpkBuilder; import at.gv.egiz.eaaf.core.impl.idp.auth.data.AuthProcessDataWrapper; @@ -67,20 +51,38 @@ import at.gv.egiz.eaaf.core.impl.idp.auth.data.SimpleIdentityLinkAssertionParser import at.gv.egiz.eaaf.core.impl.idp.auth.modules.AbstractAuthServletTask; import at.gv.egiz.eaaf.core.impl.utils.DomUtils; import at.gv.egiz.eaaf.core.impl.utils.XPathUtils; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import eu.eidas.auth.commons.attribute.AttributeDefinition; import eu.eidas.auth.commons.attribute.AttributeValue; import eu.eidas.auth.commons.light.ILightResponse; import eu.eidas.auth.commons.protocol.eidas.impl.PostalAddress; import lombok.extern.slf4j.Slf4j; +import lombok.val; +import org.apache.commons.lang3.StringUtils; +import org.joda.time.DateTime; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.w3c.dom.Element; +import org.w3c.dom.Node; import szrservices.IdentityLinkType; import szrservices.PersonInfoType; import szrservices.TravelDocumentType; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.InputStream; +import java.security.KeyStoreException; +import java.util.Base64; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + /** * Task that creates the IdentityLink for an eIDAS authenticated person. - * - * @author tlenz * + * @author tlenz */ @Slf4j @Component("CreateIdentityLinkTask") @@ -95,7 +97,7 @@ public class CreateIdentityLinkTask extends AbstractAuthServletTask { /* * (non-Javadoc) - * + * * @see at.gv.egovernment.moa.id.process.springweb.MoaIdTask#execute(at.gv. * egovernment.moa.id.process.api.ExecutionContext, * javax.servlet.http.HttpServletRequest, @@ -103,7 +105,7 @@ public class CreateIdentityLinkTask extends AbstractAuthServletTask { */ @Override public void execute(ExecutionContext executionContext, - HttpServletRequest request, HttpServletResponse response) + HttpServletRequest request, HttpServletResponse response) throws TaskExecutionException { try { final AuthProcessDataWrapper authProcessData = pendingReq.getSessionData(AuthProcessDataWrapper.class); @@ -217,43 +219,64 @@ public class CreateIdentityLinkTask extends AbstractAuthServletTask { } } - final IdentityLinkType result = szrClient.getIdentityLinkInRawMode(personInfo); + String eidMode = pendingReq.getServiceProviderConfiguration().getConfigurationValue(MsEidasNodeConstants.PROP_CONFIG_SP_EID_MODE, "old"); + if (eidMode.equals("new")) { - final Element idlFromSzr = (Element) result.getAssertion(); - identityLink = new SimpleIdentityLinkAssertionParser(idlFromSzr).parseIdentityLink(); + String vsz = szrClient.getEncryptedStammzahl(personInfo); - // write ERnB inputdata into revisionlog - if (basicConfig.getBasicConfigurationBoolean( - Constants.CONIG_PROPS_EIDAS_SZRCLIENT_WORKAROUND_REVISIONLOGDATASTORE_ACTIVE, false)) { - revisionsLogger.logEvent(pendingReq, - MsConnectorEventCodes.SZR_ERNB_EIDAS_RAW_ID, - (String) simpleAttrMap.get(Constants.eIDAS_ATTR_PERSONALIDENTIFIER)); - revisionsLogger.logEvent(pendingReq, - MsConnectorEventCodes.SZR_ERNB_EIDAS_ERNB_ID, eidData.getPseudonym()); + // build Keystore + String pK64 = getPkFromKeystore(); + // setzte Keystore in config ?path? lade rein + // key pair art siehe jose utils - } - // get bPK from SZR - if (basicConfig.getBasicConfigurationBoolean( - Constants.CONIG_PROPS_EIDAS_SZRCLIENT_DEBUG_USESRZFORBPKGENERATION, true)) { - bpk = szrClient.getBpk( - personInfo, - pendingReq.getServiceProviderConfiguration().getAreaSpecificTargetIdentifier(), - basicConfig.getBasicConfiguration( - Constants.CONIG_PROPS_EIDAS_SZRCLIENT_PARAMS_VKZ, - "no VKZ defined")); + String signedEidasBind = szrClient.getBcBind(vsz, pK64, "urn:eidgvat:eid.status.eidas"); //eidstatus TODO as config? + + //build AuthBlock JWS + ObjectMapper mapper = new ObjectMapper(); + String jwsPayload = mapper.writeValueAsString(pendingReq.getUniqueTransactionIdentifier()); + +// JoseUtils.createSignature(new Pair<>(ks, ks.getProvider()), "connectorkeypair", passord.chararray(), jwsPayload, false, ); //TODO joseutils kopiern } else { - log.debug("Calculating bPK from baseId ... "); - new BpkBuilder(); - final Pair bpkCalc = BpkBuilder.generateAreaSpecificPersonIdentifier( - identityLink.getIdentificationValue(), - identityLink.getIdentificationType(), - pendingReq.getServiceProviderConfiguration().getAreaSpecificTargetIdentifier()); - bpk = bpkCalc.getFirst(); - } + final IdentityLinkType result = szrClient.getIdentityLinkInRawMode(personInfo); + final Element idlFromSzr = (Element) result.getAssertion(); + identityLink = new SimpleIdentityLinkAssertionParser(idlFromSzr).parseIdentityLink(); + + // write ERnB inputdata into revisionlog + if (basicConfig.getBasicConfigurationBoolean( + Constants.CONIG_PROPS_EIDAS_SZRCLIENT_WORKAROUND_REVISIONLOGDATASTORE_ACTIVE, false)) { + revisionsLogger.logEvent(pendingReq, + MsConnectorEventCodes.SZR_ERNB_EIDAS_RAW_ID, + (String) simpleAttrMap.get(Constants.eIDAS_ATTR_PERSONALIDENTIFIER)); + revisionsLogger.logEvent(pendingReq, + MsConnectorEventCodes.SZR_ERNB_EIDAS_ERNB_ID, eidData.getPseudonym()); + + } + + // get bPK from SZR + if (basicConfig.getBasicConfigurationBoolean( + Constants.CONIG_PROPS_EIDAS_SZRCLIENT_DEBUG_USESRZFORBPKGENERATION, true)) { + bpk = szrClient.getBpk( + personInfo, + pendingReq.getServiceProviderConfiguration().getAreaSpecificTargetIdentifier(), + basicConfig.getBasicConfiguration( + Constants.CONIG_PROPS_EIDAS_SZRCLIENT_PARAMS_VKZ, + "no VKZ defined")).get(0); + + } else { + log.debug("Calculating bPK from baseId ... "); + new BpkBuilder(); + final Pair bpkCalc = BpkBuilder.generateAreaSpecificPersonIdentifier( + identityLink.getIdentificationValue(), + identityLink.getIdentificationType(), + pendingReq.getServiceProviderConfiguration().getAreaSpecificTargetIdentifier()); + bpk = bpkCalc.getFirst(); + + } + } } if (identityLink == null) { @@ -307,6 +330,20 @@ public class CreateIdentityLinkTask extends AbstractAuthServletTask { } } + private String getPkFromKeystore() throws EaafException, KeyStoreException { + EaafKeyStoreFactory keyStoreFactory = new EaafKeyStoreFactory(); + KeyStoreConfiguration configuration = new KeyStoreConfiguration(); + + final String current = new java.io.File(".").toURI().toString(); + configuration.setSoftKeyStoreFilePath(current + "src/test/resources/keystore/teststore.jks"); + + configuration.setSoftKeyStorePassword("f/+saJBc3a}*/T^s"); + configuration.setKeyStoreType(KeyStoreConfiguration.KeyStoreType.JKS); + val ks = keyStoreFactory.buildNewKeyStore(configuration); + val publicKey = ks.getFirst().getCertificate("connectorkeypair").getPublicKey(); + return Base64.getEncoder().encodeToString(publicKey.getEncoded()); + } + private String extendBpkByPrefix(String bpk, String type) { String bpkType = null; diff --git a/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/utils/JoseUtils.java b/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/utils/JoseUtils.java new file mode 100644 index 00000000..e81c4c92 --- /dev/null +++ b/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/utils/JoseUtils.java @@ -0,0 +1,305 @@ +package at.asitplus.eidas.specific.modules.auth.eidas.v2.utils; + +import at.gv.egiz.eaaf.core.exception.EaafKeyUsageException; +import at.gv.egiz.eaaf.core.exceptions.EaafException; +import at.gv.egiz.eaaf.core.impl.credential.EaafKeyStoreUtils; +import at.gv.egiz.eaaf.core.impl.data.Pair; +import at.gv.egiz.eaaf.core.impl.utils.X509Utils; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.jose4j.jca.ProviderContext; +import org.jose4j.jwa.AlgorithmConstraints; +import org.jose4j.jws.AlgorithmIdentifiers; +import org.jose4j.jws.JsonWebSignature; +import org.jose4j.jwx.Headers; +import org.jose4j.jwx.JsonWebStructure; +import org.jose4j.keys.resolvers.X509VerificationKeyResolver; +import org.jose4j.lang.JoseException; +import org.springframework.util.Base64Utils; + +import javax.annotation.Nonnull; +import java.io.IOException; +import java.security.Key; +import java.security.KeyStore; +import java.security.Provider; +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.RSAPrivateKey; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +/** + * {@link JoseUtils} provides static methods JWS and JWE processing. + * + * @author tlenz + * + */ +@Slf4j +public class JoseUtils { + + /** + * Create a JWS signature. + * + *

+ * Use {@link AlgorithmIdentifiers.RSA_PSS_USING_SHA256} in case + * of a RSA based key and + * {@link AlgorithmIdentifiers.ECDSA_USING_P256_CURVE_AND_SHA256} + * in case of an ECC based key. + *

+ * + * @param keyStore KeyStore that should be used + * @param keyAlias Alias of the private key + * @param keyPassword Password to access the key + * @param payLoad PayLoad to sign + * @param addFullCertChain If true the full certificate chain will be + * added, otherwise only the + * X509CertSha256Fingerprint is added into JOSE + * header + * @param friendlyNameForLogging FriendlyName for the used KeyStore for logging + * purposes only + * @return Signed PayLoad in serialized form + * @throws EaafException In case of a key-access or key-usage error + * @throws JoseException In case of a JOSE error + */ + public static String createSignature(@Nonnull Pair keyStore, + @Nonnull final String keyAlias, @Nonnull final char[] keyPassword, + @Nonnull final String payLoad, boolean addFullCertChain, + @Nonnull String friendlyNameForLogging) throws EaafException, JoseException { + return createSignature(keyStore, keyAlias, keyPassword, payLoad, addFullCertChain, Collections.emptyMap(), + AlgorithmIdentifiers.RSA_PSS_USING_SHA256, AlgorithmIdentifiers.ECDSA_USING_P256_CURVE_AND_SHA256, + friendlyNameForLogging); + + } + + /** + * Create a JWS signature. + * + *

+ * Use {@link AlgorithmIdentifiers.RSA_PSS_USING_SHA256} in case + * of a RSA based key and + * {@link AlgorithmIdentifiers.ECDSA_USING_P256_CURVE_AND_SHA256} + * in case of an ECC based key. + *

+ * + * @param keyStore KeyStore that should be used + * @param keyAlias Alias of the private key + * @param keyPassword Password to access the key + * @param payLoad PayLoad to sign + * @param addFullCertChain If true the full certificate chain will be + * added, otherwise only the + * X509CertSha256Fingerprint is added into JOSE + * header + * @param joseHeaders HeaderName and HeaderValue that should be set + * into JOSE header + * @param friendlyNameForLogging FriendlyName for the used KeyStore for logging + * purposes only + * @return Signed PayLoad in serialized form + * @throws EaafException In case of a key-access or key-usage error + * @throws JoseException In case of a JOSE error + */ + public static String createSignature(@Nonnull Pair keyStore, + @Nonnull final String keyAlias, @Nonnull final char[] keyPassword, + @Nonnull final String payLoad, boolean addFullCertChain, + @Nonnull final Map joseHeaders, + @Nonnull String friendlyNameForLogging) throws EaafException, JoseException { + return createSignature(keyStore, keyAlias, keyPassword, payLoad, addFullCertChain, joseHeaders, + AlgorithmIdentifiers.RSA_PSS_USING_SHA256, AlgorithmIdentifiers.ECDSA_USING_P256_CURVE_AND_SHA256, + friendlyNameForLogging); + + } + + /** + * Create a JWS signature. + * + * @param keyStore KeyStore that should be used + * @param keyAlias Alias of the private key + * @param keyPassword Password to access the key + * @param payLoad PayLoad to sign + * @param addFullCertChain If true the full certificate chain will be + * added, otherwise only the + * X509CertSha256Fingerprint is added into JOSE + * header + * @param joseHeaders HeaderName and HeaderValue that should be set + * into JOSE header + * @param rsaAlgToUse Signing algorithm that should be used in case + * of a signing key based on RSA + * @param eccAlgToUse Signing algorithm that should be used in case + * of a signing key based on ECC + * @param friendlyNameForLogging FriendlyName for the used KeyStore for logging + * purposes only + * @return Signed PayLoad in serialized form + * @throws EaafException In case of a key-access or key-usage error + * @throws JoseException In case of a JOSE error + */ + public static String createSignature(@Nonnull Pair keyStore, + @Nonnull final String keyAlias, @Nonnull final char[] keyPassword, + @Nonnull final String payLoad, boolean addFullCertChain, + @Nonnull final Map joseHeaders, + @Nonnull final String rsaAlgToUse, @Nonnull final String eccAlgToUse, + @Nonnull String friendlyNameForLogging) throws EaafException, JoseException { + + final JsonWebSignature jws = new JsonWebSignature(); + + // set payload + jws.setPayload(payLoad); + + // set JOSE headers + for (final Entry el : joseHeaders.entrySet()) { + log.trace("Set JOSE header: {} with value: {} into JWS", el.getKey(), el.getValue()); + jws.setHeader(el.getKey(), el.getValue()); + + } + + // set signing information + final Pair signingCred = EaafKeyStoreUtils.getPrivateKeyAndCertificates( + keyStore.getFirst(), keyAlias, keyPassword, true, friendlyNameForLogging); + jws.setKey(signingCred.getFirst()); + jws.setAlgorithmHeaderValue(getKeyOperationAlgorithmFromCredential( + jws.getKey(), rsaAlgToUse, eccAlgToUse, friendlyNameForLogging)); + + // set special provider if required + if (keyStore.getSecond() != null) { + log.trace("Injecting special Java Security Provider: {}", keyStore.getSecond().getName()); + final ProviderContext providerCtx = new ProviderContext(); + providerCtx.getSuppliedKeyProviderContext().setSignatureProvider( + keyStore.getSecond().getName()); + jws.setProviderContext(providerCtx); + + } + + if (addFullCertChain) { + jws.setCertificateChainHeaderValue(signingCred.getSecond()); + + } + + jws.setX509CertSha256ThumbprintHeaderValue(signingCred.getSecond()[0]); + + return jws.getCompactSerialization(); + + } + + /** + * Verify a JOSE signature. + * + * @param serializedContent Serialized content that should be verified + * @param trustedCerts Trusted certificates that should be used for + * verification + * @param constraints {@link AlgorithmConstraints} for verification + * @return {@link JwsResult} object + * @throws JoseException In case of a signature verification error + * @throws IOException In case of a general error + */ + public static JwsResult validateSignature(@Nonnull final String serializedContent, + @Nonnull final List trustedCerts, @Nonnull final AlgorithmConstraints constraints) + throws JoseException, IOException { + final JsonWebSignature jws = new JsonWebSignature(); + // set payload + jws.setCompactSerialization(serializedContent); + + // set security constrains + jws.setAlgorithmConstraints(constraints); + + // load signinc certs + Key selectedKey = null; + final List x5cCerts = jws.getCertificateChainHeaderValue(); + final String x5t256 = jws.getX509CertSha256ThumbprintHeaderValue(); + if (x5cCerts != null) { + log.debug("Found x509 certificate in JOSE header ... "); + log.trace("Sorting received X509 certificates ... "); + final List sortedX5cCerts = X509Utils.sortCertificates(x5cCerts); + + if (trustedCerts.contains(sortedX5cCerts.get(0))) { + selectedKey = sortedX5cCerts.get(0).getPublicKey(); + + } else { + log.info("Can NOT find JOSE certificate in truststore."); + if (log.isDebugEnabled()) { + try { + log.debug("Cert: {}", Base64Utils.encodeToString(sortedX5cCerts.get(0).getEncoded())); + + } catch (final CertificateEncodingException e) { + log.warn("Can not create DEBUG output", e); + + } + } + } + + } else if (StringUtils.isNotEmpty(x5t256)) { + log.debug("Found x5t256 fingerprint in JOSE header .... "); + final X509VerificationKeyResolver x509VerificationKeyResolver = new X509VerificationKeyResolver( + trustedCerts); + selectedKey = x509VerificationKeyResolver.resolveKey(jws, Collections.emptyList()); + + } else { + throw new JoseException("JWS contains NO signature certificate or NO certificate fingerprint"); + + } + + if (selectedKey == null) { + throw new JoseException("Can NOT select verification key for JWS. Signature verification FAILED"); + + } + + // set verification key + jws.setKey(selectedKey); + + // load payLoad + return new JwsResult( + jws.verifySignature(), + jws.getUnverifiedPayload(), + jws.getHeaders(), + x5cCerts); + + } + + /** + * Select signature algorithm for a given credential. + * + * @param key {@link X509Credential} that will be used for + * key operations + * @param rsaSigAlgorithm RSA based algorithm that should be used in case + * of RSA credential + * @param ecSigAlgorithm EC based algorithm that should be used in case + * of RSA credential + * @param friendlyNameForLogging KeyStore friendlyName for logging purposes + * @return either the RSA based algorithm or the EC based algorithm + * @throws EaafKeyUsageException In case of an unsupported private-key type + */ + private static String getKeyOperationAlgorithmFromCredential(Key key, + String rsaSigAlgorithm, String ecSigAlgorithm, String friendlyNameForLogging) + throws EaafKeyUsageException { + if (key instanceof RSAPrivateKey) { + return rsaSigAlgorithm; + + } else if (key instanceof ECPrivateKey) { + return ecSigAlgorithm; + + } else { + log.warn("Could NOT select the cryptographic algorithm from Private-Key type"); + throw new EaafKeyUsageException(EaafKeyUsageException.ERROR_CODE_01, + friendlyNameForLogging, + "Can not select cryptographic algorithm"); + + } + + } + + private JoseUtils() { + + } + + @Getter + @AllArgsConstructor + public static class JwsResult { + final boolean valid; + final String payLoad; + final Headers fullJoseHeader; + final List x5cCerts; + + } +} -- cgit v1.2.3