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 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 9 * libqpdf/QPDFWriter.cc (unparseObject): avoid compressing
4 10 Metadata streams if possible.
5 11  
... ...
1 1 2.1
2 2 ===
3 3  
  4 + * Really need to handle /Crypt filter for Metadata. Search for crypt
  5 + below.
  6 +
4 7 * Update documentation to reflect new command line flags and any
5 8 other relevant changes. Should read through ChangeLog and the
6 9 manual before releasing 2.1.
... ... @@ -16,9 +19,6 @@
16 19 * Add comments for the security functions that map them back to the
17 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 22 * Add error codes to QPDFException. Change the error interface so
23 23 that warnings and errors are pointers that can be queried using
24 24 more C API functions. We need a way to get a full string as well
... ... @@ -47,49 +47,7 @@
47 47  
48 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 52 2.2
95 53 ===
... ... @@ -127,7 +85,13 @@ General
127 85 crypt filters, and there are already special cases in the code to
128 86 handle those. Most likely, it won't be a problem, but someday
129 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 96 * The second xref stream for linearized files has to be padded only
133 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 98 // you are sure the PDF file in question has no features of newer
99 99 // versions of PDF or if you are willing to create files that old
100 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 107 void forcePDFVersion(std::string const&);
102 108  
103 109 // Cause a static /ID value to be generated. Use only in test
... ... @@ -193,6 +199,7 @@ class DLL_EXPORT QPDFWriter
193 199 char const* user_password, char const* owner_password,
194 200 bool allow_accessibility, bool allow_extract,
195 201 r3_print_e print, r3_modify_e modify);
  202 + void disableIncompatbleEncryption(float v);
196 203 void setEncryptionParameters(
197 204 char const* user_password, char const* owner_password,
198 205 int V, int R, int key_len, std::set<int>& bits_to_clear);
... ...
libqpdf/QPDFWriter.cc
... ... @@ -345,6 +345,52 @@ QPDFWriter::copyEncryptionParameters()
345 345 }
346 346  
347 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 394 QPDFWriter::setEncryptionParametersInternal(
349 395 int V, int R, int key_len, long P,
350 396 std::string const& O, std::string const& U,
... ... @@ -965,7 +1011,7 @@ QPDFWriter::unparseObject(QPDFObjectHandle object, int level,
965 1011 Buffer* buf = bufpl.getBuffer();
966 1012 val = QPDF_String(
967 1013 std::string((char*)buf->getBuffer(),
968   - (size_t)buf->getSize())).unparse();
  1014 + (size_t)buf->getSize())).unparse(true);
969 1015 delete buf;
970 1016 }
971 1017 else
... ... @@ -1423,6 +1469,17 @@ QPDFWriter::write()
1423 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 1483 if (this->qdf_mode || this->normalize_content ||
1427 1484 (this->stream_data_mode == s_uncompress))
1428 1485 {
... ...
qpdf/qpdf.testcov
... ... @@ -166,4 +166,5 @@ QPDF_encryption CFM AESV2 0
166 166 QPDF_encryption aes decode string 0
167 167 QPDF_encryption cleartext metadata 0
168 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 998 $td->NORMALIZE_NEWLINES);
999 999 $td->runtest("linearize and encrypt file",
1000 1000 {$td->COMMAND =>
1001   - "qpdf --linearize --encrypt user owner 128 --" .
  1001 + "qpdf --linearize --encrypt user owner 128 --use-aes=y --" .
1002 1002 " lin-special.pdf a.pdf"},
1003 1003 {$td->STRING => "",
1004 1004 $td->EXIT_STATUS => 0});
1005 1005 $td->runtest("check encryption",
1006 1006 {$td->COMMAND => "qpdf --show-encryption --password=owner a.pdf",
1007 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 1009 $td->EXIT_STATUS => 0},
1010 1010 $td->NORMALIZE_NEWLINES);
1011 1011 $td->runtest("check linearization",
... ... @@ -1015,6 +1015,68 @@ $td-&gt;runtest(&quot;check linearization&quot;,
1015 1015 $td->EXIT_STATUS => 0},
1016 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 1080 show_ntests();
1019 1081 # ----------
1020 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
... ...