Skip to content

Commit cb59745

Browse files
committed
Cover TSAClientBouncyCastle with tests
DEVSIX-6150
1 parent e7a7258 commit cb59745

File tree

2 files changed

+273
-13
lines changed

2 files changed

+273
-13
lines changed
Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
/*
2+
This file is part of the iText (R) project.
3+
Copyright (c) 1998-2022 iText Group NV
4+
Authors: iText 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.commons.utils.MessageFormatUtil;
26+
import com.itextpdf.kernel.exceptions.PdfException;
27+
import com.itextpdf.signatures.exceptions.SignExceptionMessageConstant;
28+
import com.itextpdf.signatures.testutils.builder.TestTimestampTokenBuilder;
29+
import com.itextpdf.test.ExtendedITextTest;
30+
import com.itextpdf.test.annotations.type.UnitTest;
31+
import com.itextpdf.test.signutils.Pkcs12FileHelper;
32+
33+
import java.security.GeneralSecurityException;
34+
import java.security.MessageDigest;
35+
import java.security.PrivateKey;
36+
import java.security.cert.Certificate;
37+
import java.util.Arrays;
38+
import java.util.List;
39+
import org.bouncycastle.tsp.TimeStampResponse;
40+
import org.bouncycastle.tsp.TimeStampToken;
41+
import org.bouncycastle.tsp.TimeStampTokenInfo;
42+
import org.junit.Assert;
43+
import org.junit.Test;
44+
import org.junit.experimental.categories.Category;
45+
46+
@Category(UnitTest.class)
47+
public class TSAClientBouncyCastleTest extends ExtendedITextTest {
48+
49+
@Test
50+
public void setTSAInfoTest() {
51+
TSAClientBouncyCastle clientBouncyCastle = new TSAClientBouncyCastle("url");
52+
CustomItsaInfoBouncyCastle infoBouncyCastle = new CustomItsaInfoBouncyCastle();
53+
clientBouncyCastle.setTSAInfo(infoBouncyCastle);
54+
Assert.assertEquals(infoBouncyCastle, clientBouncyCastle.tsaInfo);
55+
}
56+
57+
@Test
58+
public void testTsaClientBouncyCastleConstructor3Args() {
59+
String userName = "user";
60+
String password = "password";
61+
String url = "url";
62+
63+
TSAClientBouncyCastle tsaClientBouncyCastle = new TSAClientBouncyCastle(url, userName, password);
64+
Assert.assertEquals(url, tsaClientBouncyCastle.tsaURL);
65+
Assert.assertEquals(userName, tsaClientBouncyCastle.tsaUsername);
66+
Assert.assertEquals(password, tsaClientBouncyCastle.tsaPassword);
67+
Assert.assertEquals(TSAClientBouncyCastle.DEFAULTTOKENSIZE, tsaClientBouncyCastle.tokenSizeEstimate);
68+
Assert.assertEquals(TSAClientBouncyCastle.DEFAULTHASHALGORITHM, tsaClientBouncyCastle.digestAlgorithm);
69+
}
70+
71+
@Test
72+
public void testTsaClientBouncyCastleConstructorAllArgs() {
73+
String userName = "user";
74+
String password = "password";
75+
String url = "url";
76+
int tokenSize = 1024;
77+
String digestAlgorithm = "SHA-1";
78+
79+
TSAClientBouncyCastle tsaClientBouncyCastle = new TSAClientBouncyCastle(url, userName, password,
80+
tokenSize, digestAlgorithm);
81+
Assert.assertEquals(url, tsaClientBouncyCastle.tsaURL);
82+
Assert.assertEquals(userName, tsaClientBouncyCastle.tsaUsername);
83+
Assert.assertEquals(password, tsaClientBouncyCastle.tsaPassword);
84+
Assert.assertEquals(tokenSize, tsaClientBouncyCastle.tokenSizeEstimate);
85+
Assert.assertEquals(digestAlgorithm, tsaClientBouncyCastle.digestAlgorithm);
86+
}
87+
88+
@Test
89+
public void testTsaClientBouncyCastleConstructor1Arg() {
90+
String url = "url";
91+
92+
TSAClientBouncyCastle tsaClientBouncyCastle = new TSAClientBouncyCastle(url);
93+
Assert.assertEquals(url, tsaClientBouncyCastle.tsaURL);
94+
Assert.assertNull(tsaClientBouncyCastle.tsaUsername);
95+
Assert.assertNull(tsaClientBouncyCastle.tsaPassword);
96+
Assert.assertEquals(TSAClientBouncyCastle.DEFAULTTOKENSIZE, tsaClientBouncyCastle.tokenSizeEstimate);
97+
Assert.assertEquals(TSAClientBouncyCastle.DEFAULTHASHALGORITHM, tsaClientBouncyCastle.digestAlgorithm);
98+
}
99+
100+
@Test
101+
public void getTokenSizeEstimateTest() {
102+
String userName = "user";
103+
String password = "password";
104+
String url = "url";
105+
String digestAlgorithm = "SHA-256";
106+
int tokenSizeEstimate = 4096;
107+
108+
TSAClientBouncyCastle tsaClientBouncyCastle = new TSAClientBouncyCastle(url, userName, password,
109+
tokenSizeEstimate, digestAlgorithm);
110+
Assert.assertEquals(tokenSizeEstimate, tsaClientBouncyCastle.getTokenSizeEstimate());
111+
}
112+
113+
@Test
114+
public void setGetTsaReqPolicyTest() {
115+
String regPolicy = "regPolicy";
116+
117+
TSAClientBouncyCastle clientBouncyCastle = new TSAClientBouncyCastle("url");
118+
clientBouncyCastle.setTSAReqPolicy(regPolicy);
119+
Assert.assertEquals(regPolicy, clientBouncyCastle.getTSAReqPolicy());
120+
}
121+
122+
@Test
123+
public void getMessageDigestTest() throws GeneralSecurityException {
124+
String userName = "user";
125+
String password = "password";
126+
String url = "url";
127+
String digestAlgorithm = "SHA-256";
128+
int tokenSizeEstimate = 4096;
129+
130+
TSAClientBouncyCastle tsaClientBouncyCastle = new TSAClientBouncyCastle(url, userName, password,
131+
tokenSizeEstimate, digestAlgorithm);
132+
MessageDigest digest = tsaClientBouncyCastle.getMessageDigest();
133+
Assert.assertNotNull(digest);
134+
Assert.assertEquals(digestAlgorithm, digest.getAlgorithm());
135+
}
136+
137+
@Test
138+
public void getTimeStampTokenTest() throws Exception {
139+
String allowedDigest = "SHA256";
140+
String signatureAlgorithm = "SHA256withRSA";
141+
String policyOid = "1.3.6.1.4.1.45794.1.1";
142+
143+
CustomTsaClientBouncyCastle tsaClientBouncyCastle = new CustomTsaClientBouncyCastle("", signatureAlgorithm,
144+
allowedDigest);
145+
tsaClientBouncyCastle.setTSAReqPolicy(policyOid);
146+
CustomItsaInfoBouncyCastle itsaInfoBouncyCastle = new CustomItsaInfoBouncyCastle();
147+
tsaClientBouncyCastle.setTSAInfo(itsaInfoBouncyCastle);
148+
byte[] timestampTokenArray = tsaClientBouncyCastle.getTimeStampToken(tsaClientBouncyCastle
149+
.getMessageDigest().digest());
150+
151+
TimeStampToken expectedToken = new TimeStampResponse(tsaClientBouncyCastle.getExpectedTsaResponseBytes())
152+
.getTimeStampToken();
153+
TimeStampTokenInfo expectedTsTokenInfo = expectedToken.getTimeStampInfo();
154+
TimeStampTokenInfo resultTsTokenInfo = itsaInfoBouncyCastle.getTimeStampTokenInfo();
155+
156+
Assert.assertNotNull(timestampTokenArray);
157+
Assert.assertNotNull(resultTsTokenInfo);
158+
Assert.assertArrayEquals(expectedTsTokenInfo.getEncoded(), resultTsTokenInfo.getEncoded());
159+
Assert.assertArrayEquals(expectedToken.getEncoded(), timestampTokenArray);
160+
}
161+
162+
@Test
163+
public void getTimeStampTokenFailureExceptionTest() throws Exception {
164+
String allowedDigest = "MD5";
165+
String signatureAlgorithm = "SHA256withRSA";
166+
String url = "url";
167+
168+
CustomTsaClientBouncyCastle tsaClientBouncyCastle = new CustomTsaClientBouncyCastle(url, signatureAlgorithm,
169+
allowedDigest);
170+
tsaClientBouncyCastle.setTSAInfo(new CustomItsaInfoBouncyCastle());
171+
172+
byte[] digest = tsaClientBouncyCastle.getMessageDigest().digest();
173+
Exception e = Assert.assertThrows(PdfException.class,
174+
() -> tsaClientBouncyCastle.getTimeStampToken(digest)
175+
);
176+
177+
Assert.assertEquals(MessageFormatUtil.format(SignExceptionMessageConstant.INVALID_TSA_RESPONSE, url, "128"),
178+
e.getMessage());
179+
}
180+
181+
private static final class CustomTsaClientBouncyCastle extends TSAClientBouncyCastle {
182+
private static final char[] PASSWORD = "testpass".toCharArray();
183+
184+
private static final String CERTS_SRC = "./src/test/resources/com/itextpdf/signatures/certs/";
185+
186+
private final PrivateKey tsaPrivateKey;
187+
private final List<Certificate> tsaCertificateChain;
188+
private final String signatureAlgorithm;
189+
private final String allowedDigest;
190+
191+
private byte[] expectedTsaResponseBytes;
192+
193+
public CustomTsaClientBouncyCastle(String url, String signatureAlgorithm, String allowedDigest)
194+
throws Exception {
195+
super(url);
196+
197+
this.signatureAlgorithm = signatureAlgorithm;
198+
this.allowedDigest = allowedDigest;
199+
tsaPrivateKey = Pkcs12FileHelper
200+
.readFirstKey(CERTS_SRC + "signCertRsa01.p12", PASSWORD, PASSWORD);
201+
202+
String tsaCertFileName = CERTS_SRC + "tsCertRsa.p12";
203+
tsaCertificateChain = Arrays.asList(Pkcs12FileHelper.readFirstChain(tsaCertFileName, PASSWORD));
204+
}
205+
206+
public byte[] getExpectedTsaResponseBytes() {
207+
return expectedTsaResponseBytes;
208+
}
209+
210+
@Override
211+
protected byte[] getTSAResponse(byte[] requestBytes) {
212+
TestTimestampTokenBuilder builder = new TestTimestampTokenBuilder(tsaCertificateChain, tsaPrivateKey);
213+
expectedTsaResponseBytes = builder.createTSAResponse(requestBytes, signatureAlgorithm, allowedDigest);
214+
return expectedTsaResponseBytes;
215+
}
216+
}
217+
218+
private static final class CustomItsaInfoBouncyCastle implements ITSAInfoBouncyCastle {
219+
220+
private TimeStampTokenInfo timeStampTokenInfo;
221+
222+
@Override
223+
public void inspectTimeStampTokenInfo(TimeStampTokenInfo info) {
224+
this.timeStampTokenInfo = info;
225+
}
226+
227+
228+
public TimeStampTokenInfo getTimeStampTokenInfo() {
229+
return timeStampTokenInfo;
230+
}
231+
}
232+
}

sign/src/test/java/com/itextpdf/signatures/testutils/builder/TestTimestampTokenBuilder.java

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,18 @@ This file is part of the iText (R) project.
4545
import com.itextpdf.commons.utils.DateTimeUtil;
4646
import com.itextpdf.commons.utils.SystemUtil;
4747
import com.itextpdf.signatures.DigestAlgorithms;
48+
4849
import java.io.IOException;
4950
import java.math.BigInteger;
5051
import java.security.PrivateKey;
5152
import java.security.cert.Certificate;
5253
import java.security.cert.CertificateEncodingException;
5354
import java.security.cert.X509Certificate;
55+
import java.util.Collections;
5456
import java.util.Date;
57+
import java.util.HashSet;
5558
import java.util.List;
59+
import java.util.Set;
5660
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
5761
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
5862
import org.bouncycastle.cert.jcajce.JcaCertStore;
@@ -66,12 +70,16 @@ This file is part of the iText (R) project.
6670
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
6771
import org.bouncycastle.tsp.TSPException;
6872
import org.bouncycastle.tsp.TimeStampRequest;
73+
import org.bouncycastle.tsp.TimeStampResponseGenerator;
6974
import org.bouncycastle.tsp.TimeStampToken;
7075
import org.bouncycastle.tsp.TimeStampTokenGenerator;
7176

7277
public class TestTimestampTokenBuilder {
7378
private static final String SIGN_ALG = "SHA256withRSA";
7479

80+
// just a more or less random oid of timestamp policy
81+
private static final String POLICY_OID = "1.3.6.1.4.1.45794.1.1";
82+
7583
private List<Certificate> tsaCertificateChain;
7684
private PrivateKey tsaPrivateKey;
7785

@@ -84,19 +92,8 @@ public TestTimestampTokenBuilder(List<Certificate> tsaCertificateChain, PrivateK
8492
}
8593

8694
public byte[] createTimeStampToken(TimeStampRequest request) throws OperatorCreationException, TSPException, IOException, CertificateEncodingException {
87-
ContentSigner signer = new JcaContentSignerBuilder(SIGN_ALG).build(tsaPrivateKey);
88-
DigestCalculatorProvider digestCalcProviderProvider = new JcaDigestCalculatorProviderBuilder().build();
89-
90-
SignerInfoGenerator siGen =
91-
new JcaSignerInfoGeneratorBuilder(digestCalcProviderProvider)
92-
.build(signer, (X509Certificate) tsaCertificateChain.get(0));
93-
94-
// just a more or less random oid of timestamp policy
95-
ASN1ObjectIdentifier policy = new ASN1ObjectIdentifier("1.3.6.1.4.1.45794.1.1");
96-
97-
String digestForTsSigningCert = DigestAlgorithms.getAllowedDigest("SHA1");
98-
DigestCalculator dgCalc = digestCalcProviderProvider.get(new AlgorithmIdentifier(new ASN1ObjectIdentifier(digestForTsSigningCert)));
99-
TimeStampTokenGenerator tsTokGen = new TimeStampTokenGenerator(siGen, dgCalc, policy);
95+
TimeStampTokenGenerator tsTokGen = createTimeStampTokenGenerator(tsaPrivateKey,
96+
tsaCertificateChain.get(0), SIGN_ALG, "SHA1", POLICY_OID);
10097
tsTokGen.setAccuracySeconds(1);
10198

10299
// TODO setting this is somewhat wrong. Acrobat and openssl recognize timestamp tokens generated with this line as corrupted
@@ -111,4 +108,35 @@ public byte[] createTimeStampToken(TimeStampRequest request) throws OperatorCrea
111108
TimeStampToken tsToken = tsTokGen.generate(request, serialNumber, genTime);
112109
return tsToken.getEncoded();
113110
}
111+
112+
public byte[] createTSAResponse(byte[] requestBytes, String signatureAlgorithm, String allowedDigest) {
113+
try {
114+
String digestForTsSigningCert = DigestAlgorithms.getAllowedDigest(allowedDigest);
115+
TimeStampTokenGenerator tokenGenerator = createTimeStampTokenGenerator(tsaPrivateKey,
116+
tsaCertificateChain.get(0), signatureAlgorithm, allowedDigest, POLICY_OID);
117+
118+
Set<String> algorithms = new HashSet<>(Collections.singletonList(digestForTsSigningCert));
119+
TimeStampResponseGenerator generator = new TimeStampResponseGenerator(tokenGenerator, algorithms);
120+
TimeStampRequest request = new TimeStampRequest(requestBytes);
121+
return generator.generate(request, request.getNonce(), new Date()).getEncoded();
122+
} catch (Exception e) {
123+
return null;
124+
}
125+
}
126+
127+
private static TimeStampTokenGenerator createTimeStampTokenGenerator(PrivateKey pk, Certificate cert,
128+
String signatureAlgorithm, String allowedDigest, String policyOid)
129+
throws TSPException, OperatorCreationException, CertificateEncodingException {
130+
ContentSigner signer = new JcaContentSignerBuilder(signatureAlgorithm).build(pk);
131+
DigestCalculatorProvider digestCalcProviderProvider = new JcaDigestCalculatorProviderBuilder().build();
132+
SignerInfoGenerator siGen =
133+
new JcaSignerInfoGeneratorBuilder(digestCalcProviderProvider)
134+
.build(signer, (X509Certificate) cert);
135+
136+
String digestForTsSigningCert = DigestAlgorithms.getAllowedDigest(allowedDigest);
137+
DigestCalculator dgCalc = digestCalcProviderProvider.get(
138+
new AlgorithmIdentifier(new ASN1ObjectIdentifier(digestForTsSigningCert)));
139+
ASN1ObjectIdentifier policy = new ASN1ObjectIdentifier(policyOid);
140+
return new TimeStampTokenGenerator(siGen, dgCalc, policy);
141+
}
114142
}

0 commit comments

Comments
 (0)