tests: port remaining C unit tests to the C++ test harness

Convert acl, acl_literal, aead2022, base64, buffer, cache (legacy
reference), crypto, ipset, ipset_reference, json, netutils, ppbloom,
process, ref, ssurl and utils tests to test_helpers.hpp. ctest names are
unchanged; every assertion is kept. Build the ported sources with the
strict C++ warning set.
This commit is contained in:
Max Lv
2026-10-15 01:33:51 +08:00
parent d168986dc6
commit 678a891ef9
18 changed files with 1274 additions and 1169 deletions
+125 -136
View File
@@ -1,53 +1,53 @@
/*
* Unit tests for the Shadowsocks 2022 Edition (SIP022) cipher suite.
*/
// SPDX-License-Identifier: GPL-3.0-or-later
// Shadowsocks 2022 Edition (SIP022) cipher suite in the legacy crypto
// reference (tests/reference/aead.cpp): BLAKE3 vectors, TCP and UDP round
// trips, tamper and replay rejection.
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include <sodium.h>
int verbose = 0;
#include "test_helpers.hpp"
#include "crypto.h"
#include "aead.h"
#include "blake3/blake3.h"
#include "crypto.h"
#include "ppbloom.h"
#include "utils.h"
#include "blake3/blake3.h"
/* Provide nonce_cache symbol needed by crypto.cpp */
struct cache *nonce_cache = NULL;
extern "C" {
int verbose = 0;
// Provide the nonce_cache symbol needed by the crypto reference.
struct cache *nonce_cache = nullptr;
}
namespace {
/* 32-byte and 16-byte PSKs in base64 */
#define PSK32 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
#define PSK16 "AAAAAAAAAAAAAAAAAAAAAA=="
static const char *methods_2022[] = {
const char *const methods_2022[] = {
"2022-blake3-aes-128-gcm",
"2022-blake3-aes-256-gcm",
"2022-blake3-chacha20-poly1305",
};
static const char *
psk_for(const char *method)
const char *psk_for(const char *method)
{
return strcmp(method, "2022-blake3-aes-128-gcm") == 0 ? PSK16 : PSK32;
return std::strcmp(method, "2022-blake3-aes-128-gcm") == 0 ? PSK16 : PSK32;
}
static crypto_t *
make_crypto(const char *method)
crypto_t *make_crypto(const char *method)
{
crypto_t *c = crypto_init(psk_for(method), NULL, method);
assert(c != NULL);
crypto_t *c = crypto_init(psk_for(method), nullptr, method);
SS_CHECK(c != nullptr);
return c;
}
static void
free_crypto(crypto_t *c)
void free_crypto(crypto_t *c)
{
/*
* crypto_init() sets up the nonce bloom filter each time it is called,
@@ -64,14 +64,13 @@ free_crypto(crypto_t *c)
* nothing interoperates. Vector generated from the BLAKE3 reference for
* context "shadowsocks 2022 session subkey" over 32 zero bytes of material.
*/
static void
test_blake3_derive_key_known_vector(void)
void test_blake3_derive_key_known_vector()
{
blake3_hasher h;
uint8_t material[32];
uint8_t out[32];
memset(material, 0, sizeof(material));
std::memset(material, 0, sizeof(material));
blake3_hasher_init_derive_key(&h, SS2022_SUBKEY_CTX);
blake3_hasher_update(&h, material, sizeof(material));
blake3_hasher_finalize(&h, out, sizeof(out));
@@ -81,20 +80,18 @@ test_blake3_derive_key_known_vector(void)
blake3_hasher_init_derive_key(&h, SS2022_SUBKEY_CTX);
blake3_hasher_update(&h, material, sizeof(material));
blake3_hasher_finalize(&h, out2, sizeof(out2));
assert(memcmp(out, out2, 32) == 0);
SS_CHECK(std::memcmp(out, out2, 32) == 0);
/* A different context string must give a different subkey. */
uint8_t out3[32];
blake3_hasher_init_derive_key(&h, "some other context");
blake3_hasher_update(&h, material, sizeof(material));
blake3_hasher_finalize(&h, out3, sizeof(out3));
assert(memcmp(out, out3, 32) != 0);
(void)out3;
SS_CHECK(std::memcmp(out, out3, 32) != 0);
}
/* BLAKE3 hash of the empty input, from the reference test vectors. */
static void
test_blake3_empty_hash_vector(void)
void test_blake3_empty_hash_vector()
{
blake3_hasher h;
uint8_t out[32];
@@ -108,16 +105,14 @@ test_blake3_empty_hash_vector(void)
blake3_hasher_init(&h);
blake3_hasher_update(&h, "", 0);
blake3_hasher_finalize(&h, out, sizeof(out));
assert(memcmp(out, expected, 32) == 0);
(void)expected;
SS_CHECK(std::memcmp(out, expected, 32) == 0);
}
/*
* A client request stream must round-trip through a server decrypt context:
* salt, fixed header, variable header with the SOCKS address, then payload.
*/
static void
test_tcp_request_roundtrip(const char *method)
void test_tcp_request_roundtrip(const char *method)
{
/* SOCKS address: IPv4 1.2.3.4:80 */
static const uint8_t addr[] = { 0x01, 1, 2, 3, 4, 0x00, 0x50 };
@@ -137,35 +132,35 @@ test_tcp_request_roundtrip(const char *method)
/* First write: the address header. */
buffer_t buf;
memset(&buf, 0, sizeof(buf));
std::memset(&buf, 0, sizeof(buf));
balloc(&buf, 4096);
memcpy(buf.data, addr, sizeof(addr));
std::memcpy(buf.data, addr, sizeof(addr));
buf.len = sizeof(addr);
assert(c->encrypt(&buf, &enc, 4096) == CRYPTO_OK);
SS_CHECK(c->encrypt(&buf, &enc, 4096) == CRYPTO_OK);
/* Second write: stream payload. */
buffer_t buf2;
memset(&buf2, 0, sizeof(buf2));
std::memset(&buf2, 0, sizeof(buf2));
balloc(&buf2, 4096);
size_t plen = strlen(payload);
memcpy(buf2.data, payload, plen);
size_t plen = std::strlen(payload);
std::memcpy(buf2.data, payload, plen);
buf2.len = plen;
assert(c->encrypt(&buf2, &enc, 4096) == CRYPTO_OK);
SS_CHECK(c->encrypt(&buf2, &enc, 4096) == CRYPTO_OK);
/* Feed both writes to the server in one go, as they arrive on the wire. */
buffer_t wire;
memset(&wire, 0, sizeof(wire));
std::memset(&wire, 0, sizeof(wire));
balloc(&wire, 8192);
memcpy(wire.data, buf.data, buf.len);
memcpy(wire.data + buf.len, buf2.data, buf2.len);
std::memcpy(wire.data, buf.data, buf.len);
std::memcpy(wire.data + buf.len, buf2.data, buf2.len);
wire.len = buf.len + buf2.len;
assert(c->decrypt(&wire, &dec, 8192) == CRYPTO_OK);
SS_CHECK(c->decrypt(&wire, &dec, 8192) == CRYPTO_OK);
/* The server sees the SOCKS address followed by the payload. */
assert(wire.len == sizeof(addr) + plen);
assert(memcmp(wire.data, addr, sizeof(addr)) == 0);
assert(memcmp(wire.data + sizeof(addr), payload, plen) == 0);
SS_CHECK(wire.len == sizeof(addr) + plen);
SS_CHECK(std::memcmp(wire.data, addr, sizeof(addr)) == 0);
SS_CHECK(std::memcmp(wire.data + sizeof(addr), payload, plen) == 0);
bfree(&buf);
bfree(&buf2);
@@ -179,8 +174,7 @@ test_tcp_request_roundtrip(const char *method)
* A stream split across arbitrary boundaries must still decrypt: the
* decryptor buffers partial chunks and returns CRYPTO_NEED_MORE.
*/
static void
test_tcp_partial_delivery(const char *method)
void test_tcp_partial_delivery(const char *method)
{
static const uint8_t addr[] = { 0x01, 10, 0, 0, 1, 0x1f, 0x90 };
crypto_t *c = make_crypto(method);
@@ -194,33 +188,32 @@ test_tcp_partial_delivery(const char *method)
dec.role = CRYPTO_ROLE_SERVER;
buffer_t buf;
memset(&buf, 0, sizeof(buf));
std::memset(&buf, 0, sizeof(buf));
balloc(&buf, 4096);
memcpy(buf.data, addr, sizeof(addr));
std::memcpy(buf.data, addr, sizeof(addr));
buf.len = sizeof(addr);
assert(c->encrypt(&buf, &enc, 4096) == CRYPTO_OK);
SS_CHECK(c->encrypt(&buf, &enc, 4096) == CRYPTO_OK);
/* Deliver the ciphertext one byte at a time. */
size_t total = buf.len;
int got_ok = 0;
for (size_t i = 0; i < total; i++) {
buffer_t piece;
memset(&piece, 0, sizeof(piece));
std::memset(&piece, 0, sizeof(piece));
balloc(&piece, 4096);
piece.data[0] = buf.data[i];
piece.len = 1;
int rc = c->decrypt(&piece, &dec, 4096);
assert(rc == CRYPTO_OK || rc == CRYPTO_NEED_MORE);
SS_CHECK(rc == CRYPTO_OK || rc == CRYPTO_NEED_MORE);
if (rc == CRYPTO_OK) {
assert(piece.len == sizeof(addr));
assert(memcmp(piece.data, addr, sizeof(addr)) == 0);
SS_CHECK(piece.len == sizeof(addr));
SS_CHECK(std::memcmp(piece.data, addr, sizeof(addr)) == 0);
got_ok = 1;
}
bfree(&piece);
}
assert(got_ok);
(void)got_ok;
SS_CHECK(got_ok);
bfree(&buf);
c->ctx_release(&enc);
@@ -229,8 +222,7 @@ test_tcp_partial_delivery(const char *method)
}
/* A tampered ciphertext must fail authentication rather than pass through. */
static void
test_tcp_tamper_detection(const char *method)
void test_tcp_tamper_detection(const char *method)
{
static const uint8_t addr[] = { 0x01, 127, 0, 0, 1, 0x00, 0x50 };
crypto_t *c = make_crypto(method);
@@ -244,16 +236,16 @@ test_tcp_tamper_detection(const char *method)
dec.role = CRYPTO_ROLE_SERVER;
buffer_t buf;
memset(&buf, 0, sizeof(buf));
std::memset(&buf, 0, sizeof(buf));
balloc(&buf, 4096);
memcpy(buf.data, addr, sizeof(addr));
std::memcpy(buf.data, addr, sizeof(addr));
buf.len = sizeof(addr);
assert(c->encrypt(&buf, &enc, 4096) == CRYPTO_OK);
SS_CHECK(c->encrypt(&buf, &enc, 4096) == CRYPTO_OK);
/* Flip a bit inside the first header chunk (past the salt). */
buf.data[c->cipher->key_len + 2] ^= 0x01;
assert(c->decrypt(&buf, &dec, 4096) == CRYPTO_ERROR);
SS_CHECK(c->decrypt(&buf, &dec, 4096) == CRYPTO_ERROR);
bfree(&buf);
c->ctx_release(&enc);
@@ -262,8 +254,7 @@ test_tcp_tamper_detection(const char *method)
}
/* UDP packets must round-trip through the session interface. */
static void
test_udp_roundtrip(const char *method)
void test_udp_roundtrip(const char *method)
{
static const uint8_t pkt[] = {
0x01, 8, 8, 8, 8, 0x00, 0x35, /* 8.8.8.8:53 */
@@ -271,26 +262,26 @@ test_udp_roundtrip(const char *method)
};
crypto_t *c = make_crypto(method);
assert(c->encrypt_udp != NULL);
assert(c->decrypt_udp != NULL);
SS_CHECK(c->encrypt_udp != nullptr);
SS_CHECK(c->decrypt_udp != nullptr);
void *client_session = NULL;
void *server_session = NULL;
void *client_session = nullptr;
void *server_session = nullptr;
buffer_t buf;
memset(&buf, 0, sizeof(buf));
std::memset(&buf, 0, sizeof(buf));
balloc(&buf, 2048);
memcpy(buf.data, pkt, sizeof(pkt));
std::memcpy(buf.data, pkt, sizeof(pkt));
buf.len = sizeof(pkt);
aead_set_role(CRYPTO_ROLE_CLIENT);
assert(c->encrypt_udp(&buf, c->cipher, 2048, &client_session) == CRYPTO_OK);
assert(buf.len > sizeof(pkt));
SS_CHECK(c->encrypt_udp(&buf, c->cipher, 2048, &client_session) == CRYPTO_OK);
SS_CHECK(buf.len > sizeof(pkt));
aead_set_role(CRYPTO_ROLE_SERVER);
assert(c->decrypt_udp(&buf, c->cipher, 2048, &server_session) == CRYPTO_OK);
assert(buf.len == sizeof(pkt));
assert(memcmp(buf.data, pkt, sizeof(pkt)) == 0);
SS_CHECK(c->decrypt_udp(&buf, c->cipher, 2048, &server_session) == CRYPTO_OK);
SS_CHECK(buf.len == sizeof(pkt));
SS_CHECK(std::memcmp(buf.data, pkt, sizeof(pkt)) == 0);
bfree(&buf);
c->udp_session_release(client_session);
@@ -300,39 +291,38 @@ test_udp_roundtrip(const char *method)
}
/* A replayed UDP packet must be rejected by the sliding window filter. */
static void
test_udp_replay_rejected(const char *method)
void test_udp_replay_rejected(const char *method)
{
static const uint8_t pkt[] = { 0x01, 8, 8, 8, 8, 0x00, 0x35, 0x42 };
crypto_t *c = make_crypto(method);
void *client_session = NULL;
void *server_session = NULL;
void *client_session = nullptr;
void *server_session = nullptr;
buffer_t buf;
memset(&buf, 0, sizeof(buf));
std::memset(&buf, 0, sizeof(buf));
balloc(&buf, 2048);
memcpy(buf.data, pkt, sizeof(pkt));
std::memcpy(buf.data, pkt, sizeof(pkt));
buf.len = sizeof(pkt);
aead_set_role(CRYPTO_ROLE_CLIENT);
assert(c->encrypt_udp(&buf, c->cipher, 2048, &client_session) == CRYPTO_OK);
SS_CHECK(c->encrypt_udp(&buf, c->cipher, 2048, &client_session) == CRYPTO_OK);
/* Keep a copy of the wire packet so we can replay it verbatim. */
buffer_t copy;
memset(&copy, 0, sizeof(copy));
std::memset(&copy, 0, sizeof(copy));
balloc(&copy, 2048);
memcpy(copy.data, buf.data, buf.len);
std::memcpy(copy.data, buf.data, buf.len);
copy.len = buf.len;
aead_set_role(CRYPTO_ROLE_SERVER);
assert(c->decrypt_udp(&buf, c->cipher, 2048, &server_session) == CRYPTO_OK);
SS_CHECK(c->decrypt_udp(&buf, c->cipher, 2048, &server_session) == CRYPTO_OK);
/* A rejected packet must not publish a session into a fresh relay slot. */
void *rejected_session = NULL;
assert(c->decrypt_udp(&copy, c->cipher, 2048, &rejected_session) == CRYPTO_ERROR);
assert(rejected_session == NULL);
void *rejected_session = nullptr;
SS_CHECK(c->decrypt_udp(&copy, c->cipher, 2048, &rejected_session) == CRYPTO_ERROR);
SS_CHECK(rejected_session == nullptr);
/* the very same packet again must be refused */
assert(c->decrypt_udp(&copy, c->cipher, 2048, &server_session) == CRYPTO_ERROR);
SS_CHECK(c->decrypt_udp(&copy, c->cipher, 2048, &server_session) == CRYPTO_ERROR);
bfree(&buf);
bfree(&copy);
@@ -343,17 +333,16 @@ test_udp_replay_rejected(const char *method)
}
/* Encrypt one client packet into buf and hand it to the server's slot. */
static void
client_to_server(crypto_t *c, buffer_t *buf, const uint8_t *pkt, size_t len,
void client_to_server(crypto_t *c, buffer_t *buf, const uint8_t *pkt, size_t len,
void **client_session, void **server_session)
{
memcpy(buf->data, pkt, len);
std::memcpy(buf->data, pkt, len);
buf->len = len;
aead_set_role(CRYPTO_ROLE_CLIENT);
assert(c->encrypt_udp(buf, c->cipher, 2048, client_session) == CRYPTO_OK);
SS_CHECK(c->encrypt_udp(buf, c->cipher, 2048, client_session) == CRYPTO_OK);
aead_set_role(CRYPTO_ROLE_SERVER);
assert(c->decrypt_udp(buf, c->cipher, 2048, server_session) == CRYPTO_OK);
assert(buf->len == len && memcmp(buf->data, pkt, len) == 0);
SS_CHECK(c->decrypt_udp(buf, c->cipher, 2048, server_session) == CRYPTO_OK);
SS_CHECK(buf->len == len && std::memcmp(buf->data, pkt, len) == 0);
}
/*
@@ -361,34 +350,33 @@ client_to_server(crypto_t *c, buffer_t *buf, const uint8_t *pkt, size_t len,
* the table with fresh client session IDs used to free the session behind
* the relay's back, so the reply path encrypted with freed memory.
*/
static void
test_udp_session_survives_table_eviction(const char *method)
void test_udp_session_survives_table_eviction(const char *method)
{
static const uint8_t pkt[] = { 0x01, 8, 8, 8, 8, 0x00, 0x35, 0x42 };
crypto_t *c = make_crypto(method);
void *client_session = NULL;
void *server_session = NULL;
void *client_session = nullptr;
void *server_session = nullptr;
buffer_t buf;
memset(&buf, 0, sizeof(buf));
std::memset(&buf, 0, sizeof(buf));
balloc(&buf, 2048);
client_to_server(c, &buf, pkt, sizeof(pkt), &client_session, &server_session);
assert(server_session != NULL);
SS_CHECK(server_session != nullptr);
void *first = server_session;
void *shared_session = NULL;
void *shared_session = nullptr;
client_to_server(c, &buf, pkt, sizeof(pkt), &client_session, &shared_session);
assert(shared_session == first);
SS_CHECK(shared_session == first);
/* Reattaching the same slot must not acquire an extra reference. */
client_to_server(c, &buf, pkt, sizeof(pkt), &client_session, &server_session);
assert(server_session == first);
SS_CHECK(server_session == first);
/* Each new client session ID inserts a table entry; overflow evicts ours. */
for (int i = 0; i < AEAD_2022_UDP_SESSION_TABLE_CAP + 1; i++) {
void *other_client = NULL, *other_server = NULL;
void *other_client = nullptr, *other_server = nullptr;
client_to_server(c, &buf, pkt, sizeof(pkt), &other_client, &other_server);
assert(other_server != NULL && other_server != first);
SS_CHECK(other_server != nullptr && other_server != first);
c->udp_session_release(other_client);
c->udp_session_release(other_server);
}
@@ -397,18 +385,18 @@ test_udp_session_survives_table_eviction(const char *method)
c->udp_session_release(shared_session);
/* The reply must still encrypt with the evicted session and verify. */
memcpy(buf.data, pkt, sizeof(pkt));
std::memcpy(buf.data, pkt, sizeof(pkt));
buf.len = sizeof(pkt);
aead_set_role(CRYPTO_ROLE_SERVER);
assert(c->encrypt_udp(&buf, c->cipher, 2048, &server_session) == CRYPTO_OK);
assert(server_session == first);
SS_CHECK(c->encrypt_udp(&buf, c->cipher, 2048, &server_session) == CRYPTO_OK);
SS_CHECK(server_session == first);
aead_set_role(CRYPTO_ROLE_CLIENT);
assert(c->decrypt_udp(&buf, c->cipher, 2048, &client_session) == CRYPTO_OK);
assert(buf.len == sizeof(pkt) && memcmp(buf.data, pkt, sizeof(pkt)) == 0);
SS_CHECK(c->decrypt_udp(&buf, c->cipher, 2048, &client_session) == CRYPTO_OK);
SS_CHECK(buf.len == sizeof(pkt) && std::memcmp(buf.data, pkt, sizeof(pkt)) == 0);
/* The client's next packet re-creates a table entry and swaps the slot. */
client_to_server(c, &buf, pkt, sizeof(pkt), &client_session, &server_session);
assert(server_session != NULL && server_session != first);
SS_CHECK(server_session != nullptr && server_session != first);
/* Releasing the slot after eviction must neither leak nor double free. */
c->udp_session_release(server_session);
@@ -420,15 +408,14 @@ test_udp_session_survives_table_eviction(const char *method)
}
/* The 2022 ciphers must reject a PSK of the wrong size rather than pad it. */
static void
test_psk_parsing(void)
void test_psk_parsing()
{
uint8_t key[32];
/* Correct size decodes. */
assert(crypto_parse_psk(PSK32, key, 32) == 32);
SS_CHECK(crypto_parse_psk(PSK32, key, 32) == 32);
for (int i = 0; i < 32; i++)
assert(key[i] == 0);
SS_CHECK(key[i] == 0);
/*
* The standard alphabet ('+' and '/') and the URL-safe one ('-' and '_')
@@ -436,38 +423,40 @@ test_psk_parsing(void)
* tools using either convention.
*/
uint8_t k1[32], k2[32];
assert(crypto_parse_psk("//////////////////////////////////////////8=", k1, 32) == 32);
assert(crypto_parse_psk("__________________________________________8=", k2, 32) == 32);
assert(memcmp(k1, k2, 32) == 0);
SS_CHECK(crypto_parse_psk("//////////////////////////////////////////8=", k1, 32) == 32);
SS_CHECK(crypto_parse_psk("__________________________________________8=", k2, 32) == 32);
SS_CHECK(std::memcmp(k1, k2, 32) == 0);
for (int i = 0; i < 32; i++)
assert(k1[i] == 0xff);
SS_CHECK(k1[i] == 0xff);
/* A key with distinct bytes must decode to exactly those bytes. */
uint8_t k3[32];
assert(crypto_parse_psk("AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=",
SS_CHECK(crypto_parse_psk("AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=",
k3, 32) == 32);
for (int i = 0; i < 32; i++)
assert(k3[i] == i);
SS_CHECK(k3[i] == i);
}
int
main(void)
} // namespace
int main()
{
if (sodium_init() < 0)
if (sodium_init() < 0) {
return 1;
}
test_blake3_empty_hash_vector();
test_blake3_derive_key_known_vector();
test_psk_parsing();
SS_RUN(test_blake3_empty_hash_vector);
SS_RUN(test_blake3_derive_key_known_vector);
SS_RUN(test_psk_parsing);
for (size_t i = 0; i < sizeof(methods_2022) / sizeof(methods_2022[0]); i++) {
const char *m = methods_2022[i];
for (const char *m : methods_2022) {
test_tcp_request_roundtrip(m);
test_tcp_partial_delivery(m);
test_tcp_tamper_detection(m);
test_udp_roundtrip(m);
test_udp_replay_rejected(m);
test_udp_session_survives_table_eviction(m);
std::printf("ok %s\n", m);
}
return 0;