Skip to main content

ChainIdentity

Enum ChainIdentity 

Source
pub enum ChainIdentity<'a> {
    LegacyV1,
    V2 {
        caller_role: &'a str,
        event_tip: &'a str,
    },
    V3 {
        caller_role: &'a str,
        event_tip: &'a str,
        caller_principal: &'a str,
    },
    V4 {
        caller_role: &'a str,
        event_tip: &'a str,
        caller_principal: &'a str,
    },
}
Expand description

Immutable fields hashed into the chain. Status is intentionally absent — see module docs.

§Security contract — chain-content immutability

Every field in this struct is captured once at INSERT time and baked into chain_hash = ed25519_sign(canonical(self) || prev_chain_hash, key). After the row is written the hash is a one-time commitment: no field in this struct may ever be mutated in place.

summary is the field most likely to attract a future “let me just fix that typo” API. Do not add an update_summary (or similar) function. If a correction is genuinely needed, choose one of the two safe options:

  1. Insert a corrective row — a new transaction row that references the original transaction_id in its own summary, leaving the original row and its chain hash untouched.
  2. Extend the chain protocol — introduce a dedicated amendment record type with its own chain link, so that both the original commitment and the correction are auditable.

Any other approach silently breaks chain integrity: verify_chain will flag the modified row as Broken because the stored signature will no longer verify against the row’s content.

The canonical serialisation is stable across SQLite/Postgres backends. Each field is emitted as

    <tag-name> 0x1E <tag-value> 0x1F

where 0x1E is the tag/value separator within a single field and 0x1F is the field separator that terminates the field and introduces the next one. We use the ASCII C0 byte values RS (0x1E) and US (0x1F) because they are guaranteed not to appear in any normal text field, but our role assignment is the inverse of the ASCII C0 convention (where RS = “record separator” and US = “unit separator”). The names are kept in the source for byte-level traceability against the canonical buffer, not as a claim about ASCII semantics.

Inside a value, the four bytes \\, NUL, RS, US are escaped to \\\\, \\0, \\1E, \\1F respectively. The escape table is prefix-free (every escape starts with \\), so any value can be injected without ambiguity. See push_field for the implementation and tests for the round-trip. Which generation of the canonical row encoding a row was signed under.

§Why this is an enum and not two Option fields

Adding a field to ChainContent changes the signed message, so every row written before the change would re-encode differently and report as Broken. Chains already exist in the field, and “delete your audit log to upgrade” is not an acceptable migration for an audit log. The stored chain_version column therefore selects the encoding per row: legacy rows keep the exact bytes they were signed over, new rows carry the identity fields.

A downgrade is not a hiding place. Rewriting a V2 row as LegacyV1 (to erase caller_role) makes verification re-encode it without the identity fields, so the stored signature — made over the V2 message — no longer verifies and the row reports as Broken.

Variants§

§

LegacyV1

chain_version = 1. Written before the identity migration.

§

V2

chain_version = 2. Binds the row to the authenticated caller and to the approval-event chain tip at insert time.

Fields

§caller_role: &'a str

Role the daemon resolved for the connection that requested this action (SO_PEERCRED for Unix sockets, token for vsock). Signing it lets an auditor answer which privilege tier asked; naming the account is v3’s caller_principal.

§event_tip: &'a str

chain_hash of the last EventRow at insert time, or "" when the event chain is empty. This is the cross-chain binding: because checkpoints anchor the transaction chain tip, a committed event_tip transitively anchors the event chain, so deleting approval events below it becomes detectable.

§

V3

chain_version = 3. Everything v2 binds, plus the individual account.

v2 answers “an Admin did this”. On a host with two members of sysknife-admin their signed records were indistinguishable, so the trail could not answer the first question an investigation asks. v3 signs a principal as well.

Fields

§caller_role: &'a str

As v2.

§event_tip: &'a str

As v2.

§caller_principal: &'a str

Scheme-prefixed identity of the caller, produced by crate::auth::CallerPrincipal: uid:1000 for a Unix-socket peer whose credentials the kernel attested, token:vsock for a vsock connection authenticated by the pre-shared token, and none:unattributed when the daemon could establish neither.

The scheme is signed along with the value on purpose. An auditor must be able to tell a kernel-attested account from a shared secret that any holder could have presented, and a bare string would erase that difference.

§

V4

chain_version = 4. The same field set as v3 — the change is in what the request_hash column holds: an HMAC-SHA256 keyed by the audit key instead of an unsalted SHA-256 (#268). The version bump exists so a row tells an auditor which scheme its request_hash was written under: on a v1..v3 row the column is still offline-crackable for low-entropy parameters, on a v4 row it is not.

Fields

§caller_role: &'a str

As v2.

§event_tip: &'a str

As v2.

§caller_principal: &'a str

As v3.

Implementations§

Source§

impl ChainIdentity<'_>

Source

pub fn version(&self) -> u32

chain_version column value for this encoding.

Trait Implementations§

Source§

impl<'a> Clone for ChainIdentity<'a>

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl<'a> Copy for ChainIdentity<'a>

Source§

impl<'a> Debug for ChainIdentity<'a>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<'a> Eq for ChainIdentity<'a>

Source§

impl<'a> PartialEq for ChainIdentity<'a>

Source§

fn eq(&self, other: &Self) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl<'a> StructuralPartialEq for ChainIdentity<'a>

Auto Trait Implementations§

§

impl<'a> Freeze for ChainIdentity<'a>

§

impl<'a> RefUnwindSafe for ChainIdentity<'a>

§

impl<'a> Send for ChainIdentity<'a>

§

impl<'a> Sync for ChainIdentity<'a>

§

impl<'a> Unpin for ChainIdentity<'a>

§

impl<'a> UnsafeUnpin for ChainIdentity<'a>

§

impl<'a> UnwindSafe for ChainIdentity<'a>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

Source§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
Source§

impl<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

Source§

fn equivalent(&self, key: &K) -> bool

Checks if this value is equivalent to the given key. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more