From: Andreas Beeker Date: Tue, 12 Aug 2014 23:33:07 +0000 (+0000) Subject: sync merge to trunk X-Git-Tag: REL_3_11_BETA3~73^2~26 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=4f4b1bedd7ea2386281a84a2dbbc16e4da764606;p=poi.git sync merge to trunk git-svn-id: https://svn.apache.org/repos/asf/poi/branches/xml_signature@1617624 13f79535-47bb-0310-9956-ffa450edef68 --- 4f4b1bedd7ea2386281a84a2dbbc16e4da764606 diff --cc .classpath index 8df184abf0,a7dc0d8fc5..bcce3b2c0d --- a/.classpath +++ b/.classpath @@@ -1,30 -1,29 +1,30 @@@ -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - -- -- -- -- - - -- -- -- ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ diff --cc .settings/org.sonar.ide.eclipse.core.prefs index 0000000000,0000000000..80551be6b1 new file mode 100644 --- /dev/null +++ b/.settings/org.sonar.ide.eclipse.core.prefs @@@ -1,0 -1,0 +1,6 @@@ ++eclipse.preferences.version=1 ++extraProperties= ++lastAnalysisDate=1394235544000 ++projectKey=org.apache.poi\:poi-main ++serverUrl=http\://nemo.sonarqube.org ++version=2 diff --cc build.xml index b2d15a9a2c,3dbf0a81b8..4423a7833d --- a/build.xml +++ b/build.xml @@@ -1465,9 -1436,8 +1454,8 @@@ under the License - + - diff --cc src/ooxml/java/org/apache/poi/poifs/crypt/dsig/HorribleProxies.java index 54a5aad325,0000000000..96395b3d08 mode 100644,000000..100644 --- a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/HorribleProxies.java +++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/HorribleProxies.java @@@ -1,375 -1,0 +1,376 @@@ +package org.apache.poi.poifs.crypt.dsig; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.math.BigInteger; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.cert.Certificate; +import java.security.cert.X509CRL; +import java.security.cert.X509Certificate; +import java.util.Collection; +import java.util.Date; + +import javax.security.auth.x500.X500Principal; +import javax.xml.crypto.MarshalException; +import javax.xml.crypto.XMLCryptoContext; +import javax.xml.crypto.dom.DOMCryptoContext; +import javax.xml.crypto.dsig.XMLSignContext; +import javax.xml.crypto.dsig.XMLSignatureException; + +import org.apache.poi.poifs.crypt.dsig.HorribleProxy.ProxyIf; +import org.w3c.dom.Node; + +public interface HorribleProxies { + public static final String xmlSecBase = "org.jcp.xml.dsig.internal.dom"; + // public static final String xmlSecBase = "org.apache.jcp.xml.dsig.internal.dom"; + + public interface ASN1InputStreamIf extends ProxyIf { + String delegateClass = "org.bouncycastle.asn1.ASN1InputStream"; + + ASN1OctetStringIf readObject$ASNString() throws IOException; + DEROctetStringIf readObject$DERString() throws IOException; + DERIntegerIf readObject$Integer() throws IOException; + ASN1SequenceIf readObject$Sequence() throws IOException; + Object readObject$Object() throws IOException; + } + + public interface ASN1ObjectIdentifierIf extends ProxyIf { + String delegateClass = "org.bouncycastle.asn1.ASN1ObjectIdentifier"; + } + + public interface ASN1OctetStringIf extends ProxyIf { + String delegateClass = "org.bouncycastle.asn1.ASN1OctetString"; + byte[] getOctets(); + } + + public interface ASN1SequenceIf extends ProxyIf { + String delegateClass = "org.bouncycastle.asn1.ASN1Sequence"; + } + + public interface AuthorityInformationAccessIf extends ProxyIf { + String delegateClass = "org.bouncycastle.asn1.x509.AuthorityInformationAccess"; + } + + public interface AuthorityKeyIdentifierIf extends ProxyIf { + String delegateClass = "org.bouncycastle.asn1.x509.AuthorityKeyIdentifier"; + byte[] getKeyIdentifier(); + } + + public interface BasicConstraintsIf extends ProxyIf { + String delegateClass = "org.bouncycastle.asn1.x509.BasicConstraints"; + } + + public interface BasicOCSPRespIf extends ProxyIf { + String delegateClass = "org.bouncycastle.cert.ocsp.BasicOCSPResp"; + Date getProducedAt(); + RespIDIf getResponderId(); + } + + public interface BcDigestCalculatorProviderIf extends ProxyIf { + String delegateClass = "org.bouncycastle.operator.bc.BcDigestCalculatorProvider"; + } + + public interface BcRSASignerInfoVerifierBuilderIf extends ProxyIf { + String delegateClass = "org.bouncycastle.cms.bc.BcRSASignerInfoVerifierBuilder"; + SignerInformationVerifierIf build(X509CertificateHolderIf holder); + } + + public interface CanonicalizerIf extends ProxyIf { + String delegateClass = "com.sun.org.apache.xml.internal.security.c14n.Canonicalizer"; + byte[] canonicalizeSubtree(Node node) throws Exception; + } + + public interface CRLNumberIf extends ProxyIf { + String delegateClass = "org.bouncycastle.asn1.x509.CRLNumber"; + } + + public interface DefaultDigestAlgorithmIdentifierFinderIf extends ProxyIf { + String delegateClass = "org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder"; + } + + public interface DistributionPointNameIf extends ProxyIf { + String delegateClass = "org.bouncycastle.asn1.x509.DistributionPointName"; + } + + public interface DistributionPointIf extends ProxyIf { + String delegateClass = "org.bouncycastle.asn1.x509.DistributionPoint"; + } + + public interface DERIA5StringIf extends ProxyIf { + String delegateClass = "org.bouncycastle.asn1.DERIA5String"; + } + + public interface DERIntegerIf extends ProxyIf { + String delegateClass = "org.bouncycastle.asn1.DERInteger"; + BigInteger getPositiveValue(); + } + + public interface DEROctetStringIf extends ProxyIf { + String delegateClass = "org.bouncycastle.asn1.DEROctetString"; + byte[] getOctets(); + } + + public interface DERTaggedObjectIf extends ProxyIf { + String delegateClass = "org.bouncycastle.asn1.DERTaggedObject"; + int getTagNo(); + ASN1OctetStringIf getObject$String(); + Object getObject$Object(); + } + + public interface DERSequenceIf extends ProxyIf { + String delegateClass = "org.bouncycastle.asn1.DERSequence"; + } + + public interface DOMKeyInfoIf extends ProxyIf { + String delegateClass = xmlSecBase+".DOMKeyInfo"; + void marshal(Node parent, Node nextSibling, String dsPrefix, DOMCryptoContext context) throws MarshalException; + } + + public interface DOMReferenceIf extends ProxyIf { + String delegateClass = xmlSecBase+".DOMReference"; + void digest(XMLSignContext paramXMLSignContext) throws XMLSignatureException; + byte[] getDigestValue(); + } + + public interface DOMSignedInfoIf extends ProxyIf { + String delegateClass = xmlSecBase+".DOMSignedInfo"; + void canonicalize(XMLCryptoContext paramXMLCryptoContext, ByteArrayOutputStream paramByteArrayOutputStream); + } + + public interface XMLSignatureIf extends ProxyIf { + String delegateClass = "com.sun.org.apache.xml.internal.security.signature.XMLSignature"; + String ALGO_ID_SIGNATURE_RSA_SHA1(); + String ALGO_ID_SIGNATURE_RSA_SHA256(); + String ALGO_ID_SIGNATURE_RSA_SHA384(); + String ALGO_ID_SIGNATURE_RSA_SHA512(); + String ALGO_ID_MAC_HMAC_RIPEMD160(); + } + + public interface DOMXMLSignatureIf extends ProxyIf { + String delegateClass = xmlSecBase+".DOMXMLSignature"; + void marshal(Node node, String prefix, DOMCryptoContext context) throws MarshalException; + } + + public interface GeneralNameIf extends ProxyIf { + String delegateClass = "org.bouncycastle.asn1.x509.GeneralName"; + + int uniformResourceIdentifier(); + + } + + public interface GeneralNamesIf extends ProxyIf { + String delegateClass = "org.bouncycastle.asn1.x509.GeneralNames"; + } + + public interface InitIf extends ProxyIf { + String delegateClass = "com.sun.org.apache.xml.internal.security.Init"; + void init(); + } + + public interface KeyUsageIf extends ProxyIf { + String delegateClass = "org.bouncycastle.asn1.x509.KeyUsage"; + int digitalSignature(); + } + + public interface OCSPRespIf extends ProxyIf { + String delegateClass = "org.bouncycastle.cert.ocsp.OCSPResp"; + BasicOCSPRespIf getResponseObject(); ++ byte[] getEncoded() throws IOException; + } + + public interface PKIFailureInfoIf extends ProxyIf { + String delegateClass = "org.bouncycastle.asn1.cmp.PKIFailureInfo"; + int intValue(); + } + + public interface RespIDIf extends ProxyIf { + String delegateClass = "org.bouncycastle.cert.ocsp.RespID"; + ResponderIDIf toASN1Object(); + } + + public interface ResponderIDIf extends ProxyIf { + String delegateClass = "org.bouncycastle.asn1.ocsp.ResponderID"; + DERTaggedObjectIf toASN1Object(); + } + + public interface SignerIdIf extends ProxyIf { + String delegateClass = "org.bouncycastle.cms.SignerId"; + BigInteger getSerialNumber(); + X500Principal getIssuer(); + } + + public interface SignerInformationVerifierIf extends ProxyIf { + String delegateClass = "org.bouncycastle.cms.SignerInformationVerifier"; + } + + public interface StoreIf extends ProxyIf { + String delegateClass = "org.bouncycastle.util.Store"; + Collection getMatches(Object selector) throws Exception; + } + + public interface SubjectKeyIdentifierIf extends ProxyIf { + String delegateClass = "org.bouncycastle.asn1.x509.SubjectKeyIdentifier"; + byte[] getKeyIdentifier(); + } + + public interface SubjectPublicKeyInfoIf extends ProxyIf { + String delegateClass = "org.bouncycastle.asn1.x509.SubjectPublicKeyInfo"; + } + + public interface TimeStampRequestGeneratorIf extends ProxyIf { + String delegateClass = "org.bouncycastle.tsp.TimeStampRequestGenerator"; + void setCertReq(boolean certReq); + void setReqPolicy(String reqPolicy); + TimeStampRequestIf generate(String igestAlgorithmOID, byte[] digest, BigInteger nonce); + } + + public interface TimeStampRequestIf extends ProxyIf { + String delegateClass = "org.bouncycastle.tsp.TimeStampRequest"; + byte[] getEncoded() throws IOException; + } + + public interface TimeStampResponseIf extends ProxyIf { + String delegateClass = "org.bouncycastle.tsp.TimeStampResponse"; + void validate(TimeStampRequestIf request) throws Exception; + int getStatus(); + String getStatusString(); + PKIFailureInfoIf getFailInfo(); + TimeStampTokenIf getTimeStampToken(); + } + + public interface TimeStampTokenIf extends ProxyIf { + String delegateClass = "org.bouncycastle.tsp.TimeStampToken"; + SignerIdIf getSID(); + StoreIf getCertificates(); + StoreIf getCRLs(); + TimeStampTokenInfoIf getTimeStampInfo(); + byte[] getEncoded() throws IOException; + void validate(SignerInformationVerifierIf verifier) throws Exception; + } + + public interface TimeStampTokenInfoIf extends ProxyIf { + String delegateClass = "org.bouncycastle.tsp.TimeStampTokenInfo"; + Date getGenTime(); + } + + public interface X509CertificateHolderIf extends ProxyIf { + String delegateClass = "org.bouncycastle.cert.X509CertificateHolder"; + } + + public interface X509NameIf extends ProxyIf { + String delegateClass = "org.bouncycastle.asn1.x509.X509Name"; + String toString$delegate(); + } + + public interface X509PrincipalIf extends ProxyIf { + String delegateClass = "org.bouncycastle.jce.X509Principal"; + String getName(); + } + + public interface X509V3CertificateGeneratorIf extends ProxyIf { + String delegateClass = "org.bouncycastle.x509.X509V3CertificateGenerator"; + + void reset(); + void setPublicKey(PublicKey key); + void setSignatureAlgorithm(String signatureAlgorithm); + void setNotBefore(Date date); + void setNotAfter(Date date); + void setIssuerDN(X509PrincipalIf issuerDN); + void setSubjectDN(X509PrincipalIf issuerDN); + void setSerialNumber(BigInteger serialNumber); + + void addExtension(ASN1ObjectIdentifierIf oid, boolean critical, SubjectKeyIdentifierIf value); + void addExtension(ASN1ObjectIdentifierIf oid, boolean critical, AuthorityKeyIdentifierIf value); + void addExtension(ASN1ObjectIdentifierIf oid, boolean critical, BasicConstraintsIf value); + void addExtension(ASN1ObjectIdentifierIf oid, boolean critical, DERSequenceIf value); + void addExtension(ASN1ObjectIdentifierIf oid, boolean critical, AuthorityInformationAccessIf value); + void addExtension(ASN1ObjectIdentifierIf oid, boolean critical, KeyUsageIf value); + + X509Certificate generate(PrivateKey issuerPrivateKey); + } + + public interface OCSPReqIf extends ProxyIf { + String delegateClass = "org.bouncycastle.cert.ocsp.OCSPReq"; + + ReqIf[] getRequestList(); + } + + public interface OCSPReqGeneratorIf extends ProxyIf { + String delegateClass = "org.bouncycastle.ocsp.OCSPReqGenerator"; + + void addRequest(CertificateIDIf certId); + OCSPReqIf generate(); + } + + public interface BasicOCSPRespGeneratorIf extends ProxyIf { + String delegateClass = "org.bouncycastle.ocsp.BasicOCSPRespGenerator"; + + void addResponse(CertificateIDIf certificateID, CertificateStatusIf certificateStatus); + BasicOCSPRespIf generate(String signatureAlgorithm, PrivateKey ocspResponderPrivateKey, + X509Certificate chain[], Date date, String provider); + } + + public interface CertificateIDIf extends ProxyIf { + String delegateClass = "org.bouncycastle.cert.ocsp.CertificateID"; + + String HASH_SHA1(); + } + + public interface X509ExtensionsIf extends ProxyIf { + String delegateClass = "org.bouncycastle.asn1.x509.X509Extensions"; + + ASN1ObjectIdentifierIf AuthorityKeyIdentifier(); + ASN1ObjectIdentifierIf SubjectKeyIdentifier(); + ASN1ObjectIdentifierIf BasicConstraints(); + ASN1ObjectIdentifierIf CRLDistributionPoints(); + ASN1ObjectIdentifierIf AuthorityInfoAccess(); + ASN1ObjectIdentifierIf KeyUsage(); + ASN1ObjectIdentifierIf CRLNumber(); + } + + public interface X509ObjectIdentifiersIf extends ProxyIf { + String delegateClass = "org.bouncycastle.asn1.x509.X509ObjectIdentifiers"; + + ASN1ObjectIdentifierIf ocspAccessMethod(); + } + + public interface X509V2CRLGeneratorIf extends ProxyIf { + String delegateClass = "org.bouncycastle.asn1.x509.X509V2CRLGenerator"; + + void setIssuerDN(X500Principal issuerDN); + void setThisUpdate(Date date); + void setNextUpdate(Date date); + void setSignatureAlgorithm(String algorithm); + + void addExtension(ASN1ObjectIdentifierIf oid, boolean critical, CRLNumberIf value); + X509CRL generate(PrivateKey privateKey); + } + + public interface ReqIf extends ProxyIf { + String delegateClass = "org.bouncycastle.cert.ocsp.Req"; + + CertificateIDIf getCertID(); + } + + public interface CertificateStatusIf extends ProxyIf { + String delegateClass = "org.bouncycastle.cert.ocsp.CertificateStatus"; + + CertificateStatusIf GOOD(); + } + + public interface RevokedStatusIf extends ProxyIf { + String delegateClass = "org.bouncycastle.cert.ocsp.RevokedStatus"; + } + + public interface CRLReasonIf extends ProxyIf { + String delegateClass = "org.bouncycastle.asn1.x509.CRLReason"; + int unspecified(); + } + + public interface OCSPRespGeneratorIf extends ProxyIf { + String delegateClass = "org.bouncycastle.ocsp.OCSPRespGenerator"; + int SUCCESSFUL(); + OCSPRespIf generate(int status, BasicOCSPRespIf basicOCSPResp); + } +} diff --cc src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureInfo.java index 4dbfa5474a,0000000000..142d56bc0a mode 100644,000000..100644 --- a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureInfo.java +++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureInfo.java @@@ -1,297 -1,0 +1,297 @@@ +/* ==================================================================== + 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 signers = new LinkedList(); + 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 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 getSigners() { + initXmlProvider(); + List signers = new LinkedList(); + getSignersAndValidate(signers, false); + return signers; + } + + protected boolean getSignersAndValidate(List signers, boolean onlyFirst) { + boolean allValid = true; + List 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.readSAXDocumentW3C(signaturePart.getInputStream()); ++ 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 getSignatureParts(boolean onlyFirst) { + List packageParts = new LinkedList(); + + 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); + } + } +} diff --cc src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/EnvelopedSignatureFacet.java index 0000000000,0000000000..4f4f9af7ad new file mode 100644 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/EnvelopedSignatureFacet.java @@@ -1,0 -1,0 +1,84 @@@ ++package org.apache.poi.poifs.crypt.dsig.facets; ++ ++import java.security.InvalidAlgorithmParameterException; ++import java.security.NoSuchAlgorithmException; ++import java.security.cert.X509Certificate; ++import java.util.LinkedList; ++import java.util.List; ++import java.util.Map; ++ ++import javax.xml.crypto.dsig.CanonicalizationMethod; ++import javax.xml.crypto.dsig.DigestMethod; ++import javax.xml.crypto.dsig.Reference; ++import javax.xml.crypto.dsig.Transform; ++import javax.xml.crypto.dsig.XMLObject; ++import javax.xml.crypto.dsig.XMLSignatureFactory; ++import javax.xml.crypto.dsig.spec.TransformParameterSpec; ++ ++import org.apache.poi.poifs.crypt.HashAlgorithm; ++import org.w3.x2000.x09.xmldsig.SignatureType; ++ ++/** ++ * Signature Facet implementation to create enveloped signatures. ++ * ++ * @author Frank Cornelis ++ * ++ */ ++public class EnvelopedSignatureFacet implements SignatureFacet { ++ ++ private final HashAlgorithm hashAlgo; ++ ++ /** ++ * Default constructor. Digest algorithm will be SHA-1. ++ */ ++ public EnvelopedSignatureFacet() { ++ this(HashAlgorithm.sha1); ++ } ++ ++ /** ++ * Main constructor. ++ * ++ * @param hashAlgo ++ * the digest algorithm to be used within the ds:Reference ++ * element. Possible values: "SHA-1", "SHA-256, or "SHA-512". ++ */ ++ public EnvelopedSignatureFacet(HashAlgorithm hashAlgo) { ++ this.hashAlgo = hashAlgo; ++ } ++ ++ @Override ++ public void postSign(SignatureType signatureElement ++ , List signingCertificateChain) { ++ // empty ++ } ++ ++ @Override ++ public void preSign(XMLSignatureFactory signatureFactory, ++ String signatureId, ++ List signingCertificateChain, ++ List references, List objects) ++ throws NoSuchAlgorithmException, InvalidAlgorithmParameterException { ++ DigestMethod digestMethod = signatureFactory.newDigestMethod( ++ this.hashAlgo.xmlSignUri, null); ++ ++ List transforms = new LinkedList(); ++ Transform envelopedTransform = signatureFactory ++ .newTransform(CanonicalizationMethod.ENVELOPED, ++ (TransformParameterSpec) null); ++ transforms.add(envelopedTransform); ++ Transform exclusiveTransform = signatureFactory ++ .newTransform(CanonicalizationMethod.EXCLUSIVE, ++ (TransformParameterSpec) null); ++ transforms.add(exclusiveTransform); ++ ++ Reference reference = signatureFactory.newReference("", digestMethod, ++ transforms, null, null); ++ ++ references.add(reference); ++ } ++ ++ @Override ++ public Map getNamespacePrefixMapping() { ++ return null; ++ } ++} diff --cc src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/XmlSignatureService.java index c09501a4a1,0000000000..bea7653430 mode 100644,000000..100644 --- a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/XmlSignatureService.java +++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/XmlSignatureService.java @@@ -1,610 -1,0 +1,612 @@@ +/* ==================================================================== + 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.services; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.net.MalformedURLException; +import java.security.InvalidAlgorithmParameterException; +import java.security.Key; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.cert.X509Certificate; +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import javax.xml.crypto.MarshalException; +import javax.xml.crypto.URIDereferencer; +import javax.xml.crypto.XMLStructure; +import javax.xml.crypto.dom.DOMCryptoContext; +import javax.xml.crypto.dsig.CanonicalizationMethod; +import javax.xml.crypto.dsig.DigestMethod; +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.XMLObject; +import javax.xml.crypto.dsig.XMLSignContext; +import javax.xml.crypto.dsig.XMLSignatureFactory; +import javax.xml.crypto.dsig.dom.DOMSignContext; +import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactoryConfigurationError; + +import org.apache.poi.openxml4j.exceptions.InvalidFormatException; +import org.apache.poi.openxml4j.opc.OPCPackage; +import org.apache.poi.openxml4j.opc.PackageNamespaces; +import org.apache.poi.openxml4j.opc.PackagePart; +import org.apache.poi.openxml4j.opc.PackagePartName; +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.openxml4j.opc.PackagingURIHelper; +import org.apache.poi.openxml4j.opc.TargetMode; +import org.apache.poi.poifs.crypt.CryptoFunctions; +import org.apache.poi.poifs.crypt.HashAlgorithm; +import org.apache.poi.poifs.crypt.dsig.HorribleProxies.DOMReferenceIf; +import org.apache.poi.poifs.crypt.dsig.HorribleProxies.DOMSignedInfoIf; +import org.apache.poi.poifs.crypt.dsig.HorribleProxies.DOMXMLSignatureIf; +import org.apache.poi.poifs.crypt.dsig.HorribleProxies.XMLSignatureIf; +import org.apache.poi.poifs.crypt.dsig.HorribleProxy; +import org.apache.poi.poifs.crypt.dsig.OOXMLURIDereferencer; +import org.apache.poi.poifs.crypt.dsig.SignatureInfo; +import org.apache.poi.poifs.crypt.dsig.facets.KeyInfoSignatureFacet; +import org.apache.poi.poifs.crypt.dsig.facets.OOXMLSignatureFacet; +import org.apache.poi.poifs.crypt.dsig.facets.Office2010SignatureFacet; +import org.apache.poi.poifs.crypt.dsig.facets.SignatureFacet; +import org.apache.poi.poifs.crypt.dsig.facets.XAdESSignatureFacet; +import org.apache.poi.poifs.crypt.dsig.spi.AddressDTO; +import org.apache.poi.poifs.crypt.dsig.spi.Constants; +import org.apache.poi.poifs.crypt.dsig.spi.DigestInfo; +import org.apache.poi.poifs.crypt.dsig.spi.IdentityDTO; +import org.apache.poi.util.POILogFactory; +import org.apache.poi.util.POILogger; +import org.apache.xmlbeans.XmlException; +import org.apache.xmlbeans.XmlOptions; +import org.w3.x2000.x09.xmldsig.SignatureDocument; +import org.w3.x2000.x09.xmldsig.SignatureType; +import org.w3.x2000.x09.xmldsig.SignatureValueType; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + + +/** + * Abstract base class for an XML Signature Service implementation. + */ +public class XmlSignatureService implements SignatureService { + private static final POILogger LOG = POILogFactory.getLogger(XmlSignatureService.class); + + protected final List signatureFacets; + + private String signatureNamespacePrefix; + private String signatureId; + private final HashAlgorithm hashAlgo; + private final OPCPackage opcPackage; + private SignatureDocument sigDoc; + private XAdESSignatureFacet xadesSignatureFacet; + + /** + * Main constructor. + */ + public XmlSignatureService(HashAlgorithm digestAlgo, OPCPackage opcPackage) { + this.signatureFacets = new LinkedList(); + this.signatureNamespacePrefix = null; + this.signatureId = null; + this.hashAlgo = digestAlgo; + this.opcPackage = opcPackage; + this.sigDoc = null; + } + + public void initFacets(Date clock) { + if (clock == null) clock = new Date(); + addSignatureFacet(new OOXMLSignatureFacet(this, clock, hashAlgo)); + addSignatureFacet(new KeyInfoSignatureFacet(true, false, false)); + + this.xadesSignatureFacet = new XAdESSignatureFacet(clock, hashAlgo, null); + this.xadesSignatureFacet.setIdSignedProperties("idSignedProperties"); + this.xadesSignatureFacet.setSignaturePolicyImplied(true); + /* + * Work-around for Office 2010. + */ + this.xadesSignatureFacet.setIssuerNameNoReverseOrder(true); + setSignatureId("idPackageSignature"); + addSignatureFacet(this.xadesSignatureFacet); + addSignatureFacet(new Office2010SignatureFacet()); + } + + + /** + * Sets the signature Id attribute value used to create the XML signature. A + * null value will trigger an automatically generated signature + * Id. + * + * @param signatureId + */ + protected void setSignatureId(String signatureId) { + this.signatureId = signatureId; + } + + /** + * Sets the XML Signature namespace prefix to be used for signature + * creation. A null value will omit the prefixing. + * + * @param signatureNamespacePrefix + */ + protected void setSignatureNamespacePrefix(String signatureNamespacePrefix) { + this.signatureNamespacePrefix = signatureNamespacePrefix; + } + + /** + * Adds a signature facet to this XML signature service. + * + * @param signatureFacet + */ - protected void addSignatureFacet(SignatureFacet signatureFacet) { - this.signatureFacets.add(signatureFacet); ++ public void addSignatureFacet(SignatureFacet... signatureFacets) { ++ for (SignatureFacet sf : signatureFacets) { ++ this.signatureFacets.add(sf); ++ } + } + + /** + * Gives back the signature digest algorithm. Allowed values are SHA-1, + * SHA-256, SHA-384, SHA-512, RIPEND160. The default algorithm is SHA-1. + * Override this method to select another signature digest algorithm. + * + * @return + */ + protected HashAlgorithm getSignatureDigestAlgorithm() { + return null != this.hashAlgo ? this.hashAlgo : HashAlgorithm.sha1; + } + + /** + * Override this method to change the URI dereferener used by the signing + * engine. + * + * @return + */ + protected URIDereferencer getURIDereferencer() { + OPCPackage ooxmlDocument = getOfficeOpenXMLDocument(); + return new OOXMLURIDereferencer(ooxmlDocument); + } + + /** + * Gives back the human-readable description of what the citizen will be + * signing. The default value is "XML Document". Override this method to + * provide the citizen with another description. + * + * @return + */ + protected String getSignatureDescription() { + return "Office OpenXML Document"; + } + + /** + * Gives back the URL of the OOXML to be signed. + * + * @return + */ + public OPCPackage getOfficeOpenXMLDocument() { + return opcPackage; + } + + + + /** + * Gives back the output stream to which to write the signed XML document. + * + * @return + */ + // protected abstract OutputStream getSignedDocumentOutputStream(); + + public DigestInfo preSign(List digestInfos, + List signingCertificateChain, + IdentityDTO identity, AddressDTO address, byte[] photo) + throws NoSuchAlgorithmException { + SignatureInfo.initXmlProvider(); + + LOG.log(POILogger.DEBUG, "preSign"); + HashAlgorithm hashAlgo = getSignatureDigestAlgorithm(); + + byte[] digestValue; + try { + digestValue = getXmlSignatureDigestValue(hashAlgo, digestInfos, signingCertificateChain); + } catch (Exception e) { + throw new RuntimeException("XML signature error: " + e.getMessage(), e); + } + + String description = getSignatureDescription(); + return new DigestInfo(digestValue, hashAlgo, description); + } + + public void postSign(byte[] signatureValue, List signingCertificateChain) + throws IOException { + LOG.log(POILogger.DEBUG, "postSign"); + SignatureInfo.initXmlProvider(); + + /* + * Retrieve the intermediate XML signature document from the temporary + * data storage. + */ + SignatureType sigType = sigDoc.getSignature(); + + /* + * Check ds:Signature node. + */ + if (!signatureId.equals(sigType.getId())) { + throw new RuntimeException("ds:Signature not found for @Id: " + signatureId); + } + + /* + * Insert signature value into the ds:SignatureValue element + */ + SignatureValueType sigVal = sigType.getSignatureValue(); + sigVal.setByteArrayValue(signatureValue); + + /* + * Allow signature facets to inject their own stuff. + */ + for (SignatureFacet signatureFacet : this.signatureFacets) { + signatureFacet.postSign(sigType, signingCertificateChain); + } + + writeDocument(); + } + + @SuppressWarnings("unchecked") + private byte[] getXmlSignatureDigestValue(HashAlgorithm hashAlgo, + List digestInfos, + List signingCertificateChain) + throws ParserConfigurationException, NoSuchAlgorithmException, + InvalidAlgorithmParameterException, MarshalException, + javax.xml.crypto.dsig.XMLSignatureException, + TransformerFactoryConfigurationError, TransformerException, + IOException, SAXException, NoSuchProviderException, XmlException { + /* + * DOM Document construction. + */ + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + dbf.setNamespaceAware(true); + Document doc = dbf.newDocumentBuilder().newDocument(); + + /* + * Signature context construction. + */ + Key key = new Key() { + private static final long serialVersionUID = 1L; + + public String getAlgorithm() { + return null; + } + + public byte[] getEncoded() { + return null; + } + + public String getFormat() { + return null; + } + }; + + // As of JDK 7, can't use sigDoc here directly, because the + // setAttributeID will be called and it's not implemented in xmlbeans + XMLSignContext xmlSignContext = new DOMSignContext(key, doc); + URIDereferencer uriDereferencer = getURIDereferencer(); + if (null != uriDereferencer) { + xmlSignContext.setURIDereferencer(uriDereferencer); + } + + xmlSignContext.putNamespacePrefix( + "http://schemas.openxmlformats.org/package/2006/digital-signature", + "mdssi"); + + if (this.signatureNamespacePrefix != null) { + /* + * OOo doesn't like ds namespaces so per default prefixing is off. + */ + xmlSignContext.putNamespacePrefix( + javax.xml.crypto.dsig.XMLSignature.XMLNS, + this.signatureNamespacePrefix); + } + + XMLSignatureFactory signatureFactory = XMLSignatureFactory.getInstance("DOM", "XMLDSig"); + + /* + * Add ds:References that come from signing client local files. + */ + List references = new LinkedList(); + addDigestInfosAsReferences(digestInfos, signatureFactory, references); + + /* + * Invoke the signature facets. + */ + String localSignatureId; + if (null == this.signatureId) { + localSignatureId = "xmldsig-" + UUID.randomUUID().toString(); + } else { + localSignatureId = this.signatureId; + } + List objects = new LinkedList(); + for (SignatureFacet signatureFacet : this.signatureFacets) { + LOG.log(POILogger.DEBUG, "invoking signature facet: " + + signatureFacet.getClass().getSimpleName()); + signatureFacet.preSign(signatureFactory, localSignatureId, signingCertificateChain, references, objects); + } + + /* + * ds:SignedInfo + */ + SignatureMethod signatureMethod = signatureFactory.newSignatureMethod( + getSignatureMethod(hashAlgo), null); + CanonicalizationMethod canonicalizationMethod = signatureFactory + .newCanonicalizationMethod(getCanonicalizationMethod(), + (C14NMethodParameterSpec) null); + SignedInfo signedInfo = signatureFactory.newSignedInfo( + canonicalizationMethod, signatureMethod, references); + + /* + * JSR105 ds:Signature creation + */ + String signatureValueId = localSignatureId + "-signature-value"; + javax.xml.crypto.dsig.XMLSignature xmlSignature = signatureFactory + .newXMLSignature(signedInfo, null, objects, localSignatureId, + signatureValueId); + + /* + * ds:Signature Marshalling. + */ + DOMXMLSignatureIf domXmlSignature; + try { + domXmlSignature = HorribleProxy.newProxy(DOMXMLSignatureIf.class, xmlSignature); + } catch (Exception e) { + throw new RuntimeException("DomXmlSignature instance error: " + e.getMessage(), e); + } + + domXmlSignature.marshal(doc, this.signatureNamespacePrefix, (DOMCryptoContext) xmlSignContext); + + registerIds(doc); + Element el = doc.getElementById("idPackageObject"); + assert (el != null); + el.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:mdssi", PackageNamespaces.DIGITAL_SIGNATURE); + + + /* + * Completion of undigested ds:References in the ds:Manifests. + */ + for (XMLObject object : objects) { + LOG.log(POILogger.DEBUG, "object java type: " + object.getClass().getName()); + List objectContentList = object.getContent(); + for (XMLStructure objectContent : objectContentList) { + LOG.log(POILogger.DEBUG, "object content java type: " + objectContent.getClass().getName()); + if (!(objectContent instanceof Manifest)) continue; + Manifest manifest = (Manifest) objectContent; + List manifestReferences = manifest.getReferences(); + for (Reference manifestReference : manifestReferences) { + if (manifestReference.getDigestValue() != null) continue; + + DOMReferenceIf manifestDOMReference; + try { + manifestDOMReference = HorribleProxy.newProxy(DOMReferenceIf.class, manifestReference); + } catch (Exception e) { + throw new RuntimeException("DOMReference instance error: " + e.getMessage(), e); + } + manifestDOMReference.digest(xmlSignContext); + } + } + } + + /* + * Completion of undigested ds:References. + */ + List signedInfoReferences = signedInfo.getReferences(); + for (Reference signedInfoReference : signedInfoReferences) { + DOMReferenceIf domReference; + try { + domReference = HorribleProxy.newProxy(DOMReferenceIf.class, signedInfoReference); + } catch (Exception e) { + throw new RuntimeException("DOMReference instance error: " + e.getMessage(), e); + } + + // ds:Reference with external digest value + if (domReference.getDigestValue() != null) continue; + + domReference.digest(xmlSignContext); + } + + /* + * Calculation of XML signature digest value. + */ + DOMSignedInfoIf domSignedInfo; + try { + domSignedInfo = HorribleProxy.newProxy(DOMSignedInfoIf.class, signedInfo); + } catch (Exception e) { + throw new RuntimeException("DOMSignedInfo instance error: " + e.getMessage(), e); + } + + ByteArrayOutputStream dataStream = new ByteArrayOutputStream(); + domSignedInfo.canonicalize(xmlSignContext, dataStream); + byte[] octets = dataStream.toByteArray(); + + sigDoc = SignatureDocument.Factory.parse(doc.getDocumentElement()); + + + /* + * TODO: we could be using DigestOutputStream here to optimize memory + * usage. + */ + + MessageDigest jcaMessageDigest = CryptoFunctions.getMessageDigest(hashAlgo); + byte[] digestValue = jcaMessageDigest.digest(octets); + return digestValue; + } + + /** + * the resulting document needs to be tweaked before it can be digested - + * this applies to the verification and signing step + * + * @param doc + */ + public void registerIds(Document doc) { + NodeList nl = doc.getElementsByTagNameNS("http://www.w3.org/2000/09/xmldsig#", "Object"); + registerIdAttribute(nl); + nl = doc.getElementsByTagNameNS("http://uri.etsi.org/01903/v1.3.2#", "SignedProperties"); + registerIdAttribute(nl); + } + + protected void registerIdAttribute(NodeList nl) { + for (int i=0; i digestInfos, + XMLSignatureFactory signatureFactory, List references) + throws NoSuchAlgorithmException, + InvalidAlgorithmParameterException, MalformedURLException { + if (digestInfos == null) return; + for (DigestInfo digestInfo : digestInfos) { + byte[] documentDigestValue = digestInfo.digestValue; + + DigestMethod digestMethod = signatureFactory.newDigestMethod( + digestInfo.hashAlgo.xmlSignUri, null); + + String uri = new File(digestInfo.description).getName(); + + Reference reference = signatureFactory.newReference(uri, + digestMethod, null, null, null, documentDigestValue); + references.add(reference); + } + } + + private String getSignatureMethod(HashAlgorithm hashAlgo) { + if (null == hashAlgo) { + throw new RuntimeException("digest algo is null"); + } + + XMLSignatureIf XmlSignature; + try { + XmlSignature = HorribleProxy.newProxy(XMLSignatureIf.class); + } catch (Exception e) { + throw new RuntimeException("JDK doesn't support XmlSignature", e); + } + + switch (hashAlgo) { + case sha1: return XmlSignature.ALGO_ID_SIGNATURE_RSA_SHA1(); + case sha256: return XmlSignature.ALGO_ID_SIGNATURE_RSA_SHA256(); + case sha384: return XmlSignature.ALGO_ID_SIGNATURE_RSA_SHA384(); + case sha512: return XmlSignature.ALGO_ID_SIGNATURE_RSA_SHA512(); + case ripemd160: return XmlSignature.ALGO_ID_MAC_HMAC_RIPEMD160(); + default: break; + } + + throw new RuntimeException("unsupported sign algo: " + hashAlgo); + } + + /** + * Gives back the used XAdES signature facet. + * + * @return + */ + protected XAdESSignatureFacet getXAdESSignatureFacet() { + return this.xadesSignatureFacet; + } + + public String getFilesDigestAlgorithm() { + return null; + } + + protected String getCanonicalizationMethod() { + return CanonicalizationMethod.INCLUSIVE; + } + + protected void writeDocument() throws IOException { + XmlOptions xo = new XmlOptions(); + Map namespaceMap = new HashMap(); + for (SignatureFacet sf : this.signatureFacets) { + Map sfm = sf.getNamespacePrefixMapping(); + if (sfm != null) { + namespaceMap.putAll(sfm); + } + } + xo.setSaveSuggestedPrefixes(namespaceMap); + xo.setUseDefaultNamespace(); + + LOG.log(POILogger.DEBUG, "output signed Office OpenXML document"); + + /* + * Copy the original OOXML content to the signed OOXML package. During + * copying some files need to changed. + */ + OPCPackage pkg = this.getOfficeOpenXMLDocument(); + + PackagePartName sigPartName, sigsPartName; + try { + // + sigPartName = PackagingURIHelper.createPartName("/_xmlsignatures/sig1.xml"); + // + sigsPartName = PackagingURIHelper.createPartName("/_xmlsignatures/origin.sigs"); + } catch (InvalidFormatException e) { + throw new IOException(e); + } + + String sigContentType = "application/vnd.openxmlformats-package.digital-signature-xmlsignature+xml"; + PackagePart sigPart = pkg.getPart(sigPartName); + if (sigPart == null) { + sigPart = pkg.createPart(sigPartName, sigContentType); + } + + OutputStream os = sigPart.getOutputStream(); + sigDoc.save(os, xo); + os.close(); + + String sigsContentType = "application/vnd.openxmlformats-package.digital-signature-origin"; + PackagePart sigsPart = pkg.getPart(sigsPartName); + if (sigsPart == null) { + // touch empty marker file + sigsPart = pkg.createPart(sigsPartName, sigsContentType); + } + + 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); + } +} diff --cc src/ooxml/testcases/org/apache/poi/poifs/crypt/TestSignatureInfo.java index 4243f6b1f5,0000000000..b3f08b3c8b mode 100644,000000..100644 --- a/src/ooxml/testcases/org/apache/poi/poifs/crypt/TestSignatureInfo.java +++ b/src/ooxml/testcases/org/apache/poi/poifs/crypt/TestSignatureInfo.java @@@ -1,267 -1,0 +1,342 @@@ +/* ==================================================================== + 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; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; ++import static org.mockito.Matchers.any; ++import static org.mockito.Matchers.eq; ++import static org.mockito.Mockito.mock; ++import static org.mockito.Mockito.when; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.security.Key; +import java.security.KeyPair; +import java.security.KeyStore; +import java.security.PrivateKey; +import java.security.cert.Certificate; ++import java.security.cert.X509CRL; +import java.security.cert.X509Certificate; ++import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.TimeZone; + +import javax.crypto.Cipher; + +import org.apache.poi.POIDataSamples; +import org.apache.poi.openxml4j.opc.OPCPackage; +import org.apache.poi.openxml4j.opc.PackageAccess; +import org.apache.poi.poifs.crypt.dsig.HorribleProxies.KeyUsageIf; ++import org.apache.poi.poifs.crypt.dsig.HorribleProxies.OCSPRespIf; +import org.apache.poi.poifs.crypt.dsig.HorribleProxy; +import org.apache.poi.poifs.crypt.dsig.SignatureInfo; ++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.SignaturePolicyService; ++import org.apache.poi.poifs.crypt.dsig.facets.XAdESSignatureFacet; ++import org.apache.poi.poifs.crypt.dsig.facets.XAdESXLSignatureFacet; ++import org.apache.poi.poifs.crypt.dsig.services.RevocationData; ++import org.apache.poi.poifs.crypt.dsig.services.RevocationDataService; ++import org.apache.poi.poifs.crypt.dsig.services.TimeStampService; +import org.apache.poi.poifs.crypt.dsig.services.XmlSignatureService; +import org.apache.poi.poifs.crypt.dsig.spi.DigestInfo; +import org.apache.poi.util.IOUtils; +import org.apache.poi.util.POILogFactory; +import org.apache.poi.util.POILogger; +import org.junit.BeforeClass; +import org.junit.Test; ++import org.mockito.invocation.InvocationOnMock; ++import org.mockito.stubbing.Answer; + +public class TestSignatureInfo { + private static final POILogger LOG = POILogFactory.getLogger(TestSignatureInfo.class); + private static final POIDataSamples testdata = POIDataSamples.getXmlDSignInstance(); + + private KeyPair keyPair = null; + private X509Certificate x509 = null; + + + + @BeforeClass + public static void initBouncy() throws MalformedURLException { + File bcJar = testdata.getFile("bcprov-ext-jdk15on-1.49.jar"); + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + URLClassLoader ucl = new URLClassLoader(new URL[]{bcJar.toURI().toURL()}, cl); + Thread.currentThread().setContextClassLoader(ucl); + } + + @Test + public void getSignerUnsigned() throws Exception { + String testFiles[] = { + "hello-world-unsigned.docx", + "hello-world-unsigned.pptx", + "hello-world-unsigned.xlsx", + "hello-world-office-2010-technical-preview-unsigned.docx" + }; + + for (String testFile : testFiles) { + OPCPackage pkg = OPCPackage.open(testdata.getFile(testFile), PackageAccess.READ); + SignatureInfo si = new SignatureInfo(pkg); + List result = si.getSigners(); + pkg.revert(); + pkg.close(); + assertNotNull(result); + assertTrue(result.isEmpty()); + } + } + + @Test + public void getSigner() throws Exception { + String testFiles[] = { + "hyperlink-example-signed.docx", + "hello-world-signed.docx", + "hello-world-signed.pptx", + "hello-world-signed.xlsx", + "hello-world-office-2010-technical-preview.docx", + "ms-office-2010-signed.docx", + "ms-office-2010-signed.pptx", + "ms-office-2010-signed.xlsx", + "Office2010-SP1-XAdES-X-L.docx", + "signed.docx", + }; + + for (String testFile : testFiles) { + OPCPackage pkg = OPCPackage.open(testdata.getFile(testFile), PackageAccess.READ); + SignatureInfo si = new SignatureInfo(pkg); + List result = si.getSigners(); + + assertNotNull(result); + assertEquals("test-file: "+testFile, 1, result.size()); + X509Certificate signer = result.get(0); + LOG.log(POILogger.DEBUG, "signer: " + signer.getSubjectX500Principal()); + + boolean b = si.verifySignature(); + assertTrue("test-file: "+testFile, b); + pkg.revert(); + } + } + + @Test + public void getMultiSigners() throws Exception { + String testFile = "hello-world-signed-twice.docx"; + OPCPackage pkg = OPCPackage.open(testdata.getFile(testFile), PackageAccess.READ); + SignatureInfo si = new SignatureInfo(pkg); + List result = si.getSigners(); + + assertNotNull(result); + assertEquals("test-file: "+testFile, 2, result.size()); + X509Certificate signer1 = result.get(0); + X509Certificate signer2 = result.get(1); + LOG.log(POILogger.DEBUG, "signer 1: " + signer1.getSubjectX500Principal()); + LOG.log(POILogger.DEBUG, "signer 2: " + signer2.getSubjectX500Principal()); + + boolean b = si.verifySignature(); + assertTrue("test-file: "+testFile, b); + pkg.revert(); + } + + @Test + public void testSignSpreadsheet() throws Exception { + String testFile = "hello-world-unsigned.xlsx"; + OPCPackage pkg = OPCPackage.open(copy(testdata.getFile(testFile)), PackageAccess.READ_WRITE); + sign(pkg, "Test", "CN=Test", 1); + pkg.close(); + } + + @Test + public void testSignSpreadsheetWithSignatureInfo() throws Exception { + String testFile = "hello-world-unsigned.xlsx"; + OPCPackage pkg = OPCPackage.open(copy(testdata.getFile(testFile)), PackageAccess.READ_WRITE); + SignatureInfo si = new SignatureInfo(pkg); + initKeyPair("Test", "CN=Test"); + // hash > sha1 doesn't work in excel viewer ... + si.confirmSignature(keyPair.getPrivate(), x509, HashAlgorithm.sha1); + List signer = si.getSigners(); + assertEquals(1, signer.size()); + pkg.close(); + } + ++ @Test ++ public void testSignEnvelopingDocument() throws Exception { ++ String testFile = "hello-world-unsigned.xlsx"; ++ OPCPackage pkg = OPCPackage.open(copy(testdata.getFile(testFile)), PackageAccess.READ_WRITE); ++ ++ // setup ++ EnvelopedSignatureFacet envelopedSignatureFacet = new EnvelopedSignatureFacet(); ++ KeyInfoSignatureFacet keyInfoSignatureFacet = new KeyInfoSignatureFacet(true, false, false); ++ SignaturePolicyService signaturePolicyService = null; ++ XAdESSignatureFacet xadesSignatureFacet = new XAdESSignatureFacet(null, null, signaturePolicyService); ++ ++ ++ TimeStampService mockTimeStampService = mock(TimeStampService.class); ++ RevocationDataService mockRevocationDataService = mock(RevocationDataService.class); ++ ++ XAdESXLSignatureFacet xadesXLSignatureFacet = new XAdESXLSignatureFacet( ++ mockTimeStampService, mockRevocationDataService); ++ XmlSignatureService testedInstance = new XmlSignatureService(HashAlgorithm.sha1, pkg); ++ testedInstance.addSignatureFacet(envelopedSignatureFacet, keyInfoSignatureFacet, ++ xadesSignatureFacet, xadesXLSignatureFacet); ++ ++ initKeyPair("Test", "CN=Test"); ++ List certificateChain = new ArrayList(); ++ /* ++ * We need at least 2 certificates for the XAdES-C complete certificate ++ * refs construction. ++ */ ++ certificateChain.add(x509); ++ certificateChain.add(x509); ++ ++ RevocationData revocationData = new RevocationData(); ++ final X509CRL crl = PkiTestUtils.generateCrl(x509, keyPair.getPrivate()); ++ revocationData.addCRL(crl); ++ OCSPRespIf ocspResp = PkiTestUtils.createOcspResp(x509, false, ++ x509, x509, keyPair.getPrivate(), "SHA1withRSA"); ++ revocationData.addOCSP(ocspResp.getEncoded()); ++ ++ when(mockTimeStampService.timeStamp(any(byte[].class), any(RevocationData.class))) ++ .thenAnswer(new Answer(){ ++ public byte[] answer(InvocationOnMock invocation) throws Throwable { ++ Object[] arguments = invocation.getArguments(); ++ RevocationData revocationData = (RevocationData) arguments[1]; ++ revocationData.addCRL(crl); ++ return "time-stamp-token".getBytes(); ++ } ++ }); ++ ++ when(mockRevocationDataService.getRevocationData(eq(certificateChain))) ++ .thenReturn(revocationData); ++ ++ // operate ++ DigestInfo digestInfo = testedInstance.preSign(null, certificateChain, null, null, null); ++ ++ // verify ++ assertNotNull(digestInfo); ++ assertEquals("SHA-1", digestInfo.hashAlgo); ++ assertNotNull(digestInfo.digestValue); ++ } + + private OPCPackage sign(OPCPackage pkgCopy, String alias, String signerDn, int signerCount) throws Exception { + /*** TODO : set cal to now ... only set to fixed date for debugging ... */ + Calendar cal = Calendar.getInstance(); + cal.clear(); + cal.setTimeZone(TimeZone.getTimeZone("UTC")); + cal.set(2014, 7, 6, 21, 42, 12); + + XmlSignatureService signatureService = new XmlSignatureService(HashAlgorithm.sha1, pkgCopy); + signatureService.initFacets(cal.getTime()); + initKeyPair(alias, signerDn); + + // operate + List x509Chain = Collections.singletonList(x509); + DigestInfo digestInfo = signatureService.preSign(null, x509Chain, null, null, null); + + // 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); + + // setup: key material, signature value + + Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); + cipher.init(Cipher.ENCRYPT_MODE, keyPair.getPrivate()); + ByteArrayOutputStream digestInfoValueBuf = new ByteArrayOutputStream(); + digestInfoValueBuf.write(SignatureInfo.SHA1_DIGEST_INFO_PREFIX); + digestInfoValueBuf.write(digestInfo.digestValue); + byte[] digestInfoValue = digestInfoValueBuf.toByteArray(); + byte[] signatureValue = cipher.doFinal(digestInfoValue); + + // operate: postSign + signatureService.postSign(signatureValue, Collections.singletonList(x509)); + + // verify: signature + SignatureInfo si = new SignatureInfo(pkgCopy); + List signers = si.getSigners(); + assertEquals(signerCount, signers.size()); + + return pkgCopy; + } + + private void initKeyPair(String alias, String subjectDN) throws Exception { + final char password[] = "test".toCharArray(); + File file = new File("build/test.pfx"); + + KeyStore keystore = KeyStore.getInstance("PKCS12"); + + if (file.exists()) { + FileInputStream fis = new FileInputStream(file); + keystore.load(fis, password); + fis.close(); + } else { + keystore.load(null, password); + } + + if (keystore.isKeyEntry(alias)) { + Key key = keystore.getKey(alias, password); + x509 = (X509Certificate)keystore.getCertificate(alias); + keyPair = new KeyPair(x509.getPublicKey(), (PrivateKey)key); + } else { + keyPair = PkiTestUtils.generateKeyPair(); + Calendar cal = Calendar.getInstance(); + Date notBefore = cal.getTime(); + cal.add(Calendar.YEAR, 1); + Date notAfter = cal.getTime(); + KeyUsageIf keyUsage = HorribleProxy.newProxy(KeyUsageIf.class); + keyUsage = HorribleProxy.newProxy(KeyUsageIf.class, keyUsage.digitalSignature()); + + x509 = PkiTestUtils.generateCertificate(keyPair.getPublic(), subjectDN + , notBefore, notAfter, null, keyPair.getPrivate(), true, 0, null, null, keyUsage); + + keystore.setKeyEntry(alias, keyPair.getPrivate(), password, new Certificate[]{x509}); + FileOutputStream fos = new FileOutputStream(file); + keystore.store(fos, password); + fos.close(); + } + } + + private static File copy(File input) throws IOException { + String extension = input.getName().replaceAll(".*?(\\.[^.]+)?$", "$1"); + if (extension == null || "".equals(extension)) extension = ".zip"; + File tmpFile = new File("build", "sigtest"+extension); + FileOutputStream fos = new FileOutputStream(tmpFile); + FileInputStream fis = new FileInputStream(input); + IOUtils.copy(fis, fos); + fis.close(); + fos.close(); + return tmpFile; + } +}