strong_box/
shared_strong_box.rs1use 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 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 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}