use std::sync::Arc;
use actr_pack::VerifiedPackage;
use async_trait::async_trait;
use ed25519_dalek::VerifyingKey;
use crate::error::{HyperError, HyperResult};
use crate::verify::cert_cache::MfrCertCache;
#[async_trait]
pub trait TrustProvider: Send + Sync + std::fmt::Debug {
async fn verify_package(&self, bytes: &[u8]) -> HyperResult<VerifiedPackage>;
}
pub(crate) fn verify_ed25519_manifest(
bytes: &[u8],
pubkey: &VerifyingKey,
) -> HyperResult<VerifiedPackage> {
let verified = actr_pack::verify(bytes, pubkey).map_err(pack_err_to_hyper)?;
tracing::info!(
actr_type = %verified.manifest.actr_type_str(),
".actr package verified"
);
Ok(verified)
}
fn pack_err_to_hyper(e: actr_pack::PackError) -> HyperError {
match e {
actr_pack::PackError::SignatureVerificationFailed(msg) => {
HyperError::SignatureVerificationFailed(msg)
}
actr_pack::PackError::BinaryHashMismatch { .. } => HyperError::BinaryHashMismatch,
actr_pack::PackError::SignatureNotFound => {
HyperError::SignatureVerificationFailed("signature not found in package".to_string())
}
actr_pack::PackError::BinaryNotFound(path) => {
HyperError::InvalidManifest(format!("binary not found: {path}"))
}
actr_pack::PackError::ManifestNotFound => HyperError::ManifestNotFound,
actr_pack::PackError::ManifestParseError(msg) => HyperError::InvalidManifest(msg),
other => HyperError::InvalidManifest(other.to_string()),
}
}
fn parse_pubkey(bytes: &[u8]) -> HyperResult<VerifyingKey> {
let arr: [u8; 32] = bytes
.try_into()
.map_err(|_| HyperError::Config("Ed25519 pubkey must be exactly 32 bytes".to_string()))?;
VerifyingKey::from_bytes(&arr)
.map_err(|e| HyperError::Config(format!("invalid Ed25519 pubkey: {e}")))
}
#[derive(Debug, Clone)]
pub struct StaticTrust {
pubkey: VerifyingKey,
}
impl StaticTrust {
pub fn new(pubkey: impl AsRef<[u8]>) -> HyperResult<Self> {
Ok(Self {
pubkey: parse_pubkey(pubkey.as_ref())?,
})
}
pub fn dev_only() -> Self {
Self {
pubkey: VerifyingKey::from_bytes(&[0u8; 32]).expect("all-zero pubkey parses"),
}
}
}
#[async_trait]
impl TrustProvider for StaticTrust {
async fn verify_package(&self, bytes: &[u8]) -> HyperResult<VerifiedPackage> {
verify_ed25519_manifest(bytes, &self.pubkey)
}
}
#[derive(Debug, Clone)]
pub struct RegistryTrust {
cache: Arc<MfrCertCache>,
}
impl RegistryTrust {
pub fn new(endpoint: impl Into<String>) -> Self {
Self {
cache: MfrCertCache::new(endpoint),
}
}
}
#[async_trait]
impl TrustProvider for RegistryTrust {
async fn verify_package(&self, bytes: &[u8]) -> HyperResult<VerifiedPackage> {
let pack_manifest = actr_pack::read_manifest(bytes).map_err(|e| match e {
actr_pack::PackError::ManifestNotFound => HyperError::ManifestNotFound,
actr_pack::PackError::ManifestParseError(msg) => HyperError::InvalidManifest(msg),
other => HyperError::InvalidManifest(other.to_string()),
})?;
let key_id = pack_manifest.signing_key_id.as_deref().ok_or_else(|| {
HyperError::InvalidManifest(
"package manifest missing `signing_key_id`; rebuild with the latest `actr build`"
.to_string(),
)
})?;
let pubkey = self
.cache
.get_or_fetch(&pack_manifest.manufacturer, Some(key_id))
.await?;
verify_ed25519_manifest(bytes, &pubkey)
}
}
#[derive(Debug, Clone)]
pub struct ChainTrust {
providers: Vec<Arc<dyn TrustProvider>>,
}
impl ChainTrust {
pub fn new(providers: Vec<Arc<dyn TrustProvider>>) -> Self {
Self { providers }
}
pub fn of(first: Arc<dyn TrustProvider>, second: Arc<dyn TrustProvider>) -> Self {
Self::new(vec![first, second])
}
}
#[async_trait]
impl TrustProvider for ChainTrust {
async fn verify_package(&self, bytes: &[u8]) -> HyperResult<VerifiedPackage> {
let mut last_err: Option<HyperError> = None;
for p in &self.providers {
match p.verify_package(bytes).await {
Ok(m) => return Ok(m),
Err(e) => last_err = Some(e),
}
}
Err(last_err.unwrap_or_else(|| {
HyperError::SignatureVerificationFailed("empty trust chain".to_string())
}))
}
}
#[cfg(test)]
mod tests {
use super::*;
use ed25519_dalek::{Signer, SigningKey};
use rand::rngs::OsRng;
fn make_minimal_package(signing_key: &SigningKey) -> Vec<u8> {
let manifest = actr_pack::PackageManifest {
manufacturer: "test-mfr".to_string(),
name: "Test".to_string(),
version: "1.0.0".to_string(),
binary: actr_pack::BinaryEntry {
path: "bin/actor.wasm".to_string(),
target: "wasm32-wasip1".to_string(),
hash: String::new(),
size: None,
kind: None,
},
signature_algorithm: "ed25519".to_string(),
signing_key_id: Some(actr_pack::compute_key_id(
&signing_key.verifying_key().to_bytes(),
)),
resources: vec![],
proto_files: vec![],
lock_file: None,
metadata: actr_pack::ManifestMetadata::default(),
};
actr_pack::pack(&actr_pack::PackOptions {
manifest,
binary_bytes: b"wasm".to_vec(),
resources: vec![],
proto_files: vec![],
lock_file: None,
signing_key: signing_key.clone(),
})
.unwrap()
}
#[tokio::test]
async fn static_trust_accepts_valid_package() {
let key = SigningKey::generate(&mut OsRng);
let vk = key.verifying_key();
let pkg = make_minimal_package(&key);
let trust = StaticTrust::new(vk.to_bytes()).unwrap();
let verified = trust.verify_package(&pkg).await.unwrap();
assert_eq!(verified.manifest.manufacturer, "test-mfr");
}
#[tokio::test]
async fn static_trust_rejects_wrong_key() {
let key = SigningKey::generate(&mut OsRng);
let wrong = SigningKey::generate(&mut OsRng);
let pkg = make_minimal_package(&key);
let trust = StaticTrust::new(wrong.verifying_key().to_bytes()).unwrap();
assert!(matches!(
trust.verify_package(&pkg).await,
Err(HyperError::SignatureVerificationFailed(_))
));
}
#[tokio::test]
async fn chain_first_match_wins() {
let key = SigningKey::generate(&mut OsRng);
let other = SigningKey::generate(&mut OsRng);
let pkg = make_minimal_package(&key);
let wrong: Arc<dyn TrustProvider> =
Arc::new(StaticTrust::new(other.verifying_key().to_bytes()).unwrap());
let right: Arc<dyn TrustProvider> =
Arc::new(StaticTrust::new(key.verifying_key().to_bytes()).unwrap());
let chain = ChainTrust::of(wrong, right);
let verified = chain.verify_package(&pkg).await.unwrap();
assert_eq!(verified.manifest.manufacturer, "test-mfr");
}
#[tokio::test]
async fn chain_all_fail_returns_last_error() {
let key = SigningKey::generate(&mut OsRng);
let wrong1 = SigningKey::generate(&mut OsRng);
let wrong2 = SigningKey::generate(&mut OsRng);
let pkg = make_minimal_package(&key);
let chain = ChainTrust::of(
Arc::new(StaticTrust::new(wrong1.verifying_key().to_bytes()).unwrap()),
Arc::new(StaticTrust::new(wrong2.verifying_key().to_bytes()).unwrap()),
);
assert!(matches!(
chain.verify_package(&pkg).await,
Err(HyperError::SignatureVerificationFailed(_))
));
}
#[allow(dead_code)]
fn _signer_sanity(key: &SigningKey) -> ed25519_dalek::Signature {
key.sign(b"x")
}
}