1use super::{certificate_header_field::CertificateHeaderField, MIN_VERIFICATION_VERSION};
2use crate::{
3 base64::BASE64,
4 error::{ResponseVerificationError, ResponseVerificationResult},
5};
6use base64::Engine as _;
7use ic_cbor::{parse_cbor_string_array, CertificateToCbor, HashTreeToCbor};
8use ic_certification::{Certificate, HashTree};
9use log::warn;
10
11#[derive(Debug, PartialEq, Eq)]
13pub struct CertificateHeader {
14 pub certificate: Certificate,
16
17 pub tree: HashTree,
19
20 pub version: u8,
22
23 pub expr_path: Option<Vec<String>>,
26}
27
28impl CertificateHeader {
29 pub fn from(header_value: &str) -> ResponseVerificationResult<CertificateHeader> {
31 let mut certificate = None;
32 let mut tree = None;
33 let mut version = None;
34 let mut expr_path = None;
35
36 for field in header_value.split(',') {
37 if let Some(CertificateHeaderField(name, value)) = CertificateHeaderField::from(field) {
38 match name {
39 "certificate" => {
40 certificate = match certificate {
41 None => {
42 let certificate_bytes = decode_base64_header(value)?;
43 let certificate = Certificate::from_cbor(&certificate_bytes)?;
44
45 Some(certificate)
46 }
47 Some(existing_certificate) => {
48 warn!("Found duplicate certificate field in certificate header, ignoring...");
49
50 Some(existing_certificate)
51 }
52 };
53 }
54 "tree" => {
55 tree = match tree {
56 None => {
57 let tree_bytes = decode_base64_header(value)?;
58 let tree = HashTree::from_cbor(&tree_bytes)?;
59
60 Some(tree)
61 }
62 Some(existing_tree) => {
63 warn!(
64 "Found duplicate tree field in certificate header, ignoring..."
65 );
66
67 Some(existing_tree)
68 }
69 };
70 }
71 "version" => {
72 version = match version {
73 None => Some(parse_int_header(value)?),
74 Some(existing_version) => {
75 warn!(
76 "Found duplicate version field in certificate header, ignoring..."
77 );
78
79 Some(existing_version)
80 }
81 };
82 }
83 "expr_path" => {
84 expr_path = match expr_path {
85 None => {
86 let expr_path_bytes = decode_base64_header(value)?;
87 let expr_path = parse_cbor_string_array(&expr_path_bytes)?;
88
89 Some(expr_path)
90 }
91 Some(existing_expr_path) => {
92 warn!(
93 "Found duplicate expr_path field in certificate header, ignoring..."
94 );
95
96 Some(existing_expr_path)
97 }
98 };
99 }
100 _ => {}
101 }
102 }
103 }
104
105 let certificate = certificate.ok_or(ResponseVerificationError::HeaderMissingCertificate)?;
106 let tree = tree.ok_or(ResponseVerificationError::HeaderMissingTree)?;
107 let version = version.unwrap_or(MIN_VERIFICATION_VERSION);
108
109 Ok(CertificateHeader {
110 certificate,
111 tree,
112 version,
113 expr_path,
114 })
115 }
116}
117
118fn decode_base64_header(value: &str) -> ResponseVerificationResult<Vec<u8>> {
119 BASE64
120 .decode(value)
121 .map_err(ResponseVerificationError::Base64DecodingError)
122}
123
124fn parse_int_header(value: &str) -> ResponseVerificationResult<u8> {
125 value
126 .parse::<u8>()
127 .map_err(ResponseVerificationError::ParseIntError)
128}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133 use crate::test_utils::{create_encoded_header_field, create_header_field, create_tree};
134 use ic_response_verification_test_utils::{cbor_encode, create_certificate};
135
136 fn base64_encode_no_padding(data: &[u8]) -> String {
137 use base64::engine::general_purpose;
138 general_purpose::STANDARD_NO_PAD.encode(data)
139 }
140
141 #[test]
142 fn certificate_header_parses_valid_header() {
143 let certificate = create_certificate(None);
144 let tree = create_tree(None);
145 let version = 2u8;
146 let expr_path = vec!["/", "assets", "img.jpg"];
147 let header = [
148 create_encoded_header_field("certificate", cbor_encode(&certificate)),
149 create_encoded_header_field("tree", cbor_encode(&tree)),
150 create_header_field("version", &version.to_string()),
151 create_encoded_header_field("expr_path", cbor_encode(&expr_path)),
152 ]
153 .join(",");
154
155 let certificate_header = CertificateHeader::from(header.as_str()).unwrap();
156
157 assert_eq!(certificate_header.certificate, certificate);
158 assert_eq!(certificate_header.tree, tree);
159 assert_eq!(certificate_header.version, version);
160 assert_eq!(certificate_header.expr_path.unwrap(), expr_path);
161 }
162
163 #[test]
164 fn certificate_header_parses_valid_header_with_unpadded_base64() {
165 let certificate = create_certificate(None);
166 let tree = create_tree(None);
167 let version = 2u8;
168 let expr_path = vec!["/", "assets", "img.jpg"];
169 let header = [
170 create_header_field(
171 "certificate",
172 &base64_encode_no_padding(&cbor_encode(&certificate)),
173 ),
174 create_header_field("tree", &base64_encode_no_padding(&cbor_encode(&tree))),
175 create_header_field("version", &version.to_string()),
176 create_header_field(
177 "expr_path",
178 &base64_encode_no_padding(&cbor_encode(&expr_path)),
179 ),
180 ]
181 .join(",");
182
183 let certificate_header = CertificateHeader::from(header.as_str()).unwrap();
184
185 assert_eq!(certificate_header.certificate, certificate);
186 assert_eq!(certificate_header.tree, tree);
187 assert_eq!(certificate_header.version, version);
188 assert_eq!(certificate_header.expr_path.unwrap(), expr_path);
189 }
190
191 #[test]
192 fn certificate_header_ignores_extraneous_fields() {
193 let certificate = create_certificate(None);
194 let tree = create_tree(None);
195 let version = 2u8;
196 let expr_path = vec!["/", "assets", "img.jpg"];
197 let header = [
198 create_encoded_header_field("certificate", cbor_encode(&certificate)),
199 create_encoded_header_field("tree", cbor_encode(&tree)),
200 create_header_field("version", &version.to_string()),
201 create_encoded_header_field("expr_path", cbor_encode(&expr_path)),
202 create_encoded_header_field("garbage", "asdhlasjdasdoou"),
203 ]
204 .join(",");
205
206 let certificate_header = CertificateHeader::from(header.as_str()).unwrap();
207
208 assert_eq!(certificate_header.certificate, certificate);
209 assert_eq!(certificate_header.tree, tree);
210 assert_eq!(certificate_header.version, version);
211 assert_eq!(certificate_header.expr_path.unwrap(), expr_path);
212 }
213
214 #[test]
215 fn certificate_header_throws_with_missing_tree() {
216 let certificate = create_certificate(None);
217 let version = 2u8;
218 let expr_path = cbor_encode(&vec!["/", "assets", "img.jpg"]);
219 let header = [
220 create_encoded_header_field("certificate", cbor_encode(&certificate)),
221 create_header_field("version", &version.to_string()),
222 create_encoded_header_field("expr_path", expr_path),
223 ]
224 .join(",");
225
226 let certificate_header = CertificateHeader::from(header.as_str());
227
228 assert!(matches!(
229 certificate_header,
230 Err(ResponseVerificationError::HeaderMissingTree)
231 ));
232 }
233
234 #[test]
235 fn certificate_header_throws_with_empty_tree() {
236 let certificate = create_certificate(None);
237 let version = 2u8;
238 let expr_path = cbor_encode(&vec!["/", "assets", "img.jpg"]);
239 let header = [
240 create_encoded_header_field("certificate", cbor_encode(&certificate)),
241 create_encoded_header_field("tree", ""),
242 create_header_field("version", &version.to_string()),
243 create_encoded_header_field("expr_path", expr_path),
244 ]
245 .join(",");
246
247 let result = CertificateHeader::from(header.as_str());
248
249 assert!(matches!(
250 result,
251 Err(ResponseVerificationError::HeaderMissingTree)
252 ));
253 }
254
255 #[test]
256 fn certificate_header_throws_with_missing_certificate() {
257 let tree = create_tree(None);
258 let version = 2u8;
259 let expr_path = cbor_encode(&vec!["/", "assets", "img.jpg"]);
260 let header = [
261 create_encoded_header_field("tree", cbor_encode(&tree)),
262 create_header_field("version", &version.to_string()),
263 create_encoded_header_field("expr_path", expr_path),
264 ]
265 .join(",");
266
267 let certificate_header = CertificateHeader::from(header.as_str());
268
269 assert!(matches!(
270 certificate_header,
271 Err(ResponseVerificationError::HeaderMissingCertificate)
272 ));
273 }
274
275 #[test]
276 fn certificate_header_throws_with_empty_certificate() {
277 let tree = create_tree(None);
278 let version = 2u8;
279 let expr_path = cbor_encode(&vec!["/", "assets", "img.jpg"]);
280 let header = [
281 create_encoded_header_field("certificate", ""),
282 create_encoded_header_field("tree", cbor_encode(&tree)),
283 create_header_field("version", &version.to_string()),
284 create_encoded_header_field("expr_path", expr_path),
285 ]
286 .join(",");
287
288 let result = CertificateHeader::from(header.as_str());
289
290 assert!(matches!(
291 result,
292 Err(ResponseVerificationError::HeaderMissingCertificate)
293 ));
294 }
295
296 #[test]
297 fn certificate_header_handles_missing_version() {
298 let certificate = create_certificate(None);
299 let tree = create_tree(None);
300 let expr_path = vec!["/", "assets", "img.jpg"];
301 let header = [
302 create_encoded_header_field("certificate", cbor_encode(&certificate)),
303 create_encoded_header_field("tree", cbor_encode(&tree)),
304 create_encoded_header_field("expr_path", cbor_encode(&&expr_path)),
305 ]
306 .join(",");
307
308 let certificate_header = CertificateHeader::from(header.as_str()).unwrap();
309
310 assert_eq!(certificate_header.certificate, certificate);
311 assert_eq!(certificate_header.tree, tree);
312 assert_eq!(certificate_header.version, MIN_VERIFICATION_VERSION);
313 assert_eq!(certificate_header.expr_path.unwrap(), expr_path);
314 }
315
316 #[test]
317 fn certificate_header_handles_empty_version() {
318 let certificate = create_certificate(None);
319 let tree = create_tree(None);
320 let expr_path = vec!["/", "assets", "img.jpg"];
321 let header = [
322 create_encoded_header_field("certificate", cbor_encode(&certificate)),
323 create_encoded_header_field("tree", cbor_encode(&tree)),
324 create_encoded_header_field("version", ""),
325 create_encoded_header_field("expr_path", cbor_encode(&expr_path)),
326 ]
327 .join(",");
328
329 let certificate_header = CertificateHeader::from(header.as_str()).unwrap();
330
331 assert_eq!(certificate_header.certificate, certificate);
332 assert_eq!(certificate_header.tree, tree);
333 assert_eq!(certificate_header.version, MIN_VERIFICATION_VERSION);
334 assert_eq!(certificate_header.expr_path.unwrap(), expr_path);
335 }
336
337 #[test]
338 fn certificate_header_handles_missing_expr_path() {
339 let certificate = create_certificate(None);
340 let tree = create_tree(None);
341 let version = 2u8;
342 let header = [
343 create_encoded_header_field("certificate", cbor_encode(&certificate)),
344 create_encoded_header_field("tree", cbor_encode(&tree)),
345 create_header_field("version", &version.to_string()),
346 ]
347 .join(",");
348
349 let certificate_header = CertificateHeader::from(header.as_str()).unwrap();
350
351 assert_eq!(certificate_header.certificate, certificate);
352 assert_eq!(certificate_header.tree, tree);
353 assert_eq!(certificate_header.version, version);
354 assert!(certificate_header.expr_path.is_none());
355 }
356
357 #[test]
358 fn certificate_header_handles_empty_expr_path() {
359 let certificate = create_certificate(None);
360 let tree = create_tree(None);
361 let version = 2u8;
362 let header = [
363 create_encoded_header_field("certificate", cbor_encode(&certificate)),
364 create_encoded_header_field("tree", cbor_encode(&tree)),
365 create_header_field("version", &version.to_string()),
366 create_encoded_header_field("expr_path", ""),
367 ]
368 .join(",");
369
370 let certificate_header = CertificateHeader::from(header.as_str()).unwrap();
371
372 assert_eq!(certificate_header.certificate, certificate);
373 assert_eq!(certificate_header.tree, tree);
374 assert_eq!(certificate_header.version, version);
375 assert!(certificate_header.expr_path.is_none());
376 }
377
378 #[test]
379 fn certificate_header_ignores_duplicate_fields() {
380 let certificate = create_certificate(None);
381 let tree = create_tree(None);
382 let version = 2u8;
383 let expr_path = vec!["/", "assets", "img.jpg"];
384
385 let second_certificate = "Goodbye Certificate!";
386 let second_tree = "Goodbye tree!";
387 let second_version = 3u8;
388 let second_expr_path = "Goodbye expr_path!";
389
390 let header = [
391 create_encoded_header_field("certificate", cbor_encode(&certificate)),
392 create_encoded_header_field("certificate", second_certificate),
393 create_encoded_header_field("tree", cbor_encode(&tree)),
394 create_encoded_header_field("tree", second_tree),
395 create_header_field("version", &version.to_string()),
396 create_encoded_header_field("expr_path", cbor_encode(&expr_path)),
397 create_encoded_header_field("version", second_version.to_string()),
398 create_encoded_header_field("expr_path", second_expr_path),
399 ]
400 .join(",");
401
402 let certificate_header = CertificateHeader::from(header.as_str()).unwrap();
403
404 assert_eq!(certificate_header.certificate, certificate);
405 assert_eq!(certificate_header.tree, tree);
406 assert_eq!(certificate_header.version, version);
407 assert_eq!(certificate_header.expr_path.unwrap(), expr_path);
408 }
409}