etebase/utils.rs
1// SPDX-FileCopyrightText: © 2020 Etebase Authors
2// SPDX-License-Identifier: LGPL-2.1-only
3
4use std::convert::TryInto;
5
6use sodiumoxide::{
7 base64,
8 padding::{pad, unpad},
9};
10
11use super::error::{Error, Result};
12
13pub type StrBase64 = str;
14pub type StringBase64 = String;
15
16/// The size of the salt added to the password to derive the symmetric encryption key
17pub const SALT_SIZE: usize = 16; // sodium.crypto_pwhash_argon2id_SALTBYTES
18/// The size of the private encryption key
19pub const PRIVATE_KEY_SIZE: usize = 32; // sodium.crypto_box_curve25519xsalsa20poly1305_SECRETKEYBYTES;
20/// The size of a symmetric encryption key
21pub const SYMMETRIC_KEY_SIZE: usize = 32; // sodium.crypto_aead_xchacha20poly1305_ietf_KEYBYTES;
22/// The size of a symmetric encryption tag
23pub const SYMMETRIC_TAG_SIZE: usize = 16; // sodium.crypto_aead_xchacha20poly1305_ietf_ABYTES;
24/// The size of a symmetric encryption nonce
25pub const SYMMETRIC_NONCE_SIZE: usize = 24; // sodium.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES;
26
27/// Returns a buffer filled with cryptographically random bytes.
28///
29/// # Examples
30///
31/// ```
32/// use etebase::utils::randombytes;
33///
34/// let a = randombytes(5);
35/// assert_eq!(5, a.len());
36///
37/// let b = randombytes(0);
38/// assert!(b.is_empty());
39/// ```
40pub fn randombytes(size: usize) -> Vec<u8> {
41 sodiumoxide::randombytes::randombytes(size)
42}
43
44/// A version of [`randombytes`] that returns a fixed-size array instead of a Vec.
45///
46/// # Examples
47///
48/// ```
49/// use etebase::utils::randombytes_array;
50///
51/// // Explicitly specifying the length as a type generic
52/// let a = randombytes_array::<5>();
53///
54/// // Letting the length be inferred from the result type
55/// let b: [u8; 10] = randombytes_array();
56/// ```
57pub fn randombytes_array<const N: usize>() -> [u8; N] {
58 sodiumoxide::randombytes::randombytes(N)
59 .try_into()
60 .expect("randombytes() returned a Vec with wrong size")
61}
62
63/// Return a buffer filled with deterministically cryptographically random bytes
64///
65/// This function is similar to [`randombytes`] but always returns the same data for the same seed.
66/// Useful for testing purposes.
67///
68/// # Arguments:
69/// * `seed` - the seed to generate the random data from
70/// * `size` - the size of the returned buffer (in bytes)
71///
72/// # Examples
73///
74/// ```
75/// use etebase::utils::randombytes_deterministic;
76///
77/// let seed = [42; 32];
78///
79/// // Equal seeds produce equal sequences, regardless of length
80/// let a = randombytes_deterministic(10, &seed);
81/// let b = randombytes_deterministic(5, &seed);
82///
83/// assert_eq!(a[..5], b);
84///
85/// // Different seeds produce different sequences
86/// let c = randombytes_deterministic(10, &[0; 32]);
87///
88/// assert_ne!(a, c);
89/// assert_eq!(c, &[5, 67, 208, 128, 105, 110, 24, 70, 104, 100]);
90/// ```
91pub fn randombytes_deterministic(size: usize, seed: &[u8; 32]) -> Vec<u8> {
92 // Not exactly like the sodium randombytes_deterministic but close enough
93 let nonce =
94 sodiumoxide::crypto::stream::xchacha20::Nonce(*b"LibsodiumDRG\0\0\0\0\0\0\0\0\0\0\0\0");
95 let key = sodiumoxide::crypto::stream::xchacha20::Key(*seed);
96
97 sodiumoxide::crypto::stream::xchacha20::stream(size, &nonce, &key)
98}
99
100/// A constant-time comparison function. Returns `true` if `x` and `y` are equal.
101///
102/// Use this when comparing secret data in order to prevent side-channel attacks.
103///
104/// # Examples
105///
106/// ```
107/// use etebase::utils::memcmp;
108///
109/// fn validate_password(input: &[u8]) -> Result<(), ()> {
110/// let password = b"hunter2";
111///
112/// if memcmp(input, password) {
113/// Ok(())
114/// } else {
115/// Err(())
116/// }
117/// }
118///
119/// assert_eq!(Err(()), validate_password(b"letmein"));
120/// assert_eq!(Err(()), validate_password(b""));
121/// assert_eq!(Ok(()), validate_password(b"hunter2"));
122/// ```
123pub fn memcmp(x: &[u8], y: &[u8]) -> bool {
124 sodiumoxide::utils::memcmp(x, y)
125}
126
127/// Converts a Base64 URL encoded string to a Vec of bytes.
128///
129/// # Examples
130///
131/// ```
132/// use etebase::utils::from_base64;
133///
134/// let data = "SGVsbG8_IFdvcmxkIQ";
135/// let decoded = from_base64(data);
136///
137/// assert_eq!(Ok(b"Hello? World!".to_vec()), decoded);
138///
139/// assert_eq!(Ok(b"".to_vec()), from_base64(""));
140/// ```
141pub fn from_base64(string: &StrBase64) -> Result<Vec<u8>> {
142 match base64::decode(string, base64::Variant::UrlSafeNoPadding) {
143 Ok(bytes) => Ok(bytes),
144 Err(_) => Err(Error::Base64("Failed decoding base64 string")),
145 }
146}
147
148/// Convert a buffer to a Base64 URL encoded string
149///
150/// # Examples
151///
152/// ```
153/// use etebase::utils::to_base64;
154///
155/// let data = b"Hello? World!";
156/// let encoded = to_base64(data);
157///
158/// assert_eq!(Ok("SGVsbG8_IFdvcmxkIQ"), encoded.as_deref());
159///
160/// assert_eq!(Ok(""), to_base64(b"").as_deref());
161/// ```
162pub fn to_base64(bytes: &[u8]) -> Result<StringBase64> {
163 Ok(base64::encode(bytes, base64::Variant::UrlSafeNoPadding))
164}
165
166/// Fisher–Yates shuffle - an unbiased shuffler
167///
168/// Shuffles the passed slice in-place and returns the new indices of all input items.
169///
170/// # Examples
171///
172/// ```no_compile
173/// let mut data = vec!["foo", "bar", "baz"];
174///
175/// let ret = shuffle(&mut data);
176/// // Let's assume the data was randomly shuffled like this:
177/// # data = vec!["bar", "baz", "foo"];
178/// assert_eq!(data, &["bar", "baz", "foo"]);
179///
180/// // The first element (foo) is now at index 2, the second is at index 0, the third is at index 1
181/// assert_eq!(ret, &[2, 0, 1]);
182/// ```
183pub(crate) fn shuffle<T>(a: &mut [T]) -> Vec<usize> {
184 let len = a.len();
185 let mut shuffled_indices: Vec<usize> = (0..len).collect();
186
187 for i in 0..len {
188 let j = i + sodiumoxide::randombytes::randombytes_uniform((len - i) as u32) as usize;
189 a.swap(i, j);
190 shuffled_indices.swap(i, j);
191 }
192
193 let mut ret = vec![0; len];
194 for i in 0..len {
195 ret[shuffled_indices[i]] = i;
196 }
197 ret
198}
199
200/// Return the recommended padding length for a buffer of specific length
201///
202/// Padding data before encrypting it is important for preventing fingerprint analysis attacks.
203/// This function aims to return the optimal balance between space efficiently and fingerprint
204/// resistance. The returned values may change between versions.
205///
206/// # Arguments:
207/// * `length` - the length of the buffer to pad
208pub fn get_padding(length: u32) -> u32 {
209 // Use the padme padding scheme for efficiently
210 // https://www.petsymposium.org/2019/files/papers/issue4/popets-2019-0056.pdf
211
212 // We want a minimum pad size of 4k
213 if length < (1 << 14) {
214 let size = (1 << 10) - 1;
215 // We add 1 so we always have some padding
216 return (length | size) + 1;
217 }
218
219 let e = (length as f64).log2().floor();
220 let s = (e.log2().floor() as u32) + 1;
221 let last_bits = (e as u32) - s;
222 let bit_mask = (1 << last_bits) - 1;
223
224 (length + bit_mask) & !bit_mask
225}
226
227// FIXME: we should properly pad the meta and probably change these functions
228pub(crate) fn buffer_pad_small(buf: &[u8]) -> Result<Vec<u8>> {
229 let len = buf.len();
230 let padding = len + 1;
231
232 buffer_pad_fixed(buf, padding)
233}
234
235pub(crate) fn buffer_pad(buf: &[u8]) -> Result<Vec<u8>> {
236 let len = buf.len();
237 let padding = get_padding(len as u32) as usize;
238
239 buffer_pad_fixed(buf, padding)
240}
241
242pub(crate) fn buffer_unpad(buf: &[u8]) -> Result<Vec<u8>> {
243 let len = buf.len();
244
245 // We pass the buffer's length as the block size because due to padme there's always some variable-sized padding.
246 buffer_unpad_fixed(buf, len)
247}
248
249pub(crate) fn buffer_pad_fixed(buf: &[u8], blocksize: usize) -> Result<Vec<u8>> {
250 let len = buf.len();
251 let missing = blocksize - (len % blocksize);
252 let padding = len + missing;
253 let mut ret = vec![0; padding];
254 ret[..len].copy_from_slice(buf);
255
256 pad(&mut ret[..], len, blocksize).map_err(|_| Error::Padding("Failed padding"))?;
257
258 Ok(ret)
259}
260
261pub(crate) fn buffer_unpad_fixed(buf: &[u8], blocksize: usize) -> Result<Vec<u8>> {
262 let len = buf.len();
263 if len == 0 {
264 return Ok(vec![0; 0]);
265 }
266
267 let mut buf = buf.to_vec();
268
269 let new_len =
270 unpad(&buf[..], len, blocksize).map_err(|_| Error::Padding("Failed unpadding"))?;
271 buf.truncate(new_len);
272 Ok(buf)
273}
274
275/// A trait for serializing and deserializing to MsgPack
276pub trait MsgPackSerilization {
277 /// The type of the struct implementing this trait
278 type Output;
279
280 /// Convert self to a msgpack encoded buffer
281 fn to_msgpack(&self) -> Result<Vec<u8>>;
282
283 /// Create the struct from a MsgPack encoded buffer
284 ///
285 /// # Arguments:
286 /// * `data` - the MsgPack buffer
287 fn from_msgpack(data: &[u8]) -> Result<Self::Output>;
288}
289
290#[cfg(test)]
291mod tests {
292 #[test]
293 fn padding() {
294 crate::init().unwrap();
295
296 // Because of how we use padding (unpadding) we need to make sure padding is always larger than the content
297 // Otherwise we risk the unpadder to fail thinking it should unpad when it shouldn't.
298
299 for i in 1..(1 << 14) {
300 if super::get_padding(i) <= i {
301 println!("Yo");
302 assert_eq!(format!("Failed for {}", i), "");
303 }
304 }
305
306 assert_eq!(super::get_padding(2343242), 2359296);
307 }
308
309 #[test]
310 fn pad_unpad() {
311 crate::init().unwrap();
312
313 let buf = [0; 1076];
314 let padded = super::buffer_pad(&buf).unwrap();
315 let unpadded = super::buffer_unpad(&padded[..]).unwrap();
316 assert_eq!(unpadded, &buf[..]);
317 }
318
319 #[test]
320 fn pad_unpad_fixed() {
321 crate::init().unwrap();
322
323 let blocksize = 32;
324 for i in 0..(blocksize * 2) {
325 let buf = vec![60; i];
326 let padded = super::buffer_pad_fixed(&buf, blocksize).unwrap();
327 let unpadded = super::buffer_unpad_fixed(&padded[..], blocksize).unwrap();
328 assert_eq!(unpadded, &buf[..]);
329 }
330 }
331}