1use card_backend::SmartcardError;
22use openpgp_card::ocard::algorithm::{AlgorithmAttributes, Curve};
23use openpgp_card::ocard::crypto::{Cryptogram, HashAlgo, PublicKeyMaterial, SigningAlgo};
24use openpgp_card::ocard::data::UserInteractionFlag;
25use openpgp_card::ocard::{KeyType, StatusBytes};
26use openpgp_card::state::Transaction;
27use sioul_core::securitykey::{Card, CardError, CardInfo, CardPublic, Password, PinMode, Protected, SlotInfo, ToDecipher, ToSign, Touch};
28
29fn touch_of(flag: Result<Option<UserInteractionFlag>, openpgp_card::Error>) -> Touch {
35 match flag {
36 Ok(Some(flag)) => Touch::from_flag(u8::from(flag.touch_policy())),
37 Ok(None) => Touch::Off,
38 Err(_) => Touch::Unknown,
39 }
40}
41
42pub(crate) struct OpenCard<'a> {
45 tx: openpgp_card::Card<Transaction<'a>>,
46 reader: String,
47 pin_taken: bool,
49 touch: (bool, bool),
51}
52
53impl<'a> OpenCard<'a> {
54 pub(crate) fn new(tx: openpgp_card::Card<Transaction<'a>>, reader: String) -> OpenCard<'a> {
56 OpenCard { tx, reader, pin_taken: false, touch: (false, false) }
57 }
58
59 fn slot(&mut self, key: KeyType, fingerprint: Option<String>, created: u32) -> Option<SlotInfo> {
61 let fingerprint = fingerprint?;
62 let algorithm = self.tx.algorithm_attributes(key).map(|a| algorithm_name(&a)).unwrap_or_default();
63 let touch = touch_of(self.tx.user_interaction_flag(key));
64 let public = match self.tx.public_key_material(key) {
65 Ok(PublicKeyMaterial::R(rsa)) => Some(CardPublic::Rsa { n: rsa.n().to_vec(), e: rsa.v().to_vec() }),
66 Ok(PublicKeyMaterial::E(ecc)) => Some(CardPublic::Ecc { point: ecc.data().to_vec() }),
67 Err(_) => None,
68 };
69 Some(SlotInfo { fingerprint, created, algorithm, touch, public })
70 }
71
72 fn failed(&self, error: openpgp_card::Error, touch: bool) -> CardError {
73 card_error(error, self.pin_taken && touch)
74 }
75}
76
77impl Card for OpenCard<'_> {
78 fn info(&mut self) -> Result<CardInfo, CardError> {
79 let aid = self.tx.application_identifier().map_err(|e| card_error(e, false))?;
80 let fingerprints = self.tx.fingerprints().map_err(|e| card_error(e, false))?;
81 let times = self.tx.key_generation_times().ok();
82 let status = self.tx.pw_status_bytes().map_err(|e| card_error(e, false))?;
83 let created = |time: Option<&openpgp_card::ocard::data::KeyGenerationTime>| time.map_or(0, u32::from);
84 let (sign_time, decrypt_time) = times.as_ref().map_or((0, 0), |t| (created(t.signature()), created(t.decryption())));
85 let sign = self.slot(KeyType::Signing, fingerprints.signature().map(|f| f.to_hex().to_ascii_uppercase()), sign_time);
86 let decrypt = self.slot(KeyType::Decryption, fingerprints.decryption().map(|f| f.to_hex().to_ascii_uppercase()), decrypt_time);
87 self.touch = (sign.as_ref().is_some_and(|s| s.touch.asked()), decrypt.as_ref().is_some_and(|s| s.touch.asked()));
88 Ok(CardInfo {
89 ident: aid.ident(),
90 manufacturer: aid.manufacturer(),
91 serial: aid.serial(),
92 holder: self.tx.cardholder_name().unwrap_or_default(),
93 url: self.tx.url().unwrap_or_default(),
94 sign,
95 decrypt,
96 pin_tries: status.err_count_pw1(),
97 reset_tries: status.err_count_rc(),
98 admin_tries: status.err_count_pw3(),
99 pin_each_signature: status.pw1_cds_valid_once(),
100 signatures: self.tx.digital_signature_count().unwrap_or(0),
101 reader: self.reader.clone(),
102 })
103 }
104
105 fn verify(&mut self, pin: &Password, mode: PinMode) -> Result<(), CardError> {
106 let pin = pin.map(|p| std::str::from_utf8(p).map(secrecy::SecretString::from)).map_err(|_| CardError::Other("the PIN is not text".into()))?;
108 let verified = match mode {
109 PinMode::Sign => self.tx.verify_user_signing_pin(pin),
110 PinMode::Decrypt => self.tx.verify_user_pin(pin),
111 };
112 verified.map_err(|e| card_error(e, false))?;
113 self.pin_taken = true;
114 Ok(())
115 }
116
117 fn sign(&mut self, input: ToSign<'_>) -> Result<Vec<u8>, CardError> {
118 let (algorithm, digest) = match input {
119 ToSign::Digest(digest) => (SigningAlgo::ECC, digest),
120 ToSign::Rsa { hash, digest } => {
121 let hash = match hash {
122 sioul_core::securitykey::HashAlgorithm::SHA256 => HashAlgo::SHA256,
123 sioul_core::securitykey::HashAlgorithm::SHA384 => HashAlgo::SHA384,
124 sioul_core::securitykey::HashAlgorithm::SHA512 => HashAlgo::SHA512,
125 other => return Err(CardError::Algorithm(format!("RSA, {other}"))),
126 };
127 (SigningAlgo::RSA(hash), digest)
128 }
129 };
130 let touch = self.touch.0;
131 self.tx.card().signature_for_hash(algorithm, digest).map_err(|e| self.failed(e, touch))
132 }
133
134 fn decipher(&mut self, input: ToDecipher<'_>) -> Result<Protected, CardError> {
135 let cryptogram = match input {
136 ToDecipher::Rsa(ciphertext) => Cryptogram::RSA(ciphertext),
137 ToDecipher::Ecdh(point) => Cryptogram::ECDH(point),
138 };
139 let touch = self.touch.1;
140 self.tx.card().decipher(cryptogram).map(Protected::from).map_err(|e| self.failed(e, touch))
142 }
143}
144
145fn algorithm_name(attributes: &AlgorithmAttributes) -> String {
147 match attributes {
148 AlgorithmAttributes::Rsa(rsa) => format!("RSA {}", rsa.len_n()),
149 AlgorithmAttributes::Ecc(ecc) => match ecc.curve() {
150 Curve::Ed25519 => "Ed25519".into(),
151 Curve::Curve25519 => "Curve25519".into(),
152 Curve::NistP256r1 => "NIST P-256".into(),
153 Curve::NistP384r1 => "NIST P-384".into(),
154 Curve::NistP521r1 => "NIST P-521".into(),
155 Curve::BrainpoolP256r1 => "brainpoolP256r1".into(),
156 Curve::BrainpoolP384r1 => "brainpoolP384r1".into(),
157 Curve::BrainpoolP512r1 => "brainpoolP512r1".into(),
158 Curve::Secp256k1 => "secp256k1".into(),
159 Curve::Ed448 => "Ed448".into(),
160 Curve::X448 => "X448".into(),
161 Curve::Unknown(_) => String::new(),
162 },
163 AlgorithmAttributes::Unknown(_) => String::new(),
164 }
165}
166
167pub(crate) fn card_error(error: openpgp_card::Error, touch_after_pin: bool) -> CardError {
171 match error {
172 openpgp_card::Error::CardStatus(StatusBytes::PasswordNotChecked(left)) => CardError::WrongPin { left },
173 openpgp_card::Error::CardStatus(StatusBytes::AuthenticationMethodBlocked) => CardError::Blocked,
174 openpgp_card::Error::CardStatus(StatusBytes::SecurityRelatedIssues) => CardError::TouchMissed,
175 openpgp_card::Error::CardStatus(StatusBytes::SecurityStatusNotSatisfied) if touch_after_pin => CardError::TouchMissed,
176 openpgp_card::Error::Smartcard(SmartcardError::Error(said)) if ["RemovedCard", "NoSmartcard", "ReaderUnavailable", "UnknownReader"].iter().any(|w| said.contains(w)) => CardError::Removed,
177 openpgp_card::Error::Smartcard(SmartcardError::ContextError(_)) => CardError::NoService,
178 other => CardError::Other(other.to_string()),
179 }
180}
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185
186 #[test]
187 fn the_cards_words_become_sioul_s() {
188 assert_eq!(card_error(openpgp_card::Error::CardStatus(StatusBytes::PasswordNotChecked(2)), false), CardError::WrongPin { left: 2 });
189 assert_eq!(card_error(openpgp_card::Error::CardStatus(StatusBytes::PasswordNotChecked(0)), false), CardError::WrongPin { left: 0 });
190 assert_eq!(card_error(openpgp_card::Error::CardStatus(StatusBytes::AuthenticationMethodBlocked), false), CardError::Blocked);
191 assert_eq!(card_error(openpgp_card::Error::CardStatus(StatusBytes::SecurityRelatedIssues), false), CardError::TouchMissed);
192 assert_eq!(card_error(openpgp_card::Error::CardStatus(StatusBytes::SecurityStatusNotSatisfied), true), CardError::TouchMissed);
194 assert!(matches!(card_error(openpgp_card::Error::CardStatus(StatusBytes::SecurityStatusNotSatisfied), false), CardError::Other(_)));
195 assert_eq!(card_error(openpgp_card::Error::Smartcard(SmartcardError::Error("Transmit failed: RemovedCard".into())), false), CardError::Removed);
196 assert_eq!(card_error(openpgp_card::Error::Smartcard(SmartcardError::ContextError("no service".into())), false), CardError::NoService);
197 }
198
199 #[test]
207 fn the_touch_settings_read_from_the_flags() {
208 let flag = |bytes: &[u8]| UserInteractionFlag::try_from(bytes.to_vec());
209 assert_eq!(touch_of(flag(&[0x00, 0x20]).map(Some)), Touch::Off);
210 assert_eq!(touch_of(flag(&[0x01, 0x20]).map(Some)), Touch::On, "a YubiKey with touch on: UIF Sign=on");
211 assert_eq!(touch_of(flag(&[0x02, 0x20]).map(Some)), Touch::Fixed);
212 assert_eq!(touch_of(flag(&[0x03, 0x20]).map(Some)), Touch::Cached);
213 assert_eq!(touch_of(flag(&[0x04, 0x20]).map(Some)), Touch::CachedFixed);
214 assert_eq!(touch_of(flag(&[0x7f, 0x20]).map(Some)), Touch::Unknown, "a setting Sioul does not know: asked");
215 assert_eq!(touch_of(flag(&[0x01]).map(Some)), Touch::Unknown);
217 assert_eq!(touch_of(Ok(None)), Touch::Off);
219 let tr = sioul_core::i18n::Translator::new("en");
220 assert!(touch_of(flag(&[0x01, 0x20]).map(Some)).words(&tr).is_some_and(|w| w.contains("hold your finger") && w.contains("15 seconds")));
221 assert!(touch_of(flag(&[0x01]).map(Some)).words(&tr).is_some_and(|w| w.starts_with("If your key asks for a touch")));
222 assert_eq!(touch_of(Ok(None)).words(&tr), None);
223 }
224}