Skip to content

Commit 9fe2316

Browse files
author
Eugene Bochilo
committed
Support MAC integrity protection for different signing modes
DEVSIX-8627
1 parent 5d52188 commit 9fe2316

File tree

43 files changed

+452
-121
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+452
-121
lines changed

bouncy-castle-connector/src/main/java/com/itextpdf/bouncycastleconnector/BouncyCastleFactoryCreator.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public final class BouncyCastleFactoryCreator {
4343

4444
private static IBouncyCastleFactory factory;
4545

46-
private static Map<String, Supplier<IBouncyCastleFactory>> factories = new LinkedHashMap<>();
46+
private static final Map<String, Supplier<IBouncyCastleFactory>> FACTORIES = new LinkedHashMap<>();
4747

4848
private static final String FACTORY_ENVIRONMENT_VARIABLE_NAME = "ITEXT_BOUNCY_CASTLE_FACTORY_NAME";
4949

@@ -53,12 +53,12 @@ public final class BouncyCastleFactoryCreator {
5353
populateFactoriesMap();
5454

5555
String factoryName = SystemUtil.getPropertyOrEnvironmentVariable(FACTORY_ENVIRONMENT_VARIABLE_NAME);
56-
Supplier<IBouncyCastleFactory> systemVariableFactoryCreator = factories.get(factoryName);
56+
Supplier<IBouncyCastleFactory> systemVariableFactoryCreator = FACTORIES.get(factoryName);
5757
if (systemVariableFactoryCreator != null) {
5858
tryCreateFactory(systemVariableFactoryCreator);
5959
}
6060

61-
for (Supplier<IBouncyCastleFactory> factorySupplier : factories.values()) {
61+
for (Supplier<IBouncyCastleFactory> factorySupplier : FACTORIES.values()) {
6262
if (factory != null) {
6363
break;
6464
}
@@ -106,7 +106,7 @@ private static void createFactory(Supplier<IBouncyCastleFactory> factoryCreator)
106106
}
107107

108108
private static void populateFactoriesMap() {
109-
factories.put("bouncy-castle", () -> new BouncyCastleFactory());
110-
factories.put("bouncy-castle-fips", () -> new BouncyCastleFipsFactory()); // Android-Conversion-Skip-Line (BC FIPS isn't supported on Android)
109+
FACTORIES.put("bouncy-castle", () -> new BouncyCastleFactory());
110+
FACTORIES.put("bouncy-castle-fips", () -> new BouncyCastleFipsFactory()); // Android-Conversion-Skip-Line (BC FIPS isn't supported on Android)
111111
}
112112
}

kernel/src/main/java/com/itextpdf/kernel/mac/IMacContainerLocator.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,13 @@ public interface IMacContainerLocator {
3737
*/
3838
void locateMacContainer(AbstractMacIntegrityProtector macIntegrityProtector);
3939

40+
/**
41+
* Indicates, if MAC container was already located.
42+
*
43+
* @return {@code true} if MAC container was already located, {@code false} otherwise
44+
*/
45+
boolean isMacContainerLocated();
46+
4047
/**
4148
* Creates {@link AbstractMacIntegrityProtector} from explicitly provided MAC properties.
4249
*

kernel/src/main/java/com/itextpdf/kernel/mac/StandaloneMacContainerLocator.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,23 @@ This file is part of the iText (R) project.
2929
* Default {@link AbstractMacIntegrityProtector} location strategy, which locates MAC container in document's trailer.
3030
*/
3131
public class StandaloneMacContainerLocator implements IMacContainerLocator {
32+
private boolean macContainerLocated = false;
33+
3234
/**
3335
* {@inheritDoc}.
3436
*/
3537
@Override
3638
public void locateMacContainer(AbstractMacIntegrityProtector macIntegrityProtector) {
3739
((StandaloneMacIntegrityProtector) macIntegrityProtector).prepareDocument();
40+
macContainerLocated = true;
41+
}
42+
43+
/**
44+
* {@inheritDoc}.
45+
*/
46+
@Override
47+
public boolean isMacContainerLocated() {
48+
return macContainerLocated;
3849
}
3950

4051
/**

kernel/src/main/java/com/itextpdf/kernel/pdf/PdfWriter.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -200,10 +200,12 @@ protected void initCryptoIfSpecified(PdfVersion version) {
200200
final int encryptionAlgorithm = crypto == null ?
201201
(encryptProps.encryptionAlgorithm & EncryptionConstants.ENCRYPTION_MASK) :
202202
crypto.getEncryptionAlgorithm();
203+
if (document.properties.disableMac) {
204+
encryptProps.macProperties = null;
205+
}
203206
if (encryptProps.macProperties == EncryptionProperties.DEFAULT_MAC_PROPERTIES) {
204-
if ((version == null || version.compareTo(PdfVersion.PDF_2_0) < 0) ||
205-
(encryptionAlgorithm != EncryptionConstants.ENCRYPTION_AES_256 &&
206-
encryptionAlgorithm != EncryptionConstants.ENCRYPTION_AES_GCM)) {
207+
if (version == null || version.compareTo(PdfVersion.PDF_2_0) < 0 ||
208+
encryptionAlgorithm < EncryptionConstants.ENCRYPTION_AES_256) {
207209
encryptProps.macProperties = null;
208210
}
209211
}

kernel/src/main/java/com/itextpdf/kernel/pdf/StampingProperties.java

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

2525

2626
public class StampingProperties extends DocumentProperties {
27-
28-
2927
protected boolean appendMode = false;
3028
protected boolean preserveEncryption = false;
3129
protected boolean disableMac = false;
@@ -37,6 +35,7 @@ public StampingProperties(StampingProperties other) {
3735
super(other);
3836
this.appendMode = other.appendMode;
3937
this.preserveEncryption = other.preserveEncryption;
38+
this.disableMac = other.disableMac;
4039
}
4140

4241
StampingProperties(DocumentProperties documentProperties) {

kernel/src/test/java/com/itextpdf/kernel/mac/MacIntegrityProtectorCreationTest.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,27 @@ public void addMacOnAppendModeTest() throws IOException, InterruptedException {
243243
outputFileName, cmpFileName, DESTINATION_FOLDER, "diff", PASSWORD, PASSWORD));
244244
}
245245

246+
@Test
247+
@LogMessages(messages = @LogMessage(messageTemplate = KernelLogMessageConstant.MD5_IS_NOT_FIPS_COMPLIANT,
248+
ignore = true))
249+
public void addMacWithDisableMacPropertyTest() throws IOException, InterruptedException {
250+
// MAC should not be added in disable MAC mode even if it was provided with writer properties
251+
String fileName = "addMacWithDisableMacPropertyTest.pdf";
252+
String outputFileName = DESTINATION_FOLDER + fileName;
253+
String cmpFileName = SOURCE_FOLDER + "cmp_" + fileName;
254+
255+
MacProperties macProperties = new MacProperties(MacDigestAlgorithm.SHA_384);
256+
WriterProperties writerProperties = new WriterProperties().setPdfVersion(PdfVersion.PDF_2_0)
257+
.setStandardEncryption(PASSWORD, PASSWORD, 0, EncryptionConstants.ENCRYPTION_AES_256, macProperties);
258+
try (PdfDocument pdfDoc = new PdfDocument(
259+
new PdfReader(SOURCE_FOLDER + "noMacProtectionDocument.pdf", new ReaderProperties().setPassword(PASSWORD)),
260+
new PdfWriter(outputFileName, writerProperties), new StampingProperties().disableMac())) {
261+
pdfDoc.addNewPage().addAnnotation(new PdfTextAnnotation(new Rectangle(100, 100, 100, 100)));
262+
}
263+
Assertions.assertNull(new CompareTool().enableEncryptionCompare().compareByContent(
264+
outputFileName, cmpFileName, DESTINATION_FOLDER, "diff", PASSWORD, PASSWORD));
265+
}
266+
246267
@Test
247268
@LogMessages(messages = @LogMessage(messageTemplate = KernelLogMessageConstant.MD5_IS_NOT_FIPS_COMPLIANT,
248269
ignore = true))

sharpenConfiguration.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -657,6 +657,14 @@
657657
<file path="com/itextpdf/signatures/sign/TimestampSigTest/cmp_timestampTest01.pdf"/>
658658
<file path="com/itextpdf/signatures/PKCS7ExternalSignatureContainerTest/cmp_testTroughPdfSignerWithTsaClient.pdf"/>
659659
<file path="com/itextpdf/signatures/PKCS7ExternalSignatureContainerTest/cmp_testTroughPdfSignerWithTsaClient_FIPS.pdf"/>
660+
661+
<file path="com/itextpdf/signatures/mac/SignedDocumentWithMacTest/cmp_signMacProtectedDocInAppendModeTest_timestamping.pdf"/>
662+
<file path="com/itextpdf/signatures/mac/SignedDocumentWithMacTest/cmp_signMacProtectedDocTest_timestamping.pdf"/>
663+
<file path="com/itextpdf/signatures/mac/SignedDocumentWithMacTest/cmp_signMacProtectedDocWithSHA3_384Test_timestamping.pdf"/>
664+
<file path="com/itextpdf/signatures/mac/SignedDocumentWithMacTest/cmp_signMacPublicEncryptionDocTest_timestamping.pdf"/>
665+
<file path="com/itextpdf/signatures/mac/SignedDocumentWithMacTest/cmp_signNotMacProtectedDoc17Test_timestamping.pdf"/>
666+
<file path="com/itextpdf/signatures/mac/SignedDocumentWithMacTest/cmp_signNotMacProtectedDocInAppendModeTest_timestamping.pdf"/>
667+
<file path="com/itextpdf/signatures/mac/SignedDocumentWithMacTest/cmp_signNotMacProtectedDocTest_timestamping.pdf"/>
660668
</fileset>
661669
<fileset reason="Bug in java version, not in sharp version, therefor a separate reference file is needed see DEVSIX-6752">
662670
<file path="com/itextpdf/kernel/pdf/xobject/GetImageBytesTest/grayImages.png" />

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

Lines changed: 59 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ This file is part of the iText (R) project.
2323
package com.itextpdf.signatures;
2424

2525
import com.itextpdf.bouncycastleconnector.BouncyCastleFactoryCreator;
26+
import com.itextpdf.commons.bouncycastle.IBouncyCastleFactory;
27+
import com.itextpdf.commons.bouncycastle.asn1.IASN1EncodableVector;
28+
import com.itextpdf.commons.bouncycastle.asn1.IASN1Sequence;
2629
import com.itextpdf.commons.bouncycastle.asn1.esf.ISignaturePolicyIdentifier;
2730
import com.itextpdf.commons.utils.FileUtil;
2831
import com.itextpdf.commons.utils.MessageFormatUtil;
@@ -46,6 +49,8 @@ This file is part of the iText (R) project.
4649
import com.itextpdf.kernel.font.PdfFont;
4750
import com.itextpdf.kernel.geom.Rectangle;
4851
import com.itextpdf.kernel.mac.IMacContainerLocator;
52+
import com.itextpdf.signatures.cms.CMSContainer;
53+
import com.itextpdf.signatures.cms.CmsAttribute;
4954
import com.itextpdf.signatures.mac.SignatureContainerGenerationEvent;
5055
import com.itextpdf.kernel.pdf.PdfArray;
5156
import com.itextpdf.kernel.pdf.PdfDate;
@@ -104,7 +109,9 @@ This file is part of the iText (R) project.
104109
* Takes care of the cryptographic options and appearances that form a signature.
105110
*/
106111
public class PdfSigner {
107-
private static final int MAXIMUM_MAC_SIZE = 788;
112+
static final int MAXIMUM_MAC_SIZE = 788;
113+
private static final IBouncyCastleFactory FACTORY = BouncyCastleFactoryCreator.getFactory();
114+
private static final String ID_ATTR_PDF_MAC_DATA = "1.0.32004.1.2";
108115

109116
/**
110117
* Enum containing the Cryptographic Standards. Possible values are "CMS" and "CADES".
@@ -598,8 +605,8 @@ public void signDetached(IExternalDigest externalDigest, IExternalSignature exte
598605
if (tsaClient != null) {
599606
estimatedSize += tsaClient.getTokenSizeEstimate() + 96;
600607
}
601-
if (document.getTrailer().getAsDictionary(PdfName.AuthCode) != null) {
602-
// if AuthCode is found in trailer, we assume MAC will be embedded and allocate additional space.
608+
if (document.getDiContainer().getInstance(IMacContainerLocator.class).isMacContainerLocated()) {
609+
// If MAC container was located, we presume MAC will be embedded and allocate additional space.
603610
estimatedSize += MAXIMUM_MAC_SIZE;
604611
}
605612
}
@@ -700,12 +707,18 @@ public void signExternalContainer(IExternalSignatureContainer externalSignatureC
700707
externalSignatureContainer.modifySigningDictionary(dic.getPdfObject());
701708
cryptoDictionary = dic;
702709

710+
if (document.getDiContainer().getInstance(IMacContainerLocator.class).isMacContainerLocated()) {
711+
// If MAC container was located, we presume MAC will be embedded and allocate additional space.
712+
estimatedSize += MAXIMUM_MAC_SIZE;
713+
}
714+
703715
Map<PdfName, Integer> exc = new HashMap<>();
704716
exc.put(PdfName.Contents, estimatedSize * 2 + 2);
705717
preClose(exc);
706718

707719
InputStream data = getRangeStream();
708720
byte[] encodedSig = externalSignatureContainer.sign(data);
721+
encodedSig = embedMacTokenIntoSignatureContainer(encodedSig);
709722

710723
if (estimatedSize < encodedSig.length) {
711724
throw new IOException(SignExceptionMessageConstant.NOT_ENOUGH_SPACE);
@@ -744,6 +757,10 @@ public void timestamp(ITSAClient tsa, String signatureName) throws IOException,
744757
}
745758

746759
int contentEstimated = tsa.getTokenSizeEstimate();
760+
if (document.getDiContainer().getInstance(IMacContainerLocator.class).isMacContainerLocated()) {
761+
// If MAC container was located, we presume MAC will be embedded and allocate additional space.
762+
contentEstimated += MAXIMUM_MAC_SIZE;
763+
}
747764
if (!isDocumentPdf2()) {
748765
addDeveloperExtension(PdfDeveloperExtension.ESIC_1_7_EXTENSIONLEVEL5);
749766
}
@@ -771,6 +788,8 @@ public void timestamp(ITSAClient tsa, String signatureName) throws IOException,
771788
throw new GeneralSecurityException(e.getMessage(), e);
772789
}
773790

791+
tsToken = embedMacTokenIntoSignatureContainer(tsToken);
792+
774793
if (contentEstimated + 2 < tsToken.length) {
775794
throw new IOException(MessageFormatUtil.format(
776795
SignExceptionMessageConstant.TOKEN_ESTIMATION_SIZE_IS_NOT_LARGE_ENOUGH,
@@ -1308,6 +1327,19 @@ protected int getWidgetPageNumber(PdfWidgetAnnotation widget) {
13081327
return pageNumber;
13091328
}
13101329

1330+
PdfSignature createSignatureDictionary(boolean includeDate) {
1331+
PdfSignature dic = new PdfSignature();
1332+
dic.setReason(this.signerProperties.getReason());
1333+
dic.setLocation(this.signerProperties.getLocation());
1334+
dic.setSignatureCreator(this.signerProperties.getSignatureCreator());
1335+
dic.setContact(this.signerProperties.getContact());
1336+
Calendar claimedSignDate = this.signerProperties.getClaimedSignDate();
1337+
if (includeDate && claimedSignDate != TimestampConstants.UNDEFINED_TIMESTAMP_DATE) {
1338+
dic.setDate(new PdfDate(claimedSignDate)); // time-stamp will over-rule this
1339+
}
1340+
return dic;
1341+
}
1342+
13111343
private static String getSignerName(X509Certificate certificate) {
13121344
String name = null;
13131345
CertificateInfo.X500Name x500name = CertificateInfo.getSubjectFields(certificate);
@@ -1355,20 +1387,6 @@ private boolean isDocumentPdf2() {
13551387
return document.getPdfVersion().compareTo(PdfVersion.PDF_2_0) >= 0;
13561388
}
13571389

1358-
PdfSignature createSignatureDictionary(boolean includeDate) {
1359-
PdfSignature dic = new PdfSignature();
1360-
dic.setReason(this.signerProperties.getReason());
1361-
dic.setLocation(this.signerProperties.getLocation());
1362-
dic.setSignatureCreator(this.signerProperties.getSignatureCreator());
1363-
dic.setContact(this.signerProperties.getContact());
1364-
Calendar claimedSignDate = this.signerProperties.getClaimedSignDate();
1365-
if (includeDate && claimedSignDate != TimestampConstants.UNDEFINED_TIMESTAMP_DATE) {
1366-
dic.setDate(new PdfDate(claimedSignDate)); // time-stamp will over-rule this
1367-
}
1368-
return dic;
1369-
}
1370-
1371-
13721390
protected void applyAccessibilityProperties(PdfFormField formField, IAccessibleElement modelElement,
13731391
PdfDocument pdfDocument) {
13741392
if (!pdfDocument.isTagged()) {
@@ -1381,6 +1399,30 @@ protected void applyAccessibilityProperties(PdfFormField formField, IAccessibleE
13811399
}
13821400
}
13831401

1402+
private byte[] embedMacTokenIntoSignatureContainer(byte[] signatureContainer) {
1403+
if (document.getDiContainer().getInstance(IMacContainerLocator.class).isMacContainerLocated()) {
1404+
try {
1405+
CMSContainer cmsContainer = new CMSContainer(signatureContainer);
1406+
// If MAC is in the signature already, we regenerate it anyway.
1407+
cmsContainer.getSignerInfo().removeUnSignedAttribute(ID_ATTR_PDF_MAC_DATA);
1408+
IASN1EncodableVector unsignedVector = FACTORY.createASN1EncodableVector();
1409+
document.dispatchEvent(new SignatureContainerGenerationEvent(unsignedVector,
1410+
cmsContainer.getSignerInfo().getSignatureData(), getRangeStream()));
1411+
if (FACTORY.createDERSequence(unsignedVector).size() != 0) {
1412+
IASN1Sequence sequence =
1413+
FACTORY.createASN1Sequence(FACTORY.createDERSequence(unsignedVector).getObjectAt(0));
1414+
cmsContainer.getSignerInfo().addUnSignedAttribute(new CmsAttribute(
1415+
FACTORY.createASN1ObjectIdentifier(sequence.getObjectAt(0)).getId(),
1416+
sequence.getObjectAt(1).toASN1Primitive()));
1417+
return cmsContainer.serialize();
1418+
}
1419+
} catch (Exception exception) {
1420+
throw new PdfException(SignExceptionMessageConstant.NOT_POSSIBLE_TO_EMBED_MAC_TO_SIGNATURE, exception);
1421+
}
1422+
}
1423+
return signatureContainer;
1424+
}
1425+
13841426
private void applyDefaultPropertiesForTheNewField(PdfSignatureFormField sigField) {
13851427
SignatureFieldAppearance formFieldElement = getSignatureAppearance();
13861428
PdfFormAnnotation annotation = sigField.getFirstFormAnnotation();

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

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

2525
import com.itextpdf.kernel.crypto.DigestAlgorithms;
2626
import com.itextpdf.kernel.exceptions.PdfException;
27+
import com.itextpdf.kernel.mac.IMacContainerLocator;
2728
import com.itextpdf.kernel.pdf.PdfDeveloperExtension;
2829
import com.itextpdf.kernel.pdf.PdfDictionary;
2930
import com.itextpdf.kernel.pdf.PdfDocument;
@@ -166,15 +167,17 @@ PdfSigner createPdfSigner(SignerProperties signerProperties) throws IOException
166167

167168
private byte[] prepareDocumentForSignature(SignerProperties signerProperties, MessageDigest messageDigest,
168169
PdfName filter, PdfName subFilter, int estimatedSize,
169-
boolean includeDate)
170-
throws IOException {
170+
boolean includeDate) throws IOException {
171171
if (closed) {
172172
throw new PdfException(SignExceptionMessageConstant.THIS_INSTANCE_OF_PDF_SIGNER_ALREADY_CLOSED);
173173
}
174174
PdfSigner pdfSigner = createPdfSigner(signerProperties);
175175

176176

177177
PdfDocument document = pdfSigner.getDocument();
178+
if (document.getDiContainer().getInstance(IMacContainerLocator.class).isMacContainerLocated()) {
179+
throw new PdfException(SignExceptionMessageConstant.NOT_POSSIBLE_TO_EMBED_MAC_TO_SIGNATURE);
180+
}
178181
if (document.getPdfVersion().compareTo(PdfVersion.PDF_2_0) < 0) {
179182
document.getCatalog().addDeveloperExtension(PdfDeveloperExtension.ESIC_1_7_EXTENSIONLEVEL2);
180183
}

0 commit comments

Comments
 (0)