Encryption tool

This commit is contained in:
Jakub Melka 2021-06-01 16:09:00 +02:00
parent 453a3a24c7
commit c22cba2f50
12 changed files with 320 additions and 13 deletions

View File

@ -495,7 +495,7 @@ PDFObjectFactory& PDFObjectFactory::operator<<(WrapEmptyArray)
PDFObject PDFObjectFactory::createTextString(QString textString)
{
if (!PDFEncoding::canConvertToEncoding(textString, PDFEncoding::Encoding::PDFDoc))
if (!PDFEncoding::canConvertToEncoding(textString, PDFEncoding::Encoding::PDFDoc, nullptr))
{
// Use unicode encoding
QByteArray ba;

View File

@ -2132,7 +2132,7 @@ QString PDFEncoding::convert(const QByteArray& stream, PDFEncoding::Encoding enc
return result;
}
QByteArray PDFEncoding::convertToEncoding(const QString& string, PDFEncoding::Encoding encoding)
QByteArray PDFEncoding::convertToEncoding(const QString& string, Encoding encoding)
{
QByteArray result;
@ -2160,11 +2160,12 @@ QByteArray PDFEncoding::convertToEncoding(const QString& string, PDFEncoding::En
return result;
}
bool PDFEncoding::canConvertToEncoding(const QString& string, PDFEncoding::Encoding encoding)
bool PDFEncoding::canConvertToEncoding(const QString& string, Encoding encoding, QString* invalidCharacters)
{
const encoding::EncodingTable* table = getTableForEncoding(encoding);
Q_ASSERT(table);
bool isConvertible = true;
for (QChar character : string)
{
ushort unicode = character.unicode();
@ -2181,14 +2182,23 @@ bool PDFEncoding::canConvertToEncoding(const QString& string, PDFEncoding::Encod
if (!converted)
{
return false;
isConvertible = false;
if (!invalidCharacters)
{
// We are not storing invalid characters - we can break on first not convertible
// character.
break;
}
*invalidCharacters += character;
}
}
return true;
return isConvertible;
}
bool PDFEncoding::canConvertFromEncoding(const QByteArray& stream, PDFEncoding::Encoding encoding)
bool PDFEncoding::canConvertFromEncoding(const QByteArray& stream, Encoding encoding)
{
const encoding::EncodingTable* table = getTableForEncoding(encoding);
for (const unsigned char index : stream)
@ -2403,6 +2413,24 @@ QString PDFEncoding::convertSmartFromByteStringToUnicode(const QByteArray& strea
return QString::fromLatin1(stream.toHex()).toUpper();
}
QString PDFEncoding::getEncodingCharacters(Encoding encoding)
{
QString string;
if (const encoding::EncodingTable* table = getTableForEncoding(encoding))
{
for (const QChar& character : *table)
{
if (character != QChar(0xFFFD))
{
string += character;
}
}
}
return string;
}
bool PDFEncoding::hasUnicodeLeadMarkings(const QByteArray& stream)
{
if (stream.size() >= 2)

View File

@ -75,7 +75,8 @@ public:
/// are also present in given encoding).
/// \param string String to be tested
/// \param encoding Encoding used in verification of conversion
static bool canConvertToEncoding(const QString& string, Encoding encoding);
/// \param[out] invalidCharacters Storage, where not convertible characters are inserted
static bool canConvertToEncoding(const QString& string, Encoding encoding, QString* invalidCharacters);
/// Checks, if stream can be converted to string using encoding (i.e. all
/// characters are defined). If all characters are valid, then true is
@ -120,6 +121,11 @@ public:
/// \returns Unicode string or string converted to hexadecimal representation
static QString convertSmartFromByteStringToUnicode(const QByteArray& stream, bool* isBinary);
/// Returns all characters of the given encoding
/// \param encoding Encoding
/// \returns All characters reprezentable by encoding.
static QString getEncodingCharacters(Encoding encoding);
private:
/// Returns true, if byte array has UTF-16BE/LE unicode marking bytes at the
/// stream start. If they are present, then byte stream is probably encoded

View File

@ -191,19 +191,26 @@ void PDFDecryptOrEncryptObjectVisitor::visitStream(const PDFStream* stream)
QByteArray processedData;
if (!processedDictionary.hasKey("Crypt"))
{
// Is it an embedded file?
const PDFObject& object = processedDictionary.get("Type");
const bool isEmbeddedFile = object.isName() && object.getString() == "EmbeddedFile";
const PDFSecurityHandler::EncryptionScope scope = !isEmbeddedFile ? PDFSecurityHandler::EncryptionScope::Stream : PDFSecurityHandler::EncryptionScope::EmbeddedFile;
switch (m_mode)
{
case pdf::PDFDecryptOrEncryptObjectVisitor::Mode::Decrypt:
processedData = m_securityHandler->decrypt(*stream->getContent(), m_reference, PDFSecurityHandler::EncryptionScope::Stream);
processedData = m_securityHandler->decrypt(*stream->getContent(), m_reference, scope);
break;
case pdf::PDFDecryptOrEncryptObjectVisitor::Mode::Encrypt:
processedData = m_securityHandler->encrypt(*stream->getContent(), m_reference, PDFSecurityHandler::EncryptionScope::Stream);
processedData = m_securityHandler->encrypt(*stream->getContent(), m_reference, scope);
break;
default:
Q_ASSERT(false);
break;
}
processedDictionary.setEntry(PDFInplaceOrMemoryString("Length"), PDFObject::createInteger(processedData.size()));
}
else
{
@ -212,8 +219,7 @@ void PDFDecryptOrEncryptObjectVisitor::visitStream(const PDFStream* stream)
case pdf::PDFDecryptOrEncryptObjectVisitor::Mode::Decrypt:
{
processedData = *stream->getContent();
processedDictionary.removeEntry(PDFSecurityHandler::OBJECT_REFERENCE_DICTIONARY_NAME);
processedDictionary.addEntry(PDFInplaceOrMemoryString(PDFSecurityHandler::OBJECT_REFERENCE_DICTIONARY_NAME), PDFObject::createReference(m_reference));
processedDictionary.setEntry(PDFInplaceOrMemoryString(PDFSecurityHandler::OBJECT_REFERENCE_DICTIONARY_NAME), PDFObject::createReference(m_reference));
break;
}
@ -1991,4 +1997,48 @@ QByteArray PDFSecurityHandlerFactory::generateRandomByteArray(QRandomGenerator&
return ba;
}
bool PDFSecurityHandlerFactory::validate(const SecuritySettings& settings, QString* errorMessage)
{
switch (settings.algorithm)
{
case pdf::PDFSecurityHandlerFactory::RC4:
case pdf::PDFSecurityHandlerFactory::AES_128:
{
QString invalidCharacters;
if (!PDFEncoding::canConvertToEncoding(settings.userPassword, PDFEncoding::Encoding::PDFDoc, &invalidCharacters))
{
if (errorMessage)
{
Q_ASSERT(!invalidCharacters.isEmpty());
*errorMessage = tr("User password contains invalid characters: %1.").arg(invalidCharacters);
}
return false;
}
if (!PDFEncoding::canConvertToEncoding(settings.ownerPassword, PDFEncoding::Encoding::PDFDoc, &invalidCharacters))
{
if (errorMessage)
{
Q_ASSERT(!invalidCharacters.isEmpty());
*errorMessage = tr("Owner password contains invalid characters: %1.").arg(invalidCharacters);
}
return false;
}
break;
}
case pdf::PDFSecurityHandlerFactory::None:
case pdf::PDFSecurityHandlerFactory::AES_256:
break;
default:
Q_ASSERT(false);
break;
}
return true;
}
} // namespace pdf

View File

@ -380,6 +380,8 @@ private:
/// Factory, which creates security handler based on settings.
class Pdf4QtLIBSHARED_EXPORT PDFSecurityHandlerFactory
{
Q_DECLARE_TR_FUNCTIONS(pdf::PDFSecurityHandlerFactory)
public:
enum Algorithm
@ -432,6 +434,11 @@ public:
/// \param generator Random number generator
/// \param size Target size
static QByteArray generateRandomByteArray(QRandomGenerator& generator, int size);
/// Validates security settings
/// \param settings Settings
/// \param[out] errorMessage Error message
static bool validate(const SecuritySettings& settings, QString* errorMessage);
};
} // namespace pdf

View File

@ -23,7 +23,7 @@
<item>
<widget class="QLabel" name="copyrightLabel">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;PdfForQtViewer&lt;/span&gt;&lt;/p&gt;&lt;p&gt;Copyright 2018-2020 Jakub Melka. All rights reserved.&lt;/p&gt;&lt;p&gt;THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;PdfForQtViewer&lt;/span&gt;&lt;/p&gt;&lt;p&gt;Copyright 2018-2021 Jakub Melka. All rights reserved.&lt;/p&gt;&lt;p&gt;THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="wordWrap">
<bool>true</bool>

View File

@ -21,6 +21,8 @@
#include "pdfutils.h"
#include "pdfsecurityhandler.h"
#include <QMessageBox>
namespace pdfviewer
{
@ -191,6 +193,13 @@ void PDFEncryptionSettingsDialog::accept()
}
}
QString errorMessage;
if (!pdf::PDFSecurityHandlerFactory::validate(settings, &errorMessage))
{
QMessageBox::critical(this, tr("Error"), errorMessage);
return;
}
m_updatedSecurityHandler = pdf::PDFSecurityHandlerFactory::createSecurityHandler(settings);
QDialog::accept();

View File

@ -47,6 +47,7 @@ SOURCES += \
pdftoolcertstore.cpp \
pdftoolcolorprofiles.cpp \
pdftooldecrypt.cpp \
pdftoolencrypt.cpp \
pdftoolfetchimages.cpp \
pdftoolfetchtext.cpp \
pdftoolinfo.cpp \
@ -80,6 +81,7 @@ HEADERS += \
pdftoolcertstore.h \
pdftoolcolorprofiles.h \
pdftooldecrypt.h \
pdftoolencrypt.h \
pdftoolfetchimages.h \
pdftoolfetchtext.h \
pdftoolinfo.h \

View File

@ -324,6 +324,15 @@ void PDFToolAbstractApplication::initializeCommandLineParser(QCommandLineParser*
{
parser->addPositionalArgument("certificate", "Certificate file");
}
if (optionFlags.testFlag(Encrypt))
{
parser->addOption(QCommandLineOption("enc-algorithm", "Encryption algorithm (valid values: rc4|aes-128|aes-256).", "encryption algorithm", "aes-256"));
parser->addOption(QCommandLineOption("enc-contents", "Encryption scope (valid values: all|all-except-metadata|only-embedded-files).", "encryption contents", "all"));
parser->addOption(QCommandLineOption("enc-user-password", "User password (for document reading).", "user password"));
parser->addOption(QCommandLineOption("enc-owner-password", "Owner password.", "owner password"));
parser->addOption(QCommandLineOption("enc-permissions", "Document permissions (flags represented as a number).", "permissions"));
}
}
PDFToolOptions PDFToolAbstractApplication::getOptions(QCommandLineParser* parser) const
@ -896,6 +905,59 @@ PDFToolOptions PDFToolAbstractApplication::getOptions(QCommandLineParser* parser
options.certificateStoreInstallCertificateFile = positionalArguments.isEmpty() ? QString() : positionalArguments.front();
}
if (optionFlags.testFlag(Encrypt))
{
QString encryptionAlgorithm = parser->value("enc-algorithm");
if (encryptionAlgorithm == "rc4")
{
options.encryptionAlgorithm = pdf::PDFSecurityHandlerFactory::Algorithm::RC4;
}
else if (encryptionAlgorithm == "aes-128")
{
options.encryptionAlgorithm = pdf::PDFSecurityHandlerFactory::Algorithm::AES_128;
}
else if (encryptionAlgorithm == "aes-256")
{
options.encryptionAlgorithm = pdf::PDFSecurityHandlerFactory::Algorithm::AES_256;
}
else
{
if (!encryptionAlgorithm.isEmpty())
{
PDFConsole::writeError(PDFToolTranslationContext::tr("Unknown encryption algorithm '%1'. Defaulting to AES-256 encryption.").arg(encryptionAlgorithm), options.outputCodec);
}
options.encryptionAlgorithm = pdf::PDFSecurityHandlerFactory::Algorithm::AES_256;
}
QString encryptionContents = parser->value("enc-contents");
if (encryptionContents == "all")
{
options.encryptionContents = pdf::PDFSecurityHandlerFactory::EncryptContents::All;
}
else if (encryptionContents == "all-except-metadata")
{
options.encryptionContents = pdf::PDFSecurityHandlerFactory::EncryptContents::AllExceptMetadata;
}
else if (encryptionContents == "only-embedded-files")
{
options.encryptionContents = pdf::PDFSecurityHandlerFactory::EncryptContents::EmbeddedFiles;
}
else
{
if (!encryptionContents.isEmpty())
{
PDFConsole::writeError(PDFToolTranslationContext::tr("Unknown encryption contents mode '%1'. Defaulting to encrypt all contents.").arg(encryptionContents), options.outputCodec);
}
options.encryptionContents = pdf::PDFSecurityHandlerFactory::EncryptContents::All;
}
options.encryptionUserPassword = parser->value("enc-user-password");
options.encryptionOwnerPassword = parser->value("enc-owner-password");
options.encryptionPermissions = parser->value("enc-permissions").toUInt();
}
return options;
}

View File

@ -146,6 +146,13 @@ struct PDFToolOptions
// For option 'CertStoreInstall'
QString certificateStoreInstallCertificateFile;
// For option 'Encrypt'
pdf::PDFSecurityHandlerFactory::Algorithm encryptionAlgorithm = pdf::PDFSecurityHandlerFactory::Algorithm::AES_256;
pdf::PDFSecurityHandlerFactory::EncryptContents encryptionContents = pdf::PDFSecurityHandlerFactory::EncryptContents::All;
QString encryptionUserPassword;
QString encryptionOwnerPassword;
uint32_t encryptionPermissions = 0;
/// Returns page range. If page range is invalid, then \p errorMessage is empty.
/// \param pageCount Page count
/// \param[out] errorMessage Error message
@ -194,7 +201,8 @@ public:
ErrorPermissions,
ErrorNoText,
ErrorCOM,
ErrorSAPI
ErrorSAPI,
ErrorEncryptionSettings
};
enum StandardString
@ -229,6 +237,7 @@ public:
Optimize = 0x00100000, ///< Settings for Optimize tool
CertStore = 0x00200000, ///< Settings for certificate store tool
CertStoreInstall = 0x00400000, ///< Settings for certificate store install certificate tool
Encrypt = 0x00800000, ///< Encryption settings
};
Q_DECLARE_FLAGS(Options, Option)

View File

@ -0,0 +1,98 @@
// Copyright (C) 2021 Jakub Melka
//
// This file is part of Pdf4Qt.
//
// Pdf4Qt is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// with the written consent of the copyright owner, any later version.
//
// Pdf4Qt is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with Pdf4Qt. If not, see <https://www.gnu.org/licenses/>.
#include "pdftoolencrypt.h"
#include "pdfdocumentbuilder.h"
#include "pdfdocumentwriter.h"
namespace pdftool
{
static PDFToolEncryptApplication s_encryptApplication;
QString PDFToolEncryptApplication::getStandardString(StandardString standardString) const
{
switch (standardString)
{
case Command:
return "encrypt";
case Name:
return PDFToolTranslationContext::tr("Encrypt");
case Description:
return PDFToolTranslationContext::tr("Encrypt the document (with only owner access only).");
default:
Q_ASSERT(false);
break;
}
return QString();
}
int PDFToolEncryptApplication::execute(const PDFToolOptions& options)
{
pdf::PDFSecurityHandlerFactory::SecuritySettings settings;
settings.algorithm = options.encryptionAlgorithm;
settings.encryptContents = options.encryptionContents;
settings.userPassword = options.encryptionUserPassword;
settings.ownerPassword = options.encryptionOwnerPassword;
settings.permissions = options.encryptionPermissions;
QString errorMessage;
if (!pdf::PDFSecurityHandlerFactory::validate(settings, &errorMessage))
{
PDFConsole::writeError(errorMessage, options.outputCodec);
return ErrorEncryptionSettings;
}
pdf::PDFSecurityHandlerPointer securityHandler = pdf::PDFSecurityHandlerFactory::createSecurityHandler(settings);
pdf::PDFDocument document;
QByteArray sourceData;
if (!readDocument(options, document, &sourceData, true))
{
if (readDocument(options, document, &sourceData, false))
{
PDFConsole::writeError(PDFToolTranslationContext::tr("Authorization as owner failed. Encryption change is not permitted if authorized as user only."), options.outputCodec);
}
return ErrorDocumentReading;
}
pdf::PDFDocumentBuilder builder(&document);
builder.setSecurityHandler(qMove(securityHandler));
document = builder.build();
pdf::PDFDocumentWriter writer(nullptr);
pdf::PDFOperationResult result = writer.write(options.document, &document, true);
if (!result)
{
PDFConsole::writeError(result.getErrorMessage(), options.outputCodec);
return ErrorDocumentWriting;
}
return ExitSuccess;
}
PDFToolAbstractApplication::Options PDFToolEncryptApplication::getOptionsFlags() const
{
return ConsoleFormat | OpenDocument | Encrypt;
}
} // namespace pdftool

36
PdfTool/pdftoolencrypt.h Normal file
View File

@ -0,0 +1,36 @@
// Copyright (C) 2021 Jakub Melka
//
// This file is part of Pdf4Qt.
//
// Pdf4Qt is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// with the written consent of the copyright owner, any later version.
//
// Pdf4Qt is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with Pdf4Qt. If not, see <https://www.gnu.org/licenses/>.
#ifndef PDFTOOLENCRYPT_H
#define PDFTOOLENCRYPT_H
#include "pdftoolabstractapplication.h"
namespace pdftool
{
class PDFToolEncryptApplication : public PDFToolAbstractApplication
{
public:
virtual QString getStandardString(StandardString standardString) const override;
virtual int execute(const PDFToolOptions& options) override;
virtual Options getOptionsFlags() const override;
};
} // namespace pdftool
#endif // PDFTOOLENCRYPT_H