Add --password-file=filename option (fixes #499)

This commit is contained in:
Jay Berkenbilt 2021-02-04 16:46:59 -05:00
parent 21b0f4acfc
commit 63158cf546
7 changed files with 157 additions and 10 deletions

View File

@ -1,5 +1,9 @@
2021-02-04 Jay Berkenbilt <ejb@ql.org>
* Add new option --pasword-file=file for reading the decryption
password from a file. file may be "-" to read from standard input.
Fixes #499.
* By default, give an error if a user attempts to encrypt a file
with an empty owner password or an owner password that is the same
as the user password. Such files are insecure. Most viewers either

View File

@ -571,13 +571,17 @@ make
linkend="ref.page-selection"/>.
</para>
<para>
If <option>@filename</option> appears anywhere in the
If <option>@filename</option> appears as a word anywhere in the
command-line, it will be read line by line, and each line will be
treated as a command-line argument. The <option>@-</option> option
allows arguments to be read from standard input. This allows qpdf
to be invoked with an arbitrary number of arbitrarily long
arguments. It is also very useful for avoiding having to pass
passwords on the command line.
passwords on the command line. Note that the
<option>@filename</option> can't appear in the middle of an
argument, so constructs such as <option>--arg=@option</option>
will not work. You would have to include the argument and its
options together in the arguments file.
</para>
<para>
<option>outfilename</option> does not have to be seekable, even
@ -714,14 +718,34 @@ make
</listitem>
</varlistentry>
<varlistentry>
<term><option>--password=password</option></term>
<term><option>--password=<replaceable>password</replaceable></option></term>
<listitem>
<para>
Specifies a password for accessing encrypted files. Note that
you can use <option>@filename</option> or <option>@-</option>
as described above to put the password in a file or pass it
via standard input so you can avoid specifying it on the
command line.
Specifies a password for accessing encrypted files. To read
the password from a file or standard input, you can use
<option>--password-file</option>, added in qpdf 10.2. Note
that you can also use <option>@filename</option> or
<option>@-</option> as described above to put the password in
a file or pass it via standard input, but you would do so by
specifying the entire
<option>--password=<replaceable>password</replaceable></option>
option in the file. Syntax such as
<option>--password=@filename</option> won't work since
<option>@filename</option> is not recognized in the middle of
an argument.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>--password-file=<replaceable>filename</replaceable></option></term>
<listitem>
<para>
Reads the first line from the specified file and uses it as
the password for accessing encrypted files.
<option><replaceable>filename</replaceable></option> may be
<literal>-</literal> to read the password from standard input.
Note that, in this case, the password is echoed and there is
no prompt, so use with caution.
</para>
</listitem>
</varlistentry>
@ -4884,6 +4908,24 @@ print "\n";
</listitem>
</itemizedlist>
</listitem>
<listitem>
<para>
CLI Enhancements
</para>
<itemizedlist>
<listitem>
<para>
The option
<option>--password-file=<replaceable>filename</replaceable></option>
can now be used to read the decryption password from a file.
You can use <literal>-</literal> as the file name to read
the password from standard input. This is an easier/more
obvious way to read passwords from files or standard input
than using <option>@file</option> for this purpose.
</para>
</listitem>
</itemizedlist>
</listitem>
<listitem>
<para>
Library Enhancements

View File

@ -4,6 +4,7 @@
#include <fcntl.h>
#include <stdio.h>
#include <ctype.h>
#include <memory>
#include <qpdf/QUtil.hh>
#include <qpdf/QTC.hh>
@ -199,6 +200,7 @@ struct Options
}
char const* password;
std::shared_ptr<char> password_alloc;
bool linearize;
bool decrypt;
int split_pages;
@ -739,6 +741,7 @@ class ArgParser
void argShowCrypto();
void argPositional(char* arg);
void argPassword(char* parameter);
void argPasswordFile(char* paramter);
void argEmpty();
void argLinearize();
void argEncrypt();
@ -955,6 +958,8 @@ ArgParser::initOptionTable()
(*t)[""] = oe_positional(&ArgParser::argPositional);
(*t)["password"] = oe_requiredParameter(
&ArgParser::argPassword, "password");
(*t)["password-file"] = oe_requiredParameter(
&ArgParser::argPasswordFile, "password-file");
(*t)["empty"] = oe_bare(&ArgParser::argEmpty);
(*t)["linearize"] = oe_bare(&ArgParser::argLinearize);
(*t)["encrypt"] = oe_bare(&ArgParser::argEncrypt);
@ -1235,6 +1240,9 @@ ArgParser::argHelp()
<< "--completion-bash output a bash complete command you can eval\n"
<< "--completion-zsh output a zsh complete command you can eval\n"
<< "--password=password specify a password for accessing encrypted files\n"
<< "--password-file=file get the password the first line \"file\"; use \"-\"\n"
<< " to read the password from stdin (without prompt or\n"
<< " disabling echo, so use with caution)\n"
<< "--is-encrypted silently exit 0 if the file is encrypted or 2\n"
<< " if not; useful for shell scripts\n"
<< "--requires-password silently exit 0 if a password (other than as\n"
@ -1273,7 +1281,8 @@ ArgParser::argHelp()
<< "\n"
<< "Note that you can use the @filename or @- syntax for any argument at any\n"
<< "point in the command. This provides a good way to specify a password without\n"
<< "having to explicitly put it on the command line.\n"
<< "having to explicitly put it on the command line. @filename or @- must be a\n"
<< "word by itself. Syntax such as --arg=@filename doesn't work.\n"
<< "\n"
<< "If none of --copy-encryption, --encrypt or --decrypt are given, qpdf will\n"
<< "preserve any encryption data associated with a file.\n"
@ -1749,6 +1758,36 @@ ArgParser::argPassword(char* parameter)
o.password = parameter;
}
void
ArgParser::argPasswordFile(char* parameter)
{
std::list<std::string> lines;
if (strcmp(parameter, "-") == 0)
{
QTC::TC("qpdf", "qpdf password stdin");
lines = QUtil::read_lines_from_file(std::cin);
}
else
{
QTC::TC("qpdf", "qpdf password file");
lines = QUtil::read_lines_from_file(parameter);
}
if (lines.size() >= 1)
{
// Make sure the memory for this stays in scope.
o.password_alloc = std::shared_ptr<char>(
QUtil::copy_string(lines.front().c_str()),
std::default_delete<char[]>());
o.password = o.password_alloc.get();
if (lines.size() > 1)
{
std::cerr << whoami << ": WARNING: all but the first line of"
<< " the password file are ignored" << std::endl;
}
}
}
void
ArgParser::argEmpty()
{

View File

@ -568,3 +568,5 @@ NNTree erased last item in tree 0
NNTree remove limits from root 0
QPDFPageObjectHelper unresolved names 0
QPDFPageObjectHelper resolving unresolved 0
qpdf password stdin 0
qpdf password file 0

View File

@ -270,7 +270,7 @@ my @check_encryption_password = (
["20-pages.pdf", "", 0, 0],
["20-pages.pdf", "user", 0, 3],
);
$n_tests += 2 * scalar(@check_encryption_password);
$n_tests += 3 * scalar(@check_encryption_password);
foreach my $d (@check_encryption_password)
{
my ($file, $pass, $is_encrypted, $requires_password) = @$d;
@ -283,6 +283,29 @@ foreach my $d (@check_encryption_password)
{$td->STRING => "", $td->EXIT_STATUS => $requires_password});
}
# Exercise reading password from file
open(F, ">args") or die;
print F "user\n";
close(F);
$td->runtest("password from file)",
{$td->COMMAND => "qpdf --check --password-file=args 20-pages.pdf"},
{$td->FILE => "20-pages-check.out", $td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
open(F, ">>args") or die;
print F "ignored\n";
close(F);
$td->runtest("ignore extra args from file)",
{$td->COMMAND => "qpdf --check --password-file=args 20-pages.pdf"},
{$td->FILE => "20-pages-check-password-warning.out",
$td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
unlink "args";
$td->runtest("password from stdin)",
{$td->COMMAND => "echo user |" .
" qpdf --check --password-file=- 20-pages.pdf"},
{$td->FILE => "20-pages-check.out", $td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
show_ntests();
# ----------
$td->notify("--- Dangling Refs ---");

View File

@ -0,0 +1,19 @@
qpdf: WARNING: all but the first line of the password file are ignored
checking 20-pages.pdf
PDF Version: 1.4
R = 3
P = -4
User password = user
Supplied password is user password
extract for accessibility: allowed
extract for any purpose: allowed
print low resolution: allowed
print high resolution: allowed
modify document assembly: allowed
modify forms: allowed
modify annotations: allowed
modify other: allowed
modify anything: allowed
File is not linearized
No syntax or stream encoding errors found; the file may still contain
errors that qpdf cannot detect

View File

@ -0,0 +1,18 @@
checking 20-pages.pdf
PDF Version: 1.4
R = 3
P = -4
User password = user
Supplied password is user password
extract for accessibility: allowed
extract for any purpose: allowed
print low resolution: allowed
print high resolution: allowed
modify document assembly: allowed
modify forms: allowed
modify annotations: allowed
modify other: allowed
modify anything: allowed
File is not linearized
No syntax or stream encoding errors found; the file may still contain
errors that qpdf cannot detect