OXYGENHUB

Coverage / crates/oxy-core/src/repo/identity.rs 88.6%

507 of 572 lines run, 61 of 92 functions · run #416 on main · run missed

1
//! Identities, signing, and signature verification (spec §14).
2
3
use super::*;
4
5
/// Outcome of verifying the signatures on a change (spec §14.3, §14.4).
6
#[derive(Clone, Debug, PartialEq, Eq)]
7
pub enum SignatureStatus {
8
    Unsigned,
9
    /// `revoked` marks a signature that verifies but whose key has since
10
    /// been revoked by policy or by a revocation this repository recorded.
11
    Valid {
12
        key: KeyId,
13
        name: String,
14
        email: String,
15
        sealed: bool,
16
        revoked: bool,
17
    },
18
    Invalid(String),
19
}
20
21
/// Make a user identity with no repository (oxygen#144), so a first
22
/// identity comes before any repository: its secret and a copy of it go to
23
/// this home's keys, and the user file names it as the author, as `oxy
24
/// identity create` does inside one. A repository reads it from the keys
25
/// when it first signs with it. `name` and `email` default to the user
26
/// file's author, then to `$USER` and `OXY_AUTHOR_*`.
271×
pub fn create_user_identity(
281×
    name: Option<&str>,
291×
    email: Option<&str>,
301×
    expires: Option<u64>,
311×
) -> Result<(KeyId, Identity)> {
321×
    let user = default_user_config();
331×
    let defaults = Config::load(Some(&user), Path::new(""))?;
341×
    let name = name.map(str::to_string).unwrap_or(defaults.author_name);
351×
    let email = email.map(str::to_string).unwrap_or(defaults.author_email);
361×
    let sk = SecretKey::generate();
371×
    let mut id = sk.identity(&name, &email, Hlc::now().wall_ms);
381×
    id.expires = expires;
391×
    let key = id.hash();
401×
    let keys = default_keys_dir();
411×
    fs::create_dir_all(&keys)?;
421×
    crate::durable::write_atomic_private(
431×
        &keys.join(format!("{}.secret", key.to_hex())),
441×
        &sk.encode(&key),
450×
    )?;
461×
    write_atomic(
471×
        &keys.join(format!("{}.identity", key.to_hex())),
481×
        &id.encode(),
490×
    )?;
501×
    Config::set_user(&user, "author.name", &id.name)?;
511×
    Config::set_user(&user, "author.email", &id.email)?;
521×
    Config::set_user(&user, "author.key", &key.to_hex())?;
531×
    Ok((key, id))
541×
}
55
56
/// The outcome of replacing the configured identity (spec §14.2).
57
#[derive(Clone, Debug)]
58
pub struct Rotation {
59
    pub old: KeyId,
60
    pub new: KeyId,
61
    pub identity: Identity,
62
    /// The policy update that moved the old key's grants to the new key,
63
    /// when the repository has a policy naming the old key.
64
    pub policy: Option<PolicyUpdate>,
65
}
66
67
impl Repo {
68
    // ------------------------------------------------------------ identity
69
70
    /// Generate a composite key pair, store its identity object, keep the
71
    /// secret in the key directory, and record the author at `scope`.
7272×
    pub fn create_identity(
7372×
        &mut self,
7472×
        name: &str,
7572×
        email: &str,
7672×
        scope: IdentityScope,
7772×
    ) -> Result<(KeyId, Identity)> {
7872×
        self.create_identity_with(name, email, scope, None)
7972×
    }
80
81
    /// `create_identity` with an expiry (milliseconds since the epoch).
8275×
    pub fn create_identity_with(
8375×
        &mut self,
8475×
        name: &str,
8575×
        email: &str,
8675×
        scope: IdentityScope,
8775×
        expires: Option<u64>,
8875×
    ) -> Result<(KeyId, Identity)> {
8975×
        let sk = SecretKey::generate();
9075×
        let mut id = sk.identity(name, email, Hlc::now().wall_ms);
9175×
        id.expires = expires;
9275×
        let key = self.install_key(&id, &sk)?;
9375×
        self.set_author(&id, &key, scope)?;
9475×
        Ok((key, id))
9575×
    }
96
97
    /// Store an identity and keep its secret (and a copy of the identity)
98
    /// in the key directory.
9993×
    fn install_key(&self, id: &Identity, sk: &SecretKey) -> Result<KeyId> {
10093×
        let key = self.store.put_identity(id)?;
10193×
        fs::create_dir_all(&self.keys_dir)?;
10293×
        let path = self.keys_dir.join(format!("{}.secret", key.to_hex()));
10393×
        crate::durable::write_atomic_private(&path, &sk.encode(&key))?;
10493×
        write_atomic(
10593×
            &self.keys_dir.join(format!("{}.identity", key.to_hex())),
10693×
            &id.encode(),
1070×
        )?;
10893×
        Ok(key)
10993×
    }
110
11194×
    fn set_author(&mut self, id: &Identity, key: &KeyId, scope: IdentityScope) -> Result<()> {
11294×
        match scope {
113
            IdentityScope::Repo => {
11437×
                self.config.set("author.name", &id.name)?;
11537×
                self.config.set("author.email", &id.email)?;
11637×
                self.config.set("author.key", &key.to_hex())?;
11737×
                self.save_config()?;
118
            }
119
            IdentityScope::User => {
12057×
                Config::set_user(&self.user_config, "author.name", &id.name)?;
12157×
                Config::set_user(&self.user_config, "author.email", &id.email)?;
12257×
                Config::set_user(&self.user_config, "author.key", &key.to_hex())?;
12357×
                self.config = Config::load(Some(&self.user_config), &self.oxy.join("config"))?;
124
            }
125
        }
12694×
        Ok(())
12794×
    }
128
129
    /// The scope that holds the configured author key.
1300×
    fn author_scope(&self) -> IdentityScope {
1310×
        if self.config.repo_layer().contains_key("author.key") {
1320×
            IdentityScope::Repo
133
        } else {
1340×
            IdentityScope::User
135
        }
1360×
    }
137
138
    /// Record a successor key everywhere the old one was named: the
139
    /// repository layer when it pins the old key, and the user file when
140
    /// it names the old key, so every checkout that followed the old key
141
    /// follows the new one (oxygen#69). A key named nowhere falls back to
142
    /// the scope that resolved it.
14318×
    fn record_successor(&mut self, id: &Identity, old: &KeyId, new_key: &KeyId) -> Result<()> {
14436×
        let names = |v: Option<&String>| v.and_then(|k| KeyId::from_hex(k)) == Some(*old);
14518×
        let repo_pins = names(self.config.repo_layer().get("author.key"));
14618×
        let user_entries = super::config::read_config_file(&self.user_config).unwrap_or_default();
14718×
        let user_names = names(user_entries.get("author.key"));
14818×
        if !repo_pins && !user_names {
1490×
            return self.set_author(id, new_key, self.author_scope());
15018×
        }
15118×
        if user_names {
1527×
            self.set_author(id, new_key, IdentityScope::User)?;
15311×
        }
15418×
        if repo_pins {
15512×
            self.set_author(id, new_key, IdentityScope::Repo)?;
1566×
        }
15718×
        Ok(())
15818×
    }
159
160
    /// Replace the configured identity by `new` signed with `sk`: store it,
161
    /// record a `rotate` attestation signed by the old key, move the old
162
    /// key's policy grants to the new key, and switch the author config.
16318×
    fn replace_identity(
16418×
        &mut self,
16518×
        new: Identity,
16618×
        sk: SecretKey,
16718×
        command: &str,
16818×
    ) -> Result<Rotation> {
16918×
        let Some((old, old_sk)) = self.secret_key()? else {
1700×
            return fail("no secret key: run `oxy identity create` first");
171
        };
17218×
        if new.previous != Some(old) {
1730×
            return fail("the successor identity must name the current key as previous");
17418×
        }
17518×
        let new_key = self.install_key(&new, &sk)?;
17618×
        let attest = Attest {
17718×
            repo: self.id()?,
17818×
            hlc: Hlc::now(),
17918×
            body: AttestBody::Rotate { new: new_key },
180
        };
18118×
        let r = self.store.put_attest(&attest)?;
18218×
        self.store
18318×
            .put_signature(&r, &old_sk.sign(Domain::Attest, &old, &r))?;
18418×
        let policy = match self.current_policy()? {
1859×
            Some((_, sp)) if sp.policy.mentions(&old) => {
1869×
                let mut p = self.next_policy()?;
1879×
                p.substitute_key(&old, new.clone());
1889×
                Some(self.sign_policy_with(p, &[(old, &old_sk), (new_key, &sk)], command)?)
189
            }
1909×
            _ => None,
191
        };
19218×
        self.record_successor(&new, &old, &new_key)?;
19318×
        Ok(Rotation {
19418×
            old,
19518×
            new: new_key,
19618×
            identity: new,
19718×
            policy,
19818×
        })
19918×
    }
200
201
    /// Rotate to a fresh key pair; the new identity names the old one as
202
    /// `previous` and keeps its name, email, delegates, and expiry.
2033×
    pub fn rotate_identity(&mut self) -> Result<Rotation> {
2043×
        let Some((old, _)) = self.secret_key()? else {
2050×
            return fail("no secret key: run `oxy identity create` first");
206
        };
2073×
        let cur = self.identity(&old)?;
2083×
        let sk = SecretKey::generate();
2093×
        let mut id = sk.identity(&cur.name, &cur.email, Hlc::now().wall_ms);
2103×
        id.previous = Some(old);
2113×
        id.delegates = cur.delegates.clone();
2123×
        id.expires = cur.expires;
2133×
        self.replace_identity(id, sk, "oxy identity rotate")
2143×
    }
215
216
    /// Re-issue the identity with the same keys and a new expiry.
2172×
    pub fn renew_identity(&mut self, expires: Option<u64>) -> Result<Rotation> {
2182×
        let Some((old, sk)) = self.secret_key()? else {
2190×
            return fail("no secret key: run `oxy identity create` first");
220
        };
2212×
        let cur = self.identity(&old)?;
2222×
        let id = cur.successor(cur.delegates.clone(), expires, Hlc::now().wall_ms);
2232×
        self.replace_identity(id, sk, "oxy identity renew")
2242×
    }
225
226
    /// Let `delegate` act on this identity's behalf within `scope` until
227
    /// `expires` (spec §14.2). Delegates are part of the identity object,
228
    /// so this re-issues it with the same keys.
22912×
    pub fn delegate_add(
23012×
        &mut self,
23112×
        delegate: Identity,
23212×
        scope: Scope,
23312×
        expires: Option<u64>,
23412×
    ) -> Result<Rotation> {
23512×
        self.delegate_add_many(vec![delegate], scope, expires)
23612×
    }
237
238
    /// As [`Repo::delegate_add`], for several delegates in one rotation.
23913×
    pub fn delegate_add_many(
24013×
        &mut self,
24113×
        new_delegates: Vec<Identity>,
24213×
        scope: Scope,
24313×
        expires: Option<u64>,
24413×
    ) -> Result<Rotation> {
24513×
        let Some((old, sk)) = self.secret_key()? else {
2460×
            return fail("no secret key: run `oxy identity create` first");
247
        };
24813×
        if scope.is_empty() {
2490×
            return fail("a delegate needs at least one scope: changes, refs or seal");
25013×
        }
25113×
        if new_delegates.is_empty() {
2520×
            return fail("name at least one identity file");
25313×
        }
25413×
        let cur = self.identity(&old)?;
25513×
        let mut delegates = cur.delegates.clone();
25613×
        let mut added = Vec::new();
25714×
        for delegate in new_delegates {
25814×
            let dk = self.store.put_identity(&delegate)?;
25914×
            added.push(dk);
26014×
            if dk == old {
2610×
                return fail("an identity cannot delegate to itself");
26214×
            }
26314×
            if cur.delegate(&dk).is_some()
26412×
                || delegates
26512×
                    .iter()
26612×
                    .skip(cur.delegates.len())
26712×
                    .any(|d| d.key == dk)
268
            {
2692×
                return fail(format!(
270
                    "{} is already a delegate; remove it first to change its scope or expiry",
2712×
                    dk.short(12)
272
                ));
27312×
            }
27412×
            delegates.push(Delegate {
27512×
                key: dk,
27612×
                scope,
27712×
                expires,
27812×
            });
279
        }
28011×
        let id = cur.successor(delegates, cur.expires, Hlc::now().wall_ms);
28111×
        self.replace_identity(
28211×
            id,
28311×
            sk,
28411×
            &format!(
285
                "oxy identity delegate add {} --scope {scope}",
28611×
                added
28711×
                    .iter()
28812×
                    .map(|k| k.short(12))
28911×
                    .collect::<Vec<_>>()
29011×
                    .join(" ")
291
            ),
292
        )
29313×
    }
294
295
    /// Withdraw a delegation; re-issues the identity with the same keys.
2963×
    pub fn delegate_remove(&mut self, delegate: &KeyId) -> Result<Rotation> {
2973×
        let Some((old, sk)) = self.secret_key()? else {
2980×
            return fail("no secret key: run `oxy identity create` first");
299
        };
3003×
        let cur = self.identity(&old)?;
3013×
        if cur.delegate(delegate).is_none() {
3021×
            return fail(format!("{} is not a delegate", delegate.short(12)));
3032×
        }
3042×
        let delegates = cur
3052×
            .delegates
3062×
            .iter()
3072×
            .copied()
3082×
            .filter(|d| d.key != *delegate)
3092×
            .collect();
3102×
        let id = cur.successor(delegates, cur.expires, Hlc::now().wall_ms);
3112×
        self.replace_identity(
3122×
            id,
3132×
            sk,
3142×
            &format!("oxy identity delegate remove {}", delegate.short(12)),
315
        )
3163×
    }
317
318
    /// Revoke `key` (your own, one you delegated to, or any key when you
319
    /// hold a policy root): record a signed `revoke` attestation and a
320
    /// local marker, and drop the key from the policy where it has one.
321
    /// Revoking a delegate does not re-issue the identity; `delegate_remove`
322
    /// does that, and the CLI does both.
3235×
    pub fn revoke_identity(&self, key: &KeyId, reason: &str) -> Result<Option<PolicyUpdate>> {
3245×
        let Some((own, sk)) = self.secret_key()? else {
3250×
            return fail("no secret key: run `oxy identity create` first");
326
        };
3275×
        let policy = self.current_policy()?.map(|(_, sp)| sp.policy);
3285×
        let is_root = policy.as_ref().is_some_and(|p| p.roots.contains(&own));
3295×
        let is_delegate = self.identity(&own)?.delegate(key).is_some();
3305×
        if *key != own && !is_root && !is_delegate {
3311×
            return fail("only the key's holder, the identity that delegated to it, or a policy root can revoke it");
3324×
        }
3334×
        let attest = Attest {
3344×
            repo: self.id()?,
3354×
            hlc: Hlc::now(),
3364×
            body: AttestBody::Revoke {
3374×
                key: *key,
3384×
                reason: reason.into(),
3394×
            },
340
        };
3414×
        let r = self.store.put_attest(&attest)?;
3424×
        self.store
3434×
            .put_signature(&r, &sk.sign(Domain::Attest, &own, &r))?;
3444×
        let dir = self.oxy.join("revoked");
3454×
        fs::create_dir_all(&dir)?;
3464×
        write_atomic(&dir.join(key.to_hex()), r.to_hex().as_bytes())?;
3471×
        match policy {
3481×
            Some(p) if p.mentions(key) => Ok(Some(self.policy_revoke(key)?)),
3493×
            _ => Ok(None),
350
        }
3515×
    }
352
353
    /// Whether `key` is revoked by the policy or by a recorded revocation.
354262×
    pub fn is_revoked(&self, key: &KeyId) -> Result<bool> {
355262×
        if self.oxy.join("revoked").join(key.to_hex()).exists() {
3565×
            return Ok(true);
357257×
        }
358257×
        Ok(self
359257×
            .current_policy()?
360257×
            .is_some_and(|(_, sp)| sp.policy.is_revoked(key)))
361262×
    }
362
363
    /// The key this repository signs with: `author.signer` when set (an
364
    /// agent's own key, acting for the author), else `author.key`.
3651019×
    pub fn signing_key(&self) -> Option<KeyId> {
3661019×
        if self.config.signer_keyless {
367
            // The key certified last, once this command has signed.
3681×
            return self.keyless_key.borrow().as_ref().map(|(k, _, _)| *k);
3691018×
        }
3701018×
        self.config.signer_key.or(self.config.author_key)
3711019×
    }
372
373
    /// The secret key for `author.signer`, else `author.key`, if configured
374
    /// and present on disk. The identity object is kept beside the secret
375
    /// so a repository that has never seen it (a fresh clone) can store it
376
    /// when it first signs.
377796×
    pub fn secret_key(&self) -> Result<Option<(KeyId, SecretKey)>> {
378796×
        if self.config.signer_keyless {
3791×
            return self.keyless_secret().map(Some);
380795×
        }
381795×
        let Some(key) = self.signing_key() else {
382220×
            return Ok(None);
383
        };
384575×
        let path = self.keys_dir.join(format!("{}.secret", key.to_hex()));
385575×
        match fs::read(&path) {
386574×
            Ok(b) => {
387574×
                let (k, sk) = SecretKey::decode(&b)?;
388574×
                if k != key {
3890×
                    return fail(format!(
390
                        "{} holds a different key than its name",
3910×
                        path.display()
392
                    ));
393574×
                }
394574×
                if !self.store.has(&key) {
3952×
                    let id_path = self.keys_dir.join(format!("{}.identity", key.to_hex()));
3962×
                    if let Ok(bytes) = fs::read(&id_path) {
3972×
                        let id = Identity::decode(&bytes)?;
3982×
                        if id.hash() == key {
3992×
                            self.store.put_identity(&id)?;
4000×
                        }
4010×
                    }
402572×
                }
403574×
                Ok(Some((k, sk)))
404
            }
4051×
            Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(None),
4060×
            Err(e) => Err(e.into()),
407
        }
408796×
    }
409
410
    /// An identity: from this repository's store, or else from the key
411
    /// directory, where `oxy identity create` keeps the user's own (a new
412
    /// repository has not stored it yet). The file must hash to `key`.
413332×
    pub fn identity(&self, key: &KeyId) -> Result<Identity> {
414332×
        match self.store.get_identity(key) {
415328×
            Ok(id) => Ok(id),
4164×
            Err(e) => {
4174×
                let path = self.keys_dir.join(format!("{}.identity", key.to_hex()));
4184×
                match fs::read(&path) {
4192×
                    Ok(bytes) => {
4202×
                        let id = Identity::decode(&bytes)?;
4212×
                        if id.hash() != *key {
4221×
                            return fail(format!(
423
                                "{} does not hold identity {}",
4241×
                                path.display(),
4251×
                                key.short(12)
426
                            ));
4271×
                        }
4281×
                        Ok(id)
429
                    }
430
                    // The configured author's identity is neither in the
431
                    // repository nor in this home's keys: say why and what
432
                    // to do (oxygen#82), rather than "object … not found".
4332×
                    Err(_) if self.config.author_key == Some(*key) => fail(format!(
434
                        "this repository has no identity {} (author.key, set in {}): it is not in the repository, nor in {}.\n\
435
                         An agent home names its human's identity, and each delegation rotates that identity, so the key goes stale. \
436
                         Pin the identity this repository uses, as its owner sees it: `oxy identity show` in the owner's shell gives the key, \
437
                         then `oxy config author.key <KEY>` here; or ask the owner.",
4382×
                        key.short(12),
4392×
                        if self.config.is_repo_set("author.key") {
4401×
                            "this repository's config"
441
                        } else {
4421×
                            "the user config"
443
                        },
4442×
                        self.keys_dir.display()
445
                    )),
4460×
                    Err(_) => Err(e.into()),
447
                }
448
            }
449
        }
450332×
    }
451
452
    /// Sign a stored change with the configured secret key.
45346×
    pub fn sign_change(&self, hash: &ChangeHash) -> Result<Signature> {
45446×
        let Some((key, sk)) = self.secret_key()? else {
4550×
            return fail("no secret key: run `oxy identity create` first");
456
        };
45746×
        let sig = sk.sign(Domain::Change, &key, hash);
45846×
        self.store.put_signature(hash, &sig)?;
45946×
        Ok(sig)
46046×
    }
461
462
    /// Check the signatures on a change against the author-or-delegate rule.
46324×
    pub fn verify_change(&self, hash: &ChangeHash) -> Result<SignatureStatus> {
46424×
        self.verify_change_known(hash, None)
46524×
    }
466
467
    /// `verify_change`, with the change's metadata when the caller has it
468
    /// (a change object can be as large as the tree it adds). Signatures
469
    /// are looked up first: an unsigned change needs no metadata.
470445×
    fn verify_change_known(
471445×
        &self,
472445×
        hash: &ChangeHash,
473445×
        known: Option<&crate::state::ChangeMeta>,
474445×
    ) -> Result<SignatureStatus> {
475445×
        let sigs = self.store.signatures(hash)?;
476445×
        if sigs.is_empty() {
477350×
            return Ok(SignatureStatus::Unsigned);
47895×
        }
479
        let read;
48095×
        let change = match known {
48117×
            Some(m) => m,
482
            None => {
48378×
                read = self.store.get_change_meta(hash)?;
48478×
                &read
485
            }
486
        };
48795×
        let mut last_err = String::new();
48895×
        for sig in &sigs {
48995×
            match self.check_signer(change, sig, Domain::Change, hash) {
49089×
                Ok(id) => {
491
                    return Ok(SignatureStatus::Valid {
49289×
                        key: sig.key,
49389×
                        name: self.certified_subject(&sig.key).unwrap_or(id.name),
49489×
                        email: id.email,
495
                        sealed: false,
49689×
                        revoked: self.is_revoked(&sig.key)?,
497
                    })
498
                }
4996×
                Err(e) => last_err = e,
500
            }
501
        }
5026×
        Ok(SignatureStatus::Invalid(last_err))
503445×
    }
504
505
    /// Whether `signer` is a short-lived key acting for `author` under a
506
    /// grant (spec §14.7): its certificate verifies with its issuer and
507
    /// carries a grant `author` signed, for the certificate's subject and
508
    /// issuer, giving `needed` at `at_ms` (the change's time, as for a
509
    /// delegation). Whether the certificate covered the time it signed is
510
    /// the policy's check, at a time the signer did not choose. `None`
511
    /// when it does; otherwise why not.
51214×
    fn granted(
51314×
        &self,
51414×
        author: &KeyId,
51514×
        signer: &KeyId,
51614×
        needed: Scope,
51714×
        at_ms: u64,
51814×
    ) -> std::result::Result<Option<String>, String> {
51914×
        let neither = || {
5207×
            Some(format!(
5217×
                "key {} is neither the author nor a delegate",
5227×
                signer.short(12)
5237×
            ))
5247×
        };
52514×
        let Ok(Some(c)) = self.store.certificate_of(signer) else {
5264×
            return Ok(neither());
527
        };
52810×
        let Ok(sc) = self.store.get_certificate(&c) else {
5290×
            return Ok(neither());
530
        };
53110×
        let Some(g) = &sc.cert.grant else {
5321×
            return Ok(neither());
533
        };
5349×
        let who = signer.short(12);
5359×
        let issuer = self
5369×
            .store
5379×
            .get_identity(&sc.cert.issuer)
5389×
            .map_err(|_| format!("key {who}'s certificate names an unknown issuer"))?;
5399×
        sc.verify(&issuer)
5409×
            .map_err(|e| format!("key {who}'s certificate: {e}"))?;
541
        // A grant by an earlier key of the author's identity counts while
542
        // it was made before that key was rotated away (oxygen#135).
5439×
        if g.grant.grantor != *author && !self.granted_before_rotation(author, &g.grant) {
5442×
            return Ok(neither());
5457×
        }
5467×
        let grantor = self
5477×
            .identity(&g.grant.grantor)
5487×
            .map_err(|_| format!("key {who}'s grant is by an unknown author"))?;
5497×
        g.verify(&grantor)
5507×
            .map_err(|e| format!("key {who}'s grant: {e}"))?;
5517×
        if g.grant.subject != sc.cert.subject || g.grant.issuer != sc.cert.issuer {
5520×
            return Err(format!(
5530×
                "key {who}'s grant is for {} from issuer {}, not its certificate's",
5540×
                g.grant.subject,
5550×
                g.grant.issuer.short(12)
5560×
            ));
5577×
        }
5587×
        if !sc.cert.scope.has(needed) || !g.grant.scope.has(needed) {
5590×
            return Err(format!("key {who} is not granted {needed}"));
5607×
        }
5617×
        if !g.grant.covers(needed, at_ms) {
5622×
            return Err(format!(
5632×
                "key {who}'s grant did not cover the time the change was made"
5642×
            ));
5655×
        }
5665×
        Ok(None)
56714×
    }
568
569
    /// Whether `grant` is by a key `author`'s identity was rotated from,
570
    /// made before that rotation (oxygen#135). An owner rotates with every
571
    /// delegation, and a repository may name the newest key as the author
572
    /// while the agent's grant is by an older one; the grant stays good, as
573
    /// a policy keeps a predecessor's signatures from before the rotation
574
    /// (spec §14.5), but a key rotated away grants nothing new.
5753×
    fn granted_before_rotation(&self, author: &KeyId, grant: &crate::cert::Grant) -> bool {
5763×
        let mut cur = self.identity(author).ok();
5773×
        for _ in 0..32 {
5783×
            let Some(id) = cur else {
5790×
                return false;
580
            };
5812×
            match id.previous {
5822×
                Some(prev) if prev == grant.grantor => return grant.not_before < id.created,
5830×
                Some(prev) => cur = self.identity(&prev).ok(),
5841×
                None => return false,
585
            }
586
        }
5870×
        false
5883×
    }
589
590
    /// The subject a short-lived key was certified for (spec §14.7), such
591
    /// as `agent:claude@andy`: what names it, since its identity is only
592
    /// a fresh key's.
593247×
    pub fn certified_subject(&self, key: &KeyId) -> Option<String> {
594247×
        let c = self.store.certificate_of(key).ok()??;
5959×
        Some(self.store.get_certificate(&c).ok()?.cert.subject)
596247×
    }
597
598
    /// Verify `sig` over `target` and check the author-or-delegate rule for `change`.
599260×
    fn check_signer(
600260×
        &self,
601260×
        change: &crate::state::ChangeMeta,
602260×
        sig: &Signature,
603260×
        domain: Domain,
604260×
        target: &ObjectRef,
605260×
    ) -> std::result::Result<Identity, String> {
606260×
        let id = self
607260×
            .store
608260×
            .get_identity(&sig.key)
609260×
            .map_err(|_| format!("unknown key {}", sig.key.short(12)))?;
610
        // The author-or-delegate rule (spec §14.4). A change whose author is
611
        // the zero key names nobody (Git imports, history from before an
612
        // identity existed), so any key may adopt it with a seal; policy
613
        // then authorises that key for the reference as usual. A delegate
614
        // acts within its scope, signing changes or sealing them, and only
615
        // until its delegation expires (§14.2).
616260×
        if !change.author.is_zero() && sig.key != change.author {
61734×
            let needed = match domain {
61822×
                Domain::Change => Scope::CHANGES,
61912×
                _ => Scope::SEAL,
620
            };
62134×
            let delegation = self
62234×
                .store
62334×
                .get_identity(&change.author)
62434×
                .ok()
62534×
                .and_then(|a| a.delegate(&sig.key).copied());
62619×
            match delegation {
627
                // A certified key acting under the author's grant (§14.7).
628
                None => {
6297×
                    if let Some(why) =
63014×
                        self.granted(&change.author, &sig.key, needed, change.hlc.wall_ms)?
631
                    {
6327×
                        return Err(why);
6335×
                    }
634
                }
63520×
                Some(d) if !d.scope.has(needed) => {
6361×
                    return Err(format!(
637
                        "key {} is a delegate for {} only and may not {}",
6381×
                        sig.key.short(12),
639
                        d.scope,
6401×
                        if needed == Scope::SEAL {
6411×
                            "seal"
642
                        } else {
6430×
                            "sign changes"
644
                        }
645
                    ))
646
                }
64719×
                Some(d) if !d.allows(needed, change.hlc.wall_ms) => {
6481×
                    return Err(format!(
6491×
                        "key {}'s delegation had expired when the change was made",
6501×
                        sig.key.short(12)
6511×
                    ))
652
                }
65318×
                Some(_) => {}
654
            }
655226×
        }
656249×
        if id.is_expired_at(change.hlc.wall_ms) {
6571×
            return Err(format!(
6581×
                "key {} had expired when the change was made",
6591×
                sig.key.short(12)
6601×
            ));
661248×
        }
662248×
        verify_signature(&id, sig, domain, target).map_err(|e| e.to_string())?;
663247×
        Ok(id)
664260×
    }
665
666
    /// Like `verify_change`, but a change with no valid individual signature
667
    /// is also authenticated by a valid seal covering it (spec §14.4).
668343×
    pub fn authenticate_change(&self, hash: &ChangeHash) -> Result<SignatureStatus> {
669343×
        self.authenticate_change_known(hash, None)
670343×
    }
671
672
    /// `authenticate_change`, with the change's metadata when the caller
673
    /// has it (from a log, say), so the change itself need not be read.
674421×
    pub fn authenticate_change_known(
675421×
        &self,
676421×
        hash: &ChangeHash,
677421×
        known: Option<&crate::state::ChangeMeta>,
678421×
    ) -> Result<SignatureStatus> {
679421×
        let direct = self.verify_change_known(hash, known)?;
680421×
        if matches!(direct, SignatureStatus::Valid { .. }) {
68174×
            return Ok(direct);
682347×
        }
683347×
        let mut last_err = match &direct {
6842×
            SignatureStatus::Invalid(e) => e.clone(),
685345×
            _ => String::new(),
686
        };
687347×
        let seals = self.store.seals_for(hash)?;
688347×
        if seals.is_empty() {
689182×
            return Ok(match last_err.is_empty() {
690180×
                true => SignatureStatus::Unsigned,
6912×
                false => SignatureStatus::Invalid(last_err),
692
            });
693165×
        }
694
        let read;
695165×
        let change = match known {
6960×
            Some(m) => m,
697
            None => {
698165×
                read = self.store.get_change_meta(hash)?;
699165×
                &read
700
            }
701
        };
702165×
        for seal_ref in seals {
703165×
            let attest = self.store.get_attest(&seal_ref)?;
704165×
            let AttestBody::Seal { hashes } = &attest.body else {
7050×
                continue;
706
            };
707165×
            if !hashes.contains(hash) {
7080×
                continue;
709165×
            }
710165×
            for sig in self.store.signatures(&seal_ref)? {
711165×
                match self.check_signer(change, &sig, Domain::Attest, &seal_ref) {
712158×
                    Ok(id) => {
713
                        return Ok(SignatureStatus::Valid {
714158×
                            key: sig.key,
715158×
                            name: self.certified_subject(&sig.key).unwrap_or(id.name),
716158×
                            email: id.email,
717
                            sealed: true,
718158×
                            revoked: self.is_revoked(&sig.key)?,
719
                        })
720
                    }
7217×
                    Err(e) => last_err = e,
722
                }
723
            }
724
        }
7257×
        if last_err.is_empty() {
7260×
            Ok(SignatureStatus::Unsigned)
727
        } else {
7287×
            Ok(SignatureStatus::Invalid(last_err))
729
        }
730421×
    }
731
732
    /// Store and sign an attestation with the configured key.
733
    /// Seal changes with the identity key (spec §14.4): a stored, indexed
734
    /// seal attestation over `hashes`, pushed with the next push. Returns
735
    /// the attestation's reference.
7363×
    pub fn seal(&self, hashes: &[ChangeHash]) -> Result<ObjectRef> {
7373×
        if hashes.is_empty() {
7380×
            return fail("nothing to seal");
7393×
        }
7403×
        if hashes.len() > crate::attest::MAX_SEAL_HASHES {
7410×
            return fail(format!(
742
                "a seal covers at most {} changes",
743
                crate::attest::MAX_SEAL_HASHES
744
            ));
7453×
        }
7463×
        for h in hashes {
7473×
            if !self.store.has(h) {
7480×
                return fail(format!("unknown change {}", h.short(12)));
7493×
            }
750
        }
7513×
        let mut sorted = hashes.to_vec();
7523×
        sorted.sort();
7533×
        sorted.dedup();
7543×
        let sa = self.sign_attest(&Attest::seal(self.id()?, sorted.clone()))?;
7553×
        let r = self.store.put_attest(&sa.attest)?;
7563×
        for sig in &sa.sigs {
7573×
            self.store.put_signature(&r, sig)?;
758
        }
7593×
        for h in &sorted {
7603×
            self.store.index_seal(h, &r)?;
761
        }
7623×
        Ok(r)
7633×
    }
764
765
    /// Whether `signer` seals changes by `author`: its own, anonymous ones
766
    /// (§14.4), and, when it signs for the configured author (a delegate,
767
    /// or a short-lived key under the author's grant, §14.7), the
768
    /// author's. A receiver still checks it may.
76982×
    pub(crate) fn seals_for_author(&self, author: &KeyId, signer: &KeyId) -> bool {
77082×
        author == signer
7718×
            || author.is_zero()
7727×
            || ((self.config.signer_key.is_some() || self.config.signer_keyless)
7735×
                && self.config.author_key == Some(*author))
77482×
    }
775
776
    /// The changes of the current bookmark this identity can authenticate
777
    /// but has not: its own unsigned ones, anonymous ones, and the
778
    /// author's when it signs for them.
7790×
    pub fn sealable(&self) -> Result<Vec<ChangeHash>> {
7800×
        let Some((key, _)) = self.secret_key()? else {
7810×
            return fail("no identity: run oxy identity create");
782
        };
7830×
        let heads = self.bookmark_heads()?;
7840×
        let state = self.load_state_history(&heads)?;
7850×
        let mut out = Vec::new();
7860×
        for (h, meta) in state.changes() {
7870×
            if self.seals_for_author(&meta.author, &key)
7880×
                && !matches!(self.authenticate_change(h)?, SignatureStatus::Valid { .. })
7890×
            {
7900×
                out.push(*h);
7910×
            }
792
        }
7930×
        out.sort();
7940×
        Ok(out)
7950×
    }
796
797185×
    pub(crate) fn sign_attest(&self, attest: &Attest) -> Result<SignedAttest> {
798185×
        let Some((key, sk)) = self.secret_key()? else {
7990×
            return fail("no secret key: run `oxy identity create` first");
800
        };
801185×
        let r = self.store.put_attest(attest)?;
802183×
        let sig = sk.sign(Domain::Attest, &key, &r);
803183×
        self.store.put_signature(&r, &sig)?;
804183×
        if let AttestBody::Seal { hashes } = &attest.body {
80555×
            for h in hashes {
80655×
                self.store.index_seal(h, &r)?;
807
            }
808131×
        }
809183×
        Ok(SignedAttest {
810183×
            attest: attest.clone(),
811183×
            sigs: vec![sig],
812183×
        })
813185×
    }
814
}