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 let expected = record.digest;
77
78 let module: Cow<'a, [u8]> = match record.location {
79 DecoderLocation::Embedded { section } => Cow::Borrowed(
80 container
81 .decoder_bytes()
82 .ok_or(Untrusted::Lost { section })?,
83 ),
84 DecoderLocation::External => {
85 let resolver = self.resolver().ok_or_else(|| Untrusted::External {
86 name: record.name.to_owned(),
87 })?;
88 let found = resolver
89 .resolve(record)
90 .ok_or_else(|| Untrusted::Unresolved {
91 name: record.name.to_owned(),
92 digest: expected,
93 })?;
94 Cow::Owned(found)
95 }
96 _ => {
99 return Err(Untrusted::Elsewhere {
100 name: record.name.to_owned(),
101 });
102 }
103 };
104
105 let found = Digest::of(&module);
106 if found != expected {
107 return Err(Untrusted::Digest { expected, found });
108 }
109
110 Ok(Verified {
111 record: record.clone(),
112 module,
113 digest: found,
114 })
115 }
116}
117
118pub fn decoder<'a>(container: &Container<'a>) -> Result<Verified<'a>, Untrusted> {
128 Policy::embedded_only().decoder(container)
129}
130
131#[cfg(test)]
132mod tests {
133 use iris_abi::CapabilitySet;
134 use iris_format::{Builder, Container, DecoderRef, Digest, SectionKind};
135
136 use super::{Policy, Untrusted, decoder};
137
138 const MODULE: &[u8] = b"a module, as far as this crate is concerned";
141
142 #[derive(Debug)]
145 struct Holding(Option<Vec<u8>>);
146
147 impl super::super::Resolve for Holding {
148 fn resolve(&self, _decoder: &DecoderRef<'_>) -> Option<Vec<u8>> {
149 self.0.clone()
150 }
151 }
152
153 fn embedded() -> Vec<u8> {
154 let mut builder = Builder::new("readings", 3);
155 builder.section(SectionKind::Data, b"rows go here".to_vec());
156 builder.embed_decoder("test", (1, 0), CapabilitySet::new(), MODULE.to_vec());
157 builder.build().expect("a container this small always fits")
158 }
159
160 fn external() -> Vec<u8> {
161 let mut builder = Builder::new("readings", 3);
162 builder.section(SectionKind::Data, b"rows go here".to_vec());
163 builder.external_decoder(
164 "elsewhere",
165 (1, 0),
166 CapabilitySet::new(),
167 Digest::of(MODULE),
168 );
169 builder.build().expect("a container this small always fits")
170 }
171
172 fn module_at(bytes: &[u8]) -> usize {
174 bytes
175 .windows(MODULE.len())
176 .position(|window| window == MODULE)
177 .expect("the builder wrote the module into the file")
178 }
179
180 #[test]
181 fn a_module_that_matches_its_digest_is_handed_over() {
182 let bytes = embedded();
183 let container = Container::parse(&bytes).expect("the container parses");
184 let verified = decoder(&container).expect("the module is the one the container names");
185
186 assert_eq!(verified.module(), MODULE);
187 assert_eq!(verified.digest(), Digest::of(MODULE));
188 assert_eq!(verified.record().name, "test");
189 }
190
191 #[test]
192 fn one_flipped_byte_in_the_module_is_refused_with_both_digests() {
193 let mut bytes = embedded();
194 let at = module_at(&bytes) + MODULE.len() / 2;
195 bytes[at] ^= 1;
196
197 let container = Container::parse(&bytes).expect("the container still parses");
202
203 let Err(Untrusted::Digest { expected, found }) = decoder(&container) else {
204 panic!("a module with a flipped byte was accepted");
205 };
206 assert_eq!(expected, Digest::of(MODULE));
207 assert_ne!(found, expected);
208
209 let message = Untrusted::Digest { expected, found }.to_string();
210 assert!(
211 message.contains(&expected.to_string()),
212 "the message does not say which module was expected: {message}"
213 );
214 assert!(
215 message.contains(&found.to_string()),
216 "the message does not say what arrived instead: {message}"
217 );
218 }
219
220 #[test]
221 fn a_container_with_no_decoder_says_so() {
222 let mut builder = Builder::new("readings", 3);
223 builder.section(SectionKind::Data, b"rows go here".to_vec());
224 let bytes = builder.build().expect("a container this small always fits");
225 let container = Container::parse(&bytes).expect("the container parses");
226
227 assert_eq!(decoder(&container).unwrap_err(), Untrusted::Missing);
228 }
229
230 #[test]
231 fn the_default_policy_refuses_a_decoder_that_is_not_in_the_container() {
232 let bytes = external();
233 let container = Container::parse(&bytes).expect("the container parses");
234
235 let error = decoder(&container).unwrap_err();
236 assert_eq!(
237 error,
238 Untrusted::External {
239 name: "elsewhere".to_owned()
240 }
241 );
242
243 let message = error.to_string();
246 assert!(
247 message.contains("Policy::with_external_decoders_resolved_by"),
248 "the message does not name the setting that would allow this: {message}"
249 );
250 }
251
252 #[test]
253 fn a_host_that_opted_in_gets_the_module_its_resolver_found() {
254 let bytes = external();
255 let container = Container::parse(&bytes).expect("the container parses");
256 let policy = Policy::with_external_decoders_resolved_by(Holding(Some(MODULE.to_vec())));
257
258 let verified = policy
259 .decoder(&container)
260 .expect("the resolver returned the module the container names");
261 assert_eq!(verified.module(), MODULE);
262 assert_eq!(verified.digest(), Digest::of(MODULE));
263 }
264
265 #[test]
266 fn a_resolver_that_returns_the_wrong_module_is_caught_by_the_digest() {
267 let bytes = external();
268 let container = Container::parse(&bytes).expect("the container parses");
269 let policy = Policy::with_external_decoders_resolved_by(Holding(Some(
270 b"some other module entirely".to_vec(),
271 )));
272
273 let Err(Untrusted::Digest { expected, found }) = policy.decoder(&container) else {
274 panic!("a fetched module nobody checked was accepted");
275 };
276 assert_eq!(expected, Digest::of(MODULE));
277 assert_ne!(found, expected);
278 }
279
280 #[test]
281 fn a_resolver_that_finds_nothing_is_not_an_attack() {
282 let bytes = external();
283 let container = Container::parse(&bytes).expect("the container parses");
284 let policy = Policy::with_external_decoders_resolved_by(Holding(None));
285
286 assert_eq!(
287 policy.decoder(&container).unwrap_err(),
288 Untrusted::Unresolved {
289 name: "elsewhere".to_owned(),
290 digest: Digest::of(MODULE),
291 }
292 );
293 }
294}