1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-14 17:04:59 +00:00

LibPDF: Add support for AESV2 encryption

This commit is contained in:
Nico Weber 2023-07-11 09:57:37 -04:00 committed by Andreas Kling
parent 67d8c8badb
commit 5998072f15
3 changed files with 163 additions and 20 deletions

View file

@ -24,6 +24,8 @@
A(C) \
A(CA) \
A(CCITTFaxDecode) \
A(CF) \
A(CFM) \
A(CalRGB) \
A(CIDSystemInfo) \
A(CIDToGIDMap) \
@ -130,6 +132,8 @@
A(SA) \
A(SM) \
A(SMask) \
A(StmF) \
A(StrF) \
A(Subject) \
A(Subtype) \
A(Supplement) \

View file

@ -5,6 +5,9 @@
*/
#include <AK/ByteBuffer.h>
#include <AK/Debug.h>
#include <AK/Random.h>
#include <LibCrypto/Cipher/AES.h>
#include <LibCrypto/Hash/MD5.h>
#include <LibPDF/CommonNames.h>
#include <LibPDF/Document.h>
@ -57,6 +60,58 @@ PDFErrorOr<NonnullRefPtr<SecurityHandler>> SecurityHandler::create(Document* doc
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)
{
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,
// "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.
int length_in_bits;
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>();
else if (v == 1)
length_in_bits = 40;
else
else if (v != 1)
return Error(Error::Type::Parse, "Can't determine length of encryption key");
auto length = length_in_bits / 8;
dbgln_if(PDF_DEBUG, "encryption v{}, method {}, length {}", v, (int)method, length);
bool encrypt_metadata = true;
if (encryption_dict->contains(CommonNames::EncryptMetadata))
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_revision(revision)
, m_o_entry(o_entry)
@ -92,6 +168,7 @@ StandardSecurityHandler::StandardSecurityHandler(Document* document, size_t revi
, m_flags(flags)
, m_encrypt_metadata(encrypt_metadata)
, m_length(length)
, m_method(method)
{
}
@ -292,15 +369,16 @@ ByteBuffer StandardSecurityHandler::compute_encryption_key(ByteBuffer password_s
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
// Algorithm 1: Encryption of data using the RC3 or AES algorithms
// FIXME: Support AES
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
// the string or stream to be encrypted. If the string is a direct object, use
// the identifier of the indirect object containing it.
@ -309,20 +387,20 @@ void StandardSecurityHandler::encrypt(NonnullRefPtr<Object> object, Reference re
// object number.
// 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
// 2 bytes of the generation number in that order, low-order byte first. ...
auto encryption_key = m_encryption_key.value();
ReadonlyBytes bytes;
Function<void(ByteBuffer const&)> assign;
Function<void(ReadonlyBytes)> assign;
if (object->is<StreamObject>()) {
auto stream = object->cast<StreamObject>();
bytes = stream->bytes();
assign = [&object](ByteBuffer const& buffer) {
object->cast<StreamObject>()->buffer() = buffer;
assign = [&object](ReadonlyBytes bytes) {
object->cast<StreamObject>()->buffer() = MUST(ByteBuffer::copy(bytes));
};
if (stream->dict()->contains(CommonNames::Filter)) {
@ -333,8 +411,8 @@ void StandardSecurityHandler::encrypt(NonnullRefPtr<Object> object, Reference re
} else if (object->is<StringObject>()) {
auto string = object->cast<StringObject>();
bytes = string->string().bytes();
assign = [&object](ByteBuffer const& buffer) {
object->cast<StringObject>()->set_string(DeprecatedString(buffer.bytes()));
assign = [&object](ReadonlyBytes bytes) {
object->cast<StringObject>()->set_string(DeprecatedString(bytes));
};
} else {
VERIFY_NOT_REACHED();
@ -349,6 +427,13 @@ void StandardSecurityHandler::encrypt(NonnullRefPtr<Object> object, Reference re
encryption_key.append(generation & 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
// function.
Crypto::Hash::MD5 md5;
@ -362,16 +447,57 @@ void StandardSecurityHandler::encrypt(NonnullRefPtr<Object> object, Reference re
if (key.size() > min(encryption_key.size(), 16))
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);
auto output = rc4.encrypt(bytes);
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
{
// AES and RC4 are both symmetric, so decryption is the same as encryption
encrypt(object, reference);
crypt(object, reference, Crypto::Cipher::Intent::Decryption);
}
static constexpr auto identity_permutation = iota_array<size_t, 256>(0);

View file

@ -9,8 +9,18 @@
#include <AK/Span.h>
#include <LibPDF/ObjectDerivatives.h>
namespace Crypto::Cipher {
enum class Intent;
}
namespace PDF {
enum class CryptFilterMethod {
None,
V2, // RC4
AESV2,
};
class SecurityHandler : public RefCounted<SecurityHandler> {
public:
static PDFErrorOr<NonnullRefPtr<SecurityHandler>> create(Document*, NonnullRefPtr<DictObject> encryption_dict);
@ -28,7 +38,7 @@ class StandardSecurityHandler : public SecurityHandler {
public:
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;
@ -41,6 +51,8 @@ protected:
void decrypt(NonnullRefPtr<Object>, Reference reference) const override;
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_v3_and_newer(ByteBuffer password_string);
@ -54,6 +66,7 @@ private:
u32 m_flags;
bool m_encrypt_metadata;
size_t m_length;
CryptFilterMethod m_method;
};
class RC4 {