PubSecHandlerUsingAesGcmTest.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.securityhandler;
import com.itextpdf.bouncycastleconnector.BouncyCastleFactoryCreator;
import com.itextpdf.commons.bouncycastle.IBouncyCastleFactory;
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.pdf.EncryptionConstants;
import com.itextpdf.kernel.pdf.PdfBoolean;
import com.itextpdf.kernel.pdf.PdfDictionary;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfEncryption;
import com.itextpdf.kernel.pdf.PdfName;
import com.itextpdf.kernel.pdf.PdfNumber;
import com.itextpdf.kernel.pdf.PdfObject;
import com.itextpdf.kernel.pdf.PdfReader;
import com.itextpdf.kernel.pdf.PdfVersion;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.kernel.pdf.ReaderProperties;
import com.itextpdf.kernel.pdf.VersionConforming;
import com.itextpdf.kernel.pdf.WriterProperties;
import com.itextpdf.kernel.utils.CompareTool;
import com.itextpdf.kernel.utils.PemFileHelper;
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 java.security.cert.CertificateException;
import java.util.HashMap;
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 PubSecHandlerUsingAesGcmTest extends ExtendedITextTest {
public static final String SOURCE_FOLDER =
"./src/test/resources/com/itextpdf/kernel/crypto/securityhandler/PubSecHandlerUsingAesGcmTest/";
public static final String DESTINATION_FOLDER =
TestUtil.getOutputPath() + "/kernel/crypto/securityhandler/PubSecHandlerUsingAesGcmTest/";
private static final IBouncyCastleFactory FACTORY = BouncyCastleFactoryCreator.getFactory();
private static final char[] PASSWORD = "testpassphrase".toCharArray();
@BeforeAll
public static void setUp() {
createOrClearDestinationFolder(DESTINATION_FOLDER);
Security.addProvider(FACTORY.getProvider());
}
@Test
public void testSimpleEncryptDecryptTest() throws Exception {
try {
BouncyCastleFactoryCreator.getFactory().isEncryptionFeatureSupported(0, true);
} catch (Exception ignored) {
Assumptions.assumeTrue(false);
}
Assumptions.assumeTrue(!BouncyCastleFactoryCreator.getFactory().isInApprovedOnlyMode());
String fileName = "simpleEncryptDecrypt.pdf";
String srcFile = SOURCE_FOLDER + fileName;
String outFile = DESTINATION_FOLDER + fileName;
doEncrypt(srcFile, outFile, true);
decryptWithCertificate(fileName, DESTINATION_FOLDER, "test.cer", "test.pem");
}
@LogMessages(messages = {@LogMessage(messageTemplate = VersionConforming.NOT_SUPPORTED_AES_GCM, ignore = true)})
@Test
public void testSimpleEncryptDecryptPdf17Test() throws Exception {
try {
BouncyCastleFactoryCreator.getFactory().isEncryptionFeatureSupported(0, true);
} catch (Exception ignored) {
Assumptions.assumeTrue(false);
}
Assumptions.assumeTrue(!BouncyCastleFactoryCreator.getFactory().isInApprovedOnlyMode());
String fileName = "simpleEncryptDecrypt_1_7.pdf";
String srcFile = SOURCE_FOLDER + fileName;
String outFile = DESTINATION_FOLDER + fileName;
doEncrypt(srcFile, outFile, false);
decryptWithCertificate(fileName, DESTINATION_FOLDER, "test.cer", "test.pem");
}
@Test
public void decryptExternalFileTest() throws Exception {
try {
BouncyCastleFactoryCreator.getFactory().isEncryptionFeatureSupported(0, true);
} catch (Exception ignored) {
Assumptions.assumeTrue(false);
}
decryptWithCertificate("externalFile.pdf", SOURCE_FOLDER, "decrypter.cert.pem", "signerkey.pem");
}
@Test
public void invalidCryptFilterTest() {
String fileName = "invalidCryptFilter.pdf";
Exception e = Assertions.assertThrows(PdfException.class,
() -> decryptWithCertificate(fileName, SOURCE_FOLDER, "test.cer", "test.pem"));
Assertions.assertEquals(KernelExceptionMessageConstant.NO_COMPATIBLE_ENCRYPTION_FOUND, e.getMessage());
}
@Test
public void encryptPdfWithMissingCFTest() throws Exception {
PrivateKey certificateKey = PemFileHelper.readPrivateKeyFromPemFile(
FileUtil.getInputStreamForFile(SOURCE_FOLDER + "signerkey.pem"), PASSWORD);
Certificate certificate = CryptoUtil.readPublicCertificate(
FileUtil.getInputStreamForFile(SOURCE_FOLDER + "decrypter.cert.pem"));
HashMap<PdfName, PdfObject> encMap = new HashMap<PdfName, PdfObject>();
encMap.put(PdfName.V, new PdfNumber(6));
encMap.put(PdfName.EncryptMetadata, PdfBoolean.TRUE);
PdfDictionary dictionary = new PdfDictionary(encMap);
Exception e = Assertions.assertThrows(PdfException.class, () -> new PdfEncryption(dictionary, certificateKey,
certificate, FACTORY.getProviderName(), null));
Assertions.assertEquals(KernelExceptionMessageConstant.CF_NOT_FOUND_ENCRYPTION, e.getMessage());
}
@Test
public void encryptPdfWithMissingDefaultCryptFilterTest() throws Exception {
PrivateKey certificateKey = PemFileHelper.readPrivateKeyFromPemFile(
FileUtil.getInputStreamForFile(SOURCE_FOLDER + "signerkey.pem"), PASSWORD);
Certificate certificate = CryptoUtil.readPublicCertificate(
FileUtil.getInputStreamForFile(SOURCE_FOLDER + "decrypter.cert.pem"));
HashMap<PdfName, PdfObject> encMap = new HashMap<PdfName, PdfObject>();
encMap.put(PdfName.V, new PdfNumber(6));
PdfDictionary embeddedFilesDict = new PdfDictionary();
embeddedFilesDict.put(PdfName.FlateDecode, new PdfDictionary());
encMap.put(PdfName.CF, embeddedFilesDict);
PdfDictionary dictionary = new PdfDictionary(encMap);
Exception e = Assertions.assertThrows(PdfException.class, () -> new PdfEncryption(dictionary, certificateKey,
certificate, FACTORY.getProviderName(), null));
Assertions.assertEquals(KernelExceptionMessageConstant.DEFAULT_CRYPT_FILTER_NOT_FOUND_ENCRYPTION,
e.getMessage());
}
@Test
public void encryptPdfWithMissingCFMTest() throws Exception {
PrivateKey certificateKey = PemFileHelper.readPrivateKeyFromPemFile(
FileUtil.getInputStreamForFile(SOURCE_FOLDER + "signerkey.pem"), PASSWORD);
Certificate certificate = CryptoUtil.readPublicCertificate(
FileUtil.getInputStreamForFile(SOURCE_FOLDER + "decrypter.cert.pem"));
HashMap<PdfName, PdfObject> encMap = new HashMap<PdfName, PdfObject>();
encMap.put(PdfName.V, new PdfNumber(6));
PdfDictionary embeddedFilesDict = new PdfDictionary();
embeddedFilesDict.put(PdfName.DefaultCryptFilter, new PdfDictionary());
encMap.put(PdfName.CF, embeddedFilesDict);
PdfDictionary dictionary = new PdfDictionary(encMap);
Exception e = Assertions.assertThrows(PdfException.class, () -> new PdfEncryption(dictionary, certificateKey,
certificate, FACTORY.getProviderName(), null));
Assertions.assertEquals(KernelExceptionMessageConstant.NO_COMPATIBLE_ENCRYPTION_FOUND, e.getMessage());
}
private void doEncrypt(String input, String output, boolean isPdf20) throws IOException, CertificateException {
Certificate certificate = CryptoUtil.readPublicCertificate(
FileUtil.getInputStreamForFile(SOURCE_FOLDER + "test.cer"));
WriterProperties writerProperties = new WriterProperties().setPublicKeyEncryption(
new Certificate[] {certificate}, new int[] {EncryptionConstants.ALLOW_PRINTING},
EncryptionConstants.ENCRYPTION_AES_GCM);
if (isPdf20) {
writerProperties.setPdfVersion(PdfVersion.PDF_2_0);
}
// Instantiate input/output document.
try (PdfDocument docIn = new PdfDocument(new PdfReader(input)); PdfDocument docOut = new PdfDocument(
new PdfWriter(output, writerProperties))) {
// Copy one page from input to output.
docIn.copyPagesTo(1, 1, docOut);
}
}
private void decryptWithCertificate(String fileName, String srcFileFolder,
String certificateName, String privateKeyName) throws Exception {
String srcFile = srcFileFolder + fileName;
String cmpFile = SOURCE_FOLDER + "cmp_" + fileName;
String outFile = DESTINATION_FOLDER + "decrypted_" + fileName;
Certificate certificate = CryptoUtil.readPublicCertificate(
FileUtil.getInputStreamForFile(SOURCE_FOLDER + certificateName));
PrivateKey privateKey = PemFileHelper.readPrivateKeyFromPemFile(
FileUtil.getInputStreamForFile(SOURCE_FOLDER + privateKeyName), PASSWORD);
ReaderProperties readerProperties = new ReaderProperties().setPublicKeySecurityParams(
certificate, privateKey, FACTORY.getProviderName(), null);
PdfDocument ignored = new PdfDocument(new PdfReader(srcFile, readerProperties), new PdfWriter(outFile));
ignored.close();
String errorMessage = new CompareTool().compareByContent(outFile, cmpFile, DESTINATION_FOLDER, "diff_");
if (errorMessage != null) {
Assertions.fail(errorMessage);
}
}
}