use coset::{CborSerializable, CoseSign1, CoseSign1Builder, HeaderBuilder, iana};
use serde::{Serialize, de::DeserializeOwned};
#[derive(Debug, Clone)]
pub struct CoseSigned<T> {
inner: CoseSign1,
_marker: std::marker::PhantomData<T>,
}
#[derive(Debug, thiserror::Error)]
pub enum CoseError {
#[error("CBOR serialization failed: {0}")]
CborSerialize(String),
#[error("CBOR deserialization failed: {0}")]
CborDeserialize(String),
#[error("COSE serialization failed: {0}")]
CoseSerialize(String),
#[error("COSE deserialization failed: {0}")]
CoseDeserialize(String),
#[error("Signature verification failed")]
VerificationFailed,
#[error("Missing payload")]
MissingPayload,
#[error("Invalid key: {0}")]
InvalidKey(String),
#[error("Invalid signature length")]
InvalidSignatureLength,
#[error("Algorithm mismatch: expected {expected}, got {got}")]
AlgorithmMismatch {
expected: String,
got: String,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SigningAlgorithm {
EdDSA,
ES256,
ES384,
}
impl std::fmt::Display for SigningAlgorithm {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SigningAlgorithm::EdDSA => write!(f, "EdDSA"),
SigningAlgorithm::ES256 => write!(f, "ES256"),
SigningAlgorithm::ES384 => write!(f, "ES384"),
}
}
}
impl SigningAlgorithm {
fn to_iana(self) -> iana::Algorithm {
match self {
SigningAlgorithm::EdDSA => iana::Algorithm::EdDSA,
SigningAlgorithm::ES256 => iana::Algorithm::ES256,
SigningAlgorithm::ES384 => iana::Algorithm::ES384,
}
}
}
impl<T> CoseSigned<T>
where
T: Serialize + DeserializeOwned,
{
pub fn kid(&self) -> Option<String> {
let kid = &self.inner.protected.header.key_id;
if kid.is_empty() {
None
} else {
String::from_utf8(kid.clone()).ok()
}
}
pub fn algorithm(&self) -> Option<SigningAlgorithm> {
match self.inner.protected.header.alg {
Some(coset::RegisteredLabelWithPrivate::Assigned(iana::Algorithm::EdDSA)) => {
Some(SigningAlgorithm::EdDSA)
}
Some(coset::RegisteredLabelWithPrivate::Assigned(iana::Algorithm::ES256)) => {
Some(SigningAlgorithm::ES256)
}
Some(coset::RegisteredLabelWithPrivate::Assigned(iana::Algorithm::ES384)) => {
Some(SigningAlgorithm::ES384)
}
_ => None,
}
}
pub fn to_bytes(&self) -> Result<Vec<u8>, CoseError> {
self.inner
.clone()
.to_vec()
.map_err(|e| CoseError::CoseSerialize(e.to_string()))
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, CoseError> {
let inner =
CoseSign1::from_slice(bytes).map_err(|e| CoseError::CoseDeserialize(e.to_string()))?;
Ok(Self {
inner,
_marker: std::marker::PhantomData,
})
}
pub fn payload_unverified(&self) -> Result<T, CoseError> {
let payload = self
.inner
.payload
.as_ref()
.ok_or(CoseError::MissingPayload)?;
ciborium::from_reader(payload.as_slice())
.map_err(|e| CoseError::CborDeserialize(e.to_string()))
}
pub fn sign_with<F>(
payload: &T,
kid: &str,
alg: SigningAlgorithm,
sign_fn: F,
) -> Result<Self, CoseError>
where
F: FnOnce(&[u8]) -> Result<Vec<u8>, CoseError>,
{
let mut cbor_payload = Vec::new();
ciborium::into_writer(payload, &mut cbor_payload)
.map_err(|e| CoseError::CborSerialize(e.to_string()))?;
let protected = HeaderBuilder::new()
.algorithm(alg.to_iana())
.key_id(kid.as_bytes().to_vec())
.build();
let sign1 = CoseSign1Builder::new()
.protected(protected)
.payload(cbor_payload)
.try_create_signature(&[], sign_fn)?
.build();
Ok(Self {
inner: sign1,
_marker: std::marker::PhantomData,
})
}
pub fn verify_with<F>(&self, verify_fn: F) -> Result<T, CoseError>
where
F: FnOnce(&[u8], &[u8]) -> Result<(), CoseError>,
{
self.inner
.verify_signature(&[], |sig, data| verify_fn(data, sig))?;
let payload = self
.inner
.payload
.as_ref()
.ok_or(CoseError::MissingPayload)?;
ciborium::from_reader(payload.as_slice())
.map_err(|e| CoseError::CborDeserialize(e.to_string()))
}
pub fn check_algorithm(&self, expected: SigningAlgorithm) -> Result<(), CoseError> {
let actual = self.algorithm();
if actual != Some(expected) {
return Err(CoseError::AlgorithmMismatch {
expected: expected.to_string(),
got: actual
.map(|a| a.to_string())
.unwrap_or_else(|| "None".to_string()),
});
}
Ok(())
}
}
impl From<coset::CoseError> for CoseError {
fn from(e: coset::CoseError) -> Self {
CoseError::CoseSerialize(format!("{:?}", e))
}
}
#[cfg(feature = "ed25519")]
mod ed25519_impl {
use super::*;
use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
impl<T> CoseSigned<T>
where
T: Serialize + DeserializeOwned,
{
pub fn sign_ed25519(
payload: &T,
kid: &str,
signing_key: &SigningKey,
) -> Result<Self, CoseError> {
Self::sign_with(payload, kid, SigningAlgorithm::EdDSA, |data| {
let sig = signing_key.sign(data);
Ok(sig.to_bytes().to_vec())
})
}
pub fn verify_ed25519(&self, verifying_key: &VerifyingKey) -> Result<T, CoseError> {
self.check_algorithm(SigningAlgorithm::EdDSA)?;
self.verify_with(|data, sig| {
let signature =
Signature::from_slice(sig).map_err(|_| CoseError::InvalidSignatureLength)?;
verifying_key
.verify(data, &signature)
.map_err(|_| CoseError::VerificationFailed)
})
}
}
}
#[cfg(feature = "p256")]
mod p256_impl {
use super::*;
use p256::ecdsa::{
Signature, SigningKey, VerifyingKey, signature::Signer, signature::Verifier,
};
impl<T> CoseSigned<T>
where
T: Serialize + DeserializeOwned,
{
pub fn sign_p256(
payload: &T,
kid: &str,
signing_key: &SigningKey,
) -> Result<Self, CoseError> {
Self::sign_with(payload, kid, SigningAlgorithm::ES256, |data| {
let sig: Signature = signing_key.sign(data);
Ok(sig.to_bytes().to_vec())
})
}
pub fn verify_p256(&self, verifying_key: &VerifyingKey) -> Result<T, CoseError> {
self.check_algorithm(SigningAlgorithm::ES256)?;
self.verify_with(|data, sig| {
let signature =
Signature::from_slice(sig).map_err(|_| CoseError::InvalidSignatureLength)?;
verifying_key
.verify(data, &signature)
.map_err(|_| CoseError::VerificationFailed)
})
}
}
}
#[cfg(feature = "p384")]
mod p384_impl {
use super::*;
use p384::ecdsa::{
Signature, SigningKey, VerifyingKey, signature::Signer, signature::Verifier,
};
impl<T> CoseSigned<T>
where
T: Serialize + DeserializeOwned,
{
pub fn sign_p384(
payload: &T,
kid: &str,
signing_key: &SigningKey,
) -> Result<Self, CoseError> {
Self::sign_with(payload, kid, SigningAlgorithm::ES384, |data| {
let sig: Signature = signing_key.sign(data);
Ok(sig.to_bytes().to_vec())
})
}
pub fn verify_p384(&self, verifying_key: &VerifyingKey) -> Result<T, CoseError> {
self.check_algorithm(SigningAlgorithm::ES384)?;
self.verify_with(|data, sig| {
let signature =
Signature::from_slice(sig).map_err(|_| CoseError::InvalidSignatureLength)?;
verifying_key
.verify(data, &signature)
.map_err(|_| CoseError::VerificationFailed)
})
}
}
}