summaryrefslogtreecommitdiff
path: root/src
Commit message (Collapse)AuthorAgeFilesLines
* ipcpd: Add congestion avoidance unit testsDimitri Staessens3 days6-0/+3653
| | | | | | | | | | | | | | | | | | | | | | | | | Cover the shared per-aggregate contexts, the mb-ecn policy and the link capacity estimator. The controller tests pin the invariants of the algorithm: the first mark is reported immediately, the receiver's congestion mean is independent of packet rate and its window adapts to hold a fixed sample count, increase and decrease are invariant under control cadence, a starved sender still cuts and later recovers, signals age out on rate-relative horizons, capacity feedback derives the rate floor and recovery slope with clamps and a staleness fallback, and the pacer bounds the burst after idle. The estimator tests drive synthetic arrival traces: the capacity code survives a round trip within its resolution, hops combine by MIN, busy-period drain measures the link rate, windows extend on slow links, sparse idle observations are tolerated where unsaturated windows are rejected, shaped links measure at the shaped rate, stale windows are discarded, and windows bordering an empty queue can only lower the estimate. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* ipcpd: Rework congestion avoidanceDimitri Staessens3 days20-311/+1539
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Congestion avoidance is a property of the layer, orthogonal to ARQ and to flow control: FRCP retransmits and lets the peer pace the sender, per flow, end-to-end; the IPCP paces path aggregates. Each signal means one thing: a loss triggers a retransmission, a mark means congestion, the peer window means a slow receiver. Every flow is paced by the same rate law whatever its QoS, so a greedy raw sender shares a bottleneck fairly with a reliable stream. The unit of control is the (destination address, QoS cube) aggregate: all flows toward that destination share one controller and one rate; a start-time fair-queuing pacer divides the rate across them by deadline instead of blocking the send path, and a new flow rides the aggregate's estimates at its current rate, with no probing of its own. Slow start runs once per aggregate. The congestion signal is a multi-bit magnitude: forwarders mark packets with their standing queue depth, MAX-combined across hops, so a packet carries the deepest queue on its path. The receiver feeds back a time-integral mean over a window that adapts to the flow's byte rate, measuring a slow flow with the same fidelity as a fast one. The sender runs AIMD scaled by elapsed wall-clock time, which makes the steady-state allocation RTT-independent. The PCI gains one byte: the path capacity as a quarter-log2 code. Forwarders estimate their egress rate from busy-period drain and MIN-stamp the byte, the receiver returns the window minimum with its feedback, and the sender scales its rate floor and additive slope to the bottleneck (C / 32). A deep cut implies a backlogged bottleneck and a backlogged bottleneck advertises its capacity, so the scaled floor is live exactly when recovery needs it: the probe heals a halving in seconds at any link rate, and the floor bounds the deepest hole to a factor 32 below the bottleneck. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* tools: Raise ocbr server thread pool to 64Dimitri Staessens3 days1-1/+1
| | | | | | | 10 threads is not enough to deeply test congestion avoidance. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* irmd: Fix congestion config from fileDimitri Staessens3 days1-1/+1
| | | | | | | The configfile logic was matching "lfa" instead of "mb-ecn". Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* irmd: Reject rekey without KEX configDimitri Staessens3 days3-0/+87
| | | | | | | | A rekey without KEX config should be rejected rather than downgrading to plaintext. Tests added. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* lib: Whitespace cleanup in dev and frctDimitri Staessens3 days2-11/+21
| | | | | Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* tools: Key oftp cleanup on path argumentsDimitri Staessens3 days1-5/+9
| | | | | | | | The cleanup guards compared the stream against the stdin/stdout globals, which tripped the clang static analyzer. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* irmd: Fix oap_test suppression listDimitri Staessens3 days1-2/+8
| | | | | | | | he crypto-disabled branch of the test driver referenced two tests that were folded into test_oap_ts_reject_all() and missed newer ones. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* lib: Fix crypt_test without crypto backendDimitri Staessens3 days1-0/+8
| | | | | | | | | Creating a context without OpenSSL fails (returns NULL) after adding the key rotation logic (requires HKDF). Assert instead that context creation returns NULL without OpenSSL. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* irmd: Avoid in-place byteswap in OAP headerDimitri Staessens3 days1-3/+3
| | | | | | | | | | gcc 12 -fanalyzer misclassifies kex_len = hton16(kex_len) followed by memcpy(&kex_len) as a read of an uninitialized value. Write the swapped value through the scratch variable like every other field in write_oap_fixed(). Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* irmd: Harden OAP client KEM completionDimitri Staessens3 days1-19/+18
| | | | | | | | | | | | | do_client_kex_complete_kem() filled key_buf in the server-encap branch and returned early in the client-encap branch; any other mode value would fall through and memcpy an uninitialized stack buffer into the session key. The client only ever sets the two known modes today, but nothing local enforces that and the server derives its mode from a wire bit. Give each mode an explicit path with no fall-through. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* irmd: Fix strdup error checks in registryDimitri Staessens3 days3-2/+6
| | | | | | | | | | reg_proc_add_name() and reg_prog_add_name() checked the already validated entry pointer instead of the fresh strdup. Found by gcc static analyzer. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* ipcpd: Fix NULL deref in DHT cleanupDimitri Staessens3 days1-1/+1
| | | | | | | | | | | | | | The dht_kv_find_node_rsp_msg function steals *contacts and NULLs it on success. When the subsequent msg->val allocation fails in dht_kv_find_value_rsp_msg(), the stolen contacts are freed along with the message, and do_dht_kv_find_value_req() is left with contacts == NULL while n_contacts is still positive: its fail_vals loop then dereferences NULL. Found by clang static analyzer. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* ipcpd: Fix unreachable code in dt_initDimitri Staessens3 days1-1/+1
| | | | | | | | With IPCP_FLOW_STATS disabled, the bmp_destroy() in the dt_init() failure handling was unreachable. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* ipcpd: Store local names before DHT peeringDimitri Staessens14 days1-1/+6
| | | | | | | | | | | A name registered before the DHT had a peer stayed unpublished until it neared its expiry deadline. Make a never-stored local value (t_repl == 0) eligible for replication immediately, and flush replication once when the worker starts so such names are published as soon as the DHT is enrolled. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* ipcpd: Fix DHT bootstrap re-seedingDimitri Staessens14 days1-1/+45
| | | | | | | | | | The bootstrap seeder reported success as soon as the query was sent. Only skip seeding once the peer is in a bucket, and return -EAGAIN until it actually appears so the caller keeps retrying rather than assuming bootstrap completed. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* ipcpd: Report eth socket egress queue in RIBDimitri Staessens14 days1-1/+7
| | | | | | | | Add the raw socket's SIOCOUTQ egress backlog to the eth RIB and relabel the existing FIONREAD RIB entry as ingress. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* ipcpd: Minimize ipcpd-eth send bufferDimitri Staessens14 days1-6/+13
| | | | | | | | The eth send buffer needs to block quickly so we can mark congestion correctly in the upper layer buffers. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* lib: Fix locking on flow readDimitri Staessens14 days1-9/+13
| | | | | | | | | | Reading a deallocated flow could cause an assert on a negative id. The raw-flow path now holds proc.lock across flow_rx_spb rather than dropping it first, and the FRCT loop no longer releases and reacquires the lock on every iteration. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* lib: Prefer writers on the proc flow lockDimitri Staessens14 days1-4/+18
| | | | | | | | | | FRCT readers hold proc.lock in a rdlock loop and could starve a pending flow accept waiting for the wrlock. Initialise the lock with the writer-nonrecursive kind on Linux with glibc, so writers are not indefinitely delayed. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* lib: Clean up on cancelled blocking readDimitri Staessens14 days2-4/+20
| | | | | | | | | Cancelling a thread that was blocked in ssm_rbuff_read_b/write_b didn't clean up the n_users reference. If it was in flow_rx_one it didn't unlock the process lock. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* lib: Separate rekey replay from epoch conflictDimitri Staessens14 days3-16/+42
| | | | | | | | | | | | | keyrot_rekey() treated any re-key attempt against a live epoch (current or previous) the same way, whether the offered root key matched the live one (a replay) or was genuinely different (a conflict). Compare the offered key against the live batch's root and return -EREPLAY for a match, keeping -1/-ECRYPT for an actual conflict, so callers can handle replayed re-keys distinctly from real ones. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* irmd: Move AUTH test macros to OAP common.hDimitri Staessens14 days3-25/+23
| | | | | | | | | | The ML-DSA OAP test defined CLI_AUTH/NO_CLI_AUTH and passed them for the srv_auth parameters of the re-key tests. Rename to the neutral AUTH/NO_AUTH already used by oap_test.c and move the pair to the shared common.h. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* irmd: Test OAP KEM re-key floorDimitri Staessens14 days1-0/+126
| | | | | | | | | Add tests for two client-encap KEM properties the server enforces: reject a client KDF below the server floor, and confirm a re-key forces ephemeral server-encap. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* irmd: Expand and parametrize OAP testsDimitri Staessens14 days4-182/+554
| | | | | | | | | | Parametrize the re-key roundtrips over the full (srv_auth, cli_auth) matrix. Add a KEM re-key axis, request-ID and cleartext-echo tamper tests, and an unsupported cipher/kdf/digest NID cluster guarded by an assertion. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* lib: Harden crypto unit testsDimitri Staessens14 days3-5/+103
| | | | | | | | | | | | | | Stop the non-OpenSSL runners from masking a real failure as SKIP by only downgrading to SKIP if they passed. Fix a stray call that ran test_kex_dhe_wrong_algo in the skip branch. Fix double free + free of an uninitialised pointer in test_store_add. Test that a KDF- less derive must fail. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* irmd: Fix OAP re-key header encodeDimitri Staessens14 days4-9/+27
| | | | | | | | | | | | | The oap_hdr_encode() function validated its own output by re-decoding with rekey hardcoded false. The case where the client signs a re-key failed its own self-check because the cert is not sent again. Now uses a flag on the encode() to signal the signed re-key without certificate. Also fixes a double free after a failed self-decode check. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* lib: Reject re-key to a live epochDimitri Staessens14 days2-12/+177
| | | | | | | | A re-key epoch arrives from the peer, so we need to reject duplicates to avoid two batches sharing a wire epoch with different keys. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* build: Fix OpenSSL version check for PQCDimitri Staessens14 days9-12/+10
| | | | | | | | | ML-KEM, ML-DSA and SLH-DSA all landed in OpenSSL 3.5.0, not 3.4.0. ML-KEM+ML-DSA and SLH-DSA can be disabled by DISABLE_ML / DISABLE_SLH. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* build: Make re-key watermark configurableDimitri Staessens2026-06-292-51/+8
| | | | | | | | | | KEY_REKEY_WM_CHECK_BITS now controls the watermark check, once per 2^n flow writes (FLOW_WM_CHECK in dev.c). Now the configuration check (check watermark < outstanding packets) is contained in lib.cmake instead of assuming the value of FLOW_WM_CHECK. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* lib: Wipe unpacked session key after copyDimitri Staessens2026-06-291-0/+4
| | | | | | | | | | Zeroize the symmetric key in the unpacked IRM message once it has been copied into the crypt context, in both flow__irm_result_des and flow_rekey__irm_result_des, so key material does not linger in the protobuf buffer until it is freed. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* oap: Rename kex_config loaders to sec_configDimitri Staessens2026-06-298-130/+130
| | | | | | | | | | Leftover from the kex_config->enc_config->sec_config rename. Finished by renames load_kex_config and the cli/srv variants to load_sec_config, the kcfg variables to scfg, and fixes the "KEX config"/"encryption config" comments. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* irmd: Harden OAP handshake and add cert-less re-keyDimitri Staessens2026-06-2911-239/+1334
| | | | | | | | | | | | | | | | | | | | | | Adds support for: Server key confirmation: the session key is bound to the negotiated algorithm via the HKDF info. The server returns a key-confirmation tag (rsp_tag, replacing the bare request-hash echo), so a cipher downgrade or key desync is detected. The cleartext path keeps a request echo, compared in constant time. Sealed server identity: AEAD-seal the certificate, signature and piggybacked data in the encrypted response (kex and rsp_tag move ahead as AAD), hiding the server identity and response sizes. Cert-less re-key: let the client omit its certificate, verifying the peer against the cached certificate. On PQC flows, ephemeral server-encap KEX (preserving forward secrecy) is used, even if the original flow allocation was client-encap. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* irmd: Reject unsupported OAP kex and cipherDimitri Staessens2026-06-291-2/+7
| | | | | | | | | Validate the configured key exchange and cipher against the supported ranks (returning ENOTSUP) instead of only rejecting an undefined cipher NID, so an unsupported algorithm is caught at config load. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* lib: Bound crc_check against short packetsDimitri Staessens2026-06-291-2/+8
| | | | | | | | | Reject a packet shorter than head_skip + CRCLEN before computing the CRC, instead of hashing over an underflowed length when the buffer is too small to hold the trailer. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* irmd: Complete bidirectional flow re-keyingDimitri Staessens2026-06-299-261/+1111
| | | | | | | | | | | | | | | | | | Extend re-key delivery beyond the locally-initiated watermark path: Handle peer-initiated re-key requests, allowing one request and one response per flow at a time. The client side wins if both ends try to re-key at the same time. Caches the peer certificate to support cert-less authenticated/signed re-keys. After a rekey, the initiator promotes first (timer) and starts sending under the new key. The responder observes the new key (peer_synced) and then the responder promotes. The responder will self-decide to use the new keys if it exhausted the older set in the case where it never sees the peer (unidirectional flow). Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* lib: Add constant-time comparison helperDimitri Staessens2026-06-291-0/+19
| | | | | | | | | Add a function crypt_ct_cmp() that wraps CRYPTO_memcmp (OpenSSL) with a volatile-loop fallback, for comparing authentication tags without leaking timing. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* lib: Group sec_config authentication fieldsDimitri Staessens2026-06-296-26/+26
| | | | | | | | | Nest the flat req_auth and cacert members of struct sec_config into a sub-struct a { req; cacert; }, keeping the authentication settings together. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* config: Rename enc.conf to sec.confDimitri Staessens2026-06-296-27/+27
| | | | | | | | | The per-name config file holds more than encryption settings (KEX, cipher, KDF, peer authentication), so renamed it from enc.conf to sec.conf and the code internals to match. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* lib: Harden OpenSSL crypto backendDimitri Staessens2026-06-292-69/+99
| | | | | | | | | | | | | | | This contains a few improvements and fixes in the OpenSSL backed. We now zeroize shared secrets with OPENSSL_clear_free. The i2d_PUBKEY output is bound against CRYPT_KEY_BUFSZ. We now return NULL rather than silently falling back to SHA-256 when a digest is unknown. FILE handles are now closed via pthread cleanup so a cancelled thread cannot leak them. The DHE kex tests now set the KDF NID explicitly, since the SHA-256 fallback is gone. Also refactors the PEM string loaders to clean up some code duplication. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* lib: Loop getrandom on short reads and EINTRDimitri Staessens2026-06-291-1/+17
| | | | | | | | | The getrandom() function may return fewer bytes than requested or fail with EINTR. Loop until the buffer is filled, retrying on EINTR, instead of trusting a single call to fill the buffer completely. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* ipcpd: Use hash_mix64 for pft keysDimitri Staessens2026-06-296-32/+7
| | | | | | | | | Drops the per-table hash_key flag and the local MD5 hash() helper. The calc_key function now folds the key with hash_mix64 unconditionally. Simplifies pft_create, which no longer takes a bool. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* lib: Add MurmurHash3 hash_mix64 for hash tablesDimitri Staessens2026-06-292-0/+50
| | | | | | | | | Adds a (non-cryptographic) MurmurHash3 fmix64 finalizer for hashing an integer key to a table index, replacing the MD5-based bucket hashing in the pft. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* irmd: Deliver flow re-keyingDimitri Staessens2026-06-2911-106/+1269
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Re-key each encrypted flow's batch root periodically so a long-lived flow never exhausts or over-uses a single root. The IRMd re-runs the OAP exchange with the peer IRMd over the flow-update relay. The per-flow re-keying state is tracked in the registry (reg_flow). A re-key delivers one root seed from the OAP exchange. keyrot immediately HKDF-expands it into 128 node keys (KR_NODES_SZ = 128 × 32 B) and wipes the root. Then each of the 128 node keys is itself a root → HKDF-expanded into 64 (2^KEY_NODE_BITS) leaf keys, forked per direction; each leaf key is the actual AEAD key, good for 2^20 packets (the low counter bits are its nonce/seq). If the number of keys runs low, a re-key will be triggered (KEY_REKEY_WATERMARK). The rekey is signalled out of band to the application. The rbuff ACL is generalized into a flags word, so an RB_REKEY bit rides alongside the access RB_RD/RB_WR and FLOWDOWN/FLOWPEER bits. The RD and WR bits are revised ditching the fcntl historical weirdness. The seed is pulled via flow_read/flow_write, installed with crypt_rekey(). TX holds the old epoch until the peer is observed on the new one (or a grace deadline elapses), promoted from both the read and write paths so a recv-mostly flow still advances. Also fix the FLOW_ACCEPT and FLOW_ALLOC handlers, which on a key-buffer allocation failure returned from inside the cleanup-push region: that leaked the reply message and skipped both the stack-key scrub and the cleanup pop. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* ipcpd: Add flow-update relayDimitri Staessens2026-06-2914-7/+483
| | | | | | | | | | | | This adds an ipcp_flow_update() call to relay opaque messages between the two IRMds (carried by FLOW_IRM_UPDATE messages), which passes it back up to the peer IRMd via ipcp_flow_update_arr(). The broadcast layer does not implement this. Needed for periodic re-keying of encrypted flows via OAP. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* lib: Harden symmetric-key rotationDimitri Staessens2026-06-2912-523/+2752
| | | | | | | | | | | | | | | | | | Flow crypto signalled rotation with a single phase-parity bit, so a loss burst that hid an even number of rotations went unnoticed and wedged the flow for good. Each packet now carries a small cleartext selector naming its key directly, so a receiver that falls behind recovers on the next packet instead of getting stuck. The selector also serves as the AEAD nonce and is authenticated as associated data (AAD). Key rotation moves into a new backend-agnostic keyrot module that rotates sub-keys to bound AEAD usage while preserving forward secrecy. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* irmd: Fail OAP config load on read errorsDimitri Staessens2026-06-292-4/+15
| | | | | | | | | | | load_sec_config_file() treated any fopen() failure as an absent config and silently disabled encryption. file_exists() similarly lumped non-ENOENT stat() errors in with "present". Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* irmd: Bound the OAP replay cacheDimitri Staessens2026-06-293-54/+263
| | | | | | | | | | | | | | | | | Replace the linked-list cache with three timestamp-generation hash buckets, each capped at OAP_REPLAY_MAX entries. A bucket is an open-addressed hash set whose slots count as live only while slot.gen equals the bucket generation, so a stale bucket clears in O(1) by bumping its generation instead of being scanned and pruned. On overflow the cache fails closed - it rejects the header rather than evicting the oldest entry. Under flood, dropping a genuine entry would let that header be replayed, so refusing new entries is the safer degradation than evict-oldest (fail-open) behaviour. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* irmd: Reject OAP peer crt with unusable CNDimitri Staessens2026-06-294-43/+100
| | | | | | | Added checks for CN > NAME_SIZE. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* irmd: Add issuer and digest pinning to OAPDimitri Staessens2026-06-2916-45/+654
| | | | | | | | | | | | | | | | | | | A peer certificate that verifies against the CA store could have been issued by any trusted CA, and a peer could pick any supported digest for its signature. Tighten the authentication contract with two local policies. cacert= pins the issuing CA: a peer certificate, if presented, must chain through the pinned CA. Whether a certificate is mandatory at all remains controlled by auth= alone. digest= now also pins the signature digest: a classical peer must sign with the locally configured digest, and may not omit the digest NID to fall back to the key's default digest. PQC signatures (ML-DSA, SLH-DSA) have an intrinsic digest and may be NID_undef. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>