Browse Source

Fix embedding of encrypted attachments

DEVSIX-9183

Autoported commit.
Original commit hash: [8095630bf]
pull/38/head
Vitali Prudnikovich 1 month ago
committed by iText Software
parent
commit
bc3b95dedb
  1. 29
      itext.tests/itext.kernel.tests/itext/kernel/crypto/pdfencryption/PdfEncryptionTest.cs
  2. 4
      itext.tests/itext.kernel.tests/itext/kernel/pdf/EncryptedEmbeddedStreamsHandlerTest.cs
  3. 125
      itext.tests/itext.kernel.tests/itext/kernel/pdf/PdfOutputStreamTest.cs
  4. 3
      itext/itext.kernel/itext/kernel/pdf/PdfName.cs
  5. 83
      itext/itext.kernel/itext/kernel/pdf/PdfOutputStream.cs
  6. 2
      port-hash

29
itext.tests/itext.kernel.tests/itext/kernel/crypto/pdfencryption/PdfEncryptionTest.cs

@ -337,6 +337,35 @@ namespace iText.Kernel.Crypto.Pdfencryption {
, textContent, ERROR_IS_EXPECTED);
}
[NUnit.Framework.Test]
public virtual void EncryptWithPasswordAes256EmbeddedFilesOnly2() {
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(System.Text.Encoding
.UTF8), 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.
bool ERROR_IS_EXPECTED = false;
encryptionUtil.CheckDecryptedWithPasswordContent(destinationFolder + filename, PdfEncryptionTestUtils.OWNER
, textContent, ERROR_IS_EXPECTED);
encryptionUtil.CheckDecryptedWithPasswordContent(destinationFolder + filename, PdfEncryptionTestUtils.USER
, textContent, ERROR_IS_EXPECTED);
}
[NUnit.Framework.Test]
public virtual void EncryptAes256Pdf2NotEncryptMetadata() {
String filename = "encryptAes256Pdf2NotEncryptMetadata.pdf";

4
itext.tests/itext.kernel.tests/itext/kernel/pdf/EncryptedEmbeddedStreamsHandlerTest.cs

@ -50,6 +50,7 @@ namespace iText.Kernel.Pdf {
[NUnit.Framework.Test]
[LogMessage(KernelLogMessageConstant.MD5_IS_NOT_FIPS_COMPLIANT, Ignore = true)]
public virtual void NoReaderStandardEncryptionAddFileAttachment() {
// Test result here is wrong. Note that createEncryptedDocument adds EncryptionConstants.EMBEDDED_FILES_ONLY.
String outFileName = destinationFolder + "noReaderStandardEncryptionAddFileAttachment.pdf";
String cmpFileName = sourceFolder + "cmp_noReaderStandardEncryptionAddFileAttachment.pdf";
PdfDocument pdfDocument = CreateEncryptedDocument(EncryptionConstants.STANDARD_ENCRYPTION_128, outFileName
@ -66,6 +67,7 @@ namespace iText.Kernel.Pdf {
[NUnit.Framework.Test]
[LogMessage(KernelLogMessageConstant.MD5_IS_NOT_FIPS_COMPLIANT, Ignore = true)]
public virtual void NoReaderAesEncryptionAddFileAttachment() {
// Test result here is wrong. Note that createEncryptedDocument adds EncryptionConstants.EMBEDDED_FILES_ONLY.
String outFileName = destinationFolder + "noReaderAesEncryptionAddFileAttachment.pdf";
String cmpFileName = sourceFolder + "cmp_noReaderAesEncryptionAddFileAttachment.pdf";
PdfDocument pdfDocument = CreateEncryptedDocument(EncryptionConstants.ENCRYPTION_AES_128, outFileName);
@ -101,6 +103,7 @@ namespace iText.Kernel.Pdf {
[NUnit.Framework.Test]
[LogMessage(KernelLogMessageConstant.MD5_IS_NOT_FIPS_COMPLIANT, Ignore = true)]
public virtual void NoReaderStandardEncryptionAddAnnotation() {
// Test result here is wrong. Note that createEncryptedDocument adds EncryptionConstants.EMBEDDED_FILES_ONLY.
String outFileName = destinationFolder + "noReaderStandardEncryptionAddAnnotation.pdf";
String cmpFileName = sourceFolder + "cmp_noReaderStandardEncryptionAddAnnotation.pdf";
PdfDocument pdfDocument = CreateEncryptedDocument(EncryptionConstants.STANDARD_ENCRYPTION_128, outFileName
@ -137,6 +140,7 @@ namespace iText.Kernel.Pdf {
[NUnit.Framework.Test]
[LogMessage(KernelLogMessageConstant.MD5_IS_NOT_FIPS_COMPLIANT, Ignore = true)]
public virtual void ReaderWithoutEncryptionWriterStandardEncryption() {
// Test result here is wrong. Note that createEncryptedDocument adds EncryptionConstants.EMBEDDED_FILES_ONLY.
String outFileName = destinationFolder + "readerWithoutEncryptionWriterStandardEncryption.pdf";
String cmpFileName = sourceFolder + "cmp_readerWithoutEncryptionWriterStandardEncryption.pdf";
PdfReader reader = new PdfReader(sourceFolder + "pdfWithUnencryptedAttachmentAnnotations.pdf");

125
itext.tests/itext.kernel.tests/itext/kernel/pdf/PdfOutputStreamTest.cs

@ -0,0 +1,125 @@
/*
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/>.
*/
using System;
using System.IO;
using iText.Commons.Utils;
using iText.Kernel.Crypto.Pdfencryption;
using iText.Kernel.Exceptions;
using iText.Kernel.Pdf.Filespec;
using iText.Kernel.Utils;
using iText.Test;
namespace iText.Kernel.Pdf {
[NUnit.Framework.Category("IntegrationTest")]
public class PdfOutputStreamTest : ExtendedITextTest {
public static readonly String DESTINATION_FOLDER = TestUtil.GetOutputPath() + "/kernel/pdf/PdfOutputStreamTest/";
[NUnit.Framework.OneTimeSetUp]
public static void BeforeClass() {
CreateOrClearDestinationFolder(DESTINATION_FOLDER);
}
[NUnit.Framework.Test]
public virtual void InvalidDecodeParamsTest() {
PdfWriter writer = new PdfWriter(new MemoryStream(), new WriterProperties().SetStandardEncryption(PdfEncryptionTestUtils
.USER, PdfEncryptionTestUtils.OWNER, 0, EncryptionConstants.ENCRYPTION_AES_256 | EncryptionConstants.EMBEDDED_FILES_ONLY
));
PdfDocument document = new PdfOutputStreamTest.CustomPdfDocument1(writer);
document.AddFileAttachment("descripton", PdfFileSpec.CreateEmbeddedFileSpec(document, "TEST".GetBytes(System.Text.Encoding
.UTF8), "descripton", "test.txt", null, null));
Exception e = NUnit.Framework.Assert.Catch(typeof(PdfException), () => document.Close());
NUnit.Framework.Assert.AreEqual(MessageFormatUtil.Format(KernelExceptionMessageConstant.THIS_DECODE_PARAMETER_TYPE_IS_NOT_SUPPORTED
, typeof(PdfName)), e.Message);
}
[NUnit.Framework.Test]
public virtual void ArrayDecodeParamsTest() {
String fileName = "arrayDecodeParamsTest.pdf";
PdfWriter writer = CompareTool.CreateTestPdfWriter(DESTINATION_FOLDER + fileName, new WriterProperties().SetStandardEncryption
(PdfEncryptionTestUtils.USER, PdfEncryptionTestUtils.OWNER, 0, EncryptionConstants.ENCRYPTION_AES_256
| EncryptionConstants.EMBEDDED_FILES_ONLY));
PdfDocument document = new PdfOutputStreamTest.CustomPdfDocument2(writer);
document.AddFileAttachment("descripton", PdfFileSpec.CreateEmbeddedFileSpec(document, "TEST".GetBytes(System.Text.Encoding
.UTF8), "descripton", "test.txt", null, null));
NUnit.Framework.Assert.DoesNotThrow(() => document.Close());
}
[NUnit.Framework.Test]
public virtual void DictDecodeParamsTest() {
String fileName = "dictDecodeParamsTest.pdf";
PdfWriter writer = CompareTool.CreateTestPdfWriter(DESTINATION_FOLDER + fileName, new WriterProperties().SetStandardEncryption
(PdfEncryptionTestUtils.USER, PdfEncryptionTestUtils.OWNER, 0, EncryptionConstants.ENCRYPTION_AES_256
| EncryptionConstants.EMBEDDED_FILES_ONLY));
PdfDocument document = new PdfOutputStreamTest.CustomPdfDocument3(writer);
document.AddFileAttachment("descripton", PdfFileSpec.CreateEmbeddedFileSpec(document, "TEST".GetBytes(System.Text.Encoding
.UTF8), "descripton", "test.txt", null, null));
NUnit.Framework.Assert.DoesNotThrow(() => document.Close());
}
private sealed class CustomPdfDocument1 : PdfDocument {
//\cond DO_NOT_DOCUMENT
internal CustomPdfDocument1(PdfWriter writer)
: base(writer) {
}
//\endcond
public override void MarkStreamAsEmbeddedFile(PdfStream stream) {
stream.Put(PdfName.DecodeParms, PdfName.Crypt);
stream.SetCompressionLevel(CompressionConstants.NO_COMPRESSION);
base.MarkStreamAsEmbeddedFile(stream);
}
}
private sealed class CustomPdfDocument2 : PdfDocument {
//\cond DO_NOT_DOCUMENT
internal CustomPdfDocument2(PdfWriter writer)
: base(writer) {
}
//\endcond
public override void MarkStreamAsEmbeddedFile(PdfStream stream) {
PdfArray decodeParmsValue = new PdfArray();
decodeParmsValue.Add(new PdfNull());
stream.Put(PdfName.DecodeParms, decodeParmsValue);
stream.SetCompressionLevel(CompressionConstants.NO_COMPRESSION);
base.MarkStreamAsEmbeddedFile(stream);
}
}
private sealed class CustomPdfDocument3 : PdfDocument {
//\cond DO_NOT_DOCUMENT
internal CustomPdfDocument3(PdfWriter writer)
: base(writer) {
}
//\endcond
public override void MarkStreamAsEmbeddedFile(PdfStream stream) {
PdfDictionary decodeParmsValue = new PdfDictionary();
decodeParmsValue.Put(PdfName.Name, new PdfNull());
stream.Put(PdfName.DecodeParms, decodeParmsValue);
stream.SetCompressionLevel(CompressionConstants.NO_COMPRESSION);
base.MarkStreamAsEmbeddedFile(stream);
}
}
}
}

3
itext/itext.kernel/itext/kernel/pdf/PdfName.cs

@ -421,6 +421,9 @@ namespace iText.Kernel.Pdf {
public static readonly iText.Kernel.Pdf.PdfName Crypt = CreateDirectName("Crypt");
public static readonly iText.Kernel.Pdf.PdfName CryptFilterDecodeParms = CreateDirectName("CryptFilterDecodeParms"
);
public static readonly iText.Kernel.Pdf.PdfName CS = CreateDirectName("CS");
public static readonly iText.Kernel.Pdf.PdfName CT = CreateDirectName("CT");

83
itext/itext.kernel/itext/kernel/pdf/PdfOutputStream.cs

@ -282,17 +282,25 @@ namespace iText.Kernel.Pdf {
Stream fout = this;
DeflaterOutputStream def = null;
OutputStreamEncryption ose = null;
long beginStreamContent;
if (crypto != null && (!crypto.IsEmbeddedFilesOnly() || document.DoesStreamBelongToEmbeddedFile(pdfStream)
)) {
UpdateCryptFilterForEmbeddedFilesOnlyMode(pdfStream);
// We should store current position here because crypto.getEncryptionStream(fout) may already
// output something into the stream (iv vector for AES256)
beginStreamContent = WritePdfStreamAndGetPosition(pdfStream);
fout = ose = crypto.GetEncryptionStream(fout);
}
if (toCompress && (allowCompression || userDefinedCompression)) {
UpdateCompressionFilter(pdfStream);
fout = def = new DeflaterOutputStream(fout, pdfStream.GetCompressionLevel(), 0x8000);
else {
if (toCompress && (allowCompression || userDefinedCompression)) {
UpdateCompressionFilter(pdfStream);
beginStreamContent = WritePdfStreamAndGetPosition(pdfStream);
fout = def = new DeflaterOutputStream(fout, pdfStream.GetCompressionLevel(), 0x8000);
}
else {
beginStreamContent = WritePdfStreamAndGetPosition(pdfStream);
}
}
this.Write((PdfDictionary)pdfStream);
WriteBytes(iText.Kernel.Pdf.PdfOutputStream.stream);
long beginStreamContent = GetCurrentPos();
byte[] buf = new byte[4192];
while (true) {
int n = pdfStream.GetInputStream().Read(buf);
@ -358,6 +366,7 @@ namespace iText.Kernel.Pdf {
}
}
if (CheckEncryption(pdfStream)) {
UpdateCryptFilterForEmbeddedFilesOnlyMode(pdfStream);
ByteArrayOutputStream encodedStream = new ByteArrayOutputStream();
OutputStreamEncryption ose = crypto.GetEncryptionStream(encodedStream);
byteArrayStream.WriteTo(ose);
@ -472,7 +481,61 @@ namespace iText.Kernel.Pdf {
}
}
}
pdfStream.Put(PdfName.Filter, filters);
}
/// <summary>
/// Adds required Filter and DecodeParms to the pdf stream if the stream is embedded file stream
/// and only embedded files are expected to be encrypted.
/// </summary>
/// <remarks>
/// Adds required Filter and DecodeParms to the pdf stream if the stream is embedded file stream
/// and only embedded files are expected to be encrypted. See
/// <see cref="EncryptionConstants.EMBEDDED_FILES_ONLY"/>.
/// </remarks>
/// <param name="pdfStream">embedded file pdf stream.</param>
protected internal virtual void UpdateCryptFilterForEmbeddedFilesOnlyMode(PdfStream pdfStream) {
if (crypto != null && crypto.IsEmbeddedFilesOnly() && document.DoesStreamBelongToEmbeddedFile(pdfStream) &&
// This code works only for AES256.
// All tests we currently have for earlier versions do not work with and without this code.
crypto.GetEncryptionAlgorithm() >= EncryptionConstants.ENCRYPTION_AES_256) {
// Filter
PdfObject currentFilters = pdfStream.Get(PdfName.Filter);
PdfArray filters = new PdfArray();
filters.Add(PdfName.Crypt);
if (currentFilters is PdfArray) {
filters.AddAll((PdfArray)currentFilters);
}
else {
if (currentFilters != null) {
filters.Add(currentFilters);
}
}
pdfStream.Put(PdfName.Filter, filters);
// DecodeParms
PdfDictionary crypt = new PdfDictionary();
crypt.Put(PdfName.Name, PdfName.StdCF);
crypt.Put(PdfName.Type, PdfName.CryptFilterDecodeParms);
PdfObject decodeParms = pdfStream.Get(PdfName.DecodeParms);
if (decodeParms is PdfDictionary || decodeParms == null) {
PdfArray array = new PdfArray();
array.Add(crypt);
if (decodeParms != null) {
array.Add(decodeParms);
}
pdfStream.Put(PdfName.DecodeParms, array);
}
else {
if (decodeParms is PdfArray) {
((PdfArray)decodeParms).Add(0, crypt);
}
else {
throw new PdfException(KernelExceptionMessageConstant.THIS_DECODE_PARAMETER_TYPE_IS_NOT_SUPPORTED).SetMessageParams
(decodeParms.GetType().ToString());
}
}
}
}
protected internal virtual byte[] DecodeFlateBytes(PdfStream stream, byte[] bytes) {
@ -586,6 +649,12 @@ namespace iText.Kernel.Pdf {
return bytes;
}
private long WritePdfStreamAndGetPosition(PdfStream pdfStream) {
Write((PdfDictionary)pdfStream);
WriteBytes(iText.Kernel.Pdf.PdfOutputStream.stream);
return GetCurrentPos();
}
private static bool IsFlushed(PdfDictionary dict, PdfName name) {
PdfObject obj = dict.Get(name);
return obj != null && obj.IsFlushed();

2
port-hash

@ -1 +1 @@
a1afcb7b8a55652a70fcb05cea0811c1108f9c2f
8095630bf37432a664f2d2e324dd14ceb795a1e7
Loading…
Cancel
Save