Public key encryption: authorization, create file encryption key

This commit is contained in:
Jakub Melka 2022-06-18 14:05:10 +02:00
parent c20959c190
commit 44fc1e021c
4 changed files with 298 additions and 129 deletions

View File

@ -10,7 +10,6 @@ Section 6: OK
Section 7: Issues
- 7.4.7 JBIG2 decoder ammendments 1 and 2 not implemented
- 7.6.5 Public-key security handlers not implemented
- 7.6.7 Unencrypted wrapper document not implemented
- 7.10.2 Only linear interpolation for sampled function is performed
@ -71,7 +70,7 @@ Section 12: Issues
to PDF specification, but all unicode characters can be used.
- 12.7.5.5 Signature field locking not implemented and signature
seed dictionary is ignored
- 12.7.6.2 Sumbit form action not implemented in viewer
- 12.7.6.2 Submit form action not implemented in viewer
- 12.7.6.4 Import data action not implemented in viewer
- 12.7.8 Form data format not implemented
- 12.8 Modification detection using UR3/DocMDP method is not

View File

@ -140,7 +140,8 @@ QFileInfoList PDFCertificateManager::getCertificates()
QString PDFCertificateManager::getCertificateDirectory()
{
QDir directory(QStandardPaths::standardLocations(QStandardPaths::AppDataLocation).front() + "/certificates/");
QString standardDataLocation = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation).front();
QDir directory(standardDataLocation + "/certificates/");
return directory.absolutePath();
}

View File

@ -1,4 +1,4 @@
// Copyright (C) 2019-2022 Jakub Melka
// Copyright (C) 2019-2022 Jakub Melka
//
// This file is part of PDF4QT.
//
@ -22,6 +22,7 @@
#include "pdfutils.h"
#include "pdfdocumentbuilder.h"
#include "pdfdbgheap.h"
#include "pdfcertificatemanager.h"
#include <QRandomGenerator>
@ -29,12 +30,18 @@
#include <openssl/md5.h>
#include <openssl/aes.h>
#include <openssl/sha.h>
#include <openssl/pkcs7.h>
#include <openssl/pkcs12.h>
#include <openssl/evp.h>
#include <array>
namespace pdf
{
template<typename T>
using openssl_ptr = std::unique_ptr<T, void(*)(T*)>;
// Padding password
static constexpr std::array<uint8_t, 32> PDFPasswordPadding = {
0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41,
@ -444,6 +451,15 @@ PDFSecurityHandlerPointer PDFSecurityHandler::createSecurityHandler(const PDFObj
{
auto typedHandler = qSharedPointerDynamicCast<PDFPublicKeySecurityHandler>(handler);
typedHandler->m_filterDefault.recipients = parseRecipients(dictionary);
if (typedHandler->m_filterDefault.recipients.isEmpty())
{
auto it = typedHandler->m_cryptFilters.find("DefaultCryptFilter");
if (it != typedHandler->m_cryptFilters.end())
{
typedHandler->m_filterDefault = it->second;
}
}
break;
}
@ -1047,6 +1063,118 @@ QByteArray PDFStandardOrPublicSecurityHandler::encryptByFilter(const QByteArray&
return encryptUsingFilter(data, it->second, reference);
}
bool PDFStandardOrPublicSecurityHandler::isUnicodeNonAsciiSpaceCharacter(ushort unicode)
{
switch (unicode)
{
case 0x00A0:
case 0x1680:
case 0x2000:
case 0x2001:
case 0x2002:
case 0x2003:
case 0x2004:
case 0x2005:
case 0x2006:
case 0x2007:
case 0x2008:
case 0x2009:
case 0x200A:
case 0x200B:
case 0x202F:
case 0x205F:
case 0x3000:
return true;
default:
return false;
}
}
bool PDFStandardOrPublicSecurityHandler::isUnicodeMappedToNothing(ushort unicode)
{
switch (unicode)
{
case 0x00AD:
case 0x034F:
case 0x1806:
case 0x180B:
case 0x180C:
case 0x180D:
case 0x200B:
case 0x200C:
case 0x200D:
return true;
default:
return false;
}
}
QByteArray PDFStandardOrPublicSecurityHandler::adjustPassword(const QString& password, int revision)
{
QByteArray result;
switch (revision)
{
case 2:
case 3:
case 4:
{
// According to the PDF specification, convert string to PDFDocEncoding encoding
result = PDFEncoding::convertToEncoding(password, PDFEncoding::Encoding::PDFDoc);
break;
}
case 5:
case 6:
{
// According to the PDF specification, use SASLprep profile for stringprep RFC 4013, please see these websites:
// - RFC 4013: https://tools.ietf.org/html/rfc4013 (SASLprep profile for stringprep algorithm)
// - RFC 3454: https://tools.ietf.org/html/rfc3454 (stringprep algorithm - preparation of internationalized strings)
//
// Note: we don't do checks according the RFC 4013, just use the mapping and normalize string in KC
QString preparedPassword;
preparedPassword.reserve(password.size());
// RFC 4013 Section 2.1, use mapping
for (const QChar character : password)
{
if (isUnicodeMappedToNothing(character.unicode()))
{
// Mapped to nothing
continue;
}
if (isUnicodeNonAsciiSpaceCharacter(character.unicode()))
{
// Map to space character
preparedPassword += QChar(QChar::Space);
}
else
{
preparedPassword += character;
}
}
// RFC 4013, Section 2.2, normalization to KC
preparedPassword = preparedPassword.normalized(QString::NormalizationForm_KC);
// We don't do other checks. We will transform password to the UTF-8 encoding
// and according the PDF specification, we take only first 127 characters.
result = preparedPassword.toUtf8().left(127);
}
default:
result = password.toLatin1();
break;
}
return result;
}
PDFSecurityHandler* PDFStandardSecurityHandler::clone() const
{
return new PDFStandardSecurityHandler(*this);
@ -1653,117 +1781,6 @@ PDFStandardSecurityHandler::UserOwnerData_r6 PDFStandardSecurityHandler::parsePa
return result;
}
QByteArray PDFStandardSecurityHandler::adjustPassword(const QString& password, int revision)
{
QByteArray result;
switch (revision)
{
case 2:
case 3:
case 4:
{
// According to the PDF specification, convert string to PDFDocEncoding encoding
result = PDFEncoding::convertToEncoding(password, PDFEncoding::Encoding::PDFDoc);
break;
}
case 5:
case 6:
{
// According to the PDF specification, use SASLprep profile for stringprep RFC 4013, please see these websites:
// - RFC 4013: https://tools.ietf.org/html/rfc4013 (SASLprep profile for stringprep algorithm)
// - RFC 3454: https://tools.ietf.org/html/rfc3454 (stringprep algorithm - preparation of internationalized strings)
//
// Note: we don't do checks according the RFC 4013, just use the mapping and normalize string in KC
QString preparedPassword;
preparedPassword.reserve(password.size());
// RFC 4013 Section 2.1, use mapping
for (const QChar character : password)
{
if (isUnicodeMappedToNothing(character.unicode()))
{
// Mapped to nothing
continue;
}
if (isUnicodeNonAsciiSpaceCharacter(character.unicode()))
{
// Map to space character
preparedPassword += QChar(QChar::Space);
}
else
{
preparedPassword += character;
}
}
// RFC 4013, Section 2.2, normalization to KC
preparedPassword = preparedPassword.normalized(QString::NormalizationForm_KC);
// We don't do other checks. We will transform password to the UTF-8 encoding
// and according the PDF specification, we take only first 127 characters.
result = preparedPassword.toUtf8().left(127);
}
default:
break;
}
return result;
}
bool PDFStandardSecurityHandler::isUnicodeNonAsciiSpaceCharacter(ushort unicode)
{
switch (unicode)
{
case 0x00A0:
case 0x1680:
case 0x2000:
case 0x2001:
case 0x2002:
case 0x2003:
case 0x2004:
case 0x2005:
case 0x2006:
case 0x2007:
case 0x2008:
case 0x2009:
case 0x200A:
case 0x200B:
case 0x202F:
case 0x205F:
case 0x3000:
return true;
default:
return false;
}
}
bool PDFStandardSecurityHandler::isUnicodeMappedToNothing(ushort unicode)
{
switch (unicode)
{
case 0x00AD:
case 0x034F:
case 0x1806:
case 0x180B:
case 0x180C:
case 0x180D:
case 0x200B:
case 0x200C:
case 0x200D:
return true;
default:
return false;
}
}
PDFSecurityHandlerPointer PDFSecurityHandlerFactory::createSecurityHandler(const SecuritySettings& settings)
{
if (settings.algorithm == Algorithm::None)
@ -2125,7 +2142,159 @@ PDFSecurityHandler* PDFPublicKeySecurityHandler::clone() const
PDFSecurityHandler::AuthorizationResult PDFPublicKeySecurityHandler::authenticate(const std::function<QString (bool*)>& getPasswordCallback, bool authorizeOwnerOnly)
{
return AuthorizationResult::Failed;
// Clear the authorization data
m_authorizationData = AuthorizationData();
if (authorizeOwnerOnly)
{
return AuthorizationResult::Failed;
}
switch (m_keyLength)
{
case 128:
case 256:
break;
default:
return AuthorizationResult::Failed;
}
bool passwordObtained = true;
constexpr int revision = 0;
QByteArray password = adjustPassword(getPasswordCallback(&passwordObtained), revision);
QFileInfoList certificates = PDFCertificateManager::getCertificates();
if (certificates.isEmpty())
{
return AuthorizationResult::Failed;
}
std::vector<openssl_ptr<PKCS7>> recipients;
for (const QByteArray& recipient : m_filterDefault.recipients)
{
const unsigned char* data = convertByteArrayToUcharPtr(recipient);
if (PKCS7* pkcs7 = d2i_PKCS7(nullptr, &data, recipient.size()))
{
recipients.emplace_back(pkcs7, PKCS7_free);
}
}
while (passwordObtained)
{
// We will iterate trough all certificates
for (const QFileInfo& certificateFileInfo : certificates)
{
if (!PDFCertificateManager::isCertificateValid(certificateFileInfo.absoluteFilePath(), password))
{
continue;
}
QFile file(certificateFileInfo.absoluteFilePath());
if (file.open(QFile::ReadOnly))
{
QByteArray data = file.readAll();
file.close();
openssl_ptr<BIO> pksBuffer(BIO_new(BIO_s_mem()), &BIO_free_all);
BIO_write(pksBuffer.get(), data.constData(), data.length());
openssl_ptr<PKCS12> pkcs12(d2i_PKCS12_bio(pksBuffer.get(), nullptr), &PKCS12_free);
if (pkcs12)
{
const char* passwordPointer = nullptr;
if (!password.isEmpty())
{
passwordPointer = password.constData();
}
EVP_PKEY* keyPtr = nullptr;
X509* certificatePtr = nullptr;
STACK_OF(X509)* certificatesPtr = nullptr;
// Parse PKCS12 with password
bool isParsed = PKCS12_parse(pkcs12.get(), passwordPointer, &keyPtr, &certificatePtr, &certificatesPtr) == 1;
if (!isParsed)
{
continue;
}
openssl_ptr<EVP_PKEY> key(keyPtr, EVP_PKEY_free);
openssl_ptr<X509> certificate(certificatePtr, X509_free);
openssl_ptr<STACK_OF(X509)> certificates(certificatesPtr, sk_X509_free);
for (const auto& recipientItem : recipients)
{
PKCS7* pkcs7 = recipientItem.get();
openssl_ptr<BIO> dataBuffer(BIO_new(BIO_s_mem()), BIO_free_all);
if (PKCS7_decrypt(pkcs7, keyPtr, certificatePtr, dataBuffer.get(), 0) == 1)
{
BUF_MEM* memoryBuffer = nullptr;
BIO_get_mem_ptr(dataBuffer.get(), &memoryBuffer);
// Acc. to chapter 7.6.5.3 - decrypted data
QByteArray decryptedData(memoryBuffer->data, int(memoryBuffer->length));
// Calculate file encryption key
EVP_MD_CTX* context = EVP_MD_CTX_new();
Q_ASSERT(context);
switch (m_keyLength)
{
case 128:
EVP_DigestInit(context, EVP_sha1());
break;
case 256:
EVP_DigestInit(context, EVP_sha256());
break;
default:
Q_ASSERT(false);
EVP_DigestInit(context, EVP_sha256());
break;
}
QByteArray seed = decryptedData.left(20);
// 7.6.5.3 a)
EVP_DigestUpdate(context, seed.constData(), seed.size());
// 7.6.5.3 b)
for (const QByteArray& recipient : m_filterDefault.recipients)
{
EVP_DigestUpdate(context, recipient.constData(), recipient.size());
}
// 7.6.5.3 c)
if (!isMetadataEncrypted())
{
constexpr uint32_t value = 0xFFFFFFFF;
EVP_DigestUpdate(context, &value, sizeof(value));
}
unsigned int size = EVP_MD_size(EVP_MD_CTX_md(context));
QByteArray digestBuffer(size, char());
EVP_DigestFinal_ex(context, convertByteArrayToUcharPtr(digestBuffer), &size);
EVP_MD_CTX_free(context);
m_authorizationData.fileEncryptionKey = digestBuffer.left(m_keyLength / 8);
m_authorizationData.authorizationResult = AuthorizationResult::UserAuthorized;
return AuthorizationResult::UserAuthorized;
}
}
}
}
}
password = adjustPassword(getPasswordCallback(&passwordObtained), revision);
}
return AuthorizationResult::Cancelled;
}
bool PDFPublicKeySecurityHandler::isMetadataEncrypted() const

View File

@ -264,6 +264,9 @@ public:
virtual AuthorizationResult getAuthorizationResult() const override { return m_authorizationData.authorizationResult; }
virtual bool isEncryptionAllowed() const override { return m_authorizationData.isAuthorized(); }
/// Adjusts the password according to the PDF specification
static QByteArray adjustPassword(const QString& password, int revision);
struct AuthorizationData
{
bool isAuthorized() const { return authorizationResult == AuthorizationResult::UserAuthorized || authorizationResult == AuthorizationResult::OwnerAuthorized; }
@ -287,6 +290,16 @@ protected:
/// \returns Encrypted data
QByteArray encryptUsingFilter(const QByteArray& data, CryptFilter filter, PDFObjectReference reference) const;
/// Returns true, if character with unicode code is non-ascii space character
/// according the RFC 3454, section C.1.2
/// \param unicode Unicode code to be tested
static bool isUnicodeNonAsciiSpaceCharacter(ushort unicode);
/// Returns true, if character with unicode code is mapped to nothing,
/// according the RFC 3454, section B.1
/// \param unicode Unicode code to be tested
static bool isUnicodeMappedToNothing(ushort unicode);
std::vector<uint8_t> createV2_ObjectEncryptionKey(PDFObjectReference reference, CryptFilter filter) const;
std::vector<uint8_t> createAESV2_ObjectEncryptionKey(PDFObjectReference reference) const;
CryptFilter getCryptFilter(EncryptionScope encryptionScope) const;
@ -307,9 +320,6 @@ public:
virtual bool isAllowed(Permission permission) const override { return m_authorizationData.authorizationResult == AuthorizationResult::OwnerAuthorized || (m_permissions & static_cast<uint32_t>(permission)); }
virtual PDFObject createEncryptionDictionaryObject() const override;
/// Adjusts the password according to the PDF specification
static QByteArray adjustPassword(const QString& password, int revision);
private:
friend class PDFSecurityHandler;
friend class PDFSecurityHandlerFactory;
@ -351,16 +361,6 @@ private:
/// Parses parts of the user/owner data (U/O values of the encryption dictionary)
UserOwnerData_r6 parseParts(const QByteArray& data) const;
/// Returns true, if character with unicode code is non-ascii space character
/// according the RFC 3454, section C.1.2
/// \param unicode Unicode code to be tested
static bool isUnicodeNonAsciiSpaceCharacter(ushort unicode);
/// Returns true, if character with unicode code is mapped to nothing,
/// according the RFC 3454, section B.1
/// \param unicode Unicode code to be tested
static bool isUnicodeMappedToNothing(ushort unicode);
/// Revision number of standard security number
int m_R = 0;