use std::collections::BTreeMap;
use std::path::PathBuf;
use tokenkey::{discover, DiscoveredKey, Discovery, KeyClass, TokenKey};
use crate::error::Result;
use crate::filter::{is_esteid_issued, modules};
use crate::EstEidError;
pub struct IdCard {
pub auth: DiscoveredKey,
pub sign: DiscoveredKey,
}
impl IdCard {
pub fn list() -> Result<Vec<IdCard>> {
Ok(cards_from_pairs(pair_cards(discover(&modules()))))
}
pub fn find() -> Result<IdCard> {
find_from_pairs(pair_cards(discover(&modules())))
}
pub fn document_number(&self) -> &str {
&self.auth.token_serial
}
pub fn auth_certificate_der(&self) -> &[u8] {
&self.auth.cert_der
}
pub fn signing_certificate_der(&self) -> &[u8] {
&self.sign.cert_der
}
pub fn open_auth(&self, pin1: &str) -> Result<TokenKey> {
Ok(TokenKey::open_discovered(&self.auth, pin1)?)
}
pub fn open_signing(&self, pin2: &str) -> Result<TokenKey> {
Ok(TokenKey::open_discovered(&self.sign, pin2)?)
}
}
enum CardOutcome {
Complete(Box<IdCard>),
Incomplete {
token_label: String,
token_serial: String,
missing: &'static str,
},
}
fn pair_cards(discovery: Discovery) -> Vec<CardOutcome> {
let mut groups: BTreeMap<(PathBuf, String), (Option<DiscoveredKey>, Option<DiscoveredKey>)> =
BTreeMap::new();
for key in discovery.keys {
if !matches!(key.usage, KeyClass::Authentication | KeyClass::Signing) {
continue;
}
if !is_esteid_issued(&key.cert_der) {
continue;
}
let token = (key.module.clone(), key.token_serial.clone());
let entry = groups.entry(token).or_insert((None, None));
match key.usage {
KeyClass::Authentication => entry.0 = Some(key),
KeyClass::Signing => entry.1 = Some(key),
KeyClass::Other => unreachable!("filtered above"),
}
}
groups
.into_values()
.map(|(auth, signing)| match (auth, signing) {
(Some(auth), Some(signing)) => CardOutcome::Complete(Box::new(IdCard {
auth,
sign: signing,
})),
(Some(auth), None) => CardOutcome::Incomplete {
token_label: auth.token_label,
token_serial: auth.token_serial,
missing: "a signing certificate",
},
(None, Some(signing)) => CardOutcome::Incomplete {
token_label: signing.token_label,
token_serial: signing.token_serial,
missing: "an authentication certificate",
},
(None, None) => unreachable!("a group is only created when a key is inserted"),
})
.collect()
}
fn cards_from_pairs(outcomes: Vec<CardOutcome>) -> Vec<IdCard> {
outcomes
.into_iter()
.filter_map(|outcome| match outcome {
CardOutcome::Complete(card) => Some(*card),
CardOutcome::Incomplete { .. } => None,
})
.collect()
}
fn find_from_pairs(outcomes: Vec<CardOutcome>) -> Result<IdCard> {
let mut complete = Vec::new();
let mut incomplete = Vec::new();
for outcome in outcomes {
match outcome {
CardOutcome::Complete(card) => complete.push(*card),
CardOutcome::Incomplete {
token_label,
token_serial,
missing,
} => incomplete.push((token_label, token_serial, missing)),
}
}
match (complete.len(), incomplete.len()) {
(0, 0) => Err(EstEidError::NoCard),
(1, 0) => Ok(complete.into_iter().next().expect("len checked above")),
(0, 1) => {
let (_, _, missing) = incomplete.into_iter().next().expect("len checked above");
Err(EstEidError::IncompleteCard(missing.to_string()))
}
_ => {
let mut seen: Vec<String> = complete
.iter()
.map(|card| describe(&card.auth.token_label, &card.auth.token_serial))
.collect();
seen.extend(
incomplete
.into_iter()
.map(|(label, serial, _)| describe(&label, &serial)),
);
Err(EstEidError::MultipleCards(seen))
}
}
}
fn describe(token_label: &str, token_serial: &str) -> String {
format!("{token_label} ({token_serial})")
}