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,25 +425,13 @@ I find it useful to make reference to them in this list.
425 http://multivalent.sourceforge.net/Tools/pdf/Extract.html 425 http://multivalent.sourceforge.net/Tools/pdf/Extract.html
426 http://multivalent.sourceforge.net/Tools/pdf/Embed.html 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 * The second xref stream for linearized files has to be padded only 436 * The second xref stream for linearized files has to be padded only
449 because we need file_size as computed in pass 1 to be accurate. If 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,7 +814,6 @@ class QPDF
814 int foreign_generation, 814 int foreign_generation,
815 qpdf_offset_t offset, 815 qpdf_offset_t offset,
816 size_t length, 816 size_t length,
817 - bool is_attachment_stream,  
818 QPDFObjectHandle local_dict); 817 QPDFObjectHandle local_dict);
819 818
820 private: 819 private:
@@ -824,7 +823,6 @@ class QPDF @@ -824,7 +823,6 @@ class QPDF
824 int foreign_generation; 823 int foreign_generation;
825 qpdf_offset_t offset; 824 qpdf_offset_t offset;
826 size_t length; 825 size_t length;
827 - bool is_attachment_stream;  
828 QPDFObjectHandle local_dict; 826 QPDFObjectHandle local_dict;
829 }; 827 };
830 828
@@ -919,7 +917,6 @@ class QPDF @@ -919,7 +917,6 @@ class QPDF
919 int& act_objid, int& act_generation); 917 int& act_objid, int& act_generation);
920 PointerHolder<QPDFObject> resolve(int objid, int generation); 918 PointerHolder<QPDFObject> resolve(int objid, int generation);
921 void resolveObjectsInStream(int obj_stream_number); 919 void resolveObjectsInStream(int obj_stream_number);
922 - void findAttachmentStreams();  
923 void stopOnError(std::string const& message); 920 void stopOnError(std::string const& message);
924 921
925 // Calls finish() on the pipeline when done but does not delete it 922 // Calls finish() on the pipeline when done but does not delete it
@@ -938,7 +935,6 @@ class QPDF @@ -938,7 +935,6 @@ class QPDF
938 int objid, int generation, 935 int objid, int generation,
939 qpdf_offset_t offset, size_t length, 936 qpdf_offset_t offset, size_t length,
940 QPDFObjectHandle dict, 937 QPDFObjectHandle dict,
941 - bool is_attachment_stream,  
942 Pipeline* pipeline, 938 Pipeline* pipeline,
943 bool suppress_warnings, 939 bool suppress_warnings,
944 bool will_retry); 940 bool will_retry);
@@ -1001,7 +997,7 @@ class QPDF @@ -1001,7 +997,7 @@ class QPDF
1001 PointerHolder<InputSource> file, 997 PointerHolder<InputSource> file,
1002 QPDF& qpdf_for_warning, Pipeline*& pipeline, 998 QPDF& qpdf_for_warning, Pipeline*& pipeline,
1003 int objid, int generation, 999 int objid, int generation,
1004 - QPDFObjectHandle& stream_dict, bool is_attachment_stream, 1000 + QPDFObjectHandle& stream_dict,
1005 std::vector<PointerHolder<Pipeline> >& heap); 1001 std::vector<PointerHolder<Pipeline> >& heap);
1006 1002
1007 // Methods to support object copying 1003 // Methods to support object copying
@@ -1422,7 +1418,6 @@ class QPDF @@ -1422,7 +1418,6 @@ class QPDF
1422 PointerHolder<QPDFObjectHandle::StreamDataProvider> copied_streams; 1418 PointerHolder<QPDFObjectHandle::StreamDataProvider> copied_streams;
1423 // copied_stream_data_provider is owned by copied_streams 1419 // copied_stream_data_provider is owned by copied_streams
1424 CopiedStreamDataProvider* copied_stream_data_provider; 1420 CopiedStreamDataProvider* copied_stream_data_provider;
1425 - std::set<QPDFObjGen> attachment_streams;  
1426 bool reconstructed_xref; 1421 bool reconstructed_xref;
1427 bool fixed_dangling_refs; 1422 bool fixed_dangling_refs;
1428 bool immediate_copy_from; 1423 bool immediate_copy_from;
libqpdf/QPDF.cc
@@ -18,7 +18,6 @@ @@ -18,7 +18,6 @@
18 #include <qpdf/FileInputSource.hh> 18 #include <qpdf/FileInputSource.hh>
19 #include <qpdf/BufferInputSource.hh> 19 #include <qpdf/BufferInputSource.hh>
20 #include <qpdf/OffsetInputSource.hh> 20 #include <qpdf/OffsetInputSource.hh>
21 -#include <qpdf/QPDFNameTreeObjectHelper.hh>  
22 21
23 #include <qpdf/QPDFExc.hh> 22 #include <qpdf/QPDFExc.hh>
24 #include <qpdf/QPDF_Null.hh> 23 #include <qpdf/QPDF_Null.hh>
@@ -98,7 +97,6 @@ QPDF::ForeignStreamData::ForeignStreamData( @@ -98,7 +97,6 @@ QPDF::ForeignStreamData::ForeignStreamData(
98 int foreign_generation, 97 int foreign_generation,
99 qpdf_offset_t offset, 98 qpdf_offset_t offset,
100 size_t length, 99 size_t length,
101 - bool is_attachment_stream,  
102 QPDFObjectHandle local_dict) 100 QPDFObjectHandle local_dict)
103 : 101 :
104 encp(encp), 102 encp(encp),
@@ -107,7 +105,6 @@ QPDF::ForeignStreamData::ForeignStreamData( @@ -107,7 +105,6 @@ QPDF::ForeignStreamData::ForeignStreamData(
107 foreign_generation(foreign_generation), 105 foreign_generation(foreign_generation),
108 offset(offset), 106 offset(offset),
109 length(length), 107 length(length),
110 - is_attachment_stream(is_attachment_stream),  
111 local_dict(local_dict) 108 local_dict(local_dict)
112 { 109 {
113 } 110 }
@@ -508,7 +505,6 @@ QPDF::parse(char const* password) @@ -508,7 +505,6 @@ QPDF::parse(char const* password)
508 } 505 }
509 506
510 initializeEncryption(); 507 initializeEncryption();
511 - findAttachmentStreams();  
512 this->m->parsed = true; 508 this->m->parsed = true;
513 } 509 }
514 510
@@ -2648,8 +2644,6 @@ QPDF::replaceForeignIndirectObjects( @@ -2648,8 +2644,6 @@ QPDF::replaceForeignIndirectObjects(
2648 foreign.getGeneration(), 2644 foreign.getGeneration(),
2649 stream->getOffset(), 2645 stream->getOffset(),
2650 stream->getLength(), 2646 stream->getLength(),
2651 - (foreign_stream_qpdf->m->attachment_streams.count(  
2652 - foreign.getObjGen()) > 0),  
2653 dict); 2647 dict);
2654 this->m->copied_stream_data_provider->registerForeignStream( 2648 this->m->copied_stream_data_provider->registerForeignStream(
2655 local_og, foreign_stream_data); 2649 local_og, foreign_stream_data);
@@ -2882,7 +2876,6 @@ QPDF::pipeStreamData(PointerHolder&lt;EncryptionParameters&gt; encp, @@ -2882,7 +2876,6 @@ QPDF::pipeStreamData(PointerHolder&lt;EncryptionParameters&gt; encp,
2882 int objid, int generation, 2876 int objid, int generation,
2883 qpdf_offset_t offset, size_t length, 2877 qpdf_offset_t offset, size_t length,
2884 QPDFObjectHandle stream_dict, 2878 QPDFObjectHandle stream_dict,
2885 - bool is_attachment_stream,  
2886 Pipeline* pipeline, 2879 Pipeline* pipeline,
2887 bool suppress_warnings, 2880 bool suppress_warnings,
2888 bool will_retry) 2881 bool will_retry)
@@ -2892,7 +2885,7 @@ QPDF::pipeStreamData(PointerHolder&lt;EncryptionParameters&gt; encp, @@ -2892,7 +2885,7 @@ QPDF::pipeStreamData(PointerHolder&lt;EncryptionParameters&gt; encp,
2892 { 2885 {
2893 decryptStream(encp, file, qpdf_for_warning, 2886 decryptStream(encp, file, qpdf_for_warning,
2894 pipeline, objid, generation, 2887 pipeline, objid, generation,
2895 - stream_dict, is_attachment_stream, to_delete); 2888 + stream_dict, to_delete);
2896 } 2889 }
2897 2890
2898 bool success = false; 2891 bool success = false;
@@ -2968,14 +2961,11 @@ QPDF::pipeStreamData(int objid, int generation, @@ -2968,14 +2961,11 @@ QPDF::pipeStreamData(int objid, int generation,
2968 bool suppress_warnings, 2961 bool suppress_warnings,
2969 bool will_retry) 2962 bool will_retry)
2970 { 2963 {
2971 - bool is_attachment_stream = (  
2972 - this->m->attachment_streams.count(  
2973 - QPDFObjGen(objid, generation)) > 0);  
2974 return pipeStreamData( 2964 return pipeStreamData(
2975 this->m->encp, this->m->file, *this, 2965 this->m->encp, this->m->file, *this,
2976 objid, generation, offset, length, 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 bool 2971 bool
@@ -2992,36 +2982,8 @@ QPDF::pipeForeignStreamData( @@ -2992,36 +2982,8 @@ QPDF::pipeForeignStreamData(
2992 foreign->encp, foreign->file, *this, 2982 foreign->encp, foreign->file, *this,
2993 foreign->foreign_objid, foreign->foreign_generation, 2983 foreign->foreign_objid, foreign->foreign_generation,
2994 foreign->offset, foreign->length, 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 void 2989 void
libqpdf/QPDF_encryption.cc
@@ -1022,6 +1022,20 @@ QPDF::initializeEncryption() @@ -1022,6 +1022,20 @@ QPDF::initializeEncryption()
1022 this->m->encp->cf_string = interpretCF(this->m->encp, StrF); 1022 this->m->encp->cf_string = interpretCF(this->m->encp, StrF);
1023 if (EFF.isName()) 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 this->m->encp->cf_file = interpretCF(this->m->encp, EFF); 1039 this->m->encp->cf_file = interpretCF(this->m->encp, EFF);
1026 } 1040 }
1027 else 1041 else
@@ -1224,7 +1238,6 @@ QPDF::decryptStream(PointerHolder&lt;EncryptionParameters&gt; encp, @@ -1224,7 +1238,6 @@ QPDF::decryptStream(PointerHolder&lt;EncryptionParameters&gt; encp,
1224 QPDF& qpdf_for_warning, Pipeline*& pipeline, 1238 QPDF& qpdf_for_warning, Pipeline*& pipeline,
1225 int objid, int generation, 1239 int objid, int generation,
1226 QPDFObjectHandle& stream_dict, 1240 QPDFObjectHandle& stream_dict,
1227 - bool is_attachment_stream,  
1228 std::vector<PointerHolder<Pipeline> >& heap) 1241 std::vector<PointerHolder<Pipeline> >& heap)
1229 { 1242 {
1230 std::string type; 1243 std::string type;
@@ -1296,15 +1309,7 @@ QPDF::decryptStream(PointerHolder&lt;EncryptionParameters&gt; encp, @@ -1296,15 +1309,7 @@ QPDF::decryptStream(PointerHolder&lt;EncryptionParameters&gt; encp,
1296 } 1309 }
1297 else 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 use_aes = false; 1315 use_aes = false;
qpdf/qpdf.testcov
@@ -401,7 +401,6 @@ qpdf image optimize no pipeline 0 @@ -401,7 +401,6 @@ qpdf image optimize no pipeline 0
401 qpdf image optimize no shrink 0 401 qpdf image optimize no shrink 0
402 qpdf image optimize too small 0 402 qpdf image optimize too small 0
403 QPDFFormFieldObjectHelper WinAnsi 0 403 QPDFFormFieldObjectHelper WinAnsi 0
404 -QPDF_encryption attachment stream 0  
405 QPDF pipe foreign encrypted stream 0 404 QPDF pipe foreign encrypted stream 0
406 QPDF copy foreign stream with provider 0 405 QPDF copy foreign stream with provider 0
407 QPDF copy foreign stream with buffer 0 406 QPDF copy foreign stream with buffer 0
qpdf/qtest/qpdf.test
@@ -1257,7 +1257,7 @@ $n_tests += 3; @@ -1257,7 +1257,7 @@ $n_tests += 3;
1257 $td->runtest("closed input source", 1257 $td->runtest("closed input source",
1258 {$td->COMMAND => "test_driver 73 minimal.pdf"}, 1258 {$td->COMMAND => "test_driver 73 minimal.pdf"},
1259 {$td->FILE => "test73.out", 1259 {$td->FILE => "test73.out",
1260 - $td->EXIT_STATUS => 0}, 1260 + $td->EXIT_STATUS => 2},
1261 $td->NORMALIZE_NEWLINES); 1261 $td->NORMALIZE_NEWLINES);
1262 1262
1263 $td->runtest("empty object", 1263 $td->runtest("empty object",
@@ -3878,7 +3878,7 @@ $td-&gt;runtest(&quot;check encryption&quot;, @@ -3878,7 +3878,7 @@ $td-&gt;runtest(&quot;check encryption&quot;,
3878 $td->NORMALIZE_NEWLINES); 3878 $td->NORMALIZE_NEWLINES);
3879 3879
3880 # Look at some actual V4 files 3880 # Look at some actual V4 files
3881 -$n_tests += 14; 3881 +$n_tests += 17;
3882 foreach my $d (['--force-V4', 'V4'], 3882 foreach my $d (['--force-V4', 'V4'],
3883 ['--cleartext-metadata', 'V4-clearmeta'], 3883 ['--cleartext-metadata', 'V4-clearmeta'],
3884 ['--use-aes=y', 'V4-aes'], 3884 ['--use-aes=y', 'V4-aes'],
@@ -3906,6 +3906,19 @@ $td-&gt;runtest(&quot;decrypt with crypt filter&quot;, @@ -3906,6 +3906,19 @@ $td-&gt;runtest(&quot;decrypt with crypt filter&quot;,
3906 $td->runtest("check output", 3906 $td->runtest("check output",
3907 {$td->FILE => 'a.pdf'}, 3907 {$td->FILE => 'a.pdf'},
3908 {$td->FILE => 'decrypted-crypt-filter.pdf'}); 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 # Copy encryption parameters 3923 # Copy encryption parameters
3911 $n_tests += 10; 3924 $n_tests += 10;
qpdf/qtest/qpdf/indirect-r-arg.out
  1 +checking indirect-r-arg.pdf
1 WARNING: indirect-r-arg.pdf (object 1 0, offset 76): unknown token while reading object; treating as string 2 WARNING: indirect-r-arg.pdf (object 1 0, offset 76): unknown token while reading object; treating as string
2 WARNING: indirect-r-arg.pdf (object 1 0, offset 62): expected dictionary key but found non-name object; inserting key /QPDFFake1 3 WARNING: indirect-r-arg.pdf (object 1 0, offset 62): expected dictionary key but found non-name object; inserting key /QPDFFake1
3 WARNING: indirect-r-arg.pdf (object 1 0, offset 62): expected dictionary key but found non-name object; inserting key /QPDFFake2 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 PDF Version: 1.3 5 PDF Version: 1.3
6 File is not encrypted 6 File is not encrypted
7 File is not linearized 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 WARNING: obj0.pdf: file is damaged 2 WARNING: obj0.pdf: file is damaged
2 WARNING: obj0.pdf (object 1 0, offset 77): expected n n obj 3 WARNING: obj0.pdf (object 1 0, offset 77): expected n n obj
3 WARNING: obj0.pdf: Attempting to reconstruct cross-reference table 4 WARNING: obj0.pdf: Attempting to reconstruct cross-reference table
4 -checking obj0.pdf  
5 PDF Version: 1.3 5 PDF Version: 1.3
6 File is not encrypted 6 File is not encrypted
7 File is not linearized 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