Added implementation of Digital Signature support using code initially developed for the eId Applet project <http://code.google.com/p/eid-applet/> and re-released under Apache License.
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@824836 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
ed1b4794a0
commit
d48987ac07
54
build.xml
54
build.xml
@ -132,6 +132,21 @@ under the License.
|
||||
<property name="ooxml.jsr173.url" value="${repository.m2}/maven2/org/apache/geronimo/specs/geronimo-stax-api_1.0_spec/1.0/geronimo-stax-api_1.0_spec-1.0.jar"/>
|
||||
<property name="ooxml.schemas.jar" location="${ooxml.lib}/ooxml-schemas-1.0.jar"/>
|
||||
<property name="ooxml.schemas.url" value="${repository.m2}/maven2/org/apache/poi/ooxml-schemas/1.0/ooxml-schemas-1.0.jar"/>
|
||||
<property name="ooxml.commons-lang.jar" location="${ooxml.lib}/commons-lang-2.4.jar"/>
|
||||
<property name="ooxml.commons-lang.url" value="${repository.m2}/maven2/commons-lang/commons-lang/2.4/commons-lang-2.4.jar"/>
|
||||
<property name="ooxml.commons-io.jar" location="${ooxml.lib}/commons-io-1.4.jar"/>
|
||||
<property name="ooxml.commons-io.url" value="${repository.m2}/maven2/commons-io/commons-io/1.4/commons-io-1.4.jar"/>
|
||||
<property name="ooxml.xmlsec.jar" location="${ooxml.lib}/xmlsec-1.4.3.jar"/>
|
||||
<property name="ooxml.xmlsec.url" value="${repository.m2}/maven2/org/apache/santuario/xmlsec/1.4.3/xmlsec-1.4.3.jar"/>
|
||||
<property name="ooxml.xalan.jar" location="${ooxml.lib}/xalan-2.7.1.jar"/>
|
||||
<property name="ooxml.xalan.url" value="${repository.m2}/maven2/xalan/xalan/2.7.1/xalan-2.7.1.jar"/>
|
||||
<property name="ooxml.xalan-serializer.jar" location="${ooxml.lib}/serializer-2.7.1.jar"/>
|
||||
<property name="ooxml.xalan-serializer.url" value="${repository.m2}/maven2/xalan/serializer/2.7.1/serializer-2.7.1.jar"/>
|
||||
<property name="ooxml.joda-time.jar" location="${ooxml.lib}/joda-time-1.6.jar"/>
|
||||
<property name="ooxml.joda-time.url" value="${repository.m2}/maven2/joda-time/joda-time/1.6/joda-time-1.6.jar"/>
|
||||
<!-- BouncyCastle is used only for OOXML Digital Signature tests -->
|
||||
<property name="ooxml.bcprov.jar" location="${ooxml.lib}/bcprov-jdk15-140.jar"/>
|
||||
<property name="ooxml.bcprov.url" value="${repository.m2}/maven2/bouncycastle/bcprov-jdk15/140/bcprov-jdk15-140.jar"/>
|
||||
|
||||
<!-- See http://www.ecma-international.org/publications/standards/Ecma-376.htm -->
|
||||
<!-- "Copy these file(s), free of charge" -->
|
||||
@ -183,6 +198,9 @@ under the License.
|
||||
<pathelement location="${scratchpad.output.test.dir}"/>
|
||||
<pathelement location="${contrib.output.dir}"/>
|
||||
<pathelement location="${contrib.output.test.dir}"/>
|
||||
<fileset dir="${ooxml.lib}">
|
||||
<include name="*.jar" />
|
||||
</fileset>
|
||||
</path>
|
||||
|
||||
<path id="ooxml.classpath">
|
||||
@ -208,6 +226,7 @@ under the License.
|
||||
<pathelement location="${main.output.test.dir}"/> <!-- ooxml tests use some utilities from main tests -->
|
||||
<pathelement location="${scratchpad.output.test.dir}"/>
|
||||
<pathelement location="${junit.jar1.dir}"/>
|
||||
<pathelement location="${ooxml.src.test}"/>
|
||||
</path>
|
||||
|
||||
|
||||
@ -351,6 +370,13 @@ under the License.
|
||||
<available file="${ooxml.xmlbeans.jar}"/>
|
||||
<available file="${ooxml.jsr173.jar}"/>
|
||||
<available file="${ooxml.schemas.jar}"/>
|
||||
<available file="${ooxml.commons-lang.jar}"/>
|
||||
<available file="${ooxml.commons-io.jar}"/>
|
||||
<available file="${ooxml.xmlsec.jar}"/>
|
||||
<available file="${ooxml.xalan.jar}"/>
|
||||
<available file="${ooxml.xalan-serializer.jar}"/>
|
||||
<available file="${ooxml.joda-time.jar}"/>
|
||||
<available file="${ooxml.bcprov.jar}"/>
|
||||
</and>
|
||||
<isset property="disconnected"/>
|
||||
</or>
|
||||
@ -373,6 +399,34 @@ under the License.
|
||||
<param name="sourcefile" value="${ooxml.schemas.url}"/>
|
||||
<param name="destfile" value="${ooxml.schemas.jar}"/>
|
||||
</antcall>
|
||||
<antcall target="downloadfile">
|
||||
<param name="sourcefile" value="${ooxml.commons-lang.url}"/>
|
||||
<param name="destfile" value="${ooxml.commons-lang.jar}"/>
|
||||
</antcall>
|
||||
<antcall target="downloadfile">
|
||||
<param name="sourcefile" value="${ooxml.commons-io.url}"/>
|
||||
<param name="destfile" value="${ooxml.commons-io.jar}"/>
|
||||
</antcall>
|
||||
<antcall target="downloadfile">
|
||||
<param name="sourcefile" value="${ooxml.xmlsec.url}"/>
|
||||
<param name="destfile" value="${ooxml.xmlsec.jar}"/>
|
||||
</antcall>
|
||||
<antcall target="downloadfile">
|
||||
<param name="sourcefile" value="${ooxml.xalan.url}"/>
|
||||
<param name="destfile" value="${ooxml.xalan.jar}"/>
|
||||
</antcall>
|
||||
<antcall target="downloadfile">
|
||||
<param name="sourcefile" value="${ooxml.xalan-serializer.url}"/>
|
||||
<param name="destfile" value="${ooxml.xalan-serializer.jar}"/>
|
||||
</antcall>
|
||||
<antcall target="downloadfile">
|
||||
<param name="sourcefile" value="${ooxml.joda-time.url}"/>
|
||||
<param name="destfile" value="${ooxml.joda-time.jar}"/>
|
||||
</antcall>
|
||||
<antcall target="downloadfile">
|
||||
<param name="sourcefile" value="${ooxml.bcprov.url}"/>
|
||||
<param name="destfile" value="${ooxml.bcprov.jar}"/>
|
||||
</antcall>
|
||||
</target>
|
||||
|
||||
<target name="check-ooxml-xsds">
|
||||
|
@ -19,3 +19,6 @@ This product contains the Piccolo XML Parser for Java
|
||||
|
||||
This product contains the chunks_parse_cmds.tbl file from the vsdump program.
|
||||
Copyright (C) 2006-2007 Valek Filippov (frob@df.ru)
|
||||
|
||||
This product contains parts that were originally based on the eID Applet project
|
||||
(http://code.google.com/p/eid-applet/). Copyright (C) 2008-2009 FedICT.
|
@ -0,0 +1,610 @@
|
||||
|
||||
/* ====================================================================
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You 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.
|
||||
==================================================================== */
|
||||
|
||||
/*
|
||||
* Based on the eID Applet Project code.
|
||||
* Original Copyright (C) 2008-2009 FedICT.
|
||||
*/
|
||||
|
||||
package org.apache.poi.ooxml.signature.service.signer;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.Key;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.xml.crypto.MarshalException;
|
||||
import javax.xml.crypto.URIDereferencer;
|
||||
import javax.xml.crypto.XMLStructure;
|
||||
import javax.xml.crypto.dom.DOMCryptoContext;
|
||||
import javax.xml.crypto.dsig.CanonicalizationMethod;
|
||||
import javax.xml.crypto.dsig.DigestMethod;
|
||||
import javax.xml.crypto.dsig.Manifest;
|
||||
import javax.xml.crypto.dsig.Reference;
|
||||
import javax.xml.crypto.dsig.SignatureMethod;
|
||||
import javax.xml.crypto.dsig.SignedInfo;
|
||||
import javax.xml.crypto.dsig.Transform;
|
||||
import javax.xml.crypto.dsig.XMLObject;
|
||||
import javax.xml.crypto.dsig.XMLSignContext;
|
||||
import javax.xml.crypto.dsig.XMLSignatureException;
|
||||
import javax.xml.crypto.dsig.XMLSignatureFactory;
|
||||
import javax.xml.crypto.dsig.dom.DOMSignContext;
|
||||
import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec;
|
||||
import javax.xml.crypto.dsig.spec.TransformParameterSpec;
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.transform.OutputKeys;
|
||||
import javax.xml.transform.Result;
|
||||
import javax.xml.transform.Source;
|
||||
import javax.xml.transform.Transformer;
|
||||
import javax.xml.transform.TransformerConfigurationException;
|
||||
import javax.xml.transform.TransformerException;
|
||||
import javax.xml.transform.TransformerFactory;
|
||||
import javax.xml.transform.TransformerFactoryConfigurationError;
|
||||
import javax.xml.transform.dom.DOMSource;
|
||||
import javax.xml.transform.stream.StreamResult;
|
||||
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.apache.poi.ooxml.signature.service.spi.DigestInfo;
|
||||
import org.apache.poi.ooxml.signature.service.spi.SignatureService;
|
||||
import org.apache.xml.security.signature.XMLSignature;
|
||||
import org.apache.xml.security.utils.Base64;
|
||||
import org.apache.xml.security.utils.Constants;
|
||||
import org.apache.xpath.XPathAPI;
|
||||
import org.jcp.xml.dsig.internal.dom.DOMReference;
|
||||
import org.jcp.xml.dsig.internal.dom.DOMSignedInfo;
|
||||
import org.jcp.xml.dsig.internal.dom.DOMXMLSignature;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
import org.xml.sax.InputSource;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Abstract base class for an XML Signature Service implementation.
|
||||
*/
|
||||
public abstract class AbstractXmlSignatureService implements SignatureService {
|
||||
|
||||
static final Log LOG = LogFactory.getLog(AbstractXmlSignatureService.class);
|
||||
|
||||
private static final String SIGNATURE_ID_ATTRIBUTE = "signature-id";
|
||||
|
||||
// TODO refactor everything using the signature aspect design pattern
|
||||
private final List<SignatureAspect> signatureAspects;
|
||||
|
||||
/**
|
||||
* Main constructor.
|
||||
*/
|
||||
public AbstractXmlSignatureService() {
|
||||
this.signatureAspects = new LinkedList<SignatureAspect>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a signature aspect to this XML signature service.
|
||||
*
|
||||
* @param signatureAspect
|
||||
*/
|
||||
protected void addSignatureAspect(SignatureAspect signatureAspect) {
|
||||
this.signatureAspects.add(signatureAspect);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives back the signature digest algorithm. Allowed values are SHA-1,
|
||||
* SHA-256, SHA-384, SHA-512, RIPEND160. The default algorithm is SHA-1.
|
||||
* Override this method to select another signature digest algorithm.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
protected String getSignatureDigestAlgorithm() {
|
||||
return "SHA-1";
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives back a list of service digest infos. Override this method to
|
||||
* provide digest infos of files located in the service itself.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
protected List<DigestInfo> getServiceDigestInfos() {
|
||||
return new LinkedList<DigestInfo>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives back the enveloping document. Return <code>null</code> in case
|
||||
* ds:Signature should be the top-level element. Implementations can
|
||||
* override this method to provide a custom enveloping document.
|
||||
*
|
||||
* @return
|
||||
* @throws SAXException
|
||||
* @throws IOException
|
||||
*/
|
||||
protected Document getEnvelopingDocument() throws ParserConfigurationException, IOException, SAXException {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives back a list of reference URIs that need to be signed. These URIs
|
||||
* can refer to elements inside the enveloping document or to external
|
||||
* resources. Override this method to feed in other ds:Reference URIs.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
protected List<String> getReferenceUris() {
|
||||
return new LinkedList<String>();
|
||||
}
|
||||
|
||||
public static class ReferenceInfo {
|
||||
private final String uri;
|
||||
private final String transform;
|
||||
|
||||
public ReferenceInfo(String uri, String transform) {
|
||||
this.uri = uri;
|
||||
this.transform = transform;
|
||||
}
|
||||
|
||||
public ReferenceInfo(String uri) {
|
||||
this(uri, null);
|
||||
}
|
||||
|
||||
public String getUri() {
|
||||
return this.uri;
|
||||
}
|
||||
|
||||
public String getTransform() {
|
||||
return this.transform;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives back a list of references that need to be signed. Implementation
|
||||
* can override this method.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
protected List<ReferenceInfo> getReferences() {
|
||||
return new LinkedList<ReferenceInfo>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this method to change the URI dereferener used by the signing
|
||||
* engine.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
protected URIDereferencer getURIDereferencer() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives back the human-readable description of what the citizen will be
|
||||
* signing. The default value is "XML Signature". Override this method to
|
||||
* provide the citizen with another description.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
protected String getSignatureDescription() {
|
||||
return "XML Signature";
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives back a temporary data storage component. This component is used for
|
||||
* temporary storage of the XML signature documents.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
protected abstract TemporaryDataStorage getTemporaryDataStorage();
|
||||
|
||||
/**
|
||||
* Gives back the output stream to which to write the signed XML document.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
protected abstract OutputStream getSignedDocumentOutputStream();
|
||||
|
||||
public DigestInfo preSign(List<DigestInfo> digestInfos, List<X509Certificate> signingCertificateChain) throws NoSuchAlgorithmException {
|
||||
LOG.debug("preSign");
|
||||
String digestAlgo = getSignatureDigestAlgorithm();
|
||||
|
||||
byte[] digestValue;
|
||||
try {
|
||||
digestValue = getXmlSignatureDigestValue(digestAlgo, digestInfos);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("XML signature error: " + e.getMessage(), e);
|
||||
}
|
||||
|
||||
String description = getSignatureDescription();
|
||||
return new DigestInfo(digestValue, digestAlgo, description);
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be overridden by XML signature service implementation to further
|
||||
* process the signed XML document.
|
||||
*
|
||||
* @param sinatureElement
|
||||
* @param signingCertificateChain
|
||||
*/
|
||||
protected void postSign(Element sinatureElement, List<X509Certificate> signingCertificateChain) {
|
||||
// empty
|
||||
}
|
||||
|
||||
public void postSign(byte[] signatureValue, List<X509Certificate> signingCertificateChain) {
|
||||
LOG.debug("postSign");
|
||||
|
||||
/*
|
||||
* Retrieve the intermediate XML signature document from the temporary
|
||||
* data storage.
|
||||
*/
|
||||
TemporaryDataStorage temporaryDataStorage = getTemporaryDataStorage();
|
||||
InputStream documentInputStream = temporaryDataStorage.getTempInputStream();
|
||||
String signatureId = (String) temporaryDataStorage.getAttribute(SIGNATURE_ID_ATTRIBUTE);
|
||||
LOG.debug("signature Id: " + signatureId);
|
||||
|
||||
/*
|
||||
* Load the signature DOM document.
|
||||
*/
|
||||
Document document;
|
||||
try {
|
||||
document = loadDocument(documentInputStream);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("DOM error: " + e.getMessage(), e);
|
||||
}
|
||||
|
||||
/*
|
||||
* Locate the correct ds:Signature node.
|
||||
*/
|
||||
Element nsElement = document.createElement("ns");
|
||||
nsElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:ds", Constants.SignatureSpecNS);
|
||||
Element signatureElement;
|
||||
try {
|
||||
signatureElement = (Element) XPathAPI.selectSingleNode(document, "//ds:Signature[@Id='" + signatureId + "']", nsElement);
|
||||
} catch (TransformerException e) {
|
||||
throw new RuntimeException("XPATH error: " + e.getMessage(), e);
|
||||
}
|
||||
if (null == signatureElement) {
|
||||
throw new RuntimeException("ds:Signature not found for @Id: " + signatureId);
|
||||
}
|
||||
|
||||
/*
|
||||
* Insert signature value into the ds:SignatureValue element
|
||||
*/
|
||||
NodeList signatureValueNodeList = signatureElement.getElementsByTagNameNS(javax.xml.crypto.dsig.XMLSignature.XMLNS, "SignatureValue");
|
||||
Element signatureValueElement = (Element) signatureValueNodeList.item(0);
|
||||
signatureValueElement.setTextContent(Base64.encode(signatureValue));
|
||||
|
||||
/*
|
||||
* Allow implementation classes to inject their own stuff.
|
||||
*/
|
||||
postSign(signatureElement, signingCertificateChain);
|
||||
|
||||
OutputStream signedDocumentOutputStream = getSignedDocumentOutputStream();
|
||||
if (null == signedDocumentOutputStream) {
|
||||
throw new IllegalArgumentException("signed document output stream is null");
|
||||
}
|
||||
try {
|
||||
writeDocument(document, signedDocumentOutputStream);
|
||||
} catch (Exception e) {
|
||||
LOG.debug("error writing the signed XML document: " + e.getMessage(), e);
|
||||
throw new RuntimeException("error writing the signed XML document: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
protected String getCanonicalizationMethod() {
|
||||
// CanonicalizationMethod.INCLUSIVE fails for OOo
|
||||
return CanonicalizationMethod.EXCLUSIVE;
|
||||
}
|
||||
|
||||
private byte[] getXmlSignatureDigestValue(String digestAlgo, List<DigestInfo> digestInfos) throws ParserConfigurationException, NoSuchAlgorithmException,
|
||||
InvalidAlgorithmParameterException, MarshalException, javax.xml.crypto.dsig.XMLSignatureException,
|
||||
TransformerFactoryConfigurationError, TransformerException, IOException, SAXException {
|
||||
/*
|
||||
* DOM Document construction.
|
||||
*/
|
||||
Document document = getEnvelopingDocument();
|
||||
if (null == document) {
|
||||
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
|
||||
documentBuilderFactory.setNamespaceAware(true);
|
||||
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
|
||||
document = documentBuilder.newDocument();
|
||||
}
|
||||
|
||||
/*
|
||||
* Signature context construction.
|
||||
*/
|
||||
Key key = new Key() {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public String getAlgorithm() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public byte[] getEncoded() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getFormat() {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
XMLSignContext xmlSignContext = new DOMSignContext(key, document);
|
||||
URIDereferencer uriDereferencer = getURIDereferencer();
|
||||
if (null != uriDereferencer) {
|
||||
xmlSignContext.setURIDereferencer(uriDereferencer);
|
||||
}
|
||||
|
||||
// OOo doesn't like ds namespaces.
|
||||
// xmlSignContext.putNamespacePrefix(
|
||||
// javax.xml.crypto.dsig.XMLSignature.XMLNS, "ds");
|
||||
|
||||
XMLSignatureFactory signatureFactory = XMLSignatureFactory.getInstance("DOM", new org.jcp.xml.dsig.internal.dom.XMLDSigRI());
|
||||
|
||||
/*
|
||||
* ds:Reference
|
||||
*/
|
||||
List<Reference> references = new LinkedList<Reference>();
|
||||
addDigestInfosAsReferences(digestInfos, signatureFactory, references);
|
||||
List<DigestInfo> serviceDigestInfos = getServiceDigestInfos();
|
||||
addDigestInfosAsReferences(serviceDigestInfos, signatureFactory, references);
|
||||
addReferenceIds(signatureFactory, xmlSignContext, references);
|
||||
addReferences(signatureFactory, references);
|
||||
|
||||
/*
|
||||
* Invoke the signature aspects.
|
||||
*/
|
||||
String signatureId = "xmldsig-" + UUID.randomUUID().toString();
|
||||
List<XMLObject> objects = new LinkedList<XMLObject>();
|
||||
for (SignatureAspect signatureAspect : this.signatureAspects) {
|
||||
LOG.debug("invoking signature aspect: " + signatureAspect.getClass().getSimpleName());
|
||||
signatureAspect.preSign(signatureFactory, document, signatureId, references, objects);
|
||||
}
|
||||
|
||||
/*
|
||||
* ds:SignedInfo
|
||||
*/
|
||||
SignatureMethod signatureMethod = signatureFactory.newSignatureMethod(getSignatureMethod(digestAlgo), null);
|
||||
CanonicalizationMethod canonicalizationMethod = signatureFactory.newCanonicalizationMethod(getCanonicalizationMethod(), (C14NMethodParameterSpec) null);
|
||||
SignedInfo signedInfo = signatureFactory.newSignedInfo(canonicalizationMethod, signatureMethod, references);
|
||||
|
||||
/*
|
||||
* JSR105 ds:Signature creation
|
||||
*/
|
||||
String signatureValueId = signatureId + "-signature-value";
|
||||
javax.xml.crypto.dsig.XMLSignature xmlSignature = signatureFactory.newXMLSignature(signedInfo, null, objects, signatureId, signatureValueId);
|
||||
|
||||
/*
|
||||
* ds:Signature Marshalling.
|
||||
*/
|
||||
DOMXMLSignature domXmlSignature = (DOMXMLSignature) xmlSignature;
|
||||
Node documentNode = document.getDocumentElement();
|
||||
if (null == documentNode) {
|
||||
/*
|
||||
* In case of an empty DOM document.
|
||||
*/
|
||||
documentNode = document;
|
||||
}
|
||||
String dsPrefix = null;
|
||||
// String dsPrefix = "ds";
|
||||
domXmlSignature.marshal(documentNode, dsPrefix, (DOMCryptoContext) xmlSignContext);
|
||||
|
||||
/*
|
||||
* Completion of undigested ds:References in the ds:Manifests.
|
||||
*/
|
||||
for (XMLObject object : objects) {
|
||||
LOG.debug("object java type: " + object.getClass().getName());
|
||||
List<XMLStructure> objectContentList = object.getContent();
|
||||
for (XMLStructure objectContent : objectContentList) {
|
||||
LOG.debug("object content java type: " + objectContent.getClass().getName());
|
||||
if (false == objectContent instanceof Manifest) {
|
||||
continue;
|
||||
}
|
||||
Manifest manifest = (Manifest) objectContent;
|
||||
List<Reference> manifestReferences = manifest.getReferences();
|
||||
for (Reference manifestReference : manifestReferences) {
|
||||
if (null != manifestReference.getDigestValue()) {
|
||||
continue;
|
||||
}
|
||||
DOMReference manifestDOMReference = (DOMReference) manifestReference;
|
||||
manifestDOMReference.digest(xmlSignContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Completion of undigested ds:References.
|
||||
*/
|
||||
List<Reference> signedInfoReferences = signedInfo.getReferences();
|
||||
for (Reference signedInfoReference : signedInfoReferences) {
|
||||
DOMReference domReference = (DOMReference) signedInfoReference;
|
||||
if (null != domReference.getDigestValue()) {
|
||||
// ds:Reference with external digest value
|
||||
continue;
|
||||
}
|
||||
domReference.digest(xmlSignContext);
|
||||
}
|
||||
|
||||
/*
|
||||
* Store the intermediate XML signature document.
|
||||
*/
|
||||
TemporaryDataStorage temporaryDataStorage = getTemporaryDataStorage();
|
||||
OutputStream tempDocumentOutputStream = temporaryDataStorage.getTempOutputStream();
|
||||
writeDocument(document, tempDocumentOutputStream);
|
||||
temporaryDataStorage.setAttribute(SIGNATURE_ID_ATTRIBUTE, signatureId);
|
||||
|
||||
/*
|
||||
* Calculation of XML signature digest value.
|
||||
*/
|
||||
DOMSignedInfo domSignedInfo = (DOMSignedInfo) signedInfo;
|
||||
ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
|
||||
domSignedInfo.canonicalize(xmlSignContext, dataStream);
|
||||
byte[] octets = dataStream.toByteArray();
|
||||
|
||||
/*
|
||||
* TODO: we could be using DigestOutputStream here to optimize memory
|
||||
* usage.
|
||||
*/
|
||||
|
||||
MessageDigest jcaMessageDigest = MessageDigest.getInstance(digestAlgo);
|
||||
byte[] digestValue = jcaMessageDigest.digest(octets);
|
||||
return digestValue;
|
||||
}
|
||||
|
||||
private void addReferenceIds(XMLSignatureFactory signatureFactory, XMLSignContext xmlSignContext, List<Reference> references)
|
||||
throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, XMLSignatureException {
|
||||
List<String> referenceUris = getReferenceUris();
|
||||
if (null == referenceUris) {
|
||||
return;
|
||||
}
|
||||
DigestMethod digestMethod = signatureFactory.newDigestMethod(DigestMethod.SHA1, null);
|
||||
for (String referenceUri : referenceUris) {
|
||||
Reference reference = signatureFactory.newReference(referenceUri, digestMethod);
|
||||
references.add(reference);
|
||||
}
|
||||
}
|
||||
|
||||
private void addReferences(XMLSignatureFactory xmlSignatureFactory, List<Reference> references) throws NoSuchAlgorithmException,
|
||||
InvalidAlgorithmParameterException {
|
||||
List<ReferenceInfo> referenceInfos = getReferences();
|
||||
if (null == referenceInfos) {
|
||||
return;
|
||||
}
|
||||
if (referenceInfos.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
DigestMethod digestMethod = xmlSignatureFactory.newDigestMethod(DigestMethod.SHA1, null);
|
||||
for (ReferenceInfo referenceInfo : referenceInfos) {
|
||||
List<Transform> transforms = new LinkedList<Transform>();
|
||||
if (null != referenceInfo.getTransform()) {
|
||||
Transform transform = xmlSignatureFactory.newTransform(referenceInfo.getTransform(), (TransformParameterSpec) null);
|
||||
transforms.add(transform);
|
||||
}
|
||||
LOG.debug("adding ds:Reference " + referenceInfo.getUri());
|
||||
Reference reference = xmlSignatureFactory.newReference(referenceInfo.getUri(), digestMethod, transforms, null, null);
|
||||
references.add(reference);
|
||||
}
|
||||
}
|
||||
|
||||
private void addDigestInfosAsReferences(List<DigestInfo> digestInfos, XMLSignatureFactory signatureFactory, List<Reference> references)
|
||||
throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, MalformedURLException {
|
||||
if (null == digestInfos) {
|
||||
return;
|
||||
}
|
||||
for (DigestInfo digestInfo : digestInfos) {
|
||||
byte[] documentDigestValue = digestInfo.digestValue;
|
||||
|
||||
DigestMethod digestMethod = signatureFactory.newDigestMethod(getXmlDigestAlgo(digestInfo.digestAlgo), null);
|
||||
|
||||
String uri = FilenameUtils.getName(new File(digestInfo.description).toURI().toURL().getFile());
|
||||
|
||||
Reference reference = signatureFactory.newReference(uri, digestMethod, null, null, null, documentDigestValue);
|
||||
references.add(reference);
|
||||
}
|
||||
}
|
||||
|
||||
private String getXmlDigestAlgo(String digestAlgo) {
|
||||
if ("SHA-1".equals(digestAlgo)) {
|
||||
return DigestMethod.SHA1;
|
||||
}
|
||||
if ("SHA-256".equals(digestAlgo)) {
|
||||
return DigestMethod.SHA256;
|
||||
}
|
||||
if ("SHA-512".equals(digestAlgo)) {
|
||||
return DigestMethod.SHA512;
|
||||
}
|
||||
throw new RuntimeException("unsupported digest algo: " + digestAlgo);
|
||||
}
|
||||
|
||||
private String getSignatureMethod(String digestAlgo) {
|
||||
if (null == digestAlgo) {
|
||||
throw new RuntimeException("digest algo is null");
|
||||
}
|
||||
if ("SHA-1".equals(digestAlgo)) {
|
||||
return SignatureMethod.RSA_SHA1;
|
||||
}
|
||||
if ("SHA-256".equals(digestAlgo)) {
|
||||
return XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA256;
|
||||
}
|
||||
if ("SHA-512".equals(digestAlgo)) {
|
||||
return XMLSignature.ALGO_ID_MAC_HMAC_SHA512;
|
||||
}
|
||||
if ("SHA-384".equals(digestAlgo)) {
|
||||
return XMLSignature.ALGO_ID_MAC_HMAC_SHA384;
|
||||
}
|
||||
if ("RIPEMD160".equals(digestAlgo)) {
|
||||
return XMLSignature.ALGO_ID_MAC_HMAC_RIPEMD160;
|
||||
}
|
||||
throw new RuntimeException("unsupported sign algo: " + digestAlgo);
|
||||
}
|
||||
|
||||
protected void writeDocument(Document document, OutputStream documentOutputStream) throws TransformerConfigurationException,
|
||||
TransformerFactoryConfigurationError, TransformerException, IOException {
|
||||
writeDocumentNoClosing(document, documentOutputStream);
|
||||
documentOutputStream.close();
|
||||
}
|
||||
|
||||
protected void writeDocumentNoClosing(Document document, OutputStream documentOutputStream) throws TransformerConfigurationException,
|
||||
TransformerFactoryConfigurationError, TransformerException, IOException {
|
||||
// we need the XML processing initial line for OOXML
|
||||
writeDocumentNoClosing(document, documentOutputStream, false);
|
||||
}
|
||||
|
||||
protected void writeDocumentNoClosing(Document document, OutputStream documentOutputStream, boolean omitXmlDeclaration)
|
||||
throws TransformerConfigurationException, TransformerFactoryConfigurationError, TransformerException, IOException {
|
||||
NoCloseOutputStream outputStream = new NoCloseOutputStream(documentOutputStream);
|
||||
Result result = new StreamResult(outputStream);
|
||||
Transformer xformer = TransformerFactory.newInstance().newTransformer();
|
||||
if (omitXmlDeclaration) {
|
||||
xformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
|
||||
}
|
||||
Source source = new DOMSource(document);
|
||||
xformer.transform(source, result);
|
||||
}
|
||||
|
||||
protected Document loadDocument(InputStream documentInputStream) throws ParserConfigurationException, SAXException, IOException {
|
||||
InputSource inputSource = new InputSource(documentInputStream);
|
||||
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
|
||||
documentBuilderFactory.setNamespaceAware(true);
|
||||
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
|
||||
Document document = documentBuilder.parse(inputSource);
|
||||
return document;
|
||||
}
|
||||
|
||||
protected Document loadDocumentNoClose(InputStream documentInputStream) throws ParserConfigurationException, SAXException, IOException {
|
||||
NoCloseInputStream noCloseInputStream = new NoCloseInputStream(documentInputStream);
|
||||
InputSource inputSource = new InputSource(noCloseInputStream);
|
||||
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
|
||||
documentBuilderFactory.setNamespaceAware(true);
|
||||
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
|
||||
Document document = documentBuilder.parse(inputSource);
|
||||
return document;
|
||||
}
|
||||
}
|
@ -0,0 +1,99 @@
|
||||
|
||||
/* ====================================================================
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You 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.
|
||||
==================================================================== */
|
||||
|
||||
/*
|
||||
* Based on the eID Applet Project code.
|
||||
* Original Copyright (C) 2008-2009 FedICT.
|
||||
*/
|
||||
|
||||
package org.apache.poi.ooxml.signature.service.signer;
|
||||
|
||||
import java.security.Key;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.List;
|
||||
|
||||
import javax.xml.crypto.AlgorithmMethod;
|
||||
import javax.xml.crypto.KeySelector;
|
||||
import javax.xml.crypto.KeySelectorException;
|
||||
import javax.xml.crypto.KeySelectorResult;
|
||||
import javax.xml.crypto.XMLCryptoContext;
|
||||
import javax.xml.crypto.XMLStructure;
|
||||
import javax.xml.crypto.dsig.keyinfo.KeyInfo;
|
||||
import javax.xml.crypto.dsig.keyinfo.X509Data;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
/**
|
||||
* JSR105 key selector implementation using the ds:KeyInfo data of the signature
|
||||
* itself.
|
||||
*/
|
||||
public class KeyInfoKeySelector extends KeySelector implements KeySelectorResult {
|
||||
|
||||
private static final Log LOG = LogFactory.getLog(KeyInfoKeySelector.class);
|
||||
|
||||
private X509Certificate certificate;
|
||||
|
||||
@Override
|
||||
public KeySelectorResult select(KeyInfo keyInfo, Purpose purpose, AlgorithmMethod method, XMLCryptoContext context) throws KeySelectorException {
|
||||
LOG.debug("select key");
|
||||
if (null == keyInfo) {
|
||||
throw new KeySelectorException("no ds:KeyInfo present");
|
||||
}
|
||||
List<XMLStructure> keyInfoContent = keyInfo.getContent();
|
||||
this.certificate = null;
|
||||
for (XMLStructure keyInfoStructure : keyInfoContent) {
|
||||
if (false == (keyInfoStructure instanceof X509Data)) {
|
||||
continue;
|
||||
}
|
||||
X509Data x509Data = (X509Data) keyInfoStructure;
|
||||
List<Object> x509DataList = x509Data.getContent();
|
||||
for (Object x509DataObject : x509DataList) {
|
||||
if (false == (x509DataObject instanceof X509Certificate)) {
|
||||
continue;
|
||||
}
|
||||
X509Certificate certificate = (X509Certificate) x509DataObject;
|
||||
LOG.debug("certificate: " + certificate.getSubjectX500Principal());
|
||||
if (null == this.certificate) {
|
||||
/*
|
||||
* The first certificate is presumably the signer.
|
||||
*/
|
||||
this.certificate = certificate;
|
||||
}
|
||||
}
|
||||
if (null != this.certificate) {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
throw new KeySelectorException("No key found!");
|
||||
}
|
||||
|
||||
public Key getKey() {
|
||||
return this.certificate.getPublicKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives back the X509 certificate used during the last signature
|
||||
* verification operation.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public X509Certificate getCertificate() {
|
||||
return this.certificate;
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
|
||||
/* ====================================================================
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You 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.
|
||||
==================================================================== */
|
||||
|
||||
/*
|
||||
* Based on the eID Applet Project code.
|
||||
* Original Copyright (C) 2008-2009 FedICT.
|
||||
*/
|
||||
|
||||
package org.apache.poi.ooxml.signature.service.signer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.apache.commons.io.input.ProxyInputStream;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
/**
|
||||
* Input Stream proxy that doesn't close the underlying input stream.
|
||||
*/
|
||||
public class NoCloseInputStream extends ProxyInputStream {
|
||||
|
||||
private static final Log LOG = LogFactory.getLog(NoCloseInputStream.class);
|
||||
|
||||
/**
|
||||
* Main constructor.
|
||||
*
|
||||
* @param proxy
|
||||
*/
|
||||
public NoCloseInputStream(InputStream proxy) {
|
||||
super(proxy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
LOG.debug("close");
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
|
||||
/* ====================================================================
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You 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.
|
||||
==================================================================== */
|
||||
|
||||
/*
|
||||
* Based on the eID Applet Project code.
|
||||
* Original Copyright (C) 2008-2009 FedICT.
|
||||
*/
|
||||
|
||||
package org.apache.poi.ooxml.signature.service.signer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import org.apache.commons.io.output.ProxyOutputStream;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
/**
|
||||
* Output Stream proxy that doesn't close the underlying stream.
|
||||
*/
|
||||
public class NoCloseOutputStream extends ProxyOutputStream {
|
||||
|
||||
private static final Log LOG = LogFactory.getLog(NoCloseOutputStream.class);
|
||||
|
||||
/**
|
||||
* Main constructor.
|
||||
*
|
||||
* @param proxy
|
||||
*/
|
||||
public NoCloseOutputStream(OutputStream proxy) {
|
||||
super(proxy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
LOG.debug("close");
|
||||
// empty
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
|
||||
/* ====================================================================
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You 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.
|
||||
==================================================================== */
|
||||
|
||||
/*
|
||||
* Based on the eID Applet Project code.
|
||||
* Original Copyright (C) 2008-2009 FedICT.
|
||||
*/
|
||||
|
||||
package org.apache.poi.ooxml.signature.service.signer;
|
||||
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.List;
|
||||
|
||||
import javax.xml.crypto.dsig.Reference;
|
||||
import javax.xml.crypto.dsig.XMLObject;
|
||||
import javax.xml.crypto.dsig.XMLSignatureFactory;
|
||||
|
||||
import org.w3c.dom.Document;
|
||||
|
||||
/**
|
||||
* JSR105 Signature Aspect interface.
|
||||
*/
|
||||
public interface SignatureAspect {
|
||||
|
||||
/**
|
||||
* This method is being invoked by the XML signature service engine during
|
||||
* pre-sign phase. Via this method a signature aspect implementation can add
|
||||
* signature aspects to an XML signature.
|
||||
*
|
||||
* @param signatureFactory
|
||||
* @param document
|
||||
* @param signatureId
|
||||
* @param references
|
||||
* @param objects
|
||||
* @throws InvalidAlgorithmParameterException
|
||||
* @throws NoSuchAlgorithmException
|
||||
*/
|
||||
void preSign(XMLSignatureFactory signatureFactory, Document document, String signatureId, List<Reference> references, List<XMLObject> objects)
|
||||
throws NoSuchAlgorithmException, InvalidAlgorithmParameterException;
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
|
||||
/* ====================================================================
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You 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.
|
||||
==================================================================== */
|
||||
|
||||
/*
|
||||
* Based on the eID Applet Project code.
|
||||
* Original Copyright (C) 2008-2009 FedICT.
|
||||
*/
|
||||
|
||||
package org.apache.poi.ooxml.signature.service.signer;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Interface for temporary data storage.
|
||||
*/
|
||||
public interface TemporaryDataStorage {
|
||||
|
||||
/**
|
||||
* Gives back the temporary output stream that can be used for data storage.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
OutputStream getTempOutputStream();
|
||||
|
||||
/**
|
||||
* Gives back the temporary input stream for retrieval of the previously
|
||||
* stored data.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
InputStream getTempInputStream();
|
||||
|
||||
/**
|
||||
* Stores an attribute to the temporary data storage.
|
||||
*
|
||||
* @param attributeName
|
||||
* @param attributeValue
|
||||
*/
|
||||
void setAttribute(String attributeName, Serializable attributeValue);
|
||||
|
||||
/**
|
||||
* Retrieves an attribute from the temporary data storage.
|
||||
*
|
||||
* @param attributeName
|
||||
* @return
|
||||
*/
|
||||
Serializable getAttribute(String attributeName);
|
||||
}
|
@ -0,0 +1,348 @@
|
||||
|
||||
/* ====================================================================
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You 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.
|
||||
==================================================================== */
|
||||
|
||||
|
||||
/*
|
||||
* Based on the eID Applet Project code.
|
||||
* Original Copyright (C) 2008-2009 FedICT.
|
||||
*/
|
||||
|
||||
package org.apache.poi.ooxml.signature.service.signer.ooxml;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URL;
|
||||
import java.security.Key;
|
||||
import java.security.KeyException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
import javax.xml.crypto.MarshalException;
|
||||
import javax.xml.crypto.URIDereferencer;
|
||||
import javax.xml.crypto.dom.DOMCryptoContext;
|
||||
import javax.xml.crypto.dsig.CanonicalizationMethod;
|
||||
import javax.xml.crypto.dsig.XMLSignContext;
|
||||
import javax.xml.crypto.dsig.dom.DOMSignContext;
|
||||
import javax.xml.crypto.dsig.keyinfo.KeyInfo;
|
||||
import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory;
|
||||
import javax.xml.crypto.dsig.keyinfo.KeyValue;
|
||||
import javax.xml.crypto.dsig.keyinfo.X509Data;
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.transform.TransformerConfigurationException;
|
||||
import javax.xml.transform.TransformerException;
|
||||
import javax.xml.transform.TransformerFactoryConfigurationError;
|
||||
|
||||
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.poi.ooxml.signature.service.signer.AbstractXmlSignatureService;
|
||||
import org.apache.xml.security.utils.Constants;
|
||||
import org.apache.xpath.XPathAPI;
|
||||
import org.jcp.xml.dsig.internal.dom.DOMKeyInfo;
|
||||
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;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Signature Service implementation for Office OpenXML document format XML
|
||||
* signatures.
|
||||
*/
|
||||
public abstract class AbstractOOXMLSignatureService extends AbstractXmlSignatureService {
|
||||
|
||||
static final Log LOG = LogFactory.getLog(AbstractOOXMLSignatureService.class);
|
||||
|
||||
protected AbstractOOXMLSignatureService() {
|
||||
addSignatureAspect(new OOXMLSignatureAspect(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getSignatureDescription() {
|
||||
return "Office OpenXML Document";
|
||||
}
|
||||
|
||||
public String getFilesDigestAlgorithm() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final URIDereferencer getURIDereferencer() {
|
||||
URL ooxmlUrl = getOfficeOpenXMLDocumentURL();
|
||||
return new OOXMLURIDereferencer(ooxmlUrl);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getCanonicalizationMethod() {
|
||||
return CanonicalizationMethod.INCLUSIVE;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void postSign(Element signatureElement, List<X509Certificate> signingCertificateChain) {
|
||||
// TODO: implement as SignatureAspect
|
||||
LOG.debug("postSign: adding ds:KeyInfo");
|
||||
/*
|
||||
* Make sure we insert right after the ds:SignatureValue element.
|
||||
*/
|
||||
Node nextSibling;
|
||||
NodeList objectNodeList = signatureElement.getElementsByTagNameNS("http://www.w3.org/2000/09/xmldsig#", "Object");
|
||||
if (0 == objectNodeList.getLength()) {
|
||||
nextSibling = null;
|
||||
} else {
|
||||
nextSibling = objectNodeList.item(0);
|
||||
}
|
||||
/*
|
||||
* Add a ds:KeyInfo entry.
|
||||
*/
|
||||
KeyInfoFactory keyInfoFactory = KeyInfoFactory.getInstance();
|
||||
List<Object> x509DataObjects = new LinkedList<Object>();
|
||||
|
||||
X509Certificate signingCertificate = signingCertificateChain.get(0);
|
||||
KeyValue keyValue;
|
||||
try {
|
||||
keyValue = keyInfoFactory.newKeyValue(signingCertificate.getPublicKey());
|
||||
} catch (KeyException e) {
|
||||
throw new RuntimeException("key exception: " + e.getMessage(), e);
|
||||
}
|
||||
|
||||
for (X509Certificate certificate : signingCertificateChain) {
|
||||
x509DataObjects.add(certificate);
|
||||
}
|
||||
X509Data x509Data = keyInfoFactory.newX509Data(x509DataObjects);
|
||||
List<Object> keyInfoContent = new LinkedList<Object>();
|
||||
keyInfoContent.add(keyValue);
|
||||
keyInfoContent.add(x509Data);
|
||||
KeyInfo keyInfo = keyInfoFactory.newKeyInfo(keyInfoContent);
|
||||
DOMKeyInfo domKeyInfo = (DOMKeyInfo) keyInfo;
|
||||
Key key = new Key() {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public String getAlgorithm() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public byte[] getEncoded() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getFormat() {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
XMLSignContext xmlSignContext = new DOMSignContext(key, signatureElement);
|
||||
DOMCryptoContext domCryptoContext = (DOMCryptoContext) xmlSignContext;
|
||||
String dsPrefix = null;
|
||||
// String dsPrefix = "ds";
|
||||
try {
|
||||
domKeyInfo.marshal(signatureElement, nextSibling, dsPrefix, domCryptoContext);
|
||||
} catch (MarshalException e) {
|
||||
throw new RuntimeException("marshall error: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private class OOXMLSignedDocumentOutputStream extends ByteArrayOutputStream {
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
LOG.debug("close OOXML signed document output stream");
|
||||
super.close();
|
||||
try {
|
||||
outputSignedOfficeOpenXMLDocument(this.toByteArray());
|
||||
} catch (Exception e) {
|
||||
throw new IOException("generic error: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The output stream to which to write the signed Office OpenXML file.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
abstract protected OutputStream getSignedOfficeOpenXMLDocumentOutputStream();
|
||||
|
||||
/**
|
||||
* Gives back the URL of the OOXML to be signed.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
abstract protected URL getOfficeOpenXMLDocumentURL();
|
||||
|
||||
private void outputSignedOfficeOpenXMLDocument(byte[] signatureData) throws IOException, ParserConfigurationException, SAXException, TransformerException {
|
||||
LOG.debug("output signed Office OpenXML document");
|
||||
OutputStream signedOOXMLOutputStream = getSignedOfficeOpenXMLDocumentOutputStream();
|
||||
if (null == signedOOXMLOutputStream) {
|
||||
throw new NullPointerException("signedOOXMLOutputStream is null");
|
||||
}
|
||||
|
||||
String signatureZipEntryName = "_xmlsignatures/sig-" + UUID.randomUUID().toString() + ".xml";
|
||||
LOG.debug("signature ZIP entry name: " + signatureZipEntryName);
|
||||
/*
|
||||
* Copy the original OOXML content to the signed OOXML package. During
|
||||
* copying some files need to changed.
|
||||
*/
|
||||
ZipOutputStream zipOutputStream = copyOOXMLContent(signatureZipEntryName, signedOOXMLOutputStream);
|
||||
|
||||
/*
|
||||
* Add the OOXML XML signature file to the OOXML package.
|
||||
*/
|
||||
ZipEntry zipEntry = new ZipEntry(signatureZipEntryName);
|
||||
zipOutputStream.putNextEntry(zipEntry);
|
||||
IOUtils.write(signatureData, zipOutputStream);
|
||||
zipOutputStream.close();
|
||||
}
|
||||
|
||||
private ZipOutputStream copyOOXMLContent(String signatureZipEntryName, OutputStream signedOOXMLOutputStream) throws IOException,
|
||||
ParserConfigurationException, SAXException, TransformerConfigurationException, TransformerFactoryConfigurationError,
|
||||
TransformerException {
|
||||
ZipOutputStream zipOutputStream = new ZipOutputStream(signedOOXMLOutputStream);
|
||||
ZipInputStream zipInputStream = new ZipInputStream(this.getOfficeOpenXMLDocumentURL().openStream());
|
||||
ZipEntry zipEntry;
|
||||
boolean hasOriginSigsRels = false;
|
||||
while (null != (zipEntry = zipInputStream.getNextEntry())) {
|
||||
LOG.debug("copy ZIP entry: " + zipEntry.getName());
|
||||
ZipEntry newZipEntry = new ZipEntry(zipEntry.getName());
|
||||
zipOutputStream.putNextEntry(newZipEntry);
|
||||
if ("[Content_Types].xml".equals(zipEntry.getName())) {
|
||||
Document contentTypesDocument = loadDocumentNoClose(zipInputStream);
|
||||
Element typesElement = contentTypesDocument.getDocumentElement();
|
||||
|
||||
/*
|
||||
* We need to add an Override element.
|
||||
*/
|
||||
Element overrideElement = contentTypesDocument.createElementNS("http://schemas.openxmlformats.org/package/2006/content-types", "Override");
|
||||
overrideElement.setAttribute("PartName", "/" + signatureZipEntryName);
|
||||
overrideElement.setAttribute("ContentType", "application/vnd.openxmlformats-package.digital-signature-xmlsignature+xml");
|
||||
typesElement.appendChild(overrideElement);
|
||||
|
||||
Element nsElement = contentTypesDocument.createElement("ns");
|
||||
nsElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:tns", "http://schemas.openxmlformats.org/package/2006/content-types");
|
||||
NodeList nodeList = XPathAPI.selectNodeList(contentTypesDocument, "/tns:Types/tns:Default[@Extension='sigs']", nsElement);
|
||||
if (0 == nodeList.getLength()) {
|
||||
/*
|
||||
* Add Default element for 'sigs' extension.
|
||||
*/
|
||||
Element defaultElement = contentTypesDocument.createElementNS("http://schemas.openxmlformats.org/package/2006/content-types", "Default");
|
||||
defaultElement.setAttribute("Extension", "sigs");
|
||||
defaultElement.setAttribute("ContentType", "application/vnd.openxmlformats-package.digital-signature-origin");
|
||||
typesElement.appendChild(defaultElement);
|
||||
}
|
||||
|
||||
writeDocumentNoClosing(contentTypesDocument, zipOutputStream, false);
|
||||
} else if ("_rels/.rels".equals(zipEntry.getName())) {
|
||||
Document relsDocument = loadDocumentNoClose(zipInputStream);
|
||||
|
||||
Element nsElement = relsDocument.createElement("ns");
|
||||
nsElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:tns", "http://schemas.openxmlformats.org/package/2006/relationships");
|
||||
NodeList nodeList = XPathAPI.selectNodeList(relsDocument, "/tns:Relationships/tns:Relationship[@Target='_xmlsignatures/origin.sigs']",
|
||||
nsElement);
|
||||
if (0 == nodeList.getLength()) {
|
||||
Element relationshipElement = relsDocument.createElementNS("http://schemas.openxmlformats.org/package/2006/relationships", "Relationship");
|
||||
relationshipElement.setAttribute("Id", "rel-id-" + UUID.randomUUID().toString());
|
||||
relationshipElement.setAttribute("Type", "http://schemas.openxmlformats.org/package/2006/relationships/digital-signature/origin");
|
||||
relationshipElement.setAttribute("Target", "_xmlsignatures/origin.sigs");
|
||||
|
||||
relsDocument.getDocumentElement().appendChild(relationshipElement);
|
||||
}
|
||||
|
||||
writeDocumentNoClosing(relsDocument, zipOutputStream, false);
|
||||
} else if ("_xmlsignatures/_rels/origin.sigs.rels".equals(zipEntry.getName())) {
|
||||
hasOriginSigsRels = true;
|
||||
Document originSignRelsDocument = loadDocumentNoClose(zipInputStream);
|
||||
|
||||
Element relationshipElement = originSignRelsDocument.createElementNS("http://schemas.openxmlformats.org/package/2006/relationships",
|
||||
"Relationship");
|
||||
String relationshipId = "rel-" + UUID.randomUUID().toString();
|
||||
relationshipElement.setAttribute("Id", relationshipId);
|
||||
relationshipElement.setAttribute("Type", "http://schemas.openxmlformats.org/package/2006/relationships/digital-signature/signature");
|
||||
String target = FilenameUtils.getName(signatureZipEntryName);
|
||||
LOG.debug("target: " + target);
|
||||
relationshipElement.setAttribute("Target", target);
|
||||
originSignRelsDocument.getDocumentElement().appendChild(relationshipElement);
|
||||
|
||||
writeDocumentNoClosing(originSignRelsDocument, zipOutputStream, false);
|
||||
} else {
|
||||
IOUtils.copy(zipInputStream, zipOutputStream);
|
||||
}
|
||||
}
|
||||
|
||||
if (false == hasOriginSigsRels) {
|
||||
/*
|
||||
* Add signature relationships document.
|
||||
*/
|
||||
addOriginSigsRels(signatureZipEntryName, zipOutputStream);
|
||||
addOriginSigs(zipOutputStream);
|
||||
}
|
||||
|
||||
/*
|
||||
* Return.
|
||||
*/
|
||||
zipInputStream.close();
|
||||
return zipOutputStream;
|
||||
}
|
||||
|
||||
private void addOriginSigs(ZipOutputStream zipOutputStream) throws IOException {
|
||||
zipOutputStream.putNextEntry(new ZipEntry("_xmlsignatures/origin.sigs"));
|
||||
}
|
||||
|
||||
private void addOriginSigsRels(String signatureZipEntryName, ZipOutputStream zipOutputStream) throws ParserConfigurationException, IOException,
|
||||
TransformerConfigurationException, TransformerFactoryConfigurationError, TransformerException {
|
||||
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
|
||||
documentBuilderFactory.setNamespaceAware(true);
|
||||
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
|
||||
Document originSignRelsDocument = documentBuilder.newDocument();
|
||||
|
||||
Element relationshipsElement = originSignRelsDocument.createElementNS("http://schemas.openxmlformats.org/package/2006/relationships", "Relationships");
|
||||
relationshipsElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns", "http://schemas.openxmlformats.org/package/2006/relationships");
|
||||
originSignRelsDocument.appendChild(relationshipsElement);
|
||||
|
||||
Element relationshipElement = originSignRelsDocument.createElementNS("http://schemas.openxmlformats.org/package/2006/relationships", "Relationship");
|
||||
String relationshipId = "rel-" + UUID.randomUUID().toString();
|
||||
relationshipElement.setAttribute("Id", relationshipId);
|
||||
relationshipElement.setAttribute("Type", "http://schemas.openxmlformats.org/package/2006/relationships/digital-signature/signature");
|
||||
String target = FilenameUtils.getName(signatureZipEntryName);
|
||||
LOG.debug("target: " + target);
|
||||
relationshipElement.setAttribute("Target", target);
|
||||
relationshipsElement.appendChild(relationshipElement);
|
||||
|
||||
zipOutputStream.putNextEntry(new ZipEntry("_xmlsignatures/_rels/origin.sigs.rels"));
|
||||
writeDocumentNoClosing(originSignRelsDocument, zipOutputStream, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected OutputStream getSignedDocumentOutputStream() {
|
||||
LOG.debug("get signed document output stream");
|
||||
/*
|
||||
* Create each time a new object; we want an empty output stream to
|
||||
* start with.
|
||||
*/
|
||||
OutputStream signedDocumentOutputStream = new OOXMLSignedDocumentOutputStream();
|
||||
return signedDocumentOutputStream;
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
|
||||
/* ====================================================================
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You 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.
|
||||
==================================================================== */
|
||||
|
||||
|
||||
/*
|
||||
* Based on the eID Applet Project code.
|
||||
* Original Copyright (C) 2008-2009 FedICT.
|
||||
*/
|
||||
|
||||
package org.apache.poi.ooxml.signature.service.signer.ooxml;
|
||||
|
||||
import java.security.Provider;
|
||||
import java.security.Security;
|
||||
|
||||
/**
|
||||
* Security Provider for Office OpenXML.
|
||||
*/
|
||||
public class OOXMLProvider extends Provider {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public static final String NAME = "OOXMLProvider";
|
||||
|
||||
private OOXMLProvider() {
|
||||
super(NAME, 1.0, "OOXML Security Provider");
|
||||
put("TransformService." + RelationshipTransformService.TRANSFORM_URI, RelationshipTransformService.class.getName());
|
||||
put("TransformService." + RelationshipTransformService.TRANSFORM_URI + " MechanismType", "DOM");
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs this security provider.
|
||||
*/
|
||||
public static void install() {
|
||||
Provider provider = Security.getProvider(NAME);
|
||||
if (null == provider) {
|
||||
Security.addProvider(new OOXMLProvider());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,353 @@
|
||||
|
||||
/* ====================================================================
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You 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.
|
||||
==================================================================== */
|
||||
|
||||
|
||||
/*
|
||||
* Based on the eID Applet Project code.
|
||||
* Original Copyright (C) 2008-2009 FedICT.
|
||||
*/
|
||||
|
||||
package org.apache.poi.ooxml.signature.service.signer.ooxml;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
import javax.xml.crypto.XMLStructure;
|
||||
import javax.xml.crypto.dom.DOMStructure;
|
||||
import javax.xml.crypto.dsig.DigestMethod;
|
||||
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.Transform;
|
||||
import javax.xml.crypto.dsig.XMLObject;
|
||||
import javax.xml.crypto.dsig.XMLSignatureFactory;
|
||||
import javax.xml.crypto.dsig.spec.TransformParameterSpec;
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.transform.TransformerException;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.apache.poi.ooxml.signature.service.signer.NoCloseInputStream;
|
||||
import org.apache.poi.ooxml.signature.service.signer.SignatureAspect;
|
||||
import org.apache.xml.security.utils.Constants;
|
||||
import org.apache.xpath.XPathAPI;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.DateTimeZone;
|
||||
import org.joda.time.format.DateTimeFormatter;
|
||||
import org.joda.time.format.ISODateTimeFormat;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
import org.xml.sax.InputSource;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Office OpenXML Signature Aspect implementation.
|
||||
*/
|
||||
public class OOXMLSignatureAspect implements SignatureAspect {
|
||||
|
||||
private static final Log LOG = LogFactory.getLog(OOXMLSignatureAspect.class);
|
||||
|
||||
private final AbstractOOXMLSignatureService signatureService;
|
||||
|
||||
/**
|
||||
* Main constructor.
|
||||
*
|
||||
* @param ooxmlUrl
|
||||
*/
|
||||
public OOXMLSignatureAspect(AbstractOOXMLSignatureService signatureService) {
|
||||
this.signatureService = signatureService;
|
||||
}
|
||||
|
||||
public void preSign(XMLSignatureFactory signatureFactory, Document document, String signatureId, List<Reference> references, List<XMLObject> objects)
|
||||
throws NoSuchAlgorithmException, InvalidAlgorithmParameterException {
|
||||
LOG.debug("pre sign");
|
||||
addManifestObject(signatureFactory, document, signatureId, references, objects);
|
||||
|
||||
addSignatureInfo(signatureFactory, document, signatureId, references, objects);
|
||||
}
|
||||
|
||||
private void addManifestObject(XMLSignatureFactory signatureFactory, Document document, String signatureId, List<Reference> references,
|
||||
List<XMLObject> objects) throws NoSuchAlgorithmException, InvalidAlgorithmParameterException {
|
||||
Manifest manifest = constructManifest(signatureFactory, document);
|
||||
String objectId = "idPackageObject"; // really has to be this value.
|
||||
List<XMLStructure> objectContent = new LinkedList<XMLStructure>();
|
||||
objectContent.add(manifest);
|
||||
|
||||
addSignatureTime(signatureFactory, document, signatureId, objectContent);
|
||||
|
||||
objects.add(signatureFactory.newXMLObject(objectContent, objectId, null, null));
|
||||
|
||||
DigestMethod digestMethod = signatureFactory.newDigestMethod(DigestMethod.SHA1, null);
|
||||
Reference reference = signatureFactory.newReference("#" + objectId, digestMethod, null, "http://www.w3.org/2000/09/xmldsig#Object", null);
|
||||
references.add(reference);
|
||||
}
|
||||
|
||||
private Manifest constructManifest(XMLSignatureFactory signatureFactory, Document document) throws NoSuchAlgorithmException,
|
||||
InvalidAlgorithmParameterException {
|
||||
List<Reference> manifestReferences = new LinkedList<Reference>();
|
||||
|
||||
try {
|
||||
addRelationshipsReferences(signatureFactory, document, manifestReferences);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("error: " + e.getMessage(), e);
|
||||
}
|
||||
|
||||
/*
|
||||
* Word
|
||||
*/
|
||||
addParts(signatureFactory, "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml", manifestReferences);
|
||||
addParts(signatureFactory, "application/vnd.openxmlformats-officedocument.wordprocessingml.fontTable+xml", manifestReferences);
|
||||
addParts(signatureFactory, "application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml", manifestReferences);
|
||||
addParts(signatureFactory, "application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml", manifestReferences);
|
||||
addParts(signatureFactory, "application/vnd.openxmlformats-officedocument.theme+xml", manifestReferences);
|
||||
addParts(signatureFactory, "application/vnd.openxmlformats-officedocument.wordprocessingml.webSettings+xml", manifestReferences);
|
||||
|
||||
/*
|
||||
* Powerpoint
|
||||
*/
|
||||
addParts(signatureFactory, "application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml", manifestReferences);
|
||||
addParts(signatureFactory, "application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml", manifestReferences);
|
||||
addParts(signatureFactory, "application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml", manifestReferences);
|
||||
addParts(signatureFactory, "application/vnd.openxmlformats-officedocument.presentationml.slide+xml", manifestReferences);
|
||||
addParts(signatureFactory, "application/vnd.openxmlformats-officedocument.presentationml.tableStyles+xml", manifestReferences);
|
||||
|
||||
Manifest manifest = signatureFactory.newManifest(manifestReferences);
|
||||
return manifest;
|
||||
}
|
||||
|
||||
private void addSignatureTime(XMLSignatureFactory signatureFactory, Document document, String signatureId, List<XMLStructure> objectContent) {
|
||||
/*
|
||||
* SignatureTime
|
||||
*/
|
||||
Element signatureTimeElement = document.createElementNS("http://schemas.openxmlformats.org/package/2006/digital-signature", "mdssi:SignatureTime");
|
||||
signatureTimeElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:mdssi", "http://schemas.openxmlformats.org/package/2006/digital-signature");
|
||||
Element formatElement = document.createElementNS("http://schemas.openxmlformats.org/package/2006/digital-signature", "mdssi:Format");
|
||||
formatElement.setTextContent("YYYY-MM-DDThh:mm:ssTZD");
|
||||
signatureTimeElement.appendChild(formatElement);
|
||||
Element valueElement = document.createElementNS("http://schemas.openxmlformats.org/package/2006/digital-signature", "mdssi:Value");
|
||||
DateTime dateTime = new DateTime(DateTimeZone.UTC);
|
||||
DateTimeFormatter fmt = ISODateTimeFormat.dateTimeNoMillis();
|
||||
String now = fmt.print(dateTime);
|
||||
LOG.debug("now: " + now);
|
||||
valueElement.setTextContent(now);
|
||||
signatureTimeElement.appendChild(valueElement);
|
||||
|
||||
List<XMLStructure> signatureTimeContent = new LinkedList<XMLStructure>();
|
||||
signatureTimeContent.add(new DOMStructure(signatureTimeElement));
|
||||
SignatureProperty signatureTimeSignatureProperty = signatureFactory.newSignatureProperty(signatureTimeContent, "#" + signatureId, "idSignatureTime");
|
||||
List<SignatureProperty> signaturePropertyContent = new LinkedList<SignatureProperty>();
|
||||
signaturePropertyContent.add(signatureTimeSignatureProperty);
|
||||
SignatureProperties signatureProperties = signatureFactory.newSignatureProperties(signaturePropertyContent, "id-signature-time-"
|
||||
+ UUID.randomUUID().toString());
|
||||
objectContent.add(signatureProperties);
|
||||
}
|
||||
|
||||
private void addSignatureInfo(XMLSignatureFactory signatureFactory, Document document, String signatureId, List<Reference> references,
|
||||
List<XMLObject> objects) throws NoSuchAlgorithmException, InvalidAlgorithmParameterException {
|
||||
List<XMLStructure> objectContent = new LinkedList<XMLStructure>();
|
||||
|
||||
Element signatureInfoElement = document.createElementNS("http://schemas.microsoft.com/office/2006/digsig", "SignatureInfoV1");
|
||||
signatureInfoElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns", "http://schemas.microsoft.com/office/2006/digsig");
|
||||
|
||||
Element manifestHashAlgorithmElement = document.createElementNS("http://schemas.microsoft.com/office/2006/digsig", "ManifestHashAlgorithm");
|
||||
manifestHashAlgorithmElement.setTextContent("http://www.w3.org/2000/09/xmldsig#sha1");
|
||||
signatureInfoElement.appendChild(manifestHashAlgorithmElement);
|
||||
|
||||
List<XMLStructure> signatureInfoContent = new LinkedList<XMLStructure>();
|
||||
signatureInfoContent.add(new DOMStructure(signatureInfoElement));
|
||||
SignatureProperty signatureInfoSignatureProperty = signatureFactory.newSignatureProperty(signatureInfoContent, "#" + signatureId, "idOfficeV1Details");
|
||||
|
||||
List<SignatureProperty> signaturePropertyContent = new LinkedList<SignatureProperty>();
|
||||
signaturePropertyContent.add(signatureInfoSignatureProperty);
|
||||
SignatureProperties signatureProperties = signatureFactory.newSignatureProperties(signaturePropertyContent, null);
|
||||
objectContent.add(signatureProperties);
|
||||
|
||||
String objectId = "idOfficeObject";
|
||||
objects.add(signatureFactory.newXMLObject(objectContent, objectId, null, null));
|
||||
|
||||
DigestMethod digestMethod = signatureFactory.newDigestMethod(DigestMethod.SHA1, null);
|
||||
Reference reference = signatureFactory.newReference("#" + objectId, digestMethod, null, "http://www.w3.org/2000/09/xmldsig#Object", null);
|
||||
references.add(reference);
|
||||
}
|
||||
|
||||
private void addRelationshipsReferences(XMLSignatureFactory signatureFactory, Document document, List<Reference> manifestReferences) throws IOException,
|
||||
ParserConfigurationException, SAXException, TransformerException, NoSuchAlgorithmException,
|
||||
InvalidAlgorithmParameterException {
|
||||
URL ooxmlUrl = this.signatureService.getOfficeOpenXMLDocumentURL();
|
||||
InputStream inputStream = ooxmlUrl.openStream();
|
||||
ZipInputStream zipInputStream = new ZipInputStream(inputStream);
|
||||
ZipEntry zipEntry;
|
||||
while (null != (zipEntry = zipInputStream.getNextEntry())) {
|
||||
if (false == zipEntry.getName().endsWith(".rels")) {
|
||||
continue;
|
||||
}
|
||||
Document relsDocument = loadDocumentNoClose(zipInputStream);
|
||||
addRelationshipsReference(signatureFactory, document, zipEntry.getName(), relsDocument, manifestReferences);
|
||||
}
|
||||
}
|
||||
|
||||
private void addRelationshipsReference(XMLSignatureFactory signatureFactory, Document document, String zipEntryName, Document relsDocument,
|
||||
List<Reference> manifestReferences) throws NoSuchAlgorithmException, InvalidAlgorithmParameterException {
|
||||
LOG.debug("relationships: " + zipEntryName);
|
||||
RelationshipTransformParameterSpec parameterSpec = new RelationshipTransformParameterSpec();
|
||||
NodeList nodeList = relsDocument.getDocumentElement().getChildNodes();
|
||||
for (int nodeIdx = 0; nodeIdx < nodeList.getLength(); nodeIdx++) {
|
||||
Node node = nodeList.item(nodeIdx);
|
||||
if (node.getNodeType() != Node.ELEMENT_NODE) {
|
||||
continue;
|
||||
}
|
||||
Element element = (Element) node;
|
||||
String relationshipType = element.getAttribute("Type");
|
||||
/*
|
||||
* We skip some relationship types.
|
||||
*/
|
||||
if ("http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties".equals(relationshipType)) {
|
||||
continue;
|
||||
}
|
||||
if ("http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties".equals(relationshipType)) {
|
||||
continue;
|
||||
}
|
||||
if ("http://schemas.openxmlformats.org/package/2006/relationships/digital-signature/origin".equals(relationshipType)) {
|
||||
continue;
|
||||
}
|
||||
if ("http://schemas.openxmlformats.org/package/2006/relationships/metadata/thumbnail".equals(relationshipType)) {
|
||||
continue;
|
||||
}
|
||||
if ("http://schemas.openxmlformats.org/officeDocument/2006/relationships/presProps".equals(relationshipType)) {
|
||||
continue;
|
||||
}
|
||||
if ("http://schemas.openxmlformats.org/officeDocument/2006/relationships/viewProps".equals(relationshipType)) {
|
||||
continue;
|
||||
}
|
||||
String relationshipId = element.getAttribute("Id");
|
||||
parameterSpec.addRelationshipReference(relationshipId);
|
||||
}
|
||||
|
||||
List<Transform> transforms = new LinkedList<Transform>();
|
||||
transforms.add(signatureFactory.newTransform(RelationshipTransformService.TRANSFORM_URI, parameterSpec));
|
||||
transforms.add(signatureFactory.newTransform("http://www.w3.org/TR/2001/REC-xml-c14n-20010315", (TransformParameterSpec) null));
|
||||
DigestMethod digestMethod = signatureFactory.newDigestMethod(DigestMethod.SHA1, null);
|
||||
Reference reference = signatureFactory.newReference("/" + zipEntryName + "?ContentType=application/vnd.openxmlformats-package.relationships+xml",
|
||||
digestMethod, transforms, null, null);
|
||||
|
||||
manifestReferences.add(reference);
|
||||
}
|
||||
|
||||
private void addParts(XMLSignatureFactory signatureFactory, String contentType, List<Reference> references) throws NoSuchAlgorithmException,
|
||||
InvalidAlgorithmParameterException {
|
||||
List<String> documentResourceNames;
|
||||
try {
|
||||
documentResourceNames = getResourceNames(this.signatureService.getOfficeOpenXMLDocumentURL(), contentType);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
DigestMethod digestMethod = signatureFactory.newDigestMethod(DigestMethod.SHA1, null);
|
||||
for (String documentResourceName : documentResourceNames) {
|
||||
LOG.debug("document resource: " + documentResourceName);
|
||||
|
||||
Reference reference = signatureFactory.newReference("/" + documentResourceName + "?ContentType=" + contentType, digestMethod);
|
||||
|
||||
references.add(reference);
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> getResourceNames(URL url, String contentType) throws IOException, ParserConfigurationException, SAXException, TransformerException {
|
||||
List<String> signatureResourceNames = new LinkedList<String>();
|
||||
if (null == url) {
|
||||
throw new RuntimeException("OOXML URL is null");
|
||||
}
|
||||
InputStream inputStream = url.openStream();
|
||||
ZipInputStream zipInputStream = new ZipInputStream(inputStream);
|
||||
ZipEntry zipEntry;
|
||||
while (null != (zipEntry = zipInputStream.getNextEntry())) {
|
||||
if (false == "[Content_Types].xml".equals(zipEntry.getName())) {
|
||||
continue;
|
||||
}
|
||||
Document contentTypesDocument = loadDocument(zipInputStream);
|
||||
Element nsElement = contentTypesDocument.createElement("ns");
|
||||
nsElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:tns", "http://schemas.openxmlformats.org/package/2006/content-types");
|
||||
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 '/'
|
||||
signatureResourceNames.add(partName);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return signatureResourceNames;
|
||||
}
|
||||
|
||||
protected Document loadDocument(String zipEntryName) throws IOException, ParserConfigurationException, SAXException {
|
||||
Document document = findDocument(zipEntryName);
|
||||
if (null != document) {
|
||||
return document;
|
||||
}
|
||||
throw new RuntimeException("ZIP entry not found: " + zipEntryName);
|
||||
}
|
||||
|
||||
protected Document findDocument(String zipEntryName) throws IOException, ParserConfigurationException, SAXException {
|
||||
URL ooxmlUrl = this.signatureService.getOfficeOpenXMLDocumentURL();
|
||||
InputStream inputStream = ooxmlUrl.openStream();
|
||||
ZipInputStream zipInputStream = new ZipInputStream(inputStream);
|
||||
ZipEntry zipEntry;
|
||||
while (null != (zipEntry = zipInputStream.getNextEntry())) {
|
||||
if (false == zipEntryName.equals(zipEntry.getName())) {
|
||||
continue;
|
||||
}
|
||||
Document document = loadDocument(zipInputStream);
|
||||
return document;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Document loadDocumentNoClose(InputStream documentInputStream) throws ParserConfigurationException, SAXException, IOException {
|
||||
NoCloseInputStream noCloseInputStream = new NoCloseInputStream(documentInputStream);
|
||||
InputSource inputSource = new InputSource(noCloseInputStream);
|
||||
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
|
||||
documentBuilderFactory.setNamespaceAware(true);
|
||||
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
|
||||
Document document = documentBuilder.parse(inputSource);
|
||||
return document;
|
||||
}
|
||||
|
||||
private Document loadDocument(InputStream documentInputStream) throws ParserConfigurationException, SAXException, IOException {
|
||||
InputSource inputSource = new InputSource(documentInputStream);
|
||||
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
|
||||
documentBuilderFactory.setNamespaceAware(true);
|
||||
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
|
||||
Document document = documentBuilder.parse(inputSource);
|
||||
return document;
|
||||
}
|
||||
}
|
@ -0,0 +1,211 @@
|
||||
|
||||
/* ====================================================================
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You 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.
|
||||
==================================================================== */
|
||||
|
||||
|
||||
/*
|
||||
* Based on the eID Applet Project code.
|
||||
* Original Copyright (C) 2008-2009 FedICT.
|
||||
*/
|
||||
|
||||
package org.apache.poi.ooxml.signature.service.signer.ooxml;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
import javax.xml.crypto.MarshalException;
|
||||
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.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.transform.TransformerException;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.apache.poi.POIXMLDocument;
|
||||
import org.apache.poi.ooxml.signature.service.signer.KeyInfoKeySelector;
|
||||
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
|
||||
import org.apache.poi.openxml4j.opc.OPCPackage;
|
||||
import org.apache.poi.openxml4j.opc.PackagePart;
|
||||
import org.apache.poi.openxml4j.opc.PackagePartName;
|
||||
import org.apache.poi.openxml4j.opc.PackageRelationship;
|
||||
import org.apache.poi.openxml4j.opc.PackageRelationshipCollection;
|
||||
import org.apache.poi.openxml4j.opc.PackageRelationshipTypes;
|
||||
import org.apache.poi.openxml4j.opc.PackagingURIHelper;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
import org.xml.sax.InputSource;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Signature verifier util class for Office Open XML file format.
|
||||
*/
|
||||
public class OOXMLSignatureVerifier {
|
||||
|
||||
private static final Log LOG = LogFactory.getLog(OOXMLSignatureVerifier.class);
|
||||
|
||||
private OOXMLSignatureVerifier() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
if (zipEntry.getSize() > 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static List<X509Certificate> getSigners(URL url) throws IOException, ParserConfigurationException, SAXException, TransformerException,
|
||||
MarshalException, XMLSignatureException, InvalidFormatException {
|
||||
List<X509Certificate> signers = new LinkedList<X509Certificate>();
|
||||
List<PackagePart> signatureParts = getSignatureParts(url);
|
||||
if (signatureParts.isEmpty()) {
|
||||
LOG.debug("no signature resources");
|
||||
}
|
||||
for (PackagePart signaturePart : signatureParts) {
|
||||
Document signatureDocument = loadDocument(signaturePart);
|
||||
if (null == signatureDocument) {
|
||||
continue;
|
||||
}
|
||||
|
||||
NodeList signatureNodeList = signatureDocument.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature");
|
||||
if (0 == signatureNodeList.getLength()) {
|
||||
return null;
|
||||
}
|
||||
Node signatureNode = signatureNodeList.item(0);
|
||||
|
||||
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 validity = xmlSignature.validate(domValidateContext);
|
||||
|
||||
if (false == validity) {
|
||||
continue;
|
||||
}
|
||||
// TODO: check what has been signed.
|
||||
|
||||
X509Certificate signer = keySelector.getCertificate();
|
||||
signers.add(signer);
|
||||
}
|
||||
return signers;
|
||||
}
|
||||
|
||||
public static boolean verifySignature(URL url) throws InvalidFormatException, IOException, ParserConfigurationException, SAXException, MarshalException,
|
||||
XMLSignatureException {
|
||||
PackagePart signaturePart = getSignaturePart(url);
|
||||
if (signaturePart == null) {
|
||||
LOG.info(url + " does not contain a signature");
|
||||
return false;
|
||||
}
|
||||
LOG.debug("signature resource name: " + signaturePart.getPartName());
|
||||
|
||||
OOXMLProvider.install();
|
||||
|
||||
Document signatureDocument = loadDocument(signaturePart);
|
||||
LOG.debug("signature loaded");
|
||||
NodeList signatureNodeList = signatureDocument.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature");
|
||||
Node signatureNode = signatureNodeList.item(0);
|
||||
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);
|
||||
return xmlSignature.validate(domValidateContext);
|
||||
}
|
||||
|
||||
private static PackagePart getSignaturePart(URL url) throws IOException, InvalidFormatException {
|
||||
List<PackagePart> packageParts = getSignatureParts(url);
|
||||
if (packageParts.isEmpty()) {
|
||||
return null;
|
||||
} else {
|
||||
return packageParts.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<PackagePart> getSignatureParts(URL url) throws IOException, InvalidFormatException {
|
||||
List<PackagePart> packageParts = new LinkedList<PackagePart>();
|
||||
OPCPackage pkg = POIXMLDocument.openPackage(url.getPath());
|
||||
PackageRelationshipCollection sigOrigRels = pkg.getRelationshipsByType(PackageRelationshipTypes.DIGITAL_SIGNATURE_ORIGIN);
|
||||
for (PackageRelationship rel : sigOrigRels) {
|
||||
PackagePartName relName = PackagingURIHelper.createPartName(rel.getTargetURI());
|
||||
PackagePart sigPart = pkg.getPart(relName);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Digital Signature Origin part = " + sigPart);
|
||||
}
|
||||
|
||||
PackageRelationshipCollection sigRels = sigPart.getRelationshipsByType(PackageRelationshipTypes.DIGITAL_SIGNATURE);
|
||||
for (PackageRelationship sigRel : sigRels) {
|
||||
PackagePartName sigRelName = PackagingURIHelper.createPartName(sigRel.getTargetURI());
|
||||
PackagePart sigRelPart = pkg.getPart(sigRelName);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("XML Signature part = " + sigRelPart);
|
||||
}
|
||||
packageParts.add(sigRelPart);
|
||||
}
|
||||
}
|
||||
return packageParts;
|
||||
}
|
||||
|
||||
private static Document loadDocument(PackagePart part) throws ParserConfigurationException, SAXException, IOException {
|
||||
InputStream documentInputStream = part.getInputStream();
|
||||
return loadDocument(documentInputStream);
|
||||
}
|
||||
|
||||
private static Document loadDocument(InputStream documentInputStream) throws ParserConfigurationException, SAXException, IOException {
|
||||
InputSource inputSource = new InputSource(documentInputStream);
|
||||
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
|
||||
documentBuilderFactory.setNamespaceAware(true);
|
||||
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
|
||||
Document document = documentBuilder.parse(inputSource);
|
||||
return document;
|
||||
}
|
||||
}
|
@ -0,0 +1,111 @@
|
||||
|
||||
/* ====================================================================
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You 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.
|
||||
==================================================================== */
|
||||
|
||||
|
||||
/*
|
||||
* Based on the eID Applet Project code.
|
||||
* Original Copyright (C) 2008-2009 FedICT.
|
||||
*/
|
||||
|
||||
package org.apache.poi.ooxml.signature.service.signer.ooxml;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URL;
|
||||
import java.net.URLDecoder;
|
||||
|
||||
import javax.xml.crypto.Data;
|
||||
import javax.xml.crypto.OctetStreamData;
|
||||
import javax.xml.crypto.URIDereferencer;
|
||||
import javax.xml.crypto.URIReference;
|
||||
import javax.xml.crypto.URIReferenceException;
|
||||
import javax.xml.crypto.XMLCryptoContext;
|
||||
import javax.xml.crypto.dsig.XMLSignatureFactory;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.apache.poi.POIXMLDocument;
|
||||
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
|
||||
import org.apache.poi.openxml4j.opc.OPCPackage;
|
||||
import org.apache.poi.openxml4j.opc.PackagePart;
|
||||
|
||||
/**
|
||||
* JSR105 URI dereferencer for Office Open XML documents.
|
||||
*/
|
||||
public class OOXMLURIDereferencer implements URIDereferencer {
|
||||
|
||||
private static final Log LOG = LogFactory.getLog(OOXMLURIDereferencer.class);
|
||||
|
||||
private final URL ooxmlUrl;
|
||||
|
||||
private final URIDereferencer baseUriDereferencer;
|
||||
|
||||
public OOXMLURIDereferencer(URL ooxmlUrl) {
|
||||
if (null == ooxmlUrl) {
|
||||
throw new IllegalArgumentException("ooxmlUrl is null");
|
||||
}
|
||||
this.ooxmlUrl = ooxmlUrl;
|
||||
XMLSignatureFactory xmlSignatureFactory = XMLSignatureFactory.getInstance();
|
||||
this.baseUriDereferencer = xmlSignatureFactory.getURIDereferencer();
|
||||
}
|
||||
|
||||
public Data dereference(URIReference uriReference, XMLCryptoContext context) throws URIReferenceException {
|
||||
if (null == uriReference) {
|
||||
throw new NullPointerException("URIReference cannot be null");
|
||||
}
|
||||
if (null == context) {
|
||||
throw new NullPointerException("XMLCrytoContext cannot be null");
|
||||
}
|
||||
|
||||
String uri = uriReference.getURI();
|
||||
try {
|
||||
uri = URLDecoder.decode(uri, "UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
LOG.warn("could not URL decode the uri: " + uri);
|
||||
}
|
||||
LOG.debug("dereference: " + uri);
|
||||
try {
|
||||
InputStream dataInputStream = findDataInputStream(uri);
|
||||
if (null == dataInputStream) {
|
||||
LOG.debug("cannot resolve, delegating to base DOM URI dereferencer: " + uri);
|
||||
return this.baseUriDereferencer.dereference(uriReference, context);
|
||||
}
|
||||
return new OctetStreamData(dataInputStream, uri, null);
|
||||
} catch (IOException e) {
|
||||
throw new URIReferenceException("I/O error: " + e.getMessage(), e);
|
||||
} catch (InvalidFormatException e) {
|
||||
throw new URIReferenceException("Invalid format error: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private InputStream findDataInputStream(String uri) throws IOException, InvalidFormatException {
|
||||
if (-1 != uri.indexOf("?")) {
|
||||
uri = uri.substring(0, uri.indexOf("?"));
|
||||
}
|
||||
OPCPackage pkg = POIXMLDocument.openPackage(this.ooxmlUrl.getPath());
|
||||
for (PackagePart part : pkg.getParts()) {
|
||||
if (uri.equals(part.getPartName().getURI().toString())) {
|
||||
LOG.debug("Part name: " + part.getPartName());
|
||||
return part.getInputStream();
|
||||
}
|
||||
}
|
||||
LOG.info("No part found for URI: " + uri);
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
|
||||
/* ====================================================================
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You 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.
|
||||
==================================================================== */
|
||||
|
||||
|
||||
/*
|
||||
* Based on the eID Applet Project code.
|
||||
* Original Copyright (C) 2008-2009 FedICT.
|
||||
*/
|
||||
|
||||
package org.apache.poi.ooxml.signature.service.signer.ooxml;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
/**
|
||||
* Comparator for Relationship DOM elements.
|
||||
*/
|
||||
public class RelationshipComparator implements Comparator<Element> {
|
||||
|
||||
public int compare(Element element1, Element element2) {
|
||||
String id1 = element1.getAttribute("Id");
|
||||
String id2 = element2.getAttribute("Id");
|
||||
return id1.compareTo(id2);
|
||||
}
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
|
||||
/* ====================================================================
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You 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.
|
||||
==================================================================== */
|
||||
|
||||
|
||||
/*
|
||||
* Based on the eID Applet Project code.
|
||||
* Original Copyright (C) 2008-2009 FedICT.
|
||||
*/
|
||||
|
||||
package org.apache.poi.ooxml.signature.service.signer.ooxml;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.xml.crypto.dsig.spec.TransformParameterSpec;
|
||||
|
||||
/**
|
||||
* Relationship Transform parameter specification class.
|
||||
*/
|
||||
public class RelationshipTransformParameterSpec implements TransformParameterSpec {
|
||||
|
||||
private final List<String> sourceIds;
|
||||
|
||||
/**
|
||||
* Main constructor.
|
||||
*/
|
||||
public RelationshipTransformParameterSpec() {
|
||||
this.sourceIds = new LinkedList<String>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a relationship reference for the given source identifier.
|
||||
*
|
||||
* @param sourceId
|
||||
*/
|
||||
public void addRelationshipReference(String sourceId) {
|
||||
this.sourceIds.add(sourceId);
|
||||
}
|
||||
|
||||
List<String> getSourceIds() {
|
||||
return this.sourceIds;
|
||||
}
|
||||
}
|
@ -0,0 +1,274 @@
|
||||
|
||||
/* ====================================================================
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You 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.
|
||||
==================================================================== */
|
||||
|
||||
|
||||
/*
|
||||
* Based on the eID Applet Project code.
|
||||
* Original Copyright (C) 2008-2009 FedICT.
|
||||
*/
|
||||
|
||||
package org.apache.poi.ooxml.signature.service.signer.ooxml;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.StringWriter;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.spec.AlgorithmParameterSpec;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.xml.crypto.Data;
|
||||
import javax.xml.crypto.MarshalException;
|
||||
import javax.xml.crypto.OctetStreamData;
|
||||
import javax.xml.crypto.XMLCryptoContext;
|
||||
import javax.xml.crypto.XMLStructure;
|
||||
import javax.xml.crypto.dom.DOMStructure;
|
||||
import javax.xml.crypto.dsig.TransformException;
|
||||
import javax.xml.crypto.dsig.TransformService;
|
||||
import javax.xml.crypto.dsig.spec.TransformParameterSpec;
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.transform.OutputKeys;
|
||||
import javax.xml.transform.Result;
|
||||
import javax.xml.transform.Source;
|
||||
import javax.xml.transform.Transformer;
|
||||
import javax.xml.transform.TransformerException;
|
||||
import javax.xml.transform.TransformerFactory;
|
||||
import javax.xml.transform.dom.DOMSource;
|
||||
import javax.xml.transform.stream.StreamResult;
|
||||
|
||||
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.InputSource;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
/**
|
||||
* JSR105 implementation of the RelationshipTransform transformation.
|
||||
*
|
||||
* <p>
|
||||
* Specs: http://openiso.org/Ecma/376/Part2/12.2.4#26
|
||||
* </p>
|
||||
*/
|
||||
public class RelationshipTransformService extends TransformService {
|
||||
|
||||
public static final String TRANSFORM_URI = "http://schemas.openxmlformats.org/package/2006/RelationshipTransform";
|
||||
|
||||
private final List<String> sourceIds;
|
||||
|
||||
private static final Log LOG = LogFactory.getLog(RelationshipTransformService.class);
|
||||
|
||||
public RelationshipTransformService() {
|
||||
super();
|
||||
LOG.debug("constructor");
|
||||
this.sourceIds = new LinkedList<String>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(TransformParameterSpec params) throws InvalidAlgorithmParameterException {
|
||||
LOG.debug("init(params)");
|
||||
if (false == params instanceof RelationshipTransformParameterSpec) {
|
||||
throw new InvalidAlgorithmParameterException();
|
||||
}
|
||||
RelationshipTransformParameterSpec relParams = (RelationshipTransformParameterSpec) params;
|
||||
for (String sourceId : relParams.getSourceIds()) {
|
||||
this.sourceIds.add(sourceId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(XMLStructure parent, XMLCryptoContext context) throws InvalidAlgorithmParameterException {
|
||||
LOG.debug("init(parent,context)");
|
||||
LOG.debug("parent java type: " + parent.getClass().getName());
|
||||
DOMStructure domParent = (DOMStructure) parent;
|
||||
Node parentNode = domParent.getNode();
|
||||
try {
|
||||
LOG.debug("parent: " + toString(parentNode));
|
||||
} catch (TransformerException e) {
|
||||
throw new InvalidAlgorithmParameterException();
|
||||
}
|
||||
Element nsElement = parentNode.getOwnerDocument().createElement("ns");
|
||||
nsElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:ds", Constants.SignatureSpecNS);
|
||||
nsElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:mdssi", "http://schemas.openxmlformats.org/package/2006/digital-signature");
|
||||
NodeList nodeList;
|
||||
try {
|
||||
nodeList = XPathAPI.selectNodeList(parentNode, "mdssi:RelationshipReference/@SourceId", nsElement);
|
||||
} catch (TransformerException e) {
|
||||
LOG.error("transformer exception: " + e.getMessage(), e);
|
||||
throw new InvalidAlgorithmParameterException();
|
||||
}
|
||||
if (0 == nodeList.getLength()) {
|
||||
LOG.warn("no RelationshipReference/@SourceId parameters present");
|
||||
}
|
||||
for (int nodeIdx = 0; nodeIdx < nodeList.getLength(); nodeIdx++) {
|
||||
Node node = nodeList.item(nodeIdx);
|
||||
String sourceId = node.getTextContent();
|
||||
LOG.debug("sourceId: " + sourceId);
|
||||
this.sourceIds.add(sourceId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void marshalParams(XMLStructure parent, XMLCryptoContext context) throws MarshalException {
|
||||
LOG.debug("marshallParams(parent,context)");
|
||||
DOMStructure domParent = (DOMStructure) parent;
|
||||
Node parentNode = domParent.getNode();
|
||||
Element parentElement = (Element) parentNode;
|
||||
parentElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:mdssi", "http://schemas.openxmlformats.org/package/2006/digital-signature");
|
||||
Document document = parentNode.getOwnerDocument();
|
||||
for (String sourceId : this.sourceIds) {
|
||||
Element relationshipReferenceElement = document.createElementNS("http://schemas.openxmlformats.org/package/2006/digital-signature",
|
||||
"mdssi:RelationshipReference");
|
||||
relationshipReferenceElement.setAttribute("SourceId", sourceId);
|
||||
parentElement.appendChild(relationshipReferenceElement);
|
||||
}
|
||||
}
|
||||
|
||||
public AlgorithmParameterSpec getParameterSpec() {
|
||||
LOG.debug("getParameterSpec");
|
||||
return null;
|
||||
}
|
||||
|
||||
public Data transform(Data data, XMLCryptoContext context) throws TransformException {
|
||||
LOG.debug("transform(data,context)");
|
||||
LOG.debug("data java type: " + data.getClass().getName());
|
||||
OctetStreamData octetStreamData = (OctetStreamData) data;
|
||||
LOG.debug("URI: " + octetStreamData.getURI());
|
||||
InputStream octetStream = octetStreamData.getOctetStream();
|
||||
Document relationshipsDocument;
|
||||
try {
|
||||
relationshipsDocument = loadDocument(octetStream);
|
||||
} catch (Exception e) {
|
||||
throw new TransformException(e.getMessage(), e);
|
||||
}
|
||||
try {
|
||||
LOG.debug("relationships document: " + toString(relationshipsDocument));
|
||||
} catch (TransformerException e) {
|
||||
throw new TransformException(e.getMessage(), e);
|
||||
}
|
||||
Element nsElement = relationshipsDocument.createElement("ns");
|
||||
nsElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:tns", "http://schemas.openxmlformats.org/package/2006/relationships");
|
||||
Element relationshipsElement = relationshipsDocument.getDocumentElement();
|
||||
NodeList childNodes = relationshipsElement.getChildNodes();
|
||||
for (int nodeIdx = 0; nodeIdx < childNodes.getLength(); nodeIdx++) {
|
||||
Node childNode = childNodes.item(nodeIdx);
|
||||
if (Node.ELEMENT_NODE != childNode.getNodeType()) {
|
||||
LOG.debug("removing node");
|
||||
relationshipsElement.removeChild(childNode);
|
||||
nodeIdx--;
|
||||
continue;
|
||||
}
|
||||
Element childElement = (Element) childNode;
|
||||
String idAttribute = childElement.getAttribute("Id");
|
||||
LOG.debug("Relationship id attribute: " + idAttribute);
|
||||
if (false == this.sourceIds.contains(idAttribute)) {
|
||||
LOG.debug("removing element: " + idAttribute);
|
||||
relationshipsElement.removeChild(childNode);
|
||||
nodeIdx--;
|
||||
}
|
||||
/*
|
||||
* See: ISO/IEC 29500-2:2008(E) - 13.2.4.24 Relationships Transform
|
||||
* Algorithm.
|
||||
*/
|
||||
if (null == childElement.getAttributeNode("TargetMode")) {
|
||||
childElement.setAttribute("TargetMode", "Internal");
|
||||
}
|
||||
}
|
||||
LOG.debug("# Relationship elements: " + relationshipsElement.getElementsByTagName("*").getLength());
|
||||
sortRelationshipElements(relationshipsElement);
|
||||
try {
|
||||
return toOctetStreamData(relationshipsDocument);
|
||||
} catch (TransformerException e) {
|
||||
throw new TransformException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private void sortRelationshipElements(Element relationshipsElement) {
|
||||
List<Element> relationshipElements = new LinkedList<Element>();
|
||||
NodeList relationshipNodes = relationshipsElement.getElementsByTagName("*");
|
||||
int nodeCount = relationshipNodes.getLength();
|
||||
for (int nodeIdx = 0; nodeIdx < nodeCount; nodeIdx++) {
|
||||
Node relationshipNode = relationshipNodes.item(0);
|
||||
Element relationshipElement = (Element) relationshipNode;
|
||||
LOG.debug("unsorted Id: " + relationshipElement.getAttribute("Id"));
|
||||
relationshipElements.add(relationshipElement);
|
||||
relationshipsElement.removeChild(relationshipNode);
|
||||
}
|
||||
Collections.sort(relationshipElements, new RelationshipComparator());
|
||||
for (Element relationshipElement : relationshipElements) {
|
||||
LOG.debug("sorted Id: " + relationshipElement.getAttribute("Id"));
|
||||
relationshipsElement.appendChild(relationshipElement);
|
||||
}
|
||||
}
|
||||
|
||||
private String toString(Node dom) throws TransformerException {
|
||||
Source source = new DOMSource(dom);
|
||||
StringWriter stringWriter = new StringWriter();
|
||||
Result result = new StreamResult(stringWriter);
|
||||
TransformerFactory transformerFactory = TransformerFactory.newInstance();
|
||||
Transformer transformer = transformerFactory.newTransformer();
|
||||
/*
|
||||
* We have to omit the ?xml declaration if we want to embed the
|
||||
* document.
|
||||
*/
|
||||
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
|
||||
transformer.transform(source, result);
|
||||
return stringWriter.getBuffer().toString();
|
||||
}
|
||||
|
||||
private OctetStreamData toOctetStreamData(Node node) throws TransformerException {
|
||||
Source source = new DOMSource(node);
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
Result result = new StreamResult(outputStream);
|
||||
TransformerFactory transformerFactory = TransformerFactory.newInstance();
|
||||
Transformer transformer = transformerFactory.newTransformer();
|
||||
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
|
||||
transformer.transform(source, result);
|
||||
LOG.debug("result: " + new String(outputStream.toByteArray()));
|
||||
return new OctetStreamData(new ByteArrayInputStream(outputStream.toByteArray()));
|
||||
}
|
||||
|
||||
private Document loadDocument(InputStream documentInputStream) throws ParserConfigurationException, SAXException, IOException {
|
||||
InputSource inputSource = new InputSource(documentInputStream);
|
||||
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
|
||||
documentBuilderFactory.setNamespaceAware(true);
|
||||
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
|
||||
Document document = documentBuilder.parse(inputSource);
|
||||
return document;
|
||||
}
|
||||
|
||||
public Data transform(Data data, XMLCryptoContext context, OutputStream os) throws TransformException {
|
||||
LOG.debug("transform(data,context,os)");
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean isFeatureSupported(String feature) {
|
||||
LOG.debug("isFeatureSupported(feature)");
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
|
||||
/* ====================================================================
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You 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.
|
||||
==================================================================== */
|
||||
|
||||
|
||||
/*
|
||||
* Based on the eID Applet Project code.
|
||||
* Original Copyright (C) 2008-2009 FedICT.
|
||||
*/
|
||||
/**
|
||||
* This package contains implementation classes for the Office Open XML Signature Service.
|
||||
*/
|
||||
package org.apache.poi.ooxml.signature.service.signer.ooxml;
|
||||
|
@ -0,0 +1,28 @@
|
||||
|
||||
/* ====================================================================
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You 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.
|
||||
==================================================================== */
|
||||
|
||||
/*
|
||||
* Based on the eID Applet Project code.
|
||||
* Original Copyright (C) 2008-2009 FedICT.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This package contains implementation classes for the Signature Service SPI.
|
||||
*/
|
||||
package org.apache.poi.ooxml.signature.service.signer;
|
||||
|
@ -0,0 +1,56 @@
|
||||
|
||||
/* ====================================================================
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You 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.
|
||||
==================================================================== */
|
||||
|
||||
|
||||
/*
|
||||
* Based on the eID Applet Project code.
|
||||
* Original Copyright (C) 2008-2009 FedICT.
|
||||
*/
|
||||
|
||||
package org.apache.poi.ooxml.signature.service.spi;
|
||||
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Interface for authentication service components.
|
||||
*/
|
||||
public interface AuthenticationService {
|
||||
|
||||
/**
|
||||
* Validates the given certificate chain. After the client has
|
||||
* verified the authentication signature, it will invoke this method on your
|
||||
* authentication service component. The implementation of this method
|
||||
* should validate the given certificate chain. This validation could be
|
||||
* based on PKI validation, or could be based on simply trusting the
|
||||
* incoming public key. The actual implementation is very dependent on your
|
||||
* type of application. This method should only be used for certificate
|
||||
* validation.
|
||||
*
|
||||
* <p>
|
||||
* Check out <a href="http://code.google.com/p/jtrust/">jTrust</a> for an
|
||||
* implementation of a PKI validation framework.
|
||||
* </p>
|
||||
*
|
||||
* @param certificateChain
|
||||
* the X509 authentication certificate chain of the citizen.
|
||||
* @throws SecurityException
|
||||
* in case the certificate chain is invalid/not accepted.
|
||||
*/
|
||||
void validateCertificateChain(List<X509Certificate> certificateChain) throws SecurityException;
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
|
||||
/* ====================================================================
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You 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.
|
||||
==================================================================== */
|
||||
|
||||
|
||||
/*
|
||||
* Based on the eID Applet Project code.
|
||||
* Original Copyright (C) 2008-2009 FedICT.
|
||||
*/
|
||||
|
||||
package org.apache.poi.ooxml.signature.service.spi;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Digest Information data transfer class.
|
||||
*/
|
||||
public class DigestInfo implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Main constructor.
|
||||
*
|
||||
* @param digestValue
|
||||
* @param digestAlgo
|
||||
* @param description
|
||||
*/
|
||||
public DigestInfo(byte[] digestValue, String digestAlgo, String description) {
|
||||
this.digestValue = digestValue;
|
||||
this.digestAlgo = digestAlgo;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public final byte[] digestValue;
|
||||
|
||||
public final String description;
|
||||
|
||||
public final String digestAlgo;
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
|
||||
/* ====================================================================
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You 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.
|
||||
==================================================================== */
|
||||
|
||||
|
||||
/*
|
||||
* Based on the eID Applet Project code.
|
||||
* Original Copyright (C) 2008-2009 FedICT.
|
||||
*/
|
||||
|
||||
package org.apache.poi.ooxml.signature.service.spi;
|
||||
|
||||
/**
|
||||
* Insecure Client Environment Exception.
|
||||
*/
|
||||
public class InsecureClientEnvironmentException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final boolean warnOnly;
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
public InsecureClientEnvironmentException() {
|
||||
this(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Main constructor.
|
||||
*
|
||||
* @param warnOnly
|
||||
* only makes that the citizen is warned about a possible
|
||||
* insecure enviroment.
|
||||
*/
|
||||
public InsecureClientEnvironmentException(boolean warnOnly) {
|
||||
this.warnOnly = warnOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* If set the eID Applet will only give a warning on case the server-side
|
||||
* marks the client environment as being insecure. Else the eID Applet will
|
||||
* abort the requested eID operation.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isWarnOnly() {
|
||||
return this.warnOnly;
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
|
||||
/* ====================================================================
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You 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.
|
||||
==================================================================== */
|
||||
|
||||
|
||||
/*
|
||||
* Based on the eID Applet Project code.
|
||||
* Original Copyright (C) 2008-2009 FedICT.
|
||||
*/
|
||||
|
||||
package org.apache.poi.ooxml.signature.service.spi;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Interface for security environment service components. Can be used by the eID
|
||||
* Applet Service to check the client environment security requirements.
|
||||
*/
|
||||
public interface SecureClientEnvironmentService {
|
||||
|
||||
/**
|
||||
* Checks whether the client environment is secure enough for this web
|
||||
* application.
|
||||
*
|
||||
* @param javaVersion
|
||||
* the version of the Java JRE on the client machine.
|
||||
* @param javaVendor
|
||||
* the vendor of the Java JRE on the client machine.
|
||||
* @param osName
|
||||
* the name of the operating system on the client machine.
|
||||
* @param osArch
|
||||
* the architecture of the client machine.
|
||||
* @param osVersion
|
||||
* the operating system version of the client machine.
|
||||
* @param userAgent
|
||||
* the user agent, i.e. browser, used on the client machine.
|
||||
* @param navigatorAppName
|
||||
* the optional navigator application name (browser)
|
||||
* @param navigatorAppVersion
|
||||
* the optional navigator application version (browser version)
|
||||
* @param navigatorUserAgent
|
||||
* the optional optional navigator user agent name.
|
||||
* @param remoteAddress
|
||||
* the address of the client machine.
|
||||
* @param sslKeySize
|
||||
* the key size of the SSL session used between server and
|
||||
* client.
|
||||
* @param sslCipherSuite
|
||||
* the cipher suite of the SSL session used between server and
|
||||
* client.
|
||||
* @param readerList
|
||||
* the list of smart card readers present on the client machine.
|
||||
* @throws InsecureClientEnvironmentException
|
||||
* if the client env is found not to be secure enough.
|
||||
*/
|
||||
void checkSecureClientEnvironment(String javaVersion, String javaVendor, String osName, String osArch, String osVersion, String userAgent,
|
||||
String navigatorAppName, String navigatorAppVersion, String navigatorUserAgent, String remoteAddress, int sslKeySize,
|
||||
String sslCipherSuite, List<String> readerList) throws InsecureClientEnvironmentException;
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
|
||||
/* ====================================================================
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You 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.
|
||||
==================================================================== */
|
||||
|
||||
|
||||
/*
|
||||
* Based on the eID Applet Project code.
|
||||
* Original Copyright (C) 2008-2009 FedICT.
|
||||
*/
|
||||
|
||||
package org.apache.poi.ooxml.signature.service.spi;
|
||||
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Interface for signature service component.
|
||||
*/
|
||||
public interface SignatureService {
|
||||
|
||||
/**
|
||||
* Gives back the digest algorithm to be used for construction of the digest
|
||||
* infos of the preSign method. Return a digest algorithm here if you want
|
||||
* to let the client sign some locally stored files. Return
|
||||
* <code>null</code> if no pre-sign digest infos are required.
|
||||
*
|
||||
* @return
|
||||
* @see #preSign(List, List)
|
||||
*/
|
||||
String getFilesDigestAlgorithm();
|
||||
|
||||
/**
|
||||
* Pre-sign callback method. Depending on the configuration some parameters
|
||||
* are passed. The returned value will be signed by the eID Applet.
|
||||
*
|
||||
* <p>
|
||||
* TODO: service must be able to throw some exception on failure.
|
||||
* </p>
|
||||
*
|
||||
* @param digestInfos
|
||||
* the optional list of digest infos.
|
||||
* @param signingCertificateChain
|
||||
* the optional list of certificates.
|
||||
* @return the digest to be signed.
|
||||
* @throws NoSuchAlgorithmException
|
||||
*/
|
||||
DigestInfo preSign(List<DigestInfo> digestInfos, List<X509Certificate> signingCertificateChain) throws NoSuchAlgorithmException;
|
||||
|
||||
/**
|
||||
* Post-sign callback method. Received the signature value. Depending on the
|
||||
* configuration the signing certificate chain is also obtained.
|
||||
*
|
||||
* <p>
|
||||
* TODO: service must be able to throw some exception on failure.
|
||||
* </p>
|
||||
*
|
||||
* @param signatureValue
|
||||
* @param signingCertificateChain
|
||||
* the optional chain of signing certificates.
|
||||
*/
|
||||
void postSign(byte[] signatureValue, List<X509Certificate> signingCertificateChain);
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
|
||||
/* ====================================================================
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You 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.
|
||||
==================================================================== */
|
||||
|
||||
|
||||
/*
|
||||
* Based on the eID Applet Project code.
|
||||
* Original Copyright (C) 2008-2009 FedICT.
|
||||
*/
|
||||
/**
|
||||
* This package contains the service provider interfaces.
|
||||
*/
|
||||
package org.apache.poi.ooxml.signature.service.spi;
|
||||
|
Binary file not shown.
Binary file not shown.
BIN
src/ooxml/testcases/hello-world-signed-twice.docx
Normal file
BIN
src/ooxml/testcases/hello-world-signed-twice.docx
Normal file
Binary file not shown.
BIN
src/ooxml/testcases/hello-world-signed.docx
Normal file
BIN
src/ooxml/testcases/hello-world-signed.docx
Normal file
Binary file not shown.
BIN
src/ooxml/testcases/hello-world-signed.pptx
Normal file
BIN
src/ooxml/testcases/hello-world-signed.pptx
Normal file
Binary file not shown.
BIN
src/ooxml/testcases/hello-world-signed.xlsx
Normal file
BIN
src/ooxml/testcases/hello-world-signed.xlsx
Normal file
Binary file not shown.
BIN
src/ooxml/testcases/hello-world-unsigned.docx
Normal file
BIN
src/ooxml/testcases/hello-world-unsigned.docx
Normal file
Binary file not shown.
BIN
src/ooxml/testcases/hello-world-unsigned.pptx
Normal file
BIN
src/ooxml/testcases/hello-world-unsigned.pptx
Normal file
Binary file not shown.
BIN
src/ooxml/testcases/hello-world-unsigned.xlsx
Normal file
BIN
src/ooxml/testcases/hello-world-unsigned.xlsx
Normal file
Binary file not shown.
BIN
src/ooxml/testcases/invalidsig.docx
Normal file
BIN
src/ooxml/testcases/invalidsig.docx
Normal file
Binary file not shown.
@ -0,0 +1,199 @@
|
||||
|
||||
/* ====================================================================
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You 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.
|
||||
==================================================================== */
|
||||
|
||||
/*
|
||||
* Based on the eID Applet Project code.
|
||||
* Original Copyright (C) 2008-2009 FedICT.
|
||||
*/
|
||||
|
||||
package org.apache.poi.ooxml.signature.service.signer;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.StringWriter;
|
||||
import java.math.BigInteger;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.SignatureException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.security.spec.RSAKeyGenParameterSpec;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.transform.OutputKeys;
|
||||
import javax.xml.transform.Result;
|
||||
import javax.xml.transform.Source;
|
||||
import javax.xml.transform.Transformer;
|
||||
import javax.xml.transform.TransformerException;
|
||||
import javax.xml.transform.TransformerFactory;
|
||||
import javax.xml.transform.dom.DOMSource;
|
||||
import javax.xml.transform.stream.StreamResult;
|
||||
|
||||
import org.bouncycastle.asn1.ASN1InputStream;
|
||||
import org.bouncycastle.asn1.ASN1Sequence;
|
||||
import org.bouncycastle.asn1.DERIA5String;
|
||||
import org.bouncycastle.asn1.DERSequence;
|
||||
import org.bouncycastle.asn1.x509.AuthorityInformationAccess;
|
||||
import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
|
||||
import org.bouncycastle.asn1.x509.BasicConstraints;
|
||||
import org.bouncycastle.asn1.x509.DistributionPoint;
|
||||
import org.bouncycastle.asn1.x509.DistributionPointName;
|
||||
import org.bouncycastle.asn1.x509.GeneralName;
|
||||
import org.bouncycastle.asn1.x509.GeneralNames;
|
||||
import org.bouncycastle.asn1.x509.KeyUsage;
|
||||
import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
|
||||
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
|
||||
import org.bouncycastle.asn1.x509.X509Extensions;
|
||||
import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
|
||||
import org.bouncycastle.jce.X509Principal;
|
||||
import org.bouncycastle.x509.X509V3CertificateGenerator;
|
||||
import org.joda.time.DateTime;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
import org.xml.sax.InputSource;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
public class PkiTestUtils {
|
||||
|
||||
public static final byte[] SHA1_DIGEST_INFO_PREFIX = new byte[] { 0x30, 0x1f, 0x30, 0x07, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x04, 0x14 };
|
||||
|
||||
private PkiTestUtils() {
|
||||
super();
|
||||
}
|
||||
|
||||
static KeyPair generateKeyPair() throws Exception {
|
||||
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
|
||||
SecureRandom random = new SecureRandom();
|
||||
keyPairGenerator.initialize(new RSAKeyGenParameterSpec(1024, RSAKeyGenParameterSpec.F4), random);
|
||||
KeyPair keyPair = keyPairGenerator.generateKeyPair();
|
||||
return keyPair;
|
||||
}
|
||||
|
||||
private static SubjectKeyIdentifier createSubjectKeyId(PublicKey publicKey) throws IOException {
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(publicKey.getEncoded());
|
||||
SubjectPublicKeyInfo info = new SubjectPublicKeyInfo((ASN1Sequence) new ASN1InputStream(bais).readObject());
|
||||
return new SubjectKeyIdentifier(info);
|
||||
}
|
||||
|
||||
private static AuthorityKeyIdentifier createAuthorityKeyId(PublicKey publicKey) throws IOException {
|
||||
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(publicKey.getEncoded());
|
||||
SubjectPublicKeyInfo info = new SubjectPublicKeyInfo((ASN1Sequence) new ASN1InputStream(bais).readObject());
|
||||
|
||||
return new AuthorityKeyIdentifier(info);
|
||||
}
|
||||
|
||||
static X509Certificate generateCertificate(PublicKey subjectPublicKey, String subjectDn, DateTime notBefore, DateTime notAfter,
|
||||
X509Certificate issuerCertificate, PrivateKey issuerPrivateKey, boolean caFlag, int pathLength, String crlUri,
|
||||
String ocspUri, KeyUsage keyUsage) throws IOException, InvalidKeyException, IllegalStateException,
|
||||
NoSuchAlgorithmException, SignatureException, CertificateException {
|
||||
String signatureAlgorithm = "SHA1withRSA";
|
||||
X509V3CertificateGenerator certificateGenerator = new X509V3CertificateGenerator();
|
||||
certificateGenerator.reset();
|
||||
certificateGenerator.setPublicKey(subjectPublicKey);
|
||||
certificateGenerator.setSignatureAlgorithm(signatureAlgorithm);
|
||||
certificateGenerator.setNotBefore(notBefore.toDate());
|
||||
certificateGenerator.setNotAfter(notAfter.toDate());
|
||||
X509Principal issuerDN;
|
||||
if (null != issuerCertificate) {
|
||||
issuerDN = new X509Principal(issuerCertificate.getSubjectX500Principal().toString());
|
||||
} else {
|
||||
issuerDN = new X509Principal(subjectDn);
|
||||
}
|
||||
certificateGenerator.setIssuerDN(issuerDN);
|
||||
certificateGenerator.setSubjectDN(new X509Principal(subjectDn));
|
||||
certificateGenerator.setSerialNumber(new BigInteger(128, new SecureRandom()));
|
||||
|
||||
certificateGenerator.addExtension(X509Extensions.SubjectKeyIdentifier, false, createSubjectKeyId(subjectPublicKey));
|
||||
PublicKey issuerPublicKey;
|
||||
issuerPublicKey = subjectPublicKey;
|
||||
certificateGenerator.addExtension(X509Extensions.AuthorityKeyIdentifier, false, createAuthorityKeyId(issuerPublicKey));
|
||||
|
||||
if (caFlag) {
|
||||
if (-1 == pathLength) {
|
||||
certificateGenerator.addExtension(X509Extensions.BasicConstraints, false, new BasicConstraints(true));
|
||||
} else {
|
||||
certificateGenerator.addExtension(X509Extensions.BasicConstraints, false, new BasicConstraints(pathLength));
|
||||
}
|
||||
}
|
||||
|
||||
if (null != crlUri) {
|
||||
GeneralName gn = new GeneralName(GeneralName.uniformResourceIdentifier, new DERIA5String(crlUri));
|
||||
GeneralNames gns = new GeneralNames(new DERSequence(gn));
|
||||
DistributionPointName dpn = new DistributionPointName(0, gns);
|
||||
DistributionPoint distp = new DistributionPoint(dpn, null, null);
|
||||
certificateGenerator.addExtension(X509Extensions.CRLDistributionPoints, false, new DERSequence(distp));
|
||||
}
|
||||
|
||||
if (null != ocspUri) {
|
||||
GeneralName ocspName = new GeneralName(GeneralName.uniformResourceIdentifier, ocspUri);
|
||||
AuthorityInformationAccess authorityInformationAccess = new AuthorityInformationAccess(X509ObjectIdentifiers.ocspAccessMethod, ocspName);
|
||||
certificateGenerator.addExtension(X509Extensions.AuthorityInfoAccess.getId(), false, authorityInformationAccess);
|
||||
}
|
||||
|
||||
if (null != keyUsage) {
|
||||
certificateGenerator.addExtension(X509Extensions.KeyUsage, true, keyUsage);
|
||||
}
|
||||
|
||||
X509Certificate certificate;
|
||||
certificate = certificateGenerator.generate(issuerPrivateKey);
|
||||
|
||||
/*
|
||||
* Next certificate factory trick is needed to make sure that the
|
||||
* certificate delivered to the caller is provided by the default
|
||||
* security provider instead of BouncyCastle. If we don't do this trick
|
||||
* we might run into trouble when trying to use the CertPath validator.
|
||||
*/
|
||||
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
|
||||
certificate = (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(certificate.getEncoded()));
|
||||
return certificate;
|
||||
}
|
||||
|
||||
static Document loadDocument(InputStream documentInputStream) throws ParserConfigurationException, SAXException, IOException {
|
||||
InputSource inputSource = new InputSource(documentInputStream);
|
||||
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
|
||||
documentBuilderFactory.setNamespaceAware(true);
|
||||
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
|
||||
Document document = documentBuilder.parse(inputSource);
|
||||
return document;
|
||||
}
|
||||
|
||||
static String toString(Node dom) throws TransformerException {
|
||||
Source source = new DOMSource(dom);
|
||||
StringWriter stringWriter = new StringWriter();
|
||||
Result result = new StreamResult(stringWriter);
|
||||
TransformerFactory transformerFactory = TransformerFactory.newInstance();
|
||||
Transformer transformer = transformerFactory.newTransformer();
|
||||
/*
|
||||
* We have to omit the ?xml declaration if we want to embed the
|
||||
* document.
|
||||
*/
|
||||
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
|
||||
transformer.transform(source, result);
|
||||
return stringWriter.getBuffer().toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
|
||||
/* ====================================================================
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You 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.
|
||||
==================================================================== */
|
||||
|
||||
/*
|
||||
* Based on the eID Applet Project code.
|
||||
* Original Copyright (C) 2008-2009 FedICT.
|
||||
*/
|
||||
|
||||
package org.apache.poi.ooxml.signature.service.signer;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.poi.ooxml.signature.service.signer.TemporaryDataStorage;
|
||||
|
||||
|
||||
|
||||
class TemporaryTestDataStorage implements TemporaryDataStorage {
|
||||
|
||||
private ByteArrayOutputStream outputStream;
|
||||
|
||||
private Map<String, Serializable> attributes;
|
||||
|
||||
public TemporaryTestDataStorage() {
|
||||
this.outputStream = new ByteArrayOutputStream();
|
||||
this.attributes = new HashMap<String, Serializable>();
|
||||
}
|
||||
|
||||
public InputStream getTempInputStream() {
|
||||
byte[] data = this.outputStream.toByteArray();
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
|
||||
return inputStream;
|
||||
}
|
||||
|
||||
public OutputStream getTempOutputStream() {
|
||||
return this.outputStream;
|
||||
}
|
||||
|
||||
public Serializable getAttribute(String attributeName) {
|
||||
return this.attributes.get(attributeName);
|
||||
}
|
||||
|
||||
public void setAttribute(String attributeName, Serializable attributeValue) {
|
||||
this.attributes.put(attributeName, attributeValue);
|
||||
}
|
||||
}
|
@ -0,0 +1,214 @@
|
||||
|
||||
/* ====================================================================
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You 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.
|
||||
==================================================================== */
|
||||
|
||||
/*
|
||||
* Based on the eID Applet Project code.
|
||||
* Original Copyright (C) 2008-2009 FedICT.
|
||||
*/
|
||||
|
||||
package org.apache.poi.ooxml.signature.service.signer;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URL;
|
||||
import java.security.KeyPair;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang.ArrayUtils;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.apache.poi.ooxml.signature.service.signer.TemporaryDataStorage;
|
||||
import org.apache.poi.ooxml.signature.service.signer.ooxml.AbstractOOXMLSignatureService;
|
||||
import org.apache.poi.ooxml.signature.service.signer.ooxml.OOXMLProvider;
|
||||
import org.apache.poi.ooxml.signature.service.signer.ooxml.OOXMLSignatureVerifier;
|
||||
import org.apache.poi.ooxml.signature.service.spi.DigestInfo;
|
||||
import org.bouncycastle.asn1.x509.KeyUsage;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
|
||||
|
||||
public class TestAbstractOOXMLSignatureService extends TestCase {
|
||||
|
||||
private static final Log LOG = LogFactory.getLog(TestAbstractOOXMLSignatureService.class);
|
||||
|
||||
static {
|
||||
OOXMLProvider.install();
|
||||
}
|
||||
|
||||
private static class OOXMLTestSignatureService extends AbstractOOXMLSignatureService {
|
||||
|
||||
private final URL ooxmlUrl;
|
||||
|
||||
private final TemporaryTestDataStorage temporaryDataStorage;
|
||||
|
||||
private final ByteArrayOutputStream signedOOXMLOutputStream;
|
||||
|
||||
public OOXMLTestSignatureService(URL ooxmlUrl) {
|
||||
this.temporaryDataStorage = new TemporaryTestDataStorage();
|
||||
this.signedOOXMLOutputStream = new ByteArrayOutputStream();
|
||||
this.ooxmlUrl = ooxmlUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected URL getOfficeOpenXMLDocumentURL() {
|
||||
return this.ooxmlUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected OutputStream getSignedOfficeOpenXMLDocumentOutputStream() {
|
||||
return this.signedOOXMLOutputStream;
|
||||
}
|
||||
|
||||
public byte[] getSignedOfficeOpenXMLDocumentData() {
|
||||
return this.signedOOXMLOutputStream.toByteArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TemporaryDataStorage getTemporaryDataStorage() {
|
||||
return this.temporaryDataStorage;
|
||||
}
|
||||
}
|
||||
|
||||
public void testPreSign() throws Exception {
|
||||
// setup
|
||||
URL ooxmlUrl = TestAbstractOOXMLSignatureService.class.getResource("/hello-world-unsigned.docx");
|
||||
assertNotNull(ooxmlUrl);
|
||||
|
||||
OOXMLTestSignatureService signatureService = new OOXMLTestSignatureService(ooxmlUrl);
|
||||
|
||||
// operate
|
||||
DigestInfo digestInfo = signatureService.preSign(null, null);
|
||||
|
||||
// verify
|
||||
assertNotNull(digestInfo);
|
||||
LOG.debug("digest algo: " + digestInfo.digestAlgo);
|
||||
LOG.debug("digest description: " + digestInfo.description);
|
||||
assertEquals("Office OpenXML Document", digestInfo.description);
|
||||
assertNotNull(digestInfo.digestAlgo);
|
||||
assertNotNull(digestInfo.digestValue);
|
||||
|
||||
TemporaryDataStorage temporaryDataStorage = signatureService.getTemporaryDataStorage();
|
||||
String preSignResult = IOUtils.toString(temporaryDataStorage.getTempInputStream());
|
||||
LOG.debug("pre-sign result: " + preSignResult);
|
||||
File tmpFile = File.createTempFile("ooxml-pre-sign-", ".xml");
|
||||
FileUtils.writeStringToFile(tmpFile, preSignResult);
|
||||
LOG.debug("tmp pre-sign file: " + tmpFile.getAbsolutePath());
|
||||
}
|
||||
|
||||
public void testPostSign() throws Exception {
|
||||
sign("/hello-world-unsigned.docx");
|
||||
}
|
||||
|
||||
public void testSignOffice2010() throws Exception {
|
||||
sign("/hello-world-office-2010-technical-preview-unsigned.docx");
|
||||
}
|
||||
|
||||
public void testSignTwice() throws Exception {
|
||||
sign("/hello-world-signed.docx", 2);
|
||||
}
|
||||
|
||||
public void testSignTwiceHere() throws Exception {
|
||||
File tmpFile = sign("/hello-world-unsigned.docx", 1);
|
||||
sign(tmpFile.toURI().toURL(), "CN=Test2", 2);
|
||||
}
|
||||
|
||||
public void testSignPowerpoint() throws Exception {
|
||||
sign("/hello-world-unsigned.pptx");
|
||||
}
|
||||
|
||||
public void testSignSpreadsheet() throws Exception {
|
||||
sign("/hello-world-unsigned.xlsx");
|
||||
}
|
||||
|
||||
private void sign(String documentResourceName) throws Exception {
|
||||
sign(documentResourceName, 1);
|
||||
}
|
||||
|
||||
private File sign(String documentResourceName, int signerCount) throws Exception {
|
||||
URL ooxmlUrl = TestAbstractOOXMLSignatureService.class.getResource(documentResourceName);
|
||||
return sign(ooxmlUrl, signerCount);
|
||||
}
|
||||
|
||||
private File sign(URL ooxmlUrl, int signerCount) throws Exception {
|
||||
return sign(ooxmlUrl, "CN=Test", signerCount);
|
||||
}
|
||||
|
||||
private File sign(URL ooxmlUrl, String signerDn, int signerCount) throws Exception {
|
||||
// setup
|
||||
assertNotNull(ooxmlUrl);
|
||||
|
||||
OOXMLTestSignatureService signatureService = new OOXMLTestSignatureService(ooxmlUrl);
|
||||
|
||||
// operate
|
||||
DigestInfo digestInfo = signatureService.preSign(null, null);
|
||||
|
||||
// verify
|
||||
assertNotNull(digestInfo);
|
||||
LOG.debug("digest algo: " + digestInfo.digestAlgo);
|
||||
LOG.debug("digest description: " + digestInfo.description);
|
||||
assertEquals("Office OpenXML Document", digestInfo.description);
|
||||
assertNotNull(digestInfo.digestAlgo);
|
||||
assertNotNull(digestInfo.digestValue);
|
||||
|
||||
TemporaryDataStorage temporaryDataStorage = signatureService.getTemporaryDataStorage();
|
||||
String preSignResult = IOUtils.toString(temporaryDataStorage.getTempInputStream());
|
||||
LOG.debug("pre-sign result: " + preSignResult);
|
||||
File tmpFile = File.createTempFile("ooxml-pre-sign-", ".xml");
|
||||
FileUtils.writeStringToFile(tmpFile, preSignResult);
|
||||
LOG.debug("tmp pre-sign file: " + tmpFile.getAbsolutePath());
|
||||
|
||||
// setup: key material, signature value
|
||||
KeyPair keyPair = PkiTestUtils.generateKeyPair();
|
||||
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, keyPair.getPrivate());
|
||||
byte[] digestInfoValue = ArrayUtils.addAll(PkiTestUtils.SHA1_DIGEST_INFO_PREFIX, digestInfo.digestValue);
|
||||
byte[] signatureValue = cipher.doFinal(digestInfoValue);
|
||||
|
||||
DateTime notBefore = new DateTime();
|
||||
DateTime notAfter = notBefore.plusYears(1);
|
||||
X509Certificate certificate = PkiTestUtils.generateCertificate(keyPair.getPublic(), signerDn, notBefore, notAfter, null, keyPair.getPrivate(), true, 0,
|
||||
null, null, new KeyUsage(KeyUsage.nonRepudiation));
|
||||
|
||||
// operate: postSign
|
||||
signatureService.postSign(signatureValue, Collections.singletonList(certificate));
|
||||
|
||||
// verify: signature
|
||||
byte[] signedOOXMLData = signatureService.getSignedOfficeOpenXMLDocumentData();
|
||||
assertNotNull(signedOOXMLData);
|
||||
LOG.debug("signed OOXML size: " + signedOOXMLData.length);
|
||||
String extension = FilenameUtils.getExtension(ooxmlUrl.getFile());
|
||||
tmpFile = File.createTempFile("ooxml-signed-", "." + extension);
|
||||
FileUtils.writeByteArrayToFile(tmpFile, signedOOXMLData);
|
||||
LOG.debug("signed OOXML file: " + tmpFile.getAbsolutePath());
|
||||
List<X509Certificate> signers = OOXMLSignatureVerifier.getSigners(tmpFile.toURI().toURL());
|
||||
assertEquals(signerCount, signers.size());
|
||||
// assertEquals(certificate, signers.get(0));
|
||||
LOG.debug("signed OOXML file: " + tmpFile.getAbsolutePath());
|
||||
return tmpFile;
|
||||
}
|
||||
}
|
@ -0,0 +1,560 @@
|
||||
|
||||
/* ====================================================================
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You 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.
|
||||
==================================================================== */
|
||||
|
||||
/*
|
||||
* Based on the eID Applet Project code.
|
||||
* Original Copyright (C) 2008-2009 FedICT.
|
||||
*/
|
||||
|
||||
package org.apache.poi.ooxml.signature.service.signer;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.security.KeyPair;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.xml.crypto.Data;
|
||||
import javax.xml.crypto.KeySelector;
|
||||
import javax.xml.crypto.OctetStreamData;
|
||||
import javax.xml.crypto.URIDereferencer;
|
||||
import javax.xml.crypto.URIReference;
|
||||
import javax.xml.crypto.URIReferenceException;
|
||||
import javax.xml.crypto.XMLCryptoContext;
|
||||
import javax.xml.crypto.dom.DOMCryptoContext;
|
||||
import javax.xml.crypto.dsig.CanonicalizationMethod;
|
||||
import javax.xml.crypto.dsig.DigestMethod;
|
||||
import javax.xml.crypto.dsig.Reference;
|
||||
import javax.xml.crypto.dsig.SignatureMethod;
|
||||
import javax.xml.crypto.dsig.SignedInfo;
|
||||
import javax.xml.crypto.dsig.XMLSignContext;
|
||||
import javax.xml.crypto.dsig.XMLSignature;
|
||||
import javax.xml.crypto.dsig.XMLSignatureFactory;
|
||||
import javax.xml.crypto.dsig.dom.DOMSignContext;
|
||||
import javax.xml.crypto.dsig.dom.DOMValidateContext;
|
||||
import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec;
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.apache.commons.lang.ArrayUtils;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.apache.poi.ooxml.signature.service.spi.DigestInfo;
|
||||
import org.apache.xml.security.utils.Constants;
|
||||
import org.apache.xpath.XPathAPI;
|
||||
import org.bouncycastle.asn1.x509.KeyUsage;
|
||||
import org.jcp.xml.dsig.internal.dom.DOMReference;
|
||||
import org.jcp.xml.dsig.internal.dom.DOMXMLSignature;
|
||||
import org.joda.time.DateTime;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
|
||||
|
||||
public class TestAbstractXmlSignatureService extends TestCase {
|
||||
|
||||
private static final Log LOG = LogFactory.getLog(TestAbstractXmlSignatureService.class);
|
||||
|
||||
private static class XmlSignatureTestService extends AbstractXmlSignatureService {
|
||||
|
||||
private Document envelopingDocument;
|
||||
|
||||
private List<String> referenceUris;
|
||||
|
||||
private TemporaryTestDataStorage temporaryDataStorage;
|
||||
|
||||
private String signatureDescription;
|
||||
|
||||
private ByteArrayOutputStream signedDocumentOutputStream;
|
||||
|
||||
private URIDereferencer uriDereferencer;
|
||||
|
||||
public XmlSignatureTestService() {
|
||||
super();
|
||||
this.referenceUris = new LinkedList<String>();
|
||||
this.temporaryDataStorage = new TemporaryTestDataStorage();
|
||||
this.signedDocumentOutputStream = new ByteArrayOutputStream();
|
||||
}
|
||||
|
||||
public byte[] getSignedDocumentData() {
|
||||
return this.signedDocumentOutputStream.toByteArray();
|
||||
}
|
||||
|
||||
public void setEnvelopingDocument(Document envelopingDocument) {
|
||||
this.envelopingDocument = envelopingDocument;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Document getEnvelopingDocument() {
|
||||
return this.envelopingDocument;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getSignatureDescription() {
|
||||
return this.signatureDescription;
|
||||
}
|
||||
|
||||
public void setSignatureDescription(String signatureDescription) {
|
||||
this.signatureDescription = signatureDescription;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> getReferenceUris() {
|
||||
return this.referenceUris;
|
||||
}
|
||||
|
||||
public void addReferenceUri(String referenceUri) {
|
||||
this.referenceUris.add(referenceUri);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected OutputStream getSignedDocumentOutputStream() {
|
||||
return this.signedDocumentOutputStream;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TemporaryDataStorage getTemporaryDataStorage() {
|
||||
return this.temporaryDataStorage;
|
||||
}
|
||||
|
||||
public String getFilesDigestAlgorithm() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected URIDereferencer getURIDereferencer() {
|
||||
return this.uriDereferencer;
|
||||
}
|
||||
|
||||
public void setUriDereferencer(URIDereferencer uriDereferencer) {
|
||||
this.uriDereferencer = uriDereferencer;
|
||||
}
|
||||
}
|
||||
|
||||
public void testSignEnvelopingDocument() throws Exception {
|
||||
// setup
|
||||
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
|
||||
documentBuilderFactory.setNamespaceAware(true);
|
||||
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
|
||||
Document document = documentBuilder.newDocument();
|
||||
Element rootElement = document.createElementNS("urn:test", "tns:root");
|
||||
rootElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:tns", "urn:test");
|
||||
document.appendChild(rootElement);
|
||||
Element dataElement = document.createElementNS("urn:test", "tns:data");
|
||||
dataElement.setAttributeNS(null, "Id", "id-1234");
|
||||
dataElement.setTextContent("data to be signed");
|
||||
rootElement.appendChild(dataElement);
|
||||
|
||||
XmlSignatureTestService testedInstance = new XmlSignatureTestService();
|
||||
testedInstance.setEnvelopingDocument(document);
|
||||
testedInstance.addReferenceUri("#id-1234");
|
||||
testedInstance.setSignatureDescription("test-signature-description");
|
||||
|
||||
// operate
|
||||
DigestInfo digestInfo = testedInstance.preSign(null, null);
|
||||
|
||||
// verify
|
||||
assertNotNull(digestInfo);
|
||||
LOG.debug("digest info description: " + digestInfo.description);
|
||||
assertEquals("test-signature-description", digestInfo.description);
|
||||
assertNotNull(digestInfo.digestValue);
|
||||
LOG.debug("digest algo: " + digestInfo.digestAlgo);
|
||||
assertEquals("SHA-1", digestInfo.digestAlgo);
|
||||
|
||||
TemporaryTestDataStorage temporaryDataStorage = (TemporaryTestDataStorage) testedInstance.getTemporaryDataStorage();
|
||||
assertNotNull(temporaryDataStorage);
|
||||
InputStream tempInputStream = temporaryDataStorage.getTempInputStream();
|
||||
assertNotNull(tempInputStream);
|
||||
Document tmpDocument = PkiTestUtils.loadDocument(tempInputStream);
|
||||
|
||||
LOG.debug("tmp document: " + PkiTestUtils.toString(tmpDocument));
|
||||
Element nsElement = tmpDocument.createElement("ns");
|
||||
nsElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:ds", Constants.SignatureSpecNS);
|
||||
Node digestValueNode = XPathAPI.selectSingleNode(tmpDocument, "//ds:DigestValue", nsElement);
|
||||
assertNotNull(digestValueNode);
|
||||
String digestValueTextContent = digestValueNode.getTextContent();
|
||||
LOG.debug("digest value text content: " + digestValueTextContent);
|
||||
assertFalse(digestValueTextContent.isEmpty());
|
||||
|
||||
/*
|
||||
* Sign the received XML signature digest value.
|
||||
*/
|
||||
KeyPair keyPair = PkiTestUtils.generateKeyPair();
|
||||
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, keyPair.getPrivate());
|
||||
byte[] digestInfoValue = ArrayUtils.addAll(PkiTestUtils.SHA1_DIGEST_INFO_PREFIX, digestInfo.digestValue);
|
||||
byte[] signatureValue = cipher.doFinal(digestInfoValue);
|
||||
|
||||
DateTime notBefore = new DateTime();
|
||||
DateTime notAfter = notBefore.plusYears(1);
|
||||
X509Certificate certificate = PkiTestUtils.generateCertificate(keyPair.getPublic(), "CN=Test", notBefore, notAfter, null, keyPair.getPrivate(), true,
|
||||
0, null, null, new KeyUsage(KeyUsage.nonRepudiation));
|
||||
|
||||
/*
|
||||
* Operate: postSign
|
||||
*/
|
||||
testedInstance.postSign(signatureValue, Collections.singletonList(certificate));
|
||||
|
||||
byte[] signedDocumentData = testedInstance.getSignedDocumentData();
|
||||
assertNotNull(signedDocumentData);
|
||||
Document signedDocument = PkiTestUtils.loadDocument(new ByteArrayInputStream(signedDocumentData));
|
||||
LOG.debug("signed document: " + PkiTestUtils.toString(signedDocument));
|
||||
|
||||
NodeList signatureNodeList = signedDocument.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature");
|
||||
assertEquals(1, signatureNodeList.getLength());
|
||||
Node signatureNode = signatureNodeList.item(0);
|
||||
|
||||
DOMValidateContext domValidateContext = new DOMValidateContext(KeySelector.singletonKeySelector(keyPair.getPublic()), signatureNode);
|
||||
XMLSignatureFactory xmlSignatureFactory = XMLSignatureFactory.getInstance();
|
||||
XMLSignature xmlSignature = xmlSignatureFactory.unmarshalXMLSignature(domValidateContext);
|
||||
boolean validity = xmlSignature.validate(domValidateContext);
|
||||
assertTrue(validity);
|
||||
}
|
||||
|
||||
public static class UriTestDereferencer implements URIDereferencer {
|
||||
|
||||
private final Map<String, byte[]> resources;
|
||||
|
||||
public UriTestDereferencer() {
|
||||
this.resources = new HashMap<String, byte[]>();
|
||||
}
|
||||
|
||||
public void addResource(String uri, byte[] data) {
|
||||
this.resources.put(uri, data);
|
||||
}
|
||||
|
||||
public Data dereference(URIReference uriReference, XMLCryptoContext xmlCryptoContext) throws URIReferenceException {
|
||||
String uri = uriReference.getURI();
|
||||
byte[] data = this.resources.get(uri);
|
||||
if (null == data) {
|
||||
return null;
|
||||
}
|
||||
return new OctetStreamData(new ByteArrayInputStream(data));
|
||||
}
|
||||
}
|
||||
|
||||
public void testSignExternalUri() throws Exception {
|
||||
// setup
|
||||
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
|
||||
documentBuilderFactory.setNamespaceAware(true);
|
||||
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
|
||||
Document document = documentBuilder.newDocument();
|
||||
|
||||
XmlSignatureTestService testedInstance = new XmlSignatureTestService();
|
||||
testedInstance.setEnvelopingDocument(document);
|
||||
testedInstance.addReferenceUri("external-uri");
|
||||
testedInstance.setSignatureDescription("test-signature-description");
|
||||
UriTestDereferencer uriDereferencer = new UriTestDereferencer();
|
||||
uriDereferencer.addResource("external-uri", "hello world".getBytes());
|
||||
testedInstance.setUriDereferencer(uriDereferencer);
|
||||
|
||||
// operate
|
||||
DigestInfo digestInfo = testedInstance.preSign(null, null);
|
||||
|
||||
// verify
|
||||
assertNotNull(digestInfo);
|
||||
LOG.debug("digest info description: " + digestInfo.description);
|
||||
assertEquals("test-signature-description", digestInfo.description);
|
||||
assertNotNull(digestInfo.digestValue);
|
||||
LOG.debug("digest algo: " + digestInfo.digestAlgo);
|
||||
assertEquals("SHA-1", digestInfo.digestAlgo);
|
||||
|
||||
TemporaryTestDataStorage temporaryDataStorage = (TemporaryTestDataStorage) testedInstance.getTemporaryDataStorage();
|
||||
assertNotNull(temporaryDataStorage);
|
||||
InputStream tempInputStream = temporaryDataStorage.getTempInputStream();
|
||||
assertNotNull(tempInputStream);
|
||||
Document tmpDocument = PkiTestUtils.loadDocument(tempInputStream);
|
||||
|
||||
LOG.debug("tmp document: " + PkiTestUtils.toString(tmpDocument));
|
||||
Element nsElement = tmpDocument.createElement("ns");
|
||||
nsElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:ds", Constants.SignatureSpecNS);
|
||||
Node digestValueNode = XPathAPI.selectSingleNode(tmpDocument, "//ds:DigestValue", nsElement);
|
||||
assertNotNull(digestValueNode);
|
||||
String digestValueTextContent = digestValueNode.getTextContent();
|
||||
LOG.debug("digest value text content: " + digestValueTextContent);
|
||||
assertFalse(digestValueTextContent.isEmpty());
|
||||
|
||||
/*
|
||||
* Sign the received XML signature digest value.
|
||||
*/
|
||||
KeyPair keyPair = PkiTestUtils.generateKeyPair();
|
||||
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, keyPair.getPrivate());
|
||||
byte[] digestInfoValue = ArrayUtils.addAll(PkiTestUtils.SHA1_DIGEST_INFO_PREFIX, digestInfo.digestValue);
|
||||
byte[] signatureValue = cipher.doFinal(digestInfoValue);
|
||||
|
||||
DateTime notBefore = new DateTime();
|
||||
DateTime notAfter = notBefore.plusYears(1);
|
||||
X509Certificate certificate = PkiTestUtils.generateCertificate(keyPair.getPublic(), "CN=Test", notBefore, notAfter, null, keyPair.getPrivate(), true,
|
||||
0, null, null, new KeyUsage(KeyUsage.nonRepudiation));
|
||||
|
||||
/*
|
||||
* Operate: postSign
|
||||
*/
|
||||
testedInstance.postSign(signatureValue, Collections.singletonList(certificate));
|
||||
|
||||
byte[] signedDocumentData = testedInstance.getSignedDocumentData();
|
||||
assertNotNull(signedDocumentData);
|
||||
Document signedDocument = PkiTestUtils.loadDocument(new ByteArrayInputStream(signedDocumentData));
|
||||
LOG.debug("signed document: " + PkiTestUtils.toString(signedDocument));
|
||||
|
||||
NodeList signatureNodeList = signedDocument.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature");
|
||||
assertEquals(1, signatureNodeList.getLength());
|
||||
Node signatureNode = signatureNodeList.item(0);
|
||||
|
||||
DOMValidateContext domValidateContext = new DOMValidateContext(KeySelector.singletonKeySelector(keyPair.getPublic()), signatureNode);
|
||||
domValidateContext.setURIDereferencer(uriDereferencer);
|
||||
XMLSignatureFactory xmlSignatureFactory = XMLSignatureFactory.getInstance();
|
||||
XMLSignature xmlSignature = xmlSignatureFactory.unmarshalXMLSignature(domValidateContext);
|
||||
boolean validity = xmlSignature.validate(domValidateContext);
|
||||
assertTrue(validity);
|
||||
}
|
||||
|
||||
public void testSignEnvelopingDocumentWithExternalDigestInfo() throws Exception {
|
||||
// setup
|
||||
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
|
||||
documentBuilderFactory.setNamespaceAware(true);
|
||||
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
|
||||
Document document = documentBuilder.newDocument();
|
||||
Element rootElement = document.createElementNS("urn:test", "tns:root");
|
||||
rootElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:tns", "urn:test");
|
||||
document.appendChild(rootElement);
|
||||
|
||||
XmlSignatureTestService testedInstance = new XmlSignatureTestService();
|
||||
testedInstance.setEnvelopingDocument(document);
|
||||
testedInstance.setSignatureDescription("test-signature-description");
|
||||
|
||||
byte[] refData = "hello world".getBytes();
|
||||
MessageDigest messageDigest = MessageDigest.getInstance("SHA-1");
|
||||
messageDigest.update(refData);
|
||||
byte[] digestValue = messageDigest.digest();
|
||||
DigestInfo refDigestInfo = new DigestInfo(digestValue, "SHA-1", "urn:test:ref");
|
||||
|
||||
// operate
|
||||
DigestInfo digestInfo = testedInstance.preSign(Collections.singletonList(refDigestInfo), null);
|
||||
|
||||
// verify
|
||||
assertNotNull(digestInfo);
|
||||
LOG.debug("digest info description: " + digestInfo.description);
|
||||
assertEquals("test-signature-description", digestInfo.description);
|
||||
assertNotNull(digestInfo.digestValue);
|
||||
LOG.debug("digest algo: " + digestInfo.digestAlgo);
|
||||
assertEquals("SHA-1", digestInfo.digestAlgo);
|
||||
|
||||
TemporaryTestDataStorage temporaryDataStorage = (TemporaryTestDataStorage) testedInstance.getTemporaryDataStorage();
|
||||
assertNotNull(temporaryDataStorage);
|
||||
InputStream tempInputStream = temporaryDataStorage.getTempInputStream();
|
||||
assertNotNull(tempInputStream);
|
||||
Document tmpDocument = PkiTestUtils.loadDocument(tempInputStream);
|
||||
|
||||
LOG.debug("tmp document: " + PkiTestUtils.toString(tmpDocument));
|
||||
Element nsElement = tmpDocument.createElement("ns");
|
||||
nsElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:ds", Constants.SignatureSpecNS);
|
||||
Node digestValueNode = XPathAPI.selectSingleNode(tmpDocument, "//ds:DigestValue", nsElement);
|
||||
assertNotNull(digestValueNode);
|
||||
String digestValueTextContent = digestValueNode.getTextContent();
|
||||
LOG.debug("digest value text content: " + digestValueTextContent);
|
||||
assertFalse(digestValueTextContent.isEmpty());
|
||||
|
||||
/*
|
||||
* Sign the received XML signature digest value.
|
||||
*/
|
||||
KeyPair keyPair = PkiTestUtils.generateKeyPair();
|
||||
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, keyPair.getPrivate());
|
||||
byte[] digestInfoValue = ArrayUtils.addAll(PkiTestUtils.SHA1_DIGEST_INFO_PREFIX, digestInfo.digestValue);
|
||||
byte[] signatureValue = cipher.doFinal(digestInfoValue);
|
||||
|
||||
DateTime notBefore = new DateTime();
|
||||
DateTime notAfter = notBefore.plusYears(1);
|
||||
X509Certificate certificate = PkiTestUtils.generateCertificate(keyPair.getPublic(), "CN=Test", notBefore, notAfter, null, keyPair.getPrivate(), true,
|
||||
0, null, null, new KeyUsage(KeyUsage.nonRepudiation));
|
||||
|
||||
/*
|
||||
* Operate: postSign
|
||||
*/
|
||||
testedInstance.postSign(signatureValue, Collections.singletonList(certificate));
|
||||
|
||||
byte[] signedDocumentData = testedInstance.getSignedDocumentData();
|
||||
assertNotNull(signedDocumentData);
|
||||
Document signedDocument = PkiTestUtils.loadDocument(new ByteArrayInputStream(signedDocumentData));
|
||||
LOG.debug("signed document: " + PkiTestUtils.toString(signedDocument));
|
||||
|
||||
NodeList signatureNodeList = signedDocument.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature");
|
||||
assertEquals(1, signatureNodeList.getLength());
|
||||
Node signatureNode = signatureNodeList.item(0);
|
||||
|
||||
DOMValidateContext domValidateContext = new DOMValidateContext(KeySelector.singletonKeySelector(keyPair.getPublic()), signatureNode);
|
||||
URIDereferencer dereferencer = new URITest2Dereferencer();
|
||||
domValidateContext.setURIDereferencer(dereferencer);
|
||||
XMLSignatureFactory xmlSignatureFactory = XMLSignatureFactory.getInstance();
|
||||
XMLSignature xmlSignature = xmlSignatureFactory.unmarshalXMLSignature(domValidateContext);
|
||||
boolean validity = xmlSignature.validate(domValidateContext);
|
||||
assertTrue(validity);
|
||||
}
|
||||
|
||||
public void testSignExternalDigestInfo() throws Exception {
|
||||
// setup
|
||||
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
|
||||
documentBuilderFactory.setNamespaceAware(true);
|
||||
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
|
||||
Document document = documentBuilder.newDocument();
|
||||
|
||||
XmlSignatureTestService testedInstance = new XmlSignatureTestService();
|
||||
testedInstance.setEnvelopingDocument(document);
|
||||
testedInstance.setSignatureDescription("test-signature-description");
|
||||
|
||||
byte[] refData = "hello world".getBytes();
|
||||
MessageDigest messageDigest = MessageDigest.getInstance("SHA-1");
|
||||
messageDigest.update(refData);
|
||||
byte[] digestValue = messageDigest.digest();
|
||||
DigestInfo refDigestInfo = new DigestInfo(digestValue, "SHA-1", "urn:test:ref");
|
||||
|
||||
// operate
|
||||
DigestInfo digestInfo = testedInstance.preSign(Collections.singletonList(refDigestInfo), null);
|
||||
|
||||
// verify
|
||||
assertNotNull(digestInfo);
|
||||
LOG.debug("digest info description: " + digestInfo.description);
|
||||
assertEquals("test-signature-description", digestInfo.description);
|
||||
assertNotNull(digestInfo.digestValue);
|
||||
LOG.debug("digest algo: " + digestInfo.digestAlgo);
|
||||
assertEquals("SHA-1", digestInfo.digestAlgo);
|
||||
|
||||
TemporaryTestDataStorage temporaryDataStorage = (TemporaryTestDataStorage) testedInstance.getTemporaryDataStorage();
|
||||
assertNotNull(temporaryDataStorage);
|
||||
InputStream tempInputStream = temporaryDataStorage.getTempInputStream();
|
||||
assertNotNull(tempInputStream);
|
||||
Document tmpDocument = PkiTestUtils.loadDocument(tempInputStream);
|
||||
|
||||
LOG.debug("tmp document: " + PkiTestUtils.toString(tmpDocument));
|
||||
Element nsElement = tmpDocument.createElement("ns");
|
||||
nsElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:ds", Constants.SignatureSpecNS);
|
||||
Node digestValueNode = XPathAPI.selectSingleNode(tmpDocument, "//ds:DigestValue", nsElement);
|
||||
assertNotNull(digestValueNode);
|
||||
String digestValueTextContent = digestValueNode.getTextContent();
|
||||
LOG.debug("digest value text content: " + digestValueTextContent);
|
||||
assertFalse(digestValueTextContent.isEmpty());
|
||||
|
||||
/*
|
||||
* Sign the received XML signature digest value.
|
||||
*/
|
||||
KeyPair keyPair = PkiTestUtils.generateKeyPair();
|
||||
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, keyPair.getPrivate());
|
||||
byte[] digestInfoValue = ArrayUtils.addAll(PkiTestUtils.SHA1_DIGEST_INFO_PREFIX, digestInfo.digestValue);
|
||||
byte[] signatureValue = cipher.doFinal(digestInfoValue);
|
||||
|
||||
DateTime notBefore = new DateTime();
|
||||
DateTime notAfter = notBefore.plusYears(1);
|
||||
X509Certificate certificate = PkiTestUtils.generateCertificate(keyPair.getPublic(), "CN=Test", notBefore, notAfter, null, keyPair.getPrivate(), true,
|
||||
0, null, null, new KeyUsage(KeyUsage.nonRepudiation));
|
||||
|
||||
/*
|
||||
* Operate: postSign
|
||||
*/
|
||||
testedInstance.postSign(signatureValue, Collections.singletonList(certificate));
|
||||
|
||||
byte[] signedDocumentData = testedInstance.getSignedDocumentData();
|
||||
assertNotNull(signedDocumentData);
|
||||
Document signedDocument = PkiTestUtils.loadDocument(new ByteArrayInputStream(signedDocumentData));
|
||||
LOG.debug("signed document: " + PkiTestUtils.toString(signedDocument));
|
||||
|
||||
NodeList signatureNodeList = signedDocument.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature");
|
||||
assertEquals(1, signatureNodeList.getLength());
|
||||
Node signatureNode = signatureNodeList.item(0);
|
||||
|
||||
DOMValidateContext domValidateContext = new DOMValidateContext(KeySelector.singletonKeySelector(keyPair.getPublic()), signatureNode);
|
||||
URIDereferencer dereferencer = new URITest2Dereferencer();
|
||||
domValidateContext.setURIDereferencer(dereferencer);
|
||||
XMLSignatureFactory xmlSignatureFactory = XMLSignatureFactory.getInstance();
|
||||
XMLSignature xmlSignature = xmlSignatureFactory.unmarshalXMLSignature(domValidateContext);
|
||||
boolean validity = xmlSignature.validate(domValidateContext);
|
||||
assertTrue(validity);
|
||||
}
|
||||
|
||||
private static class URITest2Dereferencer implements URIDereferencer {
|
||||
|
||||
private static final Log LOG = LogFactory.getLog(URITest2Dereferencer.class);
|
||||
|
||||
public Data dereference(URIReference uriReference, XMLCryptoContext context) throws URIReferenceException {
|
||||
LOG.debug("dereference: " + uriReference.getURI());
|
||||
return new OctetStreamData(new ByteArrayInputStream("hello world".getBytes()));
|
||||
}
|
||||
}
|
||||
|
||||
public void testJsr105Signature() throws Exception {
|
||||
KeyPair keyPair = PkiTestUtils.generateKeyPair();
|
||||
|
||||
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
|
||||
documentBuilderFactory.setNamespaceAware(true);
|
||||
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
|
||||
Document document = documentBuilder.newDocument();
|
||||
Element rootElement = document.createElementNS("urn:test", "tns:root");
|
||||
rootElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:tns", "urn:test");
|
||||
document.appendChild(rootElement);
|
||||
Element dataElement = document.createElementNS("urn:test", "tns:data");
|
||||
dataElement.setAttributeNS(null, "Id", "id-1234");
|
||||
dataElement.setTextContent("data to be signed");
|
||||
rootElement.appendChild(dataElement);
|
||||
|
||||
XMLSignatureFactory signatureFactory = XMLSignatureFactory.getInstance("DOM", new org.jcp.xml.dsig.internal.dom.XMLDSigRI());
|
||||
|
||||
XMLSignContext signContext = new DOMSignContext(keyPair.getPrivate(), document.getDocumentElement());
|
||||
signContext.putNamespacePrefix(javax.xml.crypto.dsig.XMLSignature.XMLNS, "ds");
|
||||
|
||||
DigestMethod digestMethod = signatureFactory.newDigestMethod(DigestMethod.SHA1, null);
|
||||
Reference reference = signatureFactory.newReference("#id-1234", digestMethod);
|
||||
DOMReference domReference = (DOMReference) reference;
|
||||
assertNull(domReference.getCalculatedDigestValue());
|
||||
assertNull(domReference.getDigestValue());
|
||||
|
||||
SignatureMethod signatureMethod = signatureFactory.newSignatureMethod(SignatureMethod.RSA_SHA1, null);
|
||||
CanonicalizationMethod canonicalizationMethod = signatureFactory.newCanonicalizationMethod(CanonicalizationMethod.EXCLUSIVE_WITH_COMMENTS,
|
||||
(C14NMethodParameterSpec) null);
|
||||
SignedInfo signedInfo = signatureFactory.newSignedInfo(canonicalizationMethod, signatureMethod, Collections.singletonList(reference));
|
||||
|
||||
javax.xml.crypto.dsig.XMLSignature xmlSignature = signatureFactory.newXMLSignature(signedInfo, null);
|
||||
|
||||
DOMXMLSignature domXmlSignature = (DOMXMLSignature) xmlSignature;
|
||||
domXmlSignature.marshal(document.getDocumentElement(), "ds", (DOMCryptoContext) signContext);
|
||||
domReference.digest(signContext);
|
||||
// xmlSignature.sign(signContext);
|
||||
// LOG.debug("signed document: " + toString(document));
|
||||
|
||||
Element nsElement = document.createElement("ns");
|
||||
nsElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:ds", Constants.SignatureSpecNS);
|
||||
Node digestValueNode = XPathAPI.selectSingleNode(document, "//ds:DigestValue", nsElement);
|
||||
assertNotNull(digestValueNode);
|
||||
String digestValueTextContent = digestValueNode.getTextContent();
|
||||
LOG.debug("digest value text content: " + digestValueTextContent);
|
||||
assertFalse(digestValueTextContent.isEmpty());
|
||||
}
|
||||
}
|
@ -0,0 +1,238 @@
|
||||
|
||||
/* ====================================================================
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You 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.
|
||||
==================================================================== */
|
||||
|
||||
/*
|
||||
* Based on the eID Applet Project code.
|
||||
* Original Copyright (C) 2008-2009 FedICT.
|
||||
*/
|
||||
|
||||
package org.apache.poi.ooxml.signature.service.signer;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.apache.poi.POIXMLDocument;
|
||||
import org.apache.poi.ooxml.signature.service.signer.ooxml.OOXMLProvider;
|
||||
import org.apache.poi.ooxml.signature.service.signer.ooxml.OOXMLSignatureVerifier;
|
||||
import org.apache.poi.openxml4j.opc.OPCPackage;
|
||||
import org.apache.poi.openxml4j.opc.PackagePart;
|
||||
import org.apache.poi.openxml4j.opc.signature.PackageDigitalSignatureManager;
|
||||
|
||||
|
||||
|
||||
public class TestOOXMLSignatureVerifier extends TestCase {
|
||||
|
||||
private static final Log LOG = LogFactory.getLog(TestOOXMLSignatureVerifier.class);
|
||||
|
||||
static {
|
||||
OOXMLProvider.install();
|
||||
}
|
||||
|
||||
public void testIsOOXMLDocument() throws Exception {
|
||||
// setup
|
||||
URL url = TestOOXMLSignatureVerifier.class.getResource("/hello-world-unsigned.docx");
|
||||
|
||||
// operate
|
||||
boolean result = OOXMLSignatureVerifier.isOOXML(url);
|
||||
|
||||
// verify
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
public void testPOI() throws Exception {
|
||||
// setup
|
||||
InputStream inputStream = TestOOXMLSignatureVerifier.class.getResourceAsStream("/hello-world-unsigned.docx");
|
||||
|
||||
// operate
|
||||
boolean result = POIXMLDocument.hasOOXMLHeader(inputStream);
|
||||
|
||||
// verify
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
public void testOPC() throws Exception {
|
||||
// setup
|
||||
InputStream inputStream = TestOOXMLSignatureVerifier.class.getResourceAsStream("/hello-world-signed.docx");
|
||||
|
||||
// operate
|
||||
OPCPackage opcPackage = OPCPackage.open(inputStream);
|
||||
|
||||
ArrayList<PackagePart> parts = opcPackage.getParts();
|
||||
for (PackagePart part : parts) {
|
||||
LOG.debug("part name: " + part.getPartName().getName());
|
||||
LOG.debug("part content type: " + part.getContentType());
|
||||
}
|
||||
|
||||
ArrayList<PackagePart> signatureParts = opcPackage.getPartsByContentType("application/vnd.openxmlformats-package.digital-signature-xmlsignature+xml");
|
||||
assertFalse(signatureParts.isEmpty());
|
||||
|
||||
PackagePart signaturePart = signatureParts.get(0);
|
||||
LOG.debug("signature part class type: " + signaturePart.getClass().getName());
|
||||
|
||||
PackageDigitalSignatureManager packageDigitalSignatureManager = new PackageDigitalSignatureManager();
|
||||
// yeah... POI implementation still missing
|
||||
}
|
||||
|
||||
public void testGetSignerUnsigned() throws Exception {
|
||||
// setup
|
||||
URL url = TestOOXMLSignatureVerifier.class.getResource("/hello-world-unsigned.docx");
|
||||
|
||||
// operate
|
||||
List<X509Certificate> result = OOXMLSignatureVerifier.getSigners(url);
|
||||
|
||||
// verify
|
||||
assertNotNull(result);
|
||||
assertTrue(result.isEmpty());
|
||||
}
|
||||
|
||||
public void testGetSignerOffice2010Unsigned() throws Exception {
|
||||
// setup
|
||||
URL url = TestOOXMLSignatureVerifier.class.getResource("/hello-world-office-2010-technical-preview-unsigned.docx");
|
||||
|
||||
// operate
|
||||
List<X509Certificate> result = OOXMLSignatureVerifier.getSigners(url);
|
||||
|
||||
// verify
|
||||
assertNotNull(result);
|
||||
assertTrue(result.isEmpty());
|
||||
}
|
||||
|
||||
public void testGetSignerUnsignedPowerpoint() throws Exception {
|
||||
// setup
|
||||
URL url = TestOOXMLSignatureVerifier.class.getResource("/hello-world-unsigned.pptx");
|
||||
|
||||
// operate
|
||||
List<X509Certificate> result = OOXMLSignatureVerifier.getSigners(url);
|
||||
|
||||
// verify
|
||||
assertNotNull(result);
|
||||
assertTrue(result.isEmpty());
|
||||
}
|
||||
|
||||
public void testGetSignerUnsignedExcel() throws Exception {
|
||||
// setup
|
||||
URL url = TestOOXMLSignatureVerifier.class.getResource("/hello-world-unsigned.xlsx");
|
||||
|
||||
// operate
|
||||
List<X509Certificate> result = OOXMLSignatureVerifier.getSigners(url);
|
||||
|
||||
// verify
|
||||
assertNotNull(result);
|
||||
assertTrue(result.isEmpty());
|
||||
}
|
||||
|
||||
public void testGetSigner() throws Exception {
|
||||
// setup
|
||||
URL url = TestOOXMLSignatureVerifier.class.getResource("/hello-world-signed.docx");
|
||||
|
||||
// operate
|
||||
List<X509Certificate> result = OOXMLSignatureVerifier.getSigners(url);
|
||||
|
||||
// verify
|
||||
assertNotNull(result);
|
||||
assertEquals(1, result.size());
|
||||
X509Certificate signer = result.get(0);
|
||||
LOG.debug("signer: " + signer.getSubjectX500Principal());
|
||||
}
|
||||
|
||||
public void testOffice2010TechnicalPreview() throws Exception {
|
||||
// setup
|
||||
URL url = TestOOXMLSignatureVerifier.class.getResource("/hello-world-office-2010-technical-preview.docx");
|
||||
|
||||
// operate
|
||||
List<X509Certificate> result = OOXMLSignatureVerifier.getSigners(url);
|
||||
|
||||
// verify
|
||||
assertNotNull(result);
|
||||
assertEquals(1, result.size());
|
||||
X509Certificate signer = result.get(0);
|
||||
LOG.debug("signer: " + signer.getSubjectX500Principal());
|
||||
}
|
||||
|
||||
public void testGetSignerPowerpoint() throws Exception {
|
||||
// setup
|
||||
URL url = TestOOXMLSignatureVerifier.class.getResource("/hello-world-signed.pptx");
|
||||
|
||||
// operate
|
||||
List<X509Certificate> result = OOXMLSignatureVerifier.getSigners(url);
|
||||
|
||||
// verify
|
||||
assertNotNull(result);
|
||||
assertEquals(1, result.size());
|
||||
X509Certificate signer = result.get(0);
|
||||
LOG.debug("signer: " + signer.getSubjectX500Principal());
|
||||
}
|
||||
|
||||
public void testGetSignerExcel() throws Exception {
|
||||
// setup
|
||||
URL url = TestOOXMLSignatureVerifier.class.getResource("/hello-world-signed.xlsx");
|
||||
|
||||
// operate
|
||||
List<X509Certificate> result = OOXMLSignatureVerifier.getSigners(url);
|
||||
|
||||
// verify
|
||||
assertNotNull(result);
|
||||
assertEquals(1, result.size());
|
||||
X509Certificate signer = result.get(0);
|
||||
LOG.debug("signer: " + signer.getSubjectX500Principal());
|
||||
}
|
||||
|
||||
public void testGetSigners() throws Exception {
|
||||
// setup
|
||||
URL url = TestOOXMLSignatureVerifier.class.getResource("/hello-world-signed-twice.docx");
|
||||
|
||||
// operate
|
||||
List<X509Certificate> result = OOXMLSignatureVerifier.getSigners(url);
|
||||
|
||||
// verify
|
||||
assertNotNull(result);
|
||||
assertEquals(2, result.size());
|
||||
X509Certificate signer1 = result.get(0);
|
||||
X509Certificate signer2 = result.get(1);
|
||||
LOG.debug("signer 1: " + signer1.getSubjectX500Principal());
|
||||
LOG.debug("signer 2: " + signer2.getSubjectX500Principal());
|
||||
}
|
||||
|
||||
public void testVerifySignature() throws Exception {
|
||||
|
||||
java.util.logging.Logger logger = java.util.logging.Logger.getLogger("org.jcp.xml.dsig.internal.dom");
|
||||
logger.log(Level.FINE, "test");
|
||||
|
||||
URL url = TestOOXMLSignatureVerifier.class.getResource("/hello-world-signed.docx");
|
||||
boolean validity = OOXMLSignatureVerifier.verifySignature(url);
|
||||
assertTrue(validity);
|
||||
}
|
||||
|
||||
public void testTamperedFile() throws Exception {
|
||||
|
||||
java.util.logging.Logger logger = java.util.logging.Logger.getLogger("org.jcp.xml.dsig.internal.dom");
|
||||
logger.log(Level.FINE, "test");
|
||||
|
||||
URL url = TestOOXMLSignatureVerifier.class.getResource("/invalidsig.docx");
|
||||
boolean validity = OOXMLSignatureVerifier.verifySignature(url);
|
||||
assertFalse(validity);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user