Encryption bugfixing (RC4)

This commit is contained in:
Jakub Melka 2021-05-30 15:37:06 +02:00
parent e3fecc0568
commit e001adc65b
11 changed files with 132 additions and 64 deletions

View File

@ -43,6 +43,26 @@ bool PDFDocument::operator==(const PDFDocument& other) const
return m_pdfObjectStorage == other.m_pdfObjectStorage; 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 QByteArray PDFDocument::getDecodedStream(const PDFStream* stream) const
{ {
return m_pdfObjectStorage.getDecodedStream(stream); return m_pdfObjectStorage.getDecodedStream(stream);

View File

@ -419,6 +419,10 @@ public:
/// Returns info about the document (title, author, etc.) /// Returns info about the document (title, author, etc.)
const PDFDocumentInfo* getInfo() const { return &m_info; } 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 /// If object is reference, the dereference attempt is performed
/// and object is returned. If it is not a reference, then self /// and object is returned. If it is not a reference, then self
/// is returned. If dereference attempt fails, then null object /// is returned. If dereference attempt fails, then null object

View File

@ -1143,6 +1143,31 @@ void PDFDocumentBuilder::copyAnnotation(PDFObjectReference pageReference, PDFObj
void PDFDocumentBuilder::setSecurityHandler(PDFSecurityHandlerPointer handler) 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)); m_storage.setSecurityHandler(qMove(handler));
} }
@ -4297,13 +4322,7 @@ void PDFDocumentBuilder::removeEncryption()
{ {
PDFObjectFactory objectBuilder; PDFObjectFactory objectBuilder;
objectBuilder.beginDictionary(); setSecurityHandler(nullptr);
objectBuilder.beginDictionaryItem("Encrypt");
objectBuilder << PDFObject();
objectBuilder.endDictionaryItem();
objectBuilder.endDictionary();
PDFObject updatedTrailerDictionary = objectBuilder.takeObject();
m_storage.updateTrailerDictionary(qMove(updatedTrailerDictionary));
} }

View File

@ -72,6 +72,12 @@ struct WrapString
} }
WrapString(const QByteArray& byteArray) :
string(byteArray)
{
}
QByteArray string; QByteArray string;
}; };
@ -421,9 +427,10 @@ public:
/// \param annotationReference Annotation reference /// \param annotationReference Annotation reference
void copyAnnotation(PDFObjectReference pageReference, PDFObjectReference annotationReference); void copyAnnotation(PDFObjectReference pageReference, PDFObjectReference annotationReference);
/// Sets security handler to the object storage. Trailer dictionary is not /// Sets security handler to the object storage. Trailer dictionary is also
/// updated and so must be updated manually. /// updated, so it is not needed to update it. Pass nullptr as handler to remove
/// \param handler New security handler /// security.
/// \param handler New security handler, or nullptr
void setSecurityHandler(PDFSecurityHandlerPointer handler); void setSecurityHandler(PDFSecurityHandlerPointer handler);
/* START GENERATED CODE */ /* START GENERATED CODE */

View File

@ -319,8 +319,18 @@ PDFOperationResult PDFDocumentWriter::write(QIODevice* device, const PDFDocument
// Jakub Melka: Adjust trailer dictionary, to be really dictionary, not a stream // Jakub Melka: Adjust trailer dictionary, to be really dictionary, not a stream
PDFDictionary trailerDictionary = *document->getTrailerDictionary(); PDFDictionary trailerDictionary = *document->getTrailerDictionary();
trailerDictionary.removeEntry("XRefStm"); PDFDictionary newTrailerDictionary;
PDFObject trailerDictionaryObject = PDFObject::createDictionary(std::make_shared<PDFDictionary>(qMove(trailerDictionary)));
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<PDFDictionary>(qMove(newTrailerDictionary)));
device->write("trailer"); device->write("trailer");
writeCRLF(device); writeCRLF(device);

View File

@ -509,7 +509,7 @@ PDFSecurityHandlerPointer PDFSecurityHandler::createSecurityHandler(const PDFObj
return PDFSecurityHandlerPointer(new PDFStandardSecurityHandler(qMove(handler))); return PDFSecurityHandlerPointer(new PDFStandardSecurityHandler(qMove(handler)));
} }
void PDFSecurityHandler::fillEncryptionDictionary(PDFObjectFactory& factory) void PDFSecurityHandler::fillEncryptionDictionary(PDFObjectFactory& factory) const
{ {
factory.beginDictionaryItem("V"); factory.beginDictionaryItem("V");
factory << PDFInteger(m_V); factory << PDFInteger(m_V);
@ -621,15 +621,15 @@ void PDFSecurityHandler::fillEncryptionDictionary(PDFObjectFactory& factory)
// Store StmF, StrF, EFF // Store StmF, StrF, EFF
factory.beginDictionaryItem("StmF"); factory.beginDictionaryItem("StmF");
factory << stmfName; factory << WrapName(stmfName);
factory.endDictionaryItem(); factory.endDictionaryItem();
factory.beginDictionaryItem("StrF"); factory.beginDictionaryItem("StrF");
factory << strfName; factory << WrapName(strfName);
factory.endDictionaryItem(); factory.endDictionaryItem();
factory.beginDictionaryItem("EFF"); factory.beginDictionaryItem("EFF");
factory << effName; factory << WrapName(effName);
factory.endDictionaryItem(); factory.endDictionaryItem();
} }
} }
@ -1141,6 +1141,8 @@ PDFObject PDFStandardSecurityHandler::createEncryptionDictionaryObject() const
factory.beginDictionary(); factory.beginDictionary();
fillEncryptionDictionary(factory);
factory.beginDictionaryItem("Filter"); factory.beginDictionaryItem("Filter");
factory << WrapName("Standard"); factory << WrapName("Standard");
factory.endDictionaryItem(); factory.endDictionaryItem();
@ -1150,21 +1152,21 @@ PDFObject PDFStandardSecurityHandler::createEncryptionDictionaryObject() const
factory.endDictionaryItem(); factory.endDictionaryItem();
factory.beginDictionaryItem("O"); factory.beginDictionaryItem("O");
factory << m_O; factory << WrapString(m_O);
factory.endDictionaryItem(); factory.endDictionaryItem();
factory.beginDictionaryItem("U"); factory.beginDictionaryItem("U");
factory << m_U; factory << WrapString(m_U);
factory.endDictionaryItem(); factory.endDictionaryItem();
if (m_R == 6) if (m_R == 6)
{ {
factory.beginDictionaryItem("OE"); factory.beginDictionaryItem("OE");
factory << m_OE; factory << WrapString(m_OE);
factory.endDictionaryItem(); factory.endDictionaryItem();
factory.beginDictionaryItem("UE"); factory.beginDictionaryItem("UE");
factory << m_UE; factory << WrapString(m_UE);
factory.endDictionaryItem(); factory.endDictionaryItem();
} }
@ -1175,7 +1177,7 @@ PDFObject PDFStandardSecurityHandler::createEncryptionDictionaryObject() const
if (m_R == 6) if (m_R == 6)
{ {
factory.beginDictionaryItem("Perms"); factory.beginDictionaryItem("Perms");
factory << m_Perms; factory << WrapString(m_Perms);
factory.endDictionaryItem(); factory.endDictionaryItem();
} }
@ -1689,6 +1691,7 @@ PDFSecurityHandlerPointer PDFSecurityHandlerFactory::createSecurityHandler(const
// Jakub Melka: create standard security handler, with given settings // Jakub Melka: create standard security handler, with given settings
PDFStandardSecurityHandler* handler = new PDFStandardSecurityHandler(); PDFStandardSecurityHandler* handler = new PDFStandardSecurityHandler();
handler->m_ID = settings.id;
const bool isEncryptingEmbeddedFilesOnly = settings.encryptContents == EncryptContents::EmbeddedFiles; 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); Q_ASSERT(handler->getAuthorizationResult() == PDFSecurityHandler::AuthorizationResult::OwnerAuthorized);
return PDFSecurityHandlerPointer(handler); return PDFSecurityHandlerPointer(handler);
} }
@ -1884,7 +1888,7 @@ int PDFSecurityHandlerFactory::getRevisionFromAlgorithm(Algorithm algorithm)
return 0; return 0;
case RC4: case RC4:
return 3; return 4;
case AES_128: case AES_128:
return 4; return 4;

View File

@ -199,7 +199,7 @@ protected:
/// Fills encryption dictionary with basic data /// Fills encryption dictionary with basic data
/// \param factory Factory /// \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 /// Version of the encryption, shall be a number from 1 to 5, according the
/// PDF specification. Other values are invalid. /// PDF specification. Other values are invalid.
@ -404,6 +404,7 @@ public:
QString userPassword; QString userPassword;
QString ownerPassword; QString ownerPassword;
uint32_t permissions = 0; uint32_t permissions = 0;
QByteArray id;
}; };
/// Creates security handler based on given settings. If security handler cannot /// Creates security handler based on given settings. If security handler cannot

View File

@ -24,10 +24,11 @@
namespace pdfviewer namespace pdfviewer
{ {
PDFEncryptionSettingsDialog::PDFEncryptionSettingsDialog(QWidget* parent) : PDFEncryptionSettingsDialog::PDFEncryptionSettingsDialog(QByteArray documentId, QWidget* parent) :
QDialog(parent), QDialog(parent),
ui(new Ui::PDFEncryptionSettingsDialog), ui(new Ui::PDFEncryptionSettingsDialog),
m_isUpdatingUi(false) m_isUpdatingUi(false),
m_documentId(documentId)
{ {
ui->setupUi(this); ui->setupUi(this);
@ -175,6 +176,7 @@ void PDFEncryptionSettingsDialog::accept()
encryptContents = pdf::PDFSecurityHandlerFactory::EmbeddedFiles; encryptContents = pdf::PDFSecurityHandlerFactory::EmbeddedFiles;
} }
settings.id = m_documentId;
settings.algorithm = static_cast<const pdf::PDFSecurityHandlerFactory::Algorithm>(ui->algorithmComboBox->currentData().toInt()); settings.algorithm = static_cast<const pdf::PDFSecurityHandlerFactory::Algorithm>(ui->algorithmComboBox->currentData().toInt());
settings.encryptContents = encryptContents; settings.encryptContents = encryptContents;
settings.userPassword = ui->userPasswordEdit->text(); settings.userPassword = ui->userPasswordEdit->text();

View File

@ -38,9 +38,11 @@ class PDFEncryptionSettingsDialog : public QDialog
Q_OBJECT Q_OBJECT
public: public:
explicit PDFEncryptionSettingsDialog(QWidget* parent); explicit PDFEncryptionSettingsDialog(QByteArray documentId, QWidget* parent);
virtual ~PDFEncryptionSettingsDialog() override; virtual ~PDFEncryptionSettingsDialog() override;
pdf::PDFSecurityHandlerPointer getUpdatedSecurityHandler() const { return m_updatedSecurityHandler; }
public slots: public slots:
virtual void accept() override; virtual void accept() override;
@ -53,6 +55,7 @@ private:
bool m_isUpdatingUi; bool m_isUpdatingUi;
std::map<QCheckBox*, pdf::PDFSecurityHandler::Permission> m_checkBoxToPermission; std::map<QCheckBox*, pdf::PDFSecurityHandler::Permission> m_checkBoxToPermission;
pdf::PDFSecurityHandlerPointer m_updatedSecurityHandler; pdf::PDFSecurityHandlerPointer m_updatedSecurityHandler;
QByteArray m_documentId;
}; };
} // namespace pdfviewer } // namespace pdfviewer

View File

@ -24,6 +24,7 @@
#include "pdfdrawspacecontroller.h" #include "pdfdrawspacecontroller.h"
#include "pdfwidgetutils.h" #include "pdfwidgetutils.h"
#include "pdfconstants.h" #include "pdfconstants.h"
#include "pdfdocumentbuilder.h"
#include "pdfviewersettings.h" #include "pdfviewersettings.h"
#include "pdfundoredomanager.h" #include "pdfundoredomanager.h"
@ -1133,16 +1134,6 @@ void PDFProgramController::onActionOptimizeTriggered()
void PDFProgramController::onActionEncryptionTriggered() void PDFProgramController::onActionEncryptionTriggered()
{ {
// Check that we have owner acces to the document
const pdf::PDFSecurityHandler* securityHandler = m_pdfDocument->getStorage().getSecurityHandler();
pdf::PDFSecurityHandler::AuthorizationResult authorizationResult = securityHandler->getAuthorizationResult();
if (authorizationResult != pdf::PDFSecurityHandler::AuthorizationResult::OwnerAuthorized &&
authorizationResult != pdf::PDFSecurityHandler::AuthorizationResult::NoAuthorizationRequired)
{
// 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) auto queryPassword = [this](bool* ok)
{ {
QString result; QString result;
@ -1151,6 +1142,15 @@ void PDFProgramController::onActionEncryptionTriggered()
return result; 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 &&
authorizationResult != pdf::PDFSecurityHandler::AuthorizationResult::NoAuthorizationRequired)
{
// Jakub Melka: we must authorize as owner, otherwise we can't continue,
// because we don't have sufficient permissions.
pdf::PDFSecurityHandlerPointer clonedSecurityHandler(securityHandler->clone());
authorizationResult = clonedSecurityHandler->authenticate(queryPassword, true); authorizationResult = clonedSecurityHandler->authenticate(queryPassword, true);
if (authorizationResult != pdf::PDFSecurityHandler::AuthorizationResult::OwnerAuthorized) if (authorizationResult != pdf::PDFSecurityHandler::AuthorizationResult::OwnerAuthorized)
@ -1167,8 +1167,30 @@ void PDFProgramController::onActionEncryptionTriggered()
onDocumentModified(qMove(document)); onDocumentModified(qMove(document));
} }
PDFEncryptionSettingsDialog dialog(m_mainWindow); PDFEncryptionSettingsDialog dialog(m_pdfDocument->getIdPart(0), m_mainWindow);
dialog.exec(); 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() void PDFProgramController::onActionFitPageTriggered()

View File

@ -8935,37 +8935,13 @@ return rootNodeReference;</property>
<QObject class="codegen::GeneratedFunction"> <QObject class="codegen::GeneratedFunction">
<property name="objectName"></property> <property name="objectName"></property>
<property name="items"> <property name="items">
<QObject class="codegen::GeneratedAction">
<property name="objectName"></property>
<property name="items">
<QObject class="codegen::GeneratedPDFObject">
<property name="objectName"></property>
<property name="items">
<QObject class="codegen::GeneratedPDFObject">
<property name="objectName"></property>
<property name="items"/>
<property name="dictionaryItemName">Encrypt</property>
<property name="objectType">DictionaryItemSimple</property>
<property name="value">PDFObject()</property>
</QObject>
</property>
<property name="dictionaryItemName"></property>
<property name="objectType">Dictionary</property>
<property name="value"></property>
</QObject>
</property>
<property name="actionType">CreateObject</property>
<property name="variableName">updatedTrailerDictionary</property>
<property name="variableType">_PDFObject</property>
<property name="code"></property>
</QObject>
<QObject class="codegen::GeneratedAction"> <QObject class="codegen::GeneratedAction">
<property name="objectName"></property> <property name="objectName"></property>
<property name="items"/> <property name="items"/>
<property name="actionType">Code</property> <property name="actionType">Code</property>
<property name="variableName"></property> <property name="variableName"></property>
<property name="variableType">_void</property> <property name="variableType">_void</property>
<property name="code">m_storage.updateTrailerDictionary(qMove(updatedTrailerDictionary));</property> <property name="code">setSecurityHandler(nullptr);</property>
</QObject> </QObject>
</property> </property>
<property name="functionType">Structure</property> <property name="functionType">Structure</property>