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");
}
+++ /dev/null
-/* ====================================================================
- 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;
-}
--- /dev/null
+/* ====================================================================
+ 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);
+ }
+ }
+}
namespacePrefixes.put(XADES_132_NS, "xd");
}
- if (onlyValidation) return;
+ if (onlyValidation) {
+ return;
+ }
if (signatureMarshalListener == null) {
signatureMarshalListener = new SignatureMarshalListener();
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
}
/**
- * @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) {
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;
/* ====================================================================
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;
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;
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;
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;
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();
* 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 & co are also valid
* // the keystore needs to contain a private key and it's certificate having a
* 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);
* // 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>
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
*/
/**
* @param signatureConfig the signature config, needs to be set before a SignatureInfo object is used
*/
+ @Override
public void setSignatureConfig(SignatureConfig signatureConfig) {
this.signatureConfig = signatureConfig;
}
* @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.
* @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.
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 {
}
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();
}
}
};
}
-
+
/**
- * 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();
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;
}
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);
}
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.
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);
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.
*/
/*
* 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.
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();
} 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);
} 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;
}
}
--- /dev/null
+/* ====================================================================
+ 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);
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+/* ====================================================================
+ 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);
+ }
+ }
+}
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;
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;
/**
protected SignatureConfig signatureConfig;
+ @Override
public void setSignatureConfig(SignatureConfig signatureConfig) {
this.signatureConfig = signatureConfig;
}
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
- }
- });
- }
- }
}
\ No newline at end of file
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;
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
//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")
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().
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);
==================================================================== */
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);
+ }
+ }
+ }
}