Security model

What identifies a peer, who trusts whom, and how to lock both sides down. Defaults are zero-config and TOFU; everything here tightens them.

Identity is the TLS fingerprint

On the encrypted transports (quic, tls) every party — hub, agent, client — has a self-signed ED25519 certificate, auto-generated under its state dir on first start (or provisioned via --cert/--key on the hub). The SHA-256 fingerprint of that certificate is the identity: it is never sent inside messages, the connection itself is authenticated. Both encrypted listeners require client certificates (mTLS).

Who verifies whomMechanism
agent → hubpins the hub fingerprint per host:port on first contact (known_hubs); a changed hub fingerprint refuses the handshake
client → hubsame TOFU pin store, written on first tls:///quic:// dial
hub → agentrequires a client certificate; pins agent_name → fingerprint at first REGISTER (hub.db); a known name with a new fingerprint is rejected
hub → clientrequires a client certificate; the fingerprint is the ACL subject

One pin covers both quic:// and tls:// on the same host:port — the listeners share the certificate.

Plaintext transports are network-trusted

Plain tcp:// and the unix socket carry no identity: no pinning, no allowlist, no ACLs — peers there are trusted with full read and write.

  • Plain tcp binds 127.0.0.1 by default. Expose it (--listen tcp://0.0.0.0:7228) only on a trusted network or VPN; the hub warns when you do.
  • The unix socket's access control is its filesystem permissions; it is also the only place the admin role is accepted (an admin HELLO over TCP or QUIC is disconnected).

Locking down agents

Default is TOFU: the first agent to claim a name pins it. To accept only pre-authorized devices, run the hub with --require-known-agents and enroll each one authorized_keys-style:

# on the device — public, safe to share; the private key never leaves it
can-hub-agent --show-identity
9abfc913fddfe9bad4cab50ba024210d81dba02140103d5019923b29adf818e1

# on the hub host
can-hub-cli pins add truck42 9abfc913fddfe9bad4cab50ba024210d81dba02140103d5019923b29adf818e1

An unknown fingerprint is rejected with no server state created (flood-proof, unlike an approval queue). The agent keeps retrying on its backoff, so authorizing it while it retries is enough. A re-keyed device needs can-hub-cli pins delete <name> before it can pin again.

The allowlist applies to quic/tls only — plain tcp carries no fingerprint and bypasses it (see above).

Client ACLs

Defaults: any client may read every interface, none may inject. Grants override that per client and per interface:

can-hub-client --show-identity                # the subject fingerprint

can-hub-cli acl add * */* rw                  # everyone read+write everywhere
can-hub-cli acl add <fp> truck42/* ro         # this client: truck42 read-only
can-hub-cli acl add <fp> truck42/can0 none    # ...except can0, fully denied
can-hub-cli acl delete <fp> truck42/can0
can-hub-cli acl
  • Subject: a client fingerprint or *. Object: agent/iface with * on either side. Level: none (no read, no write), ro, rw.
  • Resolution is most-specific-wins with the subject dominating: a rule naming the fingerprint always beats a * rule; within the same subject rank the narrower object wins (agent/can0 > agent/* > */*). No matching rule → read yes, write no.
  • Enforcement is at OPEN (read/write denied up front) and at the frame boundary: an injected frame on a non-writable channel is dropped even if the client lied at OPEN.
  • Clients on plaintext transports carry no fingerprint and always get full access — ACLs only bind on tls/quic.

Scope

Pre-1.0: the wire protocol is version 0 and unfrozen (protocol). can-hub is designed for telemetry, diagnostics and bus replication — explicitly not for control loops over the WAN.