#65623 - Create XAdES-T signature with XAdESXLSignatureFacet

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1894049 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Andreas Beeker 2021-10-08 23:08:51 +00:00
parent 91f423e33c
commit 60e9de813e
2 changed files with 168 additions and 62 deletions

View File

@ -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();
}
UnsignedSignaturePropertiesType unsignedSigProps = unsignedProps.getUnsignedSignatureProperties();
if (unsignedSigProps == null) {
unsignedSigProps = unsignedProps.addNewUnsignedSignatureProperties();
}
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);
}
// 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);
}
}
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<X509Certificate> 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)
);
// XAdES-C: complete revocation refs
CompleteRevocationRefsType completeRevocationRefs =
unsignedSigProps.addNewCompleteRevocationRefs();
RevocationData revocationData = signatureConfig.getRevocationDataService()
.getRevocationData(certChain);
return completeCertificateRefs;
}
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<Node> 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<Node> nodeList, String c14nAlgoId) {
private static byte[] getC14nValue(List<Node> nodeList, String c14nAlgoId) {
try (UnsynchronizedByteArrayOutputStream c14nValue = new UnsynchronizedByteArrayOutputStream()) {
for (Node node : nodeList) {
/*

View File

@ -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")