Selaa lähdekoodia

LibPDF: Add support for AESV2 encryption

Nico Weber 2 vuotta sitten
vanhempi
commit
5998072f15

+ 4 - 0
Userland/Libraries/LibPDF/CommonNames.h

@@ -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)                 \

+ 145 - 19
Userland/Libraries/LibPDF/Encryption.cpp

@@ -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);

+ 14 - 1
Userland/Libraries/LibPDF/Encryption.h

@@ -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 {