cloud_sdk/operation/permit/fingerprint/
evidence.rs1use cloud_sdk_sanitization::sanitize_bytes;
2
3use super::encoding::encode_with_authorization_evidence;
4use super::error::map_infallible;
5use super::{
6 DigestRollback, MAX_CANONICAL_PLAN_BYTES, PlanConfirmation, PlanFingerprintBuildError,
7 PlanFingerprintDigest, validate,
8};
9use crate::buffer::{SnapshotEncoder, encode_snapshot_bounded};
10use crate::operation::PermitTimestamp;
11use crate::retry::FingerprintHasher;
12
13const DOMAIN: &[u8] = b"cloud-sdk/authorization-evidence/v1\0";
14
15pub trait PlanAuthorizationEvidence {
21 fn valid_until(&self) -> Option<PermitTimestamp> {
26 None
27 }
28
29 fn encode<E: Copy>(
31 &self,
32 writer: &mut SnapshotEncoder<'_, PlanFingerprintBuildError<E>>,
33 ) -> Result<(), PlanFingerprintBuildError<E>>;
34}
35
36pub fn build_plan_digest_with_authorization_evidence<
41 'output,
42 'plan,
43 'request,
44 H: FingerprintHasher,
45 A: PlanAuthorizationEvidence + ?Sized,
46>(
47 plan: PlanConfirmation<'plan, 'request>,
48 evidence: &A,
49 scratch: &mut [u8],
50 output: &'output mut [u8],
51 hasher: &H,
52) -> Result<PlanFingerprintDigest<'output, 'plan, 'request>, PlanFingerprintBuildError<H::Error>> {
53 sanitize_bytes(output);
54 sanitize_bytes(scratch);
55 let mut scratch = SensitiveScratch::new(scratch);
56 let mut rollback = DigestRollback::new(output);
57 let scope = validate(&plan, true)?;
58 if evidence
59 .valid_until()
60 .is_some_and(|expires_at| plan.validity.expires_at() > expires_at)
61 {
62 return Err(PlanFingerprintBuildError::AuthorizationEvidenceValidityMismatch);
63 }
64 let len = encode_snapshot_bounded(
65 (plan, evidence),
66 scratch.as_mut(),
67 MAX_CANONICAL_PLAN_BYTES,
68 PlanFingerprintBuildError::InputTooLarge,
69 encode_with_evidence::<core::convert::Infallible, A>,
70 )
71 .map_err(map_infallible)?;
72 let algorithm = hasher.algorithm();
73 let expected = algorithm.output_len();
74 if rollback.len() < expected {
75 return Err(PlanFingerprintBuildError::OutputTooSmall);
76 }
77 let digest_len = hasher
78 .digest(scratch.bytes(len), rollback.target(expected))
79 .map_err(PlanFingerprintBuildError::Hasher)?;
80 if digest_len != expected {
81 return Err(PlanFingerprintBuildError::InvalidDigestLength);
82 }
83 let output = rollback.disarm();
84 Ok(PlanFingerprintDigest {
85 algorithm,
86 storage: output,
87 len: digest_len,
88 plan,
89 scope,
90 })
91}
92
93fn encode_with_evidence<E: Copy, A: PlanAuthorizationEvidence + ?Sized>(
94 (plan, evidence): (PlanConfirmation<'_, '_>, &A),
95 writer: &mut SnapshotEncoder<'_, PlanFingerprintBuildError<E>>,
96) -> Result<(), PlanFingerprintBuildError<E>> {
97 encode_with_authorization_evidence(&plan, writer)?;
98 writer.bytes(DOMAIN)?;
99 evidence.encode(writer)
100}
101
102struct SensitiveScratch<'a>(&'a mut [u8]);
103
104impl<'a> SensitiveScratch<'a> {
105 fn new(scratch: &'a mut [u8]) -> Self {
106 Self(scratch)
107 }
108
109 fn bytes(&self, len: usize) -> &[u8] {
110 self.0.get(..len).unwrap_or_default()
111 }
112
113 fn as_mut(&mut self) -> &mut [u8] {
114 self.0
115 }
116}
117
118impl Drop for SensitiveScratch<'_> {
119 fn drop(&mut self) {
120 sanitize_bytes(self.0);
121 }
122}