Add encrypt key to json

This commit is contained in:
Jay Berkenbilt 2020-01-26 14:38:56 -05:00
parent 656d7bc006
commit 12777a04ca
18 changed files with 3341 additions and 3 deletions

View File

@ -1,5 +1,9 @@
2020-01-26 Jay Berkenbilt <ejb@ql.org>
* Add "encrypt" key to the json output. This contains largely the
same information as given by --show-encryption but in a
consistent, parseable format.
* Add options --is-encrypted and --requires-password. These can be
used with files, including encrypted files with unknown passwords,
to determine whether or not a file is encrypted and whether a

View File

@ -4725,6 +4725,18 @@ print "\n";
</para>
</listitem>
</itemizedlist>
<itemizedlist>
<listitem>
<para>
Added <literal>encrypt</literal> key to JSON options. With
the exception of the reconstructed user password for older
encryption formats, this provides the same information as
<option>--show-encryption</option> but in a consistent,
parseable format. See output of <command>qpdf
--json-help</command> for details.
</para>
</listitem>
</itemizedlist>
</listitem>
</itemizedlist>
</listitem>

View File

@ -564,6 +564,83 @@ static JSON json_schema(std::set<std::string>* keys = 0)
"annotation flags from /F --"
" see pdf_annotation_flag_e in qpdf/Constants.h"));
}
if (all_keys || keys->count("encrypt"))
{
JSON encrypt = schema.addDictionaryMember(
"encrypt", JSON::makeDictionary());
encrypt.addDictionaryMember(
"encrypted",
JSON::makeString("whether the document is encrypted"));
encrypt.addDictionaryMember(
"userpasswordmatched",
JSON::makeString("whether supplied password matched user password;"
" always false for non-encrypted files"));
encrypt.addDictionaryMember(
"ownerpasswordmatched",
JSON::makeString("whether supplied password matched owner password;"
" always false for non-encrypted files"));
JSON capabilities = encrypt.addDictionaryMember(
"capabilities", JSON::makeDictionary());
capabilities.addDictionaryMember(
"accessibility",
JSON::makeString("allow extraction for accessibility?"));
capabilities.addDictionaryMember(
"extract",
JSON::makeString("allow extraction?"));
capabilities.addDictionaryMember(
"printlow",
JSON::makeString("allow low resolution printing?"));
capabilities.addDictionaryMember(
"printhigh",
JSON::makeString("allow high resolution printing?"));
capabilities.addDictionaryMember(
"modifyassembly",
JSON::makeString("allow modifying document assembly?"));
capabilities.addDictionaryMember(
"modifyforms",
JSON::makeString("allow modifying forms?"));
capabilities.addDictionaryMember(
"moddifyannotations",
JSON::makeString("allow modifying annotations?"));
capabilities.addDictionaryMember(
"modifyother",
JSON::makeString("allow other modifications?"));
capabilities.addDictionaryMember(
"modify",
JSON::makeString("allow all modifications?"));
JSON parameters = encrypt.addDictionaryMember(
"parameters", JSON::makeDictionary());
parameters.addDictionaryMember(
"R",
JSON::makeString("R value from Encrypt dictionary"));
parameters.addDictionaryMember(
"V",
JSON::makeString("V value from Encrypt dictionary"));
parameters.addDictionaryMember(
"P",
JSON::makeString("P value from Encrypt dictionary"));
parameters.addDictionaryMember(
"bits",
JSON::makeString("encryption key bit length"));
parameters.addDictionaryMember(
"key",
JSON::makeString("encryption key; will be null"
" unless --show-encryption-key was specified"));
parameters.addDictionaryMember(
"method",
JSON::makeString("overall encryption method:"
" none, mixed, RC4, AESv2, AESv3"));
parameters.addDictionaryMember(
"streammethod",
JSON::makeString("encryption method for streams"));
parameters.addDictionaryMember(
"stringmethod",
JSON::makeString("encryption method for string"));
parameters.addDictionaryMember(
"filemethod",
JSON::makeString("encryption method for attachments"));
}
return schema;
}
@ -936,7 +1013,8 @@ ArgParser::initOptionTable()
// The list of selectable top-level keys id duplicated in three
// places: json_schema, do_json, and initOptionTable.
char const* json_key_choices[] = {
"objects", "pages", "pagelabels", "outlines", "acroform", 0};
"objects", "pages", "pagelabels", "outlines", "acroform",
"encrypt", 0};
(*t)["json-key"] = oe_requiredChoices(
&ArgParser::argJsonKey, json_key_choices);
(*t)["json-object"] = oe_requiredParameter(
@ -1568,8 +1646,11 @@ ArgParser::argJsonHelp()
<< std::endl
<< "specify a subset of top-level keys when you invoke qpdf, but the \"version\""
<< std::endl
<< "and \"parameters\" keys will always be present."
<< "and \"parameters\" keys will always be present. Note that the \"encrypt\""
<< std::endl
<< "key's values will be populated for non-encrypted files. Some values will"
<< std::endl
<< "be null, and others will have values that apply to unencrypted files."
<< std::endl
<< json_schema().unparse()
<< std::endl;
@ -3817,6 +3898,106 @@ static void do_json_acroform(QPDF& pdf, Options& o, JSON& j)
}
}
static void do_json_encrypt(QPDF& pdf, Options& o, JSON& j)
{
int R = 0;
int P = 0;
int V = 0;
QPDF::encryption_method_e stream_method = QPDF::e_none;
QPDF::encryption_method_e string_method = QPDF::e_none;
QPDF::encryption_method_e file_method = QPDF::e_none;
bool is_encrypted = pdf.isEncrypted(
R, P, V, stream_method, string_method, file_method);
JSON j_encrypt = j.addDictionaryMember(
"encrypt", JSON::makeDictionary());
j_encrypt.addDictionaryMember(
"encrypted",
JSON::makeBool(is_encrypted));
j_encrypt.addDictionaryMember(
"userpasswordmatched",
JSON::makeBool(is_encrypted && pdf.userPasswordMatched()));
j_encrypt.addDictionaryMember(
"ownerpasswordmatched",
JSON::makeBool(is_encrypted && pdf.ownerPasswordMatched()));
JSON j_capabilities = j_encrypt.addDictionaryMember(
"capabilities", JSON::makeDictionary());
j_capabilities.addDictionaryMember(
"accessibility",
JSON::makeBool(pdf.allowAccessibility()));
j_capabilities.addDictionaryMember(
"extract",
JSON::makeBool(pdf.allowExtractAll()));
j_capabilities.addDictionaryMember(
"printlow",
JSON::makeBool(pdf.allowPrintLowRes()));
j_capabilities.addDictionaryMember(
"printhigh",
JSON::makeBool(pdf.allowPrintHighRes()));
j_capabilities.addDictionaryMember(
"modifyassembly",
JSON::makeBool(pdf.allowModifyAssembly()));
j_capabilities.addDictionaryMember(
"modifyforms",
JSON::makeBool(pdf.allowModifyForm()));
j_capabilities.addDictionaryMember(
"moddifyannotations",
JSON::makeBool(pdf.allowModifyAnnotation()));
j_capabilities.addDictionaryMember(
"modifyother",
JSON::makeBool(pdf.allowModifyOther()));
j_capabilities.addDictionaryMember(
"modify",
JSON::makeBool(pdf.allowModifyAll()));
JSON j_parameters = j_encrypt.addDictionaryMember(
"parameters", JSON::makeDictionary());
j_parameters.addDictionaryMember("R", JSON::makeInt(R));
j_parameters.addDictionaryMember("V", JSON::makeInt(V));
j_parameters.addDictionaryMember("P", JSON::makeInt(P));
int bits = 0;
JSON key = JSON::makeNull();
if (is_encrypted)
{
std::string encryption_key = pdf.getEncryptionKey();
bits = QIntC::to_int(encryption_key.length() * 8);
if (o.show_encryption_key)
{
key = JSON::makeString(QUtil::hex_encode(encryption_key));
}
}
j_parameters.addDictionaryMember("bits", JSON::makeInt(bits));
j_parameters.addDictionaryMember("key", key);
auto fix_method = [is_encrypted](QPDF::encryption_method_e& m) {
if (is_encrypted && m == QPDF::e_none)
{
m = QPDF::e_rc4;
}
};
fix_method(stream_method);
fix_method(string_method);
fix_method(file_method);
std::string s_stream_method = show_encryption_method(stream_method);
std::string s_string_method = show_encryption_method(string_method);
std::string s_file_method = show_encryption_method(file_method);
std::string s_overall_method;
if ((stream_method == string_method) &&
(stream_method == file_method))
{
s_overall_method = s_stream_method;
}
else
{
s_overall_method = "mixed";
}
j_parameters.addDictionaryMember(
"method", JSON::makeString(s_overall_method));
j_parameters.addDictionaryMember(
"streammethod", JSON::makeString(s_stream_method));
j_parameters.addDictionaryMember(
"stringmethod", JSON::makeString(s_string_method));
j_parameters.addDictionaryMember(
"filemethod", JSON::makeString(s_file_method));
}
static void do_json(QPDF& pdf, Options& o)
{
JSON j = JSON::makeDictionary();
@ -3869,6 +4050,10 @@ static void do_json(QPDF& pdf, Options& o)
{
do_json_acroform(pdf, o, j);
}
if (all_keys || o.json_keys.count("encrypt"))
{
do_json_encrypt(pdf, o, j);
}
// Check against schema

View File

@ -607,6 +607,7 @@ my @json_files = (
['image-streams', []],
['image-streams-small', []],
['field-types', []],
['field-types', ['--show-encryption-key']],
['image-streams', ['--decode-level=all']],
['image-streams', ['--decode-level=specialized']],
['page-labels-and-outlines', ['--json-key=objects']],
@ -621,6 +622,8 @@ my @json_files = (
['--json-key=objects', '--json-object=trailer', '--json-object=2 0 R']],
['field-types', ['--json-key=acroform']],
['need-appearances', ['--json-key=acroform']],
['V4-aes', ['--json-key=encrypt']],
['V4-aes', ['--json-key=encrypt', '--show-encryption-key']],
);
$n_tests += scalar(@json_files);
foreach my $d (@json_files)
@ -3176,7 +3179,7 @@ my @encrypted_files =
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
);
$n_tests += 5 + (2 * (@encrypted_files)) + (6 * (@encrypted_files - 6)) + 9;
$n_tests += 5 + (2 * (@encrypted_files)) + (7 * (@encrypted_files - 6)) + 9;
$td->runtest("encrypted file",
{$td->COMMAND => "test_driver 2 encrypted-with-images.pdf"},
@ -3216,7 +3219,9 @@ foreach my $d (@encrypted_files)
$modifyother, $modifyall) = @$d;
my $f = sub { $_[0] ? "allowed" : "not allowed" };
my $jf = sub { $_[0] ? "true" : "false" };
my $enc_details = "";
my $enc_json = "{\n \"encrypt\": {\n \"capabilities\": {\n";
if ($match_owner)
{
$enc_details .= "Supplied password is owner password\n";
@ -3235,6 +3240,37 @@ foreach my $d (@encrypted_files)
"modify annotations: " . &$f($modifyannot) . "\n" .
"modify other: " . &$f($modifyother) . "\n" .
"modify anything: " . &$f($modifyall) . "\n";
$enc_json .=
" \"accessibility\": " . &$jf($accessible) . ",\n" .
" \"extract\": " . &$jf($extract) . ",\n" .
" \"moddifyannotations\": " . &$jf($modifyannot) . ",\n" .
" \"modify\": " . &$jf($modifyall) . ",\n" .
" \"modifyassembly\": " . &$jf($modifyassembly) . ",\n" .
" \"modifyforms\": " . &$jf($modifyform) . ",\n" .
" \"modifyother\": " . &$jf($modifyother) . ",\n" .
" \"printhigh\": " . &$jf($printhigh) . ",\n" .
" \"printlow\": " . &$jf($printlow) . "\n" .
" },\n" .
" \"encrypted\": true,\n" .
" \"ownerpasswordmatched\": ---opm---,\n" .
" \"parameters\": {\n" .
" \"P\": ---P---,\n" .
" \"R\": ---R---,\n" .
" \"V\": ---V---,\n" .
" \"bits\": ---bits---,\n" .
" \"filemethod\": \"---method---\",\n" .
" \"key\": null,\n" .
" \"method\": \"---method---\",\n" .
" \"streammethod\": \"---method---\",\n" .
" \"stringmethod\": \"---method---\"\n" .
" },\n" .
" \"userpasswordmatched\": ---upm---\n" .
" },\n" .
" \"parameters\": {\n" .
" \"decodelevel\": \"generalized\"\n" .
" },\n" .
" \"version\": 1\n" .
"}\n";
if ($file =~ m/XI-/)
{
$enc_details .=
@ -3277,6 +3313,16 @@ foreach my $d (@encrypted_files)
my $upass = $3 || "";
my $opass = $4 || "";
my $bits = (($V == 5) ? 256 : ($V == 2) ? 128 : 40);
my $method = $bits == 256 ? "AESv3" : "RC4";
my $opm = ($pass eq $opass ? "true" : "false");
my $upm = ($pass eq $upass ? "true" : "false");
$enc_json =~ s/---R---/$R/;
$enc_json =~ s/---P---/$P/;
$enc_json =~ s/---V---/$V/;
$enc_json =~ s/---bits---/$bits/;
$enc_json =~ s/---method---/$method/g;
$enc_json =~ s/---opm---/$opm/;
$enc_json =~ s/---upm---/$upm/;
my $eflags = "-encrypt \"$upass\" \"$opass\" $bits $xeflags --";
if (($pass ne $upass) && ($V >= 5))
@ -3307,6 +3353,13 @@ foreach my $d (@encrypted_files)
"User password = $upass\n$enc_details",
$td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
$td->runtest("json encrypt key ($enc_n)",
{$td->COMMAND =>
"qpdf --json --json-key=encrypt" .
" --password=\"$pass\"" .
" $file.enc2"},
{$td->STRING => $enc_json, $td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
$td->runtest("decrypt again",
{$td->COMMAND =>
"qpdf --static-id --no-original-object-ids -qdf" .

View File

@ -4,6 +4,33 @@
"hasacroform": false,
"needappearances": false
},
"encrypt": {
"capabilities": {
"accessibility": true,
"extract": true,
"moddifyannotations": true,
"modify": true,
"modifyassembly": true,
"modifyforms": true,
"modifyother": true,
"printhigh": true,
"printlow": true
},
"encrypted": false,
"ownerpasswordmatched": false,
"parameters": {
"P": 0,
"R": 0,
"V": 0,
"bits": 0,
"filemethod": "none",
"key": null,
"method": "none",
"streammethod": "none",
"stringmethod": "none"
},
"userpasswordmatched": false
},
"objects": {
"1 0 R": {
"/Pages": "2 0 R",

View File

@ -0,0 +1,33 @@
{
"encrypt": {
"capabilities": {
"accessibility": true,
"extract": true,
"moddifyannotations": true,
"modify": true,
"modifyassembly": true,
"modifyforms": true,
"modifyother": true,
"printhigh": true,
"printlow": true
},
"encrypted": true,
"ownerpasswordmatched": true,
"parameters": {
"P": -4,
"R": 4,
"V": 4,
"bits": 128,
"filemethod": "AESv2",
"key": "474258b004f2ce0017adeb6e79574357",
"method": "AESv2",
"streammethod": "AESv2",
"stringmethod": "AESv2"
},
"userpasswordmatched": true
},
"parameters": {
"decodelevel": "generalized"
},
"version": 1
}

View File

@ -0,0 +1,33 @@
{
"encrypt": {
"capabilities": {
"accessibility": true,
"extract": true,
"moddifyannotations": true,
"modify": true,
"modifyassembly": true,
"modifyforms": true,
"modifyother": true,
"printhigh": true,
"printlow": true
},
"encrypted": true,
"ownerpasswordmatched": true,
"parameters": {
"P": -4,
"R": 4,
"V": 4,
"bits": 128,
"filemethod": "AESv2",
"key": null,
"method": "AESv2",
"streammethod": "AESv2",
"stringmethod": "AESv2"
},
"userpasswordmatched": true
},
"parameters": {
"decodelevel": "generalized"
},
"version": 1
}

File diff suppressed because it is too large Load Diff

View File

@ -385,6 +385,33 @@
"hasacroform": true,
"needappearances": true
},
"encrypt": {
"capabilities": {
"accessibility": true,
"extract": true,
"moddifyannotations": true,
"modify": true,
"modifyassembly": true,
"modifyforms": true,
"modifyother": true,
"printhigh": true,
"printlow": true
},
"encrypted": false,
"ownerpasswordmatched": false,
"parameters": {
"P": 0,
"R": 0,
"V": 0,
"bits": 0,
"filemethod": "none",
"key": null,
"method": "none",
"streammethod": "none",
"stringmethod": "none"
},
"userpasswordmatched": false
},
"objects": {
"1 0 R": {
"/AcroForm": {

View File

@ -4,6 +4,33 @@
"hasacroform": false,
"needappearances": false
},
"encrypt": {
"capabilities": {
"accessibility": true,
"extract": true,
"moddifyannotations": true,
"modify": true,
"modifyassembly": true,
"modifyforms": true,
"modifyother": true,
"printhigh": true,
"printlow": true
},
"encrypted": false,
"ownerpasswordmatched": false,
"parameters": {
"P": 0,
"R": 0,
"V": 0,
"bits": 0,
"filemethod": "none",
"key": null,
"method": "none",
"streammethod": "none",
"stringmethod": "none"
},
"userpasswordmatched": false
},
"objects": {
"1 0 R": {
"/Pages": "2 0 R",

View File

@ -4,6 +4,33 @@
"hasacroform": false,
"needappearances": false
},
"encrypt": {
"capabilities": {
"accessibility": true,
"extract": true,
"moddifyannotations": true,
"modify": true,
"modifyassembly": true,
"modifyforms": true,
"modifyother": true,
"printhigh": true,
"printlow": true
},
"encrypted": false,
"ownerpasswordmatched": false,
"parameters": {
"P": 0,
"R": 0,
"V": 0,
"bits": 0,
"filemethod": "none",
"key": null,
"method": "none",
"streammethod": "none",
"stringmethod": "none"
},
"userpasswordmatched": false
},
"objects": {
"1 0 R": {
"/Pages": "2 0 R",

View File

@ -4,6 +4,33 @@
"hasacroform": false,
"needappearances": false
},
"encrypt": {
"capabilities": {
"accessibility": true,
"extract": true,
"moddifyannotations": true,
"modify": true,
"modifyassembly": true,
"modifyforms": true,
"modifyother": true,
"printhigh": true,
"printlow": true
},
"encrypted": false,
"ownerpasswordmatched": false,
"parameters": {
"P": 0,
"R": 0,
"V": 0,
"bits": 0,
"filemethod": "none",
"key": null,
"method": "none",
"streammethod": "none",
"stringmethod": "none"
},
"userpasswordmatched": false
},
"objects": {
"1 0 R": {
"/Pages": "2 0 R",

View File

@ -4,6 +4,33 @@
"hasacroform": false,
"needappearances": false
},
"encrypt": {
"capabilities": {
"accessibility": true,
"extract": true,
"moddifyannotations": true,
"modify": true,
"modifyassembly": true,
"modifyforms": true,
"modifyother": true,
"printhigh": true,
"printlow": true
},
"encrypted": false,
"ownerpasswordmatched": false,
"parameters": {
"P": 0,
"R": 0,
"V": 0,
"bits": 0,
"filemethod": "none",
"key": null,
"method": "none",
"streammethod": "none",
"stringmethod": "none"
},
"userpasswordmatched": false
},
"objects": {
"1 0 R": {
"/Pages": "2 0 R",

View File

@ -4,6 +4,33 @@
"hasacroform": false,
"needappearances": false
},
"encrypt": {
"capabilities": {
"accessibility": true,
"extract": true,
"moddifyannotations": true,
"modify": true,
"modifyassembly": true,
"modifyforms": true,
"modifyother": true,
"printhigh": true,
"printlow": true
},
"encrypted": false,
"ownerpasswordmatched": false,
"parameters": {
"P": 0,
"R": 0,
"V": 0,
"bits": 0,
"filemethod": "none",
"key": null,
"method": "none",
"streammethod": "none",
"stringmethod": "none"
},
"userpasswordmatched": false
},
"objects": {
"1 0 R": {
"/Names": {

View File

@ -4,6 +4,33 @@
"hasacroform": false,
"needappearances": false
},
"encrypt": {
"capabilities": {
"accessibility": true,
"extract": true,
"moddifyannotations": true,
"modify": true,
"modifyassembly": true,
"modifyforms": true,
"modifyother": true,
"printhigh": true,
"printlow": true
},
"encrypted": false,
"ownerpasswordmatched": false,
"parameters": {
"P": 0,
"R": 0,
"V": 0,
"bits": 0,
"filemethod": "none",
"key": null,
"method": "none",
"streammethod": "none",
"stringmethod": "none"
},
"userpasswordmatched": false
},
"objects": {
"1 0 R": {
"/Dests": "107 0 R",

View File

@ -4,6 +4,33 @@
"hasacroform": false,
"needappearances": false
},
"encrypt": {
"capabilities": {
"accessibility": true,
"extract": true,
"moddifyannotations": true,
"modify": true,
"modifyassembly": true,
"modifyforms": true,
"modifyother": true,
"printhigh": true,
"printlow": true
},
"encrypted": false,
"ownerpasswordmatched": false,
"parameters": {
"P": 0,
"R": 0,
"V": 0,
"bits": 0,
"filemethod": "none",
"key": null,
"method": "none",
"streammethod": "none",
"stringmethod": "none"
},
"userpasswordmatched": false
},
"objects": {
"1 0 R": {
"/Outlines": "95 0 R",

View File

@ -4,6 +4,33 @@
"hasacroform": false,
"needappearances": false
},
"encrypt": {
"capabilities": {
"accessibility": true,
"extract": true,
"moddifyannotations": true,
"modify": true,
"modifyassembly": true,
"modifyforms": true,
"modifyother": true,
"printhigh": true,
"printlow": true
},
"encrypted": false,
"ownerpasswordmatched": false,
"parameters": {
"P": 0,
"R": 0,
"V": 0,
"bits": 0,
"filemethod": "none",
"key": null,
"method": "none",
"streammethod": "none",
"stringmethod": "none"
},
"userpasswordmatched": false
},
"objects": {
"1 0 R": {
"/PageLabels": "2 0 R",

View File

@ -4,6 +4,33 @@
"hasacroform": false,
"needappearances": false
},
"encrypt": {
"capabilities": {
"accessibility": true,
"extract": true,
"moddifyannotations": true,
"modify": true,
"modifyassembly": true,
"modifyforms": true,
"modifyother": true,
"printhigh": true,
"printlow": true
},
"encrypted": false,
"ownerpasswordmatched": false,
"parameters": {
"P": 0,
"R": 0,
"V": 0,
"bits": 0,
"filemethod": "none",
"key": null,
"method": "none",
"streammethod": "none",
"stringmethod": "none"
},
"userpasswordmatched": false
},
"objects": {
"1 0 R": {
"/Pages": "3 0 R",