From b7bbf12e85fa46e7971d84143d1597c992045af1 Mon Sep 17 00:00:00 2001 From: Jay Berkenbilt Date: Mon, 30 May 2022 10:55:07 -0400 Subject: [PATCH] In json mode, reveal recovered user password when otherwise unavailable --- ChangeLog | 5 +++++ TODO | 2 -- libqpdf/QPDFJob.cc | 10 ++++++++++ manual/release-notes.rst | 6 ++++++ qpdf/qtest/encryption.test | 4 ++++ .../json-V4-aes-encrypt---show-encryption-key-v1.out | 1 + .../json-V4-aes-encrypt---show-encryption-key-v2.out | 1 + qpdf/qtest/qpdf/json-V4-aes-encrypt-v1.out | 1 + qpdf/qtest/qpdf/json-V4-aes-encrypt-v2.out | 1 + qpdf/qtest/qpdf/json-attachment-fields-v1.out | 1 + qpdf/qtest/qpdf/json-attachment-fields-v2.out | 1 + .../qpdf/json-field-types---show-encryption-key-v1.out | 1 + .../qpdf/json-field-types---show-encryption-key-v2.out | 1 + qpdf/qtest/qpdf/json-field-types-v1.out | 1 + qpdf/qtest/qpdf/json-field-types-v2.out | 1 + qpdf/qtest/qpdf/json-image-streams-all-v1.out | 1 + qpdf/qtest/qpdf/json-image-streams-all-v2.out | 1 + qpdf/qtest/qpdf/json-image-streams-small-v1.out | 1 + qpdf/qtest/qpdf/json-image-streams-small-v2.out | 1 + qpdf/qtest/qpdf/json-image-streams-specialized-v1.out | 1 + qpdf/qtest/qpdf/json-image-streams-specialized-v2.out | 1 + qpdf/qtest/qpdf/json-image-streams-v1.out | 1 + qpdf/qtest/qpdf/json-image-streams-v2.out | 1 + qpdf/qtest/qpdf/json-outlines-with-actions-v1.out | 1 + qpdf/qtest/qpdf/json-outlines-with-actions-v2.out | 1 + .../qpdf/json-outlines-with-old-root-dests-v1.out | 1 + .../qpdf/json-outlines-with-old-root-dests-v2.out | 1 + qpdf/qtest/qpdf/json-page-labels-and-outlines-v1.out | 1 + qpdf/qtest/qpdf/json-page-labels-and-outlines-v2.out | 1 + qpdf/qtest/qpdf/json-page-labels-num-tree-v1.out | 1 + qpdf/qtest/qpdf/json-page-labels-num-tree-v2.out | 1 + 31 files changed, 51 insertions(+), 2 deletions(-) diff --git a/ChangeLog b/ChangeLog index 0d60467d..2013020a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,10 @@ 2022-05-30 Jay Berkenbilt + * When showing encryption data in json output, when the user + password was recovered with by the owner password and the + specified password does not match the user password, reveal the + user password. This is not possible with 256-bit keys. + * Include additional information in --list-attachments --verbose and in --json --json-key=attachments. diff --git a/TODO b/TODO index 663dbb40..b52dd93a 100644 --- a/TODO +++ b/TODO @@ -70,8 +70,6 @@ Remaining work: * --show-xref: add - * --encryption: show recovered user password when available - * Consider having --check, --show-encryption, etc., just select the right keys when in json mode. I don't think I want check on by default, so that might be different. diff --git a/libqpdf/QPDFJob.cc b/libqpdf/QPDFJob.cc index d7cc66cf..b6ba4b4f 100644 --- a/libqpdf/QPDFJob.cc +++ b/libqpdf/QPDFJob.cc @@ -1382,6 +1382,15 @@ QPDFJob::doJSONEncrypt(Pipeline* p, bool& first, QPDF& pdf) j_encrypt.addDictionaryMember( "ownerpasswordmatched", JSON::makeBool(is_encrypted && pdf.ownerPasswordMatched())); + if (is_encrypted && (V < 5) && pdf.ownerPasswordMatched() && + (!pdf.userPasswordMatched())) { + std::string user_password = pdf.getTrimmedUserPassword(); + j_encrypt.addDictionaryMember( + "recovereduserpassword", JSON::makeString(user_password)); + } else { + j_encrypt.addDictionaryMember( + "recovereduserpassword", JSON::makeNull()); + } JSON j_capabilities = j_encrypt.addDictionaryMember("capabilities", JSON::makeDictionary()); j_capabilities.addDictionaryMember( @@ -1669,6 +1678,7 @@ QPDFJob::json_schema(int json_version, std::set* keys) }, "encrypted": "whether the document is encrypted", "ownerpasswordmatched": "whether supplied password matched owner password; always false for non-encrypted files", + "recovereduserpassword": "If the owner password was used to recover the user password, reveal user password; otherwise null", "parameters": { "P": "P value from Encrypt dictionary", "R": "R value from Encrypt dictionary", diff --git a/manual/release-notes.rst b/manual/release-notes.rst index d106ec11..ad8d14f9 100644 --- a/manual/release-notes.rst +++ b/manual/release-notes.rst @@ -103,6 +103,12 @@ For a detailed list of changes, please see the file attachments is also included in the ``attachments`` json key with ``--json``. + - For encrypted files, ``qpdf --json`` reveals the user password + when the specified password did not match the user password and + the owner password was used to recover the user password. The + user password is not recoverable from the owner password when + 256-bit keys are in use. + - Library Enhancements - New methods ``insertItemAndGet``, ``appendItemAndGet``, diff --git a/qpdf/qtest/encryption.test b/qpdf/qtest/encryption.test index 622c595b..27974a82 100644 --- a/qpdf/qtest/encryption.test +++ b/qpdf/qtest/encryption.test @@ -219,6 +219,7 @@ foreach my $d (@encrypted_files) " \"streammethod\": \"---method---\",\n" . " \"stringmethod\": \"---method---\"\n" . " },\n" . + " \"recovereduserpassword\": ---rup---,\n" . " \"userpasswordmatched\": ---upm---\n" . " }\n" . "}\n"; @@ -267,6 +268,8 @@ foreach my $d (@encrypted_files) my $method = $bits == 256 ? "AESv3" : "RC4"; my $opm = ($pass eq $opass ? "true" : "false"); my $upm = ($pass eq $upass ? "true" : "false"); + my $rup = (($pass eq $opass) && ($pass ne $upass) && ($V < 5)) + ? "\"$upass\"" : "null"; $enc_json =~ s/---R---/$R/; $enc_json =~ s/---P---/$P/; $enc_json =~ s/---V---/$V/; @@ -274,6 +277,7 @@ foreach my $d (@encrypted_files) $enc_json =~ s/---method---/$method/g; $enc_json =~ s/---opm---/$opm/; $enc_json =~ s/---upm---/$upm/; + $enc_json =~ s/---rup---/$rup/; my $eflags = "--allow-weak-crypto" . " -encrypt \"$upass\" \"$opass\" $bits $xeflags --"; diff --git a/qpdf/qtest/qpdf/json-V4-aes-encrypt---show-encryption-key-v1.out b/qpdf/qtest/qpdf/json-V4-aes-encrypt---show-encryption-key-v1.out index 4fd5eb7f..234d38c4 100644 --- a/qpdf/qtest/qpdf/json-V4-aes-encrypt---show-encryption-key-v1.out +++ b/qpdf/qtest/qpdf/json-V4-aes-encrypt---show-encryption-key-v1.out @@ -28,6 +28,7 @@ "streammethod": "AESv2", "stringmethod": "AESv2" }, + "recovereduserpassword": null, "userpasswordmatched": true } } diff --git a/qpdf/qtest/qpdf/json-V4-aes-encrypt---show-encryption-key-v2.out b/qpdf/qtest/qpdf/json-V4-aes-encrypt---show-encryption-key-v2.out index 5f32eb44..c400571f 100644 --- a/qpdf/qtest/qpdf/json-V4-aes-encrypt---show-encryption-key-v2.out +++ b/qpdf/qtest/qpdf/json-V4-aes-encrypt---show-encryption-key-v2.out @@ -28,6 +28,7 @@ "streammethod": "AESv2", "stringmethod": "AESv2" }, + "recovereduserpassword": null, "userpasswordmatched": true } } diff --git a/qpdf/qtest/qpdf/json-V4-aes-encrypt-v1.out b/qpdf/qtest/qpdf/json-V4-aes-encrypt-v1.out index f67c29f6..f1247a5a 100644 --- a/qpdf/qtest/qpdf/json-V4-aes-encrypt-v1.out +++ b/qpdf/qtest/qpdf/json-V4-aes-encrypt-v1.out @@ -28,6 +28,7 @@ "streammethod": "AESv2", "stringmethod": "AESv2" }, + "recovereduserpassword": null, "userpasswordmatched": true } } diff --git a/qpdf/qtest/qpdf/json-V4-aes-encrypt-v2.out b/qpdf/qtest/qpdf/json-V4-aes-encrypt-v2.out index 959f0103..b516a301 100644 --- a/qpdf/qtest/qpdf/json-V4-aes-encrypt-v2.out +++ b/qpdf/qtest/qpdf/json-V4-aes-encrypt-v2.out @@ -28,6 +28,7 @@ "streammethod": "AESv2", "stringmethod": "AESv2" }, + "recovereduserpassword": null, "userpasswordmatched": true } } diff --git a/qpdf/qtest/qpdf/json-attachment-fields-v1.out b/qpdf/qtest/qpdf/json-attachment-fields-v1.out index 21fe88be..fbb07895 100644 --- a/qpdf/qtest/qpdf/json-attachment-fields-v1.out +++ b/qpdf/qtest/qpdf/json-attachment-fields-v1.out @@ -96,6 +96,7 @@ "streammethod": "none", "stringmethod": "none" }, + "recovereduserpassword": null, "userpasswordmatched": false }, "outlines": [], diff --git a/qpdf/qtest/qpdf/json-attachment-fields-v2.out b/qpdf/qtest/qpdf/json-attachment-fields-v2.out index e94e05dc..0241dc15 100644 --- a/qpdf/qtest/qpdf/json-attachment-fields-v2.out +++ b/qpdf/qtest/qpdf/json-attachment-fields-v2.out @@ -96,6 +96,7 @@ "streammethod": "none", "stringmethod": "none" }, + "recovereduserpassword": null, "userpasswordmatched": false }, "outlines": [], diff --git a/qpdf/qtest/qpdf/json-field-types---show-encryption-key-v1.out b/qpdf/qtest/qpdf/json-field-types---show-encryption-key-v1.out index 43e07e53..9c9371b8 100644 --- a/qpdf/qtest/qpdf/json-field-types---show-encryption-key-v1.out +++ b/qpdf/qtest/qpdf/json-field-types---show-encryption-key-v1.out @@ -428,6 +428,7 @@ "streammethod": "none", "stringmethod": "none" }, + "recovereduserpassword": null, "userpasswordmatched": false }, "outlines": [], diff --git a/qpdf/qtest/qpdf/json-field-types---show-encryption-key-v2.out b/qpdf/qtest/qpdf/json-field-types---show-encryption-key-v2.out index f5229296..51c75655 100644 --- a/qpdf/qtest/qpdf/json-field-types---show-encryption-key-v2.out +++ b/qpdf/qtest/qpdf/json-field-types---show-encryption-key-v2.out @@ -428,6 +428,7 @@ "streammethod": "none", "stringmethod": "none" }, + "recovereduserpassword": null, "userpasswordmatched": false }, "outlines": [], diff --git a/qpdf/qtest/qpdf/json-field-types-v1.out b/qpdf/qtest/qpdf/json-field-types-v1.out index 43e07e53..9c9371b8 100644 --- a/qpdf/qtest/qpdf/json-field-types-v1.out +++ b/qpdf/qtest/qpdf/json-field-types-v1.out @@ -428,6 +428,7 @@ "streammethod": "none", "stringmethod": "none" }, + "recovereduserpassword": null, "userpasswordmatched": false }, "outlines": [], diff --git a/qpdf/qtest/qpdf/json-field-types-v2.out b/qpdf/qtest/qpdf/json-field-types-v2.out index f5229296..51c75655 100644 --- a/qpdf/qtest/qpdf/json-field-types-v2.out +++ b/qpdf/qtest/qpdf/json-field-types-v2.out @@ -428,6 +428,7 @@ "streammethod": "none", "stringmethod": "none" }, + "recovereduserpassword": null, "userpasswordmatched": false }, "outlines": [], diff --git a/qpdf/qtest/qpdf/json-image-streams-all-v1.out b/qpdf/qtest/qpdf/json-image-streams-all-v1.out index e8dba0e9..75881c03 100644 --- a/qpdf/qtest/qpdf/json-image-streams-all-v1.out +++ b/qpdf/qtest/qpdf/json-image-streams-all-v1.out @@ -271,6 +271,7 @@ "streammethod": "none", "stringmethod": "none" }, + "recovereduserpassword": null, "userpasswordmatched": false }, "outlines": [], diff --git a/qpdf/qtest/qpdf/json-image-streams-all-v2.out b/qpdf/qtest/qpdf/json-image-streams-all-v2.out index 18e2e7b9..5f09fe46 100644 --- a/qpdf/qtest/qpdf/json-image-streams-all-v2.out +++ b/qpdf/qtest/qpdf/json-image-streams-all-v2.out @@ -271,6 +271,7 @@ "streammethod": "none", "stringmethod": "none" }, + "recovereduserpassword": null, "userpasswordmatched": false }, "outlines": [], diff --git a/qpdf/qtest/qpdf/json-image-streams-small-v1.out b/qpdf/qtest/qpdf/json-image-streams-small-v1.out index 0f6ef237..606b5875 100644 --- a/qpdf/qtest/qpdf/json-image-streams-small-v1.out +++ b/qpdf/qtest/qpdf/json-image-streams-small-v1.out @@ -271,6 +271,7 @@ "streammethod": "none", "stringmethod": "none" }, + "recovereduserpassword": null, "userpasswordmatched": false }, "outlines": [], diff --git a/qpdf/qtest/qpdf/json-image-streams-small-v2.out b/qpdf/qtest/qpdf/json-image-streams-small-v2.out index 515192e1..c5f7abbe 100644 --- a/qpdf/qtest/qpdf/json-image-streams-small-v2.out +++ b/qpdf/qtest/qpdf/json-image-streams-small-v2.out @@ -271,6 +271,7 @@ "streammethod": "none", "stringmethod": "none" }, + "recovereduserpassword": null, "userpasswordmatched": false }, "outlines": [], diff --git a/qpdf/qtest/qpdf/json-image-streams-specialized-v1.out b/qpdf/qtest/qpdf/json-image-streams-specialized-v1.out index 558baaf8..f4e24c63 100644 --- a/qpdf/qtest/qpdf/json-image-streams-specialized-v1.out +++ b/qpdf/qtest/qpdf/json-image-streams-specialized-v1.out @@ -271,6 +271,7 @@ "streammethod": "none", "stringmethod": "none" }, + "recovereduserpassword": null, "userpasswordmatched": false }, "outlines": [], diff --git a/qpdf/qtest/qpdf/json-image-streams-specialized-v2.out b/qpdf/qtest/qpdf/json-image-streams-specialized-v2.out index d5a0eba9..773a1ba6 100644 --- a/qpdf/qtest/qpdf/json-image-streams-specialized-v2.out +++ b/qpdf/qtest/qpdf/json-image-streams-specialized-v2.out @@ -271,6 +271,7 @@ "streammethod": "none", "stringmethod": "none" }, + "recovereduserpassword": null, "userpasswordmatched": false }, "outlines": [], diff --git a/qpdf/qtest/qpdf/json-image-streams-v1.out b/qpdf/qtest/qpdf/json-image-streams-v1.out index 9cb0f859..98dc7f0f 100644 --- a/qpdf/qtest/qpdf/json-image-streams-v1.out +++ b/qpdf/qtest/qpdf/json-image-streams-v1.out @@ -271,6 +271,7 @@ "streammethod": "none", "stringmethod": "none" }, + "recovereduserpassword": null, "userpasswordmatched": false }, "outlines": [], diff --git a/qpdf/qtest/qpdf/json-image-streams-v2.out b/qpdf/qtest/qpdf/json-image-streams-v2.out index 6169cb3c..f0fa7775 100644 --- a/qpdf/qtest/qpdf/json-image-streams-v2.out +++ b/qpdf/qtest/qpdf/json-image-streams-v2.out @@ -271,6 +271,7 @@ "streammethod": "none", "stringmethod": "none" }, + "recovereduserpassword": null, "userpasswordmatched": false }, "outlines": [], diff --git a/qpdf/qtest/qpdf/json-outlines-with-actions-v1.out b/qpdf/qtest/qpdf/json-outlines-with-actions-v1.out index c52e9b58..b7959c23 100644 --- a/qpdf/qtest/qpdf/json-outlines-with-actions-v1.out +++ b/qpdf/qtest/qpdf/json-outlines-with-actions-v1.out @@ -462,6 +462,7 @@ "streammethod": "none", "stringmethod": "none" }, + "recovereduserpassword": null, "userpasswordmatched": false }, "outlines": [ diff --git a/qpdf/qtest/qpdf/json-outlines-with-actions-v2.out b/qpdf/qtest/qpdf/json-outlines-with-actions-v2.out index f235dec4..224f5d8d 100644 --- a/qpdf/qtest/qpdf/json-outlines-with-actions-v2.out +++ b/qpdf/qtest/qpdf/json-outlines-with-actions-v2.out @@ -462,6 +462,7 @@ "streammethod": "none", "stringmethod": "none" }, + "recovereduserpassword": null, "userpasswordmatched": false }, "outlines": [ diff --git a/qpdf/qtest/qpdf/json-outlines-with-old-root-dests-v1.out b/qpdf/qtest/qpdf/json-outlines-with-old-root-dests-v1.out index 49ecb410..c82e6128 100644 --- a/qpdf/qtest/qpdf/json-outlines-with-old-root-dests-v1.out +++ b/qpdf/qtest/qpdf/json-outlines-with-old-root-dests-v1.out @@ -567,6 +567,7 @@ "streammethod": "none", "stringmethod": "none" }, + "recovereduserpassword": null, "userpasswordmatched": false }, "outlines": [ diff --git a/qpdf/qtest/qpdf/json-outlines-with-old-root-dests-v2.out b/qpdf/qtest/qpdf/json-outlines-with-old-root-dests-v2.out index 357e99d2..25b59719 100644 --- a/qpdf/qtest/qpdf/json-outlines-with-old-root-dests-v2.out +++ b/qpdf/qtest/qpdf/json-outlines-with-old-root-dests-v2.out @@ -567,6 +567,7 @@ "streammethod": "none", "stringmethod": "none" }, + "recovereduserpassword": null, "userpasswordmatched": false }, "outlines": [ diff --git a/qpdf/qtest/qpdf/json-page-labels-and-outlines-v1.out b/qpdf/qtest/qpdf/json-page-labels-and-outlines-v1.out index 4154693d..d51face5 100644 --- a/qpdf/qtest/qpdf/json-page-labels-and-outlines-v1.out +++ b/qpdf/qtest/qpdf/json-page-labels-and-outlines-v1.out @@ -637,6 +637,7 @@ "streammethod": "none", "stringmethod": "none" }, + "recovereduserpassword": null, "userpasswordmatched": false }, "outlines": [ diff --git a/qpdf/qtest/qpdf/json-page-labels-and-outlines-v2.out b/qpdf/qtest/qpdf/json-page-labels-and-outlines-v2.out index 14775582..5e73a3d6 100644 --- a/qpdf/qtest/qpdf/json-page-labels-and-outlines-v2.out +++ b/qpdf/qtest/qpdf/json-page-labels-and-outlines-v2.out @@ -637,6 +637,7 @@ "streammethod": "none", "stringmethod": "none" }, + "recovereduserpassword": null, "userpasswordmatched": false }, "outlines": [ diff --git a/qpdf/qtest/qpdf/json-page-labels-num-tree-v1.out b/qpdf/qtest/qpdf/json-page-labels-num-tree-v1.out index a4bc2cfa..ac67bcec 100644 --- a/qpdf/qtest/qpdf/json-page-labels-num-tree-v1.out +++ b/qpdf/qtest/qpdf/json-page-labels-num-tree-v1.out @@ -534,6 +534,7 @@ "streammethod": "none", "stringmethod": "none" }, + "recovereduserpassword": null, "userpasswordmatched": false }, "outlines": [], diff --git a/qpdf/qtest/qpdf/json-page-labels-num-tree-v2.out b/qpdf/qtest/qpdf/json-page-labels-num-tree-v2.out index 1bfa1ad3..902223fc 100644 --- a/qpdf/qtest/qpdf/json-page-labels-num-tree-v2.out +++ b/qpdf/qtest/qpdf/json-page-labels-num-tree-v2.out @@ -534,6 +534,7 @@ "streammethod": "none", "stringmethod": "none" }, + "recovereduserpassword": null, "userpasswordmatched": false }, "outlines": [],