diff --git a/Makefile b/Makefile index 794fb1e..4909919 100644 --- a/Makefile +++ b/Makefile @@ -131,7 +131,8 @@ CORE_SRCS = kernel/main.c kernel/task.c kernel/sync.c kernel/timer.c kernel/prin lib/string.c lib/vsprintf.c lib/bitmap.c lib/rbtree.c lib/kfifo.c lib/hashtable.c lib/font8x8.c \ init/cmdline.c init/service.c init/bootlogo.c init/logo_data.c usr/initramfs.c block/block.c block/elevator.c \ ipc/sem.c ipc/shm.c ipc/shm_posix.c ipc/pipe.c ipc/ipc.c ipc/msg.c \ - crypto/sha256.c crypto/md5.c crypto/crc32.c crypto/prng.c sound/beep.c + crypto/sha256.c crypto/md5.c crypto/crc32.c crypto/prng.c \ + crypto/x25519.c crypto/chacha20poly1305.c crypto/hkdf.c sound/beep.c # If no .config exists, default all core options to y ifeq ($(wildcard .config),) @@ -227,7 +228,7 @@ endif # Network Subsystem ifeq ($(CONFIG_NET_STACK), y) - CONFIG_SRCS-y += net/core/net.c net/core/socket.c net/ipv4/tcp.c net/ipv4/udp.c net/ipv4/dhcp.c net/ipv4/dns.c net/ipv4/dns_cache.c net/http_client.c + CONFIG_SRCS-y += net/core/net.c net/core/socket.c net/ipv4/tcp.c net/ipv4/udp.c net/ipv4/dhcp.c net/ipv4/dns.c net/ipv4/dns_cache.c net/tls.c net/http_client.c endif ifeq ($(CONFIG_NET_FILTER), y) CONFIG_SRCS-y += net/filter.c diff --git a/crypto/chacha20poly1305.c b/crypto/chacha20poly1305.c new file mode 100644 index 0000000..ba2f232 --- /dev/null +++ b/crypto/chacha20poly1305.c @@ -0,0 +1,285 @@ +/* + * ChaCha20-Poly1305 AEAD, RFC 8439. + * + * The record-protection cipher for the TLS 1.3 client. ChaCha20 is a pure + * add-rotate-xor stream cipher (no tables, so no data-dependent timing), and + * Poly1305 is its one-time authenticator; together they form the AEAD that + * encrypts and authenticates every TLS record. All integer arithmetic, so it + * is safe in the freestanding kernel. + * + * Poly1305 here is the 32-bit "donna" formulation (public domain), which needs + * only 64-bit intermediate products. + */ +#include +#include + +// =========================================================================== +// ChaCha20 +// =========================================================================== +static inline uint32_t rotl32(uint32_t x, int n) { + return (x << n) | (x >> (32 - n)); +} + +static inline uint32_t load32_le(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 inline void store32_le(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); +} + +#define QR(a, b, c, d) \ + a += b; d ^= a; d = rotl32(d, 16); \ + c += d; b ^= c; b = rotl32(b, 12); \ + a += b; d ^= a; d = rotl32(d, 8); \ + c += d; b ^= c; b = rotl32(b, 7) + +static void chacha20_block(const uint32_t in[16], uint8_t out[64]) { + uint32_t x[16]; + for (int i = 0; i < 16; i++) x[i] = in[i]; + for (int i = 0; i < 10; i++) { + QR(x[0], x[4], x[8], x[12]); + QR(x[1], x[5], x[9], x[13]); + QR(x[2], x[6], x[10], x[14]); + QR(x[3], x[7], x[11], x[15]); + QR(x[0], x[5], x[10], x[15]); + QR(x[1], x[6], x[11], x[12]); + QR(x[2], x[7], x[8], x[13]); + QR(x[3], x[4], x[9], x[14]); + } + for (int i = 0; i < 16; i++) store32_le(out + 4 * i, x[i] + in[i]); +} + +static void chacha20_init(uint32_t state[16], const uint8_t key[32], + uint32_t counter, const uint8_t nonce[12]) { + state[0] = 0x61707865; state[1] = 0x3320646e; + state[2] = 0x79622d32; state[3] = 0x6b206574; + for (int i = 0; i < 8; i++) state[4 + i] = load32_le(key + 4 * i); + state[12] = counter; + state[13] = load32_le(nonce + 0); + state[14] = load32_le(nonce + 4); + state[15] = load32_le(nonce + 8); +} + +// Encrypt/decrypt src into dst (they may alias) starting at block counter. +void chacha20_xor(uint8_t* dst, const uint8_t* src, size_t len, + const uint8_t key[32], uint32_t counter, const uint8_t nonce[12]) { + uint32_t state[16]; + uint8_t block[64]; + chacha20_init(state, key, counter, nonce); + + size_t off = 0; + while (off < len) { + chacha20_block(state, block); + state[12]++; // next block counter + size_t n = len - off < 64 ? len - off : 64; + for (size_t i = 0; i < n; i++) dst[off + i] = src[off + i] ^ block[i]; + off += n; + } +} + +// =========================================================================== +// Poly1305 (32-bit donna) +// =========================================================================== +typedef struct { + uint32_t r[5]; + uint32_t h[5]; + uint32_t pad[4]; + size_t leftover; + uint8_t buffer[16]; + uint8_t final; +} poly1305_ctx; + +static void poly1305_init(poly1305_ctx* st, const uint8_t key[32]) { + st->r[0] = (load32_le(&key[0])) & 0x3ffffff; + st->r[1] = (load32_le(&key[3]) >> 2) & 0x3ffff03; + st->r[2] = (load32_le(&key[6]) >> 4) & 0x3ffc0ff; + st->r[3] = (load32_le(&key[9]) >> 6) & 0x3f03fff; + st->r[4] = (load32_le(&key[12]) >> 8) & 0x00fffff; + + st->h[0] = st->h[1] = st->h[2] = st->h[3] = st->h[4] = 0; + st->pad[0] = load32_le(&key[16]); + st->pad[1] = load32_le(&key[20]); + st->pad[2] = load32_le(&key[24]); + st->pad[3] = load32_le(&key[28]); + st->leftover = 0; + st->final = 0; +} + +static void poly1305_blocks(poly1305_ctx* st, const uint8_t* m, size_t bytes) { + const uint32_t hibit = st->final ? 0 : (1UL << 24); + uint32_t r0 = st->r[0], r1 = st->r[1], r2 = st->r[2], r3 = st->r[3], r4 = st->r[4]; + uint32_t s1 = r1 * 5, s2 = r2 * 5, s3 = r3 * 5, s4 = r4 * 5; + uint32_t h0 = st->h[0], h1 = st->h[1], h2 = st->h[2], h3 = st->h[3], h4 = st->h[4]; + + while (bytes >= 16) { + uint64_t d0, d1, d2, d3, d4; + uint32_t c; + + h0 += (load32_le(m + 0)) & 0x3ffffff; + h1 += (load32_le(m + 3) >> 2) & 0x3ffffff; + h2 += (load32_le(m + 6) >> 4) & 0x3ffffff; + h3 += (load32_le(m + 9) >> 6) & 0x3ffffff; + h4 += (load32_le(m + 12) >> 8) | hibit; + + d0 = (uint64_t)h0 * r0 + (uint64_t)h1 * s4 + (uint64_t)h2 * s3 + (uint64_t)h3 * s2 + (uint64_t)h4 * s1; + d1 = (uint64_t)h0 * r1 + (uint64_t)h1 * r0 + (uint64_t)h2 * s4 + (uint64_t)h3 * s3 + (uint64_t)h4 * s2; + d2 = (uint64_t)h0 * r2 + (uint64_t)h1 * r1 + (uint64_t)h2 * r0 + (uint64_t)h3 * s4 + (uint64_t)h4 * s3; + d3 = (uint64_t)h0 * r3 + (uint64_t)h1 * r2 + (uint64_t)h2 * r1 + (uint64_t)h3 * r0 + (uint64_t)h4 * s4; + 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 & 0x3ffffff; + d1 += c; c = (uint32_t)(d1 >> 26); h1 = (uint32_t)d1 & 0x3ffffff; + d2 += c; c = (uint32_t)(d2 >> 26); h2 = (uint32_t)d2 & 0x3ffffff; + d3 += c; c = (uint32_t)(d3 >> 26); h3 = (uint32_t)d3 & 0x3ffffff; + d4 += c; c = (uint32_t)(d4 >> 26); h4 = (uint32_t)d4 & 0x3ffffff; + h0 += c * 5; c = (h0 >> 26); h0 &= 0x3ffffff; + h1 += c; + + m += 16; + bytes -= 16; + } + + st->h[0] = h0; st->h[1] = h1; st->h[2] = h2; st->h[3] = h3; st->h[4] = h4; +} + +static void poly1305_update(poly1305_ctx* st, const uint8_t* m, size_t bytes) { + if (st->leftover) { + size_t want = 16 - st->leftover; + if (want > bytes) want = bytes; + for (size_t i = 0; i < want; i++) st->buffer[st->leftover + i] = m[i]; + bytes -= want; + m += want; + st->leftover += want; + if (st->leftover < 16) return; + poly1305_blocks(st, st->buffer, 16); + st->leftover = 0; + } + if (bytes >= 16) { + size_t want = bytes & ~(size_t)15; + poly1305_blocks(st, m, want); + m += want; + bytes -= want; + } + for (size_t i = 0; i < bytes; i++) st->buffer[st->leftover + i] = m[i]; + st->leftover += bytes; +} + +static void poly1305_finish(poly1305_ctx* st, uint8_t mac[16]) { + if (st->leftover) { + size_t i = st->leftover; + st->buffer[i++] = 1; + for (; i < 16; i++) st->buffer[i] = 0; + st->final = 1; + poly1305_blocks(st, st->buffer, 16); + } + + uint32_t h0 = st->h[0], h1 = st->h[1], h2 = st->h[2], h3 = st->h[3], h4 = st->h[4]; + uint32_t c; + + c = h1 >> 26; h1 &= 0x3ffffff; + h2 += c; c = h2 >> 26; h2 &= 0x3ffffff; + h3 += c; c = h3 >> 26; h3 &= 0x3ffffff; + h4 += c; c = h4 >> 26; h4 &= 0x3ffffff; + h0 += c * 5; c = h0 >> 26; h0 &= 0x3ffffff; + h1 += c; + + uint32_t g0 = h0 + 5; c = g0 >> 26; g0 &= 0x3ffffff; + uint32_t g1 = h1 + c; c = g1 >> 26; g1 &= 0x3ffffff; + uint32_t g2 = h2 + c; c = g2 >> 26; g2 &= 0x3ffffff; + uint32_t g3 = h3 + c; c = g3 >> 26; g3 &= 0x3ffffff; + uint32_t g4 = h4 + c - (1UL << 26); + + uint32_t mask = (g4 >> 31) - 1; // 0 if g>=p (use g), 0xffffffff if g

> 6) | (h2 << 20); + h2 = (h2 >> 12) | (h3 << 14); + h3 = (h3 >> 18) | (h4 << 8); + + f = (uint64_t)h0 + st->pad[0]; h0 = (uint32_t)f; + f = (uint64_t)h1 + st->pad[1] + (f >> 32); h1 = (uint32_t)f; + f = (uint64_t)h2 + st->pad[2] + (f >> 32); h2 = (uint32_t)f; + f = (uint64_t)h3 + st->pad[3] + (f >> 32); h3 = (uint32_t)f; + + store32_le(mac + 0, h0); + store32_le(mac + 4, h1); + store32_le(mac + 8, h2); + store32_le(mac + 12, h3); +} + +// =========================================================================== +// AEAD construction (RFC 8439 section 2.8) +// =========================================================================== +static void poly1305_key_gen(uint8_t out_key[32], const uint8_t key[32], + const uint8_t nonce[12]) { + uint32_t state[16]; + uint8_t block[64]; + chacha20_init(state, key, 0, nonce); + chacha20_block(state, block); + memcpy(out_key, block, 32); +} + +static const uint8_t poly_zeros[16] = { 0 }; + +static void aead_mac(uint8_t tag[16], const uint8_t poly_key[32], + const uint8_t* aad, size_t aad_len, + const uint8_t* ct, size_t ct_len) { + poly1305_ctx st; + poly1305_init(&st, poly_key); + + poly1305_update(&st, aad, aad_len); + if (aad_len % 16) poly1305_update(&st, poly_zeros, 16 - (aad_len % 16)); + + poly1305_update(&st, ct, ct_len); + if (ct_len % 16) poly1305_update(&st, poly_zeros, 16 - (ct_len % 16)); + + uint8_t lengths[16]; + for (int i = 0; i < 8; i++) lengths[i] = (uint8_t)(aad_len >> (8 * i)); + for (int i = 0; i < 8; i++) lengths[8 + i] = (uint8_t)(ct_len >> (8 * i)); + poly1305_update(&st, lengths, 16); + + poly1305_finish(&st, tag); +} + +// Encrypt plaintext -> ciphertext (same length) and produce a 16-byte tag. +void chacha20poly1305_encrypt(uint8_t* ct, uint8_t tag[16], + const uint8_t key[32], const uint8_t nonce[12], + const uint8_t* aad, size_t aad_len, + const uint8_t* pt, size_t pt_len) { + uint8_t poly_key[32]; + poly1305_key_gen(poly_key, key, nonce); + chacha20_xor(ct, pt, pt_len, key, 1, nonce); // counter starts at 1 + aead_mac(tag, poly_key, aad, aad_len, ct, pt_len); +} + +// Verify tag and decrypt ciphertext -> plaintext. Returns true on success. +bool chacha20poly1305_decrypt(uint8_t* pt, const uint8_t key[32], + const uint8_t nonce[12], + const uint8_t* aad, size_t aad_len, + const uint8_t* ct, size_t ct_len, + const uint8_t tag[16]) { + uint8_t poly_key[32]; + poly1305_key_gen(poly_key, key, nonce); + + uint8_t expect[16]; + aead_mac(expect, poly_key, aad, aad_len, ct, ct_len); + + uint8_t diff = 0; + for (int i = 0; i < 16; i++) diff |= expect[i] ^ tag[i]; + if (diff) return false; // authentication failed + + chacha20_xor(pt, ct, ct_len, key, 1, nonce); + return true; +} diff --git a/crypto/hkdf.c b/crypto/hkdf.c new file mode 100644 index 0000000..cce0835 --- /dev/null +++ b/crypto/hkdf.c @@ -0,0 +1,113 @@ +/* + * HMAC-SHA256 (RFC 2104), HKDF (RFC 5869), and the TLS 1.3 HKDF-Expand-Label + * / Derive-Secret helpers (RFC 8446 section 7.1). + * + * These sit on top of the existing SHA-256 and drive the TLS 1.3 key schedule: + * every traffic key, IV and Finished key is produced by an Expand-Label over a + * secret and the running transcript hash. + */ +#include +#include + +#define SHA256_BLOCK 64 +#define SHA256_LEN 32 + +void hmac_sha256(const uint8_t* key, size_t key_len, + const uint8_t* msg, size_t msg_len, uint8_t out[32]) { + uint8_t k[SHA256_BLOCK]; + uint8_t ipad[SHA256_BLOCK], opad[SHA256_BLOCK]; + + if (key_len > SHA256_BLOCK) { + sha256(key, key_len, k); + memset(k + SHA256_LEN, 0, SHA256_BLOCK - SHA256_LEN); + } else { + memcpy(k, key, key_len); + memset(k + key_len, 0, SHA256_BLOCK - key_len); + } + + for (int i = 0; i < SHA256_BLOCK; i++) { + ipad[i] = k[i] ^ 0x36; + opad[i] = k[i] ^ 0x5c; + } + + uint8_t inner[SHA256_LEN]; + sha256_ctx_t c; + sha256_init(&c); + sha256_update(&c, ipad, SHA256_BLOCK); + sha256_update(&c, msg, msg_len); + sha256_final(inner, &c); + + sha256_init(&c); + sha256_update(&c, opad, SHA256_BLOCK); + sha256_update(&c, inner, SHA256_LEN); + sha256_final(out, &c); +} + +void hkdf_extract(const uint8_t* salt, size_t salt_len, + const uint8_t* ikm, size_t ikm_len, uint8_t prk[32]) { + static const uint8_t zero[SHA256_LEN] = { 0 }; + if (!salt || salt_len == 0) { salt = zero; salt_len = SHA256_LEN; } + hmac_sha256(salt, salt_len, ikm, ikm_len, prk); +} + +void hkdf_expand(const uint8_t prk[32], const uint8_t* info, size_t info_len, + uint8_t* out, size_t out_len) { + uint8_t t[SHA256_LEN]; + size_t t_len = 0; + size_t done = 0; + uint8_t counter = 1; + + while (done < out_len) { + // T(n) = HMAC(PRK, T(n-1) | info | counter) + sha256_ctx_t hc; (void)hc; + uint8_t block[SHA256_LEN + 512 + 1]; // t | info | counter + size_t bl = 0; + if (t_len) { memcpy(block, t, t_len); bl += t_len; } + if (info_len > 512) info_len = 512; + memcpy(block + bl, info, info_len); bl += info_len; + block[bl++] = counter; + + hmac_sha256(prk, SHA256_LEN, block, bl, t); + t_len = SHA256_LEN; + + size_t n = out_len - done < SHA256_LEN ? out_len - done : SHA256_LEN; + memcpy(out + done, t, n); + done += n; + counter++; + } +} + +// TLS 1.3 HKDF-Expand-Label: +// struct { uint16 length; opaque label<7..255>; opaque context<0..255>; } +// label is prefixed with "tls13 ". +void tls13_expand_label(const uint8_t secret[32], const char* label, + const uint8_t* context, size_t context_len, + uint8_t* out, size_t out_len) { + uint8_t info[512]; + size_t p = 0; + + info[p++] = (uint8_t)(out_len >> 8); + info[p++] = (uint8_t)(out_len & 0xff); + + char full_label[64] = "tls13 "; + size_t ll = strlen(full_label); + size_t rest = strlen(label); + if (ll + rest > sizeof(full_label) - 1) rest = sizeof(full_label) - 1 - ll; + memcpy(full_label + ll, label, rest); + size_t label_len = ll + rest; + + info[p++] = (uint8_t)label_len; + memcpy(info + p, full_label, label_len); p += label_len; + + info[p++] = (uint8_t)context_len; + if (context_len) { memcpy(info + p, context, context_len); p += context_len; } + + hkdf_expand(secret, info, p, out, out_len); +} + +// Derive-Secret(secret, label, messages) = Expand-Label(secret, label, +// Hash(messages), Hash.length). `transcript_hash` is that Hash(messages). +void tls13_derive_secret(const uint8_t secret[32], const char* label, + const uint8_t transcript_hash[32], uint8_t out[32]) { + tls13_expand_label(secret, label, transcript_hash, SHA256_LEN, out, SHA256_LEN); +} diff --git a/crypto/x25519.c b/crypto/x25519.c new file mode 100644 index 0000000..3ffe136 --- /dev/null +++ b/crypto/x25519.c @@ -0,0 +1,163 @@ +/* + * X25519 (Curve25519 ECDH), RFC 7748. + * + * This is the compact TweetNaCl field arithmetic (public domain), which uses + * only 64-bit integer operations -- no floating point, no wide multiply beyond + * what the compiler provides -- so it is safe in the freestanding kernel. It is + * the key exchange for the TLS 1.3 client: x25519_base() turns a random private + * scalar into a public key to send, and x25519() combines our private scalar + * with the peer's public key to produce the shared secret. + */ +#include +#include + +typedef int64_t i64; +typedef uint8_t u8; +typedef i64 gf[16]; + +static const gf _121665 = { 0xDB41, 1 }; + +static void car25519(gf o) { + int i; + i64 c; + for (i = 0; i < 16; i++) { + o[i] += (1LL << 16); + c = o[i] >> 16; + o[(i + 1) * (i < 15)] += c - 1 + 37 * (c - 1) * (i == 15); + o[i] -= c << 16; + } +} + +static void sel25519(gf p, gf q, int b) { + i64 t, i, c = ~(b - 1); + for (i = 0; i < 16; i++) { + t = c & (p[i] ^ q[i]); + p[i] ^= t; + q[i] ^= t; + } +} + +static void pack25519(u8* o, const gf n) { + int i, j, b; + gf m, t; + for (i = 0; i < 16; i++) t[i] = n[i]; + car25519(t); + car25519(t); + car25519(t); + for (j = 0; j < 2; j++) { + m[0] = t[0] - 0xffed; + for (i = 1; i < 15; i++) { + m[i] = t[i] - 0xffff - ((m[i - 1] >> 16) & 1); + m[i - 1] &= 0xffff; + } + m[15] = t[15] - 0x7fff - ((m[14] >> 16) & 1); + b = (m[15] >> 16) & 1; + m[14] &= 0xffff; + sel25519(t, m, 1 - b); + } + for (i = 0; i < 16; i++) { + o[2 * i] = t[i] & 0xff; + o[2 * i + 1] = t[i] >> 8; + } +} + +static void unpack25519(gf o, const u8* n) { + int i; + for (i = 0; i < 16; i++) o[i] = n[2 * i] + ((i64)n[2 * i + 1] << 8); + o[15] &= 0x7fff; +} + +static void A(gf o, const gf a, const gf b) { + int i; + for (i = 0; i < 16; i++) o[i] = a[i] + b[i]; +} + +static void Z(gf o, const gf a, const gf b) { + int i; + for (i = 0; i < 16; i++) o[i] = a[i] - b[i]; +} + +static void M(gf o, const gf a, const gf b) { + i64 i, j, t[31]; + for (i = 0; i < 31; i++) t[i] = 0; + for (i = 0; i < 16; i++) + for (j = 0; j < 16; j++) t[i + j] += a[i] * b[j]; + for (i = 0; i < 15; i++) t[i] += 38 * t[i + 16]; + for (i = 0; i < 16; i++) o[i] = t[i]; + car25519(o); + car25519(o); +} + +static void S(gf o, const gf a) { M(o, a, a); } + +static void inv25519(gf o, const gf i) { + gf c; + int a; + for (a = 0; a < 16; a++) c[a] = i[a]; + for (a = 253; a >= 0; a--) { + S(c, c); + if (a != 2 && a != 4) M(c, c, i); + } + for (a = 0; a < 16; a++) o[a] = c[a]; +} + +static int crypto_scalarmult(u8* q, const u8* n, const u8* p) { + u8 z[32]; + i64 x[80], r, i; + gf a, b, c, d, e, f; + for (i = 0; i < 31; i++) z[i] = n[i]; + z[31] = (n[31] & 127) | 64; + z[0] &= 248; + unpack25519(x, p); + for (i = 0; i < 16; i++) { + b[i] = x[i]; + d[i] = a[i] = c[i] = 0; + } + a[0] = d[0] = 1; + for (i = 254; i >= 0; --i) { + r = (z[i >> 3] >> (i & 7)) & 1; + sel25519(a, b, r); + sel25519(c, d, r); + A(e, a, c); + Z(a, a, c); + A(c, b, d); + Z(b, b, d); + S(d, e); + S(f, a); + M(a, c, a); + M(c, b, e); + A(e, a, c); + Z(a, a, c); + S(b, a); + Z(c, d, f); + M(a, c, _121665); + A(a, a, d); + M(c, c, a); + M(a, d, f); + M(d, b, x); + S(b, e); + sel25519(a, b, r); + sel25519(c, d, r); + } + for (i = 0; i < 16; i++) { + x[i + 16] = a[i]; + x[i + 32] = c[i]; + x[i + 48] = b[i]; + x[i + 64] = d[i]; + } + inv25519(x + 32, x + 32); + M(x + 16, x + 16, x + 32); + pack25519(q, x + 16); + return 0; +} + +// The Curve25519 base point, u = 9. +static const u8 x25519_basepoint[32] = { 9 }; + +void x25519_base(uint8_t out_pub[32], const uint8_t priv[32]) { + crypto_scalarmult(out_pub, priv, x25519_basepoint); +} + +void x25519(uint8_t out_shared[32], const uint8_t priv[32], const uint8_t peer_pub[32]) { + crypto_scalarmult(out_shared, priv, peer_pub); +} diff --git a/gui/gui_app_web.c b/gui/gui_app_web.c index 2c6f645..9edfd15 100644 --- a/gui/gui_app_web.c +++ b/gui/gui_app_web.c @@ -631,7 +631,7 @@ void gui_app_web_launch(int x, int y, int w, int h) { if (!wd->text) { kfree(wd); gui_wm_destroy_window(win->id); return; } wd->reader = true; - strcpy(wd->url, "10.0.2.2:8000/"); // the host, reachable under QEMU slirp + strcpy(wd->url, "https://example.com/"); // a real HTTPS page as the default win->user_data = wd; win->paint = web_paint; diff --git a/include/crypto/crypto.h b/include/crypto/crypto.h index 19f4b94..79024f3 100644 --- a/include/crypto/crypto.h +++ b/include/crypto/crypto.h @@ -46,6 +46,36 @@ static inline void sha256(const void* data, size_t len, uint8_t hash[32]) { sha256_final(hash, &ctx); } +// X25519 (Curve25519 ECDH), RFC 7748. +void x25519_base(uint8_t out_pub[32], const uint8_t priv[32]); +void x25519(uint8_t out_shared[32], const uint8_t priv[32], const uint8_t peer_pub[32]); + +// ChaCha20-Poly1305 AEAD, RFC 8439. +void chacha20_xor(uint8_t* dst, const uint8_t* src, size_t len, + const uint8_t key[32], uint32_t counter, const uint8_t nonce[12]); +void chacha20poly1305_encrypt(uint8_t* ct, uint8_t tag[16], + const uint8_t key[32], const uint8_t nonce[12], + const uint8_t* aad, size_t aad_len, + const uint8_t* pt, size_t pt_len); +bool chacha20poly1305_decrypt(uint8_t* pt, const uint8_t key[32], + const uint8_t nonce[12], + const uint8_t* aad, size_t aad_len, + const uint8_t* ct, size_t ct_len, + const uint8_t tag[16]); + +// HMAC-SHA256 / HKDF (RFC 2104, 5869) and the TLS 1.3 key-schedule helpers. +void hmac_sha256(const uint8_t* key, size_t key_len, + const uint8_t* msg, size_t msg_len, uint8_t out[32]); +void hkdf_extract(const uint8_t* salt, size_t salt_len, + const uint8_t* ikm, size_t ikm_len, uint8_t prk[32]); +void hkdf_expand(const uint8_t prk[32], const uint8_t* info, size_t info_len, + uint8_t* out, size_t out_len); +void tls13_expand_label(const uint8_t secret[32], const char* label, + const uint8_t* context, size_t context_len, + uint8_t* out, size_t out_len); +void tls13_derive_secret(const uint8_t secret[32], const char* label, + const uint8_t transcript_hash[32], uint8_t out[32]); + // PRNG (Cryptographic Pseudo-Random Number Generator) void prng_seed(uint64_t seed1, uint64_t seed2); uint64_t prng_rand64(void); diff --git a/include/net/tls.h b/include/net/tls.h new file mode 100644 index 0000000..69b78d8 --- /dev/null +++ b/include/net/tls.h @@ -0,0 +1,41 @@ +#ifndef _NET_TLS_H +#define _NET_TLS_H + +#include +#include +#include + +// Minimal TLS 1.3 client (RFC 8446), enough to fetch an HTTPS page. +// +// Scope, stated plainly: +// - TLS 1.3 only, one cipher suite (TLS_CHACHA20_POLY1305_SHA256) and one +// key exchange (X25519). These are universally supported by modern servers. +// - The server certificate is NOT verified. The handshake completes and the +// channel is encrypted, but a man-in-the-middle could impersonate the peer. +// Real certificate-chain validation (X.509 parsing, signature checks, a CA +// bundle, hostname matching) is a large separate piece not built here. +// +// The calls mirror the TCP client and block the same cooperative way, so they +// run on the async HTTP worker thread, not the compositor. + +typedef struct tls_conn tls_conn_t; + +// Open a TLS connection to ip:port, using `host` for SNI. Returns NULL on any +// failure (connect, handshake, unsupported server parameters). +tls_conn_t* tls_connect(const char* host, uint32_t ip, uint16_t port, + uint32_t timeout_ms); + +// Send application data (the HTTP request). Returns bytes sent or -1. +int tls_send(tls_conn_t* c, const void* data, int len); + +// Receive up to len bytes of application data, waiting up to timeout_ms. +// Returns the byte count, 0 at clean end of stream, or -1 on error. +int tls_recv(tls_conn_t* c, void* buf, int len, uint32_t timeout_ms); + +// Shut down and release the connection. +void tls_close(tls_conn_t* c); + +// A short reason for the last failure, for diagnostics. +const char* tls_last_error(void); + +#endif // _NET_TLS_H diff --git a/net/http_client.c b/net/http_client.c index 3ca87f6..7e010a3 100644 --- a/net/http_client.c +++ b/net/http_client.c @@ -14,6 +14,7 @@ */ #include #include +#include #include #include #include @@ -142,13 +143,10 @@ static bool apply_redirect(http_fetch_t* f, const char* location) { snprintf(f->err, sizeof(f->err), "redirect to a URL we cannot parse"); return false; } - if (https) { - snprintf(f->err, sizeof(f->err), "redirect to HTTPS (TLS unsupported): %s", host); - return false; - } strcpy(f->host, host); strcpy(f->path, path); f->port = port; + f->tls = https; // follow http->https upgrades; TLS handles it return true; } @@ -176,8 +174,9 @@ static int parse_status(const char* buf, int len) { // =========================================================================== #define HTTP_MAX_REDIRECTS 5 -// Perform one request/response against the current host/path/port. Returns the -// HTTP status code, or a negative error code; the raw response lands in f->buf. +// Perform one request/response against the current host/path/port, over TLS +// when the URL is https. Returns the HTTP status code, or a negative error +// code; the raw response lands in f->buf. static int do_one_request(http_fetch_t* f) { f->ip = ip_parse(f->host); if (f->ip == 0) f->ip = dns_resolve(f->host); @@ -186,33 +185,53 @@ static int do_one_request(http_fetch_t* f) { return -1; } - tcp_conn_t* c = tcp_connect(f->ip, f->port, HTTP_CONNECT_TIMEOUT_MS); - if (!c) { - snprintf(f->err, sizeof(f->err), "connect to %s:%u refused/timed out", - f->host, f->port); - return -1; - } - char req[512]; int rl = snprintf(req, sizeof(req), "GET %s HTTP/1.0\r\nHost: %s\r\nUser-Agent: SUB-OS/webfetch\r\n" "Connection: close\r\n\r\n", f->path, f->host); - if (tcp_send(c, req, (uint16_t)rl) < 0) { - snprintf(f->err, sizeof(f->err), "send failed"); - tcp_close(c); - return -1; - } f->len = 0; - while (f->len < f->cap - 1) { - int n = tcp_recv(c, f->buf + f->len, (uint16_t)(f->cap - 1 - f->len), - HTTP_RECV_TIMEOUT_MS); - if (n <= 0) break; - f->len += n; - } - f->buf[f->len] = '\0'; - tcp_close(c); + if (f->tls) { + tls_conn_t* c = tls_connect(f->host, f->ip, f->port, HTTP_CONNECT_TIMEOUT_MS); + if (!c) { + snprintf(f->err, sizeof(f->err), "TLS to %s:%u failed: %s", + f->host, f->port, tls_last_error()); + return -1; + } + if (tls_send(c, req, rl) < 0) { + snprintf(f->err, sizeof(f->err), "TLS send failed"); + tls_close(c); + return -1; + } + while (f->len < f->cap - 1) { + int n = tls_recv(c, f->buf + f->len, f->cap - 1 - f->len, HTTP_RECV_TIMEOUT_MS); + if (n <= 0) break; + f->len += n; + } + tls_close(c); + } else { + tcp_conn_t* c = tcp_connect(f->ip, f->port, HTTP_CONNECT_TIMEOUT_MS); + if (!c) { + snprintf(f->err, sizeof(f->err), "connect to %s:%u refused/timed out", + f->host, f->port); + return -1; + } + if (tcp_send(c, req, (uint16_t)rl) < 0) { + snprintf(f->err, sizeof(f->err), "send failed"); + tcp_close(c); + return -1; + } + while (f->len < f->cap - 1) { + int n = tcp_recv(c, f->buf + f->len, (uint16_t)(f->cap - 1 - f->len), + HTTP_RECV_TIMEOUT_MS); + if (n <= 0) break; + f->len += n; + } + tcp_close(c); + } + + f->buf[f->len] = '\0'; if (f->len == 0) { snprintf(f->err, sizeof(f->err), "no data received"); return -1; @@ -225,21 +244,13 @@ static int do_one_request(http_fetch_t* f) { // Returning the state keeps the job private to the worker until that hand-off. // // A 3xx response with a Location header is followed, up to a bounded number of -// hops, so a real-world URL that redirects (a bare host to a path, http to a -// canonical host) resolves to actual content. An https target is reported -// rather than followed, since this client has no TLS. +// hops, so a real-world URL that redirects (a bare host to a path, http to +// https, a canonical host) resolves to actual content -- including the very +// common http->https upgrade, now that TLS is available. static int do_fetch(http_fetch_t* f) { uint64_t t0 = pit_get_ticks(); f->redirects = 0; - // The client has no TLS, so an https URL cannot be served. Say so plainly - // rather than sending a plaintext request to port 443 and reporting a - // confusing timeout. - if (f->tls) { - snprintf(f->err, sizeof(f->err), "%s is HTTPS; this client has no TLS", f->host); - return HTTP_STATE_ERROR; - } - for (;;) { int status = do_one_request(f); if (status < 0) return HTTP_STATE_ERROR; // f->err already set @@ -249,7 +260,7 @@ static int do_fetch(http_fetch_t* f) { char location[512]; if (is_redirect && f->redirects < HTTP_MAX_REDIRECTS && find_header(f->buf, f->len, "Location", location, sizeof(location))) { - if (!apply_redirect(f, location)) return HTTP_STATE_ERROR; // e.g. HTTPS + if (!apply_redirect(f, location)) return HTTP_STATE_ERROR; f->redirects++; continue; } diff --git a/net/tls.c b/net/tls.c new file mode 100644 index 0000000..c37bbaa --- /dev/null +++ b/net/tls.c @@ -0,0 +1,572 @@ +/* + * TLS 1.3 client for SUB-OS (RFC 8446), single suite: + * TLS_CHACHA20_POLY1305_SHA256 over X25519. See net/tls.h for the scope and + * the honest security caveat (the server certificate is not verified). + * + * Structure: + * - a small record layer (read/write, plaintext then AEAD-protected), + * - the handshake: ClientHello -> ServerHello -> key schedule -> decrypt the + * server flight -> send Finished -> switch to application keys, + * - the TLS 1.3 key schedule driven by crypto/hkdf.c over a running + * transcript hash. + * + * All large buffers live in the heap-allocated connection object, never on the + * stack, because this runs on the 16 KB worker-thread stack. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#define TLS_PLAINTEXT_MAX 16384 +#define TLS_REC_MAX (TLS_PLAINTEXT_MAX + 256) + +// Content types. +#define CT_CHANGE_CIPHER_SPEC 20 +#define CT_ALERT 21 +#define CT_HANDSHAKE 22 +#define CT_APPLICATION_DATA 23 + +// Handshake message types. +#define HS_CLIENT_HELLO 1 +#define HS_SERVER_HELLO 2 +#define HS_NEW_SESSION_TICKET 4 +#define HS_ENCRYPTED_EXT 8 +#define HS_CERTIFICATE 11 +#define HS_CERT_REQUEST 13 +#define HS_CERT_VERIFY 15 +#define HS_FINISHED 20 + +#define TLS_AEAD_TAG 16 + +struct tls_conn { + tcp_conn_t* tcp; + + sha256_ctx_t transcript; + + uint8_t priv[32]; // our X25519 private scalar + uint8_t handshake_secret[32]; + uint8_t c_hs_secret[32]; // client handshake traffic secret + + uint8_t c_hs_key[32], c_hs_iv[12]; + uint8_t s_hs_key[32], s_hs_iv[12]; + uint8_t c_ap_key[32], c_ap_iv[12]; + uint8_t s_ap_key[32], s_ap_iv[12]; + + uint64_t send_seq; + uint64_t recv_seq; + bool app_keys; // switched from handshake to application keys + bool established; + + // Decrypted application-data left over from a record, awaiting the reader. + uint8_t rbuf[TLS_PLAINTEXT_MAX]; + int rbuf_len, rbuf_off; + + // Decrypted handshake bytes of the current record, consumed by hs_getbytes. + uint8_t hs_cur[TLS_PLAINTEXT_MAX]; + int hs_cur_len, hs_cur_off; + + // Scratch for record assembly / reads (kept off the stack). + uint8_t rec[TLS_REC_MAX]; + uint8_t pt[TLS_PLAINTEXT_MAX + 1]; +}; + +static char g_tls_err[96] = ""; +static void tls_err(const char* m) { + strncpy(g_tls_err, m, sizeof(g_tls_err) - 1); + g_tls_err[sizeof(g_tls_err) - 1] = '\0'; +} +const char* tls_last_error(void) { return g_tls_err; } + +// =========================================================================== +// TCP framing helpers +// =========================================================================== +static bool read_exact(tcp_conn_t* c, uint8_t* buf, int n, uint32_t timeout_ms) { + int got = 0; + while (got < n) { + int r = tcp_recv(c, buf + got, (uint16_t)(n - got), timeout_ms); + if (r <= 0) return false; + got += r; + } + return true; +} + +static bool send_all(tcp_conn_t* c, const uint8_t* buf, int n) { + return tcp_send(c, buf, (uint16_t)n) == n; +} + +// =========================================================================== +// Transcript hash +// =========================================================================== +static void transcript_add(tls_conn_t* t, const uint8_t* data, int len) { + sha256_update(&t->transcript, data, len); +} +static void transcript_hash(tls_conn_t* t, uint8_t out[32]) { + sha256_ctx_t copy = t->transcript; // snapshot; hashing is non-destructive + sha256_final(out, ©); +} + +// =========================================================================== +// Key schedule (RFC 8446 section 7.1) +// =========================================================================== +static void derive_traffic_keys(const uint8_t secret[32], + uint8_t key[32], uint8_t iv[12]) { + tls13_expand_label(secret, "key", NULL, 0, key, 32); + tls13_expand_label(secret, "iv", NULL, 0, iv, 12); +} + +// After ClientHello and ServerHello are in the transcript, derive the +// handshake traffic keys from the X25519 shared secret. +static void key_schedule_handshake(tls_conn_t* t, const uint8_t shared[32]) { + uint8_t empty_hash[32]; + sha256((const uint8_t*)"", 0, empty_hash); + + uint8_t psk[32] = { 0 }; + uint8_t early_secret[32]; + hkdf_extract(NULL, 0, psk, 32, early_secret); + + uint8_t derived[32]; + tls13_derive_secret(early_secret, "derived", empty_hash, derived); + hkdf_extract(derived, 32, shared, 32, t->handshake_secret); + + uint8_t th[32]; + transcript_hash(t, th); // CH || SH + + uint8_t s_hs_secret[32]; + tls13_derive_secret(t->handshake_secret, "c hs traffic", th, t->c_hs_secret); + tls13_derive_secret(t->handshake_secret, "s hs traffic", th, s_hs_secret); + + derive_traffic_keys(t->c_hs_secret, t->c_hs_key, t->c_hs_iv); + derive_traffic_keys(s_hs_secret, t->s_hs_key, t->s_hs_iv); + + t->send_seq = t->recv_seq = 0; +} + +// After the server Finished is in the transcript, derive the application keys. +static void key_schedule_application(tls_conn_t* t) { + uint8_t empty_hash[32]; + sha256((const uint8_t*)"", 0, empty_hash); + + uint8_t derived[32]; + tls13_derive_secret(t->handshake_secret, "derived", empty_hash, derived); + + uint8_t psk[32] = { 0 }; + uint8_t master[32]; + hkdf_extract(derived, 32, psk, 32, master); + + uint8_t th[32]; + transcript_hash(t, th); // CH .. server Finished + + uint8_t c_ap_secret[32], s_ap_secret[32]; + tls13_derive_secret(master, "c ap traffic", th, c_ap_secret); + tls13_derive_secret(master, "s ap traffic", th, s_ap_secret); + + derive_traffic_keys(c_ap_secret, t->c_ap_key, t->c_ap_iv); + derive_traffic_keys(s_ap_secret, t->s_ap_key, t->s_ap_iv); +} + +// =========================================================================== +// Record layer +// =========================================================================== +static void build_nonce(uint8_t nonce[12], const uint8_t iv[12], uint64_t seq) { + memcpy(nonce, iv, 12); + for (int i = 0; i < 8; i++) nonce[11 - i] ^= (uint8_t)(seq >> (8 * i)); +} + +// Encrypt `data` (of inner content type `inner_type`) as one record and send it. +static bool write_record(tls_conn_t* t, uint8_t inner_type, + const uint8_t* data, int len) { + const uint8_t* key = t->app_keys ? t->c_ap_key : t->c_hs_key; + const uint8_t* iv = t->app_keys ? t->c_ap_iv : t->c_hs_iv; + + int ptlen = len + 1; // data || inner_type + int reclen = ptlen + TLS_AEAD_TAG; + + uint8_t* out = t->rec; + out[0] = CT_APPLICATION_DATA; + out[1] = 0x03; out[2] = 0x03; + out[3] = (uint8_t)(reclen >> 8); + out[4] = (uint8_t)(reclen & 0xff); + + memcpy(t->pt, data, len); + t->pt[len] = inner_type; + + uint8_t nonce[12]; + build_nonce(nonce, iv, t->send_seq); + + uint8_t tag[16]; + chacha20poly1305_encrypt(out + 5, tag, key, nonce, out, 5, t->pt, ptlen); + memcpy(out + 5 + ptlen, tag, TLS_AEAD_TAG); + + t->send_seq++; + return send_all(t->tcp, out, 5 + reclen); +} + +// Read one record. On an encrypted record, decrypt it and return the inner +// content type in *inner_type and the plaintext length; ChangeCipherSpec +// records are skipped transparently. Cleartext handshake (ServerHello) is +// returned as-is. Returns -1 on error. +static int read_record(tls_conn_t* t, uint8_t* inner_type, + uint8_t* out, int outcap, uint32_t timeout_ms) { + for (;;) { + uint8_t hdr[5]; + if (!read_exact(t->tcp, hdr, 5, timeout_ms)) { tls_err("record header read failed"); return -1; } + int type = hdr[0]; + int len = (hdr[3] << 8) | hdr[4]; + if (len < 0 || len > TLS_REC_MAX) { tls_err("record too large"); return -1; } + + if (!read_exact(t->tcp, t->rec, len, timeout_ms)) { tls_err("record body read failed"); return -1; } + + if (type == CT_CHANGE_CIPHER_SPEC) continue; // middlebox compat: ignore + + if (type == CT_HANDSHAKE) { + // Cleartext handshake (only ServerHello before keys are set). + if (len > outcap) { tls_err("handshake record too large"); return -1; } + memcpy(out, t->rec, len); + *inner_type = CT_HANDSHAKE; + return len; + } + + if (type == CT_ALERT && !t->app_keys && t->recv_seq == 0 && t->send_seq == 0) { + tls_err("server sent a cleartext alert"); + return -1; + } + + if (type == CT_APPLICATION_DATA) { + const uint8_t* key = t->app_keys ? t->s_ap_key : t->s_hs_key; + const uint8_t* iv = t->app_keys ? t->s_ap_iv : t->s_hs_iv; + if (len < TLS_AEAD_TAG) { tls_err("short encrypted record"); return -1; } + int ct_len = len - TLS_AEAD_TAG; + + uint8_t nonce[12]; + build_nonce(nonce, iv, t->recv_seq); + + if (!chacha20poly1305_decrypt(t->pt, key, nonce, hdr, 5, + t->rec, ct_len, t->rec + ct_len)) { + tls_err("record decrypt/auth failed"); + return -1; + } + t->recv_seq++; + + // Strip zero padding; the last non-zero byte is the inner type. + int n = ct_len; + while (n > 0 && t->pt[n - 1] == 0) n--; + if (n == 0) { tls_err("empty inner plaintext"); return -1; } + *inner_type = t->pt[n - 1]; + int body = n - 1; + if (body > outcap) { tls_err("inner plaintext too large"); return -1; } + memcpy(out, t->pt, body); + return body; + } + + tls_err("unexpected record type"); + return -1; + } +} + +// =========================================================================== +// Handshake byte stream (server flight), feeding the transcript as it goes +// =========================================================================== +static bool hs_getbytes(tls_conn_t* t, uint8_t* dst, int n, uint32_t timeout_ms) { + int got = 0; + while (got < n) { + if (t->hs_cur_off >= t->hs_cur_len) { + uint8_t inner_type; + int r = read_record(t, &inner_type, t->hs_cur, sizeof(t->hs_cur), timeout_ms); + if (r < 0) return false; + if (inner_type == CT_ALERT) { tls_err("alert during handshake"); return false; } + if (inner_type != CT_HANDSHAKE) { tls_err("non-handshake in flight"); return false; } + transcript_add(t, t->hs_cur, r); // every handshake byte is hashed + t->hs_cur_len = r; + t->hs_cur_off = 0; + } + int take = t->hs_cur_len - t->hs_cur_off; + if (take > n - got) take = n - got; + memcpy(dst + got, t->hs_cur + t->hs_cur_off, take); + t->hs_cur_off += take; + got += take; + } + return true; +} + +// =========================================================================== +// ClientHello +// =========================================================================== +// A tiny append-and-backpatch buffer. +typedef struct { uint8_t* p; int len; int cap; } buf_t; +static void bput(buf_t* b, uint8_t v) { if (b->len < b->cap) b->p[b->len] = v; b->len++; } +static void bput16(buf_t* b, uint16_t v) { bput(b, v >> 8); bput(b, v & 0xff); } +static void bputn(buf_t* b, const uint8_t* d, int n) { for (int i = 0; i < n; i++) bput(b, d[i]); } + +static bool send_client_hello(tls_conn_t* t, const char* host, const uint8_t pub[32]) { + uint8_t body[1024]; + buf_t b = { body, 0, sizeof(body) }; + + bput16(&b, 0x0303); // legacy_version + uint8_t random[32]; + prng_get_bytes(random, 32); + bputn(&b, random, 32); + + uint8_t sid[32]; + prng_get_bytes(sid, 32); + bput(&b, 32); bputn(&b, sid, 32); // legacy_session_id (compat) + + bput16(&b, 2); bput16(&b, 0x1303); // cipher_suites: chacha20-poly1305 + bput(&b, 1); bput(&b, 0); // compression: null + + // Extensions. + int ext_len_at = b.len; + bput16(&b, 0); // placeholder for extensions length + int ext_start = b.len; + + // supported_versions (43): TLS 1.3 + bput16(&b, 43); bput16(&b, 3); bput(&b, 2); bput16(&b, 0x0304); + + // supported_groups (10): x25519 + bput16(&b, 10); bput16(&b, 4); bput16(&b, 2); bput16(&b, 0x001d); + + // signature_algorithms (13): a common set (unused -- we do not verify) + static const uint16_t sigs[] = { 0x0403, 0x0804, 0x0401, 0x0805, 0x0806, 0x0807 }; + int nsig = (int)(sizeof(sigs) / sizeof(sigs[0])); + bput16(&b, 13); bput16(&b, 2 + nsig * 2); bput16(&b, nsig * 2); + for (int i = 0; i < nsig; i++) bput16(&b, sigs[i]); + + // key_share (51): x25519 public key + bput16(&b, 51); bput16(&b, 38); bput16(&b, 36); + bput16(&b, 0x001d); bput16(&b, 32); bputn(&b, pub, 32); + + // server_name (0): SNI + int hlen = (int)strlen(host); + bput16(&b, 0); bput16(&b, hlen + 5); bput16(&b, hlen + 3); + bput(&b, 0); bput16(&b, hlen); bputn(&b, (const uint8_t*)host, hlen); + + // ALPN (16): http/1.1 + bput16(&b, 16); bput16(&b, 11); bput16(&b, 9); bput(&b, 8); + bputn(&b, (const uint8_t*)"http/1.1", 8); + + int ext_len = b.len - ext_start; + body[ext_len_at] = (uint8_t)(ext_len >> 8); + body[ext_len_at + 1] = (uint8_t)(ext_len & 0xff); + + if (b.len > (int)sizeof(body)) { tls_err("ClientHello overflow"); return false; } + + // Wrap in a handshake header, then a plaintext record. + uint8_t hs[4 + sizeof(body)]; + hs[0] = HS_CLIENT_HELLO; + hs[1] = (uint8_t)(b.len >> 16); + hs[2] = (uint8_t)(b.len >> 8); + hs[3] = (uint8_t)(b.len); + memcpy(hs + 4, body, b.len); + int hs_len = 4 + b.len; + + transcript_add(t, hs, hs_len); + + uint8_t rec[5 + 4 + sizeof(body)]; + rec[0] = CT_HANDSHAKE; rec[1] = 0x03; rec[2] = 0x01; // legacy 0x0301 for CH + rec[3] = (uint8_t)(hs_len >> 8); + rec[4] = (uint8_t)(hs_len & 0xff); + memcpy(rec + 5, hs, hs_len); + return send_all(t->tcp, rec, 5 + hs_len); +} + +// =========================================================================== +// ServerHello +// =========================================================================== +static bool parse_server_hello(tls_conn_t* t, const uint8_t* msg, int len, + uint8_t server_pub[32]) { + // msg is the handshake message: type(1) len(3) body. + if (len < 4 || msg[0] != HS_SERVER_HELLO) { tls_err("expected ServerHello"); return false; } + const uint8_t* p = msg + 4; + const uint8_t* end = msg + len; + + if (end - p < 2 + 32 + 1) { tls_err("ServerHello truncated"); return false; } + p += 2; // legacy_version + p += 32; // random + int sid_len = *p++; + if (end - p < sid_len + 3) { tls_err("ServerHello truncated"); return false; } + p += sid_len; // session id echo + + uint16_t cipher = ((uint16_t)p[0] << 8) | p[1]; p += 2; + if (cipher != 0x1303) { tls_err("server chose an unsupported cipher"); return false; } + p += 1; // compression + + if (end - p < 2) { tls_err("ServerHello no extensions"); return false; } + int ext_total = (p[0] << 8) | p[1]; p += 2; + const uint8_t* ext_end = p + ext_total; + if (ext_end > end) { tls_err("ServerHello ext overflow"); return false; } + + bool got_key = false; + while (p + 4 <= ext_end) { + uint16_t etype = ((uint16_t)p[0] << 8) | p[1]; + uint16_t elen = ((uint16_t)p[2] << 8) | p[3]; + p += 4; + if (p + elen > ext_end) { tls_err("ServerHello ext len"); return false; } + + if (etype == 51) { // key_share + if (elen < 4) { tls_err("bad key_share"); return false; } + uint16_t group = ((uint16_t)p[0] << 8) | p[1]; + uint16_t klen = ((uint16_t)p[2] << 8) | p[3]; + if (group != 0x001d || klen != 32 || elen < 4 + 32) { + tls_err("server key_share is not X25519"); + return false; + } + memcpy(server_pub, p + 4, 32); + got_key = true; + } + p += elen; + } + if (!got_key) { tls_err("server sent no X25519 key_share (HelloRetry?)"); return false; } + return true; +} + +// =========================================================================== +// Server flight: EncryptedExtensions, Certificate*, CertVerify, Finished +// =========================================================================== +static bool read_server_flight(tls_conn_t* t, uint32_t timeout_ms) { + for (;;) { + uint8_t hdr[4]; + if (!hs_getbytes(t, hdr, 4, timeout_ms)) return false; + int htype = hdr[0]; + int hlen = (hdr[1] << 16) | (hdr[2] << 8) | hdr[3]; + + // Consume (and discard) the message body; every byte was already fed to + // the transcript by hs_getbytes. + uint8_t skip[512]; + int left = hlen; + while (left > 0) { + int chunk = left < (int)sizeof(skip) ? left : (int)sizeof(skip); + if (!hs_getbytes(t, skip, chunk, timeout_ms)) return false; + left -= chunk; + } + + if (htype == HS_FINISHED) return true; // end of the server flight + if (htype == HS_CERT_REQUEST) { + // We do not present a client certificate; most public servers do not + // request one. Proceeding without it is the documented limitation. + } + // EE / Certificate / CertificateVerify: accepted without verification. + } +} + +// =========================================================================== +// Client Finished +// =========================================================================== +static bool send_client_finished(tls_conn_t* t) { + uint8_t finished_key[32]; + tls13_expand_label(t->c_hs_secret, "finished", NULL, 0, finished_key, 32); + + uint8_t th[32]; + transcript_hash(t, th); // CH .. server Finished + + uint8_t verify[32]; + hmac_sha256(finished_key, 32, th, 32, verify); + + uint8_t msg[4 + 32]; + msg[0] = HS_FINISHED; msg[1] = 0; msg[2] = 0; msg[3] = 32; + memcpy(msg + 4, verify, 32); + + // Sent under handshake keys. + return write_record(t, CT_HANDSHAKE, msg, sizeof(msg)); +} + +// =========================================================================== +// Public API +// =========================================================================== +tls_conn_t* tls_connect(const char* host, uint32_t ip, uint16_t port, + uint32_t timeout_ms) { + tls_err(""); + if (!host || ip == 0 || port == 0) { tls_err("bad arguments"); return NULL; } + + tls_conn_t* t = (tls_conn_t*)kzalloc(sizeof(tls_conn_t)); + if (!t) { tls_err("out of memory"); return NULL; } + + sha256_init(&t->transcript); + + t->tcp = tcp_connect(ip, port, timeout_ms); + if (!t->tcp) { tls_err("TCP connect failed"); kfree(t); return NULL; } + + uint8_t pub[32]; + prng_get_bytes(t->priv, 32); + x25519_base(pub, t->priv); + + if (!send_client_hello(t, host, pub)) { tls_err("ClientHello send failed"); goto fail; } + + // ServerHello (cleartext handshake record). + uint8_t inner_type; + int shlen = read_record(t, &inner_type, t->hs_cur, sizeof(t->hs_cur), timeout_ms); + if (shlen < 0) goto fail; + if (inner_type != CT_HANDSHAKE) { tls_err("expected ServerHello record"); goto fail; } + transcript_add(t, t->hs_cur, shlen); + + uint8_t server_pub[32]; + if (!parse_server_hello(t, t->hs_cur, shlen, server_pub)) goto fail; + + uint8_t shared[32]; + x25519(shared, t->priv, server_pub); + key_schedule_handshake(t, shared); + + // Server flight is now encrypted under the server handshake key. + if (!read_server_flight(t, timeout_ms)) goto fail; + + // Application keys are derived over CH..server Finished. + key_schedule_application(t); + + if (!send_client_finished(t)) { tls_err("Finished send failed"); goto fail; } + + // From here on both directions use the application keys. + t->app_keys = true; + t->send_seq = t->recv_seq = 0; + t->established = true; + return t; + +fail: + if (t->tcp) tcp_close(t->tcp); + kfree(t); + return NULL; +} + +int tls_send(tls_conn_t* t, const void* data, int len) { + if (!t || !t->established) return -1; + const uint8_t* p = (const uint8_t*)data; + int sent = 0; + while (sent < len) { + int chunk = len - sent; + if (chunk > TLS_PLAINTEXT_MAX) chunk = TLS_PLAINTEXT_MAX; + if (!write_record(t, CT_APPLICATION_DATA, p + sent, chunk)) return sent ? sent : -1; + sent += chunk; + } + return sent; +} + +int tls_recv(tls_conn_t* t, void* buf, int len, uint32_t timeout_ms) { + if (!t || !t->established) return -1; + + // Serve any leftover decrypted application data first. + if (t->rbuf_off >= t->rbuf_len) { + for (;;) { + uint8_t inner_type; + int r = read_record(t, &inner_type, t->rbuf, sizeof(t->rbuf), timeout_ms); + if (r < 0) return -1; + if (inner_type == CT_APPLICATION_DATA) { t->rbuf_len = r; t->rbuf_off = 0; break; } + if (inner_type == CT_HANDSHAKE) continue; // NewSessionTicket etc.: ignore + if (inner_type == CT_ALERT) return 0; // close_notify or fatal: end of stream + // anything else: ignore and keep reading + } + } + + int avail = t->rbuf_len - t->rbuf_off; + int n = avail < len ? avail : len; + memcpy(buf, t->rbuf + t->rbuf_off, n); + t->rbuf_off += n; + return n; +} + +void tls_close(tls_conn_t* t) { + if (!t) return; + if (t->tcp) tcp_close(t->tcp); + kfree(t); +}