cloud_sdk_sanitization/
lib.rs1#![no_std]
2#![doc = include_str!("../README.md")]
3
4#[cfg(feature = "std")]
5extern crate std;
6
7#[cfg(feature = "alloc")]
8extern crate alloc;
9
10#[cfg(feature = "alloc")]
11pub use sanitization::SecretString;
12
13#[inline]
18pub fn sanitize_bytes(bytes: &mut [u8]) {
19 sanitization::wipe::bytes(bytes);
20}
21
22#[inline]
27pub fn sanitize_value<T: sanitization::SecureSanitize + ?Sized>(value: &mut T) {
28 sanitization::SecureSanitize::secure_sanitize(value);
29}
30
31#[cfg(feature = "alloc")]
33#[inline]
34pub fn sanitize_string(value: &mut alloc::string::String) {
35 sanitization::wipe::string(value);
36}
37
38pub struct SecretBuffer<'a> {
44 bytes: &'a mut [u8],
45}
46
47impl<'a> SecretBuffer<'a> {
48 #[must_use]
50 pub const fn new(bytes: &'a mut [u8]) -> Self {
51 Self { bytes }
52 }
53
54 #[must_use]
56 pub fn as_mut_slice(&mut self) -> &mut [u8] {
57 self.bytes
58 }
59
60 #[must_use]
62 pub fn as_slice(&self) -> &[u8] {
63 self.bytes
64 }
65}
66
67impl Drop for SecretBuffer<'_> {
68 fn drop(&mut self) {
69 sanitize_bytes(self.bytes);
70 }
71}
72
73impl core::fmt::Debug for SecretBuffer<'_> {
74 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
75 formatter.write_str("SecretBuffer([redacted])")
76 }
77}
78
79#[cfg(test)]
80mod tests {
81 #[cfg(feature = "alloc")]
82 extern crate alloc;
83
84 #[cfg(feature = "alloc")]
85 use super::SecretString;
86 #[cfg(feature = "alloc")]
87 use super::sanitize_string;
88 use super::{SecretBuffer, sanitize_bytes, sanitize_value};
89
90 #[test]
91 fn explicit_sanitization_clears_every_byte() {
92 let mut bytes = [0xa5_u8; 8];
93 sanitize_bytes(&mut bytes);
94 assert_eq!(bytes, [0; 8]);
95 }
96
97 #[test]
98 fn scalar_sanitization_uses_the_same_audited_boundary() {
99 let mut value = usize::MAX;
100 sanitize_value(&mut value);
101 assert_eq!(value, 0);
102 }
103
104 #[cfg(feature = "alloc")]
105 #[test]
106 fn owned_string_sanitization_clears_length_and_complete_capacity() {
107 let mut value = alloc::string::String::with_capacity(32);
108 value.push_str("sensitive key");
109 sanitize_string(&mut value);
110 assert!(value.is_empty());
111 assert_eq!(value.capacity(), 32);
112 }
113
114 #[test]
115 fn guard_clears_its_full_buffer_on_drop() {
116 let mut bytes = [0xa5_u8; 8];
117 {
118 let mut guarded = SecretBuffer::new(&mut bytes);
119 if let Some(first) = guarded.as_mut_slice().first_mut() {
120 *first = 0x42;
121 }
122 assert_eq!(guarded.as_slice().first(), Some(&0x42));
123 }
124 assert_eq!(bytes, [0; 8]);
125 }
126
127 #[test]
128 fn guard_clears_after_an_early_error() {
129 fn write_then_fail(output: &mut [u8]) -> Result<(), ()> {
130 let mut guarded = SecretBuffer::new(output);
131 if let Some(first) = guarded.as_mut_slice().first_mut() {
132 *first = 0x42;
133 }
134 Err(())
135 }
136
137 let mut bytes = [0xa5_u8; 8];
138 assert_eq!(write_then_fail(&mut bytes), Err(()));
139 assert_eq!(bytes, [0; 8]);
140 }
141
142 #[cfg(feature = "alloc")]
143 #[test]
144 fn reexported_secret_string_uses_scoped_access_and_redacted_debug() {
145 let secret = SecretString::from_string(alloc::string::String::from("temporary secret"));
146
147 assert_eq!(
148 secret.try_with_secret(|value| value == "temporary secret"),
149 Ok(true)
150 );
151 assert!(!alloc::format!("{secret:?}").contains("temporary secret"));
152 }
153}