use pamoja_security::{DeviceIdentity, PublicIdentity};
use crate::cbor::{Reader, Writer};
use crate::error::{Refusal, Result};
use crate::manifest::{expect_key, read_key, seal, Envelope};
pub const DELEGATION_MAX: usize = 192;
const KEY_LEN: usize = 32;
const KEY_EPOCH: u64 = 1;
const KEY_RELEASE: u64 = 2;
const KEY_EXPIRES: u64 = 3;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Delegation {
pub epoch: u64,
pub release_key: [u8; KEY_LEN],
pub expires: u64,
}
impl Delegation {
pub fn encode(&self, buf: &mut [u8]) -> Result<usize> {
let mut writer = Writer::new(buf);
writer.map(3)?;
writer.uint(KEY_EPOCH)?;
writer.uint(self.epoch)?;
writer.uint(KEY_RELEASE)?;
writer.bytes(&self.release_key)?;
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()? != 3 {
return Err(Refusal::Malformed);
}
let epoch = read_key(&mut reader, KEY_EPOCH)?;
expect_key(&mut reader, KEY_RELEASE)?;
let release_key =
<[u8; KEY_LEN]>::try_from(reader.bytes()?).map_err(|_| Refusal::Malformed)?;
let expires = read_key(&mut reader, KEY_EXPIRES)?;
if reader.position() != bytes.len() {
return Err(Refusal::Malformed);
}
Ok(Self {
epoch,
release_key,
expires,
})
}
pub fn sign(&self, anchor: &DeviceIdentity, buf: &mut [u8]) -> Result<usize> {
let mut body = [0u8; DELEGATION_MAX];
let body_len = self.encode(&mut body)?;
seal(&body[..body_len], anchor, buf)
}
pub fn open(envelope: &[u8], anchor: &PublicIdentity) -> Result<Self> {
let body = Envelope::decode(envelope)?.verified_body(anchor)?;
Self::decode(body)
}
pub fn signer(&self) -> Result<PublicIdentity> {
PublicIdentity::from_bytes(&self.release_key).map_err(|_| Refusal::Signature)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn anchor() -> DeviceIdentity {
DeviceIdentity::from_seed(&[1u8; 32])
}
fn delegation(epoch: u64, seed: u8) -> Delegation {
Delegation {
epoch,
release_key: DeviceIdentity::from_seed(&[seed; 32]).public().to_bytes(),
expires: 0,
}
}
#[test]
fn a_delegation_round_trips() {
let original = delegation(3, 2);
let mut buf = [0u8; DELEGATION_MAX];
let written = original.encode(&mut buf).expect("encode");
assert_eq!(
Delegation::decode(&buf[..written]).expect("decode"),
original
);
}
#[test]
fn the_encoding_fits_the_documented_buffer_size() {
let mut buf = [0u8; DELEGATION_MAX];
assert!(delegation(u64::MAX, 2).sign(&anchor(), &mut buf).is_ok());
}
#[test]
fn a_delegation_opens_against_its_anchor() {
let original = delegation(1, 2);
let mut buf = [0u8; DELEGATION_MAX];
let written = original.sign(&anchor(), &mut buf).expect("sign");
assert_eq!(
Delegation::open(&buf[..written], &anchor().public()).expect("open"),
original
);
}
#[test]
fn a_delegation_from_anyone_else_is_refused() {
let impostor = DeviceIdentity::from_seed(&[9u8; 32]);
let mut buf = [0u8; DELEGATION_MAX];
let written = delegation(1, 2).sign(&impostor, &mut buf).expect("sign");
assert_eq!(
Delegation::open(&buf[..written], &anchor().public()),
Err(Refusal::Signature),
"only the trust anchor may hand on the right to sign updates"
);
}
#[test]
fn altering_a_delegation_breaks_its_signature() {
let mut buf = [0u8; DELEGATION_MAX];
let written = delegation(1, 2).sign(&anchor(), &mut buf).expect("sign");
buf[10] ^= 0x01;
assert_eq!(
Delegation::open(&buf[..written], &anchor().public()),
Err(Refusal::Signature)
);
}
#[test]
fn the_delegated_key_is_usable() {
let release = DeviceIdentity::from_seed(&[2u8; 32]);
let signer = delegation(1, 2).signer().expect("signer");
assert_eq!(signer.to_bytes(), release.public().to_bytes());
}
}