Commit 750aca5b94351f730fa768b07caa3fc26c8d27c0

Authored by Jay Berkenbilt
1 parent 37916f39

First increment of improving handling of weak crypto (fixes #358)

ChangeLog
  1 +2021-11-10 Jay Berkenbilt <ejb@ql.org>
  2 +
  3 + * Add --allow-weak-crypto option to suppress warnings about use of
  4 + weak cryptographic algorithms. Update documentation around this
  5 + issue. Fixes #358.
  6 +
1 7 2021-11-07 Jay Berkenbilt <ejb@ql.org>
2 8  
3 9 * Relax xref recovery logic a bit so that files whose objects are
... ...
README.md
... ... @@ -62,6 +62,10 @@ For example, if you want to guarantee that the GnuTLS crypto provider is used, y
62 62  
63 63 Please see the section on crypto providers in the manual for more details.
64 64  
  65 +## Note about weak cryptographic algorithms
  66 +
  67 +The PDF file format used to rely on RC4 for encryption. Using 256-bit keys always uses AES instead, and with 128-bit keys, you can elect to use AES. qpdf does its best to warn when someone is writing a file with weak cryptographic algorithms, but qpdf must always retain support for being able to read and even write files with weak encryption to be able to fully support older PDF files and older PDF readers.
  68 +
65 69 # Building from a pristine checkout
66 70  
67 71 When building qpdf from a pristine checkout from version control, generated documentation files are not present. You may either generate them (by passing `--enable-doc-maintenance` to `./configure` and satisfying the extra build-time dependencies) or obtain them from a released source package, which includes them. If you want to grab just the files that are in the source distribution but not in the repository, extract a source distribution in a temporary directory, and run `make CLEAN=1 distfiles.zip`. This will create a file called `distfiles.zip`, which can you can extract in a checkout of the source repository. This step is optional unless you are running make install and want the html and PDF versions of the documentation to be installed.
... ...
... ... @@ -187,6 +187,23 @@ Comments appear in the code prefixed by &quot;ABI&quot;
187 187 before copying, though maybe we don't because it could cause
188 188 multiple copies to be made...usually it's better to handle that
189 189 explicitly.
  190 +* Deal with weak cryptographic algorithms:
  191 + * Github issue #576
  192 + * Add something to QPDFWriter that you must call in order to allow
  193 + creation of files with insecure crypto. Maybe
  194 + QPDFWriter::allowWeakCrypto. Call this when --allow-weak-crypto is
  195 + passed and probably also when copying encryption by default from
  196 + an input file.
  197 + * Change deterministic id to use something other than MD5 but allow
  198 + the old way for compatibility -- maybe rename the method to force
  199 + the developer to make a choice
  200 + * Find other uses of MD5 and find the ones that are discretionary,
  201 + if any
  202 + * Have QPDFWriter raise an exception if it's about to write using
  203 + weak crypto and hasn't been given permission
  204 + * Search for --allow-weak-crypto in the manual and in qpdf.cc's help
  205 + information
  206 + * Update the ref.weak-crypto section of the manual
190 207  
191 208 Page splitting/merging
192 209 ======================
... ...
fuzz/qpdf_fuzzer.cc
... ... @@ -107,7 +107,7 @@ FuzzHelper::testWrite()
107 107 w->setStaticID(true);
108 108 w->setObjectStreamMode(qpdf_o_disable);
109 109 w->setR3EncryptionParameters(
110   - "u", "o", true, true, qpdf_r3p_full, qpdf_r3m_all);
  110 + "u", "o", true, true, true, true, true, true, qpdf_r3p_full);
111 111 doWrite(w);
112 112  
113 113 q = getQpdf();
... ...
include/qpdf/QPDFCryptoImpl.hh
... ... @@ -69,6 +69,9 @@ class QPDF_DLL_CLASS QPDFCryptoImpl
69 69  
70 70 // Encryption/Decryption
71 71  
  72 + // QPDF must support RC4 to be able to work with older PDF files
  73 + // and readers. Search for RC4 in README.md
  74 +
72 75 // key_len of -1 means treat key_data as a null-terminated string
73 76 QPDF_DLL
74 77 virtual void RC4_init(unsigned char const* key_data, int key_len = -1) = 0;
... ...
include/qpdf/QPDFWriter.hh
... ... @@ -359,6 +359,16 @@ class QPDFWriter
359 359 // this from your own application, QUtil contains many transcoding
360 360 // functions that could be useful to you, most notably
361 361 // utf8_to_pdf_doc.
  362 +
  363 + // R3 uses RC4, which is a weak cryptographic algorithm. Don't use
  364 + // it unless you have to.
  365 + QPDF_DLL
  366 + void setR2EncryptionParameters(
  367 + char const* user_password, char const* owner_password,
  368 + bool allow_print, bool allow_modify,
  369 + bool allow_extract, bool allow_annotate);
  370 + // R3 uses RC4, which is a weak cryptographic algorithm. Don't use
  371 + // it unless you have to.
362 372 QPDF_DLL
363 373 void setR3EncryptionParameters(
364 374 char const* user_password, char const* owner_password,
... ... @@ -366,6 +376,8 @@ class QPDFWriter
366 376 bool allow_assemble, bool allow_annotate_and_form,
367 377 bool allow_form_filling, bool allow_modify_other,
368 378 qpdf_r3_print_e print);
  379 + // R4 uses RC4, which is a weak cryptographic algorithm, when
  380 + // use_aes=false. Don't use it unless you have to.
369 381 QPDF_DLL
370 382 void setR4EncryptionParameters(
371 383 char const* user_password, char const* owner_password,
... ... @@ -392,28 +404,27 @@ class QPDFWriter
392 404 qpdf_r3_print_e print, bool encrypt_metadata_aes);
393 405  
394 406 // Pre qpdf 8.4.0 API
395   - QPDF_DLL
396   - void setR2EncryptionParameters(
397   - char const* user_password, char const* owner_password,
398   - bool allow_print, bool allow_modify,
399   - bool allow_extract, bool allow_annotate);
  407 + [[deprecated("see newer API above")]]
400 408 QPDF_DLL
401 409 void setR3EncryptionParameters(
402 410 char const* user_password, char const* owner_password,
403 411 bool allow_accessibility, bool allow_extract,
404 412 qpdf_r3_print_e print, qpdf_r3_modify_e modify);
  413 + [[deprecated("see newer API above")]]
405 414 QPDF_DLL
406 415 void setR4EncryptionParameters(
407 416 char const* user_password, char const* owner_password,
408 417 bool allow_accessibility, bool allow_extract,
409 418 qpdf_r3_print_e print, qpdf_r3_modify_e modify,
410 419 bool encrypt_metadata, bool use_aes);
  420 + [[deprecated("see newer API above")]]
411 421 QPDF_DLL
412 422 void setR5EncryptionParameters(
413 423 char const* user_password, char const* owner_password,
414 424 bool allow_accessibility, bool allow_extract,
415 425 qpdf_r3_print_e print, qpdf_r3_modify_e modify,
416 426 bool encrypt_metadata);
  427 + [[deprecated("see newer API above")]]
417 428 QPDF_DLL
418 429 void setR6EncryptionParameters(
419 430 char const* user_password, char const* owner_password,
... ...
lgtm.yml 0 โ†’ 100644
  1 +# See https://lgtm.com/help/lgtm/lgtm.yml-configuration-file
  2 +
  3 +# Suppress alerts for weak cryptographic algorithms. The PDF file
  4 +# format used to rely on various weak algorithms. It is no longer
  5 +# necessary to use them when creating encrypted PDF files, but qpdf
  6 +# must always retain support for those algorithms in order to be able
  7 +# to read older files. qpdf issues warnings to try to prevent users
  8 +# from creating new files with weak encryption algorithms.
  9 +
  10 +queries:
  11 +- exclude: cpp/weak-cryptographic-algorithm
... ...
libqpdf/Pl_RC4.cc
... ... @@ -34,6 +34,7 @@ Pl_RC4::write(unsigned char* data, size_t len)
34 34 size_t bytes =
35 35 (bytes_left < this->out_bufsize ? bytes_left : out_bufsize);
36 36 bytes_left -= bytes;
  37 + // lgtm[cpp/weak-cryptographic-algorithm]
37 38 rc4.process(p, bytes, outbuf.getPointer());
38 39 p += bytes;
39 40 getNext()->write(outbuf.getPointer(), bytes);
... ...
libqpdf/qpdf-c.cc
... ... @@ -702,10 +702,20 @@ void qpdf_set_r3_encryption_parameters(
702 702 QPDF_BOOL allow_accessibility, QPDF_BOOL allow_extract,
703 703 qpdf_r3_print_e print, qpdf_r3_modify_e modify)
704 704 {
  705 +#ifdef _MSC_VER
  706 +# pragma warning (disable: 4996)
  707 +#endif
  708 +#if (defined(__GNUC__) || defined(__clang__))
  709 +# pragma GCC diagnostic push
  710 +# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
  711 +#endif
705 712 qpdf->qpdf_writer->setR3EncryptionParameters(
706 713 user_password, owner_password,
707 714 allow_accessibility != QPDF_FALSE, allow_extract != QPDF_FALSE,
708 715 print, modify);
  716 +#if (defined(__GNUC__) || defined(__clang__))
  717 +# pragma GCC diagnostic pop
  718 +#endif
709 719 }
710 720  
711 721 void qpdf_set_r4_encryption_parameters(
... ... @@ -714,11 +724,21 @@ void qpdf_set_r4_encryption_parameters(
714 724 qpdf_r3_print_e print, qpdf_r3_modify_e modify,
715 725 QPDF_BOOL encrypt_metadata, QPDF_BOOL use_aes)
716 726 {
  727 +#ifdef _MSC_VER
  728 +# pragma warning (disable: 4996)
  729 +#endif
  730 +#if (defined(__GNUC__) || defined(__clang__))
  731 +# pragma GCC diagnostic push
  732 +# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
  733 +#endif
717 734 qpdf->qpdf_writer->setR4EncryptionParameters(
718 735 user_password, owner_password,
719 736 allow_accessibility != QPDF_FALSE, allow_extract != QPDF_FALSE,
720 737 print, modify,
721 738 encrypt_metadata != QPDF_FALSE, use_aes != QPDF_FALSE);
  739 +#if (defined(__GNUC__) || defined(__clang__))
  740 +# pragma GCC diagnostic pop
  741 +#endif
722 742 }
723 743  
724 744 void qpdf_set_r5_encryption_parameters(
... ... @@ -727,11 +747,21 @@ void qpdf_set_r5_encryption_parameters(
727 747 qpdf_r3_print_e print, qpdf_r3_modify_e modify,
728 748 QPDF_BOOL encrypt_metadata)
729 749 {
  750 +#ifdef _MSC_VER
  751 +# pragma warning (disable: 4996)
  752 +#endif
  753 +#if (defined(__GNUC__) || defined(__clang__))
  754 +# pragma GCC diagnostic push
  755 +# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
  756 +#endif
730 757 qpdf->qpdf_writer->setR5EncryptionParameters(
731 758 user_password, owner_password,
732 759 allow_accessibility != QPDF_FALSE, allow_extract != QPDF_FALSE,
733 760 print, modify,
734 761 encrypt_metadata != QPDF_FALSE);
  762 +#if (defined(__GNUC__) || defined(__clang__))
  763 +# pragma GCC diagnostic pop
  764 +#endif
735 765 }
736 766  
737 767 void qpdf_set_r6_encryption_parameters(
... ... @@ -740,10 +770,20 @@ void qpdf_set_r6_encryption_parameters(
740 770 qpdf_r3_print_e print, qpdf_r3_modify_e modify,
741 771 QPDF_BOOL encrypt_metadata)
742 772 {
  773 +#ifdef _MSC_VER
  774 +# pragma warning (disable: 4996)
  775 +#endif
  776 +#if (defined(__GNUC__) || defined(__clang__))
  777 +# pragma GCC diagnostic push
  778 +# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
  779 +#endif
743 780 qpdf->qpdf_writer->setR6EncryptionParameters(
744 781 user_password, owner_password,
745 782 allow_accessibility != QPDF_FALSE, allow_extract != QPDF_FALSE,
746 783 print, modify, encrypt_metadata != QPDF_FALSE);
  784 +#if (defined(__GNUC__) || defined(__clang__))
  785 +# pragma GCC diagnostic pop
  786 +#endif
747 787 }
748 788  
749 789 void qpdf_set_linearization(qpdf_data qpdf, QPDF_BOOL value)
... ...
manual/qpdf-manual.xml
... ... @@ -874,6 +874,19 @@ make
874 874 </listitem>
875 875 </varlistentry>
876 876 <varlistentry>
  877 + <term><option>--allow-weak-crypto</option></term>
  878 + <listitem>
  879 + <para>
  880 + Starting with version 10.4, qpdf issues warnings when
  881 + requested to create files using RC4 encryption. This option
  882 + suppresses those warnings. In future versions of qpdf, qpdf
  883 + will refuse to create files with weak cryptography when this
  884 + flag is not given. See <xref linkend="ref.weak-crypto"/> for
  885 + additional details.
  886 + </para>
  887 + </listitem>
  888 + </varlistentry>
  889 + <varlistentry>
877 890 <term><option>--encrypt options --</option></term>
878 891 <listitem>
879 892 <para>
... ... @@ -3355,6 +3368,43 @@ outfile.pdf&lt;/option&gt;
3355 3368 </para>
3356 3369 </sect1>
3357 3370 </chapter>
  3371 + <chapter id="ref.weak-crypto">
  3372 + <title>Weak Cryptography</title>
  3373 + <para>
  3374 + Start with version 10.4, qpdf is taking steps to reduce the
  3375 + likelihood of a user <emphasis>accidentally</emphasis> creating PDF
  3376 + files with insecure cryptography but will continue to allow
  3377 + creation of such files indefinitely with explicit acknowledgment.
  3378 + </para>
  3379 + <para>
  3380 + The PDF file format makes use of RC4, which is known to be a weak
  3381 + cryptography algorithm, and MD5, which is a weak hashing algorithm.
  3382 + In version 10.4, qpdf generates warnings for some (but not all)
  3383 + cases of writing files with weak cryptography when invoked from the
  3384 + command-line. These warnings can be suppressed using the
  3385 + <option>--allow-weak-crypto</option> option.
  3386 + </para>
  3387 + <para>
  3388 + It is planned for qpdf version 11 to be stricter, making it an
  3389 + error to write files with insecure cryptography from the
  3390 + command-line tool in most cases without specifying the
  3391 + <option>--allow-weak-crypto</option> flag and also to require
  3392 + explicit steps when using the C++ library to enable use of insecure
  3393 + cryptography.
  3394 + </para>
  3395 + <para>
  3396 + Note that qpdf must always retain support for weak cryptographic
  3397 + algorithms since this is required for reading older PDF files that
  3398 + use it. Additionally, qpdf will always retain the ability to create
  3399 + files using weak cryptographic algorithms since, as a development
  3400 + tool, qpdf explicitly supports creating older or deprecated types
  3401 + of PDF files since these are sometimes needed to test or work with
  3402 + older versions of software. Even if other cryptography libraries
  3403 + drop support for RC4 or MD5, qpdf can always fall back to its
  3404 + internal implementations of those algorithms, so they are not going
  3405 + to disappear from qpdf.
  3406 + </para>
  3407 + </chapter>
3358 3408 <chapter id="ref.json">
3359 3409 <title>QPDF JSON</title>
3360 3410 <sect1 id="ref.json-overview">
... ... @@ -5072,6 +5122,27 @@ print &quot;\n&quot;;
5072 5122 <itemizedlist>
5073 5123 <listitem>
5074 5124 <para>
  5125 + Handling of Weak Cryptography Algorithms
  5126 + </para>
  5127 + <itemizedlist>
  5128 + <listitem>
  5129 + <para>
  5130 + From the qpdf CLI, the <option>--allow-weak-crypto</option>
  5131 + is now required to suppress a warning when explicitly
  5132 + creating PDF files using RC4 encryption. While qpdf will
  5133 + always retain the ability to read and write such files,
  5134 + doing so will require explicit acknowledgment moving
  5135 + forward. For qpdf 10.4, this change only affects the
  5136 + command-line tool. Starting in qpdf 11, there will be small
  5137 + API changes to require explicit acknowledgment in those
  5138 + cases as well. For additional information, see <xref
  5139 + linkend="ref.weak-crypto"/>.
  5140 + </para>
  5141 + </listitem>
  5142 + </itemizedlist>
  5143 + </listitem>
  5144 + <listitem>
  5145 + <para>
5075 5146 Bug Fixes
5076 5147 </para>
5077 5148 <itemizedlist>
... ...
qpdf/qpdf.cc
... ... @@ -141,6 +141,7 @@ struct Options
141 141 suppress_password_recovery(false),
142 142 password_mode(pm_auto),
143 143 allow_insecure(false),
  144 + allow_weak_crypto(false),
144 145 keylen(0),
145 146 r2_print(true),
146 147 r2_modify(true),
... ... @@ -242,6 +243,7 @@ struct Options
242 243 bool suppress_password_recovery;
243 244 password_mode_e password_mode;
244 245 bool allow_insecure;
  246 + bool allow_weak_crypto;
245 247 std::string user_password;
246 248 std::string owner_password;
247 249 int keylen;
... ... @@ -811,6 +813,7 @@ class ArgParser
811 813 void argDecrypt();
812 814 void argPasswordIsHexKey();
813 815 void argAllowInsecure();
  816 + void argAllowWeakCrypto();
814 817 void argPasswordMode(char* parameter);
815 818 void argSuppressPasswordRecovery();
816 819 void argCopyEncryption(char* parameter);
... ... @@ -1169,6 +1172,7 @@ ArgParser::initOptionTable()
1169 1172 (*t)["replace-input"] = oe_bare(&ArgParser::argReplaceInput);
1170 1173 (*t)["is-encrypted"] = oe_bare(&ArgParser::argIsEncrypted);
1171 1174 (*t)["requires-password"] = oe_bare(&ArgParser::argRequiresPassword);
  1175 + (*t)["allow-weak-crypto"] = oe_bare(&ArgParser::argAllowWeakCrypto);
1172 1176  
1173 1177 t = &this->encrypt40_option_table;
1174 1178 (*t)["--"] = oe_bare(&ArgParser::argEndEncrypt);
... ... @@ -1376,6 +1380,8 @@ ArgParser::argHelp()
1376 1380 << "--encryption-file-password=password\n"
1377 1381 << " password used to open the file from which encryption\n"
1378 1382 << " parameters are being copied\n"
  1383 + << "--allow-weak-crypto allow creation of files using weak cryptographic\n"
  1384 + << " algorithms\n"
1379 1385 << "--encrypt options -- generate an encrypted file\n"
1380 1386 << "--decrypt remove any encryption on the file\n"
1381 1387 << "--password-is-hex-key treat primary password option as a hex-encoded key\n"
... ... @@ -1502,6 +1508,11 @@ ArgParser::argHelp()
1502 1508 << "to be used even if not otherwise needed. This option is primarily useful\n"
1503 1509 << "for testing qpdf and has no other practical use.\n"
1504 1510 << "\n"
  1511 + << "A warning will be issued if you attempt to encrypt a file with a format that\n"
  1512 + << "uses a weak cryptographic algorithm such as RC4. To suppress the warning,\n"
  1513 + << "specify the option --allow-weak-crypto. This option is outside of encryption\n"
  1514 + << "options (e.g. --allow-week-crypto --encrypt u o 128 --)\n"
  1515 + << "\n"
1505 1516 << "\n"
1506 1517 << "Password Modes\n"
1507 1518 << "--------------\n"
... ... @@ -2069,6 +2080,12 @@ ArgParser::argAllowInsecure()
2069 2080 }
2070 2081  
2071 2082 void
  2083 +ArgParser::argAllowWeakCrypto()
  2084 +{
  2085 + o.allow_weak_crypto = true;
  2086 +}
  2087 +
  2088 +void
2072 2089 ArgParser::argCopyEncryption(char* parameter)
2073 2090 {
2074 2091 o.encryption_file = parameter;
... ... @@ -6253,6 +6270,26 @@ static void set_encryption_options(QPDF&amp; pdf, Options&amp; o, QPDFWriter&amp; w)
6253 6270 }
6254 6271 maybe_fix_write_password(R, o, o.user_password);
6255 6272 maybe_fix_write_password(R, o, o.owner_password);
  6273 + if ((R < 4) || ((R == 4) && (! o.use_aes)))
  6274 + {
  6275 + if (! o.allow_weak_crypto)
  6276 + {
  6277 + // Do not set exit code to EXIT_WARNING for this case as
  6278 + // this does not reflect a potential problem with the
  6279 + // input file.
  6280 + QTC::TC("qpdf", "qpdf weak crypto warning");
  6281 + std::cerr
  6282 + << whoami
  6283 + << ": writing a file with RC4, a weak cryptographic algorithm"
  6284 + << std::endl
  6285 + << "Please use 256-bit keys for better security."
  6286 + << std::endl
  6287 + << "Pass --allow-weak-crypto to suppress this warning."
  6288 + << std::endl
  6289 + << "This will become an error in a future version of qpdf."
  6290 + << std::endl;
  6291 + }
  6292 + }
6256 6293 switch (R)
6257 6294 {
6258 6295 case 2:
... ...
qpdf/qpdf.testcov
... ... @@ -598,3 +598,4 @@ check unclosed --pages 1
598 598 QPDF_pages findPage not found 0
599 599 qpdf overlay page with no resources 0
600 600 QPDFObjectHandle check ownership 0
  601 +qpdf weak crypto warning 0
... ...
qpdf/qtest/qpdf.test
... ... @@ -1358,7 +1358,8 @@ foreach my $file (qw(short-id long-id))
1358 1358 {
1359 1359 $td->runtest("encrypt $file.pdf",
1360 1360 {$td->COMMAND =>
1361   - "qpdf --encrypt '' pass 40 -- $file.pdf a.pdf"},
  1361 + "qpdf --allow-weak-crypto".
  1362 + " --encrypt '' pass 40 -- $file.pdf a.pdf"},
1362 1363 {$td->STRING => "",
1363 1364 $td->EXIT_STATUS => 0},
1364 1365 $td->NORMALIZE_NEWLINES);
... ... @@ -2057,7 +2058,8 @@ $td-&gt;notify(&quot;--- Split Pages ---&quot;);
2057 2058 my @sp_cases = (
2058 2059 [11, '%d at beginning', '', '%d_split-out.zdf'],
2059 2060 [11, '%d at end', '--qdf', 'split-out.zdf_%d'],
2060   - [11, '%d in middle', '--encrypt u o 128 --', 'a-%d-split-out.zdf'],
  2061 + [11, '%d in middle', '--allow-weak-crypto --encrypt u o 128 --',
  2062 + 'a-%d-split-out.zdf'],
2061 2063 [11, 'pdf extension', '', 'split-out.Pdf'],
2062 2064 [4, 'fallback', '--pages 11-pages.pdf 1-3 minimal.pdf --', 'split-out'],
2063 2065 [1, 'broken data', '--pages broken-lzw.pdf --', 'split-out.pdf',
... ... @@ -2718,6 +2720,7 @@ $td-&gt;runtest(&quot;check output&quot;,
2718 2720 $td->runtest("avoid respecification of password",
2719 2721 {$td->COMMAND =>
2720 2722 "qpdf --empty a.pdf --copy-encryption=20-pages.pdf" .
  2723 + " --allow-weak-crypto" .
2721 2724 " --encryption-file-password=user" .
2722 2725 " --pages 20-pages.pdf 1,z -- --static-id"},
2723 2726 {$td->STRING => "", $td->EXIT_STATUS => 0});
... ... @@ -3483,7 +3486,7 @@ for (my $n = 16; $n &lt;= 19; ++$n)
3483 3486 '-object-streams=preserve',
3484 3487 '-object-streams=generate')
3485 3488 {
3486   - foreach my $qdf ('-qdf', '', '-encrypt "" x 128 --')
  3489 + foreach my $qdf ('-qdf', '', '-allow-weak-crypto -encrypt "" x 128 --')
3487 3490 {
3488 3491 # 4 tests + 1 compare_pdfs * 36 cases
3489 3492 # 2 additional tests * 12 cases
... ... @@ -3716,19 +3719,22 @@ foreach my $f (qw(compressed-metadata.pdf enc-base.pdf))
3716 3719 check_metadata("a.pdf", 0, 1);
3717 3720 $td->runtest("encrypt normally",
3718 3721 {$td->COMMAND =>
3719   - "qpdf --encrypt '' o 128 -- a.pdf b.pdf"},
  3722 + "qpdf --allow-weak-crypto" .
  3723 + " --encrypt '' o 128 -- a.pdf b.pdf"},
3720 3724 {$td->STRING => "", $td->EXIT_STATUS => 0});
3721 3725 check_metadata("b.pdf", 1, 0);
3722 3726 unlink "b.pdf";
3723 3727 $td->runtest("encrypt V4",
3724 3728 {$td->COMMAND =>
3725   - "qpdf --encrypt '' o 128 --force-V4 -- a.pdf b.pdf"},
  3729 + "qpdf --allow-weak-crypto" .
  3730 + " --encrypt '' o 128 --force-V4 -- a.pdf b.pdf"},
3726 3731 {$td->STRING => "", $td->EXIT_STATUS => 0});
3727 3732 check_metadata("b.pdf", 1, 0);
3728 3733 unlink "b.pdf";
3729 3734 $td->runtest("encrypt with cleartext metadata",
3730 3735 {$td->COMMAND =>
3731   - "qpdf --encrypt '' o 128 --cleartext-metadata --" .
  3736 + "qpdf --allow-weak-crypto" .
  3737 + " --encrypt '' o 128 --cleartext-metadata --" .
3732 3738 " a.pdf b.pdf"},
3733 3739 {$td->STRING => "", $td->EXIT_STATUS => 0});
3734 3740 check_metadata("b.pdf", 1, 1);
... ... @@ -3753,6 +3759,31 @@ foreach my $f (qw(compressed-metadata.pdf enc-base.pdf))
3753 3759  
3754 3760 show_ntests();
3755 3761 # ----------
  3762 +$td->notify("--- Weak Cryptography ---");
  3763 +$n_tests += 4;
  3764 +$td->runtest("256-bit: no warning",
  3765 + {$td->COMMAND => 'qpdf --encrypt "" "" 256 -- minimal.pdf a.pdf'},
  3766 + {$td->STRING => "", $td->EXIT_STATUS => 0},
  3767 + $td->NORMALIZE_NEWLINES);
  3768 +$td->runtest("128-bit with AES: no warning",
  3769 + {$td->COMMAND => 'qpdf --encrypt "" "" 128 --use-aes=y --' .
  3770 + ' minimal.pdf a.pdf'},
  3771 + {$td->STRING => "", $td->EXIT_STATUS => 0},
  3772 + $td->NORMALIZE_NEWLINES);
  3773 +# Note: we intentionally have exit status 0 for this warning.
  3774 +$td->runtest("128-bit without AES: warning",
  3775 + {$td->COMMAND => 'qpdf --encrypt "" "" 128 -- minimal.pdf a.pdf'},
  3776 + {$td->REGEXP => "Pass --allow-weak-crypto to suppress",
  3777 + $td->EXIT_STATUS => 0},
  3778 + $td->NORMALIZE_NEWLINES);
  3779 +$td->runtest("40-bit: warning",
  3780 + {$td->COMMAND => 'qpdf --encrypt "" "" 40 -- minimal.pdf a.pdf'},
  3781 + {$td->REGEXP => "Pass --allow-weak-crypto to suppress",
  3782 + $td->EXIT_STATUS => 0},
  3783 + $td->NORMALIZE_NEWLINES);
  3784 +
  3785 +show_ntests();
  3786 +# ----------
3756 3787 $td->notify("--- Linearization Tests ---");
3757 3788 # $n_tests incremented after initialization of @linearized_files and
3758 3789 # @to_linearize.
... ... @@ -4128,7 +4159,8 @@ foreach my $d (@encrypted_files)
4128 4159 $enc_json =~ s/---opm---/$opm/;
4129 4160 $enc_json =~ s/---upm---/$upm/;
4130 4161  
4131   - my $eflags = "-encrypt \"$upass\" \"$opass\" $bits $xeflags --";
  4162 + my $eflags = "--allow-weak-crypto" .
  4163 + " -encrypt \"$upass\" \"$opass\" $bits $xeflags --";
4132 4164 if (($opass eq "") && ($bits == 256))
4133 4165 {
4134 4166 $eflags =~ s/--$/--allow-insecure --/;
... ... @@ -4391,7 +4423,7 @@ foreach my $d ([&#39;--force-V4&#39;, &#39;V4&#39;],
4391 4423 my ($args, $out) = @$d;
4392 4424 $td->runtest("encrypt $args",
4393 4425 {$td->COMMAND => "qpdf --static-aes-iv --static-id" .
4394   - " --encrypt '' '' 128 $args --" .
  4426 + " --allow-weak-crypto --encrypt '' '' 128 $args --" .
4395 4427 " enc-base.pdf a.pdf"},
4396 4428 {$td->STRING => "", $td->EXIT_STATUS => 0});
4397 4429 $td->runtest("check output",
... ... @@ -4677,6 +4709,7 @@ foreach my $d (@unicode_pw_cases)
4677 4709 $td->runtest("encode $bits, $pw, $w_encoding",
4678 4710 {$td->COMMAND =>
4679 4711 "qpdf $xargs --static-id --static-aes-iv" .
  4712 + " --allow-weak-crypto" .
4680 4713 " --encrypt $upass o $bits -- minimal.pdf a.pdf"},
4681 4714 {$td->STRING => $exp, $td->EXIT_STATUS => ($exp ? 2 : 0)},
4682 4715 $td->NORMALIZE_NEWLINES);
... ... @@ -4718,7 +4751,8 @@ $n_tests += 5;
4718 4751  
4719 4752 $td->runtest("bytes fallback warning",
4720 4753 {$td->COMMAND =>
4721   - "qpdf --encrypt \@password-bare-complex-utf8 o 128 --" .
  4754 + "qpdf --allow-weak-crypto" .
  4755 + " --encrypt \@password-bare-complex-utf8 o 128 --" .
4722 4756 " minimal.pdf a.pdf"},
4723 4757 {$td->FILE => "bytes-fallback.out", $td->EXIT_STATUS => 0},
4724 4758 $td->NORMALIZE_NEWLINES);
... ... @@ -4814,9 +4848,9 @@ my @flags = ([&quot;-qdf&quot;, # 1
4814 4848 "decrypted"],
4815 4849 ["-linearize", # 9
4816 4850 "linearized"],
4817   - ["-encrypt \"\" owner 128 --", # 10
  4851 + ["-allow-weak-crypto -encrypt \"\" owner 128 --", # 10
4818 4852 "encrypted"],
4819   - ["-linearize -encrypt \"\" o 128 --", # 11
  4853 + ["-linearize -allow-weak-crypto -encrypt \"\" o 128 --", # 11
4820 4854 "linearized and encrypted"],
4821 4855 ["", # 12
4822 4856 "no arguments"],
... ... @@ -4985,9 +5019,15 @@ $n_tests += 2;
4985 5019 $n_tests += 12;
4986 5020 foreach my $i (qw(40 128 256))
4987 5021 {
  5022 + my $x = "";
  5023 + if ($i < 256)
  5024 + {
  5025 + $x = "--allow-weak-crypto";
  5026 + }
4988 5027 $td->runtest("encrypt $i",
4989 5028 {$td->COMMAND =>
4990   - "qpdf --encrypt '' o $i -- digitally-signed.pdf a.pdf"},
  5029 + "qpdf $x --encrypt '' o $i --" .
  5030 + " digitally-signed.pdf a.pdf"},
4991 5031 {$td->STRING => "",
4992 5032 $td->EXIT_STATUS => 0});
4993 5033 $td->runtest("find desired contents (encrypt $i)",
... ... @@ -5010,9 +5050,15 @@ foreach my $i (qw(40 128 256))
5010 5050 $n_tests += 15;
5011 5051 foreach my $i (qw(40 128 256))
5012 5052 {
  5053 + my $x = "";
  5054 + if ($i < 256)
  5055 + {
  5056 + $x = "--allow-weak-crypto";
  5057 + }
5013 5058 $td->runtest("non sig dict encrypt $i",
5014 5059 {$td->COMMAND =>
5015   - "qpdf --encrypt '' o $i -- comment-annotation.pdf a.pdf"},
  5060 + "qpdf $x --encrypt '' o $i --" .
  5061 + " comment-annotation.pdf a.pdf"},
5016 5062 {$td->STRING => "",
5017 5063 $td->EXIT_STATUS => 0});
5018 5064 $td->runtest("plain text not found due to encryption (non sig dict encrypt $i)",
... ...