summaryrefslogtreecommitdiff
path: root/src/irmd/reg
diff options
context:
space:
mode:
authorDimitri Staessens <dimitri@ouroboros.rocks>2026-06-14 16:16:03 +0200
committerSander Vrijders <sander@ouroboros.rocks>2026-06-29 08:32:58 +0200
commitfdb50b8256f1038d5bc4f906b41605cacc769bf4 (patch)
tree8962c4188a208f81e3cdba39cc54a01da933d787 /src/irmd/reg
parentc386d9b7caa56f472fdce20ff5b2841ed41dd539 (diff)
downloadouroboros-fdb50b8256f1038d5bc4f906b41605cacc769bf4.tar.gz
ouroboros-fdb50b8256f1038d5bc4f906b41605cacc769bf4.zip
irmd: Deliver flow re-keying
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>
Diffstat (limited to 'src/irmd/reg')
-rw-r--r--src/irmd/reg/flow.c4
-rw-r--r--src/irmd/reg/flow.h11
-rw-r--r--src/irmd/reg/reg.c189
-rw-r--r--src/irmd/reg/reg.h34
4 files changed, 238 insertions, 0 deletions
diff --git a/src/irmd/reg/flow.c b/src/irmd/reg/flow.c
index 5c709dea..ccb2562d 100644
--- a/src/irmd/reg/flow.c
+++ b/src/irmd/reg/flow.c
@@ -24,6 +24,7 @@
#define OUROBOROS_PREFIX "reg/flow"
+#include <ouroboros/crypt.h>
#include <ouroboros/logs.h>
#include "flow.h"
@@ -32,6 +33,7 @@
#include <errno.h>
#include <stdbool.h>
#include <stdlib.h>
+#include <string.h>
struct reg_flow * reg_flow_create(const struct flow_info * info)
{
@@ -79,6 +81,8 @@ void reg_flow_destroy(struct reg_flow * flow)
{
assert(flow != NULL);
+ crypt_secure_clear(flow->rk.pending_seed, SYMMKEYSZ);
+
switch(flow->info.state) {
case FLOW_ACCEPT_PENDING:
clrbuf(flow->req_data);
diff --git a/src/irmd/reg/flow.h b/src/irmd/reg/flow.h
index 9a4046d3..15fc7b8f 100644
--- a/src/irmd/reg/flow.h
+++ b/src/irmd/reg/flow.h
@@ -49,6 +49,17 @@ struct reg_flow {
bool direct;
+ /* Tier-2 re-key state (encrypted flows only) */
+ struct {
+ bool encrypted; /* flow carries a cipher */
+ uint8_t epoch; /* last epoch installed by app */
+ bool initiator; /* OAP initiator (role 0) */
+ bool in_flight; /* a re-key is in progress */
+ uint8_t pending_seed[SYMMKEYSZ];
+ uint8_t pending_epoch;
+ bool has_pending; /* new seed awaits app pull */
+ } rk;
+
struct ssm_rbuff * n_rb;
struct ssm_rbuff * n_1_rb;
};
diff --git a/src/irmd/reg/reg.c b/src/irmd/reg/reg.c
index 365064e5..70baf64e 100644
--- a/src/irmd/reg/reg.c
+++ b/src/irmd/reg/reg.c
@@ -25,6 +25,7 @@ The IPC Resource Manager - Registry
#define OUROBOROS_PREFIX "reg"
#include <ouroboros/bitmap.h>
+#include <ouroboros/crypt.h>
#include <ouroboros/errno.h>
#include <ouroboros/list.h>
#include <ouroboros/logs.h>
@@ -2102,6 +2103,194 @@ bool reg_flow_is_direct(int flow_id)
return ret;
}
+void reg_flow_set_rekey(int flow_id,
+ bool initiator)
+{
+ struct reg_flow * flow;
+
+ pthread_mutex_lock(&reg.mtx);
+
+ flow = __reg_get_flow(flow_id);
+ if (flow != NULL) {
+ flow->rk.encrypted = true;
+ flow->rk.initiator = initiator;
+ flow->rk.epoch = 0;
+ }
+
+ pthread_mutex_unlock(&reg.mtx);
+}
+
+int reg_flow_get_epoch(int flow_id)
+{
+ struct reg_flow * flow;
+ int epoch = -1;
+
+ pthread_mutex_lock(&reg.mtx);
+
+ flow = __reg_get_flow(flow_id);
+ if (flow != NULL && flow->rk.encrypted)
+ epoch = flow->rk.epoch;
+
+ pthread_mutex_unlock(&reg.mtx);
+
+ return epoch;
+}
+
+bool reg_flow_rekey_pending(int flow_id)
+{
+ struct reg_flow * flow;
+ bool ret = false;
+
+ pthread_mutex_lock(&reg.mtx);
+
+ flow = __reg_get_flow(flow_id);
+ if (flow != NULL)
+ ret = flow->rk.has_pending;
+
+ pthread_mutex_unlock(&reg.mtx);
+
+ return ret;
+}
+
+pid_t reg_flow_get_n_1_pid(int flow_id)
+{
+ struct reg_flow * flow;
+ pid_t pid = -1;
+
+ pthread_mutex_lock(&reg.mtx);
+
+ flow = __reg_get_flow(flow_id);
+ if (flow != NULL)
+ pid = flow->info.n_1_pid;
+
+ pthread_mutex_unlock(&reg.mtx);
+
+ return pid;
+}
+
+int reg_flow_snapshot_rekey_due(struct rekey_info * snap,
+ int max)
+{
+ struct list_head * p;
+ int n = 0;
+
+ pthread_mutex_lock(&reg.mtx);
+
+ llist_for_each(p, &reg.flows) {
+ struct reg_flow * f;
+
+ if (n == max)
+ break;
+
+ f = list_entry(p, struct reg_flow, next);
+
+ if (f->info.state != FLOW_ALLOCATED || f->direct)
+ continue;
+
+ if (!f->rk.encrypted || !f->rk.initiator)
+ continue;
+
+ if (f->rk.in_flight || f->rk.has_pending)
+ continue;
+
+ f->rk.in_flight = true;
+
+ snap[n].flow_id = f->info.id;
+ snap[n].n_pid = f->info.n_pid;
+ snap[n].n_1_pid = f->info.n_1_pid;
+ snap[n].epoch = f->rk.epoch;
+ strcpy(snap[n].name, f->name);
+ ++n;
+ }
+
+ pthread_mutex_unlock(&reg.mtx);
+
+ return n;
+}
+
+void reg_flow_clear_in_flight(int flow_id)
+{
+ struct reg_flow * flow;
+
+ pthread_mutex_lock(&reg.mtx);
+
+ flow = __reg_get_flow(flow_id);
+ if (flow != NULL)
+ flow->rk.in_flight = false;
+
+ pthread_mutex_unlock(&reg.mtx);
+}
+
+int reg_flow_store_pending(int flow_id,
+ const uint8_t * seed,
+ uint8_t epoch)
+{
+ struct reg_flow * flow;
+ int ret = -ENOENT;
+
+ pthread_mutex_lock(&reg.mtx);
+
+ flow = __reg_get_flow(flow_id);
+ if (flow != NULL) {
+ memcpy(flow->rk.pending_seed, seed, SYMMKEYSZ);
+ flow->rk.pending_epoch = epoch;
+ flow->rk.has_pending = true;
+ flow->rk.in_flight = false;
+ /* Doorbell raised only after the seed is parked. */
+ if (flow->n_rb != NULL)
+ ssm_rbuff_set_bits(flow->n_rb, RB_REKEY);
+ ret = 0;
+ }
+
+ pthread_mutex_unlock(&reg.mtx);
+
+ return ret;
+}
+
+bool reg_flow_take_pending(int flow_id,
+ uint8_t * seed,
+ uint8_t * epoch)
+{
+ struct reg_flow * flow;
+ bool ret = false;
+
+ pthread_mutex_lock(&reg.mtx);
+
+ flow = __reg_get_flow(flow_id);
+ if (flow != NULL && flow->rk.has_pending) {
+ memcpy(seed, flow->rk.pending_seed, SYMMKEYSZ);
+ *epoch = flow->rk.pending_epoch;
+ flow->rk.epoch = flow->rk.pending_epoch; /* app installed it */
+ flow->rk.has_pending = false;
+ crypt_secure_clear(flow->rk.pending_seed, SYMMKEYSZ);
+ if (flow->n_rb != NULL)
+ ssm_rbuff_clr_bits(flow->n_rb, RB_REKEY);
+ ret = true;
+ }
+
+ pthread_mutex_unlock(&reg.mtx);
+
+ return ret;
+}
+
+void reg_notify_flow(int flow_id,
+ int event)
+{
+ struct reg_flow * flow;
+ struct reg_proc * proc;
+
+ pthread_mutex_lock(&reg.mtx);
+
+ flow = __reg_get_flow(flow_id);
+ if (flow != NULL) {
+ proc = __reg_get_proc(flow->info.n_pid);
+ if (proc != NULL)
+ ssm_flow_set_notify(proc->set, flow_id, event);
+ }
+
+ pthread_mutex_unlock(&reg.mtx);
+}
+
int reg_respond_flow_direct(int flow_id,
buffer_t * pbuf)
{
diff --git a/src/irmd/reg/reg.h b/src/irmd/reg/reg.h
index 6b576471..e0c64fed 100644
--- a/src/irmd/reg/reg.h
+++ b/src/irmd/reg/reg.h
@@ -163,6 +163,40 @@ int reg_wait_flow_direct(int flow_id,
bool reg_flow_is_direct(int flow_id);
+/* Per-flow snapshot for the re-key timer */
+struct rekey_info {
+ int flow_id;
+ pid_t n_pid;
+ pid_t n_1_pid;
+ char name[NAME_SIZE + 1];
+ uint8_t epoch;
+};
+
+void reg_flow_set_rekey(int flow_id,
+ bool initiator);
+
+int reg_flow_get_epoch(int flow_id);
+
+bool reg_flow_rekey_pending(int flow_id);
+
+pid_t reg_flow_get_n_1_pid(int flow_id);
+
+int reg_flow_snapshot_rekey_due(struct rekey_info * snap,
+ int max);
+
+void reg_flow_clear_in_flight(int flow_id);
+
+int reg_flow_store_pending(int flow_id,
+ const uint8_t * seed,
+ uint8_t epoch);
+
+bool reg_flow_take_pending(int flow_id,
+ uint8_t * seed,
+ uint8_t * epoch);
+
+void reg_notify_flow(int flow_id,
+ int event);
+
void reg_dealloc_flow(struct flow_info * info);
void reg_dealloc_flow_resp(struct flow_info * info);