Commit 7d7e2234a537b6cd2205fb5cf942d5a9e8a866e3
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.
Showing
12 changed files
with
224 additions
and
118 deletions
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("--encryption-file-password", "transformation", "supply passwor | @@ -187,6 +172,9 @@ ap.addOptionHelp("--encryption-file-password", "transformation", "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("--ii-min-bytes", "transformation", "set minimum size for --ext | @@ -282,9 +270,6 @@ ap.addOptionHelp("--ii-min-bytes", "transformation", "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("--keep-inline-images", "modification", "exclude inline images | @@ -404,18 +392,33 @@ ap.addOptionHelp("--keep-inline-images", "modification", "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->runtest("empty and replace-input", | @@ -108,5 +108,21 @@ $td->runtest("empty and replace-input", | ||
| 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
qpdf/qtest/qpdf/completion-encrypt.out
qpdf/qtest/qpdf/completion-quoting.out
qpdf/qtest/qpdf/encrypt-u