summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--ChangeLog12
-rw-r--r--TODO8
-rw-r--r--include/qpdf/QPDF.hh6
-rw-r--r--libqpdf/QPDF_encryption.cc16
-rw-r--r--qpdf/qpdf.cc125
-rw-r--r--qpdf/qtest/qpdf.test54
-rw-r--r--qpdf/qtest/qpdf/V4-aes-clearmeta-encryption.out15
-rw-r--r--qpdf/qtest/qpdf/V4-aes-encryption.out15
-rw-r--r--qpdf/qtest/qpdf/V4-clearmeta-encryption.out15
-rw-r--r--qpdf/qtest/qpdf/V4-encryption.out15
-rw-r--r--qpdf/qtest/qpdf/copied-encryption.out3
-rwxr-xr-xqpdf/qtest/qpdf/diff-ignore-ID-version8
-rw-r--r--qpdf/test_driver.cc27
13 files changed, 282 insertions, 37 deletions
diff --git a/ChangeLog b/ChangeLog
index 0c43cd83..1b76b011 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,8 +1,18 @@
2012-07-15 Jay Berkenbilt <ejb@ql.org>
+ * add new QPDF::isEncrypted method that returns some additional
+ information beyond other versions.
+
+ * libqpdf/QPDFWriter.cc: fix copyEncryptionParameters to fix the
+ minimum PDF version based on other file's encryption needs. This
+ is a fix to code added on 2012-07-14 and did not impact previously
+ released code.
+
* libqpdf/QPDFWriter.cc (copyEncryptionParameters): Bug fix: qpdf
was not preserving whether or not AES encryption was being used
- when copying encryption parameters.
+ when copying encryption parameters. The file would still have
+ been properly encrypted, but a file that started off encrypted
+ with AES could have become encrypted with RC4.
2012-07-14 Jay Berkenbilt <ejb@ql.org>
diff --git a/TODO b/TODO
index d38cb002..6c8e043e 100644
--- a/TODO
+++ b/TODO
@@ -64,9 +64,11 @@ Next
- Tests through qpdf command line: copy pages from multiple PDFs
starting with one PDF and also starting with empty.
- * qpdf commandline: provide an option to copy encryption parameters
- from another file, specifying file and password. Search for "Copy
- encryption parameters" in qpdf.test.
+ * Document --copy-encryption and --encryption-file-password in
+ manual. Mention that the first half of /ID as well as all the
+ encryption parameters are copied. Maybe mention about StrF and
+ StrM with respect to AES here and also with encryption
+ preservation.
Soon
diff --git a/include/qpdf/QPDF.hh b/include/qpdf/QPDF.hh
index 4c8ede86..3b72e981 100644
--- a/include/qpdf/QPDF.hh
+++ b/include/qpdf/QPDF.hh
@@ -248,6 +248,12 @@ class QPDF
QPDF_DLL
bool isEncrypted(int& R, int& P);
+ QPDF_DLL
+ bool isEncrypted(int& R, int& P, int& V,
+ encryption_method_e& stream_method,
+ encryption_method_e& string_method,
+ encryption_method_e& file_method);
+
// Encryption permissions -- not enforced by QPDF
QPDF_DLL
bool allowAccessibility();
diff --git a/libqpdf/QPDF_encryption.cc b/libqpdf/QPDF_encryption.cc
index ee5d5685..c73a47bf 100644
--- a/libqpdf/QPDF_encryption.cc
+++ b/libqpdf/QPDF_encryption.cc
@@ -744,14 +744,30 @@ QPDF::isEncrypted() const
bool
QPDF::isEncrypted(int& R, int& P)
{
+ int V;
+ encryption_method_e stream, string, file;
+ return isEncrypted(R, P, V, stream, string, file);
+}
+
+bool
+QPDF::isEncrypted(int& R, int& P, int& V,
+ encryption_method_e& stream_method,
+ encryption_method_e& string_method,
+ encryption_method_e& file_method)
+{
if (this->encrypted)
{
QPDFObjectHandle trailer = getTrailer();
QPDFObjectHandle encrypt = trailer.getKey("/Encrypt");
QPDFObjectHandle Pkey = encrypt.getKey("/P");
QPDFObjectHandle Rkey = encrypt.getKey("/R");
+ QPDFObjectHandle Vkey = encrypt.getKey("/V");
P = Pkey.getIntValue();
R = Rkey.getIntValue();
+ V = Vkey.getIntValue();
+ stream_method = this->cf_stream;
+ string_method = this->cf_stream;
+ file_method = this->cf_file;
return true;
}
else
diff --git a/qpdf/qpdf.cc b/qpdf/qpdf.cc
index 148dff04..cf99ca44 100644
--- a/qpdf/qpdf.cc
+++ b/qpdf/qpdf.cc
@@ -33,18 +33,29 @@ Usage: qpdf [ options ] infilename [ outfilename ]\n\
\n\
An option summary appears below. Please see the documentation for details.\n\
\n\
+Note that when contradictory options are provided, whichever options are\n\
+provided last take precedence.\n\
+\n\
\n\
Basic Options\n\
-------------\n\
\n\
--password=password specify a password for accessing encrypted files\n\
--linearize generated a linearized (web optimized) file\n\
+--copy-encryption=file copy encryption parameters from specified file\n\
+--encryption-file-password=password\n\
+ password used to open the file from which encryption\n\
+ parameters are being copied\n\
--encrypt options -- generate an encrypted file\n\
--decrypt remove any encryption on the file\n\
\n\
-If neither --encrypt or --decrypt are given, qpdf will preserve any\n\
-encryption data associated with a file.\n\
+If none of --copy-encryption, --encrypt or --decrypt are given, qpdf will\n\
+preserve any encryption data associated with a file.\n\
\n\
+Note that when copying encryption parameters from another file, all\n\
+parameters will be copied, including both user and owner passwords, even\n\
+if the user password is used to open the other file. This works even if\n\
+the owner password is not known.\n\
\n\
Encryption Options\n\
------------------\n\
@@ -192,12 +203,39 @@ static std::string show_bool(bool v)
return v ? "allowed" : "not allowed";
}
+static std::string show_encryption_method(QPDF::encryption_method_e method)
+{
+ std::string result = "unknown";
+ switch (method)
+ {
+ case QPDF::e_none:
+ result = "none";
+ break;
+ case QPDF::e_unknown:
+ result = "unknown";
+ break;
+ case QPDF::e_rc4:
+ result = "RC4";
+ break;
+ case QPDF::e_aes:
+ result = "AESv2";
+ break;
+ // no default so gcc will warn for missing case
+ }
+ return result;
+}
+
static void show_encryption(QPDF& pdf)
{
// Extract /P from /Encrypt
int R = 0;
int P = 0;
- if (! pdf.isEncrypted(R, P))
+ int V = 0;
+ QPDF::encryption_method_e stream_method = QPDF::e_unknown;
+ QPDF::encryption_method_e string_method = QPDF::e_unknown;
+ QPDF::encryption_method_e file_method = QPDF::e_unknown;
+ if (! pdf.isEncrypted(R, P, V,
+ stream_method, string_method, file_method))
{
std::cout << "File is not encrypted" << std::endl;
}
@@ -206,25 +244,34 @@ static void show_encryption(QPDF& pdf)
std::cout << "R = " << R << std::endl;
std::cout << "P = " << P << std::endl;
std::string user_password = pdf.getTrimmedUserPassword();
- std::cout << "User password = " << user_password << std::endl;
- std::cout << "extract for accessibility: "
- << show_bool(pdf.allowAccessibility()) << std::endl;
- std::cout << "extract for any purpose: "
- << show_bool(pdf.allowExtractAll()) << std::endl;
- std::cout << "print low resolution: "
- << show_bool(pdf.allowPrintLowRes()) << std::endl;
- std::cout << "print high resolution: "
- << show_bool(pdf.allowPrintHighRes()) << std::endl;
- std::cout << "modify document assembly: "
- << show_bool(pdf.allowModifyAssembly()) << std::endl;
- std::cout << "modify forms: "
- << show_bool(pdf.allowModifyForm()) << std::endl;
- std::cout << "modify annotations: "
- << show_bool(pdf.allowModifyAnnotation()) << std::endl;
- std::cout << "modify other: "
- << show_bool(pdf.allowModifyOther()) << std::endl;
- std::cout << "modify anything: "
+ std::cout << "User password = " << user_password << std::endl
+ << "extract for accessibility: "
+ << show_bool(pdf.allowAccessibility()) << std::endl
+ << "extract for any purpose: "
+ << show_bool(pdf.allowExtractAll()) << std::endl
+ << "print low resolution: "
+ << show_bool(pdf.allowPrintLowRes()) << std::endl
+ << "print high resolution: "
+ << show_bool(pdf.allowPrintHighRes()) << std::endl
+ << "modify document assembly: "
+ << show_bool(pdf.allowModifyAssembly()) << std::endl
+ << "modify forms: "
+ << show_bool(pdf.allowModifyForm()) << std::endl
+ << "modify annotations: "
+ << show_bool(pdf.allowModifyAnnotation()) << std::endl
+ << "modify other: "
+ << show_bool(pdf.allowModifyOther()) << std::endl
+ << "modify anything: "
<< show_bool(pdf.allowModifyAll()) << std::endl;
+ if (V >= 4)
+ {
+ std::cout << "stream encryption method: "
+ << show_encryption_method(stream_method) << std::endl
+ << "string encryption method: "
+ << show_encryption_method(string_method) << std::endl
+ << "file encryption method: "
+ << show_encryption_method(file_method) << std::endl;
+ }
}
}
@@ -579,6 +626,10 @@ int main(int argc, char* argv[])
bool linearize = false;
bool decrypt = false;
+ bool copy_encryption = false;
+ char const* encryption_file = 0;
+ char const* encryption_file_password = "";
+
bool encrypt = false;
std::string user_password;
std::string owner_password;
@@ -664,11 +715,36 @@ int main(int argc, char* argv[])
r3_accessibility, r3_extract, r3_print, r3_modify,
force_V4, cleartext_metadata, use_aes);
encrypt = true;
+ decrypt = false;
+ copy_encryption = false;
}
else if (strcmp(arg, "decrypt") == 0)
{
decrypt = true;
+ encrypt = false;
+ copy_encryption = false;
}
+ else if (strcmp(arg, "copy-encryption") == 0)
+ {
+ if (parameter == 0)
+ {
+ usage("--copy-encryption must be given as"
+ "--copy_encryption=file");
+ }
+ encryption_file = parameter;
+ copy_encryption = true;
+ encrypt = false;
+ decrypt = false;
+ }
+ else if (strcmp(arg, "encryption-file-password") == 0)
+ {
+ if (parameter == 0)
+ {
+ usage("--encryption-file-password must be given as"
+ "--encryption-file-password=password");
+ }
+ encryption_file_password = parameter;
+ }
else if (strcmp(arg, "stream-data") == 0)
{
if (parameter == 0)
@@ -865,6 +941,7 @@ int main(int argc, char* argv[])
try
{
QPDF pdf;
+ QPDF encryption_pdf;
if (ignore_xref_streams)
{
pdf.setIgnoreXRefStreams(true);
@@ -1082,6 +1159,12 @@ int main(int argc, char* argv[])
{
w.setSuppressOriginalObjectIDs(true);
}
+ if (copy_encryption)
+ {
+ encryption_pdf.processFile(
+ encryption_file, encryption_file_password);
+ w.copyEncryptionParameters(encryption_pdf);
+ }
if (encrypt)
{
if (keylen == 40)
diff --git a/qpdf/qtest/qpdf.test b/qpdf/qtest/qpdf.test
index 8687d713..1b979724 100644
--- a/qpdf/qtest/qpdf.test
+++ b/qpdf/qtest/qpdf.test
@@ -1271,7 +1271,7 @@ $td->runtest("linearize and encrypt file",
$td->EXIT_STATUS => 0});
$td->runtest("check encryption",
{$td->COMMAND => "qpdf --show-encryption --password=owner a.pdf",
- $td->FILTER => "grep -v allowed"},
+ $td->FILTER => "grep -v allowed | grep -v method"},
{$td->STRING => "R = 4\nP = -4\nUser password = user\n",
$td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
@@ -1290,7 +1290,7 @@ $td->runtest("encrypt with AES",
{$td->STRING => "", $td->EXIT_STATUS => 0});
$td->runtest("check encryption",
{$td->COMMAND => "qpdf --show-encryption a.pdf",
- $td->FILTER => "grep -v allowed"},
+ $td->FILTER => "grep -v allowed | grep -v method"},
{$td->STRING => "R = 4\nP = -4\nUser password = \n",
$td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
@@ -1311,7 +1311,7 @@ $td->runtest("linearize with AES and object streams",
{$td->STRING => "", $td->EXIT_STATUS => 0});
$td->runtest("check encryption",
{$td->COMMAND => "qpdf --show-encryption a.pdf",
- $td->FILTER => "grep -v allowed"},
+ $td->FILTER => "grep -v allowed | grep -v method"},
{$td->STRING => "R = 4\nP = -4\nUser password = \n",
$td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
@@ -1345,7 +1345,7 @@ $td->runtest("make sure there is no xref stream",
$td->NORMALIZE_NEWLINES);
# Look at some actual V4 files
-$n_tests += 10;
+$n_tests += 14;
foreach my $d (['--force-V4', 'V4'],
['--cleartext-metadata', 'V4-clearmeta'],
['--use-aes=y', 'V4-aes'],
@@ -1359,6 +1359,10 @@ foreach my $d (['--force-V4', 'V4'],
$td->runtest("check output",
{$td->FILE => "a.pdf"},
{$td->FILE => "$out.pdf"});
+ $td->runtest("show encryption",
+ {$td->COMMAND => "qpdf --show-encryption a.pdf"},
+ {$td->FILE => "$out-encryption.out", $td->EXIT_STATUS => 0},
+ $td->NORMALIZE_NEWLINES);
}
# Crypt Filter
$td->runtest("decrypt with crypt filter",
@@ -1370,7 +1374,11 @@ $td->runtest("check output",
{$td->FILE => 'decrypted-crypt-filter.pdf'});
# Copy encryption parameters
-$n_tests += 3;
+$n_tests += 10;
+$td->runtest("create reference qdf",
+ {$td->COMMAND =>
+ "qpdf --qdf --no-original-object-ids minimal.pdf a.qdf"},
+ {$td->STRING => "", $td->EXIT_STATUS => 0});
$td->runtest("create encrypted file",
{$td->COMMAND =>
"qpdf --encrypt user owner 128 --use-aes=y --extract=n --" .
@@ -1380,11 +1388,42 @@ $td->runtest("copy encryption parameters",
{$td->COMMAND => "test_driver 30 minimal.pdf a.pdf"},
{$td->STRING => "test 30 done\n", $td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
-$td->runtest("checkout encryption",
+$td->runtest("check output encryption",
{$td->COMMAND => "qpdf --show-encryption b.pdf --password=owner"},
{$td->FILE => "copied-encryption.out",
$td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
+$td->runtest("convert to qdf",
+ {$td->COMMAND =>
+ "qpdf --qdf b.pdf b.qdf" .
+ " --password=owner --no-original-object-ids"},
+ {$td->STRING => "", $td->EXIT_STATUS => 0});
+$td->runtest("compare qdf",
+ {$td->COMMAND => "./diff-ignore-ID-version a.qdf b.qdf"},
+ {$td->STRING => "okay\n", $td->EXIT_STATUS => 0},
+ $td->NORMALIZE_NEWLINES);
+$td->runtest("copy encryption with qpdf",
+ {$td->COMMAND =>
+ "qpdf --copy-encryption=a.pdf".
+ " --encryption-file-password=user" .
+ " minimal.pdf c.pdf"},
+ {$td->STRING => "", $td->EXIT_STATUS => 0},
+ $td->NORMALIZE_NEWLINES);
+$td->runtest("check output encryption",
+ {$td->COMMAND => "qpdf --show-encryption c.pdf --password=owner"},
+ {$td->FILE => "copied-encryption.out",
+ $td->EXIT_STATUS => 0},
+ $td->NORMALIZE_NEWLINES);
+$td->runtest("convert to qdf",
+ {$td->COMMAND =>
+ "qpdf --qdf c.pdf c.qdf" .
+ " --password=owner --no-original-object-ids"},
+ {$td->STRING => "", $td->EXIT_STATUS => 0});
+$td->runtest("compare qdf",
+ {$td->COMMAND => "./diff-ignore-ID-version a.qdf c.qdf"},
+ {$td->STRING => "okay\n", $td->EXIT_STATUS => 0},
+ $td->NORMALIZE_NEWLINES);
+
show_ntests();
# ----------
@@ -1753,6 +1792,5 @@ sub get_md5_checksum
sub cleanup
{
- system("rm -rf *.ps *.pnm a.pdf a.qdf b.pdf b.qdf c.pdf" .
- " *.enc* tif1 tif2 tiff-cache");
+ system("rm -rf *.ps *.pnm ?.pdf ?.qdf *.enc* tif1 tif2 tiff-cache");
}
diff --git a/qpdf/qtest/qpdf/V4-aes-clearmeta-encryption.out b/qpdf/qtest/qpdf/V4-aes-clearmeta-encryption.out
new file mode 100644
index 00000000..928818dc
--- /dev/null
+++ b/qpdf/qtest/qpdf/V4-aes-clearmeta-encryption.out
@@ -0,0 +1,15 @@
+R = 4
+P = -4
+User password =
+extract for accessibility: allowed
+extract for any purpose: allowed
+print low resolution: allowed
+print high resolution: allowed
+modify document assembly: allowed
+modify forms: allowed
+modify annotations: allowed
+modify other: allowed
+modify anything: allowed
+stream encryption method: AESv2
+string encryption method: AESv2
+file encryption method: AESv2
diff --git a/qpdf/qtest/qpdf/V4-aes-encryption.out b/qpdf/qtest/qpdf/V4-aes-encryption.out
new file mode 100644
index 00000000..928818dc
--- /dev/null
+++ b/qpdf/qtest/qpdf/V4-aes-encryption.out
@@ -0,0 +1,15 @@
+R = 4
+P = -4
+User password =
+extract for accessibility: allowed
+extract for any purpose: allowed
+print low resolution: allowed
+print high resolution: allowed
+modify document assembly: allowed
+modify forms: allowed
+modify annotations: allowed
+modify other: allowed
+modify anything: allowed
+stream encryption method: AESv2
+string encryption method: AESv2
+file encryption method: AESv2
diff --git a/qpdf/qtest/qpdf/V4-clearmeta-encryption.out b/qpdf/qtest/qpdf/V4-clearmeta-encryption.out
new file mode 100644
index 00000000..157fb8da
--- /dev/null
+++ b/qpdf/qtest/qpdf/V4-clearmeta-encryption.out
@@ -0,0 +1,15 @@
+R = 4
+P = -4
+User password =
+extract for accessibility: allowed
+extract for any purpose: allowed
+print low resolution: allowed
+print high resolution: allowed
+modify document assembly: allowed
+modify forms: allowed
+modify annotations: allowed
+modify other: allowed
+modify anything: allowed
+stream encryption method: RC4
+string encryption method: RC4
+file encryption method: RC4
diff --git a/qpdf/qtest/qpdf/V4-encryption.out b/qpdf/qtest/qpdf/V4-encryption.out
new file mode 100644
index 00000000..157fb8da
--- /dev/null
+++ b/qpdf/qtest/qpdf/V4-encryption.out
@@ -0,0 +1,15 @@
+R = 4
+P = -4
+User password =
+extract for accessibility: allowed
+extract for any purpose: allowed
+print low resolution: allowed
+print high resolution: allowed
+modify document assembly: allowed
+modify forms: allowed
+modify annotations: allowed
+modify other: allowed
+modify anything: allowed
+stream encryption method: RC4
+string encryption method: RC4
+file encryption method: RC4
diff --git a/qpdf/qtest/qpdf/copied-encryption.out b/qpdf/qtest/qpdf/copied-encryption.out
index 6db6fa63..cbec2305 100644
--- a/qpdf/qtest/qpdf/copied-encryption.out
+++ b/qpdf/qtest/qpdf/copied-encryption.out
@@ -10,3 +10,6 @@ modify forms: allowed
modify annotations: allowed
modify other: allowed
modify anything: allowed
+stream encryption method: AESv2
+string encryption method: AESv2
+file encryption method: AESv2
diff --git a/qpdf/qtest/qpdf/diff-ignore-ID-version b/qpdf/qtest/qpdf/diff-ignore-ID-version
new file mode 100755
index 00000000..e6b33470
--- /dev/null
+++ b/qpdf/qtest/qpdf/diff-ignore-ID-version
@@ -0,0 +1,8 @@
+#!/bin/sh
+lines=$(expr + $(diff $1 $2 | egrep '^[<>]' | \
+ egrep -v '/ID' | egrep -v '%PDF-' | wc -l))
+if [ "$lines" = "0" ]; then
+ echo okay
+else
+ diff -a -U 0 $1 $2
+fi
diff --git a/qpdf/test_driver.cc b/qpdf/test_driver.cc
index 518e5569..3d2f0dca 100644
--- a/qpdf/test_driver.cc
+++ b/qpdf/test_driver.cc
@@ -58,12 +58,17 @@ class Provider: public QPDFObjectHandle::StreamDataProvider
bool bad_length;
};
-static void checkPageContents(QPDFObjectHandle page,
- std::string const& wanted_string)
+static std::string getPageContents(QPDFObjectHandle page)
{
PointerHolder<Buffer> b1 =
page.getKey("/Contents").getStreamData();
- std::string contents = std::string((char *)(b1->getBuffer()));
+ return std::string((char *)(b1->getBuffer()), b1->getSize()) + "\0";
+}
+
+static void checkPageContents(QPDFObjectHandle page,
+ std::string const& wanted_string)
+{
+ std::string contents = getPageContents(page);
if (contents.find(wanted_string) == std::string::npos)
{
std::cout << "didn't find " << wanted_string << " in "
@@ -1030,10 +1035,24 @@ void runtest(int n, char const* filename1, char const* filename2)
QPDF encrypted;
encrypted.processFile(filename2, "user");
QPDFWriter w(pdf, "b.pdf");
- w.setStaticID(true);
w.setStreamDataMode(qpdf_s_preserve);
w.copyEncryptionParameters(encrypted);
w.write();
+
+ // Make sure the contents are actually the same
+ QPDF final;
+ final.processFile("b.pdf", "user");
+ std::vector<QPDFObjectHandle> pages = pdf.getAllPages();
+ std::string orig_contents = getPageContents(pages[0]);
+ pages = final.getAllPages();
+ std::string new_contents = getPageContents(pages[0]);
+ if (orig_contents != new_contents)
+ {
+ std::cout << "oops -- page contents don't match" << std::endl
+ << "original:\n" << orig_contents
+ << "new:\n" << new_contents
+ << std::endl;
+ }
}
else
{