1use std::fmt::{Debug, Formatter};
6use std::sync::Mutex;
7
8use chrono::{DateTime, SubsecRound, Utc};
9use openpgp_card::ocard::crypto::Hash;
10use openpgp_card::ocard::KeyType;
11use openpgp_card::state::Transaction;
12use openpgp_card::Card;
13use pgp::composed::{Esk, Message, PlainSessionKey};
14use pgp::crypto::checksum;
15use pgp::crypto::ecc_curve::ECCCurve;
16use pgp::crypto::hash::HashAlgorithm;
17use pgp::crypto::public_key::PublicKeyAlgorithm;
18use pgp::crypto::sym::SymmetricKeyAlgorithm;
19use pgp::packet::{PublicKey, Signature, SignatureConfig, SignatureType, Subpacket, SubpacketData};
20use pgp::types::{
21 EcdhPublicParams, EcdsaPublicParams, Fingerprint, KeyDetails, KeyId, KeyVersion, Mpi, Password,
22 PkeskBytes, PublicKeyTrait, PublicParams, SecretKeyTrait, SignatureBytes,
23};
24use rand::thread_rng;
25use rsa::traits::PublicKeyParts;
26
27use crate::Error;
28
29pub struct CardSlot<'cs, 't> {
31 tx: Mutex<&'cs mut Card<Transaction<'t>>>,
32
33 key_type: KeyType,
35
36 public_key: PublicKey,
41
42 touch_prompt: &'cs (dyn Fn() + Send + Sync),
43}
44
45impl<'cs, 't> CardSlot<'cs, 't> {
46 pub fn with_public_key(
50 tx: &'cs mut Card<Transaction<'t>>,
51 key_type: KeyType,
52 public_key: PublicKey,
53 touch_prompt: &'cs (dyn Fn() + Send + Sync),
54 ) -> Result<Self, crate::Error> {
55 Ok(Self {
58 tx: Mutex::new(tx),
59 public_key,
60 key_type,
61 touch_prompt,
62 })
63 }
64
65 pub fn init_from_card(
69 tx: &'cs mut Card<Transaction<'t>>,
70 key_type: KeyType,
71 touch_prompt: &'cs (dyn Fn() + Send + Sync),
72 ) -> Result<Self, crate::Error> {
73 let pk = crate::rpgp::pubkey_from_card(tx, key_type)?;
74
75 Self::with_public_key(tx, key_type, pk, touch_prompt)
76 }
77}
78
79impl CardSlot<'_, '_> {
80 pub fn public_key(&self) -> &PublicKey {
82 &self.public_key
83 }
84
85 pub fn key_type(&self) -> KeyType {
87 self.key_type
88 }
89
90 fn touch_required(&self, tx: &mut Card<Transaction<'_>>) -> bool {
91 if let Ok(Some(uif)) = tx.user_interaction_flag(self.key_type) {
95 uif.touch_policy().touch_required()
96 } else {
97 false
98 }
99 }
100
101 pub fn decrypt(&self, values: &PkeskBytes) -> Result<(Vec<u8>, SymmetricKeyAlgorithm), Error> {
102 #[allow(clippy::unwrap_used)]
103 let mut tx = self.tx.lock().unwrap();
104
105 let mut ecdh = |public_point: &[u8],
106 encrypted_session_key: &[u8],
107 hash: HashAlgorithm,
108 alg_sym: SymmetricKeyAlgorithm,
109 curve|
110 -> Result<Vec<u8>, pgp::errors::Error> {
111 let ciphertext = match curve {
112 ECCCurve::Curve25519 => &public_point[1..],
113 _ => public_point,
114 };
115
116 let cryptogram = openpgp_card::ocard::crypto::Cryptogram::ECDH(ciphertext);
117
118 if self.touch_required(&mut tx) {
119 (self.touch_prompt)();
120 }
121
122 let shared_secret: Vec<u8> = tx.card().decipher(cryptogram).map_err(|e| {
123 Error::Message(format!("ECDH decipher operation on card failed {e}"))
124 })?;
125
126 let encrypted_key_len = encrypted_session_key.len();
127
128 let decrypted_key: Vec<u8> = pgp::crypto::ecdh::derive_session_key(
129 &shared_secret,
130 encrypted_session_key,
131 encrypted_key_len,
132 curve,
133 hash,
134 alg_sym,
135 self.public_key.fingerprint().as_bytes(),
136 )?;
137
138 Ok(decrypted_key)
139 };
140
141 let decrypted_key = match (self.public_key.public_params(), values) {
142 (PublicParams::RSA(public), PkeskBytes::Rsa { mpi }) => {
143 let mut ciphertext = mpi.as_ref().to_vec();
144
145 let modulus_len = public.key.n().to_bytes_be().len();
152
153 while modulus_len > ciphertext.len() {
160 ciphertext.insert(0, 0u8);
161 }
162
163 let cryptogram = openpgp_card::ocard::crypto::Cryptogram::RSA(&ciphertext);
164
165 if self.touch_required(&mut tx) {
166 (self.touch_prompt)();
167 }
168
169 tx.card().decipher(cryptogram).map_err(|e| {
170 Error::Message(format!("RSA decipher operation on card failed: {e}"))
171 })?
172 }
173
174 (
175 PublicParams::ECDH(EcdhPublicParams::Curve25519 {
176 p: _p,
177 alg_sym,
178 hash,
179 ..
180 }),
181 PkeskBytes::Ecdh {
182 public_point,
183 encrypted_session_key,
184 },
185 ) => ecdh(
186 public_point.as_ref(),
187 encrypted_session_key,
188 *hash,
189 *alg_sym,
190 ECCCurve::Curve25519.clone(),
191 )?,
192
193 (
194 PublicParams::ECDH(EcdhPublicParams::P256 {
195 p: _p,
196 alg_sym,
197 hash,
198 ..
199 }),
200 PkeskBytes::Ecdh {
201 public_point,
202 encrypted_session_key,
203 },
204 ) => ecdh(
205 public_point.as_ref(),
206 encrypted_session_key,
207 *hash,
208 *alg_sym,
209 ECCCurve::P256.clone(),
210 )?,
211
212 (
213 PublicParams::ECDH(EcdhPublicParams::P384 {
214 p: _p,
215 alg_sym,
216 hash,
217 ..
218 }),
219 PkeskBytes::Ecdh {
220 public_point,
221 encrypted_session_key,
222 },
223 ) => ecdh(
224 public_point.as_ref(),
225 encrypted_session_key,
226 *hash,
227 *alg_sym,
228 ECCCurve::P384.clone(),
229 )?,
230
231 (
232 PublicParams::ECDH(EcdhPublicParams::P521 {
233 p: _p,
234 alg_sym,
235 hash,
236 ..
237 }),
238 PkeskBytes::Ecdh {
239 public_point,
240 encrypted_session_key,
241 },
242 ) => ecdh(
243 public_point.as_ref(),
244 encrypted_session_key,
245 *hash,
246 *alg_sym,
247 ECCCurve::P521.clone(),
248 )?,
249
250 pp => {
251 return Err(Error::Message(format!(
252 "decrypt: Unsupported key type {pp:?}"
253 )));
254 }
255 };
256
257 let dec_len = decrypted_key.len();
259 let (sessionkey, checksum) = (
260 &decrypted_key[1..dec_len - 2],
261 &decrypted_key[dec_len - 2..],
262 );
263
264 checksum::simple(
266 #[allow(clippy::expect_used)]
267 checksum.try_into().expect("this is two bytes long"),
268 sessionkey,
269 )
270 .map_err(|_| Error::Message("checksum mismatch while decrypting".to_string()))?;
271
272 let session_key_algorithm = decrypted_key[0].into();
273 Ok((sessionkey.to_vec(), session_key_algorithm))
274 }
275
276 pub fn decrypt_message<'a>(&self, message: Message<'a>) -> Result<Message<'a>, Error> {
278 let Message::Encrypted { esk, mut edata, .. } = message else {
279 return Err(Error::Message(
280 "message must be Message::Encrypted".to_string(),
281 ));
282 };
283
284 let mut sk = None;
286
287 for e in &esk {
288 if let Esk::PublicKeyEncryptedSessionKey(pkesk) = e {
289 if let Ok(v) = pkesk.values() {
290 if let Ok((key, algo)) = self.decrypt(v) {
292 sk = Some((key, algo));
293 break;
294 }
295 }
296 }
297 }
298
299 match sk {
300 Some(sk) => {
301 let plain_session_key = PlainSessionKey::V3_4 {
302 key: sk.0.into(),
303 sym_alg: sk.1,
304 };
305
306 edata.decrypt(&plain_session_key)?;
307
308 Ok(Message::from_bytes(edata)?)
309 }
310 None => Err(Error::Message(
311 "Failed to decrypt any PublicKeyEncryptedSessionKey".to_string(),
312 )),
313 }
314 }
315
316 pub fn sign_data(
317 &self,
318 data: &[u8],
319 text_mode: bool,
320 key_pw: &Password,
321 hash_algorithm: HashAlgorithm,
322 ) -> Result<Signature, Error> {
323 let algorithm = self.algorithm();
326
327 let rng = thread_rng();
328
329 let typ = if text_mode {
330 SignatureType::Text
331 } else {
332 SignatureType::Binary
333 };
334
335 let mut config = match self.version() {
336 KeyVersion::V4 => SignatureConfig::v4(typ, algorithm, hash_algorithm),
337 KeyVersion::V6 => SignatureConfig::v6(rng, typ, algorithm, hash_algorithm)?,
338 v => return Err(Error::Message(format!("Unsupported key version {v:?}"))),
339 };
340 config.hashed_subpackets = vec![
341 Subpacket::regular(SubpacketData::IssuerFingerprint(self.fingerprint()))?,
342 Subpacket::critical(SubpacketData::SignatureCreationTime(
343 Utc::now().trunc_subsecs(0),
344 ))?,
345 ];
346 config.unhashed_subpackets =
347 vec![Subpacket::regular(SubpacketData::Issuer(self.key_id()))?];
348
349 let signature = config.sign(self, key_pw, data)?;
350
351 Ok(signature)
352 }
353}
354
355impl Debug for CardSlot<'_, '_> {
356 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
357 write!(f, "CardSlot for {:?}", self.public_key)?;
359
360 Ok(())
361 }
362}
363
364impl KeyDetails for CardSlot<'_, '_> {
365 fn version(&self) -> KeyVersion {
366 KeyVersion::V4 }
368
369 fn fingerprint(&self) -> Fingerprint {
370 self.public_key.fingerprint()
371 }
372
373 fn key_id(&self) -> KeyId {
374 self.public_key.key_id()
375 }
376
377 fn algorithm(&self) -> PublicKeyAlgorithm {
378 self.public_key.algorithm()
379 }
380}
381
382impl PublicKeyTrait for CardSlot<'_, '_> {
383 fn created_at(&self) -> &DateTime<Utc> {
384 self.public_key.created_at()
385 }
386
387 fn expiration(&self) -> Option<u16> {
388 None
389 }
390
391 fn verify_signature(
392 &self,
393 hash: HashAlgorithm,
394 data: &[u8],
395 sig: &SignatureBytes,
396 ) -> pgp::errors::Result<()> {
397 self.public_key.verify_signature(hash, data, sig)
398 }
399
400 fn public_params(&self) -> &PublicParams {
401 self.public_key.public_params()
402 }
403}
404
405impl SecretKeyTrait for CardSlot<'_, '_> {
406 fn create_signature(
407 &self,
408 _key_pw: &Password,
409 hash: HashAlgorithm,
410 data: &[u8],
411 ) -> pgp::errors::Result<SignatureBytes> {
412 #[allow(clippy::unwrap_used)]
413 let mut tx = self.tx.lock().unwrap();
414
415 let hash = match self.public_key.algorithm() {
416 PublicKeyAlgorithm::RSA => to_hash_rsa(data, hash)?,
417 PublicKeyAlgorithm::ECDSA => Hash::ECDSA({
418 match self.public_key.public_params() {
419 PublicParams::ECDSA(EcdsaPublicParams::P256 { .. }) => {
420 if data.len() < 32 {
421 return Err(
422 Error::Message("hash too short for P256".to_string()).into()
423 );
424 }
425
426 &data[..32]
427 }
428 PublicParams::ECDSA(EcdsaPublicParams::P384 { .. }) => {
429 if data.len() < 48 {
430 return Err(
431 Error::Message("hash too short for P384".to_string()).into()
432 );
433 }
434
435 &data[..48]
436 }
437 PublicParams::ECDSA(EcdsaPublicParams::P521 { .. }) => {
438 if data.len() < 64 {
439 return Err(
440 Error::Message("hash too short for P521".to_string()).into()
441 );
442 }
443
444 &data[..64]
445 }
446 _ => data,
447 }
448 }),
449 PublicKeyAlgorithm::EdDSALegacy => Hash::EdDSA(data),
450
451 _ => {
452 return Err(Error::Message(format!(
453 "Unsupported PublicKeyAlgorithm for signature creation: {:?}",
454 self.public_key.algorithm()
455 ))
456 .into())
457 }
458 };
459
460 if self.touch_required(&mut tx) {
461 (self.touch_prompt)();
462 }
463
464 let sig = match self.key_type {
465 KeyType::Signing => tx
466 .card()
467 .signature_for_hash(hash)
468 .map_err(|e| Error::Message(format!("openpgp-card error: {e:?}")))?,
469 KeyType::Authentication => tx
470 .card()
471 .authenticate_for_hash(hash)
472 .map_err(|e| Error::Message(format!("openpgp-card error: {e:?}")))?,
473 _ => {
474 return Err(Error::Message(format!(
475 "Unsupported KeyType for signature creation: {:?}",
476 self.key_type
477 ))
478 .into())
479 }
480 };
481
482 let mpis = match self.public_key.algorithm() {
483 PublicKeyAlgorithm::RSA => vec![Mpi::from_slice(&sig)],
484
485 PublicKeyAlgorithm::ECDSA => {
486 let mid = sig.len() / 2;
487
488 vec![Mpi::from_slice(&sig[..mid]), Mpi::from_slice(&sig[mid..])]
489 }
490 PublicKeyAlgorithm::EdDSALegacy => {
491 if sig.len() != 64 {
493 return Err(Error::Message(format!(
494 "Unexpected signature length {} for EdDSA",
495 sig.len()
496 ))
497 .into());
498 }
499
500 vec![Mpi::from_slice(&sig[..32]), Mpi::from_slice(&sig[32..])]
501 }
502
503 alg => {
504 return Err(Error::Message(format!(
505 "Unsupported algorithm for signature creation: {alg:?}"
506 ))
507 .into())
508 }
509 };
510
511 Ok(SignatureBytes::Mpis(mpis))
512 }
513
514 fn hash_alg(&self) -> HashAlgorithm {
515 self.public_key.public_params().hash_alg()
516 }
517}
518
519fn to_hash_rsa(data: &[u8], hash: HashAlgorithm) -> Result<Hash<'_>, Error> {
520 match hash {
521 HashAlgorithm::Sha256 => {
522 if data.len() == 0x20 {
523 #[allow(clippy::unwrap_used)]
524 Ok(Hash::SHA256(data.try_into().unwrap()))
525 } else {
526 Err(Error::Message(format!(
527 "Illegal digest len for SHA256: {}",
528 data.len()
529 )))
530 }
531 }
532 HashAlgorithm::Sha384 => {
533 if data.len() == 0x30 {
534 #[allow(clippy::unwrap_used)]
535 Ok(Hash::SHA384(data.try_into().unwrap()))
536 } else {
537 Err(Error::Message(format!(
538 "Illegal digest len for SHA384: {}",
539 data.len()
540 )))
541 }
542 }
543 HashAlgorithm::Sha512 => {
544 if data.len() == 0x40 {
545 #[allow(clippy::unwrap_used)]
546 Ok(Hash::SHA512(data.try_into().unwrap()))
547 } else {
548 Err(Error::Message(format!(
549 "Illegal digest len for SHA512: {}",
550 data.len()
551 )))
552 }
553 }
554 _ => Err(Error::Message(format!(
555 "Unsupported HashAlgorithm for RSA: {hash:?}"
556 ))),
557 }
558}