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