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}