mirror of
https://github.com/JakubMelka/PDF4QT.git
synced 2025-03-24 23:30:25 +01:00
Handling of encrypted documents - basic functionality for standard encryption handler revision 2,3,4
This commit is contained in:
parent
0a6e7bb866
commit
0434a70de5
@ -33,8 +33,9 @@
|
|||||||
namespace pdf
|
namespace pdf
|
||||||
{
|
{
|
||||||
|
|
||||||
PDFDocumentReader::PDFDocumentReader() :
|
PDFDocumentReader::PDFDocumentReader(const std::function<QString(bool*)>& getPasswordCallback) :
|
||||||
m_successfull(true)
|
m_successfull(true),
|
||||||
|
m_getPasswordCallback(getPasswordCallback)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -277,18 +278,42 @@ PDFDocument PDFDocumentReader::readFromBuffer(const QByteArray& buffer)
|
|||||||
}
|
}
|
||||||
const PDFDictionary* trailerDictionary = trailerDictionaryObject.getDictionary();
|
const PDFDictionary* trailerDictionary = trailerDictionaryObject.getDictionary();
|
||||||
|
|
||||||
|
// Read the document ID
|
||||||
|
QByteArray id;
|
||||||
|
const PDFObject& idArrayObject = trailerDictionary->get("ID");
|
||||||
|
if (idArrayObject.isArray())
|
||||||
|
{
|
||||||
|
const PDFArray* idArray = idArrayObject.getArray();
|
||||||
|
if (idArray->getCount() > 0)
|
||||||
|
{
|
||||||
|
const PDFObject& idArrayItem = idArray->getItem(0);
|
||||||
|
if (idArrayItem.isString())
|
||||||
|
{
|
||||||
|
id = idArrayItem.getString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
PDFObject encryptObject = trailerDictionary->get("Encrypt");
|
PDFObject encryptObject = trailerDictionary->get("Encrypt");
|
||||||
if (encryptObject.isReference())
|
if (encryptObject.isReference())
|
||||||
{
|
{
|
||||||
PDFObjectReference encryptObjectReference = encryptObject.getReference();
|
PDFObjectReference encryptObjectReference = encryptObject.getReference();
|
||||||
if (encryptObjectReference.objectNumber < objects.size() && objects[encryptObjectReference.objectNumber].generation == encryptObjectReference.generation)
|
if (static_cast<size_t>(encryptObjectReference.objectNumber) < objects.size() && objects[encryptObjectReference.objectNumber].generation == encryptObjectReference.generation)
|
||||||
{
|
{
|
||||||
encryptObject = objects[encryptObjectReference.objectNumber].object;
|
encryptObject = objects[encryptObjectReference.objectNumber].object;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read the security handler
|
// Read the security handler
|
||||||
PDFSecurityHandlerPointer securityHandler = PDFSecurityHandler::createSecurityHandler(encryptObject);
|
PDFSecurityHandlerPointer securityHandler = PDFSecurityHandler::createSecurityHandler(encryptObject, id);
|
||||||
|
PDFSecurityHandler::AuthorizationResult authorizationResult = securityHandler->authenticate(m_getPasswordCallback);
|
||||||
|
|
||||||
|
if (authorizationResult == PDFSecurityHandler::AuthorizationResult::Failed ||
|
||||||
|
authorizationResult == PDFSecurityHandler::AuthorizationResult::Cancelled)
|
||||||
|
{
|
||||||
|
// TODO: If user cancels it, do not display error message.
|
||||||
|
throw PDFParserException(PDFTranslationContext::tr("Authorization failed. Bad password provided."));
|
||||||
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------
|
||||||
// SECURITY - security handler created
|
// SECURITY - security handler created
|
||||||
|
@ -36,7 +36,7 @@ class PDFFORQTLIBSHARED_EXPORT PDFDocumentReader
|
|||||||
Q_DECLARE_TR_FUNCTIONS(pdf::PDFDocumentReader)
|
Q_DECLARE_TR_FUNCTIONS(pdf::PDFDocumentReader)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit PDFDocumentReader();
|
explicit PDFDocumentReader(const std::function<QString(bool*)>& getPasswordCallback);
|
||||||
|
|
||||||
constexpr inline PDFDocumentReader(const PDFDocumentReader&) = delete;
|
constexpr inline PDFDocumentReader(const PDFDocumentReader&) = delete;
|
||||||
constexpr inline PDFDocumentReader(PDFDocumentReader&&) = delete;
|
constexpr inline PDFDocumentReader(PDFDocumentReader&&) = delete;
|
||||||
@ -91,6 +91,9 @@ private:
|
|||||||
|
|
||||||
/// Version of the scanned file
|
/// Version of the scanned file
|
||||||
PDFVersion m_version;
|
PDFVersion m_version;
|
||||||
|
|
||||||
|
/// Callback to obtain password from the user
|
||||||
|
std::function<QString(bool*)> m_getPasswordCallback;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace pdf
|
} // namespace pdf
|
||||||
|
@ -19,11 +19,22 @@
|
|||||||
#include "pdfexception.h"
|
#include "pdfexception.h"
|
||||||
|
|
||||||
#include <openssl/rc4.h>
|
#include <openssl/rc4.h>
|
||||||
|
#include <openssl/md5.h>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
|
||||||
namespace pdf
|
namespace pdf
|
||||||
{
|
{
|
||||||
|
|
||||||
PDFSecurityHandlerPointer PDFSecurityHandler::createSecurityHandler(const PDFObject& encryptionDictionaryObject)
|
// Padding password
|
||||||
|
static constexpr std::array<uint8_t, 32> 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
|
||||||
|
};
|
||||||
|
|
||||||
|
PDFSecurityHandlerPointer PDFSecurityHandler::createSecurityHandler(const PDFObject& encryptionDictionaryObject, const QByteArray& id)
|
||||||
{
|
{
|
||||||
if (encryptionDictionaryObject.isNull())
|
if (encryptionDictionaryObject.isNull())
|
||||||
{
|
{
|
||||||
@ -215,7 +226,337 @@ PDFSecurityHandlerPointer PDFSecurityHandler::createSecurityHandler(const PDFObj
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int R = getInt(dictionary, "R", true);
|
||||||
|
if (R < 2 || R > 6 || R == 5)
|
||||||
|
{
|
||||||
|
throw PDFParserException(PDFTranslationContext::tr("Revision %1 of standard security handler is not supported.").arg(R));
|
||||||
|
}
|
||||||
|
handler.m_R = R;
|
||||||
|
|
||||||
|
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 PDFParserException(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 PDFParserException(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) ? 32 : 48);
|
||||||
|
handler.m_U = readByteArray("U", (R != 6) ? 32 : 48);
|
||||||
|
|
||||||
|
handler.m_permissions = static_cast<uint32_t>(static_cast<int>(getInt(dictionary, "P", true)));
|
||||||
|
|
||||||
|
if (R == 6)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
|
||||||
return PDFSecurityHandlerPointer(new PDFStandardSecurityHandler(qMove(handler)));
|
return PDFSecurityHandlerPointer(new PDFStandardSecurityHandler(qMove(handler)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PDFSecurityHandler::AuthorizationResult PDFStandardSecurityHandler::authenticate(const std::function<QString(bool*)>& getPasswordCallback)
|
||||||
|
{
|
||||||
|
QByteArray password;
|
||||||
|
bool passwordObtained = true;
|
||||||
|
|
||||||
|
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
|
||||||
|
return AuthorizationResult::OwnerAuthorized;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to authorize user password
|
||||||
|
QByteArray fileEncryptionKey = createFileEncryptionKey(password);
|
||||||
|
QByteArray U = createEntryValueU_r234(fileEncryptionKey);
|
||||||
|
|
||||||
|
if (U == m_U)
|
||||||
|
{
|
||||||
|
// We have authorized owner access
|
||||||
|
return AuthorizationResult::UserAuthorized;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 6:
|
||||||
|
{
|
||||||
|
// TODO: Implement revision 6 encryption
|
||||||
|
return AuthorizationResult::Failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return AuthorizationResult::Failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: Handle passwords better - in some revisions, must be in PDFDocEncoding!
|
||||||
|
password = getPasswordCallback(&passwordObtained).toUtf8();
|
||||||
|
}
|
||||||
|
|
||||||
|
return AuthorizationResult::Cancelled;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray PDFStandardSecurityHandler::createFileEncryptionKey(const QByteArray& password) const
|
||||||
|
{
|
||||||
|
QByteArray result;
|
||||||
|
|
||||||
|
switch (m_R)
|
||||||
|
{
|
||||||
|
case 2:
|
||||||
|
case 3:
|
||||||
|
case 4:
|
||||||
|
{
|
||||||
|
std::array<uint8_t, 32> 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<uint8_t, MD5_DIGEST_LENGTH> fileEncryptionKey;
|
||||||
|
MD5_Final(fileEncryptionKey.data(), &context);
|
||||||
|
|
||||||
|
const int keyByteLength = m_keyLength / 8;
|
||||||
|
if (keyByteLength > MD5_DIGEST_LENGTH)
|
||||||
|
{
|
||||||
|
throw PDFParserException(PDFTranslationContext::tr("Encryption key length (%1) exceeded maximal value of.").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 6:
|
||||||
|
{
|
||||||
|
// TODO: Implement revision 6 key
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
throw PDFParserException(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(), reinterpret_cast<const unsigned char*>(fileEncryptionKey.data()));
|
||||||
|
|
||||||
|
result.resize(static_cast<int>(PDFPasswordPadding.size()));
|
||||||
|
RC4(&key, PDFPasswordPadding.size(), PDFPasswordPadding.data(), reinterpret_cast<unsigned char*>(result.data()));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
case 4:
|
||||||
|
{
|
||||||
|
std::array<uint8_t, MD5_DIGEST_LENGTH> 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(), reinterpret_cast<const unsigned char*>(fileEncryptionKey.data()));
|
||||||
|
|
||||||
|
std::array<uint8_t, MD5_DIGEST_LENGTH> encryptedHash;
|
||||||
|
RC4(&key, hash.size(), hash.data(), reinterpret_cast<unsigned char*>(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<uint8_t>(fileEncryptionKey[j]) ^ static_cast<uint8_t>(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
RC4_set_key(&key, transformedKey.size(), reinterpret_cast<const unsigned char*>(transformedKey.data()));
|
||||||
|
RC4(&key, encryptedHash.size(), encryptedHash.data(), reinterpret_cast<unsigned char*>(encryptedHash.data()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
std::copy_n(encryptedHash.begin(), encryptedHash.size(), result.begin());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
throw PDFParserException(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<uint8_t, 32> paddedPassword = createPaddedPassword32(password);
|
||||||
|
std::array<uint8_t, MD5_DIGEST_LENGTH> 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 PDFParserException(PDFTranslationContext::tr("Encryption key length (%1) exceeded maximal value of.").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, reinterpret_cast<const unsigned char*>(hash.data()));
|
||||||
|
result.resize(m_O.size());
|
||||||
|
RC4(&key, m_O.size(), reinterpret_cast<const unsigned char*>(m_O.data()), reinterpret_cast<unsigned char*>(result.data()));
|
||||||
|
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<uint8_t>(hash[j]) ^ static_cast<uint8_t>(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
RC4_KEY key = { };
|
||||||
|
RC4_set_key(&key, transformedKey.size(), reinterpret_cast<const unsigned char*>(transformedKey.data()));
|
||||||
|
RC4(&key, buffer.size(), reinterpret_cast<const unsigned char*>(buffer.data()), reinterpret_cast<unsigned char*>(buffer.data()));
|
||||||
|
}
|
||||||
|
|
||||||
|
result = buffer;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
throw PDFParserException(PDFTranslationContext::tr("Revision %1 of standard security handler is not supported.").arg(m_R));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<uint8_t, 32> PDFStandardSecurityHandler::createPaddedPassword32(const QByteArray& password) const
|
||||||
|
{
|
||||||
|
std::array<uint8_t, 32> result = { };
|
||||||
|
|
||||||
|
int copiedBytes = qMin<int>(static_cast<int>(result.size()), password.size());
|
||||||
|
auto it = result.begin();
|
||||||
|
|
||||||
|
for (int i = 0; i < copiedBytes; ++i)
|
||||||
|
{
|
||||||
|
*it++ = static_cast<uint8_t>(password[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto itPadding = PDFPasswordPadding.cbegin();
|
||||||
|
for (; it != result.cend();)
|
||||||
|
{
|
||||||
|
Q_ASSERT(itPadding != PDFPasswordPadding.cend());
|
||||||
|
*it++ = *itPadding++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace pdf
|
} // namespace pdf
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
#include <QSharedPointer>
|
#include <QSharedPointer>
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
namespace pdf
|
namespace pdf
|
||||||
{
|
{
|
||||||
@ -73,14 +74,24 @@ public:
|
|||||||
explicit PDFSecurityHandler() = default;
|
explicit PDFSecurityHandler() = default;
|
||||||
virtual ~PDFSecurityHandler() = default;
|
virtual ~PDFSecurityHandler() = default;
|
||||||
|
|
||||||
|
enum class AuthorizationResult
|
||||||
|
{
|
||||||
|
UserAuthorized,
|
||||||
|
OwnerAuthorized,
|
||||||
|
Failed,
|
||||||
|
Cancelled
|
||||||
|
};
|
||||||
|
|
||||||
virtual EncryptionMode getMode() const = 0;
|
virtual EncryptionMode getMode() const = 0;
|
||||||
|
virtual AuthorizationResult authenticate(const std::function<QString(bool*)>& getPasswordCallback) = 0;
|
||||||
|
|
||||||
/// Creates a security handler from the object. If object is null, then
|
/// Creates a security handler from the object. If object is null, then
|
||||||
/// "None" security handler is created. If error occurs, then exception is thrown.
|
/// "None" security handler is created. If error occurs, then exception is thrown.
|
||||||
/// \param encryptionDictionaryObject Encryption dictionary object
|
/// \param encryptionDictionaryObject Encryption dictionary object
|
||||||
static PDFSecurityHandlerPointer createSecurityHandler(const PDFObject& encryptionDictionaryObject);
|
/// \param id First part of the id of the document
|
||||||
|
static PDFSecurityHandlerPointer createSecurityHandler(const PDFObject& encryptionDictionaryObject, const QByteArray& id);
|
||||||
|
|
||||||
private:
|
protected:
|
||||||
/// 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.
|
||||||
int m_V = 0;
|
int m_V = 0;
|
||||||
@ -107,6 +118,7 @@ class PDFNoneSecurityHandler : public PDFSecurityHandler
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
virtual EncryptionMode getMode() const { return EncryptionMode::None; }
|
virtual EncryptionMode getMode() const { return EncryptionMode::None; }
|
||||||
|
virtual AuthorizationResult authenticate(const std::function<QString(bool*)>&) override { return AuthorizationResult::OwnerAuthorized; }
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Specifies the security using standard security handler (see PDF specification
|
/// Specifies the security using standard security handler (see PDF specification
|
||||||
@ -115,9 +127,67 @@ class PDFStandardSecurityHandler : public PDFSecurityHandler
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
virtual EncryptionMode getMode() const { return EncryptionMode::Standard; }
|
virtual EncryptionMode getMode() const { return EncryptionMode::Standard; }
|
||||||
|
virtual AuthorizationResult authenticate(const std::function<QString(bool*)>& getPasswordCallback) override;
|
||||||
|
|
||||||
|
struct AuthorizationData
|
||||||
|
{
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
friend static PDFSecurityHandlerPointer PDFSecurityHandler::createSecurityHandler(const PDFObject& encryptionDictionaryObject, const QByteArray& id);
|
||||||
|
|
||||||
|
/// Creates file encryption key from passed password, based on the revision
|
||||||
|
/// \param password Password to be used to create file encryption key
|
||||||
|
/// \note Password must be in PDFDocEncoding for revision 4 or earlier,
|
||||||
|
/// otherwise it must be encoded in UTF-8.
|
||||||
|
QByteArray createFileEncryptionKey(const QByteArray& password) const;
|
||||||
|
|
||||||
|
/// Creates entry value U based on the file encryption key. This function
|
||||||
|
/// is valid only for revisions 2, 3 and 4.
|
||||||
|
/// \param fileEncryptionKey File encryption key
|
||||||
|
QByteArray createEntryValueU_r234(const QByteArray& fileEncryptionKey) const;
|
||||||
|
|
||||||
|
/// Creates user password from the owner password. User password must be then
|
||||||
|
/// authenticated.
|
||||||
|
QByteArray createUserPasswordFromOwnerPassword(const QByteArray& password) const;
|
||||||
|
|
||||||
|
/// Creates 32-byte padded password from the passed password. If password is empty,
|
||||||
|
/// then padding password is returned.
|
||||||
|
std::array<uint8_t, 32> createPaddedPassword32(const QByteArray& password) const;
|
||||||
|
|
||||||
|
/// Revision number of standard security number
|
||||||
|
int m_R = 0;
|
||||||
|
|
||||||
|
/// 32 byte string if revision number is 4 or less, or 48 byte string,
|
||||||
|
/// if revision number is 6, based on both owner and user passwords,
|
||||||
|
/// used for authenticate owner, and create a file encryption key.
|
||||||
|
QByteArray m_O;
|
||||||
|
|
||||||
|
/// 32 byte string if revision number is 4 or less, or 48 byte string,
|
||||||
|
/// if revision number is 6, based on both owner and user passwords,
|
||||||
|
/// used for authenticate owner, and create a file encryption key.
|
||||||
|
QByteArray m_U;
|
||||||
|
|
||||||
|
/// For revision number 6 only. 32 bytes string based on both owner
|
||||||
|
/// and user password, that shall be used to compute file encryption key.
|
||||||
|
QByteArray m_OE;
|
||||||
|
|
||||||
|
/// For revision number 6 only. 32 bytes string based on both owner
|
||||||
|
/// and user password, that shall be used to compute file encryption key.
|
||||||
|
QByteArray m_UE;
|
||||||
|
|
||||||
|
/// What operations shall be permitted, when document is opened with user access.
|
||||||
|
uint32_t m_permissions = 0;
|
||||||
|
|
||||||
|
/// For revision number 6 only. 16 byte encrypted version of permissions.
|
||||||
|
QByteArray m_Perms;
|
||||||
|
|
||||||
|
/// Optional, meaningfull only if revision number is 4 or 5.
|
||||||
|
bool m_encryptMetadata = true;
|
||||||
|
|
||||||
|
/// First part of the id of the document
|
||||||
|
QByteArray m_ID;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace pdf
|
} // namespace pdf
|
||||||
|
@ -38,6 +38,7 @@
|
|||||||
#include <QTreeView>
|
#include <QTreeView>
|
||||||
#include <QLayout>
|
#include <QLayout>
|
||||||
#include <QHeaderView>
|
#include <QHeaderView>
|
||||||
|
#include <QInputDialog>
|
||||||
|
|
||||||
namespace pdfviewer
|
namespace pdfviewer
|
||||||
{
|
{
|
||||||
@ -268,9 +269,15 @@ void PDFViewerMainWindow::openDocument(const QString& fileName)
|
|||||||
// First close old document
|
// First close old document
|
||||||
closeDocument();
|
closeDocument();
|
||||||
|
|
||||||
|
// Password callback
|
||||||
|
auto getPasswordCallback = [this](bool* ok) -> QString
|
||||||
|
{
|
||||||
|
return QInputDialog::getText(this, tr("Encrypted document"), tr("Enter password to acces document content"), QLineEdit::Password, QString(), ok);
|
||||||
|
};
|
||||||
|
|
||||||
// Try to open a new document
|
// Try to open a new document
|
||||||
QApplication::setOverrideCursor(Qt::WaitCursor);
|
QApplication::setOverrideCursor(Qt::WaitCursor);
|
||||||
pdf::PDFDocumentReader reader;
|
pdf::PDFDocumentReader reader(qMove(getPasswordCallback));
|
||||||
pdf::PDFDocument document = reader.readFromFile(fileName);
|
pdf::PDFDocument document = reader.readFromFile(fileName);
|
||||||
QApplication::restoreOverrideCursor();
|
QApplication::restoreOverrideCursor();
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user