#62159 - Support XML signature over windows certificate store

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1825948 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Andreas Beeker 2018-03-06 00:07:20 +00:00
parent ff96f4c64d
commit 58a0a100f5
10 changed files with 567 additions and 451 deletions

View File

@ -20,55 +20,72 @@ package org.apache.poi.poifs.crypt;
import org.apache.poi.EncryptedDocumentException; import org.apache.poi.EncryptedDocumentException;
public enum HashAlgorithm { public enum HashAlgorithm {
none ( "", 0x0000, "", 0, "", false), none ( "", 0x0000, "", 0, "", false, ""),
sha1 ( "SHA-1", 0x8004, "SHA1", 20, "HmacSHA1", false), sha1 ( "SHA-1", 0x8004, "SHA1", 20, "HmacSHA1", false, "1.3.14.3.2.26"),
sha256 ( "SHA-256", 0x800C, "SHA256", 32, "HmacSHA256", false), sha256 ( "SHA-256", 0x800C, "SHA256", 32, "HmacSHA256", false, "2.16.840.1.101.3.4.2.1"),
sha384 ( "SHA-384", 0x800D, "SHA384", 48, "HmacSHA384", false), sha384 ( "SHA-384", 0x800D, "SHA384", 48, "HmacSHA384", false, "2.16.840.1.101.3.4.2.2"),
sha512 ( "SHA-512", 0x800E, "SHA512", 64, "HmacSHA512", false), sha512 ( "SHA-512", 0x800E, "SHA512", 64, "HmacSHA512", false, "2.16.840.1.101.3.4.2.3"),
/* only for agile encryption */ /* only for agile encryption */
md5 ( "MD5", -1, "MD5", 16, "HmacMD5", false), md5 ( "MD5", -1, "MD5", 16, "HmacMD5", false, "1.2.840.113549.2.5" ),
// although sunjc2 supports md2, hmac-md2 is only supported by bouncycastle // although sunjc2 supports md2, hmac-md2 is only supported by bouncycastle
md2 ( "MD2", -1, "MD2", 16, "Hmac-MD2", true), md2 ( "MD2", -1, "MD2", 16, "Hmac-MD2", true, "1.2.840.113549.2.2" ),
md4 ( "MD4", -1, "MD4", 16, "Hmac-MD4", true), md4 ( "MD4", -1, "MD4", 16, "Hmac-MD4", true, "1.2.840.113549.2.4" ),
ripemd128("RipeMD128", -1, "RIPEMD-128", 16, "HMac-RipeMD128", true), ripemd128("RipeMD128", -1, "RIPEMD-128", 16, "HMac-RipeMD128", true, "1.3.36.3.2.2"),
ripemd160("RipeMD160", -1, "RIPEMD-160", 20, "HMac-RipeMD160", true), ripemd160("RipeMD160", -1, "RIPEMD-160", 20, "HMac-RipeMD160", true, "1.3.36.3.2.1"),
whirlpool("Whirlpool", -1, "WHIRLPOOL", 64, "HMac-Whirlpool", true), whirlpool("Whirlpool", -1, "WHIRLPOOL", 64, "HMac-Whirlpool", true, "1.0.10118.3.0.55"),
// only for xml signing // only for xml signing
sha224 ( "SHA-224", -1, "SHA224", 28, "HmacSHA224", true); sha224 ( "SHA-224", -1, "SHA224", 28, "HmacSHA224", true, "2.16.840.1.101.3.4.2.4"),
ripemd256("RipeMD256", -1, "RIPEMD-256", 32, "HMac-RipeMD256", true, "1.3.36.3.2.3")
;
/** the id used for initializing the JCE message digest **/
public final String jceId; public final String jceId;
/** the id used for the BIFF encryption info header **/
public final int ecmaId; public final int ecmaId;
/** the id used for OOXML encryption info header **/
public final String ecmaString; public final String ecmaString;
/** the length of the digest byte array **/
public final int hashSize; public final int hashSize;
/** the id used for the integrity algorithm in agile encryption **/
public final String jceHmacId; public final String jceHmacId;
/** is bouncycastle necessary for calculating the digest **/
public final boolean needsBouncyCastle; public final boolean needsBouncyCastle;
/** ASN1 object identifier of the digest value in combination with the RSA cipher */
public final String rsaOid;
HashAlgorithm(String jceId, int ecmaId, String ecmaString, int hashSize, String jceHmacId, boolean needsBouncyCastle) { HashAlgorithm(String jceId, int ecmaId, String ecmaString, int hashSize, String jceHmacId, boolean needsBouncyCastle, String rsaOid) {
this.jceId = jceId; this.jceId = jceId;
this.ecmaId = ecmaId; this.ecmaId = ecmaId;
this.ecmaString = ecmaString; this.ecmaString = ecmaString;
this.hashSize = hashSize; this.hashSize = hashSize;
this.jceHmacId = jceHmacId; this.jceHmacId = jceHmacId;
this.needsBouncyCastle = needsBouncyCastle; this.needsBouncyCastle = needsBouncyCastle;
this.rsaOid = rsaOid;
} }
public static HashAlgorithm fromEcmaId(int ecmaId) { public static HashAlgorithm fromEcmaId(int ecmaId) {
for (HashAlgorithm ha : values()) { for (HashAlgorithm ha : values()) {
if (ha.ecmaId == ecmaId) return ha; if (ha.ecmaId == ecmaId) {
return ha;
}
} }
throw new EncryptedDocumentException("hash algorithm not found"); throw new EncryptedDocumentException("hash algorithm not found");
} }
public static HashAlgorithm fromEcmaId(String ecmaString) { public static HashAlgorithm fromEcmaId(String ecmaString) {
for (HashAlgorithm ha : values()) { for (HashAlgorithm ha : values()) {
if (ha.ecmaString.equals(ecmaString)) return ha; if (ha.ecmaString.equals(ecmaString)) {
return ha;
}
} }
throw new EncryptedDocumentException("hash algorithm not found"); throw new EncryptedDocumentException("hash algorithm not found");
} }
public static HashAlgorithm fromString(String string) { public static HashAlgorithm fromString(String string) {
for (HashAlgorithm ha : values()) { for (HashAlgorithm ha : values()) {
if (ha.ecmaString.equalsIgnoreCase(string) || ha.jceId.equalsIgnoreCase(string)) return ha; if (ha.ecmaString.equalsIgnoreCase(string) || ha.jceId.equalsIgnoreCase(string)) {
return ha;
}
} }
throw new EncryptedDocumentException("hash algorithm not found"); throw new EncryptedDocumentException("hash algorithm not found");
} }

View File

@ -1,56 +0,0 @@
/* ====================================================================
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.
==================================================================== */
/* ====================================================================
This product contains an ASLv2 licensed version of the OOXML signer
package from the eID Applet project
http://code.google.com/p/eid-applet/source/browse/trunk/README.txt
Copyright (C) 2008-2014 FedICT.
================================================================= */
package org.apache.poi.poifs.crypt.dsig;
import java.io.Serializable;
import org.apache.poi.poifs.crypt.HashAlgorithm;
/**
* Digest Information data transfer class.
*/
public class DigestInfo implements Serializable {
private static final long serialVersionUID = 1L;
/**
* Main constructor.
*
* @param digestValue
* @param hashAlgo
* @param description
*/
public DigestInfo(byte[] digestValue, HashAlgorithm hashAlgo, String description) {
this.digestValue = digestValue.clone();
this.hashAlgo = hashAlgo;
this.description = description;
}
public final byte[] digestValue;
public final String description;
public final HashAlgorithm hashAlgo;
}

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.
==================================================================== */
package org.apache.poi.poifs.crypt.dsig;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.PrivateKey;
import javax.crypto.Cipher;
import org.apache.poi.EncryptedDocumentException;
import org.apache.poi.poifs.crypt.ChainingMode;
import org.apache.poi.poifs.crypt.CipherAlgorithm;
import org.apache.poi.poifs.crypt.CryptoFunctions;
import org.apache.poi.poifs.crypt.HashAlgorithm;
import org.ietf.jgss.GSSException;
import org.ietf.jgss.Oid;
/* package */ class DigestOutputStream extends OutputStream {
final HashAlgorithm algo;
final PrivateKey key;
private MessageDigest md;
DigestOutputStream(final HashAlgorithm algo, final PrivateKey key) {
this.algo = algo;
this.key = key;
}
public void init() throws GeneralSecurityException {
if (isMSCapi(key)) {
// see https://stackoverflow.com/questions/39196145 for problems with SunMSCAPI
// and why we can't sign the calculated digest
throw new EncryptedDocumentException(
"Windows keystore entries can't be signed with the "+algo+" hash. Please "+
"use one digest algorithm of sha1 / sha256 / sha384 / sha512.");
}
md = CryptoFunctions.getMessageDigest(algo);
}
@Override
public void write(final int b) throws IOException {
md.update((byte)b);
}
@Override
public void write(final byte[] data, final int off, final int len) throws IOException {
md.update(data, off, len);
}
public byte[] sign() throws IOException, GeneralSecurityException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
bos.write(getHashMagic());
bos.write(md.digest());
final Cipher cipher = CryptoFunctions.getCipher(key, CipherAlgorithm.rsa
, ChainingMode.ecb, null, Cipher.ENCRYPT_MODE, "PKCS1Padding");
return cipher.doFinal(bos.toByteArray());
}
static boolean isMSCapi(final PrivateKey key) {
return key != null && key.getClass().getName().contains("mscapi");
}
/**
* Each digest method has its own ASN1 header
*
* @return the ASN1 header bytes for the signatureValue / digestInfo
*
* @see <a href="https://tools.ietf.org/html/rfc2313#section-10.1.2">Data encoding</a>
*/
byte[] getHashMagic() {
// in an earlier release the hashMagic (aka DigestAlgorithmIdentifier) contained only
// an object identifier, but to conform with the header generated by the
// javax-signature API, the empty <associated parameters> are also included
try {
final byte[] oidBytes = new Oid(algo.rsaOid).getDER();
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
bos.write(0x30);
bos.write(algo.hashSize+oidBytes.length+6);
bos.write(0x30);
bos.write(oidBytes.length+2);
bos.write(oidBytes);
bos.write(new byte[] {5,0,4});
bos.write(algo.hashSize);
return bos.toByteArray();
} catch (GSSException|IOException e) {
throw new IllegalStateException(e);
}
}
}

View File

@ -186,7 +186,9 @@ public class SignatureConfig {
namespacePrefixes.put(XADES_132_NS, "xd"); namespacePrefixes.put(XADES_132_NS, "xd");
} }
if (onlyValidation) return; if (onlyValidation) {
return;
}
if (signatureMarshalListener == null) { if (signatureMarshalListener == null) {
signatureMarshalListener = new SignatureMarshalListener(); signatureMarshalListener = new SignatureMarshalListener();
@ -711,55 +713,6 @@ public class SignatureConfig {
return value == null ? defaultValue : value; return value == null ? defaultValue : value;
} }
/**
* Each digest method has its own IV (initial vector)
*
* @return the IV depending on the main digest method
*/
public byte[] getHashMagic() {
// see https://www.ietf.org/rfc/rfc3110.txt
// RSA/SHA1 SIG Resource Records
byte result[];
switch (getDigestAlgo()) {
case sha1: result = new byte[]
{ 0x30, 0x1f, 0x30, 0x07, 0x06, 0x05, 0x2b, 0x0e
, 0x03, 0x02, 0x1a, 0x04, 0x14 };
break;
case sha224: result = new byte[]
{ 0x30, 0x2b, 0x30, 0x0b, 0x06, 0x09, 0x60, (byte) 0x86
, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x04, 0x1c };
break;
case sha256: result = new byte[]
{ 0x30, 0x2f, 0x30, 0x0b, 0x06, 0x09, 0x60, (byte) 0x86
, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x04, 0x20 };
break;
case sha384: result = new byte[]
{ 0x30, 0x3f, 0x30, 0x0b, 0x06, 0x09, 0x60, (byte) 0x86
, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x04, 0x30 };
break;
case sha512: result = new byte[]
{ 0x30, 0x4f, 0x30, 0x0b, 0x06, 0x09, 0x60, (byte) 0x86
, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x04, 0x40 };
break;
case ripemd128: result = new byte[]
{ 0x30, 0x1b, 0x30, 0x07, 0x06, 0x05, 0x2b, 0x24
, 0x03, 0x02, 0x02, 0x04, 0x10 };
break;
case ripemd160: result = new byte[]
{ 0x30, 0x1f, 0x30, 0x07, 0x06, 0x05, 0x2b, 0x24
, 0x03, 0x02, 0x01, 0x04, 0x14 };
break;
// case ripemd256: result = new byte[]
// { 0x30, 0x2b, 0x30, 0x07, 0x06, 0x05, 0x2b, 0x24
// , 0x03, 0x02, 0x03, 0x04, 0x20 };
// break;
default: throw new EncryptedDocumentException("Hash algorithm "
+getDigestAlgo()+" not supported for signing.");
}
return result;
}
/** /**
* @return the uri for the signature method, i.e. currently only rsa is * @return the uri for the signature method, i.e. currently only rsa is
* supported, so it's the rsa variant of the main digest * supported, so it's the rsa variant of the main digest
@ -785,7 +738,10 @@ public class SignatureConfig {
} }
/** /**
* @param digestAlgo the digest algo, currently only sha* and ripemd160 is supported * Sets the digest algorithm - currently only sha* and ripemd160 is supported.
* MS Office only supports sha1, sha256, sha384, sha512.
*
* @param digestAlgo the digest algorithm
* @return the uri for the given digest * @return the uri for the given digest
*/ */
public static String getDigestMethodUri(HashAlgorithm digestAlgo) { public static String getDigestMethodUri(HashAlgorithm digestAlgo) {
@ -857,11 +813,15 @@ public class SignatureConfig {
if (prov == null) { if (prov == null) {
String dsigProviderNames[] = { String dsigProviderNames[] = {
System.getProperty("jsr105Provider"), System.getProperty("jsr105Provider"),
"org.apache.jcp.xml.dsig.internal.dom.XMLDSigRI", // Santuario xmlsec // Santuario xmlsec
"org.jcp.xml.dsig.internal.dom.XMLDSigRI" // JDK xmlsec "org.apache.jcp.xml.dsig.internal.dom.XMLDSigRI",
// JDK xmlsec
"org.jcp.xml.dsig.internal.dom.XMLDSigRI"
}; };
for (String pn : dsigProviderNames) { for (String pn : dsigProviderNames) {
if (pn == null) continue; if (pn == null) {
continue;
}
try { try {
prov = (Provider)Class.forName(pn).newInstance(); prov = (Provider)Class.forName(pn).newInstance();
break; break;

View File

@ -27,7 +27,18 @@ package org.apache.poi.poifs.crypt.dsig;
import static org.apache.poi.POIXMLTypeLoader.DEFAULT_XML_OPTIONS; import static org.apache.poi.POIXMLTypeLoader.DEFAULT_XML_OPTIONS;
import static org.apache.poi.poifs.crypt.dsig.facets.SignatureFacet.XML_DIGSIG_NS; import static org.apache.poi.poifs.crypt.dsig.facets.SignatureFacet.XML_DIGSIG_NS;
import javax.crypto.Cipher; import java.io.IOException;
import java.io.OutputStream;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import javax.xml.bind.DatatypeConverter;
import javax.xml.crypto.MarshalException; import javax.xml.crypto.MarshalException;
import javax.xml.crypto.URIDereferencer; import javax.xml.crypto.URIDereferencer;
import javax.xml.crypto.XMLStructure; import javax.xml.crypto.XMLStructure;
@ -36,38 +47,16 @@ import javax.xml.crypto.dsig.Manifest;
import javax.xml.crypto.dsig.Reference; import javax.xml.crypto.dsig.Reference;
import javax.xml.crypto.dsig.SignatureMethod; import javax.xml.crypto.dsig.SignatureMethod;
import javax.xml.crypto.dsig.SignedInfo; import javax.xml.crypto.dsig.SignedInfo;
import javax.xml.crypto.dsig.TransformException;
import javax.xml.crypto.dsig.XMLObject; import javax.xml.crypto.dsig.XMLObject;
import javax.xml.crypto.dsig.XMLSignContext;
import javax.xml.crypto.dsig.XMLSignature;
import javax.xml.crypto.dsig.XMLSignatureException; import javax.xml.crypto.dsig.XMLSignatureException;
import javax.xml.crypto.dsig.XMLSignatureFactory; import javax.xml.crypto.dsig.XMLSignatureFactory;
import javax.xml.crypto.dsig.XMLValidateContext;
import javax.xml.crypto.dsig.dom.DOMSignContext; import javax.xml.crypto.dsig.dom.DOMSignContext;
import javax.xml.crypto.dsig.dom.DOMValidateContext;
import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec; import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.Provider;
import java.security.Security;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import org.apache.jcp.xml.dsig.internal.dom.DOMReference; import org.apache.jcp.xml.dsig.internal.dom.DOMReference;
import org.apache.jcp.xml.dsig.internal.dom.DOMSignedInfo; import org.apache.jcp.xml.dsig.internal.dom.DOMSignedInfo;
import org.apache.jcp.xml.dsig.internal.dom.DOMSubTreeData;
import org.apache.poi.EncryptedDocumentException; import org.apache.poi.EncryptedDocumentException;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException; import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.openxml4j.opc.ContentTypes; import org.apache.poi.openxml4j.opc.ContentTypes;
@ -79,9 +68,8 @@ import org.apache.poi.openxml4j.opc.PackageRelationshipCollection;
import org.apache.poi.openxml4j.opc.PackageRelationshipTypes; import org.apache.poi.openxml4j.opc.PackageRelationshipTypes;
import org.apache.poi.openxml4j.opc.PackagingURIHelper; import org.apache.poi.openxml4j.opc.PackagingURIHelper;
import org.apache.poi.openxml4j.opc.TargetMode; import org.apache.poi.openxml4j.opc.TargetMode;
import org.apache.poi.poifs.crypt.ChainingMode;
import org.apache.poi.poifs.crypt.CipherAlgorithm;
import org.apache.poi.poifs.crypt.CryptoFunctions; import org.apache.poi.poifs.crypt.CryptoFunctions;
import org.apache.poi.poifs.crypt.HashAlgorithm;
import org.apache.poi.poifs.crypt.dsig.SignatureConfig.SignatureConfigurable; import org.apache.poi.poifs.crypt.dsig.SignatureConfig.SignatureConfigurable;
import org.apache.poi.poifs.crypt.dsig.facets.SignatureFacet; import org.apache.poi.poifs.crypt.dsig.facets.SignatureFacet;
import org.apache.poi.poifs.crypt.dsig.services.RelationshipTransformService; import org.apache.poi.poifs.crypt.dsig.services.RelationshipTransformService;
@ -90,7 +78,7 @@ import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger; import org.apache.poi.util.POILogger;
import org.apache.xml.security.Init; import org.apache.xml.security.Init;
import org.apache.xml.security.utils.Base64; import org.apache.xml.security.utils.Base64;
import org.apache.xmlbeans.XmlException; import org.apache.xml.security.utils.XMLUtils;
import org.apache.xmlbeans.XmlOptions; import org.apache.xmlbeans.XmlOptions;
import org.w3.x2000.x09.xmldsig.SignatureDocument; import org.w3.x2000.x09.xmldsig.SignatureDocument;
import org.w3c.dom.Document; import org.w3c.dom.Document;
@ -98,7 +86,6 @@ import org.w3c.dom.Element;
import org.w3c.dom.NodeList; import org.w3c.dom.NodeList;
import org.w3c.dom.events.EventListener; import org.w3c.dom.events.EventListener;
import org.w3c.dom.events.EventTarget; import org.w3c.dom.events.EventTarget;
import org.xml.sax.SAXException;
/** /**
@ -175,119 +162,6 @@ public class SignatureInfo implements SignatureConfigurable {
private SignatureConfig signatureConfig; private SignatureConfig signatureConfig;
public class SignaturePart {
private final PackagePart signaturePart;
private X509Certificate signer;
private List<X509Certificate> certChain;
private SignaturePart(PackagePart signaturePart) {
this.signaturePart = signaturePart;
}
/**
* @return the package part containing the signature
*/
public PackagePart getPackagePart() {
return signaturePart;
}
/**
* @return the signer certificate
*/
public X509Certificate getSigner() {
return signer;
}
/**
* @return the certificate chain of the signer
*/
public List<X509Certificate> getCertChain() {
return certChain;
}
/**
* Helper method for examining the xml signature
*
* @return the xml signature document
* @throws IOException if the xml signature doesn't exist or can't be read
* @throws XmlException if the xml signature is malformed
*/
public SignatureDocument getSignatureDocument() throws IOException, XmlException {
// TODO: check for XXE
return SignatureDocument.Factory.parse(signaturePart.getInputStream(), DEFAULT_XML_OPTIONS);
}
/**
* @return true, when the xml signature is valid, false otherwise
*
* @throws EncryptedDocumentException if the signature can't be extracted or if its malformed
*/
@SuppressWarnings("unchecked")
public boolean validate() {
KeyInfoKeySelector keySelector = new KeyInfoKeySelector();
try {
Document doc = DocumentHelper.readDocument(signaturePart.getInputStream());
XPath xpath = XPathFactory.newInstance().newXPath();
NodeList nl = (NodeList)xpath.compile("//*[@Id]").evaluate(doc, XPathConstants.NODESET);
final int length = nl.getLength();
for (int i=0; i<length; i++) {
((Element)nl.item(i)).setIdAttribute("Id", true);
}
DOMValidateContext domValidateContext = new DOMValidateContext(keySelector, doc);
domValidateContext.setProperty("org.jcp.xml.dsig.validateManifests", Boolean.TRUE);
domValidateContext.setURIDereferencer(signatureConfig.getUriDereferencer());
brokenJvmWorkaround(domValidateContext);
XMLSignatureFactory xmlSignatureFactory = signatureConfig.getSignatureFactory();
XMLSignature xmlSignature = xmlSignatureFactory.unmarshalXMLSignature(domValidateContext);
// TODO: replace with property when xml-sec patch is applied
// workaround added in r1637283 2014-11-07
for (Reference ref : (List<Reference>)xmlSignature.getSignedInfo().getReferences()) {
SignatureFacet.brokenJvmWorkaround(ref);
}
for (XMLObject xo : (List<XMLObject>)xmlSignature.getObjects()) {
for (XMLStructure xs : (List<XMLStructure>)xo.getContent()) {
if (xs instanceof Manifest) {
for (Reference ref : (List<Reference>)((Manifest)xs).getReferences()) {
SignatureFacet.brokenJvmWorkaround(ref);
}
}
}
}
boolean valid = xmlSignature.validate(domValidateContext);
if (valid) {
signer = keySelector.getSigner();
certChain = keySelector.getCertChain();
}
return valid;
} catch (IOException e) {
String s = "error in reading document";
LOG.log(POILogger.ERROR, s, e);
throw new EncryptedDocumentException(s, e);
} catch (SAXException e) {
String s = "error in parsing document";
LOG.log(POILogger.ERROR, s, e);
throw new EncryptedDocumentException(s, e);
} catch (XPathExpressionException e) {
String s = "error in searching document with xpath expression";
LOG.log(POILogger.ERROR, s, e);
throw new EncryptedDocumentException(s, e);
} catch (MarshalException e) {
String s = "error in unmarshalling the signature";
LOG.log(POILogger.ERROR, s, e);
throw new EncryptedDocumentException(s, e);
} catch (XMLSignatureException e) {
String s = "error in validating the signature";
LOG.log(POILogger.ERROR, s, e);
throw new EncryptedDocumentException(s, e);
}
}
}
/** /**
* Constructor initializes xml signature environment, if it hasn't been initialized before * Constructor initializes xml signature environment, if it hasn't been initialized before
@ -306,6 +180,7 @@ public class SignatureInfo implements SignatureConfigurable {
/** /**
* @param signatureConfig the signature config, needs to be set before a SignatureInfo object is used * @param signatureConfig the signature config, needs to be set before a SignatureInfo object is used
*/ */
@Override
public void setSignatureConfig(SignatureConfig signatureConfig) { public void setSignatureConfig(SignatureConfig signatureConfig) {
this.signatureConfig = signatureConfig; this.signatureConfig = signatureConfig;
} }
@ -329,18 +204,31 @@ public class SignatureInfo implements SignatureConfigurable {
* @throws MarshalException * @throws MarshalException
*/ */
public void confirmSignature() throws XMLSignatureException, MarshalException { public void confirmSignature() throws XMLSignatureException, MarshalException {
Document document = DocumentHelper.createDocument(); final Document document = DocumentHelper.createDocument();
final DOMSignContext xmlSignContext = createXMLSignContext(document);
// operate // operate
DigestInfo digestInfo = preSign(document, null); final DOMSignedInfo signedInfo = preSign(xmlSignContext);
// setup: key material, signature value // setup: key material, signature value
byte[] signatureValue = signDigest(digestInfo.digestValue); final String signatureValue = signDigest(xmlSignContext, signedInfo);
// operate: postSign // operate: postSign
postSign(document, signatureValue); postSign(xmlSignContext, signatureValue);
} }
/**
* Convenience method for creating the signature context
*
* @param document the document the signature is based on
*
* @return the initialized signature context
*/
public DOMSignContext createXMLSignContext(final Document document) {
return new DOMSignContext(signatureConfig.getKey(), document);
}
/** /**
* Sign (encrypt) the digest with the private key. * Sign (encrypt) the digest with the private key.
* Currently only rsa is supported. * Currently only rsa is supported.
@ -348,17 +236,36 @@ public class SignatureInfo implements SignatureConfigurable {
* @param digest the hashed input * @param digest the hashed input
* @return the encrypted hash * @return the encrypted hash
*/ */
public byte[] signDigest(byte digest[]) { public String signDigest(final DOMSignContext xmlSignContext, final DOMSignedInfo signedInfo) {
Cipher cipher = CryptoFunctions.getCipher(signatureConfig.getKey(), CipherAlgorithm.rsa final PrivateKey key = signatureConfig.getKey();
, ChainingMode.ecb, null, Cipher.ENCRYPT_MODE, "PKCS1Padding"); final HashAlgorithm algo = signatureConfig.getDigestAlgo();
if (algo.hashSize*4/3 > Base64.BASE64DEFAULTLENGTH && !XMLUtils.ignoreLineBreaks()) {
throw new EncryptedDocumentException("The hash size of the choosen hash algorithm ("+algo+" = "+algo.hashSize+" bytes), "+
"will motivate XmlSec to add linebreaks to the generated digest, which results in an invalid signature (... at least "+
"for Office) - please persuade it otherwise by adding '-Dorg.apache.xml.security.ignoreLineBreaks=true' to the JVM "+
"system properties.");
}
try { try {
ByteArrayOutputStream digestInfoValueBuf = new ByteArrayOutputStream(); final DigestOutputStream dos;
digestInfoValueBuf.write(signatureConfig.getHashMagic()); switch (algo) {
digestInfoValueBuf.write(digest); case md2: case md5: case sha1: case sha256: case sha384: case sha512:
byte[] digestInfoValue = digestInfoValueBuf.toByteArray(); dos = new SignatureOutputStream(algo, key);
return cipher.doFinal(digestInfoValue); break;
} catch (Exception e) { default:
dos = new DigestOutputStream(algo, key);
break;
}
dos.init();
final Document document = (Document)xmlSignContext.getParent();
final Element el = getDsigElement(document, "SignedInfo");
final DOMSubTreeData subTree = new DOMSubTreeData(el, true);
signedInfo.getCanonicalizationMethod().transform(subTree, xmlSignContext, dos);
return DatatypeConverter.printBase64Binary(dos.sign());
} catch (GeneralSecurityException|IOException|TransformException e) {
throw new EncryptedDocumentException(e); throw new EncryptedDocumentException(e);
} }
} }
@ -370,6 +277,7 @@ public class SignatureInfo implements SignatureConfigurable {
public Iterable<SignaturePart> getSignatureParts() { public Iterable<SignaturePart> getSignatureParts() {
signatureConfig.init(true); signatureConfig.init(true);
return new Iterable<SignaturePart>() { return new Iterable<SignaturePart>() {
@Override
public Iterator<SignaturePart> iterator() { public Iterator<SignaturePart> iterator() {
return new Iterator<SignaturePart>() { return new Iterator<SignaturePart>() {
OPCPackage pkg = signatureConfig.getOpcPackage(); OPCPackage pkg = signatureConfig.getOpcPackage();
@ -378,9 +286,12 @@ public class SignatureInfo implements SignatureConfigurable {
Iterator<PackageRelationship> sigRels; Iterator<PackageRelationship> sigRels;
PackagePart sigPart; PackagePart sigPart;
@Override
public boolean hasNext() { public boolean hasNext() {
while (sigRels == null || !sigRels.hasNext()) { while (sigRels == null || !sigRels.hasNext()) {
if (!sigOrigRels.hasNext()) return false; if (!sigOrigRels.hasNext()) {
return false;
}
sigPart = pkg.getPart(sigOrigRels.next()); sigPart = pkg.getPart(sigOrigRels.next());
LOG.log(POILogger.DEBUG, "Digital Signature Origin part", sigPart); LOG.log(POILogger.DEBUG, "Digital Signature Origin part", sigPart);
try { try {
@ -392,20 +303,24 @@ public class SignatureInfo implements SignatureConfigurable {
return true; return true;
} }
@Override
public SignaturePart next() { public SignaturePart next() {
PackagePart sigRelPart = null; PackagePart sigRelPart = null;
do { do {
try { try {
if (!hasNext()) throw new NoSuchElementException(); if (!hasNext()) {
throw new NoSuchElementException();
}
sigRelPart = sigPart.getRelatedPart(sigRels.next()); sigRelPart = sigPart.getRelatedPart(sigRels.next());
LOG.log(POILogger.DEBUG, "XML Signature part", sigRelPart); LOG.log(POILogger.DEBUG, "XML Signature part", sigRelPart);
} catch (InvalidFormatException e) { } catch (InvalidFormatException e) {
LOG.log(POILogger.WARN, "Reference to signature is invalid.", e); LOG.log(POILogger.WARN, "Reference to signature is invalid.", e);
} }
} while (sigPart == null); } while (sigPart == null);
return new SignaturePart(sigRelPart); return new SignaturePart(sigRelPart, signatureConfig);
} }
@Override
public void remove() { public void remove() {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@ -418,7 +333,9 @@ public class SignatureInfo implements SignatureConfigurable {
* Initialize the xml signing environment and the bouncycastle provider * Initialize the xml signing environment and the bouncycastle provider
*/ */
protected static synchronized void initXmlProvider() { protected static synchronized void initXmlProvider() {
if (isInitialized) return; if (isInitialized) {
return;
}
isInitialized = true; isInitialized = true;
try { try {
@ -435,10 +352,12 @@ public class SignatureInfo implements SignatureConfigurable {
* Normally {@link #confirmSignature()} is sufficient to be used. * Normally {@link #confirmSignature()} is sufficient to be used.
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public DigestInfo preSign(Document document, List<DigestInfo> digestInfos) public DOMSignedInfo preSign(final DOMSignContext xmlSignContext)
throws XMLSignatureException, MarshalException { throws XMLSignatureException, MarshalException {
signatureConfig.init(false); signatureConfig.init(false);
final Document document = (Document)xmlSignContext.getParent();
// it's necessary to explicitly set the mdssi namespace, but the sign() method has no // it's necessary to explicitly set the mdssi namespace, but the sign() method has no
// normal way to interfere with, so we need to add the namespace under the hand ... // normal way to interfere with, so we need to add the namespace under the hand ...
EventTarget target = (EventTarget)document; EventTarget target = (EventTarget)document;
@ -453,7 +372,6 @@ public class SignatureInfo implements SignatureConfigurable {
/* /*
* Signature context construction. * Signature context construction.
*/ */
XMLSignContext xmlSignContext = new DOMSignContext(signatureConfig.getKey(), document);
URIDereferencer uriDereferencer = signatureConfig.getUriDereferencer(); URIDereferencer uriDereferencer = signatureConfig.getUriDereferencer();
if (null != uriDereferencer) { if (null != uriDereferencer) {
xmlSignContext.setURIDereferencer(uriDereferencer); xmlSignContext.setURIDereferencer(uriDereferencer);
@ -465,22 +383,12 @@ public class SignatureInfo implements SignatureConfigurable {
xmlSignContext.setDefaultNamespacePrefix(""); xmlSignContext.setDefaultNamespacePrefix("");
// signatureConfig.getNamespacePrefixes().get(XML_DIGSIG_NS)); // signatureConfig.getNamespacePrefixes().get(XML_DIGSIG_NS));
brokenJvmWorkaround(xmlSignContext);
XMLSignatureFactory signatureFactory = signatureConfig.getSignatureFactory(); XMLSignatureFactory signatureFactory = signatureConfig.getSignatureFactory();
/* /*
* Add ds:References that come from signing client local files. * Add ds:References that come from signing client local files.
*/ */
List<Reference> references = new ArrayList<>(); List<Reference> references = new ArrayList<>();
for (DigestInfo digestInfo : safe(digestInfos)) {
byte[] documentDigestValue = digestInfo.digestValue;
String uri = new File(digestInfo.description).getName();
Reference reference = SignatureFacet.newReference
(uri, null, null, null, documentDigestValue, signatureConfig);
references.add(reference);
}
/* /*
* Invoke the signature facets. * Invoke the signature facets.
@ -528,11 +436,15 @@ public class SignatureInfo implements SignatureConfigurable {
List<XMLStructure> objectContentList = object.getContent(); List<XMLStructure> objectContentList = object.getContent();
for (XMLStructure objectContent : objectContentList) { for (XMLStructure objectContent : objectContentList) {
LOG.log(POILogger.DEBUG, "object content java type: " + objectContent.getClass().getName()); LOG.log(POILogger.DEBUG, "object content java type: " + objectContent.getClass().getName());
if (!(objectContent instanceof Manifest)) continue; if (!(objectContent instanceof Manifest)) {
continue;
}
Manifest manifest = (Manifest) objectContent; Manifest manifest = (Manifest) objectContent;
List<Reference> manifestReferences = manifest.getReferences(); List<Reference> manifestReferences = manifest.getReferences();
for (Reference manifestReference : manifestReferences) { for (Reference manifestReference : manifestReferences) {
if (manifestReference.getDigestValue() != null) continue; if (manifestReference.getDigestValue() != null) {
continue;
}
DOMReference manifestDOMReference = (DOMReference)manifestReference; DOMReference manifestDOMReference = (DOMReference)manifestReference;
manifestDOMReference.digest(xmlSignContext); manifestDOMReference.digest(xmlSignContext);
@ -548,40 +460,26 @@ public class SignatureInfo implements SignatureConfigurable {
DOMReference domReference = (DOMReference)signedInfoReference; DOMReference domReference = (DOMReference)signedInfoReference;
// ds:Reference with external digest value // ds:Reference with external digest value
if (domReference.getDigestValue() != null) continue; if (domReference.getDigestValue() != null) {
continue;
}
domReference.digest(xmlSignContext); domReference.digest(xmlSignContext);
} }
/* return (DOMSignedInfo)signedInfo;
* 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 md = CryptoFunctions.getMessageDigest(signatureConfig.getDigestAlgo());
byte[] digestValue = md.digest(octets);
String description = signatureConfig.getSignatureDescription();
return new DigestInfo(digestValue, signatureConfig.getDigestAlgo(), description);
} }
/** /**
* Helper method for adding informations after the signing. * Helper method for adding informations after the signing.
* Normally {@link #confirmSignature()} is sufficient to be used. * Normally {@link #confirmSignature()} is sufficient to be used.
*/ */
public void postSign(Document document, byte[] signatureValue) public void postSign(final DOMSignContext xmlSignContext, final String signatureValue)
throws MarshalException { throws MarshalException {
LOG.log(POILogger.DEBUG, "postSign"); LOG.log(POILogger.DEBUG, "postSign");
final Document document = (Document)xmlSignContext.getParent();
/* /*
* Check ds:Signature node. * Check ds:Signature node.
*/ */
@ -593,11 +491,11 @@ public class SignatureInfo implements SignatureConfigurable {
/* /*
* Insert signature value into the ds:SignatureValue element * Insert signature value into the ds:SignatureValue element
*/ */
NodeList sigValNl = document.getElementsByTagNameNS(XML_DIGSIG_NS, "SignatureValue"); final Element signatureNode = getDsigElement(document, "SignatureValue");
if (sigValNl.getLength() != 1) { if (signatureNode == null) {
throw new RuntimeException("preSign has to be called before postSign"); throw new RuntimeException("preSign has to be called before postSign");
} }
sigValNl.item(0).setTextContent(Base64.encode(signatureValue)); signatureNode.setTextContent(signatureValue);
/* /*
* Allow signature facets to inject their own stuff. * Allow signature facets to inject their own stuff.
@ -671,30 +569,14 @@ public class SignatureInfo implements SignatureConfigurable {
sigsPart.addRelationship(sigPartName, TargetMode.INTERNAL, PackageRelationshipTypes.DIGITAL_SIGNATURE); sigsPart.addRelationship(sigPartName, TargetMode.INTERNAL, PackageRelationshipTypes.DIGITAL_SIGNATURE);
} }
/** private Element getDsigElement(final Document document, final String localName) {
* Helper method for null lists, which are converted to empty lists NodeList sigValNl = document.getElementsByTagNameNS(XML_DIGSIG_NS, localName);
* if (sigValNl.getLength() == 1) {
* @param other the reference to wrap, if null return (Element)sigValNl.item(0);
* @return if other is null, an empty lists is returned, otherwise other is returned
*/
private static <T> List<T> safe(List<T> other) {
List<T> emptyList = Collections.emptyList();
return other == null ? emptyList : other;
} }
private void brokenJvmWorkaround(XMLSignContext context) { LOG.log(POILogger.WARN, "Signature element '"+localName+"' was "+(sigValNl.getLength() == 0 ? "not found" : "multiple times"));
// workaround for https://bugzilla.redhat.com/show_bug.cgi?id=1155012
Provider bcProv = Security.getProvider("BC");
if (bcProv != null) {
context.setProperty("org.jcp.xml.dsig.internal.dom.SignatureProvider", bcProv);
}
}
private void brokenJvmWorkaround(XMLValidateContext context) { return null;
// workaround for https://bugzilla.redhat.com/show_bug.cgi?id=1155012
Provider bcProv = Security.getProvider("BC");
if (bcProv != null) {
context.setProperty("org.jcp.xml.dsig.internal.dom.SignatureProvider", bcProv);
}
} }
} }

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.
==================================================================== */
package org.apache.poi.poifs.crypt.dsig;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.SignatureException;
import org.apache.poi.poifs.crypt.HashAlgorithm;
/* package */ class SignatureOutputStream extends DigestOutputStream {
Signature signature;
SignatureOutputStream(final HashAlgorithm algo, PrivateKey key) {
super(algo, key);
}
@Override
public void init() throws GeneralSecurityException {
final String provider = isMSCapi(key) ? "SunMSCAPI" : "SunRsaSign";
signature = Signature.getInstance(algo.ecmaString+"withRSA", provider);
signature.initSign(key);
}
@Override
public byte[] sign() throws SignatureException {
return signature.sign();
}
@Override
public void write(final int b) throws IOException {
try {
signature.update((byte)b);
} catch (final SignatureException e) {
throw new IOException(e);
}
}
@Override
public void write(final byte[] data, final int off, final int len) throws IOException {
try {
signature.update(data, off, len);
} catch (final SignatureException e) {
throw new IOException(e);
}
}
}

View File

@ -0,0 +1,149 @@
/* ====================================================================
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.
==================================================================== */
package org.apache.poi.poifs.crypt.dsig;
import static org.apache.poi.POIXMLTypeLoader.DEFAULT_XML_OPTIONS;
import java.io.IOException;
import java.security.cert.X509Certificate;
import java.util.List;
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.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.apache.poi.EncryptedDocumentException;
import org.apache.poi.openxml4j.opc.PackagePart;
import org.apache.poi.util.DocumentHelper;
import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger;
import org.apache.xmlbeans.XmlException;
import org.w3.x2000.x09.xmldsig.SignatureDocument;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
public class SignaturePart {
private static final POILogger LOG = POILogFactory.getLogger(SignaturePart.class);
private static final String XMLSEC_VALIDATE_MANIFEST = "org.jcp.xml.dsig.validateManifests";
private final PackagePart signaturePart;
private final SignatureConfig signatureConfig;
private X509Certificate signer;
private List<X509Certificate> certChain;
/* package */ SignaturePart(final PackagePart signaturePart, final SignatureConfig signatureConfig) {
this.signaturePart = signaturePart;
this.signatureConfig = signatureConfig;
}
/**
* @return the package part containing the signature
*/
public PackagePart getPackagePart() {
return signaturePart;
}
/**
* @return the signer certificate
*/
public X509Certificate getSigner() {
return signer;
}
/**
* @return the certificate chain of the signer
*/
public List<X509Certificate> getCertChain() {
return certChain;
}
/**
* Helper method for examining the xml signature
*
* @return the xml signature document
* @throws IOException if the xml signature doesn't exist or can't be read
* @throws XmlException if the xml signature is malformed
*/
public SignatureDocument getSignatureDocument() throws IOException, XmlException {
// TODO: check for XXE
return SignatureDocument.Factory.parse(signaturePart.getInputStream(), DEFAULT_XML_OPTIONS);
}
/**
* @return true, when the xml signature is valid, false otherwise
*
* @throws EncryptedDocumentException if the signature can't be extracted or if its malformed
*/
public boolean validate() {
KeyInfoKeySelector keySelector = new KeyInfoKeySelector();
try {
Document doc = DocumentHelper.readDocument(signaturePart.getInputStream());
XPath xpath = XPathFactory.newInstance().newXPath();
NodeList nl = (NodeList)xpath.compile("//*[@Id]").evaluate(doc, XPathConstants.NODESET);
final int length = nl.getLength();
for (int i=0; i<length; i++) {
((Element)nl.item(i)).setIdAttribute("Id", true);
}
DOMValidateContext domValidateContext = new DOMValidateContext(keySelector, doc);
domValidateContext.setProperty(XMLSEC_VALIDATE_MANIFEST, Boolean.TRUE);
domValidateContext.setURIDereferencer(signatureConfig.getUriDereferencer());
XMLSignatureFactory xmlSignatureFactory = signatureConfig.getSignatureFactory();
XMLSignature xmlSignature = xmlSignatureFactory.unmarshalXMLSignature(domValidateContext);
boolean valid = xmlSignature.validate(domValidateContext);
if (valid) {
signer = keySelector.getSigner();
certChain = keySelector.getCertChain();
}
return valid;
} catch (IOException e) {
String s = "error in reading document";
LOG.log(POILogger.ERROR, s, e);
throw new EncryptedDocumentException(s, e);
} catch (SAXException e) {
String s = "error in parsing document";
LOG.log(POILogger.ERROR, s, e);
throw new EncryptedDocumentException(s, e);
} catch (XPathExpressionException e) {
String s = "error in searching document with xpath expression";
LOG.log(POILogger.ERROR, s, e);
throw new EncryptedDocumentException(s, e);
} catch (MarshalException e) {
String s = "error in unmarshalling the signature";
LOG.log(POILogger.ERROR, s, e);
throw new EncryptedDocumentException(s, e);
} catch (XMLSignatureException e) {
String s = "error in validating the signature";
LOG.log(POILogger.ERROR, s, e);
throw new EncryptedDocumentException(s, e);
}
}
}

View File

@ -24,14 +24,7 @@
package org.apache.poi.poifs.crypt.dsig.facets; package org.apache.poi.poifs.crypt.dsig.facets;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.PrivilegedAction;
import java.security.Provider;
import java.security.Security;
import java.util.List; import java.util.List;
import javax.xml.XMLConstants; import javax.xml.XMLConstants;
@ -45,14 +38,11 @@ import javax.xml.crypto.dsig.XMLSignatureException;
import javax.xml.crypto.dsig.XMLSignatureFactory; import javax.xml.crypto.dsig.XMLSignatureFactory;
import javax.xml.crypto.dsig.spec.TransformParameterSpec; import javax.xml.crypto.dsig.spec.TransformParameterSpec;
import org.apache.jcp.xml.dsig.internal.dom.DOMDigestMethod;
import org.apache.jcp.xml.dsig.internal.dom.DOMReference;
import org.apache.poi.openxml4j.opc.PackageNamespaces; import org.apache.poi.openxml4j.opc.PackageNamespaces;
import org.apache.poi.poifs.crypt.dsig.SignatureConfig; import org.apache.poi.poifs.crypt.dsig.SignatureConfig;
import org.apache.poi.poifs.crypt.dsig.SignatureConfig.SignatureConfigurable; import org.apache.poi.poifs.crypt.dsig.SignatureConfig.SignatureConfigurable;
import org.apache.poi.util.POILogFactory; import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger; import org.apache.poi.util.POILogger;
import org.apache.poi.util.SuppressForbidden;
import org.w3c.dom.Document; import org.w3c.dom.Document;
/** /**
@ -71,6 +61,7 @@ public abstract class SignatureFacet implements SignatureConfigurable {
protected SignatureConfig signatureConfig; protected SignatureConfig signatureConfig;
@Override
public void setSignatureConfig(SignatureConfig signatureConfig) { public void setSignatureConfig(SignatureConfig signatureConfig) {
this.signatureConfig = signatureConfig; this.signatureConfig = signatureConfig;
} }
@ -153,38 +144,7 @@ public abstract class SignatureFacet implements SignatureConfigurable {
reference = sigFac.newReference(uri, digestMethod, transforms, type, id, digestValue); reference = sigFac.newReference(uri, digestMethod, transforms, type, id, digestValue);
} }
brokenJvmWorkaround(reference);
return reference; return reference;
} }
// helper method ... will be removed soon
public static void brokenJvmWorkaround(final Reference reference) {
final DigestMethod digestMethod = reference.getDigestMethod();
final String digestMethodUri = digestMethod.getAlgorithm();
final Provider bcProv = Security.getProvider("BC");
if (bcProv != null && !DigestMethod.SHA1.equals(digestMethodUri)) {
// workaround for https://bugzilla.redhat.com/show_bug.cgi?id=1155012
// overwrite standard message digest, if a digest <> SHA1 is used
AccessController.doPrivileged(new PrivilegedAction<Void>() {
@Override
@SuppressForbidden("Workaround for a bug, needs access to private JDK members (may fail in Java 9): https://bugzilla.redhat.com/show_bug.cgi?id=1155012")
public Void run() {
try {
Method m = DOMDigestMethod.class.getDeclaredMethod("getMessageDigestAlgorithm");
m.setAccessible(true);
String mdAlgo = (String)m.invoke(digestMethod);
MessageDigest md = MessageDigest.getInstance(mdAlgo, bcProv);
Field f = DOMReference.class.getDeclaredField("md");
f.setAccessible(true);
f.set(reference, md);
} catch (Exception e) {
LOG.log(POILogger.WARN, "Can't overwrite message digest (workaround for https://bugzilla.redhat.com/show_bug.cgi?id=1155012)", e);
}
return null; // Void
}
});
}
}
} }

View File

@ -56,15 +56,17 @@ import java.util.Date;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import javax.xml.crypto.dsig.dom.DOMSignContext;
import org.apache.jcp.xml.dsig.internal.dom.DOMSignedInfo;
import org.apache.poi.POIDataSamples; import org.apache.poi.POIDataSamples;
import org.apache.poi.POITestCase; import org.apache.poi.POITestCase;
import org.apache.poi.openxml4j.opc.OPCPackage; import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.openxml4j.opc.PackageAccess; import org.apache.poi.openxml4j.opc.PackageAccess;
import org.apache.poi.openxml4j.opc.PackageRelationshipTypes; import org.apache.poi.openxml4j.opc.PackageRelationshipTypes;
import org.apache.poi.poifs.crypt.dsig.DigestInfo;
import org.apache.poi.poifs.crypt.dsig.SignatureConfig; import org.apache.poi.poifs.crypt.dsig.SignatureConfig;
import org.apache.poi.poifs.crypt.dsig.SignatureInfo; import org.apache.poi.poifs.crypt.dsig.SignatureInfo;
import org.apache.poi.poifs.crypt.dsig.SignatureInfo.SignaturePart; import org.apache.poi.poifs.crypt.dsig.SignaturePart;
import org.apache.poi.poifs.crypt.dsig.facets.EnvelopedSignatureFacet; import org.apache.poi.poifs.crypt.dsig.facets.EnvelopedSignatureFacet;
import org.apache.poi.poifs.crypt.dsig.facets.KeyInfoSignatureFacet; import org.apache.poi.poifs.crypt.dsig.facets.KeyInfoSignatureFacet;
import org.apache.poi.poifs.crypt.dsig.facets.XAdESSignatureFacet; import org.apache.poi.poifs.crypt.dsig.facets.XAdESSignatureFacet;
@ -120,8 +122,6 @@ public class TestSignatureInfo {
cal = LocaleUtil.getLocaleCalendar(LocaleUtil.TIMEZONE_UTC); cal = LocaleUtil.getLocaleCalendar(LocaleUtil.TIMEZONE_UTC);
assertNotNull(cal); assertNotNull(cal);
// cal.set(2014, 7, 6, 21, 42, 12);
// cal.clear(Calendar.MILLISECOND);
// don't run this test when we are using older Xerces as it triggers an XML Parser backwards compatibility issue // don't run this test when we are using older Xerces as it triggers an XML Parser backwards compatibility issue
// in the xmlsec jar file // in the xmlsec jar file
@ -129,6 +129,11 @@ public class TestSignatureInfo {
//System.out.println("Having: " + additionalJar); //System.out.println("Having: " + additionalJar);
Assume.assumeTrue("Not running TestSignatureInfo because we are testing with additionaljar set to " + additionalJar, Assume.assumeTrue("Not running TestSignatureInfo because we are testing with additionaljar set to " + additionalJar,
additionalJar == null || additionalJar.trim().length() == 0); additionalJar == null || additionalJar.trim().length() == 0);
System.setProperty("org.apache.xml.security.ignoreLineBreaks", "true");
// Set line.separator for bug61182
// System.setProperty("line.separator", "\n");
} }
@Ignore("This test is very sensitive, it breaks with every little change to the produced XML") @Ignore("This test is very sensitive, it breaks with every little change to the produced XML")
@ -198,18 +203,18 @@ public class TestSignatureInfo {
if (sep == null || "\n".equals(sep)) { if (sep == null || "\n".equals(sep)) {
// Unix // Unix
signExp = signExp =
"HDdvgXblLMiE6gZSoRSQUof6+aedrhK9i51we1n+4Q/ioqrQCeh5UkfQ8lD63nV4ZDbM4/pIVFi6VpMpN/HMnA"+ "QkqTFQZjXagjRAoOWKpAGa8AR0rKqkSfBtfSWqtjBmTgyjarn+t2POHkpySIpheHAbg+90GKSH88ACMtPHbG7q"+
"UHeVdVUCVTgpn3Iz21Ymcd9/aerNov2BjHLhS8X3oUE+XTu2TbJLNmms0I9G4lfg6HWP9t7ZCXBXy6vyCMArc="; "FL4gtgAD9Kjew6j16j0IRBwy145UlPrSLFMfF7YF7UlU1k1LBkIlRJ6Fv4MAJl6XspuzZOZIUmHZrWrdxycUQ=";
} else if ("\r\n".equals(sep)){ } else if ("\r\n".equals(sep)){
// Windows // Windows
signExp = signExp =
"jVW6EPMywZ8jr4+I4alDosXzqrVuDG4wTdrr+la8QVbXfLm6HOh9AUFlo5yUZuWo/1gXrrkc34UTYNzuslyrOx"+ "GmAlL7+bT1r3FsMHJOp3pKg8betblYieZTjhMIrPZPRBbSzjO7KsYRGNtr0aOE3qr8xzyYJN6/8QdF5X7pUEUc"+
"KqadPOIRKUssJzdCh/hKeTxs/YtyWkpGHggrUjrF/vUUIeIXRHo+1DCAh6ptoicviH/I/Dtoa5NgkEHVuOHk8="; "2m8ctrm7s5o2vZTkAqk9ENJGDjBPXX7TnuVOiVeL1cJdtjHC2QpjtRwkFR+B54G6b1OXLOFuQpP3vqR3+/XXE=";
} else { } else {
// Mac // Mac
signExp = signExp =
"GSaOQp2eVRkQl2GJgWxoxFdCadJJnmmKeoQtIwGrP3zzk+BnLeytGLN3bqmwCTjvtG7DyxENS+92e2xq/MiC9b"+ "NZedY/LNTYU4nAUEUhIOg5+fKdgVtzRXKmdD3v+47E7Mb84oeiUGv9cCEE91DU3StF/JFIhjOJqavOzKnCsNcz"+
"CtNUfXfCdM0M8fzAny/Ewn9HckIsxjBztmsryt/OZQaKu52VU0ohQu7bG+cGPzcM+qTEss+GUbD0sVAoC34HM="; "NJ4j/inggUl1OJUsicqIGQnA7E8vzWnN1kf5lINgJLv+0PyrrX9sQZbItzxUpgqyOFYcD0trid+31nRt4wtaA=";
} }
String signAct = si.getSignatureParts().iterator().next(). String signAct = si.getSignatureParts().iterator().next().
@ -721,24 +726,21 @@ public class TestSignatureInfo {
SignatureInfo si = new SignatureInfo(); SignatureInfo si = new SignatureInfo();
si.setSignatureConfig(signatureConfig); si.setSignatureConfig(signatureConfig);
Document document = DocumentHelper.createDocument(); final Document document = DocumentHelper.createDocument();
final DOMSignContext xmlSignContext = si.createXMLSignContext(document);
// operate // operate
DigestInfo digestInfo = si.preSign(document, null); final DOMSignedInfo signedInfo = si.preSign(xmlSignContext);
// verify // verify
assertNotNull(digestInfo); assertNotNull(signedInfo);
LOG.log(POILogger.DEBUG, "digest algo: " + digestInfo.hashAlgo); assertEquals("Office OpenXML Document", signatureConfig.getSignatureDescription());
LOG.log(POILogger.DEBUG, "digest description: " + digestInfo.description);
assertEquals("Office OpenXML Document", digestInfo.description);
assertNotNull(digestInfo.hashAlgo);
assertNotNull(digestInfo.digestValue);
// setup: key material, signature value // setup: key material, signature value
byte[] signatureValue = si.signDigest(digestInfo.digestValue); final String signatureValue = si.signDigest(xmlSignContext, signedInfo);
// operate: postSign // operate: postSign
si.postSign(document, signatureValue); si.postSign(xmlSignContext, signatureValue);
// verify: signature // verify: signature
si.getSignatureConfig().setOpcPackage(pkgCopy); si.getSignatureConfig().setOpcPackage(pkgCopy);

View File

@ -16,20 +16,46 @@
==================================================================== */ ==================================================================== */
package org.apache.poi.poifs.crypt.dsig; package org.apache.poi.poifs.crypt.dsig;
import static org.junit.Assert.assertEquals;
import java.io.IOException;
import javax.xml.bind.DatatypeConverter;
import org.apache.poi.poifs.crypt.HashAlgorithm; import org.apache.poi.poifs.crypt.HashAlgorithm;
import org.junit.Ignore; import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class TestSignatureConfig { public class TestSignatureConfig {
@Ignore("failing in automated builds, due to issues loading security classes")
@Test @Test
@Ignore("failing in automated builds, due to issues loading security classes")
public void testDigestAlgo() throws Exception { public void testDigestAlgo() throws Exception {
SignatureConfig sc = new SignatureConfig(); SignatureConfig sc = new SignatureConfig();
assertEquals(HashAlgorithm.sha256, sc.getDigestAlgo()); assertEquals(HashAlgorithm.sha256, sc.getDigestAlgo());
sc.setDigestAlgo(HashAlgorithm.sha1); sc.setDigestAlgo(HashAlgorithm.sha1);
assertEquals(HashAlgorithm.sha1, sc.getDigestAlgo()); assertEquals(HashAlgorithm.sha1, sc.getDigestAlgo());
} }
@Test
public void testHashOids() throws IOException {
final String[][] checks = {
{ "sha1", "MCEwCQYFKw4DAhoFAAQU" },
{ "sha224", "MC0wDQYJYIZIAWUDBAIEBQAEHA==" },
{ "sha256", "MDEwDQYJYIZIAWUDBAIBBQAEIA==" },
{ "sha384", "MEEwDQYJYIZIAWUDBAICBQAEMA==" },
{ "sha512", "MFEwDQYJYIZIAWUDBAIDBQAEQA==" },
{ "ripemd128", "MB0wCQYFKyQDAgIFAAQQ" },
{ "ripemd160", "MCEwCQYFKyQDAgEFAAQU" },
{ "ripemd256", "MC0wCQYFKyQDAgMFAAQg" },
};
for (final String[] check : checks) {
final HashAlgorithm ha = HashAlgorithm.valueOf(check[0]);
try (final DigestOutputStream dos = new DigestOutputStream(ha, null)) {
final String magic = DatatypeConverter.printBase64Binary(dos.getHashMagic());
assertEquals("hash digest magic mismatches", check[1], magic);
}
}
}
} }