1use std::borrow::Cow;
4
5use iris_format::{Container, DecoderLocation, DecoderRef, Digest};
6
7use crate::error::Untrusted;
8use crate::policy::Policy;
9
10#[derive(Clone, Debug)]
21pub struct Verified<'a> {
22 record: DecoderRef<'a>,
23 module: Cow<'a, [u8]>,
24 digest: Digest,
25}
26
27impl<'a> Verified<'a> {
28 #[must_use]
30 pub fn module(&self) -> &[u8] {
31 &self.module
32 }
33
34 #[must_use]
41 pub const fn digest(&self) -> Digest {
42 self.digest
43 }
44
45 #[must_use]
47 pub const fn record(&self) -> &DecoderRef<'a> {
48 &self.record
49 }
50}
51
52impl Policy {
53 pub fn decoder<'a>(&self, container: &Container<'a>) -> Result<Verified<'a>, Untrusted> {
75 let record = container.decoder().ok_or(Untrusted::Missing)?;
76 self.check(record, container.decoder_bytes().map(Cow::Borrowed))
77 }
78
79 pub fn decoder_read<'a>(
95 &self,
96 record: &DecoderRef<'a>,
97 embedded: Option<Vec<u8>>,
98 ) -> Result<Verified<'a>, Untrusted> {
99 self.check(record, embedded.map(Cow::Owned))
100 }
101
102 fn check<'a>(
104 &self,
105 record: &DecoderRef<'a>,
106 embedded: Option<Cow<'a, [u8]>>,
107 ) -> Result<Verified<'a>, Untrusted> {
108 let expected = record.digest;
109
110 let module: Cow<'a, [u8]> = match record.location {
111 DecoderLocation::Embedded { section } => embedded.ok_or(Untrusted::Lost { section })?,
112 DecoderLocation::External => {
113 let resolver = self.resolver().ok_or_else(|| Untrusted::External {
114 name: record.name.to_owned(),
115 })?;
116 let found = resolver
117 .resolve(record)
118 .ok_or_else(|| Untrusted::Unresolved {
119 name: record.name.to_owned(),
120 digest: expected,
121 })?;
122 Cow::Owned(found)
123 }
124 _ => {
127 return Err(Untrusted::Elsewhere {
128 name: record.name.to_owned(),
129 });
130 }
131 };
132
133 let found = Digest::of(&module);
134 if found != expected {
135 return Err(Untrusted::Digest { expected, found });
136 }
137
138 Ok(Verified {
139 record: record.clone(),
140 module,
141 digest: found,
142 })
143 }
144}
145
146pub fn decoder<'a>(container: &Container<'a>) -> Result<Verified<'a>, Untrusted> {
156 Policy::embedded_only().decoder(container)
157}
158
159#[cfg(test)]
160mod tests {
161 use iris_abi::CapabilitySet;
162 use iris_format::{Builder, Container, DecoderRef, Digest, SectionKind};
163
164 use super::{Policy, Untrusted, decoder};
165
166 const MODULE: &[u8] = b"a module, as far as this crate is concerned";
169
170 #[derive(Debug)]
173 struct Holding(Option<Vec<u8>>);
174
175 impl super::super::Resolve for Holding {
176 fn resolve(&self, _decoder: &DecoderRef<'_>) -> Option<Vec<u8>> {
177 self.0.clone()
178 }
179 }
180
181 fn embedded() -> Vec<u8> {
182 let mut builder = Builder::new("readings", 3);
183 builder.section(SectionKind::Data, b"rows go here".to_vec());
184 builder.embed_decoder("test", (1, 0), CapabilitySet::new(), MODULE.to_vec());
185 builder.build().expect("a container this small always fits")
186 }
187
188 fn external() -> Vec<u8> {
189 let mut builder = Builder::new("readings", 3);
190 builder.section(SectionKind::Data, b"rows go here".to_vec());
191 builder.external_decoder(
192 "elsewhere",
193 (1, 0),
194 CapabilitySet::new(),
195 Digest::of(MODULE),
196 );
197 builder.build().expect("a container this small always fits")
198 }
199
200 fn module_at(bytes: &[u8]) -> usize {
202 bytes
203 .windows(MODULE.len())
204 .position(|window| window == MODULE)
205 .expect("the builder wrote the module into the file")
206 }
207
208 #[test]
209 fn a_module_that_matches_its_digest_is_handed_over() {
210 let bytes = embedded();
211 let container = Container::parse(&bytes).expect("the container parses");
212 let verified = decoder(&container).expect("the module is the one the container names");
213
214 assert_eq!(verified.module(), MODULE);
215 assert_eq!(verified.digest(), Digest::of(MODULE));
216 assert_eq!(verified.record().name, "test");
217 }
218
219 #[test]
220 fn one_flipped_byte_in_the_module_is_refused_with_both_digests() {
221 let mut bytes = embedded();
222 let at = module_at(&bytes) + MODULE.len() / 2;
223 bytes[at] ^= 1;
224
225 let container = Container::parse(&bytes).expect("the container still parses");
230
231 let Err(Untrusted::Digest { expected, found }) = decoder(&container) else {
232 panic!("a module with a flipped byte was accepted");
233 };
234 assert_eq!(expected, Digest::of(MODULE));
235 assert_ne!(found, expected);
236
237 let message = Untrusted::Digest { expected, found }.to_string();
238 assert!(
239 message.contains(&expected.to_string()),
240 "the message does not say which module was expected: {message}"
241 );
242 assert!(
243 message.contains(&found.to_string()),
244 "the message does not say what arrived instead: {message}"
245 );
246 }
247
248 #[test]
249 fn a_container_with_no_decoder_says_so() {
250 let mut builder = Builder::new("readings", 3);
251 builder.section(SectionKind::Data, b"rows go here".to_vec());
252 let bytes = builder.build().expect("a container this small always fits");
253 let container = Container::parse(&bytes).expect("the container parses");
254
255 assert_eq!(decoder(&container).unwrap_err(), Untrusted::Missing);
256 }
257
258 #[test]
259 fn the_default_policy_refuses_a_decoder_that_is_not_in_the_container() {
260 let bytes = external();
261 let container = Container::parse(&bytes).expect("the container parses");
262
263 let error = decoder(&container).unwrap_err();
264 assert_eq!(
265 error,
266 Untrusted::External {
267 name: "elsewhere".to_owned()
268 }
269 );
270
271 let message = error.to_string();
274 assert!(
275 message.contains("Policy::with_external_decoders_resolved_by"),
276 "the message does not name the setting that would allow this: {message}"
277 );
278 }
279
280 #[test]
281 fn a_host_that_opted_in_gets_the_module_its_resolver_found() {
282 let bytes = external();
283 let container = Container::parse(&bytes).expect("the container parses");
284 let policy = Policy::with_external_decoders_resolved_by(Holding(Some(MODULE.to_vec())));
285
286 let verified = policy
287 .decoder(&container)
288 .expect("the resolver returned the module the container names");
289 assert_eq!(verified.module(), MODULE);
290 assert_eq!(verified.digest(), Digest::of(MODULE));
291 }
292
293 #[test]
294 fn a_resolver_that_returns_the_wrong_module_is_caught_by_the_digest() {
295 let bytes = external();
296 let container = Container::parse(&bytes).expect("the container parses");
297 let policy = Policy::with_external_decoders_resolved_by(Holding(Some(
298 b"some other module entirely".to_vec(),
299 )));
300
301 let Err(Untrusted::Digest { expected, found }) = policy.decoder(&container) else {
302 panic!("a fetched module nobody checked was accepted");
303 };
304 assert_eq!(expected, Digest::of(MODULE));
305 assert_ne!(found, expected);
306 }
307
308 #[test]
309 fn a_module_read_out_of_a_file_is_checked_the_same_way() {
310 let bytes = embedded();
311 let container = Container::parse(&bytes).expect("the container parses");
312 let record = container.decoder().expect("the container names a decoder");
313
314 let read = container
317 .decoder_bytes()
318 .expect("the section is here")
319 .to_vec();
320 let verified = Policy::embedded_only()
321 .decoder_read(record, Some(read))
322 .expect("the module is the one the container names");
323
324 assert_eq!(verified.module(), MODULE);
325 assert_eq!(verified.digest(), Digest::of(MODULE));
326 assert_eq!(verified.record().name, "test");
327
328 assert_eq!(
331 decoder(&container)
332 .expect("the resident path agrees")
333 .digest(),
334 verified.digest()
335 );
336 }
337
338 #[test]
339 fn a_module_read_wrong_is_refused_by_the_digest_and_not_by_where_it_came_from() {
340 let bytes = embedded();
341 let container = Container::parse(&bytes).expect("the container parses");
342 let record = container.decoder().expect("the container names a decoder");
343
344 let mut read = container
345 .decoder_bytes()
346 .expect("the section is here")
347 .to_vec();
348 read[MODULE.len() / 2] ^= 1;
349
350 let Err(Untrusted::Digest { expected, found }) =
351 Policy::embedded_only().decoder_read(record, Some(read))
352 else {
353 panic!("a module that was read wrong was accepted");
354 };
355 assert_eq!(expected, Digest::of(MODULE));
356 assert_ne!(found, expected);
357 }
358
359 #[test]
360 fn an_embedded_record_with_nothing_read_says_the_section_is_lost() {
361 let bytes = embedded();
362 let container = Container::parse(&bytes).expect("the container parses");
363 let record = container.decoder().expect("the container names a decoder");
364
365 assert_eq!(
368 Policy::embedded_only()
369 .decoder_read(record, None)
370 .unwrap_err(),
371 Untrusted::Lost { section: 1 }
372 );
373 }
374
375 #[test]
376 fn a_resolver_that_finds_nothing_is_not_an_attack() {
377 let bytes = external();
378 let container = Container::parse(&bytes).expect("the container parses");
379 let policy = Policy::with_external_decoders_resolved_by(Holding(None));
380
381 assert_eq!(
382 policy.decoder(&container).unwrap_err(),
383 Untrusted::Unresolved {
384 name: "elsewhere".to_owned(),
385 digest: Digest::of(MODULE),
386 }
387 );
388 }
389}