mirror of https://github.com/JakubMelka/PDF4QT.git
Encryption - RC4
This commit is contained in:
parent
936fe2fbe7
commit
e3fecc0568
|
@ -496,10 +496,11 @@ public:
|
|||
|
||||
enum ModificationFlag
|
||||
{
|
||||
None = 0x0000, ///< No flag
|
||||
Reset = 0x0001, ///< Whole document content is changed (for example, new document is being set)
|
||||
Annotation = 0x0002, ///< Annotations changed
|
||||
FormField = 0x0004, ///< Form field content changed
|
||||
None = 0x0000, ///< No flag
|
||||
Reset = 0x0001, ///< Whole document content is changed (for example, new document is being set)
|
||||
Annotation = 0x0002, ///< Annotations changed
|
||||
FormField = 0x0004, ///< Form field content changed
|
||||
Authorization = 0x0008, ///< Authorization has changed (for example, old document is granted user access, but for new, owner access)
|
||||
};
|
||||
|
||||
Q_DECLARE_FLAGS(ModificationFlags, ModificationFlag)
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include "pdfencoding.h"
|
||||
#include "pdfvisitor.h"
|
||||
#include "pdfutils.h"
|
||||
#include "pdfdocumentbuilder.h"
|
||||
|
||||
#include <QRandomGenerator>
|
||||
|
||||
|
@ -508,6 +509,131 @@ PDFSecurityHandlerPointer PDFSecurityHandler::createSecurityHandler(const PDFObj
|
|||
return PDFSecurityHandlerPointer(new PDFStandardSecurityHandler(qMove(handler)));
|
||||
}
|
||||
|
||||
void PDFSecurityHandler::fillEncryptionDictionary(PDFObjectFactory& factory)
|
||||
{
|
||||
factory.beginDictionaryItem("V");
|
||||
factory << PDFInteger(m_V);
|
||||
factory.endDictionaryItem();
|
||||
|
||||
if (m_V == 2 || m_V == 3)
|
||||
{
|
||||
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 << stmfName;
|
||||
factory.endDictionaryItem();
|
||||
|
||||
factory.beginDictionaryItem("StrF");
|
||||
factory << strfName;
|
||||
factory.endDictionaryItem();
|
||||
|
||||
factory.beginDictionaryItem("EFF");
|
||||
factory << effName;
|
||||
factory.endDictionaryItem();
|
||||
}
|
||||
}
|
||||
|
||||
PDFSecurityHandler* PDFStandardSecurityHandler::clone() const
|
||||
{
|
||||
return new PDFStandardSecurityHandler(*this);
|
||||
|
@ -1009,6 +1135,62 @@ QByteArray PDFStandardSecurityHandler::encryptByFilter(const QByteArray& data, c
|
|||
return encryptUsingFilter(data, it->second, reference);
|
||||
}
|
||||
|
||||
PDFObject PDFStandardSecurityHandler::createEncryptionDictionaryObject() const
|
||||
{
|
||||
PDFObjectFactory factory;
|
||||
|
||||
factory.beginDictionary();
|
||||
|
||||
factory.beginDictionaryItem("Filter");
|
||||
factory << WrapName("Standard");
|
||||
factory.endDictionaryItem();
|
||||
|
||||
factory.beginDictionaryItem("R");
|
||||
factory << PDFInteger(m_R);
|
||||
factory.endDictionaryItem();
|
||||
|
||||
factory.beginDictionaryItem("O");
|
||||
factory << m_O;
|
||||
factory.endDictionaryItem();
|
||||
|
||||
factory.beginDictionaryItem("U");
|
||||
factory << m_U;
|
||||
factory.endDictionaryItem();
|
||||
|
||||
if (m_R == 6)
|
||||
{
|
||||
factory.beginDictionaryItem("OE");
|
||||
factory << m_OE;
|
||||
factory.endDictionaryItem();
|
||||
|
||||
factory.beginDictionaryItem("UE");
|
||||
factory << m_UE;
|
||||
factory.endDictionaryItem();
|
||||
}
|
||||
|
||||
factory.beginDictionaryItem("P");
|
||||
factory << PDFInteger(m_permissions);
|
||||
factory.endDictionaryItem();
|
||||
|
||||
if (m_R == 6)
|
||||
{
|
||||
factory.beginDictionaryItem("Perms");
|
||||
factory << 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;
|
||||
|
@ -1127,6 +1309,13 @@ QByteArray PDFStandardSecurityHandler::createEntryValueU_r234(const QByteArray&
|
|||
// want to compare byte arrays entirely (otherwise we must compare only 16 bytes to authenticate
|
||||
// user password).
|
||||
result = m_U;
|
||||
|
||||
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;
|
||||
}
|
||||
|
@ -1491,9 +1680,149 @@ bool PDFStandardSecurityHandler::isUnicodeMappedToNothing(ushort unicode)
|
|||
}
|
||||
}
|
||||
|
||||
PDFSecurityHandlerPointer PDFSecurityHandlerFactory::createSecurityHandler(const PDFSecurityHandlerFactory::SecuritySettings& settings)
|
||||
PDFSecurityHandlerPointer PDFSecurityHandlerFactory::createSecurityHandler(const SecuritySettings& settings)
|
||||
{
|
||||
return nullptr;
|
||||
if (settings.algorithm == Algorithm::None)
|
||||
{
|
||||
return PDFSecurityHandlerPointer(new PDFNoneSecurityHandler);
|
||||
}
|
||||
|
||||
// Jakub Melka: create standard security handler, with given settings
|
||||
PDFStandardSecurityHandler* handler = new PDFStandardSecurityHandler();
|
||||
|
||||
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["DefaultCF"] = handler->m_filterDefault;
|
||||
handler->m_R = getRevisionFromAlgorithm(settings.algorithm);
|
||||
handler->m_permissions = settings.permissions;
|
||||
|
||||
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<uint8_t, 32> 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;
|
||||
}
|
||||
|
||||
// TODO: Dodelat R6
|
||||
|
||||
default:
|
||||
{
|
||||
Q_ASSERT(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
handler->authenticate([&settings](bool* b) { *b = false; return settings.ownerPassword; }, true);
|
||||
Q_ASSERT(handler->getAuthorizationResult() == PDFSecurityHandler::AuthorizationResult::OwnerAuthorized);
|
||||
return PDFSecurityHandlerPointer(handler);
|
||||
}
|
||||
|
||||
int PDFSecurityHandlerFactory::getPasswordOptimalEntropy()
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
|
||||
namespace pdf
|
||||
{
|
||||
class PDFObjectFactory;
|
||||
|
||||
enum class EncryptionMode
|
||||
{
|
||||
|
@ -61,6 +62,9 @@ enum class CryptFilterApplication
|
|||
|
||||
struct CryptFilter
|
||||
{
|
||||
bool operator ==(const CryptFilter&) const = default;
|
||||
bool operator !=(const CryptFilter&) const = default;
|
||||
|
||||
CryptFilterType type = CryptFilterType::None;
|
||||
AuthEvent authEvent = AuthEvent::DocOpen;
|
||||
int keyLength = 0; ///< Key length in bytes
|
||||
|
@ -179,6 +183,9 @@ public:
|
|||
/// Returns result of authorization process
|
||||
virtual AuthorizationResult getAuthorizationResult() const = 0;
|
||||
|
||||
/// Creates encryption dictionary object
|
||||
virtual PDFObject createEncryptionDictionaryObject() const = 0;
|
||||
|
||||
/// Returns version of the encryption
|
||||
int getVersion() const { return m_V; }
|
||||
|
||||
|
@ -189,6 +196,11 @@ public:
|
|||
static PDFSecurityHandlerPointer createSecurityHandler(const PDFObject& encryptionDictionaryObject, const QByteArray& id);
|
||||
|
||||
protected:
|
||||
|
||||
/// Fills encryption dictionary with basic data
|
||||
/// \param factory Factory
|
||||
void fillEncryptionDictionary(PDFObjectFactory& factory);
|
||||
|
||||
/// Version of the encryption, shall be a number from 1 to 5, according the
|
||||
/// PDF specification. Other values are invalid.
|
||||
int m_V = 0;
|
||||
|
@ -227,6 +239,7 @@ public:
|
|||
virtual bool isAllowed(Permission) const override { return true; }
|
||||
virtual bool isEncryptionAllowed() const override { return true; }
|
||||
virtual AuthorizationResult getAuthorizationResult() const override { return AuthorizationResult::NoAuthorizationRequired; }
|
||||
virtual PDFObject createEncryptionDictionaryObject() const override { return PDFObject(); }
|
||||
};
|
||||
|
||||
/// Specifies the security using standard security handler (see PDF specification
|
||||
|
@ -245,6 +258,7 @@ public:
|
|||
virtual bool isAllowed(Permission permission) const override { return m_authorizationData.authorizationResult == AuthorizationResult::OwnerAuthorized || (m_permissions & static_cast<uint32_t>(permission)); }
|
||||
virtual bool isEncryptionAllowed() const override { return m_authorizationData.isAuthorized(); }
|
||||
virtual AuthorizationResult getAuthorizationResult() const override { return m_authorizationData.authorizationResult; }
|
||||
virtual PDFObject createEncryptionDictionaryObject() const override;
|
||||
|
||||
struct AuthorizationData
|
||||
{
|
||||
|
@ -258,6 +272,7 @@ public:
|
|||
static QByteArray adjustPassword(const QString& password, int revision);
|
||||
|
||||
private:
|
||||
friend class PDFSecurityHandlerFactory;
|
||||
friend PDFSecurityHandlerPointer PDFSecurityHandler::createSecurityHandler(const PDFObject& encryptionDictionaryObject, const QByteArray& id);
|
||||
|
||||
struct UserOwnerData_r6
|
||||
|
|
|
@ -128,6 +128,16 @@ void PDFEncryptionSettingsDialog::updateUi()
|
|||
ui->userPasswordEdit->setEnabled(ui->userPasswordEnableCheckBox->isChecked());
|
||||
ui->ownerPasswordEdit->setEnabled(ui->ownerPasswordEnableCheckBox->isChecked());
|
||||
|
||||
if (!ui->userPasswordEdit->isEnabled())
|
||||
{
|
||||
ui->userPasswordEdit->clear();
|
||||
}
|
||||
|
||||
if (!ui->ownerPasswordEdit->isEnabled())
|
||||
{
|
||||
ui->ownerPasswordEdit->clear();
|
||||
}
|
||||
|
||||
ui->userPasswordStrengthHintWidget->setEnabled(ui->userPasswordEnableCheckBox->isChecked());
|
||||
ui->ownerPasswordStrengthHintWidget->setEnabled(ui->ownerPasswordEnableCheckBox->isChecked());
|
||||
|
||||
|
@ -151,4 +161,37 @@ void PDFEncryptionSettingsDialog::updatePasswordScore()
|
|||
ui->ownerPasswordStrengthHintWidget->setCurrentValue(ownerPasswordScore);
|
||||
}
|
||||
|
||||
void PDFEncryptionSettingsDialog::accept()
|
||||
{
|
||||
pdf::PDFSecurityHandlerFactory::SecuritySettings settings;
|
||||
pdf::PDFSecurityHandlerFactory::EncryptContents encryptContents = pdf::PDFSecurityHandlerFactory::All;
|
||||
|
||||
if (ui->encryptAllExceptMetadataRadioButton->isChecked())
|
||||
{
|
||||
encryptContents = pdf::PDFSecurityHandlerFactory::AllExceptMetadata;
|
||||
}
|
||||
else if (ui->encryptFileAttachmentsOnlyRadioButton->isChecked())
|
||||
{
|
||||
encryptContents = pdf::PDFSecurityHandlerFactory::EmbeddedFiles;
|
||||
}
|
||||
|
||||
settings.algorithm = static_cast<const pdf::PDFSecurityHandlerFactory::Algorithm>(ui->algorithmComboBox->currentData().toInt());
|
||||
settings.encryptContents = encryptContents;
|
||||
settings.userPassword = ui->userPasswordEdit->text();
|
||||
settings.ownerPassword = ui->ownerPasswordEdit->text();
|
||||
settings.permissions = 0;
|
||||
|
||||
for (auto item : m_checkBoxToPermission)
|
||||
{
|
||||
if (item.first->isChecked())
|
||||
{
|
||||
settings.permissions += uint32_t(item.second);
|
||||
}
|
||||
}
|
||||
|
||||
m_updatedSecurityHandler = pdf::PDFSecurityHandlerFactory::createSecurityHandler(settings);
|
||||
|
||||
QDialog::accept();
|
||||
}
|
||||
|
||||
} // namespace pdfviewer
|
||||
|
|
|
@ -41,6 +41,9 @@ public:
|
|||
explicit PDFEncryptionSettingsDialog(QWidget* parent);
|
||||
virtual ~PDFEncryptionSettingsDialog() override;
|
||||
|
||||
public slots:
|
||||
virtual void accept() override;
|
||||
|
||||
private:
|
||||
Ui::PDFEncryptionSettingsDialog* ui;
|
||||
|
||||
|
@ -49,6 +52,7 @@ private:
|
|||
|
||||
bool m_isUpdatingUi;
|
||||
std::map<QCheckBox*, pdf::PDFSecurityHandler::Permission> m_checkBoxToPermission;
|
||||
pdf::PDFSecurityHandlerPointer m_updatedSecurityHandler;
|
||||
};
|
||||
|
||||
} // namespace pdfviewer
|
||||
|
|
|
@ -1163,7 +1163,7 @@ void PDFProgramController::onActionEncryptionTriggered()
|
|||
storage.setSecurityHandler(qMove(clonedSecurityHandler));
|
||||
|
||||
pdf::PDFDocumentPointer pointer(new pdf::PDFDocument(qMove(storage), m_pdfDocument->getInfo()->version));
|
||||
pdf::PDFModifiedDocument document(qMove(pointer), m_optionalContentActivity, pdf::PDFModifiedDocument::Reset);
|
||||
pdf::PDFModifiedDocument document(qMove(pointer), m_optionalContentActivity, pdf::PDFModifiedDocument::Authorization);
|
||||
onDocumentModified(qMove(document));
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue