Commit e076c9bf084ba5f90f52d829255e4ef04e3a8031

Authored by Jay Berkenbilt
1 parent ac2b3b96

Remove erroneous handling of /EFF for stream decryption

I thought /EFF was supposed to be used as a default for decrypting
embedded file streams, but actually it's supposed to be advice to a
conforming writer about handling new ones. This makes sense since the
findAttachmentStreams code, which is not actually needed, was never
right.
... ... @@ -425,25 +425,13 @@ I find it useful to make reference to them in this list.
425 425 http://multivalent.sourceforge.net/Tools/pdf/Extract.html
426 426 http://multivalent.sourceforge.net/Tools/pdf/Embed.html
427 427  
428   - * The description of Crypt filters is unclear with respect to how to
429   - use them to override /StmF for specific streams. I'm not sure
430   - whether qpdf will do the right thing for any specific individual
431   - streams that might have crypt filters, but I believe it does based
432   - on my testing of a limited subset. The specification seems to imply
433   - that only embedded file streams and metadata streams can have crypt
434   - filters, and there are already special cases in the code to handle
435   - those. Most likely, it won't be a problem, but someday someone may
436   - find a file that qpdf doesn't work on because of crypt filters.
437   - There is an example in the spec of using a crypt filter on a
438   - metadata stream.
439   -
440   - For now, we notice /Crypt filters and decode parameters consistent
441   - with the example in the PDF specification, and the right thing
442   - happens for metadata filters that happen to be uncompressed or
443   - otherwise compressed in a way we can filter. This should handle
444   - all normal cases, but it's more or less just a guess since I don't
445   - have any test files that actually use stream-specific crypt filters
446   - in them.
  428 + * Qpdf does not honor /EFF when adding new file attachments. When it
  429 + encrypts, it never generates streams with explicit crypt filters.
  430 + Prior to 10.2, there was an incorrect attempt to treat /EFF as a
  431 + default value for decrypting file attachment streams, but it is not
  432 + supposed to mean that. Instead, it is intended for comforming
  433 + writers to obey this when adding new attachments. Qpdf is not a
  434 + conforming writer in that respect.
447 435  
448 436 * The second xref stream for linearized files has to be padded only
449 437 because we need file_size as computed in pass 1 to be accurate. If
... ...
include/qpdf/QPDF.hh
... ... @@ -814,7 +814,6 @@ class QPDF
814 814 int foreign_generation,
815 815 qpdf_offset_t offset,
816 816 size_t length,
817   - bool is_attachment_stream,
818 817 QPDFObjectHandle local_dict);
819 818  
820 819 private:
... ... @@ -824,7 +823,6 @@ class QPDF
824 823 int foreign_generation;
825 824 qpdf_offset_t offset;
826 825 size_t length;
827   - bool is_attachment_stream;
828 826 QPDFObjectHandle local_dict;
829 827 };
830 828  
... ... @@ -919,7 +917,6 @@ class QPDF
919 917 int& act_objid, int& act_generation);
920 918 PointerHolder<QPDFObject> resolve(int objid, int generation);
921 919 void resolveObjectsInStream(int obj_stream_number);
922   - void findAttachmentStreams();
923 920 void stopOnError(std::string const& message);
924 921  
925 922 // Calls finish() on the pipeline when done but does not delete it
... ... @@ -938,7 +935,6 @@ class QPDF
938 935 int objid, int generation,
939 936 qpdf_offset_t offset, size_t length,
940 937 QPDFObjectHandle dict,
941   - bool is_attachment_stream,
942 938 Pipeline* pipeline,
943 939 bool suppress_warnings,
944 940 bool will_retry);
... ... @@ -1001,7 +997,7 @@ class QPDF
1001 997 PointerHolder<InputSource> file,
1002 998 QPDF& qpdf_for_warning, Pipeline*& pipeline,
1003 999 int objid, int generation,
1004   - QPDFObjectHandle& stream_dict, bool is_attachment_stream,
  1000 + QPDFObjectHandle& stream_dict,
1005 1001 std::vector<PointerHolder<Pipeline> >& heap);
1006 1002  
1007 1003 // Methods to support object copying
... ... @@ -1422,7 +1418,6 @@ class QPDF
1422 1418 PointerHolder<QPDFObjectHandle::StreamDataProvider> copied_streams;
1423 1419 // copied_stream_data_provider is owned by copied_streams
1424 1420 CopiedStreamDataProvider* copied_stream_data_provider;
1425   - std::set<QPDFObjGen> attachment_streams;
1426 1421 bool reconstructed_xref;
1427 1422 bool fixed_dangling_refs;
1428 1423 bool immediate_copy_from;
... ...
libqpdf/QPDF.cc
... ... @@ -18,7 +18,6 @@
18 18 #include <qpdf/FileInputSource.hh>
19 19 #include <qpdf/BufferInputSource.hh>
20 20 #include <qpdf/OffsetInputSource.hh>
21   -#include <qpdf/QPDFNameTreeObjectHelper.hh>
22 21  
23 22 #include <qpdf/QPDFExc.hh>
24 23 #include <qpdf/QPDF_Null.hh>
... ... @@ -98,7 +97,6 @@ QPDF::ForeignStreamData::ForeignStreamData(
98 97 int foreign_generation,
99 98 qpdf_offset_t offset,
100 99 size_t length,
101   - bool is_attachment_stream,
102 100 QPDFObjectHandle local_dict)
103 101 :
104 102 encp(encp),
... ... @@ -107,7 +105,6 @@ QPDF::ForeignStreamData::ForeignStreamData(
107 105 foreign_generation(foreign_generation),
108 106 offset(offset),
109 107 length(length),
110   - is_attachment_stream(is_attachment_stream),
111 108 local_dict(local_dict)
112 109 {
113 110 }
... ... @@ -508,7 +505,6 @@ QPDF::parse(char const* password)
508 505 }
509 506  
510 507 initializeEncryption();
511   - findAttachmentStreams();
512 508 this->m->parsed = true;
513 509 }
514 510  
... ... @@ -2648,8 +2644,6 @@ QPDF::replaceForeignIndirectObjects(
2648 2644 foreign.getGeneration(),
2649 2645 stream->getOffset(),
2650 2646 stream->getLength(),
2651   - (foreign_stream_qpdf->m->attachment_streams.count(
2652   - foreign.getObjGen()) > 0),
2653 2647 dict);
2654 2648 this->m->copied_stream_data_provider->registerForeignStream(
2655 2649 local_og, foreign_stream_data);
... ... @@ -2882,7 +2876,6 @@ QPDF::pipeStreamData(PointerHolder&lt;EncryptionParameters&gt; encp,
2882 2876 int objid, int generation,
2883 2877 qpdf_offset_t offset, size_t length,
2884 2878 QPDFObjectHandle stream_dict,
2885   - bool is_attachment_stream,
2886 2879 Pipeline* pipeline,
2887 2880 bool suppress_warnings,
2888 2881 bool will_retry)
... ... @@ -2892,7 +2885,7 @@ QPDF::pipeStreamData(PointerHolder&lt;EncryptionParameters&gt; encp,
2892 2885 {
2893 2886 decryptStream(encp, file, qpdf_for_warning,
2894 2887 pipeline, objid, generation,
2895   - stream_dict, is_attachment_stream, to_delete);
  2888 + stream_dict, to_delete);
2896 2889 }
2897 2890  
2898 2891 bool success = false;
... ... @@ -2968,14 +2961,11 @@ QPDF::pipeStreamData(int objid, int generation,
2968 2961 bool suppress_warnings,
2969 2962 bool will_retry)
2970 2963 {
2971   - bool is_attachment_stream = (
2972   - this->m->attachment_streams.count(
2973   - QPDFObjGen(objid, generation)) > 0);
2974 2964 return pipeStreamData(
2975 2965 this->m->encp, this->m->file, *this,
2976 2966 objid, generation, offset, length,
2977   - stream_dict, is_attachment_stream,
2978   - pipeline, suppress_warnings, will_retry);
  2967 + stream_dict, pipeline,
  2968 + suppress_warnings, will_retry);
2979 2969 }
2980 2970  
2981 2971 bool
... ... @@ -2992,36 +2982,8 @@ QPDF::pipeForeignStreamData(
2992 2982 foreign->encp, foreign->file, *this,
2993 2983 foreign->foreign_objid, foreign->foreign_generation,
2994 2984 foreign->offset, foreign->length,
2995   - foreign->local_dict, foreign->is_attachment_stream,
2996   - pipeline, suppress_warnings, will_retry);
2997   -}
2998   -
2999   -void
3000   -QPDF::findAttachmentStreams()
3001   -{
3002   - QPDFObjectHandle root = getRoot();
3003   - QPDFObjectHandle names = root.getKey("/Names");
3004   - if (! names.isDictionary())
3005   - {
3006   - return;
3007   - }
3008   - QPDFObjectHandle embedded_files = names.getKey("/EmbeddedFiles");
3009   - if (! embedded_files.isDictionary())
3010   - {
3011   - return;
3012   - }
3013   - QPDFNameTreeObjectHelper ef_tree(embedded_files, *this);
3014   - for (auto const& i: ef_tree)
3015   - {
3016   - QPDFObjectHandle item = i.second;
3017   - if (item.isDictionary() &&
3018   - item.getKey("/EF").isDictionary() &&
3019   - item.getKey("/EF").getKey("/F").isStream())
3020   - {
3021   - QPDFObjectHandle stream = item.getKey("/EF").getKey("/F");
3022   - this->m->attachment_streams.insert(stream.getObjGen());
3023   - }
3024   - }
  2985 + foreign->local_dict, pipeline,
  2986 + suppress_warnings, will_retry);
3025 2987 }
3026 2988  
3027 2989 void
... ...
libqpdf/QPDF_encryption.cc
... ... @@ -1022,6 +1022,20 @@ QPDF::initializeEncryption()
1022 1022 this->m->encp->cf_string = interpretCF(this->m->encp, StrF);
1023 1023 if (EFF.isName())
1024 1024 {
  1025 + // qpdf does not use this for anything other than
  1026 + // informational purposes. This is intended to instruct
  1027 + // conforming writers on which crypt filter should be used
  1028 + // when new file attachments are added to a PDF file, but
  1029 + // qpdf never generates encrypted files with non-default
  1030 + // crypt filters. Prior to 10.2, I was under the mistaken
  1031 + // impression that this was supposed to be used for
  1032 + // decrypting attachments, but the code was wrong in a way
  1033 + // that turns out not to have mattered because no writers
  1034 + // were generating files the way I was imagining. Still,
  1035 + // providing this information could be useful when looking
  1036 + // at a file generated by something else, such as Acrobat
  1037 + // when specifying that only attachments should be
  1038 + // encrypted.
1025 1039 this->m->encp->cf_file = interpretCF(this->m->encp, EFF);
1026 1040 }
1027 1041 else
... ... @@ -1224,7 +1238,6 @@ QPDF::decryptStream(PointerHolder&lt;EncryptionParameters&gt; encp,
1224 1238 QPDF& qpdf_for_warning, Pipeline*& pipeline,
1225 1239 int objid, int generation,
1226 1240 QPDFObjectHandle& stream_dict,
1227   - bool is_attachment_stream,
1228 1241 std::vector<PointerHolder<Pipeline> >& heap)
1229 1242 {
1230 1243 std::string type;
... ... @@ -1296,15 +1309,7 @@ QPDF::decryptStream(PointerHolder&lt;EncryptionParameters&gt; encp,
1296 1309 }
1297 1310 else
1298 1311 {
1299   - if (is_attachment_stream)
1300   - {
1301   - QTC::TC("qpdf", "QPDF_encryption attachment stream");
1302   - method = encp->cf_file;
1303   - }
1304   - else
1305   - {
1306   - method = encp->cf_stream;
1307   - }
  1312 + method = encp->cf_stream;
1308 1313 }
1309 1314 }
1310 1315 use_aes = false;
... ...
qpdf/qpdf.testcov
... ... @@ -401,7 +401,6 @@ qpdf image optimize no pipeline 0
401 401 qpdf image optimize no shrink 0
402 402 qpdf image optimize too small 0
403 403 QPDFFormFieldObjectHelper WinAnsi 0
404   -QPDF_encryption attachment stream 0
405 404 QPDF pipe foreign encrypted stream 0
406 405 QPDF copy foreign stream with provider 0
407 406 QPDF copy foreign stream with buffer 0
... ...
qpdf/qtest/qpdf.test
... ... @@ -1257,7 +1257,7 @@ $n_tests += 3;
1257 1257 $td->runtest("closed input source",
1258 1258 {$td->COMMAND => "test_driver 73 minimal.pdf"},
1259 1259 {$td->FILE => "test73.out",
1260   - $td->EXIT_STATUS => 0},
  1260 + $td->EXIT_STATUS => 2},
1261 1261 $td->NORMALIZE_NEWLINES);
1262 1262  
1263 1263 $td->runtest("empty object",
... ... @@ -3878,7 +3878,7 @@ $td-&gt;runtest(&quot;check encryption&quot;,
3878 3878 $td->NORMALIZE_NEWLINES);
3879 3879  
3880 3880 # Look at some actual V4 files
3881   -$n_tests += 14;
  3881 +$n_tests += 17;
3882 3882 foreach my $d (['--force-V4', 'V4'],
3883 3883 ['--cleartext-metadata', 'V4-clearmeta'],
3884 3884 ['--use-aes=y', 'V4-aes'],
... ... @@ -3906,6 +3906,19 @@ $td-&gt;runtest(&quot;decrypt with crypt filter&quot;,
3906 3906 $td->runtest("check output",
3907 3907 {$td->FILE => 'a.pdf'},
3908 3908 {$td->FILE => 'decrypted-crypt-filter.pdf'});
  3909 +$td->runtest("nontrivial crypt filter",
  3910 + {$td->COMMAND => "qpdf --qdf --decrypt --static-id" .
  3911 + " nontrivial-crypt-filter.pdf --password=asdfqwer a.pdf"},
  3912 + {$td->STRING => "", $td->EXIT_STATUS => 0});
  3913 +$td->runtest("check output",
  3914 + {$td->FILE => 'a.pdf'},
  3915 + {$td->FILE => 'nontrivial-crypt-filter-decrypted.pdf'});
  3916 +$td->runtest("show nontrivial EFF",
  3917 + {$td->COMMAND => "qpdf --show-encryption" .
  3918 + " nontrivial-crypt-filter.pdf --password=asdfqwer"},
  3919 + {$td->FILE => "nontrivial-crypt-filter.out",
  3920 + $td->EXIT_STATUS => 0},
  3921 + $td->NORMALIZE_NEWLINES);
3909 3922  
3910 3923 # Copy encryption parameters
3911 3924 $n_tests += 10;
... ...
qpdf/qtest/qpdf/indirect-r-arg.out
  1 +checking indirect-r-arg.pdf
1 2 WARNING: indirect-r-arg.pdf (object 1 0, offset 76): unknown token while reading object; treating as string
2 3 WARNING: indirect-r-arg.pdf (object 1 0, offset 62): expected dictionary key but found non-name object; inserting key /QPDFFake1
3 4 WARNING: indirect-r-arg.pdf (object 1 0, offset 62): expected dictionary key but found non-name object; inserting key /QPDFFake2
4   -checking indirect-r-arg.pdf
5 5 PDF Version: 1.3
6 6 File is not encrypted
7 7 File is not linearized
... ...
qpdf/qtest/qpdf/nontrivial-crypt-filter-decrypted.pdf 0 โ†’ 100644
No preview for this file type
qpdf/qtest/qpdf/nontrivial-crypt-filter.out 0 โ†’ 100644
  1 +R = 6
  2 +P = -1028
  3 +User password = asdfqwer
  4 +Supplied password is owner password
  5 +Supplied password is user password
  6 +extract for accessibility: allowed
  7 +extract for any purpose: allowed
  8 +print low resolution: allowed
  9 +print high resolution: allowed
  10 +modify document assembly: not allowed
  11 +modify forms: allowed
  12 +modify annotations: allowed
  13 +modify other: allowed
  14 +modify anything: not allowed
  15 +stream encryption method: none
  16 +string encryption method: none
  17 +file encryption method: AESv3
... ...
qpdf/qtest/qpdf/nontrivial-crypt-filter.pdf 0 โ†’ 100644
No preview for this file type
qpdf/qtest/qpdf/obj0-check.out
  1 +checking obj0.pdf
1 2 WARNING: obj0.pdf: file is damaged
2 3 WARNING: obj0.pdf (object 1 0, offset 77): expected n n obj
3 4 WARNING: obj0.pdf: Attempting to reconstruct cross-reference table
4   -checking obj0.pdf
5 5 PDF Version: 1.3
6 6 File is not encrypted
7 7 File is not linearized
... ...
qpdf/qtest/qpdf/test73.out
1   -WARNING: closed input source: object 2/0: error reading object: QPDF operation attempted after closing input source
2   -test 73 done
  1 +WARNING: closed input source: object 1/0: error reading object: QPDF operation attempted after closing input source
  2 +closed input source: unable to find /Root dictionary
... ...