1
//! Legacy GNOME Keyring file format low level API.
2

            
3
use std::{
4
    collections::HashMap,
5
    io::{self, Cursor, Read},
6
};
7

            
8
use endi::{Endian, ReadBytes};
9
use zeroize::{Zeroize, ZeroizeOnDrop};
10

            
11
use super::{Secret, UnlockedItem};
12
use crate::{
13
    AsAttributes, crypto,
14
    file::{Error, WeakKeyError},
15
};
16

            
17
const FILE_HEADER: &[u8] = b"GnomeKeyring\n\r\0\n";
18
const FILE_HEADER_LEN: usize = FILE_HEADER.len();
19

            
20
pub const MAJOR_VERSION: u8 = 0;
21
pub const MINOR_VERSION: u8 = 0;
22

            
23
#[derive(Debug, Zeroize, ZeroizeOnDrop)]
24
pub struct Keyring {
25
    salt: Vec<u8>,
26
    iteration_count: u32,
27
    encrypted_content: Vec<u8>,
28
    item_count: usize,
29
}
30

            
31
impl Keyring {
32
8
    pub fn decrypt_items(self, secret: &Secret) -> Result<Vec<UnlockedItem>, Error> {
33
        let (key, iv) = crypto::legacy_derive_key_and_iv(
34
8
            &**secret,
35
8
            self.key_strength(secret),
36
            &self.salt,
37
8
            self.iteration_count.try_into().unwrap(),
38
        )?;
39
16
        let decrypted = crypto::decrypt_no_padding(&self.encrypted_content, &key, iv)?;
40
16
        let (digest, content) = decrypted.split_at(16);
41
8
        if !crypto::verify_checksum_md5(digest, content) {
42
4
            return Err(Error::ChecksumMismatch);
43
        }
44
8
        self.read_items(content)
45
    }
46

            
47
8
    fn read_attributes<'a>(
48
        cursor: &mut Cursor<&'a [u8]>,
49
        count: usize,
50
    ) -> Result<impl AsAttributes + 'a, Error> {
51
8
        let mut result = HashMap::new();
52
24
        for _ in 0..count {
53
16
            let name = Self::read_string(cursor)?.ok_or_else(|| {
54
                io::Error::new(io::ErrorKind::InvalidInput, "empty attribute name")
55
            })?;
56
8
            let value = match cursor.read_u32(Endian::Big)? {
57
24
                0 => Self::read_string(cursor)?
58
8
                    .ok_or_else(|| {
59
                        io::Error::new(io::ErrorKind::InvalidInput, "empty attribute value")
60
                    })?
61
                    .to_string(),
62
                1 => cursor.read_u32(Endian::Big)?.to_string(),
63
                _ => {
64
                    return Err(io::Error::new(
65
                        io::ErrorKind::InvalidInput,
66
                        "unknown attribute type",
67
                    )
68
                    .into());
69
                }
70
            };
71
16
            result.insert(name, value);
72
        }
73
8
        Ok(result)
74
    }
75

            
76
8
    fn read_items(self, decrypted: &[u8]) -> Result<Vec<UnlockedItem>, Error> {
77
8
        let mut cursor = Cursor::new(decrypted);
78
8
        let mut items = Vec::with_capacity(self.item_count);
79
16
        for _ in 0..self.item_count {
80
24
            let display_name = Self::read_string(&mut cursor)?
81
8
                .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "empty item label"))?;
82
16
            let secret = Self::read_byte_array(&mut cursor)?
83
8
                .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "empty item secret"))?;
84
8
            let _created_time = Self::read_time(&mut cursor)?;
85
8
            let _modified_time = Self::read_time(&mut cursor)?;
86
8
            let _reserved = Self::read_string(&mut cursor)?;
87
16
            for _ in 0..4 {
88
16
                let _ = cursor.read_u32(Endian::Big)?;
89
            }
90
8
            let attribute_count = cursor.read_u32(Endian::Big)? as usize;
91
8
            let attributes = Self::read_attributes(&mut cursor, attribute_count)?;
92
16
            items.push(UnlockedItem::new(display_name, &attributes, secret));
93
8
            let acl_count = cursor.read_u32(Endian::Big)? as usize;
94
8
            Self::skip_acls(&mut cursor, acl_count)?;
95
        }
96
8
        Ok(items)
97
    }
98

            
99
8
    fn key_strength(&self, _secret: &[u8]) -> Result<(), WeakKeyError> {
100
8
        Ok(())
101
    }
102

            
103
8
    fn read_byte_array<'a>(cursor: &mut Cursor<&'a [u8]>) -> Result<Option<&'a [u8]>, Error> {
104
8
        let len = cursor.read_u32(Endian::Big)? as usize;
105
16
        if len == 0xffffffff {
106
8
            Ok(None)
107
8
        } else if len >= 0x7fffffff {
108
            Err(io::Error::new(io::ErrorKind::OutOfMemory, "").into())
109
16
        } else if len > cursor.get_ref().len() {
110
            Err(Error::NoData)
111
        } else {
112
8
            let pos = cursor.position() as usize;
113
16
            let bytes = &cursor.get_ref()[pos..pos + len];
114
16
            cursor.set_position((pos + len) as u64);
115
8
            Ok(Some(bytes))
116
        }
117
    }
118

            
119
8
    fn read_string<'a>(cursor: &mut Cursor<&'a [u8]>) -> Result<Option<&'a str>, Error> {
120
16
        match Self::read_byte_array(cursor) {
121
8
            Ok(Some(bytes)) => Ok(Some(std::str::from_utf8(bytes)?)),
122
8
            Ok(None) => Ok(None),
123
            Err(e) => Err(e),
124
        }
125
    }
126

            
127
8
    fn read_time(cursor: &mut Cursor<&[u8]>) -> Result<u64, Error> {
128
8
        let hi = cursor.read_u32(Endian::Big)? as u64;
129
8
        let lo = cursor.read_u32(Endian::Big)? as u64;
130
8
        Ok((hi << 32) | lo)
131
    }
132

            
133
8
    fn skip_hashed_items(cursor: &mut Cursor<&[u8]>, count: usize) -> Result<(), Error> {
134
16
        for _ in 0..count {
135
8
            let _id = cursor.read_u32(Endian::Big)?;
136
8
            let _type = cursor.read_u32(Endian::Big)?;
137
8
            let num_attributes = cursor.read_u32(Endian::Big)?;
138
16
            for _ in 0..num_attributes {
139
8
                let _name = Self::read_string(cursor)?;
140
8
                match cursor.read_u32(Endian::Big)? {
141
                    0 => {
142
8
                        let _value = Self::read_string(cursor);
143
                    }
144
                    1 => {
145
                        let _value = cursor.read_u32(Endian::Big);
146
                    }
147
                    _ => {
148
                        return Err(io::Error::new(
149
                            io::ErrorKind::InvalidInput,
150
                            "unknown attribute type",
151
                        )
152
                        .into());
153
                    }
154
                }
155
            }
156
        }
157
8
        Ok(())
158
    }
159

            
160
8
    fn skip_acls(cursor: &mut Cursor<&[u8]>, count: usize) -> Result<(), Error> {
161
8
        for _ in 0..count {
162
            let _flags = cursor.read_u32(Endian::Big)?;
163
            let _display_name = Self::read_string(cursor)?;
164
            let _path = Self::read_string(cursor)?;
165
            let _reserved0 = Self::read_string(cursor)?;
166
            let _reserved1 = cursor.read_u32(Endian::Big)?;
167
        }
168
8
        Ok(())
169
    }
170

            
171
8
    fn parse(data: &[u8]) -> Result<Self, Error> {
172
8
        let mut cursor = Cursor::new(data);
173
8
        let crypto = cursor.read_u8(Endian::Big)?;
174
8
        if crypto != 0 {
175
            return Err(Error::AlgorithmMismatch(crypto));
176
        }
177
8
        let hash = cursor.read_u8(Endian::Big)?;
178
8
        if hash != 0 {
179
            return Err(Error::AlgorithmMismatch(hash));
180
        }
181
8
        let _display_name = Self::read_string(&mut cursor)?;
182
8
        let _created_time = Self::read_time(&mut cursor)?;
183
8
        let _modified_time = Self::read_time(&mut cursor)?;
184
8
        let _flags = cursor.read_u32(Endian::Big)?;
185
8
        let _lock_timeout = cursor.read_u32(Endian::Big)?;
186
8
        let iteration_count = cursor.read_u32(Endian::Big)?;
187
8
        let mut salt = vec![0; 8];
188
16
        cursor.read_exact(salt.as_mut_slice())?;
189
16
        for _ in 0..4 {
190
16
            let _ = cursor.read_u32(Endian::Big)?;
191
        }
192
8
        let item_count = cursor.read_u32(Endian::Big)? as usize;
193
8
        Self::skip_hashed_items(&mut cursor, item_count)?;
194
8
        let mut size = cursor.read_u32(Endian::Big)? as usize;
195
8
        let pos = cursor.position() as usize;
196
8
        if size > cursor.get_ref()[pos..].len() {
197
            return Err(Error::NoData);
198
        }
199
16
        if !size.is_multiple_of(16) {
200
            size = (size / 16) * 16;
201
        }
202
16
        let encrypted_content = Vec::from(&cursor.get_ref()[pos..pos + size]);
203

            
204
8
        Ok(Self {
205
8
            salt,
206
            iteration_count,
207
            encrypted_content,
208
            item_count,
209
        })
210
    }
211
}
212

            
213
impl TryFrom<&[u8]> for Keyring {
214
    type Error = Error;
215

            
216
8
    fn try_from(value: &[u8]) -> Result<Self, Error> {
217
8
        let header = value.get(..FILE_HEADER.len());
218
8
        if header != Some(FILE_HEADER) {
219
            return Err(Error::FileHeaderMismatch(
220
                header.map(|x| String::from_utf8_lossy(x).to_string()),
221
            ));
222
        }
223

            
224
8
        let version = value.get(FILE_HEADER_LEN..(FILE_HEADER_LEN + 2));
225
8
        if version != Some(&[MAJOR_VERSION, MINOR_VERSION]) {
226
            return Err(Error::VersionMismatch(version.map(|x| x.to_vec())));
227
        }
228

            
229
16
        if let Some(data) = value.get((FILE_HEADER_LEN + 2)..) {
230
8
            Self::parse(data)
231
        } else {
232
            Err(Error::NoData)
233
        }
234
    }
235
}
236

            
237
#[cfg(test)]
238
mod tests {
239
    use std::path::PathBuf;
240

            
241
    use super::*;
242

            
243
    #[test]
244
    fn legacy_decrypt() -> Result<(), Error> {
245
        let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
246
            .join("fixtures")
247
            .join("legacy.keyring");
248
        let blob = std::fs::read(path)?;
249
        let keyring = Keyring::try_from(blob.as_slice())?;
250
        let secret = Secret::blob("test");
251
        let items = keyring.decrypt_items(&secret)?;
252

            
253
        assert_eq!(items.len(), 1);
254
        assert_eq!(items[0].label(), "foo");
255
        assert_eq!(items[0].secret(), Secret::blob("foo"));
256
        let attributes = items[0].attributes();
257
        assert_eq!(attributes.len(), 2); // also content-type
258
        assert_eq!(
259
            attributes
260
                .get(crate::XDG_SCHEMA_ATTRIBUTE)
261
                .map(|v| v.as_ref()),
262
            Some("org.gnome.keyring.Note")
263
        );
264

            
265
        Ok(())
266
    }
267
}