# Lilush TLS Stack: LITLS

## Overview

_LITLS_ is a minimal, TLS 1.3-only cryptographic and TLS stack built for
Lilush. The goal was to assemble a secure, efficient, and self-contained
TLS implementation from components compatible with the OWL license --
meaning only **public domain** and **MIT licensed** code.

The foundation is [Daniel J. Bernstein's](https://cr.yp.to/djb.html) 
cryptographic designs: X25519 for key exchange, ChaCha20-Poly1305 as 
the preferred cipher, Ed25519 for signatures. 

If the world ran on DJB crypto alone, _LITLS_ would be a much smaller project. 
But RFC 8446 compliance and real-world certificate chains demand more: 
AES-GCM is a mandatory cipher suite, and virtually every CA
issues ECDSA P-256 or RSA certificates. So _LITLS_ pragmatically borrows
constant-time AES, ECDSA, and RSA implementations from BearSSL (MIT
licensed) to cover the gaps.

_LITLS_ has no external library dependency. All cryptographic code ships
in-tree under `src/litls/`, compiled directly into the binary. The TLS 1.3
state machine is hand-written, purpose-built for Lilush's non-blocking
I/O model.

::: warning

**Security notice**: LITLS has not undergone a third-party security audit.

While it has test coverage and is being actively hardened, use in
security-sensitive production environments is at your own risk.

Contributions and responsible disclosure are welcome.

:::

## Design principles

- **DJB-first**: X25519 for key exchange, ChaCha20-Poly1305 as the preferred
  cipher suite. AES-GCM and ECDSA P-256 are included because RFC 8446 §9.1
  mandates `TLS_AES_128_GCM_SHA256` and because real-world certificate chains
  (Let's Encrypt, Kubernetes) use ECDSA P-256 or RSA.
- **TLS 1.3 only**: No backward compatibility with TLS 1.2 or earlier.
- **No heap allocation in core C API**: All `litls_` functions use
  caller-provided output buffers. The Lua binding layer (`litls_lua.c`)
  may heap-allocate for buffers exceeding 64 KB.
  Returns 0 on success, negative on error (unless otherwise noted).
- **Non-blocking I/O**: The TLS state machine returns `WANT_READ` /
  `WANT_WRITE` for integration with LEV's epoll loop.
- **Linux only**: Uses `getrandom(2)` for CSPRNG. No portability layer.

## Source layout

```
src/litls/
  litls.h                  -- Public C API (primitives + X.509 + PEM)
  litls_lua.c              -- Lua bindings (registered as litls.core)
  litls_sha1.c             -- SHA-1 (legacy, git metadata only)
  litls_sha256.c           -- SHA-256 (streaming + single-shot)
  litls_sha384.c           -- SHA-384 (truncated SHA-512)
  litls_sha512.c           -- SHA-512
  litls_hmac.c             -- HMAC-SHA256, HMAC-SHA384
  litls_hkdf.c             -- HKDF extract/expand (SHA-256, SHA-384)
  litls_chacha20.c         -- ChaCha20 stream cipher (RFC 8439)
  litls_poly1305.c         -- Poly1305 MAC
  litls_chacha20poly1305.c -- ChaCha20-Poly1305 AEAD
  litls_gcm.c              -- AES-128/256-GCM AEAD (wraps BearSSL)
  litls_aes.c              -- AES block cipher (wraps BearSSL)
  litls_x25519.c           -- X25519 ECDH (from TweetNaCl)
  litls_ed25519.c          -- Ed25519 sign/verify (from TweetNaCl)
  litls_ecdsa_p256.c       -- ECDSA P-256 keygen/sign/verify (wraps BearSSL)
  litls_ecdsa_p384.c       -- ECDSA P-384 verify only (wraps BearSSL)
  litls_rsa_verify.c       -- RSA PKCS#1 v1.5 + PSS verify (wraps BearSSL)
  litls_base64.c           -- Base64 / Base64url encode/decode
  litls_rng.c              -- CSPRNG + secure memcmp + secure zero
  litls_pem_encode.c       -- EC private key PEM encoding
  bearssl/                 -- Extracted BearSSL primitives (MIT)
    aes_ct_ctr.c           -- AES in counter mode, constant-time
    ghash_ctmul32.c        -- GHASH for GCM, constant-time
    ec_p256_m15.c          -- P-256 field arithmetic
    ecdsa_i15_*.c          -- ECDSA operations
    rsa_i15_*.c            -- RSA verification
    ...                    -- ~40 files total
  x509/                    -- Self-written X.509/PKI layer
    asn1.c / asn1.h        -- ASN.1/DER reader + back-patching writer
    parse.c                -- X.509 certificate parser
    verify.c               -- X.509 chain validator
    csr.c                  -- PKCS#10 CSR DER encoder
  tls13/                   -- TLS 1.3 state machine
    tls13.h                -- Internal TLS 1.3 API
    record.c               -- Record layer (encrypt/decrypt, padding)
    keys.c                 -- Key schedule and traffic secret derivation
    handshake.c            -- Handshake message parsing/building
    client.c               -- Client handshake path
    server.c               -- Server handshake path
```

## Cryptographic primitives

### Sourcing rationale

The "DJB-first" principle applies where it does not compromise
interoperability: X25519 for key exchange and ChaCha20-Poly1305 as the
preferred cipher suite. AES-GCM and ECDSA P-256 are included because
RFC 8446 §9.1 mandates `TLS_AES_128_GCM_SHA256` and because real-world
certificate chains use ECDSA P-256 or RSA.

### Primitive reference

| Primitive | C API | Source | License |
|-----------|-------|--------|---------|
| SHA-1 | `litls_sha1()` | Self-written | — |
| SHA-256 | `litls_sha256()`, streaming ctx | Brad Conte's crypto-algorithms | Public domain |
| SHA-384 | `litls_sha384()`, streaming ctx | Same | Public domain |
| SHA-512 | `litls_sha512()`, streaming ctx | TweetNaCl (internal) | Public domain |
| HMAC-SHA256 | `litls_hmac_sha256()` | Self-written (RFC 2104) | — |
| HMAC-SHA384 | `litls_hmac_sha384()` | Self-written | — |
| HKDF-SHA256 | `litls_hkdf_extract_sha256()`, `_expand_sha256()` | Self-written (RFC 5869) | — |
| HKDF-SHA384 | `litls_hkdf_extract_sha384()`, `_expand_sha384()` | Self-written | — |
| ChaCha20 | `litls_chacha20_block()`, `_encrypt()` | DJB reference, IETF variant | Public domain |
| Poly1305 | `litls_poly1305_auth()`, streaming `_init/_update/_final()` | DJB / Andrew Moon | Public domain |
| ChaCha20-Poly1305 | `litls_chacha20_poly1305_encrypt/decrypt()` | Composition of above | Public domain |
| AES-128-GCM | `litls_aes128_gcm_encrypt/decrypt()` | BearSSL `aes_ct_ctr` + `ghash_ctmul32` | MIT |
| AES-256-GCM | `litls_aes256_gcm_encrypt/decrypt()` | BearSSL (same backend) | MIT |
| X25519 | `litls_x25519()`, `_base()` | TweetNaCl `crypto_scalarmult_curve25519` | Public domain |
| Ed25519 | `litls_ed25519_keypair()`, `_sign()`, `_verify()` | TweetNaCl `crypto_sign_ed25519` | Public domain |
| ECDSA P-256 | `litls_p256_keygen()`, `_ecdsa_sign()`, `_ecdsa_verify()` | BearSSL `ec_p256_m15` + `ecdsa_i15` | MIT |
| ECDSA P-384 | `litls_p384_ecdsa_verify()` (verify only) | BearSSL `ec_p384_m15` | MIT |
| RSA PKCS#1 v1.5 | `litls_rsa_pkcs1_verify()` | BearSSL `rsa_i15_pkcs1_vrfy` | MIT |
| RSA-PSS-SHA256 | `litls_rsa_pss_sha256_verify()` | RFC 8017 + BearSSL RSA | MIT |
| Base64 | `litls_base64_encode/decode()`, `_url_*()` | Self-written (RFC 4648) | — |
| CSPRNG | `litls_random_bytes()` | Linux `getrandom(2)` | — |
| Secure memcmp | `litls_secure_memcmp()` | Self-written | — |
| Secure zero | `litls_secure_zero()` | `explicit_bzero` wrapper | — |
| ECDSA sig convert | `litls_ecdsa_sig_der_to_raw()`, `_raw_to_der()` | Self-written | — |

### TweetNaCl

TweetNaCl (~700 lines, public domain) is imported for X25519 and Ed25519
only. TweetNaCl uses **XSalsa20**, not IETF ChaCha20 -- the two are
different constructions. For TLS 1.3's `TLS_CHACHA20_POLY1305_SHA256`,
the IETF ChaCha20 variant (RFC 8439, 96-bit nonce) is used separately.

TweetNaCl functions used:
- `crypto_scalarmult_curve25519` -- X25519 ECDH
- `crypto_sign_ed25519` / `crypto_sign_ed25519_open` -- Ed25519
- `crypto_hash_sha512` -- SHA-512 (internal to Ed25519)
- `crypto_verify_32` -- constant-time 32-byte comparison

All other TweetNaCl functions (secretbox, box, stream) are unused and
compiled out.

### BearSSL extracts

BearSSL is a TLS 1.2 library; only its low-level cryptographic components
are extracted. BearSSL's higher-level components (`x509_minimal.c`, ASN.1
codecs, PEM handling, TLS engine) are **not** imported -- the X.509/PKI
and TLS 1.3 layers are self-written.

Extracted components:
- `aes_ct_ctr.c` -- AES in counter mode, constant-time (cache-safe)
- `ghash_ctmul32.c` -- GHASH for GCM, constant-time
- `ec_p256_m15.c` / `ec_p384_m15.c` -- P-256 and P-384 field arithmetic
- `ecdsa_i15_sign_asn1.c` / `ecdsa_i15_vrfy_asn1.c` -- ECDSA operations
- `rsa_i15_pkcs1_vrfy.c` -- RSA verification

BearSSL is MIT licensed. Original copyright headers are preserved in all
extracted files under `src/litls/bearssl/`. See [CREDITS](CREDITS.md).

## X.509 / PKI layer

The X.509/PKI layer is entirely self-written (~1700 lines of C, no heap
allocation). BearSSL's `br_x509_minimal_engine` was evaluated but rejected
due to vtable mismatch and dependency explosion.

### Certificate parsing

`litls_x509_parse(der, der_len, info)` extracts from a DER-encoded X.509
certificate:

| Field | Type | Description |
|-------|------|-------------|
| `common_name` | `char[256]` | Subject CN |
| `common_name_len` | `size_t` | Length of CN |
| `not_before` / `not_after` | `int64_t` | Validity window (Unix timestamps) |
| `sans[]` | struct `{name[256], len}` | Subject Alternative Names (DNS), max 32 |
| `san_count` | `int` | Number of SANs parsed |
| `key_type` | `int` | `LITLS_KEY_*` identifier |
| `pubkey` | `uint8_t[512]` | Subject public key (raw bytes) |
| `pubkey_len` | `size_t` | Length of public key |
| `rsa_e` / `rsa_e_len` | `uint8_t[8]` / `size_t` | RSA public exponent (RSA keys only) |
| `sig_algo` | `int` | `LITLS_SIG_*` identifier |
| `signature` / `signature_len` | `const uint8_t*` / `size_t` | Raw signature (points into input DER) |
| `tbs` / `tbs_len` | `const uint8_t*` / `size_t` | TBS region (points into input DER) |

Key types: `LITLS_KEY_ECDSA_P256` (1), `LITLS_KEY_RSA` (2),
`LITLS_KEY_ECDSA_P384` (3), `LITLS_KEY_ED25519` (4).

Signature algorithms: `LITLS_SIG_ECDSA_SHA256` (1), `LITLS_SIG_ECDSA_SHA384` (2),
`LITLS_SIG_RSA_SHA256` (3), `LITLS_SIG_RSA_SHA384` (4), `LITLS_SIG_RSA_SHA512` (5),
`LITLS_SIG_ED25519` (6).

### Chain validation

`litls_x509_verify_chain()` validates a certificate chain (leaf-first):

1. Parse each certificate via `litls_x509_parse()`.
2. For each cert `i`, verify its signature against cert `i+1`'s public key.
3. For the final cert, try all trust anchors by signature verification
   (not CN matching -- handles cross-signed intermediates correctly).
4. Check validity periods against a caller-provided timestamp (`now=0` skips
   time validation).

Error codes:

| Code | Constant | Meaning |
|------|----------|---------|
| 0 | `LITLS_X509_OK` | Chain is valid |
| -1 | `LITLS_X509_ERR_PARSE` | Failed to parse a certificate |
| -2 | `LITLS_X509_ERR_SIGNATURE` | Signature verification failed |
| -3 | `LITLS_X509_ERR_EXPIRED` | Certificate outside validity window |
| -4 | `LITLS_X509_ERR_NO_ANCHOR` | No matching trust anchor found |
| -5 | `LITLS_X509_ERR_CHAIN` | Chain structure error |
| -6 | `LITLS_X509_ERR_CONSTRAINT` | BasicConstraints / KeyUsage / EKU / pathLen violation |
| -7 | `LITLS_X509_ERR_HOSTNAME` | Leaf SAN/CN does not match expected hostname |

Limits: max 8 certificates in chain, max 256 trust anchors.

`litls_x509_load_anchors(pem_data, pem_len, anchors, max)` loads trust
anchors from a PEM CA bundle, parsing each certificate to extract its
public key.

### PEM handling

| Function | Description |
|----------|-------------|
| `litls_pem_decode()` | Strip PEM armor, base64-decode to DER |
| `litls_ec_privkey_to_pem()` | Encode P-256 private key to SEC1 PEM |
| `litls_parse_private_key_pem()` | Parse EC PRIVATE KEY or PKCS#8 PEM (P-256 and Ed25519) |

### CSR generation

`litls_csr_generate(params, der_out, der_cap)` creates a DER-encoded
PKCS#10 CSR using ECDSA P-256 + SHA-256, for ACME certificate issuance.
The CSR structure is hand-encoded DER (one domain + SANs). Max output:
4096 bytes.

## TLS 1.3 protocol

### Cipher suites

| Suite | Code | Priority | Hash |
|-------|------|----------|------|
| `TLS_CHACHA20_POLY1305_SHA256` | `0x1303` | 1st (preferred) | SHA-256 |
| `TLS_AES_128_GCM_SHA256` | `0x1301` | 2nd | SHA-256 |
| `TLS_AES_256_GCM_SHA384` | `0x1302` | 3rd | SHA-384 |

All three are advertised in ClientHello. The server selects from the
intersection.

### Key exchange

Only X25519 (`NamedGroup 0x001D`) is offered in the `key_share` extension.
If a server rejects X25519 with `HelloRetryRequest`, the HRR is honored
only if it requests X25519 again (error otherwise). Cookie extension
(up to 512 bytes) is supported for HRR flows.

No P-256 key exchange -- only P-256 certificate verification.

### Signature algorithms

Advertised in ClientHello and accepted in `CertificateVerify` (RFC 8446
§4.2.3 forbids RSASSA-PKCS1-v1_5 for TLS 1.3 handshake signatures, so it
is *not* offered):

| Algorithm | Code | Usage |
|-----------|------|-------|
| `ecdsa_secp256r1_sha256` | `0x0403` | P-256 server certs |
| `rsa_pss_rsae_sha256` | `0x0804` | RSA server certs (CertificateVerify) |
| `ed25519` | `0x0807` | Ed25519 server certs |

RSA PKCS#1 v1.5 with SHA-256/384/512 is still accepted as an X.509
*chain* signature algorithm in `litls_x509_verify_chain` -- it is only
forbidden in TLS 1.3 `CertificateVerify`.

### Client handshake

```
ClientHello
  - supported_versions: [TLS 1.3]
  - supported_groups: [x25519]
  - key_share: [x25519: ephemeral_public_key]
  - signature_algorithms: [ecdsa_secp256r1_sha256, rsa_pss_rsae_sha256,
                            ed25519]
  - server_name: <SNI hostname>
  - cipher_suites: [0x1303, 0x1301, 0x1302]

  ServerHello
    - parse key_share (server X25519 public key)
    - derive shared secret via X25519(ephemeral_priv, server_pub)
    - derive handshake_secret via HKDF key schedule

  {EncryptedExtensions}

  {Certificate}
    - decode certificate chain
    - validate against trust anchors
    - extract server public key

  {CertificateVerify}
    - verify signature over transcript hash

  {Finished}
    - verify HMAC over transcript

ClientFinished
  - send client Finished

  derive application traffic secrets
  CONNECTED
```

### Server handshake

```
  ClientHello
    - parse cipher suites (select best)
    - parse key_shares (expect x25519)
    - parse SNI, select cert/key
    - generate ephemeral X25519 keypair
    - derive shared secret, handshake_secret

ServerHello (cipher suite, server X25519 key_share)
{EncryptedExtensions}
{Certificate} (leaf + chain)
{CertificateVerify} (sign transcript with server private key)
{Finished} (HMAC over transcript)

  ClientFinished
    - verify client Finished

  derive application traffic secrets
  CONNECTED
```

### Key schedule

RFC 8446 §7.1 key schedule, using HKDF-SHA256 (or SHA384 for AES-256-GCM):

```
early_secret      = HKDF-Extract(0, PSK=0)
derived_secret    = Derive-Secret(early_secret, "derived", "")
handshake_secret  = HKDF-Extract(derived_secret, DHE)
client_hs_traffic = Derive-Secret(handshake_secret, "c hs traffic", CH..SH)
server_hs_traffic = Derive-Secret(handshake_secret, "s hs traffic", CH..SH)
...
master_secret     = HKDF-Extract(Derive-Secret(handshake_secret, "derived", ""), 0)
client_ap_traffic = Derive-Secret(master_secret, "c ap traffic", CH..SF)
server_ap_traffic = Derive-Secret(master_secret, "s ap traffic", CH..SF)
```

AEAD keys and IVs are derived per §7.3:

```
key = HKDF-Expand-Label(traffic_secret, "key", "", key_len)
iv  = HKDF-Expand-Label(traffic_secret, "iv",  "", 12)
```

Record nonce = per-record sequence number XOR'd with IV (§5.3).

### Post-handshake messages

- **NewSessionTicket**: Received and silently discarded (no resumption).
- **KeyUpdate**: Not implemented. A received KeyUpdate is a fatal error.
- **close_notify**: Sent and honored on shutdown.

### Non-blocking I/O integration

The TLS state machine is fully non-blocking. `litls_tls13_handshake_step()`
returns one of:

| Return | Meaning |
|--------|---------|
| `LITLS_DONE` | Handshake complete |
| `LITLS_WANT_READ` | Needs more data from socket |
| `LITLS_WANT_WRITE` | Has data to send |
| negative | Fatal error |

LEV's C core (`lev_core.c`) drives the state machine via
`starttls_init()` and `starttls_step()`. The Lua coroutine loop in
`lev.lua` yields on `WANT_READ`/`WANT_WRITE` and resumes when epoll
signals readiness. See [LEV](LEV.md) for the Lua-facing TLS API.

## Lua API

### litls.core module

The `litls.core` module exposes all LITLS primitives to Lua. Most
application code should use the higher-level `crypto` module or LEV's
TLS integration instead of calling `litls.core` directly.

**Base64**

| Function | Arguments | Returns |
|----------|-----------|---------|
| `base64_encode(data)` | binary string | base64 string |
| `base64_decode(data)` | base64 string | binary string |
| `base64url_encode(data)` | binary string | base64url string |
| `base64url_decode(data)` | base64url string | binary string |

**Random**

| Function | Arguments | Returns |
|----------|-----------|---------|
| `random_bytes(n)` | length (0-65536) | binary string |

**Hashes**

| Function | Arguments | Returns |
|----------|-----------|---------|
| `sha1(data)` | binary string | 20-byte hash |
| `sha256(data)` | binary string | 32-byte hash |
| `sha384(data)` | binary string | 48-byte hash |
| `sha512(data)` | binary string | 64-byte hash |

**HMAC**

| Function | Arguments | Returns |
|----------|-----------|---------|
| `hmac_sha256(key, msg)` | binary strings | 32-byte MAC |
| `hmac_sha384(key, msg)` | binary strings | 48-byte MAC |

**HKDF (RFC 5869)**

| Function | Arguments | Returns |
|----------|-----------|---------|
| `hkdf_extract_sha256(salt, ikm)` | binary strings | 32-byte PRK |
| `hkdf_expand_sha256(prk, info, len)` | PRK + info + output length | binary string |
| `hkdf_extract_sha384(salt, ikm)` | binary strings | 48-byte PRK |
| `hkdf_expand_sha384(prk, info, len)` | PRK + info + output length | binary string |

**ChaCha20 / Poly1305**

| Function | Arguments | Returns |
|----------|-----------|---------|
| `chacha20_block(key, nonce, counter)` | 32B key, 12B nonce, integer | 64-byte keystream block |
| `chacha20_encrypt(key, nonce, counter, plaintext)` | 32B key, 12B nonce, integer, data | ciphertext |
| `poly1305_auth(key, msg)` | 32B key, data | 16-byte tag |

**AEAD**

| Function | Arguments | Returns |
|----------|-----------|---------|
| `chacha20_poly1305_encrypt(key, nonce, aad, pt)` | 32B key, 12B nonce, AAD, plaintext | ciphertext, 16B tag |
| `chacha20_poly1305_decrypt(key, nonce, aad, ct, tag)` | 32B key, 12B nonce, AAD, ciphertext, tag | plaintext or `nil` |
| `aes128_gcm_encrypt(key, nonce, aad, pt)` | 16B key, 12B nonce, AAD, plaintext | ciphertext, 16B tag |
| `aes128_gcm_decrypt(key, nonce, aad, ct, tag)` | 16B key, 12B nonce, AAD, ciphertext, tag | plaintext or `nil` |
| `aes256_gcm_encrypt(key, nonce, aad, pt)` | 32B key, 12B nonce, AAD, plaintext | ciphertext, 16B tag |
| `aes256_gcm_decrypt(key, nonce, aad, ct, tag)` | 32B key, 12B nonce, AAD, ciphertext, tag | plaintext or `nil` |

**X25519**

| Function | Arguments | Returns |
|----------|-----------|---------|
| `x25519(private_key, peer_public)` | 32B each | 32-byte shared secret, or `nil` |
| `x25519_base(private_key)` | 32B scalar | 32-byte public key |

**Ed25519**

| Function | Arguments | Returns |
|----------|-----------|---------|
| `ed25519_keypair(seed)` | 32B seed | public_key (32B), secret_key (64B) |
| `ed25519_sign(msg, pk, sk)` | message, 32B pk, 64B sk | 64-byte signature |
| `ed25519_verify(sig, msg, pk)` | 64B sig, message, 32B pk | `true` or `false` |

**Ed25519 / X25519 key conversion**

| Function | Arguments | Returns |
|----------|-----------|---------|
| `ed25519_to_x25519_pk(pk)` | 32B Ed25519 public key | 32-byte X25519 public key |
| `ed25519_to_x25519_sk(seed)` | 32B Ed25519 seed | 32-byte X25519 private key |
| `openssh_parse_ed25519(pem)` | OpenSSH private key PEM string | seed (32B), public_key (32B) or `nil, err` |

**ECDSA P-256**

| Function | Arguments | Returns |
|----------|-----------|---------|
| `p256_keygen()` | none | private_key (32B), public_key (65B) |
| `p256_ecdsa_sign(hash, private_key)` | hash digest, 32B key | DER-encoded signature |
| `p256_ecdsa_verify(hash, public_key, sig)` | hash, 65B key, DER sig | `true` or `false` |

**ECDSA P-384**

| Function | Arguments | Returns |
|----------|-----------|---------|
| `p384_ecdsa_verify(hash, public_key, sig)` | hash, public key, DER sig | `true` or `false` |

**RSA verify**

| Function | Arguments | Returns |
|----------|-----------|---------|
| `rsa_pkcs1_verify(hash, hash_id, sig, n, e)` | hash, `HASH_SHA*` constant, sig, modulus, exponent | `true` or `false` |
| `rsa_pss_sha256_verify(hash, sig, n, e)` | SHA-256 hash, sig, modulus, exponent | `true` or `false` |

**Hex helpers**

| Function | Arguments | Returns |
|----------|-----------|---------|
| `hex_encode(data)` | binary string | hex string |
| `hex_decode(hex)` | hex string | binary string |

**X.509 / PEM / CSR**

| Function | Arguments | Returns |
|----------|-----------|---------|
| `pem_decode(pem)` | PEM string | DER binary |
| `x509_parse(der)` | DER certificate | cert info table |
| `x509_verify_chain(chain, ca_pem)` | array of DER certs, CA PEM | `true` or error code |
| `x509_verify_hostname(der, hostname)` | leaf DER, hostname string | `true` on match, `nil, err` otherwise |
| `csr_generate(priv, pub, domain, sans)` | 32B key, 65B pub, domain, SANs table | DER binary |

**TLS 1.3 key schedule** (for testing)

| Function | Arguments | Returns |
|----------|-----------|---------|
| `tls13_expand_label_256(secret, label, context, len)` | secret, label string, context, output length | expanded key material |
| `tls13_derive_secret_256(secret, label, hash)` | secret, label string, transcript hash | derived secret |

**Constants**

| Name | Value | Description |
|------|-------|-------------|
| `HASH_SHA256` | 4 | Hash identifier for RSA verify functions |
| `HASH_SHA384` | 5 | Hash identifier for RSA verify functions |
| `HASH_SHA512` | 6 | Hash identifier for RSA verify functions |

### Higher-level modules

**crypto module** (`src/crypto/crypto.lua`): High-level wrapper over
`litls.core` (via the `crypto.core` C binding). Provides SHA-1, SHA-256,
HMAC, Base64/Base64url, ECC P-256 key management (keygen, sign, verify,
JWK save/load), Ed25519 (keygen, sign, verify), CSR generation, PEM
encoding, and X.509 certificate parsing. Most application code should use
`crypto` rather than `litls.core` directly.

**LEV TLS integration** (`src/lev/`): TLS connections are created via
`lev.connect()` with a `tls` config table, or `sock:starttls()` for
server-side upgrade. The TLS handshake runs non-blocking inside the LEV
event loop. See [LEV](LEV.md) for the full TLS API.

### To be done
- **Mutual TLS (server-side)**: Server-side CertificateRequest sending
  and client certificate verification is not yet implemented.
  Client-side mTLS (sending client certificates when requested by the
  server) is supported for ECDSA P-256 and Ed25519 client keys.
- **Post-quantum crypto**: Probably will be added in the future

## Non-goals

- **TLS 1.2 or earlier**: Not supported, will not be added.
- **DTLS**: Not supported.
- **Post-quantum key exchange**: Not supported at the moment.
- **Session resumption / 0-RTT**: Deferred. Session tickets are received
  and discarded. Early data is not advertised (correct security default
  due to replay risk).
- **FIPS compliance**: Not a goal.
- **Non-Linux platforms**: LITLS uses Linux-specific syscalls.

## Testing

All LITLS tests live in `tests/litls/` and use the
[testimony](../src/testimony/) framework. Every primitive is validated
against official RFC test vectors.

| Test file | Coverage |
|-----------|----------|
| `test_sha.lua` | SHA-256, SHA-384, SHA-512 -- FIPS 180-4 vectors |
| `test_hmac.lua` | HMAC-SHA256, HMAC-SHA384 -- RFC 4231 vectors |
| `test_hkdf.lua` | HKDF extract/expand -- RFC 5869 Appendix A vectors |
| `test_chacha20.lua` | ChaCha20 block + encrypt -- RFC 8439 §2.4.2 vectors |
| `test_poly1305.lua` | Poly1305 MAC -- RFC 8439 §2.5.2 vectors |
| `test_aead.lua` | ChaCha20-Poly1305 + AES-GCM AEAD -- RFC 8439 + NIST vectors |
| `test_x25519.lua` | X25519 key exchange -- RFC 7748 §6.1 vectors |
| `test_ed25519.lua` | Ed25519 sign/verify -- RFC 8032 vectors |
| `test_ecdsa_p256.lua` | ECDSA P-256 keygen/sign/verify |
| `test_base64.lua` | Base64 + Base64url encode/decode -- RFC 4648 vectors |
| `test_rsa_verify.lua` | RSA PKCS#1 v1.5 + RSA-PSS-SHA256 verify -- NIST vectors |
| `test_x509.lua` | Certificate parsing, chain validation, CSR generation |
| `test_tls13_keys.lua` | Key schedule, Expand-Label, Derive-Secret -- RFC 8448 vectors |
| `test_tls13_record.lua` | Record framing, AEAD encrypt/decrypt, nonce construction |
| `test_tls13_client.lua` | Live TLS 1.3 client handshake, HTTPS, cert verification |
| `test_tls13_server.lua` | Server-side handshake, SNI, bidirectional data exchange |
| `test_crypto_module.lua` | `crypto` module API (base64, SHA, HMAC, ECC, Ed25519, CSR, PEM) |

The TLS section in `tests/lev/test_lev.lua` serves as the integration
regression suite.

## Known limitations

- **Experimental status**: Hand-written TLS 1.3 state machine. Security
  review recommended before production use with untrusted traffic.
- **ASN.1 edge cases**: The DER parser handles common encoding patterns
  but has not been tested against the full X.509 corpus.
- **No session resumption**: Every connection performs a full handshake.
  Latency cost for repeated connections to the same host.
- **No 0-RTT**: Early data not supported (intentional -- replay risk).
- **No KeyUpdate**: A received `KeyUpdate` message is a fatal error.
  Unlikely to be triggered in practice for short-lived connections.
