1- using System . IO ;
2- using FluentAssertions ;
1+ using FluentAssertions ;
32using PdfSharpCore . Drawing ;
43using PdfSharpCore . Pdf ;
54using PdfSharpCore . Pdf . IO ;
65using PdfSharpCore . Pdf . Security ;
6+ using PdfSharpCore . Test . Helpers ;
7+ using System . IO ;
78using Xunit ;
9+ using Xunit . Abstractions ;
810
911namespace 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