UnicodeBasedPasswordEncryptionTest.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.kernel.logs.KernelLogMessageConstant;
import com.itextpdf.kernel.pdf.EncryptionConstants;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfPage;
import com.itextpdf.kernel.pdf.PdfVersion;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.kernel.pdf.WriterProperties;
import com.itextpdf.kernel.utils.CompareTool;
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.nio.charset.StandardCharsets;
import java.security.Security;
import java.util.LinkedHashMap;
import java.util.Map;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.fail;
@Tag("BouncyCastleIntegrationTest")
public class UnicodeBasedPasswordEncryptionTest extends ExtendedITextTest {
public static final String destinationFolder = TestUtil.getOutputPath() + "/kernel/crypto/pdfencryption/UnicodeBasedPasswordEncryptionTest/";
public static final String sourceFolder = "./src/test/resources/com/itextpdf/kernel/crypto/pdfencryption/UnicodeBasedPasswordEncryptionTest/";
private static Map<String, SaslPreparedString> nameToSaslPrepared;
PdfEncryptionTestUtils encryptionUtil = new PdfEncryptionTestUtils(destinationFolder,sourceFolder);
static {
// values are calculated with com.ibm.icu.text.StringPrep class in icu4j v58.2 lib
nameToSaslPrepared = new LinkedHashMap<>();
//������������
nameToSaslPrepared.put("arabic01",
new SaslPreparedString("\u0627\u0644\u0631\u062D\u064A\u0645",
"\u0627\u0644\u0631\u062D\u064A\u0645"));
//����,��11������
nameToSaslPrepared.put("arabic02",
new SaslPreparedString("\u0627\u0644,\u063111\u062D\u064A\u0645",
"\u0627\u0644,\u063111\u062D\u064A\u0645"));
// ������
nameToSaslPrepared.put("arabic03",
new SaslPreparedString("\u0644\u0640\u0647",
"\u0644\u0640\u0647"));
// ���
nameToSaslPrepared.put("arabic04",
new SaslPreparedString("\ufefb",
"\u0644\u0627"));
// ����
nameToSaslPrepared.put("arabic05",
new SaslPreparedString("\u0644\u0627",
"\u0644\u0627"));
// ��������������� ������������������������
nameToSaslPrepared.put("devanagari01",
new SaslPreparedString("\u0936\u093e\u0902\u0924\u093f \u0926\u0947\u0935\u0928\u093E\u0917\u0930\u0940",
"\u0936\u093E\u0902\u0924\u093F \u0926\u0947\u0935\u0928\u093E\u0917\u0930\u0940"));
// ������ ���������������������������
nameToSaslPrepared.put("devanagari02",
new SaslPreparedString("\u0915\u0940 \u092A\u094D\u0930\u093E\u091A\u0940\u0928\u0924\u092E",
"\u0915\u0940 \u092A\u094D\u0930\u093E\u091A\u0940\u0928\u0924\u092E"));
// ��������������� ���������������
nameToSaslPrepared.put("gurmukhi01",
new SaslPreparedString("\u0A17\u0A4D\u0A30\u0A70\u0A25 \u0A38\u0A3E\u0A39\u0A3F\u0A2C",
"\u0A17\u0A4D\u0A30\u0A70\u0A25 \u0A38\u0A3E\u0A39\u0A3F\u0A2C"));
// ������������
nameToSaslPrepared.put("khmer01",
new SaslPreparedString("\u1789\u17D2\u1785\u17BC",
"\u1789\u17D2\u1785\u17BC"));
//��������������������� ��������� ��������� ������������������������
nameToSaslPrepared.put("tamil01",
new SaslPreparedString("\u0B87\u0BB2\u0B95\u0BCD\u0B95\u0BBF\u0BAF \u0BA8\u0B9F\u0BC8 \u0B95\u0BC2\u0B9F \u0BAE\u0B95\u0BCD\u0B95\u0BB3\u0BBE\u0BB2\u0BCD",
"\u0B87\u0BB2\u0B95\u0BCD\u0B95\u0BBF\u0BAF \u0BA8\u0B9F\u0BC8 \u0B95\u0BC2\u0B9F \u0BAE\u0B95\u0BCD\u0B95\u0BB3\u0BBE\u0BB2\u0BCD"));
// ���������������������������
nameToSaslPrepared.put("thai01",
new SaslPreparedString("\u0E1B\u0E23\u0E30\u0E40\u0E17\u0E28\u0E44\u0E17\u0E22",
"\u0E1B\u0E23\u0E30\u0E40\u0E17\u0E28\u0E44\u0E17\u0E22"));
nameToSaslPrepared.put("unicodeBom01",
new SaslPreparedString("\uFEFFab\uFEFFc",
"abc"));
nameToSaslPrepared.put("emoji01",
new SaslPreparedString("\u267B",
"\u267B"));
nameToSaslPrepared.put("rfc4013Example01",
new SaslPreparedString("I\u00ADX",
"IX"));
nameToSaslPrepared.put("rfc4013Example02",
new SaslPreparedString("user",
"user"));
nameToSaslPrepared.put("rfc4013Example03",
new SaslPreparedString("\u00AA",
"a"));
// match rfc4013Example01
nameToSaslPrepared.put("rfc4013Example04",
new SaslPreparedString("\u2168",
"IX"));
nameToSaslPrepared.put("nonAsciiSpace01",
new SaslPreparedString("\u2008 \u2009 \u200A \u200B",
" "));
// normalization tests
nameToSaslPrepared.put("nfkcNormalization01",
new SaslPreparedString("\u09C7\u09BE",
"\u09CB"));
nameToSaslPrepared.put("nfkcNormalization02",
new SaslPreparedString("\u30AD\u3099\u30AB\u3099",
"\u30AE\u30AC"));
nameToSaslPrepared.put("nfkcNormalization03",
new SaslPreparedString("\u3310",
"\u30AE\u30AC"));
nameToSaslPrepared.put("nfkcNormalization04",
new SaslPreparedString("\u1100\u1161\u11A8",
"\uAC01"));
nameToSaslPrepared.put("nfkcNormalization05",
new SaslPreparedString("\uF951",
"\u964B"));
/*
// Arabic
bidirectional check fail: "\u0627\u0644\u0631\u0651\u064E\u200C\u062D\u0652\u0645\u064E\u0640\u0670\u0646\u0650"
bidirectional check fail: "1\u0627\u0644\u0631\u062D\u064A\u06452"
// RFC4013 examples
bidirectional check fail: "\u0627\u0031"
prohibited character fail: "\u0007"
// unassigned code point for Unicode 3.2
"\uD83E\uDD14"
"\u038Ba\u038Db\u03A2c\u03CF"
*/
}
private static class SaslPreparedString {
String unicodeInputString;
String preparedString;
SaslPreparedString(String unicodeInputString, String preparedString) {
this.unicodeInputString = unicodeInputString;
this.preparedString = preparedString;
}
}
@BeforeAll
public static void before() {
Security.addProvider(BouncyCastleFactoryCreator.getFactory().getProvider());
createOrClearDestinationFolder(destinationFolder);
}
@AfterAll
public static void afterClass() {
CompareTool.cleanup(destinationFolder);
}
@Test
@LogMessages(messages = @LogMessage(messageTemplate = KernelLogMessageConstant.MD5_IS_NOT_FIPS_COMPLIANT,
ignore = true))
public void aes256EncryptedPdfWithUnicodeBasedPassword() throws IOException, InterruptedException {
String fileNameTemplate = "unicodePassword_";
for (Map.Entry<String, SaslPreparedString> entry : nameToSaslPrepared.entrySet()) {
String filename = fileNameTemplate + entry.getKey() + ".pdf";
byte[] ownerPassword = entry.getValue().preparedString.getBytes(StandardCharsets.UTF_8);
encryptAes256AndCheck(filename, ownerPassword);
}
}
// TODO after DEVSIX-1220 finished:
// 1. Create with both inputString and prepareString.
// 1.1 Check opening both of these documents with both strings.
// 2. Try encrypt document with invalid input string.
// 3. Try open encrypted document with password that contains unassigned code points and ensure error is due to wrong password instead of the invalid input string.
private void encryptAes256AndCheck(String filename, byte[] ownerPassword) throws IOException, InterruptedException {
int permissions = EncryptionConstants.ALLOW_SCREENREADERS;
WriterProperties writerProperties = new WriterProperties()
.setStandardEncryption(PdfEncryptionTestUtils.USER, ownerPassword, permissions, EncryptionConstants.ENCRYPTION_AES_256)
.setPdfVersion(PdfVersion.PDF_2_0);
PdfWriter writer = CompareTool.createTestPdfWriter(destinationFolder + filename, writerProperties.addXmpMetadata());
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.checkDecryptedWithPasswordContent(destinationFolder + filename, ownerPassword, PdfEncryptionTestUtils.PAGE_TEXT_CONTENT);
CompareTool compareTool = new CompareTool().enableEncryptionCompare(false);
String compareResult = compareTool.compareByContent(destinationFolder + filename, sourceFolder + "cmp_" + filename, destinationFolder, "diff_", ownerPassword, ownerPassword);
if (compareResult != null) {
fail(compareResult);
}
}
}