Commit b7bbf12e85fa46e7971d84143d1597c992045af1
1 parent
f049a77c
In json mode, reveal recovered user password when otherwise unavailable
Showing
31 changed files
with
51 additions
and
2 deletions
ChangeLog
| 1 | 1 | 2022-05-30 Jay Berkenbilt <ejb@ql.org> |
| 2 | 2 | |
| 3 | + * When showing encryption data in json output, when the user | |
| 4 | + password was recovered with by the owner password and the | |
| 5 | + specified password does not match the user password, reveal the | |
| 6 | + user password. This is not possible with 256-bit keys. | |
| 7 | + | |
| 3 | 8 | * Include additional information in --list-attachments --verbose |
| 4 | 9 | and in --json --json-key=attachments. |
| 5 | 10 | ... | ... |
TODO
| ... | ... | @@ -70,8 +70,6 @@ Remaining work: |
| 70 | 70 | |
| 71 | 71 | * --show-xref: add |
| 72 | 72 | |
| 73 | - * --encryption: show recovered user password when available | |
| 74 | - | |
| 75 | 73 | * Consider having --check, --show-encryption, etc., just select the |
| 76 | 74 | right keys when in json mode. I don't think I want check on by |
| 77 | 75 | default, so that might be different. | ... | ... |
libqpdf/QPDFJob.cc
| ... | ... | @@ -1382,6 +1382,15 @@ QPDFJob::doJSONEncrypt(Pipeline* p, bool& first, QPDF& pdf) |
| 1382 | 1382 | j_encrypt.addDictionaryMember( |
| 1383 | 1383 | "ownerpasswordmatched", |
| 1384 | 1384 | JSON::makeBool(is_encrypted && pdf.ownerPasswordMatched())); |
| 1385 | + if (is_encrypted && (V < 5) && pdf.ownerPasswordMatched() && | |
| 1386 | + (!pdf.userPasswordMatched())) { | |
| 1387 | + std::string user_password = pdf.getTrimmedUserPassword(); | |
| 1388 | + j_encrypt.addDictionaryMember( | |
| 1389 | + "recovereduserpassword", JSON::makeString(user_password)); | |
| 1390 | + } else { | |
| 1391 | + j_encrypt.addDictionaryMember( | |
| 1392 | + "recovereduserpassword", JSON::makeNull()); | |
| 1393 | + } | |
| 1385 | 1394 | JSON j_capabilities = |
| 1386 | 1395 | j_encrypt.addDictionaryMember("capabilities", JSON::makeDictionary()); |
| 1387 | 1396 | j_capabilities.addDictionaryMember( |
| ... | ... | @@ -1669,6 +1678,7 @@ QPDFJob::json_schema(int json_version, std::set<std::string>* keys) |
| 1669 | 1678 | }, |
| 1670 | 1679 | "encrypted": "whether the document is encrypted", |
| 1671 | 1680 | "ownerpasswordmatched": "whether supplied password matched owner password; always false for non-encrypted files", |
| 1681 | + "recovereduserpassword": "If the owner password was used to recover the user password, reveal user password; otherwise null", | |
| 1672 | 1682 | "parameters": { |
| 1673 | 1683 | "P": "P value from Encrypt dictionary", |
| 1674 | 1684 | "R": "R value from Encrypt dictionary", | ... | ... |
manual/release-notes.rst
| ... | ... | @@ -103,6 +103,12 @@ For a detailed list of changes, please see the file |
| 103 | 103 | attachments is also included in the ``attachments`` json key |
| 104 | 104 | with ``--json``. |
| 105 | 105 | |
| 106 | + - For encrypted files, ``qpdf --json`` reveals the user password | |
| 107 | + when the specified password did not match the user password and | |
| 108 | + the owner password was used to recover the user password. The | |
| 109 | + user password is not recoverable from the owner password when | |
| 110 | + 256-bit keys are in use. | |
| 111 | + | |
| 106 | 112 | - Library Enhancements |
| 107 | 113 | |
| 108 | 114 | - New methods ``insertItemAndGet``, ``appendItemAndGet``, | ... | ... |
qpdf/qtest/encryption.test
| ... | ... | @@ -219,6 +219,7 @@ foreach my $d (@encrypted_files) |
| 219 | 219 | " \"streammethod\": \"---method---\",\n" . |
| 220 | 220 | " \"stringmethod\": \"---method---\"\n" . |
| 221 | 221 | " },\n" . |
| 222 | + " \"recovereduserpassword\": ---rup---,\n" . | |
| 222 | 223 | " \"userpasswordmatched\": ---upm---\n" . |
| 223 | 224 | " }\n" . |
| 224 | 225 | "}\n"; |
| ... | ... | @@ -267,6 +268,8 @@ foreach my $d (@encrypted_files) |
| 267 | 268 | my $method = $bits == 256 ? "AESv3" : "RC4"; |
| 268 | 269 | my $opm = ($pass eq $opass ? "true" : "false"); |
| 269 | 270 | my $upm = ($pass eq $upass ? "true" : "false"); |
| 271 | + my $rup = (($pass eq $opass) && ($pass ne $upass) && ($V < 5)) | |
| 272 | + ? "\"$upass\"" : "null"; | |
| 270 | 273 | $enc_json =~ s/---R---/$R/; |
| 271 | 274 | $enc_json =~ s/---P---/$P/; |
| 272 | 275 | $enc_json =~ s/---V---/$V/; |
| ... | ... | @@ -274,6 +277,7 @@ foreach my $d (@encrypted_files) |
| 274 | 277 | $enc_json =~ s/---method---/$method/g; |
| 275 | 278 | $enc_json =~ s/---opm---/$opm/; |
| 276 | 279 | $enc_json =~ s/---upm---/$upm/; |
| 280 | + $enc_json =~ s/---rup---/$rup/; | |
| 277 | 281 | |
| 278 | 282 | my $eflags = "--allow-weak-crypto" . |
| 279 | 283 | " -encrypt \"$upass\" \"$opass\" $bits $xeflags --"; | ... | ... |
qpdf/qtest/qpdf/json-V4-aes-encrypt---show-encryption-key-v1.out
qpdf/qtest/qpdf/json-V4-aes-encrypt---show-encryption-key-v2.out
qpdf/qtest/qpdf/json-V4-aes-encrypt-v1.out
qpdf/qtest/qpdf/json-V4-aes-encrypt-v2.out
qpdf/qtest/qpdf/json-attachment-fields-v1.out
qpdf/qtest/qpdf/json-attachment-fields-v2.out
qpdf/qtest/qpdf/json-field-types---show-encryption-key-v1.out
qpdf/qtest/qpdf/json-field-types---show-encryption-key-v2.out
qpdf/qtest/qpdf/json-field-types-v1.out
qpdf/qtest/qpdf/json-field-types-v2.out
qpdf/qtest/qpdf/json-image-streams-all-v1.out
qpdf/qtest/qpdf/json-image-streams-all-v2.out
qpdf/qtest/qpdf/json-image-streams-small-v1.out
qpdf/qtest/qpdf/json-image-streams-small-v2.out
qpdf/qtest/qpdf/json-image-streams-specialized-v1.out
qpdf/qtest/qpdf/json-image-streams-specialized-v2.out
qpdf/qtest/qpdf/json-image-streams-v1.out
qpdf/qtest/qpdf/json-image-streams-v2.out
qpdf/qtest/qpdf/json-outlines-with-actions-v1.out
qpdf/qtest/qpdf/json-outlines-with-actions-v2.out
qpdf/qtest/qpdf/json-outlines-with-old-root-dests-v1.out
qpdf/qtest/qpdf/json-outlines-with-old-root-dests-v2.out
qpdf/qtest/qpdf/json-page-labels-and-outlines-v1.out
qpdf/qtest/qpdf/json-page-labels-and-outlines-v2.out
qpdf/qtest/qpdf/json-page-labels-num-tree-v1.out