actr_hyper/verify/
trust.rs1use std::sync::Arc;
20
21use actr_pack::VerifiedPackage;
22use async_trait::async_trait;
23use ed25519_dalek::VerifyingKey;
24
25use crate::error::{HyperError, HyperResult};
26use crate::verify::cert_cache::MfrCertCache;
27
28#[async_trait]
34pub trait TrustProvider: Send + Sync + std::fmt::Debug {
35 async fn verify_package(&self, bytes: &[u8]) -> HyperResult<VerifiedPackage>;
36}
37
38pub(crate) fn verify_ed25519_manifest(
44 bytes: &[u8],
45 pubkey: &VerifyingKey,
46) -> HyperResult<VerifiedPackage> {
47 let verified = actr_pack::verify(bytes, pubkey).map_err(pack_err_to_hyper)?;
48
49 tracing::info!(
50 actr_type = %verified.manifest.actr_type_str(),
51 ".actr package verified"
52 );
53
54 Ok(verified)
55}
56
57fn pack_err_to_hyper(e: actr_pack::PackError) -> HyperError {
58 match e {
59 actr_pack::PackError::SignatureVerificationFailed(msg) => {
60 HyperError::SignatureVerificationFailed(msg)
61 }
62 actr_pack::PackError::BinaryHashMismatch { .. } => HyperError::BinaryHashMismatch,
63 actr_pack::PackError::SignatureNotFound => {
64 HyperError::SignatureVerificationFailed("signature not found in package".to_string())
65 }
66 actr_pack::PackError::BinaryNotFound(path) => {
67 HyperError::InvalidManifest(format!("binary not found: {path}"))
68 }
69 actr_pack::PackError::ManifestNotFound => HyperError::ManifestNotFound,
70 actr_pack::PackError::ManifestParseError(msg) => HyperError::InvalidManifest(msg),
71 other => HyperError::InvalidManifest(other.to_string()),
72 }
73}
74
75fn parse_pubkey(bytes: &[u8]) -> HyperResult<VerifyingKey> {
76 let arr: [u8; 32] = bytes
77 .try_into()
78 .map_err(|_| HyperError::Config("Ed25519 pubkey must be exactly 32 bytes".to_string()))?;
79 VerifyingKey::from_bytes(&arr)
80 .map_err(|e| HyperError::Config(format!("invalid Ed25519 pubkey: {e}")))
81}
82
83#[derive(Debug, Clone)]
92pub struct StaticTrust {
93 pubkey: VerifyingKey,
94}
95
96impl StaticTrust {
97 pub fn new(pubkey: impl AsRef<[u8]>) -> HyperResult<Self> {
99 Ok(Self {
100 pubkey: parse_pubkey(pubkey.as_ref())?,
101 })
102 }
103
104 pub fn dev_only() -> Self {
116 Self {
119 pubkey: VerifyingKey::from_bytes(&[0u8; 32]).expect("all-zero pubkey parses"),
120 }
121 }
122}
123
124#[async_trait]
125impl TrustProvider for StaticTrust {
126 async fn verify_package(&self, bytes: &[u8]) -> HyperResult<VerifiedPackage> {
127 verify_ed25519_manifest(bytes, &self.pubkey)
128 }
129}
130
131#[derive(Debug, Clone)]
140pub struct RegistryTrust {
141 cache: Arc<MfrCertCache>,
142}
143
144impl RegistryTrust {
145 pub fn new(endpoint: impl Into<String>) -> Self {
146 Self {
147 cache: MfrCertCache::new(endpoint),
148 }
149 }
150}
151
152#[async_trait]
153impl TrustProvider for RegistryTrust {
154 async fn verify_package(&self, bytes: &[u8]) -> HyperResult<VerifiedPackage> {
155 let pack_manifest = actr_pack::read_manifest(bytes).map_err(|e| match e {
156 actr_pack::PackError::ManifestNotFound => HyperError::ManifestNotFound,
157 actr_pack::PackError::ManifestParseError(msg) => HyperError::InvalidManifest(msg),
158 other => HyperError::InvalidManifest(other.to_string()),
159 })?;
160
161 let key_id = pack_manifest.signing_key_id.as_deref().ok_or_else(|| {
162 HyperError::InvalidManifest(
163 "package manifest missing `signing_key_id`; rebuild with the latest `actr build`"
164 .to_string(),
165 )
166 })?;
167
168 let pubkey = self
169 .cache
170 .get_or_fetch(&pack_manifest.manufacturer, Some(key_id))
171 .await?;
172
173 verify_ed25519_manifest(bytes, &pubkey)
174 }
175}
176
177#[derive(Debug, Clone)]
185pub struct ChainTrust {
186 providers: Vec<Arc<dyn TrustProvider>>,
187}
188
189impl ChainTrust {
190 pub fn new(providers: Vec<Arc<dyn TrustProvider>>) -> Self {
191 Self { providers }
192 }
193
194 pub fn of(first: Arc<dyn TrustProvider>, second: Arc<dyn TrustProvider>) -> Self {
196 Self::new(vec![first, second])
197 }
198}
199
200#[async_trait]
201impl TrustProvider for ChainTrust {
202 async fn verify_package(&self, bytes: &[u8]) -> HyperResult<VerifiedPackage> {
203 let mut last_err: Option<HyperError> = None;
204 for p in &self.providers {
205 match p.verify_package(bytes).await {
206 Ok(m) => return Ok(m),
207 Err(e) => last_err = Some(e),
208 }
209 }
210 Err(last_err.unwrap_or_else(|| {
211 HyperError::SignatureVerificationFailed("empty trust chain".to_string())
212 }))
213 }
214}
215
216#[cfg(test)]
217mod tests {
218 use super::*;
219 use ed25519_dalek::{Signer, SigningKey};
220 use rand::rngs::OsRng;
221
222 fn make_minimal_package(signing_key: &SigningKey) -> Vec<u8> {
223 let manifest = actr_pack::PackageManifest {
224 manufacturer: "test-mfr".to_string(),
225 name: "Test".to_string(),
226 version: "1.0.0".to_string(),
227 binary: actr_pack::BinaryEntry {
228 path: "bin/actor.wasm".to_string(),
229 target: "wasm32-wasip1".to_string(),
230 hash: String::new(),
231 size: None,
232 kind: None,
233 },
234 signature_algorithm: "ed25519".to_string(),
235 signing_key_id: Some(actr_pack::compute_key_id(
236 &signing_key.verifying_key().to_bytes(),
237 )),
238 resources: vec![],
239 proto_files: vec![],
240 lock_file: None,
241 metadata: actr_pack::ManifestMetadata::default(),
242 };
243 actr_pack::pack(&actr_pack::PackOptions {
244 manifest,
245 binary_bytes: b"wasm".to_vec(),
246 resources: vec![],
247 proto_files: vec![],
248 lock_file: None,
249 signing_key: signing_key.clone(),
250 })
251 .unwrap()
252 }
253
254 #[tokio::test]
255 async fn static_trust_accepts_valid_package() {
256 let key = SigningKey::generate(&mut OsRng);
257 let vk = key.verifying_key();
258 let pkg = make_minimal_package(&key);
259
260 let trust = StaticTrust::new(vk.to_bytes()).unwrap();
261 let verified = trust.verify_package(&pkg).await.unwrap();
262 assert_eq!(verified.manifest.manufacturer, "test-mfr");
263 }
264
265 #[tokio::test]
266 async fn static_trust_rejects_wrong_key() {
267 let key = SigningKey::generate(&mut OsRng);
268 let wrong = SigningKey::generate(&mut OsRng);
269 let pkg = make_minimal_package(&key);
270
271 let trust = StaticTrust::new(wrong.verifying_key().to_bytes()).unwrap();
272 assert!(matches!(
273 trust.verify_package(&pkg).await,
274 Err(HyperError::SignatureVerificationFailed(_))
275 ));
276 }
277
278 #[tokio::test]
279 async fn chain_first_match_wins() {
280 let key = SigningKey::generate(&mut OsRng);
281 let other = SigningKey::generate(&mut OsRng);
282 let pkg = make_minimal_package(&key);
283
284 let wrong: Arc<dyn TrustProvider> =
285 Arc::new(StaticTrust::new(other.verifying_key().to_bytes()).unwrap());
286 let right: Arc<dyn TrustProvider> =
287 Arc::new(StaticTrust::new(key.verifying_key().to_bytes()).unwrap());
288
289 let chain = ChainTrust::of(wrong, right);
290 let verified = chain.verify_package(&pkg).await.unwrap();
291 assert_eq!(verified.manifest.manufacturer, "test-mfr");
292 }
293
294 #[tokio::test]
295 async fn chain_all_fail_returns_last_error() {
296 let key = SigningKey::generate(&mut OsRng);
297 let wrong1 = SigningKey::generate(&mut OsRng);
298 let wrong2 = SigningKey::generate(&mut OsRng);
299 let pkg = make_minimal_package(&key);
300
301 let chain = ChainTrust::of(
302 Arc::new(StaticTrust::new(wrong1.verifying_key().to_bytes()).unwrap()),
303 Arc::new(StaticTrust::new(wrong2.verifying_key().to_bytes()).unwrap()),
304 );
305 assert!(matches!(
306 chain.verify_package(&pkg).await,
307 Err(HyperError::SignatureVerificationFailed(_))
308 ));
309 }
310
311 #[allow(dead_code)]
313 fn _signer_sanity(key: &SigningKey) -> ed25519_dalek::Signature {
314 key.sign(b"x")
315 }
316}