sequoia_gpg_agent/
keyinfo.rs

1//! KeyInfo and related data structures.
2//!
3//! This module defines the [`KeyInfo`] family of data structures.
4//! [`KeyInfoList`] is a collection of `KeyInfo`, and is returned by
5//! [`Agent::list_keys`](crate::Agent::list_keys).  `KeyInfoList` can
6//! be iterated over, and individual keys can be efficiently looked up
7//! by their keygrip.  Note: a keygrip is not an OpenPGP fingerprint.
8//! gpg-agent does not support addressing keys by their OpenPGP
9//! fingerprint; they can only be looked up by their keygrip.
10use std::borrow::Borrow;
11use std::collections::HashMap;
12use std::str::FromStr;
13
14use sequoia_openpgp as openpgp;
15use openpgp::packet::key;
16use openpgp::packet::Key;
17
18use sequoia_ipc as ipc;
19use ipc::Keygrip;
20
21use anyhow::Context;
22
23use crate::Result;
24
25/// KeyInfo-related errors.
26#[derive(thiserror::Error, Debug)]
27#[non_exhaustive]
28pub enum Error {
29    #[error("Error parsing keyinfo data: {0}")]
30    ParseError(String),
31}
32
33/// The type of key.
34#[derive(Debug, Clone, PartialEq, Eq)]
35#[non_exhaustive]
36pub enum KeyType {
37    /// Regular key stored on disk,
38    Regular,
39    /// Key is stored on a smartcard (token),
40    Smartcard,
41    /// Unknown type,
42    Unknown,
43    /// Key type is missing.
44    Missing,
45}
46
47/// A key's protection, if any.
48#[derive(Debug, Clone, PartialEq, Eq)]
49#[non_exhaustive]
50pub enum KeyProtection {
51    /// The key is protected with a passphrase.
52    Protected,
53    /// The key is not protected.
54    NotProtected,
55    /// Unknown protection.
56    UnknownProtection,
57}
58
59/// A key's flags, if any.
60#[derive(Debug, Clone, PartialEq, Eq)]
61#[non_exhaustive]
62enum KeyFlag {
63    /// The key has been disabled.
64    Disabled,
65    /// The key is listed in sshcontrol.
66    SSHControl,
67    /// Use of the key needs to be confirmed.
68    ConfirmationRequired,
69}
70
71/// Information about a key.
72///
73/// Returned by `Agent::list_keys`.
74#[derive(Debug, Clone, PartialEq, Eq)]
75pub struct KeyInfo {
76    keygrip: Keygrip,
77
78    /// TYPE is describes the type of the key:
79    ///
80    ///  - 'D' - Regular key stored on disk,
81    ///  - 'T' - Key is stored on a smartcard (token),
82    ///  - 'X' - Unknown type,
83    ///  - '-' - Key is missing.
84    keytype: KeyType,
85
86    /// SERIALNO is an ASCII string with the serial number of the
87    /// smartcard.  If the serial number is not known a single dash
88    /// '-' is used instead.
89    serialno: Option<String>,
90
91    /// If the key is on a smartcard, this is used to distinguish the
92    /// keys on the smartcard.  If it is not known a dash is used
93    /// instead.
94    ///
95    /// Example: `OPENPGP.1`.
96    idstr: Option<String>,
97
98    /// If the passphrase for the key was found in the key cache.
99    passphrase_cached: bool,
100
101    /// The key protection type:
102    ///
103    ///   - 'P' - The key is protected with a passphrase,
104    ///   - 'C' - The key is not protected,
105    ///   - '-' - Unknown protection.
106    protection: KeyProtection,
107
108    /// The TTL in seconds for the key or None if not available.
109    ttl: Option<u64>,
110
111    /// The key's flag.
112    flags: Vec<KeyFlag>,
113}
114
115impl KeyInfo {
116    /// Returns the key's keygrip.
117    pub fn keygrip(&self) -> &ipc::Keygrip {
118        &self.keygrip
119    }
120
121    /// Returns the key's type.
122    pub fn keytype(&self) -> KeyType {
123        self.keytype.clone()
124    }
125
126    /// Returns the serial number of the smartcard.
127    ///
128    /// If the key is not on a smartcard, this returns `None`.
129    pub fn serialno(&self) -> Option<&str> {
130        self.serialno.as_deref()
131    }
132
133    /// Returns the key's identifier on the smartcard.
134    ///
135    /// If the key is not on a smartcard, this returns `None`.
136    /// Example: `OPENPGP.1`.
137    pub fn idstr(&self) -> Option<&str> {
138        self.idstr.as_deref()
139    }
140
141    /// Returns whether the passphrase for the key was found in the
142    /// key cache.
143    pub fn passphrase_cached(&self) -> bool {
144        self.passphrase_cached
145    }
146
147    /// Returns the key's protection, if any.
148    pub fn protection(&self) -> &KeyProtection {
149        &self.protection
150    }
151
152    /// Returns the TTL in seconds for the key.
153    ///
154    /// If not available, returns `None`.
155    pub fn ttl(&self) -> Option<u64> {
156        self.ttl
157    }
158
159    /// Returns whether the key has been disabled.
160    pub fn key_disabled(&self) -> bool {
161        self.flags.contains(&KeyFlag::Disabled)
162    }
163
164    /// Returns whether the key is listed in sshcontrol.
165    pub fn in_ssh_control(&self) -> bool {
166        self.flags.contains(&KeyFlag::SSHControl)
167    }
168
169    /// Returns whether use of the key needs to be confirmed.
170    pub fn confirmation_required(&self) -> bool {
171        self.flags.contains(&KeyFlag::ConfirmationRequired)
172    }
173
174    /// Parses a single keyinfo entry.
175    ///
176    /// An entry is of the form:
177    ///
178    /// ```text
179    /// KEYINFO <keygrip> <type> <serialno> <idstr> <cached> <protection> <ssh-fpr> <ttl> <flags>
180    /// ```
181    ///
182    /// Note: `keyinfo` should be the whole line without the leading
183    /// `KEYINFO`.
184    pub(crate) fn parse(keyinfo: &str) -> Result<Self> {
185        let mut iter = keyinfo.split(' ');
186
187        let Some(keygrip) = iter.next() else {
188            return Err(Error::ParseError("KEYGRIP field missing".into()).into());
189        };
190        let keygrip = Keygrip::from_str(keygrip)
191            .with_context(|| {
192                format!("Parsing {:?} as a keygrip", keygrip)
193            })?;
194
195        // TYPE is describes the type of the key:
196        //     'D' - Regular key stored on disk,
197        //     'T' - Key is stored on a smartcard (token),
198        //     'X' - Unknown type,
199        //     '-' - Key is missing.
200        let Some(keytype) = iter.next() else {
201            return Err(Error::ParseError("TYPE field missing".into()).into());
202        };
203        let keytype = match keytype {
204            "D" => KeyType::Regular,
205            "T" => KeyType::Smartcard,
206            "-" => KeyType::Missing,
207            "X"|_ => KeyType::Unknown,
208        };
209
210        // SERIALNO is an ASCII string with the serial number of the
211        //          smartcard.  If the serial number is not known a single
212        //          dash '-' is used instead.
213        let Some(serialno) = iter.next() else {
214            return Err(Error::ParseError("SERIALNO field missing".into()).into());
215        };
216        let serialno = if serialno == "-" {
217            None
218        } else {
219            Some(serialno.to_string())
220        };
221
222        // IDSTR is the IDSTR used to distinguish keys on a smartcard.  If it
223        //       is not known a dash is used instead.
224        let Some(idstr) = iter.next() else {
225            return Err(Error::ParseError("IDSTR field missing".into()).into());
226        };
227        let idstr = if idstr == "-" {
228            None
229        } else {
230            Some(idstr.to_string())
231        };
232
233        // CACHED is 1 if the passphrase for the key was found in the key cache.
234        //        If not, a '-' is used instead.
235        let Some(cached) = iter.next() else {
236            return Err(Error::ParseError("CACHED field missing".into()).into());
237        };
238        let passphrase_cached = match cached {
239            "1" => true,
240            _ => false,
241        };
242
243        // PROTECTION describes the key protection type:
244        //     'P' - The key is protected with a passphrase,
245        //     'C' - The key is not protected,
246        //     '-' - Unknown protection.
247        let Some(protection) = iter.next() else {
248            return Err(Error::ParseError("PROTECTION field missing".into()).into());
249        };
250        let protection = match protection {
251            "P" => KeyProtection::Protected,
252            "C" => KeyProtection::NotProtected,
253            _ => KeyProtection::UnknownProtection,
254        };
255
256        // FPR returns the formatted ssh-style fingerprint of the key.  It is only
257        //     printed if the option --ssh-fpr has been used.  If ALGO is not given
258        //     to that option the default ssh fingerprint algo is used.  Without the
259        //     option a '-' is printed.
260        let Some(_ssh_fpr) = iter.next() else {
261            return Err(Error::ParseError("FPR field missing".into()).into());
262        };
263
264        // TTL is the TTL in seconds for that key or '-' if n/a.
265        let Some(ttl) = iter.next() else {
266            return Err(Error::ParseError("TTL field missing".into()).into());
267        };
268        let ttl = if ttl == "-" {
269            None
270        } else {
271            Some(u64::from_str_radix(ttl, 10).with_context(|| {
272                format!("Parsing {:?} as a u64", ttl)
273            })?)
274        };
275
276        // FLAGS is a word consisting of one-letter flags:
277        //       'D' - The key has been disabled,
278        //       'S' - The key is listed in sshcontrol (requires --with-ssh),
279        //       'c' - Use of the key needs to be confirmed,
280        //       '-' - No flags given.
281        let Some(flags) = iter.next() else {
282            return Err(Error::ParseError("FLAGS field missing".into()).into());
283        };
284        let flags = flags.chars()
285            .filter_map(|c| {
286                match c {
287                    'D' => Some(KeyFlag::Disabled),
288                    'S' => Some(KeyFlag::SSHControl),
289                    'c' => Some(KeyFlag::ConfirmationRequired),
290                    _ => None,
291                }
292            })
293            .collect::<Vec<KeyFlag>>();
294
295        Ok(KeyInfo {
296            keygrip,
297            keytype,
298            serialno,
299            idstr,
300            passphrase_cached,
301            protection,
302            ttl,
303            flags,
304        })
305    }
306}
307
308/// A collection of `KeyInfo`.
309pub struct KeyInfoList {
310    keys: HashMap<Keygrip, KeyInfo>,
311}
312
313impl FromIterator<KeyInfo> for KeyInfoList {
314    fn from_iter<I>(iter: I) -> Self
315    where I: IntoIterator<Item=KeyInfo>
316    {
317        KeyInfoList {
318            keys: HashMap::from_iter(
319                iter.into_iter().map(|i| (i.keygrip().clone(), i))),
320        }
321    }
322}
323
324impl Default for KeyInfoList {
325    fn default() -> Self {
326        Self {
327            keys: Default::default(),
328        }
329    }
330}
331
332impl KeyInfoList {
333    /// Returns an empty `KeyInfoList`.
334    pub fn empty() -> Self {
335        Self::default()
336    }
337
338    /// Returns the information about the specified key, if any.
339    pub fn lookup<K>(&self, keygrip: K) -> Option<&KeyInfo>
340        where K: Borrow<Keygrip>
341    {
342        let keygrip = keygrip.borrow();
343        self.keys.get(keygrip)
344    }
345
346    /// Returns the information about the specified key, if any.
347    pub fn lookup_by_key<K, R, P>(&self, key: K) -> Option<&KeyInfo>
348    where K: Borrow<Key<P, R>>,
349          P: key:: KeyParts,
350          R: key:: KeyRole,
351    {
352        let key = key.borrow();
353        // The mapping from Key to Keygrip is not total so this might
354        // fail.  But if there isn't a keygrip for a key, then
355        // gpg-agent can't manage it, and thus can't return it.
356        // Therefore if a key doesn't have a keygrip, the correct
357        // answer is not to return an error, but to return `None`.
358        let keygrip = Keygrip::of(key.mpis()).ok()?;
359        self.lookup(keygrip)
360    }
361
362    /// Returns an iterator over the elements of the `KeyInfoList`.
363    pub fn iter(&self) -> KeyInfoListIter {
364        KeyInfoListIter {
365            iter: self.keys.values(),
366        }
367    }
368
369    /// Returns the number of elements.
370    pub fn len(&self) -> usize {
371        self.keys.len()
372    }
373}
374
375/// An iterator over a `KeyInfoList`.
376pub struct KeyInfoListIter<'a> {
377    iter: std::collections::hash_map::Values<'a, Keygrip, KeyInfo>,
378}
379
380impl<'a> IntoIterator for &'a KeyInfoList {
381    type Item = &'a KeyInfo;
382    type IntoIter = KeyInfoListIter<'a>;
383
384    fn into_iter(self) -> Self::IntoIter {
385        KeyInfoListIter {
386            iter: self.keys.values()
387        }
388    }
389}
390
391impl<'a> Iterator for KeyInfoListIter<'a> {
392    type Item = &'a KeyInfo;
393
394    fn next(&mut self) -> Option<Self::Item> {
395        self.iter.next()
396    }
397}
398
399#[cfg(test)]
400mod test {
401    use super::*;
402
403    #[test]
404    fn parse_keyinfo_list() -> Result<()> {
405        let entry = "EF8CE31AE9E310D660C7C9709A028442A8B52112 D - - - C - - -";
406        eprintln!("{}", entry);
407        assert_eq!(
408            KeyInfo::parse(entry).unwrap(),
409            KeyInfo {
410                keygrip: Keygrip::from_str(
411                    "EF8CE31AE9E310D660C7C9709A028442A8B52112").unwrap(),
412                keytype: KeyType::Regular,
413                serialno: None,
414                idstr: None,
415                passphrase_cached: false,
416                protection: KeyProtection::NotProtected,
417                ttl: None,
418                flags: vec![ ],
419            });
420
421        let entry = "EAFE58E4F5269129D7233A0FA6307C366A3EA2CC D - - - P - - -";
422        eprintln!("{}", entry);
423        assert_eq!(
424            KeyInfo::parse(entry).unwrap(),
425            KeyInfo {
426                keygrip: Keygrip::from_str(
427                    "EAFE58E4F5269129D7233A0FA6307C366A3EA2CC").unwrap(),
428                keytype: KeyType::Regular,
429                serialno: None,
430                idstr: None,
431                passphrase_cached: false,
432                protection: KeyProtection::Protected,
433                ttl: None,
434                flags: vec![ ],
435            });
436
437        let entry = "9483454871CC1239D4C2A1416F2742D39A14DB14 T D2760001240103040006181329630000 OPENPGP.3 - - - - -";
438        eprintln!("{}", entry);
439        assert_eq!(
440            KeyInfo::parse(entry).unwrap(),
441            KeyInfo {
442                keygrip: Keygrip::from_str(
443                    "9483454871CC1239D4C2A1416F2742D39A14DB14").unwrap(),
444                keytype: KeyType::Smartcard,
445                serialno: Some("D2760001240103040006181329630000".to_string()),
446                idstr: Some("OPENPGP.3".to_string()),
447                passphrase_cached: false,
448                protection: KeyProtection::UnknownProtection,
449                ttl: None,
450                flags: vec![ ],
451            });
452
453        Ok(())
454    }
455}