1#![allow(non_camel_case_types)]
7
8use openssl::{bn, ec, hash, nid, pkey, rsa, sha, sign, x509};
9
10use super::error::*;
11use crate::proto::*;
12
13use x509_parser::prelude::{X509Error, X509Name};
14
15fn pkey_verify_signature(
24 pkey: &pkey::PKeyRef<pkey::Public>,
25 stype: COSEAlgorithm,
26 signature: &[u8],
27 verification_data: &[u8],
28) -> Result<bool, WebauthnError> {
29 let mut verifier = match stype {
30 COSEAlgorithm::ES256 => sign::Verifier::new(hash::MessageDigest::sha256(), pkey)
31 .map_err(WebauthnError::OpenSSLError),
32 COSEAlgorithm::RS256 => {
33 let mut verifier = sign::Verifier::new(hash::MessageDigest::sha256(), pkey)
34 .map_err(WebauthnError::OpenSSLError)?;
35 verifier
36 .set_rsa_padding(rsa::Padding::PKCS1)
37 .map_err(WebauthnError::OpenSSLError)?;
38 Ok(verifier)
39 }
40 COSEAlgorithm::EDDSA => {
41 sign::Verifier::new_without_digest(pkey).map_err(WebauthnError::OpenSSLError)
42 }
43 COSEAlgorithm::INSECURE_RS1 => {
44 error!("INSECURE SHA1 USAGE DETECTED");
45 Err(WebauthnError::CredentialInsecureCryptography)
46 }
47 c_alg => {
48 debug!(?c_alg, "WebauthnError::COSEKeyInvalidType");
49 Err(WebauthnError::COSEKeyInvalidType)
50 }
51 }?;
52
53 verifier
55 .verify_oneshot(signature, verification_data)
56 .map_err(WebauthnError::OpenSSLError)
57}
58
59pub fn verify_signature(
61 alg: COSEAlgorithm,
62 pubk: &x509::X509,
63 signature: &[u8],
64 verification_data: &[u8],
65) -> Result<bool, WebauthnError> {
66 let pkey = pubk.public_key().map_err(WebauthnError::OpenSSLError)?;
67
68 pkey_verify_signature(&pkey, alg, signature, verification_data)
69}
70
71pub(crate) fn check_extension<T, F>(
72 extension: &Result<Option<T>, X509Error>,
73 must_be_present: bool,
74 f: F,
75) -> WebauthnResult<()>
76where
77 F: Fn(&T) -> bool,
78{
79 match extension {
80 Ok(Some(extension)) => {
81 if f(extension) {
82 Ok(())
83 } else {
84 trace!("Custome extension check failed");
85 Err(WebauthnError::AttestationCertificateRequirementsNotMet)
86 }
87 }
88 Ok(None) => {
89 if must_be_present {
90 trace!("Extension not present");
91 Err(WebauthnError::AttestationCertificateRequirementsNotMet)
92 } else {
93 Ok(())
94 }
95 }
96 Err(_) => {
97 debug!("extension present multiple times or invalid");
98 Err(WebauthnError::AttestationCertificateRequirementsNotMet)
99 }
100 }
101}
102
103pub(crate) struct TpmSanData<'a> {
104 pub manufacturer: &'a str,
105 pub _model: &'a str,
106 pub _version: &'a str,
107}
108
109#[derive(Default)]
110struct TpmSanDataBuilder<'a> {
111 manufacturer: Option<&'a str>,
112 model: Option<&'a str>,
113 version: Option<&'a str>,
114}
115
116impl<'a> TpmSanDataBuilder<'a> {
117 pub(crate) fn new() -> Self {
118 Default::default()
119 }
120
121 pub(crate) fn manufacturer(mut self, value: &'a str) -> Self {
122 self.manufacturer = Some(value);
123 self
124 }
125
126 pub(crate) fn model(mut self, value: &'a str) -> Self {
127 self.model = Some(value);
128 self
129 }
130
131 pub(crate) fn version(mut self, value: &'a str) -> Self {
132 self.version = Some(value);
133 self
134 }
135
136 pub(crate) fn build(self) -> WebauthnResult<TpmSanData<'a>> {
137 self.manufacturer
138 .zip(self.model)
139 .zip(self.version)
140 .map(|((manufacturer, model), version)| TpmSanData {
141 manufacturer,
142 _model: model,
143 _version: version,
144 })
145 .ok_or(WebauthnError::AttestationCertificateRequirementsNotMet)
146 }
147}
148
149pub(crate) const TCG_AT_TPM_MANUFACTURER_RAW: &[u8] = &der_parser::oid!(raw 2.23.133 .2 .1);
154pub(crate) const TCG_AT_TPM_MODEL_RAW: &[u8] = &der_parser::oid!(raw 2.23.133 .2 .2);
155pub(crate) const TCG_AT_TPM_VERSION_RAW: &[u8] = &der_parser::oid!(raw 2.23.133 .2 .3);
156
157impl<'a> TryFrom<&'a X509Name<'a>> for TpmSanData<'a> {
158 type Error = WebauthnError;
159
160 fn try_from(x509_name: &'a X509Name<'a>) -> Result<Self, Self::Error> {
161 x509_name
162 .iter_attributes()
163 .try_fold(TpmSanDataBuilder::new(), |builder, attribute| {
164 Ok(match attribute.attr_type().as_bytes() {
165 TCG_AT_TPM_MANUFACTURER_RAW => {
166 builder.manufacturer(attribute.attr_value().as_str()?)
167 }
168 TCG_AT_TPM_MODEL_RAW => builder.model(attribute.attr_value().as_str()?),
169 TCG_AT_TPM_VERSION_RAW => builder.version(attribute.attr_value().as_str()?),
170 _ => builder,
171 })
172 })
173 .map_err(|_: der_parser::error::Error| WebauthnError::ParseNOMFailure)
174 .and_then(TpmSanDataBuilder::build)
175 }
176}
177
178impl TryFrom<nid::Nid> for ECDSACurve {
179 type Error = WebauthnError;
180 fn try_from(nid: nid::Nid) -> Result<Self, Self::Error> {
181 match nid {
182 nid::Nid::X9_62_PRIME256V1 => Ok(ECDSACurve::SECP256R1),
183 nid::Nid::SECP384R1 => Ok(ECDSACurve::SECP384R1),
184 nid::Nid::SECP521R1 => Ok(ECDSACurve::SECP521R1),
185 _ => Err(WebauthnError::ECDSACurveInvalidNid),
186 }
187 }
188}
189
190impl ECDSACurve {
191 fn to_openssl_nid(&self) -> nid::Nid {
192 match self {
193 ECDSACurve::SECP256R1 => nid::Nid::X9_62_PRIME256V1,
194 ECDSACurve::SECP384R1 => nid::Nid::SECP384R1,
195 ECDSACurve::SECP521R1 => nid::Nid::SECP521R1,
196 }
197 }
198}
199
200pub(crate) fn only_hash_from_type(
212 alg: COSEAlgorithm,
213 _input: &[u8],
214) -> Result<Vec<u8>, WebauthnError> {
215 match alg {
216 COSEAlgorithm::INSECURE_RS1 => {
217 warn!("INSECURE SHA1 USAGE DETECTED");
219 Err(WebauthnError::CredentialInsecureCryptography)
220 }
221 c_alg => {
222 debug!(?c_alg, "WebauthnError::COSEKeyInvalidType");
223 Err(WebauthnError::COSEKeyInvalidType)
224 }
225 }
226}
227
228impl TryFrom<&serde_cbor_2::Value> for COSEKey {
229 type Error = WebauthnError;
230 fn try_from(d: &serde_cbor_2::Value) -> Result<COSEKey, Self::Error> {
231 let m = cbor_try_map!(d)?;
232
233 let key_type_value = m
247 .get(&serde_cbor_2::Value::Integer(1))
248 .ok_or(WebauthnError::COSEKeyInvalidCBORValue)?;
249 let key_type = cbor_try_i128!(key_type_value)?;
250 let content_type_value = m
269 .get(&serde_cbor_2::Value::Integer(3))
270 .ok_or(WebauthnError::COSEKeyInvalidCBORValue)?;
271 let content_type = cbor_try_i128!(content_type_value)?;
272
273 let type_ = COSEAlgorithm::try_from(content_type)
274 .map_err(|_| WebauthnError::COSEKeyInvalidAlgorithm)?;
275
276 if key_type == (COSEKeyTypeId::EC_EC2 as i128)
281 && (type_ == COSEAlgorithm::ES256
282 || type_ == COSEAlgorithm::ES384
283 || type_ == COSEAlgorithm::ES512)
284 {
285 let curve_type_value = m
290 .get(&serde_cbor_2::Value::Integer(-1))
291 .ok_or(WebauthnError::COSEKeyInvalidCBORValue)?;
292 let curve_type = cbor_try_i128!(curve_type_value)?;
293
294 let curve = ECDSACurve::try_from(curve_type)?;
295
296 let x_value = m
297 .get(&serde_cbor_2::Value::Integer(-2))
298 .ok_or(WebauthnError::COSEKeyInvalidCBORValue)?;
299 let x = cbor_try_bytes!(x_value)?;
300
301 let y_value = m
302 .get(&serde_cbor_2::Value::Integer(-3))
303 .ok_or(WebauthnError::COSEKeyInvalidCBORValue)?;
304 let y = cbor_try_bytes!(y_value)?;
305
306 let coord_len = curve.coordinate_size();
307 if x.len() != coord_len || y.len() != coord_len {
308 return Err(WebauthnError::COSEKeyECDSAXYInvalid);
309 }
310
311 let cose_key = COSEKey {
313 type_,
314 key: COSEKeyType::EC_EC2(COSEEC2Key {
315 curve,
316 x: x.to_vec().into(),
317 y: y.to_vec().into(),
318 }),
319 };
320
321 cose_key.validate()?;
327 Ok(cose_key)
329 } else if key_type == (COSEKeyTypeId::EC_RSA as i128) && (type_ == COSEAlgorithm::RS256) {
330 let n_value = m
339 .get(&serde_cbor_2::Value::Integer(-1))
340 .ok_or(WebauthnError::COSEKeyInvalidCBORValue)?;
341 let n = cbor_try_bytes!(n_value)?;
342
343 let e_value = m
344 .get(&serde_cbor_2::Value::Integer(-2))
345 .ok_or(WebauthnError::COSEKeyInvalidCBORValue)?;
346 let e = cbor_try_bytes!(e_value)?;
347
348 if n.len() != 256 || e.len() != 3 {
349 return Err(WebauthnError::COSEKeyRSANEInvalid);
350 }
351
352 let mut e_temp = [0; 3];
354 e_temp.copy_from_slice(e.as_slice());
355
356 let cose_key = COSEKey {
358 type_,
359 key: COSEKeyType::RSA(COSERSAKey {
360 n: n.to_vec().into(),
361 e: e_temp,
362 }),
363 };
364
365 cose_key.validate()?;
366 Ok(cose_key)
368 } else if key_type == (COSEKeyTypeId::EC_OKP as i128) && (type_ == COSEAlgorithm::EDDSA) {
369 let curve_type_value = m
372 .get(&serde_cbor_2::Value::Integer(-1))
373 .ok_or(WebauthnError::COSEKeyInvalidCBORValue)?;
374 let curve = cbor_try_i128!(curve_type_value).and_then(EDDSACurve::try_from)?;
375
376 let x_value = m
394 .get(&serde_cbor_2::Value::Integer(-2))
395 .ok_or(WebauthnError::COSEKeyInvalidCBORValue)?;
396 let x = cbor_try_bytes!(x_value)?;
397
398 if x.len() != curve.coordinate_size() {
399 return Err(WebauthnError::COSEKeyEDDSAXInvalid);
400 }
401
402 let cose_key = COSEKey {
403 type_,
404 key: COSEKeyType::EC_OKP(COSEOKPKey {
405 curve,
406 x: x.to_vec().into(),
407 }),
408 };
409
410 cose_key.validate()?;
415 Ok(cose_key)
417 } else {
418 debug!(?key_type, ?type_, "WebauthnError::COSEKeyInvalidType");
419 Err(WebauthnError::COSEKeyInvalidType)
420 }
421 }
422}
423
424impl TryFrom<(COSEAlgorithm, &x509::X509)> for COSEKey {
425 type Error = WebauthnError;
426 fn try_from((alg, pubk): (COSEAlgorithm, &x509::X509)) -> Result<COSEKey, Self::Error> {
427 let key = match alg {
428 COSEAlgorithm::ES256 | COSEAlgorithm::ES384 | COSEAlgorithm::ES512 => {
429 let ec_key = pubk
430 .public_key()
431 .and_then(|pk| pk.ec_key())
432 .map_err(WebauthnError::OpenSSLError)?;
433
434 ec_key.check_key().map_err(WebauthnError::OpenSSLError)?;
435
436 let ec_grpref = ec_key.group();
437
438 let mut ctx =
439 openssl::bn::BigNumContext::new().map_err(WebauthnError::OpenSSLError)?;
440 let mut xbn = openssl::bn::BigNum::new().map_err(WebauthnError::OpenSSLError)?;
441 let mut ybn = openssl::bn::BigNum::new().map_err(WebauthnError::OpenSSLError)?;
442
443 ec_key
444 .public_key()
445 .affine_coordinates_gfp(ec_grpref, &mut xbn, &mut ybn, &mut ctx)
446 .map_err(WebauthnError::OpenSSLError)?;
447
448 let curve = ec_grpref
449 .curve_name()
450 .ok_or(WebauthnError::OpenSSLErrorNoCurveName)
451 .and_then(ECDSACurve::try_from)?;
452
453 if xbn.num_bytes() as usize != curve.coordinate_size()
454 || ybn.num_bytes() as usize != curve.coordinate_size()
455 {
456 return Err(WebauthnError::COSEKeyECDSAXYInvalid);
457 }
458
459 Ok(COSEKeyType::EC_EC2(COSEEC2Key {
460 curve,
461 x: xbn.to_vec().into(),
462 y: ybn.to_vec().into(),
463 }))
464 }
465 COSEAlgorithm::RS256
466 | COSEAlgorithm::RS384
467 | COSEAlgorithm::RS512
468 | COSEAlgorithm::PS256
469 | COSEAlgorithm::PS384
470 | COSEAlgorithm::PS512
471 | COSEAlgorithm::EDDSA
472 | COSEAlgorithm::PinUvProtocol
473 | COSEAlgorithm::INSECURE_RS1 => {
474 error!(
475 "unsupported X509 to COSE conversion for COSE algorithm type {:?}",
476 alg
477 );
478 Err(WebauthnError::COSEKeyInvalidType)
479 }
480 }?;
481
482 Ok(COSEKey { type_: alg, key })
483 }
484}
485
486impl COSEKey {
487 pub(crate) fn get_alg_key_ecc_x962_raw(&self) -> Result<Vec<u8>, WebauthnError> {
488 match &self.key {
491 COSEKeyType::EC_EC2(ecpk) => {
492 let r: [u8; 1] = [0x04];
493 Ok(r.iter()
494 .chain(ecpk.x.iter())
495 .chain(ecpk.y.iter())
496 .copied()
497 .collect())
498 }
499 _ => {
500 debug!("get_alg_key_ecc_x962_raw");
501 Err(WebauthnError::COSEKeyInvalidType)
502 }
503 }
504 }
505
506 pub(crate) fn validate(&self) -> Result<(), WebauthnError> {
507 self.get_openssl_pkey().map(|_| ())
508 }
509
510 pub fn get_openssl_pkey(&self) -> Result<pkey::PKey<pkey::Public>, WebauthnError> {
512 match &self.key {
513 COSEKeyType::EC_EC2(ec2k) => {
514 let curve = ec2k.curve.to_openssl_nid();
516 let ec_group =
517 ec::EcGroup::from_curve_name(curve).map_err(WebauthnError::OpenSSLError)?;
518
519 let xbn =
520 bn::BigNum::from_slice(ec2k.x.as_ref()).map_err(WebauthnError::OpenSSLError)?;
521 let ybn =
522 bn::BigNum::from_slice(ec2k.y.as_ref()).map_err(WebauthnError::OpenSSLError)?;
523
524 let ec_key = ec::EcKey::from_public_key_affine_coordinates(&ec_group, &xbn, &ybn)
525 .map_err(WebauthnError::OpenSSLError)?;
526
527 ec_key.check_key().map_err(WebauthnError::OpenSSLError)?;
530
531 let p = pkey::PKey::from_ec_key(ec_key).map_err(WebauthnError::OpenSSLError)?;
532 Ok(p)
533 }
534 COSEKeyType::RSA(rsak) => {
535 let nbn =
536 bn::BigNum::from_slice(rsak.n.as_ref()).map_err(WebauthnError::OpenSSLError)?;
537 let ebn = bn::BigNum::from_slice(&rsak.e).map_err(WebauthnError::OpenSSLError)?;
538
539 let rsa_key = rsa::Rsa::from_public_components(nbn, ebn)
540 .map_err(WebauthnError::OpenSSLError)?;
541
542 let p = pkey::PKey::from_rsa(rsa_key).map_err(WebauthnError::OpenSSLError)?;
543 Ok(p)
544 }
545 COSEKeyType::EC_OKP(edk) => {
546 let id = match &edk.curve {
547 EDDSACurve::ED25519 => pkey::Id::ED25519,
548 EDDSACurve::ED448 => pkey::Id::ED448,
549 };
550
551 pkey::PKey::public_key_from_raw_bytes(edk.x.as_ref(), id)
552 .map_err(WebauthnError::OpenSSLError)
553 }
554 }
555 }
556
557 pub fn verify_signature(
559 &self,
560 signature: &[u8],
561 verification_data: &[u8],
562 ) -> Result<bool, WebauthnError> {
563 let pkey = self.get_openssl_pkey()?;
564 pkey_verify_signature(&pkey, self.type_, signature, verification_data)
565 }
566}
567
568pub fn compute_sha256(data: &[u8]) -> [u8; 32] {
570 let mut hasher = sha::Sha256::new();
571 hasher.update(data);
572 hasher.finish()
573}
574
575#[cfg(test)]
576mod tests {
577 #![allow(clippy::panic)]
578
579 use super::*;
580 use hex_literal::hex;
581 use serde_cbor_2::Value;
582 #[test]
583 fn nid_to_curve() {
584 assert_eq!(
585 ECDSACurve::try_from(nid::Nid::X9_62_PRIME256V1).unwrap(),
586 ECDSACurve::SECP256R1
587 );
588 }
589
590 #[test]
591 fn cbor_es256() {
592 let hex_data = hex!(
593 "A5" "01 02" "03 26" "20 01" "21 58 20 65eda5a12577c2bae829437fe338701a10aaa375e1bb5b5de108de439c08551d" "22 58 20 1e52ed75701163f7f9e40ddf9f341b3dc9ba860af7e0ca7ca7e9eecd0084d19c" );
600
601 let val: Value = serde_cbor_2::from_slice(&hex_data).unwrap();
602 let key = COSEKey::try_from(&val).unwrap();
603
604 assert_eq!(key.type_, COSEAlgorithm::ES256);
605 match key.key {
606 COSEKeyType::EC_EC2(pkey) => {
607 assert_eq!(
608 pkey.x.as_ref(),
609 hex!("65eda5a12577c2bae829437fe338701a10aaa375e1bb5b5de108de439c08551d")
610 );
611 assert_eq!(
612 pkey.y.as_ref(),
613 hex!("1e52ed75701163f7f9e40ddf9f341b3dc9ba860af7e0ca7ca7e9eecd0084d19c")
614 );
615 assert_eq!(pkey.curve, ECDSACurve::SECP256R1);
616 }
617 _ => panic!("Key should be parsed EC2 key"),
618 }
619 }
620
621 #[test]
622 fn cbor_es384() {
623 let hex_data = hex!(
624 "A5" "01 02" "03 38 22" "20 02" "21 58 30 ceeaf818731db7af2d02e029854823d71bdbf65fb0c6ff69" "42c9cf891efe18ea81430517d777f5c43550da801be5bf2f"
630 "22 58 30 dda1d0ead72e042efb7c36a38cc021abb2ca1a2e38159edd" "a8c25f391e9a38d79dd56b9427d1c7c70cfa778ab849b087"
632 );
633
634 let val: Value = serde_cbor_2::from_slice(&hex_data).unwrap();
635 let key = COSEKey::try_from(&val).unwrap();
636
637 assert_eq!(key.type_, COSEAlgorithm::ES384);
638 match key.key {
639 COSEKeyType::EC_EC2(pkey) => {
640 assert_eq!(
641 pkey.x.as_ref(),
642 hex!(
643 "ceeaf818731db7af2d02e029854823d71bdbf65fb0c6ff69
644 42c9cf891efe18ea81430517d777f5c43550da801be5bf2f"
645 )
646 );
647 assert_eq!(
648 pkey.y.as_ref(),
649 hex!(
650 "dda1d0ead72e042efb7c36a38cc021abb2ca1a2e38159edd
651 a8c25f391e9a38d79dd56b9427d1c7c70cfa778ab849b087"
652 )
653 );
654 assert_eq!(pkey.curve, ECDSACurve::SECP384R1);
655 }
656 _ => panic!("Key should be parsed EC2 key"),
657 }
658 }
659
660 #[test]
661 fn cbor_es512() {
662 let hex_data = hex!(
663 "A5" "01 02" "03 38 23" "20 03" "21 58 42 0106cfaacf34b13f24bbb2f806fd9cfacff9a2a5ef9ecfcd85664609a0b2f6d4fd" "b8e1d58630905f13f38d8eed8714eceb716920a3a235581623261fed961f7b7d72"
669 "22 58 42 0089597a052a8d3c8b2b5692d467dea19f8e1b9ca17fa563a1a826855dade04811" "b2881819e72f1706daeaf7d3773b2e284983a0eec33c2fe3ff5697722e95b29536");
671
672 let val: Value = serde_cbor_2::from_slice(&hex_data).unwrap();
673 let key = COSEKey::try_from(&val).unwrap();
674
675 assert_eq!(key.type_, COSEAlgorithm::ES512);
676 match key.key {
677 COSEKeyType::EC_EC2(pkey) => {
678 assert_eq!(
679 pkey.x.as_ref(),
680 hex!(
681 "0106cfaacf34b13f24bbb2f806fd9cfacff9a2a5ef9ecfcd85664609a0b2f6d4fd
682 b8e1d58630905f13f38d8eed8714eceb716920a3a235581623261fed961f7b7d72"
683 )
684 );
685 assert_eq!(
686 pkey.y.as_ref(),
687 hex!(
688 "0089597a052a8d3c8b2b5692d467dea19f8e1b9ca17fa563a1a826855dade04811
689 b2881819e72f1706daeaf7d3773b2e284983a0eec33c2fe3ff5697722e95b29536"
690 )
691 );
692 assert_eq!(pkey.curve, ECDSACurve::SECP521R1);
693 }
694 _ => panic!("Key should be parsed EC2 key"),
695 }
696 }
697
698 #[test]
699 fn cbor_ed25519() {
700 let hex_data = hex!(
701 "A4" "01 01" "03 27" "20 06" "21 58 20 43565027f918beb00257d112b903d15b93f5cbc7562dfc8458fbefd714546e3c" );
707 let val: Value = serde_cbor_2::from_slice(&hex_data).unwrap();
708 let key = COSEKey::try_from(&val).unwrap();
709 assert_eq!(key.type_, COSEAlgorithm::EDDSA);
710 match key.key {
711 COSEKeyType::EC_OKP(pkey) => {
712 assert_eq!(
713 pkey.x.as_ref(),
714 hex!("43565027f918beb00257d112b903d15b93f5cbc7562dfc8458fbefd714546e3c")
715 );
716 assert_eq!(pkey.curve, EDDSACurve::ED25519);
717 }
718 _ => panic!("Key should be parsed OKP key"),
719 }
720 }
721
722 #[test]
723 fn cbor_ed448() {
724 let hex_data = hex!(
725 "A4" "01 01" "03 27" "20 07" "21 58 39 0c04658f79c3fd86c4b3d676057b76353126e9b905a7e204c07846c1a2ab3791b02fc5e9c6930345ea7bf8524b944220d4bd711c010c9b2a80" );
731 let val: Value = serde_cbor_2::from_slice(&hex_data).unwrap();
732 let key = COSEKey::try_from(&val).unwrap();
733 assert_eq!(key.type_, COSEAlgorithm::EDDSA);
734 match key.key {
735 COSEKeyType::EC_OKP(pkey) => {
736 assert_eq!(
737 pkey.x.as_ref(),
738 hex!("0c04658f79c3fd86c4b3d676057b76353126e9b905a7e204c07846c1a2ab3791b02fc5e9c6930345ea7bf8524b944220d4bd711c010c9b2a80")
739 );
740 assert_eq!(pkey.curve, EDDSACurve::ED448);
741 }
742 _ => panic!("Key should be parsed OKP key"),
743 }
744 }
745}