mirror of
https://github.com/qpdf/qpdf.git
synced 2024-06-03 10:50:53 +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:
parent
ac2b3b96e1
commit
e076c9bf08
26
TODO
26
TODO
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
BIN
qpdf/qtest/qpdf/nontrivial-crypt-filter-decrypted.pdf
Normal file
BIN
qpdf/qtest/qpdf/nontrivial-crypt-filter-decrypted.pdf
Normal file
Binary file not shown.
17
qpdf/qtest/qpdf/nontrivial-crypt-filter.out
Normal file
17
qpdf/qtest/qpdf/nontrivial-crypt-filter.out
Normal 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
|
BIN
qpdf/qtest/qpdf/nontrivial-crypt-filter.pdf
Normal file
BIN
qpdf/qtest/qpdf/nontrivial-crypt-filter.pdf
Normal file
Binary file not shown.
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue
Block a user