]> git.lizzy.rs Git - plan9front.git/blob - sys/lib/ghostscript/pdf_sec.ps
webfs: preauth support
[plan9front.git] / sys / lib / ghostscript / pdf_sec.ps
1 %   Copyright (C) 1996-1998 Geoffrey Keating. 
2 %       Copyright (C) 2001 Artifex Software, Inc.
3 % This file may be freely distributed with or without modifications,
4 % so long as modified versions are marked as such and copyright notices are
5 % not removed.
6
7 % $Id: pdf_sec.ps,v 1.15 2004/03/12 01:55:58 dan Exp $
8 % Implementation of security hooks for PDF reader.
9
10 % This file contains the procedures that have to take encryption into
11 % account when reading a PDF file. It replaces the stub version of this
12 % file that is shipped with GhostScript. It requires GhostScript 7.01
13 % or later.
14
15 % Documentation for using this file is available at
16 % http://www.ozemail.com.au/%7Egeoffk/pdfencrypt/
17
18 % Modified by Alex Cherepanov to work with GS 6.60 and higher.
19 % New versions of GS require explicit checks for /true , /false, and /null
20 % in .decpdfrun . This fix is backward-compatible.
21
22 % Modified by Raph Levien and Ralph Giles to use the new C
23 % implementations of md5 and arcfour in ghostscript 7.01, and to
24 % be compatible with PDF 1.4 128-bit encryption.
25
26 /.setlanguagelevel where { pop 2 .setlanguagelevel } if
27 .currentglobal true .setglobal
28 /pdfdict where { pop } { /pdfdict 100 dict def } ifelse
29 pdfdict begin
30
31 % Older ghostscript versions do not have .pdftoken, so we use 'token' instead.
32 /.pdftoken where { pop } { /.pdftoken /token load def } ifelse
33
34 % take a stream and arc4 decrypt it.
35 % <stream> <key> arc4decodefilter <stream>
36 /arc4decodefilter {
37   1 dict begin
38   /Key exch def
39   currentdict end /ArcfourDecode filter
40 } bind def
41
42 % <ciphertext> <key> arc4decode <plaintext>
43 /arc4decode {
44   %(key: ) print dup == (ct: ) print 1 index ==
45   1 index length 0 eq {
46     pop
47   } {
48     1 index length string 3 1 roll arc4decodefilter exch readstring pop
49   } ifelse
50 } bind def
51
52 /md5 {
53   16 string dup /MD5Encode filter dup 4 3 roll writestring closefile
54 } bind def
55
56 /md5_trunk {
57   md5 0 pdf_key_length getinterval
58 } bind def
59
60
61 /pdf_padding_string
62    <28bf4e5e4e758a41 64004e56fffa0108
63     2e2e00b6d0683e80 2f0ca9fe6453697a>
64 def
65
66 % Pad a key out to 32 bytes.
67 /pdf_pad_key {          % <key> pdf_pad_key <padded key>
68   dup length 32 gt { 0 32 getinterval } if
69   pdf_padding_string
70   0 32 3 index length sub getinterval
71   concatstrings
72 } bind def
73
74 /pdf_xorbytes {      % <iter-num> <key> pdf_xorbytes <xored-key>
75   dup length dup string
76   exch 1 sub 0 1 3 2 roll {
77     % <iter-num> <key> <new-key> <byte-num>
78     dup 3 index exch get 4 index xor
79     % <iter-num> <key> <new-key> <byte-num> <byte>
80     3 copy put pop pop
81   } for
82   3 1 roll pop pop
83 } bind def
84
85 % Get length of encryption key in bytes
86 /pdf_key_length {    % pdf_key_length <key_length>
87   Trailer /Encrypt oget /Length knownoget { -3 bitshift } { 5 } ifelse
88 } bind def
89
90 % Algorithm 3.2
91 /pdf_compute_encryption_key {  % <password> pdf_compute_encryption_key <key>
92   % Step 1.
93   pdf_pad_key
94
95   % Step 2, 3.
96   Trailer /Encrypt oget dup /O oget
97   % <padded-key> <encrypt> <O>
98
99   % Step 4.
100   exch /P oget 4 string exch
101   2 copy 255 and 0 exch put
102   2 copy -8 bitshift 255 and 1 exch put
103   2 copy -16 bitshift 255 and 2 exch put
104   2 copy -24 bitshift 255 and 3 exch put pop
105   % <padded-key> <O> <P>
106
107   % Step 5.
108   Trailer /ID knownoget { 0 oget } {
109     ()
110     (   **** ID key in the trailer is required for encrypted files.\n) pdfformaterror
111   } ifelse
112   3 { concatstrings } repeat 
113   % We will finish step 5 after possibly including step 6.
114
115   % The following only executed for /R equal to 3 or more
116   Trailer /Encrypt oget dup /R oget dup 3 ge {
117
118      % Step 6.  If EncryptMetadata is false, pass 0xFFFFFFFF to md5 function
119      % The PDF 1.5 Spec says that EncryptMetadata is an undocumented
120      % feature of PDF 1.4.  That implies that this piece of logic should
121      % be executed if R >= 3.  However testing with Acrobat 5.0 and 6.0 shows
122      % that this step is not executed if R equal to 3.  Thus we have a test for
123      % R being >= 4.
124      4 ge {
125        /EncryptMetadata knownoget       % Get EncryptMetadata (if present)
126        not { true } if                  % Default is true
127        not {                            % If EncryptMetadata is false
128          <ff ff ff ff> concatstrings    % Add 0xFFFFFFFF to working string
129        } if
130      } {
131        pop                              % Remove Encrypt dict
132      } ifelse
133      md5_trunk                          % Finish step 5 and 6.
134
135      % Step 7.  Executed as part of step 6
136      % Step 8.  (This step is defintely a part of PDF 1.4.)
137      50 { md5_trunk } repeat
138   } {
139      pop pop md5_trunk                  % Remove R, Encrypt dict, finish step 5
140   } ifelse
141
142   % Step 9 - Done in md5_trunk.
143 } bind def
144
145 % Algorithm 3.4
146 /pdf_gen_user_password_R2 { % <filekey> pdf_gen_user_password_R2 <U>
147
148   % Step 2.
149   pdf_padding_string exch arc4decode
150 } bind def
151
152 % Algorithm 3.5
153 /pdf_gen_user_password_R3 { % <filekey> pdf_gen_user_password_R3 <U>
154
155   % Step 2.
156   pdf_padding_string
157
158   % Step 3.
159   Trailer /ID knownoget { 0 oget } {
160     ()
161     (   **** ID key in the trailer is required for encrypted files.\n) pdfformaterror
162   } ifelse
163   concatstrings md5
164
165   % Step 4.
166   1 index arc4decode
167
168   % Step 5.
169   1 1 19 {
170     2 index pdf_xorbytes arc4decode
171   } for
172   exch pop
173
174 } bind def
175
176 /pdf_gen_user_password { % <password> pdf_gen_user_password <filekey> <U>
177   % common Step 1 of Algorithms 3.4 and 3.5.
178   pdf_compute_encryption_key dup
179
180   Trailer /Encrypt oget
181
182   /R oget dup 2 eq {
183     pop pdf_gen_user_password_R2
184   } {
185     dup 3 eq {
186       pop pdf_gen_user_password_R3
187     } {
188       dup 4 eq {        % 4 uses the algorithm as 3
189         pop pdf_gen_user_password_R3
190       } {
191         (   **** This file uses an unknown standard security handler revision: )
192         exch =string cvs concatstrings pdfformaterror printProducer
193         /pdf_check_user_password cvx /undefined signalerror
194       } ifelse
195     } ifelse
196   } ifelse
197 } bind def
198
199 % Algorithm 3.6
200 /pdf_check_user_password { % <password> pdf_check_user_password <filekey> true
201                            % <password> pdf_check_user_password false
202   pdf_gen_user_password
203
204   Trailer /Encrypt oget /U oget
205
206   0 2 index length getinterval eq {
207     true
208   } {
209     pop false
210   } ifelse
211 } bind def
212
213 % Compute an owner key, ie the result of step 4 of Algorithm 3.3
214 /pdf_owner_key % <password> pdf_owner_key <owner-key>
215 {
216   % Step 1.
217   pdf_pad_key
218
219   % Step 2.
220   md5_trunk
221
222   % 3.3 Step 3.  Only executed for /R equal to 3 or more
223   Trailer /Encrypt oget /R oget 3 ge {
224     50 { md5_trunk } repeat
225   } if
226
227   % Step 4 - Done in md5_trunk.
228 } bind def
229
230 % Algorithm 3.7
231 /pdf_check_owner_password { % <password> pdf_check_owner_password <filekey> true
232                             % <password> pdf_check_owner_password false
233   % Step 1.
234   pdf_owner_key
235
236   % Step 2.
237   Trailer /Encrypt oget dup /O oget 2 index arc4decode
238   % <encryption-key> <encrypt-dict> <decrypted-O>
239
240   % Step 3.  Only executed for /R equal to 3 or more
241   exch /R oget 3 ge {
242     1 1 19 {
243       2 index pdf_xorbytes arc4decode
244     } for
245   } if
246   exch pop
247   % <result-of-step-3>
248
249   pdf_check_user_password
250 } bind def
251
252 % Process the encryption information in the Trailer.
253 /pdf_process_Encrypt {
254   Trailer /Encrypt oget
255   /Filter oget /Standard eq not {
256     (   **** This file uses an unknown security handler.\n) pdfformaterror
257     printProducer
258     /pdf_process_Encrypt cvx /undefined signalerror
259   } if
260   () pdf_check_user_password
261   {
262     /FileKey exch def
263   } {
264     /PDFPassword where {
265        pop PDFPassword pdf_check_user_password
266        {
267          /FileKey exch def
268        } {
269          PDFPassword pdf_check_owner_password
270          {
271            /FileKey exch def
272          } {
273            (   **** Password did not work.\n) pdfformaterror
274            printProducer
275            /pdf_process_Encrypt cvx /invalidfileaccess signalerror
276          } ifelse
277        } ifelse
278     } {
279       (   **** This file requires a password for access.\n) pdfformaterror
280       printProducer
281       /pdf_process_Encrypt cvx /invalidfileaccess signalerror
282     } ifelse
283   } ifelse
284
285 %   Trailer /Encrypt oget /P oget 4 and 0 eq #? and
286 %    { (   ****This owner of this file has requested you do not print it.\n)
287 %      pdfformaterror printProducer
288 %      /pdf_process_Encrypt cvx /invalidfileaccess signalerror
289 %    }
290 %   if
291 } bind def
292
293 % Calculate the key used to decrypt an object (to pass to .decpdfrun or
294 % put into a stream dictionary).
295 /computeobjkey  % <object#> <generation#> computeobjkey <keystring>
296 {
297   exch
298   FileKey length 5 add string
299   dup 0 FileKey putinterval
300   exch
301                 % stack:  gen# string obj#
302     2 copy 255 and FileKey length exch put
303     2 copy -8 bitshift 255 and FileKey length 1 add exch put
304     2 copy -16 bitshift 255 and FileKey length 2 add exch put
305   pop exch
306     2 copy 255 and FileKey length 3 add exch put
307     2 copy -8 bitshift 255 and FileKey length 4 add exch put
308   pop md5 0 FileKey length 5 add 2 index length .min getinterval
309 } bind def
310
311 % As .pdfrun, but decrypt strings with key <key>.
312 /.decpdfrun                     % <file> <keystring> <opdict> .decpdfrun -
313  {      % Construct a procedure with the file, opdict and key bound into it.
314    2 index cvlit mark mark 5 2 roll
315     { .pdftoken not { (%%EOF) cvn cvx } if
316       dup xcheck
317        { PDFDEBUG { dup == flush } if
318          3 -1 roll pop
319          2 copy .knownget
320           { exch pop exch pop exec
321           }
322           { exch pop
323             dup /true eq
324               { pop //true
325               }
326               { dup /false eq
327                   { pop //false 
328                   }
329                   { dup /null eq
330                       { pop //null
331                       }
332                       { (   **** Unknown operator: ) 
333                         exch =string cvs concatstrings (\n) concatstrings
334                         pdfformaterror
335                       }
336                     ifelse
337                   }
338                 ifelse
339               }
340             ifelse
341           }
342          ifelse
343        }
344        { exch pop PDFDEBUG { dup ==only ( ) print flush } if
345          dup type /stringtype eq
346           {
347         % Check if we have encrypted strings  R=4 allows for
348         % selection of encryption on streams and strings
349             Trailer /Encrypt oget       % Get encryption dictionary
350             dup /R oget 4 lt            % only 4 has selectable
351              {                          % R < 4 --> encrypted strings
352                pop 1 index arc4decode   % Decrypt string
353                PDFDEBUG { (%Decrypted: ) print dup == flush } if
354              } {                        % Else R = 4
355                /StrF knownoget          % Get StrF (if present)
356                 {                       % If StrF is present ...
357                   /Identity eq not      % Check if StrF != Identity
358                    { 1 index arc4decode % Decrypt string
359                      PDFDEBUG { (%Decrypted: ) print dup == flush } if
360                    }
361                   if                    % If StrF != identity
362                 }
363                if                       % If StrF is known
364              }
365             ifelse                      % Ifelse R < 4
366           }
367          if                             % If  = stringtype
368          exch pop
369        }
370       ifelse
371     }
372    aload pop .packtomark cvx
373    /loop cvx 2 packedarray cvx
374     { stopped /PDFsource } aload pop
375    PDFsource
376     { store { stop } if } aload pop .packtomark cvx 
377    /PDFsource 3 -1 roll store exec
378  } bind def
379
380 % Run the code to resolve an object reference.
381 /pdf_run_resolve
382 { /FileKey where                        % Check if the file is encrypted
383   { pop                                 % File is encrypted
384     2 copy computeobjkey dup 4 1 roll
385     PDFfile exch resolveopdict .decpdfrun
386     dup dup dup 5 2 roll
387         % stack: object object key object object
388     {   % Use loop to provide an exitable context.
389       xcheck exch type /dicttype eq and % Check if executable dictionary
390       not {                             % If object is not ...
391         pop pop                         % ignore object
392         exit                            % Exit 'loop' context
393       } if                              % If not possible stream
394         % Starting with PDF 1.4 (R = 3), there are some extra features
395         % which control encryption of streams.  The EncryptMetadata entry
396         % in the Encrypt dict controls the encryption of metadata streams.
397       Trailer /Encrypt oget             % Get encryption dictionary
398       dup /R oget dup 3 lt              % Only PDF 1.4 and higher has options
399       {                                 % R < 3 --> all streams encrypted
400         pop pop /StreamKey exch put     % Insert StreamKey in dictionary
401         exit                            % Exit 'loop' context
402       } if
403         % Check EncryptMeta.  stack:  object object key Encrypt R
404       exch dup /EncryptMetadata knownoget % Get EncryptMetadata (if present)
405       not { true } if                   % If not present default = true
406       not                               % Check if EncryptMetadata = false
407       {                                 % if false we need to check the stream type
408         3 index /Type knownoget         % Get stream type (if present)
409         not { //null } if               % If type not present use fake name
410         /Metadata eq                    % Check if the type is Metadata
411         { pop pop pop pop               % Type == Metadata --> no encryption
412           exit                          % Exit 'loop' context
413         } if
414       } if
415         % PDF 1.5 encryption (R == 4) has selectable encryption handlers.  If
416         % this is not PDF 1.5 encryption (R < 4) then we are done checking and
417         % we need to decrypt the stream.  stack:  object object key R Encrypt
418       exch 4 lt                         % Check for less than PDF 1.5
419       { pop /StreamKey exch put         % Insert StreamKey in dictionary
420         exit                            % Exit 'loop' context
421       } if
422         % Check if the stream encryption handler (StmF) == Identity.
423       /StmF knownoget                   % Get StmF (if present)
424       not { /Identity } if              % If StmF not present default = Identity
425       /Identity eq                      % Check if StmF == Identity
426       { pop pop                         % Identity --> no encryption
427         exit                            % Exit 'loop' context
428       } if
429         % If we get here then we need to decrypt the stream.
430       /StreamKey exch put               % Insert StreamKey into dictionary
431       exit                              % Exit 'loop' context, never loop
432     } loop                              % End of loop exitable context
433   } {                                   % Else file is not encrypted
434     PDFfile resolveopdict .pdfrun
435   } ifelse                              % Ifelse encrypted
436 } bind def
437
438 % Prefix a decryption filter to a stream if needed.
439 % Stack: readdata? dict parms file/string filternames
440 % (both before and after).
441 /pdf_decrypt_stream
442  { 3 index /StreamKey known     % Check if the file is encrypted
443    {
444       exch 
445         % Stack: readdata? dict parms filternames file/string
446       3 index /Length oget
447       dup 0 eq {
448         % Handle Length=0 case specially to avoid SubFileDecode semantics
449         pop pop ()
450       } {
451         () /SubFileDecode filter
452       } ifelse
453       3 index /StreamKey get arc4decodefilter
454       exch
455    } if
456  } bind def
457
458 end                     % pdfdict
459 .setglobal