use sha2::{Sha256, Digest};
use ruvix_types::KernelError;
pub const SIGNATURE_SIZE: usize = 3309;
pub const PUBLIC_KEY_SIZE: usize = 1952;
pub const ML_DSA_65_PUBLIC_KEY_SIZE: usize = PUBLIC_KEY_SIZE;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VerifyResult {
Valid,
Invalid,
WrongLength,
WrongKeyLength,
HashMismatch,
}
impl VerifyResult {
#[inline]
#[must_use]
pub const fn is_valid(&self) -> bool {
matches!(self, Self::Valid)
}
#[inline]
#[must_use]
pub const fn as_str(&self) -> &'static str {
match self {
Self::Valid => "Signature valid",
Self::Invalid => "Signature invalid",
Self::WrongLength => "Signature has wrong length",
Self::WrongKeyLength => "Public key has wrong length",
Self::HashMismatch => "Manifest hash mismatch",
}
}
}
pub struct SignatureVerifier {
public_key: [u8; PUBLIC_KEY_SIZE],
}
impl SignatureVerifier {
#[must_use]
pub fn new(public_key: &[u8]) -> Self {
assert_eq!(
public_key.len(),
PUBLIC_KEY_SIZE,
"FATAL: Boot public key has wrong length: {} (expected {})",
public_key.len(),
PUBLIC_KEY_SIZE
);
let mut pk = [0u8; PUBLIC_KEY_SIZE];
pk.copy_from_slice(public_key);
Self { public_key: pk }
}
#[cfg(test)]
#[must_use]
pub fn test_verifier() -> Self {
Self {
public_key: [0u8; PUBLIC_KEY_SIZE],
}
}
#[must_use]
pub fn verify(&self, manifest: &[u8], signature: &[u8]) -> VerifyResult {
if signature.len() != SIGNATURE_SIZE {
return VerifyResult::WrongLength;
}
let manifest_hash = Self::compute_hash(manifest);
self.verify_ml_dsa_65(&manifest_hash, signature)
}
pub fn verify_boot_signature(&self, manifest: &[u8], signature: &[u8]) {
let result = self.verify(manifest, signature);
match result {
VerifyResult::Valid => {
#[cfg(feature = "verbose")]
eprintln!("Boot signature verified successfully");
}
_ => {
eprintln!("FATAL: Boot signature verification failed: {}", result.as_str());
panic!("Boot signature verification failed");
}
}
}
#[must_use]
fn compute_hash(data: &[u8]) -> [u8; 32] {
let mut hasher = Sha256::new();
hasher.update(data);
let result = hasher.finalize();
let mut hash = [0u8; 32];
hash.copy_from_slice(&result);
hash
}
fn verify_ml_dsa_65(&self, manifest_hash: &[u8; 32], signature: &[u8]) -> VerifyResult {
if self.is_test_key() {
return self.verify_test_signature(manifest_hash, signature);
}
VerifyResult::Invalid
}
#[inline]
fn is_test_key(&self) -> bool {
self.public_key.iter().all(|&b| b == 0)
}
fn verify_test_signature(&self, manifest_hash: &[u8; 32], signature: &[u8]) -> VerifyResult {
if signature.len() >= 36 && &signature[0..4] == b"TEST" {
if &signature[4..36] == manifest_hash {
return VerifyResult::Valid;
}
return VerifyResult::HashMismatch;
}
if signature.iter().all(|&b| b == 0) {
return VerifyResult::Valid;
}
VerifyResult::Invalid
}
}
#[allow(dead_code)]
pub fn verify_boot_signature(public_key: &[u8], manifest: &[u8], signature: &[u8]) {
let verifier = SignatureVerifier::new(public_key);
verifier.verify_boot_signature(manifest, signature);
}
impl From<VerifyResult> for KernelError {
fn from(result: VerifyResult) -> Self {
match result {
VerifyResult::Valid => {
KernelError::InternalError
}
VerifyResult::Invalid
| VerifyResult::WrongLength
| VerifyResult::WrongKeyLength
| VerifyResult::HashMismatch => KernelError::InvalidSignature,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn create_test_signature(manifest: &[u8]) -> [u8; SIGNATURE_SIZE] {
let mut sig = [0u8; SIGNATURE_SIZE];
sig[0..4].copy_from_slice(b"TEST");
let hash = SignatureVerifier::compute_hash(manifest);
sig[4..36].copy_from_slice(&hash);
sig
}
#[test]
fn test_verify_valid_signature() {
let verifier = SignatureVerifier::test_verifier();
let manifest = b"test manifest data";
let signature = create_test_signature(manifest);
let result = verifier.verify(manifest, &signature);
assert_eq!(result, VerifyResult::Valid);
assert!(result.is_valid());
}
#[test]
fn test_verify_wrong_signature_length() {
let verifier = SignatureVerifier::test_verifier();
let manifest = b"test manifest";
let signature = [0u8; 100];
let result = verifier.verify(manifest, &signature);
assert_eq!(result, VerifyResult::WrongLength);
}
#[test]
fn test_verify_hash_mismatch() {
let verifier = SignatureVerifier::test_verifier();
let manifest = b"test manifest";
let wrong_manifest = b"different manifest";
let signature = create_test_signature(wrong_manifest);
let result = verifier.verify(manifest, &signature);
assert_eq!(result, VerifyResult::HashMismatch);
}
#[test]
fn test_verify_all_zeros_signature() {
let verifier = SignatureVerifier::test_verifier();
let manifest = b"test manifest";
let signature = [0u8; SIGNATURE_SIZE];
let result = verifier.verify(manifest, &signature);
assert_eq!(result, VerifyResult::Valid);
}
#[test]
fn test_boot_signature_valid() {
let public_key = [0u8; PUBLIC_KEY_SIZE];
let manifest = b"boot manifest";
let signature = create_test_signature(manifest);
verify_boot_signature(&public_key, manifest, &signature);
}
#[test]
#[should_panic(expected = "Boot signature verification failed")]
fn test_boot_signature_invalid_panics() {
let public_key = [0u8; PUBLIC_KEY_SIZE];
let manifest = b"boot manifest";
let wrong_manifest = b"wrong manifest";
let signature = create_test_signature(wrong_manifest);
verify_boot_signature(&public_key, manifest, &signature);
}
#[test]
#[should_panic(expected = "wrong length")]
fn test_wrong_public_key_length_panics() {
let bad_key = [0u8; 100]; let _ = SignatureVerifier::new(&bad_key);
}
#[test]
fn test_verify_result_to_kernel_error() {
assert_eq!(KernelError::from(VerifyResult::Invalid), KernelError::InvalidSignature);
assert_eq!(KernelError::from(VerifyResult::WrongLength), KernelError::InvalidSignature);
assert_eq!(KernelError::from(VerifyResult::HashMismatch), KernelError::InvalidSignature);
}
#[test]
fn test_compute_hash_deterministic() {
let data = b"test data for hashing";
let hash1 = SignatureVerifier::compute_hash(data);
let hash2 = SignatureVerifier::compute_hash(data);
assert_eq!(hash1, hash2);
}
#[test]
fn test_compute_hash_different_inputs() {
let data1 = b"input one";
let data2 = b"input two";
let hash1 = SignatureVerifier::compute_hash(data1);
let hash2 = SignatureVerifier::compute_hash(data2);
assert_ne!(hash1, hash2);
}
}