Commit e25910b59ab84c7aada0b651e7dbb47f6830ad3d

Authored by Jay Berkenbilt
1 parent c13bc66d

reading crypt filters is largely implemented but not fully tested

git-svn-id: svn+q:///qpdf/trunk@812 71b93d88-0707-0410-a8cf-f5a4172ac649
include/qpdf/QPDF.hh
@@ -87,6 +87,7 @@ class DLL_EXPORT QPDF @@ -87,6 +87,7 @@ class DLL_EXPORT QPDF
87 87
88 // Encryption support 88 // Encryption support
89 89
  90 + enum encryption_method_e { e_none, e_unknown, e_rc4, e_aes };
90 struct EncryptionData 91 struct EncryptionData
91 { 92 {
92 // This class holds data read from the encryption dictionary. 93 // This class holds data read from the encryption dictionary.
@@ -397,6 +398,7 @@ class DLL_EXPORT QPDF @@ -397,6 +398,7 @@ class DLL_EXPORT QPDF
397 std::vector<QPDFObjectHandle>& result); 398 std::vector<QPDFObjectHandle>& result);
398 399
399 // methods to support encryption -- implemented in QPDF_encryption.cc 400 // methods to support encryption -- implemented in QPDF_encryption.cc
  401 + encryption_method_e interpretCF(QPDFObjectHandle);
400 void initializeEncryption(); 402 void initializeEncryption();
401 std::string getKeyForObject(int objid, int generation, bool use_aes); 403 std::string getKeyForObject(int objid, int generation, bool use_aes);
402 void decryptString(std::string&, int objid, int generation); 404 void decryptString(std::string&, int objid, int generation);
@@ -739,7 +741,10 @@ class DLL_EXPORT QPDF @@ -739,7 +741,10 @@ class DLL_EXPORT QPDF
739 bool attempt_recovery; 741 bool attempt_recovery;
740 int encryption_V; 742 int encryption_V;
741 bool encrypt_metadata; 743 bool encrypt_metadata;
742 - QPDFObjectHandle encryption_dictionary; 744 + std::map<std::string, encryption_method_e> crypt_filters;
  745 + encryption_method_e cf_stream;
  746 + encryption_method_e cf_string;
  747 + encryption_method_e cf_file;
743 std::string provided_password; 748 std::string provided_password;
744 std::string user_password; 749 std::string user_password;
745 std::string encryption_key; 750 std::string encryption_key;
libqpdf/Pl_AES_PDF.cc
@@ -9,7 +9,7 @@ @@ -9,7 +9,7 @@
9 #include <stdlib.h> 9 #include <stdlib.h>
10 10
11 Pl_AES_PDF::Pl_AES_PDF(char const* identifier, Pipeline* next, 11 Pl_AES_PDF::Pl_AES_PDF(char const* identifier, Pipeline* next,
12 - bool encrypt, unsigned char key[key_size]) : 12 + bool encrypt, unsigned char const key[key_size]) :
13 Pipeline(identifier, next), 13 Pipeline(identifier, next),
14 encrypt(encrypt), 14 encrypt(encrypt),
15 cbc_mode(true), 15 cbc_mode(true),
libqpdf/QPDF.cc
@@ -255,6 +255,9 @@ QPDF::QPDF() : @@ -255,6 +255,9 @@ QPDF::QPDF() :
255 attempt_recovery(true), 255 attempt_recovery(true),
256 encryption_V(0), 256 encryption_V(0),
257 encrypt_metadata(true), 257 encrypt_metadata(true),
  258 + cf_stream(e_none),
  259 + cf_string(e_none),
  260 + cf_file(e_none),
258 cached_key_objid(0), 261 cached_key_objid(0),
259 cached_key_generation(0), 262 cached_key_generation(0),
260 first_xref_item_offset(0), 263 first_xref_item_offset(0),
libqpdf/QPDF_Stream.cc
@@ -136,6 +136,11 @@ QPDF_Stream::filterable(std::vector&lt;std::string&gt;&amp; filters, @@ -136,6 +136,11 @@ QPDF_Stream::filterable(std::vector&lt;std::string&gt;&amp; filters,
136 filterable = false; 136 filterable = false;
137 } 137 }
138 } 138 }
  139 + else if (key == "/Crypt")
  140 + {
  141 + // XXX untested
  142 + // we handle this in decryptStream
  143 + }
139 else 144 else
140 { 145 {
141 filterable = false; 146 filterable = false;
@@ -212,7 +217,8 @@ QPDF_Stream::filterable(std::vector&lt;std::string&gt;&amp; filters, @@ -212,7 +217,8 @@ QPDF_Stream::filterable(std::vector&lt;std::string&gt;&amp; filters,
212 iter != filters.end(); ++iter) 217 iter != filters.end(); ++iter)
213 { 218 {
214 std::string const& filter = *iter; 219 std::string const& filter = *iter;
215 - if (! ((filter == "/FlateDecode") || 220 + if (! ((filter == "/Crypt") ||
  221 + (filter == "/FlateDecode") ||
216 (filter == "/LZWDecode") || 222 (filter == "/LZWDecode") ||
217 (filter == "/ASCII85Decode") || 223 (filter == "/ASCII85Decode") ||
218 (filter == "/ASCIIHexDecode"))) 224 (filter == "/ASCIIHexDecode")))
@@ -266,7 +272,11 @@ QPDF_Stream::pipeStreamData(Pipeline* pipeline, bool filter, @@ -266,7 +272,11 @@ QPDF_Stream::pipeStreamData(Pipeline* pipeline, bool filter,
266 iter != filters.rend(); ++iter) 272 iter != filters.rend(); ++iter)
267 { 273 {
268 std::string const& filter = *iter; 274 std::string const& filter = *iter;
269 - if (filter == "/FlateDecode") 275 + if (filter == "/Crypt")
  276 + {
  277 + // Ignore -- handled by pipeStreamData
  278 + }
  279 + else if (filter == "/FlateDecode")
270 { 280 {
271 if (predictor == 12) 281 if (predictor == 12)
272 { 282 {
libqpdf/QPDF_encryption.cc
@@ -9,6 +9,7 @@ @@ -9,6 +9,7 @@
9 #include <qpdf/QUtil.hh> 9 #include <qpdf/QUtil.hh>
10 #include <qpdf/Pl_RC4.hh> 10 #include <qpdf/Pl_RC4.hh>
11 #include <qpdf/Pl_AES_PDF.hh> 11 #include <qpdf/Pl_AES_PDF.hh>
  12 +#include <qpdf/Pl_Buffer.hh>
12 #include <qpdf/RC4.hh> 13 #include <qpdf/RC4.hh>
13 #include <qpdf/MD5.hh> 14 #include <qpdf/MD5.hh>
14 15
@@ -281,6 +282,27 @@ check_owner_password(std::string&amp; user_password, @@ -281,6 +282,27 @@ check_owner_password(std::string&amp; user_password,
281 return result; 282 return result;
282 } 283 }
283 284
  285 +QPDF::encryption_method_e
  286 +QPDF::interpretCF(QPDFObjectHandle cf)
  287 +{
  288 + if (cf.isName())
  289 + {
  290 + std::string filter = cf.getName();
  291 + if (this->crypt_filters.count(filter) != 0)
  292 + {
  293 + return this->crypt_filters[filter];
  294 + }
  295 + else
  296 + {
  297 + return e_unknown;
  298 + }
  299 + }
  300 + else
  301 + {
  302 + return e_none;
  303 + }
  304 +}
  305 +
284 void 306 void
285 QPDF::initializeEncryption() 307 QPDF::initializeEncryption()
286 { 308 {
@@ -322,8 +344,7 @@ QPDF::initializeEncryption() @@ -322,8 +344,7 @@ QPDF::initializeEncryption()
322 "incorrect length"); 344 "incorrect length");
323 } 345 }
324 346
325 - this->encryption_dictionary = this->trailer.getKey("/Encrypt");  
326 - QPDFObjectHandle& encryption_dict = this->encryption_dictionary; 347 + QPDFObjectHandle encryption_dict = this->trailer.getKey("/Encrypt");
327 if (! encryption_dict.isDictionary()) 348 if (! encryption_dict.isDictionary())
328 { 349 {
329 throw QPDFExc(this->file.getName(), this->file.getLastOffset(), 350 throw QPDFExc(this->file.getName(), this->file.getLastOffset(),
@@ -336,6 +357,12 @@ QPDF::initializeEncryption() @@ -336,6 +357,12 @@ QPDF::initializeEncryption()
336 throw QPDFExc(this->file.getName(), this->file.getLastOffset(), 357 throw QPDFExc(this->file.getName(), this->file.getLastOffset(),
337 "unsupported encryption filter"); 358 "unsupported encryption filter");
338 } 359 }
  360 + if (! encryption_dict.getKey("/SubFilter").isNull())
  361 + {
  362 + warn(QPDFExc(this->file.getName(), this->file.getLastOffset(),
  363 + "file uses encryption SubFilters,"
  364 + " which qpdf does not support"));
  365 + }
339 366
340 if (! (encryption_dict.getKey("/V").isInteger() && 367 if (! (encryption_dict.getKey("/V").isInteger() &&
341 encryption_dict.getKey("/R").isInteger() && 368 encryption_dict.getKey("/R").isInteger() &&
@@ -388,10 +415,55 @@ QPDF::initializeEncryption() @@ -388,10 +415,55 @@ QPDF::initializeEncryption()
388 encryption_dict.getKey("/EncryptMetadata").getBoolValue(); 415 encryption_dict.getKey("/EncryptMetadata").getBoolValue();
389 } 416 }
390 417
391 - // XXX warn if /SubFilter is present  
392 if (V == 4) 418 if (V == 4)
393 { 419 {
394 - // XXX get CF 420 + QPDFObjectHandle CF = encryption_dict.getKey("/CF");
  421 + std::set<std::string> keys = CF.getKeys();
  422 + for (std::set<std::string>::iterator iter = keys.begin();
  423 + iter != keys.end(); ++iter)
  424 + {
  425 + std::string const& filter = *iter;
  426 + QPDFObjectHandle cdict = CF.getKey(filter);
  427 + if (cdict.isDictionary())
  428 + {
  429 + encryption_method_e method = e_none;
  430 + if (cdict.getKey("/CFM").isName())
  431 + {
  432 + std::string method_name = cdict.getKey("/CFM").getName();
  433 + if (method_name == "/V2")
  434 + {
  435 + // XXX coverage
  436 + method = e_rc4;
  437 + }
  438 + else if (method_name == "/AESV2")
  439 + {
  440 + // XXX coverage
  441 + method = e_aes;
  442 + }
  443 + else
  444 + {
  445 + // Don't complain now -- maybe we won't need
  446 + // to reference this type.
  447 + method = e_unknown;
  448 + }
  449 + }
  450 + this->crypt_filters[filter] = method;
  451 + }
  452 + }
  453 +
  454 + QPDFObjectHandle StmF = encryption_dict.getKey("/StmF");
  455 + QPDFObjectHandle StrF = encryption_dict.getKey("/StrF");
  456 + QPDFObjectHandle EFF = encryption_dict.getKey("/EFF");
  457 + this->cf_stream = interpretCF(StmF);
  458 + this->cf_string = interpretCF(StrF);
  459 + if (EFF.isName())
  460 + {
  461 + this->cf_file = interpretCF(EFF);
  462 + }
  463 + else
  464 + {
  465 + this->cf_file = this->cf_stream;
  466 + }
395 } 467 }
396 EncryptionData data(V, R, Length / 8, P, O, U, id1, this->encrypt_metadata); 468 EncryptionData data(V, R, Length / 8, P, O, U, id1, this->encrypt_metadata);
397 if (check_owner_password( 469 if (check_owner_password(
@@ -440,15 +512,50 @@ QPDF::decryptString(std::string&amp; str, int objid, int generation) @@ -440,15 +512,50 @@ QPDF::decryptString(std::string&amp; str, int objid, int generation)
440 { 512 {
441 return; 513 return;
442 } 514 }
443 - bool use_aes = false; // XXX 515 + bool use_aes = false;
  516 + if (this->encryption_V == 4)
  517 + {
  518 + switch (this->cf_string)
  519 + {
  520 + case e_none:
  521 + return;
  522 +
  523 + case e_aes:
  524 + use_aes = true;
  525 + break;
  526 +
  527 + case e_rc4:
  528 + break;
  529 +
  530 + default:
  531 + warn(QPDFExc(this->file.getName(), this->file.getLastOffset(),
  532 + "unknown encryption filter for strings"
  533 + " (check /StrF in /Encrypt dictionary);"
  534 + " strings may be decrypted improperly"));
  535 + // To avoid repeated warnings, reset cf_string. Assume
  536 + // we'd want to use AES if V == 4.
  537 + this->cf_string = e_aes;
  538 + break;
  539 + }
  540 + }
  541 +
444 std::string key = getKeyForObject(objid, generation, use_aes); 542 std::string key = getKeyForObject(objid, generation, use_aes);
445 if (use_aes) 543 if (use_aes)
446 { 544 {
447 - // XXX  
448 - throw std::logic_error("XXX"); 545 + // XXX coverage
  546 + assert(key.length() == Pl_AES_PDF::key_size);
  547 + Pl_Buffer bufpl("decrypted string");
  548 + Pl_AES_PDF pl("aes decrypt string", &bufpl, false,
  549 + (unsigned char const*)key.c_str());
  550 + pl.write((unsigned char*)str.c_str(), str.length());
  551 + pl.finish();
  552 + Buffer* buf = bufpl.getBuffer();
  553 + str = std::string((char*)buf->getBuffer(), (size_t)buf->getSize());
  554 + delete buf;
449 } 555 }
450 else 556 else
451 { 557 {
  558 + QTC::TC("qpdf", "QPDF_encryption rc4 decode string");
452 unsigned int vlen = str.length(); 559 unsigned int vlen = str.length();
453 char* tmp = QUtil::copy_string(str); 560 char* tmp = QUtil::copy_string(str);
454 RC4 rc4((unsigned char const*)key.c_str(), key.length()); 561 RC4 rc4((unsigned char const*)key.c_str(), key.length());
@@ -463,7 +570,6 @@ QPDF::decryptStream(Pipeline*&amp; pipeline, int objid, int generation, @@ -463,7 +570,6 @@ QPDF::decryptStream(Pipeline*&amp; pipeline, int objid, int generation,
463 QPDFObjectHandle& stream_dict, 570 QPDFObjectHandle& stream_dict,
464 std::vector<PointerHolder<Pipeline> >& heap) 571 std::vector<PointerHolder<Pipeline> >& heap)
465 { 572 {
466 - bool decrypt = true;  
467 std::string type; 573 std::string type;
468 if (stream_dict.getKey("/Type").isName()) 574 if (stream_dict.getKey("/Type").isName())
469 { 575 {
@@ -471,34 +577,77 @@ QPDF::decryptStream(Pipeline*&amp; pipeline, int objid, int generation, @@ -471,34 +577,77 @@ QPDF::decryptStream(Pipeline*&amp; pipeline, int objid, int generation,
471 } 577 }
472 if (type == "/XRef") 578 if (type == "/XRef")
473 { 579 {
474 - QTC::TC("qpdf", "QPDF piping xref stream from encrypted file");  
475 - decrypt = false; 580 + QTC::TC("qpdf", "QPDF_encryption xref stream from encrypted file");
  581 + return;
476 } 582 }
477 bool use_aes = false; 583 bool use_aes = false;
478 if (this->encryption_V == 4) 584 if (this->encryption_V == 4)
479 { 585 {
480 - if ((! this->encrypt_metadata) && (type == "/Metadata")) 586 + encryption_method_e method = e_unknown;
  587 + std::string method_source = "/StmF from /Encrypt dictionary";
  588 +
  589 + if (stream_dict.getKey("/DecodeParms").isDictionary())
481 { 590 {
482 - // XXX no test case for this  
483 - decrypt = false; 591 + QPDFObjectHandle decode_parms = stream_dict.getKey("/DecodeParms");
  592 + if (decode_parms.getKey("/Crypt").isDictionary())
  593 + {
  594 + // XXX coverage
  595 + QPDFObjectHandle crypt = decode_parms.getKey("/Crypt");
  596 + method = interpretCF(crypt.getKey("/Name"));
  597 + method_source = "stream's Crypt decode parameters";
  598 + }
484 } 599 }
485 - // XXX check crypt filter; if not found, use StmF; see TODO  
486 - use_aes = true; // XXX  
487 - }  
488 - if (! decrypt)  
489 - {  
490 - return;  
491 - }  
492 600
  601 + if (method == e_unknown)
  602 + {
  603 + if ((! this->encrypt_metadata) && (type == "/Metadata"))
  604 + {
  605 + // XXX coverage
  606 + method = e_none;
  607 + }
  608 + else
  609 + {
  610 + method = this->cf_stream;
  611 + }
  612 + // XXX What about embedded file streams?
  613 + }
  614 + use_aes = false;
  615 + switch (this->cf_stream)
  616 + {
  617 + case e_none:
  618 + return;
  619 + break;
  620 +
  621 + case e_aes:
  622 + use_aes = true;
  623 + break;
  624 +
  625 + case e_rc4:
  626 + break;
  627 +
  628 + default:
  629 + // filter local to this stream.
  630 + warn(QPDFExc(this->file.getName(), this->file.getLastOffset(),
  631 + "unknown encryption filter for streams"
  632 + " (check " + method_source + ");"
  633 + " streams may be decrypted improperly"));
  634 + // To avoid repeated warnings, reset cf_stream. Assume
  635 + // we'd want to use AES if V == 4.
  636 + this->cf_stream = e_aes;
  637 + break;
  638 + }
  639 + }
493 std::string key = getKeyForObject(objid, generation, use_aes); 640 std::string key = getKeyForObject(objid, generation, use_aes);
494 if (use_aes) 641 if (use_aes)
495 { 642 {
  643 + // XXX coverage
496 assert(key.length() == Pl_AES_PDF::key_size); 644 assert(key.length() == Pl_AES_PDF::key_size);
497 pipeline = new Pl_AES_PDF("AES stream decryption", pipeline, 645 pipeline = new Pl_AES_PDF("AES stream decryption", pipeline,
498 false, (unsigned char*) key.c_str()); 646 false, (unsigned char*) key.c_str());
499 } 647 }
500 else 648 else
501 { 649 {
  650 + QTC::TC("qpdf", "QPDF_encryption rc4 decode stream");
502 pipeline = new Pl_RC4("RC4 stream decryption", pipeline, 651 pipeline = new Pl_RC4("RC4 stream decryption", pipeline,
503 (unsigned char*) key.c_str(), key.length()); 652 (unsigned char*) key.c_str(), key.length());
504 } 653 }
libqpdf/qpdf/Pl_AES_PDF.hh
@@ -12,7 +12,7 @@ class DLL_EXPORT Pl_AES_PDF: public Pipeline @@ -12,7 +12,7 @@ class DLL_EXPORT Pl_AES_PDF: public Pipeline
12 // key_data should be a pointer to key_size bytes of data 12 // key_data should be a pointer to key_size bytes of data
13 static unsigned int const key_size = 16; 13 static unsigned int const key_size = 16;
14 Pl_AES_PDF(char const* identifier, Pipeline* next, 14 Pl_AES_PDF(char const* identifier, Pipeline* next,
15 - bool encrypt, unsigned char key[key_size]); 15 + bool encrypt, unsigned char const key[key_size]);
16 virtual ~Pl_AES_PDF(); 16 virtual ~Pl_AES_PDF();
17 17
18 virtual void write(unsigned char* data, int len); 18 virtual void write(unsigned char* data, int len);
qpdf/qpdf.testcov
@@ -112,7 +112,7 @@ QPDF found wrong endstream in recovery 0 @@ -112,7 +112,7 @@ QPDF found wrong endstream in recovery 0
112 QPDFObjectHandle indirect to unknown 0 112 QPDFObjectHandle indirect to unknown 0
113 QPDF_Stream pipeStreamData with null pipeline 0 113 QPDF_Stream pipeStreamData with null pipeline 0
114 QPDFWriter not recompressing /FlateDecode 0 114 QPDFWriter not recompressing /FlateDecode 0
115 -QPDF piping xref stream from encrypted file 0 115 +QPDF_encryption xref stream from encrypted file 0
116 unable to filter 0 116 unable to filter 0
117 QPDF_String non-trivial UTF-16 0 117 QPDF_String non-trivial UTF-16 0
118 QPDF xref overwrite object 0 118 QPDF xref overwrite object 0
@@ -158,3 +158,5 @@ QPDFWriter using forced PDF version 0 @@ -158,3 +158,5 @@ QPDFWriter using forced PDF version 0
158 qpdf-c called qpdf_set_minimum_pdf_version 0 158 qpdf-c called qpdf_set_minimum_pdf_version 0
159 qpdf-c called qpdf_force_pdf_version 0 159 qpdf-c called qpdf_force_pdf_version 0
160 qpdf-c called qpdf_init_write multiple times 0 160 qpdf-c called qpdf_init_write multiple times 0
  161 +QPDF_encryption rc4 decode string 0
  162 +QPDF_encryption rc4 decode stream 0