Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

TLS

ytls.h

The ytls.h header file defines the interface for the TLS (Transport Layer Security) functionality in the Yuneta framework. It provides function declarations and structures for handling secure communication using TLS. Key features include:

Architecture

The ytls module uses a backend-agnostic design. The public API (ytls.h / ytls.c) exposes a single api_tls_t dispatch table, while the actual crypto is provided by two interchangeable backends configured via Kconfig (one or both can be enabled):

Both backends can be enabled simultaneously. When both are present, OpenSSL is preferred as the default.

ytls.h is the single source of truth for the backend names:

At runtime, two matching yuno global variables are available — root-linux’s yunetas_register_c_core() publishes them into gobj’s global-variable pool via gobj_add_global_variable(), so gobj-c itself stays free of any CONFIG_HAVE_OPENSSL / CONFIG_HAVE_MBEDTLS checks:

Source files

FilePurpose
ytls.h / ytls.cPublic API and dispatch table
tls/openssl.c / openssl.hOpenSSL backend implementation
tls/mbedtls.c / mbedtls.hmbed-TLS backend implementation

Backend implementations

Both backends implement the same functionality:

This module ensures that Yuneta applications can securely transmit data over the network using industry-standard encryption protocols.

Hot-reloading certificates

ytls can swap the certificate material of a running TLS listener without dropping the sessions that are already established. This is how Yuneta keeps MQTT brokers, intake gates and control-plane channels online while Let’s Encrypt renews a certificate in the background.

API

ytls_reload_certificates() is the only entry point. It rebuilds the per-backend context from the cert paths already stored in the ytls handle, validates it, and atomically swaps it in. If any step fails, the previous context is kept intact and the caller sees a negative return — traffic is never interrupted by a bad reload.

ytls_get_cert_info() returns subject, issuer, not_before, not_after, serial and days_remaining for the currently-active context, so operators can observe the live cert (not just the file on disk).

How live sessions survive the swap

The ytls handle is repointed from old_ctx to new_ctx and frees its own reference to old_ctx; live SSL sessions still hold their own refs, so old_ctx survives until the last in-flight session closes, while new sessions attach to new_ctx.

The handle drops its ref on old_ctx; live sessions keep theirs, so the old context outlives the swap.

The invariant is: live sessions hold their own reference on the old context, so the swap only drops the handle’s reference. This is the easiest thing to break when touching the reload path.

Callers

Yuneta ships three layers of defence that all drive ytls_reload_certificates() through the same path — see the TLS certificate management guide for the full picture:

  1. Layer 1 — certbot deploy hook. Pushes the reload-certs command down the yuno tree the moment certbot succeeds.

  2. Layer 2 — c_agent auto-sync timer. Re-reads the cert files every cert_sync_interval_sec seconds (default 15 min) and broadcasts reload-certs when size+mtime changes.

  3. Layer 3 — c_yuno expiry monitor. Periodic view-cert walk with warning / critical thresholds (cert_warn_days, cert_critical_days); alerts only — never reloads.

Each C_TCP_S / C_UDP_S listener also exposes reload-certs and view-cert directly, so a single listener can be targeted from ycommand without touching the rest of the yuno.

TLS security posture (hardening)

ytls is secure-by-default: a gate that sets no TLS knobs gets the hardened behaviour, and every deliberate relaxation is an explicit, logged downgrade — the yuneta “no silent errors” axiom applied to crypto. The knobs below live in the gate’s crypto config; the same key means the same thing on both backends.

Protocol floor — ssl_min_version

Both backends floor at TLS 1.2 when ssl_min_version is unset.

Renegotiation — ssl_disable_renegotiation

Defaults to true (renegotiation disabled), closing the renegotiation-based DoS/abuse surface; TLS 1.3 has no renegotiation at all. Set it to false only on a gate that genuinely needs it — that re-enable is logged as an auditable downgrade. OpenSSL backend only.

Peer verification — ssl_verify_mode

When unset, the mode is computed from the gate’s role and whether a CA is configured:

SituationComputed default
no CA configurednone — preserves historical IoT / PSK / self-signed behaviour
server with a CAoptional — request + verify the client cert if presented
client with a CArequired — validate the server cert + hostname

ssl_verify_mode (required / optional / none) overrides the computed default; ssl_use_system_ca adds the OS trust store; ssl_verify_depth bounds the chain. A verify failure is never silent: under optional a non-verifying peer is accepted but logged post-handshake (OpenSSL SSL_get_verify_result(), mbed-TLS mbedtls_ssl_get_verify_result()); under required the verify callback logs the chain error and aborts the handshake. The mbed-TLS verify default is deliberately left IoT-tolerant (optional, surfaced) — raise it per gate with ssl_verify_mode=required.

Knob summary

KnobDefaultEffect
ssl_min_versionTLS 1.2 floorminimum negotiated protocol version
ssl_disable_renegotiationtrueOpenSSL renegotiation off
ssl_verify_modecomputed (table above)required / optional / none
ssl_use_system_cafalsealso trust the OS CA store
ssl_verify_depth1max certificate chain depth
ssl_ciphersbackend defaultcipher list (@SECLEVEL=0 to reach legacy suites)

Regression coverage: test_tls_floor_openssl.c asserts that an explicit sub-TLS1.2 floor is logged and that a real TLS1.0 ClientHello is rejected by the default floor; test_tls_verify_openssl.c drives a real client/server handshake and asserts that a trusted cert with a matching host connects, while a hostname mismatch or an unknown CA is rejected.

Deployment — gate profiles & rollout

Secure-by-default means the upgrade itself breaks nothing: modern peers keep negotiating on the new TLS 1.2 floor, renegotiation-off is transparent, and peer verification stays off on any gate until you give it a CA. Hardening a gate is then just a few keys in its crypto config. Two profiles cover almost every gate.

Profile A — high-security gate

For SPA / BFF front ends, host-to-host links and the control plane — gates that talk to modern peers. Turn verification on; the floor, reneg-off and the computed required/optional mode are already the defaults, so the CA is the only thing you add.

A client that dials out (validates the server it connects to):

"crypto": {
    "library": "openssl",
    "ssl_trusted_certificate": "/yuneta/store/.../trusted_ca.pem",
    "ssl_server_name": "api.example.com"
}

A server doing mutual-TLS (validates the client certificate):

"crypto": {
    "library": "openssl",
    "ssl_certificate": "/path/server.pem",
    "ssl_certificate_key": "/path/server.key",
    "ssl_trusted_certificate": "/path/client_ca.pem",
    "ssl_verify_mode": "required"
}

A CA-configured server defaults to optional (request + verify the client cert if presented, tolerate absent); set required to reject clients without a valid certificate.

Profile B — IoT / legacy-compat gate

For old devices that cannot do TLS 1.2, or that use PSK / self-signed certs. Relax explicitly — each relaxation is logged and stays greppable.

"crypto": {
    "library": "openssl",
    "ssl_min_version": "TLS1.0",
    "ssl_ciphers": "HIGH:@SECLEVEL=0",
    "ssl_verify_mode": "none"
}

Rollout procedure

  1. Upgrade. Nothing breaks; the reduced-security gates simply start announcing themselves in the log.

  2. Enumerate from the logs. Every gap is designed to be greppable:

    Log messageMeaningAction
    TLS client WITHOUT server-certificate validationa client is not validating the server it dialsadd a CA → Profile A
    TLS handshake rejecteda peer was refused (often a legacy device below the floor)if legitimate, move that gate to Profile B
    legacy floor below TLS1.2a gate is on a relaxed floorconfirm it is an intended IoT gate
    renegotiation explicitly enableda gate re-enabled renegotiationconfirm it is needed
    peer certificate did NOT verifyan optional gate accepted an invalid certfix the chain, or raise to required
  3. Harden the high-level gates one at a time: apply Profile A, restart, confirm the warning is gone and traffic still flows.

  4. Pin the legacy gates that showed TLS handshake rejected to Profile B.

  5. Validate in staging, then production. Roll the config to a staging environment first and watch the same logs; only then promote to production.

Goal state: no WITHOUT server-certificate validation lines outside known IoT gates, and every legacy floor / renegotiation enabled line traceable to a deliberate Profile-B gate.

Philosophy of ytls

The ytls module is built with the core philosophy of Yuneta in mind:

By following these principles, ytls ensures that Yuneta-based applications maintain strong security without unnecessary complexity.