Commit 3de67173de1b162ad967f67dc23e4a2663b94f9b

Authored by Jay Berkenbilt
1 parent 63158cf5

Better fix to insecure password check (fixes #501)

ChangeLog
@@ -5,11 +5,11 @@ @@ -5,11 +5,11 @@
5 Fixes #499. 5 Fixes #499.
6 6
7 * By default, give an error if a user attempts to encrypt a file 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 2021-02-02 Jay Berkenbilt <ejb@ql.org> 14 2021-02-02 Jay Berkenbilt <ejb@ql.org>
15 15
manual/qpdf-manual.xml
@@ -1239,10 +1239,11 @@ make @@ -1239,10 +1239,11 @@ make
1239 <para> 1239 <para>
1240 Either or both of the user password and the owner password may be 1240 Either or both of the user password and the owner password may be
1241 empty strings. Starting in qpdf 10.2, qpdf defaults to not 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 </para> 1247 </para>
1247 <para> 1248 <para>
1248 The value for 1249 The value for
@@ -1252,25 +1253,6 @@ make @@ -1252,25 +1253,6 @@ make
1252 fully permissive. 1253 fully permissive.
1253 </para> 1254 </para>
1254 <para> 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 If <option><replaceable>key-length</replaceable></option> is 40, 1256 If <option><replaceable>key-length</replaceable></option> is 40,
1275 the following restriction options are available: 1257 the following restriction options are available:
1276 <variablelist> 1258 <variablelist>
@@ -1466,6 +1448,21 @@ make @@ -1466,6 +1448,21 @@ make
1466 </listitem> 1448 </listitem>
1467 </varlistentry> 1449 </varlistentry>
1468 <varlistentry> 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 <term><option>--force-V4</option></term> 1466 <term><option>--force-V4</option></term>
1470 <listitem> 1467 <listitem>
1471 <para> 1468 <para>
@@ -4877,15 +4874,16 @@ print &quot;\n&quot;; @@ -4877,15 +4874,16 @@ print &quot;\n&quot;;
4877 <listitem> 4874 <listitem>
4878 <para> 4875 <para>
4879 By default, <command>qpdf</command> no longer allows 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 </para> 4887 </para>
4890 </listitem> 4888 </listitem>
4891 </itemizedlist> 4889 </itemizedlist>
qpdf/qpdf.cc
@@ -1082,7 +1082,6 @@ ArgParser::initOptionTable() @@ -1082,7 +1082,6 @@ ArgParser::initOptionTable()
1082 1082
1083 t = &this->encrypt40_option_table; 1083 t = &this->encrypt40_option_table;
1084 (*t)["--"] = oe_bare(&ArgParser::argEndEncrypt); 1084 (*t)["--"] = oe_bare(&ArgParser::argEndEncrypt);
1085 - (*t)["allow-insecure"] = oe_bare(&ArgParser::argAllowInsecure);  
1086 // The above 40-bit options are also 128-bit and 256-bit options, 1085 // The above 40-bit options are also 128-bit and 256-bit options,
1087 // so copy what we have so far to 128. Then continue separately 1086 // so copy what we have so far to 128. Then continue separately
1088 // with 128. We later copy 128 to 256. 1087 // with 128. We later copy 128 to 256.
@@ -1117,6 +1116,7 @@ ArgParser::initOptionTable() @@ -1117,6 +1116,7 @@ ArgParser::initOptionTable()
1117 1116
1118 t = &this->encrypt256_option_table; 1117 t = &this->encrypt256_option_table;
1119 (*t)["force-R5"] = oe_bare(&ArgParser::arg256ForceR5); 1118 (*t)["force-R5"] = oe_bare(&ArgParser::arg256ForceR5);
  1119 + (*t)["allow-insecure"] = oe_bare(&ArgParser::argAllowInsecure);
1120 1120
1121 t = &this->under_overlay_option_table; 1121 t = &this->under_overlay_option_table;
1122 (*t)[""] = oe_positional(&ArgParser::argUOpositional); 1122 (*t)[""] = oe_positional(&ArgParser::argUOpositional);
@@ -1333,10 +1333,6 @@ ArgParser::argHelp() @@ -1333,10 +1333,6 @@ ArgParser::argHelp()
1333 << "\n" 1333 << "\n"
1334 << "Additional flags are dependent upon key length.\n" 1334 << "Additional flags are dependent upon key length.\n"
1335 << "\n" 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 << " If 40:\n" 1336 << " If 40:\n"
1341 << "\n" 1337 << "\n"
1342 << " --print=[yn] allow printing\n" 1338 << " --print=[yn] allow printing\n"
@@ -1362,6 +1358,9 @@ ArgParser::argHelp() @@ -1362,6 +1358,9 @@ ArgParser::argHelp()
1362 << " --force-V4 this option is not available with 256-bit keys\n" 1358 << " --force-V4 this option is not available with 256-bit keys\n"
1363 << " --use-aes this option is always on with 256-bit keys\n" 1359 << " --use-aes this option is always on with 256-bit keys\n"
1364 << " --force-R5 forces use of deprecated R=5 encryption\n" 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 << "\n" 1364 << "\n"
1366 << " print-opt may be:\n" 1365 << " print-opt may be:\n"
1367 << "\n" 1366 << "\n"
@@ -3394,15 +3393,21 @@ ArgParser::doFinalChecks() @@ -3394,15 +3393,21 @@ ArgParser::doFinalChecks()
3394 } 3393 }
3395 3394
3396 if (o.encrypt && (! o.allow_insecure) && 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 if (o.require_outfile && o.outfilename && 3413 if (o.require_outfile && o.outfilename &&
qpdf/qtest/qpdf.test
@@ -3464,7 +3464,7 @@ my @encrypted_files = @@ -3464,7 +3464,7 @@ my @encrypted_files =
3464 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 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 $td->runtest("encrypted file", 3469 $td->runtest("encrypted file",
3470 {$td->COMMAND => "test_driver 2 encrypted-with-images.pdf"}, 3470 {$td->COMMAND => "test_driver 2 encrypted-with-images.pdf"},
@@ -3482,17 +3482,12 @@ $td-&gt;runtest(&quot;recheck encrypted file&quot;, @@ -3482,17 +3482,12 @@ $td-&gt;runtest(&quot;recheck encrypted file&quot;,
3482 $td->NORMALIZE_NEWLINES); 3482 $td->NORMALIZE_NEWLINES);
3483 3483
3484 $td->runtest("empty owner password", 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 {$td->REGEXP => ".*is insecure.*--allow-insecure.*", 3486 {$td->REGEXP => ".*is insecure.*--allow-insecure.*",
3492 $td->EXIT_STATUS => 2}, 3487 $td->EXIT_STATUS => 2},
3493 $td->NORMALIZE_NEWLINES); 3488 $td->NORMALIZE_NEWLINES);
3494 $td->runtest("allow insecure", 3489 $td->runtest("allow insecure",
3495 - {$td->COMMAND => "qpdf --encrypt '' '' 128 --allow-insecure --" . 3490 + {$td->COMMAND => "qpdf --encrypt u '' 256 --allow-insecure --" .
3496 " minimal.pdf a.pdf"}, 3491 " minimal.pdf a.pdf"},
3497 {$td->STRING => "", $td->EXIT_STATUS => 0}, 3492 {$td->STRING => "", $td->EXIT_STATUS => 0},
3498 $td->NORMALIZE_NEWLINES); 3493 $td->NORMALIZE_NEWLINES);
@@ -3630,7 +3625,7 @@ foreach my $d (@encrypted_files) @@ -3630,7 +3625,7 @@ foreach my $d (@encrypted_files)
3630 $enc_json =~ s/---upm---/$upm/; 3625 $enc_json =~ s/---upm---/$upm/;
3631 3626
3632 my $eflags = "-encrypt \"$upass\" \"$opass\" $bits $xeflags --"; 3627 my $eflags = "-encrypt \"$upass\" \"$opass\" $bits $xeflags --";
3633 - if (($opass eq "") || ($opass eq $upass)) 3628 + if (($opass eq "") && ($bits == 256))
3634 { 3629 {
3635 $eflags =~ s/--$/--allow-insecure --/; 3630 $eflags =~ s/--$/--allow-insecure --/;
3636 } 3631 }
@@ -3892,7 +3887,7 @@ foreach my $d ([&#39;--force-V4&#39;, &#39;V4&#39;], @@ -3892,7 +3887,7 @@ foreach my $d ([&#39;--force-V4&#39;, &#39;V4&#39;],
3892 my ($args, $out) = @$d; 3887 my ($args, $out) = @$d;
3893 $td->runtest("encrypt $args", 3888 $td->runtest("encrypt $args",
3894 {$td->COMMAND => "qpdf --static-aes-iv --static-id" . 3889 {$td->COMMAND => "qpdf --static-aes-iv --static-id" .
3895 - " --encrypt '' '' 128 $args --allow-insecure --" . 3890 + " --encrypt '' '' 128 $args --" .
3896 " enc-base.pdf a.pdf"}, 3891 " enc-base.pdf a.pdf"},
3897 {$td->STRING => "", $td->EXIT_STATUS => 0}); 3892 {$td->STRING => "", $td->EXIT_STATUS => 0});
3898 $td->runtest("check output", 3893 $td->runtest("check output",
qpdf/qtest/qpdf/insecure-passwords.out
1 checking a.pdf 1 checking a.pdf
2 -PDF Version: 1.4  
3 -R = 3 2 +PDF Version: 1.7 extension level 8
  3 +R = 6
4 P = -4 4 P = -4
5 User password = 5 User password =
6 Supplied password is owner password 6 Supplied password is owner password
7 -Supplied password is user password  
8 extract for accessibility: allowed 7 extract for accessibility: allowed
9 extract for any purpose: allowed 8 extract for any purpose: allowed
10 print low resolution: allowed 9 print low resolution: allowed
@@ -14,6 +13,9 @@ modify forms: allowed @@ -14,6 +13,9 @@ modify forms: allowed
14 modify annotations: allowed 13 modify annotations: allowed
15 modify other: allowed 14 modify other: allowed
16 modify anything: allowed 15 modify anything: allowed
  16 +stream encryption method: AESv3
  17 +string encryption method: AESv3
  18 +file encryption method: AESv3
17 File is not linearized 19 File is not linearized
18 No syntax or stream encoding errors found; the file may still contain 20 No syntax or stream encoding errors found; the file may still contain
19 errors that qpdf cannot detect 21 errors that qpdf cannot detect