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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
240
    use super::*;
241

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

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

            
264
        Ok(())
265
    }
266
}