ccxt_core/credentials.rs
1//! Secure credential types with automatic memory zeroization.
2//!
3//! All sensitive data is automatically cleared from memory when dropped,
4//! preventing credential leakage through memory dumps or core files.
5//!
6//! # Security
7//!
8//! This module provides types that implement the `Zeroize` and `ZeroizeOnDrop`
9//! traits from the `zeroize` crate. When these types go out of scope, their
10//! memory is securely overwritten with zeros before being deallocated.
11//!
12//! # Example
13//!
14//! ```rust
15//! use ccxt_core::credentials::SecretString;
16//!
17//! let api_key = SecretString::new("my-api-key");
18//!
19//! // Access the secret when needed
20//! let key_value = api_key.expose_secret();
21//!
22//! // Debug output is redacted
23//! println!("{:?}", api_key); // Prints: [REDACTED]
24//!
25//! // When api_key goes out of scope, memory is automatically zeroed
26//! ```
27
28use std::fmt;
29use zeroize::{Zeroize, ZeroizeOnDrop};
30
31/// A secure string that is automatically zeroed when dropped.
32///
33/// Use this for API keys, secrets, passwords, and other sensitive data.
34/// The underlying memory is securely cleared when the value is dropped,
35/// preventing credential leakage through memory inspection.
36///
37/// # Security Features
38///
39/// - Memory is zeroed on drop using the `zeroize` crate
40/// - Debug and Display implementations are redacted to prevent accidental logging
41/// - Clone creates a new zeroed copy (original remains secure)
42///
43/// # Example
44///
45/// ```rust
46/// use ccxt_core::credentials::SecretString;
47///
48/// let secret = SecretString::new("my-secret-key");
49///
50/// // Use expose_secret() to access the value
51/// assert_eq!(secret.expose_secret(), "my-secret-key");
52///
53/// // Debug output is safe
54/// println!("{:?}", secret); // Prints: [REDACTED]
55/// ```
56#[derive(Clone, Zeroize, ZeroizeOnDrop, PartialEq, Eq)]
57pub struct SecretString(String);
58
59impl SecretString {
60 /// Creates a new secret string.
61 ///
62 /// # Arguments
63 ///
64 /// * `value` - The secret value to store
65 ///
66 /// # Example
67 ///
68 /// ```rust
69 /// use ccxt_core::credentials::SecretString;
70 ///
71 /// let secret = SecretString::new("api-key-12345");
72 /// ```
73 pub fn new(value: impl Into<String>) -> Self {
74 Self(value.into())
75 }
76
77 /// Returns the secret value.
78 ///
79 /// # Security
80 ///
81 /// Avoid storing the returned reference longer than necessary.
82 /// The reference should be used immediately and not persisted.
83 ///
84 /// # Example
85 ///
86 /// ```rust
87 /// use ccxt_core::credentials::SecretString;
88 ///
89 /// let secret = SecretString::new("my-key");
90 /// let value = secret.expose_secret();
91 /// // Use value immediately, don't store it
92 /// ```
93 #[inline]
94 pub fn expose_secret(&self) -> &str {
95 &self.0
96 }
97
98 /// Returns the secret as bytes.
99 ///
100 /// # Security
101 ///
102 /// Same security considerations as `expose_secret()` apply.
103 #[inline]
104 pub fn expose_secret_bytes(&self) -> &[u8] {
105 self.0.as_bytes()
106 }
107
108 /// Returns the length of the secret string.
109 #[inline]
110 pub fn len(&self) -> usize {
111 self.0.len()
112 }
113
114 /// Returns true if the secret string is empty.
115 #[inline]
116 pub fn is_empty(&self) -> bool {
117 self.0.is_empty()
118 }
119}
120
121// Prevent accidental logging of sensitive data
122impl fmt::Debug for SecretString {
123 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
124 f.write_str("[REDACTED]")
125 }
126}
127
128impl fmt::Display for SecretString {
129 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130 f.write_str("[REDACTED]")
131 }
132}
133
134// Convenient conversions
135impl From<String> for SecretString {
136 fn from(s: String) -> Self {
137 Self::new(s)
138 }
139}
140
141impl From<&str> for SecretString {
142 fn from(s: &str) -> Self {
143 Self::new(s)
144 }
145}
146
147/// Secure bytes that are automatically zeroed when dropped.
148///
149/// Use this for private keys, binary secrets, and other sensitive byte data.
150///
151/// # Example
152///
153/// ```rust
154/// use ccxt_core::credentials::SecretBytes;
155///
156/// let private_key = SecretBytes::new(vec![0x01, 0x02, 0x03, 0x04]);
157/// let bytes = private_key.expose_secret();
158/// ```
159#[derive(Clone, Zeroize, ZeroizeOnDrop, PartialEq, Eq)]
160pub struct SecretBytes(Vec<u8>);
161
162impl SecretBytes {
163 /// Creates new secret bytes.
164 ///
165 /// # Arguments
166 ///
167 /// * `value` - The secret bytes to store
168 pub fn new(value: impl Into<Vec<u8>>) -> Self {
169 Self(value.into())
170 }
171
172 /// Creates secret bytes from a fixed-size array.
173 ///
174 /// # Arguments
175 ///
176 /// * `value` - The secret bytes array to store
177 pub fn from_array<const N: usize>(value: [u8; N]) -> Self {
178 Self(value.to_vec())
179 }
180
181 /// Returns the secret bytes.
182 ///
183 /// # Security
184 ///
185 /// Avoid storing the returned reference longer than necessary.
186 #[inline]
187 pub fn expose_secret(&self) -> &[u8] {
188 &self.0
189 }
190
191 /// Returns the length of the secret bytes.
192 #[inline]
193 pub fn len(&self) -> usize {
194 self.0.len()
195 }
196
197 /// Returns true if the secret bytes are empty.
198 #[inline]
199 pub fn is_empty(&self) -> bool {
200 self.0.is_empty()
201 }
202}
203
204impl fmt::Debug for SecretBytes {
205 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
206 write!(f, "[REDACTED {} bytes]", self.0.len())
207 }
208}
209
210impl<const N: usize> From<[u8; N]> for SecretBytes {
211 fn from(arr: [u8; N]) -> Self {
212 Self::from_array(arr)
213 }
214}
215
216impl From<Vec<u8>> for SecretBytes {
217 fn from(v: Vec<u8>) -> Self {
218 Self::new(v)
219 }
220}
221
222#[cfg(test)]
223mod tests {
224 use super::*;
225
226 #[test]
227 fn test_secret_string_debug_redacted() {
228 let secret = SecretString::new("my-api-key");
229 assert_eq!(format!("{:?}", secret), "[REDACTED]");
230 }
231
232 #[test]
233 fn test_secret_string_display_redacted() {
234 let secret = SecretString::new("my-api-key");
235 assert_eq!(format!("{}", secret), "[REDACTED]");
236 }
237
238 #[test]
239 fn test_secret_string_expose() {
240 let secret = SecretString::new("my-api-key");
241 assert_eq!(secret.expose_secret(), "my-api-key");
242 }
243
244 #[test]
245 fn test_secret_string_expose_bytes() {
246 let secret = SecretString::new("test");
247 assert_eq!(secret.expose_secret_bytes(), b"test");
248 }
249
250 #[test]
251 fn test_secret_string_len() {
252 let secret = SecretString::new("12345");
253 assert_eq!(secret.len(), 5);
254 assert!(!secret.is_empty());
255 }
256
257 #[test]
258 fn test_secret_string_empty() {
259 let secret = SecretString::new("");
260 assert!(secret.is_empty());
261 }
262
263 #[test]
264 fn test_secret_from_string() {
265 let secret: SecretString = String::from("test").into();
266 assert_eq!(secret.expose_secret(), "test");
267 }
268
269 #[test]
270 fn test_secret_from_str() {
271 let secret: SecretString = "test".into();
272 assert_eq!(secret.expose_secret(), "test");
273 }
274
275 #[test]
276 fn test_secret_bytes_debug_redacted() {
277 let secret = SecretBytes::new(vec![1, 2, 3, 4, 5]);
278 assert_eq!(format!("{:?}", secret), "[REDACTED 5 bytes]");
279 }
280
281 #[test]
282 fn test_secret_bytes_expose() {
283 let secret = SecretBytes::new(vec![1, 2, 3]);
284 assert_eq!(secret.expose_secret(), &[1, 2, 3]);
285 }
286
287 #[test]
288 fn test_secret_bytes_from_array() {
289 let secret = SecretBytes::from_array([1u8, 2, 3, 4]);
290 assert_eq!(secret.expose_secret(), &[1, 2, 3, 4]);
291 }
292
293 #[test]
294 fn test_secret_bytes_len() {
295 let secret = SecretBytes::new(vec![1, 2, 3]);
296 assert_eq!(secret.len(), 3);
297 assert!(!secret.is_empty());
298 }
299
300 #[test]
301 fn test_secret_clone() {
302 let secret1 = SecretString::new("test");
303 let secret2 = secret1.clone();
304 assert_eq!(secret1.expose_secret(), secret2.expose_secret());
305 }
306}