MacIntegrityProtectorReadingAndRewritingTest.java
/*
This file is part of the iText (R) project.
Copyright (c) 1998-2025 Apryse Group NV
Authors: Apryse Software.
This program is offered under a commercial and under the AGPL license.
For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below.
AGPL licensing:
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itextpdf.kernel.mac;
import com.itextpdf.bouncycastleconnector.BouncyCastleFactoryCreator;
import com.itextpdf.commons.utils.FileUtil;
import com.itextpdf.kernel.crypto.CryptoUtil;
import com.itextpdf.kernel.exceptions.KernelExceptionMessageConstant;
import com.itextpdf.kernel.exceptions.PdfException;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.logs.KernelLogMessageConstant;
import com.itextpdf.kernel.mac.MacProperties.MacDigestAlgorithm;
import com.itextpdf.kernel.pdf.EncryptionConstants;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfReader;
import com.itextpdf.kernel.pdf.PdfVersion;
import com.itextpdf.kernel.pdf.ReaderProperties;
import com.itextpdf.kernel.pdf.StampingProperties;
import com.itextpdf.kernel.pdf.WriterProperties;
import com.itextpdf.kernel.pdf.annot.PdfTextAnnotation;
import com.itextpdf.kernel.utils.CompareTool;
import com.itextpdf.test.AssertUtil;
import com.itextpdf.test.ExtendedITextTest;
import com.itextpdf.test.TestUtil;
import com.itextpdf.test.annotations.LogMessage;
import com.itextpdf.test.annotations.LogMessages;
import java.io.IOException;
import java.security.PrivateKey;
import java.security.Security;
import java.security.cert.Certificate;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
@Tag("BouncyCastleIntegrationTest")
public class MacIntegrityProtectorReadingAndRewritingTest extends ExtendedITextTest {
private static final String SOURCE_FOLDER = "./src/test/resources/com/itextpdf/kernel/mac/MacIntegrityProtectorReadingAndRewritingTest/";
private static final String DESTINATION_FOLDER = TestUtil.getOutputPath() + "/kernel/mac/MacIntegrityProtectorReadingAndRewritingTest/";
private static final String CERTS_SRC = "./src/test/resources/com/itextpdf/kernel/mac/MacIntegrityProtectorReadingAndRewritingTest/certs/";
private static final byte[] PASSWORD = "123".getBytes();
private static final String PROVIDER_NAME = BouncyCastleFactoryCreator.getFactory().getProviderName();
@BeforeAll
public static void beforeClass() {
createOrClearDestinationFolder(DESTINATION_FOLDER);
Security.addProvider(BouncyCastleFactoryCreator.getFactory().getProvider());
}
@AfterAll
public static void afterClass() {
CompareTool.cleanup(DESTINATION_FOLDER);
}
@Test
@LogMessages(messages = @LogMessage(messageTemplate = KernelLogMessageConstant.MD5_IS_NOT_FIPS_COMPLIANT,
ignore = true))
public void appendModeTest() throws IOException, InterruptedException {
String fileName = "appendModeTest.pdf";
String outputFileName = DESTINATION_FOLDER + fileName;
String cmpFileName = SOURCE_FOLDER + "cmp_" + fileName;
try (PdfDocument pdfDoc = new PdfDocument(new PdfReader(SOURCE_FOLDER + "macProtectedDocument.pdf",
new ReaderProperties().setPassword(PASSWORD)), CompareTool.createTestPdfWriter(outputFileName),
new StampingProperties().useAppendMode())) {
pdfDoc.addNewPage().addAnnotation(new PdfTextAnnotation(new Rectangle(100, 100, 100, 100)));
}
Assertions.assertNull(new CompareTool().enableEncryptionCompare().compareByContent(
outputFileName, cmpFileName, DESTINATION_FOLDER, "diff", PASSWORD, PASSWORD));
}
@Test
@LogMessages(messages = @LogMessage(messageTemplate = KernelLogMessageConstant.MD5_IS_NOT_FIPS_COMPLIANT,
ignore = true))
public void preserveEncryptionTest() throws IOException, InterruptedException {
String fileName = "preserveEncryptionTest.pdf";
String outputFileName = DESTINATION_FOLDER + fileName;
String cmpFileName = SOURCE_FOLDER + "cmp_" + fileName;
try (PdfDocument pdfDoc = new PdfDocument(
new PdfReader(SOURCE_FOLDER + "macProtectedDocument.pdf", new ReaderProperties().setPassword(PASSWORD)),
CompareTool.createTestPdfWriter(outputFileName),
new StampingProperties().preserveEncryption())) {
pdfDoc.addNewPage().addAnnotation(new PdfTextAnnotation(new Rectangle(100, 100, 100, 100)));
}
Assertions.assertNull(new CompareTool().enableEncryptionCompare().compareByContent(
outputFileName, cmpFileName, DESTINATION_FOLDER, "diff", PASSWORD, PASSWORD));
}
@Test
@LogMessages(messages = @LogMessage(messageTemplate = KernelLogMessageConstant.MD5_IS_NOT_FIPS_COMPLIANT,
ignore = true))
public void disableMacTest() throws IOException, InterruptedException {
String fileName = "disableMacTest.pdf";
String outputFileName = DESTINATION_FOLDER + fileName;
String cmpFileName = SOURCE_FOLDER + "cmp_" + fileName;
try (PdfDocument pdfDoc = new PdfDocument(
new PdfReader(SOURCE_FOLDER + "macProtectedDocument.pdf", new ReaderProperties().setPassword(PASSWORD)),
CompareTool.createTestPdfWriter(outputFileName),
new StampingProperties().preserveEncryption().disableMac())) {
pdfDoc.addNewPage().addAnnotation(new PdfTextAnnotation(new Rectangle(100, 100, 100, 100)));
}
Assertions.assertNull(new CompareTool().enableEncryptionCompare().compareByContent(
outputFileName, cmpFileName, DESTINATION_FOLDER, "diff", PASSWORD, PASSWORD));
}
@Test
@LogMessages(messages = @LogMessage(messageTemplate = KernelLogMessageConstant.MD5_IS_NOT_FIPS_COMPLIANT,
ignore = true))
public void disableMacInAppendModeTest() throws IOException, InterruptedException {
// We do not disable MAC in append mode if it was there
String fileName = "disableMacInAppendModeTest.pdf";
String outputFileName = DESTINATION_FOLDER + fileName;
String cmpFileName = SOURCE_FOLDER + "cmp_" + fileName;
try (PdfDocument pdfDoc = new PdfDocument(
new PdfReader(SOURCE_FOLDER + "macProtectedDocument.pdf", new ReaderProperties().setPassword(PASSWORD)),
CompareTool.createTestPdfWriter(outputFileName),
new StampingProperties().useAppendMode().disableMac())) {
pdfDoc.addNewPage().addAnnotation(new PdfTextAnnotation(new Rectangle(100, 100, 100, 100)));
}
Assertions.assertNull(new CompareTool().enableEncryptionCompare().compareByContent(
outputFileName, cmpFileName, DESTINATION_FOLDER, "diff", PASSWORD, PASSWORD));
}
@Test
@LogMessages(messages = @LogMessage(messageTemplate = KernelLogMessageConstant.MD5_IS_NOT_FIPS_COMPLIANT,
ignore = true))
public void writerPropertiesTest() throws IOException, InterruptedException {
String fileName = "writerPropertiesTest.pdf";
String outputFileName = DESTINATION_FOLDER + fileName;
String cmpFileName = SOURCE_FOLDER + "cmp_" + fileName;
MacProperties macProperties = new MacProperties(MacDigestAlgorithm.SHA_512);
WriterProperties writerProperties = new WriterProperties().setPdfVersion(PdfVersion.PDF_2_0)
.setStandardEncryption(PASSWORD, PASSWORD, 0, EncryptionConstants.ENCRYPTION_AES_256, macProperties);
try (PdfDocument pdfDoc = new PdfDocument(
new PdfReader(SOURCE_FOLDER + "macProtectedDocument.pdf", new ReaderProperties().setPassword(PASSWORD)),
CompareTool.createTestPdfWriter(outputFileName, writerProperties))) {
pdfDoc.addNewPage().addAnnotation(new PdfTextAnnotation(new Rectangle(100, 100, 100, 100)));
}
Assertions.assertNull(new CompareTool().compareByContent(
outputFileName, cmpFileName, DESTINATION_FOLDER, "diff", PASSWORD, PASSWORD));
}
@Test
@LogMessages(messages = @LogMessage(messageTemplate = KernelLogMessageConstant.MD5_IS_NOT_FIPS_COMPLIANT,
ignore = true))
public void macShouldNotBePreservedWithEncryptionTest() throws IOException, InterruptedException {
String fileName = "macShouldNotBePreservedWithEncryptionTest.pdf";
String outputFileName = DESTINATION_FOLDER + fileName;
String cmpFileName = SOURCE_FOLDER + "cmp_" + fileName;
WriterProperties writerProperties = new WriterProperties().setPdfVersion(PdfVersion.PDF_1_7)
.setStandardEncryption(PASSWORD, PASSWORD, 0, EncryptionConstants.ENCRYPTION_AES_128);
try (PdfDocument pdfDoc = new PdfDocument(new PdfReader(SOURCE_FOLDER + "macProtectedDocument.pdf",
new ReaderProperties().setPassword(PASSWORD)), CompareTool.createTestPdfWriter(outputFileName, writerProperties))) {
pdfDoc.addNewPage().addAnnotation(new PdfTextAnnotation(new Rectangle(100, 100, 100, 100)));
}
Assertions.assertNull(new CompareTool().enableEncryptionCompare().compareByContent(
outputFileName, cmpFileName, DESTINATION_FOLDER, "diff", PASSWORD, PASSWORD));
}
@Test
@LogMessages(messages = @LogMessage(messageTemplate = KernelLogMessageConstant.MD5_IS_NOT_FIPS_COMPLIANT,
ignore = true))
public void macShouldNotBePreservedTest() throws IOException, InterruptedException {
String fileName = "macShouldNotBePreservedTest.pdf";
String outputFileName = DESTINATION_FOLDER + fileName;
String cmpFileName = SOURCE_FOLDER + "cmp_" + fileName;
try (PdfDocument pdfDoc = new PdfDocument(
new PdfReader(SOURCE_FOLDER + "macProtectedDocument.pdf", new ReaderProperties().setPassword(PASSWORD)),
CompareTool.createTestPdfWriter(outputFileName))) {
pdfDoc.addNewPage().addAnnotation(new PdfTextAnnotation(new Rectangle(100, 100, 100, 100)));
}
Assertions.assertNull(new CompareTool().compareByContent(
outputFileName, cmpFileName, DESTINATION_FOLDER, "diff", PASSWORD, PASSWORD));
}
@Test
@LogMessages(messages = @LogMessage(messageTemplate = KernelLogMessageConstant.MD5_IS_NOT_FIPS_COMPLIANT,
ignore = true))
public void invalidMacTokenTest() {
String fileName = "invalidMacTokenTest.pdf";
String outputFileName = DESTINATION_FOLDER + fileName;
String exceptionMessage = Assertions.assertThrows(PdfException.class, () -> {
try (PdfDocument pdfDoc = new PdfDocument(new PdfReader(SOURCE_FOLDER + "invalidMacProtectedDocument.pdf",
new ReaderProperties().setPassword(PASSWORD)), CompareTool.createTestPdfWriter(outputFileName))) {
pdfDoc.addNewPage().addAnnotation(new PdfTextAnnotation(new Rectangle(100, 100, 100, 100)));
}
}).getMessage();
Assertions.assertEquals(KernelExceptionMessageConstant.MAC_VALIDATION_FAILED, exceptionMessage);
}
@Test
@LogMessages(messages = @LogMessage(messageTemplate = KernelLogMessageConstant.MD5_IS_NOT_FIPS_COMPLIANT,
ignore = true))
public void invalidPublicKeyMacProtectedDocumentTest() throws Exception {
try {
BouncyCastleFactoryCreator.getFactory().isEncryptionFeatureSupported(0, true);
} catch (Exception ignored) {
Assumptions.assumeTrue(false);
}
String fileName = "invalidPublicKeyMacProtectedDocumentTest.pdf";
String outputFileName = DESTINATION_FOLDER + fileName;
Certificate certificate = CryptoUtil.readPublicCertificate(
FileUtil.getInputStreamForFile(CERTS_SRC + "SHA256withRSA.cer"));
PrivateKey privateKey = MacIntegrityProtectorCreationTest.getPrivateKey(CERTS_SRC + "SHA256withRSA.key");
String exceptionMessage = Assertions.assertThrows(PdfException.class, () -> {
try (PdfDocument pdfDoc = new PdfDocument(
new PdfReader(SOURCE_FOLDER + "invalidPublicKeyMacProtectedDocument.pdf",
new ReaderProperties().setPublicKeySecurityParams(certificate, privateKey, PROVIDER_NAME,
null)), CompareTool.createTestPdfWriter(outputFileName))) {
pdfDoc.addNewPage().addAnnotation(new PdfTextAnnotation(new Rectangle(100, 100, 100, 100)));
}
}).getMessage();
Assertions.assertEquals(KernelExceptionMessageConstant.MAC_VALIDATION_FAILED,
exceptionMessage);
}
@Test
@LogMessages(messages = @LogMessage(messageTemplate = KernelLogMessageConstant.MD5_IS_NOT_FIPS_COMPLIANT,
ignore = true))
public void readSignedMacProtectedDocumentWithoutAttributeTest() {
String message = Assertions.assertThrows(PdfException.class, () -> {
try (PdfDocument ignored = new PdfDocument(new PdfReader(SOURCE_FOLDER + "signedMacProtectedDocWithoutAttribute.pdf",
new ReaderProperties().setPassword(PASSWORD)))) {
}
}).getMessage();
Assertions.assertEquals(KernelExceptionMessageConstant.MAC_ATTRIBUTE_NOT_SPECIFIED, message);
}
@Test
@LogMessages(messages = @LogMessage(messageTemplate = KernelLogMessageConstant.MD5_IS_NOT_FIPS_COMPLIANT,
ignore = true))
public void macProtectionStrippedTest() {
String message = Assertions.assertThrows(PdfException.class, () -> {
try (PdfDocument ignored = new PdfDocument(new PdfReader(SOURCE_FOLDER + "macProtectionStrippedTest.pdf",
new ReaderProperties().setPassword(PASSWORD)))) {
}
}).getMessage();
Assertions.assertEquals(KernelExceptionMessageConstant.MAC_PERMS_WITHOUT_MAC, message);
}
@Test
@LogMessages(messages = @LogMessage(messageTemplate = KernelLogMessageConstant.MD5_IS_NOT_FIPS_COMPLIANT,
ignore = true))
public void readSignedMacProtectedDocumentTest() {
AssertUtil.doesNotThrow(() -> {
try (PdfDocument ignored = new PdfDocument(new PdfReader(SOURCE_FOLDER + "signedMacProtectedDocument.pdf",
new ReaderProperties().setPassword(PASSWORD)))) {
}
});
}
@Test
@LogMessages(messages = @LogMessage(messageTemplate = KernelLogMessageConstant.MD5_IS_NOT_FIPS_COMPLIANT,
ignore = true))
public void readThirdPartyMacProtectedDocumentTest() {
AssertUtil.doesNotThrow(() -> {
try (PdfDocument ignored = new PdfDocument(new PdfReader(SOURCE_FOLDER + "thirdPartyMacProtectedDocument.pdf",
new ReaderProperties().setPassword(PASSWORD)))) {
}
});
}
@Test
@LogMessages(messages = @LogMessage(messageTemplate = KernelLogMessageConstant.MD5_IS_NOT_FIPS_COMPLIANT,
ignore = true))
public void readThirdPartyPublicKeyMacProtectedDocumentTest() throws Exception {
try {
BouncyCastleFactoryCreator.getFactory().isEncryptionFeatureSupported(0, true);
} catch (Exception ignored) {
Assumptions.assumeTrue(false);
}
PrivateKey privateKey = MacIntegrityProtectorCreationTest.getPrivateKey(CERTS_SRC + "keyForEncryption.pem");
Certificate certificate = CryptoUtil.readPublicCertificate(
FileUtil.getInputStreamForFile(CERTS_SRC + "certForEncryption.crt"));
AssertUtil.doesNotThrow(() -> {
try (PdfDocument ignored = new PdfDocument(new PdfReader(SOURCE_FOLDER + "thirdPartyPublicKeyMacProtectedDocument.pdf",
new ReaderProperties().setPublicKeySecurityParams(certificate, privateKey, PROVIDER_NAME, null)))) {
}
});
}
@Test
@LogMessages(messages = @LogMessage(messageTemplate = KernelLogMessageConstant.MD5_IS_NOT_FIPS_COMPLIANT,
ignore = true))
public void readMacProtectedPdf1_7() {
AssertUtil.doesNotThrow(() -> {
try (PdfDocument ignored = new PdfDocument(new PdfReader(SOURCE_FOLDER + "macProtectedDocumentPdf1_7.pdf",
new ReaderProperties().setPassword(PASSWORD)))) {
}
});
}
@Test
@LogMessages(messages = @LogMessage(messageTemplate = KernelLogMessageConstant.MD5_IS_NOT_FIPS_COMPLIANT,
ignore = true))
public void noSaltTest() {
String message = Assertions.assertThrows(PdfException.class, () -> {
try (PdfDocument pdfDoc = new PdfDocument(new PdfReader(SOURCE_FOLDER + "noSaltTest.pdf",
new ReaderProperties().setPassword(PASSWORD)))) {
}
}).getMessage();
Assertions.assertEquals(KernelExceptionMessageConstant.MAC_VALIDATION_NO_SALT, message);
}
@Test
@LogMessages(messages = @LogMessage(messageTemplate = KernelLogMessageConstant.MD5_IS_NOT_FIPS_COMPLIANT,
ignore = true))
public void readTamperedMacProtectedDocumentTest() {
String message = Assertions.assertThrows(PdfException.class, () -> {
try (PdfDocument ignored = new PdfDocument(
new PdfReader(SOURCE_FOLDER + "thirdPartyMacProtectedDocumentTampered.pdf",
new ReaderProperties().setPassword(PASSWORD)))) {
}
}).getMessage();
Assertions.assertEquals(KernelExceptionMessageConstant.MAC_VALIDATION_FAILED, message);
}
@Test
@LogMessages(messages = @LogMessage(messageTemplate = KernelLogMessageConstant.MD5_IS_NOT_FIPS_COMPLIANT,
ignore = true))
public void doNotThrowOnValidationTest1() throws IOException, InterruptedException {
String fileName = "doNotThrowOnValidationTest1.pdf";
String outputFileName = DESTINATION_FOLDER + fileName;
String cmpFileName = SOURCE_FOLDER + "cmp_" + fileName;
StampingProperties stampingProperties = new StampingProperties();
stampingProperties.registerDependency(IMacContainerLocator.class,
new StandaloneMacContainerLocator() {
@Override
public void handleMacValidationError(MacValidationException exception) {
// do nothing
}
});
try (PdfDocument pdfDoc = new PdfDocument(new PdfReader(SOURCE_FOLDER + "macProtectionStrippedTest.pdf",
new ReaderProperties().setPassword(PASSWORD)),
CompareTool.createTestPdfWriter(outputFileName), stampingProperties)) {
}
new CompareTool().compareByContent(outputFileName, cmpFileName, DESTINATION_FOLDER, "diff");
}
@Test
@LogMessages(messages = @LogMessage(messageTemplate = KernelLogMessageConstant.MD5_IS_NOT_FIPS_COMPLIANT,
ignore = true))
public void doNotThrowOnValidationTest2() throws IOException, InterruptedException {
String fileName = "doNotThrowOnValidationTest2.pdf";
String outputFileName = DESTINATION_FOLDER + fileName;
String cmpFileName = SOURCE_FOLDER + "cmp_" + fileName;
StampingProperties stampingProperties = new StampingProperties();
stampingProperties.registerDependency(IMacContainerLocator.class,
new StandaloneMacContainerLocator() {
@Override
public void handleMacValidationError(MacValidationException exception) {
// do nothing
}
});
try (PdfDocument pdfDoc = new PdfDocument(
new PdfReader(SOURCE_FOLDER + "thirdPartyMacProtectedDocumentTampered.pdf",
new ReaderProperties().setPassword(PASSWORD)),
CompareTool.createTestPdfWriter(outputFileName), stampingProperties)) {
}
new CompareTool().compareByContent(outputFileName, cmpFileName, DESTINATION_FOLDER, "diff");
}
}