use std::fs;
use std::path::Path;
use hush_core::{Hash, Keypair, PublicKey, Signature};
use serde::{Deserialize, Serialize};
use crate::error::{Error, Result};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PackageSignature {
pub hash: Hash,
pub signature: Signature,
#[serde(skip_serializing_if = "Option::is_none")]
pub public_key: Option<PublicKey>,
}
pub fn sign_package(archive_path: &Path, keypair: &Keypair) -> Result<PackageSignature> {
let bytes = fs::read(archive_path)?;
let hash = hush_core::sha256(&bytes);
let signature = keypair.sign(hash.as_bytes());
Ok(PackageSignature {
hash,
signature,
public_key: Some(keypair.public_key()),
})
}
pub fn verify_package(
archive_path: &Path,
signature: &PackageSignature,
public_key: &PublicKey,
) -> Result<bool> {
let bytes = fs::read(archive_path)?;
let hash = hush_core::sha256(&bytes);
if hash != signature.hash {
return Ok(false);
}
Ok(public_key.verify(hash.as_bytes(), &signature.signature))
}
pub fn verify_package_embedded(archive_path: &Path, signature: &PackageSignature) -> Result<bool> {
match &signature.public_key {
Some(pk) => verify_package(archive_path, signature, pk),
None => Err(Error::PkgError(
"no public key embedded in signature".to_string(),
)),
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
fn create_test_archive(tmp: &Path) -> PathBuf {
let archive = tmp.join("test.cpkg");
fs::write(&archive, b"fake archive content for signing").unwrap();
archive
}
#[test]
fn sign_and_verify_roundtrip() {
let tmp = tempfile::tempdir().unwrap();
let archive = create_test_archive(tmp.path());
let keypair = Keypair::generate();
let sig = sign_package(&archive, &keypair).unwrap();
assert!(verify_package(&archive, &sig, &keypair.public_key()).unwrap());
}
#[test]
fn verify_embedded_roundtrip() {
let tmp = tempfile::tempdir().unwrap();
let archive = create_test_archive(tmp.path());
let keypair = Keypair::generate();
let sig = sign_package(&archive, &keypair).unwrap();
assert!(verify_package_embedded(&archive, &sig).unwrap());
}
#[test]
fn tamper_detection() {
let tmp = tempfile::tempdir().unwrap();
let archive = create_test_archive(tmp.path());
let keypair = Keypair::generate();
let sig = sign_package(&archive, &keypair).unwrap();
fs::write(&archive, b"tampered content!!!").unwrap();
assert!(!verify_package(&archive, &sig, &keypair.public_key()).unwrap());
}
#[test]
fn wrong_key_fails() {
let tmp = tempfile::tempdir().unwrap();
let archive = create_test_archive(tmp.path());
let keypair = Keypair::generate();
let other_keypair = Keypair::generate();
let sig = sign_package(&archive, &keypair).unwrap();
assert!(!verify_package(&archive, &sig, &other_keypair.public_key()).unwrap());
}
#[test]
fn signature_serde_roundtrip() {
let tmp = tempfile::tempdir().unwrap();
let archive = create_test_archive(tmp.path());
let keypair = Keypair::generate();
let sig = sign_package(&archive, &keypair).unwrap();
let json = serde_json::to_string(&sig).unwrap();
let restored: PackageSignature = serde_json::from_str(&json).unwrap();
assert!(verify_package(&archive, &restored, &keypair.public_key()).unwrap());
}
}