Commit 63158cf546f0566eed61b0c76afd1a5c886ae8a8
1 parent
21b0f4ac
Add --password-file=filename option (fixes #499)
Showing
7 changed files
with
157 additions
and
10 deletions
ChangeLog
| 1 | 1 | 2021-02-04 Jay Berkenbilt <ejb@ql.org> |
| 2 | 2 | |
| 3 | + * Add new option --pasword-file=file for reading the decryption | |
| 4 | + password from a file. file may be "-" to read from standard input. | |
| 5 | + Fixes #499. | |
| 6 | + | |
| 3 | 7 | * By default, give an error if a user attempts to encrypt a file |
| 4 | 8 | with an empty owner password or an owner password that is the same |
| 5 | 9 | as the user password. Such files are insecure. Most viewers either | ... | ... |
manual/qpdf-manual.xml
| ... | ... | @@ -571,13 +571,17 @@ make |
| 571 | 571 | linkend="ref.page-selection"/>. |
| 572 | 572 | </para> |
| 573 | 573 | <para> |
| 574 | - If <option>@filename</option> appears anywhere in the | |
| 574 | + If <option>@filename</option> appears as a word anywhere in the | |
| 575 | 575 | command-line, it will be read line by line, and each line will be |
| 576 | 576 | treated as a command-line argument. The <option>@-</option> option |
| 577 | 577 | allows arguments to be read from standard input. This allows qpdf |
| 578 | 578 | to be invoked with an arbitrary number of arbitrarily long |
| 579 | 579 | arguments. It is also very useful for avoiding having to pass |
| 580 | - passwords on the command line. | |
| 580 | + passwords on the command line. Note that the | |
| 581 | + <option>@filename</option> can't appear in the middle of an | |
| 582 | + argument, so constructs such as <option>--arg=@option</option> | |
| 583 | + will not work. You would have to include the argument and its | |
| 584 | + options together in the arguments file. | |
| 581 | 585 | </para> |
| 582 | 586 | <para> |
| 583 | 587 | <option>outfilename</option> does not have to be seekable, even |
| ... | ... | @@ -714,14 +718,34 @@ make |
| 714 | 718 | </listitem> |
| 715 | 719 | </varlistentry> |
| 716 | 720 | <varlistentry> |
| 717 | - <term><option>--password=password</option></term> | |
| 721 | + <term><option>--password=<replaceable>password</replaceable></option></term> | |
| 718 | 722 | <listitem> |
| 719 | 723 | <para> |
| 720 | - Specifies a password for accessing encrypted files. Note that | |
| 721 | - you can use <option>@filename</option> or <option>@-</option> | |
| 722 | - as described above to put the password in a file or pass it | |
| 723 | - via standard input so you can avoid specifying it on the | |
| 724 | - command line. | |
| 724 | + Specifies a password for accessing encrypted files. To read | |
| 725 | + the password from a file or standard input, you can use | |
| 726 | + <option>--password-file</option>, added in qpdf 10.2. Note | |
| 727 | + that you can also use <option>@filename</option> or | |
| 728 | + <option>@-</option> as described above to put the password in | |
| 729 | + a file or pass it via standard input, but you would do so by | |
| 730 | + specifying the entire | |
| 731 | + <option>--password=<replaceable>password</replaceable></option> | |
| 732 | + option in the file. Syntax such as | |
| 733 | + <option>--password=@filename</option> won't work since | |
| 734 | + <option>@filename</option> is not recognized in the middle of | |
| 735 | + an argument. | |
| 736 | + </para> | |
| 737 | + </listitem> | |
| 738 | + </varlistentry> | |
| 739 | + <varlistentry> | |
| 740 | + <term><option>--password-file=<replaceable>filename</replaceable></option></term> | |
| 741 | + <listitem> | |
| 742 | + <para> | |
| 743 | + Reads the first line from the specified file and uses it as | |
| 744 | + the password for accessing encrypted files. | |
| 745 | + <option><replaceable>filename</replaceable></option> may be | |
| 746 | + <literal>-</literal> to read the password from standard input. | |
| 747 | + Note that, in this case, the password is echoed and there is | |
| 748 | + no prompt, so use with caution. | |
| 725 | 749 | </para> |
| 726 | 750 | </listitem> |
| 727 | 751 | </varlistentry> |
| ... | ... | @@ -4886,6 +4910,24 @@ print "\n"; |
| 4886 | 4910 | </listitem> |
| 4887 | 4911 | <listitem> |
| 4888 | 4912 | <para> |
| 4913 | + CLI Enhancements | |
| 4914 | + </para> | |
| 4915 | + <itemizedlist> | |
| 4916 | + <listitem> | |
| 4917 | + <para> | |
| 4918 | + The option | |
| 4919 | + <option>--password-file=<replaceable>filename</replaceable></option> | |
| 4920 | + can now be used to read the decryption password from a file. | |
| 4921 | + You can use <literal>-</literal> as the file name to read | |
| 4922 | + the password from standard input. This is an easier/more | |
| 4923 | + obvious way to read passwords from files or standard input | |
| 4924 | + than using <option>@file</option> for this purpose. | |
| 4925 | + </para> | |
| 4926 | + </listitem> | |
| 4927 | + </itemizedlist> | |
| 4928 | + </listitem> | |
| 4929 | + <listitem> | |
| 4930 | + <para> | |
| 4889 | 4931 | Library Enhancements |
| 4890 | 4932 | </para> |
| 4891 | 4933 | <itemizedlist> | ... | ... |
qpdf/qpdf.cc
| ... | ... | @@ -4,6 +4,7 @@ |
| 4 | 4 | #include <fcntl.h> |
| 5 | 5 | #include <stdio.h> |
| 6 | 6 | #include <ctype.h> |
| 7 | +#include <memory> | |
| 7 | 8 | |
| 8 | 9 | #include <qpdf/QUtil.hh> |
| 9 | 10 | #include <qpdf/QTC.hh> |
| ... | ... | @@ -199,6 +200,7 @@ struct Options |
| 199 | 200 | } |
| 200 | 201 | |
| 201 | 202 | char const* password; |
| 203 | + std::shared_ptr<char> password_alloc; | |
| 202 | 204 | bool linearize; |
| 203 | 205 | bool decrypt; |
| 204 | 206 | int split_pages; |
| ... | ... | @@ -739,6 +741,7 @@ class ArgParser |
| 739 | 741 | void argShowCrypto(); |
| 740 | 742 | void argPositional(char* arg); |
| 741 | 743 | void argPassword(char* parameter); |
| 744 | + void argPasswordFile(char* paramter); | |
| 742 | 745 | void argEmpty(); |
| 743 | 746 | void argLinearize(); |
| 744 | 747 | void argEncrypt(); |
| ... | ... | @@ -955,6 +958,8 @@ ArgParser::initOptionTable() |
| 955 | 958 | (*t)[""] = oe_positional(&ArgParser::argPositional); |
| 956 | 959 | (*t)["password"] = oe_requiredParameter( |
| 957 | 960 | &ArgParser::argPassword, "password"); |
| 961 | + (*t)["password-file"] = oe_requiredParameter( | |
| 962 | + &ArgParser::argPasswordFile, "password-file"); | |
| 958 | 963 | (*t)["empty"] = oe_bare(&ArgParser::argEmpty); |
| 959 | 964 | (*t)["linearize"] = oe_bare(&ArgParser::argLinearize); |
| 960 | 965 | (*t)["encrypt"] = oe_bare(&ArgParser::argEncrypt); |
| ... | ... | @@ -1235,6 +1240,9 @@ ArgParser::argHelp() |
| 1235 | 1240 | << "--completion-bash output a bash complete command you can eval\n" |
| 1236 | 1241 | << "--completion-zsh output a zsh complete command you can eval\n" |
| 1237 | 1242 | << "--password=password specify a password for accessing encrypted files\n" |
| 1243 | + << "--password-file=file get the password the first line \"file\"; use \"-\"\n" | |
| 1244 | + << " to read the password from stdin (without prompt or\n" | |
| 1245 | + << " disabling echo, so use with caution)\n" | |
| 1238 | 1246 | << "--is-encrypted silently exit 0 if the file is encrypted or 2\n" |
| 1239 | 1247 | << " if not; useful for shell scripts\n" |
| 1240 | 1248 | << "--requires-password silently exit 0 if a password (other than as\n" |
| ... | ... | @@ -1273,7 +1281,8 @@ ArgParser::argHelp() |
| 1273 | 1281 | << "\n" |
| 1274 | 1282 | << "Note that you can use the @filename or @- syntax for any argument at any\n" |
| 1275 | 1283 | << "point in the command. This provides a good way to specify a password without\n" |
| 1276 | - << "having to explicitly put it on the command line.\n" | |
| 1284 | + << "having to explicitly put it on the command line. @filename or @- must be a\n" | |
| 1285 | + << "word by itself. Syntax such as --arg=@filename doesn't work.\n" | |
| 1277 | 1286 | << "\n" |
| 1278 | 1287 | << "If none of --copy-encryption, --encrypt or --decrypt are given, qpdf will\n" |
| 1279 | 1288 | << "preserve any encryption data associated with a file.\n" |
| ... | ... | @@ -1750,6 +1759,36 @@ ArgParser::argPassword(char* parameter) |
| 1750 | 1759 | } |
| 1751 | 1760 | |
| 1752 | 1761 | void |
| 1762 | +ArgParser::argPasswordFile(char* parameter) | |
| 1763 | +{ | |
| 1764 | + std::list<std::string> lines; | |
| 1765 | + if (strcmp(parameter, "-") == 0) | |
| 1766 | + { | |
| 1767 | + QTC::TC("qpdf", "qpdf password stdin"); | |
| 1768 | + lines = QUtil::read_lines_from_file(std::cin); | |
| 1769 | + } | |
| 1770 | + else | |
| 1771 | + { | |
| 1772 | + QTC::TC("qpdf", "qpdf password file"); | |
| 1773 | + lines = QUtil::read_lines_from_file(parameter); | |
| 1774 | + } | |
| 1775 | + if (lines.size() >= 1) | |
| 1776 | + { | |
| 1777 | + // Make sure the memory for this stays in scope. | |
| 1778 | + o.password_alloc = std::shared_ptr<char>( | |
| 1779 | + QUtil::copy_string(lines.front().c_str()), | |
| 1780 | + std::default_delete<char[]>()); | |
| 1781 | + o.password = o.password_alloc.get(); | |
| 1782 | + | |
| 1783 | + if (lines.size() > 1) | |
| 1784 | + { | |
| 1785 | + std::cerr << whoami << ": WARNING: all but the first line of" | |
| 1786 | + << " the password file are ignored" << std::endl; | |
| 1787 | + } | |
| 1788 | + } | |
| 1789 | +} | |
| 1790 | + | |
| 1791 | +void | |
| 1753 | 1792 | ArgParser::argEmpty() |
| 1754 | 1793 | { |
| 1755 | 1794 | o.infilename = ""; | ... | ... |
qpdf/qpdf.testcov
qpdf/qtest/qpdf.test
| ... | ... | @@ -270,7 +270,7 @@ my @check_encryption_password = ( |
| 270 | 270 | ["20-pages.pdf", "", 0, 0], |
| 271 | 271 | ["20-pages.pdf", "user", 0, 3], |
| 272 | 272 | ); |
| 273 | -$n_tests += 2 * scalar(@check_encryption_password); | |
| 273 | +$n_tests += 3 * scalar(@check_encryption_password); | |
| 274 | 274 | foreach my $d (@check_encryption_password) |
| 275 | 275 | { |
| 276 | 276 | my ($file, $pass, $is_encrypted, $requires_password) = @$d; |
| ... | ... | @@ -283,6 +283,29 @@ foreach my $d (@check_encryption_password) |
| 283 | 283 | {$td->STRING => "", $td->EXIT_STATUS => $requires_password}); |
| 284 | 284 | } |
| 285 | 285 | |
| 286 | +# Exercise reading password from file | |
| 287 | +open(F, ">args") or die; | |
| 288 | +print F "user\n"; | |
| 289 | +close(F); | |
| 290 | +$td->runtest("password from file)", | |
| 291 | + {$td->COMMAND => "qpdf --check --password-file=args 20-pages.pdf"}, | |
| 292 | + {$td->FILE => "20-pages-check.out", $td->EXIT_STATUS => 0}, | |
| 293 | + $td->NORMALIZE_NEWLINES); | |
| 294 | +open(F, ">>args") or die; | |
| 295 | +print F "ignored\n"; | |
| 296 | +close(F); | |
| 297 | +$td->runtest("ignore extra args from file)", | |
| 298 | + {$td->COMMAND => "qpdf --check --password-file=args 20-pages.pdf"}, | |
| 299 | + {$td->FILE => "20-pages-check-password-warning.out", | |
| 300 | + $td->EXIT_STATUS => 0}, | |
| 301 | + $td->NORMALIZE_NEWLINES); | |
| 302 | +unlink "args"; | |
| 303 | +$td->runtest("password from stdin)", | |
| 304 | + {$td->COMMAND => "echo user |" . | |
| 305 | + " qpdf --check --password-file=- 20-pages.pdf"}, | |
| 306 | + {$td->FILE => "20-pages-check.out", $td->EXIT_STATUS => 0}, | |
| 307 | + $td->NORMALIZE_NEWLINES); | |
| 308 | + | |
| 286 | 309 | show_ntests(); |
| 287 | 310 | # ---------- |
| 288 | 311 | $td->notify("--- Dangling Refs ---"); | ... | ... |
qpdf/qtest/qpdf/20-pages-check-password-warning.out
0 → 100644
| 1 | +qpdf: WARNING: all but the first line of the password file are ignored | |
| 2 | +checking 20-pages.pdf | |
| 3 | +PDF Version: 1.4 | |
| 4 | +R = 3 | |
| 5 | +P = -4 | |
| 6 | +User password = user | |
| 7 | +Supplied password is user password | |
| 8 | +extract for accessibility: allowed | |
| 9 | +extract for any purpose: allowed | |
| 10 | +print low resolution: allowed | |
| 11 | +print high resolution: allowed | |
| 12 | +modify document assembly: allowed | |
| 13 | +modify forms: allowed | |
| 14 | +modify annotations: allowed | |
| 15 | +modify other: allowed | |
| 16 | +modify anything: allowed | |
| 17 | +File is not linearized | |
| 18 | +No syntax or stream encoding errors found; the file may still contain | |
| 19 | +errors that qpdf cannot detect | ... | ... |
qpdf/qtest/qpdf/20-pages-check.out
0 → 100644
| 1 | +checking 20-pages.pdf | |
| 2 | +PDF Version: 1.4 | |
| 3 | +R = 3 | |
| 4 | +P = -4 | |
| 5 | +User password = user | |
| 6 | +Supplied password is user password | |
| 7 | +extract for accessibility: allowed | |
| 8 | +extract for any purpose: allowed | |
| 9 | +print low resolution: allowed | |
| 10 | +print high resolution: allowed | |
| 11 | +modify document assembly: allowed | |
| 12 | +modify forms: allowed | |
| 13 | +modify annotations: allowed | |
| 14 | +modify other: allowed | |
| 15 | +modify anything: allowed | |
| 16 | +File is not linearized | |
| 17 | +No syntax or stream encoding errors found; the file may still contain | |
| 18 | +errors that qpdf cannot detect | ... | ... |