sequoia_gpg_agent/
cardinfo.rs

1//! CardInfo and related data structures.
2//!
3//! This module defines the [`CardInfo`] family of data structures.
4//! It is returned by [`Agent::card_info`](crate::Agent::card_info).
5use std::str::FromStr;
6
7use sequoia_openpgp as openpgp;
8use openpgp::Fingerprint;
9
10use sequoia_ipc as ipc;
11use ipc::Keygrip;
12
13use crate::Result;
14
15/// KeyInfo-related errors.
16#[derive(thiserror::Error, Debug)]
17#[non_exhaustive]
18pub enum Error {
19    #[error("Error parsing card info data: {0}")]
20    ParseError(String),
21}
22
23/// Information about a key.
24///
25/// Returned by `Agent::card_info`.
26#[derive(Debug, Clone, PartialEq, Eq)]
27pub struct CardInfo {
28    raw: Vec<(String, String)>,
29}
30
31impl CardInfo {
32    /// Returns the raw information.
33    ///
34    /// The first string is the keyword, e.g., `KEY-FPR` and the
35    /// second string is the value `3
36    /// 2DC50AB55BE2F3B04C2D2CF8A3506AFB820ABD08`.  The same keyword
37    /// may be returned multiple times.
38    pub fn raw(&self) -> impl Iterator<Item=&(String, String)> {
39        self.raw.iter()
40    }
41
42    /// Returns the available keys.
43    pub fn keys<'a>(&'a self) -> impl Iterator<Item=Fingerprint> + 'a {
44        self.raw()
45            .filter_map(|(keyword, value)| {
46                if keyword == "KEY-FPR" {
47                    if let Some(fpr) = value.split(" ").nth(1) {
48                        return Fingerprint::from_str(fpr).ok();
49                    }
50                }
51
52                None
53            })
54    }
55
56    /// Returns the available keys by their keygrip.
57    pub fn keys_keygrips<'a>(&'a self) -> impl Iterator<Item=Keygrip> + 'a {
58        self.raw()
59            .filter_map(|(keyword, value)| {
60                if keyword == "KEYPAIRINFO" {
61                    if let Some(fpr) = value.split(" ").nth(0) {
62                        return Keygrip::from_str(fpr).ok();
63                    }
64                }
65
66                None
67            })
68    }
69
70    /// Parses the status lines returned by `learn --sendinfo`.
71    ///
72    /// The output looks like:
73    ///
74    /// ```text
75    /// S KDF �%01%00
76    /// S SIG-COUNTER 793
77    /// S CHV-STATUS +1+127+127+127+3+0+3
78    /// S KEY-TIME 3 1428399828
79    /// S KEY-TIME 2 1428399800
80    /// S KEY-TIME 1 1428399766
81    /// S KEY-FPR 3 2DC50AB55BE2F3B04C2D2CF8A3506AFB820ABD08
82    /// S KEY-FPR 2 50E6D924308DBF223CFB510AC2B819056C652598
83    /// S KEY-FPR 1 C03FA6411B03AE12576461187223B56678E02528
84    /// S DISP-SEX 9
85    /// S MANUFACTURER 6 Yubico
86    /// S EXTCAP gc=1+ki=1+fc=1+pd=1+mcl3=2048+aac=1+sm=0+si=5+dec=0+bt=1+kdf=1
87    /// S APPTYPE openpgp
88    /// S SERIALNO D2760001240103040006181329630000
89    /// S READER 1050:0407:X:0
90    /// S KEYPAIRINFO 9483454871CC1239D4C2A1416F2742D39A14DB14 OPENPGP.3
91    /// S KEYPAIRINFO 9873FD355DE470DDC151CD9919AC9785C3C2FDDE OPENPGP.2
92    /// S KEYPAIRINFO BE2FE8C8793141322AC30E3EAFD1E4F9D8DACCC4 OPENPGP.1
93    /// ```
94    pub(crate) fn parse(raw: Vec<(String, String)>) -> Result<Self> {
95        Ok(Self {
96            raw,
97        })
98    }
99}
100
101#[cfg(test)]
102mod test {
103    use super::*;
104
105    #[test]
106    fn parse_cardinfo() -> Result<()> {
107        let lines = &[
108            ("KDF", "�%01%00"),
109            ("SIG-COUNTER", "793"),
110            ("CHV-STATUS", "+1+127+127+127+3+0+3"),
111            ("KEY-TIME", "3 1428399828"),
112            ("KEY-TIME", "2 1428399800"),
113            ("KEY-TIME", "1 1428399766"),
114            ("KEY-FPR", "3 2DC50AB55BE2F3B04C2D2CF8A3506AFB820ABD08"),
115            ("KEY-FPR", "2 50E6D924308DBF223CFB510AC2B819056C652598"),
116            ("KEY-FPR", "1 C03FA6411B03AE12576461187223B56678E02528"),
117            ("DISP-SEX", "9"),
118            ("MANUFACTURER", "6 Yubico"),
119            ("EXTCAP", "gc=1+ki=1+fc=1+pd=1+mcl3=2048+aac=1+sm=0+si=5+dec=0+bt=1+kdf=1"),
120            ("APPTYPE", "openpgp"),
121            ("SERIALNO", "D2760001240103040006181329630000"),
122            ("READER", "1050:0407:X:0"),
123            ("KEYPAIRINFO", "9483454871CC1239D4C2A1416F2742D39A14DB14 OPENPGP.3"),
124            ("KEYPAIRINFO", "9873FD355DE470DDC151CD9919AC9785C3C2FDDE OPENPGP.2"),
125            ("KEYPAIRINFO", "BE2FE8C8793141322AC30E3EAFD1E4F9D8DACCC4 OPENPGP.1"),
126        ][..];
127
128        let info = CardInfo::parse(
129            lines.into_iter()
130                .map(|(k, m)| {
131                    (k.to_string(), m.to_string())
132                })
133                .collect::<Vec<_>>())
134            .expect("valid");
135
136        let fprs = info.keys().collect::<Vec<Fingerprint>>();
137        assert_eq!(fprs.len(), 3);
138        assert_eq!(
139            fprs[0],
140            "2DC50AB55BE2F3B04C2D2CF8A3506AFB820ABD08".parse::<Fingerprint>().expect("ok"));
141        assert_eq!(
142            fprs[1],
143            "50E6D924308DBF223CFB510AC2B819056C652598".parse::<Fingerprint>().expect("ok"));
144        assert_eq!(
145            fprs[2],
146            "C03FA6411B03AE12576461187223B56678E02528".parse::<Fingerprint>().expect("ok"));
147
148        let keygrips = info.keys_keygrips().collect::<Vec<Keygrip>>();
149        assert_eq!(keygrips.len(), 3);
150        assert_eq!(
151            keygrips[0],
152            "9483454871CC1239D4C2A1416F2742D39A14DB14".parse::<Keygrip>().expect("ok"));
153        assert_eq!(
154            keygrips[1],
155            "9873FD355DE470DDC151CD9919AC9785C3C2FDDE".parse::<Keygrip>().expect("ok"));
156        assert_eq!(
157            keygrips[2],
158            "BE2FE8C8793141322AC30E3EAFD1E4F9D8DACCC4".parse::<Keygrip>().expect("ok"));
159
160        Ok(())
161    }
162}