248 lines
8.0 KiB
C++
248 lines
8.0 KiB
C++
/*
|
|
* test_crypto.cpp — тесты криптографических функций (Crypto::)
|
|
*/
|
|
|
|
#include <QTest>
|
|
#include <QTemporaryFile>
|
|
#include <QByteArray>
|
|
#include <QElapsedTimer>
|
|
#include <QAtomicInt>
|
|
#include <QDebug>
|
|
#include <cstring>
|
|
#include "../transport/crypto.h"
|
|
|
|
class TestCrypto : public QObject {
|
|
Q_OBJECT
|
|
|
|
private slots:
|
|
void initTestCase();
|
|
void test_derive_key_sizes();
|
|
void test_derive_priv_not_empty();
|
|
void test_derive_pub_not_empty();
|
|
void test_priv_ne_pub();
|
|
void test_sign_verify_roundtrip();
|
|
void test_sign_verify_empty();
|
|
void test_verify_wrong_sig();
|
|
void test_verify_wrong_key();
|
|
void test_sign_empty_key();
|
|
void test_sign_invalid_key_size();
|
|
void test_signFile_verifyFile();
|
|
void test_signFile_nonexistent();
|
|
void test_sign_verify_100mb_parallel();
|
|
|
|
private:
|
|
void writeTestFile(const QString& path, unsigned char pattern, size_t fileSize);
|
|
|
|
private:
|
|
QByteArray m_x25519Priv; // 32 random bytes
|
|
QByteArray m_edPriv; // derived Ed25519 private key
|
|
QByteArray m_edPub; // derived Ed25519 public key
|
|
QByteArray m_message;
|
|
};
|
|
|
|
void TestCrypto::initTestCase() {
|
|
m_x25519Priv = QByteArray(Crypto::kKeySize, 0);
|
|
for (int i = 0; i < static_cast<int>(Crypto::kKeySize); i++)
|
|
m_x25519Priv[i] = static_cast<char>(0x42 + i); /* детерминированное значение */
|
|
|
|
m_edPriv = Crypto::deriveEd25519Privkey(m_x25519Priv);
|
|
m_edPub = Crypto::deriveEd25519Pubkey(m_x25519Priv);
|
|
m_message = QByteArray("hello world");
|
|
}
|
|
|
|
void TestCrypto::test_derive_key_sizes() {
|
|
QCOMPARE(m_edPriv.size(), static_cast<int>(Crypto::kKeySize));
|
|
QCOMPARE(m_edPub.size(), static_cast<int>(Crypto::kKeySize));
|
|
}
|
|
|
|
void TestCrypto::test_derive_priv_not_empty() {
|
|
QVERIFY(!m_edPriv.isEmpty());
|
|
}
|
|
|
|
void TestCrypto::test_derive_pub_not_empty() {
|
|
QVERIFY(!m_edPub.isEmpty());
|
|
}
|
|
|
|
void TestCrypto::test_priv_ne_pub() {
|
|
QVERIFY(m_edPriv != m_edPub);
|
|
}
|
|
|
|
void TestCrypto::test_sign_verify_roundtrip() {
|
|
QByteArray sig = Crypto::sign(m_edPriv, m_message);
|
|
QCOMPARE(sig.size(), static_cast<int>(Crypto::kSignSize));
|
|
QVERIFY(Crypto::verify(m_edPub, m_message, sig));
|
|
}
|
|
|
|
void TestCrypto::test_sign_verify_empty() {
|
|
QByteArray empty;
|
|
QByteArray sig = Crypto::sign(m_edPriv, empty);
|
|
QCOMPARE(sig.size(), static_cast<int>(Crypto::kSignSize));
|
|
QVERIFY(Crypto::verify(m_edPub, empty, sig));
|
|
}
|
|
|
|
void TestCrypto::test_verify_wrong_sig() {
|
|
QByteArray sig = Crypto::sign(m_edPriv, m_message);
|
|
QVERIFY(!sig.isEmpty());
|
|
sig[8] = static_cast<char>(sig[8] ^ 1); /* портим подпись */
|
|
QVERIFY(!Crypto::verify(m_edPub, m_message, sig));
|
|
}
|
|
|
|
void TestCrypto::test_verify_wrong_key() {
|
|
QByteArray sig = Crypto::sign(m_edPriv, m_message);
|
|
/* другой ключ */
|
|
QByteArray otherPriv(Crypto::kKeySize, 0);
|
|
for (int i = 0; i < static_cast<int>(Crypto::kKeySize); i++)
|
|
otherPriv[i] = static_cast<char>(0x77 + i);
|
|
QByteArray otherPub = Crypto::deriveEd25519Pubkey(otherPriv);
|
|
QVERIFY(!otherPub.isEmpty());
|
|
QVERIFY(!Crypto::verify(otherPub, m_message, sig));
|
|
}
|
|
|
|
void TestCrypto::test_sign_empty_key() {
|
|
QByteArray sig = Crypto::sign(QByteArray(), m_message);
|
|
QVERIFY(sig.isEmpty());
|
|
QVERIFY(!Crypto::verify(QByteArray(), m_message, sig));
|
|
}
|
|
|
|
void TestCrypto::test_sign_invalid_key_size() {
|
|
QByteArray badKey(16, 0x42);
|
|
QByteArray sig = Crypto::sign(badKey, m_message);
|
|
QVERIFY(sig.isEmpty());
|
|
}
|
|
|
|
void TestCrypto::test_signFile_verifyFile() {
|
|
QTemporaryFile tmp;
|
|
QVERIFY(tmp.open());
|
|
QByteArray content("file content for signing test");
|
|
tmp.write(content);
|
|
tmp.close();
|
|
|
|
QByteArray sig;
|
|
QEventLoop loop;
|
|
Crypto::signFile(tmp.fileName(), m_edPriv,
|
|
[&](const Crypto::SignResult& r) {
|
|
sig = r.signature;
|
|
QVERIFY(r.ok);
|
|
QVERIFY(!sig.isEmpty());
|
|
loop.quit();
|
|
});
|
|
loop.exec();
|
|
|
|
QEventLoop loop2;
|
|
Crypto::verifyFile(tmp.fileName(), m_edPub, sig,
|
|
[&](const Crypto::SignResult& r) {
|
|
QVERIFY(r.ok);
|
|
loop2.quit();
|
|
});
|
|
loop2.exec();
|
|
}
|
|
|
|
void TestCrypto::test_signFile_nonexistent() {
|
|
QEventLoop loop;
|
|
Crypto::signFile("/nonexistent/path/test", m_edPriv,
|
|
[&](const Crypto::SignResult& r) {
|
|
QVERIFY(!r.ok);
|
|
QVERIFY(!r.error.isEmpty());
|
|
loop.quit();
|
|
});
|
|
loop.exec();
|
|
}
|
|
|
|
void TestCrypto::writeTestFile(const QString& path, unsigned char pattern, size_t fileSize) {
|
|
QFile f(path);
|
|
QVERIFY(f.open(QIODevice::WriteOnly));
|
|
constexpr size_t kChunkSize = 65536;
|
|
QByteArray chunk(static_cast<int>(kChunkSize), Qt::Uninitialized);
|
|
memset(chunk.data(), pattern, kChunkSize);
|
|
size_t remaining = fileSize;
|
|
while (remaining >= kChunkSize) {
|
|
QCOMPARE(f.write(chunk), static_cast<qint64>(kChunkSize));
|
|
remaining -= kChunkSize;
|
|
}
|
|
if (remaining > 0) f.write(chunk.constData(), static_cast<qint64>(remaining));
|
|
f.close();
|
|
}
|
|
|
|
void TestCrypto::test_sign_verify_100mb_parallel() {
|
|
constexpr size_t kFileSize = 100ULL * 1024 * 1024;
|
|
constexpr int kNumFiles = 3;
|
|
|
|
QString paths[kNumFiles];
|
|
QTemporaryFile tmp[kNumFiles];
|
|
for (int i = 0; i < kNumFiles; i++) {
|
|
QVERIFY(tmp[i].open());
|
|
paths[i] = tmp[i].fileName();
|
|
tmp[i].close();
|
|
writeTestFile(paths[i], static_cast<unsigned char>(0xAA + i), kFileSize);
|
|
}
|
|
|
|
/* ------- Sign (параллельно) ------- */
|
|
QByteArray sigs[kNumFiles];
|
|
qint64 signTimesMs[kNumFiles] = {};
|
|
QElapsedTimer totalSignTimer;
|
|
QAtomicInt signDone{0};
|
|
QEventLoop signLoop;
|
|
|
|
totalSignTimer.start();
|
|
for (int i = 0; i < kNumFiles; i++) {
|
|
QElapsedTimer t;
|
|
t.start();
|
|
Crypto::signFile(paths[i], m_edPriv,
|
|
[&, i, t](const Crypto::SignResult& r) mutable {
|
|
QVERIFY(r.ok);
|
|
QVERIFY(!r.signature.isEmpty());
|
|
sigs[i] = r.signature;
|
|
signTimesMs[i] = t.elapsed();
|
|
if (signDone.fetchAndAddRelaxed(1) == kNumFiles - 1)
|
|
signLoop.quit();
|
|
});
|
|
}
|
|
signLoop.exec();
|
|
qint64 totalSignMs = totalSignTimer.elapsed();
|
|
|
|
/* ------- Verify (параллельно) ------- */
|
|
qint64 verifyTimesMs[kNumFiles] = {};
|
|
QElapsedTimer totalVerifyTimer;
|
|
QAtomicInt verifyDone{0};
|
|
QEventLoop verifyLoop;
|
|
|
|
totalVerifyTimer.start();
|
|
for (int i = 0; i < kNumFiles; i++) {
|
|
QElapsedTimer t;
|
|
t.start();
|
|
Crypto::verifyFile(paths[i], m_edPub, sigs[i],
|
|
[&, i, t](const Crypto::SignResult& r) mutable {
|
|
QVERIFY(r.ok);
|
|
verifyTimesMs[i] = t.elapsed();
|
|
if (verifyDone.fetchAndAddRelaxed(1) == kNumFiles - 1)
|
|
verifyLoop.quit();
|
|
});
|
|
}
|
|
verifyLoop.exec();
|
|
qint64 totalVerifyMs = totalVerifyTimer.elapsed();
|
|
|
|
/* ------- Профилирование ------- */
|
|
qint64 signSum = 0, verifySum = 0;
|
|
for (int i = 0; i < kNumFiles; i++) {
|
|
signSum += signTimesMs[i];
|
|
verifySum += verifyTimesMs[i];
|
|
}
|
|
|
|
qDebug() << "\n======= test_sign_verify_100mb_parallel =======";
|
|
for (int i = 0; i < kNumFiles; i++)
|
|
qDebug().nospace() << " File " << i << ": sign=" << signTimesMs[i]
|
|
<< "ms | verify=" << verifyTimesMs[i] << "ms";
|
|
qDebug().nospace() << "Phase sign: " << totalSignMs << "ms total"
|
|
<< " (sum=" << signSum << "ms, speedup="
|
|
<< (double)signSum / totalSignMs << "x)";
|
|
qDebug().nospace() << "Phase verify: " << totalVerifyMs << "ms total"
|
|
<< " (sum=" << verifySum << "ms, speedup="
|
|
<< (double)verifySum / totalVerifyMs << "x)";
|
|
|
|
/* Вычищаем временные файлы */
|
|
for (int i = 0; i < kNumFiles; i++) QFile::remove(paths[i]);
|
|
}
|
|
|
|
QTEST_MAIN(TestCrypto)
|
|
#include "test_crypto.moc"
|