#!/usr/bin/env perl require 5.008; use warnings; use strict; unshift(@INC, '.'); require qpdf_test_helpers; chdir("qpdf") or die "chdir testdir failed: $!\n"; require TestDriver; cleanup(); my $td = new TestDriver('encryption'); my $n_tests = 0; # $n_tests incremented below # The enc-file.pdf files were encrypted using Acrobat 5.0, not the # qpdf library. The files are decrypted using qpdf, then re-encrypted # using qpdf with specific flags. The /P value is checked. The # resulting files were saved and manually checked with Acrobat 5.0 to # ensure that the security settings were as intended. # The enc-XI-file.pdf files were treated the same way but with Acrobat # XI instead of Acrobat 5.0. They were used to create test files with # newer encryption formats. # Values: basename, password, encryption flags, /P Encrypt key, # extract-for-accessibility, extract-for-any-purpose, # print-low-res, print-high-res, modify-assembly, modify-forms, # modify-annotate, modify-other, modify-all my @encrypted_files = (['base', ''], # 1 ['R3,V2', '', # 2 '-accessibility=n -extract=n -print=full -modify=all', -532, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1], ['R3,V2,U=view,O=view', 'view', # 3 '-accessibility=y -extract=n -print=none -modify=none', -3392, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0], ['R3,V2,O=master', 'master', # 4 '-accessibility=n -extract=y -print=none -modify=annotate', -2576, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0], ['R3,V2,O=master', '', # 5 '-accessibility=n -extract=n -print=none -modify=form', -2624, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0], ['R3,V2,U=view,O=master', 'view', # 6 '-accessibility=n -extract=n -print=none -modify=assembly', -2880, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0], ['R3,V2,U=view,O=master', 'master', # 7 '-accessibility=n -print=low', -2564, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1], ['R3,V2,U=view,O=master', 'master', # 8 '-modify=all -assemble=n', -1028, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0], ['R3,V2,U=view,O=master', 'master', # 9 '-modify=none -form=y', -1068, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0], ['R3,V2,U=view,O=master', 'master', # 10 '-modify=annotate -assemble=n', -1036, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0], ['R3,V2,U=view,O=master', 'master', # 11 '-form=n', -260, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0], ['R3,V2,U=view,O=master', 'master', # 12 '-annotate=n', -36, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0], ['R3,V2,U=view,O=master', 'master', # 13 '-modify-other=n', -12, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0], ['R2,V1', '', # 14 '-print=n -modify=n -extract=n -annotate=n', -64, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0], ['R2,V1,U=view,O=view', 'view', # 15 '-print=y -modify=n -extract=n -annotate=n', -60, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0], ['R2,V1,O=master', 'master', # 16 '-print=n -modify=y -extract=n -annotate=n', -56, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0], ['R2,V1,O=master', '', # 17 '-print=n -modify=n -extract=y -annotate=n', -48, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0], ['R2,V1,U=view,O=master', 'view', # 18 '-print=n -modify=n -extract=n -annotate=y', -32, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0], ['R2,V1,U=view,O=master', 'master', # 19 '', -4, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1], ['long-password', 'asdf asdf asdf asdf asdf asdf qwer'], # 20 ['long-password', 'asdf asdf asdf asdf asdf asdf qw'], # 21 ['XI-base', ''], # 22 ['XI-R6,V5,O=master', '', # 23 '-extract=n -print=none -modify=assembly', -2368, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0], ['XI-R6,V5,O=master', 'master', # 24 '-extract=n -print=none -modify=assembly', -2368, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0], ['XI-R6,V5,U=view,O=master', 'view', # 25 '-print=low', -2052, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1], ['XI-R6,V5,U=view,O=master', 'master', # 26 '-print=low', -2052, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1], ['XI-R6,V5,U=view,O=master', 'master', # 27 '-accessibility=n', -4, # -accessibility=n has no effect 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1], ['XI-long-password', 'qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnm'], # 28; -accessibility=n has no effect ['XI-long-password', 'qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcv'], # 29 ['XI-R6,V5,U=wwwww,O=wwwww', 'wwwww', # 30 '', -4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], ); $n_tests += 8 + (2 * (@encrypted_files)) + (7 * (@encrypted_files - 6)) + 10; $td->runtest("encrypted file", {$td->COMMAND => "test_driver 2 encrypted-with-images.pdf"}, {$td->FILE => "encrypted1.out", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); $td->runtest("preserve encryption", {$td->COMMAND => "qpdf encrypted-with-images.pdf encrypted-with-images.enc"}, {$td->STRING => "", $td->EXIT_STATUS => 0}); $td->runtest("recheck encrypted file", {$td->COMMAND => "test_driver 2 encrypted-with-images.enc"}, {$td->FILE => "encrypted1.out", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); $td->runtest("empty owner password", {$td->COMMAND => "qpdf --encrypt u '' 256 -- minimal.pdf a.pdf"}, {$td->REGEXP => ".*is insecure.*--allow-insecure.*", $td->EXIT_STATUS => 2}, $td->NORMALIZE_NEWLINES); $td->runtest("allow insecure", {$td->COMMAND => "qpdf --encrypt --user-password=u --bits=256 --allow-insecure --" . " minimal.pdf a.pdf"}, {$td->STRING => "", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); $td->runtest("check insecure", {$td->COMMAND => "qpdf --check a.pdf"}, {$td->FILE => "insecure-passwords.out", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); # Test that long passwords that are one character too short fail. We # test the truncation cases in the loop below by using passwords # longer than the supported length. $td->runtest("significant password characters (V < 5)", {$td->COMMAND => "qpdf --check enc-long-password.pdf" . " --password='asdf asdf asdf asdf asdf asdf q'"}, {$td->REGEXP => ".*invalid password.*", $td->EXIT_STATUS => 2}); $td->runtest("significant password characters (V = 5)", {$td->COMMAND => "qpdf --check enc-XI-long-password.pdf" . " --password=qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxc"}, {$td->REGEXP => ".*invalid password.*", $td->EXIT_STATUS => 2}); my $enc_base = undef; my $enc_n = 0; foreach my $d (@encrypted_files) { ++$enc_n; my ($file, $pass, $xeflags, $P, $match_owner, $match_user, $accessible, $extract, $printlow, $printhigh, $modifyassembly, $modifyform, $modifyannot, $modifyother, $modifyall) = @$d; my $f = sub { $_[0] ? "allowed" : "not allowed" }; my $jf = sub { $_[0] ? "true" : "false" }; my $enc_details = ""; my $enc_json = "{\n" . " \"version\": 2,\n" . " \"parameters\": {\n" . " \"decodelevel\": \"generalized\"\n" . " },\n" . " \"encrypt\": {\n" . " \"capabilities\": {\n"; if ($match_owner) { $enc_details .= "Supplied password is owner password\n"; } if ($match_user) { $enc_details .= "Supplied password is user password\n"; } $enc_details .= "extract for accessibility: " . &$f($accessible) . "\n" . "extract for any purpose: " . &$f($extract) . "\n" . "print low resolution: " . &$f($printlow) . "\n" . "print high resolution: " . &$f($printhigh) . "\n" . "modify document assembly: " . &$f($modifyassembly) . "\n" . "modify forms: " . &$f($modifyform) . "\n" . "modify annotations: " . &$f($modifyannot) . "\n" . "modify other: " . &$f($modifyother) . "\n" . "modify anything: " . &$f($modifyall) . "\n"; $enc_json .= " \"accessibility\": " . &$jf($accessible) . ",\n" . " \"extract\": " . &$jf($extract) . ",\n" . " \"modify\": " . &$jf($modifyall) . ",\n" . " \"modifyannotations\": " . &$jf($modifyannot) . ",\n" . " \"modifyassembly\": " . &$jf($modifyassembly) . ",\n" . " \"modifyforms\": " . &$jf($modifyform) . ",\n" . " \"modifyother\": " . &$jf($modifyother) . ",\n" . " \"printhigh\": " . &$jf($printhigh) . ",\n" . " \"printlow\": " . &$jf($printlow) . "\n" . " },\n" . " \"encrypted\": true,\n" . " \"ownerpasswordmatched\": ---opm---,\n" . " \"parameters\": {\n" . " \"P\": ---P---,\n" . " \"R\": ---R---,\n" . " \"V\": ---V---,\n" . " \"bits\": ---bits---,\n" . " \"filemethod\": \"---method---\",\n" . " \"key\": null,\n" . " \"method\": \"---method---\",\n" . " \"streammethod\": \"---method---\",\n" . " \"stringmethod\": \"---method---\"\n" . " },\n" . " \"recovereduserpassword\": ---rup---,\n" . " \"userpasswordmatched\": ---upm---\n" . " }\n" . "}\n"; if ($file =~ m/XI-/) { $enc_details .= "stream encryption method: AESv3\n" . "string encryption method: AESv3\n" . "file encryption method: AESv3\n"; } # Test writing to stdout $td->runtest("decrypt $file", {$td->COMMAND => "qpdf --static-id -qdf --object-streams=disable" . " --no-original-object-ids" . " --password=\"$pass\" enc-$file.pdf -" . " > $file.enc"}, {$td->STRING => "", $td->EXIT_STATUS => 0}); if ($file =~ m/base$/) { $enc_base = $file; $td->runtest("check ID", {$td->COMMAND => "perl check-ID.pl $file.enc"}, {$td->STRING => "ID okay\n", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); } else { $td->runtest("check against base", {$td->COMMAND => "sh ./diff-encrypted $enc_base.enc $file.enc"}, {$td->STRING => "okay\n", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); } if ($file =~ m/^(?:XI-)?R(\d),V(\d)(?:,U=(\w+))?(?:,O=(\w+))?$/) { my $R = $1; my $V = $2; my $upass = $3 || ""; my $opass = $4 || ""; my $bits = (($V == 5) ? 256 : ($V == 2) ? 128 : 40); my $method = $bits == 256 ? "AESv3" : "RC4"; my $opm = ($pass eq $opass ? "true" : "false"); my $upm = ($pass eq $upass ? "true" : "false"); my $rup = (($pass eq $opass) && ($pass ne $upass) && ($V < 5)) ? "\"$upass\"" : "null"; $enc_json =~ s/---R---/$R/; $enc_json =~ s/---P---/$P/; $enc_json =~ s/---V---/$V/; $enc_json =~ s/---bits---/$bits/; $enc_json =~ s/---method---/$method/g; $enc_json =~ s/---opm---/$opm/; $enc_json =~ s/---upm---/$upm/; $enc_json =~ s/---rup---/$rup/; my $eflags = "--allow-weak-crypto" . " -encrypt \"$upass\" \"$opass\" $bits $xeflags --"; if (($opass eq "") && ($bits == 256)) { $eflags =~ s/--$/--allow-insecure --/; } if (($pass ne $upass) && ($V >= 5)) { # V >= 5 can no longer recover user password with owner # password. $upass = ""; } my $accessibility_warning = ""; if (($R > 3) && ($eflags =~ /accessibility=n/)) { $accessibility_warning = "qpdf: -accessibility=n is ignored" . " for modern encryption formats\n"; } $td->runtest("encrypt $file", {$td->COMMAND => "qpdf --static-id --no-original-object-ids -qdf" . " $eflags $file.enc $file.enc2"}, {$td->STRING => $accessibility_warning, $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); $td->runtest("check /P enc2 ($enc_n)", {$td->COMMAND => "qpdf --show-encryption --password=\"$pass\"" . " $file.enc2"}, {$td->STRING => "R = $R\nP = $P\n" . "User password = $upass\n$enc_details", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); $td->runtest("json encrypt key ($enc_n)", {$td->COMMAND => "qpdf --json --json-key=encrypt" . " --password=\"$pass\"" . " $file.enc2"}, {$td->STRING => $enc_json, $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); $td->runtest("decrypt again", {$td->COMMAND => "qpdf --static-id --no-original-object-ids -qdf" . " --password=\"$pass\"" . " $file.enc2 $file.enc3"}, {$td->STRING => "", $td->EXIT_STATUS => 0}); $td->runtest("compare", {$td->FILE => "$file.enc"}, {$td->FILE => "$file.enc3"}); $td->runtest("preserve encryption", {$td->COMMAND => "qpdf --static-id --password=\"$pass\"" . " $file.enc2 $file.enc4"}, {$td->STRING => "", $td->EXIT_STATUS => 0}); $td->runtest("check /P enc4 ($enc_n)", {$td->COMMAND => "qpdf --show-encryption --password=\"$pass\"" . " $file.enc4"}, {$td->STRING => "R = $R\nP = $P\n" . "User password = $upass\n$enc_details", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); } } $td->runtest("non-encrypted", {$td->COMMAND => "qpdf --show-encryption enc-base.pdf"}, {$td->STRING => "File is not encrypted\n", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); $td->runtest("invalid password", {$td->COMMAND => "qpdf -qdf --password=quack" . " enc-R2,V1,U=view,O=view.pdf a.qdf"}, {$td->STRING => "qpdf: enc-R2,V1,U=view,O=view.pdf: invalid password\n", $td->EXIT_STATUS => 2}, $td->NORMALIZE_NEWLINES); $td->runtest("C API: invalid password", {$td->COMMAND => "qpdf-ctest 2 enc-R2,V1,U=view,O=view.pdf '' a.qdf"}, {$td->FILE => "c-invalid-password.out", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); $td->runtest("show-encryption works invalid password", {$td->COMMAND => "qpdf --show-encryption --password=quack" . " enc-R2,V1,U=view,O=view.pdf"}, {$td->FILE => "invalid-password-encrypt.out", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); my @cenc = ( [11, 'hybrid-xref.pdf', "''", 'r2', "", "user1"], [12, 'hybrid-xref.pdf', "''", 'r3', "", "user2"], [15, 'hybrid-xref.pdf', "''", 'r4', "", "user2"], [17, 'hybrid-xref.pdf', "''", 'r5', "", "owner3"], [18, 'hybrid-xref.pdf', "''", 'r6', "", "user4"], [13, 'c-r2.pdf', 'user1', 'decrypt with user', "user password: user1\n", ""], [13, 'c-r3.pdf', 'owner2', 'decrypt with owner', "user password: user2\n", ""], [13, 'c-r5-in.pdf', 'user3', 'decrypt R5 with user', "user password: user3\n", ""], [13, 'c-r6-in.pdf', 'owner4', 'decrypt R6 with owner', "user password: \n", ""], ); $n_tests += 2 * @cenc; foreach my $d (@cenc) { my ($n, $infile, $pass, $description, $output, $checkpass) = @$d; my $outfile = $description; $outfile =~ s/ /-/g; my $pdf_outfile = "c-$outfile.pdf"; my $check_outfile = "c-$outfile.out"; $td->runtest("C API encryption: $description", {$td->COMMAND => "qpdf-ctest $n $infile $pass a.pdf"}, {$td->STRING => $output . "C test $n done\n", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); if (-f $pdf_outfile) { $td->runtest("check $description content", {$td->COMMAND => "qpdf-test-compare a.pdf $pdf_outfile $checkpass"}, {$td->FILE => $pdf_outfile, $td->EXIT_STATUS => 0}); } else { # QPDF doesn't provide any way to make the random bits in # /Perms static, so we have no way to predictably create a # /V=5 encrypted file. It's not worth adding this...the test # suite is adequate without having a statically predictable # file. (qpdf-test-compare ignores /Perms, but it's not worth # adding output files for these cases.) $td->runtest("check $description", {$td->COMMAND => "qpdf --check a.pdf --password=$checkpass"}, {$td->FILE => $check_outfile, $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); } } # Test combinations of linearization and encryption. Note that we do # content checks on encrypted and linearized files in various # combinations below. Here we are just making sure that they are # linearized and/or encrypted as desired. $td->runtest("linearize encrypted file", {$td->COMMAND => "qpdf --linearize encrypted-with-images.pdf a.pdf"}, {$td->STRING => "", $td->EXIT_STATUS => 0}); $td->runtest("check encryption", {$td->COMMAND => "qpdf --show-encryption a.pdf", $td->FILTER => "grep -v allowed | grep -v Supplied"}, {$td->STRING => "R = 3\nP = -4\nUser password = \n", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); $td->runtest("check linearization", {$td->COMMAND => "qpdf --check-linearization a.pdf"}, {$td->STRING => "a.pdf: no linearization errors\n", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); $td->runtest("linearize and encrypt file", {$td->COMMAND => "qpdf --linearize --encrypt user owner 128 --use-aes=y --" . " lin-special.pdf a.pdf"}, {$td->STRING => "", $td->EXIT_STATUS => 0}); $td->runtest("check encryption", {$td->COMMAND => "qpdf --show-encryption --password=owner a.pdf", $td->FILTER => "grep -v allowed | grep -v method | grep -v Supplied"}, {$td->STRING => "R = 4\nP = -4\nUser password = user\n", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); $td->runtest("check linearization", {$td->COMMAND => "qpdf --check-linearization" . " --password=user a.pdf"}, {$td->STRING => "a.pdf: no linearization errors\n", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); # Test --check-linearization of non-linearized file $n_tests += 1; $td->runtest("check linearization of non-linearized file", {$td->COMMAND => "qpdf --check-linearization minimal.pdf"}, {$td->STRING => "minimal.pdf is not linearized\n", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); # Test AES encryption in various ways. $n_tests += 18; $td->runtest("encrypt with AES", {$td->COMMAND => "qpdf --encrypt '' o 128 --use-aes=y --" . " enc-base.pdf a.pdf"}, {$td->STRING => "", $td->EXIT_STATUS => 0}); $td->runtest("check encryption", {$td->COMMAND => "qpdf --show-encryption a.pdf", $td->FILTER => "grep -v allowed | grep -v method | grep -v Supplied"}, {$td->STRING => "R = 4\nP = -4\nUser password = \n", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); $td->runtest("convert original to qdf", {$td->COMMAND => "qpdf --static-id --no-original-object-ids" . " --qdf --min-version=1.6 enc-base.pdf a.qdf"}, {$td->STRING => "", $td->EXIT_STATUS => 0}); $td->runtest("convert encrypted to qdf", {$td->COMMAND => "qpdf --static-id --no-original-object-ids" . " --qdf a.pdf b.qdf"}, {$td->STRING => "", $td->EXIT_STATUS => 0}); $td->runtest("compare files", {$td->COMMAND => "qpdf-test-compare a.qdf b.qdf"}, {$td->FILE => 'b.qdf', $td->EXIT_STATUS => 0}); $td->runtest("linearize with AES and object streams", {$td->COMMAND => "qpdf --encrypt '' o 128 --use-aes=y --" . " --linearize --object-streams=generate enc-base.pdf a.pdf"}, {$td->STRING => "", $td->EXIT_STATUS => 0}); $td->runtest("check encryption", {$td->COMMAND => "qpdf --show-encryption a.pdf", $td->FILTER => "grep -v allowed | grep -v method | grep -v Supplied"}, {$td->STRING => "R = 4\nP = -4\nUser password = \n", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); $td->runtest("linearize original", {$td->COMMAND => "qpdf --linearize --object-streams=generate" . " enc-base.pdf b.pdf"}, {$td->STRING => "", $td->EXIT_STATUS => 0}); $td->runtest("convert linearized original to qdf", {$td->COMMAND => "qpdf --static-id --no-original-object-ids" . " --qdf --object-streams=generate --min-version=1.6" . " b.pdf a.qdf"}, {$td->STRING => "", $td->EXIT_STATUS => 0}); $td->runtest("convert encrypted to qdf", {$td->COMMAND => "qpdf --static-id --no-original-object-ids" . " --qdf --object-streams=generate a.pdf b.qdf"}, {$td->STRING => "", $td->EXIT_STATUS => 0}); $td->runtest("compare files", {$td->FILE => 'a.qdf'}, {$td->FILE => 'b.qdf'}); $td->runtest("force version on aes encrypted", {$td->COMMAND => "qpdf --force-version=1.4 a.pdf b.pdf"}, {$td->STRING => "", $td->EXIT_STATUS => 0}); $td->runtest("check", {$td->COMMAND => "qpdf --check b.pdf"}, {$td->FILE => "aes-forced-check.out", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); $td->runtest("make sure there is no xref stream", {$td->COMMAND => "grep /ObjStm b.pdf | wc -l"}, {$td->REGEXP => "\\s*0\\s*", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); $td->runtest("encrypt with V=5,R=5", {$td->COMMAND => "qpdf --encrypt user owner 256 --force-R5 -- " . "minimal.pdf a.pdf"}, {$td->STRING => "", $td->EXIT_STATUS => 0}); $td->runtest("check encryption", {$td->COMMAND => "qpdf --check a.pdf --password=owner"}, {$td->FILE => "V5R5.out", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); $td->runtest("encrypt with V=5,R=6", {$td->COMMAND => "qpdf --encrypt user owner 256 -- " . "minimal.pdf a.pdf"}, {$td->STRING => "", $td->EXIT_STATUS => 0}); $td->runtest("check encryption", {$td->COMMAND => "qpdf --check a.pdf --password=user"}, {$td->FILE => "V5R6.out", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); # Look at some actual V4 files $n_tests += 17; foreach my $d (['--force-V4', 'V4'], ['--cleartext-metadata', 'V4-clearmeta'], ['--use-aes=y', 'V4-aes'], ['--cleartext-metadata --use-aes=y', 'V4-aes-clearmeta']) { my ($args, $out) = @$d; $td->runtest("encrypt $args", {$td->COMMAND => "qpdf --static-aes-iv --static-id" . " --allow-weak-crypto --encrypt --bits=128 $args --" . " enc-base.pdf a.pdf"}, {$td->STRING => "", $td->EXIT_STATUS => 0}); $td->runtest("check output", {$td->COMMAND => "qpdf-test-compare a.pdf $out.pdf"}, {$td->FILE => "$out.pdf", $td->EXIT_STATUS => 0}); $td->runtest("show encryption", {$td->COMMAND => "qpdf --show-encryption a.pdf"}, {$td->FILE => "$out-encryption.out", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); } # Crypt Filter $td->runtest("decrypt with crypt filter", {$td->COMMAND => "qpdf --decrypt --static-id" . " metadata-crypt-filter.pdf a.pdf"}, {$td->STRING => "", $td->EXIT_STATUS => 0}); $td->runtest("check output", {$td->FILE => 'a.pdf'}, {$td->FILE => 'decrypted-crypt-filter.pdf'}); $td->runtest("nontrivial crypt filter", {$td->COMMAND => "qpdf --qdf --decrypt --static-id" . " nontrivial-crypt-filter.pdf --password=asdfqwer a.pdf"}, {$td->STRING => "", $td->EXIT_STATUS => 0}); $td->runtest("check output", {$td->FILE => 'a.pdf'}, {$td->FILE => 'nontrivial-crypt-filter-decrypted.pdf'}); $td->runtest("show nontrivial EFF", {$td->COMMAND => "qpdf --show-encryption" . " nontrivial-crypt-filter.pdf --password=asdfqwer"}, {$td->FILE => "nontrivial-crypt-filter.out", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); # Copy encryption parameters $n_tests += 10; $td->runtest("create reference qdf", {$td->COMMAND => "qpdf --qdf --no-original-object-ids minimal.pdf a.qdf"}, {$td->STRING => "", $td->EXIT_STATUS => 0}); $td->runtest("create encrypted file", {$td->COMMAND => "qpdf --encrypt user owner 128 --use-aes=y --extract=n --" . " minimal.pdf a.pdf"}, {$td->STRING => "", $td->EXIT_STATUS => 0}); $td->runtest("copy encryption parameters", {$td->COMMAND => "test_driver 30 minimal.pdf a.pdf"}, {$td->STRING => "test 30 done\n", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); $td->runtest("check output encryption", {$td->COMMAND => "qpdf --show-encryption b.pdf --password=owner"}, {$td->FILE => "copied-encryption.out", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); $td->runtest("convert to qdf", {$td->COMMAND => "qpdf --qdf b.pdf b.qdf" . " --password=owner --no-original-object-ids"}, {$td->STRING => "", $td->EXIT_STATUS => 0}); $td->runtest("compare qdf", {$td->COMMAND => "sh ./diff-ignore-ID-version a.qdf b.qdf"}, {$td->STRING => "okay\n", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); $td->runtest("copy encryption with qpdf", {$td->COMMAND => "qpdf --copy-encryption=a.pdf". " --encryption-file-password=user" . " minimal.pdf c.pdf"}, {$td->STRING => "", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); $td->runtest("check output encryption", {$td->COMMAND => "qpdf --show-encryption c.pdf --password=owner"}, {$td->FILE => "copied-encryption.out", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); $td->runtest("convert to qdf", {$td->COMMAND => "qpdf --qdf c.pdf c.qdf" . " --password=owner --no-original-object-ids"}, {$td->STRING => "", $td->EXIT_STATUS => 0}); $td->runtest("compare qdf", {$td->COMMAND => "sh ./diff-ignore-ID-version a.qdf c.qdf"}, {$td->STRING => "okay\n", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); # Files with attachments my @attachments = ( 'enc-XI-attachments-base.pdf', 'enc-XI-R6,V5,U=attachment,encrypted-attachments.pdf', 'enc-XI-R6,V5,U=view,attachments,cleartext-metadata.pdf'); $n_tests += 4 * @attachments + 3; foreach my $f (@attachments) { my $pass = ''; my $tpass = ''; if ($f =~ m/U=([^,\.]+)/) { $pass = "--password=$1"; $tpass = $1; } $td->runtest("decrypt $f", {$td->COMMAND => "qpdf --decrypt $pass $f a.pdf"}, {$td->STRING => "", $td->EXIT_STATUS => 0}); $td->runtest("extract attachments", {$td->COMMAND => "test_driver 35 a.pdf"}, {$td->FILE => "attachments.out", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); $td->runtest("copy $f", {$td->COMMAND => "qpdf $pass $f a.pdf"}, {$td->STRING => "", $td->EXIT_STATUS => 0}); $td->runtest("extract attachments", {$td->COMMAND => "test_driver 35 a.pdf $tpass"}, {$td->FILE => "attachments.out", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); } $td->runtest("unfilterable with crypt", {$td->COMMAND => "test_driver 36 unfilterable-with-crypt.pdf attachment"}, {$td->FILE => "unfilterable-with-crypt-before.out", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); unlink "a.pdf"; $td->runtest("decrypt file", {$td->COMMAND => "qpdf -decrypt --password=attachment" . " unfilterable-with-crypt.pdf a.pdf"}, {$td->STRING => "", $td->EXIT_STATUS => 0}); $td->runtest("copy of unfilterable with crypt", {$td->COMMAND => "test_driver 36 a.pdf attachment"}, {$td->FILE => "unfilterable-with-crypt-after.out", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); # Raw encryption key my @enc_key = (['user', '--password=user3'], ['owner', '--password=owner3'], ['hex', '--password-is-hex-key --password=35ea16a48b6a3045133b69ac0906c2e8fb0a2cc97903ae17b51a5786ebdba020']); $n_tests += scalar(@enc_key); foreach my $d (@enc_key) { my ($description, $pass) = @$d; $td->runtest("use/show encryption key ($description)", {$td->COMMAND => "qpdf --check --show-encryption-key c-r5-in.pdf $pass"}, {$td->FILE => "c-r5-key-$description.out", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); } # Miscellaneous encryption tests $n_tests += 3; $td->runtest("set encryption before set filename", {$td->COMMAND => "test_driver 63 minimal.pdf"}, {$td->STRING => "test 63 done\n", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); $td->runtest("check file's validity", {$td->COMMAND => "qpdf --check --password=u a.pdf"}, {$td->FILE => "encrypt-before-filename.out", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); $td->runtest("handle missing/invalid Length", {$td->COMMAND => "qpdf --check bad-encryption-length.pdf"}, {$td->FILE => "bad-encryption-length.out", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); cleanup(); $td->report($n_tests);