mirror of
https://github.com/qpdf/qpdf.git
synced 2024-12-23 03:18:59 +00:00
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:
parent
cdc46d78f4
commit
5a842792b6
@ -481,6 +481,7 @@ class QPDFWriter
|
|||||||
static int const f_filtered = 1 << 1;
|
static int const f_filtered = 1 << 1;
|
||||||
static int const f_in_ostream = 1 << 2;
|
static int const f_in_ostream = 1 << 2;
|
||||||
static int const f_hex_string = 1 << 3;
|
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 };
|
enum trailer_e { t_normal, t_lin_first, t_lin_second };
|
||||||
|
|
||||||
|
@ -1779,12 +1779,19 @@ QPDFObjectHandle::parseInternal(PointerHolder<InputSource> input,
|
|||||||
bool done = false;
|
bool done = false;
|
||||||
int bad_count = 0;
|
int bad_count = 0;
|
||||||
int good_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)
|
while (! done)
|
||||||
{
|
{
|
||||||
bool bad = false;
|
bool bad = false;
|
||||||
SparseOHArray& olist = olist_stack.back();
|
SparseOHArray& olist = olist_stack.back();
|
||||||
parser_state_e state = state_stack.back();
|
parser_state_e state = state_stack.back();
|
||||||
offset = offset_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();
|
object = QPDFObjectHandle();
|
||||||
set_offset = false;
|
set_offset = false;
|
||||||
@ -1894,6 +1901,9 @@ QPDFObjectHandle::parseInternal(PointerHolder<InputSource> input,
|
|||||||
state_stack.push_back(
|
state_stack.push_back(
|
||||||
(token.getType() == QPDFTokenizer::tt_array_open) ?
|
(token.getType() == QPDFTokenizer::tt_array_open) ?
|
||||||
st_array : st_dictionary);
|
st_array : st_dictionary);
|
||||||
|
b_contents = false;
|
||||||
|
contents_string_stack.push_back("");
|
||||||
|
contents_offset_stack.push_back(-1);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -1914,7 +1924,19 @@ QPDFObjectHandle::parseInternal(PointerHolder<InputSource> input,
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case QPDFTokenizer::tt_name:
|
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;
|
break;
|
||||||
|
|
||||||
case QPDFTokenizer::tt_word:
|
case QPDFTokenizer::tt_word:
|
||||||
@ -1975,6 +1997,12 @@ QPDFObjectHandle::parseInternal(PointerHolder<InputSource> input,
|
|||||||
std::string val = token.getValue();
|
std::string val = token.getValue();
|
||||||
if (decrypter)
|
if (decrypter)
|
||||||
{
|
{
|
||||||
|
if (b_contents)
|
||||||
|
{
|
||||||
|
contents_string = val;
|
||||||
|
contents_offset = input->getLastOffset();
|
||||||
|
b_contents = false;
|
||||||
|
}
|
||||||
decrypter->decryptString(val);
|
decrypter->decryptString(val);
|
||||||
}
|
}
|
||||||
object = QPDFObjectHandle::newString(val);
|
object = QPDFObjectHandle::newString(val);
|
||||||
@ -2168,6 +2196,18 @@ QPDFObjectHandle::parseInternal(PointerHolder<InputSource> input,
|
|||||||
}
|
}
|
||||||
dict[key] = val;
|
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);
|
object = newDictionary(dict);
|
||||||
setObjectDescriptionFromInput(
|
setObjectDescriptionFromInput(
|
||||||
object, context, object_description, input, offset);
|
object, context, object_description, input, offset);
|
||||||
@ -2190,6 +2230,8 @@ QPDFObjectHandle::parseInternal(PointerHolder<InputSource> input,
|
|||||||
{
|
{
|
||||||
olist_stack.back().append(object);
|
olist_stack.back().append(object);
|
||||||
}
|
}
|
||||||
|
contents_string_stack.pop_back();
|
||||||
|
contents_offset_stack.pop_back();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1695,7 +1695,7 @@ QPDFWriter::unparseObject(QPDFObjectHandle object, int level,
|
|||||||
{
|
{
|
||||||
QTC::TC("qpdf", "QPDFWriter no encryption sig contents");
|
QTC::TC("qpdf", "QPDFWriter no encryption sig contents");
|
||||||
unparseChild(object.getKey(key), level + 1,
|
unparseChild(object.getKey(key), level + 1,
|
||||||
child_flags | f_hex_string);
|
child_flags | f_hex_string | f_no_encryption);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -1866,6 +1866,7 @@ QPDFWriter::unparseObject(QPDFObjectHandle object, int level,
|
|||||||
std::string val;
|
std::string val;
|
||||||
if (this->m->encrypted &&
|
if (this->m->encrypted &&
|
||||||
(! (flags & f_in_ostream)) &&
|
(! (flags & f_in_ostream)) &&
|
||||||
|
(! (flags & f_no_encryption)) &&
|
||||||
(! this->m->cur_data_key.empty()))
|
(! this->m->cur_data_key.empty()))
|
||||||
{
|
{
|
||||||
val = object.getStringValue();
|
val = object.getStringValue();
|
||||||
|
@ -3999,7 +3999,6 @@ show_ntests();
|
|||||||
# ----------
|
# ----------
|
||||||
$td->notify("--- Signature Dictionary ---");
|
$td->notify("--- Signature Dictionary ---");
|
||||||
$n_tests += 6;
|
$n_tests += 6;
|
||||||
|
|
||||||
foreach my $i (qw(preserve disable generate))
|
foreach my $i (qw(preserve disable generate))
|
||||||
{
|
{
|
||||||
$td->runtest("sig dict contents hex (object-streams=$i)",
|
$td->runtest("sig dict contents hex (object-streams=$i)",
|
||||||
@ -4017,6 +4016,88 @@ foreach my $i (qw(preserve disable generate))
|
|||||||
$td->EXIT_STATUS => 0});
|
$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();
|
show_ntests();
|
||||||
# ----------
|
# ----------
|
||||||
$td->notify("--- Get XRef Table ---");
|
$td->notify("--- Get XRef Table ---");
|
||||||
|
Loading…
Reference in New Issue
Block a user