use dig_capsule::capsule::Capsule as CapsuleReader;
use crate::error::{DigStoreError, DigStoreResult};
use crate::types::{Bytes32, CapsuleIdentity};
pub fn get_capsule_identity(module_bytes: &[u8]) -> DigStoreResult<CapsuleIdentity> {
let capsule = CapsuleReader::from_module_bytes(module_bytes)
.map_err(|error| DigStoreError::Capsule(error.to_string()))?;
Ok(CapsuleIdentity {
store_id: Bytes32::new(capsule.store_id.0),
root_hash: Bytes32::new(capsule.root_hash.0),
})
}
pub fn open_capsule(
module_bytes: &[u8],
expected_store_id: Bytes32,
) -> DigStoreResult<CapsuleIdentity> {
let identity = get_capsule_identity(module_bytes)?;
if identity.store_id != expected_store_id {
return Err(DigStoreError::Capsule(format!(
"capsule store_id mismatch: module declares {} but the trusted anchor is {expected_store_id}",
identity.store_id
)));
}
Ok(identity)
}
#[cfg(test)]
mod tests {
use super::*;
fn golden_module() -> Vec<u8> {
let hex = include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/fixtures/golden_capsule_module.hex"
))
.trim();
hex_decode(hex)
}
fn golden_store_id() -> Bytes32 {
Bytes32::new([0xAB; 32])
}
const GOLDEN_ROOT_HEX: &str =
"da2b3372876ec1d3dd9b846f22cb6a9afcb2dadbf579f2930f8e5efe81b0905a";
fn hex_decode(s: &str) -> Vec<u8> {
assert!(s.len() % 2 == 0, "hex length must be even");
(0..s.len())
.step_by(2)
.map(|i| u8::from_str_radix(&s[i..i + 2], 16).expect("valid hex"))
.collect()
}
#[test]
fn get_capsule_identity_recovers_the_declared_pair() {
let identity = get_capsule_identity(&golden_module()).expect("golden module reads");
assert_eq!(identity.store_id, golden_store_id());
assert_eq!(identity.root_hash, Bytes32::new(hex_to_32(GOLDEN_ROOT_HEX)));
}
#[test]
fn open_capsule_accepts_a_matching_anchor() {
let identity =
open_capsule(&golden_module(), golden_store_id()).expect("matching anchor opens");
assert_eq!(identity.store_id, golden_store_id());
}
#[test]
fn open_capsule_rejects_a_mismatched_anchor() {
let wrong_anchor = Bytes32::new([0x01; 32]);
assert!(
matches!(
open_capsule(&golden_module(), wrong_anchor),
Err(DigStoreError::Capsule(_))
),
"a store_id that does not match the trusted anchor must fail closed"
);
}
#[test]
fn get_capsule_identity_rejects_non_module_bytes() {
let garbage = [0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x01, 0x02, 0x03];
assert!(matches!(
get_capsule_identity(&garbage),
Err(DigStoreError::Capsule(_))
));
}
#[test]
fn get_capsule_identity_rejects_a_tampered_module() {
let mut module = golden_module();
let last = module.len() - 1;
module[last] ^= 0xFF;
assert!(matches!(
get_capsule_identity(&module),
Err(DigStoreError::Capsule(_))
));
}
fn hex_to_32(s: &str) -> [u8; 32] {
let bytes = hex_decode(s);
let mut out = [0u8; 32];
out.copy_from_slice(&bytes);
out
}
}