mirror of
https://github.com/qpdf/qpdf.git
synced 2025-01-31 02:48:31 +00:00
Support for encryption with /V=5 and /R=5 and /R=6
Read and write support is implemented for /V=5 with /R=5 as well as /R=6. /R=5 is the deprecated encryption method used by Acrobat IX. /R=6 is the encryption method used by PDF 2.0 from ISO 32000-2.
This commit is contained in:
parent
93ac1695a4
commit
e57c25814e
26
TODO
26
TODO
@ -1,32 +1,6 @@
|
||||
General
|
||||
=======
|
||||
|
||||
* See if I can support the encryption format used with /R 5 /V 5
|
||||
(AESV3), even though a qpdf-announce subscriber with an adobe.com
|
||||
email address mentioned that this is deprecated. There is also a
|
||||
new encryption format coming in a future release (PDF 2.0), which
|
||||
may be better to support. As of the qpdf 3.0 release, the
|
||||
specification was not publicly available yet.
|
||||
|
||||
AESV3 encryption is supported with PDF 1.7 extension level 3 and is
|
||||
being deprecated, but there are plenty of files out there. The
|
||||
encryption format is decribed in adobe_supplement_iso32000.pdf.
|
||||
Such a file must specify that it uses these extensions in its
|
||||
document catalog:
|
||||
|
||||
<<
|
||||
/Type /Catalog
|
||||
/Extensions <<
|
||||
/ADBE <<
|
||||
/BaseVersion /1.7
|
||||
/ExtensionLevel 3
|
||||
>>
|
||||
>>
|
||||
>>
|
||||
|
||||
Possible sha256 implementations: http://sol-biotech.com/code/sha2/,
|
||||
http://hashlib2plus.sourceforge.net/
|
||||
|
||||
* Improve the random number seed to make it more secure so that we
|
||||
have stronger random numbers, particularly when multiple files are
|
||||
generated in the same second. This code may need to be
|
||||
|
@ -224,7 +224,7 @@ class QPDF
|
||||
|
||||
// Encryption support
|
||||
|
||||
enum encryption_method_e { e_none, e_unknown, e_rc4, e_aes };
|
||||
enum encryption_method_e { e_none, e_unknown, e_rc4, e_aes, e_aesv3 };
|
||||
class EncryptionData
|
||||
{
|
||||
public:
|
||||
@ -326,7 +326,7 @@ class QPDF
|
||||
QPDF_DLL
|
||||
static std::string compute_data_key(
|
||||
std::string const& encryption_key, int objid, int generation,
|
||||
bool use_aes);
|
||||
bool use_aes, int encryption_V, int encryption_R);
|
||||
QPDF_DLL
|
||||
static std::string compute_encryption_key(
|
||||
std::string const& password, EncryptionData const& data);
|
||||
@ -337,6 +337,14 @@ class QPDF
|
||||
int V, int R, int key_len, int P, bool encrypt_metadata,
|
||||
std::string const& id1,
|
||||
std::string& O, std::string& U);
|
||||
QPDF_DLL
|
||||
static void 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);
|
||||
// Return the full user password as stored in the PDF file. If
|
||||
// you are attempting to recover the user password in a
|
||||
// user-presentable form, call getTrimmedUserPassword() instead.
|
||||
@ -345,6 +353,10 @@ class QPDF
|
||||
// Return human-readable form of user password.
|
||||
QPDF_DLL
|
||||
std::string getTrimmedUserPassword() const;
|
||||
// Return the previously computed or retrieved encryption key for
|
||||
// this file
|
||||
QPDF_DLL
|
||||
std::string getEncryptionKey() const;
|
||||
|
||||
// Linearization support
|
||||
|
||||
@ -628,6 +640,13 @@ class QPDF
|
||||
void initializeEncryption();
|
||||
std::string getKeyForObject(int objid, int generation, bool use_aes);
|
||||
void decryptString(std::string&, int objid, int generation);
|
||||
static std::string compute_encryption_key_from_password(
|
||||
std::string const& password, EncryptionData const& data);
|
||||
static std::string recover_encryption_key_with_password(
|
||||
std::string const& password, EncryptionData const& data);
|
||||
static std::string recover_encryption_key_with_password(
|
||||
std::string const& password, EncryptionData const& data,
|
||||
bool& perms_valid);
|
||||
void decryptStream(
|
||||
Pipeline*& pipeline, int objid, int generation,
|
||||
QPDFObjectHandle& stream_dict,
|
||||
@ -981,6 +1000,7 @@ class QPDF
|
||||
std::ostream* err_stream;
|
||||
bool attempt_recovery;
|
||||
int encryption_V;
|
||||
int encryption_R;
|
||||
bool encrypt_metadata;
|
||||
std::map<std::string, encryption_method_e> crypt_filters;
|
||||
encryption_method_e cf_stream;
|
||||
|
@ -223,8 +223,9 @@ class QPDFWriter
|
||||
// content normalization. Note that setting R2 encryption
|
||||
// parameters sets the PDF version to at least 1.3, setting R3
|
||||
// encryption parameters pushes the PDF version number to at least
|
||||
// 1.4, and setting R4 parameters pushes the version to at least
|
||||
// 1.5, or if AES is used, 1.6.
|
||||
// 1.4, setting R4 parameters pushes the version to at least 1.5,
|
||||
// or if AES is used, 1.6, and setting R5 or R6 parameters pushes
|
||||
// the version to at least 1.7 with extension level 3.
|
||||
QPDF_DLL
|
||||
void setR2EncryptionParameters(
|
||||
char const* user_password, char const* owner_password,
|
||||
@ -241,6 +242,21 @@ class QPDFWriter
|
||||
bool allow_accessibility, bool allow_extract,
|
||||
qpdf_r3_print_e print, qpdf_r3_modify_e modify,
|
||||
bool encrypt_metadata, bool use_aes);
|
||||
// R5 is deprecated. Do not use it for production use. Writing
|
||||
// R5 is supported by qpdf primarily to generate test files for
|
||||
// applications that may need to test R5 support.
|
||||
QPDF_DLL
|
||||
void setR5EncryptionParameters(
|
||||
char const* user_password, char const* owner_password,
|
||||
bool allow_accessibility, bool allow_extract,
|
||||
qpdf_r3_print_e print, qpdf_r3_modify_e modify,
|
||||
bool encrypt_metadata);
|
||||
QPDF_DLL
|
||||
void setR6EncryptionParameters(
|
||||
char const* user_password, char const* owner_password,
|
||||
bool allow_accessibility, bool allow_extract,
|
||||
qpdf_r3_print_e print, qpdf_r3_modify_e modify,
|
||||
bool encrypt_metadata_aes);
|
||||
|
||||
// Create linearized output. Disables qdf mode, content
|
||||
// normalization, and stream prefiltering.
|
||||
@ -302,7 +318,8 @@ class QPDFWriter
|
||||
int V, int R, int key_len, long P,
|
||||
std::string const& O, std::string const& U,
|
||||
std::string const& OE, std::string const& UE, std::string const& Perms,
|
||||
std::string const& id1, std::string const& user_password);
|
||||
std::string const& id1, std::string const& user_password,
|
||||
std::string const& encryption_key);
|
||||
void setDataKey(int objid);
|
||||
int openObject(int objid = 0);
|
||||
void closeObject(int objid);
|
||||
@ -378,6 +395,8 @@ class QPDFWriter
|
||||
bool encrypt_metadata;
|
||||
bool encrypt_use_aes;
|
||||
std::map<std::string, std::string> encryption_dictionary;
|
||||
int encryption_V;
|
||||
int encryption_R;
|
||||
|
||||
std::string id1; // for /ID key of
|
||||
std::string id2; // trailer dictionary
|
||||
|
@ -97,6 +97,7 @@ QPDF::QPDF() :
|
||||
err_stream(&std::cerr),
|
||||
attempt_recovery(true),
|
||||
encryption_V(0),
|
||||
encryption_R(0),
|
||||
encrypt_metadata(true),
|
||||
cf_stream(e_none),
|
||||
cf_string(e_none),
|
||||
|
@ -67,6 +67,8 @@ QPDFWriter::init()
|
||||
min_extension_level = 0;
|
||||
final_extension_level = 0;
|
||||
forced_extension_level = 0;
|
||||
encryption_V = 0;
|
||||
encryption_R = 0;
|
||||
encryption_dict_objid = 0;
|
||||
next_objid = 1;
|
||||
cur_stream_length_id = 0;
|
||||
@ -343,6 +345,38 @@ QPDFWriter::setR4EncryptionParameters(
|
||||
setEncryptionParameters(user_password, owner_password, 4, 4, 16, clear);
|
||||
}
|
||||
|
||||
void
|
||||
QPDFWriter::setR5EncryptionParameters(
|
||||
char const* user_password, char const* owner_password,
|
||||
bool allow_accessibility, bool allow_extract,
|
||||
qpdf_r3_print_e print, qpdf_r3_modify_e modify,
|
||||
bool encrypt_metadata)
|
||||
{
|
||||
std::set<int> clear;
|
||||
interpretR3EncryptionParameters(
|
||||
clear, user_password, owner_password,
|
||||
allow_accessibility, allow_extract, print, modify);
|
||||
this->encrypt_use_aes = true;
|
||||
this->encrypt_metadata = encrypt_metadata;
|
||||
setEncryptionParameters(user_password, owner_password, 5, 5, 32, clear);
|
||||
}
|
||||
|
||||
void
|
||||
QPDFWriter::setR6EncryptionParameters(
|
||||
char const* user_password, char const* owner_password,
|
||||
bool allow_accessibility, bool allow_extract,
|
||||
qpdf_r3_print_e print, qpdf_r3_modify_e modify,
|
||||
bool encrypt_metadata)
|
||||
{
|
||||
std::set<int> clear;
|
||||
interpretR3EncryptionParameters(
|
||||
clear, user_password, owner_password,
|
||||
allow_accessibility, allow_extract, print, modify);
|
||||
this->encrypt_use_aes = true;
|
||||
this->encrypt_metadata = encrypt_metadata;
|
||||
setEncryptionParameters(user_password, owner_password, 5, 6, 32, clear);
|
||||
}
|
||||
|
||||
void
|
||||
QPDFWriter::interpretR3EncryptionParameters(
|
||||
std::set<int>& clear,
|
||||
@ -426,6 +460,12 @@ QPDFWriter::setEncryptionParameters(
|
||||
bits_to_clear.insert(1);
|
||||
bits_to_clear.insert(2);
|
||||
|
||||
if (R > 3)
|
||||
{
|
||||
// Bit 10 is deprecated and should always be set.
|
||||
bits_to_clear.erase(10);
|
||||
}
|
||||
|
||||
int P = 0;
|
||||
// Create the complement of P, then invert.
|
||||
for (std::set<int>::iterator iter = bits_to_clear.begin();
|
||||
@ -438,11 +478,26 @@ QPDFWriter::setEncryptionParameters(
|
||||
generateID();
|
||||
std::string O;
|
||||
std::string U;
|
||||
QPDF::compute_encryption_O_U(
|
||||
user_password, owner_password, V, R, key_len, P,
|
||||
this->encrypt_metadata, this->id1, O, U);
|
||||
std::string OE;
|
||||
std::string UE;
|
||||
std::string Perms;
|
||||
std::string encryption_key;
|
||||
if (V < 5)
|
||||
{
|
||||
QPDF::compute_encryption_O_U(
|
||||
user_password, owner_password, V, R, key_len, P,
|
||||
this->encrypt_metadata, this->id1, O, U);
|
||||
}
|
||||
else
|
||||
{
|
||||
QPDF::compute_encryption_parameters_V5(
|
||||
user_password, owner_password, V, R, key_len, P,
|
||||
this->encrypt_metadata, this->id1,
|
||||
encryption_key, O, U, OE, UE, Perms);
|
||||
}
|
||||
setEncryptionParametersInternal(
|
||||
V, R, key_len, P, O, U, "", "", "", this->id1, user_password);
|
||||
V, R, key_len, P, O, U, OE, UE, Perms,
|
||||
this->id1, user_password, encryption_key);
|
||||
}
|
||||
|
||||
void
|
||||
@ -482,6 +537,19 @@ QPDFWriter::copyEncryptionParameters(QPDF& qpdf)
|
||||
this->encrypt_metadata ? 0 : 1);
|
||||
QTC::TC("qpdf", "QPDFWriter copy use_aes",
|
||||
this->encrypt_use_aes ? 0 : 1);
|
||||
std::string OE;
|
||||
std::string UE;
|
||||
std::string Perms;
|
||||
std::string encryption_key;
|
||||
if (V >= 5)
|
||||
{
|
||||
QTC::TC("qpdf", "QPDFWriter copy V5");
|
||||
OE = encrypt.getKey("/OE").getStringValue();
|
||||
UE = encrypt.getKey("/UE").getStringValue();
|
||||
Perms = encrypt.getKey("/Perms").getStringValue();
|
||||
encryption_key = qpdf.getEncryptionKey();
|
||||
}
|
||||
|
||||
setEncryptionParametersInternal(
|
||||
V,
|
||||
encrypt.getKey("/R").getIntValue(),
|
||||
@ -489,11 +557,12 @@ QPDFWriter::copyEncryptionParameters(QPDF& qpdf)
|
||||
encrypt.getKey("/P").getIntValue(),
|
||||
encrypt.getKey("/O").getStringValue(),
|
||||
encrypt.getKey("/U").getStringValue(),
|
||||
"", // XXXX OE
|
||||
"", // XXXX UE
|
||||
"", // XXXX Perms
|
||||
OE,
|
||||
UE,
|
||||
Perms,
|
||||
this->id1, // this->id1 == the other file's id1
|
||||
qpdf.getPaddedUserPassword());
|
||||
qpdf.getPaddedUserPassword(),
|
||||
encryption_key);
|
||||
}
|
||||
}
|
||||
|
||||
@ -605,10 +674,11 @@ QPDFWriter::setEncryptionParametersInternal(
|
||||
int V, int R, int key_len, long P,
|
||||
std::string const& O, std::string const& U,
|
||||
std::string const& OE, std::string const& UE, std::string const& Perms,
|
||||
std::string const& id1, std::string const& user_password)
|
||||
std::string const& id1, std::string const& user_password,
|
||||
std::string const& encryption_key)
|
||||
{
|
||||
// XXXX OE, UE, Perms, V=5
|
||||
|
||||
this->encryption_V = V;
|
||||
this->encryption_R = R;
|
||||
encryption_dictionary["/Filter"] = "/Standard";
|
||||
encryption_dictionary["/V"] = QUtil::int_to_string(V);
|
||||
encryption_dictionary["/Length"] = QUtil::int_to_string(key_len * 8);
|
||||
@ -618,9 +688,15 @@ QPDFWriter::setEncryptionParametersInternal(
|
||||
encryption_dictionary["/U"] = QPDF_String(U).unparse(true);
|
||||
if (V >= 5)
|
||||
{
|
||||
setMinimumPDFVersion("1.4");
|
||||
encryption_dictionary["/OE"] = QPDF_String(OE).unparse(true);
|
||||
encryption_dictionary["/UE"] = QPDF_String(UE).unparse(true);
|
||||
encryption_dictionary["/Perms"] = QPDF_String(Perms).unparse(true);
|
||||
}
|
||||
if (R >= 5)
|
||||
if (R >= 6)
|
||||
{
|
||||
setMinimumPDFVersion("1.7", 8);
|
||||
}
|
||||
else if (R == 5)
|
||||
{
|
||||
setMinimumPDFVersion("1.7", 3);
|
||||
}
|
||||
@ -641,14 +717,16 @@ QPDFWriter::setEncryptionParametersInternal(
|
||||
{
|
||||
encryption_dictionary["/EncryptMetadata"] = "false";
|
||||
}
|
||||
if (V == 4)
|
||||
if ((V == 4) || (V == 5))
|
||||
{
|
||||
// The spec says the value for the crypt filter key can be
|
||||
// anything, and xpdf seems to agree. However, Adobe Reader
|
||||
// won't open our files unless we use /StdCF.
|
||||
encryption_dictionary["/StmF"] = "/StdCF";
|
||||
encryption_dictionary["/StrF"] = "/StdCF";
|
||||
std::string method = (this->encrypt_use_aes ? "/AESV2" : "/V2");
|
||||
std::string method = (this->encrypt_use_aes
|
||||
? ((V < 5) ? "/AESV2" : "/AESV3")
|
||||
: "/V2");
|
||||
encryption_dictionary["/CF"] =
|
||||
"<< /StdCF << /AuthEvent /DocOpen /CFM " + method + " >> >>";
|
||||
}
|
||||
@ -656,15 +734,23 @@ QPDFWriter::setEncryptionParametersInternal(
|
||||
this->encrypted = true;
|
||||
QPDF::EncryptionData encryption_data(
|
||||
V, R, key_len, P, O, U, OE, UE, Perms, id1, this->encrypt_metadata);
|
||||
this->encryption_key = QPDF::compute_encryption_key(
|
||||
user_password, encryption_data);
|
||||
if (V < 5)
|
||||
{
|
||||
this->encryption_key = QPDF::compute_encryption_key(
|
||||
user_password, encryption_data);
|
||||
}
|
||||
else
|
||||
{
|
||||
this->encryption_key = encryption_key;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
QPDFWriter::setDataKey(int objid)
|
||||
{
|
||||
this->cur_data_key = QPDF::compute_data_key(
|
||||
this->encryption_key, objid, 0, this->encrypt_use_aes);
|
||||
this->encryption_key, objid, 0,
|
||||
this->encrypt_use_aes, this->encryption_V, this->encryption_R);
|
||||
}
|
||||
|
||||
int
|
||||
|
@ -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,9 +24,15 @@ 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
|
||||
{
|
||||
@ -120,7 +127,7 @@ QPDF::EncryptionData::setV5EncryptionParameters(
|
||||
}
|
||||
|
||||
static void
|
||||
pad_or_truncate_password(std::string const& password, char k1[key_bytes])
|
||||
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;
|
||||
@ -159,13 +166,19 @@ QPDF::trim_user_password(std::string& user_password)
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
@ -199,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);
|
||||
@ -230,12 +374,38 @@ QPDF::compute_data_key(std::string const& encryption_key,
|
||||
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);
|
||||
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();
|
||||
@ -261,8 +431,13 @@ 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())
|
||||
{
|
||||
@ -270,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.getR() >= 3) ? 50 : 0));
|
||||
memcpy(key, digest, O_key_bytes);
|
||||
memcpy(key, digest, OU_key_bytes_V4);
|
||||
}
|
||||
|
||||
static std::string
|
||||
@ -283,11 +458,11 @@ 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.getLengthBytes(),
|
||||
(data.getR() >= 3) ? 20 : 1, false);
|
||||
@ -303,7 +478,7 @@ 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.getLengthBytes(), 1, false);
|
||||
return std::string(udata, key_bytes);
|
||||
@ -319,7 +494,7 @@ 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);
|
||||
pad_or_truncate_password_V4("").c_str(), key_bytes);
|
||||
md5.encodeDataIncrementally(data.getId1().c_str(),
|
||||
(int)data.getId1().length());
|
||||
MD5::Digest digest;
|
||||
@ -350,38 +525,212 @@ 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.getR() >= 3) ? sizeof(MD5::Digest) : key_bytes);
|
||||
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_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, (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);
|
||||
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)
|
||||
{
|
||||
// Algorithm 3.7 from the PDF 1.7 Reference Manual
|
||||
|
||||
unsigned char key[O_key_bytes];
|
||||
compute_O_rc4_key(user_password, owner_password, data, key);
|
||||
unsigned char O_data[key_bytes];
|
||||
memcpy(O_data, (unsigned char*) data.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);
|
||||
bool result = false;
|
||||
if (check_user_password(new_user_password, data))
|
||||
if (data.getV() < 5)
|
||||
{
|
||||
result = true;
|
||||
user_password = new_user_password;
|
||||
return check_owner_password_V4(user_password, owner_password, data);
|
||||
}
|
||||
return result;
|
||||
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
|
||||
@ -487,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(),
|
||||
@ -524,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();
|
||||
@ -549,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
|
||||
@ -574,13 +969,14 @@ QPDF::initializeEncryption()
|
||||
this->cf_file = this->cf_stream;
|
||||
}
|
||||
}
|
||||
EncryptionData data(V, R, Length / 8, P, O, U, "", "", "",
|
||||
|
||||
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))
|
||||
{
|
||||
@ -592,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
|
||||
@ -608,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;
|
||||
}
|
||||
@ -624,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)
|
||||
{
|
||||
@ -635,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;
|
||||
|
||||
@ -710,7 +1134,7 @@ 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";
|
||||
@ -747,7 +1171,7 @@ QPDF::decryptStream(Pipeline*& pipeline, int objid, int generation,
|
||||
if (crypt_params.isDictionary() &&
|
||||
crypt_params.getKey("/Name").isName())
|
||||
{
|
||||
// XXX QTC::TC("qpdf", "QPDF_encrypt crypt array");
|
||||
QTC::TC("qpdf", "QPDF_encrypt crypt array");
|
||||
method = interpretCF(
|
||||
crypt_params.getKey("/Name"));
|
||||
method_source = "stream's Crypt "
|
||||
@ -790,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;
|
||||
|
||||
@ -831,6 +1259,11 @@ 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)
|
||||
{
|
||||
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));
|
||||
@ -839,6 +1272,26 @@ QPDF::compute_encryption_O_U(
|
||||
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&
|
||||
QPDF::getPaddedUserPassword() const
|
||||
{
|
||||
@ -853,6 +1306,12 @@ QPDF::getTrimmedUserPassword() const
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string
|
||||
QPDF::getEncryptionKey() const
|
||||
{
|
||||
return this->encryption_key;
|
||||
}
|
||||
|
||||
bool
|
||||
QPDF::isEncrypted() const
|
||||
{
|
||||
|
67
qpdf/qpdf.cc
67
qpdf/qpdf.cc
@ -97,7 +97,7 @@ Note that -- terminates parsing of encryption flags.\n\
|
||||
Either or both of the user password and the owner password may be\n\
|
||||
empty strings.\n\
|
||||
\n\
|
||||
key-length may be 40 or 128\n\
|
||||
key-length may be 40, 128, or 256\n\
|
||||
\n\
|
||||
Additional flags are dependent upon key length.\n\
|
||||
\n\
|
||||
@ -117,6 +117,11 @@ Additional flags are dependent upon key length.\n\
|
||||
--cleartext-metadata prevents encryption of metadata\n\
|
||||
--use-aes=[yn] indicates whether to use AES encryption\n\
|
||||
--force-V4 forces use of V=4 encryption handler\n\
|
||||
\n\
|
||||
If 256, options are the same as 128 with these exceptions:\n\
|
||||
--force-V4 this option is not available with 256-bit keys\n\
|
||||
--use-aes this option is always on with 256-bit keys\n\
|
||||
--force-R5 forces use of deprecated R=5 encryption\n\
|
||||
\n\
|
||||
print-opt may be:\n\
|
||||
\n\
|
||||
@ -283,6 +288,9 @@ static std::string show_encryption_method(QPDF::encryption_method_e method)
|
||||
case QPDF::e_aes:
|
||||
result = "AESv2";
|
||||
break;
|
||||
case QPDF::e_aesv3:
|
||||
result = "AESv3";
|
||||
break;
|
||||
// no default so gcc will warn for missing case
|
||||
}
|
||||
return result;
|
||||
@ -485,7 +493,8 @@ parse_encrypt_options(
|
||||
bool& r2_print, bool& r2_modify, bool& r2_extract, bool& r2_annotate,
|
||||
bool& r3_accessibility, bool& r3_extract,
|
||||
qpdf_r3_print_e& r3_print, qpdf_r3_modify_e& r3_modify,
|
||||
bool& force_V4, bool& cleartext_metadata, bool& use_aes)
|
||||
bool& force_V4, bool& cleartext_metadata, bool& use_aes,
|
||||
bool& force_R5)
|
||||
{
|
||||
if (cur_arg + 3 >= argc)
|
||||
{
|
||||
@ -502,9 +511,14 @@ parse_encrypt_options(
|
||||
{
|
||||
keylen = 128;
|
||||
}
|
||||
else if (len_str == "256")
|
||||
{
|
||||
keylen = 256;
|
||||
use_aes = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
usage("encryption key length must be 40 or 128");
|
||||
usage("encryption key length must be 40, 128, or 256");
|
||||
}
|
||||
while (1)
|
||||
{
|
||||
@ -736,15 +750,30 @@ parse_encrypt_options(
|
||||
{
|
||||
usage("--force-V4 does not take a parameter");
|
||||
}
|
||||
if (keylen == 40)
|
||||
if (keylen != 128)
|
||||
{
|
||||
usage("--force-V4 is invalid for 40-bit keys");
|
||||
usage("--force-V4 is invalid only for 128-bit keys");
|
||||
}
|
||||
else
|
||||
{
|
||||
force_V4 = true;
|
||||
}
|
||||
}
|
||||
else if (strcmp(arg, "force-R5") == 0)
|
||||
{
|
||||
if (parameter)
|
||||
{
|
||||
usage("--force-R5 does not take a parameter");
|
||||
}
|
||||
if (keylen != 256)
|
||||
{
|
||||
usage("--force-R5 is invalid only for 256-bit keys");
|
||||
}
|
||||
else
|
||||
{
|
||||
force_R5 = true;
|
||||
}
|
||||
}
|
||||
else if (strcmp(arg, "use-aes") == 0)
|
||||
{
|
||||
if (parameter == 0)
|
||||
@ -765,10 +794,16 @@ parse_encrypt_options(
|
||||
{
|
||||
usage("invalid -use-aes parameter");
|
||||
}
|
||||
if (keylen == 40)
|
||||
if ((keylen == 40) && result)
|
||||
{
|
||||
usage("use-aes is invalid for 40-bit keys");
|
||||
}
|
||||
else if ((keylen == 256) && (! result))
|
||||
{
|
||||
// qpdf would happily create files encrypted with RC4
|
||||
// using /V=5, but Adobe reader can't read them.
|
||||
usage("use-aes can't be disabled with 256-bit keys");
|
||||
}
|
||||
else
|
||||
{
|
||||
use_aes = result;
|
||||
@ -921,6 +956,7 @@ int main(int argc, char* argv[])
|
||||
qpdf_r3_print_e r3_print = qpdf_r3p_full;
|
||||
qpdf_r3_modify_e r3_modify = qpdf_r3m_all;
|
||||
bool force_V4 = false;
|
||||
bool force_R5 = false;
|
||||
bool cleartext_metadata = false;
|
||||
bool use_aes = false;
|
||||
|
||||
@ -1004,7 +1040,7 @@ int main(int argc, char* argv[])
|
||||
user_password, owner_password, keylen,
|
||||
r2_print, r2_modify, r2_extract, r2_annotate,
|
||||
r3_accessibility, r3_extract, r3_print, r3_modify,
|
||||
force_V4, cleartext_metadata, use_aes);
|
||||
force_V4, cleartext_metadata, use_aes, force_R5);
|
||||
encrypt = true;
|
||||
decrypt = false;
|
||||
copy_encryption = false;
|
||||
@ -1612,6 +1648,23 @@ int main(int argc, char* argv[])
|
||||
r3_accessibility, r3_extract, r3_print, r3_modify);
|
||||
}
|
||||
}
|
||||
else if (keylen == 256)
|
||||
{
|
||||
if (force_R5)
|
||||
{
|
||||
w.setR5EncryptionParameters(
|
||||
user_password.c_str(), owner_password.c_str(),
|
||||
r3_accessibility, r3_extract, r3_print, r3_modify,
|
||||
!cleartext_metadata);
|
||||
}
|
||||
else
|
||||
{
|
||||
w.setR6EncryptionParameters(
|
||||
user_password.c_str(), owner_password.c_str(),
|
||||
r3_accessibility, r3_extract, r3_print, r3_modify,
|
||||
!cleartext_metadata);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::logic_error("bad encryption keylen");
|
||||
|
@ -243,6 +243,7 @@ QPDFWriter extra header text add newline 0
|
||||
QPDF bogus 0 offset 0
|
||||
QPDF global offset 0
|
||||
QPDFWriter make stream key direct 0
|
||||
QPDFWriter copy V5 0
|
||||
QPDFWriter increasing extension level 0
|
||||
QPDFWriter make Extensions direct 0
|
||||
QPDFWriter make ADBE direct 1
|
||||
@ -253,3 +254,5 @@ QPDFWriter remove existing Extensions 0
|
||||
QPDFWriter skip Extensions 0
|
||||
QPDFWriter preserve ADBE 0
|
||||
QPDF_encryption skip 0x28 0
|
||||
QPDF_encrypt crypt array 0
|
||||
QPDF_encryption CFM AESV3 0
|
||||
|
@ -1250,6 +1250,10 @@ $td->notify("--- Encryption Tests ---");
|
||||
# resulting files were saved and manually checked with Acrobat 5.0 to
|
||||
# ensure that the security settings were as intended.
|
||||
|
||||
# The enc-XI-file.pdf files were treated the same way but with Acrobat
|
||||
# XI instead of Acrobat 5.0. They were used to create test files with
|
||||
# newer encryption formats.
|
||||
|
||||
# Values: basename, password, encryption flags, /P Encrypt key,
|
||||
# extract-for-accessibility, extract-for-any-purpose,
|
||||
# print-low-res, print-high-res, modify-assembly, modify-forms,
|
||||
@ -1293,9 +1297,25 @@ my @encrypted_files =
|
||||
'', -4,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
['long-password', 'asdf asdf asdf asdf asdf asdf qwer'],
|
||||
['long-password', 'asdf asdf asdf asdf asdf asdf qw']);
|
||||
['long-password', 'asdf asdf asdf asdf asdf asdf qw'],
|
||||
['XI-base', ''],
|
||||
['XI-R6,V5,O=master', '',
|
||||
'-extract=n -print=none -modify=assembly', -2368,
|
||||
1, 0, 0, 0, 1, 0, 0, 0, 0],
|
||||
['XI-R6,V5,O=master', 'master',
|
||||
'-extract=n -print=none -modify=assembly', -2368,
|
||||
1, 0, 0, 0, 1, 0, 0, 0, 0],
|
||||
['XI-R6,V5,U=view,O=master', 'view',
|
||||
'-print=low', -2052,
|
||||
1, 1, 1, 0, 1, 1, 1, 1, 1],
|
||||
['XI-R6,V5,U=view,O=master', 'master',
|
||||
'-print=low', -2052,
|
||||
1, 1, 1, 0, 1, 1, 1, 1, 1],
|
||||
['XI-long-password', 'qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnm'],
|
||||
['XI-long-password', 'qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcv'],
|
||||
);
|
||||
|
||||
$n_tests += 3 + (2 * (@encrypted_files)) + (6 * (@encrypted_files - 3)) + 9;
|
||||
$n_tests += 5 + (2 * (@encrypted_files)) + (6 * (@encrypted_files - 6)) + 9;
|
||||
|
||||
$td->runtest("encrypted file",
|
||||
{$td->COMMAND => "test_driver 2 U25A0.pdf"},
|
||||
@ -1312,6 +1332,19 @@ $td->runtest("recheck encrypted file",
|
||||
$td->EXIT_STATUS => 0},
|
||||
$td->NORMALIZE_NEWLINES);
|
||||
|
||||
# Test that long passwords that are one character too short fail. We
|
||||
# test the truncation cases in the loop below by using passwords
|
||||
# longer than the supported length.
|
||||
$td->runtest("significant password characters (V < 5)",
|
||||
{$td->COMMAND => "qpdf --check enc-long-password.pdf" .
|
||||
" --password='asdf asdf asdf asdf asdf asdf q'"},
|
||||
{$td->REGEXP => ".*invalid password.*", $td->EXIT_STATUS => 2});
|
||||
$td->runtest("significant password characters (V = 5)",
|
||||
{$td->COMMAND => "qpdf --check enc-XI-long-password.pdf" .
|
||||
" --password=qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxc"},
|
||||
{$td->REGEXP => ".*invalid password.*", $td->EXIT_STATUS => 2});
|
||||
|
||||
my $enc_base = undef;
|
||||
foreach my $d (@encrypted_files)
|
||||
{
|
||||
my ($file, $pass, $xeflags, $P,
|
||||
@ -1330,17 +1363,26 @@ foreach my $d (@encrypted_files)
|
||||
"modify annotations: " . &$f($modifyannot) . "\n" .
|
||||
"modify other: " . &$f($modifyother) . "\n" .
|
||||
"modify anything: " . &$f($modifyall) . "\n";
|
||||
if ($file =~ m/XI-/)
|
||||
{
|
||||
$enc_details .=
|
||||
"stream encryption method: AESv3\n" .
|
||||
"string encryption method: AESv3\n" .
|
||||
"file encryption method: AESv3\n";
|
||||
}
|
||||
|
||||
# Test writing to stdout
|
||||
$td->runtest("decrypt $file",
|
||||
{$td->COMMAND =>
|
||||
"qpdf --static-id -qdf --no-original-object-ids" .
|
||||
"qpdf --static-id -qdf --object-streams=disable" .
|
||||
" --no-original-object-ids" .
|
||||
" --password=\"$pass\" enc-$file.pdf -" .
|
||||
" > $file.enc"},
|
||||
{$td->STRING => "",
|
||||
$td->EXIT_STATUS => 0});
|
||||
if ($file eq 'base')
|
||||
if ($file =~ m/base$/)
|
||||
{
|
||||
$enc_base = $file;
|
||||
$td->runtest("check ID",
|
||||
{$td->COMMAND => "perl check-ID.pl $file.enc"},
|
||||
{$td->STRING => "ID okay\n",
|
||||
@ -1350,20 +1392,27 @@ foreach my $d (@encrypted_files)
|
||||
else
|
||||
{
|
||||
$td->runtest("check against base",
|
||||
{$td->COMMAND => "./diff-encrypted base.enc $file.enc"},
|
||||
{$td->COMMAND =>
|
||||
"./diff-encrypted $enc_base.enc $file.enc"},
|
||||
{$td->STRING => "okay\n",
|
||||
$td->EXIT_STATUS => 0},
|
||||
$td->NORMALIZE_NEWLINES);
|
||||
}
|
||||
if ($file =~ m/^R(\d),V(\d)(?:,U=(\w+))?(?:,O=(\w+))?$/)
|
||||
if ($file =~ m/^(?:XI-)?R(\d),V(\d)(?:,U=(\w+))?(?:,O=(\w+))?$/)
|
||||
{
|
||||
my $R = $1;
|
||||
my $V = $2;
|
||||
my $upass = $3 || "";
|
||||
my $opass = $4 || "";
|
||||
my $bits = (($V == 2) ? 128 : 40);
|
||||
my $bits = (($V == 5) ? 256 : ($V == 2) ? 128 : 40);
|
||||
|
||||
my $eflags = "-encrypt \"$upass\" \"$opass\" $bits $xeflags --";
|
||||
if (($pass ne $upass) && ($V >= 5))
|
||||
{
|
||||
# V >= 5 can no longer recover user password with owner
|
||||
# password.
|
||||
$upass = "";
|
||||
}
|
||||
$td->runtest("encrypt $file",
|
||||
{$td->COMMAND =>
|
||||
"qpdf --static-id --no-original-object-ids -qdf" .
|
||||
@ -1488,7 +1537,7 @@ $td->runtest("check linearization",
|
||||
$td->NORMALIZE_NEWLINES);
|
||||
|
||||
# Test AES encryption in various ways.
|
||||
$n_tests += 14;
|
||||
$n_tests += 18;
|
||||
$td->runtest("encrypt with AES",
|
||||
{$td->COMMAND => "qpdf --encrypt '' '' 128 --use-aes=y --" .
|
||||
" enc-base.pdf a.pdf"},
|
||||
@ -1548,6 +1597,24 @@ $td->runtest("make sure there is no xref stream",
|
||||
{$td->COMMAND => "grep /ObjStm b.pdf | wc -l"},
|
||||
{$td->REGEXP => "\\s*0\\s*", $td->EXIT_STATUS => 0},
|
||||
$td->NORMALIZE_NEWLINES);
|
||||
$td->runtest("encrypt with V=5,R=5",
|
||||
{$td->COMMAND =>
|
||||
"qpdf --encrypt user owner 256 --force-R5 -- " .
|
||||
"minimal.pdf a.pdf"},
|
||||
{$td->STRING => "", $td->EXIT_STATUS => 0});
|
||||
$td->runtest("check encryption",
|
||||
{$td->COMMAND => "qpdf --check a.pdf --password=owner"},
|
||||
{$td->FILE => "V5R5.out", $td->EXIT_STATUS => 0},
|
||||
$td->NORMALIZE_NEWLINES);
|
||||
$td->runtest("encrypt with V=5,R=6",
|
||||
{$td->COMMAND =>
|
||||
"qpdf --encrypt user owner 256 -- " .
|
||||
"minimal.pdf a.pdf"},
|
||||
{$td->STRING => "", $td->EXIT_STATUS => 0});
|
||||
$td->runtest("check encryption",
|
||||
{$td->COMMAND => "qpdf --check a.pdf --password=user"},
|
||||
{$td->FILE => "V5R6.out", $td->EXIT_STATUS => 0},
|
||||
$td->NORMALIZE_NEWLINES);
|
||||
|
||||
# Look at some actual V4 files
|
||||
$n_tests += 14;
|
||||
@ -1629,6 +1696,36 @@ $td->runtest("compare qdf",
|
||||
{$td->STRING => "okay\n", $td->EXIT_STATUS => 0},
|
||||
$td->NORMALIZE_NEWLINES);
|
||||
|
||||
# Files with attachments
|
||||
my @attachments = (
|
||||
'enc-XI-attachments-base.pdf',
|
||||
'enc-XI-R6,V5,U=attachment,encrypted-attachments.pdf',
|
||||
'enc-XI-R6,V5,U=view,attachments,cleartext-metadata.pdf');
|
||||
$n_tests += 4 * @attachments;
|
||||
foreach my $f (@attachments)
|
||||
{
|
||||
my $pass = '';
|
||||
my $tpass = '';
|
||||
if ($f =~ m/U=([^,]+)/)
|
||||
{
|
||||
$pass = "--password=$1";
|
||||
$tpass = $1;
|
||||
}
|
||||
$td->runtest("decrypt $f",
|
||||
{$td->COMMAND => "qpdf --decrypt $pass $f a.pdf"},
|
||||
{$td->STRING => "", $td->EXIT_STATUS => 0});
|
||||
$td->runtest("extract attachments",
|
||||
{$td->COMMAND => "test_driver 35 a.pdf"},
|
||||
{$td->FILE => "attachments.out", $td->EXIT_STATUS => 0},
|
||||
$td->NORMALIZE_NEWLINES);
|
||||
$td->runtest("copy $f",
|
||||
{$td->COMMAND => "qpdf $pass $f a.pdf"},
|
||||
{$td->STRING => "", $td->EXIT_STATUS => 0});
|
||||
$td->runtest("extract attachments",
|
||||
{$td->COMMAND => "test_driver 35 a.pdf $tpass"},
|
||||
{$td->FILE => "attachments.out", $td->EXIT_STATUS => 0},
|
||||
$td->NORMALIZE_NEWLINES);
|
||||
}
|
||||
|
||||
show_ntests();
|
||||
# ----------
|
||||
|
20
qpdf/qtest/qpdf/V5R5.out
Normal file
20
qpdf/qtest/qpdf/V5R5.out
Normal file
@ -0,0 +1,20 @@
|
||||
checking a.pdf
|
||||
PDF Version: 1.7 extension level 3
|
||||
R = 5
|
||||
P = -4
|
||||
User password =
|
||||
extract for accessibility: allowed
|
||||
extract for any purpose: allowed
|
||||
print low resolution: allowed
|
||||
print high resolution: allowed
|
||||
modify document assembly: allowed
|
||||
modify forms: allowed
|
||||
modify annotations: allowed
|
||||
modify other: allowed
|
||||
modify anything: allowed
|
||||
stream encryption method: AESv3
|
||||
string encryption method: AESv3
|
||||
file encryption method: AESv3
|
||||
File is not linearized
|
||||
No syntax or stream encoding errors found; the file may still contain
|
||||
errors that qpdf cannot detect
|
20
qpdf/qtest/qpdf/V5R6.out
Normal file
20
qpdf/qtest/qpdf/V5R6.out
Normal file
@ -0,0 +1,20 @@
|
||||
checking a.pdf
|
||||
PDF Version: 1.7 extension level 8
|
||||
R = 6
|
||||
P = -4
|
||||
User password = user
|
||||
extract for accessibility: allowed
|
||||
extract for any purpose: allowed
|
||||
print low resolution: allowed
|
||||
print high resolution: allowed
|
||||
modify document assembly: allowed
|
||||
modify forms: allowed
|
||||
modify annotations: allowed
|
||||
modify other: allowed
|
||||
modify anything: allowed
|
||||
stream encryption method: AESv3
|
||||
string encryption method: AESv3
|
||||
file encryption method: AESv3
|
||||
File is not linearized
|
||||
No syntax or stream encoding errors found; the file may still contain
|
||||
errors that qpdf cannot detect
|
6
qpdf/qtest/qpdf/attachments.out
Normal file
6
qpdf/qtest/qpdf/attachments.out
Normal file
@ -0,0 +1,6 @@
|
||||
attachment1.txt:
|
||||
This is the first attachment.
|
||||
--END--
|
||||
attachment2.png:
|
||||
.PNG........IHDR...1 (2620 bytes)--END--
|
||||
test 35 done
|
@ -1,5 +1,5 @@
|
||||
#!/bin/sh
|
||||
lines=$(expr + $(diff $1 $2 | egrep '^[<>]' | egrep -v 'Date' | wc -l))
|
||||
lines=$(expr + $(diff $1 $2 | egrep '^[<>]' | egrep -v '(Date|InstanceID)' | wc -l))
|
||||
if [ "$lines" = "0" ]; then
|
||||
echo okay
|
||||
else
|
||||
|
BIN
qpdf/qtest/qpdf/enc-XI-R6,V5,O=master.pdf
Normal file
BIN
qpdf/qtest/qpdf/enc-XI-R6,V5,O=master.pdf
Normal file
Binary file not shown.
Binary file not shown.
BIN
qpdf/qtest/qpdf/enc-XI-R6,V5,U=view,O=master.pdf
Normal file
BIN
qpdf/qtest/qpdf/enc-XI-R6,V5,U=view,O=master.pdf
Normal file
Binary file not shown.
Binary file not shown.
BIN
qpdf/qtest/qpdf/enc-XI-attachments-base.pdf
Normal file
BIN
qpdf/qtest/qpdf/enc-XI-attachments-base.pdf
Normal file
Binary file not shown.
BIN
qpdf/qtest/qpdf/enc-XI-base.pdf
Normal file
BIN
qpdf/qtest/qpdf/enc-XI-base.pdf
Normal file
Binary file not shown.
BIN
qpdf/qtest/qpdf/enc-XI-long-password.pdf
Normal file
BIN
qpdf/qtest/qpdf/enc-XI-long-password.pdf
Normal file
Binary file not shown.
@ -112,7 +112,12 @@ void runtest(int n, char const* filename1, char const* arg2)
|
||||
{
|
||||
pdf.setAttemptRecovery(false);
|
||||
}
|
||||
if (n % 2 == 0)
|
||||
if ((n == 35) && (arg2 != 0))
|
||||
{
|
||||
// arg2 is password
|
||||
pdf.processFile(filename1, arg2);
|
||||
}
|
||||
else if (n % 2 == 0)
|
||||
{
|
||||
if (n % 4 == 0)
|
||||
{
|
||||
@ -1150,6 +1155,65 @@ void runtest(int n, char const* filename1, char const* arg2)
|
||||
<< "extension level: " << pdf.getExtensionLevel() << std::endl
|
||||
<< pdf.getRoot().getKey("/Extensions").unparse() << std::endl;
|
||||
}
|
||||
else if (n == 35)
|
||||
{
|
||||
// Extract attachments
|
||||
|
||||
std::map<std::string, PointerHolder<Buffer> > attachments;
|
||||
QPDFObjectHandle root = pdf.getRoot();
|
||||
QPDFObjectHandle names = root.getKey("/Names");
|
||||
QPDFObjectHandle embeddedFiles = names.getKey("/EmbeddedFiles");
|
||||
names = embeddedFiles.getKey("/Names");
|
||||
for (int i = 0; i < names.getArrayNItems(); ++i)
|
||||
{
|
||||
QPDFObjectHandle item = names.getArrayItem(i);
|
||||
if (item.isDictionary() &&
|
||||
item.getKey("/Type").isName() &&
|
||||
(item.getKey("/Type").getName() == "/Filespec") &&
|
||||
item.getKey("/EF").isDictionary() &&
|
||||
item.getKey("/EF").getKey("/F").isStream())
|
||||
{
|
||||
std::string filename = item.getKey("/F").getStringValue();
|
||||
QPDFObjectHandle stream = item.getKey("/EF").getKey("/F");
|
||||
attachments[filename] = stream.getStreamData();
|
||||
}
|
||||
}
|
||||
for (std::map<std::string, PointerHolder<Buffer> >::iterator iter =
|
||||
attachments.begin(); iter != attachments.end(); ++iter)
|
||||
{
|
||||
std::string const& filename = (*iter).first;
|
||||
std::string data = std::string(
|
||||
(char const*)(*iter).second->getBuffer(),
|
||||
(*iter).second->getSize());
|
||||
bool is_binary = false;
|
||||
for (size_t i = 0; i < data.size(); ++i)
|
||||
{
|
||||
if (data[i] < 0)
|
||||
{
|
||||
is_binary = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (is_binary)
|
||||
{
|
||||
std::string t;
|
||||
for (size_t i = 0; i < std::min(data.size(), (size_t)20); ++i)
|
||||
{
|
||||
if ((data[i] >= 32) && (data[i] <= 126))
|
||||
{
|
||||
t += data[i];
|
||||
}
|
||||
else
|
||||
{
|
||||
t += ".";
|
||||
}
|
||||
}
|
||||
t += " (" + QUtil::int_to_string(data.size()) + " bytes)";
|
||||
data = t;
|
||||
}
|
||||
std::cout << filename << ":\n" << data << "--END--\n";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error(std::string("invalid test ") +
|
||||
|
Loading…
x
Reference in New Issue
Block a user