mirror of
https://github.com/subhobhai943/SUB-OS.git
synced 2026-10-25 16:52:29 +00:00
feat(crypto,net): TLS 1.3 client -- SUB-OS can now browse HTTPS sites
The browser could only reach plain HTTP, which is almost none of the real
web. This adds a from-scratch TLS 1.3 client so it can fetch https:// pages,
including real sites on the open internet.
Crypto primitives (all integer-only, freestanding, verified on the host
against their RFC test vectors before going in the kernel):
- crypto/x25519.c X25519 ECDH (RFC 7748), compact TweetNaCl field
arithmetic.
- crypto/chacha20poly1305.c ChaCha20 + Poly1305 AEAD (RFC 8439).
- crypto/hkdf.c HMAC-SHA256, HKDF (RFC 2104/5869) and the TLS 1.3
HKDF-Expand-Label / Derive-Secret helpers, on top
of the existing SHA-256.
A host harness checked every one against its published vectors (X25519 s6.1,
ChaCha20 s2.4.2, AEAD s2.8.2 incl. tamper rejection, HKDF TC1) -- all pass.
TLS 1.3 client (net/tls.c), single suite TLS_CHACHA20_POLY1305_SHA256 over
X25519: ClientHello (with SNI and ALPN http/1.1) -> ServerHello -> the RFC 8446
key schedule over a running transcript hash -> decrypt the server flight ->
send Finished -> switch to application keys. A small record layer handles the
plaintext-then-AEAD split and the middlebox ChangeCipherSpec. All large buffers
live in the heap-allocated connection, never on the 16 KB worker stack.
http_client routes https:// through TLS instead of raw TCP, so the async
worker, webget, netcheck and the GUI Web app all get HTTPS for free; the
redirect follower now follows http->https upgrades. The Web app's default page
is now https://example.com/.
Verified end to end on x86_64: the handshake interoperates with openssl
s_server (TLS 1.3, ChaCha20) -- GET returned HTTP 200 with the decrypted body
-- and, over QEMU's NAT, `webget https://example.com/` and the GUI Web app both
loaded the real Example Domain page served by Cloudflare over TLS 1.3 (HTTP
200, 828 bytes). The server only accepts our Finished if the transcript hash
and key schedule match byte-for-byte, so a 200 is proof the whole stack is
correct. Builds clean on x86_64, aarch64 and armv8i.
SECURITY CAVEAT, stated plainly: the server certificate is NOT verified. The
channel is encrypted and interoperates, 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 and
is not built here. This is fine for a hobby OS over a trusted local NAT; it is
not fit to protect real secrets. Also: TLS 1.3 only, one cipher suite, and
still no JavaScript/media -- so JS-heavy or app-style sites (YouTube) remain
out of reach; this makes static/text HTTPS pages readable.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
This commit is contained in:
1 parent
9f90361265
commit
8a55ac345b
9 files changed
+1257
-41
No files matched your search
@@ -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
|
||||
|
||||
@@ -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 <crypto/crypto.h>
|
||||
#include <lib/string.h>
|
||||
|
||||
// ===========================================================================
|
||||
// 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<p (use h)
|
||||
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;
|
||||
|
||||
// Serialise h into 128-bit little-endian, add pad.
|
||||
uint64_t f;
|
||||
h0 = (h0) | (h1 << 26);
|
||||
h1 = (h1 >> 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;
|
||||
}
|
||||
+113
@@ -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 <crypto/crypto.h>
|
||||
#include <lib/string.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
+163
@@ -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 <crypto/crypto.h>
|
||||
#include <lib/string.h>
|
||||
|
||||
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);
|
||||
}
|
||||
+1
-1
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
#ifndef _NET_TLS_H
|
||||
#define _NET_TLS_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
// 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
|
||||
+49
-38
@@ -14,6 +14,7 @@
|
||||
*/
|
||||
#include <net/http_client.h>
|
||||
#include <net/tcp.h>
|
||||
#include <net/tls.h>
|
||||
#include <net/dns.h>
|
||||
#include <net/net.h>
|
||||
#include <mm/kmalloc.h>
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 <net/tls.h>
|
||||
#include <net/tcp.h>
|
||||
#include <net/net.h>
|
||||
#include <crypto/crypto.h>
|
||||
#include <mm/kmalloc.h>
|
||||
#include <lib/string.h>
|
||||
#include <lib/printf.h>
|
||||
#include <kernel/printk.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
Reference in new issue
Block a user