Skip to content

Commit 0171de5

Browse files
committed
Add CMS container class for use with 2 phase signing
DEVSIX-7842
1 parent 6ef7dd7 commit 0171de5

File tree

25 files changed

+3194
-82
lines changed

25 files changed

+3194
-82
lines changed

bouncy-castle-adapter/src/main/java/com/itextpdf/bouncycastle/asn1/ASN1EncodableBC.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ This file is part of the iText (R) project.
2424

2525
import com.itextpdf.commons.bouncycastle.asn1.IASN1Encodable;
2626
import com.itextpdf.commons.bouncycastle.asn1.IASN1Primitive;
27+
import org.bouncycastle.asn1.ASN1Encodable;
2728

2829
import java.util.Objects;
29-
import org.bouncycastle.asn1.ASN1Encodable;
3030

3131
/**
3232
* Wrapper class for {@link ASN1Encodable}.
@@ -76,9 +76,10 @@ public boolean equals(Object o) {
7676
if (this == o) {
7777
return true;
7878
}
79-
if (o == null || getClass() != o.getClass()) {
79+
if (o == null || !this.getClass().isAssignableFrom(o.getClass())) {
8080
return false;
8181
}
82+
8283
ASN1EncodableBC that = (ASN1EncodableBC) o;
8384
return Objects.equals(encodable, that.encodable);
8485
}

bouncy-castle-adapter/src/main/java/com/itextpdf/bouncycastle/cert/X509CertificateHolderBC.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,13 @@ This file is part of the iText (R) project.
2222
*/
2323
package com.itextpdf.bouncycastle.cert;
2424

25+
import com.itextpdf.bouncycastle.asn1.x509.AlgorithmIdentifierBC;
26+
import com.itextpdf.commons.bouncycastle.asn1.x509.IAlgorithmIdentifier;
2527
import com.itextpdf.commons.bouncycastle.cert.IX509CertificateHolder;
2628

2729
import java.io.IOException;
2830
import java.util.Objects;
31+
2932
import org.bouncycastle.cert.X509CertificateHolder;
3033

3134
/**
@@ -62,6 +65,16 @@ public X509CertificateHolder getCertificateHolder() {
6265
return certificateHolder;
6366
}
6467

68+
/**
69+
* {@inheritDoc}
70+
*
71+
* @return {@inheritDoc}
72+
*/
73+
@Override
74+
public IAlgorithmIdentifier getSignatureAlgorithm() {
75+
return new AlgorithmIdentifierBC(certificateHolder.getSignatureAlgorithm());
76+
}
77+
6578
/**
6679
* Indicates whether some other object is "equal to" this one. Compares wrapped objects.
6780
*/

bouncy-castle-fips-adapter/src/main/java/com/itextpdf/bouncycastlefips/asn1/ASN1EncodableBCFips.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ This file is part of the iText (R) project.
2424

2525
import com.itextpdf.commons.bouncycastle.asn1.IASN1Encodable;
2626
import com.itextpdf.commons.bouncycastle.asn1.IASN1Primitive;
27+
import org.bouncycastle.asn1.ASN1Encodable;
2728

2829
import java.util.Objects;
29-
import org.bouncycastle.asn1.ASN1Encodable;
3030

3131
/**
3232
* Wrapper class for {@link ASN1Encodable}.
@@ -76,9 +76,10 @@ public boolean equals(Object o) {
7676
if (this == o) {
7777
return true;
7878
}
79-
if (o == null || getClass() != o.getClass()) {
79+
if (o == null || !this.getClass().isAssignableFrom(o.getClass())) {
8080
return false;
8181
}
82+
8283
ASN1EncodableBCFips that = (ASN1EncodableBCFips) o;
8384
return Objects.equals(encodable, that.encodable);
8485
}

bouncy-castle-fips-adapter/src/main/java/com/itextpdf/bouncycastlefips/cert/X509CertificateHolderBCFips.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,13 @@ This file is part of the iText (R) project.
2222
*/
2323
package com.itextpdf.bouncycastlefips.cert;
2424

25+
import com.itextpdf.bouncycastlefips.asn1.x509.AlgorithmIdentifierBCFips;
26+
import com.itextpdf.commons.bouncycastle.asn1.x509.IAlgorithmIdentifier;
2527
import com.itextpdf.commons.bouncycastle.cert.IX509CertificateHolder;
2628

2729
import java.io.IOException;
2830
import java.util.Objects;
31+
2932
import org.bouncycastle.cert.X509CertificateHolder;
3033

3134
/**
@@ -62,6 +65,16 @@ public X509CertificateHolder getCertificateHolder() {
6265
return certificateHolder;
6366
}
6467

68+
/**
69+
* {@inheritDoc}
70+
*
71+
* @return {@inheritDoc}
72+
*/
73+
@Override
74+
public IAlgorithmIdentifier getSignatureAlgorithm() {
75+
return new AlgorithmIdentifierBCFips(certificateHolder.getSignatureAlgorithm());
76+
}
77+
6578
/**
6679
* Indicates whether some other object is "equal to" this one. Compares wrapped objects.
6780
*/

commons/src/main/java/com/itextpdf/commons/bouncycastle/cert/IX509CertificateHolder.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,18 @@ This file is part of the iText (R) project.
2222
*/
2323
package com.itextpdf.commons.bouncycastle.cert;
2424

25+
import com.itextpdf.commons.bouncycastle.asn1.x509.IAlgorithmIdentifier;
26+
2527
/**
2628
* This interface represents the wrapper for X509CertificateHolder that provides the ability
2729
* to switch between bouncy-castle and bouncy-castle FIPS implementations.
2830
*/
2931
public interface IX509CertificateHolder {
32+
33+
/**
34+
* Retrieves signature algorithm identifier from the certificate.
35+
*
36+
* @return signature algorithm.
37+
*/
38+
IAlgorithmIdentifier getSignatureAlgorithm();
3039
}

sharpenConfiguration.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,7 @@
453453
<fileset reason="Java.Security does not exist in .net">
454454
<file path="com/itextpdf/signatures/RSASSAPSSMechanismParams.java"/>
455455
<file path="com/itextpdf/signatures/sign/RSASSAPSSTest.java"/>
456+
<file path="com/itextpdf/signatures/sign/TwoPhaseSigningTest.java"/>
456457
</fileset>
457458
<fileset reason="Date and DateTime UTC time constructors difference in Java and .NET">
458459
<file path="com/itextpdf/signatures/testutils/TimeTestUtil.java"/>

sign/src/main/java/com/itextpdf/signatures/CertificateUtil.java

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,12 @@ This file is part of the iText (R) project.
4040

4141
import java.io.ByteArrayInputStream;
4242
import java.io.IOException;
43+
import java.io.InputStream;
4344
import java.net.URL;
4445
import java.nio.charset.StandardCharsets;
4546
import java.security.cert.CRL;
4647
import java.security.cert.CRLException;
48+
import java.security.cert.Certificate;
4749
import java.security.cert.CertificateException;
4850
import java.security.cert.X509Certificate;
4951

@@ -209,11 +211,24 @@ public static String getTSAURL(X509Certificate certificate) {
209211
}
210212
}
211213

214+
/**
215+
* Generates a certificate object and initializes it with the data read from the input stream inStream.
216+
*
217+
* @param data the input stream with the certificates.
218+
*
219+
* @return a certificate object initialized with the data from the input stream.
220+
*
221+
* @throws CertificateException on parsing errors.
222+
*/
223+
public static Certificate generateCertificate(InputStream data) throws CertificateException {
224+
return SignUtils.generateCertificate(data, FACTORY.getProvider());
225+
}
226+
212227
/**
213228
* Checks if the certificate is signed by provided issuer certificate.
214229
*
215230
* @param subjectCertificate a certificate to check
216-
* @param issuerCertificate an issuer certificate to check
231+
* @param issuerCertificate an issuer certificate to check
217232
*
218233
* @return true if the first passed certificate is signed by next passed certificate.
219234
*/
@@ -240,9 +255,9 @@ static boolean isSelfSigned(X509Certificate certificate) {
240255
*
241256
* @return the extension value as an {@link IASN1Primitive} object.
242257
*
243-
* @throws IOException
258+
* @throws IOException on processing exception
244259
*/
245-
private static IASN1Primitive getExtensionValue(X509Certificate certificate, String oid) throws IOException {
260+
public static IASN1Primitive getExtensionValue(X509Certificate certificate, String oid) throws IOException {
246261
return getExtensionValueFromByteArray(SignUtils.getExtensionValueByOid(certificate, oid));
247262
}
248263

@@ -252,7 +267,7 @@ private static IASN1Primitive getExtensionValue(X509Certificate certificate, Str
252267
*
253268
* @return the extension value as an {@link IASN1Primitive} object.
254269
*
255-
* @throws IOException
270+
* @throws IOException on processing exception
256271
*/
257272
private static IASN1Primitive getExtensionValue(CRL crl, String oid) throws IOException {
258273
return getExtensionValueFromByteArray(SignUtils.getExtensionValueByOid(crl, oid));
@@ -265,7 +280,7 @@ private static IASN1Primitive getExtensionValue(CRL crl, String oid) throws IOEx
265280
*
266281
* @return the extension value as an {@link IASN1Primitive} object.
267282
*
268-
* @throws IOException
283+
* @throws IOException on processing exception.
269284
*/
270285
private static IASN1Primitive getExtensionValueFromByteArray(byte[] extensionValue) throws IOException {
271286
if (extensionValue == null) {
@@ -296,7 +311,7 @@ private static String getStringFromGeneralName(IASN1Primitive names) {
296311
* Retrieves accessLocation value for specified accessMethod from the Authority Information Access extension.
297312
*
298313
* @param extensionValue Authority Information Access extension value
299-
* @param accessMethod accessMethod OID; usually id-ad-caIssuers or id-ad-ocsp
314+
* @param accessMethod accessMethod OID; usually id-ad-caIssuers or id-ad-ocsp
300315
*
301316
* @return the location (URI) of the information.
302317
*/

sign/src/main/java/com/itextpdf/signatures/PdfSigner.java

Lines changed: 76 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ This file is part of the iText (R) project.
6060
import com.itextpdf.kernel.pdf.annot.PdfAnnotation;
6161
import com.itextpdf.kernel.pdf.annot.PdfWidgetAnnotation;
6262
import com.itextpdf.pdfa.PdfAAgnosticPdfDocument;
63+
import com.itextpdf.signatures.cms.CMSContainer;
6364
import com.itextpdf.signatures.exceptions.SignExceptionMessageConstant;
6465

6566
import java.io.ByteArrayOutputStream;
@@ -253,7 +254,7 @@ public PdfSigner(PdfReader reader, OutputStream outputStream, String path, Stamp
253254
appearance = new PdfSignatureAppearance(document, new Rectangle(0, 0), 1);
254255
appearance.setSignDate(signDate);
255256
}
256-
257+
257258
PdfSigner(PdfDocument document, OutputStream outputStream, ByteArrayOutputStream temporaryOS, File tempFile) {
258259
if (tempFile == null) {
259260
this.temporaryOS = temporaryOS;
@@ -946,31 +947,31 @@ public void timestamp(ITSAClient tsa, String signatureName) throws IOException,
946947
*/
947948
public byte[] prepareDocumentForSignature(String digestAlgorithm, PdfName filter, PdfName subFilter,
948949
int estimatedSize, boolean includeDate) throws IOException, GeneralSecurityException {
949-
if (closed) {
950-
throw new PdfException(SignExceptionMessageConstant.THIS_INSTANCE_OF_PDF_SIGNER_ALREADY_CLOSED);
951-
}
952-
953-
cryptoDictionary = createSignatureDictionary(includeDate);
954-
cryptoDictionary.put(PdfName.Filter, filter);
955-
cryptoDictionary.put(PdfName.SubFilter, subFilter);
956-
957-
958-
Map<PdfName, Integer> exc = new HashMap<>();
959-
exc.put(PdfName.Contents, estimatedSize * 2 + 2);
960-
preClose(exc);
961-
962-
InputStream data = getRangeStream();
963-
byte[] digest = DigestAlgorithms.digest(data, SignUtils.getMessageDigest(digestAlgorithm));
964-
965-
byte[] paddedSig = new byte[estimatedSize];
966-
967-
PdfDictionary dic2 = new PdfDictionary();
968-
dic2.put(PdfName.Contents, new PdfString(paddedSig).setHexWriting(true));
969-
close(dic2);
970-
971-
closed = true;
950+
return prepareDocumentForSignature(SignUtils.getMessageDigest(digestAlgorithm), filter, subFilter,
951+
estimatedSize, includeDate);
952+
}
972953

973-
return digest;
954+
/**
955+
* Prepares document for signing, calculates the document digest to sign and closes the document.
956+
*
957+
* @param externalDigest an external digest to provide the MessageDigest
958+
* @param digestAlgorithm the algorithm to generate the digest with
959+
* @param filter PdfName of the signature handler to use when validating this signature
960+
* @param subFilter PdfName that describes the encoding of the signature
961+
* @param estimatedSize the estimated size of the signature, this is the size of the space reserved for
962+
* the Cryptographic Message Container
963+
* @param includeDate specifies if the signing date should be set to the signature dictionary
964+
*
965+
* @return the message digest of the prepared document.
966+
*
967+
* @throws IOException if some I/O problem occurs.
968+
* @throws GeneralSecurityException if some problem during apply security algorithms occurs.
969+
*/
970+
public byte[] prepareDocumentForSignature(IExternalDigest externalDigest, String digestAlgorithm, PdfName filter,
971+
PdfName subFilter, int estimatedSize, boolean includeDate)
972+
throws IOException, GeneralSecurityException {
973+
return prepareDocumentForSignature(externalDigest.getMessageDigest(digestAlgorithm), filter, subFilter,
974+
estimatedSize, includeDate);
974975
}
975976

976977
/**
@@ -985,11 +986,30 @@ public byte[] prepareDocumentForSignature(String digestAlgorithm, PdfName filter
985986
* @throws GeneralSecurityException if some problem during apply security algorithms occurs.
986987
*/
987988
public static void addSignatureToPreparedDocument(PdfDocument document, String fieldName, OutputStream outs,
988-
byte[] signedContent) throws IOException, GeneralSecurityException {
989+
byte[] signedContent)
990+
throws IOException, GeneralSecurityException {
989991
SignatureApplier applier = new SignatureApplier(document, fieldName, outs);
990992
applier.apply(a -> signedContent);
991993
}
992994

995+
/**
996+
* Adds an existing signature to a PDF where space was already reserved.
997+
*
998+
* @param document the original PDF
999+
* @param fieldName the field to sign. It must be the last field
1000+
* @param outs the output PDF
1001+
* @param cmsContainer the finalized CMS container
1002+
*
1003+
* @throws IOException if some I/O problem occurs.
1004+
* @throws GeneralSecurityException if some problem during apply security algorithms occurs.
1005+
*/
1006+
public static void addSignatureToPreparedDocument(PdfDocument document, String fieldName, OutputStream outs,
1007+
CMSContainer cmsContainer)
1008+
throws IOException, GeneralSecurityException {
1009+
SignatureApplier applier = new SignatureApplier(document, fieldName, outs);
1010+
applier.apply(a -> cmsContainer.serialize());
1011+
}
1012+
9931013
/**
9941014
* Signs a PDF where space was already reserved.
9951015
*
@@ -1057,7 +1077,8 @@ protected boolean isPreClosed() {
10571077
* document. Note that due to the hex string coding this size should be byte_size*2+2.
10581078
*
10591079
* @param exclusionSizes Map with names and sizes to be excluded in the signature
1060-
* calculation. The key is a PdfName and the value an Integer. At least the /Contents must be present
1080+
* calculation. The key is a PdfName and the value an Integer.
1081+
* At least the /Contents must be present
10611082
* @throws IOException on error
10621083
*/
10631084
protected void preClose(Map<PdfName, Integer> exclusionSizes) throws IOException {
@@ -1470,6 +1491,34 @@ protected int getWidgetPageNumber(PdfWidgetAnnotation widget) {
14701491
return pageNumber;
14711492
}
14721493

1494+
private byte[] prepareDocumentForSignature(MessageDigest messageDigest, PdfName filter,
1495+
PdfName subFilter, int estimatedSize, boolean includeDate)
1496+
throws IOException {
1497+
if (closed) {
1498+
throw new PdfException(SignExceptionMessageConstant.THIS_INSTANCE_OF_PDF_SIGNER_ALREADY_CLOSED);
1499+
}
1500+
1501+
cryptoDictionary = createSignatureDictionary(includeDate);
1502+
cryptoDictionary.put(PdfName.Filter, filter);
1503+
cryptoDictionary.put(PdfName.SubFilter, subFilter);
1504+
1505+
1506+
Map<PdfName, Integer> exc = new HashMap<>();
1507+
exc.put(PdfName.Contents, estimatedSize * 2 + 2);
1508+
preClose(exc);
1509+
1510+
InputStream data = getRangeStream();
1511+
byte[] digest = DigestAlgorithms.digest(data, messageDigest);
1512+
byte[] paddedSig = new byte[estimatedSize];
1513+
1514+
PdfDictionary dic2 = new PdfDictionary();
1515+
dic2.put(PdfName.Contents, new PdfString(paddedSig).setHexWriting(true));
1516+
close(dic2);
1517+
1518+
closed = true;
1519+
return digest;
1520+
}
1521+
14731522
private boolean isDocumentPdf2() {
14741523
return document.getPdfVersion().compareTo(PdfVersion.PDF_2_0) >= 0;
14751524
}

sign/src/main/java/com/itextpdf/signatures/SecurityIDs.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,10 @@ public class SecurityIDs {
3535
public static final String ID_RSA_WITH_SHA3_512 = "2.16.840.1.101.3.4.3.16";
3636
public static final String ID_DSA = "1.2.840.10040.4.1";
3737
public static final String ID_ECDSA = "1.2.840.10045.2.1";
38-
3938
public static final String ID_ED25519 = "1.3.101.112";
4039
public static final String ID_ED448 = "1.3.101.113";
4140
public static final String ID_SHA256 = "2.16.840.1.101.3.4.2.1";
41+
public static final String ID_SHA384 = "2.16.840.1.101.3.4.2.2";
4242
public static final String ID_SHA512 = "2.16.840.1.101.3.4.2.3";
4343
public static final String ID_SHAKE256 = "2.16.840.1.101.3.4.2.12";
4444
public static final String ID_CONTENT_TYPE = "1.2.840.113549.1.9.3";

0 commit comments

Comments
 (0)