1use crate::artifacts::token::{PicTokenClaims, sign_token};
35use crate::artifacts::{
36 AttenuationsWire, BitmapAttenuation, ContractAdditions, PicContinuityPayload, PicPcaCose,
37 PicPcaPayload, PicTransitionCose, PicTransitionPayload, Predecessor, ProofOfRelationship,
38 TransitionChallenge, artifact_sha256,
39};
40use crate::authority::attenuation::{Attenuations, materialize};
41use crate::error::{ContinuityError, RejectReason};
42use crate::trust::{ArtifactSigner, ArtifactVerifier};
43
44#[derive(Debug, Clone, Default)]
46pub struct CandidateRequest {
47 pub attenuations: Attenuations,
49 pub next_challenge: Vec<u8>,
51 pub proof_of_relationship: Option<ProofOfRelationship>,
53 pub request_digest: Option<Vec<u8>>,
55 pub executor_evidence: Option<ciborium::Value>,
57 pub iss: Option<String>,
61 pub aud: Option<String>,
63 pub iat: Option<i64>,
65}
66
67#[derive(Debug, Clone)]
69pub struct CandidateArtifacts {
70 pub token: String,
73 pub continuity_bytes: Vec<u8>,
75 pub transition_bytes: Vec<u8>,
77 pub transition: PicTransitionPayload,
79}
80
81pub fn build_candidate(
89 current_pca_bytes: &[u8],
90 request: CandidateRequest,
91 workload: &dyn ArtifactSigner,
92 realm: Option<&dyn ArtifactVerifier>,
93) -> Result<CandidateArtifacts, ContinuityError> {
94 let pca_cose = PicPcaCose::from_bytes(current_pca_bytes)?;
96 let checkpoint: PicPcaPayload = match realm {
97 Some(v) => pca_cose.verify_with(|data, sig| {
98 if v.verify(data, sig) {
99 Ok(())
100 } else {
101 Err(crate::cose::CoseError::VerificationFailed)
102 }
103 })?,
104 None => pca_cose.payload_unverified()?,
105 };
106 checkpoint.validate()?;
107
108 if request.next_challenge.is_empty() {
109 return Err(RejectReason::NextChallengeInvalid.into());
110 }
111 let por = request
112 .proof_of_relationship
113 .ok_or_else(|| RejectReason::PorRejected("proof_of_relationship is required".into()))?;
114
115 materialize(&checkpoint.context_of_authority, &request.attenuations)?;
118
119 let attenuations_wire = to_wire(&request.attenuations);
122 let transition = PicTransitionPayload {
123 profile: crate::PROFILE_0_2.to_string(),
124 position: checkpoint.position + 1,
125 predecessor: Predecessor {
126 predecessor_type: crate::PREDECESSOR_TYPE_PCA.to_string(),
127 hash: artifact_sha256(current_pca_bytes),
128 },
129 challenge: TransitionChallenge {
130 previous_challenge: checkpoint.challenge.next_challenge.clone(),
131 next_challenge: request.next_challenge,
132 },
133 attenuations: attenuations_wire,
134 proof_of_relationship: por,
135 request_digest: request.request_digest,
136 executor_evidence: request.executor_evidence,
137 };
138
139 let transition_cose: PicTransitionCose = crate::cose::CoseSigned::sign_with(
141 &transition,
142 workload.kid(),
143 workload.cose_algorithm(),
144 |data| workload.sign(data),
145 )?;
146 let transition_bytes = transition_cose.to_bytes()?;
147
148 let continuity =
149 PicContinuityPayload::candidate(current_pca_bytes.to_vec(), transition_bytes.clone());
150 let continuity_cose: crate::artifacts::PicContinuityCose = crate::cose::CoseSigned::sign_with(
151 &continuity,
152 workload.kid(),
153 workload.cose_algorithm(),
154 |data| workload.sign(data),
155 )?;
156 let continuity_bytes = continuity_cose.to_bytes()?;
157
158 let mut claims = PicTokenClaims::for_continuity(&continuity_bytes);
159 claims.iss = request.iss;
160 claims.aud = request.aud;
161 claims.iat = request.iat;
162 let token = sign_token(&claims, workload)?;
163
164 Ok(CandidateArtifacts {
165 token,
166 continuity_bytes,
167 transition_bytes,
168 transition,
169 })
170}
171
172fn to_wire(attenuations: &Attenuations) -> Option<AttenuationsWire> {
173 if attenuations.is_empty() {
174 return None;
175 }
176 Some(AttenuationsWire {
177 identity_context: attenuations
178 .identity_context
179 .as_ref()
180 .map(|b| BitmapAttenuation {
181 remove_bitmap: b.bytes().to_vec(),
182 }),
183 invariants: attenuations.invariants.as_ref().map(|b| BitmapAttenuation {
184 remove_bitmap: b.bytes().to_vec(),
185 }),
186 execution_contract: if attenuations.execution_contract_additions.is_empty() {
187 None
188 } else {
189 Some(ContractAdditions {
190 additions: attenuations.execution_contract_additions.clone(),
191 })
192 },
193 })
194}