struct_box/
lib.rs

1//! Make any serializable struct or enum encryptable.
2//!
3//! Usage is entirely straightforward, and is best demonstrated with an example:
4//!
5//! ```rust
6//! use strong_box::{generate_key, StaticStrongBox, StrongBox};
7//! use struct_box::StructBox;
8//! use serde::{Serialize, Deserialize};
9//!
10//! #[derive(Serialize, Deserialize, StructBox)]
11//! struct SensitiveData {
12//!     data: String,
13//! }
14//!
15//! # fn main() -> Result<(), strong_box::Error> {
16//! // This is an example of how to setup a StrongBox, which is the encryption engine we use behind
17//! // the scenes
18//! let strong_key = generate_key();
19//! let strong_box = StaticStrongBox::new(strong_key, vec![strong_key]);
20//!
21//! // This is the data we want to securely encrypt
22//! let data = SensitiveData { data: "something very important and secret".to_string() };
23//!
24//! // The contents of ciphertext is a Vec<u8> that can be safely shared, stored, etc, without
25//! // worrying about anyone who doesn't have the key being able to read it
26//! let ciphertext = data.encrypt(&strong_box, b"encryption context")?;
27//!
28//! // Of course, it's not much use if we can't *decrypt* it again, though...
29//! let decrypted_data = SensitiveData::decrypt(&ciphertext, &strong_box, b"encryption context")?;
30//!
31//! assert_eq!(data.data, decrypted_data.data);
32//! # Ok(())
33//! # }
34//! ```
35//!
36//! As this derive macro is little more than a wrapper around
37//! [`strong-box`](https://docs.rs/strong-box), consult that crate's documentation for details
38//! about available StrongBox types, and the importance of the "encryption context" that is passed
39//! into the `encrypt` and `decrypt` calls.
40
41use proc_macro2::{Ident, TokenStream, TokenTree};
42use quote::quote;
43
44#[proc_macro_derive(StructBox)]
45pub fn struct_box(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
46	let mut tokens = TokenStream::from(input).into_iter().peekable();
47
48	let mut vis = TokenStream::new();
49	let mut struct_name: Option<Ident> = None;
50
51	while struct_name.is_none() {
52		match tokens.next() {
53			Some(TokenTree::Ident(ident)) => match ident.to_string().as_str() {
54				"pub" => {
55					vis.extend([TokenTree::Ident(ident.clone())]);
56					if let Some(TokenTree::Group(g)) = tokens.peek() {
57						vis.extend([TokenTree::Group(g.clone())]);
58						// Consume that next token, too
59						tokens.next();
60					}
61				}
62				"struct" | "enum" => (),
63				_ => struct_name = Some(ident.clone()),
64			},
65			Some(_) => (),
66			None => return quote! { compile_error!("no type name found?!") }.into(),
67		}
68	}
69
70	let struct_name = struct_name.unwrap();
71
72	quote! {
73		impl #struct_name {
74			#vis fn encrypt(&self, strong_box: &impl ::strong_box::StrongBox, ctx: impl AsRef<[u8]>) -> Result<Vec<u8>, ::strong_box::Error> {
75				let mut serialized = Vec::<u8>::new();
76
77				::strong_box::ciborium::into_writer(self, &mut serialized)?;
78				strong_box.encrypt(&serialized, ctx.as_ref())
79			}
80
81			#vis fn decrypt(ciphertext: impl AsRef<[u8]>, strong_box: &impl ::strong_box::StrongBox, ctx: impl AsRef<[u8]>) -> Result<Self, ::strong_box::Error> {
82				let plaintext = strong_box.decrypt(ciphertext.as_ref(), ctx.as_ref())?;
83				Ok(::strong_box::ciborium::from_reader::<Self, &[u8]>(&plaintext)?)
84			}
85		}
86	}.into()
87}