diff --git a/compiler/src/emit.ml b/compiler/src/emit.ml index 095ea2b..ef480a2 100644 --- a/compiler/src/emit.ml +++ b/compiler/src/emit.ml @@ -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) diff --git a/compiler/src/types.ml b/compiler/src/types.ml index 621834a..d9eac44 100644 --- a/compiler/src/types.ml +++ b/compiler/src/types.ml @@ -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 diff --git a/runtime/src/builtin.c b/runtime/src/builtin.c index 04ced17..35f0f8a 100644 --- a/runtime/src/builtin.c +++ b/runtime/src/builtin.c @@ -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 diff --git a/runtime/src/crypto.c b/runtime/src/crypto.c index c262ae8..505c078 100644 --- a/runtime/src/crypto.c +++ b/runtime/src/crypto.c @@ -6,6 +6,7 @@ * (Sec-WebSocket-Accept is SHA-1 by RFC 6455, not a choice). */ #include "crypto.h" +#include #include #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; diff --git a/runtime/src/crypto.h b/runtime/src/crypto.h index f3efe09..9e9c08e 100644 --- a/runtime/src/crypto.h +++ b/runtime/src/crypto.h @@ -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 diff --git a/runtime/src/loader.c b/runtime/src/loader.c index 6a6c88a..fab902e 100644 --- a/runtime/src/loader.c +++ b/runtime/src/loader.c @@ -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, diff --git a/runtime/src/wob.h b/runtime/src/wob.h index 3d6f176..4802e26 100644 --- a/runtime/src/wob.h +++ b/runtime/src/wob.h @@ -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 diff --git a/runtime/test/test_crypto.c b/runtime/test/test_crypto.c index 7e87a1e..0159fb0 100644 --- a/runtime/test/test_crypto.c +++ b/runtime/test/test_crypto.c @@ -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"); }