macula_kem_keyring (macula v13.2.0)
View SourceA node's KEM keys (E2E design, Amendment A1), one current key per node identity, shared by every pool of that identity in the VM.
A provider's advertisement carries its current key, which a caller seals requests to. The keys live only in memory: none is ever written to disk, and a restart makes new ones. Every rotate_after_ms (24 hours) a node gets a new current key. The key it replaces still opens calls for retain_ms (30 minutes), which covers the last advertisement that named it (5 minutes), the clock tolerance (5), the longest deadline (10) and admission's tolerance past a deadline (5), and is then deleted. A stolen key therefore opens at most about 24.5 hours of calls.
This process owns the key table and runs under macula_root. Callers read the table directly, so opening a sealed request never waits on this process; only making, rotating and deleting keys go through it. The table is protected, and no private key is ever in this process's state, so no crash report or state dump can print one.
Precondition: one node identity runs in one VM. Two VMs of one identity would each advertise their own key (see the design's Amendment A1).
Summary
Functions
The node's current key and its id, for its advertisements. A renewal reads it when it signs, so a rotated key reaches the next advertisement.
As current/1, from the key table Table.
Make sure the node NodeId has a current key in Profile. A node that has one keeps it, so every pool of one identity shares it.
As ensure/2, in the key table Table.
What the node opens sealed requests with: a lookup of a key it holds by the key's id, and the id of its current key, which a refusal names (macula_sealed_call:holder()). The lookup reads the table when it runs, so a key deleted since answers error.
As holder/1, from the key table Table.
Give the node a new current key now; the one it replaces opens calls until it is retired.
As rotate/1, in the key table Table.
Types
-type current() :: #{key_id := <<_:64>>, key := binary()}.
-type options() :: #{table => atom(), rotate_after_ms => pos_integer(), retain_ms => pos_integer()}.
A node's current key, as its advertisement carries it, and that key's id.
Functions
-spec current(<<_:256>>) -> {ok, current()} | error.
The node's current key and its id, for its advertisements. A renewal reads it when it signs, so a rotated key reaches the next advertisement.
As current/1, from the key table Table.
-spec ensure(<<_:256>>, macula_seal:profile()) -> ok.
Make sure the node NodeId has a current key in Profile. A node that has one keeps it, so every pool of one identity shares it.
-spec ensure(atom(), <<_:256>>, macula_seal:profile()) -> ok.
As ensure/2, in the key table Table.
-spec holder(<<_:256>>) -> {ok, macula_sealed_call:holder()} | error.
What the node opens sealed requests with: a lookup of a key it holds by the key's id, and the id of its current key, which a refusal names (macula_sealed_call:holder()). The lookup reads the table when it runs, so a key deleted since answers error.
-spec holder(atom(), <<_:256>>) -> {ok, macula_sealed_call:holder()} | error.
As holder/1, from the key table Table.
-spec rotate(<<_:256>>) -> ok | error.
Give the node a new current key now; the one it replaces opens calls until it is retired.
-spec rotate(atom(), <<_:256>>) -> ok | error.
As rotate/1, in the key table Table.