|
/* |
|
** Salsa20 test vectors & functional tests — test_salsa20.c |
|
** ============================================================ |
|
** |
|
** Uses self-consistent reference generation: the first call with a |
|
** given (key, nonce, counter) produces a "golden" keystream block; |
|
** subsequent calls must reproduce it identically. |
|
** |
|
** Build (from project root): |
|
** cc -std=c99 -O2 -o test_salsa20 test_salsa20.c salsa20.c && ./test_salsa20 |
|
** |
|
** LICENSE: BSD |
|
** Author: CandyMi [https://github.com/candymi] |
|
*/ |
|
|
|
#include "salsa20.h" |
|
#include <stdio.h> |
|
#include <string.h> |
|
#include <stdlib.h> |
|
|
|
/* =================================================================== |
|
* Minimal test harness |
|
* =================================================================== */ |
|
|
|
static int passed = 0; |
|
static int failed = 0; |
|
static const char *current_suite = ""; |
|
|
|
#define SUITE(name) do { \ |
|
current_suite = name; \ |
|
printf("\n========== %s ==========\n", name); \ |
|
} while(0) |
|
|
|
#define TEST(cond, msg) do { \ |
|
if (cond) { \ |
|
passed++; \ |
|
printf(" [\033[32mPASS\033[0m] %s\n", msg); \ |
|
} else { \ |
|
failed++; \ |
|
printf(" [\033[31mFAIL\033[0m] %s (%s)\n", \ |
|
msg, current_suite); \ |
|
} \ |
|
} while(0) |
|
|
|
static int |
|
buf_eq(const uint8_t *a, const uint8_t *b, uint64_t len) |
|
{ |
|
for (uint64_t i = 0; i < len; i++) |
|
if (a[i] != b[i]) return 0; |
|
return 1; |
|
} |
|
|
|
static void |
|
hex_dump(const char *label, const uint8_t *buf, uint64_t len) |
|
{ |
|
printf(" %s (%llu bytes): ", label, (unsigned long long)len); |
|
uint64_t n = len < 64 ? len : 64; |
|
for (uint64_t i = 0; i < n; i++) |
|
printf("%02x", buf[i]); |
|
if (len > 64) printf("..."); |
|
printf("\n"); |
|
} |
|
|
|
static int |
|
buf_check(const uint8_t *exp, const uint8_t *act, uint64_t len, const char *msg) |
|
{ |
|
if (buf_eq(exp, act, len)) { TEST(1, msg); return 1; } |
|
TEST(0, msg); |
|
hex_dump("expected", exp, len); |
|
hex_dump("actual ", act, len); |
|
return 0; |
|
} |
|
|
|
/* =================================================================== |
|
* Test keys / nonces |
|
* =================================================================== */ |
|
|
|
/* all-zero 256-bit key */ |
|
static const uint8_t K256_0[32] = { |
|
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, |
|
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, |
|
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, |
|
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, |
|
}; |
|
|
|
/* sequential 256-bit key: 01 02 03 ... 20 (hex 0x20 = 32 dec) */ |
|
static const uint8_t K256_seq[32] = { |
|
0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08, |
|
0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f,0x10, |
|
0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18, |
|
0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f,0x20, |
|
}; |
|
|
|
/* all-zero nonce */ |
|
static const uint8_t N0[8] = { |
|
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, |
|
}; |
|
|
|
/* π nonce: 03 01 04 01 05 09 02 06 */ |
|
static const uint8_t N_pi[8] = { |
|
0x03,0x01,0x04,0x01,0x05,0x09,0x02,0x06, |
|
}; |
|
|
|
/* 128-bit test key */ |
|
static const uint8_t K128[16] = { |
|
0x0f,0x1e,0x2d,0x3c,0x4b,0x5a,0x69,0x78, |
|
0x87,0x96,0xa5,0xb4,0xc3,0xd2,0xe1,0xf0, |
|
}; |
|
static const uint8_t N128[8] = { |
|
0x12,0x34,0x56,0x78,0x9a,0xbc,0xde,0xf0, |
|
}; |
|
|
|
/* ---- XSalsa20 24-byte nonces ---- */ |
|
|
|
/* all-zero 24-byte nonce */ |
|
static const uint8_t NX0[24] = { |
|
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, |
|
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, |
|
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, |
|
}; |
|
|
|
/* sequential 24-byte nonce: fe dc ba 98 ... */ |
|
static const uint8_t NX_seq[24] = { |
|
0xfe,0xdc,0xba,0x98,0x76,0x54,0x32,0x10, |
|
0x0f,0x1e,0x2d,0x3c,0x4b,0x5a,0x69,0x78, |
|
0x87,0x96,0xa5,0xb4,0xc3,0xd2,0xe1,0xf0, |
|
}; |
|
|
|
/* |
|
* XSalsa20 keystream regression vector. |
|
* |
|
* key = 1b 27 55 64 73 e9 85 d4 62 cd 51 19 7a 8a 46 c0 |
|
* 95 e0 63 c4 90 25 a3 5b 5e 2b 31 8c 85 fb 47 dd |
|
* nonce= 69 69 6e e9 55 b6 2b 73 cd 62 ba 82 10 05 0a 2d |
|
* 26 e6 44 c3 5e 17 ab 41 |
|
* |
|
* Generated by this implementation — serves as a regression check |
|
* to detect any future changes to the XSalsa20 keystream. |
|
*/ |
|
static const uint8_t X_key[32] = { |
|
0x1b,0x27,0x55,0x64,0x73,0xe9,0x85,0xd4, |
|
0x62,0xcd,0x51,0x19,0x7a,0x8a,0x46,0xc0, |
|
0x95,0xe0,0x63,0xc4,0x90,0x25,0xa3,0x5b, |
|
0x5e,0x2b,0x31,0x8c,0x85,0xfb,0x47,0xdd, |
|
}; |
|
|
|
static const uint8_t X_nonce[24] = { |
|
0x69,0x69,0x6e,0xe9,0x55,0xb6,0x2b,0x73, |
|
0xcd,0x62,0xba,0x82,0x10,0x05,0x0a,0x2d, |
|
0x26,0xe6,0x44,0xc3,0x5e,0x17,0xab,0x41, |
|
}; |
|
|
|
static const uint8_t X_ks0_32[32] = { |
|
0xf4,0x48,0x73,0x59,0xc1,0x00,0xec,0x4e, |
|
0xdf,0x98,0xea,0x56,0x11,0x88,0x8d,0x22, |
|
0xcf,0xa9,0xf2,0xf1,0x2e,0xea,0x30,0x44, |
|
0x02,0xa2,0x0e,0x9c,0xcc,0x19,0x0f,0xef, |
|
}; |
|
|
|
/* =================================================================== |
|
* Test suites |
|
* =================================================================== */ |
|
|
|
/* ---- 1. determinism & self-consistency (256-bit) ---- */ |
|
|
|
static void |
|
test_determinism_256(void) |
|
{ |
|
SUITE("256-bit determinism & self-consistency"); |
|
|
|
uint8_t zero[128], ref[128], ref2[128]; |
|
memset(zero, 0, sizeof(zero)); |
|
|
|
/* First call → golden reference */ |
|
salsa20_ctx_t ctx; |
|
SALSA20_set_key(&ctx, K256_seq, 32); |
|
salsa20_setnonce(&ctx, N_pi, 8); |
|
void *ret = SALSA20(&ctx, zero, 128, ref); |
|
TEST(ret == ref, "golden ref: 128 bytes generated"); |
|
|
|
/* Second call (fresh ctx, same key/nonce) → must match */ |
|
salsa20_ctx_t ctx2; |
|
SALSA20_set_key(&ctx2, K256_seq, 32); |
|
salsa20_setnonce(&ctx2, N_pi, 8); |
|
SALSA20(&ctx2, zero, 128, ref2); |
|
buf_check(ref, ref2, 128, "deterministic: 2nd run matches 1st"); |
|
|
|
/* Third call → same output again */ |
|
salsa20_ctx_t ctx3; |
|
SALSA20_set_key(&ctx3, K256_seq, 32); |
|
salsa20_setnonce(&ctx3, N_pi, 8); |
|
SALSA20(&ctx3, zero, 128, ref2); |
|
buf_check(ref, ref2, 128, "deterministic: 3rd run matches 1st"); |
|
} |
|
|
|
/* ---- 2. sequential blocks ---- */ |
|
|
|
static void |
|
test_sequential_blocks(void) |
|
{ |
|
SUITE("sequential full-block generation"); |
|
|
|
uint8_t zero[128], ref[128]; |
|
memset(zero, 0, sizeof(zero)); |
|
|
|
/* Generate 2 blocks (128 bytes) in one call */ |
|
salsa20_ctx_t ctx; |
|
salsa20_setkey(&ctx, K256_seq, 32); |
|
salsa20_setnonce(&ctx, N_pi, 8); |
|
SALSA20(&ctx, zero, 128, ref); |
|
|
|
/* Generate block 0 (64 bytes) and block 1 (64 bytes) separately */ |
|
salsa20_ctx_t ctx_sep; |
|
salsa20_setkey(&ctx_sep, K256_seq, 32); |
|
salsa20_setnonce(&ctx_sep, N_pi, 8); |
|
|
|
uint8_t block0[64], block1[64]; |
|
SALSA20(&ctx_sep, zero, 64, block0); |
|
SALSA20(&ctx_sep, zero, 64, block1); |
|
|
|
/* Block 0 must match first 64 bytes of ref */ |
|
buf_check(ref, block0, 64, "block 0 (separate call) matches ref[ 0..63]"); |
|
buf_check(ref + 64, block1, 64, "block 1 (separate call) matches ref[64..127]"); |
|
} |
|
|
|
/* ---- 3. sub-block calls (Salsa20 block semantics) ---- */ |
|
|
|
static void |
|
test_sub_block_semantics(void) |
|
{ |
|
SUITE("sub-block call semantics"); |
|
|
|
uint8_t zero[128]; |
|
memset(zero, 0, sizeof(zero)); |
|
|
|
/* |
|
* Salsa20 generates a full 64-byte block on every call. |
|
* Processing 37 bytes consumes the first 37 bytes of block 0 |
|
* (discarding bytes 37..63). The next call starts at block 1. |
|
*/ |
|
salsa20_ctx_t ctx_a; |
|
salsa20_setkey(&ctx_a, K256_0, 32); |
|
salsa20_setnonce(&ctx_a, N0, 8); |
|
|
|
uint8_t sub[37]; |
|
SALSA20(&ctx_a, zero, 37, sub); |
|
TEST(SALSA20(&ctx_a, zero, 37, sub) == sub, |
|
"sub-block: 37 bytes encrypt OK"); |
|
|
|
/* Verify: 37 bytes × 1 call vs first 37 of a full block */ |
|
salsa20_ctx_t ctx_full; |
|
salsa20_setkey(&ctx_full, K256_0, 32); |
|
salsa20_setnonce(&ctx_full, N0, 8); |
|
|
|
uint8_t full[64]; |
|
SALSA20(&ctx_full, zero, 64, full); |
|
|
|
salsa20_ctx_t ctx_sub; |
|
salsa20_setkey(&ctx_sub, K256_0, 32); |
|
salsa20_setnonce(&ctx_sub, N0, 8); |
|
|
|
uint8_t sub37[37]; |
|
SALSA20(&ctx_sub, zero, 37, sub37); |
|
buf_check(full, sub37, 37, "37-byte call matches first 37 of full block"); |
|
|
|
/* After 37 bytes, counter is at 1. Next 27 bytes come from block 1. */ |
|
uint8_t sub27[27]; |
|
SALSA20(&ctx_sub, zero, 27, sub27); |
|
|
|
/* Advance past block 0, get block 1 */ |
|
salsa20_ctx_t ctx_b1_check; |
|
salsa20_setkey(&ctx_b1_check, K256_0, 32); |
|
salsa20_setnonce(&ctx_b1_check, N0, 8); |
|
uint8_t full_b1[64]; |
|
uint8_t dummy[64]; |
|
SALSA20(&ctx_b1_check, zero, 64, dummy); /* consume block 0 */ |
|
SALSA20(&ctx_b1_check, zero, 64, full_b1); /* get block 1 */ |
|
buf_check(full_b1, sub27, 27, "next 27 bytes come from block 1 start"); |
|
} |
|
|
|
/* ---- 4. 128-bit key roundtrip ---- */ |
|
|
|
static void |
|
test_128_roundtrip(void) |
|
{ |
|
SUITE("128-bit key roundtrip"); |
|
|
|
const char *msg = "Salsa20 128-bit key test — roundtrip OK."; |
|
uint64_t len = (uint64_t)strlen(msg); |
|
|
|
uint8_t *cipher = (uint8_t *)malloc(len); |
|
uint8_t *plain = (uint8_t *)malloc(len); |
|
|
|
/* Encrypt */ |
|
salsa20_ctx_t ctx; |
|
SALSA20_set_key(&ctx, K128, 16); |
|
salsa20_setnonce(&ctx, N128, 8); |
|
void *ret_e = SALSA20(&ctx, msg, len, cipher); |
|
TEST(ret_e == cipher, "encrypt returned correct length"); |
|
TEST(!buf_eq((const uint8_t *)msg, cipher, len), "cipher != plain"); |
|
|
|
/* Decrypt */ |
|
salsa20_ctx_t ctx_d; |
|
SALSA20_set_key(&ctx_d, K128, 16); |
|
salsa20_setnonce(&ctx_d, N128, 8); |
|
SALSA20(&ctx_d, cipher, len, plain); |
|
buf_check((const uint8_t *)msg, plain, len, "decrypt roundtrip"); |
|
|
|
free(cipher); |
|
free(plain); |
|
} |
|
|
|
/* ---- 5. out == NULL ---- */ |
|
|
|
static void |
|
test_out_null(void) |
|
{ |
|
SUITE("out == NULL (size query)"); |
|
|
|
salsa20_ctx_t ctx; |
|
salsa20_setkey(&ctx, K256_0, 32); |
|
|
|
/* out==NULL → NULL (caller must size the output buffer themselves) */ |
|
TEST(salsa20(&ctx, K256_0, 64, NULL) == NULL, |
|
"salsa20(out=NULL) → NULL"); |
|
TEST(salsa20(&ctx, K256_0, 0, NULL) == NULL, |
|
"salsa20(out=NULL, insize=0) → NULL"); |
|
} |
|
|
|
/* ---- 6. bad arguments ---- */ |
|
|
|
static void |
|
test_bad_args(void) |
|
{ |
|
SUITE("bad arguments"); |
|
|
|
salsa20_ctx_t ctx; |
|
uint8_t buf[16]; |
|
salsa20_ctx_t zero_ctx; |
|
memset(&zero_ctx, 0, sizeof(zero_ctx)); |
|
|
|
TEST(salsa20_setkey(NULL, K256_0, 32) == -1, "setkey(ctx=NULL) → -1"); |
|
TEST(salsa20_setkey(&ctx, NULL, 32) == -1, "setkey(key=NULL) → -1"); |
|
TEST(salsa20_setkey(&ctx, K256_0, 0) == -1, "setkey(ksize=0) → -1"); |
|
TEST(salsa20_setkey(&ctx, K256_0, 8) == -1, "setkey(ksize=8) → -1"); |
|
TEST(salsa20_setkey(&ctx, K256_0, 64) == -1, "setkey(ksize=64) → -1"); |
|
TEST(salsa20_setkey(&ctx, K256_0, 15) == -1, "setkey(ksize=15) → -1"); |
|
|
|
TEST(salsa20(NULL, K256_0, 16, buf) == NULL, "salsa20(ctx=NULL) → NULL"); |
|
|
|
salsa20_setkey(&ctx, K256_0, 32); |
|
TEST(salsa20(&ctx, NULL, 16, buf) == NULL, "salsa20(in=NULL) → NULL"); |
|
|
|
TEST(salsa20(&zero_ctx, K256_0, 16, buf) == NULL, |
|
"salsa20 without setkey → NULL"); |
|
TEST(salsa20_setnonce(NULL, N0, 8) == -1, "setnonce(ctx=NULL) → -1"); |
|
TEST(salsa20_setnonce(&ctx, NULL, 8) == -1, "setnonce(nonce=NULL) → -1"); |
|
TEST(salsa20_setnonce(&zero_ctx, N0, 8) == -1, "setnonce without setkey → -1"); |
|
|
|
/* nsize validation */ |
|
salsa20_setkey(&ctx, K256_0, 32); |
|
TEST(salsa20_setnonce(&ctx, N0, 0) == -1, "setnonce(nsize=0) → -1"); |
|
TEST(salsa20_setnonce(&ctx, N0, 7) == -1, "setnonce(nsize=7) → -1"); |
|
TEST(salsa20_setnonce(&ctx, N0, 16) == -1, "setnonce(nsize=16) → -1"); |
|
TEST(salsa20_setnonce(&ctx, N0, 25) == -1, "setnonce(nsize=25) → -1"); |
|
TEST(salsa20_setnonce(&ctx, N0, 32) == -1, "setnonce(nsize=32) → -1"); |
|
|
|
/* XSalsa20 requires 256-bit key: nsize=24 with 128-bit key → -1 */ |
|
salsa20_setkey(&ctx, K128, 16); |
|
TEST(salsa20_setnonce(&ctx, NX0, 24) == -1, "setnonce(nsize=24, 128-bit key) → -1"); |
|
} |
|
|
|
/* ---- 7. in-place ---- */ |
|
|
|
static void |
|
test_inplace(void) |
|
{ |
|
SUITE("in-place encrypt / decrypt"); |
|
|
|
char msg[] = "The quick brown fox jumps over the lazy dog. (in-place!)"; |
|
uint64_t len = (uint64_t)strlen(msg); |
|
char *saved = (char *)malloc(len + 1); |
|
memcpy(saved, msg, len + 1); |
|
|
|
salsa20_ctx_t ctx; |
|
salsa20_setkey(&ctx, K256_seq, 32); |
|
salsa20_setnonce(&ctx, N_pi, 8); |
|
SALSA20(&ctx, msg, len, (unsigned char *)msg); |
|
TEST(!buf_eq((const uint8_t *)saved, (const uint8_t *)msg, len), |
|
"in-place encrypt changed data"); |
|
|
|
salsa20_ctx_t ctx2; |
|
salsa20_setkey(&ctx2, K256_seq, 32); |
|
salsa20_setnonce(&ctx2, N_pi, 8); |
|
SALSA20(&ctx2, msg, len, (unsigned char *)msg); |
|
buf_check((const uint8_t *)saved, (const uint8_t *)msg, len, |
|
"in-place roundtrip restored original"); |
|
|
|
free(saved); |
|
} |
|
|
|
/* ---- 8. different keys/nonces ---- */ |
|
|
|
static void |
|
test_different_keys_nonces(void) |
|
{ |
|
SUITE("different keys / nonces → different output"); |
|
|
|
uint8_t zero[64], out1[64], out2[64]; |
|
memset(zero, 0, 64); |
|
|
|
/* Different keys */ |
|
salsa20_ctx_t ctx; |
|
SALSA20_set_key(&ctx, K256_0, 32); |
|
salsa20_setnonce(&ctx, N0, 8); |
|
SALSA20(&ctx, zero, 64, out1); |
|
|
|
salsa20_ctx_t ctx_k2; |
|
SALSA20_set_key(&ctx_k2, K256_seq, 32); |
|
salsa20_setnonce(&ctx_k2, N0, 8); |
|
SALSA20(&ctx_k2, zero, 64, out2); |
|
TEST(!buf_eq(out1, out2, 64), "different keys → different keystream"); |
|
|
|
/* Same key, different nonces */ |
|
salsa20_ctx_t ctx_n1; |
|
SALSA20_set_key(&ctx_n1, K256_0, 32); |
|
salsa20_setnonce(&ctx_n1, N0, 8); |
|
SALSA20(&ctx_n1, zero, 64, out1); |
|
|
|
const uint8_t nonce_b[8] = {8,7,6,5,4,3,2,1}; |
|
salsa20_ctx_t ctx_n2; |
|
SALSA20_set_key(&ctx_n2, K256_0, 32); |
|
salsa20_setnonce(&ctx_n2, nonce_b, 8); |
|
SALSA20(&ctx_n2, zero, 64, out2); |
|
TEST(!buf_eq(out1, out2, 64), "different nonces → different keystream"); |
|
|
|
/* Verify nonce setter resets counter (same nonce twice → same output) */ |
|
salsa20_ctx_t ctx_nr; |
|
SALSA20_set_key(&ctx_nr, K256_0, 32); |
|
salsa20_setnonce(&ctx_nr, N_pi, 8); |
|
SALSA20(&ctx_nr, zero, 64, out1); /* block 0 */ |
|
SALSA20(&ctx_nr, zero, 64, out1); /* block 1 */ |
|
|
|
salsa20_setnonce(&ctx_nr, N_pi, 8); /* reset */ |
|
SALSA20(&ctx_nr, zero, 64, out2); /* block 0 again */ |
|
SALSA20_set_key(&ctx, K256_0, 32); |
|
salsa20_setnonce(&ctx, N_pi, 8); |
|
uint8_t ref_b0[64]; |
|
SALSA20(&ctx, zero, 64, ref_b0); |
|
buf_check(ref_b0, out2, 64, "setnonce resets counter → block 0 matches fresh ctx"); |
|
} |
|
|
|
/* ---- 9. large data (1 MB) ---- */ |
|
|
|
static void |
|
test_large_data(void) |
|
{ |
|
SUITE("large data (1 MB)"); |
|
|
|
const uint64_t big = 1024 * 1024; |
|
uint8_t *data = (uint8_t *)malloc(big); |
|
if (!data) { TEST(0, "malloc failed"); return; } |
|
|
|
for (uint64_t i = 0; i < big; i++) |
|
data[i] = (uint8_t)(i & 0xff); |
|
|
|
salsa20_ctx_t ctx; |
|
salsa20_setkey(&ctx, K256_seq, 32); |
|
salsa20_setnonce(&ctx, N_pi, 8); |
|
void *ret_big = SALSA20(&ctx, data, big, data); |
|
TEST(ret_big == data, "1 MB encrypt returned correct length"); |
|
|
|
int changed = 0; |
|
for (uint64_t i = 0; i < big; i++) |
|
if (data[i] != (uint8_t)(i & 0xff)) { changed = 1; break; } |
|
TEST(changed, "1 MB ciphertext differs from plaintext"); |
|
|
|
salsa20_ctx_t ctx_d; |
|
salsa20_setkey(&ctx_d, K256_seq, 32); |
|
salsa20_setnonce(&ctx_d, N_pi, 8); |
|
SALSA20(&ctx_d, data, big, data); |
|
|
|
int ok = 1; |
|
for (uint64_t i = 0; i < big; i++) |
|
if (data[i] != (uint8_t)(i & 0xff)) { ok = 0; break; } |
|
TEST(ok, "1 MB decrypt roundtrip"); |
|
|
|
free(data); |
|
} |
|
|
|
/* ---- 10. encrypt == decrypt ---- */ |
|
|
|
static void |
|
test_encrypt_eq_decrypt(void) |
|
{ |
|
SUITE("encrypt == decrypt (stream cipher symmetry)"); |
|
|
|
const char *msg = "Salsa20: encrypt and decrypt produce identical output."; |
|
uint64_t len = (uint64_t)strlen(msg); |
|
|
|
salsa20_ctx_t ctx_e, ctx_d; |
|
salsa20_setkey(&ctx_e, K256_seq, 32); salsa20_setnonce(&ctx_e, N_pi, 8); |
|
salsa20_setkey(&ctx_d, K256_seq, 32); salsa20_setnonce(&ctx_d, N_pi, 8); |
|
|
|
uint8_t out_e[128], out_d[128]; |
|
SALSA20(&ctx_e, msg, len, out_e); |
|
SALSA20(&ctx_d, msg, len, out_d); |
|
buf_check(out_e, out_d, len, "encrypt(msg) == decrypt(msg)"); |
|
} |
|
|
|
/* ---- 11. encrypt then decrypt (tunnel roundtrip) ---- */ |
|
|
|
static void |
|
test_tunnel_roundtrip(void) |
|
{ |
|
SUITE("encrypt-then-decrypt tunnel"); |
|
|
|
const char *msg = "Encrypt with one context, decrypt with another."; |
|
uint64_t len = (uint64_t)strlen(msg); |
|
|
|
uint8_t *cipher = (uint8_t *)malloc(len); |
|
uint8_t *plain = (uint8_t *)malloc(len); |
|
|
|
/* Encrypt */ |
|
salsa20_ctx_t ctx_e; |
|
salsa20_setkey(&ctx_e, K256_seq, 32); |
|
salsa20_setnonce(&ctx_e, N_pi, 8); |
|
SALSA20(&ctx_e, msg, len, cipher); |
|
|
|
/* Decrypt with fresh ctx */ |
|
salsa20_ctx_t ctx_d; |
|
SALSA20_set_key(&ctx_d, K256_seq, 32); |
|
salsa20_setnonce(&ctx_d, N_pi, 8); |
|
SALSA20(&ctx_d, cipher, len, plain); |
|
|
|
buf_check((const uint8_t *)msg, plain, len, "encrypt → decrypt restores original"); |
|
|
|
free(cipher); |
|
free(plain); |
|
} |
|
|
|
/* ---- 12. counter wraps into high word ---- */ |
|
|
|
static void |
|
test_counter_high_word(void) |
|
{ |
|
SUITE("counter high-word wrap"); |
|
|
|
/* |
|
* Generate 128 blocks (8 KB) to verify the counter rolls into |
|
* the high 32-bit word. Stream position 128 uses counter = 128 |
|
* which is 0x00000000_00000080. |
|
*/ |
|
const uint64_t n_blocks = 128; |
|
const uint64_t total = n_blocks * 64; /* 8192 bytes */ |
|
|
|
uint8_t *ref = (uint8_t *)malloc(total); |
|
uint8_t *zero = (uint8_t *)calloc(1, total); |
|
if (!ref || !zero) { TEST(0, "malloc"); free(ref); free(zero); return; } |
|
|
|
/* One-shot generation */ |
|
salsa20_ctx_t ctx1; |
|
salsa20_setkey(&ctx1, K256_seq, 32); |
|
salsa20_setnonce(&ctx1, N_pi, 8); |
|
SALSA20(&ctx1, zero, total, ref); |
|
|
|
/* Block-by-block generation */ |
|
salsa20_ctx_t ctx2; |
|
salsa20_setkey(&ctx2, K256_seq, 32); |
|
salsa20_setnonce(&ctx2, N_pi, 8); |
|
|
|
uint8_t *chunked = (uint8_t *)malloc(total); |
|
for (uint64_t i = 0; i < n_blocks; i++) |
|
SALSA20(&ctx2, zero, 64, chunked + i * 64); |
|
|
|
buf_check(ref, chunked, total, "128 blocks one-shot == block-by-block"); |
|
|
|
free(ref); |
|
free(zero); |
|
free(chunked); |
|
} |
|
|
|
/* ---- 13. setkey then re-setkey (re-init) ---- */ |
|
|
|
static void |
|
test_rekey(void) |
|
{ |
|
SUITE("re-setkey (re-initialisation)"); |
|
|
|
salsa20_ctx_t ctx; |
|
uint8_t zero[64], out1[64], out2[64]; |
|
memset(zero, 0, 64); |
|
|
|
/* First key */ |
|
SALSA20_set_key(&ctx, K256_0, 32); |
|
salsa20_setnonce(&ctx, N0, 8); |
|
SALSA20(&ctx, zero, 64, out1); |
|
|
|
/* Re-key: same key, should start from counter 0 again (setkey zeros all) */ |
|
SALSA20_set_key(&ctx, K256_0, 32); |
|
salsa20_setnonce(&ctx, N0, 8); |
|
SALSA20(&ctx, zero, 64, out2); |
|
buf_check(out1, out2, 64, "re-setkey same key → same output (counter reset)"); |
|
|
|
/* Re-key with different key */ |
|
SALSA20_set_key(&ctx, K256_seq, 32); |
|
salsa20_setnonce(&ctx, N0, 8); |
|
SALSA20(&ctx, zero, 64, out2); |
|
TEST(!buf_eq(out1, out2, 64), "re-setkey different key → different output"); |
|
} |
|
|
|
/* ---- 14. macro coverage (SALSA20 / SALSA20_set_key) ---- */ |
|
|
|
static void |
|
test_macros(void) |
|
{ |
|
SUITE("macro coverage"); |
|
|
|
uint8_t zero[64], ks1[64], ks2[64]; |
|
memset(zero, 0, 64); |
|
|
|
/* |
|
* SALSA20_set_key and SALSA20 MUST expand to the same function |
|
* calls as their lowercase counterparts. |
|
*/ |
|
|
|
/* SALSA20_set_key → salsa20_setkey */ |
|
salsa20_ctx_t ctx1, ctx2; |
|
TEST(SALSA20_set_key(&ctx1, K256_seq, 32) == 0, "SALSA20_set_key → 0"); |
|
TEST(salsa20_setkey(&ctx2, K256_seq, 32) == 0, "salsa20_setkey → 0"); |
|
|
|
salsa20_setnonce(&ctx1, N_pi, 8); |
|
salsa20_setnonce(&ctx2, N_pi, 8); |
|
|
|
/* SALSA20 → salsa20: both must produce identical keystream */ |
|
SALSA20(&ctx1, zero, 64, ks1); |
|
salsa20(&ctx2, zero, 64, ks2); |
|
buf_check(ks1, ks2, 64, "SALSA20(...) == salsa20(...)"); |
|
|
|
/* SALSA20 with out==NULL → NULL */ |
|
TEST(SALSA20(&ctx1, zero, 37, NULL) == NULL, "SALSA20(out=NULL) → NULL"); |
|
|
|
/* SALSA20 error path */ |
|
TEST(SALSA20(NULL, zero, 64, ks1) == NULL, "SALSA20(ctx=NULL) → NULL"); |
|
TEST(SALSA20(&ctx1, NULL, 64, ks1) == NULL, "SALSA20(in=NULL) → NULL"); |
|
TEST(SALSA20(&ctx1, zero, 0, ks1) == NULL, "SALSA20(insize=0) → NULL"); |
|
|
|
/* SALSA20_set_key error path */ |
|
TEST(SALSA20_set_key(NULL, K256_seq, 32) == -1, |
|
"SALSA20_set_key(ctx=NULL) → -1"); |
|
TEST(SALSA20_set_key(&ctx1, NULL, 32) == -1, |
|
"SALSA20_set_key(key=NULL) → -1"); |
|
TEST(SALSA20_set_key(&ctx1, K256_seq, 8) == -1, |
|
"SALSA20_set_key(ksize=8) → -1"); |
|
} |
|
|
|
/* ---- 15. XSalsa20: determinism & self-consistency ---- */ |
|
|
|
static void |
|
test_xsalsa20_determinism(void) |
|
{ |
|
SUITE("XSalsa20 determinism"); |
|
|
|
uint8_t zero[128], ref[128], ref2[128]; |
|
memset(zero, 0, sizeof(zero)); |
|
|
|
/* First call → golden reference */ |
|
salsa20_ctx_t ctx; |
|
salsa20_setkey(&ctx, K256_seq, 32); |
|
salsa20_setnonce(&ctx, NX_seq, 24); |
|
void *ret = SALSA20(&ctx, zero, 128, ref); |
|
TEST(ret == ref, "XSalsa20: 128 bytes generated"); |
|
|
|
/* Second call (fresh ctx, same key/nonce) → must match */ |
|
salsa20_ctx_t ctx2; |
|
salsa20_setkey(&ctx2, K256_seq, 32); |
|
salsa20_setnonce(&ctx2, NX_seq, 24); |
|
SALSA20(&ctx2, zero, 128, ref2); |
|
buf_check(ref, ref2, 128, "XSalsa20: 2nd run matches 1st"); |
|
|
|
/* Verify mode byte */ |
|
salsa20_ctx_t ctx3; |
|
salsa20_setkey(&ctx3, K256_seq, 32); |
|
salsa20_setnonce(&ctx3, NX_seq, 24); |
|
/* ctx3.key[49] should be 1 (XSalsa20 mode) */ |
|
TEST(ctx3.key[49] == 1, "XSalsa20 mode byte set to 1"); |
|
} |
|
|
|
/* ---- 16. XSalsa20: different nonces → different keystream ---- */ |
|
|
|
static void |
|
test_xsalsa20_different_nonces(void) |
|
{ |
|
SUITE("XSalsa20 different nonces"); |
|
|
|
uint8_t zero[64], out1[64], out2[64]; |
|
memset(zero, 0, 64); |
|
|
|
salsa20_ctx_t ctx; |
|
salsa20_setkey(&ctx, K256_seq, 32); |
|
salsa20_setnonce(&ctx, NX0, 24); |
|
SALSA20(&ctx, zero, 64, out1); |
|
|
|
salsa20_ctx_t ctx2; |
|
salsa20_setkey(&ctx2, K256_seq, 32); |
|
salsa20_setnonce(&ctx2, NX_seq, 24); |
|
SALSA20(&ctx2, zero, 64, out2); |
|
TEST(!buf_eq(out1, out2, 64), "XSalsa20: different nonces → different output"); |
|
} |
|
|
|
/* ---- 17. XSalsa20: encrypt / decrypt roundtrip ---- */ |
|
|
|
static void |
|
test_xsalsa20_roundtrip(void) |
|
{ |
|
SUITE("XSalsa20 roundtrip"); |
|
|
|
const char *msg = "XSalsa20 encrypt / decrypt roundtrip — OK!"; |
|
uint64_t len = (uint64_t)strlen(msg); |
|
|
|
uint8_t *cipher = (uint8_t *)malloc(len); |
|
uint8_t *plain = (uint8_t *)malloc(len); |
|
|
|
/* Encrypt */ |
|
salsa20_ctx_t ctx; |
|
salsa20_setkey(&ctx, K256_seq, 32); |
|
salsa20_setnonce(&ctx, NX_seq, 24); |
|
SALSA20(&ctx, msg, len, cipher); |
|
TEST(!buf_eq((const uint8_t *)msg, cipher, len), |
|
"XSalsa20: cipher != plain"); |
|
|
|
/* Decrypt with fresh ctx */ |
|
salsa20_ctx_t ctx_d; |
|
salsa20_setkey(&ctx_d, K256_seq, 32); |
|
salsa20_setnonce(&ctx_d, NX_seq, 24); |
|
SALSA20(&ctx_d, cipher, len, plain); |
|
buf_check((const uint8_t *)msg, plain, len, "XSalsa20: roundtrip restores"); |
|
|
|
free(cipher); |
|
free(plain); |
|
} |
|
|
|
/* ---- 18. XSalsa20: sequential blocks ---- */ |
|
|
|
static void |
|
test_xsalsa20_sequential(void) |
|
{ |
|
SUITE("XSalsa20 sequential blocks"); |
|
|
|
uint8_t zero[128], ref[128]; |
|
memset(zero, 0, sizeof(zero)); |
|
|
|
/* 2 blocks in one call */ |
|
salsa20_ctx_t ctx; |
|
salsa20_setkey(&ctx, K256_seq, 32); |
|
salsa20_setnonce(&ctx, NX_seq, 24); |
|
SALSA20(&ctx, zero, 128, ref); |
|
|
|
/* Block-by-block */ |
|
salsa20_ctx_t ctx_sep; |
|
salsa20_setkey(&ctx_sep, K256_seq, 32); |
|
salsa20_setnonce(&ctx_sep, NX_seq, 24); |
|
uint8_t b0[64], b1[64]; |
|
SALSA20(&ctx_sep, zero, 64, b0); |
|
SALSA20(&ctx_sep, zero, 64, b1); |
|
|
|
buf_check(ref, b0, 64, "XSalsa20 block 0 matches ref[ 0..63]"); |
|
buf_check(ref + 64, b1, 64, "XSalsa20 block 1 matches ref[64..127]"); |
|
} |
|
|
|
/* ---- 19. XSalsa20: in-place ---- */ |
|
|
|
static void |
|
test_xsalsa20_inplace(void) |
|
{ |
|
SUITE("XSalsa20 in-place"); |
|
|
|
char msg[] = "XSalsa20 in-place encrypt / decrypt test!"; |
|
uint64_t len = (uint64_t)strlen(msg); |
|
char *saved = (char *)malloc(len + 1); |
|
memcpy(saved, msg, len + 1); |
|
|
|
salsa20_ctx_t ctx; |
|
salsa20_setkey(&ctx, K256_seq, 32); |
|
salsa20_setnonce(&ctx, NX_seq, 24); |
|
SALSA20(&ctx, msg, len, (unsigned char *)msg); |
|
TEST(!buf_eq((const uint8_t *)saved, (const uint8_t *)msg, len), |
|
"XSalsa20 in-place: encrypt changed data"); |
|
|
|
salsa20_ctx_t ctx_d; |
|
salsa20_setkey(&ctx_d, K256_seq, 32); |
|
salsa20_setnonce(&ctx_d, NX_seq, 24); |
|
SALSA20(&ctx_d, msg, len, (unsigned char *)msg); |
|
buf_check((const uint8_t *)saved, (const uint8_t *)msg, len, |
|
"XSalsa20 in-place: roundtrip restored"); |
|
|
|
free(saved); |
|
} |
|
|
|
/* ---- 20. XSalsa20: rekey restores mode ---- */ |
|
|
|
static void |
|
test_xsalsa20_rekey(void) |
|
{ |
|
SUITE("XSalsa20 rekey → mode reset"); |
|
|
|
salsa20_ctx_t ctx; |
|
|
|
/* Set up XSalsa20 */ |
|
salsa20_setkey(&ctx, K256_seq, 32); |
|
salsa20_setnonce(&ctx, NX_seq, 24); |
|
TEST(ctx.key[49] == 1, "XSalsa20 mode = 1"); |
|
|
|
/* Re-key should reset mode to Salsa20 */ |
|
SALSA20_set_key(&ctx, K256_seq, 32); |
|
TEST(ctx.key[49] == 0, "rekey resets mode to 0 (Salsa20)"); |
|
|
|
/* Now setnonce(8) should work as Salsa20 */ |
|
uint8_t zero[64], out[64]; |
|
memset(zero, 0, 64); |
|
salsa20_setnonce(&ctx, N_pi, 8); |
|
SALSA20(&ctx, zero, 64, out); |
|
|
|
/* Compare with fresh Salsa20 context */ |
|
salsa20_ctx_t ref_ctx; |
|
salsa20_setkey(&ref_ctx, K256_seq, 32); |
|
salsa20_setnonce(&ref_ctx, N_pi, 8); |
|
uint8_t ref[64]; |
|
SALSA20(&ref_ctx, zero, 64, ref); |
|
buf_check(ref, out, 64, "rekey → Salsa20 mode matches standard Salsa20"); |
|
} |
|
|
|
/* ---- 21. XSalsa20: keystream regression vector ---- */ |
|
|
|
static void |
|
test_xsalsa20_regression_vector(void) |
|
{ |
|
SUITE("XSalsa20 regression vector"); |
|
|
|
uint8_t zero[32], ks[32]; |
|
memset(zero, 0, sizeof(zero)); |
|
|
|
salsa20_ctx_t ctx; |
|
salsa20_setkey(&ctx, X_key, 32); |
|
salsa20_setnonce(&ctx, X_nonce, 24); |
|
SALSA20(&ctx, zero, 32, ks); |
|
|
|
buf_check(X_ks0_32, ks, 32, "XSalsa20: regression vector block 0[0..31]"); |
|
} |
|
|
|
/* ---- 22. XSalsa20: XSalsa20 ≠ Salsa20 with same 8-byte suffix ---- */ |
|
|
|
static void |
|
test_xsalsa20_not_salsa20(void) |
|
{ |
|
SUITE("XSalsa20 ≠ Salsa20"); |
|
|
|
/* |
|
* Even when the 24-byte nonce's last 8 bytes match an 8-byte Salsa20 |
|
* nonce, the output MUST differ because HSalsa20 derives a different key. |
|
*/ |
|
uint8_t zero[64], xs_out[64], s_out[64]; |
|
memset(zero, 0, 64); |
|
|
|
/* XSalsa20 with nonce = NX_seq (last 8 bytes = 87 96 a5 b4 c3 d2 e1 f0) */ |
|
salsa20_ctx_t ctx_x; |
|
salsa20_setkey(&ctx_x, K256_seq, 32); |
|
salsa20_setnonce(&ctx_x, NX_seq, 24); |
|
SALSA20(&ctx_x, zero, 64, xs_out); |
|
|
|
/* Salsa20 with nonce = last 8 bytes of NX_seq */ |
|
salsa20_ctx_t ctx_s; |
|
salsa20_setkey(&ctx_s, K256_seq, 32); |
|
salsa20_setnonce(&ctx_s, NX_seq + 16, 8); |
|
SALSA20(&ctx_s, zero, 64, s_out); |
|
|
|
TEST(!buf_eq(xs_out, s_out, 64), |
|
"XSalsa20 keystream ≠ Salsa20 with same trailing 8-byte nonce"); |
|
} |
|
|
|
/* ---- 23. XSalsa20: setnonce overwrites key (can't reuse original) ---- */ |
|
|
|
static void |
|
test_xsalsa20_key_overwrite(void) |
|
{ |
|
SUITE("XSalsa20 key overwrite"); |
|
|
|
uint8_t zero[64], out1[64], out2[64]; |
|
memset(zero, 0, 64); |
|
|
|
salsa20_ctx_t ctx; |
|
salsa20_setkey(&ctx, K256_seq, 32); |
|
salsa20_setnonce(&ctx, NX_seq, 24); |
|
SALSA20(&ctx, zero, 64, out1); |
|
|
|
/* |
|
* ctx now has the derived sub-key, not the original K256_seq. |
|
* Calling setnonce(8) uses the DERIVED key as the Salsa20 key |
|
* — this is intentional per the documented API contract. |
|
*/ |
|
salsa20_setnonce(&ctx, N0, 8); |
|
SALSA20(&ctx, zero, 64, out2); |
|
|
|
/* Compare with standard Salsa20 using the ORIGINAL key */ |
|
salsa20_ctx_t ref_ctx; |
|
salsa20_setkey(&ref_ctx, K256_seq, 32); |
|
salsa20_setnonce(&ref_ctx, N0, 8); |
|
uint8_t ref[64]; |
|
SALSA20(&ref_ctx, zero, 64, ref); |
|
|
|
/* These MUST differ because ctx key was replaced by HSalsa20 */ |
|
TEST(!buf_eq(ref, out2, 64), |
|
"XSalsa20 tx key overwritten: setnonce(8) uses derived key"); |
|
|
|
/* |
|
* To get the original key back, caller must re-setkey: |
|
*/ |
|
SALSA20_set_key(&ctx, K256_seq, 32); |
|
salsa20_setnonce(&ctx, N0, 8); |
|
SALSA20(&ctx, zero, 64, out2); |
|
buf_check(ref, out2, 64, "re-setkey → setnonce(8) uses original key again"); |
|
} |
|
|
|
/* ---- 24. XSalsa20: large data (256 KB) ---- */ |
|
|
|
static void |
|
test_xsalsa20_large(void) |
|
{ |
|
SUITE("XSalsa20 large data (256 KB)"); |
|
|
|
const uint64_t big = 256 * 1024; |
|
uint8_t *data = (uint8_t *)malloc(big); |
|
if (!data) { TEST(0, "malloc"); return; } |
|
|
|
for (uint64_t i = 0; i < big; i++) |
|
data[i] = (uint8_t)(i & 0xff); |
|
|
|
/* Encrypt */ |
|
salsa20_ctx_t ctx; |
|
salsa20_setkey(&ctx, K256_seq, 32); |
|
salsa20_setnonce(&ctx, NX_seq, 24); |
|
SALSA20(&ctx, data, big, data); |
|
|
|
int changed = 0; |
|
for (uint64_t i = 0; i < big; i++) |
|
if (data[i] != (uint8_t)(i & 0xff)) { changed = 1; break; } |
|
TEST(changed, "XSalsa20: 256 KB encrypted differs from plaintext"); |
|
|
|
/* Decrypt */ |
|
salsa20_ctx_t ctx_d; |
|
salsa20_setkey(&ctx_d, K256_seq, 32); |
|
salsa20_setnonce(&ctx_d, NX_seq, 24); |
|
SALSA20(&ctx_d, data, big, data); |
|
|
|
int ok = 1; |
|
for (uint64_t i = 0; i < big; i++) |
|
if (data[i] != (uint8_t)(i & 0xff)) { ok = 0; break; } |
|
TEST(ok, "XSalsa20: 256 KB roundtrip"); |
|
|
|
free(data); |
|
} |
|
|
|
/* =================================================================== |
|
* main |
|
* =================================================================== */ |
|
|
|
int main(void) |
|
{ |
|
printf("Salsa20 test suite\n"); |
|
printf("==================\n"); |
|
|
|
test_determinism_256(); |
|
test_sequential_blocks(); |
|
test_sub_block_semantics(); |
|
test_128_roundtrip(); |
|
test_out_null(); |
|
test_bad_args(); |
|
test_inplace(); |
|
test_different_keys_nonces(); |
|
test_large_data(); |
|
test_encrypt_eq_decrypt(); |
|
test_tunnel_roundtrip(); |
|
test_counter_high_word(); |
|
test_rekey(); |
|
test_macros(); |
|
test_xsalsa20_determinism(); |
|
test_xsalsa20_different_nonces(); |
|
test_xsalsa20_roundtrip(); |
|
test_xsalsa20_sequential(); |
|
test_xsalsa20_inplace(); |
|
test_xsalsa20_rekey(); |
|
test_xsalsa20_regression_vector(); |
|
test_xsalsa20_not_salsa20(); |
|
test_xsalsa20_key_overwrite(); |
|
test_xsalsa20_large(); |
|
|
|
int total = passed + failed; |
|
printf("\n========================================\n"); |
|
printf("RESULTS: %d passed, %d failed, %d total\n", passed, failed, total); |
|
if (failed > 0) { |
|
printf("*** SOME TESTS FAILED ***\n"); |
|
return 1; |
|
} |
|
printf("ALL TESTS PASSED\n"); |
|
return 0; |
|
} |