poi/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureInfo.java

298 lines
13 KiB
Java

/* ====================================================================
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.ByteArrayOutputStream;
import java.io.IOException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.security.Security;
import java.security.cert.X509Certificate;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import javax.crypto.Cipher;
import javax.xml.crypto.dsig.XMLSignature;
import javax.xml.crypto.dsig.XMLSignatureFactory;
import javax.xml.crypto.dsig.dom.DOMValidateContext;
import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory;
import org.apache.poi.EncryptedDocumentException;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.openxml4j.opc.PackagePart;
import org.apache.poi.openxml4j.opc.PackageRelationship;
import org.apache.poi.openxml4j.opc.PackageRelationshipCollection;
import org.apache.poi.openxml4j.opc.PackageRelationshipTypes;
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.HorribleProxies.InitIf;
import org.apache.poi.poifs.crypt.dsig.services.RelationshipTransformService;
import org.apache.poi.poifs.crypt.dsig.services.XmlSignatureService;
import org.apache.poi.poifs.crypt.dsig.spi.DigestInfo;
import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger;
import org.apache.poi.util.SAXHelper;
import org.apache.xmlbeans.XmlCursor;
import org.apache.xmlbeans.XmlObject;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
public class SignatureInfo {
public static final byte[] SHA1_DIGEST_INFO_PREFIX = new byte[]
{ 0x30, 0x1f, 0x30, 0x07, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x04, 0x14 };
public static final byte[] SHA224_DIGEST_INFO_PREFIX = new byte[]
{ 0x30, 0x2b, 0x30, 0x0b, 0x06, 0x09, 0x60, (byte) 0x86
, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x04, 0x1c };
public static final byte[] SHA256_DIGEST_INFO_PREFIX = new byte[]
{ 0x30, 0x2f, 0x30, 0x0b, 0x06, 0x09, 0x60, (byte) 0x86
, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x04, 0x20 };
public static final byte[] SHA384_DIGEST_INFO_PREFIX = new byte[]
{ 0x30, 0x3f, 0x30, 0x0b, 0x06, 0x09, 0x60, (byte) 0x86
, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x04, 0x30 };
public static final byte[] SHA512_DIGEST_INFO_PREFIX = new byte[]
{ 0x30, 0x4f, 0x30, 0x0b, 0x06, 0x09, 0x60, (byte) 0x86
, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x04, 0x40 };
public static final byte[] RIPEMD128_DIGEST_INFO_PREFIX = new byte[]
{ 0x30, 0x1b, 0x30, 0x07, 0x06, 0x05, 0x2b, 0x24, 0x03, 0x02, 0x02, 0x04, 0x10 };
public static final byte[] RIPEMD160_DIGEST_INFO_PREFIX = new byte[]
{ 0x30, 0x1f, 0x30, 0x07, 0x06, 0x05, 0x2b, 0x24, 0x03, 0x02, 0x01, 0x04, 0x14 };
public static final byte[] RIPEMD256_DIGEST_INFO_PREFIX = new byte[]
{ 0x30, 0x2b, 0x30, 0x07, 0x06, 0x05, 0x2b, 0x24, 0x03, 0x02, 0x03, 0x04, 0x20 };
private static final POILogger LOG = POILogFactory.getLogger(SignatureInfo.class);
private static boolean isInitialized = false;
private final OPCPackage pkg;
public SignatureInfo(OPCPackage pkg) {
this.pkg = pkg;
}
public boolean verifySignature() {
initXmlProvider();
// http://www.oracle.com/technetwork/articles/javase/dig-signature-api-140772.html
List<X509Certificate> signers = new LinkedList<X509Certificate>();
return getSignersAndValidate(signers, true);
}
public void confirmSignature(Key key, X509Certificate x509)
throws NoSuchAlgorithmException, IOException {
confirmSignature(key, x509, HashAlgorithm.sha1);
}
public void confirmSignature(Key key, X509Certificate x509, HashAlgorithm hashAlgo)
throws NoSuchAlgorithmException, IOException {
XmlSignatureService signatureService = createSignatureService(hashAlgo, pkg);
// operate
List<X509Certificate> x509Chain = Collections.singletonList(x509);
DigestInfo digestInfo = signatureService.preSign(null, x509Chain, null, null, null);
// setup: key material, signature value
Cipher cipher = CryptoFunctions.getCipher(key, CipherAlgorithm.rsa
, ChainingMode.ecb, null, Cipher.ENCRYPT_MODE, "PKCS1Padding");
byte[] signatureValue;
try {
ByteArrayOutputStream digestInfoValueBuf = new ByteArrayOutputStream();
digestInfoValueBuf.write(getHashMagic(hashAlgo));
digestInfoValueBuf.write(digestInfo.digestValue);
byte[] digestInfoValue = digestInfoValueBuf.toByteArray();
signatureValue = cipher.doFinal(digestInfoValue);
} catch (Exception e) {
throw new EncryptedDocumentException(e);
}
// operate: postSign
signatureService.postSign(signatureValue, Collections.singletonList(x509));
}
public XmlSignatureService createSignatureService(HashAlgorithm hashAlgo, OPCPackage pkg) {
XmlSignatureService signatureService = new XmlSignatureService(hashAlgo, pkg);
signatureService.initFacets(new Date());
return signatureService;
}
public List<X509Certificate> getSigners() {
initXmlProvider();
List<X509Certificate> signers = new LinkedList<X509Certificate>();
getSignersAndValidate(signers, false);
return signers;
}
protected boolean getSignersAndValidate(List<X509Certificate> signers, boolean onlyFirst) {
boolean allValid = true;
List<PackagePart> signatureParts = getSignatureParts(onlyFirst);
if (signatureParts.isEmpty()) {
LOG.log(POILogger.DEBUG, "no signature resources");
allValid = false;
}
for (PackagePart signaturePart : signatureParts) {
KeyInfoKeySelector keySelector = new KeyInfoKeySelector();
try {
Document doc = SAXHelper.readSAXDocument(signaturePart.getInputStream());
// dummy call to createSignatureService to tweak document afterwards
createSignatureService(HashAlgorithm.sha1, pkg).registerIds(doc);
DOMValidateContext domValidateContext = new DOMValidateContext(keySelector, doc);
domValidateContext.setProperty("org.jcp.xml.dsig.validateManifests", Boolean.TRUE);
OOXMLURIDereferencer dereferencer = new OOXMLURIDereferencer(pkg);
domValidateContext.setURIDereferencer(dereferencer);
XMLSignatureFactory xmlSignatureFactory = getSignatureFactory();
XMLSignature xmlSignature = xmlSignatureFactory.unmarshalXMLSignature(domValidateContext);
boolean validity = xmlSignature.validate(domValidateContext);
allValid &= validity;
if (!validity) continue;
// TODO: check what has been signed.
} catch (Exception e) {
LOG.log(POILogger.ERROR, "error in marshalling and validating the signature", e);
continue;
}
X509Certificate signer = keySelector.getCertificate();
signers.add(signer);
}
return allValid;
}
protected List<PackagePart> getSignatureParts(boolean onlyFirst) {
List<PackagePart> packageParts = new LinkedList<PackagePart>();
PackageRelationshipCollection sigOrigRels = pkg.getRelationshipsByType(PackageRelationshipTypes.DIGITAL_SIGNATURE_ORIGIN);
for (PackageRelationship rel : sigOrigRels) {
PackagePart sigPart = pkg.getPart(rel);
LOG.log(POILogger.DEBUG, "Digital Signature Origin part", sigPart);
try {
PackageRelationshipCollection sigRels = sigPart.getRelationshipsByType(PackageRelationshipTypes.DIGITAL_SIGNATURE);
for (PackageRelationship sigRel : sigRels) {
PackagePart sigRelPart = sigPart.getRelatedPart(sigRel);
LOG.log(POILogger.DEBUG, "XML Signature part", sigRelPart);
packageParts.add(sigRelPart);
if (onlyFirst) break;
}
} catch (InvalidFormatException e) {
LOG.log(POILogger.WARN, "Reference to signature is invalid.", e);
}
if (onlyFirst && !packageParts.isEmpty()) break;
}
return packageParts;
}
public static XMLSignatureFactory getSignatureFactory() {
Provider p = Security.getProvider("XMLDSig");
assert(p != null);
return XMLSignatureFactory.getInstance("DOM", p);
}
public static KeyInfoFactory getKeyInfoFactory() {
Provider p = Security.getProvider("XMLDSig");
assert(p != null);
return KeyInfoFactory.getInstance("DOM", p);
}
public static void insertXChild(XmlObject root, XmlObject child) {
XmlCursor rootCursor = root.newCursor();
insertXChild(rootCursor, child);
rootCursor.dispose();
}
public static void insertXChild(XmlCursor rootCursor, XmlObject child) {
rootCursor.toEndToken();
XmlCursor childCursor = child.newCursor();
childCursor.toNextToken();
childCursor.moveXml(rootCursor);
childCursor.dispose();
}
public static void setPrefix(XmlObject xobj, String ns, String prefix) {
for (XmlCursor cur = xobj.newCursor(); cur.hasNextToken(); cur.toNextToken()) {
if (cur.isStart()) {
Element el = (Element)cur.getDomNode();
if (ns.equals(el.getNamespaceURI())) el.setPrefix(prefix);
}
}
}
protected static byte[] getHashMagic(HashAlgorithm hashAlgo) {
switch (hashAlgo) {
case sha1: return SHA1_DIGEST_INFO_PREFIX;
// sha224: return SHA224_DIGEST_INFO_PREFIX;
case sha256: return SHA256_DIGEST_INFO_PREFIX;
case sha384: return SHA384_DIGEST_INFO_PREFIX;
case sha512: return SHA512_DIGEST_INFO_PREFIX;
case ripemd128: return RIPEMD128_DIGEST_INFO_PREFIX;
case ripemd160: return RIPEMD160_DIGEST_INFO_PREFIX;
// case ripemd256: return RIPEMD256_DIGEST_INFO_PREFIX;
default: throw new EncryptedDocumentException("Hash algorithm "+hashAlgo+" not supported for signing.");
}
}
public static synchronized void initXmlProvider() {
if (isInitialized) return;
isInitialized = true;
try {
InitIf init = HorribleProxy.newProxy(InitIf.class);
init.init();
RelationshipTransformService.registerDsigProvider();
Provider bcProv = Security.getProvider("BC");
if (bcProv == null) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
Class<?> c = cl.loadClass("org.bouncycastle.jce.provider.BouncyCastleProvider");
bcProv = (Provider)c.newInstance();
Security.addProvider(bcProv);
}
} catch (Exception e) {
throw new RuntimeException("Xml & BouncyCastle-Provider initialization failed", e);
}
}
}