Handling of encrypted documents - basic functionality for standard encryption handler revision 2,3,4

This commit is contained in:
Jakub Melka 2019-08-10 17:24:12 +02:00
parent 0a6e7bb866
commit 0434a70de5
5 changed files with 455 additions and 9 deletions

View File

@ -33,8 +33,9 @@
namespace pdf
{
PDFDocumentReader::PDFDocumentReader() :
m_successfull(true)
PDFDocumentReader::PDFDocumentReader(const std::function<QString(bool*)>& getPasswordCallback) :
m_successfull(true),
m_getPasswordCallback(getPasswordCallback)
{
}
@ -277,18 +278,42 @@ PDFDocument PDFDocumentReader::readFromBuffer(const QByteArray& buffer)
}
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");
if (encryptObject.isReference())
{
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;
}
}
// 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

View File

@ -36,7 +36,7 @@ class PDFFORQTLIBSHARED_EXPORT PDFDocumentReader
Q_DECLARE_TR_FUNCTIONS(pdf::PDFDocumentReader)
public:
explicit PDFDocumentReader();
explicit PDFDocumentReader(const std::function<QString(bool*)>& getPasswordCallback);
constexpr inline PDFDocumentReader(const PDFDocumentReader&) = delete;
constexpr inline PDFDocumentReader(PDFDocumentReader&&) = delete;
@ -91,6 +91,9 @@ private:
/// Version of the scanned file
PDFVersion m_version;
/// Callback to obtain password from the user
std::function<QString(bool*)> m_getPasswordCallback;
};
} // namespace pdf

View File

@ -19,11 +19,22 @@
#include "pdfexception.h"
#include <openssl/rc4.h>
#include <openssl/md5.h>
#include <array>
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())
{
@ -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)));
}
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

View File

@ -25,6 +25,7 @@
#include <QSharedPointer>
#include <map>
#include <functional>
namespace pdf
{
@ -73,14 +74,24 @@ public:
explicit PDFSecurityHandler() = default;
virtual ~PDFSecurityHandler() = default;
enum class AuthorizationResult
{
UserAuthorized,
OwnerAuthorized,
Failed,
Cancelled
};
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
/// "None" security handler is created. If error occurs, then exception is thrown.
/// \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
/// PDF specification. Other values are invalid.
int m_V = 0;
@ -107,6 +118,7 @@ class PDFNoneSecurityHandler : public PDFSecurityHandler
{
public:
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
@ -115,9 +127,67 @@ class PDFStandardSecurityHandler : public PDFSecurityHandler
{
public:
virtual EncryptionMode getMode() const { return EncryptionMode::Standard; }
virtual AuthorizationResult authenticate(const std::function<QString(bool*)>& getPasswordCallback) override;
struct AuthorizationData
{
};
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

View File

@ -38,6 +38,7 @@
#include <QTreeView>
#include <QLayout>
#include <QHeaderView>
#include <QInputDialog>
namespace pdfviewer
{
@ -268,9 +269,15 @@ void PDFViewerMainWindow::openDocument(const QString& fileName)
// First close old document
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
QApplication::setOverrideCursor(Qt::WaitCursor);
pdf::PDFDocumentReader reader;
pdf::PDFDocumentReader reader(qMove(getPasswordCallback));
pdf::PDFDocument document = reader.readFromFile(fileName);
QApplication::restoreOverrideCursor();