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}