1use std::fmt::DebugStruct;
2
3use ciborium::Value;
4
5use crate::cose::{
6 ContentNamespace, SAFE_CONTENT_NAMESPACE, SAFE_OBJECT_NAMESPACE, SafeObjectNamespace,
7 extract_integer, symmetric::CoseContentEncryptionAlgorithm,
8};
9
10#[derive(Debug)]
11pub(super) enum ExtractionError {
12 MissingNamespace,
13 InvalidNamespace,
14}
15
16pub(super) fn extract_safe_object_namespace(
17 header: &coset::Header,
18) -> Result<SafeObjectNamespace, ExtractionError> {
19 match extract_integer(header, SAFE_OBJECT_NAMESPACE, "safe object namespace") {
20 Ok(value) => value
21 .try_into()
22 .map_err(|_| ExtractionError::InvalidNamespace),
23 Err(_) => Err(ExtractionError::MissingNamespace),
24 }
25}
26
27pub(super) fn extract_safe_content_namespace<T: ContentNamespace>(
28 header: &coset::Header,
29) -> Result<T, ExtractionError> {
30 match extract_integer(header, SAFE_CONTENT_NAMESPACE, "safe content namespace") {
31 Ok(value) => value
32 .try_into()
33 .map_err(|_| ExtractionError::InvalidNamespace),
34 Err(_) => Err(ExtractionError::MissingNamespace),
35 }
36}
37
38pub(super) fn debug_fmt<C: ContentNamespace>(
39 debug_struct: &mut DebugStruct,
40 header: &coset::Header,
41) {
42 if let Ok(object_namespace) = extract_safe_object_namespace(header) {
43 debug_struct.field("object_namespace", &object_namespace);
44 }
45 if let Ok(content_namespace) = extract_safe_content_namespace::<C>(header) {
46 debug_struct.field("content_namespace", &content_namespace);
47 }
48 if let Some(algorithm) = header.alg.as_ref()
49 && let Ok(content_encryption_algorithm) =
50 CoseContentEncryptionAlgorithm::try_from(algorithm)
51 {
52 let label = match content_encryption_algorithm {
53 CoseContentEncryptionAlgorithm::Aes256Gcm => "AES-256-GCM",
54 CoseContentEncryptionAlgorithm::XAes256Gcm => "XAES-256-GCM",
55 CoseContentEncryptionAlgorithm::XChaCha20Poly1305 => "XChaCha20-Poly1305",
56 };
57 debug_struct.field("content_encryption_algorithm", &label);
58 }
59}
60
61pub(super) fn set_header_value(header: &mut coset::Header, label: i64, value: Value) {
62 if let Some((_, existing_value)) =
63 header
64 .rest
65 .iter_mut()
66 .find(|(existing_label, _)| matches!(existing_label, coset::Label::Int(existing) if *existing == label))
67 {
68 *existing_value = value;
69 } else {
70 header.rest.push((coset::Label::Int(label), value));
71 }
72}
73
74pub(super) fn set_safe_namespaces<T: ContentNamespace>(
75 header: &mut coset::Header,
76 object_namespace: SafeObjectNamespace,
77 content_namespace: T,
78) {
79 set_header_value(
80 header,
81 SAFE_OBJECT_NAMESPACE,
82 Value::from(i128::from(object_namespace)),
83 );
84 set_header_value(
85 header,
86 SAFE_CONTENT_NAMESPACE,
87 Value::from(content_namespace.into()),
88 );
89}
90
91pub(super) fn validate_safe_namespaces<T: ContentNamespace>(
96 header: &coset::Header,
97 expected_object_namespace: SafeObjectNamespace,
98 expected_content_namespace: T,
99) -> Result<(), ExtractionError> {
100 match extract_safe_object_namespace(header) {
101 Ok(ns) if ns == expected_object_namespace => (),
102 Ok(_) => return Err(ExtractionError::InvalidNamespace),
104 Err(ExtractionError::MissingNamespace) => (),
106 Err(ExtractionError::InvalidNamespace) => return Err(ExtractionError::InvalidNamespace),
109 }
110
111 match extract_safe_content_namespace::<T>(header) {
112 Ok(ns) if ns == expected_content_namespace => Ok(()),
113 Ok(_) => Err(ExtractionError::InvalidNamespace),
115 Err(ExtractionError::MissingNamespace) => Ok(()),
117 Err(ExtractionError::InvalidNamespace) => Err(ExtractionError::InvalidNamespace),
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use ciborium::Value;
126
127 use super::*;
128 use crate::{cose::SAFE_OBJECT_NAMESPACE, safe::DataEnvelopeNamespace};
129
130 fn count_label(header: &coset::Header, label: i64) -> usize {
131 header
132 .rest
133 .iter()
134 .filter(
135 |(existing_label, _)| {
136 matches!(existing_label, coset::Label::Int(existing) if *existing == label)
137 },
138 )
139 .count()
140 }
141
142 fn extract_safe_namespaces<T: ContentNamespace>(
143 header: &coset::Header,
144 ) -> Result<(SafeObjectNamespace, T), ExtractionError> {
145 let object_namespace = extract_safe_object_namespace(header)?;
146 let content_namespace = extract_safe_content_namespace(header)?;
147
148 Ok((object_namespace, content_namespace))
149 }
150
151 #[test]
152 fn set_safe_namespaces_sets_both_namespace_labels() {
153 let mut header = coset::HeaderBuilder::new().build();
154
155 set_safe_namespaces(
156 &mut header,
157 SafeObjectNamespace::DataEnvelope,
158 DataEnvelopeNamespace::ExampleNamespace,
159 );
160
161 let extracted = extract_safe_namespaces::<DataEnvelopeNamespace>(&header);
162 assert!(matches!(
163 extracted,
164 Ok((
165 SafeObjectNamespace::DataEnvelope,
166 DataEnvelopeNamespace::ExampleNamespace
167 ))
168 ));
169 }
170
171 #[test]
172 fn set_safe_namespaces_overwrites_existing_namespace_values() {
173 let mut header = coset::HeaderBuilder::new()
174 .value(SAFE_OBJECT_NAMESPACE, Value::from(999_i64))
175 .value(SAFE_CONTENT_NAMESPACE, Value::from(999_i64))
176 .build();
177
178 set_safe_namespaces(
179 &mut header,
180 SafeObjectNamespace::DataEnvelope,
181 DataEnvelopeNamespace::ExampleNamespace,
182 );
183
184 assert_eq!(count_label(&header, SAFE_OBJECT_NAMESPACE), 1);
185 assert_eq!(count_label(&header, SAFE_CONTENT_NAMESPACE), 1);
186 assert!(matches!(
187 extract_safe_namespaces::<DataEnvelopeNamespace>(&header),
188 Ok((
189 SafeObjectNamespace::DataEnvelope,
190 DataEnvelopeNamespace::ExampleNamespace
191 ))
192 ));
193 }
194
195 #[test]
196 fn extract_safe_namespaces_fails_when_namespace_missing() {
197 let header = coset::HeaderBuilder::new().build();
198
199 assert!(matches!(
200 extract_safe_namespaces::<DataEnvelopeNamespace>(&header),
201 Err(ExtractionError::MissingNamespace)
202 ));
203 }
204
205 #[test]
206 fn extract_safe_namespaces_fails_when_namespace_invalid() {
207 let header = coset::HeaderBuilder::new()
208 .value(
209 SAFE_OBJECT_NAMESPACE,
210 Value::from(SafeObjectNamespace::DataEnvelope as i64),
211 )
212 .value(SAFE_CONTENT_NAMESPACE, Value::from(999_i64))
213 .build();
214
215 assert!(matches!(
216 extract_safe_namespaces::<DataEnvelopeNamespace>(&header),
217 Err(ExtractionError::InvalidNamespace)
218 ));
219 }
220
221 #[test]
222 fn validate_safe_namespaces_allows_missing_labels_for_backwards_compat() {
223 let header = coset::HeaderBuilder::new().build();
224
225 let result = validate_safe_namespaces(
226 &header,
227 SafeObjectNamespace::DataEnvelope,
228 DataEnvelopeNamespace::ExampleNamespace,
229 );
230 assert!(result.is_ok());
231 }
232
233 #[test]
234 fn validate_safe_namespaces_rejects_namespace_mismatch() {
235 let mut header = coset::HeaderBuilder::new().build();
236 set_safe_namespaces(
237 &mut header,
238 SafeObjectNamespace::DataEnvelope,
239 DataEnvelopeNamespace::ExampleNamespace,
240 );
241
242 let result = validate_safe_namespaces(
243 &header,
244 SafeObjectNamespace::DataEnvelope,
245 DataEnvelopeNamespace::ExampleNamespace2,
246 );
247 assert!(matches!(result, Err(ExtractionError::InvalidNamespace)));
248 }
249}