mirror of
https://github.com/qpdf/qpdf.git
synced 2024-12-23 03:18:59 +00:00
reading crypt filters is largely implemented but not fully tested
git-svn-id: svn+q:///qpdf/trunk@812 71b93d88-0707-0410-a8cf-f5a4172ac649
This commit is contained in:
parent
c13bc66de8
commit
e25910b59a
@ -87,6 +87,7 @@ class DLL_EXPORT QPDF
|
|||||||
|
|
||||||
// Encryption support
|
// Encryption support
|
||||||
|
|
||||||
|
enum encryption_method_e { e_none, e_unknown, e_rc4, e_aes };
|
||||||
struct EncryptionData
|
struct EncryptionData
|
||||||
{
|
{
|
||||||
// This class holds data read from the encryption dictionary.
|
// This class holds data read from the encryption dictionary.
|
||||||
@ -397,6 +398,7 @@ class DLL_EXPORT QPDF
|
|||||||
std::vector<QPDFObjectHandle>& result);
|
std::vector<QPDFObjectHandle>& result);
|
||||||
|
|
||||||
// methods to support encryption -- implemented in QPDF_encryption.cc
|
// methods to support encryption -- implemented in QPDF_encryption.cc
|
||||||
|
encryption_method_e interpretCF(QPDFObjectHandle);
|
||||||
void initializeEncryption();
|
void initializeEncryption();
|
||||||
std::string getKeyForObject(int objid, int generation, bool use_aes);
|
std::string getKeyForObject(int objid, int generation, bool use_aes);
|
||||||
void decryptString(std::string&, int objid, int generation);
|
void decryptString(std::string&, int objid, int generation);
|
||||||
@ -739,7 +741,10 @@ class DLL_EXPORT QPDF
|
|||||||
bool attempt_recovery;
|
bool attempt_recovery;
|
||||||
int encryption_V;
|
int encryption_V;
|
||||||
bool encrypt_metadata;
|
bool encrypt_metadata;
|
||||||
QPDFObjectHandle encryption_dictionary;
|
std::map<std::string, encryption_method_e> crypt_filters;
|
||||||
|
encryption_method_e cf_stream;
|
||||||
|
encryption_method_e cf_string;
|
||||||
|
encryption_method_e cf_file;
|
||||||
std::string provided_password;
|
std::string provided_password;
|
||||||
std::string user_password;
|
std::string user_password;
|
||||||
std::string encryption_key;
|
std::string encryption_key;
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
Pl_AES_PDF::Pl_AES_PDF(char const* identifier, Pipeline* next,
|
Pl_AES_PDF::Pl_AES_PDF(char const* identifier, Pipeline* next,
|
||||||
bool encrypt, unsigned char key[key_size]) :
|
bool encrypt, unsigned char const key[key_size]) :
|
||||||
Pipeline(identifier, next),
|
Pipeline(identifier, next),
|
||||||
encrypt(encrypt),
|
encrypt(encrypt),
|
||||||
cbc_mode(true),
|
cbc_mode(true),
|
||||||
|
@ -255,6 +255,9 @@ QPDF::QPDF() :
|
|||||||
attempt_recovery(true),
|
attempt_recovery(true),
|
||||||
encryption_V(0),
|
encryption_V(0),
|
||||||
encrypt_metadata(true),
|
encrypt_metadata(true),
|
||||||
|
cf_stream(e_none),
|
||||||
|
cf_string(e_none),
|
||||||
|
cf_file(e_none),
|
||||||
cached_key_objid(0),
|
cached_key_objid(0),
|
||||||
cached_key_generation(0),
|
cached_key_generation(0),
|
||||||
first_xref_item_offset(0),
|
first_xref_item_offset(0),
|
||||||
|
@ -136,6 +136,11 @@ QPDF_Stream::filterable(std::vector<std::string>& filters,
|
|||||||
filterable = false;
|
filterable = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (key == "/Crypt")
|
||||||
|
{
|
||||||
|
// XXX untested
|
||||||
|
// we handle this in decryptStream
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
filterable = false;
|
filterable = false;
|
||||||
@ -212,7 +217,8 @@ QPDF_Stream::filterable(std::vector<std::string>& filters,
|
|||||||
iter != filters.end(); ++iter)
|
iter != filters.end(); ++iter)
|
||||||
{
|
{
|
||||||
std::string const& filter = *iter;
|
std::string const& filter = *iter;
|
||||||
if (! ((filter == "/FlateDecode") ||
|
if (! ((filter == "/Crypt") ||
|
||||||
|
(filter == "/FlateDecode") ||
|
||||||
(filter == "/LZWDecode") ||
|
(filter == "/LZWDecode") ||
|
||||||
(filter == "/ASCII85Decode") ||
|
(filter == "/ASCII85Decode") ||
|
||||||
(filter == "/ASCIIHexDecode")))
|
(filter == "/ASCIIHexDecode")))
|
||||||
@ -266,7 +272,11 @@ QPDF_Stream::pipeStreamData(Pipeline* pipeline, bool filter,
|
|||||||
iter != filters.rend(); ++iter)
|
iter != filters.rend(); ++iter)
|
||||||
{
|
{
|
||||||
std::string const& filter = *iter;
|
std::string const& filter = *iter;
|
||||||
if (filter == "/FlateDecode")
|
if (filter == "/Crypt")
|
||||||
|
{
|
||||||
|
// Ignore -- handled by pipeStreamData
|
||||||
|
}
|
||||||
|
else if (filter == "/FlateDecode")
|
||||||
{
|
{
|
||||||
if (predictor == 12)
|
if (predictor == 12)
|
||||||
{
|
{
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
#include <qpdf/QUtil.hh>
|
#include <qpdf/QUtil.hh>
|
||||||
#include <qpdf/Pl_RC4.hh>
|
#include <qpdf/Pl_RC4.hh>
|
||||||
#include <qpdf/Pl_AES_PDF.hh>
|
#include <qpdf/Pl_AES_PDF.hh>
|
||||||
|
#include <qpdf/Pl_Buffer.hh>
|
||||||
#include <qpdf/RC4.hh>
|
#include <qpdf/RC4.hh>
|
||||||
#include <qpdf/MD5.hh>
|
#include <qpdf/MD5.hh>
|
||||||
|
|
||||||
@ -281,6 +282,27 @@ check_owner_password(std::string& user_password,
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QPDF::encryption_method_e
|
||||||
|
QPDF::interpretCF(QPDFObjectHandle cf)
|
||||||
|
{
|
||||||
|
if (cf.isName())
|
||||||
|
{
|
||||||
|
std::string filter = cf.getName();
|
||||||
|
if (this->crypt_filters.count(filter) != 0)
|
||||||
|
{
|
||||||
|
return this->crypt_filters[filter];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return e_unknown;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return e_none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
QPDF::initializeEncryption()
|
QPDF::initializeEncryption()
|
||||||
{
|
{
|
||||||
@ -322,8 +344,7 @@ QPDF::initializeEncryption()
|
|||||||
"incorrect length");
|
"incorrect length");
|
||||||
}
|
}
|
||||||
|
|
||||||
this->encryption_dictionary = this->trailer.getKey("/Encrypt");
|
QPDFObjectHandle encryption_dict = this->trailer.getKey("/Encrypt");
|
||||||
QPDFObjectHandle& encryption_dict = this->encryption_dictionary;
|
|
||||||
if (! encryption_dict.isDictionary())
|
if (! encryption_dict.isDictionary())
|
||||||
{
|
{
|
||||||
throw QPDFExc(this->file.getName(), this->file.getLastOffset(),
|
throw QPDFExc(this->file.getName(), this->file.getLastOffset(),
|
||||||
@ -336,6 +357,12 @@ QPDF::initializeEncryption()
|
|||||||
throw QPDFExc(this->file.getName(), this->file.getLastOffset(),
|
throw QPDFExc(this->file.getName(), this->file.getLastOffset(),
|
||||||
"unsupported encryption filter");
|
"unsupported encryption filter");
|
||||||
}
|
}
|
||||||
|
if (! encryption_dict.getKey("/SubFilter").isNull())
|
||||||
|
{
|
||||||
|
warn(QPDFExc(this->file.getName(), this->file.getLastOffset(),
|
||||||
|
"file uses encryption SubFilters,"
|
||||||
|
" which qpdf does not support"));
|
||||||
|
}
|
||||||
|
|
||||||
if (! (encryption_dict.getKey("/V").isInteger() &&
|
if (! (encryption_dict.getKey("/V").isInteger() &&
|
||||||
encryption_dict.getKey("/R").isInteger() &&
|
encryption_dict.getKey("/R").isInteger() &&
|
||||||
@ -388,10 +415,55 @@ QPDF::initializeEncryption()
|
|||||||
encryption_dict.getKey("/EncryptMetadata").getBoolValue();
|
encryption_dict.getKey("/EncryptMetadata").getBoolValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
// XXX warn if /SubFilter is present
|
|
||||||
if (V == 4)
|
if (V == 4)
|
||||||
{
|
{
|
||||||
// XXX get CF
|
QPDFObjectHandle CF = encryption_dict.getKey("/CF");
|
||||||
|
std::set<std::string> keys = CF.getKeys();
|
||||||
|
for (std::set<std::string>::iterator iter = keys.begin();
|
||||||
|
iter != keys.end(); ++iter)
|
||||||
|
{
|
||||||
|
std::string const& filter = *iter;
|
||||||
|
QPDFObjectHandle cdict = CF.getKey(filter);
|
||||||
|
if (cdict.isDictionary())
|
||||||
|
{
|
||||||
|
encryption_method_e method = e_none;
|
||||||
|
if (cdict.getKey("/CFM").isName())
|
||||||
|
{
|
||||||
|
std::string method_name = cdict.getKey("/CFM").getName();
|
||||||
|
if (method_name == "/V2")
|
||||||
|
{
|
||||||
|
// XXX coverage
|
||||||
|
method = e_rc4;
|
||||||
|
}
|
||||||
|
else if (method_name == "/AESV2")
|
||||||
|
{
|
||||||
|
// XXX coverage
|
||||||
|
method = e_aes;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Don't complain now -- maybe we won't need
|
||||||
|
// to reference this type.
|
||||||
|
method = e_unknown;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this->crypt_filters[filter] = method;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QPDFObjectHandle StmF = encryption_dict.getKey("/StmF");
|
||||||
|
QPDFObjectHandle StrF = encryption_dict.getKey("/StrF");
|
||||||
|
QPDFObjectHandle EFF = encryption_dict.getKey("/EFF");
|
||||||
|
this->cf_stream = interpretCF(StmF);
|
||||||
|
this->cf_string = interpretCF(StrF);
|
||||||
|
if (EFF.isName())
|
||||||
|
{
|
||||||
|
this->cf_file = interpretCF(EFF);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this->cf_file = this->cf_stream;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
EncryptionData data(V, R, Length / 8, P, O, U, id1, this->encrypt_metadata);
|
EncryptionData data(V, R, Length / 8, P, O, U, id1, this->encrypt_metadata);
|
||||||
if (check_owner_password(
|
if (check_owner_password(
|
||||||
@ -440,15 +512,50 @@ QPDF::decryptString(std::string& str, int objid, int generation)
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
bool use_aes = false; // XXX
|
bool use_aes = false;
|
||||||
|
if (this->encryption_V == 4)
|
||||||
|
{
|
||||||
|
switch (this->cf_string)
|
||||||
|
{
|
||||||
|
case e_none:
|
||||||
|
return;
|
||||||
|
|
||||||
|
case e_aes:
|
||||||
|
use_aes = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case e_rc4:
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
warn(QPDFExc(this->file.getName(), this->file.getLastOffset(),
|
||||||
|
"unknown encryption filter for strings"
|
||||||
|
" (check /StrF in /Encrypt dictionary);"
|
||||||
|
" strings may be decrypted improperly"));
|
||||||
|
// To avoid repeated warnings, reset cf_string. Assume
|
||||||
|
// we'd want to use AES if V == 4.
|
||||||
|
this->cf_string = e_aes;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
std::string key = getKeyForObject(objid, generation, use_aes);
|
std::string key = getKeyForObject(objid, generation, use_aes);
|
||||||
if (use_aes)
|
if (use_aes)
|
||||||
{
|
{
|
||||||
// XXX
|
// XXX coverage
|
||||||
throw std::logic_error("XXX");
|
assert(key.length() == Pl_AES_PDF::key_size);
|
||||||
|
Pl_Buffer bufpl("decrypted string");
|
||||||
|
Pl_AES_PDF pl("aes decrypt string", &bufpl, false,
|
||||||
|
(unsigned char const*)key.c_str());
|
||||||
|
pl.write((unsigned char*)str.c_str(), str.length());
|
||||||
|
pl.finish();
|
||||||
|
Buffer* buf = bufpl.getBuffer();
|
||||||
|
str = std::string((char*)buf->getBuffer(), (size_t)buf->getSize());
|
||||||
|
delete buf;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
QTC::TC("qpdf", "QPDF_encryption rc4 decode string");
|
||||||
unsigned int vlen = str.length();
|
unsigned int vlen = str.length();
|
||||||
char* tmp = QUtil::copy_string(str);
|
char* tmp = QUtil::copy_string(str);
|
||||||
RC4 rc4((unsigned char const*)key.c_str(), key.length());
|
RC4 rc4((unsigned char const*)key.c_str(), key.length());
|
||||||
@ -463,7 +570,6 @@ QPDF::decryptStream(Pipeline*& pipeline, int objid, int generation,
|
|||||||
QPDFObjectHandle& stream_dict,
|
QPDFObjectHandle& stream_dict,
|
||||||
std::vector<PointerHolder<Pipeline> >& heap)
|
std::vector<PointerHolder<Pipeline> >& heap)
|
||||||
{
|
{
|
||||||
bool decrypt = true;
|
|
||||||
std::string type;
|
std::string type;
|
||||||
if (stream_dict.getKey("/Type").isName())
|
if (stream_dict.getKey("/Type").isName())
|
||||||
{
|
{
|
||||||
@ -471,34 +577,77 @@ QPDF::decryptStream(Pipeline*& pipeline, int objid, int generation,
|
|||||||
}
|
}
|
||||||
if (type == "/XRef")
|
if (type == "/XRef")
|
||||||
{
|
{
|
||||||
QTC::TC("qpdf", "QPDF piping xref stream from encrypted file");
|
QTC::TC("qpdf", "QPDF_encryption xref stream from encrypted file");
|
||||||
decrypt = false;
|
return;
|
||||||
}
|
}
|
||||||
bool use_aes = false;
|
bool use_aes = false;
|
||||||
if (this->encryption_V == 4)
|
if (this->encryption_V == 4)
|
||||||
{
|
{
|
||||||
if ((! this->encrypt_metadata) && (type == "/Metadata"))
|
encryption_method_e method = e_unknown;
|
||||||
{
|
std::string method_source = "/StmF from /Encrypt dictionary";
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (stream_dict.getKey("/DecodeParms").isDictionary())
|
||||||
|
{
|
||||||
|
QPDFObjectHandle decode_parms = stream_dict.getKey("/DecodeParms");
|
||||||
|
if (decode_parms.getKey("/Crypt").isDictionary())
|
||||||
|
{
|
||||||
|
// XXX coverage
|
||||||
|
QPDFObjectHandle crypt = decode_parms.getKey("/Crypt");
|
||||||
|
method = interpretCF(crypt.getKey("/Name"));
|
||||||
|
method_source = "stream's Crypt decode parameters";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (method == e_unknown)
|
||||||
|
{
|
||||||
|
if ((! this->encrypt_metadata) && (type == "/Metadata"))
|
||||||
|
{
|
||||||
|
// XXX coverage
|
||||||
|
method = e_none;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
method = this->cf_stream;
|
||||||
|
}
|
||||||
|
// XXX What about embedded file streams?
|
||||||
|
}
|
||||||
|
use_aes = false;
|
||||||
|
switch (this->cf_stream)
|
||||||
|
{
|
||||||
|
case e_none:
|
||||||
|
return;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case e_aes:
|
||||||
|
use_aes = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case e_rc4:
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// filter local to this stream.
|
||||||
|
warn(QPDFExc(this->file.getName(), this->file.getLastOffset(),
|
||||||
|
"unknown encryption filter for streams"
|
||||||
|
" (check " + method_source + ");"
|
||||||
|
" streams may be decrypted improperly"));
|
||||||
|
// To avoid repeated warnings, reset cf_stream. Assume
|
||||||
|
// we'd want to use AES if V == 4.
|
||||||
|
this->cf_stream = e_aes;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
std::string key = getKeyForObject(objid, generation, use_aes);
|
std::string key = getKeyForObject(objid, generation, use_aes);
|
||||||
if (use_aes)
|
if (use_aes)
|
||||||
{
|
{
|
||||||
|
// XXX coverage
|
||||||
assert(key.length() == Pl_AES_PDF::key_size);
|
assert(key.length() == Pl_AES_PDF::key_size);
|
||||||
pipeline = new Pl_AES_PDF("AES stream decryption", pipeline,
|
pipeline = new Pl_AES_PDF("AES stream decryption", pipeline,
|
||||||
false, (unsigned char*) key.c_str());
|
false, (unsigned char*) key.c_str());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
QTC::TC("qpdf", "QPDF_encryption rc4 decode stream");
|
||||||
pipeline = new Pl_RC4("RC4 stream decryption", pipeline,
|
pipeline = new Pl_RC4("RC4 stream decryption", pipeline,
|
||||||
(unsigned char*) key.c_str(), key.length());
|
(unsigned char*) key.c_str(), key.length());
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ class DLL_EXPORT Pl_AES_PDF: public Pipeline
|
|||||||
// key_data should be a pointer to key_size bytes of data
|
// key_data should be a pointer to key_size bytes of data
|
||||||
static unsigned int const key_size = 16;
|
static unsigned int const key_size = 16;
|
||||||
Pl_AES_PDF(char const* identifier, Pipeline* next,
|
Pl_AES_PDF(char const* identifier, Pipeline* next,
|
||||||
bool encrypt, unsigned char key[key_size]);
|
bool encrypt, unsigned char const key[key_size]);
|
||||||
virtual ~Pl_AES_PDF();
|
virtual ~Pl_AES_PDF();
|
||||||
|
|
||||||
virtual void write(unsigned char* data, int len);
|
virtual void write(unsigned char* data, int len);
|
||||||
|
@ -112,7 +112,7 @@ QPDF found wrong endstream in recovery 0
|
|||||||
QPDFObjectHandle indirect to unknown 0
|
QPDFObjectHandle indirect to unknown 0
|
||||||
QPDF_Stream pipeStreamData with null pipeline 0
|
QPDF_Stream pipeStreamData with null pipeline 0
|
||||||
QPDFWriter not recompressing /FlateDecode 0
|
QPDFWriter not recompressing /FlateDecode 0
|
||||||
QPDF piping xref stream from encrypted file 0
|
QPDF_encryption xref stream from encrypted file 0
|
||||||
unable to filter 0
|
unable to filter 0
|
||||||
QPDF_String non-trivial UTF-16 0
|
QPDF_String non-trivial UTF-16 0
|
||||||
QPDF xref overwrite object 0
|
QPDF xref overwrite object 0
|
||||||
@ -158,3 +158,5 @@ QPDFWriter using forced PDF version 0
|
|||||||
qpdf-c called qpdf_set_minimum_pdf_version 0
|
qpdf-c called qpdf_set_minimum_pdf_version 0
|
||||||
qpdf-c called qpdf_force_pdf_version 0
|
qpdf-c called qpdf_force_pdf_version 0
|
||||||
qpdf-c called qpdf_init_write multiple times 0
|
qpdf-c called qpdf_init_write multiple times 0
|
||||||
|
QPDF_encryption rc4 decode string 0
|
||||||
|
QPDF_encryption rc4 decode stream 0
|
||||||
|
Loading…
Reference in New Issue
Block a user