nqtlnew(3)
NAME
nqtlnew, nqtlct,
nqtlhs, nqtlwr,
nqtlrd, nqtlend,
nqtlwhy, nqtlfree —
a TLS 1.3 client over the caller's transport
LIBRARY
libnoqtis, -lnoqtis
SYNOPSIS
#include
<noqtis.h>
typedef struct {
int (*wr)(void *user, const uint8_t *b, size_t n);
int (*rd)(void *user, uint8_t *b, size_t cap, size_t *got);
void *user;
} nqtlio;
int
nqtlnew(nqtls **t,
const char *host, const uint8_t
*ca, size_t calen, const nqtlio
*io);
int
nqtlct(nqtls
*t, const uint8_t
*json, size_t
len);
int
nqtlhs(nqtls
*t);
int
nqtlwr(nqtls
*t, const uint8_t
*b, size_t n);
int
nqtlrd(nqtls
*t, uint8_t *b,
size_t cap,
size_t *got);
int
nqtlend(nqtls
*t);
const char *
nqtlwhy(const
nqtls *t);
void
nqtlfree(nqtls
*t);
DESCRIPTION
These functions are a TLS 1.3 client (RFC 8446), so that a program
can fetch over HTTPS with noqtis alone. They exist only when noqtis was
built with the CMake option NQTLS; otherwise every
one returns NQENOSUP and
nqtlwhy()
says so. The library opens no connection: the caller connects a socket and
passes two callbacks. wr sends all
n bytes and returns NQOK, or
NQEIO. rd reads 1 to
cap bytes, sets *got, and
returns NQOK (with *got 0 at
the end of the stream) or NQEIO. Deadlines are the
callbacks' job.
nqtlnew()
makes a client for host: a DNS name (ASCII letters,
digits and hyphens, at most 253 bytes; one trailing dot is dropped), sent as
the server name and matched against the certificate, or an IPv4 or IPv6
address literal, matched against the certificate's addresses and not sent.
ca is the trust anchors: a PEM bundle (every
BEGIN CERTIFICATE block; a block noqtis cannot read
is left out) or one DER certificate, calen bytes,
copied.
nqtlhs()
runs the handshake: one ClientHello offering the groups X25519MLKEM768 (the
ML-KEM-768 hybrid of draft-ietf-tls-ecdhe-mlkem) and X25519 with a key share
for each, then secp256r1 and secp384r1 (P-256 and P-384) without one, the
suites TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384 and
TLS_CHACHA20_POLY1305_SHA256, and the signature schemes
ecdsa_secp256r1_sha256, ecdsa_secp384r1_sha384, rsa_pss_rsae_sha256/384/512
and, for certificates only, rsa_pkcs1; at most one HelloRetryRequest (with a
fresh key share for the group it asks for); the server's certificate path to
one of the anchors at the current time (names, signatures, validity,
basicConstraints, keyUsage, the serverAuth purpose, name constraints on DNS
names, addresses, directory names and mail addresses), the host name
(subjectAltName only; a wildcard only as the whole leftmost label), the
CertificateVerify signature and the Finished MAC. A CertificateRequest is
answered with an empty Certificate.
The ClientHello asks for OCSP stapling (status_request, RFC 6066). An OCSP response the server staples to its certificate (RFC 8446 section 4.4.2.1) must be about that certificate, signed by its issuer or by a responder the issuer named, and current; a revoked certificate ends the handshake with the alert certificate_revoked, a response that does not check with bad_certificate_status_response. No response is fine, except when the certificate carries the TLS Feature extension with status_request (must-staple, RFC 7633).
nqtlct(),
called before nqtlhs(), turns on Certificate
Transparency (RFC 6962). json is a log list of
len bytes, copied, in the form of Chrome's
log_list.json, version 3: operators, each with logs,
each with its log_id, key and state. The ClientHello then offers
signed_certificate_timestamp, and the server's certificate must carry SCTs,
embedded in it or sent in TLS, that come from logs of at least two
operators. An SCT counts when its log is in the list and was qualified,
usable or read-only (or retired, but after the SCT), the certificate's
notAfter is in the log's temporal interval if it has one, the SCT is not
from the future, and its signature (ECDSA P-256 or RSA, SHA-256) verifies
over the certificate, or for an embedded SCT over the precertificate the log
saw. Without nqtlct() no SCT is looked at: a log
list goes stale, so the caller keeps it current.
nqtlwr()
sends application data;
nqtlrd()
reads it, 1 to cap bytes at a time, or
*got 0 once the server has sent close_notify. A stream
that ends without close_notify is NQEIO: the data
may have been cut short. Session tickets are read and dropped; a KeyUpdate
is followed, and answered when the server asks.
nqtlend()
sends close_notify.
Any error is final: the client sends the alert RFC
8446 names, records a sentence that
nqtlwhy()
returns, and every later call returns the same code.
nqtlfree()
wipes every secret and frees the client; NULL is
allowed.
RETURN VALUES
NQOK, or:
NQEVRFY- a certificate, signature or MAC that does not check out
NQEDEC- a message TLS 1.3 does not allow at that point, a bundle with no certificate, or a log list that does not parse, holds no log, or a log whose log_id is not the hash of its key
NQENOSUP- a server that speaks nothing the client offers, or a build without
NQTLS NQEIO- the transport failed, the server ended the connection, or sent an alert
NQESTATE- a call out of order (data before the handshake, after close_notify;
nqtlct() afternqtlhs()) NQEARG- a bad argument, or a host name the client will not send
NQELIM,NQEMEM- a message over its limit (256 KiB, ten certificates), or no memory
SEE ALSO
STANDARDS
RFC 8446 (TLS 1.3), RFC 8448 (its example traces, which the tests reproduce byte for byte), draft-ietf-tls-ecdhe-mlkem, RFC 5280, RFC 6125, RFC 6066 and RFC 6960 (OCSP stapling), RFC 7633 (must-staple), RFC 6962 (Certificate Transparency).
CAVEATS
Not done: CRLs and OCSP queries (only a stapled response is checked, and only for the server's own certificate), policy constraints (a certificate that carries them as a critical extension is refused), name constraints on other name forms (a certificate with a name of such a form below one is refused), SCTs in an OCSP response, the rule of Chrome's policy on the number of SCTs by certificate lifetime, finite-field groups (a server with none of the offered groups fails), Ed25519 and ML-DSA server keys, TLS 1.2, resumption, 0-RTT, client certificates.