You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

247 lines
8.0 KiB

/*
* 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"