Public key encryption: initiate new security handler

This commit is contained in:
Jakub Melka 2022-06-26 15:49:23 +02:00
parent 6ff29d5d38
commit ac039a1539
6 changed files with 346 additions and 142 deletions

View File

@ -1823,9 +1823,26 @@ PDFSecurityHandlerPointer PDFSecurityHandlerFactory::createSecurityHandler(const
return PDFSecurityHandlerPointer(new PDFNoneSecurityHandler); return PDFSecurityHandlerPointer(new PDFNoneSecurityHandler);
} }
// Jakub Melka: create standard security handler, with given settings // Jakub Melka: create standard security or public key handler, with given settings
PDFStandardSecurityHandler* handler = new PDFStandardSecurityHandler(); PDFStandardSecurityHandler* standardHandler = nullptr;
handler->m_ID = settings.id; PDFPublicKeySecurityHandler* publicKeyHandler = nullptr;
PDFStandardOrPublicSecurityHandler* handler = nullptr;
if (settings.algorithm != Algorithm::Certificate)
{
standardHandler = new PDFStandardSecurityHandler();
handler = standardHandler;
}
else
{
publicKeyHandler = new PDFPublicKeySecurityHandler();
handler = publicKeyHandler;
}
if (standardHandler)
{
standardHandler->m_ID = settings.id;
}
const bool isEncryptingEmbeddedFilesOnly = settings.encryptContents == EncryptContents::EmbeddedFiles; const bool isEncryptingEmbeddedFilesOnly = settings.encryptContents == EncryptContents::EmbeddedFiles;
@ -1858,6 +1875,7 @@ PDFSecurityHandlerPointer PDFSecurityHandlerFactory::createSecurityHandler(const
} }
case AES_256: case AES_256:
case Certificate:
{ {
handler->m_V = 5; handler->m_V = 5;
handler->m_keyLength = 256; handler->m_keyLength = 256;
@ -1878,27 +1896,122 @@ PDFSecurityHandlerPointer PDFSecurityHandlerFactory::createSecurityHandler(const
CryptFilter identityFilter; CryptFilter identityFilter;
identityFilter.type = CryptFilterType::Identity; identityFilter.type = CryptFilterType::Identity;
if (standardHandler)
{
standardHandler->m_encryptMetadata = settings.encryptContents == All;
}
if (publicKeyHandler)
{
publicKeyHandler->m_filterDefault.encryptMetadata = settings.encryptContents == All;
publicKeyHandler->m_pkcs7Type = PDFPublicKeySecurityHandler::PKCS7_Type::PKCS7_S5;
publicKeyHandler->m_permissions = 0;
if (settings.permissions & uint32_t(PDFSecurityHandler::Permission::PrintLowResolution))
{
publicKeyHandler->m_permissions = publicKeyHandler->m_permissions | PDFPublicKeySecurityHandler::PKSH_PrintLowResolution;
}
if (settings.permissions & uint32_t(PDFSecurityHandler::Permission::Modify))
{
publicKeyHandler->m_permissions = publicKeyHandler->m_permissions | PDFPublicKeySecurityHandler::PKSH_Modify;
}
if (settings.permissions & uint32_t(PDFSecurityHandler::Permission::CopyContent))
{
publicKeyHandler->m_permissions = publicKeyHandler->m_permissions | PDFPublicKeySecurityHandler::PKSH_CopyContent;
}
if (settings.permissions & uint32_t(PDFSecurityHandler::Permission::ModifyInteractiveItems))
{
publicKeyHandler->m_permissions = publicKeyHandler->m_permissions | PDFPublicKeySecurityHandler::PKSH_ModifyAnnotationsFillFormFields;
}
if (settings.permissions & uint32_t(PDFSecurityHandler::Permission::ModifyFormFields))
{
publicKeyHandler->m_permissions = publicKeyHandler->m_permissions | PDFPublicKeySecurityHandler::PKSH_FillFormFields;
}
if (settings.permissions & uint32_t(PDFSecurityHandler::Permission::Assemble))
{
publicKeyHandler->m_permissions = publicKeyHandler->m_permissions | PDFPublicKeySecurityHandler::PKSH_Assemble;
}
if (settings.permissions & uint32_t(PDFSecurityHandler::Permission::PrintHighResolution))
{
publicKeyHandler->m_permissions = publicKeyHandler->m_permissions | PDFPublicKeySecurityHandler::PKSH_PrintHighResolution;
}
QFile file(settings.certificateFileName);
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)
{
QByteArray password = PDFStandardOrPublicSecurityHandler::adjustPassword(settings.userPassword, 0);
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)
{
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);
openssl_ptr<BIO> dataToBeSigned(BIO_new(BIO_s_mem()), BIO_free_all);
uint32_t permissions = qToLittleEndian(publicKeyHandler->m_permissions);
QRandomGenerator generator = QRandomGenerator::securelySeeded();
QByteArray randomKey = generateRandomByteArray(generator, 20);
BIO_write(dataToBeSigned.get(), randomKey.data(), randomKey.length());
BIO_write(dataToBeSigned.get(), &permissions, sizeof(permissions));
openssl_ptr<PKCS7> pkcs7(PKCS7_sign(certificate.get(), key.get(), certificates.get(), dataToBeSigned.get(), PKCS7_BINARY), PKCS7_free);
if (pkcs7)
{
openssl_ptr<BIO> storedData(BIO_new(BIO_s_mem()), BIO_free_all);
if (i2d_PKCS7_bio(storedData.get(), pkcs7.get()))
{
BUF_MEM* memoryBuffer = nullptr;
BIO_get_mem_ptr(storedData.get(), &memoryBuffer);
QByteArray recipient(memoryBuffer->data, int(memoryBuffer->length));
publicKeyHandler->m_filterDefault.recipients << recipient;
}
}
}
}
}
}
switch (settings.encryptContents) switch (settings.encryptContents)
{ {
case All: case All:
handler->m_filterStrings = handler->m_filterDefault; handler->m_filterStrings = handler->m_filterDefault;
handler->m_filterStreams = handler->m_filterDefault; handler->m_filterStreams = handler->m_filterDefault;
handler->m_filterEmbeddedFiles = handler->m_filterDefault; handler->m_filterEmbeddedFiles = handler->m_filterDefault;
handler->m_encryptMetadata = true;
break; break;
case AllExceptMetadata: case AllExceptMetadata:
handler->m_filterStrings = handler->m_filterDefault; handler->m_filterStrings = handler->m_filterDefault;
handler->m_filterStreams = handler->m_filterDefault; handler->m_filterStreams = handler->m_filterDefault;
handler->m_filterEmbeddedFiles = handler->m_filterDefault; handler->m_filterEmbeddedFiles = handler->m_filterDefault;
handler->m_encryptMetadata = false;
break; break;
case EmbeddedFiles: case EmbeddedFiles:
handler->m_filterStrings = identityFilter; handler->m_filterStrings = identityFilter;
handler->m_filterStreams = identityFilter; handler->m_filterStreams = identityFilter;
handler->m_filterEmbeddedFiles = handler->m_filterDefault; handler->m_filterEmbeddedFiles = handler->m_filterDefault;
handler->m_encryptMetadata = false;
break; break;
default: default:
@ -1906,135 +2019,148 @@ PDFSecurityHandlerPointer PDFSecurityHandlerFactory::createSecurityHandler(const
break; break;
} }
handler->m_filterDefault.encryptMetadata = settings.encryptContents == All;
handler->m_cryptFilters["StdCF"] = handler->m_filterDefault; handler->m_cryptFilters["StdCF"] = handler->m_filterDefault;
handler->m_R = getRevisionFromAlgorithm(settings.algorithm);
handler->m_permissions = settings.permissions | 0xFFFFF000;
QByteArray adjustedOwnerPassword = handler->adjustPassword(settings.ownerPassword, handler->m_R); if (standardHandler)
QByteArray adjustedUserPassword = handler->adjustPassword(settings.userPassword, handler->m_R);
// Generate encryption entries
switch (handler->m_R)
{ {
case 2: standardHandler->m_R = getRevisionFromAlgorithm(settings.algorithm);
case 3: standardHandler->m_permissions = settings.permissions | 0xFFFFF000;
case 4:
QByteArray adjustedOwnerPassword = handler->adjustPassword(settings.ownerPassword, standardHandler->m_R);
QByteArray adjustedUserPassword = handler->adjustPassword(settings.userPassword, standardHandler->m_R);
// Generate encryption entries
switch (standardHandler->m_R)
{ {
// Trick for computing "O" entry for revisions 2,3,4: in O entry, there is stored case 2:
// user password encrypted by owner password. Because RC4 cipher is symmetric, we case 3:
// can store user password in "O" entry and then use standard function to retrieve case 4:
// 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()); // 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 = standardHandler->createPaddedPassword32(adjustedUserPassword);
QByteArray paddedUserPassword;
paddedUserPassword.resize(int(paddedUserPasswordArray.size()));
std::copy(paddedUserPasswordArray.cbegin(), paddedUserPasswordArray.cend(), paddedUserPassword.data());
standardHandler->m_O = paddedUserPassword;
QByteArray entryO = standardHandler->createUserPasswordFromOwnerPassword(adjustedOwnerPassword);
standardHandler->m_O = entryO;
Q_ASSERT(standardHandler->createUserPasswordFromOwnerPassword(adjustedOwnerPassword) == paddedUserPassword);
standardHandler->m_U.resize(32);
QRandomGenerator randomNumberGenerator = QRandomGenerator::securelySeeded();
for (int i = 0; i < standardHandler->m_U.size(); ++i)
{
standardHandler->m_U[i] = char(randomNumberGenerator.generate());
}
QByteArray fileEncryptionKey = standardHandler->createFileEncryptionKey(paddedUserPassword);
QByteArray U = standardHandler->createEntryValueU_r234(fileEncryptionKey);
standardHandler->m_U = U;
break;
} }
QByteArray fileEncryptionKey = handler->createFileEncryptionKey(paddedUserPassword); case 6:
QByteArray U = handler->createEntryValueU_r234(fileEncryptionKey); {
handler->m_U = U; PDFStandardSecurityHandler::UserOwnerData_r6 userData;
PDFStandardSecurityHandler::UserOwnerData_r6 ownerData;
break; QRandomGenerator randomNumberGenerator = QRandomGenerator::securelySeeded();
}
case 6: // Generate file encryption key
{ handler->m_authorizationData.fileEncryptionKey = generateRandomByteArray(randomNumberGenerator, 32);
PDFStandardSecurityHandler::UserOwnerData_r6 userData; handler->m_authorizationData.authorizationResult = PDFSecurityHandler::AuthorizationResult::OwnerAuthorized;
PDFStandardSecurityHandler::UserOwnerData_r6 ownerData;
QRandomGenerator randomNumberGenerator = QRandomGenerator::securelySeeded(); // Compute m_U entry
userData.keySalt = generateRandomByteArray(randomNumberGenerator, 8);
userData.validationSalt = generateRandomByteArray(randomNumberGenerator, 8);
userData.hash = standardHandler->createHash_r6(adjustedUserPassword + userData.validationSalt, adjustedUserPassword, false);
standardHandler->m_U = userData.hash + userData.validationSalt + userData.keySalt;
// Generate file encryption key // Compute m_UE entry
handler->m_authorizationData.fileEncryptionKey = generateRandomByteArray(randomNumberGenerator, 32); QByteArray userFileEncryptionKeyInputData = adjustedUserPassword + userData.keySalt;
handler->m_authorizationData.authorizationResult = PDFSecurityHandler::AuthorizationResult::OwnerAuthorized; QByteArray userFileEncryptionKey = standardHandler->createHash_r6(userFileEncryptionKeyInputData, adjustedUserPassword, false);
// Compute m_U entry Q_ASSERT(userFileEncryptionKey.size() == 32);
userData.keySalt = generateRandomByteArray(randomNumberGenerator, 8); AES_KEY userKey = { };
userData.validationSalt = generateRandomByteArray(randomNumberGenerator, 8); AES_set_encrypt_key(convertByteArrayToUcharPtr(userFileEncryptionKey), userFileEncryptionKey.size() * 8, &userKey);
userData.hash = handler->createHash_r6(adjustedUserPassword + userData.validationSalt, adjustedUserPassword, false); unsigned char aesUserInitializationVector[AES_BLOCK_SIZE] = { };
handler->m_U = userData.hash + userData.validationSalt + userData.keySalt; standardHandler->m_UE.resize(handler->m_authorizationData.fileEncryptionKey.size());
unsigned char* userInputBuffer = convertByteArrayToUcharPtr(handler->m_authorizationData.fileEncryptionKey);
unsigned char* userTargetBuffer = convertByteArrayToUcharPtr(standardHandler->m_UE);
AES_cbc_encrypt(userInputBuffer, userTargetBuffer, standardHandler->m_UE.size(), &userKey, aesUserInitializationVector, AES_ENCRYPT);
// Compute m_UE entry // Compute m_O entry
QByteArray userFileEncryptionKeyInputData = adjustedUserPassword + userData.keySalt; ownerData.keySalt = generateRandomByteArray(randomNumberGenerator, 8);
QByteArray userFileEncryptionKey = handler->createHash_r6(userFileEncryptionKeyInputData, adjustedUserPassword, false); ownerData.validationSalt = generateRandomByteArray(randomNumberGenerator, 8);
ownerData.hash = standardHandler->createHash_r6(adjustedOwnerPassword + ownerData.validationSalt + standardHandler->m_U, adjustedOwnerPassword, true);
standardHandler->m_O = ownerData.hash + ownerData.validationSalt + ownerData.keySalt;
Q_ASSERT(userFileEncryptionKey.size() == 32); // Compute m_OE entry
AES_KEY userKey = { }; QByteArray ownerFileEncryptionKeyInputData = adjustedOwnerPassword + ownerData.keySalt + standardHandler->m_U;
AES_set_encrypt_key(convertByteArrayToUcharPtr(userFileEncryptionKey), userFileEncryptionKey.size() * 8, &userKey); QByteArray ownerFileEncryptionKey = standardHandler->createHash_r6(ownerFileEncryptionKeyInputData, adjustedOwnerPassword, true);
unsigned char aesUserInitializationVector[AES_BLOCK_SIZE] = { };
handler->m_UE.resize(handler->m_authorizationData.fileEncryptionKey.size());
unsigned char* userInputBuffer = convertByteArrayToUcharPtr(handler->m_authorizationData.fileEncryptionKey);
unsigned char* userTargetBuffer = convertByteArrayToUcharPtr(handler->m_UE);
AES_cbc_encrypt(userInputBuffer, userTargetBuffer, handler->m_UE.size(), &userKey, aesUserInitializationVector, AES_ENCRYPT);
// Compute m_O entry AES_KEY ownerKey = { };
ownerData.keySalt = generateRandomByteArray(randomNumberGenerator, 8); AES_set_encrypt_key(convertByteArrayToUcharPtr(ownerFileEncryptionKey), ownerFileEncryptionKey.size() * 8, &ownerKey);
ownerData.validationSalt = generateRandomByteArray(randomNumberGenerator, 8); unsigned char aesOwnerInitializationVector[AES_BLOCK_SIZE] = { };
ownerData.hash = handler->createHash_r6(adjustedOwnerPassword + ownerData.validationSalt + handler->m_U, adjustedOwnerPassword, true); standardHandler->m_OE.resize(handler->m_authorizationData.fileEncryptionKey.size());
handler->m_O = ownerData.hash + ownerData.validationSalt + ownerData.keySalt; unsigned char* ownerInputBuffer = convertByteArrayToUcharPtr(handler->m_authorizationData.fileEncryptionKey);
unsigned char* ownerTargetBuffer = convertByteArrayToUcharPtr(standardHandler->m_OE);
AES_cbc_encrypt(ownerInputBuffer, ownerTargetBuffer, standardHandler->m_OE.size(), &ownerKey, aesOwnerInitializationVector, AES_ENCRYPT);
// Compute m_OE entry // Perms entry
QByteArray ownerFileEncryptionKeyInputData = adjustedOwnerPassword + ownerData.keySalt + handler->m_U; standardHandler->m_Perms = QByteArray(AES_BLOCK_SIZE, char(0));
QByteArray ownerFileEncryptionKey = handler->createHash_r6(ownerFileEncryptionKeyInputData, adjustedOwnerPassword, true); unsigned char* permsData = convertByteArrayToUcharPtr(standardHandler->m_Perms);
permsData[0] = standardHandler->m_permissions & 0xFF;
permsData[1] = (standardHandler->m_permissions >> 8) & 0xFF;
permsData[2] = (standardHandler->m_permissions >> 16) & 0xFF;
permsData[3] = (standardHandler->m_permissions >> 24) & 0xFF;
permsData[4] = 0xFF;
permsData[5] = 0xFF;
permsData[6] = 0xFF;
permsData[7] = 0xFF;
permsData[8] = standardHandler->m_encryptMetadata ? 'T' : 'F';
permsData[9] = 'a';
permsData[10] = 'd';
permsData[11] = 'b';
permsData[12] = randomNumberGenerator.generate() & 0xFF;
permsData[13] = randomNumberGenerator.generate() & 0xFF;
permsData[14] = randomNumberGenerator.generate() & 0xFF;
permsData[15] = randomNumberGenerator.generate() & 0xFF;
AES_KEY ownerKey = { }; Q_ASSERT(standardHandler->m_Perms.size() == AES_BLOCK_SIZE);
AES_set_encrypt_key(convertByteArrayToUcharPtr(ownerFileEncryptionKey), ownerFileEncryptionKey.size() * 8, &ownerKey); AES_KEY key = { };
unsigned char aesOwnerInitializationVector[AES_BLOCK_SIZE] = { }; AES_set_encrypt_key(convertByteArrayToUcharPtr(handler->m_authorizationData.fileEncryptionKey), handler->m_authorizationData.fileEncryptionKey.size() * 8, &key);
handler->m_OE.resize(handler->m_authorizationData.fileEncryptionKey.size()); AES_ecb_encrypt(convertByteArrayToUcharPtr(standardHandler->m_Perms), convertByteArrayToUcharPtr(standardHandler->m_Perms), &key, AES_ENCRYPT);
unsigned char* ownerInputBuffer = convertByteArrayToUcharPtr(handler->m_authorizationData.fileEncryptionKey);
unsigned char* ownerTargetBuffer = convertByteArrayToUcharPtr(handler->m_OE);
AES_cbc_encrypt(ownerInputBuffer, ownerTargetBuffer, handler->m_OE.size(), &ownerKey, aesOwnerInitializationVector, AES_ENCRYPT);
// Perms entry break;
handler->m_Perms = QByteArray(AES_BLOCK_SIZE, char(0)); }
unsigned char* permsData = convertByteArrayToUcharPtr(handler->m_Perms);
permsData[0] = handler->m_permissions & 0xFF;
permsData[1] = (handler->m_permissions >> 8) & 0xFF;
permsData[2] = (handler->m_permissions >> 16) & 0xFF;
permsData[3] = (handler->m_permissions >> 24) & 0xFF;
permsData[4] = 0xFF;
permsData[5] = 0xFF;
permsData[6] = 0xFF;
permsData[7] = 0xFF;
permsData[8] = handler->m_encryptMetadata ? 'T' : 'F';
permsData[9] = 'a';
permsData[10] = 'd';
permsData[11] = 'b';
permsData[12] = randomNumberGenerator.generate() & 0xFF;
permsData[13] = randomNumberGenerator.generate() & 0xFF;
permsData[14] = randomNumberGenerator.generate() & 0xFF;
permsData[15] = randomNumberGenerator.generate() & 0xFF;
Q_ASSERT(handler->m_Perms.size() == AES_BLOCK_SIZE); default:
AES_KEY key = { }; {
AES_set_encrypt_key(convertByteArrayToUcharPtr(handler->m_authorizationData.fileEncryptionKey), handler->m_authorizationData.fileEncryptionKey.size() * 8, &key); Q_ASSERT(false);
AES_ecb_encrypt(convertByteArrayToUcharPtr(handler->m_Perms), convertByteArrayToUcharPtr(handler->m_Perms), &key, AES_ENCRYPT); break;
}
break;
}
default:
{
Q_ASSERT(false);
break;
} }
} }
PDFSecurityHandlerPointer handlerPointer(handler);
bool firstTry = true; bool firstTry = true;
handler->authenticate([&settings, &firstTry](bool* b) { *b = firstTry; firstTry = false; return settings.ownerPassword; }, true); const bool isPublicKeySecurity = settings.algorithm == Algorithm::Certificate;
Q_ASSERT(handler->getAuthorizationResult() == PDFSecurityHandler::AuthorizationResult::OwnerAuthorized); auto passwordCallback = [isPublicKeySecurity, &settings, &firstTry](bool* b) { *b = firstTry; firstTry = false; return !isPublicKeySecurity ? settings.ownerPassword : settings.userPassword; };
return PDFSecurityHandlerPointer(handler); handler->authenticate(passwordCallback, !isPublicKeySecurity);
if (handler->getAuthorizationResult() == PDFSecurityHandler::AuthorizationResult::OwnerAuthorized ||
(isPublicKeySecurity && handler->getAuthorizationResult() == PDFSecurityHandler::AuthorizationResult::UserAuthorized))
{
return handlerPointer;
}
return nullptr;
} }
int PDFSecurityHandlerFactory::getPasswordOptimalEntropy() int PDFSecurityHandlerFactory::getPasswordOptimalEntropy()
@ -2104,6 +2230,9 @@ int PDFSecurityHandlerFactory::getRevisionFromAlgorithm(Algorithm algorithm)
case AES_256: case AES_256:
return 6; return 6;
case Certificate:
return 0;
default: default:
Q_ASSERT(false); Q_ASSERT(false);
break; break;
@ -2161,6 +2290,17 @@ bool PDFSecurityHandlerFactory::validate(const SecuritySettings& settings, QStri
case pdf::PDFSecurityHandlerFactory::AES_256: case pdf::PDFSecurityHandlerFactory::AES_256:
break; break;
case pdf::PDFSecurityHandlerFactory::Certificate:
{
if (!pdf::PDFCertificateManager::isCertificateValid(settings.certificateFileName, settings.userPassword))
{
*errorMessage = tr("Invalid certificate or password.");
return false;
}
break;
}
default: default:
Q_ASSERT(false); Q_ASSERT(false);
break; break;
@ -2265,7 +2405,7 @@ PDFSecurityHandler::AuthorizationResult PDFPublicKeySecurityHandler::authenticat
PKCS7* pkcs7 = recipientItem.get(); PKCS7* pkcs7 = recipientItem.get();
openssl_ptr<BIO> dataBuffer(BIO_new(BIO_s_mem()), BIO_free_all); openssl_ptr<BIO> dataBuffer(BIO_new(BIO_s_mem()), BIO_free_all);
if (PKCS7_decrypt(pkcs7, keyPtr, certificatePtr, dataBuffer.get(), 0) == 1) if (PKCS7_decrypt(pkcs7, keyPtr, certificatePtr, dataBuffer.get(), PKCS7_BINARY) == 1)
{ {
BUF_MEM* memoryBuffer = nullptr; BUF_MEM* memoryBuffer = nullptr;
BIO_get_mem_ptr(dataBuffer.get(), &memoryBuffer); BIO_get_mem_ptr(dataBuffer.get(), &memoryBuffer);
@ -2351,18 +2491,6 @@ bool PDFPublicKeySecurityHandler::isAllowed(Permission permission) const
return m_permissions & static_cast<uint32_t>(permission); return m_permissions & static_cast<uint32_t>(permission);
} }
enum PermissionFlag : uint32_t
{
PKSH_Owner = 1 << 1,
PKSH_PrintLowResolution = 1 << 2,
PKSH_Modify = 1 << 3,
PKSH_CopyContent = 1 << 4,
PKSH_ModifyAnnotationsFillFormFields = 1 << 5,
PKSH_FillFormFields = 1 << 8,
PKSH_Assemble = 1 << 10,
PKSH_PrintHighResolution = 1 << 11
};
if (m_permissions & PKSH_Owner) if (m_permissions & PKSH_Owner)
{ {
return true; return true;

View File

@ -201,6 +201,8 @@ public:
static PDFSecurityHandlerPointer createSecurityHandler(const PDFObject& encryptionDictionaryObject, const QByteArray& id); static PDFSecurityHandlerPointer createSecurityHandler(const PDFObject& encryptionDictionaryObject, const QByteArray& id);
protected: protected:
friend class PDFSecurityHandlerFactory;
static bool parseBool(const PDFDictionary* dictionary, const char* key, bool required, bool defaultValue = true); static bool parseBool(const PDFDictionary* dictionary, const char* key, bool required, bool defaultValue = true);
static QByteArray parseName(const PDFDictionary* dictionary, const char* key, bool required, const char* defaultValue = nullptr); static QByteArray parseName(const PDFDictionary* dictionary, const char* key, bool required, const char* defaultValue = nullptr);
static PDFInteger parseInt(const PDFDictionary* dictionary, const char* key, bool required, PDFInteger defaultValue = -1); static PDFInteger parseInt(const PDFDictionary* dictionary, const char* key, bool required, PDFInteger defaultValue = -1);
@ -278,6 +280,8 @@ public:
}; };
protected: protected:
friend class PDFSecurityHandlerFactory;
/// Decrypts data using specified filter. This function can be called only, if authorization was successfull. /// Decrypts data using specified filter. This function can be called only, if authorization was successfull.
/// \param data Data to be decrypted /// \param data Data to be decrypted
/// \param filter Filter to be used for decryption /// \param filter Filter to be used for decryption
@ -421,6 +425,18 @@ private:
PKCS7_S5 PKCS7_S5
}; };
enum PermissionFlag : uint32_t
{
PKSH_Owner = 1 << 1,
PKSH_PrintLowResolution = 1 << 2,
PKSH_Modify = 1 << 3,
PKSH_CopyContent = 1 << 4,
PKSH_ModifyAnnotationsFillFormFields = 1 << 5,
PKSH_FillFormFields = 1 << 8,
PKSH_Assemble = 1 << 10,
PKSH_PrintHighResolution = 1 << 11
};
/// What operations shall be permitted, when document is opened with user access. /// What operations shall be permitted, when document is opened with user access.
uint32_t m_permissions = 0; uint32_t m_permissions = 0;
@ -440,7 +456,8 @@ public:
None, None,
RC4, RC4,
AES_128, AES_128,
AES_256 AES_256,
Certificate
}; };
enum EncryptContents enum EncryptContents
@ -458,6 +475,7 @@ public:
QString ownerPassword; QString ownerPassword;
uint32_t permissions = 0; uint32_t permissions = 0;
QByteArray id; QByteArray id;
QString certificateFileName;
}; };
/// Creates security handler based on given settings. If security handler cannot /// Creates security handler based on given settings. If security handler cannot

View File

@ -21,6 +21,7 @@
#include "pdfutils.h" #include "pdfutils.h"
#include "pdfwidgetutils.h" #include "pdfwidgetutils.h"
#include "pdfsecurityhandler.h" #include "pdfsecurityhandler.h"
#include "pdfcertificatemanager.h"
#include "pdfdbgheap.h" #include "pdfdbgheap.h"
#include <QMessageBox> #include <QMessageBox>
@ -40,6 +41,7 @@ PDFEncryptionSettingsDialog::PDFEncryptionSettingsDialog(QByteArray documentId,
ui->algorithmComboBox->addItem(tr("RC4 128-bit | R4"), int(pdf::PDFSecurityHandlerFactory::RC4)); ui->algorithmComboBox->addItem(tr("RC4 128-bit | R4"), int(pdf::PDFSecurityHandlerFactory::RC4));
ui->algorithmComboBox->addItem(tr("AES 128-bit | R4"), int(pdf::PDFSecurityHandlerFactory::AES_128)); ui->algorithmComboBox->addItem(tr("AES 128-bit | R4"), int(pdf::PDFSecurityHandlerFactory::AES_128));
ui->algorithmComboBox->addItem(tr("AES 256-bit | R6"), int(pdf::PDFSecurityHandlerFactory::AES_256)); ui->algorithmComboBox->addItem(tr("AES 256-bit | R6"), int(pdf::PDFSecurityHandlerFactory::AES_256));
ui->algorithmComboBox->addItem(tr("Certificate Encryption"), int(pdf::PDFSecurityHandlerFactory::Certificate));
ui->algorithmComboBox->setCurrentIndex(0); ui->algorithmComboBox->setCurrentIndex(0);
@ -73,6 +75,7 @@ PDFEncryptionSettingsDialog::PDFEncryptionSettingsDialog(QByteArray documentId,
m_checkBoxToPermission[ui->permAssembleCheckBox] = pdf::PDFSecurityHandler::Permission::Assemble; m_checkBoxToPermission[ui->permAssembleCheckBox] = pdf::PDFSecurityHandler::Permission::Assemble;
m_checkBoxToPermission[ui->permPrintHighResolutionCheckBox] = pdf::PDFSecurityHandler::Permission::PrintHighResolution; m_checkBoxToPermission[ui->permPrintHighResolutionCheckBox] = pdf::PDFSecurityHandler::Permission::PrintHighResolution;
updateCertificates();
updateUi(); updateUi();
updatePasswordScore(); updatePasswordScore();
@ -95,6 +98,7 @@ void PDFEncryptionSettingsDialog::updateUi()
const pdf::PDFSecurityHandlerFactory::Algorithm algorithm = static_cast<const pdf::PDFSecurityHandlerFactory::Algorithm>(ui->algorithmComboBox->currentData().toInt()); const pdf::PDFSecurityHandlerFactory::Algorithm algorithm = static_cast<const pdf::PDFSecurityHandlerFactory::Algorithm>(ui->algorithmComboBox->currentData().toInt());
const bool encrypted = algorithm != pdf::PDFSecurityHandlerFactory::None; const bool encrypted = algorithm != pdf::PDFSecurityHandlerFactory::None;
const bool isEncryptedUsingCertificate = algorithm == pdf::PDFSecurityHandlerFactory::Certificate;
switch (algorithm) switch (algorithm)
{ {
@ -108,6 +112,7 @@ void PDFEncryptionSettingsDialog::updateUi()
ui->algorithmHintWidget->setCurrentValue(4); ui->algorithmHintWidget->setCurrentValue(4);
break; break;
case pdf::PDFSecurityHandlerFactory::AES_256: case pdf::PDFSecurityHandlerFactory::AES_256:
case pdf::PDFSecurityHandlerFactory::Certificate:
ui->algorithmHintWidget->setCurrentValue(5); ui->algorithmHintWidget->setCurrentValue(5);
break; break;
@ -116,20 +121,40 @@ void PDFEncryptionSettingsDialog::updateUi()
break; break;
} }
ui->userPasswordEnableCheckBox->setEnabled(encrypted); ui->certificateComboBox->setEnabled(isEncryptedUsingCertificate);
ui->ownerPasswordEnableCheckBox->setEnabled(false);
if (!encrypted) if (!isEncryptedUsingCertificate)
{ {
ui->userPasswordEnableCheckBox->setChecked(false); ui->userPasswordEnableCheckBox->setEnabled(encrypted);
ui->ownerPasswordEnableCheckBox->setChecked(false); ui->ownerPasswordEnableCheckBox->setEnabled(false);
ui->userPasswordEdit->clear(); if (!encrypted)
ui->ownerPasswordEdit->clear(); {
ui->userPasswordEnableCheckBox->setChecked(false);
ui->ownerPasswordEnableCheckBox->setChecked(false);
ui->userPasswordEdit->clear();
ui->ownerPasswordEdit->clear();
}
else
{
ui->ownerPasswordEnableCheckBox->setChecked(true);
}
ui->certificateComboBox->setCurrentIndex(-1);
} }
else else
{ {
ui->ownerPasswordEnableCheckBox->setChecked(true); ui->userPasswordEnableCheckBox->setEnabled(false);
ui->ownerPasswordEnableCheckBox->setEnabled(false);
ui->userPasswordEnableCheckBox->setChecked(true);
ui->ownerPasswordEnableCheckBox->setChecked(false);
if (ui->certificateComboBox->currentIndex() == -1 && ui->certificateComboBox->count() > 0)
{
ui->certificateComboBox->setCurrentIndex(0);
}
} }
ui->userPasswordEdit->setEnabled(ui->userPasswordEnableCheckBox->isChecked()); ui->userPasswordEdit->setEnabled(ui->userPasswordEnableCheckBox->isChecked());
@ -158,6 +183,21 @@ void PDFEncryptionSettingsDialog::updateUi()
} }
} }
void PDFEncryptionSettingsDialog::updateCertificates()
{
QFileInfoList certificates = pdf::PDFCertificateManager::getCertificates();
QVariant currentCertificate = ui->certificateComboBox->currentData();
ui->certificateComboBox->clear();
for (const QFileInfo& certificateItem : certificates)
{
ui->certificateComboBox->addItem(certificateItem.fileName(), certificateItem.absoluteFilePath());
}
ui->certificateComboBox->setCurrentIndex(ui->certificateComboBox->findData(currentCertificate));
}
void PDFEncryptionSettingsDialog::updatePasswordScore() void PDFEncryptionSettingsDialog::updatePasswordScore()
{ {
const pdf::PDFSecurityHandlerFactory::Algorithm algorithm = static_cast<const pdf::PDFSecurityHandlerFactory::Algorithm>(ui->algorithmComboBox->currentData().toInt()); const pdf::PDFSecurityHandlerFactory::Algorithm algorithm = static_cast<const pdf::PDFSecurityHandlerFactory::Algorithm>(ui->algorithmComboBox->currentData().toInt());
@ -188,6 +228,7 @@ void PDFEncryptionSettingsDialog::accept()
settings.userPassword = ui->userPasswordEdit->text(); settings.userPassword = ui->userPasswordEdit->text();
settings.ownerPassword = ui->ownerPasswordEdit->text(); settings.ownerPassword = ui->ownerPasswordEdit->text();
settings.permissions = 0; settings.permissions = 0;
settings.certificateFileName = ui->certificateComboBox->currentData().toString();
for (auto item : m_checkBoxToPermission) for (auto item : m_checkBoxToPermission)
{ {

View File

@ -50,6 +50,7 @@ private:
Ui::PDFEncryptionSettingsDialog* ui; Ui::PDFEncryptionSettingsDialog* ui;
void updateUi(); void updateUi();
void updateCertificates();
void updatePasswordScore(); void updatePasswordScore();
bool m_isUpdatingUi; bool m_isUpdatingUi;

View File

@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>705</width> <width>843</width>
<height>609</height> <height>620</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@ -20,8 +20,8 @@
<string>Encryption Method</string> <string>Encryption Method</string>
</property> </property>
<layout class="QGridLayout" name="methodGroupBoxLayout"> <layout class="QGridLayout" name="methodGroupBoxLayout">
<item row="0" column="1"> <item row="0" column="2">
<widget class="QComboBox" name="algorithmComboBox"/> <widget class="pdfviewer::PDFEncryptionStrengthHintWidget" name="algorithmHintWidget" native="true"/>
</item> </item>
<item row="0" column="0"> <item row="0" column="0">
<widget class="QLabel" name="encryptionAlgorithm"> <widget class="QLabel" name="encryptionAlgorithm">
@ -30,19 +30,29 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="2"> <item row="0" column="1">
<widget class="pdfviewer::PDFEncryptionStrengthHintWidget" name="algorithmHintWidget" native="true"/> <widget class="QComboBox" name="algorithmComboBox"/>
</item> </item>
<item row="1" column="0" colspan="3"> <item row="2" column="0" colspan="3">
<widget class="QLabel" name="encryptionMethodHintLabel"> <widget class="QLabel" name="encryptionMethodHintLabel">
<property name="text"> <property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select encryption algorithm. AES-256 is strongly recommended, because older encryption algorithm can be potentially broken. Select older algorithms (as AES-128 or RC4) only, if you need backward compatibility. Also, choose a strong password to ensure strong encryption.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select encryption algorithm. AES-256 is strongly recommended, because older encryption algorithm can be potentially broken. Select older algorithms (as AES-128 or RC4) only, if you need backward compatibility. Also, choose a strong password to ensure strong encryption.&lt;/p&gt;&lt;p&gt;Public key security using certificate is also supported. In that case, you must select a certificate with private key, and this certificate is then used to encrypt data. User, which wants to open document encrypted with certificate, must have a private key to the certificae.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="wordWrap"> <property name="wordWrap">
<bool>true</bool> <bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="0">
<widget class="QLabel" name="certificateLabel">
<property name="text">
<string>Certificate</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="certificateComboBox"/>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>

View File

@ -1202,6 +1202,12 @@ void PDFProgramController::onActionEncryptionTriggered()
{ {
pdf::PDFSecurityHandlerPointer updatedSecurityHandler = dialog.getUpdatedSecurityHandler(); pdf::PDFSecurityHandlerPointer updatedSecurityHandler = dialog.getUpdatedSecurityHandler();
if (!updatedSecurityHandler)
{
QMessageBox::critical(m_mainWindow, QApplication::applicationDisplayName(), tr("Failed to create security handler."));
return;
}
// Jakub Melka: If we changed encryption (password), recheck, that user doesn't // Jakub Melka: If we changed encryption (password), recheck, that user doesn't
// forgot (or accidentally entered wrong) password. So, we require owner authentization // forgot (or accidentally entered wrong) password. So, we require owner authentization
// to continue. // to continue.