mirror of
https://github.com/qpdf/qpdf.git
synced 2024-12-22 10:58:58 +00:00
more testing, bug fix for linearized aes encrypted files
git-svn-id: svn+q:///qpdf/trunk@824 71b93d88-0707-0410-a8cf-f5a4172ac649
This commit is contained in:
parent
94131116a9
commit
09175e4578
@ -1,5 +1,11 @@
|
||||
2009-10-18 Jay Berkenbilt <ejb@ql.org>
|
||||
|
||||
* If forcing version, disable object stream creation and/or
|
||||
encryption if previous specifications are incompatible with new
|
||||
version. It is still possible that PDF content, compression
|
||||
schemes, etc., may be incompatible with the new version, but at
|
||||
least this way, older viewers will at least have a chance.
|
||||
|
||||
* libqpdf/QPDFWriter.cc (unparseObject): avoid compressing
|
||||
Metadata streams if possible.
|
||||
|
||||
|
56
TODO
56
TODO
@ -1,6 +1,9 @@
|
||||
2.1
|
||||
===
|
||||
|
||||
* Really need to handle /Crypt filter for Metadata. Search for crypt
|
||||
below.
|
||||
|
||||
* Update documentation to reflect new command line flags and any
|
||||
other relevant changes. Should read through ChangeLog and the
|
||||
manual before releasing 2.1.
|
||||
@ -16,9 +19,6 @@
|
||||
* Add comments for the security functions that map them back to the
|
||||
items in Adobe's products.
|
||||
|
||||
* Have force version at least turn off object streams and maybe
|
||||
change security settings?
|
||||
|
||||
* Add error codes to QPDFException. Change the error interface so
|
||||
that warnings and errors are pointers that can be queried using
|
||||
more C API functions. We need a way to get a full string as well
|
||||
@ -47,49 +47,7 @@
|
||||
|
||||
- Update C API for R4 encryption
|
||||
|
||||
- When we write encrypted files, we must remember to omit any
|
||||
encryption filter settings from original streams.
|
||||
|
||||
- test various combinations with and without cleartext-metadata
|
||||
and aes in compression tests
|
||||
|
||||
- figure out a way to test crypt filters defined on a stream
|
||||
|
||||
- test combinations of linearization and v4 encryption
|
||||
|
||||
- would be nice to test strings and streams with different
|
||||
encryption types, but without sample data, we'd have to write
|
||||
them ourselves which is not that useful
|
||||
|
||||
- figure out how to look at the metadata so I can tell whether
|
||||
/EncryptMetadata is working the way it's supposed to
|
||||
|
||||
- Do something with embedded files, but what and how?
|
||||
|
||||
- General notes:
|
||||
|
||||
/CF - keys are crypt filter names, values are are crypt
|
||||
dictionaries
|
||||
|
||||
Individual streams may also have crypt filters. Filter type
|
||||
/Crypt; /DecodeParms must contain a Crypt filter decode
|
||||
parameters dictionary whose /Name entry specifies the particular
|
||||
filter to be used. If /Name is missing, use /Identity.
|
||||
/DecodeParms << /Crypt << /Name /XYZ >> >> where /XYZ is
|
||||
/Identity or a key in /CF.
|
||||
|
||||
/Identity means not to encrypt.
|
||||
|
||||
Crypt Dictionaries
|
||||
|
||||
/Type (optional) /CryptFilter
|
||||
/CFM:
|
||||
/V2 - use rc4
|
||||
/AESV2 - use aes
|
||||
/Length - supposed to be key length, but the one file I have
|
||||
has a bogus value for it, so I'm ignoring it.
|
||||
|
||||
We will ignore remaining fields and values.
|
||||
|
||||
2.2
|
||||
===
|
||||
@ -127,7 +85,13 @@ General
|
||||
crypt filters, and there are already special cases in the code to
|
||||
handle 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.
|
||||
filters. There is an example in the spec of using a crypt filter
|
||||
on a metadata stream.
|
||||
|
||||
When we write encrypted files, we must remember to omit any
|
||||
encryption filter settings from original streams.
|
||||
|
||||
We need a way to test this.
|
||||
|
||||
* 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
|
||||
|
@ -98,6 +98,12 @@ class DLL_EXPORT QPDFWriter
|
||||
// you are sure the PDF file in question has no features of newer
|
||||
// versions of PDF or if you are willing to create files that old
|
||||
// viewers may try to open but not be able to properly interpret.
|
||||
// If any encryption has been applied to the document either
|
||||
// explicitly or by preserving the encryption of the source
|
||||
// document, forcing the PDF version to a value too low to support
|
||||
// that type of encryption will explicitly disable decryption.
|
||||
// Additionally, forcing to a version below 1.5 will disable
|
||||
// object streams.
|
||||
void forcePDFVersion(std::string const&);
|
||||
|
||||
// Cause a static /ID value to be generated. Use only in test
|
||||
@ -193,6 +199,7 @@ class DLL_EXPORT QPDFWriter
|
||||
char const* user_password, char const* owner_password,
|
||||
bool allow_accessibility, bool allow_extract,
|
||||
r3_print_e print, r3_modify_e modify);
|
||||
void disableIncompatbleEncryption(float v);
|
||||
void setEncryptionParameters(
|
||||
char const* user_password, char const* owner_password,
|
||||
int V, int R, int key_len, std::set<int>& bits_to_clear);
|
||||
|
@ -344,6 +344,52 @@ QPDFWriter::copyEncryptionParameters()
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
QPDFWriter::disableIncompatbleEncryption(float v)
|
||||
{
|
||||
if (! this->encrypted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool disable = false;
|
||||
if (v < 1.3)
|
||||
{
|
||||
disable = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
int V = atoi(encryption_dictionary["/V"].c_str());
|
||||
int R = atoi(encryption_dictionary["/R"].c_str());
|
||||
if (v < 1.4)
|
||||
{
|
||||
if ((V > 1) || (R > 2))
|
||||
{
|
||||
disable = true;
|
||||
}
|
||||
}
|
||||
else if (v < 1.5)
|
||||
{
|
||||
if ((V > 2) || (R > 3))
|
||||
{
|
||||
disable = true;
|
||||
}
|
||||
}
|
||||
else if (v < 1.6)
|
||||
{
|
||||
if (this->encrypt_use_aes)
|
||||
{
|
||||
disable = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (disable)
|
||||
{
|
||||
QTC::TC("qpdf", "QPDFWriter forced version disabled encryption");
|
||||
this->encrypted = false;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
QPDFWriter::setEncryptionParametersInternal(
|
||||
int V, int R, int key_len, long P,
|
||||
@ -965,7 +1011,7 @@ QPDFWriter::unparseObject(QPDFObjectHandle object, int level,
|
||||
Buffer* buf = bufpl.getBuffer();
|
||||
val = QPDF_String(
|
||||
std::string((char*)buf->getBuffer(),
|
||||
(size_t)buf->getSize())).unparse();
|
||||
(size_t)buf->getSize())).unparse(true);
|
||||
delete buf;
|
||||
}
|
||||
else
|
||||
@ -1423,6 +1469,17 @@ QPDFWriter::write()
|
||||
copyEncryptionParameters();
|
||||
}
|
||||
|
||||
if (! this->forced_pdf_version.empty())
|
||||
{
|
||||
float v = atof(this->forced_pdf_version.c_str());
|
||||
disableIncompatbleEncryption(v);
|
||||
if (v < 1.5)
|
||||
{
|
||||
QTC::TC("qpdf", "QPDFWriter forcing object stream disable");
|
||||
this->object_stream_mode = o_disable;
|
||||
}
|
||||
}
|
||||
|
||||
if (this->qdf_mode || this->normalize_content ||
|
||||
(this->stream_data_mode == s_uncompress))
|
||||
{
|
||||
|
@ -166,4 +166,5 @@ QPDF_encryption CFM AESV2 0
|
||||
QPDF_encryption aes decode string 0
|
||||
QPDF_encryption cleartext metadata 0
|
||||
QPDF_encryption aes decode stream 0
|
||||
QPDF_encryption stream crypt filter 0
|
||||
QPDFWriter forcing object stream disable 0
|
||||
QPDFWriter forced version disabled encryption 0
|
||||
|
@ -998,14 +998,14 @@ $td->runtest("check linearization",
|
||||
$td->NORMALIZE_NEWLINES);
|
||||
$td->runtest("linearize and encrypt file",
|
||||
{$td->COMMAND =>
|
||||
"qpdf --linearize --encrypt user owner 128 --" .
|
||||
"qpdf --linearize --encrypt user owner 128 --use-aes=y --" .
|
||||
" lin-special.pdf a.pdf"},
|
||||
{$td->STRING => "",
|
||||
$td->EXIT_STATUS => 0});
|
||||
$td->runtest("check encryption",
|
||||
{$td->COMMAND => "qpdf --show-encryption --password=owner a.pdf",
|
||||
$td->FILTER => "grep -v allowed"},
|
||||
{$td->STRING => "R = 3\nP = -4\nUser password = user\n",
|
||||
{$td->STRING => "R = 4\nP = -4\nUser password = user\n",
|
||||
$td->EXIT_STATUS => 0},
|
||||
$td->NORMALIZE_NEWLINES);
|
||||
$td->runtest("check linearization",
|
||||
@ -1015,6 +1015,68 @@ $td->runtest("check linearization",
|
||||
$td->EXIT_STATUS => 0},
|
||||
$td->NORMALIZE_NEWLINES);
|
||||
|
||||
# Test AES encryption in various ways.
|
||||
$n_tests += 14;
|
||||
$td->runtest("encrypt with AES",
|
||||
{$td->COMMAND => "qpdf --encrypt '' '' 128 --use-aes=y --" .
|
||||
" enc-base.pdf a.pdf"},
|
||||
{$td->STRING => "", $td->EXIT_STATUS => 0});
|
||||
$td->runtest("check encryption",
|
||||
{$td->COMMAND => "qpdf --show-encryption a.pdf",
|
||||
$td->FILTER => "grep -v allowed"},
|
||||
{$td->STRING => "R = 4\nP = -4\nUser password = \n",
|
||||
$td->EXIT_STATUS => 0},
|
||||
$td->NORMALIZE_NEWLINES);
|
||||
$td->runtest("convert original to qdf",
|
||||
{$td->COMMAND => "qpdf --static-id --no-original-object-ids" .
|
||||
" --qdf --min-version=1.6 enc-base.pdf a.qdf"},
|
||||
{$td->STRING => "", $td->EXIT_STATUS => 0});
|
||||
$td->runtest("convert encrypted to qdf",
|
||||
{$td->COMMAND => "qpdf --static-id --no-original-object-ids" .
|
||||
" --qdf a.pdf b.qdf"},
|
||||
{$td->STRING => "", $td->EXIT_STATUS => 0});
|
||||
$td->runtest("compare files",
|
||||
{$td->FILE => 'a.qdf'},
|
||||
{$td->FILE => 'b.qdf'});
|
||||
$td->runtest("linearize with AES and object streams",
|
||||
{$td->COMMAND => "qpdf --encrypt '' '' 128 --use-aes=y --" .
|
||||
" --linearize --object-streams=generate enc-base.pdf a.pdf"},
|
||||
{$td->STRING => "", $td->EXIT_STATUS => 0});
|
||||
$td->runtest("check encryption",
|
||||
{$td->COMMAND => "qpdf --show-encryption a.pdf",
|
||||
$td->FILTER => "grep -v allowed"},
|
||||
{$td->STRING => "R = 4\nP = -4\nUser password = \n",
|
||||
$td->EXIT_STATUS => 0},
|
||||
$td->NORMALIZE_NEWLINES);
|
||||
$td->runtest("linearize original",
|
||||
{$td->COMMAND => "qpdf --linearize --object-streams=generate" .
|
||||
" enc-base.pdf b.pdf"},
|
||||
{$td->STRING => "", $td->EXIT_STATUS => 0});
|
||||
$td->runtest("convert linearized original to qdf",
|
||||
{$td->COMMAND => "qpdf --static-id --no-original-object-ids" .
|
||||
" --qdf --object-streams=generate --min-version=1.6" .
|
||||
" b.pdf a.qdf"},
|
||||
{$td->STRING => "", $td->EXIT_STATUS => 0});
|
||||
$td->runtest("convert encrypted to qdf",
|
||||
{$td->COMMAND => "qpdf --static-id --no-original-object-ids" .
|
||||
" --qdf --object-streams=generate a.pdf b.qdf"},
|
||||
{$td->STRING => "", $td->EXIT_STATUS => 0});
|
||||
$td->runtest("compare files",
|
||||
{$td->FILE => 'a.qdf'},
|
||||
{$td->FILE => 'b.qdf'});
|
||||
$td->runtest("force version on aes encrypted",
|
||||
{$td->COMMAND => "qpdf --force-version=1.4 a.pdf b.pdf"},
|
||||
{$td->STRING => "", $td->EXIT_STATUS => 0});
|
||||
$td->runtest("check",
|
||||
{$td->COMMAND => "qpdf --check b.pdf"},
|
||||
{$td->FILE => "aes-forced-check.out",
|
||||
$td->EXIT_STATUS => 0},
|
||||
$td->NORMALIZE_NEWLINES);
|
||||
$td->runtest("make sure there is no xref stream",
|
||||
{$td->COMMAND => "grep /ObjStm b.pdf | wc -l"},
|
||||
{$td->REGEXP => "\\s*0\\s*", $td->EXIT_STATUS => 0},
|
||||
$td->NORMALIZE_NEWLINES);
|
||||
|
||||
show_ntests();
|
||||
# ----------
|
||||
$td->notify("--- Content Preservation Tests ---");
|
||||
|
5
qpdf/qtest/qpdf/aes-forced-check.out
Normal file
5
qpdf/qtest/qpdf/aes-forced-check.out
Normal file
@ -0,0 +1,5 @@
|
||||
checking b.pdf
|
||||
PDF Version: 1.4
|
||||
File is not encrypted
|
||||
File is not linearized
|
||||
No errors found
|
Loading…
Reference in New Issue
Block a user