paseto_v1/core/
local.rs

1use alloc::boxed::Box;
2#[cfg(feature = "encrypting")]
3use alloc::vec::Vec;
4
5use cipher::StreamCipher;
6use generic_array::GenericArray;
7use generic_array::sequence::Split;
8use generic_array::typenum::{U16, U32};
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, V1};
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 V1 {
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 nonce: &GenericArray<u8, U32> = nonce.into();
47        let (n1, n2) = nonce.split();
48
49        let ek = kdf(&self.0, b"paseto-encryption-key", n1);
50        let ak = kdf(&self.0, b"paseto-auth-key-for-aead", n1);
51
52        let cipher = ctr::Ctr64BE::<aes::Aes256>::new(&ek, n2);
53        let mac = hmac::Hmac::new_from_slice(&ak).expect("key should be valid");
54        (cipher, mac)
55    }
56}
57
58#[cfg(feature = "encrypting")]
59impl paseto_core::version::SealingVersion<Local> for V1 {
60    fn unsealing_key(key: &LocalKey) -> LocalKey {
61        LocalKey(key.0)
62    }
63
64    fn random() -> Result<LocalKey, PasetoError> {
65        let mut bytes = [0; 32];
66        getrandom::fill(&mut bytes).map_err(|_| PasetoError::CryptoError)?;
67        Ok(LocalKey(bytes))
68    }
69
70    fn nonce() -> Result<Vec<u8>, PasetoError> {
71        let mut nonce = [0; 32];
72        getrandom::fill(&mut nonce).map_err(|_| PasetoError::CryptoError)?;
73
74        let mut payload = Vec::with_capacity(80);
75        payload.extend_from_slice(&nonce);
76        Ok(payload)
77    }
78
79    fn dangerous_seal_with_nonce(
80        key: &LocalKey,
81        encoding: &'static str,
82        mut payload: Vec<u8>,
83        footer: &[u8],
84        aad: &[u8],
85    ) -> Result<Vec<u8>, PasetoError> {
86        if !aad.is_empty() {
87            return Err(PasetoError::ClaimsError);
88        }
89
90        let (nonce, ciphertext) = payload
91            .split_first_chunk_mut::<32>()
92            .ok_or(PasetoError::InvalidToken)?;
93
94        let mut n: hmac::Hmac<sha2::Sha384> =
95            digest::Mac::new_from_slice(nonce).expect("all sized keys are valid with hmac");
96        n.update(ciphertext);
97        *nonce = n.finalize().into_bytes()[0..32]
98            .try_into()
99            .expect("nonce is 32 bytes");
100
101        let (mut cipher, mut mac) = key.keys(nonce);
102        cipher.apply_keystream(ciphertext);
103        preauth_local(&mut mac, encoding, nonce, ciphertext, footer);
104        payload.extend_from_slice(&mac.finalize().into_bytes());
105
106        Ok(payload)
107    }
108}
109
110#[cfg(feature = "decrypting")]
111impl paseto_core::version::UnsealingVersion<Local> for V1 {
112    fn unseal<'a>(
113        key: &LocalKey,
114        encoding: &'static str,
115        payload: &'a mut [u8],
116        footer: &[u8],
117        aad: &[u8],
118    ) -> Result<&'a [u8], PasetoError> {
119        if !aad.is_empty() {
120            return Err(PasetoError::ClaimsError);
121        }
122
123        let len = payload.len();
124        if len < 80 {
125            return Err(PasetoError::InvalidToken);
126        }
127
128        let (ciphertext, tag) = payload
129            .split_last_chunk_mut::<48>()
130            .ok_or(PasetoError::InvalidToken)?;
131        let (nonce, ciphertext) = ciphertext
132            .split_first_chunk_mut::<32>()
133            .ok_or(PasetoError::InvalidToken)?;
134
135        let (mut cipher, mut mac) = key.keys(nonce);
136        preauth_local(&mut mac, encoding, nonce, ciphertext, footer);
137        mac.verify_slice(tag)
138            .map_err(|_| PasetoError::CryptoError)?;
139        cipher.apply_keystream(ciphertext);
140
141        Ok(ciphertext)
142    }
143}
144
145fn kdf(key: &[u8], sep: &'static [u8], nonce: &GenericArray<u8, U16>) -> GenericArray<u8, U32> {
146    let mut output = GenericArray::<u8, U32>::default();
147    hkdf::Hkdf::<sha2::Sha384>::new(Some(nonce), key)
148        .expand(sep, &mut output)
149        .unwrap();
150    output
151}
152
153fn preauth_local(
154    mac: &mut hmac::Hmac<sha2::Sha384>,
155    encoding: &'static str,
156    nonce: &[u8],
157    ciphertext: &[u8],
158    footer: &[u8],
159) {
160    use paseto_core::key::KeyType;
161    struct Context<'a>(&'a mut hmac::Hmac<sha2::Sha384>);
162    impl WriteBytes for Context<'_> {
163        fn write(&mut self, slice: &[u8]) {
164            self.0.update(slice);
165        }
166    }
167
168    pre_auth_encode(
169        [
170            &[
171                "v1".as_bytes(),
172                encoding.as_bytes(),
173                Local::HEADER.as_bytes(),
174            ],
175            &[nonce],
176            &[ciphertext],
177            &[footer],
178        ],
179        Context(mac),
180    );
181}