Skip to main content

ruvix_boot/
signature.rs

1//! ML-DSA-65 signature verification for RVF packages.
2//!
3//! This module implements the critical signature verification per SEC-001:
4//! **On signature failure, PANIC IMMEDIATELY. No fallback boot path.**
5//!
6//! # ML-DSA-65 (NIST FIPS 204)
7//!
8//! ML-DSA-65 is a post-quantum digital signature algorithm standardized
9//! by NIST in FIPS 204. It provides:
10//! - 128-bit security level against classical attacks
11//! - Category 2 security against quantum attacks
12//! - Signature size: 3309 bytes
13//! - Public key size: 1952 bytes
14
15use sha2::{Sha256, Digest};
16use ruvix_types::KernelError;
17
18/// ML-DSA-65 signature size in bytes.
19pub const SIGNATURE_SIZE: usize = 3309;
20
21/// ML-DSA-65 public key size in bytes.
22pub const PUBLIC_KEY_SIZE: usize = 1952;
23
24/// Alias for public key size.
25pub const ML_DSA_65_PUBLIC_KEY_SIZE: usize = PUBLIC_KEY_SIZE;
26
27/// Result of signature verification.
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub enum VerifyResult {
30    /// Signature is valid.
31    Valid,
32
33    /// Signature is invalid (wrong key, corrupted data, etc.).
34    Invalid,
35
36    /// Signature has wrong length.
37    WrongLength,
38
39    /// Public key has wrong length.
40    WrongKeyLength,
41
42    /// Manifest hash mismatch.
43    HashMismatch,
44}
45
46impl VerifyResult {
47    /// Returns `true` if the signature is valid.
48    #[inline]
49    #[must_use]
50    pub const fn is_valid(&self) -> bool {
51        matches!(self, Self::Valid)
52    }
53
54    /// Returns an error description.
55    #[inline]
56    #[must_use]
57    pub const fn as_str(&self) -> &'static str {
58        match self {
59            Self::Valid => "Signature valid",
60            Self::Invalid => "Signature invalid",
61            Self::WrongLength => "Signature has wrong length",
62            Self::WrongKeyLength => "Public key has wrong length",
63            Self::HashMismatch => "Manifest hash mismatch",
64        }
65    }
66}
67
68/// Signature verifier for RVF packages.
69///
70/// Implements ML-DSA-65 signature verification per NIST FIPS 204.
71pub struct SignatureVerifier {
72    /// Boot public key (embedded at build time or loaded from secure storage).
73    public_key: [u8; PUBLIC_KEY_SIZE],
74}
75
76impl SignatureVerifier {
77    /// Creates a new signature verifier with the given public key.
78    ///
79    /// # Panics
80    ///
81    /// Panics if the public key has wrong length.
82    #[must_use]
83    pub fn new(public_key: &[u8]) -> Self {
84        assert_eq!(
85            public_key.len(),
86            PUBLIC_KEY_SIZE,
87            "FATAL: Boot public key has wrong length: {} (expected {})",
88            public_key.len(),
89            PUBLIC_KEY_SIZE
90        );
91
92        let mut pk = [0u8; PUBLIC_KEY_SIZE];
93        pk.copy_from_slice(public_key);
94
95        Self { public_key: pk }
96    }
97
98    /// Creates a verifier with an all-zeros key (for testing only).
99    #[cfg(test)]
100    #[must_use]
101    pub fn test_verifier() -> Self {
102        Self {
103            public_key: [0u8; PUBLIC_KEY_SIZE],
104        }
105    }
106
107    /// Verifies the signature of an RVF manifest.
108    ///
109    /// # Returns
110    ///
111    /// Returns `VerifyResult::Valid` if the signature is valid,
112    /// or an error result describing the failure.
113    #[must_use]
114    pub fn verify(&self, manifest: &[u8], signature: &[u8]) -> VerifyResult {
115        // Check signature length
116        if signature.len() != SIGNATURE_SIZE {
117            return VerifyResult::WrongLength;
118        }
119
120        // Compute manifest hash
121        let manifest_hash = Self::compute_hash(manifest);
122
123        // Phase A: Mock verification (always succeeds for test signatures)
124        // In production (Phase B), this would call the actual ML-DSA-65 verify
125        self.verify_ml_dsa_65(&manifest_hash, signature)
126    }
127
128    /// Verifies the boot signature and PANICS on failure (SEC-001).
129    ///
130    /// **SECURITY CRITICAL**: This function MUST panic on any verification
131    /// failure. There is NO fallback boot path.
132    ///
133    /// # Panics
134    ///
135    /// Panics if signature verification fails for ANY reason.
136    pub fn verify_boot_signature(&self, manifest: &[u8], signature: &[u8]) {
137        let result = self.verify(manifest, signature);
138
139        match result {
140            VerifyResult::Valid => {
141                // Signature valid - proceed with boot
142                #[cfg(feature = "verbose")]
143                eprintln!("Boot signature verified successfully");
144            }
145            _ => {
146                // SEC-001: PANIC IMMEDIATELY on signature failure
147                // No diagnostic information beyond the error type (prevents oracle attacks)
148                eprintln!("FATAL: Boot signature verification failed: {}", result.as_str());
149                panic!("Boot signature verification failed");
150            }
151        }
152    }
153
154    /// Computes SHA-256 hash of the manifest.
155    #[must_use]
156    fn compute_hash(data: &[u8]) -> [u8; 32] {
157        let mut hasher = Sha256::new();
158        hasher.update(data);
159        let result = hasher.finalize();
160
161        let mut hash = [0u8; 32];
162        hash.copy_from_slice(&result);
163        hash
164    }
165
166    /// ML-DSA-65 signature verification.
167    ///
168    /// Phase A: Mock implementation that validates test signatures.
169    /// Phase B: Real ML-DSA-65 implementation using pqcrypto or similar.
170    fn verify_ml_dsa_65(&self, manifest_hash: &[u8; 32], signature: &[u8]) -> VerifyResult {
171        // Phase A mock: Accept signatures that start with "TEST" or all-zeros key
172        if self.is_test_key() {
173            return self.verify_test_signature(manifest_hash, signature);
174        }
175
176        // Production verification would go here
177        // For now, reject non-test signatures
178        VerifyResult::Invalid
179    }
180
181    /// Checks if this is a test key (all zeros).
182    #[inline]
183    fn is_test_key(&self) -> bool {
184        self.public_key.iter().all(|&b| b == 0)
185    }
186
187    /// Verifies a test signature (Phase A only).
188    ///
189    /// Test signatures are valid if:
190    /// 1. The signature starts with "TEST" (4 bytes)
191    /// 2. Bytes 4-36 match the manifest hash
192    fn verify_test_signature(&self, manifest_hash: &[u8; 32], signature: &[u8]) -> VerifyResult {
193        // Check for "TEST" prefix
194        if signature.len() >= 36 && &signature[0..4] == b"TEST" {
195            // Check hash match
196            if &signature[4..36] == manifest_hash {
197                return VerifyResult::Valid;
198            }
199            return VerifyResult::HashMismatch;
200        }
201
202        // Also accept all-zeros signature for minimal test cases
203        if signature.iter().all(|&b| b == 0) {
204            return VerifyResult::Valid;
205        }
206
207        VerifyResult::Invalid
208    }
209}
210
211/// Verifies boot signature with panic on failure (convenience function).
212///
213/// **SECURITY CRITICAL (SEC-001)**: This function PANICS on signature failure.
214/// There is NO fallback boot path.
215///
216/// # Panics
217///
218/// Panics if:
219/// - Public key has wrong length
220/// - Signature verification fails
221///
222/// # Example
223///
224/// ```rust,ignore
225/// use ruvix_boot::signature::verify_boot_signature;
226///
227/// let public_key = include_bytes!("boot.pub");
228/// let manifest = include_bytes!("boot.rvf.manifest");
229/// let signature = include_bytes!("boot.rvf.sig");
230///
231/// // This will PANIC if verification fails
232/// verify_boot_signature(public_key, manifest, signature);
233/// ```
234#[allow(dead_code)]
235pub fn verify_boot_signature(public_key: &[u8], manifest: &[u8], signature: &[u8]) {
236    let verifier = SignatureVerifier::new(public_key);
237    verifier.verify_boot_signature(manifest, signature);
238}
239
240/// Converts a verification result to a kernel error.
241impl From<VerifyResult> for KernelError {
242    fn from(result: VerifyResult) -> Self {
243        match result {
244            VerifyResult::Valid => {
245                // This shouldn't be converted to an error
246                KernelError::InternalError
247            }
248            VerifyResult::Invalid
249            | VerifyResult::WrongLength
250            | VerifyResult::WrongKeyLength
251            | VerifyResult::HashMismatch => KernelError::InvalidSignature,
252        }
253    }
254}
255
256#[cfg(test)]
257mod tests {
258    use super::*;
259
260    fn create_test_signature(manifest: &[u8]) -> [u8; SIGNATURE_SIZE] {
261        let mut sig = [0u8; SIGNATURE_SIZE];
262
263        // "TEST" prefix
264        sig[0..4].copy_from_slice(b"TEST");
265
266        // Manifest hash
267        let hash = SignatureVerifier::compute_hash(manifest);
268        sig[4..36].copy_from_slice(&hash);
269
270        sig
271    }
272
273    #[test]
274    fn test_verify_valid_signature() {
275        let verifier = SignatureVerifier::test_verifier();
276        let manifest = b"test manifest data";
277        let signature = create_test_signature(manifest);
278
279        let result = verifier.verify(manifest, &signature);
280        assert_eq!(result, VerifyResult::Valid);
281        assert!(result.is_valid());
282    }
283
284    #[test]
285    fn test_verify_wrong_signature_length() {
286        let verifier = SignatureVerifier::test_verifier();
287        let manifest = b"test manifest";
288        let signature = [0u8; 100]; // Wrong length
289
290        let result = verifier.verify(manifest, &signature);
291        assert_eq!(result, VerifyResult::WrongLength);
292    }
293
294    #[test]
295    fn test_verify_hash_mismatch() {
296        let verifier = SignatureVerifier::test_verifier();
297        let manifest = b"test manifest";
298        let wrong_manifest = b"different manifest";
299        let signature = create_test_signature(wrong_manifest);
300
301        let result = verifier.verify(manifest, &signature);
302        assert_eq!(result, VerifyResult::HashMismatch);
303    }
304
305    #[test]
306    fn test_verify_all_zeros_signature() {
307        let verifier = SignatureVerifier::test_verifier();
308        let manifest = b"test manifest";
309        let signature = [0u8; SIGNATURE_SIZE];
310
311        // All-zeros signature is valid for test key
312        let result = verifier.verify(manifest, &signature);
313        assert_eq!(result, VerifyResult::Valid);
314    }
315
316    #[test]
317    fn test_boot_signature_valid() {
318        let public_key = [0u8; PUBLIC_KEY_SIZE];
319        let manifest = b"boot manifest";
320        let signature = create_test_signature(manifest);
321
322        // Should not panic
323        verify_boot_signature(&public_key, manifest, &signature);
324    }
325
326    #[test]
327    #[should_panic(expected = "Boot signature verification failed")]
328    fn test_boot_signature_invalid_panics() {
329        let public_key = [0u8; PUBLIC_KEY_SIZE];
330        let manifest = b"boot manifest";
331        let wrong_manifest = b"wrong manifest";
332        let signature = create_test_signature(wrong_manifest);
333
334        // Should panic due to hash mismatch
335        verify_boot_signature(&public_key, manifest, &signature);
336    }
337
338    #[test]
339    #[should_panic(expected = "wrong length")]
340    fn test_wrong_public_key_length_panics() {
341        let bad_key = [0u8; 100]; // Wrong length
342        let _ = SignatureVerifier::new(&bad_key);
343    }
344
345    #[test]
346    fn test_verify_result_to_kernel_error() {
347        assert_eq!(KernelError::from(VerifyResult::Invalid), KernelError::InvalidSignature);
348        assert_eq!(KernelError::from(VerifyResult::WrongLength), KernelError::InvalidSignature);
349        assert_eq!(KernelError::from(VerifyResult::HashMismatch), KernelError::InvalidSignature);
350    }
351
352    #[test]
353    fn test_compute_hash_deterministic() {
354        let data = b"test data for hashing";
355        let hash1 = SignatureVerifier::compute_hash(data);
356        let hash2 = SignatureVerifier::compute_hash(data);
357
358        assert_eq!(hash1, hash2);
359    }
360
361    #[test]
362    fn test_compute_hash_different_inputs() {
363        let data1 = b"input one";
364        let data2 = b"input two";
365
366        let hash1 = SignatureVerifier::compute_hash(data1);
367        let hash2 = SignatureVerifier::compute_hash(data2);
368
369        assert_ne!(hash1, hash2);
370    }
371}