OXYGENHUB

identity export (and every identity lookup) falls back to the key directory in a repository that has not stored the identity yet, checking it hashes to the key; its help names the file it writes e3d071cecfe2

closes #9
Author
Andy McCall @andymccall <[email protected]>
When
· 2026-09-28 19:24 UTC
Signature
✔ signed by Andy McCall
Hash
e3d071cecfe240b44cc4225a6649d78830ba5d6ee9ff8b97684d8e91d8b7e225
Depends on
982864068a59 afaba5f1306d d42369abf180 fce44ae9ad29
Depended on by
a39754d02c58 cbe8499a5a8c cc7a00565161
In bookmarks
agents-in-readme bare-hashes carried-not-removed carry-identical doctor-trust identical-sides keyless-tags main merge-remote-bookmark only-help-103 paths-for-resolutions-and-amend push-notes show-stat subrepo-pin-state switch-says
In tags
v0.1.0 v0.10.0 v0.11.0 v0.2.0 v0.3.0 v0.3.1 v0.4.0 v0.4.1 v0.4.2 v0.4.3 v0.4.4 v0.4.5 v0.4.6 v0.5.0 v0.5.1 v0.6.0 v0.7.0 v0.8.0 v0.9.0
Edits
6 atoms, 0 tree operations, 3 files

Browse the tree at this change

modified crates/oxy-core/src/repo/identity.rs +23 −1
…
292 unchanged lines
293 293
        }
294 294
    }
295 295
296 +
    /// An identity: from this repository's store, or else from the key
297 +
    /// directory, where `oxy identity create` keeps the user's own (a new
298 +
    /// repository has not stored it yet). The file must hash to `key`.
296 299
    pub fn identity(&self, key: &KeyId) -> Result<Identity> {
297 -
        Ok(self.store.get_identity(key)?)
300 +
        match self.store.get_identity(key) {
301 +
            Ok(id) => Ok(id),
302 +
            Err(e) => {
303 +
                let path = self.keys_dir.join(format!("{}.identity", key.to_hex()));
304 +
                match fs::read(&path) {
305 +
                    Ok(bytes) => {
306 +
                        let id = Identity::decode(&bytes)?;
307 +
                        if id.hash() != *key {
308 +
                            return fail(format!(
309 +
                                "{} does not hold identity {}",
310 +
                                path.display(),
311 +
                                key.short(12)
312 +
                            ));
313 +
                        }
314 +
                        Ok(id)
315 +
                    }
316 +
                    Err(_) => Err(e.into()),
317 +
                }
318 +
            }
319 +
        }
298 320
    }
299 321
300 322
    /// Sign a stored change with the configured secret key.
…
186 unchanged lines
modified crates/oxy-core/src/repo/tests.rs +37 −0
…
4768 unchanged lines
4769 4769
    assert!(repo2.load_state(&repo2.bookmark_heads().unwrap()).is_err());
4770 4770
    assert!(repo2.fsck().unwrap().errors() > 0, "fsck reports it");
4771 4771
}
4772 +
4773 +
/// A new repository has not stored the user's identity yet (that happens
4774 +
/// at the first signed change); looking it up falls back to the key
4775 +
/// directory, and a file there that does not hash to the key is refused.
4776 +
#[test]
4777 +
fn identity_is_found_in_the_key_directory_for_a_new_repository() {
4778 +
    hermetic();
4779 +
    let dir = tempfile::tempdir().unwrap();
4780 +
    let home = dir.path().join("home");
4781 +
    let mut first = Repo::init(&dir.path().join("a"))
4782 +
        .unwrap()
4783 +
        .with_home(home.clone());
4784 +
    let (key, _) = first
4785 +
        .create_identity("Ada", "[email protected]", IdentityScope::User)
4786 +
        .unwrap();
4787 +
    let fresh = Repo::init(&dir.path().join("b"))
4788 +
        .unwrap()
4789 +
        .with_home(home.clone());
4790 +
    assert!(!fresh.store().has(&key), "nothing stored yet");
4791 +
    let id = fresh.identity(&key).unwrap();
4792 +
    assert_eq!((id.name.as_str(), id.hash()), ("Ada", key));
4793 +
    // A key file that is not this identity is an error, not a wrong answer.
4794 +
    let keys = fresh.keys_dir.clone();
4795 +
    let (other, _) = first
4796 +
        .create_identity("Bob", "[email protected]", IdentityScope::User)
4797 +
        .unwrap();
4798 +
    fs::copy(
4799 +
        keys.join(format!("{}.identity", other.to_hex())),
4800 +
        keys.join(format!("{}.identity", key.to_hex())),
4801 +
    )
4802 +
    .unwrap();
4803 +
    assert!(fresh
4804 +
        .identity(&key)
4805 +
        .unwrap_err()
4806 +
        .to_string()
4807 +
        .contains("does not hold identity"));
4808 +
}
modified src/cli/mod.rs +1 −1
…
636 unchanged lines
637 637
    Show,
638 638
    /// Write your identity object to a file, to hand to a policy holder
639 639
    Export {
640 -
        /// File to write (default: stdout)
640 +
        /// File to write (default: <key>.identity in the current directory)
641 641
        path: Option<PathBuf>,
642 642
    },
643 643
    /// Sign a host's challenge (hex, at least 16 bytes) to prove you hold your key; prints the signature as hex
…
430 unchanged lines