ic_response_verification/verification/
certificate_header.rs

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/// Parsed `Ic-Certificate` header, containing a certificate and tree.
12#[derive(Debug, PartialEq, Eq)]
13pub struct CertificateHeader {
14    /// The [`Certificate`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#certificate) contained in the header.
15    pub certificate: Certificate,
16
17    /// A pruned hash tree containing a witness that certifies the response for the given certificate.
18    pub tree: HashTree,
19
20    /// The version of the verification algorithm that should be used to verify the response.
21    pub version: u8,
22
23    /// The path in the `HashTree` pointing to the CEL expression used to calulate the response's certification.
24    /// This field is not present for response verification v1.
25    pub expr_path: Option<Vec<String>>,
26}
27
28impl CertificateHeader {
29    /// Parses the given header and returns a new CertificateHeader.
30    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}