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 | General | 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 | * Improve the random number seed to make it more secure so that we | 4 | * Improve the random number seed to make it more secure so that we |
| 31 | have stronger random numbers, particularly when multiple files are | 5 | have stronger random numbers, particularly when multiple files are |
| 32 | generated in the same second. This code may need to be | 6 | generated in the same second. This code may need to be |
include/qpdf/QPDF.hh
| @@ -224,7 +224,7 @@ class QPDF | @@ -224,7 +224,7 @@ class QPDF | ||
| 224 | 224 | ||
| 225 | // Encryption support | 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 | class EncryptionData | 228 | class EncryptionData |
| 229 | { | 229 | { |
| 230 | public: | 230 | public: |
| @@ -326,7 +326,7 @@ class QPDF | @@ -326,7 +326,7 @@ class QPDF | ||
| 326 | QPDF_DLL | 326 | QPDF_DLL |
| 327 | static std::string compute_data_key( | 327 | static std::string compute_data_key( |
| 328 | std::string const& encryption_key, int objid, int generation, | 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 | QPDF_DLL | 330 | QPDF_DLL |
| 331 | static std::string compute_encryption_key( | 331 | static std::string compute_encryption_key( |
| 332 | std::string const& password, EncryptionData const& data); | 332 | std::string const& password, EncryptionData const& data); |
| @@ -337,6 +337,14 @@ class QPDF | @@ -337,6 +337,14 @@ class QPDF | ||
| 337 | int V, int R, int key_len, int P, bool encrypt_metadata, | 337 | int V, int R, int key_len, int P, bool encrypt_metadata, |
| 338 | std::string const& id1, | 338 | std::string const& id1, |
| 339 | std::string& O, std::string& U); | 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 | // Return the full user password as stored in the PDF file. If | 348 | // Return the full user password as stored in the PDF file. If |
| 341 | // you are attempting to recover the user password in a | 349 | // you are attempting to recover the user password in a |
| 342 | // user-presentable form, call getTrimmedUserPassword() instead. | 350 | // user-presentable form, call getTrimmedUserPassword() instead. |
| @@ -345,6 +353,10 @@ class QPDF | @@ -345,6 +353,10 @@ class QPDF | ||
| 345 | // Return human-readable form of user password. | 353 | // Return human-readable form of user password. |
| 346 | QPDF_DLL | 354 | QPDF_DLL |
| 347 | std::string getTrimmedUserPassword() const; | 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 | // Linearization support | 361 | // Linearization support |
| 350 | 362 | ||
| @@ -628,6 +640,13 @@ class QPDF | @@ -628,6 +640,13 @@ class QPDF | ||
| 628 | void initializeEncryption(); | 640 | void initializeEncryption(); |
| 629 | std::string getKeyForObject(int objid, int generation, bool use_aes); | 641 | std::string getKeyForObject(int objid, int generation, bool use_aes); |
| 630 | void decryptString(std::string&, int objid, int generation); | 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 | void decryptStream( | 650 | void decryptStream( |
| 632 | Pipeline*& pipeline, int objid, int generation, | 651 | Pipeline*& pipeline, int objid, int generation, |
| 633 | QPDFObjectHandle& stream_dict, | 652 | QPDFObjectHandle& stream_dict, |
| @@ -981,6 +1000,7 @@ class QPDF | @@ -981,6 +1000,7 @@ class QPDF | ||
| 981 | std::ostream* err_stream; | 1000 | std::ostream* err_stream; |
| 982 | bool attempt_recovery; | 1001 | bool attempt_recovery; |
| 983 | int encryption_V; | 1002 | int encryption_V; |
| 1003 | + int encryption_R; | ||
| 984 | bool encrypt_metadata; | 1004 | bool encrypt_metadata; |
| 985 | std::map<std::string, encryption_method_e> crypt_filters; | 1005 | std::map<std::string, encryption_method_e> crypt_filters; |
| 986 | encryption_method_e cf_stream; | 1006 | encryption_method_e cf_stream; |
include/qpdf/QPDFWriter.hh
| @@ -223,8 +223,9 @@ class QPDFWriter | @@ -223,8 +223,9 @@ class QPDFWriter | ||
| 223 | // content normalization. Note that setting R2 encryption | 223 | // content normalization. Note that setting R2 encryption |
| 224 | // parameters sets the PDF version to at least 1.3, setting R3 | 224 | // parameters sets the PDF version to at least 1.3, setting R3 |
| 225 | // encryption parameters pushes the PDF version number to at least | 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 | QPDF_DLL | 229 | QPDF_DLL |
| 229 | void setR2EncryptionParameters( | 230 | void setR2EncryptionParameters( |
| 230 | char const* user_password, char const* owner_password, | 231 | char const* user_password, char const* owner_password, |
| @@ -241,6 +242,21 @@ class QPDFWriter | @@ -241,6 +242,21 @@ class QPDFWriter | ||
| 241 | bool allow_accessibility, bool allow_extract, | 242 | bool allow_accessibility, bool allow_extract, |
| 242 | qpdf_r3_print_e print, qpdf_r3_modify_e modify, | 243 | qpdf_r3_print_e print, qpdf_r3_modify_e modify, |
| 243 | bool encrypt_metadata, bool use_aes); | 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 | // Create linearized output. Disables qdf mode, content | 261 | // Create linearized output. Disables qdf mode, content |
| 246 | // normalization, and stream prefiltering. | 262 | // normalization, and stream prefiltering. |
| @@ -302,7 +318,8 @@ class QPDFWriter | @@ -302,7 +318,8 @@ class QPDFWriter | ||
| 302 | int V, int R, int key_len, long P, | 318 | int V, int R, int key_len, long P, |
| 303 | std::string const& O, std::string const& U, | 319 | std::string const& O, std::string const& U, |
| 304 | std::string const& OE, std::string const& UE, std::string const& Perms, | 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 | void setDataKey(int objid); | 323 | void setDataKey(int objid); |
| 307 | int openObject(int objid = 0); | 324 | int openObject(int objid = 0); |
| 308 | void closeObject(int objid); | 325 | void closeObject(int objid); |
| @@ -378,6 +395,8 @@ class QPDFWriter | @@ -378,6 +395,8 @@ class QPDFWriter | ||
| 378 | bool encrypt_metadata; | 395 | bool encrypt_metadata; |
| 379 | bool encrypt_use_aes; | 396 | bool encrypt_use_aes; |
| 380 | std::map<std::string, std::string> encryption_dictionary; | 397 | std::map<std::string, std::string> encryption_dictionary; |
| 398 | + int encryption_V; | ||
| 399 | + int encryption_R; | ||
| 381 | 400 | ||
| 382 | std::string id1; // for /ID key of | 401 | std::string id1; // for /ID key of |
| 383 | std::string id2; // trailer dictionary | 402 | std::string id2; // trailer dictionary |
libqpdf/QPDF.cc
| @@ -97,6 +97,7 @@ QPDF::QPDF() : | @@ -97,6 +97,7 @@ QPDF::QPDF() : | ||
| 97 | err_stream(&std::cerr), | 97 | err_stream(&std::cerr), |
| 98 | attempt_recovery(true), | 98 | attempt_recovery(true), |
| 99 | encryption_V(0), | 99 | encryption_V(0), |
| 100 | + encryption_R(0), | ||
| 100 | encrypt_metadata(true), | 101 | encrypt_metadata(true), |
| 101 | cf_stream(e_none), | 102 | cf_stream(e_none), |
| 102 | cf_string(e_none), | 103 | cf_string(e_none), |
libqpdf/QPDFWriter.cc
| @@ -67,6 +67,8 @@ QPDFWriter::init() | @@ -67,6 +67,8 @@ QPDFWriter::init() | ||
| 67 | min_extension_level = 0; | 67 | min_extension_level = 0; |
| 68 | final_extension_level = 0; | 68 | final_extension_level = 0; |
| 69 | forced_extension_level = 0; | 69 | forced_extension_level = 0; |
| 70 | + encryption_V = 0; | ||
| 71 | + encryption_R = 0; | ||
| 70 | encryption_dict_objid = 0; | 72 | encryption_dict_objid = 0; |
| 71 | next_objid = 1; | 73 | next_objid = 1; |
| 72 | cur_stream_length_id = 0; | 74 | cur_stream_length_id = 0; |
| @@ -344,6 +346,38 @@ QPDFWriter::setR4EncryptionParameters( | @@ -344,6 +346,38 @@ QPDFWriter::setR4EncryptionParameters( | ||
| 344 | } | 346 | } |
| 345 | 347 | ||
| 346 | void | 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 | QPDFWriter::interpretR3EncryptionParameters( | 381 | QPDFWriter::interpretR3EncryptionParameters( |
| 348 | std::set<int>& clear, | 382 | std::set<int>& clear, |
| 349 | char const* user_password, char const* owner_password, | 383 | char const* user_password, char const* owner_password, |
| @@ -426,6 +460,12 @@ QPDFWriter::setEncryptionParameters( | @@ -426,6 +460,12 @@ QPDFWriter::setEncryptionParameters( | ||
| 426 | bits_to_clear.insert(1); | 460 | bits_to_clear.insert(1); |
| 427 | bits_to_clear.insert(2); | 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 | int P = 0; | 469 | int P = 0; |
| 430 | // Create the complement of P, then invert. | 470 | // Create the complement of P, then invert. |
| 431 | for (std::set<int>::iterator iter = bits_to_clear.begin(); | 471 | for (std::set<int>::iterator iter = bits_to_clear.begin(); |
| @@ -438,11 +478,26 @@ QPDFWriter::setEncryptionParameters( | @@ -438,11 +478,26 @@ QPDFWriter::setEncryptionParameters( | ||
| 438 | generateID(); | 478 | generateID(); |
| 439 | std::string O; | 479 | std::string O; |
| 440 | std::string U; | 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 | setEncryptionParametersInternal( | 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 | void | 503 | void |
| @@ -482,6 +537,19 @@ QPDFWriter::copyEncryptionParameters(QPDF& qpdf) | @@ -482,6 +537,19 @@ QPDFWriter::copyEncryptionParameters(QPDF& qpdf) | ||
| 482 | this->encrypt_metadata ? 0 : 1); | 537 | this->encrypt_metadata ? 0 : 1); |
| 483 | QTC::TC("qpdf", "QPDFWriter copy use_aes", | 538 | QTC::TC("qpdf", "QPDFWriter copy use_aes", |
| 484 | this->encrypt_use_aes ? 0 : 1); | 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 | setEncryptionParametersInternal( | 553 | setEncryptionParametersInternal( |
| 486 | V, | 554 | V, |
| 487 | encrypt.getKey("/R").getIntValue(), | 555 | encrypt.getKey("/R").getIntValue(), |
| @@ -489,11 +557,12 @@ QPDFWriter::copyEncryptionParameters(QPDF& qpdf) | @@ -489,11 +557,12 @@ QPDFWriter::copyEncryptionParameters(QPDF& qpdf) | ||
| 489 | encrypt.getKey("/P").getIntValue(), | 557 | encrypt.getKey("/P").getIntValue(), |
| 490 | encrypt.getKey("/O").getStringValue(), | 558 | encrypt.getKey("/O").getStringValue(), |
| 491 | encrypt.getKey("/U").getStringValue(), | 559 | encrypt.getKey("/U").getStringValue(), |
| 492 | - "", // XXXX OE | ||
| 493 | - "", // XXXX UE | ||
| 494 | - "", // XXXX Perms | 560 | + OE, |
| 561 | + UE, | ||
| 562 | + Perms, | ||
| 495 | this->id1, // this->id1 == the other file's id1 | 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,10 +674,11 @@ QPDFWriter::setEncryptionParametersInternal( | ||
| 605 | int V, int R, int key_len, long P, | 674 | int V, int R, int key_len, long P, |
| 606 | std::string const& O, std::string const& U, | 675 | std::string const& O, std::string const& U, |
| 607 | std::string const& OE, std::string const& UE, std::string const& Perms, | 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 | encryption_dictionary["/Filter"] = "/Standard"; | 682 | encryption_dictionary["/Filter"] = "/Standard"; |
| 613 | encryption_dictionary["/V"] = QUtil::int_to_string(V); | 683 | encryption_dictionary["/V"] = QUtil::int_to_string(V); |
| 614 | encryption_dictionary["/Length"] = QUtil::int_to_string(key_len * 8); | 684 | encryption_dictionary["/Length"] = QUtil::int_to_string(key_len * 8); |
| @@ -618,9 +688,15 @@ QPDFWriter::setEncryptionParametersInternal( | @@ -618,9 +688,15 @@ QPDFWriter::setEncryptionParametersInternal( | ||
| 618 | encryption_dictionary["/U"] = QPDF_String(U).unparse(true); | 688 | encryption_dictionary["/U"] = QPDF_String(U).unparse(true); |
| 619 | if (V >= 5) | 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 | setMinimumPDFVersion("1.7", 3); | 701 | setMinimumPDFVersion("1.7", 3); |
| 626 | } | 702 | } |
| @@ -641,14 +717,16 @@ QPDFWriter::setEncryptionParametersInternal( | @@ -641,14 +717,16 @@ QPDFWriter::setEncryptionParametersInternal( | ||
| 641 | { | 717 | { |
| 642 | encryption_dictionary["/EncryptMetadata"] = "false"; | 718 | encryption_dictionary["/EncryptMetadata"] = "false"; |
| 643 | } | 719 | } |
| 644 | - if (V == 4) | 720 | + if ((V == 4) || (V == 5)) |
| 645 | { | 721 | { |
| 646 | // The spec says the value for the crypt filter key can be | 722 | // The spec says the value for the crypt filter key can be |
| 647 | // anything, and xpdf seems to agree. However, Adobe Reader | 723 | // anything, and xpdf seems to agree. However, Adobe Reader |
| 648 | // won't open our files unless we use /StdCF. | 724 | // won't open our files unless we use /StdCF. |
| 649 | encryption_dictionary["/StmF"] = "/StdCF"; | 725 | encryption_dictionary["/StmF"] = "/StdCF"; |
| 650 | encryption_dictionary["/StrF"] = "/StdCF"; | 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 | encryption_dictionary["/CF"] = | 730 | encryption_dictionary["/CF"] = |
| 653 | "<< /StdCF << /AuthEvent /DocOpen /CFM " + method + " >> >>"; | 731 | "<< /StdCF << /AuthEvent /DocOpen /CFM " + method + " >> >>"; |
| 654 | } | 732 | } |
| @@ -656,15 +734,23 @@ QPDFWriter::setEncryptionParametersInternal( | @@ -656,15 +734,23 @@ QPDFWriter::setEncryptionParametersInternal( | ||
| 656 | this->encrypted = true; | 734 | this->encrypted = true; |
| 657 | QPDF::EncryptionData encryption_data( | 735 | QPDF::EncryptionData encryption_data( |
| 658 | V, R, key_len, P, O, U, OE, UE, Perms, id1, this->encrypt_metadata); | 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 | void | 748 | void |
| 664 | QPDFWriter::setDataKey(int objid) | 749 | QPDFWriter::setDataKey(int objid) |
| 665 | { | 750 | { |
| 666 | this->cur_data_key = QPDF::compute_data_key( | 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 | int | 756 | int |
libqpdf/QPDF_encryption.cc
| @@ -10,6 +10,7 @@ | @@ -10,6 +10,7 @@ | ||
| 10 | #include <qpdf/Pl_RC4.hh> | 10 | #include <qpdf/Pl_RC4.hh> |
| 11 | #include <qpdf/Pl_AES_PDF.hh> | 11 | #include <qpdf/Pl_AES_PDF.hh> |
| 12 | #include <qpdf/Pl_Buffer.hh> | 12 | #include <qpdf/Pl_Buffer.hh> |
| 13 | +#include <qpdf/Pl_SHA2.hh> | ||
| 13 | #include <qpdf/RC4.hh> | 14 | #include <qpdf/RC4.hh> |
| 14 | #include <qpdf/MD5.hh> | 15 | #include <qpdf/MD5.hh> |
| 15 | 16 | ||
| @@ -23,9 +24,15 @@ static unsigned char const padding_string[] = { | @@ -23,9 +24,15 @@ static unsigned char const padding_string[] = { | ||
| 23 | 0x2f, 0x0c, 0xa9, 0xfe, 0x64, 0x53, 0x69, 0x7a | 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 | static unsigned int const key_bytes = 32; | 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 | int | 36 | int |
| 30 | QPDF::EncryptionData::getV() const | 37 | QPDF::EncryptionData::getV() const |
| 31 | { | 38 | { |
| @@ -120,7 +127,7 @@ QPDF::EncryptionData::setV5EncryptionParameters( | @@ -120,7 +127,7 @@ QPDF::EncryptionData::setV5EncryptionParameters( | ||
| 120 | } | 127 | } |
| 121 | 128 | ||
| 122 | static void | 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 | int password_bytes = std::min(key_bytes, (unsigned int)password.length()); | 132 | int password_bytes = std::min(key_bytes, (unsigned int)password.length()); |
| 126 | int pad_bytes = key_bytes - password_bytes; | 133 | int pad_bytes = key_bytes - password_bytes; |
| @@ -159,13 +166,19 @@ QPDF::trim_user_password(std::string& user_password) | @@ -159,13 +166,19 @@ QPDF::trim_user_password(std::string& user_password) | ||
| 159 | } | 166 | } |
| 160 | 167 | ||
| 161 | static std::string | 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 | char k1[key_bytes]; | 171 | char k1[key_bytes]; |
| 165 | - pad_or_truncate_password(password, k1); | 172 | + pad_or_truncate_password_V4(password, k1); |
| 166 | return std::string(k1, key_bytes); | 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 | static void | 182 | static void |
| 170 | iterate_md5_digest(MD5& md5, MD5::Digest& digest, int iterations) | 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,15 +212,146 @@ iterate_rc4(unsigned char* data, int data_len, | ||
| 199 | delete [] key; | 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 | std::string | 339 | std::string |
| 203 | QPDF::compute_data_key(std::string const& encryption_key, | 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 | // Algorithm 3.1 from the PDF 1.7 Reference Manual | 344 | // Algorithm 3.1 from the PDF 1.7 Reference Manual |
| 208 | 345 | ||
| 209 | std::string result = encryption_key; | 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 | // Append low three bytes of object ID and low two bytes of generation | 355 | // Append low three bytes of object ID and low two bytes of generation |
| 212 | result += (char) (objid & 0xff); | 356 | result += (char) (objid & 0xff); |
| 213 | result += (char) ((objid >> 8) & 0xff); | 357 | result += (char) ((objid >> 8) & 0xff); |
| @@ -231,11 +375,37 @@ std::string | @@ -231,11 +375,37 @@ std::string | ||
| 231 | QPDF::compute_encryption_key( | 375 | QPDF::compute_encryption_key( |
| 232 | std::string const& password, EncryptionData const& data) | 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 | // Algorithm 3.2 from the PDF 1.7 Reference Manual | 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 | MD5 md5; | 406 | MD5 md5; |
| 237 | md5.encodeDataIncrementally( | 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 | md5.encodeDataIncrementally(data.getO().c_str(), key_bytes); | 409 | md5.encodeDataIncrementally(data.getO().c_str(), key_bytes); |
| 240 | char pbytes[4]; | 410 | char pbytes[4]; |
| 241 | int P = data.getP(); | 411 | int P = data.getP(); |
| @@ -261,8 +431,13 @@ static void | @@ -261,8 +431,13 @@ static void | ||
| 261 | compute_O_rc4_key(std::string const& user_password, | 431 | compute_O_rc4_key(std::string const& user_password, |
| 262 | std::string const& owner_password, | 432 | std::string const& owner_password, |
| 263 | QPDF::EncryptionData const& data, | 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 | std::string password = owner_password; | 441 | std::string password = owner_password; |
| 267 | if (password.empty()) | 442 | if (password.empty()) |
| 268 | { | 443 | { |
| @@ -270,10 +445,10 @@ compute_O_rc4_key(std::string const& user_password, | @@ -270,10 +445,10 @@ compute_O_rc4_key(std::string const& user_password, | ||
| 270 | } | 445 | } |
| 271 | MD5 md5; | 446 | MD5 md5; |
| 272 | md5.encodeDataIncrementally( | 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 | MD5::Digest digest; | 449 | MD5::Digest digest; |
| 275 | iterate_md5_digest(md5, digest, ((data.getR() >= 3) ? 50 : 0)); | 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 | static std::string | 454 | static std::string |
| @@ -283,11 +458,11 @@ compute_O_value(std::string const& user_password, | @@ -283,11 +458,11 @@ compute_O_value(std::string const& user_password, | ||
| 283 | { | 458 | { |
| 284 | // Algorithm 3.3 from the PDF 1.7 Reference Manual | 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 | compute_O_rc4_key(user_password, owner_password, data, O_key); | 462 | compute_O_rc4_key(user_password, owner_password, data, O_key); |
| 288 | 463 | ||
| 289 | char upass[key_bytes]; | 464 | char upass[key_bytes]; |
| 290 | - pad_or_truncate_password(user_password, upass); | 465 | + pad_or_truncate_password_V4(user_password, upass); |
| 291 | iterate_rc4((unsigned char*) upass, key_bytes, | 466 | iterate_rc4((unsigned char*) upass, key_bytes, |
| 292 | O_key, data.getLengthBytes(), | 467 | O_key, data.getLengthBytes(), |
| 293 | (data.getR() >= 3) ? 20 : 1, false); | 468 | (data.getR() >= 3) ? 20 : 1, false); |
| @@ -303,7 +478,7 @@ compute_U_value_R2(std::string const& user_password, | @@ -303,7 +478,7 @@ compute_U_value_R2(std::string const& user_password, | ||
| 303 | 478 | ||
| 304 | std::string k1 = QPDF::compute_encryption_key(user_password, data); | 479 | std::string k1 = QPDF::compute_encryption_key(user_password, data); |
| 305 | char udata[key_bytes]; | 480 | char udata[key_bytes]; |
| 306 | - pad_or_truncate_password("", udata); | 481 | + pad_or_truncate_password_V4("", udata); |
| 307 | iterate_rc4((unsigned char*) udata, key_bytes, | 482 | iterate_rc4((unsigned char*) udata, key_bytes, |
| 308 | (unsigned char*)k1.c_str(), data.getLengthBytes(), 1, false); | 483 | (unsigned char*)k1.c_str(), data.getLengthBytes(), 1, false); |
| 309 | return std::string(udata, key_bytes); | 484 | return std::string(udata, key_bytes); |
| @@ -319,7 +494,7 @@ compute_U_value_R3(std::string const& user_password, | @@ -319,7 +494,7 @@ compute_U_value_R3(std::string const& user_password, | ||
| 319 | std::string k1 = QPDF::compute_encryption_key(user_password, data); | 494 | std::string k1 = QPDF::compute_encryption_key(user_password, data); |
| 320 | MD5 md5; | 495 | MD5 md5; |
| 321 | md5.encodeDataIncrementally( | 496 | md5.encodeDataIncrementally( |
| 322 | - pad_or_truncate_password("").c_str(), key_bytes); | 497 | + pad_or_truncate_password_V4("").c_str(), key_bytes); |
| 323 | md5.encodeDataIncrementally(data.getId1().c_str(), | 498 | md5.encodeDataIncrementally(data.getId1().c_str(), |
| 324 | (int)data.getId1().length()); | 499 | (int)data.getId1().length()); |
| 325 | MD5::Digest digest; | 500 | MD5::Digest digest; |
| @@ -350,40 +525,214 @@ compute_U_value(std::string const& user_password, | @@ -350,40 +525,214 @@ compute_U_value(std::string const& user_password, | ||
| 350 | } | 525 | } |
| 351 | 526 | ||
| 352 | static bool | 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 | // Algorithm 3.6 from the PDF 1.7 Reference Manual | 531 | // Algorithm 3.6 from the PDF 1.7 Reference Manual |
| 357 | 532 | ||
| 358 | std::string u_value = compute_U_value(user_password, data); | 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 | return (memcmp(data.getU().c_str(), u_value.c_str(), to_compare) == 0); | 536 | return (memcmp(data.getU().c_str(), u_value.c_str(), to_compare) == 0); |
| 361 | } | 537 | } |
| 362 | 538 | ||
| 363 | static bool | 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 | // Algorithm 3.7 from the PDF 1.7 Reference Manual | 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 | compute_O_rc4_key(user_password, owner_password, data, key); | 573 | compute_O_rc4_key(user_password, owner_password, data, key); |
| 372 | unsigned char O_data[key_bytes]; | 574 | unsigned char O_data[key_bytes]; |
| 373 | memcpy(O_data, (unsigned char*) data.getO().c_str(), key_bytes); | 575 | memcpy(O_data, (unsigned char*) data.getO().c_str(), key_bytes); |
| 374 | iterate_rc4(O_data, key_bytes, key, data.getLengthBytes(), | 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 | std::string new_user_password = | 578 | std::string new_user_password = |
| 377 | - std::string((char*)O_data, key_bytes); | 579 | + std::string((char*)O_data, key_bytes); |
| 378 | bool result = false; | 580 | bool result = false; |
| 379 | if (check_user_password(new_user_password, data)) | 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 | return result; | 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 | QPDF::encryption_method_e | 736 | QPDF::encryption_method_e |
| 388 | QPDF::interpretCF(QPDFObjectHandle cf) | 737 | QPDF::interpretCF(QPDFObjectHandle cf) |
| 389 | { | 738 | { |
| @@ -487,29 +836,70 @@ QPDF::initializeEncryption() | @@ -487,29 +836,70 @@ QPDF::initializeEncryption() | ||
| 487 | std::string U = encryption_dict.getKey("/U").getStringValue(); | 836 | std::string U = encryption_dict.getKey("/U").getStringValue(); |
| 488 | unsigned int P = (unsigned int) encryption_dict.getKey("/P").getIntValue(); | 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 | throw QPDFExc(qpdf_e_unsupported, this->file->getName(), | 844 | throw QPDFExc(qpdf_e_unsupported, this->file->getName(), |
| 494 | "encryption dictionary", this->file->getLastOffset(), | 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 | this->encryption_V = V; | 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 | int Length = 40; | 898 | int Length = 40; |
| 509 | if (encryption_dict.getKey("/Length").isInteger()) | 899 | if (encryption_dict.getKey("/Length").isInteger()) |
| 510 | { | 900 | { |
| 511 | Length = encryption_dict.getKey("/Length").getIntValue(); | 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 | throw QPDFExc(qpdf_e_damaged_pdf, this->file->getName(), | 904 | throw QPDFExc(qpdf_e_damaged_pdf, this->file->getName(), |
| 515 | "encryption dictionary", this->file->getLastOffset(), | 905 | "encryption dictionary", this->file->getLastOffset(), |
| @@ -524,7 +914,7 @@ QPDF::initializeEncryption() | @@ -524,7 +914,7 @@ QPDF::initializeEncryption() | ||
| 524 | encryption_dict.getKey("/EncryptMetadata").getBoolValue(); | 914 | encryption_dict.getKey("/EncryptMetadata").getBoolValue(); |
| 525 | } | 915 | } |
| 526 | 916 | ||
| 527 | - if (V == 4) | 917 | + if ((V == 4) || (V == 5)) |
| 528 | { | 918 | { |
| 529 | QPDFObjectHandle CF = encryption_dict.getKey("/CF"); | 919 | QPDFObjectHandle CF = encryption_dict.getKey("/CF"); |
| 530 | std::set<std::string> keys = CF.getKeys(); | 920 | std::set<std::string> keys = CF.getKeys(); |
| @@ -549,6 +939,11 @@ QPDF::initializeEncryption() | @@ -549,6 +939,11 @@ QPDF::initializeEncryption() | ||
| 549 | QTC::TC("qpdf", "QPDF_encryption CFM AESV2"); | 939 | QTC::TC("qpdf", "QPDF_encryption CFM AESV2"); |
| 550 | method = e_aes; | 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 | else | 947 | else |
| 553 | { | 948 | { |
| 554 | // Don't complain now -- maybe we won't need | 949 | // Don't complain now -- maybe we won't need |
| @@ -574,13 +969,14 @@ QPDF::initializeEncryption() | @@ -574,13 +969,14 @@ QPDF::initializeEncryption() | ||
| 574 | this->cf_file = this->cf_stream; | 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 | id1, this->encrypt_metadata); | 974 | id1, this->encrypt_metadata); |
| 579 | if (check_owner_password( | 975 | if (check_owner_password( |
| 580 | this->user_password, this->provided_password, data)) | 976 | this->user_password, this->provided_password, data)) |
| 581 | { | 977 | { |
| 582 | // password supplied was owner password; user_password has | 978 | // password supplied was owner password; user_password has |
| 583 | - // been initialized | 979 | + // been initialized for V < 5 |
| 584 | } | 980 | } |
| 585 | else if (check_user_password(this->provided_password, data)) | 981 | else if (check_user_password(this->provided_password, data)) |
| 586 | { | 982 | { |
| @@ -592,7 +988,30 @@ QPDF::initializeEncryption() | @@ -592,7 +988,30 @@ QPDF::initializeEncryption() | ||
| 592 | "", 0, "invalid password"); | 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 | std::string | 1017 | std::string |
| @@ -608,7 +1027,8 @@ QPDF::getKeyForObject(int objid, int generation, bool use_aes) | @@ -608,7 +1027,8 @@ QPDF::getKeyForObject(int objid, int generation, bool use_aes) | ||
| 608 | (generation == this->cached_key_generation))) | 1027 | (generation == this->cached_key_generation))) |
| 609 | { | 1028 | { |
| 610 | this->cached_object_encryption_key = | 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 | this->cached_key_objid = objid; | 1032 | this->cached_key_objid = objid; |
| 613 | this->cached_key_generation = generation; | 1033 | this->cached_key_generation = generation; |
| 614 | } | 1034 | } |
| @@ -624,7 +1044,7 @@ QPDF::decryptString(std::string& str, int objid, int generation) | @@ -624,7 +1044,7 @@ QPDF::decryptString(std::string& str, int objid, int generation) | ||
| 624 | return; | 1044 | return; |
| 625 | } | 1045 | } |
| 626 | bool use_aes = false; | 1046 | bool use_aes = false; |
| 627 | - if (this->encryption_V == 4) | 1047 | + if (this->encryption_V >= 4) |
| 628 | { | 1048 | { |
| 629 | switch (this->cf_string) | 1049 | switch (this->cf_string) |
| 630 | { | 1050 | { |
| @@ -635,6 +1055,10 @@ QPDF::decryptString(std::string& str, int objid, int generation) | @@ -635,6 +1055,10 @@ QPDF::decryptString(std::string& str, int objid, int generation) | ||
| 635 | use_aes = true; | 1055 | use_aes = true; |
| 636 | break; | 1056 | break; |
| 637 | 1057 | ||
| 1058 | + case e_aesv3: | ||
| 1059 | + use_aes = true; | ||
| 1060 | + break; | ||
| 1061 | + | ||
| 638 | case e_rc4: | 1062 | case e_rc4: |
| 639 | break; | 1063 | break; |
| 640 | 1064 | ||
| @@ -710,7 +1134,7 @@ QPDF::decryptStream(Pipeline*& pipeline, int objid, int generation, | @@ -710,7 +1134,7 @@ QPDF::decryptStream(Pipeline*& pipeline, int objid, int generation, | ||
| 710 | return; | 1134 | return; |
| 711 | } | 1135 | } |
| 712 | bool use_aes = false; | 1136 | bool use_aes = false; |
| 713 | - if (this->encryption_V == 4) | 1137 | + if (this->encryption_V >= 4) |
| 714 | { | 1138 | { |
| 715 | encryption_method_e method = e_unknown; | 1139 | encryption_method_e method = e_unknown; |
| 716 | std::string method_source = "/StmF from /Encrypt dictionary"; | 1140 | std::string method_source = "/StmF from /Encrypt dictionary"; |
| @@ -747,7 +1171,7 @@ QPDF::decryptStream(Pipeline*& pipeline, int objid, int generation, | @@ -747,7 +1171,7 @@ QPDF::decryptStream(Pipeline*& pipeline, int objid, int generation, | ||
| 747 | if (crypt_params.isDictionary() && | 1171 | if (crypt_params.isDictionary() && |
| 748 | crypt_params.getKey("/Name").isName()) | 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 | method = interpretCF( | 1175 | method = interpretCF( |
| 752 | crypt_params.getKey("/Name")); | 1176 | crypt_params.getKey("/Name")); |
| 753 | method_source = "stream's Crypt " | 1177 | method_source = "stream's Crypt " |
| @@ -790,6 +1214,10 @@ QPDF::decryptStream(Pipeline*& pipeline, int objid, int generation, | @@ -790,6 +1214,10 @@ QPDF::decryptStream(Pipeline*& pipeline, int objid, int generation, | ||
| 790 | use_aes = true; | 1214 | use_aes = true; |
| 791 | break; | 1215 | break; |
| 792 | 1216 | ||
| 1217 | + case e_aesv3: | ||
| 1218 | + use_aes = true; | ||
| 1219 | + break; | ||
| 1220 | + | ||
| 793 | case e_rc4: | 1221 | case e_rc4: |
| 794 | break; | 1222 | break; |
| 795 | 1223 | ||
| @@ -831,6 +1259,11 @@ QPDF::compute_encryption_O_U( | @@ -831,6 +1259,11 @@ QPDF::compute_encryption_O_U( | ||
| 831 | int V, int R, int key_len, int P, bool encrypt_metadata, | 1259 | int V, int R, int key_len, int P, bool encrypt_metadata, |
| 832 | std::string const& id1, std::string& O, std::string& U) | 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 | EncryptionData data(V, R, key_len, P, "", "", "", "", "", | 1267 | EncryptionData data(V, R, key_len, P, "", "", "", "", "", |
| 835 | id1, encrypt_metadata); | 1268 | id1, encrypt_metadata); |
| 836 | data.setO(compute_O_value(user_password, owner_password, data)); | 1269 | data.setO(compute_O_value(user_password, owner_password, data)); |
| @@ -839,6 +1272,26 @@ QPDF::compute_encryption_O_U( | @@ -839,6 +1272,26 @@ QPDF::compute_encryption_O_U( | ||
| 839 | U = data.getU(); | 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 | std::string const& | 1295 | std::string const& |
| 843 | QPDF::getPaddedUserPassword() const | 1296 | QPDF::getPaddedUserPassword() const |
| 844 | { | 1297 | { |
| @@ -853,6 +1306,12 @@ QPDF::getTrimmedUserPassword() const | @@ -853,6 +1306,12 @@ QPDF::getTrimmedUserPassword() const | ||
| 853 | return result; | 1306 | return result; |
| 854 | } | 1307 | } |
| 855 | 1308 | ||
| 1309 | +std::string | ||
| 1310 | +QPDF::getEncryptionKey() const | ||
| 1311 | +{ | ||
| 1312 | + return this->encryption_key; | ||
| 1313 | +} | ||
| 1314 | + | ||
| 856 | bool | 1315 | bool |
| 857 | QPDF::isEncrypted() const | 1316 | QPDF::isEncrypted() const |
| 858 | { | 1317 | { |
qpdf/qpdf.cc
| @@ -97,7 +97,7 @@ Note that -- terminates parsing of encryption flags.\n\ | @@ -97,7 +97,7 @@ Note that -- terminates parsing of encryption flags.\n\ | ||
| 97 | Either or both of the user password and the owner password may be\n\ | 97 | Either or both of the user password and the owner password may be\n\ |
| 98 | empty strings.\n\ | 98 | empty strings.\n\ |
| 99 | \n\ | 99 | \n\ |
| 100 | -key-length may be 40 or 128\n\ | 100 | +key-length may be 40, 128, or 256\n\ |
| 101 | \n\ | 101 | \n\ |
| 102 | Additional flags are dependent upon key length.\n\ | 102 | Additional flags are dependent upon key length.\n\ |
| 103 | \n\ | 103 | \n\ |
| @@ -118,6 +118,11 @@ Additional flags are dependent upon key length.\n\ | @@ -118,6 +118,11 @@ Additional flags are dependent upon key length.\n\ | ||
| 118 | --use-aes=[yn] indicates whether to use AES encryption\n\ | 118 | --use-aes=[yn] indicates whether to use AES encryption\n\ |
| 119 | --force-V4 forces use of V=4 encryption handler\n\ | 119 | --force-V4 forces use of V=4 encryption handler\n\ |
| 120 | \n\ | 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 | print-opt may be:\n\ | 126 | print-opt may be:\n\ |
| 122 | \n\ | 127 | \n\ |
| 123 | full allow full printing\n\ | 128 | full allow full printing\n\ |
| @@ -283,6 +288,9 @@ static std::string show_encryption_method(QPDF::encryption_method_e method) | @@ -283,6 +288,9 @@ static std::string show_encryption_method(QPDF::encryption_method_e method) | ||
| 283 | case QPDF::e_aes: | 288 | case QPDF::e_aes: |
| 284 | result = "AESv2"; | 289 | result = "AESv2"; |
| 285 | break; | 290 | break; |
| 291 | + case QPDF::e_aesv3: | ||
| 292 | + result = "AESv3"; | ||
| 293 | + break; | ||
| 286 | // no default so gcc will warn for missing case | 294 | // no default so gcc will warn for missing case |
| 287 | } | 295 | } |
| 288 | return result; | 296 | return result; |
| @@ -485,7 +493,8 @@ parse_encrypt_options( | @@ -485,7 +493,8 @@ parse_encrypt_options( | ||
| 485 | bool& r2_print, bool& r2_modify, bool& r2_extract, bool& r2_annotate, | 493 | bool& r2_print, bool& r2_modify, bool& r2_extract, bool& r2_annotate, |
| 486 | bool& r3_accessibility, bool& r3_extract, | 494 | bool& r3_accessibility, bool& r3_extract, |
| 487 | qpdf_r3_print_e& r3_print, qpdf_r3_modify_e& r3_modify, | 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 | if (cur_arg + 3 >= argc) | 499 | if (cur_arg + 3 >= argc) |
| 491 | { | 500 | { |
| @@ -502,9 +511,14 @@ parse_encrypt_options( | @@ -502,9 +511,14 @@ parse_encrypt_options( | ||
| 502 | { | 511 | { |
| 503 | keylen = 128; | 512 | keylen = 128; |
| 504 | } | 513 | } |
| 514 | + else if (len_str == "256") | ||
| 515 | + { | ||
| 516 | + keylen = 256; | ||
| 517 | + use_aes = true; | ||
| 518 | + } | ||
| 505 | else | 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 | while (1) | 523 | while (1) |
| 510 | { | 524 | { |
| @@ -736,15 +750,30 @@ parse_encrypt_options( | @@ -736,15 +750,30 @@ parse_encrypt_options( | ||
| 736 | { | 750 | { |
| 737 | usage("--force-V4 does not take a parameter"); | 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 | else | 757 | else |
| 744 | { | 758 | { |
| 745 | force_V4 = true; | 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 | else if (strcmp(arg, "use-aes") == 0) | 777 | else if (strcmp(arg, "use-aes") == 0) |
| 749 | { | 778 | { |
| 750 | if (parameter == 0) | 779 | if (parameter == 0) |
| @@ -765,10 +794,16 @@ parse_encrypt_options( | @@ -765,10 +794,16 @@ parse_encrypt_options( | ||
| 765 | { | 794 | { |
| 766 | usage("invalid -use-aes parameter"); | 795 | usage("invalid -use-aes parameter"); |
| 767 | } | 796 | } |
| 768 | - if (keylen == 40) | 797 | + if ((keylen == 40) && result) |
| 769 | { | 798 | { |
| 770 | usage("use-aes is invalid for 40-bit keys"); | 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 | else | 807 | else |
| 773 | { | 808 | { |
| 774 | use_aes = result; | 809 | use_aes = result; |
| @@ -921,6 +956,7 @@ int main(int argc, char* argv[]) | @@ -921,6 +956,7 @@ int main(int argc, char* argv[]) | ||
| 921 | qpdf_r3_print_e r3_print = qpdf_r3p_full; | 956 | qpdf_r3_print_e r3_print = qpdf_r3p_full; |
| 922 | qpdf_r3_modify_e r3_modify = qpdf_r3m_all; | 957 | qpdf_r3_modify_e r3_modify = qpdf_r3m_all; |
| 923 | bool force_V4 = false; | 958 | bool force_V4 = false; |
| 959 | + bool force_R5 = false; | ||
| 924 | bool cleartext_metadata = false; | 960 | bool cleartext_metadata = false; |
| 925 | bool use_aes = false; | 961 | bool use_aes = false; |
| 926 | 962 | ||
| @@ -1004,7 +1040,7 @@ int main(int argc, char* argv[]) | @@ -1004,7 +1040,7 @@ int main(int argc, char* argv[]) | ||
| 1004 | user_password, owner_password, keylen, | 1040 | user_password, owner_password, keylen, |
| 1005 | r2_print, r2_modify, r2_extract, r2_annotate, | 1041 | r2_print, r2_modify, r2_extract, r2_annotate, |
| 1006 | r3_accessibility, r3_extract, r3_print, r3_modify, | 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 | encrypt = true; | 1044 | encrypt = true; |
| 1009 | decrypt = false; | 1045 | decrypt = false; |
| 1010 | copy_encryption = false; | 1046 | copy_encryption = false; |
| @@ -1612,6 +1648,23 @@ int main(int argc, char* argv[]) | @@ -1612,6 +1648,23 @@ int main(int argc, char* argv[]) | ||
| 1612 | r3_accessibility, r3_extract, r3_print, r3_modify); | 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 | else | 1668 | else |
| 1616 | { | 1669 | { |
| 1617 | throw std::logic_error("bad encryption keylen"); | 1670 | throw std::logic_error("bad encryption keylen"); |
qpdf/qpdf.testcov
| @@ -243,6 +243,7 @@ QPDFWriter extra header text add newline 0 | @@ -243,6 +243,7 @@ QPDFWriter extra header text add newline 0 | ||
| 243 | QPDF bogus 0 offset 0 | 243 | QPDF bogus 0 offset 0 |
| 244 | QPDF global offset 0 | 244 | QPDF global offset 0 |
| 245 | QPDFWriter make stream key direct 0 | 245 | QPDFWriter make stream key direct 0 |
| 246 | +QPDFWriter copy V5 0 | ||
| 246 | QPDFWriter increasing extension level 0 | 247 | QPDFWriter increasing extension level 0 |
| 247 | QPDFWriter make Extensions direct 0 | 248 | QPDFWriter make Extensions direct 0 |
| 248 | QPDFWriter make ADBE direct 1 | 249 | QPDFWriter make ADBE direct 1 |
| @@ -253,3 +254,5 @@ QPDFWriter remove existing Extensions 0 | @@ -253,3 +254,5 @@ QPDFWriter remove existing Extensions 0 | ||
| 253 | QPDFWriter skip Extensions 0 | 254 | QPDFWriter skip Extensions 0 |
| 254 | QPDFWriter preserve ADBE 0 | 255 | QPDFWriter preserve ADBE 0 |
| 255 | QPDF_encryption skip 0x28 0 | 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,6 +1250,10 @@ $td->notify("--- Encryption Tests ---"); | ||
| 1250 | # resulting files were saved and manually checked with Acrobat 5.0 to | 1250 | # resulting files were saved and manually checked with Acrobat 5.0 to |
| 1251 | # ensure that the security settings were as intended. | 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 | # Values: basename, password, encryption flags, /P Encrypt key, | 1257 | # Values: basename, password, encryption flags, /P Encrypt key, |
| 1254 | # extract-for-accessibility, extract-for-any-purpose, | 1258 | # extract-for-accessibility, extract-for-any-purpose, |
| 1255 | # print-low-res, print-high-res, modify-assembly, modify-forms, | 1259 | # print-low-res, print-high-res, modify-assembly, modify-forms, |
| @@ -1293,9 +1297,25 @@ my @encrypted_files = | @@ -1293,9 +1297,25 @@ my @encrypted_files = | ||
| 1293 | '', -4, | 1297 | '', -4, |
| 1294 | 1, 1, 1, 1, 1, 1, 1, 1, 1], | 1298 | 1, 1, 1, 1, 1, 1, 1, 1, 1], |
| 1295 | ['long-password', 'asdf asdf asdf asdf asdf asdf qwer'], | 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 | $td->runtest("encrypted file", | 1320 | $td->runtest("encrypted file", |
| 1301 | {$td->COMMAND => "test_driver 2 U25A0.pdf"}, | 1321 | {$td->COMMAND => "test_driver 2 U25A0.pdf"}, |
| @@ -1312,6 +1332,19 @@ $td->runtest("recheck encrypted file", | @@ -1312,6 +1332,19 @@ $td->runtest("recheck encrypted file", | ||
| 1312 | $td->EXIT_STATUS => 0}, | 1332 | $td->EXIT_STATUS => 0}, |
| 1313 | $td->NORMALIZE_NEWLINES); | 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 | foreach my $d (@encrypted_files) | 1348 | foreach my $d (@encrypted_files) |
| 1316 | { | 1349 | { |
| 1317 | my ($file, $pass, $xeflags, $P, | 1350 | my ($file, $pass, $xeflags, $P, |
| @@ -1330,17 +1363,26 @@ foreach my $d (@encrypted_files) | @@ -1330,17 +1363,26 @@ foreach my $d (@encrypted_files) | ||
| 1330 | "modify annotations: " . &$f($modifyannot) . "\n" . | 1363 | "modify annotations: " . &$f($modifyannot) . "\n" . |
| 1331 | "modify other: " . &$f($modifyother) . "\n" . | 1364 | "modify other: " . &$f($modifyother) . "\n" . |
| 1332 | "modify anything: " . &$f($modifyall) . "\n"; | 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 | # Test writing to stdout | 1374 | # Test writing to stdout |
| 1335 | $td->runtest("decrypt $file", | 1375 | $td->runtest("decrypt $file", |
| 1336 | {$td->COMMAND => | 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 | " --password=\"$pass\" enc-$file.pdf -" . | 1379 | " --password=\"$pass\" enc-$file.pdf -" . |
| 1339 | " > $file.enc"}, | 1380 | " > $file.enc"}, |
| 1340 | {$td->STRING => "", | 1381 | {$td->STRING => "", |
| 1341 | $td->EXIT_STATUS => 0}); | 1382 | $td->EXIT_STATUS => 0}); |
| 1342 | - if ($file eq 'base') | 1383 | + if ($file =~ m/base$/) |
| 1343 | { | 1384 | { |
| 1385 | + $enc_base = $file; | ||
| 1344 | $td->runtest("check ID", | 1386 | $td->runtest("check ID", |
| 1345 | {$td->COMMAND => "perl check-ID.pl $file.enc"}, | 1387 | {$td->COMMAND => "perl check-ID.pl $file.enc"}, |
| 1346 | {$td->STRING => "ID okay\n", | 1388 | {$td->STRING => "ID okay\n", |
| @@ -1350,20 +1392,27 @@ foreach my $d (@encrypted_files) | @@ -1350,20 +1392,27 @@ foreach my $d (@encrypted_files) | ||
| 1350 | else | 1392 | else |
| 1351 | { | 1393 | { |
| 1352 | $td->runtest("check against base", | 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 | {$td->STRING => "okay\n", | 1397 | {$td->STRING => "okay\n", |
| 1355 | $td->EXIT_STATUS => 0}, | 1398 | $td->EXIT_STATUS => 0}, |
| 1356 | $td->NORMALIZE_NEWLINES); | 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 | my $R = $1; | 1403 | my $R = $1; |
| 1361 | my $V = $2; | 1404 | my $V = $2; |
| 1362 | my $upass = $3 || ""; | 1405 | my $upass = $3 || ""; |
| 1363 | my $opass = $4 || ""; | 1406 | my $opass = $4 || ""; |
| 1364 | - my $bits = (($V == 2) ? 128 : 40); | 1407 | + my $bits = (($V == 5) ? 256 : ($V == 2) ? 128 : 40); |
| 1365 | 1408 | ||
| 1366 | my $eflags = "-encrypt \"$upass\" \"$opass\" $bits $xeflags --"; | 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 | $td->runtest("encrypt $file", | 1416 | $td->runtest("encrypt $file", |
| 1368 | {$td->COMMAND => | 1417 | {$td->COMMAND => |
| 1369 | "qpdf --static-id --no-original-object-ids -qdf" . | 1418 | "qpdf --static-id --no-original-object-ids -qdf" . |
| @@ -1488,7 +1537,7 @@ $td->runtest("check linearization", | @@ -1488,7 +1537,7 @@ $td->runtest("check linearization", | ||
| 1488 | $td->NORMALIZE_NEWLINES); | 1537 | $td->NORMALIZE_NEWLINES); |
| 1489 | 1538 | ||
| 1490 | # Test AES encryption in various ways. | 1539 | # Test AES encryption in various ways. |
| 1491 | -$n_tests += 14; | 1540 | +$n_tests += 18; |
| 1492 | $td->runtest("encrypt with AES", | 1541 | $td->runtest("encrypt with AES", |
| 1493 | {$td->COMMAND => "qpdf --encrypt '' '' 128 --use-aes=y --" . | 1542 | {$td->COMMAND => "qpdf --encrypt '' '' 128 --use-aes=y --" . |
| 1494 | " enc-base.pdf a.pdf"}, | 1543 | " enc-base.pdf a.pdf"}, |
| @@ -1548,6 +1597,24 @@ $td->runtest("make sure there is no xref stream", | @@ -1548,6 +1597,24 @@ $td->runtest("make sure there is no xref stream", | ||
| 1548 | {$td->COMMAND => "grep /ObjStm b.pdf | wc -l"}, | 1597 | {$td->COMMAND => "grep /ObjStm b.pdf | wc -l"}, |
| 1549 | {$td->REGEXP => "\\s*0\\s*", $td->EXIT_STATUS => 0}, | 1598 | {$td->REGEXP => "\\s*0\\s*", $td->EXIT_STATUS => 0}, |
| 1550 | $td->NORMALIZE_NEWLINES); | 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 | # Look at some actual V4 files | 1619 | # Look at some actual V4 files |
| 1553 | $n_tests += 14; | 1620 | $n_tests += 14; |
| @@ -1629,6 +1696,36 @@ $td->runtest("compare qdf", | @@ -1629,6 +1696,36 @@ $td->runtest("compare qdf", | ||
| 1629 | {$td->STRING => "okay\n", $td->EXIT_STATUS => 0}, | 1696 | {$td->STRING => "okay\n", $td->EXIT_STATUS => 0}, |
| 1630 | $td->NORMALIZE_NEWLINES); | 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 | show_ntests(); | 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,7 +112,12 @@ void runtest(int n, char const* filename1, char const* arg2) | ||
| 112 | { | 112 | { |
| 113 | pdf.setAttemptRecovery(false); | 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 | if (n % 4 == 0) | 122 | if (n % 4 == 0) |
| 118 | { | 123 | { |
| @@ -1150,6 +1155,65 @@ void runtest(int n, char const* filename1, char const* arg2) | @@ -1150,6 +1155,65 @@ void runtest(int n, char const* filename1, char const* arg2) | ||
| 1150 | << "extension level: " << pdf.getExtensionLevel() << std::endl | 1155 | << "extension level: " << pdf.getExtensionLevel() << std::endl |
| 1151 | << pdf.getRoot().getKey("/Extensions").unparse() << std::endl; | 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 | else | 1217 | else |
| 1154 | { | 1218 | { |
| 1155 | throw std::runtime_error(std::string("invalid test ") + | 1219 | throw std::runtime_error(std::string("invalid test ") + |