use std::borrow::Cow;
use iris_format::{Container, DecoderLocation, DecoderRef, Digest};
use crate::error::Untrusted;
use crate::policy::Policy;
#[derive(Clone, Debug)]
pub struct Verified<'a> {
record: DecoderRef<'a>,
module: Cow<'a, [u8]>,
digest: Digest,
}
impl<'a> Verified<'a> {
#[must_use]
pub fn module(&self) -> &[u8] {
&self.module
}
#[must_use]
pub const fn digest(&self) -> Digest {
self.digest
}
#[must_use]
pub const fn record(&self) -> &DecoderRef<'a> {
&self.record
}
}
impl Policy {
pub fn decoder<'a>(&self, container: &Container<'a>) -> Result<Verified<'a>, Untrusted> {
let record = container.decoder().ok_or(Untrusted::Missing)?;
self.check(record, container.decoder_bytes().map(Cow::Borrowed))
}
pub fn decoder_read<'a>(
&self,
record: &DecoderRef<'a>,
embedded: Option<Vec<u8>>,
) -> Result<Verified<'a>, Untrusted> {
self.check(record, embedded.map(Cow::Owned))
}
fn check<'a>(
&self,
record: &DecoderRef<'a>,
embedded: Option<Cow<'a, [u8]>>,
) -> Result<Verified<'a>, Untrusted> {
let expected = record.digest;
let module: Cow<'a, [u8]> = match record.location {
DecoderLocation::Embedded { section } => embedded.ok_or(Untrusted::Lost { section })?,
DecoderLocation::External => {
let resolver = self.resolver().ok_or_else(|| Untrusted::External {
name: record.name.to_owned(),
})?;
let found = resolver
.resolve(record)
.ok_or_else(|| Untrusted::Unresolved {
name: record.name.to_owned(),
digest: expected,
})?;
Cow::Owned(found)
}
_ => {
return Err(Untrusted::Elsewhere {
name: record.name.to_owned(),
});
}
};
let found = Digest::of(&module);
if found != expected {
return Err(Untrusted::Digest { expected, found });
}
Ok(Verified {
record: record.clone(),
module,
digest: found,
})
}
}
pub fn decoder<'a>(container: &Container<'a>) -> Result<Verified<'a>, Untrusted> {
Policy::embedded_only().decoder(container)
}
#[cfg(test)]
mod tests {
use iris_abi::CapabilitySet;
use iris_format::{Builder, Container, DecoderRef, Digest, SectionKind};
use super::{Policy, Untrusted, decoder};
const MODULE: &[u8] = b"a module, as far as this crate is concerned";
#[derive(Debug)]
struct Holding(Option<Vec<u8>>);
impl super::super::Resolve for Holding {
fn resolve(&self, _decoder: &DecoderRef<'_>) -> Option<Vec<u8>> {
self.0.clone()
}
}
fn embedded() -> Vec<u8> {
let mut builder = Builder::new("readings", 3);
builder.section(SectionKind::Data, b"rows go here".to_vec());
builder.embed_decoder("test", (1, 0), CapabilitySet::new(), MODULE.to_vec());
builder.build().expect("a container this small always fits")
}
fn external() -> Vec<u8> {
let mut builder = Builder::new("readings", 3);
builder.section(SectionKind::Data, b"rows go here".to_vec());
builder.external_decoder(
"elsewhere",
(1, 0),
CapabilitySet::new(),
Digest::of(MODULE),
);
builder.build().expect("a container this small always fits")
}
fn module_at(bytes: &[u8]) -> usize {
bytes
.windows(MODULE.len())
.position(|window| window == MODULE)
.expect("the builder wrote the module into the file")
}
#[test]
fn a_module_that_matches_its_digest_is_handed_over() {
let bytes = embedded();
let container = Container::parse(&bytes).expect("the container parses");
let verified = decoder(&container).expect("the module is the one the container names");
assert_eq!(verified.module(), MODULE);
assert_eq!(verified.digest(), Digest::of(MODULE));
assert_eq!(verified.record().name, "test");
}
#[test]
fn one_flipped_byte_in_the_module_is_refused_with_both_digests() {
let mut bytes = embedded();
let at = module_at(&bytes) + MODULE.len() / 2;
bytes[at] ^= 1;
let container = Container::parse(&bytes).expect("the container still parses");
let Err(Untrusted::Digest { expected, found }) = decoder(&container) else {
panic!("a module with a flipped byte was accepted");
};
assert_eq!(expected, Digest::of(MODULE));
assert_ne!(found, expected);
let message = Untrusted::Digest { expected, found }.to_string();
assert!(
message.contains(&expected.to_string()),
"the message does not say which module was expected: {message}"
);
assert!(
message.contains(&found.to_string()),
"the message does not say what arrived instead: {message}"
);
}
#[test]
fn a_container_with_no_decoder_says_so() {
let mut builder = Builder::new("readings", 3);
builder.section(SectionKind::Data, b"rows go here".to_vec());
let bytes = builder.build().expect("a container this small always fits");
let container = Container::parse(&bytes).expect("the container parses");
assert_eq!(decoder(&container).unwrap_err(), Untrusted::Missing);
}
#[test]
fn the_default_policy_refuses_a_decoder_that_is_not_in_the_container() {
let bytes = external();
let container = Container::parse(&bytes).expect("the container parses");
let error = decoder(&container).unwrap_err();
assert_eq!(
error,
Untrusted::External {
name: "elsewhere".to_owned()
}
);
let message = error.to_string();
assert!(
message.contains("Policy::with_external_decoders_resolved_by"),
"the message does not name the setting that would allow this: {message}"
);
}
#[test]
fn a_host_that_opted_in_gets_the_module_its_resolver_found() {
let bytes = external();
let container = Container::parse(&bytes).expect("the container parses");
let policy = Policy::with_external_decoders_resolved_by(Holding(Some(MODULE.to_vec())));
let verified = policy
.decoder(&container)
.expect("the resolver returned the module the container names");
assert_eq!(verified.module(), MODULE);
assert_eq!(verified.digest(), Digest::of(MODULE));
}
#[test]
fn a_resolver_that_returns_the_wrong_module_is_caught_by_the_digest() {
let bytes = external();
let container = Container::parse(&bytes).expect("the container parses");
let policy = Policy::with_external_decoders_resolved_by(Holding(Some(
b"some other module entirely".to_vec(),
)));
let Err(Untrusted::Digest { expected, found }) = policy.decoder(&container) else {
panic!("a fetched module nobody checked was accepted");
};
assert_eq!(expected, Digest::of(MODULE));
assert_ne!(found, expected);
}
#[test]
fn a_module_read_out_of_a_file_is_checked_the_same_way() {
let bytes = embedded();
let container = Container::parse(&bytes).expect("the container parses");
let record = container.decoder().expect("the container names a decoder");
let read = container
.decoder_bytes()
.expect("the section is here")
.to_vec();
let verified = Policy::embedded_only()
.decoder_read(record, Some(read))
.expect("the module is the one the container names");
assert_eq!(verified.module(), MODULE);
assert_eq!(verified.digest(), Digest::of(MODULE));
assert_eq!(verified.record().name, "test");
assert_eq!(
decoder(&container)
.expect("the resident path agrees")
.digest(),
verified.digest()
);
}
#[test]
fn a_module_read_wrong_is_refused_by_the_digest_and_not_by_where_it_came_from() {
let bytes = embedded();
let container = Container::parse(&bytes).expect("the container parses");
let record = container.decoder().expect("the container names a decoder");
let mut read = container
.decoder_bytes()
.expect("the section is here")
.to_vec();
read[MODULE.len() / 2] ^= 1;
let Err(Untrusted::Digest { expected, found }) =
Policy::embedded_only().decoder_read(record, Some(read))
else {
panic!("a module that was read wrong was accepted");
};
assert_eq!(expected, Digest::of(MODULE));
assert_ne!(found, expected);
}
#[test]
fn an_embedded_record_with_nothing_read_says_the_section_is_lost() {
let bytes = embedded();
let container = Container::parse(&bytes).expect("the container parses");
let record = container.decoder().expect("the container names a decoder");
assert_eq!(
Policy::embedded_only()
.decoder_read(record, None)
.unwrap_err(),
Untrusted::Lost { section: 1 }
);
}
#[test]
fn a_resolver_that_finds_nothing_is_not_an_attack() {
let bytes = external();
let container = Container::parse(&bytes).expect("the container parses");
let policy = Policy::with_external_decoders_resolved_by(Holding(None));
assert_eq!(
policy.decoder(&container).unwrap_err(),
Untrusted::Unresolved {
name: "elsewhere".to_owned(),
digest: Digest::of(MODULE),
}
);
}
}