use sha2::{Digest, Sha256};
use crate::error::EbpfError;
pub const AA_FILE_IO_BPF_SHA256: &str = env!("AA_FILE_IO_BPF_SHA256");
pub const AA_EXEC_BPF_SHA256: &str = env!("AA_EXEC_BPF_SHA256");
pub const AA_TLS_BPF_SHA256: &str = env!("AA_TLS_BPF_SHA256");
pub const AA_SYSCALL_GUARD_BPF_SHA256: &str = env!("AA_SYSCALL_GUARD_BPF_SHA256");
pub fn verify_bytecode(object: &str, bytes: &[u8], expected_hex: &str) -> Result<(), EbpfError> {
let actual = hex::encode(Sha256::digest(bytes));
if expected_hex.is_empty() {
return Err(EbpfError::IntegrityMismatch {
object: object.to_string(),
expected: "<unset: no signed digest baked in>".to_string(),
actual,
});
}
if !actual.eq_ignore_ascii_case(expected_hex) {
return Err(EbpfError::IntegrityMismatch {
object: object.to_string(),
expected: expected_hex.to_string(),
actual,
});
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn matching_digest_passes() {
let bytes = b"hello bpf";
let expected = hex::encode(Sha256::digest(bytes));
assert!(verify_bytecode("aa-test", bytes, &expected).is_ok());
}
#[test]
fn mismatched_digest_is_rejected() {
let bytes = b"tampered bytecode";
let wrong = hex::encode(Sha256::digest(b"original bytecode"));
let err = verify_bytecode("aa-test", bytes, &wrong).unwrap_err();
match err {
EbpfError::IntegrityMismatch {
object,
expected,
actual,
} => {
assert_eq!(object, "aa-test");
assert_eq!(expected, wrong);
assert_ne!(actual, wrong);
}
other => panic!("unexpected error: {other:?}"),
}
}
#[test]
fn empty_expected_is_unverifiable_and_rejected() {
let err = verify_bytecode("aa-test", b"anything", "").unwrap_err();
assert!(matches!(err, EbpfError::IntegrityMismatch { .. }));
}
#[test]
fn digest_comparison_is_case_insensitive() {
let bytes = b"case test";
let expected = hex::encode(Sha256::digest(bytes)).to_uppercase();
assert!(verify_bytecode("aa-test", bytes, &expected).is_ok());
}
}