std1.0.0-beta-7 SafeC docs

Source crypto/tls.sc

View in the repository

// SafeC Standard Library — TLS 1.3 Record Layer (implementation)
#pragma once
#include <std/stddef.h>
#include <std/crypto/tls.h>

namespace std {

extern unsigned char* memcpy(unsigned char* d, const unsigned char* s, size_t n);
extern unsigned char* memset(unsigned char* p, int v, size_t n);
extern unsigned char* memmove(unsigned char* d, const unsigned char* s, size_t n);

// ── Internal helpers ───────────────────────────────────────────────────────────

// Write a 16-bit value in network (big-endian) byte order.
static void put_u16_be_(unsigned char* p, unsigned short v) {
    unsafe {
        p[0] = (unsigned char)((v >> 8) & 0xFF);
        p[1] = (unsigned char)( v       & 0xFF);
    }
}

// Read a 16-bit big-endian value.
static unsigned short get_u16_be_(const unsigned char* p) {
    unsafe {
        return (unsigned short)(((unsigned short)p[0] << 8) | (unsigned short)p[1]);
    }
}

// Write a 64-bit value in network (big-endian) byte order into 8 bytes.
static void put_u64_be_(unsigned char* p, unsigned long long v) {
    unsafe {
        int k = 7;
        while (k >= 0) {
            p[k] = (unsigned char)(v & (unsigned long long)0xFF);
            v = v >> (unsigned long long)8;
            k = k - 1;
        }
    }
}

// ── TlsTranscript ─────────────────────────────────────────────────────────────

inline void TlsTranscript::update_n_(const &unsigned char data, unsigned long len) {
    unsafe { self.hash.update_n_((const &unsigned char)data, len); }
}

inline void TlsTranscript::update([]const unsigned char data_s) {
    unsigned long len = data_s.len;
    const &unsigned char data = crypto_in_(data_s, 0UL);
    self.update_n_(data, len);
}

inline void TlsTranscript::finish([]unsigned char out) {
    // Finish on a copy so the transcript can continue to be used.
    struct Sha256Ctx snap;
    unsafe { memcpy((unsigned char*)&snap, (const unsigned char*)&self.hash, (unsigned long)sizeof(struct Sha256Ctx)); }
    snap.finish(out);
}

// ── TlsSession::new ───────────────────────────────────────────────────────────

inline struct TlsSession TlsSession::new() {
    struct TlsSession s;
    unsafe { memset((unsigned char*)&s, 0, (unsigned long)sizeof(struct TlsSession)); }
    s.transcript.hash = Sha256Ctx::new();
    return s;
}

// ── TlsSession methods ────────────────────────────────────────────────────────

inline int TlsSession::is_established() const {
    return self.handshake_done;
}

void TlsSession::install_keys(const unsigned char write_key[TLS_KEY_LEN],
                               const unsigned char write_iv[TLS_IV_LEN],
                               const unsigned char read_key[TLS_KEY_LEN],
                               const unsigned char read_iv[TLS_IV_LEN]) {
    unsafe {
        memcpy((unsigned char*)self.write_key, (const unsigned char*)write_key, (unsigned long)TLS_KEY_LEN);
        memcpy((unsigned char*)self.write_iv,  (const unsigned char*)write_iv,  (unsigned long)TLS_IV_LEN);
        memcpy((unsigned char*)self.read_key,  (const unsigned char*)read_key,  (unsigned long)TLS_KEY_LEN);
        memcpy((unsigned char*)self.read_iv,   (const unsigned char*)read_iv,   (unsigned long)TLS_IV_LEN);
    }
    self.write_seq = (unsigned long long)0;
    self.read_seq  = (unsigned long long)0;
    self.handshake_done = 1;   // records are protected from here on
}

inline void TlsSession::compute_nonce(unsigned char nonce[TLS_IV_LEN], int is_write) const {
    // Copy base IV into nonce, then XOR bytes [4..11] with the 8-byte big-endian
    // sequence number per RFC 8446 §5.3.
    unsigned char seq_bytes[8];
    unsafe {
        unsigned long long seq = (is_write != 0) ? self.write_seq : self.read_seq;
        put_u64_be_((unsigned char*)seq_bytes, seq);

        int k = 0;
        while (k < TLS_IV_LEN) {
            nonce[k] = (is_write != 0) ? self.write_iv[k] : self.read_iv[k];
            k = k + 1;
        }
        // XOR the last 8 bytes of the 12-byte IV with the sequence number
        k = 0;
        while (k < 8) {
            nonce[4 + k] = nonce[4 + k] ^ seq_bytes[k];
            k = k + 1;
        }
    }
}

// ── encode_record ──────────────────────────────────────────────────────────────
//
// Before keys are installed: a 5-byte header + the plaintext payload.
// After install_keys (RFC 8446 §5.2): TLSInnerPlaintext = payload ||
// content_type, sealed with AES-256-GCM under the per-record nonce; the
// outer record is TLS_APPLICATION_DATA, 0x03 0x03, length, and the 5-byte
// header is the AEAD's additional data. No zero padding is added.

static void tls_header_(unsigned char* out, unsigned char type, unsigned long len) {
    unsafe {
        out[0] = type;
        out[1] = (unsigned char)0x03;
        out[2] = (unsigned char)0x03;
        put_u16_be_(out + 3, (unsigned short)len);
    }
}

unsigned long TlsSession::encode_record_n_(unsigned char content_type,
                                         const &unsigned char payload_ref,
                                         unsigned long payload_len,
                                         &unsigned char out_ref,
                                         unsigned long out_cap) {
    const unsigned char* payload;
    unsigned char* out;
    unsafe { payload = (const unsigned char*)payload_ref; out = (unsigned char*)out_ref; }
    if (payload_len > (unsigned long)TLS_MAX_RECORD_LEN) { return (unsigned long)0; }

    if (self.handshake_done == 0) {
        unsigned long total = (unsigned long)5 + payload_len;
        if (total > out_cap) { return (unsigned long)0; }
        unsafe {
            tls_header_(out, content_type, payload_len);
            memcpy((unsigned char*)(out + 5), (const unsigned char*)payload, payload_len);
        }
        return total;
    }

    // Check room before consuming a sequence number: a record that isn't
    // emitted must not advance write_seq (the peer's read_seq wouldn't).
    unsigned long inner_len = payload_len + 1UL;
    unsigned long rec_len = inner_len + (unsigned long)TLS_TAG_LEN;
    unsigned long total = (unsigned long)5 + rec_len;
    if (total > out_cap) { return (unsigned long)0; }

    unsigned char nonce[TLS_IV_LEN];
    self.compute_nonce((unsigned char*)nonce, 1);
    struct AesCtx aes = AesCtx::new_256_n_(self.write_key);
    unsafe {
        tls_header_(out, (unsigned char)TLS_APPLICATION_DATA, rec_len);
        memmove((unsigned char*)(out + 5), (const unsigned char*)payload, payload_len);
        out[5UL + payload_len] = content_type;                  // TLSInnerPlaintext.type
        aes.gcm_encrypt_n_((const &unsigned char)&nonce[0], (const ?&unsigned char)out, 5UL,
                        (&unsigned char)(out + 5), inner_len, (&unsigned char)(out + 5UL + inner_len));
    }
    aes.destroy();
    self.write_seq = self.write_seq + (unsigned long long)1;
    return total;
}

unsigned long TlsSession::encode_record(unsigned char content_type, []const unsigned char payload_s, []unsigned char out_s) {
    unsigned long payload_len = payload_s.len;
    const &unsigned char payload = crypto_in_(payload_s, 0UL);
    unsigned long out_cap = out_s.len;
    &unsigned char out = crypto_out_(out_s, 0UL);
    return self.encode_record_n_(content_type, payload, payload_len, out, out_cap);
}

// ── decode_record ──────────────────────────────────────────────────────────────

unsigned long TlsSession::decode_record_n_(const &unsigned char data_ref,
                                         unsigned long data_len,
                                         &unsigned char content_type_out,
                                         &unsigned char out_ref,
                                         unsigned long out_cap) {
    const unsigned char* data;
    unsigned char* out;
    unsafe { data = (const unsigned char*)data_ref; out = (unsigned char*)out_ref; }
    if (data_len < (unsigned long)5) { return (unsigned long)0; }

    unsigned char wire_ct;
    unsigned long flen;
    unsafe {
        wire_ct = data[0];
        if (data[1] != (unsigned char)0x03 || data[2] != (unsigned char)0x03) { return (unsigned long)0; }
        flen = (unsigned long)get_u16_be_(data + 3);
    }
    if (data_len < (unsigned long)5 + flen) { return (unsigned long)0; }   // incomplete

    if (self.handshake_done == 0) {
        if (flen > (unsigned long)TLS_MAX_RECORD_LEN || flen > out_cap) { return (unsigned long)0; }
        unsafe {
            memcpy((unsigned char*)out, (const unsigned char*)(data + 5), flen);
            *content_type_out = wire_ct;
        }
        return flen;
    }

    // Protected: the outer type is always application_data, and the body is
    // TLSInnerPlaintext (at least the type byte) + the 16-byte tag.
    if (wire_ct != (unsigned char)TLS_APPLICATION_DATA) { return (unsigned long)0; }
    if (flen < (unsigned long)TLS_TAG_LEN + 1UL ||
        flen > (unsigned long)TLS_MAX_RECORD_LEN + 256UL) { return (unsigned long)0; }
    unsigned long inner_len = flen - (unsigned long)TLS_TAG_LEN;

    unsigned char plain[16640];   // TLS_MAX_RECORD_LEN + 256
    unsigned char nonce[TLS_IV_LEN];
    self.compute_nonce((unsigned char*)nonce, 0);
    struct AesCtx aes = AesCtx::new_256_n_(self.read_key);
    int ok = 0;
    unsafe {
        memcpy((unsigned char*)plain, (const unsigned char*)(data + 5), inner_len);
        ok = aes.gcm_decrypt_n_((const &unsigned char)&nonce[0], (const ?&unsigned char)data, 5UL,
                             (&unsigned char)&plain[0], inner_len,
                             (const &unsigned char)(data + 5UL + inner_len));
    }
    aes.destroy();
    if (!ok) { return (unsigned long)0; }                    // forged or corrupted

    // The content type is the last non-zero byte; zeros after it are padding.
    unsigned long end = inner_len;
    unsafe { while (end > 0UL && plain[end - 1UL] == (unsigned char)0) { end = end - 1UL; } }
    if (end == 0UL) { return (unsigned long)0; }              // no content type
    unsigned long payload_len = end - 1UL;
    if (payload_len > out_cap || payload_len > (unsigned long)TLS_MAX_RECORD_LEN) { return (unsigned long)0; }
    unsafe {
        *content_type_out = plain[payload_len];
        memcpy((unsigned char*)out, (const unsigned char*)plain, payload_len);
    }
    self.read_seq = self.read_seq + (unsigned long long)1;
    return payload_len;
}

unsigned long TlsSession::decode_record([]const unsigned char data_s, &unsigned char content_type_out, []unsigned char out_s) {
    unsigned long data_len = data_s.len;
    const &unsigned char data = crypto_in_(data_s, 0UL);
    unsigned long out_cap = out_s.len;
    &unsigned char out = crypto_out_(out_s, 0UL);
    return self.decode_record_n_(data, data_len, content_type_out, out, out_cap);
}

// ── build_alert ───────────────────────────────────────────────────────────────

unsigned long TlsSession::build_alert_n_(unsigned char level,
                                       unsigned char desc,
                                       &unsigned char out_ref,
                                       unsigned long out_cap) {
    // Alert body: level (1 byte) + description (1 byte)
    unsigned char alert_body[2];
    unsafe {
        alert_body[0] = level;
        alert_body[1] = desc;
    }
    return self.encode_record_n_((unsigned char)TLS_ALERT,
                               alert_body,
                               (unsigned long)2,
                               out_ref, out_cap);
}

unsigned long TlsSession::build_alert(unsigned char level, unsigned char desc, []unsigned char out_s) {
    unsigned long out_cap = out_s.len;
    &unsigned char out = crypto_out_(out_s, 0UL);
    return self.build_alert_n_(level, desc, out, out_cap);
}

} // namespace std