diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureInfo.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureInfo.java index cd01d60c0..6ebb78439 100644 --- a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureInfo.java +++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureInfo.java @@ -44,8 +44,10 @@ import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.NoSuchElementException; import javax.crypto.Cipher; import javax.xml.crypto.MarshalException; @@ -142,6 +144,58 @@ public class SignatureInfo implements SignatureConfigurable { public static final byte[] RIPEMD256_DIGEST_INFO_PREFIX = new byte[] { 0x30, 0x2b, 0x30, 0x07, 0x06, 0x05, 0x2b, 0x24, 0x03, 0x02, 0x03, 0x04, 0x20 }; + + private static final POILogger LOG = POILogFactory.getLogger(SignatureInfo.class); + private static boolean isInitialized = false; + + private SignatureConfig signatureConfig; + + public class SignaturePart { + private final PackagePart signaturePart; + private X509Certificate signer; + + private SignaturePart(PackagePart signaturePart) { + this.signaturePart = signaturePart; + } + + public PackagePart getPackagePart() { + return signaturePart; + } + + public X509Certificate getSigner() { + return signer; + } + + public SignatureDocument getSignatureDocument() throws IOException, XmlException { + // TODO: check for XXE + return SignatureDocument.Factory.parse(signaturePart.getInputStream()); + } + + public boolean validate() { + KeyInfoKeySelector keySelector = new KeyInfoKeySelector(); + try { + Document doc = DocumentHelper.readDocument(signaturePart.getInputStream()); + registerIds(doc); + + DOMValidateContext domValidateContext = new DOMValidateContext(keySelector, doc); + domValidateContext.setProperty("org.jcp.xml.dsig.validateManifests", Boolean.TRUE); + domValidateContext.setURIDereferencer(signatureConfig.getUriDereferencer()); + + XMLSignatureFactory xmlSignatureFactory = getSignatureFactory(); + XMLSignature xmlSignature = xmlSignatureFactory.unmarshalXMLSignature(domValidateContext); + boolean valid = xmlSignature.validate(domValidateContext); + + if (valid) { + signer = keySelector.getCertificate(); + } + + return valid; + } catch (Exception e) { + LOG.log(POILogger.ERROR, "error in marshalling and validating the signature", e); + return false; + } + } + } protected static class SignCreationListener implements EventListener, SignatureConfigurable { ThreadLocal target = new ThreadLocal(); @@ -168,11 +222,10 @@ public class SignatureInfo implements SignatureConfigurable { } - private static final POILogger LOG = POILogFactory.getLogger(SignatureInfo.class); - private static boolean isInitialized = false; + public SignatureInfo() { + initXmlProvider(); + } - private SignatureConfig signatureConfig; - public SignatureConfig getSignatureConfig() { return signatureConfig; } @@ -182,10 +235,12 @@ public class SignatureInfo implements SignatureConfigurable { } public boolean verifySignature() { - initXmlProvider(); // http://www.oracle.com/technetwork/articles/javase/dig-signature-api-140772.html - List signers = new ArrayList(); - return getSignersAndValidate(signers, true); + for (SignaturePart sp : getSignatureParts()){ + // only validate first part + return sp.validate(); + } + return false; } public void confirmSignature() @@ -218,77 +273,50 @@ public class SignatureInfo implements SignatureConfigurable { } } - public List getSigners() { - initXmlProvider(); - List signers = new ArrayList(); - getSignersAndValidate(signers, false); - return signers; - } - - protected boolean getSignersAndValidate(List signers, boolean onlyFirst) { - signatureConfig.init(true); - - boolean allValid = true; - List signatureParts = getSignatureParts(onlyFirst); - if (signatureParts.isEmpty()) { - LOG.log(POILogger.DEBUG, "no signature resources"); - allValid = false; - } - - for (PackagePart signaturePart : signatureParts) { - KeyInfoKeySelector keySelector = new KeyInfoKeySelector(); - - try { - Document doc = DocumentHelper.readDocument(signaturePart.getInputStream()); - registerIds(doc); - - DOMValidateContext domValidateContext = new DOMValidateContext(keySelector, doc); - domValidateContext.setProperty("org.jcp.xml.dsig.validateManifests", Boolean.TRUE); - domValidateContext.setURIDereferencer(signatureConfig.getUriDereferencer()); - - XMLSignatureFactory xmlSignatureFactory = getSignatureFactory(); - XMLSignature xmlSignature = xmlSignatureFactory.unmarshalXMLSignature(domValidateContext); - boolean validity = xmlSignature.validate(domValidateContext); - allValid &= validity; - if (!validity) continue; - // TODO: check what has been signed. - } catch (Exception e) { - LOG.log(POILogger.ERROR, "error in marshalling and validating the signature", e); - continue; + public Iterable getSignatureParts() { + return new Iterable() { + public Iterator iterator() { + return new Iterator() { + OPCPackage pkg = signatureConfig.getOpcPackage(); + Iterator sigOrigRels = + pkg.getRelationshipsByType(PackageRelationshipTypes.DIGITAL_SIGNATURE_ORIGIN).iterator(); + Iterator sigRels = null; + PackagePart sigPart = null; + + public boolean hasNext() { + while (sigRels == null || !sigRels.hasNext()) { + if (!sigOrigRels.hasNext()) return false; + sigPart = pkg.getPart(sigOrigRels.next()); + LOG.log(POILogger.DEBUG, "Digital Signature Origin part", sigPart); + try { + sigRels = sigPart.getRelationshipsByType(PackageRelationshipTypes.DIGITAL_SIGNATURE).iterator(); + } catch (InvalidFormatException e) { + LOG.log(POILogger.WARN, "Reference to signature is invalid.", e); + } + } + return true; + } + + public SignaturePart next() { + PackagePart sigRelPart = null; + do { + try { + if (!hasNext()) throw new NoSuchElementException(); + sigRelPart = sigPart.getRelatedPart(sigRels.next()); + LOG.log(POILogger.DEBUG, "XML Signature part", sigRelPart); + } catch (InvalidFormatException e) { + LOG.log(POILogger.WARN, "Reference to signature is invalid.", e); + } + } while (sigPart == null); + return new SignaturePart(sigRelPart); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + }; } - - X509Certificate signer = keySelector.getCertificate(); - signers.add(signer); - } - - return allValid; - } - - protected List getSignatureParts(boolean onlyFirst) { - List packageParts = new ArrayList(); - OPCPackage pkg = signatureConfig.getOpcPackage(); - - PackageRelationshipCollection sigOrigRels = pkg.getRelationshipsByType(PackageRelationshipTypes.DIGITAL_SIGNATURE_ORIGIN); - for (PackageRelationship rel : sigOrigRels) { - PackagePart sigPart = pkg.getPart(rel); - LOG.log(POILogger.DEBUG, "Digital Signature Origin part", sigPart); - - try { - PackageRelationshipCollection sigRels = sigPart.getRelationshipsByType(PackageRelationshipTypes.DIGITAL_SIGNATURE); - for (PackageRelationship sigRel : sigRels) { - PackagePart sigRelPart = sigPart.getRelatedPart(sigRel); - LOG.log(POILogger.DEBUG, "XML Signature part", sigRelPart); - packageParts.add(sigRelPart); - if (onlyFirst) break; - } - } catch (InvalidFormatException e) { - LOG.log(POILogger.WARN, "Reference to signature is invalid.", e); - } - - if (onlyFirst && !packageParts.isEmpty()) break; - } - - return packageParts; + }; } public static XMLSignatureFactory getSignatureFactory() { diff --git a/src/ooxml/testcases/org/apache/poi/poifs/crypt/TestSignatureInfo.java b/src/ooxml/testcases/org/apache/poi/poifs/crypt/TestSignatureInfo.java index 4eca018a5..77299c937 100644 --- a/src/ooxml/testcases/org/apache/poi/poifs/crypt/TestSignatureInfo.java +++ b/src/ooxml/testcases/org/apache/poi/poifs/crypt/TestSignatureInfo.java @@ -45,19 +45,16 @@ import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; import java.util.Date; +import java.util.Iterator; import java.util.List; import java.util.TimeZone; -import javax.xml.crypto.KeySelector; -import javax.xml.crypto.dsig.XMLSignature; -import javax.xml.crypto.dsig.XMLSignatureFactory; -import javax.xml.crypto.dsig.dom.DOMValidateContext; - import org.apache.poi.POIDataSamples; import org.apache.poi.openxml4j.opc.OPCPackage; import org.apache.poi.openxml4j.opc.PackageAccess; import org.apache.poi.poifs.crypt.dsig.SignatureConfig; import org.apache.poi.poifs.crypt.dsig.SignatureInfo; +import org.apache.poi.poifs.crypt.dsig.SignatureInfo.SignaturePart; 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.XAdESSignatureFacet; @@ -78,6 +75,7 @@ import org.etsi.uri.x01903.v13.DigestAlgAndValueType; import org.etsi.uri.x01903.v13.QualifyingPropertiesType; import org.junit.BeforeClass; import org.junit.Test; +import org.w3.x2000.x09.xmldsig.ReferenceType; import org.w3.x2000.x09.xmldsig.SignatureDocument; import org.w3c.dom.Document; @@ -122,7 +120,12 @@ public class TestSignatureInfo { sic.setOpcPackage(pkg); SignatureInfo si = new SignatureInfo(); si.setSignatureConfig(sic); - List result = si.getSigners(); + List result = new ArrayList(); + for (SignaturePart sp : si.getSignatureParts()) { + if (sp.validate()) { + result.add(sp.getSigner()); + } + } pkg.revert(); pkg.close(); assertNotNull(result); @@ -151,7 +154,12 @@ public class TestSignatureInfo { sic.setOpcPackage(pkg); SignatureInfo si = new SignatureInfo(); si.setSignatureConfig(sic); - List result = si.getSigners(); + List result = new ArrayList(); + for (SignaturePart sp : si.getSignatureParts()) { + if (sp.validate()) { + result.add(sp.getSigner()); + } + } assertNotNull(result); assertEquals("test-file: "+testFile, 1, result.size()); @@ -172,7 +180,12 @@ public class TestSignatureInfo { sic.setOpcPackage(pkg); SignatureInfo si = new SignatureInfo(); si.setSignatureConfig(sic); - List result = si.getSigners(); + List result = new ArrayList(); + for (SignaturePart sp : si.getSignatureParts()) { + if (sp.validate()) { + result.add(sp.getSigner()); + } + } assertNotNull(result); assertEquals("test-file: "+testFile, 2, result.size()); @@ -207,12 +220,16 @@ public class TestSignatureInfo { si.setSignatureConfig(sic); // hash > sha1 doesn't work in excel viewer ... si.confirmSignature(); - List signer = si.getSigners(); - assertEquals(1, signer.size()); + List result = new ArrayList(); + for (SignaturePart sp : si.getSignatureParts()) { + if (sp.validate()) { + result.add(sp.getSigner()); + } + } + assertEquals(1, result.size()); pkg.close(); } - @SuppressWarnings("unused") @Test public void testSignEnvelopingDocument() throws Exception { String testFile = "hello-world-unsigned.xlsx"; @@ -283,60 +300,45 @@ public class TestSignatureInfo { }; signatureConfig.setRevocationDataService(revocationDataService); + // operate SignatureInfo si = new SignatureInfo(); si.setSignatureConfig(signatureConfig); + si.confirmSignature(); - Document document = DocumentHelper.createDocument(); - - // operate - DigestInfo digestInfo = si.preSign(document, null); - // verify - assertNotNull(digestInfo); - assertEquals(HashAlgorithm.sha1, digestInfo.hashAlgo); - assertNotNull(digestInfo.digestValue); + Iterator spIter = si.getSignatureParts().iterator(); + assertTrue(spIter.hasNext()); + SignaturePart sp = spIter.next(); + boolean valid = sp.validate(); + assertTrue(valid); - SignatureDocument sigDoc = SignatureDocument.Factory.parse(document); - String certDigestXQuery = - "declare namespace xades='http://uri.etsi.org/01903/v1.3.2#'; " - + "declare namespace ds='http://www.w3.org/2000/09/xmldsig#'; " - + "$this/ds:Signature/ds:Object/xades:QualifyingProperties/xades:SignedProperties/xades:SignedSignatureProperties/xades:SigningCertificate/xades:Cert/xades:CertDigest"; + SignatureDocument sigDoc = sp.getSignatureDocument(); + String declareNS = + "declare namespace xades='http://uri.etsi.org/01903/v1.3.2#'; " + + "declare namespace ds='http://www.w3.org/2000/09/xmldsig#'; "; + + String digestValXQuery = declareNS + + "$this/ds:Signature/ds:SignedInfo/ds:Reference"; + for (ReferenceType rt : (ReferenceType[])sigDoc.selectPath(digestValXQuery)) { + assertNotNull(rt.getDigestValue()); + assertEquals(HashAlgorithm.sha1.xmlSignUri, rt.getDigestMethod().getAlgorithm()); + } + + String certDigestXQuery = declareNS + + "$this//xades:SigningCertificate/xades:Cert/xades:CertDigest"; XmlObject xoList[] = sigDoc.selectPath(certDigestXQuery); assertEquals(xoList.length, 1); DigestAlgAndValueType certDigest = (DigestAlgAndValueType)xoList[0]; assertNotNull(certDigest.getDigestValue()); - // Sign the received XML signature digest value. - byte[] signatureValue = si.signDigest(digestInfo.digestValue); - - // Operate: postSign - si.postSign(document, signatureValue); - - DOMValidateContext domValidateContext = new DOMValidateContext( - KeySelector.singletonKeySelector(keyPair.getPublic()), - document); - XMLSignatureFactory xmlSignatureFactory = SignatureInfo.getSignatureFactory(); - XMLSignature xmlSignature = xmlSignatureFactory - .unmarshalXMLSignature(domValidateContext); - boolean validity = xmlSignature.validate(domValidateContext); - assertTrue(validity); - - sigDoc = SignatureDocument.Factory.parse(document); - xoList = sigDoc.selectPath(certDigestXQuery); - assertEquals(xoList.length, 1); - certDigest = (DigestAlgAndValueType)xoList[0]; - assertNotNull(certDigest.getDigestValue()); - - String qualPropXQuery = - "declare namespace xades='http://uri.etsi.org/01903/v1.3.2#'; " - + "declare namespace ds='http://www.w3.org/2000/09/xmldsig#'; " - + "$this/ds:Signature/ds:Object/xades:QualifyingProperties"; + String qualPropXQuery = declareNS + + "$this/ds:Signature/ds:Object/xades:QualifyingProperties"; xoList = sigDoc.selectPath(qualPropXQuery); assertEquals(xoList.length, 1); QualifyingPropertiesType qualProp = (QualifyingPropertiesType)xoList[0]; boolean qualPropXsdOk = qualProp.validate(); assertTrue(qualPropXsdOk); - + pkg.close(); } @@ -374,8 +376,13 @@ public class TestSignatureInfo { // verify: signature si.getSignatureConfig().setOpcPackage(pkgCopy); - List signers = si.getSigners(); - assertEquals(signerCount, signers.size()); + List result = new ArrayList(); + for (SignaturePart sp : si.getSignatureParts()) { + if (sp.validate()) { + result.add(sp.getSigner()); + } + } + assertEquals(signerCount, result.size()); return pkgCopy; }