2
1
mirror of https://github.com/qpdf/qpdf.git synced 2024-06-01 18:00:52 +00:00

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.
This commit is contained in:
Jay Berkenbilt 2021-02-06 16:25:10 -05:00
parent ac2b3b96e1
commit e076c9bf08
12 changed files with 64 additions and 85 deletions

26
TODO
View File

@ -425,25 +425,13 @@ I find it useful to make reference to them in this list.
http://multivalent.sourceforge.net/Tools/pdf/Extract.html http://multivalent.sourceforge.net/Tools/pdf/Extract.html
http://multivalent.sourceforge.net/Tools/pdf/Embed.html http://multivalent.sourceforge.net/Tools/pdf/Embed.html
* The description of Crypt filters is unclear with respect to how to * Qpdf does not honor /EFF when adding new file attachments. When it
use them to override /StmF for specific streams. I'm not sure encrypts, it never generates streams with explicit crypt filters.
whether qpdf will do the right thing for any specific individual Prior to 10.2, there was an incorrect attempt to treat /EFF as a
streams that might have crypt filters, but I believe it does based default value for decrypting file attachment streams, but it is not
on my testing of a limited subset. The specification seems to imply supposed to mean that. Instead, it is intended for comforming
that only embedded file streams and metadata streams can have crypt writers to obey this when adding new attachments. Qpdf is not a
filters, and there are already special cases in the code to handle conforming writer in that respect.
those. Most likely, it won't be a problem, but someday someone may
find a file that qpdf doesn't work on because of crypt filters.
There is an example in the spec of using a crypt filter on a
metadata stream.
For now, we notice /Crypt filters and decode parameters consistent
with the example in the PDF specification, and the right thing
happens for metadata filters that happen to be uncompressed or
otherwise compressed in a way we can filter. This should handle
all normal cases, but it's more or less just a guess since I don't
have any test files that actually use stream-specific crypt filters
in them.
* The second xref stream for linearized files has to be padded only * The second xref stream for linearized files has to be padded only
because we need file_size as computed in pass 1 to be accurate. If because we need file_size as computed in pass 1 to be accurate. If

View File

@ -814,7 +814,6 @@ class QPDF
int foreign_generation, int foreign_generation,
qpdf_offset_t offset, qpdf_offset_t offset,
size_t length, size_t length,
bool is_attachment_stream,
QPDFObjectHandle local_dict); QPDFObjectHandle local_dict);
private: private:
@ -824,7 +823,6 @@ class QPDF
int foreign_generation; int foreign_generation;
qpdf_offset_t offset; qpdf_offset_t offset;
size_t length; size_t length;
bool is_attachment_stream;
QPDFObjectHandle local_dict; QPDFObjectHandle local_dict;
}; };
@ -919,7 +917,6 @@ class QPDF
int& act_objid, int& act_generation); int& act_objid, int& act_generation);
PointerHolder<QPDFObject> resolve(int objid, int generation); PointerHolder<QPDFObject> resolve(int objid, int generation);
void resolveObjectsInStream(int obj_stream_number); void resolveObjectsInStream(int obj_stream_number);
void findAttachmentStreams();
void stopOnError(std::string const& message); void stopOnError(std::string const& message);
// Calls finish() on the pipeline when done but does not delete it // Calls finish() on the pipeline when done but does not delete it
@ -938,7 +935,6 @@ class QPDF
int objid, int generation, int objid, int generation,
qpdf_offset_t offset, size_t length, qpdf_offset_t offset, size_t length,
QPDFObjectHandle dict, QPDFObjectHandle dict,
bool is_attachment_stream,
Pipeline* pipeline, Pipeline* pipeline,
bool suppress_warnings, bool suppress_warnings,
bool will_retry); bool will_retry);
@ -1001,7 +997,7 @@ class QPDF
PointerHolder<InputSource> file, PointerHolder<InputSource> file,
QPDF& qpdf_for_warning, Pipeline*& pipeline, QPDF& qpdf_for_warning, Pipeline*& pipeline,
int objid, int generation, int objid, int generation,
QPDFObjectHandle& stream_dict, bool is_attachment_stream, QPDFObjectHandle& stream_dict,
std::vector<PointerHolder<Pipeline> >& heap); std::vector<PointerHolder<Pipeline> >& heap);
// Methods to support object copying // Methods to support object copying
@ -1422,7 +1418,6 @@ class QPDF
PointerHolder<QPDFObjectHandle::StreamDataProvider> copied_streams; PointerHolder<QPDFObjectHandle::StreamDataProvider> copied_streams;
// copied_stream_data_provider is owned by copied_streams // copied_stream_data_provider is owned by copied_streams
CopiedStreamDataProvider* copied_stream_data_provider; CopiedStreamDataProvider* copied_stream_data_provider;
std::set<QPDFObjGen> attachment_streams;
bool reconstructed_xref; bool reconstructed_xref;
bool fixed_dangling_refs; bool fixed_dangling_refs;
bool immediate_copy_from; bool immediate_copy_from;

View File

@ -18,7 +18,6 @@
#include <qpdf/FileInputSource.hh> #include <qpdf/FileInputSource.hh>
#include <qpdf/BufferInputSource.hh> #include <qpdf/BufferInputSource.hh>
#include <qpdf/OffsetInputSource.hh> #include <qpdf/OffsetInputSource.hh>
#include <qpdf/QPDFNameTreeObjectHelper.hh>
#include <qpdf/QPDFExc.hh> #include <qpdf/QPDFExc.hh>
#include <qpdf/QPDF_Null.hh> #include <qpdf/QPDF_Null.hh>
@ -98,7 +97,6 @@ QPDF::ForeignStreamData::ForeignStreamData(
int foreign_generation, int foreign_generation,
qpdf_offset_t offset, qpdf_offset_t offset,
size_t length, size_t length,
bool is_attachment_stream,
QPDFObjectHandle local_dict) QPDFObjectHandle local_dict)
: :
encp(encp), encp(encp),
@ -107,7 +105,6 @@ QPDF::ForeignStreamData::ForeignStreamData(
foreign_generation(foreign_generation), foreign_generation(foreign_generation),
offset(offset), offset(offset),
length(length), length(length),
is_attachment_stream(is_attachment_stream),
local_dict(local_dict) local_dict(local_dict)
{ {
} }
@ -508,7 +505,6 @@ QPDF::parse(char const* password)
} }
initializeEncryption(); initializeEncryption();
findAttachmentStreams();
this->m->parsed = true; this->m->parsed = true;
} }
@ -2648,8 +2644,6 @@ QPDF::replaceForeignIndirectObjects(
foreign.getGeneration(), foreign.getGeneration(),
stream->getOffset(), stream->getOffset(),
stream->getLength(), stream->getLength(),
(foreign_stream_qpdf->m->attachment_streams.count(
foreign.getObjGen()) > 0),
dict); dict);
this->m->copied_stream_data_provider->registerForeignStream( this->m->copied_stream_data_provider->registerForeignStream(
local_og, foreign_stream_data); local_og, foreign_stream_data);
@ -2882,7 +2876,6 @@ QPDF::pipeStreamData(PointerHolder<EncryptionParameters> encp,
int objid, int generation, int objid, int generation,
qpdf_offset_t offset, size_t length, qpdf_offset_t offset, size_t length,
QPDFObjectHandle stream_dict, QPDFObjectHandle stream_dict,
bool is_attachment_stream,
Pipeline* pipeline, Pipeline* pipeline,
bool suppress_warnings, bool suppress_warnings,
bool will_retry) bool will_retry)
@ -2892,7 +2885,7 @@ QPDF::pipeStreamData(PointerHolder<EncryptionParameters> encp,
{ {
decryptStream(encp, file, qpdf_for_warning, decryptStream(encp, file, qpdf_for_warning,
pipeline, objid, generation, pipeline, objid, generation,
stream_dict, is_attachment_stream, to_delete); stream_dict, to_delete);
} }
bool success = false; bool success = false;
@ -2968,14 +2961,11 @@ QPDF::pipeStreamData(int objid, int generation,
bool suppress_warnings, bool suppress_warnings,
bool will_retry) bool will_retry)
{ {
bool is_attachment_stream = (
this->m->attachment_streams.count(
QPDFObjGen(objid, generation)) > 0);
return pipeStreamData( return pipeStreamData(
this->m->encp, this->m->file, *this, this->m->encp, this->m->file, *this,
objid, generation, offset, length, objid, generation, offset, length,
stream_dict, is_attachment_stream, stream_dict, pipeline,
pipeline, suppress_warnings, will_retry); suppress_warnings, will_retry);
} }
bool bool
@ -2992,36 +2982,8 @@ QPDF::pipeForeignStreamData(
foreign->encp, foreign->file, *this, foreign->encp, foreign->file, *this,
foreign->foreign_objid, foreign->foreign_generation, foreign->foreign_objid, foreign->foreign_generation,
foreign->offset, foreign->length, foreign->offset, foreign->length,
foreign->local_dict, foreign->is_attachment_stream, foreign->local_dict, pipeline,
pipeline, suppress_warnings, will_retry); suppress_warnings, will_retry);
}
void
QPDF::findAttachmentStreams()
{
QPDFObjectHandle root = getRoot();
QPDFObjectHandle names = root.getKey("/Names");
if (! names.isDictionary())
{
return;
}
QPDFObjectHandle embedded_files = names.getKey("/EmbeddedFiles");
if (! embedded_files.isDictionary())
{
return;
}
QPDFNameTreeObjectHelper ef_tree(embedded_files, *this);
for (auto const& i: ef_tree)
{
QPDFObjectHandle item = i.second;
if (item.isDictionary() &&
item.getKey("/EF").isDictionary() &&
item.getKey("/EF").getKey("/F").isStream())
{
QPDFObjectHandle stream = item.getKey("/EF").getKey("/F");
this->m->attachment_streams.insert(stream.getObjGen());
}
}
} }
void void

View File

@ -1022,6 +1022,20 @@ QPDF::initializeEncryption()
this->m->encp->cf_string = interpretCF(this->m->encp, StrF); this->m->encp->cf_string = interpretCF(this->m->encp, StrF);
if (EFF.isName()) if (EFF.isName())
{ {
// qpdf does not use this for anything other than
// informational purposes. This is intended to instruct
// conforming writers on which crypt filter should be used
// when new file attachments are added to a PDF file, but
// qpdf never generates encrypted files with non-default
// crypt filters. Prior to 10.2, I was under the mistaken
// impression that this was supposed to be used for
// decrypting attachments, but the code was wrong in a way
// that turns out not to have mattered because no writers
// were generating files the way I was imagining. Still,
// providing this information could be useful when looking
// at a file generated by something else, such as Acrobat
// when specifying that only attachments should be
// encrypted.
this->m->encp->cf_file = interpretCF(this->m->encp, EFF); this->m->encp->cf_file = interpretCF(this->m->encp, EFF);
} }
else else
@ -1224,7 +1238,6 @@ QPDF::decryptStream(PointerHolder<EncryptionParameters> encp,
QPDF& qpdf_for_warning, Pipeline*& pipeline, QPDF& qpdf_for_warning, Pipeline*& pipeline,
int objid, int generation, int objid, int generation,
QPDFObjectHandle& stream_dict, QPDFObjectHandle& stream_dict,
bool is_attachment_stream,
std::vector<PointerHolder<Pipeline> >& heap) std::vector<PointerHolder<Pipeline> >& heap)
{ {
std::string type; std::string type;
@ -1296,15 +1309,7 @@ QPDF::decryptStream(PointerHolder<EncryptionParameters> encp,
} }
else else
{ {
if (is_attachment_stream) method = encp->cf_stream;
{
QTC::TC("qpdf", "QPDF_encryption attachment stream");
method = encp->cf_file;
}
else
{
method = encp->cf_stream;
}
} }
} }
use_aes = false; use_aes = false;

View File

@ -401,7 +401,6 @@ qpdf image optimize no pipeline 0
qpdf image optimize no shrink 0 qpdf image optimize no shrink 0
qpdf image optimize too small 0 qpdf image optimize too small 0
QPDFFormFieldObjectHelper WinAnsi 0 QPDFFormFieldObjectHelper WinAnsi 0
QPDF_encryption attachment stream 0
QPDF pipe foreign encrypted stream 0 QPDF pipe foreign encrypted stream 0
QPDF copy foreign stream with provider 0 QPDF copy foreign stream with provider 0
QPDF copy foreign stream with buffer 0 QPDF copy foreign stream with buffer 0

View File

@ -1257,7 +1257,7 @@ $n_tests += 3;
$td->runtest("closed input source", $td->runtest("closed input source",
{$td->COMMAND => "test_driver 73 minimal.pdf"}, {$td->COMMAND => "test_driver 73 minimal.pdf"},
{$td->FILE => "test73.out", {$td->FILE => "test73.out",
$td->EXIT_STATUS => 0}, $td->EXIT_STATUS => 2},
$td->NORMALIZE_NEWLINES); $td->NORMALIZE_NEWLINES);
$td->runtest("empty object", $td->runtest("empty object",
@ -3878,7 +3878,7 @@ $td->runtest("check encryption",
$td->NORMALIZE_NEWLINES); $td->NORMALIZE_NEWLINES);
# Look at some actual V4 files # Look at some actual V4 files
$n_tests += 14; $n_tests += 17;
foreach my $d (['--force-V4', 'V4'], foreach my $d (['--force-V4', 'V4'],
['--cleartext-metadata', 'V4-clearmeta'], ['--cleartext-metadata', 'V4-clearmeta'],
['--use-aes=y', 'V4-aes'], ['--use-aes=y', 'V4-aes'],
@ -3906,6 +3906,19 @@ $td->runtest("decrypt with crypt filter",
$td->runtest("check output", $td->runtest("check output",
{$td->FILE => 'a.pdf'}, {$td->FILE => 'a.pdf'},
{$td->FILE => 'decrypted-crypt-filter.pdf'}); {$td->FILE => 'decrypted-crypt-filter.pdf'});
$td->runtest("nontrivial crypt filter",
{$td->COMMAND => "qpdf --qdf --decrypt --static-id" .
" nontrivial-crypt-filter.pdf --password=asdfqwer a.pdf"},
{$td->STRING => "", $td->EXIT_STATUS => 0});
$td->runtest("check output",
{$td->FILE => 'a.pdf'},
{$td->FILE => 'nontrivial-crypt-filter-decrypted.pdf'});
$td->runtest("show nontrivial EFF",
{$td->COMMAND => "qpdf --show-encryption" .
" nontrivial-crypt-filter.pdf --password=asdfqwer"},
{$td->FILE => "nontrivial-crypt-filter.out",
$td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
# Copy encryption parameters # Copy encryption parameters
$n_tests += 10; $n_tests += 10;

View File

@ -1,7 +1,7 @@
checking indirect-r-arg.pdf
WARNING: indirect-r-arg.pdf (object 1 0, offset 76): unknown token while reading object; treating as string WARNING: indirect-r-arg.pdf (object 1 0, offset 76): unknown token while reading object; treating as string
WARNING: indirect-r-arg.pdf (object 1 0, offset 62): expected dictionary key but found non-name object; inserting key /QPDFFake1 WARNING: indirect-r-arg.pdf (object 1 0, offset 62): expected dictionary key but found non-name object; inserting key /QPDFFake1
WARNING: indirect-r-arg.pdf (object 1 0, offset 62): expected dictionary key but found non-name object; inserting key /QPDFFake2 WARNING: indirect-r-arg.pdf (object 1 0, offset 62): expected dictionary key but found non-name object; inserting key /QPDFFake2
checking indirect-r-arg.pdf
PDF Version: 1.3 PDF Version: 1.3
File is not encrypted File is not encrypted
File is not linearized File is not linearized

Binary file not shown.

View File

@ -0,0 +1,17 @@
R = 6
P = -1028
User password = asdfqwer
Supplied password is owner password
Supplied password is user password
extract for accessibility: allowed
extract for any purpose: allowed
print low resolution: allowed
print high resolution: allowed
modify document assembly: not allowed
modify forms: allowed
modify annotations: allowed
modify other: allowed
modify anything: not allowed
stream encryption method: none
string encryption method: none
file encryption method: AESv3

Binary file not shown.

View File

@ -1,7 +1,7 @@
checking obj0.pdf
WARNING: obj0.pdf: file is damaged WARNING: obj0.pdf: file is damaged
WARNING: obj0.pdf (object 1 0, offset 77): expected n n obj WARNING: obj0.pdf (object 1 0, offset 77): expected n n obj
WARNING: obj0.pdf: Attempting to reconstruct cross-reference table WARNING: obj0.pdf: Attempting to reconstruct cross-reference table
checking obj0.pdf
PDF Version: 1.3 PDF Version: 1.3
File is not encrypted File is not encrypted
File is not linearized File is not linearized

View File

@ -1,2 +1,2 @@
WARNING: closed input source: object 2/0: error reading object: QPDF operation attempted after closing input source WARNING: closed input source: object 1/0: error reading object: QPDF operation attempted after closing input source
test 73 done closed input source: unable to find /Root dictionary