Commit 3de67173de1b162ad967f67dc23e4a2663b94f9b
1 parent
63158cf5
Better fix to insecure password check (fixes #501)
Showing
5 changed files
with
64 additions
and
64 deletions
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 "\n"; |
| 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->runtest("recheck encrypted file", |
| 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 (['--force-V4', 'V4'], |
| 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 | ... | ... |