pic_continuity/artifacts/
continuity.rs1use super::{artifact_sha256, check_profile};
23use crate::cose::CoseSigned;
24use crate::error::RejectReason;
25use serde::{Deserialize, Serialize};
26
27#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
30pub struct ContinuityRoot {
31 #[serde(with = "serde_bytes")]
34 pub pca_hash: Vec<u8>,
35 #[serde(with = "serde_bytes")]
37 pub pca: Vec<u8>,
38}
39
40#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
46pub struct PicContinuityPayload {
47 pub profile: String,
49 pub root: ContinuityRoot,
51 pub transitions: Option<Vec<serde_bytes::ByteBuf>>,
54}
55
56impl PicContinuityPayload {
57 fn root_for(exact_pca_bytes: Vec<u8>) -> ContinuityRoot {
58 ContinuityRoot {
59 pca_hash: artifact_sha256(&exact_pca_bytes),
60 pca: exact_pca_bytes,
61 }
62 }
63
64 pub fn settled(exact_pca_bytes: Vec<u8>) -> Self {
66 Self {
67 profile: crate::PROFILE_0_2.to_string(),
68 root: Self::root_for(exact_pca_bytes),
69 transitions: None,
70 }
71 }
72
73 pub fn candidate(exact_pca_bytes: Vec<u8>, exact_transition_bytes: Vec<u8>) -> Self {
75 Self {
76 profile: crate::PROFILE_0_2.to_string(),
77 root: Self::root_for(exact_pca_bytes),
78 transitions: Some(vec![serde_bytes::ByteBuf::from(exact_transition_bytes)]),
79 }
80 }
81
82 pub fn is_settled(&self) -> bool {
84 self.transitions.is_none()
85 }
86
87 pub fn require_settled(&self) -> Result<(), RejectReason> {
89 if self.is_settled() {
90 Ok(())
91 } else {
92 Err(RejectReason::SettledCarriesTransitions)
93 }
94 }
95
96 pub fn candidate_transition(&self) -> Result<&[u8], RejectReason> {
98 match &self.transitions {
99 Some(list) if list.len() == 1 => Ok(list[0].as_ref()),
100 Some(list) => Err(RejectReason::TransitionCount(list.len())),
101 None => Err(RejectReason::TransitionCount(0)),
102 }
103 }
104
105 pub fn check_root_hash(&self) -> Result<(), RejectReason> {
108 if artifact_sha256(&self.root.pca) == self.root.pca_hash {
109 Ok(())
110 } else {
111 Err(RejectReason::PcaHashMismatch)
112 }
113 }
114
115 pub fn check_profile(&self) -> Result<(), RejectReason> {
117 check_profile("pic-continuity+cose", &self.profile)
118 }
119}
120
121pub type PicContinuityCose = CoseSigned<PicContinuityPayload>;
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127
128 #[test]
129 fn continuity_settled_vs_candidate() {
130 let pca_bytes = b"exact-signed-pca-bytes".to_vec();
131
132 let settled = PicContinuityPayload::settled(pca_bytes.clone());
133 assert!(settled.is_settled());
134 assert!(settled.require_settled().is_ok());
135 assert!(settled.check_root_hash().is_ok());
136 assert_eq!(
137 settled.candidate_transition().unwrap_err(),
138 RejectReason::TransitionCount(0)
139 );
140
141 let candidate =
142 PicContinuityPayload::candidate(pca_bytes.clone(), b"transition-bytes".to_vec());
143 assert!(!candidate.is_settled());
144 assert_eq!(
145 candidate.require_settled().unwrap_err(),
146 RejectReason::SettledCarriesTransitions
147 );
148 assert_eq!(
149 candidate.candidate_transition().unwrap(),
150 b"transition-bytes"
151 );
152
153 let mut two = candidate.clone();
156 two.transitions = Some(vec![
157 serde_bytes::ByteBuf::from(b"t1".to_vec()),
158 serde_bytes::ByteBuf::from(b"t2".to_vec()),
159 ]);
160 assert_eq!(
161 two.candidate_transition().unwrap_err(),
162 RejectReason::TransitionCount(2)
163 );
164 }
165
166 #[test]
167 fn root_hash_recomputed_not_trusted() {
168 let mut settled = PicContinuityPayload::settled(b"pca".to_vec());
169 settled.root.pca_hash[0] ^= 0xFF;
170 assert_eq!(
171 settled.check_root_hash().unwrap_err(),
172 RejectReason::PcaHashMismatch
173 );
174 }
175
176 #[test]
177 fn transitions_null_is_explicit_on_the_wire() {
178 let settled = PicContinuityPayload::settled(b"pca".to_vec());
179 let mut buf = Vec::new();
180 ciborium::into_writer(&settled, &mut buf).unwrap();
181 let decoded: PicContinuityPayload = ciborium::from_reader(buf.as_slice()).unwrap();
182 assert!(decoded.is_settled());
183 }
184}