Files
Jonathan BennettandClaude Opus 5 c7826791d4 feat(portduino): admin keys in config.yaml, beyond the protobuf's three (#12056)
* feat(portduino): admin keys in config.yaml, beyond the protobuf's three

meshtasticd can now list the public keys authorized to administer the node
in its own config, as Security.AdminKeys, and list more than the three
config.security.admin_key holds. The host's file is the authority: it is
root-owned, so unlike the persisted protobuf an authorized remote cannot
rewrite it, and applyHostKeys() re-asserts it at boot and after every
remote write of the security config. A key removed from the file loses
access on the next boot instead of lingering in flash.

The protobuf array stays at three. It is not an arbitrary cap: a security
set_config already encodes to ~207 bytes against the 221 a PKC admin
packet can carry (233 payload bytes less 12 of PKC overhead), so a fourth
key would not fit in one admin message on any platform.

Four sites decided who may administer a node, each unrolling
admin_key[0..2] by hand - the PKC gate in AdminModule, the same gate in
DMShell, the is_managed guard, and the admin-key trial decrypt in
Router::perhapsDecode. They now share AdminKeys::isAuthorized(), so a key
source reaches all four at once, and enumeration skips empty slots rather
than trusting admin_key_count.

Each configured key costs one trial decryption per PKI packet from an
unknown sender, so the host list is capped at 16 and duplicates are
dropped. Files in config.d append to the list, one per operator. A key
that is not exactly 32 canonical base64 bytes stops meshtasticd rather
than silently authorizing something else, and --check reports it with its
line instead.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SP5hpFwdTBC2K9ggGFLpcs

* test(portduino): config-check fixtures for Security.AdminKeys

bin/test-config-check.sh drives a built meshtasticd against the fixtures,
which is the only place the two process-level behaviours here are
reachable: what --check reports, and that a normal boot refuses the file.

A clean two-key list, a key three characters short of 32 bytes, and one
key written as a scalar instead of a list - the last two as errors under
--check and as a non-zero exit on a normal boot, since a truncated admin
key would authorize something other than what was written and a file
whose only key is unreadable would otherwise boot a node nobody can
administer. adminkeys-append/ pins the append-across-config.d rule by
value through --output-yaml, with the first key in config.yaml so the
effective list does not depend on the filesystem's order.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SP5hpFwdTBC2K9ggGFLpcs

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-10-03 03:19:01 +00:00
..