use crate::artifacts::token::{PicTokenClaims, sign_token};
use crate::artifacts::{
AttenuationsWire, BitmapAttenuation, ContractAdditions, PicContinuityPayload, PicPcaCose,
PicPcaPayload, PicTransitionCose, PicTransitionPayload, Predecessor, ProofOfRelationship,
TransitionChallenge, artifact_sha256,
};
use crate::authority::attenuation::{Attenuations, materialize};
use crate::error::{ContinuityError, RejectReason};
use crate::trust::{ArtifactSigner, ArtifactVerifier};
#[derive(Debug, Clone, Default)]
pub struct CandidateRequest {
pub attenuations: Attenuations,
pub next_challenge: Vec<u8>,
pub proof_of_relationship: Option<ProofOfRelationship>,
pub request_digest: Option<Vec<u8>>,
pub executor_evidence: Option<ciborium::Value>,
pub iss: Option<String>,
pub aud: Option<String>,
pub iat: Option<i64>,
}
#[derive(Debug, Clone)]
pub struct CandidateArtifacts {
pub token: String,
pub continuity_bytes: Vec<u8>,
pub transition_bytes: Vec<u8>,
pub transition: PicTransitionPayload,
}
pub fn build_candidate(
current_pca_bytes: &[u8],
request: CandidateRequest,
workload: &dyn ArtifactSigner,
realm: Option<&dyn ArtifactVerifier>,
) -> Result<CandidateArtifacts, ContinuityError> {
let pca_cose = PicPcaCose::from_bytes(current_pca_bytes)?;
let checkpoint: PicPcaPayload = match realm {
Some(v) => pca_cose.verify_with(|data, sig| {
if v.verify(data, sig) {
Ok(())
} else {
Err(crate::cose::CoseError::VerificationFailed)
}
})?,
None => pca_cose.payload_unverified()?,
};
checkpoint.validate()?;
if request.next_challenge.is_empty() {
return Err(RejectReason::NextChallengeInvalid.into());
}
let por = request
.proof_of_relationship
.ok_or_else(|| RejectReason::PorRejected("proof_of_relationship is required".into()))?;
materialize(&checkpoint.context_of_authority, &request.attenuations)?;
let attenuations_wire = to_wire(&request.attenuations);
let transition = PicTransitionPayload {
profile: crate::PROFILE_0_2.to_string(),
position: checkpoint.position + 1,
predecessor: Predecessor {
predecessor_type: crate::PREDECESSOR_TYPE_PCA.to_string(),
hash: artifact_sha256(current_pca_bytes),
},
challenge: TransitionChallenge {
previous_challenge: checkpoint.challenge.next_challenge.clone(),
next_challenge: request.next_challenge,
},
attenuations: attenuations_wire,
proof_of_relationship: por,
request_digest: request.request_digest,
executor_evidence: request.executor_evidence,
};
let transition_cose: PicTransitionCose = crate::cose::CoseSigned::sign_with(
&transition,
workload.kid(),
workload.cose_algorithm(),
|data| workload.sign(data),
)?;
let transition_bytes = transition_cose.to_bytes()?;
let continuity =
PicContinuityPayload::candidate(current_pca_bytes.to_vec(), transition_bytes.clone());
let continuity_cose: crate::artifacts::PicContinuityCose = crate::cose::CoseSigned::sign_with(
&continuity,
workload.kid(),
workload.cose_algorithm(),
|data| workload.sign(data),
)?;
let continuity_bytes = continuity_cose.to_bytes()?;
let mut claims = PicTokenClaims::for_continuity(&continuity_bytes);
claims.iss = request.iss;
claims.aud = request.aud;
claims.iat = request.iat;
claims.exp = checkpoint.expires_at;
claims.jti = checkpoint.lineage_id.clone();
let token = sign_token(&claims, workload)?;
Ok(CandidateArtifacts {
token,
continuity_bytes,
transition_bytes,
transition,
})
}
fn to_wire(attenuations: &Attenuations) -> Option<AttenuationsWire> {
if attenuations.is_empty() {
return None;
}
Some(AttenuationsWire {
identity_context: attenuations
.identity_context
.as_ref()
.map(|b| BitmapAttenuation {
remove_bitmap: b.bytes().to_vec(),
}),
invariants: attenuations.invariants.as_ref().map(|b| BitmapAttenuation {
remove_bitmap: b.bytes().to_vec(),
}),
execution_contract: if attenuations.execution_contract_additions.is_empty() {
None
} else {
Some(ContractAdditions {
additions: attenuations.execution_contract_additions.clone(),
})
},
})
}