Warning, file /office/calligra/libs/store/KoEncryptedStore.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /* This file is part of the KDE project
0002    Copyright (C) 2006 Thomas Schaap <thomas.schaap@kdemail.net>
0003    Copyright (C) 2010 C. Boemann <cbo@boemann.dk>
0004 
0005    This library is free software; you can redistribute it and/or
0006    modify it under the terms of the GNU Library General Public
0007    License as published by the Free Software Foundation; either
0008    version 2 of the License, or (at your option) any later version.
0009 
0010    This library is distributed in the hope that it will be useful,
0011    but WITHOUT ANY WARRANTY; without even the implied warranty of
0012    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0013    Library General Public License for more details.
0014 
0015    You should have received a copy of the GNU Library General Public License
0016    along with this library; see the file COPYING.LIB.  If not, write to
0017    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0018    Boston, MA 02110-1301, USA.
0019 */
0020 #ifdef QCA2
0021 
0022 #include "KoEncryptedStore.h"
0023 #include "KoEncryptionChecker.h"
0024 #include "KoStore_p.h"
0025 #include "KoXmlReader.h"
0026 #include <KoXmlNS.h>
0027 
0028 #include <QString>
0029 #include <QByteArray>
0030 #include <QIODevice>
0031 #include <QWidget>
0032 #include <QBuffer>
0033 #include <kpassworddialog.h>
0034 #include <knewpassworddialog.h>
0035 #include <kwallet.h>
0036 #include <klocalizedstring.h>
0037 #include <kfilterdev.h>
0038 #include <kmessage.h>
0039 #include <kmessagebox.h>
0040 #include <kzip.h>
0041 #include <KoNetAccess.h>
0042 #include <QTemporaryFile>
0043 #include <StoreDebug.h>
0044 
0045 struct KoEncryptedStore_EncryptionData {
0046     // Needed for Key Derivation
0047     QCA::SecureArray salt;
0048     unsigned int iterationCount;
0049 
0050     // Needed for enc/decryption
0051     QCA::SecureArray initVector;
0052 
0053     // Needed for (optional) password-checking
0054     QCA::SecureArray checksum;
0055     // checksumShort is set to true if the checksum-algorithm is SHA1/1K, which basically means we only use the first 1024 bytes of the unencrypted file to check against (see also http://www.openoffice.org/servlets/ReadMsg?list=dev&msgNo=17498)
0056     bool checksumShort;
0057 
0058     // The size of the uncompressed file
0059     qint64 filesize;
0060 };
0061 
0062 // TODO: Discuss naming of this filer in saving-dialogues
0063 // TODO: Discuss possibility of allowing programs to remember the password after opening to enable them to supply it when saving
0064 // TODO: Discuss autosaving and password/leakage-problem (currently: hardcoded no autosave)
0065 namespace
0066 {
0067 const char MANIFEST_FILE[] = "META-INF/manifest.xml";
0068 const char META_FILE[] = "meta.xml";
0069 const char THUMBNAIL_FILE[] = "Thumbnails/thumbnail.png";
0070 }
0071 
0072 KoEncryptedStore::KoEncryptedStore(const QString & filename, Mode mode,
0073                                    const QByteArray & appIdentification, bool writeMimetype)
0074   : KoStore(mode, writeMimetype)
0075   , m_filename(filename)
0076   , m_tempFile(0)
0077   , m_bPasswordUsed(false)
0078   , m_bPasswordDeclined(false)
0079   , m_currentDir(0)
0080 {
0081     Q_D(KoStore);
0082 
0083     m_pZip = new KZip(filename);
0084     d->good = true;
0085     d->localFileName = filename;
0086 
0087     init(appIdentification);
0088 }
0089 
0090 KoEncryptedStore::KoEncryptedStore(QIODevice *dev, Mode mode, const QByteArray & appIdentification,
0091                                    bool writeMimetype)
0092     : KoStore(mode, writeMimetype)
0093     , m_tempFile(0)
0094     , m_bPasswordUsed(false)
0095     , m_bPasswordDeclined(false)
0096     , m_currentDir(0)
0097 {
0098     Q_D(KoStore);
0099 
0100     m_pZip = new KZip(dev);
0101     d->good = true;
0102 
0103     init(appIdentification);
0104 }
0105 
0106 KoEncryptedStore::KoEncryptedStore(QWidget* window, const QUrl &url, const QString & filename,
0107                                    Mode mode,
0108                                    const QByteArray & appIdentification, bool writeMimetype)
0109     : KoStore(mode, writeMimetype)
0110     , m_filename(url.url())
0111     , m_tempFile(0)
0112     , m_bPasswordUsed(false)
0113     , m_bPasswordDeclined(false)
0114     , m_currentDir(0)
0115 {
0116     Q_D(KoStore);
0117 
0118     d->window = window;
0119     d->good = true;
0120 
0121     if (mode == Read) {
0122         d->fileMode = KoStorePrivate::RemoteRead;
0123         d->localFileName = filename;
0124         m_pZip = new KZip(d->localFileName);
0125     } else {
0126         d->fileMode = KoStorePrivate::RemoteWrite;
0127         m_tempFile = new QTemporaryFile();
0128         if (!m_tempFile->open()) {
0129             d->good = false;
0130         } else {
0131             d->localFileName = m_tempFile->fileName();
0132             m_pZip = new KZip(m_tempFile);
0133         }
0134     }
0135     d->url = url;
0136 
0137     init(appIdentification);
0138 }
0139 
0140 void KoEncryptedStore::init(const QByteArray & appIdentification)
0141 {
0142     Q_D(KoStore);
0143     bool checksumErrorShown = false;
0144     bool unreadableErrorShown = false;
0145     if (d->mode == Write) {
0146         d->good = KoEncryptionChecker::isEncryptionSupported();
0147         if (d->good) {
0148             if (!m_pZip->open(QIODevice::WriteOnly)) {
0149                 d->good = false;
0150                 return;
0151             }
0152             m_pZip->setExtraField(KZip::NoExtraField);
0153             // Write identification
0154             if (d->writeMimetype) {
0155                 m_pZip->setCompression(KZip::NoCompression);
0156                 (void)m_pZip->writeFile(QLatin1String("mimetype"), appIdentification);
0157             }
0158             // FIXME: Hmm, seems to be a bug here since this is
0159             //        inconsistent with the code in openWrite():
0160             m_pZip->setCompression(KZip::DeflateCompression);
0161             // We don't need the extra field in Calligra - so we leave it as "no extra field".
0162         }
0163     } else {
0164         d->good = m_pZip->open(QIODevice::ReadOnly);
0165         d->good &= m_pZip->directory() != 0;
0166         if (!d->good) {
0167             return;
0168         }
0169 
0170         // Read the manifest-file, so we can get the data we'll need to decrypt the other files in the store
0171         const KArchiveEntry* manifestArchiveEntry = m_pZip->directory()->entry(MANIFEST_FILE);
0172         if (!manifestArchiveEntry || !manifestArchiveEntry->isFile()) {
0173             // No manifest file? OK, *I* won't complain
0174             return;
0175         }
0176         QIODevice *dev = (static_cast< const KArchiveFile* >(manifestArchiveEntry))->createDevice();
0177 
0178         KoXmlDocument xmldoc;
0179         bool namespaceProcessing = true; // for the manifest ignore the namespace (bug #260515)
0180         if (!xmldoc.setContent(dev, namespaceProcessing) || xmldoc.documentElement().localName() != "manifest" || xmldoc.documentElement().namespaceURI() != KoXmlNS::manifest) {
0181             //KMessage::message(KMessage::Warning, i18n("The manifest file seems to be corrupted. The document could not be opened."));
0182             /// FIXME this message is not something we actually want to not mention, but it makes thumbnails noisy at times, so... let's not
0183             dev->close();
0184             delete dev;
0185             m_pZip->close();
0186             d->good = false;
0187             return;
0188         }
0189         KoXmlElement xmlroot = xmldoc.documentElement();
0190         if (xmlroot.hasChildNodes()) {
0191             QCA::Base64 base64decoder(QCA::Decode);
0192             KoXmlNode xmlnode = xmlroot.firstChild();
0193             while (!xmlnode.isNull()) {
0194                 // Search for files
0195                 if (!xmlnode.isElement() || xmlroot.namespaceURI() != KoXmlNS::manifest || xmlnode.toElement().localName() != "file-entry" || !xmlnode.toElement().hasAttribute("full-path") || !xmlnode.hasChildNodes()) {
0196                     xmlnode = xmlnode.nextSibling();
0197                     continue;
0198                 }
0199 
0200                 // Build a structure to hold the data and fill it with defaults
0201                 KoEncryptedStore_EncryptionData encData;
0202                 encData.filesize = 0;
0203                 encData.checksum = QCA::SecureArray();
0204                 encData.checksumShort = false;
0205                 encData.salt = QCA::SecureArray();
0206                 encData.iterationCount = 0;
0207                 encData.initVector = QCA::SecureArray();
0208 
0209                 // Get some info about the file
0210                 QString fullpath = xmlnode.toElement().attribute("full-path");
0211 
0212                 if (xmlnode.toElement().hasAttribute("size")) {
0213                     encData.filesize = xmlnode.toElement().attribute("size").toUInt();
0214                 }
0215 
0216                 // Find the embedded encryption-data block
0217                 KoXmlNode xmlencnode = xmlnode.firstChild();
0218                 while (!xmlencnode.isNull() && (!xmlencnode.isElement() || xmlencnode.toElement().localName() != "encryption-data" || !xmlencnode.hasChildNodes())) {
0219                     xmlencnode = xmlencnode.nextSibling();
0220                 }
0221                 if (xmlencnode.isNull()) {
0222                     xmlnode = xmlnode.nextSibling();
0223                     continue;
0224                 }
0225 
0226                 // Find some things about the checksum
0227                 if (xmlencnode.toElement().hasAttribute("checksum")) {
0228                     base64decoder.clear();
0229                     encData.checksum = base64decoder.decode(QCA::SecureArray(xmlencnode.toElement().attribute("checksum").toLatin1()));
0230                     if (xmlencnode.toElement().hasAttribute("checksum-type")) {
0231                         QString checksumType = xmlencnode.toElement().attribute("checksum-type");
0232                         if (checksumType == "SHA1") {
0233                             encData.checksumShort = false;
0234                         }
0235                         // For this particual hash-type: check KoEncryptedStore_encryptionData.checksumShort
0236                         else if (checksumType == "SHA1/1K") {
0237                             encData.checksumShort = true;
0238                         } else {
0239                             // Checksum type unknown
0240                             if (!checksumErrorShown) {
0241                                 KMessage::message(KMessage::Warning, i18n("This document contains an unknown checksum. When you give a password it might not be verified."));
0242                                 checksumErrorShown = true;
0243                             }
0244                             encData.checksum = QCA::SecureArray();
0245                         }
0246                     } else {
0247                         encData.checksumShort = false;
0248                     }
0249                 }
0250 
0251                 KoXmlNode xmlencattr = xmlencnode.firstChild();
0252                 bool algorithmFound = false;
0253                 bool keyDerivationFound = false;
0254                 // Search all data about encryption
0255                 while (!xmlencattr.isNull()) {
0256                     if (!xmlencattr.isElement()) {
0257                         xmlencattr = xmlencattr.nextSibling();
0258                         continue;
0259                     }
0260 
0261                     // Find some things about the encryption algorithm
0262                     if (xmlencattr.toElement().localName() == "algorithm" && xmlencattr.toElement().hasAttribute("initialisation-vector")) {
0263                         algorithmFound = true;
0264                         encData.initVector = base64decoder.decode(QCA::SecureArray(xmlencattr.toElement().attribute("initialisation-vector").toLatin1()));
0265                         if (xmlencattr.toElement().hasAttribute("algorithm-name") && xmlencattr.toElement().attribute("algorithm-name") != "Blowfish CFB") {
0266                             if (!unreadableErrorShown) {
0267                                 KMessage::message(KMessage::Warning, i18n("This document contains an unknown encryption method. Some parts may be unreadable."));
0268                                 unreadableErrorShown = true;
0269                             }
0270                             encData.initVector = QCA::SecureArray();
0271                         }
0272                     }
0273 
0274                     // Find some things about the key derivation
0275                     if (xmlencattr.toElement().localName() == "key-derivation" && xmlencattr.toElement().hasAttribute("salt")) {
0276                         keyDerivationFound = true;
0277                         encData.salt = base64decoder.decode(QCA::SecureArray(xmlencattr.toElement().attribute("salt").toLatin1()));
0278                         encData.iterationCount = 1024;
0279                         if (xmlencattr.toElement().hasAttribute("iteration-count")) {
0280                             encData.iterationCount = xmlencattr.toElement().attribute("iteration-count").toUInt();
0281                         }
0282                         if (xmlencattr.toElement().hasAttribute("key-derivation-name") && xmlencattr.toElement().attribute("key-derivation-name") != "PBKDF2") {
0283                             if (!unreadableErrorShown) {
0284                                 KMessage::message(KMessage::Warning, i18n("This document contains an unknown encryption method. Some parts may be unreadable."));
0285                                 unreadableErrorShown = true;
0286                             }
0287                             encData.salt = QCA::SecureArray();
0288                         }
0289                     }
0290 
0291                     xmlencattr = xmlencattr.nextSibling();
0292                 }
0293 
0294                 // Only use this encryption data if it makes sense to use it
0295                 if (!(encData.salt.isEmpty() || encData.initVector.isEmpty())) {
0296                     m_encryptionData.insert(fullpath, encData);
0297                     if (!(algorithmFound && keyDerivationFound)) {
0298                         if (!unreadableErrorShown) {
0299                             KMessage::message(KMessage::Warning, i18n("This document contains incomplete encryption data. Some parts may be unreadable."));
0300                             unreadableErrorShown = true;
0301                         }
0302                     }
0303                 }
0304 
0305                 xmlnode = xmlnode.nextSibling();
0306             }
0307         }
0308         dev->close();
0309         delete dev;
0310 
0311         if (isEncrypted() && !(QCA::isSupported("sha1") && QCA::isSupported("pbkdf2(sha1)") && QCA::isSupported("blowfish-cfb"))) {
0312             d->good = false;
0313             KMessage::message(KMessage::Error, i18n("QCA has currently no support for SHA1 or PBKDF2 using SHA1. The document can not be opened."));
0314         }
0315     }
0316 }
0317 
0318 bool KoEncryptedStore::doFinalize()
0319 {
0320     Q_D(KoStore);
0321     if (d->good) {
0322         if (isOpen()) {
0323             close();
0324         }
0325         if (d->mode == Write) {
0326             // First change the manifest file and write it
0327             // We'll use the QDom classes here, since KoXmlReader and KoXmlWriter have no way of copying a complete xml-file
0328             // other than parsing it completely and rebuilding it.
0329             // Errorhandling here is done to prevent data from being lost whatever happens
0330             // TODO: Convert this to KoXML when KoXML is extended enough
0331             // Note: right now this is impossible due to lack of possibilities to copy an element as-is
0332             QDomDocument document;
0333             if (m_manifestBuffer.isEmpty()) {
0334                 // No manifest? Better create one
0335                 document = QDomDocument();
0336                 QDomElement rootElement = document.createElement("manifest:manifest");
0337                 rootElement.setAttribute("xmlns:manifest", "urn:oasis:names:tc:opendocument:xmlns:manifest:1.0");
0338                 document.appendChild(rootElement);
0339             }
0340             if (!m_manifestBuffer.isEmpty() && !document.setContent(m_manifestBuffer)) {
0341                 // Oi! That's fresh XML we should have here!
0342                 // This is the only case we can't fix
0343                 KMessage::message(KMessage::Error, i18n("The manifest file seems to be corrupted. It cannot be modified and the document will remain unreadable. Please try and save the document again to prevent losing your work."));
0344                 m_pZip->close();
0345                 return false;
0346             }
0347             QDomElement documentElement = document.documentElement();
0348             QDomNodeList fileElements = documentElement.elementsByTagName("manifest:file-entry");
0349             // Search all files in the manifest
0350             QStringList foundFiles;
0351             for (int i = 0; i < fileElements.size(); i++) {
0352                 QDomElement fileElement = fileElements.item(i).toElement();
0353                 QString fullpath = fileElement.toElement().attribute("manifest:full-path");
0354                 // See if it's encrypted
0355                 if (fullpath.isEmpty() || !m_encryptionData.contains(fullpath)) {
0356                     continue;
0357                 }
0358                 foundFiles += fullpath;
0359                 KoEncryptedStore_EncryptionData encData = m_encryptionData.value(fullpath);
0360                 // Set the unencrypted size of the file
0361                 fileElement.setAttribute("manifest:size", encData.filesize);
0362                 // See if the user of this store has already provided (old) encryption data
0363                 QDomNodeList childElements = fileElement.elementsByTagName("manifest:encryption-data");
0364                 QDomElement encryptionElement;
0365                 QDomElement algorithmElement;
0366                 QDomElement keyDerivationElement;
0367                 if (childElements.isEmpty()) {
0368                     encryptionElement = document.createElement("manifest:encryption-data");
0369                     fileElement.appendChild(encryptionElement);
0370                 } else {
0371                     encryptionElement = childElements.item(0).toElement();
0372                 }
0373                 childElements = encryptionElement.elementsByTagName("manifest:algorithm");
0374                 if (childElements.isEmpty()) {
0375                     algorithmElement = document.createElement("manifest:algorithm");
0376                     encryptionElement.appendChild(algorithmElement);
0377                 } else {
0378                     algorithmElement = childElements.item(0).toElement();
0379                 }
0380                 childElements = encryptionElement.elementsByTagName("manifest:key-derivation");
0381                 if (childElements.isEmpty()) {
0382                     keyDerivationElement = document.createElement("manifest:key-derivation");
0383                     encryptionElement.appendChild(keyDerivationElement);
0384                 } else {
0385                     keyDerivationElement = childElements.item(0).toElement();
0386                 }
0387                 // Set the right encryption data
0388                 QCA::Base64 encoder;
0389                 QCA::SecureArray checksum = encoder.encode(encData.checksum);
0390                 if (encData.checksumShort) {
0391                     encryptionElement.setAttribute("manifest:checksum-type", "SHA1/1K");
0392                 } else {
0393                     encryptionElement.setAttribute("manifest:checksum-type", "SHA1");
0394                 }
0395                 encryptionElement.setAttribute("manifest:checksum", QString(checksum.toByteArray()));
0396                 QCA::SecureArray initVector = encoder.encode(encData.initVector);
0397                 algorithmElement.setAttribute("manifest:algorithm-name", "Blowfish CFB");
0398                 algorithmElement.setAttribute("manifest:initialisation-vector", QString(initVector.toByteArray()));
0399                 QCA::SecureArray salt = encoder.encode(encData.salt);
0400                 keyDerivationElement.setAttribute("manifest:key-derivation-name", "PBKDF2");
0401                 keyDerivationElement.setAttribute("manifest:iteration-count", QString::number(encData.iterationCount));
0402                 keyDerivationElement.setAttribute("manifest:salt", QString(salt.toByteArray()));
0403             }
0404             if (foundFiles.size() < m_encryptionData.size()) {
0405                 QList<QString> keys = m_encryptionData.keys();
0406                 for (int i = 0; i < keys.size(); i++) {
0407                     if (!foundFiles.contains(keys.value(i))) {
0408                         KoEncryptedStore_EncryptionData encData = m_encryptionData.value(keys.value(i));
0409                         QDomElement fileElement = document.createElement("manifest:file-entry");
0410                         fileElement.setAttribute("manifest:full-path", keys.value(i));
0411                         fileElement.setAttribute("manifest:size", encData.filesize);
0412                         fileElement.setAttribute("manifest:media-type", "");
0413                         documentElement.appendChild(fileElement);
0414                         QDomElement encryptionElement = document.createElement("manifest:encryption-data");
0415                         QCA::Base64 encoder;
0416                         QCA::SecureArray checksum = encoder.encode(encData.checksum);
0417                         QCA::SecureArray initVector = encoder.encode(encData.initVector);
0418                         QCA::SecureArray salt = encoder.encode(encData.salt);
0419                         if (encData.checksumShort) {
0420                             encryptionElement.setAttribute("manifest:checksum-type", "SHA1/1K");
0421                         } else {
0422                             encryptionElement.setAttribute("manifest:checksum-type", "SHA1");
0423                         }
0424                         encryptionElement.setAttribute("manifest:checksum", QString(checksum.toByteArray()));
0425                         fileElement.appendChild(encryptionElement);
0426                         QDomElement algorithmElement = document.createElement("manifest:algorithm");
0427                         algorithmElement.setAttribute("manifest:algorithm-name", "Blowfish CFB");
0428                         algorithmElement.setAttribute("manifest:initialisation-vector", QString(initVector.toByteArray()));
0429                         encryptionElement.appendChild(algorithmElement);
0430                         QDomElement keyDerivationElement = document.createElement("manifest:key-derivation");
0431                         keyDerivationElement.setAttribute("manifest:key-derivation-name", "PBKDF2");
0432                         keyDerivationElement.setAttribute("manifest:iteration-count", QString::number(encData.iterationCount));
0433                         keyDerivationElement.setAttribute("manifest:salt", QString(salt.toByteArray()));
0434                         encryptionElement.appendChild(keyDerivationElement);
0435                     }
0436                 }
0437             }
0438             m_manifestBuffer = document.toByteArray();
0439             m_pZip->setCompression(KZip::DeflateCompression);
0440             if (!m_pZip->writeFile(QLatin1String(MANIFEST_FILE), m_manifestBuffer)) {
0441                 KMessage::message(KMessage::Error, i18n("The manifest file cannot be written. The document will remain unreadable. Please try and save the document again to prevent losing your work."));
0442                 m_pZip->close();
0443                 return false;
0444             }
0445         }
0446     }
0447     if (m_pZip)
0448         return m_pZip->close();
0449     else
0450         return true;
0451 }
0452 
0453 KoEncryptedStore::~KoEncryptedStore()
0454 {
0455     Q_D(KoStore);
0456     /* Finalization of an encrypted store must happen earlier than deleting the zip. This rule normally is executed by KoStore, but too late to do any good.*/
0457     if (!d->finalized) {
0458         finalize();
0459     }
0460 
0461     delete m_pZip;
0462 
0463     if (d->fileMode == KoStorePrivate::RemoteWrite) {
0464         KIO::NetAccess::upload(d->localFileName, d->url, d->window);
0465         delete m_tempFile;
0466     } else if (d->fileMode == KoStorePrivate::RemoteRead) {
0467         KIO::NetAccess::removeTempFile(d->localFileName);
0468     }
0469 
0470     delete d->stream;
0471 }
0472 
0473 bool KoEncryptedStore::isEncrypted()
0474 {
0475     Q_D(KoStore);
0476     if (d->mode == Read) {
0477         return !m_encryptionData.isEmpty();
0478     }
0479     return true;
0480 }
0481 
0482 QStringList KoEncryptedStore::directoryList() const
0483 {
0484     QStringList retval;
0485     const KArchiveDirectory *directory = m_pZip->directory();
0486     foreach(const QString &name, directory->entries()) {
0487         const KArchiveEntry* fileArchiveEntry = m_pZip->directory()->entry(name);
0488         if (fileArchiveEntry->isDirectory()) {
0489             retval << name;
0490         }
0491     }
0492     return retval;
0493 }
0494 
0495 bool KoEncryptedStore::isToBeEncrypted(const QString& name)
0496 {
0497     return !(name == META_FILE || name == MANIFEST_FILE || name == THUMBNAIL_FILE);
0498 }
0499 
0500 bool KoEncryptedStore::openRead(const QString& name)
0501 {
0502     Q_D(KoStore);
0503     if (bad())
0504         return false;
0505 
0506     const KArchiveEntry* fileArchiveEntry = m_pZip->directory()->entry(name);
0507     if (!fileArchiveEntry) {
0508         return false;
0509     }
0510     if (fileArchiveEntry->isDirectory()) {
0511         warnStore << name << " is a directory!";
0512         return false;
0513     }
0514     const KZipFileEntry* fileZipEntry = static_cast<const KZipFileEntry*>(fileArchiveEntry);
0515 
0516     delete d->stream;
0517     d->stream = fileZipEntry->createDevice();
0518     d->size = fileZipEntry->size();
0519     if (m_encryptionData.contains(name)) {
0520         // This file is encrypted, do some decryption first
0521         if (m_bPasswordDeclined) {
0522             // The user has already declined to give a password
0523             // Open the file as empty
0524             d->stream->close();
0525             delete d->stream;
0526             d->stream = new QBuffer();
0527             d->stream->open(QIODevice::ReadOnly);
0528             d->size = 0;
0529             return true;
0530         }
0531         QCA::SecureArray encryptedFile(d->stream->readAll());
0532         if (encryptedFile.size() != d->size) {
0533             // Read error detected
0534             d->stream->close();
0535             delete d->stream;
0536             d->stream = nullptr;
0537             warnStore << "read error";
0538             return false;
0539         }
0540         d->stream->close();
0541         delete d->stream;
0542         d->stream = nullptr;
0543         KoEncryptedStore_EncryptionData encData = m_encryptionData.value(name);
0544         QCA::SecureArray decrypted;
0545 
0546         // If we don't have a password yet, try and find one
0547         if (m_password.isEmpty()) {
0548             findPasswordInKWallet();
0549         }
0550 
0551         while (true) {
0552             QCA::SecureArray password;
0553             bool keepPass = false;
0554             // I already have a password! Let's test it. If it's not good, we can dump it, anyway.
0555             if (!m_password.isEmpty()) {
0556                 password = m_password;
0557                 m_password = QCA::SecureArray();
0558             } else {
0559                 if (!m_filename.isNull())
0560                     keepPass = false;
0561                 KPasswordDialog dlg(d->window , keepPass ? KPasswordDialog::ShowKeepPassword : static_cast<KPasswordDialog::KPasswordDialogFlags>(0));
0562                 dlg.setPrompt(i18n("Please enter the password to open this file."));
0563                 if (! dlg.exec()) {
0564                     m_bPasswordDeclined = true;
0565                     d->stream = new QBuffer();
0566                     d->stream->open(QIODevice::ReadOnly);
0567                     d->size = 0;
0568                     return true;
0569                 }
0570                 password = QCA::SecureArray(dlg.password().toUtf8());
0571                 if (keepPass)
0572                     keepPass = dlg.keepPassword();
0573                 if (password.isEmpty()) {
0574                     continue;
0575                 }
0576             }
0577 
0578             decrypted = decryptFile(encryptedFile, encData, password);
0579             if (decrypted.isEmpty()) {
0580                 errorStore << "empty decrypted file" << endl;
0581                 return false;
0582             }
0583 
0584             if (!encData.checksum.isEmpty()) {
0585                 QCA::SecureArray checksum;
0586                 if (encData.checksumShort && decrypted.size() > 1024) {
0587                     // TODO: Eww!!!! I don't want to convert via insecure arrays to get the first 1K characters of a secure array <- fix QCA?
0588                     checksum = QCA::Hash("sha1").hash(QCA::SecureArray(decrypted.toByteArray().left(1024)));
0589                 } else {
0590                     checksum = QCA::Hash("sha1").hash(decrypted);
0591                 }
0592                 if (checksum != encData.checksum) {
0593                     continue;
0594                 }
0595             }
0596 
0597             // The password passed all possible tests, so let's accept it
0598             m_password = password;
0599             m_bPasswordUsed = true;
0600 
0601             if (keepPass) {
0602                 savePasswordInKWallet();
0603             }
0604 
0605             break;
0606         }
0607 
0608         QByteArray *resultArray = new QByteArray(decrypted.toByteArray());
0609         KCompressionDevice::CompressionType type = KFilterDev::compressionTypeForMimeType("application/x-gzip");
0610         QIODevice *resultDevice = new KCompressionDevice(new QBuffer(resultArray, nullptr), false, type);
0611 
0612         if (!resultDevice) {
0613             delete resultArray;
0614             return false;
0615         }
0616         static_cast<KFilterDev*>(resultDevice)->setSkipHeaders();
0617         d->stream = resultDevice;
0618         d->size = encData.filesize;
0619     }
0620     if (!d->stream->isOpen()) {
0621         d->stream->open(QIODevice::ReadOnly);
0622     }
0623     return true;
0624 }
0625 
0626 bool KoEncryptedStore::closeRead()
0627 {
0628     Q_D(KoStore);
0629     delete d->stream;
0630     d->stream = nullptr;
0631     return true;
0632 }
0633 
0634 void KoEncryptedStore::findPasswordInKWallet()
0635 {
0636     Q_D(KoStore);
0637     /* About KWallet access
0638      *
0639      * The choice has been made to postfix every entry in a kwallet concerning passwords for opendocument files with /opendocument
0640      * This choice has been made since, at the time of this writing, the author could not find any reference to standardized
0641      * naming schemes for entries in the wallet. Since collision of passwords in entries should be avoided and is at least possible,
0642      * considering remote files might be both protected by a secured web-area (konqueror makes an entry) and a password (we make an
0643      * entry), it seems a good thing to make sure it won't happen.
0644      */
0645     if (!m_filename.isNull() && !KWallet::Wallet::folderDoesNotExist(KWallet::Wallet::LocalWallet(), KWallet::Wallet::PasswordFolder()) && !KWallet::Wallet::keyDoesNotExist(KWallet::Wallet::LocalWallet(), KWallet::Wallet::PasswordFolder(), m_filename + "/opendocument")) {
0646         KWallet::Wallet *wallet = KWallet::Wallet::openWallet(KWallet::Wallet::LocalWallet(), d->window ? d->window->winId() : 0);
0647         if (wallet) {
0648             if (wallet->setFolder(KWallet::Wallet::PasswordFolder())) {
0649                 QString pass;
0650                 wallet->readPassword(m_filename + "/opendocument", pass);
0651                 m_password = QCA::SecureArray(pass.toUtf8());
0652             }
0653             delete wallet;
0654         }
0655     }
0656 }
0657 
0658 void KoEncryptedStore::savePasswordInKWallet()
0659 {
0660     Q_D(KoStore);
0661     KWallet::Wallet *wallet = KWallet::Wallet::openWallet(KWallet::Wallet::LocalWallet(), d->window ? d->window->winId() : 0);
0662     if (wallet) {
0663         if (!wallet->hasFolder(KWallet::Wallet::PasswordFolder())) {
0664             wallet->createFolder(KWallet::Wallet::PasswordFolder());
0665         }
0666         if (wallet->setFolder(KWallet::Wallet::PasswordFolder())) {
0667             if (wallet->hasEntry(m_filename + "/opendocument")) {
0668                 wallet->removeEntry(m_filename + "/opendocument");
0669             }
0670             wallet->writePassword(m_filename + "/opendocument", m_password.toByteArray().constData());
0671         }
0672         delete wallet;
0673     }
0674 }
0675 
0676 QCA::SecureArray KoEncryptedStore::decryptFile(QCA::SecureArray & encryptedFile, KoEncryptedStore_EncryptionData & encData, QCA::SecureArray & password)
0677 {
0678     QCA::SecureArray keyhash = QCA::Hash("sha1").hash(password);
0679     QCA::SymmetricKey key = QCA::PBKDF2("sha1").makeKey(keyhash, QCA::InitializationVector(encData.salt), 16, encData.iterationCount);
0680     QCA::Cipher decrypter("blowfish", QCA::Cipher::CFB, QCA::Cipher::DefaultPadding, QCA::Decode, key, QCA::InitializationVector(encData.initVector));
0681     QCA::SecureArray result = decrypter.update(encryptedFile);
0682     result += decrypter.final();
0683     return result;
0684 }
0685 
0686 bool KoEncryptedStore::setPassword(const QString& password)
0687 {
0688     if (m_bPasswordUsed || password.isEmpty()) {
0689         return false;
0690     }
0691     m_password = QCA::SecureArray(password.toUtf8());
0692     return true;
0693 }
0694 
0695 QString KoEncryptedStore::password()
0696 {
0697     if (m_password.isEmpty()) {
0698         return QString();
0699     }
0700     return QString(m_password.toByteArray());
0701 }
0702 
0703 bool KoEncryptedStore::openWrite(const QString& name)
0704 {
0705     Q_D(KoStore);
0706     if (bad())
0707         return false;
0708     if (isToBeEncrypted(name)) {
0709         // Encrypted files will be compressed by this class and should be stored in the zip as not compressed
0710         m_pZip->setCompression(KZip::NoCompression);
0711     } else {
0712         m_pZip->setCompression(KZip::DeflateCompression);
0713     }
0714     d->stream = new QBuffer();
0715     (static_cast< QBuffer* >(d->stream))->open(QIODevice::WriteOnly);
0716     if (name == MANIFEST_FILE)
0717         return true;
0718     return m_pZip->prepareWriting(name, "", "", 0);
0719 }
0720 
0721 bool KoEncryptedStore::closeWrite()
0722 {
0723     Q_D(KoStore);
0724     bool passWasAsked = false;
0725     if (d->fileName == MANIFEST_FILE) {
0726         m_manifestBuffer = static_cast<QBuffer*>(d->stream)->buffer();
0727         return true;
0728     }
0729 
0730     // Find a password
0731     // Do not accept empty passwords for compatibility with OOo
0732     if (m_password.isEmpty()) {
0733         findPasswordInKWallet();
0734     }
0735     while (m_password.isEmpty()) {
0736         KNewPasswordDialog dlg(d->window);
0737         dlg.setPrompt(i18n("Please enter the password to encrypt the document with."));
0738         if (! dlg.exec()) {
0739             // Without the first password, prevent asking again by deadsimply refusing to continue functioning
0740             // TODO: This feels rather hackish. There should be a better way to do this.
0741             delete m_pZip;
0742             m_pZip = 0;
0743             d->good = false;
0744             return false;
0745         }
0746         m_password = QCA::SecureArray(dlg.password().toUtf8());
0747         passWasAsked = true;
0748     }
0749 
0750     // Ask the user to save the password
0751     if (passWasAsked && KMessageBox::questionYesNo(d->window, i18n("Do you want to save the password?")) == KMessageBox::Yes) {
0752         savePasswordInKWallet();
0753     }
0754 
0755     QByteArray resultData;
0756     if (d->fileName == THUMBNAIL_FILE) {
0757         // TODO: Replace with a generic 'encrypted'-thumbnail
0758         resultData = static_cast<QBuffer*>(d->stream)->buffer();
0759     } else if (!isToBeEncrypted(d->fileName)) {
0760         resultData = static_cast<QBuffer*>(d->stream)->buffer();
0761     } else {
0762         m_bPasswordUsed = true;
0763         // Build all cryptographic data
0764         QCA::SecureArray passwordHash = QCA::Hash("sha1").hash(m_password);
0765         QCA::Random random;
0766         KoEncryptedStore_EncryptionData encData;
0767         encData.initVector = random.randomArray(8);
0768         encData.salt = random.randomArray(16);
0769         encData.iterationCount = 1024;
0770         QCA::SymmetricKey key = QCA::PBKDF2("sha1").makeKey(passwordHash, QCA::InitializationVector(encData.salt), 16, encData.iterationCount);
0771         QCA::Cipher encrypter("blowfish", QCA::Cipher::CFB, QCA::Cipher::DefaultPadding, QCA::Encode, key, QCA::InitializationVector(encData.initVector));
0772 
0773         // Get the written data
0774         QByteArray data = static_cast<QBuffer*>(d->stream)->buffer();
0775         encData.filesize = data.size();
0776 
0777         // Compress the data
0778         QBuffer compressedData;
0779         KCompressionDevice::CompressionType type = KFilterDev::compressionTypeForMimeType("application/x-gzip");
0780         QIODevice *compressDevice = new KCompressionDevice(&compressedData, false, type);
0781 
0782         if (!compressDevice) {
0783             return false;
0784         }
0785         static_cast<KFilterDev*>(compressDevice)->setSkipHeaders();
0786         if (!compressDevice->open(QIODevice::WriteOnly)) {
0787             delete compressDevice;
0788             return false;
0789         }
0790         if (compressDevice->write(data) != data.size()) {
0791             delete compressDevice;
0792             return false;
0793         }
0794         compressDevice->close();
0795         delete compressDevice;
0796 
0797         encData.checksum = QCA::Hash("sha1").hash(QCA::SecureArray(compressedData.buffer()));
0798         encData.checksumShort = false;
0799 
0800         // Encrypt the data
0801         QCA::SecureArray result = encrypter.update(QCA::SecureArray(compressedData.buffer()));
0802         result += encrypter.final();
0803         resultData = result.toByteArray();
0804 
0805         m_encryptionData.insert(d->fileName, encData);
0806     }
0807 
0808     if (!m_pZip->writeData(resultData.data(), resultData.size())) {
0809         m_pZip->finishWriting(resultData.size());
0810         return false;
0811     }
0812 
0813     return m_pZip->finishWriting(resultData.size());
0814 }
0815 
0816 bool KoEncryptedStore::enterRelativeDirectory(const QString& dirName)
0817 {
0818     Q_D(KoStore);
0819     if (d->mode == Read) {
0820         if (!m_currentDir) {
0821             m_currentDir = m_pZip->directory(); // initialize
0822         }
0823         const KArchiveEntry *entry = m_currentDir->entry(dirName);
0824         if (entry && entry->isDirectory()) {
0825             m_currentDir = dynamic_cast<const KArchiveDirectory*>(entry);
0826             return m_currentDir != 0;
0827         }
0828         return false;
0829     } else { // Write, no checking here
0830         return true;
0831     }
0832 }
0833 
0834 bool KoEncryptedStore::enterAbsoluteDirectory(const QString& path)
0835 {
0836     if (path.isEmpty()) {
0837         m_currentDir = 0;
0838         return true;
0839     }
0840     m_currentDir = dynamic_cast<const KArchiveDirectory*>(m_pZip->directory()->entry(path));
0841     return m_currentDir != 0;
0842 }
0843 
0844 bool KoEncryptedStore::fileExists(const QString& absPath) const
0845 {
0846     const KArchiveEntry *entry = m_pZip->directory()->entry(absPath);
0847     return (entry && entry->isFile()) || (absPath == MANIFEST_FILE && !m_manifestBuffer.isNull());
0848 }
0849 
0850 #endif // QCA2