Commit e57c25814e49b82863753894ee7d97c18e4c4525
1 parent
93ac1695
Support for encryption with /V=5 and /R=5 and /R=6
Read and write support is implemented for /V=5 with /R=5 as well as /R=6. /R=5 is the deprecated encryption method used by Acrobat IX. /R=6 is the encryption method used by PDF 2.0 from ISO 32000-2.
Showing
21 changed files
with
930 additions
and
108 deletions
TODO
| 1 | 1 | General |
| 2 | 2 | ======= |
| 3 | 3 | |
| 4 | - * See if I can support the encryption format used with /R 5 /V 5 | |
| 5 | - (AESV3), even though a qpdf-announce subscriber with an adobe.com | |
| 6 | - email address mentioned that this is deprecated. There is also a | |
| 7 | - new encryption format coming in a future release (PDF 2.0), which | |
| 8 | - may be better to support. As of the qpdf 3.0 release, the | |
| 9 | - specification was not publicly available yet. | |
| 10 | - | |
| 11 | - AESV3 encryption is supported with PDF 1.7 extension level 3 and is | |
| 12 | - being deprecated, but there are plenty of files out there. The | |
| 13 | - encryption format is decribed in adobe_supplement_iso32000.pdf. | |
| 14 | - Such a file must specify that it uses these extensions in its | |
| 15 | - document catalog: | |
| 16 | - | |
| 17 | - << | |
| 18 | - /Type /Catalog | |
| 19 | - /Extensions << | |
| 20 | - /ADBE << | |
| 21 | - /BaseVersion /1.7 | |
| 22 | - /ExtensionLevel 3 | |
| 23 | - >> | |
| 24 | - >> | |
| 25 | - >> | |
| 26 | - | |
| 27 | - Possible sha256 implementations: http://sol-biotech.com/code/sha2/, | |
| 28 | - http://hashlib2plus.sourceforge.net/ | |
| 29 | - | |
| 30 | 4 | * Improve the random number seed to make it more secure so that we |
| 31 | 5 | have stronger random numbers, particularly when multiple files are |
| 32 | 6 | generated in the same second. This code may need to be | ... | ... |
include/qpdf/QPDF.hh
| ... | ... | @@ -224,7 +224,7 @@ class QPDF |
| 224 | 224 | |
| 225 | 225 | // Encryption support |
| 226 | 226 | |
| 227 | - enum encryption_method_e { e_none, e_unknown, e_rc4, e_aes }; | |
| 227 | + enum encryption_method_e { e_none, e_unknown, e_rc4, e_aes, e_aesv3 }; | |
| 228 | 228 | class EncryptionData |
| 229 | 229 | { |
| 230 | 230 | public: |
| ... | ... | @@ -326,7 +326,7 @@ class QPDF |
| 326 | 326 | QPDF_DLL |
| 327 | 327 | static std::string compute_data_key( |
| 328 | 328 | std::string const& encryption_key, int objid, int generation, |
| 329 | - bool use_aes); | |
| 329 | + bool use_aes, int encryption_V, int encryption_R); | |
| 330 | 330 | QPDF_DLL |
| 331 | 331 | static std::string compute_encryption_key( |
| 332 | 332 | std::string const& password, EncryptionData const& data); |
| ... | ... | @@ -337,6 +337,14 @@ class QPDF |
| 337 | 337 | int V, int R, int key_len, int P, bool encrypt_metadata, |
| 338 | 338 | std::string const& id1, |
| 339 | 339 | std::string& O, std::string& U); |
| 340 | + QPDF_DLL | |
| 341 | + static void compute_encryption_parameters_V5( | |
| 342 | + char const* user_password, char const* owner_password, | |
| 343 | + int V, int R, int key_len, int P, bool encrypt_metadata, | |
| 344 | + std::string const& id1, | |
| 345 | + std::string& encryption_key, | |
| 346 | + std::string& O, std::string& U, | |
| 347 | + std::string& OE, std::string& UE, std::string& Perms); | |
| 340 | 348 | // Return the full user password as stored in the PDF file. If |
| 341 | 349 | // you are attempting to recover the user password in a |
| 342 | 350 | // user-presentable form, call getTrimmedUserPassword() instead. |
| ... | ... | @@ -345,6 +353,10 @@ class QPDF |
| 345 | 353 | // Return human-readable form of user password. |
| 346 | 354 | QPDF_DLL |
| 347 | 355 | std::string getTrimmedUserPassword() const; |
| 356 | + // Return the previously computed or retrieved encryption key for | |
| 357 | + // this file | |
| 358 | + QPDF_DLL | |
| 359 | + std::string getEncryptionKey() const; | |
| 348 | 360 | |
| 349 | 361 | // Linearization support |
| 350 | 362 | |
| ... | ... | @@ -628,6 +640,13 @@ class QPDF |
| 628 | 640 | void initializeEncryption(); |
| 629 | 641 | std::string getKeyForObject(int objid, int generation, bool use_aes); |
| 630 | 642 | void decryptString(std::string&, int objid, int generation); |
| 643 | + static std::string compute_encryption_key_from_password( | |
| 644 | + std::string const& password, EncryptionData const& data); | |
| 645 | + static std::string recover_encryption_key_with_password( | |
| 646 | + std::string const& password, EncryptionData const& data); | |
| 647 | + static std::string recover_encryption_key_with_password( | |
| 648 | + std::string const& password, EncryptionData const& data, | |
| 649 | + bool& perms_valid); | |
| 631 | 650 | void decryptStream( |
| 632 | 651 | Pipeline*& pipeline, int objid, int generation, |
| 633 | 652 | QPDFObjectHandle& stream_dict, |
| ... | ... | @@ -981,6 +1000,7 @@ class QPDF |
| 981 | 1000 | std::ostream* err_stream; |
| 982 | 1001 | bool attempt_recovery; |
| 983 | 1002 | int encryption_V; |
| 1003 | + int encryption_R; | |
| 984 | 1004 | bool encrypt_metadata; |
| 985 | 1005 | std::map<std::string, encryption_method_e> crypt_filters; |
| 986 | 1006 | encryption_method_e cf_stream; | ... | ... |
include/qpdf/QPDFWriter.hh
| ... | ... | @@ -223,8 +223,9 @@ class QPDFWriter |
| 223 | 223 | // content normalization. Note that setting R2 encryption |
| 224 | 224 | // parameters sets the PDF version to at least 1.3, setting R3 |
| 225 | 225 | // encryption parameters pushes the PDF version number to at least |
| 226 | - // 1.4, and setting R4 parameters pushes the version to at least | |
| 227 | - // 1.5, or if AES is used, 1.6. | |
| 226 | + // 1.4, setting R4 parameters pushes the version to at least 1.5, | |
| 227 | + // or if AES is used, 1.6, and setting R5 or R6 parameters pushes | |
| 228 | + // the version to at least 1.7 with extension level 3. | |
| 228 | 229 | QPDF_DLL |
| 229 | 230 | void setR2EncryptionParameters( |
| 230 | 231 | char const* user_password, char const* owner_password, |
| ... | ... | @@ -241,6 +242,21 @@ class QPDFWriter |
| 241 | 242 | bool allow_accessibility, bool allow_extract, |
| 242 | 243 | qpdf_r3_print_e print, qpdf_r3_modify_e modify, |
| 243 | 244 | bool encrypt_metadata, bool use_aes); |
| 245 | + // R5 is deprecated. Do not use it for production use. Writing | |
| 246 | + // R5 is supported by qpdf primarily to generate test files for | |
| 247 | + // applications that may need to test R5 support. | |
| 248 | + QPDF_DLL | |
| 249 | + void setR5EncryptionParameters( | |
| 250 | + char const* user_password, char const* owner_password, | |
| 251 | + bool allow_accessibility, bool allow_extract, | |
| 252 | + qpdf_r3_print_e print, qpdf_r3_modify_e modify, | |
| 253 | + bool encrypt_metadata); | |
| 254 | + QPDF_DLL | |
| 255 | + void setR6EncryptionParameters( | |
| 256 | + char const* user_password, char const* owner_password, | |
| 257 | + bool allow_accessibility, bool allow_extract, | |
| 258 | + qpdf_r3_print_e print, qpdf_r3_modify_e modify, | |
| 259 | + bool encrypt_metadata_aes); | |
| 244 | 260 | |
| 245 | 261 | // Create linearized output. Disables qdf mode, content |
| 246 | 262 | // normalization, and stream prefiltering. |
| ... | ... | @@ -302,7 +318,8 @@ class QPDFWriter |
| 302 | 318 | int V, int R, int key_len, long P, |
| 303 | 319 | std::string const& O, std::string const& U, |
| 304 | 320 | std::string const& OE, std::string const& UE, std::string const& Perms, |
| 305 | - std::string const& id1, std::string const& user_password); | |
| 321 | + std::string const& id1, std::string const& user_password, | |
| 322 | + std::string const& encryption_key); | |
| 306 | 323 | void setDataKey(int objid); |
| 307 | 324 | int openObject(int objid = 0); |
| 308 | 325 | void closeObject(int objid); |
| ... | ... | @@ -378,6 +395,8 @@ class QPDFWriter |
| 378 | 395 | bool encrypt_metadata; |
| 379 | 396 | bool encrypt_use_aes; |
| 380 | 397 | std::map<std::string, std::string> encryption_dictionary; |
| 398 | + int encryption_V; | |
| 399 | + int encryption_R; | |
| 381 | 400 | |
| 382 | 401 | std::string id1; // for /ID key of |
| 383 | 402 | std::string id2; // trailer dictionary | ... | ... |
libqpdf/QPDF.cc
libqpdf/QPDFWriter.cc
| ... | ... | @@ -67,6 +67,8 @@ QPDFWriter::init() |
| 67 | 67 | min_extension_level = 0; |
| 68 | 68 | final_extension_level = 0; |
| 69 | 69 | forced_extension_level = 0; |
| 70 | + encryption_V = 0; | |
| 71 | + encryption_R = 0; | |
| 70 | 72 | encryption_dict_objid = 0; |
| 71 | 73 | next_objid = 1; |
| 72 | 74 | cur_stream_length_id = 0; |
| ... | ... | @@ -344,6 +346,38 @@ QPDFWriter::setR4EncryptionParameters( |
| 344 | 346 | } |
| 345 | 347 | |
| 346 | 348 | void |
| 349 | +QPDFWriter::setR5EncryptionParameters( | |
| 350 | + char const* user_password, char const* owner_password, | |
| 351 | + bool allow_accessibility, bool allow_extract, | |
| 352 | + qpdf_r3_print_e print, qpdf_r3_modify_e modify, | |
| 353 | + bool encrypt_metadata) | |
| 354 | +{ | |
| 355 | + std::set<int> clear; | |
| 356 | + interpretR3EncryptionParameters( | |
| 357 | + clear, user_password, owner_password, | |
| 358 | + allow_accessibility, allow_extract, print, modify); | |
| 359 | + this->encrypt_use_aes = true; | |
| 360 | + this->encrypt_metadata = encrypt_metadata; | |
| 361 | + setEncryptionParameters(user_password, owner_password, 5, 5, 32, clear); | |
| 362 | +} | |
| 363 | + | |
| 364 | +void | |
| 365 | +QPDFWriter::setR6EncryptionParameters( | |
| 366 | + char const* user_password, char const* owner_password, | |
| 367 | + bool allow_accessibility, bool allow_extract, | |
| 368 | + qpdf_r3_print_e print, qpdf_r3_modify_e modify, | |
| 369 | + bool encrypt_metadata) | |
| 370 | +{ | |
| 371 | + std::set<int> clear; | |
| 372 | + interpretR3EncryptionParameters( | |
| 373 | + clear, user_password, owner_password, | |
| 374 | + allow_accessibility, allow_extract, print, modify); | |
| 375 | + this->encrypt_use_aes = true; | |
| 376 | + this->encrypt_metadata = encrypt_metadata; | |
| 377 | + setEncryptionParameters(user_password, owner_password, 5, 6, 32, clear); | |
| 378 | +} | |
| 379 | + | |
| 380 | +void | |
| 347 | 381 | QPDFWriter::interpretR3EncryptionParameters( |
| 348 | 382 | std::set<int>& clear, |
| 349 | 383 | char const* user_password, char const* owner_password, |
| ... | ... | @@ -426,6 +460,12 @@ QPDFWriter::setEncryptionParameters( |
| 426 | 460 | bits_to_clear.insert(1); |
| 427 | 461 | bits_to_clear.insert(2); |
| 428 | 462 | |
| 463 | + if (R > 3) | |
| 464 | + { | |
| 465 | + // Bit 10 is deprecated and should always be set. | |
| 466 | + bits_to_clear.erase(10); | |
| 467 | + } | |
| 468 | + | |
| 429 | 469 | int P = 0; |
| 430 | 470 | // Create the complement of P, then invert. |
| 431 | 471 | for (std::set<int>::iterator iter = bits_to_clear.begin(); |
| ... | ... | @@ -438,11 +478,26 @@ QPDFWriter::setEncryptionParameters( |
| 438 | 478 | generateID(); |
| 439 | 479 | std::string O; |
| 440 | 480 | std::string U; |
| 441 | - QPDF::compute_encryption_O_U( | |
| 442 | - user_password, owner_password, V, R, key_len, P, | |
| 443 | - this->encrypt_metadata, this->id1, O, U); | |
| 481 | + std::string OE; | |
| 482 | + std::string UE; | |
| 483 | + std::string Perms; | |
| 484 | + std::string encryption_key; | |
| 485 | + if (V < 5) | |
| 486 | + { | |
| 487 | + QPDF::compute_encryption_O_U( | |
| 488 | + user_password, owner_password, V, R, key_len, P, | |
| 489 | + this->encrypt_metadata, this->id1, O, U); | |
| 490 | + } | |
| 491 | + else | |
| 492 | + { | |
| 493 | + QPDF::compute_encryption_parameters_V5( | |
| 494 | + user_password, owner_password, V, R, key_len, P, | |
| 495 | + this->encrypt_metadata, this->id1, | |
| 496 | + encryption_key, O, U, OE, UE, Perms); | |
| 497 | + } | |
| 444 | 498 | setEncryptionParametersInternal( |
| 445 | - V, R, key_len, P, O, U, "", "", "", this->id1, user_password); | |
| 499 | + V, R, key_len, P, O, U, OE, UE, Perms, | |
| 500 | + this->id1, user_password, encryption_key); | |
| 446 | 501 | } |
| 447 | 502 | |
| 448 | 503 | void |
| ... | ... | @@ -482,6 +537,19 @@ QPDFWriter::copyEncryptionParameters(QPDF& qpdf) |
| 482 | 537 | this->encrypt_metadata ? 0 : 1); |
| 483 | 538 | QTC::TC("qpdf", "QPDFWriter copy use_aes", |
| 484 | 539 | this->encrypt_use_aes ? 0 : 1); |
| 540 | + std::string OE; | |
| 541 | + std::string UE; | |
| 542 | + std::string Perms; | |
| 543 | + std::string encryption_key; | |
| 544 | + if (V >= 5) | |
| 545 | + { | |
| 546 | + QTC::TC("qpdf", "QPDFWriter copy V5"); | |
| 547 | + OE = encrypt.getKey("/OE").getStringValue(); | |
| 548 | + UE = encrypt.getKey("/UE").getStringValue(); | |
| 549 | + Perms = encrypt.getKey("/Perms").getStringValue(); | |
| 550 | + encryption_key = qpdf.getEncryptionKey(); | |
| 551 | + } | |
| 552 | + | |
| 485 | 553 | setEncryptionParametersInternal( |
| 486 | 554 | V, |
| 487 | 555 | encrypt.getKey("/R").getIntValue(), |
| ... | ... | @@ -489,11 +557,12 @@ QPDFWriter::copyEncryptionParameters(QPDF& qpdf) |
| 489 | 557 | encrypt.getKey("/P").getIntValue(), |
| 490 | 558 | encrypt.getKey("/O").getStringValue(), |
| 491 | 559 | encrypt.getKey("/U").getStringValue(), |
| 492 | - "", // XXXX OE | |
| 493 | - "", // XXXX UE | |
| 494 | - "", // XXXX Perms | |
| 560 | + OE, | |
| 561 | + UE, | |
| 562 | + Perms, | |
| 495 | 563 | this->id1, // this->id1 == the other file's id1 |
| 496 | - qpdf.getPaddedUserPassword()); | |
| 564 | + qpdf.getPaddedUserPassword(), | |
| 565 | + encryption_key); | |
| 497 | 566 | } |
| 498 | 567 | } |
| 499 | 568 | |
| ... | ... | @@ -605,10 +674,11 @@ QPDFWriter::setEncryptionParametersInternal( |
| 605 | 674 | int V, int R, int key_len, long P, |
| 606 | 675 | std::string const& O, std::string const& U, |
| 607 | 676 | std::string const& OE, std::string const& UE, std::string const& Perms, |
| 608 | - std::string const& id1, std::string const& user_password) | |
| 677 | + std::string const& id1, std::string const& user_password, | |
| 678 | + std::string const& encryption_key) | |
| 609 | 679 | { |
| 610 | - // XXXX OE, UE, Perms, V=5 | |
| 611 | - | |
| 680 | + this->encryption_V = V; | |
| 681 | + this->encryption_R = R; | |
| 612 | 682 | encryption_dictionary["/Filter"] = "/Standard"; |
| 613 | 683 | encryption_dictionary["/V"] = QUtil::int_to_string(V); |
| 614 | 684 | encryption_dictionary["/Length"] = QUtil::int_to_string(key_len * 8); |
| ... | ... | @@ -618,9 +688,15 @@ QPDFWriter::setEncryptionParametersInternal( |
| 618 | 688 | encryption_dictionary["/U"] = QPDF_String(U).unparse(true); |
| 619 | 689 | if (V >= 5) |
| 620 | 690 | { |
| 621 | - setMinimumPDFVersion("1.4"); | |
| 691 | + encryption_dictionary["/OE"] = QPDF_String(OE).unparse(true); | |
| 692 | + encryption_dictionary["/UE"] = QPDF_String(UE).unparse(true); | |
| 693 | + encryption_dictionary["/Perms"] = QPDF_String(Perms).unparse(true); | |
| 694 | + } | |
| 695 | + if (R >= 6) | |
| 696 | + { | |
| 697 | + setMinimumPDFVersion("1.7", 8); | |
| 622 | 698 | } |
| 623 | - if (R >= 5) | |
| 699 | + else if (R == 5) | |
| 624 | 700 | { |
| 625 | 701 | setMinimumPDFVersion("1.7", 3); |
| 626 | 702 | } |
| ... | ... | @@ -641,14 +717,16 @@ QPDFWriter::setEncryptionParametersInternal( |
| 641 | 717 | { |
| 642 | 718 | encryption_dictionary["/EncryptMetadata"] = "false"; |
| 643 | 719 | } |
| 644 | - if (V == 4) | |
| 720 | + if ((V == 4) || (V == 5)) | |
| 645 | 721 | { |
| 646 | 722 | // The spec says the value for the crypt filter key can be |
| 647 | 723 | // anything, and xpdf seems to agree. However, Adobe Reader |
| 648 | 724 | // won't open our files unless we use /StdCF. |
| 649 | 725 | encryption_dictionary["/StmF"] = "/StdCF"; |
| 650 | 726 | encryption_dictionary["/StrF"] = "/StdCF"; |
| 651 | - std::string method = (this->encrypt_use_aes ? "/AESV2" : "/V2"); | |
| 727 | + std::string method = (this->encrypt_use_aes | |
| 728 | + ? ((V < 5) ? "/AESV2" : "/AESV3") | |
| 729 | + : "/V2"); | |
| 652 | 730 | encryption_dictionary["/CF"] = |
| 653 | 731 | "<< /StdCF << /AuthEvent /DocOpen /CFM " + method + " >> >>"; |
| 654 | 732 | } |
| ... | ... | @@ -656,15 +734,23 @@ QPDFWriter::setEncryptionParametersInternal( |
| 656 | 734 | this->encrypted = true; |
| 657 | 735 | QPDF::EncryptionData encryption_data( |
| 658 | 736 | V, R, key_len, P, O, U, OE, UE, Perms, id1, this->encrypt_metadata); |
| 659 | - this->encryption_key = QPDF::compute_encryption_key( | |
| 660 | - user_password, encryption_data); | |
| 737 | + if (V < 5) | |
| 738 | + { | |
| 739 | + this->encryption_key = QPDF::compute_encryption_key( | |
| 740 | + user_password, encryption_data); | |
| 741 | + } | |
| 742 | + else | |
| 743 | + { | |
| 744 | + this->encryption_key = encryption_key; | |
| 745 | + } | |
| 661 | 746 | } |
| 662 | 747 | |
| 663 | 748 | void |
| 664 | 749 | QPDFWriter::setDataKey(int objid) |
| 665 | 750 | { |
| 666 | 751 | this->cur_data_key = QPDF::compute_data_key( |
| 667 | - this->encryption_key, objid, 0, this->encrypt_use_aes); | |
| 752 | + this->encryption_key, objid, 0, | |
| 753 | + this->encrypt_use_aes, this->encryption_V, this->encryption_R); | |
| 668 | 754 | } |
| 669 | 755 | |
| 670 | 756 | int | ... | ... |
libqpdf/QPDF_encryption.cc
| ... | ... | @@ -10,6 +10,7 @@ |
| 10 | 10 | #include <qpdf/Pl_RC4.hh> |
| 11 | 11 | #include <qpdf/Pl_AES_PDF.hh> |
| 12 | 12 | #include <qpdf/Pl_Buffer.hh> |
| 13 | +#include <qpdf/Pl_SHA2.hh> | |
| 13 | 14 | #include <qpdf/RC4.hh> |
| 14 | 15 | #include <qpdf/MD5.hh> |
| 15 | 16 | |
| ... | ... | @@ -23,9 +24,15 @@ static unsigned char const padding_string[] = { |
| 23 | 24 | 0x2f, 0x0c, 0xa9, 0xfe, 0x64, 0x53, 0x69, 0x7a |
| 24 | 25 | }; |
| 25 | 26 | |
| 26 | -static unsigned int const O_key_bytes = sizeof(MD5::Digest); | |
| 27 | 27 | static unsigned int const key_bytes = 32; |
| 28 | 28 | |
| 29 | +// V4 key lengths apply to V <= 4 | |
| 30 | +static unsigned int const OU_key_bytes_V4 = sizeof(MD5::Digest); | |
| 31 | + | |
| 32 | +static unsigned int const OU_key_bytes_V5 = 48; | |
| 33 | +static unsigned int const OUE_key_bytes_V5 = 32; | |
| 34 | +static unsigned int const Perms_key_bytes_V5 = 16; | |
| 35 | + | |
| 29 | 36 | int |
| 30 | 37 | QPDF::EncryptionData::getV() const |
| 31 | 38 | { |
| ... | ... | @@ -120,7 +127,7 @@ QPDF::EncryptionData::setV5EncryptionParameters( |
| 120 | 127 | } |
| 121 | 128 | |
| 122 | 129 | static void |
| 123 | -pad_or_truncate_password(std::string const& password, char k1[key_bytes]) | |
| 130 | +pad_or_truncate_password_V4(std::string const& password, char k1[key_bytes]) | |
| 124 | 131 | { |
| 125 | 132 | int password_bytes = std::min(key_bytes, (unsigned int)password.length()); |
| 126 | 133 | int pad_bytes = key_bytes - password_bytes; |
| ... | ... | @@ -159,13 +166,19 @@ QPDF::trim_user_password(std::string& user_password) |
| 159 | 166 | } |
| 160 | 167 | |
| 161 | 168 | static std::string |
| 162 | -pad_or_truncate_password(std::string const& password) | |
| 169 | +pad_or_truncate_password_V4(std::string const& password) | |
| 163 | 170 | { |
| 164 | 171 | char k1[key_bytes]; |
| 165 | - pad_or_truncate_password(password, k1); | |
| 172 | + pad_or_truncate_password_V4(password, k1); | |
| 166 | 173 | return std::string(k1, key_bytes); |
| 167 | 174 | } |
| 168 | 175 | |
| 176 | +static std::string | |
| 177 | +truncate_password_V5(std::string const& password) | |
| 178 | +{ | |
| 179 | + return password.substr(0, std::min((size_t)127, password.length())); | |
| 180 | +} | |
| 181 | + | |
| 169 | 182 | static void |
| 170 | 183 | iterate_md5_digest(MD5& md5, MD5::Digest& digest, int iterations) |
| 171 | 184 | { |
| ... | ... | @@ -199,15 +212,146 @@ iterate_rc4(unsigned char* data, int data_len, |
| 199 | 212 | delete [] key; |
| 200 | 213 | } |
| 201 | 214 | |
| 215 | +static std::string | |
| 216 | +process_with_aes(std::string const& key, | |
| 217 | + bool encrypt, | |
| 218 | + std::string const& data, | |
| 219 | + size_t outlength = 0, | |
| 220 | + unsigned int repetitions = 1, | |
| 221 | + unsigned char const* iv = 0, | |
| 222 | + size_t iv_length = 0) | |
| 223 | +{ | |
| 224 | + Pl_Buffer buffer("buffer"); | |
| 225 | + Pl_AES_PDF aes("aes", &buffer, encrypt, | |
| 226 | + (unsigned char const*)key.c_str(), | |
| 227 | + (unsigned int)key.length()); | |
| 228 | + if (iv) | |
| 229 | + { | |
| 230 | + aes.setIV(iv, iv_length); | |
| 231 | + } | |
| 232 | + else | |
| 233 | + { | |
| 234 | + aes.useZeroIV(); | |
| 235 | + } | |
| 236 | + aes.disablePadding(); | |
| 237 | + for (unsigned int i = 0; i < repetitions; ++i) | |
| 238 | + { | |
| 239 | + aes.write((unsigned char*)data.c_str(), data.length()); | |
| 240 | + } | |
| 241 | + aes.finish(); | |
| 242 | + PointerHolder<Buffer> bufp = buffer.getBuffer(); | |
| 243 | + if (outlength == 0) | |
| 244 | + { | |
| 245 | + outlength = bufp->getSize(); | |
| 246 | + } | |
| 247 | + else | |
| 248 | + { | |
| 249 | + outlength = std::min(outlength, bufp->getSize()); | |
| 250 | + } | |
| 251 | + return std::string((char const*)bufp->getBuffer(), outlength); | |
| 252 | +} | |
| 253 | + | |
| 254 | +static std::string | |
| 255 | +hash_V5(std::string const& password, | |
| 256 | + std::string const& salt, | |
| 257 | + std::string const& udata, | |
| 258 | + QPDF::EncryptionData const& data) | |
| 259 | +{ | |
| 260 | + Pl_SHA2 hash(256); | |
| 261 | + hash.write((unsigned char*)password.c_str(), password.length()); | |
| 262 | + hash.write((unsigned char*)salt.c_str(), salt.length()); | |
| 263 | + hash.write((unsigned char*)udata.c_str(), udata.length()); | |
| 264 | + hash.finish(); | |
| 265 | + std::string K = hash.getRawDigest(); | |
| 266 | + | |
| 267 | + std::string result; | |
| 268 | + if (data.getR() < 6) | |
| 269 | + { | |
| 270 | + result = K; | |
| 271 | + } | |
| 272 | + else | |
| 273 | + { | |
| 274 | + // Algorithm 2.B from ISO 32000-1 chapter 7: Computing a hash | |
| 275 | + | |
| 276 | + int round_number = 0; | |
| 277 | + bool done = false; | |
| 278 | + while (! done) | |
| 279 | + { | |
| 280 | + // The hash algorithm has us setting K initially to the R5 | |
| 281 | + // value and then repeating a series of steps 64 times | |
| 282 | + // before starting with the termination case testing. The | |
| 283 | + // wording of the specification is very unclear as to the | |
| 284 | + // exact number of times it should be run since the | |
| 285 | + // wording about whether the initial setup counts as round | |
| 286 | + // 0 or not is ambiguous. This code counts the initial | |
| 287 | + // setup (R5) value as round 0, which appears to be | |
| 288 | + // correct. This was determined to be correct by | |
| 289 | + // increasing or decreasing the number of rounds by 1 or 2 | |
| 290 | + // from this value and generating 20 test files. In this | |
| 291 | + // interpretation, all the test files worked with Adobe | |
| 292 | + // Reader X. In the other configurations, many of the | |
| 293 | + // files did not work, and we were accurately able to | |
| 294 | + // predict which files didn't work by looking at the | |
| 295 | + // conditions under which we terminated repetition. | |
| 296 | + | |
| 297 | + ++round_number; | |
| 298 | + std::string K1 = password + K + udata; | |
| 299 | + assert(K.length() >= 32); | |
| 300 | + std::string E = process_with_aes( | |
| 301 | + K.substr(0, 16), true, K1, 0, 64, | |
| 302 | + (unsigned char*)K.substr(16, 16).c_str(), 16); | |
| 303 | + | |
| 304 | + // E_mod_3 is supposed to be mod 3 of the first 16 bytes | |
| 305 | + // of E taken as as a (128-bit) big-endian number. Since | |
| 306 | + // (xy mod n) is equal to ((x mod n) + (y mod n)) mod n | |
| 307 | + // and since 256 mod n is 1, we can just take the sums of | |
| 308 | + // the the mod 3s of each byte to get the same result. | |
| 309 | + int E_mod_3 = 0; | |
| 310 | + for (unsigned int i = 0; i < 16; ++i) | |
| 311 | + { | |
| 312 | + E_mod_3 += (unsigned char)E[i]; | |
| 313 | + } | |
| 314 | + E_mod_3 %= 3; | |
| 315 | + int next_hash = ((E_mod_3 == 0) ? 256 : | |
| 316 | + (E_mod_3 == 1) ? 384 : | |
| 317 | + 512); | |
| 318 | + Pl_SHA2 hash(next_hash); | |
| 319 | + hash.write((unsigned char*)E.c_str(), E.length()); | |
| 320 | + hash.finish(); | |
| 321 | + K = hash.getRawDigest(); | |
| 322 | + | |
| 323 | + if (round_number >= 64) | |
| 324 | + { | |
| 325 | + unsigned int ch = (unsigned int)((unsigned char) *(E.rbegin())); | |
| 326 | + | |
| 327 | + if (ch <= (unsigned int)(round_number - 32)) | |
| 328 | + { | |
| 329 | + done = true; | |
| 330 | + } | |
| 331 | + } | |
| 332 | + } | |
| 333 | + result = K.substr(0, 32); | |
| 334 | + } | |
| 335 | + | |
| 336 | + return result; | |
| 337 | +} | |
| 338 | + | |
| 202 | 339 | std::string |
| 203 | 340 | QPDF::compute_data_key(std::string const& encryption_key, |
| 204 | - int objid, int generation, | |
| 205 | - bool use_aes) | |
| 341 | + int objid, int generation, bool use_aes, | |
| 342 | + int encryption_V, int encryption_R) | |
| 206 | 343 | { |
| 207 | 344 | // Algorithm 3.1 from the PDF 1.7 Reference Manual |
| 208 | 345 | |
| 209 | 346 | std::string result = encryption_key; |
| 210 | 347 | |
| 348 | + if (encryption_V >= 5) | |
| 349 | + { | |
| 350 | + // Algorithm 3.1a (PDF 1.7 extension level 3): just use | |
| 351 | + // encryption key straight. | |
| 352 | + return result; | |
| 353 | + } | |
| 354 | + | |
| 211 | 355 | // Append low three bytes of object ID and low two bytes of generation |
| 212 | 356 | result += (char) (objid & 0xff); |
| 213 | 357 | result += (char) ((objid >> 8) & 0xff); |
| ... | ... | @@ -231,11 +375,37 @@ std::string |
| 231 | 375 | QPDF::compute_encryption_key( |
| 232 | 376 | std::string const& password, EncryptionData const& data) |
| 233 | 377 | { |
| 378 | + if (data.getV() >= 5) | |
| 379 | + { | |
| 380 | + // For V >= 5, the encryption key is generated and stored in | |
| 381 | + // the file, encrypted separately with both user and owner | |
| 382 | + // passwords. | |
| 383 | + return recover_encryption_key_with_password(password, data); | |
| 384 | + } | |
| 385 | + else | |
| 386 | + { | |
| 387 | + // For V < 5, the encryption key is derived from the user | |
| 388 | + // password. | |
| 389 | + return compute_encryption_key_from_password(password, data); | |
| 390 | + } | |
| 391 | +} | |
| 392 | + | |
| 393 | +std::string | |
| 394 | +QPDF::compute_encryption_key_from_password( | |
| 395 | + std::string const& password, EncryptionData const& data) | |
| 396 | +{ | |
| 234 | 397 | // Algorithm 3.2 from the PDF 1.7 Reference Manual |
| 235 | 398 | |
| 399 | + // This code does not properly handle Unicode passwords. | |
| 400 | + // Passwords are supposed to be converted from OS codepage | |
| 401 | + // characters to PDFDocEncoding. Unicode passwords are supposed | |
| 402 | + // to be converted to OS codepage before converting to | |
| 403 | + // PDFDocEncoding. We instead require the password to be | |
| 404 | + // presented in its final form. | |
| 405 | + | |
| 236 | 406 | MD5 md5; |
| 237 | 407 | md5.encodeDataIncrementally( |
| 238 | - pad_or_truncate_password(password).c_str(), key_bytes); | |
| 408 | + pad_or_truncate_password_V4(password).c_str(), key_bytes); | |
| 239 | 409 | md5.encodeDataIncrementally(data.getO().c_str(), key_bytes); |
| 240 | 410 | char pbytes[4]; |
| 241 | 411 | int P = data.getP(); |
| ... | ... | @@ -261,8 +431,13 @@ static void |
| 261 | 431 | compute_O_rc4_key(std::string const& user_password, |
| 262 | 432 | std::string const& owner_password, |
| 263 | 433 | QPDF::EncryptionData const& data, |
| 264 | - unsigned char key[O_key_bytes]) | |
| 434 | + unsigned char key[OU_key_bytes_V4]) | |
| 265 | 435 | { |
| 436 | + if (data.getV() >= 5) | |
| 437 | + { | |
| 438 | + throw std::logic_error( | |
| 439 | + "compute_O_rc4_key called for file with V >= 5"); | |
| 440 | + } | |
| 266 | 441 | std::string password = owner_password; |
| 267 | 442 | if (password.empty()) |
| 268 | 443 | { |
| ... | ... | @@ -270,10 +445,10 @@ compute_O_rc4_key(std::string const& user_password, |
| 270 | 445 | } |
| 271 | 446 | MD5 md5; |
| 272 | 447 | md5.encodeDataIncrementally( |
| 273 | - pad_or_truncate_password(password).c_str(), key_bytes); | |
| 448 | + pad_or_truncate_password_V4(password).c_str(), key_bytes); | |
| 274 | 449 | MD5::Digest digest; |
| 275 | 450 | iterate_md5_digest(md5, digest, ((data.getR() >= 3) ? 50 : 0)); |
| 276 | - memcpy(key, digest, O_key_bytes); | |
| 451 | + memcpy(key, digest, OU_key_bytes_V4); | |
| 277 | 452 | } |
| 278 | 453 | |
| 279 | 454 | static std::string |
| ... | ... | @@ -283,11 +458,11 @@ compute_O_value(std::string const& user_password, |
| 283 | 458 | { |
| 284 | 459 | // Algorithm 3.3 from the PDF 1.7 Reference Manual |
| 285 | 460 | |
| 286 | - unsigned char O_key[O_key_bytes]; | |
| 461 | + unsigned char O_key[OU_key_bytes_V4]; | |
| 287 | 462 | compute_O_rc4_key(user_password, owner_password, data, O_key); |
| 288 | 463 | |
| 289 | 464 | char upass[key_bytes]; |
| 290 | - pad_or_truncate_password(user_password, upass); | |
| 465 | + pad_or_truncate_password_V4(user_password, upass); | |
| 291 | 466 | iterate_rc4((unsigned char*) upass, key_bytes, |
| 292 | 467 | O_key, data.getLengthBytes(), |
| 293 | 468 | (data.getR() >= 3) ? 20 : 1, false); |
| ... | ... | @@ -303,7 +478,7 @@ compute_U_value_R2(std::string const& user_password, |
| 303 | 478 | |
| 304 | 479 | std::string k1 = QPDF::compute_encryption_key(user_password, data); |
| 305 | 480 | char udata[key_bytes]; |
| 306 | - pad_or_truncate_password("", udata); | |
| 481 | + pad_or_truncate_password_V4("", udata); | |
| 307 | 482 | iterate_rc4((unsigned char*) udata, key_bytes, |
| 308 | 483 | (unsigned char*)k1.c_str(), data.getLengthBytes(), 1, false); |
| 309 | 484 | return std::string(udata, key_bytes); |
| ... | ... | @@ -319,7 +494,7 @@ compute_U_value_R3(std::string const& user_password, |
| 319 | 494 | std::string k1 = QPDF::compute_encryption_key(user_password, data); |
| 320 | 495 | MD5 md5; |
| 321 | 496 | md5.encodeDataIncrementally( |
| 322 | - pad_or_truncate_password("").c_str(), key_bytes); | |
| 497 | + pad_or_truncate_password_V4("").c_str(), key_bytes); | |
| 323 | 498 | md5.encodeDataIncrementally(data.getId1().c_str(), |
| 324 | 499 | (int)data.getId1().length()); |
| 325 | 500 | MD5::Digest digest; |
| ... | ... | @@ -350,40 +525,214 @@ compute_U_value(std::string const& user_password, |
| 350 | 525 | } |
| 351 | 526 | |
| 352 | 527 | static bool |
| 353 | -check_user_password(std::string const& user_password, | |
| 354 | - QPDF::EncryptionData const& data) | |
| 528 | +check_user_password_V4(std::string const& user_password, | |
| 529 | + QPDF::EncryptionData const& data) | |
| 355 | 530 | { |
| 356 | 531 | // Algorithm 3.6 from the PDF 1.7 Reference Manual |
| 357 | 532 | |
| 358 | 533 | std::string u_value = compute_U_value(user_password, data); |
| 359 | - int to_compare = ((data.getR() >= 3) ? sizeof(MD5::Digest) : key_bytes); | |
| 534 | + int to_compare = ((data.getR() >= 3) ? sizeof(MD5::Digest) | |
| 535 | + : key_bytes); | |
| 360 | 536 | return (memcmp(data.getU().c_str(), u_value.c_str(), to_compare) == 0); |
| 361 | 537 | } |
| 362 | 538 | |
| 363 | 539 | static bool |
| 364 | -check_owner_password(std::string& user_password, | |
| 365 | - std::string const& owner_password, | |
| 366 | - QPDF::EncryptionData const& data) | |
| 540 | +check_user_password_V5(std::string const& user_password, | |
| 541 | + QPDF::EncryptionData const& data) | |
| 542 | +{ | |
| 543 | + // Algorithm 3.11 from the PDF 1.7 extension level 3 | |
| 544 | + | |
| 545 | + std::string user_data = data.getU().substr(0, 32); | |
| 546 | + std::string validation_salt = data.getU().substr(32, 8); | |
| 547 | + std::string password = truncate_password_V5(user_password); | |
| 548 | + return (hash_V5(password, validation_salt, "", data) == user_data); | |
| 549 | +} | |
| 550 | + | |
| 551 | +static bool | |
| 552 | +check_user_password(std::string const& user_password, | |
| 553 | + QPDF::EncryptionData const& data) | |
| 554 | +{ | |
| 555 | + if (data.getV() < 5) | |
| 556 | + { | |
| 557 | + return check_user_password_V4(user_password, data); | |
| 558 | + } | |
| 559 | + else | |
| 560 | + { | |
| 561 | + return check_user_password_V5(user_password, data); | |
| 562 | + } | |
| 563 | +} | |
| 564 | + | |
| 565 | +static bool | |
| 566 | +check_owner_password_V4(std::string& user_password, | |
| 567 | + std::string const& owner_password, | |
| 568 | + QPDF::EncryptionData const& data) | |
| 367 | 569 | { |
| 368 | 570 | // Algorithm 3.7 from the PDF 1.7 Reference Manual |
| 369 | 571 | |
| 370 | - unsigned char key[O_key_bytes]; | |
| 572 | + unsigned char key[OU_key_bytes_V4]; | |
| 371 | 573 | compute_O_rc4_key(user_password, owner_password, data, key); |
| 372 | 574 | unsigned char O_data[key_bytes]; |
| 373 | 575 | memcpy(O_data, (unsigned char*) data.getO().c_str(), key_bytes); |
| 374 | 576 | iterate_rc4(O_data, key_bytes, key, data.getLengthBytes(), |
| 375 | - (data.getR() >= 3) ? 20 : 1, true); | |
| 577 | + (data.getR() >= 3) ? 20 : 1, true); | |
| 376 | 578 | std::string new_user_password = |
| 377 | - std::string((char*)O_data, key_bytes); | |
| 579 | + std::string((char*)O_data, key_bytes); | |
| 378 | 580 | bool result = false; |
| 379 | 581 | if (check_user_password(new_user_password, data)) |
| 380 | 582 | { |
| 381 | - result = true; | |
| 382 | - user_password = new_user_password; | |
| 583 | + result = true; | |
| 584 | + user_password = new_user_password; | |
| 383 | 585 | } |
| 384 | 586 | return result; |
| 385 | 587 | } |
| 386 | 588 | |
| 589 | +static bool | |
| 590 | +check_owner_password_V5(std::string const& owner_password, | |
| 591 | + QPDF::EncryptionData const& data) | |
| 592 | +{ | |
| 593 | + // Algorithm 3.12 from the PDF 1.7 extension level 3 | |
| 594 | + | |
| 595 | + std::string user_data = data.getU().substr(0, 48); | |
| 596 | + std::string owner_data = data.getO().substr(0, 32); | |
| 597 | + std::string validation_salt = data.getO().substr(32, 8); | |
| 598 | + std::string password = truncate_password_V5(owner_password); | |
| 599 | + return (hash_V5(password, validation_salt, user_data, | |
| 600 | + data) == owner_data); | |
| 601 | +} | |
| 602 | + | |
| 603 | +static bool | |
| 604 | +check_owner_password(std::string& user_password, | |
| 605 | + std::string const& owner_password, | |
| 606 | + QPDF::EncryptionData const& data) | |
| 607 | +{ | |
| 608 | + if (data.getV() < 5) | |
| 609 | + { | |
| 610 | + return check_owner_password_V4(user_password, owner_password, data); | |
| 611 | + } | |
| 612 | + else | |
| 613 | + { | |
| 614 | + return check_owner_password_V5(owner_password, data); | |
| 615 | + } | |
| 616 | +} | |
| 617 | + | |
| 618 | +std::string | |
| 619 | +QPDF::recover_encryption_key_with_password( | |
| 620 | + std::string const& password, EncryptionData const& data) | |
| 621 | +{ | |
| 622 | + // Disregard whether Perms is valid. | |
| 623 | + bool disregard; | |
| 624 | + return recover_encryption_key_with_password(password, data, disregard); | |
| 625 | +} | |
| 626 | + | |
| 627 | +static void | |
| 628 | +compute_U_UE_value_V5(std::string const& user_password, | |
| 629 | + std::string const& encryption_key, | |
| 630 | + QPDF::EncryptionData const& data, | |
| 631 | + std::string& U, std::string& UE) | |
| 632 | +{ | |
| 633 | + // Algorithm 3.8 from the PDF 1.7 extension level 3 | |
| 634 | + char k[16]; | |
| 635 | + QUtil::initializeWithRandomBytes((unsigned char*) k, sizeof(k)); | |
| 636 | + std::string validation_salt(k, 8); | |
| 637 | + std::string key_salt(k + 8, 8); | |
| 638 | + U = hash_V5(user_password, validation_salt, "", data) + | |
| 639 | + validation_salt + key_salt; | |
| 640 | + std::string intermediate_key = hash_V5(user_password, key_salt, "", data); | |
| 641 | + UE = process_with_aes(intermediate_key, true, encryption_key); | |
| 642 | +} | |
| 643 | + | |
| 644 | +static void | |
| 645 | +compute_O_OE_value_V5(std::string const& owner_password, | |
| 646 | + std::string const& encryption_key, | |
| 647 | + QPDF::EncryptionData const& data, | |
| 648 | + std::string const& U, | |
| 649 | + std::string& O, std::string& OE) | |
| 650 | +{ | |
| 651 | + // Algorithm 3.9 from the PDF 1.7 extension level 3 | |
| 652 | + char k[16]; | |
| 653 | + QUtil::initializeWithRandomBytes((unsigned char*) k, sizeof(k)); | |
| 654 | + std::string validation_salt(k, 8); | |
| 655 | + std::string key_salt(k + 8, 8); | |
| 656 | + O = hash_V5(owner_password, validation_salt, U, data) + | |
| 657 | + validation_salt + key_salt; | |
| 658 | + std::string intermediate_key = hash_V5(owner_password, key_salt, U, data); | |
| 659 | + OE = process_with_aes(intermediate_key, true, encryption_key); | |
| 660 | +} | |
| 661 | + | |
| 662 | +void | |
| 663 | +compute_Perms_value_V5_clear(std::string const& encryption_key, | |
| 664 | + QPDF::EncryptionData const& data, | |
| 665 | + unsigned char k[16]) | |
| 666 | +{ | |
| 667 | + // From algorithm 3.10 from the PDF 1.7 extension level 3 | |
| 668 | + unsigned long long extended_perms = 0xffffffff00000000LL | data.getP(); | |
| 669 | + for (int i = 0; i < 8; ++i) | |
| 670 | + { | |
| 671 | + k[i] = (unsigned char) (extended_perms & 0xff); | |
| 672 | + extended_perms >>= 8; | |
| 673 | + } | |
| 674 | + k[8] = data.getEncryptMetadata() ? 'T' : 'F'; | |
| 675 | + k[9] = 'a'; | |
| 676 | + k[10] = 'd'; | |
| 677 | + k[11] = 'b'; | |
| 678 | + QUtil::initializeWithRandomBytes(k + 12, 4); | |
| 679 | +} | |
| 680 | + | |
| 681 | +static std::string | |
| 682 | +compute_Perms_value_V5(std::string const& encryption_key, | |
| 683 | + QPDF::EncryptionData const& data) | |
| 684 | +{ | |
| 685 | + // Algorithm 3.10 from the PDF 1.7 extension level 3 | |
| 686 | + unsigned char k[16]; | |
| 687 | + compute_Perms_value_V5_clear(encryption_key, data, k); | |
| 688 | + return process_with_aes(encryption_key, true, | |
| 689 | + std::string((char const*) k, sizeof(k))); | |
| 690 | +} | |
| 691 | + | |
| 692 | +std::string | |
| 693 | +QPDF::recover_encryption_key_with_password( | |
| 694 | + std::string const& password, EncryptionData const& data, | |
| 695 | + bool& perms_valid) | |
| 696 | +{ | |
| 697 | + // Algorithm 3.2a from the PDF 1.7 extension level 3 | |
| 698 | + | |
| 699 | + // This code does not handle Unicode passwords correctly. | |
| 700 | + // Empirical evidence suggests that most viewers don't. We are | |
| 701 | + // supposed to process the input string with the SASLprep (RFC | |
| 702 | + // 4013) profile of stringprep (RFC 3454) and then convert the | |
| 703 | + // result to UTF-8. | |
| 704 | + | |
| 705 | + perms_valid = false; | |
| 706 | + std::string key_password = truncate_password_V5(password); | |
| 707 | + std::string key_salt; | |
| 708 | + std::string user_data; | |
| 709 | + std::string encrypted_file_key; | |
| 710 | + if (check_owner_password_V5(key_password, data)) | |
| 711 | + { | |
| 712 | + key_salt = data.getO().substr(40, 8); | |
| 713 | + user_data = data.getU().substr(0, 48); | |
| 714 | + encrypted_file_key = data.getOE().substr(0, 32); | |
| 715 | + } | |
| 716 | + else if (check_user_password_V5(key_password, data)) | |
| 717 | + { | |
| 718 | + key_salt = data.getU().substr(40, 8); | |
| 719 | + encrypted_file_key = data.getUE().substr(0, 32); | |
| 720 | + } | |
| 721 | + std::string intermediate_key = | |
| 722 | + hash_V5(key_password, key_salt, user_data, data); | |
| 723 | + std::string file_key = | |
| 724 | + process_with_aes(intermediate_key, false, encrypted_file_key); | |
| 725 | + | |
| 726 | + // Decrypt Perms and check against expected value | |
| 727 | + std::string perms_check = | |
| 728 | + process_with_aes(file_key, false, data.getPerms(), 12); | |
| 729 | + unsigned char k[16]; | |
| 730 | + compute_Perms_value_V5_clear(file_key, data, k); | |
| 731 | + perms_valid = (memcmp(perms_check.c_str(), k, 12) == 0); | |
| 732 | + | |
| 733 | + return file_key; | |
| 734 | +} | |
| 735 | + | |
| 387 | 736 | QPDF::encryption_method_e |
| 388 | 737 | QPDF::interpretCF(QPDFObjectHandle cf) |
| 389 | 738 | { |
| ... | ... | @@ -487,29 +836,70 @@ QPDF::initializeEncryption() |
| 487 | 836 | std::string U = encryption_dict.getKey("/U").getStringValue(); |
| 488 | 837 | unsigned int P = (unsigned int) encryption_dict.getKey("/P").getIntValue(); |
| 489 | 838 | |
| 490 | - if (! (((R == 2) || (R == 3) || (R == 4)) && | |
| 491 | - ((V == 1) || (V == 2) || (V == 4)))) | |
| 839 | + // If supporting new encryption R/V values, remember to update | |
| 840 | + // error message inside this if statement. | |
| 841 | + if (! (((R >= 2) && (R <= 6)) && | |
| 842 | + ((V == 1) || (V == 2) || (V == 4) || (V == 5)))) | |
| 492 | 843 | { |
| 493 | 844 | throw QPDFExc(qpdf_e_unsupported, this->file->getName(), |
| 494 | 845 | "encryption dictionary", this->file->getLastOffset(), |
| 495 | - "Unsupported /R or /V in encryption dictionary"); | |
| 846 | + "Unsupported /R or /V in encryption dictionary; R = " + | |
| 847 | + QUtil::int_to_string(R) + " (max 6), V = " + | |
| 848 | + QUtil::int_to_string(V) + " (max 5)"); | |
| 496 | 849 | } |
| 497 | 850 | |
| 498 | 851 | this->encryption_V = V; |
| 852 | + this->encryption_R = R; | |
| 853 | + | |
| 854 | + // OE, UE, and Perms are only present if V >= 5. | |
| 855 | + std::string OE; | |
| 856 | + std::string UE; | |
| 857 | + std::string Perms; | |
| 499 | 858 | |
| 500 | - if (! ((O.length() == key_bytes) && (U.length() == key_bytes))) | |
| 859 | + if (V < 5) | |
| 501 | 860 | { |
| 502 | - throw QPDFExc(qpdf_e_damaged_pdf, this->file->getName(), | |
| 503 | - "encryption dictionary", this->file->getLastOffset(), | |
| 504 | - "incorrect length for /O and/or /P in " | |
| 505 | - "encryption dictionary"); | |
| 861 | + if (! ((O.length() == key_bytes) && (U.length() == key_bytes))) | |
| 862 | + { | |
| 863 | + throw QPDFExc(qpdf_e_damaged_pdf, this->file->getName(), | |
| 864 | + "encryption dictionary", this->file->getLastOffset(), | |
| 865 | + "incorrect length for /O and/or /U in " | |
| 866 | + "encryption dictionary"); | |
| 867 | + } | |
| 868 | + } | |
| 869 | + else | |
| 870 | + { | |
| 871 | + if (! (encryption_dict.getKey("/OE").isString() && | |
| 872 | + encryption_dict.getKey("/UE").isString() && | |
| 873 | + encryption_dict.getKey("/Perms").isString())) | |
| 874 | + { | |
| 875 | + throw QPDFExc(qpdf_e_damaged_pdf, this->file->getName(), | |
| 876 | + "encryption dictionary", this->file->getLastOffset(), | |
| 877 | + "some V=5 encryption dictionary parameters are " | |
| 878 | + "missing or the wrong type"); | |
| 879 | + } | |
| 880 | + OE = encryption_dict.getKey("/OE").getStringValue(); | |
| 881 | + UE = encryption_dict.getKey("/UE").getStringValue(); | |
| 882 | + Perms = encryption_dict.getKey("/Perms").getStringValue(); | |
| 883 | + | |
| 884 | + if ((O.length() < OU_key_bytes_V5) || | |
| 885 | + (U.length() < OU_key_bytes_V5) || | |
| 886 | + (OE.length() < OUE_key_bytes_V5) || | |
| 887 | + (UE.length() < OUE_key_bytes_V5) || | |
| 888 | + (Perms.length() < Perms_key_bytes_V5)) | |
| 889 | + { | |
| 890 | + throw QPDFExc(qpdf_e_damaged_pdf, this->file->getName(), | |
| 891 | + "encryption dictionary", this->file->getLastOffset(), | |
| 892 | + "incorrect length for some of" | |
| 893 | + " /O, /U, /OE, /UE, or /Perms in" | |
| 894 | + " encryption dictionary"); | |
| 895 | + } | |
| 506 | 896 | } |
| 507 | 897 | |
| 508 | 898 | int Length = 40; |
| 509 | 899 | if (encryption_dict.getKey("/Length").isInteger()) |
| 510 | 900 | { |
| 511 | 901 | Length = encryption_dict.getKey("/Length").getIntValue(); |
| 512 | - if ((Length % 8) || (Length < 40) || (Length > 128)) | |
| 902 | + if ((Length % 8) || (Length < 40) || (Length > 256)) | |
| 513 | 903 | { |
| 514 | 904 | throw QPDFExc(qpdf_e_damaged_pdf, this->file->getName(), |
| 515 | 905 | "encryption dictionary", this->file->getLastOffset(), |
| ... | ... | @@ -524,7 +914,7 @@ QPDF::initializeEncryption() |
| 524 | 914 | encryption_dict.getKey("/EncryptMetadata").getBoolValue(); |
| 525 | 915 | } |
| 526 | 916 | |
| 527 | - if (V == 4) | |
| 917 | + if ((V == 4) || (V == 5)) | |
| 528 | 918 | { |
| 529 | 919 | QPDFObjectHandle CF = encryption_dict.getKey("/CF"); |
| 530 | 920 | std::set<std::string> keys = CF.getKeys(); |
| ... | ... | @@ -549,6 +939,11 @@ QPDF::initializeEncryption() |
| 549 | 939 | QTC::TC("qpdf", "QPDF_encryption CFM AESV2"); |
| 550 | 940 | method = e_aes; |
| 551 | 941 | } |
| 942 | + else if (method_name == "/AESV3") | |
| 943 | + { | |
| 944 | + QTC::TC("qpdf", "QPDF_encryption CFM AESV3"); | |
| 945 | + method = e_aesv3; | |
| 946 | + } | |
| 552 | 947 | else |
| 553 | 948 | { |
| 554 | 949 | // Don't complain now -- maybe we won't need |
| ... | ... | @@ -574,13 +969,14 @@ QPDF::initializeEncryption() |
| 574 | 969 | this->cf_file = this->cf_stream; |
| 575 | 970 | } |
| 576 | 971 | } |
| 577 | - EncryptionData data(V, R, Length / 8, P, O, U, "", "", "", | |
| 972 | + | |
| 973 | + EncryptionData data(V, R, Length / 8, P, O, U, OE, UE, Perms, | |
| 578 | 974 | id1, this->encrypt_metadata); |
| 579 | 975 | if (check_owner_password( |
| 580 | 976 | this->user_password, this->provided_password, data)) |
| 581 | 977 | { |
| 582 | 978 | // password supplied was owner password; user_password has |
| 583 | - // been initialized | |
| 979 | + // been initialized for V < 5 | |
| 584 | 980 | } |
| 585 | 981 | else if (check_user_password(this->provided_password, data)) |
| 586 | 982 | { |
| ... | ... | @@ -592,7 +988,30 @@ QPDF::initializeEncryption() |
| 592 | 988 | "", 0, "invalid password"); |
| 593 | 989 | } |
| 594 | 990 | |
| 595 | - this->encryption_key = compute_encryption_key(this->user_password, data); | |
| 991 | + if (V < 5) | |
| 992 | + { | |
| 993 | + // For V < 5, the user password is encrypted with the owner | |
| 994 | + // password, and the user password is always used for | |
| 995 | + // computing the encryption key. | |
| 996 | + this->encryption_key = compute_encryption_key( | |
| 997 | + this->user_password, data); | |
| 998 | + } | |
| 999 | + else | |
| 1000 | + { | |
| 1001 | + // For V >= 5, either password can be used independently to | |
| 1002 | + // compute the encryption key, and neither password can be | |
| 1003 | + // used to recover the other. | |
| 1004 | + bool perms_valid; | |
| 1005 | + this->encryption_key = recover_encryption_key_with_password( | |
| 1006 | + this->provided_password, data, perms_valid); | |
| 1007 | + if (! perms_valid) | |
| 1008 | + { | |
| 1009 | + warn(QPDFExc(qpdf_e_damaged_pdf, this->file->getName(), | |
| 1010 | + "encryption dictionary", this->file->getLastOffset(), | |
| 1011 | + "/Perms field in encryption dictionary" | |
| 1012 | + " doesn't match expected value")); | |
| 1013 | + } | |
| 1014 | + } | |
| 596 | 1015 | } |
| 597 | 1016 | |
| 598 | 1017 | std::string |
| ... | ... | @@ -608,7 +1027,8 @@ QPDF::getKeyForObject(int objid, int generation, bool use_aes) |
| 608 | 1027 | (generation == this->cached_key_generation))) |
| 609 | 1028 | { |
| 610 | 1029 | this->cached_object_encryption_key = |
| 611 | - compute_data_key(this->encryption_key, objid, generation, use_aes); | |
| 1030 | + compute_data_key(this->encryption_key, objid, generation, | |
| 1031 | + use_aes, this->encryption_V, this->encryption_R); | |
| 612 | 1032 | this->cached_key_objid = objid; |
| 613 | 1033 | this->cached_key_generation = generation; |
| 614 | 1034 | } |
| ... | ... | @@ -624,7 +1044,7 @@ QPDF::decryptString(std::string& str, int objid, int generation) |
| 624 | 1044 | return; |
| 625 | 1045 | } |
| 626 | 1046 | bool use_aes = false; |
| 627 | - if (this->encryption_V == 4) | |
| 1047 | + if (this->encryption_V >= 4) | |
| 628 | 1048 | { |
| 629 | 1049 | switch (this->cf_string) |
| 630 | 1050 | { |
| ... | ... | @@ -635,6 +1055,10 @@ QPDF::decryptString(std::string& str, int objid, int generation) |
| 635 | 1055 | use_aes = true; |
| 636 | 1056 | break; |
| 637 | 1057 | |
| 1058 | + case e_aesv3: | |
| 1059 | + use_aes = true; | |
| 1060 | + break; | |
| 1061 | + | |
| 638 | 1062 | case e_rc4: |
| 639 | 1063 | break; |
| 640 | 1064 | |
| ... | ... | @@ -710,7 +1134,7 @@ QPDF::decryptStream(Pipeline*& pipeline, int objid, int generation, |
| 710 | 1134 | return; |
| 711 | 1135 | } |
| 712 | 1136 | bool use_aes = false; |
| 713 | - if (this->encryption_V == 4) | |
| 1137 | + if (this->encryption_V >= 4) | |
| 714 | 1138 | { |
| 715 | 1139 | encryption_method_e method = e_unknown; |
| 716 | 1140 | std::string method_source = "/StmF from /Encrypt dictionary"; |
| ... | ... | @@ -747,7 +1171,7 @@ QPDF::decryptStream(Pipeline*& pipeline, int objid, int generation, |
| 747 | 1171 | if (crypt_params.isDictionary() && |
| 748 | 1172 | crypt_params.getKey("/Name").isName()) |
| 749 | 1173 | { |
| 750 | -// XXX QTC::TC("qpdf", "QPDF_encrypt crypt array"); | |
| 1174 | + QTC::TC("qpdf", "QPDF_encrypt crypt array"); | |
| 751 | 1175 | method = interpretCF( |
| 752 | 1176 | crypt_params.getKey("/Name")); |
| 753 | 1177 | method_source = "stream's Crypt " |
| ... | ... | @@ -790,6 +1214,10 @@ QPDF::decryptStream(Pipeline*& pipeline, int objid, int generation, |
| 790 | 1214 | use_aes = true; |
| 791 | 1215 | break; |
| 792 | 1216 | |
| 1217 | + case e_aesv3: | |
| 1218 | + use_aes = true; | |
| 1219 | + break; | |
| 1220 | + | |
| 793 | 1221 | case e_rc4: |
| 794 | 1222 | break; |
| 795 | 1223 | |
| ... | ... | @@ -831,6 +1259,11 @@ QPDF::compute_encryption_O_U( |
| 831 | 1259 | int V, int R, int key_len, int P, bool encrypt_metadata, |
| 832 | 1260 | std::string const& id1, std::string& O, std::string& U) |
| 833 | 1261 | { |
| 1262 | + if (V >= 5) | |
| 1263 | + { | |
| 1264 | + throw std::logic_error( | |
| 1265 | + "compute_encryption_O_U called for file with V >= 5"); | |
| 1266 | + } | |
| 834 | 1267 | EncryptionData data(V, R, key_len, P, "", "", "", "", "", |
| 835 | 1268 | id1, encrypt_metadata); |
| 836 | 1269 | data.setO(compute_O_value(user_password, owner_password, data)); |
| ... | ... | @@ -839,6 +1272,26 @@ QPDF::compute_encryption_O_U( |
| 839 | 1272 | U = data.getU(); |
| 840 | 1273 | } |
| 841 | 1274 | |
| 1275 | +void | |
| 1276 | +QPDF::compute_encryption_parameters_V5( | |
| 1277 | + char const* user_password, char const* owner_password, | |
| 1278 | + int V, int R, int key_len, int P, bool encrypt_metadata, | |
| 1279 | + std::string const& id1, | |
| 1280 | + std::string& encryption_key, | |
| 1281 | + std::string& O, std::string& U, | |
| 1282 | + std::string& OE, std::string& UE, std::string& Perms) | |
| 1283 | +{ | |
| 1284 | + EncryptionData data(V, R, key_len, P, "", "", "", "", "", | |
| 1285 | + id1, encrypt_metadata); | |
| 1286 | + unsigned char k[key_bytes]; | |
| 1287 | + QUtil::initializeWithRandomBytes(k, key_bytes); | |
| 1288 | + encryption_key = std::string((char const*)k, key_bytes); | |
| 1289 | + compute_U_UE_value_V5(user_password, encryption_key, data, U, UE); | |
| 1290 | + compute_O_OE_value_V5(owner_password, encryption_key, data, U, O, OE); | |
| 1291 | + Perms = compute_Perms_value_V5(encryption_key, data); | |
| 1292 | + data.setV5EncryptionParameters(O, OE, U, UE, Perms); | |
| 1293 | +} | |
| 1294 | + | |
| 842 | 1295 | std::string const& |
| 843 | 1296 | QPDF::getPaddedUserPassword() const |
| 844 | 1297 | { |
| ... | ... | @@ -853,6 +1306,12 @@ QPDF::getTrimmedUserPassword() const |
| 853 | 1306 | return result; |
| 854 | 1307 | } |
| 855 | 1308 | |
| 1309 | +std::string | |
| 1310 | +QPDF::getEncryptionKey() const | |
| 1311 | +{ | |
| 1312 | + return this->encryption_key; | |
| 1313 | +} | |
| 1314 | + | |
| 856 | 1315 | bool |
| 857 | 1316 | QPDF::isEncrypted() const |
| 858 | 1317 | { | ... | ... |
qpdf/qpdf.cc
| ... | ... | @@ -97,7 +97,7 @@ Note that -- terminates parsing of encryption flags.\n\ |
| 97 | 97 | Either or both of the user password and the owner password may be\n\ |
| 98 | 98 | empty strings.\n\ |
| 99 | 99 | \n\ |
| 100 | -key-length may be 40 or 128\n\ | |
| 100 | +key-length may be 40, 128, or 256\n\ | |
| 101 | 101 | \n\ |
| 102 | 102 | Additional flags are dependent upon key length.\n\ |
| 103 | 103 | \n\ |
| ... | ... | @@ -118,6 +118,11 @@ Additional flags are dependent upon key length.\n\ |
| 118 | 118 | --use-aes=[yn] indicates whether to use AES encryption\n\ |
| 119 | 119 | --force-V4 forces use of V=4 encryption handler\n\ |
| 120 | 120 | \n\ |
| 121 | + If 256, options are the same as 128 with these exceptions:\n\ | |
| 122 | + --force-V4 this option is not available with 256-bit keys\n\ | |
| 123 | + --use-aes this option is always on with 256-bit keys\n\ | |
| 124 | + --force-R5 forces use of deprecated R=5 encryption\n\ | |
| 125 | +\n\ | |
| 121 | 126 | print-opt may be:\n\ |
| 122 | 127 | \n\ |
| 123 | 128 | full allow full printing\n\ |
| ... | ... | @@ -283,6 +288,9 @@ static std::string show_encryption_method(QPDF::encryption_method_e method) |
| 283 | 288 | case QPDF::e_aes: |
| 284 | 289 | result = "AESv2"; |
| 285 | 290 | break; |
| 291 | + case QPDF::e_aesv3: | |
| 292 | + result = "AESv3"; | |
| 293 | + break; | |
| 286 | 294 | // no default so gcc will warn for missing case |
| 287 | 295 | } |
| 288 | 296 | return result; |
| ... | ... | @@ -485,7 +493,8 @@ parse_encrypt_options( |
| 485 | 493 | bool& r2_print, bool& r2_modify, bool& r2_extract, bool& r2_annotate, |
| 486 | 494 | bool& r3_accessibility, bool& r3_extract, |
| 487 | 495 | qpdf_r3_print_e& r3_print, qpdf_r3_modify_e& r3_modify, |
| 488 | - bool& force_V4, bool& cleartext_metadata, bool& use_aes) | |
| 496 | + bool& force_V4, bool& cleartext_metadata, bool& use_aes, | |
| 497 | + bool& force_R5) | |
| 489 | 498 | { |
| 490 | 499 | if (cur_arg + 3 >= argc) |
| 491 | 500 | { |
| ... | ... | @@ -502,9 +511,14 @@ parse_encrypt_options( |
| 502 | 511 | { |
| 503 | 512 | keylen = 128; |
| 504 | 513 | } |
| 514 | + else if (len_str == "256") | |
| 515 | + { | |
| 516 | + keylen = 256; | |
| 517 | + use_aes = true; | |
| 518 | + } | |
| 505 | 519 | else |
| 506 | 520 | { |
| 507 | - usage("encryption key length must be 40 or 128"); | |
| 521 | + usage("encryption key length must be 40, 128, or 256"); | |
| 508 | 522 | } |
| 509 | 523 | while (1) |
| 510 | 524 | { |
| ... | ... | @@ -736,15 +750,30 @@ parse_encrypt_options( |
| 736 | 750 | { |
| 737 | 751 | usage("--force-V4 does not take a parameter"); |
| 738 | 752 | } |
| 739 | - if (keylen == 40) | |
| 753 | + if (keylen != 128) | |
| 740 | 754 | { |
| 741 | - usage("--force-V4 is invalid for 40-bit keys"); | |
| 755 | + usage("--force-V4 is invalid only for 128-bit keys"); | |
| 742 | 756 | } |
| 743 | 757 | else |
| 744 | 758 | { |
| 745 | 759 | force_V4 = true; |
| 746 | 760 | } |
| 747 | 761 | } |
| 762 | + else if (strcmp(arg, "force-R5") == 0) | |
| 763 | + { | |
| 764 | + if (parameter) | |
| 765 | + { | |
| 766 | + usage("--force-R5 does not take a parameter"); | |
| 767 | + } | |
| 768 | + if (keylen != 256) | |
| 769 | + { | |
| 770 | + usage("--force-R5 is invalid only for 256-bit keys"); | |
| 771 | + } | |
| 772 | + else | |
| 773 | + { | |
| 774 | + force_R5 = true; | |
| 775 | + } | |
| 776 | + } | |
| 748 | 777 | else if (strcmp(arg, "use-aes") == 0) |
| 749 | 778 | { |
| 750 | 779 | if (parameter == 0) |
| ... | ... | @@ -765,10 +794,16 @@ parse_encrypt_options( |
| 765 | 794 | { |
| 766 | 795 | usage("invalid -use-aes parameter"); |
| 767 | 796 | } |
| 768 | - if (keylen == 40) | |
| 797 | + if ((keylen == 40) && result) | |
| 769 | 798 | { |
| 770 | 799 | usage("use-aes is invalid for 40-bit keys"); |
| 771 | 800 | } |
| 801 | + else if ((keylen == 256) && (! result)) | |
| 802 | + { | |
| 803 | + // qpdf would happily create files encrypted with RC4 | |
| 804 | + // using /V=5, but Adobe reader can't read them. | |
| 805 | + usage("use-aes can't be disabled with 256-bit keys"); | |
| 806 | + } | |
| 772 | 807 | else |
| 773 | 808 | { |
| 774 | 809 | use_aes = result; |
| ... | ... | @@ -921,6 +956,7 @@ int main(int argc, char* argv[]) |
| 921 | 956 | qpdf_r3_print_e r3_print = qpdf_r3p_full; |
| 922 | 957 | qpdf_r3_modify_e r3_modify = qpdf_r3m_all; |
| 923 | 958 | bool force_V4 = false; |
| 959 | + bool force_R5 = false; | |
| 924 | 960 | bool cleartext_metadata = false; |
| 925 | 961 | bool use_aes = false; |
| 926 | 962 | |
| ... | ... | @@ -1004,7 +1040,7 @@ int main(int argc, char* argv[]) |
| 1004 | 1040 | user_password, owner_password, keylen, |
| 1005 | 1041 | r2_print, r2_modify, r2_extract, r2_annotate, |
| 1006 | 1042 | r3_accessibility, r3_extract, r3_print, r3_modify, |
| 1007 | - force_V4, cleartext_metadata, use_aes); | |
| 1043 | + force_V4, cleartext_metadata, use_aes, force_R5); | |
| 1008 | 1044 | encrypt = true; |
| 1009 | 1045 | decrypt = false; |
| 1010 | 1046 | copy_encryption = false; |
| ... | ... | @@ -1612,6 +1648,23 @@ int main(int argc, char* argv[]) |
| 1612 | 1648 | r3_accessibility, r3_extract, r3_print, r3_modify); |
| 1613 | 1649 | } |
| 1614 | 1650 | } |
| 1651 | + else if (keylen == 256) | |
| 1652 | + { | |
| 1653 | + if (force_R5) | |
| 1654 | + { | |
| 1655 | + w.setR5EncryptionParameters( | |
| 1656 | + user_password.c_str(), owner_password.c_str(), | |
| 1657 | + r3_accessibility, r3_extract, r3_print, r3_modify, | |
| 1658 | + !cleartext_metadata); | |
| 1659 | + } | |
| 1660 | + else | |
| 1661 | + { | |
| 1662 | + w.setR6EncryptionParameters( | |
| 1663 | + user_password.c_str(), owner_password.c_str(), | |
| 1664 | + r3_accessibility, r3_extract, r3_print, r3_modify, | |
| 1665 | + !cleartext_metadata); | |
| 1666 | + } | |
| 1667 | + } | |
| 1615 | 1668 | else |
| 1616 | 1669 | { |
| 1617 | 1670 | throw std::logic_error("bad encryption keylen"); | ... | ... |
qpdf/qpdf.testcov
| ... | ... | @@ -243,6 +243,7 @@ QPDFWriter extra header text add newline 0 |
| 243 | 243 | QPDF bogus 0 offset 0 |
| 244 | 244 | QPDF global offset 0 |
| 245 | 245 | QPDFWriter make stream key direct 0 |
| 246 | +QPDFWriter copy V5 0 | |
| 246 | 247 | QPDFWriter increasing extension level 0 |
| 247 | 248 | QPDFWriter make Extensions direct 0 |
| 248 | 249 | QPDFWriter make ADBE direct 1 |
| ... | ... | @@ -253,3 +254,5 @@ QPDFWriter remove existing Extensions 0 |
| 253 | 254 | QPDFWriter skip Extensions 0 |
| 254 | 255 | QPDFWriter preserve ADBE 0 |
| 255 | 256 | QPDF_encryption skip 0x28 0 |
| 257 | +QPDF_encrypt crypt array 0 | |
| 258 | +QPDF_encryption CFM AESV3 0 | ... | ... |
qpdf/qtest/qpdf.test
| ... | ... | @@ -1250,6 +1250,10 @@ $td->notify("--- Encryption Tests ---"); |
| 1250 | 1250 | # resulting files were saved and manually checked with Acrobat 5.0 to |
| 1251 | 1251 | # ensure that the security settings were as intended. |
| 1252 | 1252 | |
| 1253 | +# The enc-XI-file.pdf files were treated the same way but with Acrobat | |
| 1254 | +# XI instead of Acrobat 5.0. They were used to create test files with | |
| 1255 | +# newer encryption formats. | |
| 1256 | + | |
| 1253 | 1257 | # Values: basename, password, encryption flags, /P Encrypt key, |
| 1254 | 1258 | # extract-for-accessibility, extract-for-any-purpose, |
| 1255 | 1259 | # print-low-res, print-high-res, modify-assembly, modify-forms, |
| ... | ... | @@ -1293,9 +1297,25 @@ my @encrypted_files = |
| 1293 | 1297 | '', -4, |
| 1294 | 1298 | 1, 1, 1, 1, 1, 1, 1, 1, 1], |
| 1295 | 1299 | ['long-password', 'asdf asdf asdf asdf asdf asdf qwer'], |
| 1296 | - ['long-password', 'asdf asdf asdf asdf asdf asdf qw']); | |
| 1300 | + ['long-password', 'asdf asdf asdf asdf asdf asdf qw'], | |
| 1301 | + ['XI-base', ''], | |
| 1302 | + ['XI-R6,V5,O=master', '', | |
| 1303 | + '-extract=n -print=none -modify=assembly', -2368, | |
| 1304 | + 1, 0, 0, 0, 1, 0, 0, 0, 0], | |
| 1305 | + ['XI-R6,V5,O=master', 'master', | |
| 1306 | + '-extract=n -print=none -modify=assembly', -2368, | |
| 1307 | + 1, 0, 0, 0, 1, 0, 0, 0, 0], | |
| 1308 | + ['XI-R6,V5,U=view,O=master', 'view', | |
| 1309 | + '-print=low', -2052, | |
| 1310 | + 1, 1, 1, 0, 1, 1, 1, 1, 1], | |
| 1311 | + ['XI-R6,V5,U=view,O=master', 'master', | |
| 1312 | + '-print=low', -2052, | |
| 1313 | + 1, 1, 1, 0, 1, 1, 1, 1, 1], | |
| 1314 | + ['XI-long-password', 'qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnm'], | |
| 1315 | + ['XI-long-password', 'qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcv'], | |
| 1316 | + ); | |
| 1297 | 1317 | |
| 1298 | -$n_tests += 3 + (2 * (@encrypted_files)) + (6 * (@encrypted_files - 3)) + 9; | |
| 1318 | +$n_tests += 5 + (2 * (@encrypted_files)) + (6 * (@encrypted_files - 6)) + 9; | |
| 1299 | 1319 | |
| 1300 | 1320 | $td->runtest("encrypted file", |
| 1301 | 1321 | {$td->COMMAND => "test_driver 2 U25A0.pdf"}, |
| ... | ... | @@ -1312,6 +1332,19 @@ $td->runtest("recheck encrypted file", |
| 1312 | 1332 | $td->EXIT_STATUS => 0}, |
| 1313 | 1333 | $td->NORMALIZE_NEWLINES); |
| 1314 | 1334 | |
| 1335 | +# Test that long passwords that are one character too short fail. We | |
| 1336 | +# test the truncation cases in the loop below by using passwords | |
| 1337 | +# longer than the supported length. | |
| 1338 | +$td->runtest("significant password characters (V < 5)", | |
| 1339 | + {$td->COMMAND => "qpdf --check enc-long-password.pdf" . | |
| 1340 | + " --password='asdf asdf asdf asdf asdf asdf q'"}, | |
| 1341 | + {$td->REGEXP => ".*invalid password.*", $td->EXIT_STATUS => 2}); | |
| 1342 | +$td->runtest("significant password characters (V = 5)", | |
| 1343 | + {$td->COMMAND => "qpdf --check enc-XI-long-password.pdf" . | |
| 1344 | + " --password=qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxc"}, | |
| 1345 | + {$td->REGEXP => ".*invalid password.*", $td->EXIT_STATUS => 2}); | |
| 1346 | + | |
| 1347 | +my $enc_base = undef; | |
| 1315 | 1348 | foreach my $d (@encrypted_files) |
| 1316 | 1349 | { |
| 1317 | 1350 | my ($file, $pass, $xeflags, $P, |
| ... | ... | @@ -1330,17 +1363,26 @@ foreach my $d (@encrypted_files) |
| 1330 | 1363 | "modify annotations: " . &$f($modifyannot) . "\n" . |
| 1331 | 1364 | "modify other: " . &$f($modifyother) . "\n" . |
| 1332 | 1365 | "modify anything: " . &$f($modifyall) . "\n"; |
| 1366 | + if ($file =~ m/XI-/) | |
| 1367 | + { | |
| 1368 | + $enc_details .= | |
| 1369 | + "stream encryption method: AESv3\n" . | |
| 1370 | + "string encryption method: AESv3\n" . | |
| 1371 | + "file encryption method: AESv3\n"; | |
| 1372 | + } | |
| 1333 | 1373 | |
| 1334 | 1374 | # Test writing to stdout |
| 1335 | 1375 | $td->runtest("decrypt $file", |
| 1336 | 1376 | {$td->COMMAND => |
| 1337 | - "qpdf --static-id -qdf --no-original-object-ids" . | |
| 1377 | + "qpdf --static-id -qdf --object-streams=disable" . | |
| 1378 | + " --no-original-object-ids" . | |
| 1338 | 1379 | " --password=\"$pass\" enc-$file.pdf -" . |
| 1339 | 1380 | " > $file.enc"}, |
| 1340 | 1381 | {$td->STRING => "", |
| 1341 | 1382 | $td->EXIT_STATUS => 0}); |
| 1342 | - if ($file eq 'base') | |
| 1383 | + if ($file =~ m/base$/) | |
| 1343 | 1384 | { |
| 1385 | + $enc_base = $file; | |
| 1344 | 1386 | $td->runtest("check ID", |
| 1345 | 1387 | {$td->COMMAND => "perl check-ID.pl $file.enc"}, |
| 1346 | 1388 | {$td->STRING => "ID okay\n", |
| ... | ... | @@ -1350,20 +1392,27 @@ foreach my $d (@encrypted_files) |
| 1350 | 1392 | else |
| 1351 | 1393 | { |
| 1352 | 1394 | $td->runtest("check against base", |
| 1353 | - {$td->COMMAND => "./diff-encrypted base.enc $file.enc"}, | |
| 1395 | + {$td->COMMAND => | |
| 1396 | + "./diff-encrypted $enc_base.enc $file.enc"}, | |
| 1354 | 1397 | {$td->STRING => "okay\n", |
| 1355 | 1398 | $td->EXIT_STATUS => 0}, |
| 1356 | 1399 | $td->NORMALIZE_NEWLINES); |
| 1357 | 1400 | } |
| 1358 | - if ($file =~ m/^R(\d),V(\d)(?:,U=(\w+))?(?:,O=(\w+))?$/) | |
| 1401 | + if ($file =~ m/^(?:XI-)?R(\d),V(\d)(?:,U=(\w+))?(?:,O=(\w+))?$/) | |
| 1359 | 1402 | { |
| 1360 | 1403 | my $R = $1; |
| 1361 | 1404 | my $V = $2; |
| 1362 | 1405 | my $upass = $3 || ""; |
| 1363 | 1406 | my $opass = $4 || ""; |
| 1364 | - my $bits = (($V == 2) ? 128 : 40); | |
| 1407 | + my $bits = (($V == 5) ? 256 : ($V == 2) ? 128 : 40); | |
| 1365 | 1408 | |
| 1366 | 1409 | my $eflags = "-encrypt \"$upass\" \"$opass\" $bits $xeflags --"; |
| 1410 | + if (($pass ne $upass) && ($V >= 5)) | |
| 1411 | + { | |
| 1412 | + # V >= 5 can no longer recover user password with owner | |
| 1413 | + # password. | |
| 1414 | + $upass = ""; | |
| 1415 | + } | |
| 1367 | 1416 | $td->runtest("encrypt $file", |
| 1368 | 1417 | {$td->COMMAND => |
| 1369 | 1418 | "qpdf --static-id --no-original-object-ids -qdf" . |
| ... | ... | @@ -1488,7 +1537,7 @@ $td->runtest("check linearization", |
| 1488 | 1537 | $td->NORMALIZE_NEWLINES); |
| 1489 | 1538 | |
| 1490 | 1539 | # Test AES encryption in various ways. |
| 1491 | -$n_tests += 14; | |
| 1540 | +$n_tests += 18; | |
| 1492 | 1541 | $td->runtest("encrypt with AES", |
| 1493 | 1542 | {$td->COMMAND => "qpdf --encrypt '' '' 128 --use-aes=y --" . |
| 1494 | 1543 | " enc-base.pdf a.pdf"}, |
| ... | ... | @@ -1548,6 +1597,24 @@ $td->runtest("make sure there is no xref stream", |
| 1548 | 1597 | {$td->COMMAND => "grep /ObjStm b.pdf | wc -l"}, |
| 1549 | 1598 | {$td->REGEXP => "\\s*0\\s*", $td->EXIT_STATUS => 0}, |
| 1550 | 1599 | $td->NORMALIZE_NEWLINES); |
| 1600 | +$td->runtest("encrypt with V=5,R=5", | |
| 1601 | + {$td->COMMAND => | |
| 1602 | + "qpdf --encrypt user owner 256 --force-R5 -- " . | |
| 1603 | + "minimal.pdf a.pdf"}, | |
| 1604 | + {$td->STRING => "", $td->EXIT_STATUS => 0}); | |
| 1605 | +$td->runtest("check encryption", | |
| 1606 | + {$td->COMMAND => "qpdf --check a.pdf --password=owner"}, | |
| 1607 | + {$td->FILE => "V5R5.out", $td->EXIT_STATUS => 0}, | |
| 1608 | + $td->NORMALIZE_NEWLINES); | |
| 1609 | +$td->runtest("encrypt with V=5,R=6", | |
| 1610 | + {$td->COMMAND => | |
| 1611 | + "qpdf --encrypt user owner 256 -- " . | |
| 1612 | + "minimal.pdf a.pdf"}, | |
| 1613 | + {$td->STRING => "", $td->EXIT_STATUS => 0}); | |
| 1614 | +$td->runtest("check encryption", | |
| 1615 | + {$td->COMMAND => "qpdf --check a.pdf --password=user"}, | |
| 1616 | + {$td->FILE => "V5R6.out", $td->EXIT_STATUS => 0}, | |
| 1617 | + $td->NORMALIZE_NEWLINES); | |
| 1551 | 1618 | |
| 1552 | 1619 | # Look at some actual V4 files |
| 1553 | 1620 | $n_tests += 14; |
| ... | ... | @@ -1629,6 +1696,36 @@ $td->runtest("compare qdf", |
| 1629 | 1696 | {$td->STRING => "okay\n", $td->EXIT_STATUS => 0}, |
| 1630 | 1697 | $td->NORMALIZE_NEWLINES); |
| 1631 | 1698 | |
| 1699 | +# Files with attachments | |
| 1700 | +my @attachments = ( | |
| 1701 | + 'enc-XI-attachments-base.pdf', | |
| 1702 | + 'enc-XI-R6,V5,U=attachment,encrypted-attachments.pdf', | |
| 1703 | + 'enc-XI-R6,V5,U=view,attachments,cleartext-metadata.pdf'); | |
| 1704 | +$n_tests += 4 * @attachments; | |
| 1705 | +foreach my $f (@attachments) | |
| 1706 | +{ | |
| 1707 | + my $pass = ''; | |
| 1708 | + my $tpass = ''; | |
| 1709 | + if ($f =~ m/U=([^,]+)/) | |
| 1710 | + { | |
| 1711 | + $pass = "--password=$1"; | |
| 1712 | + $tpass = $1; | |
| 1713 | + } | |
| 1714 | + $td->runtest("decrypt $f", | |
| 1715 | + {$td->COMMAND => "qpdf --decrypt $pass $f a.pdf"}, | |
| 1716 | + {$td->STRING => "", $td->EXIT_STATUS => 0}); | |
| 1717 | + $td->runtest("extract attachments", | |
| 1718 | + {$td->COMMAND => "test_driver 35 a.pdf"}, | |
| 1719 | + {$td->FILE => "attachments.out", $td->EXIT_STATUS => 0}, | |
| 1720 | + $td->NORMALIZE_NEWLINES); | |
| 1721 | + $td->runtest("copy $f", | |
| 1722 | + {$td->COMMAND => "qpdf $pass $f a.pdf"}, | |
| 1723 | + {$td->STRING => "", $td->EXIT_STATUS => 0}); | |
| 1724 | + $td->runtest("extract attachments", | |
| 1725 | + {$td->COMMAND => "test_driver 35 a.pdf $tpass"}, | |
| 1726 | + {$td->FILE => "attachments.out", $td->EXIT_STATUS => 0}, | |
| 1727 | + $td->NORMALIZE_NEWLINES); | |
| 1728 | +} | |
| 1632 | 1729 | |
| 1633 | 1730 | show_ntests(); |
| 1634 | 1731 | # ---------- | ... | ... |
qpdf/qtest/qpdf/V5R5.out
0 → 100644
| 1 | +checking a.pdf | |
| 2 | +PDF Version: 1.7 extension level 3 | |
| 3 | +R = 5 | |
| 4 | +P = -4 | |
| 5 | +User password = | |
| 6 | +extract for accessibility: allowed | |
| 7 | +extract for any purpose: allowed | |
| 8 | +print low resolution: allowed | |
| 9 | +print high resolution: allowed | |
| 10 | +modify document assembly: allowed | |
| 11 | +modify forms: allowed | |
| 12 | +modify annotations: allowed | |
| 13 | +modify other: allowed | |
| 14 | +modify anything: allowed | |
| 15 | +stream encryption method: AESv3 | |
| 16 | +string encryption method: AESv3 | |
| 17 | +file encryption method: AESv3 | |
| 18 | +File is not linearized | |
| 19 | +No syntax or stream encoding errors found; the file may still contain | |
| 20 | +errors that qpdf cannot detect | ... | ... |
qpdf/qtest/qpdf/V5R6.out
0 → 100644
| 1 | +checking a.pdf | |
| 2 | +PDF Version: 1.7 extension level 8 | |
| 3 | +R = 6 | |
| 4 | +P = -4 | |
| 5 | +User password = user | |
| 6 | +extract for accessibility: allowed | |
| 7 | +extract for any purpose: allowed | |
| 8 | +print low resolution: allowed | |
| 9 | +print high resolution: allowed | |
| 10 | +modify document assembly: allowed | |
| 11 | +modify forms: allowed | |
| 12 | +modify annotations: allowed | |
| 13 | +modify other: allowed | |
| 14 | +modify anything: allowed | |
| 15 | +stream encryption method: AESv3 | |
| 16 | +string encryption method: AESv3 | |
| 17 | +file encryption method: AESv3 | |
| 18 | +File is not linearized | |
| 19 | +No syntax or stream encoding errors found; the file may still contain | |
| 20 | +errors that qpdf cannot detect | ... | ... |
qpdf/qtest/qpdf/attachments.out
0 → 100644
qpdf/qtest/qpdf/diff-encrypted
qpdf/qtest/qpdf/enc-XI-R6,V5,O=master.pdf
0 → 100644
No preview for this file type
qpdf/qtest/qpdf/enc-XI-R6,V5,U=attachment,encrypted-attachments.pdf
0 → 100644
No preview for this file type
qpdf/qtest/qpdf/enc-XI-R6,V5,U=view,O=master.pdf
0 → 100644
No preview for this file type
qpdf/qtest/qpdf/enc-XI-R6,V5,U=view,attachments,cleartext-metadata.pdf
0 → 100644
No preview for this file type
qpdf/qtest/qpdf/enc-XI-attachments-base.pdf
0 → 100644
No preview for this file type
qpdf/qtest/qpdf/enc-XI-base.pdf
0 → 100644
No preview for this file type
qpdf/qtest/qpdf/enc-XI-long-password.pdf
0 → 100644
No preview for this file type
qpdf/test_driver.cc
| ... | ... | @@ -112,7 +112,12 @@ void runtest(int n, char const* filename1, char const* arg2) |
| 112 | 112 | { |
| 113 | 113 | pdf.setAttemptRecovery(false); |
| 114 | 114 | } |
| 115 | - if (n % 2 == 0) | |
| 115 | + if ((n == 35) && (arg2 != 0)) | |
| 116 | + { | |
| 117 | + // arg2 is password | |
| 118 | + pdf.processFile(filename1, arg2); | |
| 119 | + } | |
| 120 | + else if (n % 2 == 0) | |
| 116 | 121 | { |
| 117 | 122 | if (n % 4 == 0) |
| 118 | 123 | { |
| ... | ... | @@ -1150,6 +1155,65 @@ void runtest(int n, char const* filename1, char const* arg2) |
| 1150 | 1155 | << "extension level: " << pdf.getExtensionLevel() << std::endl |
| 1151 | 1156 | << pdf.getRoot().getKey("/Extensions").unparse() << std::endl; |
| 1152 | 1157 | } |
| 1158 | + else if (n == 35) | |
| 1159 | + { | |
| 1160 | + // Extract attachments | |
| 1161 | + | |
| 1162 | + std::map<std::string, PointerHolder<Buffer> > attachments; | |
| 1163 | + QPDFObjectHandle root = pdf.getRoot(); | |
| 1164 | + QPDFObjectHandle names = root.getKey("/Names"); | |
| 1165 | + QPDFObjectHandle embeddedFiles = names.getKey("/EmbeddedFiles"); | |
| 1166 | + names = embeddedFiles.getKey("/Names"); | |
| 1167 | + for (int i = 0; i < names.getArrayNItems(); ++i) | |
| 1168 | + { | |
| 1169 | + QPDFObjectHandle item = names.getArrayItem(i); | |
| 1170 | + if (item.isDictionary() && | |
| 1171 | + item.getKey("/Type").isName() && | |
| 1172 | + (item.getKey("/Type").getName() == "/Filespec") && | |
| 1173 | + item.getKey("/EF").isDictionary() && | |
| 1174 | + item.getKey("/EF").getKey("/F").isStream()) | |
| 1175 | + { | |
| 1176 | + std::string filename = item.getKey("/F").getStringValue(); | |
| 1177 | + QPDFObjectHandle stream = item.getKey("/EF").getKey("/F"); | |
| 1178 | + attachments[filename] = stream.getStreamData(); | |
| 1179 | + } | |
| 1180 | + } | |
| 1181 | + for (std::map<std::string, PointerHolder<Buffer> >::iterator iter = | |
| 1182 | + attachments.begin(); iter != attachments.end(); ++iter) | |
| 1183 | + { | |
| 1184 | + std::string const& filename = (*iter).first; | |
| 1185 | + std::string data = std::string( | |
| 1186 | + (char const*)(*iter).second->getBuffer(), | |
| 1187 | + (*iter).second->getSize()); | |
| 1188 | + bool is_binary = false; | |
| 1189 | + for (size_t i = 0; i < data.size(); ++i) | |
| 1190 | + { | |
| 1191 | + if (data[i] < 0) | |
| 1192 | + { | |
| 1193 | + is_binary = true; | |
| 1194 | + break; | |
| 1195 | + } | |
| 1196 | + } | |
| 1197 | + if (is_binary) | |
| 1198 | + { | |
| 1199 | + std::string t; | |
| 1200 | + for (size_t i = 0; i < std::min(data.size(), (size_t)20); ++i) | |
| 1201 | + { | |
| 1202 | + if ((data[i] >= 32) && (data[i] <= 126)) | |
| 1203 | + { | |
| 1204 | + t += data[i]; | |
| 1205 | + } | |
| 1206 | + else | |
| 1207 | + { | |
| 1208 | + t += "."; | |
| 1209 | + } | |
| 1210 | + } | |
| 1211 | + t += " (" + QUtil::int_to_string(data.size()) + " bytes)"; | |
| 1212 | + data = t; | |
| 1213 | + } | |
| 1214 | + std::cout << filename << ":\n" << data << "--END--\n"; | |
| 1215 | + } | |
| 1216 | + } | |
| 1153 | 1217 | else |
| 1154 | 1218 | { |
| 1155 | 1219 | throw std::runtime_error(std::string("invalid test ") + | ... | ... |