#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:
parent
ff96f4c64d
commit
58a0a100f5
@ -20,55 +20,72 @@ package org.apache.poi.poifs.crypt;
|
||||
import org.apache.poi.EncryptedDocumentException;
|
||||
|
||||
public enum HashAlgorithm {
|
||||
none ( "", 0x0000, "", 0, "", false),
|
||||
sha1 ( "SHA-1", 0x8004, "SHA1", 20, "HmacSHA1", false),
|
||||
sha256 ( "SHA-256", 0x800C, "SHA256", 32, "HmacSHA256", false),
|
||||
sha384 ( "SHA-384", 0x800D, "SHA384", 48, "HmacSHA384", false),
|
||||
sha512 ( "SHA-512", 0x800E, "SHA512", 64, "HmacSHA512", false),
|
||||
none ( "", 0x0000, "", 0, "", false, ""),
|
||||
sha1 ( "SHA-1", 0x8004, "SHA1", 20, "HmacSHA1", false, "1.3.14.3.2.26"),
|
||||
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, "2.16.840.1.101.3.4.2.2"),
|
||||
sha512 ( "SHA-512", 0x800E, "SHA512", 64, "HmacSHA512", false, "2.16.840.1.101.3.4.2.3"),
|
||||
/* 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
|
||||
md2 ( "MD2", -1, "MD2", 16, "Hmac-MD2", true),
|
||||
md4 ( "MD4", -1, "MD4", 16, "Hmac-MD4", true),
|
||||
ripemd128("RipeMD128", -1, "RIPEMD-128", 16, "HMac-RipeMD128", true),
|
||||
ripemd160("RipeMD160", -1, "RIPEMD-160", 20, "HMac-RipeMD160", true),
|
||||
whirlpool("Whirlpool", -1, "WHIRLPOOL", 64, "HMac-Whirlpool", true),
|
||||
md2 ( "MD2", -1, "MD2", 16, "Hmac-MD2", true, "1.2.840.113549.2.2" ),
|
||||
md4 ( "MD4", -1, "MD4", 16, "Hmac-MD4", true, "1.2.840.113549.2.4" ),
|
||||
ripemd128("RipeMD128", -1, "RIPEMD-128", 16, "HMac-RipeMD128", true, "1.3.36.3.2.2"),
|
||||
ripemd160("RipeMD160", -1, "RIPEMD-160", 20, "HMac-RipeMD160", true, "1.3.36.3.2.1"),
|
||||
whirlpool("Whirlpool", -1, "WHIRLPOOL", 64, "HMac-Whirlpool", true, "1.0.10118.3.0.55"),
|
||||
// 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;
|
||||
/** the id used for the BIFF encryption info header **/
|
||||
public final int ecmaId;
|
||||
/** the id used for OOXML encryption info header **/
|
||||
public final String ecmaString;
|
||||
/** the length of the digest byte array **/
|
||||
public final int hashSize;
|
||||
/** the id used for the integrity algorithm in agile encryption **/
|
||||
public final String jceHmacId;
|
||||
/** is bouncycastle necessary for calculating the digest **/
|
||||
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.ecmaId = ecmaId;
|
||||
this.ecmaString = ecmaString;
|
||||
this.hashSize = hashSize;
|
||||
this.jceHmacId = jceHmacId;
|
||||
this.needsBouncyCastle = needsBouncyCastle;
|
||||
this.rsaOid = rsaOid;
|
||||
}
|
||||
|
||||
public static HashAlgorithm fromEcmaId(int ecmaId) {
|
||||
for (HashAlgorithm ha : values()) {
|
||||
if (ha.ecmaId == ecmaId) return ha;
|
||||
if (ha.ecmaId == ecmaId) {
|
||||
return ha;
|
||||
}
|
||||
}
|
||||
throw new EncryptedDocumentException("hash algorithm not found");
|
||||
}
|
||||
|
||||
public static HashAlgorithm fromEcmaId(String ecmaString) {
|
||||
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");
|
||||
}
|
||||
|
||||
public static HashAlgorithm fromString(String string) {
|
||||
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");
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -186,7 +186,9 @@ public class SignatureConfig {
|
||||
namespacePrefixes.put(XADES_132_NS, "xd");
|
||||
}
|
||||
|
||||
if (onlyValidation) return;
|
||||
if (onlyValidation) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (signatureMarshalListener == null) {
|
||||
signatureMarshalListener = new SignatureMarshalListener();
|
||||
@ -711,55 +713,6 @@ public class SignatureConfig {
|
||||
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
|
||||
* 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
|
||||
*/
|
||||
public static String getDigestMethodUri(HashAlgorithm digestAlgo) {
|
||||
@ -857,11 +813,15 @@ public class SignatureConfig {
|
||||
if (prov == null) {
|
||||
String dsigProviderNames[] = {
|
||||
System.getProperty("jsr105Provider"),
|
||||
"org.apache.jcp.xml.dsig.internal.dom.XMLDSigRI", // Santuario xmlsec
|
||||
"org.jcp.xml.dsig.internal.dom.XMLDSigRI" // JDK xmlsec
|
||||
// Santuario xmlsec
|
||||
"org.apache.jcp.xml.dsig.internal.dom.XMLDSigRI",
|
||||
// JDK xmlsec
|
||||
"org.jcp.xml.dsig.internal.dom.XMLDSigRI"
|
||||
};
|
||||
for (String pn : dsigProviderNames) {
|
||||
if (pn == null) continue;
|
||||
if (pn == null) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
prov = (Provider)Class.forName(pn).newInstance();
|
||||
break;
|
||||
|
@ -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.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.URIDereferencer;
|
||||
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.SignatureMethod;
|
||||
import javax.xml.crypto.dsig.SignedInfo;
|
||||
import javax.xml.crypto.dsig.TransformException;
|
||||
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.XMLSignatureFactory;
|
||||
import javax.xml.crypto.dsig.XMLValidateContext;
|
||||
import javax.xml.crypto.dsig.dom.DOMSignContext;
|
||||
import javax.xml.crypto.dsig.dom.DOMValidateContext;
|
||||
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.DOMSignedInfo;
|
||||
import org.apache.jcp.xml.dsig.internal.dom.DOMSubTreeData;
|
||||
import org.apache.poi.EncryptedDocumentException;
|
||||
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
|
||||
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.PackagingURIHelper;
|
||||
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.HashAlgorithm;
|
||||
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.services.RelationshipTransformService;
|
||||
@ -90,7 +78,7 @@ import org.apache.poi.util.POILogFactory;
|
||||
import org.apache.poi.util.POILogger;
|
||||
import org.apache.xml.security.Init;
|
||||
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.w3.x2000.x09.xmldsig.SignatureDocument;
|
||||
import org.w3c.dom.Document;
|
||||
@ -98,7 +86,6 @@ import org.w3c.dom.Element;
|
||||
import org.w3c.dom.NodeList;
|
||||
import org.w3c.dom.events.EventListener;
|
||||
import org.w3c.dom.events.EventTarget;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
|
||||
/**
|
||||
@ -175,119 +162,6 @@ public class SignatureInfo implements SignatureConfigurable {
|
||||
|
||||
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
|
||||
@ -306,6 +180,7 @@ public class SignatureInfo implements SignatureConfigurable {
|
||||
/**
|
||||
* @param signatureConfig the signature config, needs to be set before a SignatureInfo object is used
|
||||
*/
|
||||
@Override
|
||||
public void setSignatureConfig(SignatureConfig signatureConfig) {
|
||||
this.signatureConfig = signatureConfig;
|
||||
}
|
||||
@ -329,18 +204,31 @@ public class SignatureInfo implements SignatureConfigurable {
|
||||
* @throws MarshalException
|
||||
*/
|
||||
public void confirmSignature() throws XMLSignatureException, MarshalException {
|
||||
Document document = DocumentHelper.createDocument();
|
||||
final Document document = DocumentHelper.createDocument();
|
||||
final DOMSignContext xmlSignContext = createXMLSignContext(document);
|
||||
|
||||
// operate
|
||||
DigestInfo digestInfo = preSign(document, null);
|
||||
final DOMSignedInfo signedInfo = preSign(xmlSignContext);
|
||||
|
||||
// setup: key material, signature value
|
||||
byte[] signatureValue = signDigest(digestInfo.digestValue);
|
||||
final String signatureValue = signDigest(xmlSignContext, signedInfo);
|
||||
|
||||
// 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.
|
||||
* Currently only rsa is supported.
|
||||
@ -348,17 +236,36 @@ public class SignatureInfo implements SignatureConfigurable {
|
||||
* @param digest the hashed input
|
||||
* @return the encrypted hash
|
||||
*/
|
||||
public byte[] signDigest(byte digest[]) {
|
||||
Cipher cipher = CryptoFunctions.getCipher(signatureConfig.getKey(), CipherAlgorithm.rsa
|
||||
, ChainingMode.ecb, null, Cipher.ENCRYPT_MODE, "PKCS1Padding");
|
||||
public String signDigest(final DOMSignContext xmlSignContext, final DOMSignedInfo signedInfo) {
|
||||
final PrivateKey key = signatureConfig.getKey();
|
||||
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 {
|
||||
ByteArrayOutputStream digestInfoValueBuf = new ByteArrayOutputStream();
|
||||
digestInfoValueBuf.write(signatureConfig.getHashMagic());
|
||||
digestInfoValueBuf.write(digest);
|
||||
byte[] digestInfoValue = digestInfoValueBuf.toByteArray();
|
||||
return cipher.doFinal(digestInfoValue);
|
||||
} catch (Exception e) {
|
||||
final DigestOutputStream dos;
|
||||
switch (algo) {
|
||||
case md2: case md5: case sha1: case sha256: case sha384: case sha512:
|
||||
dos = new SignatureOutputStream(algo, key);
|
||||
break;
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -370,6 +277,7 @@ public class SignatureInfo implements SignatureConfigurable {
|
||||
public Iterable<SignaturePart> getSignatureParts() {
|
||||
signatureConfig.init(true);
|
||||
return new Iterable<SignaturePart>() {
|
||||
@Override
|
||||
public Iterator<SignaturePart> iterator() {
|
||||
return new Iterator<SignaturePart>() {
|
||||
OPCPackage pkg = signatureConfig.getOpcPackage();
|
||||
@ -378,9 +286,12 @@ public class SignatureInfo implements SignatureConfigurable {
|
||||
Iterator<PackageRelationship> sigRels;
|
||||
PackagePart sigPart;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
while (sigRels == null || !sigRels.hasNext()) {
|
||||
if (!sigOrigRels.hasNext()) return false;
|
||||
if (!sigOrigRels.hasNext()) {
|
||||
return false;
|
||||
}
|
||||
sigPart = pkg.getPart(sigOrigRels.next());
|
||||
LOG.log(POILogger.DEBUG, "Digital Signature Origin part", sigPart);
|
||||
try {
|
||||
@ -392,20 +303,24 @@ public class SignatureInfo implements SignatureConfigurable {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SignaturePart next() {
|
||||
PackagePart sigRelPart = null;
|
||||
do {
|
||||
try {
|
||||
if (!hasNext()) throw new NoSuchElementException();
|
||||
if (!hasNext()) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
sigRelPart = sigPart.getRelatedPart(sigRels.next());
|
||||
LOG.log(POILogger.DEBUG, "XML Signature part", sigRelPart);
|
||||
} catch (InvalidFormatException e) {
|
||||
LOG.log(POILogger.WARN, "Reference to signature is invalid.", e);
|
||||
}
|
||||
} while (sigPart == null);
|
||||
return new SignaturePart(sigRelPart);
|
||||
return new SignaturePart(sigRelPart, signatureConfig);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
@ -418,7 +333,9 @@ public class SignatureInfo implements SignatureConfigurable {
|
||||
* Initialize the xml signing environment and the bouncycastle provider
|
||||
*/
|
||||
protected static synchronized void initXmlProvider() {
|
||||
if (isInitialized) return;
|
||||
if (isInitialized) {
|
||||
return;
|
||||
}
|
||||
isInitialized = true;
|
||||
|
||||
try {
|
||||
@ -435,10 +352,12 @@ public class SignatureInfo implements SignatureConfigurable {
|
||||
* Normally {@link #confirmSignature()} is sufficient to be used.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public DigestInfo preSign(Document document, List<DigestInfo> digestInfos)
|
||||
public DOMSignedInfo preSign(final DOMSignContext xmlSignContext)
|
||||
throws XMLSignatureException, MarshalException {
|
||||
signatureConfig.init(false);
|
||||
|
||||
final Document document = (Document)xmlSignContext.getParent();
|
||||
|
||||
// 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 ...
|
||||
EventTarget target = (EventTarget)document;
|
||||
@ -453,7 +372,6 @@ public class SignatureInfo implements SignatureConfigurable {
|
||||
/*
|
||||
* Signature context construction.
|
||||
*/
|
||||
XMLSignContext xmlSignContext = new DOMSignContext(signatureConfig.getKey(), document);
|
||||
URIDereferencer uriDereferencer = signatureConfig.getUriDereferencer();
|
||||
if (null != uriDereferencer) {
|
||||
xmlSignContext.setURIDereferencer(uriDereferencer);
|
||||
@ -465,22 +383,12 @@ public class SignatureInfo implements SignatureConfigurable {
|
||||
xmlSignContext.setDefaultNamespacePrefix("");
|
||||
// signatureConfig.getNamespacePrefixes().get(XML_DIGSIG_NS));
|
||||
|
||||
brokenJvmWorkaround(xmlSignContext);
|
||||
|
||||
XMLSignatureFactory signatureFactory = signatureConfig.getSignatureFactory();
|
||||
|
||||
/*
|
||||
* Add ds:References that come from signing client local files.
|
||||
*/
|
||||
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.
|
||||
@ -528,11 +436,15 @@ public class SignatureInfo implements SignatureConfigurable {
|
||||
List<XMLStructure> objectContentList = object.getContent();
|
||||
for (XMLStructure objectContent : objectContentList) {
|
||||
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;
|
||||
List<Reference> manifestReferences = manifest.getReferences();
|
||||
for (Reference manifestReference : manifestReferences) {
|
||||
if (manifestReference.getDigestValue() != null) continue;
|
||||
if (manifestReference.getDigestValue() != null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
DOMReference manifestDOMReference = (DOMReference)manifestReference;
|
||||
manifestDOMReference.digest(xmlSignContext);
|
||||
@ -548,40 +460,26 @@ public class SignatureInfo implements SignatureConfigurable {
|
||||
DOMReference domReference = (DOMReference)signedInfoReference;
|
||||
|
||||
// ds:Reference with external digest value
|
||||
if (domReference.getDigestValue() != null) continue;
|
||||
if (domReference.getDigestValue() != null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
domReference.digest(xmlSignContext);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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);
|
||||
return (DOMSignedInfo)signedInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method for adding informations after the signing.
|
||||
* 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 {
|
||||
LOG.log(POILogger.DEBUG, "postSign");
|
||||
|
||||
final Document document = (Document)xmlSignContext.getParent();
|
||||
|
||||
/*
|
||||
* Check ds:Signature node.
|
||||
*/
|
||||
@ -593,11 +491,11 @@ public class SignatureInfo implements SignatureConfigurable {
|
||||
/*
|
||||
* Insert signature value into the ds:SignatureValue element
|
||||
*/
|
||||
NodeList sigValNl = document.getElementsByTagNameNS(XML_DIGSIG_NS, "SignatureValue");
|
||||
if (sigValNl.getLength() != 1) {
|
||||
final Element signatureNode = getDsigElement(document, "SignatureValue");
|
||||
if (signatureNode == null) {
|
||||
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.
|
||||
@ -671,30 +569,14 @@ public class SignatureInfo implements SignatureConfigurable {
|
||||
sigsPart.addRelationship(sigPartName, TargetMode.INTERNAL, PackageRelationshipTypes.DIGITAL_SIGNATURE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method for null lists, which are converted to empty lists
|
||||
*
|
||||
* @param other the reference to wrap, if null
|
||||
* @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 Element getDsigElement(final Document document, final String localName) {
|
||||
NodeList sigValNl = document.getElementsByTagNameNS(XML_DIGSIG_NS, localName);
|
||||
if (sigValNl.getLength() == 1) {
|
||||
return (Element)sigValNl.item(0);
|
||||
}
|
||||
|
||||
private void brokenJvmWorkaround(XMLSignContext context) {
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
LOG.log(POILogger.WARN, "Signature element '"+localName+"' was "+(sigValNl.getLength() == 0 ? "not found" : "multiple times"));
|
||||
|
||||
private void brokenJvmWorkaround(XMLValidateContext context) {
|
||||
// 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);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -24,14 +24,7 @@
|
||||
|
||||
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.MessageDigest;
|
||||
import java.security.PrivilegedAction;
|
||||
import java.security.Provider;
|
||||
import java.security.Security;
|
||||
import java.util.List;
|
||||
|
||||
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.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.poifs.crypt.dsig.SignatureConfig;
|
||||
import org.apache.poi.poifs.crypt.dsig.SignatureConfig.SignatureConfigurable;
|
||||
import org.apache.poi.util.POILogFactory;
|
||||
import org.apache.poi.util.POILogger;
|
||||
import org.apache.poi.util.SuppressForbidden;
|
||||
import org.w3c.dom.Document;
|
||||
|
||||
/**
|
||||
@ -71,6 +61,7 @@ public abstract class SignatureFacet implements SignatureConfigurable {
|
||||
|
||||
protected SignatureConfig signatureConfig;
|
||||
|
||||
@Override
|
||||
public void setSignatureConfig(SignatureConfig signatureConfig) {
|
||||
this.signatureConfig = signatureConfig;
|
||||
}
|
||||
@ -153,38 +144,7 @@ public abstract class SignatureFacet implements SignatureConfigurable {
|
||||
reference = sigFac.newReference(uri, digestMethod, transforms, type, id, digestValue);
|
||||
}
|
||||
|
||||
brokenJvmWorkaround(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
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -56,15 +56,17 @@ import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
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.POITestCase;
|
||||
import org.apache.poi.openxml4j.opc.OPCPackage;
|
||||
import org.apache.poi.openxml4j.opc.PackageAccess;
|
||||
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.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.KeyInfoSignatureFacet;
|
||||
import org.apache.poi.poifs.crypt.dsig.facets.XAdESSignatureFacet;
|
||||
@ -120,8 +122,6 @@ public class TestSignatureInfo {
|
||||
|
||||
cal = LocaleUtil.getLocaleCalendar(LocaleUtil.TIMEZONE_UTC);
|
||||
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
|
||||
// in the xmlsec jar file
|
||||
@ -129,6 +129,11 @@ public class TestSignatureInfo {
|
||||
//System.out.println("Having: " + additionalJar);
|
||||
Assume.assumeTrue("Not running TestSignatureInfo because we are testing with additionaljar set to " + additionalJar,
|
||||
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")
|
||||
@ -198,18 +203,18 @@ public class TestSignatureInfo {
|
||||
if (sep == null || "\n".equals(sep)) {
|
||||
// Unix
|
||||
signExp =
|
||||
"HDdvgXblLMiE6gZSoRSQUof6+aedrhK9i51we1n+4Q/ioqrQCeh5UkfQ8lD63nV4ZDbM4/pIVFi6VpMpN/HMnA"+
|
||||
"UHeVdVUCVTgpn3Iz21Ymcd9/aerNov2BjHLhS8X3oUE+XTu2TbJLNmms0I9G4lfg6HWP9t7ZCXBXy6vyCMArc=";
|
||||
"QkqTFQZjXagjRAoOWKpAGa8AR0rKqkSfBtfSWqtjBmTgyjarn+t2POHkpySIpheHAbg+90GKSH88ACMtPHbG7q"+
|
||||
"FL4gtgAD9Kjew6j16j0IRBwy145UlPrSLFMfF7YF7UlU1k1LBkIlRJ6Fv4MAJl6XspuzZOZIUmHZrWrdxycUQ=";
|
||||
} else if ("\r\n".equals(sep)){
|
||||
// Windows
|
||||
signExp =
|
||||
"jVW6EPMywZ8jr4+I4alDosXzqrVuDG4wTdrr+la8QVbXfLm6HOh9AUFlo5yUZuWo/1gXrrkc34UTYNzuslyrOx"+
|
||||
"KqadPOIRKUssJzdCh/hKeTxs/YtyWkpGHggrUjrF/vUUIeIXRHo+1DCAh6ptoicviH/I/Dtoa5NgkEHVuOHk8=";
|
||||
"GmAlL7+bT1r3FsMHJOp3pKg8betblYieZTjhMIrPZPRBbSzjO7KsYRGNtr0aOE3qr8xzyYJN6/8QdF5X7pUEUc"+
|
||||
"2m8ctrm7s5o2vZTkAqk9ENJGDjBPXX7TnuVOiVeL1cJdtjHC2QpjtRwkFR+B54G6b1OXLOFuQpP3vqR3+/XXE=";
|
||||
} else {
|
||||
// Mac
|
||||
signExp =
|
||||
"GSaOQp2eVRkQl2GJgWxoxFdCadJJnmmKeoQtIwGrP3zzk+BnLeytGLN3bqmwCTjvtG7DyxENS+92e2xq/MiC9b"+
|
||||
"CtNUfXfCdM0M8fzAny/Ewn9HckIsxjBztmsryt/OZQaKu52VU0ohQu7bG+cGPzcM+qTEss+GUbD0sVAoC34HM=";
|
||||
"NZedY/LNTYU4nAUEUhIOg5+fKdgVtzRXKmdD3v+47E7Mb84oeiUGv9cCEE91DU3StF/JFIhjOJqavOzKnCsNcz"+
|
||||
"NJ4j/inggUl1OJUsicqIGQnA7E8vzWnN1kf5lINgJLv+0PyrrX9sQZbItzxUpgqyOFYcD0trid+31nRt4wtaA=";
|
||||
}
|
||||
|
||||
String signAct = si.getSignatureParts().iterator().next().
|
||||
@ -721,24 +726,21 @@ public class TestSignatureInfo {
|
||||
SignatureInfo si = new SignatureInfo();
|
||||
si.setSignatureConfig(signatureConfig);
|
||||
|
||||
Document document = DocumentHelper.createDocument();
|
||||
final Document document = DocumentHelper.createDocument();
|
||||
final DOMSignContext xmlSignContext = si.createXMLSignContext(document);
|
||||
|
||||
// operate
|
||||
DigestInfo digestInfo = si.preSign(document, null);
|
||||
final DOMSignedInfo signedInfo = si.preSign(xmlSignContext);
|
||||
|
||||
// verify
|
||||
assertNotNull(digestInfo);
|
||||
LOG.log(POILogger.DEBUG, "digest algo: " + digestInfo.hashAlgo);
|
||||
LOG.log(POILogger.DEBUG, "digest description: " + digestInfo.description);
|
||||
assertEquals("Office OpenXML Document", digestInfo.description);
|
||||
assertNotNull(digestInfo.hashAlgo);
|
||||
assertNotNull(digestInfo.digestValue);
|
||||
assertNotNull(signedInfo);
|
||||
assertEquals("Office OpenXML Document", signatureConfig.getSignatureDescription());
|
||||
|
||||
// setup: key material, signature value
|
||||
byte[] signatureValue = si.signDigest(digestInfo.digestValue);
|
||||
final String signatureValue = si.signDigest(xmlSignContext, signedInfo);
|
||||
|
||||
// operate: postSign
|
||||
si.postSign(document, signatureValue);
|
||||
si.postSign(xmlSignContext, signatureValue);
|
||||
|
||||
// verify: signature
|
||||
si.getSignatureConfig().setOpcPackage(pkgCopy);
|
||||
|
@ -16,20 +16,46 @@
|
||||
==================================================================== */
|
||||
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.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class TestSignatureConfig {
|
||||
|
||||
@Ignore("failing in automated builds, due to issues loading security classes")
|
||||
@Test
|
||||
@Ignore("failing in automated builds, due to issues loading security classes")
|
||||
public void testDigestAlgo() throws Exception {
|
||||
SignatureConfig sc = new SignatureConfig();
|
||||
assertEquals(HashAlgorithm.sha256, sc.getDigestAlgo());
|
||||
sc.setDigestAlgo(HashAlgorithm.sha1);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user