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