From d408e537554a204c69f2bc27cf3ec3f5d5ff1ad5 Mon Sep 17 00:00:00 2001 From: ryan Date: Sun, 25 Oct 2026 11:12:44 +0330 Subject: [PATCH] Show the sequence diagrams as pre-rendered SVG images GoLand and Typora fail to draw the Mermaid sequence diagrams even though the same text renders on the Mermaid versions they bundle. Embed SVGs rendered with Mermaid 11.16.1 (white background) and keep the Mermaid source in a collapsed section under each image. Co-Authored-By: Claude Sonnet 5.5 --- docs/diagrams/activation-flow.svg | 1 + docs/diagrams/enrollment-protocol.svg | 1 + docs/diagrams/model-a-activation.svg | 1 + docs/diagrams/model-b-activation.svg | 1 + docs/diagrams/stage0-enrollment.svg | 1 + docs/enrollment-and-activation.md | 18 ++++++++++++++++++ docs/sn-as-username-process.md | 12 ++++++++++++ 7 files changed, 35 insertions(+) create mode 100644 docs/diagrams/activation-flow.svg create mode 100644 docs/diagrams/enrollment-protocol.svg create mode 100644 docs/diagrams/model-a-activation.svg create mode 100644 docs/diagrams/model-b-activation.svg create mode 100644 docs/diagrams/stage0-enrollment.svg diff --git a/docs/diagrams/activation-flow.svg b/docs/diagrams/activation-flow.svg new file mode 100644 index 0000000..d1adc2f --- /dev/null +++ b/docs/diagrams/activation-flow.svg @@ -0,0 +1 @@ +Board (FN session)MosquittoGo backendOperator (UI)Board (FN session)MosquittoGo backendOperator (UI)fresh X25519 pair (b2, B2), nonce n2, SAS = digits(hash(B2, n2, FN))board/[FN]/activation/request [B2, n2]pending request FN, MAC label, SAS, timechoose SN, enter activation code, confirm SAS matches the boardDB: ACTIVATING (FN, SN, next generation), unique constraintephemeral (s2, S2), shared2, pw_sn = HKDF(shared2, salt=n2, info = label, SN, generation)dynsec create or update client SN: pw_sn, role vending-device, clientid = FNboard/[FN]/activation/response [SN, next generation, S2, sig]verify sig, derive pw_sn, persist SN, pw_sn and generation, reconnectCONNECT username=SN, clientId=FN, password=pw_snfirst message on device/[SN]/... (or presence)DB: ACTIVE, disable the FN client, audit \ No newline at end of file diff --git a/docs/diagrams/enrollment-protocol.svg b/docs/diagrams/enrollment-protocol.svg new file mode 100644 index 0000000..4a8df37 --- /dev/null +++ b/docs/diagrams/enrollment-protocol.svg @@ -0,0 +1 @@ +Go backendMosquittoBoardGo backendMosquittoBoardrandom Client ID, X25519 pair (b, B), nonce n1CONNECT anonymous, clientId=random (TLS)SUBSCRIBE enroll/response/[cid] (own topic only)enroll/request/[cid] [B, n1, mac]reserve [cid], assign FN, ephemeral (s, S)shared = X25519(s, B), pw = HKDF(shared, salt=n1, info = label, FN)dynsec createClient FN, pw, clientid=FN, role board-bootstrapenroll/response/[cid] [FN, S, sig] (not retained)verify sig, shared = X25519(b, S), same pwpersist FN and pw to flash, then reconnect as FN \ No newline at end of file diff --git a/docs/diagrams/model-a-activation.svg b/docs/diagrams/model-a-activation.svg new file mode 100644 index 0000000..c02f33d --- /dev/null +++ b/docs/diagrams/model-a-activation.svg @@ -0,0 +1 @@ +Board (FN)MosquittoGo backendOperatorBoard (FN)MosquittoGo backendOperatorboard/[FN]/activation/requestpick SN, confirm code and short stringensure role machine-[SN] (literal ACLs device/[SN]/...)add roles machine-[SN] + capability roles to client FNboard/[FN]/activation/response [SN, sig]store SN, reconnect as FNuse device/[SN]/... topics \ No newline at end of file diff --git a/docs/diagrams/model-b-activation.svg b/docs/diagrams/model-b-activation.svg new file mode 100644 index 0000000..81f2515 --- /dev/null +++ b/docs/diagrams/model-b-activation.svg @@ -0,0 +1 @@ +Board (FN)MosquittoGo backendOperatorBoard (FN)MosquittoGo backendOperatornew ephemeral X25519 keypair (b2, B2)board/[FN]/activation/request [B2, n2]pick SN, confirm code and short stringephemeral (s2, S2), shared = X25519(s2, B2)pw = HKDF(shared, SN, n2, generation)create or update client SN: pw, clientId bound to board, role vending-deviceboard/[FN]/activation/response [SN, S2, sig]verify sig, derive same pw, store SN + pwreconnect as SN / pwdisable FN client (or keep for rescue / re-enrollment) \ No newline at end of file diff --git a/docs/diagrams/stage0-enrollment.svg b/docs/diagrams/stage0-enrollment.svg new file mode 100644 index 0000000..487485a --- /dev/null +++ b/docs/diagrams/stage0-enrollment.svg @@ -0,0 +1 @@ +Go backendMosquittoBoardGo backendMosquittoBoardrandom Client ID, X25519 keypair (b, B), nonce n1CONNECT (TLS, anonymous, clientId=random)enroll/request/[cid] [B, n1, mac]create FN record, ephemeral (s, S)shared = X25519(s, B), pw = HKDF(shared, FN, n1)Dynamic Security: create client FN, pw, role board-bootstrapenroll/response/[cid] [FN, S, sig] (not retained)verify sig with pinned public keyshared = X25519(b, S), same pwpersist FN and pw to flashreconnect as FN / pw \ No newline at end of file diff --git a/docs/enrollment-and-activation.md b/docs/enrollment-and-activation.md index d92dd2b..f877ce2 100644 --- a/docs/enrollment-and-activation.md +++ b/docs/enrollment-and-activation.md @@ -39,6 +39,10 @@ Mosquitto does **not** require passwords: with `allow_anonymous true` on a dedic Only the backend may subscribe to `enroll/request/+`. Devices cannot publish to response topics. +![Stage 0 enrollment sequence diagram](diagrams/stage0-enrollment.svg) + +
Mermaid source of this diagram + ```mermaid sequenceDiagram participant B as Board @@ -57,6 +61,8 @@ sequenceDiagram B->>M: reconnect as FN / pw ``` +
+ - `sig` = Ed25519 signature by the backend over `S`, `B`, `n1`, and the Client ID. It stops a man in the middle from substituting their own key. - The password is never transmitted. First successful FN login is the enrollment acknowledgement. - Plaintext passwords are never stored; Dynamic Security keeps a hash, so re-issue means rotate. @@ -88,6 +94,10 @@ The database is authoritative for ownership. A successful broker command alone d The board's credential is created once at enrollment and never changes. Activation only changes which roles the FN client holds. +![Model A activation sequence diagram](diagrams/model-a-activation.svg) + +
Mermaid source of this diagram + ```mermaid sequenceDiagram participant O as Operator @@ -103,6 +113,8 @@ sequenceDiagram B->>M: use device/[SN]/... topics ``` +
+ - **Roles:** one assignment role per machine, `machine-`, with **literal** topics such as `device/SN000123/sales/report`. `%u` cannot express the SN here because the username is the FN. Plus shared capability roles (`vending`, `news`, `updates`). - **Backend** subscribes with wildcards (`device/+/...`), takes the SN from the topic, and checks in the database that this SN is assigned to the publishing board. - **Replacing a board for SN000123:** disable and kick `FN-old`, remove its roles; the new board enrolls as a new FN; activation attaches `machine-SN000123` to the new FN. The role's ACL text is unchanged; only which client holds it changes. @@ -112,6 +124,10 @@ sequenceDiagram At activation the board gets a second, machine-level broker identity. The same X25519 exchange is reused. +![Model B activation sequence diagram](diagrams/model-b-activation.svg) + +
Mermaid source of this diagram + ```mermaid sequenceDiagram participant O as Operator @@ -130,6 +146,8 @@ sequenceDiagram G->>M: disable FN client (or keep for rescue / re-enrollment) ``` +
+ - **One shared role** `vending-device` with `device/%u/...`, because the username equals the SN in the topic. - Use a **fresh** keypair on the board at each activation, not the enrollment one. - **Bind the SN client's Client ID** to the current board (for example its FN) with `setClientId`, if the deployed version supports it. A copied SN password is then useless from any other device, and the binding is readable with `getClient`. diff --git a/docs/sn-as-username-process.md b/docs/sn-as-username-process.md index e6c70ad..91e8922 100644 --- a/docs/sn-as-username-process.md +++ b/docs/sn-as-username-process.md @@ -111,6 +111,10 @@ Role `enroll` (assigned to the anonymous group): ### 7.2 Protocol +![Enrollment protocol sequence diagram](diagrams/enrollment-protocol.svg) + +
Mermaid source of this diagram + ```mermaid sequenceDiagram participant B as Board @@ -128,6 +132,8 @@ sequenceDiagram B->>B: persist FN and pw to flash, then reconnect as FN ``` +
+ - `sig` = Ed25519 over `S || B || n1 || clientid || FN`. Binding the FN and the Client ID means a reply for another request, board or identity does not verify. - The password is **hex of HKDF-SHA256(shared secret, salt = n1, info = "robot-market/mqtt-pw/v1|" + FN, 32 bytes)**. The label separates this use of the secret from any other. - The first successful FN login is the enrollment acknowledgement. @@ -155,6 +161,10 @@ The board logs in as `FN`, Client ID `FN`, with the derived password, sends `boa ### 9.2 Flow +![Activation flow sequence diagram](diagrams/activation-flow.svg) + +
Mermaid source of this diagram + ```mermaid sequenceDiagram participant O as Operator (UI) @@ -175,6 +185,8 @@ sequenceDiagram G->>G: DB: ACTIVE, disable the FN client, audit ``` +
+ ### 9.3 Rules that make it safe 1. **SAS bound to the activation key.** The board shows a code derived from `B2` (and `n2`, FN). The operator compares it with what the backend received. An attacker who stole an FN password and sends a request with their own key produces a code that does not match what the real board displays. **This check is mandatory**, not optional; without it the operator could attach a stranger's FN to a real SN. 2. **Activation code** (from the existing activation flow): single use, expiring, bound to one SN, attempt-limited. It proves the operator is physically at the machine.