common_access_token/header.rs
1//! # Header Types for Common Access Token
2//!
3//! This module provides the header structure and related types for Common Access Tokens.
4//!
5//! Headers in Common Access Tokens are divided into two categories:
6//!
7//! - **Protected Headers**: These headers are integrity-protected and are part of the signature input.
8//! - **Unprotected Headers**: These headers are not integrity-protected and can be modified without invalidating the signature.
9//!
10//! The most important header parameters are:
11//!
12//! - **Algorithm (alg)**: Specifies the cryptographic algorithm used to secure the token.
13//! - **Key ID (kid)**: Identifies the key used to secure the token.
14
15use crate::constants::cose_algs;
16use std::collections::BTreeMap;
17
18/// Supported algorithms for token signing and verification.
19///
20/// This enum represents the cryptographic algorithms that can be used
21/// to sign and verify Common Access Tokens.
22///
23/// Currently, only HMAC-SHA256 is supported, but the design allows for
24/// easy extension to support additional algorithms in the future.
25///
26/// # Example
27///
28/// ```
29/// use common_access_token::Algorithm;
30///
31/// // Create a token with HMAC-SHA256 algorithm
32/// let alg = Algorithm::HmacSha256;
33/// assert_eq!(alg.identifier(), 5); // COSE algorithm identifier
34/// ```
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub enum Algorithm {
37 /// HMAC with SHA-256 (COSE algorithm identifier: 5)
38 HmacSha256,
39}
40
41impl Algorithm {
42 /// Get the algorithm identifier as defined in the COSE spec
43 pub fn identifier(&self) -> i32 {
44 match self {
45 Algorithm::HmacSha256 => cose_algs::HMAC_SHA_256,
46 }
47 }
48
49 /// Create an Algorithm from an identifier
50 pub fn from_identifier(id: i32) -> Option<Self> {
51 match id {
52 cose_algs::HMAC_SHA_256 => Some(Algorithm::HmacSha256),
53 _ => None,
54 }
55 }
56}
57
58/// Key identifier that can be either a binary or string value.
59///
60/// The key identifier (kid) is used to indicate which key was used to secure the token.
61/// This allows the recipient to select the appropriate key for verification without
62/// having to try each key.
63///
64/// # Examples
65///
66/// Creating a string key identifier:
67///
68/// ```
69/// use common_access_token::KeyId;
70///
71/// let string_kid = KeyId::string("my-key-2023");
72/// ```
73///
74/// Creating a binary key identifier:
75///
76/// ```
77/// use common_access_token::KeyId;
78///
79/// let binary_kid = KeyId::binary(vec![0x01, 0x02, 0x03, 0x04]);
80/// ```
81#[derive(Debug, Clone, PartialEq, Eq)]
82pub enum KeyId {
83 /// Binary key identifier (for binary key identifiers like UUIDs or hashes)
84 Binary(Vec<u8>),
85 /// String key identifier (for human-readable key identifiers)
86 String(String),
87}
88
89impl KeyId {
90 /// Create a new binary key identifier
91 pub fn binary<T: Into<Vec<u8>>>(data: T) -> Self {
92 KeyId::Binary(data.into())
93 }
94
95 /// Create a new string key identifier
96 pub fn string<T: Into<String>>(data: T) -> Self {
97 KeyId::String(data.into())
98 }
99
100 /// Get the key identifier as bytes
101 pub fn as_bytes(&self) -> &[u8] {
102 match self {
103 KeyId::Binary(data) => data,
104 KeyId::String(data) => data.as_bytes(),
105 }
106 }
107}
108
109/// CBOR value type for header and claim values.
110///
111/// This enum represents the different types of values that can be stored
112/// in token headers and claims. It supports the most common CBOR data types:
113/// integers, byte strings, text strings, and maps (which can contain nested values).
114///
115/// # Examples
116///
117/// Creating different types of CBOR values:
118///
119/// ```
120/// use common_access_token::CborValue;
121/// use std::collections::BTreeMap;
122///
123/// // Integer value
124/// let int_value = CborValue::Integer(42);
125///
126/// // Text string value
127/// let text_value = CborValue::Text("Hello, world!".to_string());
128///
129/// // Byte string value
130/// let bytes_value = CborValue::Bytes(vec![0x01, 0x02, 0x03]);
131///
132/// // Map value (nested CBOR map)
133/// let mut map = BTreeMap::new();
134/// map.insert(1, CborValue::Text("nested value".to_string()));
135/// let map_value = CborValue::Map(map);
136/// ```
137#[derive(Debug, Clone, PartialEq)]
138pub enum CborValue {
139 /// Integer value (signed 64-bit integer)
140 Integer(i64),
141 /// Byte string value (binary data)
142 Bytes(Vec<u8>),
143 /// Text string value (UTF-8 encoded string)
144 Text(String),
145 /// Map value (nested CBOR map with integer keys and CBOR values)
146 Map(BTreeMap<i32, CborValue>),
147 /// Array value (list of CBOR values)
148 Array(Vec<CborValue>),
149 /// Null value
150 Null,
151}
152
153/// Type alias for header maps
154pub type HeaderMap = BTreeMap<i32, CborValue>;
155
156/// COSE header parameter labels
157pub mod labels {
158 use crate::constants::cose_labels;
159
160 /// Algorithm (used in protected header)
161 pub const ALG: i32 = cose_labels::ALG;
162 /// Key identifier (used in protected or unprotected header)
163 pub const KID: i32 = cose_labels::KID;
164}
165
166/// Header for a Common Access Token.
167///
168/// The header contains metadata about the token, such as the algorithm used
169/// for signing and the key identifier. It is divided into two parts:
170///
171/// - **Protected Header**: Contains parameters that are integrity-protected and
172/// included in the signature input. This typically includes the algorithm.
173///
174/// - **Unprotected Header**: Contains parameters that are not integrity-protected
175/// and can be modified without invalidating the signature. This might include
176/// non-critical metadata.
177///
178/// # Examples
179///
180/// Creating a header with algorithm and key identifier:
181///
182/// ```
183/// use common_access_token::{Algorithm, Header, KeyId};
184///
185/// let header = Header::new()
186/// .with_algorithm(Algorithm::HmacSha256)
187/// .with_protected_key_id(KeyId::string("my-key-2023"));
188///
189/// assert_eq!(header.algorithm(), Some(Algorithm::HmacSha256));
190/// ```
191#[derive(Debug, Clone, Default)]
192pub struct Header {
193 /// Protected header parameters (must be integrity protected)
194 pub protected: HeaderMap,
195 /// Unprotected header parameters
196 pub unprotected: HeaderMap,
197}
198
199impl Header {
200 /// Creates a new empty header with no parameters.
201 ///
202 /// # Example
203 ///
204 /// ```
205 /// use common_access_token::Header;
206 ///
207 /// let header = Header::new();
208 /// assert!(header.protected.is_empty());
209 /// assert!(header.unprotected.is_empty());
210 /// ```
211 pub fn new() -> Self {
212 Self::default()
213 }
214
215 /// Sets the algorithm in the protected header.
216 ///
217 /// The algorithm is always placed in the protected header because
218 /// it is a critical parameter that must be integrity-protected.
219 ///
220 /// # Example
221 ///
222 /// ```
223 /// use common_access_token::{Algorithm, Header};
224 ///
225 /// let header = Header::new().with_algorithm(Algorithm::HmacSha256);
226 /// assert_eq!(header.algorithm(), Some(Algorithm::HmacSha256));
227 /// ```
228 pub fn with_algorithm(mut self, alg: Algorithm) -> Self {
229 self.protected
230 .insert(labels::ALG, CborValue::Integer(alg.identifier() as i64));
231 self
232 }
233
234 /// Sets the key identifier in the protected header.
235 ///
236 /// The key identifier in the protected header is integrity-protected
237 /// and cannot be modified without invalidating the signature.
238 ///
239 /// # Example
240 ///
241 /// ```
242 /// use common_access_token::{Header, KeyId};
243 ///
244 /// let header = Header::new().with_protected_key_id(KeyId::string("my-key-2023"));
245 /// if let Some(KeyId::String(kid)) = header.key_id() {
246 /// assert_eq!(kid, "my-key-2023");
247 /// }
248 /// ```
249 pub fn with_protected_key_id(mut self, kid: KeyId) -> Self {
250 match kid {
251 KeyId::Binary(data) => {
252 self.protected.insert(labels::KID, CborValue::Bytes(data));
253 }
254 KeyId::String(data) => {
255 self.protected.insert(labels::KID, CborValue::Text(data));
256 }
257 }
258 self
259 }
260
261 /// Sets the key identifier in the unprotected header.
262 ///
263 /// The key identifier in the unprotected header is not integrity-protected
264 /// and can be modified without invalidating the signature.
265 ///
266 /// # Example
267 ///
268 /// ```
269 /// use common_access_token::{Header, KeyId};
270 ///
271 /// let header = Header::new().with_unprotected_key_id(KeyId::string("my-key-2023"));
272 /// if let Some(KeyId::String(kid)) = header.key_id() {
273 /// assert_eq!(kid, "my-key-2023");
274 /// }
275 /// ```
276 pub fn with_unprotected_key_id(mut self, kid: KeyId) -> Self {
277 match kid {
278 KeyId::Binary(data) => {
279 self.unprotected.insert(labels::KID, CborValue::Bytes(data));
280 }
281 KeyId::String(data) => {
282 self.unprotected.insert(labels::KID, CborValue::Text(data));
283 }
284 }
285 self
286 }
287
288 /// Gets the algorithm from the protected header.
289 ///
290 /// Returns `None` if the algorithm is not present or is not a valid algorithm.
291 ///
292 /// # Example
293 ///
294 /// ```
295 /// use common_access_token::{Algorithm, Header};
296 ///
297 /// let header = Header::new().with_algorithm(Algorithm::HmacSha256);
298 /// assert_eq!(header.algorithm(), Some(Algorithm::HmacSha256));
299 ///
300 /// let empty_header = Header::new();
301 /// assert_eq!(empty_header.algorithm(), None);
302 /// ```
303 pub fn algorithm(&self) -> Option<Algorithm> {
304 if let Some(CborValue::Integer(alg)) = self.protected.get(&labels::ALG) {
305 Algorithm::from_identifier(*alg as i32)
306 } else {
307 None
308 }
309 }
310
311 /// Gets the key identifier from the protected or unprotected header.
312 ///
313 /// This method first checks the protected header, and if the key identifier
314 /// is not found there, it checks the unprotected header.
315 ///
316 /// Returns `None` if the key identifier is not present in either header.
317 ///
318 /// # Example
319 ///
320 /// ```
321 /// use common_access_token::{Header, KeyId};
322 ///
323 /// let header = Header::new().with_protected_key_id(KeyId::string("my-key-2023"));
324 /// if let Some(KeyId::String(kid)) = header.key_id() {
325 /// assert_eq!(kid, "my-key-2023");
326 /// }
327 ///
328 /// let empty_header = Header::new();
329 /// assert_eq!(empty_header.key_id(), None);
330 /// ```
331 pub fn key_id(&self) -> Option<KeyId> {
332 // First check protected header
333 if let Some(kid) = self.protected.get(&labels::KID) {
334 return match kid {
335 CborValue::Bytes(data) => Some(KeyId::Binary(data.clone())),
336 CborValue::Text(data) => Some(KeyId::String(data.clone())),
337 _ => None,
338 };
339 }
340
341 // Then check unprotected header
342 if let Some(kid) = self.unprotected.get(&labels::KID) {
343 return match kid {
344 CborValue::Bytes(data) => Some(KeyId::Binary(data.clone())),
345 CborValue::Text(data) => Some(KeyId::String(data.clone())),
346 _ => None,
347 };
348 }
349
350 None
351 }
352}