aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMasamichi Hosoda <trueroad@trueroad.jp>2019-10-18 12:41:53 +0200
committerJay Berkenbilt <ejb@ql.org>2019-10-22 22:20:21 +0200
commit5a842792b69550cf441d4598feb1daff2fa8c83f (patch)
tree5c0bb58f6028cf48aab01f421a79b68fd92688ed
parentcdc46d78f441526d566c9da195e79b900617bb13 (diff)
downloadqpdf-5a842792b69550cf441d4598feb1daff2fa8c83f.tar.zst
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.
-rw-r--r--include/qpdf/QPDFWriter.hh1
-rw-r--r--libqpdf/QPDFObjectHandle.cc44
-rw-r--r--libqpdf/QPDFWriter.cc3
-rw-r--r--qpdf/qtest/qpdf.test83
4 files changed, 128 insertions, 3 deletions
diff --git a/include/qpdf/QPDFWriter.hh b/include/qpdf/QPDFWriter.hh
index 06de6a56..f5cd2e06 100644
--- a/include/qpdf/QPDFWriter.hh
+++ b/include/qpdf/QPDFWriter.hh
@@ -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 };
diff --git a/libqpdf/QPDFObjectHandle.cc b/libqpdf/QPDFObjectHandle.cc
index d49976b6..3dacfb8e 100644
--- a/libqpdf/QPDFObjectHandle.cc
+++ b/libqpdf/QPDFObjectHandle.cc
@@ -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();
}
}
diff --git a/libqpdf/QPDFWriter.cc b/libqpdf/QPDFWriter.cc
index e7eae5c6..a31f5da9 100644
--- a/libqpdf/QPDFWriter.cc
+++ b/libqpdf/QPDFWriter.cc
@@ -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();
diff --git a/qpdf/qtest/qpdf.test b/qpdf/qtest/qpdf.test
index 2604ab43..1df8291e 100644
--- a/qpdf/qtest/qpdf.test
+++ b/qpdf/qtest/qpdf.test
@@ -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 ---");