@@ -129,18 +129,10 @@ func rc4Crypt(key, data []byte) []byte {
129129 return out
130130}
131131
132- // buildRC4EncryptedStreamPDF builds a V2/R3 RC4-encrypted PDF (empty password)
133- // whose object 4 is a stream carrying RC4-encrypted plaintext.
134- func buildRC4EncryptedStreamPDF (t * testing.T , plaintext []byte ) []byte {
135- t .Helper ()
136- owner := bytes .Repeat ([]byte {0x5a }, 32 )
137- id0 := bytes .Repeat ([]byte {0x7c }, 16 )
138- const bits = 128
139- var p int32 = - 44
140- fileKey := emptyPwRC4Key (owner , id0 , p , bits )
141- u := emptyPwU (fileKey , id0 )
142- enc := rc4Crypt (objKeyRC4 (fileKey , 4 , 0 ), plaintext )
143-
132+ // assembleEncryptedPDF builds a classical-xref PDF — catalog (1), pages (2),
133+ // the given /Encrypt dict body (3), and an already-encrypted stream (4) — with
134+ // the trailer wired to /Encrypt 3 0 R and /ID [id0 id0].
135+ func assembleEncryptedPDF (encryptBody string , encStream , id0 []byte ) []byte {
144136 var buf bytes.Buffer
145137 off := func () int { return buf .Len () }
146138 fmt .Fprint (& buf , "%PDF-1.7\n %\xE2 \xE3 \xCF \xD3 \n " )
@@ -150,12 +142,10 @@ func buildRC4EncryptedStreamPDF(t *testing.T, plaintext []byte) []byte {
150142 offsets [2 ] = off ()
151143 fmt .Fprint (& buf , "2 0 obj\n << /Type /Pages /Count 0 /Kids [] >>\n endobj\n " )
152144 offsets [3 ] = off ()
153- fmt .Fprintf (& buf ,
154- "3 0 obj\n << /Filter /Standard /V 2 /R 3 /Length %d /O <%s> /U <%s> /P %d >>\n endobj\n " ,
155- bits , hex .EncodeToString (owner ), hex .EncodeToString (u ), p )
145+ fmt .Fprintf (& buf , "3 0 obj\n %s\n endobj\n " , encryptBody )
156146 offsets [4 ] = off ()
157- fmt .Fprintf (& buf , "4 0 obj\n << /Length %d >>\n stream\n " , len (enc ))
158- buf .Write (enc )
147+ fmt .Fprintf (& buf , "4 0 obj\n << /Length %d >>\n stream\n " , len (encStream ))
148+ buf .Write (encStream )
159149 fmt .Fprint (& buf , "\n endstream\n endobj\n " )
160150
161151 xrefOff := off ()
@@ -171,6 +161,40 @@ func buildRC4EncryptedStreamPDF(t *testing.T, plaintext []byte) []byte {
171161 return buf .Bytes ()
172162}
173163
164+ // buildRC4EncryptedStreamPDF builds a V2/R3 RC4-encrypted PDF (empty password)
165+ // whose object 4 is a stream carrying RC4-encrypted plaintext.
166+ func buildRC4EncryptedStreamPDF (t * testing.T , plaintext []byte ) []byte {
167+ t .Helper ()
168+ owner := bytes .Repeat ([]byte {0x5a }, 32 )
169+ id0 := bytes .Repeat ([]byte {0x7c }, 16 )
170+ const bits = 128
171+ var p int32 = - 44
172+ fileKey := emptyPwRC4Key (owner , id0 , p , bits )
173+ u := emptyPwU (fileKey , id0 )
174+ enc := rc4Crypt (objKeyRC4 (fileKey , 4 , 0 ), plaintext )
175+ body := fmt .Sprintf ("<< /Filter /Standard /V 2 /R 3 /Length %d /O <%s> /U <%s> /P %d >>" ,
176+ bits , hex .EncodeToString (owner ), hex .EncodeToString (u ), p )
177+ return assembleEncryptedPDF (body , enc , id0 )
178+ }
179+
180+ // buildV4RC4EncryptedStreamPDF builds a V4/R4 PDF whose StdCF crypt filter uses
181+ // CFM /V2 (RC4) — same empty-password key derivation as V2/R3, reached through
182+ // the V4 /CF + /StmF parsing path.
183+ func buildV4RC4EncryptedStreamPDF (t * testing.T , plaintext []byte ) []byte {
184+ t .Helper ()
185+ owner := bytes .Repeat ([]byte {0x5a }, 32 )
186+ id0 := bytes .Repeat ([]byte {0x7c }, 16 )
187+ const bits = 128
188+ var p int32 = - 44
189+ fileKey := emptyPwRC4Key (owner , id0 , p , bits )
190+ u := emptyPwU (fileKey , id0 )
191+ enc := rc4Crypt (objKeyRC4 (fileKey , 4 , 0 ), plaintext )
192+ body := fmt .Sprintf ("<< /Filter /Standard /V 4 /R 4 /Length %d /O <%s> /U <%s> /P %d " +
193+ "/CF << /StdCF << /CFM /V2 /Length 16 >> >> /StmF /StdCF /StrF /StdCF /EncryptMetadata true >>" ,
194+ bits , hex .EncodeToString (owner ), hex .EncodeToString (u ), p )
195+ return assembleEncryptedPDF (body , enc , id0 )
196+ }
197+
174198// Open must accept an RC4-encrypted PDF secured with the empty user password
175199// and decrypt its stream content end-to-end.
176200func TestOpenDecryptsRC4Stream (t * testing.T ) {
@@ -189,3 +213,75 @@ func TestOpenDecryptsRC4Stream(t *testing.T) {
189213 t .Fatalf ("decrypted stream mismatch:\n got %q\n want %q" , got , plaintext )
190214 }
191215}
216+
217+ // The V4 path resolves the stream cipher through /CF + /StmF rather than /V
218+ // directly, so it must be exercised end-to-end too.
219+ func TestOpenDecryptsV4RC4Stream (t * testing.T ) {
220+ plaintext := []byte ("BT (V4 crypt filter) Tj ET" )
221+ data := buildV4RC4EncryptedStreamPDF (t , plaintext )
222+ r , err := Open (bytes .NewReader (data ))
223+ if err != nil {
224+ t .Fatalf ("Open: %v" , err )
225+ }
226+ defer r .Close ()
227+ got , err := r .DecodeStream (Reference {Number : 4 , Generation : 0 })
228+ if err != nil {
229+ t .Fatalf ("DecodeStream: %v" , err )
230+ }
231+ if ! bytes .Equal (got , plaintext ) {
232+ t .Fatalf ("V4 decrypted stream mismatch:\n got %q\n want %q" , got , plaintext )
233+ }
234+ }
235+
236+ // buildPDFWithEncryptObj wraps an arbitrary /Encrypt dict body as object 3 of a
237+ // classical-xref PDF, with the trailer pointing /Encrypt at it.
238+ func buildPDFWithEncryptObj (t * testing.T , encryptBody string ) []byte {
239+ t .Helper ()
240+ var buf bytes.Buffer
241+ off := func () int { return buf .Len () }
242+ fmt .Fprint (& buf , "%PDF-1.7\n %\xE2 \xE3 \xCF \xD3 \n " )
243+ offsets := make ([]int , 4 )
244+ offsets [1 ] = off ()
245+ fmt .Fprint (& buf , "1 0 obj\n << /Type /Catalog /Pages 2 0 R >>\n endobj\n " )
246+ offsets [2 ] = off ()
247+ fmt .Fprint (& buf , "2 0 obj\n << /Type /Pages /Count 0 /Kids [] >>\n endobj\n " )
248+ offsets [3 ] = off ()
249+ fmt .Fprintf (& buf , "3 0 obj\n %s\n endobj\n " , encryptBody )
250+ xrefOff := off ()
251+ fmt .Fprint (& buf , "xref\n 0 4\n " )
252+ fmt .Fprintf (& buf , "%010d %05d f \n " , 0 , 65535 )
253+ for i := 1 ; i <= 3 ; i ++ {
254+ fmt .Fprintf (& buf , "%010d %05d n \n " , offsets [i ], 0 )
255+ }
256+ id := "<00112233445566778899aabbccddeeff>"
257+ fmt .Fprintf (& buf , "trailer\n << /Size 4 /Root 1 0 R /Encrypt 3 0 R /ID [%s %s] >>\n " , id , id )
258+ fmt .Fprintf (& buf , "startxref\n %d\n %%%%EOF\n " , xrefOff )
259+ return buf .Bytes ()
260+ }
261+
262+ // Only the /Standard security handler is supported; any other /Filter must be
263+ // rejected rather than silently treated as unencrypted.
264+ func TestEncryptNonStandardFilterRejected (t * testing.T ) {
265+ data := buildPDFWithEncryptObj (t , "<< /Filter /FooSecurity /V 2 /R 3 /Length 128 >>" )
266+ if _ , err := Open (bytes .NewReader (data )); err == nil {
267+ t .Fatal ("expected an error for a non-Standard /Filter, got nil" )
268+ }
269+ }
270+
271+ // Malformed /Encrypt dictionaries (wrong type, missing fields, short V5 entries)
272+ // must surface as errors during Open, never panics.
273+ func TestEncryptMalformedNoPanic (t * testing.T ) {
274+ cases := map [string ]string {
275+ "encrypt not a dict" : "42" ,
276+ "missing V and R" : "<< /Filter /Standard >>" ,
277+ "short O and U" : "<< /Filter /Standard /V 2 /R 3 /Length 128 /O <00> /U <00> /P 0 >>" ,
278+ "v5 short entries" : "<< /Filter /Standard /V 5 /R 6 /Length 256 /O <00> /U <00> /OE <00> /UE <00> /Perms <00> /P 0 >>" ,
279+ }
280+ for name , body := range cases {
281+ t .Run (name , func (t * testing.T ) {
282+ if _ , err := Open (bytes .NewReader (buildPDFWithEncryptObj (t , body ))); err == nil {
283+ t .Fatal ("expected an error, got nil" )
284+ }
285+ })
286+ }
287+ }
0 commit comments