packages/tls/src/tls.nu — a pure-NURL TLS 1.3 client (RFC 8446).
No OpenSSL, no FFI beyond the libc TCP socket: the handshake crypto (X25519, ChaCha20-Poly1305, HKDF, SHA-256) is all pure NURL, so a program can open an authenticated, encrypted TLS 1.3 connection on a host with nothing installed — Linux, macOS, the BSDs, Windows.
Cipher suites: TLS_AES_128_GCM_SHA256 and TLS_CHACHA20_POLY1305_SHA256 with the X25519 group — between them accepted by essentially every modern TLS 1.3 server (OpenSSL, BoringSSL, nginx, the big CDNs).
Surface: ( tls_connect host port server_name ) → !TlsConn TlsErr (verify-full) ( tls_connect_insecure host port server_name ) → !TlsConn TlsErr (no cert check) ( tls_write conn bytes ) → !v TlsErr ( tls_read conn max ) → !( Vec u ) TlsErr ([] at EOF) ( tls_close conn ) → v
tls_connect is secure by default: it completes the handshake and then verifies the server certificate chain (see verify.nu) against the system trust store, failing with TlsBadCert otherwise. tls_connect_insecure is the encrypted-but-unauthenticated escape hatch.
& libc @ nurl_tcp_connect s host i port → i& c @ nurl_rand_fill *u buf i n → i: | TlsErr: | TlsErr {
TlsConnect
TlsHandshake
TlsDecrypt
TlsRead
TlsWrite
TlsClosed
TlsAlert
TlsProtocol
TlsBadCipher
TlsHRR
TlsBadCert
}
@ tls_err_name TlsErr e → s: TlsConn: TlsConn {
TcpConn tcp
( Vec u ) rxbuf // raw socket bytes not yet split into records
( Vec u ) hsbuf // decrypted handshake bytes not yet a full message
( Vec u ) appbuf // decrypted application bytes for the caller
( Vec u ) s_key
( Vec u ) s_iv
( Vec u ) c_key
( Vec u ) c_iv
i s_seq
i c_seq
i enc_read // 1 once server records are encrypted
i established
i closed
( Vec u ) cert_msg // raw Certificate handshake message (full chain)
( Vec u ) cv_sig // CertificateVerify signature
( Vec u ) th_cert // transcript hash through Certificate (for CertVerify)
i cv_scheme // CertificateVerify SignatureScheme
i cipher // 0 = ChaCha20-Poly1305, 1 = AES-128-GCM
i version // 13 = TLS 1.3, 12 = TLS 1.2
( Vec u ) kx_p256 // P-256 ephemeral private key (empty if X25519 chosen)
}
Live connection state. Vec fields are reassigned as keys rotate (handshake → application) and as buffers are consumed.
: TlsRecord { i rtype ( Vec u ) body }Record = type(1) ver(2) length(2) body. Returns (type, body); body is an owned slice the caller frees.
@ tls_connect_insecure s host i port s server_name → !*TlsConn TlsErrEstablish a TLS 1.3 connection WITHOUT verifying the server certificate — encrypted but not authenticated (MITM-able). Use only for pinned/self-signed/testing cases. tls_connect (below) is the secure, verifying entry point. The presented chain, CertificateVerify signature and transcript hash are captured on the conn for the verifier.
@ tls_attach i raw s server_name → !*TlsConn TlsErrRun the TLS handshake over a socket that is ALREADY connected (a raw libc fd handle as returned by nurl_tcp_connect). This is what STARTTLS-style protocols need — PostgreSQL's SSLRequest, SMTP STARTTLS, IMAP/FTP — where the plaintext leg must exchange a few bytes before the channel is upgraded to TLS on the same socket. No certificate verification (see tls_attach_verify for the secure variant).
@ tls_connect s host i port s server_name → !*TlsConn TlsErr@ tls_attach_verify i raw s server_name → !*TlsConn TlsErrVerifying counterpart of tls_attach: upgrade an already-connected socket to TLS and verify the chain / hostname against the system trust store (the secure default for STARTTLS-style upgrades).
@ tls_write * TlsConn c ( Vec u ) data → !v TlsErr── application data ──────────────────────────────────────────────
@ tls_read * TlsConn c i max → !( Vec u ) TlsErrRead up to max decrypted application bytes. Returns [] on clean EOF (close_notify). Post-handshake messages (tickets, key updates) are consumed transparently.
@ tls_close * TlsConn c → vpackages/tls/src/verify.nu — TLS 1.3 certificate verification.
Ties the pure-NURL X.509 parser and RSA/ECDSA verifiers into a real "verify-full": it checks the server's CertificateVerify signature, walks the presented chain (each cert signed by the next), anchors the top to a certificate in the system trust store, enforces the validity window, and matches the hostname against the leaf SANs. No OpenSSL.
( tls_cert_verify cert_msg cv_scheme cv_sig th_cert hostname ) → i 0 ok · 1 no certs · 2 parse · 3 CertVerify · 4 chain link 5 no trust anchor · 6 anchor sig · 7 hostname · 8 expired 9 unsupported algorithm
@ tls_chain_verify ( Vec u ) cert_msg i is12 s hostname → iTop-level certificate verification (see header for return codes). Chain-only verification (no CertificateVerify): hostname + validity + chain links + trust anchor. Shared by the TLS 1.3 and 1.2 paths. Same return codes as tls_cert_verify (0 ok).
@ tls_leaf_cert ( Vec u ) cert_msg i is12 → X509Leaf public key (parsed) from a Certificate message — for verifying the TLS 1.2 ServerKeyExchange signature. Caller x509_frees it.
@ tls12_ske_verify X509 leaf i scheme ( Vec u ) sig ( Vec u ) signed → iVerify a signature with the leaf's public key under a TLS SignatureScheme (the TLS 1.2 ServerKeyExchange check). 0 ok · 9 unsupported · 3 bad sig.
@ tls_cert_verify ( Vec u ) cert_msg i cv_scheme ( Vec u ) cv_sig ( Vec u ) th_cert s hostname → itlsget — fetch an HTTPS URL over the pure-NURL TLS client and print the response. The installable binary for the tls package.
tlsget https://example.com/ (verify-full, the default) tlsget example.com 443 / (host / port / path form) tlsget --insecure https://self-signed.example/ (skip cert check)
@ main → i