mega/utils/
mod.rs

1use std::collections::HashMap;
2
3use aes::cipher::generic_array::GenericArray;
4use aes::cipher::{BlockEncrypt, KeyInit};
5use aes::Aes128;
6use aes_gcm::{AeadInPlace, Aes128Gcm};
7use base64::prelude::{Engine, BASE64_URL_SAFE_NO_PAD};
8use cipher::BlockDecrypt;
9use hkdf::Hkdf;
10use pbkdf2::pbkdf2_hmac_array;
11use rand::distributions::{Alphanumeric, DistString};
12use serde::{Deserialize, Serialize};
13use sha2::{Sha256, Sha512};
14
15pub mod rsa;
16
17use crate::http::UserSession;
18use crate::protocol::commands::UserAttributesResponse;
19use crate::Result;
20
21/// Represents storage quotas from MEGA.
22#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
23pub struct StorageQuotas {
24    /// The amount of memory used (in bytes).
25    pub memory_used: u64,
26    /// The total amount of memory, used or unused (in bytes).
27    pub memory_total: u64,
28}
29
30pub(crate) fn prepare_key_v1(password: &[u8]) -> [u8; 16] {
31    let mut data = GenericArray::from([
32        0x93u8, 0xC4, 0x67, 0xE3, 0x7D, 0xB0, 0xC7, 0xA4, 0xD1, 0xBE, 0x3F, 0x81, 0x01, 0x52, 0xCB,
33        0x56,
34    ]);
35
36    for _ in 0..65536 {
37        for chunk in password.chunks(16) {
38            let mut key = [0u8; 16];
39            key[0..chunk.len()].copy_from_slice(chunk);
40            let aes = Aes128::new(&GenericArray::from(key));
41            aes.encrypt_block(&mut data);
42        }
43    }
44
45    data.into()
46}
47
48pub(crate) fn prepare_key_v2(password: &[u8], salt: &[u8]) -> [u8; 32] {
49    pbkdf2_hmac_array::<Sha512, 32>(password, salt, 100_000)
50}
51
52pub(crate) fn encrypt_ebc_in_place(key: &[u8], data: &mut [u8]) {
53    let aes = Aes128::new(key.into());
54    for block in data.chunks_mut(16) {
55        aes.encrypt_block(block.into())
56    }
57}
58
59pub(crate) fn decrypt_ebc_in_place(key: &[u8], data: &mut [u8]) {
60    let aes = Aes128::new(key.into());
61    for block in data.chunks_mut(16) {
62        aes.decrypt_block(block.into())
63    }
64}
65
66pub(crate) fn unmerge_key_mac(key: &mut [u8]) {
67    let (fst, snd) = key.split_at_mut(16);
68    for (a, b) in fst.iter_mut().zip(snd) {
69        *a ^= *b;
70    }
71}
72
73pub(crate) fn merge_key_mac(key: &mut [u8]) {
74    let (fst, snd) = key.split_at_mut(16);
75    for (a, b) in fst.iter_mut().zip(snd) {
76        *a ^= *b;
77    }
78}
79
80pub(crate) fn random_string(len: usize) -> String {
81    let mut rng = rand::thread_rng();
82    Alphanumeric.sample_string(&mut rng, len)
83}
84
85#[repr(u8)]
86#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
87enum KeysAttrTag {
88    Version = 1,
89    CreationTime = 2,
90    Identity = 3,
91    Generation = 4,
92    Attr = 5,
93    PrivEd25519 = 16,
94    PrivCu25519 = 17,
95    PrivRsa = 18,
96    AuthringEd25519 = 32,
97    AuthringCu25519 = 33,
98    ShareKeys = 48,
99    PendingOutshares = 64,
100    PendingInshares = 65,
101    Backups = 80,
102    Warnings = 96,
103}
104
105impl TryFrom<u8> for KeysAttrTag {
106    type Error = ();
107
108    fn try_from(value: u8) -> Result<Self, Self::Error> {
109        match value {
110            1 => Ok(KeysAttrTag::Version),
111            2 => Ok(KeysAttrTag::CreationTime),
112            3 => Ok(KeysAttrTag::Identity),
113            4 => Ok(KeysAttrTag::Generation),
114            5 => Ok(KeysAttrTag::Attr),
115            16 => Ok(KeysAttrTag::PrivEd25519),
116            17 => Ok(KeysAttrTag::PrivCu25519),
117            18 => Ok(KeysAttrTag::PrivRsa),
118            32 => Ok(KeysAttrTag::AuthringEd25519),
119            33 => Ok(KeysAttrTag::AuthringCu25519),
120            48 => Ok(KeysAttrTag::ShareKeys),
121            64 => Ok(KeysAttrTag::PendingOutshares),
122            65 => Ok(KeysAttrTag::PendingInshares),
123            80 => Ok(KeysAttrTag::Backups),
124            96 => Ok(KeysAttrTag::Warnings),
125            _ => Err(()),
126        }
127    }
128}
129
130pub(crate) fn extract_share_keys(
131    session: &UserSession,
132    attr: &UserAttributesResponse,
133) -> Result<HashMap<String, Vec<u8>>> {
134    let hkdf = Hkdf::<Sha256>::new(None, &session.key);
135    let mut derived_key = [0u8; 16];
136    hkdf.expand(&[1], &mut derived_key)?;
137
138    let attr_value = BASE64_URL_SAFE_NO_PAD.decode(&attr.attr_value)?;
139
140    // the attribute value consists of:
141    // - 1 byte guaranteed to be `20`.
142    // - 1 byte that is declared reserved (usually `0`).
143    // - 12 bytes of IV data suitable for the AES-128-GCM algorithm.
144    // - arbitrarily-sized payload to be decrypted using AES-128-GCM.
145
146    assert_eq!(attr_value[0], 20);
147    let (iv, data) = attr_value[2..].split_at(12);
148    let gcm = Aes128Gcm::new(derived_key.as_slice().into());
149    let mut data = data.to_vec();
150    gcm.decrypt_in_place(iv.into(), &[], &mut data)?;
151
152    // this attribute's payload consists of a variable number of "packets"
153    // that are prefixed with a tag (1 byte) and the length of their payload (3 bytes).
154
155    let mut share_keys = HashMap::default();
156
157    let mut cursor = 0;
158    while cursor < data.len() {
159        let Ok(tag) = KeysAttrTag::try_from(data[cursor]) else {
160            continue;
161        };
162        let len = (usize::from(data[cursor + 1]) << 16)
163            + (usize::from(data[cursor + 2]) << 8)
164            + usize::from(data[cursor + 3]);
165        cursor += 4;
166
167        if tag == KeysAttrTag::ShareKeys {
168            // The share keys section consists of multiple 23 bytes long chunks,
169            // each corresponding to a single share key.
170            // Each chunk consists of:
171            // - the shared node's handle (6 bytes)
172            // - the node's share key (16 bytes)
173            // - trust flag (1 byte)
174            //   (this flag seems to have to do with whether the key has been "exposed" in some way?)
175
176            for chunk in data[cursor..(cursor + len)].chunks(23) {
177                let (handle, rest) = chunk.split_at(6);
178                let (share_key, _trust) = rest.split_at(16);
179                let handle = BASE64_URL_SAFE_NO_PAD.encode(handle);
180                share_keys.insert(handle, share_key.to_vec());
181            }
182
183            break;
184        }
185
186        cursor += len;
187    }
188
189    Ok(share_keys)
190}
191
192pub(crate) fn extract_attachments(attrs_str: &str) -> (Option<String>, Option<String>) {
193    let mut thumbnail_handle = None;
194    let mut preview_image_handle = None;
195
196    // format: {bundle_id}:{attr_type}*{attr_handle}
197    let attrs = attrs_str
198        .split('/')
199        .filter_map(|it| it.split_once(':')?.1.split_once('*'));
200
201    for (kind, handle) in attrs {
202        match kind {
203            "0" => {
204                thumbnail_handle = Some(handle.to_string());
205            }
206            "1" => {
207                preview_image_handle = Some(handle.to_string());
208            }
209            _ => continue,
210        }
211    }
212
213    (thumbnail_handle, preview_image_handle)
214}
215
216/// Produces an infinite iterator of all the consecutive chunk bounds.
217#[allow(unused)]
218pub(crate) fn chunks_iterator() -> impl Iterator<Item = (u64, u64)> {
219    std::iter::successors(Some(131_072), |&(mut chunk_size): &u64| {
220        if chunk_size < 1_048_576 {
221            chunk_size += 131_072;
222        }
223        Some(chunk_size)
224    })
225    .scan(0, |start, chunk_size| {
226        let bounds = (*start, *start + chunk_size - 1);
227        *start = bounds.1 + 1;
228        Some(bounds)
229    })
230}
231
232#[cfg(test)]
233mod tests {
234    use super::*;
235
236    fn test_buffer(size: usize, start: usize, step: usize) -> Vec<u8> {
237        (0..size)
238            .map(|i| u8::try_from((start + i * step) % 255).unwrap())
239            .collect()
240    }
241
242    #[test]
243    fn prepare_key_v1_8_bytes_test() {
244        let buffer = test_buffer(8, 0, 1);
245        let result = prepare_key_v1(buffer.as_slice());
246        let result = hex::encode(result);
247
248        assert_eq!(result.as_str(), "c4589a459956887caf0b408635c3c03b");
249    }
250
251    #[test]
252    fn prepare_key_v1_10_bytes_test() {
253        let buffer = test_buffer(10, 0, 1);
254        let result = prepare_key_v1(buffer.as_slice());
255        let result = hex::encode(result);
256
257        assert_eq!(result.as_str(), "59930b1c55d783ac77df4c4ff261b0f1");
258    }
259
260    #[test]
261    fn prepare_key_v1_64_bytes_test() {
262        let buffer = test_buffer(64, 0, 1);
263        let result = prepare_key_v1(buffer.as_slice());
264        let result = hex::encode(result);
265
266        assert_eq!(result.as_str(), "83bd84689f057f9ed9834b3ecb81d80e");
267    }
268}