NURLNURL registrynurl-lang.org →

← tls

tls 0.1.1 API

tls.nu

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.

API

& 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 TlsErr

Establish 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 TlsErr

Run 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 TlsErr

Verifying 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 ) TlsErr

Read 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 → v


verify.nu

packages/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

API

@ tls_chain_verify ( Vec u ) cert_msg i is12 s hostname → i

Top-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 → X509

Leaf 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 → i

Verify 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 → i


main.nu

tlsget — 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)

API

@ main → i