% Copyright (C) 1996-1998 Geoffrey Keating.
% Copyright (C) 2001-2008 Artifex Software, Inc.
% This file may be freely distributed with or without modifications,
% so long as modified versions are marked as such and copyright notices are
% not removed.
% $Id: pdf_sec.ps 9740 2009-05-13 23:37:28Z alexcher $
% Implementation of security hooks for PDF reader.
% This file contains the procedures that have to take encryption into
% account when reading a PDF file. It was originally distributed
% separately by Geoffrey Keating as an add-on to version 6 and earlier.
% Modified by Alex Cherepanov to work with GS 6.60 and higher.
% New versions of GS require explicit checks for /true, /false, and /null
% in .decpdfrun. This fix is backward-compatible.
% Modified by Raph Levien and Ralph Giles to use the new C
% implementations of md5 and arcfour in ghostscript 7.01, and to
% be compatible with PDF 1.4 128-bit encryption.
% Modified by Ralph Giles for PDF 1.6 AES encryption.
/.setlanguagelevel where { pop 2 .setlanguagelevel } if
.currentglobal true .setglobal
/pdfdict where { pop } { /pdfdict 100 dict def } ifelse
pdfdict begin
% Older ghostscript versions do not have .pdftoken, so we use 'token' instead.
/.pdftoken where { pop } { /.pdftoken /token load def } ifelse
% take a stream and arc4 decrypt it.
% arc4decodefilter
/arc4decodefilter {
1 dict begin
/Key exch def
currentdict end /ArcfourDecode filter
} bind def
% arc4decode
/arc4decode {
1 index length 0 eq {
pop
} {
1 index length string 3 1 roll arc4decodefilter exch readstring pop
} ifelse
} bind def
% take a stream and aes decrypt it.
% aesdecodefilter
/aesdecodefilter {
1 dict begin
/Key exch def
currentdict end
/AESDecode filter
} bind def
% aesdecode
/aesdecode {
1 index length 0 eq {
pop
} {
1 index length string 3 1 roll
aesdecodefilter exch readstring pop
} ifelse
} bind def
/md5 {
16 string dup /MD5Encode filter dup 4 3 roll writestring closefile
} bind def
/md5_trunk {
md5 0 pdf_key_length getinterval
} bind def
/pdf_padding_string
<28bf4e5e4e758a41 64004e56fffa0108
2e2e00b6d0683e80 2f0ca9fe6453697a>
def
% Pad a key out to 32 bytes.
/pdf_pad_key { % pdf_pad_key
dup length 32 gt { 0 32 getinterval } if
pdf_padding_string
0 32 3 index length sub getinterval
concatstrings
} bind def
/pdf_xorbytes { % pdf_xorbytes
dup length dup string
exch 1 sub 0 1 3 2 roll {
%
dup 3 index exch get 4 index xor
%
3 copy put pop pop
} for
3 1 roll pop pop
} bind def
% Get length of encryption key in bytes
/pdf_key_length { % pdf_key_length
Trailer /Encrypt oget
dup /V knownoget not { 0 } if 1 eq
{ pop 5 } % If V == 1 then always use 40 bits
{ /Length knownoget { -3 bitshift } { 5 } ifelse }
ifelse
} bind def
% Algorithm 3.2
/pdf_compute_encryption_key { % pdf_compute_encryption_key
% Step 1.
pdf_pad_key
% Step 2, 3.
Trailer /Encrypt oget dup /O oget
%
% Step 4.
exch /P oget 4 string exch
2 copy 255 and 0 exch put
2 copy -8 bitshift 255 and 1 exch put
2 copy -16 bitshift 255 and 2 exch put
2 copy -24 bitshift 255 and 3 exch put pop
%
% Step 5.
Trailer /ID knownoget { 0 oget } {
()
( **** ID key in the trailer is required for encrypted files.\n) pdfformaterror
} ifelse
3 { concatstrings } repeat
% We will finish step 5 after possibly including step 6.
% The following only executed for /R equal to 3 or more
Trailer /Encrypt oget dup /R oget dup 3 ge {
% Step 6. If EncryptMetadata is false, pass 0xFFFFFFFF to md5 function
% The PDF 1.5 Spec says that EncryptMetadata is an undocumented
% feature of PDF 1.4. That implies that this piece of logic should
% be executed if R >= 3. However testing with Acrobat 5.0 and 6.0 shows
% that this step is not executed if R equal to 3. Thus we have a test for
% R being >= 4.
4 ge {
/EncryptMetadata knownoget % Get EncryptMetadata (if present)
not { true } if % Default is true
not { % If EncryptMetadata is false
concatstrings % Add 0xFFFFFFFF to working string
} if
} {
pop % Remove Encrypt dict
} ifelse
md5_trunk % Finish step 5 and 6.
% Step 7. Executed as part of step 6
% Step 8. (This step is defintely a part of PDF 1.4.)
50 { md5_trunk } repeat
} {
pop pop md5_trunk % Remove R, Encrypt dict, finish step 5
} ifelse
% Step 9 - Done in md5_trunk.
} bind def
% Algorithm 3.4
/pdf_gen_user_password_R2 { % pdf_gen_user_password_R2
% Step 2.
pdf_padding_string exch arc4decode
} bind def
% Algorithm 3.5
/pdf_gen_user_password_R3 { % pdf_gen_user_password_R3
% Step 2.
pdf_padding_string
% Step 3.
Trailer /ID knownoget { 0 oget } {
()
( **** ID key in the trailer is required for encrypted files.\n) pdfformaterror
} ifelse
concatstrings md5
% Step 4.
1 index arc4decode
% Step 5.
1 1 19 {
2 index pdf_xorbytes arc4decode
} for
exch pop
} bind def
/pdf_gen_user_password { % pdf_gen_user_password
% common Step 1 of Algorithms 3.4 and 3.5.
pdf_compute_encryption_key dup
Trailer /Encrypt oget
/R oget dup 2 eq {
pop pdf_gen_user_password_R2
} {
dup 3 eq {
pop pdf_gen_user_password_R3
} {
dup 4 eq { % 4 uses the algorithm as 3
pop pdf_gen_user_password_R3
} {
( **** This file uses an unknown standard security handler revision: )
exch =string cvs concatstrings pdfformaterror printProducer
/pdf_check_user_password cvx /undefined signalerror
} ifelse
} ifelse
} ifelse
} bind def
% Algorithm 3.6
/pdf_check_user_password { % pdf_check_user_password true
% pdf_check_user_password false
pdf_gen_user_password
Trailer /Encrypt oget /U oget
0 2 index length getinterval eq {
true
} {
pop false
} ifelse
} bind def
% Compute an owner key, ie the result of step 4 of Algorithm 3.3
/pdf_owner_key % pdf_owner_key
{
% Step 1.
pdf_pad_key
% Step 2.
md5_trunk
% 3.3 Step 3. Only executed for /R equal to 3 or more
Trailer /Encrypt oget /R oget 3 ge {
50 { md5_trunk } repeat
} if
% Step 4 - Done in md5_trunk.
} bind def
% Algorithm 3.7
/pdf_check_owner_password { % pdf_check_owner_password true
% pdf_check_owner_password false
% Step 1.
pdf_owner_key
% Step 2.
Trailer /Encrypt oget dup /O oget 2 index arc4decode
%
% Step 3. Only executed for /R equal to 3 or more
exch /R oget 3 ge {
1 1 19 {
2 index pdf_xorbytes arc4decode
} for
} if
exch pop
%
pdf_check_user_password
} bind def
% Process the encryption information in the Trailer.
/pdf_process_Encrypt {
Trailer /Encrypt oget
/Filter oget /Standard eq not {
( **** This file uses an unknown security handler.\n) pdfformaterror
printProducer
/pdf_process_Encrypt cvx /undefined signalerror
} if
() pdf_check_user_password
{
/FileKey exch def
} {
/PDFPassword where {
pop PDFPassword pdf_check_user_password
{
/FileKey exch def
} {
PDFPassword pdf_check_owner_password
{
/FileKey exch def
} {
( **** Password did not work.\n) pdfformaterror
printProducer
/pdf_process_Encrypt cvx /invalidfileaccess signalerror
} ifelse
} ifelse
} {
( **** This file requires a password for access.\n) pdfformaterror
printProducer
/pdf_process_Encrypt cvx /invalidfileaccess signalerror
} ifelse
} ifelse
% Trailer /Encrypt oget /P oget 4 and 0 eq #? and
% { ( ****This owner of this file has requested you do not print it.\n)
% pdfformaterror printProducer
% /pdf_process_Encrypt cvx /invalidfileaccess signalerror
% }
% if
} bind def
% Calculate the key used to decrypt an object (to pass to .decpdfrun or
% put into a stream dictionary).
/computeobjkey %