Fix ensure_trusted(): pin by serial number, not the arbitrary id label

id in printers.toml is just a label you chose for config purposes - nothing
ties it to a specific physical printer, and renaming it (or reusing it for
a different unit down the line) would silently break the pin lookup and
re-trigger trust-on-first-connect for hardware that was already trusted.
The serial number is the one thing about a printer that can't change, so
that's what a pin should be keyed by: certs/pinned/<sn>.pem instead of
certs/pinned/<id>.pem.

Verified against a real TLS server: pinned a printer under one id, renamed
it in printers.toml with the sn left unchanged, and confirmed the second
run found the existing pin silently (no re-TOFU) rather than re-pinning.
This commit is contained in:
2026-08-28 21:22:16 +00:00
parent af2f20a45b
commit a77e93940f
2 changed files with 12 additions and 7 deletions
+3 -3
View File
@@ -4,9 +4,9 @@
//! fetch_bambu_cert combined: for each Bambu printer in printers.toml, //! fetch_bambu_cert combined: for each Bambu printer in printers.toml,
//! makes sure it has a working certificate, auto-pinning one via //! makes sure it has a working certificate, auto-pinning one via
//! trust-on-first-connect if the bundled CA doesn't verify. Safe to run //! trust-on-first-connect if the bundled CA doesn't verify. Safe to run
//! repeatedly — a printer that's already pinned (certs/pinned/<id>.pem) //! repeatedly — a printer that's already pinned (certs/pinned/<serial
//! just gets re-verified against its pin, no network trust decision is //! number>.pem) just gets re-verified against its pin, no network trust
//! made again. //! decision is made again.
use continuum_proxy::fleet; use continuum_proxy::fleet;
use continuum_proxy::printer::PrinterHandle; use continuum_proxy::printer::PrinterHandle;
+9 -4
View File
@@ -168,14 +168,19 @@ impl BambuGenericPrinter {
/// the "smooth, no manual steps" version of the fetch_bambu_cert /// the "smooth, no manual steps" version of the fetch_bambu_cert
/// workflow, built the way SSH handles host keys: /// workflow, built the way SSH handles host keys:
/// ///
/// - A pinned certificate already on disk (`cert_dir/<id>.pem`) is used /// - A pinned certificate already on disk (`cert_dir/<sn>.pem`) is used
/// directly. No new trust decision gets made on every run — that /// directly. No new trust decision gets made on every run — that
/// already happened once, this just re-verifies against it. /// already happened once, this just re-verifies against it. Keyed by
/// *serial number*, not `id`: `id` is just an arbitrary label you
/// picked in printers.toml — rename a printer's `id` and the pin
/// should still find it, because a pin certifies "this physical
/// printer", and the serial number is the one thing about it that
/// can't change.
/// - No pin yet, and the bundled CA verifies fine (current-generation /// - No pin yet, and the bundled CA verifies fine (current-generation
/// printers): nothing to do. /// printers): nothing to do.
/// - No pin yet, and the bundled CA does *not* verify (P1P, etc.): /// - No pin yet, and the bundled CA does *not* verify (P1P, etc.):
/// fetches the certificate the printer presents, confirms it /// fetches the certificate the printer presents, confirms it
/// produces a working connection, and saves it to `cert_dir/<id>.pem` /// produces a working connection, and saves it to `cert_dir/<sn>.pem`
/// for every run after this one. This is the one moment a MITM /// for every run after this one. This is the one moment a MITM
/// active on your network *right now* could plant a certificate that /// active on your network *right now* could plant a certificate that
/// gets trusted from then on — the same tradeoff SSH accepts on a /// gets trusted from then on — the same tradeoff SSH accepts on a
@@ -186,7 +191,7 @@ impl BambuGenericPrinter {
/// since that's exactly the signal pinning exists to give you (the /// since that's exactly the signal pinning exists to give you (the
/// cert rotated, or something worse). /// cert rotated, or something worse).
pub fn ensure_trusted(&mut self, cert_dir: &Path) -> anyhow::Result<()> { pub fn ensure_trusted(&mut self, cert_dir: &Path) -> anyhow::Result<()> {
let pinned_path = cert_dir.join(format!("{}.pem", self.base.id)); let pinned_path = cert_dir.join(format!("{}.pem", self.sn));
if matches!(self.tls, BambuTls::BundledCa) && pinned_path.exists() { if matches!(self.tls, BambuTls::BundledCa) && pinned_path.exists() {
self.tls = BambuTls::Custom(std::fs::read(&pinned_path)?); self.tls = BambuTls::Custom(std::fs::read(&pinned_path)?);