mega/types/
file_key.rs

1use base64::Engine;
2use base64::engine::general_purpose::URL_SAFE_NO_PAD;
3
4const KEY_SIZE: usize = 16;
5pub(crate) const BASE64_LEN: usize = 43;
6const BASE64_DECODE_BUFFER_LEN: usize = (BASE64_LEN * 2).div_ceil(4) * 3;
7
8/// An error that may occur while parsing a FileKey.
9#[derive(Debug, thiserror::Error)]
10pub enum ParseError {
11    /// The base64 string is the wrong size
12    #[error("invalid base64 length \"{length}\", expected length \"{BASE64_LEN}\"")]
13    InvalidBase64Length { length: usize },
14
15    /// An error occured while decoding base64
16    #[error("base64 decode error")]
17    Base64Decode(#[from] base64::DecodeSliceError),
18
19    /// The key is the wrong size
20    #[error("invalid key length \"{length}\", expected length \"{KEY_SIZE}\"")]
21    InvalidLength { length: usize },
22}
23
24/// The encryption key for a file.
25///
26/// This includes:
27/// * The 128 bit AES key
28/// * The IV
29/// * The meta mac
30#[derive(Debug, PartialEq, Eq, Hash, Clone, serde::Serialize, serde::Deserialize)]
31#[serde(into = "String", try_from = "String")]
32pub struct FileKey {
33    /// The 128 bit AES key
34    pub key: u128,
35
36    /// The IV
37    pub iv: u128,
38
39    /// The meta mac
40    pub meta_mac: u64,
41}
42
43impl FileKey {
44    /// Make a FileKey from encoded bytes.
45    pub(crate) fn from_encoded_bytes(input: &[u8; KEY_SIZE * 2]) -> Self {
46        let key = {
47            let (n1, n2) = input.split_at(KEY_SIZE);
48
49            // Lengths are verified via split above and the function array size limit
50            let n1 = u128::from_be_bytes(n1.try_into().unwrap());
51            let n2 = u128::from_be_bytes(n2.try_into().unwrap());
52
53            n1 ^ n2
54        };
55
56        let (iv, meta_mac) = input[KEY_SIZE..].split_at(std::mem::size_of::<u64>());
57
58        // Length is verified by split above.
59        let iv = u128::from(u64::from_be_bytes(iv.try_into().unwrap())) << 64;
60
61        // Length is verified by split and length of input.
62        let meta_mac = u64::from_be_bytes(meta_mac.try_into().unwrap());
63
64        Self { key, iv, meta_mac }
65    }
66
67    /// Turn this into encoded bytes.
68    pub(crate) fn to_encoded_bytes(&self) -> [u8; KEY_SIZE * 2] {
69        let meta_mac_bytes = self.meta_mac.to_be_bytes();
70        let iv = u64::try_from(self.iv >> 64).unwrap().to_be_bytes();
71
72        let mut buffer = [0; KEY_SIZE * 2];
73        let (iv_buffer, meta_mac_buffer) =
74            buffer[KEY_SIZE..].split_at_mut(std::mem::size_of::<u64>());
75        iv_buffer.copy_from_slice(&iv);
76        meta_mac_buffer.copy_from_slice(&meta_mac_bytes);
77
78        let n2 = u128::from_be_bytes(buffer[KEY_SIZE..].try_into().unwrap());
79        let n1 = self.key ^ n2;
80        buffer[..KEY_SIZE].copy_from_slice(&n1.to_be_bytes());
81
82        buffer
83    }
84}
85
86impl std::str::FromStr for FileKey {
87    type Err = ParseError;
88
89    fn from_str(input: &str) -> Result<Self, Self::Err> {
90        let length = input.len();
91        if length != BASE64_LEN {
92            return Err(ParseError::InvalidBase64Length { length });
93        }
94
95        let mut base64_decode_buffer = [0; BASE64_DECODE_BUFFER_LEN];
96        let decoded_len = URL_SAFE_NO_PAD.decode_slice(input, &mut base64_decode_buffer)?;
97        let input = &base64_decode_buffer[..decoded_len];
98        let length = input.len();
99        if length != KEY_SIZE * 2 {
100            return Err(ParseError::InvalidLength { length });
101        }
102
103        // Length is checked above
104        Ok(Self::from_encoded_bytes(input.try_into().unwrap()))
105    }
106}
107
108impl std::fmt::Display for FileKey {
109    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
110        let mut buffer = [0; BASE64_LEN];
111        let encoded_len = URL_SAFE_NO_PAD
112            .encode_slice(self.to_encoded_bytes(), &mut buffer)
113            .expect("output buffer should never be too small");
114        let value = std::str::from_utf8(&buffer[..encoded_len]).expect("output should be utf8");
115
116        f.write_str(value)
117    }
118}
119
120impl From<FileKey> for String {
121    fn from(key: FileKey) -> Self {
122        key.to_string()
123    }
124}
125
126impl TryFrom<String> for FileKey {
127    type Error = <Self as std::str::FromStr>::Err;
128
129    fn try_from(value: String) -> Result<Self, Self::Error> {
130        value.parse()
131    }
132}
133
134#[cfg(test)]
135mod test {
136    use super::*;
137    use crate::test::*;
138
139    #[test]
140    fn round() {
141        let file_key = FileKey {
142            key: TEST_FILE_KEY_KEY_DECODED,
143            iv: TEST_FILE_KEY_IV_DECODED,
144            meta_mac: TEST_FILE_META_MAC_DECODED,
145        };
146        let file_key_str = file_key.to_string();
147        let new_file_key: FileKey = file_key_str.parse().expect("failed to parse");
148        assert!(file_key.key == new_file_key.key);
149        assert!(file_key.iv == new_file_key.iv);
150        assert!(file_key.meta_mac == new_file_key.meta_mac);
151    }
152}