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