Commit e57c25814e49b82863753894ee7d97c18e4c4525

Authored by Jay Berkenbilt
1 parent 93ac1695

Support for encryption with /V=5 and /R=5 and /R=6

Read and write support is implemented for /V=5 with /R=5 as well as
/R=6.  /R=5 is the deprecated encryption method used by Acrobat IX.
/R=6 is the encryption method used by PDF 2.0 from ISO 32000-2.
1 1 General
2 2 =======
3 3  
4   - * See if I can support the encryption format used with /R 5 /V 5
5   - (AESV3), even though a qpdf-announce subscriber with an adobe.com
6   - email address mentioned that this is deprecated. There is also a
7   - new encryption format coming in a future release (PDF 2.0), which
8   - may be better to support. As of the qpdf 3.0 release, the
9   - specification was not publicly available yet.
10   -
11   - AESV3 encryption is supported with PDF 1.7 extension level 3 and is
12   - being deprecated, but there are plenty of files out there. The
13   - encryption format is decribed in adobe_supplement_iso32000.pdf.
14   - Such a file must specify that it uses these extensions in its
15   - document catalog:
16   -
17   - <<
18   - /Type /Catalog
19   - /Extensions <<
20   - /ADBE <<
21   - /BaseVersion /1.7
22   - /ExtensionLevel 3
23   - >>
24   - >>
25   - >>
26   -
27   - Possible sha256 implementations: http://sol-biotech.com/code/sha2/,
28   - http://hashlib2plus.sourceforge.net/
29   -
30 4 * Improve the random number seed to make it more secure so that we
31 5 have stronger random numbers, particularly when multiple files are
32 6 generated in the same second. This code may need to be
... ...
include/qpdf/QPDF.hh
... ... @@ -224,7 +224,7 @@ class QPDF
224 224  
225 225 // Encryption support
226 226  
227   - enum encryption_method_e { e_none, e_unknown, e_rc4, e_aes };
  227 + enum encryption_method_e { e_none, e_unknown, e_rc4, e_aes, e_aesv3 };
228 228 class EncryptionData
229 229 {
230 230 public:
... ... @@ -326,7 +326,7 @@ class QPDF
326 326 QPDF_DLL
327 327 static std::string compute_data_key(
328 328 std::string const& encryption_key, int objid, int generation,
329   - bool use_aes);
  329 + bool use_aes, int encryption_V, int encryption_R);
330 330 QPDF_DLL
331 331 static std::string compute_encryption_key(
332 332 std::string const& password, EncryptionData const& data);
... ... @@ -337,6 +337,14 @@ class QPDF
337 337 int V, int R, int key_len, int P, bool encrypt_metadata,
338 338 std::string const& id1,
339 339 std::string& O, std::string& U);
  340 + QPDF_DLL
  341 + static void compute_encryption_parameters_V5(
  342 + char const* user_password, char const* owner_password,
  343 + int V, int R, int key_len, int P, bool encrypt_metadata,
  344 + std::string const& id1,
  345 + std::string& encryption_key,
  346 + std::string& O, std::string& U,
  347 + std::string& OE, std::string& UE, std::string& Perms);
340 348 // Return the full user password as stored in the PDF file. If
341 349 // you are attempting to recover the user password in a
342 350 // user-presentable form, call getTrimmedUserPassword() instead.
... ... @@ -345,6 +353,10 @@ class QPDF
345 353 // Return human-readable form of user password.
346 354 QPDF_DLL
347 355 std::string getTrimmedUserPassword() const;
  356 + // Return the previously computed or retrieved encryption key for
  357 + // this file
  358 + QPDF_DLL
  359 + std::string getEncryptionKey() const;
348 360  
349 361 // Linearization support
350 362  
... ... @@ -628,6 +640,13 @@ class QPDF
628 640 void initializeEncryption();
629 641 std::string getKeyForObject(int objid, int generation, bool use_aes);
630 642 void decryptString(std::string&, int objid, int generation);
  643 + static std::string compute_encryption_key_from_password(
  644 + std::string const& password, EncryptionData const& data);
  645 + static std::string recover_encryption_key_with_password(
  646 + std::string const& password, EncryptionData const& data);
  647 + static std::string recover_encryption_key_with_password(
  648 + std::string const& password, EncryptionData const& data,
  649 + bool& perms_valid);
631 650 void decryptStream(
632 651 Pipeline*& pipeline, int objid, int generation,
633 652 QPDFObjectHandle& stream_dict,
... ... @@ -981,6 +1000,7 @@ class QPDF
981 1000 std::ostream* err_stream;
982 1001 bool attempt_recovery;
983 1002 int encryption_V;
  1003 + int encryption_R;
984 1004 bool encrypt_metadata;
985 1005 std::map<std::string, encryption_method_e> crypt_filters;
986 1006 encryption_method_e cf_stream;
... ...
include/qpdf/QPDFWriter.hh
... ... @@ -223,8 +223,9 @@ class QPDFWriter
223 223 // content normalization. Note that setting R2 encryption
224 224 // parameters sets the PDF version to at least 1.3, setting R3
225 225 // encryption parameters pushes the PDF version number to at least
226   - // 1.4, and setting R4 parameters pushes the version to at least
227   - // 1.5, or if AES is used, 1.6.
  226 + // 1.4, setting R4 parameters pushes the version to at least 1.5,
  227 + // or if AES is used, 1.6, and setting R5 or R6 parameters pushes
  228 + // the version to at least 1.7 with extension level 3.
228 229 QPDF_DLL
229 230 void setR2EncryptionParameters(
230 231 char const* user_password, char const* owner_password,
... ... @@ -241,6 +242,21 @@ class QPDFWriter
241 242 bool allow_accessibility, bool allow_extract,
242 243 qpdf_r3_print_e print, qpdf_r3_modify_e modify,
243 244 bool encrypt_metadata, bool use_aes);
  245 + // R5 is deprecated. Do not use it for production use. Writing
  246 + // R5 is supported by qpdf primarily to generate test files for
  247 + // applications that may need to test R5 support.
  248 + QPDF_DLL
  249 + void setR5EncryptionParameters(
  250 + char const* user_password, char const* owner_password,
  251 + bool allow_accessibility, bool allow_extract,
  252 + qpdf_r3_print_e print, qpdf_r3_modify_e modify,
  253 + bool encrypt_metadata);
  254 + QPDF_DLL
  255 + void setR6EncryptionParameters(
  256 + char const* user_password, char const* owner_password,
  257 + bool allow_accessibility, bool allow_extract,
  258 + qpdf_r3_print_e print, qpdf_r3_modify_e modify,
  259 + bool encrypt_metadata_aes);
244 260  
245 261 // Create linearized output. Disables qdf mode, content
246 262 // normalization, and stream prefiltering.
... ... @@ -302,7 +318,8 @@ class QPDFWriter
302 318 int V, int R, int key_len, long P,
303 319 std::string const& O, std::string const& U,
304 320 std::string const& OE, std::string const& UE, std::string const& Perms,
305   - std::string const& id1, std::string const& user_password);
  321 + std::string const& id1, std::string const& user_password,
  322 + std::string const& encryption_key);
306 323 void setDataKey(int objid);
307 324 int openObject(int objid = 0);
308 325 void closeObject(int objid);
... ... @@ -378,6 +395,8 @@ class QPDFWriter
378 395 bool encrypt_metadata;
379 396 bool encrypt_use_aes;
380 397 std::map<std::string, std::string> encryption_dictionary;
  398 + int encryption_V;
  399 + int encryption_R;
381 400  
382 401 std::string id1; // for /ID key of
383 402 std::string id2; // trailer dictionary
... ...
libqpdf/QPDF.cc
... ... @@ -97,6 +97,7 @@ QPDF::QPDF() :
97 97 err_stream(&std::cerr),
98 98 attempt_recovery(true),
99 99 encryption_V(0),
  100 + encryption_R(0),
100 101 encrypt_metadata(true),
101 102 cf_stream(e_none),
102 103 cf_string(e_none),
... ...
libqpdf/QPDFWriter.cc
... ... @@ -67,6 +67,8 @@ QPDFWriter::init()
67 67 min_extension_level = 0;
68 68 final_extension_level = 0;
69 69 forced_extension_level = 0;
  70 + encryption_V = 0;
  71 + encryption_R = 0;
70 72 encryption_dict_objid = 0;
71 73 next_objid = 1;
72 74 cur_stream_length_id = 0;
... ... @@ -344,6 +346,38 @@ QPDFWriter::setR4EncryptionParameters(
344 346 }
345 347  
346 348 void
  349 +QPDFWriter::setR5EncryptionParameters(
  350 + char const* user_password, char const* owner_password,
  351 + bool allow_accessibility, bool allow_extract,
  352 + qpdf_r3_print_e print, qpdf_r3_modify_e modify,
  353 + bool encrypt_metadata)
  354 +{
  355 + std::set<int> clear;
  356 + interpretR3EncryptionParameters(
  357 + clear, user_password, owner_password,
  358 + allow_accessibility, allow_extract, print, modify);
  359 + this->encrypt_use_aes = true;
  360 + this->encrypt_metadata = encrypt_metadata;
  361 + setEncryptionParameters(user_password, owner_password, 5, 5, 32, clear);
  362 +}
  363 +
  364 +void
  365 +QPDFWriter::setR6EncryptionParameters(
  366 + char const* user_password, char const* owner_password,
  367 + bool allow_accessibility, bool allow_extract,
  368 + qpdf_r3_print_e print, qpdf_r3_modify_e modify,
  369 + bool encrypt_metadata)
  370 +{
  371 + std::set<int> clear;
  372 + interpretR3EncryptionParameters(
  373 + clear, user_password, owner_password,
  374 + allow_accessibility, allow_extract, print, modify);
  375 + this->encrypt_use_aes = true;
  376 + this->encrypt_metadata = encrypt_metadata;
  377 + setEncryptionParameters(user_password, owner_password, 5, 6, 32, clear);
  378 +}
  379 +
  380 +void
347 381 QPDFWriter::interpretR3EncryptionParameters(
348 382 std::set<int>& clear,
349 383 char const* user_password, char const* owner_password,
... ... @@ -426,6 +460,12 @@ QPDFWriter::setEncryptionParameters(
426 460 bits_to_clear.insert(1);
427 461 bits_to_clear.insert(2);
428 462  
  463 + if (R > 3)
  464 + {
  465 + // Bit 10 is deprecated and should always be set.
  466 + bits_to_clear.erase(10);
  467 + }
  468 +
429 469 int P = 0;
430 470 // Create the complement of P, then invert.
431 471 for (std::set<int>::iterator iter = bits_to_clear.begin();
... ... @@ -438,11 +478,26 @@ QPDFWriter::setEncryptionParameters(
438 478 generateID();
439 479 std::string O;
440 480 std::string U;
441   - QPDF::compute_encryption_O_U(
442   - user_password, owner_password, V, R, key_len, P,
443   - this->encrypt_metadata, this->id1, O, U);
  481 + std::string OE;
  482 + std::string UE;
  483 + std::string Perms;
  484 + std::string encryption_key;
  485 + if (V < 5)
  486 + {
  487 + QPDF::compute_encryption_O_U(
  488 + user_password, owner_password, V, R, key_len, P,
  489 + this->encrypt_metadata, this->id1, O, U);
  490 + }
  491 + else
  492 + {
  493 + QPDF::compute_encryption_parameters_V5(
  494 + user_password, owner_password, V, R, key_len, P,
  495 + this->encrypt_metadata, this->id1,
  496 + encryption_key, O, U, OE, UE, Perms);
  497 + }
444 498 setEncryptionParametersInternal(
445   - V, R, key_len, P, O, U, "", "", "", this->id1, user_password);
  499 + V, R, key_len, P, O, U, OE, UE, Perms,
  500 + this->id1, user_password, encryption_key);
446 501 }
447 502  
448 503 void
... ... @@ -482,6 +537,19 @@ QPDFWriter::copyEncryptionParameters(QPDF&amp; qpdf)
482 537 this->encrypt_metadata ? 0 : 1);
483 538 QTC::TC("qpdf", "QPDFWriter copy use_aes",
484 539 this->encrypt_use_aes ? 0 : 1);
  540 + std::string OE;
  541 + std::string UE;
  542 + std::string Perms;
  543 + std::string encryption_key;
  544 + if (V >= 5)
  545 + {
  546 + QTC::TC("qpdf", "QPDFWriter copy V5");
  547 + OE = encrypt.getKey("/OE").getStringValue();
  548 + UE = encrypt.getKey("/UE").getStringValue();
  549 + Perms = encrypt.getKey("/Perms").getStringValue();
  550 + encryption_key = qpdf.getEncryptionKey();
  551 + }
  552 +
485 553 setEncryptionParametersInternal(
486 554 V,
487 555 encrypt.getKey("/R").getIntValue(),
... ... @@ -489,11 +557,12 @@ QPDFWriter::copyEncryptionParameters(QPDF&amp; qpdf)
489 557 encrypt.getKey("/P").getIntValue(),
490 558 encrypt.getKey("/O").getStringValue(),
491 559 encrypt.getKey("/U").getStringValue(),
492   - "", // XXXX OE
493   - "", // XXXX UE
494   - "", // XXXX Perms
  560 + OE,
  561 + UE,
  562 + Perms,
495 563 this->id1, // this->id1 == the other file's id1
496   - qpdf.getPaddedUserPassword());
  564 + qpdf.getPaddedUserPassword(),
  565 + encryption_key);
497 566 }
498 567 }
499 568  
... ... @@ -605,10 +674,11 @@ QPDFWriter::setEncryptionParametersInternal(
605 674 int V, int R, int key_len, long P,
606 675 std::string const& O, std::string const& U,
607 676 std::string const& OE, std::string const& UE, std::string const& Perms,
608   - std::string const& id1, std::string const& user_password)
  677 + std::string const& id1, std::string const& user_password,
  678 + std::string const& encryption_key)
609 679 {
610   - // XXXX OE, UE, Perms, V=5
611   -
  680 + this->encryption_V = V;
  681 + this->encryption_R = R;
612 682 encryption_dictionary["/Filter"] = "/Standard";
613 683 encryption_dictionary["/V"] = QUtil::int_to_string(V);
614 684 encryption_dictionary["/Length"] = QUtil::int_to_string(key_len * 8);
... ... @@ -618,9 +688,15 @@ QPDFWriter::setEncryptionParametersInternal(
618 688 encryption_dictionary["/U"] = QPDF_String(U).unparse(true);
619 689 if (V >= 5)
620 690 {
621   - setMinimumPDFVersion("1.4");
  691 + encryption_dictionary["/OE"] = QPDF_String(OE).unparse(true);
  692 + encryption_dictionary["/UE"] = QPDF_String(UE).unparse(true);
  693 + encryption_dictionary["/Perms"] = QPDF_String(Perms).unparse(true);
  694 + }
  695 + if (R >= 6)
  696 + {
  697 + setMinimumPDFVersion("1.7", 8);
622 698 }
623   - if (R >= 5)
  699 + else if (R == 5)
624 700 {
625 701 setMinimumPDFVersion("1.7", 3);
626 702 }
... ... @@ -641,14 +717,16 @@ QPDFWriter::setEncryptionParametersInternal(
641 717 {
642 718 encryption_dictionary["/EncryptMetadata"] = "false";
643 719 }
644   - if (V == 4)
  720 + if ((V == 4) || (V == 5))
645 721 {
646 722 // The spec says the value for the crypt filter key can be
647 723 // anything, and xpdf seems to agree. However, Adobe Reader
648 724 // won't open our files unless we use /StdCF.
649 725 encryption_dictionary["/StmF"] = "/StdCF";
650 726 encryption_dictionary["/StrF"] = "/StdCF";
651   - std::string method = (this->encrypt_use_aes ? "/AESV2" : "/V2");
  727 + std::string method = (this->encrypt_use_aes
  728 + ? ((V < 5) ? "/AESV2" : "/AESV3")
  729 + : "/V2");
652 730 encryption_dictionary["/CF"] =
653 731 "<< /StdCF << /AuthEvent /DocOpen /CFM " + method + " >> >>";
654 732 }
... ... @@ -656,15 +734,23 @@ QPDFWriter::setEncryptionParametersInternal(
656 734 this->encrypted = true;
657 735 QPDF::EncryptionData encryption_data(
658 736 V, R, key_len, P, O, U, OE, UE, Perms, id1, this->encrypt_metadata);
659   - this->encryption_key = QPDF::compute_encryption_key(
660   - user_password, encryption_data);
  737 + if (V < 5)
  738 + {
  739 + this->encryption_key = QPDF::compute_encryption_key(
  740 + user_password, encryption_data);
  741 + }
  742 + else
  743 + {
  744 + this->encryption_key = encryption_key;
  745 + }
661 746 }
662 747  
663 748 void
664 749 QPDFWriter::setDataKey(int objid)
665 750 {
666 751 this->cur_data_key = QPDF::compute_data_key(
667   - this->encryption_key, objid, 0, this->encrypt_use_aes);
  752 + this->encryption_key, objid, 0,
  753 + this->encrypt_use_aes, this->encryption_V, this->encryption_R);
668 754 }
669 755  
670 756 int
... ...
libqpdf/QPDF_encryption.cc
... ... @@ -10,6 +10,7 @@
10 10 #include <qpdf/Pl_RC4.hh>
11 11 #include <qpdf/Pl_AES_PDF.hh>
12 12 #include <qpdf/Pl_Buffer.hh>
  13 +#include <qpdf/Pl_SHA2.hh>
13 14 #include <qpdf/RC4.hh>
14 15 #include <qpdf/MD5.hh>
15 16  
... ... @@ -23,9 +24,15 @@ static unsigned char const padding_string[] = {
23 24 0x2f, 0x0c, 0xa9, 0xfe, 0x64, 0x53, 0x69, 0x7a
24 25 };
25 26  
26   -static unsigned int const O_key_bytes = sizeof(MD5::Digest);
27 27 static unsigned int const key_bytes = 32;
28 28  
  29 +// V4 key lengths apply to V <= 4
  30 +static unsigned int const OU_key_bytes_V4 = sizeof(MD5::Digest);
  31 +
  32 +static unsigned int const OU_key_bytes_V5 = 48;
  33 +static unsigned int const OUE_key_bytes_V5 = 32;
  34 +static unsigned int const Perms_key_bytes_V5 = 16;
  35 +
29 36 int
30 37 QPDF::EncryptionData::getV() const
31 38 {
... ... @@ -120,7 +127,7 @@ QPDF::EncryptionData::setV5EncryptionParameters(
120 127 }
121 128  
122 129 static void
123   -pad_or_truncate_password(std::string const& password, char k1[key_bytes])
  130 +pad_or_truncate_password_V4(std::string const& password, char k1[key_bytes])
124 131 {
125 132 int password_bytes = std::min(key_bytes, (unsigned int)password.length());
126 133 int pad_bytes = key_bytes - password_bytes;
... ... @@ -159,13 +166,19 @@ QPDF::trim_user_password(std::string&amp; user_password)
159 166 }
160 167  
161 168 static std::string
162   -pad_or_truncate_password(std::string const& password)
  169 +pad_or_truncate_password_V4(std::string const& password)
163 170 {
164 171 char k1[key_bytes];
165   - pad_or_truncate_password(password, k1);
  172 + pad_or_truncate_password_V4(password, k1);
166 173 return std::string(k1, key_bytes);
167 174 }
168 175  
  176 +static std::string
  177 +truncate_password_V5(std::string const& password)
  178 +{
  179 + return password.substr(0, std::min((size_t)127, password.length()));
  180 +}
  181 +
169 182 static void
170 183 iterate_md5_digest(MD5& md5, MD5::Digest& digest, int iterations)
171 184 {
... ... @@ -199,15 +212,146 @@ iterate_rc4(unsigned char* data, int data_len,
199 212 delete [] key;
200 213 }
201 214  
  215 +static std::string
  216 +process_with_aes(std::string const& key,
  217 + bool encrypt,
  218 + std::string const& data,
  219 + size_t outlength = 0,
  220 + unsigned int repetitions = 1,
  221 + unsigned char const* iv = 0,
  222 + size_t iv_length = 0)
  223 +{
  224 + Pl_Buffer buffer("buffer");
  225 + Pl_AES_PDF aes("aes", &buffer, encrypt,
  226 + (unsigned char const*)key.c_str(),
  227 + (unsigned int)key.length());
  228 + if (iv)
  229 + {
  230 + aes.setIV(iv, iv_length);
  231 + }
  232 + else
  233 + {
  234 + aes.useZeroIV();
  235 + }
  236 + aes.disablePadding();
  237 + for (unsigned int i = 0; i < repetitions; ++i)
  238 + {
  239 + aes.write((unsigned char*)data.c_str(), data.length());
  240 + }
  241 + aes.finish();
  242 + PointerHolder<Buffer> bufp = buffer.getBuffer();
  243 + if (outlength == 0)
  244 + {
  245 + outlength = bufp->getSize();
  246 + }
  247 + else
  248 + {
  249 + outlength = std::min(outlength, bufp->getSize());
  250 + }
  251 + return std::string((char const*)bufp->getBuffer(), outlength);
  252 +}
  253 +
  254 +static std::string
  255 +hash_V5(std::string const& password,
  256 + std::string const& salt,
  257 + std::string const& udata,
  258 + QPDF::EncryptionData const& data)
  259 +{
  260 + Pl_SHA2 hash(256);
  261 + hash.write((unsigned char*)password.c_str(), password.length());
  262 + hash.write((unsigned char*)salt.c_str(), salt.length());
  263 + hash.write((unsigned char*)udata.c_str(), udata.length());
  264 + hash.finish();
  265 + std::string K = hash.getRawDigest();
  266 +
  267 + std::string result;
  268 + if (data.getR() < 6)
  269 + {
  270 + result = K;
  271 + }
  272 + else
  273 + {
  274 + // Algorithm 2.B from ISO 32000-1 chapter 7: Computing a hash
  275 +
  276 + int round_number = 0;
  277 + bool done = false;
  278 + while (! done)
  279 + {
  280 + // The hash algorithm has us setting K initially to the R5
  281 + // value and then repeating a series of steps 64 times
  282 + // before starting with the termination case testing. The
  283 + // wording of the specification is very unclear as to the
  284 + // exact number of times it should be run since the
  285 + // wording about whether the initial setup counts as round
  286 + // 0 or not is ambiguous. This code counts the initial
  287 + // setup (R5) value as round 0, which appears to be
  288 + // correct. This was determined to be correct by
  289 + // increasing or decreasing the number of rounds by 1 or 2
  290 + // from this value and generating 20 test files. In this
  291 + // interpretation, all the test files worked with Adobe
  292 + // Reader X. In the other configurations, many of the
  293 + // files did not work, and we were accurately able to
  294 + // predict which files didn't work by looking at the
  295 + // conditions under which we terminated repetition.
  296 +
  297 + ++round_number;
  298 + std::string K1 = password + K + udata;
  299 + assert(K.length() >= 32);
  300 + std::string E = process_with_aes(
  301 + K.substr(0, 16), true, K1, 0, 64,
  302 + (unsigned char*)K.substr(16, 16).c_str(), 16);
  303 +
  304 + // E_mod_3 is supposed to be mod 3 of the first 16 bytes
  305 + // of E taken as as a (128-bit) big-endian number. Since
  306 + // (xy mod n) is equal to ((x mod n) + (y mod n)) mod n
  307 + // and since 256 mod n is 1, we can just take the sums of
  308 + // the the mod 3s of each byte to get the same result.
  309 + int E_mod_3 = 0;
  310 + for (unsigned int i = 0; i < 16; ++i)
  311 + {
  312 + E_mod_3 += (unsigned char)E[i];
  313 + }
  314 + E_mod_3 %= 3;
  315 + int next_hash = ((E_mod_3 == 0) ? 256 :
  316 + (E_mod_3 == 1) ? 384 :
  317 + 512);
  318 + Pl_SHA2 hash(next_hash);
  319 + hash.write((unsigned char*)E.c_str(), E.length());
  320 + hash.finish();
  321 + K = hash.getRawDigest();
  322 +
  323 + if (round_number >= 64)
  324 + {
  325 + unsigned int ch = (unsigned int)((unsigned char) *(E.rbegin()));
  326 +
  327 + if (ch <= (unsigned int)(round_number - 32))
  328 + {
  329 + done = true;
  330 + }
  331 + }
  332 + }
  333 + result = K.substr(0, 32);
  334 + }
  335 +
  336 + return result;
  337 +}
  338 +
202 339 std::string
203 340 QPDF::compute_data_key(std::string const& encryption_key,
204   - int objid, int generation,
205   - bool use_aes)
  341 + int objid, int generation, bool use_aes,
  342 + int encryption_V, int encryption_R)
206 343 {
207 344 // Algorithm 3.1 from the PDF 1.7 Reference Manual
208 345  
209 346 std::string result = encryption_key;
210 347  
  348 + if (encryption_V >= 5)
  349 + {
  350 + // Algorithm 3.1a (PDF 1.7 extension level 3): just use
  351 + // encryption key straight.
  352 + return result;
  353 + }
  354 +
211 355 // Append low three bytes of object ID and low two bytes of generation
212 356 result += (char) (objid & 0xff);
213 357 result += (char) ((objid >> 8) & 0xff);
... ... @@ -231,11 +375,37 @@ std::string
231 375 QPDF::compute_encryption_key(
232 376 std::string const& password, EncryptionData const& data)
233 377 {
  378 + if (data.getV() >= 5)
  379 + {
  380 + // For V >= 5, the encryption key is generated and stored in
  381 + // the file, encrypted separately with both user and owner
  382 + // passwords.
  383 + return recover_encryption_key_with_password(password, data);
  384 + }
  385 + else
  386 + {
  387 + // For V < 5, the encryption key is derived from the user
  388 + // password.
  389 + return compute_encryption_key_from_password(password, data);
  390 + }
  391 +}
  392 +
  393 +std::string
  394 +QPDF::compute_encryption_key_from_password(
  395 + std::string const& password, EncryptionData const& data)
  396 +{
234 397 // Algorithm 3.2 from the PDF 1.7 Reference Manual
235 398  
  399 + // This code does not properly handle Unicode passwords.
  400 + // Passwords are supposed to be converted from OS codepage
  401 + // characters to PDFDocEncoding. Unicode passwords are supposed
  402 + // to be converted to OS codepage before converting to
  403 + // PDFDocEncoding. We instead require the password to be
  404 + // presented in its final form.
  405 +
236 406 MD5 md5;
237 407 md5.encodeDataIncrementally(
238   - pad_or_truncate_password(password).c_str(), key_bytes);
  408 + pad_or_truncate_password_V4(password).c_str(), key_bytes);
239 409 md5.encodeDataIncrementally(data.getO().c_str(), key_bytes);
240 410 char pbytes[4];
241 411 int P = data.getP();
... ... @@ -261,8 +431,13 @@ static void
261 431 compute_O_rc4_key(std::string const& user_password,
262 432 std::string const& owner_password,
263 433 QPDF::EncryptionData const& data,
264   - unsigned char key[O_key_bytes])
  434 + unsigned char key[OU_key_bytes_V4])
265 435 {
  436 + if (data.getV() >= 5)
  437 + {
  438 + throw std::logic_error(
  439 + "compute_O_rc4_key called for file with V >= 5");
  440 + }
266 441 std::string password = owner_password;
267 442 if (password.empty())
268 443 {
... ... @@ -270,10 +445,10 @@ compute_O_rc4_key(std::string const&amp; user_password,
270 445 }
271 446 MD5 md5;
272 447 md5.encodeDataIncrementally(
273   - pad_or_truncate_password(password).c_str(), key_bytes);
  448 + pad_or_truncate_password_V4(password).c_str(), key_bytes);
274 449 MD5::Digest digest;
275 450 iterate_md5_digest(md5, digest, ((data.getR() >= 3) ? 50 : 0));
276   - memcpy(key, digest, O_key_bytes);
  451 + memcpy(key, digest, OU_key_bytes_V4);
277 452 }
278 453  
279 454 static std::string
... ... @@ -283,11 +458,11 @@ compute_O_value(std::string const&amp; user_password,
283 458 {
284 459 // Algorithm 3.3 from the PDF 1.7 Reference Manual
285 460  
286   - unsigned char O_key[O_key_bytes];
  461 + unsigned char O_key[OU_key_bytes_V4];
287 462 compute_O_rc4_key(user_password, owner_password, data, O_key);
288 463  
289 464 char upass[key_bytes];
290   - pad_or_truncate_password(user_password, upass);
  465 + pad_or_truncate_password_V4(user_password, upass);
291 466 iterate_rc4((unsigned char*) upass, key_bytes,
292 467 O_key, data.getLengthBytes(),
293 468 (data.getR() >= 3) ? 20 : 1, false);
... ... @@ -303,7 +478,7 @@ compute_U_value_R2(std::string const&amp; user_password,
303 478  
304 479 std::string k1 = QPDF::compute_encryption_key(user_password, data);
305 480 char udata[key_bytes];
306   - pad_or_truncate_password("", udata);
  481 + pad_or_truncate_password_V4("", udata);
307 482 iterate_rc4((unsigned char*) udata, key_bytes,
308 483 (unsigned char*)k1.c_str(), data.getLengthBytes(), 1, false);
309 484 return std::string(udata, key_bytes);
... ... @@ -319,7 +494,7 @@ compute_U_value_R3(std::string const&amp; user_password,
319 494 std::string k1 = QPDF::compute_encryption_key(user_password, data);
320 495 MD5 md5;
321 496 md5.encodeDataIncrementally(
322   - pad_or_truncate_password("").c_str(), key_bytes);
  497 + pad_or_truncate_password_V4("").c_str(), key_bytes);
323 498 md5.encodeDataIncrementally(data.getId1().c_str(),
324 499 (int)data.getId1().length());
325 500 MD5::Digest digest;
... ... @@ -350,40 +525,214 @@ compute_U_value(std::string const&amp; user_password,
350 525 }
351 526  
352 527 static bool
353   -check_user_password(std::string const& user_password,
354   - QPDF::EncryptionData const& data)
  528 +check_user_password_V4(std::string const& user_password,
  529 + QPDF::EncryptionData const& data)
355 530 {
356 531 // Algorithm 3.6 from the PDF 1.7 Reference Manual
357 532  
358 533 std::string u_value = compute_U_value(user_password, data);
359   - int to_compare = ((data.getR() >= 3) ? sizeof(MD5::Digest) : key_bytes);
  534 + int to_compare = ((data.getR() >= 3) ? sizeof(MD5::Digest)
  535 + : key_bytes);
360 536 return (memcmp(data.getU().c_str(), u_value.c_str(), to_compare) == 0);
361 537 }
362 538  
363 539 static bool
364   -check_owner_password(std::string& user_password,
365   - std::string const& owner_password,
366   - QPDF::EncryptionData const& data)
  540 +check_user_password_V5(std::string const& user_password,
  541 + QPDF::EncryptionData const& data)
  542 +{
  543 + // Algorithm 3.11 from the PDF 1.7 extension level 3
  544 +
  545 + std::string user_data = data.getU().substr(0, 32);
  546 + std::string validation_salt = data.getU().substr(32, 8);
  547 + std::string password = truncate_password_V5(user_password);
  548 + return (hash_V5(password, validation_salt, "", data) == user_data);
  549 +}
  550 +
  551 +static bool
  552 +check_user_password(std::string const& user_password,
  553 + QPDF::EncryptionData const& data)
  554 +{
  555 + if (data.getV() < 5)
  556 + {
  557 + return check_user_password_V4(user_password, data);
  558 + }
  559 + else
  560 + {
  561 + return check_user_password_V5(user_password, data);
  562 + }
  563 +}
  564 +
  565 +static bool
  566 +check_owner_password_V4(std::string& user_password,
  567 + std::string const& owner_password,
  568 + QPDF::EncryptionData const& data)
367 569 {
368 570 // Algorithm 3.7 from the PDF 1.7 Reference Manual
369 571  
370   - unsigned char key[O_key_bytes];
  572 + unsigned char key[OU_key_bytes_V4];
371 573 compute_O_rc4_key(user_password, owner_password, data, key);
372 574 unsigned char O_data[key_bytes];
373 575 memcpy(O_data, (unsigned char*) data.getO().c_str(), key_bytes);
374 576 iterate_rc4(O_data, key_bytes, key, data.getLengthBytes(),
375   - (data.getR() >= 3) ? 20 : 1, true);
  577 + (data.getR() >= 3) ? 20 : 1, true);
376 578 std::string new_user_password =
377   - std::string((char*)O_data, key_bytes);
  579 + std::string((char*)O_data, key_bytes);
378 580 bool result = false;
379 581 if (check_user_password(new_user_password, data))
380 582 {
381   - result = true;
382   - user_password = new_user_password;
  583 + result = true;
  584 + user_password = new_user_password;
383 585 }
384 586 return result;
385 587 }
386 588  
  589 +static bool
  590 +check_owner_password_V5(std::string const& owner_password,
  591 + QPDF::EncryptionData const& data)
  592 +{
  593 + // Algorithm 3.12 from the PDF 1.7 extension level 3
  594 +
  595 + std::string user_data = data.getU().substr(0, 48);
  596 + std::string owner_data = data.getO().substr(0, 32);
  597 + std::string validation_salt = data.getO().substr(32, 8);
  598 + std::string password = truncate_password_V5(owner_password);
  599 + return (hash_V5(password, validation_salt, user_data,
  600 + data) == owner_data);
  601 +}
  602 +
  603 +static bool
  604 +check_owner_password(std::string& user_password,
  605 + std::string const& owner_password,
  606 + QPDF::EncryptionData const& data)
  607 +{
  608 + if (data.getV() < 5)
  609 + {
  610 + return check_owner_password_V4(user_password, owner_password, data);
  611 + }
  612 + else
  613 + {
  614 + return check_owner_password_V5(owner_password, data);
  615 + }
  616 +}
  617 +
  618 +std::string
  619 +QPDF::recover_encryption_key_with_password(
  620 + std::string const& password, EncryptionData const& data)
  621 +{
  622 + // Disregard whether Perms is valid.
  623 + bool disregard;
  624 + return recover_encryption_key_with_password(password, data, disregard);
  625 +}
  626 +
  627 +static void
  628 +compute_U_UE_value_V5(std::string const& user_password,
  629 + std::string const& encryption_key,
  630 + QPDF::EncryptionData const& data,
  631 + std::string& U, std::string& UE)
  632 +{
  633 + // Algorithm 3.8 from the PDF 1.7 extension level 3
  634 + char k[16];
  635 + QUtil::initializeWithRandomBytes((unsigned char*) k, sizeof(k));
  636 + std::string validation_salt(k, 8);
  637 + std::string key_salt(k + 8, 8);
  638 + U = hash_V5(user_password, validation_salt, "", data) +
  639 + validation_salt + key_salt;
  640 + std::string intermediate_key = hash_V5(user_password, key_salt, "", data);
  641 + UE = process_with_aes(intermediate_key, true, encryption_key);
  642 +}
  643 +
  644 +static void
  645 +compute_O_OE_value_V5(std::string const& owner_password,
  646 + std::string const& encryption_key,
  647 + QPDF::EncryptionData const& data,
  648 + std::string const& U,
  649 + std::string& O, std::string& OE)
  650 +{
  651 + // Algorithm 3.9 from the PDF 1.7 extension level 3
  652 + char k[16];
  653 + QUtil::initializeWithRandomBytes((unsigned char*) k, sizeof(k));
  654 + std::string validation_salt(k, 8);
  655 + std::string key_salt(k + 8, 8);
  656 + O = hash_V5(owner_password, validation_salt, U, data) +
  657 + validation_salt + key_salt;
  658 + std::string intermediate_key = hash_V5(owner_password, key_salt, U, data);
  659 + OE = process_with_aes(intermediate_key, true, encryption_key);
  660 +}
  661 +
  662 +void
  663 +compute_Perms_value_V5_clear(std::string const& encryption_key,
  664 + QPDF::EncryptionData const& data,
  665 + unsigned char k[16])
  666 +{
  667 + // From algorithm 3.10 from the PDF 1.7 extension level 3
  668 + unsigned long long extended_perms = 0xffffffff00000000LL | data.getP();
  669 + for (int i = 0; i < 8; ++i)
  670 + {
  671 + k[i] = (unsigned char) (extended_perms & 0xff);
  672 + extended_perms >>= 8;
  673 + }
  674 + k[8] = data.getEncryptMetadata() ? 'T' : 'F';
  675 + k[9] = 'a';
  676 + k[10] = 'd';
  677 + k[11] = 'b';
  678 + QUtil::initializeWithRandomBytes(k + 12, 4);
  679 +}
  680 +
  681 +static std::string
  682 +compute_Perms_value_V5(std::string const& encryption_key,
  683 + QPDF::EncryptionData const& data)
  684 +{
  685 + // Algorithm 3.10 from the PDF 1.7 extension level 3
  686 + unsigned char k[16];
  687 + compute_Perms_value_V5_clear(encryption_key, data, k);
  688 + return process_with_aes(encryption_key, true,
  689 + std::string((char const*) k, sizeof(k)));
  690 +}
  691 +
  692 +std::string
  693 +QPDF::recover_encryption_key_with_password(
  694 + std::string const& password, EncryptionData const& data,
  695 + bool& perms_valid)
  696 +{
  697 + // Algorithm 3.2a from the PDF 1.7 extension level 3
  698 +
  699 + // This code does not handle Unicode passwords correctly.
  700 + // Empirical evidence suggests that most viewers don't. We are
  701 + // supposed to process the input string with the SASLprep (RFC
  702 + // 4013) profile of stringprep (RFC 3454) and then convert the
  703 + // result to UTF-8.
  704 +
  705 + perms_valid = false;
  706 + std::string key_password = truncate_password_V5(password);
  707 + std::string key_salt;
  708 + std::string user_data;
  709 + std::string encrypted_file_key;
  710 + if (check_owner_password_V5(key_password, data))
  711 + {
  712 + key_salt = data.getO().substr(40, 8);
  713 + user_data = data.getU().substr(0, 48);
  714 + encrypted_file_key = data.getOE().substr(0, 32);
  715 + }
  716 + else if (check_user_password_V5(key_password, data))
  717 + {
  718 + key_salt = data.getU().substr(40, 8);
  719 + encrypted_file_key = data.getUE().substr(0, 32);
  720 + }
  721 + std::string intermediate_key =
  722 + hash_V5(key_password, key_salt, user_data, data);
  723 + std::string file_key =
  724 + process_with_aes(intermediate_key, false, encrypted_file_key);
  725 +
  726 + // Decrypt Perms and check against expected value
  727 + std::string perms_check =
  728 + process_with_aes(file_key, false, data.getPerms(), 12);
  729 + unsigned char k[16];
  730 + compute_Perms_value_V5_clear(file_key, data, k);
  731 + perms_valid = (memcmp(perms_check.c_str(), k, 12) == 0);
  732 +
  733 + return file_key;
  734 +}
  735 +
387 736 QPDF::encryption_method_e
388 737 QPDF::interpretCF(QPDFObjectHandle cf)
389 738 {
... ... @@ -487,29 +836,70 @@ QPDF::initializeEncryption()
487 836 std::string U = encryption_dict.getKey("/U").getStringValue();
488 837 unsigned int P = (unsigned int) encryption_dict.getKey("/P").getIntValue();
489 838  
490   - if (! (((R == 2) || (R == 3) || (R == 4)) &&
491   - ((V == 1) || (V == 2) || (V == 4))))
  839 + // If supporting new encryption R/V values, remember to update
  840 + // error message inside this if statement.
  841 + if (! (((R >= 2) && (R <= 6)) &&
  842 + ((V == 1) || (V == 2) || (V == 4) || (V == 5))))
492 843 {
493 844 throw QPDFExc(qpdf_e_unsupported, this->file->getName(),
494 845 "encryption dictionary", this->file->getLastOffset(),
495   - "Unsupported /R or /V in encryption dictionary");
  846 + "Unsupported /R or /V in encryption dictionary; R = " +
  847 + QUtil::int_to_string(R) + " (max 6), V = " +
  848 + QUtil::int_to_string(V) + " (max 5)");
496 849 }
497 850  
498 851 this->encryption_V = V;
  852 + this->encryption_R = R;
  853 +
  854 + // OE, UE, and Perms are only present if V >= 5.
  855 + std::string OE;
  856 + std::string UE;
  857 + std::string Perms;
499 858  
500   - if (! ((O.length() == key_bytes) && (U.length() == key_bytes)))
  859 + if (V < 5)
501 860 {
502   - throw QPDFExc(qpdf_e_damaged_pdf, this->file->getName(),
503   - "encryption dictionary", this->file->getLastOffset(),
504   - "incorrect length for /O and/or /P in "
505   - "encryption dictionary");
  861 + if (! ((O.length() == key_bytes) && (U.length() == key_bytes)))
  862 + {
  863 + throw QPDFExc(qpdf_e_damaged_pdf, this->file->getName(),
  864 + "encryption dictionary", this->file->getLastOffset(),
  865 + "incorrect length for /O and/or /U in "
  866 + "encryption dictionary");
  867 + }
  868 + }
  869 + else
  870 + {
  871 + if (! (encryption_dict.getKey("/OE").isString() &&
  872 + encryption_dict.getKey("/UE").isString() &&
  873 + encryption_dict.getKey("/Perms").isString()))
  874 + {
  875 + throw QPDFExc(qpdf_e_damaged_pdf, this->file->getName(),
  876 + "encryption dictionary", this->file->getLastOffset(),
  877 + "some V=5 encryption dictionary parameters are "
  878 + "missing or the wrong type");
  879 + }
  880 + OE = encryption_dict.getKey("/OE").getStringValue();
  881 + UE = encryption_dict.getKey("/UE").getStringValue();
  882 + Perms = encryption_dict.getKey("/Perms").getStringValue();
  883 +
  884 + if ((O.length() < OU_key_bytes_V5) ||
  885 + (U.length() < OU_key_bytes_V5) ||
  886 + (OE.length() < OUE_key_bytes_V5) ||
  887 + (UE.length() < OUE_key_bytes_V5) ||
  888 + (Perms.length() < Perms_key_bytes_V5))
  889 + {
  890 + throw QPDFExc(qpdf_e_damaged_pdf, this->file->getName(),
  891 + "encryption dictionary", this->file->getLastOffset(),
  892 + "incorrect length for some of"
  893 + " /O, /U, /OE, /UE, or /Perms in"
  894 + " encryption dictionary");
  895 + }
506 896 }
507 897  
508 898 int Length = 40;
509 899 if (encryption_dict.getKey("/Length").isInteger())
510 900 {
511 901 Length = encryption_dict.getKey("/Length").getIntValue();
512   - if ((Length % 8) || (Length < 40) || (Length > 128))
  902 + if ((Length % 8) || (Length < 40) || (Length > 256))
513 903 {
514 904 throw QPDFExc(qpdf_e_damaged_pdf, this->file->getName(),
515 905 "encryption dictionary", this->file->getLastOffset(),
... ... @@ -524,7 +914,7 @@ QPDF::initializeEncryption()
524 914 encryption_dict.getKey("/EncryptMetadata").getBoolValue();
525 915 }
526 916  
527   - if (V == 4)
  917 + if ((V == 4) || (V == 5))
528 918 {
529 919 QPDFObjectHandle CF = encryption_dict.getKey("/CF");
530 920 std::set<std::string> keys = CF.getKeys();
... ... @@ -549,6 +939,11 @@ QPDF::initializeEncryption()
549 939 QTC::TC("qpdf", "QPDF_encryption CFM AESV2");
550 940 method = e_aes;
551 941 }
  942 + else if (method_name == "/AESV3")
  943 + {
  944 + QTC::TC("qpdf", "QPDF_encryption CFM AESV3");
  945 + method = e_aesv3;
  946 + }
552 947 else
553 948 {
554 949 // Don't complain now -- maybe we won't need
... ... @@ -574,13 +969,14 @@ QPDF::initializeEncryption()
574 969 this->cf_file = this->cf_stream;
575 970 }
576 971 }
577   - EncryptionData data(V, R, Length / 8, P, O, U, "", "", "",
  972 +
  973 + EncryptionData data(V, R, Length / 8, P, O, U, OE, UE, Perms,
578 974 id1, this->encrypt_metadata);
579 975 if (check_owner_password(
580 976 this->user_password, this->provided_password, data))
581 977 {
582 978 // password supplied was owner password; user_password has
583   - // been initialized
  979 + // been initialized for V < 5
584 980 }
585 981 else if (check_user_password(this->provided_password, data))
586 982 {
... ... @@ -592,7 +988,30 @@ QPDF::initializeEncryption()
592 988 "", 0, "invalid password");
593 989 }
594 990  
595   - this->encryption_key = compute_encryption_key(this->user_password, data);
  991 + if (V < 5)
  992 + {
  993 + // For V < 5, the user password is encrypted with the owner
  994 + // password, and the user password is always used for
  995 + // computing the encryption key.
  996 + this->encryption_key = compute_encryption_key(
  997 + this->user_password, data);
  998 + }
  999 + else
  1000 + {
  1001 + // For V >= 5, either password can be used independently to
  1002 + // compute the encryption key, and neither password can be
  1003 + // used to recover the other.
  1004 + bool perms_valid;
  1005 + this->encryption_key = recover_encryption_key_with_password(
  1006 + this->provided_password, data, perms_valid);
  1007 + if (! perms_valid)
  1008 + {
  1009 + warn(QPDFExc(qpdf_e_damaged_pdf, this->file->getName(),
  1010 + "encryption dictionary", this->file->getLastOffset(),
  1011 + "/Perms field in encryption dictionary"
  1012 + " doesn't match expected value"));
  1013 + }
  1014 + }
596 1015 }
597 1016  
598 1017 std::string
... ... @@ -608,7 +1027,8 @@ QPDF::getKeyForObject(int objid, int generation, bool use_aes)
608 1027 (generation == this->cached_key_generation)))
609 1028 {
610 1029 this->cached_object_encryption_key =
611   - compute_data_key(this->encryption_key, objid, generation, use_aes);
  1030 + compute_data_key(this->encryption_key, objid, generation,
  1031 + use_aes, this->encryption_V, this->encryption_R);
612 1032 this->cached_key_objid = objid;
613 1033 this->cached_key_generation = generation;
614 1034 }
... ... @@ -624,7 +1044,7 @@ QPDF::decryptString(std::string&amp; str, int objid, int generation)
624 1044 return;
625 1045 }
626 1046 bool use_aes = false;
627   - if (this->encryption_V == 4)
  1047 + if (this->encryption_V >= 4)
628 1048 {
629 1049 switch (this->cf_string)
630 1050 {
... ... @@ -635,6 +1055,10 @@ QPDF::decryptString(std::string&amp; str, int objid, int generation)
635 1055 use_aes = true;
636 1056 break;
637 1057  
  1058 + case e_aesv3:
  1059 + use_aes = true;
  1060 + break;
  1061 +
638 1062 case e_rc4:
639 1063 break;
640 1064  
... ... @@ -710,7 +1134,7 @@ QPDF::decryptStream(Pipeline*&amp; pipeline, int objid, int generation,
710 1134 return;
711 1135 }
712 1136 bool use_aes = false;
713   - if (this->encryption_V == 4)
  1137 + if (this->encryption_V >= 4)
714 1138 {
715 1139 encryption_method_e method = e_unknown;
716 1140 std::string method_source = "/StmF from /Encrypt dictionary";
... ... @@ -747,7 +1171,7 @@ QPDF::decryptStream(Pipeline*&amp; pipeline, int objid, int generation,
747 1171 if (crypt_params.isDictionary() &&
748 1172 crypt_params.getKey("/Name").isName())
749 1173 {
750   -// XXX QTC::TC("qpdf", "QPDF_encrypt crypt array");
  1174 + QTC::TC("qpdf", "QPDF_encrypt crypt array");
751 1175 method = interpretCF(
752 1176 crypt_params.getKey("/Name"));
753 1177 method_source = "stream's Crypt "
... ... @@ -790,6 +1214,10 @@ QPDF::decryptStream(Pipeline*&amp; pipeline, int objid, int generation,
790 1214 use_aes = true;
791 1215 break;
792 1216  
  1217 + case e_aesv3:
  1218 + use_aes = true;
  1219 + break;
  1220 +
793 1221 case e_rc4:
794 1222 break;
795 1223  
... ... @@ -831,6 +1259,11 @@ QPDF::compute_encryption_O_U(
831 1259 int V, int R, int key_len, int P, bool encrypt_metadata,
832 1260 std::string const& id1, std::string& O, std::string& U)
833 1261 {
  1262 + if (V >= 5)
  1263 + {
  1264 + throw std::logic_error(
  1265 + "compute_encryption_O_U called for file with V >= 5");
  1266 + }
834 1267 EncryptionData data(V, R, key_len, P, "", "", "", "", "",
835 1268 id1, encrypt_metadata);
836 1269 data.setO(compute_O_value(user_password, owner_password, data));
... ... @@ -839,6 +1272,26 @@ QPDF::compute_encryption_O_U(
839 1272 U = data.getU();
840 1273 }
841 1274  
  1275 +void
  1276 +QPDF::compute_encryption_parameters_V5(
  1277 + char const* user_password, char const* owner_password,
  1278 + int V, int R, int key_len, int P, bool encrypt_metadata,
  1279 + std::string const& id1,
  1280 + std::string& encryption_key,
  1281 + std::string& O, std::string& U,
  1282 + std::string& OE, std::string& UE, std::string& Perms)
  1283 +{
  1284 + EncryptionData data(V, R, key_len, P, "", "", "", "", "",
  1285 + id1, encrypt_metadata);
  1286 + unsigned char k[key_bytes];
  1287 + QUtil::initializeWithRandomBytes(k, key_bytes);
  1288 + encryption_key = std::string((char const*)k, key_bytes);
  1289 + compute_U_UE_value_V5(user_password, encryption_key, data, U, UE);
  1290 + compute_O_OE_value_V5(owner_password, encryption_key, data, U, O, OE);
  1291 + Perms = compute_Perms_value_V5(encryption_key, data);
  1292 + data.setV5EncryptionParameters(O, OE, U, UE, Perms);
  1293 +}
  1294 +
842 1295 std::string const&
843 1296 QPDF::getPaddedUserPassword() const
844 1297 {
... ... @@ -853,6 +1306,12 @@ QPDF::getTrimmedUserPassword() const
853 1306 return result;
854 1307 }
855 1308  
  1309 +std::string
  1310 +QPDF::getEncryptionKey() const
  1311 +{
  1312 + return this->encryption_key;
  1313 +}
  1314 +
856 1315 bool
857 1316 QPDF::isEncrypted() const
858 1317 {
... ...
qpdf/qpdf.cc
... ... @@ -97,7 +97,7 @@ Note that -- terminates parsing of encryption flags.\n\
97 97 Either or both of the user password and the owner password may be\n\
98 98 empty strings.\n\
99 99 \n\
100   -key-length may be 40 or 128\n\
  100 +key-length may be 40, 128, or 256\n\
101 101 \n\
102 102 Additional flags are dependent upon key length.\n\
103 103 \n\
... ... @@ -118,6 +118,11 @@ Additional flags are dependent upon key length.\n\
118 118 --use-aes=[yn] indicates whether to use AES encryption\n\
119 119 --force-V4 forces use of V=4 encryption handler\n\
120 120 \n\
  121 + If 256, options are the same as 128 with these exceptions:\n\
  122 + --force-V4 this option is not available with 256-bit keys\n\
  123 + --use-aes this option is always on with 256-bit keys\n\
  124 + --force-R5 forces use of deprecated R=5 encryption\n\
  125 +\n\
121 126 print-opt may be:\n\
122 127 \n\
123 128 full allow full printing\n\
... ... @@ -283,6 +288,9 @@ static std::string show_encryption_method(QPDF::encryption_method_e method)
283 288 case QPDF::e_aes:
284 289 result = "AESv2";
285 290 break;
  291 + case QPDF::e_aesv3:
  292 + result = "AESv3";
  293 + break;
286 294 // no default so gcc will warn for missing case
287 295 }
288 296 return result;
... ... @@ -485,7 +493,8 @@ parse_encrypt_options(
485 493 bool& r2_print, bool& r2_modify, bool& r2_extract, bool& r2_annotate,
486 494 bool& r3_accessibility, bool& r3_extract,
487 495 qpdf_r3_print_e& r3_print, qpdf_r3_modify_e& r3_modify,
488   - bool& force_V4, bool& cleartext_metadata, bool& use_aes)
  496 + bool& force_V4, bool& cleartext_metadata, bool& use_aes,
  497 + bool& force_R5)
489 498 {
490 499 if (cur_arg + 3 >= argc)
491 500 {
... ... @@ -502,9 +511,14 @@ parse_encrypt_options(
502 511 {
503 512 keylen = 128;
504 513 }
  514 + else if (len_str == "256")
  515 + {
  516 + keylen = 256;
  517 + use_aes = true;
  518 + }
505 519 else
506 520 {
507   - usage("encryption key length must be 40 or 128");
  521 + usage("encryption key length must be 40, 128, or 256");
508 522 }
509 523 while (1)
510 524 {
... ... @@ -736,15 +750,30 @@ parse_encrypt_options(
736 750 {
737 751 usage("--force-V4 does not take a parameter");
738 752 }
739   - if (keylen == 40)
  753 + if (keylen != 128)
740 754 {
741   - usage("--force-V4 is invalid for 40-bit keys");
  755 + usage("--force-V4 is invalid only for 128-bit keys");
742 756 }
743 757 else
744 758 {
745 759 force_V4 = true;
746 760 }
747 761 }
  762 + else if (strcmp(arg, "force-R5") == 0)
  763 + {
  764 + if (parameter)
  765 + {
  766 + usage("--force-R5 does not take a parameter");
  767 + }
  768 + if (keylen != 256)
  769 + {
  770 + usage("--force-R5 is invalid only for 256-bit keys");
  771 + }
  772 + else
  773 + {
  774 + force_R5 = true;
  775 + }
  776 + }
748 777 else if (strcmp(arg, "use-aes") == 0)
749 778 {
750 779 if (parameter == 0)
... ... @@ -765,10 +794,16 @@ parse_encrypt_options(
765 794 {
766 795 usage("invalid -use-aes parameter");
767 796 }
768   - if (keylen == 40)
  797 + if ((keylen == 40) && result)
769 798 {
770 799 usage("use-aes is invalid for 40-bit keys");
771 800 }
  801 + else if ((keylen == 256) && (! result))
  802 + {
  803 + // qpdf would happily create files encrypted with RC4
  804 + // using /V=5, but Adobe reader can't read them.
  805 + usage("use-aes can't be disabled with 256-bit keys");
  806 + }
772 807 else
773 808 {
774 809 use_aes = result;
... ... @@ -921,6 +956,7 @@ int main(int argc, char* argv[])
921 956 qpdf_r3_print_e r3_print = qpdf_r3p_full;
922 957 qpdf_r3_modify_e r3_modify = qpdf_r3m_all;
923 958 bool force_V4 = false;
  959 + bool force_R5 = false;
924 960 bool cleartext_metadata = false;
925 961 bool use_aes = false;
926 962  
... ... @@ -1004,7 +1040,7 @@ int main(int argc, char* argv[])
1004 1040 user_password, owner_password, keylen,
1005 1041 r2_print, r2_modify, r2_extract, r2_annotate,
1006 1042 r3_accessibility, r3_extract, r3_print, r3_modify,
1007   - force_V4, cleartext_metadata, use_aes);
  1043 + force_V4, cleartext_metadata, use_aes, force_R5);
1008 1044 encrypt = true;
1009 1045 decrypt = false;
1010 1046 copy_encryption = false;
... ... @@ -1612,6 +1648,23 @@ int main(int argc, char* argv[])
1612 1648 r3_accessibility, r3_extract, r3_print, r3_modify);
1613 1649 }
1614 1650 }
  1651 + else if (keylen == 256)
  1652 + {
  1653 + if (force_R5)
  1654 + {
  1655 + w.setR5EncryptionParameters(
  1656 + user_password.c_str(), owner_password.c_str(),
  1657 + r3_accessibility, r3_extract, r3_print, r3_modify,
  1658 + !cleartext_metadata);
  1659 + }
  1660 + else
  1661 + {
  1662 + w.setR6EncryptionParameters(
  1663 + user_password.c_str(), owner_password.c_str(),
  1664 + r3_accessibility, r3_extract, r3_print, r3_modify,
  1665 + !cleartext_metadata);
  1666 + }
  1667 + }
1615 1668 else
1616 1669 {
1617 1670 throw std::logic_error("bad encryption keylen");
... ...
qpdf/qpdf.testcov
... ... @@ -243,6 +243,7 @@ QPDFWriter extra header text add newline 0
243 243 QPDF bogus 0 offset 0
244 244 QPDF global offset 0
245 245 QPDFWriter make stream key direct 0
  246 +QPDFWriter copy V5 0
246 247 QPDFWriter increasing extension level 0
247 248 QPDFWriter make Extensions direct 0
248 249 QPDFWriter make ADBE direct 1
... ... @@ -253,3 +254,5 @@ QPDFWriter remove existing Extensions 0
253 254 QPDFWriter skip Extensions 0
254 255 QPDFWriter preserve ADBE 0
255 256 QPDF_encryption skip 0x28 0
  257 +QPDF_encrypt crypt array 0
  258 +QPDF_encryption CFM AESV3 0
... ...
qpdf/qtest/qpdf.test
... ... @@ -1250,6 +1250,10 @@ $td-&gt;notify(&quot;--- Encryption Tests ---&quot;);
1250 1250 # resulting files were saved and manually checked with Acrobat 5.0 to
1251 1251 # ensure that the security settings were as intended.
1252 1252  
  1253 +# The enc-XI-file.pdf files were treated the same way but with Acrobat
  1254 +# XI instead of Acrobat 5.0. They were used to create test files with
  1255 +# newer encryption formats.
  1256 +
1253 1257 # Values: basename, password, encryption flags, /P Encrypt key,
1254 1258 # extract-for-accessibility, extract-for-any-purpose,
1255 1259 # print-low-res, print-high-res, modify-assembly, modify-forms,
... ... @@ -1293,9 +1297,25 @@ my @encrypted_files =
1293 1297 '', -4,
1294 1298 1, 1, 1, 1, 1, 1, 1, 1, 1],
1295 1299 ['long-password', 'asdf asdf asdf asdf asdf asdf qwer'],
1296   - ['long-password', 'asdf asdf asdf asdf asdf asdf qw']);
  1300 + ['long-password', 'asdf asdf asdf asdf asdf asdf qw'],
  1301 + ['XI-base', ''],
  1302 + ['XI-R6,V5,O=master', '',
  1303 + '-extract=n -print=none -modify=assembly', -2368,
  1304 + 1, 0, 0, 0, 1, 0, 0, 0, 0],
  1305 + ['XI-R6,V5,O=master', 'master',
  1306 + '-extract=n -print=none -modify=assembly', -2368,
  1307 + 1, 0, 0, 0, 1, 0, 0, 0, 0],
  1308 + ['XI-R6,V5,U=view,O=master', 'view',
  1309 + '-print=low', -2052,
  1310 + 1, 1, 1, 0, 1, 1, 1, 1, 1],
  1311 + ['XI-R6,V5,U=view,O=master', 'master',
  1312 + '-print=low', -2052,
  1313 + 1, 1, 1, 0, 1, 1, 1, 1, 1],
  1314 + ['XI-long-password', 'qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnm'],
  1315 + ['XI-long-password', 'qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcv'],
  1316 + );
1297 1317  
1298   -$n_tests += 3 + (2 * (@encrypted_files)) + (6 * (@encrypted_files - 3)) + 9;
  1318 +$n_tests += 5 + (2 * (@encrypted_files)) + (6 * (@encrypted_files - 6)) + 9;
1299 1319  
1300 1320 $td->runtest("encrypted file",
1301 1321 {$td->COMMAND => "test_driver 2 U25A0.pdf"},
... ... @@ -1312,6 +1332,19 @@ $td-&gt;runtest(&quot;recheck encrypted file&quot;,
1312 1332 $td->EXIT_STATUS => 0},
1313 1333 $td->NORMALIZE_NEWLINES);
1314 1334  
  1335 +# Test that long passwords that are one character too short fail. We
  1336 +# test the truncation cases in the loop below by using passwords
  1337 +# longer than the supported length.
  1338 +$td->runtest("significant password characters (V < 5)",
  1339 + {$td->COMMAND => "qpdf --check enc-long-password.pdf" .
  1340 + " --password='asdf asdf asdf asdf asdf asdf q'"},
  1341 + {$td->REGEXP => ".*invalid password.*", $td->EXIT_STATUS => 2});
  1342 +$td->runtest("significant password characters (V = 5)",
  1343 + {$td->COMMAND => "qpdf --check enc-XI-long-password.pdf" .
  1344 + " --password=qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxc"},
  1345 + {$td->REGEXP => ".*invalid password.*", $td->EXIT_STATUS => 2});
  1346 +
  1347 +my $enc_base = undef;
1315 1348 foreach my $d (@encrypted_files)
1316 1349 {
1317 1350 my ($file, $pass, $xeflags, $P,
... ... @@ -1330,17 +1363,26 @@ foreach my $d (@encrypted_files)
1330 1363 "modify annotations: " . &$f($modifyannot) . "\n" .
1331 1364 "modify other: " . &$f($modifyother) . "\n" .
1332 1365 "modify anything: " . &$f($modifyall) . "\n";
  1366 + if ($file =~ m/XI-/)
  1367 + {
  1368 + $enc_details .=
  1369 + "stream encryption method: AESv3\n" .
  1370 + "string encryption method: AESv3\n" .
  1371 + "file encryption method: AESv3\n";
  1372 + }
1333 1373  
1334 1374 # Test writing to stdout
1335 1375 $td->runtest("decrypt $file",
1336 1376 {$td->COMMAND =>
1337   - "qpdf --static-id -qdf --no-original-object-ids" .
  1377 + "qpdf --static-id -qdf --object-streams=disable" .
  1378 + " --no-original-object-ids" .
1338 1379 " --password=\"$pass\" enc-$file.pdf -" .
1339 1380 " > $file.enc"},
1340 1381 {$td->STRING => "",
1341 1382 $td->EXIT_STATUS => 0});
1342   - if ($file eq 'base')
  1383 + if ($file =~ m/base$/)
1343 1384 {
  1385 + $enc_base = $file;
1344 1386 $td->runtest("check ID",
1345 1387 {$td->COMMAND => "perl check-ID.pl $file.enc"},
1346 1388 {$td->STRING => "ID okay\n",
... ... @@ -1350,20 +1392,27 @@ foreach my $d (@encrypted_files)
1350 1392 else
1351 1393 {
1352 1394 $td->runtest("check against base",
1353   - {$td->COMMAND => "./diff-encrypted base.enc $file.enc"},
  1395 + {$td->COMMAND =>
  1396 + "./diff-encrypted $enc_base.enc $file.enc"},
1354 1397 {$td->STRING => "okay\n",
1355 1398 $td->EXIT_STATUS => 0},
1356 1399 $td->NORMALIZE_NEWLINES);
1357 1400 }
1358   - if ($file =~ m/^R(\d),V(\d)(?:,U=(\w+))?(?:,O=(\w+))?$/)
  1401 + if ($file =~ m/^(?:XI-)?R(\d),V(\d)(?:,U=(\w+))?(?:,O=(\w+))?$/)
1359 1402 {
1360 1403 my $R = $1;
1361 1404 my $V = $2;
1362 1405 my $upass = $3 || "";
1363 1406 my $opass = $4 || "";
1364   - my $bits = (($V == 2) ? 128 : 40);
  1407 + my $bits = (($V == 5) ? 256 : ($V == 2) ? 128 : 40);
1365 1408  
1366 1409 my $eflags = "-encrypt \"$upass\" \"$opass\" $bits $xeflags --";
  1410 + if (($pass ne $upass) && ($V >= 5))
  1411 + {
  1412 + # V >= 5 can no longer recover user password with owner
  1413 + # password.
  1414 + $upass = "";
  1415 + }
1367 1416 $td->runtest("encrypt $file",
1368 1417 {$td->COMMAND =>
1369 1418 "qpdf --static-id --no-original-object-ids -qdf" .
... ... @@ -1488,7 +1537,7 @@ $td-&gt;runtest(&quot;check linearization&quot;,
1488 1537 $td->NORMALIZE_NEWLINES);
1489 1538  
1490 1539 # Test AES encryption in various ways.
1491   -$n_tests += 14;
  1540 +$n_tests += 18;
1492 1541 $td->runtest("encrypt with AES",
1493 1542 {$td->COMMAND => "qpdf --encrypt '' '' 128 --use-aes=y --" .
1494 1543 " enc-base.pdf a.pdf"},
... ... @@ -1548,6 +1597,24 @@ $td-&gt;runtest(&quot;make sure there is no xref stream&quot;,
1548 1597 {$td->COMMAND => "grep /ObjStm b.pdf | wc -l"},
1549 1598 {$td->REGEXP => "\\s*0\\s*", $td->EXIT_STATUS => 0},
1550 1599 $td->NORMALIZE_NEWLINES);
  1600 +$td->runtest("encrypt with V=5,R=5",
  1601 + {$td->COMMAND =>
  1602 + "qpdf --encrypt user owner 256 --force-R5 -- " .
  1603 + "minimal.pdf a.pdf"},
  1604 + {$td->STRING => "", $td->EXIT_STATUS => 0});
  1605 +$td->runtest("check encryption",
  1606 + {$td->COMMAND => "qpdf --check a.pdf --password=owner"},
  1607 + {$td->FILE => "V5R5.out", $td->EXIT_STATUS => 0},
  1608 + $td->NORMALIZE_NEWLINES);
  1609 +$td->runtest("encrypt with V=5,R=6",
  1610 + {$td->COMMAND =>
  1611 + "qpdf --encrypt user owner 256 -- " .
  1612 + "minimal.pdf a.pdf"},
  1613 + {$td->STRING => "", $td->EXIT_STATUS => 0});
  1614 +$td->runtest("check encryption",
  1615 + {$td->COMMAND => "qpdf --check a.pdf --password=user"},
  1616 + {$td->FILE => "V5R6.out", $td->EXIT_STATUS => 0},
  1617 + $td->NORMALIZE_NEWLINES);
1551 1618  
1552 1619 # Look at some actual V4 files
1553 1620 $n_tests += 14;
... ... @@ -1629,6 +1696,36 @@ $td-&gt;runtest(&quot;compare qdf&quot;,
1629 1696 {$td->STRING => "okay\n", $td->EXIT_STATUS => 0},
1630 1697 $td->NORMALIZE_NEWLINES);
1631 1698  
  1699 +# Files with attachments
  1700 +my @attachments = (
  1701 + 'enc-XI-attachments-base.pdf',
  1702 + 'enc-XI-R6,V5,U=attachment,encrypted-attachments.pdf',
  1703 + 'enc-XI-R6,V5,U=view,attachments,cleartext-metadata.pdf');
  1704 +$n_tests += 4 * @attachments;
  1705 +foreach my $f (@attachments)
  1706 +{
  1707 + my $pass = '';
  1708 + my $tpass = '';
  1709 + if ($f =~ m/U=([^,]+)/)
  1710 + {
  1711 + $pass = "--password=$1";
  1712 + $tpass = $1;
  1713 + }
  1714 + $td->runtest("decrypt $f",
  1715 + {$td->COMMAND => "qpdf --decrypt $pass $f a.pdf"},
  1716 + {$td->STRING => "", $td->EXIT_STATUS => 0});
  1717 + $td->runtest("extract attachments",
  1718 + {$td->COMMAND => "test_driver 35 a.pdf"},
  1719 + {$td->FILE => "attachments.out", $td->EXIT_STATUS => 0},
  1720 + $td->NORMALIZE_NEWLINES);
  1721 + $td->runtest("copy $f",
  1722 + {$td->COMMAND => "qpdf $pass $f a.pdf"},
  1723 + {$td->STRING => "", $td->EXIT_STATUS => 0});
  1724 + $td->runtest("extract attachments",
  1725 + {$td->COMMAND => "test_driver 35 a.pdf $tpass"},
  1726 + {$td->FILE => "attachments.out", $td->EXIT_STATUS => 0},
  1727 + $td->NORMALIZE_NEWLINES);
  1728 +}
1632 1729  
1633 1730 show_ntests();
1634 1731 # ----------
... ...
qpdf/qtest/qpdf/V5R5.out 0 → 100644
  1 +checking a.pdf
  2 +PDF Version: 1.7 extension level 3
  3 +R = 5
  4 +P = -4
  5 +User password =
  6 +extract for accessibility: allowed
  7 +extract for any purpose: allowed
  8 +print low resolution: allowed
  9 +print high resolution: allowed
  10 +modify document assembly: allowed
  11 +modify forms: allowed
  12 +modify annotations: allowed
  13 +modify other: allowed
  14 +modify anything: allowed
  15 +stream encryption method: AESv3
  16 +string encryption method: AESv3
  17 +file encryption method: AESv3
  18 +File is not linearized
  19 +No syntax or stream encoding errors found; the file may still contain
  20 +errors that qpdf cannot detect
... ...
qpdf/qtest/qpdf/V5R6.out 0 → 100644
  1 +checking a.pdf
  2 +PDF Version: 1.7 extension level 8
  3 +R = 6
  4 +P = -4
  5 +User password = user
  6 +extract for accessibility: allowed
  7 +extract for any purpose: allowed
  8 +print low resolution: allowed
  9 +print high resolution: allowed
  10 +modify document assembly: allowed
  11 +modify forms: allowed
  12 +modify annotations: allowed
  13 +modify other: allowed
  14 +modify anything: allowed
  15 +stream encryption method: AESv3
  16 +string encryption method: AESv3
  17 +file encryption method: AESv3
  18 +File is not linearized
  19 +No syntax or stream encoding errors found; the file may still contain
  20 +errors that qpdf cannot detect
... ...
qpdf/qtest/qpdf/attachments.out 0 → 100644
  1 +attachment1.txt:
  2 +This is the first attachment.
  3 +--END--
  4 +attachment2.png:
  5 +.PNG........IHDR...1 (2620 bytes)--END--
  6 +test 35 done
... ...
qpdf/qtest/qpdf/diff-encrypted
1 1 #!/bin/sh
2   -lines=$(expr + $(diff $1 $2 | egrep '^[<>]' | egrep -v 'Date' | wc -l))
  2 +lines=$(expr + $(diff $1 $2 | egrep '^[<>]' | egrep -v '(Date|InstanceID)' | wc -l))
3 3 if [ "$lines" = "0" ]; then
4 4 echo okay
5 5 else
... ...
qpdf/qtest/qpdf/enc-XI-R6,V5,O=master.pdf 0 → 100644
No preview for this file type
qpdf/qtest/qpdf/enc-XI-R6,V5,U=attachment,encrypted-attachments.pdf 0 → 100644
No preview for this file type
qpdf/qtest/qpdf/enc-XI-R6,V5,U=view,O=master.pdf 0 → 100644
No preview for this file type
qpdf/qtest/qpdf/enc-XI-R6,V5,U=view,attachments,cleartext-metadata.pdf 0 → 100644
No preview for this file type
qpdf/qtest/qpdf/enc-XI-attachments-base.pdf 0 → 100644
No preview for this file type
qpdf/qtest/qpdf/enc-XI-base.pdf 0 → 100644
No preview for this file type
qpdf/qtest/qpdf/enc-XI-long-password.pdf 0 → 100644
No preview for this file type
qpdf/test_driver.cc
... ... @@ -112,7 +112,12 @@ void runtest(int n, char const* filename1, char const* arg2)
112 112 {
113 113 pdf.setAttemptRecovery(false);
114 114 }
115   - if (n % 2 == 0)
  115 + if ((n == 35) && (arg2 != 0))
  116 + {
  117 + // arg2 is password
  118 + pdf.processFile(filename1, arg2);
  119 + }
  120 + else if (n % 2 == 0)
116 121 {
117 122 if (n % 4 == 0)
118 123 {
... ... @@ -1150,6 +1155,65 @@ void runtest(int n, char const* filename1, char const* arg2)
1150 1155 << "extension level: " << pdf.getExtensionLevel() << std::endl
1151 1156 << pdf.getRoot().getKey("/Extensions").unparse() << std::endl;
1152 1157 }
  1158 + else if (n == 35)
  1159 + {
  1160 + // Extract attachments
  1161 +
  1162 + std::map<std::string, PointerHolder<Buffer> > attachments;
  1163 + QPDFObjectHandle root = pdf.getRoot();
  1164 + QPDFObjectHandle names = root.getKey("/Names");
  1165 + QPDFObjectHandle embeddedFiles = names.getKey("/EmbeddedFiles");
  1166 + names = embeddedFiles.getKey("/Names");
  1167 + for (int i = 0; i < names.getArrayNItems(); ++i)
  1168 + {
  1169 + QPDFObjectHandle item = names.getArrayItem(i);
  1170 + if (item.isDictionary() &&
  1171 + item.getKey("/Type").isName() &&
  1172 + (item.getKey("/Type").getName() == "/Filespec") &&
  1173 + item.getKey("/EF").isDictionary() &&
  1174 + item.getKey("/EF").getKey("/F").isStream())
  1175 + {
  1176 + std::string filename = item.getKey("/F").getStringValue();
  1177 + QPDFObjectHandle stream = item.getKey("/EF").getKey("/F");
  1178 + attachments[filename] = stream.getStreamData();
  1179 + }
  1180 + }
  1181 + for (std::map<std::string, PointerHolder<Buffer> >::iterator iter =
  1182 + attachments.begin(); iter != attachments.end(); ++iter)
  1183 + {
  1184 + std::string const& filename = (*iter).first;
  1185 + std::string data = std::string(
  1186 + (char const*)(*iter).second->getBuffer(),
  1187 + (*iter).second->getSize());
  1188 + bool is_binary = false;
  1189 + for (size_t i = 0; i < data.size(); ++i)
  1190 + {
  1191 + if (data[i] < 0)
  1192 + {
  1193 + is_binary = true;
  1194 + break;
  1195 + }
  1196 + }
  1197 + if (is_binary)
  1198 + {
  1199 + std::string t;
  1200 + for (size_t i = 0; i < std::min(data.size(), (size_t)20); ++i)
  1201 + {
  1202 + if ((data[i] >= 32) && (data[i] <= 126))
  1203 + {
  1204 + t += data[i];
  1205 + }
  1206 + else
  1207 + {
  1208 + t += ".";
  1209 + }
  1210 + }
  1211 + t += " (" + QUtil::int_to_string(data.size()) + " bytes)";
  1212 + data = t;
  1213 + }
  1214 + std::cout << filename << ":\n" << data << "--END--\n";
  1215 + }
  1216 + }
1153 1217 else
1154 1218 {
1155 1219 throw std::runtime_error(std::string("invalid test ") +
... ...