1use alloc::boxed::Box;
2#[cfg(feature = "encrypting")]
3use alloc::vec::Vec;
4
5use blake2::Blake2bMac;
6use chacha20::XChaCha20;
7use cipher::StreamCipher;
8use digest::Mac;
9use generic_array::GenericArray;
10use generic_array::sequence::Split;
11use generic_array::typenum::{U32, U56};
12use paseto_core::PasetoError;
13use paseto_core::key::HasKey;
14use paseto_core::pae::pre_auth_encode;
15use paseto_core::version::Local;
16
17use super::{LocalKey, PreAuthEncodeDigest, V4, kdf};
18
19impl LocalKey {
20 pub fn as_raw_bytes(&self) -> &[u8; 32] {
21 &self.0
22 }
23
24 pub fn from_raw_bytes(b: [u8; 32]) -> Self {
25 Self(b)
26 }
27}
28
29impl HasKey<Local> for V4 {
30 type Key = LocalKey;
31
32 fn decode(bytes: &[u8]) -> Result<LocalKey, PasetoError> {
33 bytes
34 .try_into()
35 .map(LocalKey)
36 .map_err(|_| PasetoError::InvalidKey)
37 }
38 fn encode(key: &LocalKey) -> Box<[u8]> {
39 key.0.to_vec().into_boxed_slice()
40 }
41}
42
43impl LocalKey {
44 fn keys(&self, nonce: &GenericArray<u8, U32>) -> (XChaCha20, Blake2bMac<U32>) {
45 use cipher::KeyIvInit;
46 use digest::Mac;
47
48 let (ek, n2) = kdf::<U56>(&self.0, b"paseto-encryption-key", nonce).split();
49 let ak: GenericArray<u8, U32> = kdf(&self.0, b"paseto-auth-key-for-aead", nonce);
50
51 let cipher = XChaCha20::new(&ek, &n2);
52 let mac = blake2::Blake2bMac::new_from_slice(&ak).expect("key should be valid");
53 (cipher, mac)
54 }
55}
56
57#[cfg(feature = "encrypting")]
58impl paseto_core::version::SealingVersion<Local> for V4 {
59 fn unsealing_key(key: &LocalKey) -> LocalKey {
60 LocalKey(key.0)
61 }
62
63 fn random() -> Result<LocalKey, PasetoError> {
64 let mut bytes = [0; 32];
65 getrandom::fill(&mut bytes).map_err(|_| PasetoError::CryptoError)?;
66 Ok(LocalKey(bytes))
67 }
68
69 fn nonce() -> Result<Vec<u8>, PasetoError> {
70 let mut nonce = [0; 32];
71 getrandom::fill(&mut nonce).map_err(|_| PasetoError::CryptoError)?;
72
73 let mut payload = Vec::with_capacity(64);
74 payload.extend_from_slice(&nonce);
75 Ok(payload)
76 }
77
78 fn dangerous_seal_with_nonce(
79 key: &LocalKey,
80 encoding: &'static str,
81 mut payload: Vec<u8>,
82 footer: &[u8],
83 aad: &[u8],
84 ) -> Result<Vec<u8>, PasetoError> {
85 let (nonce, ciphertext) = payload.split_at_mut(32);
86 let nonce: &[u8] = nonce;
87
88 let (mut cipher, mut mac) = key.keys(nonce.into());
89 cipher.apply_keystream(ciphertext);
90 preauth_local(&mut mac, encoding, nonce, ciphertext, footer, aad);
91 payload.extend_from_slice(&mac.finalize().into_bytes());
92
93 Ok(payload)
94 }
95}
96
97#[cfg(feature = "decrypting")]
98impl paseto_core::version::UnsealingVersion<Local> for V4 {
99 fn unseal<'a>(
100 key: &LocalKey,
101 encoding: &'static str,
102 payload: &'a mut [u8],
103 footer: &[u8],
104 aad: &[u8],
105 ) -> Result<&'a [u8], PasetoError> {
106 let (ciphertext, tag) = payload
107 .split_last_chunk_mut::<32>()
108 .ok_or(PasetoError::InvalidToken)?;
109 let (nonce, ciphertext) = ciphertext
110 .split_first_chunk_mut::<32>()
111 .ok_or(PasetoError::InvalidToken)?;
112 let nonce: &[u8; 32] = nonce;
113 let tag: &[u8; 32] = tag;
114
115 let (mut cipher, mut mac) = key.keys(nonce.into());
116 preauth_local(&mut mac, encoding, nonce, ciphertext, footer, aad);
117 mac.verify(tag.into())
118 .map_err(|_| PasetoError::CryptoError)?;
119 cipher.apply_keystream(ciphertext);
120
121 Ok(ciphertext)
122 }
123}
124
125fn preauth_local(
126 mac: &mut blake2::Blake2bMac<U32>,
127 encoding: &'static str,
128 nonce: &[u8],
129 ciphertext: &[u8],
130 footer: &[u8],
131 aad: &[u8],
132) {
133 use paseto_core::key::KeyType;
134 pre_auth_encode(
135 [
136 &[
137 "v4".as_bytes(),
138 encoding.as_bytes(),
139 Local::HEADER.as_bytes(),
140 ],
141 &[nonce],
142 &[ciphertext],
143 &[footer],
144 &[aad],
145 ],
146 PreAuthEncodeDigest(mac),
147 );
148}