Commit 8ccd3a8a89d95ae0613679ba7b394a4f87699e12
1 parent
2213ed0c
Mark weak encryption with API changes (fixes #576)
Showing
14 changed files
with
289 additions
and
71 deletions
ChangeLog
| 1 | 2022-04-30 Jay Berkenbilt <ejb@ql.org> | 1 | 2022-04-30 Jay Berkenbilt <ejb@ql.org> |
| 2 | 2 | ||
| 3 | + * QPDFWriter: change encryption API calls | ||
| 4 | + - Remove deprecated versions of setR*EncryptionParameters | ||
| 5 | + methods from before qpdf 8.4.0 | ||
| 6 | + - Replace setR2EncryptionParameters with | ||
| 7 | + setR2EncryptionParametersInsecure | ||
| 8 | + - Replace setR3EncryptionParameters with | ||
| 9 | + setR3EncryptionParametersInsecure | ||
| 10 | + - Replace setR4EncryptionParameters with | ||
| 11 | + setR4EncryptionParametersInsecure | ||
| 12 | + | ||
| 13 | + * C API: change encryption API calls to match C++ interface | ||
| 14 | + - Remove pre-8.4.0 functions: | ||
| 15 | + - qpdf_set_r3_encryption_parameters | ||
| 16 | + - qpdf_set_r4_encryption_parameters | ||
| 17 | + - qpdf_set_r5_encryption_parameters | ||
| 18 | + - qpdf_set_r6_encryption_parameters | ||
| 19 | + - Add "_insecure" to insecure encryption triggers: | ||
| 20 | + - Replace void qpdf_set_r2_encryption_parameters | ||
| 21 | + with qpdf_set_r2_encryption_parameters_insecure | ||
| 22 | + - Replace void qpdf_set_r3_encryption_parameters2 | ||
| 23 | + with qpdf_set_r3_encryption_parameters_insecure | ||
| 24 | + - Replace void qpdf_set_r4_encryption_parameters2 | ||
| 25 | + with qpdf_set_r4_encryption_parameters_insecure | ||
| 26 | + | ||
| 3 | * Make attempting to write encrypted files that use RC4 (40-bit or | 27 | * Make attempting to write encrypted files that use RC4 (40-bit or |
| 4 | 128-bit without AES) an error rather than a warning when | 28 | 128-bit without AES) an error rather than a warning when |
| 5 | - --allow-weak-crypto is not specified. | 29 | + --allow-weak-crypto is not specified. Fixes #576. |
| 6 | 30 | ||
| 7 | 2022-04-29 Jay Berkenbilt <ejb@ql.org> | 31 | 2022-04-29 Jay Berkenbilt <ejb@ql.org> |
| 8 | 32 |
TODO
| @@ -475,18 +475,6 @@ Comments appear in the code prefixed by "ABI". Always Search for ABI | @@ -475,18 +475,6 @@ Comments appear in the code prefixed by "ABI". Always Search for ABI | ||
| 475 | in source and header files to find items not listed here. Also search | 475 | in source and header files to find items not listed here. Also search |
| 476 | for "[[deprecated" to find deprecated APIs that can be removed. | 476 | for "[[deprecated" to find deprecated APIs that can be removed. |
| 477 | 477 | ||
| 478 | -* Deal with weak cryptographic algorithms: | ||
| 479 | - * Github issue #576 | ||
| 480 | - * Add something to QPDFWriter that you must call in order to allow | ||
| 481 | - creation of files with insecure crypto. Maybe | ||
| 482 | - QPDFWriter::allowWeakCrypto. Call this when --allow-weak-crypto is | ||
| 483 | - passed and probably also when copying encryption by default from | ||
| 484 | - an input file. There should be some API change so that, when | ||
| 485 | - people recompile with qpdf 11, their code won't suddenly stop | ||
| 486 | - working. Getting this right will take careful consideration of the | ||
| 487 | - developer and user experience. We don't want to create a situation | ||
| 488 | - where exactly the same code fails to work in 11 but worked on 10. | ||
| 489 | - See #576 for latest notes. | ||
| 490 | 478 | ||
| 491 | Page splitting/merging | 479 | Page splitting/merging |
| 492 | ====================== | 480 | ====================== |
fuzz/qpdf_fuzzer.cc
| @@ -107,7 +107,7 @@ FuzzHelper::testWrite() | @@ -107,7 +107,7 @@ FuzzHelper::testWrite() | ||
| 107 | w = getWriter(q); | 107 | w = getWriter(q); |
| 108 | w->setStaticID(true); | 108 | w->setStaticID(true); |
| 109 | w->setObjectStreamMode(qpdf_o_disable); | 109 | w->setObjectStreamMode(qpdf_o_disable); |
| 110 | - w->setR3EncryptionParameters( | 110 | + w->setR3EncryptionParametersInsecure( |
| 111 | "u", "o", true, true, true, true, true, true, qpdf_r3p_full); | 111 | "u", "o", true, true, true, true, true, true, qpdf_r3p_full); |
| 112 | doWrite(w); | 112 | doWrite(w); |
| 113 | 113 |
include/qpdf/QPDFWriter.hh
| @@ -366,10 +366,12 @@ class QPDFWriter | @@ -366,10 +366,12 @@ class QPDFWriter | ||
| 366 | // functions that could be useful to you, most notably | 366 | // functions that could be useful to you, most notably |
| 367 | // utf8_to_pdf_doc. | 367 | // utf8_to_pdf_doc. |
| 368 | 368 | ||
| 369 | - // R3 uses RC4, which is a weak cryptographic algorithm. Don't use | ||
| 370 | - // it unless you have to. | 369 | + // R2 uses RC4, which is a weak cryptographic algorithm. Don't use |
| 370 | + // it unless you have to. See "Weak Cryptography" in the manual. | ||
| 371 | + // This encryption format is deprecated in the PDF 2.0 | ||
| 372 | + // specification. | ||
| 371 | QPDF_DLL | 373 | QPDF_DLL |
| 372 | - void setR2EncryptionParameters( | 374 | + void setR2EncryptionParametersInsecure( |
| 373 | char const* user_password, | 375 | char const* user_password, |
| 374 | char const* owner_password, | 376 | char const* owner_password, |
| 375 | bool allow_print, | 377 | bool allow_print, |
| @@ -377,9 +379,11 @@ class QPDFWriter | @@ -377,9 +379,11 @@ class QPDFWriter | ||
| 377 | bool allow_extract, | 379 | bool allow_extract, |
| 378 | bool allow_annotate); | 380 | bool allow_annotate); |
| 379 | // R3 uses RC4, which is a weak cryptographic algorithm. Don't use | 381 | // R3 uses RC4, which is a weak cryptographic algorithm. Don't use |
| 380 | - // it unless you have to. | 382 | + // it unless you have to. See "Weak Cryptography" in the manual. |
| 383 | + // This encryption format is deprecated in the PDF 2.0 | ||
| 384 | + // specification. | ||
| 381 | QPDF_DLL | 385 | QPDF_DLL |
| 382 | - void setR3EncryptionParameters( | 386 | + void setR3EncryptionParametersInsecure( |
| 383 | char const* user_password, | 387 | char const* user_password, |
| 384 | char const* owner_password, | 388 | char const* owner_password, |
| 385 | bool allow_accessibility, | 389 | bool allow_accessibility, |
| @@ -389,10 +393,13 @@ class QPDFWriter | @@ -389,10 +393,13 @@ class QPDFWriter | ||
| 389 | bool allow_form_filling, | 393 | bool allow_form_filling, |
| 390 | bool allow_modify_other, | 394 | bool allow_modify_other, |
| 391 | qpdf_r3_print_e print); | 395 | qpdf_r3_print_e print); |
| 392 | - // R4 uses RC4, which is a weak cryptographic algorithm, when | ||
| 393 | - // use_aes=false. Don't use it unless you have to. | 396 | + // When use_aes=false, this call enables R4 with RC4, which is a |
| 397 | + // weak cryptographic algorithm. Even with use_aes=true, the | ||
| 398 | + // overall encryption scheme is weak. Don't use it unless you have | ||
| 399 | + // to. See "Weak Cryptography" in the manual. This encryption | ||
| 400 | + // format is deprecated in the PDF 2.0 specification. | ||
| 394 | QPDF_DLL | 401 | QPDF_DLL |
| 395 | - void setR4EncryptionParameters( | 402 | + void setR4EncryptionParametersInsecure( |
| 396 | char const* user_password, | 403 | char const* user_password, |
| 397 | char const* owner_password, | 404 | char const* owner_password, |
| 398 | bool allow_accessibility, | 405 | bool allow_accessibility, |
| @@ -419,6 +426,8 @@ class QPDFWriter | @@ -419,6 +426,8 @@ class QPDFWriter | ||
| 419 | bool allow_modify_other, | 426 | bool allow_modify_other, |
| 420 | qpdf_r3_print_e print, | 427 | qpdf_r3_print_e print, |
| 421 | bool encrypt_metadata); | 428 | bool encrypt_metadata); |
| 429 | + // This is the only password-based encryption format supported by | ||
| 430 | + // the PDF specification. | ||
| 422 | QPDF_DLL | 431 | QPDF_DLL |
| 423 | void setR6EncryptionParameters( | 432 | void setR6EncryptionParameters( |
| 424 | char const* user_password, | 433 | char const* user_password, |
include/qpdf/qpdf-c.h
| @@ -458,8 +458,13 @@ extern "C" { | @@ -458,8 +458,13 @@ extern "C" { | ||
| 458 | QPDF_DLL | 458 | QPDF_DLL |
| 459 | void qpdf_set_preserve_encryption(qpdf_data qpdf, QPDF_BOOL value); | 459 | void qpdf_set_preserve_encryption(qpdf_data qpdf, QPDF_BOOL value); |
| 460 | 460 | ||
| 461 | + /* The *_insecure functions are identical to the old versions but | ||
| 462 | + * have been renamed as a an alert to the caller that they are | ||
| 463 | + * insecure. See "Weak Cryptographic" in the manual for | ||
| 464 | + * details. | ||
| 465 | + */ | ||
| 461 | QPDF_DLL | 466 | QPDF_DLL |
| 462 | - void qpdf_set_r2_encryption_parameters( | 467 | + void qpdf_set_r2_encryption_parameters_insecure( |
| 463 | qpdf_data qpdf, | 468 | qpdf_data qpdf, |
| 464 | char const* user_password, | 469 | char const* user_password, |
| 465 | char const* owner_password, | 470 | char const* owner_password, |
| @@ -469,7 +474,7 @@ extern "C" { | @@ -469,7 +474,7 @@ extern "C" { | ||
| 469 | QPDF_BOOL allow_annotate); | 474 | QPDF_BOOL allow_annotate); |
| 470 | 475 | ||
| 471 | QPDF_DLL | 476 | QPDF_DLL |
| 472 | - void qpdf_set_r3_encryption_parameters2( | 477 | + void qpdf_set_r3_encryption_parameters_insecure( |
| 473 | qpdf_data qpdf, | 478 | qpdf_data qpdf, |
| 474 | char const* user_password, | 479 | char const* user_password, |
| 475 | char const* owner_password, | 480 | char const* owner_password, |
| @@ -482,7 +487,7 @@ extern "C" { | @@ -482,7 +487,7 @@ extern "C" { | ||
| 482 | enum qpdf_r3_print_e print); | 487 | enum qpdf_r3_print_e print); |
| 483 | 488 | ||
| 484 | QPDF_DLL | 489 | QPDF_DLL |
| 485 | - void qpdf_set_r4_encryption_parameters2( | 490 | + void qpdf_set_r4_encryption_parameters_insecure( |
| 486 | qpdf_data qpdf, | 491 | qpdf_data qpdf, |
| 487 | char const* user_password, | 492 | char const* user_password, |
| 488 | char const* owner_password, | 493 | char const* owner_password, |
job.sums
| @@ -14,4 +14,4 @@ libqpdf/qpdf/auto_job_json_decl.hh 06caa46eaf71db8a50c046f91866baa8087745a947431 | @@ -14,4 +14,4 @@ libqpdf/qpdf/auto_job_json_decl.hh 06caa46eaf71db8a50c046f91866baa8087745a947431 | ||
| 14 | libqpdf/qpdf/auto_job_json_init.hh 06d51f11c117011256e175386eee9946441f3c22b49dd91fc591bbc1fa3bbeec | 14 | libqpdf/qpdf/auto_job_json_init.hh 06d51f11c117011256e175386eee9946441f3c22b49dd91fc591bbc1fa3bbeec |
| 15 | libqpdf/qpdf/auto_job_schema.hh 43273b9edfc48b1f4cccbff1d2b31916a9057c474ef97d2936b2f1f14170885b | 15 | libqpdf/qpdf/auto_job_schema.hh 43273b9edfc48b1f4cccbff1d2b31916a9057c474ef97d2936b2f1f14170885b |
| 16 | manual/_ext/qpdf.py e9ac9d6c70642a3d29281ee5ad92ae2422dee8be9306fb8a0bc9dba0ed5e28f3 | 16 | manual/_ext/qpdf.py e9ac9d6c70642a3d29281ee5ad92ae2422dee8be9306fb8a0bc9dba0ed5e28f3 |
| 17 | -manual/cli.rst 6a2d99acedbd207370a8dc2807f6657323c42bccbe51ebdc6bc2d00f6851219c | 17 | +manual/cli.rst 70258db13d89b0476248e9703bf5f50ffe28fce2a179dfeca241582dd28b455c |
libqpdf/QPDFJob.cc
| @@ -2815,19 +2815,22 @@ QPDFJob::setEncryptionOptions(QPDF& pdf, QPDFWriter& w) | @@ -2815,19 +2815,22 @@ QPDFJob::setEncryptionOptions(QPDF& pdf, QPDFWriter& w) | ||
| 2815 | QTC::TC("qpdf", "QPDFJob weak crypto error"); | 2815 | QTC::TC("qpdf", "QPDFJob weak crypto error"); |
| 2816 | *(this->m->cerr) | 2816 | *(this->m->cerr) |
| 2817 | << this->m->message_prefix | 2817 | << this->m->message_prefix |
| 2818 | - << ": refusing to write a file with RC4, a weak cryptographic algorithm" | 2818 | + << ": refusing to write a file with RC4, a weak cryptographic " |
| 2819 | + "algorithm" | ||
| 2819 | << std::endl | 2820 | << std::endl |
| 2820 | << "Please use 256-bit keys for better security." << std::endl | 2821 | << "Please use 256-bit keys for better security." << std::endl |
| 2821 | << "Pass --allow-weak-crypto to enable writing insecure files." | 2822 | << "Pass --allow-weak-crypto to enable writing insecure files." |
| 2822 | << std::endl | 2823 | << std::endl |
| 2823 | - << "See also https://qpdf.readthedocs.io/en/stable/weak-crypto.html" | 2824 | + << "See also " |
| 2825 | + "https://qpdf.readthedocs.io/en/stable/weak-crypto.html" | ||
| 2824 | << std::endl; | 2826 | << std::endl; |
| 2825 | - throw std::runtime_error("refusing to write a file with weak crypto"); | 2827 | + throw std::runtime_error( |
| 2828 | + "refusing to write a file with weak crypto"); | ||
| 2826 | } | 2829 | } |
| 2827 | } | 2830 | } |
| 2828 | switch (R) { | 2831 | switch (R) { |
| 2829 | case 2: | 2832 | case 2: |
| 2830 | - w.setR2EncryptionParameters( | 2833 | + w.setR2EncryptionParametersInsecure( |
| 2831 | m->user_password.c_str(), | 2834 | m->user_password.c_str(), |
| 2832 | m->owner_password.c_str(), | 2835 | m->owner_password.c_str(), |
| 2833 | m->r2_print, | 2836 | m->r2_print, |
| @@ -2836,7 +2839,7 @@ QPDFJob::setEncryptionOptions(QPDF& pdf, QPDFWriter& w) | @@ -2836,7 +2839,7 @@ QPDFJob::setEncryptionOptions(QPDF& pdf, QPDFWriter& w) | ||
| 2836 | m->r2_annotate); | 2839 | m->r2_annotate); |
| 2837 | break; | 2840 | break; |
| 2838 | case 3: | 2841 | case 3: |
| 2839 | - w.setR3EncryptionParameters( | 2842 | + w.setR3EncryptionParametersInsecure( |
| 2840 | m->user_password.c_str(), | 2843 | m->user_password.c_str(), |
| 2841 | m->owner_password.c_str(), | 2844 | m->owner_password.c_str(), |
| 2842 | m->r3_accessibility, | 2845 | m->r3_accessibility, |
| @@ -2848,7 +2851,7 @@ QPDFJob::setEncryptionOptions(QPDF& pdf, QPDFWriter& w) | @@ -2848,7 +2851,7 @@ QPDFJob::setEncryptionOptions(QPDF& pdf, QPDFWriter& w) | ||
| 2848 | m->r3_print); | 2851 | m->r3_print); |
| 2849 | break; | 2852 | break; |
| 2850 | case 4: | 2853 | case 4: |
| 2851 | - w.setR4EncryptionParameters( | 2854 | + w.setR4EncryptionParametersInsecure( |
| 2852 | m->user_password.c_str(), | 2855 | m->user_password.c_str(), |
| 2853 | m->owner_password.c_str(), | 2856 | m->owner_password.c_str(), |
| 2854 | m->r3_accessibility, | 2857 | m->r3_accessibility, |
libqpdf/QPDFWriter.cc
| @@ -365,7 +365,7 @@ QPDFWriter::setPCLm(bool val) | @@ -365,7 +365,7 @@ QPDFWriter::setPCLm(bool val) | ||
| 365 | } | 365 | } |
| 366 | 366 | ||
| 367 | void | 367 | void |
| 368 | -QPDFWriter::setR2EncryptionParameters( | 368 | +QPDFWriter::setR2EncryptionParametersInsecure( |
| 369 | char const* user_password, | 369 | char const* user_password, |
| 370 | char const* owner_password, | 370 | char const* owner_password, |
| 371 | bool allow_print, | 371 | bool allow_print, |
| @@ -391,7 +391,7 @@ QPDFWriter::setR2EncryptionParameters( | @@ -391,7 +391,7 @@ QPDFWriter::setR2EncryptionParameters( | ||
| 391 | } | 391 | } |
| 392 | 392 | ||
| 393 | void | 393 | void |
| 394 | -QPDFWriter::setR3EncryptionParameters( | 394 | +QPDFWriter::setR3EncryptionParametersInsecure( |
| 395 | char const* user_password, | 395 | char const* user_password, |
| 396 | char const* owner_password, | 396 | char const* owner_password, |
| 397 | bool allow_accessibility, | 397 | bool allow_accessibility, |
| @@ -419,7 +419,7 @@ QPDFWriter::setR3EncryptionParameters( | @@ -419,7 +419,7 @@ QPDFWriter::setR3EncryptionParameters( | ||
| 419 | } | 419 | } |
| 420 | 420 | ||
| 421 | void | 421 | void |
| 422 | -QPDFWriter::setR4EncryptionParameters( | 422 | +QPDFWriter::setR4EncryptionParametersInsecure( |
| 423 | char const* user_password, | 423 | char const* user_password, |
| 424 | char const* owner_password, | 424 | char const* owner_password, |
| 425 | bool allow_accessibility, | 425 | bool allow_accessibility, |
libqpdf/qpdf-c.cc
| @@ -674,7 +674,7 @@ qpdf_set_preserve_encryption(qpdf_data qpdf, QPDF_BOOL value) | @@ -674,7 +674,7 @@ qpdf_set_preserve_encryption(qpdf_data qpdf, QPDF_BOOL value) | ||
| 674 | } | 674 | } |
| 675 | 675 | ||
| 676 | void | 676 | void |
| 677 | -qpdf_set_r2_encryption_parameters( | 677 | +qpdf_set_r2_encryption_parameters_insecure( |
| 678 | qpdf_data qpdf, | 678 | qpdf_data qpdf, |
| 679 | char const* user_password, | 679 | char const* user_password, |
| 680 | char const* owner_password, | 680 | char const* owner_password, |
| @@ -683,8 +683,8 @@ qpdf_set_r2_encryption_parameters( | @@ -683,8 +683,8 @@ qpdf_set_r2_encryption_parameters( | ||
| 683 | QPDF_BOOL allow_extract, | 683 | QPDF_BOOL allow_extract, |
| 684 | QPDF_BOOL allow_annotate) | 684 | QPDF_BOOL allow_annotate) |
| 685 | { | 685 | { |
| 686 | - QTC::TC("qpdf", "qpdf-c called qpdf_set_r2_encryption_parameters"); | ||
| 687 | - qpdf->qpdf_writer->setR2EncryptionParameters( | 686 | + QTC::TC("qpdf", "qpdf-c called qpdf_set_r2_encryption_parameters_insecure"); |
| 687 | + qpdf->qpdf_writer->setR2EncryptionParametersInsecure( | ||
| 688 | user_password, | 688 | user_password, |
| 689 | owner_password, | 689 | owner_password, |
| 690 | allow_print != QPDF_FALSE, | 690 | allow_print != QPDF_FALSE, |
| @@ -694,7 +694,7 @@ qpdf_set_r2_encryption_parameters( | @@ -694,7 +694,7 @@ qpdf_set_r2_encryption_parameters( | ||
| 694 | } | 694 | } |
| 695 | 695 | ||
| 696 | void | 696 | void |
| 697 | -qpdf_set_r3_encryption_parameters2( | 697 | +qpdf_set_r3_encryption_parameters_insecure( |
| 698 | qpdf_data qpdf, | 698 | qpdf_data qpdf, |
| 699 | char const* user_password, | 699 | char const* user_password, |
| 700 | char const* owner_password, | 700 | char const* owner_password, |
| @@ -706,8 +706,8 @@ qpdf_set_r3_encryption_parameters2( | @@ -706,8 +706,8 @@ qpdf_set_r3_encryption_parameters2( | ||
| 706 | QPDF_BOOL allow_modify_other, | 706 | QPDF_BOOL allow_modify_other, |
| 707 | enum qpdf_r3_print_e print) | 707 | enum qpdf_r3_print_e print) |
| 708 | { | 708 | { |
| 709 | - QTC::TC("qpdf", "qpdf-c called qpdf_set_r3_encryption_parameters"); | ||
| 710 | - qpdf->qpdf_writer->setR3EncryptionParameters( | 709 | + QTC::TC("qpdf", "qpdf-c called qpdf_set_r3_encryption_parameters_insecure"); |
| 710 | + qpdf->qpdf_writer->setR3EncryptionParametersInsecure( | ||
| 711 | user_password, | 711 | user_password, |
| 712 | owner_password, | 712 | owner_password, |
| 713 | allow_accessibility != QPDF_FALSE, | 713 | allow_accessibility != QPDF_FALSE, |
| @@ -720,7 +720,7 @@ qpdf_set_r3_encryption_parameters2( | @@ -720,7 +720,7 @@ qpdf_set_r3_encryption_parameters2( | ||
| 720 | } | 720 | } |
| 721 | 721 | ||
| 722 | void | 722 | void |
| 723 | -qpdf_set_r4_encryption_parameters2( | 723 | +qpdf_set_r4_encryption_parameters_insecure( |
| 724 | qpdf_data qpdf, | 724 | qpdf_data qpdf, |
| 725 | char const* user_password, | 725 | char const* user_password, |
| 726 | char const* owner_password, | 726 | char const* owner_password, |
| @@ -734,8 +734,8 @@ qpdf_set_r4_encryption_parameters2( | @@ -734,8 +734,8 @@ qpdf_set_r4_encryption_parameters2( | ||
| 734 | QPDF_BOOL encrypt_metadata, | 734 | QPDF_BOOL encrypt_metadata, |
| 735 | QPDF_BOOL use_aes) | 735 | QPDF_BOOL use_aes) |
| 736 | { | 736 | { |
| 737 | - QTC::TC("qpdf", "qpdf-c called qpdf_set_r4_encryption_parameters"); | ||
| 738 | - qpdf->qpdf_writer->setR4EncryptionParameters( | 737 | + QTC::TC("qpdf", "qpdf-c called qpdf_set_r4_encryption_parameters_insecure"); |
| 738 | + qpdf->qpdf_writer->setR4EncryptionParametersInsecure( | ||
| 739 | user_password, | 739 | user_password, |
| 740 | owner_password, | 740 | owner_password, |
| 741 | allow_accessibility != QPDF_FALSE, | 741 | allow_accessibility != QPDF_FALSE, |
manual/cli.rst
| @@ -470,12 +470,18 @@ Related Options | @@ -470,12 +470,18 @@ Related Options | ||
| 470 | option is necessary to create 40-bit files or 128-bit files that | 470 | option is necessary to create 40-bit files or 128-bit files that |
| 471 | use RC4 encryption. | 471 | use RC4 encryption. |
| 472 | 472 | ||
| 473 | - Starting with version 10.4, qpdf issues warnings when requested to | ||
| 474 | - create files using RC4 encryption. This option suppresses those | ||
| 475 | - warnings. In future versions of qpdf, qpdf will refuse to create | ||
| 476 | - files with weak cryptography when this flag is not given. See | 473 | + Encrypted PDF files using 40-bit keys or 128-bit keys without AES |
| 474 | + use the insecure *RC4* encryption algorithm. Starting with version | ||
| 475 | + 11.0, qpdf's default behavior is to refuse to write files using RC4 | ||
| 476 | + encryption. Use this option to allow creation of such files. In | ||
| 477 | + versions 10.4 through 10.6, attempting to create weak encrypted | ||
| 478 | + files was a warning, rather than an error, without this flag. See | ||
| 477 | :ref:`weak-crypto` for additional details. | 479 | :ref:`weak-crypto` for additional details. |
| 478 | 480 | ||
| 481 | + No check is performed for weak crypto when preserving encryption | ||
| 482 | + parameters from or copying encryption parameters from other files. | ||
| 483 | + The rationale for this is discussed in :ref:`weak-crypto`. | ||
| 484 | + | ||
| 479 | .. qpdf:option:: --keep-files-open=[y|n] | 485 | .. qpdf:option:: --keep-files-open=[y|n] |
| 480 | 486 | ||
| 481 | .. help: manage keeping multiple files open | 487 | .. help: manage keeping multiple files open |
| @@ -741,6 +747,9 @@ Related Options | @@ -741,6 +747,9 @@ Related Options | ||
| 741 | file without having to manual specify all the individual settings. | 747 | file without having to manual specify all the individual settings. |
| 742 | See also :qpdf:ref:`--decrypt`. | 748 | See also :qpdf:ref:`--decrypt`. |
| 743 | 749 | ||
| 750 | + Checks for weak cryptographic algorithms are intentionally not made | ||
| 751 | + by this operation. See :ref:`weak-crypto` for the rationale. | ||
| 752 | + | ||
| 744 | .. qpdf:option:: --encryption-file-password=password | 753 | .. qpdf:option:: --encryption-file-password=password |
| 745 | 754 | ||
| 746 | .. help: supply password for --copy-encryption | 755 | .. help: supply password for --copy-encryption |
manual/release-notes.rst
| @@ -62,6 +62,10 @@ For a detailed list of changes, please see the file | @@ -62,6 +62,10 @@ For a detailed list of changes, please see the file | ||
| 62 | - The default json output version when :qpdf:ref:`--json` is | 62 | - The default json output version when :qpdf:ref:`--json` is |
| 63 | specified has been changed from ``1`` to ``latest``. | 63 | specified has been changed from ``1`` to ``latest``. |
| 64 | 64 | ||
| 65 | + - The :qpdf:ref:`--allow-weak-crypto` flag is now mandatory when | ||
| 66 | + explicitly creating files with weak cryptographic algorithms. | ||
| 67 | + See :ref:`weak-crypto` for a discussion. | ||
| 68 | + | ||
| 65 | - API: breaking changes | 69 | - API: breaking changes |
| 66 | 70 | ||
| 67 | - Remove | 71 | - Remove |
| @@ -73,6 +77,19 @@ For a detailed list of changes, please see the file | @@ -73,6 +77,19 @@ For a detailed list of changes, please see the file | ||
| 73 | ``QPDFNumberTreeObjectHelper`` constructors that don't take a | 77 | ``QPDFNumberTreeObjectHelper`` constructors that don't take a |
| 74 | ``QPDF&`` argument. | 78 | ``QPDF&`` argument. |
| 75 | 79 | ||
| 80 | + - Intentionally break API to call attention to operations that | ||
| 81 | + write files with insecure encryption: | ||
| 82 | + | ||
| 83 | + - Remove pre qpdf-8.4.0 encryption API methods from ``QPDFWriter`` | ||
| 84 | + and their corresponding C API functions | ||
| 85 | + | ||
| 86 | + - Add ``Insecure`` to the names of some ``QPDFWriter`` methods | ||
| 87 | + and ``_insecure`` to the names of some C API functions without | ||
| 88 | + otherwise changing their behavior | ||
| 89 | + | ||
| 90 | + - See :ref:`breaking-crypto-api` for specific details, and see | ||
| 91 | + :ref:`weak-crypto` for a general discussion. | ||
| 92 | + | ||
| 76 | - Library Enhancements | 93 | - Library Enhancements |
| 77 | 94 | ||
| 78 | - Support for more fluent programming with ``QPDFObjectHandle``. | 95 | - Support for more fluent programming with ``QPDFObjectHandle``. |
manual/weak-crypto.rst
| @@ -3,24 +3,55 @@ | @@ -3,24 +3,55 @@ | ||
| 3 | Weak Cryptography | 3 | Weak Cryptography |
| 4 | ================= | 4 | ================= |
| 5 | 5 | ||
| 6 | -Start with version 10.4, qpdf is taking steps to reduce the likelihood | ||
| 7 | -of a user *accidentally* creating PDF files with insecure cryptography | ||
| 8 | -but will continue to allow creation of such files indefinitely with | ||
| 9 | -explicit acknowledgment. | ||
| 10 | - | ||
| 11 | -The PDF file format makes use of RC4, which is known to be a weak | ||
| 12 | -cryptography algorithm, and MD5, which is a weak hashing algorithm. In | ||
| 13 | -version 10.4, qpdf generates warnings for some (but not all) cases of | ||
| 14 | -writing files with weak cryptography when invoked from the command-line. | ||
| 15 | -These warnings can be suppressed using the | ||
| 16 | -:qpdf:ref:`--allow-weak-crypto` option. | 6 | +For help with compiler errors in qpdf 11.0 or newer, see |
| 7 | +:ref:`breaking-crypto-api`. | ||
| 8 | + | ||
| 9 | +Since 2006, the PDF specification has offered ways to create encrypted | ||
| 10 | +PDF files without using weak cryptography, though it took a few years | ||
| 11 | +for many PDF readers and writers to catch up. It is still necessary to | ||
| 12 | +support weak encryption algorithms to read encrypted PDF files that | ||
| 13 | +were created using weak encryption algorithms, including all PDF files | ||
| 14 | +created before the modern formats were introduced or widely supported. | ||
| 15 | + | ||
| 16 | +Starting with version 10.4, qpdf began taking steps to reduce the | ||
| 17 | +likelihood of a user *accidentally* creating PDF files with insecure | ||
| 18 | +cryptography but will continue to allow creation of such files | ||
| 19 | +indefinitely with explicit acknowledgment. The restrictions on use of | ||
| 20 | +weak cryptography were made stricter with qpdf 11. | ||
| 21 | + | ||
| 22 | +Definition of Weak Cryptographic Algorithm | ||
| 23 | +------------------------------------------ | ||
| 24 | + | ||
| 25 | +We divide weak cryptographic algorithms into two categories: weak | ||
| 26 | +encryption and weak hashing. Encryption is encoding data such that a | ||
| 27 | +key of some sort is required to decode it. Hashing is creating a short | ||
| 28 | +value from data in such a way that it is extremely improbable to find | ||
| 29 | +two documents with the same hash (known has a hash collision) and | ||
| 30 | +extremely difficult to intentionally create a document with a specific | ||
| 31 | +hash or two documents with the same hash. | ||
| 17 | 32 | ||
| 18 | -It is planned for qpdf version 11 to be stricter, making it an error to | ||
| 19 | -write files with insecure cryptography from the command-line tool in | ||
| 20 | -most cases without specifying the | ||
| 21 | -:qpdf:ref:`--allow-weak-crypto` flag and also to require | ||
| 22 | -explicit steps when using the C++ library to enable use of insecure | ||
| 23 | -cryptography. | 33 | +When we say that an encryption algorithm is weak, we either mean that |
| 34 | +a mathematical flaw has been discovered that makes it inherently | ||
| 35 | +crackable or that it is sufficiently simple that modern computer | ||
| 36 | +technology makes it possible to use "brute force" to crack. For | ||
| 37 | +example, when 40-bit keys were originally introduced, it wasn't | ||
| 38 | +practical to consider trying all possible keys, but today such a thing | ||
| 39 | +is possible. | ||
| 40 | + | ||
| 41 | +When we say that a hashing algorithm is weak, we mean that, either | ||
| 42 | +because of mathematical flaw or insufficient complexity, it is | ||
| 43 | +computationally feasible to intentionally construct a hash collision. | ||
| 44 | + | ||
| 45 | +While weak encryption should always be avoided, there are cases in | ||
| 46 | +which it is safe to use a weak hashing algorithm when security is not | ||
| 47 | +a factor. For example, a weak hashing algorithm should not be used as | ||
| 48 | +the only mechanism to test whether a file has been tampered with. In | ||
| 49 | +other words, you can't use a weak hash as a digital signature. There | ||
| 50 | +is no harm, however, in using a weak hash as a way to sort or index | ||
| 51 | +documents as long as hash collisions are tolerated. It is also common | ||
| 52 | +to use weak hashes as checksums, which are often used a check that a | ||
| 53 | +file wasn't damanged in transit or storage, though for true integrity, | ||
| 54 | +a strong hash would be better. | ||
| 24 | 55 | ||
| 25 | Note that qpdf must always retain support for weak cryptographic | 56 | Note that qpdf must always retain support for weak cryptographic |
| 26 | algorithms since this is required for reading older PDF files that use | 57 | algorithms since this is required for reading older PDF files that use |
| @@ -31,3 +62,135 @@ since these are sometimes needed to test or work with older versions of | @@ -31,3 +62,135 @@ since these are sometimes needed to test or work with older versions of | ||
| 31 | software. Even if other cryptography libraries drop support for RC4 or | 62 | software. Even if other cryptography libraries drop support for RC4 or |
| 32 | MD5, qpdf can always fall back to its internal implementations of those | 63 | MD5, qpdf can always fall back to its internal implementations of those |
| 33 | algorithms, so they are not going to disappear from qpdf. | 64 | algorithms, so they are not going to disappear from qpdf. |
| 65 | + | ||
| 66 | +Uses of Weak Encryption in qpdf | ||
| 67 | +--------------------------------- | ||
| 68 | + | ||
| 69 | +When PDF files are encrypted using 40-bit encryption or 128-bit | ||
| 70 | +encryption without AES, then the weak *RC4* algorithm is used. You can | ||
| 71 | +avoid using weak encryption in qpdf by always using 256-bit | ||
| 72 | +encryption. Unless you are trying to create files that need to be | ||
| 73 | +opened with PDF readers from before about 2010 (by which time most | ||
| 74 | +readers had added support for the stronger encryption algorithms) or | ||
| 75 | +are creating insecure files explicitly for testing or some similar | ||
| 76 | +purpose, there is no reason to use anything other than 256-bit | ||
| 77 | +encryption. | ||
| 78 | + | ||
| 79 | +By default, qpdf refuses to write a file that uses weak encryption. | ||
| 80 | +You can explicitly allow this by specifying the | ||
| 81 | +:qpdf:ref:`--allow-weak-crypto` option. | ||
| 82 | + | ||
| 83 | +In qpdf 11, all library methods that could potentially cause files to | ||
| 84 | +be written with weak encryption were deprecated, and methods to enable | ||
| 85 | +weak encryption were either given explicit names indicating this or | ||
| 86 | +take required arguments to enable the insecure behavior. | ||
| 87 | + | ||
| 88 | +There is one exception: when encryption parameters are copied from the | ||
| 89 | +input file or another file to the output file, there is no prohibition | ||
| 90 | +or even warning against using insecure encryption. The reason is that | ||
| 91 | +many qpdf operations simply preserve whatever encryption is there, and | ||
| 92 | +requiring confirmation to *preserve* insecure encryption would cause | ||
| 93 | +qpdf to break when non-encryption-related operations were performed on | ||
| 94 | +files that happened to be encrypted. Failing or generating warnings in | ||
| 95 | +this case would likely have the effect of making people use the | ||
| 96 | +:qpdf:ref:`--allow-weak-crypto` option blindly, which would be worse | ||
| 97 | +than just letting those files go so that explicit, conscious selection | ||
| 98 | +of weak crypto would be more likely to be noticed. Why, you might ask, | ||
| 99 | +does this apply to :qpdf:ref:`--copy-encryption` as well as to the | ||
| 100 | +default behavior preserving encryption? The answer is that | ||
| 101 | +:qpdf:ref:`--copy-encryption` works with an unencrypted file as input, | ||
| 102 | +which enables workflows where one may start with a file, decrypt it | ||
| 103 | +*just in case*, perform a series of operations, and then reapply the | ||
| 104 | +original encryption, *if any*. Also, one may have a template used for | ||
| 105 | +encryption that one may apply to a variety of output files, and it | ||
| 106 | +would be annoying to be warned about it for every output file. | ||
| 107 | + | ||
| 108 | +Uses of Weak Hashing In QPDF | ||
| 109 | +---------------------------- | ||
| 110 | + | ||
| 111 | +The PDF specification makes use the weak *MD5* hashing algorithm in | ||
| 112 | +several places. While it is used in the encryption algorithms, | ||
| 113 | +breaking MD5 would not be adequate to crack an encrypted file when | ||
| 114 | +256-bit encryption is in use, so using 256-bit encryption is adequate | ||
| 115 | +for avoiding the use of MD5 for anything security-sensitive. | ||
| 116 | + | ||
| 117 | +MD5 is used in the following non-security-sensitive ways: | ||
| 118 | + | ||
| 119 | +- Generation of the document ID. The document ID is an input parameter | ||
| 120 | + to the document encryption but is not itself considered to be | ||
| 121 | + secure. They are supposed to be unique, but they are not | ||
| 122 | + tamper-resistent in non-encrypted PDF files, and hash collisions | ||
| 123 | + must be tolerated. | ||
| 124 | + | ||
| 125 | + The PDF specification recommends but does not require the use of MD5 | ||
| 126 | + in generation of document IDs. Usually there is also a random | ||
| 127 | + component to document ID generation. There is a qpdf-specific | ||
| 128 | + feature of generating a *deterministic ID* (see | ||
| 129 | + :qpdf:ref:`--deterministic-id`) which also uses MD5. While it would | ||
| 130 | + certainly be possible to change the deterministic ID algorithm to | ||
| 131 | + not use MD5, doing so would break all previous deterministic IDs | ||
| 132 | + (which would render the feature useless for many cases) and would | ||
| 133 | + offer very little benefit since even a securely generated document | ||
| 134 | + ID is not itself a security-sensitive value. | ||
| 135 | + | ||
| 136 | +- Checksums in embedded file streams -- the PDF specification | ||
| 137 | + specifies the use of MD5. | ||
| 138 | + | ||
| 139 | +It is therefore not possible completely avoid the use of MD5 with | ||
| 140 | +qpdf, but as long as you are using 256-bit encryption, it is not used | ||
| 141 | +in a securty-sensitive fashion. | ||
| 142 | + | ||
| 143 | +.. _breaking-crypto-api: | ||
| 144 | + | ||
| 145 | +API-Breaking Changes in qpdf 11.0 | ||
| 146 | +--------------------------------- | ||
| 147 | + | ||
| 148 | +In qpdf 11, several deprecated functions and methods were removed. | ||
| 149 | +These methods provided an incomplete API. Alternatives were added in | ||
| 150 | +qpdf 8.4.0. The removed functions are | ||
| 151 | + | ||
| 152 | +- C API: ``qpdf_set_r3_encryption_parameters``, | ||
| 153 | + ``qpdf_set_r4_encryption_parameters``, | ||
| 154 | + ``qpdf_set_r5_encryption_parameters``, | ||
| 155 | + ``qpdf_set_r6_encryption_parameters`` | ||
| 156 | + | ||
| 157 | +- ``QPDFWriter``: overloaded versions of these methods with fewer | ||
| 158 | + arguments: ``setR3EncryptionParameters``, | ||
| 159 | + ``setR4EncryptionParameters``, ``setR5EncryptionParameters``, and | ||
| 160 | + ``setR6EncryptionParameters`` | ||
| 161 | + | ||
| 162 | +Additionally, remaining functions/methods had their names changed to | ||
| 163 | +signal that they are insecure and to force developers to make a | ||
| 164 | +decision. If you intentionally want to continue to use insecure | ||
| 165 | +cryptographic algorithms and create insecure files, you can change | ||
| 166 | +your code just add ``_insecure`` or ``Insecure`` to the end of the | ||
| 167 | +function as needed. (Note the disappearance of ``2`` in some of the C | ||
| 168 | +functions as well.) Better, you should migrate your code to use more | ||
| 169 | +secure encryption as documented in :file:`QPDFWriter.hh`. Use the | ||
| 170 | +``R6`` methods (or their corresponding C functions) to create files | ||
| 171 | +with 256-bit encryption. | ||
| 172 | + | ||
| 173 | +.. list-table:: Renamed Functions | ||
| 174 | + :widths: 50 50 | ||
| 175 | + :header-rows: 1 | ||
| 176 | + | ||
| 177 | + - - Old Name | ||
| 178 | + - New Name | ||
| 179 | + | ||
| 180 | + - - qpdf_set_r2_encryption_parameters | ||
| 181 | + - qpdf_set_r2_encryption_parameters_insecure | ||
| 182 | + | ||
| 183 | + - - qpdf_set_r3_encryption_parameters2 | ||
| 184 | + - qpdf_set_r3_encryption_parameters_insecure | ||
| 185 | + | ||
| 186 | + - - qpdf_set_r4_encryption_parameters2 | ||
| 187 | + - qpdf_set_r2_encryption_parameters_insecure | ||
| 188 | + | ||
| 189 | + - - QPDFWriter::setR2EncryptionParameters | ||
| 190 | + - QPDFWriter::setR2EncryptionParametersInsecure | ||
| 191 | + | ||
| 192 | + - - QPDFWriter::setR3EncryptionParameters | ||
| 193 | + - QPDFWriter::setR3EncryptionParametersInsecure | ||
| 194 | + | ||
| 195 | + - - QPDFWriter::setR4EncryptionParameters | ||
| 196 | + - QPDFWriter::setR4EncryptionParametersInsecure |
qpdf/qpdf-ctest.c
| @@ -320,7 +320,7 @@ test11( | @@ -320,7 +320,7 @@ test11( | ||
| 320 | qpdf_read(qpdf, infile, password); | 320 | qpdf_read(qpdf, infile, password); |
| 321 | qpdf_init_write(qpdf, outfile); | 321 | qpdf_init_write(qpdf, outfile); |
| 322 | qpdf_set_static_ID(qpdf, QPDF_TRUE); | 322 | qpdf_set_static_ID(qpdf, QPDF_TRUE); |
| 323 | - qpdf_set_r2_encryption_parameters( | 323 | + qpdf_set_r2_encryption_parameters_insecure( |
| 324 | qpdf, "user1", "owner1", QPDF_FALSE, QPDF_TRUE, QPDF_TRUE, QPDF_TRUE); | 324 | qpdf, "user1", "owner1", QPDF_FALSE, QPDF_TRUE, QPDF_TRUE, QPDF_TRUE); |
| 325 | qpdf_write(qpdf); | 325 | qpdf_write(qpdf); |
| 326 | report_errors(); | 326 | report_errors(); |
| @@ -336,7 +336,7 @@ test12( | @@ -336,7 +336,7 @@ test12( | ||
| 336 | qpdf_read(qpdf, infile, password); | 336 | qpdf_read(qpdf, infile, password); |
| 337 | qpdf_init_write(qpdf, outfile); | 337 | qpdf_init_write(qpdf, outfile); |
| 338 | qpdf_set_static_ID(qpdf, QPDF_TRUE); | 338 | qpdf_set_static_ID(qpdf, QPDF_TRUE); |
| 339 | - qpdf_set_r3_encryption_parameters2( | 339 | + qpdf_set_r3_encryption_parameters_insecure( |
| 340 | qpdf, | 340 | qpdf, |
| 341 | "user2", | 341 | "user2", |
| 342 | "owner2", | 342 | "owner2", |
| @@ -397,7 +397,7 @@ test15( | @@ -397,7 +397,7 @@ test15( | ||
| 397 | qpdf_init_write(qpdf, outfile); | 397 | qpdf_init_write(qpdf, outfile); |
| 398 | qpdf_set_static_ID(qpdf, QPDF_TRUE); | 398 | qpdf_set_static_ID(qpdf, QPDF_TRUE); |
| 399 | qpdf_set_static_aes_IV(qpdf, QPDF_TRUE); | 399 | qpdf_set_static_aes_IV(qpdf, QPDF_TRUE); |
| 400 | - qpdf_set_r4_encryption_parameters2( | 400 | + qpdf_set_r4_encryption_parameters_insecure( |
| 401 | qpdf, | 401 | qpdf, |
| 402 | "user2", | 402 | "user2", |
| 403 | "owner2", | 403 | "owner2", |
qpdf/qpdf.testcov
| @@ -130,8 +130,8 @@ qpdf-c called qpdf_set_qdf_mode 0 | @@ -130,8 +130,8 @@ qpdf-c called qpdf_set_qdf_mode 0 | ||
| 130 | qpdf-c called qpdf_set_static_ID 0 | 130 | qpdf-c called qpdf_set_static_ID 0 |
| 131 | qpdf-c called qpdf_set_suppress_original_object_IDs 0 | 131 | qpdf-c called qpdf_set_suppress_original_object_IDs 0 |
| 132 | qpdf-c called qpdf_set_preserve_encryption 0 | 132 | qpdf-c called qpdf_set_preserve_encryption 0 |
| 133 | -qpdf-c called qpdf_set_r2_encryption_parameters 0 | ||
| 134 | -qpdf-c called qpdf_set_r3_encryption_parameters 0 | 133 | +qpdf-c called qpdf_set_r2_encryption_parameters_insecure 0 |
| 134 | +qpdf-c called qpdf_set_r3_encryption_parameters_insecure 0 | ||
| 135 | qpdf-c called qpdf_set_linearization 0 | 135 | qpdf-c called qpdf_set_linearization 0 |
| 136 | qpdf-c called qpdf_write 1 | 136 | qpdf-c called qpdf_write 1 |
| 137 | qpdf-c called qpdf_allow_accessibility 0 | 137 | qpdf-c called qpdf_allow_accessibility 0 |
| @@ -158,7 +158,7 @@ QPDF_encryption cleartext metadata 0 | @@ -158,7 +158,7 @@ QPDF_encryption cleartext metadata 0 | ||
| 158 | QPDF_encryption aes decode stream 0 | 158 | QPDF_encryption aes decode stream 0 |
| 159 | QPDFWriter forcing object stream disable 0 | 159 | QPDFWriter forcing object stream disable 0 |
| 160 | QPDFWriter forced version disabled encryption 0 | 160 | QPDFWriter forced version disabled encryption 0 |
| 161 | -qpdf-c called qpdf_set_r4_encryption_parameters 0 | 161 | +qpdf-c called qpdf_set_r4_encryption_parameters_insecure 0 |
| 162 | qpdf-c called qpdf_set_static_aes_IV 0 | 162 | qpdf-c called qpdf_set_static_aes_IV 0 |
| 163 | QPDF_encryption stream crypt filter 0 | 163 | QPDF_encryption stream crypt filter 0 |
| 164 | QPDF ERR object stream with wrong type 0 | 164 | QPDF ERR object stream with wrong type 0 |