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}