use super::{artifact_sha256, check_profile};
use crate::cose::CoseSigned;
use crate::error::RejectReason;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ContinuityRoot {
#[serde(with = "serde_bytes")]
pub pca_hash: Vec<u8>,
#[serde(with = "serde_bytes")]
pub pca: Vec<u8>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PicContinuityPayload {
pub profile: String,
pub root: ContinuityRoot,
pub transitions: Option<Vec<serde_bytes::ByteBuf>>,
}
impl PicContinuityPayload {
fn root_for(exact_pca_bytes: Vec<u8>) -> ContinuityRoot {
ContinuityRoot {
pca_hash: artifact_sha256(&exact_pca_bytes),
pca: exact_pca_bytes,
}
}
pub fn settled(exact_pca_bytes: Vec<u8>) -> Self {
Self {
profile: crate::PROFILE_0_2.to_string(),
root: Self::root_for(exact_pca_bytes),
transitions: None,
}
}
pub fn candidate(exact_pca_bytes: Vec<u8>, exact_transition_bytes: Vec<u8>) -> Self {
Self {
profile: crate::PROFILE_0_2.to_string(),
root: Self::root_for(exact_pca_bytes),
transitions: Some(vec![serde_bytes::ByteBuf::from(exact_transition_bytes)]),
}
}
pub fn is_settled(&self) -> bool {
self.transitions.is_none()
}
pub fn require_settled(&self) -> Result<(), RejectReason> {
if self.is_settled() {
Ok(())
} else {
Err(RejectReason::SettledCarriesTransitions)
}
}
pub fn candidate_transition(&self) -> Result<&[u8], RejectReason> {
match &self.transitions {
Some(list) if list.len() == 1 => Ok(list[0].as_ref()),
Some(list) => Err(RejectReason::TransitionCount(list.len())),
None => Err(RejectReason::TransitionCount(0)),
}
}
pub fn check_root_hash(&self) -> Result<(), RejectReason> {
if artifact_sha256(&self.root.pca) == self.root.pca_hash {
Ok(())
} else {
Err(RejectReason::PcaHashMismatch)
}
}
pub fn check_profile(&self) -> Result<(), RejectReason> {
check_profile("pic-continuity+cose", &self.profile)
}
}
pub type PicContinuityCose = CoseSigned<PicContinuityPayload>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn continuity_settled_vs_candidate() {
let pca_bytes = b"exact-signed-pca-bytes".to_vec();
let settled = PicContinuityPayload::settled(pca_bytes.clone());
assert!(settled.is_settled());
assert!(settled.require_settled().is_ok());
assert!(settled.check_root_hash().is_ok());
assert_eq!(
settled.candidate_transition().unwrap_err(),
RejectReason::TransitionCount(0)
);
let candidate =
PicContinuityPayload::candidate(pca_bytes.clone(), b"transition-bytes".to_vec());
assert!(!candidate.is_settled());
assert_eq!(
candidate.require_settled().unwrap_err(),
RejectReason::SettledCarriesTransitions
);
assert_eq!(
candidate.candidate_transition().unwrap(),
b"transition-bytes"
);
let mut two = candidate.clone();
two.transitions = Some(vec![
serde_bytes::ByteBuf::from(b"t1".to_vec()),
serde_bytes::ByteBuf::from(b"t2".to_vec()),
]);
assert_eq!(
two.candidate_transition().unwrap_err(),
RejectReason::TransitionCount(2)
);
}
#[test]
fn root_hash_recomputed_not_trusted() {
let mut settled = PicContinuityPayload::settled(b"pca".to_vec());
settled.root.pca_hash[0] ^= 0xFF;
assert_eq!(
settled.check_root_hash().unwrap_err(),
RejectReason::PcaHashMismatch
);
}
#[test]
fn transitions_null_is_explicit_on_the_wire() {
let settled = PicContinuityPayload::settled(b"pca".to_vec());
let mut buf = Vec::new();
ciborium::into_writer(&settled, &mut buf).unwrap();
let decoded: PicContinuityPayload = ciborium::from_reader(buf.as_slice()).unwrap();
assert!(decoded.is_settled());
}
}