sanitization_bytes/
lib.rs1#![no_std]
2#![deny(unsafe_code)]
3
4use bytes::BytesMut;
11use core::fmt;
12use sanitization::{sanitize_bytes, 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]
86 #[inline]
87 pub fn from_bytes_mut(inner: BytesMut) -> Self {
88 Self { inner }
89 }
90
91 #[must_use]
93 #[inline]
94 pub fn len(&self) -> usize {
95 self.inner.len()
96 }
97
98 #[must_use]
100 #[inline]
101 pub fn is_empty(&self) -> bool {
102 self.inner.is_empty()
103 }
104
105 #[must_use]
107 #[inline]
108 pub fn capacity(&self) -> usize {
109 self.inner.capacity()
110 }
111
112 #[inline]
118 pub fn extend_from_slice(&mut self, bytes: &[u8]) -> Result<(), CapacityError> {
119 let remaining = self.inner.capacity().saturating_sub(self.inner.len());
120 if bytes.len() > remaining {
121 return Err(CapacityError {
122 capacity: self.inner.capacity(),
123 len: self.inner.len(),
124 additional: bytes.len(),
125 });
126 }
127
128 self.inner.extend_from_slice(bytes);
129 Ok(())
130 }
131
132 #[must_use]
134 #[inline]
135 pub fn as_slice(&self) -> &[u8] {
136 self.inner.as_ref()
137 }
138
139 #[inline]
141 pub fn with_secret<R>(&self, inspect: impl FnOnce(&[u8]) -> R) -> R {
142 inspect(self.as_slice())
143 }
144
145 #[inline]
147 pub fn with_secret_mut<R>(&mut self, edit: impl FnOnce(&mut [u8]) -> R) -> R {
148 edit(self.inner.as_mut())
149 }
150
151 #[inline]
153 pub fn clear_secret(&mut self) {
154 let capacity = self.inner.capacity();
155 self.inner.resize(capacity, 0);
156 sanitize_bytes(self.inner.as_mut());
157 self.inner.clear();
158 }
159
160 #[inline]
162 pub fn into_cleared(mut self) {
163 self.clear_secret();
164 }
165}
166
167impl Default for SecretBytesMut {
168 #[inline]
169 fn default() -> Self {
170 Self::new()
171 }
172}
173
174impl SecureSanitize for SecretBytesMut {
175 #[inline]
176 fn secure_sanitize(&mut self) {
177 self.clear_secret();
178 }
179}
180
181impl Drop for SecretBytesMut {
182 #[inline]
183 fn drop(&mut self) {
184 self.clear_secret();
185 }
186}
187
188impl fmt::Debug for SecretBytesMut {
189 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
190 formatter
191 .debug_struct("SecretBytesMut")
192 .field("len", &self.len())
193 .field("capacity", &self.capacity())
194 .field("contents", &"<redacted>")
195 .finish()
196 }
197}
198
199#[cfg(test)]
200mod tests {
201 use super::*;
202
203 #[test]
204 fn bytes_mut_wrapper_round_trip_and_clear() {
205 let mut secret = SecretBytesMut::with_capacity(8);
206
207 secret.extend_from_slice(b"token").unwrap();
208 secret.extend_from_slice(b"-v2").unwrap();
209
210 assert_eq!(secret.len(), 8);
211 assert_eq!(secret.with_secret(|bytes| bytes[0]), b't');
212
213 secret.with_secret_mut(|bytes| bytes[0] = b'T');
214 assert_eq!(secret.with_secret(|bytes| bytes[0]), b'T');
215
216 secret.clear_secret();
217 assert!(secret.is_empty());
218 }
219
220 #[test]
221 fn bytes_mut_wrapper_refuses_growth_past_capacity() {
222 let mut secret = SecretBytesMut::with_capacity(5);
223
224 secret.extend_from_slice(b"token").unwrap();
225
226 assert_eq!(
227 secret.extend_from_slice(b"-v2"),
228 Err(CapacityError {
229 capacity: 5,
230 len: 5,
231 additional: 3,
232 })
233 );
234 assert!(secret.with_secret(|bytes| bytes == b"token"));
235 }
236
237 #[test]
238 fn bytes_mut_wrapper_debug_is_redacted() {
239 let secret = SecretBytesMut::from_slice(b"token");
240 let rendered = std::format!("{secret:?}");
241
242 assert!(rendered.contains("redacted"));
243 assert!(!rendered.contains("token"));
244 }
245}