Feature/tls keylog - #34
Draft
heshaoqiong-tuya wants to merge 4 commits into
Draft
Conversation
heshaoqiong-tuya
marked this pull request as draft
September 7, 2026 10:24
The Unreleased entry said (#32), the number the fix was expected to get. It merged as #31 (squash commit 3556669) and #32 does not exist yet, so the one link from the one-line entry back to its reasoning pointed at nothing. Also adds the trailing newline AGENTS.md was missing. Whitespace only. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Export TLS session secrets in NSS SSLKEYLOGFILE format so a packet capture of the SDK's cloud traffic can be decrypted in Wireshark. Opt-in and off by default; enabling it covers every channel (MQTT, ATOP, IoT-DNS, RTC/TAI) at once, since the facility lives in common/tls.c. - tls_keylog_open_file() / tls_keylog_close_file() write lines to a file (compiled in on POSIX and ESP-IDF, or with TLS_KEYLOG_FILE_SINK=1); tls_set_keylog_handler() routes them to a custom sink. - Process-wide, snapshotted per connection: tls_connect() reads the sink on the connecting thread, so the export callback is lock-free and every call site is inside the handshake. - The line bound is derived and self-checked; over-long lines are dropped, never truncated into an undecryptable log. - The file is the secret: 0600 from creation, O_NOFOLLOW|O_CLOEXEC, unbuffered and fsync'ed per line, stack copy zeroized, write failure logged once. - Runtime debug switch only; enabling it logs a LOG_WARN. Export needs mbedTLS 3.x. pal_t has no file interface, so the file sink uses libc I/O — a deliberate gap recorded in AGENTS.md rule 5. Docs: docs-site/docs/guides/tls-keylog.md covers the Wireshark setup. Tests: mqtt_test.c gains handler-capture and file-sink cases (skipped without a CA cert). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
SDK_VERSION agentic-kit_0.4.0 -> agentic-kit_0.5.0-dev, what `tools/bump_version next --minor` produces. v0.4.0 is tagged at 238045b. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
heshaoqiong-tuya
force-pushed
the
feature/tls-keylog
branch
from
September 8, 2026 10:01
05d9480 to
8cc03ee
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
feat(common): opt-in TLS key log for Wireshark decryption
What & why
All SDK-to-cloud traffic (IoT-DNS, ATOP HTTPS, MQTT, RTC/TAI) runs over TLS, so a
packet capture shows only ciphertext. Debugging "the cloud says my field is wrong" or
"the downstream frame won't parse" needs the plaintext application-layer data.
This PR adds an opt-in, off-by-default TLS key log in
common/tls.c: during thehandshake it exports the session secrets in NSS
SSLKEYLOGFILEformat, which Wiresharkreads to decrypt the same capture. Unlike terminating TLS at a proxy, this changes
neither the device's connection target nor its certificate. Because the facility lives in
common/tls.c, enabling it once covers every channel (MQTT / ATOP / IoT-DNS / RTC-TAI).API (
common/tls.h)tls_keylog_open_file(path)/tls_keylog_close_file()— write key-log lines to afile. Compiled in by default on POSIX and ESP-IDF; elsewhere opt in with
-DTLS_KEYLOG_FILE_SINK=1.tls_set_keylog_handler(fn, ctx)— route each line to your own sink (UART, log server,ring buffer).
per-connection setup.
LOG_WARN(
[tls] key logging ENABLED) — the only sign in production logs that a debug switchshipped.
Design notes
tls_connect()snapshots the current sinkinto the
tls_ton the connecting thread, and the export callback reads only its owncopy. Every
f_export_keyscall site is inside the handshake, and tls.c never enablesrenegotiation / KeyUpdate export, so the "read at handshake time" invariant tls.h
documents holds — swap the sink only while no
tls_connect()is in flight.TLS_KEYLOG_LINE_SIZEis derived fromMAX_LABEL(40)andMAX_SECRET(64)(= 236), with a_Static_asserton the longestlabel and a runtime check against the real
secret_lenbefore every write — anover-long line is dropped, never truncated into a silently undecryptable log.
open(2)withO_NOFOLLOW|O_CLOEXECand mode 0600 from creation (no fopen+fchmod 0666 window); thestream is unbuffered (a buffer holding secrets can't be wiped, and an unflushed line
won't decrypt), and a stream that can't be made unbuffered is refused; each line is
fsync'ed so a mid-session reset still leaves a decryptable capture; the stack linecopy is wiped with
mbedtls_platform_zeroize; a write failure is logged once(
LOG_ERROR).pal_thas no file interface, so the file sink uses libcfopen/fputs, compiled in only where a writable filesystem is the norm (this"deliberate gap" is documented in AGENTS.md rule 5) — a bare newlib port without
_open/_closestubs still linkstls.o.Output format
Which lines appear depends on the negotiated version: TLS 1.2 (what iot-client pins)
emits one
CLIENT_RANDOMline per connection; TLS 1.3 (what the RTC/TAI server maynegotiate) emits the handshake and application traffic secrets.
Tests
Two new tests in
modules/iot-client/test/mqtt_test.c(need a local test broker + CA;skipped when no certificate is loaded):
test_keylog_handler_captures_client_random: one TLS 1.2 handshake yields exactly onewell-formed
CLIENT_RANDOMline (48-byte master secret → 96 hex chars); asserts thekey logging ENABLEDwarning; clearing the sink stops further lines.test_keylog_file_sink: the file is written with exactly one line, a second open isrefused, replacing the sink leaves the file closable twice (idempotent), and the mode is
0600 on POSIX; the file is
unlink'ed on pass or fail so no keys are left behind.