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}