You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

318 lines
12 KiB

/***************************************************************************
* Copyright (C) 2007, 2009 by Brad Hards <bradh@frogmouth.net> *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
***************************************************************************/
#include "manifest.h"
#include <QBuffer>
#include <QXmlStreamReader>
#include <KFilterDev>
#include <KLocalizedString>
#include <KMessageBox>
using namespace OOO;
Q_LOGGING_CATEGORY(OkularOooDebug, "org.kde.okular.generators.ooo", QtWarningMsg)
//---------------------------------------------------------------------
ManifestEntry::ManifestEntry(const QString &fileName)
: m_fileName(fileName)
{
}
void ManifestEntry::setMimeType(const QString &mimeType)
{
m_mimeType = mimeType;
}
void ManifestEntry::setSize(const QString &size)
{
m_size = size;
}
QString ManifestEntry::fileName() const
{
return m_fileName;
}
QString ManifestEntry::mimeType() const
{
return m_mimeType;
}
QString ManifestEntry::size() const
{
return m_size;
}
void ManifestEntry::setChecksumType(const QString &checksumType)
{
m_checksumType = checksumType;
}
QString ManifestEntry::checksumType() const
{
return m_checksumType;
}
void ManifestEntry::setChecksum(const QString &checksum)
{
m_checksum = QByteArray::fromBase64(checksum.toLatin1());
}
QByteArray ManifestEntry::checksum() const
{
return m_checksum;
}
void ManifestEntry::setAlgorithm(const QString &algorithm)
{
m_algorithm = algorithm;
}
QString ManifestEntry::algorithm() const
{
return m_algorithm;
}
void ManifestEntry::setInitialisationVector(const QString &initialisationVector)
{
m_initialisationVector = QByteArray::fromBase64(initialisationVector.toLatin1());
}
QByteArray ManifestEntry::initialisationVector() const
{
return m_initialisationVector;
}
void ManifestEntry::setKeyDerivationName(const QString &keyDerivationName)
{
m_keyDerivationName = keyDerivationName;
}
QString ManifestEntry::keyDerivationName() const
{
return m_keyDerivationName;
}
void ManifestEntry::setIterationCount(const QString &iterationCount)
{
m_iterationCount = iterationCount.toInt();
}
int ManifestEntry::iterationCount() const
{
return m_iterationCount;
}
void ManifestEntry::setSalt(const QString &salt)
{
m_salt = QByteArray::fromBase64(salt.toLatin1());
}
QByteArray ManifestEntry::salt() const
{
return m_salt;
}
//---------------------------------------------------------------------
Manifest::Manifest(const QString &odfFileName, const QByteArray &manifestData, const QString &password)
: m_odfFileName(odfFileName)
, m_haveGoodPassword(false)
, m_password(password)
{
// I don't know why the parser barfs on this.
QByteArray manifestCopy = manifestData;
manifestCopy.replace(QByteArray("DOCTYPE manifest:manifest"), QByteArray("DOCTYPE manifest"));
QXmlStreamReader xml(manifestCopy);
ManifestEntry *currentEntry = nullptr;
while (!xml.atEnd()) {
xml.readNext();
if ((xml.tokenType() == QXmlStreamReader::NoToken) || (xml.tokenType() == QXmlStreamReader::Invalid) || (xml.tokenType() == QXmlStreamReader::StartDocument) || (xml.tokenType() == QXmlStreamReader::EndDocument) ||
(xml.tokenType() == QXmlStreamReader::DTD) || (xml.tokenType() == QXmlStreamReader::Characters)) {
continue;
}
if (xml.tokenType() == QXmlStreamReader::StartElement) {
if (xml.name().toString() == QLatin1String("manifest")) {
continue;
} else if (xml.name().toString() == QLatin1String("file-entry")) {
QXmlStreamAttributes attributes = xml.attributes();
if (currentEntry != nullptr) {
qCWarning(OkularOooDebug) << "Got new StartElement for new file-entry, but haven't finished the last one yet!";
qCWarning(OkularOooDebug) << "processing" << currentEntry->fileName() << ", got" << attributes.value(QStringLiteral("manifest:full-path")).toString();
}
currentEntry = new ManifestEntry(attributes.value(QStringLiteral("manifest:full-path")).toString());
currentEntry->setMimeType(attributes.value(QStringLiteral("manifest:media-type")).toString());
currentEntry->setSize(attributes.value(QStringLiteral("manifest:size")).toString());
} else if (xml.name().toString() == QLatin1String("encryption-data")) {
if (currentEntry == nullptr) {
qCWarning(OkularOooDebug) << "Got encryption-data without valid file-entry at line" << xml.lineNumber();
continue;
}
QXmlStreamAttributes encryptionAttributes = xml.attributes();
currentEntry->setChecksumType(encryptionAttributes.value(QStringLiteral("manifest:checksum-type")).toString());
currentEntry->setChecksum(encryptionAttributes.value(QStringLiteral("manifest:checksum")).toString());
} else if (xml.name().toString() == QLatin1String("algorithm")) {
if (currentEntry == nullptr) {
qCWarning(OkularOooDebug) << "Got algorithm without valid file-entry at line" << xml.lineNumber();
continue;
}
QXmlStreamAttributes algorithmAttributes = xml.attributes();
currentEntry->setAlgorithm(algorithmAttributes.value(QStringLiteral("manifest:algorithm-name")).toString());
currentEntry->setInitialisationVector(algorithmAttributes.value(QStringLiteral("manifest:initialisation-vector")).toString());
} else if (xml.name().toString() == QLatin1String("key-derivation")) {
if (currentEntry == nullptr) {
qCWarning(OkularOooDebug) << "Got key-derivation without valid file-entry at line" << xml.lineNumber();
continue;
}
QXmlStreamAttributes kdfAttributes = xml.attributes();
currentEntry->setKeyDerivationName(kdfAttributes.value(QStringLiteral("manifest:key-derivation-name")).toString());
currentEntry->setIterationCount(kdfAttributes.value(QStringLiteral("manifest:iteration-count")).toString());
currentEntry->setSalt(kdfAttributes.value(QStringLiteral("manifest:salt")).toString());
} else {
// handle other StartDocument types here
qCWarning(OkularOooDebug) << "Unexpected start document type: " << xml.name().toString();
}
} else if (xml.tokenType() == QXmlStreamReader::EndElement) {
if (xml.name().toString() == QLatin1String("manifest")) {
continue;
} else if (xml.name().toString() == QLatin1String("file-entry")) {
if (currentEntry == nullptr) {
qCWarning(OkularOooDebug) << "Got EndElement for file-entry without valid StartElement at line" << xml.lineNumber();
continue;
}
// we're finished processing that file entry
if (mEntries.contains(currentEntry->fileName())) {
qCWarning(OkularOooDebug) << "Can't insert entry because of duplicate name:" << currentEntry->fileName();
delete currentEntry;
} else {
mEntries.insert(currentEntry->fileName(), currentEntry);
}
currentEntry = nullptr;
}
}
}
if (xml.hasError()) {
qCWarning(OkularOooDebug) << "error: " << xml.errorString() << xml.lineNumber() << xml.columnNumber();
}
}
Manifest::~Manifest()
{
qDeleteAll(mEntries);
}
ManifestEntry *Manifest::entryByName(const QString &filename)
{
return mEntries.value(filename, nullptr);
}
bool Manifest::testIfEncrypted(const QString &filename)
{
ManifestEntry *entry = entryByName(filename);
if (entry) {
return (entry->salt().length() > 0);
}
return false;
}
void Manifest::checkPassword(ManifestEntry *entry, const QByteArray &fileData, QByteArray *decryptedData)
{
#ifdef QCA2
QCA::SymmetricKey key = QCA::PBKDF2(QStringLiteral("sha1"))
.makeKey(QCA::Hash(QStringLiteral("sha1")).hash(m_password.toLocal8Bit()),
QCA::InitializationVector(entry->salt()),
16, // 128 bit key
entry->iterationCount());
QCA::Cipher decoder(QStringLiteral("blowfish"), QCA::Cipher::CFB, QCA::Cipher::DefaultPadding, QCA::Decode, key, QCA::InitializationVector(entry->initialisationVector()));
*decryptedData = decoder.update(QCA::MemoryRegion(fileData)).toByteArray();
*decryptedData += decoder.final().toByteArray();
QByteArray csum;
if (entry->checksumType() == QLatin1String("SHA1/1K")) {
csum = QCA::Hash(QStringLiteral("sha1")).hash(decryptedData->left(1024)).toByteArray();
} else if (entry->checksumType() == QLatin1String("SHA1")) {
csum = QCA::Hash(QStringLiteral("sha1")).hash(*decryptedData).toByteArray();
} else {
qCDebug(OkularOooDebug) << "unknown checksum type: " << entry->checksumType();
// we can only assume it will be OK.
m_haveGoodPassword = true;
return;
}
if (entry->checksum() == csum) {
m_haveGoodPassword = true;
} else {
m_haveGoodPassword = false;
}
#else
m_haveGoodPassword = false;
Q_UNUSED(entry);
Q_UNUSED(fileData);
Q_UNUSED(decryptedData);
#endif
}
QByteArray Manifest::decryptFile(const QString &filename, const QByteArray &fileData)
{
#ifdef QCA2
ManifestEntry *entry = entryByName(filename);
if (!QCA::isSupported("sha1")) {
KMessageBox::error(nullptr, i18n("This document is encrypted, and crypto support is compiled in, but a hashing plugin could not be located"));
// in the hope that it wasn't really encrypted...
return QByteArray(fileData);
}
if (!QCA::isSupported("pbkdf2(sha1)")) {
KMessageBox::error(nullptr, i18n("This document is encrypted, and crypto support is compiled in, but a key derivation plugin could not be located"));
// in the hope that it wasn't really encrypted...
return QByteArray(fileData);
}
if (!QCA::isSupported("blowfish-cfb")) {
KMessageBox::error(nullptr, i18n("This document is encrypted, and crypto support is compiled in, but a cipher plugin could not be located"));
// in the hope that it wasn't really encrypted...
return QByteArray(fileData);
}
QByteArray decryptedData;
checkPassword(entry, fileData, &decryptedData);
if (!m_haveGoodPassword) {
return QByteArray();
}
QIODevice *decompresserDevice = new KCompressionDevice(new QBuffer(&decryptedData, nullptr), true, KCompressionDevice::GZip);
if (!decompresserDevice) {
qCDebug(OkularOooDebug) << "Couldn't create decompressor";
// hopefully it isn't compressed then!
return QByteArray(fileData);
}
static_cast<KFilterDev *>(decompresserDevice)->setSkipHeaders();
decompresserDevice->open(QIODevice::ReadOnly);
return decompresserDevice->readAll();
#else
// TODO: This should have a proper parent
KMessageBox::error(0, i18n("This document is encrypted, but Okular was compiled without crypto support. This document will probably not open."));
// this is equivalent to what happened before all this Manifest stuff :-)
return QByteArray(fileData);
Q_UNUSED(filename);
#endif
}