mirror of https://github.com/JakubMelka/PDF4QT.git
Adjusting password
This commit is contained in:
parent
c4ea7a3ea8
commit
cfc9593d14
|
@ -34,7 +34,7 @@ namespace pdf
|
|||
{
|
||||
|
||||
PDFDocumentReader::PDFDocumentReader(const std::function<QString(bool*)>& getPasswordCallback) :
|
||||
m_successfull(true),
|
||||
m_result(Result::OK),
|
||||
m_getPasswordCallback(getPasswordCallback)
|
||||
{
|
||||
|
||||
|
@ -56,13 +56,13 @@ PDFDocument PDFDocumentReader::readFromFile(const QString& fileName)
|
|||
}
|
||||
else
|
||||
{
|
||||
m_successfull = false;
|
||||
m_result = Result::Failed;
|
||||
m_errorMessage = tr("File '%1' cannot be opened for reading. %1").arg(file.errorString());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_successfull = false;
|
||||
m_result = Result::Failed;
|
||||
m_errorMessage = tr("File '%1' doesn't exist.").arg(fileName);
|
||||
}
|
||||
|
||||
|
@ -82,7 +82,7 @@ PDFDocument PDFDocumentReader::readFromDevice(QIODevice* device)
|
|||
}
|
||||
else
|
||||
{
|
||||
m_successfull = false;
|
||||
m_result = Result::Failed;
|
||||
m_errorMessage = tr("Device is not opened for reading.");
|
||||
}
|
||||
}
|
||||
|
@ -94,7 +94,7 @@ PDFDocument PDFDocumentReader::readFromDevice(QIODevice* device)
|
|||
}
|
||||
else
|
||||
{
|
||||
m_successfull = false;
|
||||
m_result = Result::Failed;
|
||||
m_errorMessage = tr("Can't open device for reading.");
|
||||
}
|
||||
|
||||
|
@ -246,7 +246,7 @@ PDFDocument PDFDocumentReader::readFromBuffer(const QByteArray& buffer)
|
|||
{
|
||||
Q_ASSERT(entry.type == PDFXRefTable::EntryType::Occupied);
|
||||
|
||||
if (m_successfull)
|
||||
if (m_result == Result::OK)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -259,7 +259,7 @@ PDFDocument PDFDocumentReader::readFromBuffer(const QByteArray& buffer)
|
|||
catch (PDFParserException exception)
|
||||
{
|
||||
QMutexLocker lock(&m_mutex);
|
||||
m_successfull = false;
|
||||
m_result = Result::Failed;
|
||||
m_errorMessage = exception.getMessage();
|
||||
}
|
||||
}
|
||||
|
@ -318,10 +318,15 @@ PDFDocument PDFDocumentReader::readFromBuffer(const QByteArray& buffer)
|
|||
PDFSecurityHandlerPointer securityHandler = PDFSecurityHandler::createSecurityHandler(encryptObject, id);
|
||||
PDFSecurityHandler::AuthorizationResult authorizationResult = securityHandler->authenticate(m_getPasswordCallback);
|
||||
|
||||
if (authorizationResult == PDFSecurityHandler::AuthorizationResult::Failed ||
|
||||
authorizationResult == PDFSecurityHandler::AuthorizationResult::Cancelled)
|
||||
if (authorizationResult == PDFSecurityHandler::AuthorizationResult::Cancelled)
|
||||
{
|
||||
// User cancelled the document reading
|
||||
m_result = Result::Cancelled;
|
||||
return PDFDocument();
|
||||
}
|
||||
|
||||
if (authorizationResult == PDFSecurityHandler::AuthorizationResult::Failed)
|
||||
{
|
||||
// TODO: If user cancels it, do not display error message.
|
||||
throw PDFParserException(PDFTranslationContext::tr("Authorization failed. Bad password provided."));
|
||||
}
|
||||
|
||||
|
@ -340,7 +345,7 @@ PDFDocument PDFDocumentReader::readFromBuffer(const QByteArray& buffer)
|
|||
|
||||
auto processObjectStream = [this, &getObject, &objectFetcher, &objects, &objectStreamEntries] (const PDFObjectReference& objectStreamReference)
|
||||
{
|
||||
if (!m_successfull)
|
||||
if (m_result != Result::OK)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
@ -423,7 +428,7 @@ PDFDocument PDFDocumentReader::readFromBuffer(const QByteArray& buffer)
|
|||
catch (PDFParserException exception)
|
||||
{
|
||||
QMutexLocker lock(&m_mutex);
|
||||
m_successfull = false;
|
||||
m_result = Result::Failed;
|
||||
m_errorMessage = exception.getMessage();
|
||||
}
|
||||
};
|
||||
|
@ -436,7 +441,7 @@ PDFDocument PDFDocumentReader::readFromBuffer(const QByteArray& buffer)
|
|||
}
|
||||
catch (PDFParserException parserException)
|
||||
{
|
||||
m_successfull = false;
|
||||
m_result = Result::Failed;
|
||||
m_errorMessage = parserException.getMessage();
|
||||
}
|
||||
|
||||
|
@ -445,7 +450,7 @@ PDFDocument PDFDocumentReader::readFromBuffer(const QByteArray& buffer)
|
|||
|
||||
void PDFDocumentReader::reset()
|
||||
{
|
||||
m_successfull = true;
|
||||
m_result = Result::OK;
|
||||
m_errorMessage = QString();
|
||||
m_version = PDFVersion();
|
||||
}
|
||||
|
|
|
@ -43,6 +43,13 @@ public:
|
|||
constexpr inline PDFDocumentReader& operator=(const PDFDocumentReader&) = delete;
|
||||
constexpr inline PDFDocumentReader& operator=(PDFDocumentReader&&) = delete;
|
||||
|
||||
enum class Result
|
||||
{
|
||||
OK, ///< Document was successfully loaded
|
||||
Failed, ///< Error occured during document reading
|
||||
Cancelled ///< User cancelled document reading
|
||||
};
|
||||
|
||||
/// Reads a PDF document from the specified file. If file doesn't exist,
|
||||
/// cannot be opened or contain invalid pdf, empty PDF file is returned.
|
||||
/// No exception is thrown.
|
||||
|
@ -58,8 +65,8 @@ public:
|
|||
/// PDF is read, then empty PDF document is returned. No exception is thrown.
|
||||
PDFDocument readFromBuffer(const QByteArray& buffer);
|
||||
|
||||
/// Returns true, if document was successfully read from device
|
||||
bool isSuccessfull() const { return m_successfull; }
|
||||
/// Returns result code for reading document from the device
|
||||
Result getReadingResult() const { return m_result; }
|
||||
|
||||
/// Returns error message, if document reading was unsuccessfull
|
||||
const QString& getErrorMessage() const { return m_errorMessage; }
|
||||
|
@ -83,8 +90,8 @@ private:
|
|||
/// (providing thread safety)
|
||||
QMutex m_mutex;
|
||||
|
||||
/// This bool flag is set, if pdf document was successfully read from the device
|
||||
std::atomic<bool> m_successfull;
|
||||
/// Result of document reading from the device
|
||||
std::atomic<Result> m_result;
|
||||
|
||||
/// In case if error occurs, it is stored here
|
||||
QString m_errorMessage;
|
||||
|
|
|
@ -1871,6 +1871,33 @@ QString PDFEncoding::convert(const QByteArray& stream, PDFEncoding::Encoding enc
|
|||
return result;
|
||||
}
|
||||
|
||||
QByteArray PDFEncoding::convertToEncoding(const QString& string, PDFEncoding::Encoding encoding)
|
||||
{
|
||||
QByteArray result;
|
||||
|
||||
const encoding::EncodingTable* table = getTableForEncoding(encoding);
|
||||
Q_ASSERT(table);
|
||||
|
||||
result.reserve(string.size());
|
||||
for (QChar character : string)
|
||||
{
|
||||
ushort unicode = character.unicode();
|
||||
unsigned char converted = 0;
|
||||
|
||||
for (int i = 0; i < table->size(); ++i)
|
||||
{
|
||||
if (unicode == (*table)[static_cast<unsigned char>(i)])
|
||||
{
|
||||
converted = i;
|
||||
}
|
||||
}
|
||||
|
||||
result.push_back(converted);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QString PDFEncoding::convertTextString(const QByteArray& stream)
|
||||
{
|
||||
if (hasUnicodeLeadMarkings(stream))
|
||||
|
|
|
@ -59,6 +59,14 @@ public:
|
|||
/// \returns Converted unicode string
|
||||
static QString convert(const QByteArray& stream, Encoding encoding);
|
||||
|
||||
/// Converts unicode string to the byte array using the specified encoding.
|
||||
/// It performs reverse functionality than function \p convert. If the character
|
||||
/// in the encoding is not found, then it is converted to character code 0.
|
||||
/// \param string String to be converted
|
||||
/// \param encoding Encoding used in the conversion
|
||||
/// \sa convert
|
||||
static QByteArray convertToEncoding(const QString& string, Encoding encoding);
|
||||
|
||||
/// Convert text string to the unicode string, using either PDFDocEncoding,
|
||||
/// or UTF-16BE encoding. Please see PDF Reference 1.7, Chapter 3.8.1. If
|
||||
/// UTF-16BE encoding is used, then leading bytes should be 0xFE and 0xFF
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
#include "pdfsecurityhandler.h"
|
||||
#include "pdfexception.h"
|
||||
#include "pdfencoding.h"
|
||||
|
||||
#include <openssl/rc4.h>
|
||||
#include <openssl/md5.h>
|
||||
|
@ -369,8 +370,8 @@ PDFSecurityHandler::AuthorizationResult PDFStandardSecurityHandler::authenticate
|
|||
AES_KEY key = { };
|
||||
AES_set_decrypt_key(reinterpret_cast<const unsigned char*>(fileEncryptionDecryptionKey.data()), fileEncryptionDecryptionKey.size() * 8, &key);
|
||||
unsigned char aesInitializationVector[AES_BLOCK_SIZE] = { };
|
||||
m_authorizationData.fileEncryptionKey.resize(m_OE.size());
|
||||
AES_cbc_encrypt(reinterpret_cast<const unsigned char*>(m_OE.data()), reinterpret_cast<unsigned char*>(m_authorizationData.fileEncryptionKey.data()), m_OE.size(), &key, aesInitializationVector, AES_DECRYPT);
|
||||
m_authorizationData.fileEncryptionKey.resize(m_UE.size());
|
||||
AES_cbc_encrypt(reinterpret_cast<const unsigned char*>(m_UE.data()), reinterpret_cast<unsigned char*>(m_authorizationData.fileEncryptionKey.data()), m_UE.size(), &key, aesInitializationVector, AES_DECRYPT);
|
||||
|
||||
// We have authorized owner access
|
||||
m_authorizationData.authorizationResult = AuthorizationResult::UserAuthorized;
|
||||
|
@ -380,6 +381,36 @@ PDFSecurityHandler::AuthorizationResult PDFStandardSecurityHandler::authenticate
|
|||
// Stop, if we authorized the document usage
|
||||
if (m_authorizationData.isAuthorized())
|
||||
{
|
||||
// According the PDF specification, we must also check, if flags are not manipulated.
|
||||
Q_ASSERT(m_Perms.size() == AES_BLOCK_SIZE);
|
||||
AES_KEY key = { };
|
||||
AES_set_decrypt_key(reinterpret_cast<const unsigned char*>(m_authorizationData.fileEncryptionKey.data()), m_authorizationData.fileEncryptionKey.size() * 8, &key);
|
||||
QByteArray decodedPerms(m_Perms.size(), char(0));
|
||||
AES_ecb_encrypt(reinterpret_cast<const unsigned char*>(m_Perms.data()), reinterpret_cast<unsigned char*>(decodedPerms.data()), &key, AES_DECRYPT);
|
||||
|
||||
// 1) Checks, if bytes 9, 10, 11 are 'a', 'd', 'b'
|
||||
if (decodedPerms[9] != 'a' || decodedPerms[10] != 'd' || decodedPerms[11] != 'b')
|
||||
{
|
||||
throw PDFParserException(PDFTranslationContext::tr("Permissions entry in the Encryption dictionary is invalid."));
|
||||
}
|
||||
|
||||
// 2) Verify, that bytes 0-3 are valid permissions entry
|
||||
const uint32_t permissions = qFromLittleEndian(*reinterpret_cast<const uint32_t*>(decodedPerms.data()));
|
||||
if (permissions != m_permissions)
|
||||
{
|
||||
throw PDFParserException(PDFTranslationContext::tr("Security permissions are manipulated. Can't open the document."));
|
||||
}
|
||||
|
||||
// 3) Verify, that byte 8 is 'T' or 'F' and is equal to EncryptMetadata entry
|
||||
if (decodedPerms[8] != 'T' && decodedPerms[8] != 'F')
|
||||
{
|
||||
throw PDFParserException(PDFTranslationContext::tr("Security permissions are manipulated. Can't open the document."));
|
||||
}
|
||||
if ((decodedPerms[8] == 'T') != m_encryptMetadata)
|
||||
{
|
||||
throw PDFParserException(PDFTranslationContext::tr("Security permissions are manipulated. Can't open the document."));
|
||||
}
|
||||
|
||||
return m_authorizationData.authorizationResult;
|
||||
}
|
||||
|
||||
|
@ -390,8 +421,7 @@ PDFSecurityHandler::AuthorizationResult PDFStandardSecurityHandler::authenticate
|
|||
return AuthorizationResult::Failed;
|
||||
}
|
||||
|
||||
// TODO: Handle passwords better - in some revisions, must be in PDFDocEncoding!
|
||||
password = getPasswordCallback(&passwordObtained).toUtf8();
|
||||
password = adjustPassword(getPasswordCallback(&passwordObtained));
|
||||
}
|
||||
|
||||
return AuthorizationResult::Cancelled;
|
||||
|
@ -767,4 +797,114 @@ PDFStandardSecurityHandler::UserOwnerData_r6 PDFStandardSecurityHandler::parsePa
|
|||
return result;
|
||||
}
|
||||
|
||||
QByteArray PDFStandardSecurityHandler::adjustPassword(const QString& password)
|
||||
{
|
||||
QByteArray result;
|
||||
|
||||
switch (m_R)
|
||||
{
|
||||
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 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;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace pdf
|
||||
|
|
|
@ -176,6 +176,19 @@ private:
|
|||
/// Parses parts of the user/owner data (U/O values of the encryption dictionary)
|
||||
UserOwnerData_r6 parseParts(const QByteArray& data) const;
|
||||
|
||||
/// Adjusts the password according to the PDF specification
|
||||
QByteArray adjustPassword(const QString& password);
|
||||
|
||||
/// 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;
|
||||
|
||||
|
|
|
@ -281,21 +281,30 @@ void PDFViewerMainWindow::openDocument(const QString& fileName)
|
|||
pdf::PDFDocument document = reader.readFromFile(fileName);
|
||||
QApplication::restoreOverrideCursor();
|
||||
|
||||
if (reader.isSuccessfull())
|
||||
switch (reader.getReadingResult())
|
||||
{
|
||||
// Mark current directory as this
|
||||
QFileInfo fileInfo(fileName);
|
||||
m_settings->setDirectory(fileInfo.dir().absolutePath());
|
||||
m_currentFile = fileInfo.fileName();
|
||||
case pdf::PDFDocumentReader::Result::OK:
|
||||
{
|
||||
// Mark current directory as this
|
||||
QFileInfo fileInfo(fileName);
|
||||
m_settings->setDirectory(fileInfo.dir().absolutePath());
|
||||
m_currentFile = fileInfo.fileName();
|
||||
|
||||
m_pdfDocument.reset(new pdf::PDFDocument(std::move(document)));
|
||||
setDocument(m_pdfDocument.data());
|
||||
m_pdfDocument.reset(new pdf::PDFDocument(std::move(document)));
|
||||
setDocument(m_pdfDocument.data());
|
||||
|
||||
statusBar()->showMessage(tr("Document '%1' was successfully loaded!").arg(fileName), 4000);
|
||||
}
|
||||
else
|
||||
{
|
||||
QMessageBox::critical(this, tr("PDF Viewer"), tr("Document read error: %1").arg(reader.getErrorMessage()));
|
||||
statusBar()->showMessage(tr("Document '%1' was successfully loaded!").arg(fileName), 4000);
|
||||
break;
|
||||
}
|
||||
|
||||
case pdf::PDFDocumentReader::Result::Failed:
|
||||
{
|
||||
QMessageBox::critical(this, tr("PDF Viewer"), tr("Document read error: %1").arg(reader.getErrorMessage()));
|
||||
break;
|
||||
}
|
||||
|
||||
case pdf::PDFDocumentReader::Result::Cancelled:
|
||||
break; // Do nothing, user cancelled the document reading
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue