Commit a051a16234726deb433b53537713b87cd8a412ba
1 parent
cb3c4c55
Refactor encryption initialization by encapsulating logic in `EncryptionParamete…
…rs::initialize` and replacing shared pointers with references for improved clarity and maintainability.
Showing
3 changed files
with
99 additions
and
103 deletions
include/qpdf/QPDF.hh
| ... | ... | @@ -964,8 +964,7 @@ class QPDF |
| 964 | 964 | void insertPageobjToPage(QPDFObjectHandle const& obj, int pos, bool check_duplicate); |
| 965 | 965 | |
| 966 | 966 | // methods to support encryption -- implemented in QPDF_encryption.cc |
| 967 | - static encryption_method_e | |
| 968 | - interpretCF(std::shared_ptr<EncryptionParameters> encp, QPDFObjectHandle); | |
| 967 | + static encryption_method_e interpretCF(EncryptionParameters& encp, QPDFObjectHandle const& cf); | |
| 969 | 968 | void initializeEncryption(); |
| 970 | 969 | static std::string |
| 971 | 970 | getKeyForObject(std::shared_ptr<EncryptionParameters> encp, QPDFObjGen og, bool use_aes); | ... | ... |
libqpdf/QPDF_encryption.cc
| ... | ... | @@ -12,6 +12,7 @@ |
| 12 | 12 | #include <qpdf/Pl_Buffer.hh> |
| 13 | 13 | #include <qpdf/Pl_RC4.hh> |
| 14 | 14 | #include <qpdf/Pl_SHA2.hh> |
| 15 | +#include <qpdf/QPDFObjectHandle_private.hh> | |
| 15 | 16 | #include <qpdf/QTC.hh> |
| 16 | 17 | #include <qpdf/QUtil.hh> |
| 17 | 18 | #include <qpdf/RC4.hh> |
| ... | ... | @@ -686,12 +687,12 @@ QPDF::EncryptionData::recover_encryption_key_with_password( |
| 686 | 687 | } |
| 687 | 688 | |
| 688 | 689 | QPDF::encryption_method_e |
| 689 | -QPDF::interpretCF(std::shared_ptr<EncryptionParameters> encp, QPDFObjectHandle cf) | |
| 690 | +QPDF::interpretCF(EncryptionParameters& encp, QPDFObjectHandle const& cf) | |
| 690 | 691 | { |
| 691 | 692 | if (cf.isName()) { |
| 692 | 693 | std::string filter = cf.getName(); |
| 693 | - auto it = encp->crypt_filters.find(filter); | |
| 694 | - if (it != encp->crypt_filters.end()) { | |
| 694 | + auto it = encp.crypt_filters.find(filter); | |
| 695 | + if (it != encp.crypt_filters.end()) { | |
| 695 | 696 | return it->second; |
| 696 | 697 | } |
| 697 | 698 | if (filter == "/Identity") { |
| ... | ... | @@ -706,62 +707,75 @@ QPDF::interpretCF(std::shared_ptr<EncryptionParameters> encp, QPDFObjectHandle c |
| 706 | 707 | void |
| 707 | 708 | QPDF::initializeEncryption() |
| 708 | 709 | { |
| 709 | - if (m->encp->encryption_initialized) { | |
| 710 | + m->encp->initialize(*this); | |
| 711 | +} | |
| 712 | + | |
| 713 | +void | |
| 714 | +QPDF::EncryptionParameters::initialize(QPDF& qpdf) | |
| 715 | +{ | |
| 716 | + if (encryption_initialized) { | |
| 710 | 717 | return; |
| 711 | 718 | } |
| 712 | - m->encp->encryption_initialized = true; | |
| 719 | + encryption_initialized = true; | |
| 720 | + | |
| 721 | + auto& qm = *qpdf.m; | |
| 722 | + auto& trailer = qm.trailer; | |
| 723 | + auto& file = qm.file; | |
| 724 | + | |
| 725 | + auto warn_damaged_pdf = [&qpdf](std::string const& msg) { | |
| 726 | + qpdf.warn(qpdf.damagedPDF("encryption dictionary", msg)); | |
| 727 | + }; | |
| 728 | + auto throw_damaged_pdf = [&qpdf](std::string const& msg) { | |
| 729 | + throw qpdf.damagedPDF("encryption dictionary", msg); | |
| 730 | + }; | |
| 731 | + auto unsupported = [&file](std::string const& msg) -> QPDFExc { | |
| 732 | + return { | |
| 733 | + qpdf_e_unsupported, | |
| 734 | + file->getName(), | |
| 735 | + "encryption dictionary", | |
| 736 | + file->getLastOffset(), | |
| 737 | + msg}; | |
| 738 | + }; | |
| 713 | 739 | |
| 714 | 740 | // After we initialize encryption parameters, we must use stored key information and never look |
| 715 | 741 | // at /Encrypt again. Otherwise, things could go wrong if someone mutates the encryption |
| 716 | 742 | // dictionary. |
| 717 | 743 | |
| 718 | - if (!m->trailer.hasKey("/Encrypt")) { | |
| 744 | + if (!trailer.hasKey("/Encrypt")) { | |
| 719 | 745 | return; |
| 720 | 746 | } |
| 721 | 747 | |
| 722 | 748 | // Go ahead and set m->encrypted here. That way, isEncrypted will return true even if there |
| 723 | 749 | // were errors reading the encryption dictionary. |
| 724 | - m->encp->encrypted = true; | |
| 750 | + encrypted = true; | |
| 725 | 751 | |
| 726 | 752 | std::string id1; |
| 727 | - QPDFObjectHandle id_obj = m->trailer.getKey("/ID"); | |
| 728 | - if ((id_obj.isArray() && (id_obj.getArrayNItems() == 2) && id_obj.getArrayItem(0).isString())) { | |
| 729 | - id1 = id_obj.getArrayItem(0).getStringValue(); | |
| 730 | - } else { | |
| 753 | + auto id_obj = trailer.getKey("/ID"); | |
| 754 | + if (!id_obj.isArray() || id_obj.getArrayNItems() != 2 || !id_obj.getArrayItem(0).isString()) { | |
| 731 | 755 | // Treating a missing ID as the empty string enables qpdf to decrypt some invalid encrypted |
| 732 | 756 | // files with no /ID that poppler can read but Adobe Reader can't. |
| 733 | - warn(damagedPDF("trailer", "invalid /ID in trailer dictionary")); | |
| 757 | + qpdf.warn(qpdf.damagedPDF("trailer", "invalid /ID in trailer dictionary")); | |
| 758 | + } else { | |
| 759 | + id1 = id_obj.getArrayItem(0).getStringValue(); | |
| 734 | 760 | } |
| 735 | 761 | |
| 736 | - QPDFObjectHandle encryption_dict = m->trailer.getKey("/Encrypt"); | |
| 762 | + auto encryption_dict = trailer.getKey("/Encrypt"); | |
| 737 | 763 | if (!encryption_dict.isDictionary()) { |
| 738 | - throw damagedPDF("/Encrypt in trailer dictionary is not a dictionary"); | |
| 764 | + throw qpdf.damagedPDF("/Encrypt in trailer dictionary is not a dictionary"); | |
| 739 | 765 | } |
| 740 | 766 | |
| 741 | 767 | if (!(encryption_dict.getKey("/Filter").isName() && |
| 742 | 768 | (encryption_dict.getKey("/Filter").getName() == "/Standard"))) { |
| 743 | - throw QPDFExc( | |
| 744 | - qpdf_e_unsupported, | |
| 745 | - m->file->getName(), | |
| 746 | - "encryption dictionary", | |
| 747 | - m->file->getLastOffset(), | |
| 748 | - "unsupported encryption filter"); | |
| 769 | + throw unsupported("unsupported encryption filter"); | |
| 749 | 770 | } |
| 750 | 771 | if (!encryption_dict.getKey("/SubFilter").isNull()) { |
| 751 | - warn( | |
| 752 | - qpdf_e_unsupported, | |
| 753 | - "encryption dictionary", | |
| 754 | - m->file->getLastOffset(), | |
| 755 | - "file uses encryption SubFilters, which qpdf does not support"); | |
| 772 | + qpdf.warn(unsupported("file uses encryption SubFilters, which qpdf does not support")); | |
| 756 | 773 | } |
| 757 | 774 | |
| 758 | 775 | if (!(encryption_dict.getKey("/V").isInteger() && encryption_dict.getKey("/R").isInteger() && |
| 759 | 776 | encryption_dict.getKey("/O").isString() && encryption_dict.getKey("/U").isString() && |
| 760 | 777 | encryption_dict.getKey("/P").isInteger())) { |
| 761 | - throw damagedPDF( | |
| 762 | - "encryption dictionary", | |
| 763 | - "some encryption dictionary parameters are missing or the wrong " | |
| 764 | - "type"); | |
| 778 | + throw_damaged_pdf("some encryption dictionary parameters are missing or the wrong type"); | |
| 765 | 779 | } |
| 766 | 780 | |
| 767 | 781 | int V = encryption_dict.getKey("/V").getIntValueAsInt(); |
| ... | ... | @@ -772,18 +786,15 @@ QPDF::initializeEncryption() |
| 772 | 786 | |
| 773 | 787 | // If supporting new encryption R/V values, remember to update error message inside this if |
| 774 | 788 | // statement. |
| 775 | - if (!(((R >= 2) && (R <= 6)) && ((V == 1) || (V == 2) || (V == 4) || (V == 5)))) { | |
| 776 | - throw QPDFExc( | |
| 777 | - qpdf_e_unsupported, | |
| 778 | - m->file->getName(), | |
| 779 | - "encryption dictionary", | |
| 780 | - m->file->getLastOffset(), | |
| 789 | + if (!(2 <= R && R <= 6 && (V == 1 || V == 2 || V == 4 || V == 5))) { | |
| 790 | + throw unsupported( | |
| 781 | 791 | "Unsupported /R or /V in encryption dictionary; R = " + std::to_string(R) + |
| 782 | - " (max 6), V = " + std::to_string(V) + " (max 5)"); | |
| 792 | + " (max 6), V = " + std::to_string(V) + " (max 5)"); | |
| 783 | 793 | } |
| 784 | 794 | |
| 785 | - m->encp->encryption_V = V; | |
| 786 | - m->encp->encryption_R = R; | |
| 795 | + encryption_P = P; | |
| 796 | + encryption_V = V; | |
| 797 | + encryption_R = R; | |
| 787 | 798 | |
| 788 | 799 | // OE, UE, and Perms are only present if V >= 5. |
| 789 | 800 | std::string OE; |
| ... | ... | @@ -794,19 +805,15 @@ QPDF::initializeEncryption() |
| 794 | 805 | // These must be exactly the right number of bytes. |
| 795 | 806 | pad_short_parameter(O, key_bytes); |
| 796 | 807 | pad_short_parameter(U, key_bytes); |
| 797 | - if (!((O.length() == key_bytes) && (U.length() == key_bytes))) { | |
| 798 | - throw damagedPDF( | |
| 799 | - "encryption dictionary", | |
| 800 | - "incorrect length for /O and/or /U in encryption dictionary"); | |
| 808 | + if (!(O.length() == key_bytes && U.length() == key_bytes)) { | |
| 809 | + throw_damaged_pdf("incorrect length for /O and/or /U in encryption dictionary"); | |
| 801 | 810 | } |
| 802 | 811 | } else { |
| 803 | 812 | if (!(encryption_dict.getKey("/OE").isString() && |
| 804 | 813 | encryption_dict.getKey("/UE").isString() && |
| 805 | 814 | encryption_dict.getKey("/Perms").isString())) { |
| 806 | - throw damagedPDF( | |
| 807 | - "encryption dictionary", | |
| 808 | - "some V=5 encryption dictionary parameters are missing or the " | |
| 809 | - "wrong type"); | |
| 815 | + throw_damaged_pdf( | |
| 816 | + "some V=5 encryption dictionary parameters are missing or the wrong type"); | |
| 810 | 817 | } |
| 811 | 818 | OE = encryption_dict.getKey("/OE").getStringValue(); |
| 812 | 819 | UE = encryption_dict.getKey("/UE").getStringValue(); |
| ... | ... | @@ -820,7 +827,7 @@ QPDF::initializeEncryption() |
| 820 | 827 | pad_short_parameter(Perms, Perms_key_bytes_V5); |
| 821 | 828 | } |
| 822 | 829 | |
| 823 | - int Length = 0; | |
| 830 | + int Length = 128; // Just take a guess. | |
| 824 | 831 | if (V <= 1) { |
| 825 | 832 | Length = 40; |
| 826 | 833 | } else if (V == 4) { |
| ... | ... | @@ -830,25 +837,20 @@ QPDF::initializeEncryption() |
| 830 | 837 | } else { |
| 831 | 838 | if (encryption_dict.getKey("/Length").isInteger()) { |
| 832 | 839 | Length = encryption_dict.getKey("/Length").getIntValueAsInt(); |
| 833 | - if ((Length % 8) || (Length < 40) || (Length > 128)) { | |
| 834 | - Length = 0; | |
| 840 | + if (Length % 8 || Length < 40 || Length > 128) { | |
| 841 | + Length = 128; // Just take a guess. | |
| 835 | 842 | } |
| 836 | 843 | } |
| 837 | 844 | } |
| 838 | - if (Length == 0) { | |
| 839 | - // Still no Length? Just take a guess. | |
| 840 | - Length = 128; | |
| 841 | - } | |
| 842 | 845 | |
| 843 | - m->encp->encrypt_metadata = true; | |
| 844 | - if ((V >= 4) && (encryption_dict.getKey("/EncryptMetadata").isBool())) { | |
| 845 | - m->encp->encrypt_metadata = encryption_dict.getKey("/EncryptMetadata").getBoolValue(); | |
| 846 | + encrypt_metadata = true; | |
| 847 | + if (V >= 4 && encryption_dict.getKey("/EncryptMetadata").isBool()) { | |
| 848 | + encrypt_metadata = encryption_dict.getKey("/EncryptMetadata").getBoolValue(); | |
| 846 | 849 | } |
| 847 | 850 | |
| 848 | - if ((V == 4) || (V == 5)) { | |
| 849 | - QPDFObjectHandle CF = encryption_dict.getKey("/CF"); | |
| 850 | - for (auto const& filter: CF.getKeys()) { | |
| 851 | - QPDFObjectHandle cdict = CF.getKey(filter); | |
| 851 | + if (V == 4 || V == 5) { | |
| 852 | + auto CF = encryption_dict.getKey("/CF"); | |
| 853 | + for (auto const& [filter, cdict]: CF.as_dictionary()) { | |
| 852 | 854 | if (cdict.isDictionary()) { |
| 853 | 855 | encryption_method_e method = e_none; |
| 854 | 856 | if (cdict.getKey("/CFM").isName()) { |
| ... | ... | @@ -867,16 +869,13 @@ QPDF::initializeEncryption() |
| 867 | 869 | method = e_unknown; |
| 868 | 870 | } |
| 869 | 871 | } |
| 870 | - m->encp->crypt_filters[filter] = method; | |
| 872 | + crypt_filters[filter] = method; | |
| 871 | 873 | } |
| 872 | 874 | } |
| 873 | 875 | |
| 874 | - QPDFObjectHandle StmF = encryption_dict.getKey("/StmF"); | |
| 875 | - QPDFObjectHandle StrF = encryption_dict.getKey("/StrF"); | |
| 876 | - QPDFObjectHandle EFF = encryption_dict.getKey("/EFF"); | |
| 877 | - m->encp->cf_stream = interpretCF(m->encp, StmF); | |
| 878 | - m->encp->cf_string = interpretCF(m->encp, StrF); | |
| 879 | - if (EFF.isName()) { | |
| 876 | + cf_stream = interpretCF(*this, encryption_dict.getKey("/StmF")); | |
| 877 | + cf_string = interpretCF(*this, encryption_dict.getKey("/StrF")); | |
| 878 | + if (auto EFF = encryption_dict.getKey("/EFF"); EFF.isName()) { | |
| 880 | 879 | // qpdf does not use this for anything other than informational purposes. This is |
| 881 | 880 | // intended to instruct conforming writers on which crypt filter should be used when new |
| 882 | 881 | // file attachments are added to a PDF file, but qpdf never generates encrypted files |
| ... | ... | @@ -886,54 +885,50 @@ QPDF::initializeEncryption() |
| 886 | 885 | // the way I was imagining. Still, providing this information could be useful when |
| 887 | 886 | // looking at a file generated by something else, such as Acrobat when specifying that |
| 888 | 887 | // only attachments should be encrypted. |
| 889 | - m->encp->cf_file = interpretCF(m->encp, EFF); | |
| 888 | + cf_file = interpretCF(*this, EFF); | |
| 890 | 889 | } else { |
| 891 | - m->encp->cf_file = m->encp->cf_stream; | |
| 890 | + cf_file = cf_stream; | |
| 892 | 891 | } |
| 893 | 892 | } |
| 894 | 893 | |
| 895 | - EncryptionData data(V, R, Length / 8, P, O, U, OE, UE, Perms, id1, m->encp->encrypt_metadata); | |
| 896 | - if (m->provided_password_is_hex_key) { | |
| 894 | + EncryptionData data(V, R, Length / 8, P, O, U, OE, UE, Perms, id1, encrypt_metadata); | |
| 895 | + if (qm.provided_password_is_hex_key) { | |
| 897 | 896 | // ignore passwords in file |
| 898 | - } else { | |
| 899 | - m->encp->owner_password_matched = | |
| 900 | - data.check_owner_password(m->encp->user_password, m->encp->provided_password); | |
| 901 | - if (m->encp->owner_password_matched && (V < 5)) { | |
| 902 | - // password supplied was owner password; user_password has been initialized for V < 5 | |
| 903 | - if (getTrimmedUserPassword() == m->encp->provided_password) { | |
| 904 | - m->encp->user_password_matched = true; | |
| 905 | - QTC::TC("qpdf", "QPDF_encryption user matches owner V < 5"); | |
| 906 | - } | |
| 907 | - } else { | |
| 908 | - m->encp->user_password_matched = data.check_user_password(m->encp->provided_password); | |
| 909 | - if (m->encp->user_password_matched) { | |
| 910 | - m->encp->user_password = m->encp->provided_password; | |
| 911 | - } | |
| 912 | - } | |
| 913 | - if (m->encp->user_password_matched && m->encp->owner_password_matched) { | |
| 914 | - QTC::TC("qpdf", "QPDF_encryption same password", (V < 5) ? 0 : 1); | |
| 897 | + encryption_key = QUtil::hex_decode(provided_password); | |
| 898 | + return; | |
| 899 | + } | |
| 900 | + | |
| 901 | + owner_password_matched = data.check_owner_password(user_password, provided_password); | |
| 902 | + if (owner_password_matched && V < 5) { | |
| 903 | + // password supplied was owner password; user_password has been initialized for V < 5 | |
| 904 | + if (qpdf.getTrimmedUserPassword() == provided_password) { | |
| 905 | + user_password_matched = true; | |
| 906 | + QTC::TC("qpdf", "QPDF_encryption user matches owner V < 5"); | |
| 915 | 907 | } |
| 916 | - if (!(m->encp->owner_password_matched || m->encp->user_password_matched)) { | |
| 917 | - throw QPDFExc(qpdf_e_password, m->file->getName(), "", 0, "invalid password"); | |
| 908 | + } else { | |
| 909 | + user_password_matched = data.check_user_password(provided_password); | |
| 910 | + if (user_password_matched) { | |
| 911 | + user_password = provided_password; | |
| 918 | 912 | } |
| 919 | 913 | } |
| 914 | + if (user_password_matched && owner_password_matched) { | |
| 915 | + QTC::TC("qpdf", "QPDF_encryption same password", (V < 5) ? 0 : 1); | |
| 916 | + } | |
| 917 | + if (!(owner_password_matched || user_password_matched)) { | |
| 918 | + throw QPDFExc(qpdf_e_password, file->getName(), "", 0, "invalid password"); | |
| 919 | + } | |
| 920 | 920 | |
| 921 | - if (m->provided_password_is_hex_key) { | |
| 922 | - m->encp->encryption_key = QUtil::hex_decode(m->encp->provided_password); | |
| 923 | - } else if (V < 5) { | |
| 921 | + if (V < 5) { | |
| 924 | 922 | // For V < 5, the user password is encrypted with the owner password, and the user password |
| 925 | 923 | // is always used for computing the encryption key. |
| 926 | - m->encp->encryption_key = compute_encryption_key(m->encp->user_password, data); | |
| 924 | + encryption_key = data.compute_encryption_key(user_password); | |
| 927 | 925 | } else { |
| 928 | 926 | // For V >= 5, either password can be used independently to compute the encryption key, and |
| 929 | 927 | // neither password can be used to recover the other. |
| 930 | 928 | bool perms_valid; |
| 931 | - m->encp->encryption_key = | |
| 932 | - data.recover_encryption_key_with_password(m->encp->provided_password, perms_valid); | |
| 929 | + encryption_key = data.recover_encryption_key_with_password(provided_password, perms_valid); | |
| 933 | 930 | if (!perms_valid) { |
| 934 | - warn(damagedPDF( | |
| 935 | - "encryption dictionary", | |
| 936 | - "/Perms field in encryption dictionary doesn't match expected value")); | |
| 931 | + warn_damaged_pdf("/Perms field in encryption dictionary doesn't match expected value"); | |
| 937 | 932 | } |
| 938 | 933 | } |
| 939 | 934 | } |
| ... | ... | @@ -1057,7 +1052,7 @@ QPDF::decryptStream( |
| 1057 | 1052 | QPDFObjectHandle decode_parms = stream_dict.getKey("/DecodeParms"); |
| 1058 | 1053 | if (decode_parms.isDictionaryOfType("/CryptFilterDecodeParms")) { |
| 1059 | 1054 | QTC::TC("qpdf", "QPDF_encryption stream crypt filter"); |
| 1060 | - method = interpretCF(encp, decode_parms.getKey("/Name")); | |
| 1055 | + method = interpretCF(*encp, decode_parms.getKey("/Name")); | |
| 1061 | 1056 | method_source = "stream's Crypt decode parameters"; |
| 1062 | 1057 | } |
| 1063 | 1058 | } else if ( |
| ... | ... | @@ -1072,7 +1067,7 @@ QPDF::decryptStream( |
| 1072 | 1067 | if (crypt_params.isDictionary() && |
| 1073 | 1068 | crypt_params.getKey("/Name").isName()) { |
| 1074 | 1069 | QTC::TC("qpdf", "QPDF_encrypt crypt array"); |
| 1075 | - method = interpretCF(encp, crypt_params.getKey("/Name")); | |
| 1070 | + method = interpretCF(*encp, crypt_params.getKey("/Name")); | |
| 1076 | 1071 | method_source = "stream's Crypt decode parameters (array)"; |
| 1077 | 1072 | } |
| 1078 | 1073 | } | ... | ... |
libqpdf/qpdf/QPDF_private.hh
| ... | ... | @@ -188,8 +188,10 @@ class QPDF::EncryptionParameters |
| 188 | 188 | EncryptionParameters() = default; |
| 189 | 189 | |
| 190 | 190 | private: |
| 191 | + void initialize(QPDF& qpdf); | |
| 191 | 192 | bool encrypted{false}; |
| 192 | 193 | bool encryption_initialized{false}; |
| 194 | + int encryption_P{0}; | |
| 193 | 195 | int encryption_V{0}; |
| 194 | 196 | int encryption_R{0}; |
| 195 | 197 | bool encrypt_metadata{true}; | ... | ... |