Commit 09175e457852c585a68a86d43280f7e0790a4a3b

Authored by Jay Berkenbilt
1 parent 94131116

more testing, bug fix for linearized aes encrypted files

git-svn-id: svn+q:///qpdf/trunk@824 71b93d88-0707-0410-a8cf-f5a4172ac649
ChangeLog
1 2009-10-18 Jay Berkenbilt <ejb@ql.org> 1 2009-10-18 Jay Berkenbilt <ejb@ql.org>
2 2
  3 + * If forcing version, disable object stream creation and/or
  4 + encryption if previous specifications are incompatible with new
  5 + version. It is still possible that PDF content, compression
  6 + schemes, etc., may be incompatible with the new version, but at
  7 + least this way, older viewers will at least have a chance.
  8 +
3 * libqpdf/QPDFWriter.cc (unparseObject): avoid compressing 9 * libqpdf/QPDFWriter.cc (unparseObject): avoid compressing
4 Metadata streams if possible. 10 Metadata streams if possible.
5 11
1 2.1 1 2.1
2 === 2 ===
3 3
  4 + * Really need to handle /Crypt filter for Metadata. Search for crypt
  5 + below.
  6 +
4 * Update documentation to reflect new command line flags and any 7 * Update documentation to reflect new command line flags and any
5 other relevant changes. Should read through ChangeLog and the 8 other relevant changes. Should read through ChangeLog and the
6 manual before releasing 2.1. 9 manual before releasing 2.1.
@@ -16,9 +19,6 @@ @@ -16,9 +19,6 @@
16 * Add comments for the security functions that map them back to the 19 * Add comments for the security functions that map them back to the
17 items in Adobe's products. 20 items in Adobe's products.
18 21
19 - * Have force version at least turn off object streams and maybe  
20 - change security settings?  
21 -  
22 * Add error codes to QPDFException. Change the error interface so 22 * Add error codes to QPDFException. Change the error interface so
23 that warnings and errors are pointers that can be queried using 23 that warnings and errors are pointers that can be queried using
24 more C API functions. We need a way to get a full string as well 24 more C API functions. We need a way to get a full string as well
@@ -47,49 +47,7 @@ @@ -47,49 +47,7 @@
47 47
48 - Update C API for R4 encryption 48 - Update C API for R4 encryption
49 49
50 - - When we write encrypted files, we must remember to omit any  
51 - encryption filter settings from original streams.  
52 -  
53 - - test various combinations with and without cleartext-metadata  
54 - and aes in compression tests  
55 -  
56 - - figure out a way to test crypt filters defined on a stream  
57 -  
58 - - test combinations of linearization and v4 encryption  
59 -  
60 - - would be nice to test strings and streams with different  
61 - encryption types, but without sample data, we'd have to write  
62 - them ourselves which is not that useful  
63 -  
64 - - figure out how to look at the metadata so I can tell whether  
65 - /EncryptMetadata is working the way it's supposed to  
66 -  
67 - - Do something with embedded files, but what and how?  
68 50
69 - - General notes:  
70 -  
71 - /CF - keys are crypt filter names, values are are crypt  
72 - dictionaries  
73 -  
74 - Individual streams may also have crypt filters. Filter type  
75 - /Crypt; /DecodeParms must contain a Crypt filter decode  
76 - parameters dictionary whose /Name entry specifies the particular  
77 - filter to be used. If /Name is missing, use /Identity.  
78 - /DecodeParms << /Crypt << /Name /XYZ >> >> where /XYZ is  
79 - /Identity or a key in /CF.  
80 -  
81 - /Identity means not to encrypt.  
82 -  
83 - Crypt Dictionaries  
84 -  
85 - /Type (optional) /CryptFilter  
86 - /CFM:  
87 - /V2 - use rc4  
88 - /AESV2 - use aes  
89 - /Length - supposed to be key length, but the one file I have  
90 - has a bogus value for it, so I'm ignoring it.  
91 -  
92 - We will ignore remaining fields and values.  
93 51
94 2.2 52 2.2
95 === 53 ===
@@ -127,7 +85,13 @@ General @@ -127,7 +85,13 @@ General
127 crypt filters, and there are already special cases in the code to 85 crypt filters, and there are already special cases in the code to
128 handle those. Most likely, it won't be a problem, but someday 86 handle those. Most likely, it won't be a problem, but someday
129 someone may find a file that qpdf doesn't work on because of crypt 87 someone may find a file that qpdf doesn't work on because of crypt
130 - filters. 88 + filters. There is an example in the spec of using a crypt filter
  89 + on a metadata stream.
  90 +
  91 + When we write encrypted files, we must remember to omit any
  92 + encryption filter settings from original streams.
  93 +
  94 + We need a way to test this.
131 95
132 * The second xref stream for linearized files has to be padded only 96 * The second xref stream for linearized files has to be padded only
133 because we need file_size as computed in pass 1 to be accurate. If 97 because we need file_size as computed in pass 1 to be accurate. If
include/qpdf/QPDFWriter.hh
@@ -98,6 +98,12 @@ class DLL_EXPORT QPDFWriter @@ -98,6 +98,12 @@ class DLL_EXPORT QPDFWriter
98 // you are sure the PDF file in question has no features of newer 98 // you are sure the PDF file in question has no features of newer
99 // versions of PDF or if you are willing to create files that old 99 // versions of PDF or if you are willing to create files that old
100 // viewers may try to open but not be able to properly interpret. 100 // viewers may try to open but not be able to properly interpret.
  101 + // If any encryption has been applied to the document either
  102 + // explicitly or by preserving the encryption of the source
  103 + // document, forcing the PDF version to a value too low to support
  104 + // that type of encryption will explicitly disable decryption.
  105 + // Additionally, forcing to a version below 1.5 will disable
  106 + // object streams.
101 void forcePDFVersion(std::string const&); 107 void forcePDFVersion(std::string const&);
102 108
103 // Cause a static /ID value to be generated. Use only in test 109 // Cause a static /ID value to be generated. Use only in test
@@ -193,6 +199,7 @@ class DLL_EXPORT QPDFWriter @@ -193,6 +199,7 @@ class DLL_EXPORT QPDFWriter
193 char const* user_password, char const* owner_password, 199 char const* user_password, char const* owner_password,
194 bool allow_accessibility, bool allow_extract, 200 bool allow_accessibility, bool allow_extract,
195 r3_print_e print, r3_modify_e modify); 201 r3_print_e print, r3_modify_e modify);
  202 + void disableIncompatbleEncryption(float v);
196 void setEncryptionParameters( 203 void setEncryptionParameters(
197 char const* user_password, char const* owner_password, 204 char const* user_password, char const* owner_password,
198 int V, int R, int key_len, std::set<int>& bits_to_clear); 205 int V, int R, int key_len, std::set<int>& bits_to_clear);
libqpdf/QPDFWriter.cc
@@ -345,6 +345,52 @@ QPDFWriter::copyEncryptionParameters() @@ -345,6 +345,52 @@ QPDFWriter::copyEncryptionParameters()
345 } 345 }
346 346
347 void 347 void
  348 +QPDFWriter::disableIncompatbleEncryption(float v)
  349 +{
  350 + if (! this->encrypted)
  351 + {
  352 + return;
  353 + }
  354 +
  355 + bool disable = false;
  356 + if (v < 1.3)
  357 + {
  358 + disable = true;
  359 + }
  360 + else
  361 + {
  362 + int V = atoi(encryption_dictionary["/V"].c_str());
  363 + int R = atoi(encryption_dictionary["/R"].c_str());
  364 + if (v < 1.4)
  365 + {
  366 + if ((V > 1) || (R > 2))
  367 + {
  368 + disable = true;
  369 + }
  370 + }
  371 + else if (v < 1.5)
  372 + {
  373 + if ((V > 2) || (R > 3))
  374 + {
  375 + disable = true;
  376 + }
  377 + }
  378 + else if (v < 1.6)
  379 + {
  380 + if (this->encrypt_use_aes)
  381 + {
  382 + disable = true;
  383 + }
  384 + }
  385 + }
  386 + if (disable)
  387 + {
  388 + QTC::TC("qpdf", "QPDFWriter forced version disabled encryption");
  389 + this->encrypted = false;
  390 + }
  391 +}
  392 +
  393 +void
348 QPDFWriter::setEncryptionParametersInternal( 394 QPDFWriter::setEncryptionParametersInternal(
349 int V, int R, int key_len, long P, 395 int V, int R, int key_len, long P,
350 std::string const& O, std::string const& U, 396 std::string const& O, std::string const& U,
@@ -965,7 +1011,7 @@ QPDFWriter::unparseObject(QPDFObjectHandle object, int level, @@ -965,7 +1011,7 @@ QPDFWriter::unparseObject(QPDFObjectHandle object, int level,
965 Buffer* buf = bufpl.getBuffer(); 1011 Buffer* buf = bufpl.getBuffer();
966 val = QPDF_String( 1012 val = QPDF_String(
967 std::string((char*)buf->getBuffer(), 1013 std::string((char*)buf->getBuffer(),
968 - (size_t)buf->getSize())).unparse(); 1014 + (size_t)buf->getSize())).unparse(true);
969 delete buf; 1015 delete buf;
970 } 1016 }
971 else 1017 else
@@ -1423,6 +1469,17 @@ QPDFWriter::write() @@ -1423,6 +1469,17 @@ QPDFWriter::write()
1423 copyEncryptionParameters(); 1469 copyEncryptionParameters();
1424 } 1470 }
1425 1471
  1472 + if (! this->forced_pdf_version.empty())
  1473 + {
  1474 + float v = atof(this->forced_pdf_version.c_str());
  1475 + disableIncompatbleEncryption(v);
  1476 + if (v < 1.5)
  1477 + {
  1478 + QTC::TC("qpdf", "QPDFWriter forcing object stream disable");
  1479 + this->object_stream_mode = o_disable;
  1480 + }
  1481 + }
  1482 +
1426 if (this->qdf_mode || this->normalize_content || 1483 if (this->qdf_mode || this->normalize_content ||
1427 (this->stream_data_mode == s_uncompress)) 1484 (this->stream_data_mode == s_uncompress))
1428 { 1485 {
qpdf/qpdf.testcov
@@ -166,4 +166,5 @@ QPDF_encryption CFM AESV2 0 @@ -166,4 +166,5 @@ QPDF_encryption CFM AESV2 0
166 QPDF_encryption aes decode string 0 166 QPDF_encryption aes decode string 0
167 QPDF_encryption cleartext metadata 0 167 QPDF_encryption cleartext metadata 0
168 QPDF_encryption aes decode stream 0 168 QPDF_encryption aes decode stream 0
169 -QPDF_encryption stream crypt filter 0 169 +QPDFWriter forcing object stream disable 0
  170 +QPDFWriter forced version disabled encryption 0
qpdf/qtest/qpdf.test
@@ -998,14 +998,14 @@ $td-&gt;runtest(&quot;check linearization&quot;, @@ -998,14 +998,14 @@ $td-&gt;runtest(&quot;check linearization&quot;,
998 $td->NORMALIZE_NEWLINES); 998 $td->NORMALIZE_NEWLINES);
999 $td->runtest("linearize and encrypt file", 999 $td->runtest("linearize and encrypt file",
1000 {$td->COMMAND => 1000 {$td->COMMAND =>
1001 - "qpdf --linearize --encrypt user owner 128 --" . 1001 + "qpdf --linearize --encrypt user owner 128 --use-aes=y --" .
1002 " lin-special.pdf a.pdf"}, 1002 " lin-special.pdf a.pdf"},
1003 {$td->STRING => "", 1003 {$td->STRING => "",
1004 $td->EXIT_STATUS => 0}); 1004 $td->EXIT_STATUS => 0});
1005 $td->runtest("check encryption", 1005 $td->runtest("check encryption",
1006 {$td->COMMAND => "qpdf --show-encryption --password=owner a.pdf", 1006 {$td->COMMAND => "qpdf --show-encryption --password=owner a.pdf",
1007 $td->FILTER => "grep -v allowed"}, 1007 $td->FILTER => "grep -v allowed"},
1008 - {$td->STRING => "R = 3\nP = -4\nUser password = user\n", 1008 + {$td->STRING => "R = 4\nP = -4\nUser password = user\n",
1009 $td->EXIT_STATUS => 0}, 1009 $td->EXIT_STATUS => 0},
1010 $td->NORMALIZE_NEWLINES); 1010 $td->NORMALIZE_NEWLINES);
1011 $td->runtest("check linearization", 1011 $td->runtest("check linearization",
@@ -1015,6 +1015,68 @@ $td-&gt;runtest(&quot;check linearization&quot;, @@ -1015,6 +1015,68 @@ $td-&gt;runtest(&quot;check linearization&quot;,
1015 $td->EXIT_STATUS => 0}, 1015 $td->EXIT_STATUS => 0},
1016 $td->NORMALIZE_NEWLINES); 1016 $td->NORMALIZE_NEWLINES);
1017 1017
  1018 +# Test AES encryption in various ways.
  1019 +$n_tests += 14;
  1020 +$td->runtest("encrypt with AES",
  1021 + {$td->COMMAND => "qpdf --encrypt '' '' 128 --use-aes=y --" .
  1022 + " enc-base.pdf a.pdf"},
  1023 + {$td->STRING => "", $td->EXIT_STATUS => 0});
  1024 +$td->runtest("check encryption",
  1025 + {$td->COMMAND => "qpdf --show-encryption a.pdf",
  1026 + $td->FILTER => "grep -v allowed"},
  1027 + {$td->STRING => "R = 4\nP = -4\nUser password = \n",
  1028 + $td->EXIT_STATUS => 0},
  1029 + $td->NORMALIZE_NEWLINES);
  1030 +$td->runtest("convert original to qdf",
  1031 + {$td->COMMAND => "qpdf --static-id --no-original-object-ids" .
  1032 + " --qdf --min-version=1.6 enc-base.pdf a.qdf"},
  1033 + {$td->STRING => "", $td->EXIT_STATUS => 0});
  1034 +$td->runtest("convert encrypted to qdf",
  1035 + {$td->COMMAND => "qpdf --static-id --no-original-object-ids" .
  1036 + " --qdf a.pdf b.qdf"},
  1037 + {$td->STRING => "", $td->EXIT_STATUS => 0});
  1038 +$td->runtest("compare files",
  1039 + {$td->FILE => 'a.qdf'},
  1040 + {$td->FILE => 'b.qdf'});
  1041 +$td->runtest("linearize with AES and object streams",
  1042 + {$td->COMMAND => "qpdf --encrypt '' '' 128 --use-aes=y --" .
  1043 + " --linearize --object-streams=generate enc-base.pdf a.pdf"},
  1044 + {$td->STRING => "", $td->EXIT_STATUS => 0});
  1045 +$td->runtest("check encryption",
  1046 + {$td->COMMAND => "qpdf --show-encryption a.pdf",
  1047 + $td->FILTER => "grep -v allowed"},
  1048 + {$td->STRING => "R = 4\nP = -4\nUser password = \n",
  1049 + $td->EXIT_STATUS => 0},
  1050 + $td->NORMALIZE_NEWLINES);
  1051 +$td->runtest("linearize original",
  1052 + {$td->COMMAND => "qpdf --linearize --object-streams=generate" .
  1053 + " enc-base.pdf b.pdf"},
  1054 + {$td->STRING => "", $td->EXIT_STATUS => 0});
  1055 +$td->runtest("convert linearized original to qdf",
  1056 + {$td->COMMAND => "qpdf --static-id --no-original-object-ids" .
  1057 + " --qdf --object-streams=generate --min-version=1.6" .
  1058 + " b.pdf a.qdf"},
  1059 + {$td->STRING => "", $td->EXIT_STATUS => 0});
  1060 +$td->runtest("convert encrypted to qdf",
  1061 + {$td->COMMAND => "qpdf --static-id --no-original-object-ids" .
  1062 + " --qdf --object-streams=generate a.pdf b.qdf"},
  1063 + {$td->STRING => "", $td->EXIT_STATUS => 0});
  1064 +$td->runtest("compare files",
  1065 + {$td->FILE => 'a.qdf'},
  1066 + {$td->FILE => 'b.qdf'});
  1067 +$td->runtest("force version on aes encrypted",
  1068 + {$td->COMMAND => "qpdf --force-version=1.4 a.pdf b.pdf"},
  1069 + {$td->STRING => "", $td->EXIT_STATUS => 0});
  1070 +$td->runtest("check",
  1071 + {$td->COMMAND => "qpdf --check b.pdf"},
  1072 + {$td->FILE => "aes-forced-check.out",
  1073 + $td->EXIT_STATUS => 0},
  1074 + $td->NORMALIZE_NEWLINES);
  1075 +$td->runtest("make sure there is no xref stream",
  1076 + {$td->COMMAND => "grep /ObjStm b.pdf | wc -l"},
  1077 + {$td->REGEXP => "\\s*0\\s*", $td->EXIT_STATUS => 0},
  1078 + $td->NORMALIZE_NEWLINES);
  1079 +
1018 show_ntests(); 1080 show_ntests();
1019 # ---------- 1081 # ----------
1020 $td->notify("--- Content Preservation Tests ---"); 1082 $td->notify("--- Content Preservation Tests ---");
qpdf/qtest/qpdf/aes-forced-check.out 0 → 100644
  1 +checking b.pdf
  2 +PDF Version: 1.4
  3 +File is not encrypted
  4 +File is not linearized
  5 +No errors found