1use core::{cell::UnsafeCell, marker::PhantomData};
4
5use super::{
6 AccountingPosture, AllocationPresence, AssuranceContext, AssuranceGenerations, AssuranceLevel,
7 AssuranceToken, AttestationEvidence, CleanupError, CleanupOutcome, CleanupReport,
8 DisposalResult, LifecyclePosture, PendingStage, PhysicalProtection, ProtectedMemoryProvider,
9 ProtectedOperationReport, ProtectionError, ProtectionRequest, ProviderAccess, ProviderHealth,
10 ProviderOperationResult, QuarantineRecord, SecretOperation, TeardownCursor, TeardownOperation,
11 ThreadMovableProvider, WipeEvidence,
12};
13
14pub struct Uninitialized {
16 _private: (),
17}
18
19pub struct Unvalidated {
21 _private: (),
22}
23
24pub struct Validated {
26 _private: (),
27}
28
29pub struct ProtectedSecret<'provider, P, State, Level>
35where
36 P: ProtectedMemoryProvider,
37 Level: AssuranceLevel,
38{
39 provider: &'provider P,
40 handle: Option<P::Handle>,
41 context: &'provider AssuranceContext,
42 generations: AssuranceGenerations,
43 attestation: Option<AttestationEvidence>,
44 provider_generation: usize,
45 health_generation: usize,
46 protection_generation: usize,
47 initialized_len: usize,
48 operation: SecretOperation,
49 _state: PhantomData<State>,
50 _level: PhantomData<Level>,
51 _not_sync_or_unwind_safe: PhantomData<(UnsafeCell<()>, &'provider mut dyn FnMut())>,
52}
53
54impl<'provider, P, Level> ProtectedSecret<'provider, P, Uninitialized, Level>
55where
56 P: ProtectedMemoryProvider,
57 Level: AssuranceLevel,
58{
59 pub fn try_new(
61 provider: &'provider P,
62 token: &AssuranceToken<'provider, Level>,
63 logical_bytes: usize,
64 ) -> Result<Self, ProtectionError> {
65 token
66 .revalidate()
67 .map_err(|_| ProtectionError::StaleAssurance)?;
68 if provider.health() != ProviderHealth::Healthy {
69 return Err(ProtectionError::ProviderUnavailable);
70 }
71 if let Some(evidence) = token.evidence()
72 && (evidence.provider_identity() != provider.provider_identity()
73 || evidence.provider_generation() != provider.provider_generation())
74 {
75 return Err(ProtectionError::StaleAssurance);
76 }
77 let request = ProtectionRequest::new(
78 logical_bytes,
79 provider.limits().page_size,
80 AssuranceToken::<Level>::requires_attestation(),
81 )?;
82 let access = ProviderAccess::new();
83 let reservation = provider.reserve(&access, request)?;
84 let handle = provider.materialize(&access, reservation)?;
85 if provider.logical_len(&access, &handle) != logical_bytes {
86 let mut owner = Self::from_handle(provider, token, handle);
87 let _ = owner.close_inner();
88 return Err(ProtectionError::ActualRangeExceededReservation);
89 }
90 if AssuranceToken::<Level>::requires_attestation()
91 && provider.physical_protection(&access, &handle)
92 != PhysicalProtection::ProtectionAttested
93 {
94 let mut owner = Self::from_handle(provider, token, handle);
95 let _ = owner.close_inner();
96 return Err(ProtectionError::ProtectionUnavailable);
97 }
98 Ok(Self::from_handle(provider, token, handle))
99 }
100
101 fn from_handle(
102 provider: &'provider P,
103 token: &AssuranceToken<'provider, Level>,
104 handle: P::Handle,
105 ) -> Self {
106 Self {
107 provider,
108 handle: Some(handle),
109 context: token.context(),
110 generations: token.generations(),
111 attestation: token.evidence(),
112 provider_generation: provider.provider_generation(),
113 health_generation: provider.health_generation(),
114 protection_generation: provider.protection_generation(),
115 initialized_len: 0,
116 operation: SecretOperation::NotStarted,
117 _state: PhantomData,
118 _level: PhantomData,
119 _not_sync_or_unwind_safe: PhantomData,
120 }
121 }
122
123 pub(crate) fn begin_unvalidated(
124 mut self,
125 token: &AssuranceToken<'provider, Level>,
126 operation: SecretOperation,
127 ) -> Result<ProtectedSecret<'provider, P, Unvalidated, Level>, (ProtectionError, Self)> {
128 if let Err(error) = self.revalidate(token) {
129 return Err((error, self));
130 }
131 self.operation = operation;
132 match self.transition() {
133 Ok(next) => Ok(next),
134 Err(error) => Err((error, self)),
135 }
136 }
137}
138
139impl<'provider, P, Level> ProtectedSecret<'provider, P, Unvalidated, Level>
140where
141 P: ProtectedMemoryProvider,
142 Level: AssuranceLevel,
143{
144 pub(crate) fn bytes_mut(&mut self) -> Result<&mut [u8], ProtectionError> {
145 let handle = self
146 .handle
147 .as_mut()
148 .ok_or(ProtectionError::ProviderUnavailable)?;
149 Ok(self.provider.bytes_mut(&ProviderAccess::new(), handle))
150 }
151
152 pub(crate) fn set_initialized_len(&mut self, len: usize) -> Result<(), ProtectionError> {
153 if len > self.capacity() {
154 return Err(ProtectionError::ActualRangeExceededReservation);
155 }
156 self.initialized_len = len;
157 Ok(())
158 }
159
160 pub(crate) fn validate(
161 mut self,
162 token: &AssuranceToken<'provider, Level>,
163 ) -> Result<ProtectedSecret<'provider, P, Validated, Level>, (ProtectionError, Self)> {
164 if let Err(error) = self.revalidate(token) {
165 return Err((error, self));
166 }
167 match self.transition() {
168 Ok(next) => Ok(next),
169 Err(error) => Err((error, self)),
170 }
171 }
172}
173
174impl<P, Level> ProtectedSecret<'_, P, Validated, Level>
175where
176 P: ProtectedMemoryProvider,
177 Level: AssuranceLevel,
178{
179 #[must_use]
181 pub fn expose_secret(&self) -> ExposedProtectedSecret<'_> {
182 let bytes = self.handle.as_ref().map_or(&[][..], |handle| {
183 self.provider.bytes(&ProviderAccess::new(), handle)
184 });
185 ExposedProtectedSecret {
186 bytes: &bytes[..self.initialized_len],
187 }
188 }
189}
190
191impl<'provider, P, State, Level> ProtectedSecret<'provider, P, State, Level>
192where
193 P: ProtectedMemoryProvider,
194 Level: AssuranceLevel,
195{
196 #[must_use]
198 pub fn capacity(&self) -> usize {
199 self.handle.as_ref().map_or(0, |handle| {
200 self.provider.logical_len(&ProviderAccess::new(), handle)
201 })
202 }
203
204 #[must_use]
206 pub const fn initialized_len(&self) -> usize {
207 self.initialized_len
208 }
209
210 pub fn operation_report(
213 &self,
214 token: &AssuranceToken<'provider, Level>,
215 ) -> Result<ProtectedOperationReport, ProtectionError> {
216 self.revalidate(token)?;
217 let physical_protection =
218 self.handle
219 .as_ref()
220 .map_or(PhysicalProtection::ProtectionUnknown, |handle| {
221 self.provider
222 .physical_protection(&ProviderAccess::new(), handle)
223 });
224 Ok(ProtectedOperationReport::live(
225 token.report(),
226 self.operation,
227 physical_protection,
228 self.provider.health(),
229 self.provider.health_generation(),
230 self.provider.protection_generation(),
231 ))
232 }
233
234 pub fn try_close(mut self) -> Result<CleanupReport, CleanupError> {
236 self.close_inner()
237 }
238
239 fn transition<Next>(
240 &mut self,
241 ) -> Result<ProtectedSecret<'provider, P, Next, Level>, ProtectionError> {
242 let handle = self
243 .handle
244 .take()
245 .ok_or(ProtectionError::ProviderUnavailable)?;
246 Ok(ProtectedSecret {
247 provider: self.provider,
248 handle: Some(handle),
249 context: self.context,
250 generations: self.generations,
251 attestation: self.attestation,
252 provider_generation: self.provider_generation,
253 health_generation: self.health_generation,
254 protection_generation: self.protection_generation,
255 initialized_len: self.initialized_len,
256 operation: self.operation,
257 _state: PhantomData,
258 _level: PhantomData,
259 _not_sync_or_unwind_safe: PhantomData,
260 })
261 }
262
263 fn revalidate(&self, token: &AssuranceToken<'provider, Level>) -> Result<(), ProtectionError> {
264 token
265 .revalidate()
266 .and_then(|()| self.context.revalidate_snapshot::<Level>(self.generations))
267 .map_err(|_| ProtectionError::StaleAssurance)?;
268 if !core::ptr::eq(token.context(), self.context)
269 || token.generations() != self.generations
270 || self.provider.provider_generation() != self.provider_generation
271 || self.provider.health_generation() != self.health_generation
272 || self.provider.protection_generation() != self.protection_generation
273 || self.provider.health() != ProviderHealth::Healthy
274 {
275 return Err(ProtectionError::StaleAssurance);
276 }
277 Ok(())
278 }
279
280 fn close_inner(&mut self) -> Result<CleanupReport, CleanupError> {
281 let Some(mut handle) = self.handle.take() else {
282 return Ok(closed_report(WipeEvidence::WipedBestEffort));
283 };
284 let mut cursor = TeardownCursor::new();
285 cursor.begin(TeardownOperation::Wipe);
286 let access = ProviderAccess::new();
287 crate::wipe_bytes(self.provider.bytes_mut(&access, &mut handle));
288 let context_current = self
289 .context
290 .revalidate_wipe_snapshot::<Level>(self.generations)
291 .is_ok();
292 let attestation_current = self.attestation.is_some_and(|evidence| {
293 evidence.provider_identity() == self.provider.provider_identity()
294 && evidence.provider_generation() == self.provider_generation
295 && self.provider.provider_generation() == self.provider_generation
296 && self.provider.health_generation() == self.health_generation
297 && self.provider.protection_generation() == self.protection_generation
298 && self.provider.health() == ProviderHealth::Healthy
299 });
300 let confirmation =
301 self.provider
302 .confirm_wipe(&access, &handle, self.attestation, &mut cursor);
303 cursor.disposition = confirmation.result.journal_disposition();
304 let required_attested = AssuranceToken::<Level>::requires_attestation();
305 let wipe_sufficient = confirmation.result == ProviderOperationResult::Applied
306 && context_current
307 && (!required_attested
308 || (confirmation.evidence == WipeEvidence::WipedAttested && attestation_current));
309 if !wipe_sufficient {
310 let wipe = if confirmation.result == ProviderOperationResult::Applied {
311 if required_attested && (!context_current || !attestation_current) {
312 WipeEvidence::WipedBestEffort
313 } else {
314 confirmation.evidence
315 }
316 } else {
317 WipeEvidence::WipeNotCompleted
318 };
319 return Err(self.quarantine(handle, PendingStage::Wipe, wipe, cursor));
320 }
321 let wipe = confirmation.evidence;
322
323 cursor.begin(TeardownOperation::ProtectionRemoval);
324 let removal = self
325 .provider
326 .remove_protection(&access, &mut handle, &mut cursor);
327 cursor.disposition = removal.journal_disposition();
328 if removal != ProviderOperationResult::Applied {
329 let physical = if removal == ProviderOperationResult::Indeterminate {
330 PhysicalProtection::ProtectionUnknown
331 } else {
332 self.provider.physical_protection(&access, &handle)
333 };
334 return Err(self.quarantine_with_physical(
335 handle,
336 PendingStage::ProtectionRemoval,
337 wipe,
338 physical,
339 cursor,
340 ));
341 }
342
343 cursor.begin(TeardownOperation::AccountingReconciliation);
344 let accounting = self
345 .provider
346 .reconcile_accounting(&access, &mut handle, &mut cursor);
347 cursor.disposition = accounting.journal_disposition();
348 if accounting != ProviderOperationResult::Applied {
349 return Err(self.quarantine_with_physical(
350 handle,
351 PendingStage::AccountingReconciliation,
352 wipe,
353 PhysicalProtection::ProtectionConfirmedAbsent,
354 cursor,
355 ));
356 }
357
358 cursor.begin(TeardownOperation::Disposal);
359 match self.provider.dispose(&access, handle, &mut cursor) {
360 DisposalResult::Applied => Ok(closed_report(wipe)),
361 DisposalResult::NotApplied(handle) => Err(self.quarantine_with_physical(
362 handle,
363 PendingStage::Disposal,
364 wipe,
365 PhysicalProtection::ProtectionConfirmedAbsent,
366 cursor,
367 )),
368 DisposalResult::AllocationPresenceUnknown => Err(CleanupError {
369 pending_stage: PendingStage::Disposal,
370 wipe,
371 physical_protection: PhysicalProtection::ProtectionUnknown,
372 accounting: AccountingPosture::Charged,
373 lifecycle: LifecyclePosture::Tombstoned {
374 last_stage: PendingStage::Disposal,
375 disposition: AllocationPresence::Unknown,
376 },
377 pending_substage: super::JournalDisposition::Indeterminate,
378 retry_attempt: 1,
379 provider_health: ProviderHealth::Shutdown,
380 }),
381 }
382 }
383
384 fn quarantine(
385 &self,
386 handle: P::Handle,
387 pending_stage: PendingStage,
388 wipe: WipeEvidence,
389 cursor: TeardownCursor,
390 ) -> CleanupError {
391 let physical = self
392 .provider
393 .physical_protection(&ProviderAccess::new(), &handle);
394 self.quarantine_with_physical(handle, pending_stage, wipe, physical, cursor)
395 }
396
397 fn quarantine_with_physical(
398 &self,
399 handle: P::Handle,
400 pending_stage: PendingStage,
401 wipe: WipeEvidence,
402 physical_protection: PhysicalProtection,
403 cursor: TeardownCursor,
404 ) -> CleanupError {
405 let record = QuarantineRecord {
406 pending_stage,
407 wipe,
408 physical_protection,
409 accounting: AccountingPosture::Charged,
410 cursor,
411 retry_attempt: 1,
412 };
413 self.provider
414 .quarantine(&ProviderAccess::new(), handle, record);
415 let health = self.provider.health();
416 CleanupError {
417 pending_stage,
418 wipe,
419 physical_protection,
420 accounting: AccountingPosture::Charged,
421 lifecycle: LifecyclePosture::Quarantined { pending_stage },
422 pending_substage: cursor.disposition,
423 retry_attempt: 1,
424 provider_health: health,
425 }
426 }
427}
428
429impl<P, State, Level> Drop for ProtectedSecret<'_, P, State, Level>
430where
431 P: ProtectedMemoryProvider,
432 Level: AssuranceLevel,
433{
434 fn drop(&mut self) {
435 let _ = self.close_inner();
436 }
437}
438
439impl<P, State, Level> core::fmt::Debug for ProtectedSecret<'_, P, State, Level>
440where
441 P: ProtectedMemoryProvider,
442 Level: AssuranceLevel,
443{
444 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
445 formatter
446 .debug_struct("ProtectedSecret")
447 .field("bytes", &"<redacted>")
448 .field("initialized_len", &self.initialized_len)
449 .field("capacity", &self.capacity())
450 .finish_non_exhaustive()
451 }
452}
453
454#[allow(unsafe_code)]
455unsafe impl<P, State, Level> Send for ProtectedSecret<'_, P, State, Level>
456where
457 P: ThreadMovableProvider + Sync,
458 P::Handle: Send,
459 State: Send,
460 Level: AssuranceLevel + Send,
461{
462}
463
464pub struct ExposedProtectedSecret<'a> {
466 bytes: &'a [u8],
467}
468
469impl ExposedProtectedSecret<'_> {
470 #[must_use]
472 pub const fn as_bytes(&self) -> &[u8] {
473 self.bytes
474 }
475}
476
477impl AsRef<[u8]> for ExposedProtectedSecret<'_> {
478 fn as_ref(&self) -> &[u8] {
479 self.bytes
480 }
481}
482
483impl core::fmt::Debug for ExposedProtectedSecret<'_> {
484 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
485 formatter.write_str("ExposedProtectedSecret(<redacted>)")
486 }
487}
488
489fn closed_report(wipe: WipeEvidence) -> CleanupReport {
490 CleanupReport {
491 outcome: CleanupOutcome::Closed,
492 wipe,
493 physical_protection: PhysicalProtection::ProtectionConfirmedAbsent,
494 accounting: AccountingPosture::Reconciled,
495 lifecycle: LifecyclePosture::Closed,
496 }
497}