2
1
mirror of https://github.com/qpdf/qpdf.git synced 2024-10-31 19:02:30 +00:00

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

This commit is contained in:
Jay Berkenbilt 2021-11-10 17:57:12 -05:00
parent 37916f3925
commit 750aca5b94
13 changed files with 267 additions and 19 deletions

View File

@ -1,3 +1,9 @@
2021-11-10 Jay Berkenbilt <ejb@ql.org>
* Add --allow-weak-crypto option to suppress warnings about use of
weak cryptographic algorithms. Update documentation around this
issue. Fixes #358.
2021-11-07 Jay Berkenbilt <ejb@ql.org> 2021-11-07 Jay Berkenbilt <ejb@ql.org>
* Relax xref recovery logic a bit so that files whose objects are * Relax xref recovery logic a bit so that files whose objects are

View File

@ -62,6 +62,10 @@ For example, if you want to guarantee that the GnuTLS crypto provider is used, y
Please see the section on crypto providers in the manual for more details. Please see the section on crypto providers in the manual for more details.
## Note about weak cryptographic algorithms
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.
# Building from a pristine checkout # Building from a pristine checkout
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. 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.

17
TODO
View File

@ -187,6 +187,23 @@ Comments appear in the code prefixed by "ABI"
before copying, though maybe we don't because it could cause before copying, though maybe we don't because it could cause
multiple copies to be made...usually it's better to handle that multiple copies to be made...usually it's better to handle that
explicitly. explicitly.
* Deal with weak cryptographic algorithms:
* Github issue #576
* Add something to QPDFWriter that you must call in order to allow
creation of files with insecure crypto. Maybe
QPDFWriter::allowWeakCrypto. Call this when --allow-weak-crypto is
passed and probably also when copying encryption by default from
an input file.
* Change deterministic id to use something other than MD5 but allow
the old way for compatibility -- maybe rename the method to force
the developer to make a choice
* Find other uses of MD5 and find the ones that are discretionary,
if any
* Have QPDFWriter raise an exception if it's about to write using
weak crypto and hasn't been given permission
* Search for --allow-weak-crypto in the manual and in qpdf.cc's help
information
* Update the ref.weak-crypto section of the manual
Page splitting/merging Page splitting/merging
====================== ======================

View File

@ -107,7 +107,7 @@ FuzzHelper::testWrite()
w->setStaticID(true); w->setStaticID(true);
w->setObjectStreamMode(qpdf_o_disable); w->setObjectStreamMode(qpdf_o_disable);
w->setR3EncryptionParameters( w->setR3EncryptionParameters(
"u", "o", true, true, qpdf_r3p_full, qpdf_r3m_all); "u", "o", true, true, true, true, true, true, qpdf_r3p_full);
doWrite(w); doWrite(w);
q = getQpdf(); q = getQpdf();

View File

@ -69,6 +69,9 @@ class QPDF_DLL_CLASS QPDFCryptoImpl
// Encryption/Decryption // Encryption/Decryption
// QPDF must support RC4 to be able to work with older PDF files
// and readers. Search for RC4 in README.md
// key_len of -1 means treat key_data as a null-terminated string // key_len of -1 means treat key_data as a null-terminated string
QPDF_DLL QPDF_DLL
virtual void RC4_init(unsigned char const* key_data, int key_len = -1) = 0; virtual void RC4_init(unsigned char const* key_data, int key_len = -1) = 0;

View File

@ -359,6 +359,16 @@ class QPDFWriter
// this from your own application, QUtil contains many transcoding // this from your own application, QUtil contains many transcoding
// functions that could be useful to you, most notably // functions that could be useful to you, most notably
// utf8_to_pdf_doc. // utf8_to_pdf_doc.
// R3 uses RC4, which is a weak cryptographic algorithm. Don't use
// it unless you have to.
QPDF_DLL
void setR2EncryptionParameters(
char const* user_password, char const* owner_password,
bool allow_print, bool allow_modify,
bool allow_extract, bool allow_annotate);
// R3 uses RC4, which is a weak cryptographic algorithm. Don't use
// it unless you have to.
QPDF_DLL QPDF_DLL
void setR3EncryptionParameters( void setR3EncryptionParameters(
char const* user_password, char const* owner_password, char const* user_password, char const* owner_password,
@ -366,6 +376,8 @@ class QPDFWriter
bool allow_assemble, bool allow_annotate_and_form, bool allow_assemble, bool allow_annotate_and_form,
bool allow_form_filling, bool allow_modify_other, bool allow_form_filling, bool allow_modify_other,
qpdf_r3_print_e print); qpdf_r3_print_e print);
// R4 uses RC4, which is a weak cryptographic algorithm, when
// use_aes=false. Don't use it unless you have to.
QPDF_DLL QPDF_DLL
void setR4EncryptionParameters( void setR4EncryptionParameters(
char const* user_password, char const* owner_password, char const* user_password, char const* owner_password,
@ -392,28 +404,27 @@ class QPDFWriter
qpdf_r3_print_e print, bool encrypt_metadata_aes); qpdf_r3_print_e print, bool encrypt_metadata_aes);
// Pre qpdf 8.4.0 API // Pre qpdf 8.4.0 API
QPDF_DLL [[deprecated("see newer API above")]]
void setR2EncryptionParameters(
char const* user_password, char const* owner_password,
bool allow_print, bool allow_modify,
bool allow_extract, bool allow_annotate);
QPDF_DLL QPDF_DLL
void setR3EncryptionParameters( void setR3EncryptionParameters(
char const* user_password, char const* owner_password, char const* user_password, char const* owner_password,
bool allow_accessibility, bool allow_extract, bool allow_accessibility, bool allow_extract,
qpdf_r3_print_e print, qpdf_r3_modify_e modify); qpdf_r3_print_e print, qpdf_r3_modify_e modify);
[[deprecated("see newer API above")]]
QPDF_DLL QPDF_DLL
void setR4EncryptionParameters( void setR4EncryptionParameters(
char const* user_password, char const* owner_password, char const* user_password, char const* owner_password,
bool allow_accessibility, bool allow_extract, bool allow_accessibility, bool allow_extract,
qpdf_r3_print_e print, qpdf_r3_modify_e modify, qpdf_r3_print_e print, qpdf_r3_modify_e modify,
bool encrypt_metadata, bool use_aes); bool encrypt_metadata, bool use_aes);
[[deprecated("see newer API above")]]
QPDF_DLL QPDF_DLL
void setR5EncryptionParameters( void setR5EncryptionParameters(
char const* user_password, char const* owner_password, char const* user_password, char const* owner_password,
bool allow_accessibility, bool allow_extract, bool allow_accessibility, bool allow_extract,
qpdf_r3_print_e print, qpdf_r3_modify_e modify, qpdf_r3_print_e print, qpdf_r3_modify_e modify,
bool encrypt_metadata); bool encrypt_metadata);
[[deprecated("see newer API above")]]
QPDF_DLL QPDF_DLL
void setR6EncryptionParameters( void setR6EncryptionParameters(
char const* user_password, char const* owner_password, char const* user_password, char const* owner_password,

11
lgtm.yml Normal file
View File

@ -0,0 +1,11 @@
# See https://lgtm.com/help/lgtm/lgtm.yml-configuration-file
# Suppress alerts for weak cryptographic algorithms. The PDF file
# format used to rely on various weak algorithms. It is no longer
# necessary to use them when creating encrypted PDF files, but qpdf
# must always retain support for those algorithms in order to be able
# to read older files. qpdf issues warnings to try to prevent users
# from creating new files with weak encryption algorithms.
queries:
- exclude: cpp/weak-cryptographic-algorithm

View File

@ -34,6 +34,7 @@ Pl_RC4::write(unsigned char* data, size_t len)
size_t bytes = size_t bytes =
(bytes_left < this->out_bufsize ? bytes_left : out_bufsize); (bytes_left < this->out_bufsize ? bytes_left : out_bufsize);
bytes_left -= bytes; bytes_left -= bytes;
// lgtm[cpp/weak-cryptographic-algorithm]
rc4.process(p, bytes, outbuf.getPointer()); rc4.process(p, bytes, outbuf.getPointer());
p += bytes; p += bytes;
getNext()->write(outbuf.getPointer(), bytes); getNext()->write(outbuf.getPointer(), bytes);

View File

@ -702,10 +702,20 @@ void qpdf_set_r3_encryption_parameters(
QPDF_BOOL allow_accessibility, QPDF_BOOL allow_extract, QPDF_BOOL allow_accessibility, QPDF_BOOL allow_extract,
qpdf_r3_print_e print, qpdf_r3_modify_e modify) qpdf_r3_print_e print, qpdf_r3_modify_e modify)
{ {
#ifdef _MSC_VER
# pragma warning (disable: 4996)
#endif
#if (defined(__GNUC__) || defined(__clang__))
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif
qpdf->qpdf_writer->setR3EncryptionParameters( qpdf->qpdf_writer->setR3EncryptionParameters(
user_password, owner_password, user_password, owner_password,
allow_accessibility != QPDF_FALSE, allow_extract != QPDF_FALSE, allow_accessibility != QPDF_FALSE, allow_extract != QPDF_FALSE,
print, modify); print, modify);
#if (defined(__GNUC__) || defined(__clang__))
# pragma GCC diagnostic pop
#endif
} }
void qpdf_set_r4_encryption_parameters( void qpdf_set_r4_encryption_parameters(
@ -714,11 +724,21 @@ void qpdf_set_r4_encryption_parameters(
qpdf_r3_print_e print, qpdf_r3_modify_e modify, qpdf_r3_print_e print, qpdf_r3_modify_e modify,
QPDF_BOOL encrypt_metadata, QPDF_BOOL use_aes) QPDF_BOOL encrypt_metadata, QPDF_BOOL use_aes)
{ {
#ifdef _MSC_VER
# pragma warning (disable: 4996)
#endif
#if (defined(__GNUC__) || defined(__clang__))
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif
qpdf->qpdf_writer->setR4EncryptionParameters( qpdf->qpdf_writer->setR4EncryptionParameters(
user_password, owner_password, user_password, owner_password,
allow_accessibility != QPDF_FALSE, allow_extract != QPDF_FALSE, allow_accessibility != QPDF_FALSE, allow_extract != QPDF_FALSE,
print, modify, print, modify,
encrypt_metadata != QPDF_FALSE, use_aes != QPDF_FALSE); encrypt_metadata != QPDF_FALSE, use_aes != QPDF_FALSE);
#if (defined(__GNUC__) || defined(__clang__))
# pragma GCC diagnostic pop
#endif
} }
void qpdf_set_r5_encryption_parameters( void qpdf_set_r5_encryption_parameters(
@ -727,11 +747,21 @@ void qpdf_set_r5_encryption_parameters(
qpdf_r3_print_e print, qpdf_r3_modify_e modify, qpdf_r3_print_e print, qpdf_r3_modify_e modify,
QPDF_BOOL encrypt_metadata) QPDF_BOOL encrypt_metadata)
{ {
#ifdef _MSC_VER
# pragma warning (disable: 4996)
#endif
#if (defined(__GNUC__) || defined(__clang__))
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif
qpdf->qpdf_writer->setR5EncryptionParameters( qpdf->qpdf_writer->setR5EncryptionParameters(
user_password, owner_password, user_password, owner_password,
allow_accessibility != QPDF_FALSE, allow_extract != QPDF_FALSE, allow_accessibility != QPDF_FALSE, allow_extract != QPDF_FALSE,
print, modify, print, modify,
encrypt_metadata != QPDF_FALSE); encrypt_metadata != QPDF_FALSE);
#if (defined(__GNUC__) || defined(__clang__))
# pragma GCC diagnostic pop
#endif
} }
void qpdf_set_r6_encryption_parameters( void qpdf_set_r6_encryption_parameters(
@ -740,10 +770,20 @@ void qpdf_set_r6_encryption_parameters(
qpdf_r3_print_e print, qpdf_r3_modify_e modify, qpdf_r3_print_e print, qpdf_r3_modify_e modify,
QPDF_BOOL encrypt_metadata) QPDF_BOOL encrypt_metadata)
{ {
#ifdef _MSC_VER
# pragma warning (disable: 4996)
#endif
#if (defined(__GNUC__) || defined(__clang__))
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif
qpdf->qpdf_writer->setR6EncryptionParameters( qpdf->qpdf_writer->setR6EncryptionParameters(
user_password, owner_password, user_password, owner_password,
allow_accessibility != QPDF_FALSE, allow_extract != QPDF_FALSE, allow_accessibility != QPDF_FALSE, allow_extract != QPDF_FALSE,
print, modify, encrypt_metadata != QPDF_FALSE); print, modify, encrypt_metadata != QPDF_FALSE);
#if (defined(__GNUC__) || defined(__clang__))
# pragma GCC diagnostic pop
#endif
} }
void qpdf_set_linearization(qpdf_data qpdf, QPDF_BOOL value) void qpdf_set_linearization(qpdf_data qpdf, QPDF_BOOL value)

View File

@ -873,6 +873,19 @@ make
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
<varlistentry>
<term><option>--allow-weak-crypto</option></term>
<listitem>
<para>
Starting with version 10.4, qpdf issues warnings when
requested to create files using RC4 encryption. This option
suppresses those warnings. In future versions of qpdf, qpdf
will refuse to create files with weak cryptography when this
flag is not given. See <xref linkend="ref.weak-crypto"/> for
additional details.
</para>
</listitem>
</varlistentry>
<varlistentry> <varlistentry>
<term><option>--encrypt options --</option></term> <term><option>--encrypt options --</option></term>
<listitem> <listitem>
@ -3355,6 +3368,43 @@ outfile.pdf</option>
</para> </para>
</sect1> </sect1>
</chapter> </chapter>
<chapter id="ref.weak-crypto">
<title>Weak Cryptography</title>
<para>
Start with version 10.4, qpdf is taking steps to reduce the
likelihood of a user <emphasis>accidentally</emphasis> creating PDF
files with insecure cryptography but will continue to allow
creation of such files indefinitely with explicit acknowledgment.
</para>
<para>
The PDF file format makes use of RC4, which is known to be a weak
cryptography algorithm, and MD5, which is a weak hashing algorithm.
In version 10.4, qpdf generates warnings for some (but not all)
cases of writing files with weak cryptography when invoked from the
command-line. These warnings can be suppressed using the
<option>--allow-weak-crypto</option> option.
</para>
<para>
It is planned for qpdf version 11 to be stricter, making it an
error to write files with insecure cryptography from the
command-line tool in most cases without specifying the
<option>--allow-weak-crypto</option> flag and also to require
explicit steps when using the C++ library to enable use of insecure
cryptography.
</para>
<para>
Note that qpdf must always retain support for weak cryptographic
algorithms since this is required for reading older PDF files that
use it. Additionally, qpdf will always retain the ability to create
files using weak cryptographic algorithms since, as a development
tool, qpdf explicitly supports creating older or deprecated types
of PDF files since these are sometimes needed to test or work with
older versions of software. Even if other cryptography libraries
drop support for RC4 or MD5, qpdf can always fall back to its
internal implementations of those algorithms, so they are not going
to disappear from qpdf.
</para>
</chapter>
<chapter id="ref.json"> <chapter id="ref.json">
<title>QPDF JSON</title> <title>QPDF JSON</title>
<sect1 id="ref.json-overview"> <sect1 id="ref.json-overview">
@ -5070,6 +5120,27 @@ print "\n";
<term>10.4.0: Month dd, YYYY</term> <term>10.4.0: Month dd, YYYY</term>
<listitem> <listitem>
<itemizedlist> <itemizedlist>
<listitem>
<para>
Handling of Weak Cryptography Algorithms
</para>
<itemizedlist>
<listitem>
<para>
From the qpdf CLI, the <option>--allow-weak-crypto</option>
is now required to suppress a warning when explicitly
creating PDF files using RC4 encryption. While qpdf will
always retain the ability to read and write such files,
doing so will require explicit acknowledgment moving
forward. For qpdf 10.4, this change only affects the
command-line tool. Starting in qpdf 11, there will be small
API changes to require explicit acknowledgment in those
cases as well. For additional information, see <xref
linkend="ref.weak-crypto"/>.
</para>
</listitem>
</itemizedlist>
</listitem>
<listitem> <listitem>
<para> <para>
Bug Fixes Bug Fixes

View File

@ -141,6 +141,7 @@ struct Options
suppress_password_recovery(false), suppress_password_recovery(false),
password_mode(pm_auto), password_mode(pm_auto),
allow_insecure(false), allow_insecure(false),
allow_weak_crypto(false),
keylen(0), keylen(0),
r2_print(true), r2_print(true),
r2_modify(true), r2_modify(true),
@ -242,6 +243,7 @@ struct Options
bool suppress_password_recovery; bool suppress_password_recovery;
password_mode_e password_mode; password_mode_e password_mode;
bool allow_insecure; bool allow_insecure;
bool allow_weak_crypto;
std::string user_password; std::string user_password;
std::string owner_password; std::string owner_password;
int keylen; int keylen;
@ -811,6 +813,7 @@ class ArgParser
void argDecrypt(); void argDecrypt();
void argPasswordIsHexKey(); void argPasswordIsHexKey();
void argAllowInsecure(); void argAllowInsecure();
void argAllowWeakCrypto();
void argPasswordMode(char* parameter); void argPasswordMode(char* parameter);
void argSuppressPasswordRecovery(); void argSuppressPasswordRecovery();
void argCopyEncryption(char* parameter); void argCopyEncryption(char* parameter);
@ -1169,6 +1172,7 @@ ArgParser::initOptionTable()
(*t)["replace-input"] = oe_bare(&ArgParser::argReplaceInput); (*t)["replace-input"] = oe_bare(&ArgParser::argReplaceInput);
(*t)["is-encrypted"] = oe_bare(&ArgParser::argIsEncrypted); (*t)["is-encrypted"] = oe_bare(&ArgParser::argIsEncrypted);
(*t)["requires-password"] = oe_bare(&ArgParser::argRequiresPassword); (*t)["requires-password"] = oe_bare(&ArgParser::argRequiresPassword);
(*t)["allow-weak-crypto"] = oe_bare(&ArgParser::argAllowWeakCrypto);
t = &this->encrypt40_option_table; t = &this->encrypt40_option_table;
(*t)["--"] = oe_bare(&ArgParser::argEndEncrypt); (*t)["--"] = oe_bare(&ArgParser::argEndEncrypt);
@ -1376,6 +1380,8 @@ ArgParser::argHelp()
<< "--encryption-file-password=password\n" << "--encryption-file-password=password\n"
<< " password used to open the file from which encryption\n" << " password used to open the file from which encryption\n"
<< " parameters are being copied\n" << " parameters are being copied\n"
<< "--allow-weak-crypto allow creation of files using weak cryptographic\n"
<< " algorithms\n"
<< "--encrypt options -- generate an encrypted file\n" << "--encrypt options -- generate an encrypted file\n"
<< "--decrypt remove any encryption on the file\n" << "--decrypt remove any encryption on the file\n"
<< "--password-is-hex-key treat primary password option as a hex-encoded key\n" << "--password-is-hex-key treat primary password option as a hex-encoded key\n"
@ -1502,6 +1508,11 @@ ArgParser::argHelp()
<< "to be used even if not otherwise needed. This option is primarily useful\n" << "to be used even if not otherwise needed. This option is primarily useful\n"
<< "for testing qpdf and has no other practical use.\n" << "for testing qpdf and has no other practical use.\n"
<< "\n" << "\n"
<< "A warning will be issued if you attempt to encrypt a file with a format that\n"
<< "uses a weak cryptographic algorithm such as RC4. To suppress the warning,\n"
<< "specify the option --allow-weak-crypto. This option is outside of encryption\n"
<< "options (e.g. --allow-week-crypto --encrypt u o 128 --)\n"
<< "\n"
<< "\n" << "\n"
<< "Password Modes\n" << "Password Modes\n"
<< "--------------\n" << "--------------\n"
@ -2068,6 +2079,12 @@ ArgParser::argAllowInsecure()
o.allow_insecure = true; o.allow_insecure = true;
} }
void
ArgParser::argAllowWeakCrypto()
{
o.allow_weak_crypto = true;
}
void void
ArgParser::argCopyEncryption(char* parameter) ArgParser::argCopyEncryption(char* parameter)
{ {
@ -6253,6 +6270,26 @@ static void set_encryption_options(QPDF& pdf, Options& o, QPDFWriter& w)
} }
maybe_fix_write_password(R, o, o.user_password); maybe_fix_write_password(R, o, o.user_password);
maybe_fix_write_password(R, o, o.owner_password); maybe_fix_write_password(R, o, o.owner_password);
if ((R < 4) || ((R == 4) && (! o.use_aes)))
{
if (! o.allow_weak_crypto)
{
// Do not set exit code to EXIT_WARNING for this case as
// this does not reflect a potential problem with the
// input file.
QTC::TC("qpdf", "qpdf weak crypto warning");
std::cerr
<< whoami
<< ": writing a file with RC4, a weak cryptographic algorithm"
<< std::endl
<< "Please use 256-bit keys for better security."
<< std::endl
<< "Pass --allow-weak-crypto to suppress this warning."
<< std::endl
<< "This will become an error in a future version of qpdf."
<< std::endl;
}
}
switch (R) switch (R)
{ {
case 2: case 2:

View File

@ -598,3 +598,4 @@ check unclosed --pages 1
QPDF_pages findPage not found 0 QPDF_pages findPage not found 0
qpdf overlay page with no resources 0 qpdf overlay page with no resources 0
QPDFObjectHandle check ownership 0 QPDFObjectHandle check ownership 0
qpdf weak crypto warning 0

View File

@ -1358,7 +1358,8 @@ foreach my $file (qw(short-id long-id))
{ {
$td->runtest("encrypt $file.pdf", $td->runtest("encrypt $file.pdf",
{$td->COMMAND => {$td->COMMAND =>
"qpdf --encrypt '' pass 40 -- $file.pdf a.pdf"}, "qpdf --allow-weak-crypto".
" --encrypt '' pass 40 -- $file.pdf a.pdf"},
{$td->STRING => "", {$td->STRING => "",
$td->EXIT_STATUS => 0}, $td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES); $td->NORMALIZE_NEWLINES);
@ -2057,7 +2058,8 @@ $td->notify("--- Split Pages ---");
my @sp_cases = ( my @sp_cases = (
[11, '%d at beginning', '', '%d_split-out.zdf'], [11, '%d at beginning', '', '%d_split-out.zdf'],
[11, '%d at end', '--qdf', 'split-out.zdf_%d'], [11, '%d at end', '--qdf', 'split-out.zdf_%d'],
[11, '%d in middle', '--encrypt u o 128 --', 'a-%d-split-out.zdf'], [11, '%d in middle', '--allow-weak-crypto --encrypt u o 128 --',
'a-%d-split-out.zdf'],
[11, 'pdf extension', '', 'split-out.Pdf'], [11, 'pdf extension', '', 'split-out.Pdf'],
[4, 'fallback', '--pages 11-pages.pdf 1-3 minimal.pdf --', 'split-out'], [4, 'fallback', '--pages 11-pages.pdf 1-3 minimal.pdf --', 'split-out'],
[1, 'broken data', '--pages broken-lzw.pdf --', 'split-out.pdf', [1, 'broken data', '--pages broken-lzw.pdf --', 'split-out.pdf',
@ -2718,6 +2720,7 @@ $td->runtest("check output",
$td->runtest("avoid respecification of password", $td->runtest("avoid respecification of password",
{$td->COMMAND => {$td->COMMAND =>
"qpdf --empty a.pdf --copy-encryption=20-pages.pdf" . "qpdf --empty a.pdf --copy-encryption=20-pages.pdf" .
" --allow-weak-crypto" .
" --encryption-file-password=user" . " --encryption-file-password=user" .
" --pages 20-pages.pdf 1,z -- --static-id"}, " --pages 20-pages.pdf 1,z -- --static-id"},
{$td->STRING => "", $td->EXIT_STATUS => 0}); {$td->STRING => "", $td->EXIT_STATUS => 0});
@ -3483,7 +3486,7 @@ for (my $n = 16; $n <= 19; ++$n)
'-object-streams=preserve', '-object-streams=preserve',
'-object-streams=generate') '-object-streams=generate')
{ {
foreach my $qdf ('-qdf', '', '-encrypt "" x 128 --') foreach my $qdf ('-qdf', '', '-allow-weak-crypto -encrypt "" x 128 --')
{ {
# 4 tests + 1 compare_pdfs * 36 cases # 4 tests + 1 compare_pdfs * 36 cases
# 2 additional tests * 12 cases # 2 additional tests * 12 cases
@ -3716,19 +3719,22 @@ foreach my $f (qw(compressed-metadata.pdf enc-base.pdf))
check_metadata("a.pdf", 0, 1); check_metadata("a.pdf", 0, 1);
$td->runtest("encrypt normally", $td->runtest("encrypt normally",
{$td->COMMAND => {$td->COMMAND =>
"qpdf --encrypt '' o 128 -- a.pdf b.pdf"}, "qpdf --allow-weak-crypto" .
" --encrypt '' o 128 -- a.pdf b.pdf"},
{$td->STRING => "", $td->EXIT_STATUS => 0}); {$td->STRING => "", $td->EXIT_STATUS => 0});
check_metadata("b.pdf", 1, 0); check_metadata("b.pdf", 1, 0);
unlink "b.pdf"; unlink "b.pdf";
$td->runtest("encrypt V4", $td->runtest("encrypt V4",
{$td->COMMAND => {$td->COMMAND =>
"qpdf --encrypt '' o 128 --force-V4 -- a.pdf b.pdf"}, "qpdf --allow-weak-crypto" .
" --encrypt '' o 128 --force-V4 -- a.pdf b.pdf"},
{$td->STRING => "", $td->EXIT_STATUS => 0}); {$td->STRING => "", $td->EXIT_STATUS => 0});
check_metadata("b.pdf", 1, 0); check_metadata("b.pdf", 1, 0);
unlink "b.pdf"; unlink "b.pdf";
$td->runtest("encrypt with cleartext metadata", $td->runtest("encrypt with cleartext metadata",
{$td->COMMAND => {$td->COMMAND =>
"qpdf --encrypt '' o 128 --cleartext-metadata --" . "qpdf --allow-weak-crypto" .
" --encrypt '' o 128 --cleartext-metadata --" .
" a.pdf b.pdf"}, " a.pdf b.pdf"},
{$td->STRING => "", $td->EXIT_STATUS => 0}); {$td->STRING => "", $td->EXIT_STATUS => 0});
check_metadata("b.pdf", 1, 1); check_metadata("b.pdf", 1, 1);
@ -3751,6 +3757,31 @@ foreach my $f (qw(compressed-metadata.pdf enc-base.pdf))
} }
} }
show_ntests();
# ----------
$td->notify("--- Weak Cryptography ---");
$n_tests += 4;
$td->runtest("256-bit: no warning",
{$td->COMMAND => 'qpdf --encrypt "" "" 256 -- minimal.pdf a.pdf'},
{$td->STRING => "", $td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
$td->runtest("128-bit with AES: no warning",
{$td->COMMAND => 'qpdf --encrypt "" "" 128 --use-aes=y --' .
' minimal.pdf a.pdf'},
{$td->STRING => "", $td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
# Note: we intentionally have exit status 0 for this warning.
$td->runtest("128-bit without AES: warning",
{$td->COMMAND => 'qpdf --encrypt "" "" 128 -- minimal.pdf a.pdf'},
{$td->REGEXP => "Pass --allow-weak-crypto to suppress",
$td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
$td->runtest("40-bit: warning",
{$td->COMMAND => 'qpdf --encrypt "" "" 40 -- minimal.pdf a.pdf'},
{$td->REGEXP => "Pass --allow-weak-crypto to suppress",
$td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
show_ntests(); show_ntests();
# ---------- # ----------
$td->notify("--- Linearization Tests ---"); $td->notify("--- Linearization Tests ---");
@ -4128,7 +4159,8 @@ foreach my $d (@encrypted_files)
$enc_json =~ s/---opm---/$opm/; $enc_json =~ s/---opm---/$opm/;
$enc_json =~ s/---upm---/$upm/; $enc_json =~ s/---upm---/$upm/;
my $eflags = "-encrypt \"$upass\" \"$opass\" $bits $xeflags --"; my $eflags = "--allow-weak-crypto" .
" -encrypt \"$upass\" \"$opass\" $bits $xeflags --";
if (($opass eq "") && ($bits == 256)) if (($opass eq "") && ($bits == 256))
{ {
$eflags =~ s/--$/--allow-insecure --/; $eflags =~ s/--$/--allow-insecure --/;
@ -4391,7 +4423,7 @@ foreach my $d (['--force-V4', 'V4'],
my ($args, $out) = @$d; my ($args, $out) = @$d;
$td->runtest("encrypt $args", $td->runtest("encrypt $args",
{$td->COMMAND => "qpdf --static-aes-iv --static-id" . {$td->COMMAND => "qpdf --static-aes-iv --static-id" .
" --encrypt '' '' 128 $args --" . " --allow-weak-crypto --encrypt '' '' 128 $args --" .
" enc-base.pdf a.pdf"}, " enc-base.pdf a.pdf"},
{$td->STRING => "", $td->EXIT_STATUS => 0}); {$td->STRING => "", $td->EXIT_STATUS => 0});
$td->runtest("check output", $td->runtest("check output",
@ -4677,6 +4709,7 @@ foreach my $d (@unicode_pw_cases)
$td->runtest("encode $bits, $pw, $w_encoding", $td->runtest("encode $bits, $pw, $w_encoding",
{$td->COMMAND => {$td->COMMAND =>
"qpdf $xargs --static-id --static-aes-iv" . "qpdf $xargs --static-id --static-aes-iv" .
" --allow-weak-crypto" .
" --encrypt $upass o $bits -- minimal.pdf a.pdf"}, " --encrypt $upass o $bits -- minimal.pdf a.pdf"},
{$td->STRING => $exp, $td->EXIT_STATUS => ($exp ? 2 : 0)}, {$td->STRING => $exp, $td->EXIT_STATUS => ($exp ? 2 : 0)},
$td->NORMALIZE_NEWLINES); $td->NORMALIZE_NEWLINES);
@ -4718,7 +4751,8 @@ $n_tests += 5;
$td->runtest("bytes fallback warning", $td->runtest("bytes fallback warning",
{$td->COMMAND => {$td->COMMAND =>
"qpdf --encrypt \@password-bare-complex-utf8 o 128 --" . "qpdf --allow-weak-crypto" .
" --encrypt \@password-bare-complex-utf8 o 128 --" .
" minimal.pdf a.pdf"}, " minimal.pdf a.pdf"},
{$td->FILE => "bytes-fallback.out", $td->EXIT_STATUS => 0}, {$td->FILE => "bytes-fallback.out", $td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES); $td->NORMALIZE_NEWLINES);
@ -4814,9 +4848,9 @@ my @flags = (["-qdf", # 1
"decrypted"], "decrypted"],
["-linearize", # 9 ["-linearize", # 9
"linearized"], "linearized"],
["-encrypt \"\" owner 128 --", # 10 ["-allow-weak-crypto -encrypt \"\" owner 128 --", # 10
"encrypted"], "encrypted"],
["-linearize -encrypt \"\" o 128 --", # 11 ["-linearize -allow-weak-crypto -encrypt \"\" o 128 --", # 11
"linearized and encrypted"], "linearized and encrypted"],
["", # 12 ["", # 12
"no arguments"], "no arguments"],
@ -4985,9 +5019,15 @@ $n_tests += 2;
$n_tests += 12; $n_tests += 12;
foreach my $i (qw(40 128 256)) foreach my $i (qw(40 128 256))
{ {
my $x = "";
if ($i < 256)
{
$x = "--allow-weak-crypto";
}
$td->runtest("encrypt $i", $td->runtest("encrypt $i",
{$td->COMMAND => {$td->COMMAND =>
"qpdf --encrypt '' o $i -- digitally-signed.pdf a.pdf"}, "qpdf $x --encrypt '' o $i --" .
" digitally-signed.pdf a.pdf"},
{$td->STRING => "", {$td->STRING => "",
$td->EXIT_STATUS => 0}); $td->EXIT_STATUS => 0});
$td->runtest("find desired contents (encrypt $i)", $td->runtest("find desired contents (encrypt $i)",
@ -5010,9 +5050,15 @@ foreach my $i (qw(40 128 256))
$n_tests += 15; $n_tests += 15;
foreach my $i (qw(40 128 256)) foreach my $i (qw(40 128 256))
{ {
my $x = "";
if ($i < 256)
{
$x = "--allow-weak-crypto";
}
$td->runtest("non sig dict encrypt $i", $td->runtest("non sig dict encrypt $i",
{$td->COMMAND => {$td->COMMAND =>
"qpdf --encrypt '' o $i -- comment-annotation.pdf a.pdf"}, "qpdf $x --encrypt '' o $i --" .
" comment-annotation.pdf a.pdf"},
{$td->STRING => "", {$td->STRING => "",
$td->EXIT_STATUS => 0}); $td->EXIT_STATUS => 0});
$td->runtest("plain text not found due to encryption (non sig dict encrypt $i)", $td->runtest("plain text not found due to encryption (non sig dict encrypt $i)",