222 lines
7.7 KiB
C++
222 lines
7.7 KiB
C++
/*
|
|
* crypto.cpp — Криптографические утилиты (Ed25519 через OpenSSL)
|
|
*/
|
|
|
|
#include "crypto.h"
|
|
|
|
#include <openssl/sha.h>
|
|
#include <openssl/evp.h>
|
|
#include <QFile>
|
|
#include <QCoreApplication>
|
|
#include <thread>
|
|
#include <cstring>
|
|
|
|
namespace Crypto {
|
|
|
|
/* ============================================================================
|
|
* Помощники
|
|
* ============================================================================ */
|
|
|
|
namespace {
|
|
|
|
void postToMain(std::function<void()> fn) {
|
|
auto* app = QCoreApplication::instance();
|
|
if (app) {
|
|
QMetaObject::invokeMethod(app, std::move(fn), Qt::QueuedConnection);
|
|
} else {
|
|
fn(); /* нет event loop — синхронно */
|
|
}
|
|
}
|
|
|
|
QByteArray sha512File(const QString& path) {
|
|
QFile file(path);
|
|
if (!file.open(QIODevice::ReadOnly)) return {};
|
|
|
|
EVP_MD_CTX* md_ctx = EVP_MD_CTX_new();
|
|
if (!md_ctx || EVP_DigestInit_ex(md_ctx, EVP_sha512(), NULL) != 1) {
|
|
if (md_ctx) EVP_MD_CTX_free(md_ctx);
|
|
return {};
|
|
}
|
|
|
|
QByteArray buf(65536, 0);
|
|
while (true) {
|
|
qint64 n = file.read(buf.data(), buf.size());
|
|
if (n <= 0) break;
|
|
EVP_DigestUpdate(md_ctx, reinterpret_cast<const uint8_t*>(buf.constData()),
|
|
static_cast<size_t>(n));
|
|
}
|
|
file.close();
|
|
|
|
QByteArray hash;
|
|
hash.resize(static_cast<int>(kSha512Size));
|
|
unsigned int hash_len = static_cast<unsigned int>(kSha512Size);
|
|
if (EVP_DigestFinal_ex(md_ctx, reinterpret_cast<uint8_t*>(hash.data()), &hash_len) != 1)
|
|
hash.clear();
|
|
EVP_MD_CTX_free(md_ctx);
|
|
return hash;
|
|
}
|
|
|
|
} // anonymous
|
|
|
|
/* ============================================================================
|
|
* Синхронные: derive ключей
|
|
* ============================================================================ */
|
|
|
|
QByteArray deriveEd25519Privkey(const QByteArray& x25519Privkey) {
|
|
if (x25519Privkey.size() != static_cast<int>(kKeySize)) return QByteArray();
|
|
|
|
uint8_t hash[kSha512Size];
|
|
SHA512(reinterpret_cast<const uint8_t*>(x25519Privkey.constData()), kKeySize, hash);
|
|
|
|
EVP_PKEY* pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL, hash, kKeySize);
|
|
if (!pkey) return QByteArray();
|
|
|
|
QByteArray result;
|
|
result.resize(static_cast<int>(kKeySize));
|
|
size_t priv_len = kKeySize;
|
|
if (EVP_PKEY_get_raw_private_key(pkey, reinterpret_cast<uint8_t*>(result.data()), &priv_len) <= 0
|
|
|| priv_len != kKeySize) {
|
|
EVP_PKEY_free(pkey);
|
|
return QByteArray();
|
|
}
|
|
EVP_PKEY_free(pkey);
|
|
return result;
|
|
}
|
|
|
|
QByteArray deriveEd25519Pubkey(const QByteArray& x25519Privkey) {
|
|
if (x25519Privkey.size() != static_cast<int>(kKeySize)) return QByteArray();
|
|
|
|
uint8_t hash[kSha512Size];
|
|
SHA512(reinterpret_cast<const uint8_t*>(x25519Privkey.constData()), kKeySize, hash);
|
|
|
|
EVP_PKEY* pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL, hash, kKeySize);
|
|
if (!pkey) return QByteArray();
|
|
|
|
QByteArray result;
|
|
result.resize(static_cast<int>(kKeySize));
|
|
size_t pub_len = kKeySize;
|
|
if (EVP_PKEY_get_raw_public_key(pkey, reinterpret_cast<uint8_t*>(result.data()), &pub_len) <= 0
|
|
|| pub_len != kKeySize) {
|
|
EVP_PKEY_free(pkey);
|
|
return QByteArray();
|
|
}
|
|
EVP_PKEY_free(pkey);
|
|
return result;
|
|
}
|
|
|
|
/* ============================================================================
|
|
* Синхронные: подпись / проверка (малые блоки)
|
|
* ============================================================================ */
|
|
|
|
QByteArray sign(const QByteArray& ed25519Privkey, const QByteArray& data) {
|
|
if (ed25519Privkey.size() != static_cast<int>(kKeySize)) return QByteArray();
|
|
|
|
EVP_PKEY* pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL,
|
|
reinterpret_cast<const uint8_t*>(ed25519Privkey.constData()), kKeySize);
|
|
if (!pkey) return QByteArray();
|
|
|
|
EVP_MD_CTX* md_ctx = EVP_MD_CTX_new();
|
|
if (!md_ctx || EVP_DigestSignInit(md_ctx, NULL, NULL, NULL, pkey) != 1) {
|
|
if (md_ctx) EVP_MD_CTX_free(md_ctx);
|
|
EVP_PKEY_free(pkey);
|
|
return QByteArray();
|
|
}
|
|
|
|
size_t sig_len = kSignSize;
|
|
QByteArray result;
|
|
result.resize(static_cast<int>(kSignSize));
|
|
if (EVP_DigestSign(md_ctx, reinterpret_cast<uint8_t*>(result.data()), &sig_len,
|
|
reinterpret_cast<const uint8_t*>(data.constData()), data.size()) != 1) {
|
|
EVP_MD_CTX_free(md_ctx);
|
|
EVP_PKEY_free(pkey);
|
|
return QByteArray();
|
|
}
|
|
|
|
EVP_MD_CTX_free(md_ctx);
|
|
EVP_PKEY_free(pkey);
|
|
result.resize(static_cast<int>(sig_len));
|
|
return result;
|
|
}
|
|
|
|
bool verify(const QByteArray& ed25519Pubkey, const QByteArray& data, const QByteArray& sig) {
|
|
if (ed25519Pubkey.size() != static_cast<int>(kKeySize)
|
|
|| sig.size() > static_cast<int>(kSignSize)) return false;
|
|
|
|
EVP_PKEY* pkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL,
|
|
reinterpret_cast<const uint8_t*>(ed25519Pubkey.constData()), kKeySize);
|
|
if (!pkey) return false;
|
|
|
|
EVP_MD_CTX* md_ctx = EVP_MD_CTX_new();
|
|
if (!md_ctx || EVP_DigestVerifyInit(md_ctx, NULL, NULL, NULL, pkey) != 1) {
|
|
if (md_ctx) EVP_MD_CTX_free(md_ctx);
|
|
EVP_PKEY_free(pkey);
|
|
return false;
|
|
}
|
|
|
|
int ret = EVP_DigestVerify(md_ctx, reinterpret_cast<const uint8_t*>(sig.constData()), sig.size(),
|
|
reinterpret_cast<const uint8_t*>(data.constData()), data.size());
|
|
EVP_MD_CTX_free(md_ctx);
|
|
EVP_PKEY_free(pkey);
|
|
return ret == 1;
|
|
}
|
|
|
|
/* ============================================================================
|
|
* Асинхронные: файлы
|
|
* ============================================================================ */
|
|
|
|
void signFile(const QString& path, const QByteArray& ed25519Privkey, SignCallback cb) {
|
|
std::thread([=]() {
|
|
SignResult r;
|
|
QByteArray hash = sha512File(path);
|
|
if (hash.isEmpty()) {
|
|
r.ok = false; r.error = QStringLiteral("failed to read/hash file");
|
|
} else {
|
|
r.signature = sign(ed25519Privkey, hash);
|
|
r.ok = !r.signature.isEmpty();
|
|
if (!r.ok) r.error = QStringLiteral("sign failed");
|
|
}
|
|
postToMain([cb, r]() { cb(r); });
|
|
}).detach();
|
|
}
|
|
|
|
void verifyFile(const QString& path, const QByteArray& ed25519Pubkey,
|
|
const QByteArray& signature, SignCallback cb) {
|
|
std::thread([=]() {
|
|
SignResult r;
|
|
QByteArray hash = sha512File(path);
|
|
if (hash.isEmpty()) {
|
|
r.ok = false; r.error = QStringLiteral("failed to read/hash file");
|
|
} else {
|
|
r.ok = verify(ed25519Pubkey, hash, signature);
|
|
if (!r.ok) r.error = QStringLiteral("signature mismatch");
|
|
}
|
|
postToMain([cb, r]() { cb(r); });
|
|
}).detach();
|
|
}
|
|
|
|
/* ============================================================================
|
|
* Асинхронные: in-memory блоки
|
|
* ============================================================================ */
|
|
|
|
void signAsync(const QByteArray& ed25519Privkey, const QByteArray& data, SignCallback cb) {
|
|
std::thread([=]() {
|
|
SignResult r;
|
|
r.signature = sign(ed25519Privkey, data);
|
|
r.ok = !r.signature.isEmpty();
|
|
if (!r.ok) r.error = QStringLiteral("sign failed");
|
|
postToMain([cb, r]() { cb(r); });
|
|
}).detach();
|
|
}
|
|
|
|
void verifyAsync(const QByteArray& ed25519Pubkey, const QByteArray& data,
|
|
const QByteArray& signature, SignCallback cb) {
|
|
std::thread([=]() {
|
|
SignResult r;
|
|
r.ok = verify(ed25519Pubkey, data, signature);
|
|
if (!r.ok) r.error = QStringLiteral("signature mismatch");
|
|
postToMain([cb, r]() { cb(r); });
|
|
}).detach();
|
|
}
|
|
|
|
} // namespace Crypto
|