Merge branch 'AsyncKeysAuth'
All checks were successful
buildbot/IOSCMakeBuilder Build finished.

This commit is contained in:
Andrei Yankovich 2022-02-14 14:42:26 +03:00
commit 2beefa22ca
11 changed files with 651 additions and 8 deletions

3
.gitmodules vendored
View File

@ -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

View File

@ -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;
}
}

View File

@ -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

View File

@ -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;
}
}

View File

@ -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

View File

@ -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)

@ -1 +0,0 @@
Subproject commit 810376b593479606e4c8841e70676bb1f5790e2c

View File

@ -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());
}

View File

@ -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

View File

@ -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)

@ -1 +1 @@
Subproject commit b971e3dffb7f2fdc63457e587f66f66eab9b9f6a
Subproject commit b6d3b0f72e129a78871a42d5e17dac18d1400cdb