// net_classic_cipher_test — 40250 _IMPROVED_PACKET_ENCRYPTION_ end to end, // no socket: a client-polarity ClassicCipher and a server-polarity one run the // DH2 key agreement against each other, then encrypt/decrypt must round-trip // symmetrically (client.encrypt -> server.decrypt and vice versa). #include "../src/net/classic/classic_cipher.h" #include "../src/net/classic/wire_classic.h" #include #include #include #include using namespace mtnet::classic; static int g_fail = 0; #define CHECK(c, msg) \ do { \ if (!(c)) { \ std::fprintf(stderr, "FAIL: %s\n", msg); \ ++g_fail; \ } \ } while (0) int main() { ClassicCipher client; // polarity = true (EterLib/NetStream.cpp:3004) ClassicCipher server; // polarity = false (desc.cpp FinishHandshake) // Each side generates its DH2 public blob (spub || epub). uint8_t cbuf[KEY_AGREEMENT_MAX_DATA_LEN]; uint8_t sbuf[KEY_AGREEMENT_MAX_DATA_LEN]; size_t clen = sizeof(cbuf); size_t slen = sizeof(sbuf); size_t cagreed = client.prepare(cbuf, &clen); size_t sagreed = server.prepare(sbuf, &slen); CHECK(cagreed != 0, "client prepare -> non-zero agreed length"); CHECK(sagreed != 0, "server prepare -> non-zero agreed length"); CHECK(cagreed == sagreed, "both sides same agreed-value length (same DH2 params)"); CHECK(clen > 0 && clen <= KEY_AGREEMENT_MAX_DATA_LEN, "client blob length sane"); CHECK(clen == slen, "blob lengths match"); // Activate: each consumes the PEER's (agreed_length, blob). bool ca = client.activate(true, sagreed, sbuf, slen); bool sa = server.activate(false, cagreed, cbuf, clen); CHECK(ca, "client activate"); CHECK(sa, "server activate"); CHECK(client.key_ready() && server.key_ready(), "both have encoder+decoder"); // The stream turns real only after GC_KEY_AGREEMENT_COMPLETED. CHECK(!client.activated(), "cipher not 'activated' until set_activated"); client.set_activated(true); server.set_activated(true); // --- round trip: client -> server --- { std::string plain = "the quick brown fox CG_MOVE \x07\x01\x00\x00 jumps"; std::vector buf(plain.begin(), plain.end()); std::vector orig = buf; client.encrypt(buf.data(), buf.size()); CHECK(buf != orig, "ciphertext differs from plaintext"); server.decrypt(buf.data(), buf.size()); CHECK(buf == orig, "server recovers client's plaintext"); } // --- round trip: server -> client, and CTR keystream continuity --- { std::string a = "GC_MAIN_CHARACTER hello world 0123456789"; std::string b = "second chunk keeps the CTR counter advancing"; std::vector ba(a.begin(), a.end()), bb(b.begin(), b.end()); std::vector oa = ba, ob = bb; server.encrypt(ba.data(), ba.size()); server.encrypt(bb.data(), bb.size()); client.decrypt(ba.data(), ba.size()); client.decrypt(bb.data(), bb.size()); CHECK(ba == oa && bb == ob, "client recovers server's two chunks (CTR continuity)"); } // --- a fresh pair must derive a *different* key (ephemeral) --- { ClassicCipher c2, s2; uint8_t x[256], y[256]; size_t xl = sizeof(x), yl = sizeof(y); size_t xa = c2.prepare(x, &xl); size_t ya = s2.prepare(y, &yl); c2.activate(true, ya, y, yl); s2.activate(false, xa, x, xl); c2.set_activated(true); s2.set_activated(true); std::string msg = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; // 32 identical bytes std::vector p1(msg.begin(), msg.end()); std::vector p2 = p1; client.encrypt(p1.data(), p1.size()); // from the first session (already advanced) c2.encrypt(p2.data(), p2.size()); // fresh session CHECK(p1 != p2, "different sessions produce different ciphertext"); } if (g_fail) { std::fprintf(stderr, "%d check(s) failed\n", g_fail); return 1; } std::puts("net_classic_cipher_test OK"); return 0; }