Merge changes I48987602,Idcc3c7e8,I9dda103f into main
* changes:
[bssl] Support conversion from COSE_Key to EVP_PKEY
[bssl] Support ECDSA P-384 signature verification
[bssl] Support ED25519/X25519 public key types for EVP_PKEY
diff --git a/libs/bssl/error/src/lib.rs b/libs/bssl/error/src/lib.rs
index c0dca2e..df79104 100644
--- a/libs/bssl/error/src/lib.rs
+++ b/libs/bssl/error/src/lib.rs
@@ -81,9 +81,14 @@
EVP_AEAD_CTX_new,
EVP_AEAD_CTX_open,
EVP_AEAD_CTX_seal,
+ EVP_Digest,
+ EVP_MD_CTX_new,
EVP_PKEY_new,
+ EVP_PKEY_new_raw_public_key,
EVP_PKEY_set1_EC_KEY,
EVP_marshal_public_key,
+ EVP_DigestVerify,
+ EVP_DigestVerifyInit,
HKDF,
HMAC,
RAND_bytes,
diff --git a/libs/bssl/src/digest.rs b/libs/bssl/src/digest.rs
index 49e66e6..e986a38 100644
--- a/libs/bssl/src/digest.rs
+++ b/libs/bssl/src/digest.rs
@@ -14,7 +14,18 @@
//! Wrappers of the digest functions in BoringSSL digest.h.
-use bssl_ffi::{EVP_MD_size, EVP_sha256, EVP_sha512, EVP_MD};
+use crate::util::{check_int_result, to_call_failed_error};
+use alloc::vec;
+use alloc::vec::Vec;
+use bssl_avf_error::{ApiName, Error, Result};
+use bssl_ffi::{
+ EVP_Digest, EVP_MD_CTX_free, EVP_MD_CTX_new, EVP_MD_size, EVP_sha256, EVP_sha384, EVP_sha512,
+ EVP_MAX_MD_SIZE, EVP_MD, EVP_MD_CTX,
+};
+use core::ptr::{self, NonNull};
+use log::error;
+
+const MAX_DIGEST_SIZE: usize = EVP_MAX_MD_SIZE as usize;
/// Message digester wrapping `EVP_MD`.
#[derive(Clone, Debug)]
@@ -28,7 +39,17 @@
let p = unsafe { EVP_sha256() };
// SAFETY: The returned pointer should always be valid and points to a static
// `EVP_MD`.
- Self(unsafe { &*p })
+ Self(unsafe { p.as_ref().unwrap() })
+ }
+
+ /// Returns a `Digester` implementing `SHA-384` algorithm.
+ pub fn sha384() -> Self {
+ // SAFETY: This function does not access any Rust variables and simply returns
+ // a pointer to the static variable in BoringSSL.
+ let p = unsafe { EVP_sha384() };
+ // SAFETY: The returned pointer should always be valid and points to a static
+ // `EVP_MD`.
+ Self(unsafe { p.as_ref().unwrap() })
}
/// Returns a `Digester` implementing `SHA-512` algorithm.
@@ -38,7 +59,7 @@
let p = unsafe { EVP_sha512() };
// SAFETY: The returned pointer should always be valid and points to a static
// `EVP_MD`.
- Self(unsafe { &*p })
+ Self(unsafe { p.as_ref().unwrap() })
}
/// Returns the digest size in bytes.
@@ -46,4 +67,64 @@
// SAFETY: The inner pointer is fetched from EVP_* hash functions in BoringSSL digest.h
unsafe { EVP_MD_size(self.0) }
}
+
+ /// Computes the digest of the provided `data`.
+ pub fn digest(&self, data: &[u8]) -> Result<Vec<u8>> {
+ let mut out = vec![0u8; MAX_DIGEST_SIZE];
+ let mut out_size = 0;
+ let engine = ptr::null_mut(); // Use the default engine.
+ let ret =
+ // SAFETY: This function reads `data` and writes to `out` within its bounds.
+ // `out` has `MAX_DIGEST_SIZE` bytes of space for write as required in the
+ // BoringSSL spec.
+ // The digester is a valid pointer to a static `EVP_MD` as it is returned by
+ // BoringSSL API during the construction of this struct.
+ unsafe {
+ EVP_Digest(
+ data.as_ptr() as *const _,
+ data.len(),
+ out.as_mut_ptr(),
+ &mut out_size,
+ self.0,
+ engine,
+ )
+ };
+ check_int_result(ret, ApiName::EVP_Digest)?;
+ let out_size = usize::try_from(out_size).map_err(|e| {
+ error!("Failed to convert digest size to usize: {:?}", e);
+ Error::InternalError
+ })?;
+ if self.size() != out_size {
+ return Err(to_call_failed_error(ApiName::EVP_Digest));
+ }
+ out.truncate(out_size);
+ Ok(out)
+ }
+}
+
+/// Message digester context wrapping `EVP_MD_CTX`.
+#[derive(Clone, Debug)]
+pub struct DigesterContext(NonNull<EVP_MD_CTX>);
+
+impl Drop for DigesterContext {
+ fn drop(&mut self) {
+ // SAFETY: This function frees any resources owned by `EVP_MD_CTX` and resets it to a
+ // freshly initialised state and then frees the context.
+ // It is safe because `EVP_MD_CTX` has been allocated by BoringSSL and isn't used after
+ // this.
+ unsafe { EVP_MD_CTX_free(self.0.as_ptr()) }
+ }
+}
+
+impl DigesterContext {
+ /// Creates a new `DigesterContext` wrapping a freshly allocated and initialised `EVP_MD_CTX`.
+ pub fn new() -> Result<Self> {
+ // SAFETY: The returned pointer is checked below.
+ let ctx = unsafe { EVP_MD_CTX_new() };
+ NonNull::new(ctx).map(Self).ok_or(to_call_failed_error(ApiName::EVP_MD_CTX_new))
+ }
+
+ pub(crate) fn as_mut_ptr(&mut self) -> *mut EVP_MD_CTX {
+ self.0.as_ptr()
+ }
}
diff --git a/libs/bssl/src/ec_key.rs b/libs/bssl/src/ec_key.rs
index 511b133..0c944cd 100644
--- a/libs/bssl/src/ec_key.rs
+++ b/libs/bssl/src/ec_key.rs
@@ -17,7 +17,9 @@
use crate::cbb::CbbFixed;
use crate::cbs::Cbs;
-use crate::util::{check_int_result, to_call_failed_error};
+use crate::util::{
+ check_int_result, get_label_value, get_label_value_as_bytes, to_call_failed_error,
+};
use alloc::vec;
use alloc::vec::Vec;
use bssl_avf_error::{ApiName, Error, Result};
@@ -33,7 +35,7 @@
use core::ptr::{self, NonNull};
use coset::{
iana::{self, EnumI64},
- CborSerializable, CoseKey, CoseKeyBuilder, Label,
+ CborSerializable, CoseKey, CoseKeyBuilder, KeyType, Label,
};
use log::error;
use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
@@ -79,13 +81,27 @@
}
/// Constructs an `EcKey` instance from the provided COSE_Key encoded public key slice.
- pub fn from_cose_public_key(cose_key: &[u8]) -> Result<Self> {
+ pub fn from_cose_public_key_slice(cose_key: &[u8]) -> Result<Self> {
let cose_key = CoseKey::from_slice(cose_key).map_err(|e| {
error!("Failed to deserialize COSE_Key: {e:?}");
Error::CoseKeyDecodingFailed
})?;
+ Self::from_cose_public_key(&cose_key)
+ }
+
+ /// Constructs an `EcKey` instance from the provided `COSE_Key`.
+ ///
+ /// The lifetime of the returned `EcKey` is not tied to the lifetime of the `cose_key`,
+ /// because the affine coordinates stored in the `cose_key` are copied into the `EcKey`.
+ ///
+ /// Currently, only the EC P-256 and P-384 curves are supported.
+ pub fn from_cose_public_key(cose_key: &CoseKey) -> Result<Self> {
+ if cose_key.kty != KeyType::Assigned(iana::KeyType::EC2) {
+ error!("Only EC2 keys are supported. Key type in the COSE Key: {:?}", cose_key.kty);
+ return Err(Error::Unimplemented);
+ }
let ec_key =
- match get_label_value(&cose_key, Label::Int(iana::Ec2KeyParameter::Crv.to_i64()))? {
+ match get_label_value(cose_key, Label::Int(iana::Ec2KeyParameter::Crv.to_i64()))? {
crv if crv == &Value::from(P256_CURVE.to_i64()) => EcKey::new_p256()?,
crv if crv == &Value::from(P384_CURVE.to_i64()) => EcKey::new_p384()?,
crv => {
@@ -96,8 +112,8 @@
return Err(Error::Unimplemented);
}
};
- let x = get_label_value_as_bytes(&cose_key, Label::Int(iana::Ec2KeyParameter::X.to_i64()))?;
- let y = get_label_value_as_bytes(&cose_key, Label::Int(iana::Ec2KeyParameter::Y.to_i64()))?;
+ let x = get_label_value_as_bytes(cose_key, Label::Int(iana::Ec2KeyParameter::X.to_i64()))?;
+ let y = get_label_value_as_bytes(cose_key, Label::Int(iana::Ec2KeyParameter::Y.to_i64()))?;
let group = ec_key.ec_group()?;
group.check_affine_coordinate_size(x)?;
@@ -309,17 +325,6 @@
}
}
-fn get_label_value_as_bytes(key: &CoseKey, label: Label) -> Result<&[u8]> {
- Ok(get_label_value(key, label)?.as_bytes().ok_or_else(|| {
- error!("Value not a bstr.");
- Error::CoseKeyDecodingFailed
- })?)
-}
-
-fn get_label_value(key: &CoseKey, label: Label) -> Result<&Value> {
- Ok(&key.params.iter().find(|(k, _)| k == &label).ok_or(Error::CoseKeyDecodingFailed)?.1)
-}
-
/// Wrapper of an `EC_GROUP` reference.
struct EcGroup<'a>(&'a EC_GROUP);
diff --git a/libs/bssl/src/evp.rs b/libs/bssl/src/evp.rs
index 5362925..86f99a8 100644
--- a/libs/bssl/src/evp.rs
+++ b/libs/bssl/src/evp.rs
@@ -15,22 +15,32 @@
//! Wrappers of the EVP functions in BoringSSL evp.h.
use crate::cbb::CbbFixed;
+use crate::digest::{Digester, DigesterContext};
use crate::ec_key::EcKey;
-use crate::util::{check_int_result, to_call_failed_error};
-use alloc::vec::Vec;
-use bssl_avf_error::{ApiName, Result};
-use bssl_ffi::{
- CBB_flush, CBB_len, EVP_PKEY_free, EVP_PKEY_new, EVP_PKEY_set1_EC_KEY, EVP_marshal_public_key,
- EVP_PKEY,
+use crate::util::{
+ check_int_result, get_label_value, get_label_value_as_bytes, to_call_failed_error,
};
-use core::ptr::NonNull;
+use alloc::vec::Vec;
+use bssl_avf_error::{ApiName, Error, Result};
+use bssl_ffi::{
+ CBB_flush, CBB_len, EVP_DigestVerify, EVP_DigestVerifyInit, EVP_PKEY_free, EVP_PKEY_new,
+ EVP_PKEY_new_raw_public_key, EVP_PKEY_set1_EC_KEY, EVP_marshal_public_key, EVP_PKEY,
+ EVP_PKEY_ED25519, EVP_PKEY_X25519,
+};
+use ciborium::Value;
+use core::ptr::{self, NonNull};
+use coset::{
+ iana::{self, EnumI64},
+ CoseKey, KeyType, Label,
+};
+use log::error;
/// Wrapper of an `EVP_PKEY` object, representing a public or private key.
pub struct PKey {
pkey: NonNull<EVP_PKEY>,
- /// Since this struct owns the inner key, the inner key remains valid as
+ /// If this struct owns the inner EC key, the inner EC key should remain valid as
/// long as the pointer to `EVP_PKEY` is valid.
- _inner_key: EcKey,
+ _inner_ec_key: Option<EcKey>,
}
impl Drop for PKey {
@@ -53,14 +63,14 @@
fn try_from(key: EcKey) -> Result<Self> {
let pkey = new_pkey()?;
- // SAFETY: The function only sets the inner key of the initialized and
+ // SAFETY: The function only sets the inner EC key of the initialized and
// non-null `EVP_PKEY` to point to the given `EC_KEY`. It only reads from
// and writes to the initialized `EVP_PKEY`.
// Since this struct owns the inner key, the inner key remains valid as
// long as `EVP_PKEY` is valid.
let ret = unsafe { EVP_PKEY_set1_EC_KEY(pkey.as_ptr(), key.0.as_ptr()) };
check_int_result(ret, ApiName::EVP_PKEY_set1_EC_KEY)?;
- Ok(Self { pkey, _inner_key: key })
+ Ok(Self { pkey, _inner_ec_key: Some(key) })
}
}
@@ -87,4 +97,126 @@
let len = unsafe { CBB_len(cbb.as_ref()) };
Ok(buf.get(0..len).ok_or(to_call_failed_error(ApiName::CBB_len))?.to_vec())
}
+
+ /// This function takes a raw public key data slice and creates a `PKey` instance wrapping
+ /// a freshly allocated `EVP_PKEY` object from it.
+ ///
+ /// The lifetime of the returned instance is not tied to the lifetime of the raw public
+ /// key slice because the raw data is copied into the `EVP_PKEY` object.
+ ///
+ /// Currently the only supported raw formats are X25519 and Ed25519, where the formats
+ /// are specified in RFC 7748 and RFC 8032 respectively.
+ pub fn new_raw_public_key(raw_public_key: &[u8], type_: PKeyType) -> Result<Self> {
+ let engine = ptr::null_mut(); // Engine is not used.
+ let pkey =
+ // SAFETY: The function only reads from the given raw public key within its bounds.
+ // The returned pointer is checked below.
+ unsafe {
+ EVP_PKEY_new_raw_public_key(
+ type_.0,
+ engine,
+ raw_public_key.as_ptr(),
+ raw_public_key.len(),
+ )
+ };
+ let pkey =
+ NonNull::new(pkey).ok_or(to_call_failed_error(ApiName::EVP_PKEY_new_raw_public_key))?;
+ Ok(Self { pkey, _inner_ec_key: None })
+ }
+
+ /// Creates a `PKey` from the given `cose_key`.
+ ///
+ /// The lifetime of the returned instance is not tied to the lifetime of the `cose_key` as the
+ /// data of `cose_key` is copied into the `EVP_PKEY` or `EC_KEY` object.
+ pub fn from_cose_public_key(cose_key: &CoseKey) -> Result<Self> {
+ match &cose_key.kty {
+ KeyType::Assigned(iana::KeyType::EC2) => {
+ EcKey::from_cose_public_key(cose_key)?.try_into()
+ }
+ KeyType::Assigned(iana::KeyType::OKP) => {
+ let curve_type =
+ get_label_value(cose_key, Label::Int(iana::OkpKeyParameter::Crv.to_i64()))?;
+ let curve_type = match curve_type {
+ crv if crv == &Value::from(iana::EllipticCurve::Ed25519.to_i64()) => {
+ PKeyType::ED25519
+ }
+ crv if crv == &Value::from(iana::EllipticCurve::X25519.to_i64()) => {
+ PKeyType::X25519
+ }
+ crv => {
+ error!("Unsupported curve type in OKP COSE key: {:?}", crv);
+ return Err(Error::Unimplemented);
+ }
+ };
+ let x = get_label_value_as_bytes(
+ cose_key,
+ Label::Int(iana::OkpKeyParameter::X.to_i64()),
+ )?;
+ Self::new_raw_public_key(x, curve_type)
+ }
+ kty => {
+ error!("Unsupported key type in COSE key: {:?}", kty);
+ Err(Error::Unimplemented)
+ }
+ }
+ }
+
+ /// Verifies the given `signature` of the `message` using the current public key.
+ ///
+ /// The `message` will be hashed using the given `digester` before verification.
+ ///
+ /// For algorithms like Ed25519 that do not use pre-hashed inputs, the `digester` should
+ /// be `None`.
+ pub fn verify(
+ &self,
+ signature: &[u8],
+ message: &[u8],
+ digester: Option<Digester>,
+ ) -> Result<()> {
+ let mut digester_context = DigesterContext::new()?;
+ // The `EVP_PKEY_CTX` is set to null as this function does not collect the context
+ // during the verification.
+ let pkey_context = ptr::null_mut();
+ let engine = ptr::null_mut(); // Use the default engine.
+ let ret =
+ // SAFETY: All the non-null parameters passed to this function have been properly
+ // initialized as required in the BoringSSL spec.
+ unsafe {
+ EVP_DigestVerifyInit(
+ digester_context.as_mut_ptr(),
+ pkey_context,
+ digester.map_or(ptr::null(), |d| d.0),
+ engine,
+ self.pkey.as_ptr(),
+ )
+ };
+ check_int_result(ret, ApiName::EVP_DigestVerifyInit)?;
+
+ // SAFETY: The function only reads from the given slices within their bounds.
+ // The `EVP_MD_CTX` is successfully initialized before this call.
+ let ret = unsafe {
+ EVP_DigestVerify(
+ digester_context.as_mut_ptr(),
+ signature.as_ptr(),
+ signature.len(),
+ message.as_ptr(),
+ message.len(),
+ )
+ };
+ check_int_result(ret, ApiName::EVP_DigestVerify)
+ }
+}
+
+/// Type of the keys supported by `PKey`.
+///
+/// It is a wrapper of the `EVP_PKEY_*` macros defined BoringSSL evp.h, which are the
+/// NID values of the corresponding keys.
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+pub struct PKeyType(i32);
+
+impl PKeyType {
+ /// EVP_PKEY_X25519 / NID_X25519
+ pub const X25519: PKeyType = PKeyType(EVP_PKEY_X25519);
+ /// EVP_PKEY_ED25519 / NID_ED25519
+ pub const ED25519: PKeyType = PKeyType(EVP_PKEY_ED25519);
}
diff --git a/libs/bssl/src/lib.rs b/libs/bssl/src/lib.rs
index 25b0163..a420168 100644
--- a/libs/bssl/src/lib.rs
+++ b/libs/bssl/src/lib.rs
@@ -38,7 +38,7 @@
pub use cbs::Cbs;
pub use digest::Digester;
pub use ec_key::{EcKey, ZVec};
-pub use evp::PKey;
+pub use evp::{PKey, PKeyType};
pub use hkdf::hkdf;
pub use hmac::hmac_sha256;
pub use rand::rand_bytes;
diff --git a/libs/bssl/src/util.rs b/libs/bssl/src/util.rs
index 880c85b..7473fe1 100644
--- a/libs/bssl/src/util.rs
+++ b/libs/bssl/src/util.rs
@@ -16,6 +16,8 @@
use crate::err::get_error_reason_code;
use bssl_avf_error::{ApiName, Error, Result};
+use ciborium::Value;
+use coset::{CoseKey, Label};
use log::error;
pub(crate) fn check_int_result(ret: i32, api_name: ApiName) -> Result<()> {
@@ -35,3 +37,14 @@
pub(crate) fn to_call_failed_error(api_name: ApiName) -> Error {
Error::CallFailed(api_name, get_error_reason_code())
}
+
+pub(crate) fn get_label_value_as_bytes(key: &CoseKey, label: Label) -> Result<&[u8]> {
+ Ok(get_label_value(key, label)?.as_bytes().ok_or_else(|| {
+ error!("Value not a bstr.");
+ Error::CoseKeyDecodingFailed
+ })?)
+}
+
+pub(crate) fn get_label_value(key: &CoseKey, label: Label) -> Result<&Value> {
+ Ok(&key.params.iter().find(|(k, _)| k == &label).ok_or(Error::CoseKeyDecodingFailed)?.1)
+}
diff --git a/libs/bssl/tests/eckey_test.rs b/libs/bssl/tests/eckey_test.rs
index 4f0697c..9c7eb4f 100644
--- a/libs/bssl/tests/eckey_test.rs
+++ b/libs/bssl/tests/eckey_test.rs
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-use bssl_avf::{sha256, ApiName, EcKey, EcdsaError, Error, PKey, Result};
+use bssl_avf::{sha256, ApiName, Digester, EcKey, EcdsaError, Error, PKey, Result};
use coset::CborSerializable;
use spki::{
der::{AnyRef, Decode},
@@ -72,7 +72,7 @@
ec_key.generate_key()?;
let cose_key = ec_key.cose_public_key()?;
let cose_key_data = cose_key.clone().to_vec().unwrap();
- let deserialized_ec_key = EcKey::from_cose_public_key(&cose_key_data)?;
+ let deserialized_ec_key = EcKey::from_cose_public_key_slice(&cose_key_data)?;
assert_eq!(cose_key, deserialized_ec_key.cose_public_key()?);
Ok(())
@@ -82,10 +82,29 @@
fn ecdsa_p256_signing_and_verification_succeed() -> Result<()> {
let mut ec_key = EcKey::new_p256()?;
ec_key.generate_key()?;
- let digest = sha256(MESSAGE1)?;
+ let digester = Digester::sha256();
+ let digest = digester.digest(MESSAGE1)?;
+ assert_eq!(digest, sha256(MESSAGE1)?);
let signature = ec_key.ecdsa_sign(&digest)?;
- ec_key.ecdsa_verify(&signature, &digest)
+ ec_key.ecdsa_verify(&signature, &digest)?;
+ // Building a `PKey` from a temporary `CoseKey` should work as the lifetime
+ // of the `PKey` is not tied to the lifetime of the `CoseKey`.
+ let pkey = PKey::from_cose_public_key(&ec_key.cose_public_key()?)?;
+ pkey.verify(&signature, MESSAGE1, Some(digester))
+}
+
+#[test]
+fn ecdsa_p384_signing_and_verification_succeed() -> Result<()> {
+ let mut ec_key = EcKey::new_p384()?;
+ ec_key.generate_key()?;
+ let digester = Digester::sha384();
+ let digest = digester.digest(MESSAGE1)?;
+
+ let signature = ec_key.ecdsa_sign(&digest)?;
+ ec_key.ecdsa_verify(&signature, &digest)?;
+ let pkey = PKey::from_cose_public_key(&ec_key.cose_public_key()?)?;
+ pkey.verify(&signature, MESSAGE1, Some(digester))
}
#[test]
@@ -100,6 +119,12 @@
let err = ec_key2.ecdsa_verify(&signature, &digest).unwrap_err();
let expected_err = Error::CallFailed(ApiName::ECDSA_verify, EcdsaError::BadSignature.into());
assert_eq!(expected_err, err);
+
+ let pkey: PKey = ec_key2.try_into()?;
+ let err = pkey.verify(&signature, MESSAGE1, Some(Digester::sha256())).unwrap_err();
+ let expected_err =
+ Error::CallFailed(ApiName::EVP_DigestVerify, EcdsaError::BadSignature.into());
+ assert_eq!(expected_err, err);
Ok(())
}
diff --git a/rialto/tests/test.rs b/rialto/tests/test.rs
index c1a8394..029895f 100644
--- a/rialto/tests/test.rs
+++ b/rialto/tests/test.rs
@@ -226,7 +226,8 @@
parent_certificate: &X509Certificate,
) -> Result<()> {
let cose_mac = CoseMac0::from_slice(maced_public_key)?;
- let authority_public_key = EcKey::from_cose_public_key(&cose_mac.payload.unwrap()).unwrap();
+ let authority_public_key =
+ EcKey::from_cose_public_key_slice(&cose_mac.payload.unwrap()).unwrap();
let (remaining, cert) = X509Certificate::from_der(certificate)?;
assert!(remaining.is_empty());
@@ -244,7 +245,7 @@
let cose_sign = CoseSign::from_slice(&csr.signed_csr_payload)?;
let csr_payload =
cose_sign.payload.as_ref().and_then(|v| CsrPayload::from_cbor_slice(v).ok()).unwrap();
- let subject_public_key = EcKey::from_cose_public_key(&csr_payload.public_key).unwrap();
+ let subject_public_key = EcKey::from_cose_public_key_slice(&csr_payload.public_key).unwrap();
let expected_spki_data =
PKey::try_from(subject_public_key).unwrap().subject_public_key_info().unwrap();
let (remaining, expected_spki) = SubjectPublicKeyInfo::from_der(&expected_spki_data)?;
diff --git a/service_vm/requests/src/client_vm.rs b/service_vm/requests/src/client_vm.rs
index ddf230b..4e87136 100644
--- a/service_vm/requests/src/client_vm.rs
+++ b/service_vm/requests/src/client_vm.rs
@@ -62,7 +62,7 @@
// the DICE chain in `cose_sign`.
// Verifies the second signature with the public key in the CSR payload.
- let ec_public_key = EcKey::from_cose_public_key(&csr_payload.public_key)?;
+ let ec_public_key = EcKey::from_cose_public_key_slice(&csr_payload.public_key)?;
cose_sign.verify_signature(ATTESTATION_KEY_SIGNATURE_INDEX, aad, |signature, message| {
ecdsa_verify(&ec_public_key, signature, message)
})?;