Parse Contents in signature dictionary without encryption

Various PDF digital signing tools do not encrypt /Contents value in
signature dictionary. Adobe Acrobat Reader DC can handle a PDF with
the /Contents value not encrypted.

Write Contents in signature dictionary without encryption

Tests ensure that string /Contents are not handled specially when not
found in sig dicts.
This commit is contained in:
Masamichi Hosoda 2019-10-18 19:41:53 +09:00 committed by Jay Berkenbilt
parent cdc46d78f4
commit 5a842792b6
4 changed files with 128 additions and 3 deletions

View File

@ -481,6 +481,7 @@ class QPDFWriter
static int const f_filtered = 1 << 1;
static int const f_in_ostream = 1 << 2;
static int const f_hex_string = 1 << 3;
static int const f_no_encryption = 1 << 4;
enum trailer_e { t_normal, t_lin_first, t_lin_second };

View File

@ -1779,12 +1779,19 @@ QPDFObjectHandle::parseInternal(PointerHolder<InputSource> input,
bool done = false;
int bad_count = 0;
int good_count = 0;
bool b_contents = false;
std::vector<std::string> contents_string_stack;
contents_string_stack.push_back("");
std::vector<qpdf_offset_t> contents_offset_stack;
contents_offset_stack.push_back(-1);
while (! done)
{
bool bad = false;
SparseOHArray& olist = olist_stack.back();
parser_state_e state = state_stack.back();
offset = offset_stack.back();
std::string& contents_string = contents_string_stack.back();
qpdf_offset_t& contents_offset = contents_offset_stack.back();
object = QPDFObjectHandle();
set_offset = false;
@ -1894,6 +1901,9 @@ QPDFObjectHandle::parseInternal(PointerHolder<InputSource> input,
state_stack.push_back(
(token.getType() == QPDFTokenizer::tt_array_open) ?
st_array : st_dictionary);
b_contents = false;
contents_string_stack.push_back("");
contents_offset_stack.push_back(-1);
}
break;
@ -1914,7 +1924,19 @@ QPDFObjectHandle::parseInternal(PointerHolder<InputSource> input,
break;
case QPDFTokenizer::tt_name:
object = newName(token.getValue());
{
std::string name = token.getValue();
object = newName(name);
if (name == "/Contents")
{
b_contents = true;
}
else
{
b_contents = false;
}
}
break;
case QPDFTokenizer::tt_word:
@ -1975,6 +1997,12 @@ QPDFObjectHandle::parseInternal(PointerHolder<InputSource> input,
std::string val = token.getValue();
if (decrypter)
{
if (b_contents)
{
contents_string = val;
contents_offset = input->getLastOffset();
b_contents = false;
}
decrypter->decryptString(val);
}
object = QPDFObjectHandle::newString(val);
@ -2168,6 +2196,18 @@ QPDFObjectHandle::parseInternal(PointerHolder<InputSource> input,
}
dict[key] = val;
}
if (!contents_string.empty() &&
dict.count("/Type") &&
dict["/Type"].isName() &&
dict["/Type"].getName() == "/Sig" &&
dict.count("/ByteRange") &&
dict.count("/Contents") &&
dict["/Contents"].isString())
{
dict["/Contents"]
= QPDFObjectHandle::newString(contents_string);
dict["/Contents"].setParsedOffset(contents_offset);
}
object = newDictionary(dict);
setObjectDescriptionFromInput(
object, context, object_description, input, offset);
@ -2190,6 +2230,8 @@ QPDFObjectHandle::parseInternal(PointerHolder<InputSource> input,
{
olist_stack.back().append(object);
}
contents_string_stack.pop_back();
contents_offset_stack.pop_back();
}
}

View File

@ -1695,7 +1695,7 @@ QPDFWriter::unparseObject(QPDFObjectHandle object, int level,
{
QTC::TC("qpdf", "QPDFWriter no encryption sig contents");
unparseChild(object.getKey(key), level + 1,
child_flags | f_hex_string);
child_flags | f_hex_string | f_no_encryption);
}
else
{
@ -1866,6 +1866,7 @@ QPDFWriter::unparseObject(QPDFObjectHandle object, int level,
std::string val;
if (this->m->encrypted &&
(! (flags & f_in_ostream)) &&
(! (flags & f_no_encryption)) &&
(! this->m->cur_data_key.empty()))
{
val = object.getStringValue();

View File

@ -3999,7 +3999,6 @@ show_ntests();
# ----------
$td->notify("--- Signature Dictionary ---");
$n_tests += 6;
foreach my $i (qw(preserve disable generate))
{
$td->runtest("sig dict contents hex (object-streams=$i)",
@ -4017,6 +4016,88 @@ foreach my $i (qw(preserve disable generate))
$td->EXIT_STATUS => 0});
}
$n_tests += 4;
foreach my $i (qw(preserve disable))
{
$td->runtest("non sig dict contents text string (object-streams=$i)",
{$td->COMMAND =>
"qpdf --object-streams=$i comment-annotation.pdf a.pdf"},
{$td->STRING => "",
$td->EXIT_STATUS => 0});
$td->runtest("find desired contents as non hex (object-streams=$i)",
{$td->COMMAND =>
"grep \"/Contents (Salad)\" a.pdf"},
{$td->REGEXP => ".*",
$td->EXIT_STATUS => 0});
}
$n_tests += 2;
$td->runtest("non sig dict contents text string (object-streams=generate)",
{$td->COMMAND =>
"qpdf --object-streams=generate comment-annotation.pdf a.pdf"},
{$td->STRING => "",
$td->EXIT_STATUS => 0});
$td->runtest("plain text not found due to compression (object-streams=generate)",
{$td->COMMAND =>
"grep \"/Contents (Salad)\" a.pdf"},
{$td->REGEXP => ".*",
$td->EXIT_STATUS => 1});
$n_tests += 12;
foreach my $i (qw(40 128 256))
{
$td->runtest("encrypt $i",
{$td->COMMAND =>
"qpdf --encrypt '' o $i -- digitally-signed.pdf a.pdf"},
{$td->STRING => "",
$td->EXIT_STATUS => 0});
$td->runtest("find desired contents (encrypt $i)",
{$td->COMMAND =>
"grep -f digitally-signed-sig-dict-contents.out a.pdf"},
{$td->REGEXP => ".*",
$td->EXIT_STATUS => 0});
$td->runtest("decrypt",
{$td->COMMAND =>
"qpdf --decrypt a.pdf b.pdf"},
{$td->REGEXP => ".*",
$td->EXIT_STATUS => 0});
$td->runtest("find desired contents (decrypt $i)",
{$td->COMMAND =>
"grep -f digitally-signed-sig-dict-contents.out b.pdf"},
{$td->REGEXP => ".*",
$td->EXIT_STATUS => 0});
}
$n_tests += 15;
foreach my $i (qw(40 128 256))
{
$td->runtest("non sig dict encrypt $i",
{$td->COMMAND =>
"qpdf --encrypt '' o $i -- comment-annotation.pdf a.pdf"},
{$td->STRING => "",
$td->EXIT_STATUS => 0});
$td->runtest("plain text not found due to encryption (non sig dict encrypt $i)",
{$td->COMMAND =>
"grep \"/Contents (Salad)\" a.pdf"},
{$td->REGEXP => ".*",
$td->EXIT_STATUS => 1});
$td->runtest("find encrypted contents (non sig dict encrypt $i)",
{$td->COMMAND =>
"grep \"/Contents <.*>\" a.pdf"},
{$td->REGEXP => ".*",
$td->EXIT_STATUS => 0});
$td->runtest("non sig dict decrypt",
{$td->COMMAND =>
"qpdf --decrypt a.pdf b.pdf"},
{$td->REGEXP => ".*",
$td->EXIT_STATUS => 0});
$td->runtest("find desired contents (non sig dict decrypt $i)",
{$td->COMMAND =>
"grep \"/Contents (Salad)\" b.pdf"},
{$td->REGEXP => ".*",
$td->EXIT_STATUS => 0});
}
show_ntests();
# ----------
$td->notify("--- Get XRef Table ---");