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#[derive(Debug, thiserror::Error)]
10pub enum ParseError {
11 #[error("invalid base64 length \"{length}\", expected length \"{BASE64_LEN}\"")]
13 InvalidBase64Length { length: usize },
14
15 #[error("base64 decode error")]
17 Base64Decode(#[from] base64::DecodeSliceError),
18
19 #[error("invalid key length \"{length}\", expected length \"{KEY_SIZE}\"")]
21 InvalidLength { length: usize },
22}
23
24#[derive(Debug, PartialEq, Eq, Hash, Clone, serde::Serialize, serde::Deserialize)]
31#[serde(into = "String", try_from = "String")]
32pub struct FileKey {
33 pub key: u128,
35
36 pub iv: u128,
38
39 pub meta_mac: u64,
41}
42
43impl FileKey {
44 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 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 let iv = u128::from(u64::from_be_bytes(iv.try_into().unwrap())) << 64;
60
61 let meta_mac = u64::from_be_bytes(meta_mac.try_into().unwrap());
63
64 Self { key, iv, meta_mac }
65 }
66
67 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 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}