Commit 392f2ece51e66dd4c92df3be7f91b637cb54c059
1 parent
e4fa5a3c
Try passwords with different string encodings
Showing
2 changed files
with
85 additions
and
1 deletions
ChangeLog
| 1 | 1 | 2019-01-17 Jay Berkenbilt <ejb@ql.org> |
| 2 | 2 | |
| 3 | + * When attempting to open an encrypted file with a password, if | |
| 4 | + the password doesn't work, try alternative passwords created by | |
| 5 | + re-interpreting the supplied password with different string | |
| 6 | + encodings. This makes qpdf able to recover passwords with | |
| 7 | + non-ASCII characters when either the decryption or encryption | |
| 8 | + operation was performed with an incorrectly encoded password. | |
| 9 | + | |
| 3 | 10 | * Fix data loss bug: qpdf was discarding referenced resources in |
| 4 | 11 | the case in which a page's resource dictionary contained an |
| 5 | 12 | indirect reference for either /Font or /XObject that contained | ... | ... |
qpdf/qpdf.cc
| ... | ... | @@ -3671,7 +3671,7 @@ ImageOptimizer::provideStreamData(int, int, Pipeline* pipeline) |
| 3671 | 3671 | } |
| 3672 | 3672 | |
| 3673 | 3673 | template <typename T> |
| 3674 | -static PointerHolder<QPDF> do_process( | |
| 3674 | +static PointerHolder<QPDF> do_process_once( | |
| 3675 | 3675 | void (QPDF::*fn)(T, char const*), |
| 3676 | 3676 | T item, char const* password, |
| 3677 | 3677 | Options& o, bool empty) |
| ... | ... | @@ -3689,6 +3689,83 @@ static PointerHolder<QPDF> do_process( |
| 3689 | 3689 | return pdf; |
| 3690 | 3690 | } |
| 3691 | 3691 | |
| 3692 | +template <typename T> | |
| 3693 | +static PointerHolder<QPDF> do_process( | |
| 3694 | + void (QPDF::*fn)(T, char const*), | |
| 3695 | + T item, char const* password, | |
| 3696 | + Options& o, bool empty) | |
| 3697 | +{ | |
| 3698 | + // If a password has been specified but doesn't work, try other | |
| 3699 | + // passwords that are equivalent in different character encodings. | |
| 3700 | + // This makes it possible to open PDF files that were encrypted | |
| 3701 | + // using incorrect string encodings. For example, if someone used | |
| 3702 | + // a password encoded in PDF Doc encoding or Windows code page | |
| 3703 | + // 1252 for an AES-encrypted file or a UTF-8-encoded password on | |
| 3704 | + // an RC4-encrypted file, or if the password was properly encoded | |
| 3705 | + // by the password given here was incorrectly encoded, there's a | |
| 3706 | + // good chance we'd succeed here. | |
| 3707 | + | |
| 3708 | + if ((password == 0) || empty || o.password_is_hex_key) | |
| 3709 | + { | |
| 3710 | + // There is no password, so just do the normal processing. | |
| 3711 | + return do_process_once(fn, item, password, o, empty); | |
| 3712 | + } | |
| 3713 | + | |
| 3714 | + // Get a list of otherwise encoded strings. Keep in scope for this | |
| 3715 | + // method. | |
| 3716 | + std::vector<std::string> passwords_str = | |
| 3717 | + QUtil::possible_repaired_encodings(password); | |
| 3718 | + // Represent to char const*, as required by the QPDF class. | |
| 3719 | + std::vector<char const*> passwords; | |
| 3720 | + for (std::vector<std::string>::iterator iter = passwords_str.begin(); | |
| 3721 | + iter != passwords_str.end(); ++iter) | |
| 3722 | + { | |
| 3723 | + passwords.push_back((*iter).c_str()); | |
| 3724 | + } | |
| 3725 | + // We always try the supplied password first because it is the | |
| 3726 | + // first string returned by possible_repaired_encodings. If there | |
| 3727 | + // is more than one option, go ahead and put the supplied password | |
| 3728 | + // at the end so that it's that decoding attempt whose exception | |
| 3729 | + // is thrown. | |
| 3730 | + if (passwords.size() > 1) | |
| 3731 | + { | |
| 3732 | + passwords.push_back(password); | |
| 3733 | + } | |
| 3734 | + | |
| 3735 | + // Try each password. If one works, return the resulting object. | |
| 3736 | + // If they all fail, throw the exception thrown by the final | |
| 3737 | + // attempt, which, like the first attempt, will be with the | |
| 3738 | + // supplied password. | |
| 3739 | + bool warned = false; | |
| 3740 | + for (std::vector<char const*>::iterator iter = passwords.begin(); | |
| 3741 | + iter != passwords.end(); ++iter) | |
| 3742 | + { | |
| 3743 | + try | |
| 3744 | + { | |
| 3745 | + return do_process_once(fn, item, *iter, o, empty); | |
| 3746 | + } | |
| 3747 | + catch (QPDFExc& e) | |
| 3748 | + { | |
| 3749 | + std::vector<char const*>::iterator next = iter; | |
| 3750 | + ++next; | |
| 3751 | + if (next == passwords.end()) | |
| 3752 | + { | |
| 3753 | + throw e; | |
| 3754 | + } | |
| 3755 | + } | |
| 3756 | + if ((! warned) && o.verbose) | |
| 3757 | + { | |
| 3758 | + warned = true; | |
| 3759 | + std::cout << whoami << ": supplied password didn't work;" | |
| 3760 | + << " trying other passwords based on interpreting" | |
| 3761 | + << " password with different string encodings" | |
| 3762 | + << std::endl; | |
| 3763 | + } | |
| 3764 | + } | |
| 3765 | + // Should not be reachable | |
| 3766 | + throw std::logic_error("do_process returned"); | |
| 3767 | +} | |
| 3768 | + | |
| 3692 | 3769 | static PointerHolder<QPDF> process_file(char const* filename, |
| 3693 | 3770 | char const* password, |
| 3694 | 3771 | Options& o) | ... | ... |