diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..d281fb8 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,74 @@ +# Changelog + +## Squawk 0.1.4 (UNRELEASED) +------------------------------ +### New features +- several ways to manage your account password: + - store it in plain text with the config (like it always was) + - store it in config jammed (local hashing with the constant seed, not secure at all but might look like it is) + - ask the account password on each program launch + +### Bug fixes +- never updating MUC avatars now update +- going offline related segfault fix + + +## Squawk 0.1.3 (Mar 31, 2020) +------------------------------ +### New features +- delivery states for the messages +- delivery receipts now work for real +- avatars in conferences +- edited messages now display correctly +- restyling to get better look with different desktop themes + +### Bug fixes +- clickable links now detects better +- fixed lost messages that came with no ID +- avatar related fixes +- message carbons now get turned on only if the server supports them +- progress spinner fix +- files in dialog now have better comment + + +## Squawk 0.1.2 (Dec 25, 2019) +------------------------------ +### New features +- icons in roster for conferences +- pal avatar in dialog window +- avatars next to every message in dialog windows (not in conferences yet) +- roster window position and size now are stored in config +- expanded accounts and groups are stored in config +- availability (from top combobox) now is stored in config + +### Bug fixes +- segfault when sending more then one attached file +- wrong path and name of saving file +- wrong message syntax when attaching file and writing text in the save message +- problem with links highlighting in dialog +- project building fixes + + +## Squawk 0.1.1 (Nov 16, 2019) +------------------------------ +A lot of bug fixes, memory leaks fixes +### New features +- exchange files via HTTP File Upload +- download VCards of your contacts +- upload your VCard with information about your contact phones email addresses, names, career information and avatar +- avatars of your contacts in roster and in notifications + + +## Squawk 0.0.5 (Oct 10, 2019) +------------------------------ +### Features +- chat directly +- have multiple accounts +- add contacts +- remove contacts +- assign contact to different groups +- chat in MUCs +- join MUCs, leave them, keep them subscribed or unsubscribed +- download attachmets +- local history +- desktop notifications of new messages diff --git a/CMakeLists.txt b/CMakeLists.txt index 3e9968c..aac3e75 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -74,6 +74,8 @@ endif() add_subdirectory(ui) add_subdirectory(core) +add_subdirectory(external/simpleCrypt) + target_link_libraries(squawk squawkUI) target_link_libraries(squawk squawkCORE) target_link_libraries(squawk uuid) diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index fab36f2..ad37198 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -37,3 +37,4 @@ target_link_libraries(squawkCORE Qt5::Gui) target_link_libraries(squawkCORE Qt5::Xml) target_link_libraries(squawkCORE qxmpp) target_link_libraries(squawkCORE lmdb) +target_link_libraries(squawkCORE simpleCrypt) diff --git a/core/squawk.cpp b/core/squawk.cpp index e8dd1ae..8c70cb8 100644 --- a/core/squawk.cpp +++ b/core/squawk.cpp @@ -52,14 +52,31 @@ void Core::Squawk::stop() QSettings settings; settings.beginGroup("core"); settings.beginWriteArray("accounts"); + SimpleCrypt crypto(passwordHash); for (std::deque::size_type i = 0; i < accounts.size(); ++i) { settings.setArrayIndex(i); Account* acc = accounts[i]; + + Shared::AccountPassword ap = acc->getPasswordType(); + QString password; + + switch (ap) { + case Shared::AccountPassword::plain: + password = acc->getPassword(); + break; + case Shared::AccountPassword::jammed: + password = crypto.encryptToString(acc->getPassword()); + break; + default: + break; + } + settings.setValue("name", acc->getName()); settings.setValue("server", acc->getServer()); settings.setValue("login", acc->getLogin()); - settings.setValue("password", acc->getPassword()); + settings.setValue("password", password); settings.setValue("resource", acc->getResource()); + settings.setValue("passwordType", static_cast(ap)); } settings.endArray(); settings.endGroup(); @@ -318,12 +335,37 @@ void Core::Squawk::modifyAccountRequest(const QString& name, const QMapsecond; Shared::ConnectionState st = acc->getState(); + QMap::const_iterator mItr; + bool needToReconnect = false; - if (st != Shared::ConnectionState::disconnected) { + mItr = map.find("login"); + if (mItr != map.end()) { + needToReconnect = acc->getLogin() != mItr->toString(); + } + + if (!needToReconnect) { + mItr = map.find("password"); + if (mItr != map.end()) { + needToReconnect = acc->getPassword() != mItr->toString(); + } + } + if (!needToReconnect) { + mItr = map.find("server"); + if (mItr != map.end()) { + needToReconnect = acc->getServer() != mItr->toString(); + } + } + if (!needToReconnect) { + mItr = map.find("resource"); + if (mItr != map.end()) { + needToReconnect = acc->getResource() != mItr->toString(); + } + } + + if (needToReconnect && st != Shared::ConnectionState::disconnected) { acc->reconnect(); } - QMap::const_iterator mItr; mItr = map.find("login"); if (mItr != map.end()) { acc->setLogin(mItr->toString()); @@ -344,6 +386,11 @@ void Core::Squawk::modifyAccountRequest(const QString& name, const QMapsetServer(mItr->toString()); } + mItr = map.find("passwordType"); + if (mItr != map.end()) { + acc->setPasswordType(Shared::Global::fromInt(mItr->toInt())); + } + emit changeAccount(name, map); } @@ -570,6 +617,17 @@ void Core::Squawk::uploadVCard(const QString& account, const Shared::VCard& card itr->second->uploadVCard(card); } +void Core::Squawk::responsePassword(const QString& account, const QString& password) +{ + AccountsMap::const_iterator itr = amap.find(account); + if (itr == amap.end()) { + qDebug() << "An attempt to set password to non existing account" << account << ", skipping"; + return; + } + itr->second->setPassword(password); + accountReady(); +} + void Core::Squawk::readSettings() { QSettings settings; @@ -614,5 +672,16 @@ void Core::Squawk::parseAccount( addAccount(login, server, password, name, resource, passwordType); accountReady(); break; + case Shared::AccountPassword::jammed: { + SimpleCrypt crypto(passwordHash); + QString decrypted = crypto.decryptToString(password); + addAccount(login, server, decrypted, name, resource, passwordType); + accountReady(); + } + break; + case Shared::AccountPassword::alwaysAsk: + addAccount(login, server, QString(), name, resource, passwordType); + emit requestPassword(name); + break; } } diff --git a/core/squawk.h b/core/squawk.h index 6d5f7d9..f96f7e8 100644 --- a/core/squawk.h +++ b/core/squawk.h @@ -32,6 +32,7 @@ #include "shared/message.h" #include "shared/global.h" #include "networkaccess.h" +#include "external/simpleCrypt/simplecrypt.h" namespace Core { @@ -73,6 +74,7 @@ signals: void uploadFileProgress(const QString& messageId, qreal value); void responseVCard(const QString& jid, const Shared::VCard& card); void changeMessage(const QString& account, const QString& jid, const QString& id, const QMap& data); + void requestPassword(const QString& account); public slots: void start(); @@ -101,6 +103,7 @@ public slots: void downloadFileRequest(const QString& messageId, const QString& url); void requestVCard(const QString& account, const QString& jid); void uploadVCard(const QString& account, const Shared::VCard& card); + void responsePassword(const QString& account, const QString& password); private: typedef std::deque Accounts; @@ -155,6 +158,8 @@ private: const QString& resource, Shared::AccountPassword passwordType ); + + static const quint64 passwordHash = 0x08d054225ac4871d; }; } diff --git a/external/simpleCrypt/CMakeLists.txt b/external/simpleCrypt/CMakeLists.txt new file mode 100644 index 0000000..bdb62c6 --- /dev/null +++ b/external/simpleCrypt/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 3.0) +project(simplecrypt) + +set(CMAKE_AUTOMOC ON) + +find_package(Qt5Core CONFIG REQUIRED) + +set(simplecrypt_SRC + simplecrypt.cpp +) + +# Tell CMake to create the helloworld executable +add_library(simpleCrypt ${simplecrypt_SRC}) + +# Use the Widgets module from Qt 5. +target_link_libraries(simpleCrypt Qt5::Core) diff --git a/external/simpleCrypt/simplecrypt.cpp b/external/simpleCrypt/simplecrypt.cpp new file mode 100644 index 0000000..9bbec90 --- /dev/null +++ b/external/simpleCrypt/simplecrypt.cpp @@ -0,0 +1,252 @@ +/* + Copyright (c) 2011, Andre Somers + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Rathenau Instituut, Andre Somers nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL ANDRE SOMERS BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR #######; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "simplecrypt.h" +#include +#include +#include +#include +#include +#include + +SimpleCrypt::SimpleCrypt(): +m_key(0), +m_compressionMode(CompressionAuto), +m_protectionMode(ProtectionChecksum), +m_lastError(ErrorNoError) +{ + qsrand(uint(QDateTime::currentMSecsSinceEpoch() & 0xFFFF)); +} + +SimpleCrypt::SimpleCrypt(quint64 key): +m_key(key), +m_compressionMode(CompressionAuto), +m_protectionMode(ProtectionChecksum), +m_lastError(ErrorNoError) +{ + qsrand(uint(QDateTime::currentMSecsSinceEpoch() & 0xFFFF)); + splitKey(); +} + +void SimpleCrypt::setKey(quint64 key) +{ + m_key = key; + splitKey(); +} + +void SimpleCrypt::splitKey() +{ + m_keyParts.clear(); + m_keyParts.resize(8); + for (int i=0;i<8;i++) { + quint64 part = m_key; + for (int j=i; j>0; j--) + part = part >> 8; + part = part & 0xff; + m_keyParts[i] = static_cast(part); + } +} + +QByteArray SimpleCrypt::encryptToByteArray(const QString& plaintext) +{ + QByteArray plaintextArray = plaintext.toUtf8(); + return encryptToByteArray(plaintextArray); +} + +QByteArray SimpleCrypt::encryptToByteArray(QByteArray plaintext) +{ + if (m_keyParts.isEmpty()) { + qWarning() << "No key set."; + m_lastError = ErrorNoKeySet; + return QByteArray(); + } + + + QByteArray ba = plaintext; + + CryptoFlags flags = CryptoFlagNone; + if (m_compressionMode == CompressionAlways) { + ba = qCompress(ba, 9); //maximum compression + flags |= CryptoFlagCompression; + } else if (m_compressionMode == CompressionAuto) { + QByteArray compressed = qCompress(ba, 9); + if (compressed.count() < ba.count()) { + ba = compressed; + flags |= CryptoFlagCompression; + } + } + + QByteArray integrityProtection; + if (m_protectionMode == ProtectionChecksum) { + flags |= CryptoFlagChecksum; + QDataStream s(&integrityProtection, QIODevice::WriteOnly); + s << qChecksum(ba.constData(), ba.length()); + } else if (m_protectionMode == ProtectionHash) { + flags |= CryptoFlagHash; + QCryptographicHash hash(QCryptographicHash::Sha1); + hash.addData(ba); + + integrityProtection += hash.result(); + } + + //prepend a random char to the string + char randomChar = char(qrand() & 0xFF); + ba = randomChar + integrityProtection + ba; + + int pos(0); + char lastChar(0); + + int cnt = ba.count(); + + while (pos < cnt) { + ba[pos] = ba.at(pos) ^ m_keyParts.at(pos % 8) ^ lastChar; + lastChar = ba.at(pos); + ++pos; + } + + QByteArray resultArray; + resultArray.append(char(0x03)); //version for future updates to algorithm + resultArray.append(char(flags)); //encryption flags + resultArray.append(ba); + + m_lastError = ErrorNoError; + return resultArray; +} + +QString SimpleCrypt::encryptToString(const QString& plaintext) +{ + QByteArray plaintextArray = plaintext.toUtf8(); + QByteArray cypher = encryptToByteArray(plaintextArray); + QString cypherString = QString::fromLatin1(cypher.toBase64()); + return cypherString; +} + +QString SimpleCrypt::encryptToString(QByteArray plaintext) +{ + QByteArray cypher = encryptToByteArray(plaintext); + QString cypherString = QString::fromLatin1(cypher.toBase64()); + return cypherString; +} + +QString SimpleCrypt::decryptToString(const QString &cyphertext) +{ + QByteArray cyphertextArray = QByteArray::fromBase64(cyphertext.toLatin1()); + QByteArray plaintextArray = decryptToByteArray(cyphertextArray); + QString plaintext = QString::fromUtf8(plaintextArray, plaintextArray.size()); + + return plaintext; +} + +QString SimpleCrypt::decryptToString(QByteArray cypher) +{ + QByteArray ba = decryptToByteArray(cypher); + QString plaintext = QString::fromUtf8(ba, ba.size()); + + return plaintext; +} + +QByteArray SimpleCrypt::decryptToByteArray(const QString& cyphertext) +{ + QByteArray cyphertextArray = QByteArray::fromBase64(cyphertext.toLatin1()); + QByteArray ba = decryptToByteArray(cyphertextArray); + + return ba; +} + +QByteArray SimpleCrypt::decryptToByteArray(QByteArray cypher) +{ + if (m_keyParts.isEmpty()) { + qWarning() << "No key set."; + m_lastError = ErrorNoKeySet; + return QByteArray(); + } + + QByteArray ba = cypher; + + if( cypher.count() < 3 ) + return QByteArray(); + + char version = ba.at(0); + + if (version !=3) { //we only work with version 3 + m_lastError = ErrorUnknownVersion; + qWarning() << "Invalid version or not a cyphertext."; + return QByteArray(); + } + + CryptoFlags flags = CryptoFlags(ba.at(1)); + + ba = ba.mid(2); + int pos(0); + int cnt(ba.count()); + char lastChar = 0; + + while (pos < cnt) { + char currentChar = ba[pos]; + ba[pos] = ba.at(pos) ^ lastChar ^ m_keyParts.at(pos % 8); + lastChar = currentChar; + ++pos; + } + + ba = ba.mid(1); //chop off the random number at the start + + bool integrityOk(true); + if (flags.testFlag(CryptoFlagChecksum)) { + if (ba.length() < 2) { + m_lastError = ErrorIntegrityFailed; + return QByteArray(); + } + quint16 storedChecksum; + { + QDataStream s(&ba, QIODevice::ReadOnly); + s >> storedChecksum; + } + ba = ba.mid(2); + quint16 checksum = qChecksum(ba.constData(), ba.length()); + integrityOk = (checksum == storedChecksum); + } else if (flags.testFlag(CryptoFlagHash)) { + if (ba.length() < 20) { + m_lastError = ErrorIntegrityFailed; + return QByteArray(); + } + QByteArray storedHash = ba.left(20); + ba = ba.mid(20); + QCryptographicHash hash(QCryptographicHash::Sha1); + hash.addData(ba); + integrityOk = (hash.result() == storedHash); + } + + if (!integrityOk) { + m_lastError = ErrorIntegrityFailed; + return QByteArray(); + } + + if (flags.testFlag(CryptoFlagCompression)) + ba = qUncompress(ba); + + m_lastError = ErrorNoError; + return ba; +} diff --git a/external/simpleCrypt/simplecrypt.h b/external/simpleCrypt/simplecrypt.h new file mode 100644 index 0000000..2a5906a --- /dev/null +++ b/external/simpleCrypt/simplecrypt.h @@ -0,0 +1,225 @@ +/* + Copyright (c) 2011, Andre Somers + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Rathenau Instituut, Andre Somers nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL ANDRE SOMERS BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR #######; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef SIMPLECRYPT_H +#define SIMPLECRYPT_H +#include +#include +#include + +/** + @ short Simple encrypt*ion and decryption of strings and byte arrays + + This class provides a simple implementation of encryption and decryption + of strings and byte arrays. + + @warning The encryption provided by this class is NOT strong encryption. It may + help to shield things from curious eyes, but it will NOT stand up to someone + determined to break the encryption. Don't say you were not warned. + + The class uses a 64 bit key. Simply create an instance of the class, set the key, + and use the encryptToString() method to calculate an encrypted version of the input string. + To decrypt that string again, use an instance of SimpleCrypt initialized with + the same key, and call the decryptToString() method with the encrypted string. If the key + matches, the decrypted version of the string will be returned again. + + If you do not provide a key, or if something else is wrong, the encryption and + decryption function will return an empty string or will return a string containing nonsense. + lastError() will return a value indicating if the method was succesful, and if not, why not. + + SimpleCrypt is prepared for the case that the encryption and decryption + algorithm is changed in a later version, by prepending a version identifier to the cypertext. + */ +class SimpleCrypt +{ +public: + /** + CompressionMode describes if compression will be applied to the data to be + encrypted. + */ + enum CompressionMode { + CompressionAuto, /*!< Only apply compression if that results in a shorter plaintext. */ + CompressionAlways, /*!< Always apply compression. Note that for short inputs, a compression may result in longer data */ + CompressionNever /*!< Never apply compression. */ + }; + /** + IntegrityProtectionMode describes measures taken to make it possible to detect problems with the data + or wrong decryption keys. + + Measures involve adding a checksum or a cryptograhpic hash to the data to be encrypted. This + increases the length of the resulting cypertext, but makes it possible to check if the plaintext + appears to be valid after decryption. + */ + enum IntegrityProtectionMode { + ProtectionNone, /*!< The integerity of the encrypted data is not protected. It is not really possible to detect a wrong key, for instance. */ + ProtectionChecksum,/*!< A simple checksum is used to verify that the data is in order. If not, an empty string is returned. */ + ProtectionHash /*!< A cryptographic hash is used to verify the integrity of the data. This method produces a much stronger, but longer check */ + }; + /** + Error describes t*he type of error that occured. + */ + enum Error { + ErrorNoError, /*!< No error occurred. */ + ErrorNoKeySet, /*!< No key was set. You can not encrypt or decrypt without a valid key. */ + ErrorUnknownVersion, /*!< The version of this data is unknown, or the data is otherwise not valid. */ + ErrorIntegrityFailed, /*!< The integrity check of the data failed. Perhaps the wrong key was used. */ + }; + + /** + Constructor. * + + Constructs a SimpleCrypt instance without a valid key set on it. + */ + SimpleCrypt(); + /** + Constructor. * + + Constructs a SimpleCrypt instance and initializes it with the given @arg key. + */ + explicit SimpleCrypt(quint64 key); + + /** + ( Re-) initializes* the key with the given @arg key. + */ + void setKey(quint64 key); + /** + Returns true if SimpleCrypt has been initialized with a key. + */ + bool hasKey() const {return !m_keyParts.isEmpty();} + + /** + Sets the compress*ion mode to use when encrypting data. The default mode is Auto. + + Note that decryption is not influenced by this mode, as the decryption recognizes + what mode was used when encrypting. + */ + void setCompressionMode(CompressionMode mode) {m_compressionMode = mode;} + /** + Returns the CompressionMode that is currently in use. + */ + CompressionMode compressionMode() const {return m_compressionMode;} + + /** + Sets the integrity mode to use when encrypting data. The default mode is Checksum. + + Note that decryption is not influenced by this mode, as the decryption recognizes + what mode was used when encrypting. + */ + void setIntegrityProtectionMode(IntegrityProtectionMode mode) {m_protectionMode = mode;} + /** + Returns the IntegrityProtectionMode that is currently in use. + */ + IntegrityProtectionMode integrityProtectionMode() const {return m_protectionMode;} + + /** + Returns the last *error that occurred. + */ + Error lastError() const {return m_lastError;} + + /** + Encrypts the @arg* plaintext string with the key the class was initialized with, and returns + a cyphertext the result. The result is a base64 encoded version of the binary array that is the + actual result of the string, so it can be stored easily in a text format. + */ + QString encryptToString(const QString& plaintext) ; + /** + Encrypts the @arg* plaintext QByteArray with the key the class was initialized with, and returns + a cyphertext the result. The result is a base64 encoded version of the binary array that is the + actual result of the encryption, so it can be stored easily in a text format. + */ + QString encryptToString(QByteArray plaintext) ; + /** + Encrypts the @arg* plaintext string with the key the class was initialized with, and returns + a binary cyphertext in a QByteArray the result. + + This method returns a byte array, that is useable for storing a binary format. If you need + a string you can store in a text file, use encryptToString() instead. + */ + QByteArray encryptToByteArray(const QString& plaintext) ; + /** + Encrypts the @arg* plaintext QByteArray with the key the class was initialized with, and returns + a binary cyphertext in a QByteArray the result. + + This method returns a byte array, that is useable for storing a binary format. If you need + a string you can store in a text file, use encryptToString() instead. + */ + QByteArray encryptToByteArray(QByteArray plaintext) ; + + /** + Decrypts a cypher*text string encrypted with this class with the set key back to the + plain text version. + + If an error occured, such as non-matching keys between encryption and decryption, + an empty string or a string containing nonsense may be returned. + */ + QString decryptToString(const QString& cyphertext) ; + /** + Decrypts a cypher*text string encrypted with this class with the set key back to the + plain text version. + + If an error occured, such as non-matching keys between encryption and decryption, + an empty string or a string containing nonsense may be returned. + */ + QByteArray decryptToByteArray(const QString& cyphertext) ; + /** + Decrypts a cypher*text binary encrypted with this class with the set key back to the + plain text version. + + If an error occured, such as non-matching keys between encryption and decryption, + an empty string or a string containing nonsense may be returned. + */ + QString decryptToString(QByteArray cypher) ; + /** + Decrypts a cypher*text binary encrypted with this class with the set key back to the + plain text version. + + If an error occured, such as non-matching keys between encryption and decryption, + an empty string or a string containing nonsense may be returned. + */ + QByteArray decryptToByteArray(QByteArray cypher) ; + + //enum to describe options that have been used for the encryption. Currently only one, but + //that only leaves room for future extensions like adding a cryptographic hash... + enum CryptoFlag{CryptoFlagNone = 0, + CryptoFlagCompression = 0x01, + CryptoFlagChecksum = 0x02, + CryptoFlagHash = 0x04 + }; + Q_DECLARE_FLAGS(CryptoFlags, CryptoFlag); +private: + + void splitKey(); + + quint64 m_key; + QVector m_keyParts; + CompressionMode m_compressionMode; + IntegrityProtectionMode m_protectionMode; + Error m_lastError; +}; +Q_DECLARE_OPERATORS_FOR_FLAGS(SimpleCrypt::CryptoFlags) + +#endif // SimpleCrypt_H diff --git a/main.cpp b/main.cpp index eeb7138..b8be72c 100644 --- a/main.cpp +++ b/main.cpp @@ -116,6 +116,7 @@ int main(int argc, char *argv[]) QObject::connect(&w, &Squawk::renameContactRequest, squawk, &Core::Squawk::renameContactRequest); QObject::connect(&w, &Squawk::requestVCard, squawk, &Core::Squawk::requestVCard); QObject::connect(&w, &Squawk::uploadVCard, squawk, &Core::Squawk::uploadVCard); + QObject::connect(&w, &Squawk::responsePassword, squawk, &Core::Squawk::responsePassword); QObject::connect(squawk, &Core::Squawk::newAccount, &w, &Squawk::newAccount); QObject::connect(squawk, &Core::Squawk::addContact, &w, &Squawk::addContact); @@ -146,6 +147,7 @@ int main(int argc, char *argv[]) QObject::connect(squawk, &Core::Squawk::uploadFileProgress, &w, &Squawk::fileProgress); QObject::connect(squawk, &Core::Squawk::uploadFileError, &w, &Squawk::fileError); QObject::connect(squawk, &Core::Squawk::responseVCard, &w, &Squawk::responseVCard); + QObject::connect(squawk, &Core::Squawk::requestPassword, &w, &Squawk::requestPassword); QObject::connect(squawk, &Core::Squawk::ready, &w, &Squawk::readSettings); coreThread->start(); diff --git a/shared/enums.h b/shared/enums.h index bf55377..cb41443 100644 --- a/shared/enums.h +++ b/shared/enums.h @@ -110,11 +110,11 @@ static const std::deque messageStateThemeIcons = {"state-offline", "sta enum class AccountPassword { plain, jammed, - kwallet, - alwaysAsk + alwaysAsk, + kwallet }; Q_ENUM_NS(AccountPassword) -static const AccountPassword AccountPasswordHighest = AccountPassword::alwaysAsk; +static const AccountPassword AccountPasswordHighest = AccountPassword::kwallet; static const AccountPassword AccountPasswordLowest = AccountPassword::plain; } diff --git a/shared/global.cpp b/shared/global.cpp index 81bee3f..c974eaf 100644 --- a/shared/global.cpp +++ b/shared/global.cpp @@ -69,8 +69,8 @@ Shared::Global::Global(): accountPassword({ tr("Plain"), tr("Jammed"), - tr("KWallet"), - tr("Always Ask") + tr("Always Ask"), + tr("KWallet") }) { if (instance != 0) { diff --git a/ui/models/account.cpp b/ui/models/account.cpp index c581439..a60315c 100644 --- a/ui/models/account.cpp +++ b/ui/models/account.cpp @@ -39,6 +39,10 @@ Models::Account::Account(const QMap& data, Models::Item* pare if (aItr != data.end()) { setAvailability(aItr.value().toUInt()); } + QMap::const_iterator pItr = data.find("passwordType"); + if (pItr != data.end()) { + setPasswordType(pItr.value().toUInt()); + } } Models::Account::~Account() diff --git a/ui/squawk.cpp b/ui/squawk.cpp index 6822dd2..3a11c1a 100644 --- a/ui/squawk.cpp +++ b/ui/squawk.cpp @@ -20,7 +20,6 @@ #include "ui_squawk.h" #include #include -#include Squawk::Squawk(QWidget *parent) : QMainWindow(parent), @@ -31,7 +30,9 @@ Squawk::Squawk(QWidget *parent) : contextMenu(new QMenu()), dbus("org.freedesktop.Notifications", "/org/freedesktop/Notifications", "org.freedesktop.Notifications", QDBusConnection::sessionBus()), requestedFiles(), - vCards() + vCards(), + requestedAccountsForPasswords(), + prompt(0) { m_ui->setupUi(this); m_ui->roster->setModel(&rosterModel); @@ -62,6 +63,18 @@ Squawk::Squawk(QWidget *parent) : if (testAttribute(Qt::WA_TranslucentBackground)) { m_ui->roster->viewport()->setAutoFillBackground(false); } + + QSettings settings; + settings.beginGroup("ui"); + settings.beginGroup("window"); + if (settings.contains("geometry")) { + restoreGeometry(settings.value("geometry").toByteArray()); + } + if (settings.contains("state")) { + restoreState(settings.value("state").toByteArray()); + } + settings.endGroup(); + settings.endGroup(); } Squawk::~Squawk() { @@ -871,14 +884,6 @@ void Squawk::readSettings() { QSettings settings; settings.beginGroup("ui"); - settings.beginGroup("window"); - if (settings.contains("geometry")) { - restoreGeometry(settings.value("geometry").toByteArray()); - } - if (settings.contains("state")) { - restoreState(settings.value("state").toByteArray()); - } - settings.endGroup(); if (settings.contains("availability")) { int avail = settings.value("availability").toInt(); @@ -958,3 +963,48 @@ void Squawk::onItemCollepsed(const QModelIndex& index) break; } } + +void Squawk::requestPassword(const QString& account) +{ + requestedAccountsForPasswords.push_back(account); + checkNextAccountForPassword(); +} + +void Squawk::checkNextAccountForPassword() +{ + if (prompt == 0 && requestedAccountsForPasswords.size() > 0) { + prompt = new QInputDialog(this); + QString accName = requestedAccountsForPasswords.front(); + connect(prompt, &QDialog::accepted, this, &Squawk::onPasswordPromptAccepted); + connect(prompt, &QDialog::rejected, this, &Squawk::onPasswordPromptRejected); + prompt->setInputMode(QInputDialog::TextInput); + prompt->setTextEchoMode(QLineEdit::Password); + prompt->setLabelText(tr("Input the password for account %1").arg(accName)); + prompt->setWindowTitle(tr("Password for account %1").arg(accName)); + prompt->setTextValue(""); + prompt->exec(); + } +} + +void Squawk::onPasswordPromptAccepted() +{ + emit responsePassword(requestedAccountsForPasswords.front(), prompt->textValue()); + onPasswordPromptDone(); +} + +void Squawk::onPasswordPromptDone() +{ + prompt->deleteLater(); + prompt = 0; + requestedAccountsForPasswords.pop_front(); + checkNextAccountForPassword(); +} + +void Squawk::onPasswordPromptRejected() +{ + //for now it's the same on reject and on accept, but one day I'm gonna make + //"Asking for the password again on the authentication failure" feature + //and here I'll be able to break the circle of password requests + emit responsePassword(requestedAccountsForPasswords.front(), prompt->textValue()); + onPasswordPromptDone(); +} diff --git a/ui/squawk.h b/ui/squawk.h index 64459ab..d5bde9c 100644 --- a/ui/squawk.h +++ b/ui/squawk.h @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -79,6 +80,7 @@ signals: void downloadFileRequest(const QString& messageId, const QString& url); void requestVCard(const QString& account, const QString& jid); void uploadVCard(const QString& account, const Shared::VCard& card); + void responsePassword(const QString& account, const QString& password); public slots: void readSettings(); @@ -107,6 +109,7 @@ public slots: void fileProgress(const QString& messageId, qreal value); void responseVCard(const QString& jid, const Shared::VCard& card); void changeMessage(const QString& account, const QString& jid, const QString& id, const QMap& data); + void requestPassword(const QString& account); private: typedef std::map Conversations; @@ -119,6 +122,8 @@ private: QDBusInterface dbus; std::map> requestedFiles; std::map vCards; + std::deque requestedAccountsForPasswords; + QInputDialog* prompt; protected: void closeEvent(QCloseEvent * event) override; @@ -146,7 +151,12 @@ private slots: void onConversationRequestLocalFile(const QString& messageId, const QString& url); void onConversationDownloadFile(const QString& messageId, const QString& url); void onItemCollepsed(const QModelIndex& index); + void onPasswordPromptAccepted(); + void onPasswordPromptRejected(); +private: + void checkNextAccountForPassword(); + void onPasswordPromptDone(); }; #endif // SQUAWK_H