From e001adc65b30dd5ae52fab77d182cdc749797d23 Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Sun, 30 May 2021 15:37:06 +0200 Subject: [PATCH] Encryption bugfixing (RC4) --- Pdf4QtLib/sources/pdfdocument.cpp | 20 +++++++++ Pdf4QtLib/sources/pdfdocument.h | 4 ++ Pdf4QtLib/sources/pdfdocumentbuilder.cpp | 33 +++++++++++--- Pdf4QtLib/sources/pdfdocumentbuilder.h | 13 ++++-- Pdf4QtLib/sources/pdfdocumentwriter.cpp | 14 +++++- Pdf4QtLib/sources/pdfsecurityhandler.cpp | 26 ++++++----- Pdf4QtLib/sources/pdfsecurityhandler.h | 3 +- Pdf4QtViewer/pdfencryptionsettingsdialog.cpp | 6 ++- Pdf4QtViewer/pdfencryptionsettingsdialog.h | 5 ++- Pdf4QtViewer/pdfprogramcontroller.cpp | 46 +++++++++++++++----- generated_code_definition.xml | 26 +---------- 11 files changed, 132 insertions(+), 64 deletions(-) diff --git a/Pdf4QtLib/sources/pdfdocument.cpp b/Pdf4QtLib/sources/pdfdocument.cpp index 957fc60..54c4aaf 100644 --- a/Pdf4QtLib/sources/pdfdocument.cpp +++ b/Pdf4QtLib/sources/pdfdocument.cpp @@ -43,6 +43,26 @@ bool PDFDocument::operator==(const PDFDocument& other) const return m_pdfObjectStorage == other.m_pdfObjectStorage; } +QByteArray PDFDocument::getIdPart(size_t index) const +{ + QByteArray id; + const PDFObject& idArrayObject = getTrailerDictionary()->get("ID"); + if (idArrayObject.isArray()) + { + const PDFArray* idArray = idArrayObject.getArray(); + if (idArray->getCount() > index) + { + const PDFObject& idArrayItem = idArray->getItem(index); + if (idArrayItem.isString()) + { + id = idArrayItem.getString(); + } + } + } + + return id; +} + QByteArray PDFDocument::getDecodedStream(const PDFStream* stream) const { return m_pdfObjectStorage.getDecodedStream(stream); diff --git a/Pdf4QtLib/sources/pdfdocument.h b/Pdf4QtLib/sources/pdfdocument.h index f261856..26edce1 100644 --- a/Pdf4QtLib/sources/pdfdocument.h +++ b/Pdf4QtLib/sources/pdfdocument.h @@ -419,6 +419,10 @@ public: /// Returns info about the document (title, author, etc.) const PDFDocumentInfo* getInfo() const { return &m_info; } + /// Returns document id part with given index. If index is invalid, + /// then empty id is returned. + QByteArray getIdPart(size_t index) const; + /// If object is reference, the dereference attempt is performed /// and object is returned. If it is not a reference, then self /// is returned. If dereference attempt fails, then null object diff --git a/Pdf4QtLib/sources/pdfdocumentbuilder.cpp b/Pdf4QtLib/sources/pdfdocumentbuilder.cpp index 78eb7be..670f62e 100644 --- a/Pdf4QtLib/sources/pdfdocumentbuilder.cpp +++ b/Pdf4QtLib/sources/pdfdocumentbuilder.cpp @@ -1143,6 +1143,31 @@ void PDFDocumentBuilder::copyAnnotation(PDFObjectReference pageReference, PDFObj void PDFDocumentBuilder::setSecurityHandler(PDFSecurityHandlerPointer handler) { + if (!handler) + { + handler.reset(new PDFNoneSecurityHandler()); + } + + PDFObjectFactory objectBuilder; + + objectBuilder.beginDictionary(); + objectBuilder.beginDictionaryItem("Encrypt"); + + PDFObject encryptionDictionaryObject = handler->createEncryptionDictionaryObject(); + Q_ASSERT(!encryptionDictionaryObject.isReference()); + + if (!encryptionDictionaryObject.isNull()) + { + encryptionDictionaryObject = PDFObject::createReference(addObject(encryptionDictionaryObject)); + } + + objectBuilder << encryptionDictionaryObject; + + objectBuilder.endDictionaryItem(); + objectBuilder.endDictionary(); + PDFObject updatedTrailerDictionary = objectBuilder.takeObject(); + m_storage.updateTrailerDictionary(qMove(updatedTrailerDictionary)); + m_storage.setSecurityHandler(qMove(handler)); } @@ -4297,13 +4322,7 @@ void PDFDocumentBuilder::removeEncryption() { PDFObjectFactory objectBuilder; - objectBuilder.beginDictionary(); - objectBuilder.beginDictionaryItem("Encrypt"); - objectBuilder << PDFObject(); - objectBuilder.endDictionaryItem(); - objectBuilder.endDictionary(); - PDFObject updatedTrailerDictionary = objectBuilder.takeObject(); - m_storage.updateTrailerDictionary(qMove(updatedTrailerDictionary)); + setSecurityHandler(nullptr); } diff --git a/Pdf4QtLib/sources/pdfdocumentbuilder.h b/Pdf4QtLib/sources/pdfdocumentbuilder.h index ac49887..7543ade 100644 --- a/Pdf4QtLib/sources/pdfdocumentbuilder.h +++ b/Pdf4QtLib/sources/pdfdocumentbuilder.h @@ -72,6 +72,12 @@ struct WrapString } + WrapString(const QByteArray& byteArray) : + string(byteArray) + { + + } + QByteArray string; }; @@ -421,9 +427,10 @@ public: /// \param annotationReference Annotation reference void copyAnnotation(PDFObjectReference pageReference, PDFObjectReference annotationReference); - /// Sets security handler to the object storage. Trailer dictionary is not - /// updated and so must be updated manually. - /// \param handler New security handler + /// Sets security handler to the object storage. Trailer dictionary is also + /// updated, so it is not needed to update it. Pass nullptr as handler to remove + /// security. + /// \param handler New security handler, or nullptr void setSecurityHandler(PDFSecurityHandlerPointer handler); /* START GENERATED CODE */ diff --git a/Pdf4QtLib/sources/pdfdocumentwriter.cpp b/Pdf4QtLib/sources/pdfdocumentwriter.cpp index 9371075..e22a0a4 100644 --- a/Pdf4QtLib/sources/pdfdocumentwriter.cpp +++ b/Pdf4QtLib/sources/pdfdocumentwriter.cpp @@ -319,8 +319,18 @@ PDFOperationResult PDFDocumentWriter::write(QIODevice* device, const PDFDocument // Jakub Melka: Adjust trailer dictionary, to be really dictionary, not a stream PDFDictionary trailerDictionary = *document->getTrailerDictionary(); - trailerDictionary.removeEntry("XRefStm"); - PDFObject trailerDictionaryObject = PDFObject::createDictionary(std::make_shared(qMove(trailerDictionary))); + PDFDictionary newTrailerDictionary; + + for (const char* entry : { "Size", "Root", "Encrypt", "Info", "ID"}) + { + PDFObject object = trailerDictionary.get(entry); + if (!object.isNull()) + { + newTrailerDictionary.addEntry(PDFInplaceOrMemoryString(entry), qMove(object)); + } + } + + PDFObject trailerDictionaryObject = PDFObject::createDictionary(std::make_shared(qMove(newTrailerDictionary))); device->write("trailer"); writeCRLF(device); diff --git a/Pdf4QtLib/sources/pdfsecurityhandler.cpp b/Pdf4QtLib/sources/pdfsecurityhandler.cpp index 0a86a79..2d02ce8 100644 --- a/Pdf4QtLib/sources/pdfsecurityhandler.cpp +++ b/Pdf4QtLib/sources/pdfsecurityhandler.cpp @@ -509,7 +509,7 @@ PDFSecurityHandlerPointer PDFSecurityHandler::createSecurityHandler(const PDFObj return PDFSecurityHandlerPointer(new PDFStandardSecurityHandler(qMove(handler))); } -void PDFSecurityHandler::fillEncryptionDictionary(PDFObjectFactory& factory) +void PDFSecurityHandler::fillEncryptionDictionary(PDFObjectFactory& factory) const { factory.beginDictionaryItem("V"); factory << PDFInteger(m_V); @@ -621,15 +621,15 @@ void PDFSecurityHandler::fillEncryptionDictionary(PDFObjectFactory& factory) // Store StmF, StrF, EFF factory.beginDictionaryItem("StmF"); - factory << stmfName; + factory << WrapName(stmfName); factory.endDictionaryItem(); factory.beginDictionaryItem("StrF"); - factory << strfName; + factory << WrapName(strfName); factory.endDictionaryItem(); factory.beginDictionaryItem("EFF"); - factory << effName; + factory << WrapName(effName); factory.endDictionaryItem(); } } @@ -1141,6 +1141,8 @@ PDFObject PDFStandardSecurityHandler::createEncryptionDictionaryObject() const factory.beginDictionary(); + fillEncryptionDictionary(factory); + factory.beginDictionaryItem("Filter"); factory << WrapName("Standard"); factory.endDictionaryItem(); @@ -1150,21 +1152,21 @@ PDFObject PDFStandardSecurityHandler::createEncryptionDictionaryObject() const factory.endDictionaryItem(); factory.beginDictionaryItem("O"); - factory << m_O; + factory << WrapString(m_O); factory.endDictionaryItem(); factory.beginDictionaryItem("U"); - factory << m_U; + factory << WrapString(m_U); factory.endDictionaryItem(); if (m_R == 6) { factory.beginDictionaryItem("OE"); - factory << m_OE; + factory << WrapString(m_OE); factory.endDictionaryItem(); factory.beginDictionaryItem("UE"); - factory << m_UE; + factory << WrapString(m_UE); factory.endDictionaryItem(); } @@ -1175,7 +1177,7 @@ PDFObject PDFStandardSecurityHandler::createEncryptionDictionaryObject() const if (m_R == 6) { factory.beginDictionaryItem("Perms"); - factory << m_Perms; + factory << WrapString(m_Perms); factory.endDictionaryItem(); } @@ -1689,6 +1691,7 @@ PDFSecurityHandlerPointer PDFSecurityHandlerFactory::createSecurityHandler(const // 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; @@ -1820,7 +1823,8 @@ PDFSecurityHandlerPointer PDFSecurityHandlerFactory::createSecurityHandler(const } } - handler->authenticate([&settings](bool* b) { *b = false; return settings.ownerPassword; }, true); + 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); } @@ -1884,7 +1888,7 @@ int PDFSecurityHandlerFactory::getRevisionFromAlgorithm(Algorithm algorithm) return 0; case RC4: - return 3; + return 4; case AES_128: return 4; diff --git a/Pdf4QtLib/sources/pdfsecurityhandler.h b/Pdf4QtLib/sources/pdfsecurityhandler.h index 8f586c1..d469359 100644 --- a/Pdf4QtLib/sources/pdfsecurityhandler.h +++ b/Pdf4QtLib/sources/pdfsecurityhandler.h @@ -199,7 +199,7 @@ protected: /// Fills encryption dictionary with basic data /// \param factory Factory - void fillEncryptionDictionary(PDFObjectFactory& factory); + void fillEncryptionDictionary(PDFObjectFactory& factory) const; /// Version of the encryption, shall be a number from 1 to 5, according the /// PDF specification. Other values are invalid. @@ -404,6 +404,7 @@ public: QString userPassword; QString ownerPassword; uint32_t permissions = 0; + QByteArray id; }; /// Creates security handler based on given settings. If security handler cannot diff --git a/Pdf4QtViewer/pdfencryptionsettingsdialog.cpp b/Pdf4QtViewer/pdfencryptionsettingsdialog.cpp index db15783..3ddd0a5 100644 --- a/Pdf4QtViewer/pdfencryptionsettingsdialog.cpp +++ b/Pdf4QtViewer/pdfencryptionsettingsdialog.cpp @@ -24,10 +24,11 @@ namespace pdfviewer { -PDFEncryptionSettingsDialog::PDFEncryptionSettingsDialog(QWidget* parent) : +PDFEncryptionSettingsDialog::PDFEncryptionSettingsDialog(QByteArray documentId, QWidget* parent) : QDialog(parent), ui(new Ui::PDFEncryptionSettingsDialog), - m_isUpdatingUi(false) + m_isUpdatingUi(false), + m_documentId(documentId) { ui->setupUi(this); @@ -175,6 +176,7 @@ void PDFEncryptionSettingsDialog::accept() encryptContents = pdf::PDFSecurityHandlerFactory::EmbeddedFiles; } + settings.id = m_documentId; settings.algorithm = static_cast(ui->algorithmComboBox->currentData().toInt()); settings.encryptContents = encryptContents; settings.userPassword = ui->userPasswordEdit->text(); diff --git a/Pdf4QtViewer/pdfencryptionsettingsdialog.h b/Pdf4QtViewer/pdfencryptionsettingsdialog.h index 2d86ee5..871838d 100644 --- a/Pdf4QtViewer/pdfencryptionsettingsdialog.h +++ b/Pdf4QtViewer/pdfencryptionsettingsdialog.h @@ -38,9 +38,11 @@ class PDFEncryptionSettingsDialog : public QDialog Q_OBJECT public: - explicit PDFEncryptionSettingsDialog(QWidget* parent); + explicit PDFEncryptionSettingsDialog(QByteArray documentId, QWidget* parent); virtual ~PDFEncryptionSettingsDialog() override; + pdf::PDFSecurityHandlerPointer getUpdatedSecurityHandler() const { return m_updatedSecurityHandler; } + public slots: virtual void accept() override; @@ -53,6 +55,7 @@ private: bool m_isUpdatingUi; std::map m_checkBoxToPermission; pdf::PDFSecurityHandlerPointer m_updatedSecurityHandler; + QByteArray m_documentId; }; } // namespace pdfviewer diff --git a/Pdf4QtViewer/pdfprogramcontroller.cpp b/Pdf4QtViewer/pdfprogramcontroller.cpp index 6bc4481..4a571dc 100644 --- a/Pdf4QtViewer/pdfprogramcontroller.cpp +++ b/Pdf4QtViewer/pdfprogramcontroller.cpp @@ -24,6 +24,7 @@ #include "pdfdrawspacecontroller.h" #include "pdfwidgetutils.h" #include "pdfconstants.h" +#include "pdfdocumentbuilder.h" #include "pdfviewersettings.h" #include "pdfundoredomanager.h" @@ -1133,7 +1134,15 @@ void PDFProgramController::onActionOptimizeTriggered() void PDFProgramController::onActionEncryptionTriggered() { - // Check that we have owner acces to the document + auto queryPassword = [this](bool* ok) + { + QString result; + *ok = false; + onQueryPasswordRequest(&result, ok); + return result; + }; + + // Check that we have owner access to the document const pdf::PDFSecurityHandler* securityHandler = m_pdfDocument->getStorage().getSecurityHandler(); pdf::PDFSecurityHandler::AuthorizationResult authorizationResult = securityHandler->getAuthorizationResult(); if (authorizationResult != pdf::PDFSecurityHandler::AuthorizationResult::OwnerAuthorized && @@ -1142,15 +1151,6 @@ void PDFProgramController::onActionEncryptionTriggered() // Jakub Melka: we must authorize as owner, otherwise we can't continue, // because we don't have sufficient permissions. pdf::PDFSecurityHandlerPointer clonedSecurityHandler(securityHandler->clone()); - - auto queryPassword = [this](bool* ok) - { - QString result; - *ok = false; - onQueryPasswordRequest(&result, ok); - return result; - }; - authorizationResult = clonedSecurityHandler->authenticate(queryPassword, true); if (authorizationResult != pdf::PDFSecurityHandler::AuthorizationResult::OwnerAuthorized) @@ -1167,8 +1167,30 @@ void PDFProgramController::onActionEncryptionTriggered() onDocumentModified(qMove(document)); } - PDFEncryptionSettingsDialog dialog(m_mainWindow); - dialog.exec(); + PDFEncryptionSettingsDialog dialog(m_pdfDocument->getIdPart(0), m_mainWindow); + if (dialog.exec() == QDialog::Accepted) + { + pdf::PDFSecurityHandlerPointer updatedSecurityHandler = dialog.getUpdatedSecurityHandler(); + + // Jakub Melka: If we changed encryption (password), recheck, that user doesn't + // forgot (or accidentally entered wrong) password. So, we require owner authentization + // to continue. + if (updatedSecurityHandler->getMode() != pdf::EncryptionMode::None) + { + if (updatedSecurityHandler->authenticate(queryPassword, true) != pdf::PDFSecurityHandler::AuthorizationResult::OwnerAuthorized) + { + QMessageBox::critical(m_mainWindow, QApplication::applicationDisplayName(), tr("Reauthorization is required to change document encryption.")); + return; + } + } + + pdf::PDFDocumentBuilder builder(m_pdfDocument.data()); + builder.setSecurityHandler(qMove(updatedSecurityHandler)); + + pdf::PDFDocumentPointer pointer(new pdf::PDFDocument(builder.build())); + pdf::PDFModifiedDocument document(qMove(pointer), m_optionalContentActivity, pdf::PDFModifiedDocument::Reset); + onDocumentModified(qMove(document)); + } } void PDFProgramController::onActionFitPageTriggered() diff --git a/generated_code_definition.xml b/generated_code_definition.xml index ce02031..0429049 100644 --- a/generated_code_definition.xml +++ b/generated_code_definition.xml @@ -8935,37 +8935,13 @@ return rootNodeReference; - - - - - - - - - - Encrypt - DictionaryItemSimple - PDFObject() - - - - Dictionary - - - - CreateObject - updatedTrailerDictionary - _PDFObject - - Code _void - m_storage.updateTrailerDictionary(qMove(updatedTrailerDictionary)); + setSecurityHandler(nullptr); Structure