mirror of
https://github.com/qpdf/qpdf.git
synced 2024-11-18 02:25:08 +00:00
4f24617e1e
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. :-)
1564 lines
47 KiB
C++
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;
|
|
}
|