use crate::cose::TokenPublicKey;
use crate::daemon_config::KernelRef;
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum KernelTrustError {
#[error("kernel hash mismatch: staged {computed}, pinned {expected}")]
HashMismatch {
computed: String,
expected: String,
},
#[error("strict posture requires a signed kernel, but this one is unsigned")]
Unsigned,
#[error("kernel signature is malformed: {0}")]
BadSigFormat(String),
#[error("kernel signature verifies against none of the configured signing keys")]
BadSignature,
#[error("kernel hash is not on the static `[compute] kernel_allowed_hashes` allow-list")]
NotAllowlisted,
#[error("a configured kernel signing key is malformed: {0}")]
BadSigningKey(String),
}
pub fn verify_kernel(
bytes: &[u8],
kref: &KernelRef,
strict: bool,
signing_keys: &[String],
allowed_hashes: &[String],
) -> Result<(), KernelTrustError> {
let computed = boatramp_types::manifest::sha256_hex(bytes);
if computed != kref.sha256 {
return Err(KernelTrustError::HashMismatch {
computed,
expected: kref.sha256.clone(),
});
}
if !strict {
return Ok(());
}
if !allowed_hashes.iter().any(|h| h == &kref.sha256) {
return Err(KernelTrustError::NotAllowlisted);
}
let sig_hex = kref.sig.as_deref().ok_or(KernelTrustError::Unsigned)?;
let sig = hex::decode(sig_hex).map_err(|e| KernelTrustError::BadSigFormat(e.to_string()))?;
let message = kref.sha256.as_bytes();
for spec in signing_keys {
let key = TokenPublicKey::from_hex(spec)
.map_err(|e| KernelTrustError::BadSigningKey(e.to_string()))?;
if key.verify(message, &sig).is_ok() {
return Ok(());
}
}
Err(KernelTrustError::BadSignature)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cose::{LocalSigner, Signer, TokenAlg};
fn kref_for(bytes: &[u8], sig: Option<String>) -> KernelRef {
KernelRef {
source: "blob".into(),
sha256: boatramp_types::manifest::sha256_hex(bytes),
sig,
}
}
#[test]
fn hash_mismatch_never_boots() {
let kref = KernelRef {
source: "blob".into(),
sha256: "0".repeat(64),
sig: None,
};
let err = verify_kernel(b"kernel-bytes", &kref, false, &[], &[]).unwrap_err();
assert!(matches!(err, KernelTrustError::HashMismatch { .. }));
}
#[test]
fn relaxed_accepts_verified_hash_pin() {
let bytes = b"a microvm vmlinux";
let kref = kref_for(bytes, None);
assert!(verify_kernel(bytes, &kref, false, &[], &[]).is_ok());
}
#[test]
fn strict_rejects_unsigned_and_unlisted() {
let bytes = b"a microvm vmlinux";
let kref = kref_for(bytes, None);
assert_eq!(
verify_kernel(bytes, &kref, true, &[], &[]).unwrap_err(),
KernelTrustError::NotAllowlisted
);
assert_eq!(
verify_kernel(bytes, &kref, true, &[], std::slice::from_ref(&kref.sha256)).unwrap_err(),
KernelTrustError::Unsigned
);
}
#[test]
#[ignore = "verifies a downloaded release kernel; needs BOATRAMP_RELEASE_* env"]
fn released_kernel_verifies_under_strict() {
let (Ok(path), Ok(sha256), Ok(sig), Ok(pubkey)) = (
std::env::var("BOATRAMP_RELEASE_KERNEL"),
std::env::var("BOATRAMP_RELEASE_SHA256"),
std::env::var("BOATRAMP_RELEASE_SIG"),
std::env::var("BOATRAMP_RELEASE_PUBKEY"),
) else {
eprintln!("SKIP: set BOATRAMP_RELEASE_{{KERNEL,SHA256,SIG,PUBKEY}}");
return;
};
let bytes = std::fs::read(&path).expect("read release kernel");
let kref = KernelRef {
source: "release".into(),
sha256: sha256.trim().to_string(),
sig: Some(sig.trim().to_string()),
};
verify_kernel(
&bytes,
&kref,
true,
&[pubkey.trim().to_string()],
&[sha256.trim().to_string()],
)
.expect("release kernel must clear the strict verify-before-boot bar");
}
#[tokio::test]
async fn strict_accepts_valid_signature_rejects_wrong_key() {
let bytes = b"a microvm vmlinux";
let hash = boatramp_types::manifest::sha256_hex(bytes);
let signer = LocalSigner::generate(TokenAlg::Es256);
let sig = signer.sign(hash.as_bytes()).await.unwrap();
let pubkey = signer.public_key().to_hex();
let kref = kref_for(bytes, Some(hex::encode(&sig)));
assert!(verify_kernel(bytes, &kref, true, &[pubkey], std::slice::from_ref(&hash)).is_ok());
let other = LocalSigner::generate(TokenAlg::Es256).public_key().to_hex();
assert_eq!(
verify_kernel(bytes, &kref, true, &[other], &[hash]).unwrap_err(),
KernelTrustError::BadSignature
);
}
}