Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/keepassxreboot/keepassxc.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt2
-rw-r--r--src/CMakeLists.txt1
-rw-r--r--src/crypto/Crypto.cpp52
-rw-r--r--src/crypto/Crypto.h2
-rw-r--r--src/crypto/CryptoHash.cpp42
-rw-r--r--src/crypto/CryptoHash.h6
-rw-r--r--src/crypto/SymmetricCipher.cpp47
-rw-r--r--src/crypto/SymmetricCipher.h15
-rw-r--r--src/crypto/SymmetricCipherGcrypt.cpp3
-rw-r--r--src/format/KeePass2.cpp16
-rw-r--r--src/format/KeePass2.h6
-rw-r--r--src/format/KeePass2RandomStream.cpp30
-rw-r--r--src/format/KeePass2RandomStream.h6
-rw-r--r--src/format/KeePass2Reader.cpp12
-rw-r--r--src/format/KeePass2Reader.h3
-rw-r--r--src/format/KeePass2Repair.cpp2
-rw-r--r--src/format/KeePass2Writer.cpp2
-rw-r--r--src/streams/HmacBlockStream.cpp267
-rw-r--r--src/streams/HmacBlockStream.h61
-rw-r--r--tests/TestCryptoHash.cpp13
-rw-r--r--tests/TestKeePass2RandomStream.cpp2
-rw-r--r--tests/TestSymmetricCipher.cpp50
-rw-r--r--tests/TestSymmetricCipher.h1
23 files changed, 616 insertions, 25 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1d5c198ee..34ac87f04 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -262,7 +262,7 @@ set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS_NONE QT_NO_DEBUG)
find_package(LibGPGError REQUIRED)
-find_package(Gcrypt 1.6.0 REQUIRED)
+find_package(Gcrypt 1.7.0 REQUIRED)
find_package(ZLIB REQUIRED)
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index c04901a11..2e10cdcf4 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -148,6 +148,7 @@ set(keepassx_SOURCES
keys/PasswordKey.cpp
keys/YkChallengeResponseKey.cpp
streams/HashedBlockStream.cpp
+ streams/HmacBlockStream.cpp
streams/LayeredStream.cpp
streams/qtiocompressor.cpp
streams/StoreDataStream.cpp
diff --git a/src/crypto/Crypto.cpp b/src/crypto/Crypto.cpp
index d00be720b..7ba78a6b3 100644
--- a/src/crypto/Crypto.cpp
+++ b/src/crypto/Crypto.cpp
@@ -95,18 +95,28 @@ bool Crypto::checkAlgorithms()
qWarning("Crypto::checkAlgorithms: %s", qPrintable(m_errorStr));
return false;
}
+ if (gcry_cipher_algo_info(GCRY_CIPHER_CHACHA20, GCRYCTL_TEST_ALGO, nullptr, nullptr) != 0) {
+ m_errorStr = "GCRY_CIPHER_CHACHA20 not found.";
+ qWarning("Crypto::checkAlgorithms: %s", qPrintable(m_errorStr));
+ return false;
+ }
if (gcry_md_test_algo(GCRY_MD_SHA256) != 0) {
m_errorStr = "GCRY_MD_SHA256 not found.";
qWarning("Crypto::checkAlgorithms: %s", qPrintable(m_errorStr));
return false;
}
+ if (gcry_md_test_algo(GCRY_MD_SHA512) != 0) {
+ m_errorStr = "GCRY_MD_SHA512 not found.";
+ qWarning("Crypto::checkAlgorithms: %s", qPrintable(m_errorStr));
+ return false;
+ }
return true;
}
bool Crypto::selfTest()
{
- return testSha256() && testAes256Cbc() && testAes256Ecb() && testTwofish() && testSalsa20();
+ return testSha256() && testSha512() && testAes256Cbc() && testAes256Ecb() && testTwofish() && testSalsa20() && testChaCha20();
}
void Crypto::raiseError(const QString& str)
@@ -128,6 +138,19 @@ bool Crypto::testSha256()
return true;
}
+bool Crypto::testSha512()
+{
+ QByteArray sha512Test = CryptoHash::hash("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
+ CryptoHash::Sha512);
+
+ if (sha512Test != QByteArray::fromHex("204a8fc6dda82f0a0ced7beb8e08a41657c16ef468b228a8279be331a703c33596fd15c13b1b07f9aa1d3bea57789ca031ad85c7a71dd70354ec631238ca3445")) {
+ raiseError("SHA-512 mismatch.");
+ return false;
+ }
+
+ return true;
+}
+
bool Crypto::testAes256Cbc()
{
QByteArray key = QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4");
@@ -285,3 +308,30 @@ bool Crypto::testSalsa20()
return true;
}
+
+bool Crypto::testChaCha20() {
+ QByteArray chacha20Key = QByteArray::fromHex("0000000000000000000000000000000000000000000000000000000000000000");
+ QByteArray chacha20iv = QByteArray::fromHex("0000000000000000");
+ QByteArray chacha20Plain = QByteArray::fromHex("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000");
+ QByteArray chacha20Cipher = QByteArray::fromHex("76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669b2ee6586");
+ bool ok;
+
+ SymmetricCipher chacha20Stream(SymmetricCipher::ChaCha20, SymmetricCipher::Stream,
+ SymmetricCipher::Encrypt);
+ if (!chacha20Stream.init(chacha20Key, chacha20iv)) {
+ raiseError(chacha20Stream.errorString());
+ return false;
+ }
+
+ QByteArray chacha20Processed = chacha20Stream.process(chacha20Plain, &ok);
+ if (!ok) {
+ raiseError(chacha20Stream.errorString());
+ return false;
+ }
+ if (chacha20Processed != chacha20Cipher) {
+ raiseError("ChaCha20 stream cipher mismatch.");
+ return false;
+ }
+
+ return true;
+} \ No newline at end of file
diff --git a/src/crypto/Crypto.h b/src/crypto/Crypto.h
index 0ce2903c6..379068eb4 100644
--- a/src/crypto/Crypto.h
+++ b/src/crypto/Crypto.h
@@ -35,10 +35,12 @@ private:
static bool selfTest();
static void raiseError(const QString& str);
static bool testSha256();
+ static bool testSha512();
static bool testAes256Cbc();
static bool testAes256Ecb();
static bool testTwofish();
static bool testSalsa20();
+ static bool testChaCha20();
static bool m_initalized;
static QString m_errorStr;
diff --git a/src/crypto/CryptoHash.cpp b/src/crypto/CryptoHash.cpp
index d116451fc..a3ffb648e 100644
--- a/src/crypto/CryptoHash.cpp
+++ b/src/crypto/CryptoHash.cpp
@@ -29,27 +29,42 @@ public:
};
CryptoHash::CryptoHash(CryptoHash::Algorithm algo)
+ : CryptoHash::CryptoHash(algo, false) {}
+
+CryptoHash::CryptoHash(CryptoHash::Algorithm algo, bool hmac)
: d_ptr(new CryptoHashPrivate())
{
Q_D(CryptoHash);
Q_ASSERT(Crypto::initalized());
- int algoGcrypt;
+ int algoGcrypt = -1;
+ unsigned int flagsGcrypt = 0;
switch (algo) {
case CryptoHash::Sha256:
algoGcrypt = GCRY_MD_SHA256;
break;
+ case CryptoHash::Sha512:
+ algoGcrypt = GCRY_MD_SHA512;
+ break;
+
default:
Q_ASSERT(false);
break;
}
- gcry_error_t error = gcry_md_open(&d->ctx, algoGcrypt, 0);
+ if (hmac) {
+ flagsGcrypt |= GCRY_MD_FLAG_HMAC;
+ }
+
+ gcry_error_t error = gcry_md_open(&d->ctx, algoGcrypt, flagsGcrypt);
+ if (error) {
+ qWarning("Gcrypt error (ctor): %s", gcry_strerror(error));
+ qWarning("Gcrypt error (ctor): %s", gcry_strsource(error));
+ }
Q_ASSERT(error == 0); // TODO: error handling
- Q_UNUSED(error);
d->hashLen = gcry_md_get_algo_dlen(algoGcrypt);
}
@@ -74,6 +89,18 @@ void CryptoHash::addData(const QByteArray& data)
gcry_md_write(d->ctx, data.constData(), data.size());
}
+void CryptoHash::setKey(const QByteArray& data)
+{
+ Q_D(CryptoHash);
+
+ gcry_error_t error = gcry_md_setkey(d->ctx, data.constData(), data.size());
+ if (error) {
+ qWarning("Gcrypt error (setKey): %s", gcry_strerror(error));
+ qWarning("Gcrypt error (setKey): %s", gcry_strsource(error));
+ }
+ Q_ASSERT(error == 0);
+}
+
void CryptoHash::reset()
{
Q_D(CryptoHash);
@@ -96,3 +123,12 @@ QByteArray CryptoHash::hash(const QByteArray& data, CryptoHash::Algorithm algo)
cryptoHash.addData(data);
return cryptoHash.result();
}
+
+QByteArray CryptoHash::hmac(const QByteArray& data, const QByteArray& key, CryptoHash::Algorithm algo)
+{
+ // replace with gcry_md_hash_buffer()?
+ CryptoHash cryptoHash(algo, true);
+ cryptoHash.setKey(key);
+ cryptoHash.addData(data);
+ return cryptoHash.result();
+}
diff --git a/src/crypto/CryptoHash.h b/src/crypto/CryptoHash.h
index 80df056f1..cf027e0f3 100644
--- a/src/crypto/CryptoHash.h
+++ b/src/crypto/CryptoHash.h
@@ -27,16 +27,20 @@ class CryptoHash
public:
enum Algorithm
{
- Sha256
+ Sha256,
+ Sha512
};
explicit CryptoHash(CryptoHash::Algorithm algo);
+ explicit CryptoHash(CryptoHash::Algorithm algo, bool hmac);
~CryptoHash();
void addData(const QByteArray& data);
void reset();
QByteArray result() const;
+ void setKey(const QByteArray& data);
static QByteArray hash(const QByteArray& data, CryptoHash::Algorithm algo);
+ static QByteArray hmac(const QByteArray& data, const QByteArray& key, Algorithm algo);
private:
CryptoHashPrivate* const d_ptr;
diff --git a/src/crypto/SymmetricCipher.cpp b/src/crypto/SymmetricCipher.cpp
index 016103b27..9bd605b43 100644
--- a/src/crypto/SymmetricCipher.cpp
+++ b/src/crypto/SymmetricCipher.cpp
@@ -24,6 +24,7 @@ SymmetricCipher::SymmetricCipher(SymmetricCipher::Algorithm algo, SymmetricCiphe
SymmetricCipher::Direction direction)
: m_backend(createBackend(algo, mode, direction))
, m_initialized(false)
+ , m_algo(algo)
{
}
@@ -61,6 +62,7 @@ SymmetricCipherBackend* SymmetricCipher::createBackend(SymmetricCipher::Algorith
case SymmetricCipher::Aes256:
case SymmetricCipher::Twofish:
case SymmetricCipher::Salsa20:
+ case SymmetricCipher::ChaCha20:
return new SymmetricCipherGcrypt(algo, mode, direction);
default:
@@ -93,10 +95,14 @@ SymmetricCipher::Algorithm SymmetricCipher::cipherToAlgorithm(Uuid cipher)
{
if (cipher == KeePass2::CIPHER_AES) {
return SymmetricCipher::Aes256;
- }
- else {
+ } else if (cipher == KeePass2::CIPHER_CHACHA20) {
+ return SymmetricCipher::ChaCha20;
+ } else if (cipher == KeePass2::CIPHER_TWOFISH) {
return SymmetricCipher::Twofish;
}
+
+ qWarning("SymmetricCipher::cipherToAlgorithm: invalid Uuid %s", cipher.toByteArray().toHex().data());
+ return InvalidAlgorithm;
}
Uuid SymmetricCipher::algorithmToCipher(SymmetricCipher::Algorithm algo)
@@ -104,7 +110,42 @@ Uuid SymmetricCipher::algorithmToCipher(SymmetricCipher::Algorithm algo)
switch (algo) {
case SymmetricCipher::Aes256:
return KeePass2::CIPHER_AES;
- default:
+ case SymmetricCipher::ChaCha20:
+ return KeePass2::CIPHER_CHACHA20;
+ case SymmetricCipher::Twofish:
return KeePass2::CIPHER_TWOFISH;
+ default:
+ qWarning("SymmetricCipher::algorithmToCipher: invalid algorithm %d", algo);
+ return Uuid();
}
}
+
+int SymmetricCipher::algorithmIvSize(SymmetricCipher::Algorithm algo) {
+ switch (algo) {
+ case SymmetricCipher::ChaCha20:
+ return 12;
+ case SymmetricCipher::Aes256:
+ case SymmetricCipher::Twofish:
+ return 16;
+ default:
+ qWarning("SymmetricCipher::algorithmIvSize: invalid algorithm %d", algo);
+ return -1;
+ }
+}
+
+SymmetricCipher::Mode SymmetricCipher::algorithmMode(SymmetricCipher::Algorithm algo) {
+ switch (algo) {
+ case SymmetricCipher::ChaCha20:
+ return SymmetricCipher::Stream;
+ case SymmetricCipher::Aes256:
+ case SymmetricCipher::Twofish:
+ return SymmetricCipher::Cbc;
+ default:
+ qWarning("SymmetricCipher::algorithmMode: invalid algorithm %d", algo);
+ return SymmetricCipher::InvalidMode;
+ }
+}
+
+SymmetricCipher::Algorithm SymmetricCipher::algorithm() const {
+ return m_algo;
+}
diff --git a/src/crypto/SymmetricCipher.h b/src/crypto/SymmetricCipher.h
index 31d10466b..981ef320e 100644
--- a/src/crypto/SymmetricCipher.h
+++ b/src/crypto/SymmetricCipher.h
@@ -33,7 +33,9 @@ public:
{
Aes256,
Twofish,
- Salsa20
+ Salsa20,
+ ChaCha20,
+ InvalidAlgorithm = -1
};
enum Mode
@@ -41,7 +43,8 @@ public:
Cbc,
Ctr,
Ecb,
- Stream
+ Stream,
+ InvalidMode = -1
};
enum Direction
@@ -74,9 +77,12 @@ public:
int keySize() const;
int blockSize() const;
QString errorString() const;
+ Algorithm algorithm() const;
- static SymmetricCipher::Algorithm cipherToAlgorithm(Uuid cipher);
- static Uuid algorithmToCipher(SymmetricCipher::Algorithm algo);
+ static Algorithm cipherToAlgorithm(Uuid cipher);
+ static Uuid algorithmToCipher(Algorithm algo);
+ static int algorithmIvSize(Algorithm algo);
+ static Mode algorithmMode(Algorithm algo);
private:
static SymmetricCipherBackend* createBackend(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode,
@@ -84,6 +90,7 @@ private:
const QScopedPointer<SymmetricCipherBackend> m_backend;
bool m_initialized;
+ Algorithm m_algo;
Q_DISABLE_COPY(SymmetricCipher)
};
diff --git a/src/crypto/SymmetricCipherGcrypt.cpp b/src/crypto/SymmetricCipherGcrypt.cpp
index 0b291e693..bbb80bf60 100644
--- a/src/crypto/SymmetricCipherGcrypt.cpp
+++ b/src/crypto/SymmetricCipherGcrypt.cpp
@@ -46,6 +46,9 @@ int SymmetricCipherGcrypt::gcryptAlgo(SymmetricCipher::Algorithm algo)
case SymmetricCipher::Salsa20:
return GCRY_CIPHER_SALSA20;
+ case SymmetricCipher::ChaCha20:
+ return GCRY_CIPHER_CHACHA20;
+
default:
Q_ASSERT(false);
return -1;
diff --git a/src/format/KeePass2.cpp b/src/format/KeePass2.cpp
index 01c15a871..8cf3df555 100644
--- a/src/format/KeePass2.cpp
+++ b/src/format/KeePass2.cpp
@@ -22,6 +22,7 @@
const Uuid KeePass2::CIPHER_AES = Uuid(QByteArray::fromHex("31c1f2e6bf714350be5805216afc5aff"));
const Uuid KeePass2::CIPHER_TWOFISH = Uuid(QByteArray::fromHex("ad68f29f576f4bb9a36ad47af965346c"));
+const Uuid KeePass2::CIPHER_CHACHA20 = Uuid(QByteArray::fromHex("D6038A2B8B6F4CB5A524339A31DBB59A"));
const Uuid KeePass2::KDF_AES = Uuid(QByteArray::fromHex("C9D9F39A628A4460BF740D08C18A4FEA"));
@@ -30,6 +31,7 @@ const QByteArray KeePass2::INNER_STREAM_SALSA20_IV("\xE8\x30\x09\x4B\x97\x20\x5D
const QList<KeePass2::UuidNamePair> KeePass2::CIPHERS {
KeePass2::UuidNamePair(KeePass2::CIPHER_AES, "AES: 256-bit"),
KeePass2::UuidNamePair(KeePass2::CIPHER_TWOFISH, "Twofish: 256-bit"),
+ KeePass2::UuidNamePair(KeePass2::CIPHER_CHACHA20, "ChaCha20: 256-bit")
};
const QList<KeePass2::UuidNamePair> KeePass2::KDFS {
KeePass2::UuidNamePair(KeePass2::KDF_AES, "AES-KDF"),
@@ -53,6 +55,20 @@ Uuid KeePass2::kdfToUuid(const Kdf& kdf)
}
}
+KeePass2::ProtectedStreamAlgo KeePass2::idToProtectedStreamAlgo(quint32 id)
+{
+ switch (id) {
+ case static_cast<quint32>(KeePass2::ArcFourVariant):
+ return KeePass2::ArcFourVariant;
+ case static_cast<quint32>(KeePass2::Salsa20):
+ return KeePass2::Salsa20;
+ case static_cast<quint32>(KeePass2::ChaCha20):
+ return KeePass2::ChaCha20;
+ default:
+ return KeePass2::InvalidProtectedStreamAlgo;
+ }
+}
+
KeePass2::UuidNamePair::UuidNamePair(const Uuid& uuid, const QString& name)
: m_uuid(uuid)
, m_name(name)
diff --git a/src/format/KeePass2.h b/src/format/KeePass2.h
index b3c7ee559..6356e15da 100644
--- a/src/format/KeePass2.h
+++ b/src/format/KeePass2.h
@@ -37,6 +37,7 @@ namespace KeePass2
extern const Uuid CIPHER_AES;
extern const Uuid CIPHER_TWOFISH;
+ extern const Uuid CIPHER_CHACHA20;
extern const Uuid KDF_AES;
@@ -75,11 +76,14 @@ namespace KeePass2
enum ProtectedStreamAlgo
{
ArcFourVariant = 1,
- Salsa20 = 2
+ Salsa20 = 2,
+ ChaCha20 = 3,
+ InvalidProtectedStreamAlgo = -1
};
Kdf* uuidToKdf(const Uuid& uuid);
Uuid kdfToUuid(const Kdf& kdf);
+ ProtectedStreamAlgo idToProtectedStreamAlgo(quint32 id);
}
#endif // KEEPASSX_KEEPASS2_H
diff --git a/src/format/KeePass2RandomStream.cpp b/src/format/KeePass2RandomStream.cpp
index 1944e5d5b..5f80fc151 100644
--- a/src/format/KeePass2RandomStream.cpp
+++ b/src/format/KeePass2RandomStream.cpp
@@ -20,16 +20,27 @@
#include "crypto/CryptoHash.h"
#include "format/KeePass2.h"
-KeePass2RandomStream::KeePass2RandomStream()
- : m_cipher(SymmetricCipher::Salsa20, SymmetricCipher::Stream, SymmetricCipher::Encrypt)
+KeePass2RandomStream::KeePass2RandomStream(KeePass2::ProtectedStreamAlgo algo)
+ : m_cipher(mapAlgo(algo), SymmetricCipher::Stream, SymmetricCipher::Encrypt)
, m_offset(0)
{
}
bool KeePass2RandomStream::init(const QByteArray& key)
{
- return m_cipher.init(CryptoHash::hash(key, CryptoHash::Sha256),
- KeePass2::INNER_STREAM_SALSA20_IV);
+ switch (m_cipher.algorithm()) {
+ case SymmetricCipher::Salsa20:
+ return m_cipher.init(CryptoHash::hash(key, CryptoHash::Sha256),
+ KeePass2::INNER_STREAM_SALSA20_IV);
+ case SymmetricCipher::ChaCha20: {
+ QByteArray keyIv = CryptoHash::hash(key, CryptoHash::Sha512);
+ return m_cipher.init(keyIv.left(32), keyIv.mid(32, 12));
+ }
+ default:
+ qWarning("Invalid stream algorithm (%d)", m_cipher.algorithm());
+ break;
+ }
+ return false;
}
QByteArray KeePass2RandomStream::randomBytes(int size, bool* ok)
@@ -109,3 +120,14 @@ bool KeePass2RandomStream::loadBlock()
return true;
}
+
+SymmetricCipher::Algorithm KeePass2RandomStream::mapAlgo(KeePass2::ProtectedStreamAlgo algo) {
+ switch (algo) {
+ case KeePass2::ChaCha20:
+ return SymmetricCipher::ChaCha20;
+ case KeePass2::Salsa20:
+ return SymmetricCipher::Salsa20;
+ default:
+ return SymmetricCipher::InvalidAlgorithm;
+ }
+} \ No newline at end of file
diff --git a/src/format/KeePass2RandomStream.h b/src/format/KeePass2RandomStream.h
index 584d738b3..1e341bacc 100644
--- a/src/format/KeePass2RandomStream.h
+++ b/src/format/KeePass2RandomStream.h
@@ -21,11 +21,13 @@
#include <QByteArray>
#include "crypto/SymmetricCipher.h"
+#include "KeePass2.h"
class KeePass2RandomStream
{
public:
- KeePass2RandomStream();
+ KeePass2RandomStream(KeePass2::ProtectedStreamAlgo algo);
+
bool init(const QByteArray& key);
QByteArray randomBytes(int size, bool* ok);
QByteArray process(const QByteArray& data, bool* ok);
@@ -38,6 +40,8 @@ private:
SymmetricCipher m_cipher;
QByteArray m_buffer;
int m_offset;
+
+ static SymmetricCipher::Algorithm mapAlgo(KeePass2::ProtectedStreamAlgo algo);
};
#endif // KEEPASSX_KEEPASS2RANDOMSTREAM_H
diff --git a/src/format/KeePass2Reader.cpp b/src/format/KeePass2Reader.cpp
index 60ebfdc82..f42e7b9bb 100644
--- a/src/format/KeePass2Reader.cpp
+++ b/src/format/KeePass2Reader.cpp
@@ -41,6 +41,7 @@ KeePass2Reader::KeePass2Reader()
, m_headerEnd(false)
, m_saveXml(false)
, m_db(nullptr)
+ , m_irsAlgo(KeePass2::InvalidProtectedStreamAlgo)
{
}
@@ -164,7 +165,7 @@ Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& ke
xmlDevice = ioCompressor.data();
}
- KeePass2RandomStream randomStream;
+ KeePass2RandomStream randomStream(m_irsAlgo);
if (!randomStream.init(m_protectedStreamKey)) {
raiseError(randomStream.errorString());
return nullptr;
@@ -447,9 +448,14 @@ void KeePass2Reader::setInnerRandomStreamID(const QByteArray& data)
}
else {
quint32 id = Endian::bytesToUInt32(data, KeePass2::BYTEORDER);
-
- if (id != KeePass2::Salsa20) {
+ m_irsAlgo = KeePass2::idToProtectedStreamAlgo(id);
+ if (m_irsAlgo == KeePass2::ArcFourVariant || m_irsAlgo == KeePass2::InvalidProtectedStreamAlgo) {
raiseError("Unsupported random stream algorithm");
}
}
}
+
+KeePass2::ProtectedStreamAlgo KeePass2Reader::protectedStreamAlgo() const
+{
+ return m_irsAlgo;
+}
diff --git a/src/format/KeePass2Reader.h b/src/format/KeePass2Reader.h
index f82b4464a..e9a947284 100644
--- a/src/format/KeePass2Reader.h
+++ b/src/format/KeePass2Reader.h
@@ -21,6 +21,7 @@
#include <QCoreApplication>
#include "keys/CompositeKey.h"
+#include "format/KeePass2.h"
class Database;
class QIODevice;
@@ -38,6 +39,7 @@ public:
void setSaveXml(bool save);
QByteArray xmlData();
QByteArray streamKey();
+ KeePass2::ProtectedStreamAlgo protectedStreamAlgo() const;
private:
void raiseError(const QString& errorMessage);
@@ -67,6 +69,7 @@ private:
QByteArray m_encryptionIV;
QByteArray m_streamStartBytes;
QByteArray m_protectedStreamKey;
+ KeePass2::ProtectedStreamAlgo m_irsAlgo;
};
#endif // KEEPASSX_KEEPASS2READER_H
diff --git a/src/format/KeePass2Repair.cpp b/src/format/KeePass2Repair.cpp
index 8d18457d4..5f2732e2a 100644
--- a/src/format/KeePass2Repair.cpp
+++ b/src/format/KeePass2Repair.cpp
@@ -71,7 +71,7 @@ KeePass2Repair::RepairOutcome KeePass2Repair::repairDatabase(QIODevice* device,
return qMakePair(RepairFailed, nullptr);
}
- KeePass2RandomStream randomStream;
+ KeePass2RandomStream randomStream(reader.protectedStreamAlgo());
randomStream.init(reader.streamKey());
KeePass2XmlReader xmlReader;
QBuffer buffer(&xmlData);
diff --git a/src/format/KeePass2Writer.cpp b/src/format/KeePass2Writer.cpp
index 0d78dc9a8..6dc89c36d 100644
--- a/src/format/KeePass2Writer.cpp
+++ b/src/format/KeePass2Writer.cpp
@@ -131,7 +131,7 @@ void KeePass2Writer::writeDatabase(QIODevice* device, Database* db)
m_device = ioCompressor.data();
}
- KeePass2RandomStream randomStream;
+ KeePass2RandomStream randomStream(KeePass2::Salsa20);
if (!randomStream.init(protectedStreamKey)) {
raiseError(randomStream.errorString());
return;
diff --git a/src/streams/HmacBlockStream.cpp b/src/streams/HmacBlockStream.cpp
new file mode 100644
index 000000000..c01c973b1
--- /dev/null
+++ b/src/streams/HmacBlockStream.cpp
@@ -0,0 +1,267 @@
+/*
+* Copyright (C) 2017 KeePassXC Team
+*
+* 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 or (at your option)
+* version 3 of the License.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "HmacBlockStream.h"
+
+#include "core/Endian.h"
+#include "crypto/CryptoHash.h"
+
+const QSysInfo::Endian HmacBlockStream::ByteOrder = QSysInfo::LittleEndian;
+
+HmacBlockStream::HmacBlockStream(QIODevice* baseDevice, QByteArray key)
+ : LayeredStream(baseDevice)
+ , m_blockSize(1024*1024)
+ , m_key(key)
+{
+ init();
+}
+
+HmacBlockStream::HmacBlockStream(QIODevice* baseDevice, QByteArray key, qint32 blockSize)
+ : LayeredStream(baseDevice)
+ , m_blockSize(blockSize)
+ , m_key(key)
+{
+ init();
+}
+
+HmacBlockStream::~HmacBlockStream()
+{
+ close();
+}
+
+void HmacBlockStream::init()
+{
+ m_buffer.clear();
+ m_bufferPos = 0;
+ m_blockIndex = 0;
+ m_eof = false;
+ m_error = false;
+}
+
+bool HmacBlockStream::reset()
+{
+ // Write final block(s) only if device is writable and we haven't
+ // already written a final block.
+ if (isWritable() && (!m_buffer.isEmpty() || m_blockIndex != 0)) {
+ if (!m_buffer.isEmpty()) {
+ if (!writeHashedBlock()) {
+ return false;
+ }
+ }
+
+ // write empty final block
+ if (!writeHashedBlock()) {
+ return false;
+ }
+ }
+
+ init();
+
+ return true;
+}
+
+void HmacBlockStream::close()
+{
+ // Write final block(s) only if device is writable and we haven't
+ // already written a final block.
+ if (isWritable() && (!m_buffer.isEmpty() || m_blockIndex != 0)) {
+ if (!m_buffer.isEmpty()) {
+ writeHashedBlock();
+ }
+
+ // write empty final block
+ writeHashedBlock();
+ }
+
+ LayeredStream::close();
+}
+
+qint64 HmacBlockStream::readData(char* data, qint64 maxSize)
+{
+ if (m_error) {
+ return -1;
+ }
+ else if (m_eof) {
+ return 0;
+ }
+
+ qint64 bytesRemaining = maxSize;
+ qint64 offset = 0;
+
+ while (bytesRemaining > 0) {
+ if (m_bufferPos == m_buffer.size()) {
+ if (!readHashedBlock()) {
+ if (m_error) {
+ return -1;
+ }
+ else {
+ return maxSize - bytesRemaining;
+ }
+ }
+ }
+
+ int bytesToCopy = qMin(bytesRemaining, static_cast<qint64>(m_buffer.size() - m_bufferPos));
+
+ memcpy(data + offset, m_buffer.constData() + m_bufferPos, bytesToCopy);
+
+ offset += bytesToCopy;
+ m_bufferPos += bytesToCopy;
+ bytesRemaining -= bytesToCopy;
+ }
+
+ return maxSize;
+}
+
+bool HmacBlockStream::readHashedBlock()
+{
+ if (m_eof) {
+ return false;
+ }
+ QByteArray hmac = m_baseDevice->read(32);
+ if (hmac.size() != 32) {
+ m_error = true;
+ setErrorString("Invalid HMAC size.");
+ return false;
+ }
+
+ QByteArray blockSizeBytes = m_baseDevice->read(4);
+ if (blockSizeBytes.size() != 4) {
+ m_error = true;
+ setErrorString("Invalid block size size.");
+ return false;
+ }
+ qint32 blockSize = Endian::bytesToInt32(blockSizeBytes, ByteOrder);
+ if (blockSize < 0) {
+ m_error = true;
+ setErrorString("Invalid block size.");
+ return false;
+ }
+
+ m_buffer = m_baseDevice->read(blockSize);
+ if (m_buffer.size() != blockSize) {
+ m_error = true;
+ setErrorString("Block too short.");
+ return false;
+ }
+
+ CryptoHash hasher(CryptoHash::Sha256, true);
+ hasher.setKey(getCurrentHmacKey());
+ hasher.addData(Endian::uint64ToBytes(m_blockIndex, ByteOrder));
+ hasher.addData(blockSizeBytes);
+ hasher.addData(m_buffer);
+
+ if (hmac != hasher.result()) {
+ m_error = true;
+ setErrorString("Mismatch between hash and data.");
+ return false;
+ }
+
+ m_bufferPos = 0;
+ m_blockIndex++;
+
+ if (blockSize == 0) {
+ m_eof = true;
+ return false;
+ }
+
+ return true;
+}
+
+qint64 HmacBlockStream::writeData(const char* data, qint64 maxSize)
+{
+ Q_ASSERT(maxSize >= 0);
+
+ if (m_error) {
+ return 0;
+ }
+
+ qint64 bytesRemaining = maxSize;
+ qint64 offset = 0;
+
+ while (bytesRemaining > 0) {
+ int bytesToCopy = qMin(bytesRemaining, static_cast<qint64>(m_blockSize - m_buffer.size()));
+
+ m_buffer.append(data + offset, bytesToCopy);
+
+ offset += bytesToCopy;
+ bytesRemaining -= bytesToCopy;
+
+ if (m_buffer.size() == m_blockSize) {
+ if (!writeHashedBlock()) {
+ if (m_error) {
+ return -1;
+ }
+ else {
+ return maxSize - bytesRemaining;
+ }
+ }
+ }
+ }
+
+ return maxSize;
+}
+
+bool HmacBlockStream::writeHashedBlock()
+{
+ CryptoHash hasher(CryptoHash::Sha256, true);
+ hasher.setKey(getCurrentHmacKey());
+ hasher.addData(Endian::uint64ToBytes(m_blockIndex, ByteOrder));
+ hasher.addData(Endian::int32ToBytes(m_buffer.size(), ByteOrder));
+ hasher.addData(m_buffer);
+ QByteArray hash = hasher.result();
+
+ if (m_baseDevice->write(hash) != hash.size()) {
+ m_error = true;
+ setErrorString(m_baseDevice->errorString());
+ return false;
+ }
+
+ if (!Endian::writeInt32(m_buffer.size(), m_baseDevice, ByteOrder)) {
+ m_error = true;
+ setErrorString(m_baseDevice->errorString());
+ return false;
+ }
+
+ if (!m_buffer.isEmpty()) {
+ if (m_baseDevice->write(m_buffer) != m_buffer.size()) {
+ m_error = true;
+ setErrorString(m_baseDevice->errorString());
+ return false;
+ }
+
+ m_buffer.clear();
+ }
+ m_blockIndex++;
+ return true;
+}
+
+QByteArray HmacBlockStream::getCurrentHmacKey() const {
+ return getHmacKey(m_blockIndex, m_key);
+}
+
+QByteArray HmacBlockStream::getHmacKey(quint64 blockIndex, QByteArray key) {
+ Q_ASSERT(key.size() == 64);
+ QByteArray indexBytes = Endian::uint64ToBytes(blockIndex, ByteOrder);
+ CryptoHash hasher(CryptoHash::Sha512);
+ hasher.addData(indexBytes);
+ hasher.addData(key);
+ return hasher.result();
+}
+
+bool HmacBlockStream::atEnd() const {
+ return m_eof;
+}
diff --git a/src/streams/HmacBlockStream.h b/src/streams/HmacBlockStream.h
new file mode 100644
index 000000000..eecd8fe92
--- /dev/null
+++ b/src/streams/HmacBlockStream.h
@@ -0,0 +1,61 @@
+/*
+* Copyright (C) 2017 KeePassXC Team
+*
+* 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 or (at your option)
+* version 3 of the License.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef KEEPASSX_HMACBLOCKSTREAM_H
+#define KEEPASSX_HMACBLOCKSTREAM_H
+
+#include <QSysInfo>
+
+#include "streams/LayeredStream.h"
+
+class HmacBlockStream : public LayeredStream
+{
+ Q_OBJECT
+
+public:
+ explicit HmacBlockStream(QIODevice* baseDevice, QByteArray key);
+ HmacBlockStream(QIODevice* baseDevice, QByteArray key, qint32 blockSize);
+ ~HmacBlockStream();
+
+ bool reset() override;
+ void close() override;
+
+ static QByteArray getHmacKey(quint64 blockIndex, QByteArray key);
+
+ bool atEnd() const override;
+
+protected:
+ qint64 readData(char* data, qint64 maxSize) override;
+ qint64 writeData(const char* data, qint64 maxSize) override;
+
+private:
+ void init();
+ bool readHashedBlock();
+ bool writeHashedBlock();
+ QByteArray getCurrentHmacKey() const;
+
+ static const QSysInfo::Endian ByteOrder;
+ qint32 m_blockSize;
+ QByteArray m_buffer;
+ QByteArray m_key;
+ int m_bufferPos;
+ quint64 m_blockIndex;
+ bool m_eof;
+ bool m_error;
+};
+
+#endif // KEEPASSX_HMACBLOCKSTREAM_H
diff --git a/tests/TestCryptoHash.cpp b/tests/TestCryptoHash.cpp
index c166f5595..469ce8192 100644
--- a/tests/TestCryptoHash.cpp
+++ b/tests/TestCryptoHash.cpp
@@ -44,4 +44,17 @@ void TestCryptoHash::test()
cryptoHash3.addData(QString("ssX").toLatin1());
QCOMPARE(cryptoHash3.result(),
QByteArray::fromHex("0b56e5f65263e747af4a833bd7dd7ad26a64d7a4de7c68e52364893dca0766b4"));
+
+ CryptoHash cryptoHash2(CryptoHash::Sha512);
+ QCOMPARE(cryptoHash2.result(),
+ QByteArray::fromHex("cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"));
+
+ QByteArray result3 = CryptoHash::hash(source2, CryptoHash::Sha512);
+ QCOMPARE(result3, QByteArray::fromHex("0d41b612584ed39ff72944c29494573e40f4bb95283455fae2e0be1e3565aa9f48057d59e6ffd777970e282871c25a549a2763e5b724794f312c97021c42f91d"));
+
+ CryptoHash cryptoHash4(CryptoHash::Sha512);
+ cryptoHash4.addData(QString("KeePa").toLatin1());
+ cryptoHash4.addData(QString("ssX").toLatin1());
+ QCOMPARE(cryptoHash4.result(),
+ QByteArray::fromHex("0d41b612584ed39ff72944c29494573e40f4bb95283455fae2e0be1e3565aa9f48057d59e6ffd777970e282871c25a549a2763e5b724794f312c97021c42f91d"));
}
diff --git a/tests/TestKeePass2RandomStream.cpp b/tests/TestKeePass2RandomStream.cpp
index 03dfbe507..bef7af540 100644
--- a/tests/TestKeePass2RandomStream.cpp
+++ b/tests/TestKeePass2RandomStream.cpp
@@ -58,7 +58,7 @@ void TestKeePass2RandomStream::test()
}
- KeePass2RandomStream randomStream;
+ KeePass2RandomStream randomStream(KeePass2::Salsa20);
bool ok;
QVERIFY(randomStream.init(key));
QByteArray randomStreamData;
diff --git a/tests/TestSymmetricCipher.cpp b/tests/TestSymmetricCipher.cpp
index c1e947063..bfa3c3db8 100644
--- a/tests/TestSymmetricCipher.cpp
+++ b/tests/TestSymmetricCipher.cpp
@@ -342,6 +342,56 @@ void TestSymmetricCipher::testSalsa20()
QCOMPARE(cipherTextB.mid(448, 64), expectedCipherText4);
}
+void TestSymmetricCipher::testChaCha20()
+{
+ // https://tools.ietf.org/html/draft-agl-tls-chacha20poly1305-04#section-7
+ bool ok;
+
+ {
+ QByteArray key = QByteArray::fromHex("0000000000000000000000000000000000000000000000000000000000000000");
+ QByteArray iv = QByteArray::fromHex("0000000000000000");
+ SymmetricCipher cipher(SymmetricCipher::ChaCha20, SymmetricCipher::Stream, SymmetricCipher::Encrypt);
+ QVERIFY(cipher.init(key, iv));
+ QCOMPARE(cipher.process(QByteArray(64, 0), &ok),
+ QByteArray::fromHex(
+ "76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669b2ee6586"));
+ QVERIFY(ok);
+ }
+
+ {
+ QByteArray key = QByteArray::fromHex("0000000000000000000000000000000000000000000000000000000000000001");
+ QByteArray iv = QByteArray::fromHex("0000000000000000");
+ SymmetricCipher cipher(SymmetricCipher::ChaCha20, SymmetricCipher::Stream, SymmetricCipher::Encrypt);
+ QVERIFY(cipher.init(key, iv));
+ QCOMPARE(cipher.process(QByteArray(64, 0), &ok),
+ QByteArray::fromHex(
+ "4540f05a9f1fb296d7736e7b208e3c96eb4fe1834688d2604f450952ed432d41bbe2a0b6ea7566d2a5d1e7e20d42af2c53d792b1c43fea817e9ad275ae546963"));
+ QVERIFY(ok);
+ }
+
+ {
+ QByteArray key = QByteArray::fromHex("0000000000000000000000000000000000000000000000000000000000000000");
+ QByteArray iv = QByteArray::fromHex("0000000000000001");
+ SymmetricCipher cipher(SymmetricCipher::ChaCha20, SymmetricCipher::Stream, SymmetricCipher::Encrypt);
+ QVERIFY(cipher.init(key, iv));
+ QCOMPARE(cipher.process(QByteArray(60, 0), &ok),
+ QByteArray::fromHex(
+ "de9cba7bf3d69ef5e786dc63973f653a0b49e015adbff7134fcb7df137821031e85a050278a7084527214f73efc7fa5b5277062eb7a0433e445f41e3"));
+ QVERIFY(ok);
+ }
+
+ {
+ QByteArray key = QByteArray::fromHex("0000000000000000000000000000000000000000000000000000000000000000");
+ QByteArray iv = QByteArray::fromHex("0100000000000000");
+ SymmetricCipher cipher(SymmetricCipher::ChaCha20, SymmetricCipher::Stream, SymmetricCipher::Encrypt);
+ QVERIFY(cipher.init(key, iv));
+ QCOMPARE(cipher.process(QByteArray(64, 0), &ok),
+ QByteArray::fromHex(
+ "ef3fdfd6c61578fbf5cf35bd3dd33b8009631634d21e42ac33960bd138e50d32111e4caf237ee53ca8ad6426194a88545ddc497a0b466e7d6bbdb0041b2f586b"));
+ QVERIFY(ok);
+ }
+}
+
void TestSymmetricCipher::testPadding()
{
QByteArray key = QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4");
diff --git a/tests/TestSymmetricCipher.h b/tests/TestSymmetricCipher.h
index cad13841a..40e3b49cf 100644
--- a/tests/TestSymmetricCipher.h
+++ b/tests/TestSymmetricCipher.h
@@ -34,6 +34,7 @@ private slots:
void testTwofish256CbcEncryption();
void testTwofish256CbcDecryption();
void testSalsa20();
+ void testChaCha20();
void testPadding();
void testStreamReset();
};