From c5ed1b8075f412b2e9cfd9cf01f41ba04d3af2bc Mon Sep 17 00:00:00 2001 From: Jay Berkenbilt Date: Sat, 22 Jun 2019 20:45:10 -0400 Subject: [PATCH] Handle invalid encryption Length (fixes #333) --- ChangeLog | 3 ++ libqpdf/QPDF_encryption.cc | 42 ++++++++++++++++------- manual/qpdf-manual.xml | 8 +++++ qpdf/qtest/qpdf.test | 7 +++- qpdf/qtest/qpdf/bad-encryption-length.out | 17 +++++++++ qpdf/qtest/qpdf/bad-encryption-length.pdf | 39 +++++++++++++++++++++ 6 files changed, 102 insertions(+), 14 deletions(-) create mode 100644 qpdf/qtest/qpdf/bad-encryption-length.out create mode 100644 qpdf/qtest/qpdf/bad-encryption-length.pdf diff --git a/ChangeLog b/ChangeLog index efb1d789..63f0233a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,8 @@ 2019-06-22 Jay Berkenbilt + * Handle encrypted files with missing or invalid /Length entries + in the encryption dictionary. + * QPDFWriter: allow calling set*EncryptionParameters before calling setFilename. Fixes #336. diff --git a/libqpdf/QPDF_encryption.cc b/libqpdf/QPDF_encryption.cc index b5f16e0c..b15cb29d 100644 --- a/libqpdf/QPDF_encryption.cc +++ b/libqpdf/QPDF_encryption.cc @@ -935,22 +935,38 @@ QPDF::initializeEncryption() pad_short_parameter(Perms, Perms_key_bytes_V5); } - int Length = 40; - if (encryption_dict.getKey("/Length").isInteger()) + int Length = 0; + if (V <= 1) { - Length = encryption_dict.getKey("/Length").getIntValueAsInt(); - if (R < 3) + Length = 40; + } + else if (V == 4) + { + Length = 128; + } + else if (V == 5) + { + Length = 256; + } + else + { + if (encryption_dict.getKey("/Length").isInteger()) { - // Force Length to 40 regardless of what the file says. - Length = 40; + Length = encryption_dict.getKey("/Length").getIntValueAsInt(); + if ((Length % 8) || (Length < 40) || (Length > 128)) + { + Length = 0; + } } - if ((Length % 8) || (Length < 40) || (Length > 256)) - { - throw QPDFExc(qpdf_e_damaged_pdf, this->m->file->getName(), - "encryption dictionary", - this->m->file->getLastOffset(), - "invalid /Length value in encryption dictionary"); - } + if (Length == 0) + { + Length = 128; + } + } + if (Length == 0) + { + // Still no Length? Just take a guess. + Length = 128; } this->m->encp->encrypt_metadata = true; diff --git a/manual/qpdf-manual.xml b/manual/qpdf-manual.xml index 3bd1f259..956f4d19 100644 --- a/manual/qpdf-manual.xml +++ b/manual/qpdf-manual.xml @@ -4353,6 +4353,14 @@ print "\n"; segmentation fault. + + + When reading encrypted files, follow the spec more closely + regarding encryption key length. This allows qpdf to open + encrypted files in most cases when they have invalid or + missing /Length keys in the encryption dictionary. + + diff --git a/qpdf/qtest/qpdf.test b/qpdf/qtest/qpdf.test index 0274f604..0329d35a 100644 --- a/qpdf/qtest/qpdf.test +++ b/qpdf/qtest/qpdf.test @@ -3523,7 +3523,7 @@ foreach my $d (@enc_key) } # Miscellaneous encryption tests -$n_tests += 2; +$n_tests += 3; $td->runtest("set encryption before set filename", {$td->COMMAND => "test_driver 63 minimal.pdf"}, @@ -3534,6 +3534,11 @@ $td->runtest("check file's validity", {$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); show_ntests(); # ---------- diff --git a/qpdf/qtest/qpdf/bad-encryption-length.out b/qpdf/qtest/qpdf/bad-encryption-length.out new file mode 100644 index 00000000..c59b750e --- /dev/null +++ b/qpdf/qtest/qpdf/bad-encryption-length.out @@ -0,0 +1,17 @@ +checking bad-encryption-length.pdf +PDF Version: 1.4 +R = 3 +P = -4 +User password = +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 +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/bad-encryption-length.pdf b/qpdf/qtest/qpdf/bad-encryption-length.pdf new file mode 100644 index 00000000..46fac865 --- /dev/null +++ b/qpdf/qtest/qpdf/bad-encryption-length.pdf @@ -0,0 +1,39 @@ +%PDF-1.4 +%¿÷¢þ +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 48 /Filter /FlateDecode >> +stream +u|+»[ –ØÄksBþSX¼A*–¥O¨<®" J(V¡¢zÉüL[}-šendstream +endobj +5 0 obj +<< /BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font >> +endobj +6 0 obj +[ /PDF /Text ] +endobj +7 0 obj +<< /Filter /Standard /Wength 128 /O <77b8fb098022d3ab34237ea5643c08710ea5123fc5f88bf993a68cca5f12b40f> /P -4 /R 3 /U <72ba254904669105f93c01524fceaa320122456a91bae5134273a6db134c87c4> /V 2 >> +endobj +xref +0 8 +0000000000 65535 f +0000000015 00000 n +0000000064 00000 n +0000000123 00000 n +0000000266 00000 n +0000000384 00000 n +0000000491 00000 n +0000000521 00000 n +trailer << /Root 1 0 R /Size 8 /ID [<2dc7d3d0f5f5b44e5b39ff85e8a4f70c><2dc7d3d0f5f5b44e5b39ff85e8a4f70c>] /Encrypt 7 0 R >> +startxref +728 +%%EOF