Commit 3de67173de1b162ad967f67dc23e4a2663b94f9b

Authored by Jay Berkenbilt
1 parent 63158cf5

Better fix to insecure password check (fixes #501)

ChangeLog
... ... @@ -5,11 +5,11 @@
5 5 Fixes #499.
6 6  
7 7 * By default, give an error if a user attempts to encrypt a file
8   - with an empty owner password or an owner password that is the same
9   - as the user password. Such files are insecure. Most viewers either
10   - won't open such files or will not enforce security settings. To
11   - allow explicit creation of files like this, pass the new
12   - --allow-insecure option. Fixes #501.
  8 + with a 256-bit key, a non-empty user password, and an empty owner
  9 + password. Such files are insecure since they can be opened with no
  10 + password. To allow explicit creation of files like this, pass the
  11 + new --allow-insecure option. Thanks to github user RobK88 for a
  12 + detailed analysis and for reporting this issue. Fixes #501.
13 13  
14 14 2021-02-02 Jay Berkenbilt <ejb@ql.org>
15 15  
... ...
manual/qpdf-manual.xml
... ... @@ -1239,10 +1239,11 @@ make
1239 1239 <para>
1240 1240 Either or both of the user password and the owner password may be
1241 1241 empty strings. Starting in qpdf 10.2, qpdf defaults to not
1242   - allowing creation of PDF files with an empty owner password or an
1243   - owner password that matches the user password. If you want to
1244   - create such files, specify the encryption option
1245   - <option>--allow-insecure</option>, as described below.
  1242 + allowing creation of PDF files with a non-empty user password, an
  1243 + empty owner password, and a 256-bit key since such files can be
  1244 + opened with no password. If you want to create such files, specify
  1245 + the encryption option <option>--allow-insecure</option>, as
  1246 + described below.
1246 1247 </para>
1247 1248 <para>
1248 1249 The value for
... ... @@ -1252,25 +1253,6 @@ make
1252 1253 fully permissive.
1253 1254 </para>
1254 1255 <para>
1255   - For all key lengths, the following options are available:
1256   - <variablelist>
1257   - <varlistentry>
1258   - <term><option>--allow-insecure</option></term>
1259   - <listitem>
1260   - <para>
1261   - From qpdf 10.2, qpdf defaults to not allowing creation of PDF
1262   - files where the owner password is blank or matches the user
1263   - password. Files created in this way are insecure and can't be
1264   - opened by some viewers. Users would ordinarily never want to
1265   - create such files. If you are using qpdf to intentionally
1266   - created strange files for testing (a definite valid use of
1267   - qpdf!), this option allows you to create such insecure files.
1268   - </para>
1269   - </listitem>
1270   - </varlistentry>
1271   - </variablelist>
1272   - </para>
1273   - <para>
1274 1256 If <option><replaceable>key-length</replaceable></option> is 40,
1275 1257 the following restriction options are available:
1276 1258 <variablelist>
... ... @@ -1466,6 +1448,21 @@ make
1466 1448 </listitem>
1467 1449 </varlistentry>
1468 1450 <varlistentry>
  1451 + <term><option>--allow-insecure</option></term>
  1452 + <listitem>
  1453 + <para>
  1454 + From qpdf 10.2, qpdf defaults to not allowing creation of PDF
  1455 + files where the user password is non-empty, the owner password
  1456 + is empty, and a 256-bit key is in use. Files created in this
  1457 + way are insecure since they can be opened without a password.
  1458 + Users would ordinarily never want to create such files. If you
  1459 + are using qpdf to intentionally created strange files for
  1460 + testing (a definite valid use of qpdf!), this option allows
  1461 + you to create such insecure files.
  1462 + </para>
  1463 + </listitem>
  1464 + </varlistentry>
  1465 + <varlistentry>
1469 1466 <term><option>--force-V4</option></term>
1470 1467 <listitem>
1471 1468 <para>
... ... @@ -4877,15 +4874,16 @@ print &quot;\n&quot;;
4877 4874 <listitem>
4878 4875 <para>
4879 4876 By default, <command>qpdf</command> no longer allows
4880   - creation of encrypted PDF files whose owner password is
4881   - empty or matches the user password. The
4882   - <option>--allow-insecure</option>, specified inside the
4883   - <option>--encrypt</option> options, allows creation of such
4884   - files. Behavior changes in the CLI are avoided when
4885   - possible, but an exception was made here because this is
4886   - security-related. qpdf must always allow creation of weird
4887   - files for testing purposes, but it should not default to
4888   - letting users unknowingly create insecure files.
  4877 + creation of encrypted PDF files whose user password is
  4878 + non-empty and owner password is empty when a 256-bit key is
  4879 + in use. The <option>--allow-insecure</option> option,
  4880 + specified inside the <option>--encrypt</option> options,
  4881 + allows creation of such files. Behavior changes in the CLI
  4882 + are avoided when possible, but an exception was made here
  4883 + because this is security-related. qpdf must always allow
  4884 + creation of weird files for testing purposes, but it should
  4885 + not default to letting users unknowingly create insecure
  4886 + files.
4889 4887 </para>
4890 4888 </listitem>
4891 4889 </itemizedlist>
... ...
qpdf/qpdf.cc
... ... @@ -1082,7 +1082,6 @@ ArgParser::initOptionTable()
1082 1082  
1083 1083 t = &this->encrypt40_option_table;
1084 1084 (*t)["--"] = oe_bare(&ArgParser::argEndEncrypt);
1085   - (*t)["allow-insecure"] = oe_bare(&ArgParser::argAllowInsecure);
1086 1085 // The above 40-bit options are also 128-bit and 256-bit options,
1087 1086 // so copy what we have so far to 128. Then continue separately
1088 1087 // with 128. We later copy 128 to 256.
... ... @@ -1117,6 +1116,7 @@ ArgParser::initOptionTable()
1117 1116  
1118 1117 t = &this->encrypt256_option_table;
1119 1118 (*t)["force-R5"] = oe_bare(&ArgParser::arg256ForceR5);
  1119 + (*t)["allow-insecure"] = oe_bare(&ArgParser::argAllowInsecure);
1120 1120  
1121 1121 t = &this->under_overlay_option_table;
1122 1122 (*t)[""] = oe_positional(&ArgParser::argUOpositional);
... ... @@ -1333,10 +1333,6 @@ ArgParser::argHelp()
1333 1333 << "\n"
1334 1334 << "Additional flags are dependent upon key length.\n"
1335 1335 << "\n"
1336   - << " For all key lengths:\n"
1337   - << " --allow-insecure allow the owner password to be empty or the\n"
1338   - << " same as the user password\n"
1339   - << "\n"
1340 1336 << " If 40:\n"
1341 1337 << "\n"
1342 1338 << " --print=[yn] allow printing\n"
... ... @@ -1362,6 +1358,9 @@ ArgParser::argHelp()
1362 1358 << " --force-V4 this option is not available with 256-bit keys\n"
1363 1359 << " --use-aes this option is always on with 256-bit keys\n"
1364 1360 << " --force-R5 forces use of deprecated R=5 encryption\n"
  1361 + << " --allow-insecure allow the owner password to be empty when the\n"
  1362 + << " user password is not empty\n"
  1363 + << "\n"
1365 1364 << "\n"
1366 1365 << " print-opt may be:\n"
1367 1366 << "\n"
... ... @@ -3394,15 +3393,21 @@ ArgParser::doFinalChecks()
3394 3393 }
3395 3394  
3396 3395 if (o.encrypt && (! o.allow_insecure) &&
3397   - (o.owner_password.empty() ||
3398   - (o.owner_password == o.user_password)))
3399   - {
3400   - usage("An encrypted PDF with an empty owner password or an"
3401   - " owner password that is the same as a user password"
3402   - " is insecure and can't be opened by some viewers. If you"
3403   - " really want to do this, you must also give the"
3404   - " --allow-insecure option before the -- that follows"
3405   - " --encrypt.");
  3396 + (o.owner_password.empty() &&
  3397 + (! o.user_password.empty()) &&
  3398 + (o.keylen == 256)))
  3399 + {
  3400 + // Note that empty owner passwords for R < 5 are copied from
  3401 + // the user password, so this lack of security is not an issue
  3402 + // for those files. Also we are consider only the ability to
  3403 + // open the file without a password to be insecure. We are not
  3404 + // concerned about whether the viwer enforces security
  3405 + // settings when the user and owner password match.
  3406 + usage("A PDF with a non-empty user password and an empty owner"
  3407 + " password encrypted with a 256-bit key is insecure as it"
  3408 + " can be opened without a password. If you really want to"
  3409 + " do this, you must also give the --allow-insecure option"
  3410 + " before the -- that follows --encrypt.");
3406 3411 }
3407 3412  
3408 3413 if (o.require_outfile && o.outfilename &&
... ...
qpdf/qtest/qpdf.test
... ... @@ -3464,7 +3464,7 @@ my @encrypted_files =
3464 3464 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
3465 3465 );
3466 3466  
3467   -$n_tests += 9 + (2 * (@encrypted_files)) + (7 * (@encrypted_files - 6)) + 9;
  3467 +$n_tests += 8 + (2 * (@encrypted_files)) + (7 * (@encrypted_files - 6)) + 9;
3468 3468  
3469 3469 $td->runtest("encrypted file",
3470 3470 {$td->COMMAND => "test_driver 2 encrypted-with-images.pdf"},
... ... @@ -3482,17 +3482,12 @@ $td-&gt;runtest(&quot;recheck encrypted file&quot;,
3482 3482 $td->NORMALIZE_NEWLINES);
3483 3483  
3484 3484 $td->runtest("empty owner password",
3485   - {$td->COMMAND => "qpdf --encrypt '' '' 128 -- minimal.pdf a.pdf"},
3486   - {$td->REGEXP => ".*is insecure.*--allow-insecure.*",
3487   - $td->EXIT_STATUS => 2},
3488   - $td->NORMALIZE_NEWLINES);
3489   -$td->runtest("matching user/owner password",
3490   - {$td->COMMAND => "qpdf --encrypt q q 128 -- minimal.pdf a.pdf"},
  3485 + {$td->COMMAND => "qpdf --encrypt u '' 256 -- minimal.pdf a.pdf"},
3491 3486 {$td->REGEXP => ".*is insecure.*--allow-insecure.*",
3492 3487 $td->EXIT_STATUS => 2},
3493 3488 $td->NORMALIZE_NEWLINES);
3494 3489 $td->runtest("allow insecure",
3495   - {$td->COMMAND => "qpdf --encrypt '' '' 128 --allow-insecure --" .
  3490 + {$td->COMMAND => "qpdf --encrypt u '' 256 --allow-insecure --" .
3496 3491 " minimal.pdf a.pdf"},
3497 3492 {$td->STRING => "", $td->EXIT_STATUS => 0},
3498 3493 $td->NORMALIZE_NEWLINES);
... ... @@ -3630,7 +3625,7 @@ foreach my $d (@encrypted_files)
3630 3625 $enc_json =~ s/---upm---/$upm/;
3631 3626  
3632 3627 my $eflags = "-encrypt \"$upass\" \"$opass\" $bits $xeflags --";
3633   - if (($opass eq "") || ($opass eq $upass))
  3628 + if (($opass eq "") && ($bits == 256))
3634 3629 {
3635 3630 $eflags =~ s/--$/--allow-insecure --/;
3636 3631 }
... ... @@ -3892,7 +3887,7 @@ foreach my $d ([&#39;--force-V4&#39;, &#39;V4&#39;],
3892 3887 my ($args, $out) = @$d;
3893 3888 $td->runtest("encrypt $args",
3894 3889 {$td->COMMAND => "qpdf --static-aes-iv --static-id" .
3895   - " --encrypt '' '' 128 $args --allow-insecure --" .
  3890 + " --encrypt '' '' 128 $args --" .
3896 3891 " enc-base.pdf a.pdf"},
3897 3892 {$td->STRING => "", $td->EXIT_STATUS => 0});
3898 3893 $td->runtest("check output",
... ...
qpdf/qtest/qpdf/insecure-passwords.out
1 1 checking a.pdf
2   -PDF Version: 1.4
3   -R = 3
  2 +PDF Version: 1.7 extension level 8
  3 +R = 6
4 4 P = -4
5 5 User password =
6 6 Supplied password is owner password
7   -Supplied password is user password
8 7 extract for accessibility: allowed
9 8 extract for any purpose: allowed
10 9 print low resolution: allowed
... ... @@ -14,6 +13,9 @@ modify forms: allowed
14 13 modify annotations: allowed
15 14 modify other: allowed
16 15 modify anything: allowed
  16 +stream encryption method: AESv3
  17 +string encryption method: AESv3
  18 +file encryption method: AESv3
17 19 File is not linearized
18 20 No syntax or stream encoding errors found; the file may still contain
19 21 errors that qpdf cannot detect
... ...