writeonce/runtime/test/test_tls.c
shoney.arickathil 7e71c1a171 feat(tls): TLS 1.3 record layer (rv2 9 phase F1)
- new tls.c/tls.h on the crypto ladder: wo_tls_record_seal/open
  (RFC 8446 §5.2) — TLSInnerPlaintext (content||type, no padding),
  5-byte header as AEAD additional-data, per-record nonce = iv XOR
  seq big-endian (§5.3)
- suite dispatch: TLS_AES_128_GCM_SHA256 (mandatory) +
  TLS_CHACHA20_POLY1305_SHA256 (AES-NI-less fallback), over phase-A AEAD
- open() strips trailing zero padding to recover the inner content type;
  rejects a length-field lie before the AEAD, and auth failure after
- KAT vs python AEAD oracle (test/gen_tls_record.py): sealed record
  byte-for-byte both suites, open() recovers it, 5-seq round-trip,
  tamper + wrong-seq + bad-suite rejected. test_tls 51 pass, ASan/UBSan

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 5021a99f8359f78a642bb0cc2b28ab66f6b624e2)
2026-09-15 01:15:31 +02:00

93 lines
4 KiB
C

/* test_tls.c — TLS 1.3 record layer (runtime-v2 9 phase F1). KAT against
* python's AEAD as oracle (tls_record_vectors.h), plus seal/open round-trip,
* a tamper-rejection, and the sequence-number nonce advancing. ASan/UBSan. */
#include <stdint.h>
#include <string.h>
#include "tls.h"
#include "t.h"
#include "tls_record_vectors.h"
int main(void) {
uint8_t iv[12], aeskey[16], chakey[32], pt[TLSREC_PTLEN];
memcpy(iv, TLSREC_IV, 12);
memcpy(aeskey, TLSREC_AESKEY, 16);
memcpy(chakey, TLSREC_CHAKEY, 32);
memcpy(pt, TLSREC_PT, TLSREC_PTLEN);
/* AES-128-GCM: sealed record must equal python's byte-for-byte. */
{
uint8_t out[TLSREC_PTLEN + WO_TLS_RECORD_OVERHEAD];
int n = wo_tls_record_seal(WO_TLS_AES_128_GCM_SHA256, aeskey, 16, iv,
TLSREC_SEQ, TLSREC_CT, pt, TLSREC_PTLEN, out);
T_CHECK(n == TLSREC_AESLEN);
T_CHECK(memcmp(out, TLSREC_AES, TLSREC_AESLEN) == 0);
}
/* ChaCha20-Poly1305: same. */
{
uint8_t out[TLSREC_PTLEN + WO_TLS_RECORD_OVERHEAD];
int n = wo_tls_record_seal(WO_TLS_CHACHA20_POLY1305_SHA256, chakey, 32,
iv, TLSREC_SEQ, TLSREC_CT, pt, TLSREC_PTLEN,
out);
T_CHECK(n == TLSREC_CHALEN);
T_CHECK(memcmp(out, TLSREC_CHA, TLSREC_CHALEN) == 0);
}
/* open() recovers the record python sealed: content, type, length. */
{
uint8_t rec[TLSREC_AESLEN], out[TLSREC_AESLEN]; uint8_t ct = 0;
memcpy(rec, TLSREC_AES, TLSREC_AESLEN);
int n = wo_tls_record_open(WO_TLS_AES_128_GCM_SHA256, aeskey, 16, iv,
TLSREC_SEQ, rec, TLSREC_AESLEN, out, &ct);
T_CHECK(n == TLSREC_PTLEN);
T_CHECK(ct == TLSREC_CT);
T_CHECK(memcmp(out, pt, TLSREC_PTLEN) == 0);
}
/* Round-trip both suites over several sequence numbers (nonce advances). */
for (int suite = 1; suite <= 2; suite++) {
const uint8_t *k = suite == WO_TLS_AES_128_GCM_SHA256 ? aeskey : chakey;
size_t kl = suite == WO_TLS_AES_128_GCM_SHA256 ? 16 : 32;
for (uint64_t seq = 0; seq < 5; seq++) {
uint8_t msg[40], rec[40 + WO_TLS_RECORD_OVERHEAD];
uint8_t got[sizeof rec]; uint8_t ct = 0;
for (size_t i = 0; i < sizeof msg; i++) msg[i] = (uint8_t)(i + seq);
int n = wo_tls_record_seal(suite, k, kl, iv, seq,
WO_TLS_CT_APPLICATION_DATA, msg,
sizeof msg, rec);
T_CHECK(n > 0);
int m = wo_tls_record_open(suite, k, kl, iv, seq, rec, (size_t)n,
got, &ct);
T_CHECK(m == (int)sizeof msg);
T_CHECK(ct == WO_TLS_CT_APPLICATION_DATA);
T_CHECK(memcmp(got, msg, sizeof msg) == 0);
}
}
/* A tampered record fails to open; a wrong sequence number fails too. */
{
uint8_t rec[TLSREC_AESLEN], out[TLSREC_AESLEN]; uint8_t ct = 0;
memcpy(rec, TLSREC_AES, TLSREC_AESLEN);
rec[10] ^= 0x01;
T_CHECK(wo_tls_record_open(WO_TLS_AES_128_GCM_SHA256, aeskey, 16, iv,
TLSREC_SEQ, rec, TLSREC_AESLEN, out, &ct) == -1);
memcpy(rec, TLSREC_AES, TLSREC_AESLEN);
T_CHECK(wo_tls_record_open(WO_TLS_AES_128_GCM_SHA256, aeskey, 16, iv,
TLSREC_SEQ + 1, rec, TLSREC_AESLEN, out,
&ct) == -1);
/* A length-field lie is rejected before the AEAD. */
memcpy(rec, TLSREC_AES, TLSREC_AESLEN);
rec[4] ^= 0x01;
T_CHECK(wo_tls_record_open(WO_TLS_AES_128_GCM_SHA256, aeskey, 16, iv,
TLSREC_SEQ, rec, TLSREC_AESLEN, out, &ct) == -1);
}
/* A bad suite id is rejected, not misdispatched. */
{
uint8_t out[64];
T_CHECK(wo_tls_record_seal(99, aeskey, 16, iv, 0, 23, pt, TLSREC_PTLEN,
out) == -1);
}
return t_report("test_tls");
}