Commit c8862b5fe7b28a530183237a48eae551ccea60d3
Committed by
GitHub
Merge pull request #1489 from m-holger/qpdf_encrypt
Modernize QPDF_encryption
Showing
13 changed files
with
755 additions
and
910 deletions
include/qpdf/QPDF.hh
| ... | ... | @@ -23,6 +23,7 @@ |
| 23 | 23 | #include <qpdf/DLL.h> |
| 24 | 24 | #include <qpdf/Types.h> |
| 25 | 25 | |
| 26 | +#include <bitset> | |
| 26 | 27 | #include <cstdio> |
| 27 | 28 | #include <functional> |
| 28 | 29 | #include <iostream> |
| ... | ... | @@ -465,7 +466,7 @@ class QPDF |
| 465 | 466 | V(V), |
| 466 | 467 | R(R), |
| 467 | 468 | Length_bytes(Length_bytes), |
| 468 | - P(P), | |
| 469 | + P(static_cast<unsigned long long>(P)), | |
| 469 | 470 | O(O), |
| 470 | 471 | U(U), |
| 471 | 472 | OE(OE), |
| ... | ... | @@ -475,11 +476,20 @@ class QPDF |
| 475 | 476 | encrypt_metadata(encrypt_metadata) |
| 476 | 477 | { |
| 477 | 478 | } |
| 479 | + EncryptionData(int V, int R, int Length_bytes, bool encrypt_metadata) : | |
| 480 | + V(V), | |
| 481 | + R(R), | |
| 482 | + Length_bytes(Length_bytes), | |
| 483 | + encrypt_metadata(encrypt_metadata) | |
| 484 | + { | |
| 485 | + } | |
| 478 | 486 | |
| 479 | 487 | int getV() const; |
| 480 | 488 | int getR() const; |
| 481 | 489 | int getLengthBytes() const; |
| 482 | 490 | int getP() const; |
| 491 | + // Bits in P are numbered from 1 as in the PDF spec. | |
| 492 | + bool getP(size_t bit) const; | |
| 483 | 493 | std::string const& getO() const; |
| 484 | 494 | std::string const& getU() const; |
| 485 | 495 | std::string const& getOE() const; |
| ... | ... | @@ -487,9 +497,12 @@ class QPDF |
| 487 | 497 | std::string const& getPerms() const; |
| 488 | 498 | std::string const& getId1() const; |
| 489 | 499 | bool getEncryptMetadata() const; |
| 490 | - | |
| 500 | + // Bits in P are numbered from 1 as in the PDF spec. | |
| 501 | + void setP(size_t bit, bool val); | |
| 502 | + void setP(unsigned long val); | |
| 491 | 503 | void setO(std::string const&); |
| 492 | 504 | void setU(std::string const&); |
| 505 | + void setId1(std::string const& val); | |
| 493 | 506 | void setV5EncryptionParameters( |
| 494 | 507 | std::string const& O, |
| 495 | 508 | std::string const& OE, |
| ... | ... | @@ -497,14 +510,51 @@ class QPDF |
| 497 | 510 | std::string const& UE, |
| 498 | 511 | std::string const& Perms); |
| 499 | 512 | |
| 513 | + std::string compute_encryption_key(std::string const& password) const; | |
| 514 | + | |
| 515 | + bool | |
| 516 | + check_owner_password(std::string& user_password, std::string const& owner_password) const; | |
| 517 | + | |
| 518 | + bool check_user_password(std::string const& user_password) const; | |
| 519 | + | |
| 520 | + std::string | |
| 521 | + recover_encryption_key_with_password(std::string const& password, bool& perms_valid) const; | |
| 522 | + | |
| 523 | + void compute_encryption_O_U(char const* user_password, char const* owner_password); | |
| 524 | + | |
| 525 | + std::string | |
| 526 | + compute_encryption_parameters_V5(char const* user_password, char const* owner_password); | |
| 527 | + | |
| 528 | + std::string compute_parameters(char const* user_password, char const* owner_password); | |
| 529 | + | |
| 500 | 530 | private: |
| 531 | + static constexpr unsigned int OU_key_bytes_V4 = 16; // ( == sizeof(MD5::Digest) | |
| 532 | + | |
| 501 | 533 | EncryptionData(EncryptionData const&) = delete; |
| 502 | 534 | EncryptionData& operator=(EncryptionData const&) = delete; |
| 503 | 535 | |
| 536 | + std::string hash_V5( | |
| 537 | + std::string const& password, std::string const& salt, std::string const& udata) const; | |
| 538 | + std::string | |
| 539 | + compute_O_value(std::string const& user_password, std::string const& owner_password) const; | |
| 540 | + std::string compute_U_value(std::string const& user_password) const; | |
| 541 | + std::string compute_encryption_key_from_password(std::string const& password) const; | |
| 542 | + std::string recover_encryption_key_with_password(std::string const& password) const; | |
| 543 | + bool check_owner_password_V4( | |
| 544 | + std::string& user_password, std::string const& owner_password) const; | |
| 545 | + bool check_owner_password_V5(std::string const& owner_passworda) const; | |
| 546 | + std::string compute_Perms_value_V5_clear() const; | |
| 547 | + std::string compute_O_rc4_key( | |
| 548 | + std::string const& user_password, std::string const& owner_password) const; | |
| 549 | + std::string compute_U_value_R2(std::string const& user_password) const; | |
| 550 | + std::string compute_U_value_R3(std::string const& user_password) const; | |
| 551 | + bool check_user_password_V4(std::string const& user_password) const; | |
| 552 | + bool check_user_password_V5(std::string const& user_password) const; | |
| 553 | + | |
| 504 | 554 | int V; |
| 505 | 555 | int R; |
| 506 | 556 | int Length_bytes; |
| 507 | - int P; | |
| 557 | + std::bitset<32> P{0xfffffffc}; // Specification always requires bits 1 and 2 to be cleared. | |
| 508 | 558 | std::string O; |
| 509 | 559 | std::string U; |
| 510 | 560 | std::string OE; |
| ... | ... | @@ -906,18 +956,10 @@ class QPDF |
| 906 | 956 | void insertPageobjToPage(QPDFObjectHandle const& obj, int pos, bool check_duplicate); |
| 907 | 957 | |
| 908 | 958 | // methods to support encryption -- implemented in QPDF_encryption.cc |
| 909 | - static encryption_method_e | |
| 910 | - interpretCF(std::shared_ptr<EncryptionParameters> encp, QPDFObjectHandle); | |
| 911 | 959 | void initializeEncryption(); |
| 912 | 960 | static std::string |
| 913 | 961 | getKeyForObject(std::shared_ptr<EncryptionParameters> encp, QPDFObjGen og, bool use_aes); |
| 914 | 962 | void decryptString(std::string&, QPDFObjGen og); |
| 915 | - static std::string | |
| 916 | - compute_encryption_key_from_password(std::string const& password, EncryptionData const& data); | |
| 917 | - static std::string | |
| 918 | - recover_encryption_key_with_password(std::string const& password, EncryptionData const& data); | |
| 919 | - static std::string recover_encryption_key_with_password( | |
| 920 | - std::string const& password, EncryptionData const& data, bool& perms_valid); | |
| 921 | 963 | static void decryptStream( |
| 922 | 964 | std::shared_ptr<EncryptionParameters> encp, |
| 923 | 965 | std::shared_ptr<InputSource> file, | ... | ... |
include/qpdf/QPDFWriter.hh
| ... | ... | @@ -23,6 +23,7 @@ |
| 23 | 23 | #include <qpdf/DLL.h> |
| 24 | 24 | #include <qpdf/Types.h> |
| 25 | 25 | |
| 26 | +#include <bitset> | |
| 26 | 27 | #include <cstdio> |
| 27 | 28 | #include <functional> |
| 28 | 29 | #include <list> |
| ... | ... | @@ -510,9 +511,6 @@ class QPDFWriter |
| 510 | 511 | std::string getOriginalID1(); |
| 511 | 512 | void generateID(); |
| 512 | 513 | void interpretR3EncryptionParameters( |
| 513 | - std::set<int>& bits_to_clear, | |
| 514 | - char const* user_password, | |
| 515 | - char const* owner_password, | |
| 516 | 514 | bool allow_accessibility, |
| 517 | 515 | bool allow_extract, |
| 518 | 516 | bool allow_assemble, |
| ... | ... | @@ -524,26 +522,8 @@ class QPDFWriter |
| 524 | 522 | void disableIncompatibleEncryption(int major, int minor, int extension_level); |
| 525 | 523 | void parseVersion(std::string const& version, int& major, int& minor) const; |
| 526 | 524 | int compareVersions(int major1, int minor1, int major2, int minor2) const; |
| 527 | - void setEncryptionParameters( | |
| 528 | - char const* user_password, | |
| 529 | - char const* owner_password, | |
| 530 | - int V, | |
| 531 | - int R, | |
| 532 | - int key_len, | |
| 533 | - std::set<int>& bits_to_clear); | |
| 534 | - void setEncryptionParametersInternal( | |
| 535 | - int V, | |
| 536 | - int R, | |
| 537 | - int key_len, | |
| 538 | - int P, | |
| 539 | - std::string const& O, | |
| 540 | - std::string const& U, | |
| 541 | - std::string const& OE, | |
| 542 | - std::string const& UE, | |
| 543 | - std::string const& Perms, | |
| 544 | - std::string const& id1, | |
| 545 | - std::string const& user_password, | |
| 546 | - std::string const& encryption_key); | |
| 525 | + void setEncryptionParameters(char const* user_password, char const* owner_password); | |
| 526 | + void setEncryptionMinimumVersion(); | |
| 547 | 527 | void setDataKey(int objid); |
| 548 | 528 | int openObject(int objid = 0); |
| 549 | 529 | void closeObject(int objid); | ... | ... |
libqpdf/MD5.cc
| ... | ... | @@ -46,12 +46,6 @@ MD5::appendString(char const* input_string) |
| 46 | 46 | } |
| 47 | 47 | |
| 48 | 48 | void |
| 49 | -MD5::encodeDataIncrementally(char const* data, size_t len) | |
| 50 | -{ | |
| 51 | - crypto->MD5_update(QUtil::unsigned_char_pointer(data), len); | |
| 52 | -} | |
| 53 | - | |
| 54 | -void | |
| 55 | 49 | MD5::encodeFile(char const* filename, qpdf_offset_t up_to_offset) |
| 56 | 50 | { |
| 57 | 51 | char buffer[1024]; |
| ... | ... | @@ -94,6 +88,24 @@ MD5::digest(Digest result) |
| 94 | 88 | crypto->MD5_digest(result); |
| 95 | 89 | } |
| 96 | 90 | |
| 91 | +std::string | |
| 92 | +MD5::digest() | |
| 93 | +{ | |
| 94 | + Digest digest_val; | |
| 95 | + digest(digest_val); | |
| 96 | + return {reinterpret_cast<char*>(digest_val), 16}; | |
| 97 | +} | |
| 98 | + | |
| 99 | +std::string | |
| 100 | +MD5::digest(std::string_view data) | |
| 101 | +{ | |
| 102 | + MD5 m; | |
| 103 | + m.encodeDataIncrementally(data.data(), data.size()); | |
| 104 | + Digest digest_val; | |
| 105 | + m.digest(digest_val); | |
| 106 | + return {reinterpret_cast<char*>(digest_val), 16}; | |
| 107 | +} | |
| 108 | + | |
| 97 | 109 | void |
| 98 | 110 | MD5::print() |
| 99 | 111 | { | ... | ... |
libqpdf/QPDFWriter.cc
| ... | ... | @@ -50,6 +50,90 @@ QPDFWriter::FunctionProgressReporter::reportProgress(int progress) |
| 50 | 50 | this->handler(progress); |
| 51 | 51 | } |
| 52 | 52 | |
| 53 | +class QPDFWriter::Members | |
| 54 | +{ | |
| 55 | + friend class QPDFWriter; | |
| 56 | + | |
| 57 | + public: | |
| 58 | + ~Members(); | |
| 59 | + | |
| 60 | + private: | |
| 61 | + Members(QPDF& pdf); | |
| 62 | + Members(Members const&) = delete; | |
| 63 | + | |
| 64 | + QPDF& pdf; | |
| 65 | + QPDFObjGen root_og{-1, 0}; | |
| 66 | + char const* filename{"unspecified"}; | |
| 67 | + FILE* file{nullptr}; | |
| 68 | + bool close_file{false}; | |
| 69 | + Pl_Buffer* buffer_pipeline{nullptr}; | |
| 70 | + Buffer* output_buffer{nullptr}; | |
| 71 | + bool normalize_content_set{false}; | |
| 72 | + bool normalize_content{false}; | |
| 73 | + bool compress_streams{true}; | |
| 74 | + bool compress_streams_set{false}; | |
| 75 | + qpdf_stream_decode_level_e stream_decode_level{qpdf_dl_generalized}; | |
| 76 | + bool stream_decode_level_set{false}; | |
| 77 | + bool recompress_flate{false}; | |
| 78 | + bool qdf_mode{false}; | |
| 79 | + bool preserve_unreferenced_objects{false}; | |
| 80 | + bool newline_before_endstream{false}; | |
| 81 | + bool static_id{false}; | |
| 82 | + bool suppress_original_object_ids{false}; | |
| 83 | + bool direct_stream_lengths{true}; | |
| 84 | + bool preserve_encryption{true}; | |
| 85 | + bool linearized{false}; | |
| 86 | + bool pclm{false}; | |
| 87 | + qpdf_object_stream_e object_stream_mode{qpdf_o_preserve}; | |
| 88 | + | |
| 89 | + std::unique_ptr<QPDF::EncryptionData> encryption; | |
| 90 | + std::string encryption_key; | |
| 91 | + bool encrypt_use_aes{false}; | |
| 92 | + | |
| 93 | + std::string id1; // for /ID key of | |
| 94 | + std::string id2; // trailer dictionary | |
| 95 | + std::string final_pdf_version; | |
| 96 | + int final_extension_level{0}; | |
| 97 | + std::string min_pdf_version; | |
| 98 | + int min_extension_level{0}; | |
| 99 | + std::string forced_pdf_version; | |
| 100 | + int forced_extension_level{0}; | |
| 101 | + std::string extra_header_text; | |
| 102 | + int encryption_dict_objid{0}; | |
| 103 | + std::string cur_data_key; | |
| 104 | + std::list<std::shared_ptr<Pipeline>> to_delete; | |
| 105 | + qpdf::pl::Count* pipeline{nullptr}; | |
| 106 | + std::vector<QPDFObjectHandle> object_queue; | |
| 107 | + size_t object_queue_front{0}; | |
| 108 | + QPDFWriter::ObjTable obj; | |
| 109 | + QPDFWriter::NewObjTable new_obj; | |
| 110 | + int next_objid{1}; | |
| 111 | + int cur_stream_length_id{0}; | |
| 112 | + size_t cur_stream_length{0}; | |
| 113 | + bool added_newline{false}; | |
| 114 | + size_t max_ostream_index{0}; | |
| 115 | + std::set<QPDFObjGen> normalized_streams; | |
| 116 | + std::map<QPDFObjGen, int> page_object_to_seq; | |
| 117 | + std::map<QPDFObjGen, int> contents_to_page_seq; | |
| 118 | + std::map<int, std::vector<QPDFObjGen>> object_stream_to_objects; | |
| 119 | + std::vector<Pipeline*> pipeline_stack; | |
| 120 | + unsigned long next_stack_id{2}; | |
| 121 | + std::string count_buffer; | |
| 122 | + bool deterministic_id{false}; | |
| 123 | + Pl_MD5* md5_pipeline{nullptr}; | |
| 124 | + std::string deterministic_id_data; | |
| 125 | + bool did_write_setup{false}; | |
| 126 | + | |
| 127 | + // For linearization only | |
| 128 | + std::string lin_pass1_filename; | |
| 129 | + | |
| 130 | + // For progress reporting | |
| 131 | + std::shared_ptr<QPDFWriter::ProgressReporter> progress_reporter; | |
| 132 | + int events_expected{0}; | |
| 133 | + int events_seen{0}; | |
| 134 | + int next_progress_report{0}; | |
| 135 | +}; | |
| 136 | + | |
| 53 | 137 | QPDFWriter::Members::Members(QPDF& pdf) : |
| 54 | 138 | pdf(pdf), |
| 55 | 139 | root_og(pdf.getRoot().getObjGen().isIndirect() ? pdf.getRoot().getObjGen() : QPDFObjGen(-1, 0)) |
| ... | ... | @@ -344,21 +428,20 @@ QPDFWriter::setR2EncryptionParametersInsecure( |
| 344 | 428 | bool allow_extract, |
| 345 | 429 | bool allow_annotate) |
| 346 | 430 | { |
| 347 | - std::set<int> clear; | |
| 431 | + m->encryption = std::make_unique<QPDF::EncryptionData>(1, 2, 5, true); | |
| 348 | 432 | if (!allow_print) { |
| 349 | - clear.insert(3); | |
| 433 | + m->encryption->setP(3, false); | |
| 350 | 434 | } |
| 351 | 435 | if (!allow_modify) { |
| 352 | - clear.insert(4); | |
| 436 | + m->encryption->setP(4, false); | |
| 353 | 437 | } |
| 354 | 438 | if (!allow_extract) { |
| 355 | - clear.insert(5); | |
| 439 | + m->encryption->setP(5, false); | |
| 356 | 440 | } |
| 357 | 441 | if (!allow_annotate) { |
| 358 | - clear.insert(6); | |
| 442 | + m->encryption->setP(6, false); | |
| 359 | 443 | } |
| 360 | - | |
| 361 | - setEncryptionParameters(user_password, owner_password, 1, 2, 5, clear); | |
| 444 | + setEncryptionParameters(user_password, owner_password); | |
| 362 | 445 | } |
| 363 | 446 | |
| 364 | 447 | void |
| ... | ... | @@ -373,11 +456,8 @@ QPDFWriter::setR3EncryptionParametersInsecure( |
| 373 | 456 | bool allow_modify_other, |
| 374 | 457 | qpdf_r3_print_e print) |
| 375 | 458 | { |
| 376 | - std::set<int> clear; | |
| 459 | + m->encryption = std::make_unique<QPDF::EncryptionData>(2, 3, 16, true); | |
| 377 | 460 | interpretR3EncryptionParameters( |
| 378 | - clear, | |
| 379 | - user_password, | |
| 380 | - owner_password, | |
| 381 | 461 | allow_accessibility, |
| 382 | 462 | allow_extract, |
| 383 | 463 | allow_assemble, |
| ... | ... | @@ -386,7 +466,7 @@ QPDFWriter::setR3EncryptionParametersInsecure( |
| 386 | 466 | allow_modify_other, |
| 387 | 467 | print, |
| 388 | 468 | qpdf_r3m_all); |
| 389 | - setEncryptionParameters(user_password, owner_password, 2, 3, 16, clear); | |
| 469 | + setEncryptionParameters(user_password, owner_password); | |
| 390 | 470 | } |
| 391 | 471 | |
| 392 | 472 | void |
| ... | ... | @@ -403,11 +483,9 @@ QPDFWriter::setR4EncryptionParametersInsecure( |
| 403 | 483 | bool encrypt_metadata, |
| 404 | 484 | bool use_aes) |
| 405 | 485 | { |
| 406 | - std::set<int> clear; | |
| 486 | + m->encryption = std::make_unique<QPDF::EncryptionData>(4, 4, 16, encrypt_metadata); | |
| 487 | + m->encrypt_use_aes = use_aes; | |
| 407 | 488 | interpretR3EncryptionParameters( |
| 408 | - clear, | |
| 409 | - user_password, | |
| 410 | - owner_password, | |
| 411 | 489 | allow_accessibility, |
| 412 | 490 | allow_extract, |
| 413 | 491 | allow_assemble, |
| ... | ... | @@ -416,9 +494,7 @@ QPDFWriter::setR4EncryptionParametersInsecure( |
| 416 | 494 | allow_modify_other, |
| 417 | 495 | print, |
| 418 | 496 | qpdf_r3m_all); |
| 419 | - m->encrypt_use_aes = use_aes; | |
| 420 | - m->encrypt_metadata = encrypt_metadata; | |
| 421 | - setEncryptionParameters(user_password, owner_password, 4, 4, 16, clear); | |
| 497 | + setEncryptionParameters(user_password, owner_password); | |
| 422 | 498 | } |
| 423 | 499 | |
| 424 | 500 | void |
| ... | ... | @@ -434,11 +510,9 @@ QPDFWriter::setR5EncryptionParameters( |
| 434 | 510 | qpdf_r3_print_e print, |
| 435 | 511 | bool encrypt_metadata) |
| 436 | 512 | { |
| 437 | - std::set<int> clear; | |
| 513 | + m->encryption = std::make_unique<QPDF::EncryptionData>(5, 5, 32, encrypt_metadata); | |
| 514 | + m->encrypt_use_aes = true; | |
| 438 | 515 | interpretR3EncryptionParameters( |
| 439 | - clear, | |
| 440 | - user_password, | |
| 441 | - owner_password, | |
| 442 | 516 | allow_accessibility, |
| 443 | 517 | allow_extract, |
| 444 | 518 | allow_assemble, |
| ... | ... | @@ -447,9 +521,7 @@ QPDFWriter::setR5EncryptionParameters( |
| 447 | 521 | allow_modify_other, |
| 448 | 522 | print, |
| 449 | 523 | qpdf_r3m_all); |
| 450 | - m->encrypt_use_aes = true; | |
| 451 | - m->encrypt_metadata = encrypt_metadata; | |
| 452 | - setEncryptionParameters(user_password, owner_password, 5, 5, 32, clear); | |
| 524 | + setEncryptionParameters(user_password, owner_password); | |
| 453 | 525 | } |
| 454 | 526 | |
| 455 | 527 | void |
| ... | ... | @@ -465,11 +537,8 @@ QPDFWriter::setR6EncryptionParameters( |
| 465 | 537 | qpdf_r3_print_e print, |
| 466 | 538 | bool encrypt_metadata) |
| 467 | 539 | { |
| 468 | - std::set<int> clear; | |
| 540 | + m->encryption = std::make_unique<QPDF::EncryptionData>(5, 6, 32, encrypt_metadata); | |
| 469 | 541 | interpretR3EncryptionParameters( |
| 470 | - clear, | |
| 471 | - user_password, | |
| 472 | - owner_password, | |
| 473 | 542 | allow_accessibility, |
| 474 | 543 | allow_extract, |
| 475 | 544 | allow_assemble, |
| ... | ... | @@ -479,15 +548,11 @@ QPDFWriter::setR6EncryptionParameters( |
| 479 | 548 | print, |
| 480 | 549 | qpdf_r3m_all); |
| 481 | 550 | m->encrypt_use_aes = true; |
| 482 | - m->encrypt_metadata = encrypt_metadata; | |
| 483 | - setEncryptionParameters(user_password, owner_password, 5, 6, 32, clear); | |
| 551 | + setEncryptionParameters(user_password, owner_password); | |
| 484 | 552 | } |
| 485 | 553 | |
| 486 | 554 | void |
| 487 | 555 | QPDFWriter::interpretR3EncryptionParameters( |
| 488 | - std::set<int>& clear, | |
| 489 | - char const* user_password, | |
| 490 | - char const* owner_password, | |
| 491 | 556 | bool allow_accessibility, |
| 492 | 557 | bool allow_extract, |
| 493 | 558 | bool allow_assemble, |
| ... | ... | @@ -526,27 +591,24 @@ QPDFWriter::interpretR3EncryptionParameters( |
| 526 | 591 | // 10: accessibility; ignored by readers, should always be set |
| 527 | 592 | // 11: document assembly even if 4 is clear |
| 528 | 593 | // 12: high-resolution printing |
| 529 | - | |
| 530 | - if (!allow_accessibility) { | |
| 531 | - // setEncryptionParameters sets this if R > 3 | |
| 532 | - clear.insert(10); | |
| 594 | + if (!allow_accessibility && m->encryption->getR() <= 3) { | |
| 595 | + // Bit 10 is deprecated and should always be set. This used to mean accessibility. There | |
| 596 | + // is no way to disable accessibility with R > 3. | |
| 597 | + m->encryption->setP(10, false); | |
| 533 | 598 | } |
| 534 | 599 | if (!allow_extract) { |
| 535 | - clear.insert(5); | |
| 600 | + m->encryption->setP(5, false); | |
| 536 | 601 | } |
| 537 | 602 | |
| 538 | - // Note: these switch statements all "fall through" (no break statements). Each option clears | |
| 539 | - // successively more access bits. | |
| 540 | 603 | switch (print) { |
| 541 | 604 | case qpdf_r3p_none: |
| 542 | - clear.insert(3); // any printing | |
| 543 | - | |
| 605 | + m->encryption->setP(3, false); // any printing | |
| 606 | + [[fallthrough]]; | |
| 544 | 607 | case qpdf_r3p_low: |
| 545 | - clear.insert(12); // high resolution printing | |
| 546 | - | |
| 608 | + m->encryption->setP(12, false); // high resolution printing | |
| 609 | + [[fallthrough]]; | |
| 547 | 610 | case qpdf_r3p_full: |
| 548 | 611 | break; |
| 549 | - | |
| 550 | 612 | // no default so gcc warns for missing cases |
| 551 | 613 | } |
| 552 | 614 | |
| ... | ... | @@ -557,96 +619,44 @@ QPDFWriter::interpretR3EncryptionParameters( |
| 557 | 619 | // NOT EXERCISED IN TEST SUITE |
| 558 | 620 | switch (modify) { |
| 559 | 621 | case qpdf_r3m_none: |
| 560 | - clear.insert(11); // document assembly | |
| 561 | - | |
| 622 | + m->encryption->setP(11, false); // document assembly | |
| 623 | + [[fallthrough]]; | |
| 562 | 624 | case qpdf_r3m_assembly: |
| 563 | - clear.insert(9); // filling in form fields | |
| 564 | - | |
| 625 | + m->encryption->setP(9, false); // filling in form fields | |
| 626 | + [[fallthrough]]; | |
| 565 | 627 | case qpdf_r3m_form: |
| 566 | - clear.insert(6); // modify annotations, fill in form fields | |
| 567 | - | |
| 628 | + m->encryption->setP(6, false); // modify annotations, fill in form fields | |
| 629 | + [[fallthrough]]; | |
| 568 | 630 | case qpdf_r3m_annotate: |
| 569 | - clear.insert(4); // other modifications | |
| 570 | - | |
| 631 | + m->encryption->setP(4, false); // other modifications | |
| 632 | + [[fallthrough]]; | |
| 571 | 633 | case qpdf_r3m_all: |
| 572 | 634 | break; |
| 573 | - | |
| 574 | 635 | // no default so gcc warns for missing cases |
| 575 | 636 | } |
| 576 | 637 | // END NOT EXERCISED IN TEST SUITE |
| 577 | 638 | |
| 578 | 639 | if (!allow_assemble) { |
| 579 | - clear.insert(11); | |
| 640 | + m->encryption->setP(11, false); | |
| 580 | 641 | } |
| 581 | 642 | if (!allow_annotate_and_form) { |
| 582 | - clear.insert(6); | |
| 643 | + m->encryption->setP(6, false); | |
| 583 | 644 | } |
| 584 | 645 | if (!allow_form_filling) { |
| 585 | - clear.insert(9); | |
| 646 | + m->encryption->setP(9, false); | |
| 586 | 647 | } |
| 587 | 648 | if (!allow_modify_other) { |
| 588 | - clear.insert(4); | |
| 649 | + m->encryption->setP(4, false); | |
| 589 | 650 | } |
| 590 | 651 | } |
| 591 | 652 | |
| 592 | 653 | void |
| 593 | -QPDFWriter::setEncryptionParameters( | |
| 594 | - char const* user_password, | |
| 595 | - char const* owner_password, | |
| 596 | - int V, | |
| 597 | - int R, | |
| 598 | - int key_len, | |
| 599 | - std::set<int>& bits_to_clear) | |
| 654 | +QPDFWriter::setEncryptionParameters(char const* user_password, char const* owner_password) | |
| 600 | 655 | { |
| 601 | - // PDF specification refers to bits with the low bit numbered 1. | |
| 602 | - // We have to convert this into a bit field. | |
| 603 | - | |
| 604 | - // Specification always requires bits 1 and 2 to be cleared. | |
| 605 | - bits_to_clear.insert(1); | |
| 606 | - bits_to_clear.insert(2); | |
| 607 | - | |
| 608 | - if (R > 3) { | |
| 609 | - // Bit 10 is deprecated and should always be set. This used to mean accessibility. There | |
| 610 | - // is no way to disable accessibility with R > 3. | |
| 611 | - bits_to_clear.erase(10); | |
| 612 | - } | |
| 613 | - | |
| 614 | - int P = 0; | |
| 615 | - // Create the complement of P, then invert. | |
| 616 | - for (int b: bits_to_clear) { | |
| 617 | - P |= (1 << (b - 1)); | |
| 618 | - } | |
| 619 | - P = ~P; | |
| 620 | - | |
| 621 | 656 | generateID(); |
| 622 | - std::string O; | |
| 623 | - std::string U; | |
| 624 | - std::string OE; | |
| 625 | - std::string UE; | |
| 626 | - std::string Perms; | |
| 627 | - std::string encryption_key; | |
| 628 | - if (V < 5) { | |
| 629 | - QPDF::compute_encryption_O_U( | |
| 630 | - user_password, owner_password, V, R, key_len, P, m->encrypt_metadata, m->id1, O, U); | |
| 631 | - } else { | |
| 632 | - QPDF::compute_encryption_parameters_V5( | |
| 633 | - user_password, | |
| 634 | - owner_password, | |
| 635 | - V, | |
| 636 | - R, | |
| 637 | - key_len, | |
| 638 | - P, | |
| 639 | - m->encrypt_metadata, | |
| 640 | - m->id1, | |
| 641 | - encryption_key, | |
| 642 | - O, | |
| 643 | - U, | |
| 644 | - OE, | |
| 645 | - UE, | |
| 646 | - Perms); | |
| 647 | - } | |
| 648 | - setEncryptionParametersInternal( | |
| 649 | - V, R, key_len, P, O, U, OE, UE, Perms, m->id1, user_password, encryption_key); | |
| 657 | + m->encryption->setId1(m->id1); | |
| 658 | + m->encryption_key = m->encryption->compute_parameters(user_password, owner_password); | |
| 659 | + setEncryptionMinimumVersion(); | |
| 650 | 660 | } |
| 651 | 661 | |
| 652 | 662 | void |
| ... | ... | @@ -663,9 +673,10 @@ QPDFWriter::copyEncryptionParameters(QPDF& qpdf) |
| 663 | 673 | if (V > 1) { |
| 664 | 674 | key_len = encrypt.getKey("/Length").getIntValueAsInt() / 8; |
| 665 | 675 | } |
| 666 | - if (encrypt.hasKey("/EncryptMetadata") && encrypt.getKey("/EncryptMetadata").isBool()) { | |
| 667 | - m->encrypt_metadata = encrypt.getKey("/EncryptMetadata").getBoolValue(); | |
| 668 | - } | |
| 676 | + const bool encrypt_metadata = | |
| 677 | + encrypt.hasKey("/EncryptMetadata") && encrypt.getKey("/EncryptMetadata").isBool() | |
| 678 | + ? encrypt.getKey("/EncryptMetadata").getBoolValue() | |
| 679 | + : true; | |
| 669 | 680 | if (V >= 4) { |
| 670 | 681 | // When copying encryption parameters, use AES even if the original file did not. |
| 671 | 682 | // Acrobat doesn't create files with V >= 4 that don't use AES, and the logic of |
| ... | ... | @@ -673,72 +684,62 @@ QPDFWriter::copyEncryptionParameters(QPDF& qpdf) |
| 673 | 684 | // all potentially having different values. |
| 674 | 685 | m->encrypt_use_aes = true; |
| 675 | 686 | } |
| 676 | - QTC::TC("qpdf", "QPDFWriter copy encrypt metadata", m->encrypt_metadata ? 0 : 1); | |
| 687 | + QTC::TC("qpdf", "QPDFWriter copy encrypt metadata", encrypt_metadata ? 0 : 1); | |
| 677 | 688 | QTC::TC("qpdf", "QPDFWriter copy use_aes", m->encrypt_use_aes ? 0 : 1); |
| 678 | - std::string OE; | |
| 679 | - std::string UE; | |
| 680 | - std::string Perms; | |
| 681 | - std::string encryption_key; | |
| 682 | - if (V >= 5) { | |
| 683 | - QTC::TC("qpdf", "QPDFWriter copy V5"); | |
| 684 | - OE = encrypt.getKey("/OE").getStringValue(); | |
| 685 | - UE = encrypt.getKey("/UE").getStringValue(); | |
| 686 | - Perms = encrypt.getKey("/Perms").getStringValue(); | |
| 687 | - encryption_key = qpdf.getEncryptionKey(); | |
| 688 | - } | |
| 689 | - | |
| 690 | - setEncryptionParametersInternal( | |
| 689 | + | |
| 690 | + m->encryption = std::make_unique<QPDF::EncryptionData>( | |
| 691 | 691 | V, |
| 692 | 692 | encrypt.getKey("/R").getIntValueAsInt(), |
| 693 | 693 | key_len, |
| 694 | 694 | static_cast<int>(encrypt.getKey("/P").getIntValue()), |
| 695 | 695 | encrypt.getKey("/O").getStringValue(), |
| 696 | 696 | encrypt.getKey("/U").getStringValue(), |
| 697 | - OE, | |
| 698 | - UE, | |
| 699 | - Perms, | |
| 697 | + V < 5 ? "" : encrypt.getKey("/OE").getStringValue(), | |
| 698 | + V < 5 ? "" : encrypt.getKey("/UE").getStringValue(), | |
| 699 | + V < 5 ? "" : encrypt.getKey("/Perms").getStringValue(), | |
| 700 | 700 | m->id1, // m->id1 == the other file's id1 |
| 701 | - qpdf.getPaddedUserPassword(), | |
| 702 | - encryption_key); | |
| 701 | + encrypt_metadata); | |
| 702 | + m->encryption_key = V >= 5 | |
| 703 | + ? qpdf.getEncryptionKey() | |
| 704 | + : m->encryption->compute_encryption_key(qpdf.getPaddedUserPassword()); | |
| 705 | + setEncryptionMinimumVersion(); | |
| 703 | 706 | } |
| 704 | 707 | } |
| 705 | 708 | |
| 706 | 709 | void |
| 707 | 710 | QPDFWriter::disableIncompatibleEncryption(int major, int minor, int extension_level) |
| 708 | 711 | { |
| 709 | - if (!m->encrypted) { | |
| 712 | + if (!m->encryption) { | |
| 710 | 713 | return; |
| 711 | 714 | } |
| 712 | - | |
| 713 | - bool disable = false; | |
| 714 | 715 | if (compareVersions(major, minor, 1, 3) < 0) { |
| 715 | - disable = true; | |
| 716 | - } else { | |
| 717 | - int V = QUtil::string_to_int(m->encryption_dictionary["/V"].c_str()); | |
| 718 | - int R = QUtil::string_to_int(m->encryption_dictionary["/R"].c_str()); | |
| 719 | - if (compareVersions(major, minor, 1, 4) < 0) { | |
| 720 | - if ((V > 1) || (R > 2)) { | |
| 721 | - disable = true; | |
| 722 | - } | |
| 723 | - } else if (compareVersions(major, minor, 1, 5) < 0) { | |
| 724 | - if ((V > 2) || (R > 3)) { | |
| 725 | - disable = true; | |
| 726 | - } | |
| 727 | - } else if (compareVersions(major, minor, 1, 6) < 0) { | |
| 728 | - if (m->encrypt_use_aes) { | |
| 729 | - disable = true; | |
| 730 | - } | |
| 731 | - } else if ( | |
| 732 | - (compareVersions(major, minor, 1, 7) < 0) || | |
| 733 | - ((compareVersions(major, minor, 1, 7) == 0) && extension_level < 3)) { | |
| 734 | - if ((V >= 5) || (R >= 5)) { | |
| 735 | - disable = true; | |
| 736 | - } | |
| 716 | + m->encryption = nullptr; | |
| 717 | + return; | |
| 718 | + } | |
| 719 | + int V = m->encryption->getV(); | |
| 720 | + int R = m->encryption->getR(); | |
| 721 | + if (compareVersions(major, minor, 1, 4) < 0) { | |
| 722 | + if (V > 1 || R > 2) { | |
| 723 | + m->encryption = nullptr; | |
| 724 | + } | |
| 725 | + } else if (compareVersions(major, minor, 1, 5) < 0) { | |
| 726 | + if (V > 2 || R > 3) { | |
| 727 | + m->encryption = nullptr; | |
| 728 | + } | |
| 729 | + } else if (compareVersions(major, minor, 1, 6) < 0) { | |
| 730 | + if (m->encrypt_use_aes) { | |
| 731 | + m->encryption = nullptr; | |
| 732 | + } | |
| 733 | + } else if ( | |
| 734 | + (compareVersions(major, minor, 1, 7) < 0) || | |
| 735 | + ((compareVersions(major, minor, 1, 7) == 0) && extension_level < 3)) { | |
| 736 | + if (V >= 5 || R >= 5) { | |
| 737 | + m->encryption = nullptr; | |
| 737 | 738 | } |
| 738 | 739 | } |
| 739 | - if (disable) { | |
| 740 | + | |
| 741 | + if (!m->encryption) { | |
| 740 | 742 | QTC::TC("qpdf", "QPDFWriter forced version disabled encryption"); |
| 741 | - m->encrypted = false; | |
| 742 | 743 | } |
| 743 | 744 | } |
| 744 | 745 | |
| ... | ... | @@ -776,34 +777,9 @@ QPDFWriter::compareVersions(int major1, int minor1, int major2, int minor2) cons |
| 776 | 777 | } |
| 777 | 778 | |
| 778 | 779 | void |
| 779 | -QPDFWriter::setEncryptionParametersInternal( | |
| 780 | - int V, | |
| 781 | - int R, | |
| 782 | - int key_len, | |
| 783 | - int P, | |
| 784 | - std::string const& O, | |
| 785 | - std::string const& U, | |
| 786 | - std::string const& OE, | |
| 787 | - std::string const& UE, | |
| 788 | - std::string const& Perms, | |
| 789 | - std::string const& id1, | |
| 790 | - std::string const& user_password, | |
| 791 | - std::string const& encryption_key) | |
| 792 | -{ | |
| 793 | - m->encryption_V = V; | |
| 794 | - m->encryption_R = R; | |
| 795 | - m->encryption_dictionary["/Filter"] = "/Standard"; | |
| 796 | - m->encryption_dictionary["/V"] = std::to_string(V); | |
| 797 | - m->encryption_dictionary["/Length"] = std::to_string(key_len * 8); | |
| 798 | - m->encryption_dictionary["/R"] = std::to_string(R); | |
| 799 | - m->encryption_dictionary["/P"] = std::to_string(P); | |
| 800 | - m->encryption_dictionary["/O"] = QPDF_String(O).unparse(true); | |
| 801 | - m->encryption_dictionary["/U"] = QPDF_String(U).unparse(true); | |
| 802 | - if (V >= 5) { | |
| 803 | - m->encryption_dictionary["/OE"] = QPDF_String(OE).unparse(true); | |
| 804 | - m->encryption_dictionary["/UE"] = QPDF_String(UE).unparse(true); | |
| 805 | - m->encryption_dictionary["/Perms"] = QPDF_String(Perms).unparse(true); | |
| 806 | - } | |
| 780 | +QPDFWriter::setEncryptionMinimumVersion() | |
| 781 | +{ | |
| 782 | + auto const R = m->encryption->getR(); | |
| 807 | 783 | if (R >= 6) { |
| 808 | 784 | setMinimumPDFVersion("1.7", 8); |
| 809 | 785 | } else if (R == 5) { |
| ... | ... | @@ -815,37 +791,20 @@ QPDFWriter::setEncryptionParametersInternal( |
| 815 | 791 | } else { |
| 816 | 792 | setMinimumPDFVersion("1.3"); |
| 817 | 793 | } |
| 818 | - | |
| 819 | - if ((R >= 4) && (!m->encrypt_metadata)) { | |
| 820 | - m->encryption_dictionary["/EncryptMetadata"] = "false"; | |
| 821 | - } | |
| 822 | - if ((V == 4) || (V == 5)) { | |
| 823 | - // The spec says the value for the crypt filter key can be anything, and xpdf seems to | |
| 824 | - // agree. However, Adobe Reader won't open our files unless we use /StdCF. | |
| 825 | - m->encryption_dictionary["/StmF"] = "/StdCF"; | |
| 826 | - m->encryption_dictionary["/StrF"] = "/StdCF"; | |
| 827 | - std::string method = (m->encrypt_use_aes ? ((V < 5) ? "/AESV2" : "/AESV3") : "/V2"); | |
| 828 | - // The PDF spec says the /Length key is optional, but the PDF previewer on some versions of | |
| 829 | - // MacOS won't open encrypted files without it. | |
| 830 | - m->encryption_dictionary["/CF"] = "<< /StdCF << /AuthEvent /DocOpen /CFM " + method + | |
| 831 | - " /Length " + std::string((V < 5) ? "16" : "32") + " >> >>"; | |
| 832 | - } | |
| 833 | - | |
| 834 | - m->encrypted = true; | |
| 835 | - QPDF::EncryptionData encryption_data( | |
| 836 | - V, R, key_len, P, O, U, OE, UE, Perms, id1, m->encrypt_metadata); | |
| 837 | - if (V < 5) { | |
| 838 | - m->encryption_key = QPDF::compute_encryption_key(user_password, encryption_data); | |
| 839 | - } else { | |
| 840 | - m->encryption_key = encryption_key; | |
| 841 | - } | |
| 842 | 794 | } |
| 843 | 795 | |
| 844 | 796 | void |
| 845 | 797 | QPDFWriter::setDataKey(int objid) |
| 846 | 798 | { |
| 847 | - m->cur_data_key = QPDF::compute_data_key( | |
| 848 | - m->encryption_key, objid, 0, m->encrypt_use_aes, m->encryption_V, m->encryption_R); | |
| 799 | + if (m->encryption) { | |
| 800 | + m->cur_data_key = QPDF::compute_data_key( | |
| 801 | + m->encryption_key, | |
| 802 | + objid, | |
| 803 | + 0, | |
| 804 | + m->encrypt_use_aes, | |
| 805 | + m->encryption->getV(), | |
| 806 | + m->encryption->getR()); | |
| 807 | + } | |
| 849 | 808 | } |
| 850 | 809 | |
| 851 | 810 | unsigned int |
| ... | ... | @@ -979,7 +938,7 @@ QPDFWriter::PipelinePopper::~PipelinePopper() |
| 979 | 938 | void |
| 980 | 939 | QPDFWriter::adjustAESStreamLength(size_t& length) |
| 981 | 940 | { |
| 982 | - if (m->encrypted && (!m->cur_data_key.empty()) && m->encrypt_use_aes) { | |
| 941 | + if (m->encryption && !m->cur_data_key.empty() && m->encrypt_use_aes) { | |
| 983 | 942 | // Stream length will be padded with 1 to 16 bytes to end up as a multiple of 16. It will |
| 984 | 943 | // also be prepended by 16 bits of random data. |
| 985 | 944 | length += 32 - (length & 0xf); |
| ... | ... | @@ -989,7 +948,7 @@ QPDFWriter::adjustAESStreamLength(size_t& length) |
| 989 | 948 | void |
| 990 | 949 | QPDFWriter::pushEncryptionFilter(PipelinePopper& pp) |
| 991 | 950 | { |
| 992 | - if (m->encrypted && (!m->cur_data_key.empty())) { | |
| 951 | + if (m->encryption && !m->cur_data_key.empty()) { | |
| 993 | 952 | Pipeline* p = nullptr; |
| 994 | 953 | if (m->encrypt_use_aes) { |
| 995 | 954 | p = new Pl_AES_PDF( |
| ... | ... | @@ -1232,7 +1191,7 @@ QPDFWriter::writeTrailer( |
| 1232 | 1191 | |
| 1233 | 1192 | if (which != t_lin_second) { |
| 1234 | 1193 | // Write reference to encryption dictionary |
| 1235 | - if (m->encrypted) { | |
| 1194 | + if (m->encryption) { | |
| 1236 | 1195 | writeString(" /Encrypt "); |
| 1237 | 1196 | writeString(std::to_string(m->encryption_dict_objid)); |
| 1238 | 1197 | writeString(" 0 R"); |
| ... | ... | @@ -1280,7 +1239,8 @@ QPDFWriter::willFilterStream( |
| 1280 | 1239 | } |
| 1281 | 1240 | bool normalize = false; |
| 1282 | 1241 | bool uncompress = false; |
| 1283 | - if (filter_on_write && is_root_metadata && (!m->encrypted || !m->encrypt_metadata)) { | |
| 1242 | + if (filter_on_write && is_root_metadata && | |
| 1243 | + (!m->encryption || !m->encryption->getEncryptMetadata())) { | |
| 1284 | 1244 | QTC::TC("qpdf", "QPDFWriter not compressing metadata"); |
| 1285 | 1245 | filter = true; |
| 1286 | 1246 | compress_stream = false; |
| ... | ... | @@ -1568,7 +1528,7 @@ QPDFWriter::unparseObject( |
| 1568 | 1528 | QPDFObjectHandle stream_dict = object.getDict(); |
| 1569 | 1529 | |
| 1570 | 1530 | m->cur_stream_length = stream_data.size(); |
| 1571 | - if (is_metadata && m->encrypted && (!m->encrypt_metadata)) { | |
| 1531 | + if (is_metadata && m->encryption && !m->encryption->getEncryptMetadata()) { | |
| 1572 | 1532 | // Don't encrypt stream data for the metadata stream |
| 1573 | 1533 | m->cur_data_key.clear(); |
| 1574 | 1534 | } |
| ... | ... | @@ -1582,17 +1542,16 @@ QPDFWriter::unparseObject( |
| 1582 | 1542 | writeString(stream_data); |
| 1583 | 1543 | } |
| 1584 | 1544 | |
| 1585 | - if (m->newline_before_endstream || (m->qdf_mode && last_char != '\n')) { | |
| 1586 | - writeString("\n"); | |
| 1587 | - m->added_newline = true; | |
| 1545 | + if ((m->added_newline = | |
| 1546 | + m->newline_before_endstream || (m->qdf_mode && last_char != '\n'))) { | |
| 1547 | + writeString("\nendstream"); | |
| 1588 | 1548 | } else { |
| 1589 | - m->added_newline = false; | |
| 1549 | + writeString("endstream"); | |
| 1590 | 1550 | } |
| 1591 | - writeString("endstream"); | |
| 1592 | 1551 | } else if (tc == ::ot_string) { |
| 1593 | 1552 | std::string val; |
| 1594 | - if (m->encrypted && (!(flags & f_in_ostream)) && (!(flags & f_no_encryption)) && | |
| 1595 | - (!m->cur_data_key.empty())) { | |
| 1553 | + if (m->encryption && !(flags & f_in_ostream) && !(flags & f_no_encryption) && | |
| 1554 | + !m->cur_data_key.empty()) { | |
| 1596 | 1555 | val = object.getStringValue(); |
| 1597 | 1556 | if (m->encrypt_use_aes) { |
| 1598 | 1557 | Pl_Buffer bufpl("encrypted string"); |
| ... | ... | @@ -1775,7 +1734,7 @@ QPDFWriter::writeObjectStream(QPDFObjectHandle object) |
| 1775 | 1734 | writeStringQDF("\n"); |
| 1776 | 1735 | writeStringNoQDF(" "); |
| 1777 | 1736 | writeString(">>\nstream\n"); |
| 1778 | - if (m->encrypted) { | |
| 1737 | + if (m->encryption) { | |
| 1779 | 1738 | QTC::TC("qpdf", "QPDFWriter encrypt object stream"); |
| 1780 | 1739 | } |
| 1781 | 1740 | { |
| ... | ... | @@ -2132,7 +2091,7 @@ QPDFWriter::doWriteSetup() |
| 2132 | 2091 | if (m->pclm) { |
| 2133 | 2092 | m->stream_decode_level = qpdf_dl_none; |
| 2134 | 2093 | m->compress_streams = false; |
| 2135 | - m->encrypted = false; | |
| 2094 | + m->encryption = nullptr; | |
| 2136 | 2095 | } |
| 2137 | 2096 | |
| 2138 | 2097 | if (m->qdf_mode) { |
| ... | ... | @@ -2147,7 +2106,7 @@ QPDFWriter::doWriteSetup() |
| 2147 | 2106 | } |
| 2148 | 2107 | } |
| 2149 | 2108 | |
| 2150 | - if (m->encrypted) { | |
| 2109 | + if (m->encryption) { | |
| 2151 | 2110 | // Encryption has been explicitly set |
| 2152 | 2111 | m->preserve_encryption = false; |
| 2153 | 2112 | } else if (m->normalize_content || !m->compress_streams || m->pclm || m->qdf_mode) { |
| ... | ... | @@ -2211,7 +2170,7 @@ QPDFWriter::doWriteSetup() |
| 2211 | 2170 | } |
| 2212 | 2171 | } |
| 2213 | 2172 | |
| 2214 | - if (m->linearized || m->encrypted) { | |
| 2173 | + if (m->linearized || m->encryption) { | |
| 2215 | 2174 | // The document catalog is not allowed to be compressed in linearized files either. It |
| 2216 | 2175 | // also appears that Adobe Reader 8.0.0 has a bug that prevents it from being able to |
| 2217 | 2176 | // handle encrypted files with compressed document catalogs, so we disable them in that |
| ... | ... | @@ -2312,13 +2271,48 @@ void |
| 2312 | 2271 | QPDFWriter::writeEncryptionDictionary() |
| 2313 | 2272 | { |
| 2314 | 2273 | m->encryption_dict_objid = openObject(m->encryption_dict_objid); |
| 2274 | + auto& enc = *m->encryption; | |
| 2275 | + auto const V = enc.getV(); | |
| 2276 | + | |
| 2315 | 2277 | writeString("<<"); |
| 2316 | - for (auto const& iter: m->encryption_dictionary) { | |
| 2317 | - writeString(" "); | |
| 2318 | - writeString(iter.first); | |
| 2319 | - writeString(" "); | |
| 2320 | - writeString(iter.second); | |
| 2278 | + if (V >= 4) { | |
| 2279 | + writeString(" /CF << /StdCF << /AuthEvent /DocOpen /CFM "); | |
| 2280 | + writeString(m->encrypt_use_aes ? ((V < 5) ? "/AESV2" : "/AESV3") : "/V2"); | |
| 2281 | + // The PDF spec says the /Length key is optional, but the PDF previewer on some versions of | |
| 2282 | + // MacOS won't open encrypted files without it. | |
| 2283 | + writeString((V < 5) ? " /Length 16 >> >>" : " /Length 32 >> >>"); | |
| 2284 | + if (!m->encryption->getEncryptMetadata()) { | |
| 2285 | + writeString(" /EncryptMetadata false"); | |
| 2286 | + } | |
| 2287 | + } | |
| 2288 | + writeString(" /Filter /Standard /Length "); | |
| 2289 | + writeString(std::to_string(enc.getLengthBytes() * 8)); | |
| 2290 | + writeString(" /O "); | |
| 2291 | + writeString(QPDF_String(enc.getO()).unparse(true)); | |
| 2292 | + if (V >= 4) { | |
| 2293 | + writeString(" /OE "); | |
| 2294 | + writeString(QPDF_String(enc.getOE()).unparse(true)); | |
| 2295 | + } | |
| 2296 | + writeString(" /P "); | |
| 2297 | + writeString(std::to_string(enc.getP())); | |
| 2298 | + if (V >= 5) { | |
| 2299 | + writeString(" /Perms "); | |
| 2300 | + writeString(QPDF_String(enc.getPerms()).unparse(true)); | |
| 2301 | + } | |
| 2302 | + writeString(" /R "); | |
| 2303 | + writeString(std::to_string(enc.getR())); | |
| 2304 | + | |
| 2305 | + if (V >= 4) { | |
| 2306 | + writeString(" /StmF /StdCF /StrF /StdCF"); | |
| 2307 | + } | |
| 2308 | + writeString(" /U "); | |
| 2309 | + writeString(QPDF_String(enc.getU()).unparse(true)); | |
| 2310 | + if (V >= 4) { | |
| 2311 | + writeString(" /UE "); | |
| 2312 | + writeString(QPDF_String(enc.getUE()).unparse(true)); | |
| 2321 | 2313 | } |
| 2314 | + writeString(" /V "); | |
| 2315 | + writeString(std::to_string(enc.getV())); | |
| 2322 | 2316 | writeString(" >>"); |
| 2323 | 2317 | closeObject(m->encryption_dict_objid); |
| 2324 | 2318 | } |
| ... | ... | @@ -2380,7 +2374,7 @@ QPDFWriter::writeHintStream(int hint_id) |
| 2380 | 2374 | writeString(std::to_string(hlen)); |
| 2381 | 2375 | writeString(" >>\nstream\n"); |
| 2382 | 2376 | |
| 2383 | - if (m->encrypted) { | |
| 2377 | + if (m->encryption) { | |
| 2384 | 2378 | QTC::TC("qpdf", "QPDFWriter encrypted hint stream"); |
| 2385 | 2379 | } |
| 2386 | 2380 | char last_char = hint_buffer.empty() ? '\0' : hint_buffer.back(); |
| ... | ... | @@ -2651,7 +2645,7 @@ QPDFWriter::writeLinearized() |
| 2651 | 2645 | int part4_first_obj = m->next_objid; |
| 2652 | 2646 | m->next_objid += QIntC::to_int(part4.size()); |
| 2653 | 2647 | int after_part4 = m->next_objid; |
| 2654 | - if (m->encrypted) { | |
| 2648 | + if (m->encryption) { | |
| 2655 | 2649 | m->encryption_dict_objid = m->next_objid++; |
| 2656 | 2650 | } |
| 2657 | 2651 | int hint_id = m->next_objid++; |
| ... | ... | @@ -2836,7 +2830,7 @@ QPDFWriter::writeLinearized() |
| 2836 | 2830 | } |
| 2837 | 2831 | writeObject(cur_object); |
| 2838 | 2832 | if (cur_object.getObjectID() == part4_end_marker) { |
| 2839 | - if (m->encrypted) { | |
| 2833 | + if (m->encryption) { | |
| 2840 | 2834 | writeEncryptionDictionary(); |
| 2841 | 2835 | } |
| 2842 | 2836 | if (pass == 1) { |
| ... | ... | @@ -3060,7 +3054,7 @@ QPDFWriter::writeStandard() |
| 3060 | 3054 | } |
| 3061 | 3055 | |
| 3062 | 3056 | // Write out the encryption dictionary, if any |
| 3063 | - if (m->encrypted) { | |
| 3057 | + if (m->encryption) { | |
| 3064 | 3058 | writeEncryptionDictionary(); |
| 3065 | 3059 | } |
| 3066 | 3060 | ... | ... |
libqpdf/QPDF_encryption.cc
| ... | ... | @@ -12,21 +12,23 @@ |
| 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> |
| 19 | +#include <qpdf/Util.hh> | |
| 18 | 20 | |
| 19 | 21 | #include <algorithm> |
| 20 | 22 | #include <cstring> |
| 21 | 23 | |
| 22 | -static unsigned char const padding_string[] = { | |
| 23 | - 0x28, 0xbf, 0x4e, 0x5e, 0x4e, 0x75, 0x8a, 0x41, 0x64, 0x00, 0x4e, 0x56, 0xff, 0xfa, 0x01, 0x08, | |
| 24 | - 0x2e, 0x2e, 0x00, 0xb6, 0xd0, 0x68, 0x3e, 0x80, 0x2f, 0x0c, 0xa9, 0xfe, 0x64, 0x53, 0x69, 0x7a}; | |
| 24 | +using namespace qpdf; | |
| 25 | +using namespace std::literals; | |
| 25 | 26 | |
| 26 | -static unsigned int const key_bytes = 32; | |
| 27 | +static std::string padding_string = | |
| 28 | + "\x28\xbf\x4e\x5e\x4e\x75\x8a\x41\x64\x00\x4e\x56\xff\xfa\x01\x08" | |
| 29 | + "\x2e\x2e\x00\xb6\xd0\x68\x3e\x80\x2f\x0c\xa9\xfe\x64\x53\x69\x7a"s; | |
| 27 | 30 | |
| 28 | -// V4 key lengths apply to V <= 4 | |
| 29 | -static unsigned int const OU_key_bytes_V4 = sizeof(MD5::Digest); | |
| 31 | +static unsigned int const key_bytes = 32; | |
| 30 | 32 | |
| 31 | 33 | static unsigned int const OU_key_bytes_V5 = 48; |
| 32 | 34 | static unsigned int const OUE_key_bytes_V5 = 32; |
| ... | ... | @@ -53,7 +55,21 @@ QPDF::EncryptionData::getLengthBytes() const |
| 53 | 55 | int |
| 54 | 56 | QPDF::EncryptionData::getP() const |
| 55 | 57 | { |
| 56 | - return this->P; | |
| 58 | + return static_cast<int>(P.to_ulong()); | |
| 59 | +} | |
| 60 | + | |
| 61 | +bool | |
| 62 | +QPDF::EncryptionData::getP(size_t bit) const | |
| 63 | +{ | |
| 64 | + qpdf_assert_debug(bit); | |
| 65 | + return P.test(bit - 1); | |
| 66 | +} | |
| 67 | + | |
| 68 | +bool | |
| 69 | +QPDF::EncryptionParameters::P(size_t bit) const | |
| 70 | +{ | |
| 71 | + qpdf_assert_debug(bit); | |
| 72 | + return P_.test(bit - 1); | |
| 57 | 73 | } |
| 58 | 74 | |
| 59 | 75 | std::string const& |
| ... | ... | @@ -111,6 +127,25 @@ QPDF::EncryptionData::setU(std::string const& U) |
| 111 | 127 | } |
| 112 | 128 | |
| 113 | 129 | void |
| 130 | +QPDF::EncryptionData::setP(size_t bit, bool val) | |
| 131 | +{ | |
| 132 | + qpdf_assert_debug(bit); | |
| 133 | + P.set(bit - 1, val); | |
| 134 | +} | |
| 135 | + | |
| 136 | +void | |
| 137 | +QPDF::EncryptionData::setP(unsigned long val) | |
| 138 | +{ | |
| 139 | + P = std::bitset<32>(val); | |
| 140 | +} | |
| 141 | + | |
| 142 | +void | |
| 143 | +QPDF::EncryptionData::setId1(std::string const& val) | |
| 144 | +{ | |
| 145 | + id1 = val; | |
| 146 | +} | |
| 147 | + | |
| 148 | +void | |
| 114 | 149 | QPDF::EncryptionData::setV5EncryptionParameters( |
| 115 | 150 | std::string const& O, |
| 116 | 151 | std::string const& OE, |
| ... | ... | @@ -125,84 +160,62 @@ QPDF::EncryptionData::setV5EncryptionParameters( |
| 125 | 160 | this->Perms = Perms; |
| 126 | 161 | } |
| 127 | 162 | |
| 128 | -static void | |
| 129 | -pad_or_truncate_password_V4(std::string const& password, char k1[key_bytes]) | |
| 130 | -{ | |
| 131 | - size_t password_bytes = std::min(QIntC::to_size(key_bytes), password.length()); | |
| 132 | - size_t pad_bytes = key_bytes - password_bytes; | |
| 133 | - memcpy(k1, password.c_str(), password_bytes); | |
| 134 | - memcpy(k1 + password_bytes, padding_string, pad_bytes); | |
| 135 | -} | |
| 136 | - | |
| 137 | 163 | void |
| 138 | 164 | QPDF::trim_user_password(std::string& user_password) |
| 139 | 165 | { |
| 140 | 166 | // Although unnecessary, this routine trims the padding string from the end of a user password. |
| 141 | 167 | // Its only purpose is for recovery of user passwords which is done in the test suite. |
| 142 | - char const* cstr = user_password.c_str(); | |
| 143 | - size_t len = user_password.length(); | |
| 144 | - if (len < key_bytes) { | |
| 168 | + if (user_password.size() < key_bytes) { | |
| 145 | 169 | return; |
| 146 | 170 | } |
| 147 | 171 | |
| 148 | - char const* p1 = cstr; | |
| 149 | - char const* p2 = nullptr; | |
| 150 | - while ((p2 = strchr(p1, '\x28')) != nullptr) { | |
| 151 | - size_t idx = toS(p2 - cstr); | |
| 152 | - if (memcmp(p2, padding_string, len - idx) == 0) { | |
| 153 | - user_password = user_password.substr(0, idx); | |
| 172 | + auto idx = user_password.find('\x28'); | |
| 173 | + | |
| 174 | + while (idx != user_password.npos) { | |
| 175 | + if (padding_string.starts_with(user_password.substr(idx))) { | |
| 176 | + user_password.resize(idx); | |
| 154 | 177 | return; |
| 155 | - } else { | |
| 156 | - QTC::TC("qpdf", "QPDF_encryption skip 0x28"); | |
| 157 | - p1 = p2 + 1; | |
| 158 | 178 | } |
| 179 | + QTC::TC("qpdf", "QPDF_encryption skip 0x28"); | |
| 180 | + idx = user_password.find('\x28', ++idx); | |
| 159 | 181 | } |
| 160 | 182 | } |
| 161 | 183 | |
| 162 | 184 | static std::string |
| 163 | -pad_or_truncate_password_V4(std::string const& password) | |
| 185 | +pad_or_truncate_password_V4(std::string password) | |
| 164 | 186 | { |
| 165 | - char k1[key_bytes]; | |
| 166 | - pad_or_truncate_password_V4(password, k1); | |
| 167 | - return {k1, key_bytes}; | |
| 187 | + if (password.size() < key_bytes) { | |
| 188 | + password.append(padding_string); | |
| 189 | + } | |
| 190 | + password.resize(key_bytes); | |
| 191 | + return password; | |
| 168 | 192 | } |
| 169 | 193 | |
| 170 | 194 | static std::string |
| 171 | -truncate_password_V5(std::string const& password) | |
| 172 | -{ | |
| 173 | - return password.substr(0, std::min(static_cast<size_t>(127), password.length())); | |
| 174 | -} | |
| 175 | - | |
| 176 | -static void | |
| 177 | -iterate_md5_digest(MD5& md5, MD5::Digest& digest, int iterations, int key_len) | |
| 195 | +iterate_md5_digest(MD5& md5, int iterations, int key_len) | |
| 178 | 196 | { |
| 197 | + MD5::Digest digest; | |
| 179 | 198 | md5.digest(digest); |
| 180 | - | |
| 199 | + auto len = std::min(QIntC::to_size(key_len), sizeof(digest)); | |
| 181 | 200 | for (int i = 0; i < iterations; ++i) { |
| 182 | 201 | MD5 m; |
| 183 | - m.encodeDataIncrementally(reinterpret_cast<char*>(digest), QIntC::to_size(key_len)); | |
| 202 | + m.encodeDataIncrementally(reinterpret_cast<char*>(digest), len); | |
| 184 | 203 | m.digest(digest); |
| 185 | 204 | } |
| 205 | + return {reinterpret_cast<char*>(digest), len}; | |
| 186 | 206 | } |
| 187 | 207 | |
| 188 | 208 | static void |
| 189 | -iterate_rc4( | |
| 190 | - unsigned char* data, | |
| 191 | - size_t data_len, | |
| 192 | - unsigned char* okey, | |
| 193 | - int key_len, | |
| 194 | - int iterations, | |
| 195 | - bool reverse) | |
| 209 | +iterate_rc4(std::string& data, std::string_view okey, int iterations, bool reverse) | |
| 196 | 210 | { |
| 197 | - auto key_ph = std::make_unique<unsigned char[]>(QIntC::to_size(key_len)); | |
| 198 | - unsigned char* key = key_ph.get(); | |
| 211 | + auto len = okey.size(); | |
| 212 | + std::string key(len, '\0'); | |
| 199 | 213 | for (int i = 0; i < iterations; ++i) { |
| 200 | 214 | int const xor_value = (reverse ? iterations - 1 - i : i); |
| 201 | - for (int j = 0; j < key_len; ++j) { | |
| 202 | - key[j] = static_cast<unsigned char>(okey[j] ^ xor_value); | |
| 215 | + for (size_t j = 0; j < len; ++j) { | |
| 216 | + key[j] = static_cast<char>(okey[j] ^ xor_value); | |
| 203 | 217 | } |
| 204 | - RC4 rc4(key, QIntC::to_int(key_len)); | |
| 205 | - rc4.process(data, data_len, data); | |
| 218 | + RC4::process(key, data); | |
| 206 | 219 | } |
| 207 | 220 | } |
| 208 | 221 | |
| ... | ... | @@ -236,12 +249,9 @@ process_with_aes( |
| 236 | 249 | } |
| 237 | 250 | } |
| 238 | 251 | |
| 239 | -static std::string | |
| 240 | -hash_V5( | |
| 241 | - std::string const& password, | |
| 242 | - std::string const& salt, | |
| 243 | - std::string const& udata, | |
| 244 | - QPDF::EncryptionData const& data) | |
| 252 | +std::string | |
| 253 | +QPDF::EncryptionData::hash_V5( | |
| 254 | + std::string const& password, std::string const& salt, std::string const& udata) const | |
| 245 | 255 | { |
| 246 | 256 | Pl_SHA2 hash(256); |
| 247 | 257 | hash.writeString(password); |
| ... | ... | @@ -251,7 +261,7 @@ hash_V5( |
| 251 | 261 | std::string K = hash.getRawDigest(); |
| 252 | 262 | |
| 253 | 263 | std::string result; |
| 254 | - if (data.getR() < 6) { | |
| 264 | + if (getR() < 6) { | |
| 255 | 265 | result = K; |
| 256 | 266 | } else { |
| 257 | 267 | // Algorithm 2.B from ISO 32000-1 chapter 7: Computing a hash |
| ... | ... | @@ -348,30 +358,31 @@ QPDF::compute_data_key( |
| 348 | 358 | if (use_aes) { |
| 349 | 359 | result += "sAlT"; |
| 350 | 360 | } |
| 351 | - | |
| 352 | - MD5 md5; | |
| 353 | - md5.encodeDataIncrementally(result.c_str(), result.length()); | |
| 354 | - MD5::Digest digest; | |
| 355 | - md5.digest(digest); | |
| 356 | - return {reinterpret_cast<char*>(digest), std::min(result.length(), toS(16))}; | |
| 361 | + return MD5::digest(result).substr(0, result.size()); | |
| 357 | 362 | } |
| 358 | 363 | |
| 359 | 364 | std::string |
| 360 | 365 | QPDF::compute_encryption_key(std::string const& password, EncryptionData const& data) |
| 361 | 366 | { |
| 362 | - if (data.getV() >= 5) { | |
| 367 | + return data.compute_encryption_key(password); | |
| 368 | +} | |
| 369 | + | |
| 370 | +std::string | |
| 371 | +QPDF::EncryptionData::compute_encryption_key(std::string const& password) const | |
| 372 | +{ | |
| 373 | + if (getV() >= 5) { | |
| 363 | 374 | // For V >= 5, the encryption key is generated and stored in the file, encrypted separately |
| 364 | 375 | // with both user and owner passwords. |
| 365 | - return recover_encryption_key_with_password(password, data); | |
| 376 | + return recover_encryption_key_with_password(password); | |
| 366 | 377 | } else { |
| 367 | 378 | // For V < 5, the encryption key is derived from the user |
| 368 | 379 | // password. |
| 369 | - return compute_encryption_key_from_password(password, data); | |
| 380 | + return compute_encryption_key_from_password(password); | |
| 370 | 381 | } |
| 371 | 382 | } |
| 372 | 383 | |
| 373 | 384 | std::string |
| 374 | -QPDF::compute_encryption_key_from_password(std::string const& password, EncryptionData const& data) | |
| 385 | +QPDF::EncryptionData::compute_encryption_key_from_password(std::string const& password) const | |
| 375 | 386 | { |
| 376 | 387 | // Algorithm 3.2 from the PDF 1.7 Reference Manual |
| 377 | 388 | |
| ... | ... | @@ -381,290 +392,185 @@ QPDF::compute_encryption_key_from_password(std::string const& password, Encrypti |
| 381 | 392 | // password to be presented in its final form. |
| 382 | 393 | |
| 383 | 394 | MD5 md5; |
| 384 | - md5.encodeDataIncrementally(pad_or_truncate_password_V4(password).c_str(), key_bytes); | |
| 385 | - md5.encodeDataIncrementally(data.getO().c_str(), key_bytes); | |
| 395 | + md5.encodeDataIncrementally(pad_or_truncate_password_V4(password)); | |
| 396 | + md5.encodeDataIncrementally(getO()); | |
| 386 | 397 | char pbytes[4]; |
| 387 | - int P = data.getP(); | |
| 388 | - pbytes[0] = static_cast<char>(P & 0xff); | |
| 389 | - pbytes[1] = static_cast<char>((P >> 8) & 0xff); | |
| 390 | - pbytes[2] = static_cast<char>((P >> 16) & 0xff); | |
| 391 | - pbytes[3] = static_cast<char>((P >> 24) & 0xff); | |
| 398 | + int p = getP(); | |
| 399 | + pbytes[0] = static_cast<char>(p & 0xff); | |
| 400 | + pbytes[1] = static_cast<char>((p >> 8) & 0xff); | |
| 401 | + pbytes[2] = static_cast<char>((p >> 16) & 0xff); | |
| 402 | + pbytes[3] = static_cast<char>((p >> 24) & 0xff); | |
| 392 | 403 | md5.encodeDataIncrementally(pbytes, 4); |
| 393 | - md5.encodeDataIncrementally(data.getId1().c_str(), data.getId1().length()); | |
| 394 | - if ((data.getR() >= 4) && (!data.getEncryptMetadata())) { | |
| 395 | - char bytes[4]; | |
| 396 | - memset(bytes, 0xff, 4); | |
| 397 | - md5.encodeDataIncrementally(bytes, 4); | |
| 404 | + md5.encodeDataIncrementally(getId1()); | |
| 405 | + if (getR() >= 4 && !getEncryptMetadata()) { | |
| 406 | + md5.encodeDataIncrementally("\xff\xff\xff\xff"); | |
| 398 | 407 | } |
| 399 | - MD5::Digest digest; | |
| 400 | - int key_len = std::min(toI(sizeof(digest)), data.getLengthBytes()); | |
| 401 | - iterate_md5_digest(md5, digest, ((data.getR() >= 3) ? 50 : 0), key_len); | |
| 402 | - return {reinterpret_cast<char*>(digest), toS(key_len)}; | |
| 408 | + return iterate_md5_digest(md5, (getR() >= 3 ? 50 : 0), getLengthBytes()); | |
| 403 | 409 | } |
| 404 | 410 | |
| 405 | -static void | |
| 406 | -compute_O_rc4_key( | |
| 407 | - std::string const& user_password, | |
| 408 | - std::string const& owner_password, | |
| 409 | - QPDF::EncryptionData const& data, | |
| 410 | - unsigned char key[OU_key_bytes_V4]) | |
| 411 | +std::string | |
| 412 | +QPDF::EncryptionData::compute_O_rc4_key( | |
| 413 | + std::string const& user_password, std::string const& owner_password) const | |
| 411 | 414 | { |
| 412 | - if (data.getV() >= 5) { | |
| 415 | + if (getV() >= 5) { | |
| 413 | 416 | throw std::logic_error("compute_O_rc4_key called for file with V >= 5"); |
| 414 | 417 | } |
| 415 | - std::string password = owner_password; | |
| 416 | - if (password.empty()) { | |
| 417 | - password = user_password; | |
| 418 | - } | |
| 418 | + std::string password = owner_password.empty() ? user_password : owner_password; | |
| 419 | 419 | MD5 md5; |
| 420 | - md5.encodeDataIncrementally(pad_or_truncate_password_V4(password).c_str(), key_bytes); | |
| 421 | - MD5::Digest digest; | |
| 422 | - int key_len = std::min(QIntC::to_int(sizeof(digest)), data.getLengthBytes()); | |
| 423 | - iterate_md5_digest(md5, digest, ((data.getR() >= 3) ? 50 : 0), key_len); | |
| 424 | - memcpy(key, digest, OU_key_bytes_V4); | |
| 420 | + md5.encodeDataIncrementally(pad_or_truncate_password_V4(password)); | |
| 421 | + return iterate_md5_digest(md5, (getR() >= 3 ? 50 : 0), getLengthBytes()); | |
| 425 | 422 | } |
| 426 | 423 | |
| 427 | -static std::string | |
| 428 | -compute_O_value( | |
| 429 | - std::string const& user_password, | |
| 430 | - std::string const& owner_password, | |
| 431 | - QPDF::EncryptionData const& data) | |
| 424 | +std::string | |
| 425 | +QPDF::EncryptionData::compute_O_value( | |
| 426 | + std::string const& user_password, std::string const& owner_password) const | |
| 432 | 427 | { |
| 433 | 428 | // Algorithm 3.3 from the PDF 1.7 Reference Manual |
| 434 | 429 | |
| 435 | - unsigned char O_key[OU_key_bytes_V4]; | |
| 436 | - compute_O_rc4_key(user_password, owner_password, data, O_key); | |
| 437 | - | |
| 438 | - char upass[key_bytes]; | |
| 439 | - pad_or_truncate_password_V4(user_password, upass); | |
| 440 | - std::string k1(reinterpret_cast<char*>(O_key), OU_key_bytes_V4); | |
| 441 | - pad_short_parameter(k1, QIntC::to_size(data.getLengthBytes())); | |
| 442 | - iterate_rc4( | |
| 443 | - QUtil::unsigned_char_pointer(upass), | |
| 444 | - key_bytes, | |
| 445 | - O_key, | |
| 446 | - data.getLengthBytes(), | |
| 447 | - (data.getR() >= 3) ? 20 : 1, | |
| 448 | - false); | |
| 449 | - return {upass, key_bytes}; | |
| 430 | + auto upass = pad_or_truncate_password_V4(user_password); | |
| 431 | + std::string O_key = compute_O_rc4_key(user_password, owner_password); | |
| 432 | + pad_short_parameter(O_key, QIntC::to_size(getLengthBytes())); | |
| 433 | + iterate_rc4(upass, O_key, getR() >= 3 ? 20 : 1, false); | |
| 434 | + return upass; | |
| 450 | 435 | } |
| 451 | 436 | |
| 452 | -static std::string | |
| 453 | -compute_U_value_R2(std::string const& user_password, QPDF::EncryptionData const& data) | |
| 437 | +std::string | |
| 438 | +QPDF::EncryptionData::compute_U_value_R2(std::string const& user_password) const | |
| 454 | 439 | { |
| 455 | 440 | // Algorithm 3.4 from the PDF 1.7 Reference Manual |
| 456 | 441 | |
| 457 | - std::string k1 = QPDF::compute_encryption_key(user_password, data); | |
| 458 | - char udata[key_bytes]; | |
| 459 | - pad_or_truncate_password_V4("", udata); | |
| 460 | - pad_short_parameter(k1, QIntC::to_size(data.getLengthBytes())); | |
| 461 | - iterate_rc4( | |
| 462 | - QUtil::unsigned_char_pointer(udata), | |
| 463 | - key_bytes, | |
| 464 | - QUtil::unsigned_char_pointer(k1), | |
| 465 | - data.getLengthBytes(), | |
| 466 | - 1, | |
| 467 | - false); | |
| 468 | - return {udata, key_bytes}; | |
| 442 | + std::string k1 = compute_encryption_key(user_password); | |
| 443 | + auto udata = padding_string; | |
| 444 | + pad_short_parameter(k1, QIntC::to_size(getLengthBytes())); | |
| 445 | + iterate_rc4(udata, k1, 1, false); | |
| 446 | + return udata; | |
| 469 | 447 | } |
| 470 | 448 | |
| 471 | -static std::string | |
| 472 | -compute_U_value_R3(std::string const& user_password, QPDF::EncryptionData const& data) | |
| 449 | +std::string | |
| 450 | +QPDF::EncryptionData::compute_U_value_R3(std::string const& user_password) const | |
| 473 | 451 | { |
| 474 | 452 | // Algorithm 3.5 from the PDF 1.7 Reference Manual |
| 475 | 453 | |
| 476 | - std::string k1 = QPDF::compute_encryption_key(user_password, data); | |
| 454 | + std::string k1 = compute_encryption_key(user_password); | |
| 477 | 455 | MD5 md5; |
| 478 | - md5.encodeDataIncrementally(pad_or_truncate_password_V4("").c_str(), key_bytes); | |
| 479 | - md5.encodeDataIncrementally(data.getId1().c_str(), data.getId1().length()); | |
| 480 | - MD5::Digest digest; | |
| 481 | - md5.digest(digest); | |
| 482 | - pad_short_parameter(k1, QIntC::to_size(data.getLengthBytes())); | |
| 483 | - iterate_rc4( | |
| 484 | - digest, | |
| 485 | - sizeof(MD5::Digest), | |
| 486 | - QUtil::unsigned_char_pointer(k1), | |
| 487 | - data.getLengthBytes(), | |
| 488 | - 20, | |
| 489 | - false); | |
| 490 | - char result[key_bytes]; | |
| 491 | - memcpy(result, digest, sizeof(MD5::Digest)); | |
| 492 | - // pad with arbitrary data -- make it consistent for the sake of | |
| 493 | - // testing | |
| 494 | - for (unsigned int i = sizeof(MD5::Digest); i < key_bytes; ++i) { | |
| 495 | - result[i] = static_cast<char>((i * i) % 0xff); | |
| 496 | - } | |
| 497 | - return {result, key_bytes}; | |
| 456 | + md5.encodeDataIncrementally(padding_string); | |
| 457 | + md5.encodeDataIncrementally(getId1()); | |
| 458 | + auto result = md5.digest(); | |
| 459 | + pad_short_parameter(k1, QIntC::to_size(getLengthBytes())); | |
| 460 | + iterate_rc4(result, k1, 20, false); | |
| 461 | + // pad with arbitrary data -- make it consistent for the sake of testing | |
| 462 | + result += "\x0\x21\x44\x69\x90\xb9\xe4\x11\x40\x71\xa4\xd9\x10\x49\x84\xc1"s; | |
| 463 | + return result; | |
| 498 | 464 | } |
| 499 | 465 | |
| 500 | -static std::string | |
| 501 | -compute_U_value(std::string const& user_password, QPDF::EncryptionData const& data) | |
| 466 | +std::string | |
| 467 | +QPDF::EncryptionData::compute_U_value(std::string const& user_password) const | |
| 502 | 468 | { |
| 503 | - if (data.getR() >= 3) { | |
| 504 | - return compute_U_value_R3(user_password, data); | |
| 469 | + if (getR() >= 3) { | |
| 470 | + return compute_U_value_R3(user_password); | |
| 505 | 471 | } |
| 506 | 472 | |
| 507 | - return compute_U_value_R2(user_password, data); | |
| 473 | + return compute_U_value_R2(user_password); | |
| 508 | 474 | } |
| 509 | 475 | |
| 510 | -static bool | |
| 511 | -check_user_password_V4(std::string const& user_password, QPDF::EncryptionData const& data) | |
| 476 | +bool | |
| 477 | +QPDF::EncryptionData::check_user_password_V4(std::string const& user_password) const | |
| 512 | 478 | { |
| 513 | 479 | // Algorithm 3.6 from the PDF 1.7 Reference Manual |
| 514 | 480 | |
| 515 | - std::string u_value = compute_U_value(user_password, data); | |
| 516 | - size_t to_compare = ((data.getR() >= 3) ? sizeof(MD5::Digest) : key_bytes); | |
| 517 | - return (memcmp(data.getU().c_str(), u_value.c_str(), to_compare) == 0); | |
| 481 | + std::string u_value = compute_U_value(user_password); | |
| 482 | + size_t to_compare = (getR() >= 3 ? sizeof(MD5::Digest) : key_bytes); | |
| 483 | + return memcmp(getU().c_str(), u_value.c_str(), to_compare) == 0; | |
| 518 | 484 | } |
| 519 | 485 | |
| 520 | -static bool | |
| 521 | -check_user_password_V5(std::string const& user_password, QPDF::EncryptionData const& data) | |
| 486 | +bool | |
| 487 | +QPDF::EncryptionData::check_user_password_V5(std::string const& user_password) const | |
| 522 | 488 | { |
| 523 | 489 | // Algorithm 3.11 from the PDF 1.7 extension level 3 |
| 524 | 490 | |
| 525 | - std::string user_data = data.getU().substr(0, 32); | |
| 526 | - std::string validation_salt = data.getU().substr(32, 8); | |
| 527 | - std::string password = truncate_password_V5(user_password); | |
| 528 | - return (hash_V5(password, validation_salt, "", data) == user_data); | |
| 491 | + std::string user_data = getU().substr(0, 32); | |
| 492 | + std::string validation_salt = getU().substr(32, 8); | |
| 493 | + std::string password = user_password.substr(0, 127); | |
| 494 | + return hash_V5(user_password.substr(0, 127), validation_salt, "") == user_data; | |
| 529 | 495 | } |
| 530 | 496 | |
| 531 | -static bool | |
| 532 | -check_user_password(std::string const& user_password, QPDF::EncryptionData const& data) | |
| 497 | +bool | |
| 498 | +QPDF::EncryptionData::check_user_password(std::string const& user_password) const | |
| 533 | 499 | { |
| 534 | - if (data.getV() < 5) { | |
| 535 | - return check_user_password_V4(user_password, data); | |
| 500 | + if (getV() < 5) { | |
| 501 | + return check_user_password_V4(user_password); | |
| 536 | 502 | } else { |
| 537 | - return check_user_password_V5(user_password, data); | |
| 503 | + return check_user_password_V5(user_password); | |
| 538 | 504 | } |
| 539 | 505 | } |
| 540 | 506 | |
| 541 | -static bool | |
| 542 | -check_owner_password_V4( | |
| 543 | - std::string& user_password, std::string const& owner_password, QPDF::EncryptionData const& data) | |
| 507 | +bool | |
| 508 | +QPDF::EncryptionData::check_owner_password_V4( | |
| 509 | + std::string& user_password, std::string const& owner_password) const | |
| 544 | 510 | { |
| 545 | 511 | // Algorithm 3.7 from the PDF 1.7 Reference Manual |
| 546 | 512 | |
| 547 | - unsigned char key[OU_key_bytes_V4]; | |
| 548 | - compute_O_rc4_key(user_password, owner_password, data, key); | |
| 549 | - unsigned char O_data[key_bytes]; | |
| 550 | - memcpy(O_data, QUtil::unsigned_char_pointer(data.getO()), key_bytes); | |
| 551 | - std::string k1(reinterpret_cast<char*>(key), OU_key_bytes_V4); | |
| 552 | - pad_short_parameter(k1, QIntC::to_size(data.getLengthBytes())); | |
| 553 | - iterate_rc4( | |
| 554 | - O_data, | |
| 555 | - key_bytes, | |
| 556 | - QUtil::unsigned_char_pointer(k1), | |
| 557 | - data.getLengthBytes(), | |
| 558 | - (data.getR() >= 3) ? 20 : 1, | |
| 559 | - true); | |
| 560 | - std::string new_user_password = std::string(reinterpret_cast<char*>(O_data), key_bytes); | |
| 561 | - bool result = false; | |
| 562 | - if (check_user_password(new_user_password, data)) { | |
| 563 | - result = true; | |
| 513 | + auto key = compute_O_rc4_key(user_password, owner_password); | |
| 514 | + pad_short_parameter(key, QIntC::to_size(getLengthBytes())); | |
| 515 | + auto new_user_password = O.substr(0, key_bytes); | |
| 516 | + iterate_rc4(new_user_password, key, (getR() >= 3) ? 20 : 1, true); | |
| 517 | + if (check_user_password(new_user_password)) { | |
| 564 | 518 | user_password = new_user_password; |
| 519 | + return true; | |
| 565 | 520 | } |
| 566 | - return result; | |
| 521 | + return false; | |
| 567 | 522 | } |
| 568 | 523 | |
| 569 | -static bool | |
| 570 | -check_owner_password_V5(std::string const& owner_password, QPDF::EncryptionData const& data) | |
| 524 | +bool | |
| 525 | +QPDF::EncryptionData::check_owner_password_V5(std::string const& owner_password) const | |
| 571 | 526 | { |
| 572 | 527 | // Algorithm 3.12 from the PDF 1.7 extension level 3 |
| 573 | 528 | |
| 574 | - std::string user_data = data.getU().substr(0, 48); | |
| 575 | - std::string owner_data = data.getO().substr(0, 32); | |
| 576 | - std::string validation_salt = data.getO().substr(32, 8); | |
| 577 | - std::string password = truncate_password_V5(owner_password); | |
| 578 | - return (hash_V5(password, validation_salt, user_data, data) == owner_data); | |
| 529 | + std::string user_data = getU().substr(0, 48); | |
| 530 | + std::string owner_data = getO().substr(0, 32); | |
| 531 | + std::string validation_salt = getO().substr(32, 8); | |
| 532 | + return hash_V5(owner_password.substr(0, 127), validation_salt, user_data) == owner_data; | |
| 579 | 533 | } |
| 580 | 534 | |
| 581 | -static bool | |
| 582 | -check_owner_password( | |
| 583 | - std::string& user_password, std::string const& owner_password, QPDF::EncryptionData const& data) | |
| 535 | +bool | |
| 536 | +QPDF::EncryptionData::check_owner_password( | |
| 537 | + std::string& user_password, std::string const& owner_password) const | |
| 584 | 538 | { |
| 585 | - if (data.getV() < 5) { | |
| 586 | - return check_owner_password_V4(user_password, owner_password, data); | |
| 539 | + if (getV() < 5) { | |
| 540 | + return check_owner_password_V4(user_password, owner_password); | |
| 587 | 541 | } else { |
| 588 | - return check_owner_password_V5(owner_password, data); | |
| 542 | + return check_owner_password_V5(owner_password); | |
| 589 | 543 | } |
| 590 | 544 | } |
| 591 | 545 | |
| 592 | 546 | std::string |
| 593 | -QPDF::recover_encryption_key_with_password(std::string const& password, EncryptionData const& data) | |
| 547 | +QPDF::EncryptionData::recover_encryption_key_with_password(std::string const& password) const | |
| 594 | 548 | { |
| 595 | 549 | // Disregard whether Perms is valid. |
| 596 | 550 | bool disregard; |
| 597 | - return recover_encryption_key_with_password(password, data, disregard); | |
| 551 | + return recover_encryption_key_with_password(password, disregard); | |
| 598 | 552 | } |
| 599 | 553 | |
| 600 | -static void | |
| 601 | -compute_U_UE_value_V5( | |
| 602 | - std::string const& user_password, | |
| 603 | - std::string const& encryption_key, | |
| 604 | - QPDF::EncryptionData const& data, | |
| 605 | - std::string& U, | |
| 606 | - std::string& UE) | |
| 607 | -{ | |
| 608 | - // Algorithm 3.8 from the PDF 1.7 extension level 3 | |
| 609 | - char k[16]; | |
| 610 | - QUtil::initializeWithRandomBytes(reinterpret_cast<unsigned char*>(k), sizeof(k)); | |
| 611 | - std::string validation_salt(k, 8); | |
| 612 | - std::string key_salt(k + 8, 8); | |
| 613 | - U = hash_V5(user_password, validation_salt, "", data) + validation_salt + key_salt; | |
| 614 | - std::string intermediate_key = hash_V5(user_password, key_salt, "", data); | |
| 615 | - UE = process_with_aes(intermediate_key, true, encryption_key); | |
| 616 | -} | |
| 617 | - | |
| 618 | -static void | |
| 619 | -compute_O_OE_value_V5( | |
| 620 | - std::string const& owner_password, | |
| 621 | - std::string const& encryption_key, | |
| 622 | - QPDF::EncryptionData const& data, | |
| 623 | - std::string const& U, | |
| 624 | - std::string& O, | |
| 625 | - std::string& OE) | |
| 626 | -{ | |
| 627 | - // Algorithm 3.9 from the PDF 1.7 extension level 3 | |
| 628 | - char k[16]; | |
| 629 | - QUtil::initializeWithRandomBytes(reinterpret_cast<unsigned char*>(k), sizeof(k)); | |
| 630 | - std::string validation_salt(k, 8); | |
| 631 | - std::string key_salt(k + 8, 8); | |
| 632 | - O = hash_V5(owner_password, validation_salt, U, data) + validation_salt + key_salt; | |
| 633 | - std::string intermediate_key = hash_V5(owner_password, key_salt, U, data); | |
| 634 | - OE = process_with_aes(intermediate_key, true, encryption_key); | |
| 635 | -} | |
| 636 | - | |
| 637 | -void | |
| 638 | -compute_Perms_value_V5_clear( | |
| 639 | - std::string const& encryption_key, QPDF::EncryptionData const& data, unsigned char k[16]) | |
| 554 | +std::string | |
| 555 | +QPDF::EncryptionData::compute_Perms_value_V5_clear() const | |
| 640 | 556 | { |
| 641 | 557 | // From algorithm 3.10 from the PDF 1.7 extension level 3 |
| 642 | - unsigned long long extended_perms = | |
| 643 | - 0xffffffff00000000LL | static_cast<unsigned long long>(data.getP()); | |
| 644 | - for (int i = 0; i < 8; ++i) { | |
| 645 | - k[i] = static_cast<unsigned char>(extended_perms & 0xff); | |
| 646 | - extended_perms >>= 8; | |
| 558 | + std::string k = " \xff\xff\xff\xffTadb "; | |
| 559 | + int perms = getP(); | |
| 560 | + for (size_t i = 0; i < 4; ++i) { | |
| 561 | + k[i] = static_cast<char>(perms & 0xff); | |
| 562 | + perms >>= 8; | |
| 647 | 563 | } |
| 648 | - k[8] = data.getEncryptMetadata() ? 'T' : 'F'; | |
| 649 | - k[9] = 'a'; | |
| 650 | - k[10] = 'd'; | |
| 651 | - k[11] = 'b'; | |
| 652 | - QUtil::initializeWithRandomBytes(k + 12, 4); | |
| 653 | -} | |
| 654 | - | |
| 655 | -static std::string | |
| 656 | -compute_Perms_value_V5(std::string const& encryption_key, QPDF::EncryptionData const& data) | |
| 657 | -{ | |
| 658 | - // Algorithm 3.10 from the PDF 1.7 extension level 3 | |
| 659 | - unsigned char k[16]; | |
| 660 | - compute_Perms_value_V5_clear(encryption_key, data, k); | |
| 661 | - return process_with_aes( | |
| 662 | - encryption_key, true, std::string(reinterpret_cast<char*>(k), sizeof(k))); | |
| 564 | + if (!getEncryptMetadata()) { | |
| 565 | + k[8] = 'F'; | |
| 566 | + } | |
| 567 | + QUtil::initializeWithRandomBytes(reinterpret_cast<unsigned char*>(&k[12]), 4); | |
| 568 | + return k; | |
| 663 | 569 | } |
| 664 | 570 | |
| 665 | 571 | std::string |
| 666 | -QPDF::recover_encryption_key_with_password( | |
| 667 | - std::string const& password, EncryptionData const& data, bool& perms_valid) | |
| 572 | +QPDF::EncryptionData::recover_encryption_key_with_password( | |
| 573 | + std::string const& password, bool& perms_valid) const | |
| 668 | 574 | { |
| 669 | 575 | // Algorithm 3.2a from the PDF 1.7 extension level 3 |
| 670 | 576 | |
| ... | ... | @@ -673,129 +579,136 @@ QPDF::recover_encryption_key_with_password( |
| 673 | 579 | // profile of stringprep (RFC 3454) and then convert the result to UTF-8. |
| 674 | 580 | |
| 675 | 581 | perms_valid = false; |
| 676 | - std::string key_password = truncate_password_V5(password); | |
| 582 | + std::string key_password = password.substr(0, 127); | |
| 677 | 583 | std::string key_salt; |
| 678 | 584 | std::string user_data; |
| 679 | 585 | std::string encrypted_file_key; |
| 680 | - if (check_owner_password_V5(key_password, data)) { | |
| 681 | - key_salt = data.getO().substr(40, 8); | |
| 682 | - user_data = data.getU().substr(0, 48); | |
| 683 | - encrypted_file_key = data.getOE().substr(0, 32); | |
| 684 | - } else if (check_user_password_V5(key_password, data)) { | |
| 685 | - key_salt = data.getU().substr(40, 8); | |
| 686 | - encrypted_file_key = data.getUE().substr(0, 32); | |
| 586 | + if (check_owner_password_V5(key_password)) { | |
| 587 | + key_salt = getO().substr(40, 8); | |
| 588 | + user_data = getU().substr(0, 48); | |
| 589 | + encrypted_file_key = getOE().substr(0, 32); | |
| 590 | + } else if (check_user_password_V5(key_password)) { | |
| 591 | + key_salt = getU().substr(40, 8); | |
| 592 | + encrypted_file_key = getUE().substr(0, 32); | |
| 687 | 593 | } |
| 688 | - std::string intermediate_key = hash_V5(key_password, key_salt, user_data, data); | |
| 594 | + std::string intermediate_key = hash_V5(key_password, key_salt, user_data); | |
| 689 | 595 | std::string file_key = process_with_aes(intermediate_key, false, encrypted_file_key); |
| 690 | 596 | |
| 691 | 597 | // Decrypt Perms and check against expected value |
| 692 | - std::string perms_check = process_with_aes(file_key, false, data.getPerms(), 12); | |
| 693 | - unsigned char k[16]; | |
| 694 | - compute_Perms_value_V5_clear(file_key, data, k); | |
| 695 | - perms_valid = (memcmp(perms_check.c_str(), k, 12) == 0); | |
| 696 | - | |
| 598 | + auto perms_check = process_with_aes(file_key, false, getPerms()).substr(0, 12); | |
| 599 | + perms_valid = compute_Perms_value_V5_clear().substr(0, 12) == perms_check; | |
| 697 | 600 | return file_key; |
| 698 | 601 | } |
| 699 | 602 | |
| 700 | 603 | QPDF::encryption_method_e |
| 701 | -QPDF::interpretCF(std::shared_ptr<EncryptionParameters> encp, QPDFObjectHandle cf) | |
| 604 | +QPDF::EncryptionParameters::interpretCF(QPDFObjectHandle const& cf) const | |
| 702 | 605 | { |
| 703 | - if (cf.isName()) { | |
| 704 | - std::string filter = cf.getName(); | |
| 705 | - auto it = encp->crypt_filters.find(filter); | |
| 706 | - if (it != encp->crypt_filters.end()) { | |
| 707 | - return it->second; | |
| 708 | - } | |
| 709 | - if (filter == "/Identity") { | |
| 710 | - return e_none; | |
| 711 | - } | |
| 712 | - return e_unknown; | |
| 606 | + if (!cf.isName()) { | |
| 607 | + // Default: /Identity | |
| 608 | + return e_none; | |
| 609 | + } | |
| 610 | + std::string filter = cf.getName(); | |
| 611 | + auto it = crypt_filters.find(filter); | |
| 612 | + if (it != crypt_filters.end()) { | |
| 613 | + return it->second; | |
| 614 | + } | |
| 615 | + if (filter == "/Identity") { | |
| 616 | + return e_none; | |
| 713 | 617 | } |
| 714 | - // Default: /Identity | |
| 715 | - return e_none; | |
| 618 | + return e_unknown; | |
| 716 | 619 | } |
| 717 | 620 | |
| 718 | 621 | void |
| 719 | 622 | QPDF::initializeEncryption() |
| 720 | 623 | { |
| 721 | - if (m->encp->encryption_initialized) { | |
| 624 | + m->encp->initialize(*this); | |
| 625 | +} | |
| 626 | + | |
| 627 | +void | |
| 628 | +QPDF::EncryptionParameters::initialize(QPDF& qpdf) | |
| 629 | +{ | |
| 630 | + if (encryption_initialized) { | |
| 722 | 631 | return; |
| 723 | 632 | } |
| 724 | - m->encp->encryption_initialized = true; | |
| 633 | + encryption_initialized = true; | |
| 634 | + | |
| 635 | + auto& qm = *qpdf.m; | |
| 636 | + auto& trailer = qm.trailer; | |
| 637 | + auto& file = qm.file; | |
| 638 | + | |
| 639 | + auto warn_damaged_pdf = [&qpdf](std::string const& msg) { | |
| 640 | + qpdf.warn(qpdf.damagedPDF("encryption dictionary", msg)); | |
| 641 | + }; | |
| 642 | + auto throw_damaged_pdf = [&qpdf](std::string const& msg) { | |
| 643 | + throw qpdf.damagedPDF("encryption dictionary", msg); | |
| 644 | + }; | |
| 645 | + auto unsupported = [&file](std::string const& msg) -> QPDFExc { | |
| 646 | + return { | |
| 647 | + qpdf_e_unsupported, | |
| 648 | + file->getName(), | |
| 649 | + "encryption dictionary", | |
| 650 | + file->getLastOffset(), | |
| 651 | + msg}; | |
| 652 | + }; | |
| 725 | 653 | |
| 726 | 654 | // After we initialize encryption parameters, we must use stored key information and never look |
| 727 | 655 | // at /Encrypt again. Otherwise, things could go wrong if someone mutates the encryption |
| 728 | 656 | // dictionary. |
| 729 | 657 | |
| 730 | - if (!m->trailer.hasKey("/Encrypt")) { | |
| 658 | + if (!trailer.hasKey("/Encrypt")) { | |
| 731 | 659 | return; |
| 732 | 660 | } |
| 733 | 661 | |
| 734 | 662 | // Go ahead and set m->encrypted here. That way, isEncrypted will return true even if there |
| 735 | 663 | // were errors reading the encryption dictionary. |
| 736 | - m->encp->encrypted = true; | |
| 664 | + encrypted = true; | |
| 737 | 665 | |
| 738 | 666 | std::string id1; |
| 739 | - QPDFObjectHandle id_obj = m->trailer.getKey("/ID"); | |
| 740 | - if ((id_obj.isArray() && (id_obj.getArrayNItems() == 2) && id_obj.getArrayItem(0).isString())) { | |
| 741 | - id1 = id_obj.getArrayItem(0).getStringValue(); | |
| 742 | - } else { | |
| 667 | + auto id_obj = trailer.getKey("/ID"); | |
| 668 | + if (!id_obj.isArray() || id_obj.getArrayNItems() != 2 || !id_obj.getArrayItem(0).isString()) { | |
| 743 | 669 | // Treating a missing ID as the empty string enables qpdf to decrypt some invalid encrypted |
| 744 | 670 | // files with no /ID that poppler can read but Adobe Reader can't. |
| 745 | - warn(damagedPDF("trailer", "invalid /ID in trailer dictionary")); | |
| 671 | + qpdf.warn(qpdf.damagedPDF("trailer", "invalid /ID in trailer dictionary")); | |
| 672 | + } else { | |
| 673 | + id1 = id_obj.getArrayItem(0).getStringValue(); | |
| 746 | 674 | } |
| 747 | 675 | |
| 748 | - QPDFObjectHandle encryption_dict = m->trailer.getKey("/Encrypt"); | |
| 676 | + auto encryption_dict = trailer.getKey("/Encrypt"); | |
| 749 | 677 | if (!encryption_dict.isDictionary()) { |
| 750 | - throw damagedPDF("/Encrypt in trailer dictionary is not a dictionary"); | |
| 678 | + throw qpdf.damagedPDF("/Encrypt in trailer dictionary is not a dictionary"); | |
| 751 | 679 | } |
| 752 | 680 | |
| 753 | 681 | if (!(encryption_dict.getKey("/Filter").isName() && |
| 754 | 682 | (encryption_dict.getKey("/Filter").getName() == "/Standard"))) { |
| 755 | - throw QPDFExc( | |
| 756 | - qpdf_e_unsupported, | |
| 757 | - m->file->getName(), | |
| 758 | - "encryption dictionary", | |
| 759 | - m->file->getLastOffset(), | |
| 760 | - "unsupported encryption filter"); | |
| 683 | + throw unsupported("unsupported encryption filter"); | |
| 761 | 684 | } |
| 762 | 685 | if (!encryption_dict.getKey("/SubFilter").isNull()) { |
| 763 | - warn( | |
| 764 | - qpdf_e_unsupported, | |
| 765 | - "encryption dictionary", | |
| 766 | - m->file->getLastOffset(), | |
| 767 | - "file uses encryption SubFilters, which qpdf does not support"); | |
| 686 | + qpdf.warn(unsupported("file uses encryption SubFilters, which qpdf does not support")); | |
| 768 | 687 | } |
| 769 | 688 | |
| 770 | 689 | if (!(encryption_dict.getKey("/V").isInteger() && encryption_dict.getKey("/R").isInteger() && |
| 771 | 690 | encryption_dict.getKey("/O").isString() && encryption_dict.getKey("/U").isString() && |
| 772 | 691 | encryption_dict.getKey("/P").isInteger())) { |
| 773 | - throw damagedPDF( | |
| 774 | - "encryption dictionary", | |
| 775 | - "some encryption dictionary parameters are missing or the wrong " | |
| 776 | - "type"); | |
| 692 | + throw_damaged_pdf("some encryption dictionary parameters are missing or the wrong type"); | |
| 777 | 693 | } |
| 778 | 694 | |
| 779 | 695 | int V = encryption_dict.getKey("/V").getIntValueAsInt(); |
| 780 | 696 | int R = encryption_dict.getKey("/R").getIntValueAsInt(); |
| 781 | 697 | std::string O = encryption_dict.getKey("/O").getStringValue(); |
| 782 | 698 | std::string U = encryption_dict.getKey("/U").getStringValue(); |
| 783 | - int P = static_cast<int>(encryption_dict.getKey("/P").getIntValue()); | |
| 699 | + int p = static_cast<int>(encryption_dict.getKey("/P").getIntValue()); | |
| 784 | 700 | |
| 785 | 701 | // If supporting new encryption R/V values, remember to update error message inside this if |
| 786 | 702 | // statement. |
| 787 | - if (!(((R >= 2) && (R <= 6)) && ((V == 1) || (V == 2) || (V == 4) || (V == 5)))) { | |
| 788 | - throw QPDFExc( | |
| 789 | - qpdf_e_unsupported, | |
| 790 | - m->file->getName(), | |
| 791 | - "encryption dictionary", | |
| 792 | - m->file->getLastOffset(), | |
| 703 | + if (!(2 <= R && R <= 6 && (V == 1 || V == 2 || V == 4 || V == 5))) { | |
| 704 | + throw unsupported( | |
| 793 | 705 | "Unsupported /R or /V in encryption dictionary; R = " + std::to_string(R) + |
| 794 | - " (max 6), V = " + std::to_string(V) + " (max 5)"); | |
| 706 | + " (max 6), V = " + std::to_string(V) + " (max 5)"); | |
| 795 | 707 | } |
| 796 | 708 | |
| 797 | - m->encp->encryption_V = V; | |
| 798 | - m->encp->encryption_R = R; | |
| 709 | + P_ = std::bitset<32>(static_cast<unsigned long long>(p)); | |
| 710 | + encryption_V = V; | |
| 711 | + R_ = R; | |
| 799 | 712 | |
| 800 | 713 | // OE, UE, and Perms are only present if V >= 5. |
| 801 | 714 | std::string OE; |
| ... | ... | @@ -806,19 +719,15 @@ QPDF::initializeEncryption() |
| 806 | 719 | // These must be exactly the right number of bytes. |
| 807 | 720 | pad_short_parameter(O, key_bytes); |
| 808 | 721 | pad_short_parameter(U, key_bytes); |
| 809 | - if (!((O.length() == key_bytes) && (U.length() == key_bytes))) { | |
| 810 | - throw damagedPDF( | |
| 811 | - "encryption dictionary", | |
| 812 | - "incorrect length for /O and/or /U in encryption dictionary"); | |
| 722 | + if (!(O.length() == key_bytes && U.length() == key_bytes)) { | |
| 723 | + throw_damaged_pdf("incorrect length for /O and/or /U in encryption dictionary"); | |
| 813 | 724 | } |
| 814 | 725 | } else { |
| 815 | 726 | if (!(encryption_dict.getKey("/OE").isString() && |
| 816 | 727 | encryption_dict.getKey("/UE").isString() && |
| 817 | 728 | encryption_dict.getKey("/Perms").isString())) { |
| 818 | - throw damagedPDF( | |
| 819 | - "encryption dictionary", | |
| 820 | - "some V=5 encryption dictionary parameters are missing or the " | |
| 821 | - "wrong type"); | |
| 729 | + throw_damaged_pdf( | |
| 730 | + "some V=5 encryption dictionary parameters are missing or the wrong type"); | |
| 822 | 731 | } |
| 823 | 732 | OE = encryption_dict.getKey("/OE").getStringValue(); |
| 824 | 733 | UE = encryption_dict.getKey("/UE").getStringValue(); |
| ... | ... | @@ -832,7 +741,7 @@ QPDF::initializeEncryption() |
| 832 | 741 | pad_short_parameter(Perms, Perms_key_bytes_V5); |
| 833 | 742 | } |
| 834 | 743 | |
| 835 | - int Length = 0; | |
| 744 | + int Length = 128; // Just take a guess. | |
| 836 | 745 | if (V <= 1) { |
| 837 | 746 | Length = 40; |
| 838 | 747 | } else if (V == 4) { |
| ... | ... | @@ -842,25 +751,20 @@ QPDF::initializeEncryption() |
| 842 | 751 | } else { |
| 843 | 752 | if (encryption_dict.getKey("/Length").isInteger()) { |
| 844 | 753 | Length = encryption_dict.getKey("/Length").getIntValueAsInt(); |
| 845 | - if ((Length % 8) || (Length < 40) || (Length > 128)) { | |
| 846 | - Length = 0; | |
| 754 | + if (Length % 8 || Length < 40 || Length > 128) { | |
| 755 | + Length = 128; // Just take a guess. | |
| 847 | 756 | } |
| 848 | 757 | } |
| 849 | 758 | } |
| 850 | - if (Length == 0) { | |
| 851 | - // Still no Length? Just take a guess. | |
| 852 | - Length = 128; | |
| 853 | - } | |
| 854 | 759 | |
| 855 | - m->encp->encrypt_metadata = true; | |
| 856 | - if ((V >= 4) && (encryption_dict.getKey("/EncryptMetadata").isBool())) { | |
| 857 | - m->encp->encrypt_metadata = encryption_dict.getKey("/EncryptMetadata").getBoolValue(); | |
| 760 | + encrypt_metadata = true; | |
| 761 | + if (V >= 4 && encryption_dict.getKey("/EncryptMetadata").isBool()) { | |
| 762 | + encrypt_metadata = encryption_dict.getKey("/EncryptMetadata").getBoolValue(); | |
| 858 | 763 | } |
| 859 | 764 | |
| 860 | - if ((V == 4) || (V == 5)) { | |
| 861 | - QPDFObjectHandle CF = encryption_dict.getKey("/CF"); | |
| 862 | - for (auto const& filter: CF.getKeys()) { | |
| 863 | - QPDFObjectHandle cdict = CF.getKey(filter); | |
| 765 | + if (V == 4 || V == 5) { | |
| 766 | + auto CF = encryption_dict.getKey("/CF"); | |
| 767 | + for (auto const& [filter, cdict]: CF.as_dictionary()) { | |
| 864 | 768 | if (cdict.isDictionary()) { |
| 865 | 769 | encryption_method_e method = e_none; |
| 866 | 770 | if (cdict.getKey("/CFM").isName()) { |
| ... | ... | @@ -879,16 +783,13 @@ QPDF::initializeEncryption() |
| 879 | 783 | method = e_unknown; |
| 880 | 784 | } |
| 881 | 785 | } |
| 882 | - m->encp->crypt_filters[filter] = method; | |
| 786 | + crypt_filters[filter] = method; | |
| 883 | 787 | } |
| 884 | 788 | } |
| 885 | 789 | |
| 886 | - QPDFObjectHandle StmF = encryption_dict.getKey("/StmF"); | |
| 887 | - QPDFObjectHandle StrF = encryption_dict.getKey("/StrF"); | |
| 888 | - QPDFObjectHandle EFF = encryption_dict.getKey("/EFF"); | |
| 889 | - m->encp->cf_stream = interpretCF(m->encp, StmF); | |
| 890 | - m->encp->cf_string = interpretCF(m->encp, StrF); | |
| 891 | - if (EFF.isName()) { | |
| 790 | + cf_stream = interpretCF(encryption_dict.getKey("/StmF")); | |
| 791 | + cf_string = interpretCF(encryption_dict.getKey("/StrF")); | |
| 792 | + if (auto EFF = encryption_dict.getKey("/EFF"); EFF.isName()) { | |
| 892 | 793 | // qpdf does not use this for anything other than informational purposes. This is |
| 893 | 794 | // intended to instruct conforming writers on which crypt filter should be used when new |
| 894 | 795 | // file attachments are added to a PDF file, but qpdf never generates encrypted files |
| ... | ... | @@ -898,55 +799,50 @@ QPDF::initializeEncryption() |
| 898 | 799 | // the way I was imagining. Still, providing this information could be useful when |
| 899 | 800 | // looking at a file generated by something else, such as Acrobat when specifying that |
| 900 | 801 | // only attachments should be encrypted. |
| 901 | - m->encp->cf_file = interpretCF(m->encp, EFF); | |
| 802 | + cf_file = interpretCF(EFF); | |
| 902 | 803 | } else { |
| 903 | - m->encp->cf_file = m->encp->cf_stream; | |
| 804 | + cf_file = cf_stream; | |
| 904 | 805 | } |
| 905 | 806 | } |
| 906 | 807 | |
| 907 | - EncryptionData data(V, R, Length / 8, P, O, U, OE, UE, Perms, id1, m->encp->encrypt_metadata); | |
| 908 | - if (m->provided_password_is_hex_key) { | |
| 808 | + EncryptionData data(V, R, Length / 8, p, O, U, OE, UE, Perms, id1, encrypt_metadata); | |
| 809 | + if (qm.provided_password_is_hex_key) { | |
| 909 | 810 | // ignore passwords in file |
| 910 | - } else { | |
| 911 | - m->encp->owner_password_matched = | |
| 912 | - check_owner_password(m->encp->user_password, m->encp->provided_password, data); | |
| 913 | - if (m->encp->owner_password_matched && (V < 5)) { | |
| 914 | - // password supplied was owner password; user_password has been initialized for V < 5 | |
| 915 | - if (getTrimmedUserPassword() == m->encp->provided_password) { | |
| 916 | - m->encp->user_password_matched = true; | |
| 917 | - QTC::TC("qpdf", "QPDF_encryption user matches owner V < 5"); | |
| 918 | - } | |
| 919 | - } else { | |
| 920 | - m->encp->user_password_matched = check_user_password(m->encp->provided_password, data); | |
| 921 | - if (m->encp->user_password_matched) { | |
| 922 | - m->encp->user_password = m->encp->provided_password; | |
| 923 | - } | |
| 924 | - } | |
| 925 | - if (m->encp->user_password_matched && m->encp->owner_password_matched) { | |
| 926 | - QTC::TC("qpdf", "QPDF_encryption same password", (V < 5) ? 0 : 1); | |
| 811 | + encryption_key = QUtil::hex_decode(provided_password); | |
| 812 | + return; | |
| 813 | + } | |
| 814 | + | |
| 815 | + owner_password_matched = data.check_owner_password(user_password, provided_password); | |
| 816 | + if (owner_password_matched && V < 5) { | |
| 817 | + // password supplied was owner password; user_password has been initialized for V < 5 | |
| 818 | + if (qpdf.getTrimmedUserPassword() == provided_password) { | |
| 819 | + user_password_matched = true; | |
| 820 | + QTC::TC("qpdf", "QPDF_encryption user matches owner V < 5"); | |
| 927 | 821 | } |
| 928 | - if (!(m->encp->owner_password_matched || m->encp->user_password_matched)) { | |
| 929 | - throw QPDFExc(qpdf_e_password, m->file->getName(), "", 0, "invalid password"); | |
| 822 | + } else { | |
| 823 | + user_password_matched = data.check_user_password(provided_password); | |
| 824 | + if (user_password_matched) { | |
| 825 | + user_password = provided_password; | |
| 930 | 826 | } |
| 931 | 827 | } |
| 828 | + if (user_password_matched && owner_password_matched) { | |
| 829 | + QTC::TC("qpdf", "QPDF_encryption same password", (V < 5) ? 0 : 1); | |
| 830 | + } | |
| 831 | + if (!(owner_password_matched || user_password_matched)) { | |
| 832 | + throw QPDFExc(qpdf_e_password, file->getName(), "", 0, "invalid password"); | |
| 833 | + } | |
| 932 | 834 | |
| 933 | - if (m->provided_password_is_hex_key) { | |
| 934 | - m->encp->encryption_key = QUtil::hex_decode(m->encp->provided_password); | |
| 935 | - } else if (V < 5) { | |
| 835 | + if (V < 5) { | |
| 936 | 836 | // For V < 5, the user password is encrypted with the owner password, and the user password |
| 937 | 837 | // is always used for computing the encryption key. |
| 938 | - m->encp->encryption_key = compute_encryption_key(m->encp->user_password, data); | |
| 838 | + encryption_key = data.compute_encryption_key(user_password); | |
| 939 | 839 | } else { |
| 940 | 840 | // For V >= 5, either password can be used independently to compute the encryption key, and |
| 941 | 841 | // neither password can be used to recover the other. |
| 942 | 842 | bool perms_valid; |
| 943 | - m->encp->encryption_key = | |
| 944 | - recover_encryption_key_with_password(m->encp->provided_password, data, perms_valid); | |
| 843 | + encryption_key = data.recover_encryption_key_with_password(provided_password, perms_valid); | |
| 945 | 844 | if (!perms_valid) { |
| 946 | - warn(damagedPDF( | |
| 947 | - "encryption dictionary", | |
| 948 | - "/Perms field in encryption dictionary doesn't match expected " | |
| 949 | - "value")); | |
| 845 | + warn_damaged_pdf("/Perms field in encryption dictionary doesn't match expected value"); | |
| 950 | 846 | } |
| 951 | 847 | } |
| 952 | 848 | } |
| ... | ... | @@ -960,12 +856,7 @@ QPDF::getKeyForObject(std::shared_ptr<EncryptionParameters> encp, QPDFObjGen og, |
| 960 | 856 | |
| 961 | 857 | if (og != encp->cached_key_og) { |
| 962 | 858 | encp->cached_object_encryption_key = compute_data_key( |
| 963 | - encp->encryption_key, | |
| 964 | - og.getObj(), | |
| 965 | - og.getGen(), | |
| 966 | - use_aes, | |
| 967 | - encp->encryption_V, | |
| 968 | - encp->encryption_R); | |
| 859 | + encp->encryption_key, og.getObj(), og.getGen(), use_aes, encp->encryption_V, encp->R()); | |
| 969 | 860 | encp->cached_key_og = og; |
| 970 | 861 | } |
| 971 | 862 | |
| ... | ... | @@ -1070,7 +961,7 @@ QPDF::decryptStream( |
| 1070 | 961 | QPDFObjectHandle decode_parms = stream_dict.getKey("/DecodeParms"); |
| 1071 | 962 | if (decode_parms.isDictionaryOfType("/CryptFilterDecodeParms")) { |
| 1072 | 963 | QTC::TC("qpdf", "QPDF_encryption stream crypt filter"); |
| 1073 | - method = interpretCF(encp, decode_parms.getKey("/Name")); | |
| 964 | + method = encp->interpretCF(decode_parms.getKey("/Name")); | |
| 1074 | 965 | method_source = "stream's Crypt decode parameters"; |
| 1075 | 966 | } |
| 1076 | 967 | } else if ( |
| ... | ... | @@ -1085,7 +976,7 @@ QPDF::decryptStream( |
| 1085 | 976 | if (crypt_params.isDictionary() && |
| 1086 | 977 | crypt_params.getKey("/Name").isName()) { |
| 1087 | 978 | QTC::TC("qpdf", "QPDF_encrypt crypt array"); |
| 1088 | - method = interpretCF(encp, crypt_params.getKey("/Name")); | |
| 979 | + method = encp->interpretCF(crypt_params.getKey("/Name")); | |
| 1089 | 980 | method_source = "stream's Crypt decode parameters (array)"; |
| 1090 | 981 | } |
| 1091 | 982 | } |
| ... | ... | @@ -1164,17 +1055,23 @@ QPDF::compute_encryption_O_U( |
| 1164 | 1055 | int P, |
| 1165 | 1056 | bool encrypt_metadata, |
| 1166 | 1057 | std::string const& id1, |
| 1167 | - std::string& O, | |
| 1168 | - std::string& U) | |
| 1058 | + std::string& out_O, | |
| 1059 | + std::string& out_U) | |
| 1060 | +{ | |
| 1061 | + EncryptionData data(V, R, key_len, P, "", "", "", "", "", id1, encrypt_metadata); | |
| 1062 | + data.compute_encryption_O_U(user_password, owner_password); | |
| 1063 | + out_O = data.getO(); | |
| 1064 | + out_U = data.getU(); | |
| 1065 | +} | |
| 1066 | + | |
| 1067 | +void | |
| 1068 | +QPDF::EncryptionData::compute_encryption_O_U(char const* user_password, char const* owner_password) | |
| 1169 | 1069 | { |
| 1170 | 1070 | if (V >= 5) { |
| 1171 | 1071 | throw std::logic_error("compute_encryption_O_U called for file with V >= 5"); |
| 1172 | 1072 | } |
| 1173 | - EncryptionData data(V, R, key_len, P, "", "", "", "", "", id1, encrypt_metadata); | |
| 1174 | - data.setO(compute_O_value(user_password, owner_password, data)); | |
| 1175 | - O = data.getO(); | |
| 1176 | - data.setU(compute_U_value(user_password, data)); | |
| 1177 | - U = data.getU(); | |
| 1073 | + O = compute_O_value(user_password, owner_password); | |
| 1074 | + U = compute_U_value(user_password); | |
| 1178 | 1075 | } |
| 1179 | 1076 | |
| 1180 | 1077 | void |
| ... | ... | @@ -1188,20 +1085,53 @@ QPDF::compute_encryption_parameters_V5( |
| 1188 | 1085 | bool encrypt_metadata, |
| 1189 | 1086 | std::string const& id1, |
| 1190 | 1087 | std::string& encryption_key, |
| 1191 | - std::string& O, | |
| 1192 | - std::string& U, | |
| 1193 | - std::string& OE, | |
| 1194 | - std::string& UE, | |
| 1195 | - std::string& Perms) | |
| 1088 | + std::string& out_O, | |
| 1089 | + std::string& out_U, | |
| 1090 | + std::string& out_OE, | |
| 1091 | + std::string& out_UE, | |
| 1092 | + std::string& out_Perms) | |
| 1196 | 1093 | { |
| 1197 | 1094 | EncryptionData data(V, R, key_len, P, "", "", "", "", "", id1, encrypt_metadata); |
| 1198 | - unsigned char k[key_bytes]; | |
| 1199 | - QUtil::initializeWithRandomBytes(k, key_bytes); | |
| 1200 | - encryption_key = std::string(reinterpret_cast<char*>(k), key_bytes); | |
| 1201 | - compute_U_UE_value_V5(user_password, encryption_key, data, U, UE); | |
| 1202 | - compute_O_OE_value_V5(owner_password, encryption_key, data, U, O, OE); | |
| 1203 | - Perms = compute_Perms_value_V5(encryption_key, data); | |
| 1204 | - data.setV5EncryptionParameters(O, OE, U, UE, Perms); | |
| 1095 | + encryption_key = data.compute_encryption_parameters_V5(user_password, owner_password); | |
| 1096 | + | |
| 1097 | + out_O = data.getO(); | |
| 1098 | + out_U = data.getU(); | |
| 1099 | + out_OE = data.getOE(); | |
| 1100 | + out_UE = data.getUE(); | |
| 1101 | + out_Perms = data.getPerms(); | |
| 1102 | +} | |
| 1103 | + | |
| 1104 | +std::string | |
| 1105 | +QPDF::EncryptionData::compute_encryption_parameters_V5( | |
| 1106 | + char const* user_password, char const* owner_password) | |
| 1107 | +{ | |
| 1108 | + auto out_encryption_key = util::random_string(key_bytes); | |
| 1109 | + // Algorithm 8 from the PDF 2.0 | |
| 1110 | + auto validation_salt = util::random_string(8); | |
| 1111 | + auto key_salt = util::random_string(8); | |
| 1112 | + U = hash_V5(user_password, validation_salt, "").append(validation_salt).append(key_salt); | |
| 1113 | + auto intermediate_key = hash_V5(user_password, key_salt, ""); | |
| 1114 | + UE = process_with_aes(intermediate_key, true, out_encryption_key); | |
| 1115 | + // Algorithm 9 from the PDF 2.0 | |
| 1116 | + validation_salt = util::random_string(8); | |
| 1117 | + key_salt = util::random_string(8); | |
| 1118 | + O = hash_V5(owner_password, validation_salt, U) + validation_salt + key_salt; | |
| 1119 | + intermediate_key = hash_V5(owner_password, key_salt, U); | |
| 1120 | + OE = process_with_aes(intermediate_key, true, out_encryption_key); | |
| 1121 | + // Algorithm 10 from the PDF 2.0 | |
| 1122 | + Perms = process_with_aes(out_encryption_key, true, compute_Perms_value_V5_clear()); | |
| 1123 | + return out_encryption_key; | |
| 1124 | +} | |
| 1125 | + | |
| 1126 | +std::string | |
| 1127 | +QPDF::EncryptionData::compute_parameters(char const* user_password, char const* owner_password) | |
| 1128 | +{ | |
| 1129 | + if (V < 5) { | |
| 1130 | + compute_encryption_O_U(user_password, owner_password); | |
| 1131 | + return compute_encryption_key(user_password); | |
| 1132 | + } else { | |
| 1133 | + return compute_encryption_parameters_V5(user_password, owner_password); | |
| 1134 | + } | |
| 1205 | 1135 | } |
| 1206 | 1136 | |
| 1207 | 1137 | std::string const& |
| ... | ... | @@ -1233,9 +1163,12 @@ QPDF::isEncrypted() const |
| 1233 | 1163 | bool |
| 1234 | 1164 | QPDF::isEncrypted(int& R, int& P) |
| 1235 | 1165 | { |
| 1236 | - int V; | |
| 1237 | - encryption_method_e stream, string, file; | |
| 1238 | - return isEncrypted(R, P, V, stream, string, file); | |
| 1166 | + if (!m->encp->encrypted) { | |
| 1167 | + return false; | |
| 1168 | + } | |
| 1169 | + P = m->encp->P(); | |
| 1170 | + R = m->encp->R(); | |
| 1171 | + return true; | |
| 1239 | 1172 | } |
| 1240 | 1173 | |
| 1241 | 1174 | bool |
| ... | ... | @@ -1247,22 +1180,16 @@ QPDF::isEncrypted( |
| 1247 | 1180 | encryption_method_e& string_method, |
| 1248 | 1181 | encryption_method_e& file_method) |
| 1249 | 1182 | { |
| 1250 | - if (m->encp->encrypted) { | |
| 1251 | - QPDFObjectHandle trailer = getTrailer(); | |
| 1252 | - QPDFObjectHandle encrypt = trailer.getKey("/Encrypt"); | |
| 1253 | - QPDFObjectHandle Pkey = encrypt.getKey("/P"); | |
| 1254 | - QPDFObjectHandle Rkey = encrypt.getKey("/R"); | |
| 1255 | - QPDFObjectHandle Vkey = encrypt.getKey("/V"); | |
| 1256 | - P = static_cast<int>(Pkey.getIntValue()); | |
| 1257 | - R = Rkey.getIntValueAsInt(); | |
| 1258 | - V = Vkey.getIntValueAsInt(); | |
| 1259 | - stream_method = m->encp->cf_stream; | |
| 1260 | - string_method = m->encp->cf_string; | |
| 1261 | - file_method = m->encp->cf_file; | |
| 1262 | - return true; | |
| 1263 | - } else { | |
| 1183 | + if (!m->encp->encrypted) { | |
| 1264 | 1184 | return false; |
| 1265 | 1185 | } |
| 1186 | + P = m->encp->P(); | |
| 1187 | + R = m->encp->R(); | |
| 1188 | + V = m->encp->encryption_V; | |
| 1189 | + stream_method = m->encp->cf_stream; | |
| 1190 | + string_method = m->encp->cf_string; | |
| 1191 | + file_method = m->encp->cf_file; | |
| 1192 | + return true; | |
| 1266 | 1193 | } |
| 1267 | 1194 | |
| 1268 | 1195 | bool |
| ... | ... | @@ -1277,135 +1204,57 @@ QPDF::userPasswordMatched() const |
| 1277 | 1204 | return m->encp->user_password_matched; |
| 1278 | 1205 | } |
| 1279 | 1206 | |
| 1280 | -static bool | |
| 1281 | -is_bit_set(int P, int bit) | |
| 1282 | -{ | |
| 1283 | - // Bits in P are numbered from 1 in the spec | |
| 1284 | - return ((P & (1 << (bit - 1))) != 0); | |
| 1285 | -} | |
| 1286 | - | |
| 1287 | 1207 | bool |
| 1288 | 1208 | QPDF::allowAccessibility() |
| 1289 | 1209 | { |
| 1290 | - int R = 0; | |
| 1291 | - int P = 0; | |
| 1292 | - bool status = true; | |
| 1293 | - if (isEncrypted(R, P)) { | |
| 1294 | - if (R < 3) { | |
| 1295 | - status = is_bit_set(P, 5); | |
| 1296 | - } else { | |
| 1297 | - status = is_bit_set(P, 10); | |
| 1298 | - } | |
| 1299 | - } | |
| 1300 | - return status; | |
| 1210 | + return m->encp->R() < 3 ? m->encp->P(5) : m->encp->P(10); | |
| 1301 | 1211 | } |
| 1302 | 1212 | |
| 1303 | 1213 | bool |
| 1304 | 1214 | QPDF::allowExtractAll() |
| 1305 | 1215 | { |
| 1306 | - int R = 0; | |
| 1307 | - int P = 0; | |
| 1308 | - bool status = true; | |
| 1309 | - if (isEncrypted(R, P)) { | |
| 1310 | - status = is_bit_set(P, 5); | |
| 1311 | - } | |
| 1312 | - return status; | |
| 1216 | + return m->encp->P(5); | |
| 1313 | 1217 | } |
| 1314 | 1218 | |
| 1315 | 1219 | bool |
| 1316 | 1220 | QPDF::allowPrintLowRes() |
| 1317 | 1221 | { |
| 1318 | - int R = 0; | |
| 1319 | - int P = 0; | |
| 1320 | - bool status = true; | |
| 1321 | - if (isEncrypted(R, P)) { | |
| 1322 | - status = is_bit_set(P, 3); | |
| 1323 | - } | |
| 1324 | - return status; | |
| 1222 | + return m->encp->P(3); | |
| 1325 | 1223 | } |
| 1326 | 1224 | |
| 1327 | 1225 | bool |
| 1328 | 1226 | QPDF::allowPrintHighRes() |
| 1329 | 1227 | { |
| 1330 | - int R = 0; | |
| 1331 | - int P = 0; | |
| 1332 | - bool status = true; | |
| 1333 | - if (isEncrypted(R, P)) { | |
| 1334 | - status = is_bit_set(P, 3); | |
| 1335 | - if ((R >= 3) && (!is_bit_set(P, 12))) { | |
| 1336 | - status = false; | |
| 1337 | - } | |
| 1338 | - } | |
| 1339 | - return status; | |
| 1228 | + return allowPrintLowRes() && (m->encp->R() < 3 ? true : m->encp->P(12)); | |
| 1340 | 1229 | } |
| 1341 | 1230 | |
| 1342 | 1231 | bool |
| 1343 | 1232 | QPDF::allowModifyAssembly() |
| 1344 | 1233 | { |
| 1345 | - int R = 0; | |
| 1346 | - int P = 0; | |
| 1347 | - bool status = true; | |
| 1348 | - if (isEncrypted(R, P)) { | |
| 1349 | - if (R < 3) { | |
| 1350 | - status = is_bit_set(P, 4); | |
| 1351 | - } else { | |
| 1352 | - status = is_bit_set(P, 11); | |
| 1353 | - } | |
| 1354 | - } | |
| 1355 | - return status; | |
| 1234 | + return m->encp->R() < 3 ? m->encp->P(4) : m->encp->P(11); | |
| 1356 | 1235 | } |
| 1357 | 1236 | |
| 1358 | 1237 | bool |
| 1359 | 1238 | QPDF::allowModifyForm() |
| 1360 | 1239 | { |
| 1361 | - int R = 0; | |
| 1362 | - int P = 0; | |
| 1363 | - bool status = true; | |
| 1364 | - if (isEncrypted(R, P)) { | |
| 1365 | - if (R < 3) { | |
| 1366 | - status = is_bit_set(P, 6); | |
| 1367 | - } else { | |
| 1368 | - status = is_bit_set(P, 9); | |
| 1369 | - } | |
| 1370 | - } | |
| 1371 | - return status; | |
| 1240 | + return m->encp->R() < 3 ? m->encp->P(6) : m->encp->P(9); | |
| 1372 | 1241 | } |
| 1373 | 1242 | |
| 1374 | 1243 | bool |
| 1375 | 1244 | QPDF::allowModifyAnnotation() |
| 1376 | 1245 | { |
| 1377 | - int R = 0; | |
| 1378 | - int P = 0; | |
| 1379 | - bool status = true; | |
| 1380 | - if (isEncrypted(R, P)) { | |
| 1381 | - status = is_bit_set(P, 6); | |
| 1382 | - } | |
| 1383 | - return status; | |
| 1246 | + return m->encp->P(6); | |
| 1384 | 1247 | } |
| 1385 | 1248 | |
| 1386 | 1249 | bool |
| 1387 | 1250 | QPDF::allowModifyOther() |
| 1388 | 1251 | { |
| 1389 | - int R = 0; | |
| 1390 | - int P = 0; | |
| 1391 | - bool status = true; | |
| 1392 | - if (isEncrypted(R, P)) { | |
| 1393 | - status = is_bit_set(P, 4); | |
| 1394 | - } | |
| 1395 | - return status; | |
| 1252 | + return m->encp->P(4); | |
| 1396 | 1253 | } |
| 1397 | 1254 | |
| 1398 | 1255 | bool |
| 1399 | 1256 | QPDF::allowModifyAll() |
| 1400 | 1257 | { |
| 1401 | - int R = 0; | |
| 1402 | - int P = 0; | |
| 1403 | - bool status = true; | |
| 1404 | - if (isEncrypted(R, P)) { | |
| 1405 | - status = (is_bit_set(P, 4) && is_bit_set(P, 6)); | |
| 1406 | - if (R >= 3) { | |
| 1407 | - status = status && (is_bit_set(P, 9) && is_bit_set(P, 11)); | |
| 1408 | - } | |
| 1409 | - } | |
| 1410 | - return status; | |
| 1258 | + return allowModifyAnnotation() && allowModifyOther() && | |
| 1259 | + (m->encp->R() < 3 ? true : allowModifyForm() && allowModifyAssembly()); | |
| 1411 | 1260 | } | ... | ... |
libqpdf/QUtil.cc
| ... | ... | @@ -2,6 +2,7 @@ |
| 2 | 2 | #include <qpdf/qpdf-config.h> |
| 3 | 3 | |
| 4 | 4 | #include <qpdf/QUtil.hh> |
| 5 | +#include <qpdf/Util.hh> | |
| 5 | 6 | |
| 6 | 7 | #include <qpdf/CryptoRandomDataProvider.hh> |
| 7 | 8 | #include <qpdf/Pipeline.hh> |
| ... | ... | @@ -1131,6 +1132,14 @@ QUtil::initializeWithRandomBytes(unsigned char* data, size_t len) |
| 1131 | 1132 | getRandomDataProvider()->provideRandomData(data, len); |
| 1132 | 1133 | } |
| 1133 | 1134 | |
| 1135 | +std::string | |
| 1136 | +util::random_string(size_t len) | |
| 1137 | +{ | |
| 1138 | + std::string result(len, '\0'); | |
| 1139 | + QUtil::initializeWithRandomBytes(reinterpret_cast<unsigned char*>(result.data()), len); | |
| 1140 | + return result; | |
| 1141 | +} | |
| 1142 | + | |
| 1134 | 1143 | long |
| 1135 | 1144 | QUtil::random() |
| 1136 | 1145 | { | ... | ... |
libqpdf/RC4.cc
| ... | ... | @@ -13,3 +13,13 @@ RC4::process(unsigned char const* in_data, size_t len, unsigned char* out_data) |
| 13 | 13 | { |
| 14 | 14 | crypto->RC4_process(in_data, len, out_data); |
| 15 | 15 | } |
| 16 | + | |
| 17 | +void | |
| 18 | +RC4::process(std::string_view key, std::string& data) | |
| 19 | +{ | |
| 20 | + RC4 rc4(reinterpret_cast<unsigned char const*>(key.data()), static_cast<int>(key.size())); | |
| 21 | + rc4.process( | |
| 22 | + reinterpret_cast<unsigned char const*>(data.data()), | |
| 23 | + data.size(), | |
| 24 | + reinterpret_cast<unsigned char*>(data.data())); | |
| 25 | +} | ... | ... |
libqpdf/qpdf/MD5.hh
| ... | ... | @@ -24,10 +24,25 @@ class MD5 |
| 24 | 24 | void appendString(char const* input_string); |
| 25 | 25 | |
| 26 | 26 | // appends arbitrary data to current md5 object |
| 27 | - void encodeDataIncrementally(char const* input_data, size_t len); | |
| 27 | + void | |
| 28 | + encodeDataIncrementally(char const* input_data, size_t len) | |
| 29 | + { | |
| 30 | + crypto->MD5_update(reinterpret_cast<unsigned char*>(const_cast<char*>(input_data)), len); | |
| 31 | + } | |
| 32 | + | |
| 33 | + // appends arbitrary data to current md5 object | |
| 34 | + void | |
| 35 | + encodeDataIncrementally(std::string_view input_data) | |
| 36 | + { | |
| 37 | + crypto->MD5_update( | |
| 38 | + reinterpret_cast<unsigned char*>(const_cast<char*>(input_data.data())), | |
| 39 | + input_data.size()); | |
| 40 | + } | |
| 28 | 41 | |
| 29 | 42 | // computes a raw digest |
| 30 | 43 | void digest(Digest); |
| 44 | + std::string digest(); | |
| 45 | + static std::string digest(std::string_view data); | |
| 31 | 46 | |
| 32 | 47 | // prints the digest to stdout terminated with \r\n (primarily for testing) |
| 33 | 48 | void print(); | ... | ... |
libqpdf/qpdf/QPDFWriter_private.hh
| ... | ... | @@ -43,92 +43,4 @@ class QPDFWriter::NewObjTable: public ::ObjTable<QPDFWriter::NewObject> |
| 43 | 43 | friend class QPDFWriter; |
| 44 | 44 | }; |
| 45 | 45 | |
| 46 | -class QPDFWriter::Members | |
| 47 | -{ | |
| 48 | - friend class QPDFWriter; | |
| 49 | - | |
| 50 | - public: | |
| 51 | - QPDF_DLL | |
| 52 | - ~Members(); | |
| 53 | - | |
| 54 | - private: | |
| 55 | - Members(QPDF& pdf); | |
| 56 | - Members(Members const&) = delete; | |
| 57 | - | |
| 58 | - QPDF& pdf; | |
| 59 | - QPDFObjGen root_og{-1, 0}; | |
| 60 | - char const* filename{"unspecified"}; | |
| 61 | - FILE* file{nullptr}; | |
| 62 | - bool close_file{false}; | |
| 63 | - Pl_Buffer* buffer_pipeline{nullptr}; | |
| 64 | - Buffer* output_buffer{nullptr}; | |
| 65 | - bool normalize_content_set{false}; | |
| 66 | - bool normalize_content{false}; | |
| 67 | - bool compress_streams{true}; | |
| 68 | - bool compress_streams_set{false}; | |
| 69 | - qpdf_stream_decode_level_e stream_decode_level{qpdf_dl_generalized}; | |
| 70 | - bool stream_decode_level_set{false}; | |
| 71 | - bool recompress_flate{false}; | |
| 72 | - bool qdf_mode{false}; | |
| 73 | - bool preserve_unreferenced_objects{false}; | |
| 74 | - bool newline_before_endstream{false}; | |
| 75 | - bool static_id{false}; | |
| 76 | - bool suppress_original_object_ids{false}; | |
| 77 | - bool direct_stream_lengths{true}; | |
| 78 | - bool encrypted{false}; | |
| 79 | - bool preserve_encryption{true}; | |
| 80 | - bool linearized{false}; | |
| 81 | - bool pclm{false}; | |
| 82 | - qpdf_object_stream_e object_stream_mode{qpdf_o_preserve}; | |
| 83 | - std::string encryption_key; | |
| 84 | - bool encrypt_metadata{true}; | |
| 85 | - bool encrypt_use_aes{false}; | |
| 86 | - std::map<std::string, std::string> encryption_dictionary; | |
| 87 | - int encryption_V{0}; | |
| 88 | - int encryption_R{0}; | |
| 89 | - | |
| 90 | - std::string id1; // for /ID key of | |
| 91 | - std::string id2; // trailer dictionary | |
| 92 | - std::string final_pdf_version; | |
| 93 | - int final_extension_level{0}; | |
| 94 | - std::string min_pdf_version; | |
| 95 | - int min_extension_level{0}; | |
| 96 | - std::string forced_pdf_version; | |
| 97 | - int forced_extension_level{0}; | |
| 98 | - std::string extra_header_text; | |
| 99 | - int encryption_dict_objid{0}; | |
| 100 | - std::string cur_data_key; | |
| 101 | - std::list<std::shared_ptr<Pipeline>> to_delete; | |
| 102 | - qpdf::pl::Count* pipeline{nullptr}; | |
| 103 | - std::vector<QPDFObjectHandle> object_queue; | |
| 104 | - size_t object_queue_front{0}; | |
| 105 | - QPDFWriter::ObjTable obj; | |
| 106 | - QPDFWriter::NewObjTable new_obj; | |
| 107 | - int next_objid{1}; | |
| 108 | - int cur_stream_length_id{0}; | |
| 109 | - size_t cur_stream_length{0}; | |
| 110 | - bool added_newline{false}; | |
| 111 | - size_t max_ostream_index{0}; | |
| 112 | - std::set<QPDFObjGen> normalized_streams; | |
| 113 | - std::map<QPDFObjGen, int> page_object_to_seq; | |
| 114 | - std::map<QPDFObjGen, int> contents_to_page_seq; | |
| 115 | - std::map<int, std::vector<QPDFObjGen>> object_stream_to_objects; | |
| 116 | - std::vector<Pipeline*> pipeline_stack; | |
| 117 | - unsigned long next_stack_id{2}; | |
| 118 | - std::string count_buffer; | |
| 119 | - bool deterministic_id{false}; | |
| 120 | - Pl_MD5* md5_pipeline{nullptr}; | |
| 121 | - std::string deterministic_id_data; | |
| 122 | - bool did_write_setup{false}; | |
| 123 | - | |
| 124 | - // For linearization only | |
| 125 | - std::string lin_pass1_filename; | |
| 126 | - | |
| 127 | - // For progress reporting | |
| 128 | - std::shared_ptr<QPDFWriter::ProgressReporter> progress_reporter; | |
| 129 | - int events_expected{0}; | |
| 130 | - int events_seen{0}; | |
| 131 | - int next_progress_report{0}; | |
| 132 | -}; | |
| 133 | - | |
| 134 | 46 | #endif // QPDFWRITER_PRIVATE_HH | ... | ... |
libqpdf/qpdf/QPDF_private.hh
| ... | ... | @@ -187,11 +187,30 @@ class QPDF::EncryptionParameters |
| 187 | 187 | public: |
| 188 | 188 | EncryptionParameters() = default; |
| 189 | 189 | |
| 190 | + int | |
| 191 | + P() const | |
| 192 | + { | |
| 193 | + return static_cast<int>(P_.to_ullong()); | |
| 194 | + } | |
| 195 | + | |
| 196 | + // Bits in P are numbered from 1 as in the PDF spec. | |
| 197 | + bool P(size_t bit) const; | |
| 198 | + | |
| 199 | + int | |
| 200 | + R() | |
| 201 | + { | |
| 202 | + return R_; | |
| 203 | + } | |
| 204 | + | |
| 205 | + void initialize(QPDF& qpdf); | |
| 206 | + encryption_method_e interpretCF(QPDFObjectHandle const& cf) const; | |
| 207 | + | |
| 190 | 208 | private: |
| 191 | 209 | bool encrypted{false}; |
| 192 | 210 | bool encryption_initialized{false}; |
| 211 | + std::bitset<32> P_{0xfffffffc}; // Specification always requires bits 1 and 2 to be cleared. | |
| 193 | 212 | int encryption_V{0}; |
| 194 | - int encryption_R{0}; | |
| 213 | + int R_{0}; | |
| 195 | 214 | bool encrypt_metadata{true}; |
| 196 | 215 | std::map<std::string, encryption_method_e> crypt_filters; |
| 197 | 216 | encryption_method_e cf_stream{e_none}; | ... | ... |
libqpdf/qpdf/RC4.hh
| ... | ... | @@ -14,6 +14,8 @@ class RC4 |
| 14 | 14 | // It is safe to pass the same pointer to in_data and out_data to encrypt/decrypt in place |
| 15 | 15 | void process(unsigned char const* in_data, size_t len, unsigned char* out_data); |
| 16 | 16 | |
| 17 | + static void process(std::string_view key, std::string& data); | |
| 18 | + | |
| 17 | 19 | private: |
| 18 | 20 | std::shared_ptr<QPDFCryptoImpl> crypto; |
| 19 | 21 | }; | ... | ... |
libqpdf/qpdf/Util.hh
qpdf/qpdf.testcov
| ... | ... | @@ -228,7 +228,6 @@ QPDFWriter extra header text no newline 0 |
| 228 | 228 | QPDFWriter extra header text add newline 0 |
| 229 | 229 | QPDF bogus 0 offset 0 |
| 230 | 230 | QPDF global offset 0 |
| 231 | -QPDFWriter copy V5 0 | |
| 232 | 231 | QPDFWriter increasing extension level 0 |
| 233 | 232 | QPDFWriter make Extensions direct 0 |
| 234 | 233 | QPDFWriter make ADBE direct 1 | ... | ... |