diff --git a/CLAUDE.md b/CLAUDE.md index 581fa3e6..bcc19b92 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -531,9 +531,15 @@ Three exceptions stand, each authorised rather than assumed: **Anything else non-ASCII: ask.** Do not quietly pick a typographic character, and do not quietly rewrite a sentence to avoid one — either way the decision goes unrecorded. -This is a rule about source. Documentation under `docs/` keeps typographic characters, -on the reasoning that prose of that length is written with an authoring tool; `README.md` -is hyphenated by hand and is the deliberate exception. +Documentation under `docs/` follows the same rule: write `-`, not an em or en dash. +One exception is worth knowing because the mechanical fix is wrong. A dash that a +hard wrap has left at the **start** of a line stays an em dash - a hyphen there is a +Markdown list marker, and converting it silently turns a sentence into a bullet. +Rewrap the paragraph or leave the character. + +Pages are being converted as they are next edited rather than in a sweep, so most of +`docs/` still carries em dashes. Convert the whole of any page you are already +rewriting, so it is at least internally consistent. ### MISRA-load-bearing `.clang-format` settings diff --git a/docs/platforms/mbedtls/index.md b/docs/platforms/mbedtls/index.md index cc066343..2a6f5d85 100644 --- a/docs/platforms/mbedtls/index.md +++ b/docs/platforms/mbedtls/index.md @@ -26,18 +26,23 @@ Credentials are passed as caller-built, caller-owned handles: a seeded `mbedtls_ctr_drbg_context` for the handshake, an `mbedtls_x509_crt` trust chain, and for mutual TLS an `mbedtls_x509_crt` and `mbedtls_pk_context` pair. No part of the adapter opens a file, which is what allows it to run on targets built -without `MBEDTLS_FS_IO`. Each handle must remain valid for the lifetime of the -stream. +without `MBEDTLS_FS_IO`. -Rotation follows from that, and needs sequencing. The adapter re-reads every -handle each time it connects, so replacing the material behind a handle is enough -— the stream does not need rebuilding. But while a connection is open, the -adapter's `ssl_config` holds pointers into that material, and freeing it there is -a use-after-free. +Two lifetimes are in play and they are not the same. The **handle objects** must +stay addressable for as long as the stream might open a connection, because the +adapter reads the pointers it was given on every connect. The **parsed material +inside them** only has to be intact while a connection is open, which is when the +adapter's `ssl_config` holds pointers into it. -So: call `SolidSyslogSender_Disconnect` first, which releases the `ssl_config`, -then free and re-parse into the same handle. The next send reconnects with the -new material. There is no reload callback and none is needed. +Rotation follows from the second lifetime. Call `SolidSyslogSender_Disconnect`, +which releases the `ssl_config` and with it every pointer into the material, then +free and re-parse into the same handle. The next send reconnects with the new +material. Freeing before the disconnect completes is a use-after-free, because +the open connection is still reading it. + +The adapter does not say when it has finished with the material, so an integrator +who wants the private key out of RAM between connections has to drive that +sequence themselves rather than being told. That is the gap recorded below. ## Coexistence is an auditable contract @@ -51,14 +56,36 @@ claim can be checked against the directory. ## Where it differs from the contract -Five differences at 0.1.0, each tracked. Read them before relying on the -corresponding obligation. +Seven differences, each tracked. Read them before relying on the corresponding +obligation. + +### A peer cannot be authorised by certificate fingerprint + +Only certification path validation is offered, so a deployment with no PKI has no +way to pin the collector's certificate. Tracked as +[#753](https://github.com/cososo-ltd/solid-syslog/issues/753). + +### A refused connection does not say which check refused it + +An expired certificate, an untrusted chain and a name mismatch all surface as the +same handshake failure, so the report does not distinguish a certificate problem +from a network one. Tracked as +[#731](https://github.com/cososo-ltd/solid-syslog/issues/731). + +### Credential material must stay parsed for the life of the stream + +The adapter binds the handles into its `ssl_config` on each connection and drops +them on close, but it never says so, so every handle has to remain valid and +parsed for as long as the stream exists. A device that connects rarely still +holds its private key in RAM continuously, and there is no point at which the +adapter invites the integrator to release it. Tracked under +[E39](https://github.com/cososo-ltd/solid-syslog/issues/782). ### A half-supplied client credential is accepted in silence A client certificate is presented only when both `ClientCertChain` and `ClientKey` are supplied. Where either is absent the other is ignored, the -connection proceeds with server-authenticated TLS, and nothing is reported — so a +connection proceeds with server-authenticated TLS, and nothing is reported, so a device configured for mutual TLS can run without presenting its certificate, and without anyone on the device knowing. The contract requires this to be reported. Until it is, check for a half-supplied pair before you open the stream. Tracked as @@ -72,14 +99,6 @@ handshake rejection from the collector rather than as a setup error on the device, which sends you looking in the wrong place. Tracked as [#719](https://github.com/cososo-ltd/solid-syslog/issues/719). -### An expired certificate stops delivery - -A peer certificate that is expired or not yet valid fails the handshake, even -where it still chains to a trusted anchor. The contract asks for it to be -reported with delivery continuing, because clock skew is the dominant cause and a -device with a wrong clock is one whose logs you still want. Tracked as -[#731](https://github.com/cososo-ltd/solid-syslog/issues/731). - ### The cipher policy cannot be expressed The configuration carries no cipher or ciphersuite field, so the ciphersuites diff --git a/docs/platforms/openssl/index.md b/docs/platforms/openssl/index.md index 8d7065ed..9df036d9 100644 --- a/docs/platforms/openssl/index.md +++ b/docs/platforms/openssl/index.md @@ -28,16 +28,36 @@ needs them present and readable by the process at the moment a connection is made, not at startup. The `SSL_CTX` is rebuilt on every open, re-reading each file named in the -configuration. Rotation is therefore a file replacement and a reconnection: -replace the file, and the new material is in force on the next connection — -either through ordinary reconnection after an outage, or immediately by calling -`SolidSyslogSender_Disconnect`. Nothing needs to be reloaded and nothing needs to -be restarted. +configuration, and freed on close. Nothing parsed from those files is held +between connections. Rotation is therefore a file replacement and a +reconnection: replace the file, and the new material is in force on the next +connection, either through ordinary reconnection after an outage or immediately +by calling `SolidSyslogSender_Disconnect`. ## Where it differs from the contract -Four differences at 0.1.0, each tracked. Read them before relying on the -corresponding obligation. +Six differences, each tracked. Read them before relying on the corresponding +obligation. + +### A peer cannot be authorised by certificate fingerprint + +Only certification path validation is offered, so a deployment with no PKI has no +way to pin the collector's certificate. Tracked as +[#753](https://github.com/cososo-ltd/solid-syslog/issues/753). + +### A refused connection does not say which check refused it + +An expired certificate, an untrusted chain and a name mismatch all surface as the +same handshake failure, so the report does not distinguish a certificate problem +from a network one. Tracked as +[#731](https://github.com/cososo-ltd/solid-syslog/issues/731). + +### Credentials come from the filesystem, and only from there + +The adapter opens the PEM files itself, so material held in a TPM, a keyring or +an encrypted store has to be written to a readable file before this adapter can +use it. Tracked under +[E39](https://github.com/cososo-ltd/solid-syslog/issues/782). ### A half-supplied client credential stops delivery @@ -50,14 +70,6 @@ This adapter is stricter than the contract rather than weaker, and the stricter behaviour is safe. Tracked as [#734](https://github.com/cososo-ltd/solid-syslog/issues/734). -### An expired certificate stops delivery - -A peer certificate that is expired or not yet valid fails the handshake, even -where it still chains to a trusted anchor. The contract asks for it to be -reported with delivery continuing, because clock skew is the dominant cause and a -device with a wrong clock is one whose logs you still want. Tracked as -[#731](https://github.com/cososo-ltd/solid-syslog/issues/731). - ### The cipher policy does not bind a TLS 1.3 connection The cipher list is passed to OpenSSL unchanged and pins nothing of the library's diff --git a/docs/rfc-compliance.md b/docs/rfc-compliance.md index ddfc64e0..f5238abb 100644 --- a/docs/rfc-compliance.md +++ b/docs/rfc-compliance.md @@ -8,9 +8,9 @@ Status key: - Supported: implemented and tested - Partial: implemented with known limitations -- Not Met: an obligation the library does not meet — the note says what is planned +- Not Met: an obligation the library does not meet - the note says what is planned - N/A: not applicable to a sender implementation, or applicable and deliberately - excluded — the note says which, and why + excluded - the note says which, and why A status describes the library: Core, and the role contracts it defines, with a conforming platform supplying the roles it needs. Almost every requirement below @@ -21,60 +21,60 @@ exception and links the issue tracking it, and the [capability matrix](platforms/index.md) shows which platform fills which role. Every status below describes a correctly wired instance. A component that could -not be built — a dependency left NULL, a pool sized too small — is replaced by its +not be built - a dependency left NULL, a pool sized too small - is replaced by its Null object and reported through the error handler, at the severity [docs/error-severity.md](error-severity.md) sets for a `Create` that fell back. That is a wiring fault to fix before shipping, not a limit on what the library supports, so the rows do not restate it. -## RFC 5424 — The Syslog Protocol +## RFC 5424 - The Syslog Protocol Checked against [RFC 5424](https://www.rfc-editor.org/rfc/rfc5424.html), Standards Track. | Section | Requirement | Status | Notes | |---|---|---|---| | [5](https://www.rfc-editor.org/rfc/rfc5424.html#section-5) | Transport MUST NOT deliberately alter the message | Supported | The sender transmits the formatted message as the formatter produced it. The length prefix added by octet-counting framing is defined by the transport mapping (RFC 5425 §4.3.1, RFC 6587 §3.4.1) and frames the message rather than altering it | -| [5.1](https://www.rfc-editor.org/rfc/rfc5424.html#section-5.1) | Minimum transport mapping — TLS MUST, UDP SHOULD | Supported | Both ship. TLS is a Stream wrapped around a byte-transport Stream; UDP is `SolidSyslogUdpSender` over the Datagram role. Which platform supplies each is in the [capability matrix](platforms/index.md). §5.1 also RECOMMENDS deployments use TLS, which is a deployment choice rather than a library one | +| [5.1](https://www.rfc-editor.org/rfc/rfc5424.html#section-5.1) | Minimum transport mapping - TLS MUST, UDP SHOULD | Supported | Both ship. TLS is a Stream wrapped around a byte-transport Stream; UDP is `SolidSyslogUdpSender` over the Datagram role. Which platform supplies each is in the [capability matrix](platforms/index.md). §5.1 also RECOMMENDS deployments use TLS, which is a deployment choice rather than a library one | | [6](https://www.rfc-editor.org/rfc/rfc5424.html#section-6) | PRINTUSASCII in header fields (codes 33-126) | Supported | Non-compliant bytes substituted with `?` at format time (HOSTNAME, APP-NAME, PROCID, MSGID) | -| [6.1](https://www.rfc-editor.org/rfc/rfc5424.html#section-6.1) | Message length — no upper limit; receivers MUST accept 480 octets and SHOULD accept 2048 | Supported | §6.1 bounds what a receiver accepts rather than what a sender emits, and permits any length. `SOLIDSYSLOG_MAX_MESSAGE_SIZE` is the sender-side choice, and defaults to the MUST floor — the only length every conforming receiver has to take. Raise it via the standard tunable mechanism where the receivers are known | -| [6.2](https://www.rfc-editor.org/rfc/rfc5424.html#section-6.2) | HEADER — seven-bit ASCII in an eight-bit field | Supported | Every HEADER field is written through the `SolidSyslogHeaderField` sink, which substitutes any byte outside PRINTUSASCII (33–126) with `?`. PRINTUSASCII is a subset of seven-bit ASCII, so the stricter substitution satisfies §6.2 as well as the per-field productions | -| [6.2.1](https://www.rfc-editor.org/rfc/rfc5424.html#section-6.2.1) | PRI — facility * 8 + severity | Supported | Invalid values fall back to `syslog.err` (facility 5, severity 3) | +| [6.1](https://www.rfc-editor.org/rfc/rfc5424.html#section-6.1) | Message length - no upper limit; receivers MUST accept 480 octets and SHOULD accept 2048 | Supported | §6.1 bounds what a receiver accepts rather than what a sender emits, and permits any length. `SOLIDSYSLOG_MAX_MESSAGE_SIZE` is the sender-side choice, and defaults to the MUST floor - the only length every conforming receiver has to take. Raise it via the standard tunable mechanism where the receivers are known | +| [6.2](https://www.rfc-editor.org/rfc/rfc5424.html#section-6.2) | HEADER - seven-bit ASCII in an eight-bit field | Supported | Every HEADER field is written through the `SolidSyslogHeaderField` sink, which substitutes any byte outside PRINTUSASCII (33-126) with `?`. PRINTUSASCII is a subset of seven-bit ASCII, so the stricter substitution satisfies §6.2 as well as the per-field productions | +| [6.2.1](https://www.rfc-editor.org/rfc/rfc5424.html#section-6.2.1) | PRI - facility * 8 + severity | Supported | Invalid values fall back to `syslog.err` (facility 5, severity 3) | | [6.2.2](https://www.rfc-editor.org/rfc/rfc5424.html#section-6.2.2) | VERSION = 1 | Supported | | -| [6.2.3](https://www.rfc-editor.org/rfc/rfc5424.html#section-6.2.3) | TIMESTAMP — derived from RFC 3339, with an optional 1–6 digit fraction | Supported | §6.2.3 restricts RFC 3339 rather than ISO 8601 generally, and its ABNF bounds TIME-SECFRAC at `1*6DIGIT` where RFC 3339 allows any number of digits. The library always writes the full 6, so microsecond resolution, with a UTC offset or `Z` | -| [6.2.3](https://www.rfc-editor.org/rfc/rfc5424.html#section-6.2.3) | TIMESTAMP — NILVALUE when clock unavailable | Supported | NilClock produces `-` | +| [6.2.3](https://www.rfc-editor.org/rfc/rfc5424.html#section-6.2.3) | TIMESTAMP - derived from RFC 3339, with an optional 1-6 digit fraction | Supported | §6.2.3 restricts RFC 3339 rather than ISO 8601 generally, and its ABNF bounds TIME-SECFRAC at `1*6DIGIT` where RFC 3339 allows any number of digits. The library always writes the full 6, so microsecond resolution, with a UTC offset or `Z` | +| [6.2.3](https://www.rfc-editor.org/rfc/rfc5424.html#section-6.2.3) | TIMESTAMP - NILVALUE when clock unavailable | Supported | NilClock produces `-` | | [6.2.3.1](https://www.rfc-editor.org/rfc/rfc5424.html#section-6.2.3.1) | TIMESTAMP examples | N/A | Illustrative. States no requirement of its own | -| [6.2.4](https://www.rfc-editor.org/rfc/rfc5424.html#section-6.2.4) | HOSTNAME — max 255 chars, PRINTUSASCII | Supported | Truncated to 255. Non-PRINTUSASCII bytes substituted with `?`. Written through the public `SolidSyslogHeaderField` sink (`SolidSyslogHeaderField_PrintUsAscii`); the underlying formatter is library-private | -| [6.2.5](https://www.rfc-editor.org/rfc/rfc5424.html#section-6.2.5) | APP-NAME — max 48 chars, PRINTUSASCII | Supported | Truncated to 48. Non-PRINTUSASCII bytes substituted with `?` | -| [6.2.6](https://www.rfc-editor.org/rfc/rfc5424.html#section-6.2.6) | PROCID — max 128 chars, PRINTUSASCII | Supported | Truncated to 128. Non-PRINTUSASCII bytes substituted with `?` | -| [6.2.7](https://www.rfc-editor.org/rfc/rfc5424.html#section-6.2.7) | MSGID — max 32 chars, PRINTUSASCII | Supported | Truncated to 32. Non-PRINTUSASCII bytes substituted with `?` | -| [6.3](https://www.rfc-editor.org/rfc/rfc5424.html#section-6.3) | STRUCTURED-DATA — SD-ELEMENTs or NILVALUE | Supported | Extensible via `SolidSyslogStructuredData` vtable | -| [6.3.1](https://www.rfc-editor.org/rfc/rfc5424.html#section-6.3.1) | SD-ELEMENT — brackets, SD-ID, space-separated SD-PARAMs | Supported | `SolidSyslogSdElement` owns the framing: `SolidSyslogSdElement_Begin` opens the bracket and writes the SD-ID, each `SolidSyslogSdElement_Param` writes a leading space and the name, and `SolidSyslogSdElement_End` closes the bracket. An author writing structured data cannot emit the delimiters directly | -| [6.3.2](https://www.rfc-editor.org/rfc/rfc5424.html#section-6.3.2) | SD-ID and PARAM-NAME conform to SD-NAME | Supported | `SolidSyslogSdElement` owns the brackets, the `@` and enterprise number, the separators and the quoting; it bounds each name to the 32 characters §6.3.2 allows and substitutes non-printable bytes and spaces. The three remaining SD-NAME exclusions — `=`, `]` and `"` — are the author's to observe, and are stated on `SolidSyslogSdElement_Begin`, as is §6.3.2's rule that an SD-ID appears at most once in a message. Names are written by the developer authoring the SD rather than carried from runtime data, so an invalid one fails visibly on the first run rather than on some input | -| [6.3.3](https://www.rfc-editor.org/rfc/rfc5424.html#section-6.3.3) | SD-PARAM value escaping (`]`, `\`, `"`) | Supported | `SolidSyslogSdValue` — every SD-PARAM value is written through this sink, which applies the escaping: RFC 3629 UTF-8 validated, ill-formed input substituted per-byte with U+FFFD (Unicode §3.9). `SolidSyslogOriginSd` streams software, swVersion, enterpriseId, and each ip into it; `SolidSyslogMetaSd` streams language via the integrator's `SolidSyslogSdValueFunction` callback. Both get the same escaping. | -| [6.3.4](https://www.rfc-editor.org/rfc/rfc5424.html#section-6.3.4) | Change control — a defined SD-ID or PARAM-NAME MUST NOT change meaning | N/A | Directed at whoever defines an SD-ID, not at an implementation emitting one. The elements this library ships are the IANA-registered ones, whose syntax §7 fixes, so there is no definition here to hold stable | +| [6.2.4](https://www.rfc-editor.org/rfc/rfc5424.html#section-6.2.4) | HOSTNAME - max 255 chars, PRINTUSASCII | Supported | Truncated to 255. Non-PRINTUSASCII bytes substituted with `?`. Written through the public `SolidSyslogHeaderField` sink (`SolidSyslogHeaderField_PrintUsAscii`); the underlying formatter is library-private | +| [6.2.5](https://www.rfc-editor.org/rfc/rfc5424.html#section-6.2.5) | APP-NAME - max 48 chars, PRINTUSASCII | Supported | Truncated to 48. Non-PRINTUSASCII bytes substituted with `?` | +| [6.2.6](https://www.rfc-editor.org/rfc/rfc5424.html#section-6.2.6) | PROCID - max 128 chars, PRINTUSASCII | Supported | Truncated to 128. Non-PRINTUSASCII bytes substituted with `?` | +| [6.2.7](https://www.rfc-editor.org/rfc/rfc5424.html#section-6.2.7) | MSGID - max 32 chars, PRINTUSASCII | Supported | Truncated to 32. Non-PRINTUSASCII bytes substituted with `?` | +| [6.3](https://www.rfc-editor.org/rfc/rfc5424.html#section-6.3) | STRUCTURED-DATA - SD-ELEMENTs or NILVALUE | Supported | Extensible via `SolidSyslogStructuredData` vtable | +| [6.3.1](https://www.rfc-editor.org/rfc/rfc5424.html#section-6.3.1) | SD-ELEMENT - brackets, SD-ID, space-separated SD-PARAMs | Supported | `SolidSyslogSdElement` owns the framing: `SolidSyslogSdElement_Begin` opens the bracket and writes the SD-ID, each `SolidSyslogSdElement_Param` writes a leading space and the name, and `SolidSyslogSdElement_End` closes the bracket. An author writing structured data cannot emit the delimiters directly | +| [6.3.2](https://www.rfc-editor.org/rfc/rfc5424.html#section-6.3.2) | SD-ID and PARAM-NAME conform to SD-NAME | Supported | `SolidSyslogSdElement` owns the brackets, the `@` and enterprise number, the separators and the quoting; it bounds each name to the 32 characters §6.3.2 allows and substitutes non-printable bytes and spaces. The three remaining SD-NAME exclusions - `=`, `]` and `"` - are the author's to observe, and are stated on `SolidSyslogSdElement_Begin`, as is §6.3.2's rule that an SD-ID appears at most once in a message. Names are written by the developer authoring the SD rather than carried from runtime data, so an invalid one fails visibly on the first run rather than on some input | +| [6.3.3](https://www.rfc-editor.org/rfc/rfc5424.html#section-6.3.3) | SD-PARAM value escaping (`]`, `\`, `"`) | Supported | `SolidSyslogSdValue` - every SD-PARAM value is written through this sink, which applies the escaping: RFC 3629 UTF-8 validated, ill-formed input substituted per-byte with U+FFFD (Unicode §3.9). `SolidSyslogOriginSd` streams software, swVersion, enterpriseId, and each ip into it; `SolidSyslogMetaSd` streams language via the integrator's `SolidSyslogSdValueFunction` callback. Both get the same escaping. | +| [6.3.4](https://www.rfc-editor.org/rfc/rfc5424.html#section-6.3.4) | Change control - a defined SD-ID or PARAM-NAME MUST NOT change meaning | N/A | Directed at whoever defines an SD-ID, not at an implementation emitting one. The elements this library ships are the IANA-registered ones, whose syntax §7 fixes, so there is no definition here to hold stable | | [6.3.5](https://www.rfc-editor.org/rfc/rfc5424.html#section-6.3.5) | STRUCTURED-DATA examples | N/A | Illustrative. States no requirement of its own | -| [6.4](https://www.rfc-editor.org/rfc/rfc5424.html#section-6.4) | MSG — UTF-8 preferred | Supported | RFC 3629 UTF-8 validated at the formatter primitives (`SolidSyslogFormatter_BoundedString`), with ill-formed input substituted per-byte with U+FFFD (Unicode §3.9). MSG is prefixed with the §6.4 UTF-8 BOM (`%xEF.BB.BF`) unconditionally. A leading BOM in the caller's body is stripped, so the wire frame contains exactly one. Truncation preserves codepoint boundaries at both layers: the formatter clips at `SOLIDSYSLOG_MAX_MESSAGE_SIZE` without splitting a codepoint, and on UDP the sender walks back over any partial codepoint when the kernel reports `EMSGSIZE` for the path MTU. TCP/TLS streams fragment transparently at the transport layer and so do not need a path-MTU trim | +| [6.4](https://www.rfc-editor.org/rfc/rfc5424.html#section-6.4) | MSG - UTF-8 preferred | Supported | RFC 3629 UTF-8 validated at the formatter primitives (`SolidSyslogFormatter_BoundedString`), with ill-formed input substituted per-byte with U+FFFD (Unicode §3.9). MSG is prefixed with the §6.4 UTF-8 BOM (`%xEF.BB.BF`) unconditionally. A leading BOM in the caller's body is stripped, so the wire frame contains exactly one. Truncation preserves codepoint boundaries at both layers: the formatter clips at `SOLIDSYSLOG_MAX_MESSAGE_SIZE` without splitting a codepoint, and on UDP the sender walks back over any partial codepoint when the kernel reports `EMSGSIZE` for the path MTU. TCP/TLS streams fragment transparently at the transport layer and so do not need a path-MTU trim | | [6.5](https://www.rfc-editor.org/rfc/rfc5424.html#section-6.5) | Message examples | N/A | Illustrative. States no requirement of its own | -| [7](https://www.rfc-editor.org/rfc/rfc5424.html#section-7) | Structured Data IDs — the IANA-registered SD-IDs are OPTIONAL | N/A | Introduces the IANA-registered SD-IDs and marks them all OPTIONAL. Its one requirement is directed at receiving applications: be prepared to accept the defined number of characters in any valid UTF-8 code point, which may be up to 6 octets each | -| [7.1](https://www.rfc-editor.org/rfc/rfc5424.html#section-7.1) | timeQuality SD — tzKnown, isSynced, syncAccuracy | Supported | `SolidSyslogTimeQualitySd` | -| [7.1.1](https://www.rfc-editor.org/rfc/rfc5424.html#section-7.1.1) | tzKnown — MUST be 1 when the time zone is known, 0 when in doubt | Supported | The `TzKnown` bool in `SolidSyslogTimeQuality` is emitted as `1` or `0`, so no other value can reach the wire. Which one is true is the integrator callback's to answer | -| [7.1.2](https://www.rfc-editor.org/rfc/rfc5424.html#section-7.1.2) | isSynced — MUST be 1 when synchronised to a reliable source, 0 when not | Supported | The `IsSynced` bool is emitted as `1` or `0` on the same basis as tzKnown | -| [7.1.3](https://www.rfc-editor.org/rfc/rfc5424.html#section-7.1.3) | syncAccuracy — MUST NOT be specified when isSynced is 0 | Supported | The library omits the parameter whenever `IsSynced` is false | +| [7](https://www.rfc-editor.org/rfc/rfc5424.html#section-7) | Structured Data IDs - the IANA-registered SD-IDs are OPTIONAL | N/A | Introduces the IANA-registered SD-IDs and marks them all OPTIONAL. Its one requirement is directed at receiving applications: be prepared to accept the defined number of characters in any valid UTF-8 code point, which may be up to 6 octets each | +| [7.1](https://www.rfc-editor.org/rfc/rfc5424.html#section-7.1) | timeQuality SD - tzKnown, isSynced, syncAccuracy | Supported | `SolidSyslogTimeQualitySd` | +| [7.1.1](https://www.rfc-editor.org/rfc/rfc5424.html#section-7.1.1) | tzKnown - MUST be 1 when the time zone is known, 0 when in doubt | Supported | The `TzKnown` bool in `SolidSyslogTimeQuality` is emitted as `1` or `0`, so no other value can reach the wire. Which one is true is the integrator callback's to answer | +| [7.1.2](https://www.rfc-editor.org/rfc/rfc5424.html#section-7.1.2) | isSynced - MUST be 1 when synchronised to a reliable source, 0 when not | Supported | The `IsSynced` bool is emitted as `1` or `0` on the same basis as tzKnown | +| [7.1.3](https://www.rfc-editor.org/rfc/rfc5424.html#section-7.1.3) | syncAccuracy - MUST NOT be specified when isSynced is 0 | Supported | The library omits the parameter whenever `IsSynced` is false | | [7.1.4](https://www.rfc-editor.org/rfc/rfc5424.html#section-7.1.4) | timeQuality examples | N/A | Illustrative. States no requirement of its own | -| [7.2](https://www.rfc-editor.org/rfc/rfc5424.html#section-7.2) | origin SD — software, swVersion, enterpriseId, ip | Supported | `SolidSyslogOriginSd` emits all four §7.2 parameters, each independently optional: a NULL string, or a NULL callback pair, omits that parameter. Wiring none is legal — §7.2 marks every parameter OPTIONAL — and produces a bare `[origin]`. Each parameter's own constraints are in the rows below; `SolidSyslogOriginSdConfig` states how the caller's strings are held and for how long | -| [7.2.1](https://www.rfc-editor.org/rfc/rfc5424.html#section-7.2.1) | ip — MUST be the textual representation of an IP address | Supported | The library opens one `ip` PARAM per index and escapes what the integrator's at-callback writes into it, applying no length bound of its own. §7.2.1 lets a multi-homed originator list one address or repeat the parameter, and the count callback chooses. The textual form of each address is the integrator's to produce | -| [7.2.2](https://www.rfc-editor.org/rfc/rfc5424.html#section-7.2.2) | enterpriseId — MUST be an IANA-registered private enterprise number | Supported | Bounded at 64 decoded bytes. §7.2.2 states no length, so 64 is this library's ceiling, set well above the private enterprise numbers and sub-identifier OIDs that appear in practice. The library registers no enterprise number of its own and does not check the value's form: which number is yours, and that it is registered with IANA, is the integrator's | -| [7.2.3](https://www.rfc-editor.org/rfc/rfc5424.html#section-7.2.3) | software — MUST NOT be longer than 48 characters | Supported | Enforced. `SolidSyslogOriginSd` writes the value through `SolidSyslogSdValue_BoundedString` with a 48 bound, so an over-long string is truncated rather than emitted. The bound counts decoded bytes — what a receiver's un-escaping decoder extracts — so for multi-byte UTF-8 it truncates earlier than the 48 characters §7.2.3 allows, never later | -| [7.2.4](https://www.rfc-editor.org/rfc/rfc5424.html#section-7.2.4) | swVersion — MUST NOT be longer than 32 characters | Supported | Enforced at 32 on the same terms as `software` above | +| [7.2](https://www.rfc-editor.org/rfc/rfc5424.html#section-7.2) | origin SD - software, swVersion, enterpriseId, ip | Supported | `SolidSyslogOriginSd` emits all four §7.2 parameters, each independently optional: a NULL string, or a NULL callback pair, omits that parameter. Wiring none is legal - §7.2 marks every parameter OPTIONAL - and produces a bare `[origin]`. Each parameter's own constraints are in the rows below; `SolidSyslogOriginSdConfig` states how the caller's strings are held and for how long | +| [7.2.1](https://www.rfc-editor.org/rfc/rfc5424.html#section-7.2.1) | ip - MUST be the textual representation of an IP address | Supported | The library opens one `ip` PARAM per index and escapes what the integrator's at-callback writes into it, applying no length bound of its own. §7.2.1 lets a multi-homed originator list one address or repeat the parameter, and the count callback chooses. The textual form of each address is the integrator's to produce | +| [7.2.2](https://www.rfc-editor.org/rfc/rfc5424.html#section-7.2.2) | enterpriseId - MUST be an IANA-registered private enterprise number | Supported | Bounded at 64 decoded bytes. §7.2.2 states no length, so 64 is this library's ceiling, set well above the private enterprise numbers and sub-identifier OIDs that appear in practice. The library registers no enterprise number of its own and does not check the value's form: which number is yours, and that it is registered with IANA, is the integrator's | +| [7.2.3](https://www.rfc-editor.org/rfc/rfc5424.html#section-7.2.3) | software - MUST NOT be longer than 48 characters | Supported | Enforced. `SolidSyslogOriginSd` writes the value through `SolidSyslogSdValue_BoundedString` with a 48 bound, so an over-long string is truncated rather than emitted. The bound counts decoded bytes - what a receiver's un-escaping decoder extracts - so for multi-byte UTF-8 it truncates earlier than the 48 characters §7.2.3 allows, never later | +| [7.2.4](https://www.rfc-editor.org/rfc/rfc5424.html#section-7.2.4) | swVersion - MUST NOT be longer than 32 characters | Supported | Enforced at 32 on the same terms as `software` above | | [7.2.5](https://www.rfc-editor.org/rfc/rfc5424.html#section-7.2.5) | origin example | N/A | Illustrative. States no requirement of its own | -| [7.3](https://www.rfc-editor.org/rfc/rfc5424.html#section-7.3) | meta SD — sequenceId, sysUpTime, language | Supported | `SolidSyslogMetaSd` emits all three IANA-registered §7.3 parameters. `sysUpTime` and `language` are independently optional — a NULL field in `SolidSyslogMetaSdConfig` omits that parameter. The counter is required: without one there is no sequenceId, so there is no meta element to emit. Each parameter's own constraints are in the rows below | -| [7.3.1](https://www.rfc-editor.org/rfc/rfc5424.html#section-7.3.1) | meta SD — sequenceId wraps at 2147483647 to 1 | Supported | The [AtomicCounter](api/structSolidSyslogAtomicCounter.md) contract carries the wrap: values run [1, `SOLIDSYSLOG_SEQUENCE_ID_MAX`] and never 0. The id is taken when a message is raised, so it records the order messages originated in. Delivery order may differ — messages raised from several threads, or any transport that reorders — and sorting on sequenceId recovers the order within one originator's run. §7.3.1 scopes it to that: the count starts at 1 when the syslog function starts and returns to 1 after the maximum, so it does not order across a restart, a wrap, or two originators. | -| [7.3.2](https://www.rfc-editor.org/rfc/rfc5424.html#section-7.3.2) | sysUpTime — MUST be a decimal integer, digits only | Supported | The callback returns `uint32_t` hundredths and the value is written through `SolidSyslogSdValue_Uint32`, which emits decimal digits only. §7.3.2 also notes the SNMP management portion may differ from the syslog one, which is the integrator's to reconcile | -| [7.3.3](https://www.rfc-editor.org/rfc/rfc5424.html#section-7.3.3) | language — MUST be a BCP 47 language identifier | Supported | Streamed through a `SolidSyslogSdValueFunction` into a `SolidSyslogSdValue`, which applies §6.3.3 escaping. The parameter is optional and the identifier is the integrator's to supply; the library does not parse BCP 47 | -| [8.1](https://www.rfc-editor.org/rfc/rfc5424.html#section-8.1) | UNICODE — shortest-form encoding REQUIRED | Supported | RFC 3629 validation at the formatter primitives rejects overlong encodings, substituting each ill-formed byte with U+FFFD per Unicode §3.9, so a non-shortest-form sequence cannot pass through | -| [8.6](https://www.rfc-editor.org/rfc/rfc5424.html#section-8.6) | Congestion control — TLS REQUIRED to implement, UDP for managed networks | Supported | The implementation obligation is the same one §5.1 states, and is met. Which transport a deployment uses, and whether its network is provisioned for UDP syslog, is the integrator's choice | +| [7.3](https://www.rfc-editor.org/rfc/rfc5424.html#section-7.3) | meta SD - sequenceId, sysUpTime, language | Supported | `SolidSyslogMetaSd` emits all three IANA-registered §7.3 parameters. `sysUpTime` and `language` are independently optional - a NULL field in `SolidSyslogMetaSdConfig` omits that parameter. The counter is required: without one there is no sequenceId, so there is no meta element to emit. Each parameter's own constraints are in the rows below | +| [7.3.1](https://www.rfc-editor.org/rfc/rfc5424.html#section-7.3.1) | meta SD - sequenceId wraps at 2147483647 to 1 | Supported | The [AtomicCounter](api/structSolidSyslogAtomicCounter.md) contract carries the wrap: values run [1, `SOLIDSYSLOG_SEQUENCE_ID_MAX`] and never 0. The id is taken when a message is raised, so it records the order messages originated in. Delivery order may differ - messages raised from several threads, or any transport that reorders - and sorting on sequenceId recovers the order within one originator's run. §7.3.1 scopes it to that: the count starts at 1 when the syslog function starts and returns to 1 after the maximum, so it does not order across a restart, a wrap, or two originators. | +| [7.3.2](https://www.rfc-editor.org/rfc/rfc5424.html#section-7.3.2) | sysUpTime - MUST be a decimal integer, digits only | Supported | The callback returns `uint32_t` hundredths and the value is written through `SolidSyslogSdValue_Uint32`, which emits decimal digits only. §7.3.2 also notes the SNMP management portion may differ from the syslog one, which is the integrator's to reconcile | +| [7.3.3](https://www.rfc-editor.org/rfc/rfc5424.html#section-7.3.3) | language - MUST be a BCP 47 language identifier | Supported | Streamed through a `SolidSyslogSdValueFunction` into a `SolidSyslogSdValue`, which applies §6.3.3 escaping. The parameter is optional and the identifier is the integrator's to supply; the library does not parse BCP 47 | +| [8.1](https://www.rfc-editor.org/rfc/rfc5424.html#section-8.1) | UNICODE - shortest-form encoding REQUIRED | Supported | RFC 3629 validation at the formatter primitives rejects overlong encodings, substituting each ill-formed byte with U+FFFD per Unicode §3.9, so a non-shortest-form sequence cannot pass through | +| [8.6](https://www.rfc-editor.org/rfc/rfc5424.html#section-8.6) | Congestion control - TLS REQUIRED to implement, UDP for managed networks | Supported | The implementation obligation is the same one §5.1 states, and is met. Which transport a deployment uses, and whether its network is provisioned for UDP syslog, is the integrator's choice | -## RFC 5425 — TLS Transport Mapping for Syslog +## RFC 5425 - TLS Transport Mapping for Syslog Checked against [RFC 5425](https://www.rfc-editor.org/rfc/rfc5425.html), Standards Track, and [RFC 9662](https://www.rfc-editor.org/rfc/rfc9662.html), Standards Track, @@ -95,28 +95,29 @@ requirement in force, rather than tabulating it separately. | Section | Requirement | Status | Notes | |---|---|---|---| -| [3](https://www.rfc-editor.org/rfc/rfc5425.html#section-3) | TLS to secure syslog | Supported | A TLS `Stream` wraps a byte-transport `Stream` — a TCP one from any platform, or a caller-supplied one. §3's own caveat holds here too: the protection is hop-by-hop, so a relay that terminates the connection is authenticated in place of the originating device | +| [3](https://www.rfc-editor.org/rfc/rfc5425.html#section-3) | TLS to secure syslog | Supported | A TLS `Stream` wraps a byte-transport `Stream` - a TCP one from any platform, or a caller-supplied one. §3's own caveat holds here too: the protection is hop-by-hop, so a relay that terminates the connection is authenticated in place of the originating device | | [4.1](https://www.rfc-editor.org/rfc/rfc5425.html#section-4.1) | Default port 6514 | Supported | `SOLIDSYSLOG_TLS_DEFAULT_PORT` in `SolidSyslogTransport.h`; the endpoint callback overrides it | | [4.2](https://www.rfc-editor.org/rfc/rfc5425.html#section-4.2) | TLS 1.2 as the mandatory-to-implement protocol | Supported | The contract pins the floor at TLS 1.2. RFC 9662 keeps it mandatory-to-implement | -| [RFC 9662 §4](https://www.rfc-editor.org/rfc/rfc9662.html#section-4) | Cipher suites — `TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256` SHOULD be offered, `TLS_RSA_WITH_AES_128_CBC_SHA` MAY be | N/A | Which cipher suites exist is a property of the TLS library linked on the target, not of this library, which neither adds nor removes any. RFC 9662 downgraded the 2009 mandatory suite because it offers no forward secrecy, which is the same reason a hardened build disables it. RFC 9662 §4 is internally awkward — it calls both suites REQUIRED and then states the offer preference above — so it is cited whole rather than paraphrased into something tidier | +| [RFC 9662 §4](https://www.rfc-editor.org/rfc/rfc9662.html#section-4) | Cipher suites - `TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256` SHOULD be offered, `TLS_RSA_WITH_AES_128_CBC_SHA` MAY be | N/A | Which cipher suites exist is a property of the TLS library linked on the target, not of this library, which neither adds nor removes any. RFC 9662 downgraded the 2009 mandatory suite because it offers no forward secrecy, which is the same reason a hardened build disables it. RFC 9662 §4 is internally awkward - it calls both suites REQUIRED and then states the offer preference above - so it is cited whole rather than paraphrased into something tidier | | [RFC 9662 §4](https://www.rfc-editor.org/rfc/rfc9662.html#section-4) | TLS 1.3 SHOULD be supported, and MUST be preferred where implemented | Supported | The contract sets a floor and deliberately no ceiling, so nothing here holds a handshake below TLS 1.3 and the later version is negotiated wherever both peers offer one. This is why no ceiling is set: pinning one to constrain cipher selection would breach the preference requirement. Whether TLS 1.3 is available at all belongs to the backend the integrator links and how it was built | -| [RFC 9662 §6](https://www.rfc-editor.org/rfc/rfc9662.html#section-6) | Early data (0-RTT) MUST NOT be used | Supported | RFC 9662 forbids it because syslog has no replay protection and early data has none between connections. Sending early data is an explicit act — no shipped TLS stream calls an early-data API, so none is sent, whatever session state the backend keeps. A caller-supplied stream is the caller's to hold to the same rule | -| [4.2.1](https://www.rfc-editor.org/rfc/rfc5425.html#section-4.2.1) | Certificate-based authentication — server | Supported | Peer verification is required, not optional: the certificate must chain to the trust anchors the caller supplies, and the peer identity the caller declares is checked against it | -| [4.2.1](https://www.rfc-editor.org/rfc/rfc5425.html#section-4.2.1) | Certificate-based authentication — client | Supported | A client certificate and its key are optional configuration on the TLS stream, presented only when both are given, and a partially configured pair is reported rather than silently ignored | +| [RFC 9662 §6](https://www.rfc-editor.org/rfc/rfc9662.html#section-6) | Early data (0-RTT) MUST NOT be used | Supported | RFC 9662 forbids it because syslog has no replay protection and early data has none between connections. Sending early data is an explicit act - no shipped TLS stream calls an early-data API, so none is sent, whatever session state the backend keeps. A caller-supplied stream is the caller's to hold to the same rule | +| [4.2.1](https://www.rfc-editor.org/rfc/rfc5425.html#section-4.2.1) | Certificate-based authentication - server | Partial | Peer verification is required, not optional: the certificate must chain to the trust anchors the caller supplies, and the peer identity the caller declares is checked against it. §4.2.1 requires support for both authorisation methods - a deployment configures whichever it uses - and its end-entity bullet states that implementations MUST support the certificate fingerprints of §4.2.2. No shipped platform does - see §5.1 below and [#753](https://github.com/cososo-ltd/solid-syslog/issues/753) | +| [4.2.1](https://www.rfc-editor.org/rfc/rfc5425.html#section-4.2.1) | Certificate-based authentication - client | Supported | A client certificate and its key are optional configuration on the TLS stream, presented only when both are given, and a partially configured pair is reported rather than silently ignored | | [4.2.1](https://www.rfc-editor.org/rfc/rfc5425.html#section-4.2.1) | Means to generate a key pair and self-signed certificate | N/A | Deliberately excluded. The library consumes trust material and does not mint it, so key generation belongs to the deployment's provisioning. Directed at a syslog application rather than at a component one is built from | | [4.2.2](https://www.rfc-editor.org/rfc/rfc5425.html#section-4.2.2) | Certificate fingerprints published through a management interface | N/A | Directed at a syslog application, not a component one is built from: the library has no management interface, and the certificate is the integrator's to hold and to publish. The fingerprint form §4.2.2 defines matters where a peer is authorised by one, which is §5.1 | -| [4.2.3](https://www.rfc-editor.org/rfc/rfc5425.html#section-4.2.3) | Administrators may select the cryptographic level | Partial | The contract requires an integrator's cipher policy to be passed through where the underlying library allows one to be selected. Neither shipped TLS platform delivers that on the connection actually negotiated — see each platform's page, and [#733](https://github.com/cososo-ltd/solid-syslog/issues/733) | +| [4.2.3](https://www.rfc-editor.org/rfc/rfc5425.html#section-4.2.3) | Administrators may select the cryptographic level | Partial | The contract requires an integrator's cipher policy to be passed through where the underlying library allows one to be selected. Neither shipped TLS platform delivers that on the connection actually negotiated - see each platform's page, and [#733](https://github.com/cososo-ltd/solid-syslog/issues/733) | +| [4.2.3](https://www.rfc-editor.org/rfc/rfc5425.html#section-4.2.3) | A resumed session's security parameters SHOULD be checked against the requirements of the session being requested | Supported | No shipped TLS stream resumes a session, so a resumed session's parameters cannot fall short of the requested one's. Each platform page states what its own adapter carries across a connection. [The contract](tls.md) requires a stream that does resume to perform the check, which is stronger than the SHOULD, so a stream that started resuming without it would breach the contract before it breached the RFC | | [4.3](https://www.rfc-editor.org/rfc/rfc5425.html#section-4.3) | All syslog messages MUST be sent as TLS application data | Supported | The TLS `Stream` carries the frames the sender writes as ordinary application data; nothing is sent outside the session, and §4.3's `APPLICATION-DATA = 1*SYSLOG-FRAME` is what the octet-counting sender produces | | [4.3.1](https://www.rfc-editor.org/rfc/rfc5425.html#section-4.3.1) | Octet-counting framing; receivers MUST process 2048 octets and SHOULD process 8192 | Supported | Reuses `SolidSyslogStreamSender`, so the frame is `MSG-LEN SP MSG`. §4.3.1 bounds what a receiver processes; the library is a sender, and emits at most `SOLIDSYSLOG_MAX_MESSAGE_SIZE`, which defaults well inside the figure every receiver must take | -| [4.4](https://www.rfc-editor.org/rfc/rfc5425.html#section-4.4) | `close_notify` before closing | Supported | Close sends `close_notify` before tearing the connection down | -| [5](https://www.rfc-editor.org/rfc/rfc5425.html#section-5) | Security policies — the deployment chooses how peers are authorised | N/A | Scoping text for the policies below. Which one a deployment runs is the integrator's, and §6.1 names §5.1 and §5.2 together as the RECOMMENDED default | -| [5.1](https://www.rfc-editor.org/rfc/rfc5425.html#section-5.1) | Authorised peers MUST be specifiable by certificate fingerprint | Not Met | The library authorises a peer by trust anchor and name, and offers no way to pin a certificate fingerprint. [The contract](tls.md) carries the obligation and [#753](https://github.com/cososo-ltd/solid-syslog/issues/753) tracks delivering it on both shipped TLS platforms, for 0.2.0 | +| [4.4](https://www.rfc-editor.org/rfc/rfc5425.html#section-4.4) | Close a connection not expected to carry more messages, sending `close_notify` first | Supported | Close sends `close_notify` before tearing the connection down. On the closure requirement itself: a sender holds one connection open while it has records to deliver, which is the case §4.4 does not ask it to close, and closes on destroy, on a send failure and on an endpoint change. Bounding a long-lived connection beyond that is the integrator's, through `SolidSyslogSender_Disconnect` - see [the contract](tls.md). §4.4's remaining sentences bind the receiver | +| [5](https://www.rfc-editor.org/rfc/rfc5425.html#section-5) | Security policies - the deployment chooses how peers are authorised | N/A | Scoping text for the policies below. Which one a deployment runs is the integrator's, and §6.1 names §5.1 and §5.2 together as the RECOMMENDED default | +| [5.1](https://www.rfc-editor.org/rfc/rfc5425.html#section-5.1) | Authorised peers MUST be specifiable by certificate fingerprint | Not Met | The library authorises a peer by trust anchor and name, and offers no way to pin a certificate fingerprint. [The contract](tls.md) carries the obligation, [#753](https://github.com/cososo-ltd/solid-syslog/issues/753) tracks delivering it on both shipped TLS platforms for 0.2.0, and [E39](https://github.com/cososo-ltd/solid-syslog/issues/782) carries the design | | [5.2](https://www.rfc-editor.org/rfc/rfc5425.html#section-5.2) | Path validation, and authorised peers specifiable by host name | Supported | Peer verification chains to the trust anchors the caller supplies, and the declared peer identity is matched against the certificate. dNSName matching and the left-most wildcard rule come from the backend the integrator links | | [5.3](https://www.rfc-editor.org/rfc/rfc5425.html#section-5.3) | Unauthenticated transport sender | N/A | A receiver-side policy: it is the receiver that chooses not to authenticate the sender. Whether this library presents a client certificate is §4.2.1 | -| [5.4](https://www.rfc-editor.org/rfc/rfc5425.html#section-5.4) | Unauthenticated transport receiver — NOT RECOMMENDED | Supported | Not offered, which is the point: peer verification is required rather than optional, so the library will not accept any certificate presented to it. Declining to check a *name* is a separate and narrower choice, and one the contract requires be reported | -| [5.5](https://www.rfc-editor.org/rfc/rfc5425.html#section-5.5) | Neither peer authenticated — NOT RECOMMENDED | Supported | Follows from §5.4: the receiver is always verified, so this policy cannot be reached from this side | +| [5.4](https://www.rfc-editor.org/rfc/rfc5425.html#section-5.4) | Unauthenticated transport receiver - NOT RECOMMENDED | Supported | Not offered, which is the point: peer verification is required rather than optional, so the library will not accept any certificate presented to it. Declining to check a *name* is a separate and narrower choice, and one the contract requires be reported | +| [5.5](https://www.rfc-editor.org/rfc/rfc5425.html#section-5.5) | Neither peer authenticated - NOT RECOMMENDED | Supported | Follows from §5.4: the receiver is always verified, so this policy cannot be reached from this side | -## RFC 5426 — Transmission of Syslog Messages over UDP +## RFC 5426 - Transmission of Syslog Messages over UDP Checked against [RFC 5426](https://www.rfc-editor.org/rfc/rfc5426.html), Standards Track. @@ -124,23 +125,23 @@ Checked against [RFC 5426](https://www.rfc-editor.org/rfc/rfc5426.html), Standar |---|---|---|---| | [3.1](https://www.rfc-editor.org/rfc/rfc5426.html#section-3.1) | One message per UDP datagram | Supported | `SolidSyslogUdpSender` sends one datagram per `Send` call | | [3.2](https://www.rfc-editor.org/rfc/rfc5426.html#section-3.2) | Message fits in single datagram | Supported | Bounded by `SOLIDSYSLOG_MAX_MESSAGE_SIZE` | -| [3.2](https://www.rfc-editor.org/rfc/rfc5426.html#section-3.2) | Avoid IP fragmentation (respect MTU) | Supported | The [Datagram](api/structSolidSyslogDatagram.md) contract obliges an implementor to report the path's largest payload and never guess high; where the platform can distinguish oversize, the sender trims and retries. Where no path MTU is available the fallback is 1232 octets, above the 1180 and 480 §3.2 calls safest when the MTU is unknown — advice it gives as prose rather than as an RFC 2119 requirement. The 480-octet default sits below every shipped adapter's floor; a raised cap on a platform that cannot distinguish oversize is the case each platform page documents ([#736](https://github.com/cososo-ltd/solid-syslog/issues/736)) | +| [3.2](https://www.rfc-editor.org/rfc/rfc5426.html#section-3.2) | Avoid IP fragmentation (respect MTU) | Supported | The [Datagram](api/structSolidSyslogDatagram.md) contract obliges an implementor to report the path's largest payload and never guess high; where the platform can distinguish oversize, the sender trims and retries. Where no path MTU is available the fallback is 1232 octets, above the 1180 and 480 §3.2 calls safest when the MTU is unknown - advice it gives as prose rather than as an RFC 2119 requirement. The 480-octet default sits below every shipped adapter's floor; a raised cap on a platform that cannot distinguish oversize is the case each platform page documents ([#736](https://github.com/cososo-ltd/solid-syslog/issues/736)) | | [3.3](https://www.rfc-editor.org/rfc/rfc5426.html#section-3.3) | Default port 514 | Supported | `SOLIDSYSLOG_UDP_DEFAULT_PORT` = 514 | | [3.4](https://www.rfc-editor.org/rfc/rfc5426.html#section-3.4) | Source IP SHOULD NOT be read as the originator identity | N/A | Directed at whoever reads the datagram. The identity of the originator travels in the message, in HOSTNAME and in the origin SD when one is wired | | [3.5](https://www.rfc-editor.org/rfc/rfc5426.html#section-3.5) | The datagram MUST adhere to the UDP and IP structure | Supported | The library hands a payload to the platform datagram, which sends it through the stack the integrator links; forming the UDP and IP headers is that stack's | -| [3.6](https://www.rfc-editor.org/rfc/rfc5426.html#section-3.6) | UDP checksums — a sender MUST NOT disable them | Supported | Nothing in the library disables UDP checksumming: no adapter sets `SO_NO_CHECK` or a vendor equivalent, so whatever the stack does by default stands. §3.6 also records that RFC 2460 mandates checksums for UDP over IPv6 | -| [4.1](https://www.rfc-editor.org/rfc/rfc5426.html#section-4.1) | Unreliable delivery — no confirmation | N/A | Inherent in UDP: §4.1 states the transport mapping provides no mechanism to detect or correct datagram loss. §5.4 restates it as a security consequence. Caller should be aware | -| [4.3](https://www.rfc-editor.org/rfc/rfc5426.html#section-4.3) | Congestion control — TLS is REQUIRED to implement, and RECOMMENDED | Supported | The TLS transport ships and is the recommended one. §4.3 confines UDP to managed networks provisioned for it — a deployment decision, and the reason the UDP rows here describe a transport the RFC would rather you did not use in the open | +| [3.6](https://www.rfc-editor.org/rfc/rfc5426.html#section-3.6) | UDP checksums - a sender MUST NOT disable them | Supported | Nothing in the library disables UDP checksumming: no adapter sets `SO_NO_CHECK` or a vendor equivalent, so whatever the stack does by default stands. §3.6 also records that RFC 2460 mandates checksums for UDP over IPv6 | +| [4.1](https://www.rfc-editor.org/rfc/rfc5426.html#section-4.1) | Unreliable delivery - no confirmation | N/A | Inherent in UDP: §4.1 states the transport mapping provides no mechanism to detect or correct datagram loss. §5.4 restates it as a security consequence. Caller should be aware | +| [4.3](https://www.rfc-editor.org/rfc/rfc5426.html#section-4.3) | Congestion control - TLS is REQUIRED to implement, and RECOMMENDED | Supported | The TLS transport ships and is the recommended one. §4.3 confines UDP to managed networks provisioned for it - a deployment decision, and the reason the UDP rows here describe a transport the RFC would rather you did not use in the open | | [4.4](https://www.rfc-editor.org/rfc/rfc5426.html#section-4.4) | Arrival order SHOULD NOT be taken as the order of generation | Supported | Nothing in the library asks a receiver to trust arrival order. The meta SD carries sequenceId for exactly this, and §7.3.1 of RFC 5424 covers what it does | -| [5](https://www.rfc-editor.org/rfc/rfc5426.html#section-5) | Security considerations — running this on an unsecured network is NOT RECOMMENDED | N/A | The recommendation is a deployment decision. The clauses below are §5's specific threats, each dispositioned rather than summarised | +| [5](https://www.rfc-editor.org/rfc/rfc5426.html#section-5) | Security considerations - running this on an unsecured network is NOT RECOMMENDED | N/A | The recommendation is a deployment decision. The clauses below are §5's specific threats, each dispositioned rather than summarised | | [5.1](https://www.rfc-editor.org/rfc/rfc5426.html#section-5.1) | Sender authentication and message forgery | N/A | UDP offers neither, and this transport mapping provides no authentication. Use the TLS transport where sender identity matters | -| [5.2](https://www.rfc-editor.org/rfc/rfc5426.html#section-5.2) | Message observation — clear text in transit | N/A | UDP offers no confidentiality. §5.2 asks that sensitive content be kept off this transport or the network be secured; both are the deployment's | +| [5.2](https://www.rfc-editor.org/rfc/rfc5426.html#section-5.2) | Message observation - clear text in transit | N/A | UDP offers no confidentiality. §5.2 asks that sensitive content be kept off this transport or the network be secured; both are the deployment's | | [5.3](https://www.rfc-editor.org/rfc/rfc5426.html#section-5.3) | Replaying | N/A | Neither this transport mapping nor syslog itself has replay protection. sequenceId lets a collector spot a gap, not a replay | | [5.4](https://www.rfc-editor.org/rfc/rfc5426.html#section-5.4) | Unreliable delivery as a security consequence | N/A | The same property §4.1 records, read as a threat: an attacker may discard datagrams to hide activity. Store-and-forward covers a sender that cannot reach its collector, not a path that drops what was sent | | [5.5](https://www.rfc-editor.org/rfc/rfc5426.html#section-5.5) | Message prioritisation and differentiation | N/A | The mapping mandates no prioritisation and the library implements none: records are sent in the order they were raised, whatever their severity | -| [5.6](https://www.rfc-editor.org/rfc/rfc5426.html#section-5.6) | Denial of service — implementers SHOULD minimise the threat | N/A | Directed at a receiver, whose defence §5.6 gives as restricting reception to known source addresses. A sender has no part in it | +| [5.6](https://www.rfc-editor.org/rfc/rfc5426.html#section-5.6) | Denial of service - implementers SHOULD minimise the threat | N/A | Directed at a receiver, whose defence §5.6 gives as restricting reception to known source addresses. A sender has no part in it | -## RFC 6587 — Transmission of Syslog Messages over TCP +## RFC 6587 - Transmission of Syslog Messages over TCP Checked against [RFC 6587](https://www.rfc-editor.org/rfc/rfc6587.html), Historic. @@ -150,18 +151,18 @@ Checked against [RFC 6587](https://www.rfc-editor.org/rfc/rfc6587.html), Histori | [3.4.1](https://www.rfc-editor.org/rfc/rfc6587.html#section-3.4.1) | Octet counting framing | Supported | `MSG-LEN SP MSG` prefix on every send | | [3.4.2](https://www.rfc-editor.org/rfc/rfc6587.html#section-3.4.2) | Non-transparent framing (LF trailer) | N/A | Deliberately not implemented. RFC 6587 describes both framings without recommending either, but §3.4 records that non-transparent framing has known problems and that octet counting does not; §3.4.1 is also the framing RFC 5425 mandates for TLS, so the library ships that alone rather than a mode selector | | [3.5](https://www.rfc-editor.org/rfc/rfc6587.html#section-3.5) | Session closure handling | Supported | On send failure the stream is closed; the next Send transparently reconnects | -| [3.5](https://www.rfc-editor.org/rfc/rfc6587.html#section-3.5) | Handle receiver-initiated close | Supported | Detected via send failure path — same reconnect-on-next-Send mechanism | -| — | Default port 601 | Supported | RFC 6587 standardises no port: §3.3 records that the protocol "has no standardized port assignment", and §4 that operators must select one per deployment. `SOLIDSYSLOG_TCP_DEFAULT_PORT = 601` (defined in `Core/Interface/SolidSyslogTransport.h`) is the IANA `syslog-conn` assignment from RFC 3195, and is caller-overridable via the endpoint callback | -| — | Address rotation without app restart | Supported | A library capability rather than an RFC 6587 requirement. App bumps `endpointVersion`; sender Disconnects and reconnects on next Send | -| — | Partial write handling (send returns short) | Supported | The [Stream](api/structSolidSyslogStream.md) contract makes `Send` all-or-nothing: a short write is a failure, never a partial success, so the stream closes itself, the sender reconnects on its next pass, and store-and-forward replays the message on the fresh connection. The same contract keeps steady-state `Send` and `Read` non-blocking and bounds `Open`, so a wedged peer or a full send buffer cannot stall the servicing pass. The connect bound is `SOLIDSYSLOG_TCP_CONNECT_TIMEOUT_MS` (default 200 ms), overridable at runtime through the per-Stream `GetConnectTimeoutMs(ConnectTimeoutContext)` accessor. How a transport detects a long-term wedge, and what it does about one, is on its own page | +| [3.5](https://www.rfc-editor.org/rfc/rfc6587.html#section-3.5) | Handle receiver-initiated close | Supported | Detected via send failure path - same reconnect-on-next-Send mechanism | +| - | Default port 601 | Supported | RFC 6587 standardises no port: §3.3 records that the protocol "has no standardized port assignment", and §4 that operators must select one per deployment. `SOLIDSYSLOG_TCP_DEFAULT_PORT = 601` (defined in `Core/Interface/SolidSyslogTransport.h`) is the IANA `syslog-conn` assignment from RFC 3195, and is caller-overridable via the endpoint callback | +| - | Address rotation without app restart | Supported | A library capability rather than an RFC 6587 requirement. App bumps `endpointVersion`; the sender calls `SolidSyslogSender_Disconnect` and reconnects on the next Send | +| - | Partial write handling (send returns short) | Supported | The [Stream](api/structSolidSyslogStream.md) contract makes `Send` all-or-nothing: a short write is a failure, never a partial success, so the stream closes itself, the sender reconnects on its next pass, and store-and-forward replays the message on the fresh connection. The same contract keeps steady-state `Send` and `Read` non-blocking and bounds `Open`, so a wedged peer or a full send buffer cannot stall the servicing pass. The connect bound is `SOLIDSYSLOG_TCP_CONNECT_TIMEOUT_MS` (default 200 ms), overridable at runtime through the per-Stream `GetConnectTimeoutMs(ConnectTimeoutContext)` accessor. How a transport detects a long-term wedge, and what it does about one, is on its own page | ## Summary -A `—` in the Section column marks a requirement the RFC does not number — one that comes from IANA, from another RFC, or from this library's own contract. They are requirements and are counted as such. +A `-` in the Section column marks a requirement the RFC does not number - one that comes from IANA, from another RFC, or from this library's own contract. They are requirements and are counted as such. | RFC | Total requirements | Supported | Partial | Not Met | N/A | |---|---|---|---|---|---| | RFC 5424 | 40 | 33 | 0 | 0 | 7 | -| RFC 5425 | 20 | 13 | 1 | 1 | 5 | +| RFC 5425 | 21 | 13 | 2 | 1 | 5 | | RFC 5426 | 17 | 8 | 0 | 0 | 9 | | RFC 6587 | 8 | 7 | 0 | 0 | 1 | diff --git a/docs/tls.md b/docs/tls.md index 8179dc6c..11bc26ce 100644 --- a/docs/tls.md +++ b/docs/tls.md @@ -5,7 +5,7 @@ choosing between the TLS platforms, assessing the library against a security standard, or writing a TLS `Stream` of your own. The obligations here are the contract. What each shipped TLS platform actually -does, and where it differs, is on its own page — the +does, and where it differs, is on its own page - the [capability matrix](platforms/index.md) shows which platforms fill the role. ## Delivery is preferred to silence @@ -13,24 +13,25 @@ does, and where it differs, is on its own page — the Syslog is how a device reports what happened to it. The moment the reporting matters most is the moment the device is under attack, and that is also the moment a security control that fails closed becomes a way to blind the collector. -An attacker who can move a clock forward, block the route to a revocation -responder, or wait for a certificate to lapse should not thereby be able to stop -the device reporting. +An attacker who can block the route to a revocation responder, or who can make a +credential look half-configured, should not thereby be able to stop the device +reporting. So the default is: **report the fault through the error handler, and keep delivering.** A fault that an operator can see and act on is worth more than a connection that refuses to open for a reason nobody is watching. -The rule has a limit, and it is one line rather than a list of exceptions: -**delivery stops when the peer fails the check the integrator asked for.** No -trust anchors to load, a certificate that does not chain to them, and a -certificate that does not match a declared identity are all that case. -Continuing through any of them would hand the records to whoever answered -instead, which loses the confidentiality of the log *and* the audit trail at the -same time, and does so without anyone noticing. +The rule has a limit: **delivery stops when the peer fails the check the +integrator asked for.** That covers a configuration naming neither trust anchors +nor pinned fingerprints, a certificate that does not validate against the trust +anchors, a certificate matching none of the configured fingerprints, and a +certificate that does not match a declared identity. Continuing through any of +them would hand the records to whoever answered instead, which loses the +confidentiality of the log and the audit trail at the same time, and does so +without anyone noticing. The check is the integrator's to set. Declaring no peer identity is a decision -rather than a failure — it says chain verification alone is enough here, which on +rather than a failure - it says chain verification alone is enough here, which on a closed network with a private CA it may be. What the contract requires is that the decision is explicit, and that the stream says so when it was never made at all. @@ -58,35 +59,157 @@ RFC 5425, asks that TLS 1.3 be supported and **preferred** where it is implemented, so a stream that pinned a ceiling to constrain something else would breach that. BCP 195 §3.1.1 says the same for TLS generally. -### Require a trust anchor, and take it from the integrator +### Require a trust anchor or a pinned fingerprint + +A peer is authorised in one of two ways, and a `Stream` requires at least one of +them. Configured with neither, it refuses to connect rather than talk to a peer +it cannot check. That is reported when a connection is attempted rather than when +the stream is created, because trust anchors are obtained per connection and a +stream cannot know at create time what its credential source will yield; the +timing rule is set out under *Check the configuration it cannot work without*. + +**Certification path validation.** The peer certificate must chain to trust +anchors the integrator supplies. There is no fallback to a system trust store: an +embedded target may not have one, and on a host that store is a far larger trust +base than a device reporting to a single collector needs. Validity dates are part +of this check, not separate from it - RFC 5280 §6.1 makes the validity period an +input to path validation, so a certificate outside its own dates does not chain +and the connection stops. + +**A pinned certificate fingerprint.** Covered in its own obligation below. A +certificate matched by fingerprint needs no chain: RFC 5425 §4.2.1 states that +such a certificate "can be self-signed, and no certification path validation is +needed". + +Where both are configured, the peer must satisfy both. That is this contract's +choice, not a requirement of RFC 5425: §6.1 recommends that both endpoints be +authenticated and authorised by one of §5.1 or §5.2, rather than that the two be +combined. Requiring both where both are given is the safer reading of an +integrator who supplied both. -The peer certificate must chain to trust anchors the integrator supplies, and a -`Stream` that cannot load them fails to open. There is no fallback to a system -trust store: an embedded target may not have one, and on a host that store is a -far larger trust base than a device reporting to a single collector needs. +### Accept a peer authorised by certificate fingerprint + +RFC 5425 §5.1 requires that a peer can be authorised by its certificate +fingerprint, not only by a chain to a trust anchor and a name. The two are +different tools: a fingerprint pins one certificate, which suits a closed network +with no PKI, where issuing and rotating a CA is more machinery than the +deployment wants. + +The accepted form is the one RFC 5425 §4.2.2 defines: an ASCII hash label, a +colon, then the hash of the DER-encoded certificate as colon-separated uppercase +hex pairs. Labels come from the IANA +[Hash Function Textual Names](https://www.iana.org/assignments/hash-function-text-names/hash-function-text-names.xhtml) +registry and are hyphenated, so `sha-256` and `sha-1` rather than `sha256` or +`sha1`. A `Stream` accepts both of those algorithms. §4.2.2 makes SHA-1 mandatory +to support; `sha-256` is the one to configure where the collector offers a +choice. + +A **list** of fingerprints is accepted, and any one of them authorises the peer. +A fingerprint covers the whole DER certificate, so it changes every time the +collector's certificate is renewed. Pinning the old and the new together is what +lets a fleet cross a renewal without every device stopping at once. + +Where fingerprints are configured and the peer's certificate matches none of +them, the connection stops, whatever the chain says. + +**A pin excuses the chain, not the clock.** A pinned certificate outside its +validity period is refused, in fingerprint-only mode exactly as in any other. A +pin says which certificate is expected, not that an expired one has become +acceptable. The two checks are independent: matching a pin settles which peer +this is, and the validity period settles whether its certificate is still one +the issuer stands behind. + +**Pinning makes the collector's expiry a fleet-wide event.** Every device pinned +to a certificate stops at the same `notAfter`, and a device that missed the pin +update stays stopped. Where the deployment controls its own CA and the collector +certificate is not otherwise constrained, RFC 5280 §4.1.2.5 provides for a +certificate with no well-defined expiry, `99991231235959Z`, and gives an embedded +device as its worked example. Where it does not, pin the next certificate +alongside the current one before the renewal rather than after it. A configured +store turns a missed renewal into delayed delivery rather than lost delivery, up +to the point the store fills. ### Treat endpoint identity as declared, not assumed The integrator declares the peer identity they expect. A `Stream` verifies it when one is declared, accepts an explicit decision not to check a name, and -reports when nothing was declared at all — because that last case is a peer that +reports when nothing was declared at all - because that last case is a peer that is chain-verified but otherwise unidentified, which is the case an attacker with any trusted certificate walks through. BCP 195 §7.1 puts it plainly: without the name check, TLS proves the certificate is valid and that the peer holds its key, but not that you reached the endpoint you wanted. -The three states, and what each means, are documented on each platform's -configuration field. +A configured fingerprint is itself a declaration of identity, and a stronger one +than a name: it names the exact certificate expected rather than a subject within +a trusted CA's namespace. So a `Stream` given fingerprints and no name does not +report an unidentified peer. -### Accept a peer authorised by certificate fingerprint +The states, and what each means, are documented on each platform's configuration +field. -RFC 5425 §5.1 requires that a peer can be authorised by its certificate -fingerprint, not only by a chain to a trust anchor and a name. The two are -different tools: a fingerprint pins one certificate, which suits a closed network -with no PKI, where issuing and rotating a CA is more machinery than the deployment -wants. +### Obtain credentials per connection, and announce when they are released + +Trust anchors, the client credential and the expected peer identity are obtained +when a connection is made, and the `Stream` says when it has finished with them. +Where credentials come from is the integrator's choice, and each platform +documents the mechanism it offers: a file, a caller-built handle, a secure +element, an encrypted store. -**No shipped platform meets this today** — see [#753](https://github.com/cososo-ltd/solid-syslog/issues/753). +Two things follow, and one thing does not. + +**A device issued new credentials while it is running uses them on its next +connection** without being restarted. Forcing that reconnection with +`SolidSyslogSender_Disconnect` makes it immediate. + +**The window in which the integrator must keep material intact is the +connection**, not the lifetime of the stream. That is the point of announcing the +release: replacing material a stream is still holding is a use-after-free, and an +integrator should not have to infer when it is safe. + +**It does not follow that the material is out of RAM for most of the time.** How +much it buys depends on two things the contract cannot settle. The first is how +long a connection lasts, which is covered below. The second is the credential +source: one that hands over a pointer to something the integrator already holds +parsed has nothing to release, whereas one that parses on demand and wipes on +release does. Read the platform page for what the source you are wiring actually +does. + +What the underlying TLS library holds during a connection is a property of that +library rather than of this contract. Each keeps the parsed certificate and key +for the duration of the session, the private key included, and a `Stream` that +cleared them mid-session would be defeating its own handshake. + +The expected identity travels with the destination. Where the destination can be +changed at runtime, redirecting a device to a different collector must carry the +identity its certificate is checked against, or the redirection quietly moves the +device to a peer nobody is verifying. + +### A connection is long-lived, and bounding it is yours + +A `Stream` opens on the first record that needs it and stays open. It closes when +a send fails, when the destination changes, when the integrator calls +`SolidSyslogSender_Disconnect`, or when the stream is destroyed. There is no idle +timeout and no maximum lifetime, because a syslog client that reconnects on a +timer costs a handshake each time and gains nothing for a device that logs +steadily. + +That is the right default and it has a consequence worth stating rather than +leaving to be discovered: **on a device that logs continuously, one connection +may last for the device's uptime, and the credential material stays resident for +all of it.** The private key is needed once, to sign during the handshake; it is +retained for the rest because neither TLS library offers a client a way to hand it +back. + +Bounding that window is the integrator's to do, and +`SolidSyslogSender_Disconnect` is how. A deployment that wants the material +resident for minutes rather than months disconnects on its own schedule; the next +record reconnects and the credential source is asked again. The same lever serves +RFC 5425 §4.4's requirement that a sender close a connection it does not expect to +carry more messages. + +Where the key must not be in application memory at all, that is a property of the +credential source rather than of the window: a source backed by a secure element +or a hardware key store never hands the key over in the first place. ### Report a partially configured client credential @@ -102,7 +225,7 @@ it is reported at the same point. Left to the handshake, it comes back as a rejection from the collector, which sends the integrator looking at the collector for a fault that is on the device. -Delivery continues. The receiver is the enforcement point for our credential — a +Delivery continues. The receiver is the enforcement point for our credential - a collector that requires a client certificate will refuse the handshake, and one that does not was never going to check. Blocking here would deny the audit trail without changing what the collector decides. @@ -116,40 +239,26 @@ the deployment is held to, and neither is knowable here. For a deployment with no policy of its own, RFC 9662 §4 asks that `TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256` be offered, and BCP 195 §4.2 recommends -the same shape — ECDHE with AES-GCM — for TLS 1.2. Both prefer it over the 2009 +the same shape - ECDHE with AES-GCM - for TLS 1.2. Both prefer it over the 2009 mandatory suite, which offers no forward secrecy. -Since no ceiling is set, the version negotiated may be later than the floor, and a -policy that binds only up to the floor does not bind the connection in use. +Since no ceiling is set, the version negotiated may be later than the floor, and +a policy that binds only up to the floor does not bind the connection in use. Passing the integrator's choice through means passing it through for whichever version is negotiated. Where the library does not allow it, its own defaults apply. What each platform can and cannot select is on its page. -### Take rotated credentials and a changed identity on the next connection +### Do not resume a session under weaker terms -A device issued new credentials while it is running uses them without being -restarted. Trust anchors, the client credential and the expected peer identity -are read when a connection is made, not remembered from when the stream was -created, so replacing them and reconnecting is all it takes. Forcing that -reconnection with `SolidSyslogSender_Disconnect` makes it immediate. +A resumed session carries the security parameters of the session it resumes, so a +`Stream` that resumes must check those parameters against what the current +configuration requires and complete a full handshake instead where they fall +short. RFC 5425 §4.2.3 recommends that check; this contract requires it. -The expected identity travels with the destination. Where the destination can be -changed at runtime, redirecting a device to a different collector must carry the -identity its certificate is checked against, or the redirection quietly moves the -device to a peer nobody is verifying. - -Each platform documents the sequence its own credential model requires, because -replacing material a stream is holding is not safe at every moment. - -### Report an unusable certificate, and keep delivering - -A certificate that is expired, not yet valid, or otherwise unusable while still -chaining to a trusted anchor is reported, and delivery continues. Clock skew is -the dominant real cause: a device without a real-time clock that boots at the -epoch, or one whose time source has been tampered with, is precisely the device -whose logs you want to keep receiving. +A `Stream` that never resumes meets this by construction, and it must not be +possible to start resuming without revisiting the check. ### Do not require revocation checking @@ -167,9 +276,15 @@ TLS implementation to implement a strategy to distrust revoked certificates, and no stream here implements one. The reasoning is above; what makes it tolerable is that the obligation moves rather than disappears. An integrator's own TLS library can be configured for CRL or OCSP, and this library neither performs that check -nor prevents it — so an assessment that needs the obligation met should say where +nor prevents it - so an assessment that needs the obligation met should say where it is met, rather than assume this library meets it. +It is also why certificate validity is enforced rather than tolerated. Chain, +fingerprint and identity checks all still reject a certificate, but they reject +the same certificate today as they did yesterday. Without a revocation check, the +validity period is the only mechanism by which a certificate that was acceptable +stops being so. + ### Bound the handshake A handshake cannot stall the servicing pass indefinitely. It runs against a @@ -185,50 +300,45 @@ collector can distinguish an orderly shutdown from a truncated session. RFC 5425 ### Check the configuration it cannot work without -A `Stream` given a configuration it has no way to use — no sleep to poll the -handshake with, no trust anchors to verify against — reports a bad configuration -and returns the Null object. It does not accept the configuration and then fail -on the first connection, and it does not dereference what is missing. What else a -given platform cannot work without is on its own page. +A `Stream` is given two kinds of thing, and each is checked at the point it can +be. + +**Wiring is checked when the stream is created.** A transport, a sleep, a source +of credentials, and whatever else the platform cannot operate without: given a +configuration missing one of these, a `Stream` reports a bad configuration and +returns the Null object. It does not accept the configuration and then fail on +the first connection, and it does not dereference what is missing. What each +platform cannot work without is on its own page. + +**Material is checked when a connection is made**, because that is when it is +obtained. Credentials that cannot be produced, a configuration naming neither +trust anchors nor fingerprints, and a fingerprint that is not well formed are all +reported then, and that connection attempt fails. The sender retries on its next +pass, so a transient source recovers on its own. This is the library-wide rule for anything that reaches the wire rather than anything specific to TLS: a failure an integrator caused at setup is reported at setup, where they are still looking. -### Report every one of these +### Report every one of these, and name the check that failed All of the above surface through the error handler rather than a return code an integrator may not read. [Error handling](error-severity.md) covers what each severity is telling you; the short form is that `CRITICAL` at create time means the `Stream` fell back to the Null object and nothing will be delivered. +Where a connection is refused, the report names which check refused it - an +expired certificate, one not yet valid, a chain that does not validate, a +fingerprint that matched nothing, a name that did not match. An integrator whose +device will not connect needs to know which of those it is, because a generic +handshake failure sends them looking at the network for a fault that is on a +certificate. + ### Key custody is yours The library holds no key material of its own and uses whatever it is given. File permissions on a private key, whether it lives in a hardware security module, and -how it is rotated are properties of your deployment, not of this contract. - -## Where this stands at 0.1.0 - -These obligations are the target, and they are not yet met uniformly. At 0.1.0 -the shipped TLS platforms diverge on several of them, and each divergence is -recorded on that platform's page and tracked as an issue. Read the page for the -platform you are wiring before you rely on any obligation above. - -Certificate validity is the one both fall short of the same way: an expired -certificate refuses the connection rather than being reported while delivery -continues. - -A partially configured client credential matters more, because the two platforms -differ. One refuses the connection, which is safe but stricter than the contract. -The other accepts it in silence and connects without the client certificate, so a -device configured for mutual TLS can run without ever presenting one. If you rely -on mutual TLS, read your platform's page before you rely on this obligation. - -Configuration checking at create time is the third shortfall, and it is not -confined to TLS. - -Fingerprint-based peer authorisation is the fourth, and both platforms fall short -of it the same way: neither offers it at all. Until -[#753](https://github.com/cososo-ltd/solid-syslog/issues/753) lands, a peer is -authorised by trust anchor and name. +how it is rotated are properties of your deployment, not of this contract. The +per-connection obligation above is what makes those choices reachable: the +`Stream` asks for material when it needs it and tells you when it is done, so +where the material rests in between is yours to decide.