// Copyright (C) 2019 Jakub Melka // // This file is part of PdfForQt. // // PdfForQt is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // PdfForQt is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with PDFForQt. If not, see . #include "pdfsecurityhandler.h" #include "pdfexception.h" #include #include #include #include #include namespace pdf { // Padding password static constexpr std::array PDFPasswordPadding = { 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 }; PDFSecurityHandlerPointer PDFSecurityHandler::createSecurityHandler(const PDFObject& encryptionDictionaryObject, const QByteArray& id) { if (encryptionDictionaryObject.isNull()) { return PDFSecurityHandlerPointer(new PDFNoneSecurityHandler()); } if (!encryptionDictionaryObject.isDictionary()) { throw PDFParserException(PDFTranslationContext::tr("Invalid encryption dictionary.")); } const PDFDictionary* dictionary = encryptionDictionaryObject.getDictionary(); auto getName = [](const PDFDictionary* dictionary, const char* key, bool required, const char* defaultValue = nullptr) -> QByteArray { const PDFObject& nameObject = dictionary->get(key); if (nameObject.isNull()) { return defaultValue ? QByteArray(defaultValue) : QByteArray(); } if (!nameObject.isName()) { if (required) { throw PDFParserException(PDFTranslationContext::tr("Invalid value for entry '%1' in encryption dictionary. Name expected.").arg(QString::fromLatin1(key))); } return defaultValue ? QByteArray(defaultValue) : QByteArray(); } return nameObject.getString(); }; auto getInt = [](const PDFDictionary* dictionary, const char* key, bool required, PDFInteger defaultValue = -1) -> PDFInteger { const PDFObject& intObject = dictionary->get(key); if (!intObject.isInt()) { if (required) { throw PDFParserException(PDFTranslationContext::tr("Invalid value for entry '%1' in encryption dictionary. Integer expected.").arg(QString::fromLatin1(key))); } return defaultValue; } return intObject.getInteger(); }; QByteArray filterName = getName(dictionary, "Filter", true); if (filterName != "Standard") { throw PDFParserException(PDFTranslationContext::tr("Unknown security handler.")); } const int V = getInt(dictionary, "V", true); // Check V if (V < 1 || V > 5) { throw PDFParserException(PDFTranslationContext::tr("Unsupported version of document encryption (V = %1).").arg(V)); } // Only valid for V == 2 or V == 3, otherwise we set file encryption key length manually int Length = 40; switch (V) { case 1: Length = 40; break; case 2: case 3: Length = getInt(dictionary, "Length", false, 40); break; case 4: Length = 128; break; case 5: Length = 256; break; default: Q_ASSERT(false); break; } // Create standard security handler PDFStandardSecurityHandler handler; handler.m_V = V; handler.m_keyLength = Length; // Add "Identity" filter to the filters CryptFilter identityFilter; identityFilter.type = CryptFilterType::Identity; handler.m_cryptFilters["Identity"] = identityFilter; if (V == 4 || V == 5) { const PDFObject& cryptFilterObjects = dictionary->get("CF"); if (cryptFilterObjects.isDictionary()) { auto parseCryptFilter = [&getName](const PDFObject& object) -> CryptFilter { if (!object.isDictionary()) { throw PDFParserException(PDFTranslationContext::tr("Crypt filter is not a dictionary!")); } const PDFDictionary* cryptFilterDictionary = object.getDictionary(); CryptFilter filter; QByteArray CFMName = getName(cryptFilterDictionary, "CFM", false, "None"); if (CFMName == "None") { filter.type = CryptFilterType::None; } else if (CFMName == "V2") { filter.type = CryptFilterType::V2; } else if (CFMName == "AESV2") { filter.type = CryptFilterType::AESV2; } else if (CFMName == "AESV3") { filter.type = CryptFilterType::AESV3; } else { throw PDFParserException(PDFTranslationContext::tr("Unsupported encryption algorithm '%1'.").arg(QString::fromLatin1(CFMName))); } QByteArray authEventName = getName(cryptFilterDictionary, "AuthEvent", false, "DocOpen"); if (authEventName == "DocOpen") { filter.authEvent = AuthEvent::DocOpen; } else if (authEventName == "EFOpen") { filter.authEvent = AuthEvent::EFOpen; } else { throw PDFParserException(PDFTranslationContext::tr("Unsupported authorization event '%1'.").arg(QString::fromLatin1(authEventName))); } return filter; }; const PDFDictionary* cryptFilters = cryptFilterObjects.getDictionary(); for (size_t i = 0, cryptFilterCount = cryptFilters->getCount(); i < cryptFilterCount; ++i) { handler.m_cryptFilters[cryptFilters->getKey(i)] = parseCryptFilter(cryptFilters->getValue(i)); } } // Now, add standard filters auto resolveFilter = [&handler](const QByteArray& name) { auto it = handler.m_cryptFilters.find(name); if (it == handler.m_cryptFilters.cend()) { throw PDFParserException(PDFTranslationContext::tr("Uknown crypt filter '%1'.").arg(QString::fromLatin1(name))); } return it->second; }; handler.m_filterStreams = resolveFilter(getName(dictionary, "StmF", false, "Identity")); handler.m_filterStrings = resolveFilter(getName(dictionary, "StrF", false, "Identity")); if (dictionary->hasKey("EFF")) { handler.m_filterEmbeddedFiles = resolveFilter(getName(dictionary, "EFF", true)); } else { // According to the PDF specification, if 'EFF' entry is omitted, then filter // for streams is used. handler.m_filterEmbeddedFiles = handler.m_filterStreams; } } int R = getInt(dictionary, "R", true); if (R < 2 || R > 6 || R == 5) { throw PDFParserException(PDFTranslationContext::tr("Revision %1 of standard security handler is not supported.").arg(R)); } handler.m_R = R; auto readByteArray = [dictionary](const char* key, int size) { QByteArray result; const PDFObject& object = dictionary->get(key); if (object.isString()) { result = object.getString(); if (result.size() != size) { throw PDFParserException(PDFTranslationContext::tr("Expected %1 characters long string in entry '%2'. Provided length is %3.").arg(size).arg(QString::fromLatin1(key)).arg(result.size())); } } else { throw PDFParserException(PDFTranslationContext::tr("Expected %1 characters long string in entry '%2'.").arg(size).arg(QString::fromLatin1(key))); } return result; }; handler.m_O = readByteArray("O", (R != 6) ? 32 : 48); handler.m_U = readByteArray("U", (R != 6) ? 32 : 48); handler.m_permissions = static_cast(static_cast(getInt(dictionary, "P", true))); if (R == 6) { handler.m_OE = readByteArray("OE", 32); handler.m_UE = readByteArray("UE", 32); handler.m_Perms = readByteArray("Perms", 16); } const PDFObject& encryptMetadataObject = dictionary->get("EncryptMetadata"); if (encryptMetadataObject.isBool()) { handler.m_encryptMetadata = encryptMetadataObject.getBool(); } handler.m_ID = id; return PDFSecurityHandlerPointer(new PDFStandardSecurityHandler(qMove(handler))); } PDFSecurityHandler::AuthorizationResult PDFStandardSecurityHandler::authenticate(const std::function& getPasswordCallback) { QByteArray password; bool passwordObtained = true; // Clear the authorization data m_authorizationData = AuthorizationData(); while (passwordObtained) { switch (m_R) { case 2: case 3: case 4: { // Try to authorize by owner password { QByteArray userPassword = createUserPasswordFromOwnerPassword(password); QByteArray fileEncryptionKey = createFileEncryptionKey(userPassword); QByteArray U = createEntryValueU_r234(fileEncryptionKey); if (U == m_U) { // We have authorized owner access m_authorizationData.authorizationResult = AuthorizationResult::OwnerAuthorized; m_authorizationData.fileEncryptionKey = fileEncryptionKey; return AuthorizationResult::OwnerAuthorized; } } // Try to authorize user password QByteArray fileEncryptionKey = createFileEncryptionKey(password); QByteArray U = createEntryValueU_r234(fileEncryptionKey); if (U == m_U) { // We have authorized owner access m_authorizationData.authorizationResult = AuthorizationResult::UserAuthorized; m_authorizationData.fileEncryptionKey = fileEncryptionKey; return AuthorizationResult::UserAuthorized; } break; } case 6: { UserOwnerData_r6 userData = parseParts(m_U); UserOwnerData_r6 ownerData = parseParts(m_O); // Try to authorize owner password { QByteArray inputData = password + ownerData.validationSalt + m_U; QByteArray hash = createHash_r6(inputData, password, true); if (hash == ownerData.hash) { // We have authorized owner access. Now we must calculate the owner encryption key QByteArray fileEncryptionKeyInputData = password + ownerData.keySalt + m_U; QByteArray fileEncryptionDecryptionKey = createHash_r6(fileEncryptionKeyInputData, password, true); Q_ASSERT(fileEncryptionDecryptionKey.size() == 32); AES_KEY key = { }; AES_set_decrypt_key(reinterpret_cast(fileEncryptionDecryptionKey.data()), fileEncryptionDecryptionKey.size() * 8, &key); unsigned char aesInitializationVector[AES_BLOCK_SIZE] = { }; m_authorizationData.fileEncryptionKey.resize(m_OE.size()); AES_cbc_encrypt(reinterpret_cast(m_OE.data()), reinterpret_cast(m_authorizationData.fileEncryptionKey.data()), m_OE.size(), &key, aesInitializationVector, AES_DECRYPT); m_authorizationData.authorizationResult = AuthorizationResult::OwnerAuthorized; } } // Try to authorize user password if (!m_authorizationData.isAuthorized()) { QByteArray inputData = password + userData.validationSalt; QByteArray hash = createHash_r6(inputData, password, false); if (hash == userData.hash) { QByteArray fileEncryptionKeyInputData = password + userData.keySalt; QByteArray fileEncryptionDecryptionKey = createHash_r6(fileEncryptionKeyInputData, password, false); Q_ASSERT(fileEncryptionDecryptionKey.size() == 32); AES_KEY key = { }; AES_set_decrypt_key(reinterpret_cast(fileEncryptionDecryptionKey.data()), fileEncryptionDecryptionKey.size() * 8, &key); unsigned char aesInitializationVector[AES_BLOCK_SIZE] = { }; m_authorizationData.fileEncryptionKey.resize(m_OE.size()); AES_cbc_encrypt(reinterpret_cast(m_OE.data()), reinterpret_cast(m_authorizationData.fileEncryptionKey.data()), m_OE.size(), &key, aesInitializationVector, AES_DECRYPT); // We have authorized owner access m_authorizationData.authorizationResult = AuthorizationResult::UserAuthorized; } } // Stop, if we authorized the document usage if (m_authorizationData.isAuthorized()) { return m_authorizationData.authorizationResult; } break; } default: return AuthorizationResult::Failed; } // TODO: Handle passwords better - in some revisions, must be in PDFDocEncoding! password = getPasswordCallback(&passwordObtained).toUtf8(); } return AuthorizationResult::Cancelled; } QByteArray PDFStandardSecurityHandler::createFileEncryptionKey(const QByteArray& password) const { QByteArray result; switch (m_R) { case 2: case 3: case 4: { std::array paddedPassword = createPaddedPassword32(password); uint32_t transformedPermissions = qToLittleEndian(m_permissions); MD5_CTX context = { }; MD5_Init(&context); MD5_Update(&context, paddedPassword.data(), paddedPassword.size()); MD5_Update(&context, m_O.constData(), m_O.size()); MD5_Update(&context, &transformedPermissions, sizeof(transformedPermissions)); MD5_Update(&context, m_ID.constData(), m_ID.size()); if (!m_encryptMetadata) { constexpr uint32_t value = 0xFFFFFFFF; MD5_Update(&context, &value, sizeof(value)); } std::array fileEncryptionKey; MD5_Final(fileEncryptionKey.data(), &context); const int keyByteLength = m_keyLength / 8; if (keyByteLength > MD5_DIGEST_LENGTH) { throw PDFParserException(PDFTranslationContext::tr("Encryption key length (%1) exceeded maximal value of.").arg(keyByteLength).arg(MD5_DIGEST_LENGTH)); } if (m_R >= 3) { for (int i = 0; i < 50; ++i) { MD5_Init(&context); MD5_Update(&context, fileEncryptionKey.data(), keyByteLength); MD5_Final(fileEncryptionKey.data(), &context); } } result.resize(keyByteLength); std::copy_n(fileEncryptionKey.cbegin(), keyByteLength, result.begin()); break; } case 6: { // This function must not be called with revision 6 Q_ASSERT(false); break; } default: { throw PDFParserException(PDFTranslationContext::tr("Revision %1 of standard security handler is not supported.").arg(m_R)); } } return result; } QByteArray PDFStandardSecurityHandler::createEntryValueU_r234(const QByteArray& fileEncryptionKey) const { QByteArray result; switch (m_R) { case 2: { RC4_KEY key = { }; RC4_set_key(&key, fileEncryptionKey.size(), reinterpret_cast(fileEncryptionKey.data())); result.resize(static_cast(PDFPasswordPadding.size())); RC4(&key, PDFPasswordPadding.size(), PDFPasswordPadding.data(), reinterpret_cast(result.data())); break; } case 3: case 4: { std::array hash; MD5_CTX context = { }; MD5_Init(&context); MD5_Update(&context, PDFPasswordPadding.data(), PDFPasswordPadding.size()); MD5_Update(&context, m_ID.data(), m_ID.size()); MD5_Final(hash.data(), &context); RC4_KEY key = { }; RC4_set_key(&key, fileEncryptionKey.size(), reinterpret_cast(fileEncryptionKey.data())); std::array encryptedHash; RC4(&key, hash.size(), hash.data(), reinterpret_cast(encryptedHash.data())); QByteArray transformedKey = fileEncryptionKey; for (int i = 1; i <= 19; ++i) { for (int j = 0, keySize = fileEncryptionKey.size(); j < keySize; ++j) { transformedKey[j] = static_cast(fileEncryptionKey[j]) ^ static_cast(i); } RC4_set_key(&key, transformedKey.size(), reinterpret_cast(transformedKey.data())); RC4(&key, encryptedHash.size(), encryptedHash.data(), reinterpret_cast(encryptedHash.data())); } // We do a hack here. In the PDF's specification, it is written, that arbitrary 16 bytes // are appended to the 16 bytes result. We use the last 16 bytes of the U entry, because we // want to compare byte arrays entirely (otherwise we must compare only 16 bytes to authenticate // user password). result = m_U; std::copy_n(encryptedHash.begin(), encryptedHash.size(), result.begin()); break; } default: { throw PDFParserException(PDFTranslationContext::tr("Revision %1 of standard security handler is not supported.").arg(m_R)); } } return result; } QByteArray PDFStandardSecurityHandler::createUserPasswordFromOwnerPassword(const QByteArray& password) const { QByteArray result; std::array paddedPassword = createPaddedPassword32(password); std::array hash; MD5_CTX context = { }; MD5_Init(&context); MD5_Update(&context, paddedPassword.data(), paddedPassword.size()); MD5_Final(hash.data(), &context); const int keyByteLength = m_keyLength / 8; if (keyByteLength > MD5_DIGEST_LENGTH) { throw PDFParserException(PDFTranslationContext::tr("Encryption key length (%1) exceeded maximal value of.").arg(keyByteLength).arg(MD5_DIGEST_LENGTH)); } if (m_R >= 3) { for (int i = 0; i < 50; ++i) { MD5_Init(&context); MD5_Update(&context, hash.data(), keyByteLength); MD5_Final(hash.data(), &context); } } switch (m_R) { case 2: { RC4_KEY key = { }; RC4_set_key(&key, keyByteLength, reinterpret_cast(hash.data())); result.resize(m_O.size()); RC4(&key, m_O.size(), reinterpret_cast(m_O.data()), reinterpret_cast(result.data())); break; } case 3: case 4: { QByteArray buffer = m_O; QByteArray transformedKey; transformedKey.resize(keyByteLength); std::copy_n(hash.data(), keyByteLength, transformedKey.data()); for (int i = 19; i >= 0; --i) { for (int j = 0, keySize = transformedKey.size(); j < keySize; ++j) { transformedKey[j] = static_cast(hash[j]) ^ static_cast(i); } RC4_KEY key = { }; RC4_set_key(&key, transformedKey.size(), reinterpret_cast(transformedKey.data())); RC4(&key, buffer.size(), reinterpret_cast(buffer.data()), reinterpret_cast(buffer.data())); } result = buffer; break; } default: { throw PDFParserException(PDFTranslationContext::tr("Revision %1 of standard security handler is not supported.").arg(m_R)); } } return result; } std::array PDFStandardSecurityHandler::createPaddedPassword32(const QByteArray& password) const { std::array result = { }; int copiedBytes = qMin(static_cast(result.size()), password.size()); auto it = result.begin(); for (int i = 0; i < copiedBytes; ++i) { *it++ = static_cast(password[i]); } auto itPadding = PDFPasswordPadding.cbegin(); for (; it != result.cend();) { Q_ASSERT(itPadding != PDFPasswordPadding.cend()); *it++ = *itPadding++; } return result; } QByteArray PDFStandardSecurityHandler::createHash_r6(const QByteArray& input, const QByteArray& inputPassword, bool useUserKey) const { QByteArray result; // First compute sha-256 digest of the input std::array inputDigest = { }; SHA256(reinterpret_cast(input.data()), input.size(), inputDigest.data()); std::vector K(inputDigest.cbegin(), inputDigest.cend()); // Fill the user key, if we use it std::vector userKey; if (useUserKey) { userKey.resize(m_U.size()); std::copy_n(m_U.constData(), m_U.size(), userKey.begin()); } const size_t userKeySize = userKey.size(); // Fill the input password std::vector password(inputPassword.constData(), inputPassword.constData() + inputPassword.size()); const size_t passwordSize = password.size(); std::vector K1; std::vector E; int round = 0; while (round < 64 || round < E.back() + 32) { const size_t blockCount = 64; const size_t KSize = K.size(); const size_t sequenceSize = passwordSize + KSize + userKeySize; const size_t totalSize = blockCount * sequenceSize; // Resize the arrays K1.resize(totalSize); E.resize(totalSize); // a) fill the input array K1 with data auto it = K1.begin(); for (size_t i = 0; i < blockCount; ++i) { std::copy_n(password.cbegin(), passwordSize, it); std::advance(it, passwordSize); std::copy_n(K.cbegin(), KSize, it); std::advance(it, KSize); std::copy_n(userKey.cbegin(), userKeySize, it); std::advance(it, userKeySize); } Q_ASSERT(it == K1.cend()); Q_ASSERT(K.size() >= 32); // b) encrypt K1 with AES-128 in CBC mode, first 16 bytes of K is key, // second 16 bytes in K is initialization vector for AES algorithm. AES_KEY key = { }; AES_set_encrypt_key(K.data(), 128, &key); AES_cbc_encrypt(K1.data(), E.data(), K1.size(), &key, K.data() + 16, AES_ENCRYPT); // c) we take first 16 bytes from E as unsigned 128 bit big-endian integer and compute // remainder modulo 3. Then we decide which SHA function we will use. // We can't directly modulo 128 bit unsigned number, because we do not have 128 bit arithmetic (yet). // We will use following trick from https://math.stackexchange.com/questions/2727954/bit-representation-and-divisibility-by-3 // // 2^n mod 3 = 2 for n = 1, 3, 5, 7, 9, ... // 2^n mod 3 = 1 for n = 0, 2, 4, 6, 8, ... // // Also, it doesn't matter the endianity of the numbers, becase for example, when we change endianity of 16 bit // numbers, then bits 0-7 became 8-15, so even/odd bits become also even/odd. int remainderAccumulator = 0; for (size_t i = 0; i < 16; ++i) { uint8_t byte = E[i]; int currentRemainder = 1; for (uint8_t i = 0; i < 8; ++i) { if ((byte >> i) & 1) { remainderAccumulator += currentRemainder; } // We alternate the remainder 1, 2, 1, 2, 1, 2, ... currentRemainder = 3 - currentRemainder; } } remainderAccumulator = remainderAccumulator % 3; // d) according to the remainder, decide, which function we will use switch (remainderAccumulator) { case 0: { K.resize(SHA256_DIGEST_LENGTH); SHA256(E.data(), E.size(), K.data()); break; } case 1: { K.resize(SHA384_DIGEST_LENGTH); SHA384(E.data(), E.size(), K.data()); break; } case 2: { K.resize(SHA512_DIGEST_LENGTH); SHA512(E.data(), E.size(), K.data()); break; } default: { // Invalid value, can't occur Q_ASSERT(false); break; } } ++round; } Q_ASSERT(K.size() >= 32); // Clamp result to 32 bytes result.resize(32); std::copy_n(K.data(), 32, reinterpret_cast(result.data())); return result; } PDFStandardSecurityHandler::UserOwnerData_r6 PDFStandardSecurityHandler::parseParts(const QByteArray& data) const { UserOwnerData_r6 result; Q_ASSERT(data.size() == 48); result.hash = data.left(32); result.validationSalt = data.mid(32, 8); result.keySalt = data.mid(40, 8); return result; } } // namespace pdf