diff --git a/ChangeLog b/ChangeLog index 7772387f..34cf93ef 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +2017-08-11 Jay Berkenbilt + + * Handle encrypted files whose encryption parameters are too + short. Fixes #96. + 2017-08-10 Jay Berkenbilt * Remove dependency on libpcre. diff --git a/libqpdf/QPDF_encryption.cc b/libqpdf/QPDF_encryption.cc index 71c28d0e..6c0c97ef 100644 --- a/libqpdf/QPDF_encryption.cc +++ b/libqpdf/QPDF_encryption.cc @@ -340,6 +340,16 @@ hash_V5(std::string const& password, return result; } +static +void pad_short_parameter(std::string& param, unsigned int max_len) +{ + if (param.length() < max_len) + { + QTC::TC("qpdf", "QPDF_encryption pad short parameter"); + param.append(max_len - param.length(), '\0'); + } +} + std::string QPDF::compute_data_key(std::string const& encryption_key, int objid, int generation, bool use_aes, @@ -876,6 +886,8 @@ QPDF::initializeEncryption() if (V < 5) { + pad_short_parameter(O, key_bytes); + pad_short_parameter(U, key_bytes); if (! ((O.length() == key_bytes) && (U.length() == key_bytes))) { throw QPDFExc(qpdf_e_damaged_pdf, this->file->getName(), @@ -899,6 +911,11 @@ QPDF::initializeEncryption() UE = encryption_dict.getKey("/UE").getStringValue(); Perms = encryption_dict.getKey("/Perms").getStringValue(); + pad_short_parameter(O, OU_key_bytes_V5); + pad_short_parameter(U, OU_key_bytes_V5); + pad_short_parameter(OE, OUE_key_bytes_V5); + pad_short_parameter(UE, OUE_key_bytes_V5); + pad_short_parameter(Perms, Perms_key_bytes_V5); if ((O.length() < OU_key_bytes_V5) || (U.length() < OU_key_bytes_V5) || (OE.length() < OUE_key_bytes_V5) || diff --git a/qpdf/qpdf.testcov b/qpdf/qpdf.testcov index c08ed721..0038aa34 100644 --- a/qpdf/qpdf.testcov +++ b/qpdf/qpdf.testcov @@ -293,3 +293,4 @@ QPDF ignore first space in xref entry 0 QPDF ignore first extra space in xref entry 0 QPDF ignore second extra space in xref entry 0 QPDF ignore length error xref entry 0 +QPDF_encryption pad short parameter 0 diff --git a/qpdf/qtest/qpdf.test b/qpdf/qtest/qpdf.test index edd9f1ec..12fb5dd2 100644 --- a/qpdf/qtest/qpdf.test +++ b/qpdf/qtest/qpdf.test @@ -232,7 +232,7 @@ foreach my $d (@bug_tests) show_ntests(); # ---------- $td->notify("--- Miscellaneous Tests ---"); -$n_tests += 87; +$n_tests += 88; $td->runtest("qpdf version", {$td->COMMAND => "qpdf --version"}, @@ -676,6 +676,16 @@ $td->runtest("recoverable xref errors", $td->EXIT_STATUS => 3}, $td->NORMALIZE_NEWLINES); +# A file was emailed privately with issue 96. short-O-U.pdf was +# created by copying encryption parameters from that file. It exhibits +# the same behavior as the original file. +$td->runtest("short /O or /U", + {$td->COMMAND => + "qpdf --password=19723102477 --check short-O-U.pdf"}, + {$td->FILE => "short-O-U.out", + $td->EXIT_STATUS => 0}, + $td->NORMALIZE_NEWLINES); + show_ntests(); # ---------- $td->notify("--- Single Page ---"); diff --git a/qpdf/qtest/qpdf/short-O-U.out b/qpdf/qtest/qpdf/short-O-U.out new file mode 100644 index 00000000..e0b19742 --- /dev/null +++ b/qpdf/qtest/qpdf/short-O-U.out @@ -0,0 +1,20 @@ +checking short-O-U.pdf +PDF Version: 1.6 +R = 4 +P = -4 +User password = 19723102477 +extract for accessibility: allowed +extract for any purpose: allowed +print low resolution: allowed +print high resolution: allowed +modify document assembly: allowed +modify forms: allowed +modify annotations: allowed +modify other: allowed +modify anything: allowed +stream encryption method: AESv2 +string encryption method: AESv2 +file encryption method: AESv2 +File is not linearized +No syntax or stream encoding errors found; the file may still contain +errors that qpdf cannot detect diff --git a/qpdf/qtest/qpdf/short-O-U.pdf b/qpdf/qtest/qpdf/short-O-U.pdf new file mode 100644 index 00000000..998a3f96 --- /dev/null +++ b/qpdf/qtest/qpdf/short-O-U.pdf @@ -0,0 +1,39 @@ +%PDF-1.6 +% +1 0 obj +<< /Pages 2 0 R /Type /Catalog >> +endobj +2 0 obj +<< /Count 1 /Kids [ 3 0 R ] /Type /Pages >> +endobj +3 0 obj +<< /Contents 4 0 R /MediaBox [ 0 0 612 792 ] /Parent 2 0 R /Resources << /Font << /F1 5 0 R >> /ProcSet 6 0 R >> /Type /Page >> +endobj +4 0 obj +<< /Length 80 /Filter /FlateDecode >> +stream +tiUj,B\hlI-Z䠢D@B;dhR.rYb\{cBcC?{av> +endobj +6 0 obj +[ /PDF /Text ] +endobj +7 0 obj +<< /CF << /StdCF << /AuthEvent /DocOpen /CFM /AESV2 /Length 16 >> >> /Filter /Standard /Length 128 /O /P -4 /R 4 /StmF /StdCF /StrF /StdCF /U <82f858c956fc27a1d051b09b9fb19b72> /V 4 >> +endobj +xref +0 8 +0000000000 65535 f +0000000015 00000 n +0000000064 00000 n +0000000123 00000 n +0000000266 00000 n +0000000416 00000 n +0000000523 00000 n +0000000553 00000 n +trailer << /Root 1 0 R /Size 8 /ID [<35653732633166343063386438346132>] /Encrypt 7 0 R >> +startxref +820 +%%EOF