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:
Jay Berkenbilt 2009-10-19 00:17:11 +00:00
parent 94131116a9
commit 09175e4578
7 changed files with 152 additions and 50 deletions

View File

@ -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
View File

@ -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

View File

@ -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);

View File

@ -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))
{

View File

@ -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

View File

@ -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 ---");

View File

@ -0,0 +1,5 @@
checking b.pdf
PDF Version: 1.4
File is not encrypted
File is not linearized
No errors found