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

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