1use aead::{Aead as _, AeadCore as _, KeyInit as _};
2use anyhow::Context as _;
3use base64ct::{Base64Url, Encoding as _};
4use chacha20poly1305::XChaCha20Poly1305;
5use rand::TryRng as _;
6
7pub type SealingKey = chacha20poly1305::Key;
12
13pub fn random_alphanumeric() -> String {
16 use rand::RngExt as _;
17
18 rand::rand_core::UnwrapErr(rand::rngs::SysRng)
19 .sample_iter(&rand::distr::Alphanumeric)
20 .take(22)
21 .map(char::from)
22 .collect()
23}
24
25pub fn random_32_bytes() -> [u8; 32] {
26 let mut bytes: [u8; 32] = [0; 32];
27
28 rand::rngs::SysRng::try_fill_bytes(&mut rand::rngs::SysRng, bytes.as_mut_slice()).unwrap();
29
30 bytes
31}
32
33pub fn url_seal<T: serde::Serialize>(
36 obj: &T,
37 key: &SealingKey,
38 aad: impl AsRef<[u8]>,
39) -> anyhow::Result<String> {
40 let buf: Vec<u8> = seal(obj, key, aad)?;
41
42 Ok(Base64Url::encode_string(&buf))
43}
44
45pub fn url_unseal<T: serde::de::DeserializeOwned>(
47 envelope: impl AsRef<str>,
48 key: &chacha20poly1305::Key,
49 aad: impl AsRef<[u8]>,
50) -> Result<T, crate::misc::error::Opaque> {
51 let buf = Base64Url::decode_vec(envelope.as_ref()).map_err(|_| crate::misc::error::OPAQUE)?;
52
53 unseal(&buf, key, aad)
54}
55
56pub fn seal<T: serde::Serialize>(
62 obj: &T,
63 key: &SealingKey,
64 aad: impl AsRef<[u8]>,
65) -> anyhow::Result<Vec<u8>> {
66 let plaintext = postcard::to_stdvec(obj).context("serializing")?;
67
68 let nonce = XChaCha20Poly1305::generate_nonce(&mut aead::OsRng);
72 let ciphertext = XChaCha20Poly1305::new(key)
73 .encrypt(
74 &nonce,
75 aead::Payload {
76 msg: plaintext.as_slice(),
77 aad: aad.as_ref(),
78 },
79 )
80 .map_err(|e| anyhow::anyhow!(e))
81 .context("encrypting")?;
82
83 let mut buf = Vec::with_capacity(nonce.len() + ciphertext.len());
84 buf.extend_from_slice(&nonce);
85 buf.extend_from_slice(&ciphertext);
86
87 Ok(buf)
88}
89
90pub fn unseal<T: serde::de::DeserializeOwned>(
92 envelope: impl AsRef<[u8]>,
93 key: &SealingKey,
94 aad: impl AsRef<[u8]>,
95) -> Result<T, crate::misc::error::Opaque> {
96 let nonce_len: usize = chacha20poly1305::XNonce::len();
97 let envelope = envelope.as_ref();
98
99 if envelope.len() < nonce_len {
100 log::debug!("unseal: envelope does not contain nonce");
101 return Err(crate::misc::error::OPAQUE);
102 }
103
104 let plaintext = XChaCha20Poly1305::new(key)
105 .decrypt(
106 (&envelope[..nonce_len]).into(),
107 aead::Payload {
108 msg: &envelope[nonce_len..],
109 aad: aad.as_ref(),
110 },
111 )
112 .map_err(|err| {
113 log::debug!("unseal: decrypting: {err}");
114 crate::misc::error::OPAQUE
115 })?;
116
117 postcard::from_bytes(&plaintext).map_err(|err| {
118 log::debug!("unseal: decoding: {err}");
119 crate::misc::error::OPAQUE
120 })
121}
122
123pub trait GenericArrayExt {
128 fn len() -> usize;
129}
130
131impl<T, U: aead::generic_array::ArrayLength<T>> GenericArrayExt
132 for aead::generic_array::GenericArray<T, U>
133{
134 fn len() -> usize {
135 <U as typenum::marker_traits::Unsigned>::USIZE
136 }
137}