Commit 734ac1e1d2b3ce10a2da1a7e736a30bdf0bc5cf8

Authored by Jay Berkenbilt
1 parent 70ae58c0

deal with stream-specific crypt filters

git-svn-id: svn+q:///qpdf/trunk@827 71b93d88-0707-0410-a8cf-f5a4172ac649
1 1 2.1
2 2 ===
3 3  
4   - * Really need to handle /Crypt filter for Metadata. Search for crypt
5   - below.
6   -
7 4 * Update documentation to reflect new command line flags and any
8 5 other relevant changes. Should read through ChangeLog and the
9 6 manual before releasing 2.1.
... ... @@ -83,10 +80,13 @@ General
83 80 filters. There is an example in the spec of using a crypt filter
84 81 on a metadata stream.
85 82  
86   - When we write encrypted files, we must remember to omit any
87   - encryption filter settings from original streams.
88   -
89   - We need a way to test this.
  83 + For now, we notice /Crypt filters and decode parameters consistent
  84 + with the example in the PDF specification, and the right thing
  85 + happens for metadata filters that happen to be uncompressed or
  86 + otherwise compressed in a way we can filter. This should handle
  87 + all normal cases, but it's more or less just a guess since I don't
  88 + have any test files that actually use stream-specific crypt filters
  89 + in them.
90 90  
91 91 * The second xref stream for linearized files has to be padded only
92 92 because we need file_size as computed in pass 1 to be accurate. If
... ...
include/qpdf/QPDFObjectHandle.hh
... ... @@ -77,14 +77,14 @@ class DLL_EXPORT QPDFObjectHandle
77 77 bool isNumber();
78 78 double getNumericValue();
79 79  
80   - // Methods for name objects
  80 + // Methods for name objects; see also name and array objects
81 81 std::string getName();
82 82  
83 83 // Methods for string objects
84 84 std::string getStringValue();
85 85 std::string getUTF8Value();
86 86  
87   - // Methods for array objects
  87 + // Methods for array objects; see also name and array objects
88 88 int getArrayNItems();
89 89 QPDFObjectHandle getArrayItem(int n);
90 90  
... ... @@ -93,6 +93,9 @@ class DLL_EXPORT QPDFObjectHandle
93 93 QPDFObjectHandle getKey(std::string const&);
94 94 std::set<std::string> getKeys();
95 95  
  96 + // Methods for name and array objects
  97 + bool isOrHasName(std::string const&);
  98 +
96 99 // Mutator methods. Use with caution.
97 100  
98 101 // Recursively copy this object, making it direct. Throws an
... ...
libqpdf/QPDFObjectHandle.cc
... ... @@ -258,6 +258,29 @@ QPDFObjectHandle::getKeys()
258 258 return dynamic_cast<QPDF_Dictionary*>(obj.getPointer())->getKeys();
259 259 }
260 260  
  261 +// Array and Name accessors
  262 +bool
  263 +QPDFObjectHandle::isOrHasName(std::string const& value)
  264 +{
  265 + if (isName() && (getName() == value))
  266 + {
  267 + return true;
  268 + }
  269 + else if (isArray())
  270 + {
  271 + int n = getArrayNItems();
  272 + for (int i = 0; i < n; ++i)
  273 + {
  274 + QPDFObjectHandle item = getArrayItem(0);
  275 + if (item.isName() && (item.getName() == value))
  276 + {
  277 + return true;
  278 + }
  279 + }
  280 + }
  281 + return false;
  282 +}
  283 +
261 284 // Dictionary mutators
262 285  
263 286 void
... ...
libqpdf/QPDF_Stream.cc
... ... @@ -136,6 +136,13 @@ QPDF_Stream::filterable(std::vector&lt;std::string&gt;&amp; filters,
136 136 filterable = false;
137 137 }
138 138 }
  139 + else if (((key == "/Type") || (key == "/Name")) &&
  140 + decode_obj.getKey("/Type").isName() &&
  141 + (decode_obj.getKey("/Type").getName() ==
  142 + "/CryptFilterDecodeParms"))
  143 + {
  144 + // we handle this in decryptStream
  145 + }
139 146 else
140 147 {
141 148 filterable = false;
... ... @@ -212,7 +219,8 @@ QPDF_Stream::filterable(std::vector&lt;std::string&gt;&amp; filters,
212 219 iter != filters.end(); ++iter)
213 220 {
214 221 std::string const& filter = *iter;
215   - if (! ((filter == "/FlateDecode") ||
  222 + if (! ((filter == "/Crypt") ||
  223 + (filter == "/FlateDecode") ||
216 224 (filter == "/LZWDecode") ||
217 225 (filter == "/ASCII85Decode") ||
218 226 (filter == "/ASCIIHexDecode")))
... ... @@ -266,7 +274,11 @@ QPDF_Stream::pipeStreamData(Pipeline* pipeline, bool filter,
266 274 iter != filters.rend(); ++iter)
267 275 {
268 276 std::string const& filter = *iter;
269   - if (filter == "/FlateDecode")
  277 + if (filter == "/Crypt")
  278 + {
  279 + // Ignore -- handled by pipeStreamData
  280 + }
  281 + else if (filter == "/FlateDecode")
270 282 {
271 283 if (predictor == 12)
272 284 {
... ...
libqpdf/QPDF_encryption.cc
... ... @@ -600,18 +600,19 @@ QPDF::decryptStream(Pipeline*&amp; pipeline, int objid, int generation,
600 600 encryption_method_e method = e_unknown;
601 601 std::string method_source = "/StmF from /Encrypt dictionary";
602 602  
603   - // NOTE: the section in the PDF specification on crypt filters
604   - // seems to suggest that there might be a /Crypt key in
605   - // /DecodeParms whose value is a crypt filter (.e.g., << /Name
606   - // /StdCF >>), but implementation notes suggest this can only
607   - // happen for metadata streams, and emperical observation
608   - // suggests that they are otherwise ignored. Not having been
609   - // able to find a sample file that uses crypt filters in any
610   - // way other than /StrF and /StmF, I'm not really sure what to
611   - // do about this. If we were to override the encryption on a
612   - // per-stream basis using crypt filters, set method_source to
613   - // something useful in the error message for unknown
614   - // encryption methods (search for method_source).
  603 + if (stream_dict.getKey("/Filter").isOrHasName("/Crypt") &&
  604 + stream_dict.getKey("/DecodeParms").isDictionary())
  605 + {
  606 + QPDFObjectHandle decode_parms = stream_dict.getKey("/DecodeParms");
  607 + if (decode_parms.getKey("/Type").isName() &&
  608 + (decode_parms.getKey("/Type").getName() ==
  609 + "/CryptFilterDecodeParms"))
  610 + {
  611 + QTC::TC("qpdf", "QPDF_encryption stream crypt filter");
  612 + method = interpretCF(decode_parms.getKey("/Name"));
  613 + method_source = "stream's Crypt decode parameters";
  614 + }
  615 + }
615 616  
616 617 if (method == e_unknown)
617 618 {
... ...
qpdf/qpdf.testcov
... ... @@ -170,3 +170,4 @@ QPDFWriter forcing object stream disable 0
170 170 QPDFWriter forced version disabled encryption 0
171 171 qpdf-c called qpdf_set_r4_encryption_parameters 0
172 172 qpdf-c called qpdf_set_static_aes_IV 0
  173 +QPDF_encryption stream crypt filter 0
... ...
qpdf/qtest/qpdf.test
... ... @@ -1079,7 +1079,7 @@ $td-&gt;runtest(&quot;make sure there is no xref stream&quot;,
1079 1079 $td->NORMALIZE_NEWLINES);
1080 1080  
1081 1081 # Look at some actual V4 files
1082   -$n_tests += 8;
  1082 +$n_tests += 10;
1083 1083 foreach my $d (['--force-V4', 'V4'],
1084 1084 ['--cleartext-metadata', 'V4-clearmeta'],
1085 1085 ['--use-aes=y', 'V4-aes'],
... ... @@ -1094,6 +1094,14 @@ foreach my $d ([&#39;--force-V4&#39;, &#39;V4&#39;],
1094 1094 {$td->FILE => "a.pdf"},
1095 1095 {$td->FILE => "$out.pdf"});
1096 1096 }
  1097 +# Crypt Filter
  1098 +$td->runtest("decrypt with crypt filter",
  1099 + {$td->COMMAND => "qpdf --decrypt --static-id" .
  1100 + " metadata-crypt-filter.pdf a.pdf"},
  1101 + {$td->STRING => "", $td->EXIT_STATUS => 0});
  1102 +$td->runtest("check output",
  1103 + {$td->FILE => 'a.pdf'},
  1104 + {$td->FILE => 'decrypted-crypt-filter.pdf'});
1097 1105  
1098 1106 show_ntests();
1099 1107 # ----------
... ...
qpdf/qtest/qpdf/decrypted-crypt-filter.pdf 0 → 100644
No preview for this file type
qpdf/qtest/qpdf/metadata-crypt-filter.pdf 0 → 100644
No preview for this file type