strong_box/
shared_strong_box.rs

1use secrecy::{ExposeSecret as _, SecretBox, SecretSlice};
2use x25519_dalek::{EphemeralSecret, PublicKey, SharedSecret, StaticSecret};
3
4use super::{Error, Key, StaticStrongBox, StrongBox};
5
6const PRIVATE_KEY: u8 = 0;
7const PUBLIC_KEY: u8 = 1;
8
9pub struct SharedStrongBoxKey {
10	key: Option<SecretBox<StaticSecret>>,
11	public: PublicKey,
12}
13
14impl std::fmt::Debug for SharedStrongBoxKey {
15	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
16		f.debug_struct("SharedStrongBoxKey")
17			.field("public", &self.public())
18			.finish()
19	}
20}
21
22impl SharedStrongBoxKey {
23	fn new() -> Self {
24		Self::new_from_key(StaticSecret::random())
25	}
26
27	fn new_from_key(key: StaticSecret) -> Self {
28		SharedStrongBoxKey {
29			public: (&key).into(),
30			key: Some(SecretBox::new(Box::new(key))),
31		}
32	}
33
34	fn new_from_pubkey(key: [u8; 32]) -> Self {
35		SharedStrongBoxKey {
36			public: PublicKey::from(key),
37			key: None,
38		}
39	}
40
41	fn diffie_hellman(&self, pubkey: &PublicKey) -> Option<SharedSecret> {
42		self.key
43			.as_ref()
44			.map(|k| k.expose_secret().diffie_hellman(pubkey))
45	}
46
47	pub fn private(&self) -> Option<SecretSlice<u8>> {
48		self.key.as_ref().map(|k| {
49			let mut v = vec![PRIVATE_KEY];
50			v.extend_from_slice(k.expose_secret().as_bytes());
51			v.into()
52		})
53	}
54
55	pub fn public(&self) -> Vec<u8> {
56		let mut v = vec![PUBLIC_KEY];
57
58		v.extend_from_slice(self.public.as_bytes());
59
60		v
61	}
62}
63
64impl TryFrom<&Vec<u8>> for SharedStrongBoxKey {
65	type Error = Error;
66
67	fn try_from(k: &Vec<u8>) -> Result<Self, Error> {
68		k[..].try_into()
69	}
70}
71
72impl TryFrom<&[u8]> for SharedStrongBoxKey {
73	type Error = Error;
74
75	fn try_from(k: &[u8]) -> Result<Self, Error> {
76		if k.len() != 33 {
77			return Err(Error::invalid_key("invalid key length"));
78		}
79
80		let inkey: [u8; 32] = k[1..33].try_into().expect("failed to read key");
81
82		match k.first() {
83			Some(&PRIVATE_KEY) => Ok(SharedStrongBoxKey::new_from_key(StaticSecret::from(inkey))),
84			Some(&PUBLIC_KEY) => Ok(SharedStrongBoxKey::new_from_pubkey(inkey)),
85			Some(n) => Err(Error::invalid_key(format!("invalid type byte {n}"))),
86			None => Err(Error::invalid_key("how the heck did we get here?!?")),
87		}
88	}
89}
90
91#[derive(Debug)]
92pub struct SharedStrongBox {
93	key: SharedStrongBoxKey,
94}
95
96impl SharedStrongBox {
97	pub fn generate_key() -> SharedStrongBoxKey {
98		SharedStrongBoxKey::new()
99	}
100
101	pub fn new(key: SharedStrongBoxKey) -> Self {
102		Self { key }
103	}
104}
105
106impl StrongBox for SharedStrongBox {
107	#[tracing::instrument(level = "debug", skip(plaintext))]
108	fn encrypt(
109		&self,
110		plaintext: impl AsRef<[u8]>,
111		ctx: impl AsRef<[u8]> + std::fmt::Debug,
112	) -> Result<Vec<u8>, Error> {
113		let tmp_key = EphemeralSecret::random();
114		let tmp_pubkey: PublicKey = (&tmp_key).into();
115
116		let box_key = tmp_key.diffie_hellman(&self.key.public);
117
118		if !box_key.was_contributory() {
119			return Err(Error::Encryption);
120		}
121
122		let mut aad = Vec::<u8>::new();
123		aad.extend_from_slice(ctx.as_ref());
124		aad.extend_from_slice(tmp_pubkey.as_bytes());
125
126		let strong_box = StaticStrongBox::new(Box::new(box_key.to_bytes()), Vec::<Key>::new());
127		let ciphertext = strong_box.encrypt(plaintext.as_ref(), &aad)?;
128
129		Ciphertext::new(tmp_pubkey.to_bytes(), ciphertext).to_bytes()
130	}
131
132	#[tracing::instrument(level = "debug", skip(ciphertext))]
133	fn decrypt(
134		&self,
135		ciphertext: impl AsRef<[u8]>,
136		ctx: impl AsRef<[u8]> + std::fmt::Debug,
137	) -> Result<Vec<u8>, Error> {
138		let ciphertext = Ciphertext::try_from(ciphertext.as_ref())?;
139
140		let box_key = self
141			.key
142			.diffie_hellman(&PublicKey::from(ciphertext.pubkey))
143			.ok_or_else(|| {
144				Error::invalid_key("this SharedStrongBox does not have a private key")
145			})?;
146
147		if !box_key.was_contributory() {
148			return Err(Error::Encryption);
149		}
150
151		let box_key = box_key.to_bytes();
152
153		let mut aad = Vec::<u8>::new();
154		aad.extend_from_slice(ctx.as_ref());
155		aad.extend_from_slice(&ciphertext.pubkey);
156
157		let strong_box = StaticStrongBox::new(Box::new(box_key), vec![Box::new(box_key)]);
158
159		strong_box.decrypt(&ciphertext.ciphertext, &aad)
160	}
161}
162
163const CIPHERTEXT_MAGIC: [u8; 3] = [0xB2, 0xC6, 0xF5];
164
165#[derive(Clone, Debug)]
166struct Ciphertext {
167	pubkey: [u8; 32],
168	ciphertext: Vec<u8>,
169}
170
171impl Ciphertext {
172	pub(crate) fn new(pubkey: [u8; 32], ciphertext: Vec<u8>) -> Self {
173		Self { pubkey, ciphertext }
174	}
175
176	pub(crate) fn to_bytes(&self) -> Result<Vec<u8>, Error> {
177		use ciborium_ll::{Encoder, Header};
178
179		let mut v: Vec<u8> = Vec::new();
180
181		v.extend_from_slice(&CIPHERTEXT_MAGIC);
182
183		let mut enc = Encoder::from(&mut v);
184		enc.push(Header::Array(Some(2)))
185			.map_err(|e| Error::ciphertext_encoding("array", e))?;
186		enc.bytes(&self.pubkey, None)
187			.map_err(|e| Error::ciphertext_encoding("pubkey", e))?;
188		enc.bytes(&self.ciphertext, None)
189			.map_err(|e| Error::ciphertext_encoding("ciphertext", e))?;
190
191		Ok(v)
192	}
193}
194
195impl TryFrom<&[u8]> for Ciphertext {
196	type Error = Error;
197
198	fn try_from(b: &[u8]) -> Result<Self, Self::Error> {
199		use ciborium_ll::{Decoder, Header};
200
201		if b.len() < 37 {
202			return Err(Error::invalid_ciphertext("too short"));
203		}
204
205		if b[0..3] != CIPHERTEXT_MAGIC {
206			return Err(Error::invalid_ciphertext("incorrect magic"));
207		}
208
209		let mut dec = Decoder::from(&b[3..]);
210
211		let Header::Array(Some(2)) = dec
212			.pull()
213			.map_err(|e| Error::ciphertext_decoding("array", e))?
214		else {
215			return Err(Error::invalid_ciphertext("expected array"));
216		};
217
218		// CBOR's great, until you have to deal with segmented bytestrings...
219		let Header::Bytes(len) = dec
220			.pull()
221			.map_err(|e| Error::ciphertext_decoding("pubkey header", e))?
222		else {
223			return Err(Error::invalid_ciphertext("expected pubkey"));
224		};
225
226		let mut segments = dec.bytes(len);
227
228		let Ok(Some(mut segment)) = segments.pull() else {
229			return Err(Error::invalid_ciphertext("bad pubkey"));
230		};
231
232		let mut buf = [0u8; 1024];
233		let mut pubkey = [0u8; 32];
234
235		if let Some(chunk) = segment
236			.pull(&mut buf[..])
237			.map_err(|e| Error::ciphertext_decoding("pubkey", e))?
238		{
239			if chunk.len() != pubkey.len() {
240				return Err(Error::invalid_ciphertext("bad pubkey length"));
241			}
242			pubkey[..].copy_from_slice(chunk);
243		} else {
244			return Err(Error::invalid_ciphertext("short pubkey"));
245		}
246
247		// ibid.
248		let Header::Bytes(len) = dec
249			.pull()
250			.map_err(|e| Error::ciphertext_decoding("ciphertext header", e))?
251		else {
252			return Err(Error::invalid_ciphertext("expected ciphertext"));
253		};
254
255		let mut segments = dec.bytes(len);
256
257		let Ok(Some(mut segment)) = segments.pull() else {
258			return Err(Error::invalid_ciphertext("bad ciphertext"));
259		};
260
261		let mut ciphertext: Vec<u8> = Vec::new();
262
263		while let Some(chunk) = segment
264			.pull(&mut buf[..])
265			.map_err(|e| Error::ciphertext_decoding("ciphertext", e))?
266		{
267			ciphertext.extend_from_slice(chunk);
268		}
269
270		Ok(Self { pubkey, ciphertext })
271	}
272}