File Coverage

blib/lib/PDF/Make/Crypt.pm
Criterion Covered Total %
statement 32 33 96.9
branch 18 22 81.8
condition 2 3 66.6
subroutine 6 6 100.0
pod 2 2 100.0
total 60 66 90.9


line stmt bran cond sub pod time code
1             package PDF::Make::Crypt;
2              
3 3     3   170672 use strict;
  3         4  
  3         102  
4 3     3   12 use warnings;
  3         6  
  3         138  
5 3     3   656 use PDF::Make;
  3         5  
  3         171  
6              
7             # Permission constants - exported for convenience
8             use constant {
9 3         1312 PERM_PRINT => (1 << 2), # bit 3
10             PERM_MODIFY => (1 << 3), # bit 4
11             PERM_COPY => (1 << 4), # bit 5
12             PERM_ANNOT => (1 << 5), # bit 6
13             PERM_FILL_FORMS => (1 << 8), # bit 9
14             PERM_EXTRACT => (1 << 9), # bit 10
15             PERM_ASSEMBLE => (1 << 10), # bit 11
16             PERM_PRINT_HIGH => (1 << 11), # bit 12
17             PERM_ALL => 0xFFFFFFFC,
18 3     3   15 };
  3         9  
19              
20             # Map permission names to bits
21             my %PERM_MAP = (
22             print => PERM_PRINT,
23             modify => PERM_MODIFY,
24             copy => PERM_COPY,
25             annotate => PERM_ANNOT,
26             annot => PERM_ANNOT,
27             fill_forms => PERM_FILL_FORMS,
28             fillforms => PERM_FILL_FORMS,
29             extract => PERM_EXTRACT,
30             assemble => PERM_ASSEMBLE,
31             print_high => PERM_PRINT_HIGH,
32             printhigh => PERM_PRINT_HIGH,
33             );
34              
35             =head1 NAME
36              
37             PDF::Make::Crypt - PDF encryption support for PDF::Make
38              
39             =head1 SYNOPSIS
40              
41             use PDF::Make;
42            
43             my $pdf = PDF::Make->new();
44             $pdf->add_page(width => 612, height => 792);
45             $pdf->text('Secret document', x => 100, y => 700);
46            
47             # Render with encryption
48             my $bytes = $pdf->render(
49             encrypt => {
50             algorithm => 'AES-256',
51             user_password => 'secret',
52             owner_password => 'admin',
53             permissions => ['print', 'copy'],
54             }
55             );
56              
57             =head1 DESCRIPTION
58              
59             This module provides PDF encryption support for PDF::Make. It implements
60             the Standard security handler per ISO 32000-2:2020 §7.6, supporting:
61              
62             =over 4
63              
64             =item * RC4-40 (R2, V=1) - 40-bit RC4, legacy
65              
66             =item * RC4-128 (R3, V=2) - 128-bit RC4
67              
68             =item * AES-128 (R4, V=4) - 128-bit AES-CBC
69              
70             =item * AES-256 (R6, V=5) - 256-bit AES-CBC (recommended)
71              
72             =back
73              
74             =head1 METHODS
75              
76             =head2 parse_permissions
77              
78             my $flags = PDF::Make::Crypt->parse_permissions(\@perms);
79              
80             Convert a list of permission names to a permission flags integer.
81              
82             Valid permission names:
83              
84             =over 4
85              
86             =item * print - Allow printing
87              
88             =item * modify - Allow document modification
89              
90             =item * copy - Allow text/graphic extraction
91              
92             =item * annotate / annot - Allow annotations
93              
94             =item * fill_forms / fillforms - Allow form filling
95              
96             =item * extract - Allow accessibility extraction
97              
98             =item * assemble - Allow document assembly
99              
100             =item * print_high / printhigh - Allow high-quality printing
101              
102             =back
103              
104             =cut
105              
106             sub parse_permissions {
107 3     3 1 327400 my ($class, $perms) = @_;
108            
109 3 100 66     15 return PERM_ALL unless $perms && ref($perms) eq 'ARRAY';
110 2 100       6 return 0 unless @$perms;
111            
112 1         10 my $flags = 0;
113 1         2 for my $perm (@$perms) {
114 3         5 my $lc_perm = lc($perm);
115 3 50       7 if (exists $PERM_MAP{$lc_perm}) {
116 3         5 $flags |= $PERM_MAP{$lc_perm};
117             } else {
118 0         0 warn "Unknown permission: $perm";
119             }
120             }
121            
122 1         3 return $flags;
123             }
124              
125             =head2 format_permissions
126              
127             my @perms = PDF::Make::Crypt->format_permissions($flags);
128              
129             Convert permission flags integer back to a list of permission names.
130              
131             =cut
132              
133             sub format_permissions {
134 2     2 1 769 my ($class, $flags) = @_;
135            
136 2         4 my @perms;
137 2 50       6 push @perms, 'print' if $flags & PERM_PRINT;
138 2 50       5 push @perms, 'modify' if $flags & PERM_MODIFY;
139 2 50       3 push @perms, 'copy' if $flags & PERM_COPY;
140 2 100       5 push @perms, 'annotate' if $flags & PERM_ANNOT;
141 2 100       4 push @perms, 'fill_forms' if $flags & PERM_FILL_FORMS;
142 2 100       5 push @perms, 'extract' if $flags & PERM_EXTRACT;
143 2 100       4 push @perms, 'assemble' if $flags & PERM_ASSEMBLE;
144 2 100       3 push @perms, 'print_high' if $flags & PERM_PRINT_HIGH;
145            
146 2         6 return @perms;
147             }
148              
149             =head2 new
150              
151             my $crypt = PDF::Make::Crypt->new();
152              
153             Create a new encryption context.
154              
155             =head2 setup
156              
157             $crypt->setup($algorithm, $user_passwd, $owner_passwd, $permissions, $doc_id);
158              
159             Set up encryption for a new document.
160              
161             =head2 authenticate
162              
163             my $result = $crypt->authenticate($password);
164              
165             Authenticate with the given password.
166              
167             Returns:
168              
169             =over 4
170              
171             =item * 1 - Owner password authenticated
172              
173             =item * 0 - User password authenticated
174              
175             =item * -1 - Authentication failed
176              
177             =back
178              
179             =head2 encrypt_string
180              
181             my $encrypted = $crypt->encrypt_string($obj_num, $gen_num, $data);
182              
183             Encrypt a string for the given object.
184              
185             =head2 decrypt_string
186              
187             my $decrypted = $crypt->decrypt_string($obj_num, $gen_num, $data);
188              
189             Decrypt a string from the given object.
190              
191             =head1 SEE ALSO
192              
193             L, ISO 32000-2:2020 §7.6
194              
195             =cut
196              
197             1;