From: Andreas Beeker Date: Fri, 8 Oct 2021 23:08:51 +0000 (+0000) Subject: #65623 - Create XAdES-T signature with XAdESXLSignatureFacet X-Git-Tag: REL_5_2_0~424 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=60e9de813e655a82bb01d69f91072f72be7c025a;p=poi.git #65623 - Create XAdES-T signature with XAdESXLSignatureFacet git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1894049 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/poi-ooxml/src/main/java/org/apache/poi/poifs/crypt/dsig/facets/XAdESXLSignatureFacet.java b/poi-ooxml/src/main/java/org/apache/poi/poifs/crypt/dsig/facets/XAdESXLSignatureFacet.java index d500c6a8c7..ad2e8c41b5 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/poifs/crypt/dsig/facets/XAdESXLSignatureFacet.java +++ b/poi-ooxml/src/main/java/org/apache/poi/poifs/crypt/dsig/facets/XAdESXLSignatureFacet.java @@ -24,6 +24,7 @@ package org.apache.poi.poifs.crypt.dsig.facets; +import static java.util.Optional.ofNullable; import static org.apache.poi.ooxml.POIXMLTypeLoader.DEFAULT_XML_OPTIONS; import static org.apache.poi.poifs.crypt.dsig.facets.XAdESSignatureFacet.insertXChild; @@ -52,6 +53,7 @@ import org.apache.logging.log4j.Logger; import org.apache.poi.poifs.crypt.dsig.SignatureConfig; import org.apache.poi.poifs.crypt.dsig.SignatureInfo; import org.apache.poi.poifs.crypt.dsig.services.RevocationData; +import org.apache.poi.poifs.crypt.dsig.services.RevocationDataService; import org.apache.xml.security.c14n.Canonicalizer; import org.apache.xmlbeans.XmlException; import org.bouncycastle.asn1.ASN1InputStream; @@ -74,14 +76,12 @@ import org.w3c.dom.NodeList; /** * XAdES-X-L v1.4.1 signature facet. This signature facet implementation will * upgrade a given XAdES-BES/EPES signature to XAdES-X-L. + * If no revocation data service is set, only a XAdES-T signature is created. * * We don't inherit from XAdESSignatureFacet as we also want to be able to use * this facet out of the context of a signature creation. This signature facet * assumes that the signature is already XAdES-BES/EPES compliant. * - * This implementation has been tested against an implementation that - * participated multiple ETSI XAdES plugtests. - * * @see XAdESSignatureFacet */ public class XAdESXLSignatureFacet implements SignatureFacet { @@ -104,35 +104,63 @@ public class XAdESXLSignatureFacet implements SignatureFacet { SignatureConfig signatureConfig = signatureInfo.getSignatureConfig(); - QualifyingPropertiesDocument qualDoc; - QualifyingPropertiesType qualProps; - // check for XAdES-BES NodeList qualNl = document.getElementsByTagNameNS(XADES_132_NS, "QualifyingProperties"); - if (qualNl.getLength() == 1) { - try { - qualDoc = QualifyingPropertiesDocument.Factory.parse(qualNl.item(0), DEFAULT_XML_OPTIONS); - } catch (XmlException e) { - throw new MarshalException(e); - } - qualProps = qualDoc.getQualifyingProperties(); - } else { - throw new MarshalException("no XAdES-BES extension present"); - } + QualifyingPropertiesType qualProps = getQualProps(qualNl); // create basic XML container structure - UnsignedPropertiesType unsignedProps = qualProps.getUnsignedProperties(); - if (unsignedProps == null) { - unsignedProps = qualProps.addNewUnsignedProperties(); + UnsignedPropertiesType unsignedProps = + ofNullable(qualProps.getUnsignedProperties()).orElseGet(qualProps::addNewUnsignedProperties); + UnsignedSignaturePropertiesType unsignedSigProps = + ofNullable(unsignedProps.getUnsignedSignatureProperties()).orElseGet(unsignedProps::addNewUnsignedSignatureProperties); + + // create the XAdES-T time-stamp + NodeList nlSigVal = document.getElementsByTagNameNS(XML_DIGSIG_NS, "SignatureValue"); + XAdESTimeStampType signatureTimeStamp = addTimestamp(nlSigVal, signatureInfo, unsignedSigProps); + + // Without revocation data service we cannot construct the XAdES-C extension. + RevocationDataService revDataSvc = signatureConfig.getRevocationDataService(); + if (revDataSvc != null) { + // XAdES-C: complete certificate refs + CompleteCertificateRefsType completeCertificateRefs = completeCertificateRefs(unsignedSigProps, signatureConfig); + + // XAdES-C: complete revocation refs + RevocationData revocationData = revDataSvc.getRevocationData(signatureConfig.getSigningCertificateChain()); + CompleteRevocationRefsType completeRevocationRefs = unsignedSigProps.addNewCompleteRevocationRefs(); + addRevocationCRL(completeRevocationRefs, signatureConfig, revocationData); + addRevocationOCSP(completeRevocationRefs, signatureConfig, revocationData); + + // XAdES-X Type 1 timestamp + addTimestampX(unsignedSigProps, signatureInfo, nlSigVal, signatureTimeStamp, completeCertificateRefs, completeRevocationRefs); + + // XAdES-X-L + addCertificateValues(unsignedSigProps, signatureConfig); + + RevocationValuesType revocationValues = unsignedSigProps.addNewRevocationValues(); + createRevocationValues(revocationValues, revocationData); } - UnsignedSignaturePropertiesType unsignedSigProps = unsignedProps.getUnsignedSignatureProperties(); - if (unsignedSigProps == null) { - unsignedSigProps = unsignedProps.addNewUnsignedSignatureProperties(); + + // marshal XAdES-X-L + Node n = document.importNode(qualProps.getDomNode(), true); + qualNl.item(0).getParentNode().replaceChild(n, qualNl.item(0)); + } + + private QualifyingPropertiesType getQualProps(NodeList qualNl) throws MarshalException { + // check for XAdES-BES + if (qualNl.getLength() != 1) { + throw new MarshalException("no XAdES-BES extension present"); } + try { + Node first = qualNl.item(0); + QualifyingPropertiesDocument qualDoc = QualifyingPropertiesDocument.Factory.parse(first, DEFAULT_XML_OPTIONS); + return qualDoc.getQualifyingProperties(); + } catch (XmlException e) { + throw new MarshalException(e); + } + } - // create the XAdES-T time-stamp - NodeList nlSigVal = document.getElementsByTagNameNS(XML_DIGSIG_NS, "SignatureValue"); + private XAdESTimeStampType addTimestamp(NodeList nlSigVal, SignatureInfo signatureInfo, UnsignedSignaturePropertiesType unsignedSigProps) { if (nlSigVal.getLength() != 1) { throw new IllegalArgumentException("SignatureValue is not set."); } @@ -151,17 +179,11 @@ public class XAdESXLSignatureFacet implements SignatureFacet { insertXChild(unsignedSigProps, validationData); } - if (signatureConfig.getRevocationDataService() == null) { - /* - * Without revocation data service we cannot construct the XAdES-C - * extension. - */ - return; - } + return signatureTimeStamp; + } - // XAdES-C: complete certificate refs - CompleteCertificateRefsType completeCertificateRefs = - unsignedSigProps.addNewCompleteCertificateRefs(); + private CompleteCertificateRefsType completeCertificateRefs(UnsignedSignaturePropertiesType unsignedSigProps, SignatureConfig signatureConfig) { + CompleteCertificateRefsType completeCertificateRefs = unsignedSigProps.addNewCompleteCertificateRefs(); CertIDListType certIdList = completeCertificateRefs.addNewCertRefs(); /* @@ -169,19 +191,14 @@ public class XAdESXLSignatureFacet implements SignatureFacet { * 4.4.3.2 of the XAdES 1.4.1 specification. */ List certChain = signatureConfig.getSigningCertificateChain(); - int chainSize = certChain.size(); - if (chainSize > 1) { - for (X509Certificate cert : certChain.subList(1, chainSize)) { - CertIDType certId = certIdList.addNewCert(); - XAdESSignatureFacet.setCertID(certId, signatureConfig, false, cert); - } - } + certChain.stream().skip(1).forEachOrdered(cert -> + XAdESSignatureFacet.setCertID(certIdList.addNewCert(), signatureConfig, false, cert) + ); + + return completeCertificateRefs; + } - // XAdES-C: complete revocation refs - CompleteRevocationRefsType completeRevocationRefs = - unsignedSigProps.addNewCompleteRevocationRefs(); - RevocationData revocationData = signatureConfig.getRevocationDataService() - .getRevocationData(certChain); + private void addRevocationCRL(CompleteRevocationRefsType completeRevocationRefs, SignatureConfig signatureConfig, RevocationData revocationData) { if (revocationData.hasCRLs()) { CRLRefsType crlRefs = completeRevocationRefs.addNewCRLRefs(); completeRevocationRefs.setCRLRefs(crlRefs); @@ -191,10 +208,9 @@ public class XAdESXLSignatureFacet implements SignatureFacet { X509CRL crl; try { crl = (X509CRL) this.certificateFactory - .generateCRL(new UnsynchronizedByteArrayInputStream(encodedCrl)); + .generateCRL(new UnsynchronizedByteArrayInputStream(encodedCrl)); } catch (CRLException e) { - throw new RuntimeException("CRL parse error: " - + e.getMessage(), e); + throw new RuntimeException("CRL parse error: " + e.getMessage(), e); } CRLIdentifierType crlIdentifier = crlRef.addNewCRLIdentifier(); @@ -209,6 +225,9 @@ public class XAdESXLSignatureFacet implements SignatureFacet { XAdESSignatureFacet.setDigestAlgAndValue(digestAlgAndValue, encodedCrl, signatureConfig.getDigestAlgo()); } } + } + + private void addRevocationOCSP(CompleteRevocationRefsType completeRevocationRefs, SignatureConfig signatureConfig, RevocationData revocationData) { if (revocationData.hasOCSPs()) { OCSPRefsType ocspRefs = completeRevocationRefs.addNewOCSPRefs(); for (byte[] ocsp : revocationData.getOCSPs()) { @@ -247,10 +266,11 @@ public class XAdESXLSignatureFacet implements SignatureFacet { } } } + } - // marshal XAdES-C + private void addTimestampX(UnsignedSignaturePropertiesType unsignedSigProps, SignatureInfo signatureInfo, NodeList nlSigVal, XAdESTimeStampType signatureTimeStamp, + CompleteCertificateRefsType completeCertificateRefs, CompleteRevocationRefsType completeRevocationRefs) { - // XAdES-X Type 1 timestamp List timeStampNodesXadesX1 = new ArrayList<>(); timeStampNodesXadesX1.add(nlSigVal.item(0)); timeStampNodesXadesX1.add(signatureTimeStamp.getDomNode()); @@ -269,9 +289,11 @@ public class XAdESXLSignatureFacet implements SignatureFacet { // marshal XAdES-X unsignedSigProps.addNewSigAndRefsTimeStamp().set(timeStampXadesX1); - // XAdES-X-L + } + + private void addCertificateValues(UnsignedSignaturePropertiesType unsignedSigProps, SignatureConfig signatureConfig) { CertificateValuesType certificateValues = unsignedSigProps.addNewCertificateValues(); - for (X509Certificate certificate : certChain) { + for (X509Certificate certificate : signatureConfig.getSigningCertificateChain()) { EncapsulatedPKIDataType encapsulatedPKIDataType = certificateValues.addNewEncapsulatedX509Certificate(); try { encapsulatedPKIDataType.setByteArrayValue(certificate.getEncoded()); @@ -279,16 +301,9 @@ public class XAdESXLSignatureFacet implements SignatureFacet { throw new RuntimeException("certificate encoding error: " + e.getMessage(), e); } } - - RevocationValuesType revocationValues = unsignedSigProps.addNewRevocationValues(); - createRevocationValues(revocationValues, revocationData); - - // marshal XAdES-X-L - Node n = document.importNode(qualProps.getDomNode(), true); - qualNl.item(0).getParentNode().replaceChild(n, qualNl.item(0)); } - public static byte[] getC14nValue(List nodeList, String c14nAlgoId) { + private static byte[] getC14nValue(List nodeList, String c14nAlgoId) { try (UnsynchronizedByteArrayOutputStream c14nValue = new UnsynchronizedByteArrayOutputStream()) { for (Node node : nodeList) { /* diff --git a/poi-ooxml/src/test/java/org/apache/poi/poifs/crypt/dsig/TestSignatureInfo.java b/poi-ooxml/src/test/java/org/apache/poi/poifs/crypt/dsig/TestSignatureInfo.java index 502a125613..9d240ef4be 100644 --- a/poi-ooxml/src/test/java/org/apache/poi/poifs/crypt/dsig/TestSignatureInfo.java +++ b/poi-ooxml/src/test/java/org/apache/poi/poifs/crypt/dsig/TestSignatureInfo.java @@ -94,6 +94,8 @@ import org.apache.poi.poifs.crypt.HashAlgorithm; import org.apache.poi.poifs.crypt.dsig.facets.EnvelopedSignatureFacet; import org.apache.poi.poifs.crypt.dsig.facets.KeyInfoSignatureFacet; import org.apache.poi.poifs.crypt.dsig.facets.OOXMLSignatureFacet; +import org.apache.poi.poifs.crypt.dsig.facets.Office2010SignatureFacet; +import org.apache.poi.poifs.crypt.dsig.facets.SignatureFacet; import org.apache.poi.poifs.crypt.dsig.facets.XAdESSignatureFacet; import org.apache.poi.poifs.crypt.dsig.facets.XAdESXLSignatureFacet; import org.apache.poi.poifs.crypt.dsig.services.RevocationData; @@ -116,6 +118,7 @@ import org.apache.poi.xwpf.usermodel.XWPFDocument; import org.apache.poi.xwpf.usermodel.XWPFHyperlinkRun; import org.apache.poi.xwpf.usermodel.XWPFSignatureLine; import org.apache.xmlbeans.SystemProperties; +import org.apache.xmlbeans.XmlCursor; import org.apache.xmlbeans.XmlException; import org.apache.xmlbeans.XmlObject; import org.bouncycastle.asn1.DEROctetString; @@ -156,7 +159,11 @@ import org.bouncycastle.operator.OperatorCreationException; import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder; import org.etsi.uri.x01903.v13.DigestAlgAndValueType; +import org.etsi.uri.x01903.v13.EncapsulatedPKIDataType; import org.etsi.uri.x01903.v13.QualifyingPropertiesType; +import org.etsi.uri.x01903.v13.UnsignedPropertiesType; +import org.etsi.uri.x01903.v13.UnsignedSignaturePropertiesType; +import org.etsi.uri.x01903.v13.XAdESTimeStampType; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Disabled; @@ -164,6 +171,7 @@ import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; +import org.w3.x2000.x09.xmldsig.ObjectType; import org.w3.x2000.x09.xmldsig.ReferenceType; import org.w3.x2000.x09.xmldsig.SignatureDocument; import org.w3c.dom.Document; @@ -923,6 +931,89 @@ class TestSignatureInfo { POIXMLDocument init(SignatureLine line, OPCPackage pkg) throws IOException, XmlException; } + @Test + void createXAdES_T_65623() throws Exception { + initKeyPair(); + + UnsynchronizedByteArrayOutputStream bos = new UnsynchronizedByteArrayOutputStream(); + try (XSSFWorkbook wb = new XSSFWorkbook()) { + wb.createSheet().createRow(0).createCell(0).setCellValue("Test"); + wb.write(bos); + } + + SignatureConfig signatureConfig = new SignatureConfig(); + signatureConfig.setDigestAlgo(HashAlgorithm.sha256); + signatureConfig.setKey(keyPair.getPrivate()); + signatureConfig.setSigningCertificateChain(Collections.singletonList(x509)); + + // mock tsp + // signatureConfig.setTspUrl("http://timestamp.digicert.com"); + final X509CRL crl = generateCrl(x509, keyPair.getPrivate()); + TimeStampService tspService = (signatureInfo, data, revocationData) -> { + revocationData.addCRL(crl); + return "time-stamp-token".getBytes(LocaleUtil.CHARSET_1252); + }; + signatureConfig.setTspService(tspService); + + signatureConfig.setTspRequestPolicy(null); // comodoca request fails, if default policy is set ... + signatureConfig.setTspOldProtocol(false); + + signatureConfig.setXadesDigestAlgo(HashAlgorithm.sha512); + signatureConfig.setXadesRole("Xades Reviewer"); + signatureConfig.setSignatureDescription("test xades signature"); + + signatureConfig.setSignatureFacets(Arrays.asList( + new OOXMLSignatureFacet(), + new KeyInfoSignatureFacet(), + new XAdESSignatureFacet(), + new Office2010SignatureFacet(), + new XAdESXLSignatureFacet() + )); + + // create signature + try (OPCPackage pkg = OPCPackage.open(bos.toInputStream())) { + SignatureInfo si = new SignatureInfo(); + si.setOpcPackage(pkg); + si.setSignatureConfig(signatureConfig); + si.confirmSignature(); + + bos.reset(); + pkg.save(bos); + } catch (EncryptedDocumentException e) { + assumeTrue(e.getMessage().startsWith("Export Restrictions")); + } + + // check if timestamp node is filled + try (OPCPackage pkg = OPCPackage.open(bos.toInputStream())) { + SignatureInfo si = new SignatureInfo(); + si.setOpcPackage(pkg); + si.setSignatureConfig(signatureConfig); + assertTrue(si.verifySignature()); + boolean found = false; + for (SignaturePart sp : si.getSignatureParts()) { + for (ObjectType ot : sp.getSignatureDocument().getSignature().getObjectArray()) { + XmlCursor xc = ot.newCursor(); + if (xc.toChild(SignatureFacet.XADES_132_NS, "QualifyingProperties")) { + QualifyingPropertiesType qpt = (QualifyingPropertiesType) xc.getObject(); + assertTrue(qpt.isSetUnsignedProperties()); + UnsignedPropertiesType up = qpt.getUnsignedProperties(); + assertTrue(up.isSetUnsignedSignatureProperties()); + UnsignedSignaturePropertiesType ups = up.getUnsignedSignatureProperties(); + assertEquals(1, ups.sizeOfSignatureTimeStampArray()); + XAdESTimeStampType ts = ups.getSignatureTimeStampArray(0); + assertEquals(1, ts.sizeOfEncapsulatedTimeStampArray()); + EncapsulatedPKIDataType ets = ts.getEncapsulatedTimeStampArray(0); + assertFalse(ets.getStringValue().isEmpty()); + found = true; + } + xc.dispose(); + } + } + assertTrue(found); + } + } + + @Test @DisabledOnJreEx("1.8.0_292") @Tag("scratchpad.ignore")