2
1
mirror of https://github.com/qpdf/qpdf.git synced 2024-11-16 01:27:07 +00:00
qpdf/libqpdf/QPDF_encryption.cc
Jay Berkenbilt 4f24617e1e Code clean up: use range-style for loops wherever possible
Where not possible, use "auto" to get the iterator type.

Editorial note: I have avoid this change for a long time because of
not wanting to make gratuitous changes to version history, which can
obscure when certain changes were made, but with having recently
touched every single file to apply automatic code formatting and with
making several broad changes to the API, I decided it was time to take
the plunge and get rid of the older (pre-C++11) verbose iterator
syntax. The new code is just easier to read and understand, and in
many cases, it will be more effecient as fewer temporary copies are
being made.

m-holger, if you're reading, you can see that I've finally come
around. :-)
2022-04-30 13:27:18 -04:00

1564 lines
47 KiB
C++

// This file implements methods from the QPDF class that involve
// encryption.
#include <qpdf/QPDF.hh>
#include <qpdf/QPDFExc.hh>
#include <qpdf/MD5.hh>
#include <qpdf/Pl_AES_PDF.hh>
#include <qpdf/Pl_Buffer.hh>
#include <qpdf/Pl_RC4.hh>
#include <qpdf/Pl_SHA2.hh>
#include <qpdf/QTC.hh>
#include <qpdf/QUtil.hh>
#include <qpdf/RC4.hh>
#include <algorithm>
#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 key_bytes = 32;
// V4 key lengths apply to V <= 4
static unsigned int const OU_key_bytes_V4 = sizeof(MD5::Digest);
static unsigned int const OU_key_bytes_V5 = 48;
static unsigned int const OUE_key_bytes_V5 = 32;
static unsigned int const Perms_key_bytes_V5 = 16;
int
QPDF::EncryptionData::getV() const
{
return this->V;
}
int
QPDF::EncryptionData::getR() const
{
return this->R;
}
int
QPDF::EncryptionData::getLengthBytes() const
{
return this->Length_bytes;
}
int
QPDF::EncryptionData::getP() const
{
return this->P;
}
std::string const&
QPDF::EncryptionData::getO() const
{
return this->O;
}
std::string const&
QPDF::EncryptionData::getU() const
{
return this->U;
}
std::string const&
QPDF::EncryptionData::getOE() const
{
return this->OE;
}
std::string const&
QPDF::EncryptionData::getUE() const
{
return this->UE;
}
std::string const&
QPDF::EncryptionData::getPerms() const
{
return this->Perms;
}
std::string const&
QPDF::EncryptionData::getId1() const
{
return this->id1;
}
bool
QPDF::EncryptionData::getEncryptMetadata() const
{
return this->encrypt_metadata;
}
void
QPDF::EncryptionData::setO(std::string const& O)
{
this->O = O;
}
void
QPDF::EncryptionData::setU(std::string const& U)
{
this->U = U;
}
void
QPDF::EncryptionData::setV5EncryptionParameters(
std::string const& O,
std::string const& OE,
std::string const& U,
std::string const& UE,
std::string const& Perms)
{
this->O = O;
this->OE = OE;
this->U = U;
this->UE = UE;
this->Perms = Perms;
}
static void
pad_or_truncate_password_V4(std::string const& password, char k1[key_bytes])
{
size_t password_bytes =
std::min(QIntC::to_size(key_bytes), password.length());
size_t 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* p1 = cstr;
char const* p2 = 0;
while ((p2 = strchr(p1, '\x28')) != 0) {
size_t idx = toS(p2 - cstr);
if (memcmp(p2, padding_string, len - idx) == 0) {
user_password = user_password.substr(0, idx);
return;
} else {
QTC::TC("qpdf", "QPDF_encryption skip 0x28");
p1 = p2 + 1;
}
}
}
static std::string
pad_or_truncate_password_V4(std::string const& password)
{
char k1[key_bytes];
pad_or_truncate_password_V4(password, k1);
return std::string(k1, key_bytes);
}
static std::string
truncate_password_V5(std::string const& password)
{
return password.substr(
0, std::min(static_cast<size_t>(127), password.length()));
}
static void
iterate_md5_digest(MD5& md5, MD5::Digest& digest, int iterations, int key_len)
{
md5.digest(digest);
for (int i = 0; i < iterations; ++i) {
MD5 m;
m.encodeDataIncrementally(
reinterpret_cast<char*>(digest), QIntC::to_size(key_len));
m.digest(digest);
}
}
static void
iterate_rc4(
unsigned char* data,
size_t data_len,
unsigned char* okey,
int key_len,
int iterations,
bool reverse)
{
auto key_ph = std::make_unique<unsigned char[]>(QIntC::to_size(key_len));
unsigned char* key = key_ph.get();
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] = static_cast<unsigned char>(okey[j] ^ xor_value);
}
RC4 rc4(key, QIntC::to_int(key_len));
rc4.process(data, data_len);
}
}
static std::string
process_with_aes(
std::string const& key,
bool encrypt,
std::string const& data,
size_t outlength = 0,
unsigned int repetitions = 1,
unsigned char const* iv = 0,
size_t iv_length = 0)
{
Pl_Buffer buffer("buffer");
Pl_AES_PDF aes(
"aes",
&buffer,
encrypt,
QUtil::unsigned_char_pointer(key),
QIntC::to_uint(key.length()));
if (iv) {
aes.setIV(iv, iv_length);
} else {
aes.useZeroIV();
}
aes.disablePadding();
for (unsigned int i = 0; i < repetitions; ++i) {
aes.write(QUtil::unsigned_char_pointer(data), data.length());
}
aes.finish();
auto bufp = buffer.getBufferSharedPointer();
if (outlength == 0) {
outlength = bufp->getSize();
} else {
outlength = std::min(outlength, bufp->getSize());
}
return std::string(reinterpret_cast<char*>(bufp->getBuffer()), outlength);
}
static std::string
hash_V5(
std::string const& password,
std::string const& salt,
std::string const& udata,
QPDF::EncryptionData const& data)
{
Pl_SHA2 hash(256);
hash.write(QUtil::unsigned_char_pointer(password), password.length());
hash.write(QUtil::unsigned_char_pointer(salt), salt.length());
hash.write(QUtil::unsigned_char_pointer(udata), udata.length());
hash.finish();
std::string K = hash.getRawDigest();
std::string result;
if (data.getR() < 6) {
result = K;
} else {
// Algorithm 2.B from ISO 32000-1 chapter 7: Computing a hash
int round_number = 0;
bool done = false;
while (!done) {
// The hash algorithm has us setting K initially to the R5
// value and then repeating a series of steps 64 times
// before starting with the termination case testing. The
// wording of the specification is very unclear as to the
// exact number of times it should be run since the
// wording about whether the initial setup counts as round
// 0 or not is ambiguous. This code counts the initial
// setup (R5) value as round 0, which appears to be
// correct. This was determined to be correct by
// increasing or decreasing the number of rounds by 1 or 2
// from this value and generating 20 test files. In this
// interpretation, all the test files worked with Adobe
// Reader X. In the other configurations, many of the
// files did not work, and we were accurately able to
// predict which files didn't work by looking at the
// conditions under which we terminated repetition.
++round_number;
std::string K1 = password + K + udata;
assert(K.length() >= 32);
std::string E = process_with_aes(
K.substr(0, 16),
true,
K1,
0,
64,
QUtil::unsigned_char_pointer(K.substr(16, 16)),
16);
// E_mod_3 is supposed to be mod 3 of the first 16 bytes
// of E taken as as a (128-bit) big-endian number. Since
// (xy mod n) is equal to ((x mod n) + (y mod n)) mod n
// and since 256 mod n is 1, we can just take the sums of
// the the mod 3s of each byte to get the same result.
int E_mod_3 = 0;
for (unsigned int i = 0; i < 16; ++i) {
E_mod_3 += static_cast<unsigned char>(E.at(i));
}
E_mod_3 %= 3;
int next_hash = ((E_mod_3 == 0) ? 256 : (E_mod_3 == 1) ? 384 : 512);
Pl_SHA2 sha2(next_hash);
sha2.write(QUtil::unsigned_char_pointer(E), E.length());
sha2.finish();
K = sha2.getRawDigest();
if (round_number >= 64) {
unsigned int ch = static_cast<unsigned char>(*(E.rbegin()));
if (ch <= QIntC::to_uint(round_number - 32)) {
done = true;
}
}
}
result = K.substr(0, 32);
}
return result;
}
static void
pad_short_parameter(std::string& param, size_t max_len)
{
if (param.length() < max_len) {
QTC::TC("qpdf", "QPDF_encryption pad short parameter");
param.append(max_len - param.length(), '\0');
}
}
std::string
QPDF::compute_data_key(
std::string const& encryption_key,
int objid,
int generation,
bool use_aes,
int encryption_V,
int encryption_R)
{
// Algorithm 3.1 from the PDF 1.7 Reference Manual
std::string result = encryption_key;
if (encryption_V >= 5) {
// Algorithm 3.1a (PDF 1.7 extension level 3): just use
// encryption key straight.
return result;
}
// Append low three bytes of object ID and low two bytes of generation
result.append(1, static_cast<char>(objid & 0xff));
result.append(1, static_cast<char>((objid >> 8) & 0xff));
result.append(1, static_cast<char>((objid >> 16) & 0xff));
result.append(1, static_cast<char>(generation & 0xff));
result.append(1, static_cast<char>((generation >> 8) & 0xff));
if (use_aes) {
result += "sAlT";
}
MD5 md5;
md5.encodeDataIncrementally(result.c_str(), result.length());
MD5::Digest digest;
md5.digest(digest);
return std::string(
reinterpret_cast<char*>(digest), std::min(result.length(), toS(16)));
}
std::string
QPDF::compute_encryption_key(
std::string const& password, EncryptionData const& data)
{
if (data.getV() >= 5) {
// For V >= 5, the encryption key is generated and stored in
// the file, encrypted separately with both user and owner
// passwords.
return recover_encryption_key_with_password(password, data);
} else {
// For V < 5, the encryption key is derived from the user
// password.
return compute_encryption_key_from_password(password, data);
}
}
std::string
QPDF::compute_encryption_key_from_password(
std::string const& password, EncryptionData const& data)
{
// Algorithm 3.2 from the PDF 1.7 Reference Manual
// This code does not properly handle Unicode passwords.
// Passwords are supposed to be converted from OS codepage
// characters to PDFDocEncoding. Unicode passwords are supposed
// to be converted to OS codepage before converting to
// PDFDocEncoding. We instead require the password to be
// presented in its final form.
MD5 md5;
md5.encodeDataIncrementally(
pad_or_truncate_password_V4(password).c_str(), key_bytes);
md5.encodeDataIncrementally(data.getO().c_str(), key_bytes);
char pbytes[4];
int P = data.getP();
pbytes[0] = static_cast<char>(P & 0xff);
pbytes[1] = static_cast<char>((P >> 8) & 0xff);
pbytes[2] = static_cast<char>((P >> 16) & 0xff);
pbytes[3] = static_cast<char>((P >> 24) & 0xff);
md5.encodeDataIncrementally(pbytes, 4);
md5.encodeDataIncrementally(data.getId1().c_str(), data.getId1().length());
if ((data.getR() >= 4) && (!data.getEncryptMetadata())) {
char bytes[4];
memset(bytes, 0xff, 4);
md5.encodeDataIncrementally(bytes, 4);
}
MD5::Digest digest;
int key_len =
std::min(QIntC::to_int(sizeof(digest)), data.getLengthBytes());
iterate_md5_digest(md5, digest, ((data.getR() >= 3) ? 50 : 0), key_len);
return std::string(
reinterpret_cast<char*>(digest), QIntC::to_size(key_len));
}
static void
compute_O_rc4_key(
std::string const& user_password,
std::string const& owner_password,
QPDF::EncryptionData const& data,
unsigned char key[OU_key_bytes_V4])
{
if (data.getV() >= 5) {
throw std::logic_error("compute_O_rc4_key called for file with V >= 5");
}
std::string password = owner_password;
if (password.empty()) {
password = user_password;
}
MD5 md5;
md5.encodeDataIncrementally(
pad_or_truncate_password_V4(password).c_str(), key_bytes);
MD5::Digest digest;
int key_len =
std::min(QIntC::to_int(sizeof(digest)), data.getLengthBytes());
iterate_md5_digest(md5, digest, ((data.getR() >= 3) ? 50 : 0), key_len);
memcpy(key, digest, OU_key_bytes_V4);
}
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[OU_key_bytes_V4];
compute_O_rc4_key(user_password, owner_password, data, O_key);
char upass[key_bytes];
pad_or_truncate_password_V4(user_password, upass);
std::string k1(reinterpret_cast<char*>(O_key), OU_key_bytes_V4);
pad_short_parameter(k1, QIntC::to_size(data.getLengthBytes()));
iterate_rc4(
QUtil::unsigned_char_pointer(upass),
key_bytes,
O_key,
data.getLengthBytes(),
(data.getR() >= 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_V4("", udata);
pad_short_parameter(k1, QIntC::to_size(data.getLengthBytes()));
iterate_rc4(
QUtil::unsigned_char_pointer(udata),
key_bytes,
QUtil::unsigned_char_pointer(k1),
data.getLengthBytes(),
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_V4("").c_str(), key_bytes);
md5.encodeDataIncrementally(data.getId1().c_str(), data.getId1().length());
MD5::Digest digest;
md5.digest(digest);
pad_short_parameter(k1, QIntC::to_size(data.getLengthBytes()));
iterate_rc4(
digest,
sizeof(MD5::Digest),
QUtil::unsigned_char_pointer(k1),
data.getLengthBytes(),
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] = static_cast<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.getR() >= 3) {
return compute_U_value_R3(user_password, data);
}
return compute_U_value_R2(user_password, data);
}
static bool
check_user_password_V4(
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);
size_t to_compare = ((data.getR() >= 3) ? sizeof(MD5::Digest) : key_bytes);
return (memcmp(data.getU().c_str(), u_value.c_str(), to_compare) == 0);
}
static bool
check_user_password_V5(
std::string const& user_password, QPDF::EncryptionData const& data)
{
// Algorithm 3.11 from the PDF 1.7 extension level 3
std::string user_data = data.getU().substr(0, 32);
std::string validation_salt = data.getU().substr(32, 8);
std::string password = truncate_password_V5(user_password);
return (hash_V5(password, validation_salt, "", data) == user_data);
}
static bool
check_user_password(
std::string const& user_password, QPDF::EncryptionData const& data)
{
if (data.getV() < 5) {
return check_user_password_V4(user_password, data);
} else {
return check_user_password_V5(user_password, data);
}
}
static bool
check_owner_password_V4(
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[OU_key_bytes_V4];
compute_O_rc4_key(user_password, owner_password, data, key);
unsigned char O_data[key_bytes];
memcpy(O_data, QUtil::unsigned_char_pointer(data.getO()), key_bytes);
std::string k1(reinterpret_cast<char*>(key), OU_key_bytes_V4);
pad_short_parameter(k1, QIntC::to_size(data.getLengthBytes()));
iterate_rc4(
O_data,
key_bytes,
QUtil::unsigned_char_pointer(k1),
data.getLengthBytes(),
(data.getR() >= 3) ? 20 : 1,
true);
std::string new_user_password =
std::string(reinterpret_cast<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;
}
static bool
check_owner_password_V5(
std::string const& owner_password, QPDF::EncryptionData const& data)
{
// Algorithm 3.12 from the PDF 1.7 extension level 3
std::string user_data = data.getU().substr(0, 48);
std::string owner_data = data.getO().substr(0, 32);
std::string validation_salt = data.getO().substr(32, 8);
std::string password = truncate_password_V5(owner_password);
return (hash_V5(password, validation_salt, user_data, data) == owner_data);
}
static bool
check_owner_password(
std::string& user_password,
std::string const& owner_password,
QPDF::EncryptionData const& data)
{
if (data.getV() < 5) {
return check_owner_password_V4(user_password, owner_password, data);
} else {
return check_owner_password_V5(owner_password, data);
}
}
std::string
QPDF::recover_encryption_key_with_password(
std::string const& password, EncryptionData const& data)
{
// Disregard whether Perms is valid.
bool disregard;
return recover_encryption_key_with_password(password, data, disregard);
}
static void
compute_U_UE_value_V5(
std::string const& user_password,
std::string const& encryption_key,
QPDF::EncryptionData const& data,
std::string& U,
std::string& UE)
{
// Algorithm 3.8 from the PDF 1.7 extension level 3
char k[16];
QUtil::initializeWithRandomBytes(
reinterpret_cast<unsigned char*>(k), sizeof(k));
std::string validation_salt(k, 8);
std::string key_salt(k + 8, 8);
U = hash_V5(user_password, validation_salt, "", data) + validation_salt +
key_salt;
std::string intermediate_key = hash_V5(user_password, key_salt, "", data);
UE = process_with_aes(intermediate_key, true, encryption_key);
}
static void
compute_O_OE_value_V5(
std::string const& owner_password,
std::string const& encryption_key,
QPDF::EncryptionData const& data,
std::string const& U,
std::string& O,
std::string& OE)
{
// Algorithm 3.9 from the PDF 1.7 extension level 3
char k[16];
QUtil::initializeWithRandomBytes(
reinterpret_cast<unsigned char*>(k), sizeof(k));
std::string validation_salt(k, 8);
std::string key_salt(k + 8, 8);
O = hash_V5(owner_password, validation_salt, U, data) + validation_salt +
key_salt;
std::string intermediate_key = hash_V5(owner_password, key_salt, U, data);
OE = process_with_aes(intermediate_key, true, encryption_key);
}
void
compute_Perms_value_V5_clear(
std::string const& encryption_key,
QPDF::EncryptionData const& data,
unsigned char k[16])
{
// From algorithm 3.10 from the PDF 1.7 extension level 3
unsigned long long extended_perms =
0xffffffff00000000LL | static_cast<unsigned long long>(data.getP());
for (int i = 0; i < 8; ++i) {
k[i] = static_cast<unsigned char>(extended_perms & 0xff);
extended_perms >>= 8;
}
k[8] = data.getEncryptMetadata() ? 'T' : 'F';
k[9] = 'a';
k[10] = 'd';
k[11] = 'b';
QUtil::initializeWithRandomBytes(k + 12, 4);
}
static std::string
compute_Perms_value_V5(
std::string const& encryption_key, QPDF::EncryptionData const& data)
{
// Algorithm 3.10 from the PDF 1.7 extension level 3
unsigned char k[16];
compute_Perms_value_V5_clear(encryption_key, data, k);
return process_with_aes(
encryption_key,
true,
std::string(reinterpret_cast<char*>(k), sizeof(k)));
}
std::string
QPDF::recover_encryption_key_with_password(
std::string const& password, EncryptionData const& data, bool& perms_valid)
{
// Algorithm 3.2a from the PDF 1.7 extension level 3
// This code does not handle Unicode passwords correctly.
// Empirical evidence suggests that most viewers don't. We are
// supposed to process the input string with the SASLprep (RFC
// 4013) profile of stringprep (RFC 3454) and then convert the
// result to UTF-8.
perms_valid = false;
std::string key_password = truncate_password_V5(password);
std::string key_salt;
std::string user_data;
std::string encrypted_file_key;
if (check_owner_password_V5(key_password, data)) {
key_salt = data.getO().substr(40, 8);
user_data = data.getU().substr(0, 48);
encrypted_file_key = data.getOE().substr(0, 32);
} else if (check_user_password_V5(key_password, data)) {
key_salt = data.getU().substr(40, 8);
encrypted_file_key = data.getUE().substr(0, 32);
}
std::string intermediate_key =
hash_V5(key_password, key_salt, user_data, data);
std::string file_key =
process_with_aes(intermediate_key, false, encrypted_file_key);
// Decrypt Perms and check against expected value
std::string perms_check =
process_with_aes(file_key, false, data.getPerms(), 12);
unsigned char k[16];
compute_Perms_value_V5_clear(file_key, data, k);
perms_valid = (memcmp(perms_check.c_str(), k, 12) == 0);
return file_key;
}
QPDF::encryption_method_e
QPDF::interpretCF(
std::shared_ptr<EncryptionParameters> encp, QPDFObjectHandle cf)
{
if (cf.isName()) {
std::string filter = cf.getName();
if (encp->crypt_filters.count(filter) != 0) {
return encp->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->m->encp->encryption_initialized) {
return;
}
this->m->encp->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->m->trailer.hasKey("/Encrypt")) {
return;
}
// Go ahead and set this->m->encrypted here. That way, isEncrypted
// will return true even if there were errors reading the
// encryption dictionary.
this->m->encp->encrypted = true;
std::string id1;
QPDFObjectHandle id_obj = this->m->trailer.getKey("/ID");
if ((id_obj.isArray() && (id_obj.getArrayNItems() == 2) &&
id_obj.getArrayItem(0).isString())) {
id1 = id_obj.getArrayItem(0).getStringValue();
} else {
// Treating a missing ID as the empty string enables qpdf to
// decrypt some invalid encrypted files with no /ID that
// poppler can read but Adobe Reader can't.
warn(
qpdf_e_damaged_pdf,
"trailer",
this->m->file->getLastOffset(),
"invalid /ID in trailer dictionary");
}
QPDFObjectHandle encryption_dict = this->m->trailer.getKey("/Encrypt");
if (!encryption_dict.isDictionary()) {
throw QPDFExc(
qpdf_e_damaged_pdf,
this->m->file->getName(),
this->m->last_object_description,
this->m->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_unsupported,
this->m->file->getName(),
"encryption dictionary",
this->m->file->getLastOffset(),
"unsupported encryption filter");
}
if (!encryption_dict.getKey("/SubFilter").isNull()) {
warn(
qpdf_e_unsupported,
"encryption dictionary",
this->m->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->m->file->getName(),
"encryption dictionary",
this->m->file->getLastOffset(),
"some encryption dictionary parameters are missing "
"or the wrong type");
}
int V = encryption_dict.getKey("/V").getIntValueAsInt();
int R = encryption_dict.getKey("/R").getIntValueAsInt();
std::string O = encryption_dict.getKey("/O").getStringValue();
std::string U = encryption_dict.getKey("/U").getStringValue();
int P = static_cast<int>(encryption_dict.getKey("/P").getIntValue());
// If supporting new encryption R/V values, remember to update
// error message inside this if statement.
if (!(((R >= 2) && (R <= 6)) &&
((V == 1) || (V == 2) || (V == 4) || (V == 5)))) {
throw QPDFExc(
qpdf_e_unsupported,
this->m->file->getName(),
"encryption dictionary",
this->m->file->getLastOffset(),
"Unsupported /R or /V in encryption dictionary; R = " +
QUtil::int_to_string(R) +
" (max 6), V = " + QUtil::int_to_string(V) + " (max 5)");
}
this->m->encp->encryption_V = V;
this->m->encp->encryption_R = R;
// OE, UE, and Perms are only present if V >= 5.
std::string OE;
std::string UE;
std::string Perms;
if (V < 5) {
// These must be exactly the right number of bytes.
pad_short_parameter(O, key_bytes);
pad_short_parameter(U, key_bytes);
if (!((O.length() == key_bytes) && (U.length() == key_bytes))) {
throw QPDFExc(
qpdf_e_damaged_pdf,
this->m->file->getName(),
"encryption dictionary",
this->m->file->getLastOffset(),
"incorrect length for /O and/or /U in "
"encryption dictionary");
}
} else {
if (!(encryption_dict.getKey("/OE").isString() &&
encryption_dict.getKey("/UE").isString() &&
encryption_dict.getKey("/Perms").isString())) {
throw QPDFExc(
qpdf_e_damaged_pdf,
this->m->file->getName(),
"encryption dictionary",
this->m->file->getLastOffset(),
"some V=5 encryption dictionary parameters are "
"missing or the wrong type");
}
OE = encryption_dict.getKey("/OE").getStringValue();
UE = encryption_dict.getKey("/UE").getStringValue();
Perms = encryption_dict.getKey("/Perms").getStringValue();
// These may be longer than the minimum number of bytes.
pad_short_parameter(O, OU_key_bytes_V5);
pad_short_parameter(U, OU_key_bytes_V5);
pad_short_parameter(OE, OUE_key_bytes_V5);
pad_short_parameter(UE, OUE_key_bytes_V5);
pad_short_parameter(Perms, Perms_key_bytes_V5);
}
int Length = 0;
if (V <= 1) {
Length = 40;
} else if (V == 4) {
Length = 128;
} else if (V == 5) {
Length = 256;
} else {
if (encryption_dict.getKey("/Length").isInteger()) {
Length = encryption_dict.getKey("/Length").getIntValueAsInt();
if ((Length % 8) || (Length < 40) || (Length > 128)) {
Length = 0;
}
}
}
if (Length == 0) {
// Still no Length? Just take a guess.
Length = 128;
}
this->m->encp->encrypt_metadata = true;
if ((V >= 4) && (encryption_dict.getKey("/EncryptMetadata").isBool())) {
this->m->encp->encrypt_metadata =
encryption_dict.getKey("/EncryptMetadata").getBoolValue();
}
if ((V == 4) || (V == 5)) {
QPDFObjectHandle CF = encryption_dict.getKey("/CF");
for (auto const& filter: CF.getKeys()) {
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 if (method_name == "/AESV3") {
QTC::TC("qpdf", "QPDF_encryption CFM AESV3");
method = e_aesv3;
} else {
// Don't complain now -- maybe we won't need
// to reference this type.
method = e_unknown;
}
}
this->m->encp->crypt_filters[filter] = method;
}
}
QPDFObjectHandle StmF = encryption_dict.getKey("/StmF");
QPDFObjectHandle StrF = encryption_dict.getKey("/StrF");
QPDFObjectHandle EFF = encryption_dict.getKey("/EFF");
this->m->encp->cf_stream = interpretCF(this->m->encp, StmF);
this->m->encp->cf_string = interpretCF(this->m->encp, StrF);
if (EFF.isName()) {
// qpdf does not use this for anything other than
// informational purposes. This is intended to instruct
// conforming writers on which crypt filter should be used
// when new file attachments are added to a PDF file, but
// qpdf never generates encrypted files with non-default
// crypt filters. Prior to 10.2, I was under the mistaken
// impression that this was supposed to be used for
// decrypting attachments, but the code was wrong in a way
// that turns out not to have mattered because no writers
// were generating files the way I was imagining. Still,
// providing this information could be useful when looking
// at a file generated by something else, such as Acrobat
// when specifying that only attachments should be
// encrypted.
this->m->encp->cf_file = interpretCF(this->m->encp, EFF);
} else {
this->m->encp->cf_file = this->m->encp->cf_stream;
}
}
EncryptionData data(
V,
R,
Length / 8,
P,
O,
U,
OE,
UE,
Perms,
id1,
this->m->encp->encrypt_metadata);
if (this->m->provided_password_is_hex_key) {
// ignore passwords in file
} else {
this->m->encp->owner_password_matched = check_owner_password(
this->m->encp->user_password,
this->m->encp->provided_password,
data);
if (this->m->encp->owner_password_matched && (V < 5)) {
// password supplied was owner password; user_password has
// been initialized for V < 5
if (getTrimmedUserPassword() == this->m->encp->provided_password) {
this->m->encp->user_password_matched = true;
QTC::TC("qpdf", "QPDF_encryption user matches owner V < 5");
}
} else {
this->m->encp->user_password_matched =
check_user_password(this->m->encp->provided_password, data);
if (this->m->encp->user_password_matched) {
this->m->encp->user_password = this->m->encp->provided_password;
}
}
if (this->m->encp->user_password_matched &&
this->m->encp->owner_password_matched) {
QTC::TC("qpdf", "QPDF_encryption same password", (V < 5) ? 0 : 1);
}
if (!(this->m->encp->owner_password_matched ||
this->m->encp->user_password_matched)) {
throw QPDFExc(
qpdf_e_password,
this->m->file->getName(),
"",
0,
"invalid password");
}
}
if (this->m->provided_password_is_hex_key) {
this->m->encp->encryption_key =
QUtil::hex_decode(this->m->encp->provided_password);
} else if (V < 5) {
// For V < 5, the user password is encrypted with the owner
// password, and the user password is always used for
// computing the encryption key.
this->m->encp->encryption_key =
compute_encryption_key(this->m->encp->user_password, data);
} else {
// For V >= 5, either password can be used independently to
// compute the encryption key, and neither password can be
// used to recover the other.
bool perms_valid;
this->m->encp->encryption_key = recover_encryption_key_with_password(
this->m->encp->provided_password, data, perms_valid);
if (!perms_valid) {
warn(
qpdf_e_damaged_pdf,
"encryption dictionary",
this->m->file->getLastOffset(),
"/Perms field in encryption dictionary"
" doesn't match expected value");
}
}
}
std::string
QPDF::getKeyForObject(
std::shared_ptr<EncryptionParameters> encp,
int objid,
int generation,
bool use_aes)
{
if (!encp->encrypted) {
throw std::logic_error(
"request for encryption key in non-encrypted PDF");
}
if (!((objid == encp->cached_key_objid) &&
(generation == encp->cached_key_generation))) {
encp->cached_object_encryption_key = compute_data_key(
encp->encryption_key,
objid,
generation,
use_aes,
encp->encryption_V,
encp->encryption_R);
encp->cached_key_objid = objid;
encp->cached_key_generation = generation;
}
return encp->cached_object_encryption_key;
}
void
QPDF::decryptString(std::string& str, int objid, int generation)
{
if (objid == 0) {
return;
}
bool use_aes = false;
if (this->m->encp->encryption_V >= 4) {
switch (this->m->encp->cf_string) {
case e_none:
return;
case e_aes:
use_aes = true;
break;
case e_aesv3:
use_aes = true;
break;
case e_rc4:
break;
default:
warn(
qpdf_e_damaged_pdf,
this->m->last_object_description,
this->m->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->m->encp->cf_string = e_aes;
use_aes = true;
break;
}
}
std::string key =
getKeyForObject(this->m->encp, objid, generation, use_aes);
try {
if (use_aes) {
QTC::TC("qpdf", "QPDF_encryption aes decode string");
Pl_Buffer bufpl("decrypted string");
Pl_AES_PDF pl(
"aes decrypt string",
&bufpl,
false,
QUtil::unsigned_char_pointer(key),
key.length());
pl.write(QUtil::unsigned_char_pointer(str), str.length());
pl.finish();
auto buf = bufpl.getBufferSharedPointer();
str = std::string(
reinterpret_cast<char*>(buf->getBuffer()), buf->getSize());
} else {
QTC::TC("qpdf", "QPDF_encryption rc4 decode string");
size_t vlen = str.length();
// Using std::shared_ptr guarantees that tmp will
// be freed even if rc4.process throws an exception.
auto tmp = QUtil::make_unique_cstr(str);
RC4 rc4(QUtil::unsigned_char_pointer(key), toI(key.length()));
rc4.process(QUtil::unsigned_char_pointer(tmp.get()), vlen);
str = std::string(tmp.get(), vlen);
}
} catch (QPDFExc&) {
throw;
} catch (std::runtime_error& e) {
throw QPDFExc(
qpdf_e_damaged_pdf,
this->m->file->getName(),
this->m->last_object_description,
this->m->file->getLastOffset(),
"error decrypting string for object " +
QUtil::int_to_string(objid) + " " +
QUtil::int_to_string(generation) + ": " + e.what());
}
}
void
QPDF::decryptStream(
std::shared_ptr<EncryptionParameters> encp,
std::shared_ptr<InputSource> file,
QPDF& qpdf_for_warning,
Pipeline*& pipeline,
int objid,
int generation,
QPDFObjectHandle& stream_dict,
std::vector<std::shared_ptr<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 (encp->encryption_V >= 4) {
encryption_method_e method = e_unknown;
std::string method_source = "/StmF from /Encrypt dictionary";
if (stream_dict.getKey("/Filter").isOrHasName("/Crypt")) {
if (stream_dict.getKey("/DecodeParms").isDictionary()) {
QPDFObjectHandle decode_parms =
stream_dict.getKey("/DecodeParms");
if (decode_parms.isDictionaryOfType(
"/CryptFilterDecodeParms")) {
QTC::TC("qpdf", "QPDF_encryption stream crypt filter");
method = interpretCF(encp, decode_parms.getKey("/Name"));
method_source = "stream's Crypt decode parameters";
}
} else if (
stream_dict.getKey("/DecodeParms").isArray() &&
stream_dict.getKey("/Filter").isArray()) {
QPDFObjectHandle filter = stream_dict.getKey("/Filter");
QPDFObjectHandle decode = stream_dict.getKey("/DecodeParms");
if (filter.getArrayNItems() == decode.getArrayNItems()) {
for (int i = 0; i < filter.getArrayNItems(); ++i) {
if (filter.getArrayItem(i).isNameAndEquals("/Crypt")) {
QPDFObjectHandle crypt_params =
decode.getArrayItem(i);
if (crypt_params.isDictionary() &&
crypt_params.getKey("/Name").isName()) {
QTC::TC("qpdf", "QPDF_encrypt crypt array");
method = interpretCF(
encp, crypt_params.getKey("/Name"));
method_source = "stream's Crypt "
"decode parameters (array)";
}
}
}
}
}
}
if (method == e_unknown) {
if ((!encp->encrypt_metadata) && (type == "/Metadata")) {
QTC::TC("qpdf", "QPDF_encryption cleartext metadata");
method = e_none;
} else {
method = encp->cf_stream;
}
}
use_aes = false;
switch (method) {
case e_none:
return;
break;
case e_aes:
use_aes = true;
break;
case e_aesv3:
use_aes = true;
break;
case e_rc4:
break;
default:
// filter local to this stream.
qpdf_for_warning.warn(QPDFExc(
qpdf_e_damaged_pdf,
file->getName(),
"",
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.
encp->cf_stream = e_aes;
use_aes = true;
break;
}
}
std::string key = getKeyForObject(encp, objid, generation, use_aes);
std::shared_ptr<Pipeline> new_pipeline;
if (use_aes) {
QTC::TC("qpdf", "QPDF_encryption aes decode stream");
new_pipeline = std::make_shared<Pl_AES_PDF>(
"AES stream decryption",
pipeline,
false,
QUtil::unsigned_char_pointer(key),
key.length());
} else {
QTC::TC("qpdf", "QPDF_encryption rc4 decode stream");
new_pipeline = std::make_shared<Pl_RC4>(
"RC4 stream decryption",
pipeline,
QUtil::unsigned_char_pointer(key),
toI(key.length()));
}
pipeline = new_pipeline.get();
heap.push_back(new_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)
{
if (V >= 5) {
throw std::logic_error(
"compute_encryption_O_U called for file with V >= 5");
}
EncryptionData data(
V, R, key_len, P, "", "", "", "", "", id1, encrypt_metadata);
data.setO(compute_O_value(user_password, owner_password, data));
O = data.getO();
data.setU(compute_U_value(user_password, data));
U = data.getU();
}
void
QPDF::compute_encryption_parameters_V5(
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& encryption_key,
std::string& O,
std::string& U,
std::string& OE,
std::string& UE,
std::string& Perms)
{
EncryptionData data(
V, R, key_len, P, "", "", "", "", "", id1, encrypt_metadata);
unsigned char k[key_bytes];
QUtil::initializeWithRandomBytes(k, key_bytes);
encryption_key = std::string(reinterpret_cast<char*>(k), key_bytes);
compute_U_UE_value_V5(user_password, encryption_key, data, U, UE);
compute_O_OE_value_V5(owner_password, encryption_key, data, U, O, OE);
Perms = compute_Perms_value_V5(encryption_key, data);
data.setV5EncryptionParameters(O, OE, U, UE, Perms);
}
std::string const&
QPDF::getPaddedUserPassword() const
{
return this->m->encp->user_password;
}
std::string
QPDF::getTrimmedUserPassword() const
{
std::string result = this->m->encp->user_password;
trim_user_password(result);
return result;
}
std::string
QPDF::getEncryptionKey() const
{
return this->m->encp->encryption_key;
}
bool
QPDF::isEncrypted() const
{
return this->m->encp->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->m->encp->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 = static_cast<int>(Pkey.getIntValue());
R = Rkey.getIntValueAsInt();
V = Vkey.getIntValueAsInt();
stream_method = this->m->encp->cf_stream;
string_method = this->m->encp->cf_string;
file_method = this->m->encp->cf_file;
return true;
} else {
return false;
}
}
bool
QPDF::ownerPasswordMatched() const
{
return this->m->encp->owner_password_matched;
}
bool
QPDF::userPasswordMatched() const
{
return this->m->encp->user_password_matched;
}
static bool
is_bit_set(int P, int bit)
{
// Bits in P are numbered from 1 in the spec
return ((P & (1 << (bit - 1))) != 0);
}
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;
}