Commit 392f2ece51e66dd4c92df3be7f91b637cb54c059

Authored by Jay Berkenbilt
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&lt;QPDF&gt; 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)
... ...