use ciborium::value::Integer;
use coset::{CborSerializable, CoseSign1, iana::CoapContentFormat};
use serde::{Serialize, de::DeserializeOwned};
use super::{
SigningNamespace, content_type, message::SerializedMessage, namespace, signing_key::SigningKey,
verifying_key::VerifyingKey,
};
use crate::{
CoseSign1Bytes, CryptoError, KEY_ID_SIZE,
content_format::CoseSign1ContentFormat,
cose::{CoseSerializable, SIGNING_NAMESPACE},
error::{EncodingError, SignatureError},
keys::KeyId,
};
#[derive(Clone, Debug)]
pub struct SignedObject(pub(crate) CoseSign1);
impl From<CoseSign1> for SignedObject {
fn from(cose_sign1: CoseSign1) -> Self {
SignedObject(cose_sign1)
}
}
impl PartialEq for SignedObject {
fn eq(&self, other: &Self) -> bool {
self.to_cose() == other.to_cose()
}
}
impl SignedObject {
pub fn content_type(&self) -> Result<CoapContentFormat, CryptoError> {
content_type(&self.0.protected)
}
pub fn signed_by_id(&self) -> Result<KeyId, CryptoError> {
let key_id_bytes = &self.0.protected.header.key_id;
let key_id: [u8; KEY_ID_SIZE] = key_id_bytes
.clone()
.try_into()
.map_err(|_| SignatureError::InvalidSignature)?;
Ok(KeyId::from(key_id))
}
#[doc(hidden)]
pub fn dangerous_unverified_decode_do_not_use_except_for_debug_logs<
Message: DeserializeOwned,
>(
&self,
) -> Option<Message> {
let payload = self.0.payload.as_deref()?;
let content_type = self.content_type().ok()?;
SerializedMessage::from_bytes(payload.to_vec(), content_type)
.decode()
.ok()
}
fn inner(&self) -> &CoseSign1 {
&self.0
}
fn namespace(&self) -> Result<SigningNamespace, CryptoError> {
namespace(&self.0.protected)
}
fn payload(&self) -> Result<Vec<u8>, CryptoError> {
self.0
.payload
.as_ref()
.ok_or(SignatureError::InvalidSignature.into())
.map(|payload| payload.to_vec())
}
pub fn verify_and_unwrap<Message: DeserializeOwned>(
&self,
verifying_key: &VerifyingKey,
namespace: &SigningNamespace,
) -> Result<Message, CryptoError> {
SerializedMessage::from_bytes(
self.verify_and_unwrap_bytes(verifying_key, namespace)?,
self.content_type()?,
)
.decode()
.map_err(Into::into)
}
fn verify_and_unwrap_bytes(
&self,
verifying_key: &VerifyingKey,
namespace: &SigningNamespace,
) -> Result<Vec<u8>, CryptoError> {
if self.inner().protected.header.alg.is_none() {
return Err(SignatureError::InvalidSignature.into());
}
if self.namespace()? != *namespace {
return Err(SignatureError::InvalidNamespace.into());
}
self.inner()
.verify_signature(&[], |sig, data| verifying_key.verify_raw(sig, data))?;
self.payload()
}
}
impl SigningKey {
fn sign_bytes(
&self,
serialized_message: &SerializedMessage,
namespace: &SigningNamespace,
) -> Result<SignedObject, CryptoError> {
let cose_sign1 = coset::CoseSign1Builder::new()
.protected(
coset::HeaderBuilder::new()
.algorithm(self.cose_algorithm())
.key_id((&self.id).into())
.content_format(serialized_message.content_type())
.value(
SIGNING_NAMESPACE,
ciborium::Value::Integer(Integer::from(namespace.as_i64())),
)
.build(),
)
.payload(serialized_message.as_bytes().to_vec())
.create_signature(&[], |pt| self.sign_raw(pt))
.build();
Ok(SignedObject(cose_sign1))
}
pub fn sign<Message: Serialize>(
&self,
message: &Message,
namespace: &SigningNamespace,
) -> Result<SignedObject, CryptoError> {
self.sign_bytes(&SerializedMessage::encode(message)?, namespace)
}
}
impl CoseSerializable<CoseSign1ContentFormat> for SignedObject {
fn from_cose(bytes: &CoseSign1Bytes) -> Result<Self, EncodingError> {
Ok(SignedObject(
CoseSign1::from_slice(bytes.as_ref())
.map_err(|_| EncodingError::InvalidCoseEncoding)?,
))
}
fn to_cose(&self) -> CoseSign1Bytes {
self.0
.clone()
.to_vec()
.expect("SignedObject is always serializable")
.into()
}
}
#[cfg(test)]
mod tests {
use serde::{Deserialize, Serialize};
use crate::{
CoseKeyBytes, CoseSerializable, CoseSign1Bytes, CryptoError, SignatureAlgorithm,
SignedObject, SigningKey, SigningNamespace, VerifyingKey,
};
const VERIFYING_KEY: &[u8] = &[
166, 1, 1, 2, 80, 55, 131, 40, 191, 230, 137, 76, 182, 184, 139, 94, 152, 45, 63, 13, 71,
3, 39, 4, 129, 2, 32, 6, 33, 88, 32, 93, 213, 35, 177, 81, 219, 226, 241, 147, 140, 238,
32, 34, 183, 213, 107, 227, 92, 75, 84, 208, 47, 198, 80, 18, 188, 172, 145, 184, 154, 26,
170,
];
const SIGNED_OBJECT: &[u8] = &[
132, 88, 30, 164, 1, 39, 3, 24, 60, 4, 80, 55, 131, 40, 191, 230, 137, 76, 182, 184, 139,
94, 152, 45, 63, 13, 71, 58, 0, 1, 56, 127, 32, 160, 85, 161, 102, 102, 105, 101, 108, 100,
49, 108, 84, 101, 115, 116, 32, 109, 101, 115, 115, 97, 103, 101, 88, 64, 206, 83, 177,
184, 37, 103, 128, 39, 120, 174, 61, 4, 29, 184, 68, 46, 47, 203, 47, 246, 108, 160, 169,
114, 7, 165, 119, 198, 3, 209, 52, 249, 89, 31, 156, 255, 212, 75, 224, 78, 183, 37, 174,
63, 112, 70, 219, 246, 19, 213, 17, 121, 249, 244, 23, 182, 36, 193, 175, 55, 250, 65, 250,
6,
];
#[derive(Deserialize, Debug, PartialEq, Serialize)]
struct TestMessage {
field1: String,
}
#[test]
fn test_roundtrip_cose() {
let signed_object =
SignedObject::from_cose(&<CoseSign1Bytes>::from(SIGNED_OBJECT)).unwrap();
assert_eq!(
signed_object.content_type().unwrap(),
coset::iana::CoapContentFormat::Cbor
);
let cose_bytes = signed_object.to_cose();
assert_eq!(cose_bytes, CoseSign1Bytes::from(SIGNED_OBJECT));
}
#[test]
fn test_verify_and_unwrap_testvector() {
let test_message = TestMessage {
field1: "Test message".to_string(),
};
let signed_object =
SignedObject::from_cose(&<CoseSign1Bytes>::from(SIGNED_OBJECT)).unwrap();
let verifying_key = VerifyingKey::from_cose(&<CoseKeyBytes>::from(VERIFYING_KEY)).unwrap();
let namespace = SigningNamespace::ExampleNamespace;
let payload: TestMessage = signed_object
.verify_and_unwrap(&verifying_key, &namespace)
.unwrap();
assert_eq!(payload, test_message);
}
#[test]
fn test_sign_verify_and_unwrap_roundtrip() {
let signing_key = SigningKey::make(SignatureAlgorithm::Ed25519);
let test_message = TestMessage {
field1: "Test message".to_string(),
};
let namespace = SigningNamespace::ExampleNamespace;
let signed_object = signing_key.sign(&test_message, &namespace).unwrap();
let verifying_key = signing_key.to_verifying_key();
let payload: TestMessage = signed_object
.verify_and_unwrap(&verifying_key, &namespace)
.unwrap();
assert_eq!(payload, test_message);
}
#[test]
fn test_fail_namespace_changed() {
let signing_key = SigningKey::make(SignatureAlgorithm::Ed25519);
let test_message = TestMessage {
field1: "Test message".to_string(),
};
let namespace = SigningNamespace::ExampleNamespace;
let signed_object = signing_key.sign(&test_message, &namespace).unwrap();
let verifying_key = signing_key.to_verifying_key();
let different_namespace = SigningNamespace::ExampleNamespace2;
let result: Result<TestMessage, CryptoError> =
signed_object.verify_and_unwrap(&verifying_key, &different_namespace);
assert!(result.is_err());
}
}