Skip to main content

gatehouse_runtime_protocol/lifecycle_v2/
capture_binding.rs

1//! Frozen capture operation-key binding. No dispatch, freshness or authority.
2use serde::Serialize;
3use sha2::{Digest, Sha256};
4#[derive(Serialize)]
5struct Envelope<'a> {
6    domain: &'static str,
7    request_sha256: &'a str,
8    host_sha256: &'a str,
9    installation_sha256: &'a str,
10    config_sha256: &'a str,
11    executable_sha256: &'a str,
12}
13/// SHA256 of the explicit field-order envelope. The request digest must cover
14/// the canonical full request including invocation; this function does not prove
15/// that a caller actually obtained any digest from trusted bytes.
16pub fn operation_key(
17    request: &str,
18    host: &str,
19    installation: &str,
20    config: &str,
21    executable: &str,
22) -> Result<String, &'static str> {
23    let bytes = envelope(request, host, installation, config, executable)?;
24    Ok(format!("{:x}", Sha256::digest(bytes)))
25}
26fn envelope(
27    request: &str,
28    host: &str,
29    installation: &str,
30    config: &str,
31    executable: &str,
32) -> Result<Vec<u8>, &'static str> {
33    for hash in [request, host, installation, config, executable] {
34        if hash.len() != 64
35            || !hash
36                .bytes()
37                .all(|b| b.is_ascii_digit() || (b'a'..=b'f').contains(&b))
38        {
39            return Err("capture binding digest rejected");
40        }
41    }
42    serde_json::to_vec(&Envelope {
43        domain: "gate-runtime/podman-capture-key-v1",
44        request_sha256: request,
45        host_sha256: host,
46        installation_sha256: installation,
47        config_sha256: config,
48        executable_sha256: executable,
49    })
50    .map_err(|_| "binding encoding")
51}
52#[cfg(test)]
53mod tests {
54    use super::*;
55    #[test]
56    fn golden_key_and_all_snapshot_changes() {
57        let hashes: Vec<String> = (0..5).map(|i| format!("{i}").repeat(64)).collect();
58        let key =
59            operation_key(&hashes[0], &hashes[1], &hashes[2], &hashes[3], &hashes[4]).unwrap();
60        assert_eq!(
61            key,
62            "5071ab535b718e57e663e2de6da7fdece81bab385f413773a00aa7c537d5de90"
63        );
64        for i in 0..5 {
65            let mut changed = hashes.clone();
66            changed[i] = "f".repeat(64);
67            assert_ne!(
68                key,
69                operation_key(
70                    &changed[0],
71                    &changed[1],
72                    &changed[2],
73                    &changed[3],
74                    &changed[4]
75                )
76                .unwrap()
77            );
78        }
79        assert!(operation_key("bad", &hashes[1], &hashes[2], &hashes[3], &hashes[4]).is_err());
80    }
81}