Skip to content

Commit 6116845

Browse files
committed
Introducing a default PKCS7 based External signature container
DEVSIX-7756
1 parent b569f71 commit 6116845

10 files changed

+384
-0
lines changed

sharpenConfiguration.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -604,6 +604,8 @@
604604
<file path="com/itextpdf/signatures/sign/PdfPadesSignerLevelsTest/cmp_prolongDocumentSignaturesTest2_FIPS.pdf"/>
605605
<file path="com/itextpdf/signatures/sign/PdfPadesSignerLevelsTest/cmp_prolongDocumentSignaturesTest3_FIPS.pdf"/>
606606
<file path="com/itextpdf/signatures/sign/TimestampSigTest/cmp_timestampTest01.pdf"/>
607+
<file path="com/itextpdf/signatures/PKCS7ExternalSignatureContainerTest/cmp_testTroughPdfSignerWithTsaClient.pdf"/>
608+
<file path="com/itextpdf/signatures/PKCS7ExternalSignatureContainerTest/cmp_testTroughPdfSignerWithTsaClient_FIPS.pdf"/>
607609
</fileset>
608610
<fileset reason="Bug in java version, not in sharp version, therefor a separate reference file is needed see DEVSIX-6752">
609611
<file path="com/itextpdf/kernel/pdf/xobject/GetImageBytesTest/grayImages.png" />
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
/*
2+
This file is part of the iText (R) project.
3+
Copyright (c) 1998-2023 Apryse Group NV
4+
Authors: Apryse Software.
5+
6+
This program is offered under a commercial and under the AGPL license.
7+
For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below.
8+
9+
AGPL licensing:
10+
This program is free software: you can redistribute it and/or modify
11+
it under the terms of the GNU Affero General Public License as published by
12+
the Free Software Foundation, either version 3 of the License, or
13+
(at your option) any later version.
14+
15+
This program is distributed in the hope that it will be useful,
16+
but WITHOUT ANY WARRANTY; without even the implied warranty of
17+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18+
GNU Affero General Public License for more details.
19+
20+
You should have received a copy of the GNU Affero General Public License
21+
along with this program. If not, see <https://www.gnu.org/licenses/>.
22+
*/
23+
package com.itextpdf.signatures;
24+
25+
import com.itextpdf.bouncycastleconnector.BouncyCastleFactoryCreator;
26+
import com.itextpdf.kernel.exceptions.PdfException;
27+
import com.itextpdf.kernel.pdf.PdfDictionary;
28+
import com.itextpdf.kernel.pdf.PdfName;
29+
30+
import java.io.IOException;
31+
import java.io.InputStream;
32+
import java.security.GeneralSecurityException;
33+
import java.security.PrivateKey;
34+
import java.security.cert.Certificate;
35+
import java.security.cert.X509Certificate;
36+
import java.util.ArrayList;
37+
import java.util.Collection;
38+
import java.util.List;
39+
40+
41+
public class PKCS7ExternalSignatureContainer implements IExternalSignatureContainer {
42+
43+
private final Certificate[] chain;
44+
private final PrivateKey privateKey;
45+
private final String hashAlgorithm;
46+
private IOcspClient ocspClient;
47+
private ICrlClient crlClient;
48+
private ITSAClient tsaClient;
49+
private PdfSigner.CryptoStandard sigType = PdfSigner.CryptoStandard.CMS;
50+
private SignaturePolicyInfo signaturePolicy;
51+
52+
/**
53+
* Creates an instance of PKCS7ExternalSignatureContainer
54+
*
55+
* @param privateKey The private key to sign with
56+
* @param chain The certificate chain
57+
* @param hashAlgorithm The hash algorithm to use
58+
*/
59+
public PKCS7ExternalSignatureContainer(PrivateKey privateKey, Certificate[] chain, String hashAlgorithm) {
60+
this.hashAlgorithm = hashAlgorithm;
61+
this.chain = chain;
62+
this.privateKey = privateKey;
63+
}
64+
65+
@Override
66+
public byte[] sign(InputStream data) throws GeneralSecurityException {
67+
PdfPKCS7 sgn = new PdfPKCS7((PrivateKey) null, chain, hashAlgorithm, null, new BouncyCastleDigest(), false);
68+
if (signaturePolicy != null) {
69+
sgn.setSignaturePolicy(signaturePolicy);
70+
}
71+
byte[] hash;
72+
try {
73+
hash = DigestAlgorithms.digest(data, SignUtils.getMessageDigest(hashAlgorithm));
74+
} catch (IOException e) {
75+
throw new PdfException(e);
76+
}
77+
78+
Collection<byte[]> crlBytes = null;
79+
int i = 0;
80+
while (crlClient != null && crlBytes == null && i < chain.length) {
81+
crlBytes = crlClient.getEncoded((X509Certificate) chain[i++], null);
82+
}
83+
84+
List<byte[]> ocspList = new ArrayList<>();
85+
if (chain.length > 1 && ocspClient != null) {
86+
for (int j = 0; j < chain.length - 1; ++j) {
87+
byte[] ocsp = ocspClient.getEncoded((X509Certificate) chain[j], (X509Certificate) chain[j + 1], null);
88+
if (ocsp != null) {
89+
ocspList.add(ocsp);
90+
}
91+
}
92+
}
93+
byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, sigType, ocspList, crlBytes);
94+
95+
PrivateKeySignature pkSign = new PrivateKeySignature(privateKey, hashAlgorithm,
96+
BouncyCastleFactoryCreator.getFactory().getProviderName());
97+
byte[] signData = pkSign.sign(sh);
98+
99+
sgn.setExternalSignatureValue(
100+
signData,
101+
null,
102+
pkSign.getSignatureAlgorithmName(),
103+
pkSign.getSignatureMechanismParameters()
104+
);
105+
106+
return sgn.getEncodedPKCS7(hash, sigType, tsaClient, ocspList, crlBytes);
107+
}
108+
109+
@Override
110+
public void modifySigningDictionary(PdfDictionary signDic) {
111+
signDic.put(PdfName.Filter, PdfName.Adobe_PPKLite);
112+
signDic.put(PdfName.SubFilter, sigType == PdfSigner.CryptoStandard.CADES
113+
? PdfName.ETSI_CAdES_DETACHED
114+
: PdfName.Adbe_pkcs7_detached);
115+
}
116+
117+
/**
118+
* Set the OcspClient if you want revocation data collected trough Ocsp to be added to the signature
119+
*
120+
* @param ocspClient the client to be used
121+
*/
122+
public void setOcspClient(IOcspClient ocspClient) {
123+
this.ocspClient = ocspClient;
124+
}
125+
126+
/**
127+
* Set the CrlClient if you want revocation data collected trough Crl to be added to the signature
128+
*
129+
* @param crlClient the client to be used
130+
*/
131+
public void setCrlClient(ICrlClient crlClient) {
132+
this.crlClient = crlClient;
133+
}
134+
135+
/**
136+
* Set the TsaClient if you want a TSA timestamp added to the signature
137+
*
138+
* @param tsaClient the client to use
139+
*/
140+
public void setTsaClient(ITSAClient tsaClient) {
141+
this.tsaClient = tsaClient;
142+
}
143+
144+
/**
145+
* Set the signature policy if you want it to be added to the signature
146+
*
147+
* @param signaturePolicy the signature to be set.
148+
*/
149+
public void setSignaturePolicy(SignaturePolicyInfo signaturePolicy) {
150+
this.signaturePolicy = signaturePolicy;
151+
}
152+
153+
/**
154+
* Set a custom signature type, default value {@link PdfSigner.CryptoStandard#CMS}
155+
*
156+
* @param sigType the type of signature to be created
157+
*/
158+
public void setSignatureType(PdfSigner.CryptoStandard sigType) {
159+
this.sigType = sigType;
160+
}
161+
162+
}
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
/*
2+
This file is part of the iText (R) project.
3+
Copyright (c) 1998-2023 Apryse Group NV
4+
Authors: Apryse Software.
5+
6+
This program is offered under a commercial and under the AGPL license.
7+
For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below.
8+
9+
AGPL licensing:
10+
This program is free software: you can redistribute it and/or modify
11+
it under the terms of the GNU Affero General Public License as published by
12+
the Free Software Foundation, either version 3 of the License, or
13+
(at your option) any later version.
14+
15+
This program is distributed in the hope that it will be useful,
16+
but WITHOUT ANY WARRANTY; without even the implied warranty of
17+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18+
GNU Affero General Public License for more details.
19+
20+
You should have received a copy of the GNU Affero General Public License
21+
along with this program. If not, see <https://www.gnu.org/licenses/>.
22+
*/
23+
package com.itextpdf.signatures;
24+
25+
import com.itextpdf.bouncycastleconnector.BouncyCastleFactoryCreator;
26+
import com.itextpdf.commons.bouncycastle.IBouncyCastleFactory;
27+
import com.itextpdf.commons.bouncycastle.operator.AbstractOperatorCreationException;
28+
import com.itextpdf.commons.bouncycastle.pkcs.AbstractPKCSException;
29+
import com.itextpdf.commons.utils.Base64;
30+
import com.itextpdf.commons.utils.DateTimeUtil;
31+
import com.itextpdf.io.source.ByteArrayOutputStream;
32+
import com.itextpdf.kernel.pdf.PdfDocument;
33+
import com.itextpdf.kernel.pdf.PdfReader;
34+
import com.itextpdf.kernel.pdf.PdfVersion;
35+
import com.itextpdf.kernel.pdf.PdfWriter;
36+
import com.itextpdf.kernel.pdf.StampingProperties;
37+
import com.itextpdf.kernel.pdf.WriterProperties;
38+
import com.itextpdf.signatures.testutils.PemFileHelper;
39+
import com.itextpdf.signatures.testutils.SignaturesCompareTool;
40+
import com.itextpdf.signatures.testutils.TimeTestUtil;
41+
import com.itextpdf.signatures.testutils.builder.TestCrlBuilder;
42+
import com.itextpdf.signatures.testutils.client.TestCrlClient;
43+
import com.itextpdf.signatures.testutils.client.TestOcspClient;
44+
import com.itextpdf.signatures.testutils.client.TestTsaClient;
45+
import com.itextpdf.test.ExtendedITextTest;
46+
import com.itextpdf.test.annotations.type.BouncyCastleUnitTest;
47+
import org.junit.Assert;
48+
import org.junit.Before;
49+
import org.junit.BeforeClass;
50+
import org.junit.Test;
51+
import org.junit.experimental.categories.Category;
52+
53+
import java.io.ByteArrayInputStream;
54+
import java.io.FileOutputStream;
55+
import java.io.IOException;
56+
import java.security.GeneralSecurityException;
57+
import java.security.PrivateKey;
58+
import java.security.Security;
59+
import java.security.cert.Certificate;
60+
import java.security.cert.CertificateException;
61+
import java.security.cert.X509Certificate;
62+
import java.util.Arrays;
63+
64+
@Category(BouncyCastleUnitTest.class)
65+
public class PKCS7ExternalSignatureContainerTest extends ExtendedITextTest {
66+
private static final IBouncyCastleFactory FACTORY = BouncyCastleFactoryCreator.getFactory();
67+
private static final boolean FIPS_MODE = "BCFIPS".equals(FACTORY.getProviderName());
68+
69+
private static final String SOURCE_FOLDER = "./src/test/resources/com/itextpdf/signatures/PKCS7ExternalSignatureContainerTest/";
70+
private static final String DESTINATION_FOLDER = "./target/test/com/itextpdf/signatures/PKCS7ExternalSignatureContainerTest/";
71+
private static final String CERTS_SRC = "./src/test/resources/com/itextpdf/signatures/certs/";
72+
73+
private static final char[] PASSWORD = "testpassphrase".toCharArray();
74+
75+
private final static String POLICY_IDENTIFIER = "2.16.724.1.3.1.1.2.1.9";
76+
private final static String POLICY_HASH_BASE64 = "G7roucf600+f03r/o0bAOQ6WAs0=";
77+
private final static byte[] POLICY_HASH = Base64.decode(POLICY_HASH_BASE64);
78+
private final static String POLICY_DIGEST_ALGORITHM = "SHA-256";
79+
private final static String POLICY_URI = "https://sede.060.gob.es/politica_de_firma_anexo_1.pdf";
80+
81+
private Certificate[] chain;
82+
private PrivateKey pk;
83+
84+
private X509Certificate caCert;
85+
private PrivateKey caPrivateKey;
86+
87+
@BeforeClass
88+
public static void before() {
89+
Security.addProvider(FACTORY.getProvider());
90+
createOrClearDestinationFolder(DESTINATION_FOLDER);
91+
}
92+
93+
@Before
94+
public void init()
95+
throws IOException, CertificateException, AbstractPKCSException, AbstractOperatorCreationException {
96+
pk = PemFileHelper.readFirstKey(CERTS_SRC + "signCertRsa01.pem", PASSWORD);
97+
chain = PemFileHelper.readFirstChain(CERTS_SRC + "signCertRsa01.pem");
98+
99+
String caCertP12FileName = CERTS_SRC + "rootRsa.pem";
100+
caCert = (X509Certificate) PemFileHelper.readFirstChain(caCertP12FileName)[0];
101+
caPrivateKey = PemFileHelper.readFirstKey(caCertP12FileName, PASSWORD);
102+
}
103+
104+
@Test
105+
public void testTroughPdfSigner() throws IOException, GeneralSecurityException {
106+
String outFileName = DESTINATION_FOLDER + "testTroughPdfSigner.pdf";
107+
String cmpFileName = SOURCE_FOLDER + "cmp_testTroughPdfSigner.pdf";
108+
PdfSigner pdfSigner = new PdfSigner(new PdfReader(createSimpleDocument()),
109+
new FileOutputStream(outFileName), new StampingProperties());
110+
PKCS7ExternalSignatureContainer pkcs7ExternalSignatureContainer = new PKCS7ExternalSignatureContainer(
111+
pk, chain, DigestAlgorithms.SHA256);
112+
pdfSigner.signExternalContainer(pkcs7ExternalSignatureContainer, 12000);
113+
114+
Assert.assertNull(SignaturesCompareTool.compareSignatures(outFileName, cmpFileName));
115+
}
116+
117+
@Test
118+
public void testTroughPdfSignerWithCrlClient() throws IOException, GeneralSecurityException {
119+
String outFileName = DESTINATION_FOLDER + "testTroughPdfSignerWithCrlClient.pdf";
120+
String cmpFileName = SOURCE_FOLDER + "cmp_testTroughPdfSignerWithCrlClient.pdf";
121+
PdfSigner pdfSigner = new PdfSigner(new PdfReader(createSimpleDocument()),
122+
new FileOutputStream(outFileName), new StampingProperties());
123+
PKCS7ExternalSignatureContainer pkcs7ExternalSignatureContainer = new PKCS7ExternalSignatureContainer(
124+
pk, chain, DigestAlgorithms.SHA256);
125+
126+
TestCrlClient crlClient = new TestCrlClient();
127+
128+
TestCrlBuilder crlBuilder = new TestCrlBuilder(caCert, caPrivateKey, DateTimeUtil.addDaysToDate(TimeTestUtil.TEST_DATE_TIME, -1));
129+
crlClient.addBuilderForCertIssuer(crlBuilder);
130+
pkcs7ExternalSignatureContainer.setCrlClient(crlClient);
131+
132+
pdfSigner.signExternalContainer(pkcs7ExternalSignatureContainer, 12000);
133+
134+
Assert.assertNull(SignaturesCompareTool.compareSignatures(outFileName, cmpFileName));
135+
}
136+
137+
@Test
138+
public void testTroughPdfSignerWithOcspClient() throws IOException, GeneralSecurityException {
139+
String outFileName = DESTINATION_FOLDER + "testTroughPdfSignerWithOcspClient.pdf";
140+
String cmpFileName = SOURCE_FOLDER + "cmp_testTroughPdfSignerWithOcspClient.pdf";
141+
PdfSigner pdfSigner = new PdfSigner(new PdfReader(createSimpleDocument()),
142+
new FileOutputStream(outFileName), new StampingProperties());
143+
PKCS7ExternalSignatureContainer pkcs7ExternalSignatureContainer = new PKCS7ExternalSignatureContainer(
144+
pk, chain, DigestAlgorithms.SHA256);
145+
146+
TestOcspClient ocspClient = new TestOcspClient();
147+
148+
ocspClient.addBuilderForCertIssuer(caCert, caPrivateKey);
149+
pkcs7ExternalSignatureContainer.setOcspClient(ocspClient);
150+
pdfSigner.signExternalContainer(pkcs7ExternalSignatureContainer, 12000);
151+
152+
Assert.assertNull(SignaturesCompareTool.compareSignatures(outFileName, cmpFileName));
153+
}
154+
155+
@Test
156+
public void testTroughPdfSignerWithTsaClient() throws IOException, GeneralSecurityException, AbstractOperatorCreationException, AbstractPKCSException {
157+
String outFileName = DESTINATION_FOLDER + "testTroughPdfSignerWithTsaClient.pdf";
158+
String cmpFileName = SOURCE_FOLDER + "cmp_testTroughPdfSignerWithTsaClient.pdf";
159+
if (FIPS_MODE) {
160+
cmpFileName = cmpFileName.replace(".pdf", "_FIPS.pdf");
161+
}
162+
PdfSigner pdfSigner = new PdfSigner(new PdfReader(createSimpleDocument()),
163+
new FileOutputStream(outFileName), new StampingProperties());
164+
PKCS7ExternalSignatureContainer pkcs7ExternalSignatureContainer = new PKCS7ExternalSignatureContainer(
165+
pk, chain, DigestAlgorithms.SHA256);
166+
String tsaCertP12FileName = CERTS_SRC + "tsCertRsa.pem";
167+
168+
pkcs7ExternalSignatureContainer.setTsaClient(prepareTsaClient(tsaCertP12FileName));
169+
170+
pdfSigner.signExternalContainer(pkcs7ExternalSignatureContainer, 12000);
171+
172+
Assert.assertNull(SignaturesCompareTool.compareSignatures(outFileName, cmpFileName));
173+
}
174+
175+
@Test
176+
public void testTroughPdfSignerWithCadesType() throws IOException, GeneralSecurityException {
177+
String outFileName = DESTINATION_FOLDER + "testTroughPdfSignerWithCadesType.pdf";
178+
String cmpFileName = SOURCE_FOLDER + "cmp_testTroughPdfSignerWithCadesType.pdf";
179+
PdfSigner pdfSigner = new PdfSigner(new PdfReader(createSimpleDocument()),
180+
new FileOutputStream(outFileName), new StampingProperties());
181+
PKCS7ExternalSignatureContainer pkcs7ExternalSignatureContainer = new PKCS7ExternalSignatureContainer(
182+
pk, chain, DigestAlgorithms.SHA256);
183+
pkcs7ExternalSignatureContainer.setSignatureType(PdfSigner.CryptoStandard.CADES);
184+
pdfSigner.signExternalContainer(pkcs7ExternalSignatureContainer, 12000);
185+
186+
Assert.assertNull(SignaturesCompareTool.compareSignatures(outFileName, cmpFileName));
187+
}
188+
189+
@Test
190+
public void testTroughPdfSignerWithSignaturePolicy() throws IOException, GeneralSecurityException {
191+
String outFileName = DESTINATION_FOLDER + "testTroughPdfSignerWithSignaturePolicy.pdf";
192+
String cmpFileName = SOURCE_FOLDER + "cmp_testTroughPdfSignerWithSignaturePolicy.pdf";
193+
PdfSigner pdfSigner = new PdfSigner(new PdfReader(createSimpleDocument()),
194+
new FileOutputStream(outFileName), new StampingProperties());
195+
PKCS7ExternalSignatureContainer pkcs7ExternalSignatureContainer = new PKCS7ExternalSignatureContainer(
196+
pk, chain, DigestAlgorithms.SHA256);
197+
SignaturePolicyInfo policy = new SignaturePolicyInfo(POLICY_IDENTIFIER, POLICY_HASH, POLICY_DIGEST_ALGORITHM, POLICY_URI);
198+
199+
pkcs7ExternalSignatureContainer.setSignaturePolicy(policy);
200+
pdfSigner.signExternalContainer(pkcs7ExternalSignatureContainer, 12000);
201+
202+
Assert.assertNull(SignaturesCompareTool.compareSignatures(outFileName, cmpFileName));
203+
}
204+
205+
private static ByteArrayInputStream createSimpleDocument() {
206+
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
207+
WriterProperties writerProperties = new WriterProperties().setPdfVersion(PdfVersion.PDF_2_0);
208+
PdfDocument document = new PdfDocument(new PdfWriter(outputStream, writerProperties));
209+
document.addNewPage();
210+
document.close();
211+
return new ByteArrayInputStream(outputStream.toByteArray());
212+
}
213+
214+
private static TestTsaClient prepareTsaClient(String tsaCertP12FileName)
215+
throws IOException, CertificateException, AbstractPKCSException, AbstractOperatorCreationException {
216+
Certificate[] tsaChain = PemFileHelper.readFirstChain(tsaCertP12FileName);
217+
PrivateKey tsaPrivateKey = PemFileHelper.readFirstKey(tsaCertP12FileName, PASSWORD);
218+
return new TestTsaClient(Arrays.asList(tsaChain), tsaPrivateKey);
219+
}
220+
}

0 commit comments

Comments
 (0)