| /* ==================================================================== |
| 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.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.BufferedReader; |
| import java.io.ByteArrayInputStream; |
| import java.io.ByteArrayOutputStream; |
| 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.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; |
| import java.util.function.Supplier; |
| |
| 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.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.CryptoFunctions; |
| import org.apache.poi.poifs.crypt.HashAlgorithm; |
| 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.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.poifs.storage.RawDataUtil; |
| import org.apache.poi.ss.usermodel.WorkbookFactory; |
| import org.apache.poi.util.IOUtils; |
| import org.apache.poi.util.LocaleUtil; |
| 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.XWPFDocument; |
| import org.apache.poi.xwpf.usermodel.XWPFSignatureLine; |
| import org.apache.xmlbeans.SystemProperties; |
| 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.QualifyingPropertiesType; |
| import org.junit.jupiter.api.AfterAll; |
| import org.junit.jupiter.api.BeforeAll; |
| import org.junit.jupiter.api.Disabled; |
| import org.junit.jupiter.api.Test; |
| 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 Calendar cal; |
| private KeyPair keyPair; |
| private X509Certificate x509; |
| |
| @BeforeAll |
| public static void setUpClass() { |
| POITestCase.setImageIOCacheDir(); |
| } |
| |
| @AfterAll |
| public static void removeUserLocale() { |
| LocaleUtil.resetUserLocale(); |
| } |
| |
| @BeforeAll |
| public static void initBouncy() { |
| CryptoFunctions.registerBouncyCastle(); |
| |
| // 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"); |
| //System.out.println("Having: " + additionalJar); |
| assumeTrue(additionalJar == null || additionalJar.trim().length() == 0, |
| "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); |
| |
| ByteArrayOutputStream bos = new ByteArrayOutputStream(100000); |
| try (XSSFWorkbook wb1 = new XSSFWorkbook()) { |
| wb1.createSheet().createRow(1).createCell(1).setCellValue("Test"); |
| wb1.write(bos); |
| } |
| |
| try (OPCPackage pkg1 = OPCPackage.open(new ByteArrayInputStream(bos.toByteArray()))) { |
| si.setOpcPackage(pkg1); |
| si.confirmSignature(); |
| assertTrue(si.verifySignature()); |
| bos.reset(); |
| pkg1.save(bos); |
| } |
| |
| try (XSSFWorkbook wb2 = new XSSFWorkbook(new ByteArrayInputStream(bos.toByteArray()))) { |
| 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); |
| } |
| } |
| |
| @Test |
| void getSignerUnsigned() throws Exception { |
| String[] testFiles = { |
| "hello-world-unsigned.docx", |
| "hello-world-unsigned.pptx", |
| "hello-world-unsigned.xlsx", |
| "hello-world-office-2010-technical-preview-unsigned.docx" |
| }; |
| |
| for (String testFile : testFiles) { |
| 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()); |
| } |
| } |
| |
| @Test |
| void getSigner() throws Exception { |
| String[] testFiles = { |
| "hyperlink-example-signed.docx", |
| "hello-world-signed.docx", |
| "hello-world-signed.pptx", |
| "hello-world-signed.xlsx", |
| "hello-world-office-2010-technical-preview.docx", |
| "ms-office-2010-signed.docx", |
| "ms-office-2010-signed.pptx", |
| "ms-office-2010-signed.xlsx", |
| "Office2010-SP1-XAdES-X-L.docx", |
| "signed.docx" |
| }; |
| |
| for (String testFile : testFiles) { |
| 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(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 |
| 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); |
| } |
| public void commit() throws IOException { |
| super.commit(); |
| } |
| } |
| |
| @Test |
| 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 |
| void testSignSpreadsheetWithSignatureInfo() throws Exception { |
| initKeyPair(); |
| String testFile = "hello-world-unsigned.xlsx"; |
| 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)); |
| 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 |
| void testSignEnvelopingDocument() throws Exception { |
| String testFile = "hello-world-unsigned.xlsx"; |
| File sigCopy = testdata.getFile(testFile); |
| ByteArrayOutputStream bos = new ByteArrayOutputStream(50000); |
| |
| final String execTimestr; |
| |
| |
| try (OPCPackage pkg = OPCPackage.open(copy(sigCopy), PackageAccess.READ_WRITE)) { |
| |
| initKeyPair(); |
| final X509CRL crl = generateCrl(x509, keyPair.getPrivate()); |
| |
| // setup |
| SignatureConfig signatureConfig = new SignatureConfig(); |
| signatureConfig.setKey(keyPair.getPrivate()); |
| |
| /* |
| * We need at least 2 certificates for the XAdES-C complete certificate |
| * refs construction. |
| */ |
| List<X509Certificate> certificateChain = new ArrayList<>(); |
| certificateChain.add(x509); |
| certificateChain.add(x509); |
| 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 (proxy != null && proxy.trim().length() > 0) { |
| 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); |
| OCSPResp ocspResp = createOcspResp(x509, x509, x509, keyPair.getPrivate(), 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(xoList.length, 1); |
| DigestAlgAndValueType certDigest = (DigestAlgAndValueType) xoList[0]; |
| assertNotNull(certDigest.getDigestValue()); |
| |
| String qualPropXQuery = declareNS + |
| "$this/ds:Signature/ds:Object/xades:QualifyingProperties"; |
| xoList = sigDoc.selectPath(qualPropXQuery); |
| assertEquals(xoList.length, 1); |
| QualifyingPropertiesType qualProp = (QualifyingPropertiesType) xoList[0]; |
| boolean qualPropXsdOk = qualProp.validate(); |
| assertTrue(qualPropXsdOk); |
| |
| pkg.save(bos); |
| } |
| |
| try (OPCPackage pkg = OPCPackage.open(new ByteArrayInputStream(bos.toByteArray()))) { |
| 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"); |
| |
| 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<X509Certificate> certChain = new ArrayList<>(); |
| for (Certificate c : chainList) { |
| certChain.add((X509Certificate)c); |
| } |
| x509 = certChain.get(0); |
| keyPair = new KeyPair(x509.getPublicKey(), (PrivateKey)key); |
| |
| String testFile = "hello-world-unsigned.xlsx"; |
| try (OPCPackage pkg = OPCPackage.open(copy(testdata.getFile(testFile)), PackageAccess.READ_WRITE)) { |
| |
| SignatureConfig signatureConfig = new SignatureConfig(); |
| signatureConfig.setKey(keyPair.getPrivate()); |
| signatureConfig.setSigningCertificateChain(certChain); |
| 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 |
| void testNonSha1() throws Exception { |
| String testFile = "hello-world-unsigned.xlsx"; |
| initKeyPair(); |
| |
| SignatureConfig signatureConfig = new SignatureConfig(); |
| signatureConfig.setKey(keyPair.getPrivate()); |
| signatureConfig.setSigningCertificateChain(Collections.singletonList(x509)); |
| |
| HashAlgorithm[] testAlgo = {HashAlgorithm.sha224, HashAlgorithm.sha256 |
| , HashAlgorithm.sha384, HashAlgorithm.sha512, HashAlgorithm.ripemd160}; |
| |
| for (HashAlgorithm ha : testAlgo) { |
| signatureConfig.setDigestAlgo(ha); |
| 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 |
| void bug58630() throws Exception { |
| // 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); |
| ByteArrayOutputStream os = new ByteArrayOutputStream(); |
| wb1.write(os); |
| |
| try (OPCPackage pkg = OPCPackage.open(new ByteArrayInputStream(os.toByteArray()))) { |
| initKeyPair(); |
| SignatureConfig signatureConfig = new SignatureConfig(); |
| signatureConfig.setKey(keyPair.getPrivate()); |
| signatureConfig.setSigningCertificateChain(Collections.singletonList(x509)); |
| |
| SignatureInfo si = new SignatureInfo(); |
| si.setOpcPackage(pkg); |
| si.setSignatureConfig(signatureConfig); |
| si.confirmSignature(); |
| assertTrue(si.verifySignature(), "invalid signature"); |
| } |
| } |
| } |
| |
| @Test |
| void testMultiSign() throws Exception { |
| 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); |
| |
| if (multi) { |
| assertEquals(2, result.size()); |
| assertEquals("CN=Muj Klic", result.get(0).getSubjectDN().toString()); |
| assertEquals("CN=My Second key", result.get(1).getSubjectDN().toString()); |
| } else { |
| assertEquals(1, result.size()); |
| assertEquals("CN=My Second key", result.get(0).getSubjectDN().toString()); |
| } |
| |
| assertTrue(si.verifySignature()); |
| pkg.revert(); |
| } |
| } |
| |
| private void signPkg63011(OPCPackage pkg, String pemFile, boolean multi) |
| throws IOException, CertificateException, XMLSignatureException, MarshalException { |
| assertNotNull(pkg); |
| initKeyFromPEM(testdata.getFile(pemFile)); |
| |
| SignatureConfig config = new SignatureConfig(); |
| config.setKey(keyPair.getPrivate()); |
| config.setSigningCertificateChain(Collections.singletonList(x509)); |
| 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(); |
| 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).getSubjectDN().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 testSignatureImage() throws Exception { |
| initKeyPair(); |
| |
| 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(keyPair.getPrivate()); |
| sic.setSigningCertificateChain(Collections.singletonList(x509)); |
| |
| 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; |
| } |
| |
| |
| private SignatureConfig prepareConfig(String pfxInput) throws Exception { |
| initKeyPair(pfxInput); |
| |
| SignatureConfig signatureConfig = new SignatureConfig(); |
| signatureConfig.setKey(keyPair.getPrivate()); |
| signatureConfig.setSigningCertificateChain(Collections.singletonList(x509)); |
| 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 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); |
| } |
| } 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()) { |
| 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; |
| } |
| |
| 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); |
| } |
| } |