1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
//! A self-owned binary format for secrets, implemented as a
//! [`serde::Serializer`] and a [`serde::Deserializer`].
//!
//! # Why not JSON
//!
//! `serde_json` cannot be made to leave no traces, and the parts that leak are
//! out of our reach:
//!
//! - its `Deserializer` holds a private `scratch: Vec<u8>` that it reuses for
//! every escaped string and never zeroizes;
//! - `from_reader` copies *every* string into that scratch, escaped or not;
//! - `Value` / `RawValue` deserialize the whole document into plain `String`s;
//! - serde's own error formatting renders `Unexpected::Str(value)` as
//! `string "…the plaintext…"`, which is exactly what ends up in a log.
//!
//! None of those are serde's trait layer — they belong to `serde_json`. A
//! format we own has no scratch buffer, no escaping pass, no `Value`, and
//! builds every error from a length, an index or a `&'static str`. The one
//! message an impl can influence is discarded rather than rendered in both
//! directions — serde's own `unknown_variant` / `unknown_field` helpers format
//! the offending name into it, and a hand-written `Serialize` impl can format
//! payload into it. Errors are then safe to log, and a test pins that.
//!
//! # Why serde traits instead of a new trait pair
//!
//! Because it costs nothing and gives away nothing. Writing a bespoke
//! `SecureSerialize` trait would mean shipping a `#[derive]` macro, which means
//! `syn`, `quote`, `proc-macro2` and a second `proc-macro = true` crate. Using
//! `serde::Serializer` / `serde::Deserializer` needs **no new dependency at
//! all** (serde is already optional here) and keeps the exact attribute surface:
//!
//! ```
//! use secure_types::{decode, encode};
//! use serde::{Deserialize, Serialize};
//!
//! #[derive(Serialize, Deserialize, Debug, PartialEq)]
//! struct VaultData {
//! label: String,
//!
//! /// AEAD key for the wallet state
//! #[serde(default)]
//! wallet_state_key: Option<u32>,
//!
//! #[serde(default, skip_serializing)]
//! contacts: Vec<String>,
//! }
//!
//! let vault = VaultData {
//! label: "main".to_owned(),
//! wallet_state_key: Some(7),
//! contacts: vec!["not persisted".to_owned()],
//! };
//!
//! let encoded = encode(&vault)?;
//!
//! // The plaintext only ever existed in locked memory.
//! let decoded = decode::<VaultData>(&encoded)?;
//!
//! assert_eq!(decoded.wallet_state_key, Some(7));
//! assert!(decoded.contacts.is_empty()); // `skip_serializing` -> default
//! # Ok::<(), Box<dyn std::error::Error>>(())
//! ```
//!
//! # What is not supported
//!
//! The format is not self-describing, so [`serde::Deserializer::deserialize_any`]
//! cannot be implemented. Anything that depends on it fails with
//! [`DecodeError::Unsupported`] rather than guessing:
//!
//! - `#[serde(flatten)]`
//! - `#[serde(untagged)]`
//! - `serde_json::Value`-shaped fields
//!
//! Note the asymmetry for untagged enums: an untagged variant *serializes*
//! successfully — writing one needs no type tag — and only fails on the way
//! back in. A successful [`encode`] is therefore not on its own a promise that
//! the value is decodable.
//!
//! [`serde::Deserializer::deserialize_ignored_any`] works in exactly one place:
//! the body of a struct field, which is the only position whose extent is known.
//! That is what makes "add a field with `#[serde(default)]`" a compatible
//! change, and it is why struct fields carry a length.
//!
//! # The wire format
//!
//! Integers are little-endian; lengths and counts are unsigned LEB128 varints.
//! [`FORMAT_VERSION`] is the first byte of every document.
//!
//! ```text
//! unit, unit_struct := (no bytes)
//! bool := 0x00 | 0x01
//! u8..u64 / i8..i64 := little-endian, fixed width
//! u128 / i128 := little-endian, 16 bytes
//! f32 / f64 := IEEE-754 bits, little-endian
//! char := u32 LE Unicode scalar value
//! str := varint(len) || utf8 bytes
//! bytes := varint(len) || bytes
//! none := 0x00
//! some(v) := 0x01 || v
//! seq / tuple /
//! tuple_struct := varint(count) || value * count
//! map := varint(count) || (key || value) * count
//! struct / struct_var := varint(count) || (str(field_name) ||
//! u32_le(value_len) || value) * count
//! newtype_struct := value
//! enum := str(variant_name) || variant_payload
//! ```
//!
//! Struct fields and enum variants are tagged by *name* rather than by position
//! or index, so reordering or skipping one cannot silently reinterpret old data,
//! and the `u32` length around each field body is what lets a reader skip a
//! field it does not know.
use crateSecureBytes;
use Serialize;
use DeserializeOwned;
pub use ;
/// Initial buffer size used by [`encode`].
///
/// Matches the JSON helpers' `DEFAULT_JSON_CAPACITY`: large enough that a
/// typical payload does not grow at all, small enough not to lock pages for
/// nothing.
const DEFAULT_CAPACITY: usize = 1024;
/// Encodes `value` into a fresh locked buffer.
///
/// The returned [`SecureBytes`] holds the only copy of the encoded value: it is
/// locked while unused and zeroized on drop, and growth wipes the previous
/// allocation. Use [`encode_with_capacity`] when the payload size is known, so
/// the buffer does not have to be reallocated and re-locked.
///
/// # Errors
///
/// Returns [`EncodeError::Secure`] if the locked buffer cannot be allocated or
/// grown, [`EncodeError::LengthOverflow`] if a value does not fit its length
/// field, and [`EncodeError::ElementCountMismatch`] if a hand-written
/// `Serialize` impl writes a different number of elements than it declared.
///
/// # Example
///
/// ```
/// use secure_types::{decode, encode, SecureString};
///
/// let secret = SecureString::from("hunter2");
///
/// let encoded = encode(&secret)?;
/// let decoded = decode::<SecureString>(&encoded)?;
///
/// decoded.unlock_str(|value| assert_eq!(value, "hunter2"));
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
/// Same as [`encode`], with an explicit initial buffer size.
///
/// Sizing the buffer to the expected payload avoids growing (and re-locking) it,
/// which keeps both the `mprotect` traffic and the `RLIMIT_MEMLOCK` pressure
/// predictable.
///
/// # Errors
///
/// Same as [`encode`].
/// Decodes `value` out of a locked buffer.
///
/// The buffer is unlocked only for the duration of the decode, and put back
/// under protection afterwards — including if the decode fails.
///
/// `T: DeserializeOwned` is load-bearing rather than decoration: it statically
/// forbids any type that would borrow out of the buffer, which is what keeps a
/// reference from surviving into the re-locked window.
///
/// # Errors
///
/// Returns [`DecodeError`] if the input is truncated or malformed, carries an
/// unsupported [`FORMAT_VERSION`], has trailing bytes, or does not match `T`.
///
/// # Example
///
/// ```
/// use secure_types::{decode, encode, SecureVec};
///
/// let key = SecureVec::from_slice(&[1u8, 2, 3])?;
///
/// let encoded = encode(&key)?;
/// let decoded = decode::<SecureVec<u8>>(&encoded)?;
///
/// decoded.unlock_slice(|value| assert_eq!(value, &[1, 2, 3]));
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
/// Same as [`decode`], for a slice the caller already holds unlocked.
///
/// `bytes` must be the complete document: trailing bytes are an error, because a
/// document that decodes with bytes left over means the reader and the writer
/// disagree about the layout.
///
/// # Errors
///
/// Same as [`decode`].