net.mbedtls #
net.mbedtls
With validate: true and an empty verify, SSL clients load trusted CA certificates from SSL_CERT_FILE when it names an existing file, or from the first available common system CA bundle. Initialization fails if no bundle is found or the selected bundle cannot be loaded. Set verify explicitly to select a CA file or in-memory CA certificates instead. Loading a CA bundle does not configure a client certificate. Servers that request optional client authentication can accept clients without cert and cert_key.
SSLConn.shutdown() also releases contexts created by new_ssl_conn before a successful connection, including their ALPN configuration. Calling it again after cleanup returns the existing connection was not open error without releasing resources a second time.
On Linux, TLS sends suppress SIGPIPE for each socket operation. A peer that closes the connection produces a connection-reset error without terminating the process or changing its SIGPIPE handler. Nonblocking backpressure remains retryable.
Thread safety
SSLConn does not synchronize access to its TLS context. Serialize calls on the same connection, including reads, writes, timeout changes, and shutdown. Concurrent calls from one reader thread and one writer thread are unsupported. A blocking read held under a mutex can prevent the writer from progressing; the mutex alone is not a duplex adapter.
Keep a connected SSLConn at a stable address and stop all users before shutdown. Accepted connections keep independent read timeouts: set_read_timeout() changes only that connection. They borrow the listener's TLS credentials and other configuration allocations, so shut down all accepted connections before shutting down their listener. See net.ssl thread safety for retry and lifetime guidance.
fn build_certificate_chain #
fn build_certificate_chain(der_certs [][]u8) !&C.mbedtls_x509_crt
build_certificate_chain parses a list of DER-encoded certificates (leaf-first, as TLS 1.3's Certificate message orders them — RFC 8446 §4.4.2: "The sender's certificate MUST come in the first CertificateEntry in the list") into one mbedTLS certificate chain, entirely standalone: no mbedtls_ssl_context is constructed or needed (confirmed safe in x509_standalone_test.v). This is the call shape net.quic needs — QUIC carries the TLS 1.3 handshake over its own CRYPTO frames, bypassing mbedTLS's own SSL/record-layer state machine (and therefore the net.mbedtls.SSLConn-mediated path net.http's TLS clients use) entirely.
The caller owns the returned chain and MUST call free_certificate_chain when done — mbedtls_x509_crt holds C-heap-allocated internal buffers with no GC visibility.
DER bytes, NOT PEM: unlike this module's PEM-string helpers elsewhere (new_sslcerts_in_memory et al.), each der_certs entry is passed to mbedtls_x509_crt_parse with its EXACT length, no NUL-terminator byte appended. Confirmed against mbedTLS's own source (x509_crt.c): the PEM-vs-DER format sniff only checks buf[buflen-1]=='\0' combined with a "-----BEGIN CERTIFICATE-----" substring match; appending a NUL byte a real DER buffer doesn't have would be an out-of-bounds read one byte past a V []u8 slice's allocation, not just a harmless extra byte.
This exact-length requirement is verified by SOURCE INSPECTION only, not by a passing test: empirically, mbedTLS's DER parser tolerates a too-long declared buflen for well-formed input (its ASN.1 SEQUENCE length is self-describing, so it simply stops reading where the structure says to) — passing der.len+1 here still passes every test in this file, because the extra out-of-bounds byte is real undefined behavior (adjacent-heap-memory dependent, not a guaranteed crash or parse failure) rather than something a functional test can observe. Trust the source-level reasoning above, not test results, for this one. Also confirmed mbedtls_x509_crt_parse_der always copies the buffer (own_buffer=1) rather than retaining a pointer into it, so the parsed chain has no dangling reference back into der_certs once this function returns.
Repeated calls append, they don't overwrite: mbedtls_x509_crt_parse_der _internal walks to the existing tail (while crt->version != 0 && crt->next != NULL) and allocates+links a new node there before parsing into it, so calling this in a loop on the same chain correctly builds a multi-certificate chain rather than clobbering earlier entries. This is source-verified (x509_crt.c), not test-verified — every test using this function so far passes only a single certificate (this codebase has one real test cert fixture available); a genuine 2+-certificate functional test needs a second cert and is deliberately deferred, not silently skipped.
fn check_server_cert_usage #
fn check_server_cert_usage(chain &C.mbedtls_x509_crt) !
check_server_cert_usage verifies chain's LEAF certificate is actually usable as a TLS server signing key: its keyUsage extension (if present) permits digitalSignature, and its extendedKeyUsage extension (if present) permits serverAuth. mbedtls_x509_crt_verify (verify_certificate_chain above) only checks chain-of-trust and hostname/SAN -- a real TLS handshake separately enforces this via mbedtls_ssl_check_cert_usage, which this standalone (never-constructs-an-SSL-context) call path has no equivalent for otherwise. Without this, a certificate that chains to a trusted CA and matches the hostname, but was issued restricted to a DIFFERENT purpose (e.g. clientAuth-only EKU, or a KeyUsage lacking digitalSignature), would still be accepted as a QUIC/TLS 1.3 server certificate (Codex P1, vlang/v#27680 pullrequestreview-4783410111). chain must be the HEAD node of a chain built by build_certificate_chain (the leaf), same precondition as get_leaf_public_key.
fn free_certificate_chain #
fn free_certificate_chain(chain &C.mbedtls_x509_crt)
free_certificate_chain releases a chain returned by build_certificate_chain. Like every other mbedTLS resource in this module, calling it twice on the same chain is a double-free — callers own exactly one matching free_certificate_chain call per build_certificate_chain call.
fn get_leaf_public_key #
fn get_leaf_public_key(chain &C.mbedtls_x509_crt) &C.mbedtls_pk_context
get_leaf_public_key returns the public key of chain's HEAD certificate -- the "leaf"/end-entity certificate. build_certificate_chain always parses the sender's own certificate first (RFC 8446 §4.4.2's own wire order: "The sender's certificate MUST come in the first CertificateEntry in the list"), and mbedtls_x509_crt_parse's chain-append walks forward from that head node to link subsequent certificates -- so chain itself, unwalked, already points at the leaf. The returned pointer's lifetime is tied to chain: do not call this after free_certificate_chain.
fn new_ssl_conn #
fn new_ssl_conn(config SSLConnectConfig) !&SSLConn
new_ssl_conn returns a new SSLConn with the given config.
fn new_ssl_listener #
fn new_ssl_listener(saddr string, config SSLConnectConfig, options SSLListenerOptions) !&SSLListener
new_ssl_listener creates a new SSLListener binding to saddr.
fn new_sslcerts #
fn new_sslcerts() &SSLCerts
new_sslcerts initializes and returns a pair of SSL certificates and key
fn new_sslcerts_from_file #
fn new_sslcerts_from_file(verify string, cert string, cert_key string) !&SSLCerts
new_sslcerts_from_file creates a new pair of SSL certificates, given their paths on the filesystem.
fn new_sslcerts_in_memory #
fn new_sslcerts_in_memory(verify string, cert string, cert_key string) !&SSLCerts
new_sslcerts_in_memory creates a pair of SSL certificates, given their contents (not paths).
fn public_key_curve_is_secp256r1 #
fn public_key_curve_is_secp256r1(pk &C.mbedtls_pk_context) bool
public_key_curve_is_secp256r1 reports whether pk is an EC key on the secp256r1 (P-256, aka prime256v1/NIST P-256) curve specifically -- distinct from merely being an EC key, which mbedtls_pk_verify_ext alone confirms. A TLS 1.3 SignatureScheme like ecdsa_secp256r1_sha256 names one exact curve (RFC 8446 §4.2.3); a certificate whose EC key is actually P-384/P-521/any other curve must not be accepted under that scheme name, even though the signature math itself would verify correctly for a genuine signature made with that OTHER curve's key (this isn't a broken- crypto scenario, it's a protocol-identity mismatch between the claimed scheme and the actual key).
fn verify_certificate_chain #
fn verify_certificate_chain(chain &C.mbedtls_x509_crt, ca_bundle_pem string, hostname string) !
verify_certificate_chain validates chain (from build_certificate_chain) against ca_bundle_pem, one or more trusted CA certificates concatenated in PEM format, AND that hostname matches the leaf certificate's SAN/CN. This mirrors SSLConnectConfig.verify's existing contract in this same module: the caller supplies the trust anchor explicitly — there is no OS trust-store lookup anywhere in this codebase today, for any TLS client (HTTP/1.1, HTTP/2, or this QUIC path).
hostname is passed straight through as mbedtls_x509_crt_verify's cn parameter — mbedTLS itself does the SAN/CN matching (DNS names and IP addresses fully supported per its own doc comment), the same mechanism mbedtls_ssl_set_hostname wires into a full SSL handshake's verification, which this standalone call deliberately bypasses. An empty hostname is intentionally NOT special-cased into "skip the check" (passing nil to mbedTLS): it is passed through as an empty C string, which cannot match any real certificate's SAN/CN, so a caller that forgets to supply a real hostname fails closed instead of silently disabling verification.
fn verify_ecdsa_signature #
fn verify_ecdsa_signature(pk &C.mbedtls_pk_context, md_alg MbedtlsMdType, hash []u8, signature []u8) !
verify_ecdsa_signature checks an ECDSA signature over hash -- a digest the CALLER has already computed using the algorithm md_alg names; this function does not hash hash itself. pk may be an MBEDTLS_PK_ECKEY context (the type produced by parsing an EC certificate, not specifically MBEDTLS_PK_ECDSA) -- confirmed against pk_wrap.c's eckey_can_do(), which explicitly accepts MBEDTLS_PK_ECDSA verification requests against an MBEDTLS_PK_ECKEY-typed key, not assumed from the type names alone.
fn verify_rsa_pss_signature #
fn verify_rsa_pss_signature(pk &C.mbedtls_pk_context, md_alg MbedtlsMdType, hash []u8, signature []u8) !
verify_rsa_pss_signature checks an RSASSA-PSS signature the same way, with the salt length pinned to exactly hash.len (not MBEDTLS_RSA_SALT_LEN_ANY) -- RFC 8446 §4.2.3 mandates this for TLS 1.3's rsa_pss_rsae_* schemes ("the length of the Salt MUST equal the length of the digest algorithm"), and this vendored build enforces it rather than silently ignoring it: MBEDTLS_USE_PSA_CRYPTO is disabled (confirmed in mbedtls_config.h, not assumed), so mbedtls_pk_verify_ext's documented "salt length not verified under PSA crypto" caveat does not apply here. The check itself is real, not a no-op: rsa.c's rsa_rsassa_pss_verify_ext rejects a mismatch outright ("if (expected_salt_len != MBEDTLS_RSA_SALT_LEN_ANY && observed_salt_len != (size_t) expected_salt_len) { ... fail }") whenever a specific length (not MBEDTLS_RSA_SALT_LEN_ANY) is supplied -- confirmed by reading rsa.c, not assumed from the header comment alone. mbedTLS's own TLS 1.3 implementation sets expected_salt_len the identical way for the identical reason (ssl_tls13_generic.c: rsassa_pss_options.expected_salt_len = PSA_HASH_LENGTH(hash_alg)), so this isn't a novel usage pattern.
fn MbedtlsMdType.from #
fn MbedtlsMdType.from[W](input W) !MbedtlsMdType
from returns the MbedtlsMdType value of an integer or of a field name, or an error if there is none.
enum MbedtlsMdType #
enum MbedtlsMdType {
sha256 = 0x09
sha384 = 0x0a
sha512 = 0x0b
}
MbedtlsMdType names the subset of mbedtls_md_type_t (md.h) this module's signature-verification functions accept, as the exact enum values mbedTLS itself defines (confirmed against the vendored header, not assumed) -- TLS 1.3's three mandatory-to-implement hash algorithms for CertificateVerify (RFC 8446 §4.2.3: ecdsa_secp256r1_sha256 and the three rsa_pss_rsae_* schemes) and nothing else, since nothing else is used anywhere in this codebase yet.
struct C.mbedtls_ctr_drbg_context #
struct C.mbedtls_ctr_drbg_context {}
struct C.mbedtls_entropy_context #
struct C.mbedtls_entropy_context {}
struct C.mbedtls_net_context #
struct C.mbedtls_net_context {
mut:
fd int
}
struct C.mbedtls_pk_context #
struct C.mbedtls_pk_context {}
struct C.mbedtls_pk_rsassa_pss_options #
struct C.mbedtls_pk_rsassa_pss_options {
mut:
mgf1_hash_id int
expected_salt_len int
}
mbedtls_pk_rsassa_pss_options mirrors mbedTLS's own struct (pk.h) field-for-field rather than being kept opaque like mbedtls_x509_crt/ mbedtls_pk_context elsewhere in this file: unlike those, it has no MBEDTLS_PRIVATE-wrapped fields and no internal invariants beyond "two plain ints" -- safe to hand-replicate, unlike the cases this module deliberately keeps behind C shims instead (see mbedtls_helpers.h).
struct C.mbedtls_ssl_config #
struct C.mbedtls_ssl_config {}
struct C.mbedtls_ssl_context #
struct C.mbedtls_ssl_context {}
struct C.mbedtls_ssl_recv_t #
struct C.mbedtls_ssl_recv_t {}
struct C.mbedtls_ssl_recv_timeout_t #
struct C.mbedtls_ssl_recv_timeout_t {}
struct C.mbedtls_ssl_send_t #
struct C.mbedtls_ssl_send_t {}
struct C.mbedtls_x509_crl #
struct C.mbedtls_x509_crl {}
struct C.mbedtls_x509_crt #
struct C.mbedtls_x509_crt {}
struct SSLCerts #
struct SSLCerts {
pub mut:
cacert C.mbedtls_x509_crt
client_cert C.mbedtls_x509_crt
client_key C.mbedtls_pk_context
}
SSLCerts represents a pair of CA and client certificates + key
fn (SSLCerts) cleanup #
fn (mut c SSLCerts) cleanup()
cleanup frees the SSL certificates
struct SSLConn #
struct SSLConn {
mut:
initialized bool
// Accepted connections borrow the allocations referenced by their config copy.
owns_config bool = true
pub:
config SSLConnectConfig
pub mut:
server_fd C.mbedtls_net_context
ssl C.mbedtls_ssl_context
conf C.mbedtls_ssl_config
certs &SSLCerts = unsafe { nil }
ctr_drbg C.mbedtls_ctr_drbg_context
entropy C.mbedtls_entropy_context
handle int
duration time.Duration
opened bool
ip string
read_timeout time.Duration
owns_socket bool
// alpn_list is a NUL-terminated C array of pointers to the protocol
// strings in config.alpn_protocols. mbedtls stores this pointer without
// copying, so it must outlive the SSL config; it is freed in shutdown().
alpn_list &&char = unsafe { nil }
// last_write_sent reports the most recent write_ptr's progress for retry
// decisions: 0 = provably nothing was sent (safe to replay), or -1 = the
// count is indeterminate because a failed/retryable write may have already
// flushed a record to the peer (TLS cannot prove zero). On full success it
// equals the bytes written.
last_write_sent int
}
SSLConn is a TLS connection. It does not synchronize access to its TLS context. Calls on the same connection must be serialized, including read, write, timeout changes, and close/shutdown. One reader and one writer must not call it concurrently.
fn (SSLConn) complete_handshake #
fn (mut conn SSLConn) complete_handshake(timeout time.Duration) !
complete_handshake finishes the TLS server handshake on a conn returned by accept_raw_with_timeout, waiting up to timeout, then restores blocking mode and the blocking bio. It never calls shutdown: on any error it returns and the caller owns cleanup (so the conn is shut down exactly once, by the caller).
fn (SSLConn) read_timeout #
fn (s &SSLConn) read_timeout() time.Duration
read_timeout returns the current SSL read timeout.
fn (SSLConn) set_read_timeout #
fn (mut s SSLConn) set_read_timeout(timeout time.Duration)
set_read_timeout sets the SSL read timeout for subsequent operations.
fn (SSLConn) close #
fn (mut s SSLConn) close() !
close terminates the ssl connection and does cleanup
fn (SSLConn) shutdown #
fn (mut s SSLConn) shutdown() !
shutdown releases the TLS context and any owned socket, including before a connection opens.
fn (SSLConn) negotiated_alpn #
fn (s &SSLConn) negotiated_alpn() string
negotiated_alpn returns the ALPN protocol selected during the TLS handshake (e.g. 'h2' or 'http/1.1'), or an empty string if no protocol was negotiated.
fn (SSLConn) connect #
fn (mut s SSLConn) connect(mut tcp_conn net.TcpConn, hostname string) !
connect sets up an ssl connection on an existing TCP connection
fn (SSLConn) dial #
fn (mut s SSLConn) dial(hostname string, port int) !
dial opens an ssl connection on hostname:port
fn (SSLConn) addr #
fn (s &SSLConn) addr() !net.Addr
addr retrieves the local ip address and port number for this connection
fn (SSLConn) peer_addr #
fn (s &SSLConn) peer_addr() !net.Addr
peer_addr retrieves the ip address and port number used by the peer
fn (SSLConn) socket_read_into_ptr #
fn (mut s SSLConn) socket_read_into_ptr(buf_ptr &u8, len int) !int
socket_read_into_ptr reads len bytes into buf
fn (SSLConn) read #
fn (mut s SSLConn) read(mut buffer []u8) !int
read reads data from the ssl connection into buffer
fn (SSLConn) write_ptr #
fn (mut s SSLConn) write_ptr(bytes &u8, len int) !int
write_ptr writes len bytes from bytes to the ssl connection
fn (SSLConn) write #
fn (mut s SSLConn) write(bytes []u8) !int
write writes data from bytes to the ssl connection
fn (SSLConn) write_string #
fn (mut s SSLConn) write_string(str string) !int
write_string writes a string to the ssl connection
fn (SSLConn) wait_for_write #
fn (mut s SSLConn) wait_for_write(timeout time.Duration) !
wait_for_write waits for a write io operation to be available. Pure raw-socket select() on s.handle — never touches the TLS context, so it is safe to call without holding any lock that guards concurrent access to the context itself (see h2_pooled_transport.v).
fn (SSLConn) wait_for_read #
fn (mut s SSLConn) wait_for_read(timeout time.Duration) !
wait_for_read waits for a read io operation to be available. Pure raw-socket select() on s.handle — never touches the TLS context, so it is safe to call without holding any lock that guards concurrent access to the context itself (see h2_pooled_transport.v).
struct SSLConnectConfig #
struct SSLConnectConfig {
pub:
verify string // the path to a rootca.pem file, containing trusted CA certificate(s)
cert string // the path to a cert.pem file, containing client certificate(s) for the request
cert_key string // the path to a key.pem file, containing private keys for the client certificate(s)
validate bool // set this to true, if you want to stop requests, when their certificates are found to be invalid
in_memory_verification bool // if true, verify, cert, and cert_key are read from memory, not from a file
get_certificate ?fn (mut SSLListener, string) !&SSLCerts
read_timeout time.Duration = default_mbedtls_client_read_timeout // the SSL client read timeout
alpn_protocols []string // the list of ALPN protocols to advertise, e.g. ['h2', 'http/1.1']; empty means no ALPN extension is sent
}
struct SSLListener #
struct SSLListener {
saddr string
config SSLConnectConfig
options SSLListenerOptions
mut:
server_fd C.mbedtls_net_context
ssl C.mbedtls_ssl_context
conf C.mbedtls_ssl_config
certs &SSLCerts = unsafe { nil }
ctr_drbg C.mbedtls_ctr_drbg_context
entropy C.mbedtls_entropy_context
rng_mutex &sync.Mutex = sync.new_mutex()
opened bool
// alpn_list is a NUL-terminated C array of pointers to the protocol
// strings in config.alpn_protocols, advertised by accepted connections.
// It must outlive the SSL config and is freed in shutdown().
alpn_list &&char = unsafe { nil }
// handle int
// duration time.Duration
}
SSLListener listens on a TCP port and accepts connection secured with TLS
fn (SSLListener) shutdown #
fn (mut l SSLListener) shutdown() !
finish the listener and clean up resources
fn (SSLListener) accept #
fn (mut l SSLListener) accept() !&SSLConn
accept accepts a new connection and returns an SSLConn of the connected client.
fn (SSLListener) accept_with_timeout #
fn (mut l SSLListener) accept_with_timeout(timeout time.Duration) !&SSLConn
accept_with_timeout waits up to timeout for a new client before accepting it.
fn (SSLListener) accept_raw_with_timeout #
fn (mut l SSLListener) accept_raw_with_timeout(accept_timeout time.Duration) !&SSLConn
accept_raw_with_timeout waits up to accept_timeout for a new client, accepts the raw TCP connection and sets up its (non-blocking) SSL context, but does NOT perform the TLS handshake. The returned conn is non-blocking with a non-blocking bio; the caller must run conn.complete_handshake to finish negotiation. This lets the threaded server accept on one thread and handshake on a worker thread.
fn (SSLListener) accept_with_timeouts #
fn (mut l SSLListener) accept_with_timeouts(accept_timeout time.Duration, handshake_timeout time.Duration) !&SSLConn
accept_with_timeouts waits up to accept_timeout for a new client, then waits up to handshake_timeout for the TLS server handshake to complete.
struct SSLListenerOptions #
struct SSLListenerOptions {
pub:
family net.AddrFamily = .unspec
}
SSLListenerOptions configures the TCP listener used by an SSLListener.
- README
- fn build_certificate_chain
- fn check_server_cert_usage
- fn free_certificate_chain
- fn get_leaf_public_key
- fn new_ssl_conn
- fn new_ssl_listener
- fn new_sslcerts
- fn new_sslcerts_from_file
- fn new_sslcerts_in_memory
- fn public_key_curve_is_secp256r1
- fn verify_certificate_chain
- fn verify_ecdsa_signature
- fn verify_rsa_pss_signature
- fn MbedtlsMdType.from
- enum MbedtlsMdType
- struct C.mbedtls_ctr_drbg_context
- struct C.mbedtls_entropy_context
- struct C.mbedtls_net_context
- struct C.mbedtls_pk_context
- struct C.mbedtls_pk_rsassa_pss_options
- struct C.mbedtls_ssl_config
- struct C.mbedtls_ssl_context
- struct C.mbedtls_ssl_recv_t
- struct C.mbedtls_ssl_recv_timeout_t
- struct C.mbedtls_ssl_send_t
- struct C.mbedtls_x509_crl
- struct C.mbedtls_x509_crt
- struct SSLCerts
- struct SSLConn
- struct SSLConnectConfig
- struct SSLListener
- struct SSLListenerOptions