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}