Skip to main content

ddd_toolkit_core/
secret.rs

1use core::fmt;
2use core::mem::ManuallyDrop;
3use zeroize::Zeroize;
4
5/// **English:**<br>
6/// Owns a value whose [`Zeroize::zeroize`] method is called before it is dropped.
7///
8/// `Debug` and `Display` write the literal `[REDACTED]` rather than formatting `T`. Access is explicit through
9/// [`Self::as_inner`], [`Self::expose_secret`], or [`Self::into_inner`]; this type implements
10/// neither `Deref` nor serde `Serialize`. With `serde`, it implements `Deserialize` for
11/// `T: serde::Deserialize<'de> + Zeroize`, taking ownership through [`Self::new`]. With
12/// `serde_with`, `ExposeSecret` explicitly opts a field into plaintext output.
13/// Internal deserialization errors are replaced with `failed to deserialize secret`;
14/// independent format errors and logs from callers or custom implementations are not redacted.
15/// This is not encryption. Successfully wrapped values are zeroized on drop, but input/output
16/// buffers, parser temporaries, partially constructed or failed values, and other copies are not
17/// covered. Cleanup during failed deserialization depends on the inner type and parser; serde's
18/// contract cannot recover a partial `T` from an error. A caller-owned `zeroize::Zeroizing`
19/// input buffer can reduce exposure, but cannot erase parser copies.
20/// Cloning copies the inner value into another independently owned `Secret`. Equality delegates
21/// to the inner type and does not promise constant-time comparison.
22///
23/// Zeroization follows `T`'s implementation and requires this wrapper's destructor to run;
24/// it does not erase copies made elsewhere. [`Self::into_inner`] bypasses that destructor.
25/// If `T::zeroize` panics, the inner value's destructor is not called by this wrapper.
26/// `Send` and `Sync` depend on `T`; no synchronization is added.
27///
28/// Formatting hides the value; the closure below explicitly reads its length.
29///
30/// **日本語:**<br>
31/// 所有する値を破棄する前に、その値の [`Zeroize::zeroize`] を呼び出すラッパーです。
32///
33/// `Debug` と `Display` は `T` を書式出力せず、リテラル `[REDACTED]` を出力します。値へのアクセスには
34/// [`Self::as_inner`]、[`Self::expose_secret`]、[`Self::into_inner`] を明示的に使います。
35/// `Deref` と serde の `Serialize` は実装しません。`serde` feature では
36/// `T: serde::Deserialize<'de> + Zeroize` に対する `Deserialize` を実装し、[`Self::new`] で
37/// 所有権を受け取ります。`serde_with` feature の `ExposeSecret` で、フィールドの
38/// 平文出力を明示的に許可できます。内部型由来の復元エラーは `failed to deserialize secret` に
39/// 置き換えますが、フォーマット側が独立に生成するエラーや呼び出し側・独自実装のログは秘匿しません。
40/// 暗号化ではありません。正常に包んだ値は破棄時にゼロ化しますが、入出力バッファ、パーサーの
41/// 一時値、構築途中・失敗時の値や別のコピーは対象外です。失敗時の後始末は内部型とパーサーに
42/// 依存し、serde の契約ではエラーから構築途中の `T` を回収できません。入力バッファを
43/// `zeroize::Zeroizing` で管理すると軽減できますが、パーサーのコピーは消去できません。
44/// クローンは内包値を複製して別の `Secret` に所有させます。等値比較は内包型に
45/// 委譲し、定数時間での比較を保証しません。
46///
47/// ゼロ化の範囲は `T` の実装に従い、このラッパーのデストラクターが実行される必要があります。
48/// 別の場所に作られたコピーは消去しません。[`Self::into_inner`] はデストラクターを迂回します。
49/// `T::zeroize` がパニックした場合、このラッパーは内包値のデストラクターを呼び出しません。
50/// `Send` と `Sync` は `T` に依存し、同期機構は追加しません。
51///
52/// 書式出力は値を隠します。次のクロージャーでは長さを明示的に読み取ります。
53///
54/// # Example / 使用例
55///
56/// ```rust
57/// use ddd_toolkit_core::Secret;
58///
59/// let secret = Secret::new(String::from("token"));
60/// assert_eq!(format!("{secret:?}"), "[REDACTED]");
61/// assert_eq!(format!("{secret}"), "[REDACTED]");
62/// assert_eq!(secret.expose_secret(|value| value.len()), 5);
63/// ```
64pub struct Secret<T: Zeroize> {
65    value: ManuallyDrop<T>,
66}
67
68impl<T: Zeroize> Secret<T> {
69    /// **English:**<br>
70    /// Takes ownership of `value` without copying or zeroizing it at construction.
71    ///
72    /// **日本語:**<br>
73    /// 構築時にコピーやゼロ化を行わず、`value` の所有権を受け取ります。
74    pub fn new(value: T) -> Self {
75        Self {
76            value: ManuallyDrop::new(value),
77        }
78    }
79
80    /// **English:**<br>
81    /// Borrows the unredacted value for no longer than the borrow of `self`.
82    ///
83    /// Callers can read or copy the value; those copies are not zeroized by this wrapper.
84    ///
85    /// **日本語:**<br>
86    /// `self` の借用期間を超えない参照として、秘匿化していない値を借用します。
87    ///
88    /// 呼び出し側は値を読み取ったりコピーしたりできますが、そのコピーはこのラッパーではゼロ化しません。
89    pub fn as_inner(&self) -> &T {
90        &self.value
91    }
92
93    /// **English:**<br>
94    /// Consumes the wrapper and returns its value without zeroizing it.
95    ///
96    /// The wrapper's destructor is not run. The caller becomes responsible for the returned
97    /// value's lifetime and any required zeroization.
98    ///
99    /// **日本語:**<br>
100    /// ラッパーを消費し、ゼロ化せずに内包値を返します。
101    ///
102    /// ラッパーのデストラクターは実行しません。返された値の生存期間と、必要なゼロ化の管理は
103    /// 呼び出し側の責任になります。
104    ///
105    /// # Example / 使用例
106    ///
107    /// ```rust
108    /// use ddd_toolkit_core::Secret;
109    /// use zeroize::Zeroize;
110    ///
111    /// let mut value = Secret::new(vec![1_u8, 2, 3]).into_inner();
112    /// assert_eq!(value, [1, 2, 3]);
113    /// value.zeroize();
114    /// ```
115    pub fn into_inner(self) -> T {
116        let mut this = ManuallyDrop::new(self);
117        // SAFETY: The value is still initialized and taken exactly once. The outer
118        // ManuallyDrop prevents Secret::drop from accessing or dropping it afterward.
119        unsafe { ManuallyDrop::take(&mut this.value) }
120    }
121
122    /// **English:**<br>
123    /// Calls `f` once with a shared reference to the value and returns its result.
124    ///
125    /// This does not consume or zeroize the value. The closure can copy it or return a borrowed
126    /// reference; the closure is not an isolation boundary. Any panic from `f` propagates.
127    ///
128    /// **日本語:**<br>
129    /// 値への共有参照を渡して `f` を1回呼び出し、その結果を返します。
130    ///
131    /// 値の消費やゼロ化は行いません。クロージャーは値をコピーしたり借用した参照を返したりでき、
132    /// 情報の隔離境界にはなりません。`f` がパニックした場合はそのまま伝播します。
133    pub fn expose_secret<R>(&self, f: impl FnOnce(&T) -> R) -> R {
134        f(self.as_inner())
135    }
136}
137
138#[cfg(feature = "serde")]
139impl<'de, T: Zeroize + serde::Deserialize<'de>> serde::Deserialize<'de> for Secret<T> {
140    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
141        T::deserialize(deserializer)
142            .map(Self::new)
143            .map_err(|_| serde::de::Error::custom("failed to deserialize secret"))
144    }
145}
146
147impl<T: Zeroize> Drop for Secret<T> {
148    fn drop(&mut self) {
149        self.value.zeroize();
150        // SAFETY: The value is initialized and has not been taken. Drop runs once,
151        // and ManuallyDrop prevents an automatic second drop of the inner value.
152        unsafe { ManuallyDrop::drop(&mut self.value) };
153    }
154}
155
156impl<T: Zeroize + Clone> Clone for Secret<T> {
157    fn clone(&self) -> Self {
158        Self::new(self.as_inner().clone())
159    }
160}
161
162impl<T: Zeroize + PartialEq> PartialEq for Secret<T> {
163    fn eq(&self, other: &Self) -> bool {
164        self.as_inner() == other.as_inner()
165    }
166}
167
168impl<T: Zeroize + Eq> Eq for Secret<T> {}
169
170impl<T: Zeroize> fmt::Debug for Secret<T> {
171    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
172        formatter.write_str("[REDACTED]")
173    }
174}
175
176impl<T: Zeroize> fmt::Display for Secret<T> {
177    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
178        formatter.write_str("[REDACTED]")
179    }
180}