1use std::collections::BTreeMap;
2use std::path::PathBuf;
3
4use tokenkey::{discover, DiscoveredKey, Discovery, KeyClass, TokenKey};
5
6use crate::error::Result;
7use crate::filter::{is_esteid_issued, modules};
8use crate::EstEidError;
9
10pub struct IdCard {
12 pub auth: DiscoveredKey,
14 pub sign: DiscoveredKey,
16}
17
18impl IdCard {
19 pub fn list() -> Result<Vec<IdCard>> {
21 Ok(cards_from_pairs(pair_cards(discover(&modules()))))
22 }
23
24 pub fn find() -> Result<IdCard> {
26 find_from_pairs(pair_cards(discover(&modules())))
27 }
28
29 pub fn document_number(&self) -> &str {
31 &self.auth.token_serial
32 }
33
34 pub fn auth_certificate_der(&self) -> &[u8] {
36 &self.auth.cert_der
37 }
38
39 pub fn signing_certificate_der(&self) -> &[u8] {
41 &self.sign.cert_der
42 }
43
44 pub fn open_auth(&self, pin1: &str) -> Result<TokenKey> {
47 Ok(TokenKey::open_discovered(&self.auth, pin1)?)
48 }
49
50 pub fn open_signing(&self, pin2: &str) -> Result<TokenKey> {
53 Ok(TokenKey::open_discovered(&self.sign, pin2)?)
54 }
55}
56
57enum CardOutcome {
58 Complete(Box<IdCard>),
60 Incomplete {
62 token_label: String,
63 token_serial: String,
64 missing: &'static str,
65 },
66}
67
68fn pair_cards(discovery: Discovery) -> Vec<CardOutcome> {
69 let mut groups: BTreeMap<(PathBuf, String), (Option<DiscoveredKey>, Option<DiscoveredKey>)> =
72 BTreeMap::new();
73
74 for key in discovery.keys {
75 if !matches!(key.usage, KeyClass::Authentication | KeyClass::Signing) {
76 continue;
77 }
78 if !is_esteid_issued(&key.cert_der) {
79 continue;
80 }
81 let token = (key.module.clone(), key.token_serial.clone());
82 let entry = groups.entry(token).or_insert((None, None));
83 match key.usage {
84 KeyClass::Authentication => entry.0 = Some(key),
85 KeyClass::Signing => entry.1 = Some(key),
86 KeyClass::Other => unreachable!("filtered above"),
87 }
88 }
89
90 groups
91 .into_values()
92 .map(|(auth, signing)| match (auth, signing) {
93 (Some(auth), Some(signing)) => CardOutcome::Complete(Box::new(IdCard {
94 auth,
95 sign: signing,
96 })),
97 (Some(auth), None) => CardOutcome::Incomplete {
98 token_label: auth.token_label,
99 token_serial: auth.token_serial,
100 missing: "a signing certificate",
101 },
102 (None, Some(signing)) => CardOutcome::Incomplete {
103 token_label: signing.token_label,
104 token_serial: signing.token_serial,
105 missing: "an authentication certificate",
106 },
107 (None, None) => unreachable!("a group is only created when a key is inserted"),
108 })
109 .collect()
110}
111
112fn cards_from_pairs(outcomes: Vec<CardOutcome>) -> Vec<IdCard> {
113 outcomes
114 .into_iter()
115 .filter_map(|outcome| match outcome {
116 CardOutcome::Complete(card) => Some(*card),
117 CardOutcome::Incomplete { .. } => None,
118 })
119 .collect()
120}
121
122fn find_from_pairs(outcomes: Vec<CardOutcome>) -> Result<IdCard> {
123 let mut complete = Vec::new();
124 let mut incomplete = Vec::new();
125 for outcome in outcomes {
126 match outcome {
127 CardOutcome::Complete(card) => complete.push(*card),
128 CardOutcome::Incomplete {
129 token_label,
130 token_serial,
131 missing,
132 } => incomplete.push((token_label, token_serial, missing)),
133 }
134 }
135
136 match (complete.len(), incomplete.len()) {
137 (0, 0) => Err(EstEidError::NoCard),
138 (1, 0) => Ok(complete.into_iter().next().expect("len checked above")),
139 (0, 1) => {
140 let (_, _, missing) = incomplete.into_iter().next().expect("len checked above");
141 Err(EstEidError::IncompleteCard(missing.to_string()))
142 }
143 _ => {
144 let mut seen: Vec<String> = complete
145 .iter()
146 .map(|card| describe(&card.auth.token_label, &card.auth.token_serial))
147 .collect();
148 seen.extend(
149 incomplete
150 .into_iter()
151 .map(|(label, serial, _)| describe(&label, &serial)),
152 );
153 Err(EstEidError::MultipleCards(seen))
154 }
155 }
156}
157
158fn describe(token_label: &str, token_serial: &str) -> String {
159 format!("{token_label} ({token_serial})")
160}