1use card_backend::SmartcardError;
30use card_backend_pcsc::PcscBackend;
31use openpgp_card::ocard::algorithm::{AlgorithmAttributes, Curve};
32use openpgp_card::ocard::crypto::{Cryptogram, HashAlgo, PublicKeyMaterial, SigningAlgo};
33use openpgp_card::ocard::data::UserInteractionFlag;
34use openpgp_card::ocard::{KeyType, StatusBytes};
35use openpgp_card::state::{Open, Transaction};
36use sioul_core::securitykey::{Card, CardError, CardInfo, CardPublic, Password, PinMode, Protected, Readers, SlotInfo, ToDecipher, ToSign, Touch, Visit, Visited};
37use std::ffi::CString;
38use std::time::Duration;
39
40pub struct PcscReaders;
42
43fn establish() -> Result<pcsc::Context, CardError> {
45 pcsc::Context::establish(pcsc::Scope::User).map_err(|e| match e {
46 pcsc::Error::NoService | pcsc::Error::ServiceStopped | pcsc::Error::NoReadersAvailable => CardError::NoService,
47 other => CardError::Other(other.to_string()),
48 })
49}
50
51fn readers_of(context: &pcsc::Context) -> Result<Vec<CString>, CardError> {
53 match context.list_readers_owned() {
54 Ok(readers) => Ok(readers),
55 Err(pcsc::Error::NoReadersAvailable) => Ok(Vec::new()),
56 Err(pcsc::Error::NoService | pcsc::Error::ServiceStopped) => Err(CardError::NoService),
57 Err(other) => Err(CardError::Other(other.to_string())),
58 }
59}
60
61impl Readers for PcscReaders {
62 fn present(&self) -> Result<Vec<String>, CardError> {
63 let context = establish()?;
64 let mut states: Vec<pcsc::ReaderState> = readers_of(&context)?.into_iter().map(|name| pcsc::ReaderState::new(name, pcsc::State::UNAWARE)).collect();
65 if states.is_empty() {
66 return Ok(Vec::new());
67 }
68 match context.get_status_change(Duration::ZERO, &mut states) {
70 Ok(()) | Err(pcsc::Error::Timeout) => {}
71 Err(pcsc::Error::NoService | pcsc::Error::ServiceStopped) => return Err(CardError::NoService),
72 Err(other) => return Err(CardError::Other(other.to_string())),
73 }
74 Ok(states.iter().filter(|s| s.event_state().contains(pcsc::State::PRESENT)).map(|s| s.name().to_string_lossy().into_owned()).collect())
75 }
76
77 fn visit(&self, visit: &mut dyn FnMut(&mut dyn Card) -> Visit) -> Result<Visited, CardError> {
78 let context = establish()?;
79 let mut visited = Visited::default();
80 for reader in readers_of(&context)? {
81 let connected = match context.connect(&reader, pcsc::ShareMode::Shared, pcsc::Protocols::ANY) {
82 Ok(card) => card,
83 Err(pcsc::Error::SharingViolation) => {
85 visited.busy += 1;
86 continue;
87 }
88 Err(_) => continue,
90 };
91 let Ok(backend) = PcscBackend::from_pcsc_card(&reader, pcsc::ShareMode::Shared, connected) else { continue };
92 let Ok(mut opened) = openpgp_card::Card::<Open>::new(backend) else { continue };
94 let Ok(transaction) = opened.transaction() else { continue };
95 let mut card = PcscCard { tx: transaction, reader: reader.to_string_lossy().into_owned(), pin_taken: false, touch: (false, false) };
96 visited.opened += 1;
97 let next = visit(&mut card);
98 drop(card);
100 drop(opened);
101 if next == Visit::Done {
102 break;
103 }
104 }
105 Ok(visited)
106 }
107}
108
109fn touch_of(flag: Result<Option<UserInteractionFlag>, openpgp_card::Error>) -> Touch {
115 match flag {
116 Ok(Some(flag)) => Touch::from_flag(u8::from(flag.touch_policy())),
117 Ok(None) => Touch::Off,
118 Err(_) => Touch::Unknown,
119 }
120}
121
122struct PcscCard<'a> {
124 tx: openpgp_card::Card<Transaction<'a>>,
125 reader: String,
126 pin_taken: bool,
128 touch: (bool, bool),
130}
131
132impl PcscCard<'_> {
133 fn slot(&mut self, key: KeyType, fingerprint: Option<String>, created: u32) -> Option<SlotInfo> {
135 let fingerprint = fingerprint?;
136 let algorithm = self.tx.algorithm_attributes(key).map(|a| algorithm_name(&a)).unwrap_or_default();
137 let touch = touch_of(self.tx.user_interaction_flag(key));
138 let public = match self.tx.public_key_material(key) {
139 Ok(PublicKeyMaterial::R(rsa)) => Some(CardPublic::Rsa { n: rsa.n().to_vec(), e: rsa.v().to_vec() }),
140 Ok(PublicKeyMaterial::E(ecc)) => Some(CardPublic::Ecc { point: ecc.data().to_vec() }),
141 Err(_) => None,
142 };
143 Some(SlotInfo { fingerprint, created, algorithm, touch, public })
144 }
145
146 fn failed(&self, error: openpgp_card::Error, touch: bool) -> CardError {
147 card_error(error, self.pin_taken && touch)
148 }
149}
150
151impl Card for PcscCard<'_> {
152 fn info(&mut self) -> Result<CardInfo, CardError> {
153 let aid = self.tx.application_identifier().map_err(|e| card_error(e, false))?;
154 let fingerprints = self.tx.fingerprints().map_err(|e| card_error(e, false))?;
155 let times = self.tx.key_generation_times().ok();
156 let status = self.tx.pw_status_bytes().map_err(|e| card_error(e, false))?;
157 let created = |time: Option<&openpgp_card::ocard::data::KeyGenerationTime>| time.map_or(0, u32::from);
158 let (sign_time, decrypt_time) = times.as_ref().map_or((0, 0), |t| (created(t.signature()), created(t.decryption())));
159 let sign = self.slot(KeyType::Signing, fingerprints.signature().map(|f| f.to_hex().to_ascii_uppercase()), sign_time);
160 let decrypt = self.slot(KeyType::Decryption, fingerprints.decryption().map(|f| f.to_hex().to_ascii_uppercase()), decrypt_time);
161 self.touch = (sign.as_ref().is_some_and(|s| s.touch.asked()), decrypt.as_ref().is_some_and(|s| s.touch.asked()));
162 Ok(CardInfo {
163 ident: aid.ident(),
164 manufacturer: aid.manufacturer(),
165 serial: aid.serial(),
166 holder: self.tx.cardholder_name().unwrap_or_default(),
167 url: self.tx.url().unwrap_or_default(),
168 sign,
169 decrypt,
170 pin_tries: status.err_count_pw1(),
171 reset_tries: status.err_count_rc(),
172 admin_tries: status.err_count_pw3(),
173 pin_each_signature: status.pw1_cds_valid_once(),
174 signatures: self.tx.digital_signature_count().unwrap_or(0),
175 reader: self.reader.clone(),
176 })
177 }
178
179 fn verify(&mut self, pin: &Password, mode: PinMode) -> Result<(), CardError> {
180 let pin = pin.map(|p| std::str::from_utf8(p).map(secrecy::SecretString::from)).map_err(|_| CardError::Other("the PIN is not text".into()))?;
182 let verified = match mode {
183 PinMode::Sign => self.tx.verify_user_signing_pin(pin),
184 PinMode::Decrypt => self.tx.verify_user_pin(pin),
185 };
186 verified.map_err(|e| card_error(e, false))?;
187 self.pin_taken = true;
188 Ok(())
189 }
190
191 fn sign(&mut self, input: ToSign<'_>) -> Result<Vec<u8>, CardError> {
192 let (algorithm, digest) = match input {
193 ToSign::Digest(digest) => (SigningAlgo::ECC, digest),
194 ToSign::Rsa { hash, digest } => {
195 let hash = match hash {
196 sioul_core::securitykey::HashAlgorithm::SHA256 => HashAlgo::SHA256,
197 sioul_core::securitykey::HashAlgorithm::SHA384 => HashAlgo::SHA384,
198 sioul_core::securitykey::HashAlgorithm::SHA512 => HashAlgo::SHA512,
199 other => return Err(CardError::Algorithm(format!("RSA, {other}"))),
200 };
201 (SigningAlgo::RSA(hash), digest)
202 }
203 };
204 let touch = self.touch.0;
205 self.tx.card().signature_for_hash(algorithm, digest).map_err(|e| self.failed(e, touch))
206 }
207
208 fn decipher(&mut self, input: ToDecipher<'_>) -> Result<Protected, CardError> {
209 let cryptogram = match input {
210 ToDecipher::Rsa(ciphertext) => Cryptogram::RSA(ciphertext),
211 ToDecipher::Ecdh(point) => Cryptogram::ECDH(point),
212 };
213 let touch = self.touch.1;
214 self.tx.card().decipher(cryptogram).map(Protected::from).map_err(|e| self.failed(e, touch))
216 }
217}
218
219fn algorithm_name(attributes: &AlgorithmAttributes) -> String {
221 match attributes {
222 AlgorithmAttributes::Rsa(rsa) => format!("RSA {}", rsa.len_n()),
223 AlgorithmAttributes::Ecc(ecc) => match ecc.curve() {
224 Curve::Ed25519 => "Ed25519".into(),
225 Curve::Curve25519 => "Curve25519".into(),
226 Curve::NistP256r1 => "NIST P-256".into(),
227 Curve::NistP384r1 => "NIST P-384".into(),
228 Curve::NistP521r1 => "NIST P-521".into(),
229 Curve::BrainpoolP256r1 => "brainpoolP256r1".into(),
230 Curve::BrainpoolP384r1 => "brainpoolP384r1".into(),
231 Curve::BrainpoolP512r1 => "brainpoolP512r1".into(),
232 Curve::Secp256k1 => "secp256k1".into(),
233 Curve::Ed448 => "Ed448".into(),
234 Curve::X448 => "X448".into(),
235 Curve::Unknown(_) => String::new(),
236 },
237 AlgorithmAttributes::Unknown(_) => String::new(),
238 }
239}
240
241fn card_error(error: openpgp_card::Error, touch_after_pin: bool) -> CardError {
245 match error {
246 openpgp_card::Error::CardStatus(StatusBytes::PasswordNotChecked(left)) => CardError::WrongPin { left },
247 openpgp_card::Error::CardStatus(StatusBytes::AuthenticationMethodBlocked) => CardError::Blocked,
248 openpgp_card::Error::CardStatus(StatusBytes::SecurityRelatedIssues) => CardError::TouchMissed,
249 openpgp_card::Error::CardStatus(StatusBytes::SecurityStatusNotSatisfied) if touch_after_pin => CardError::TouchMissed,
250 openpgp_card::Error::Smartcard(SmartcardError::Error(said)) if ["RemovedCard", "NoSmartcard", "ReaderUnavailable", "UnknownReader"].iter().any(|w| said.contains(w)) => CardError::Removed,
251 openpgp_card::Error::Smartcard(SmartcardError::ContextError(_)) => CardError::NoService,
252 other => CardError::Other(other.to_string()),
253 }
254}
255
256#[cfg(test)]
257mod tests {
258 use super::*;
259
260 #[test]
261 fn the_cards_words_become_sioul_s() {
262 assert_eq!(card_error(openpgp_card::Error::CardStatus(StatusBytes::PasswordNotChecked(2)), false), CardError::WrongPin { left: 2 });
263 assert_eq!(card_error(openpgp_card::Error::CardStatus(StatusBytes::PasswordNotChecked(0)), false), CardError::WrongPin { left: 0 });
264 assert_eq!(card_error(openpgp_card::Error::CardStatus(StatusBytes::AuthenticationMethodBlocked), false), CardError::Blocked);
265 assert_eq!(card_error(openpgp_card::Error::CardStatus(StatusBytes::SecurityRelatedIssues), false), CardError::TouchMissed);
266 assert_eq!(card_error(openpgp_card::Error::CardStatus(StatusBytes::SecurityStatusNotSatisfied), true), CardError::TouchMissed);
268 assert!(matches!(card_error(openpgp_card::Error::CardStatus(StatusBytes::SecurityStatusNotSatisfied), false), CardError::Other(_)));
269 assert_eq!(card_error(openpgp_card::Error::Smartcard(SmartcardError::Error("Transmit failed: RemovedCard".into())), false), CardError::Removed);
270 assert_eq!(card_error(openpgp_card::Error::Smartcard(SmartcardError::ContextError("no service".into())), false), CardError::NoService);
271 }
272
273 #[test]
281 fn the_touch_settings_read_from_the_flags() {
282 let flag = |bytes: &[u8]| UserInteractionFlag::try_from(bytes.to_vec());
283 assert_eq!(touch_of(flag(&[0x00, 0x20]).map(Some)), Touch::Off);
284 assert_eq!(touch_of(flag(&[0x01, 0x20]).map(Some)), Touch::On, "a YubiKey with touch on: UIF Sign=on");
285 assert_eq!(touch_of(flag(&[0x02, 0x20]).map(Some)), Touch::Fixed);
286 assert_eq!(touch_of(flag(&[0x03, 0x20]).map(Some)), Touch::Cached);
287 assert_eq!(touch_of(flag(&[0x04, 0x20]).map(Some)), Touch::CachedFixed);
288 assert_eq!(touch_of(flag(&[0x7f, 0x20]).map(Some)), Touch::Unknown, "a setting Sioul does not know: asked");
289 assert_eq!(touch_of(flag(&[0x01]).map(Some)), Touch::Unknown);
291 assert_eq!(touch_of(Ok(None)), Touch::Off);
293 let tr = sioul_core::i18n::Translator::new("en");
294 assert!(touch_of(flag(&[0x01, 0x20]).map(Some)).words(&tr).is_some_and(|w| w.contains("hold your finger") && w.contains("15 seconds")));
295 assert!(touch_of(flag(&[0x01]).map(Some)).words(&tr).is_some_and(|w| w.starts_with("If your key asks for a touch")));
296 assert_eq!(touch_of(Ok(None)).words(&tr), None);
297 }
298
299 #[test]
300 #[ignore = "reads a real security key: the owner runs it himself, his key plugged in"]
301 fn a_real_key_reads_without_its_pin() {
302 let tr = sioul_core::i18n::Translator::new("en");
303 match sioul_core::securitykey::cards(&PcscReaders) {
304 Ok(cards) => {
305 for card in cards {
306 println!("{} ({}), holder {:?}, reader {:?}", card.label(&tr), card.ident, card.holder, card.reader);
307 for (what, slot) in [("signs", &card.sign), ("decrypts", &card.decrypt)] {
308 if let Some(slot) = slot {
309 println!(" {what}: {} {}, made {}, touch {:?}, public part read: {}", slot.algorithm, slot.fingerprint, slot.created, slot.touch, slot.public.is_some());
310 }
311 }
312 println!(" PIN tries {}, reset code {}, Admin PIN {}; PIN for each signature: {}; signatures made {}; URL {:?}", card.pin_tries, card.reset_tries, card.admin_tries, card.pin_each_signature, card.signatures, card.url);
313 }
314 }
315 Err(e) => println!("{}", e.sentence(&tr, sioul_core::securitykey::Purpose::Read)),
316 }
317 }
318}