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

View File

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

View File

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

View File

@ -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 */

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
PDFDictionary trailerDictionary = *document->getTrailerDictionary();
trailerDictionary.removeEntry("XRefStm");
PDFObject trailerDictionaryObject = PDFObject::createDictionary(std::make_shared<PDFDictionary>(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<PDFDictionary>(qMove(newTrailerDictionary)));
device->write("trailer");
writeCRLF(device);

View File

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

View File

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

View File

@ -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<const pdf::PDFSecurityHandlerFactory::Algorithm>(ui->algorithmComboBox->currentData().toInt());
settings.encryptContents = encryptContents;
settings.userPassword = ui->userPasswordEdit->text();

View File

@ -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<QCheckBox*, pdf::PDFSecurityHandler::Permission> m_checkBoxToPermission;
pdf::PDFSecurityHandlerPointer m_updatedSecurityHandler;
QByteArray m_documentId;
};
} // namespace pdfviewer

View File

@ -24,6 +24,7 @@
#include "pdfdrawspacecontroller.h"
#include "pdfwidgetutils.h"
#include "pdfconstants.h"
#include "pdfdocumentbuilder.h"
#include "pdfviewersettings.h"
#include "pdfundoredomanager.h"
@ -1133,16 +1134,6 @@ void PDFProgramController::onActionOptimizeTriggered()
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)
{
QString result;
@ -1151,6 +1142,15 @@ void PDFProgramController::onActionEncryptionTriggered()
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);
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()

View File

@ -8935,37 +8935,13 @@ return rootNodeReference;</property>
<QObject class="codegen::GeneratedFunction">
<property name="objectName"></property>
<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">
<property name="objectName"></property>
<property name="items"/>
<property name="actionType">Code</property>
<property name="variableName"></property>
<property name="variableType">_void</property>
<property name="code">m_storage.updateTrailerDictionary(qMove(updatedTrailerDictionary));</property>
<property name="code">setSecurityHandler(nullptr);</property>
</QObject>
</property>
<property name="functionType">Structure</property>