Skip to content

feat: S03.09 mutual TLS — client cert + key for SL4 non-repudiation#184

Merged
DavidCozens merged 10 commits into
mainfrom
feat/mtls-client-identity
Apr 22, 2026
Merged

feat: S03.09 mutual TLS — client cert + key for SL4 non-repudiation#184
DavidCozens merged 10 commits into
mainfrom
feat/mtls-client-identity

Conversation

@DavidCozens

@DavidCozens DavidCozens commented Apr 22, 2026

Copy link
Copy Markdown
Owner

Purpose

Closes #173.

Ships mutual TLS on SolidSyslogTlsStream: a client cert chain + matching private key, satisfying IEC 62443 SL4 CR 2.12 (non-repudiation). The SIEM can now cryptographically identify which device sent each audit record.

Change Description

API. Two opt-in fields on SolidSyslogTlsStreamConfig:

const char* clientCertChainPath;  /* PEM: leaf cert (+ intermediates); NULL = no mTLS */
const char* clientKeyPath;        /* PEM: matching private key;       NULL = no mTLS */

Semantics:

  • Both NULL → non-mTLS (today's behaviour unchanged).
  • Both set → mTLS: SSL_CTX_use_certificate_chain_file + SSL_CTX_use_PrivateKey_file(PEM) + SSL_CTX_check_private_key, each return-value-checked, ctx freed on any failure.
  • Exactly one set → Open fails — no silent downgrade.

Rotation. Not a callback — the SSL_CTX is rebuilt on every Open, so on-disk cert rotation is picked up automatically on the next reconnect. S03.10 can revisit if a callback turns out to be needed.

Integration harness. TlsTestCert_WritePrivateKeyPemToFile lets scenarios stage a client key on disk. TlsTestServerConfig.clientCaCert turns the in-process server into a mutual-auth server (SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, TLS 1.2 pinned so server-side rejection is visible within SSL_connect rather than deferred to the next read as it is in TLS 1.3). Test certs now emit basicConstraints=CA:TRUE so OpenSSL 3's chain validation builds through CA-signed client certs.

BDD. syslog-ng gets a second TLS source on port 6515 with peer-verify(required-trusted), leaving the existing 6514 source on optional-untrusted. Both paths stay under test. New @mtls @buffered scenario; ExampleMtlsConfig wires the threaded example's --transport mtls mode. The switch config maps mtls onto the existing TLS slot (the TlsStream is a singleton, so mTLS is a flavour of TLS — a fourth slot waits on the multi-instance allocation-strategy epic).

Docs. docs/iec62443.md promotes mTLS from remaining to available in the SL4 TLS section.

Test Evidence

Strict TDD throughout. Each red confirmed before each green, each green committed.

  • Tests/SolidSyslogTlsStreamTest.cpp gains 17 mTLS cases covering: skip path, happy path (cert/key/check invocations + pointer chains), partial-config rejection (both directions), failure injection per libssl call, and ctx-free on every failure path.
  • Tests/OpenSslIntegration/SolidSyslogTlsStreamIntegrationTest.cpp gains 4 scenarios against real libssl: handshake succeeds with trusted CA-signed client cert; server rejects when client sends nothing; Open fails locally on cert/key mismatch; server rejects when client cert is signed by an untrusted CA.
  • Bdd/features/mtls_transport.feature proves end-to-end delivery over mTLS.
  • All presets green locally: debug, clang-debug, sanitize, coverage (100% line), tidy, cppcheck, format. Full BDD suite passes (34/34).

Areas Affected

  • Platform/OpenSsl/ (Tier 2): SolidSyslogTlsStream + its public config header.
  • Tests/Support/OpenSslFake.{h,c}: new captures / counters / failure injectors.
  • Tests/OpenSslIntegration/: TlsTestCert (key PEM writer, CA:TRUE), TlsTestServer (clientCaCert), new mTLS scenarios.
  • Example/ (Tier 3): new ExampleMtlsConfig; --transport mtls accepted; switch config maps it onto the TLS slot.
  • Bdd/: new mTLS source in syslog-ng.conf / syslog-ng-full.conf, regenerated BDD test certs incl. new client.pem/client.key, new mtls entry in PER_TRANSPORT_LOG, new feature file.
  • docs/iec62443.md, DEVLOG.md.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added mutual TLS (mTLS) transport support as a command-line option alongside existing UDP, TCP, and TLS transports.
    • Extended syslog listener to accept mTLS connections on port 6515 with client certificate verification.
    • Implemented client certificate and key authentication for mTLS endpoints.
  • Tests

    • Added end-to-end BDD coverage for mTLS message delivery scenarios.
    • Expanded integration tests to cover mTLS handshakes with client certificate validation and rejection cases.

Pins current behaviour: when clientCertChainPath and clientKeyPath are
both NULL, Open must not call SSL_CTX_use_certificate_chain_file,
SSL_CTX_use_PrivateKey_file, or SSL_CTX_check_private_key. Adds the
three call counters + failure injectors to OpenSslFake ready to drive
the mTLS happy path in subsequent cycles.
… set

When clientCertChainPath is configured, Open now calls
SSL_CTX_use_certificate_chain_file, SSL_CTX_use_PrivateKey_file and
SSL_CTX_check_private_key in order, wiring up client-side TLS
identity for mTLS. All three return values are checked — any failure
aborts Open and the ctx is freed by the existing error path.
When exactly one of clientCertChainPath and clientKeyPath is set, Open
now fails without making any libssl client-identity calls, and the ctx
is freed via the existing error path. Prevents a silent downgrade into
server-auth TLS when the caller intended mTLS but supplied incomplete
material.
Covers SSL_CTX_use_certificate_chain_file, SSL_CTX_use_PrivateKey_file
and SSL_CTX_check_private_key: each return-checked, each failure
propagates through Open, each call receives the ctx from SSL_CTX_new.
Inherits the existing CreateSslContext rollback — no production change
needed, the assertions just formalise it.
Adds TlsTestCert_WritePrivateKeyPemToFile so integration scenarios can
stage a client key on disk for SolidSyslogTlsStream to load. Adds
TlsTestServerConfig.clientCaCert — when set, the in-process server
trusts the given CA for client certs and insists on peer verification
via SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT.
Four scenarios cover the SL4 CR 2.12 failure surface for client
authentication:
- handshake succeeds when the client cert is signed by the CA the
  server trusts;
- server rejects the handshake when the client presents no cert;
- Open fails locally via SSL_CTX_check_private_key when the key does
  not match the cert;
- server rejects the handshake when the client cert is signed by a CA
  the server does not trust.

TlsTestCert now emits basicConstraints=CA:TRUE so OpenSSL 3 will build
a chain through CA-signed client certs. TlsTestServer pins TLS 1.2
in mTLS mode — in TLS 1.3 the client's SSL_connect can return before
the server verifies the client cert, which would hide a server-side
rejection until the next read.
- syslog-ng gains a second TLS source on port 6515 with
  peer-verify(required-trusted), leaving the existing 6514 source on
  optional-untrusted so both paths stay under test.
- generate.sh now emits client.pem + client.key signed by the BDD test
  CA; all test certs regenerated with matching CA.
- ExampleMtlsConfig provides host, port, CA bundle, server name and
  client cert/key paths; the threaded example accepts --transport mtls
  and wires those paths into SolidSyslogTlsStreamConfig when selected.
- New @Mtls @Buffered feature asserts a message is delivered end-to-end
  over mTLS. The SwitchingSender config maps 'mtls' to the TLS slot
  since the TLS stream is a singleton — mTLS is a flavour of TLS rather
  than a new transport slot.
- Fold tls/mtls branches into one in ExampleSwitchConfig (branch-clone).
- Replace strcpy-based temp file template with a templated helper that
  uses memcpy and a static_assert for the destination size.
- Re-run clang-format on files touched this story.
@coderabbitai

coderabbitai Bot commented Apr 22, 2026

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@DavidCozens has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 7 minutes and 19 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 7 minutes and 19 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 809c6a2d-1aae-4b57-aa54-07b67136ae0e

📥 Commits

Reviewing files that changed from the base of the PR and between 5d554a2 and b947af0.

📒 Files selected for processing (5)
  • Example/Threaded/main.c
  • Tests/SolidSyslogTlsStreamTest.cpp
  • Tests/Support/OpenSslFake.c
  • Tests/Support/OpenSslFake.h
  • docs/iec62443.md
📝 Walkthrough

Walkthrough

This PR implements mutual TLS (mTLS) support for SolidSyslogTlsStream. It introduces optional client certificate chain and private key configuration fields, adds OpenSSL identity validation logic, extends the example application and BDD test harness, and provides comprehensive unit, integration, and end-to-end test coverage.

Changes

Cohort / File(s) Summary
TLS Stream Core & Configuration
Platform/OpenSsl/Interface/SolidSyslogTlsStream.h, Platform/OpenSsl/Source/SolidSyslogTlsStream.c
Extended SolidSyslogTlsStreamConfig struct with optional clientCertChainPath and clientKeyPath fields; implemented TlsStream_ConfigureClientIdentity to enforce all-or-nothing mTLS configuration, load certificate chain and private key files, and validate key pairing via SSL_CTX_check_private_key.
OpenSSL Test Fakes
Tests/Support/OpenSslFake.h, Tests/Support/OpenSslFake.c
Added link-interposed fake implementations and capture/control functions for SSL_CTX_use_certificate_chain_file, SSL_CTX_use_PrivateKey_file, and SSL_CTX_check_private_key with call counters, argument capture, and failure injection toggles.
TLS Integration Test Infrastructure
Tests/OpenSslIntegration/TlsTestCert.h, Tests/OpenSslIntegration/TlsTestCert.c, Tests/OpenSslIntegration/TlsTestServer.h, Tests/OpenSslIntegration/TlsTestServer.c
Added TlsTestCert_WritePrivateKeyPemToFile function to export private keys; enhanced test certificates with basicConstraints=CA:TRUE for OpenSSL 3 compatibility; extended TlsTestServerConfig with optional clientCaCert field and corresponding server-side mTLS verification/TLS 1.2 pinning logic.
TLS Integration Tests
Tests/OpenSslIntegration/SolidSyslogTlsStreamIntegrationTest.cpp
Added client CA generation, client identity staging, and four new mTLS scenarios: successful handshake with trusted client cert, rejection when client cert absent, local key-mismatch detection, and rejection when client cert signed by untrusted CA; introduced makeTempFile helper for secure test-file creation.
TLS Unit Tests
Tests/SolidSyslogTlsStreamTest.cpp
Added comprehensive unit test coverage for mTLS client identity handling: verified correct OpenSSL API sequencing, path arguments, partial config rejection, and context threading; tested failure modes for each certificate/key operation.
Example mTLS Configuration
Example/Common/ExampleMtlsConfig.h, Example/Common/ExampleMtlsConfig.c
Added new mTLS endpoint configuration module exporting functions to retrieve host, port, CA bundle path, server name, client certificate chain path, and client key path; implements ExampleMtlsConfig_GetEndpoint to populate endpoint struct.
Example Application Wiring
Example/CMakeLists.txt, Example/Common/ExampleCommandLine.c, Example/Common/ExampleSwitchConfig.c, Example/Threaded/main.c
Extended command-line transport validation to accept "mtls" token; updated ExampleSwitchConfig_SetByName to route "mtls" to TLS path; modified main.c to detect mtlsModeActive and conditionally wire TLS stream config and endpoint selection to ExampleMtlsConfig functions when mTLS mode is active.
syslog-ng mTLS Configuration
Bdd/syslog-ng/syslog-ng.conf, Bdd/syslog-ng/syslog-ng-full.conf
Added new mTLS source s_mtls on port 6515 with peer-verify(required-trusted); added corresponding file destination d_file_mtls writing to /var/log/syslog-ng/received_mtls.log; extended logging pipelines to route mTLS messages separately and include mTLS in combined-source logging.
syslog-ng TLS Artifacts
Bdd/syslog-ng/tls/ca.key, Bdd/syslog-ng/tls/server.key, Bdd/syslog-ng/tls/client.key, Bdd/syslog-ng/tls/generate.sh
Updated CA and server private keys; added new client private key (client.key) and client certificate (client.pem); extended generate.sh to produce client CSR and CA-signed client certificate with matching permissions (0644 for cert, 0600 for key).
BDD Test Infrastructure
Bdd/features/environment.py, Bdd/features/steps/syslog_steps.py
Added RECEIVED_MTLS_LOG constant pointing to mTLS message sink; extended PER_TRANSPORT_LOG mapping to include "mtls" transport key enabling per-transport message counting for mTLS scenarios.
BDD Feature Coverage
Bdd/features/mtls_transport.feature, .devcontainer/docker-compose.yml
Added new mTLS end-to-end scenario verifying syslog-ng receives exactly 1 message via mTLS with priority 134; extended docker-compose port mappings to expose port 6515 for mTLS listener.
Documentation
DEVLOG.md, docs/iec62443.md
Documented mutual TLS design decisions (opt-in config, all-or-nothing semantics, context rebuild per Open for rotation), TLS 1.2 pinning in tests, and OpenSSL 3 basicConstraints; updated IEC 62443 SL4 control to list mTLS as available with non-repudiation coverage (CR 2.12).

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client App
    participant Stream as SolidSyslogTlsStream
    participant OpenSSL as OpenSSL Library
    participant Server as syslog-ng Server
    
    Client->>Stream: Open(tlsConfig with clientCertChainPath, clientKeyPath)
    Stream->>OpenSSL: SSL_CTX_new()
    OpenSSL-->>Stream: ctx
    Stream->>OpenSSL: SSL_CTX_use_certificate_chain_file(clientCertChainPath)
    OpenSSL-->>Stream: success/failure
    alt Certificate load failed
        Stream->>OpenSSL: SSL_CTX_free(ctx)
        Stream-->>Client: false (Open failed)
    end
    Stream->>OpenSSL: SSL_CTX_use_PrivateKey_file(clientKeyPath, PEM)
    OpenSSL-->>Stream: success/failure
    alt Key load failed
        Stream->>OpenSSL: SSL_CTX_free(ctx)
        Stream-->>Client: false (Open failed)
    end
    Stream->>OpenSSL: SSL_CTX_check_private_key(ctx)
    OpenSSL-->>Stream: match/mismatch
    alt Key mismatch
        Stream->>OpenSSL: SSL_CTX_free(ctx)
        Stream-->>Client: false (Open failed)
    end
    Stream->>OpenSSL: SSL_connect()
    OpenSSL->>Server: TLS Handshake (presents client cert)
    Server->>Server: Verify client cert against CA
    alt Client cert invalid/untrusted
        Server-->>OpenSSL: Handshake failure
        OpenSSL-->>Stream: error
        Stream-->>Client: false (Open failed)
    end
    alt Client cert valid
        Server-->>OpenSSL: Handshake success
        OpenSSL-->>Stream: connected
        Stream-->>Client: true (Open succeeded)
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Possibly related PRs

Poem

🐰 A client hops with certificates in hand,
"Trust me, dear server, as sure as the land!"
Keys match their certs—no mismatch today—
mTLS handshake brings secure passage the way! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'feat: S03.09 mutual TLS — client cert + key for SL4 non-repudiation' is clear, concise, and accurately summarizes the primary change (adding optional mutual TLS support).
Description check ✅ Passed The PR description follows the template structure with clear Purpose (closes #173), Change Description (API details, rotation strategy, integration harness, BDD, docs), and Test Evidence sections demonstrating comprehensive TDD coverage.
Linked Issues check ✅ Passed All coding requirements from issue #173 are met: mTLS API with opt-in fields, OpenSSL API calls with error handling, partial-config rejection, unit tests with fake captures/counters/injectors (17 cases), integration scenarios (4 libssl tests), BDD scenario, example wiring, and docs updates.
Out of Scope Changes check ✅ Passed All changes are scoped to #173 objectives: mTLS implementation, test harness extension, example integration, BDD additions, and documentation updates. No unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/mtls-client-identity

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

Copy link
Copy Markdown
Contributor

☀️   Quality Summary

   🚦   Unit Tests (GCC): 100% successful (✔️ 771 passed, 🙈 3 skipped)
   🚦   Unit Tests (Clang): 100% successful (✔️ 723 passed, 🙈 3 skipped)
   🚦   Unit Tests (Sanitize): 100% successful (✔️ 723 passed, 🙈 3 skipped)
   🚦   Integration Tests (OpenSSL): 100% successful (✔️ 9 passed)
   🚦   BDD Tests (Linux): 100% successful (✔️ 34 passed)
   🚦   BDD Tests (Windows): 62% successful (✔️ 21 passed, 🙈 13 skipped)
   🚦   Unit Tests (MSVC): 100% successful (✔️ 599 passed, 🙈 1 skipped)
   ⚠️   Clang-Tidy: No warnings
   ⚠️   CPPCheck: No warnings


Created by Quality Monitor v1.14.0 (#f3859fd). More details are shown in the GitHub Checks Result.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Bdd/features/steps/syslog_steps.py (1)

22-27: ⚠️ Potential issue | 🟠 Major

Use the mTLS oracle log before waiting for the one-shot transport send.

PER_TRANSPORT_LOG["mtls"] lets the Then ... over mtls step count received_mtls.log, but run_threaded_example() still waits and parses via context.received_log during the When step. For the new mTLS scenario, point context.received_log and context.lines_before at the per-transport log before launching the threaded example.

🐛 Proposed fix
 `@when`("the threaded example sends a syslog message with transport {transport}")
 def step_threaded_sends_with_transport(context, transport):
+    if context.oracle_format == "syslog-ng" and transport in PER_TRANSPORT_LOG:
+        context.received_log = PER_TRANSPORT_LOG[transport]
+        context.lines_before = context.lines_before_per_transport.get(
+            transport,
+            line_count(context.received_log),
+        )
     run_threaded_example(context, ["--transport", transport])

Also applies to: 577-579

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Bdd/features/steps/syslog_steps.py` around lines 22 - 27, The test uses
PER_TRANSPORT_LOG["mtls"] for counting but still points context.received_log and
context.lines_before at the generic log when launching the one-shot threaded
sender; update the When-step before calling run_threaded_example() so that for
the "mtls" transport you set context.received_log = PER_TRANSPORT_LOG["mtls"]
and context.lines_before = len(context.received_log) (same change where other
transports set these), ensuring run_threaded_example() and subsequent checks use
RECEIVED_MTLS_LOG; mirror this fix wherever the one-shot mtls path is handled
(also at the other occurrence around lines 577-579).
🧹 Nitpick comments (2)
Bdd/syslog-ng/tls/generate.sh (1)

41-43: Optional: consider adding extendedKeyUsage=clientAuth to the client cert.

Signing client.pem via openssl x509 -req without an -extfile emits a cert with no v3 extensions (including no EKU). syslog-ng's peer-verify(required-trusted) happens to accept this today, but stricter verifiers (or a future OpenSSL default tightening) may reject a client cert that lacks extendedKeyUsage=clientAuth. Adding an explicit clientAuth EKU (mirroring how server.pem carries its SAN extfile) would make the test fixture more robust and closer to real-world client certs.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Bdd/syslog-ng/tls/generate.sh` around lines 41 - 43, The client certificate
signing step (openssl x509 -req producing client.pem from client.csr) currently
omits v3 extensions; add an extfile that sets extendedKeyUsage = clientAuth
(similar to the server cert SAN extfile) and pass it to the signing command (use
-extfile and -extensions) so client.pem explicitly contains the clientAuth EKU,
ensuring stricter verifiers accept the cert.
Tests/Support/OpenSslFake.c (1)

147-151: Capture the private-key file type argument for unit test assertion.

SSL_CTX_use_PrivateKey_file discards the type parameter, preventing unit tests from asserting that SSL_FILETYPE_PEM is passed. Add a static variable and accessor to capture this argument, then assert it in the mTLS unit test. Production correctly passes the right value, but the test harness should pin this contract at the unit level.

Implementation sketch

Add static variable in OpenSslFake.c:

static int lastUsePrivateKeyFileTypeArg;

Update the fake in OpenSslFake.c:

int SSL_CTX_use_PrivateKey_file(SSL_CTX* ctx, const char* file, int type)
{
    usePrivateKeyFileCallCount++;
    lastUsePrivateKeyFileCtxArg = ctx;
    lastClientKeyPath           = file;
    lastUsePrivateKeyFileTypeArg = type;
    return usePrivateKeyFileFails ? 0 : 1;
}

Expose accessor in OpenSslFake.h:

int OpenSslFake_LastUsePrivateKeyFileTypeArg(void);

Assert in mTLS unit test:

STRCMP_EQUAL(SSL_FILETYPE_PEM, OpenSslFake_LastUsePrivateKeyFileTypeArg());
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Tests/Support/OpenSslFake.c` around lines 147 - 151, Add a static int to
capture the `type` arg (e.g., lastUsePrivateKeyFileTypeArg) in OpenSslFake.c,
set it inside the fake implementation of SSL_CTX_use_PrivateKey_file alongside
usePrivateKeyFileCallCount/lastClientKeyPath/lastUsePrivateKeyFileCtxArg, expose
a getter function in OpenSslFake.h (e.g.,
OpenSslFake_LastUsePrivateKeyFileTypeArg), and update the mTLS unit test to
assert STRCMP_EQUAL(SSL_FILETYPE_PEM,
OpenSslFake_LastUsePrivateKeyFileTypeArg()) so the test can verify the PEM
filetype was passed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/iec62443.md`:
- Around line 187-190: Update the epic reference to use the project zero-padding
convention: replace the bare "E3" with "E03" in the markdown snippet and any
other occurrences (e.g., the lines containing "E3" around the E3 work note and
at the other noted location). Verify story references like "S03.10" and "S03.11"
remain zero-padded and unchanged, and commit the corrected epic token "E03" in
docs/iec62443.md so all epic IDs follow E00-E99 formatting.

In `@Example/Threaded/main.c`:
- Around line 109-110: The comment is wrong: setting tlsModeActive = false does
not fall back to TCP because the "tls"/"mtls" transport still selects
EXAMPLE_SWITCH_TLS, which is wired to udpSender; update the code and/or comment
so that when tlsModeActive is false the transport selection for "tls"/"mtls" is
remapped to the TCP branch (or change EXAMPLE_SWITCH_TLS wiring) so it does not
use udpSender; specifically, modify the transport selection logic that checks
tlsModeActive and EXAMPLE_SWITCH_TLS to route to the TCP sender path instead of
udpSender, and update the inline comment to accurately describe the fallback
behavior.

In `@Tests/SolidSyslogTlsStreamTest.cpp`:
- Around line 647-655: The test
SolidSyslogTlsStream/OpenLoadsClientKeyFromConfig currently only asserts the
client key path; modify the OpenSSL fake to record the file type passed into
SSL_CTX_use_PrivateKey_file (add an accessor like
OpenSslFake_LastClientKeyType()) and then update the test to also ASSERT that
OpenSslFake_LastClientKeyType() equals SSL_FILETYPE_PEM after calling
SolidSyslogStream_Open(stream, addr); keep the existing path assertion using
OpenSslFake_LastClientKeyPath() and ensure the fake stores the type parameter
when SSL_CTX_use_PrivateKey_file is invoked.

---

Outside diff comments:
In `@Bdd/features/steps/syslog_steps.py`:
- Around line 22-27: The test uses PER_TRANSPORT_LOG["mtls"] for counting but
still points context.received_log and context.lines_before at the generic log
when launching the one-shot threaded sender; update the When-step before calling
run_threaded_example() so that for the "mtls" transport you set
context.received_log = PER_TRANSPORT_LOG["mtls"] and context.lines_before =
len(context.received_log) (same change where other transports set these),
ensuring run_threaded_example() and subsequent checks use RECEIVED_MTLS_LOG;
mirror this fix wherever the one-shot mtls path is handled (also at the other
occurrence around lines 577-579).

---

Nitpick comments:
In `@Bdd/syslog-ng/tls/generate.sh`:
- Around line 41-43: The client certificate signing step (openssl x509 -req
producing client.pem from client.csr) currently omits v3 extensions; add an
extfile that sets extendedKeyUsage = clientAuth (similar to the server cert SAN
extfile) and pass it to the signing command (use -extfile and -extensions) so
client.pem explicitly contains the clientAuth EKU, ensuring stricter verifiers
accept the cert.

In `@Tests/Support/OpenSslFake.c`:
- Around line 147-151: Add a static int to capture the `type` arg (e.g.,
lastUsePrivateKeyFileTypeArg) in OpenSslFake.c, set it inside the fake
implementation of SSL_CTX_use_PrivateKey_file alongside
usePrivateKeyFileCallCount/lastClientKeyPath/lastUsePrivateKeyFileCtxArg, expose
a getter function in OpenSslFake.h (e.g.,
OpenSslFake_LastUsePrivateKeyFileTypeArg), and update the mTLS unit test to
assert STRCMP_EQUAL(SSL_FILETYPE_PEM,
OpenSslFake_LastUsePrivateKeyFileTypeArg()) so the test can verify the PEM
filetype was passed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e657f3ef-ce97-4950-9ea5-7c6890dbf7fc

📥 Commits

Reviewing files that changed from the base of the PR and between 1d0c16e and 5d554a2.

⛔ Files ignored due to path filters (3)
  • Bdd/syslog-ng/tls/ca.pem is excluded by !**/*.pem
  • Bdd/syslog-ng/tls/client.pem is excluded by !**/*.pem
  • Bdd/syslog-ng/tls/server.pem is excluded by !**/*.pem
📒 Files selected for processing (28)
  • .devcontainer/docker-compose.yml
  • Bdd/features/environment.py
  • Bdd/features/mtls_transport.feature
  • Bdd/features/steps/syslog_steps.py
  • Bdd/syslog-ng/syslog-ng-full.conf
  • Bdd/syslog-ng/syslog-ng.conf
  • Bdd/syslog-ng/tls/ca.key
  • Bdd/syslog-ng/tls/client.key
  • Bdd/syslog-ng/tls/generate.sh
  • Bdd/syslog-ng/tls/server.key
  • DEVLOG.md
  • Example/CMakeLists.txt
  • Example/Common/ExampleCommandLine.c
  • Example/Common/ExampleMtlsConfig.c
  • Example/Common/ExampleMtlsConfig.h
  • Example/Common/ExampleSwitchConfig.c
  • Example/Threaded/main.c
  • Platform/OpenSsl/Interface/SolidSyslogTlsStream.h
  • Platform/OpenSsl/Source/SolidSyslogTlsStream.c
  • Tests/OpenSslIntegration/SolidSyslogTlsStreamIntegrationTest.cpp
  • Tests/OpenSslIntegration/TlsTestCert.c
  • Tests/OpenSslIntegration/TlsTestCert.h
  • Tests/OpenSslIntegration/TlsTestServer.c
  • Tests/OpenSslIntegration/TlsTestServer.h
  • Tests/SolidSyslogTlsStreamTest.cpp
  • Tests/Support/OpenSslFake.c
  • Tests/Support/OpenSslFake.h
  • docs/iec62443.md

Comment thread docs/iec62443.md Outdated
Comment thread Example/Threaded/main.c Outdated
Comment thread Tests/SolidSyslogTlsStreamTest.cpp
…ILETYPE_PEM assertion

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

☀️   Quality Summary

   🚦   Unit Tests (GCC): 100% successful (✔️ 771 passed, 🙈 3 skipped)
   🚦   Unit Tests (Clang): 100% successful (✔️ 723 passed, 🙈 3 skipped)
   🚦   Unit Tests (Sanitize): 100% successful (✔️ 723 passed, 🙈 3 skipped)
   🚦   Integration Tests (OpenSSL): 100% successful (✔️ 9 passed)
   🚦   BDD Tests (Linux): 100% successful (✔️ 34 passed)
   🚦   BDD Tests (Windows): 62% successful (✔️ 21 passed, 🙈 13 skipped)
   🚦   Unit Tests (MSVC): 100% successful (✔️ 599 passed, 🙈 1 skipped)
   ⚠️   Clang-Tidy: No warnings
   ⚠️   CPPCheck: No warnings


Created by Quality Monitor v1.14.0 (#f3859fd). More details are shown in the GitHub Checks Result.

@DavidCozens

Copy link
Copy Markdown
Owner Author

CodeRabbit review — remaining items

Follow-up on the two items that live in the review body (not inline), for traceability:

Outside-diff major — Bdd/features/steps/syslog_steps.py L22-27 / L577-579 (mTLS oracle log)

Not actioned — false positive. The concern was that the When ... transport mtls step calls run_threaded_example() which waits on context.received_log (the generic received.log), while mTLS writes to received_mtls.log. In practice Bdd/syslog-ng/syslog-ng-full.conf has a catch-all log { source(s_udp); source(s_tcp); source(s_tls); source(s_mtls); destination(d_file); } block that fans every source into received.log in addition to the per-transport destinations, so the existing wait_for_messages call already sees the mTLS message. The bdd CI check passing on b947af0 confirms it.

Nitpick — Bdd/syslog-ng/tls/generate.sh clientAuth EKU

Not actioned — test-fixture hardening only. syslog-ng's peer-verify(required-trusted) accepts the current client.pem without an explicit extendedKeyUsage=clientAuth, and the end-to-end mTLS scenario passes. A stricter verifier (or a future OpenSSL default change) could reject it, but that's a fixture-robustness concern rather than a functional issue with this PR, so leaving it for a later pass if/when it bites.

Nitpick — Tests/Support/OpenSslFake.c L147-151 (capture private-key file type)

Addressed in b947af0 — see inline reply on the related Tests/SolidSyslogTlsStreamTest.cpp comment.

@DavidCozens DavidCozens merged commit f6ae430 into main Apr 22, 2026
14 checks passed
@DavidCozens DavidCozens deleted the feat/mtls-client-identity branch April 22, 2026 10:15
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.

S03.09: Mutual TLS — client cert + key (SL4: CR 2.12 non-repudiation)

1 participant