package at.gv.egiz.eaaf.modules.sigverify.moasig.impl; import java.io.ByteArrayInputStream; import java.security.cert.CertificateEncodingException; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Map; import javax.annotation.PostConstruct; import org.apache.commons.lang3.time.DateFormatUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.lang.Nullable; import org.springframework.stereotype.Service; import org.springframework.util.Base64Utils; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import at.gv.egiz.eaaf.core.impl.data.Pair; import at.gv.egiz.eaaf.modules.sigverify.moasig.api.ISignatureVerificationService; import at.gv.egiz.eaaf.modules.sigverify.moasig.api.data.ICmsSignatureVerificationResponse; import at.gv.egiz.eaaf.modules.sigverify.moasig.api.data.IPdfSignatureVerificationResponse; import at.gv.egiz.eaaf.modules.sigverify.moasig.api.data.IXmlSignatureVerificationResponse; import at.gv.egiz.eaaf.modules.sigverify.moasig.exceptions.MoaSigServiceBuilderException; import at.gv.egiz.eaaf.modules.sigverify.moasig.exceptions.MoaSigServiceException; import at.gv.egiz.eaaf.modules.sigverify.moasig.impl.data.GenericSignatureVerificationResponse; import at.gv.egiz.eaaf.modules.sigverify.moasig.impl.data.VerifyPdfSignatureResponse; import at.gv.egiz.eaaf.modules.sigverify.moasig.impl.data.VerifyPdfSignatureResponse.CoversFullDocument; import at.gv.egiz.eaaf.modules.sigverify.moasig.impl.parser.VerifyXmlSignatureResponseParser; import at.gv.egovernment.moa.spss.MOAException; import at.gv.egovernment.moa.spss.api.cmsverify.VerifyCMSSignatureRequest; import at.gv.egovernment.moa.spss.api.cmsverify.VerifyCMSSignatureResponse; import at.gv.egovernment.moa.spss.api.cmsverify.VerifyCMSSignatureResponseElement; import at.gv.egovernment.moa.spss.api.impl.VerifyCMSSignatureRequestImpl; import at.gv.egovernment.moa.spss.api.xmlbind.VerifyXMLSignatureRequestParser; import at.gv.egovernment.moa.spss.api.xmlbind.VerifyXMLSignatureResponseBuilder; import at.gv.egovernment.moa.spss.api.xmlverify.VerifyXMLSignatureRequest; import at.gv.egovernment.moa.spss.api.xmlverify.VerifyXMLSignatureResponse; import at.gv.egovernment.moa.spss.server.invoke.CMSSignatureVerificationInvoker; import at.gv.egovernment.moa.spss.server.invoke.XMLSignatureVerificationInvoker; import at.gv.egovernment.moaspss.util.Constants; /** * MOA-Sig based signature verification implementation. * * @author tlenz * */ @Service(value = "moaSigVerifyService") public class SignatureVerificationService extends AbstractSignatureService implements ISignatureVerificationService { private static final Logger log = LoggerFactory.getLogger(SignatureVerificationService.class); private static final String XMLNS_NS_URI = Constants.XMLNS_NS_URI; private static final String MOA_NS_URI = Constants.MOA_NS_URI; private static final String DSIG = Constants.DSIG_PREFIX + ":"; private static final String DEFAULT_XPATH_SIGNATURE_LOCATION = "//" + DSIG + "Signature"; public static final String PATTERN_ISSUE_INSTANT = "yyyy-MM-dd'T'HH:mm:ssXXX"; private CMSSignatureVerificationInvoker cadesInvoker; private XMLSignatureVerificationInvoker xadesInvocer; /* * (non-Javadoc) * * @see at.gv.egiz.eid.authhandler.modules.sigverify.moasig.impl. * ISignatureVerificationService# verifyCMSSignature(byte[], java.lang.String) */ @Override @Nullable public ICmsSignatureVerificationResponse verifyCmsSignature(final byte[] signature, final String trustProfileID) throws MoaSigServiceException { return verifyCmsSignature(signature, trustProfileID, false); } @Override public ICmsSignatureVerificationResponse verifyCmsSignature(byte[] signature, String trustProfileID, boolean performExtendedValidation) throws MoaSigServiceException { try { // setup context setUpContexts(Thread.currentThread().getName()); // verify signature final VerifyCMSSignatureRequest cmsSigVerifyReq = buildVerfifyCmsRequest(signature, trustProfileID, false, performExtendedValidation); final VerifyCMSSignatureResponse cmsSigVerifyResp = cadesInvoker.verifyCMSSignature(cmsSigVerifyReq); return parseCmsVerificationResult(cmsSigVerifyResp); } catch (final MOAException e) { log.warn("CMS signature verification has an error.", e); throw new MoaSigServiceException("service.moasig.03", new Object[] { e.toString() }, e); } catch (final CertificateEncodingException e) { log.warn("Can NOT serialize X509 certificate from CMS/CAdES signature-verification response", e); throw new MoaSigServiceException("service.moasig.03", new Object[] { e.toString() }, e); } finally { tearDownContexts(); } } @Override public List verifyPdfSignature(byte[] pdf, String trustProfileID) throws MoaSigServiceException { return verifyPdfSignature(pdf, trustProfileID, false); } @Override public List verifyPdfSignature(byte[] pdf, String trustProfileID, boolean performExtendedValidation) throws MoaSigServiceException { try { // setup context setUpContexts(Thread.currentThread().getName()); // verify signature final VerifyCMSSignatureResponse cmsSigVerifyResp = cadesInvoker.verifyCMSSignature( buildVerfifyCmsRequest(pdf, trustProfileID, true, performExtendedValidation)); return parsePdfVerificationResult(cmsSigVerifyResp); } catch (final MOAException e) { log.warn("PDF signature verification has an error.", e); throw new MoaSigServiceException("service.moasig.03", new Object[] { e.toString() }, e); } catch (final CertificateEncodingException e) { log.warn("Can NOT serialize X509 certificate from PDF/PAdES signature-verification response", e); throw new MoaSigServiceException("service.moasig.03", new Object[] { e.toString() }, e); } finally { tearDownContexts(); } } /* * (non-Javadoc) * * @see at.gv.egiz.eid.authhandler.modules.sigverify.moasig.impl. * ISignatureVerificationService# verifyXMLSignature(byte[], java.lang.String) */ @Override public IXmlSignatureVerificationResponse verifyXmlSignature(final byte[] signature, final String trustProfileID) throws MoaSigServiceException { return verifyXmlSignature(signature, trustProfileID, null, DEFAULT_XPATH_SIGNATURE_LOCATION, null, Collections.emptyMap()); } /* * (non-Javadoc) * * @see at.gv.egiz.eid.authhandler.modules.sigverify.moasig.impl. * ISignatureVerificationService# verifyXMLSignature(byte[], java.lang.String, * java.util.List) */ @Override public IXmlSignatureVerificationResponse verifyXmlSignature(final byte[] signature, final String trustProfileID, final List verifyTransformsInfoProfileID) throws MoaSigServiceException { return verifyXmlSignature(signature, trustProfileID, verifyTransformsInfoProfileID, DEFAULT_XPATH_SIGNATURE_LOCATION, null, Collections.emptyMap()); } /* * (non-Javadoc) * * @see at.gv.egiz.eid.authhandler.modules.sigverify.moasig.impl. * ISignatureVerificationService# verifyXMLSignature(byte[], java.lang.String, * java.lang.String) */ @Override public IXmlSignatureVerificationResponse verifyXmlSignature(final byte[] signature, final String trustProfileID, final String signatureLocationXpath) throws MoaSigServiceException { return verifyXmlSignature(signature, trustProfileID, null, signatureLocationXpath, null, Collections.emptyMap()); } @Override public IXmlSignatureVerificationResponse verifyXmlSignature(byte[] signature, String trustProfileID, Date signingDate) throws MoaSigServiceException { return verifyXmlSignature(signature, trustProfileID, null, DEFAULT_XPATH_SIGNATURE_LOCATION, signingDate, Collections.emptyMap()); } @Override public IXmlSignatureVerificationResponse verifyXmlSignature(final byte[] signature, final String trustProfileID, final List verifyTransformsInfoProfileID, final String xpathSignatureLocation, Date signingDate) throws MoaSigServiceException { return verifyXmlSignature(signature, trustProfileID, verifyTransformsInfoProfileID, xpathSignatureLocation, signingDate, Collections.emptyMap()); } @Override public IXmlSignatureVerificationResponse verifyXmlSignature(final byte[] signature, final String trustProfileID, final List verifyTransformsInfoProfileID, final String xpathSignatureLocation, Date signingDate, final Map supplementContent) throws MoaSigServiceException { try { // setup context setUpContexts(Thread.currentThread().getName()); // build signature-verification request final Element domVerifyXmlSignatureRequest = buildVerifyXmlRequest(signature, trustProfileID, verifyTransformsInfoProfileID, xpathSignatureLocation, signingDate, supplementContent); // send signature-verification to MOA-Sig final VerifyXMLSignatureRequest vsrequest = new VerifyXMLSignatureRequestParser().parse(domVerifyXmlSignatureRequest); final VerifyXMLSignatureResponse vsresponse = xadesInvocer.verifyXMLSignature(vsrequest); final Document result = new VerifyXMLSignatureResponseBuilder(true).build(vsresponse); // parses the final IXmlSignatureVerificationResponse verifyXmlSignatureResponse = new VerifyXmlSignatureResponseParser(result.getDocumentElement()).parseData(); return verifyXmlSignatureResponse; } catch (final MoaSigServiceException e) { throw e; } catch (final MOAException e) { log.warn("MOA-Sig signature-verification has an internal error." + " MsgCode: " + e.getMessageId() + " Msg: " + e.getMessage(), e); throw new MoaSigServiceException("service.moasig.03", new Object[] { e.getMessage() }, e); } finally { tearDownContexts(); } } private ICmsSignatureVerificationResponse parseCmsVerificationResult( final VerifyCMSSignatureResponse cmsSigVerifyResp) throws CertificateEncodingException { if (cmsSigVerifyResp.getResponseElements() == null || cmsSigVerifyResp.getResponseElements().isEmpty()) { log.info("No CMS signature FOUND. "); return null; } if (cmsSigVerifyResp.getResponseElements().size() > 1) { log.warn( "CMS or CAdES signature contains more than one technical signatures. Only validate the first signature"); } return (ICmsSignatureVerificationResponse) parseBasisSignatureInformation( new at.gv.egiz.eaaf.modules.sigverify.moasig.impl.data.VerifyCmsSignatureResponse(), (VerifyCMSSignatureResponseElement) cmsSigVerifyResp.getResponseElements().get(0)); } private List parsePdfVerificationResult( VerifyCMSSignatureResponse cmsSigVerifyResp) throws CertificateEncodingException { List result = new ArrayList<>(); if (cmsSigVerifyResp.getResponseElements() == null || cmsSigVerifyResp.getResponseElements().isEmpty()) { log.info("No CMS signature FOUND. "); } else { Iterator it = cmsSigVerifyResp.getResponseElements().iterator(); while (it.hasNext()) { VerifyCMSSignatureResponseElement el = (VerifyCMSSignatureResponseElement) it.next(); VerifyPdfSignatureResponse pdfSigResult = (VerifyPdfSignatureResponse) parseBasisSignatureInformation(new VerifyPdfSignatureResponse(), el); pdfSigResult.setSignatureCoversFullDocument( el.getCoversFullDocument() != null ? el.getCoversFullDocument() ? CoversFullDocument.YES : CoversFullDocument.NO : CoversFullDocument.UNKNOWN); pdfSigResult.setByteRange(convertByteRanges(el.getByteRangeOfSignature())); result.add(pdfSigResult); } } return result; } private List> convertByteRanges(int[] byteRangeOfSignature) { List> result = new ArrayList<>(); if (byteRangeOfSignature != null) { for (int i = 0; i < byteRangeOfSignature.length / 2; i++) { result.add(Pair.newInstance( Integer.valueOf(byteRangeOfSignature[i]), Integer.valueOf(byteRangeOfSignature[i + 1]))); } } else { log.debug("PDF signature-verification result contains no byte-range information"); } return result; } private GenericSignatureVerificationResponse parseBasisSignatureInformation( GenericSignatureVerificationResponse result, VerifyCMSSignatureResponseElement resp) throws CertificateEncodingException { // parse results into response container result.setSignatureCheckCode(resp.getSignatureCheck().getCode()); result.setCertificateCheckCode(resp.getCertificateCheck().getCode()); if (resp.getSignerInfo() != null) { result.setSigningDateTime(resp.getSignerInfo().getSigningTime()); result .setX509CertificateEncoded(resp.getSignerInfo().getSignerCertificate().getEncoded()); result.setQualifiedCertificate(resp.getSignerInfo().isQualifiedCertificate()); result.setPublicAuthority(resp.getSignerInfo().isPublicAuthority()); result.setPublicAuthorityCode(resp.getSignerInfo().getPublicAuhtorityID()); } else { log.info("CMS or CAdES verification result contains no SignerInfo"); } //TODO: add extended validation infos result.setSignatureAlgorithmIdentifier(resp.getSignatureAlgorithm()); result.setExtendedCertificateCheckResult(resp.getExtendedCertificateCheck()); result.setFormValidationResults(resp.getAdESFormResults()); return result; } /** * Build a VerifyCMS-Siganture request for MOA-Sig.
*
* This builder only generates verification-request for enveloped CMS or CAdES * signatures
* This * * @param signature CMS or CAdES signature * @param trustProfileID trustProfileID MOA-Sig Trust-Profile * @param isPdfSignature Make CAdES signature as part of an PAdES * document * @param performExtendedValidation To extended validation. See MOA-Sig * documentation for detailed information * @return */ private VerifyCMSSignatureRequest buildVerfifyCmsRequest(final byte[] signature, final String trustProfileID, final boolean isPdfSignature, final boolean performExtendedValidation) { final VerifyCMSSignatureRequestImpl verifyCmsSignatureRequest = new VerifyCMSSignatureRequestImpl(); verifyCmsSignatureRequest.setDateTime(null); verifyCmsSignatureRequest.setCMSSignature(new ByteArrayInputStream(signature)); verifyCmsSignatureRequest.setDataObject(null); verifyCmsSignatureRequest.setTrustProfileId(trustProfileID); verifyCmsSignatureRequest.setSignatories(VerifyCMSSignatureRequest.ALL_SIGNATORIES); verifyCmsSignatureRequest.setPDF(isPdfSignature); verifyCmsSignatureRequest.setExtended(performExtendedValidation); return verifyCmsSignatureRequest; } /** * Build a VerifyXML-Signature request for MOA-Sig. * * @param signature Serialized XML signature * @param trustProfileID MOA-Sig Trust-Profile * @param verifyTransformsInfoProfileID {@link List} of Transformation-Profiles * used for validation * @param xpathSignatureLocation Xpath that points to location of * Signature element * @param sigValDate Signature timestamp * @param supplementContent Map that contains supplement profile content; keyed by references. Each entry * in this map becomes a Content/Base64Content child in the SupplementProfile * node. Use this map to specify content of references that the verification * service cannot resolve. * @return MOA-Sig verification request element * @throws MoaSigServiceBuilderException In case of an error */ private Element buildVerifyXmlRequest(final byte[] signature, final String trustProfileID, final List verifyTransformsInfoProfileID, final String xpathSignatureLocation, Date sigValDate, final Map supplementContent) throws MoaSigServiceBuilderException { try { // build empty document final Document requestDoc_ = getNewDocumentBuilder(); final Element requestElem_ = requestDoc_.createElementNS(MOA_NS_URI, "VerifyXMLSignatureRequest"); requestElem_.setAttributeNS(XMLNS_NS_URI, "xmlns", MOA_NS_URI); requestElem_.setAttributeNS(XMLNS_NS_URI, "xmlns:" + Constants.DSIG_PREFIX, Constants.DSIG_NS_URI); requestDoc_.appendChild(requestElem_); // build the request // build set signing time if (sigValDate != null) { final Element dateTimeElem = requestDoc_.createElementNS(MOA_NS_URI, "DateTime"); requestElem_.appendChild(dateTimeElem); final Node dateTime = requestDoc_.createTextNode( DateFormatUtils.format(sigValDate, PATTERN_ISSUE_INSTANT)); dateTimeElem.appendChild(dateTime); } //set other parameters final Element verifiySignatureInfoElem = requestDoc_.createElementNS(MOA_NS_URI, "VerifySignatureInfo"); requestElem_.appendChild(verifiySignatureInfoElem); final Element verifySignatureEnvironmentElem = requestDoc_.createElementNS(MOA_NS_URI, "VerifySignatureEnvironment"); verifiySignatureInfoElem.appendChild(verifySignatureEnvironmentElem); final Element base64ContentElem = requestDoc_.createElementNS(MOA_NS_URI, "Base64Content"); verifySignatureEnvironmentElem.appendChild(base64ContentElem); // insert the base64 encoded signature String base64EncodedAssertion = Base64Utils.encodeToString(signature); // replace all '\r' characters by no char. final StringBuffer replaced = new StringBuffer(); for (int i = 0; i < base64EncodedAssertion.length(); i++) { final char c = base64EncodedAssertion.charAt(i); if (c != '\r') { replaced.append(c); } } base64EncodedAssertion = replaced.toString(); final Node base64Content = requestDoc_.createTextNode(base64EncodedAssertion); base64ContentElem.appendChild(base64Content); // specify the signature location final Element verifySignatureLocationElem = requestDoc_.createElementNS(MOA_NS_URI, "VerifySignatureLocation"); verifiySignatureInfoElem.appendChild(verifySignatureLocationElem); final Node signatureLocation = requestDoc_.createTextNode(xpathSignatureLocation); verifySignatureLocationElem.appendChild(signatureLocation); // signature manifest params if (verifyTransformsInfoProfileID != null && !verifyTransformsInfoProfileID.isEmpty()) { final Element signatureManifestCheckParamsElem = requestDoc_.createElementNS(MOA_NS_URI, "SignatureManifestCheckParams"); requestElem_.appendChild(signatureManifestCheckParamsElem); signatureManifestCheckParamsElem.setAttribute("ReturnReferenceInputData", "false"); // verify transformations final Element referenceInfoElem = requestDoc_.createElementNS(MOA_NS_URI, "ReferenceInfo"); signatureManifestCheckParamsElem.appendChild(referenceInfoElem); for (final String element : verifyTransformsInfoProfileID) { final Element verifyTransformsInfoProfileIdElem = requestDoc_.createElementNS(MOA_NS_URI, "VerifyTransformsInfoProfileID"); referenceInfoElem.appendChild(verifyTransformsInfoProfileIdElem); verifyTransformsInfoProfileIdElem.appendChild(requestDoc_.createTextNode(element)); } } // hashinput data final Element returnHashInputDataElem = requestDoc_.createElementNS(MOA_NS_URI, "ReturnHashInputData"); requestElem_.appendChild(returnHashInputDataElem); // add trustProfileID final Element trustProfileIdElem = requestDoc_.createElementNS(MOA_NS_URI, "TrustProfileID"); trustProfileIdElem.appendChild(requestDoc_.createTextNode(trustProfileID)); requestElem_.appendChild(trustProfileIdElem); // add supplement profile if (!supplementContent.isEmpty()) { final Element supplementProfile = requestDoc_.createElementNS(MOA_NS_URI, "SupplementProfile"); for (Map.Entry entry: supplementContent.entrySet()) { String reference = entry.getKey(); byte[] contentBytes = entry.getValue(); final Element content = requestDoc_.createElementNS(MOA_NS_URI, "Content"); content.setAttribute("Reference", reference); final Element b64content = requestDoc_.createElementNS(MOA_NS_URI, "Base64Content"); b64content.setTextContent(Base64Utils.encodeToString(contentBytes)); content.appendChild(b64content); supplementProfile.appendChild(content); } requestElem_.appendChild(supplementProfile); } return requestElem_; } catch (final Throwable t) { log.warn("Can NOT build VerifyXML-Signature request for MOA-Sig", t); throw new MoaSigServiceBuilderException("service.moasig.03", new Object[] { t.getMessage() }, t); } } @PostConstruct protected void internalInitializer() { log.debug("Instanzing SignatureVerificationService implementation ... "); // svs = // at.gv.egovernment.moa.spss.api.SignatureVerificationService.getInstance(); cadesInvoker = CMSSignatureVerificationInvoker.getInstance(); xadesInvocer = XMLSignatureVerificationInvoker.getInstance(); log.info("MOA-Sig signature-verification service initialized"); } }