use sha2::{Digest, Sha256};
pub fn report_data_binds(report_data: &[u8], signing_address: &str, nonce: &str) -> bool {
if report_data.len() != 64 {
return false;
}
let Some(addr) = decode_hex(
signing_address
.strip_prefix("0x")
.unwrap_or(signing_address),
) else {
return false;
};
if addr.len() != 20 {
return false;
}
let mut expected_addr = [0u8; 32];
expected_addr[..addr.len()].copy_from_slice(&addr);
if report_data[..32] != expected_addr {
return false;
}
match decode_hex(nonce) {
Some(nonce_bytes) => report_data[32..] == nonce_bytes[..],
None => false,
}
}
pub fn compose_matches_mr_config(app_compose: &str, mr_config: &str) -> bool {
let digest = Sha256::digest(app_compose.as_bytes());
hex::encode(digest).eq_ignore_ascii_case(mr_config)
}
fn decode_hex(s: &str) -> Option<Vec<u8>> {
hex::decode(s).ok()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::near::report::AttestationReport;
const FIXTURE: &str = include_str!("../../tests/fixtures/near_report.json");
fn fixture_report_data(quote_hex: &str) -> Vec<u8> {
let q = hex::decode(quote_hex).expect("quote is hex");
q[48 + 520..48 + 584].to_vec()
}
fn fixture() -> AttestationReport {
serde_json::from_str(FIXTURE).expect("fixture parses")
}
#[test]
fn report_data_binds_the_attested_key_and_nonce() {
let r = fixture();
let m = &r.model_attestations[0];
let rd = fixture_report_data(&m.intel_quote);
assert!(report_data_binds(&rd, &m.signing_address, &m.request_nonce));
}
#[test]
fn report_data_rejects_a_swapped_nonce() {
let r = fixture();
let m = &r.model_attestations[0];
let rd = fixture_report_data(&m.intel_quote);
let other_nonce = "00".repeat(32);
assert!(!report_data_binds(&rd, &m.signing_address, &other_nonce));
}
#[test]
fn report_data_rejects_a_swapped_address() {
let r = fixture();
let m = &r.model_attestations[0];
let rd = fixture_report_data(&m.intel_quote);
let attacker = "0xdead000000000000000000000000000000000000";
assert!(!report_data_binds(&rd, attacker, &m.request_nonce));
}
#[test]
fn report_data_rejects_wrong_length_or_bad_hex() {
assert!(!report_data_binds(&[0u8; 10], "0xbb", "00"));
assert!(!report_data_binds(&[0u8; 64], "nothex", &"00".repeat(32)));
}
#[test]
fn report_data_rejects_an_empty_or_short_address() {
let nonce = "11".repeat(32);
let mut rd = [0u8; 64];
rd[32..].copy_from_slice(&hex::decode(&nonce).unwrap());
assert!(!report_data_binds(&rd, "", &nonce));
assert!(!report_data_binds(&rd, "0x", &nonce));
assert!(!report_data_binds(&rd, "0xdead", &nonce)); }
#[test]
fn compose_hash_matches_the_reported_config() {
let r = fixture();
let info = &r.model_attestations[0].info;
let raw: serde_json::Value = serde_json::from_str(FIXTURE).unwrap();
let app_compose = raw["model_attestations"][0]["info"]["tcb_info"]["app_compose"]
.as_str()
.unwrap();
assert!(compose_matches_mr_config(app_compose, &info.compose_hash));
assert!(!compose_matches_mr_config(
"tampered compose",
&info.compose_hash
));
}
}