paseto_v4/core/
pw_wrap.rs

1use alloc::vec::Vec;
2
3use blake2::Blake2bMac;
4use chacha20::XChaCha20;
5use cipher::StreamCipher;
6use digest::Mac;
7use generic_array::GenericArray;
8use generic_array::typenum::U32;
9use paseto_core::PasetoError;
10use paseto_core::paserk::PwWrapVersion;
11use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Unaligned, big_endian};
12
13use super::V4;
14
15fn wrap_keys(pass: &[u8], prefix: &Prefix) -> Result<(XChaCha20, Blake2bMac<U32>), PasetoError> {
16    use cipher::KeyIvInit;
17
18    let mut key = [0u8; 32];
19    prefix
20        .params
21        .pbkdf()?
22        .hash_password_into(pass, &prefix.salt, &mut key)
23        .map_err(|_| PasetoError::CryptoError)?;
24
25    let ek = kdf(&key, 0xFF);
26    let ak = kdf(&key, 0xFE);
27
28    let cipher = XChaCha20::new(&ek, (&prefix.nonce).into());
29    let mac = blake2::Blake2bMac::new_from_slice(&ak).expect("key should be valid");
30    Ok((cipher, mac))
31}
32
33#[derive(FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)]
34#[repr(C)]
35struct Prefix {
36    salt: [u8; 16],
37    params: Params,
38    nonce: [u8; 24],
39}
40
41#[derive(FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)]
42#[repr(C)]
43struct Suffix {
44    tag: [u8; 32],
45}
46
47#[derive(FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned, Clone, Copy)]
48#[repr(C)]
49pub struct Params {
50    mem: big_endian::U64,
51    time: big_endian::U32,
52    para: big_endian::U32,
53}
54
55impl Default for Params {
56    fn default() -> Self {
57        const {
58            Self {
59                mem: big_endian::U64::new(argon2::Params::DEFAULT_M_COST as u64 * 1024),
60                time: big_endian::U32::new(argon2::Params::DEFAULT_T_COST),
61                para: big_endian::U32::new(argon2::Params::DEFAULT_P_COST),
62            }
63        }
64    }
65}
66
67impl Params {
68    fn pbkdf(&self) -> Result<argon2::Argon2<'static>, PasetoError> {
69        let mem = self.mem.get();
70        if !mem.is_multiple_of(1024) {
71            return Err(PasetoError::InvalidKey);
72        }
73        let mem = mem / 1024;
74        let mem = u32::try_from(mem).map_err(|_| PasetoError::InvalidKey)?;
75
76        let params = argon2::ParamsBuilder::new()
77            .m_cost(mem)
78            .p_cost(self.para.get())
79            .t_cost(self.time.get())
80            .build()
81            .map_err(|_| PasetoError::InvalidKey)?;
82
83        Ok(argon2::Argon2::new(
84            argon2::Algorithm::Argon2id,
85            argon2::Version::V0x13,
86            params,
87        ))
88    }
89}
90
91impl PwWrapVersion for V4 {
92    type Params = Params;
93
94    fn pw_wrap_key(
95        header: &'static str,
96        pass: &[u8],
97        params: &Params,
98        mut key_data: Vec<u8>,
99    ) -> Result<Vec<u8>, PasetoError> {
100        let mut out =
101            Vec::with_capacity(size_of::<Prefix>() + key_data.len() + size_of::<Suffix>());
102        out.extend_from_slice(&[0; size_of::<Prefix>()]);
103        let prefix = Prefix::mut_from_bytes(&mut out).expect("should be correct size");
104
105        prefix.params = *params;
106        getrandom::fill(&mut prefix.salt).map_err(|_| PasetoError::CryptoError)?;
107        getrandom::fill(&mut prefix.nonce).map_err(|_| PasetoError::CryptoError)?;
108
109        let (mut cipher, mut mac) = wrap_keys(pass, prefix)?;
110        cipher.apply_keystream(&mut key_data);
111        auth(&mut mac, header, prefix, &key_data);
112
113        out.extend_from_slice(&key_data);
114        out.extend_from_slice(&mac.finalize().into_bytes());
115        Ok(out)
116    }
117
118    fn get_params(key_data: &[u8]) -> Result<Self::Params, PasetoError> {
119        let (prefix, _) = Prefix::ref_from_prefix(key_data).map_err(|_| PasetoError::InvalidKey)?;
120        Ok(prefix.params)
121    }
122
123    fn pw_unwrap_key<'key>(
124        header: &'static str,
125        pass: &[u8],
126        key_data: &'key mut [u8],
127    ) -> Result<&'key [u8], PasetoError> {
128        let (prefix, ciphertext) =
129            Prefix::mut_from_prefix(key_data).map_err(|_| PasetoError::InvalidKey)?;
130        let (ciphertext, suffix) =
131            Suffix::mut_from_suffix(ciphertext).map_err(|_| PasetoError::InvalidKey)?;
132
133        let (mut cipher, mut mac) = wrap_keys(pass, prefix)?;
134        auth(&mut mac, header, prefix, ciphertext);
135        mac.verify((&suffix.tag).into())
136            .map_err(|_| PasetoError::CryptoError)?;
137
138        cipher.apply_keystream(ciphertext);
139
140        Ok(ciphertext)
141    }
142}
143
144fn kdf(key: &[u8], sep: u8) -> GenericArray<u8, U32> {
145    use digest::Digest;
146
147    let mut mac = blake2::Blake2b::<U32>::default();
148    mac.update([sep]);
149    mac.update(key);
150    mac.finalize()
151}
152
153fn auth(
154    mac: &mut blake2::Blake2bMac<U32>,
155    header: &'static str,
156    prefix: &Prefix,
157    ciphertext: &[u8],
158) {
159    mac.update(b"k4");
160    mac.update(header.as_bytes());
161    mac.update(prefix.as_bytes());
162    mac.update(ciphertext);
163}