Commit b873dc9c59f4445239f68d7138aebb53cc0df648

Authored by Jay Berkenbilt
1 parent 5c253d1c

implemented writing R4/V4 encryption except that the output files don't open in Adobe reader 9.1.3.

git-svn-id: svn+q:///qpdf/trunk@816 71b93d88-0707-0410-a8cf-f5a4172ac649
... ... @@ -43,48 +43,53 @@
43 43 (http://delphi.about.com). .. use at your own risk and for whatever
44 44 the purpose you want .. no support provided. Sample code provided."
45 45  
46   - * Implement as much of R = 4 encryption as possible. Already able to
47   - decode AES-128-CBC and check passwords.
  46 + * R = 4, V = 4 encryption.
48 47  
49   - aes test suite: use fips-197 test vector with cbc disabled; encrypt
50   - and decrypt some other files including multiples of 16 and not to
51   - test cbc mode.
  48 + - Update C API for R4 encryption
52 49  
53   - /Encrypt keys (if V == 4)
  50 + - When we write encrypted files, we must remember to omit any
  51 + encryption filter settings from original streams.
54 52  
55   - /StmF - name of crypt filter for streams; default /Identity
56   - /StrF - name of crypt filter for strings; default /Identity
57   - /EFF - crypt filter for embedded files without their own crypt
58   - filters; default is to use /StmF
  53 + - test various combinations with and without cleartext-metadata
  54 + and aes in compression tests
59 55  
60   - /CF - keys are crypt filter names, values are are crypt
61   - dictionaries
  56 + - figure out a way to test crypt filters defined on a stream
62 57  
63   - Individual streams may also have crypt filters. Filter type
64   - /Crypt; /DecodeParms must contain a Crypt filter decode
65   - parameters dictionary whose /Name entry specifies the particular
66   - filter to be used. If /Name is missing, use /Identity.
67   - /DecodeParms << /Crypt << /Name /XYZ >> >> where /XYZ is
68   - /Identity or a key in /CF.
  58 + - would be nice to test strings and streams with different
  59 + encryption types, but without sample data, we'd have to write
  60 + them ourselves which is not that useful
69 61  
70   - /Identity means not to encrypt.
  62 + - figure out why xpdf can open my files but not acroread
71 63  
72   - Crypt Dictionaries
  64 + - figure out how to look at the metadata so I can tell whether
  65 + /EncryptMetadata is working the way it's supposed to
73 66  
74   - /Type (optional) /CryptFilter
75   - /CFM:
76   - /V2 - use rc4
77   - /AESV2 - use aes
78   - /Length - supposed to be key length, but the one file I have
79   - has a bogus value for it, so I'm ignoring it.
  67 + - Do something with embedded files, but what and how?
80 68  
81   - We will ignore remaining fields and values.
  69 + - General notes:
82 70  
83   - Remember to honor /EncryptMetadata; applies to streams of /Type
84   - /Metadata
  71 + /CF - keys are crypt filter names, values are are crypt
  72 + dictionaries
85 73  
86   - When we write encrypted files, we must remember to omit any
87   - encryption filter settings from original streams.
  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.
88 93  
89 94 2.2
90 95 ===
... ...
include/qpdf/QPDFWriter.hh
... ... @@ -118,9 +118,10 @@ class DLL_EXPORT QPDFWriter
118 118  
119 119 // Set up for encrypted output. Disables stream prefiltering and
120 120 // content normalization. Note that setting R2 encryption
121   - // parameters sets the PDF version to at least 1.3, and setting R3
  121 + // parameters sets the PDF version to at least 1.3, setting R3
122 122 // encryption parameters pushes the PDF version number to at least
123   - // 1.4.
  123 + // 1.4, and setting R4 parameters pushes the version to at least
  124 + // 1.5, or if AES is used, 1.6.
124 125 void setR2EncryptionParameters(
125 126 char const* user_password, char const* owner_password,
126 127 bool allow_print, bool allow_modify,
... ... @@ -143,6 +144,11 @@ class DLL_EXPORT QPDFWriter
143 144 char const* user_password, char const* owner_password,
144 145 bool allow_accessibility, bool allow_extract,
145 146 r3_print_e print, r3_modify_e modify);
  147 + void setR4EncryptionParameters(
  148 + char const* user_password, char const* owner_password,
  149 + bool allow_accessibility, bool allow_extract,
  150 + r3_print_e print, r3_modify_e modify,
  151 + bool encrypt_metadata, bool use_aes);
146 152  
147 153 // Create linearized output. Disables qdf mode, content
148 154 // normalization, and stream prefiltering.
... ... @@ -182,6 +188,11 @@ class DLL_EXPORT QPDFWriter
182 188 void preserveObjectStreams();
183 189 void generateObjectStreams();
184 190 void generateID();
  191 + void interpretR3EncryptionParameters(
  192 + std::set<int>& bits_to_clear,
  193 + char const* user_password, char const* owner_password,
  194 + bool allow_accessibility, bool allow_extract,
  195 + r3_print_e print, r3_modify_e modify);
185 196 void setEncryptionParameters(
186 197 char const* user_password, char const* owner_password,
187 198 int V, int R, int key_len, std::set<int>& bits_to_clear);
... ... @@ -231,6 +242,7 @@ class DLL_EXPORT QPDFWriter
231 242 // stack items are of type Pl_Buffer, the buffer is retrieved.
232 243 void popPipelineStack(PointerHolder<Buffer>* bp = 0);
233 244  
  245 + void adjustAESStreamLength(unsigned long& length);
234 246 void pushEncryptionFilter();
235 247 void pushDiscardFilter();
236 248  
... ... @@ -251,6 +263,8 @@ class DLL_EXPORT QPDFWriter
251 263 bool linearized;
252 264 object_stream_e object_stream_mode;
253 265 std::string encryption_key;
  266 + bool encrypt_metadata;
  267 + bool encrypt_use_aes;
254 268 std::map<std::string, std::string> encryption_dictionary;
255 269  
256 270 std::string id1; // for /ID key of
... ... @@ -267,7 +281,7 @@ class DLL_EXPORT QPDFWriter
267 281 std::map<int, size_t> lengths;
268 282 int next_objid;
269 283 int cur_stream_length_id;
270   - int cur_stream_length;
  284 + unsigned long cur_stream_length;
271 285 bool added_newline;
272 286 int max_ostream_index;
273 287 std::set<int> normalized_streams;
... ...
libqpdf/QPDFWriter.cc
... ... @@ -6,6 +6,7 @@
6 6 #include <qpdf/Pl_Discard.hh>
7 7 #include <qpdf/Pl_Buffer.hh>
8 8 #include <qpdf/Pl_RC4.hh>
  9 +#include <qpdf/Pl_AES_PDF.hh>
9 10 #include <qpdf/Pl_Flate.hh>
10 11 #include <qpdf/Pl_PNGFilter.hh>
11 12 #include <qpdf/QUtil.hh>
... ... @@ -37,6 +38,8 @@ QPDFWriter::QPDFWriter(QPDF&amp; pdf, char const* filename) :
37 38 preserve_encryption(true),
38 39 linearized(false),
39 40 object_stream_mode(o_preserve),
  41 + encrypt_metadata(true),
  42 + encrypt_use_aes(false),
40 43 encryption_dict_objid(0),
41 44 next_objid(1),
42 45 cur_stream_length_id(0),
... ... @@ -188,6 +191,38 @@ QPDFWriter::setR3EncryptionParameters(
188 191 bool allow_accessibility, bool allow_extract,
189 192 r3_print_e print, r3_modify_e modify)
190 193 {
  194 + std::set<int> clear;
  195 + interpretR3EncryptionParameters(
  196 + clear, user_password, owner_password,
  197 + allow_accessibility, allow_extract, print, modify);
  198 + setMinimumPDFVersion("1.4");
  199 + setEncryptionParameters(user_password, owner_password, 2, 3, 16, clear);
  200 +}
  201 +
  202 +void
  203 +QPDFWriter::setR4EncryptionParameters(
  204 + char const* user_password, char const* owner_password,
  205 + bool allow_accessibility, bool allow_extract,
  206 + r3_print_e print, r3_modify_e modify,
  207 + bool encrypt_metadata, bool use_aes)
  208 +{
  209 + std::set<int> clear;
  210 + interpretR3EncryptionParameters(
  211 + clear, user_password, owner_password,
  212 + allow_accessibility, allow_extract, print, modify);
  213 + this->encrypt_use_aes = use_aes;
  214 + this->encrypt_metadata = encrypt_metadata;
  215 + setMinimumPDFVersion(use_aes ? "1.6" : "1.5");
  216 + setEncryptionParameters(user_password, owner_password, 4, 4, 16, clear);
  217 +}
  218 +
  219 +void
  220 +QPDFWriter::interpretR3EncryptionParameters(
  221 + std::set<int>& clear,
  222 + char const* user_password, char const* owner_password,
  223 + bool allow_accessibility, bool allow_extract,
  224 + r3_print_e print, r3_modify_e modify)
  225 +{
191 226 // Acrobat 5 security options:
192 227  
193 228 // Checkboxes:
... ... @@ -206,7 +241,6 @@ QPDFWriter::setR3EncryptionParameters(
206 241 // Low Resolution
207 242 // Full printing
208 243  
209   - std::set<int> clear;
210 244 if (! allow_accessibility)
211 245 {
212 246 clear.insert(10);
... ... @@ -251,9 +285,6 @@ QPDFWriter::setR3EncryptionParameters(
251 285  
252 286 // no default so gcc warns for missing cases
253 287 }
254   -
255   - setMinimumPDFVersion("1.4");
256   - setEncryptionParameters(user_password, owner_password, 2, 3, 16, clear);
257 288 }
258 289  
259 290 void
... ... @@ -282,7 +313,7 @@ QPDFWriter::setEncryptionParameters(
282 313 std::string U;
283 314 QPDF::compute_encryption_O_U(
284 315 user_password, owner_password, V, R, key_len, P,
285   - /*XXX encrypt_metadata*/true, this->id1, O, U);
  316 + this->encrypt_metadata, this->id1, O, U);
286 317 setEncryptionParametersInternal(
287 318 V, R, key_len, P, O, U, this->id1, user_password);
288 319 }
... ... @@ -326,9 +357,22 @@ QPDFWriter::setEncryptionParametersInternal(
326 357 encryption_dictionary["/P"] = QUtil::int_to_string(P);
327 358 encryption_dictionary["/O"] = QPDF_String(O).unparse(true);
328 359 encryption_dictionary["/U"] = QPDF_String(U).unparse(true);
  360 + if ((R >= 4) && (! encrypt_metadata))
  361 + {
  362 + encryption_dictionary["/EncryptMetadata"] = "false";
  363 + }
  364 + if (V == 4)
  365 + {
  366 + encryption_dictionary["/StmF"] = "/CF1";
  367 + encryption_dictionary["/StrF"] = "/CF1";
  368 + std::string method = (this->encrypt_use_aes ? "/AESV2" : "/V2");
  369 + encryption_dictionary["/CF"] =
  370 + "<< /CF1 << /AuthEvent /DocOpen /CFM " + method + " >> >>";
  371 + }
  372 +
329 373 this->encrypted = true;
330   - QPDF::EncryptionData encryption_data(V, R, key_len, P, O, U, this->id1,
331   - /*XXX encrypt_metadata*/true);
  374 + QPDF::EncryptionData encryption_data(
  375 + V, R, key_len, P, O, U, this->id1, this->encrypt_metadata);
332 376 this->encryption_key = QPDF::compute_encryption_key(
333 377 user_password, encryption_data);
334 378 }
... ... @@ -337,7 +381,7 @@ void
337 381 QPDFWriter::setDataKey(int objid)
338 382 {
339 383 this->cur_data_key = QPDF::compute_data_key(
340   - this->encryption_key, objid, 0, /*XXX use_aes */false);
  384 + this->encryption_key, objid, 0, this->encrypt_use_aes);
341 385 }
342 386  
343 387 int
... ... @@ -436,14 +480,36 @@ QPDFWriter::popPipelineStack(PointerHolder&lt;Buffer&gt;* bp)
436 480 }
437 481  
438 482 void
  483 +QPDFWriter::adjustAESStreamLength(unsigned long& length)
  484 +{
  485 + if (this->encrypted && (! this->cur_data_key.empty()) &&
  486 + this->encrypt_use_aes)
  487 + {
  488 + // Stream length will be padded with 1 to 16 bytes to end up
  489 + // as a multiple of 16. It will also be prepended by 16 bits
  490 + // of random data.
  491 + length += 32 - (length & 0xf);
  492 + }
  493 +}
  494 +
  495 +void
439 496 QPDFWriter::pushEncryptionFilter()
440 497 {
441 498 if (this->encrypted && (! this->cur_data_key.empty()))
442 499 {
443   - Pipeline* p =
444   - new Pl_RC4("stream encryption", this->pipeline,
445   - (unsigned char*) this->cur_data_key.c_str(),
446   - this->cur_data_key.length());
  500 + Pipeline* p = 0;
  501 + if (this->encrypt_use_aes)
  502 + {
  503 + p = new Pl_AES_PDF(
  504 + "aes stream encryption", this->pipeline, true,
  505 + (unsigned char*) this->cur_data_key.c_str());
  506 + }
  507 + else
  508 + {
  509 + p = new Pl_RC4("rc4 stream encryption", this->pipeline,
  510 + (unsigned char*) this->cur_data_key.c_str(),
  511 + this->cur_data_key.length());
  512 + }
447 513 pushPipeline(p);
448 514 }
449 515 // Must call this unconditionally so we can call popPipelineStack
... ... @@ -722,6 +788,8 @@ QPDFWriter::unparseObject(QPDFObjectHandle object, int level,
722 788 }
723 789 else if (object.isDictionary())
724 790 {
  791 + // XXX Must not preserve Crypt filters from original stream
  792 + // dictionary
725 793 writeString("<<");
726 794 writeStringQDF("\n");
727 795 std::set<std::string> keys = object.getKeys();
... ... @@ -836,6 +904,15 @@ QPDFWriter::unparseObject(QPDFObjectHandle object, int level,
836 904 }
837 905  
838 906 this->cur_stream_length = stream_data.getPointer()->getSize();
  907 + if (this->encrypted &&
  908 + stream_dict.getKey("/Type").isName() &&
  909 + (stream_dict.getKey("/Type").getName() == "/Metadata") &&
  910 + (! this->encrypt_metadata))
  911 + {
  912 + // Don't encrypt stream data for the metadata stream
  913 + this->cur_data_key.clear();
  914 + }
  915 + adjustAESStreamLength(this->cur_stream_length);
839 916 unparseObject(stream_dict, 0, flags, this->cur_stream_length, compress);
840 917 writeString("\nstream\n");
841 918 pushEncryptionFilter();
... ... @@ -864,13 +941,29 @@ QPDFWriter::unparseObject(QPDFObjectHandle object, int level,
864 941 (! this->cur_data_key.empty()))
865 942 {
866 943 val = object.getStringValue();
867   - char* tmp = QUtil::copy_string(val);
868   - unsigned int vlen = val.length();
869   - RC4 rc4((unsigned char const*)this->cur_data_key.c_str(),
870   - this->cur_data_key.length());
871   - rc4.process((unsigned char*)tmp, vlen);
872   - val = QPDF_String(std::string(tmp, vlen)).unparse();
873   - delete [] tmp;
  944 + if (this->encrypt_use_aes)
  945 + {
  946 + Pl_Buffer bufpl("encrypted string");
  947 + Pl_AES_PDF pl("aes encrypt string", &bufpl, true,
  948 + (unsigned char const*)this->cur_data_key.c_str());
  949 + pl.write((unsigned char*) val.c_str(), val.length());
  950 + pl.finish();
  951 + Buffer* buf = bufpl.getBuffer();
  952 + val = QPDF_String(
  953 + std::string((char*)buf->getBuffer(),
  954 + (size_t)buf->getSize())).unparse();
  955 + delete buf;
  956 + }
  957 + else
  958 + {
  959 + char* tmp = QUtil::copy_string(val);
  960 + unsigned int vlen = val.length();
  961 + RC4 rc4((unsigned char const*)this->cur_data_key.c_str(),
  962 + this->cur_data_key.length());
  963 + rc4.process((unsigned char*)tmp, vlen);
  964 + val = QPDF_String(std::string(tmp, vlen)).unparse();
  965 + delete [] tmp;
  966 + }
874 967 }
875 968 else
876 969 {
... ... @@ -1000,8 +1093,9 @@ QPDFWriter::writeObjectStream(QPDFObjectHandle object)
1000 1093 writeStringQDF("\n ");
1001 1094 writeString(" /Type /ObjStm");
1002 1095 writeStringQDF("\n ");
1003   - writeString(" /Length " +
1004   - QUtil::int_to_string(stream_buffer.getPointer()->getSize()));
  1096 + unsigned long length = stream_buffer.getPointer()->getSize();
  1097 + adjustAESStreamLength(length);
  1098 + writeString(" /Length " + QUtil::int_to_string(length));
1005 1099 writeStringQDF("\n ");
1006 1100 if (compressed)
1007 1101 {
... ... @@ -1489,6 +1583,7 @@ QPDFWriter::writeHintStream(int hint_id)
1489 1583 writeString(QUtil::int_to_string(O));
1490 1584 }
1491 1585 writeString(" /Length ");
  1586 + adjustAESStreamLength(hlen);
1492 1587 writeString(QUtil::int_to_string(hlen));
1493 1588 writeString(" >>\nstream\n");
1494 1589  
... ...
qpdf/qpdf.cc
... ... @@ -72,6 +72,9 @@ Additional flags are dependent upon key length.\n\
72 72 --extract=[yn] allow other text/graphic extraction\n\
73 73 --print=print-opt control printing access\n\
74 74 --modify=modify-opt control modify access\n\
  75 + --cleartext-metadata prevents encryption of metadata\n\
  76 + --use-aes=[yn] indicates whether to use AES encryption\n\
  77 + --force-V4 forces use of V=4 encryption handler\n\
75 78 \n\
76 79 print-opt may be:\n\
77 80 \n\
... ... @@ -89,6 +92,14 @@ Additional flags are dependent upon key length.\n\
89 92 \n\
90 93 The default for each permission option is to be fully permissive.\n\
91 94 \n\
  95 +Specifying cleartext-metadata forces the PDF version to at least 1.5.\n\
  96 +Specifying use of AES forces the PDF version to at least 1.6. These\n\
  97 +options are both off by default.\n\
  98 +\n\
  99 +The --force-V4 flag forces the V=4 encryption handler introduced in PDF 1.5\n\
  100 +to be used even if not otherwise needed. This option is primarily useful\n\
  101 +for testing qpdf and has no other practical use.\n\
  102 +\n\
92 103 \n\
93 104 Advanced Transformation Options\n\
94 105 -------------------------------\n\
... ... @@ -220,7 +231,8 @@ parse_encrypt_options(
220 231 std::string& user_password, std::string& owner_password, int& keylen,
221 232 bool& r2_print, bool& r2_modify, bool& r2_extract, bool& r2_annotate,
222 233 bool& r3_accessibility, bool& r3_extract,
223   - QPDFWriter::r3_print_e& r3_print, QPDFWriter::r3_modify_e& r3_modify)
  234 + QPDFWriter::r3_print_e& r3_print, QPDFWriter::r3_modify_e& r3_modify,
  235 + bool& force_V4, bool& cleartext_metadata, bool& use_aes)
224 236 {
225 237 if (cur_arg + 3 >= argc)
226 238 {
... ... @@ -450,6 +462,65 @@ parse_encrypt_options(
450 462 usage("-accessibility invalid for 40-bit keys");
451 463 }
452 464 }
  465 + else if (strcmp(arg, "cleartext-metadata") == 0)
  466 + {
  467 + if (parameter)
  468 + {
  469 + usage("--cleartext-metadata does not take a parameter");
  470 + }
  471 + if (keylen == 40)
  472 + {
  473 + usage("--cleartext-metadata is invalid for 40-bit keys");
  474 + }
  475 + else
  476 + {
  477 + cleartext_metadata = true;
  478 + }
  479 + }
  480 + else if (strcmp(arg, "force-V4") == 0)
  481 + {
  482 + if (parameter)
  483 + {
  484 + usage("--force-V4 does not take a parameter");
  485 + }
  486 + if (keylen == 40)
  487 + {
  488 + usage("--force-V4 is invalid for 40-bit keys");
  489 + }
  490 + else
  491 + {
  492 + force_V4 = true;
  493 + }
  494 + }
  495 + else if (strcmp(arg, "use-aes") == 0)
  496 + {
  497 + if (parameter == 0)
  498 + {
  499 + usage("--use-aes must be given as --extract=option");
  500 + }
  501 + std::string val = parameter;
  502 + bool result = false;
  503 + if (val == "y")
  504 + {
  505 + result = true;
  506 + }
  507 + else if (val == "n")
  508 + {
  509 + result = false;
  510 + }
  511 + else
  512 + {
  513 + usage("invalid -use-aes parameter");
  514 + }
  515 + if (keylen == 40)
  516 + {
  517 + usage("use-aes is invalid for 40-bit keys");
  518 + }
  519 + else
  520 + {
  521 + use_aes = result;
  522 + }
  523 + }
453 524 else
454 525 {
455 526 usage(std::string("invalid encryption parameter --") + arg);
... ... @@ -516,6 +587,9 @@ int main(int argc, char* argv[])
516 587 bool r3_extract = true;
517 588 QPDFWriter::r3_print_e r3_print = QPDFWriter::r3p_full;
518 589 QPDFWriter::r3_modify_e r3_modify = QPDFWriter::r3m_all;
  590 + bool force_V4 = false;
  591 + bool cleartext_metadata = false;
  592 + bool use_aes = false;
519 593  
520 594 bool stream_data_set = false;
521 595 QPDFWriter::stream_data_e stream_data_mode = QPDFWriter::s_compress;
... ... @@ -582,7 +656,8 @@ int main(int argc, char* argv[])
582 656 argc, argv, ++i,
583 657 user_password, owner_password, keylen,
584 658 r2_print, r2_modify, r2_extract, r2_annotate,
585   - r3_accessibility, r3_extract, r3_print, r3_modify);
  659 + r3_accessibility, r3_extract, r3_print, r3_modify,
  660 + force_V4, cleartext_metadata, use_aes);
586 661 encrypt = true;
587 662 }
588 663 else if (strcmp(arg, "decrypt") == 0)
... ... @@ -988,9 +1063,19 @@ int main(int argc, char* argv[])
988 1063 }
989 1064 else if (keylen == 128)
990 1065 {
991   - w.setR3EncryptionParameters(
992   - user_password.c_str(), owner_password.c_str(),
993   - r3_accessibility, r3_extract, r3_print, r3_modify);
  1066 + if (force_V4 || cleartext_metadata || use_aes)
  1067 + {
  1068 + w.setR4EncryptionParameters(
  1069 + user_password.c_str(), owner_password.c_str(),
  1070 + r3_accessibility, r3_extract, r3_print, r3_modify,
  1071 + !cleartext_metadata, use_aes);
  1072 + }
  1073 + else
  1074 + {
  1075 + w.setR3EncryptionParameters(
  1076 + user_password.c_str(), owner_password.c_str(),
  1077 + r3_accessibility, r3_extract, r3_print, r3_modify);
  1078 + }
994 1079 }
995 1080 else
996 1081 {
... ...