From 37e8f7249358721262972ab2817806ad678f3e7f Mon Sep 17 00:00:00 2001 From: Andreas Beeker Date: Sun, 7 Nov 2021 23:48:15 +0000 Subject: [PATCH] #65672 - Digitial Signature - set commitment type and purpose git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1894820 13f79535-47bb-0310-9956-ffa450edef68 --- .../poi/poifs/crypt/dsig/SignatureConfig.java | 19 + .../dsig/SignatureMarshalDefaultListener.java | 25 +- .../poi/poifs/crypt/dsig/SignaturePart.java | 1 + .../dsig/facets/OOXMLSignatureFacet.java | 51 ++- .../dsig/facets/XAdESSignatureFacet.java | 69 ++- .../poi/poifs/crypt/dsig/DummyKeystore.java | 355 +++++++++++++++ .../poifs/crypt/dsig/TestSignatureInfo.java | 428 ++++-------------- 7 files changed, 572 insertions(+), 376 deletions(-) create mode 100644 poi-ooxml/src/test/java/org/apache/poi/poifs/crypt/dsig/DummyKeystore.java diff --git a/poi-ooxml/src/main/java/org/apache/poi/poifs/crypt/dsig/SignatureConfig.java b/poi-ooxml/src/main/java/org/apache/poi/poifs/crypt/dsig/SignatureConfig.java index 1a6006c0f9..b2b0b66e94 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/poifs/crypt/dsig/SignatureConfig.java +++ b/poi-ooxml/src/main/java/org/apache/poi/poifs/crypt/dsig/SignatureConfig.java @@ -219,6 +219,8 @@ public class SignatureConfig { */ private boolean secureValidation = true; + private String commitmentType = "Created and approved this document"; + public SignatureConfig() { // OOo doesn't like ds namespaces so per default prefixing is off. @@ -1117,4 +1119,21 @@ public class SignatureConfig { public void setSecureValidation(boolean secureValidation) { this.secureValidation = secureValidation; } + + public String getCommitmentType() { + return commitmentType; + } + + /** + * Set the commitmentType, which is usually one of ... + * + */ + public void setCommitmentType(String commitmentType) { + this.commitmentType = commitmentType; + } } \ No newline at end of file diff --git a/poi-ooxml/src/main/java/org/apache/poi/poifs/crypt/dsig/SignatureMarshalDefaultListener.java b/poi-ooxml/src/main/java/org/apache/poi/poifs/crypt/dsig/SignatureMarshalDefaultListener.java index 2c7ccdabe9..2ffc2cfed3 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/poifs/crypt/dsig/SignatureMarshalDefaultListener.java +++ b/poi-ooxml/src/main/java/org/apache/poi/poifs/crypt/dsig/SignatureMarshalDefaultListener.java @@ -25,6 +25,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; +import java.util.function.Consumer; import org.w3c.dom.Document; import org.w3c.dom.Element; @@ -43,7 +44,7 @@ import org.w3c.dom.traversal.NodeIterator; */ public class SignatureMarshalDefaultListener implements SignatureMarshalListener { private static final String OBJECT_TAG = "Object"; - private final Set IGNORE_NS = new HashSet<>(Arrays.asList(null, XML_NS, XML_DIGSIG_NS)); + private static final Set IGNORE_NS = new HashSet<>(Arrays.asList(null, XML_NS, XML_DIGSIG_NS)); @Override public void handleElement(SignatureInfo signatureInfo, Document doc, EventTarget target, EventListener parentListener) { @@ -52,15 +53,23 @@ public class SignatureMarshalDefaultListener implements SignatureMarshalListener final DocumentTraversal traversal = (DocumentTraversal) doc; final Map prefixCfg = signatureInfo.getSignatureConfig().getNamespacePrefixes(); - final Map prefixUsed = new HashMap<>(); - NodeList nl = doc.getElementsByTagName(OBJECT_TAG); - final int objLen = nl.getLength(); - for (int i=0; i objNode.setAttributeNS(XML_NS, "xmlns:"+prefix, ns)); + forEachElement(doc.getElementsByTagName(OBJECT_TAG), (o) -> { + forEachElement(o.getChildNodes(), (c) -> { + getAllNamespaces(traversal, c, prefixCfg, prefixUsed); + prefixUsed.forEach((ns, prefix) -> c.setAttributeNS(XML_NS, "xmlns:"+prefix, ns)); + }); + }); + } + + private static void forEachElement(NodeList nl, Consumer consumer) { + int len = nl.getLength(); + for(int i=0; i> me : m.entrySet()) { String val = (String)xpath.compile(me.getKey()).evaluate(doc, XPathConstants.STRING); diff --git a/poi-ooxml/src/main/java/org/apache/poi/poifs/crypt/dsig/facets/OOXMLSignatureFacet.java b/poi-ooxml/src/main/java/org/apache/poi/poifs/crypt/dsig/facets/OOXMLSignatureFacet.java index b9063e30fc..265c620ec8 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/poifs/crypt/dsig/facets/OOXMLSignatureFacet.java +++ b/poi-ooxml/src/main/java/org/apache/poi/poifs/crypt/dsig/facets/OOXMLSignatureFacet.java @@ -30,7 +30,6 @@ import static org.apache.poi.poifs.crypt.dsig.facets.SignatureFacetHelper.newTra import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; -import java.util.Arrays; import java.util.Base64; import java.util.Collections; import java.util.Comparator; @@ -260,25 +259,8 @@ public class OOXMLSignatureFacet implements SignatureFacet { List objectContent = new ArrayList<>(); - SignatureInfoV1Document sigV1 = SignatureInfoV1Document.Factory.newInstance(); - CTSignatureInfoV1 ctSigV1 = sigV1.addNewSignatureInfoV1(); - if (signatureConfig.getDigestAlgo() != HashAlgorithm.sha1) { - ctSigV1.setManifestHashAlgorithm(signatureConfig.getDigestMethodUri()); - } - - String desc = signatureConfig.getSignatureDescription(); - if (desc != null) { - ctSigV1.setSignatureComments(desc); - } - - byte[] image = signatureConfig.getSignatureImage(); - if (image != null) { - ctSigV1.setSetupID(signatureConfig.getSignatureImageSetupId().toString()); - ctSigV1.setSignatureImage(image); - ctSigV1.setSignatureType(2); - } - - Element n = (Element)document.importNode(ctSigV1.getDomNode(), true); + SignatureInfoV1Document sigV1 = createSignatureInfoV1(signatureInfo); + Element n = (Element)document.importNode(sigV1.getSignatureInfoV1().getDomNode(), true); n.setAttributeNS(XML_NS, XMLConstants.XMLNS_ATTRIBUTE, MS_DIGSIG_NS); List signatureInfoContent = new ArrayList<>(); @@ -321,6 +303,35 @@ public class OOXMLSignatureFacet implements SignatureFacet { } } + /** + * Create SignatureInfoV1 element. This method can be easily extended by subclasses, to fill its other elements. + */ + protected SignatureInfoV1Document createSignatureInfoV1(SignatureInfo signatureInfo) { + SignatureConfig signatureConfig = signatureInfo.getSignatureConfig(); + + SignatureInfoV1Document sigV1 = SignatureInfoV1Document.Factory.newInstance(); + CTSignatureInfoV1 ctSigV1 = sigV1.addNewSignatureInfoV1(); + if (signatureConfig.getDigestAlgo() != HashAlgorithm.sha1) { + ctSigV1.setManifestHashAlgorithm(signatureConfig.getDigestMethodUri()); + } + + String desc = signatureConfig.getSignatureDescription(); + if (desc != null) { + ctSigV1.setSignatureComments(desc); + } + + byte[] image = signatureConfig.getSignatureImage(); + if (image == null) { + ctSigV1.setSignatureType(1); + } else { + ctSigV1.setSetupID(signatureConfig.getSignatureImageSetupId().toString()); + ctSigV1.setSignatureImage(image); + ctSigV1.setSignatureType(2); + } + + return sigV1; + } + protected static String getRelationshipReferenceURI(String zipEntryName) { return "/" + zipEntryName diff --git a/poi-ooxml/src/main/java/org/apache/poi/poifs/crypt/dsig/facets/XAdESSignatureFacet.java b/poi-ooxml/src/main/java/org/apache/poi/poifs/crypt/dsig/facets/XAdESSignatureFacet.java index d20912a519..0d782b8b04 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/poifs/crypt/dsig/facets/XAdESSignatureFacet.java +++ b/poi-ooxml/src/main/java/org/apache/poi/poifs/crypt/dsig/facets/XAdESSignatureFacet.java @@ -63,6 +63,7 @@ import org.etsi.uri.x01903.v13.AnyType; import org.etsi.uri.x01903.v13.CertIDListType; import org.etsi.uri.x01903.v13.CertIDType; import org.etsi.uri.x01903.v13.ClaimedRolesListType; +import org.etsi.uri.x01903.v13.CommitmentTypeIndicationType; import org.etsi.uri.x01903.v13.DataObjectFormatType; import org.etsi.uri.x01903.v13.DigestAlgAndValueType; import org.etsi.uri.x01903.v13.ObjectIdentifierType; @@ -70,6 +71,7 @@ import org.etsi.uri.x01903.v13.QualifyingPropertiesDocument; import org.etsi.uri.x01903.v13.QualifyingPropertiesType; import org.etsi.uri.x01903.v13.SignaturePolicyIdType; import org.etsi.uri.x01903.v13.SignaturePolicyIdentifierType; +import org.etsi.uri.x01903.v13.SignedDataObjectPropertiesType; import org.etsi.uri.x01903.v13.SignedPropertiesType; import org.etsi.uri.x01903.v13.SignedSignaturePropertiesType; import org.etsi.uri.x01903.v13.SignerRoleType; @@ -114,9 +116,22 @@ public class XAdESSignatureFacet implements SignatureFacet { QualifyingPropertiesType qualifyingProperties = qualDoc.addNewQualifyingProperties(); qualifyingProperties.setTarget("#" + signatureConfig.getPackageSignatureId()); + createSignedProperties(signatureInfo, qualifyingProperties); + + // add XAdES ds:Object + objects.add(addXadesObject(signatureInfo, document, qualifyingProperties)); + + // add XAdES ds:Reference + references.add(addXadesReference(signatureInfo)); + } + + /** + * Create the signedProperties. Subclasses can easily extend the signed properties. + */ + protected SignedPropertiesType createSignedProperties(SignatureInfo signatureInfo, QualifyingPropertiesType qualifyingProperties) { // SignedProperties SignedPropertiesType signedProperties = qualifyingProperties.addNewSignedProperties(); - signedProperties.setId(signatureConfig.getXadesSignatureId()); + signedProperties.setId(signatureInfo.getSignatureConfig().getXadesSignatureId()); // SignedSignatureProperties SignedSignaturePropertiesType signedSignatureProperties = signedProperties.addNewSignedSignatureProperties(); @@ -134,16 +149,14 @@ public class XAdESSignatureFacet implements SignatureFacet { addPolicy(signatureInfo, signedSignatureProperties); // DataObjectFormat - addMimeTypes(signedProperties); + addMimeTypes(signatureInfo, signedProperties); - // add XAdES ds:Object - objects.add(addXadesObject(signatureInfo, document, qualifyingProperties)); + addCommitmentType(signatureInfo, signedProperties); - // add XAdES ds:Reference - references.add(addXadesReference(signatureInfo)); + return signedProperties; } - private void addSigningTime(SignatureInfo signatureInfo, SignedSignaturePropertiesType signedSignatureProperties) { + protected void addSigningTime(SignatureInfo signatureInfo, SignedSignaturePropertiesType signedSignatureProperties) { SignatureConfig signatureConfig = signatureInfo.getSignatureConfig(); Calendar xmlGregorianCalendar = Calendar.getInstance(TimeZone.getTimeZone("Z"), Locale.ROOT); xmlGregorianCalendar.setTime(signatureConfig.getExecutionTime()); @@ -151,7 +164,7 @@ public class XAdESSignatureFacet implements SignatureFacet { signedSignatureProperties.setSigningTime(xmlGregorianCalendar); } - private void addCertificate(SignatureInfo signatureInfo, SignedSignaturePropertiesType signedSignatureProperties) { + protected void addCertificate(SignatureInfo signatureInfo, SignedSignaturePropertiesType signedSignatureProperties) { SignatureConfig signatureConfig = signatureInfo.getSignatureConfig(); List chain = signatureConfig.getSigningCertificateChain(); if (chain == null || chain.isEmpty()) { @@ -162,7 +175,7 @@ public class XAdESSignatureFacet implements SignatureFacet { setCertID(certId, signatureConfig, signatureConfig.isXadesIssuerNameNoReverseOrder(), chain.get(0)); } - private void addXadesRole(SignatureInfo signatureInfo, SignedSignaturePropertiesType signedSignatureProperties) { + protected void addXadesRole(SignatureInfo signatureInfo, SignedSignaturePropertiesType signedSignatureProperties) { SignatureConfig signatureConfig = signatureInfo.getSignatureConfig(); String role = signatureConfig.getXadesRole(); if (role == null || role.isEmpty()) { @@ -178,7 +191,7 @@ public class XAdESSignatureFacet implements SignatureFacet { insertXChild(claimedRole, roleString); } - private void addPolicy(SignatureInfo signatureInfo, SignedSignaturePropertiesType signedSignatureProperties) { + protected void addPolicy(SignatureInfo signatureInfo, SignedSignaturePropertiesType signedSignatureProperties) { SignatureConfig signatureConfig = signatureInfo.getSignatureConfig(); SignaturePolicyService policyService = signatureConfig.getSignaturePolicyService(); if (policyService == null) { @@ -214,7 +227,7 @@ public class XAdESSignatureFacet implements SignatureFacet { insertXChild(sigPolicyQualifier, spUriElement); } - private void addMimeTypes(SignedPropertiesType signedProperties) { + protected void addMimeTypes(SignatureInfo signatureInfo, SignedPropertiesType signedProperties) { if (dataObjectFormatMimeTypes.isEmpty()) { return; } @@ -232,14 +245,40 @@ public class XAdESSignatureFacet implements SignatureFacet { }); } - private XMLObject addXadesObject(SignatureInfo signatureInfo, Document document, QualifyingPropertiesType qualifyingProperties) { + protected XMLObject addXadesObject(SignatureInfo signatureInfo, Document document, QualifyingPropertiesType qualifyingProperties) { Element qualDocEl = importNode(document, qualifyingProperties); List xadesObjectContent = singletonList(new DOMStructure(qualDocEl)); - XMLObject xo = signatureInfo.getSignatureFactory().newXMLObject(xadesObjectContent, null, null, null); - return xo; + return signatureInfo.getSignatureFactory().newXMLObject(xadesObjectContent, null, null, null); + } + + protected void addCommitmentType(SignatureInfo signatureInfo, SignedPropertiesType signedProperties) { + SignatureConfig signatureConfig = signatureInfo.getSignatureConfig(); + String desc = signatureConfig.getSignatureDescription(); + String commit = signatureConfig.getCommitmentType(); + if (desc == null && commit == null) { + return; + } + + SignedDataObjectPropertiesType dopt = signedProperties.isSetSignedDataObjectProperties() + ? signedProperties.getSignedDataObjectProperties() + : signedProperties.addNewSignedDataObjectProperties(); + + CommitmentTypeIndicationType cti = dopt.addNewCommitmentTypeIndication(); + if (commit != null) { + ObjectIdentifierType ctid = cti.addNewCommitmentTypeId(); + ctid.addNewIdentifier().setStringValue("http://uri.etsi.org/01903/v1.2.2#ProofOfOrigin"); + ctid.setDescription(signatureConfig.getCommitmentType()); + } + if (desc != null) { + cti.addNewAllSignedDataObjects(); + AnyType ctq = cti.addNewCommitmentTypeQualifiers().addNewCommitmentTypeQualifier(); + ctq.set(XmlString.Factory.newValue(desc)); + } } - private Reference addXadesReference(SignatureInfo signatureInfo) throws XMLSignatureException { + + + protected Reference addXadesReference(SignatureInfo signatureInfo) throws XMLSignatureException { SignatureConfig signatureConfig = signatureInfo.getSignatureConfig(); List transforms = singletonList(newTransform(signatureInfo, CanonicalizationMethod.INCLUSIVE)); return newReference(signatureInfo, "#"+signatureConfig.getXadesSignatureId(), transforms, XADES_TYPE); diff --git a/poi-ooxml/src/test/java/org/apache/poi/poifs/crypt/dsig/DummyKeystore.java b/poi-ooxml/src/test/java/org/apache/poi/poifs/crypt/dsig/DummyKeystore.java new file mode 100644 index 0000000000..b5e17ac0dd --- /dev/null +++ b/poi-ooxml/src/test/java/org/apache/poi/poifs/crypt/dsig/DummyKeystore.java @@ -0,0 +1,355 @@ +/* ==================================================================== + 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.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.cert.Certificate; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.X509CRL; +import java.security.cert.X509Certificate; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.RSAKeyGenParameterSpec; +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.apache.poi.poifs.crypt.CryptoFunctions; +import org.apache.poi.poifs.storage.RawDataUtil; +import org.apache.poi.util.LocaleUtil; +import org.bouncycastle.asn1.DEROctetString; +import org.bouncycastle.asn1.ocsp.OCSPObjectIdentifiers; +import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier; +import org.bouncycastle.asn1.x509.BasicConstraints; +import org.bouncycastle.asn1.x509.CRLNumber; +import org.bouncycastle.asn1.x509.Extension; +import org.bouncycastle.asn1.x509.Extensions; +import org.bouncycastle.asn1.x509.KeyUsage; +import org.bouncycastle.asn1.x509.SubjectKeyIdentifier; +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; +import org.bouncycastle.cert.X509CRLHolder; +import org.bouncycastle.cert.X509CertificateHolder; +import org.bouncycastle.cert.X509ExtensionUtils; +import org.bouncycastle.cert.X509v2CRLBuilder; +import org.bouncycastle.cert.X509v3CertificateBuilder; +import org.bouncycastle.cert.jcajce.JcaX509CRLConverter; +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; +import org.bouncycastle.cert.ocsp.BasicOCSPResp; +import org.bouncycastle.cert.ocsp.BasicOCSPRespBuilder; +import org.bouncycastle.cert.ocsp.CertificateID; +import org.bouncycastle.cert.ocsp.CertificateStatus; +import org.bouncycastle.cert.ocsp.OCSPException; +import org.bouncycastle.cert.ocsp.OCSPReq; +import org.bouncycastle.cert.ocsp.OCSPReqBuilder; +import org.bouncycastle.cert.ocsp.OCSPResp; +import org.bouncycastle.cert.ocsp.OCSPRespBuilder; +import org.bouncycastle.cert.ocsp.Req; +import org.bouncycastle.crypto.params.RSAKeyParameters; +import org.bouncycastle.crypto.util.SubjectPublicKeyInfoFactory; +import org.bouncycastle.openssl.PEMParser; +import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; +import org.bouncycastle.operator.ContentSigner; +import org.bouncycastle.operator.DigestCalculator; +import org.bouncycastle.operator.OperatorCreationException; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; +import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder; + +public class DummyKeystore { + public static class KeyCertPair { + private final PrivateKey key; + private final List x509chain; + + public KeyCertPair(PrivateKey key, Certificate[] x509chain) { + this.key = key; + this.x509chain = Stream.of(x509chain).map(X509Certificate.class::cast).collect(Collectors.toList()); + } + + public PrivateKey getKey() { + return key; + } + + public X509Certificate getX509() { + return x509chain.get(0); + } + + public List getX509Chain() { + return x509chain; + } + } + + private static final SecureRandom RANDOM = new SecureRandom(); + private static final String DUMMY_ALIAS = "Test"; + private static final String DUMMY_PASS = "test"; + + private final KeyStore keystore; + + public DummyKeystore(String storePass) throws GeneralSecurityException, IOException { + this((File)null, storePass); + } + + public DummyKeystore(File storeFile, String storePass) throws GeneralSecurityException, IOException { + CryptoFunctions.registerBouncyCastle(); + keystore = KeyStore.getInstance("PKCS12"); + try (InputStream fis = storeFile != null && storeFile.exists() ? new FileInputStream(storeFile) : null) { + keystore.load(fis, storePass.toCharArray()); + } + } + + public DummyKeystore(String pfxInput, String storePass) throws GeneralSecurityException, IOException { + CryptoFunctions.registerBouncyCastle(); + keystore = KeyStore.getInstance("PKCS12"); + try (InputStream fis = new ByteArrayInputStream(RawDataUtil.decompress(pfxInput))) { + keystore.load(fis, storePass.toCharArray()); + } + } + + /** + * Create dummy key + * @return the alias of the dummy key + */ + public KeyCertPair createDummyKey() throws GeneralSecurityException, IOException, OperatorCreationException { + return addEntry(DUMMY_ALIAS, DUMMY_PASS, 2048, 24); + } + + public KeyCertPair addEntryFromPEM(File pemFile, String keyPass) throws IOException, CertificateException, KeyStoreException { + // see https://stackoverflow.com/questions/11787571/how-to-read-pem-file-to-get-private-and-public-key + PrivateKey key = null; + X509Certificate x509 = null; + + try (BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(pemFile), StandardCharsets.ISO_8859_1))) { + PEMParser parser = new PEMParser(br); + for (Object obj; (obj = parser.readObject()) != null; ) { + if (obj instanceof PrivateKeyInfo) { + key = new JcaPEMKeyConverter().setProvider("BC").getPrivateKey((PrivateKeyInfo)obj); + } else if (obj instanceof X509CertificateHolder) { + x509 = new JcaX509CertificateConverter().setProvider("BC").getCertificate((X509CertificateHolder)obj); + } + } + } + + if (key == null || x509 == null) { + throw new IOException("Please add private key and certificate in the PEM file."); + } + + String alias = x509.getSubjectDN().getName(); + keystore.setKeyEntry(alias, key, keyPass.toCharArray(), new Certificate[]{x509}); + + return new KeyCertPair(key, new Certificate[]{x509}); + } + + + /** + * Add an entry with password, keySize and expiry values. Ignore if alias is already in keystore + * @param keySize multiple of 1024, e.g. 1024, 2048 + */ + public KeyCertPair addEntry(String keyAlias, String keyPass, int keySize, int expiryInMonths) throws GeneralSecurityException, IOException, OperatorCreationException { + if (!keystore.isKeyEntry(keyAlias)) { + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); + keyPairGenerator.initialize(new RSAKeyGenParameterSpec(keySize, RSAKeyGenParameterSpec.F4), RANDOM); + KeyPair pair = keyPairGenerator.generateKeyPair(); + + Date notBefore = new Date(); + Calendar cal = LocaleUtil.getLocaleCalendar(LocaleUtil.TIMEZONE_UTC); + cal.add(Calendar.MONTH, expiryInMonths); + Date notAfter = cal.getTime(); + KeyUsage keyUsage = new KeyUsage(KeyUsage.digitalSignature); + + X509Certificate x509 = generateCertificate(pair.getPublic(), notBefore, notAfter, pair.getPrivate(), keyUsage); + keystore.setKeyEntry(keyAlias, pair.getPrivate(), keyPass.toCharArray(), new Certificate[]{x509}); + return new KeyCertPair(pair.getPrivate(), new X509Certificate[]{x509}); + } else { + return new KeyCertPair(getKey(keyAlias, keyPass), keystore.getCertificateChain(keyAlias)); + } + } + + public KeyCertPair getKeyPair(String keyAlias, String keyPass) throws GeneralSecurityException { + return new KeyCertPair(getKey(keyAlias, keyPass), keystore.getCertificateChain(keyAlias)); + } + + public PrivateKey getKey(String keyAlias, String keyPass) throws GeneralSecurityException { + return (PrivateKey)keystore.getKey(keyAlias, keyPass.toCharArray()); + } + + public X509Certificate getFirstX509(String alias) throws KeyStoreException { + return (X509Certificate)keystore.getCertificate(alias); + } + + public void save(File storeFile, String storePass) throws IOException, GeneralSecurityException { + try (FileOutputStream fos = new FileOutputStream(storeFile)) { + keystore.store(fos, storePass.toCharArray()); + } + } + + public X509CRL generateCrl(KeyCertPair certPair) + throws GeneralSecurityException, IOException, OperatorCreationException { + + PrivateKey issuerPrivateKey = certPair.getKey(); + X509Certificate issuer = certPair.getX509(); + + X509CertificateHolder holder = new X509CertificateHolder(issuer.getEncoded()); + X509v2CRLBuilder crlBuilder = new X509v2CRLBuilder(holder.getIssuer(), new Date()); + crlBuilder.setNextUpdate(new Date(new Date().getTime() + 100000)); + JcaContentSignerBuilder contentBuilder = new JcaContentSignerBuilder("SHA1withRSA").setProvider("BC"); + + CRLNumber crlNumber = new CRLNumber(new BigInteger("1234")); + + crlBuilder.addExtension(Extension.cRLNumber, false, crlNumber); + X509CRLHolder x509Crl = crlBuilder.build(contentBuilder.build(issuerPrivateKey)); + return new JcaX509CRLConverter().setProvider("BC").getCRL(x509Crl); + } + + + + private static X509Certificate generateCertificate(PublicKey subjectPublicKey, + Date notBefore, Date notAfter, + PrivateKey issuerPrivateKey, + KeyUsage keyUsage) + throws IOException, OperatorCreationException, CertificateException { + final String signatureAlgorithm = "SHA1withRSA"; + final String subjectDn = "CN=Test"; + X500Name issuerName = new X500Name(subjectDn); + + RSAPublicKey rsaPubKey = (RSAPublicKey)subjectPublicKey; + RSAKeyParameters rsaSpec = new RSAKeyParameters(false, rsaPubKey.getModulus(), rsaPubKey.getPublicExponent()); + + SubjectPublicKeyInfo subjectPublicKeyInfo = + SubjectPublicKeyInfoFactory.createSubjectPublicKeyInfo(rsaSpec); + + DigestCalculator digestCalc = new JcaDigestCalculatorProviderBuilder() + .setProvider("BC").build().get(CertificateID.HASH_SHA1); + + X509v3CertificateBuilder certificateGenerator = new X509v3CertificateBuilder( + issuerName + , new BigInteger(128, new SecureRandom()) + , notBefore + , notAfter + , new X500Name(subjectDn) + , subjectPublicKeyInfo + ); + + X509ExtensionUtils exUtils = new X509ExtensionUtils(digestCalc); + SubjectKeyIdentifier subKeyId = exUtils.createSubjectKeyIdentifier(subjectPublicKeyInfo); + AuthorityKeyIdentifier autKeyId = exUtils.createAuthorityKeyIdentifier(subjectPublicKeyInfo); + + certificateGenerator.addExtension(Extension.subjectKeyIdentifier, false, subKeyId); + certificateGenerator.addExtension(Extension.authorityKeyIdentifier, false, autKeyId); + + BasicConstraints bc = new BasicConstraints(0); + certificateGenerator.addExtension(Extension.basicConstraints, false, bc); + + if (null != keyUsage) { + certificateGenerator.addExtension(Extension.keyUsage, true, keyUsage); + } + + JcaContentSignerBuilder signerBuilder = new JcaContentSignerBuilder(signatureAlgorithm); + signerBuilder.setProvider("BC"); + + X509CertificateHolder certHolder = + certificateGenerator.build(signerBuilder.build(issuerPrivateKey)); + + return new JcaX509CertificateConverter().getCertificate(certHolder); + } + + public OCSPResp createOcspResp(KeyCertPair certPair, long nonceTimeinMillis) + throws OperatorCreationException, OCSPException, CertificateEncodingException, IOException { + X509Certificate certificate = certPair.getX509(); + X509Certificate issuerCertificate = certPair.getX509(); + X509Certificate ocspResponderCertificate = certPair.getX509(); + PrivateKey ocspResponderPrivateKey = certPair.getKey(); + + + DigestCalculator digestCalc = new JcaDigestCalculatorProviderBuilder() + .setProvider("BC").build().get(CertificateID.HASH_SHA1); + X509CertificateHolder issuerHolder = new X509CertificateHolder(issuerCertificate.getEncoded()); + CertificateID certId = new CertificateID(digestCalc, issuerHolder, certificate.getSerialNumber()); + + // request + //create a nonce to avoid replay attack + BigInteger nonce = BigInteger.valueOf(nonceTimeinMillis); + DEROctetString nonceDer = new DEROctetString(nonce.toByteArray()); + Extension ext = new Extension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce, true, nonceDer); + Extensions exts = new Extensions(ext); + + OCSPReqBuilder ocspReqBuilder = new OCSPReqBuilder(); + ocspReqBuilder.addRequest(certId); + ocspReqBuilder.setRequestExtensions(exts); + OCSPReq ocspReq = ocspReqBuilder.build(); + + + SubjectPublicKeyInfo keyInfo = new SubjectPublicKeyInfo + (CertificateID.HASH_SHA1, ocspResponderCertificate.getPublicKey().getEncoded()); + + BasicOCSPRespBuilder basicOCSPRespBuilder = new BasicOCSPRespBuilder(keyInfo, digestCalc); + basicOCSPRespBuilder.setResponseExtensions(exts); + + // request processing + Req[] requestList = ocspReq.getRequestList(); + for (Req ocspRequest : requestList) { + CertificateID certificateID = ocspRequest.getCertID(); + CertificateStatus certificateStatus = CertificateStatus.GOOD; + basicOCSPRespBuilder.addResponse(certificateID, certificateStatus); + } + + // basic response generation + X509CertificateHolder[] chain = null; + if (!ocspResponderCertificate.equals(issuerCertificate)) { + // TODO: HorribleProxy can't convert array input params yet + chain = new X509CertificateHolder[] { + new X509CertificateHolder(ocspResponderCertificate.getEncoded()), + issuerHolder + }; + } + + ContentSigner contentSigner = new JcaContentSignerBuilder("SHA1withRSA") + .setProvider("BC").build(ocspResponderPrivateKey); + BasicOCSPResp basicOCSPResp = basicOCSPRespBuilder.build(contentSigner, chain, new Date(nonceTimeinMillis)); + + + OCSPRespBuilder ocspRespBuilder = new OCSPRespBuilder(); + + return ocspRespBuilder.build(OCSPRespBuilder.SUCCESSFUL, basicOCSPResp); + } + +} diff --git a/poi-ooxml/src/test/java/org/apache/poi/poifs/crypt/dsig/TestSignatureInfo.java b/poi-ooxml/src/test/java/org/apache/poi/poifs/crypt/dsig/TestSignatureInfo.java index ec21a8d982..754fc465ec 100644 --- a/poi-ooxml/src/test/java/org/apache/poi/poifs/crypt/dsig/TestSignatureInfo.java +++ b/poi-ooxml/src/test/java/org/apache/poi/poifs/crypt/dsig/TestSignatureInfo.java @@ -30,42 +30,23 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assumptions.assumeFalse; import static org.junit.jupiter.api.Assumptions.assumeTrue; -import java.io.BufferedReader; -import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; -import java.io.InputStreamReader; import java.io.OutputStream; -import java.math.BigInteger; import java.net.ConnectException; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.SocketTimeoutException; import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.security.Key; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.KeyStore; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.SecureRandom; -import java.security.cert.CRLException; -import java.security.cert.Certificate; -import java.security.cert.CertificateEncodingException; -import java.security.cert.CertificateException; +import java.security.GeneralSecurityException; import java.security.cert.X509CRL; import java.security.cert.X509Certificate; -import java.security.interfaces.RSAPublicKey; -import java.security.spec.RSAKeyGenParameterSpec; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; -import java.util.Collections; -import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.function.BiFunction; @@ -89,8 +70,8 @@ import org.apache.poi.openxml4j.exceptions.InvalidFormatException; import org.apache.poi.openxml4j.opc.OPCPackage; import org.apache.poi.openxml4j.opc.PackageAccess; import org.apache.poi.openxml4j.opc.PackageRelationshipTypes; -import org.apache.poi.poifs.crypt.CryptoFunctions; import org.apache.poi.poifs.crypt.HashAlgorithm; +import org.apache.poi.poifs.crypt.dsig.DummyKeystore.KeyCertPair; 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.OOXMLSignatureFacet; @@ -102,7 +83,6 @@ 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.TimeStampServiceValidator; -import org.apache.poi.poifs.storage.RawDataUtil; import org.apache.poi.ss.usermodel.WorkbookFactory; import org.apache.poi.util.ConditionalExecution.DisabledOnJreEx; import org.apache.poi.util.IOUtils; @@ -121,43 +101,7 @@ import org.apache.xmlbeans.SystemProperties; import org.apache.xmlbeans.XmlCursor; import org.apache.xmlbeans.XmlException; import org.apache.xmlbeans.XmlObject; -import org.bouncycastle.asn1.DEROctetString; -import org.bouncycastle.asn1.ocsp.OCSPObjectIdentifiers; -import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; -import org.bouncycastle.asn1.x500.X500Name; -import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier; -import org.bouncycastle.asn1.x509.BasicConstraints; -import org.bouncycastle.asn1.x509.CRLNumber; -import org.bouncycastle.asn1.x509.Extension; -import org.bouncycastle.asn1.x509.Extensions; -import org.bouncycastle.asn1.x509.KeyUsage; -import org.bouncycastle.asn1.x509.SubjectKeyIdentifier; -import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; -import org.bouncycastle.cert.X509CRLHolder; -import org.bouncycastle.cert.X509CertificateHolder; -import org.bouncycastle.cert.X509ExtensionUtils; -import org.bouncycastle.cert.X509v2CRLBuilder; -import org.bouncycastle.cert.X509v3CertificateBuilder; -import org.bouncycastle.cert.jcajce.JcaX509CRLConverter; -import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; -import org.bouncycastle.cert.ocsp.BasicOCSPResp; -import org.bouncycastle.cert.ocsp.BasicOCSPRespBuilder; -import org.bouncycastle.cert.ocsp.CertificateID; -import org.bouncycastle.cert.ocsp.CertificateStatus; -import org.bouncycastle.cert.ocsp.OCSPReq; -import org.bouncycastle.cert.ocsp.OCSPReqBuilder; import org.bouncycastle.cert.ocsp.OCSPResp; -import org.bouncycastle.cert.ocsp.OCSPRespBuilder; -import org.bouncycastle.cert.ocsp.Req; -import org.bouncycastle.crypto.params.RSAKeyParameters; -import org.bouncycastle.crypto.util.SubjectPublicKeyInfoFactory; -import org.bouncycastle.openssl.PEMParser; -import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; -import org.bouncycastle.operator.ContentSigner; -import org.bouncycastle.operator.DigestCalculator; -import org.bouncycastle.operator.OperatorCreationException; -import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; -import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder; import org.etsi.uri.x01903.v13.DigestAlgAndValueType; import org.etsi.uri.x01903.v13.EncapsulatedPKIDataType; import org.etsi.uri.x01903.v13.QualifyingPropertiesType; @@ -180,15 +124,7 @@ import org.w3c.dom.Document; class TestSignatureInfo { private static final Logger LOG = LogManager.getLogger(TestSignatureInfo.class); private static final POIDataSamples testdata = POIDataSamples.getXmlDSignInstance(); - - private static Calendar cal; - private KeyPair keyPair; - private X509Certificate x509; - - @BeforeAll - public static void setUpClass() { - POITestCase.setImageIOCacheDir(); - } + private static final String STORE_PASS = "test"; @AfterAll public static void removeUserLocale() { @@ -196,16 +132,13 @@ class TestSignatureInfo { } @BeforeAll - public static void initBouncy() { - CryptoFunctions.registerBouncyCastle(); + public static void initXmlsec() { + POITestCase.setImageIOCacheDir(); // Set cal to now ... only set to fixed date for debugging ... LocaleUtil.resetUserLocale(); LocaleUtil.resetUserTimeZone(); - cal = LocaleUtil.getLocaleCalendar(LocaleUtil.TIMEZONE_UTC); - assertNotNull(cal); - // don't run this test when we are using older Xerces as it triggers an XML Parser backwards compatibility issue // in the xmlsec jar file String additionalJar = System.getProperty("additionaljar"); @@ -460,12 +393,15 @@ class TestSignatureInfo { @Test @DisabledOnJreEx("1.8.0_292") void testSignSpreadsheetWithSignatureInfo() throws Exception { - initKeyPair(); String testFile = "hello-world-unsigned.xlsx"; + DummyKeystore ks = new DummyKeystore(STORE_PASS); + KeyCertPair certPair = ks.createDummyKey(); + + try (OPCPackage pkg = OPCPackage.open(copy(testdata.getFile(testFile)), PackageAccess.READ_WRITE)) { SignatureConfig sic = new SignatureConfig(); - sic.setKey(keyPair.getPrivate()); - sic.setSigningCertificateChain(Collections.singletonList(x509)); + sic.setKey(certPair.getKey()); + sic.setSigningCertificateChain(certPair.getX509Chain()); SignatureInfo si = new SignatureInfo(); si.setOpcPackage(pkg); si.setSignatureConfig(sic); @@ -490,23 +426,24 @@ class TestSignatureInfo { final String execTimestr; + DummyKeystore ks = new DummyKeystore(STORE_PASS); + KeyCertPair certPair = ks.createDummyKey(); try (OPCPackage pkg = OPCPackage.open(copy(sigCopy), PackageAccess.READ_WRITE)) { - initKeyPair(); - final X509CRL crl = generateCrl(x509, keyPair.getPrivate()); + final X509CRL crl = ks.generateCrl(certPair); // setup SignatureConfig signatureConfig = new SignatureConfig(); - signatureConfig.setKey(keyPair.getPrivate()); + signatureConfig.setKey(certPair.getKey()); /* * We need at least 2 certificates for the XAdES-C complete certificate * refs construction. */ List certificateChain = new ArrayList<>(); - certificateChain.add(x509); - certificateChain.add(x509); + certificateChain.add(certPair.getX509()); + certificateChain.add(certPair.getX509()); signatureConfig.setSigningCertificateChain(certificateChain); signatureConfig.addSignatureFacet(new OOXMLSignatureFacet()); @@ -558,7 +495,8 @@ class TestSignatureInfo { final RevocationData revocationData = new RevocationData(); revocationData.addCRL(crl); - OCSPResp ocspResp = createOcspResp(x509, x509, x509, keyPair.getPrivate(), cal.getTimeInMillis()); + Calendar cal = LocaleUtil.getLocaleCalendar(LocaleUtil.TIMEZONE_UTC); + OCSPResp ocspResp = ks.createOcspResp(certPair, cal.getTimeInMillis()); revocationData.addOCSP(ocspResp.getEncoded()); RevocationDataService revocationDataService = revocationChain -> revocationData; @@ -686,27 +624,15 @@ class TestSignatureInfo { void testCertChain() throws Exception { final boolean isIBM = System.getProperty("java.vendor").contains("IBM"); - KeyStore keystore = KeyStore.getInstance("PKCS12"); - String password = "test"; - try (InputStream is = testdata.openResourceAsStream("chaintest.pfx")) { - keystore.load(is, password.toCharArray()); - } - - Key key = keystore.getKey("poitest", password.toCharArray()); - Certificate[] chainList = keystore.getCertificateChain("poitest"); - List certChain = new ArrayList<>(); - for (Certificate c : chainList) { - certChain.add((X509Certificate)c); - } - x509 = certChain.get(0); - keyPair = new KeyPair(x509.getPublicKey(), (PrivateKey)key); + DummyKeystore ks = new DummyKeystore(testdata.getFile("chaintest.pfx"), STORE_PASS); + KeyCertPair certPair = ks.getKeyPair("poitest", "test"); String testFile = "hello-world-unsigned.xlsx"; try (OPCPackage pkg = OPCPackage.open(copy(testdata.getFile(testFile)), PackageAccess.READ_WRITE)) { SignatureConfig signatureConfig = new SignatureConfig(); - signatureConfig.setKey(keyPair.getPrivate()); - signatureConfig.setSigningCertificateChain(certChain); + signatureConfig.setKey(certPair.getKey()); + signatureConfig.setSigningCertificateChain(certPair.getX509Chain()); Calendar oldCal = LocaleUtil.getLocaleCalendar(2007, 7, 1); signatureConfig.setExecutionTime(oldCal.getTime()); signatureConfig.setDigestAlgo(HashAlgorithm.sha1); @@ -735,11 +661,12 @@ class TestSignatureInfo { @DisabledOnJreEx("1.8.0_292") void testNonSha1() throws Exception { String testFile = "hello-world-unsigned.xlsx"; - initKeyPair(); + DummyKeystore ks = new DummyKeystore(STORE_PASS); + KeyCertPair certPair = ks.createDummyKey(); SignatureConfig signatureConfig = new SignatureConfig(); - signatureConfig.setKey(keyPair.getPrivate()); - signatureConfig.setSigningCertificateChain(Collections.singletonList(x509)); + signatureConfig.setKey(certPair.getKey()); + signatureConfig.setSigningCertificateChain(certPair.getX509Chain()); HashAlgorithm[] testAlgo = {HashAlgorithm.sha224, HashAlgorithm.sha256 , HashAlgorithm.sha384, HashAlgorithm.sha512, HashAlgorithm.ripemd160}; @@ -764,7 +691,8 @@ class TestSignatureInfo { @Test @DisabledOnJreEx("1.8.0_292") void bug65214() throws Exception { - initKeyPair(); + DummyKeystore ks = new DummyKeystore(STORE_PASS); + KeyCertPair certPair = ks.createDummyKey(); UnsynchronizedByteArrayOutputStream bos = new UnsynchronizedByteArrayOutputStream(); try (XWPFDocument doc = new XWPFDocument()) { @@ -776,8 +704,8 @@ class TestSignatureInfo { } SignatureConfig signatureConfig = new SignatureConfig(); - signatureConfig.setKey(keyPair.getPrivate()); - signatureConfig.setSigningCertificateChain(Collections.singletonList(x509)); + signatureConfig.setKey(certPair.getKey()); + signatureConfig.setSigningCertificateChain(certPair.getX509Chain()); signatureConfig.setDigestAlgo(HashAlgorithm.sha256); try (OPCPackage pkg = OPCPackage.open(bos.toInputStream())) { SignatureInfo si = new SignatureInfo(); @@ -803,6 +731,9 @@ class TestSignatureInfo { @Test @DisabledOnJreEx("1.8.0_292") void bug58630() throws Exception { + DummyKeystore ks = new DummyKeystore(STORE_PASS); + KeyCertPair certPair = ks.createDummyKey(); + // test deletion of sheet 0 and signing File tpl = copy(testdata.getFile("bug58630.xlsx")); try (SXSSFWorkbook wb1 = new SXSSFWorkbook((XSSFWorkbook)WorkbookFactory.create(tpl), 10)) { @@ -812,10 +743,9 @@ class TestSignatureInfo { wb1.write(os); try (OPCPackage pkg = OPCPackage.open(os.toInputStream())) { - initKeyPair(); SignatureConfig signatureConfig = new SignatureConfig(); - signatureConfig.setKey(keyPair.getPrivate()); - signatureConfig.setSigningCertificateChain(Collections.singletonList(x509)); + signatureConfig.setKey(certPair.getKey()); + signatureConfig.setSigningCertificateChain(certPair.getX509Chain()); SignatureInfo si = new SignatureInfo(); si.setOpcPackage(pkg); @@ -828,7 +758,7 @@ class TestSignatureInfo { @Test void testMultiSign() throws Exception { - cal = LocaleUtil.getLocaleCalendar(LocaleUtil.TIMEZONE_UTC); + Calendar cal = LocaleUtil.getLocaleCalendar(LocaleUtil.TIMEZONE_UTC); cal.clear(); cal.setTimeZone(LocaleUtil.TIMEZONE_UTC); cal.set(2018, Calendar.DECEMBER, 14); @@ -892,13 +822,16 @@ class TestSignatureInfo { } private void signPkg63011(OPCPackage pkg, String pemFile, boolean multi) - throws IOException, CertificateException, XMLSignatureException, MarshalException { + throws IOException, GeneralSecurityException, XMLSignatureException, MarshalException { assertNotNull(pkg); - initKeyFromPEM(testdata.getFile(pemFile)); + + DummyKeystore ks = new DummyKeystore("test"); + KeyCertPair certPair = ks.addEntryFromPEM(testdata.getFile(pemFile), "test"); SignatureConfig config = new SignatureConfig(); - config.setKey(keyPair.getPrivate()); - config.setSigningCertificateChain(Collections.singletonList(x509)); + config.setKey(certPair.getKey()); + config.setSigningCertificateChain(certPair.getX509Chain()); + Calendar cal = LocaleUtil.getLocaleCalendar(LocaleUtil.TIMEZONE_UTC); config.setExecutionTime(cal.getTime()); config.setAllowMultipleSignatures(multi); @@ -939,7 +872,8 @@ class TestSignatureInfo { @Test void createXAdES_T_65623() throws Exception { - initKeyPair(); + DummyKeystore ks = new DummyKeystore(STORE_PASS); + KeyCertPair certPair = ks.createDummyKey(); UnsynchronizedByteArrayOutputStream bos = new UnsynchronizedByteArrayOutputStream(); try (XSSFWorkbook wb = new XSSFWorkbook()) { @@ -949,12 +883,12 @@ class TestSignatureInfo { SignatureConfig signatureConfig = new SignatureConfig(); signatureConfig.setDigestAlgo(HashAlgorithm.sha256); - signatureConfig.setKey(keyPair.getPrivate()); - signatureConfig.setSigningCertificateChain(Collections.singletonList(x509)); + signatureConfig.setKey(certPair.getKey()); + signatureConfig.setSigningCertificateChain(certPair.getX509Chain()); // mock tsp // signatureConfig.setTspUrl("http://timestamp.digicert.com"); - final X509CRL crl = generateCrl(x509, keyPair.getPrivate()); + final X509CRL crl = ks.generateCrl(certPair); TimeStampService tspService = (signatureInfo, data, revocationData) -> { revocationData.addCRL(crl); return "time-stamp-token".getBytes(LocaleUtil.CHARSET_1252); @@ -1024,7 +958,8 @@ class TestSignatureInfo { @DisabledOnJreEx("1.8.0_292") @Tag("scratchpad.ignore") void testSignatureImage() throws Exception { - initKeyPair(); + DummyKeystore ks = new DummyKeystore(STORE_PASS); + KeyCertPair certPair = ks.createDummyKey(); List> lines = Arrays.asList(XSSFSignatureLine::new, XWPFSignatureLine::new); for (Supplier sup : lines) { @@ -1051,8 +986,8 @@ class TestSignatureInfo { try (OPCPackage pkg = OPCPackage.open(signDoc, PackageAccess.READ_WRITE)) { SignatureConfig sic = new SignatureConfig(); - sic.setKey(keyPair.getPrivate()); - sic.setSigningCertificateChain(Collections.singletonList(x509)); + sic.setKey(certPair.getKey()); + sic.setSigningCertificateChain(certPair.getX509Chain()); line.updateSignatureConfig(sic); @@ -1115,13 +1050,52 @@ class TestSignatureInfo { return xls; } + @Test + void commitmentType65672() throws Exception { + DummyKeystore ks = new DummyKeystore(STORE_PASS); + KeyCertPair certPair = ks.createDummyKey(); + + UnsynchronizedByteArrayOutputStream bos = new UnsynchronizedByteArrayOutputStream(); + try (XSSFWorkbook wb = new XSSFWorkbook()) { + wb.createSheet().createRow(0).createCell(0).setCellValue("test"); + wb.write(bos); + } + + String commitType = "POI Test commit"; + try (OPCPackage pkg = OPCPackage.open(bos.toInputStream())) { + SignatureConfig sc = new SignatureConfig(); + sc.setKey(certPair.getKey()); + sc.setSigningCertificateChain(certPair.getX509Chain()); + sc.setCommitmentType(commitType); + SignatureInfo si = new SignatureInfo(); + si.setSignatureConfig(sc); + si.setOpcPackage(pkg); + si.confirmSignature(); + bos.reset(); + pkg.save(bos); + } + + try (OPCPackage pkg = OPCPackage.open(bos.toInputStream())) { + SignatureInfo si = new SignatureInfo(); + SignatureConfig sc = new SignatureConfig(); + sc.setUpdateConfigOnValidate(true); + si.setSignatureConfig(sc); + si.setOpcPackage(pkg); + si.verifySignature(); + assertEquals(commitType, sc.getCommitmentType()); + } + } + private SignatureConfig prepareConfig(String pfxInput) throws Exception { - initKeyPair(pfxInput); + DummyKeystore ks = (pfxInput == null) ? new DummyKeystore(STORE_PASS) : new DummyKeystore(pfxInput, STORE_PASS); + KeyCertPair certPair = ks.createDummyKey();; SignatureConfig signatureConfig = new SignatureConfig(); - signatureConfig.setKey(keyPair.getPrivate()); - signatureConfig.setSigningCertificateChain(Collections.singletonList(x509)); + signatureConfig.setKey(certPair.getKey()); + signatureConfig.setSigningCertificateChain(certPair.getX509Chain()); + + Calendar cal = LocaleUtil.getLocaleCalendar(LocaleUtil.TIMEZONE_UTC); signatureConfig.setExecutionTime(cal.getTime()); signatureConfig.setDigestAlgo(HashAlgorithm.sha1); @@ -1164,77 +1138,6 @@ class TestSignatureInfo { assertEquals(signerCount, result.size()); } - private void initKeyPair() throws Exception { - initKeyPair(null); - } - - private void initKeyPair(String pfxInput) throws Exception { - final String alias = "Test"; - final char[] password = "test".toCharArray(); - File file = new File("build/test.pfx"); - assertTrue(file.getParentFile().exists() || file.getParentFile().mkdir()); - - KeyStore keystore = KeyStore.getInstance("PKCS12"); - - if (pfxInput != null) { - try (InputStream fis = new ByteArrayInputStream(RawDataUtil.decompress(pfxInput))) { - keystore.load(fis, password); - } - } else if (file.exists()) { - try (InputStream fis = new FileInputStream(file)) { - keystore.load(fis, password); - } catch (IOException e) { - throw new IOException("Failed when reading file " + file, e); - } - } 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 = generateKeyPair(); - Date notBefore = cal.getTime(); - Calendar cal2 = (Calendar)cal.clone(); - cal2.add(Calendar.YEAR, 1); - Date notAfter = cal2.getTime(); - KeyUsage keyUsage = new KeyUsage(KeyUsage.digitalSignature); - - x509 = generateCertificate(keyPair.getPublic(), notBefore, notAfter, keyPair.getPrivate(), keyUsage); - - keystore.setKeyEntry(alias, keyPair.getPrivate(), password, new Certificate[]{x509}); - - if (pfxInput == null) { - try (FileOutputStream fos = new FileOutputStream(file)) { - keystore.store(fos, password); - } - } - } - } - - private void initKeyFromPEM(File pemFile) throws IOException, CertificateException { - // see https://stackoverflow.com/questions/11787571/how-to-read-pem-file-to-get-private-and-public-key - PrivateKey key = null; - x509 = null; - - try (BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(pemFile), StandardCharsets.ISO_8859_1))) { - PEMParser parser = new PEMParser(br); - for (Object obj; (obj = parser.readObject()) != null; ) { - if (obj instanceof PrivateKeyInfo) { - key = new JcaPEMKeyConverter().setProvider("BC").getPrivateKey((PrivateKeyInfo)obj); - } else if (obj instanceof X509CertificateHolder) { - x509 = new JcaX509CertificateConverter().setProvider("BC").getCertificate((X509CertificateHolder)obj); - } - } - } - - if (key != null && x509 != null) { - keyPair = new KeyPair(x509.getPublicKey(), key); - } - } - private static File copy(File input) throws IOException { String extension = input.getName().replaceAll(".*?(\\.[^.]+)?$", "$1"); if (extension.isEmpty()) { @@ -1257,145 +1160,4 @@ class TestSignatureInfo { return tmpFile; } - - private static KeyPair generateKeyPair() throws Exception { - KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); - SecureRandom random = new SecureRandom(); - keyPairGenerator.initialize(new RSAKeyGenParameterSpec(1024, - RSAKeyGenParameterSpec.F4), random); - return keyPairGenerator.generateKeyPair(); - } - - private static X509Certificate generateCertificate(PublicKey subjectPublicKey, - Date notBefore, Date notAfter, - PrivateKey issuerPrivateKey, - KeyUsage keyUsage) - throws IOException, OperatorCreationException, CertificateException { - final String signatureAlgorithm = "SHA1withRSA"; - final String subjectDn = "CN=Test"; - X500Name issuerName = new X500Name(subjectDn); - - RSAPublicKey rsaPubKey = (RSAPublicKey)subjectPublicKey; - RSAKeyParameters rsaSpec = new RSAKeyParameters(false, rsaPubKey.getModulus(), rsaPubKey.getPublicExponent()); - - SubjectPublicKeyInfo subjectPublicKeyInfo = - SubjectPublicKeyInfoFactory.createSubjectPublicKeyInfo(rsaSpec); - - DigestCalculator digestCalc = new JcaDigestCalculatorProviderBuilder() - .setProvider("BC").build().get(CertificateID.HASH_SHA1); - - X509v3CertificateBuilder certificateGenerator = new X509v3CertificateBuilder( - issuerName - , new BigInteger(128, new SecureRandom()) - , notBefore - , notAfter - , new X500Name(subjectDn) - , subjectPublicKeyInfo - ); - - X509ExtensionUtils exUtils = new X509ExtensionUtils(digestCalc); - SubjectKeyIdentifier subKeyId = exUtils.createSubjectKeyIdentifier(subjectPublicKeyInfo); - AuthorityKeyIdentifier autKeyId = exUtils.createAuthorityKeyIdentifier(subjectPublicKeyInfo); - - certificateGenerator.addExtension(Extension.subjectKeyIdentifier, false, subKeyId); - certificateGenerator.addExtension(Extension.authorityKeyIdentifier, false, autKeyId); - - BasicConstraints bc = new BasicConstraints(0); - certificateGenerator.addExtension(Extension.basicConstraints, false, bc); - - if (null != keyUsage) { - certificateGenerator.addExtension(Extension.keyUsage, true, keyUsage); - } - - JcaContentSignerBuilder signerBuilder = new JcaContentSignerBuilder(signatureAlgorithm); - signerBuilder.setProvider("BC"); - - X509CertificateHolder certHolder = - certificateGenerator.build(signerBuilder.build(issuerPrivateKey)); - - /* - * Next certificate factory trick is needed to make sure that the - * certificate delivered to the caller is provided by the default - * security provider instead of BouncyCastle. If we don't do this trick - * we might run into trouble when trying to use the CertPath validator. - */ -// CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); -// certificate = (X509Certificate) certificateFactory -// .generateCertificate(new ByteArrayInputStream(certificate -// .getEncoded())); - return new JcaX509CertificateConverter().getCertificate(certHolder); - } - - private static X509CRL generateCrl(X509Certificate issuer, PrivateKey issuerPrivateKey) - throws CertificateEncodingException, IOException, CRLException, OperatorCreationException { - - X509CertificateHolder holder = new X509CertificateHolder(issuer.getEncoded()); - X509v2CRLBuilder crlBuilder = new X509v2CRLBuilder(holder.getIssuer(), new Date()); - crlBuilder.setNextUpdate(new Date(new Date().getTime() + 100000)); - JcaContentSignerBuilder contentBuilder = new JcaContentSignerBuilder("SHA1withRSA").setProvider("BC"); - - CRLNumber crlNumber = new CRLNumber(new BigInteger("1234")); - - crlBuilder.addExtension(Extension.cRLNumber, false, crlNumber); - X509CRLHolder x509Crl = crlBuilder.build(contentBuilder.build(issuerPrivateKey)); - return new JcaX509CRLConverter().setProvider("BC").getCRL(x509Crl); - } - - private static OCSPResp createOcspResp(X509Certificate certificate, - X509Certificate issuerCertificate, - X509Certificate ocspResponderCertificate, - PrivateKey ocspResponderPrivateKey, - long nonceTimeinMillis) - throws Exception { - DigestCalculator digestCalc = new JcaDigestCalculatorProviderBuilder() - .setProvider("BC").build().get(CertificateID.HASH_SHA1); - X509CertificateHolder issuerHolder = new X509CertificateHolder(issuerCertificate.getEncoded()); - CertificateID certId = new CertificateID(digestCalc, issuerHolder, certificate.getSerialNumber()); - - // request - //create a nonce to avoid replay attack - BigInteger nonce = BigInteger.valueOf(nonceTimeinMillis); - DEROctetString nonceDer = new DEROctetString(nonce.toByteArray()); - Extension ext = new Extension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce, true, nonceDer); - Extensions exts = new Extensions(ext); - - OCSPReqBuilder ocspReqBuilder = new OCSPReqBuilder(); - ocspReqBuilder.addRequest(certId); - ocspReqBuilder.setRequestExtensions(exts); - OCSPReq ocspReq = ocspReqBuilder.build(); - - - SubjectPublicKeyInfo keyInfo = new SubjectPublicKeyInfo - (CertificateID.HASH_SHA1, ocspResponderCertificate.getPublicKey().getEncoded()); - - BasicOCSPRespBuilder basicOCSPRespBuilder = new BasicOCSPRespBuilder(keyInfo, digestCalc); - basicOCSPRespBuilder.setResponseExtensions(exts); - - // request processing - Req[] requestList = ocspReq.getRequestList(); - for (Req ocspRequest : requestList) { - CertificateID certificateID = ocspRequest.getCertID(); - CertificateStatus certificateStatus = CertificateStatus.GOOD; - basicOCSPRespBuilder.addResponse(certificateID, certificateStatus); - } - - // basic response generation - X509CertificateHolder[] chain = null; - if (!ocspResponderCertificate.equals(issuerCertificate)) { - // TODO: HorribleProxy can't convert array input params yet - chain = new X509CertificateHolder[] { - new X509CertificateHolder(ocspResponderCertificate.getEncoded()), - issuerHolder - }; - } - - ContentSigner contentSigner = new JcaContentSignerBuilder("SHA1withRSA") - .setProvider("BC").build(ocspResponderPrivateKey); - BasicOCSPResp basicOCSPResp = basicOCSPRespBuilder.build(contentSigner, chain, new Date(nonceTimeinMillis)); - - - OCSPRespBuilder ocspRespBuilder = new OCSPRespBuilder(); - - return ocspRespBuilder.build(OCSPRespBuilder.SUCCESSFUL, basicOCSPResp); - } } -- 2.39.5