1use chrono::{DateTime, SubsecRound, Utc};
6use openpgp_card::ocard::algorithm::{AlgorithmAttributes, Curve};
7use openpgp_card::ocard::crypto::{EccType, PublicKeyMaterial};
8use openpgp_card::ocard::data::{Fingerprint, KeyGenerationTime};
9use openpgp_card::ocard::KeyType;
10use pgp::composed::{SignedKeyDetails, SignedPublicKey, SignedPublicSubKey};
11use pgp::crypto::ecc_curve::ECCCurve;
12use pgp::crypto::hash::HashAlgorithm;
13use pgp::crypto::public_key::PublicKeyAlgorithm;
14use pgp::crypto::sym::SymmetricKeyAlgorithm;
15use pgp::packet::{
16 Features, KeyFlags, PacketHeader, PacketTrait, PubKeyInner, PublicKey, PublicSubkey,
17 SignatureConfig, SignatureType, Subpacket, SubpacketData, UserId,
18};
19use pgp::types::{
20 EcdhKdfType, EcdhPublicParams, EcdsaPublicParams, EddsaLegacyPublicParams, KeyDetails,
21 KeyVersion, PacketHeaderVersion, Password, PublicKeyTrait, PublicParams, RsaPublicParams,
22 SecretKeyTrait, SignedUser, Tag,
23};
24use rsa::RsaPublicKey;
25use secrecy::SecretString;
26
27use crate::{CardSlot, Error};
28
29const ECDH_PARAM: &[(Option<HashAlgorithm>, Option<SymmetricKeyAlgorithm>)] = &[
31 (
32 Some(HashAlgorithm::Sha256),
33 Some(SymmetricKeyAlgorithm::AES128),
34 ),
35 (
36 Some(HashAlgorithm::Sha512),
37 Some(SymmetricKeyAlgorithm::AES256),
38 ),
39 (
40 Some(HashAlgorithm::Sha384),
41 Some(SymmetricKeyAlgorithm::AES256),
42 ),
43 (
44 Some(HashAlgorithm::Sha384),
45 Some(SymmetricKeyAlgorithm::AES192),
46 ),
47 (
48 Some(HashAlgorithm::Sha256),
49 Some(SymmetricKeyAlgorithm::AES256),
50 ),
51];
52
53fn pubkey(
54 algo: PublicKeyAlgorithm,
55 created: DateTime<Utc>,
56 param: PublicParams,
57) -> Result<PublicKey, Error> {
58 Ok(PublicKey::from_inner(PubKeyInner::new(
59 KeyVersion::V4, algo,
61 created,
62 None,
63 param,
64 )?)?)
65}
66
67fn map_curve(c: &Curve) -> Result<ECCCurve, Error> {
68 Ok(match c {
69 Curve::NistP256r1 => ECCCurve::P256,
70 Curve::NistP384r1 => ECCCurve::P384,
71 Curve::NistP521r1 => ECCCurve::P521,
72 Curve::BrainpoolP256r1 => ECCCurve::BrainpoolP256r1,
73 Curve::BrainpoolP384r1 => ECCCurve::BrainpoolP384r1,
74 Curve::BrainpoolP512r1 => ECCCurve::BrainpoolP512r1,
75 Curve::Ed25519 => ECCCurve::Ed25519,
76 Curve::Curve25519 => ECCCurve::Curve25519,
77
78 _ => return Err(Error::Message(format!("Can't map curve {c:?}"))),
79 })
80}
81
82pub fn public_key_material_and_fp_to_key(
91 pkm: &PublicKeyMaterial,
92 key_type: KeyType,
93 created: &KeyGenerationTime,
94 fingerprint: &Fingerprint,
95) -> Result<PublicKey, Error> {
96 let param: &[_] = match (pkm, key_type) {
100 (PublicKeyMaterial::E(_), KeyType::Decryption) => ECDH_PARAM,
101 _ => &[(None, None)],
102 };
103
104 for (hash, alg_sym) in param {
105 if let Ok(key) = public_key_material_to_key(pkm, key_type, created, *hash, *alg_sym) {
106 if key.fingerprint().as_bytes() == fingerprint.as_bytes() {
108 return Ok(key);
110 }
111 }
112 }
113
114 Err(Error::Message(
115 "Couldn't find key with matching fingerprint".to_string(),
116 ))
117}
118
119pub fn public_key_material_to_key(
127 pkm: &PublicKeyMaterial,
128 key_type: KeyType,
129 created: &KeyGenerationTime,
130 hash: Option<HashAlgorithm>,
131 alg_sym: Option<SymmetricKeyAlgorithm>,
132) -> Result<PublicKey, Error> {
133 #[allow(clippy::expect_used)]
134 let created =
135 DateTime::<Utc>::from_timestamp(created.get() as i64, 0).expect("u32 time from card");
136
137 match pkm {
138 PublicKeyMaterial::R(rsa) => pubkey(
139 PublicKeyAlgorithm::RSA,
140 created,
141 PublicParams::RSA(RsaPublicParams {
142 key: RsaPublicKey::new(
143 rsa::BigUint::from_bytes_be(rsa.n()),
144 rsa::BigUint::from_bytes_be(rsa.v()),
145 )?,
146 }),
147 ),
148
149 PublicKeyMaterial::E(ecc) => match ecc.algo() {
150 AlgorithmAttributes::Ecc(ecc_attr) => {
151 let typ = ecc_attr.ecc_type();
152
153 let curve = map_curve(ecc_attr.curve())?;
154
155 let (pka, pp) = match typ {
156 EccType::ECDH => {
157 if key_type != KeyType::Decryption {
158 return Err(Error::Message(format!(
159 "ECDH is unsupported in key slot {key_type:?}"
160 )));
161 }
162
163 let hash = hash.unwrap_or(curve.hash_algo()?);
164 let alg_sym = alg_sym.unwrap_or(curve.sym_algo()?);
165
166 let ecdh_pp = match curve {
167 ECCCurve::Curve25519 => {
168 if ecc.data().len() != 32 {
169 return Err(Error::Message(format!(
170 "x25519 public key unexpected length {}",
171 ecc.data().len()
172 )));
173 }
174
175 let mut public_key_arr = [0u8; 32];
176 public_key_arr[..].copy_from_slice(ecc.data());
177
178 let p = x25519_dalek::PublicKey::from(public_key_arr);
179 EcdhPublicParams::Curve25519 {
180 p,
181 hash,
182 alg_sym,
183 ecdh_kdf_type: EcdhKdfType::Native,
184 }
185 }
186 ECCCurve::P256 => {
187 let p = p256::PublicKey::from_sec1_bytes(ecc.data())?;
188 EcdhPublicParams::P256 { p, hash, alg_sym }
189 }
190 ECCCurve::P384 => {
191 let p = p384::PublicKey::from_sec1_bytes(ecc.data())?;
192 EcdhPublicParams::P384 { p, hash, alg_sym }
193 }
194 ECCCurve::P521 => {
195 let p = p521::PublicKey::from_sec1_bytes(ecc.data())?;
196 EcdhPublicParams::P521 { p, hash, alg_sym }
197 }
198 _ => {
199 return Err(Error::Message(format!(
200 "Unsupported curve {:?} for ecdh",
201 curve.name()
202 )))
203 }
204 };
205
206 (PublicKeyAlgorithm::ECDH, PublicParams::ECDH(ecdh_pp))
207 }
208
209 EccType::ECDSA => {
210 let ecdsa_pp = match curve {
211 ECCCurve::P256 => {
212 let key = p256::PublicKey::from_sec1_bytes(ecc.data())?;
213 EcdsaPublicParams::P256 { key }
214 }
215 ECCCurve::P384 => {
216 let key = p384::PublicKey::from_sec1_bytes(ecc.data())?;
217 EcdsaPublicParams::P384 { key }
218 }
219 ECCCurve::P521 => {
220 let key = p521::PublicKey::from_sec1_bytes(ecc.data())?;
221 EcdsaPublicParams::P521 { key }
222 }
223 _ => {
225 return Err(Error::Message(format!(
226 "Unsupported curve {:?} for ecdsa",
227 curve.name()
228 )))
229 }
230 };
231
232 (PublicKeyAlgorithm::ECDSA, PublicParams::ECDSA(ecdsa_pp))
233 }
234
235 EccType::EdDSA => {
236 if curve != ECCCurve::Ed25519 {
237 return Err(Error::Message(format!(
238 "Inconsistent curve {} for EdDSA",
239 curve.name()
240 )));
241 }
242
243 let key: ed25519_dalek::VerifyingKey = ecc.data().try_into()?;
244
245 (
246 PublicKeyAlgorithm::EdDSALegacy,
247 PublicParams::EdDSALegacy(EddsaLegacyPublicParams::Ed25519 { key }),
248 )
249 }
250 };
251
252 pubkey(pka, created, pp)
253 }
254
255 _ => Err(Error::Message(format!(
256 "Unexpected AlgorithmAttributes type in Ecc {:?}",
257 ecc.algo(),
258 ))),
259 },
260 }
261}
262
263pub(crate) fn pubkey_from_card(
264 tx: &mut openpgp_card::Card<openpgp_card::state::Transaction>,
265 key_type: KeyType,
266) -> Result<PublicKey, Error> {
267 let pkm = tx.public_key_material(key_type)?;
268
269 let Some(created) = tx.key_generation_time(key_type)? else {
270 return Err(Error::Message(format!(
272 "No creation time set for OpenPGP card key type {key_type:?}",
273 )));
274 };
275
276 let Some(fingerprint) = tx.fingerprint(key_type)? else {
277 return Err(Error::Message(format!(
279 "No fingerprint found for key slot {key_type:?}"
280 )));
281 };
282
283 public_key_material_and_fp_to_key(&pkm, key_type, &created, &fingerprint)
284}
285
286pub fn public_to_fingerprint(
288 pkm: &PublicKeyMaterial,
289 kgt: KeyGenerationTime,
290 kt: KeyType,
291) -> Result<Fingerprint, openpgp_card::Error> {
292 let key = public_key_material_to_key(pkm, kt, &kgt, None, None).map_err(|e| {
293 openpgp_card::Error::InternalError(format!("public_key_material_to_key: {e}"))
294 })?;
295
296 let fp = key.fingerprint();
297
298 openpgp_card::ocard::data::Fingerprint::try_from(fp.as_bytes())
299}
300
301fn pri_to_sub(pubkey: PublicKey) -> pgp::errors::Result<PublicSubkey> {
303 let header = PacketHeader::from_parts(
306 pubkey.packet_header().version(),
307 Tag::PublicSubkey,
308 pubkey.packet_header().packet_length(),
309 )?;
310
311 PublicSubkey::new_with_header(
312 header,
313 pubkey.version(),
314 pubkey.algorithm(),
315 *pubkey.created_at(),
316 pubkey.expiration(),
317 pubkey.public_params().clone(),
318 )
319}
320
321#[allow(clippy::too_many_arguments)]
343pub fn bind_into_certificate(
344 tx: &mut openpgp_card::Card<openpgp_card::state::Transaction>,
345 sig: PublicKey,
346 dec: Option<PublicKey>,
347 aut: Option<PublicKey>,
348 user_ids: &[String],
349 user_pin: Option<SecretString>,
350 pinpad_prompt: &dyn Fn(),
351 touch_prompt: &(dyn Fn() + Send + Sync),
352) -> Result<SignedPublicKey, Error> {
353 let primary = sig;
354
355 if user_ids.is_empty() {
356 return Err(Error::Message(
357 "At least one User ID must be added to create a valid certificate".to_string(),
358 ));
359 }
360
361 let verify_signing_pin =
363 |txx: &mut openpgp_card::Card<openpgp_card::state::Transaction>|
364 -> Result<(), openpgp_card::Error> {
365 if let Some(pw1) = user_pin.clone() {
367 txx.verify_user_signing_pin(pw1)?;
368 } else {
369 txx.verify_user_signing_pinpad(pinpad_prompt)?;
370 }
371 Ok(())
372 };
373
374 let mut subkeys = vec![];
375
376 if let Some(dec) = dec {
377 let key = pri_to_sub(dec)?;
379
380 verify_signing_pin(tx)?;
381
382 let cs = CardSlot::with_public_key(tx, KeyType::Signing, primary.clone(), touch_prompt)?;
384
385 let mut kf = KeyFlags::default();
386 kf.set_encrypt_comms(true);
387 kf.set_encrypt_storage(true);
388
389 let mut config =
390 SignatureConfig::v4(SignatureType::SubkeyBinding, cs.algorithm(), cs.hash_alg());
391
392 config.hashed_subpackets = vec![
393 Subpacket::critical(SubpacketData::SignatureCreationTime(
394 Utc::now().trunc_subsecs(0),
395 ))?,
396 Subpacket::critical(SubpacketData::KeyFlags(kf))?,
397 Subpacket::regular(SubpacketData::IssuerFingerprint(cs.fingerprint()))?,
398 ];
399 config.unhashed_subpackets = vec![Subpacket::regular(SubpacketData::Issuer(cs.key_id()))?];
400
401 let sig = config.sign_subkey_binding(&cs, cs.public_key(), &Password::empty(), &key)?;
402
403 let sps = SignedPublicSubKey {
404 key,
405 signatures: vec![sig],
406 };
407
408 subkeys.push(sps);
409 }
410
411 if let Some(aut) = aut {
412 let key = pri_to_sub(aut)?;
414
415 verify_signing_pin(tx)?;
416
417 let cs = CardSlot::with_public_key(tx, KeyType::Signing, primary.clone(), touch_prompt)?;
419
420 let mut kf = KeyFlags::default();
421 kf.set_authentication(true);
422
423 let mut config =
424 SignatureConfig::v4(SignatureType::SubkeyBinding, cs.algorithm(), cs.hash_alg());
425
426 config.hashed_subpackets = vec![
427 Subpacket::critical(SubpacketData::SignatureCreationTime(
428 Utc::now().trunc_subsecs(0),
429 ))?,
430 Subpacket::critical(SubpacketData::KeyFlags(kf))?,
431 Subpacket::regular(SubpacketData::IssuerFingerprint(cs.fingerprint()))?,
432 ];
433 config.unhashed_subpackets = vec![Subpacket::regular(SubpacketData::Issuer(cs.key_id()))?];
434
435 let sig = config.sign_subkey_binding(&cs, &cs.public_key(), &Password::empty(), &key)?;
436
437 let sps = SignedPublicSubKey {
438 key,
439 signatures: vec![sig],
440 };
441
442 subkeys.push(sps);
443 }
444
445 let mut users = vec![];
446
447 for (n, uid) in user_ids.iter().enumerate() {
449 let uid = UserId::from_str(PacketHeaderVersion::New, uid)?;
450
451 let mut kf = KeyFlags::default();
452 kf.set_certify(true);
453 kf.set_sign(true);
454
455 verify_signing_pin(tx)?;
456
457 let cs = CardSlot::with_public_key(tx, KeyType::Signing, primary.clone(), touch_prompt)?;
458 let mut config =
459 SignatureConfig::v4(SignatureType::CertPositive, cs.algorithm(), cs.hash_alg());
460
461 config.hashed_subpackets = vec![
462 Subpacket::critical(SubpacketData::SignatureCreationTime(*primary.created_at()))?,
464 Subpacket::critical(SubpacketData::KeyFlags(kf))?,
465 Subpacket::regular(SubpacketData::IsPrimary(n == 0))?,
467 Subpacket::regular(SubpacketData::Features(Features::from(&[0x01][..])))?,
469 ];
470 config.unhashed_subpackets = vec![Subpacket::regular(SubpacketData::Issuer(cs.key_id()))?];
471
472 let sig =
473 config.sign_certification(&cs, cs.public_key(), &Password::empty(), uid.tag(), &uid)?;
474
475 let suid = SignedUser::new(uid, vec![sig]);
476
477 users.push(suid);
478 }
479
480 let details = SignedKeyDetails::new(vec![], vec![], users, vec![]);
483
484 let spk = SignedPublicKey::new(primary, details, subkeys);
485
486 Ok(spk)
487}