// Copyright (C) 2019-2022 Jakub Melka // // This file is part of PDF4QT. // // PDF4QT 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 // with the written consent of the copyright owner, any later version. // // PDF4QT 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 PDF4QT. If not, see . #include "pdfsecurityhandler.h" #include "pdfexception.h" #include "pdfencoding.h" #include "pdfvisitor.h" #include "pdfutils.h" #include "pdfdocumentbuilder.h" #include "pdfdbgheap.h" #include "pdfcertificatemanager.h" #include #include #include #include #include #include #include #include #include namespace pdf { template using openssl_ptr = std::unique_ptr; // 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 }; class PDFDecryptOrEncryptObjectVisitor : public PDFAbstractVisitor { public: enum class Mode { Decrypt, Encrypt }; explicit PDFDecryptOrEncryptObjectVisitor(const PDFSecurityHandler* securityHandler, PDFObjectReference reference, Mode mode) : m_securityHandler(securityHandler), m_reference(reference), m_mode(mode) { m_objectStack.reserve(32); } virtual void visitNull() override; virtual void visitBool(bool value) override; virtual void visitInt(PDFInteger value) override; virtual void visitReal(PDFReal value) override; virtual void visitString(PDFStringRef string) override; virtual void visitName(PDFStringRef name) override; virtual void visitArray(const PDFArray* array) override; virtual void visitDictionary(const PDFDictionary* dictionary) override; virtual void visitStream(const PDFStream* stream) override; virtual void visitReference(const PDFObjectReference reference) override; PDFObject getProcessedObject(); private: const PDFSecurityHandler* m_securityHandler = nullptr; std::vector m_objectStack; PDFObjectReference m_reference; Mode m_mode = Mode::Decrypt; }; void PDFDecryptOrEncryptObjectVisitor::visitNull() { m_objectStack.push_back(PDFObject::createNull()); } void PDFDecryptOrEncryptObjectVisitor::visitBool(bool value) { m_objectStack.push_back(PDFObject::createBool(value)); } void PDFDecryptOrEncryptObjectVisitor::visitInt(PDFInteger value) { m_objectStack.push_back(PDFObject::createInteger(value)); } void PDFDecryptOrEncryptObjectVisitor::visitReal(PDFReal value) { m_objectStack.push_back(PDFObject::createReal(value)); } void PDFDecryptOrEncryptObjectVisitor::visitString(PDFStringRef string) { switch (m_mode) { case pdf::PDFDecryptOrEncryptObjectVisitor::Mode::Decrypt: m_objectStack.push_back(PDFObject::createString(m_securityHandler->decrypt(string.getString(), m_reference, PDFSecurityHandler::EncryptionScope::String))); break; case pdf::PDFDecryptOrEncryptObjectVisitor::Mode::Encrypt: m_objectStack.push_back(PDFObject::createString(m_securityHandler->encrypt(string.getString(), m_reference, PDFSecurityHandler::EncryptionScope::String))); break; default: Q_ASSERT(false); break; } } void PDFDecryptOrEncryptObjectVisitor::visitName(PDFStringRef name) { m_objectStack.push_back(PDFObject::createName(name)); } void PDFDecryptOrEncryptObjectVisitor::visitArray(const PDFArray* array) { acceptArray(array); // We have all objects on the stack Q_ASSERT(array->getCount() <= m_objectStack.size()); auto it = std::next(m_objectStack.cbegin(), m_objectStack.size() - array->getCount()); std::vector objects(it, m_objectStack.cend()); PDFObject object = PDFObject::createArray(std::make_shared(qMove(objects))); m_objectStack.erase(it, m_objectStack.cend()); m_objectStack.push_back(object); } void PDFDecryptOrEncryptObjectVisitor::visitDictionary(const PDFDictionary* dictionary) { Q_ASSERT(dictionary); // We must check, if it is or isn't a signature dictionary. If it is, // then don't decrypt/encrypt the Content value. We also don't check, if signature // isn't indirectly referenced by reference. Hope it isn't... const PDFObject& typeObject = dictionary->get("Type"); bool isSignatureObject = (typeObject.isName() && typeObject.getString() == "Sig"); std::vector entries; entries.reserve(dictionary->getCount()); for (size_t i = 0, count = dictionary->getCount(); i < count; ++i) { if (isSignatureObject && dictionary->getKey(i) == "Contents") { entries.emplace_back(dictionary->getKey(i), dictionary->getValue(i)); } else { dictionary->getValue(i).accept(this); entries.emplace_back(dictionary->getKey(i), m_objectStack.back()); m_objectStack.pop_back(); } } m_objectStack.push_back(PDFObject::createDictionary(std::make_shared(qMove(entries)))); } void PDFDecryptOrEncryptObjectVisitor::visitStream(const PDFStream* stream) { // Don't decrypt/encrypt, if it is a Metadata stream and Metadata encryption is turned off const PDFDictionary* dictionary = stream->getDictionary(); const PDFObject& typeObject = dictionary->get("Type"); bool isMetadata = (typeObject.isName() && typeObject.getString() == "Metadata"); if (isMetadata && !m_securityHandler->isMetadataEncrypted()) { m_objectStack.push_back(PDFObject::createStream(std::make_shared(PDFDictionary(*dictionary), QByteArray(*stream->getContent())))); return; } // Decrypt/encrypt the dictionary visitDictionary(dictionary); PDFObject dictionaryObject = m_objectStack.back(); m_objectStack.pop_back(); // We must also handle situation, that stream has specified Crypt filter. // In this case, we must delegate decryption/encryption to the stream filters. PDFDictionary processedDictionary(*dictionaryObject.getDictionary()); QByteArray processedData; if (!processedDictionary.hasKey("Crypt")) { // Is it an embedded file? const PDFObject& object = processedDictionary.get("Type"); const bool isEmbeddedFile = object.isName() && object.getString() == "EmbeddedFile"; const PDFSecurityHandler::EncryptionScope scope = !isEmbeddedFile ? PDFSecurityHandler::EncryptionScope::Stream : PDFSecurityHandler::EncryptionScope::EmbeddedFile; switch (m_mode) { case pdf::PDFDecryptOrEncryptObjectVisitor::Mode::Decrypt: processedData = m_securityHandler->decrypt(*stream->getContent(), m_reference, scope); break; case pdf::PDFDecryptOrEncryptObjectVisitor::Mode::Encrypt: processedData = m_securityHandler->encrypt(*stream->getContent(), m_reference, scope); break; default: Q_ASSERT(false); break; } processedDictionary.setEntry(PDFInplaceOrMemoryString("Length"), PDFObject::createInteger(processedData.size())); } else { switch (m_mode) { case pdf::PDFDecryptOrEncryptObjectVisitor::Mode::Decrypt: { processedData = *stream->getContent(); processedDictionary.setEntry(PDFInplaceOrMemoryString(PDFSecurityHandler::OBJECT_REFERENCE_DICTIONARY_NAME), PDFObject::createReference(m_reference)); break; } case pdf::PDFDecryptOrEncryptObjectVisitor::Mode::Encrypt: { processedData = *stream->getContent(); processedDictionary.removeEntry(PDFSecurityHandler::OBJECT_REFERENCE_DICTIONARY_NAME); break; } default: Q_ASSERT(false); break; } } m_objectStack.push_back(PDFObject::createStream(std::make_shared(qMove(processedDictionary), qMove(processedData)))); } void PDFDecryptOrEncryptObjectVisitor::visitReference(const PDFObjectReference reference) { m_objectStack.push_back(PDFObject::createReference(reference)); } PDFObject PDFDecryptOrEncryptObjectVisitor::getProcessedObject() { Q_ASSERT(m_objectStack.size() == 1); return qMove(m_objectStack.back()); } PDFObject PDFSecurityHandler::decryptObject(const PDFObject& object, PDFObjectReference reference) const { PDFDecryptOrEncryptObjectVisitor visitor(this, reference, PDFDecryptOrEncryptObjectVisitor::Mode::Decrypt); object.accept(&visitor); return visitor.getProcessedObject(); } PDFObject PDFSecurityHandler::encryptObject(const PDFObject& object, PDFObjectReference reference) const { PDFDecryptOrEncryptObjectVisitor visitor(this, reference, PDFDecryptOrEncryptObjectVisitor::Mode::Encrypt); object.accept(&visitor); return visitor.getProcessedObject(); } void PDFSecurityHandler::parseCryptFilters(const PDFDictionary* dictionary, PDFSecurityHandler& handler, int Length) { const PDFObject& cryptFilterObjects = dictionary->get("CF"); if (cryptFilterObjects.isDictionary()) { const PDFDictionary* cryptFilters = cryptFilterObjects.getDictionary(); for (size_t i = 0, cryptFilterCount = cryptFilters->getCount(); i < cryptFilterCount; ++i) { handler.m_cryptFilters[cryptFilters->getKey(i).getString()] = parseCryptFilter(Length, 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 PDFException(PDFTranslationContext::tr("Unknown crypt filter '%1'.").arg(QString::fromLatin1(name))); } return it->second; }; handler.m_filterStreams = resolveFilter(parseName(dictionary, "StmF", false, IDENTITY_FILTER_NAME)); handler.m_filterStrings = resolveFilter(parseName(dictionary, "StrF", false, IDENTITY_FILTER_NAME)); if (dictionary->hasKey("EFF")) { handler.m_filterEmbeddedFiles = resolveFilter(parseName(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; } } void PDFSecurityHandler::parseDataStandardSecurityHandler(const PDFDictionary* dictionary, const QByteArray& id, int Length, PDFStandardSecurityHandler& handler) { int R = parseInt(dictionary, "R", true); if (R < 2 || R > 6) { throw PDFException(PDFTranslationContext::tr("Revision %1 of standard security handler is not supported.").arg(R)); } handler.m_R = R; handler.m_filterDefault.authEvent = AuthEvent::DocOpen; handler.m_filterDefault.keyLength = Length / 8; handler.m_filterDefault.type = (R > 4) ? CryptFilterType::AESV3 : CryptFilterType::V2; 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 PDFException(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 PDFException(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 && R != 5) ? 32 : 48); handler.m_U = readByteArray("U", (R != 6 && R != 5) ? 32 : 48); handler.m_permissions = static_cast(static_cast(parseInt(dictionary, "P", true))); if (R == 6 || R == 5) { 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; } PDFSecurityHandlerPointer PDFSecurityHandler::createSecurityHandler(const PDFObject& encryptionDictionaryObject, const QByteArray& id) { if (encryptionDictionaryObject.isNull()) { return PDFSecurityHandlerPointer(new PDFNoneSecurityHandler()); } if (!encryptionDictionaryObject.isDictionary()) { throw PDFException(PDFTranslationContext::tr("Invalid encryption dictionary.")); } const PDFDictionary* dictionary = encryptionDictionaryObject.getDictionary(); PDFSecurityHandlerPointer handler = createSecurityHandlerInstance(dictionary); if (!handler) { throw PDFException(PDFTranslationContext::tr("Unknown security handler.")); } const int V = parseInt(dictionary, "V", true); // Check V if (V < 1 || V > 5) { throw PDFException(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 = parseInt(dictionary, "Length", false, 40); break; case 4: Length = 128; break; case 5: Length = 256; break; default: Q_ASSERT(false); break; } // Create security 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_FILTER_NAME] = identityFilter; if (V == 4 || V == 5) { parseCryptFilters(dictionary, *handler, Length); } switch (handler->getMode()) { case EncryptionMode::Standard: { auto typedHandler = qSharedPointerDynamicCast(handler); parseDataStandardSecurityHandler(dictionary, id, Length, *typedHandler); break; } case EncryptionMode::PublicKey: { auto typedHandler = qSharedPointerDynamicCast(handler); typedHandler->m_filterDefault.recipients = parseRecipients(dictionary); if (typedHandler->m_filterDefault.recipients.isEmpty()) { auto it = typedHandler->m_cryptFilters.find("DefaultCryptFilter"); if (it != typedHandler->m_cryptFilters.end()) { typedHandler->m_filterDefault = it->second; } } break; } case EncryptionMode::None: case EncryptionMode::Custom: Q_ASSERT(false); break; default: Q_ASSERT(false); } return handler; } void PDFSecurityHandler::fillEncryptionDictionary(PDFObjectFactory& factory) const { factory.beginDictionaryItem("V"); factory << PDFInteger(m_V); factory.endDictionaryItem(); factory.beginDictionaryItem("Length"); factory << PDFInteger(m_keyLength); factory.endDictionaryItem(); if (m_V == 4 || m_V == 5) { factory.beginDictionaryItem("CF"); factory.beginDictionary(); QByteArray stmfName = "Identity"; QByteArray strfName = stmfName; QByteArray effName = stmfName; for (const auto& cryptFilter : m_cryptFilters) { factory.beginDictionaryItem(cryptFilter.first); factory.beginDictionary(); factory.beginDictionaryItem("CFM"); if (cryptFilter.second == m_filterStrings) { strfName = cryptFilter.first; } if (cryptFilter.second == m_filterStreams) { stmfName = cryptFilter.first; } if (cryptFilter.second == m_filterEmbeddedFiles) { effName = cryptFilter.first; } switch (cryptFilter.second.type) { case CryptFilterType::None: // The application shall decrypt the data using the security handler factory << WrapName("None"); break; case CryptFilterType::V2: // Use file encryption key for RC4 algorithm factory << WrapName("V2"); break; case CryptFilterType::AESV2: // Use file encryption key for AES algorithm factory << WrapName("AESV2"); break; case CryptFilterType::AESV3: // Use file encryption key for AES 256 bit algorithm factory << WrapName("AESV3"); break; case CryptFilterType::Identity: // Don't decrypt anything, use identity function factory << WrapName("Identity"); break; default: Q_ASSERT(false); factory << WrapName("None"); break; } factory.endDictionaryItem(); factory.beginDictionaryItem("AuthEvent"); switch (cryptFilter.second.authEvent) { case AuthEvent::DocOpen: factory << WrapName("DocOpen"); break; case AuthEvent::EFOpen: factory << WrapName("EFOpen"); break; default: Q_ASSERT(false); break; } factory.endDictionaryItem(); factory.beginDictionaryItem("Length"); factory << cryptFilter.second.keyLength; factory.endDictionaryItem(); factory.endDictionary(); factory.endDictionaryItem(); } factory.endDictionary(); factory.endDictionaryItem(); // Store StmF, StrF, EFF factory.beginDictionaryItem("StmF"); factory << WrapName(stmfName); factory.endDictionaryItem(); factory.beginDictionaryItem("StrF"); factory << WrapName(strfName); factory.endDictionaryItem(); factory.beginDictionaryItem("EFF"); factory << WrapName(effName); factory.endDictionaryItem(); } } QByteArray PDFSecurityHandler::parseName(const PDFDictionary* dictionary, const char* key, bool required, const char* defaultValue) { const PDFObject& nameObject = dictionary->get(key); if (nameObject.isNull()) { return defaultValue ? QByteArray(defaultValue) : QByteArray(); } if (!nameObject.isName()) { if (required) { throw PDFException(PDFTranslationContext::tr("Invalid value for entry '%1' in encryption dictionary. Name expected.").arg(QString::fromLatin1(key))); } return defaultValue ? QByteArray(defaultValue) : QByteArray(); } return nameObject.getString(); } PDFInteger PDFSecurityHandler::parseInt(const PDFDictionary* dictionary, const char* key, bool required, PDFInteger defaultValue) { const PDFObject& intObject = dictionary->get(key); if (!intObject.isInt()) { if (required) { throw PDFException(PDFTranslationContext::tr("Invalid value for entry '%1' in encryption dictionary. Integer expected.").arg(QString::fromLatin1(key))); } return defaultValue; } return intObject.getInteger(); } CryptFilter PDFSecurityHandler::parseCryptFilter(PDFInteger length, const PDFObject& object) { if (!object.isDictionary()) { throw PDFException(PDFTranslationContext::tr("Crypt filter is not a dictionary!")); } const PDFDictionary* cryptFilterDictionary = object.getDictionary(); CryptFilter filter; QByteArray CFMName = parseName(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 PDFException(PDFTranslationContext::tr("Unsupported encryption algorithm '%1'.").arg(QString::fromLatin1(CFMName))); } QByteArray authEventName = parseName(cryptFilterDictionary, "AuthEvent", false, "DocOpen"); if (authEventName == "DocOpen") { filter.authEvent = AuthEvent::DocOpen; } else if (authEventName == "EFOpen") { filter.authEvent = AuthEvent::EFOpen; } else { throw PDFException(PDFTranslationContext::tr("Unsupported authorization event '%1'.").arg(QString::fromLatin1(authEventName))); } filter.keyLength = parseInt(cryptFilterDictionary, "Length", false, length / 8); // Recipients filter.recipients = parseRecipients(cryptFilterDictionary); return filter; } PDFSecurityHandlerPointer PDFSecurityHandler::createSecurityHandlerInstance(const PDFDictionary* dictionary) { QByteArray filterName = parseName(dictionary, "Filter", true); if (filterName == "Standard") { return PDFSecurityHandlerPointer(new PDFStandardSecurityHandler()); } if (filterName == "Entrust.PPKEF" || filterName == "Adobe.PPKLite" || filterName == "Adobe.PubSec") { QByteArray subFilter = parseName(dictionary, "SubFilter", true); if (subFilter == "adbe.pkcs7.s3" || subFilter == "adbe.pkcs7.s4" || subFilter == "adbe.pkcs7.s5") { return PDFSecurityHandlerPointer(new PDFPublicKeySecurityHandler()); } } return PDFSecurityHandlerPointer(); } QByteArrayList PDFSecurityHandler::parseRecipients(const PDFDictionary* dictionary) { QByteArrayList result; const PDFObject& recipients = dictionary->get("Recipients"); if (recipients.isArray()) { for (const PDFObject& object : *recipients.getArray()) { if (object.isString()) { result << object.getString(); } } } return result; } std::vector PDFStandardOrPublicSecurityHandler::createV2_ObjectEncryptionKey(PDFObjectReference reference, CryptFilter filter) const { std::vector inputKeyData = convertByteArrayToVector(m_authorizationData.fileEncryptionKey); uint32_t objectNumber = qToLittleEndian(static_cast(reference.objectNumber)); uint32_t generation = qToLittleEndian(static_cast(reference.generation)); inputKeyData.insert(inputKeyData.cend(), { uint8_t(objectNumber & 0xFF), uint8_t((objectNumber >> 8) & 0xFF), uint8_t((objectNumber >> 16) & 0xFF), uint8_t(generation & 0xFF), uint8_t((generation >> 8) & 0xFF) }); std::vector objectEncryptionKey(MD5_DIGEST_LENGTH, uint8_t(0)); MD5(inputKeyData.data(), inputKeyData.size(), objectEncryptionKey.data()); // Use up to (n + 5) bytes, maximally 16, from the digest as object encryption key size_t objectEncryptionKeySize = qMin(filter.keyLength + 5, MD5_DIGEST_LENGTH); objectEncryptionKey.resize(objectEncryptionKeySize); return objectEncryptionKey; } std::vector PDFStandardOrPublicSecurityHandler::createAESV2_ObjectEncryptionKey(PDFObjectReference reference) const { std::vector inputKeyData = convertByteArrayToVector(m_authorizationData.fileEncryptionKey); uint32_t objectNumber = qToLittleEndian(static_cast(reference.objectNumber)); uint32_t generation = qToLittleEndian(static_cast(reference.generation)); inputKeyData.insert(inputKeyData.cend(), { uint8_t(objectNumber & 0xFF), uint8_t((objectNumber >> 8) & 0xFF), uint8_t((objectNumber >> 16) & 0xFF), uint8_t(generation & 0xFF), uint8_t((generation >> 8) & 0xFF), 0x73, 0x41, 0x6C, 0x54 }); std::vector objectEncryptionKey(MD5_DIGEST_LENGTH, uint8_t(0)); MD5(inputKeyData.data(), inputKeyData.size(), objectEncryptionKey.data()); return objectEncryptionKey; } QByteArray PDFStandardOrPublicSecurityHandler::decryptUsingFilter(const QByteArray& data, CryptFilter filter, PDFObjectReference reference) const { QByteArray decryptedData; Q_ASSERT(m_authorizationData.isAuthorized()); struct AES_data { QByteArray initializationVector; QByteArray paddedData; }; auto prepareAES_data = [](const QByteArray& data) { AES_data result; result.initializationVector = data.left(AES_BLOCK_SIZE); // This is an error. But to handle it, we resize the vector // with arbitrary data. if (result.initializationVector.size() < AES_BLOCK_SIZE) { result.initializationVector.resize(AES_BLOCK_SIZE); } result.paddedData = data.mid(AES_BLOCK_SIZE); // Remove errorneous data - we must have a data of multiple of AES_BLOCK_SIZE const int remainder = result.paddedData.size() % AES_BLOCK_SIZE; if (remainder != 0) { result.paddedData = result.paddedData.left(result.paddedData.size() - remainder); } return result; }; auto removeAES_padding = [](const QByteArray& data) { if (data.isEmpty()) { return data; } // If padding doesnt fit from 1 to AES_BLOCK_SIZE, then it is // an error, but just clamp the value. const int padding = data.back(); const int clampedPadding = qBound(1, padding, AES_BLOCK_SIZE); return data.left(data.size() - clampedPadding); }; switch (filter.type) { case CryptFilterType::None: // The application shall decrypt the data using the security handler { // This shouldn't occur, because in case the used filter has None value, then default filter // is used and default filter can't have this value. Q_ASSERT(false); break; } case CryptFilterType::V2: // Use file encryption key for RC4 algorithm { std::vector objectEncryptionKey = createV2_ObjectEncryptionKey(reference, filter); decryptedData.resize(data.size()); RC4_KEY key = { }; RC4_set_key(&key, static_cast(objectEncryptionKey.size()), objectEncryptionKey.data()); RC4(&key, data.size(), convertByteArrayToUcharPtr(data), convertByteArrayToUcharPtr(decryptedData)); break; } case CryptFilterType::AESV2: // Use file encryption key for AES algorithm { std::vector objectEncryptionKey = createAESV2_ObjectEncryptionKey(reference); // For AES algorithm, always use 16 bytes key (128 bit encryption mode) AES_KEY key = { }; AES_set_decrypt_key(objectEncryptionKey.data(), static_cast(objectEncryptionKey.size()) * 8, &key); AES_data aes_data = prepareAES_data(data); if (!aes_data.paddedData.isEmpty()) { decryptedData.resize(aes_data.paddedData.size()); AES_cbc_encrypt(convertByteArrayToUcharPtr(aes_data.paddedData), convertByteArrayToUcharPtr(decryptedData), aes_data.paddedData.length(), &key, convertByteArrayToUcharPtr(aes_data.initializationVector), AES_DECRYPT); decryptedData = removeAES_padding(decryptedData); } break; } case CryptFilterType::AESV3: // Use file encryption key for AES 256 bit algorithm { Q_ASSERT(m_authorizationData.fileEncryptionKey.size() == 32); AES_KEY key = { }; AES_set_decrypt_key(convertByteArrayToUcharPtr(m_authorizationData.fileEncryptionKey), static_cast(m_authorizationData.fileEncryptionKey.size()) * 8, &key); AES_data aes_data = prepareAES_data(data); if (!aes_data.paddedData.isEmpty()) { decryptedData.resize(aes_data.paddedData.size()); AES_cbc_encrypt(convertByteArrayToUcharPtr(aes_data.paddedData), convertByteArrayToUcharPtr(decryptedData), aes_data.paddedData.length(), &key, convertByteArrayToUcharPtr(aes_data.initializationVector), AES_DECRYPT); decryptedData = removeAES_padding(decryptedData); } break; } case CryptFilterType::Identity: // Don't decrypt anything, use identity function { decryptedData = data; break; } } return decryptedData; } QByteArray PDFStandardOrPublicSecurityHandler::encryptUsingFilter(const QByteArray& data, CryptFilter filter, PDFObjectReference reference) const { QByteArray encryptedData; Q_ASSERT(m_authorizationData.isAuthorized()); struct AES_data { QByteArray initializationVector; QByteArray paddedData; }; auto prepareAES_data = [](const QByteArray& data) { AES_data result; QRandomGenerator randomNumberGenerator = QRandomGenerator::securelySeeded(); result.initializationVector.resize(AES_BLOCK_SIZE); for (int i = 0; i < AES_BLOCK_SIZE; ++i) { result.initializationVector[i] = uint8_t(randomNumberGenerator.generate()); } result.paddedData = data; // Add padding remainder according to the specification int size = data.size(); const int paddingRemainder = AES_BLOCK_SIZE - (size % AES_BLOCK_SIZE); result.initializationVector.reserve(result.initializationVector.size() + paddingRemainder); for (int i = 0; i < paddingRemainder; ++i) { result.paddedData.push_back(paddingRemainder); } return result; }; switch (filter.type) { case CryptFilterType::None: // The application shall encrypt the data using the security handler { // This shouldn't occur, because in case the used filter has None value, then default filter // is used and default filter can't have this value. Q_ASSERT(false); break; } case CryptFilterType::V2: // Use file encryption key for RC4 algorithm { // This algorithm is same as the encrypt algorithm, because RC4 cipher is symmetrical std::vector objectEncryptionKey = createV2_ObjectEncryptionKey(reference, filter); encryptedData.resize(data.size()); RC4_KEY key = { }; RC4_set_key(&key, static_cast(objectEncryptionKey.size()), objectEncryptionKey.data()); RC4(&key, data.size(), convertByteArrayToUcharPtr(data), convertByteArrayToUcharPtr(encryptedData)); break; } case CryptFilterType::AESV2: // Use file encryption key for AES algorithm { std::vector objectEncryptionKey = createAESV2_ObjectEncryptionKey(reference); // For AES algorithm, always use 16 bytes key (128 bit encryption mode) AES_KEY key = { }; AES_set_encrypt_key(objectEncryptionKey.data(), static_cast(objectEncryptionKey.size()) * 8, &key); AES_data aes_data = prepareAES_data(data); if (!aes_data.paddedData.isEmpty()) { QByteArray initializationVectorCopy = aes_data.initializationVector; encryptedData.resize(aes_data.paddedData.size()); AES_cbc_encrypt(convertByteArrayToUcharPtr(aes_data.paddedData), convertByteArrayToUcharPtr(encryptedData), aes_data.paddedData.length(), &key, convertByteArrayToUcharPtr(aes_data.initializationVector), AES_ENCRYPT); encryptedData.prepend(initializationVectorCopy); } break; } case CryptFilterType::AESV3: // Use file encryption key for AES 256 bit algorithm { Q_ASSERT(m_authorizationData.fileEncryptionKey.size() == 32); AES_KEY key = { }; AES_set_encrypt_key(convertByteArrayToUcharPtr(m_authorizationData.fileEncryptionKey), static_cast(m_authorizationData.fileEncryptionKey.size()) * 8, &key); AES_data aes_data = prepareAES_data(data); if (!aes_data.paddedData.isEmpty()) { QByteArray initializationVectorCopy = aes_data.initializationVector; encryptedData.resize(aes_data.paddedData.size()); AES_cbc_encrypt(convertByteArrayToUcharPtr(aes_data.paddedData), convertByteArrayToUcharPtr(encryptedData), aes_data.paddedData.length(), &key, convertByteArrayToUcharPtr(aes_data.initializationVector), AES_ENCRYPT); encryptedData.prepend(initializationVectorCopy); } break; } case CryptFilterType::Identity: // Don't decrypt anything, use identity function { encryptedData = data; break; } } return encryptedData; } QByteArray PDFStandardOrPublicSecurityHandler::decrypt(const QByteArray& data, PDFObjectReference reference, EncryptionScope encryptionScope) const { CryptFilter filter = getCryptFilter(encryptionScope); return decryptUsingFilter(data, filter, reference); } QByteArray PDFStandardOrPublicSecurityHandler::decryptByFilter(const QByteArray& data, const QByteArray& filterName, PDFObjectReference reference) const { auto it = m_cryptFilters.find(filterName); if (it == m_cryptFilters.cend()) { throw PDFException(PDFTranslationContext::tr("Crypt filter '%1' not found.").arg(QString::fromLatin1(filterName))); } return decryptUsingFilter(data, it->second, reference); } CryptFilter PDFStandardOrPublicSecurityHandler::getCryptFilter(EncryptionScope encryptionScope) const { CryptFilter filter = m_filterDefault; switch (encryptionScope) { case EncryptionScope::String: { if (m_filterStrings.type != CryptFilterType::None) { filter = m_filterStrings; } break; } case EncryptionScope::Stream: { if (m_filterStreams.type != CryptFilterType::None) { filter = m_filterStreams; } break; } case EncryptionScope::EmbeddedFile: { if (m_filterEmbeddedFiles.type != CryptFilterType::None) { filter = m_filterEmbeddedFiles; } break; } } return filter; } QByteArray PDFStandardOrPublicSecurityHandler::encrypt(const QByteArray& data, PDFObjectReference reference, EncryptionScope encryptionScope) const { CryptFilter filter = getCryptFilter(encryptionScope); return encryptUsingFilter(data, filter, reference); } QByteArray PDFStandardOrPublicSecurityHandler::encryptByFilter(const QByteArray& data, const QByteArray& filterName, PDFObjectReference reference) const { auto it = m_cryptFilters.find(filterName); if (it == m_cryptFilters.cend()) { throw PDFException(PDFTranslationContext::tr("Crypt filter '%1' not found.").arg(QString::fromLatin1(filterName))); } return encryptUsingFilter(data, it->second, reference); } bool PDFStandardOrPublicSecurityHandler::isUnicodeNonAsciiSpaceCharacter(ushort unicode) { switch (unicode) { case 0x00A0: case 0x1680: case 0x2000: case 0x2001: case 0x2002: case 0x2003: case 0x2004: case 0x2005: case 0x2006: case 0x2007: case 0x2008: case 0x2009: case 0x200A: case 0x200B: case 0x202F: case 0x205F: case 0x3000: return true; default: return false; } } bool PDFStandardOrPublicSecurityHandler::isUnicodeMappedToNothing(ushort unicode) { switch (unicode) { case 0x00AD: case 0x034F: case 0x1806: case 0x180B: case 0x180C: case 0x180D: case 0x200B: case 0x200C: case 0x200D: return true; default: return false; } } QByteArray PDFStandardOrPublicSecurityHandler::adjustPassword(const QString& password, int revision) { QByteArray result; switch (revision) { case 2: case 3: case 4: { // According to the PDF specification, convert string to PDFDocEncoding encoding result = PDFEncoding::convertToEncoding(password, PDFEncoding::Encoding::PDFDoc); break; } case 5: case 6: { // According to the PDF specification, use SASLprep profile for stringprep RFC 4013, please see these websites: // - RFC 4013: https://tools.ietf.org/html/rfc4013 (SASLprep profile for stringprep algorithm) // - RFC 3454: https://tools.ietf.org/html/rfc3454 (stringprep algorithm - preparation of internationalized strings) // // Note: we don't do checks according the RFC 4013, just use the mapping and normalize string in KC QString preparedPassword; preparedPassword.reserve(password.size()); // RFC 4013 Section 2.1, use mapping for (const QChar character : password) { if (isUnicodeMappedToNothing(character.unicode())) { // Mapped to nothing continue; } if (isUnicodeNonAsciiSpaceCharacter(character.unicode())) { // Map to space character preparedPassword += QChar(QChar::Space); } else { preparedPassword += character; } } // RFC 4013, Section 2.2, normalization to KC preparedPassword = preparedPassword.normalized(QString::NormalizationForm_KC); // We don't do other checks. We will transform password to the UTF-8 encoding // and according the PDF specification, we take only first 127 characters. result = preparedPassword.toUtf8().left(127); } default: result = password.toLatin1(); break; } return result; } PDFSecurityHandler* PDFStandardSecurityHandler::clone() const { return new PDFStandardSecurityHandler(*this); } PDFSecurityHandler::AuthorizationResult PDFStandardSecurityHandler::authenticate(const std::function& getPasswordCallback, bool authorizeOwnerOnly) { 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 if (!authorizeOwnerOnly) { QByteArray fileEncryptionKey = createFileEncryptionKey(password); QByteArray U = createEntryValueU_r234(fileEncryptionKey); if (U == m_U) { // We have authorized user access m_authorizationData.authorizationResult = AuthorizationResult::UserAuthorized; m_authorizationData.fileEncryptionKey = fileEncryptionKey; return AuthorizationResult::UserAuthorized; } } break; } case 5: case 6: { UserOwnerData_r6 userData = parseParts(m_U); UserOwnerData_r6 ownerData = parseParts(m_O); auto createHash_r5 = [](const QByteArray& inputData) { QByteArray result(SHA256_DIGEST_LENGTH, char(0)); SHA256(convertByteArrayToUcharPtr(inputData), inputData.size(), convertByteArrayToUcharPtr(result)); return result; }; auto createHash_r56 = [this, &createHash_r5](const QByteArray& input, const QByteArray& inputPassword, bool useUserKey) { return (m_R == 5) ? createHash_r5(input) : createHash_r6(input, inputPassword, useUserKey); }; // Try to authorize owner password { QByteArray inputData = password + ownerData.validationSalt + m_U; QByteArray hash = createHash_r56(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_r56(fileEncryptionKeyInputData, password, true); Q_ASSERT(fileEncryptionDecryptionKey.size() == 32); AES_KEY key = { }; AES_set_decrypt_key(convertByteArrayToUcharPtr(fileEncryptionDecryptionKey), fileEncryptionDecryptionKey.size() * 8, &key); unsigned char aesInitializationVector[AES_BLOCK_SIZE] = { }; m_authorizationData.fileEncryptionKey.resize(m_OE.size()); AES_cbc_encrypt(convertByteArrayToUcharPtr(m_OE), convertByteArrayToUcharPtr(m_authorizationData.fileEncryptionKey), m_OE.size(), &key, aesInitializationVector, AES_DECRYPT); m_authorizationData.authorizationResult = AuthorizationResult::OwnerAuthorized; } } // Try to authorize user password if (!m_authorizationData.isAuthorized() && !authorizeOwnerOnly) { QByteArray inputData = password + userData.validationSalt; QByteArray hash = createHash_r56(inputData, password, false); if (hash == userData.hash) { QByteArray fileEncryptionKeyInputData = password + userData.keySalt; QByteArray fileEncryptionDecryptionKey = createHash_r56(fileEncryptionKeyInputData, password, false); Q_ASSERT(fileEncryptionDecryptionKey.size() == 32); AES_KEY key = { }; AES_set_decrypt_key(convertByteArrayToUcharPtr(fileEncryptionDecryptionKey), fileEncryptionDecryptionKey.size() * 8, &key); unsigned char aesInitializationVector[AES_BLOCK_SIZE] = { }; m_authorizationData.fileEncryptionKey.resize(m_UE.size()); AES_cbc_encrypt(convertByteArrayToUcharPtr(m_UE), convertByteArrayToUcharPtr(m_authorizationData.fileEncryptionKey), m_UE.size(), &key, aesInitializationVector, AES_DECRYPT); // We have authorized user access m_authorizationData.authorizationResult = AuthorizationResult::UserAuthorized; } } // Stop, if we authorized the document usage if (m_authorizationData.isAuthorized()) { // According the PDF specification, we must also check, if flags are not manipulated. Q_ASSERT(m_Perms.size() == AES_BLOCK_SIZE); AES_KEY key = { }; AES_set_decrypt_key(convertByteArrayToUcharPtr(m_authorizationData.fileEncryptionKey), m_authorizationData.fileEncryptionKey.size() * 8, &key); QByteArray decodedPerms(m_Perms.size(), char(0)); AES_ecb_encrypt(convertByteArrayToUcharPtr(m_Perms), convertByteArrayToUcharPtr(decodedPerms), &key, AES_DECRYPT); // 1) Checks, if bytes 9, 10, 11 are 'a', 'd', 'b' if (decodedPerms[9] != 'a' || decodedPerms[10] != 'd' || decodedPerms[11] != 'b') { throw PDFException(PDFTranslationContext::tr("Permissions entry in the Encryption dictionary is invalid.")); } // 2) Verify, that bytes 0-3 are valid permissions entry const uint32_t permissions = qFromLittleEndian(*reinterpret_cast(decodedPerms.data())); if (permissions != m_permissions) { throw PDFException(PDFTranslationContext::tr("Security permissions are manipulated. Can't open the document.")); } // 3) Verify, that byte 8 is 'T' or 'F' and is equal to EncryptMetadata entry if (decodedPerms[8] != 'T' && decodedPerms[8] != 'F') { throw PDFException(PDFTranslationContext::tr("Security permissions are manipulated. Can't open the document.")); } if ((decodedPerms[8] == 'T') != m_encryptMetadata) { throw PDFException(PDFTranslationContext::tr("Security permissions are manipulated. Can't open the document.")); } return m_authorizationData.authorizationResult; } break; } default: return AuthorizationResult::Failed; } password = adjustPassword(getPasswordCallback(&passwordObtained), m_R); } return AuthorizationResult::Cancelled; } PDFObject PDFStandardSecurityHandler::createEncryptionDictionaryObject() const { PDFObjectFactory factory; factory.beginDictionary(); fillEncryptionDictionary(factory); factory.beginDictionaryItem("Filter"); factory << WrapName("Standard"); factory.endDictionaryItem(); factory.beginDictionaryItem("R"); factory << PDFInteger(m_R); factory.endDictionaryItem(); factory.beginDictionaryItem("O"); factory << WrapString(m_O); factory.endDictionaryItem(); factory.beginDictionaryItem("U"); factory << WrapString(m_U); factory.endDictionaryItem(); if (m_R == 6) { factory.beginDictionaryItem("OE"); factory << WrapString(m_OE); factory.endDictionaryItem(); factory.beginDictionaryItem("UE"); factory << WrapString(m_UE); factory.endDictionaryItem(); } factory.beginDictionaryItem("P"); factory << PDFInteger(int32_t(m_permissions)); factory.endDictionaryItem(); if (m_R == 6) { factory.beginDictionaryItem("Perms"); factory << WrapString(m_Perms); factory.endDictionaryItem(); } if (m_V == 4 || m_V == 5) { factory.beginDictionaryItem("EncryptMetadata"); factory << m_encryptMetadata; factory.endDictionaryItem(); } factory.endDictionary(); return factory.takeObject(); } 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 PDFException(PDFTranslationContext::tr("Encryption key length (%1) exceeded maximal value of %2.").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 5: case 6: { // This function must not be called with revision 5/6 Q_ASSERT(false); break; } default: { throw PDFException(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(), convertByteArrayToUcharPtr(fileEncryptionKey)); result.resize(static_cast(PDFPasswordPadding.size())); RC4(&key, PDFPasswordPadding.size(), PDFPasswordPadding.data(), convertByteArrayToUcharPtr(result)); 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(), convertByteArrayToUcharPtr(fileEncryptionKey)); std::array encryptedHash = { }; std::array targetBuffer = { }; RC4(&key, hash.size(), hash.data(), 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(), convertByteArrayToUcharPtr(transformedKey)); RC4(&key, encryptedHash.size(), encryptedHash.data(), targetBuffer.data()); encryptedHash = targetBuffer; } // 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; result.detach(); if (result.size() != 32) { // In case of error, we resize it to correct size. We can't assume, that m_U has correct length. result.resize(32); } std::copy_n(encryptedHash.begin(), encryptedHash.size(), result.begin()); break; } default: { throw PDFException(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 PDFException(PDFTranslationContext::tr("Encryption key length (%1) exceeded maximal value of %2.").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, hash.data()); result.resize(m_O.size()); RC4(&key, m_O.size(), convertByteArrayToUcharPtr(m_O), convertByteArrayToUcharPtr(result)); 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(), convertByteArrayToUcharPtr(transformedKey)); RC4(&key, buffer.size(), convertByteArrayToUcharPtr(buffer), convertByteArrayToUcharPtr(buffer)); } result = buffer; break; } default: { throw PDFException(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(convertByteArrayToUcharPtr(input), 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, 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; } PDFSecurityHandlerPointer PDFSecurityHandlerFactory::createSecurityHandler(const SecuritySettings& settings) { if (settings.algorithm == Algorithm::None) { return PDFSecurityHandlerPointer(new PDFNoneSecurityHandler); } // Jakub Melka: create standard security handler, with given settings PDFStandardSecurityHandler* handler = new PDFStandardSecurityHandler(); handler->m_ID = settings.id; const bool isEncryptingEmbeddedFilesOnly = settings.encryptContents == EncryptContents::EmbeddedFiles; switch (settings.algorithm) { case RC4: { handler->m_V = 4; handler->m_keyLength = 128; CryptFilter defaultFilter; defaultFilter.type = CryptFilterType::V2; defaultFilter.authEvent = !isEncryptingEmbeddedFilesOnly ? AuthEvent::DocOpen : AuthEvent::EFOpen; defaultFilter.keyLength = handler->m_keyLength / 8; handler->m_filterDefault = defaultFilter; break; } case AES_128: { handler->m_V = 4; handler->m_keyLength = 128; CryptFilter defaultFilter; defaultFilter.type = CryptFilterType::AESV2; defaultFilter.authEvent = !isEncryptingEmbeddedFilesOnly ? AuthEvent::DocOpen : AuthEvent::EFOpen; defaultFilter.keyLength = handler->m_keyLength / 8; handler->m_filterDefault = defaultFilter; break; } case AES_256: { handler->m_V = 5; handler->m_keyLength = 256; CryptFilter defaultFilter; defaultFilter.type = CryptFilterType::AESV3; defaultFilter.authEvent = !isEncryptingEmbeddedFilesOnly ? AuthEvent::DocOpen : AuthEvent::EFOpen; defaultFilter.keyLength = handler->m_keyLength / 8; handler->m_filterDefault = defaultFilter; break; } default: Q_ASSERT(false); break; } CryptFilter identityFilter; identityFilter.type = CryptFilterType::Identity; switch (settings.encryptContents) { case All: handler->m_filterStrings = handler->m_filterDefault; handler->m_filterStreams = handler->m_filterDefault; handler->m_filterEmbeddedFiles = handler->m_filterDefault; handler->m_encryptMetadata = true; break; case AllExceptMetadata: handler->m_filterStrings = handler->m_filterDefault; handler->m_filterStreams = handler->m_filterDefault; handler->m_filterEmbeddedFiles = handler->m_filterDefault; handler->m_encryptMetadata = false; break; case EmbeddedFiles: handler->m_filterStrings = identityFilter; handler->m_filterStreams = identityFilter; handler->m_filterEmbeddedFiles = handler->m_filterDefault; handler->m_encryptMetadata = false; break; default: Q_ASSERT(false); break; } handler->m_cryptFilters["StdCF"] = handler->m_filterDefault; handler->m_R = getRevisionFromAlgorithm(settings.algorithm); handler->m_permissions = settings.permissions | 0xFFFFF000; QByteArray adjustedOwnerPassword = handler->adjustPassword(settings.ownerPassword, handler->m_R); QByteArray adjustedUserPassword = handler->adjustPassword(settings.userPassword, handler->m_R); // Generate encryption entries switch (handler->m_R) { case 2: case 3: case 4: { // Trick for computing "O" entry for revisions 2,3,4: in O entry, there is stored // user password encrypted by owner password. Because RC4 cipher is symmetric, we // can store user password in "O" entry and then use standard function to retrieve // user password, which in fact will be encrypted user password. std::array paddedUserPasswordArray = handler->createPaddedPassword32(adjustedUserPassword); QByteArray paddedUserPassword; paddedUserPassword.resize(int(paddedUserPasswordArray.size())); std::copy(paddedUserPasswordArray.cbegin(), paddedUserPasswordArray.cend(), paddedUserPassword.data()); handler->m_O = paddedUserPassword; QByteArray entryO = handler->createUserPasswordFromOwnerPassword(adjustedOwnerPassword); handler->m_O = entryO; Q_ASSERT(handler->createUserPasswordFromOwnerPassword(adjustedOwnerPassword) == paddedUserPassword); handler->m_U.resize(32); QRandomGenerator randomNumberGenerator = QRandomGenerator::securelySeeded(); for (int i = 0; i < handler->m_U.size(); ++i) { handler->m_U[i] = char(randomNumberGenerator.generate()); } QByteArray fileEncryptionKey = handler->createFileEncryptionKey(paddedUserPassword); QByteArray U = handler->createEntryValueU_r234(fileEncryptionKey); handler->m_U = U; break; } case 6: { PDFStandardSecurityHandler::UserOwnerData_r6 userData; PDFStandardSecurityHandler::UserOwnerData_r6 ownerData; QRandomGenerator randomNumberGenerator = QRandomGenerator::securelySeeded(); // Generate file encryption key handler->m_authorizationData.fileEncryptionKey = generateRandomByteArray(randomNumberGenerator, 32); handler->m_authorizationData.authorizationResult = PDFSecurityHandler::AuthorizationResult::OwnerAuthorized; // Compute m_U entry userData.keySalt = generateRandomByteArray(randomNumberGenerator, 8); userData.validationSalt = generateRandomByteArray(randomNumberGenerator, 8); userData.hash = handler->createHash_r6(adjustedUserPassword + userData.validationSalt, adjustedUserPassword, false); handler->m_U = userData.hash + userData.validationSalt + userData.keySalt; // Compute m_UE entry QByteArray userFileEncryptionKeyInputData = adjustedUserPassword + userData.keySalt; QByteArray userFileEncryptionKey = handler->createHash_r6(userFileEncryptionKeyInputData, adjustedUserPassword, false); Q_ASSERT(userFileEncryptionKey.size() == 32); AES_KEY userKey = { }; AES_set_encrypt_key(convertByteArrayToUcharPtr(userFileEncryptionKey), userFileEncryptionKey.size() * 8, &userKey); unsigned char aesUserInitializationVector[AES_BLOCK_SIZE] = { }; handler->m_UE.resize(handler->m_authorizationData.fileEncryptionKey.size()); unsigned char* userInputBuffer = convertByteArrayToUcharPtr(handler->m_authorizationData.fileEncryptionKey); unsigned char* userTargetBuffer = convertByteArrayToUcharPtr(handler->m_UE); AES_cbc_encrypt(userInputBuffer, userTargetBuffer, handler->m_UE.size(), &userKey, aesUserInitializationVector, AES_ENCRYPT); // Compute m_O entry ownerData.keySalt = generateRandomByteArray(randomNumberGenerator, 8); ownerData.validationSalt = generateRandomByteArray(randomNumberGenerator, 8); ownerData.hash = handler->createHash_r6(adjustedOwnerPassword + ownerData.validationSalt + handler->m_U, adjustedOwnerPassword, true); handler->m_O = ownerData.hash + ownerData.validationSalt + ownerData.keySalt; // Compute m_OE entry QByteArray ownerFileEncryptionKeyInputData = adjustedOwnerPassword + ownerData.keySalt + handler->m_U; QByteArray ownerFileEncryptionKey = handler->createHash_r6(ownerFileEncryptionKeyInputData, adjustedOwnerPassword, true); AES_KEY ownerKey = { }; AES_set_encrypt_key(convertByteArrayToUcharPtr(ownerFileEncryptionKey), ownerFileEncryptionKey.size() * 8, &ownerKey); unsigned char aesOwnerInitializationVector[AES_BLOCK_SIZE] = { }; handler->m_OE.resize(handler->m_authorizationData.fileEncryptionKey.size()); unsigned char* ownerInputBuffer = convertByteArrayToUcharPtr(handler->m_authorizationData.fileEncryptionKey); unsigned char* ownerTargetBuffer = convertByteArrayToUcharPtr(handler->m_OE); AES_cbc_encrypt(ownerInputBuffer, ownerTargetBuffer, handler->m_OE.size(), &ownerKey, aesOwnerInitializationVector, AES_ENCRYPT); // Perms entry handler->m_Perms = QByteArray(AES_BLOCK_SIZE, char(0)); unsigned char* permsData = convertByteArrayToUcharPtr(handler->m_Perms); permsData[0] = handler->m_permissions & 0xFF; permsData[1] = (handler->m_permissions >> 8) & 0xFF; permsData[2] = (handler->m_permissions >> 16) & 0xFF; permsData[3] = (handler->m_permissions >> 24) & 0xFF; permsData[4] = 0xFF; permsData[5] = 0xFF; permsData[6] = 0xFF; permsData[7] = 0xFF; permsData[8] = handler->m_encryptMetadata ? 'T' : 'F'; permsData[9] = 'a'; permsData[10] = 'd'; permsData[11] = 'b'; permsData[12] = randomNumberGenerator.generate() & 0xFF; permsData[13] = randomNumberGenerator.generate() & 0xFF; permsData[14] = randomNumberGenerator.generate() & 0xFF; permsData[15] = randomNumberGenerator.generate() & 0xFF; Q_ASSERT(handler->m_Perms.size() == AES_BLOCK_SIZE); AES_KEY key = { }; AES_set_encrypt_key(convertByteArrayToUcharPtr(handler->m_authorizationData.fileEncryptionKey), handler->m_authorizationData.fileEncryptionKey.size() * 8, &key); AES_ecb_encrypt(convertByteArrayToUcharPtr(handler->m_Perms), convertByteArrayToUcharPtr(handler->m_Perms), &key, AES_ENCRYPT); break; } default: { Q_ASSERT(false); break; } } bool firstTry = true; handler->authenticate([&settings, &firstTry](bool* b) { *b = firstTry; firstTry = false; return settings.ownerPassword; }, true); Q_ASSERT(handler->getAuthorizationResult() == PDFSecurityHandler::AuthorizationResult::OwnerAuthorized); return PDFSecurityHandlerPointer(handler); } int PDFSecurityHandlerFactory::getPasswordOptimalEntropy() { return 128; } int PDFSecurityHandlerFactory::getPasswordEntropy(const QString& password, Algorithm algorithm) { if (algorithm == None) { return 0; } QByteArray adjustedPassword = PDFStandardSecurityHandler::adjustPassword(password, getRevisionFromAlgorithm(algorithm)); if (adjustedPassword.isEmpty()) { return 0; } const int length = adjustedPassword.length(); std::sort(adjustedPassword.begin(), adjustedPassword.end()); int charCount = 0; char lastChar = adjustedPassword.front(); PDFReal entropy = 0.0; for (int i = 0; i < length; ++i) { const char currentChar = adjustedPassword[i]; if (currentChar == lastChar) { ++charCount; } else { const PDFReal probability = PDFReal(charCount) / PDFReal(length); entropy += -probability * std::log2(probability); charCount = 1; lastChar = currentChar; } } // Jakub Melka: last character const PDFReal probability = PDFReal(charCount) / PDFReal(length); entropy += -probability * std::log2(probability); return entropy * length; } int PDFSecurityHandlerFactory::getRevisionFromAlgorithm(Algorithm algorithm) { switch (algorithm) { case None: return 0; case RC4: return 4; case AES_128: return 4; case AES_256: return 6; default: Q_ASSERT(false); break; } return 0; } QByteArray PDFSecurityHandlerFactory::generateRandomByteArray(QRandomGenerator& generator, int size) { QByteArray ba; ba.reserve(size); for (int i = 0; i < size; ++i) { ba.push_back(static_cast(generator.generate())); } return ba; } bool PDFSecurityHandlerFactory::validate(const SecuritySettings& settings, QString* errorMessage) { switch (settings.algorithm) { case pdf::PDFSecurityHandlerFactory::RC4: case pdf::PDFSecurityHandlerFactory::AES_128: { QString invalidCharacters; if (!PDFEncoding::canConvertToEncoding(settings.userPassword, PDFEncoding::Encoding::PDFDoc, &invalidCharacters)) { if (errorMessage) { Q_ASSERT(!invalidCharacters.isEmpty()); *errorMessage = tr("User password contains invalid characters: %1.").arg(invalidCharacters); } return false; } if (!PDFEncoding::canConvertToEncoding(settings.ownerPassword, PDFEncoding::Encoding::PDFDoc, &invalidCharacters)) { if (errorMessage) { Q_ASSERT(!invalidCharacters.isEmpty()); *errorMessage = tr("Owner password contains invalid characters: %1.").arg(invalidCharacters); } return false; } break; } case pdf::PDFSecurityHandlerFactory::None: case pdf::PDFSecurityHandlerFactory::AES_256: break; default: Q_ASSERT(false); break; } return true; } PDFSecurityHandler* PDFPublicKeySecurityHandler::clone() const { return new PDFPublicKeySecurityHandler(*this); } PDFSecurityHandler::AuthorizationResult PDFPublicKeySecurityHandler::authenticate(const std::function& getPasswordCallback, bool authorizeOwnerOnly) { // Clear the authorization data m_authorizationData = AuthorizationData(); if (authorizeOwnerOnly) { return AuthorizationResult::Failed; } switch (m_keyLength) { case 128: case 256: break; default: return AuthorizationResult::Failed; } bool passwordObtained = true; constexpr int revision = 0; QByteArray password = adjustPassword(getPasswordCallback(&passwordObtained), revision); QFileInfoList certificates = PDFCertificateManager::getCertificates(); if (certificates.isEmpty()) { return AuthorizationResult::Failed; } std::vector> recipients; for (const QByteArray& recipient : m_filterDefault.recipients) { const unsigned char* data = convertByteArrayToUcharPtr(recipient); if (PKCS7* pkcs7 = d2i_PKCS7(nullptr, &data, recipient.size())) { recipients.emplace_back(pkcs7, PKCS7_free); } } while (passwordObtained) { // We will iterate trough all certificates for (const QFileInfo& certificateFileInfo : certificates) { if (!PDFCertificateManager::isCertificateValid(certificateFileInfo.absoluteFilePath(), password)) { continue; } QFile file(certificateFileInfo.absoluteFilePath()); if (file.open(QFile::ReadOnly)) { QByteArray data = file.readAll(); file.close(); openssl_ptr pksBuffer(BIO_new(BIO_s_mem()), &BIO_free_all); BIO_write(pksBuffer.get(), data.constData(), data.length()); openssl_ptr pkcs12(d2i_PKCS12_bio(pksBuffer.get(), nullptr), &PKCS12_free); if (pkcs12) { const char* passwordPointer = nullptr; if (!password.isEmpty()) { passwordPointer = password.constData(); } EVP_PKEY* keyPtr = nullptr; X509* certificatePtr = nullptr; STACK_OF(X509)* certificatesPtr = nullptr; // Parse PKCS12 with password bool isParsed = PKCS12_parse(pkcs12.get(), passwordPointer, &keyPtr, &certificatePtr, &certificatesPtr) == 1; if (!isParsed) { continue; } openssl_ptr key(keyPtr, EVP_PKEY_free); openssl_ptr certificate(certificatePtr, X509_free); openssl_ptr certificates(certificatesPtr, sk_X509_free); for (const auto& recipientItem : recipients) { PKCS7* pkcs7 = recipientItem.get(); openssl_ptr dataBuffer(BIO_new(BIO_s_mem()), BIO_free_all); if (PKCS7_decrypt(pkcs7, keyPtr, certificatePtr, dataBuffer.get(), 0) == 1) { BUF_MEM* memoryBuffer = nullptr; BIO_get_mem_ptr(dataBuffer.get(), &memoryBuffer); // Acc. to chapter 7.6.5.3 - decrypted data QByteArray decryptedData(memoryBuffer->data, int(memoryBuffer->length)); // Calculate file encryption key EVP_MD_CTX* context = EVP_MD_CTX_new(); Q_ASSERT(context); switch (m_keyLength) { case 128: EVP_DigestInit(context, EVP_sha1()); break; case 256: EVP_DigestInit(context, EVP_sha256()); break; default: Q_ASSERT(false); EVP_DigestInit(context, EVP_sha256()); break; } QByteArray seed = decryptedData.left(20); // 7.6.5.3 a) EVP_DigestUpdate(context, seed.constData(), seed.size()); // 7.6.5.3 b) for (const QByteArray& recipient : m_filterDefault.recipients) { EVP_DigestUpdate(context, recipient.constData(), recipient.size()); } // 7.6.5.3 c) if (!isMetadataEncrypted()) { constexpr uint32_t value = 0xFFFFFFFF; EVP_DigestUpdate(context, &value, sizeof(value)); } unsigned int size = EVP_MD_size(EVP_MD_CTX_md(context)); QByteArray digestBuffer(size, char()); EVP_DigestFinal_ex(context, convertByteArrayToUcharPtr(digestBuffer), &size); EVP_MD_CTX_free(context); m_authorizationData.fileEncryptionKey = digestBuffer.left(m_keyLength / 8); m_authorizationData.authorizationResult = AuthorizationResult::UserAuthorized; return AuthorizationResult::UserAuthorized; } } } } } password = adjustPassword(getPasswordCallback(&passwordObtained), revision); } return AuthorizationResult::Cancelled; } bool PDFPublicKeySecurityHandler::isMetadataEncrypted() const { return true; } bool PDFPublicKeySecurityHandler::isAllowed(Permission permission) const { return false; } PDFObject PDFPublicKeySecurityHandler::createEncryptionDictionaryObject() const { return PDFObject(); } } // namespace pdf