sanitization_bytes/
lib.rs1#![no_std]
2#![deny(unsafe_code)]
3
4use bytes::BytesMut;
11use core::fmt;
12use sanitization::{wipe, SecureSanitize};
13
14#[cfg(test)]
15extern crate std;
16
17#[derive(Clone, Copy, Debug, Eq, PartialEq)]
19pub struct CapacityError {
20 pub capacity: usize,
22 pub len: usize,
24 pub additional: usize,
26}
27
28impl fmt::Display for CapacityError {
29 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
30 write!(
31 formatter,
32 "insufficient secret buffer capacity: capacity {}, len {}, additional {}",
33 self.capacity, self.len, self.additional
34 )
35 }
36}
37
38pub struct SecretBytesMut {
53 inner: BytesMut,
54}
55
56impl SecretBytesMut {
57 #[must_use]
59 #[inline]
60 pub fn new() -> Self {
61 Self {
62 inner: BytesMut::new(),
63 }
64 }
65
66 #[must_use]
68 #[inline]
69 pub fn with_capacity(capacity: usize) -> Self {
70 Self {
71 inner: BytesMut::with_capacity(capacity),
72 }
73 }
74
75 #[must_use]
77 #[inline]
78 pub fn from_slice(bytes: &[u8]) -> Self {
79 let mut inner = BytesMut::with_capacity(bytes.len());
80 inner.extend_from_slice(bytes);
81 Self { inner }
82 }
83
84 #[must_use]
90 #[inline]
91 pub fn from_bytes_mut(inner: BytesMut) -> Self {
92 let mut inner = inner;
93 let len = inner.len();
94 let capacity = inner.capacity();
95 inner.resize(capacity, 0);
96 wipe::bytes(&mut inner[len..]);
97 inner.truncate(len);
98 Self { inner }
99 }
100
101 #[must_use]
103 #[inline]
104 pub fn len(&self) -> usize {
105 self.inner.len()
106 }
107
108 #[must_use]
110 #[inline]
111 pub fn is_empty(&self) -> bool {
112 self.inner.is_empty()
113 }
114
115 #[must_use]
117 #[inline]
118 pub fn capacity(&self) -> usize {
119 self.inner.capacity()
120 }
121
122 #[inline]
128 pub fn extend_from_slice(&mut self, bytes: &[u8]) -> Result<(), CapacityError> {
129 let remaining = self.inner.capacity().saturating_sub(self.inner.len());
130 if bytes.len() > remaining {
131 return Err(CapacityError {
132 capacity: self.inner.capacity(),
133 len: self.inner.len(),
134 additional: bytes.len(),
135 });
136 }
137
138 self.inner.extend_from_slice(bytes);
139 Ok(())
140 }
141
142 #[must_use]
144 #[inline]
145 pub fn as_slice(&self) -> &[u8] {
146 self.inner.as_ref()
147 }
148
149 #[inline]
151 pub fn with_secret<R>(&self, inspect: impl FnOnce(&[u8]) -> R) -> R {
152 inspect(self.as_slice())
153 }
154
155 #[inline]
157 pub fn with_secret_mut<R>(&mut self, edit: impl FnOnce(&mut [u8]) -> R) -> R {
158 edit(self.inner.as_mut())
159 }
160
161 #[inline]
163 pub fn clear_secret(&mut self) {
164 let capacity = self.inner.capacity();
165 self.inner.resize(capacity, 0);
166 wipe::bytes(self.inner.as_mut());
167 self.inner.clear();
168 }
169
170 #[inline]
172 pub fn into_cleared(mut self) {
173 self.clear_secret();
174 }
175}
176
177impl Default for SecretBytesMut {
178 #[inline]
179 fn default() -> Self {
180 Self::new()
181 }
182}
183
184impl SecureSanitize for SecretBytesMut {
185 #[inline]
186 fn secure_sanitize(&mut self) {
187 self.clear_secret();
188 }
189}
190
191impl Drop for SecretBytesMut {
192 #[inline]
193 fn drop(&mut self) {
194 self.clear_secret();
195 }
196}
197
198impl fmt::Debug for SecretBytesMut {
199 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
200 formatter
201 .debug_struct("SecretBytesMut")
202 .field("len", &self.len())
203 .field("capacity", &self.capacity())
204 .field("contents", &"<redacted>")
205 .finish()
206 }
207}
208
209#[cfg(test)]
210mod tests {
211 use super::*;
212
213 #[test]
214 fn bytes_mut_wrapper_round_trip_and_clear() {
215 let mut secret = SecretBytesMut::with_capacity(8);
216
217 secret.extend_from_slice(b"token").unwrap();
218 secret.extend_from_slice(b"-v2").unwrap();
219
220 assert_eq!(secret.len(), 8);
221 assert_eq!(secret.with_secret(|bytes| bytes[0]), b't');
222
223 secret.with_secret_mut(|bytes| bytes[0] = b'T');
224 assert_eq!(secret.with_secret(|bytes| bytes[0]), b'T');
225
226 secret.clear_secret();
227 assert!(secret.is_empty());
228 }
229
230 #[test]
231 fn bytes_mut_wrapper_refuses_growth_past_capacity() {
232 let mut secret = SecretBytesMut::with_capacity(5);
233
234 secret.extend_from_slice(b"token").unwrap();
235
236 assert_eq!(
237 secret.extend_from_slice(b"-v2"),
238 Err(CapacityError {
239 capacity: 5,
240 len: 5,
241 additional: 3,
242 })
243 );
244 assert!(secret.with_secret(|bytes| bytes == b"token"));
245 }
246
247 #[test]
248 fn bytes_mut_wrapper_debug_is_redacted() {
249 let secret = SecretBytesMut::from_slice(b"token");
250 let rendered = std::format!("{secret:?}");
251
252 assert!(rendered.contains("redacted"));
253 assert!(!rendered.contains("token"));
254 }
255
256 #[test]
257 #[allow(unsafe_code)]
258 fn wrapping_clears_spare_capacity_and_preserves_live_bytes() {
259 let mut incoming = BytesMut::with_capacity(16);
260 incoming.extend_from_slice(b"historical-data");
261 incoming.truncate(5);
262 let capacity = incoming.capacity();
263
264 let mut wrapped = SecretBytesMut::from_bytes_mut(incoming);
265
266 assert_eq!(wrapped.as_slice(), b"histo");
267 assert_eq!(wrapped.capacity(), capacity);
268
269 let live_len = wrapped.len();
270 unsafe { wrapped.inner.set_len(capacity) };
274 assert!(wrapped.inner[live_len..].iter().all(|byte| *byte == 0));
275 unsafe { wrapped.inner.set_len(live_len) };
278 }
279}