From 5a484aaafd579387969d50bb650551630a2a958a Mon Sep 17 00:00:00 2001 From: Tim Date: Sun, 18 Oct 2026 07:59:01 -0700 Subject: [PATCH] settings_adoption: forget, giving the device away Removes the stored adoption, its kind, the owner pin and its mark; the device key stays, so the next owner adopts the same DID afresh. The pin goes last, so a power cut part way is finished by the next call. Co-Authored-By: Claude Opus 5.5 --- .../src/settings_adoption.rs | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/crates/rusty_esp_mid-core/src/settings_adoption.rs b/crates/rusty_esp_mid-core/src/settings_adoption.rs index 4ee83fe..f17e961 100644 --- a/crates/rusty_esp_mid-core/src/settings_adoption.rs +++ b/crates/rusty_esp_mid-core/src/settings_adoption.rs @@ -31,6 +31,11 @@ //! with the verifier re-checks the stored container and drops the pin if it //! fails. The settings arrive only over the cable, and the hub verifies the //! signature in full before it treats anyone as the owner. +//! +//! **Giving the device away** is [`forget`]: the grant, the pin and its mark +//! go, the device key stays, so the next owner adopts the same DID as a +//! first adoption. Only a hand on the device asks for it (the firmware's +//! button), never a message. use alloc::string::String; use alloc::vec; @@ -254,6 +259,21 @@ pub fn stored(identity: &I, my_did: &str) -> Result(identity: &mut I) -> Result { + let held = stored_pin(identity)?.is_some(); + identity.remove(KV_ADOPTION)?; + identity.remove(KV_ADOPTION_KIND)?; + identity.remove(KV_PIN_PROVISIONAL)?; + identity.remove(KV_OWNER_PIN)?; + Ok(held) +} + /// Take the settings' adoption into the identity store (see the module /// docs). `my_did` is this device's `did:mata`. pub fn apply( @@ -364,6 +384,50 @@ mod tests { (device, owner, other) } + #[test] + fn forgetting_the_owner_keeps_the_key_and_the_next_owner_adopts_afresh() { + let (device, owner, other) = setup(); + let (mut s, mut id) = (MemoryKv::new(), MemoryKv::new()); + id.put("mid.devkey", &[7; 32]).unwrap(); + assert_eq!(forget(&mut id), Ok(false), "nothing held"); + + s.put( + SETTINGS_ADOPT, + &container(&device, &owner, &owner, 1, STATEMENT_CONTEXT), + ) + .unwrap(); + apply(&mut s, &mut id, &device).unwrap(); + assert!(stored(&id, &device).unwrap().is_some()); + + assert_eq!(forget(&mut id), Ok(true)); + let mut buf = vec![0u8; ADOPT_MAX]; + for k in [ + KV_ADOPTION, + KV_ADOPTION_KIND, + KV_OWNER_PIN, + KV_PIN_PROVISIONAL, + ] { + assert_eq!(id.get(k, &mut buf).unwrap(), None, "{k}"); + } + assert_eq!( + id.get("mid.devkey", &mut buf).unwrap(), + Some(32), + "the key stays" + ); + assert_eq!(stored(&id, &device), Ok(None)); + + // another owner, refused before, is now a first adoption + s.put( + SETTINGS_ADOPT, + &container(&device, &other, &other, 1, STATEMENT_CONTEXT), + ) + .unwrap(); + assert!(matches!( + apply(&mut s, &mut id, &device), + Ok(Outcome::Adopted { owner_did, .. }) if owner_did == other + )); + } + #[test] fn the_stored_adoption_is_read_back_for_the_status() { let (device, owner, other) = setup();