git-svn-id: https://svn.apache.org/repos/asf/poi/branches/xml_signature@1628107 13f79535-47bb-0310-9956-ffa450edef68tags/REL_3_11_BETA3
package org.apache.poi.poifs.crypt; | package org.apache.poi.poifs.crypt; | ||||
import javax.xml.crypto.dsig.DigestMethod; | |||||
import org.apache.poi.EncryptedDocumentException; | import org.apache.poi.EncryptedDocumentException; | ||||
public enum HashAlgorithm { | public enum HashAlgorithm { | ||||
none ( "", 0x0000, "", 0, "", null, false), | |||||
sha1 ( "SHA-1", 0x8004, "SHA1", 20, "HmacSHA1", DigestMethod.SHA1, false), | |||||
sha256 ( "SHA-256", 0x800C, "SHA256", 32, "HmacSHA256", DigestMethod.SHA256, false), | |||||
sha384 ( "SHA-384", 0x800D, "SHA384", 48, "HmacSHA384", null, false), | |||||
sha512 ( "SHA-512", 0x800E, "SHA512", 64, "HmacSHA512", DigestMethod.SHA512, false), | |||||
none ( "", 0x0000, "", 0, "", false), | |||||
sha1 ( "SHA-1", 0x8004, "SHA1", 20, "HmacSHA1", false), | |||||
sha256 ( "SHA-256", 0x800C, "SHA256", 32, "HmacSHA256", false), | |||||
sha384 ( "SHA-384", 0x800D, "SHA384", 48, "HmacSHA384", false), | |||||
sha512 ( "SHA-512", 0x800E, "SHA512", 64, "HmacSHA512", false), | |||||
/* only for agile encryption */ | /* only for agile encryption */ | ||||
md5 ( "MD5", -1, "MD5", 16, "HmacMD5", null, false), | |||||
md5 ( "MD5", -1, "MD5", 16, "HmacMD5", false), | |||||
// although sunjc2 supports md2, hmac-md2 is only supported by bouncycastle | // although sunjc2 supports md2, hmac-md2 is only supported by bouncycastle | ||||
md2 ( "MD2", -1, "MD2", 16, "Hmac-MD2", null, true), | |||||
md4 ( "MD4", -1, "MD4", 16, "Hmac-MD4", null, true), | |||||
ripemd128("RipeMD128", -1, "RIPEMD-128", 16, "HMac-RipeMD128", null, true), | |||||
ripemd160("RipeMD160", -1, "RIPEMD-160", 20, "HMac-RipeMD160", DigestMethod.RIPEMD160, true), | |||||
whirlpool("Whirlpool", -1, "WHIRLPOOL", 64, "HMac-Whirlpool", null, true), | |||||
md2 ( "MD2", -1, "MD2", 16, "Hmac-MD2", true), | |||||
md4 ( "MD4", -1, "MD4", 16, "Hmac-MD4", true), | |||||
ripemd128("RipeMD128", -1, "RIPEMD-128", 16, "HMac-RipeMD128", true), | |||||
ripemd160("RipeMD160", -1, "RIPEMD-160", 20, "HMac-RipeMD160", true), | |||||
whirlpool("Whirlpool", -1, "WHIRLPOOL", 64, "HMac-Whirlpool", true), | |||||
// only for xml signing | |||||
sha224 ( "SHA-224", -1, "SHA224", 28, "HmacSHA224", true); | |||||
; | ; | ||||
public final String jceId; | public final String jceId; | ||||
public final String ecmaString; | public final String ecmaString; | ||||
public final int hashSize; | public final int hashSize; | ||||
public final String jceHmacId; | public final String jceHmacId; | ||||
public final String xmlSignUri; | |||||
public final boolean needsBouncyCastle; | public final boolean needsBouncyCastle; | ||||
HashAlgorithm(String jceId, int ecmaId, String ecmaString, int hashSize, String jceHmacId, String xmlSignUri, boolean needsBouncyCastle) { | |||||
HashAlgorithm(String jceId, int ecmaId, String ecmaString, int hashSize, String jceHmacId, boolean needsBouncyCastle) { | |||||
this.jceId = jceId; | this.jceId = jceId; | ||||
this.ecmaId = ecmaId; | this.ecmaId = ecmaId; | ||||
this.ecmaString = ecmaString; | this.ecmaString = ecmaString; | ||||
this.hashSize = hashSize; | this.hashSize = hashSize; | ||||
this.jceHmacId = jceHmacId; | this.jceHmacId = jceHmacId; | ||||
this.xmlSignUri = xmlSignUri; | |||||
this.needsBouncyCastle = needsBouncyCastle; | this.needsBouncyCastle = needsBouncyCastle; | ||||
} | } | ||||
import java.security.Key; | import java.security.Key; | ||||
import java.security.cert.X509Certificate; | import java.security.cert.X509Certificate; | ||||
import java.util.ArrayList; | |||||
import java.util.List; | import java.util.List; | ||||
import javax.xml.crypto.AlgorithmMethod; | import javax.xml.crypto.AlgorithmMethod; | ||||
private static final POILogger LOG = POILogFactory.getLogger(KeyInfoKeySelector.class); | private static final POILogger LOG = POILogFactory.getLogger(KeyInfoKeySelector.class); | ||||
private X509Certificate certificate; | |||||
private List<X509Certificate> certChain = new ArrayList<X509Certificate>(); | |||||
@SuppressWarnings("unchecked") | @SuppressWarnings("unchecked") | ||||
@Override | @Override | ||||
throw new KeySelectorException("no ds:KeyInfo present"); | throw new KeySelectorException("no ds:KeyInfo present"); | ||||
} | } | ||||
List<XMLStructure> keyInfoContent = keyInfo.getContent(); | List<XMLStructure> keyInfoContent = keyInfo.getContent(); | ||||
this.certificate = null; | |||||
certChain.clear(); | |||||
for (XMLStructure keyInfoStructure : keyInfoContent) { | for (XMLStructure keyInfoStructure : keyInfoContent) { | ||||
if (false == (keyInfoStructure instanceof X509Data)) { | |||||
if (!(keyInfoStructure instanceof X509Data)) { | |||||
continue; | continue; | ||||
} | } | ||||
X509Data x509Data = (X509Data) keyInfoStructure; | X509Data x509Data = (X509Data) keyInfoStructure; | ||||
List<Object> x509DataList = x509Data.getContent(); | List<Object> x509DataList = x509Data.getContent(); | ||||
for (Object x509DataObject : x509DataList) { | for (Object x509DataObject : x509DataList) { | ||||
if (false == (x509DataObject instanceof X509Certificate)) { | |||||
if (!(x509DataObject instanceof X509Certificate)) { | |||||
continue; | continue; | ||||
} | } | ||||
X509Certificate certificate = (X509Certificate) x509DataObject; | X509Certificate certificate = (X509Certificate) x509DataObject; | ||||
LOG.log(POILogger.DEBUG, "certificate", certificate.getSubjectX500Principal()); | LOG.log(POILogger.DEBUG, "certificate", certificate.getSubjectX500Principal()); | ||||
if (null == this.certificate) { | |||||
/* | |||||
* The first certificate is presumably the signer. | |||||
*/ | |||||
this.certificate = certificate; | |||||
} | |||||
} | |||||
if (null != this.certificate) { | |||||
return this; | |||||
certChain.add(certificate); | |||||
} | } | ||||
} | } | ||||
throw new KeySelectorException("No key found!"); | |||||
if (certChain.isEmpty()) { | |||||
throw new KeySelectorException("No key found!"); | |||||
} | |||||
return this; | |||||
} | } | ||||
public Key getKey() { | public Key getKey() { | ||||
return this.certificate.getPublicKey(); | |||||
// The first certificate is presumably the signer. | |||||
return certChain.isEmpty() ? null : certChain.get(0).getPublicKey(); | |||||
} | } | ||||
/** | /** | ||||
* | * | ||||
* @return | * @return | ||||
*/ | */ | ||||
public X509Certificate getCertificate() { | |||||
return this.certificate; | |||||
public X509Certificate getSigner() { | |||||
// The first certificate is presumably the signer. | |||||
return certChain.isEmpty() ? null : certChain.get(0); | |||||
} | |||||
public List<X509Certificate> getCertChain() { | |||||
return certChain; | |||||
} | } | ||||
} | } |
import javax.xml.crypto.URIDereferencer; | import javax.xml.crypto.URIDereferencer; | ||||
import javax.xml.crypto.dsig.CanonicalizationMethod; | import javax.xml.crypto.dsig.CanonicalizationMethod; | ||||
import javax.xml.crypto.dsig.DigestMethod; | |||||
import org.apache.poi.EncryptedDocumentException; | import org.apache.poi.EncryptedDocumentException; | ||||
import org.apache.poi.openxml4j.opc.OPCPackage; | import org.apache.poi.openxml4j.opc.OPCPackage; | ||||
// timestamp service provider URL | // timestamp service provider URL | ||||
private String tspUrl; | private String tspUrl; | ||||
private boolean tspOldProtocol = false; | private boolean tspOldProtocol = false; | ||||
private HashAlgorithm tspDigestAlgo = HashAlgorithm.sha1; | |||||
/** | |||||
* if not defined, it's the same as the main digest | |||||
*/ | |||||
private HashAlgorithm tspDigestAlgo = null; | |||||
private String tspUser; | private String tspUser; | ||||
private String tspPass; | private String tspPass; | ||||
private TimeStampServiceValidator tspValidator; | private TimeStampServiceValidator tspValidator; | ||||
* When <code>null</code> the signature will be limited to XAdES-T only. | * When <code>null</code> the signature will be limited to XAdES-T only. | ||||
*/ | */ | ||||
private RevocationDataService revocationDataService; | private RevocationDataService revocationDataService; | ||||
private HashAlgorithm xadesDigestAlgo = HashAlgorithm.sha1; | |||||
/** | |||||
* if not defined, it's the same as the main digest | |||||
*/ | |||||
private HashAlgorithm xadesDigestAlgo = null; | |||||
private String xadesRole = null; | private String xadesRole = null; | ||||
private String xadesSignatureId = null; | private String xadesSignatureId = null; | ||||
private boolean xadesSignaturePolicyImplied = true; | private boolean xadesSignaturePolicyImplied = true; | ||||
return packageSignatureId; | return packageSignatureId; | ||||
} | } | ||||
public void setPackageSignatureId(String packageSignatureId) { | public void setPackageSignatureId(String packageSignatureId) { | ||||
this.packageSignatureId = (packageSignatureId != null) | |||||
? packageSignatureId | |||||
: "xmldsig-" + UUID.randomUUID(); | |||||
this.packageSignatureId = nvl(packageSignatureId,"xmldsig-"+UUID.randomUUID()); | |||||
} | } | ||||
public String getTspUrl() { | public String getTspUrl() { | ||||
return tspUrl; | return tspUrl; | ||||
this.tspOldProtocol = tspOldProtocol; | this.tspOldProtocol = tspOldProtocol; | ||||
} | } | ||||
public HashAlgorithm getTspDigestAlgo() { | public HashAlgorithm getTspDigestAlgo() { | ||||
return tspDigestAlgo; | |||||
return nvl(tspDigestAlgo,digestAlgo); | |||||
} | } | ||||
public void setTspDigestAlgo(HashAlgorithm tspDigestAlgo) { | public void setTspDigestAlgo(HashAlgorithm tspDigestAlgo) { | ||||
this.tspDigestAlgo = tspDigestAlgo; | this.tspDigestAlgo = tspDigestAlgo; | ||||
this.revocationDataService = revocationDataService; | this.revocationDataService = revocationDataService; | ||||
} | } | ||||
public HashAlgorithm getXadesDigestAlgo() { | public HashAlgorithm getXadesDigestAlgo() { | ||||
return xadesDigestAlgo; | |||||
return nvl(xadesDigestAlgo,digestAlgo); | |||||
} | } | ||||
public void setXadesDigestAlgo(HashAlgorithm xadesDigestAlgo) { | public void setXadesDigestAlgo(HashAlgorithm xadesDigestAlgo) { | ||||
this.xadesDigestAlgo = xadesDigestAlgo; | this.xadesDigestAlgo = xadesDigestAlgo; | ||||
public void setNamespacePrefixes(Map<String, String> namespacePrefixes) { | public void setNamespacePrefixes(Map<String, String> namespacePrefixes) { | ||||
this.namespacePrefixes = namespacePrefixes; | this.namespacePrefixes = namespacePrefixes; | ||||
} | } | ||||
protected static <T> T nvl(T value, T defaultValue) { | |||||
return value == null ? defaultValue : value; | |||||
} | |||||
public byte[] getHashMagic() { | |||||
// see https://www.ietf.org/rfc/rfc3110.txt | |||||
// RSA/SHA1 SIG Resource Records | |||||
byte result[]; | |||||
switch (getDigestAlgo()) { | |||||
case sha1: result = new byte[] | |||||
{ 0x30, 0x1f, 0x30, 0x07, 0x06, 0x05, 0x2b, 0x0e | |||||
, 0x03, 0x02, 0x1a, 0x04, 0x14 }; | |||||
break; | |||||
case sha224: result = new byte[] | |||||
{ 0x30, 0x2b, 0x30, 0x0b, 0x06, 0x09, 0x60, (byte) 0x86 | |||||
, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x04, 0x1c }; | |||||
break; | |||||
case sha256: result = new byte[] | |||||
{ 0x30, 0x2f, 0x30, 0x0b, 0x06, 0x09, 0x60, (byte) 0x86 | |||||
, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x04, 0x20 }; | |||||
break; | |||||
case sha384: result = new byte[] | |||||
{ 0x30, 0x3f, 0x30, 0x0b, 0x06, 0x09, 0x60, (byte) 0x86 | |||||
, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x04, 0x30 }; | |||||
break; | |||||
case sha512: result = new byte[] | |||||
{ 0x30, 0x4f, 0x30, 0x0b, 0x06, 0x09, 0x60, (byte) 0x86 | |||||
, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x04, 0x40 }; | |||||
break; | |||||
case ripemd128: result = new byte[] | |||||
{ 0x30, 0x1b, 0x30, 0x07, 0x06, 0x05, 0x2b, 0x24 | |||||
, 0x03, 0x02, 0x02, 0x04, 0x10 }; | |||||
break; | |||||
case ripemd160: result = new byte[] | |||||
{ 0x30, 0x1f, 0x30, 0x07, 0x06, 0x05, 0x2b, 0x24 | |||||
, 0x03, 0x02, 0x01, 0x04, 0x14 }; | |||||
break; | |||||
// case ripemd256: result = new byte[] | |||||
// { 0x30, 0x2b, 0x30, 0x07, 0x06, 0x05, 0x2b, 0x24 | |||||
// , 0x03, 0x02, 0x03, 0x04, 0x20 }; | |||||
// break; | |||||
default: throw new EncryptedDocumentException("Hash algorithm " | |||||
+getDigestAlgo()+" not supported for signing."); | |||||
} | |||||
return result; | |||||
} | |||||
public String getSignatureMethod() { | |||||
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; | |||||
default: throw new EncryptedDocumentException("Hash algorithm " | |||||
+getDigestAlgo()+" not supported for signing."); | |||||
} | |||||
} | |||||
public String getDigestMethodUri() { | |||||
return getDigestMethodUri(getDigestAlgo()); | |||||
} | |||||
public static String getDigestMethodUri(HashAlgorithm digestAlgo) { | |||||
switch (digestAlgo) { | |||||
case sha1: return DigestMethod.SHA1; | |||||
case sha224: return "http://www.w3.org/2001/04/xmldsig-more#sha224"; | |||||
case sha256: return DigestMethod.SHA256; | |||||
case sha384: return "http://www.w3.org/2001/04/xmldsig-more#sha384"; | |||||
case sha512: return DigestMethod.SHA512; | |||||
case ripemd160: return DigestMethod.RIPEMD160; | |||||
default: throw new EncryptedDocumentException("Hash algorithm " | |||||
+digestAlgo+" not supported for signing."); | |||||
} | |||||
} | |||||
} | } |
package org.apache.poi.poifs.crypt.dsig; | package org.apache.poi.poifs.crypt.dsig; | ||||
import static org.apache.poi.poifs.crypt.dsig.facets.SignatureFacet.XML_DIGSIG_NS; | import static org.apache.poi.poifs.crypt.dsig.facets.SignatureFacet.XML_DIGSIG_NS; | ||||
import static org.apache.xml.security.signature.XMLSignature.ALGO_ID_MAC_HMAC_RIPEMD160; | |||||
import static org.apache.xml.security.signature.XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA1; | |||||
import static org.apache.xml.security.signature.XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA256; | |||||
import static org.apache.xml.security.signature.XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA384; | |||||
import static org.apache.xml.security.signature.XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA512; | |||||
import java.io.ByteArrayOutputStream; | import java.io.ByteArrayOutputStream; | ||||
import java.io.File; | import java.io.File; | ||||
public class SignatureInfo implements SignatureConfigurable { | public class SignatureInfo implements SignatureConfigurable { | ||||
// see https://www.ietf.org/rfc/rfc3110.txt | |||||
// RSA/SHA1 SIG Resource Records | |||||
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 final POILogger LOG = POILogFactory.getLogger(SignatureInfo.class); | ||||
private static boolean isInitialized = false; | private static boolean isInitialized = false; | ||||
public class SignaturePart { | public class SignaturePart { | ||||
private final PackagePart signaturePart; | private final PackagePart signaturePart; | ||||
private X509Certificate signer; | private X509Certificate signer; | ||||
private List<X509Certificate> certChain; | |||||
private SignaturePart(PackagePart signaturePart) { | private SignaturePart(PackagePart signaturePart) { | ||||
this.signaturePart = signaturePart; | this.signaturePart = signaturePart; | ||||
return signer; | return signer; | ||||
} | } | ||||
public List<X509Certificate> getCertChain() { | |||||
return certChain; | |||||
} | |||||
public SignatureDocument getSignatureDocument() throws IOException, XmlException { | public SignatureDocument getSignatureDocument() throws IOException, XmlException { | ||||
// TODO: check for XXE | // TODO: check for XXE | ||||
return SignatureDocument.Factory.parse(signaturePart.getInputStream()); | return SignatureDocument.Factory.parse(signaturePart.getInputStream()); | ||||
boolean valid = xmlSignature.validate(domValidateContext); | boolean valid = xmlSignature.validate(domValidateContext); | ||||
if (valid) { | if (valid) { | ||||
signer = keySelector.getCertificate(); | |||||
signer = keySelector.getSigner(); | |||||
certChain = keySelector.getCertChain(); | |||||
} | } | ||||
return valid; | return valid; | ||||
try { | try { | ||||
ByteArrayOutputStream digestInfoValueBuf = new ByteArrayOutputStream(); | ByteArrayOutputStream digestInfoValueBuf = new ByteArrayOutputStream(); | ||||
digestInfoValueBuf.write(getHashMagic()); | |||||
digestInfoValueBuf.write(signatureConfig.getHashMagic()); | |||||
digestInfoValueBuf.write(digest); | digestInfoValueBuf.write(digest); | ||||
byte[] digestInfoValue = digestInfoValueBuf.toByteArray(); | byte[] digestInfoValue = digestInfoValueBuf.toByteArray(); | ||||
byte[] signatureValue = cipher.doFinal(digestInfoValue); | byte[] signatureValue = cipher.doFinal(digestInfoValue); | ||||
throw new RuntimeException("JRE doesn't support default xml signature provider - set jsr105Provider system property!"); | throw new RuntimeException("JRE doesn't support default xml signature provider - set jsr105Provider system property!"); | ||||
} | } | ||||
protected byte[] getHashMagic() { | |||||
switch (signatureConfig.getDigestAlgo()) { | |||||
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 "+signatureConfig.getDigestAlgo()+" not supported for signing."); | |||||
} | |||||
} | |||||
protected String getSignatureMethod() { | |||||
switch (signatureConfig.getDigestAlgo()) { | |||||
case sha1: return ALGO_ID_SIGNATURE_RSA_SHA1; | |||||
case sha256: return ALGO_ID_SIGNATURE_RSA_SHA256; | |||||
case sha384: return ALGO_ID_SIGNATURE_RSA_SHA384; | |||||
case sha512: return ALGO_ID_SIGNATURE_RSA_SHA512; | |||||
case ripemd160: return ALGO_ID_MAC_HMAC_RIPEMD160; | |||||
default: throw new EncryptedDocumentException("Hash algorithm "+signatureConfig.getDigestAlgo()+" not supported for signing."); | |||||
} | |||||
} | |||||
protected static synchronized void initXmlProvider() { | protected static synchronized void initXmlProvider() { | ||||
if (isInitialized) return; | if (isInitialized) return; | ||||
isInitialized = true; | isInitialized = true; | ||||
for (DigestInfo digestInfo : safe(digestInfos)) { | for (DigestInfo digestInfo : safe(digestInfos)) { | ||||
byte[] documentDigestValue = digestInfo.digestValue; | byte[] documentDigestValue = digestInfo.digestValue; | ||||
DigestMethod digestMethod = signatureFactory.newDigestMethod( | |||||
digestInfo.hashAlgo.xmlSignUri, null); | |||||
DigestMethod digestMethod = signatureFactory.newDigestMethod | |||||
(signatureConfig.getDigestMethodUri(), null); | |||||
String uri = new File(digestInfo.description).getName(); | String uri = new File(digestInfo.description).getName(); | ||||
/* | /* | ||||
* ds:SignedInfo | * ds:SignedInfo | ||||
*/ | */ | ||||
SignatureMethod signatureMethod = signatureFactory.newSignatureMethod(getSignatureMethod(), null); | |||||
SignatureMethod signatureMethod = signatureFactory.newSignatureMethod | |||||
(signatureConfig.getSignatureMethod(), null); | |||||
CanonicalizationMethod canonicalizationMethod = signatureFactory | CanonicalizationMethod canonicalizationMethod = signatureFactory | ||||
.newCanonicalizationMethod(signatureConfig.getCanonicalizationMethod(), | .newCanonicalizationMethod(signatureConfig.getCanonicalizationMethod(), | ||||
(C14NMethodParameterSpec) null); | (C14NMethodParameterSpec) null); |
, List<Reference> references | , List<Reference> references | ||||
, List<XMLObject> objects) | , List<XMLObject> objects) | ||||
throws NoSuchAlgorithmException, InvalidAlgorithmParameterException { | throws NoSuchAlgorithmException, InvalidAlgorithmParameterException { | ||||
DigestMethod digestMethod = signatureFactory.newDigestMethod(signatureConfig.getDigestAlgo().xmlSignUri, null); | |||||
DigestMethod digestMethod = signatureFactory.newDigestMethod | |||||
(signatureConfig.getDigestMethodUri(), null); | |||||
List<Transform> transforms = new ArrayList<Transform>(); | List<Transform> transforms = new ArrayList<Transform>(); | ||||
Transform envelopedTransform = signatureFactory | |||||
.newTransform(CanonicalizationMethod.ENVELOPED, | |||||
(TransformParameterSpec) null); | |||||
Transform envelopedTransform = signatureFactory.newTransform | |||||
(CanonicalizationMethod.ENVELOPED, (TransformParameterSpec) null); | |||||
transforms.add(envelopedTransform); | transforms.add(envelopedTransform); | ||||
Transform exclusiveTransform = signatureFactory | |||||
.newTransform(CanonicalizationMethod.EXCLUSIVE, | |||||
(TransformParameterSpec) null); | |||||
Transform exclusiveTransform = signatureFactory.newTransform | |||||
(CanonicalizationMethod.EXCLUSIVE, (TransformParameterSpec) null); | |||||
transforms.add(exclusiveTransform); | transforms.add(exclusiveTransform); | ||||
Reference reference = signatureFactory.newReference("", digestMethod, | Reference reference = signatureFactory.newReference("", digestMethod, |
XMLObject xo = signatureFactory.newXMLObject(objectContent, objectId, null, null); | XMLObject xo = signatureFactory.newXMLObject(objectContent, objectId, null, null); | ||||
objects.add(xo); | objects.add(xo); | ||||
DigestMethod digestMethod = signatureFactory.newDigestMethod(signatureConfig.getDigestAlgo().xmlSignUri, null); | |||||
DigestMethod digestMethod = signatureFactory.newDigestMethod | |||||
(signatureConfig.getDigestMethodUri(), null); | |||||
Reference reference = signatureFactory.newReference | Reference reference = signatureFactory.newReference | ||||
("#" + objectId, digestMethod, null, XML_DIGSIG_NS+"Object", null); | ("#" + objectId, digestMethod, null, XML_DIGSIG_NS+"Object", null); | ||||
references.add(reference); | references.add(reference); | ||||
OPCPackage ooxml = signatureConfig.getOpcPackage(); | OPCPackage ooxml = signatureConfig.getOpcPackage(); | ||||
List<PackagePart> relsEntryNames = ooxml.getPartsByContentType(ContentTypes.RELATIONSHIPS_PART); | List<PackagePart> relsEntryNames = ooxml.getPartsByContentType(ContentTypes.RELATIONSHIPS_PART); | ||||
DigestMethod digestMethod = signatureFactory.newDigestMethod(signatureConfig.getDigestAlgo().xmlSignUri, null); | |||||
DigestMethod digestMethod = signatureFactory.newDigestMethod | |||||
(signatureConfig.getDigestMethodUri(), null); | |||||
Set<String> digestedPartNames = new HashSet<String>(); | Set<String> digestedPartNames = new HashSet<String>(); | ||||
for (PackagePart pp : relsEntryNames) { | for (PackagePart pp : relsEntryNames) { | ||||
String baseUri = pp.getPartName().getName().replaceFirst("(.*)/_rels/.*", "$1"); | String baseUri = pp.getPartName().getName().replaceFirst("(.*)/_rels/.*", "$1"); | ||||
SignatureInfoV1Document sigV1 = SignatureInfoV1Document.Factory.newInstance(); | SignatureInfoV1Document sigV1 = SignatureInfoV1Document.Factory.newInstance(); | ||||
CTSignatureInfoV1 ctSigV1 = sigV1.addNewSignatureInfoV1(); | CTSignatureInfoV1 ctSigV1 = sigV1.addNewSignatureInfoV1(); | ||||
ctSigV1.setManifestHashAlgorithm(signatureConfig.getDigestAlgo().xmlSignUri); | |||||
ctSigV1.setManifestHashAlgorithm(signatureConfig.getDigestMethodUri()); | |||||
Element n = (Element)document.importNode(ctSigV1.getDomNode(), true); | Element n = (Element)document.importNode(ctSigV1.getDomNode(), true); | ||||
n.setAttributeNS(XML_NS, XMLConstants.XMLNS_ATTRIBUTE, MS_DIGSIG_NS); | n.setAttributeNS(XML_NS, XMLConstants.XMLNS_ATTRIBUTE, MS_DIGSIG_NS); | ||||
String objectId = "idOfficeObject"; | String objectId = "idOfficeObject"; | ||||
objects.add(signatureFactory.newXMLObject(objectContent, objectId, null, null)); | objects.add(signatureFactory.newXMLObject(objectContent, objectId, null, null)); | ||||
DigestMethod digestMethod = signatureFactory.newDigestMethod(signatureConfig.getDigestAlgo().xmlSignUri, null); | |||||
DigestMethod digestMethod = signatureFactory.newDigestMethod | |||||
(signatureConfig.getDigestMethodUri(), null); | |||||
Reference reference = signatureFactory.newReference | Reference reference = signatureFactory.newReference | ||||
("#" + objectId, digestMethod, null, XML_DIGSIG_NS+"Object", null); | ("#" + objectId, digestMethod, null, XML_DIGSIG_NS+"Object", null); | ||||
references.add(reference); | references.add(reference); |
objects.add(xadesObject); | objects.add(xadesObject); | ||||
// add XAdES ds:Reference | // add XAdES ds:Reference | ||||
DigestMethod digestMethod = signatureFactory.newDigestMethod(signatureConfig.getDigestAlgo().xmlSignUri, null); | |||||
DigestMethod digestMethod = signatureFactory.newDigestMethod(signatureConfig.getDigestMethodUri(), null); | |||||
List<Transform> transforms = new ArrayList<Transform>(); | List<Transform> transforms = new ArrayList<Transform>(); | ||||
Transform exclusiveTransform = signatureFactory | Transform exclusiveTransform = signatureFactory | ||||
.newTransform(CanonicalizationMethod.INCLUSIVE, | .newTransform(CanonicalizationMethod.INCLUSIVE, | ||||
protected static void setDigestAlgAndValue( | protected static void setDigestAlgAndValue( | ||||
DigestAlgAndValueType digestAlgAndValue, | DigestAlgAndValueType digestAlgAndValue, | ||||
byte[] data, | byte[] data, | ||||
HashAlgorithm hashAlgo) { | |||||
HashAlgorithm digestAlgo) { | |||||
DigestMethodType digestMethod = digestAlgAndValue.addNewDigestMethod(); | DigestMethodType digestMethod = digestAlgAndValue.addNewDigestMethod(); | ||||
digestMethod.setAlgorithm(hashAlgo.xmlSignUri); | |||||
digestMethod.setAlgorithm(SignatureConfig.getDigestMethodUri(digestAlgo)); | |||||
MessageDigest messageDigest = CryptoFunctions.getMessageDigest(hashAlgo); | |||||
MessageDigest messageDigest = CryptoFunctions.getMessageDigest(digestAlgo); | |||||
byte[] digestValue = messageDigest.digest(data); | byte[] digestValue = messageDigest.digest(data); | ||||
digestAlgAndValue.setDigestValue(digestValue); | digestAlgAndValue.setDigestValue(digestValue); | ||||
} | } |
package org.apache.poi.poifs.crypt; | package org.apache.poi.poifs.crypt; | ||||
import static org.junit.Assert.assertEquals; | import static org.junit.Assert.assertEquals; | ||||
import static org.junit.Assert.assertFalse; | |||||
import static org.junit.Assert.assertNotNull; | import static org.junit.Assert.assertNotNull; | ||||
import static org.junit.Assert.assertTrue; | import static org.junit.Assert.assertTrue; | ||||
import java.io.FileInputStream; | import java.io.FileInputStream; | ||||
import java.io.FileOutputStream; | import java.io.FileOutputStream; | ||||
import java.io.IOException; | import java.io.IOException; | ||||
import java.io.InputStream; | |||||
import java.lang.reflect.Method; | |||||
import java.net.MalformedURLException; | import java.net.MalformedURLException; | ||||
import java.net.URL; | import java.net.URL; | ||||
import java.net.URLClassLoader; | import java.net.URLClassLoader; | ||||
import org.apache.poi.util.IOUtils; | import org.apache.poi.util.IOUtils; | ||||
import org.apache.poi.util.POILogFactory; | import org.apache.poi.util.POILogFactory; | ||||
import org.apache.poi.util.POILogger; | import org.apache.poi.util.POILogger; | ||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook; | |||||
import org.apache.xmlbeans.XmlObject; | import org.apache.xmlbeans.XmlObject; | ||||
import org.bouncycastle.asn1.x509.KeyUsage; | import org.bouncycastle.asn1.x509.KeyUsage; | ||||
import org.bouncycastle.cert.ocsp.OCSPResp; | import org.bouncycastle.cert.ocsp.OCSPResp; | ||||
pkg.close(); | pkg.close(); | ||||
} | } | ||||
@Test | |||||
public void testManipulation() throws Exception { | |||||
// sign & validate | |||||
String testFile = "hello-world-unsigned.xlsx"; | |||||
OPCPackage pkg = OPCPackage.open(copy(testdata.getFile(testFile)), PackageAccess.READ_WRITE); | |||||
sign(pkg, "Test", "CN=Test", 1); | |||||
// manipulate | |||||
XSSFWorkbook wb = new XSSFWorkbook(pkg); | |||||
wb.setSheetName(0, "manipulated"); | |||||
// ... I don't know, why commit is protected ... | |||||
Method m = XSSFWorkbook.class.getDeclaredMethod("commit"); | |||||
m.setAccessible(true); | |||||
m.invoke(wb); | |||||
// todo: test a manipulation on a package part, which is not signed | |||||
// ... maybe in combination with #56164 | |||||
// validate | |||||
SignatureConfig sic = new SignatureConfig(); | |||||
sic.setOpcPackage(pkg); | |||||
SignatureInfo si = new SignatureInfo(); | |||||
si.setSignatureConfig(sic); | |||||
boolean b = si.verifySignature(); | |||||
assertFalse("signature should be broken", b); | |||||
wb.close(); | |||||
} | |||||
@Test | @Test | ||||
public void testSignSpreadsheetWithSignatureInfo() throws Exception { | public void testSignSpreadsheetWithSignatureInfo() throws Exception { | ||||
initKeyPair("Test", "CN=Test"); | initKeyPair("Test", "CN=Test"); | ||||
"$this/ds:Signature/ds:SignedInfo/ds:Reference"; | "$this/ds:Signature/ds:SignedInfo/ds:Reference"; | ||||
for (ReferenceType rt : (ReferenceType[])sigDoc.selectPath(digestValXQuery)) { | for (ReferenceType rt : (ReferenceType[])sigDoc.selectPath(digestValXQuery)) { | ||||
assertNotNull(rt.getDigestValue()); | assertNotNull(rt.getDigestValue()); | ||||
assertEquals(HashAlgorithm.sha1.xmlSignUri, rt.getDigestMethod().getAlgorithm()); | |||||
assertEquals(signatureConfig.getDigestMethodUri(), rt.getDigestMethod().getAlgorithm()); | |||||
} | } | ||||
String certDigestXQuery = declareNS + | String certDigestXQuery = declareNS + | ||||
pkg.close(); | pkg.close(); | ||||
} | } | ||||
@Test | |||||
public void testCertChain() throws Exception { | |||||
KeyStore keystore = KeyStore.getInstance("PKCS12"); | |||||
String password = "test"; | |||||
InputStream is = testdata.openResourceAsStream("chaintest.pfx"); | |||||
keystore.load(is, password.toCharArray()); | |||||
is.close(); | |||||
Key key = keystore.getKey("poitest", password.toCharArray()); | |||||
Certificate chainList[] = keystore.getCertificateChain("poitest"); | |||||
List<X509Certificate> certChain = new ArrayList<X509Certificate>(); | |||||
for (Certificate c : chainList) { | |||||
certChain.add((X509Certificate)c); | |||||
} | |||||
x509 = certChain.get(0); | |||||
keyPair = new KeyPair(x509.getPublicKey(), (PrivateKey)key); | |||||
String testFile = "hello-world-unsigned.xlsx"; | |||||
OPCPackage pkg = OPCPackage.open(copy(testdata.getFile(testFile)), PackageAccess.READ_WRITE); | |||||
SignatureConfig signatureConfig = new SignatureConfig(); | |||||
signatureConfig.setKey(keyPair.getPrivate()); | |||||
signatureConfig.setSigningCertificateChain(certChain); | |||||
Calendar cal = Calendar.getInstance(); | |||||
cal.set(2007, 7, 1); | |||||
signatureConfig.setExecutionTime(cal.getTime()); | |||||
signatureConfig.setDigestAlgo(HashAlgorithm.sha1); | |||||
signatureConfig.setOpcPackage(pkg); | |||||
SignatureInfo si = new SignatureInfo(); | |||||
si.setSignatureConfig(signatureConfig); | |||||
si.confirmSignature(); | |||||
for (SignaturePart sp : si.getSignatureParts()){ | |||||
boolean b = sp.validate(); | |||||
assertTrue(b); | |||||
X509Certificate signer = sp.getSigner(); | |||||
assertNotNull("signer undefined?!", signer); | |||||
List<X509Certificate> certChainRes = sp.getCertChain(); | |||||
assertEquals(3, certChainRes.size()); | |||||
} | |||||
pkg.close(); | |||||
} | |||||
@Test | |||||
public void testNonSha1() throws Exception { | |||||
String testFile = "hello-world-unsigned.xlsx"; | |||||
initKeyPair("Test", "CN=Test"); | |||||
SignatureConfig signatureConfig = new SignatureConfig(); | |||||
signatureConfig.setKey(keyPair.getPrivate()); | |||||
signatureConfig.setSigningCertificateChain(Collections.singletonList(x509)); | |||||
HashAlgorithm testAlgo[] = { HashAlgorithm.sha224, HashAlgorithm.sha256 | |||||
, HashAlgorithm.sha384, HashAlgorithm.sha512, HashAlgorithm.ripemd160 }; | |||||
for (HashAlgorithm ha : testAlgo) { | |||||
signatureConfig.setDigestAlgo(ha); | |||||
OPCPackage pkg = OPCPackage.open(copy(testdata.getFile(testFile)), PackageAccess.READ_WRITE); | |||||
signatureConfig.setOpcPackage(pkg); | |||||
SignatureInfo si = new SignatureInfo(); | |||||
si.setSignatureConfig(signatureConfig); | |||||
si.confirmSignature(); | |||||
boolean b = si.verifySignature(); | |||||
pkg.close(); | |||||
private OPCPackage sign(OPCPackage pkgCopy, String alias, String signerDn, int signerCount) throws Exception { | |||||
assertTrue(b); | |||||
} | |||||
} | |||||
private void sign(OPCPackage pkgCopy, String alias, String signerDn, int signerCount) throws Exception { | |||||
initKeyPair(alias, signerDn); | initKeyPair(alias, signerDn); | ||||
SignatureConfig signatureConfig = new SignatureConfig(); | SignatureConfig signatureConfig = new SignatureConfig(); | ||||
} | } | ||||
} | } | ||||
assertEquals(signerCount, result.size()); | assertEquals(signerCount, result.size()); | ||||
return pkgCopy; | |||||
} | } | ||||
private void initKeyPair(String alias, String subjectDN) throws Exception { | private void initKeyPair(String alias, String subjectDN) throws Exception { |