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:
Ugo Cei 2009-10-13 16:31:28 +00:00
parent ed1b4794a0
commit d48987ac07
39 changed files with 4129 additions and 0 deletions

View File

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

View File

@ -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.

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

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

View File

@ -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
}
}

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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());
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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());
}
}

View File

@ -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);
}
}