File Coverage

blib/lib/CAM/PDF/Decrypt.pm
Criterion Covered Total %
statement 189 238 79.4
branch 40 66 60.6
condition 13 29 44.8
subroutine 23 24 95.8
pod 6 6 100.0
total 271 363 74.6


line stmt bran cond sub pod time code
1             package CAM::PDF::Decrypt;
2              
3             # These are included at runtime via eval below
4             # use Digest::MD5;
5             # use Crypt::RC4;
6              
7 3     3   51 use 5.006;
  3         9  
  3         112  
8 3     3   15 use warnings;
  3         5  
  3         87  
9 3     3   19 use strict;
  3         5  
  3         77  
10 3     3   15 use Carp;
  3         4  
  3         220  
11 3     3   15 use English qw(-no_match_vars);
  3         4  
  3         15  
12 3     3   1406 use CAM::PDF;
  3         6  
  3         65  
13 3     3   14 use CAM::PDF::Node;
  3         5  
  3         8657  
14              
15             our $VERSION = '1.60';
16              
17             =for stopwords Decrypt
18              
19             =head1 NAME
20              
21             CAM::PDF::Decrypt - PDF security helper
22              
23             =head1 LICENSE
24              
25             See CAM::PDF.
26              
27             =head1 SYNOPSIS
28              
29             use CAM::PDF;
30             my $pdf = CAM::PDF->new($filename);
31              
32             =head1 DESCRIPTION
33              
34             This class is used invisibly by CAM::PDF whenever it detects that a
35             document is encrypted. See new(), getPrefs() and setPrefs() in that
36             module.
37              
38             =cut
39              
40             # These constants come from the Adobe PDF Reference document, IIRC
41             my $padding = pack 'C*',
42             0x28, 0xbf, 0x4e, 0x5e,
43             0x4e, 0x75, 0x8a, 0x41,
44             0x64, 0x00, 0x4e, 0x56,
45             0xff, 0xfa, 0x01, 0x08,
46             0x2e, 0x2e, 0x00, 0xb6,
47             0xd0, 0x68, 0x3e, 0x80,
48             0x2f, 0x0c, 0xa9, 0xfe,
49             0x64, 0x53, 0x69, 0x7a;
50              
51              
52             =head1 FUNCTIONS
53              
54             =over
55              
56             =item $pkg->new($pdf, $ownerpass, $userpass, $prompt)
57              
58             Create and validate a new decryption object. If this fails, it will
59             set $CAM::PDF::errstr and return undef.
60              
61             C<$prompt> is a boolean that says whether the user should be prompted for
62             a password on the command line.
63              
64             =cut
65              
66             sub new
67             {
68 38     38 1 80 my $pkg = shift;
69 38         74 my $doc = shift;
70 38         75 my $opassword = shift;
71 38         66 my $upassword = shift;
72 38         65 my $prompt = shift;
73              
74 38 50       138 if (!$doc)
75             {
76 0         0 $CAM::PDF::errstr = "This is an invalid PDF doc\n";
77 0         0 return;
78             }
79              
80 38 50       130 if (!exists $doc->{trailer})
81             {
82 0         0 $CAM::PDF::errstr = "This PDF doc has no trailer\n";
83 0         0 return;
84             }
85              
86 38         204 my $self = bless {
87             keycache => {},
88             }, $pkg;
89              
90 38 100       166 if (!exists $doc->{trailer}->{Encrypt})
91             {
92             # This PDF doc is not encrypted. Return an empty object
93 10         32 $self->{noop} = 1;
94 10         45 return $self;
95             }
96              
97 28 50       84 if (!$doc->{ID})
98             {
99 0         0 $CAM::PDF::errstr = "This PDF lacks an ID. The document cannot be decrypted.\n";
100 0         0 return;
101             }
102              
103 28         121 return $self->_init($doc, $opassword, $upassword, $prompt);
104             }
105              
106             sub _init
107             {
108 28     28   41 my $self = shift;
109 28         115 my $doc = shift;
110 28         54 my $opassword = shift;
111 28         50 my $upassword = shift;
112 28         44 my $prompt = shift;
113              
114 28 50       134 if ($doc->{trailer}->{Encrypt}->{type} eq 'reference')
115             {
116             # If the encryption block is an indirect reference, store
117             # it's location so we don't accidentally encrypt it.
118 28         108 $self->{EncryptBlock} = $doc->{trailer}->{Encrypt}->{value};
119             }
120              
121 28         118 my $dict = $doc->getValue($doc->{trailer}->{Encrypt});
122              
123 28 50 33     237 if ($dict->{Filter}->{value} ne 'Standard' || ($dict->{V}->{value} != 1 && $dict->{V}->{value} != 2))
      33        
124             {
125 0         0 $CAM::PDF::errstr = "CAM::PDF only supports Version 1 and 2 of the Standard encryption filter. This PDF uses something else.\n";
126 0         0 return;
127             }
128              
129             # V == 1 means MD5+RC4, keylength == 40
130             # V == 2 means MD5+RC4, keylength == {40, 48, 56, 64, ..., 128}
131             # See PDF Ref 1.5 p93
132 28 0       110 $self->{keylength} = $dict->{V}->{value} == 1 ? 40 : $dict->{Length} ? $dict->{Length}->{value} : 40;
    50          
133 28 50 33     246 if (40 > $self->{keylength} || 128 < $self->{keylength} || 0 != $self->{keylength} % 8)
      33        
134             {
135 0         0 $CAM::PDF::errstr = "Invalid key length $self->{keylength}. The document cannot be decrypted.\n";
136 0         0 return;
137             }
138              
139             # PDF Ref 1.5 pp. 97-98
140 28         76 foreach my $key ('R', 'O', 'U', 'P')
141             {
142 112 50       209 if (exists $dict->{$key})
143             {
144 112         326 $self->{$key} = $dict->{$key}->{value};
145             }
146             else
147             {
148 0         0 $CAM::PDF::errstr = "Requred decryption datum $key is missing. The document cannot be decrypted.\n";
149 0         0 return;
150             }
151             }
152              
153 28         303 require Digest::MD5;
154 28         127 require Crypt::RC4;
155              
156 28         45 while (1)
157             {
158 28 100       128 if ($self->_check_opass($opassword, $upassword))
    50          
    50          
159             {
160 16         65 $self->{code} = $self->_compute_hash($doc->{ID}, $opassword);
161 16         44 last;
162             }
163             elsif ($self->_check_upass($doc->{ID}, $upassword))
164             {
165 0         0 $self->{code} = $self->_compute_hash($doc->{ID}, $upassword);
166 0         0 last;
167             }
168             elsif ($prompt)
169             {
170 0         0 print {*STDERR} 'Enter owner password: ';
  0         0  
171 0         0 $opassword = ; ## no critic(InputOutput::ProhibitExplicitStdin)
172 0         0 chomp $opassword;
173              
174 0         0 print {*STDERR} 'Enter user password: ';
  0         0  
175 0         0 $upassword = ; ## no critic(InputOutput::ProhibitExplicitStdin)
176 0         0 chomp $upassword;
177             }
178             else
179             {
180 12         28 $CAM::PDF::errstr = "Incorrect password(s). The document cannot be decrypted.\n";
181 12         102 return;
182             }
183             }
184              
185 16         43 $self->{opass} = $opassword;
186 16         32 $self->{upass} = $upassword;
187              
188 16         161 return $self;
189             }
190              
191             =item $self->decode_permissions($field)
192              
193             Given a binary encoded permissions string from a PDF document, return
194             the four individual boolean fields as an array:
195              
196             print boolean
197             modify boolean
198             copy boolean
199             add boolean
200              
201             =cut
202              
203             sub decode_permissions
204             {
205 28     28 1 41 my $self = shift;
206 28         43 my $p = shift;
207              
208 28         127 my $bytes = unpack 'b*', pack 'V', $p;
209 28         217 return split //xms, substr $bytes, 2, 4;
210             }
211              
212             =item $self->encode_permissions($print, $modify, $copy, $add)
213              
214             Given four booleans, pack them into a single field in the PDF style
215             that decode_permissions can understand. Returns that scalar.
216              
217             =cut
218              
219             sub encode_permissions
220             {
221 12     12 1 27 my $self = shift;
222              
223 12         23 my %allow;
224 12         29 $allow{print} = shift;
225 12         33 $allow{modify} = shift;
226 12         27 $allow{copy} = shift;
227 12         29 $allow{add} = shift;
228              
229 12         53 foreach my $key (keys %allow)
230             {
231 48 100       123 $allow{$key} = $allow{$key} ? 1 : 0;
232             }
233              
234             # This is more complicated that decode, because we need to pad
235             # endian-appropriately
236              
237 12         70 my $perms = join q{}, $allow{print}, $allow{modify}, $allow{copy}, $allow{add};
238 12         31 my $bytes = '00' . $perms . '11'; # 8 bits: 2 pad, 4 data, 2 pad
239             # Pad to 32 bits with the right endian-ness
240 12         56 my $binary = unpack 'B16', pack 's', 255;
241 12 50       49 if ('1' eq substr $binary, 0, 1)
242             {
243             # little endian
244 12         28 $bytes .= '11111111' x 3;
245             }
246             else
247             {
248             # big endian
249 0         0 $bytes = ('11111111' x 3) . $bytes;
250             }
251             # Make a signed 32-bit number (NOTE: should this really be signed??? need to check spec...)
252 12         63 my $p = unpack 'l', pack 'b32', $bytes;
253              
254 12         54 return $p;
255             }
256              
257             =item $self->set_passwords($doc, $ownerpass, $userpass)
258              
259             =item $self->set_passwords($doc, $ownerpass, $userpass, $permissions)
260              
261             Change the PDF passwords to the specified values. When the PDF is
262             output, it will be encrypted with the new passwords.
263              
264             PERMISSIONS is an optional scalar of the form that decode_permissions
265             can understand. If not specified, the existing values will be
266             retained.
267              
268             Note: we only support writing using encryption version 1, even though
269             we can read encryption version 2 as well.
270              
271             =cut
272              
273             sub set_passwords
274             {
275 12     12 1 24 my $self = shift;
276 12         24 my $doc = shift;
277 12         21 my $opass = shift;
278 12         24 my $upass = shift;
279 12   33     60 my $p = shift || $self->{P} || $self->encode_permissions(1,1,1,1);
280              
281 12         98 require Digest::MD5;
282 12         971 require Crypt::RC4;
283              
284 12         842 $doc->clean(); # Mark EVERYTHING changed
285              
286             # if no crypt block, create it and a trailer entry
287 12         57 my $dict = CAM::PDF::Node->new('dictionary',
288             {
289             Filter => CAM::PDF::Node->new('label', 'Standard'),
290             V => CAM::PDF::Node->new('number', 1),
291             R => CAM::PDF::Node->new('number', 2),
292             P => CAM::PDF::Node->new('number', $p),
293             O => CAM::PDF::Node->new('string', q{}),
294             U => CAM::PDF::Node->new('string', q{}),
295             });
296 12         49 my $objnode = CAM::PDF::Node->new('object', $dict);
297              
298 12         37 my $objnum = $self->{EncryptBlock};
299 12 100       29 if ($objnum)
300             {
301 8         46 $doc->replaceObject($objnum, undef, $objnode, 0);
302             }
303             else
304             {
305 4         23 $objnum = $doc->appendObject(undef, $objnode, 0);
306             }
307              
308 12 50       47 if (!$doc->{trailer})
309             {
310 0         0 die 'No trailer';
311             }
312              
313             # This may overwrite an existing ref, but that's no big deal, just a tiny bit inefficient
314 12         55 $doc->{trailer}->{Encrypt} = CAM::PDF::Node->new('reference', $objnum);
315             # if no ID, create it
316 12 50       46 if (!$doc->{ID})
317             {
318 0         0 $doc->createID();
319             #print 'new ID: ' . unpack('h*',$doc->{ID}) . ' (' . length($doc->{ID}) . ")\n";
320             }
321             #else { print 'old ID: '.unpack('h*',$doc->{ID}) . ' (' . length($doc->{ID}) . ")\n"; }
322              
323             # Recompute O and U
324             # To do so, we must set up a couple of dependent variables first:
325 12         29 $self->{R} = 2;
326 12         24 $self->{keylength} = 40;
327 12         21 $self->{P} = $p;
328              
329             # set O (has to be first because U uses O)
330 12         47 $self->{O} = $self->_compute_o($opass, $upass);
331              
332             # set U
333 12         54 $self->{U} = $self->_compute_u($doc->{ID}, $upass);
334              
335             # save O and U in the Encrypt block
336 12         12220 $dict = $doc->getObjValue($objnum);
337 12         57 $dict->{O}->{value} = $self->{O};
338 12         32 $dict->{U}->{value} = $self->{U};
339              
340             # Create a brand new instance
341 12         26 my $pkg = ref $self;
342 12   50     55 $doc->{crypt} = $pkg->new($doc, $opass, $upass, 0)
343             || die "$CAM::PDF::errstr\n";
344              
345 12         89 return $doc->{crypt};
346             }
347              
348             =item $self->encrypt($doc, $string)
349              
350             Encrypt the scalar using the passwords previously specified.
351              
352             =cut
353              
354             sub encrypt
355             {
356 431     431 1 1582 my ($self, @rest) = @_;
357 431         1196 return $self->_crypt(@rest);
358             }
359              
360             =item $self->decrypt($doc, $string)
361              
362             Decrypt the scalar using the passwords previously specified.
363              
364             =cut
365              
366             sub decrypt
367             {
368 87     87 1 277 my ($self, @rest) = @_;
369 87         242 return $self->_crypt(@rest);
370             }
371              
372             # INTERNAL FUNCTION
373             # The real work behind encrpyt/decrypt
374              
375             sub _crypt
376             {
377 518     518   733 my $self = shift;
378 518         676 my $doc = shift;
379 518         675 my $content = shift;
380 518         707 my $objnum = shift;
381 518         640 my $gennum = shift;
382              
383 518 100       3239 return $content if ($self->{noop});
384              
385 85 50 33     685 if (ref $content || ref $objnum || ref $gennum)
      33        
386             {
387 0         0 die 'Trying to crypt data with non-scalar obj/gennum or content';
388             }
389              
390             # DO NOT encrypt the encryption block!! :-)
391 85 100 66     671 return $content if ($objnum && $self->{EncryptBlock} && $objnum == $self->{EncryptBlock});
      100        
392              
393 77 100       218 if (!defined $gennum)
394             {
395 8 50       21 if (!$objnum)
396             {
397             # This is not a real document object. It might be a trailer object.
398 8         70 return $content;
399             }
400              
401 0         0 croak 'gennum missing in crypt';
402             }
403              
404 69         204 return Crypt::RC4::RC4($self->_compute_key($objnum, $gennum), $content);
405             }
406              
407             sub _compute_key
408             {
409 69     69   98 my $self = shift;
410 69         105 my $objnum = shift;
411 69         111 my $gennum = shift;
412              
413 69         150 my $id = $objnum . '_' .$gennum;
414 69 100       239 if (!exists $self->{keycache}->{$id})
415             {
416 44         169 my $objstr = pack 'V', $objnum;
417 44         105 my $genstr = pack 'V', $gennum;
418              
419 44         103 my $objpadding = substr $objstr, 0, 3;
420 44         81 my $genpadding = substr $genstr, 0, 2;
421              
422 44         418 my $hash = Digest::MD5::md5($self->{code} . $objpadding . $genpadding);
423              
424             # size(bytes) = nbits/8 + 3 for objnum + 2 for gennum; max of 16; PDF ref 1.5 pp 94-95
425 44         139 my $size = ($self->{keylength} >> 3) + 5;
426 44 50       124 if ($size > 16) {
427 0         0 $size = 16;
428             }
429 44         3953 $self->{keycache}->{$id} = substr $hash, 0, $size;
430             }
431 69         374 return $self->{keycache}->{$id};
432             }
433              
434             sub _compute_hash
435             {
436 40     40   76 my $self = shift;
437 40         81 my $doc_id = shift;
438 40         65 my $pass = shift;
439              
440             #print "_compute_hash for password $pass, P: $self->{P}, ID: $doc_id, O: $self->{O}\n" if ($pass);
441              
442 40         106 $pass = $self->_format_pass($pass);
443              
444 40         154 my $p = pack 'L', $self->{P}+0;
445 40         145 my $bytes = unpack 'b32', $p;
446 40 50       154 if (1 == substr $bytes, 0, 1)
447             {
448             # big endian, so byte swap
449 0         0 $p = (substr $p,3,1).(substr $p,2,1).(substr $p,1,1).(substr $p,0,1);
450             }
451              
452 40         79 my $id = substr $doc_id, 0, 16;
453              
454 40         105 my $input = $pass . $self->{O} . $p . $id;
455              
456 40 50       112 if ($self->{R} == 3) {
457             # I don't know how to decide this. Maybe not applicable with Standard filter?
458             #if document metadata is not encrypted
459             # input .= pack 'L', 0xFFFFFFFF;
460             }
461              
462 40         182 my $hash = Digest::MD5::md5($input);
463              
464 40 50       121 if ($self->{R} == 3)
465             {
466 0         0 for my $iter (1..50) {
467 0         0 $hash = Digest::MD5::md5($hash);
468             }
469             }
470              
471             # desired number of bytes for the key
472             # for V==1, size == 5
473             # for V==2, 5 < size < 16
474 40         80 my $size = $self->{keylength} >> 3;
475 40         168 return substr $hash, 0, $size;
476             }
477              
478             sub _compute_u
479             {
480 24     24   39 my $self = shift;
481 24         42 my $doc_id = shift;
482 24         40 my $upass = shift;
483              
484 24         134 my $hash = $self->_compute_hash($doc_id, $upass);
485 24 50       59 if ($self->{R} == 3) {
486 0         0 my $id = substr $doc_id, 0, 16;
487 0         0 my $input = $padding . $id;
488 0         0 my $code = Digest::MD5::md5($input);
489 0         0 $code = substr $code, 0, 16;
490 0         0 return $self->_do_iter_crypt($hash, $code) . substr $padding, 0, 16;
491             } else {
492 24         72 return Crypt::RC4::RC4($hash, $padding);
493             }
494             }
495              
496             sub _compute_o
497             {
498 40     40   66 my $self = shift;
499 40         59 my $opass = shift;
500 40         54 my $upass = shift;
501 40         55 my $backward = shift;
502              
503 40         127 my $o = $self->_format_pass($opass);
504 40         85 my $u = $self->_format_pass($upass);
505              
506 40         229 my $hash = Digest::MD5::md5($o);
507              
508 40 50       121 if ($self->{R} == 3) {
509 0         0 for my $iter (1..50) {
510 0         0 $hash = Digest::MD5::md5($hash);
511             }
512             }
513              
514 40         185 my $size = $self->{keylength} >> 3;
515 40         72 my $code = substr $hash, 0, $size;
516 40         136 return $self->_do_iter_crypt($code, $u, $backward);
517             }
518              
519             sub _do_iter_crypt {
520 40     40   58 my $self = shift;
521 40         57 my $code = shift;
522 40         55 my $pass = shift;
523 40         68 my $backward = shift;
524              
525 40 50       120 if ($self->{R} == 3) {
526 0         0 my @steps = 0..19;
527 0 0       0 if ($backward) {
528 0         0 @steps = reverse @steps;
529             }
530 0         0 my $size = $self->{keylength} >> 3;
531 0         0 for my $iter (@steps) {
532 0         0 my $xor = chr($iter) x $size;
533 0         0 my $itercode = $code ^ $xor;
534 0         0 $pass = Crypt::RC4::RC4($itercode, $pass);
535             }
536             } else {
537 40         185 $pass = Crypt::RC4::RC4($code, $pass);
538             }
539 40         27978 return $pass;
540             }
541              
542             sub _check_opass
543             {
544 28     28   47 my $self = shift;
545 28         47 my $opass = shift;
546 28         40 my $upass = shift;
547              
548 28         94 my $crypto = $self->_compute_o($opass, $upass, 1);
549              
550             #printf "O: %s\n%s\n vs.\n%s\n", defined $opass ? $opass : '(undef)', _hex($crypto), _hex($self->{O});
551              
552 28         196 return $crypto eq $self->{O};
553             }
554              
555             sub _check_upass
556             {
557 12     12   24 my $self = shift;
558 12         20 my $doc_id = shift;
559 12         20 my $upass = shift;
560              
561 12         44 my $cryptu = $self->_compute_u($doc_id, $upass);
562              
563             #printf "U: %s\n%s\n vs.\n%s\n", defined $upass ? $upass : '(undef)', _hex($cryptu), _hex($self->{U});
564              
565 12         9455 return $cryptu eq $self->{U};
566             }
567              
568             sub _hex {
569 0     0   0 my $val = shift;
570 0         0 return join q{}, map {sprintf '%08x', $_} unpack 'N*', $val;
  0         0  
571             }
572              
573             sub _format_pass
574             {
575 120     120   155 my $self = shift;
576 120         166 my $pass = shift;
577              
578 120 100       297 if (!defined $pass)
579             {
580 12         20 $pass = q{};
581             }
582              
583 120         332 return substr $pass.$padding, 0, 32;
584             }
585              
586             1;
587             __END__