Skip to main content

mz_ore/
secure.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License in the LICENSE file at the
6// root of this repository, or online at
7//
8//     http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16//! Utilities for handling sensitive data that must be zeroed from memory on drop.
17//!
18//! This module provides:
19//!
20//! - Re-exports of [`zeroize`] crate fundamentals ([`Zeroize`], [`ZeroizeOnDrop`],
21//!   [`Zeroizing`]) so that downstream crates can depend on `mz-ore` alone.
22//!
23//! - [`SecureString`]: a `String` wrapper that is zeroed on drop and redacted
24//!   in `Debug`/`Display` output. Use for passwords, tokens, and credentials.
25//!
26//! - [`SecureVec`]: a `Vec<u8>` wrapper that is zeroed on drop and redacted
27//!   in `Debug`/`Display` output. Use for raw key material and secret bytes.
28//!
29//! # When to use
30//!
31//! Use these types whenever a value contains secret material (passwords, keys,
32//! tokens, salts, nonces) that should not linger in process memory after use.
33//!
34//! # Examples
35//!
36//! ```
37//! use mz_ore::secure::{SecureString, SecureVec, Zeroizing};
38//!
39//! // Wrap a password — zeroed on drop, redacted in logs
40//! let password = SecureString::from("hunter2");
41//! assert_eq!(password.unsecure(), "hunter2");
42//! assert!(!format!("{:?}", password).contains("hunter2"));
43//!
44//! // Wrap raw key bytes
45//! let key = SecureVec::from(vec![0xDE, 0xAD, 0xBE, 0xEF]);
46//! assert_eq!(key.unsecure(), &[0xDE, 0xAD, 0xBE, 0xEF]);
47//!
48//! // Use Zeroizing<T> for temporary buffers
49//! let buf = Zeroizing::new([0u8; 32]);
50//! ```
51
52use std::fmt;
53
54pub use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
55
56/// A `String` that is zeroed from memory on drop and redacted in
57/// `Debug`/`Display` output.
58///
59/// `SecureString` intentionally does **not** implement `Clone` to prevent
60/// untracked copies of sensitive data. Use [`unsecure`](SecureString::unsecure)
61/// to access the inner value when needed, and pass by reference where possible.
62#[derive(Zeroize, ZeroizeOnDrop, PartialEq, Eq)]
63pub struct SecureString(String);
64
65impl SecureString {
66    /// Returns a reference to the inner string.
67    ///
68    /// Prefer passing `&SecureString` over calling this method, to keep the
69    /// secret wrapped as long as possible.
70    pub fn unsecure(&self) -> &str {
71        &self.0
72    }
73}
74
75impl From<String> for SecureString {
76    fn from(s: String) -> Self {
77        SecureString(s)
78    }
79}
80
81impl From<&str> for SecureString {
82    fn from(s: &str) -> Self {
83        SecureString(s.to_string())
84    }
85}
86
87impl fmt::Debug for SecureString {
88    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
89        f.write_str("SecureString(<redacted>)")
90    }
91}
92
93impl fmt::Display for SecureString {
94    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95        f.write_str("<redacted>")
96    }
97}
98
99/// A `Vec<u8>` that is zeroed from memory on drop and redacted in
100/// `Debug`/`Display` output.
101///
102/// `SecureVec` intentionally does **not** implement `Clone` to prevent
103/// untracked copies of sensitive data. Use [`unsecure`](SecureVec::unsecure)
104/// to access the inner bytes when needed.
105#[derive(Zeroize, ZeroizeOnDrop, PartialEq, Eq)]
106pub struct SecureVec(Vec<u8>);
107
108impl SecureVec {
109    /// Returns a reference to the inner byte slice.
110    ///
111    /// Prefer passing `&SecureVec` over calling this method, to keep the
112    /// secret wrapped as long as possible.
113    pub fn unsecure(&self) -> &[u8] {
114        &self.0
115    }
116}
117
118impl From<Vec<u8>> for SecureVec {
119    fn from(v: Vec<u8>) -> Self {
120        SecureVec(v)
121    }
122}
123
124impl fmt::Debug for SecureVec {
125    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126        f.write_str("SecureVec(<redacted>)")
127    }
128}
129
130impl fmt::Display for SecureVec {
131    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
132        f.write_str("<redacted>")
133    }
134}
135
136#[cfg(test)]
137mod tests {
138    use super::*;
139
140    #[crate::test]
141    fn secure_string_round_trip_and_redaction() {
142        let s = SecureString::from("super-secret");
143        assert_eq!(s.unsecure(), "super-secret");
144        assert!(!format!("{:?}", s).contains("super-secret"));
145        assert!(!format!("{}", s).contains("super-secret"));
146    }
147
148    #[crate::test]
149    fn secure_vec_round_trip_and_redaction() {
150        let v = SecureVec::from(vec![0xDE, 0xAD, 0xBE, 0xEF]);
151        assert_eq!(v.unsecure(), &[0xDE, 0xAD, 0xBE, 0xEF]);
152        assert!(!format!("{:?}", v).contains("222")); // 0xDE = 222
153    }
154
155    #[crate::test]
156    fn secure_string_from_str() {
157        let s = SecureString::from("literal");
158        assert_eq!(s.unsecure(), "literal");
159    }
160
161    #[crate::test]
162    fn types_implement_zeroize_on_drop() {
163        fn assert_zod<T: ZeroizeOnDrop>() {}
164        assert_zod::<SecureString>();
165        assert_zod::<SecureVec>();
166    }
167}