Skip to main content

ic_http_certification/utils/
response_header.rs

1use crate::{HttpResponse, CERTIFICATE_HEADER_NAME};
2use base64::{engine::general_purpose::STANDARD as BASE64, Engine};
3use ic_certification::HashTree;
4use serde::Serialize;
5
6/// Adds the [`IC-Certificate` header](https://internetcomputer.org/docs/references/http-gateway-protocol-spec/#the-certificate-header)
7/// to a given [`HttpResponse`]. This header is used by the HTTP Gateway to verify the authenticity of query call responses made to the
8/// `http_request` method of the target canister.
9///
10/// # Arguments
11///
12/// * `data_certificate` - A certificate used by the HTTP Gateway to verify a response.
13///   Retrieved using `ic_cdk::api::data_certificate`. This value is not validated by this function
14///   and is expected to be a valid certificate. The function will not fail if the certificate is invalid,
15///   but verification of the certificate by the HTTP Gateway will fail.
16/// * `response` - The [`HttpResponse`] to add the certificate header to.
17///   Created using [`HttpResponse::builder()`](crate::HttpResponse::builder).
18/// * `witness` - A pruned merkle tree revealing the relevant certification for the current response.
19///   Created using [`HttpCertificationTree::witness()`](crate::HttpCertificationTree::witness).
20///   The witness is not validated to be correct for the given response, and the function will not fail
21///   if the witness is invalid. The HTTP Gateway will fail to verify the response if the witness is invalid.
22/// * `expr_path` - An expression path for the current response informing the HTTP Gateway where the
23///   relevant certification is present in the merkle tree. Created using
24///   [`HttpCertificationPath::to_expr_path()`](crate::HttpCertificationPath::to_expr_path). The expression path
25///   is not validated to be correct for the given response, and the function will not fail if the expression path is invalid.
26///
27/// # Examples
28///
29/// ```
30/// use ic_http_certification::{HttpCertification, HttpRequest, HttpResponse, DefaultCelBuilder, DefaultResponseCertification, HttpCertificationTree, HttpCertificationTreeEntry, HttpCertificationPath, CERTIFICATE_EXPRESSION_HEADER_NAME, CERTIFICATE_HEADER_NAME, utils::add_v2_certificate_header};
31///
32/// let cel_expr = DefaultCelBuilder::full_certification().build();
33///
34/// let request = HttpRequest::get("/index.html?foo=a&bar=b&baz=c").build();
35///
36/// let mut response = HttpResponse::builder()
37///     .with_headers(vec![(CERTIFICATE_EXPRESSION_HEADER_NAME.to_string(), cel_expr.to_string())])
38///     .build();
39///
40/// let request_url = "/example.json";
41/// let path = HttpCertificationPath::exact(request_url);
42/// let expr_path = path.to_expr_path();
43///
44/// let certification = HttpCertification::full(&cel_expr, &request, &response, None).unwrap();
45/// let entry = HttpCertificationTreeEntry::new(&path, &certification);
46///
47/// let mut http_certification_tree = HttpCertificationTree::default();
48/// http_certification_tree.insert(&entry);
49///
50/// // this should normally be retrieved using `ic_cdk::api::data_certificate()`.
51/// let data_certificate = vec![1, 2, 3];
52///
53/// let witness = http_certification_tree.witness(&entry, request_url).unwrap();
54/// add_v2_certificate_header(
55///     &data_certificate,
56///     &mut response,
57///     &witness,
58///     &expr_path
59/// );
60///
61/// assert_eq!(
62///     response.headers(),
63///     vec![
64///         (CERTIFICATE_EXPRESSION_HEADER_NAME.to_string(), cel_expr.to_string()),
65///         (
66///             CERTIFICATE_HEADER_NAME.to_string(),
67///             "certificate=:AQID:, tree=:2dn3gwJJaHR0cF9leHBygwJMZXhhbXBsZS5qc29ugwJDPCQ+gwJYIFJ2k+R/YYbgGPADidRdRwDurH06HXACVHlTIVrv1q4WgwJYIGvHTtoVXrGXb4aD1BvH+OW26d0CtLUdA43LP+42N6xpgwJYIM7zUx3VibIaHEUF14Kx813l3Xlilg43Y5uGaABAA/i9ggNA:, expr_path=:2dn3g2lodHRwX2V4cHJsZXhhbXBsZS5qc29uYzwkPg==:, version=2".to_string(),
68///         ),
69///     ]
70/// );
71/// ```
72pub fn add_v2_certificate_header(
73    data_certificate: &[u8],
74    response: &mut HttpResponse,
75    witness: &HashTree,
76    expr_path: &[String],
77) {
78    let witness = cbor_encode(witness);
79    let expr_path = cbor_encode(&expr_path);
80
81    response.add_header((
82        CERTIFICATE_HEADER_NAME.to_string(),
83        format!(
84            "certificate=:{}:, tree=:{}:, expr_path=:{}:, version=2",
85            BASE64.encode(data_certificate),
86            BASE64.encode(witness),
87            BASE64.encode(expr_path)
88        ),
89    ));
90}
91
92fn cbor_encode(value: &impl Serialize) -> Vec<u8> {
93    let mut serializer = serde_cbor::Serializer::new(Vec::new());
94    serializer
95        .self_describe()
96        .expect("Failed to self describe CBOR");
97    value
98        .serialize(&mut serializer)
99        .expect("Failed to serialize value");
100    serializer.into_inner()
101}
102
103/// Serializes a value as self-describing CBOR and returns its base64 (standard alphabet) encoding.
104///
105/// This is useful for encoding `expr_path` and witness trees in the IC-Certificate header format.
106pub fn cbor_encode_to_base64(value: &impl Serialize) -> String {
107    BASE64.encode(cbor_encode(value))
108}
109
110/// Builds the value portion of the `IC-Certificate` response header.
111///
112/// Returns the full header value string in the format:
113/// `certificate=:…:, tree=:…:, expr_path=:…:, version=2`
114///
115/// Unlike [`add_v2_certificate_header`], this function returns the header value as a [`String`]
116/// rather than mutating an [`HttpResponse`]. The `expr_path_b64` argument is expected to already
117/// be a base64-encoded self-describing CBOR value (e.g. produced by [`cbor_encode_to_base64`]).
118pub fn build_v2_certificate_header_value(
119    data_certificate: &[u8],
120    witness: &HashTree,
121    expr_path_b64: &str,
122) -> String {
123    let witness = cbor_encode(witness);
124    format!(
125        "certificate=:{}:, tree=:{}:, expr_path=:{}:, version=2",
126        BASE64.encode(data_certificate),
127        BASE64.encode(witness),
128        expr_path_b64,
129    )
130}