Commit 94c79bb8f65e2a13c7bbe03437d2c8354068acb6
1 parent
7e078971
Support --show-encryption without a valid password (fixes #598)
Showing
7 changed files
with
98 additions
and
38 deletions
ChangeLog
| 1 | +2022-09-06 Jay Berkenbilt <ejb@ql.org> | |
| 2 | + | |
| 3 | + * The --show-encryption option now works even if a correct | |
| 4 | + password is not supplied. If you were using --show-encryption to | |
| 5 | + test whether you have the right password, use --requires-password | |
| 6 | + instead. Fixes #598. | |
| 7 | + | |
| 1 | 8 | 2022-09-05 Jay Berkenbilt <ejb@ql.org> |
| 2 | 9 | |
| 3 | 10 | * Add a move constructor to Buffer, making it possible to move | ... | ... |
include/qpdf/QPDFJob.hh
| ... | ... | @@ -490,22 +490,26 @@ class QPDFJob |
| 490 | 490 | std::vector<int> parseNumrange(char const* range, int max); |
| 491 | 491 | |
| 492 | 492 | // Basic file processing |
| 493 | - std::shared_ptr<QPDF> processFile( | |
| 493 | + void processFile( | |
| 494 | + std::shared_ptr<QPDF>&, | |
| 494 | 495 | char const* filename, |
| 495 | 496 | char const* password, |
| 496 | 497 | bool used_for_input, |
| 497 | 498 | bool main_input); |
| 498 | - std::shared_ptr<QPDF> processInputSource( | |
| 499 | + void processInputSource( | |
| 500 | + std::shared_ptr<QPDF>&, | |
| 499 | 501 | std::shared_ptr<InputSource> is, |
| 500 | 502 | char const* password, |
| 501 | 503 | bool used_for_input); |
| 502 | - std::shared_ptr<QPDF> doProcess( | |
| 504 | + void doProcess( | |
| 505 | + std::shared_ptr<QPDF>&, | |
| 503 | 506 | std::function<void(QPDF*, char const*)> fn, |
| 504 | 507 | char const* password, |
| 505 | 508 | bool empty, |
| 506 | 509 | bool used_for_input, |
| 507 | 510 | bool main_input); |
| 508 | - std::shared_ptr<QPDF> doProcessOnce( | |
| 511 | + void doProcessOnce( | |
| 512 | + std::shared_ptr<QPDF>&, | |
| 509 | 513 | std::function<void(QPDF*, char const*)> fn, |
| 510 | 514 | char const* password, |
| 511 | 515 | bool empty, | ... | ... |
libqpdf/QPDFJob.cc
| ... | ... | @@ -564,22 +564,27 @@ void |
| 564 | 564 | QPDFJob::run() |
| 565 | 565 | { |
| 566 | 566 | checkConfiguration(); |
| 567 | - std::shared_ptr<QPDF> pdf_ph; | |
| 567 | + std::shared_ptr<QPDF> pdf_sp; | |
| 568 | 568 | try { |
| 569 | - pdf_ph = | |
| 570 | - processFile(m->infilename.get(), m->password.get(), true, true); | |
| 569 | + processFile(pdf_sp, m->infilename.get(), m->password.get(), true, true); | |
| 571 | 570 | } catch (QPDFExc& e) { |
| 572 | - if ((e.getErrorCode() == qpdf_e_password) && | |
| 573 | - (m->check_is_encrypted || m->check_requires_password)) { | |
| 574 | - // Allow --is-encrypted and --requires-password to | |
| 575 | - // work when an incorrect password is supplied. | |
| 576 | - this->m->encryption_status = | |
| 577 | - qpdf_es_encrypted | qpdf_es_password_incorrect; | |
| 578 | - return; | |
| 571 | + if (e.getErrorCode() == qpdf_e_password) { | |
| 572 | + // Allow certain operations to work when an incorrect | |
| 573 | + // password is supplied. | |
| 574 | + if (m->check_is_encrypted || m->check_requires_password) { | |
| 575 | + this->m->encryption_status = | |
| 576 | + qpdf_es_encrypted | qpdf_es_password_incorrect; | |
| 577 | + return; | |
| 578 | + } | |
| 579 | + if (m->show_encryption && pdf_sp) { | |
| 580 | + this->m->log->info("Incorrect password supplied\n"); | |
| 581 | + showEncryption(*pdf_sp); | |
| 582 | + return; | |
| 583 | + } | |
| 579 | 584 | } |
| 580 | 585 | throw e; |
| 581 | 586 | } |
| 582 | - QPDF& pdf = *pdf_ph; | |
| 587 | + QPDF& pdf = *pdf_sp; | |
| 583 | 588 | if (pdf.isEncrypted()) { |
| 584 | 589 | this->m->encryption_status = qpdf_es_encrypted; |
| 585 | 590 | } |
| ... | ... | @@ -1981,15 +1986,16 @@ QPDFJob::doInspection(QPDF& pdf) |
| 1981 | 1986 | } |
| 1982 | 1987 | } |
| 1983 | 1988 | |
| 1984 | -std::shared_ptr<QPDF> | |
| 1989 | +void | |
| 1985 | 1990 | QPDFJob::doProcessOnce( |
| 1991 | + std::shared_ptr<QPDF>& pdf, | |
| 1986 | 1992 | std::function<void(QPDF*, char const*)> fn, |
| 1987 | 1993 | char const* password, |
| 1988 | 1994 | bool empty, |
| 1989 | 1995 | bool used_for_input, |
| 1990 | 1996 | bool main_input) |
| 1991 | 1997 | { |
| 1992 | - auto pdf = QPDF::create(); | |
| 1998 | + pdf = QPDF::create(); | |
| 1993 | 1999 | setQPDFOptions(*pdf); |
| 1994 | 2000 | if (empty) { |
| 1995 | 2001 | pdf->emptyPDF(); |
| ... | ... | @@ -2002,11 +2008,11 @@ QPDFJob::doProcessOnce( |
| 2002 | 2008 | this->m->max_input_version.updateIfGreater( |
| 2003 | 2009 | pdf->getVersionAsPDFVersion()); |
| 2004 | 2010 | } |
| 2005 | - return pdf; | |
| 2006 | 2011 | } |
| 2007 | 2012 | |
| 2008 | -std::shared_ptr<QPDF> | |
| 2013 | +void | |
| 2009 | 2014 | QPDFJob::doProcess( |
| 2015 | + std::shared_ptr<QPDF>& pdf, | |
| 2010 | 2016 | std::function<void(QPDF*, char const*)> fn, |
| 2011 | 2017 | char const* password, |
| 2012 | 2018 | bool empty, |
| ... | ... | @@ -2037,7 +2043,8 @@ QPDFJob::doProcess( |
| 2037 | 2043 | m->suppress_password_recovery) { |
| 2038 | 2044 | // There is no password, or we're not doing recovery, so just |
| 2039 | 2045 | // do the normal processing with the supplied password. |
| 2040 | - return doProcessOnce(fn, password, empty, used_for_input, main_input); | |
| 2046 | + doProcessOnce(pdf, fn, password, empty, used_for_input, main_input); | |
| 2047 | + return; | |
| 2041 | 2048 | } |
| 2042 | 2049 | |
| 2043 | 2050 | // Get a list of otherwise encoded strings. Keep in scope for this |
| ... | ... | @@ -2065,7 +2072,8 @@ QPDFJob::doProcess( |
| 2065 | 2072 | bool warned = false; |
| 2066 | 2073 | for (auto iter = passwords.begin(); iter != passwords.end(); ++iter) { |
| 2067 | 2074 | try { |
| 2068 | - return doProcessOnce(fn, *iter, empty, used_for_input, main_input); | |
| 2075 | + doProcessOnce(pdf, fn, *iter, empty, used_for_input, main_input); | |
| 2076 | + return; | |
| 2069 | 2077 | } catch (QPDFExc& e) { |
| 2070 | 2078 | auto next = iter; |
| 2071 | 2079 | ++next; |
| ... | ... | @@ -2086,8 +2094,9 @@ QPDFJob::doProcess( |
| 2086 | 2094 | throw std::logic_error("do_process returned"); |
| 2087 | 2095 | } |
| 2088 | 2096 | |
| 2089 | -std::shared_ptr<QPDF> | |
| 2097 | +void | |
| 2090 | 2098 | QPDFJob::processFile( |
| 2099 | + std::shared_ptr<QPDF>& pdf, | |
| 2091 | 2100 | char const* filename, |
| 2092 | 2101 | char const* password, |
| 2093 | 2102 | bool used_for_input, |
| ... | ... | @@ -2096,17 +2105,25 @@ QPDFJob::processFile( |
| 2096 | 2105 | auto f1 = std::mem_fn<void(char const*, char const*)>(&QPDF::processFile); |
| 2097 | 2106 | auto fn = |
| 2098 | 2107 | std::bind(f1, std::placeholders::_1, filename, std::placeholders::_2); |
| 2099 | - return doProcess( | |
| 2100 | - fn, password, strcmp(filename, "") == 0, used_for_input, main_input); | |
| 2108 | + doProcess( | |
| 2109 | + pdf, | |
| 2110 | + fn, | |
| 2111 | + password, | |
| 2112 | + strcmp(filename, "") == 0, | |
| 2113 | + used_for_input, | |
| 2114 | + main_input); | |
| 2101 | 2115 | } |
| 2102 | 2116 | |
| 2103 | -std::shared_ptr<QPDF> | |
| 2117 | +void | |
| 2104 | 2118 | QPDFJob::processInputSource( |
| 2105 | - std::shared_ptr<InputSource> is, char const* password, bool used_for_input) | |
| 2119 | + std::shared_ptr<QPDF>& pdf, | |
| 2120 | + std::shared_ptr<InputSource> is, | |
| 2121 | + char const* password, | |
| 2122 | + bool used_for_input) | |
| 2106 | 2123 | { |
| 2107 | 2124 | auto f1 = std::mem_fn(&QPDF::processInputSource); |
| 2108 | 2125 | auto fn = std::bind(f1, std::placeholders::_1, is, std::placeholders::_2); |
| 2109 | - return doProcess(fn, password, false, used_for_input, false); | |
| 2126 | + doProcess(pdf, fn, password, false, used_for_input, false); | |
| 2110 | 2127 | } |
| 2111 | 2128 | |
| 2112 | 2129 | void |
| ... | ... | @@ -2117,8 +2134,7 @@ QPDFJob::validateUnderOverlay(QPDF& pdf, UnderOverlay* uo) |
| 2117 | 2134 | } |
| 2118 | 2135 | QPDFPageDocumentHelper main_pdh(pdf); |
| 2119 | 2136 | int main_npages = QIntC::to_int(main_pdh.getAllPages().size()); |
| 2120 | - uo->pdf = | |
| 2121 | - processFile(uo->filename.c_str(), uo->password.get(), true, false); | |
| 2137 | + processFile(uo->pdf, uo->filename.c_str(), uo->password.get(), true, false); | |
| 2122 | 2138 | QPDFPageDocumentHelper uo_pdh(*(uo->pdf)); |
| 2123 | 2139 | int uo_npages = QIntC::to_int(uo_pdh.getAllPages().size()); |
| 2124 | 2140 | try { |
| ... | ... | @@ -2375,8 +2391,13 @@ QPDFJob::copyAttachments(QPDF& pdf) |
| 2375 | 2391 | v << prefix << ": copying attachments from " << to_copy.path |
| 2376 | 2392 | << "\n"; |
| 2377 | 2393 | }); |
| 2378 | - auto other = processFile( | |
| 2379 | - to_copy.path.c_str(), to_copy.password.c_str(), false, false); | |
| 2394 | + std::shared_ptr<QPDF> other; | |
| 2395 | + processFile( | |
| 2396 | + other, | |
| 2397 | + to_copy.path.c_str(), | |
| 2398 | + to_copy.password.c_str(), | |
| 2399 | + false, | |
| 2400 | + false); | |
| 2380 | 2401 | QPDFEmbeddedFileDocumentHelper other_efdh(*other); |
| 2381 | 2402 | auto other_attachments = other_efdh.getEmbeddedFiles(); |
| 2382 | 2403 | for (auto const& iter: other_attachments) { |
| ... | ... | @@ -2702,10 +2723,10 @@ QPDFJob::handlePageSpecs( |
| 2702 | 2723 | new FileInputSource(page_spec.filename.c_str()); |
| 2703 | 2724 | is = std::shared_ptr<InputSource>(fis); |
| 2704 | 2725 | } |
| 2705 | - std::shared_ptr<QPDF> qpdf_ph = | |
| 2706 | - processInputSource(is, password, true); | |
| 2707 | - page_heap.push_back(qpdf_ph); | |
| 2708 | - page_spec_qpdfs[page_spec.filename] = qpdf_ph.get(); | |
| 2726 | + std::shared_ptr<QPDF> qpdf_sp; | |
| 2727 | + processInputSource(qpdf_sp, is, password, true); | |
| 2728 | + page_heap.push_back(qpdf_sp); | |
| 2729 | + page_spec_qpdfs[page_spec.filename] = qpdf_sp.get(); | |
| 2709 | 2730 | if (cis) { |
| 2710 | 2731 | cis->stayOpen(false); |
| 2711 | 2732 | page_spec_cfis[page_spec.filename] = cis; |
| ... | ... | @@ -3209,7 +3230,9 @@ QPDFJob::setWriterOptions(QPDF& pdf, QPDFWriter& w) |
| 3209 | 3230 | w.setSuppressOriginalObjectIDs(true); |
| 3210 | 3231 | } |
| 3211 | 3232 | if (m->copy_encryption) { |
| 3212 | - std::shared_ptr<QPDF> encryption_pdf = processFile( | |
| 3233 | + std::shared_ptr<QPDF> encryption_pdf; | |
| 3234 | + processFile( | |
| 3235 | + encryption_pdf, | |
| 3213 | 3236 | m->encryption_file.c_str(), |
| 3214 | 3237 | m->encryption_file_password.get(), |
| 3215 | 3238 | false, | ... | ... |
manual/release-notes.rst
| ... | ... | @@ -85,6 +85,11 @@ For a detailed list of changes, please see the file |
| 85 | 85 | |
| 86 | 86 | - CLI: breaking changes |
| 87 | 87 | |
| 88 | + - The :qpdf:ref:`--show-encryption` flag now provides encryption | |
| 89 | + information even if a correct password is not supplied. If you | |
| 90 | + were relying on its not working in this case, see | |
| 91 | + :qpdf:ref:`--requires-password` for a reliable test. | |
| 92 | + | |
| 88 | 93 | - The default json output version when :qpdf:ref:`--json` is |
| 89 | 94 | specified has been changed from ``1`` to ``latest``, which is |
| 90 | 95 | now ``2``. | ... | ... |
qpdf/qtest/encryption.test
| ... | ... | @@ -112,7 +112,7 @@ my @encrypted_files = |
| 112 | 112 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], |
| 113 | 113 | ); |
| 114 | 114 | |
| 115 | -$n_tests += 8 + (2 * (@encrypted_files)) + (7 * (@encrypted_files - 6)) + 9; | |
| 115 | +$n_tests += 8 + (2 * (@encrypted_files)) + (7 * (@encrypted_files - 6)) + 10; | |
| 116 | 116 | |
| 117 | 117 | $td->runtest("encrypted file", |
| 118 | 118 | {$td->COMMAND => "test_driver 2 encrypted-with-images.pdf"}, |
| ... | ... | @@ -365,6 +365,12 @@ $td->runtest("C API: invalid password", |
| 365 | 365 | "qpdf-ctest 2 enc-R2,V1,U=view,O=view.pdf '' a.qdf"}, |
| 366 | 366 | {$td->FILE => "c-invalid-password.out", $td->EXIT_STATUS => 0}, |
| 367 | 367 | $td->NORMALIZE_NEWLINES); |
| 368 | +$td->runtest("show-encryption works invalid password", | |
| 369 | + {$td->COMMAND => "qpdf --show-encryption --password=quack" . | |
| 370 | + " enc-R2,V1,U=view,O=view.pdf"}, | |
| 371 | + {$td->FILE => "invalid-password-encrypt.out", | |
| 372 | + $td->EXIT_STATUS => 0}, | |
| 373 | + $td->NORMALIZE_NEWLINES); | |
| 368 | 374 | |
| 369 | 375 | my @cenc = ( |
| 370 | 376 | [11, 'hybrid-xref.pdf', "''", 'r2', "", ""], | ... | ... |
qpdf/qtest/qpdf/invalid-password-encrypt.out
0 → 100644
| 1 | +Incorrect password supplied | |
| 2 | +R = 2 | |
| 3 | +P = -64 | |
| 4 | +User password = | |
| 5 | +extract for accessibility: not allowed | |
| 6 | +extract for any purpose: not allowed | |
| 7 | +print low resolution: not allowed | |
| 8 | +print high resolution: not allowed | |
| 9 | +modify document assembly: not allowed | |
| 10 | +modify forms: not allowed | |
| 11 | +modify annotations: not allowed | |
| 12 | +modify other: not allowed | |
| 13 | +modify anything: not allowed | ... | ... |
qpdf/qtest/unicode-password.test
| ... | ... | @@ -147,9 +147,11 @@ foreach my $d (@unicode_pw_cases) |
| 147 | 147 | } |
| 148 | 148 | my $r_output = ""; |
| 149 | 149 | $r_output .= "trying other\n" if $tried_others; |
| 150 | + my $arg = "--show-encryption"; | |
| 150 | 151 | if ($xfail) |
| 151 | 152 | { |
| 152 | 153 | $r_output .= "qpdf: a.pdf: invalid password\n"; |
| 154 | + $arg = "--check"; | |
| 153 | 155 | } |
| 154 | 156 | else |
| 155 | 157 | { |
| ... | ... | @@ -162,7 +164,7 @@ foreach my $d (@unicode_pw_cases) |
| 162 | 164 | $r_xargs .= $strict ? ' --suppress-password-recovery' : ''; |
| 163 | 165 | $td->runtest("decrypt $pw, $r_encoding, strict=$strict", |
| 164 | 166 | {$td->COMMAND => |
| 165 | - "qpdf --show-encryption --verbose" . | |
| 167 | + "qpdf $arg --verbose" . | |
| 166 | 168 | " $r_xargs a.pdf \@$r_pfile", |
| 167 | 169 | $td->FILTER => "perl show-unicode-encryption.pl"}, |
| 168 | 170 | {$td->STRING => "$r_output", | ... | ... |