use anyhow::{Context, Result};
use ed25519_dalek::{Signature, Verifier, VerifyingKey};
use sha2::{Digest, Sha256};
use std::path::Path;
const OTA_PUBLIC_KEY: &[u8; 32] = include_bytes!("../../keys/ota_signing_key.pub.raw");
pub const REQUIRE_SIGNATURE: bool = false;
pub fn verify_bundle(bundle_path: &Path, sig_path: &Path) -> Result<()> {
verify_bundle_with_key(bundle_path, sig_path, OTA_PUBLIC_KEY)
}
pub fn verify_bundle_with_key(
bundle_path: &Path,
sig_path: &Path,
public_key_bytes: &[u8; 32],
) -> Result<()> {
tracing::info!("Verifying bundle signature: {}", bundle_path.display());
let verifying_key =
VerifyingKey::from_bytes(public_key_bytes).context("Invalid embedded public key")?;
let bundle_bytes = std::fs::read(bundle_path).context("Failed to read bundle")?;
let digest = Sha256::digest(&bundle_bytes);
let sig_bytes = std::fs::read(sig_path).context("Failed to read signature")?;
let signature = Signature::from_slice(&sig_bytes)
.context("Invalid signature format (expected 64 bytes)")?;
verifying_key
.verify(digest.as_slice(), &signature)
.map_err(|e| anyhow::anyhow!("Signature verification FAILED: {}", e))?;
tracing::info!("Signature verified OK");
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use ed25519_dalek::SigningKey;
use sha2::{Digest, Sha256};
use std::fs;
use tempfile::tempdir;
fn test_keypair() -> (SigningKey, VerifyingKey) {
let signing_key = SigningKey::from_bytes(&[1u8; 32]);
let verifying_key = signing_key.verifying_key();
(signing_key, verifying_key)
}
fn sign_file(signing_key: &SigningKey, file_path: &Path) -> Vec<u8> {
use ed25519_dalek::Signer;
let content = fs::read(file_path).unwrap();
let digest = Sha256::digest(&content);
let sig = signing_key.sign(digest.as_slice());
sig.to_bytes().to_vec()
}
#[test]
fn test_valid_signature() {
let dir = tempdir().unwrap();
let bundle_path = dir.path().join("bundle.tar.gz");
let sig_path = dir.path().join("bundle.tar.gz.sig");
fs::write(&bundle_path, b"test bundle content").unwrap();
let (signing_key, verifying_key) = test_keypair();
let sig = sign_file(&signing_key, &bundle_path);
fs::write(&sig_path, &sig).unwrap();
let pubkey_bytes: &[u8; 32] = verifying_key.as_bytes();
let result = verify_bundle_with_key(&bundle_path, &sig_path, pubkey_bytes);
assert!(
result.is_ok(),
"Valid signature should verify: {:?}",
result
);
}
#[test]
fn test_corrupted_signature_fails() {
let dir = tempdir().unwrap();
let bundle_path = dir.path().join("bundle.tar.gz");
let sig_path = dir.path().join("bundle.tar.gz.sig");
fs::write(&bundle_path, b"test bundle content").unwrap();
let (signing_key, verifying_key) = test_keypair();
let mut sig = sign_file(&signing_key, &bundle_path);
sig[0] ^= 0xff; fs::write(&sig_path, &sig).unwrap();
let pubkey_bytes: &[u8; 32] = verifying_key.as_bytes();
let result = verify_bundle_with_key(&bundle_path, &sig_path, pubkey_bytes);
assert!(result.is_err(), "Corrupted signature should fail");
}
#[test]
fn test_wrong_key_fails() {
let dir = tempdir().unwrap();
let bundle_path = dir.path().join("bundle.tar.gz");
let sig_path = dir.path().join("bundle.tar.gz.sig");
fs::write(&bundle_path, b"test bundle content").unwrap();
let (signing_key, _) = test_keypair();
let sig = sign_file(&signing_key, &bundle_path);
fs::write(&sig_path, &sig).unwrap();
let wrong_key = SigningKey::from_bytes(&[2u8; 32]);
let wrong_verifying_key = wrong_key.verifying_key();
let wrong_pubkey: &[u8; 32] = wrong_verifying_key.as_bytes();
let result = verify_bundle_with_key(&bundle_path, &sig_path, wrong_pubkey);
assert!(result.is_err(), "Wrong key should fail verification");
}
#[test]
fn test_tampered_bundle_fails() {
let dir = tempdir().unwrap();
let bundle_path = dir.path().join("bundle.tar.gz");
let sig_path = dir.path().join("bundle.tar.gz.sig");
fs::write(&bundle_path, b"original content").unwrap();
let (signing_key, verifying_key) = test_keypair();
let sig = sign_file(&signing_key, &bundle_path);
fs::write(&sig_path, &sig).unwrap();
fs::write(&bundle_path, b"tampered content").unwrap();
let pubkey_bytes: &[u8; 32] = verifying_key.as_bytes();
let result = verify_bundle_with_key(&bundle_path, &sig_path, pubkey_bytes);
assert!(result.is_err(), "Tampered bundle should fail verification");
}
#[test]
fn test_missing_sig_file() {
let dir = tempdir().unwrap();
let bundle_path = dir.path().join("bundle.tar.gz");
let sig_path = dir.path().join("nonexistent.sig");
fs::write(&bundle_path, b"test content").unwrap();
let (_, verifying_key) = test_keypair();
let pubkey_bytes: &[u8; 32] = verifying_key.as_bytes();
let result = verify_bundle_with_key(&bundle_path, &sig_path, pubkey_bytes);
assert!(result.is_err(), "Missing sig file should fail");
}
#[test]
fn test_truncated_signature() {
let dir = tempdir().unwrap();
let bundle_path = dir.path().join("bundle.tar.gz");
let sig_path = dir.path().join("bundle.tar.gz.sig");
fs::write(&bundle_path, b"test content").unwrap();
fs::write(&sig_path, b"too short").unwrap();
let (_, verifying_key) = test_keypair();
let pubkey_bytes: &[u8; 32] = verifying_key.as_bytes();
let result = verify_bundle_with_key(&bundle_path, &sig_path, pubkey_bytes);
assert!(result.is_err(), "Truncated signature should fail");
}
#[test]
fn test_empty_bundle() {
let dir = tempdir().unwrap();
let bundle_path = dir.path().join("bundle.tar.gz");
let sig_path = dir.path().join("bundle.tar.gz.sig");
fs::write(&bundle_path, b"").unwrap();
let (signing_key, verifying_key) = test_keypair();
let sig = sign_file(&signing_key, &bundle_path);
fs::write(&sig_path, &sig).unwrap();
let pubkey_bytes: &[u8; 32] = verifying_key.as_bytes();
let result = verify_bundle_with_key(&bundle_path, &sig_path, pubkey_bytes);
assert!(
result.is_ok(),
"Empty bundle with valid signature should verify"
);
}
}