mirror of
https://github.com/RGBCube/serenity
synced 2025-05-14 16:24:57 +00:00
LibPDF: Add support for AESV2 encryption
This commit is contained in:
parent
67d8c8badb
commit
5998072f15
3 changed files with 163 additions and 20 deletions
|
@ -24,6 +24,8 @@
|
||||||
A(C) \
|
A(C) \
|
||||||
A(CA) \
|
A(CA) \
|
||||||
A(CCITTFaxDecode) \
|
A(CCITTFaxDecode) \
|
||||||
|
A(CF) \
|
||||||
|
A(CFM) \
|
||||||
A(CalRGB) \
|
A(CalRGB) \
|
||||||
A(CIDSystemInfo) \
|
A(CIDSystemInfo) \
|
||||||
A(CIDToGIDMap) \
|
A(CIDToGIDMap) \
|
||||||
|
@ -130,6 +132,8 @@
|
||||||
A(SA) \
|
A(SA) \
|
||||||
A(SM) \
|
A(SM) \
|
||||||
A(SMask) \
|
A(SMask) \
|
||||||
|
A(StmF) \
|
||||||
|
A(StrF) \
|
||||||
A(Subject) \
|
A(Subject) \
|
||||||
A(Subtype) \
|
A(Subtype) \
|
||||||
A(Supplement) \
|
A(Supplement) \
|
||||||
|
|
|
@ -5,6 +5,9 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <AK/ByteBuffer.h>
|
#include <AK/ByteBuffer.h>
|
||||||
|
#include <AK/Debug.h>
|
||||||
|
#include <AK/Random.h>
|
||||||
|
#include <LibCrypto/Cipher/AES.h>
|
||||||
#include <LibCrypto/Hash/MD5.h>
|
#include <LibCrypto/Hash/MD5.h>
|
||||||
#include <LibPDF/CommonNames.h>
|
#include <LibPDF/CommonNames.h>
|
||||||
#include <LibPDF/Document.h>
|
#include <LibPDF/Document.h>
|
||||||
|
@ -57,6 +60,58 @@ PDFErrorOr<NonnullRefPtr<SecurityHandler>> SecurityHandler::create(Document* doc
|
||||||
TODO();
|
TODO();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct CryptFilter {
|
||||||
|
CryptFilterMethod method { CryptFilterMethod::None };
|
||||||
|
int length_in_bits { 0 };
|
||||||
|
};
|
||||||
|
|
||||||
|
static PDFErrorOr<CryptFilter> parse_v4_crypt(Document* document, NonnullRefPtr<DictObject> encryption_dict, DeprecatedString filter)
|
||||||
|
{
|
||||||
|
// See 3.5 Encryption, Table 3.18 "Entries common to all encryption dictionaries" for StmF and StrF,
|
||||||
|
// and 3.5.4 Crypt Filters in the 1.7 spec, in particular Table 3.22 "Entries common to all crypt filter dictionaries".
|
||||||
|
|
||||||
|
if (filter == "Identity")
|
||||||
|
return CryptFilter {};
|
||||||
|
|
||||||
|
// "Every crypt filter used in the document must have an entry in this dictionary"
|
||||||
|
if (!encryption_dict->contains(CommonNames::CF))
|
||||||
|
return Error(Error::Type::Parse, "Missing CF key in encryption dict for v4");
|
||||||
|
|
||||||
|
auto crypt_filter_dicts = TRY(encryption_dict->get_dict(document, CommonNames::CF));
|
||||||
|
if (!crypt_filter_dicts->contains(filter))
|
||||||
|
return Error(Error::Type::Parse, "Missing key in CF dict for v4");
|
||||||
|
|
||||||
|
auto crypt_filter_dict = TRY(crypt_filter_dicts->get_dict(document, filter));
|
||||||
|
|
||||||
|
// "Default value: None"
|
||||||
|
if (!crypt_filter_dict->contains(CommonNames::CFM))
|
||||||
|
return CryptFilter {};
|
||||||
|
auto crypt_filter_method = TRY(crypt_filter_dict->get_name(document, CommonNames::CFM))->name();
|
||||||
|
if (crypt_filter_method == "None")
|
||||||
|
return CryptFilter {};
|
||||||
|
|
||||||
|
// Table 3.22 in the 1.7 spec says this is optional but doesn't give a default value.
|
||||||
|
// But the 2.0 spec (ISO 32000 2020) says it's required.
|
||||||
|
// The 2.0 spec also says "The standard security handler expresses the Length entry in bytes" (!).
|
||||||
|
if (!crypt_filter_dict->contains(CommonNames::Length))
|
||||||
|
return Error(Error::Type::Parse, "crypt filter /Length missing");
|
||||||
|
auto length_in_bits = crypt_filter_dict->get_value(CommonNames::Length).get<int>() * 8;
|
||||||
|
|
||||||
|
// NOTE: /CFM's /AuthEvent should be ignored for /StmF, /StrF.
|
||||||
|
|
||||||
|
if (crypt_filter_method == "V2")
|
||||||
|
return CryptFilter { CryptFilterMethod::V2, length_in_bits };
|
||||||
|
|
||||||
|
if (crypt_filter_method == "AESV2") {
|
||||||
|
// "the AES algorithm in Cipher Block Chaining (CBC) mode with a 16-byte block size"
|
||||||
|
if (length_in_bits != 128)
|
||||||
|
return Error(Error::Type::Parse, "Unexpected bit size for AESV2");
|
||||||
|
return CryptFilter { CryptFilterMethod::AESV2, length_in_bits };
|
||||||
|
}
|
||||||
|
|
||||||
|
return Error(Error::Type::Parse, "Unknown crypt filter method");
|
||||||
|
}
|
||||||
|
|
||||||
PDFErrorOr<NonnullRefPtr<StandardSecurityHandler>> StandardSecurityHandler::create(Document* document, NonnullRefPtr<DictObject> encryption_dict)
|
PDFErrorOr<NonnullRefPtr<StandardSecurityHandler>> StandardSecurityHandler::create(Document* document, NonnullRefPtr<DictObject> encryption_dict)
|
||||||
{
|
{
|
||||||
auto revision = encryption_dict->get_value(CommonNames::R).get<int>();
|
auto revision = encryption_dict->get_value(CommonNames::R).get<int>();
|
||||||
|
@ -66,25 +121,46 @@ PDFErrorOr<NonnullRefPtr<StandardSecurityHandler>> StandardSecurityHandler::crea
|
||||||
|
|
||||||
// V, number: [...] 1 "Algorithm 1 Encryption of data using the RC4 or AES algorithms" in 7.6.2,
|
// V, number: [...] 1 "Algorithm 1 Encryption of data using the RC4 or AES algorithms" in 7.6.2,
|
||||||
// "General Encryption Algorithm," with an encryption key length of 40 bits, see below [...]
|
// "General Encryption Algorithm," with an encryption key length of 40 bits, see below [...]
|
||||||
// Lenght, integer: (Optional; PDF 1.4; only if V is 2 or 3) The length of the encryption key, in bits.
|
// Length, integer: (Optional; PDF 1.4; only if V is 2 or 3) The length of the encryption key, in bits.
|
||||||
// The value shall be a multiple of 8, in the range 40 to 128. Default value: 40.
|
// The value shall be a multiple of 8, in the range 40 to 128. Default value: 40.
|
||||||
int length_in_bits;
|
|
||||||
auto v = encryption_dict->get_value(CommonNames::V).get<int>();
|
auto v = encryption_dict->get_value(CommonNames::V).get<int>();
|
||||||
if (encryption_dict->contains(CommonNames::Length))
|
|
||||||
|
auto method = CryptFilterMethod::V2;
|
||||||
|
size_t length_in_bits = 40;
|
||||||
|
|
||||||
|
if (v == 4) {
|
||||||
|
// "Default value: Identity"
|
||||||
|
DeprecatedString stream_filter = "Identity";
|
||||||
|
if (encryption_dict->contains(CommonNames::StmF))
|
||||||
|
stream_filter = TRY(encryption_dict->get_name(document, CommonNames::StmF))->name();
|
||||||
|
|
||||||
|
DeprecatedString string_filter = "Identity";
|
||||||
|
if (encryption_dict->contains(CommonNames::StrF))
|
||||||
|
string_filter = TRY(encryption_dict->get_name(document, CommonNames::StrF))->name();
|
||||||
|
|
||||||
|
if (stream_filter != string_filter)
|
||||||
|
return Error(Error::Type::Parse, "Can't handle StmF and StrF being different");
|
||||||
|
|
||||||
|
auto crypt_filter = TRY(parse_v4_crypt(document, encryption_dict, stream_filter));
|
||||||
|
method = crypt_filter.method;
|
||||||
|
length_in_bits = crypt_filter.length_in_bits;
|
||||||
|
} else if (encryption_dict->contains(CommonNames::Length))
|
||||||
length_in_bits = encryption_dict->get_value(CommonNames::Length).get<int>();
|
length_in_bits = encryption_dict->get_value(CommonNames::Length).get<int>();
|
||||||
else if (v == 1)
|
else if (v != 1)
|
||||||
length_in_bits = 40;
|
|
||||||
else
|
|
||||||
return Error(Error::Type::Parse, "Can't determine length of encryption key");
|
return Error(Error::Type::Parse, "Can't determine length of encryption key");
|
||||||
|
|
||||||
auto length = length_in_bits / 8;
|
auto length = length_in_bits / 8;
|
||||||
|
|
||||||
|
dbgln_if(PDF_DEBUG, "encryption v{}, method {}, length {}", v, (int)method, length);
|
||||||
|
|
||||||
bool encrypt_metadata = true;
|
bool encrypt_metadata = true;
|
||||||
if (encryption_dict->contains(CommonNames::EncryptMetadata))
|
if (encryption_dict->contains(CommonNames::EncryptMetadata))
|
||||||
encryption_dict->get_value(CommonNames::EncryptMetadata).get<bool>();
|
encryption_dict->get_value(CommonNames::EncryptMetadata).get<bool>();
|
||||||
return adopt_ref(*new StandardSecurityHandler(document, revision, o, u, p, encrypt_metadata, length));
|
|
||||||
|
return adopt_ref(*new StandardSecurityHandler(document, revision, o, u, p, encrypt_metadata, length, method));
|
||||||
}
|
}
|
||||||
|
|
||||||
StandardSecurityHandler::StandardSecurityHandler(Document* document, size_t revision, DeprecatedString const& o_entry, DeprecatedString const& u_entry, u32 flags, bool encrypt_metadata, size_t length)
|
StandardSecurityHandler::StandardSecurityHandler(Document* document, size_t revision, DeprecatedString const& o_entry, DeprecatedString const& u_entry, u32 flags, bool encrypt_metadata, size_t length, CryptFilterMethod method)
|
||||||
: m_document(document)
|
: m_document(document)
|
||||||
, m_revision(revision)
|
, m_revision(revision)
|
||||||
, m_o_entry(o_entry)
|
, m_o_entry(o_entry)
|
||||||
|
@ -92,6 +168,7 @@ StandardSecurityHandler::StandardSecurityHandler(Document* document, size_t revi
|
||||||
, m_flags(flags)
|
, m_flags(flags)
|
||||||
, m_encrypt_metadata(encrypt_metadata)
|
, m_encrypt_metadata(encrypt_metadata)
|
||||||
, m_length(length)
|
, m_length(length)
|
||||||
|
, m_method(method)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -292,15 +369,16 @@ ByteBuffer StandardSecurityHandler::compute_encryption_key(ByteBuffer password_s
|
||||||
return encryption_key;
|
return encryption_key;
|
||||||
}
|
}
|
||||||
|
|
||||||
void StandardSecurityHandler::encrypt(NonnullRefPtr<Object> object, Reference reference) const
|
void StandardSecurityHandler::crypt(NonnullRefPtr<Object> object, Reference reference, Crypto::Cipher::Intent direction) const
|
||||||
{
|
{
|
||||||
// 7.6.2 General Encryption Algorithm
|
// 7.6.2 General Encryption Algorithm
|
||||||
// Algorithm 1: Encryption of data using the RC3 or AES algorithms
|
// Algorithm 1: Encryption of data using the RC3 or AES algorithms
|
||||||
|
|
||||||
// FIXME: Support AES
|
|
||||||
|
|
||||||
VERIFY(m_encryption_key.has_value());
|
VERIFY(m_encryption_key.has_value());
|
||||||
|
|
||||||
|
if (m_method == CryptFilterMethod::None)
|
||||||
|
return;
|
||||||
|
|
||||||
// a) Obtain the object number and generation number from the object identifier of
|
// a) Obtain the object number and generation number from the object identifier of
|
||||||
// the string or stream to be encrypted. If the string is a direct object, use
|
// the string or stream to be encrypted. If the string is a direct object, use
|
||||||
// the identifier of the indirect object containing it.
|
// the identifier of the indirect object containing it.
|
||||||
|
@ -309,20 +387,20 @@ void StandardSecurityHandler::encrypt(NonnullRefPtr<Object> object, Reference re
|
||||||
// object number.
|
// object number.
|
||||||
|
|
||||||
// b) For all strings and streams with crypt filter specifier; treating the object
|
// b) For all strings and streams with crypt filter specifier; treating the object
|
||||||
// number as binary integers, extends the origin n-byte encryption key to n + 5
|
// number as binary integers, extend the original n-byte encryption key to n + 5
|
||||||
// bytes by appending the low-order 3 bytes of the object number and the low-order
|
// bytes by appending the low-order 3 bytes of the object number and the low-order
|
||||||
// 2 bytes of the generation number in that order, low-order byte first. ...
|
// 2 bytes of the generation number in that order, low-order byte first. ...
|
||||||
|
|
||||||
auto encryption_key = m_encryption_key.value();
|
auto encryption_key = m_encryption_key.value();
|
||||||
ReadonlyBytes bytes;
|
ReadonlyBytes bytes;
|
||||||
Function<void(ByteBuffer const&)> assign;
|
Function<void(ReadonlyBytes)> assign;
|
||||||
|
|
||||||
if (object->is<StreamObject>()) {
|
if (object->is<StreamObject>()) {
|
||||||
auto stream = object->cast<StreamObject>();
|
auto stream = object->cast<StreamObject>();
|
||||||
bytes = stream->bytes();
|
bytes = stream->bytes();
|
||||||
|
|
||||||
assign = [&object](ByteBuffer const& buffer) {
|
assign = [&object](ReadonlyBytes bytes) {
|
||||||
object->cast<StreamObject>()->buffer() = buffer;
|
object->cast<StreamObject>()->buffer() = MUST(ByteBuffer::copy(bytes));
|
||||||
};
|
};
|
||||||
|
|
||||||
if (stream->dict()->contains(CommonNames::Filter)) {
|
if (stream->dict()->contains(CommonNames::Filter)) {
|
||||||
|
@ -333,8 +411,8 @@ void StandardSecurityHandler::encrypt(NonnullRefPtr<Object> object, Reference re
|
||||||
} else if (object->is<StringObject>()) {
|
} else if (object->is<StringObject>()) {
|
||||||
auto string = object->cast<StringObject>();
|
auto string = object->cast<StringObject>();
|
||||||
bytes = string->string().bytes();
|
bytes = string->string().bytes();
|
||||||
assign = [&object](ByteBuffer const& buffer) {
|
assign = [&object](ReadonlyBytes bytes) {
|
||||||
object->cast<StringObject>()->set_string(DeprecatedString(buffer.bytes()));
|
object->cast<StringObject>()->set_string(DeprecatedString(bytes));
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
VERIFY_NOT_REACHED();
|
VERIFY_NOT_REACHED();
|
||||||
|
@ -349,6 +427,13 @@ void StandardSecurityHandler::encrypt(NonnullRefPtr<Object> object, Reference re
|
||||||
encryption_key.append(generation & 0xff);
|
encryption_key.append(generation & 0xff);
|
||||||
encryption_key.append((generation >> 8) & 0xff);
|
encryption_key.append((generation >> 8) & 0xff);
|
||||||
|
|
||||||
|
if (m_method == CryptFilterMethod::AESV2) {
|
||||||
|
encryption_key.append('s');
|
||||||
|
encryption_key.append('A');
|
||||||
|
encryption_key.append('l');
|
||||||
|
encryption_key.append('T');
|
||||||
|
}
|
||||||
|
|
||||||
// c) Initialize the MD5 hash function and pass the result of step (b) as input to this
|
// c) Initialize the MD5 hash function and pass the result of step (b) as input to this
|
||||||
// function.
|
// function.
|
||||||
Crypto::Hash::MD5 md5;
|
Crypto::Hash::MD5 md5;
|
||||||
|
@ -362,16 +447,57 @@ void StandardSecurityHandler::encrypt(NonnullRefPtr<Object> object, Reference re
|
||||||
if (key.size() > min(encryption_key.size(), 16))
|
if (key.size() > min(encryption_key.size(), 16))
|
||||||
key.resize(encryption_key.size());
|
key.resize(encryption_key.size());
|
||||||
|
|
||||||
|
if (m_method == CryptFilterMethod::AESV2) {
|
||||||
|
auto cipher = Crypto::Cipher::AESCipher::CBCMode(key, m_length * 8, direction, Crypto::Cipher::PaddingMode::CMS);
|
||||||
|
|
||||||
|
// "The block size parameter is 16 bytes, and the initialization vector is a 16-byte random number
|
||||||
|
// that is stored as the first 16 bytes of the encrypted stream or string."
|
||||||
|
static_assert(Crypto::Cipher::AESCipher::block_size() == 16);
|
||||||
|
if (direction == Crypto::Cipher::Intent::Encryption) {
|
||||||
|
auto encrypted = MUST(cipher.create_aligned_buffer(bytes.size()));
|
||||||
|
auto encrypted_span = encrypted.bytes();
|
||||||
|
|
||||||
|
auto iv = MUST(ByteBuffer::create_uninitialized(Crypto::Cipher::AESCipher::block_size()));
|
||||||
|
fill_with_random(iv);
|
||||||
|
|
||||||
|
cipher.encrypt(bytes, encrypted_span, iv);
|
||||||
|
|
||||||
|
ByteBuffer output;
|
||||||
|
output.append(iv);
|
||||||
|
output.append(encrypted_span);
|
||||||
|
assign(output);
|
||||||
|
} else {
|
||||||
|
VERIFY(direction == Crypto::Cipher::Intent::Decryption);
|
||||||
|
|
||||||
|
auto iv = bytes.trim(16);
|
||||||
|
bytes = bytes.slice(16);
|
||||||
|
|
||||||
|
auto decrypted = MUST(cipher.create_aligned_buffer(bytes.size()));
|
||||||
|
auto decrypted_span = decrypted.bytes();
|
||||||
|
cipher.decrypt(bytes, decrypted_span, iv);
|
||||||
|
|
||||||
|
assign(decrypted_span);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// RC4 is symmetric, so decryption is the same as encryption.
|
||||||
|
VERIFY(m_method == CryptFilterMethod::V2);
|
||||||
RC4 rc4(key);
|
RC4 rc4(key);
|
||||||
auto output = rc4.encrypt(bytes);
|
auto output = rc4.encrypt(bytes);
|
||||||
|
|
||||||
assign(output);
|
assign(output);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void StandardSecurityHandler::encrypt(NonnullRefPtr<Object> object, Reference reference) const
|
||||||
|
{
|
||||||
|
crypt(object, reference, Crypto::Cipher::Intent::Encryption);
|
||||||
|
}
|
||||||
|
|
||||||
void StandardSecurityHandler::decrypt(NonnullRefPtr<Object> object, Reference reference) const
|
void StandardSecurityHandler::decrypt(NonnullRefPtr<Object> object, Reference reference) const
|
||||||
{
|
{
|
||||||
// AES and RC4 are both symmetric, so decryption is the same as encryption
|
crypt(object, reference, Crypto::Cipher::Intent::Decryption);
|
||||||
encrypt(object, reference);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static constexpr auto identity_permutation = iota_array<size_t, 256>(0);
|
static constexpr auto identity_permutation = iota_array<size_t, 256>(0);
|
||||||
|
|
|
@ -9,8 +9,18 @@
|
||||||
#include <AK/Span.h>
|
#include <AK/Span.h>
|
||||||
#include <LibPDF/ObjectDerivatives.h>
|
#include <LibPDF/ObjectDerivatives.h>
|
||||||
|
|
||||||
|
namespace Crypto::Cipher {
|
||||||
|
enum class Intent;
|
||||||
|
}
|
||||||
|
|
||||||
namespace PDF {
|
namespace PDF {
|
||||||
|
|
||||||
|
enum class CryptFilterMethod {
|
||||||
|
None,
|
||||||
|
V2, // RC4
|
||||||
|
AESV2,
|
||||||
|
};
|
||||||
|
|
||||||
class SecurityHandler : public RefCounted<SecurityHandler> {
|
class SecurityHandler : public RefCounted<SecurityHandler> {
|
||||||
public:
|
public:
|
||||||
static PDFErrorOr<NonnullRefPtr<SecurityHandler>> create(Document*, NonnullRefPtr<DictObject> encryption_dict);
|
static PDFErrorOr<NonnullRefPtr<SecurityHandler>> create(Document*, NonnullRefPtr<DictObject> encryption_dict);
|
||||||
|
@ -28,7 +38,7 @@ class StandardSecurityHandler : public SecurityHandler {
|
||||||
public:
|
public:
|
||||||
static PDFErrorOr<NonnullRefPtr<StandardSecurityHandler>> create(Document*, NonnullRefPtr<DictObject> encryption_dict);
|
static PDFErrorOr<NonnullRefPtr<StandardSecurityHandler>> create(Document*, NonnullRefPtr<DictObject> encryption_dict);
|
||||||
|
|
||||||
StandardSecurityHandler(Document*, size_t revision, DeprecatedString const& o_entry, DeprecatedString const& u_entry, u32 flags, bool encrypt_metadata, size_t length);
|
StandardSecurityHandler(Document*, size_t revision, DeprecatedString const& o_entry, DeprecatedString const& u_entry, u32 flags, bool encrypt_metadata, size_t length, CryptFilterMethod method);
|
||||||
|
|
||||||
~StandardSecurityHandler() override = default;
|
~StandardSecurityHandler() override = default;
|
||||||
|
|
||||||
|
@ -41,6 +51,8 @@ protected:
|
||||||
void decrypt(NonnullRefPtr<Object>, Reference reference) const override;
|
void decrypt(NonnullRefPtr<Object>, Reference reference) const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void crypt(NonnullRefPtr<Object>, Reference reference, Crypto::Cipher::Intent) const;
|
||||||
|
|
||||||
ByteBuffer compute_user_password_value_v2(ByteBuffer password_string);
|
ByteBuffer compute_user_password_value_v2(ByteBuffer password_string);
|
||||||
ByteBuffer compute_user_password_value_v3_and_newer(ByteBuffer password_string);
|
ByteBuffer compute_user_password_value_v3_and_newer(ByteBuffer password_string);
|
||||||
|
|
||||||
|
@ -54,6 +66,7 @@ private:
|
||||||
u32 m_flags;
|
u32 m_flags;
|
||||||
bool m_encrypt_metadata;
|
bool m_encrypt_metadata;
|
||||||
size_t m_length;
|
size_t m_length;
|
||||||
|
CryptFilterMethod m_method;
|
||||||
};
|
};
|
||||||
|
|
||||||
class RC4 {
|
class RC4 {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue