docs(wiki): ATECC608 is upcoming — retag ledger signing to the on-host reality
No secure element is on-site: event signing runs on the software HMAC (EVENT_SIGNING_KEY, an env var on the host disk), so the ledger is tamper-EVIDENT but forgeable by anyone who owns the host. Several pages overstated it as present-tense "ATECC608-signed / unforgeable"; correct them. - NEW concepts/hardware-signer-options.md: four options for a non-extractable signing key (USB HSM / YubiKey / reuse the TPM / plain-dongle trap) + the recommendation (TPM interim → USB-HSM target; ATECC608 stays for the embedded ESP32, wrong part for a PC host). - entities/atecc608.md: UPCOMING-not-present status banner + PC-vs-embedded. - disk-os-hardening.md: fix the live-USB row (BIOS boot-order password is load-bearing, not Secure Boot — a signed live USB runs); add a physical-tamper chain (Dell 7070 CMOS-reset → live-USB → PCR-7 same-signer unseal) + accepted risks (that unseal, unsigned-initramfs evil-maid, operator-USB read TODO). - open-questions #6 reframed; standing-decisions / overview / threat-model / index de-overstated; log query entry. Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
This commit is contained in:
@@ -0,0 +1,90 @@
|
|||||||
|
---
|
||||||
|
type: concept
|
||||||
|
tags: [parking, security, integrity, crypto, hardware, threat-model]
|
||||||
|
sources: []
|
||||||
|
updated: 2026-07-02
|
||||||
|
---
|
||||||
|
|
||||||
|
# Hardware signer options (non-extractable ledger signing key)
|
||||||
|
|
||||||
|
Where the **[[append-only-event-chain|signed ledger]]'s** private signing key should live so that an
|
||||||
|
adversary who **owns the host** — including one who decrypts the disk via the [[disk-os-hardening]]
|
||||||
|
physical-tamper chain — still cannot **forge** the ledger. This is the still-open
|
||||||
|
[[open-questions|open-question #6]], reframed once it became clear (2026-07-02) that **no secure
|
||||||
|
element is on-site** and the [[atecc608]] isn't even the right part for a PC.
|
||||||
|
|
||||||
|
## The problem in one line
|
||||||
|
|
||||||
|
Today signing uses the software `SoftwareSigner` — **HMAC-SHA256, key in `EVENT_SIGNING_KEY`, an env
|
||||||
|
var on the host disk**. HMAC is symmetric: the same secret signs *and* verifies, and it sits on the
|
||||||
|
box. So the chain is tamper-**evident** (a blind editor without the key breaks it, and `verifyChain()`
|
||||||
|
pinpoints where) but **not unforgeable** — anyone who reads the key re-signs a doctored chain and it
|
||||||
|
verifies clean. The fix is a signer whose **private key is non-extractable**: the host can ask it to
|
||||||
|
sign, but can never read the key. That is a property of an HSM / smartcard / TPM — **not** of anything
|
||||||
|
that merely *stores* a key.
|
||||||
|
|
||||||
|
## The four options
|
||||||
|
|
||||||
|
### 1. USB HSM — Nitrokey HSM 2 / SmartCard-HSM *(recommended target)*
|
||||||
|
A ~€50 USB device with a non-extractable EC key (secp256r1 — same curve family as the ATECC608). The
|
||||||
|
host signs each event over **PKCS#11**; the key never leaves the token. It's essentially "the
|
||||||
|
ATECC608, but on USB instead of soldered", so it drops into the existing `signer.ts` seam (the
|
||||||
|
`keyId` field + the `TODO(atecc608)` public-key-verifier hook).
|
||||||
|
- **License fit:** OpenSC (the PKCS#11 stack) is LGPL/permissive — no vendor lock-in, matches the
|
||||||
|
all-MIT/Apache/BSD [[technology-stack|stack constraint]].
|
||||||
|
- **Downside:** it's *removable* — an operator can pocket it. But that fails **closed** (no token →
|
||||||
|
can't sign → visibly noticed), and it can be epoxied / locked inside the case.
|
||||||
|
|
||||||
|
### 2. YubiKey (PIV or OpenPGP applet)
|
||||||
|
Also a non-extractable EC/RSA key over PKCS#11; very robust, widely deployed; works the same way as
|
||||||
|
the Nitrokey for this purpose. Slightly more oriented to human 2FA than to an always-present signing
|
||||||
|
oracle, and the vendor stack is less fully-open than OpenSC. **~$50–70.** Fine as a substitute for
|
||||||
|
option 1.
|
||||||
|
|
||||||
|
### 3. Reuse the on-board TPM 2.0 *(recommended interim — free)*
|
||||||
|
The booth PC already has a TPM (it's what seals LUKS — [[tpm]]). A TPM can also hold a non-extractable
|
||||||
|
signing key and sign over it. **Zero extra hardware, closes the "key in a plaintext env file" hole
|
||||||
|
immediately.**
|
||||||
|
- **The nuance:** don't reuse the *LUKS* sealing arrangement. That key is PCR-7-sealed, and the
|
||||||
|
battery-pull → live-USB → PCR-7 chain ([[disk-os-hardening]]) defeats PCR-7-only policies. Bind the
|
||||||
|
**signing** key to the TPM **without a PCR policy** (or with a PIN) so it's about non-extractability,
|
||||||
|
not boot-state — then an attacker who decrypts the disk still can't pull it.
|
||||||
|
- **Weaker than a dedicated HSM** against a sophisticated bus-sniffing attacker, but far stronger than
|
||||||
|
today's on-disk HMAC.
|
||||||
|
|
||||||
|
### 4. Plain USB "sentinel" / dongle *(the trap — avoid)*
|
||||||
|
A generic USB flash drive holding a key file, or a license-dongle that only gates "is this USB
|
||||||
|
present". **Useless here:** if the key is *readable* off the stick, the host-owner copies it, exactly
|
||||||
|
like the env var. Presence-gating is not integrity. Only a device that **signs internally** delivers
|
||||||
|
non-extractability. Do not go this route.
|
||||||
|
|
||||||
|
## Recommendation
|
||||||
|
|
||||||
|
1. **Now (free):** move signing to a **TPM-held key** (option 3, no PCR policy) — kills the
|
||||||
|
plaintext-key-on-disk exposure using hardware already present.
|
||||||
|
2. **Target (purchasable):** a **USB HSM (Nitrokey HSM 2)** as the concrete stand-in for the still-
|
||||||
|
planned [[atecc608]] — same non-extractable-EC model, fits `signer.ts`, open tooling; physically
|
||||||
|
lock it in the case.
|
||||||
|
3. **[[atecc608]]** stays reserved for the **deferred** [[esp32-custom-controller]] (embedded), not
|
||||||
|
the PC host.
|
||||||
|
|
||||||
|
Whichever is chosen, the code seam already exists: each event stores its `keyId`, so a swap is a new
|
||||||
|
`Signer` impl and **old events stay verifiable** under their original key — no migration of history.
|
||||||
|
|
||||||
|
## The mental model to keep
|
||||||
|
|
||||||
|
Against the physical adversary who opens the case, **the only real protection for the financial
|
||||||
|
record is a signing key even root can't read.** That is an HSM/smartcard/TPM property, never a
|
||||||
|
USB-storage property — and it's precisely what's missing today. Note the layering: [[disk-os-hardening]]
|
||||||
|
raises the cost of *reaching* the disk; a hardware signer makes the ledger unforgeable *even after*
|
||||||
|
the disk is reached; and [[reconciliation]] against records the box doesn't hold is the backstop that
|
||||||
|
survives a fully-owned host. They are complements, not substitutes.
|
||||||
|
|
||||||
|
## Relates
|
||||||
|
- [[append-only-event-chain]] — the ledger this key signs; why software signing isn't enough.
|
||||||
|
- [[atecc608]] — the originally-specified secure element (upcoming; embedded, not PC).
|
||||||
|
- [[tpm]] — the on-board part; option 3, and the LUKS-sealing analysis.
|
||||||
|
- [[disk-os-hardening]] — the physical-tamper chain that makes a non-extractable key necessary.
|
||||||
|
- [[open-questions]] #6 (secure-element integration), #7 (JWT symmetric→asymmetric — same key-custody
|
||||||
|
argument for auth tokens).
|
||||||
|
- [[threat-model]] / [[reconciliation]] — the adversary and the backstop control.
|
||||||
@@ -26,7 +26,10 @@ authorised access *through the app*. Encryption does nothing against the classic
|
|||||||
The controls that actually address insider/operator fraud are different in kind:
|
The controls that actually address insider/operator fraud are different in kind:
|
||||||
|
|
||||||
- **[[append-only-event-chain]]** — events appended, never edited/deleted; a "void" is itself a
|
- **[[append-only-event-chain]]** — events appended, never edited/deleted; a "void" is itself a
|
||||||
recorded event, hash-chained, and **[[atecc608]]-signed** (unforgeable).
|
recorded event, hash-chained, and signed. ⚠ Signing is **software today** (key on the host disk) →
|
||||||
|
tamper-*evident* but forgeable by a host owner; a non-extractable **hardware signer**
|
||||||
|
([[hardware-signer-options]]; [[atecc608]] upcoming) is what makes it truly *unforgeable*. Which is
|
||||||
|
why the load-bearing insider control is reconciliation, next.
|
||||||
- **[[reconciliation]]** against an authority the operator can't alter — *this is what remote
|
- **[[reconciliation]]** against an authority the operator can't alter — *this is what remote
|
||||||
sync really is*: a fraud-control mechanism, not just a backup.
|
sync really is*: a fraud-control mechanism, not just a backup.
|
||||||
- **[[disk-os-hardening]]** still worthwhile (defeats boot-from-USB) but **not the main event**;
|
- **[[disk-os-hardening]]** still worthwhile (defeats boot-from-USB) but **not the main event**;
|
||||||
|
|||||||
@@ -20,14 +20,27 @@ across the first real provisioning (2026-06-23) and the firmware-update episode
|
|||||||
> **not** replace reconciliation, and it cannot stop a *legitimate, logged-in* operator from
|
> **not** replace reconciliation, and it cannot stop a *legitimate, logged-in* operator from
|
||||||
> committing fraud through the app (that's what the signed ledger + reconciliation are for).
|
> committing fraud through the app (that's what the signed ledger + reconciliation are for).
|
||||||
|
|
||||||
|
> ⚠ **Caveat on the ledger's tamper-resistance (2026-07-02).** The "signed event chain" above is
|
||||||
|
> **software-signed today** (HMAC, key in `EVENT_SIGNING_KEY` on the host disk) — the [[atecc608]]
|
||||||
|
> secure element is [[open-questions|upcoming, not present]]. So a physical adversary who *decrypts
|
||||||
|
> the disk* (see the tamper chain below) reads the signing key and can **forge/re-sign a doctored
|
||||||
|
> ledger undetectably** — the chain does not save you against a host-owner until a non-extractable
|
||||||
|
> hardware signer ([[tpm|TPM]] / USB HSM — [[hardware-signer-options]]) is wired. Until then the disk's
|
||||||
|
> confidentiality/integrity leans harder on the controls below, and the residual backstop against
|
||||||
|
> forgery is **external reconciliation** against records the box doesn't hold (payments, an offsite
|
||||||
|
> backup, a separate witness), not the on-disk signature.
|
||||||
|
|
||||||
## What it defends against
|
## What it defends against
|
||||||
|
|
||||||
The appliance sits on-site, physically reachable by the [[threat-model|booth operator (the primary
|
The appliance sits on-site, physically reachable by the [[threat-model|booth operator (the primary
|
||||||
adversary)]] and by an outsider who can open the case. Without host hardening, either can:
|
adversary)]] and by an outsider who can open the case. Without host hardening, either can:
|
||||||
|
|
||||||
- **Pull the SSD** and read/alter the SQLite ledger offline → FDE (LUKS) defeats this.
|
- **Pull the SSD** and read/alter the SQLite ledger offline → FDE (LUKS) defeats this.
|
||||||
- **Boot a live USB** to mount and edit the disk → Secure Boot + TPM-sealing (PCR 7) defeats booting
|
- **Boot a live USB** to mount and edit the disk → **the BIOS boot-order/boot-menu password is the
|
||||||
a tampered/unsigned kernel; FDE keeps the data unreadable.
|
load-bearing control here**, NOT Secure Boot. Secure Boot happily runs a *signed* Ubuntu live USB,
|
||||||
|
and PCR-7-only sealing can't distinguish it from our own boot (same signing authorities → same
|
||||||
|
PCR 7 → the TPM would unseal). So confidentiality rests on the operator being unable to *select*
|
||||||
|
the USB. See the physical-tamper chain below (a CMOS reset strips that password).
|
||||||
- **Edit the GRUB cmdline** (`init=/bin/bash`) for a no-login root shell on the *decrypted* disk →
|
- **Edit the GRUB cmdline** (`init=/bin/bash`) for a no-login root shell on the *decrypted* disk →
|
||||||
the GRUB edit-lock defeats this (the TPM seal does NOT — see below).
|
the GRUB edit-lock defeats this (the TPM seal does NOT — see below).
|
||||||
- **Escalate from the operator login** (sudo, `docker`/`lxd` groups) → the unprivileged-operator
|
- **Escalate from the operator login** (sudo, `docker`/`lxd` groups) → the unprivileged-operator
|
||||||
@@ -74,6 +87,50 @@ turns a routine "security update" into a booth-availability risk:
|
|||||||
> false safety signal). Before any firmware change, prove a *typed* passphrase still unlocks the disk
|
> false safety signal). Before any firmware change, prove a *typed* passphrase still unlocks the disk
|
||||||
> with `--disable-external-tokens` — see [[appliance-provisioning]] §4a.
|
> with `--disable-external-tokens` — see [[appliance-provisioning]] §4a.
|
||||||
|
|
||||||
|
## Physical-tamper chain & accepted risks (traced 2026-07-02, Dell OptiPlex 7070)
|
||||||
|
|
||||||
|
The BIOS admin password gates Setup **and** the one-time boot menu (verified: choosing a USB device
|
||||||
|
at F12 prompts for the password). That closes the live-USB path — *while the password holds*. The
|
||||||
|
uncomfortable finding is that the password is a **soft control** against a case-opening adversary:
|
||||||
|
|
||||||
|
1. **CMOS reset** (pull the coin cell, or the on-board PSWD/CMOS-clear jumper — Dell documents its
|
||||||
|
location) → BIOS *settings* return to factory defaults: **admin password cleared, boot menu
|
||||||
|
open**. It does NOT wipe the Secure-Boot key databases (PK/KEK/db/dbx live in SPI-flash NVRAM,
|
||||||
|
not the battery-backed RTC), and the 7070's factory default is **Secure Boot = Enabled**. So
|
||||||
|
after the reset Secure Boot comes back **on, same MS keys** → **PCR 7 reconstructs to the same
|
||||||
|
value.**
|
||||||
|
2. **Boot a signed Ubuntu live USB** (now selectable). Same signing authorities → PCR 7 matches the
|
||||||
|
sealing policy → the attacker runs `cryptsetup`/`systemd-cryptsetup` and the **TPM releases the
|
||||||
|
LUKS key**. Disk decrypts; they have **root on the decrypted filesystem**, including the ledger.
|
||||||
|
- Note the asymmetry: if instead they *disable* Secure Boot in the now-unlocked BIOS, PCR 7
|
||||||
|
**changes** → the TPM refuses → the box drops to the slot-0 passphrase prompt they don't have.
|
||||||
|
So *disabling* Secure Boot locks them out; leaving it at the reset default lets them in. This is
|
||||||
|
the PCR-7-only "same-signer" weakness (systemd docs recommend PCR 7 **+ a PIN** to close it).
|
||||||
|
|
||||||
|
**Net:** a battery-pull alone yields nothing (disk stays sealed), but **battery-pull → live-USB →
|
||||||
|
PCR-7 unseal** is a realistic chain to **root on the decrypted data**. What it costs the attacker: a
|
||||||
|
screwdriver and a signed USB. What still holds after it: they never get the **escrowed slot-0
|
||||||
|
passphrase**, and (the point of the caveat above) the ledger's forgery-resistance depends on the
|
||||||
|
signer — with today's **on-disk HMAC key they CAN forge the ledger**; only a hardware signer
|
||||||
|
([[hardware-signer-options]]) would keep the financial record unforgeable through this.
|
||||||
|
|
||||||
|
**Accepted risks (named, not silently "covered"):**
|
||||||
|
|
||||||
|
- **PCR-7 same-signer unseal via CMOS reset** (above). *Not* mitigated by the current config. Real
|
||||||
|
fixes both cost the thing we optimised for: a **TPM PIN** (`--tpm2-with-pin=yes`) closes it but
|
||||||
|
**kills unattended boot** (someone types a PIN each power event); **custom Secure-Boot keys / more
|
||||||
|
PCRs** close it but reintroduce re-seal churn on kernel/shim updates (the exact thing PCR-7-only
|
||||||
|
avoids). Deferred decision — accept for now; the primary control remains reconciliation + escrowed
|
||||||
|
backups, not disk confidentiality.
|
||||||
|
- **Unsigned initramfs (evil-maid).** `/boot` is unencrypted and Ubuntu does **not** sign the
|
||||||
|
initramfs (Secure Boot verifies shim→GRUB→kernel; PCR 7 doesn't measure the initrd). The initramfs
|
||||||
|
is exactly the code that receives the LUKS key, so a maid who tampers it and waits one boot can
|
||||||
|
harvest the key. Accepted: needs repeated privileged physical access, and a hardware signer would
|
||||||
|
still keep the ledger unforgeable even from a fully-owned host.
|
||||||
|
- **Operator USB at an auto-logged-in session** — unverified what the unprivileged operator account
|
||||||
|
can read on the host (e.g. can it reach the Docker-mounted `/data`?). **TODO: verify** the operator
|
||||||
|
can't read the DB volume or `EVENT_SIGNING_KEY` from its own login.
|
||||||
|
|
||||||
## Where the commands live
|
## Where the commands live
|
||||||
|
|
||||||
This page is the rationale. The **verified, run-on-real-hardware commands** are in
|
This page is the rationale. The **verified, run-on-real-hardware commands** are in
|
||||||
@@ -89,4 +146,6 @@ root-capable remote agent — see [[fleet-deployment-komodo]] (bind to the NetBi
|
|||||||
- [[threat-model]] — the operator-adversary framing this hardening serves.
|
- [[threat-model]] — the operator-adversary framing this hardening serves.
|
||||||
- [[reconciliation]] / [[append-only-event-chain]] — the **primary** anti-fraud control this
|
- [[reconciliation]] / [[append-only-event-chain]] — the **primary** anti-fraud control this
|
||||||
complements, never replaces.
|
complements, never replaces.
|
||||||
|
- [[hardware-signer-options]] — TPM / USB-HSM / ATECC608 options for a non-extractable ledger
|
||||||
|
signing key (the missing piece this page's tamper chain exposes).
|
||||||
- [[fleet-deployment-komodo]] — Periphery as part of the trusted computing base.
|
- [[fleet-deployment-komodo]] — Periphery as part of the trusted computing base.
|
||||||
|
|||||||
@@ -40,10 +40,19 @@ procurement. (See [[parking-system-architecture]] §10.)
|
|||||||
but **unverifiable after the machine dies**). **Restore is admin-only/out-of-band** (operator-adversary
|
but **unverifiable after the machine dies**). **Restore is admin-only/out-of-band** (operator-adversary
|
||||||
surface — [[threat-model]]). See [[backup-recovery]], [[fleet-deployment-komodo]], [[disk-os-hardening]],
|
surface — [[threat-model]]). See [[backup-recovery]], [[fleet-deployment-komodo]], [[disk-os-hardening]],
|
||||||
[[reconciliation]] (#4).
|
[[reconciliation]] (#4).
|
||||||
6. **Secure-element integration.** Confirm [[atecc608]] wiring/usage on the host (event
|
6. **Secure-element integration.** _(Updated 2026-07-02: no secure element is on-site today.)_
|
||||||
signing). The [[esp32-custom-controller]] command-authentication use is **deferred — not
|
Event signing currently runs on the **software `SoftwareSigner`** (HMAC-SHA256, key in
|
||||||
being implemented for now** (access control is the [[dingtian-relay]] behind
|
`EVENT_SIGNING_KEY` — an env var **on the host disk**). So the ledger is tamper-EVIDENT but
|
||||||
[[network-isolation]]); revisit only if prevention-grade device auth becomes a requirement.
|
**not** unforgeable by anyone who owns the host: a case-opening adversary who decrypts the disk
|
||||||
|
reads the key and can re-sign a doctored chain (see [[append-only-event-chain]] "pull-the-disk",
|
||||||
|
[[disk-os-hardening]] physical-tamper chain). The **[[atecc608]] is UPCOMING, not present** — and
|
||||||
|
it isn't even the right host part: on a PC appliance the realistic non-extractable host signer is
|
||||||
|
the **[[tpm|TPM 2.0]]** the box already has, or a **USB HSM** (Nitrokey HSM 2 / SmartCard-HSM);
|
||||||
|
reserve the ATECC608 for the (deferred) [[esp32-custom-controller]]. The concrete menu +
|
||||||
|
recommendation (TPM interim → USB-HSM target) is in [[hardware-signer-options]]. The
|
||||||
|
controller command-authentication use is **deferred — not being implemented for now** (access
|
||||||
|
control is the [[dingtian-relay]] behind [[network-isolation]]); revisit only if prevention-grade
|
||||||
|
device auth becomes a requirement.
|
||||||
7. **JWT signing: symmetric vs. asymmetric key.** _(Raised by the commit security review, not the
|
7. **JWT signing: symmetric vs. asymmetric key.** _(Raised by the commit security review, not the
|
||||||
source doc.)_ Auth currently uses a symmetric HMAC secret (`@fastify/jwt`, see
|
source doc.)_ Auth currently uses a symmetric HMAC secret (`@fastify/jwt`, see
|
||||||
[[local-jwt-auth]]) — the same secret signs *and* verifies, so it must live on every host that
|
[[local-jwt-auth]]) — the same secret signs *and* verifies, so it must live on every host that
|
||||||
|
|||||||
@@ -25,9 +25,11 @@ The decisions treated as settled in the design notes. (See [[parking-system-arch
|
|||||||
deny-by-default native surface that fits [[threat-model|the booth-operator threat model]]. The
|
deny-by-default native surface that fits [[threat-model|the booth-operator threat model]]. The
|
||||||
shell stays **thin**: all privileged logic remains in [[fastify]]. One open dependency — the
|
shell stays **thin**: all privileged logic remains in [[fastify]]. One open dependency — the
|
||||||
appliance's WebKitGTK version (see [[open-questions]] #11).
|
appliance's WebKitGTK version (see [[open-questions]] #11).
|
||||||
- **Integrity:** append-only, hash-chained, [[atecc608]]-signed event log
|
- **Integrity:** append-only, hash-chained, **software-signed** event log
|
||||||
([[append-only-event-chain]]); **[[reconciliation]] is the anti-fraud control**; encryption
|
([[append-only-event-chain]]) — hardware-backed signing (a non-extractable key in the
|
||||||
protects only at-rest (see [[threat-model]]).
|
**[[tpm|TPM]]** or a **USB HSM**; the [[atecc608]] is [[open-questions|upcoming, not present]]) is
|
||||||
|
the target that makes it unforgeable by a host owner ([[hardware-signer-options]]). **[[reconciliation]]
|
||||||
|
is the anti-fraud control**; encryption protects only at-rest (see [[threat-model]]).
|
||||||
- **Access control:** the **[[dingtian-relay]]** relay+input controller, on an **isolated VLAN**
|
- **Access control:** the **[[dingtian-relay]]** relay+input controller, on an **isolated VLAN**
|
||||||
([[network-isolation]]). Chosen because its **inputs are decoupled from its relays**, enabling
|
([[network-isolation]]). Chosen because its **inputs are decoupled from its relays**, enabling
|
||||||
host-in-the-loop ticket-first entry — the resolution to [[access-controller-button-flow]].
|
host-in-the-loop ticket-first entry — the resolution to [[access-controller-button-flow]].
|
||||||
|
|||||||
+23
-11
@@ -2,26 +2,38 @@
|
|||||||
type: entity
|
type: entity
|
||||||
tags: [parking, hardware, security, crypto]
|
tags: [parking, hardware, security, crypto]
|
||||||
sources: [parking-system-architecture]
|
sources: [parking-system-architecture]
|
||||||
updated: 2026-06-14
|
updated: 2026-07-02
|
||||||
---
|
---
|
||||||
|
|
||||||
# ATECC608 (secure element)
|
# ATECC608 (secure element)
|
||||||
|
|
||||||
An inexpensive **secure element** holding a signing key that **cannot be extracted, even by
|
An inexpensive **secure element** holding a signing key that **cannot be extracted, even by
|
||||||
someone who owns the machine**. The keystone of integrity in this system. (See
|
someone who owns the machine**. The design's intended keystone of ledger integrity. (See
|
||||||
[[parking-system-architecture]] §3, §7.)
|
[[parking-system-architecture]] §3, §7.)
|
||||||
|
|
||||||
Two distinct uses:
|
> **⚠ STATUS — UPCOMING, NOT PRESENT (2026-07-02).** No ATECC608 (nor any secure element) is on
|
||||||
|
> the booth today. Event signing runs on the **software `SoftwareSigner`** (HMAC-SHA256, key in
|
||||||
|
> `EVENT_SIGNING_KEY`, an env var on the host disk). Consequence: the [[append-only-event-chain]]
|
||||||
|
> is tamper-**evident** but **not** unforgeable by an adversary who owns the host — they can read
|
||||||
|
> the key and re-sign a doctored chain ([[disk-os-hardening]] physical-tamper chain). Do not describe
|
||||||
|
> the ledger as "hardware-signed / unforgeable" in present tense. This is the still-open
|
||||||
|
> [[open-questions|open-question #6]].
|
||||||
|
|
||||||
1. **Host-side event signing.** Each event in the [[append-only-event-chain]] is signed by the
|
Two distinct **intended** uses:
|
||||||
ATECC608 on the host machine. This is what makes the hash chain **unforgeable** rather than
|
|
||||||
merely self-consistent.
|
|
||||||
2. **Custom controller command authentication.** On the [[esp32-custom-controller]], it holds
|
|
||||||
the key(s) for [[challenge-response-auth]] — generated on-chip, non-extractable, so popping
|
|
||||||
the cabinet and dumping flash yields nothing usable.
|
|
||||||
|
|
||||||
Confirming ATECC608 wiring/usage on both ends is [[open-questions]] #6. Listed in the [[bom]]
|
1. **Host-side event signing.** The design has each event in the [[append-only-event-chain]] signed
|
||||||
on the host machine.
|
by a non-extractable key so the hash chain is **unforgeable** rather than merely self-consistent.
|
||||||
|
**Caveat:** the ATECC608 is an external I²C part native to embedded boards, **not a PC component** —
|
||||||
|
on the PC-based booth appliance the realistic non-extractable host signer is the **[[tpm|TPM 2.0]]**
|
||||||
|
already on the machine, or a **USB HSM** (Nitrokey HSM 2 / SmartCard-HSM). See
|
||||||
|
[[hardware-signer-options]] for the full menu + the TPM-interim → USB-HSM-target recommendation.
|
||||||
|
2. **Custom controller command authentication.** On the (deferred) [[esp32-custom-controller]], it
|
||||||
|
would hold the key(s) for [[challenge-response-auth]] — generated on-chip, non-extractable, so
|
||||||
|
popping the cabinet and dumping flash yields nothing usable. This use is **deferred** (access
|
||||||
|
control is the [[dingtian-relay]] behind [[network-isolation]]).
|
||||||
|
|
||||||
|
Confirming a real secure-element signer on the host is [[open-questions]] #6. Listed (aspirationally)
|
||||||
|
in the [[bom]]; treat as a future line item until procured.
|
||||||
|
|
||||||
> **Platform caveat (2026-06-21):** the ATECC608 is **not a PC component** — it's an external I²C
|
> **Platform caveat (2026-06-21):** the ATECC608 is **not a PC component** — it's an external I²C
|
||||||
> secure element you add/solder, native to embedded boards (the [[esp32-custom-controller]]), not to
|
> secure element you add/solder, native to embedded boards (the [[esp32-custom-controller]]), not to
|
||||||
|
|||||||
+5
-3
@@ -1,13 +1,13 @@
|
|||||||
---
|
---
|
||||||
type: overview
|
type: overview
|
||||||
tags: [parking, index]
|
tags: [parking, index]
|
||||||
updated: 2026-06-21
|
updated: 2026-07-02
|
||||||
---
|
---
|
||||||
|
|
||||||
# Index
|
# Index
|
||||||
|
|
||||||
Content catalog for the wiki. Start at [[overview]]. Maintained on every ingest.
|
Content catalog for the wiki. Start at [[overview]]. Maintained on every ingest.
|
||||||
Counts: 4 sources · 19 entities · 46 concepts · 7 decision records.
|
Counts: 4 sources · 19 entities · 47 concepts · 7 decision records.
|
||||||
|
|
||||||
## Overview & navigation
|
## Overview & navigation
|
||||||
- [[overview]] — the top-level synthesis and entry point.
|
- [[overview]] — the top-level synthesis and entry point.
|
||||||
@@ -53,7 +53,8 @@ Counts: 4 sources · 19 entities · 46 concepts · 7 decision records.
|
|||||||
- [[tpm]] — TPM 2.0 hardening: how it works, sealed-LUKS auto-unlock + non-extractable signing key; limits (live-root, bus-sniff) + TPM-vs-ATECC608 by platform; complements, not replaces, reconciliation.
|
- [[tpm]] — TPM 2.0 hardening: how it works, sealed-LUKS auto-unlock + non-extractable signing key; limits (live-root, bus-sniff) + TPM-vs-ATECC608 by platform; complements, not replaces, reconciliation.
|
||||||
|
|
||||||
## Concepts — integrity & anti-fraud
|
## Concepts — integrity & anti-fraud
|
||||||
- [[append-only-event-chain]] — append-only + hash chain + ATECC608 signing = unforgeable log.
|
- [[append-only-event-chain]] — append-only + hash chain + signing = unforgeable log (signing is **software today**; hardware signer pending — see below).
|
||||||
|
- [[hardware-signer-options]] — where the ledger signing key should live (TPM interim → USB-HSM target; ATECC608 upcoming, not on-site) so a host-owner can't forge the chain.
|
||||||
- [[reconciliation]] — the real anti-fraud control; what remote sync actually is.
|
- [[reconciliation]] — the real anti-fraud control; what remote sync actually is.
|
||||||
- [[disk-os-hardening]] — the *why* of host hardening: LUKS FDE + TPM-sealed auto-unlock (PCR 7) + Secure Boot + GRUB edit-lock + unprivileged operator + firmware/dbx lockdown; secondary control (reconciliation is the main event). Commands → [[appliance-provisioning]].
|
- [[disk-os-hardening]] — the *why* of host hardening: LUKS FDE + TPM-sealed auto-unlock (PCR 7) + Secure Boot + GRUB edit-lock + unprivileged operator + firmware/dbx lockdown; secondary control (reconciliation is the main event). Commands → [[appliance-provisioning]].
|
||||||
- [[backup-recovery]] — admin-driven encrypted full-DB backup (local/SMB/SFTP) + DR; signing key escrowed & decoupled from TPM so the ledger survives total hardware loss; restore is admin-only.
|
- [[backup-recovery]] — admin-driven encrypted full-DB backup (local/SMB/SFTP) + DR; signing key escrowed & decoupled from TPM so the ledger survives total hardware loss; restore is admin-only.
|
||||||
@@ -107,6 +108,7 @@ Counts: 4 sources · 19 entities · 46 concepts · 7 decision records.
|
|||||||
- [[subscription]] — recurring plan (e.g. 10,000 ALL/month); RF/QR or plate identity, car-count + max-concurrent, host-in-loop; short-circuits payment. (Renamed from "permit"; time-of-day windows noted, deferred.)
|
- [[subscription]] — recurring plan (e.g. 10,000 ALL/month); RF/QR or plate identity, car-count + max-concurrent, host-in-loop; short-circuits payment. (Renamed from "permit"; time-of-day windows noted, deferred.)
|
||||||
- [[opencv-anpr-service]] — host-side vision microservice: ANPR (plate identity) + vehicle verification (anti-plate-spoofing witness); fast-alpr (MIT, YOLOv9+CCT/ONNX) the evaluated recognizer baseline.
|
- [[opencv-anpr-service]] — host-side vision microservice: ANPR (plate identity) + vehicle verification (anti-plate-spoofing witness); fast-alpr (MIT, YOLOv9+CCT/ONNX) the evaluated recognizer baseline.
|
||||||
- [[lane-presence-and-anpr-entry]] — camera vehicle detection → (BUILT) advisory lane busy/free booth lights + (BUILT) the ANPR "bridge" (`anpr-entry.ts`): a subscriber's plate read at the lane admits them via the existing gated subscription flow (match-before-emit; subscriber-only). Measured camera limits; rejected the queue-tracking/livestream ideas.
|
- [[lane-presence-and-anpr-entry]] — camera vehicle detection → (BUILT) advisory lane busy/free booth lights + (BUILT) the ANPR "bridge" (`anpr-entry.ts`): a subscriber's plate read at the lane admits them via the existing gated subscription flow (match-before-emit; subscriber-only). Measured camera limits; rejected the queue-tracking/livestream ideas.
|
||||||
|
- [[vision-service-hardening]] — fix/hardening backlog for `apps/vision/` (2026-07-02 reviews): DoS (body-cap, pixel-bomb, event-loop-blocking inference), unauthenticated + operator-writable model weights, `0.0.0.0` default bind, + correctness/hygiene items. Not yet fixed — the to-do list.
|
||||||
- [[blocklist]] — barred plates/cards refused at entry (never at exit); signed, attributed.
|
- [[blocklist]] — barred plates/cards refused at entry (never at exit); signed, attributed.
|
||||||
|
|
||||||
## Concepts — frontend / operator UI
|
## Concepts — frontend / operator UI
|
||||||
|
|||||||
+68
@@ -2139,3 +2139,71 @@ override-releases-with-attribution, low-confidence-no-warning, own-plate-no-warn
|
|||||||
operator-issued-entry.md + plate-reconciliation.md; cross-linked from entry-exit-points,
|
operator-issued-entry.md + plate-reconciliation.md; cross-linked from entry-exit-points,
|
||||||
capacity-occupancy, index. Preserves "a plate never OPENS a barrier alone — and now never TRAPS a car
|
capacity-occupancy, index. Preserves "a plate never OPENS a barrier alone — and now never TRAPS a car
|
||||||
alone either."
|
alone either."
|
||||||
|
|
||||||
|
## [2026-07-01] deploy | Promote dev → stage (d2ab2e0) → park-buzi, pinned TAG=stage-d2ab2e0
|
||||||
|
|
||||||
|
Merged dev → stage (no-ff, clean — stage content was fully contained in dev). Shipped to the staging
|
||||||
|
booth: snapshot content-type fix, Active Sessions/modal rework, DB reset CLI, drawer redesign (operator
|
||||||
|
records / admin reviews), card tender disabled (no POS), operator-issued entry + exit plate-swap
|
||||||
|
reconciliation. Push to stage triggered CI → built parking-server/vision :stage + :stage-d2ab2e0. Pinned
|
||||||
|
TAG=stage-d2ab2e0 in komodo/resources.toml on BOTH stage and dev (the ResourceSync's source branch is a
|
||||||
|
Core-side config, so both agree — see komodo/README.md; they're identical content anyway).
|
||||||
|
|
||||||
|
Migration note: this promotion carries migrations 0018 (drawer:create) + 0019 (session:create). BOTH are
|
||||||
|
DATA SEEDS, not schema — INSERT OR IGNORE one role_permissions row each for the built-in `operator` role;
|
||||||
|
idempotent, no CREATE/ALTER, existing data untouched. They apply automatically at container boot
|
||||||
|
(docker-entrypoint.sh → migrate-runtime.mjs, before the server starts) against the /data volume DB, which
|
||||||
|
survives the redeploy. Caveat recorded in container-deployment.md: a permission seeded to the built-in
|
||||||
|
operator role does NOT reach a CUSTOM role — an admin toggles it in Setup → Roles.
|
||||||
|
|
||||||
|
Deploy (operator, in Komodo Core): refresh ResourceSync (TAG diff enables Execute) → Execute → Deploy
|
||||||
|
(Destroy+Deploy for a clean recreate; parking-data volume persists). Watch for `[migrate] done` in logs.
|
||||||
|
|
||||||
|
## [2026-07-02] query | Physical-tamper of the booth disk + ledger signing reality (ATECC608 is upcoming, not present)
|
||||||
|
|
||||||
|
Q (operator): can a malicious user boot a live Ubuntu / reset the BIOS (coin cell or PSWD jumper) and
|
||||||
|
get root on the storage? Traced on the actual box (Dell OptiPlex 7070): the BIOS admin password DOES
|
||||||
|
gate the F12 boot menu (selecting the USB prompts for it), so the live-USB path is closed **while the
|
||||||
|
password holds**. But a CMOS reset clears the admin password + reopens the boot menu WITHOUT wiping the
|
||||||
|
Secure-Boot key DBs (SPI-flash NVRAM, not RTC), and the 7070 default is Secure Boot=Enabled → PCR 7
|
||||||
|
reconstructs to the SAME value → a signed live Ubuntu (same signing authorities) matches the PCR-7-only
|
||||||
|
seal → the TPM releases the LUKS key → root on the decrypted disk. Battery-pull alone = nothing;
|
||||||
|
battery-pull → live-USB → PCR-7 unseal = realistic root-on-data. (Disabling Secure Boot instead CHANGES
|
||||||
|
PCR 7 → passphrase prompt → locked out; the same-signer default is the hole. systemd docs: PCR 7 + PIN.)
|
||||||
|
|
||||||
|
BIGGER correction surfaced: the ledger is NOT ATECC608-signed today. No secure element is on-site. Signing
|
||||||
|
runs on the software SoftwareSigner (HMAC, key = EVENT_SIGNING_KEY, an env var on the host disk). So the
|
||||||
|
chain is tamper-EVIDENT but forgeable by whoever owns the host — the disk-decryption chain above hands
|
||||||
|
them the key too. The ATECC608 was overstated as present in several pages; it's also the wrong part for a
|
||||||
|
PC (external I²C, embedded-native) — reserve it for the deferred ESP32; the realistic host signer is the
|
||||||
|
on-board TPM or a USB HSM.
|
||||||
|
|
||||||
|
Actions:
|
||||||
|
- NEW concepts/hardware-signer-options.md — four options (USB HSM/Nitrokey HSM 2 [target], YubiKey, reuse
|
||||||
|
the TPM [free interim, bind signing key with NO PCR policy], plain USB dongle [trap, avoid]) + the
|
||||||
|
recommendation (TPM now → USB-HSM target; ATECC608 stays for embedded). Notes the signer.ts keyId seam.
|
||||||
|
- Retag pass ATECC608 → UPCOMING/NOT-PRESENT + "software-signed today, forgeable by host owner" caveat:
|
||||||
|
entities/atecc608.md (status banner + PC-vs-embedded), append-only-event-chain already honest,
|
||||||
|
standing-decisions.md, overview.md, threat-model.md, open-questions.md #6 (reframed), index.md.
|
||||||
|
- disk-os-hardening.md: fixed the live-USB row (BIOS boot-order password is load-bearing, not Secure
|
||||||
|
Boot — signed live USB runs), added a caveat banner (software signer → disk decryption = ledger
|
||||||
|
forgery) + a "Physical-tamper chain & accepted risks" section (CMOS-reset chain; accepted risks:
|
||||||
|
PCR-7 same-signer unseal, unsigned initramfs evil-maid, operator-USB read TODO).
|
||||||
|
- Verify items for the box: (a) confirm F12/one-time-boot is password-gated (done — it is); (b) after a
|
||||||
|
CMOS clear does Secure Boot return Enabled? (expected yes on Dell); (c) can the unprivileged operator
|
||||||
|
login read /data or EVENT_SIGNING_KEY?
|
||||||
|
- Residual: signer.ts still uses HMAC (no code change this pass); the load-bearing anti-fraud control
|
||||||
|
remains reconciliation + escrowed offsite backups, NOT on-disk confidentiality/signature.
|
||||||
|
|
||||||
|
## [2026-07-02] review | Vision service (apps/vision/) hardening + fix backlog
|
||||||
|
Two code reviews of the Python/FastAPI ANPR service (general: bottlenecks/bugs/best-practice,
|
||||||
|
and a security-focused pass). Filed the findings as a prioritised, not-yet-fixed to-do list at
|
||||||
|
[[vision-service-hardening]]; cross-linked from [[opencv-anpr-service]] ("consult before touching")
|
||||||
|
and cataloged in index.md. Headline items: DoS (12MB cap checked *after* the body is buffered;
|
||||||
|
`cv2.imdecode` pixel-bomb; CPU inference on the async event loop stalling `/health`);
|
||||||
|
unauthenticated **and** operator-writable model weights → persistent recognition-poisoning
|
||||||
|
([[threat-model]]); `0.0.0.0`-by-default bind at all three layers; dev compose publishing 8089 on
|
||||||
|
all interfaces; plus correctness/hygiene (cwd-relative `.env`, `/health` always-200, pre-warm
|
||||||
|
swallowing failures, unbounded `min_confidence`). Reassurance recorded: a forged image can't open a
|
||||||
|
barrier (server re-gates at 0.85 + debounce), content-type isn't trusted, non-root, `.env` not baked
|
||||||
|
into the image. Nothing fixed yet — this is the backlog to work from.
|
||||||
|
|||||||
+5
-3
@@ -25,9 +25,11 @@ deployed on-site at a parking facility. Two forces shape nearly every decision:
|
|||||||
[[react-vite-spa]] · [[sqlite]] + [[drizzle-orm]] · [[local-jwt-auth]] — all open-licensed to
|
[[react-vite-spa]] · [[sqlite]] + [[drizzle-orm]] · [[local-jwt-auth]] — all open-licensed to
|
||||||
avoid lock-in (cf. rejected [[payload-cms]], [[refine]], [[logto-zitadel-oidc]]). The operator UI
|
avoid lock-in (cf. rejected [[payload-cms]], [[refine]], [[logto-zitadel-oidc]]). The operator UI
|
||||||
ships as a thin **[[desktop-shell-tauri|Tauri v2]]** kiosk shell (chosen over Electron).
|
ships as a thin **[[desktop-shell-tauri|Tauri v2]]** kiosk shell (chosen over Electron).
|
||||||
- **Integrity** is the heart of it: an [[append-only-event-chain]] (hash-chained, [[atecc608]]-
|
- **Integrity** is the heart of it: an [[append-only-event-chain]] (hash-chained, signed) plus
|
||||||
signed) plus external [[reconciliation]] — *that's* what remote sync really is. Encryption at
|
external [[reconciliation]] — *that's* what remote sync really is. Signing is **software today**
|
||||||
rest ([[disk-os-hardening]]) defends a secondary threat.
|
(key on-disk → forgeable by a host owner); a non-extractable **hardware signer** (TPM / USB-HSM;
|
||||||
|
the [[atecc608]] is upcoming) is the pending fix — [[hardware-signer-options]]. Encryption at rest
|
||||||
|
([[disk-os-hardening]]) defends a secondary threat.
|
||||||
- **Devices** sit behind a [[device-adapter-pattern]] (swap hardware → new adapter only), with
|
- **Devices** sit behind a [[device-adapter-pattern]] (swap hardware → new adapter only), with
|
||||||
the [[barrier-not-a-door]] safety principle keeping physical safety in barrier-operator firmware.
|
the [[barrier-not-a-door]] safety principle keeping physical safety in barrier-operator firmware.
|
||||||
- **Access control** today is the **[[dingtian-relay]]** relay+input controller behind
|
- **Access control** today is the **[[dingtian-relay]]** relay+input controller behind
|
||||||
|
|||||||
Reference in New Issue
Block a user