Skip to content

Add TLS 1.3 transport with PQC hybrid key exchange#526

Open
sergio-correia wants to merge 4 commits into
linux-audit:tlsfrom
sergio-correia:pqc
Open

Add TLS 1.3 transport with PQC hybrid key exchange#526
sergio-correia wants to merge 4 commits into
linux-audit:tlsfrom
sergio-correia:pqc

Conversation

@sergio-correia
Copy link
Copy Markdown
Contributor

Adds encrypted transport for remote audit logging using TLS 1.3 with X25519MLKEM768 hybrid key exchange (OpenSSL >= 3.5). Supports PSK and certificate authentication (mutually exclusive). Session resumption and 0-RTT disabled to force fresh PQC key exchange per connection.

New config options: tls_cert_file, tls_key_file, tls_ca_file, tls_psk_file, tls_psk_identity, tls_cipher_suites, tls_key_exchange, tls_require_pqc, tls_client_auth (server only).

Add --enable-tls build option (OpenSSL >= 3.5), client-side TLS
config parsing, and TLS transport to the audisp-remote plugin.

The transport uses TLS 1.3 with X25519MLKEM768 hybrid key exchange
for post-quantum confidentiality, with classical X25519 fallback
when PQC groups are unavailable. The tls_require_pqc option enables
fail-closed PQC enforcement via an allowlist in common/common.h.

Both PSK and certificate-based authentication are supported. Server
certificate verification is gated on tls_ca_file presence, with
hostname/IP-aware SNI handling per RFC 6066. Session resumption
and 0-RTT are disabled to force fresh key exchange per connection.

Shared TLS helpers (is_pqc_group, tls_validate_key_file,
tls_load_psk) are placed in common/common.h with a log callback
to avoid code duplication with the server side.

Assisted-by: Claude Opus 4.6
Signed-off-by: Sergio Correia <scorreia@redhat.com>
Add server-side TLS config parsing and transport to auditd for
receiving audit events over encrypted connections.

Mirrors the client-side TLS implementation with the same crypto
defaults: TLS 1.3 minimum, X25519MLKEM768 hybrid key exchange,
session resumption disabled. Adds tls_client_auth for optional
or required mutual TLS with client certificates.

PSK identity comparison uses CRYPTO_memcmp. Identity logging is
sanitized to ASCII printable range. TLS config strings are freed
during SIGHUP reconfigure to prevent leaks.

Assisted-by: Claude Opus 4.6
Signed-off-by: Sergio Correia <scorreia@redhat.com>
Add test-tls.sh covering PSK and certificate handshakes, PQC key
exchange negotiation, and binary linkage checks. Hardened with
set -euo pipefail and dynamic port allocation.

Document all TLS config options in both man pages, including PQC
posture differences between PSK and certificate modes, certificate
chain support, and SIGHUP reload limitations.

Assisted-by: Claude Opus 4.6
Signed-off-by: Sergio Correia <scorreia@redhat.com>
The blocking SSL_accept held the single-threaded libev event loop
for up to 5 seconds per connection, allowing a slow or malicious
client to stall audit event processing for all connected clients.

Replace it with a non-blocking state machine driven by ev_io and
ev_timer callbacks. Pre-handshake clients live in a separate chain
with a concurrency limit to prevent connection flooding. Per-address
counting walks both chains so a single IP cannot exhaust the global
handshake pool.

Also fixes a config pointer scope bug where tls_require_pqc
referenced an out-of-scope variable in the accept handler.

Assisted-by: Claude Opus 4.6
Signed-off-by: Sergio Correia <scorreia@redhat.com>
@stevegrubb
Copy link
Copy Markdown
Contributor

Hello, thanks for taking this on. I have a road map for the audit project and this is on that roadmap in addition to requirements this needed to meet. This is also identified as the highest priority missing piece in the whole project. So, this is timely and needed. It will take a me a couple days to get to this. I don't know of anything in flight that would affect this area of the code. So, it should be stable to let this sit during review.

What I'd like to do is mirror your PR branch and overlay that with the project requirements to see if there are any missing requirements and look for issues in the PR's implementation of the requirements. So...give me a couple days and I'll get back on this.

@sergio-correia
Copy link
Copy Markdown
Contributor Author

Sure thing, thanks for looking into this.

@stevegrubb
Copy link
Copy Markdown
Contributor

stevegrubb commented May 12, 2026

Hello, I think I am ready for the feedback. This is going to be a big dump of information. I am going to post our TLS specification to the other specifications. This is about a 7 phase project. This patch represents Phase 0. This is a summary of the technical requirements needed for Phase 0 (actual details to follow separately):

  1. Add tls_auth = psk.
  2. Add tls_crypto_profile = standard | fips | pqc.
  3. Make standard the default.
  4. Make pqc fail closed on classical-only negotiation.
  5. Make fips either work through OpenSSL/system policy or fail closed.
  6. Require tls_psk_identity in PSK mode.
  7. Add tls_allowed_clients and authorize identity before event ingestion.
  8. Emit NIAP-style audit-system records for crypto connection establishment failures, including stable failure reasons, and emit collector-side success records as an auditd-specific strengthening.
  9. Transactionally reload tls_allowed_clients.

In terms of the code in this patch, here is the implementation cleanups needed:

A. Configure Probes Are Too Weak

configure.ac:288-300 only checks for libssl, openssl/ssl.h, and OpenSSL >= 1.1.1. The implementation uses more specific APIs:

  • SSL_CTX_set_ciphersuites;
  • SSL_CTX_set1_groups_list;
  • TLS 1.3 PSK session callbacks;
  • SSL_get_negotiated_group;
  • SSL_group_to_name;
  • provider/FIPS APIs if fips is added.

Add configure-time feature probes for the APIs actually used. Where provider configuration can change runtime availability, keep runtime failure paths too. This matters because vendors backport APIs unevenly and because the code currently advertises OpenSSL 1.1.1 support while using APIs that may not exist on every supported target.

B. OpenSSL Return Values Are Ignored

Examples:

  • src/auditd-listen.c:1353-1356;
  • audisp/plugins/remote/audisp-remote.c:1178-1185.

The code calls setters for protocol version, early data, tickets, and session cache behavior without checking whether the calls succeeded. At least the profile-critical settings should fail closed with a clear error if OpenSSL rejects them.

C. PSK Secrets Can Survive Failed Context Initialization

Examples:

  • server path: src/auditd-listen.c:1387-1397 can load server_psk_key, then
    later jump to err without cleansing/freeing it;
  • client path: audisp/plugins/remote/audisp-remote.c:1218-1228 can load
    psk_key, then later jump to err without cleansing/freeing it.

Move cleanup into the error paths or build into temporary local state and only publish global pointers after the whole TLS context is valid.

D. TLS Helper Code Needs Its Own Internal Library

common/common.h:115-337 adds substantial TLS logic as static inline functions. This includes file validation, PSK decoding, cipher selection, polling write loops, and TLS shutdown.

This code is TLS-specific, not general common utility code. It is currently used by auditd and audisp-remote, while files under common/ make up libaucommon and are linked by many unrelated tools. Moving this code into libaucommon would spread the OpenSSL dependency farther than necessary.

The better shape is a small internal TLS library, for example:

autls/
  Makefile.am
  autls.h
  autls-psk.c
  autls-io.c
  autls-profile.c

with a non-installed libtool target:

noinst_LTLIBRARIES = libautls.la
libautls_la_SOURCES = autls.h autls-psk.c autls-io.c
libautls_la_LIBADD = $(OPENSSL_LIBS)

Then link libautls.la only into auditd, audisp-remote, and TLS tests. This keeps OpenSSL out of libaucommon consumers, gives the TLS code a clear ownership boundary, and avoids using header-only static inline functions as a substitute for a real object file.

Only role-neutral helpers belong in libautls.la, such as PSK file loading, PSK/key file validation, identity validation, TLS write/shutdown wrappers, TLS 1.3 cipher lookup, and PQC group classification. Sender-specific and collector-specific behavior should stay in the sender or collector code: OpenSSL PSK callbacks, TLS context setup tied to auditd or audisp-remote configuration structs, connection logging, listener state, reconnect policy, and record ingestion gates.

E. New Functions Need Kernel-Style Function Comments

The project instructions require a comment before any new function describing the function, input variables, and return codes. Many new TLS functions have only short topic comments or no function header comment, for example:

  • src/auditd-listen.c:970 tls_handshake_timeout_cb;
  • src/auditd-listen.c:980 tls_handshake_handler;
  • src/auditd-listen.c:1291 tls_psk_find_session_cb;
  • src/auditd-listen.c:1343 init_tls_server_context;
  • audisp/plugins/remote/audisp-remote.c:1129 tls_psk_use_session_cb;
  • audisp/plugins/remote/audisp-remote.c:1166 init_tls_context;
  • audisp/plugins/remote/audisp-remote.c:1324 tls_connect;
  • common/common.h:115 is_pqc_group;
  • common/common.h:133 tls_validate_key_file;
  • common/common.h:165 tls_load_psk.

Add concise function comments that explain purpose, inputs, and return values. OpenSSL callback signatures are allowed to exceed the "avoid more than four arguments" preference because the signature is externally defined.

F. Add glibc function attributes to aid problem detection

Where the shared libautls.la API is introduced, use the existing glibc/GCC attribute wrappers from gcc-attributes.h when the contract supports them. Good candidates include:

int autls_validate_key_file(const char *path, autls_log_fn log_fn)
	__nonnull((1, 2)) __wur;

int autls_load_psk(const char *path, unsigned char **key, size_t *key_len,
		   autls_log_fn log_fn)
	__nonnull((1, 2, 3, 4)) __wur;

int autls_ssl_write(SSL *ssl, const void *buf, int len, int timeout_ms)
	__nonnull((1, 2)) __attr_access ((__read_only__, 2, 3)) __wur;

int autls_identity_valid(const char *identity)
	__nonnull((1)) __attribute_pure__ __wur;

For allocation-style APIs, prefer a typed object and matching free routine so the compiler can check ownership:

struct autls_psk *autls_psk_load_file(const char *path, autls_log_fn log_fn)
	__nonnull((1, 2))
	__attribute_malloc__
	__attr_dealloc(autls_psk_free, 1)
	__wur;

void autls_psk_free(struct autls_psk *psk);

Use __nonnull only when NULL is truly invalid. For example, a function that deliberately treats NULL as "not a PQC group" should not be marked __nonnull. Keep the raw GCC format(printf, ...) attribute on logging callbacks; that matches the current project style better than inventing a new wrapper for printf-format checking.

G. Non-ASCII Characters In Comments

The branch introduces em dashes in comments:

  • common/common.h:117;
  • audisp/plugins/remote/audisp-remote.c:1183;
  • audisp/plugins/remote/test-tls-helpers.c:55, 66, 70, 104, 138,
    150, 153, 158, 167, 220.

Use ASCII comments. This matches the repository's C style expectations.

I'll try to find another way to provide the actual details of the technical requirements.

@stevegrubb
Copy link
Copy Markdown
Contributor

stevegrubb commented May 12, 2026

The full TLS requirements can be found here:
https://github.com/linux-audit/audit-documentation/wiki/SPEC-Remote-Logging-TLS-Requirements

The overall delivery plan broken down into phases:
https://github.com/linux-audit/audit-userspace/wiki/TLS-Overall-Plan

The details of phase 0, which represents a robust protoype, can be found here:
https://github.com/linux-audit/audit-userspace/wiki/TLS-Phase-0-Details

@sergio-correia sergio-correia changed the base branch from master to tls May 15, 2026 10:11
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