Commit 7d7e2234a537b6cd2205fb5cf942d5a9e8a866e3

Authored by Jay Berkenbilt
1 parent 1173a0bd

Implement new --encrypt args and completion (fixes #784)

Positional arguments are supported in a backward-compatible way, but
completion no longer guides users to it.
ChangeLog
1 2023-12-22 Jay Berkenbilt <ejb@ql.org> 1 2023-12-22 Jay Berkenbilt <ejb@ql.org>
2 2
  3 + * Allow the syntax "--encrypt --user-password=user-password
  4 + --owner-password=owner-password --bits={40,128,256}" when
  5 + encrypting PDF files. This is an alternative to the syntax
  6 + "--encrypt user-password owner-password {40,128,256}", which will
  7 + continue to be supported. The new syntax works better with shell
  8 + completion and allows creation of passwords that start with "-".
  9 + Fixes #874.
  10 +
3 * When setting a check box value, allow any value other than /Off 11 * When setting a check box value, allow any value other than /Off
4 to mean checked. This is permitted by the spec. Previously, any 12 to mean checked. This is permitted by the spec. Previously, any
5 value other than /Yes or /Off was rejected. Fixes #1056. 13 value other than /Yes or /Off was rejected. Fixes #1056.
job.sums
@@ -8,10 +8,10 @@ include/qpdf/auto_job_c_pages.hh b3cc0f21029f6d89efa043dcdbfa183cb59325b6506001c @@ -8,10 +8,10 @@ include/qpdf/auto_job_c_pages.hh b3cc0f21029f6d89efa043dcdbfa183cb59325b6506001c
8 include/qpdf/auto_job_c_uo.hh ae21b69a1efa9333050f4833d465f6daff87e5b38e5106e49bbef5d4132e4ed1 8 include/qpdf/auto_job_c_uo.hh ae21b69a1efa9333050f4833d465f6daff87e5b38e5106e49bbef5d4132e4ed1
9 job.yml 4f89fc7b622df897d30d403d8035aa36fc7de8d8c43042c736e0300d904cb05c 9 job.yml 4f89fc7b622df897d30d403d8035aa36fc7de8d8c43042c736e0300d904cb05c
10 libqpdf/qpdf/auto_job_decl.hh 9c6f701c29f3f764d620186bed92685a2edf2e4d11e4f4532862c05470cfc4d2 10 libqpdf/qpdf/auto_job_decl.hh 9c6f701c29f3f764d620186bed92685a2edf2e4d11e4f4532862c05470cfc4d2
11 -libqpdf/qpdf/auto_job_help.hh 788320d439519ecd284621531e96ee698965a9ad342fd423c5fb1de75d2a06b1 11 +libqpdf/qpdf/auto_job_help.hh ea1fdca2aa405bdf193732c5a2789c602efe2add3aa6e2dceecfacee175ce65c
12 libqpdf/qpdf/auto_job_init.hh b4c2b3724fba61f1206fd3bae81951636852592f67a63ef9539839c2c5995065 12 libqpdf/qpdf/auto_job_init.hh b4c2b3724fba61f1206fd3bae81951636852592f67a63ef9539839c2c5995065
13 libqpdf/qpdf/auto_job_json_decl.hh 06caa46eaf71db8a50c046f91866baa8087745a9474319fb7c86d92634cc8297 13 libqpdf/qpdf/auto_job_json_decl.hh 06caa46eaf71db8a50c046f91866baa8087745a9474319fb7c86d92634cc8297
14 libqpdf/qpdf/auto_job_json_init.hh f5acb9aa103131cb68dec0e12c4d237a6459bdb49b24773c24f0c2724a462b8f 14 libqpdf/qpdf/auto_job_json_init.hh f5acb9aa103131cb68dec0e12c4d237a6459bdb49b24773c24f0c2724a462b8f
15 libqpdf/qpdf/auto_job_schema.hh b53c006fec2e75b1b73588d242d49a32f7d3db820b1541de106c5d4c27fbb4d9 15 libqpdf/qpdf/auto_job_schema.hh b53c006fec2e75b1b73588d242d49a32f7d3db820b1541de106c5d4c27fbb4d9
16 manual/_ext/qpdf.py 6add6321666031d55ed4aedf7c00e5662bba856dfcd66ccb526563bffefbb580 16 manual/_ext/qpdf.py 6add6321666031d55ed4aedf7c00e5662bba856dfcd66ccb526563bffefbb580
17 -manual/cli.rst b524f96f2a6f338f3e4350703598c56ba22e8f12a8efb7a441648c6dbf0a455e 17 +manual/cli.rst 28cc6b36b26377404022bab467e6a16085023fdfa5d9d419595ffcae6c69d531
libqpdf/QPDFJob_argv.cc
@@ -35,6 +35,9 @@ namespace @@ -35,6 +35,9 @@ namespace
35 std::shared_ptr<QPDFJob::EncConfig> c_enc; 35 std::shared_ptr<QPDFJob::EncConfig> c_enc;
36 std::vector<std::string> accumulated_args; 36 std::vector<std::string> accumulated_args;
37 std::shared_ptr<char> pages_password{nullptr}; 37 std::shared_ptr<char> pages_password{nullptr};
  38 + std::string user_password;
  39 + std::string owner_password;
  40 + bool used_enc_password_args{false};
38 bool gave_input{false}; 41 bool gave_input{false};
39 bool gave_output{false}; 42 bool gave_output{false};
40 }; 43 };
@@ -161,64 +164,67 @@ void @@ -161,64 +164,67 @@ void
161 ArgParser::argEncrypt() 164 ArgParser::argEncrypt()
162 { 165 {
163 this->accumulated_args.clear(); 166 this->accumulated_args.clear();
164 - if (this->ap.isCompleting() && this->ap.argsLeft() == 0) {  
165 - this->ap.insertCompletion("user-password");  
166 - }  
167 this->ap.selectOptionTable(O_ENCRYPTION); 167 this->ap.selectOptionTable(O_ENCRYPTION);
168 } 168 }
169 169
170 void 170 void
171 ArgParser::argEncPositional(std::string const& arg) 171 ArgParser::argEncPositional(std::string const& arg)
172 { 172 {
  173 + if (used_enc_password_args) {
  174 + usage("positional and dashed encryption arguments may not be mixed");
  175 + }
  176 +
173 this->accumulated_args.push_back(arg); 177 this->accumulated_args.push_back(arg);
174 - size_t n_args = this->accumulated_args.size();  
175 - if (n_args < 3) {  
176 - if (this->ap.isCompleting() && (this->ap.argsLeft() == 0)) {  
177 - if (n_args == 1) {  
178 - this->ap.insertCompletion("owner-password");  
179 - } else if (n_args == 2) {  
180 - this->ap.insertCompletion("40");  
181 - this->ap.insertCompletion("128");  
182 - this->ap.insertCompletion("256");  
183 - }  
184 - } 178 + if (this->accumulated_args.size() < 3) {
185 return; 179 return;
186 } 180 }
187 - std::string user_password = this->accumulated_args.at(0);  
188 - std::string owner_password = this->accumulated_args.at(1);  
189 - std::string len_str = this->accumulated_args.at(2);  
190 - int keylen = 0;  
191 - if (len_str == "40") {  
192 - keylen = 40;  
193 - this->ap.selectOptionTable(O_40_BIT_ENCRYPTION);  
194 - } else if (len_str == "128") {  
195 - keylen = 128;  
196 - this->ap.selectOptionTable(O_128_BIT_ENCRYPTION);  
197 - } else if (len_str == "256") {  
198 - keylen = 256;  
199 - this->ap.selectOptionTable(O_256_BIT_ENCRYPTION);  
200 - } else {  
201 - usage("encryption key length must be 40, 128, or 256");  
202 - }  
203 - this->c_enc = c_main->encrypt(keylen, user_password, owner_password); 181 + user_password = this->accumulated_args.at(0);
  182 + owner_password = this->accumulated_args.at(1);
  183 + auto len_str = this->accumulated_args.at(2);
  184 + this->accumulated_args.clear();
  185 + argEncBits(len_str);
204 } 186 }
205 187
206 void 188 void
207 ArgParser::argEncUserPassword(std::string const& arg) 189 ArgParser::argEncUserPassword(std::string const& arg)
208 { 190 {
209 - // QXXXQ 191 + if (!accumulated_args.empty()) {
  192 + usage("positional and dashed encryption arguments may not be mixed");
  193 + }
  194 + this->used_enc_password_args = true;
  195 + this->user_password = arg;
210 } 196 }
211 197
212 void 198 void
213 ArgParser::argEncOwnerPassword(std::string const& arg) 199 ArgParser::argEncOwnerPassword(std::string const& arg)
214 { 200 {
215 - // QXXXQ 201 + if (!accumulated_args.empty()) {
  202 + usage("positional and dashed encryption arguments may not be mixed");
  203 + }
  204 + this->used_enc_password_args = true;
  205 + this->owner_password = arg;
216 } 206 }
217 207
218 void 208 void
219 ArgParser::argEncBits(std::string const& arg) 209 ArgParser::argEncBits(std::string const& arg)
220 { 210 {
221 - // QXXXQ 211 + if (!accumulated_args.empty()) {
  212 + usage("positional and dashed encryption arguments may not be mixed");
  213 + }
  214 + int keylen = 0;
  215 + if (arg == "40") {
  216 + keylen = 40;
  217 + this->ap.selectOptionTable(O_40_BIT_ENCRYPTION);
  218 + } else if (arg == "128") {
  219 + keylen = 128;
  220 + this->ap.selectOptionTable(O_128_BIT_ENCRYPTION);
  221 + } else if (arg == "256") {
  222 + keylen = 256;
  223 + this->ap.selectOptionTable(O_256_BIT_ENCRYPTION);
  224 + } else {
  225 + usage("encryption key length must be 40, 128, or 256");
  226 + }
  227 + this->c_enc = c_main->encrypt(keylen, user_password, owner_password);
222 } 228 }
223 229
224 void 230 void
libqpdf/qpdf/auto_job_help.hh
@@ -148,29 +148,14 @@ the structure without changing the content. @@ -148,29 +148,14 @@ the structure without changing the content.
148 )"); 148 )");
149 ap.addOptionHelp("--linearize", "transformation", "linearize (web-optimize) output", R"(Create linearized (web-optimized) output files. 149 ap.addOptionHelp("--linearize", "transformation", "linearize (web-optimize) output", R"(Create linearized (web-optimized) output files.
150 )"); 150 )");
151 -ap.addOptionHelp("--encrypt", "transformation", "start encryption options", R"(--encrypt user-password owner-password key-length [options] -- 151 +ap.addOptionHelp("--encrypt", "transformation", "start encryption options", R"(--encrypt [options] --
152 152
153 Run qpdf --help=encryption for details. 153 Run qpdf --help=encryption for details.
154 )"); 154 )");
155 -ap.addOptionHelp("--user-password", "transformation", "specify user password", R"(--user-password=user-password  
156 -  
157 -Set the user password.  
158 -)");  
159 -ap.addOptionHelp("--owner-password", "transformation", "specify owner password", R"(--owner-password=owner-password  
160 -  
161 -Set the owner password.  
162 -)");  
163 -ap.addOptionHelp("--bits", "transformation", "specify encryption bit depth", R"(--bits={48|128|256}  
164 -  
165 -Set the encrypt bit depth. Use 256.  
166 -)");  
167 ap.addOptionHelp("--decrypt", "transformation", "remove encryption from input file", R"(Create an unencrypted output file even if the input file was 155 ap.addOptionHelp("--decrypt", "transformation", "remove encryption from input file", R"(Create an unencrypted output file even if the input file was
168 encrypted. Normally qpdf preserves whatever encryption was 156 encrypted. Normally qpdf preserves whatever encryption was
169 present on the input file. This option overrides that behavior. 157 present on the input file. This option overrides that behavior.
170 )"); 158 )");
171 -}  
172 -static void add_help_3(QPDFArgParser& ap)  
173 -{  
174 ap.addOptionHelp("--remove-restrictions", "transformation", "remove security restrictions from input file", R"(Remove restrictions associated with digitally signed PDF files. 159 ap.addOptionHelp("--remove-restrictions", "transformation", "remove security restrictions from input file", R"(Remove restrictions associated with digitally signed PDF files.
175 This may be combined with --decrypt to allow free editing of 160 This may be combined with --decrypt to allow free editing of
176 previously signed/encrypted files. This option invalidates the 161 previously signed/encrypted files. This option invalidates the
@@ -187,6 +172,9 @@ ap.addOptionHelp(&quot;--encryption-file-password&quot;, &quot;transformation&quot;, &quot;supply passwor @@ -187,6 +172,9 @@ ap.addOptionHelp(&quot;--encryption-file-password&quot;, &quot;transformation&quot;, &quot;supply passwor
187 If the file named in --copy-encryption requires a password, use 172 If the file named in --copy-encryption requires a password, use
188 this option to supply the password. 173 this option to supply the password.
189 )"); 174 )");
  175 +}
  176 +static void add_help_3(QPDFArgParser& ap)
  177 +{
190 ap.addOptionHelp("--qdf", "transformation", "enable viewing PDF code in a text editor", R"(Create a PDF file suitable for viewing in a text editor and even 178 ap.addOptionHelp("--qdf", "transformation", "enable viewing PDF code in a text editor", R"(Create a PDF file suitable for viewing in a text editor and even
191 editing. This is for editing the PDF code, not the page contents. 179 editing. This is for editing the PDF code, not the page contents.
192 All streams that can be uncompressed are uncompressed, and 180 All streams that can be uncompressed are uncompressed, and
@@ -282,9 +270,6 @@ ap.addOptionHelp(&quot;--ii-min-bytes&quot;, &quot;transformation&quot;, &quot;set minimum size for --ext @@ -282,9 +270,6 @@ ap.addOptionHelp(&quot;--ii-min-bytes&quot;, &quot;transformation&quot;, &quot;set minimum size for --ext
282 Don't externalize inline images smaller than this size. The 270 Don't externalize inline images smaller than this size. The
283 default is 1,024. Use 0 for no minimum. 271 default is 1,024. Use 0 for no minimum.
284 )"); 272 )");
285 -}  
286 -static void add_help_4(QPDFArgParser& ap)  
287 -{  
288 ap.addOptionHelp("--min-version", "transformation", "set minimum PDF version", R"(--min-version=version 273 ap.addOptionHelp("--min-version", "transformation", "set minimum PDF version", R"(--min-version=version
289 274
290 Force the PDF version of the output to be at least the specified 275 Force the PDF version of the output to be at least the specified
@@ -312,6 +297,9 @@ resulting set of pages, where :odd starts with the first page and @@ -312,6 +297,9 @@ resulting set of pages, where :odd starts with the first page and
312 :even starts with the second page. These are odd and even pages 297 :even starts with the second page. These are odd and even pages
313 from the resulting set, not based on the original page numbers. 298 from the resulting set, not based on the original page numbers.
314 )"); 299 )");
  300 +}
  301 +static void add_help_4(QPDFArgParser& ap)
  302 +{
315 ap.addHelpTopic("modification", "change parts of the PDF", R"(Modification options make systematic changes to certain parts of 303 ap.addHelpTopic("modification", "change parts of the PDF", R"(Modification options make systematic changes to certain parts of
316 the PDF, causing the PDF to render differently from the original. 304 the PDF, causing the PDF to render differently from the original.
317 )"); 305 )");
@@ -404,18 +392,33 @@ ap.addOptionHelp(&quot;--keep-inline-images&quot;, &quot;modification&quot;, &quot;exclude inline images @@ -404,18 +392,33 @@ ap.addOptionHelp(&quot;--keep-inline-images&quot;, &quot;modification&quot;, &quot;exclude inline images
404 )"); 392 )");
405 ap.addOptionHelp("--remove-page-labels", "modification", "remove explicit page numbers", R"(Exclude page labels (explicit page numbers) from the output file. 393 ap.addOptionHelp("--remove-page-labels", "modification", "remove explicit page numbers", R"(Exclude page labels (explicit page numbers) from the output file.
406 )"); 394 )");
407 -}  
408 -static void add_help_5(QPDFArgParser& ap)  
409 -{  
410 ap.addHelpTopic("encryption", "create encrypted files", R"(Create encrypted files. Usage: 395 ap.addHelpTopic("encryption", "create encrypted files", R"(Create encrypted files. Usage:
411 396
  397 +--encrypt \
  398 + [--user-password=user-password] \
  399 + [--owner-password=owner-password] \
  400 + --bits=key-length [options] --
  401 +
  402 +OR
  403 +
412 --encrypt user-password owner-password key-length [options] -- 404 --encrypt user-password owner-password key-length [options] --
413 405
414 -Either or both of user-password and owner-password may be empty  
415 -strings, though setting either to the empty string enables the file  
416 -to be opened and decrypted without a password. key-length may be  
417 -40, 128, or 256. Encryption options are terminated by "--" by  
418 -itself. 406 +The first form, with flags for the passwords and bit length, was
  407 +introduced in qpdf 11.7.0. Only the --bits option is is mandatory.
  408 +This form allows you to use any text as the password. If passwords
  409 +are specified, they must be given before the --bits option.
  410 +
  411 +The second form has been in qpdf since the beginning and wil
  412 +continue to be supported. Either or both of user-password and
  413 +owner-password may be empty strings.
  414 +
  415 +The key-length parameter must be either 40, 128, or 256. The user
  416 +and/or owner password may be omitted. Omitting either pasword
  417 +enables the PDF file to be opened without a password. Specifying
  418 +the same value for the user and owner password and specifying an
  419 +empty owner password are both considered insecure.
  420 +
  421 +Encryption options are terminated by "--" by itself.
419 422
420 40-bit encryption is insecure, as is 128-bit encryption without 423 40-bit encryption is insecure, as is 128-bit encryption without
421 AES. Use 256-bit encryption unless you have a specific reason to 424 AES. Use 256-bit encryption unless you have a specific reason to
@@ -468,6 +471,22 @@ Values for modify-opt: @@ -468,6 +471,22 @@ Values for modify-opt:
468 annotate form + commenting and modifying forms 471 annotate form + commenting and modifying forms
469 all allow full document modification 472 all allow full document modification
470 )"); 473 )");
  474 +ap.addOptionHelp("--user-password", "encryption", "specify user password", R"(--user-password=user-password
  475 +
  476 +Set the user password of the encrypted file.
  477 +)");
  478 +ap.addOptionHelp("--owner-password", "encryption", "specify owner password", R"(--owner-password=owner-password
  479 +
  480 +Set the owner password of the encrypted file.
  481 +)");
  482 +}
  483 +static void add_help_5(QPDFArgParser& ap)
  484 +{
  485 +ap.addOptionHelp("--bits", "encryption", "specify encryption key length", R"(--bits={48|128|256}
  486 +
  487 +Specify the encryption key length. For best security, always use
  488 +a key length of 256.
  489 +)");
471 ap.addOptionHelp("--accessibility", "encryption", "restrict document accessibility", R"(--accessibility=[y|n] 490 ap.addOptionHelp("--accessibility", "encryption", "restrict document accessibility", R"(--accessibility=[y|n]
472 491
473 This option is ignored except with very old encryption formats. 492 This option is ignored except with very old encryption formats.
manual/cli.rst
@@ -714,7 +714,7 @@ Related Options @@ -714,7 +714,7 @@ Related Options
714 important cross-reference information typically appears at the end 714 important cross-reference information typically appears at the end
715 of the file. 715 of the file.
716 716
717 -.. qpdf:option:: --encrypt user-password owner-password key-length [options] -- 717 +.. qpdf:option:: --encrypt [options] --
718 718
719 .. help: start encryption options 719 .. help: start encryption options
720 720
@@ -723,32 +723,6 @@ Related Options @@ -723,32 +723,6 @@ Related Options
723 This flag starts encryption options, used to create encrypted 723 This flag starts encryption options, used to create encrypted
724 files. Please see :ref:`encryption-options` for details. 724 files. Please see :ref:`encryption-options` for details.
725 725
726 -.. qpdf:option:: --user-password=user-password  
727 -  
728 - .. help: specify user password  
729 -  
730 - Set the user password.  
731 -  
732 - Set the user password for the encrypted file.  
733 -  
734 -.. qpdf:option:: --owner-password=owner-password  
735 -  
736 - .. help: specify owner password  
737 -  
738 - Set the owner password.  
739 -  
740 - Set the owner password for the encrypted file.  
741 -  
742 -.. qpdf:option:: --bits={48|128|256}  
743 -  
744 - .. help: specify encryption bit depth  
745 -  
746 - Set the encrypt bit depth. Use 256.  
747 -  
748 - Set the bit depth for encrypted files. You should always use  
749 - ``--bits=256`` unless you have a strong reason to create a file  
750 - with weaker encryption.  
751 -  
752 .. qpdf:option:: --decrypt 726 .. qpdf:option:: --decrypt
753 727
754 .. help: remove encryption from input file 728 .. help: remove encryption from input file
@@ -1758,13 +1732,31 @@ Encryption @@ -1758,13 +1732,31 @@ Encryption
1758 1732
1759 Create encrypted files. Usage: 1733 Create encrypted files. Usage:
1760 1734
  1735 + --encrypt \
  1736 + [--user-password=user-password] \
  1737 + [--owner-password=owner-password] \
  1738 + --bits=key-length [options] --
  1739 +
  1740 + OR
  1741 +
1761 --encrypt user-password owner-password key-length [options] -- 1742 --encrypt user-password owner-password key-length [options] --
1762 1743
1763 - Either or both of user-password and owner-password may be empty  
1764 - strings, though setting either to the empty string enables the file  
1765 - to be opened and decrypted without a password. key-length may be  
1766 - 40, 128, or 256. Encryption options are terminated by "--" by  
1767 - itself. 1744 + The first form, with flags for the passwords and bit length, was
  1745 + introduced in qpdf 11.7.0. Only the --bits option is is mandatory.
  1746 + This form allows you to use any text as the password. If passwords
  1747 + are specified, they must be given before the --bits option.
  1748 +
  1749 + The second form has been in qpdf since the beginning and wil
  1750 + continue to be supported. Either or both of user-password and
  1751 + owner-password may be empty strings.
  1752 +
  1753 + The key-length parameter must be either 40, 128, or 256. The user
  1754 + and/or owner password may be omitted. Omitting either pasword
  1755 + enables the PDF file to be opened without a password. Specifying
  1756 + the same value for the user and owner password and specifying an
  1757 + empty owner password are both considered insecure.
  1758 +
  1759 + Encryption options are terminated by "--" by itself.
1768 1760
1769 40-bit encryption is insecure, as is 128-bit encryption without 1761 40-bit encryption is insecure, as is 128-bit encryption without
1770 AES. Use 256-bit encryption unless you have a specific reason to 1762 AES. Use 256-bit encryption unless you have a specific reason to
@@ -1823,17 +1815,38 @@ and :qpdf:ref:`--copy-encryption`. For a more in-depth technical @@ -1823,17 +1815,38 @@ and :qpdf:ref:`--copy-encryption`. For a more in-depth technical
1823 discussion of how PDF encryption works internally, see 1815 discussion of how PDF encryption works internally, see
1824 :ref:`pdf-encryption`. 1816 :ref:`pdf-encryption`.
1825 1817
1826 -To create an encrypted file, use 1818 +To create an encrypted file, use one of
  1819 +
  1820 +::
  1821 +
  1822 + --encrypt \
  1823 + [--user-password=user-password] \
  1824 + [--owner-password=owner-password] \
  1825 + --bits=key-length [options] --
  1826 +
  1827 +OR
1827 1828
1828 :: 1829 ::
1829 1830
1830 --encrypt user-password owner-password key-length [options] -- 1831 --encrypt user-password owner-password key-length [options] --
1831 1832
1832 -Either or both of :samp:`{user-password}` and :samp:`{owner-password}`  
1833 -may be empty strings, though setting either to the empty string  
1834 -enables the file to be opened and decrypted without a password..  
1835 -:samp:`{key-length}` may be ``40``, ``128``, or ``256``. Encryption  
1836 -options are terminated by ``--`` by itself. 1833 +The first form, with flags for the passwords and bit length, was
  1834 +introduced in qpdf 11.7.0. Only the :qpdf:ref:`--bits` option is is
  1835 +mandatory. This form allows you to use any text as the password. If
  1836 +passwords are specified, they must be given before the
  1837 +:qpdf:ref:`--bits` option.
  1838 +
  1839 +The second form has been in qpdf since the beginning and wil
  1840 +continue to be supported. Either or both of user-password and
  1841 +owner-password may be empty strings.
  1842 +
  1843 +The ``key-length`` parameter must be either ``40``, ``128``, or
  1844 +``256``. The user and/or owner password may be omitted. Omitting
  1845 +either pasword enables the PDF file to be opened without a password.
  1846 +Specifying the same value for the user and owner password and
  1847 +specifying an empty owner password are both considered insecure.
  1848 +
  1849 +Encryption options are terminated by ``--`` by itself.
1837 1850
1838 40-bit encryption is insecure, as is 128-bit encryption without AES. 1851 40-bit encryption is insecure, as is 128-bit encryption without AES.
1839 Use 256-bit encryption unless you have a specific reason to use an 1852 Use 256-bit encryption unless you have a specific reason to use an
@@ -1971,6 +1984,36 @@ help for each option. @@ -1971,6 +1984,36 @@ help for each option.
1971 Related Options 1984 Related Options
1972 ~~~~~~~~~~~~~~~ 1985 ~~~~~~~~~~~~~~~
1973 1986
  1987 +.. qpdf:option:: --user-password=user-password
  1988 +
  1989 + .. help: specify user password
  1990 +
  1991 + Set the user password of the encrypted file.
  1992 +
  1993 + Set the user passwrod of the encrypted file. Conforming readers
  1994 + apply security restrictions to files opened with the user password.
  1995 +
  1996 +.. qpdf:option:: --owner-password=owner-password
  1997 +
  1998 + .. help: specify owner password
  1999 +
  2000 + Set the owner password of the encrypted file.
  2001 +
  2002 + Set the owner passwrod of the encrypted file. Conforming readers
  2003 + apply allow security restrictions to be changed or overridden when
  2004 + files are opened with the owner password.
  2005 +
  2006 +.. qpdf:option:: --bits={48|128|256}
  2007 +
  2008 + .. help: specify encryption key length
  2009 +
  2010 + Specify the encryption key length. For best security, always use
  2011 + a key length of 256.
  2012 +
  2013 + Set the key length for encrypted files. You should always use
  2014 + ``--bits=256`` unless you have a strong reason to create a file
  2015 + with weaker encryption.
  2016 +
1974 .. qpdf:option:: --accessibility=[y|n] 2017 .. qpdf:option:: --accessibility=[y|n]
1975 2018
1976 .. help: restrict document accessibility 2019 .. help: restrict document accessibility
manual/release-notes.rst
@@ -61,6 +61,16 @@ Planned changes for future 12.x (subject to change): @@ -61,6 +61,16 @@ Planned changes for future 12.x (subject to change):
61 Previously, any value other than ``/Yes`` or ``/Off`` was 61 Previously, any value other than ``/Yes`` or ``/Off`` was
62 rejected. 62 rejected.
63 63
  64 + - CLI Enhancements:
  65 +
  66 + - Allow the syntax ``--encrypt --user-password=user-password
  67 + --owner-password=owner-password --bits={40,128,256}`` when
  68 + encrypting PDF files. This is an alternative to the syntax
  69 + ``--encrypt user-password owner-password {40,128,256}``, which
  70 + will continue to be supported. The new syntax works better with
  71 + shell completion and allows creation of passwords that start
  72 + with ``-``.
  73 +
64 - Build Enhancements: 74 - Build Enhancements:
65 75
66 - The qpdf test suite now passes when qpdf is linked with an 76 - The qpdf test suite now passes when qpdf is linked with an
qpdf/qtest/arg-parsing.test
@@ -15,7 +15,7 @@ cleanup(); @@ -15,7 +15,7 @@ cleanup();
15 15
16 my $td = new TestDriver('arg-parsing'); 16 my $td = new TestDriver('arg-parsing');
17 17
18 -my $n_tests = 17; 18 +my $n_tests = 21;
19 19
20 $td->runtest("required argument", 20 $td->runtest("required argument",
21 {$td->COMMAND => "qpdf --password minimal.pdf"}, 21 {$td->COMMAND => "qpdf --password minimal.pdf"},
@@ -108,5 +108,21 @@ $td-&gt;runtest(&quot;empty and replace-input&quot;, @@ -108,5 +108,21 @@ $td-&gt;runtest(&quot;empty and replace-input&quot;,
108 $td->EXIT_STATUS => 2}, 108 $td->EXIT_STATUS => 2},
109 $td->NORMALIZE_NEWLINES); 109 $td->NORMALIZE_NEWLINES);
110 110
  111 +# Disallow mixing positional and flag-style encryption arguments.
  112 +my @bad_enc = (
  113 + "u --owner-password=x",
  114 + "u o --bits=128",
  115 + "--user-password=u o",
  116 + "--user-password=u --owner-password=o 256",
  117 + );
  118 +foreach my $arg (@bad_enc)
  119 +{
  120 + $td->runtest("mixed encryption args ($arg)",
  121 + {$td->COMMAND => "qpdf --encrypt $arg"},
  122 + {$td->REGEXP => ".*positional and dashed encryption arguments may not be mixed",
  123 + $td->EXIT_STATUS => 2},
  124 + $td->NORMALIZE_NEWLINES);
  125 +}
  126 +
111 cleanup(); 127 cleanup();
112 $td->report($n_tests); 128 $td->report($n_tests);
qpdf/qtest/completion.test
@@ -29,14 +29,7 @@ my @completion_tests = ( @@ -29,14 +29,7 @@ my @completion_tests = (
29 ['qpdf ', undef, 'top'], 29 ['qpdf ', undef, 'top'],
30 ['qpdf -', undef, 'top-arg'], 30 ['qpdf -', undef, 'top-arg'],
31 ['qpdf --enc', undef, 'enc'], 31 ['qpdf --enc', undef, 'enc'],
32 - ['qpdf --encrypt ', undef, 'encrypt'],  
33 - ['qpdf --encrypt u ', undef, 'encrypt-u'],  
34 - ['qpdf --encrypt u o ', undef, 'encrypt-u-o'],  
35 - ['qpdf @encrypt-u o ', undef, 'encrypt-u-o'],  
36 - ['qpdf --encrypt u o 40 --', undef, 'encrypt-40'],  
37 - ['qpdf --encrypt u o 128 --', undef, 'encrypt-128'],  
38 - ['qpdf --encrypt u o 256 --', undef, 'encrypt-256'],  
39 - ['qpdf --encrypt u o bad --', undef, 'encrypt-bad'], 32 + ['qpdf --encrypt -', undef, 'encrypt'],
40 ['qpdf --split-pag', undef, 'split'], 33 ['qpdf --split-pag', undef, 'split'],
41 ['qpdf --decode-l', undef, 'decode-l'], 34 ['qpdf --decode-l', undef, 'decode-l'],
42 ['qpdf --decode-lzzz', 15, 'decode-l'], 35 ['qpdf --decode-lzzz', 15, 'decode-l'],
@@ -44,11 +37,11 @@ my @completion_tests = ( @@ -44,11 +37,11 @@ my @completion_tests = (
44 ['qpdf --decode-level=g', undef, 'decode-level-g'], 37 ['qpdf --decode-level=g', undef, 'decode-level-g'],
45 ['qpdf --check -', undef, 'later-arg'], 38 ['qpdf --check -', undef, 'later-arg'],
46 ['qpdf infile outfile oops --ch', undef, 'usage-empty'], 39 ['qpdf infile outfile oops --ch', undef, 'usage-empty'],
47 - ['qpdf --encrypt \'user " password\' ', undef, 'quoting'],  
48 - ['qpdf --encrypt \'user password\' ', undef, 'quoting'],  
49 - ['qpdf --encrypt "user password" ', undef, 'quoting'],  
50 - ['qpdf --encrypt "user pass\'word" ', undef, 'quoting'],  
51 - ['qpdf --encrypt user\ password ', undef, 'quoting'], 40 + ['qpdf \'input " file\' --q', undef, 'quoting'],
  41 + ['qpdf \'input file\' --q', undef, 'quoting'],
  42 + ['qpdf "input file" --q', undef, 'quoting'],
  43 + ['qpdf "input fi\'le" --q', undef, 'quoting'],
  44 + ['qpdf input\ file --q', undef, 'quoting'],
52 ); 45 );
53 my $n_tests = 2 * scalar(@completion_tests); 46 my $n_tests = 2 * scalar(@completion_tests);
54 my $completion_filter = 47 my $completion_filter =
qpdf/qtest/qpdf/completion-encrypt-zsh.out 0 → 100644
  1 +--bits=128
  2 +--bits=256
  3 +--bits=40
  4 +--owner-password=
  5 +--user-password=
  6 +!--print
qpdf/qtest/qpdf/completion-encrypt.out
1 -user-password 1 +--bits=
  2 +--owner-password=
  3 +--user-password=
  4 +!--bits=128
  5 +!--bits=256
  6 +!--bits=40
2 !--print 7 !--print
qpdf/qtest/qpdf/completion-quoting.out
1 -owner-password 1 +--qdf
qpdf/qtest/qpdf/encrypt-u
1 --encrypt 1 --encrypt
2 -u 2 +--bits=2