Skip to content

H1 Phase 2: WAN cert mismatch dialog + Pinned Certificates settings UI #2951

Description

@ten9876

Context

Phase 1 of GHSA-wfx7-w6p8-4jr2 shipped in PR #2947 (commit 5275f42). It silently captures the radio's TLS cert fingerprint on first SmartLink connect and logs a warning on mismatch — but the connection still proceeds.

Phase 2 turns the warn-only behavior into actual enforcement + UX.

What Phase 2 adds

1. Mismatch dialog (enforcement)

When WanConnection::onTlsConnected detects a fingerprint mismatch:

  • Pause the connection (don't send wan validate handle=... yet)
  • Emit new signal certFingerprintMismatch(host, expectedHex, presentedHex)
  • UI catches signal and shows a modal:

Certificate changed for <radio nickname / IP>
Expected: aa:bb:cc:…:ff (pinned 2026-04-15)
Presented: 11:22:33:…:99

This may be normal (radio firmware update, replaced radio) or a possible man-in-the-middle attack.

[Accept new certificate] [Reject and disconnect]

  • Accept → overwrite the stored pin, resume connection (call internal sendWanValidate())
  • RejectdisconnectFromRadio(), emit errorOccurred("Certificate rejected by user")

2. Pinned Certificates settings UI

New section at Radio Setup → SmartLink → Pinned Certificates:

  • List view: <host, fingerprint, pinned-date> rows
  • Per-entry "Forget" button removes that host's pin (next connect re-pins as first-use)
  • Global "Forget all" button at the bottom

Use case: user deliberately updated radio firmware and expects a cert change → forget the old pin so the next connect doesn't trip the mismatch dialog.

3. Cache schema bump (with back-compat)

Phase 1 cache shape: {"<host>": "<hex>"}
Phase 2 cache shape: {"<host>": {"fp": "<hex>", "pinnedAt": "<iso8601>"}}

Cache loader must handle the Phase 1 string-only entries (treat as pinnedAt: null) to avoid breaking existing pins on upgrade.

Touch points

File Change
src/core/WanConnection.h Add acceptPresentedCert(), rejectPresentedCert(), signal certFingerprintMismatch(...); defer wan validate write until user decision
src/core/WanConnection.cpp Mismatch path emits signal + stores presented cert pending decision; cache JSON shape bump + back-compat reader
src/gui/SmartLinkApplet.cpp (or wherever SmartLink connect UI lives) Modal dialog wired to the mismatch signal
New: src/gui/PinnedCertsApplet.cpp List/delete UI in Radio Setup → SmartLink

Open questions

  • Defer-vs-disconnect-and-reprompt: preferred to defer the wan validate send until user responds, so the attacker never sees an authenticated session. Alternative is to let the session complete and then disconnect on rejection — simpler code but exposes a live session window to the MITM.
  • First-use prompt? Phase 1 silently pins on first use. Adding a "Pin this cert?" prompt is UX overhead at a moment when the user has no signal to decide on. Recommend keeping silent first-use; only mismatch prompts.

Effort estimate

~half day:

  • WanConnection changes: ~2 hours
  • Mismatch dialog: ~1 hour
  • Pinned Certs Settings applet: ~2 hours
  • Test pass with manual cert rotation: ~30 min

Test plan

Requires SmartLink connection over WAN (cannot exercise from LAN-attached radio). Need either:

  1. Off-LAN test via phone hotspot tether, OR
  2. A second tester / external network

Verify:

  • First-use: pin captured silently
  • Match: silent debug log
  • Mismatch dialog: appears on intentional cert rotation; Accept persists new pin; Reject disconnects cleanly
  • Forget button: removes pin from cache; next connect re-pins
  • Upgrade from Phase 1: existing string-only cache entries don't crash; treated as legitimate prior pins

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    GUIUser interfaceSmartLinkSmartLink WAN remote connectionenhancementImprovement to existing featuremaintainer-reviewRequires maintainer review before any action is takenpriority: highHigh prioritypriority: mediumMedium priority

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions