This document defines the LITLS cryptographic and TLS stack (src/litls/).
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 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.
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.
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.
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
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 | 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 (~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 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.
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.
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).
litls_x509_verify_chain() validates a certificate chain (leaf-first):
Parse each certificate via litls_x509_parse().
For each cert i, verify its signature against cert i+1's public key.
For the final cert, try all trust anchors by signature verification (not CN matching -- handles cross-signed intermediates correctly).
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.
| 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) |
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.
| 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.
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.
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.
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
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
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).
NewSessionTicket: Received and silently discarded (no resumption).
KeyUpdate: Not implemented. A received KeyUpdate is a fatal error.
close_notify: Sent and honored on shutdown.
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 for the Lua-facing TLS API.
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 |
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 for the full TLS API.
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.
TLS 1.2 or earlier: Not supported, will not be added.
DTLS: Not supported.
Post-quantum key exchange: Not supported.
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.
All LITLS tests live in tests/litls/ and use the
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.
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.