summaryrefslogtreecommitdiff
path: root/libqpdf/QPDF_encryption.cc
diff options
context:
space:
mode:
Diffstat (limited to 'libqpdf/QPDF_encryption.cc')
-rw-r--r--libqpdf/QPDF_encryption.cc801
1 files changed, 689 insertions, 112 deletions
diff --git a/libqpdf/QPDF_encryption.cc b/libqpdf/QPDF_encryption.cc
index c73a47b..60d54b7 100644
--- a/libqpdf/QPDF_encryption.cc
+++ b/libqpdf/QPDF_encryption.cc
@@ -10,6 +10,7 @@
#include <qpdf/Pl_RC4.hh>
#include <qpdf/Pl_AES_PDF.hh>
#include <qpdf/Pl_Buffer.hh>
+#include <qpdf/Pl_SHA2.hh>
#include <qpdf/RC4.hh>
#include <qpdf/MD5.hh>
@@ -23,11 +24,110 @@ static unsigned char const padding_string[] = {
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;
+// 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
-pad_or_truncate_password(std::string const& password, char k1[key_bytes])
+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])
{
int password_bytes = std::min(key_bytes, (unsigned int)password.length());
int pad_bytes = key_bytes - password_bytes;
@@ -48,25 +148,37 @@ QPDF::trim_user_password(std::string& user_password)
return;
}
- char const* p = 0;
- while ((p = strchr(cstr, '\x28')) != 0)
+ char const* p1 = cstr;
+ char const* p2 = 0;
+ while ((p2 = strchr(p1, '\x28')) != 0)
{
- if (memcmp(p, padding_string, len - (p - cstr)) == 0)
+ if (memcmp(p2, padding_string, len - (p2 - cstr)) == 0)
{
- user_password = user_password.substr(0, p - cstr);
+ user_password = user_password.substr(0, p2 - cstr);
return;
}
+ else
+ {
+ QTC::TC("qpdf", "QPDF_encryption skip 0x28");
+ p1 = p2 + 1;
+ }
}
}
static std::string
-pad_or_truncate_password(std::string const& password)
+pad_or_truncate_password_V4(std::string const& password)
{
char k1[key_bytes];
- pad_or_truncate_password(password, k1);
+ 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((size_t)127, password.length()));
+}
+
static void
iterate_md5_digest(MD5& md5, MD5::Digest& digest, int iterations)
{
@@ -100,15 +212,146 @@ iterate_rc4(unsigned char* data, int data_len,
delete [] key;
}
+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,
+ (unsigned char const*)key.c_str(),
+ (unsigned int)key.length());
+ if (iv)
+ {
+ aes.setIV(iv, iv_length);
+ }
+ else
+ {
+ aes.useZeroIV();
+ }
+ aes.disablePadding();
+ for (unsigned int i = 0; i < repetitions; ++i)
+ {
+ aes.write((unsigned char*)data.c_str(), data.length());
+ }
+ aes.finish();
+ PointerHolder<Buffer> bufp = buffer.getBuffer();
+ if (outlength == 0)
+ {
+ outlength = bufp->getSize();
+ }
+ else
+ {
+ outlength = std::min(outlength, bufp->getSize());
+ }
+ return std::string((char const*)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((unsigned char*)password.c_str(), password.length());
+ hash.write((unsigned char*)salt.c_str(), salt.length());
+ hash.write((unsigned char*)udata.c_str(), 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,
+ (unsigned char*)K.substr(16, 16).c_str(), 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 += (unsigned char)E[i];
+ }
+ E_mod_3 %= 3;
+ int next_hash = ((E_mod_3 == 0) ? 256 :
+ (E_mod_3 == 1) ? 384 :
+ 512);
+ Pl_SHA2 hash(next_hash);
+ hash.write((unsigned char*)E.c_str(), E.length());
+ hash.finish();
+ K = hash.getRawDigest();
+
+ if (round_number >= 64)
+ {
+ unsigned int ch = (unsigned int)((unsigned char) *(E.rbegin()));
+
+ if (ch <= (unsigned int)(round_number - 32))
+ {
+ done = true;
+ }
+ }
+ }
+ result = K.substr(0, 32);
+ }
+
+ return result;
+}
+
std::string
QPDF::compute_data_key(std::string const& encryption_key,
- int objid, int generation,
- bool use_aes)
+ 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 += (char) (objid & 0xff);
result += (char) ((objid >> 8) & 0xff);
@@ -132,36 +375,69 @@ 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(password).c_str(), key_bytes);
- md5.encodeDataIncrementally(data.O.c_str(), key_bytes);
+ pad_or_truncate_password_V4(password).c_str(), key_bytes);
+ md5.encodeDataIncrementally(data.getO().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);
+ int P = data.getP();
+ pbytes[0] = (char) (P & 0xff);
+ pbytes[1] = (char) ((P >> 8) & 0xff);
+ pbytes[2] = (char) ((P >> 16) & 0xff);
+ pbytes[3] = (char) ((P >> 24) & 0xff);
md5.encodeDataIncrementally(pbytes, 4);
- md5.encodeDataIncrementally(data.id1.c_str(), (int)data.id1.length());
- if ((data.R >= 4) && (! data.encrypt_metadata))
+ md5.encodeDataIncrementally(data.getId1().c_str(),
+ (int)data.getId1().length());
+ if ((data.getR() >= 4) && (! data.getEncryptMetadata()))
{
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);
+ iterate_md5_digest(md5, digest, ((data.getR() >= 3) ? 50 : 0));
+ return std::string((char*)digest, data.getLengthBytes());
}
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])
+ 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())
{
@@ -169,10 +445,10 @@ compute_O_rc4_key(std::string const& user_password,
}
MD5 md5;
md5.encodeDataIncrementally(
- pad_or_truncate_password(password).c_str(), key_bytes);
+ pad_or_truncate_password_V4(password).c_str(), key_bytes);
MD5::Digest digest;
- iterate_md5_digest(md5, digest, ((data.R >= 3) ? 50 : 0));
- memcpy(key, digest, O_key_bytes);
+ iterate_md5_digest(md5, digest, ((data.getR() >= 3) ? 50 : 0));
+ memcpy(key, digest, OU_key_bytes_V4);
}
static std::string
@@ -182,13 +458,14 @@ compute_O_value(std::string const& user_password,
{
// Algorithm 3.3 from the PDF 1.7 Reference Manual
- unsigned char O_key[O_key_bytes];
+ 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(user_password, upass);
+ pad_or_truncate_password_V4(user_password, upass);
iterate_rc4((unsigned char*) upass, key_bytes,
- O_key, data.Length_bytes, (data.R >= 3) ? 20 : 1, false);
+ O_key, data.getLengthBytes(),
+ (data.getR() >= 3) ? 20 : 1, false);
return std::string(upass, key_bytes);
}
@@ -201,9 +478,9 @@ compute_U_value_R2(std::string const& user_password,
std::string k1 = QPDF::compute_encryption_key(user_password, data);
char udata[key_bytes];
- pad_or_truncate_password("", udata);
+ pad_or_truncate_password_V4("", udata);
iterate_rc4((unsigned char*) udata, key_bytes,
- (unsigned char*)k1.c_str(), data.Length_bytes, 1, false);
+ (unsigned char*)k1.c_str(), data.getLengthBytes(), 1, false);
return std::string(udata, key_bytes);
}
@@ -217,12 +494,13 @@ compute_U_value_R3(std::string const& user_password,
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());
+ pad_or_truncate_password_V4("").c_str(), key_bytes);
+ md5.encodeDataIncrementally(data.getId1().c_str(),
+ (int)data.getId1().length());
MD5::Digest digest;
md5.digest(digest);
iterate_rc4(digest, sizeof(MD5::Digest),
- (unsigned char*) k1.c_str(), data.Length_bytes, 20, false);
+ (unsigned char*) k1.c_str(), 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
@@ -238,7 +516,7 @@ static std::string
compute_U_value(std::string const& user_password,
QPDF::EncryptionData const& data)
{
- if (data.R >= 3)
+ if (data.getR() >= 3)
{
return compute_U_value_R3(user_password, data);
}
@@ -247,40 +525,214 @@ compute_U_value(std::string const& user_password,
}
static bool
-check_user_password(std::string const& user_password,
- QPDF::EncryptionData const& data)
+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);
- int to_compare = ((data.R >= 3) ? sizeof(MD5::Digest) : key_bytes);
- return (memcmp(data.U.c_str(), u_value.c_str(), to_compare) == 0);
+ int 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_owner_password(std::string& user_password,
- std::string const& owner_password,
- QPDF::EncryptionData const& data)
+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[O_key_bytes];
+ 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, (unsigned char*) data.O.c_str(), key_bytes);
- iterate_rc4(O_data, key_bytes, key, data.Length_bytes,
- (data.R >= 3) ? 20 : 1, true);
+ memcpy(O_data, (unsigned char*) data.getO().c_str(), key_bytes);
+ iterate_rc4(O_data, key_bytes, key, data.getLengthBytes(),
+ (data.getR() >= 3) ? 20 : 1, true);
std::string new_user_password =
- std::string((char*)O_data, key_bytes);
+ 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;
+ 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((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((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 | data.getP();
+ for (int i = 0; i < 8; ++i)
+ {
+ k[i] = (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((char const*) 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(QPDFObjectHandle cf)
{
@@ -384,29 +836,70 @@ QPDF::initializeEncryption()
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))))
+ // 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->file->getName(),
"encryption dictionary", this->file->getLastOffset(),
- "Unsupported /R or /V in encryption dictionary");
+ "Unsupported /R or /V in encryption dictionary; R = " +
+ QUtil::int_to_string(R) + " (max 6), V = " +
+ QUtil::int_to_string(V) + " (max 5)");
}
this->encryption_V = V;
+ this->encryption_R = R;
- if (! ((O.length() == key_bytes) && (U.length() == key_bytes)))
+ // OE, UE, and Perms are only present if V >= 5.
+ std::string OE;
+ std::string UE;
+ std::string Perms;
+
+ if (V < 5)
{
- throw QPDFExc(qpdf_e_damaged_pdf, this->file->getName(),
- "encryption dictionary", this->file->getLastOffset(),
- "incorrect length for /O and/or /P in "
- "encryption dictionary");
+ 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 /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->file->getName(),
+ "encryption dictionary", this->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();
+
+ if ((O.length() < OU_key_bytes_V5) ||
+ (U.length() < OU_key_bytes_V5) ||
+ (OE.length() < OUE_key_bytes_V5) ||
+ (UE.length() < OUE_key_bytes_V5) ||
+ (Perms.length() < Perms_key_bytes_V5))
+ {
+ throw QPDFExc(qpdf_e_damaged_pdf, this->file->getName(),
+ "encryption dictionary", this->file->getLastOffset(),
+ "incorrect length for some of"
+ " /O, /U, /OE, /UE, or /Perms 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))
+ if ((Length % 8) || (Length < 40) || (Length > 256))
{
throw QPDFExc(qpdf_e_damaged_pdf, this->file->getName(),
"encryption dictionary", this->file->getLastOffset(),
@@ -421,7 +914,7 @@ QPDF::initializeEncryption()
encryption_dict.getKey("/EncryptMetadata").getBoolValue();
}
- if (V == 4)
+ if ((V == 4) || (V == 5))
{
QPDFObjectHandle CF = encryption_dict.getKey("/CF");
std::set<std::string> keys = CF.getKeys();
@@ -446,6 +939,11 @@ QPDF::initializeEncryption()
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
@@ -470,35 +968,15 @@ QPDF::initializeEncryption()
{
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);
+
+ EncryptionData data(V, R, Length / 8, P, O, U, OE, UE, Perms,
+ 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
+ // been initialized for V < 5
}
else if (check_user_password(this->provided_password, data))
{
@@ -510,7 +988,30 @@ QPDF::initializeEncryption()
"", 0, "invalid password");
}
- this->encryption_key = compute_encryption_key(this->user_password, data);
+ 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->encryption_key = compute_encryption_key(
+ this->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->encryption_key = recover_encryption_key_with_password(
+ this->provided_password, data, perms_valid);
+ if (! perms_valid)
+ {
+ warn(QPDFExc(qpdf_e_damaged_pdf, this->file->getName(),
+ "encryption dictionary", this->file->getLastOffset(),
+ "/Perms field in encryption dictionary"
+ " doesn't match expected value"));
+ }
+ }
}
std::string
@@ -526,7 +1027,8 @@ QPDF::getKeyForObject(int objid, int generation, bool use_aes)
(generation == this->cached_key_generation)))
{
this->cached_object_encryption_key =
- compute_data_key(this->encryption_key, objid, generation, use_aes);
+ compute_data_key(this->encryption_key, objid, generation,
+ use_aes, this->encryption_V, this->encryption_R);
this->cached_key_objid = objid;
this->cached_key_generation = generation;
}
@@ -542,7 +1044,7 @@ QPDF::decryptString(std::string& str, int objid, int generation)
return;
}
bool use_aes = false;
- if (this->encryption_V == 4)
+ if (this->encryption_V >= 4)
{
switch (this->cf_string)
{
@@ -553,6 +1055,10 @@ QPDF::decryptString(std::string& str, int objid, int generation)
use_aes = true;
break;
+ case e_aesv3:
+ use_aes = true;
+ break;
+
case e_rc4:
break;
@@ -576,10 +1082,10 @@ QPDF::decryptString(std::string& str, int objid, int generation)
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());
+ (unsigned char const*)key.c_str(),
+ (unsigned int)key.length());
pl.write((unsigned char*)str.c_str(), str.length());
pl.finish();
PointerHolder<Buffer> buf = bufpl.getBuffer();
@@ -628,23 +1134,53 @@ QPDF::decryptStream(Pipeline*& pipeline, int objid, int generation,
return;
}
bool use_aes = false;
- if (this->encryption_V == 4)
+ 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 (stream_dict.getKey("/Filter").isOrHasName("/Crypt"))
+ {
+ if (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";
+ }
+ }
+ 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).isName() &&
+ (filter.getArrayItem(i).getName() == "/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(
+ crypt_params.getKey("/Name"));
+ method_source = "stream's Crypt "
+ "decode parameters (array)";
+ }
+ }
+ }
+ }
+ }
}
if (method == e_unknown)
@@ -656,12 +1192,15 @@ QPDF::decryptStream(Pipeline*& pipeline, int objid, int generation,
}
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;
+ if (this->attachment_streams.count(
+ ObjGen(objid, generation)) > 0)
+ {
+ method = this->cf_file;
+ }
+ else
+ {
+ method = this->cf_stream;
+ }
}
}
use_aes = false;
@@ -675,6 +1214,10 @@ QPDF::decryptStream(Pipeline*& pipeline, int objid, int generation,
use_aes = true;
break;
+ case e_aesv3:
+ use_aes = true;
+ break;
+
case e_rc4:
break;
@@ -696,15 +1239,16 @@ QPDF::decryptStream(Pipeline*& pipeline, int objid, int generation,
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());
+ false, (unsigned char*) key.c_str(),
+ (unsigned int) key.length());
}
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());
+ (unsigned char*) key.c_str(),
+ (unsigned int) key.length());
}
heap.push_back(pipeline);
}
@@ -715,10 +1259,37 @@ QPDF::compute_encryption_O_U(
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);
+ 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((char const*)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&
@@ -735,6 +1306,12 @@ QPDF::getTrimmedUserPassword() const
return result;
}
+std::string
+QPDF::getEncryptionKey() const
+{
+ return this->encryption_key;
+}
+
bool
QPDF::isEncrypted() const
{