Commit c13bc66de8d6ef553c4ed05247774476a859a5f3

Authored by Jay Berkenbilt
1 parent 27e8d4bb

checkpoint -- partially implemented /V=4 encryption

git-svn-id: svn+q:///qpdf/trunk@811 71b93d88-0707-0410-a8cf-f5a4172ac649
... ... @@ -43,6 +43,49 @@
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.
  48 +
  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.
  52 +
  53 + /Encrypt keys (if V == 4)
  54 +
  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
  59 +
  60 + /CF - keys are crypt filter names, values are are crypt
  61 + dictionaries
  62 +
  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.
  69 +
  70 + /Identity means not to encrypt.
  71 +
  72 + Crypt Dictionaries
  73 +
  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.
  80 +
  81 + We will ignore remaining fields and values.
  82 +
  83 + Remember to honor /EncryptMetadata; applies to streams of /Type
  84 + /Metadata
  85 +
  86 + When we write encrypted files, we must remember to omit any
  87 + encryption filter settings from original streams.
  88 +
46 89 2.2
47 90 ===
48 91  
... ... @@ -52,22 +95,6 @@
52 95 Stefan Heinsen <stefan.heinsen@gmx.de> in August, 2009. He seems
53 96 to like to send encrypted mail. (key 01FCC336)
54 97  
55   - * See whether we can do anything with /V > 3 in the encryption
56   - dictionary. (V = 4 is Crypt Filters.) See
57   - ~/Q/pdf-collection/R4-encrypt-PDF_Inside_and_Out.pdf
58   -
59   - Search for XXX in the code. Implementation has been started.
60   -
61   - Algorithms from PDF Spec in QPDF_encrypt.cc have been updated. We
62   - can at least properly verify the user password with an R4 file. In
63   - order to finish the job, we need an aes-128-cbc implementation.
64   - Then we can fill in the gaps for the aes pipeline and actually run
65   - the test suite. The pipeline may be able to hard-code the
66   - initialization vector stuff by taking the first block of input and
67   - by writing a random block for output. The padding is already in
68   - the code, but the initialization vector is not since I accidentally
69   - started using an aes256 implementation instead of aes128-cbc.
70   -
71 98 * Look at page splitting.
72 99  
73 100  
... ... @@ -109,9 +136,9 @@ General
109 136 of doing this seems very low since no viewer seems to care, so it's
110 137 probably not worth it.
111 138  
112   - * Embedded files streams: figure out why running qpdf over the pdf
113   - 1.7 spec results in a file that crashes acrobat reader when you try
114   - to save nested documents.
  139 + * Embedded file streams: figure out why running qpdf over the pdf 1.7
  140 + spec results in a file that crashes acrobat reader when you try to
  141 + save nested documents.
115 142  
116 143 * QPDFObjectHandle::getPageImages() doesn't notice images in
117 144 inherited resource dictionaries. See comments in that function.
... ...
include/qpdf/QPDF.hh
... ... @@ -141,7 +141,7 @@ class DLL_EXPORT QPDF
141 141  
142 142 static void compute_encryption_O_U(
143 143 char const* user_password, char const* owner_password,
144   - int V, int R, int key_len, int P,
  144 + int V, int R, int key_len, int P, bool encrypt_metadata,
145 145 std::string const& id1,
146 146 std::string& O, std::string& U);
147 147 // Return the full user password as stored in the PDF file. If
... ... @@ -398,10 +398,12 @@ class DLL_EXPORT QPDF
398 398  
399 399 // methods to support encryption -- implemented in QPDF_encryption.cc
400 400 void initializeEncryption();
401   - std::string getKeyForObject(int objid, int generation);
  401 + std::string getKeyForObject(int objid, int generation, bool use_aes);
402 402 void decryptString(std::string&, int objid, int generation);
403   - void decryptStream(Pipeline*& pipeline, int objid, int generation,
404   - std::vector<PointerHolder<Pipeline> >& heap);
  403 + void decryptStream(
  404 + Pipeline*& pipeline, int objid, int generation,
  405 + QPDFObjectHandle& stream_dict,
  406 + std::vector<PointerHolder<Pipeline> >& heap);
405 407  
406 408 // Linearization Hint table structures.
407 409 // Naming conventions:
... ... @@ -735,7 +737,9 @@ class DLL_EXPORT QPDF
735 737 bool ignore_xref_streams;
736 738 bool suppress_warnings;
737 739 bool attempt_recovery;
738   - bool encryption_use_aes;
  740 + int encryption_V;
  741 + bool encrypt_metadata;
  742 + QPDFObjectHandle encryption_dictionary;
739 743 std::string provided_password;
740 744 std::string user_password;
741 745 std::string encryption_key;
... ...
libqpdf/Pl_AES_PDF.cc
1 1 #include <qpdf/Pl_AES_PDF.hh>
2 2 #include <qpdf/QUtil.hh>
  3 +#include <qpdf/MD5.hh>
3 4 #include <cstring>
4 5 #include <assert.h>
5 6 #include <stdexcept>
6 7 #include <qpdf/rijndael.h>
7   -
8   -// XXX Still need CBC
  8 +#include <string>
  9 +#include <stdlib.h>
9 10  
10 11 Pl_AES_PDF::Pl_AES_PDF(char const* identifier, Pipeline* next,
11 12 bool encrypt, unsigned char key[key_size]) :
12 13 Pipeline(identifier, next),
13 14 encrypt(encrypt),
  15 + cbc_mode(true),
  16 + first(true),
14 17 offset(0),
15 18 nrounds(0)
16 19 {
... ... @@ -21,6 +24,7 @@ Pl_AES_PDF::Pl_AES_PDF(char const* identifier, Pipeline* next,
21 24 std::memset(this->rk, 0, sizeof(this->rk));
22 25 std::memset(this->inbuf, 0, this->buf_size);
23 26 std::memset(this->outbuf, 0, this->buf_size);
  27 + std::memset(this->cbc_block, 0, this->buf_size);
24 28 if (encrypt)
25 29 {
26 30 this->nrounds = rijndaelSetupEncrypt(this->rk, this->key, keybits);
... ... @@ -38,6 +42,12 @@ Pl_AES_PDF::~Pl_AES_PDF()
38 42 }
39 43  
40 44 void
  45 +Pl_AES_PDF::disableCBC()
  46 +{
  47 + this->cbc_mode = false;
  48 +}
  49 +
  50 +void
41 51 Pl_AES_PDF::write(unsigned char* data, int len)
42 52 {
43 53 unsigned int bytes_left = len;
... ... @@ -91,16 +101,79 @@ Pl_AES_PDF::finish()
91 101 }
92 102  
93 103 void
  104 +Pl_AES_PDF::initializeVector()
  105 +{
  106 + std::string seed_str;
  107 + seed_str += QUtil::int_to_string((int)QUtil::get_current_time());
  108 + seed_str += " QPDF aes random";
  109 + MD5 m;
  110 + m.encodeString(seed_str.c_str());
  111 + MD5::Digest digest;
  112 + m.digest(digest);
  113 + assert(sizeof(digest) >= sizeof(unsigned int));
  114 + unsigned int seed;
  115 + memcpy((void*)(&seed), digest, sizeof(unsigned int));
  116 + srandom(seed);
  117 + for (unsigned int i = 0; i < this->buf_size; ++i)
  118 + {
  119 + this->cbc_block[i] = (unsigned char)(random() & 0xff);
  120 + }
  121 +}
  122 +
  123 +void
94 124 Pl_AES_PDF::flush(bool strip_padding)
95 125 {
96 126 assert(this->offset == this->buf_size);
  127 +
  128 + if (first)
  129 + {
  130 + first = false;
  131 + if (this->cbc_mode)
  132 + {
  133 + if (encrypt)
  134 + {
  135 + // Set cbc_block to a random initialization vector and
  136 + // write it to the output stream
  137 + initializeVector();
  138 + getNext()->write(this->cbc_block, this->buf_size);
  139 + }
  140 + else
  141 + {
  142 + // Take the first block of input as the initialization
  143 + // vector. There's nothing to write at this time.
  144 + memcpy(this->cbc_block, this->inbuf, this->buf_size);
  145 + this->offset = 0;
  146 + return;
  147 + }
  148 + }
  149 + }
  150 +
97 151 if (this->encrypt)
98 152 {
  153 + if (this->cbc_mode)
  154 + {
  155 + for (unsigned int i = 0; i < this->buf_size; ++i)
  156 + {
  157 + this->inbuf[i] ^= this->cbc_block[i];
  158 + }
  159 + }
99 160 rijndaelEncrypt(this->rk, this->nrounds, this->inbuf, this->outbuf);
  161 + if (this->cbc_mode)
  162 + {
  163 + memcpy(this->cbc_block, this->outbuf, this->buf_size);
  164 + }
100 165 }
101 166 else
102 167 {
103 168 rijndaelDecrypt(this->rk, this->nrounds, this->inbuf, this->outbuf);
  169 + if (this->cbc_mode)
  170 + {
  171 + for (unsigned int i = 0; i < this->buf_size; ++i)
  172 + {
  173 + this->outbuf[i] ^= this->cbc_block[i];
  174 + }
  175 + memcpy(this->cbc_block, this->inbuf, this->buf_size);
  176 + }
104 177 }
105 178 unsigned int bytes = this->buf_size;
106 179 if (strip_padding)
... ...
libqpdf/QPDF.cc
... ... @@ -253,7 +253,8 @@ QPDF::QPDF() :
253 253 ignore_xref_streams(false),
254 254 suppress_warnings(false),
255 255 attempt_recovery(true),
256   - encryption_use_aes(false),
  256 + encryption_V(0),
  257 + encrypt_metadata(true),
257 258 cached_key_objid(0),
258 259 cached_key_generation(0),
259 260 first_xref_item_offset(0),
... ... @@ -1813,17 +1814,7 @@ QPDF::pipeStreamData(int objid, int generation,
1813 1814 std::vector<PointerHolder<Pipeline> > to_delete;
1814 1815 if (this->encrypted)
1815 1816 {
1816   - bool xref_stream = false;
1817   - if (stream_dict.getKey("/Type").isName() &&
1818   - (stream_dict.getKey("/Type").getName() == "/XRef"))
1819   - {
1820   - QTC::TC("qpdf", "QPDF piping xref stream from encrypted file");
1821   - xref_stream = true;
1822   - }
1823   - if (! xref_stream)
1824   - {
1825   - decryptStream(pipeline, objid, generation, to_delete);
1826   - }
  1817 + decryptStream(pipeline, objid, generation, stream_dict, to_delete);
1827 1818 }
1828 1819  
1829 1820 try
... ...
libqpdf/QPDFWriter.cc
... ... @@ -281,7 +281,8 @@ QPDFWriter::setEncryptionParameters(
281 281 std::string O;
282 282 std::string U;
283 283 QPDF::compute_encryption_O_U(
284   - user_password, owner_password, V, R, key_len, P, this->id1, O, U);
  284 + user_password, owner_password, V, R, key_len, P,
  285 + /*XXX encrypt_metadata*/true, this->id1, O, U);
285 286 setEncryptionParametersInternal(
286 287 V, R, key_len, P, O, U, this->id1, user_password);
287 288 }
... ...
libqpdf/QPDF_encryption.cc
... ... @@ -5,11 +5,14 @@
5 5  
6 6 #include <qpdf/QPDFExc.hh>
7 7  
  8 +#include <qpdf/QTC.hh>
8 9 #include <qpdf/QUtil.hh>
9 10 #include <qpdf/Pl_RC4.hh>
  11 +#include <qpdf/Pl_AES_PDF.hh>
10 12 #include <qpdf/RC4.hh>
11 13 #include <qpdf/MD5.hh>
12 14  
  15 +#include <assert.h>
13 16 #include <string.h>
14 17  
15 18 static char const padding_string[] = {
... ... @@ -123,9 +126,6 @@ QPDF::compute_data_key(std::string const&amp; encryption_key,
123 126 md5.digest(digest);
124 127 return std::string((char*) digest,
125 128 std::min(result.length(), (size_t) 16));
126   -
127   - // XXX Item 4 in Algorithm 3.1 mentions CBC and a random number.
128   - // We still have to incorporate that.
129 129 }
130 130  
131 131 std::string
... ... @@ -322,7 +322,8 @@ QPDF::initializeEncryption()
322 322 "incorrect length");
323 323 }
324 324  
325   - QPDFObjectHandle encryption_dict = this->trailer.getKey("/Encrypt");
  325 + this->encryption_dictionary = this->trailer.getKey("/Encrypt");
  326 + QPDFObjectHandle& encryption_dict = this->encryption_dictionary;
326 327 if (! encryption_dict.isDictionary())
327 328 {
328 329 throw QPDFExc(this->file.getName(), this->file.getLastOffset(),
... ... @@ -360,12 +361,7 @@ QPDF::initializeEncryption()
360 361 "Unsupported /R or /V in encryption dictionary");
361 362 }
362 363  
363   - // XXX remove this check to continue implementing R4.
364   - if ((R == 4) || (V == 4))
365   - {
366   - throw QPDFExc(this->file.getName(), this->file.getLastOffset(),
367   - "PDF >= 1.5 encryption support is not fully implemented");
368   - }
  364 + this->encryption_V = V;
369 365  
370 366 if (! ((O.length() == key_bytes) && (U.length() == key_bytes)))
371 367 {
... ... @@ -385,19 +381,21 @@ QPDF::initializeEncryption()
385 381 }
386 382 }
387 383  
388   - bool encrypt_metadata = true;
  384 + this->encrypt_metadata = true;
389 385 if ((V >= 4) && (encryption_dict.getKey("/EncryptMetadata").isBool()))
390 386 {
391   - encrypt_metadata =
  387 + this->encrypt_metadata =
392 388 encryption_dict.getKey("/EncryptMetadata").getBoolValue();
393 389 }
394   - // XXX not really...
395   - if (R >= 4)
  390 +
  391 + // XXX warn if /SubFilter is present
  392 + if (V == 4)
396 393 {
397   - this->encryption_use_aes = true;
  394 + // XXX get CF
398 395 }
399   - EncryptionData data(V, R, Length / 8, P, O, U, id1, encrypt_metadata);
400   - if (check_owner_password(this->user_password, this->provided_password, data))
  396 + EncryptionData data(V, R, Length / 8, P, O, U, id1, this->encrypt_metadata);
  397 + if (check_owner_password(
  398 + this->user_password, this->provided_password, data))
401 399 {
402 400 // password supplied was owner password; user_password has
403 401 // been initialized
... ... @@ -415,7 +413,7 @@ QPDF::initializeEncryption()
415 413 }
416 414  
417 415 std::string
418   -QPDF::getKeyForObject(int objid, int generation)
  416 +QPDF::getKeyForObject(int objid, int generation, bool use_aes)
419 417 {
420 418 if (! this->encrypted)
421 419 {
... ... @@ -427,8 +425,7 @@ QPDF::getKeyForObject(int objid, int generation)
427 425 (generation == this->cached_key_generation)))
428 426 {
429 427 this->cached_object_encryption_key =
430   - compute_data_key(this->encryption_key, objid, generation,
431   - this->encryption_use_aes);
  428 + compute_data_key(this->encryption_key, objid, generation, use_aes);
432 429 this->cached_key_objid = objid;
433 430 this->cached_key_generation = generation;
434 431 }
... ... @@ -443,23 +440,62 @@ QPDF::decryptString(std::string&amp; str, int objid, int generation)
443 440 {
444 441 return;
445 442 }
446   - std::string key = getKeyForObject(objid, generation);
447   - char* tmp = QUtil::copy_string(str);
448   - unsigned int vlen = str.length();
449   - RC4 rc4((unsigned char const*)key.c_str(), key.length());
450   - rc4.process((unsigned char*)tmp, vlen);
451   - str = std::string(tmp, vlen);
452   - delete [] tmp;
  443 + bool use_aes = false; // XXX
  444 + std::string key = getKeyForObject(objid, generation, use_aes);
  445 + if (use_aes)
  446 + {
  447 + // XXX
  448 + throw std::logic_error("XXX");
  449 + }
  450 + else
  451 + {
  452 + unsigned int vlen = str.length();
  453 + char* tmp = QUtil::copy_string(str);
  454 + RC4 rc4((unsigned char const*)key.c_str(), key.length());
  455 + rc4.process((unsigned char*)tmp, vlen);
  456 + str = std::string(tmp, vlen);
  457 + delete [] tmp;
  458 + }
453 459 }
454 460  
455 461 void
456 462 QPDF::decryptStream(Pipeline*& pipeline, int objid, int generation,
  463 + QPDFObjectHandle& stream_dict,
457 464 std::vector<PointerHolder<Pipeline> >& heap)
458 465 {
459   - std::string key = getKeyForObject(objid, generation);
460   - if (this->encryption_use_aes)
  466 + bool decrypt = true;
  467 + std::string type;
  468 + if (stream_dict.getKey("/Type").isName())
  469 + {
  470 + type = stream_dict.getKey("/Type").getName();
  471 + }
  472 + if (type == "/XRef")
  473 + {
  474 + QTC::TC("qpdf", "QPDF piping xref stream from encrypted file");
  475 + decrypt = false;
  476 + }
  477 + bool use_aes = false;
  478 + if (this->encryption_V == 4)
  479 + {
  480 + if ((! this->encrypt_metadata) && (type == "/Metadata"))
  481 + {
  482 + // XXX no test case for this
  483 + decrypt = false;
  484 + }
  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 +
  493 + std::string key = getKeyForObject(objid, generation, use_aes);
  494 + if (use_aes)
461 495 {
462   - throw std::logic_error("aes not yet implemented"); // XXX
  496 + assert(key.length() == Pl_AES_PDF::key_size);
  497 + pipeline = new Pl_AES_PDF("AES stream decryption", pipeline,
  498 + false, (unsigned char*) key.c_str());
463 499 }
464 500 else
465 501 {
... ... @@ -472,11 +508,10 @@ QPDF::decryptStream(Pipeline*&amp; pipeline, int objid, int generation,
472 508 void
473 509 QPDF::compute_encryption_O_U(
474 510 char const* user_password, char const* owner_password,
475   - int V, int R, int key_len, int P,
  511 + int V, int R, int key_len, int P, bool encrypt_metadata,
476 512 std::string const& id1, std::string& O, std::string& U)
477 513 {
478   - EncryptionData data(V, R, key_len, P, "", "", id1,
479   - /*XXX encrypt_metadata*/true);
  514 + EncryptionData data(V, R, key_len, P, "", "", id1, encrypt_metadata);
480 515 data.O = compute_O_value(user_password, owner_password, data);
481 516 O = data.O;
482 517 U = compute_U_value(user_password, data);
... ...
libqpdf/qpdf/Pl_AES_PDF.hh
... ... @@ -18,17 +18,24 @@ class DLL_EXPORT Pl_AES_PDF: public Pipeline
18 18 virtual void write(unsigned char* data, int len);
19 19 virtual void finish();
20 20  
  21 + // For testing only; PDF always uses CBC
  22 + void disableCBC();
  23 +
21 24 private:
22 25 void flush(bool discard_padding);
  26 + void initializeVector();
23 27  
24 28 static unsigned int const buf_size = 16;
25 29  
26 30 bool encrypt;
  31 + bool cbc_mode;
  32 + bool first;
27 33 unsigned int offset;
28 34 unsigned char key[key_size];
29 35 uint32_t rk[key_size + 28];
30 36 unsigned char inbuf[buf_size];
31 37 unsigned char outbuf[buf_size];
  38 + unsigned char cbc_block[buf_size];
32 39 unsigned int nrounds;
33 40 };
34 41  
... ...