aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJay Berkenbilt <ejb@ql.org>2009-10-19 02:17:11 +0200
committerJay Berkenbilt <ejb@ql.org>2009-10-19 02:17:11 +0200
commit09175e457852c585a68a86d43280f7e0790a4a3b (patch)
tree64269f6bb06d53975f4b8fe51a3900f868748629
parent94131116a90a076c49e799aa5e4c63ce0ecb0391 (diff)
downloadqpdf-09175e457852c585a68a86d43280f7e0790a4a3b.tar.zst
more testing, bug fix for linearized aes encrypted files
git-svn-id: svn+q:///qpdf/trunk@824 71b93d88-0707-0410-a8cf-f5a4172ac649
-rw-r--r--ChangeLog6
-rw-r--r--TODO56
-rw-r--r--include/qpdf/QPDFWriter.hh7
-rw-r--r--libqpdf/QPDFWriter.cc59
-rw-r--r--qpdf/qpdf.testcov3
-rw-r--r--qpdf/qtest/qpdf.test66
-rw-r--r--qpdf/qtest/qpdf/aes-forced-check.out5
7 files changed, 152 insertions, 50 deletions
diff --git a/ChangeLog b/ChangeLog
index fe79bdfe..13badadb 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -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.
diff --git a/TODO b/TODO
index 952f5c80..81f05087 100644
--- a/TODO
+++ b/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
diff --git a/include/qpdf/QPDFWriter.hh b/include/qpdf/QPDFWriter.hh
index 8a4e9ebe..e16c5f99 100644
--- a/include/qpdf/QPDFWriter.hh
+++ b/include/qpdf/QPDFWriter.hh
@@ -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);
diff --git a/libqpdf/QPDFWriter.cc b/libqpdf/QPDFWriter.cc
index d094aa66..3c1640f7 100644
--- a/libqpdf/QPDFWriter.cc
+++ b/libqpdf/QPDFWriter.cc
@@ -345,6 +345,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,
std::string const& O, std::string const& U,
@@ -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))
{
diff --git a/qpdf/qpdf.testcov b/qpdf/qpdf.testcov
index fc4cb383..d1cf3293 100644
--- a/qpdf/qpdf.testcov
+++ b/qpdf/qpdf.testcov
@@ -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
diff --git a/qpdf/qtest/qpdf.test b/qpdf/qtest/qpdf.test
index b4dc07ad..4ff06406 100644
--- a/qpdf/qtest/qpdf.test
+++ b/qpdf/qtest/qpdf.test
@@ -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 ---");
diff --git a/qpdf/qtest/qpdf/aes-forced-check.out b/qpdf/qtest/qpdf/aes-forced-check.out
new file mode 100644
index 00000000..9a71439d
--- /dev/null
+++ b/qpdf/qtest/qpdf/aes-forced-check.out
@@ -0,0 +1,5 @@
+checking b.pdf
+PDF Version: 1.4
+File is not encrypted
+File is not linearized
+No errors found