Commit a051a16234726deb433b53537713b87cd8a412ba

Authored by m-holger
1 parent cb3c4c55

Refactor encryption initialization by encapsulating logic in `EncryptionParamete…

…rs::initialize` and replacing shared pointers with references for improved clarity and maintainability.
include/qpdf/QPDF.hh
@@ -964,8 +964,7 @@ class QPDF @@ -964,8 +964,7 @@ class QPDF
964 void insertPageobjToPage(QPDFObjectHandle const& obj, int pos, bool check_duplicate); 964 void insertPageobjToPage(QPDFObjectHandle const& obj, int pos, bool check_duplicate);
965 965
966 // methods to support encryption -- implemented in QPDF_encryption.cc 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 void initializeEncryption(); 968 void initializeEncryption();
970 static std::string 969 static std::string
971 getKeyForObject(std::shared_ptr<EncryptionParameters> encp, QPDFObjGen og, bool use_aes); 970 getKeyForObject(std::shared_ptr<EncryptionParameters> encp, QPDFObjGen og, bool use_aes);
libqpdf/QPDF_encryption.cc
@@ -12,6 +12,7 @@ @@ -12,6 +12,7 @@
12 #include <qpdf/Pl_Buffer.hh> 12 #include <qpdf/Pl_Buffer.hh>
13 #include <qpdf/Pl_RC4.hh> 13 #include <qpdf/Pl_RC4.hh>
14 #include <qpdf/Pl_SHA2.hh> 14 #include <qpdf/Pl_SHA2.hh>
  15 +#include <qpdf/QPDFObjectHandle_private.hh>
15 #include <qpdf/QTC.hh> 16 #include <qpdf/QTC.hh>
16 #include <qpdf/QUtil.hh> 17 #include <qpdf/QUtil.hh>
17 #include <qpdf/RC4.hh> 18 #include <qpdf/RC4.hh>
@@ -686,12 +687,12 @@ QPDF::EncryptionData::recover_encryption_key_with_password( @@ -686,12 +687,12 @@ QPDF::EncryptionData::recover_encryption_key_with_password(
686 } 687 }
687 688
688 QPDF::encryption_method_e 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 if (cf.isName()) { 692 if (cf.isName()) {
692 std::string filter = cf.getName(); 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 return it->second; 696 return it->second;
696 } 697 }
697 if (filter == "/Identity") { 698 if (filter == "/Identity") {
@@ -706,62 +707,75 @@ QPDF::interpretCF(std::shared_ptr&lt;EncryptionParameters&gt; encp, QPDFObjectHandle c @@ -706,62 +707,75 @@ QPDF::interpretCF(std::shared_ptr&lt;EncryptionParameters&gt; encp, QPDFObjectHandle c
706 void 707 void
707 QPDF::initializeEncryption() 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 return; 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 // After we initialize encryption parameters, we must use stored key information and never look 740 // After we initialize encryption parameters, we must use stored key information and never look
715 // at /Encrypt again. Otherwise, things could go wrong if someone mutates the encryption 741 // at /Encrypt again. Otherwise, things could go wrong if someone mutates the encryption
716 // dictionary. 742 // dictionary.
717 743
718 - if (!m->trailer.hasKey("/Encrypt")) { 744 + if (!trailer.hasKey("/Encrypt")) {
719 return; 745 return;
720 } 746 }
721 747
722 // Go ahead and set m->encrypted here. That way, isEncrypted will return true even if there 748 // Go ahead and set m->encrypted here. That way, isEncrypted will return true even if there
723 // were errors reading the encryption dictionary. 749 // were errors reading the encryption dictionary.
724 - m->encp->encrypted = true; 750 + encrypted = true;
725 751
726 std::string id1; 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 // Treating a missing ID as the empty string enables qpdf to decrypt some invalid encrypted 755 // Treating a missing ID as the empty string enables qpdf to decrypt some invalid encrypted
732 // files with no /ID that poppler can read but Adobe Reader can't. 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 if (!encryption_dict.isDictionary()) { 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 if (!(encryption_dict.getKey("/Filter").isName() && 767 if (!(encryption_dict.getKey("/Filter").isName() &&
742 (encryption_dict.getKey("/Filter").getName() == "/Standard"))) { 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 if (!encryption_dict.getKey("/SubFilter").isNull()) { 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 if (!(encryption_dict.getKey("/V").isInteger() && encryption_dict.getKey("/R").isInteger() && 775 if (!(encryption_dict.getKey("/V").isInteger() && encryption_dict.getKey("/R").isInteger() &&
759 encryption_dict.getKey("/O").isString() && encryption_dict.getKey("/U").isString() && 776 encryption_dict.getKey("/O").isString() && encryption_dict.getKey("/U").isString() &&
760 encryption_dict.getKey("/P").isInteger())) { 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 int V = encryption_dict.getKey("/V").getIntValueAsInt(); 781 int V = encryption_dict.getKey("/V").getIntValueAsInt();
@@ -772,18 +786,15 @@ QPDF::initializeEncryption() @@ -772,18 +786,15 @@ QPDF::initializeEncryption()
772 786
773 // If supporting new encryption R/V values, remember to update error message inside this if 787 // If supporting new encryption R/V values, remember to update error message inside this if
774 // statement. 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 "Unsupported /R or /V in encryption dictionary; R = " + std::to_string(R) + 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 // OE, UE, and Perms are only present if V >= 5. 799 // OE, UE, and Perms are only present if V >= 5.
789 std::string OE; 800 std::string OE;
@@ -794,19 +805,15 @@ QPDF::initializeEncryption() @@ -794,19 +805,15 @@ QPDF::initializeEncryption()
794 // These must be exactly the right number of bytes. 805 // These must be exactly the right number of bytes.
795 pad_short_parameter(O, key_bytes); 806 pad_short_parameter(O, key_bytes);
796 pad_short_parameter(U, key_bytes); 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 } else { 811 } else {
803 if (!(encryption_dict.getKey("/OE").isString() && 812 if (!(encryption_dict.getKey("/OE").isString() &&
804 encryption_dict.getKey("/UE").isString() && 813 encryption_dict.getKey("/UE").isString() &&
805 encryption_dict.getKey("/Perms").isString())) { 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 OE = encryption_dict.getKey("/OE").getStringValue(); 818 OE = encryption_dict.getKey("/OE").getStringValue();
812 UE = encryption_dict.getKey("/UE").getStringValue(); 819 UE = encryption_dict.getKey("/UE").getStringValue();
@@ -820,7 +827,7 @@ QPDF::initializeEncryption() @@ -820,7 +827,7 @@ QPDF::initializeEncryption()
820 pad_short_parameter(Perms, Perms_key_bytes_V5); 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 if (V <= 1) { 831 if (V <= 1) {
825 Length = 40; 832 Length = 40;
826 } else if (V == 4) { 833 } else if (V == 4) {
@@ -830,25 +837,20 @@ QPDF::initializeEncryption() @@ -830,25 +837,20 @@ QPDF::initializeEncryption()
830 } else { 837 } else {
831 if (encryption_dict.getKey("/Length").isInteger()) { 838 if (encryption_dict.getKey("/Length").isInteger()) {
832 Length = encryption_dict.getKey("/Length").getIntValueAsInt(); 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 if (cdict.isDictionary()) { 854 if (cdict.isDictionary()) {
853 encryption_method_e method = e_none; 855 encryption_method_e method = e_none;
854 if (cdict.getKey("/CFM").isName()) { 856 if (cdict.getKey("/CFM").isName()) {
@@ -867,16 +869,13 @@ QPDF::initializeEncryption() @@ -867,16 +869,13 @@ QPDF::initializeEncryption()
867 method = e_unknown; 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 // qpdf does not use this for anything other than informational purposes. This is 879 // qpdf does not use this for anything other than informational purposes. This is
881 // intended to instruct conforming writers on which crypt filter should be used when new 880 // intended to instruct conforming writers on which crypt filter should be used when new
882 // file attachments are added to a PDF file, but qpdf never generates encrypted files 881 // file attachments are added to a PDF file, but qpdf never generates encrypted files
@@ -886,54 +885,50 @@ QPDF::initializeEncryption() @@ -886,54 +885,50 @@ QPDF::initializeEncryption()
886 // the way I was imagining. Still, providing this information could be useful when 885 // the way I was imagining. Still, providing this information could be useful when
887 // looking at a file generated by something else, such as Acrobat when specifying that 886 // looking at a file generated by something else, such as Acrobat when specifying that
888 // only attachments should be encrypted. 887 // only attachments should be encrypted.
889 - m->encp->cf_file = interpretCF(m->encp, EFF); 888 + cf_file = interpretCF(*this, EFF);
890 } else { 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 // ignore passwords in file 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 // For V < 5, the user password is encrypted with the owner password, and the user password 922 // For V < 5, the user password is encrypted with the owner password, and the user password
925 // is always used for computing the encryption key. 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 } else { 925 } else {
928 // For V >= 5, either password can be used independently to compute the encryption key, and 926 // For V >= 5, either password can be used independently to compute the encryption key, and
929 // neither password can be used to recover the other. 927 // neither password can be used to recover the other.
930 bool perms_valid; 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 if (!perms_valid) { 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,7 +1052,7 @@ QPDF::decryptStream(
1057 QPDFObjectHandle decode_parms = stream_dict.getKey("/DecodeParms"); 1052 QPDFObjectHandle decode_parms = stream_dict.getKey("/DecodeParms");
1058 if (decode_parms.isDictionaryOfType("/CryptFilterDecodeParms")) { 1053 if (decode_parms.isDictionaryOfType("/CryptFilterDecodeParms")) {
1059 QTC::TC("qpdf", "QPDF_encryption stream crypt filter"); 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 method_source = "stream's Crypt decode parameters"; 1056 method_source = "stream's Crypt decode parameters";
1062 } 1057 }
1063 } else if ( 1058 } else if (
@@ -1072,7 +1067,7 @@ QPDF::decryptStream( @@ -1072,7 +1067,7 @@ QPDF::decryptStream(
1072 if (crypt_params.isDictionary() && 1067 if (crypt_params.isDictionary() &&
1073 crypt_params.getKey("/Name").isName()) { 1068 crypt_params.getKey("/Name").isName()) {
1074 QTC::TC("qpdf", "QPDF_encrypt crypt array"); 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 method_source = "stream's Crypt decode parameters (array)"; 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,8 +188,10 @@ class QPDF::EncryptionParameters
188 EncryptionParameters() = default; 188 EncryptionParameters() = default;
189 189
190 private: 190 private:
  191 + void initialize(QPDF& qpdf);
191 bool encrypted{false}; 192 bool encrypted{false};
192 bool encryption_initialized{false}; 193 bool encryption_initialized{false};
  194 + int encryption_P{0};
193 int encryption_V{0}; 195 int encryption_V{0};
194 int encryption_R{0}; 196 int encryption_R{0};
195 bool encrypt_metadata{true}; 197 bool encrypt_metadata{true};