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 new file mode 100644 index 0000000..67d12ee --- /dev/null +++ b/Heart/AbstractSpace/asynckeysauth.cpp @@ -0,0 +1,85 @@ +/* + * 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" +#include <QString> +#include <time.h> + +namespace QH { + +AsyncKeysAuth::AsyncKeysAuth() { + +} + +bool AsyncKeysAuth::auth(int allowedTimeRangeSec, QString* userId) const { + + int diff = time(0) - _unixTime; + + if (diff < 0) { + return false; + } + + if (diff >= allowedTimeRangeSec) { + return false; + } + + QByteArray data = _publicKey; + data.insert(0, reinterpret_cast<const char*>(&_unixTime), + sizeof(_unixTime)); + + bool result = checkSign(data, _signature, _publicKey); + + if (result && userId) { + *userId = QCryptographicHash::hash(_publicKey, + QCryptographicHash::Sha256).toHex(); + } + + return result; +} + +bool AsyncKeysAuth::prepare() { + _unixTime = time(0); + + QByteArray data = _publicKey; + data.insert(0, reinterpret_cast<const char*>(&_unixTime), + sizeof(_unixTime)); + + setSignature(signMessage(data, 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..c369547 --- /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> + +#include "heart_global.h" + +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.signMessage(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 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. + * + * + */ +class HEARTSHARED_EXPORT AsyncKeysAuth +{ +public: + AsyncKeysAuth(); + + /** + * @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, QString* retLoginedUserId) 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 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 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. + * @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 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. + * @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); + + unsigned int _unixTime = 0; + QByteArray _signature; + QByteArray _publicKey; +}; + +} + +#endif // ASYNCKEYSAUTH_H diff --git a/Heart/AbstractSpace/authecdsa.cpp b/Heart/AbstractSpace/authecdsa.cpp new file mode 100644 index 0000000..a0a9482 --- /dev/null +++ b/Heart/AbstractSpace/authecdsa.cpp @@ -0,0 +1,241 @@ +//# +//# 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/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 { + +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); + BN_bn2mpi(num, data.data()); + QByteArray result; + result.insert(0, reinterpret_cast<char*>(data.data()), data.length()); + return result; +} + +BIGNUM* bignumFromArray(const QByteArray& array) { + 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); + return bignumToArray(ec_priv); +} + +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) { + printlastOpenSSlError(); + 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; + + if (!prepareKeyAdnGroupObjects(&eckey, &ecgroup)) { + return false; + } + + if (!EC_KEY_generate_key(eckey)) { + printlastOpenSSlError(); + EC_GROUP_free(ecgroup); + EC_KEY_free(eckey); + return false; + } + + pubKey = extractPublicKey(eckey, ecgroup); + privKey = extractPrivateKey(eckey); + + return pubKey.length() && privKey.length(); +} + +QByteArray AuthECDSA::signMessage(const QByteArray &inputData, + const QByteArray &key) const { + + EC_KEY *eckey= nullptr; + EC_GROUP *ecgroup = nullptr; + + if (!prepareKeyAdnGroupObjects(&eckey, &ecgroup)) { + return {}; + } + + 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); + + 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 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); + + + 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 new file mode 100644 index 0000000..b5cdb3b --- /dev/null +++ b/Heart/AbstractSpace/authecdsa.h @@ -0,0 +1,50 @@ +//# +//# 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> +#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 { + +/** + * @brief The AuthECDSA class is ecdsa implementation of the Async authentication. This implementation based on Openssl library. + */ +class HEARTSHARED_EXPORT 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; + +private: + static bool prepareKeyAdnGroupObjects(EC_KEY **eckey, EC_GROUP **ecgroup); + +}; + + +} + + +#endif // AUTHECDSA_H 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 diff --git a/HeartTests/AbstractSpace/ecdsaauthtest.cpp b/HeartTests/AbstractSpace/ecdsaauthtest.cpp new file mode 100644 index 0000000..e81ff82 --- /dev/null +++ b/HeartTests/AbstractSpace/ecdsaauthtest.cpp @@ -0,0 +1,96 @@ +/* + * 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> +#include "thread" +/* + * 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() { + // 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()); + + // 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, &userID)); + QVERIFY(userID.isEmpty()); + + // 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, &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, &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()); + + + + +} 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 b971e3d..b6d3b0f 160000 --- a/QuasarAppLib +++ b/QuasarAppLib @@ -1 +1 @@ -Subproject commit b971e3dffb7f2fdc63457e587f66f66eab9b9f6a +Subproject commit b6d3b0f72e129a78871a42d5e17dac18d1400cdb