Skip to content

fix(service): only log TLS session keys when SSLKEYLOGFILE is set - #357

Merged
gab-arrobo merged 2 commits into
omec-project:mainfrom
midwell:fix/gate-tls-key-logging
Oct 7, 2026
Merged

gab-arrobo merged 2 commits into
omec-project:mainfrom
midwell:fix/gate-tls-key-logging

Conversation

@midwell

@midwell midwell commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

The SBI server writes the TLS session secrets of every connection it serves to a file, on every start, with no way to turn it off.

service/init.go hands http2_util.NewServer a key-log path next to the configuration file, so KeyLogWriter is always set and Go appends the secrets of each TLS session to sslkey.log for the life of the process. Anyone who can read that file can decrypt all the SBI traffic the NSSF has served over TLS, including traffic captured before they read it.

This is one of eight PRs, one per NF (amf, nrf, smf, pcf, udr, ausf, udm, nssf), with the same change in each. They are independent and can merge in any order.

What goes wrong

On a deployment with scheme: https and TLS 1.3, collecting sslkey.log from the eight NFs was enough to decrypt the SBI requests captured on the same deployment: 138 of them, across the NFs' restart and one UE's registration, PDU session and deregistration, with no configuration changed. The decrypted traffic includes the subscriber identifiers and the key material that crosses the SBI during authentication. TLS is in place and working; the file is what undoes it.

http2_util.NewServer already leaves key logging off for an empty path, and webconsole passes "". The NFs each compute a path unconditionally, so the library's gate is never used.

The change

The key-log path comes from SSLKEYLOGFILE, the variable curl, Firefox and Wireshark's documentation use for this. Go's standard library does not read it, so the NF reads it and passes it on. Unset, which is every normal deployment, the path is "" and no file is created. Set, the NF logs a warning at Warn that names the file and says what it exposes, from the https case and only when the server has a KeyLogWriter, that is, only when session keys will actually be written:

TLS key logging is enabled (SSLKEYLOGFILE=/home/sslkey.log): SBI traffic is decryptable by anyone who can read this file

No change to util, to the configuration schema, or to the Helm chart.

With key logging off, server.TLSConfig stays nil. That does not change ALPN: the old config set only KeyLogWriter, and ServeTLS takes h2 from http.Server.Protocols the same way for a nil config.

Limits

  • Anyone who can read the NF's filesystem can usually read its TLS private key as well. With TLS 1.3 that key alone does not decrypt traffic someone has captured; the key log does, for every session in it. This change removes that, and adds no authentication between NFs.
  • With scheme: http and SSLKEYLOGFILE set, the file is created but stays empty, and no warning is logged.
  • If SSLKEYLOGFILE names a path the NSSF cannot create, the open error is logged as initialize HTTP server: …, and the SBI server starts without key logging and without the warning.
  • A debug build (-tags debug) with util before v1.9.1 uses a debug NewServer that opens the path unconditionally and fails on an empty one, so it needs SSLKEYLOGFILE set or its SBI server does not start. util v1.9.1 fixes this; this branch pins v1.8.13.

Tests

No unit test: the change is in Start(), which binds the SBI port and blocks. Verified instead:

  • Locally, http2_util.NewServer(addr, "", h) with ListenAndServeTLS (util v1.8.13, Go 1.26): TLSConfig stays nil, a client offering h2 gets h2 and is served HTTP/2.0, one offering only http/1.1 gets http/1.1, and no key-log file is created.
  • On a test cluster with all eight NFs on scheme: https, built from the commit each was deployed at plus this change. With SSLKEYLOGFILE unset:
    • no NF wrote a key log, and none logged the warning;
    • a UE registered, set up a PDU session, passed traffic and deregistered;
    • every NF answered a TLS 1.3 handshake offering h2 with h2;
    • a capture of the same procedure, taken while the NFs restarted, could not be decrypted: 0 requests, against 138 before.
  • With SSLKEYLOGFILE set on the NRF only, and the NRF at log level warn: the file appeared, the warning was logged, and the NRF's own traffic in a new capture could be decrypted again (16 requests, all on the NRF's port, none on the other NFs').
  • After the second commit, on the NRF: with a writable SSLKEYLOGFILE the warning is logged once, from the https case, and the file is written; with /nonexistent/dir/sslkey.log there is no warning, the open error is logged as initialize HTTP server: create pre-master-secret log [...] fail, and the NRF still serves TLS 1.3 with h2.

Gate

pre-commit run --all-files passed on 223d5a8 in a Linux container (golang:1.26, GOWORK=off): gitleaks v8.30.1, gci v0.14.0, staticcheck v0.8.1, go test -race ./... -count=20, golangci-lint v2.13.2 (the version CI pins), yamlfmt v0.21.0, pre-commit-hooks v6.0.0 and reuse v6.2.0.

🤖 Generated with Claude Code

midwell and others added 2 commits October 7, 2026 08:42
The SBI server was given a key-log path next to the configuration file
on every start, so Go wrote the TLS session secrets of every SBI
connection to sslkey.log for the life of the process. Anyone able to
read that file can decrypt all SBI traffic the NF served over TLS.

http2_util.NewServer already leaves key logging off for an empty path
(webconsole passes ""). Take the path from SSLKEYLOGFILE instead, so it
is off unless an operator asks for it, and log a warning naming the
file when it is on.

With key logging off, server.TLSConfig stays nil; ServeTLS still
negotiates h2 from http.Server.Protocols, as before.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Edvin Lindqvist <edvin.lindqvist@forsway.com>
The warning fired whenever SSLKEYLOGFILE was set, before the server
scheme was known and before the file was opened. With scheme: http no
TLS session keys are written, and if the file cannot be opened no key
log is attached, so in both cases the warning claimed an exposure that
did not exist.

Warn in the https case, and only when the server has a KeyLogWriter.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Edvin Lindqvist <edvin.lindqvist@forsway.com>
@gab-arrobo
gab-arrobo force-pushed the fix/gate-tls-key-logging branch from 223d5a8 to daf762e Compare October 7, 2026 15:42
@gab-arrobo
gab-arrobo merged commit 82d157c into omec-project:main Oct 7, 2026
12 checks passed
@midwell
midwell deleted the fix/gate-tls-key-logging branch October 7, 2026 17:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants