mirror of
https://github.com/qpdf/qpdf.git
synced 2024-12-22 19:08:59 +00:00
a101533e0a
Add --copy-encryption and --encryption-file-password options to qpdf. Also strengthen test suite for copying encryption. The strengthened test suite would have caught the failure to preserve AES and the failure to update the file version, which was invalidating the encrypted data.
931 lines
24 KiB
C++
931 lines
24 KiB
C++
// This file implements methods from the QPDF class that involve
|
|
// encryption.
|
|
|
|
#include <qpdf/QPDF.hh>
|
|
|
|
#include <qpdf/QPDFExc.hh>
|
|
|
|
#include <qpdf/QTC.hh>
|
|
#include <qpdf/QUtil.hh>
|
|
#include <qpdf/Pl_RC4.hh>
|
|
#include <qpdf/Pl_AES_PDF.hh>
|
|
#include <qpdf/Pl_Buffer.hh>
|
|
#include <qpdf/RC4.hh>
|
|
#include <qpdf/MD5.hh>
|
|
|
|
#include <assert.h>
|
|
#include <string.h>
|
|
|
|
static unsigned char const padding_string[] = {
|
|
0x28, 0xbf, 0x4e, 0x5e, 0x4e, 0x75, 0x8a, 0x41,
|
|
0x64, 0x00, 0x4e, 0x56, 0xff, 0xfa, 0x01, 0x08,
|
|
0x2e, 0x2e, 0x00, 0xb6, 0xd0, 0x68, 0x3e, 0x80,
|
|
0x2f, 0x0c, 0xa9, 0xfe, 0x64, 0x53, 0x69, 0x7a
|
|
};
|
|
|
|
static unsigned int const O_key_bytes = sizeof(MD5::Digest);
|
|
static unsigned int const key_bytes = 32;
|
|
|
|
void
|
|
pad_or_truncate_password(std::string const& password, char k1[key_bytes])
|
|
{
|
|
int password_bytes = std::min(key_bytes, (unsigned int)password.length());
|
|
int pad_bytes = key_bytes - password_bytes;
|
|
memcpy(k1, password.c_str(), password_bytes);
|
|
memcpy(k1 + password_bytes, padding_string, pad_bytes);
|
|
}
|
|
|
|
void
|
|
QPDF::trim_user_password(std::string& user_password)
|
|
{
|
|
// Although unnecessary, this routine trims the padding string
|
|
// from the end of a user password. Its only purpose is for
|
|
// recovery of user passwords which is done in the test suite.
|
|
char const* cstr = user_password.c_str();
|
|
size_t len = user_password.length();
|
|
if (len < key_bytes)
|
|
{
|
|
return;
|
|
}
|
|
|
|
char const* p = 0;
|
|
while ((p = strchr(cstr, '\x28')) != 0)
|
|
{
|
|
if (memcmp(p, padding_string, len - (p - cstr)) == 0)
|
|
{
|
|
user_password = user_password.substr(0, p - cstr);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
static std::string
|
|
pad_or_truncate_password(std::string const& password)
|
|
{
|
|
char k1[key_bytes];
|
|
pad_or_truncate_password(password, k1);
|
|
return std::string(k1, key_bytes);
|
|
}
|
|
|
|
static void
|
|
iterate_md5_digest(MD5& md5, MD5::Digest& digest, int iterations)
|
|
{
|
|
md5.digest(digest);
|
|
|
|
for (int i = 0; i < iterations; ++i)
|
|
{
|
|
MD5 m;
|
|
m.encodeDataIncrementally((char*)digest, sizeof(digest));
|
|
m.digest(digest);
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
iterate_rc4(unsigned char* data, int data_len,
|
|
unsigned char* okey, int key_len,
|
|
int iterations, bool reverse)
|
|
{
|
|
unsigned char* key = new unsigned char[key_len];
|
|
for (int i = 0; i < iterations; ++i)
|
|
{
|
|
int const xor_value = (reverse ? iterations - 1 - i : i);
|
|
for (int j = 0; j < key_len; ++j)
|
|
{
|
|
key[j] = okey[j] ^ xor_value;
|
|
}
|
|
RC4 rc4(key, key_len);
|
|
rc4.process(data, data_len);
|
|
}
|
|
delete [] key;
|
|
}
|
|
|
|
std::string
|
|
QPDF::compute_data_key(std::string const& encryption_key,
|
|
int objid, int generation,
|
|
bool use_aes)
|
|
{
|
|
// Algorithm 3.1 from the PDF 1.7 Reference Manual
|
|
|
|
std::string result = encryption_key;
|
|
|
|
// Append low three bytes of object ID and low two bytes of generation
|
|
result += (char) (objid & 0xff);
|
|
result += (char) ((objid >> 8) & 0xff);
|
|
result += (char) ((objid >> 16) & 0xff);
|
|
result += (char) (generation & 0xff);
|
|
result += (char) ((generation >> 8) & 0xff);
|
|
if (use_aes)
|
|
{
|
|
result += "sAlT";
|
|
}
|
|
|
|
MD5 md5;
|
|
md5.encodeDataIncrementally(result.c_str(), (int)result.length());
|
|
MD5::Digest digest;
|
|
md5.digest(digest);
|
|
return std::string((char*) digest,
|
|
std::min(result.length(), (size_t) 16));
|
|
}
|
|
|
|
std::string
|
|
QPDF::compute_encryption_key(
|
|
std::string const& password, EncryptionData const& data)
|
|
{
|
|
// Algorithm 3.2 from the PDF 1.7 Reference Manual
|
|
|
|
MD5 md5;
|
|
md5.encodeDataIncrementally(
|
|
pad_or_truncate_password(password).c_str(), key_bytes);
|
|
md5.encodeDataIncrementally(data.O.c_str(), key_bytes);
|
|
char pbytes[4];
|
|
pbytes[0] = (char) (data.P & 0xff);
|
|
pbytes[1] = (char) ((data.P >> 8) & 0xff);
|
|
pbytes[2] = (char) ((data.P >> 16) & 0xff);
|
|
pbytes[3] = (char) ((data.P >> 24) & 0xff);
|
|
md5.encodeDataIncrementally(pbytes, 4);
|
|
md5.encodeDataIncrementally(data.id1.c_str(), (int)data.id1.length());
|
|
if ((data.R >= 4) && (! data.encrypt_metadata))
|
|
{
|
|
char bytes[4];
|
|
memset(bytes, 0xff, 4);
|
|
md5.encodeDataIncrementally(bytes, 4);
|
|
}
|
|
MD5::Digest digest;
|
|
iterate_md5_digest(md5, digest, ((data.R >= 3) ? 50 : 0));
|
|
return std::string((char*)digest, data.Length_bytes);
|
|
}
|
|
|
|
static void
|
|
compute_O_rc4_key(std::string const& user_password,
|
|
std::string const& owner_password,
|
|
QPDF::EncryptionData const& data,
|
|
unsigned char key[O_key_bytes])
|
|
{
|
|
std::string password = owner_password;
|
|
if (password.empty())
|
|
{
|
|
password = user_password;
|
|
}
|
|
MD5 md5;
|
|
md5.encodeDataIncrementally(
|
|
pad_or_truncate_password(password).c_str(), key_bytes);
|
|
MD5::Digest digest;
|
|
iterate_md5_digest(md5, digest, ((data.R >= 3) ? 50 : 0));
|
|
memcpy(key, digest, O_key_bytes);
|
|
}
|
|
|
|
static std::string
|
|
compute_O_value(std::string const& user_password,
|
|
std::string const& owner_password,
|
|
QPDF::EncryptionData const& data)
|
|
{
|
|
// Algorithm 3.3 from the PDF 1.7 Reference Manual
|
|
|
|
unsigned char O_key[O_key_bytes];
|
|
compute_O_rc4_key(user_password, owner_password, data, O_key);
|
|
|
|
char upass[key_bytes];
|
|
pad_or_truncate_password(user_password, upass);
|
|
iterate_rc4((unsigned char*) upass, key_bytes,
|
|
O_key, data.Length_bytes, (data.R >= 3) ? 20 : 1, false);
|
|
return std::string(upass, key_bytes);
|
|
}
|
|
|
|
static
|
|
std::string
|
|
compute_U_value_R2(std::string const& user_password,
|
|
QPDF::EncryptionData const& data)
|
|
{
|
|
// Algorithm 3.4 from the PDF 1.7 Reference Manual
|
|
|
|
std::string k1 = QPDF::compute_encryption_key(user_password, data);
|
|
char udata[key_bytes];
|
|
pad_or_truncate_password("", udata);
|
|
iterate_rc4((unsigned char*) udata, key_bytes,
|
|
(unsigned char*)k1.c_str(), data.Length_bytes, 1, false);
|
|
return std::string(udata, key_bytes);
|
|
}
|
|
|
|
static
|
|
std::string
|
|
compute_U_value_R3(std::string const& user_password,
|
|
QPDF::EncryptionData const& data)
|
|
{
|
|
// Algorithm 3.5 from the PDF 1.7 Reference Manual
|
|
|
|
std::string k1 = QPDF::compute_encryption_key(user_password, data);
|
|
MD5 md5;
|
|
md5.encodeDataIncrementally(
|
|
pad_or_truncate_password("").c_str(), key_bytes);
|
|
md5.encodeDataIncrementally(data.id1.c_str(), (int)data.id1.length());
|
|
MD5::Digest digest;
|
|
md5.digest(digest);
|
|
iterate_rc4(digest, sizeof(MD5::Digest),
|
|
(unsigned char*) k1.c_str(), data.Length_bytes, 20, false);
|
|
char result[key_bytes];
|
|
memcpy(result, digest, sizeof(MD5::Digest));
|
|
// pad with arbitrary data -- make it consistent for the sake of
|
|
// testing
|
|
for (unsigned int i = sizeof(MD5::Digest); i < key_bytes; ++i)
|
|
{
|
|
result[i] = (char)((i * i) % 0xff);
|
|
}
|
|
return std::string(result, key_bytes);
|
|
}
|
|
|
|
static std::string
|
|
compute_U_value(std::string const& user_password,
|
|
QPDF::EncryptionData const& data)
|
|
{
|
|
if (data.R >= 3)
|
|
{
|
|
return compute_U_value_R3(user_password, data);
|
|
}
|
|
|
|
return compute_U_value_R2(user_password, data);
|
|
}
|
|
|
|
static bool
|
|
check_user_password(std::string const& user_password,
|
|
QPDF::EncryptionData const& data)
|
|
{
|
|
// Algorithm 3.6 from the PDF 1.7 Reference Manual
|
|
|
|
std::string u_value = compute_U_value(user_password, data);
|
|
int to_compare = ((data.R >= 3) ? sizeof(MD5::Digest) : key_bytes);
|
|
return (memcmp(data.U.c_str(), u_value.c_str(), to_compare) == 0);
|
|
}
|
|
|
|
static bool
|
|
check_owner_password(std::string& user_password,
|
|
std::string const& owner_password,
|
|
QPDF::EncryptionData const& data)
|
|
{
|
|
// Algorithm 3.7 from the PDF 1.7 Reference Manual
|
|
|
|
unsigned char key[O_key_bytes];
|
|
compute_O_rc4_key(user_password, owner_password, data, key);
|
|
unsigned char O_data[key_bytes];
|
|
memcpy(O_data, (unsigned char*) data.O.c_str(), key_bytes);
|
|
iterate_rc4(O_data, key_bytes, key, data.Length_bytes,
|
|
(data.R >= 3) ? 20 : 1, true);
|
|
std::string new_user_password =
|
|
std::string((char*)O_data, key_bytes);
|
|
bool result = false;
|
|
if (check_user_password(new_user_password, data))
|
|
{
|
|
result = true;
|
|
user_password = new_user_password;
|
|
}
|
|
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 if (filter == "/Identity")
|
|
{
|
|
return e_none;
|
|
}
|
|
else
|
|
{
|
|
return e_unknown;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Default: /Identity
|
|
return e_none;
|
|
}
|
|
}
|
|
|
|
void
|
|
QPDF::initializeEncryption()
|
|
{
|
|
if (this->encryption_initialized)
|
|
{
|
|
return;
|
|
}
|
|
this->encryption_initialized = true;
|
|
|
|
// After we initialize encryption parameters, we must used stored
|
|
// key information and never look at /Encrypt again. Otherwise,
|
|
// things could go wrong if someone mutates the encryption
|
|
// dictionary.
|
|
|
|
if (! this->trailer.hasKey("/Encrypt"))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Go ahead and set this->encryption here. That way, isEncrypted
|
|
// will return true even if there were errors reading the
|
|
// encryption dictionary.
|
|
this->encrypted = true;
|
|
|
|
QPDFObjectHandle id_obj = this->trailer.getKey("/ID");
|
|
if (! (id_obj.isArray() &&
|
|
(id_obj.getArrayNItems() == 2) &&
|
|
id_obj.getArrayItem(0).isString()))
|
|
{
|
|
throw QPDFExc(qpdf_e_damaged_pdf, this->file->getName(),
|
|
"trailer", this->file->getLastOffset(),
|
|
"invalid /ID in trailer dictionary");
|
|
}
|
|
|
|
std::string id1 = id_obj.getArrayItem(0).getStringValue();
|
|
QPDFObjectHandle encryption_dict = this->trailer.getKey("/Encrypt");
|
|
if (! encryption_dict.isDictionary())
|
|
{
|
|
throw QPDFExc(qpdf_e_damaged_pdf, this->file->getName(),
|
|
this->last_object_description,
|
|
this->file->getLastOffset(),
|
|
"/Encrypt in trailer dictionary is not a dictionary");
|
|
}
|
|
|
|
if (! (encryption_dict.getKey("/Filter").isName() &&
|
|
(encryption_dict.getKey("/Filter").getName() == "/Standard")))
|
|
{
|
|
throw QPDFExc(qpdf_e_damaged_pdf, this->file->getName(),
|
|
"encryption dictionary", this->file->getLastOffset(),
|
|
"unsupported encryption filter");
|
|
}
|
|
if (! encryption_dict.getKey("/SubFilter").isNull())
|
|
{
|
|
warn(QPDFExc(qpdf_e_unsupported, this->file->getName(),
|
|
"encryption dictionary", this->file->getLastOffset(),
|
|
"file uses encryption SubFilters,"
|
|
" which qpdf does not support"));
|
|
}
|
|
|
|
if (! (encryption_dict.getKey("/V").isInteger() &&
|
|
encryption_dict.getKey("/R").isInteger() &&
|
|
encryption_dict.getKey("/O").isString() &&
|
|
encryption_dict.getKey("/U").isString() &&
|
|
encryption_dict.getKey("/P").isInteger()))
|
|
{
|
|
throw QPDFExc(qpdf_e_damaged_pdf, this->file->getName(),
|
|
"encryption dictionary", this->file->getLastOffset(),
|
|
"some encryption dictionary parameters are missing "
|
|
"or the wrong type");
|
|
}
|
|
|
|
int V = encryption_dict.getKey("/V").getIntValue();
|
|
int R = encryption_dict.getKey("/R").getIntValue();
|
|
std::string O = encryption_dict.getKey("/O").getStringValue();
|
|
std::string U = encryption_dict.getKey("/U").getStringValue();
|
|
unsigned int P = (unsigned int) encryption_dict.getKey("/P").getIntValue();
|
|
|
|
if (! (((R == 2) || (R == 3) || (R == 4)) &&
|
|
((V == 1) || (V == 2) || (V == 4))))
|
|
{
|
|
throw QPDFExc(qpdf_e_unsupported, this->file->getName(),
|
|
"encryption dictionary", this->file->getLastOffset(),
|
|
"Unsupported /R or /V in encryption dictionary");
|
|
}
|
|
|
|
this->encryption_V = V;
|
|
|
|
if (! ((O.length() == key_bytes) && (U.length() == key_bytes)))
|
|
{
|
|
throw QPDFExc(qpdf_e_damaged_pdf, this->file->getName(),
|
|
"encryption dictionary", this->file->getLastOffset(),
|
|
"incorrect length for /O and/or /P in "
|
|
"encryption dictionary");
|
|
}
|
|
|
|
int Length = 40;
|
|
if (encryption_dict.getKey("/Length").isInteger())
|
|
{
|
|
Length = encryption_dict.getKey("/Length").getIntValue();
|
|
if ((Length % 8) || (Length < 40) || (Length > 128))
|
|
{
|
|
throw QPDFExc(qpdf_e_damaged_pdf, this->file->getName(),
|
|
"encryption dictionary", this->file->getLastOffset(),
|
|
"invalid /Length value in encryption dictionary");
|
|
}
|
|
}
|
|
|
|
this->encrypt_metadata = true;
|
|
if ((V >= 4) && (encryption_dict.getKey("/EncryptMetadata").isBool()))
|
|
{
|
|
this->encrypt_metadata =
|
|
encryption_dict.getKey("/EncryptMetadata").getBoolValue();
|
|
}
|
|
|
|
if (V == 4)
|
|
{
|
|
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")
|
|
{
|
|
QTC::TC("qpdf", "QPDF_encryption CFM V2");
|
|
method = e_rc4;
|
|
}
|
|
else if (method_name == "/AESV2")
|
|
{
|
|
QTC::TC("qpdf", "QPDF_encryption CFM AESV2");
|
|
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;
|
|
}
|
|
if (this->cf_file != this->cf_stream)
|
|
{
|
|
// The issue for qpdf is that it can't tell the difference
|
|
// between an embedded file stream and a regular stream.
|
|
// Search for a comment containing cf_file. To fix this,
|
|
// we need files with encrypted embedded files and
|
|
// non-encrypted native streams and vice versa. Also if
|
|
// it is possible for them to be encrypted in different
|
|
// ways, we should have some of those too. In cases where
|
|
// we can detect whether a stream is encrypted or not, we
|
|
// might want to try to detecet that automatically in
|
|
// defense of possible logic errors surrounding detection
|
|
// of embedded file streams, unless that's really clear
|
|
// from the specification.
|
|
throw QPDFExc(qpdf_e_unsupported, this->file->getName(),
|
|
"encryption dictionary", this->file->getLastOffset(),
|
|
"This document has embedded files that are"
|
|
" encrypted differently from the rest of the file."
|
|
" qpdf does not presently support this due to"
|
|
" lack of test data; if possible, please submit"
|
|
" a bug report that includes this file.");
|
|
}
|
|
}
|
|
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
|
|
}
|
|
else if (check_user_password(this->provided_password, data))
|
|
{
|
|
this->user_password = this->provided_password;
|
|
}
|
|
else
|
|
{
|
|
throw QPDFExc(qpdf_e_password, this->file->getName(),
|
|
"", 0, "invalid password");
|
|
}
|
|
|
|
this->encryption_key = compute_encryption_key(this->user_password, data);
|
|
}
|
|
|
|
std::string
|
|
QPDF::getKeyForObject(int objid, int generation, bool use_aes)
|
|
{
|
|
if (! this->encrypted)
|
|
{
|
|
throw std::logic_error(
|
|
"request for encryption key in non-encrypted PDF");
|
|
}
|
|
|
|
if (! ((objid == this->cached_key_objid) &&
|
|
(generation == this->cached_key_generation)))
|
|
{
|
|
this->cached_object_encryption_key =
|
|
compute_data_key(this->encryption_key, objid, generation, use_aes);
|
|
this->cached_key_objid = objid;
|
|
this->cached_key_generation = generation;
|
|
}
|
|
|
|
return this->cached_object_encryption_key;
|
|
}
|
|
|
|
void
|
|
QPDF::decryptString(std::string& str, int objid, int generation)
|
|
{
|
|
if (objid == 0)
|
|
{
|
|
return;
|
|
}
|
|
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(qpdf_e_damaged_pdf, this->file->getName(),
|
|
this->last_object_description,
|
|
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);
|
|
try
|
|
{
|
|
if (use_aes)
|
|
{
|
|
QTC::TC("qpdf", "QPDF_encryption aes decode string");
|
|
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();
|
|
PointerHolder<Buffer> buf = bufpl.getBuffer();
|
|
str = std::string((char*)buf->getBuffer(), buf->getSize());
|
|
}
|
|
else
|
|
{
|
|
QTC::TC("qpdf", "QPDF_encryption rc4 decode string");
|
|
unsigned int vlen = (int)str.length();
|
|
// Using PointerHolder guarantees that tmp will
|
|
// be freed even if rc4.process throws an exception.
|
|
PointerHolder<char> tmp(true, QUtil::copy_string(str));
|
|
RC4 rc4((unsigned char const*)key.c_str(), (int)key.length());
|
|
rc4.process((unsigned char*)tmp.getPointer(), vlen);
|
|
str = std::string(tmp.getPointer(), vlen);
|
|
}
|
|
}
|
|
catch (QPDFExc& e)
|
|
{
|
|
throw;
|
|
}
|
|
catch (std::runtime_error& e)
|
|
{
|
|
throw QPDFExc(qpdf_e_damaged_pdf, this->file->getName(),
|
|
this->last_object_description,
|
|
this->file->getLastOffset(),
|
|
"error decrypting string for object " +
|
|
QUtil::int_to_string(objid) + " " +
|
|
QUtil::int_to_string(generation) + ": " + e.what());
|
|
}
|
|
}
|
|
|
|
void
|
|
QPDF::decryptStream(Pipeline*& pipeline, int objid, int generation,
|
|
QPDFObjectHandle& stream_dict,
|
|
std::vector<PointerHolder<Pipeline> >& heap)
|
|
{
|
|
std::string type;
|
|
if (stream_dict.getKey("/Type").isName())
|
|
{
|
|
type = stream_dict.getKey("/Type").getName();
|
|
}
|
|
if (type == "/XRef")
|
|
{
|
|
QTC::TC("qpdf", "QPDF_encryption xref stream from encrypted file");
|
|
return;
|
|
}
|
|
bool use_aes = false;
|
|
if (this->encryption_V == 4)
|
|
{
|
|
encryption_method_e method = e_unknown;
|
|
std::string method_source = "/StmF from /Encrypt dictionary";
|
|
|
|
if (stream_dict.getKey("/Filter").isOrHasName("/Crypt") &&
|
|
stream_dict.getKey("/DecodeParms").isDictionary())
|
|
{
|
|
QPDFObjectHandle decode_parms = stream_dict.getKey("/DecodeParms");
|
|
if (decode_parms.getKey("/Type").isName() &&
|
|
(decode_parms.getKey("/Type").getName() ==
|
|
"/CryptFilterDecodeParms"))
|
|
{
|
|
QTC::TC("qpdf", "QPDF_encryption stream crypt filter");
|
|
method = interpretCF(decode_parms.getKey("/Name"));
|
|
method_source = "stream's Crypt decode parameters";
|
|
}
|
|
}
|
|
|
|
if (method == e_unknown)
|
|
{
|
|
if ((! this->encrypt_metadata) && (type == "/Metadata"))
|
|
{
|
|
QTC::TC("qpdf", "QPDF_encryption cleartext metadata");
|
|
method = e_none;
|
|
}
|
|
else
|
|
{
|
|
// NOTE: We should should use cf_file if this is an
|
|
// embedded file, but we can't yet detect embedded
|
|
// file streams as such. When fixing, search for all
|
|
// occurrences of cf_file to find a reference to this
|
|
// comment.
|
|
method = this->cf_stream;
|
|
}
|
|
}
|
|
use_aes = false;
|
|
switch (method)
|
|
{
|
|
case e_none:
|
|
return;
|
|
break;
|
|
|
|
case e_aes:
|
|
use_aes = true;
|
|
break;
|
|
|
|
case e_rc4:
|
|
break;
|
|
|
|
default:
|
|
// filter local to this stream.
|
|
warn(QPDFExc(qpdf_e_damaged_pdf, this->file->getName(),
|
|
this->last_object_description,
|
|
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);
|
|
if (use_aes)
|
|
{
|
|
QTC::TC("qpdf", "QPDF_encryption aes decode stream");
|
|
assert(key.length() == Pl_AES_PDF::key_size);
|
|
pipeline = new Pl_AES_PDF("AES stream decryption", pipeline,
|
|
false, (unsigned char*) key.c_str());
|
|
}
|
|
else
|
|
{
|
|
QTC::TC("qpdf", "QPDF_encryption rc4 decode stream");
|
|
pipeline = new Pl_RC4("RC4 stream decryption", pipeline,
|
|
(unsigned char*) key.c_str(), (int)key.length());
|
|
}
|
|
heap.push_back(pipeline);
|
|
}
|
|
|
|
void
|
|
QPDF::compute_encryption_O_U(
|
|
char const* user_password, char const* owner_password,
|
|
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, encrypt_metadata);
|
|
data.O = compute_O_value(user_password, owner_password, data);
|
|
O = data.O;
|
|
U = compute_U_value(user_password, data);
|
|
}
|
|
|
|
std::string const&
|
|
QPDF::getPaddedUserPassword() const
|
|
{
|
|
return this->user_password;
|
|
}
|
|
|
|
std::string
|
|
QPDF::getTrimmedUserPassword() const
|
|
{
|
|
std::string result = this->user_password;
|
|
trim_user_password(result);
|
|
return result;
|
|
}
|
|
|
|
bool
|
|
QPDF::isEncrypted() const
|
|
{
|
|
return this->encrypted;
|
|
}
|
|
|
|
bool
|
|
QPDF::isEncrypted(int& R, int& P)
|
|
{
|
|
int V;
|
|
encryption_method_e stream, string, file;
|
|
return isEncrypted(R, P, V, stream, string, file);
|
|
}
|
|
|
|
bool
|
|
QPDF::isEncrypted(int& R, int& P, int& V,
|
|
encryption_method_e& stream_method,
|
|
encryption_method_e& string_method,
|
|
encryption_method_e& file_method)
|
|
{
|
|
if (this->encrypted)
|
|
{
|
|
QPDFObjectHandle trailer = getTrailer();
|
|
QPDFObjectHandle encrypt = trailer.getKey("/Encrypt");
|
|
QPDFObjectHandle Pkey = encrypt.getKey("/P");
|
|
QPDFObjectHandle Rkey = encrypt.getKey("/R");
|
|
QPDFObjectHandle Vkey = encrypt.getKey("/V");
|
|
P = Pkey.getIntValue();
|
|
R = Rkey.getIntValue();
|
|
V = Vkey.getIntValue();
|
|
stream_method = this->cf_stream;
|
|
string_method = this->cf_stream;
|
|
file_method = this->cf_file;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static bool
|
|
is_bit_set(int P, int bit)
|
|
{
|
|
// Bits in P are numbered from 1 in the spec
|
|
return (P & (1 << (bit - 1)));
|
|
}
|
|
|
|
bool
|
|
QPDF::allowAccessibility()
|
|
{
|
|
int R = 0;
|
|
int P = 0;
|
|
bool status = true;
|
|
if (isEncrypted(R, P))
|
|
{
|
|
if (R < 3)
|
|
{
|
|
status = is_bit_set(P, 5);
|
|
}
|
|
else
|
|
{
|
|
status = is_bit_set(P, 10);
|
|
}
|
|
}
|
|
return status;
|
|
}
|
|
|
|
bool
|
|
QPDF::allowExtractAll()
|
|
{
|
|
int R = 0;
|
|
int P = 0;
|
|
bool status = true;
|
|
if (isEncrypted(R, P))
|
|
{
|
|
status = is_bit_set(P, 5);
|
|
}
|
|
return status;
|
|
}
|
|
|
|
bool
|
|
QPDF::allowPrintLowRes()
|
|
{
|
|
int R = 0;
|
|
int P = 0;
|
|
bool status = true;
|
|
if (isEncrypted(R, P))
|
|
{
|
|
status = is_bit_set(P, 3);
|
|
}
|
|
return status;
|
|
}
|
|
|
|
bool
|
|
QPDF::allowPrintHighRes()
|
|
{
|
|
int R = 0;
|
|
int P = 0;
|
|
bool status = true;
|
|
if (isEncrypted(R, P))
|
|
{
|
|
status = is_bit_set(P, 3);
|
|
if ((R >= 3) && (! is_bit_set(P, 12)))
|
|
{
|
|
status = false;
|
|
}
|
|
}
|
|
return status;
|
|
}
|
|
|
|
bool
|
|
QPDF::allowModifyAssembly()
|
|
{
|
|
int R = 0;
|
|
int P = 0;
|
|
bool status = true;
|
|
if (isEncrypted(R, P))
|
|
{
|
|
if (R < 3)
|
|
{
|
|
status = is_bit_set(P, 4);
|
|
}
|
|
else
|
|
{
|
|
status = is_bit_set(P, 11);
|
|
}
|
|
}
|
|
return status;
|
|
}
|
|
|
|
bool
|
|
QPDF::allowModifyForm()
|
|
{
|
|
int R = 0;
|
|
int P = 0;
|
|
bool status = true;
|
|
if (isEncrypted(R, P))
|
|
{
|
|
if (R < 3)
|
|
{
|
|
status = is_bit_set(P, 6);
|
|
}
|
|
else
|
|
{
|
|
status = is_bit_set(P, 9);
|
|
}
|
|
}
|
|
return status;
|
|
}
|
|
|
|
bool
|
|
QPDF::allowModifyAnnotation()
|
|
{
|
|
int R = 0;
|
|
int P = 0;
|
|
bool status = true;
|
|
if (isEncrypted(R, P))
|
|
{
|
|
status = is_bit_set(P, 6);
|
|
}
|
|
return status;
|
|
}
|
|
|
|
bool
|
|
QPDF::allowModifyOther()
|
|
{
|
|
int R = 0;
|
|
int P = 0;
|
|
bool status = true;
|
|
if (isEncrypted(R, P))
|
|
{
|
|
status = is_bit_set(P, 4);
|
|
}
|
|
return status;
|
|
}
|
|
|
|
bool
|
|
QPDF::allowModifyAll()
|
|
{
|
|
int R = 0;
|
|
int P = 0;
|
|
bool status = true;
|
|
if (isEncrypted(R, P))
|
|
{
|
|
status = (is_bit_set(P, 4) && is_bit_set(P, 6));
|
|
if (R >= 3)
|
|
{
|
|
status = status && (is_bit_set(P, 9) && is_bit_set(P, 11));
|
|
}
|
|
}
|
|
return status;
|
|
}
|