mirror of https://github.com/apache/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
This commit is contained in:
parent
91f423e33c
commit
60e9de813e
|
@ -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) {
|
||||
/*
|
||||
|
|
|
@ -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")
|
||||
|
|
Loading…
Reference in New Issue