mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-18 16:46:17 +00:00
Hardens TrustCACertFromBytes against the failure mode behind issue #262 (corrupted /etc/pki/tls/certs/ca-bundle.crt on a SoundTouch 20) and against silent transport-time corruption of our own writes. Three-part change. 1. Atomic write path. The previous flow piped bytes straight into the live bundle via `cat > <path>`; a dropped SSH session or partial write left the device with a half-written trust store and no way to roll back. The new path: - uploads to <bundlePath>.aftertouch.tmp (sibling on the same filesystem, same rw remount), - reads the tmp back over SSH, - validates the readback at the PEM-frame layer + the AfterTouch sentinel bracketing, - atomically `mv`s the tmp into place, - on any verification failure: `rm -f` the tmp; the live bundle is never touched, so there is no rollback semantics to reason about. The .original backup written on first install stays as defense-in-depth (manual recovery for corruption from outside this code path), but it is no longer the primary safety net. 2. New validators in pkg/service/setup/ca_validation.go. - validateCABundleBytes: BEGIN/END marker counts match, every decoded block is a CERTIFICATE with a non-empty body, decoded block count equals BEGIN-marker count (catches a block with unparseable base64 body), trailing non-PEM/non-comment content rejected. - validateAfterTouchLabelBracketing: CALabel appears exactly twice and brackets exactly one CERTIFICATE block. - stripAfterTouchEntries: collapses any number of stale AfterTouch entries from the existing bundle. Older releases reported to have appended without stripping, so long-lived devices can carry several copies; we strip them all and log the cleanup count rather than failing validation. Unpaired sentinels (truncated prior install) surface as a structured anomaly the caller logs and warns about. The validators stay at the PEM-frame layer on purpose — an earlier iteration called x509.ParseCertificate per block and rejected the real ST20 bundle on block 29 (Go 1.23+ disallows negative serial numbers, but Mozilla CCADB still ships ancient CA roots that have them). Shipping that version would have made every legitimate speaker install fail. The corruption mode #262 surfaces at the PEM-framing layer; x509-level checks aren't what we needed. 3. testdata/ca_bundle_st20_pristine.crt is the pristine /etc/pki/tls/certs/ca-bundle.crt captured off a real SoundTouch 20 (firmware 27.0.6.46330.5043500, snapshot 2022-08-04). Mozilla CCADB public dataset, 165 certs, ~251 KB. TestValidateRealSpeakerBundle locks in the cert count and asserts the strip pass is a no-op against a bundle that has never been touched by AfterTouch. Test infrastructure. mockSSH (both the setup-package and the handlers-package copies) now mirrors UploadContent into a private map so a subsequent `cat <path>` on the same path returns what was written there. Lets the tmp-readback step in TrustCACertFromBytes work against tests that only scripted the live-bundle path, without per-test wiring. Two new behavioural tests in setup_test.go: TestTrustCACert_StripsMultipleStaleEntriesSilently (pins the multi-entry cleanup contract) and TestTrustCACert_PostUploadVerificationFailureCleansUpTmp (pins the rollback-free recovery: live bundle untouched, tmp removed). Refs #262. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>