Skip to content

Commit 0f5aeaf

Browse files
committed
Fixed issue with R5 encryption.
Refactored test-cases, added test-documents.
1 parent c1f6ecc commit 0f5aeaf

13 files changed

+263
-193
lines changed
17.4 KB
Binary file not shown.
Binary file not shown.
Binary file not shown.
23.5 KB
Binary file not shown.

PdfSharpCore.Test/IO/PdfReader.cs

Lines changed: 1 addition & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using FluentAssertions;
22
using PdfSharpCore.Pdf;
33
using PdfSharpCore.Pdf.IO;
4-
using PdfSharpCore.Pdf.IO.enums;
54
using PdfSharpCore.Test.Helpers;
65
using System;
76
using System.IO;
@@ -19,81 +18,6 @@ public void Should_beAbleToReadExistingPdf_When_inputIsStream()
1918
AssertIsAValidPdfDocumentWithProperties(inputDocument, 38148);
2019
}
2120

22-
[Fact]
23-
public void ShouldBeAbleToOpenAesEncryptedDocuments()
24-
{
25-
// this document has a V value of 4 (see PdfReference 1.7, Chapter 7.6.1, Table 20)
26-
// and an R value of 4 (see PdfReference 1.7, Chapter 7.6.3.2, Table 21)
27-
// see also: Adobe Supplement to the ISO 32000, BaseVersion: 1.7, ExtensionLevel: 3
28-
// Chapter 3.5.2, Table 3.19
29-
// TODO: find documents with V value of 5 and R values of 5 and 6
30-
var file = PathHelper.GetInstance().GetAssetPath("AesEncrypted.pdf");
31-
var fi = new FileInfo(file);
32-
var document = Pdf.IO.PdfReader.Open(file, PdfDocumentOpenMode.Import);
33-
34-
// verify document was actually AES-encrypted
35-
var cf = document.SecurityHandler.Elements.GetDictionary("/CF");
36-
var stdCf = cf.Elements.GetDictionary("/StdCF");
37-
stdCf.Elements.GetString("/CFM").Should().Be("/AESV2");
38-
39-
AssertIsAValidPdfDocumentWithProperties(document, (int)fi.Length);
40-
}
41-
42-
[Fact]
43-
public void CanReadDocumentThatWasSavedEncrypted()
44-
{
45-
// document that is known to be AES-encrypted
46-
var file = PathHelper.GetInstance().GetAssetPath("AesEncrypted.pdf");
47-
var document = Pdf.IO.PdfReader.Open(file, PdfDocumentOpenMode.Import);
48-
49-
// import pages into a new document
50-
var encryptedDoc = new PdfDocument();
51-
foreach (var page in document.Pages)
52-
encryptedDoc.AddPage(page);
53-
54-
// save enrypted
55-
encryptedDoc.SecuritySettings.OwnerPassword = "supersecret!11";
56-
var saveFileName = PathHelper.GetInstance().GetAssetPath("SavedEncrypted.pdf");
57-
encryptedDoc.Save(saveFileName);
58-
59-
// read back and check
60-
var fi = new FileInfo(saveFileName);
61-
var readBackDoc = Pdf.IO.PdfReader.Open(saveFileName, PdfDocumentOpenMode.Import);
62-
AssertIsAValidPdfDocumentWithProperties(readBackDoc, (int)fi.Length);
63-
readBackDoc.PageCount.Should().Be(document.PageCount);
64-
}
65-
66-
[Fact]
67-
public void DocumentWithUserPasswordCannotBeOpenedWithoutPassword()
68-
{
69-
var file = PathHelper.GetInstance().GetAssetPath("AesEncrypted.pdf");
70-
var document = Pdf.IO.PdfReader.Open(file, PdfDocumentOpenMode.Import);
71-
72-
// import pages into a new document
73-
var encryptedDoc = new PdfDocument();
74-
foreach (var page in document.Pages)
75-
encryptedDoc.AddPage(page);
76-
77-
// save enrypted
78-
encryptedDoc.SecuritySettings.UserPassword = "supersecret!11";
79-
var saveFileName = PathHelper.GetInstance().GetAssetPath("SavedEncrypted.pdf");
80-
encryptedDoc.Save(saveFileName);
81-
82-
// should throw because no password was provided
83-
var ex = Assert.Throws<PdfReaderException>(() =>
84-
{
85-
var readBackDoc = Pdf.IO.PdfReader.Open(saveFileName, PdfDocumentOpenMode.Import);
86-
});
87-
ex.Message.Should().Contain("A password is required to open the PDF document");
88-
89-
// check with password
90-
// TODO: should be checked in a separate test, but i was lazy...
91-
var fi = new FileInfo(saveFileName);
92-
var readBackDoc = Pdf.IO.PdfReader.Open(saveFileName, "supersecret!11", PdfDocumentOpenMode.Import);
93-
AssertIsAValidPdfDocumentWithProperties(readBackDoc, (int)fi.Length);
94-
readBackDoc.PageCount.Should().Be(document.PageCount);
95-
}
96-
9721
[Fact]
9822
public void WillThrowExceptionWhenReadingInvalidPdf()
9923
{
@@ -102,7 +26,7 @@ public void WillThrowExceptionWhenReadingInvalidPdf()
10226
act.Should().Throw<InvalidOperationException>().WithMessage("The file is not a valid PDF document.");
10327
}
10428

105-
private void AssertIsAValidPdfDocumentWithProperties(PdfDocument inputDocument, int expectedFileSize)
29+
internal static void AssertIsAValidPdfDocumentWithProperties(PdfDocument inputDocument, int expectedFileSize)
10630
{
10731
inputDocument.Should().NotBeNull();
10832
inputDocument.FileSize.Should().Be(expectedFileSize);

PdfSharpCore.Test/PdfSharpCore.Test.csproj

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,18 @@
4242
<None Update="Assets\NotAValid.pdf">
4343
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
4444
</None>
45+
<None Update="Assets\protected-adobe.pdf">
46+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
47+
</None>
48+
<None Update="Assets\protected-ilovepdf.pdf">
49+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
50+
</None>
51+
<None Update="Assets\protected-pdfencrypt.pdf">
52+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
53+
</None>
54+
<None Update="Assets\protected-sodapdf.pdf">
55+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
56+
</None>
4557
<None Update="Assets\test.pdf">
4658
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
4759
</None>

PdfSharpCore.Test/Security/PdfSecurity.cs

Lines changed: 93 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,24 @@
1-
using System.IO;
2-
using FluentAssertions;
1+
using FluentAssertions;
32
using PdfSharpCore.Drawing;
43
using PdfSharpCore.Pdf;
54
using PdfSharpCore.Pdf.IO;
65
using PdfSharpCore.Pdf.Security;
6+
using PdfSharpCore.Test.Helpers;
7+
using System.IO;
78
using Xunit;
9+
using Xunit.Abstractions;
810

911
namespace PdfSharpCore.Test.Security
1012
{
1113
public class PdfSecurity
1214
{
15+
private readonly ITestOutputHelper output;
16+
17+
public PdfSecurity(ITestOutputHelper testOutputHelper)
18+
{
19+
output = testOutputHelper;
20+
}
21+
1322
[Theory]
1423
[InlineData(PdfDocumentSecurityLevel.Encrypted40Bit, "hunter1")]
1524
[InlineData(PdfDocumentSecurityLevel.Encrypted128Bit, "hunter1")]
@@ -19,6 +28,8 @@ public void CreateAndReadPasswordProtectedPdf(PdfDocumentSecurityLevel securityL
1928
var pageNewRenderer = document.AddPage();
2029
var renderer = XGraphics.FromPdfPage(pageNewRenderer);
2130
renderer.DrawString("Test Test Test", new XFont("Arial", 12), XBrushes.Black, new XPoint(12, 12));
31+
// validate correct handling of unicode strings (issue #264)
32+
document.Outlines.Add("The only page", pageNewRenderer);
2233
document.SecuritySettings.DocumentSecurityLevel = securityLevel;
2334
document.SecuritySettings.UserPassword = password;
2435

@@ -30,6 +41,86 @@ public void CreateAndReadPasswordProtectedPdf(PdfDocumentSecurityLevel securityL
3041
delegate(PdfPasswordProviderArgs args) { args.Password = password; });
3142

3243
loadDocument.PageCount.Should().Be(1);
44+
loadDocument.Outlines[0].Title.Should().Be("The only page");
45+
loadDocument.Info.Producer.Should().Contain("PDFsharp");
46+
}
47+
48+
[Fact]
49+
public void ShouldBeAbleToOpenAesEncryptedDocuments()
50+
{
51+
// this document has a V value of 4 (see PdfReference 1.7, Chapter 7.6.1, Table 20)
52+
// and an R value of 4 (see PdfReference 1.7, Chapter 7.6.3.2, Table 21)
53+
// see also: Adobe Supplement to the ISO 32000, BaseVersion: 1.7, ExtensionLevel: 3
54+
// Chapter 3.5.2, Table 3.19
55+
var file = PathHelper.GetInstance().GetAssetPath("AesEncrypted.pdf");
56+
var fi = new FileInfo(file);
57+
var document = Pdf.IO.PdfReader.Open(file, PdfDocumentOpenMode.Import);
58+
59+
// verify document was actually AES-encrypted
60+
var cf = document.SecurityHandler.Elements.GetDictionary("/CF");
61+
var stdCf = cf.Elements.GetDictionary("/StdCF");
62+
stdCf.Elements.GetString("/CFM").Should().Be("/AESV2");
63+
64+
IO.PdfReader.AssertIsAValidPdfDocumentWithProperties(document, (int)fi.Length);
65+
}
66+
67+
[Fact]
68+
public void DocumentWithUserPasswordCannotBeOpenedWithoutPassword()
69+
{
70+
var file = PathHelper.GetInstance().GetAssetPath("AesEncrypted.pdf");
71+
var document = Pdf.IO.PdfReader.Open(file, PdfDocumentOpenMode.Import);
72+
73+
// import pages into a new document
74+
var encryptedDoc = new PdfDocument();
75+
foreach (var page in document.Pages)
76+
encryptedDoc.AddPage(page);
77+
78+
// save enrypted
79+
encryptedDoc.SecuritySettings.UserPassword = "supersecret!11";
80+
var saveFileName = PathHelper.GetInstance().GetAssetPath("SavedEncrypted.pdf");
81+
encryptedDoc.Save(saveFileName);
82+
83+
// should throw because no password was provided
84+
var ex = Assert.Throws<PdfReaderException>(() =>
85+
{
86+
var readBackDoc = Pdf.IO.PdfReader.Open(saveFileName, PdfDocumentOpenMode.Import);
87+
});
88+
ex.Message.Should().Contain("A password is required to open the PDF document");
89+
90+
// check with password
91+
// TODO: should be checked in a separate test, but i was lazy...
92+
var fi = new FileInfo(saveFileName);
93+
var readBackDoc = Pdf.IO.PdfReader.Open(saveFileName, "supersecret!11", PdfDocumentOpenMode.Import);
94+
IO.PdfReader.AssertIsAValidPdfDocumentWithProperties(readBackDoc, (int)fi.Length);
95+
readBackDoc.PageCount.Should().Be(document.PageCount);
96+
}
97+
98+
// Same PDF protected by different tools or online-services
99+
[Theory]
100+
// https://www.ilovepdf.com/protect-pdf, 128 bit, /V 2 /R 3
101+
[InlineData(@"protected-ilovepdf.pdf", "test123")]
102+
103+
// https://www.adobe.com/de/acrobat/online/password-protect-pdf.html, 128 bit, /V 4 /R 4
104+
[InlineData(@"protected-adobe.pdf", "test123")]
105+
106+
// https://pdfencrypt.net, 256 bit, /V 5 /R 5
107+
[InlineData(@"protected-pdfencrypt.pdf", "test123")]
108+
109+
// https://www.sodapdf.com/password-protect-pdf/
110+
// this is the only tool tested, that encrypts with the latest known algorithm (256 bit, /V 5 /R 6)
111+
// Note: SodaPdf also produced a pdf that would be considered "invalid" by PdfSharp, because of incorrect stream-lengths
112+
// (in the Stream-Dictionary, the length was reported as 32, but in fact the length was 16)
113+
// this needed to be handled as well
114+
[InlineData(@"protected-sodapdf.pdf", "test123")]
115+
public void CanReadPdfEncryptedWithSupportedAlgorithms(string fileName, string password)
116+
{
117+
var path = PathHelper.GetInstance().GetAssetPath(fileName);
118+
119+
var doc = Pdf.IO.PdfReader.Open(path, password, PdfDocumentOpenMode.Import);
120+
doc.Should().NotBeNull();
121+
doc.PageCount.Should().BeGreaterThan(0);
122+
output.WriteLine("Creator : {0}", doc.Info.Creator);
123+
output.WriteLine("Producer: {0}", doc.Info.Producer);
33124
}
34125
}
35126
}

0 commit comments

Comments
 (0)