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/// Use [`unsecure`](SecureString::unsecure)
60/// to access the inner value when needed, and pass by reference where possible.
61#[derive(Clone, Zeroize, ZeroizeOnDrop, PartialEq, Eq)]
62pub struct SecureString(String);
63
64impl SecureString {
65 /// Returns a reference to the inner string.
66 ///
67 /// Prefer passing `&SecureString` over calling this method, to keep the
68 /// secret wrapped as long as possible.
69 pub fn unsecure(&self) -> &str {
70 &self.0
71 }
72}
73
74impl From<String> for SecureString {
75 fn from(s: String) -> Self {
76 SecureString(s)
77 }
78}
79
80impl From<&str> for SecureString {
81 fn from(s: &str) -> Self {
82 SecureString(s.to_string())
83 }
84}
85
86impl fmt::Debug for SecureString {
87 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88 f.write_str("SecureString(<redacted>)")
89 }
90}
91
92impl fmt::Display for SecureString {
93 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
94 f.write_str("<redacted>")
95 }
96}
97
98/// A `Vec<u8>` that is zeroed from memory on drop and redacted in
99/// `Debug`/`Display` output.
100///
101/// Use [`unsecure`](SecureVec::unsecure)
102/// to access the inner bytes when needed.
103#[derive(Clone, Zeroize, ZeroizeOnDrop, PartialEq, Eq)]
104pub struct SecureVec(Vec<u8>);
105
106impl SecureVec {
107 /// Returns a reference to the inner byte slice.
108 ///
109 /// Prefer passing `&SecureVec` over calling this method, to keep the
110 /// secret wrapped as long as possible.
111 pub fn unsecure(&self) -> &[u8] {
112 &self.0
113 }
114}
115
116impl From<Vec<u8>> for SecureVec {
117 fn from(v: Vec<u8>) -> Self {
118 SecureVec(v)
119 }
120}
121
122impl fmt::Debug for SecureVec {
123 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
124 f.write_str("SecureVec(<redacted>)")
125 }
126}
127
128impl fmt::Display for SecureVec {
129 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130 f.write_str("<redacted>")
131 }
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137
138 #[crate::test]
139 fn secure_string_round_trip_and_redaction() {
140 let s = SecureString::from("super-secret");
141 assert_eq!(s.unsecure(), "super-secret");
142 assert!(!format!("{:?}", s).contains("super-secret"));
143 assert!(!format!("{}", s).contains("super-secret"));
144 }
145
146 #[crate::test]
147 fn secure_vec_round_trip_and_redaction() {
148 let v = SecureVec::from(vec![0xDE, 0xAD, 0xBE, 0xEF]);
149 assert_eq!(v.unsecure(), &[0xDE, 0xAD, 0xBE, 0xEF]);
150 assert!(!format!("{:?}", v).contains("222")); // 0xDE = 222
151 }
152
153 #[crate::test]
154 fn secure_string_from_str() {
155 let s = SecureString::from("literal");
156 assert_eq!(s.unsecure(), "literal");
157 }
158
159 #[crate::test]
160 fn types_implement_zeroize_on_drop() {
161 fn assert_zod<T: ZeroizeOnDrop>() {}
162 assert_zod::<SecureString>();
163 assert_zod::<SecureVec>();
164 }
165}