Skip to main content

base64_ng/v2/assurance/
operations.rs

1//! Token- and allocation-gated assured one-shot operations.
2
3use super::{
4    AssuranceError, AssuranceLevel, AssuranceToken, CleanupError, ProtectedMemoryProvider,
5    ProtectedSecret, ProtectionError, SecretOperation, Uninitialized, Validated,
6};
7use crate::v2::{
8    Base64, Codec,
9    secret::{SecretDecodeError, SecretEncodeError, SecretInput},
10    secret_decoder::SecretDecoderState,
11    secret_encoder::SecretEncoderState,
12    specifications::CodecSettings,
13};
14
15/// Redacted assured decode failure.
16#[derive(Clone, Copy, Debug, Eq, PartialEq)]
17#[non_exhaustive]
18pub enum AssuredDecodeError {
19    /// Token or context evidence is stale.
20    Assurance(AssuranceError),
21    /// Allocation-specific protection is unavailable or stale.
22    Protection(ProtectionError),
23    /// Secret decoder rejected the input without localized diagnostics.
24    Decode(SecretDecodeError),
25    /// Cleanup transferred the consumed allocation to quarantine or tombstone.
26    Cleanup(CleanupError),
27}
28
29/// Redacted assured encode failure.
30#[derive(Clone, Copy, Debug, Eq, PartialEq)]
31#[non_exhaustive]
32pub enum AssuredEncodeError {
33    /// Token or context evidence is stale.
34    Assurance(AssuranceError),
35    /// Allocation-specific protection is unavailable or stale.
36    Protection(ProtectionError),
37    /// Secret encoder rejected a public bound or internal state.
38    Encode(SecretEncodeError),
39    /// Cleanup transferred the consumed allocation to quarantine or tombstone.
40    Cleanup(CleanupError),
41}
42
43impl core::fmt::Display for AssuredDecodeError {
44    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
45        match self {
46            Self::Assurance(error) => error.fmt(formatter),
47            Self::Protection(error) => error.fmt(formatter),
48            Self::Decode(error) => error.fmt(formatter),
49            Self::Cleanup(error) => error.fmt(formatter),
50        }
51    }
52}
53
54impl core::fmt::Display for AssuredEncodeError {
55    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
56        match self {
57            Self::Assurance(error) => error.fmt(formatter),
58            Self::Protection(error) => error.fmt(formatter),
59            Self::Encode(error) => error.fmt(formatter),
60            Self::Cleanup(error) => error.fmt(formatter),
61        }
62    }
63}
64
65#[cfg(feature = "std")]
66impl std::error::Error for AssuredDecodeError {}
67
68#[cfg(feature = "std")]
69impl std::error::Error for AssuredEncodeError {}
70
71impl<S: Codec> Base64<S> {
72    /// Decodes directly through one protected allocation's typestates.
73    ///
74    /// The allocation is consumed on every path. Safe code cannot observe its
75    /// bytes until the secret result gate succeeds and returns `Validated`.
76    pub fn decode_assured<'provider, P, Level>(
77        &self,
78        token: &AssuranceToken<'provider, Level>,
79        allocation: ProtectedSecret<'provider, P, Uninitialized, Level>,
80        input: &SecretInput<'_>,
81    ) -> Result<ProtectedSecret<'provider, P, Validated, Level>, AssuredDecodeError>
82    where
83        P: ProtectedMemoryProvider,
84        Level: AssuranceLevel,
85    {
86        token.revalidate().map_err(AssuredDecodeError::Assurance)?;
87        let mut allocation = match allocation.begin_unvalidated(token, SecretOperation::Decode) {
88            Ok(allocation) => allocation,
89            Err((error, allocation)) => {
90                return Err(match allocation.try_close() {
91                    Ok(_) => AssuredDecodeError::Protection(error),
92                    Err(cleanup) => AssuredDecodeError::Cleanup(cleanup),
93                });
94            }
95        };
96        let output = match allocation.bytes_mut() {
97            Ok(output) => output,
98            Err(error) => {
99                return Err(match allocation.try_close() {
100                    Ok(_) => AssuredDecodeError::Protection(error),
101                    Err(cleanup) => AssuredDecodeError::Cleanup(cleanup),
102                });
103            }
104        };
105        let result = decode_one_shot(self.settings(), input.classified_bytes(), output);
106        let written = match result {
107            Ok(written) => written,
108            Err(error) => {
109                return Err(match allocation.try_close() {
110                    Ok(_) => AssuredDecodeError::Decode(error),
111                    Err(cleanup) => AssuredDecodeError::Cleanup(cleanup),
112                });
113            }
114        };
115        if let Err(error) = allocation.set_initialized_len(written) {
116            return Err(match allocation.try_close() {
117                Ok(_) => AssuredDecodeError::Protection(error),
118                Err(cleanup) => AssuredDecodeError::Cleanup(cleanup),
119            });
120        }
121        match allocation.validate(token) {
122            Ok(validated) => Ok(validated),
123            Err((error, allocation)) => Err(match allocation.try_close() {
124                Ok(_) => AssuredDecodeError::Protection(error),
125                Err(cleanup) => AssuredDecodeError::Cleanup(cleanup),
126            }),
127        }
128    }
129
130    /// Encodes directly through one protected allocation's typestates.
131    pub fn encode_assured<'provider, P, Level>(
132        &self,
133        token: &AssuranceToken<'provider, Level>,
134        allocation: ProtectedSecret<'provider, P, Uninitialized, Level>,
135        input: &SecretInput<'_>,
136    ) -> Result<ProtectedSecret<'provider, P, Validated, Level>, AssuredEncodeError>
137    where
138        P: ProtectedMemoryProvider,
139        Level: AssuranceLevel,
140    {
141        token.revalidate().map_err(AssuredEncodeError::Assurance)?;
142        let mut allocation = match allocation.begin_unvalidated(token, SecretOperation::Encode) {
143            Ok(allocation) => allocation,
144            Err((error, allocation)) => {
145                return Err(match allocation.try_close() {
146                    Ok(_) => AssuredEncodeError::Protection(error),
147                    Err(cleanup) => AssuredEncodeError::Cleanup(cleanup),
148                });
149            }
150        };
151        let output = match allocation.bytes_mut() {
152            Ok(output) => output,
153            Err(error) => {
154                return Err(match allocation.try_close() {
155                    Ok(_) => AssuredEncodeError::Protection(error),
156                    Err(cleanup) => AssuredEncodeError::Cleanup(cleanup),
157                });
158            }
159        };
160        let result = encode_one_shot(self.settings(), input.classified_bytes(), output);
161        let written = match result {
162            Ok(written) => written,
163            Err(error) => {
164                return Err(match allocation.try_close() {
165                    Ok(_) => AssuredEncodeError::Encode(error),
166                    Err(cleanup) => AssuredEncodeError::Cleanup(cleanup),
167                });
168            }
169        };
170        if let Err(error) = allocation.set_initialized_len(written) {
171            return Err(match allocation.try_close() {
172                Ok(_) => AssuredEncodeError::Protection(error),
173                Err(cleanup) => AssuredEncodeError::Cleanup(cleanup),
174            });
175        }
176        match allocation.validate(token) {
177            Ok(validated) => Ok(validated),
178            Err((error, allocation)) => Err(match allocation.try_close() {
179                Ok(_) => AssuredEncodeError::Protection(error),
180                Err(cleanup) => AssuredEncodeError::Cleanup(cleanup),
181            }),
182        }
183    }
184}
185
186fn decode_one_shot(
187    settings: CodecSettings,
188    input: &[u8],
189    output: &mut [u8],
190) -> Result<usize, SecretDecodeError> {
191    crate::wipe_bytes(output);
192    let mut state = SecretDecoderState::new(settings, output.len())?;
193    if let Err(error) = state.update(input, output) {
194        crate::wipe_bytes(output);
195        return Err(error);
196    }
197    let candidate = match state.finish() {
198        Ok(candidate) => candidate,
199        Err(error) => {
200            crate::wipe_bytes(output);
201            return Err(error);
202        }
203    };
204    let written = candidate.written();
205    output[candidate.staged_len..written].copy_from_slice(&candidate.bytes[..candidate.len]);
206    crate::wipe_tail(output, written);
207    Ok(written)
208}
209
210fn encode_one_shot(
211    settings: CodecSettings,
212    input: &[u8],
213    output: &mut [u8],
214) -> Result<usize, SecretEncodeError> {
215    crate::wipe_bytes(output);
216    let mut state = SecretEncoderState::new(settings, input.len(), output.len())?;
217    if let Err(error) = state.update(input, output) {
218        crate::wipe_bytes(output);
219        return Err(error);
220    }
221    match state.finish(output) {
222        Ok(written) => Ok(written),
223        Err(error) => {
224            crate::wipe_bytes(output);
225            Err(error)
226        }
227    }
228}