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