common_access_token/
cose.rs

1//! CBOR Object Signing and Encryption (COSE) implementation.
2//!
3//! This module provides functionality for working with COSE structures:
4//! - Creating and verifying COSE_Mac0 structures
5//! - Serializing and deserializing COSE objects
6//! - Working with COSE headers and tags
7//!
8//! The implementation follows RFC 8152 (CBOR Object Signing and Encryption)
9//! and is focused on the COSE_Mac0 structure, which is used for HMAC-based
10//! authentication of CAT tokens.
11//!
12//! This module is primarily used internally by the `cat` module and is not
13//! typically used directly by applications.
14
15use crate::error::Error;
16use hmac_sha256::HMAC;
17use std::collections::BTreeMap;
18
19// COSE algorithm identifiers
20pub const ALG_HS256: i64 = 5; // HMAC 256-bit SHA-256
21
22// COSE header parameters
23pub const HEADER_ALG: i64 = 1; // Algorithm
24pub const HEADER_KID: i64 = 4; // Key ID
25
26// COSE tags
27pub const TAG_COSE_MAC0: u64 = 17;
28pub const TAG_CWT: u64 = 61;
29
30/// COSE_Mac0 structure as defined in RFC 8152 Section 6.2.
31///
32/// This structure represents a MAC-protected message with a single recipient.
33/// It consists of:
34/// - Protected header: Cryptographically protected parameters
35/// - Unprotected header: Parameters not cryptographically protected
36/// - Payload: The content being protected
37/// - Tag: The authentication tag
38///
39/// The COSE_Mac0 structure is used to create and verify HMAC-based
40/// authentication codes for CAT tokens.
41#[derive(Debug)]
42pub struct CoseMac0 {
43    /// Parameters that are cryptographically protected
44    protected_header: BTreeMap<i64, serde_json::Value>,
45
46    /// Parameters that are not cryptographically protected
47    unprotected_header: BTreeMap<i64, serde_json::Value>,
48
49    /// The content being protected
50    payload: Vec<u8>,
51
52    /// The authentication tag
53    tag: Vec<u8>,
54}
55
56impl CoseMac0 {
57    /// Creates a new COSE_Mac0 structure.
58    ///
59    /// # Arguments
60    ///
61    /// * `protected_header` - Parameters that are cryptographically protected
62    /// * `unprotected_header` - Parameters that are not cryptographically protected
63    /// * `payload` - The content being protected
64    ///
65    /// # Returns
66    ///
67    /// A new COSE_Mac0 structure with an empty tag. The tag must be created
68    /// using the `create_tag` method before the structure can be used.
69    ///
70    /// # Examples
71    ///
72    /// ```
73    /// # use common_access_token::cose::CoseMac0;
74    /// # use std::collections::BTreeMap;
75    /// #
76    /// let protected_header = BTreeMap::new();
77    /// let unprotected_header = BTreeMap::new();
78    /// let payload = vec![1, 2, 3];
79    ///
80    /// let cose_mac0 = CoseMac0::new(protected_header, unprotected_header, payload);
81    /// ```
82    pub fn new(
83        protected_header: BTreeMap<i64, serde_json::Value>,
84        unprotected_header: BTreeMap<i64, serde_json::Value>,
85        payload: Vec<u8>,
86    ) -> Self {
87        CoseMac0 {
88            protected_header,
89            unprotected_header,
90            payload,
91            tag: Vec::new(),
92        }
93    }
94
95    /// Creates an authentication tag for the COSE_Mac0 structure.
96    ///
97    /// This method computes an HMAC-SHA256 tag over the MAC_structure as defined
98    /// in RFC 8152 Section 6.3. The MAC_structure includes:
99    /// - The context string "MAC0"
100    /// - The protected header
101    /// - The external AAD (empty in this implementation)
102    /// - The payload
103    ///
104    /// # Arguments
105    ///
106    /// * `key` - The cryptographic key to use for creating the tag
107    ///
108    /// # Returns
109    ///
110    /// * `Ok(())` - If the tag was successfully created
111    /// * `Err(Error)` - If an error occurred during tag creation
112    ///
113    /// # Examples
114    ///
115    /// ```
116    /// # use common_access_token::cose::CoseMac0;
117    /// # use std::collections::BTreeMap;
118    /// #
119    /// # let protected_header = BTreeMap::new();
120    /// # let unprotected_header = BTreeMap::new();
121    /// # let payload = vec![1, 2, 3];
122    /// # let key = vec![4, 5, 6];
123    /// #
124    /// let mut cose_mac0 = CoseMac0::new(protected_header, unprotected_header, payload);
125    /// cose_mac0.create_tag(&key).expect("Failed to create tag");
126    /// ```
127    pub fn create_tag(&mut self, key: &[u8]) -> Result<(), Error> {
128        // Serialize the protected header to CBOR
129        let mut protected_header_cbor = Vec::new();
130        ciborium::ser::into_writer(&self.protected_header, &mut protected_header_cbor)?;
131
132        // Create the MAC_structure as defined in RFC 8152 Section 6.3
133        // MAC_structure = [
134        //   context: "MAC0",
135        //   protected: bstr,
136        //   external_aad: bstr,
137        //   payload: bstr
138        // ]
139        let mac_structure = (
140            "MAC0",
141            protected_header_cbor,
142            Vec::<u8>::new(), // empty external_aad
143            &self.payload,
144        );
145
146        // Serialize the MAC_structure to CBOR
147        let mut mac_structure_cbor = Vec::new();
148        ciborium::ser::into_writer(&mac_structure, &mut mac_structure_cbor)?;
149
150        // Compute the HMAC
151        let tag = HMAC::mac(key, &mac_structure_cbor);
152        self.tag = tag.to_vec();
153        Ok(())
154    }
155
156    pub fn verify(&self, key: &[u8]) -> Result<(), Error> {
157        // Serialize the protected header to CBOR
158        let mut protected_header_cbor = Vec::new();
159        ciborium::ser::into_writer(&self.protected_header, &mut protected_header_cbor)?;
160
161        // Create the MAC_structure
162        let mac_structure = (
163            "MAC0",
164            protected_header_cbor,
165            Vec::<u8>::new(), // empty external_aad
166            &self.payload,
167        );
168
169        // Serialize the MAC_structure to CBOR
170        let mut mac_structure_cbor = Vec::new();
171        ciborium::ser::into_writer(&mac_structure, &mut mac_structure_cbor)?;
172
173        // Compute and verify the HMAC
174        let expected_tag = HMAC::mac(key, &mac_structure_cbor);
175
176        if expected_tag.as_ref() != self.tag.as_slice() {
177            return Err(Error::TagMismatch);
178        }
179
180        Ok(())
181    }
182
183    pub fn to_cbor(&self) -> Result<Vec<u8>, Error> {
184        // Serialize the protected header to CBOR
185        let mut protected_header_cbor = Vec::new();
186        ciborium::ser::into_writer(&self.protected_header, &mut protected_header_cbor)?;
187
188        // COSE_Mac0 = [
189        //   protected: bstr,
190        //   unprotected: map,
191        //   payload: bstr,
192        //   tag: bstr
193        // ]
194        let cose_mac0 = (
195            protected_header_cbor,
196            &self.unprotected_header,
197            &self.payload,
198            &self.tag,
199        );
200
201        let mut cose_mac0_cbor = Vec::new();
202        ciborium::ser::into_writer(&cose_mac0, &mut cose_mac0_cbor)?;
203
204        Ok(cose_mac0_cbor)
205    }
206
207    pub fn from_cbor(cbor_data: &[u8]) -> Result<Self, Error> {
208        // Create a simple structure to hold the COSE_Mac0 components
209        #[derive(serde::Deserialize)]
210        struct CoseMac0Data(Vec<u8>, BTreeMap<i64, serde_json::Value>, Vec<u8>, Vec<u8>);
211
212        // Directly deserialize the CBOR data
213        let cose_mac0_data: CoseMac0Data = ciborium::de::from_reader(cbor_data)?;
214
215        // Extract the components
216        let (protected_header_cbor, unprotected_header, payload, tag) = (
217            cose_mac0_data.0,
218            cose_mac0_data.1,
219            cose_mac0_data.2,
220            cose_mac0_data.3,
221        );
222
223        // Deserialize the protected header
224        let protected_header: BTreeMap<i64, serde_json::Value> = if protected_header_cbor.is_empty()
225        {
226            BTreeMap::new()
227        } else {
228            ciborium::de::from_reader::<BTreeMap<i64, serde_json::Value>, _>(
229                &protected_header_cbor[..],
230            )
231            .unwrap_or_default()
232        };
233
234        Ok(CoseMac0 {
235            protected_header,
236            unprotected_header,
237            payload,
238            tag,
239        })
240    }
241
242    pub fn get_payload(&self) -> &[u8] {
243        &self.payload
244    }
245
246    pub fn get_kid(&self) -> Option<Vec<u8>> {
247        if let Some(serde_json::Value::Array(kid_array)) = self.unprotected_header.get(&HEADER_KID)
248        {
249            // Convert array of numbers back to bytes
250            let bytes: Option<Vec<u8>> = kid_array
251                .iter()
252                .map(|value| {
253                    if let serde_json::Value::Number(num) = value {
254                        num.as_u64()
255                            .and_then(|n| if n <= 255 { Some(n as u8) } else { None })
256                    } else {
257                        None
258                    }
259                })
260                .collect();
261            bytes
262        } else if let Some(serde_json::Value::String(kid)) =
263            self.unprotected_header.get(&HEADER_KID)
264        {
265            // For backward compatibility, also handle string values
266            Some(kid.as_bytes().to_vec())
267        } else {
268            None
269        }
270    }
271}