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
use std::{fmt::Debug, time::Duration};

#[cfg(doc)]
use super::StrongBox;
use super::{kdf, Key, RotatingStrongBox, StaticStrongBox};

/// A way to derive many [`StrongBox`]es from one set of keys.
///
/// Splitting different encryption usages to use different keys prevents accidental misuse,
/// and reduces the chances of insecurity from overuse.  Rather than have to manage a whole
/// bunch of keys, though, a [`StemStrongBox`] allows you to "derive" [`StrongBox`]es for
/// different uses from a single "root" [`StemStrongBox`].
///
/// Let us say, for instance, that you have a typical web application.  You want to keep
/// session data in cookies, but that needs to be encrypted to prevent disclosure and
/// tampering.  You also have to encrypt the state data that you send to your OAuth providers,
/// and you have a couple of database fields that are *super* sensitive, that you'd like to
/// encrypt.
///
/// Traditionally, you'd need to have a set of keys for each of those uses -- which is an absolute pain.
/// However, with the [`StemStrongBox`], you only need to manage one set of keys (the current
/// encryption key, and any previous decryption keys that old data might still be encrypted
/// under), and the other keys can all be *derived* from that one "root" set of keys.
///
/// We might have a "key hierarchy" that looks something like this:
///
/// ```text
///                              +--------+
///                              |  root  |
///                              +--------+
///                                /  |  \
///                              /    |    \
///                            /      |      \
///                          /        |        \
///                        /          |          \
///                +=========+    +-------+    +=========+
///                | cookies |    |  DB   |    |  OAuth  |
///                +=========+    +-------+    +=========+
///                                 /   \
///                               /       \
///                             /           \
///                           /               \
///                     +----------+      +-----------+
///                     |  table1  |      |   table2  |
///                     +----------+      +-----------+
///                       /                 /       \
///                     /                 /           \
///                   /                 /               \
///                 /                 /                   \
///             +===========+    +===========+    +===========+
///             | sensitive |    | sensitive |    | sensitive |
///             |  column A |    |  column B |    |  column C |
///             +===========+    +===========+    +===========+
/// ```
///
/// In the above diagram, the boxes with `---` at top and bottom are [`StemStrongBox`]es, from
/// which you can derive other StrongBoxes (any of [`StemStrongBox`], [`StaticStrongBox`], or [``RotatingStrongBox`]).
/// The boxes with `===` at top and bottom are regular [`StrongBox`]es, and are the ones we use to
/// do cryptography.
///
/// You deliberately cannot have a kind of [`StrongBox`] that can both perform encryption and key
/// derivation, because it is a terrible idea, security wise, to use the same key for different
/// purposes.  Through the power of Rust's type system, we can enforce that.
///
/// # Example
///
/// This is how you might setup the above "tree" of [`StrongBox`]es.
///
/// ```rust
/// # use strong_box::{Error, StemStrongBox};
/// # use std::time::Duration;
/// # fn main() -> Result<(), Error> {
/// # const WEEKLY: Duration = Duration::from_secs(7 * 24 * 3600);
///
/// // A couple of keys are always useful to have
/// let old_key = strong_box::generate_key();
/// let new_key = strong_box::generate_key();
///
/// // This is the basis of all our other boxes
/// let root = StemStrongBox::new(new_key, [old_key, new_key]);
///
/// // This creates a RotatingStrongBox for secure cookie storage
/// let cookies = root.derive_rotating("cookies", WEEKLY, 52);
///
/// // This is the OAuth provider state box
/// let oauth = root.derive_static("OAuth");
///
/// // Then the great tree of DB column encryption boxes
/// let db = root.derive_stem("DB");
/// let table1 = db.derive_stem("table1");
/// let table2 = db.derive_stem("table2");
///
/// let sensitive_column_a = table1.derive_static("sensitive column A");
/// let sensitive_column_b = table2.derive_static("sensitive column B");
/// let sensitive_column_c = table2.derive_static("sensitive column C");
///
/// // We can now call encrypt/decrypt on any of the boxes created by .derive or .derive_rotating, but
/// // not any of the boxes created by derive_stem, as they are only for further derivation
/// # Ok(())
/// # }
/// ```
#[derive(Clone, Debug)]
pub struct StemStrongBox {
	encryption_key: Key<[u8; 32]>,
	decryption_keys: Vec<Key<[u8; 32]>>,
}

impl StemStrongBox {
	/// Create a new [`StemStrongBox`].
	#[tracing::instrument(level = "debug", skip(enc_key, dec_keys))]
	pub fn new(
		enc_key: impl Into<Key<[u8; 32]>> + Debug,
		dec_keys: impl IntoIterator<Item = impl Into<Key<[u8; 32]>>> + Debug,
	) -> Self {
		Self {
			encryption_key: enc_key.into(),
			decryption_keys: dec_keys.into_iter().map(|k| k.into()).collect(),
		}
	}

	/// Derive a [`StaticStrongBox`] from the keys in this [`StemStrongBox`], for the specified purpose.
	#[tracing::instrument(level = "debug")]
	pub fn derive_static(&self, purpose: impl AsRef<[u8]> + Debug) -> StaticStrongBox {
		let mut context: Vec<u8> = b"derive::".to_vec();
		context.extend_from_slice(purpose.as_ref());
		StaticStrongBox::new(
			kdf::derive_key(&self.encryption_key, &context),
			self.decryption_keys
				.iter()
				.map(|k| kdf::derive_key(k, &context)),
		)
	}

	/// Derive a new [`StemStrongBox`] from the keys in this [`StemStrongBox`], for the specified
	/// purpose.
	#[tracing::instrument(level = "debug")]
	pub fn derive_stem(&self, purpose: impl AsRef<[u8]> + Debug) -> StemStrongBox {
		let mut context: Vec<u8> = b"derive_stem::".to_vec();
		context.extend_from_slice(purpose.as_ref());
		StemStrongBox::new(
			kdf::derive_key(&self.encryption_key, &context),
			self.decryption_keys
				.iter()
				.map(|k| kdf::derive_key(k, &context)),
		)
	}

	/// Derive a new [`RotatingStrongBox`] from the keys in this [`StemStrongBox`], for the specified purpose.
	///
	/// For data that is only valid for a certain period of time, it can be convenient to
	/// automatically "expire" old data by just forgetting the key that encrypted that data.  You
	/// can also avoid the "many encryptions" vulnerability by periodically rotating the key that
	/// is actually used for encryption.
	///
	/// This method creates a [`RotatingStrongBox`], a variant of the regular [`StrongBox`] which
	/// changes the encryption key periodically, and can "look back" a certain number of rotations
	/// to decrypt data that was encrypted with a key produced in a previous rotation period.  You
	/// can always decrypt ciphertexts encrypted in the *current* time period, which is indicated
	/// by a `backtrack` of `0`.
	///
	/// Bear in mind that rotation periods are non-overlapping.  With a `backtrack` of `0`,
	/// a ciphertext created at the very end of a rotation period will only be decryptable for
	/// as little as a nanosecond before the key is expired.  Thus the minimum *practical* value
	/// for `backtrack` is probably `1` in almost all cases.
	///
	/// # Example
	///
	/// Let's say you're encrypting an authentication cookie, and because you're encrypting so many
	/// cookies, you want to rotate the key every week.  However, you allow users to stay logged in
	/// for up to a year, so cookies from up to 52 weeks ago need to still be readable by your
	/// application.
	///
	/// In that case, you could create a [`RotatingStrongBox`] with a "weekly" period, and
	/// use up to 52 previous keys to decrypt the data, like this:
	///
	/// ```rust
	/// # use strong_box::StemStrongBox;
	/// # use std::time::Duration;
	///
	/// // Seven days, each of 24 hours, each hour with 3,600 seconds
	/// const WEEKLY: Duration = Duration::from_secs(7 * 24 * 3600);
	///
	/// let key = strong_box::generate_key();
	///
	/// let cookie_box = StemStrongBox::new(key, Vec::<[u8; 32]>::new()).derive_rotating(b"cookies", WEEKLY, 52);
	///
	/// // You can now encrypt/decrypt to your heart's content with the cookie_box
	/// ```
	pub fn derive_rotating(
		&self,
		purpose: impl AsRef<[u8]> + Debug,
		period: Duration,
		backtrack: u16,
	) -> RotatingStrongBox {
		let mut context: Vec<u8> = b"derive_stem::".to_vec();
		context.extend_from_slice(purpose.as_ref());
		RotatingStrongBox::new(
			kdf::derive_key(&self.encryption_key, &context),
			self.decryption_keys
				.iter()
				.map(|k| kdf::derive_key(k, &context))
				.collect(),
			period,
			backtrack,
		)
	}
}