/* * eID Applet Project. * Copyright (C) 2009 FedICT. * Copyright (C) 2015 e-Contract.be BVBA. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License version * 3.0 as published by the Free Software Foundation. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, see * http://www.gnu.org/licenses/. */ /* * Copyright (C) 2008-2009 FedICT. * This file is part of the eID Applet Project. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package be.fedict.eid.applet.service.signer.ooxml; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.security.cert.X509Certificate; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBElement; import javax.xml.bind.JAXBException; import javax.xml.bind.Unmarshaller; import javax.xml.crypto.MarshalException; import javax.xml.crypto.XMLStructure; import javax.xml.crypto.dom.DOMStructure; import javax.xml.crypto.dsig.CanonicalizationMethod; import javax.xml.crypto.dsig.Manifest; import javax.xml.crypto.dsig.Reference; import javax.xml.crypto.dsig.SignatureProperties; import javax.xml.crypto.dsig.SignatureProperty; import javax.xml.crypto.dsig.XMLObject; import javax.xml.crypto.dsig.XMLSignature; import javax.xml.crypto.dsig.XMLSignatureException; import javax.xml.crypto.dsig.XMLSignatureFactory; import javax.xml.crypto.dsig.dom.DOMValidateContext; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.TransformerException; import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.xml.security.utils.Constants; import org.apache.xpath.XPathAPI; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; import be.fedict.eid.applet.service.signer.KeyInfoKeySelector; import be.fedict.eid.applet.service.signer.facets.XAdESXLSignatureFacet; import be.fedict.eid.applet.service.signer.jaxb.opc.contenttypes.CTDefault; import be.fedict.eid.applet.service.signer.jaxb.opc.contenttypes.CTOverride; import be.fedict.eid.applet.service.signer.jaxb.opc.contenttypes.CTTypes; import be.fedict.eid.applet.service.signer.jaxb.opc.relationships.CTRelationship; import be.fedict.eid.applet.service.signer.jaxb.opc.relationships.CTRelationships; import be.fedict.eid.applet.service.signer.jaxb.opc.relationships.ObjectFactory; import be.fedict.eid.applet.service.signer.jaxb.opc.relationships.STTargetMode; /** * Signature verifier util class for Office Open XML file format. * <p/> * Implementation according to: Office Open XML - Part 2: Open Packaging * Conventions - ECMA-376-2 * * @author Frank Cornelis */ public class OOXMLSignatureVerifier { private static final Log LOG = LogFactory.getLog(OOXMLSignatureVerifier.class); public static final String DIGITAL_SIGNATURE_ORIGIN_REL_TYPE = "http://schemas.openxmlformats.org/package/2006/relationships/digital-signature/origin"; public static final String DIGITAL_SIGNATURE_REL_TYPE = "http://schemas.openxmlformats.org/package/2006/relationships/digital-signature/signature"; private final Unmarshaller relationshipsUnmarshaller; public OOXMLSignatureVerifier() { try { JAXBContext relationshipsJAXBContext = JAXBContext.newInstance(ObjectFactory.class); this.relationshipsUnmarshaller = relationshipsJAXBContext.createUnmarshaller(); } catch (JAXBException e) { throw new RuntimeException("JAXB error: " + e.getMessage(), e); } } /** * Checks whether the file referred by the given URL is an OOXML document. * * @param url * @return * @throws IOException */ public static boolean isOOXML(URL url) throws IOException { ZipInputStream zipInputStream = new ZipInputStream(url.openStream()); ZipEntry zipEntry; while (null != (zipEntry = zipInputStream.getNextEntry())) { if (false == "[Content_Types].xml".equals(zipEntry.getName())) { continue; } return true; } return false; } public List<X509Certificate> getSigners(URL url) throws IOException, ParserConfigurationException, SAXException, TransformerException, MarshalException, XMLSignatureException, JAXBException { List<X509Certificate> signers = new LinkedList<X509Certificate>(); List<String> signatureResourceNames = getSignatureResourceNames(url); if (signatureResourceNames.isEmpty()) { LOG.debug("no signature resources"); } for (String signatureResourceName : signatureResourceNames) { Document signatureDocument = getSignatureDocument(url, signatureResourceName); if (null == signatureDocument) { continue; } NodeList signatureNodeList = signatureDocument.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature"); if (0 == signatureNodeList.getLength()) { return null; } Node signatureNode = signatureNodeList.item(0); // work-around for Java 7 Element signedPropertiesElement = (Element) ((Element) signatureNode) .getElementsByTagNameNS(XAdESXLSignatureFacet.XADES_NAMESPACE, "SignedProperties").item(0); if (null != signedPropertiesElement) { signedPropertiesElement.setIdAttribute("Id", true); } KeyInfoKeySelector keySelector = new KeyInfoKeySelector(); DOMValidateContext domValidateContext = new DOMValidateContext(keySelector, signatureNode); domValidateContext.setProperty("org.jcp.xml.dsig.validateManifests", Boolean.TRUE); OOXMLURIDereferencer dereferencer = new OOXMLURIDereferencer(url); domValidateContext.setURIDereferencer(dereferencer); XMLSignatureFactory xmlSignatureFactory = XMLSignatureFactory.getInstance(); XMLSignature xmlSignature = xmlSignatureFactory.unmarshalXMLSignature(domValidateContext); boolean valid = xmlSignature.validate(domValidateContext); if (!valid) { LOG.debug("not a valid signature"); continue; } /* * Check the content of idPackageObject. */ List<XMLObject> objects = xmlSignature.getObjects(); XMLObject idPackageObject = null; for (XMLObject object : objects) { if ("idPackageObject".equals(object.getId())) { idPackageObject = object; break; } } if (null == idPackageObject) { LOG.debug("idPackageObject ds:Object not present"); continue; } List<XMLStructure> idPackageObjectContent = idPackageObject.getContent(); Manifest idPackageObjectManifest = null; for (XMLStructure content : idPackageObjectContent) { if (content instanceof Manifest) { idPackageObjectManifest = (Manifest) content; break; } } if (null == idPackageObjectManifest) { LOG.debug("no ds:Manifest present within idPackageObject ds:Object"); continue; } LOG.debug("ds:Manifest present within idPackageObject ds:Object"); List<Reference> idPackageObjectReferences = idPackageObjectManifest.getReferences(); Set<String> idPackageObjectReferenceUris = new HashSet<String>(); Set<String> remainingIdPackageObjectReferenceUris = new HashSet<String>(); for (Reference idPackageObjectReference : idPackageObjectReferences) { idPackageObjectReferenceUris.add(idPackageObjectReference.getURI()); remainingIdPackageObjectReferenceUris.add(idPackageObjectReference.getURI()); } LOG.debug("idPackageObject ds:Reference URIs: " + idPackageObjectReferenceUris); CTTypes contentTypes = getContentTypes(url); List<String> relsEntryNames = getRelsEntryNames(url); for (String relsEntryName : relsEntryNames) { LOG.debug("---- relationship entry name: " + relsEntryName); CTRelationships relationships = getRelationships(url, relsEntryName); List<CTRelationship> relationshipList = relationships.getRelationship(); boolean includeRelationshipInSignature = false; for (CTRelationship relationship : relationshipList) { String relationshipType = relationship.getType(); STTargetMode targetMode = relationship.getTargetMode(); if (null != targetMode) { LOG.debug("TargetMode: " + targetMode.name()); if (targetMode == STTargetMode.EXTERNAL) { /* * ECMA-376 Part 2 - 3rd edition * * 13.2.4.16 Manifest Element * * "The producer shall not create a Manifest element that references any data outside of the package." */ continue; } } if (false == OOXMLSignatureFacet.isSignedRelationship(relationshipType)) { continue; } String relationshipTarget = relationship.getTarget(); String baseUri = "/" + relsEntryName.substring(0, relsEntryName.indexOf("_rels/")); String streamEntry = baseUri + relationshipTarget; LOG.debug("stream entry: " + streamEntry); streamEntry = FilenameUtils.separatorsToUnix(FilenameUtils.normalize(streamEntry)); LOG.debug("normalized stream entry: " + streamEntry); String contentType = getContentType(contentTypes, streamEntry); if (relationshipType.endsWith("customXml")) { if (false == contentType.equals("inkml+xml") && false == contentType.equals("text/xml")) { LOG.debug("skipping customXml with content type: " + contentType); continue; } } includeRelationshipInSignature = true; LOG.debug("content type: " + contentType); String referenceUri = streamEntry + "?ContentType=" + contentType; LOG.debug("reference URI: " + referenceUri); if (false == idPackageObjectReferenceUris.contains(referenceUri)) { throw new RuntimeException( "no reference in idPackageObject ds:Object for relationship target: " + streamEntry); } remainingIdPackageObjectReferenceUris.remove(referenceUri); } String relsReferenceUri = "/" + relsEntryName + "?ContentType=application/vnd.openxmlformats-package.relationships+xml"; if (includeRelationshipInSignature && false == idPackageObjectReferenceUris.contains(relsReferenceUri)) { LOG.debug("missing ds:Reference for: " + relsEntryName); throw new RuntimeException("missing ds:Reference for: " + relsEntryName); } remainingIdPackageObjectReferenceUris.remove(relsReferenceUri); } if (false == remainingIdPackageObjectReferenceUris.isEmpty()) { LOG.debug("remaining idPackageObject reference URIs" + idPackageObjectReferenceUris); throw new RuntimeException("idPackageObject manifest contains unknown ds:References: " + remainingIdPackageObjectReferenceUris); } X509Certificate signer = keySelector.getCertificate(); signers.add(signer); } return signers; } private String getContentType(CTTypes contentTypes, String partName) { List<Object> defaultOrOverrideList = contentTypes.getDefaultOrOverride(); for (Object defaultOrOverride : defaultOrOverrideList) { if (defaultOrOverride instanceof CTOverride) { CTOverride override = (CTOverride) defaultOrOverride; if (partName.equals(override.getPartName())) { return override.getContentType(); } } } for (Object defaultOrOverride : defaultOrOverrideList) { if (defaultOrOverride instanceof CTDefault) { CTDefault ctDefault = (CTDefault) defaultOrOverride; if (partName.endsWith(ctDefault.getExtension())) { return ctDefault.getContentType(); } } } return null; } private CTRelationships getRelationships(URL url, String relationshipsEntryName) throws IOException, JAXBException { ZipInputStream zipInputStream = new ZipInputStream(url.openStream()); ZipEntry zipEntry; InputStream relationshipsInputStream = null; while (null != (zipEntry = zipInputStream.getNextEntry())) { if (false == relationshipsEntryName.equals(zipEntry.getName())) { continue; } relationshipsInputStream = zipInputStream; break; } if (null == relationshipsInputStream) { return null; } JAXBContext jaxbContext = JAXBContext .newInstance(be.fedict.eid.applet.service.signer.jaxb.opc.relationships.ObjectFactory.class); Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); JAXBElement<CTRelationships> relationshipsElement = (JAXBElement<CTRelationships>) unmarshaller .unmarshal(relationshipsInputStream); return relationshipsElement.getValue(); } private List<String> getRelsEntryNames(URL url) throws IOException { List<String> relsEntryNames = new LinkedList<String>(); ZipInputStream zipInputStream = new ZipInputStream(url.openStream()); ZipEntry zipEntry; while (null != (zipEntry = zipInputStream.getNextEntry())) { String entryName = zipEntry.getName(); if (entryName.endsWith(".rels")) { relsEntryNames.add(entryName); } } return relsEntryNames; } private CTTypes getContentTypes(URL url) throws IOException, ParserConfigurationException, SAXException, JAXBException { ZipInputStream zipInputStream = new ZipInputStream(url.openStream()); ZipEntry zipEntry; InputStream contentTypesInputStream = null; while (null != (zipEntry = zipInputStream.getNextEntry())) { if (!"[Content_Types].xml".equals(zipEntry.getName())) { continue; } contentTypesInputStream = zipInputStream; break; } if (null == contentTypesInputStream) { return null; } JAXBContext jaxbContext = JAXBContext .newInstance(be.fedict.eid.applet.service.signer.jaxb.opc.contenttypes.ObjectFactory.class); Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); JAXBElement<CTTypes> contentTypesElement = (JAXBElement<CTTypes>) unmarshaller .unmarshal(contentTypesInputStream); return contentTypesElement.getValue(); } public Document getSignatureDocument(URL url, String signatureResourceName) throws IOException, ParserConfigurationException, SAXException { return getSignatureDocument(url.openStream(), signatureResourceName); } public Document getSignatureDocument(InputStream documentInputStream, String signatureResourceName) throws IOException, ParserConfigurationException, SAXException { ZipInputStream zipInputStream = new ZipInputStream(documentInputStream); ZipEntry zipEntry; while (null != (zipEntry = zipInputStream.getNextEntry())) { if (!signatureResourceName.equals(zipEntry.getName())) { continue; } return OOXMLSignatureFacet.loadDocument(zipInputStream); } return null; } @SuppressWarnings("unchecked") public List<String> getSignatureResourceNames(byte[] document) throws IOException, JAXBException { List<String> signatureResourceNames = new LinkedList<String>(); ZipInputStream zipInputStream = new ZipInputStream(new ByteArrayInputStream(document)); ZipEntry zipEntry; while (null != (zipEntry = zipInputStream.getNextEntry())) { if ("_rels/.rels".equals(zipEntry.getName())) { break; } } if (null == zipEntry) { LOG.debug("no _rels/.rels relationship part present"); return signatureResourceNames; } String dsOriginPart = null; JAXBElement<CTRelationships> packageRelationshipsElement = (JAXBElement<CTRelationships>) this.relationshipsUnmarshaller .unmarshal(zipInputStream); CTRelationships packageRelationships = packageRelationshipsElement.getValue(); List<CTRelationship> packageRelationshipList = packageRelationships.getRelationship(); for (CTRelationship packageRelationship : packageRelationshipList) { if (DIGITAL_SIGNATURE_ORIGIN_REL_TYPE.equals(packageRelationship.getType())) { dsOriginPart = packageRelationship.getTarget(); break; } } if (null == dsOriginPart) { LOG.debug("no Digital Signature Origin part present"); return signatureResourceNames; } LOG.debug("Digital Signature Origin part: " + dsOriginPart); String dsOriginName = dsOriginPart.substring(dsOriginPart.lastIndexOf("/") + 1); LOG.debug("Digital Signature Origin base: " + dsOriginName); String dsOriginSegment = dsOriginPart.substring(0, dsOriginPart.lastIndexOf("/")) + "/"; LOG.debug("Digital Signature Origin segment: " + dsOriginSegment); String dsOriginRels = dsOriginSegment + "_rels/" + dsOriginName + ".rels"; LOG.debug("Digital Signature Origin relationship part: " + dsOriginRels); zipInputStream = new ZipInputStream(new ByteArrayInputStream(document)); while (null != (zipEntry = zipInputStream.getNextEntry())) { if (dsOriginRels.equals(zipEntry.getName())) { break; } } if (null == zipEntry) { LOG.debug("no Digital Signature Origin relationship part present"); return signatureResourceNames; } JAXBElement<CTRelationships> dsoRelationshipsElement = (JAXBElement<CTRelationships>) this.relationshipsUnmarshaller .unmarshal(zipInputStream); CTRelationships dsoRelationships = dsoRelationshipsElement.getValue(); List<CTRelationship> dsoRelationshipList = dsoRelationships.getRelationship(); for (CTRelationship dsoRelationship : dsoRelationshipList) { if (DIGITAL_SIGNATURE_REL_TYPE.equals(dsoRelationship.getType())) { String signatureResourceName = dsOriginSegment + dsoRelationship.getTarget(); signatureResourceNames.add(signatureResourceName); } } return signatureResourceNames; } @SuppressWarnings("unchecked") public boolean isValidOOXMLSignature(XMLSignature xmlSignature, byte[] document) throws IOException, TransformerException, SAXException, ParserConfigurationException { // check c18n == http://www.w3.org/TR/2001/REC-xml-c14n-20010315 if (!xmlSignature.getSignedInfo().getCanonicalizationMethod().getAlgorithm() .equals(CanonicalizationMethod.INCLUSIVE)) { LOG.error("Invalid c18n method on OOXML Signature"); return false; } List<Reference> refs = xmlSignature.getSignedInfo().getReferences(); // check #idPackageObject reference Reference idPackageObjectRef = findReferenceFromURI(refs, "#idPackageObject"); if (null == idPackageObjectRef) { LOG.error("No \"idPackageObject\" reference found!"); return false; } // check idPackageObject element XMLObject idPackageObject = findObject(xmlSignature, "idPackageObject"); if (null == idPackageObject) { LOG.error("No \"idPackageObject\" object found!"); return false; } if (!isIdPackageObjectValid(xmlSignature.getId(), idPackageObject, document)) { LOG.error("Invalid \"idPackageObject\"."); return false; } // check #idOfficeObject reference Reference idOfficeObjectRef = findReferenceFromURI(refs, "#idOfficeObject"); if (null == idOfficeObjectRef) { LOG.error("No \"idOfficeObject\" reference found!"); return false; } // check idOfficeObject element XMLObject idOfficeObject = findObject(xmlSignature, "idOfficeObject"); if (null == idOfficeObject) { LOG.error("No \"idOfficeObject\" object found!"); return false; } if (!isIdOfficeObjectValid(xmlSignature.getId(), idOfficeObject)) { LOG.error("Invalid \"idOfficeObject\"."); return false; } return true; } @SuppressWarnings("unchecked") private boolean isIdOfficeObjectValid(String signatureId, XMLObject idOfficeObject) { SignatureProperties signatureProperties; if (1 != idOfficeObject.getContent().size()) { LOG.error("Expect SignatureProperties element in \"idPackageObject\"."); return false; } signatureProperties = (SignatureProperties) idOfficeObject.getContent().get(0); if (signatureProperties.getProperties().size() != 1) { LOG.error("Unexpected # of SignatureProperty's in idOfficeObject"); return false; } // SignatureInfo SignatureProperty signatureInfoProperty = (SignatureProperty) signatureProperties.getProperties().get(0); if (!signatureInfoProperty.getId().equals("idOfficeV1Details")) { LOG.error("Unexpected SignatureProperty: expected id=idOfficeV1Details " + "but got: " + signatureInfoProperty.getId()); return false; } if (!signatureInfoProperty.getTarget().equals("#" + signatureId)) { LOG.error("Unexpected SignatureProperty: expected target=#" + signatureId + " but got: " + signatureInfoProperty.getTarget()); LOG.warn("Allowing this error because of a bug in Office2010"); // work-around for existing bug in Office2011 // return false; } // SignatureInfoV1 if (signatureInfoProperty.getContent().size() != 1) { LOG.error("Unexpected content in SignatureInfoProperty."); return false; } DOMStructure signatureInfoV1DOM = (DOMStructure) signatureInfoProperty.getContent().get(0); Node signatureInfoElement = signatureInfoV1DOM.getNode(); if (!signatureInfoElement.getNamespaceURI().equals(OOXMLSignatureFacet.OFFICE_DIGSIG_NS)) { LOG.error("Unexpected SignatureInfoProperty content: NS=" + signatureInfoElement.getNamespaceURI()); return false; } // TODO: validate childs: validate all possible from 2.5.2.5 // ([MS-OFFCRYPTO]) or just ManifestHashAlgorithm? return true; } @SuppressWarnings("unchecked") private boolean isIdPackageObjectValid(String signatureId, XMLObject idPackageObject, byte[] document) throws IOException, TransformerException, SAXException, ParserConfigurationException { Manifest manifest; SignatureProperties signatureProperties; if (2 != idPackageObject.getContent().size()) { LOG.error("Expect Manifest + SignatureProperties elements in \"idPackageObject\"."); return false; } manifest = (Manifest) idPackageObject.getContent().get(0); signatureProperties = (SignatureProperties) idPackageObject.getContent().get(1); // Manifest List<Reference> refs = manifest.getReferences(); ByteArrayInputStream bais = new ByteArrayInputStream(document); ZipInputStream zipInputStream = new ZipInputStream(bais); ZipEntry zipEntry; while (null != (zipEntry = zipInputStream.getNextEntry())) { if (validZipEntryStream(zipEntry.getName())) { // check relationship refs String relationshipReferenceURI = OOXMLSignatureFacet.getRelationshipReferenceURI(zipEntry.getName()); if (null == findReferenceFromURI(refs, relationshipReferenceURI)) { LOG.error("Did not find relationship ref: \"" + relationshipReferenceURI + "\""); if (relationshipReferenceURI.startsWith("/customXml")) { continue; } return false; } } } // check streams signed for (Map.Entry<String, String> resourceEntry : getResources(document).entrySet()) { String resourceReferenceURI = OOXMLSignatureFacet.getResourceReferenceURI(resourceEntry.getKey(), resourceEntry.getValue()); if (null == findReferenceFromURI(refs, resourceReferenceURI)) { LOG.error("Did not find resource ref: \"" + resourceReferenceURI + "\""); return false; } } // SignatureProperties if (signatureProperties.getProperties().size() != 1) { LOG.error("Unexpected # of SignatureProperty's in idPackageObject"); return false; } if (!validateSignatureProperty((SignatureProperty) signatureProperties.getProperties().get(0), signatureId)) { return false; } return true; } @SuppressWarnings("unchecked") private boolean validateSignatureProperty(SignatureProperty signatureProperty, String signatureId) { if (!signatureProperty.getId().equals("idSignatureTime")) { LOG.error("Unexpected SignatureProperty: expected id=idSignatureTime " + "but got: " + signatureProperty.getId()); return false; } if (!signatureProperty.getTarget().equals("#" + signatureId)) { LOG.error("Unexpected SignatureProperty: expected target=#" + signatureId + "but got: " + signatureProperty.getTarget()); return false; } List<XMLStructure> signatureTimeContent = signatureProperty.getContent(); if (signatureTimeContent.size() != 1) { LOG.error("Unexpected SignatureTime content."); return false; } DOMStructure signatureTimeDOM = (DOMStructure) signatureTimeContent.get(0); Node signatureTimeElement = signatureTimeDOM.getNode(); if (!signatureTimeElement.getNamespaceURI().equals(OOXMLSignatureFacet.OOXML_DIGSIG_NS)) { LOG.error("Invalid SignatureTime element: NS=" + signatureTimeElement.getNamespaceURI()); return false; } if (!signatureTimeElement.getLocalName().equals("SignatureTime")) { LOG.error("Invalid SignatureTime element: Name=" + signatureTimeElement.getLocalName()); return false; } if (signatureTimeElement.getChildNodes().getLength() != 2) { LOG.error("Invalid SignatureTime element: Childs=" + signatureTimeElement.getChildNodes().getLength() + ", expected 2 (Format+Value)"); return false; } // format element Node formatElement = signatureTimeElement.getChildNodes().item(0); if (!formatElement.getNamespaceURI().equals(OOXMLSignatureFacet.OOXML_DIGSIG_NS)) { LOG.error("Invalid SignatureTime.Format element: NS=" + formatElement.getNamespaceURI()); return false; } if (!formatElement.getLocalName().equals("Format")) { LOG.error("Invalid SignatureTime.Format element: Name=" + formatElement.getLocalName()); return false; } // value element Node valueElement = signatureTimeElement.getChildNodes().item(1); if (!valueElement.getNamespaceURI().equals(OOXMLSignatureFacet.OOXML_DIGSIG_NS)) { LOG.error("Invalid SignatureTime.Value element: NS=" + valueElement.getNamespaceURI()); return false; } if (!valueElement.getLocalName().equals("Value")) { LOG.error("Invalid SignatureTime.Value element: Name=" + valueElement.getLocalName()); return false; } // TODO: validate value? return true; } private boolean validZipEntryStream(String zipEntryName) { if (!zipEntryName.endsWith(".rels")) { return false; } for (String excludedStream : excludedStreams) { if (zipEntryName.startsWith(excludedStream + "/")) { return false; } } return true; } // returns map of <partName,contentType> entries of the document private Map<String, String> getResources(byte[] document) throws IOException, ParserConfigurationException, SAXException, TransformerException { Map<String, String> signatureResources = new HashMap<String, String>(); ByteArrayInputStream bais = new ByteArrayInputStream(document); ZipInputStream zipInputStream = new ZipInputStream(bais); ZipEntry zipEntry; while (null != (zipEntry = zipInputStream.getNextEntry())) { if (!"[Content_Types].xml".equals(zipEntry.getName())) { continue; } Document contentTypesDocument = OOXMLSignatureFacet.loadDocument(zipInputStream); Element nsElement = contentTypesDocument.createElement("ns"); nsElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:tns", "http://schemas.openxmlformats.org/package/2006/content-types"); for (String contentType : OOXMLSignatureFacet.contentTypes) { NodeList nodeList = XPathAPI.selectNodeList(contentTypesDocument, "/tns:Types/tns:Override[@ContentType='" + contentType + "']/@PartName", nsElement); for (int nodeIdx = 0; nodeIdx < nodeList.getLength(); nodeIdx++) { String partName = nodeList.item(nodeIdx).getTextContent(); LOG.debug("part name: " + partName); partName = partName.substring(1); // remove '/' signatureResources.put(partName, contentType); } } break; } return signatureResources; } @SuppressWarnings("unchecked") private XMLObject findObject(XMLSignature xmlSignature, String objectId) { List<XMLObject> objects = xmlSignature.getObjects(); for (XMLObject object : objects) { if (objectId.equals(object.getId())) { LOG.debug("Found \"" + objectId + "\" ds:object"); return object; } } return null; } private Reference findReferenceFromURI(List<Reference> refs, String referenceURI) { for (Reference ref : refs) { if (ref.getURI().equals(referenceURI)) { LOG.debug("Found \"" + referenceURI + "\" ds:reference"); return ref; } } return null; } public List<String> getSignatureResourceNames(URL url) throws IOException, ParserConfigurationException, SAXException, TransformerException, JAXBException { byte[] document = IOUtils.toByteArray(url.openStream()); return getSignatureResourceNames(document); } public static String[] excludedStreams = { "0x05Bagaaqy23kudbhchAaq5u2chNd", "0x06DataSpaces", "Xmlsignatures", "MsoDataStore", "0x09DRMContent", "_signatures", "_xmlsignatures", "0x05SummaryInformation", "0x05DocumentSummaryInformation" }; }