1use core::{
4 cell::UnsafeCell,
5 marker::PhantomData,
6 sync::atomic::{AtomicUsize, Ordering},
7};
8
9use crate::runtime::{CtGatePosture, WipePosture};
10
11use super::provider::ProtectedMemoryProvider;
12
13#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
15pub struct AssuranceGenerations {
16 pub ordinary_backend: usize,
18 pub secret_algorithm: usize,
20 pub wipe_barrier: usize,
22 pub speculation: usize,
24}
25
26#[derive(Debug)]
28pub struct BestEffort {
29 _private: (),
30}
31
32#[derive(Debug)]
34pub struct Attested {
35 _private: (),
36}
37
38mod sealed {
39 pub trait Level {
40 const ATTESTED: bool;
41 }
42}
43
44impl sealed::Level for BestEffort {
45 const ATTESTED: bool = false;
46}
47
48impl sealed::Level for Attested {
49 const ATTESTED: bool = true;
50}
51
52pub trait AssuranceLevel: sealed::Level {}
54
55impl AssuranceLevel for BestEffort {}
56impl AssuranceLevel for Attested {}
57
58#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
60#[non_exhaustive]
61pub enum TargetAttestation {
62 X86,
64 X86_64,
66 Aarch64Csdb,
68 ReviewedEmbedded,
70}
71
72impl TargetAttestation {
73 const fn matches_current_target(self) -> bool {
74 match self {
75 Self::X86 => cfg!(target_arch = "x86"),
76 Self::X86_64 => cfg!(target_arch = "x86_64"),
77 Self::Aarch64Csdb => {
78 cfg!(all(
79 target_arch = "aarch64",
80 base64_ng_aarch64_csdb_attested
81 ))
82 }
83 Self::ReviewedEmbedded => true,
84 }
85 }
86}
87
88#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
90#[non_exhaustive]
91pub enum WipeAttestation {
92 VolatileBytesAndSelectedBarrier,
94}
95
96#[derive(Clone, Copy, Debug, Eq, PartialEq)]
98pub struct AttestationEvidence {
99 target: TargetAttestation,
100 wipe: WipeAttestation,
101 wipe_posture: WipePosture,
102 speculation_posture: CtGatePosture,
103 provider_identity: usize,
104 provider_generation: usize,
105}
106
107impl AttestationEvidence {
108 #[must_use]
117 #[allow(unsafe_code)]
118 pub const unsafe fn new(
119 target: TargetAttestation,
120 wipe: WipeAttestation,
121 wipe_posture: WipePosture,
122 speculation_posture: CtGatePosture,
123 provider_identity: usize,
124 provider_generation: usize,
125 ) -> Self {
126 Self {
127 target,
128 wipe,
129 wipe_posture,
130 speculation_posture,
131 provider_identity,
132 provider_generation,
133 }
134 }
135
136 pub(crate) const fn provider_identity(self) -> usize {
137 self.provider_identity
138 }
139
140 pub(crate) const fn provider_generation(self) -> usize {
141 self.provider_generation
142 }
143
144 pub(crate) const fn wipe_posture(self) -> WipePosture {
145 self.wipe_posture
146 }
147
148 pub(crate) const fn speculation_posture(self) -> CtGatePosture {
149 self.speculation_posture
150 }
151}
152
153#[allow(unsafe_code)]
161pub unsafe trait PlatformAttestation {
162 fn attest(&self) -> Result<AttestationEvidence, AssuranceError>;
164}
165
166#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
168#[non_exhaustive]
169pub enum AssuranceError {
170 StaleGeneration,
172 HighAssuranceBuildRequired,
174 MismatchedAttestation,
176 InsufficientPosture,
178 ProviderUnavailable,
180}
181
182impl core::fmt::Display for AssuranceError {
183 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
184 formatter.write_str(match self {
185 Self::StaleGeneration => "assurance generation is stale",
186 Self::HighAssuranceBuildRequired => "high-assurance build policy is required",
187 Self::MismatchedAttestation => "platform attestation does not match",
188 Self::InsufficientPosture => "platform posture is insufficient",
189 Self::ProviderUnavailable => "protected-memory provider is unavailable",
190 })
191 }
192}
193
194#[cfg(feature = "std")]
195impl std::error::Error for AssuranceError {}
196
197pub struct AssuranceContext {
203 ordinary_backend: AtomicUsize,
204 secret_algorithm: AtomicUsize,
205 wipe_barrier: AtomicUsize,
206 speculation: AtomicUsize,
207}
208
209impl AssuranceContext {
210 #[must_use]
212 pub const fn new() -> Self {
213 Self {
214 ordinary_backend: AtomicUsize::new(1),
215 secret_algorithm: AtomicUsize::new(1),
216 wipe_barrier: AtomicUsize::new(crate::cleanup::WIPE_PRIMITIVE_REVISION),
217 speculation: AtomicUsize::new(1),
218 }
219 }
220
221 #[must_use]
223 pub fn generations(&self) -> AssuranceGenerations {
224 AssuranceGenerations {
225 ordinary_backend: self.ordinary_backend.load(Ordering::Acquire),
226 secret_algorithm: self.secret_algorithm.load(Ordering::Acquire),
227 wipe_barrier: self.wipe_barrier.load(Ordering::Acquire),
228 speculation: self.speculation.load(Ordering::Acquire),
229 }
230 }
231
232 #[must_use]
234 pub fn best_effort_token(&self) -> AssuranceToken<'_, BestEffort> {
235 AssuranceToken::new(self, self.generations(), None)
236 }
237
238 pub fn attested_token<P>(
240 &self,
241 provider: &P,
242 ) -> Result<AssuranceToken<'_, Attested>, AssuranceError>
243 where
244 P: PlatformAttestation + ProtectedMemoryProvider,
245 {
246 if !cfg!(base64_ng_require_high_assurance) {
247 return Err(AssuranceError::HighAssuranceBuildRequired);
248 }
249 let evidence = provider.attest()?;
250 if !evidence.target.matches_current_target()
251 || evidence.provider_identity != provider.provider_identity()
252 || evidence.provider_generation != provider.provider_generation()
253 {
254 return Err(AssuranceError::MismatchedAttestation);
255 }
256 if evidence.wipe != WipeAttestation::VolatileBytesAndSelectedBarrier
257 || !wipe_posture_is_attestable(evidence.wipe_posture)
258 || !speculation_posture_is_attestable(evidence.speculation_posture)
259 {
260 return Err(AssuranceError::InsufficientPosture);
261 }
262 let generations = self.generations();
263 Ok(AssuranceToken::new(self, generations, Some(evidence)))
264 }
265
266 pub fn invalidate_ordinary_backend(&self) {
268 advance(&self.ordinary_backend);
269 }
270
271 pub fn invalidate_secret_algorithm(&self) {
273 advance(&self.secret_algorithm);
274 }
275
276 pub fn invalidate_wipe_barrier(&self) {
278 advance(&self.wipe_barrier);
279 }
280
281 pub fn invalidate_speculation(&self) {
283 advance(&self.speculation);
284 }
285}
286
287impl Default for AssuranceContext {
288 fn default() -> Self {
289 Self::new()
290 }
291}
292
293pub struct AssuranceToken<'context, Level: AssuranceLevel> {
299 context: &'context AssuranceContext,
300 generations: AssuranceGenerations,
301 evidence: Option<AttestationEvidence>,
302 _level: PhantomData<Level>,
303 _not_sync_or_unwind_safe: PhantomData<(UnsafeCell<()>, &'context mut dyn FnMut())>,
304}
305
306impl<'context, Level: AssuranceLevel> AssuranceToken<'context, Level> {
307 fn new(
308 context: &'context AssuranceContext,
309 generations: AssuranceGenerations,
310 evidence: Option<AttestationEvidence>,
311 ) -> Self {
312 Self {
313 context,
314 generations,
315 evidence,
316 _level: PhantomData,
317 _not_sync_or_unwind_safe: PhantomData,
318 }
319 }
320
321 #[must_use]
323 pub const fn generations(&self) -> AssuranceGenerations {
324 self.generations
325 }
326
327 pub fn revalidate(&self) -> Result<(), AssuranceError> {
329 let current = self.context.generations();
330 if current.secret_algorithm == 0
331 || current.wipe_barrier == 0
332 || (Level::ATTESTED && current.speculation == 0)
333 || current.secret_algorithm != self.generations.secret_algorithm
334 || current.wipe_barrier != self.generations.wipe_barrier
335 || (Level::ATTESTED && current.speculation != self.generations.speculation)
336 {
337 return Err(AssuranceError::StaleGeneration);
338 }
339 Ok(())
340 }
341
342 pub(crate) const fn context(&self) -> &'context AssuranceContext {
343 self.context
344 }
345
346 pub(crate) const fn evidence(&self) -> Option<AttestationEvidence> {
347 self.evidence
348 }
349
350 pub(crate) const fn requires_attestation() -> bool {
351 Level::ATTESTED
352 }
353}
354
355impl AssuranceContext {
356 pub(crate) fn revalidate_snapshot<Level: AssuranceLevel>(
357 &self,
358 generations: AssuranceGenerations,
359 ) -> Result<(), AssuranceError> {
360 let current = self.generations();
361 if current.secret_algorithm == 0
362 || current.wipe_barrier == 0
363 || (Level::ATTESTED && current.speculation == 0)
364 || current.secret_algorithm != generations.secret_algorithm
365 || current.wipe_barrier != generations.wipe_barrier
366 || (Level::ATTESTED && current.speculation != generations.speculation)
367 {
368 Err(AssuranceError::StaleGeneration)
369 } else {
370 Ok(())
371 }
372 }
373
374 pub(crate) fn revalidate_wipe_snapshot<Level: AssuranceLevel>(
375 &self,
376 generations: AssuranceGenerations,
377 ) -> Result<(), AssuranceError> {
378 let current = self.generations();
379 if current.wipe_barrier == 0
380 || (Level::ATTESTED && current.speculation == 0)
381 || current.wipe_barrier != generations.wipe_barrier
382 || (Level::ATTESTED && current.speculation != generations.speculation)
383 {
384 Err(AssuranceError::StaleGeneration)
385 } else {
386 Ok(())
387 }
388 }
389}
390
391impl<Level: AssuranceLevel> core::fmt::Debug for AssuranceToken<'_, Level> {
392 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
393 formatter
394 .debug_struct("AssuranceToken")
395 .field(
396 "level",
397 &if Level::ATTESTED {
398 "attested"
399 } else {
400 "best-effort"
401 },
402 )
403 .field("generations", &self.generations)
404 .finish_non_exhaustive()
405 }
406}
407
408#[allow(deprecated)]
411fn advance(generation: &AtomicUsize) {
412 let _ = generation.fetch_update(Ordering::AcqRel, Ordering::Acquire, |value| {
413 Some(if value == 0 {
414 0
415 } else {
416 value.checked_add(1).unwrap_or(0)
417 })
418 });
419}
420
421const fn wipe_posture_is_attestable(posture: WipePosture) -> bool {
422 matches!(posture, WipePosture::HardwareFence)
423}
424
425const fn speculation_posture_is_attestable(posture: CtGatePosture) -> bool {
426 matches!(
427 posture,
428 CtGatePosture::HardwareSpeculationBarrier
429 | CtGatePosture::HardwareSpeculationBarrierBuildAsserted
430 )
431}