#![allow(non_camel_case_types)]
use core::convert::TryFrom;
use openssl::{bn, ec, hash, nid, pkey, rsa, sha, sign, x509};
use x509_parser::x509::X509Version;
use super::error::*;
use crate::attestation::{AttestationX509Extension, FidoGenCeAaguid};
use crate::proto::*;
use crate::internals::{tpm_device_attribute_parser, TpmVendor};
fn pkey_verify_signature(
pkey: &pkey::PKeyRef<pkey::Public>,
stype: COSEAlgorithm,
signature: &[u8],
verification_data: &[u8],
) -> Result<bool, WebauthnError> {
let mut verifier = match stype {
COSEAlgorithm::ES256 => sign::Verifier::new(hash::MessageDigest::sha256(), pkey)
.map_err(WebauthnError::OpenSSLError),
COSEAlgorithm::RS256 => {
let mut verifier = sign::Verifier::new(hash::MessageDigest::sha256(), pkey)
.map_err(WebauthnError::OpenSSLError)?;
verifier
.set_rsa_padding(rsa::Padding::PKCS1)
.map_err(WebauthnError::OpenSSLError)?;
Ok(verifier)
}
COSEAlgorithm::INSECURE_RS1 => {
if cfg!(feature = "insecure-rs1") {
warn!("INSECURE SHA1 USAGE DETECTED");
let mut verifier = sign::Verifier::new(hash::MessageDigest::sha1(), pkey)
.map_err(WebauthnError::OpenSSLError)?;
verifier
.set_rsa_padding(rsa::Padding::PKCS1)
.map_err(WebauthnError::OpenSSLError)?;
Ok(verifier)
} else {
error!("INSECURE SHA1 USAGE DETECTED");
Err(WebauthnError::CredentialInsecureCryptography)
}
}
c_alg => {
debug!(?c_alg, "WebauthnError::COSEKeyInvalidType");
Err(WebauthnError::COSEKeyInvalidType)
}
}?;
verifier
.update(verification_data)
.map_err(WebauthnError::OpenSSLError)?;
verifier
.verify(signature)
.map_err(WebauthnError::OpenSSLError)
}
pub(crate) fn verify_signature(
alg: COSEAlgorithm,
pubk: &x509::X509,
signature: &[u8],
verification_data: &[u8],
) -> Result<bool, WebauthnError> {
let pkey = pubk.public_key().map_err(WebauthnError::OpenSSLError)?;
pkey_verify_signature(&pkey, alg, signature, verification_data)
}
use x509_parser::prelude::{GeneralName, X509Error, X509Name};
fn check_extension<T, F>(
extension: &Result<Option<T>, X509Error>,
must_be_present: bool,
f: F,
) -> WebauthnResult<()>
where
F: Fn(&T) -> bool,
{
match extension {
Ok(Some(extension)) => {
if f(extension) {
Ok(())
} else {
trace!("Custome extension check failed");
Err(WebauthnError::AttestationCertificateRequirementsNotMet)
}
}
Ok(None) => {
if must_be_present {
trace!("Extension not present");
Err(WebauthnError::AttestationCertificateRequirementsNotMet)
} else {
Ok(())
}
}
Err(_) => {
debug!("extension present multiple times or invalid");
Err(WebauthnError::AttestationCertificateRequirementsNotMet)
}
}
}
pub struct TpmSanData<'a> {
pub manufacturer: &'a str,
pub model: &'a str,
pub version: &'a str,
}
#[derive(Default)]
struct TpmSanDataBuilder<'a> {
manufacturer: Option<&'a str>,
model: Option<&'a str>,
version: Option<&'a str>,
}
impl<'a> TpmSanDataBuilder<'a> {
pub(crate) fn new() -> Self {
Default::default()
}
pub(crate) fn manufacturer(mut self, value: &'a str) -> Self {
self.manufacturer = Some(value);
self
}
pub(crate) fn model(mut self, value: &'a str) -> Self {
self.model = Some(value);
self
}
pub(crate) fn version(mut self, value: &'a str) -> Self {
self.version = Some(value);
self
}
pub(crate) fn build(self) -> WebauthnResult<TpmSanData<'a>> {
self.manufacturer
.zip(self.model)
.zip(self.version)
.map(|((manufacturer, model), version)| TpmSanData {
manufacturer,
model,
version,
})
.ok_or(WebauthnError::AttestationCertificateRequirementsNotMet)
}
}
pub(crate) const TCG_AT_TPM_MANUFACTURER_RAW: &[u8] = &der_parser::oid!(raw 2.23.133 .2 .1);
pub(crate) const TCG_AT_TPM_MODEL_RAW: &[u8] = &der_parser::oid!(raw 2.23.133 .2 .2);
pub(crate) const TCG_AT_TPM_VERSION_RAW: &[u8] = &der_parser::oid!(raw 2.23.133 .2 .3);
impl<'a> TryFrom<&'a X509Name<'a>> for TpmSanData<'a> {
type Error = WebauthnError;
fn try_from(x509_name: &'a X509Name<'a>) -> Result<Self, Self::Error> {
x509_name
.iter_attributes()
.try_fold(TpmSanDataBuilder::new(), |builder, attribute| {
Ok(match attribute.attr_type().as_bytes() {
TCG_AT_TPM_MANUFACTURER_RAW => {
builder.manufacturer(attribute.attr_value().as_str()?)
}
TCG_AT_TPM_MODEL_RAW => builder.model(attribute.attr_value().as_str()?),
TCG_AT_TPM_VERSION_RAW => builder.version(attribute.attr_value().as_str()?),
_ => builder,
})
})
.map_err(|_: der_parser::error::Error| WebauthnError::ParseNOMFailure)
.and_then(TpmSanDataBuilder::build)
}
}
pub(crate) fn assert_tpm_attest_req(x509: &x509::X509) -> Result<(), WebauthnError> {
let der_bytes = x509.to_der()?;
let x509_cert = x509_parser::parse_x509_certificate(&der_bytes)
.map_err(|_| WebauthnError::AttestationStatementX5CInvalid)?
.1;
if x509_cert.version != X509Version::V3 {
return Err(WebauthnError::AttestationCertificateRequirementsNotMet);
}
let subject_name_ref = x509.subject_name();
if subject_name_ref.entries().count() != 0 {
return Err(WebauthnError::AttestationCertificateRequirementsNotMet);
}
check_extension(
&x509_cert.subject_alternative_name(),
true,
|subject_alternative_name| {
if !subject_alternative_name.critical {
return false;
};
subject_alternative_name
.value
.general_names
.iter()
.any(|general_name| {
if let GeneralName::DirectoryName(x509_name) = general_name {
TpmSanData::try_from(x509_name)
.and_then(|san_data| {
tpm_device_attribute_parser(san_data.manufacturer.as_bytes())
.map_err(|_| WebauthnError::ParseNOMFailure)
})
.and_then(|(_, manufacturer_bytes)| {
TpmVendor::try_from(manufacturer_bytes)
})
.is_ok()
} else {
false
}
})
},
)?;
check_extension(
&x509_cert.extended_key_usage(),
true,
|extended_key_usage| {
extended_key_usage
.value
.other
.contains(&der_parser::oid!(2.23.133 .8 .3))
},
)?;
check_extension(&x509_cert.basic_constraints(), true, |basic_constraints| {
!basic_constraints.value.ca
})?;
Ok(())
}
pub(crate) fn assert_packed_attest_req(pubk: &x509::X509) -> Result<(), WebauthnError> {
let der_bytes = pubk.to_der()?;
let x509_cert = x509_parser::parse_x509_certificate(&der_bytes)
.map_err(|_| WebauthnError::AttestationStatementX5CInvalid)?
.1;
if x509_cert.version != X509Version::V3 {
trace!("X509 Version != v3");
return Err(WebauthnError::AttestationCertificateRequirementsNotMet);
}
let subject = &x509_cert.subject;
let subject_c = subject.iter_country().take(1).next();
let subject_o = subject.iter_organization().take(1).next();
let subject_ou = subject.iter_organizational_unit().take(1).next();
let subject_cn = subject.iter_common_name().take(1).next();
if subject_c.is_none() || subject_o.is_none() || subject_cn.is_none() {
trace!("Invalid subject details");
return Err(WebauthnError::AttestationCertificateRequirementsNotMet);
}
match subject_ou {
Some(ou) => match ou.attr_value().as_str() {
Ok(ou_d) => {
if ou_d != "Authenticator Attestation" {
trace!("ou != Authenticator Attestation");
return Err(WebauthnError::AttestationCertificateRequirementsNotMet);
}
}
Err(_) => {
trace!("ou invalid");
return Err(WebauthnError::AttestationCertificateRequirementsNotMet);
}
},
None => {
trace!("ou not found");
return Err(WebauthnError::AttestationCertificateRequirementsNotMet);
}
}
check_extension(
&x509_cert.get_extension_unique(&FidoGenCeAaguid::OID),
false,
|fido_gen_ce_aaguid| !fido_gen_ce_aaguid.critical,
)?;
check_extension(&x509_cert.basic_constraints(), true, |basic_constraints| {
!basic_constraints.value.ca
})?;
Ok(())
}
impl TryFrom<nid::Nid> for ECDSACurve {
type Error = WebauthnError;
fn try_from(nid: nid::Nid) -> Result<Self, Self::Error> {
match nid {
nid::Nid::X9_62_PRIME256V1 => Ok(ECDSACurve::SECP256R1),
nid::Nid::SECP384R1 => Ok(ECDSACurve::SECP384R1),
nid::Nid::SECP521R1 => Ok(ECDSACurve::SECP521R1),
_ => Err(WebauthnError::ECDSACurveInvalidNid),
}
}
}
impl ECDSACurve {
fn to_openssl_nid(&self) -> nid::Nid {
match self {
ECDSACurve::SECP256R1 => nid::Nid::X9_62_PRIME256V1,
ECDSACurve::SECP384R1 => nid::Nid::SECP384R1,
ECDSACurve::SECP521R1 => nid::Nid::SECP521R1,
}
}
}
pub(crate) fn only_hash_from_type(
alg: COSEAlgorithm,
input: &[u8],
) -> Result<Vec<u8>, WebauthnError> {
match alg {
COSEAlgorithm::INSECURE_RS1 => {
warn!("INSECURE SHA1 USAGE DETECTED");
hash::hash(hash::MessageDigest::sha1(), input)
.map(|dbytes| Vec::from(dbytes.as_ref()))
.map_err(WebauthnError::OpenSSLError)
}
c_alg => {
debug!(?c_alg, "WebauthnError::COSEKeyInvalidType");
Err(WebauthnError::COSEKeyInvalidType)
}
}
}
impl TryFrom<&serde_cbor::Value> for COSEKey {
type Error = WebauthnError;
fn try_from(d: &serde_cbor::Value) -> Result<COSEKey, Self::Error> {
let m = cbor_try_map!(d)?;
let key_type_value = m
.get(&serde_cbor::Value::Integer(1))
.ok_or(WebauthnError::COSEKeyInvalidCBORValue)?;
let key_type = cbor_try_i128!(key_type_value)?;
let content_type_value = m
.get(&serde_cbor::Value::Integer(3))
.ok_or(WebauthnError::COSEKeyInvalidCBORValue)?;
let content_type = cbor_try_i128!(content_type_value)?;
let type_ = COSEAlgorithm::try_from(content_type)
.map_err(|_| WebauthnError::COSEKeyInvalidAlgorithm)?;
if key_type == (COSEKeyTypeId::EC_EC2 as i128)
&& (type_ == COSEAlgorithm::ES256
|| type_ == COSEAlgorithm::ES384
|| type_ == COSEAlgorithm::ES512)
{
let curve_type_value = m
.get(&serde_cbor::Value::Integer(-1))
.ok_or(WebauthnError::COSEKeyInvalidCBORValue)?;
let curve_type = cbor_try_i128!(curve_type_value)?;
let curve = ECDSACurve::try_from(curve_type)?;
let x_value = m
.get(&serde_cbor::Value::Integer(-2))
.ok_or(WebauthnError::COSEKeyInvalidCBORValue)?;
let x = cbor_try_bytes!(x_value)?;
let y_value = m
.get(&serde_cbor::Value::Integer(-3))
.ok_or(WebauthnError::COSEKeyInvalidCBORValue)?;
let y = cbor_try_bytes!(y_value)?;
let coord_len = curve.coordinate_size();
if x.len() != coord_len || y.len() != coord_len {
return Err(WebauthnError::COSEKeyECDSAXYInvalid);
}
let cose_key = COSEKey {
type_,
key: COSEKeyType::EC_EC2(COSEEC2Key {
curve,
x: x.to_vec().into(),
y: y.to_vec().into(),
}),
};
cose_key.validate()?;
Ok(cose_key)
} else if key_type == (COSEKeyTypeId::EC_RSA as i128) && (type_ == COSEAlgorithm::RS256) {
let n_value = m
.get(&serde_cbor::Value::Integer(-1))
.ok_or(WebauthnError::COSEKeyInvalidCBORValue)?;
let n = cbor_try_bytes!(n_value)?;
let e_value = m
.get(&serde_cbor::Value::Integer(-2))
.ok_or(WebauthnError::COSEKeyInvalidCBORValue)?;
let e = cbor_try_bytes!(e_value)?;
if n.len() != 256 || e.len() != 3 {
return Err(WebauthnError::COSEKeyRSANEInvalid);
}
let mut e_temp = [0; 3];
e_temp.copy_from_slice(e.as_slice());
let cose_key = COSEKey {
type_,
key: COSEKeyType::RSA(COSERSAKey {
n: n.to_vec().into(),
e: e_temp,
}),
};
cose_key.validate()?;
Ok(cose_key)
} else if key_type == (COSEKeyTypeId::EC_OKP as i128) && (type_ == COSEAlgorithm::EDDSA) {
debug!(?d, "WebauthnError::COSEKeyInvalidType - EC_OKP");
let curve_type_value = m
.get(&serde_cbor::Value::Integer(-1))
.ok_or(WebauthnError::COSEKeyInvalidCBORValue)?;
let curve_type = cbor_try_i128!(curve_type_value)?;
let x_value = m
.get(&serde_cbor::Value::Integer(-2))
.ok_or(WebauthnError::COSEKeyInvalidCBORValue)?;
let x = cbor_try_bytes!(x_value)?;
if x.len() != 32 {
return Err(WebauthnError::COSEKeyEDDSAXInvalid);
}
let mut x_temp = [0; 32];
x_temp.copy_from_slice(x);
let cose_key = COSEKey {
type_,
key: COSEKeyType::EC_OKP(COSEOKPKey {
curve: EDDSACurve::try_from(curve_type)?,
x: x_temp,
}),
};
cose_key.validate()?;
Ok(cose_key)
} else {
debug!(?key_type, ?type_, "WebauthnError::COSEKeyInvalidType");
Err(WebauthnError::COSEKeyInvalidType)
}
}
}
impl TryFrom<(COSEAlgorithm, &x509::X509)> for COSEKey {
type Error = WebauthnError;
fn try_from((alg, pubk): (COSEAlgorithm, &x509::X509)) -> Result<COSEKey, Self::Error> {
let key = match alg {
COSEAlgorithm::ES256 | COSEAlgorithm::ES384 | COSEAlgorithm::ES512 => {
let ec_key = pubk
.public_key()
.and_then(|pk| pk.ec_key())
.map_err(WebauthnError::OpenSSLError)?;
ec_key.check_key().map_err(WebauthnError::OpenSSLError)?;
let ec_grpref = ec_key.group();
let mut ctx =
openssl::bn::BigNumContext::new().map_err(WebauthnError::OpenSSLError)?;
let mut xbn = openssl::bn::BigNum::new().map_err(WebauthnError::OpenSSLError)?;
let mut ybn = openssl::bn::BigNum::new().map_err(WebauthnError::OpenSSLError)?;
ec_key
.public_key()
.affine_coordinates_gfp(ec_grpref, &mut xbn, &mut ybn, &mut ctx)
.map_err(WebauthnError::OpenSSLError)?;
let curve = ec_grpref
.curve_name()
.ok_or(WebauthnError::OpenSSLErrorNoCurveName)
.and_then(ECDSACurve::try_from)?;
if xbn.num_bytes() as usize != curve.coordinate_size()
|| ybn.num_bytes() as usize != curve.coordinate_size()
{
return Err(WebauthnError::COSEKeyECDSAXYInvalid);
}
Ok(COSEKeyType::EC_EC2(COSEEC2Key {
curve,
x: xbn.to_vec().into(),
y: ybn.to_vec().into(),
}))
}
COSEAlgorithm::RS256
| COSEAlgorithm::RS384
| COSEAlgorithm::RS512
| COSEAlgorithm::PS256
| COSEAlgorithm::PS384
| COSEAlgorithm::PS512
| COSEAlgorithm::EDDSA
| COSEAlgorithm::INSECURE_RS1 => {
error!(
"unsupported X509 to COSE conversion for COSE algorithm type {:?}",
alg
);
Err(WebauthnError::COSEKeyInvalidType)
}
}?;
Ok(COSEKey { type_: alg, key })
}
}
impl COSEKey {
pub(crate) fn get_alg_key_ecc_x962_raw(&self) -> Result<Vec<u8>, WebauthnError> {
match &self.key {
COSEKeyType::EC_EC2(ecpk) => {
let r: [u8; 1] = [0x04];
Ok(r.iter()
.chain(ecpk.x.0.iter())
.chain(ecpk.y.0.iter())
.copied()
.collect())
}
_ => {
debug!("get_alg_key_ecc_x962_raw");
Err(WebauthnError::COSEKeyInvalidType)
}
}
}
pub(crate) fn validate(&self) -> Result<(), WebauthnError> {
match &self.key {
COSEKeyType::EC_EC2(ec2k) => {
let curve = ec2k.curve.to_openssl_nid();
let ec_group =
ec::EcGroup::from_curve_name(curve).map_err(WebauthnError::OpenSSLError)?;
let xbn =
bn::BigNum::from_slice(ec2k.x.as_ref()).map_err(WebauthnError::OpenSSLError)?;
let ybn =
bn::BigNum::from_slice(ec2k.y.as_ref()).map_err(WebauthnError::OpenSSLError)?;
let ec_key = ec::EcKey::from_public_key_affine_coordinates(&ec_group, &xbn, &ybn)
.map_err(WebauthnError::OpenSSLError)?;
ec_key.check_key().map_err(WebauthnError::OpenSSLError)
}
COSEKeyType::RSA(rsak) => {
let nbn =
bn::BigNum::from_slice(rsak.n.as_ref()).map_err(WebauthnError::OpenSSLError)?;
let ebn = bn::BigNum::from_slice(&rsak.e).map_err(WebauthnError::OpenSSLError)?;
let _rsa_key = rsa::Rsa::from_public_components(nbn, ebn)
.map_err(WebauthnError::OpenSSLError)?;
Ok(())
}
COSEKeyType::EC_OKP(_edk) => {
warn!("ED25519 or ED448 keys are not currently supported");
Err(WebauthnError::COSEKeyEDUnsupported)
}
}
}
fn get_openssl_pkey(&self) -> Result<pkey::PKey<pkey::Public>, WebauthnError> {
match &self.key {
COSEKeyType::EC_EC2(ec2k) => {
let curve = ec2k.curve.to_openssl_nid();
let ec_group =
ec::EcGroup::from_curve_name(curve).map_err(WebauthnError::OpenSSLError)?;
let xbn =
bn::BigNum::from_slice(ec2k.x.as_ref()).map_err(WebauthnError::OpenSSLError)?;
let ybn =
bn::BigNum::from_slice(ec2k.y.as_ref()).map_err(WebauthnError::OpenSSLError)?;
let ec_key = ec::EcKey::from_public_key_affine_coordinates(&ec_group, &xbn, &ybn)
.map_err(WebauthnError::OpenSSLError)?;
ec_key.check_key().map_err(WebauthnError::OpenSSLError)?;
let p = pkey::PKey::from_ec_key(ec_key).map_err(WebauthnError::OpenSSLError)?;
Ok(p)
}
COSEKeyType::RSA(rsak) => {
let nbn =
bn::BigNum::from_slice(rsak.n.as_ref()).map_err(WebauthnError::OpenSSLError)?;
let ebn = bn::BigNum::from_slice(&rsak.e).map_err(WebauthnError::OpenSSLError)?;
let rsa_key = rsa::Rsa::from_public_components(nbn, ebn)
.map_err(WebauthnError::OpenSSLError)?;
let p = pkey::PKey::from_rsa(rsa_key).map_err(WebauthnError::OpenSSLError)?;
Ok(p)
}
_ => {
debug!("get_openssl_pkey");
Err(WebauthnError::COSEKeyInvalidType)
}
}
}
pub(crate) fn verify_signature(
&self,
signature: &[u8],
verification_data: &[u8],
) -> Result<bool, WebauthnError> {
let pkey = self.get_openssl_pkey()?;
pkey_verify_signature(&pkey, self.type_, signature, verification_data)
}
}
pub fn compute_sha256(data: &[u8]) -> [u8; 32] {
let mut hasher = sha::Sha256::new();
hasher.update(data);
hasher.finish()
}
#[cfg(test)]
mod tests {
use super::*;
use hex_literal::hex;
use serde_cbor::Value;
#[test]
fn nid_to_curve() {
assert_eq!(
ECDSACurve::try_from(nid::Nid::X9_62_PRIME256V1).unwrap(),
ECDSACurve::SECP256R1
);
}
#[test]
fn cbor_es256() {
let hex_data = hex!(
"
A5 // Map - 5 elements
01 02 // 1: 2, ; kty: EC2 key type
03 26 // 3: -7, ; alg: ES256 signature algorithm
20 01 // -1: 1, ; crv: P-256 curve
21 58 20 65eda5a12577c2bae829437fe338701a10aaa375e1bb5b5de108de439c08551d // -2: x, ; x-coordinate
22 58 20 1e52ed75701163f7f9e40ddf9f341b3dc9ba860af7e0ca7ca7e9eecd0084d19c // -3: y, ; y-coordinate");
let val: Value = serde_cbor::from_slice(&hex_data).unwrap();
let key = COSEKey::try_from(&val).unwrap();
assert_eq!(key.type_, COSEAlgorithm::ES256);
match key.key {
COSEKeyType::EC_EC2(pkey) => {
assert_eq!(
pkey.x.as_ref(),
hex!("65eda5a12577c2bae829437fe338701a10aaa375e1bb5b5de108de439c08551d")
);
assert_eq!(
pkey.y.as_ref(),
hex!("1e52ed75701163f7f9e40ddf9f341b3dc9ba860af7e0ca7ca7e9eecd0084d19c")
);
assert_eq!(pkey.curve, ECDSACurve::SECP256R1);
}
_ => panic!("Key should be parsed EC2 key"),
}
}
#[test]
fn cbor_es384() {
let hex_data = hex!(
"
A5 // Map - 5 elements
01 02 // 1: 2, ; kty: EC2 key type
03 38 22 // 3: -35, ; alg: ES384 signature algorithm
20 02 // -1: 2, ; crv: P-384 curve
21 58 30 ceeaf818731db7af2d02e029854823d71bdbf65fb0c6ff69 // -2: x, ; x-coordinate
42c9cf891efe18ea81430517d777f5c43550da801be5bf2f
22 58 30 dda1d0ead72e042efb7c36a38cc021abb2ca1a2e38159edd // -3: y ; y-coordinate
a8c25f391e9a38d79dd56b9427d1c7c70cfa778ab849b087 "
);
let val: Value = serde_cbor::from_slice(&hex_data).unwrap();
let key = COSEKey::try_from(&val).unwrap();
assert_eq!(key.type_, COSEAlgorithm::ES384);
match key.key {
COSEKeyType::EC_EC2(pkey) => {
assert_eq!(
pkey.x.as_ref(),
hex!(
"ceeaf818731db7af2d02e029854823d71bdbf65fb0c6ff69
42c9cf891efe18ea81430517d777f5c43550da801be5bf2f"
)
);
assert_eq!(
pkey.y.as_ref(),
hex!(
"dda1d0ead72e042efb7c36a38cc021abb2ca1a2e38159edd
a8c25f391e9a38d79dd56b9427d1c7c70cfa778ab849b087"
)
);
assert_eq!(pkey.curve, ECDSACurve::SECP384R1);
}
_ => panic!("Key should be parsed EC2 key"),
}
}
#[test]
fn cbor_es512() {
let hex_data = hex!(
"
A5 // Map - 5 elements
01 02 // 1: 2, ; kty: EC2 key type
03 38 23 // 3: -36, ; alg: ES512 signature algorithm
20 03 // -1: 3, ; crv: P-521 curve
21 58 42 0106cfaacf34b13f24bbb2f806fd9cfacff9a2a5ef9ecfcd85664609a0b2f6d4fd // -2: x, ; x-coordinate
b8e1d58630905f13f38d8eed8714eceb716920a3a235581623261fed961f7b7d72
22 58 42 0089597a052a8d3c8b2b5692d467dea19f8e1b9ca17fa563a1a826855dade04811 // -3: y, ; y-coordinate
b2881819e72f1706daeaf7d3773b2e284983a0eec33c2fe3ff5697722e95b29536");
let val: Value = serde_cbor::from_slice(&hex_data).unwrap();
let key = COSEKey::try_from(&val).unwrap();
assert_eq!(key.type_, COSEAlgorithm::ES512);
match key.key {
COSEKeyType::EC_EC2(pkey) => {
assert_eq!(
pkey.x.as_ref(),
hex!(
"0106cfaacf34b13f24bbb2f806fd9cfacff9a2a5ef9ecfcd85664609a0b2f6d4fd
b8e1d58630905f13f38d8eed8714eceb716920a3a235581623261fed961f7b7d72"
)
);
assert_eq!(
pkey.y.as_ref(),
hex!(
"0089597a052a8d3c8b2b5692d467dea19f8e1b9ca17fa563a1a826855dade04811
b2881819e72f1706daeaf7d3773b2e284983a0eec33c2fe3ff5697722e95b29536"
)
);
assert_eq!(pkey.curve, ECDSACurve::SECP521R1);
}
_ => panic!("Key should be parsed EC2 key"),
}
}
}