| /* ==================================================================== |
| 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 static org.junit.jupiter.api.Assertions.assertArrayEquals; |
| import static org.junit.jupiter.api.Assertions.assertEquals; |
| import static org.junit.jupiter.api.Assertions.assertFalse; |
| import static org.junit.jupiter.api.Assertions.assertNotNull; |
| 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.File; |
| import java.io.FileInputStream; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.net.ConnectException; |
| import java.net.HttpURLConnection; |
| import java.net.MalformedURLException; |
| import java.net.SocketTimeoutException; |
| import java.net.URL; |
| import java.security.GeneralSecurityException; |
| import java.security.KeyStoreException; |
| import java.security.cert.X509CRL; |
| import java.security.cert.X509Certificate; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Calendar; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.function.BiFunction; |
| import java.util.function.Supplier; |
| |
| import javax.security.auth.x500.X500Principal; |
| import javax.xml.crypto.MarshalException; |
| import javax.xml.crypto.dsig.CanonicalizationMethod; |
| import javax.xml.crypto.dsig.XMLSignatureException; |
| import javax.xml.crypto.dsig.dom.DOMSignContext; |
| |
| import org.apache.commons.io.output.UnsynchronizedByteArrayOutputStream; |
| import org.apache.jcp.xml.dsig.internal.dom.DOMSignedInfo; |
| import org.apache.logging.log4j.LogManager; |
| import org.apache.logging.log4j.Logger; |
| import org.apache.poi.EncryptedDocumentException; |
| import org.apache.poi.POIDataSamples; |
| import org.apache.poi.POITestCase; |
| import org.apache.poi.ooxml.POIXMLDocument; |
| import org.apache.poi.ooxml.util.DocumentHelper; |
| 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.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; |
| import org.apache.poi.poifs.crypt.dsig.facets.Office2010SignatureFacet; |
| import org.apache.poi.poifs.crypt.dsig.facets.SignatureFacet; |
| import org.apache.poi.poifs.crypt.dsig.facets.XAdESSignatureFacet; |
| import org.apache.poi.poifs.crypt.dsig.facets.XAdESXLSignatureFacet; |
| import org.apache.poi.poifs.crypt.dsig.services.RevocationData; |
| import org.apache.poi.poifs.crypt.dsig.services.RevocationDataService; |
| import org.apache.poi.poifs.crypt.dsig.services.TimeStampService; |
| import org.apache.poi.poifs.crypt.dsig.services.TimeStampServiceValidator; |
| import org.apache.poi.ss.usermodel.WorkbookFactory; |
| import org.apache.poi.util.ConditionalExecution.DisabledOnJreEx; |
| import org.apache.poi.util.IOUtils; |
| import org.apache.poi.util.LocaleUtil; |
| import org.apache.poi.util.StringUtil; |
| import org.apache.poi.util.TempFile; |
| import org.apache.poi.xssf.streaming.SXSSFWorkbook; |
| import org.apache.poi.xssf.usermodel.XSSFClientAnchor; |
| import org.apache.poi.xssf.usermodel.XSSFSheet; |
| import org.apache.poi.xssf.usermodel.XSSFSignatureLine; |
| import org.apache.poi.xssf.usermodel.XSSFWorkbook; |
| import org.apache.poi.xwpf.usermodel.UnderlinePatterns; |
| import org.apache.poi.xwpf.usermodel.XWPFDocument; |
| import org.apache.poi.xwpf.usermodel.XWPFHyperlinkRun; |
| import org.apache.poi.xwpf.usermodel.XWPFSignatureLine; |
| import org.apache.xmlbeans.SystemProperties; |
| import org.apache.xmlbeans.XmlCursor; |
| import org.apache.xmlbeans.XmlException; |
| import org.apache.xmlbeans.XmlObject; |
| import org.bouncycastle.cert.ocsp.OCSPResp; |
| import org.etsi.uri.x01903.v13.DigestAlgAndValueType; |
| import org.etsi.uri.x01903.v13.EncapsulatedPKIDataType; |
| import org.etsi.uri.x01903.v13.QualifyingPropertiesType; |
| import org.etsi.uri.x01903.v13.UnsignedPropertiesType; |
| import org.etsi.uri.x01903.v13.UnsignedSignaturePropertiesType; |
| import org.etsi.uri.x01903.v13.XAdESTimeStampType; |
| import org.junit.jupiter.api.AfterAll; |
| import org.junit.jupiter.api.BeforeAll; |
| import org.junit.jupiter.api.Disabled; |
| import org.junit.jupiter.api.Tag; |
| import org.junit.jupiter.api.Test; |
| import org.junit.jupiter.params.ParameterizedTest; |
| import org.junit.jupiter.params.provider.CsvSource; |
| import org.junit.jupiter.params.provider.ValueSource; |
| import org.w3.x2000.x09.xmldsig.ObjectType; |
| import org.w3.x2000.x09.xmldsig.ReferenceType; |
| import org.w3.x2000.x09.xmldsig.SignatureDocument; |
| 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 final String STORE_PASS = "test"; |
| |
| @AfterAll |
| public static void removeUserLocale() { |
| LocaleUtil.resetUserLocale(); |
| } |
| |
| @BeforeAll |
| public static void initXmlsec() { |
| POITestCase.setImageIOCacheDir(); |
| |
| // Set cal to now ... only set to fixed date for debugging ... |
| LocaleUtil.resetUserLocale(); |
| LocaleUtil.resetUserTimeZone(); |
| |
| // 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"); |
| //System.out.println("Having: " + additionalJar); |
| assumeTrue(StringUtil.isBlank(additionalJar), |
| "Not running TestSignatureInfo because we are testing with additionaljar set to " + additionalJar); |
| |
| System.setProperty("org.apache.xml.security.ignoreLineBreaks", "true"); |
| |
| // Set line.separator for bug61182 |
| // System.setProperty("line.separator", "\n"); |
| } |
| |
| @Disabled("This test is very sensitive, it breaks with every little change to the produced XML") |
| @Test |
| void bug61182() throws Exception { |
| final String pfxInput = |
| "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"; |
| |
| // Unix |
| final String unixSignExp = |
| "QkqTFQZjXagjRAoOWKpAGa8AR0rKqkSfBtfSWqtjBmTgyjarn+t2POHkpySIpheHAbg+90GKSH88ACMtPHbG7q" + |
| "FL4gtgAD9Kjew6j16j0IRBwy145UlPrSLFMfF7YF7UlU1k1LBkIlRJ6Fv4MAJl6XspuzZOZIUmHZrWrdxycUQ="; |
| |
| // Windows |
| final String winSignExp = |
| "GmAlL7+bT1r3FsMHJOp3pKg8betblYieZTjhMIrPZPRBbSzjO7KsYRGNtr0aOE3qr8xzyYJN6/8QdF5X7pUEUc" + |
| "2m8ctrm7s5o2vZTkAqk9ENJGDjBPXX7TnuVOiVeL1cJdtjHC2QpjtRwkFR+B54G6b1OXLOFuQpP3vqR3+/XXE="; |
| |
| // Mac |
| final String macSignExp = |
| "NZedY/LNTYU4nAUEUhIOg5+fKdgVtzRXKmdD3v+47E7Mb84oeiUGv9cCEE91DU3StF/JFIhjOJqavOzKnCsNcz" + |
| "NJ4j/inggUl1OJUsicqIGQnA7E8vzWnN1kf5lINgJLv+0PyrrX9sQZbItzxUpgqyOFYcD0trid+31nRt4wtaA="; |
| |
| |
| |
| Calendar cal = LocaleUtil.getLocaleCalendar(LocaleUtil.TIMEZONE_UTC); |
| cal.clear(); |
| cal.setTimeZone(LocaleUtil.TIMEZONE_UTC); |
| cal.set(2017, Calendar.JULY, 1); |
| |
| SignatureConfig signatureConfig = prepareConfig(pfxInput); |
| signatureConfig.setExecutionTime(cal.getTime()); |
| |
| SignatureInfo si = new SignatureInfo(); |
| si.setSignatureConfig(signatureConfig); |
| |
| UnsynchronizedByteArrayOutputStream bos = new UnsynchronizedByteArrayOutputStream(100000); |
| try (XSSFWorkbook wb1 = new XSSFWorkbook()) { |
| wb1.createSheet().createRow(1).createCell(1).setCellValue("Test"); |
| wb1.write(bos); |
| } |
| |
| try (OPCPackage pkg1 = OPCPackage.open(bos.toInputStream())) { |
| si.setOpcPackage(pkg1); |
| si.confirmSignature(); |
| assertTrue(si.verifySignature()); |
| bos.reset(); |
| pkg1.save(bos); |
| } |
| |
| try (XSSFWorkbook wb2 = new XSSFWorkbook(bos.toInputStream())) { |
| assertEquals("Test", wb2.getSheetAt(0).getRow(1).getCell(1).getStringCellValue()); |
| OPCPackage pkg2 = wb2.getPackage(); |
| si.setOpcPackage(pkg2); |
| assertTrue(si.verifySignature()); |
| |
| // xmlbeans adds line-breaks depending on the system setting, so we get different |
| // test results on Unix/Mac/Windows |
| // if the xml documents eventually change, this test needs to be run with the |
| // separator set to the various system configurations |
| String sep = SystemProperties.getProperty("line.separator"); |
| String signExp; |
| assumeTrue(sep == null || "\n".equals(sep) || "\r\n".equals(sep) || "\r".equals(sep), "Hashes only known for Windows/Unix/Mac"); |
| signExp = (sep == null || "\n".equals(sep)) ? unixSignExp : ("\r\n".equals(sep)) ? winSignExp : macSignExp; |
| |
| String signAct = si.getSignatureParts().iterator().next(). |
| getSignatureDocument().getSignature().getSignatureValue().getStringValue(); |
| assertEquals(signExp, signAct); |
| } |
| } |
| |
| @Test |
| void office2007prettyPrintedRels() throws Exception { |
| try (OPCPackage pkg = OPCPackage.open(testdata.getFile("office2007prettyPrintedRels.docx"), PackageAccess.READ)) { |
| SignatureConfig sic = new SignatureConfig(); |
| SignatureInfo si = new SignatureInfo(); |
| si.setOpcPackage(pkg); |
| si.setSignatureConfig(sic); |
| boolean isValid = si.verifySignature(); |
| assertTrue(isValid); |
| } |
| } |
| |
| @ParameterizedTest |
| @ValueSource(strings = { |
| "hello-world-unsigned.docx", |
| "hello-world-unsigned.pptx", |
| "hello-world-unsigned.xlsx", |
| "hello-world-office-2010-technical-preview-unsigned.docx" |
| }) |
| void getSignerUnsigned(String testFile) throws Exception { |
| List<X509Certificate> result = new ArrayList<>(); |
| try (OPCPackage pkg = OPCPackage.open(testdata.getFile(testFile), PackageAccess.READ)) { |
| SignatureConfig sic = new SignatureConfig(); |
| SignatureInfo si = new SignatureInfo(); |
| si.setOpcPackage(pkg); |
| si.setSignatureConfig(sic); |
| for (SignaturePart sp : si.getSignatureParts()) { |
| if (sp.validate()) { |
| result.add(sp.getSigner()); |
| } |
| } |
| pkg.revert(); |
| } |
| assertNotNull(result); |
| assertTrue(result.isEmpty()); |
| } |
| |
| @ParameterizedTest |
| @CsvSource(value = { |
| "hyperlink-example-signed.docx, true", |
| "hello-world-signed.docx, true", |
| "hello-world-signed.pptx, false", |
| "hello-world-signed.xlsx, true", |
| "hello-world-office-2010-technical-preview.docx, true", |
| "ms-office-2010-signed.docx, true", |
| "ms-office-2010-signed.pptx, false", |
| "ms-office-2010-signed.xlsx, true", |
| "Office2010-SP1-XAdES-X-L.docx, true", |
| "signed.docx, true" |
| }) |
| void getSigner(String testFile, boolean secureValidation) throws Exception { |
| try (OPCPackage pkg = OPCPackage.open(testdata.getFile(testFile), PackageAccess.READ)) { |
| SignatureConfig sic = new SignatureConfig(); |
| sic.setSecureValidation(secureValidation); |
| SignatureInfo si = new SignatureInfo(); |
| si.setOpcPackage(pkg); |
| si.setSignatureConfig(sic); |
| List<X509Certificate> result = new ArrayList<>(); |
| for (SignaturePart sp : si.getSignatureParts()) { |
| if (sp.validate()) { |
| result.add(sp.getSigner()); |
| } |
| } |
| |
| assertNotNull(result); |
| assertEquals(1, result.size(), "test-file: " + testFile); |
| X509Certificate signer = result.get(0); |
| LOG.atDebug().log("signer: {}", signer.getSubjectX500Principal()); |
| |
| boolean b = si.verifySignature(); |
| assertTrue(b, "test-file: " + testFile); |
| pkg.revert(); |
| } |
| } |
| |
| @Test |
| void getMultiSigners() throws Exception { |
| String testFile = "hello-world-signed-twice.docx"; |
| try (OPCPackage pkg = OPCPackage.open(testdata.getFile(testFile), PackageAccess.READ)) { |
| SignatureConfig sic = new SignatureConfig(); |
| SignatureInfo si = new SignatureInfo(); |
| si.setOpcPackage(pkg); |
| si.setSignatureConfig(sic); |
| List<X509Certificate> result = new ArrayList<>(); |
| for (SignaturePart sp : si.getSignatureParts()) { |
| if (sp.validate()) { |
| result.add(sp.getSigner()); |
| } |
| } |
| |
| assertNotNull(result); |
| assertEquals(2, result.size(), "test-file: " + testFile); |
| X509Certificate signer1 = result.get(0); |
| X509Certificate signer2 = result.get(1); |
| LOG.atDebug().log("signer 1: {}", signer1.getSubjectX500Principal()); |
| LOG.atDebug().log("signer 2: {}", signer2.getSubjectX500Principal()); |
| |
| boolean b = si.verifySignature(); |
| assertTrue(b, "test-file: " + testFile); |
| pkg.revert(); |
| } |
| } |
| |
| @Test |
| @DisabledOnJreEx("1.8.0_292") |
| void testSignSpreadsheet() throws Exception { |
| String testFile = "hello-world-unsigned.xlsx"; |
| try (OPCPackage pkg = OPCPackage.open(copy(testdata.getFile(testFile)), PackageAccess.READ_WRITE)) { |
| sign(pkg); |
| } |
| } |
| |
| private static class CommitableWorkbook extends XSSFWorkbook { |
| CommitableWorkbook(OPCPackage pkg) throws IOException { |
| super(pkg); |
| } |
| @Override |
| public void commit() throws IOException { |
| super.commit(); |
| } |
| } |
| |
| @Test |
| @DisabledOnJreEx("1.8.0_292") |
| void testManipulation() throws Exception { |
| // sign & validate |
| String testFile = "hello-world-unsigned.xlsx"; |
| try (OPCPackage pkg = OPCPackage.open(copy(testdata.getFile(testFile)), PackageAccess.READ_WRITE)) { |
| sign(pkg); |
| |
| // manipulate |
| try (CommitableWorkbook wb = new CommitableWorkbook(pkg)) { |
| wb.setSheetName(0, "manipulated"); |
| // ... I don't know, why commit is protected ... |
| wb.commit(); |
| |
| // todo: test a manipulation on a package part, which is not signed |
| // ... maybe in combination with #56164 |
| |
| // validate |
| SignatureConfig sic = new SignatureConfig(); |
| SignatureInfo si = new SignatureInfo(); |
| si.setOpcPackage(pkg); |
| si.setSignatureConfig(sic); |
| boolean b = si.verifySignature(); |
| assertFalse(b, "signature should be broken"); |
| } |
| } |
| } |
| |
| @Test |
| @DisabledOnJreEx("1.8.0_292") |
| void testSignSpreadsheetWithSignatureInfo() throws Exception { |
| 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(certPair.getKey()); |
| sic.setSigningCertificateChain(certPair.getX509Chain()); |
| SignatureInfo si = new SignatureInfo(); |
| si.setOpcPackage(pkg); |
| si.setSignatureConfig(sic); |
| // hash > sha1 doesn't work in excel viewer ... |
| si.confirmSignature(); |
| List<X509Certificate> result = new ArrayList<>(); |
| for (SignaturePart sp : si.getSignatureParts()) { |
| if (sp.validate()) { |
| result.add(sp.getSigner()); |
| } |
| } |
| assertEquals(1, result.size()); |
| } |
| } |
| |
| @Test |
| @DisabledOnJreEx("1.8.0_292") |
| void testSignEnvelopingDocument() throws Exception { |
| String testFile = "hello-world-unsigned.xlsx"; |
| File sigCopy = testdata.getFile(testFile); |
| UnsynchronizedByteArrayOutputStream bos = new UnsynchronizedByteArrayOutputStream(50000); |
| |
| final String execTimestr; |
| |
| DummyKeystore ks = new DummyKeystore(STORE_PASS); |
| KeyCertPair certPair = ks.createDummyKey(); |
| |
| try (OPCPackage pkg = OPCPackage.open(copy(sigCopy), PackageAccess.READ_WRITE)) { |
| |
| final X509CRL crl = ks.generateCrl(certPair); |
| |
| // setup |
| SignatureConfig signatureConfig = new SignatureConfig(); |
| signatureConfig.setKey(certPair.getKey()); |
| |
| /* |
| * We need at least 2 certificates for the XAdES-C complete certificate |
| * refs construction. |
| */ |
| List<X509Certificate> certificateChain = new ArrayList<>(); |
| certificateChain.add(certPair.getX509()); |
| certificateChain.add(certPair.getX509()); |
| signatureConfig.setSigningCertificateChain(certificateChain); |
| |
| signatureConfig.addSignatureFacet(new OOXMLSignatureFacet()); |
| signatureConfig.addSignatureFacet(new EnvelopedSignatureFacet()); |
| signatureConfig.addSignatureFacet(new KeyInfoSignatureFacet()); |
| signatureConfig.addSignatureFacet(new XAdESSignatureFacet()); |
| signatureConfig.addSignatureFacet(new XAdESXLSignatureFacet()); |
| |
| // check for internet, no error means it works |
| boolean mockTsp = (getAccessError("http://timestamp.comodoca.com/rfc3161", true, 10000) != null); |
| |
| // http://timestamping.edelweb.fr/service/tsp |
| // http://tsa.belgium.be/connect |
| // http://timestamp.comodoca.com/authenticode |
| // http://timestamp.comodoca.com/rfc3161 |
| // http://services.globaltrustfinder.com/adss/tsa |
| signatureConfig.setTspUrl("http://timestamp.comodoca.com/rfc3161"); |
| signatureConfig.setTspRequestPolicy(null); // comodoca request fails, if default policy is set ... |
| signatureConfig.setTspOldProtocol(false); |
| |
| signatureConfig.setXadesDigestAlgo(HashAlgorithm.sha512); |
| signatureConfig.setXadesRole("Xades Reviewer"); |
| signatureConfig.setSignatureDescription("test xades signature"); |
| |
| execTimestr = signatureConfig.formatExecutionTime(); |
| |
| //set proxy info if any |
| String proxy = System.getProperty("http_proxy"); |
| if (StringUtil.isNotBlank(proxy)) { |
| signatureConfig.setProxyUrl(proxy); |
| } |
| |
| if (mockTsp) { |
| TimeStampService tspService = (signatureInfo, data, revocationData) -> { |
| revocationData.addCRL(crl); |
| return "time-stamp-token".getBytes(LocaleUtil.CHARSET_1252); |
| }; |
| signatureConfig.setTspService(tspService); |
| } else { |
| TimeStampServiceValidator tspValidator = (validateChain, revocationData) -> { |
| for (X509Certificate certificate : validateChain) { |
| LOG.atDebug().log("certificate: {}", certificate.getSubjectX500Principal()); |
| LOG.atDebug().log("validity: {} - {}", certificate.getNotBefore(), certificate.getNotAfter()); |
| } |
| }; |
| signatureConfig.setTspValidator(tspValidator); |
| signatureConfig.setTspOldProtocol(signatureConfig.getTspUrl().contains("edelweb")); |
| } |
| |
| final RevocationData revocationData = new RevocationData(); |
| revocationData.addCRL(crl); |
| Calendar cal = LocaleUtil.getLocaleCalendar(LocaleUtil.TIMEZONE_UTC); |
| OCSPResp ocspResp = ks.createOcspResp(certPair, cal.getTimeInMillis()); |
| revocationData.addOCSP(ocspResp.getEncoded()); |
| |
| RevocationDataService revocationDataService = revocationChain -> revocationData; |
| signatureConfig.setRevocationDataService(revocationDataService); |
| |
| // operate |
| SignatureInfo si = new SignatureInfo(); |
| si.setOpcPackage(pkg); |
| si.setSignatureConfig(signatureConfig); |
| try { |
| si.confirmSignature(); |
| } catch (RuntimeException e) { |
| // only allow a ConnectException because of timeout, we see this in Jenkins from time to time... |
| if (e.getCause() == null) { |
| throw e; |
| } |
| if ((e.getCause() instanceof ConnectException) || (e.getCause() instanceof SocketTimeoutException)) { |
| assumeFalse(e.getCause().getMessage().contains("timed out"), |
| "Only allowing ConnectException with 'timed out' as message here, but had: " + e); |
| } else if (e.getCause() instanceof IOException) { |
| assumeFalse(e.getCause().getMessage().contains("Error contacting TSP server"), |
| "Only allowing IOException with 'Error contacting TSP server' as message here, but had: " + e); |
| } else if (e.getCause() instanceof RuntimeException) { |
| assumeFalse(e.getCause().getMessage().contains("This site is cur"), |
| "Only allowing RuntimeException with 'This site is cur' as message here, but had: " + e); |
| } |
| throw e; |
| } |
| |
| // verify |
| Iterator<SignaturePart> spIter = si.getSignatureParts().iterator(); |
| assertTrue(spIter.hasNext(), "Had: " + pkg.getRelationshipsByType(PackageRelationshipTypes.DIGITAL_SIGNATURE_ORIGIN)); |
| SignaturePart sp = spIter.next(); |
| boolean valid = sp.validate(); |
| assertTrue(valid); |
| |
| SignatureDocument sigDoc = sp.getSignatureDocument(); |
| String declareNS = |
| "declare namespace xades='http://uri.etsi.org/01903/v1.3.2#'; " |
| + "declare namespace ds='http://www.w3.org/2000/09/xmldsig#'; "; |
| |
| String digestValXQuery = declareNS + |
| "$this/ds:Signature/ds:SignedInfo/ds:Reference"; |
| for (ReferenceType rt : (ReferenceType[]) sigDoc.selectPath(digestValXQuery)) { |
| assertNotNull(rt.getDigestValue()); |
| assertEquals(signatureConfig.getDigestMethodUri(), rt.getDigestMethod().getAlgorithm()); |
| } |
| |
| String certDigestXQuery = declareNS + |
| "$this//xades:SigningCertificate/xades:Cert/xades:CertDigest"; |
| XmlObject[] xoList = sigDoc.selectPath(certDigestXQuery); |
| assertEquals(1, xoList.length); |
| DigestAlgAndValueType certDigest = (DigestAlgAndValueType) xoList[0]; |
| assertNotNull(certDigest.getDigestValue()); |
| |
| String qualPropXQuery = declareNS + |
| "$this/ds:Signature/ds:Object/xades:QualifyingProperties"; |
| xoList = sigDoc.selectPath(qualPropXQuery); |
| assertEquals(1, xoList.length); |
| QualifyingPropertiesType qualProp = (QualifyingPropertiesType) xoList[0]; |
| boolean qualPropXsdOk = qualProp.validate(); |
| assertTrue(qualPropXsdOk); |
| |
| pkg.save(bos); |
| } |
| |
| try (OPCPackage pkg = OPCPackage.open(bos.toInputStream())) { |
| SignatureConfig signatureConfig = new SignatureConfig(); |
| signatureConfig.setUpdateConfigOnValidate(true); |
| |
| SignatureInfo si = new SignatureInfo(); |
| si.setOpcPackage(pkg); |
| si.setSignatureConfig(signatureConfig); |
| |
| assertTrue(si.verifySignature()); |
| |
| assertEquals(HashAlgorithm.sha512, signatureConfig.getXadesDigestAlgo()); |
| assertEquals("Xades Reviewer", signatureConfig.getXadesRole()); |
| assertEquals("test xades signature", signatureConfig.getSignatureDescription()); |
| assertEquals(execTimestr, signatureConfig.formatExecutionTime()); |
| } |
| } |
| |
| public static String getAccessError(String destinationUrl, boolean fireRequest, int timeout) { |
| URL url; |
| try { |
| url = new URL(destinationUrl); |
| } catch (MalformedURLException e) { |
| throw new IllegalArgumentException("Invalid destination URL", e); |
| } |
| |
| HttpURLConnection conn = null; |
| try { |
| conn = (HttpURLConnection) url.openConnection(); |
| |
| // set specified timeout if non-zero |
| if(timeout != 0) { |
| conn.setConnectTimeout(timeout); |
| conn.setReadTimeout(timeout); |
| } |
| |
| conn.setDoOutput(false); |
| conn.setDoInput(true); |
| |
| /* if connecting is not possible this will throw a connection refused exception */ |
| conn.connect(); |
| |
| if (fireRequest) { |
| conn.getInputStream().close(); |
| } |
| /* if connecting is possible we return true here */ |
| return null; |
| |
| } catch (IOException e) { |
| /* exception is thrown -> server not available */ |
| return e.getClass().getName() + ": " + e.getMessage(); |
| } finally { |
| if (conn != null) { |
| conn.disconnect(); |
| } |
| } |
| } |
| |
| @Test |
| void testCertChain() throws Exception { |
| final boolean isIBM = System.getProperty("java.vendor").contains("IBM"); |
| |
| 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(certPair.getKey()); |
| signatureConfig.setSigningCertificateChain(certPair.getX509Chain()); |
| Calendar oldCal = LocaleUtil.getLocaleCalendar(2007, 7, 1); |
| signatureConfig.setExecutionTime(oldCal.getTime()); |
| signatureConfig.setDigestAlgo(HashAlgorithm.sha1); |
| |
| SignatureInfo si = new SignatureInfo(); |
| si.setOpcPackage(pkg); |
| si.setSignatureConfig(signatureConfig); |
| |
| si.confirmSignature(); |
| |
| for (SignaturePart sp : si.getSignatureParts()) { |
| assertTrue(sp.validate(), "Could not validate"); |
| X509Certificate signer = sp.getSigner(); |
| assertNotNull(signer, "signer undefined?!"); |
| List<X509Certificate> certChainRes = sp.getCertChain(); |
| |
| // IBM JDK is still buggy, even after fix for APAR IJ21985 |
| int exp = isIBM ? 1 : 3; |
| assertEquals(exp, certChainRes.size()); |
| } |
| |
| } |
| } |
| |
| @Test |
| @DisabledOnJreEx("1.8.0_292") |
| void testNonSha1() throws Exception { |
| String testFile = "hello-world-unsigned.xlsx"; |
| DummyKeystore ks = new DummyKeystore(STORE_PASS); |
| KeyCertPair certPair = ks.createDummyKey(); |
| |
| SignatureConfig signatureConfig = new SignatureConfig(); |
| signatureConfig.setKey(certPair.getKey()); |
| signatureConfig.setSigningCertificateChain(certPair.getX509Chain()); |
| |
| HashAlgorithm[] testAlgo = {HashAlgorithm.sha224, HashAlgorithm.sha256 |
| , HashAlgorithm.sha384, HashAlgorithm.sha512, HashAlgorithm.ripemd160}; |
| |
| for (HashAlgorithm ha : testAlgo) { |
| signatureConfig.setDigestAlgo(ha); |
| try (OPCPackage pkg = OPCPackage.open(copy(testdata.getFile(testFile)), PackageAccess.READ_WRITE)) { |
| SignatureInfo si = new SignatureInfo(); |
| si.setOpcPackage(pkg); |
| si.setSignatureConfig(signatureConfig); |
| |
| si.confirmSignature(); |
| boolean b = si.verifySignature(); |
| assertTrue(b, "Signature not correctly calculated for " + ha); |
| } catch (EncryptedDocumentException e) { |
| assumeTrue(e.getMessage().startsWith("Export Restrictions")); |
| } |
| } |
| } |
| |
| // Test signing of external references / hyperlinks |
| @Test |
| @DisabledOnJreEx("1.8.0_292") |
| void bug65214() throws Exception { |
| DummyKeystore ks = new DummyKeystore(STORE_PASS); |
| KeyCertPair certPair = ks.createDummyKey(); |
| |
| UnsynchronizedByteArrayOutputStream bos = new UnsynchronizedByteArrayOutputStream(); |
| try (XWPFDocument doc = new XWPFDocument()) { |
| XWPFHyperlinkRun r = doc.createParagraph().createHyperlinkRun("http://poi.apache.org"); |
| r.setText("Hyperlink"); |
| r.setUnderline(UnderlinePatterns.SINGLE); |
| r.setUnderlineColor("0000FF"); |
| doc.write(bos); |
| } |
| |
| SignatureConfig signatureConfig = new SignatureConfig(); |
| signatureConfig.setKey(certPair.getKey()); |
| signatureConfig.setSigningCertificateChain(certPair.getX509Chain()); |
| signatureConfig.setDigestAlgo(HashAlgorithm.sha256); |
| try (OPCPackage pkg = OPCPackage.open(bos.toInputStream())) { |
| SignatureInfo si = new SignatureInfo(); |
| si.setOpcPackage(pkg); |
| si.setSignatureConfig(signatureConfig); |
| si.confirmSignature(); |
| bos.reset(); |
| pkg.save(bos); |
| } catch (EncryptedDocumentException e) { |
| assumeTrue(e.getMessage().startsWith("Export Restrictions")); |
| } |
| |
| try (OPCPackage pkg = OPCPackage.open(bos.toInputStream())) { |
| SignatureInfo si = new SignatureInfo(); |
| si.setOpcPackage(pkg); |
| si.setSignatureConfig(signatureConfig); |
| si.verifySignature(); |
| } catch (EncryptedDocumentException e) { |
| assumeTrue(e.getMessage().startsWith("Export Restrictions")); |
| } |
| } |
| |
| @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)) { |
| wb1.setCompressTempFiles(true); |
| wb1.removeSheetAt(0); |
| UnsynchronizedByteArrayOutputStream os = new UnsynchronizedByteArrayOutputStream(); |
| wb1.write(os); |
| |
| try (OPCPackage pkg = OPCPackage.open(os.toInputStream())) { |
| SignatureConfig signatureConfig = new SignatureConfig(); |
| signatureConfig.setKey(certPair.getKey()); |
| signatureConfig.setSigningCertificateChain(certPair.getX509Chain()); |
| |
| SignatureInfo si = new SignatureInfo(); |
| si.setOpcPackage(pkg); |
| si.setSignatureConfig(signatureConfig); |
| si.confirmSignature(); |
| assertTrue(si.verifySignature(), "invalid signature"); |
| } |
| } |
| } |
| |
| @Test |
| void testMultiSign() throws Exception { |
| Calendar cal = LocaleUtil.getLocaleCalendar(LocaleUtil.TIMEZONE_UTC); |
| cal.clear(); |
| cal.setTimeZone(LocaleUtil.TIMEZONE_UTC); |
| cal.set(2018, Calendar.DECEMBER, 14); |
| |
| // test signing with separate opened packages |
| File tpl = copy(testdata.getFile("hello-world-unsigned.xlsx")); |
| try (OPCPackage pkg = OPCPackage.open(tpl)) { |
| signPkg63011(pkg, "bug63011_key1.pem", true); |
| } |
| |
| try (OPCPackage pkg = OPCPackage.open(tpl)) { |
| signPkg63011(pkg, "bug63011_key2.pem", true); |
| } |
| |
| verifyPkg63011(tpl, true); |
| |
| // test signing with single opened package |
| tpl = copy(testdata.getFile("hello-world-unsigned.xlsx")); |
| try (OPCPackage pkg = OPCPackage.open(tpl)) { |
| signPkg63011(pkg, "bug63011_key1.pem", true); |
| signPkg63011(pkg, "bug63011_key2.pem", true); |
| } |
| |
| verifyPkg63011(tpl, true); |
| |
| try (OPCPackage pkg = OPCPackage.open(tpl)) { |
| signPkg63011(pkg, "bug63011_key1.pem", true); |
| signPkg63011(pkg, "bug63011_key2.pem", false); |
| } |
| |
| verifyPkg63011(tpl, false); |
| } |
| |
| private void verifyPkg63011(File tpl, boolean multi) throws InvalidFormatException, IOException { |
| try (OPCPackage pkg = OPCPackage.open(tpl, PackageAccess.READ)) { |
| SignatureConfig sic = new SignatureConfig(); |
| SignatureInfo si = new SignatureInfo(); |
| si.setOpcPackage(pkg); |
| si.setSignatureConfig(sic); |
| List<X509Certificate> result = new ArrayList<>(); |
| for (SignaturePart sp : si.getSignatureParts()) { |
| if (sp.validate()) { |
| result.add(sp.getSigner()); |
| } |
| } |
| |
| assertNotNull(result); |
| |
| String[] act = result.stream().map(X509Certificate::getSubjectX500Principal).map(X500Principal::getName).toArray(String[]::new); |
| String[] exp = multi ? new String[]{ "CN=Muj Klic", "CN=My Second key" } : new String[]{ "CN=My Second key" }; |
| assertArrayEquals(exp, act); |
| |
| assertTrue(si.verifySignature()); |
| pkg.revert(); |
| } |
| } |
| |
| private void signPkg63011(OPCPackage pkg, String pemFile, boolean multi) |
| throws IOException, GeneralSecurityException, XMLSignatureException, MarshalException { |
| assertNotNull(pkg); |
| |
| DummyKeystore ks = new DummyKeystore("test"); |
| final KeyCertPair certPair; |
| try { |
| certPair = ks.addEntryFromPEM(testdata.getFile(pemFile), "test"); |
| } catch (KeyStoreException e) { |
| // some JDKs do not have the proper setup, let's avoid strange test-failures due to this |
| assumeTrue(e.getMessage().startsWith("unrecognized algorithm name: PBEWithSHA1AndDESede")); |
| throw e; |
| } |
| |
| SignatureConfig config = new SignatureConfig(); |
| config.setKey(certPair.getKey()); |
| config.setSigningCertificateChain(certPair.getX509Chain()); |
| Calendar cal = LocaleUtil.getLocaleCalendar(LocaleUtil.TIMEZONE_UTC); |
| config.setExecutionTime(cal.getTime()); |
| config.setAllowMultipleSignatures(multi); |
| |
| SignatureInfo si = new SignatureInfo(); |
| si.setOpcPackage(pkg); |
| si.setSignatureConfig(config); |
| si.confirmSignature(); |
| } |
| |
| @Test |
| void testRetrieveCertificate() throws InvalidFormatException, IOException { |
| SignatureConfig sic = new SignatureConfig(); |
| // starting with xmlsec 2.3.0 disabling secure validation was necessary because of limitations |
| // on the amount of processed internal references (max. 30) |
| sic.setSecureValidation(false); |
| |
| final File file = testdata.getFile("PPT2016withComment.pptx"); |
| try (final OPCPackage pkg = OPCPackage.open(file, PackageAccess.READ)) { |
| sic.setUpdateConfigOnValidate(true); |
| SignatureInfo si = new SignatureInfo(); |
| si.setOpcPackage(pkg); |
| si.setSignatureConfig(sic); |
| assertTrue(si.verifySignature()); |
| } |
| |
| final List<X509Certificate> certs = sic.getSigningCertificateChain(); |
| assertEquals(1, certs.size()); |
| assertEquals("CN=Test", certs.get(0).getSubjectX500Principal().getName()); |
| assertEquals("SuperDuper-Reviewer", sic.getXadesRole()); |
| assertEquals("Purpose for signing", sic.getSignatureDescription()); |
| assertEquals("2018-06-10T09:00:54Z", sic.formatExecutionTime()); |
| assertEquals(CanonicalizationMethod.INCLUSIVE, sic.getCanonicalizationMethod()); |
| } |
| |
| private interface XmlDocumentPackageInit { |
| POIXMLDocument init(SignatureLine line, OPCPackage pkg) throws IOException, XmlException; |
| } |
| |
| @Test |
| void createXAdES_T_65623() throws Exception { |
| DummyKeystore ks = new DummyKeystore(STORE_PASS); |
| final KeyCertPair certPair; |
| try { |
| certPair = ks.createDummyKey(); |
| } catch (KeyStoreException e) { |
| // some JDKs do not have the proper setup, let's avoid strange test-failures due to this |
| assumeTrue(e.getMessage().startsWith("unrecognized algorithm name: PBEWithSHA1AndDESede")); |
| throw e; |
| } |
| |
| UnsynchronizedByteArrayOutputStream bos = new UnsynchronizedByteArrayOutputStream(); |
| try (XSSFWorkbook wb = new XSSFWorkbook()) { |
| wb.createSheet().createRow(0).createCell(0).setCellValue("Test"); |
| wb.write(bos); |
| } |
| |
| SignatureConfig signatureConfig = new SignatureConfig(); |
| signatureConfig.setDigestAlgo(HashAlgorithm.sha256); |
| signatureConfig.setKey(certPair.getKey()); |
| signatureConfig.setSigningCertificateChain(certPair.getX509Chain()); |
| |
| // mock tsp |
| // signatureConfig.setTspUrl("http://timestamp.digicert.com"); |
| final X509CRL crl = ks.generateCrl(certPair); |
| TimeStampService tspService = (signatureInfo, data, revocationData) -> { |
| revocationData.addCRL(crl); |
| return "time-stamp-token".getBytes(LocaleUtil.CHARSET_1252); |
| }; |
| signatureConfig.setTspService(tspService); |
| |
| signatureConfig.setTspRequestPolicy(null); // comodoca request fails, if default policy is set ... |
| signatureConfig.setTspOldProtocol(false); |
| |
| signatureConfig.setXadesDigestAlgo(HashAlgorithm.sha512); |
| signatureConfig.setXadesRole("Xades Reviewer"); |
| signatureConfig.setSignatureDescription("test xades signature"); |
| |
| signatureConfig.setSignatureFacets(Arrays.asList( |
| new OOXMLSignatureFacet(), |
| new KeyInfoSignatureFacet(), |
| new XAdESSignatureFacet(), |
| new Office2010SignatureFacet(), |
| new XAdESXLSignatureFacet() |
| )); |
| |
| // create signature |
| try (OPCPackage pkg = OPCPackage.open(bos.toInputStream())) { |
| SignatureInfo si = new SignatureInfo(); |
| si.setOpcPackage(pkg); |
| si.setSignatureConfig(signatureConfig); |
| si.confirmSignature(); |
| |
| bos.reset(); |
| pkg.save(bos); |
| } catch (EncryptedDocumentException e) { |
| assumeTrue(e.getMessage().startsWith("Export Restrictions")); |
| } |
| |
| // check if timestamp node is filled |
| try (OPCPackage pkg = OPCPackage.open(bos.toInputStream())) { |
| SignatureInfo si = new SignatureInfo(); |
| si.setOpcPackage(pkg); |
| si.setSignatureConfig(signatureConfig); |
| assertTrue(si.verifySignature()); |
| boolean found = false; |
| for (SignaturePart sp : si.getSignatureParts()) { |
| for (ObjectType ot : sp.getSignatureDocument().getSignature().getObjectArray()) { |
| try (XmlCursor xc = ot.newCursor()) { |
| if (xc.toChild(SignatureFacet.XADES_132_NS, "QualifyingProperties")) { |
| QualifyingPropertiesType qpt = (QualifyingPropertiesType) xc.getObject(); |
| assertTrue(qpt.isSetUnsignedProperties()); |
| UnsignedPropertiesType up = qpt.getUnsignedProperties(); |
| assertTrue(up.isSetUnsignedSignatureProperties()); |
| UnsignedSignaturePropertiesType ups = up.getUnsignedSignatureProperties(); |
| assertEquals(1, ups.sizeOfSignatureTimeStampArray()); |
| XAdESTimeStampType ts = ups.getSignatureTimeStampArray(0); |
| assertEquals(1, ts.sizeOfEncapsulatedTimeStampArray()); |
| EncapsulatedPKIDataType ets = ts.getEncapsulatedTimeStampArray(0); |
| assertFalse(ets.getStringValue().isEmpty()); |
| found = true; |
| } |
| } |
| } |
| } |
| assertTrue(found); |
| } |
| } |
| |
| |
| @Test |
| @DisabledOnJreEx("1.8.0_292") |
| @Tag("scratchpad.ignore") |
| void testSignatureImage() throws Exception { |
| DummyKeystore ks = new DummyKeystore(STORE_PASS); |
| KeyCertPair certPair = ks.createDummyKey(); |
| |
| List<Supplier<SignatureLine>> lines = Arrays.asList(XSSFSignatureLine::new, XWPFSignatureLine::new); |
| for (Supplier<SignatureLine> sup : lines) { |
| SignatureLine line = sup.get(); |
| line.setSuggestedSigner("Jack Sparrow"); |
| line.setSuggestedSigner2("Captain"); |
| line.setSuggestedSignerEmail("jack.bl@ck.perl"); |
| line.setInvalidStamp("Bungling!"); |
| line.setPlainSignature(testdata.readFile("jack-sign.emf")); |
| |
| String[] ext = { "" }; |
| BiFunction<SignatureLine,String[],POIXMLDocument> init = |
| (line instanceof XSSFSignatureLine) |
| ? this::initSignatureImageXSSF |
| : this::initSignatureImageXWPF; |
| |
| File signDoc; |
| try (POIXMLDocument xmlDoc = init.apply(line,ext)) { |
| signDoc = TempFile.createTempFile("visual-signature", ext[0]); |
| try (FileOutputStream fos = new FileOutputStream(signDoc)) { |
| xmlDoc.write(fos); |
| } |
| } |
| |
| try (OPCPackage pkg = OPCPackage.open(signDoc, PackageAccess.READ_WRITE)) { |
| SignatureConfig sic = new SignatureConfig(); |
| sic.setKey(certPair.getKey()); |
| sic.setSigningCertificateChain(certPair.getX509Chain()); |
| |
| line.updateSignatureConfig(sic); |
| |
| sic.setDigestAlgo(HashAlgorithm.sha1); |
| SignatureInfo si = new SignatureInfo(); |
| si.setOpcPackage(pkg); |
| si.setSignatureConfig(sic); |
| // hash > sha1 doesn't work in excel viewer ... |
| si.confirmSignature(); |
| } catch (java.util.ServiceConfigurationError e) { |
| assumeFalse(true, "running on module-path / JPMS and batik is \"kaputt\" WRT JPMS"); |
| } |
| |
| XmlDocumentPackageInit reinit = |
| (line instanceof XSSFSignatureLine) |
| ? this::initSignatureImageXSSF |
| : this::initSignatureImageXWPF; |
| |
| try (OPCPackage pkg = OPCPackage.open(signDoc, PackageAccess.READ)) { |
| SignatureLine line2 = sup.get(); |
| try (POIXMLDocument doc = reinit.init(line2, pkg)) { |
| assertNotNull(doc); |
| |
| line2.parse(); |
| assertEquals(line.getSuggestedSigner(), line2.getSuggestedSigner()); |
| assertEquals(line.getSuggestedSigner2(), line2.getSuggestedSigner2()); |
| assertEquals(line.getSuggestedSignerEmail(), line2.getSuggestedSignerEmail()); |
| } |
| |
| pkg.revert(); |
| } |
| } |
| } |
| |
| private XWPFDocument initSignatureImageXWPF(SignatureLine line, String[] ext) { |
| XWPFDocument doc = new XWPFDocument(); |
| ((XWPFSignatureLine)line).add(doc.createParagraph()); |
| ext[0] = ".docx"; |
| return doc; |
| } |
| |
| private XWPFDocument initSignatureImageXWPF(SignatureLine line, OPCPackage pkg) throws IOException, XmlException { |
| XWPFDocument doc = new XWPFDocument(pkg); |
| ((XWPFSignatureLine)line).parse(doc); |
| return doc; |
| } |
| |
| private XSSFWorkbook initSignatureImageXSSF(SignatureLine line, String[] ext) { |
| XSSFWorkbook xls = new XSSFWorkbook(); |
| XSSFSheet sheet = xls.createSheet(); |
| XSSFClientAnchor anchor = new XSSFClientAnchor(0,0,0,0,3,3,8,13); |
| ((XSSFSignatureLine)line).add(sheet, anchor); |
| ext[0] = ".xlsx"; |
| return xls; |
| } |
| |
| private XSSFWorkbook initSignatureImageXSSF(SignatureLine line, OPCPackage pkg) throws IOException, XmlException { |
| XSSFWorkbook xls = new XSSFWorkbook(pkg); |
| ((XSSFSignatureLine)line).parse(xls.getSheetAt(0)); |
| return xls; |
| } |
| |
| @Test |
| void commitmentType65672() throws Exception { |
| DummyKeystore ks = new DummyKeystore(STORE_PASS); |
| final KeyCertPair certPair; |
| try { |
| certPair = ks.createDummyKey(); |
| } catch (KeyStoreException e) { |
| // some JDKs do not have the proper setup, let's avoid strange test-failures due to this |
| assumeTrue(e.getMessage().startsWith("unrecognized algorithm name: PBEWithSHA1AndDESede")); |
| throw e; |
| } |
| |
| 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 { |
| DummyKeystore ks = (pfxInput == null) ? new DummyKeystore(STORE_PASS) : new DummyKeystore(pfxInput, STORE_PASS); |
| KeyCertPair certPair = ks.createDummyKey(); |
| |
| SignatureConfig signatureConfig = new SignatureConfig(); |
| signatureConfig.setKey(certPair.getKey()); |
| signatureConfig.setSigningCertificateChain(certPair.getX509Chain()); |
| |
| Calendar cal = LocaleUtil.getLocaleCalendar(LocaleUtil.TIMEZONE_UTC); |
| signatureConfig.setExecutionTime(cal.getTime()); |
| signatureConfig.setDigestAlgo(HashAlgorithm.sha1); |
| |
| return signatureConfig; |
| } |
| |
| private void sign(OPCPackage pkgCopy) throws Exception { |
| int signerCount = 1; |
| |
| SignatureConfig signatureConfig = prepareConfig(null); |
| |
| SignatureInfo si = new SignatureInfo(); |
| si.setOpcPackage(pkgCopy); |
| si.setSignatureConfig(signatureConfig); |
| |
| final Document document = DocumentHelper.createDocument(); |
| final DOMSignContext xmlSignContext = si.createXMLSignContext(document); |
| |
| // operate |
| final DOMSignedInfo signedInfo = si.preSign(xmlSignContext); |
| |
| // verify |
| assertNotNull(signedInfo); |
| assertEquals("Office OpenXML Document", signatureConfig.getSignatureDescription()); |
| |
| // setup: key material, signature value |
| final String signatureValue = si.signDigest(xmlSignContext, signedInfo); |
| |
| // operate: postSign |
| si.postSign(xmlSignContext, signatureValue); |
| |
| // verify: signature |
| si.setOpcPackage(pkgCopy); |
| List<X509Certificate> result = new ArrayList<>(); |
| for (SignaturePart sp : si.getSignatureParts()) { |
| if (sp.validate()) { |
| result.add(sp.getSigner()); |
| } |
| } |
| assertEquals(signerCount, result.size()); |
| } |
| |
| private static File copy(File input) throws IOException { |
| String extension = input.getName().replaceAll(".*?(\\.[^.]+)?$", "$1"); |
| if (extension.isEmpty()) { |
| extension = ".zip"; |
| } |
| |
| // ensure that we create the "build" directory as it might not be existing |
| // in the Sonar Maven runs where we are at a different source directory |
| File buildDir = new File("build"); |
| if(!buildDir.exists()) { |
| assertTrue(buildDir.mkdirs(), "Failed to create " + buildDir.getAbsolutePath()); |
| } |
| File tmpFile = new File(buildDir, "sigtest"+extension); |
| |
| try (OutputStream fos = new FileOutputStream(tmpFile)) { |
| try (InputStream fis = new FileInputStream(input)) { |
| IOUtils.copy(fis, fos); |
| } |
| } |
| |
| return tmpFile; |
| } |
| } |