PdfEncryptionTest.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.crypto.pdfencryption;
import com.itextpdf.bouncycastleconnector.BouncyCastleFactoryCreator;
import com.itextpdf.commons.bouncycastle.IBouncyCastleFactory;
import com.itextpdf.commons.bouncycastle.operator.AbstractOperatorCreationException;
import com.itextpdf.commons.bouncycastle.pkcs.AbstractPKCSException;
import com.itextpdf.commons.utils.FileUtil;
import com.itextpdf.kernel.crypto.CryptoUtil;
import com.itextpdf.kernel.crypto.securityhandler.StandardHandlerUsingAes256;
import com.itextpdf.kernel.exceptions.BadPasswordException;
import com.itextpdf.kernel.exceptions.KernelExceptionMessageConstant;
import com.itextpdf.kernel.exceptions.PdfException;
import com.itextpdf.kernel.logs.KernelLogMessageConstant;
import com.itextpdf.kernel.pdf.CompressionConstants;
import com.itextpdf.kernel.pdf.EncryptionConstants;
import com.itextpdf.kernel.pdf.PdfDictionary;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfName;
import com.itextpdf.kernel.pdf.PdfNumber;
import com.itextpdf.kernel.pdf.PdfPage;
import com.itextpdf.kernel.pdf.PdfReader;
import com.itextpdf.kernel.pdf.PdfString;
import com.itextpdf.kernel.pdf.PdfVersion;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.kernel.pdf.ReaderProperties;
import com.itextpdf.kernel.pdf.StampingProperties;
import com.itextpdf.kernel.pdf.VersionConforming;
import com.itextpdf.kernel.pdf.WriterProperties;
import com.itextpdf.kernel.pdf.canvas.parser.PdfTextExtractor;
import com.itextpdf.kernel.pdf.filespec.PdfFileSpec;
import com.itextpdf.kernel.utils.CompareTool;
import com.itextpdf.kernel.utils.PemFileHelper;
import com.itextpdf.kernel.xmp.XMPConst;
import com.itextpdf.kernel.xmp.XMPException;
import com.itextpdf.kernel.xmp.XMPMeta;
import com.itextpdf.kernel.xmp.properties.XMPProperty;
import com.itextpdf.test.ExtendedITextTest;
import com.itextpdf.test.ITextTest;
import com.itextpdf.test.TestUtil;
import com.itextpdf.test.annotations.LogMessage;
import com.itextpdf.test.annotations.LogMessages;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Paths;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
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;
/**
* Due to import control restrictions by the governments of a few countries,
* the encryption libraries shipped by default with the Java SDK restrict the
* length, and as a result the strength, of encryption keys. Be aware that in
* this test by using {@link ITextTest#removeCryptographyRestrictions()} we
* remove cryptography restrictions via reflection for testing purposes.
* <br/>
* For more conventional way of solving this problem you need to replace the
* default security JARs in your Java installation with the Java Cryptography
* Extension (JCE) Unlimited Strength Jurisdiction Policy Files. These JARs
* are available for download from http://java.oracle.com/ in eligible countries.
*/
@Tag("BouncyCastleIntegrationTest")
public class PdfEncryptionTest extends ExtendedITextTest {
private static final IBouncyCastleFactory FACTORY = BouncyCastleFactoryCreator.getFactory();
public static final String destinationFolder = TestUtil.getOutputPath() + "/kernel/crypto/pdfencryption/PdfEncryptionTest/";
public static final String sourceFolder = "./src/test/resources/com/itextpdf/kernel/crypto/pdfencryption/PdfEncryptionTest/";
public static final char[] PRIVATE_KEY_PASS = "testpassphrase".toCharArray();
public static final String CERT = sourceFolder + "test.cer";
public static final String PRIVATE_KEY = sourceFolder + "test.pem";
private PrivateKey privateKey;
PdfEncryptionTestUtils encryptionUtil = new PdfEncryptionTestUtils(destinationFolder, sourceFolder);
@BeforeAll
public static void beforeClass() {
createOrClearDestinationFolder(destinationFolder);
Security.addProvider(FACTORY.getProvider());
}
@AfterAll
public static void afterClass() {
CompareTool.cleanup(destinationFolder);
}
@Test
@LogMessages(messages = @LogMessage(messageTemplate = KernelLogMessageConstant.MD5_IS_NOT_FIPS_COMPLIANT,
ignore = true))
public void encryptWithPasswordStandard128() throws IOException, InterruptedException {
String filename = "encryptWithPasswordStandard128.pdf";
int encryptionType = EncryptionConstants.STANDARD_ENCRYPTION_128;
encryptWithPassword2(filename, encryptionType, CompressionConstants.DEFAULT_COMPRESSION);
}
@Test
@LogMessages(messages = @LogMessage(messageTemplate = KernelLogMessageConstant.MD5_IS_NOT_FIPS_COMPLIANT,
ignore = true))
public void encryptWithPasswordStandard40() throws IOException, InterruptedException {
String filename = "encryptWithPasswordStandard40.pdf";
int encryptionType = EncryptionConstants.STANDARD_ENCRYPTION_40;
encryptWithPassword2(filename, encryptionType, CompressionConstants.DEFAULT_COMPRESSION);
}
@Test
@LogMessages(messages = @LogMessage(messageTemplate = KernelLogMessageConstant.MD5_IS_NOT_FIPS_COMPLIANT,
ignore = true))
public void encryptWithPasswordStandard128NoCompression() throws IOException, InterruptedException {
String filename = "encryptWithPasswordStandard128NoCompression.pdf";
int encryptionType = EncryptionConstants.STANDARD_ENCRYPTION_128;
encryptWithPassword2(filename, encryptionType, CompressionConstants.NO_COMPRESSION);
}
@Test
@LogMessages(messages = @LogMessage(messageTemplate = KernelLogMessageConstant.MD5_IS_NOT_FIPS_COMPLIANT,
ignore = true))
public void encryptWithPasswordStandard40NoCompression() throws IOException, InterruptedException {
String filename = "encryptWithPasswordStandard40NoCompression.pdf";
int encryptionType = EncryptionConstants.STANDARD_ENCRYPTION_40;
encryptWithPassword2(filename, encryptionType, CompressionConstants.NO_COMPRESSION);
}
@Test
@LogMessages(messages = @LogMessage(messageTemplate = KernelLogMessageConstant.MD5_IS_NOT_FIPS_COMPLIANT,
ignore = true))
public void encryptWithPasswordAes128() throws IOException, InterruptedException {
String filename = "encryptWithPasswordAes128.pdf";
int encryptionType = EncryptionConstants.ENCRYPTION_AES_128;
encryptWithPassword2(filename, encryptionType, CompressionConstants.DEFAULT_COMPRESSION);
}
@Test
public void encryptWithPasswordAes256() throws IOException, InterruptedException {
String filename = "encryptWithPasswordAes256.pdf";
int encryptionType = EncryptionConstants.ENCRYPTION_AES_256;
encryptWithPassword2(filename, encryptionType, CompressionConstants.DEFAULT_COMPRESSION);
}
@Test
@LogMessages(messages = @LogMessage(messageTemplate = KernelLogMessageConstant.MD5_IS_NOT_FIPS_COMPLIANT,
ignore = true))
public void encryptWithPasswordAes128NoCompression() throws IOException, InterruptedException {
String filename = "encryptWithPasswordAes128NoCompression.pdf";
int encryptionType = EncryptionConstants.ENCRYPTION_AES_128;
encryptWithPassword2(filename, encryptionType, CompressionConstants.NO_COMPRESSION);
}
@Test
public void encryptWithPasswordAes256NoCompression() throws IOException, InterruptedException {
String filename = "encryptWithPasswordAes256NoCompression.pdf";
int encryptionType = EncryptionConstants.ENCRYPTION_AES_256;
encryptWithPassword2(filename, encryptionType, CompressionConstants.NO_COMPRESSION);
}
@Test
@LogMessages(messages = @LogMessage(messageTemplate = KernelLogMessageConstant.MD5_IS_NOT_FIPS_COMPLIANT,
ignore = true))
public void openEncryptedDocWithoutPassword() throws IOException {
try (PdfReader reader = new PdfReader(sourceFolder + "encryptedWithPasswordStandard40.pdf")) {
Exception e = Assertions.assertThrows(BadPasswordException.class, () -> new PdfDocument(reader));
Assertions.assertEquals(KernelExceptionMessageConstant.BAD_USER_PASSWORD, e.getMessage());
}
}
@Test
@LogMessages(messages = @LogMessage(messageTemplate = KernelLogMessageConstant.MD5_IS_NOT_FIPS_COMPLIANT,
ignore = true))
public void openEncryptedDocWithWrongPassword() throws IOException {
try (PdfReader reader = new PdfReader(sourceFolder + "encryptedWithPasswordStandard40.pdf",
new ReaderProperties().setPassword("wrong_password".getBytes(StandardCharsets.ISO_8859_1)))) {
Exception e = Assertions.assertThrows(BadPasswordException.class, () -> new PdfDocument(reader));
Assertions.assertEquals(KernelExceptionMessageConstant.BAD_USER_PASSWORD, e.getMessage());
}
}
@Test
public void openEncryptedDocWithoutCertificate() throws IOException {
try (PdfReader reader = new PdfReader(sourceFolder + "encryptedWithCertificateAes128.pdf")) {
Exception e = Assertions.assertThrows(PdfException.class, () -> new PdfDocument(reader));
Assertions.assertEquals(
KernelExceptionMessageConstant.CERTIFICATE_IS_NOT_PROVIDED_DOCUMENT_IS_ENCRYPTED_WITH_PUBLIC_KEY_CERTIFICATE,
e.getMessage());
}
}
@Test
@LogMessages(messages = @LogMessage(messageTemplate = KernelLogMessageConstant.MD5_IS_NOT_FIPS_COMPLIANT,
ignore = true))
public void openEncryptedDocWithoutPrivateKey() throws IOException, CertificateException {
try (PdfReader reader = new PdfReader(sourceFolder + "encryptedWithCertificateAes128.pdf",
new ReaderProperties()
.setPublicKeySecurityParams(
getPublicCertificate(sourceFolder + "wrong.cer"),
null,
FACTORY.getProviderName(),
null))) {
Exception e = Assertions.assertThrows(PdfException.class,
() -> new PdfDocument(reader)
);
Assertions.assertEquals(KernelExceptionMessageConstant.BAD_CERTIFICATE_AND_KEY, e.getMessage());
}
}
@Test
@LogMessages(messages = @LogMessage(messageTemplate = KernelLogMessageConstant.MD5_IS_NOT_FIPS_COMPLIANT,
ignore = true))
public void openEncryptedDocWithWrongCertificate()
throws IOException, GeneralSecurityException, AbstractPKCSException, AbstractOperatorCreationException {
try (PdfReader reader = new PdfReader(sourceFolder + "encryptedWithCertificateAes128.pdf",
new ReaderProperties()
.setPublicKeySecurityParams(
getPublicCertificate(sourceFolder + "wrong.cer"),
getPrivateKey(),
FACTORY.getProviderName(),
null))) {
Exception e = Assertions.assertThrows(PdfException.class,
() -> new PdfDocument(reader)
);
Assertions.assertEquals(KernelExceptionMessageConstant.BAD_CERTIFICATE_AND_KEY, e.getMessage());
}
}
@Test
@LogMessages(messages = @LogMessage(messageTemplate = KernelLogMessageConstant.MD5_IS_NOT_FIPS_COMPLIANT,
ignore = true))
public void openEncryptedDocWithWrongCertificateAndPrivateKey()
throws IOException, GeneralSecurityException, AbstractPKCSException, AbstractOperatorCreationException {
try (PdfReader reader = new PdfReader(sourceFolder + "encryptedWithCertificateAes128.pdf",
new ReaderProperties()
.setPublicKeySecurityParams(
getPublicCertificate(sourceFolder + "wrong.cer"),
PemFileHelper.readPrivateKeyFromPemFile(
FileUtil.getInputStreamForFile(sourceFolder + "wrong.pem"), PRIVATE_KEY_PASS),
FACTORY.getProviderName(),
null))) {
Exception e = Assertions.assertThrows(PdfException.class,
() -> new PdfDocument(reader)
);
Assertions.assertEquals(KernelExceptionMessageConstant.BAD_CERTIFICATE_AND_KEY, e.getMessage());
}
}
@Test
@LogMessages(messages = @LogMessage(messageTemplate = KernelLogMessageConstant.MD5_IS_NOT_FIPS_COMPLIANT,
ignore = true))
public void metadataReadingInEncryptedDoc() throws IOException, XMPException {
PdfReader reader = new PdfReader(sourceFolder + "encryptedWithPlainMetadata.pdf",
new ReaderProperties().setPassword(PdfEncryptionTestUtils.OWNER));
PdfDocument doc = new PdfDocument(reader);
XMPMeta xmpMeta = doc.getXmpMetadata();
XMPProperty creatorToolXmp = xmpMeta.getProperty(XMPConst.NS_XMP, "CreatorTool");
doc.close();
Assertions.assertNotNull(creatorToolXmp);
Assertions.assertEquals("iText", creatorToolXmp.getValue());
}
@Test
@LogMessages(messages = @LogMessage(messageTemplate = KernelLogMessageConstant.MD5_IS_NOT_FIPS_COMPLIANT,
ignore = true))
public void copyEncryptedDocument() throws GeneralSecurityException, IOException, InterruptedException,
AbstractPKCSException, AbstractOperatorCreationException {
// I don't know how this source doc was created. Currently it's not opening by Acrobat and Foxit.
// If I recreate it using iText, decrypting it in bc-fips on dotnet will start failing. But we probably still
// want this test.
PdfDocument srcDoc = new PdfDocument(new PdfReader(sourceFolder + "encryptedWithCertificateAes128.pdf",
new ReaderProperties().
setPublicKeySecurityParams(getPublicCertificate(CERT), getPrivateKey(),
FACTORY.getProviderName(), null)));
String fileName = "copiedEncryptedDoc.pdf";
PdfDocument destDoc = new PdfDocument(CompareTool.createTestPdfWriter(destinationFolder + fileName));
srcDoc.copyPagesTo(1, 1, destDoc);
PdfDictionary srcInfo = srcDoc.getTrailer().getAsDictionary(PdfName.Info);
PdfDictionary destInfo = destDoc.getTrailer().getAsDictionary(PdfName.Info);
if (destInfo == null) {
destInfo = new PdfDictionary();
destDoc.getTrailer().put(PdfName.Info, destInfo);
}
for (PdfName srcInfoKey : srcInfo.keySet()) {
destInfo.put((PdfName) srcInfoKey.copyTo(destDoc), srcInfo.get(srcInfoKey).copyTo(destDoc));
}
srcDoc.close();
destDoc.close();
Assertions.assertNull(new CompareTool()
.compareByContent(destinationFolder + fileName, sourceFolder + "cmp_" + fileName,
destinationFolder, "diff_"));
}
@Test
@LogMessages(messages = @LogMessage(messageTemplate = KernelLogMessageConstant.MD5_IS_NOT_FIPS_COMPLIANT,
ignore = true))
public void openDocNoUserPassword() throws IOException {
String fileName = "noUserPassword.pdf";
PdfDocument document = new PdfDocument(new PdfReader(sourceFolder + fileName));
document.close();
encryptionUtil.checkDecryptedWithPasswordContent(sourceFolder + fileName, null,
PdfEncryptionTestUtils.PAGE_TEXT_CONTENT);
}
@Test
@LogMessages(messages = @LogMessage(messageTemplate = KernelLogMessageConstant.MD5_IS_NOT_FIPS_COMPLIANT,
ignore = true))
public void stampDocNoUserPassword() throws IOException {
String fileName = "stampedNoPassword.pdf";
try (PdfReader reader = new PdfReader(sourceFolder + "noUserPassword.pdf");
PdfWriter writer = CompareTool.createTestPdfWriter(destinationFolder + fileName)) {
Exception e = Assertions.assertThrows(BadPasswordException.class, () -> new PdfDocument(reader, writer));
Assertions.assertEquals(BadPasswordException.PdfReaderNotOpenedWithOwnerPassword, e.getMessage());
}
}
@Test
@LogMessages(messages = @LogMessage(messageTemplate = KernelLogMessageConstant.MD5_IS_NOT_FIPS_COMPLIANT,
ignore = true))
public void encryptWithPasswordAes128EmbeddedFilesOnly() throws IOException {
String filename = "encryptWithPasswordAes128EmbeddedFilesOnly.pdf";
int encryptionType = EncryptionConstants.ENCRYPTION_AES_128 | EncryptionConstants.EMBEDDED_FILES_ONLY;
String outFileName = destinationFolder + filename;
int permissions = EncryptionConstants.ALLOW_SCREENREADERS;
PdfWriter writer = CompareTool.createTestPdfWriter(outFileName,
new WriterProperties().setStandardEncryption(PdfEncryptionTestUtils.USER, PdfEncryptionTestUtils.OWNER, permissions,
encryptionType).addXmpMetadata()
);
PdfDocument document = new PdfDocument(writer);
document.getDocumentInfo().setMoreInfo(PdfEncryptionTestUtils.CUSTOM_INFO_ENTRY_KEY, PdfEncryptionTestUtils.CUSTOM_INFO_ENTRY_VALUE);
PdfPage page = document.addNewPage();
String textContent = "Hello world!";
PdfEncryptionTestUtils.writeTextBytesOnPageContent(page, textContent);
String descripton = "encryptedFile";
String path = sourceFolder + "pageWithContent.pdf";
document.addFileAttachment(descripton,
PdfFileSpec.createEmbeddedFileSpec(document, path, descripton, path, null, null));
page.flush();
document.close();
//TODO DEVSIX-5355 Specific crypto filters for EFF StmF and StrF are not supported at the moment.
// However we can read embedded files only mode.
boolean ERROR_IS_EXPECTED = false;
encryptionUtil.checkDecryptedWithPasswordContent(destinationFolder + filename, PdfEncryptionTestUtils.OWNER,
textContent, ERROR_IS_EXPECTED);
encryptionUtil.checkDecryptedWithPasswordContent(destinationFolder + filename, PdfEncryptionTestUtils.USER,
textContent, ERROR_IS_EXPECTED);
}
@Test
public void encryptWithPasswordAes256EmbeddedFilesOnly() throws IOException {
String filename = "encryptWithPasswordAes256EmbeddedFilesOnly.pdf";
int encryptionType = EncryptionConstants.ENCRYPTION_AES_256 | EncryptionConstants.EMBEDDED_FILES_ONLY;
String outFileName = destinationFolder + filename;
int permissions = EncryptionConstants.ALLOW_SCREENREADERS;
PdfWriter writer = CompareTool.createTestPdfWriter(outFileName,
new WriterProperties().setStandardEncryption(PdfEncryptionTestUtils.USER, PdfEncryptionTestUtils.OWNER, permissions,
encryptionType).addXmpMetadata().setPdfVersion(PdfVersion.PDF_2_0)
);
PdfDocument document = new PdfDocument(writer);
document.getDocumentInfo().setMoreInfo(PdfEncryptionTestUtils.CUSTOM_INFO_ENTRY_KEY, PdfEncryptionTestUtils.CUSTOM_INFO_ENTRY_VALUE);
PdfPage page = document.addNewPage();
String textContent = "Hello world!";
PdfEncryptionTestUtils.writeTextBytesOnPageContent(page, textContent);
String descripton = "encryptedFile";
String path = sourceFolder + "pageWithContent.pdf";
document.addFileAttachment(descripton,
PdfFileSpec.createEmbeddedFileSpec(document, path, descripton, path, null, null));
page.flush();
document.close();
//TODO DEVSIX-5355 Specific crypto filters for EFF StmF and StrF are not supported at the moment.
// However we can read embedded files only mode.
boolean ERROR_IS_EXPECTED = false;
encryptionUtil.checkDecryptedWithPasswordContent(destinationFolder + filename, PdfEncryptionTestUtils.OWNER,
textContent, ERROR_IS_EXPECTED);
encryptionUtil.checkDecryptedWithPasswordContent(destinationFolder + filename, PdfEncryptionTestUtils.USER,
textContent, ERROR_IS_EXPECTED);
}
@Test
public void encryptWithPasswordAes256EmbeddedFilesOnly2() throws IOException {
String filename = "encryptWithPasswordAes256EmbeddedFilesOnly2.pdf";
int encryptionType = EncryptionConstants.ENCRYPTION_AES_256 | EncryptionConstants.EMBEDDED_FILES_ONLY;
String outFileName = destinationFolder + filename;
int permissions = EncryptionConstants.ALLOW_SCREENREADERS;
PdfWriter writer = new PdfWriter(outFileName,
new WriterProperties().setStandardEncryption(PdfEncryptionTestUtils.USER, PdfEncryptionTestUtils.OWNER, permissions,
encryptionType).addXmpMetadata().setPdfVersion(PdfVersion.PDF_2_0)
);
PdfDocument document = new PdfDocument(writer);
document.getDocumentInfo().setMoreInfo(PdfEncryptionTestUtils.CUSTOM_INFO_ENTRY_KEY, PdfEncryptionTestUtils.CUSTOM_INFO_ENTRY_VALUE);
PdfPage page = document.addNewPage();
String textContent = "Hello world!";
PdfEncryptionTestUtils.writeTextBytesOnPageContent(page, textContent);
String descripton = "encryptedFile";
document.addFileAttachment(descripton,
PdfFileSpec.createEmbeddedFileSpec(document, "TEST".getBytes(StandardCharsets.UTF_8), descripton, "test.txt", null, null));
page.flush();
document.close();
//TODO DEVSIX-5355 Specific crypto filters for EFF StmF and StrF are not supported at the moment.
// However we can read embedded files only mode.
boolean ERROR_IS_EXPECTED = false;
encryptionUtil.checkDecryptedWithPasswordContent(destinationFolder + filename, PdfEncryptionTestUtils.OWNER,
textContent, ERROR_IS_EXPECTED);
encryptionUtil.checkDecryptedWithPasswordContent(destinationFolder + filename, PdfEncryptionTestUtils.USER,
textContent, ERROR_IS_EXPECTED);
}
@Test
public void encryptAes256Pdf2NotEncryptMetadata() throws InterruptedException, IOException {
String filename = "encryptAes256Pdf2NotEncryptMetadata.pdf";
int encryptionType = EncryptionConstants.ENCRYPTION_AES_256 | EncryptionConstants.DO_NOT_ENCRYPT_METADATA;
encryptWithPassword2(filename, encryptionType, CompressionConstants.DEFAULT_COMPRESSION);
}
@Test
public void encryptAes256Pdf2NotEncryptMetadata02() throws InterruptedException, IOException {
String filename = "encryptAes256Pdf2NotEncryptMetadata02.pdf";
int encryptionType = EncryptionConstants.ENCRYPTION_AES_256 | EncryptionConstants.DO_NOT_ENCRYPT_METADATA;
encryptWithPassword(filename, encryptionType, CompressionConstants.DEFAULT_COMPRESSION, true);
}
@Test
@LogMessages(messages = @LogMessage(messageTemplate = KernelLogMessageConstant.MD5_IS_NOT_FIPS_COMPLIANT,
ignore = true))
public void encryptAes256EncryptedStampingUpdate() throws InterruptedException, IOException {
String filename = "encryptAes256EncryptedStampingUpdate.pdf";
String src = sourceFolder + "encryptedWithPlainMetadata.pdf";
String out = destinationFolder + filename;
PdfDocument pdfDoc = new PdfDocument(
new PdfReader(src, new ReaderProperties().setPassword(PdfEncryptionTestUtils.OWNER)),
CompareTool.createTestPdfWriter(out, new WriterProperties()
.setStandardEncryption(PdfEncryptionTestUtils.USER, PdfEncryptionTestUtils.OWNER,
EncryptionConstants.ALLOW_PRINTING, EncryptionConstants.STANDARD_ENCRYPTION_40)),
new StampingProperties());
pdfDoc.close();
CompareTool compareTool = new CompareTool().enableEncryptionCompare();
String compareResult = compareTool.compareByContent(out, sourceFolder + "cmp_" + filename, destinationFolder,
"diff_", PdfEncryptionTestUtils.USER, PdfEncryptionTestUtils.USER);
if (compareResult != null) {
Assertions.fail(compareResult);
}
}
@Test
public void encryptAes256FullCompression() throws InterruptedException, IOException {
String filename = "encryptAes256FullCompression.pdf";
int encryptionType = EncryptionConstants.ENCRYPTION_AES_256;
encryptWithPassword2(filename, encryptionType, CompressionConstants.DEFAULT_COMPRESSION, true);
}
@Test
public void encryptWithPasswordAes256Pdf2() throws InterruptedException, IOException {
String filename = "encryptWithPasswordAes256Pdf2.pdf";
int encryptionType = EncryptionConstants.ENCRYPTION_AES_256;
encryptWithPassword2(filename, encryptionType, CompressionConstants.DEFAULT_COMPRESSION, true);
}
@Test
@LogMessages(messages = {
@LogMessage(messageTemplate = KernelLogMessageConstant.MD5_IS_NOT_FIPS_COMPLIANT, ignore = true),
@LogMessage(messageTemplate = VersionConforming.DEPRECATED_ENCRYPTION_ALGORITHMS, count = 2)})
public void encryptWithPasswordAes128Pdf2() throws InterruptedException, IOException {
String filename = "encryptWithPasswordAes128Pdf2.pdf";
int encryptionType = EncryptionConstants.ENCRYPTION_AES_128;
encryptWithPassword2(filename, encryptionType, CompressionConstants.DEFAULT_COMPRESSION, true);
}
@Test
public void stampAndUpdateVersionNewAes256() throws InterruptedException, IOException {
String filename = "stampAndUpdateVersionNewAes256.pdf";
PdfDocument doc = new PdfDocument(
new PdfReader(sourceFolder + "encryptedWithPasswordAes256.pdf",
new ReaderProperties().setPassword(PdfEncryptionTestUtils.OWNER)),
CompareTool.createTestPdfWriter(destinationFolder + filename,
new WriterProperties()
.setPdfVersion(PdfVersion.PDF_2_0)
.setStandardEncryption(PdfEncryptionTestUtils.USER, PdfEncryptionTestUtils.OWNER, 0,
EncryptionConstants.ENCRYPTION_AES_256)));
doc.close();
encryptionUtil.compareEncryptedPdf(filename);
}
@Test
public void encryptAes256Pdf2Permissions() throws InterruptedException, IOException {
String filename = "encryptAes256Pdf2Permissions.pdf";
int permissions = EncryptionConstants.ALLOW_FILL_IN | EncryptionConstants.ALLOW_SCREENREADERS
| EncryptionConstants.ALLOW_DEGRADED_PRINTING;
PdfDocument doc = new PdfDocument(
CompareTool.createTestPdfWriter(destinationFolder + filename,
new WriterProperties()
.setPdfVersion(PdfVersion.PDF_2_0)
.setStandardEncryption(PdfEncryptionTestUtils.USER, PdfEncryptionTestUtils.OWNER, permissions,
EncryptionConstants.ENCRYPTION_AES_256)));
doc.getDocumentInfo().setMoreInfo(PdfEncryptionTestUtils.CUSTOM_INFO_ENTRY_KEY, PdfEncryptionTestUtils.CUSTOM_INFO_ENTRY_VALUE);
PdfEncryptionTestUtils.writeTextBytesOnPageContent(doc.addNewPage(), PdfEncryptionTestUtils.PAGE_TEXT_CONTENT);
doc.close();
encryptionUtil.compareEncryptedPdf(filename);
}
@Test
@LogMessages(messages = @LogMessage(messageTemplate = KernelLogMessageConstant.MD5_IS_NOT_FIPS_COMPLIANT,
ignore = true))
public void encryptWithPasswordAes128NoMetadataCompression() throws Exception {
String srcFilename = "srcEncryptWithPasswordAes128NoMetadataCompression.pdf";
PdfReader reader = new PdfReader(sourceFolder + srcFilename, new ReaderProperties());
WriterProperties props = new WriterProperties()
.setStandardEncryption("superuser".getBytes(), "superowner".getBytes(),
EncryptionConstants.ALLOW_PRINTING,
EncryptionConstants.ENCRYPTION_AES_128 |
EncryptionConstants.DO_NOT_ENCRYPT_METADATA);
String outFilename = "encryptWithPasswordAes128NoMetadataCompression.pdf";
PdfWriter writer = CompareTool.createTestPdfWriter(destinationFolder + outFilename, props);
PdfDocument pdfDoc = new PdfDocument(reader, writer);
pdfDoc.close();
CompareTool compareTool = new CompareTool();
compareTool.enableEncryptionCompare();
compareTool.getOutReaderProperties().setPassword("superowner".getBytes());
compareTool.getCmpReaderProperties().setPassword("superowner".getBytes());
String outPdf = destinationFolder + outFilename;
String cmpPdf = sourceFolder + "cmp_" + outFilename;
Assertions.assertNull(compareTool.compareByContent(outPdf, cmpPdf, destinationFolder, "diff_"));
}
@Test
public void checkMD5LogAbsenceInUnapprovedMode() throws IOException {
Assumptions.assumeTrue(!FACTORY.isInApprovedOnlyMode());
String fileName = "noUserPassword.pdf";
try (PdfDocument document = new PdfDocument(new PdfReader(sourceFolder + fileName))) {
// this test checks log message absence
}
}
@Test
public void decryptAdobeWithPasswordAes256() throws IOException {
String filename = Paths.get(sourceFolder + "AdobeAes256.pdf").toString();
decryptWithPassword(filename, "user".getBytes());
decryptWithPassword(filename, "owner".getBytes());
}
@Test
@LogMessages(messages = @LogMessage(messageTemplate = KernelLogMessageConstant.MD5_IS_NOT_FIPS_COMPLIANT,
ignore = true))
public void decodeDictionaryWithInvalidOwnerHashAes256() {
PdfDictionary dictionary = new PdfDictionary();
dictionary.put(PdfName.R, new PdfNumber(0));
//Setting password hash which exceeds 48 bytes and contains non 0 elements after first 48 bytes
dictionary.put(PdfName.O, new PdfString("��\u0010\u001D`��\u0084n����j{\f����\u0089J��N*\u0090��>No\u0099" +
"\u0087J��\u0013\"V\u008E\fT!\u0082\u0003\u009E��\u008Fc\u0004��].\u008C\u009C\u009C\u0000" +
"\u0000\u0000\u0000\u0013\u0000\u0013\u0013\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000" +
"\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0013"));
Exception e = Assertions.assertThrows(PdfException.class,
() -> new StandardHandlerUsingAes256(dictionary, "owner".getBytes()));
Assertions.assertEquals(KernelExceptionMessageConstant.BAD_PASSWORD_HASH, e.getCause().getMessage());
}
@Test
@LogMessages(messages = @LogMessage(messageTemplate = KernelLogMessageConstant.MD5_IS_NOT_FIPS_COMPLIANT,
ignore = true))
public void openEncryptedWithPasswordDocWithDefaultKeyLength() throws IOException {
try (PdfReader reader = new PdfReader(sourceFolder + "encryptedWithPasswordWithDefaultKeyLength.pdf",
new ReaderProperties().setPassword("user".getBytes(StandardCharsets.UTF_8)));
PdfDocument document = new PdfDocument(reader)) {
Assertions.assertFalse(document.getTrailer().getAsDictionary(PdfName.Encrypt).containsKey(PdfName.Length));
}
}
@Test
public void checkPermissionsLongValue() throws IOException {
// The test checks
// that no IoLogMessageConstant.ENCRYPTION_ENTRIES_P_AND_ENCRYPT_METADATA_NOT_CORRESPOND_PERMS_ENTRY is logged
PdfDocument doc = new PdfDocument(
new PdfReader(sourceFolder + "encryptedWithPasswordAes256_modifiedPermissions.pdf",
new ReaderProperties().setPassword(PdfEncryptionTestUtils.OWNER)));
doc.close();
}
public void encryptWithPassword2(String filename, int encryptionType, int compression)
throws IOException, InterruptedException {
encryptWithPassword2(filename, encryptionType, compression, false);
}
public void encryptWithPassword2(String filename, int encryptionType, int compression, boolean isPdf2)
throws IOException, InterruptedException {
int permissions = EncryptionConstants.ALLOW_SCREENREADERS;
WriterProperties writerProperties = new WriterProperties().setStandardEncryption(PdfEncryptionTestUtils.USER,
PdfEncryptionTestUtils.OWNER, permissions, encryptionType);
if (isPdf2) {
writerProperties.setPdfVersion(PdfVersion.PDF_2_0);
}
PdfWriter writer = CompareTool.createTestPdfWriter(destinationFolder + filename,
writerProperties.addXmpMetadata());
writer.setCompressionLevel(compression);
PdfDocument document = new PdfDocument(writer);
document.getDocumentInfo().setMoreInfo(PdfEncryptionTestUtils.CUSTOM_INFO_ENTRY_KEY,
PdfEncryptionTestUtils.CUSTOM_INFO_ENTRY_VALUE);
PdfPage page = document.addNewPage();
PdfEncryptionTestUtils.writeTextBytesOnPageContent(page, PdfEncryptionTestUtils.PAGE_TEXT_CONTENT);
page.flush();
document.close();
encryptionUtil.compareEncryptedPdf(filename);
checkEncryptedWithPasswordDocumentStamping(filename, PdfEncryptionTestUtils.OWNER);
checkEncryptedWithPasswordDocumentAppending(filename, PdfEncryptionTestUtils.OWNER);
}
public void encryptWithPassword(String filename, int encryptionType, int compression, boolean fullCompression)
throws IOException, InterruptedException {
String outFileName = destinationFolder + filename;
int permissions = EncryptionConstants.ALLOW_SCREENREADERS;
PdfWriter writer = CompareTool.createTestPdfWriter(outFileName,
new WriterProperties()
.setStandardEncryption(PdfEncryptionTestUtils.USER, PdfEncryptionTestUtils.OWNER, permissions, encryptionType)
.addXmpMetadata()
.setFullCompressionMode(fullCompression));
writer.setCompressionLevel(compression);
PdfDocument document = new PdfDocument(writer);
document.getDocumentInfo().setMoreInfo(PdfEncryptionTestUtils.CUSTOM_INFO_ENTRY_KEY,
PdfEncryptionTestUtils.CUSTOM_INFO_ENTRY_VALUE);
PdfPage page = document.addNewPage();
PdfEncryptionTestUtils.writeTextBytesOnPageContent(page, PdfEncryptionTestUtils.PAGE_TEXT_CONTENT);
page.flush();
document.close();
encryptionUtil.compareEncryptedPdf(filename);
checkEncryptedWithPasswordDocumentStamping(filename, PdfEncryptionTestUtils.OWNER);
checkEncryptedWithPasswordDocumentAppending(filename, PdfEncryptionTestUtils.OWNER);
}
public Certificate getPublicCertificate(String path) throws IOException, CertificateException {
InputStream is = FileUtil.getInputStreamForFile(path);
return CryptoUtil.readPublicCertificate(is);
}
public PrivateKey getPrivateKey() throws IOException, AbstractPKCSException, AbstractOperatorCreationException {
if (privateKey == null) {
privateKey = PemFileHelper.readPrivateKeyFromPemFile(
FileUtil.getInputStreamForFile(PRIVATE_KEY), PRIVATE_KEY_PASS);
}
return privateKey;
}
// basically this is comparing content of decrypted by itext document with content of encrypted document
public void checkEncryptedWithPasswordDocumentStamping(String filename, byte[] password)
throws IOException, InterruptedException {
String srcFileName = destinationFolder + filename;
String outFileName = destinationFolder + "stamped_" + filename;
PdfReader reader = CompareTool.createOutputReader(srcFileName, new ReaderProperties().setPassword(password));
PdfDocument document = new PdfDocument(reader, CompareTool.createTestPdfWriter(outFileName));
document.close();
CompareTool compareTool = new CompareTool();
String compareResult = compareTool.compareByContent(outFileName, sourceFolder + "cmp_" + filename,
destinationFolder, "diff_", PdfEncryptionTestUtils.USER, PdfEncryptionTestUtils.USER);
if (compareResult != null) {
Assertions.fail(compareResult);
}
}
public void checkEncryptedWithPasswordDocumentAppending(String filename, byte[] password)
throws IOException, InterruptedException {
String srcFileName = destinationFolder + filename;
String outFileName = destinationFolder + "appended_" + filename;
PdfReader reader = CompareTool.createOutputReader(srcFileName, new ReaderProperties().setPassword(password));
PdfDocument document = new PdfDocument(reader, CompareTool.createTestPdfWriter(outFileName),
new StampingProperties().useAppendMode());
PdfPage newPage = document.addNewPage();
newPage.put(PdfName.Default, new PdfString("Hello world string"));
PdfEncryptionTestUtils.writeTextBytesOnPageContent(newPage, "Hello world page_2!");
document.close();
CompareTool compareTool = new CompareTool().enableEncryptionCompare(false);
String compareResult = compareTool.compareByContent(outFileName, sourceFolder + "cmp_appended_" + filename,
destinationFolder, "diff_", PdfEncryptionTestUtils.USER, PdfEncryptionTestUtils.USER);
if (compareResult != null) {
Assertions.fail(compareResult);
}
}
private void decryptWithPassword(String fileName, byte[] password) throws IOException {
ReaderProperties readerProperties = new ReaderProperties().setPassword(password);
try (PdfReader reader = new PdfReader(fileName, readerProperties);
PdfDocument pdfDocument = new PdfDocument(reader)) {
Assertions.assertTrue(PdfTextExtractor.getTextFromPage(pdfDocument.getFirstPage())
.startsWith("Content encrypted by "));
}
}
}