OXYGENHUB

certify_challenge: what a fresh key signs to ask for its certificate 098227d48b1a

Bound to the credential it is asked with, so the proof cannot be replayed
with another.

Refs andymccall/oxygenhub#241
Author
Andy McCall @andymccall <[email protected]>
via
agent Claude Code <[email protected]> · delegate for changes, until 2026-11-13 19:00Z · key ea5f78160088
When
· 2026-10-17 14:11 UTC
Signature
✔ signed by Claude Code
Hash
098227d48b1a9ec8224e82bc8ddab09b246c0ee1cbe5cfe364a3b0b35d36fcd4
Depends on
02e691881a4e
Depended on by
a2387d547373
In bookmarks
carried-not-removed doctor-trust identical-sides keyless-tags main merge-remote-bookmark paths-for-resolutions-and-amend push-notes show-stat subrepo-pin-state
In tags
v0.10.0 v0.11.0 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
2 atoms, 0 tree operations, 1 file

Browse the tree at this change

modified crates/oxy-core/src/cert.rs +26 −0
…
134 unchanged lines
135 135
    }
136 136
}
137 137
138 +
/// What a fresh key signs (a challenge, §14.3) to show its holder asks for
139 +
/// the certificate: bound to the credential it is asked with, so the
140 +
/// proof cannot be replayed with another.
141 +
pub fn certify_challenge(credential: &str) -> Vec<u8> {
142 +
    let mut c = b"oxy1 certify ".to_vec();
143 +
    c.extend_from_slice(blake3::hash(credential.as_bytes()).as_bytes());
144 +
    c
145 +
}
146 +
138 147
#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
139 148
pub enum CertError {
140 149
    #[error("the certificate names issuer {0}, not this one")]
…
144 unchanged lines
285 294
        assert!(!c.covers(Scope::NONE, 2_000));
286 295
        let (_, long) = issue(&hub_sk, &hub, 61);
287 296
        assert_eq!(long.verify(&hub), Err(CertError::BadLifetime));
297 +
    }
298 +
299 +
    #[test]
300 +
    fn the_proof_of_possession_is_bound_to_its_credential() {
301 +
        let sk = SecretKey::generate();
302 +
        let id = sk.identity("job", "[email protected]", 1);
303 +
        let proof = sk
304 +
            .sign_challenge(&id.hash(), &certify_challenge("flowjob_aaa"))
305 +
            .unwrap();
306 +
        assert!(
307 +
            crate::identity::verify_challenge(&id, &proof, &certify_challenge("flowjob_aaa"))
308 +
                .is_ok()
309 +
        );
310 +
        assert!(
311 +
            crate::identity::verify_challenge(&id, &proof, &certify_challenge("flowjob_bbb"))
312 +
                .is_err()
313 +
        );
288 314
    }
289 315
290 316
    #[test]
…
10 unchanged lines