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 claims.exp = checkpoint.expires_at;
163 claims.jti = checkpoint.lineage_id.clone();
164 let token = sign_token(&claims, workload)?;
165
166 Ok(CandidateArtifacts {
167 token,
168 continuity_bytes,
169 transition_bytes,
170 transition,
171 })
172}
173
174fn to_wire(attenuations: &Attenuations) -> Option<AttenuationsWire> {
175 if attenuations.is_empty() {
176 return None;
177 }
178 Some(AttenuationsWire {
179 identity_context: attenuations
180 .identity_context
181 .as_ref()
182 .map(|b| BitmapAttenuation {
183 remove_bitmap: b.bytes().to_vec(),
184 }),
185 invariants: attenuations.invariants.as_ref().map(|b| BitmapAttenuation {
186 remove_bitmap: b.bytes().to_vec(),
187 }),
188 execution_contract: if attenuations.execution_contract_additions.is_empty() {
189 None
190 } else {
191 Some(ContractAdditions {
192 additions: attenuations.execution_contract_additions.clone(),
193 })
194 },
195 })
196}