From ee2471a5a9ee19bba371a0ff43ff2a2aac43791c Mon Sep 17 00:00:00 2001 From: Andreas Beeker Date: Fri, 10 Oct 2014 22:48:21 +0000 Subject: [PATCH] xml signature: mainly javadocs - a few reorgs git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1631003 13f79535-47bb-0310-9956-ffa450edef68 --- .../poi/poifs/crypt/dsig/SignatureConfig.java | 294 +++++++++++++++++- .../poi/poifs/crypt/dsig/SignatureInfo.java | 65 +++- .../crypt/dsig/facets/SignatureFacet.java | 1 - .../dsig/facets/XAdESXLSignatureFacet.java | 11 +- 4 files changed, 345 insertions(+), 26 deletions(-) diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureConfig.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureConfig.java index 52ba1f3ba1..71602c6d09 100644 --- a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureConfig.java +++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureConfig.java @@ -51,6 +51,7 @@ import org.apache.poi.poifs.crypt.dsig.services.TimeStampService; import org.apache.poi.poifs.crypt.dsig.services.TimeStampServiceValidator; import org.apache.poi.util.POILogFactory; import org.apache.poi.util.POILogger; +import org.apache.xml.security.signature.XMLSignature; import org.w3c.dom.events.EventListener; /** @@ -122,8 +123,9 @@ public class SignatureConfig { */ private HashAlgorithm xadesDigestAlgo = null; private String xadesRole = null; - private String xadesSignatureId = null; + private String xadesSignatureId = "idSignedProperties"; private boolean xadesSignaturePolicyImplied = true; + private String xadesCanonicalizationMethod = CanonicalizationMethod.EXCLUSIVE; /** * Work-around for Office 2010 IssuerName encoding. @@ -198,10 +200,6 @@ public class SignatureConfig { tspService.setSignatureConfig(this); } - if (xadesSignatureId == null || xadesSignatureId.isEmpty()) { - xadesSignatureId = "idSignedProperties"; - } - if (signatureFacets.isEmpty()) { addSignatureFacet(new OOXMLSignatureFacet()); addSignatureFacet(new KeyInfoSignatureFacet()); @@ -366,141 +364,358 @@ public class SignatureConfig { public void setCanonicalizationMethod(String canonicalizationMethod) { this.canonicalizationMethod = canonicalizationMethod; } + + /** + * @return The signature Id attribute value used to create the XML signature. + * Defaults to "idPackageSignature" + */ public String getPackageSignatureId() { return packageSignatureId; } + + /** + * @param packageSignatureId The signature Id attribute value used to create the XML signature. + * A null value will trigger an automatically generated signature Id. + */ public void setPackageSignatureId(String packageSignatureId) { this.packageSignatureId = nvl(packageSignatureId,"xmldsig-"+UUID.randomUUID()); } + + /** + * @return the url of the timestamp provider (TSP) + */ public String getTspUrl() { return tspUrl; } + + /** + * @param tspUrl the url of the timestamp provider (TSP) + */ public void setTspUrl(String tspUrl) { this.tspUrl = tspUrl; } + + /** + * @return if true, uses timestamp-request/response mimetype, + * if false, timestamp-query/reply mimetype + */ public boolean isTspOldProtocol() { return tspOldProtocol; } + + /** + * @param tspOldProtocol defines the timestamp-protocol mimetype + * @see {@link #isTspOldProtocol()} + */ public void setTspOldProtocol(boolean tspOldProtocol) { this.tspOldProtocol = tspOldProtocol; } + + /** + * @return the hash algorithm to be used for the timestamp entry. + * Defaults to the hash algorithm of the main entry + */ public HashAlgorithm getTspDigestAlgo() { return nvl(tspDigestAlgo,digestAlgo); } + + /** + * @param tspDigestAlgo the algorithm to be used for the timestamp entry. + * if null, the hash algorithm of the main entry + */ public void setTspDigestAlgo(HashAlgorithm tspDigestAlgo) { this.tspDigestAlgo = tspDigestAlgo; } + + /** + * @return the proxy url to be used for all communications. + * Currently this affects the timestamp service + */ public String getProxyUrl() { return proxyUrl; } + + /** + * @param proxyUrl the proxy url to be used for all communications. + * Currently this affects the timestamp service + */ public void setProxyUrl(String proxyUrl) { this.proxyUrl = proxyUrl; } + + /** + * @return the timestamp service. Defaults to {@link TSPTimeStampService} + */ public TimeStampService getTspService() { return tspService; } + + /** + * @param tspService the timestamp service + */ public void setTspService(TimeStampService tspService) { this.tspService = tspService; } + + /** + * @return the user id for the timestamp service - currently only basic authorization is supported + */ public String getTspUser() { return tspUser; } + + /** + * @param tspUser the user id for the timestamp service - currently only basic authorization is supported + */ public void setTspUser(String tspUser) { this.tspUser = tspUser; } + + /** + * @return the password for the timestamp service + */ public String getTspPass() { return tspPass; } + + /** + * @param tspPass the password for the timestamp service + */ public void setTspPass(String tspPass) { this.tspPass = tspPass; } + + /** + * @return the validator for the timestamp service (certificate) + */ public TimeStampServiceValidator getTspValidator() { return tspValidator; } + + /** + * @param tspValidator the validator for the timestamp service (certificate) + */ public void setTspValidator(TimeStampServiceValidator tspValidator) { this.tspValidator = tspValidator; } + + /** + * @return the optional revocation data service used for XAdES-C and XAdES-X-L. + * When null the signature will be limited to XAdES-T only. + */ public RevocationDataService getRevocationDataService() { return revocationDataService; } + + /** + * @param revocationDataService the optional revocation data service used for XAdES-C and XAdES-X-L. + * When null the signature will be limited to XAdES-T only. + */ public void setRevocationDataService(RevocationDataService revocationDataService) { this.revocationDataService = revocationDataService; } + + /** + * @return hash algorithm used for XAdES. Defaults to the {@link #getDigestAlgo()} + */ public HashAlgorithm getXadesDigestAlgo() { return nvl(xadesDigestAlgo,digestAlgo); } + + /** + * @param xadesDigestAlgo hash algorithm used for XAdES. + * When null, defaults to {@link #getDigestAlgo()} + */ public void setXadesDigestAlgo(HashAlgorithm xadesDigestAlgo) { this.xadesDigestAlgo = xadesDigestAlgo; } + + /** + * @return the user agent used for http communication (e.g. to the TSP) + */ public String getUserAgent() { return userAgent; } + + /** + * @param userAgent the user agent used for http communication (e.g. to the TSP) + */ public void setUserAgent(String userAgent) { this.userAgent = userAgent; } + + /** + * @return the asn.1 object id for the tsp request policy. + * Defaults to 1.3.6.1.4.1.13762.3 + */ public String getTspRequestPolicy() { return tspRequestPolicy; } + + /** + * @param tspRequestPolicy the asn.1 object id for the tsp request policy. + */ public void setTspRequestPolicy(String tspRequestPolicy) { this.tspRequestPolicy = tspRequestPolicy; } + + /** + * @return true, if the whole certificate chain is included in the signature. + * When false, only the signer cert will be included + */ public boolean isIncludeEntireCertificateChain() { return includeEntireCertificateChain; } + + /** + * @param includeEntireCertificateChain if true, include the whole certificate chain. + * If false, only include the signer cert + */ public void setIncludeEntireCertificateChain(boolean includeEntireCertificateChain) { this.includeEntireCertificateChain = includeEntireCertificateChain; } + + /** + * @return if true, issuer serial number is included + */ public boolean isIncludeIssuerSerial() { return includeIssuerSerial; } + + /** + * @param includeIssuerSerial if true, issuer serial number is included + */ public void setIncludeIssuerSerial(boolean includeIssuerSerial) { this.includeIssuerSerial = includeIssuerSerial; } + + /** + * @return if true, the key value of the public key (certificate) is included + */ public boolean isIncludeKeyValue() { return includeKeyValue; } + + /** + * @param includeKeyValue if true, the key value of the public key (certificate) is included + */ public void setIncludeKeyValue(boolean includeKeyValue) { this.includeKeyValue = includeKeyValue; } + + /** + * @return the xades role element. If null the claimed role element is omitted. + * Defaults to null + */ public String getXadesRole() { return xadesRole; } + + /** + * @param xadesRole the xades role element. If null the claimed role element is omitted. + */ public void setXadesRole(String xadesRole) { this.xadesRole = xadesRole; } + + /** + * @return the Id for the XAdES SignedProperties element. + * Defaults to idSignedProperties + */ public String getXadesSignatureId() { - return xadesSignatureId; + return nvl(xadesSignatureId, "idSignedProperties"); } + + /** + * @param xadesSignatureId the Id for the XAdES SignedProperties element. + * When null defaults to idSignedProperties + */ public void setXadesSignatureId(String xadesSignatureId) { this.xadesSignatureId = xadesSignatureId; } + + /** + * @return when true, include the policy-implied block. + * Defaults to true + */ public boolean isXadesSignaturePolicyImplied() { return xadesSignaturePolicyImplied; } + + /** + * @param xadesSignaturePolicyImplied when true, include the policy-implied block + */ public void setXadesSignaturePolicyImplied(boolean xadesSignaturePolicyImplied) { this.xadesSignaturePolicyImplied = xadesSignaturePolicyImplied; } + + /** + * Make sure the DN is encoded using the same order as present + * within the certificate. This is an Office2010 work-around. + * Should be reverted back. + * + * XXX: not correct according to RFC 4514. + * + * @return when true, the issuer DN is used instead of the issuer X500 principal + */ public boolean isXadesIssuerNameNoReverseOrder() { return xadesIssuerNameNoReverseOrder; } + + /** + * @param xadesIssuerNameNoReverseOrder when true, the issuer DN instead of the issuer X500 prinicpal is used + */ public void setXadesIssuerNameNoReverseOrder(boolean xadesIssuerNameNoReverseOrder) { this.xadesIssuerNameNoReverseOrder = xadesIssuerNameNoReverseOrder; } + + + /** + * @return the event listener which is active while xml structure for + * the signature is created. + * Defaults to {@link SignatureMarshalListener} + */ public EventListener getSignatureMarshalListener() { return signatureMarshalListener; } + + /** + * @param signatureMarshalListener the event listener watching the xml structure + * generation for the signature + */ public void setSignatureMarshalListener(EventListener signatureMarshalListener) { this.signatureMarshalListener = signatureMarshalListener; } + + /** + * @return the map of namespace uri (key) to prefix (value) + */ public Map getNamespacePrefixes() { return namespacePrefixes; } + + /** + * @param namespacePrefixes the map of namespace uri (key) to prefix (value) + */ public void setNamespacePrefixes(Map namespacePrefixes) { this.namespacePrefixes = namespacePrefixes; } + + /** + * helper method for null/default value handling + * @param value + * @param defaultValue + * @return if value is not null, return value otherwise defaultValue + */ protected static T nvl(T value, T defaultValue) { 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 @@ -545,23 +760,34 @@ public class SignatureConfig { return result; } - public String getSignatureMethod() { + /** + * @return the uri for the signature method, i.e. currently only rsa is + * supported, so it's the rsa variant of the main digest + */ + public String getSignatureMethodUri() { switch (getDigestAlgo()) { - case sha1: return org.apache.xml.security.signature.XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA1; - case sha224: return org.apache.xml.security.signature.XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA224; - case sha256: return org.apache.xml.security.signature.XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA256; - case sha384: return org.apache.xml.security.signature.XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA384; - case sha512: return org.apache.xml.security.signature.XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA512; - case ripemd160: return org.apache.xml.security.signature.XMLSignature.ALGO_ID_SIGNATURE_RSA_RIPEMD160; + case sha1: return XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA1; + case sha224: return XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA224; + 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_SIGNATURE_RSA_RIPEMD160; default: throw new EncryptedDocumentException("Hash algorithm " +getDigestAlgo()+" not supported for signing."); } } + /** + * @return the uri for the main digest + */ public String getDigestMethodUri() { return getDigestMethodUri(getDigestAlgo()); } + /** + * @param digestAlgo the digest algo, currently only sha* and ripemd160 is supported + * @return the uri for the given digest + */ public static String getDigestMethodUri(HashAlgorithm digestAlgo) { switch (digestAlgo) { case sha1: return DigestMethod.SHA1; @@ -575,10 +801,16 @@ public class SignatureConfig { } } + /** + * @param signatureFactory the xml signature factory, saved as thread-local + */ public void setSignatureFactory(XMLSignatureFactory signatureFactory) { this.signatureFactory.set(signatureFactory); } + /** + * @return the xml signature factory (thread-local) + */ public XMLSignatureFactory getSignatureFactory() { XMLSignatureFactory sigFac = signatureFactory.get(); if (sigFac == null) { @@ -588,10 +820,16 @@ public class SignatureConfig { return sigFac; } + /** + * @param keyInfoFactory the key factory, saved as thread-local + */ public void setKeyInfoFactory(KeyInfoFactory keyInfoFactory) { this.keyInfoFactory.set(keyInfoFactory); } + /** + * @return the key factory (thread-local) + */ public KeyInfoFactory getKeyInfoFactory() { KeyInfoFactory keyFac = keyInfoFactory.get(); if (keyFac == null) { @@ -601,7 +839,19 @@ public class SignatureConfig { return keyFac; } - // currently classes are linked to Apache Santuario, so this might be superfluous + /** + * This method tests the existence of xml signature provider in the following order: + *
    + *
  • the class pointed to by the system property "jsr105Provider"
  • + *
  • the Santuario xmlsec provider
  • + *
  • the JDK xmlsec provider
  • + *
+ * + * For signing the classes are linked against the Santuario xmlsec, so this might + * only work for validation (not tested). + * + * @return the xml dsig provider + */ public Provider getProvider() { Provider prov = provider.get(); if (prov == null) { @@ -627,7 +877,21 @@ public class SignatureConfig { return prov; } - + /** + * @return the cannonicalization method for XAdES-XL signing. + * Defaults to EXCLUSIVE + * @see {@link CanonicalizationMethod} + */ + public String getXadesCanonicalizationMethod() { + return xadesCanonicalizationMethod; + } + /** + * @param xadesCanonicalizationMethod the cannonicalization method for XAdES-XL signing + * @see {@link CanonicalizationMethod} + */ + public void setXadesCanonicalizationMethod(String xadesCanonicalizationMethod) { + this.xadesCanonicalizationMethod = xadesCanonicalizationMethod; + } } diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureInfo.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureInfo.java index 94de8950a8..864b970f24 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 @@ -179,23 +179,42 @@ public class SignatureInfo implements SignatureConfigurable { 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 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()); } + /** + * @return true, when the xml signature is valid, false otherwise + */ public boolean validate() { KeyInfoKeySelector keySelector = new KeyInfoKeySelector(); try { @@ -227,18 +246,30 @@ public class SignatureInfo implements SignatureConfigurable { } } + /** + * Constructor initializes xml signature environment, if it hasn't been initialized before + */ public SignatureInfo() { initXmlProvider(); } + /** + * @return the signature config + */ public SignatureConfig getSignatureConfig() { return signatureConfig; } + /** + * @param signatureConfig the signature config, needs to be set before a SignatureInfo object is used + */ public void setSignatureConfig(SignatureConfig signatureConfig) { this.signatureConfig = signatureConfig; } + /** + * @return true, if first signature part is valid + */ public boolean verifySignature() { // http://www.oracle.com/technetwork/articles/javase/dig-signature-api-140772.html for (SignaturePart sp : getSignatureParts()){ @@ -248,6 +279,12 @@ public class SignatureInfo implements SignatureConfigurable { return false; } + /** + * add the xml signature to the document + * + * @throws XMLSignatureException + * @throws MarshalException + */ public void confirmSignature() throws XMLSignatureException, MarshalException { Document document = DocumentHelper.createDocument(); @@ -261,6 +298,13 @@ public class SignatureInfo implements SignatureConfigurable { postSign(document, signatureValue); } + /** + * 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"); @@ -277,6 +321,10 @@ public class SignatureInfo implements SignatureConfigurable { } } + /** + * @return a signature part for each signature document. + * the parts can be validated independently. + */ public Iterable getSignatureParts() { signatureConfig.init(true); return new Iterable() { @@ -324,6 +372,9 @@ public class SignatureInfo implements SignatureConfigurable { }; } + /** + * Initialize the xml signing environment and the bouncycastle provider + */ protected static synchronized void initXmlProvider() { if (isInitialized) return; isInitialized = true; @@ -401,7 +452,7 @@ public class SignatureInfo implements SignatureConfigurable { SignedInfo signedInfo; try { SignatureMethod signatureMethod = signatureFactory.newSignatureMethod - (signatureConfig.getSignatureMethod(), null); + (signatureConfig.getSignatureMethodUri(), null); CanonicalizationMethod canonicalizationMethod = signatureFactory .newCanonicalizationMethod(signatureConfig.getCanonicalizationMethod(), (C14NMethodParameterSpec) null); @@ -513,6 +564,12 @@ public class SignatureInfo implements SignatureConfigurable { writeDocument(document); } + /** + * Write XML signature into the OPC package + * + * @param document the xml signature document + * @throws MarshalException + */ protected void writeDocument(Document document) throws MarshalException { XmlOptions xo = new XmlOptions(); Map namespaceMap = new HashMap(); @@ -569,6 +626,12 @@ public class SignatureInfo implements SignatureConfigurable { sigsPart.addRelationship(sigPartName, TargetMode.INTERNAL, PackageRelationshipTypes.DIGITAL_SIGNATURE); } + /** + * Helper method for null lists, which are converted to empty lists + * + * @param other the reference to wrap, if null + * @return if other is null, an empty lists is returned, otherwise other is returned + */ @SuppressWarnings("unchecked") private static List safe(List other) { return other == null ? Collections.EMPTY_LIST : other; diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/SignatureFacet.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/SignatureFacet.java index 58507274ee..40c29aabae 100644 --- a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/SignatureFacet.java +++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/SignatureFacet.java @@ -56,7 +56,6 @@ public abstract class SignatureFacet implements SignatureConfigurable { public static final String XADES_141_NS = "http://uri.etsi.org/01903/v1.4.1#"; protected SignatureConfig signatureConfig; - protected ThreadLocal signatureFactory; public void setSignatureConfig(SignatureConfig signatureConfig) { this.signatureConfig = signatureConfig; diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/XAdESXLSignatureFacet.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/XAdESXLSignatureFacet.java index b092f1c091..1a1ac18391 100644 --- a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/XAdESXLSignatureFacet.java +++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/XAdESXLSignatureFacet.java @@ -42,7 +42,6 @@ import java.util.List; import java.util.UUID; import javax.xml.crypto.MarshalException; -import javax.xml.crypto.dsig.CanonicalizationMethod; import org.apache.poi.poifs.crypt.dsig.services.RevocationData; import org.apache.poi.util.POILogFactory; @@ -105,8 +104,6 @@ public class XAdESXLSignatureFacet extends SignatureFacet { private static final POILogger LOG = POILogFactory.getLogger(XAdESXLSignatureFacet.class); - private String c14nAlgoId = CanonicalizationMethod.EXCLUSIVE; - private final CertificateFactory certificateFactory; public XAdESXLSignatureFacet() { @@ -117,10 +114,6 @@ public class XAdESXLSignatureFacet extends SignatureFacet { } } - public void setCanonicalizerAlgorithm(String c14nAlgoId) { - this.c14nAlgoId = c14nAlgoId; - } - @Override public void postSign(Document document) throws MarshalException { LOG.log(POILogger.DEBUG, "XAdES-X-L post sign phase"); @@ -351,7 +344,7 @@ public class XAdESXLSignatureFacet extends SignatureFacet { private XAdESTimeStampType createXAdESTimeStamp( List nodeList, RevocationData revocationData) { - byte[] c14nSignatureValueElement = getC14nValue(nodeList, c14nAlgoId); + byte[] c14nSignatureValueElement = getC14nValue(nodeList, signatureConfig.getXadesCanonicalizationMethod()); return createXAdESTimeStamp(c14nSignatureValueElement, revocationData); } @@ -370,7 +363,7 @@ public class XAdESXLSignatureFacet extends SignatureFacet { XAdESTimeStampType xadesTimeStamp = XAdESTimeStampType.Factory.newInstance(); xadesTimeStamp.setId("time-stamp-" + UUID.randomUUID().toString()); CanonicalizationMethodType c14nMethod = xadesTimeStamp.addNewCanonicalizationMethod(); - c14nMethod.setAlgorithm(c14nAlgoId); + c14nMethod.setAlgorithm(signatureConfig.getXadesCanonicalizationMethod()); // embed the time-stamp EncapsulatedPKIDataType encapsulatedTimeStamp = xadesTimeStamp.addNewEncapsulatedTimeStamp(); -- 2.39.5