1use super::signing::ModuleSignatureData;
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
13pub enum TrustLevel {
14 Full,
16 Scoped(Vec<String>),
18 Pinned([u8; 32]),
20}
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct TrustedAuthor {
25 pub name: String,
27 pub public_key: [u8; 32],
29 pub trust_level: TrustLevel,
31}
32
33#[derive(Clone)]
35pub struct Keychain {
36 trusted: HashMap<[u8; 32], TrustedAuthor>,
37 require_signatures: bool,
38}
39
40#[derive(Debug, Clone, PartialEq, Eq)]
42pub enum VerifyResult {
43 Trusted,
45 Unsigned,
47 Rejected(String),
49}
50
51impl Keychain {
52 pub fn new(require_signatures: bool) -> Self {
56 Self {
57 trusted: HashMap::new(),
58 require_signatures,
59 }
60 }
61
62 pub fn add_trusted(&mut self, author: TrustedAuthor) {
64 self.trusted.insert(author.public_key, author);
65 }
66
67 pub fn remove_trusted(&mut self, public_key: &[u8; 32]) -> Option<TrustedAuthor> {
71 self.trusted.remove(public_key)
72 }
73
74 pub fn is_trusted(
77 &self,
78 public_key: &[u8; 32],
79 module_name: &str,
80 manifest_hash: &[u8; 32],
81 ) -> bool {
82 let Some(author) = self.trusted.get(public_key) else {
83 return false;
84 };
85 match &author.trust_level {
86 TrustLevel::Full => true,
87 TrustLevel::Scoped(prefixes) => prefixes
88 .iter()
89 .any(|prefix| module_name.starts_with(prefix)),
90 TrustLevel::Pinned(pinned_hash) => pinned_hash == manifest_hash,
91 }
92 }
93
94 pub fn verify_module(
101 &self,
102 module_name: &str,
103 manifest_hash: &[u8; 32],
104 signature: Option<&ModuleSignatureData>,
105 ) -> VerifyResult {
106 let Some(sig) = signature else {
107 return if self.require_signatures {
108 VerifyResult::Rejected("module is unsigned and signatures are required".into())
109 } else {
110 VerifyResult::Unsigned
111 };
112 };
113
114 if !sig.verify(manifest_hash) {
115 return VerifyResult::Rejected("invalid signature".into());
116 }
117
118 if !self.is_trusted(&sig.author_key, module_name, manifest_hash) {
119 return VerifyResult::Rejected(format!(
120 "author key {} is not trusted for module '{}'",
121 hex::encode(sig.author_key),
122 module_name,
123 ));
124 }
125
126 VerifyResult::Trusted
127 }
128
129 pub fn requires_signatures(&self) -> bool {
131 self.require_signatures
132 }
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138 use crate::crypto::signing::generate_keypair;
139
140 fn make_author(name: &str, key: [u8; 32], trust: TrustLevel) -> TrustedAuthor {
141 TrustedAuthor {
142 name: name.to_string(),
143 public_key: key,
144 trust_level: trust,
145 }
146 }
147
148 #[test]
149 fn test_unsigned_allowed_when_not_required() {
150 let kc = Keychain::new(false);
151 let result = kc.verify_module("my_mod", &[0u8; 32], None);
152 assert_eq!(result, VerifyResult::Unsigned);
153 }
154
155 #[test]
156 fn test_unsigned_rejected_when_required() {
157 let kc = Keychain::new(true);
158 let result = kc.verify_module("my_mod", &[0u8; 32], None);
159 assert!(matches!(result, VerifyResult::Rejected(_)));
160 }
161
162 #[test]
163 fn test_full_trust_verifies() {
164 let (signing_key, verifying_key) = generate_keypair();
165 let mut kc = Keychain::new(true);
166 kc.add_trusted(make_author(
167 "alice",
168 verifying_key.to_bytes(),
169 TrustLevel::Full,
170 ));
171
172 let hash = [1u8; 32];
173 let sig = ModuleSignatureData::sign(&hash, &signing_key);
174 assert_eq!(
175 kc.verify_module("anything", &hash, Some(&sig)),
176 VerifyResult::Trusted
177 );
178 }
179
180 #[test]
181 fn test_scoped_trust_allows_matching_prefix() {
182 let (signing_key, verifying_key) = generate_keypair();
183 let mut kc = Keychain::new(true);
184 kc.add_trusted(make_author(
185 "bob",
186 verifying_key.to_bytes(),
187 TrustLevel::Scoped(vec!["std::".to_string()]),
188 ));
189
190 let hash = [2u8; 32];
191 let sig = ModuleSignatureData::sign(&hash, &signing_key);
192 assert_eq!(
193 kc.verify_module("std::core::math", &hash, Some(&sig)),
194 VerifyResult::Trusted
195 );
196 }
197
198 #[test]
199 fn test_scoped_trust_rejects_non_matching() {
200 let (signing_key, verifying_key) = generate_keypair();
201 let mut kc = Keychain::new(true);
202 kc.add_trusted(make_author(
203 "bob",
204 verifying_key.to_bytes(),
205 TrustLevel::Scoped(vec!["std::".to_string()]),
206 ));
207
208 let hash = [2u8; 32];
209 let sig = ModuleSignatureData::sign(&hash, &signing_key);
210 let result = kc.verify_module("vendor::malware", &hash, Some(&sig));
211 assert!(matches!(result, VerifyResult::Rejected(_)));
212 }
213
214 #[test]
215 fn test_pinned_trust_matching_hash() {
216 let (signing_key, verifying_key) = generate_keypair();
217 let pinned_hash = [5u8; 32];
218 let mut kc = Keychain::new(true);
219 kc.add_trusted(make_author(
220 "carol",
221 verifying_key.to_bytes(),
222 TrustLevel::Pinned(pinned_hash),
223 ));
224
225 let sig = ModuleSignatureData::sign(&pinned_hash, &signing_key);
226 assert_eq!(
227 kc.verify_module("some_mod", &pinned_hash, Some(&sig)),
228 VerifyResult::Trusted
229 );
230 }
231
232 #[test]
233 fn test_pinned_trust_wrong_hash() {
234 let (signing_key, verifying_key) = generate_keypair();
235 let pinned_hash = [5u8; 32];
236 let mut kc = Keychain::new(true);
237 kc.add_trusted(make_author(
238 "carol",
239 verifying_key.to_bytes(),
240 TrustLevel::Pinned(pinned_hash),
241 ));
242
243 let different_hash = [6u8; 32];
244 let sig = ModuleSignatureData::sign(&different_hash, &signing_key);
245 let result = kc.verify_module("some_mod", &different_hash, Some(&sig));
246 assert!(matches!(result, VerifyResult::Rejected(_)));
247 }
248
249 #[test]
250 fn test_untrusted_key_rejected() {
251 let (signing_key, _) = generate_keypair();
252 let kc = Keychain::new(true);
253
254 let hash = [3u8; 32];
255 let sig = ModuleSignatureData::sign(&hash, &signing_key);
256 let result = kc.verify_module("my_mod", &hash, Some(&sig));
257 assert!(matches!(result, VerifyResult::Rejected(_)));
258 }
259
260 #[test]
261 fn test_invalid_signature_rejected() {
262 let (signing_key, verifying_key) = generate_keypair();
263 let mut kc = Keychain::new(true);
264 kc.add_trusted(make_author(
265 "dave",
266 verifying_key.to_bytes(),
267 TrustLevel::Full,
268 ));
269
270 let hash = [4u8; 32];
271 let mut sig = ModuleSignatureData::sign(&hash, &signing_key);
272 sig.signature[0] ^= 0xFF; let result = kc.verify_module("mod", &hash, Some(&sig));
274 assert!(matches!(result, VerifyResult::Rejected(_)));
275 }
276
277 #[test]
278 fn test_remove_trusted() {
279 let (_, verifying_key) = generate_keypair();
280 let mut kc = Keychain::new(false);
281 let key_bytes = verifying_key.to_bytes();
282 kc.add_trusted(make_author("eve", key_bytes, TrustLevel::Full));
283 assert!(kc.is_trusted(&key_bytes, "any", &[0u8; 32]));
284
285 let removed = kc.remove_trusted(&key_bytes);
286 assert!(removed.is_some());
287 assert!(!kc.is_trusted(&key_bytes, "any", &[0u8; 32]));
288 }
289}