feat(crypto): ChaCha20-Poly1305 AEAD (rv2 8 phase A, ids 111/112)
- hand-rolled ChaCha20 + poly1305-donna-32 + RFC 8439 §2.8 AEAD in crypto.c; constant-time (add/xor/rotate + limb math, no tables, no data-dep branches), constant-time tag compare - two bare-name crypto-family builtins beside sha256/hmac: chacha20poly1305_seal(key,nonce,aad,pt) -> Bytes (ct||tag) chacha20poly1305_open(key,nonce,aad,ct||tag) -> ?Bytes (nil on auth fail) key 32B, nonce 12B (caller-supplied, per TLS's per-record nonce need) - wiring: wob.h enum + WO_B_MAX 112; builtin.c crypto dispatch range; loader.c arity 4; emit.ml (ids, arity_of 4-case, return type, is_builtin_name, name->id); types.ml (registration + return type) - VERIFIED: matches RFC 8439 §2.8.2 byte-for-byte (vs python cryptography + the RFC vector); test_crypto 24/0 (Poly1305 §2.5.2 + AEAD seal/open/tamper); ASan/UBSan clean; runtime battery + compiler 557/0 green - first rung of the TLS ladder (rv2 9 phase A) (cherry picked from commit 961854a8f4e9e632b6fa17f7f2e519e2d08f4936)
This commit is contained in:
parent
7f7a601e2f
commit
ac52c3fdb5
8 changed files with 354 additions and 3 deletions
|
|
@ -294,6 +294,9 @@ let b_text_of_bytes = 83
|
|||
let b_sha1 = 85
|
||||
let b_sha256 = 86
|
||||
let b_hmac_sha256 = 87
|
||||
(* runtime-v2 8 phase A: ChaCha20-Poly1305 AEAD (ids match wob.h 111/112) *)
|
||||
let b_chacha20poly1305_seal = 111
|
||||
let b_chacha20poly1305_open = 112
|
||||
let b_call = 88
|
||||
let b_monitor = 89
|
||||
let b_split = 28
|
||||
|
|
@ -1099,6 +1102,8 @@ let builtin_ret (name : string) (argty : Ast.field_ty option) : Ast.field_ty opt
|
|||
| "bytes_eq" -> Some (Scalar "Bool")
|
||||
| "bytes_slice" | "bytes_concat" | "bytes_of_text" -> Some (Scalar "Bytes")
|
||||
| "sha1" | "sha256" | "hmac_sha256" -> Some (Scalar "Bytes")
|
||||
| "chacha20poly1305_seal" -> Some (Scalar "Bytes")
|
||||
| "chacha20poly1305_open" -> Some (Nullable (Scalar "Bytes"))
|
||||
| "base64_decode" -> Some (Nullable (Scalar "Bytes"))
|
||||
| _ -> None
|
||||
|
||||
|
|
@ -1117,7 +1122,9 @@ let is_builtin_name (n : string) =
|
|||
"bytes_slice"; "bytes_eq"; "bytes_concat"; "base64_encode"; "base64_decode";
|
||||
"bytes_of_text"; "text_of_bytes";
|
||||
(* iteration 34: digests *)
|
||||
"sha1"; "sha256"; "hmac_sha256" ]
|
||||
"sha1"; "sha256"; "hmac_sha256";
|
||||
(* runtime-v2 8 phase A: AEAD *)
|
||||
"chacha20poly1305_seal"; "chacha20poly1305_open" ]
|
||||
|
||||
(* ---- unions and variants (haxe-parity Task 4) ------------------------
|
||||
|
||||
|
|
@ -3696,6 +3703,8 @@ and emit_builtin (p : pctx) (f : fstate) (v : views) ~(dst : int) ?expected (e :
|
|||
(* iteration 24, two arguments *)
|
||||
|| id = b_call
|
||||
then 2
|
||||
else if id = b_chacha20poly1305_seal || id = b_chacha20poly1305_open then 4
|
||||
(* rv2 8: (key, nonce, aad, plaintext|ciphertext) *)
|
||||
else 3 (* b_bytes_slice lands here with substr's shape: (value, start, len) *)
|
||||
in
|
||||
let container_id first_arg on_multi on_map =
|
||||
|
|
@ -3815,6 +3824,8 @@ and emit_builtin (p : pctx) (f : fstate) (v : views) ~(dst : int) ?expected (e :
|
|||
| "sha1" -> fixed b_sha1
|
||||
| "sha256" -> fixed b_sha256
|
||||
| "hmac_sha256" -> fixed b_hmac_sha256
|
||||
| "chacha20poly1305_seal" -> fixed b_chacha20poly1305_seal
|
||||
| "chacha20poly1305_open" -> fixed b_chacha20poly1305_open
|
||||
| "multi_new" | "map_new" ->
|
||||
let is_map = name = "map_new" in
|
||||
if args <> [] then bad (Printf.sprintf "builtin `%s` takes no arguments" name)
|
||||
|
|
|
|||
|
|
@ -953,6 +953,10 @@ let builtin_signatures : (string * int * builtin_arg_req list) list =
|
|||
("sha1", 1, [ ReqBytes ]);
|
||||
("sha256", 1, [ ReqBytes ]);
|
||||
("hmac_sha256", 2, [ ReqBytes; ReqBytes ]);
|
||||
(* runtime-v2 8 phase A: ChaCha20-Poly1305 AEAD. (key, nonce, aad,
|
||||
plaintext|ciphertext). seal -> Bytes; open -> ?Bytes (nil on auth fail). *)
|
||||
("chacha20poly1305_seal", 4, [ ReqBytes; ReqBytes; ReqBytes; ReqBytes ]);
|
||||
("chacha20poly1305_open", 4, [ ReqBytes; ReqBytes; ReqBytes; ReqBytes ]);
|
||||
]
|
||||
|
||||
let rec unwrap_nullable (t : typ) : typ =
|
||||
|
|
@ -1159,6 +1163,8 @@ let builtin_confident_ret (name : string) (arg0 : typ option) : typ option =
|
|||
| "bytes_eq" -> Some (TScalar "Bool")
|
||||
| "bytes_slice" | "bytes_concat" | "bytes_of_text" -> Some (TScalar "Bytes")
|
||||
| "sha1" | "sha256" | "hmac_sha256" -> Some (TScalar "Bytes")
|
||||
| "chacha20poly1305_seal" -> Some (TScalar "Bytes")
|
||||
| "chacha20poly1305_open" -> Some (TNullable (TScalar "Bytes"))
|
||||
(* malformed base64 is nil, not a trap: it arrives from the network *)
|
||||
| "base64_decode" -> Some (TNullable (TScalar "Bytes"))
|
||||
| _ -> None
|
||||
|
|
|
|||
|
|
@ -174,7 +174,8 @@ int wo_builtin(wo_vm *vm, uint64_t *R, uint32_t ins, const char **msg) {
|
|||
if ((C >= WO_B_SYS_FIRST && C <= WO_B_PROC_RUN) || C == WO_B_TIME_TICKS
|
||||
|| (C >= WO_B_NET_READ_DL && C <= WO_B_NET_CONNECT))
|
||||
return wo_builtin_sys(vm, R, ins, msg);
|
||||
if (C >= WO_B_SHA1 && C <= WO_B_HMAC_SHA256)
|
||||
if ((C >= WO_B_SHA1 && C <= WO_B_HMAC_SHA256)
|
||||
|| (C >= WO_B_CHACHA20POLY1305_SEAL && C <= WO_B_CHACHA20POLY1305_OPEN))
|
||||
return wo_builtin_crypto(vm, R, ins, msg);
|
||||
if (C >= WO_B_DB_INSERT && C <= WO_B_DB_PROBE) {
|
||||
/* arc stage 3: the database is an actor on shard 0. A worker shard
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
* (Sec-WebSocket-Accept is SHA-1 by RFC 6455, not a choice). */
|
||||
#include "crypto.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "obj.h"
|
||||
|
|
@ -198,6 +199,200 @@ void wo_hmac_sha256(const uint8_t *key, size_t klen, const uint8_t *msg,
|
|||
wo_sha256(outer, 96, out);
|
||||
}
|
||||
|
||||
/* ---- ChaCha20-Poly1305 AEAD (rv2 8 phase A, RFC 8439) ------------------
|
||||
* Hand-rolled, libc-only, constant-time by construction (add/xor/rotate and
|
||||
* limb arithmetic; no data-dependent branches, no table lookups). The
|
||||
* reference is RFC 8439; the paper .dev/reference/cryptography-06-00030.pdf
|
||||
* describes the same algorithm. Vectors pinned in test/test_crypto.c. */
|
||||
|
||||
static uint32_t rd32le(const uint8_t *p) {
|
||||
return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) |
|
||||
((uint32_t)p[3] << 24);
|
||||
}
|
||||
static void wr32le(uint8_t *p, uint32_t v) {
|
||||
p[0] = (uint8_t)v; p[1] = (uint8_t)(v >> 8);
|
||||
p[2] = (uint8_t)(v >> 16); p[3] = (uint8_t)(v >> 24);
|
||||
}
|
||||
static void wr64le(uint8_t *p, uint64_t v) {
|
||||
for (int i = 0; i < 8; i++) p[i] = (uint8_t)(v >> (8 * i));
|
||||
}
|
||||
|
||||
#define CHACHA_QR(x, a, b, c, d) \
|
||||
do { \
|
||||
x[a] += x[b]; x[d] ^= x[a]; x[d] = rotl32(x[d], 16); \
|
||||
x[c] += x[d]; x[b] ^= x[c]; x[b] = rotl32(x[b], 12); \
|
||||
x[a] += x[b]; x[d] ^= x[a]; x[d] = rotl32(x[d], 8); \
|
||||
x[c] += x[d]; x[b] ^= x[c]; x[b] = rotl32(x[b], 7); \
|
||||
} while (0)
|
||||
|
||||
static void chacha20_block(const uint8_t key[32], uint32_t counter,
|
||||
const uint8_t nonce[12], uint8_t out[64]) {
|
||||
uint32_t s[16], x[16];
|
||||
s[0] = 0x61707865u; s[1] = 0x3320646eu;
|
||||
s[2] = 0x79622d32u; s[3] = 0x6b206574u;
|
||||
for (int i = 0; i < 8; i++) s[4 + i] = rd32le(key + 4 * i);
|
||||
s[12] = counter;
|
||||
s[13] = rd32le(nonce); s[14] = rd32le(nonce + 4); s[15] = rd32le(nonce + 8);
|
||||
for (int i = 0; i < 16; i++) x[i] = s[i];
|
||||
for (int i = 0; i < 10; i++) {
|
||||
CHACHA_QR(x, 0, 4, 8, 12); CHACHA_QR(x, 1, 5, 9, 13);
|
||||
CHACHA_QR(x, 2, 6, 10, 14); CHACHA_QR(x, 3, 7, 11, 15);
|
||||
CHACHA_QR(x, 0, 5, 10, 15); CHACHA_QR(x, 1, 6, 11, 12);
|
||||
CHACHA_QR(x, 2, 7, 8, 13); CHACHA_QR(x, 3, 4, 9, 14);
|
||||
}
|
||||
for (int i = 0; i < 16; i++) wr32le(out + 4 * i, x[i] + s[i]);
|
||||
}
|
||||
|
||||
/* XOR the ChaCha20 keystream (from `counter`) over `len` bytes. in==out safe. */
|
||||
static void chacha20_xor(const uint8_t key[32], const uint8_t nonce[12],
|
||||
uint32_t counter, const uint8_t *in, size_t len,
|
||||
uint8_t *out) {
|
||||
uint8_t blk[64];
|
||||
size_t off = 0;
|
||||
while (len > 0) {
|
||||
chacha20_block(key, counter, nonce, blk);
|
||||
size_t n = len < 64 ? len : 64;
|
||||
for (size_t i = 0; i < n; i++) out[off + i] = in[off + i] ^ blk[i];
|
||||
off += n; len -= n; counter++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Poly1305 one-shot (poly1305-donna 32-bit, RFC 8439 §2.5). key = r||s. */
|
||||
void wo_poly1305(const uint8_t key[32], const uint8_t *m, size_t bytes,
|
||||
uint8_t mac[16]) {
|
||||
uint32_t t0 = rd32le(key), t1 = rd32le(key + 4),
|
||||
t2 = rd32le(key + 8), t3 = rd32le(key + 12);
|
||||
uint32_t r0 = t0 & 0x3ffffffu;
|
||||
uint32_t r1 = ((t0 >> 26) | (t1 << 6)) & 0x3ffff03u;
|
||||
uint32_t r2 = ((t1 >> 20) | (t2 << 12)) & 0x3ffc0ffu;
|
||||
uint32_t r3 = ((t2 >> 14) | (t3 << 18)) & 0x3f03fffu;
|
||||
uint32_t r4 = (t3 >> 8) & 0x00fffffu;
|
||||
uint32_t s1 = r1 * 5, s2 = r2 * 5, s3 = r3 * 5, s4 = r4 * 5;
|
||||
uint32_t h0 = 0, h1 = 0, h2 = 0, h3 = 0, h4 = 0, c;
|
||||
|
||||
while (bytes > 0) {
|
||||
uint8_t block[16];
|
||||
size_t n = bytes < 16 ? bytes : 16;
|
||||
uint32_t hibit;
|
||||
if (n < 16) {
|
||||
memset(block, 0, 16);
|
||||
memcpy(block, m, n);
|
||||
block[n] = 1;
|
||||
hibit = 0;
|
||||
} else {
|
||||
memcpy(block, m, 16);
|
||||
hibit = 1u << 24;
|
||||
}
|
||||
t0 = rd32le(block); t1 = rd32le(block + 4);
|
||||
t2 = rd32le(block + 8); t3 = rd32le(block + 12);
|
||||
h0 += t0 & 0x3ffffffu;
|
||||
h1 += ((t0 >> 26) | (t1 << 6)) & 0x3ffffffu;
|
||||
h2 += ((t1 >> 20) | (t2 << 12)) & 0x3ffffffu;
|
||||
h3 += ((t2 >> 14) | (t3 << 18)) & 0x3ffffffu;
|
||||
h4 += (t3 >> 8) | hibit;
|
||||
|
||||
uint64_t d0 = (uint64_t)h0 * r0 + (uint64_t)h1 * s4 + (uint64_t)h2 * s3 +
|
||||
(uint64_t)h3 * s2 + (uint64_t)h4 * s1;
|
||||
uint64_t d1 = (uint64_t)h0 * r1 + (uint64_t)h1 * r0 + (uint64_t)h2 * s4 +
|
||||
(uint64_t)h3 * s3 + (uint64_t)h4 * s2;
|
||||
uint64_t d2 = (uint64_t)h0 * r2 + (uint64_t)h1 * r1 + (uint64_t)h2 * r0 +
|
||||
(uint64_t)h3 * s4 + (uint64_t)h4 * s3;
|
||||
uint64_t d3 = (uint64_t)h0 * r3 + (uint64_t)h1 * r2 + (uint64_t)h2 * r1 +
|
||||
(uint64_t)h3 * r0 + (uint64_t)h4 * s4;
|
||||
uint64_t d4 = (uint64_t)h0 * r4 + (uint64_t)h1 * r3 + (uint64_t)h2 * r2 +
|
||||
(uint64_t)h3 * r1 + (uint64_t)h4 * r0;
|
||||
|
||||
c = (uint32_t)(d0 >> 26); h0 = (uint32_t)d0 & 0x3ffffffu;
|
||||
d1 += c; c = (uint32_t)(d1 >> 26); h1 = (uint32_t)d1 & 0x3ffffffu;
|
||||
d2 += c; c = (uint32_t)(d2 >> 26); h2 = (uint32_t)d2 & 0x3ffffffu;
|
||||
d3 += c; c = (uint32_t)(d3 >> 26); h3 = (uint32_t)d3 & 0x3ffffffu;
|
||||
d4 += c; c = (uint32_t)(d4 >> 26); h4 = (uint32_t)d4 & 0x3ffffffu;
|
||||
h0 += c * 5; c = h0 >> 26; h0 &= 0x3ffffffu; h1 += c;
|
||||
|
||||
m += n; bytes -= n;
|
||||
}
|
||||
|
||||
c = h1 >> 26; h1 &= 0x3ffffffu; h2 += c;
|
||||
c = h2 >> 26; h2 &= 0x3ffffffu; h3 += c;
|
||||
c = h3 >> 26; h3 &= 0x3ffffffu; h4 += c;
|
||||
c = h4 >> 26; h4 &= 0x3ffffffu; h0 += c * 5;
|
||||
c = h0 >> 26; h0 &= 0x3ffffffu; h1 += c;
|
||||
|
||||
uint32_t g0 = h0 + 5; c = g0 >> 26; g0 &= 0x3ffffffu;
|
||||
uint32_t g1 = h1 + c; c = g1 >> 26; g1 &= 0x3ffffffu;
|
||||
uint32_t g2 = h2 + c; c = g2 >> 26; g2 &= 0x3ffffffu;
|
||||
uint32_t g3 = h3 + c; c = g3 >> 26; g3 &= 0x3ffffffu;
|
||||
uint32_t g4 = h4 + c - (1u << 26);
|
||||
|
||||
uint32_t mask = (g4 >> 31) - 1;
|
||||
g0 &= mask; g1 &= mask; g2 &= mask; g3 &= mask; g4 &= mask;
|
||||
mask = ~mask;
|
||||
h0 = (h0 & mask) | g0; h1 = (h1 & mask) | g1; h2 = (h2 & mask) | g2;
|
||||
h3 = (h3 & mask) | g3; h4 = (h4 & mask) | g4;
|
||||
|
||||
h0 = (h0 | (h1 << 26));
|
||||
h1 = ((h1 >> 6) | (h2 << 20));
|
||||
h2 = ((h2 >> 12) | (h3 << 14));
|
||||
h3 = ((h3 >> 18) | (h4 << 8));
|
||||
|
||||
uint64_t f = (uint64_t)h0 + rd32le(key + 16); h0 = (uint32_t)f;
|
||||
f = (uint64_t)h1 + rd32le(key + 20) + (f >> 32); h1 = (uint32_t)f;
|
||||
f = (uint64_t)h2 + rd32le(key + 24) + (f >> 32); h2 = (uint32_t)f;
|
||||
f = (uint64_t)h3 + rd32le(key + 28) + (f >> 32); h3 = (uint32_t)f;
|
||||
|
||||
wr32le(mac, h0); wr32le(mac + 4, h1); wr32le(mac + 8, h2); wr32le(mac + 12, h3);
|
||||
}
|
||||
|
||||
static int ct_memeq(const uint8_t *a, const uint8_t *b, size_t n) {
|
||||
uint8_t d = 0;
|
||||
for (size_t i = 0; i < n; i++) d |= (uint8_t)(a[i] ^ b[i]);
|
||||
return d == 0;
|
||||
}
|
||||
|
||||
/* The AEAD MAC: Poly1305 over aad || pad16 || ct || pad16 || le64(aadlen) ||
|
||||
* le64(ctlen). Returns 0, or -1 on OOM building the (16-aligned) buffer. */
|
||||
static int aead_tag(const uint8_t polykey[32], const uint8_t *aad, size_t aadlen,
|
||||
const uint8_t *ct, size_t ctlen, uint8_t tag[16]) {
|
||||
size_t apad = (aadlen + 15u) & ~(size_t)15u;
|
||||
size_t cpad = (ctlen + 15u) & ~(size_t)15u;
|
||||
size_t mlen = apad + cpad + 16u;
|
||||
uint8_t *mb = (uint8_t *)calloc(1, mlen);
|
||||
if (!mb) return -1;
|
||||
if (aadlen) memcpy(mb, aad, aadlen);
|
||||
if (ctlen) memcpy(mb + apad, ct, ctlen);
|
||||
wr64le(mb + apad + cpad, (uint64_t)aadlen);
|
||||
wr64le(mb + apad + cpad + 8, (uint64_t)ctlen);
|
||||
wo_poly1305(polykey, mb, mlen, tag);
|
||||
free(mb);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* RFC 8439 §2.8 seal: out = ciphertext || 16-byte tag (out must hold
|
||||
* ptlen+16). Returns 0, or -1 on OOM. */
|
||||
int wo_chacha20poly1305_seal(const uint8_t key[32], const uint8_t nonce[12],
|
||||
const uint8_t *aad, size_t aadlen,
|
||||
const uint8_t *pt, size_t ptlen, uint8_t *out) {
|
||||
uint8_t polyblock[64];
|
||||
chacha20_block(key, 0, nonce, polyblock); /* Poly1305 key = counter-0 block */
|
||||
chacha20_xor(key, nonce, 1, pt, ptlen, out);
|
||||
return aead_tag(polyblock, aad, aadlen, out, ptlen, out + ptlen);
|
||||
}
|
||||
|
||||
/* Open: verify the tag over `ct` (ctlen, the ciphertext WITHOUT the tag) and
|
||||
* `tag`, then decrypt into `out` (ctlen bytes). 0 = ok, 1 = auth failure,
|
||||
* -1 = OOM. Constant-time tag compare; on failure `out` is not written. */
|
||||
int wo_chacha20poly1305_open(const uint8_t key[32], const uint8_t nonce[12],
|
||||
const uint8_t *aad, size_t aadlen,
|
||||
const uint8_t *ct, size_t ctlen,
|
||||
const uint8_t tag[16], uint8_t *out) {
|
||||
uint8_t polyblock[64], want[16];
|
||||
chacha20_block(key, 0, nonce, polyblock);
|
||||
if (aead_tag(polyblock, aad, aadlen, ct, ctlen, want) != 0) return -1;
|
||||
if (!ct_memeq(want, tag, 16)) return 1;
|
||||
chacha20_xor(key, nonce, 1, ct, ctlen, out);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* The VM half: Bytes in, fresh Bytes out. Wrong class id traps
|
||||
* WO_T_BOUNDS with the Bytes builtins' message shape. */
|
||||
static const wo_str *arg_bytes(uint64_t r, const char **msg) {
|
||||
|
|
@ -238,6 +433,59 @@ int wo_builtin_crypto(wo_vm *vm, uint64_t *R, uint32_t ins, const char **msg) {
|
|||
dlen = 32;
|
||||
break;
|
||||
}
|
||||
case WO_B_CHACHA20POLY1305_SEAL: {
|
||||
const wo_str *k = arg_bytes(R[B], msg);
|
||||
const wo_str *n = k ? arg_bytes(R[B + 1], msg) : NULL;
|
||||
const wo_str *a = n ? arg_bytes(R[B + 2], msg) : NULL;
|
||||
const wo_str *p = a ? arg_bytes(R[B + 3], msg) : NULL;
|
||||
if (!p) return WO_T_BOUNDS;
|
||||
if (k->len != 32 || n->len != 12) {
|
||||
*msg = "chacha20poly1305: key must be 32 bytes, nonce 12";
|
||||
return WO_T_BOUNDS;
|
||||
}
|
||||
uint8_t *buf = (uint8_t *)malloc(p->len + 16u);
|
||||
if (!buf) { *msg = "out of memory"; return WO_T_OOM; }
|
||||
if (wo_chacha20poly1305_seal((const uint8_t *)k->data,
|
||||
(const uint8_t *)n->data,
|
||||
(const uint8_t *)a->data, a->len,
|
||||
(const uint8_t *)p->data, p->len, buf) != 0) {
|
||||
free(buf);
|
||||
*msg = "out of memory";
|
||||
return WO_T_OOM;
|
||||
}
|
||||
wo_str *o = wo_bytes_new(rt, (const char *)buf, (uint32_t)(p->len + 16u));
|
||||
free(buf);
|
||||
if (!o) { *msg = "out of memory"; return WO_T_OOM; }
|
||||
R[A] = (uint64_t)(uintptr_t)o;
|
||||
return 0;
|
||||
}
|
||||
case WO_B_CHACHA20POLY1305_OPEN: {
|
||||
const wo_str *k = arg_bytes(R[B], msg);
|
||||
const wo_str *n = k ? arg_bytes(R[B + 1], msg) : NULL;
|
||||
const wo_str *a = n ? arg_bytes(R[B + 2], msg) : NULL;
|
||||
const wo_str *ctag = a ? arg_bytes(R[B + 3], msg) : NULL;
|
||||
if (!ctag) return WO_T_BOUNDS;
|
||||
if (k->len != 32 || n->len != 12) {
|
||||
*msg = "chacha20poly1305: key must be 32 bytes, nonce 12";
|
||||
return WO_T_BOUNDS;
|
||||
}
|
||||
if (ctag->len < 16) { R[A] = 0; return 0; } /* no room for a tag: reject */
|
||||
uint32_t bodylen = ctag->len - 16u;
|
||||
uint8_t *buf = (uint8_t *)malloc(bodylen ? bodylen : 1u);
|
||||
if (!buf) { *msg = "out of memory"; return WO_T_OOM; }
|
||||
int rc = wo_chacha20poly1305_open(
|
||||
(const uint8_t *)k->data, (const uint8_t *)n->data,
|
||||
(const uint8_t *)a->data, a->len,
|
||||
(const uint8_t *)ctag->data, bodylen,
|
||||
(const uint8_t *)ctag->data + bodylen, buf);
|
||||
if (rc == -1) { free(buf); *msg = "out of memory"; return WO_T_OOM; }
|
||||
if (rc != 0) { free(buf); R[A] = 0; return 0; } /* auth failure -> nil */
|
||||
wo_str *o = wo_bytes_new(rt, (const char *)buf, bodylen);
|
||||
free(buf);
|
||||
if (!o) { *msg = "out of memory"; return WO_T_OOM; }
|
||||
R[A] = (uint64_t)(uintptr_t)o;
|
||||
return 0;
|
||||
}
|
||||
default:
|
||||
*msg = "unknown crypto builtin";
|
||||
return WO_T_BOUNDS;
|
||||
|
|
|
|||
|
|
@ -14,6 +14,18 @@ void wo_sha256(const uint8_t *msg, size_t len, uint8_t out[32]);
|
|||
void wo_hmac_sha256(const uint8_t *key, size_t klen, const uint8_t *msg,
|
||||
size_t mlen, uint8_t out[32]);
|
||||
|
||||
/* ChaCha20-Poly1305 AEAD (rv2 8 phase A, RFC 8439). Raw cores exposed for
|
||||
* the unit test; the VM enters through wo_builtin_crypto. */
|
||||
void wo_poly1305(const uint8_t key[32], const uint8_t *m, size_t bytes,
|
||||
uint8_t mac[16]);
|
||||
int wo_chacha20poly1305_seal(const uint8_t key[32], const uint8_t nonce[12],
|
||||
const uint8_t *aad, size_t aadlen,
|
||||
const uint8_t *pt, size_t ptlen, uint8_t *out);
|
||||
int wo_chacha20poly1305_open(const uint8_t key[32], const uint8_t nonce[12],
|
||||
const uint8_t *aad, size_t aadlen,
|
||||
const uint8_t *ct, size_t ctlen,
|
||||
const uint8_t tag[16], uint8_t *out);
|
||||
|
||||
int wo_builtin_crypto(wo_vm *vm, uint64_t *R, uint32_t ins, const char **msg);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -79,6 +79,7 @@ static const uint8_t b_arity[WO_B_MAX + 1] = {
|
|||
[WO_B_SIGNAL_ON] = 3, [WO_B_TERM_RAW] = 1, [WO_B_TERM_RESTORE] = 1,
|
||||
[WO_B_NET_SEND_FD] = 2, [WO_B_NET_RECV_FD] = 1, [WO_B_NET_CONNECT_UNIX] = 1,
|
||||
[WO_B_TERM_SIZE] = 2, [WO_B_TERM_WIDTH] = 1, [WO_B_NET_CONNECT] = 2,
|
||||
[WO_B_CHACHA20POLY1305_SEAL] = 4, [WO_B_CHACHA20POLY1305_OPEN] = 4,
|
||||
/* json (json.c): encode takes the value's static kind, decode the class
|
||||
id to build */
|
||||
[WO_B_JSON_ENCODE] = 2, [WO_B_JSON_DECODE] = 2, [WO_B_MAP_GET_OPT] = 2,
|
||||
|
|
|
|||
|
|
@ -552,9 +552,15 @@ enum {
|
|||
* for the plane, mirroring WO_B_NET_CONNECT_UNIX. A _dl deadline/park
|
||||
* variant is the next slice. Underneath runtime-v2 9's outbound TLS. */
|
||||
WO_B_NET_CONNECT = 110, /* (host, port) -> Int: outbound TCP client fd */
|
||||
/* ---- runtime-v2 8 phase A: ChaCha20-Poly1305 AEAD (RFC 8439). Bare-name
|
||||
* crypto-family builtins beside sha256/hmac; key 32B, nonce 12B, caller-
|
||||
* supplied. seal -> ciphertext||tag (Bytes); open -> ?Bytes (nil on auth
|
||||
* failure or a too-short input). */
|
||||
WO_B_CHACHA20POLY1305_SEAL = 111, /* (key, nonce, aad, plaintext) -> Bytes */
|
||||
WO_B_CHACHA20POLY1305_OPEN = 112, /* (key, nonce, aad, ct||tag) -> ?Bytes */
|
||||
};
|
||||
|
||||
#define WO_B_MAX 110u
|
||||
#define WO_B_MAX 112u
|
||||
/* ids at or above this one live in sysio.c, not builtin.c */
|
||||
#define WO_B_SYS_FIRST WO_B_FS_EXISTS
|
||||
|
||||
|
|
|
|||
|
|
@ -42,6 +42,16 @@ static void t_hmac(const uint8_t *key, size_t klen, const char *msg,
|
|||
T_CHECK(strcmp(got, want) == 0);
|
||||
}
|
||||
|
||||
/* rv2 8 phase A: ChaCha20-Poly1305 (RFC 8439 §2.5.2 Poly1305 + §2.8.2 AEAD) */
|
||||
static void t_poly1305(const uint8_t key[32], const char *msg, size_t mlen,
|
||||
const char *want) {
|
||||
uint8_t tag[16];
|
||||
char got[33];
|
||||
wo_poly1305(key, (const uint8_t *)msg, mlen, tag);
|
||||
hex(tag, 16, got);
|
||||
T_CHECK(strcmp(got, want) == 0);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
/* RFC 3174 */
|
||||
t_sha1("abc", 3, "a9993e364706816aba3e25717850c26c9cd0d89d");
|
||||
|
|
@ -108,5 +118,61 @@ int main(void) {
|
|||
"60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54");
|
||||
}
|
||||
|
||||
/* RFC 8439 §2.5.2 — Poly1305 */
|
||||
{
|
||||
uint8_t pk[32];
|
||||
for (int i = 0; i < 32; i++) pk[i] = 0;
|
||||
static const uint8_t pkv[32] = {
|
||||
0x85, 0xd6, 0xbe, 0x78, 0x57, 0x55, 0x6d, 0x33, 0x7f, 0x44, 0x52,
|
||||
0xfe, 0x42, 0xd5, 0x06, 0xa8, 0x01, 0x03, 0x80, 0x8a, 0xfb, 0x0d,
|
||||
0xb2, 0xfd, 0x4a, 0xbf, 0xf6, 0xaf, 0x41, 0x49, 0xf5, 0x1b };
|
||||
memcpy(pk, pkv, 32);
|
||||
t_poly1305(pk, "Cryptographic Forum Research Group", 34,
|
||||
"a8061dc1305136c6c22b8baf0c0127a9");
|
||||
}
|
||||
|
||||
/* RFC 8439 §2.8.2 — ChaCha20-Poly1305 AEAD: seal matches the vector,
|
||||
* open round-trips, and a tampered tag is rejected. */
|
||||
{
|
||||
uint8_t key[32], nonce[12], aad[12];
|
||||
for (int i = 0; i < 32; i++) key[i] = (uint8_t)(0x80 + i);
|
||||
static const uint8_t nv[12] = { 0x07, 0x00, 0x00, 0x00, 0x40, 0x41,
|
||||
0x42, 0x43, 0x44, 0x45, 0x46, 0x47 };
|
||||
static const uint8_t av[12] = { 0x50, 0x51, 0x52, 0x53, 0xc0, 0xc1,
|
||||
0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7 };
|
||||
memcpy(nonce, nv, 12);
|
||||
memcpy(aad, av, 12);
|
||||
const char *pt =
|
||||
"Ladies and Gentlemen of the class of '99: If I could offer you "
|
||||
"only one tip for the future, sunscreen would be it.";
|
||||
size_t ptlen = strlen(pt);
|
||||
uint8_t out[114 + 16];
|
||||
int rc = wo_chacha20poly1305_seal(key, nonce, aad, 12,
|
||||
(const uint8_t *)pt, ptlen, out);
|
||||
T_CHECK(rc == 0);
|
||||
char got[(114 + 16) * 2 + 1];
|
||||
hex(out, ptlen + 16, got);
|
||||
T_CHECK(strcmp(got,
|
||||
"d31a8d34648e60db7b86afbc53ef7ec2a4aded51296e08fea9e2b5a736ee62d6"
|
||||
"3dbea45e8ca9671282fafb69da92728b1a71de0a9e060b2905d6a5b67ecd3b36"
|
||||
"92ddbd7f2d778b8c9803aee328091b58fab324e4fad675945585808b4831d7bc"
|
||||
"3ff4def08e4b7a9de576d26586cec64b61161ae10b594f09e26a7e902ecbd060"
|
||||
"0691") == 0);
|
||||
|
||||
uint8_t back[114];
|
||||
rc = wo_chacha20poly1305_open(key, nonce, aad, 12, out, ptlen,
|
||||
out + ptlen, back);
|
||||
T_CHECK(rc == 0);
|
||||
T_CHECK(memcmp(back, pt, ptlen) == 0);
|
||||
|
||||
/* flip one tag bit — must be rejected (rc == 1), not decrypted */
|
||||
uint8_t tampered[16];
|
||||
memcpy(tampered, out + ptlen, 16);
|
||||
tampered[0] ^= 0x01;
|
||||
rc = wo_chacha20poly1305_open(key, nonce, aad, 12, out, ptlen,
|
||||
tampered, back);
|
||||
T_CHECK(rc == 1);
|
||||
}
|
||||
|
||||
return t_report("test_crypto");
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue