use pamoja_security::{DeviceIdentity, PublicIdentity, Signature};
use crate::cbor::{Reader, Writer};
use crate::error::{Refusal, Result};
pub const STRUCTURE_VERSION: u8 = 1;
pub const ID_LEN: usize = 16;
pub const DIGEST_LEN: usize = 32;
pub const MANIFEST_MAX: usize = 128;
pub const ENVELOPE_MAX: usize = 224;
const SIGNATURE_LEN: usize = 64;
const KEY_STRUCTURE_VERSION: u64 = 1;
const KEY_SEQUENCE: u64 = 2;
const KEY_VENDOR: u64 = 3;
const KEY_CLASS: u64 = 4;
const KEY_FORMAT: u64 = 5;
const KEY_STORAGE: u64 = 6;
const KEY_DIGEST: u64 = 7;
const KEY_SIZE: u64 = 8;
const KEY_EXPIRES: u64 = 9;
const KEY_BODY: u64 = 1;
const KEY_SIGNATURE: u64 = 2;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PayloadFormat {
Raw = 1,
}
impl PayloadFormat {
fn from_value(value: u64) -> Result<Self> {
match value {
1 => Ok(Self::Raw),
_ => Err(Refusal::UnsupportedVersion),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Manifest {
pub structure_version: u8,
pub sequence: u64,
pub vendor_id: [u8; ID_LEN],
pub class_id: [u8; ID_LEN],
pub format: PayloadFormat,
pub storage: u8,
pub digest: [u8; DIGEST_LEN],
pub size: u32,
pub expires: u64,
}
impl Manifest {
pub fn encode(&self, buf: &mut [u8]) -> Result<usize> {
let mut writer = Writer::new(buf);
writer.map(9)?;
writer.uint(KEY_STRUCTURE_VERSION)?;
writer.uint(u64::from(self.structure_version))?;
writer.uint(KEY_SEQUENCE)?;
writer.uint(self.sequence)?;
writer.uint(KEY_VENDOR)?;
writer.bytes(&self.vendor_id)?;
writer.uint(KEY_CLASS)?;
writer.bytes(&self.class_id)?;
writer.uint(KEY_FORMAT)?;
writer.uint(self.format as u64)?;
writer.uint(KEY_STORAGE)?;
writer.uint(u64::from(self.storage))?;
writer.uint(KEY_DIGEST)?;
writer.bytes(&self.digest)?;
writer.uint(KEY_SIZE)?;
writer.uint(u64::from(self.size))?;
writer.uint(KEY_EXPIRES)?;
writer.uint(self.expires)?;
Ok(writer.finish())
}
pub fn decode(bytes: &[u8]) -> Result<Self> {
let mut reader = Reader::new(bytes);
if reader.map()? != 9 {
return Err(Refusal::Malformed);
}
let structure_version = read_key(&mut reader, KEY_STRUCTURE_VERSION)?;
let structure_version = u8::try_from(structure_version).map_err(|_| Refusal::Malformed)?;
if structure_version != STRUCTURE_VERSION {
return Err(Refusal::UnsupportedVersion);
}
let sequence = read_key(&mut reader, KEY_SEQUENCE)?;
expect_key(&mut reader, KEY_VENDOR)?;
let vendor_id = read_id(&mut reader)?;
expect_key(&mut reader, KEY_CLASS)?;
let class_id = read_id(&mut reader)?;
let format = PayloadFormat::from_value(read_key(&mut reader, KEY_FORMAT)?)?;
let storage = read_key(&mut reader, KEY_STORAGE)?;
let storage = u8::try_from(storage).map_err(|_| Refusal::Malformed)?;
expect_key(&mut reader, KEY_DIGEST)?;
let digest_bytes = reader.bytes()?;
let digest = <[u8; DIGEST_LEN]>::try_from(digest_bytes).map_err(|_| Refusal::Malformed)?;
let size = read_key(&mut reader, KEY_SIZE)?;
let size = u32::try_from(size).map_err(|_| Refusal::Malformed)?;
let expires = read_key(&mut reader, KEY_EXPIRES)?;
if reader.position() != bytes.len() {
return Err(Refusal::Malformed);
}
Ok(Self {
structure_version,
sequence,
vendor_id,
class_id,
format,
storage,
digest,
size,
expires,
})
}
pub fn sign(&self, author: &DeviceIdentity, buf: &mut [u8]) -> Result<usize> {
let mut body = [0u8; MANIFEST_MAX];
let body_len = self.encode(&mut body)?;
seal(&body[..body_len], author, buf)
}
}
#[derive(Clone, Copy, Debug)]
pub struct Envelope<'a> {
body: &'a [u8],
signature: [u8; SIGNATURE_LEN],
}
impl<'a> Envelope<'a> {
pub fn decode(bytes: &'a [u8]) -> Result<Self> {
let mut reader = Reader::new(bytes);
if reader.map()? != 2 {
return Err(Refusal::Malformed);
}
expect_key(&mut reader, KEY_BODY)?;
let body = reader.bytes()?;
expect_key(&mut reader, KEY_SIGNATURE)?;
let signature =
<[u8; SIGNATURE_LEN]>::try_from(reader.bytes()?).map_err(|_| Refusal::Malformed)?;
if reader.position() != bytes.len() {
return Err(Refusal::Malformed);
}
Ok(Self { body, signature })
}
pub fn verify(&self, author: &PublicIdentity) -> Result<Manifest> {
Manifest::decode(self.verified_body(author)?)
}
pub fn verified_body(&self, signer: &PublicIdentity) -> Result<&'a [u8]> {
let signature = Signature::from_bytes(&self.signature);
signer
.verify(self.body, &signature)
.map_err(|_| Refusal::Signature)?;
Ok(self.body)
}
pub fn body(&self) -> &'a [u8] {
self.body
}
}
pub(crate) fn seal(body: &[u8], signer: &DeviceIdentity, buf: &mut [u8]) -> Result<usize> {
let signature = signer.sign(body);
let mut writer = Writer::new(buf);
writer.map(2)?;
writer.uint(KEY_BODY)?;
writer.bytes(body)?;
writer.uint(KEY_SIGNATURE)?;
writer.bytes(&signature.to_bytes())?;
Ok(writer.finish())
}
pub(crate) fn expect_key(reader: &mut Reader<'_>, key: u64) -> Result<()> {
if reader.uint()? != key {
return Err(Refusal::Malformed);
}
Ok(())
}
pub(crate) fn read_key(reader: &mut Reader<'_>, key: u64) -> Result<u64> {
expect_key(reader, key)?;
reader.uint()
}
fn read_id(reader: &mut Reader<'_>) -> Result<[u8; ID_LEN]> {
<[u8; ID_LEN]>::try_from(reader.bytes()?).map_err(|_| Refusal::Malformed)
}
#[cfg(test)]
mod tests {
use super::*;
fn sample() -> Manifest {
Manifest {
structure_version: STRUCTURE_VERSION,
sequence: 42,
vendor_id: [0xab; ID_LEN],
class_id: [0xcd; ID_LEN],
format: PayloadFormat::Raw,
storage: 1,
digest: [0x5a; DIGEST_LEN],
size: 65_536,
expires: 0,
}
}
#[test]
fn a_manifest_round_trips() {
let manifest = sample();
let mut buf = [0u8; MANIFEST_MAX];
let written = manifest.encode(&mut buf).expect("encode");
assert_eq!(Manifest::decode(&buf[..written]).expect("decode"), manifest);
}
#[test]
fn the_encoding_fits_the_documented_buffer_sizes() {
let mut body = [0u8; MANIFEST_MAX];
assert!(sample().encode(&mut body).expect("encode") <= MANIFEST_MAX);
let author = DeviceIdentity::from_seed(&[3u8; 32]);
let mut envelope = [0u8; ENVELOPE_MAX];
assert!(sample().sign(&author, &mut envelope).expect("sign") <= ENVELOPE_MAX);
}
#[test]
fn a_signed_envelope_verifies_against_its_author() {
let author = DeviceIdentity::from_seed(&[3u8; 32]);
let mut buf = [0u8; ENVELOPE_MAX];
let written = sample().sign(&author, &mut buf).expect("sign");
let envelope = Envelope::decode(&buf[..written]).expect("decode");
assert_eq!(envelope.verify(&author.public()).expect("verify"), sample());
}
#[test]
fn a_different_author_is_refused() {
let author = DeviceIdentity::from_seed(&[3u8; 32]);
let impostor = DeviceIdentity::from_seed(&[4u8; 32]);
let mut buf = [0u8; ENVELOPE_MAX];
let written = sample().sign(&author, &mut buf).expect("sign");
let envelope = Envelope::decode(&buf[..written]).expect("decode");
assert_eq!(
envelope.verify(&impostor.public()),
Err(Refusal::Signature),
"an envelope signed by someone else is not this device's update"
);
}
#[test]
fn altering_the_body_breaks_the_signature() {
let author = DeviceIdentity::from_seed(&[3u8; 32]);
let mut buf = [0u8; ENVELOPE_MAX];
let written = sample().sign(&author, &mut buf).expect("sign");
buf[6] ^= 0x01;
let envelope = Envelope::decode(&buf[..written]).expect("decode");
assert_eq!(envelope.verify(&author.public()), Err(Refusal::Signature));
}
#[test]
fn a_newer_structure_version_is_refused() {
let mut manifest = sample();
manifest.structure_version = STRUCTURE_VERSION + 1;
let mut buf = [0u8; MANIFEST_MAX];
let written = manifest.encode(&mut buf).expect("encode");
assert_eq!(
Manifest::decode(&buf[..written]),
Err(Refusal::UnsupportedVersion)
);
}
#[test]
fn an_unknown_payload_format_is_refused() {
let mut buf = [0u8; MANIFEST_MAX];
let written = sample().encode(&mut buf).expect("encode");
let at = buf[..written]
.windows(2)
.position(|pair| pair == [KEY_FORMAT as u8, PayloadFormat::Raw as u8])
.expect("the format pair");
buf[at + 1] = 9;
assert_eq!(
Manifest::decode(&buf[..written]),
Err(Refusal::UnsupportedVersion)
);
}
#[test]
fn a_reordered_map_is_refused() {
let mut buf = [0u8; MANIFEST_MAX];
let written = sample().encode(&mut buf).expect("encode");
buf[1] = KEY_SEQUENCE as u8;
assert_eq!(Manifest::decode(&buf[..written]), Err(Refusal::Malformed));
}
#[test]
fn trailing_bytes_are_refused() {
let mut buf = [0u8; MANIFEST_MAX];
let written = sample().encode(&mut buf).expect("encode");
assert_eq!(
Manifest::decode(&buf[..written + 1]),
Err(Refusal::Malformed)
);
}
#[test]
fn a_truncated_manifest_is_refused() {
let mut buf = [0u8; MANIFEST_MAX];
let written = sample().encode(&mut buf).expect("encode");
assert!(Manifest::decode(&buf[..written - 1]).is_err());
}
#[test]
fn a_buffer_too_small_to_hold_the_manifest_is_refused() {
let mut buf = [0u8; 8];
assert_eq!(sample().encode(&mut buf), Err(Refusal::Malformed));
}
}