webauthn_rs_core/
crypto.rs

1//! Cryptographic operation wrapper for Webauthn. This module exists to
2//! allow ease of auditing, safe operation wrappers for the webauthn library,
3//! and cryptographic provider abstraction. This module currently uses OpenSSL
4//! as the cryptographic primitive provider.
5
6#![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
15// Why OpenSSL over another rust crate?
16// - The openssl crate allows us to reconstruct a public key from the
17//   x/y group coords, where most others want a pkcs formatted structure. As
18//   a result, it's easiest to use openssl as it gives us exactly what we need
19//   for these operations, and despite it's many challenges as a library, it
20//   has resources and investment into it's maintenance, so we can a least
21//   assert a higher level of confidence in it that <backyard crypto here>.
22
23fn 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    // ed25519/ed448 require oneshot mode.
54    verifier
55        .verify_oneshot(signature, verification_data)
56        .map_err(WebauthnError::OpenSSLError)
57}
58
59/// Validate an x509 signature is valid for the supplied data
60pub 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
149// pub(crate) const TCG_AT_TPM_MANUFACTURER: Oid = der_parser::oid!(2.23.133 .2 .1);
150// pub(crate) const TCG_AT_TPM_MODEL: Oid = der_parser::oid!(2.23.133 .2 .2);
151// pub(crate) const TCG_AT_TPM_VERSION: Oid = der_parser::oid!(2.23.133 .2 .3);
152
153pub(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
200/*
201impl EDDSACurve {
202    fn to_openssl_nid(&self) -> nid::Nid {
203        match self {
204            EDDSACurve::ED25519 => nid::Nid::X9_62_PRIME256V1,
205            EDDSACurve::ED448 => nid::Nid::SECP384R1,
206        }
207    }
208}
209*/
210
211pub(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            // sha1
218            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        // See also https://tools.ietf.org/html/rfc8152#section-3.1
234        // These values look like:
235        // Object({
236        //     // negative (-) values are per-algo specific
237        //     Integer(-3): Bytes([48, 185, 178, 204, 113, 186, 105, 138, 190, 33, 160, 46, 131, 253, 100, 177, 91, 243, 126, 128, 245, 119, 209, 59, 186, 41, 215, 196, 24, 222, 46, 102]),
238        //     Integer(-2): Bytes([158, 212, 171, 234, 165, 197, 86, 55, 141, 122, 253, 6, 92, 242, 242, 114, 158, 221, 238, 163, 127, 214, 120, 157, 145, 226, 232, 250, 144, 150, 218, 138]),
239        //     Integer(-1): U64(1),
240        //     Integer(1): U64(2), // algorithm identifier
241        //     Integer(3): I64(-7) // content type see https://tools.ietf.org/html/rfc8152#section-8.1 -7 being ES256 + SHA256
242        // })
243        // Now each of these integers has a specific meaning, and you need to parse them in order.
244        // First, value 1 for the key type.
245
246        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        /*
251            // Some keys may return this as a string rather than int.
252            // The only key so far is the solokey and it's ed25519 support
253            // is broken, so there isn't much point enabling this today.
254            .or_else(|_| {
255                // tstr is also supported as a type on this field.
256                cbor_try_string!(key_type_value)
257                    .and_then(|kt_str| {
258                        match kt_str.as_str() {
259                            "OKP" => Ok(1),
260                            "EC2" => Ok(2),
261                            "RSA" => Ok(3),
262                            _ => Err(WebauthnError::COSEKeyInvalidCBORValue)
263                        }
264                    })
265            })?;
266        */
267
268        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        // https://www.iana.org/assignments/cose/cose.xhtml
277        // https://www.w3.org/TR/webauthn/#sctn-encoded-credPubKey-examples
278        // match key_type {
279        // 1 => {} OctetKey
280        if key_type == (COSEKeyTypeId::EC_EC2 as i128)
281            && (type_ == COSEAlgorithm::ES256
282                || type_ == COSEAlgorithm::ES384
283                || type_ == COSEAlgorithm::ES512)
284        {
285            // This indicates this is an EC2 key consisting of crv, x, y, which are stored in
286            // crv (-1), x (-2) and y (-3)
287            // Get these values now ....
288
289            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            // Right, now build the struct.
312            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            // The rfc additionally states:
322            //   "   Applications MUST check that the curve and the key type are
323            //     consistent and reject a key if they are not."
324            // this means feeding the values to openssl to validate them for us!
325
326            cose_key.validate()?;
327            // return it
328            Ok(cose_key)
329        } else if key_type == (COSEKeyTypeId::EC_RSA as i128) && (type_ == COSEAlgorithm::RS256) {
330            // RSAKey
331
332            // -37 -> PS256
333            // -257 -> RS256 aka RSASSA-PKCS1-v1_5 with SHA-256
334
335            // -1 -> n 256 bytes
336            // -2 -> e 3 bytes
337
338            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            // Set the n and e, we know they are proper sizes.
353            let mut e_temp = [0; 3];
354            e_temp.copy_from_slice(e.as_slice());
355
356            // Right, now build the struct.
357            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            // return it
367            Ok(cose_key)
368        } else if key_type == (COSEKeyTypeId::EC_OKP as i128) && (type_ == COSEAlgorithm::EDDSA) {
369            // https://datatracker.ietf.org/doc/html/rfc8152#section-13.2
370
371            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            /*
377                // Some keys may return this as a string rather than int.
378                // The only key so far is the solokey and it's ed25519 support
379                // is broken, so there isn't much point enabling this today.
380                .or_else(|_| {
381                    // tstr is also supported as a type on this field.
382                    cbor_try_string!(curve_type_value)
383                        .and_then(|ct_str| {
384                            trace!(?ct_str);
385                            match ct_str.as_str() {
386                                "EdDSA" => Ok(-8),
387                                _ => Err(WebauthnError::COSEKeyInvalidCBORValue)
388                            }
389                        })
390                })?;
391            */
392
393            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            // The rfc additionally states:
411            //   "   Applications MUST check that the curve and the key type are
412            //     consistent and reject a key if they are not."
413            // this means feeding the values to openssl to validate them for us!
414            cose_key.validate()?;
415            // return it
416            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        // Let publicKeyU2F be the concatenation 0x04 || x || y.
489        // Note: This signifies uncompressed ECC key format.
490        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    /// Retrieve the public key of this COSEKey as an OpenSSL structure
511    pub fn get_openssl_pkey(&self) -> Result<pkey::PKey<pkey::Public>, WebauthnError> {
512        match &self.key {
513            COSEKeyType::EC_EC2(ec2k) => {
514                // Get the curve type
515                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                // Validate the key is sound. IIRC this actually checks the values
528                // are correctly on the curve as specified
529                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    /// Verifies data was signed with this [COSEKey].
558    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
568/// Compute the sha256 of a slice of data.
569pub 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"         // Map - 5 elements
594                "01 02"      //   1:   2,  ; kty: EC2 key type
595                "03 26"      //   3:  -7,  ; alg: ES256 signature algorithm
596                "20 01"      //  -1:   1,  ; crv: P-256 curve
597                "21 58 20   65eda5a12577c2bae829437fe338701a10aaa375e1bb5b5de108de439c08551d" // -2:   x,  ; x-coordinate
598                "22 58 20   1e52ed75701163f7f9e40ddf9f341b3dc9ba860af7e0ca7ca7e9eecd0084d19c" // -3:   y,  ; y-coordinate
599        );
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"         // Map - 5 elements
625                "01 02"      //   1:   2,  ; kty: EC2 key type
626                "03 38 22"   //   3:  -35,  ; alg: ES384 signature algorithm
627                "20 02"      //  -1:   2,  ; crv: P-384 curve
628                "21 58 30   ceeaf818731db7af2d02e029854823d71bdbf65fb0c6ff69" // -2: x, ; x-coordinate
629                           "42c9cf891efe18ea81430517d777f5c43550da801be5bf2f"
630                "22 58 30   dda1d0ead72e042efb7c36a38cc021abb2ca1a2e38159edd" // -3: y ; y-coordinate
631                           "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"         // Map - 5 elements
664                "01 02"      //   1:   2,  ; kty: EC2 key type
665                "03 38 23"   //   3:  -36,  ; alg: ES512 signature algorithm
666                "20 03"      //  -1:   3,  ; crv: P-521 curve
667                "21 58 42   0106cfaacf34b13f24bbb2f806fd9cfacff9a2a5ef9ecfcd85664609a0b2f6d4fd" // -2:   x,  ; x-coordinate
668                           "b8e1d58630905f13f38d8eed8714eceb716920a3a235581623261fed961f7b7d72"
669                "22 58 42   0089597a052a8d3c8b2b5692d467dea19f8e1b9ca17fa563a1a826855dade04811" // -3:   y,  ; y-coordinate
670                           "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"         // Map - 4 elements
702        "01 01"      //   1:   1,  ; kty: OKP key type
703        "03 27"      //   3:  -8,  ; alg: EDDSA signature algorithm
704        "20 06"      //  -1:   6,  ; crv: Ed25519 curve
705        "21 58 20   43565027f918beb00257d112b903d15b93f5cbc7562dfc8458fbefd714546e3c" // -2:   x,  ; Y-coordinate
706        );
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"         // Map - 4 elements
726            "01 01"      //   1:   1,  ; kty: OKP key type
727            "03 27"      //   3:  -8,  ; alg: EDDSA signature algorithm
728            "20 07"      //  -1:   7,  ; crv: Ed448 curve
729            "21 58 39   0c04658f79c3fd86c4b3d676057b76353126e9b905a7e204c07846c1a2ab3791b02fc5e9c6930345ea7bf8524b944220d4bd711c010c9b2a80" // -2:   x,  ; Y-coordinate
730        );
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}