From d09a7747bc50949e9e490e3e1290b2b061a52a61 Mon Sep 17 00:00:00 2001 From: EndrII <EndrIIMail@gmail.com> Date: Sat, 5 Feb 2022 19:15:15 +0300 Subject: [PATCH 01/10] added new class AsyncKeysAuth --- Heart/AbstractSpace/asynckeysauth.cpp | 77 +++++++++++++ Heart/AbstractSpace/asynckeysauth.h | 152 ++++++++++++++++++++++++++ 2 files changed, 229 insertions(+) create mode 100644 Heart/AbstractSpace/asynckeysauth.cpp create mode 100644 Heart/AbstractSpace/asynckeysauth.h diff --git a/Heart/AbstractSpace/asynckeysauth.cpp b/Heart/AbstractSpace/asynckeysauth.cpp new file mode 100644 index 0000000..fb45a88 --- /dev/null +++ b/Heart/AbstractSpace/asynckeysauth.cpp @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2021-2021 QuasarApp. + * Distributed under the lgplv3 software license, see the accompanying + * Everyone is permitted to copy and distribute verbatim copies + * of this license document, but changing it is not allowed. +*/ + +#include "asynckeysauth.h" +#include "QCryptographicHash" + + +namespace QH { + +AsyncKeysAuth::AsyncKeysAuth() { + +} + +bool AsyncKeysAuth::auth(int allowedTimeRangeSec) const { + + if (std::abs(time(0) - _unixTime) > allowedTimeRangeSec) { + return false; + } + + QByteArray data = _publicKey; + data.insert(0, reinterpret_cast<const char*>(&_unixTime), + sizeof(_unixTime)); + + auto signData = QCryptographicHash::hash(data, + QCryptographicHash::Sha256); + + return decrypt(signData, _publicKey) == signData; +} + +bool AsyncKeysAuth::prepare() { + _unixTime = time(0); + + QByteArray data = _publicKey; + data.insert(0, reinterpret_cast<const char*>(&_unixTime), + sizeof(_unixTime)); + + auto signData = QCryptographicHash::hash(data, + QCryptographicHash::Sha256); + + setSignature(encrypt(signData, getPrivateKey())); + + return isValid(); +} + +unsigned int AsyncKeysAuth::unixTime() const { + return _unixTime; +} + +void AsyncKeysAuth::setUnixTime(unsigned int newUnixTime) { + _unixTime = newUnixTime; +} + +const QByteArray &AsyncKeysAuth::signature() const { + return _signature; +} + +const QByteArray &AsyncKeysAuth::publicKey() const { + return _publicKey; +} + +void AsyncKeysAuth::setPublicKey(const QByteArray &newPublicKey) { + _publicKey = newPublicKey; +} + +bool AsyncKeysAuth::isValid() const { + return _publicKey.size() && _signature.size() && _unixTime; +} + +void AsyncKeysAuth::setSignature(const QByteArray &newSignature) { + _signature = newSignature; +} + +} diff --git a/Heart/AbstractSpace/asynckeysauth.h b/Heart/AbstractSpace/asynckeysauth.h new file mode 100644 index 0000000..c194aab --- /dev/null +++ b/Heart/AbstractSpace/asynckeysauth.h @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2021-2021 QuasarApp. + * Distributed under the lgplv3 software license, see the accompanying + * Everyone is permitted to copy and distribute verbatim copies + * of this license document, but changing it is not allowed. +*/ + +#ifndef ASYNCKEYSAUTH_H +#define ASYNCKEYSAUTH_H + +#include <QByteArray> + + + +namespace QH { + +/** + * @brief The AsyncKeysAuth class is base class for works with authorization of a pair of asynchronous keys + * + * ### How to it works: + * + * * ----- + * + * ** Client Part ** + * + * * Client make a pair keys (public and private) = PUB and PRIV + * * Client get current unix time = U + * * Client make a data for signing = S + * S = SHA256{U + PUB} + * * Client make a signature SIG + * SIG = PRIV.encript(S) + * * Client prepare a auth request for server = R: + * R = {U,SIG,PUB} + * * Cleint send R to server + * + * * ----- + * + * ** Server Part ** + * + * * Server receive R from client. + * * Server compare U time with current unix time. + * * If the diferrence more then allowed value then server reject an auth + * * Server make S value as a client + * * Server decript SIG value and comapre it with S value + * * If decripted value is deferrend of the S then server reject an auth. + * * If decripted value equals with S value then server accept an auth. + * * After accept server create new user with ID = sha256(PUB) or + * if user alredy exits make them as a logined user. + * + * + */ +class AsyncKeysAuth +{ +public: + AsyncKeysAuth(); + + /** + * @brief auth This method make authentication and return true if the authentication finished successful else false. + * @return true if the authentication finished successful else false. + */ + bool auth(int allowedTimeRangeSec) const; + + /** + * @brief prepare This method will generate signature for autentication of client. Please inboke this method before send request to server. + * @return true if signature generated sucessuful. + */ + bool prepare(); + + /** + * @brief unixTime This method return unix time that client added for authentication. + * @return unix time that client added for authentication. + * @see AsyncKeysAuth::setUnixTime + */ + unsigned int unixTime() const; + + /** + * @brief setUnixTime This method sets new value of the unixTime propertye. + * @param newUnixTime This is new unix time value. Unix time sets in secunds from 1970 year + */ + void setUnixTime(unsigned int newUnixTime); + + /** + * @brief signature This method return signature array. + * @return signature array. + * @see AsyncKeysAuth::setSignature + */ + const QByteArray &signature() const; + + /** + * @brief publicKey This method return public key that client added for authentication. + * @note The @a publicKey will be used forcreate user id. + * @return public key that client added for authentication. + * @see AsyncKeysAuth::setPublicKey + */ + const QByteArray &publicKey() const; + + /** + * @brief setPublicKey This method sets new public key for authentication. + * @param newPublicKey Thiy is new key. + * @see AsyncKeysAuth::publicKey + */ + void setPublicKey(const QByteArray &newPublicKey); + + /** + * @brief isValid this method check this ibject to valid. + * @return return true if object contains valid signature else false. + * @note Invoke the AsyncKeysAuth::prepare method before check valid of object. All object that not be preparred is invalid. + */ + bool isValid() const; + +protected: + + /** + * @brief encrypt This method should be encript the @a inputData using the @a key. + * @param inputData This is input data that should be encripted. + * @param key This is a privete key for encription the @a inpputData. + * @return encripted data array. + * @see AsyncKeysAuth::descrupt + */ + virtual QByteArray encrypt(const QByteArray& inputData, const QByteArray& key) const = 0; + + /** + * @brief decrypt This method should be decrypt the @a inputData using the @a key. + * @param inputData This is input data that should be decripted. + * @param key This is a public key for encription the @a inpputData. + * @return decripted data array. + * @see AsyncKeysAuth::encrypt + */ + virtual QByteArray decrypt(const QByteArray& inputData, const QByteArray& key) const = 0; + + /** + * @brief getPrivateKey This method should be return private key for the public key that saved in this object. + * @return private key for the public key that saved in this object. + */ + virtual QByteArray getPrivateKey() const = 0; + + /** + * @brief setSignature Tihis is internal method for sets new signature value. + * @param newSignature new signature value. + * @note used in the + */ + void setSignature(const QByteArray &newSignature); + +private: + unsigned int _unixTime = 0; + QByteArray _signature; + QByteArray _publicKey; +}; + +} + +#endif // ASYNCKEYSAUTH_H From 1e290c6810b046cde9f942449040ad6c7956e216 Mon Sep 17 00:00:00 2001 From: EndrII <EndrIIMail@gmail.com> Date: Sat, 5 Feb 2022 19:15:56 +0300 Subject: [PATCH 02/10] fix windows --- Heart/AbstractSpace/asynckeysauth.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Heart/AbstractSpace/asynckeysauth.h b/Heart/AbstractSpace/asynckeysauth.h index c194aab..a5a4d28 100644 --- a/Heart/AbstractSpace/asynckeysauth.h +++ b/Heart/AbstractSpace/asynckeysauth.h @@ -10,7 +10,7 @@ #include <QByteArray> - +#include "heart_global.h" namespace QH { @@ -49,7 +49,7 @@ namespace QH { * * */ -class AsyncKeysAuth +class HEARTSHARED_EXPORT AsyncKeysAuth { public: AsyncKeysAuth(); From 9845f972feb526da2a7231f924272fdca34941c4 Mon Sep 17 00:00:00 2001 From: EndrII <EndrIIMail@gmail.com> Date: Mon, 7 Feb 2022 10:13:36 +0300 Subject: [PATCH 03/10] remove qtsecret --- .gitmodules | 3 --- Heart/AbstractSpace/asynckeysauth.cpp | 4 ++-- Heart/AbstractSpace/asynckeysauth.h | 25 ++++++++++++------------- Heart/CMakeLists.txt | 3 --- Heart/Qt-Secret | 1 - 5 files changed, 14 insertions(+), 22 deletions(-) delete mode 160000 Heart/Qt-Secret diff --git a/.gitmodules b/.gitmodules index 44502c6..39d96f8 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,6 +2,3 @@ path = QuasarAppLib url = https://github.com/QuasarApp/QuasarAppLib.git -[submodule "Heart/Qt-Secret"] - path = Heart/Qt-Secret - url = https://github.com/QuasarApp/Qt-Secret.git diff --git a/Heart/AbstractSpace/asynckeysauth.cpp b/Heart/AbstractSpace/asynckeysauth.cpp index fb45a88..912e3e5 100644 --- a/Heart/AbstractSpace/asynckeysauth.cpp +++ b/Heart/AbstractSpace/asynckeysauth.cpp @@ -28,7 +28,7 @@ bool AsyncKeysAuth::auth(int allowedTimeRangeSec) const { auto signData = QCryptographicHash::hash(data, QCryptographicHash::Sha256); - return decrypt(signData, _publicKey) == signData; + return checkSign(signData, signData, _publicKey); } bool AsyncKeysAuth::prepare() { @@ -41,7 +41,7 @@ bool AsyncKeysAuth::prepare() { auto signData = QCryptographicHash::hash(data, QCryptographicHash::Sha256); - setSignature(encrypt(signData, getPrivateKey())); + setSignature(signMessage(signData, getPrivateKey())); return isValid(); } diff --git a/Heart/AbstractSpace/asynckeysauth.h b/Heart/AbstractSpace/asynckeysauth.h index a5a4d28..fa2874c 100644 --- a/Heart/AbstractSpace/asynckeysauth.h +++ b/Heart/AbstractSpace/asynckeysauth.h @@ -28,7 +28,7 @@ namespace QH { * * Client make a data for signing = S * S = SHA256{U + PUB} * * Client make a signature SIG - * SIG = PRIV.encript(S) + * SIG = PRIV.signMessage(S) * * Client prepare a auth request for server = R: * R = {U,SIG,PUB} * * Cleint send R to server @@ -41,9 +41,8 @@ namespace QH { * * Server compare U time with current unix time. * * If the diferrence more then allowed value then server reject an auth * * Server make S value as a client - * * Server decript SIG value and comapre it with S value - * * If decripted value is deferrend of the S then server reject an auth. - * * If decripted value equals with S value then server accept an auth. + * * Server check SIG value and comapre it with S value + * * If message sign is valid then server accept an auth else reject. * * After accept server create new user with ID = sha256(PUB) or * if user alredy exits make them as a logined user. * @@ -111,22 +110,23 @@ public: protected: /** - * @brief encrypt This method should be encript the @a inputData using the @a key. - * @param inputData This is input data that should be encripted. - * @param key This is a privete key for encription the @a inpputData. - * @return encripted data array. + * @brief signMessage This method should be sign the @a message using the @a key. + * @param message This is input data that should be signed. + * @param key This is a privete key for encription the @a message. + * @return signature data array. * @see AsyncKeysAuth::descrupt */ - virtual QByteArray encrypt(const QByteArray& inputData, const QByteArray& key) const = 0; + virtual QByteArray &signMessage(const QByteArray& message, const QByteArray& key) const = 0; /** - * @brief decrypt This method should be decrypt the @a inputData using the @a key. - * @param inputData This is input data that should be decripted. + * @brief checkSign This method should be check signature of the @a message using the @a key. + * @param message This is input data that should be decripted. + * @param signature This is signature that will be checked for the @a message. * @param key This is a public key for encription the @a inpputData. * @return decripted data array. * @see AsyncKeysAuth::encrypt */ - virtual QByteArray decrypt(const QByteArray& inputData, const QByteArray& key) const = 0; + virtual bool checkSign(const QByteArray& message, const QByteArray& signature, const QByteArray& key) const = 0; /** * @brief getPrivateKey This method should be return private key for the public key that saved in this object. @@ -141,7 +141,6 @@ protected: */ void setSignature(const QByteArray &newSignature); -private: unsigned int _unixTime = 0; QByteArray _signature; QByteArray _publicKey; diff --git a/Heart/CMakeLists.txt b/Heart/CMakeLists.txt index 15176c3..5732390 100644 --- a/Heart/CMakeLists.txt +++ b/Heart/CMakeLists.txt @@ -7,9 +7,6 @@ cmake_minimum_required(VERSION 3.10) -if (${HEART_BUILD_LVL} GREATER_EQUAL 2) - add_subdirectory(Qt-Secret) -endif() set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_CXX_STANDARD 17) diff --git a/Heart/Qt-Secret b/Heart/Qt-Secret deleted file mode 160000 index 810376b..0000000 --- a/Heart/Qt-Secret +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 810376b593479606e4c8841e70676bb1f5790e2c From 622302a3ff87f8ca4b75a93e9acab93a613fc47c Mon Sep 17 00:00:00 2001 From: EndrII <EndrIIMail@gmail.com> Date: Wed, 9 Feb 2022 18:09:24 +0300 Subject: [PATCH 04/10] fix auth signature --- Heart/AbstractSpace/asynckeysauth.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Heart/AbstractSpace/asynckeysauth.h b/Heart/AbstractSpace/asynckeysauth.h index fa2874c..f4a8b29 100644 --- a/Heart/AbstractSpace/asynckeysauth.h +++ b/Heart/AbstractSpace/asynckeysauth.h @@ -116,7 +116,7 @@ protected: * @return signature data array. * @see AsyncKeysAuth::descrupt */ - virtual QByteArray &signMessage(const QByteArray& message, const QByteArray& key) const = 0; + virtual QByteArray signMessage(const QByteArray& message, const QByteArray& key) const = 0; /** * @brief checkSign This method should be check signature of the @a message using the @a key. From 5ff6c3ef96674d49769f8d98f86cfdc7d8327e83 Mon Sep 17 00:00:00 2001 From: EndrII <EndrIIMail@gmail.com> Date: Thu, 10 Feb 2022 20:06:39 +0300 Subject: [PATCH 05/10] added tests for ecdsa auth --- Heart/AbstractSpace/authecdsa.cpp | 180 +++++++++++++++++++++ Heart/AbstractSpace/authecdsa.h | 45 ++++++ HeartTests/AbstractSpace/ecdsaauthtest.cpp | 62 +++++++ HeartTests/AbstractSpace/ecdsaauthtest.h | 24 +++ HeartTests/tst_testprotockol.cpp | 2 + QuasarAppLib | 2 +- 6 files changed, 314 insertions(+), 1 deletion(-) create mode 100644 Heart/AbstractSpace/authecdsa.cpp create mode 100644 Heart/AbstractSpace/authecdsa.h create mode 100644 HeartTests/AbstractSpace/ecdsaauthtest.cpp create mode 100644 HeartTests/AbstractSpace/ecdsaauthtest.h diff --git a/Heart/AbstractSpace/authecdsa.cpp b/Heart/AbstractSpace/authecdsa.cpp new file mode 100644 index 0000000..b396b74 --- /dev/null +++ b/Heart/AbstractSpace/authecdsa.cpp @@ -0,0 +1,180 @@ +//# +//# Copyright (C) 2021-2022 QuasarApp. +//# Distributed under the GPLv3 software license, see the accompanying +//# Everyone is permitted to copy and distribute verbatim copies +//# of this license document, but changing it is not allowed. +//# + + +#include "authecdsa.h" + +#include <openssl/ec.h> // for EC_GROUP_new_by_curve_name, EC_GROUP_free, EC_KEY_new, EC_KEY_set_group, EC_KEY_generate_key, EC_KEY_free +#include <openssl/ecdsa.h> // for ECDSA_do_sign, ECDSA_do_verify +#include <openssl/obj_mac.h> // for NID_secp192k1 +#include <openssl/evp.h> + +#include <QCryptographicHash> +#include <QIODevice> +#include <QVector> + +namespace QH { + +AuthECDSA::AuthECDSA() { + +} + +QByteArray bignumToArray(const BIGNUM* num) { + int length = BN_bn2mpi(num, nullptr); + QVector<unsigned char> data(length); + BN_bn2mpi(num, data.data()); + QByteArray result; + result.insert(0, reinterpret_cast<char*>(data.data()), data.length()); + return result; +} + +BIGNUM* bignumFromArray(const QByteArray& array) { + return BN_mpi2bn(reinterpret_cast<const unsigned char*>(array.data()), array.length(), nullptr); +} + +QByteArray extractPrivateKey(EC_KEY* ec_key) { + const BIGNUM* ec_priv = EC_KEY_get0_private_key(ec_key); + int length = BN_bn2mpi(ec_priv, nullptr); + QVector<unsigned char> data(length); + data.insert(0, length); + BN_bn2mpi(ec_priv, data.data()); + QByteArray result; + result.insert(0, reinterpret_cast<char*>(data.data()), data.length()); + return result; +} + +QByteArray extractPublicKey(EC_KEY* key, EC_GROUP* group) { + + QByteArray data; + point_conversion_form_t form = EC_GROUP_get_point_conversion_form(group); + + unsigned char* pub_key_buffer; + size_t length = EC_KEY_key2buf(key, form, &pub_key_buffer, nullptr); + + if (length <= 0) { + return {}; + } + + data.insert(0, reinterpret_cast<const char*>(pub_key_buffer), length); + + OPENSSL_free(pub_key_buffer); + + return data; +} + +bool AuthECDSA::makeKeys(QByteArray &pubKey, QByteArray &privKey) { + + EC_KEY *eckey= nullptr; + EC_GROUP *ecgroup = nullptr; + + + auto free = [&eckey, &ecgroup] () { + if (ecgroup) + EC_GROUP_free(ecgroup); + + if (eckey) + EC_KEY_free(eckey); + }; + + eckey = EC_KEY_new(); + if (!eckey) + return false; + + ecgroup = EC_GROUP_new_by_curve_name(NID_secp256k1); + + if (!ecgroup) { + free(); + return false; + } + + const int success = 1; + if ( success != EC_KEY_set_group(eckey, ecgroup)) { + free(); + return false; + } + + if ( success != EC_KEY_generate_key(eckey)) { + free(); + return false; + } + + pubKey = extractPublicKey(eckey, ecgroup); + privKey = extractPrivateKey(eckey); + + return pubKey.length() && privKey.length(); +} + +QByteArray AuthECDSA::signMessage(const QByteArray &inputData, + const QByteArray &key) const { + + auto hash = QCryptographicHash::hash(inputData, QCryptographicHash::Sha256); + + EC_KEY *eckey = EC_KEY_new_by_curve_name(NID_secp256k1); + + BIGNUM* priv = BN_mpi2bn(reinterpret_cast<const unsigned char*>(key.data()), + key.length(), nullptr); + EC_KEY_set_private_key(eckey, priv); + BN_free(priv); + + ECDSA_SIG *signature = ECDSA_do_sign(reinterpret_cast<const unsigned char*>(hash.data()), + hash.length(), eckey); + EC_KEY_free(eckey); + + const BIGNUM * R, *S; + ECDSA_SIG_get0(signature, &R, &S); + + QByteArray result; + QDataStream stream(&result, QIODevice::WriteOnly); + + stream << bignumToArray(R); + stream << bignumToArray(S); + + ECDSA_SIG_free(signature); + + return result; +} + +bool AuthECDSA::checkSign(const QByteArray &inputData, + const QByteArray &signature, + const QByteArray &key) const { + + // extract key from raw array; + EC_KEY *eckey = EC_KEY_new_by_curve_name(NID_secp256k1); + const EC_GROUP* ec_group = EC_KEY_get0_group(eckey); + EC_POINT* ec_point = EC_POINT_new(ec_group); + EC_POINT_oct2point(ec_group, ec_point, + reinterpret_cast<const unsigned char*>(key.data()), + key.length(), nullptr); + EC_KEY_set_public_key(eckey, ec_point); + EC_POINT_free(ec_point); + + // extract signature from raw array + + BIGNUM * R, *S; + QDataStream stream(signature); + + QByteArray rR,rS; + stream >> rR; + stream >> rS; + R = bignumFromArray(rR); + S = bignumFromArray(rS); + + ECDSA_SIG *sig = ECDSA_SIG_new(); + ECDSA_SIG_set0(sig, R, S); + + auto hash = QCryptographicHash::hash(inputData, QCryptographicHash::Sha256); + + int verify_status = ECDSA_do_verify(reinterpret_cast<const unsigned char*>(hash.data()), + hash.length(), sig, eckey); + + ECDSA_SIG_free(sig); + + return verify_status == 1; + +} + +} diff --git a/Heart/AbstractSpace/authecdsa.h b/Heart/AbstractSpace/authecdsa.h new file mode 100644 index 0000000..efec031 --- /dev/null +++ b/Heart/AbstractSpace/authecdsa.h @@ -0,0 +1,45 @@ +//# +//# Copyright (C) 2021-2022 QuasarApp. +//# Distributed under the GPLv3 software license, see the accompanying +//# Everyone is permitted to copy and distribute verbatim copies +//# of this license document, but changing it is not allowed. +//# + + +#ifndef AUTHECDSA_H +#define AUTHECDSA_H + +#include "abstractdata.h" +#include <asynckeysauth.h> + +namespace QH { + +/** + * @brief The AuthECDSA class is ecdsa implementation of the Async authentication. This implementation based on Openssl library. + */ +class AuthECDSA: public QH::AsyncKeysAuth +{ + +public: + AuthECDSA(); + + /** + * @brief makeKeys This static method generate the public and private keys of the ECDSA. + * @param pubKey This is result public key. + * @param privKey This is result private key. + * @return true if keys generated successful. + */ + static bool makeKeys(QByteArray &pubKey, QByteArray &privKey); + + // AsyncKeysAuth interface +protected: + QByteArray signMessage(const QByteArray &inputData, const QByteArray &key) const override; + bool checkSign(const QByteArray &inputData, const QByteArray &signature, const QByteArray &key) const override; + +}; + + +} + + +#endif // AUTHECDSA_H diff --git a/HeartTests/AbstractSpace/ecdsaauthtest.cpp b/HeartTests/AbstractSpace/ecdsaauthtest.cpp new file mode 100644 index 0000000..286cdb8 --- /dev/null +++ b/HeartTests/AbstractSpace/ecdsaauthtest.cpp @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2022-2022 QuasarApp. + * Distributed under the lgplv3 software license, see the accompanying + * Everyone is permitted to copy and distribute verbatim copies + * of this license document, but changing it is not allowed. +*/ + +#include "ecdsaauthtest.h" +#include "authecdsa.h" +#include <QtTest> + +/* + * test class + */ +class ECDSA: public QH::AuthECDSA { + +public: + ECDSA(const QByteArray &publicKey, const QByteArray &privKey) { + setPublicKey(publicKey); + _priv = privKey; + } + + // AsyncKeysAuth interface +protected: + QByteArray getPrivateKey() const override { + return _priv; + }; + +private: + QByteArray _priv; + +}; + +ECDSAAuthTest::ECDSAAuthTest() { + +} + +ECDSAAuthTest::~ECDSAAuthTest() { + +} + +void ECDSAAuthTest::test() { + QByteArray pub, priv; + + QVERIFY(QH::AuthECDSA::makeKeys(pub, priv)); + + QVERIFY(pub.length() && priv.length()); + + ECDSA edsa(pub, priv); + + QVERIFY(!edsa.isValid()); + + QVERIFY(!edsa.auth(60)); + + QVERIFY(edsa.prepare()); + QVERIFY(edsa.isValid()); + + QVERIFY(edsa.auth(60)); + QVERIFY(!edsa.auth(0)); + + +} diff --git a/HeartTests/AbstractSpace/ecdsaauthtest.h b/HeartTests/AbstractSpace/ecdsaauthtest.h new file mode 100644 index 0000000..886ed9d --- /dev/null +++ b/HeartTests/AbstractSpace/ecdsaauthtest.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2022-2022 QuasarApp. + * Distributed under the lgplv3 software license, see the accompanying + * Everyone is permitted to copy and distribute verbatim copies + * of this license document, but changing it is not allowed. +*/ + +#ifndef ECDSAAUTHTEST_H +#define ECDSAAUTHTEST_H + +#include "test.h" +#include "testutils.h" + +class ECDSAAuthTest: public Test, protected TestUtils +{ +public: + ECDSAAuthTest(); + ~ECDSAAuthTest(); + + void test(); + +}; + +#endif // ECDSAAUTHTEST_H diff --git a/HeartTests/tst_testprotockol.cpp b/HeartTests/tst_testprotockol.cpp index d2d0b43..f22fa71 100644 --- a/HeartTests/tst_testprotockol.cpp +++ b/HeartTests/tst_testprotockol.cpp @@ -12,6 +12,7 @@ #include "abstractnodetest.h" #include <shedullertest.h> #include <bigdatatest.h> +#include <ecdsaauthtest.h> #endif #if HEART_BUILD_LVL >= 1 #include <basenodetest.h> @@ -42,6 +43,7 @@ private slots: TestCase(abstractNodeTest, AbstractNodeTest) TestCase(bigDataTest, BigDataTest); TestCase(shedullerTest, ShedullerTest); + TestCase(ecdsaAuthTest, ECDSAAuthTest); #endif #if HEART_BUILD_LVL >= 1 TestCase(baseNodeTest, BaseNodeTest) diff --git a/QuasarAppLib b/QuasarAppLib index f7ee5cd..9cc9e77 160000 --- a/QuasarAppLib +++ b/QuasarAppLib @@ -1 +1 @@ -Subproject commit f7ee5cd272d6e49d27b557e3fa481e54fe27e519 +Subproject commit 9cc9e77babc5cbf2f46276caff3783c273764556 From 918357b9e7b938cde33c4139441bbbe9912f5913 Mon Sep 17 00:00:00 2001 From: EndrII <EndrIIMail@gmail.com> Date: Sun, 13 Feb 2022 18:45:11 +0300 Subject: [PATCH 06/10] added support ecdsa login --- Heart/AbstractSpace/asynckeysauth.cpp | 12 +- Heart/AbstractSpace/authecdsa.cpp | 161 ++++++++++++++------- Heart/AbstractSpace/authecdsa.h | 5 + HeartTests/AbstractSpace/ecdsaauthtest.cpp | 4 +- QuasarAppLib | 2 +- 5 files changed, 122 insertions(+), 62 deletions(-) diff --git a/Heart/AbstractSpace/asynckeysauth.cpp b/Heart/AbstractSpace/asynckeysauth.cpp index 912e3e5..da920ec 100644 --- a/Heart/AbstractSpace/asynckeysauth.cpp +++ b/Heart/AbstractSpace/asynckeysauth.cpp @@ -17,7 +17,7 @@ AsyncKeysAuth::AsyncKeysAuth() { bool AsyncKeysAuth::auth(int allowedTimeRangeSec) const { - if (std::abs(time(0) - _unixTime) > allowedTimeRangeSec) { + if (std::abs(time(0) - _unixTime) >= allowedTimeRangeSec) { return false; } @@ -25,10 +25,7 @@ bool AsyncKeysAuth::auth(int allowedTimeRangeSec) const { data.insert(0, reinterpret_cast<const char*>(&_unixTime), sizeof(_unixTime)); - auto signData = QCryptographicHash::hash(data, - QCryptographicHash::Sha256); - - return checkSign(signData, signData, _publicKey); + return checkSign(data, _signature, _publicKey); } bool AsyncKeysAuth::prepare() { @@ -38,10 +35,7 @@ bool AsyncKeysAuth::prepare() { data.insert(0, reinterpret_cast<const char*>(&_unixTime), sizeof(_unixTime)); - auto signData = QCryptographicHash::hash(data, - QCryptographicHash::Sha256); - - setSignature(signMessage(signData, getPrivateKey())); + setSignature(signMessage(data, getPrivateKey())); return isValid(); } diff --git a/Heart/AbstractSpace/authecdsa.cpp b/Heart/AbstractSpace/authecdsa.cpp index b396b74..a0a9482 100644 --- a/Heart/AbstractSpace/authecdsa.cpp +++ b/Heart/AbstractSpace/authecdsa.cpp @@ -8,14 +8,15 @@ #include "authecdsa.h" -#include <openssl/ec.h> // for EC_GROUP_new_by_curve_name, EC_GROUP_free, EC_KEY_new, EC_KEY_set_group, EC_KEY_generate_key, EC_KEY_free #include <openssl/ecdsa.h> // for ECDSA_do_sign, ECDSA_do_verify #include <openssl/obj_mac.h> // for NID_secp192k1 #include <openssl/evp.h> +#include <openssl/err.h> #include <QCryptographicHash> #include <QIODevice> #include <QVector> +#include <quasarapp.h> namespace QH { @@ -23,6 +24,14 @@ AuthECDSA::AuthECDSA() { } +void printlastOpenSSlError() { + int error = ERR_get_error(); + char buffer[256]; + ERR_error_string(error, buffer); + QuasarAppUtils::Params::log(QString("openssl: %0").arg(buffer), + QuasarAppUtils::Error); +} + QByteArray bignumToArray(const BIGNUM* num) { int length = BN_bn2mpi(num, nullptr); QVector<unsigned char> data(length); @@ -33,18 +42,19 @@ QByteArray bignumToArray(const BIGNUM* num) { } BIGNUM* bignumFromArray(const QByteArray& array) { - return BN_mpi2bn(reinterpret_cast<const unsigned char*>(array.data()), array.length(), nullptr); + auto d = reinterpret_cast<const unsigned char*>(array.data()); + BIGNUM* result = BN_mpi2bn(d, + array.length(), nullptr); + if (!result) { + printlastOpenSSlError(); + } + + return result; } QByteArray extractPrivateKey(EC_KEY* ec_key) { const BIGNUM* ec_priv = EC_KEY_get0_private_key(ec_key); - int length = BN_bn2mpi(ec_priv, nullptr); - QVector<unsigned char> data(length); - data.insert(0, length); - BN_bn2mpi(ec_priv, data.data()); - QByteArray result; - result.insert(0, reinterpret_cast<char*>(data.data()), data.length()); - return result; + return bignumToArray(ec_priv); } QByteArray extractPublicKey(EC_KEY* key, EC_GROUP* group) { @@ -56,6 +66,7 @@ QByteArray extractPublicKey(EC_KEY* key, EC_GROUP* group) { size_t length = EC_KEY_key2buf(key, form, &pub_key_buffer, nullptr); if (length <= 0) { + printlastOpenSSlError(); return {}; } @@ -71,34 +82,14 @@ bool AuthECDSA::makeKeys(QByteArray &pubKey, QByteArray &privKey) { EC_KEY *eckey= nullptr; EC_GROUP *ecgroup = nullptr; - - auto free = [&eckey, &ecgroup] () { - if (ecgroup) - EC_GROUP_free(ecgroup); - - if (eckey) - EC_KEY_free(eckey); - }; - - eckey = EC_KEY_new(); - if (!eckey) - return false; - - ecgroup = EC_GROUP_new_by_curve_name(NID_secp256k1); - - if (!ecgroup) { - free(); + if (!prepareKeyAdnGroupObjects(&eckey, &ecgroup)) { return false; } - const int success = 1; - if ( success != EC_KEY_set_group(eckey, ecgroup)) { - free(); - return false; - } - - if ( success != EC_KEY_generate_key(eckey)) { - free(); + if (!EC_KEY_generate_key(eckey)) { + printlastOpenSSlError(); + EC_GROUP_free(ecgroup); + EC_KEY_free(eckey); return false; } @@ -111,18 +102,34 @@ bool AuthECDSA::makeKeys(QByteArray &pubKey, QByteArray &privKey) { QByteArray AuthECDSA::signMessage(const QByteArray &inputData, const QByteArray &key) const { - auto hash = QCryptographicHash::hash(inputData, QCryptographicHash::Sha256); + EC_KEY *eckey= nullptr; + EC_GROUP *ecgroup = nullptr; - EC_KEY *eckey = EC_KEY_new_by_curve_name(NID_secp256k1); + if (!prepareKeyAdnGroupObjects(&eckey, &ecgroup)) { + return {}; + } - BIGNUM* priv = BN_mpi2bn(reinterpret_cast<const unsigned char*>(key.data()), - key.length(), nullptr); - EC_KEY_set_private_key(eckey, priv); - BN_free(priv); + auto hash = QCryptographicHash::hash(inputData, + QCryptographicHash::Sha256); + + BIGNUM* priv = bignumFromArray(key); + if (!EC_KEY_set_private_key(eckey, priv)) { + printlastOpenSSlError(); + EC_GROUP_free(ecgroup); + EC_KEY_free(eckey); + return {}; + }; ECDSA_SIG *signature = ECDSA_do_sign(reinterpret_cast<const unsigned char*>(hash.data()), hash.length(), eckey); + BN_free(priv); EC_KEY_free(eckey); + EC_GROUP_free(ecgroup); + + if (!signature) { + printlastOpenSSlError(); + return {}; + } const BIGNUM * R, *S; ECDSA_SIG_get0(signature, &R, &S); @@ -142,15 +149,6 @@ bool AuthECDSA::checkSign(const QByteArray &inputData, const QByteArray &signature, const QByteArray &key) const { - // extract key from raw array; - EC_KEY *eckey = EC_KEY_new_by_curve_name(NID_secp256k1); - const EC_GROUP* ec_group = EC_KEY_get0_group(eckey); - EC_POINT* ec_point = EC_POINT_new(ec_group); - EC_POINT_oct2point(ec_group, ec_point, - reinterpret_cast<const unsigned char*>(key.data()), - key.length(), nullptr); - EC_KEY_set_public_key(eckey, ec_point); - EC_POINT_free(ec_point); // extract signature from raw array @@ -166,15 +164,78 @@ bool AuthECDSA::checkSign(const QByteArray &inputData, ECDSA_SIG *sig = ECDSA_SIG_new(); ECDSA_SIG_set0(sig, R, S); - auto hash = QCryptographicHash::hash(inputData, QCryptographicHash::Sha256); + auto hash = QCryptographicHash::hash(inputData, + QCryptographicHash::Sha256); + + + EC_KEY *eckey= nullptr; + EC_GROUP *ecgroup = nullptr; + + if (!prepareKeyAdnGroupObjects(&eckey, &ecgroup)) { + ECDSA_SIG_free(sig); + return {}; + } + + + // extract key from raw array; + EC_POINT* ec_point = EC_POINT_new(ecgroup); + EC_POINT_oct2point(ecgroup, ec_point, + reinterpret_cast<const unsigned char*>(key.data()), + key.length(), nullptr); + + EC_KEY_set_public_key(eckey, ec_point); + int verify_status = ECDSA_do_verify(reinterpret_cast<const unsigned char*>(hash.data()), hash.length(), sig, eckey); ECDSA_SIG_free(sig); + EC_POINT_free(ec_point); return verify_status == 1; } +bool AuthECDSA::prepareKeyAdnGroupObjects(EC_KEY **eckey, EC_GROUP **ecgroup) { + + // input data should be valid pointers to pointers of key and group objects. + if (!(eckey && ecgroup)) + return false; + + // input pointers should be nullptr; + if ((*eckey) || (*ecgroup)) + return false; + + auto free = [eckey, ecgroup] () { + if (*ecgroup) + EC_GROUP_free(*ecgroup); + + if (*eckey) + EC_KEY_free(*eckey); + }; + + *eckey = EC_KEY_new(); + if (!*eckey) { + printlastOpenSSlError(); + free(); + return false; + } + + *ecgroup = EC_GROUP_new_by_curve_name(NID_secp256k1); + + if (!*ecgroup) { + printlastOpenSSlError(); + free(); + return false; + } + + if (!EC_KEY_set_group(*eckey, *ecgroup)) { + printlastOpenSSlError(); + free(); + return false; + } + + return true; +} + } diff --git a/Heart/AbstractSpace/authecdsa.h b/Heart/AbstractSpace/authecdsa.h index efec031..faa8363 100644 --- a/Heart/AbstractSpace/authecdsa.h +++ b/Heart/AbstractSpace/authecdsa.h @@ -11,6 +11,8 @@ #include "abstractdata.h" #include <asynckeysauth.h> +#include <openssl/ec.h> // for EC_GROUP_new_by_curve_name, EC_GROUP_free, EC_KEY_new, EC_KEY_set_group, EC_KEY_generate_key, EC_KEY_free + namespace QH { @@ -36,6 +38,9 @@ protected: QByteArray signMessage(const QByteArray &inputData, const QByteArray &key) const override; bool checkSign(const QByteArray &inputData, const QByteArray &signature, const QByteArray &key) const override; +private: + static bool prepareKeyAdnGroupObjects(EC_KEY **eckey, EC_GROUP **ecgroup); + }; diff --git a/HeartTests/AbstractSpace/ecdsaauthtest.cpp b/HeartTests/AbstractSpace/ecdsaauthtest.cpp index 286cdb8..2e21ed2 100644 --- a/HeartTests/AbstractSpace/ecdsaauthtest.cpp +++ b/HeartTests/AbstractSpace/ecdsaauthtest.cpp @@ -50,12 +50,12 @@ void ECDSAAuthTest::test() { QVERIFY(!edsa.isValid()); - QVERIFY(!edsa.auth(60)); + QVERIFY(!edsa.auth(600)); QVERIFY(edsa.prepare()); QVERIFY(edsa.isValid()); - QVERIFY(edsa.auth(60)); + QVERIFY(edsa.auth(600)); QVERIFY(!edsa.auth(0)); diff --git a/QuasarAppLib b/QuasarAppLib index 9cc9e77..b6d3b0f 160000 --- a/QuasarAppLib +++ b/QuasarAppLib @@ -1 +1 @@ -Subproject commit 9cc9e77babc5cbf2f46276caff3783c273764556 +Subproject commit b6d3b0f72e129a78871a42d5e17dac18d1400cdb From e3b03b1022ff408e9d96e045814c9bd1c2959180 Mon Sep 17 00:00:00 2001 From: EndrII <EndrIIMail@gmail.com> Date: Sun, 13 Feb 2022 19:04:06 +0300 Subject: [PATCH 07/10] added test description --- Heart/AbstractSpace/asynckeysauth.cpp | 8 +++++++- HeartTests/AbstractSpace/ecdsaauthtest.cpp | 12 ++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/Heart/AbstractSpace/asynckeysauth.cpp b/Heart/AbstractSpace/asynckeysauth.cpp index da920ec..3d6f1e1 100644 --- a/Heart/AbstractSpace/asynckeysauth.cpp +++ b/Heart/AbstractSpace/asynckeysauth.cpp @@ -17,7 +17,13 @@ AsyncKeysAuth::AsyncKeysAuth() { bool AsyncKeysAuth::auth(int allowedTimeRangeSec) const { - if (std::abs(time(0) - _unixTime) >= allowedTimeRangeSec) { + int diff = time(0) - _unixTime; + + if (diff < 0) { + return false; + } + + if (diff >= allowedTimeRangeSec) { return false; } diff --git a/HeartTests/AbstractSpace/ecdsaauthtest.cpp b/HeartTests/AbstractSpace/ecdsaauthtest.cpp index 2e21ed2..cc37905 100644 --- a/HeartTests/AbstractSpace/ecdsaauthtest.cpp +++ b/HeartTests/AbstractSpace/ecdsaauthtest.cpp @@ -40,23 +40,35 @@ ECDSAAuthTest::~ECDSAAuthTest() { } void ECDSAAuthTest::test() { + // create a publick and private keys array. QByteArray pub, priv; + // make public and private keys. QVERIFY(QH::AuthECDSA::makeKeys(pub, priv)); + // check createed keys. should be larget then 0. QVERIFY(pub.length() && priv.length()); + // create test auth object using ecdsa algorithm ECDSA edsa(pub, priv); + // The terst object should be invalid because it is not prepared. QVERIFY(!edsa.isValid()); + // the authetication should be failed bacause ecdsa class is invalid. QVERIFY(!edsa.auth(600)); + // prepare an authentication object. QVERIFY(edsa.prepare()); + // the prepared object should be valid. QVERIFY(edsa.isValid()); + // authentication should be finished successful because auth object contains prepared valid signature. QVERIFY(edsa.auth(600)); + // authentication should be failed because the time range is depricated. QVERIFY(!edsa.auth(0)); + + } From 48e07658e908b1a3ef790dae3eacea942b8c6fde Mon Sep 17 00:00:00 2001 From: EndrII <EndrIIMail@gmail.com> Date: Sun, 13 Feb 2022 20:01:06 +0300 Subject: [PATCH 08/10] fiw windows build --- Heart/AbstractSpace/asynckeysauth.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Heart/AbstractSpace/asynckeysauth.cpp b/Heart/AbstractSpace/asynckeysauth.cpp index 3d6f1e1..eede591 100644 --- a/Heart/AbstractSpace/asynckeysauth.cpp +++ b/Heart/AbstractSpace/asynckeysauth.cpp @@ -7,7 +7,7 @@ #include "asynckeysauth.h" #include "QCryptographicHash" - +#include <time.h> namespace QH { From 0e8a9d324ef7b66266504b2aed3566ea8a7d2c72 Mon Sep 17 00:00:00 2001 From: EndrII <EndrIIMail@gmail.com> Date: Sun, 13 Feb 2022 20:08:19 +0300 Subject: [PATCH 09/10] fix windows --- Heart/AbstractSpace/authecdsa.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Heart/AbstractSpace/authecdsa.h b/Heart/AbstractSpace/authecdsa.h index faa8363..b5cdb3b 100644 --- a/Heart/AbstractSpace/authecdsa.h +++ b/Heart/AbstractSpace/authecdsa.h @@ -19,7 +19,7 @@ namespace QH { /** * @brief The AuthECDSA class is ecdsa implementation of the Async authentication. This implementation based on Openssl library. */ -class AuthECDSA: public QH::AsyncKeysAuth +class HEARTSHARED_EXPORT AuthECDSA: public QH::AsyncKeysAuth { public: From ba3f765639ad4c9c3e959a843eaa03bc30460b54 Mon Sep 17 00:00:00 2001 From: EndrII <EndrIIMail@gmail.com> Date: Mon, 14 Feb 2022 10:42:58 +0300 Subject: [PATCH 10/10] id generator option --- Heart/AbstractSpace/asynckeysauth.cpp | 12 +++++++-- Heart/AbstractSpace/asynckeysauth.h | 3 ++- HeartTests/AbstractSpace/ecdsaauthtest.cpp | 30 +++++++++++++++++++--- 3 files changed, 38 insertions(+), 7 deletions(-) diff --git a/Heart/AbstractSpace/asynckeysauth.cpp b/Heart/AbstractSpace/asynckeysauth.cpp index eede591..67d12ee 100644 --- a/Heart/AbstractSpace/asynckeysauth.cpp +++ b/Heart/AbstractSpace/asynckeysauth.cpp @@ -7,6 +7,7 @@ #include "asynckeysauth.h" #include "QCryptographicHash" +#include <QString> #include <time.h> namespace QH { @@ -15,7 +16,7 @@ AsyncKeysAuth::AsyncKeysAuth() { } -bool AsyncKeysAuth::auth(int allowedTimeRangeSec) const { +bool AsyncKeysAuth::auth(int allowedTimeRangeSec, QString* userId) const { int diff = time(0) - _unixTime; @@ -31,7 +32,14 @@ bool AsyncKeysAuth::auth(int allowedTimeRangeSec) const { data.insert(0, reinterpret_cast<const char*>(&_unixTime), sizeof(_unixTime)); - return checkSign(data, _signature, _publicKey); + bool result = checkSign(data, _signature, _publicKey); + + if (result && userId) { + *userId = QCryptographicHash::hash(_publicKey, + QCryptographicHash::Sha256).toHex(); + } + + return result; } bool AsyncKeysAuth::prepare() { diff --git a/Heart/AbstractSpace/asynckeysauth.h b/Heart/AbstractSpace/asynckeysauth.h index f4a8b29..c369547 100644 --- a/Heart/AbstractSpace/asynckeysauth.h +++ b/Heart/AbstractSpace/asynckeysauth.h @@ -55,9 +55,10 @@ public: /** * @brief auth This method make authentication and return true if the authentication finished successful else false. + * @brief retLoginedUserId This is logined user id in hex * @return true if the authentication finished successful else false. */ - bool auth(int allowedTimeRangeSec) const; + bool auth(int allowedTimeRangeSec, QString* retLoginedUserId) const; /** * @brief prepare This method will generate signature for autentication of client. Please inboke this method before send request to server. diff --git a/HeartTests/AbstractSpace/ecdsaauthtest.cpp b/HeartTests/AbstractSpace/ecdsaauthtest.cpp index cc37905..e81ff82 100644 --- a/HeartTests/AbstractSpace/ecdsaauthtest.cpp +++ b/HeartTests/AbstractSpace/ecdsaauthtest.cpp @@ -8,7 +8,7 @@ #include "ecdsaauthtest.h" #include "authecdsa.h" #include <QtTest> - +#include "thread" /* * test class */ @@ -42,10 +42,16 @@ ECDSAAuthTest::~ECDSAAuthTest() { void ECDSAAuthTest::test() { // create a publick and private keys array. QByteArray pub, priv; + QString userID; // make public and private keys. QVERIFY(QH::AuthECDSA::makeKeys(pub, priv)); + // make user id + QString userIDOfPubKey = QCryptographicHash::hash(pub, + QCryptographicHash::Sha256). + toHex(); + // check createed keys. should be larget then 0. QVERIFY(pub.length() && priv.length()); @@ -56,7 +62,8 @@ void ECDSAAuthTest::test() { QVERIFY(!edsa.isValid()); // the authetication should be failed bacause ecdsa class is invalid. - QVERIFY(!edsa.auth(600)); + QVERIFY(!edsa.auth(600, &userID)); + QVERIFY(userID.isEmpty()); // prepare an authentication object. QVERIFY(edsa.prepare()); @@ -64,9 +71,24 @@ void ECDSAAuthTest::test() { QVERIFY(edsa.isValid()); // authentication should be finished successful because auth object contains prepared valid signature. - QVERIFY(edsa.auth(600)); + QVERIFY(edsa.auth(600, &userID)); + QVERIFY(userID == userIDOfPubKey); + + // forget user id before new auth + userID.clear(); + // authentication should be failed because the time range is depricated. - QVERIFY(!edsa.auth(0)); + QVERIFY(!edsa.auth(0, &userID)); + QVERIFY(userID.isEmpty()); + + // change subsribe time and try login. + edsa.setUnixTime(time(0) + 1); + + std::this_thread::sleep_for(std::chrono::seconds(1)); + + // should be failed because signature is different of the time. + QVERIFY(!edsa.auth(600, &userID)); + QVERIFY(userID.isEmpty());