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 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&lt;EncryptionParameters&gt; 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};
... ...