Pārlūkot izejas kodu

#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
pull/103/merge
Andreas Beeker pirms 6 gadiem
vecāks
revīzija
58a0a100f5

+ 35
- 18
src/java/org/apache/poi/poifs/crypt/HashAlgorithm.java Parādīt failu

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

public final String jceId;
public final int ecmaId;
/** 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");
}

+ 0
- 56
src/ooxml/java/org/apache/poi/poifs/crypt/dsig/DigestInfo.java Parādīt failu

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

+ 111
- 0
src/ooxml/java/org/apache/poi/poifs/crypt/dsig/DigestOutputStream.java Parādīt failu

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

+ 14
- 54
src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureConfig.java Parādīt failu

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

+ 141
- 259
src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureInfo.java Parādīt failu

@@ -18,16 +18,27 @@
/* ====================================================================
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
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 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,15 +86,14 @@ 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;


/**
* <p>This class is the default entry point for XML signatures and can be used for
* validating an existing signed office document and signing a office document.</p>
*
*
* <p><b>Validating a signed office document</b></p>
*
*
* <pre>
* OPCPackage pkg = OPCPackage.open(..., PackageAccess.READ);
* SignatureConfig sic = new SignatureConfig();
@@ -116,9 +103,9 @@ import org.xml.sax.SAXException;
* boolean isValid = si.validate();
* ...
* </pre>
*
*
* <p><b>Signing an office document</b></p>
*
*
* <pre>
* // loading the keystore - pkcs12 is used here, but of course jks &amp; co are also valid
* // the keystore needs to contain a private key and it's certificate having a
@@ -129,19 +116,19 @@ import org.xml.sax.SAXException;
* FileInputStream fis = new FileInputStream(file);
* keystore.load(fis, password);
* fis.close();
*
*
* // extracting private key and certificate
* String alias = "xyz"; // alias of the keystore entry
* Key key = keystore.getKey(alias, password);
* X509Certificate x509 = (X509Certificate)keystore.getCertificate(alias);
*
*
* // filling the SignatureConfig entries (minimum fields, more options are available ...)
* SignatureConfig signatureConfig = new SignatureConfig();
* signatureConfig.setKey(keyPair.getPrivate());
* signatureConfig.setSigningCertificateChain(Collections.singletonList(x509));
* OPCPackage pkg = OPCPackage.open(..., PackageAccess.READ_WRITE);
* signatureConfig.setOpcPackage(pkg);
*
*
* // adding the signature document to the package
* SignatureInfo si = new SignatureInfo();
* si.setSignatureConfig(signatureConfig);
@@ -152,14 +139,14 @@ import org.xml.sax.SAXException;
* // write the changes back to disc
* pkg.close();
* </pre>
*
*
* <p><b>Implementation notes:</b></p>
*
*
* <p>Although there's a XML signature implementation in the Oracle JDKs 6 and higher,
* compatibility with IBM JDKs is also in focus (... but maybe not thoroughly tested ...).
* Therefore we are using the Apache Santuario libs (xmlsec) instead of the built-in classes,
* as the compatibility seems to be provided there.</p>
*
*
* <p>To use SignatureInfo and its sibling classes, you'll need to have the following libs
* in the classpath:</p>
* <ul>
@@ -172,130 +159,17 @@ public class SignatureInfo implements SignatureConfigurable {

private static final POILogger LOG = POILogFactory.getLogger(SignatureInfo.class);
private static boolean isInitialized;
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
*/
public SignatureInfo() {
initXmlProvider();
initXmlProvider();
}

/**
* @return the signature config
*/
@@ -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,21 +236,40 @@ 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);
}
}
/**
* @return a signature part for each signature document.
* the parts can be validated independently.
@@ -370,17 +277,21 @@ 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();
Iterator<PackageRelationship> sigOrigRels =
Iterator<PackageRelationship> sigOrigRels =
pkg.getRelationshipsByType(PackageRelationshipTypes.DIGITAL_SIGNATURE_ORIGIN).iterator();
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 {
@@ -391,21 +302,25 @@ public class SignatureInfo implements SignatureConfigurable {
}
return true;
}

@Override
public SignaturePart next() {
PackagePart sigRelPart = null;
do {
try {
if (!hasNext()) throw new NoSuchElementException();
sigRelPart = sigPart.getRelatedPart(sigRels.next());
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();
}
@@ -413,14 +328,16 @@ 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() {
if (isInitialized) return;
if (isInitialized) {
return;
}
isInitialized = true;
try {
Init.init();
RelationshipTransformService.registerDsigProvider();
@@ -429,16 +346,18 @@ public class SignatureInfo implements SignatureConfigurable {
throw new RuntimeException("Xml & BouncyCastle-Provider initialization failed", e);
}
}
/**
* Helper method for adding informations before the signing.
* 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;
@@ -449,11 +368,10 @@ public class SignatureInfo implements SignatureConfigurable {
}
SignatureMarshalListener.setListener(target, creationListener, true);
}
/*
* Signature context construction.
*/
XMLSignContext xmlSignContext = new DOMSignContext(signatureConfig.getKey(), document);
URIDereferencer uriDereferencer = signatureConfig.getUriDereferencer();
if (null != uriDereferencer) {
xmlSignContext.setURIDereferencer(uriDereferencer);
@@ -464,23 +382,13 @@ 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.
@@ -620,7 +518,7 @@ public class SignatureInfo implements SignatureConfigurable {
Map<String,String> namespaceMap = new HashMap<>();
for(Map.Entry<String,String> entry : signatureConfig.getNamespacePrefixes().entrySet()){
namespaceMap.put(entry.getValue(), entry.getKey());
}
}
xo.setSaveSuggestedPrefixes(namespaceMap);
xo.setUseDefaultNamespace();

@@ -641,12 +539,12 @@ public class SignatureInfo implements SignatureConfigurable {
} catch (InvalidFormatException e) {
throw new MarshalException(e);
}
PackagePart sigPart = pkg.getPart(sigPartName);
if (sigPart == null) {
sigPart = pkg.createPart(sigPartName, ContentTypes.DIGITAL_SIGNATURE_XML_SIGNATURE_PART);
}
try {
OutputStream os = sigPart.getOutputStream();
SignatureDocument sigDoc = SignatureDocument.Factory.parse(document, DEFAULT_XML_OPTIONS);
@@ -655,46 +553,30 @@ public class SignatureInfo implements SignatureConfigurable {
} catch (Exception e) {
throw new MarshalException("Unable to write signature document", e);
}
PackagePart sigsPart = pkg.getPart(sigsPartName);
if (sigsPart == null) {
// touch empty marker file
sigsPart = pkg.createPart(sigsPartName, ContentTypes.DIGITAL_SIGNATURE_ORIGIN_PART);
}
PackageRelationshipCollection relCol = pkg.getRelationshipsByType(PackageRelationshipTypes.DIGITAL_SIGNATURE_ORIGIN);
for (PackageRelationship pr : relCol) {
pkg.removeRelationship(pr.getId());
}
pkg.addRelationship(sigsPartName, TargetMode.INTERNAL, PackageRelationshipTypes.DIGITAL_SIGNATURE_ORIGIN);
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 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);
}
}
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(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);
}
LOG.log(POILogger.WARN, "Signature element '"+localName+"' was "+(sigValNl.getLength() == 0 ? "not found" : "multiple times"));
return null;
}
}

+ 65
- 0
src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureOutputStream.java Parādīt failu

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

+ 149
- 0
src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignaturePart.java Parādīt failu

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

+ 1
- 41
src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/SignatureFacet.java Parādīt failu

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

+ 22
- 20
src/ooxml/testcases/org/apache/poi/poifs/crypt/TestSignatureInfo.java Parādīt failu

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

+ 29
- 3
src/ooxml/testcases/org/apache/poi/poifs/crypt/dsig/TestSignatureConfig.java Parādīt failu

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

Notiek ielāde…
Atcelt
Saglabāt