bitwarden_crypto/signing/
message.rs

1//! This file contains message serialization for messages intended to be signed.
2//!
3//! Consumers of the signing API should not care about or implement individual ways to represent
4//! structs. Thus, the only publicly exposed api takes a struct, and the signing module takes care
5//! of the serialization under the hood. This requires converting the struct to a byte array
6//! using some serialization format. Further, the serialization format must be written to the
7//! signature object so that it can be used upon deserialization to use the correct deserializer.
8//!
9//! To provide this interface, the SerializedMessage struct is introduced. SerializedMessage
10//! represents the serialized bytes along with the content format used for serialization. The latter
11//! is stored on the signed object, in e.g. a COSE header, so that upon deserialization the correct
12//! deserializer can be used.
13//!
14//! Currently, only CBOR serialization / deserialization is implemented, since it is compact and is
15//! what COSE already uses.
16
17use coset::iana::CoapContentFormat;
18use serde::{de::DeserializeOwned, Serialize};
19
20use crate::error::EncodingError;
21
22/// A message (struct) to be signed, serialized to a byte array, along with the content format of
23/// the bytes.
24pub struct SerializedMessage {
25    serialized_message_bytes: Vec<u8>,
26    content_type: CoapContentFormat,
27}
28
29impl AsRef<[u8]> for SerializedMessage {
30    fn as_ref(&self) -> &[u8] {
31        &self.serialized_message_bytes
32    }
33}
34
35impl SerializedMessage {
36    /// Creates a new `SerializedMessage` from a byte array and content type.
37    pub fn from_bytes(bytes: Vec<u8>, content_type: CoapContentFormat) -> Self {
38        SerializedMessage {
39            serialized_message_bytes: bytes,
40            content_type,
41        }
42    }
43
44    /// Returns the serialized message bytes as a slice. This representation needs to be used
45    /// together with a content type to deserialize the message correctly.
46    pub fn as_bytes(&self) -> &[u8] {
47        &self.serialized_message_bytes
48    }
49
50    pub(super) fn content_type(&self) -> CoapContentFormat {
51        self.content_type
52    }
53
54    /// Encodes a message into a `SerializedMessage` using CBOR serialization.
55    pub(super) fn encode<Message: Serialize>(message: &Message) -> Result<Self, EncodingError> {
56        let mut buffer = Vec::new();
57        ciborium::ser::into_writer(message, &mut buffer)
58            .map_err(|_| EncodingError::InvalidCborSerialization)?;
59        Ok(SerializedMessage {
60            serialized_message_bytes: buffer,
61            content_type: CoapContentFormat::Cbor,
62        })
63    }
64
65    /// Creates a new `SerializedMessage` from a byte array and content type.
66    /// This currently implements only CBOR serialization, so the content type must be `Cbor`.
67    pub fn decode<Message: DeserializeOwned>(&self) -> Result<Message, EncodingError> {
68        if self.content_type != CoapContentFormat::Cbor {
69            return Err(EncodingError::InvalidValue("Unsupported content type"));
70        }
71
72        ciborium::de::from_reader(self.serialized_message_bytes.as_slice())
73            .map_err(|_| EncodingError::InvalidCborSerialization)
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use serde::{Deserialize, Serialize};
80
81    use super::*;
82
83    #[derive(Serialize, Deserialize, Debug, PartialEq)]
84    struct TestMessage {
85        field1: String,
86        field2: u32,
87    }
88
89    #[test]
90    fn test_serialization() {
91        let message = TestMessage {
92            field1: "Hello".to_string(),
93            field2: 42,
94        };
95
96        let serialized = SerializedMessage::encode(&message).unwrap();
97        let deserialized: TestMessage = serialized.decode().unwrap();
98
99        assert_eq!(message, deserialized);
100    }
101
102    #[test]
103    fn test_bytes() {
104        let message = TestMessage {
105            field1: "Hello".to_string(),
106            field2: 42,
107        };
108
109        let serialized = SerializedMessage::encode(&message).unwrap();
110        let deserialized: TestMessage = SerializedMessage::from_bytes(
111            serialized.as_bytes().to_vec(),
112            serialized.content_type(),
113        )
114        .decode()
115        .unwrap();
116        assert_eq!(message, deserialized);
117    }
118}