From c13bc66de8d6ef553c4ed05247774476a859a5f3 Mon Sep 17 00:00:00 2001 From: Jay Berkenbilt Date: Sat, 17 Oct 2009 18:54:51 +0000 Subject: checkpoint -- partially implemented /V=4 encryption git-svn-id: svn+q:///qpdf/trunk@811 71b93d88-0707-0410-a8cf-f5a4172ac649 --- libqpdf/Pl_AES_PDF.cc | 77 +++++++++++++++++++++++++++++++++- libqpdf/QPDF.cc | 15 ++----- libqpdf/QPDFWriter.cc | 3 +- libqpdf/QPDF_encryption.cc | 101 ++++++++++++++++++++++++++++++--------------- libqpdf/qpdf/Pl_AES_PDF.hh | 7 ++++ 5 files changed, 155 insertions(+), 48 deletions(-) (limited to 'libqpdf') diff --git a/libqpdf/Pl_AES_PDF.cc b/libqpdf/Pl_AES_PDF.cc index 26dc58ae..fa8bc3cf 100644 --- a/libqpdf/Pl_AES_PDF.cc +++ b/libqpdf/Pl_AES_PDF.cc @@ -1,16 +1,19 @@ #include #include +#include #include #include #include #include - -// XXX Still need CBC +#include +#include Pl_AES_PDF::Pl_AES_PDF(char const* identifier, Pipeline* next, bool encrypt, unsigned char key[key_size]) : Pipeline(identifier, next), encrypt(encrypt), + cbc_mode(true), + first(true), offset(0), nrounds(0) { @@ -21,6 +24,7 @@ Pl_AES_PDF::Pl_AES_PDF(char const* identifier, Pipeline* next, std::memset(this->rk, 0, sizeof(this->rk)); std::memset(this->inbuf, 0, this->buf_size); std::memset(this->outbuf, 0, this->buf_size); + std::memset(this->cbc_block, 0, this->buf_size); if (encrypt) { this->nrounds = rijndaelSetupEncrypt(this->rk, this->key, keybits); @@ -37,6 +41,12 @@ Pl_AES_PDF::~Pl_AES_PDF() // nothing needed } +void +Pl_AES_PDF::disableCBC() +{ + this->cbc_mode = false; +} + void Pl_AES_PDF::write(unsigned char* data, int len) { @@ -90,17 +100,80 @@ Pl_AES_PDF::finish() getNext()->finish(); } +void +Pl_AES_PDF::initializeVector() +{ + std::string seed_str; + seed_str += QUtil::int_to_string((int)QUtil::get_current_time()); + seed_str += " QPDF aes random"; + MD5 m; + m.encodeString(seed_str.c_str()); + MD5::Digest digest; + m.digest(digest); + assert(sizeof(digest) >= sizeof(unsigned int)); + unsigned int seed; + memcpy((void*)(&seed), digest, sizeof(unsigned int)); + srandom(seed); + for (unsigned int i = 0; i < this->buf_size; ++i) + { + this->cbc_block[i] = (unsigned char)(random() & 0xff); + } +} + void Pl_AES_PDF::flush(bool strip_padding) { assert(this->offset == this->buf_size); + + if (first) + { + first = false; + if (this->cbc_mode) + { + if (encrypt) + { + // Set cbc_block to a random initialization vector and + // write it to the output stream + initializeVector(); + getNext()->write(this->cbc_block, this->buf_size); + } + else + { + // Take the first block of input as the initialization + // vector. There's nothing to write at this time. + memcpy(this->cbc_block, this->inbuf, this->buf_size); + this->offset = 0; + return; + } + } + } + if (this->encrypt) { + if (this->cbc_mode) + { + for (unsigned int i = 0; i < this->buf_size; ++i) + { + this->inbuf[i] ^= this->cbc_block[i]; + } + } rijndaelEncrypt(this->rk, this->nrounds, this->inbuf, this->outbuf); + if (this->cbc_mode) + { + memcpy(this->cbc_block, this->outbuf, this->buf_size); + } } else { rijndaelDecrypt(this->rk, this->nrounds, this->inbuf, this->outbuf); + if (this->cbc_mode) + { + for (unsigned int i = 0; i < this->buf_size; ++i) + { + this->outbuf[i] ^= this->cbc_block[i]; + } + memcpy(this->cbc_block, this->inbuf, this->buf_size); + } } unsigned int bytes = this->buf_size; if (strip_padding) diff --git a/libqpdf/QPDF.cc b/libqpdf/QPDF.cc index dd1fea56..10777aa4 100644 --- a/libqpdf/QPDF.cc +++ b/libqpdf/QPDF.cc @@ -253,7 +253,8 @@ QPDF::QPDF() : ignore_xref_streams(false), suppress_warnings(false), attempt_recovery(true), - encryption_use_aes(false), + encryption_V(0), + encrypt_metadata(true), cached_key_objid(0), cached_key_generation(0), first_xref_item_offset(0), @@ -1813,17 +1814,7 @@ QPDF::pipeStreamData(int objid, int generation, std::vector > to_delete; if (this->encrypted) { - bool xref_stream = false; - if (stream_dict.getKey("/Type").isName() && - (stream_dict.getKey("/Type").getName() == "/XRef")) - { - QTC::TC("qpdf", "QPDF piping xref stream from encrypted file"); - xref_stream = true; - } - if (! xref_stream) - { - decryptStream(pipeline, objid, generation, to_delete); - } + decryptStream(pipeline, objid, generation, stream_dict, to_delete); } try diff --git a/libqpdf/QPDFWriter.cc b/libqpdf/QPDFWriter.cc index 2a990fa3..71d1fa5c 100644 --- a/libqpdf/QPDFWriter.cc +++ b/libqpdf/QPDFWriter.cc @@ -281,7 +281,8 @@ QPDFWriter::setEncryptionParameters( std::string O; std::string U; QPDF::compute_encryption_O_U( - user_password, owner_password, V, R, key_len, P, this->id1, O, U); + user_password, owner_password, V, R, key_len, P, + /*XXX encrypt_metadata*/true, this->id1, O, U); setEncryptionParametersInternal( V, R, key_len, P, O, U, this->id1, user_password); } diff --git a/libqpdf/QPDF_encryption.cc b/libqpdf/QPDF_encryption.cc index 190d2d6a..8e403631 100644 --- a/libqpdf/QPDF_encryption.cc +++ b/libqpdf/QPDF_encryption.cc @@ -5,11 +5,14 @@ #include +#include #include #include +#include #include #include +#include #include static char const padding_string[] = { @@ -123,9 +126,6 @@ QPDF::compute_data_key(std::string const& encryption_key, md5.digest(digest); return std::string((char*) digest, std::min(result.length(), (size_t) 16)); - - // XXX Item 4 in Algorithm 3.1 mentions CBC and a random number. - // We still have to incorporate that. } std::string @@ -322,7 +322,8 @@ QPDF::initializeEncryption() "incorrect length"); } - QPDFObjectHandle encryption_dict = this->trailer.getKey("/Encrypt"); + this->encryption_dictionary = this->trailer.getKey("/Encrypt"); + QPDFObjectHandle& encryption_dict = this->encryption_dictionary; if (! encryption_dict.isDictionary()) { throw QPDFExc(this->file.getName(), this->file.getLastOffset(), @@ -360,12 +361,7 @@ QPDF::initializeEncryption() "Unsupported /R or /V in encryption dictionary"); } - // XXX remove this check to continue implementing R4. - if ((R == 4) || (V == 4)) - { - throw QPDFExc(this->file.getName(), this->file.getLastOffset(), - "PDF >= 1.5 encryption support is not fully implemented"); - } + this->encryption_V = V; if (! ((O.length() == key_bytes) && (U.length() == key_bytes))) { @@ -385,19 +381,21 @@ QPDF::initializeEncryption() } } - bool encrypt_metadata = true; + this->encrypt_metadata = true; if ((V >= 4) && (encryption_dict.getKey("/EncryptMetadata").isBool())) { - encrypt_metadata = + this->encrypt_metadata = encryption_dict.getKey("/EncryptMetadata").getBoolValue(); } - // XXX not really... - if (R >= 4) + + // XXX warn if /SubFilter is present + if (V == 4) { - this->encryption_use_aes = true; + // XXX get CF } - EncryptionData data(V, R, Length / 8, P, O, U, id1, encrypt_metadata); - if (check_owner_password(this->user_password, this->provided_password, data)) + EncryptionData data(V, R, Length / 8, P, O, U, id1, this->encrypt_metadata); + if (check_owner_password( + this->user_password, this->provided_password, data)) { // password supplied was owner password; user_password has // been initialized @@ -415,7 +413,7 @@ QPDF::initializeEncryption() } std::string -QPDF::getKeyForObject(int objid, int generation) +QPDF::getKeyForObject(int objid, int generation, bool use_aes) { if (! this->encrypted) { @@ -427,8 +425,7 @@ QPDF::getKeyForObject(int objid, int generation) (generation == this->cached_key_generation))) { this->cached_object_encryption_key = - compute_data_key(this->encryption_key, objid, generation, - this->encryption_use_aes); + compute_data_key(this->encryption_key, objid, generation, use_aes); this->cached_key_objid = objid; this->cached_key_generation = generation; } @@ -443,23 +440,62 @@ QPDF::decryptString(std::string& str, int objid, int generation) { return; } - std::string key = getKeyForObject(objid, generation); - char* tmp = QUtil::copy_string(str); - unsigned int vlen = str.length(); - RC4 rc4((unsigned char const*)key.c_str(), key.length()); - rc4.process((unsigned char*)tmp, vlen); - str = std::string(tmp, vlen); - delete [] tmp; + bool use_aes = false; // XXX + std::string key = getKeyForObject(objid, generation, use_aes); + if (use_aes) + { + // XXX + throw std::logic_error("XXX"); + } + else + { + unsigned int vlen = str.length(); + char* tmp = QUtil::copy_string(str); + RC4 rc4((unsigned char const*)key.c_str(), key.length()); + rc4.process((unsigned char*)tmp, vlen); + str = std::string(tmp, vlen); + delete [] tmp; + } } void QPDF::decryptStream(Pipeline*& pipeline, int objid, int generation, + QPDFObjectHandle& stream_dict, std::vector >& heap) { - std::string key = getKeyForObject(objid, generation); - if (this->encryption_use_aes) + bool decrypt = true; + std::string type; + if (stream_dict.getKey("/Type").isName()) + { + type = stream_dict.getKey("/Type").getName(); + } + if (type == "/XRef") + { + QTC::TC("qpdf", "QPDF piping xref stream from encrypted file"); + decrypt = false; + } + bool use_aes = false; + if (this->encryption_V == 4) + { + if ((! this->encrypt_metadata) && (type == "/Metadata")) + { + // XXX no test case for this + decrypt = false; + } + // XXX check crypt filter; if not found, use StmF; see TODO + use_aes = true; // XXX + } + if (! decrypt) + { + return; + } + + std::string key = getKeyForObject(objid, generation, use_aes); + if (use_aes) { - throw std::logic_error("aes not yet implemented"); // XXX + assert(key.length() == Pl_AES_PDF::key_size); + pipeline = new Pl_AES_PDF("AES stream decryption", pipeline, + false, (unsigned char*) key.c_str()); } else { @@ -472,11 +508,10 @@ QPDF::decryptStream(Pipeline*& pipeline, int objid, int generation, void QPDF::compute_encryption_O_U( char const* user_password, char const* owner_password, - int V, int R, int key_len, int P, + int V, int R, int key_len, int P, bool encrypt_metadata, std::string const& id1, std::string& O, std::string& U) { - EncryptionData data(V, R, key_len, P, "", "", id1, - /*XXX encrypt_metadata*/true); + EncryptionData data(V, R, key_len, P, "", "", id1, encrypt_metadata); data.O = compute_O_value(user_password, owner_password, data); O = data.O; U = compute_U_value(user_password, data); diff --git a/libqpdf/qpdf/Pl_AES_PDF.hh b/libqpdf/qpdf/Pl_AES_PDF.hh index 442c9bf3..adacc6e5 100644 --- a/libqpdf/qpdf/Pl_AES_PDF.hh +++ b/libqpdf/qpdf/Pl_AES_PDF.hh @@ -18,17 +18,24 @@ class DLL_EXPORT Pl_AES_PDF: public Pipeline virtual void write(unsigned char* data, int len); virtual void finish(); + // For testing only; PDF always uses CBC + void disableCBC(); + private: void flush(bool discard_padding); + void initializeVector(); static unsigned int const buf_size = 16; bool encrypt; + bool cbc_mode; + bool first; unsigned int offset; unsigned char key[key_size]; uint32_t rk[key_size + 28]; unsigned char inbuf[buf_size]; unsigned char outbuf[buf_size]; + unsigned char cbc_block[buf_size]; unsigned int nrounds; }; -- cgit v1.2.3-70-g09d2