blob: 938d8a662288263ac681072bb9f708be49d92f1b [file] [log] [blame]
/* ====================================================================
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);
}
}