Skip to content

feat: move request and response hashing from ic-response-verification to ic-http-certification #245

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Dec 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions packages/ic-http-certification/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ candid.workspace = true
serde.workspace = true
http.workspace = true
urlencoding.workspace = true
ic-representation-independent-hash.workspace = true
ic-certification.workspace = true
thiserror.workspace = true

[dev-dependencies]
rstest.workspace = true
hex.workspace = true
8 changes: 4 additions & 4 deletions packages/ic-http-certification/src/cel/cel_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::borrow::Cow;
/// A certification CEL expression defintion.
/// Contains an enum variant for each CEL function supported for certification.
/// Currently only one variant is supported: [CelExpression::DefaultCertification].
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CelExpression<'a> {
/// A certification CEL expression definition that uses the `default_certification` function.
/// This is currently the only supported function.
Expand All @@ -27,7 +27,7 @@ impl<'a> CelExpression<'a> {
///
/// [request_certification](DefaultCertification::request_certification) is used for configuring request certification, and
/// [response_certification](DefaultCertification::response_certification) is used for configuring response certification.
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DefaultCertification<'a> {
/// Options for configuring certification of a request.
///
Expand All @@ -48,7 +48,7 @@ pub struct DefaultCertification<'a> {
/// The request method and body are always certified, but this struct allows configuring the
/// certification of request [headers](DefaultRequestCertification::headers) and
/// [query parameters](DefaultRequestCertification::query_parameters).
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DefaultRequestCertification<'a> {
/// A list of request headers to include in certification.
///
Expand All @@ -70,7 +70,7 @@ pub struct DefaultRequestCertification<'a> {
/// [CertifiedResponseHeaders](DefaultResponseCertification::CertifiedResponseHeaders) variant,
/// and response headers may be excluded using the
/// [ResponseHeaderExclusions](DefaultResponseCertification::ResponseHeaderExclusions) variant.
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DefaultResponseCertification<'a> {
/// A list of response headers to include in certification.
///
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@ pub use request_hash::*;

mod response_hash;
pub use response_hash::*;

/// Sha256 Digest: 32 bytes
pub type Hash = [u8; 32];
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
use crate::error::ResponseVerificationResult;
use crate::types::RequestCertification;
use ic_certification::hash_tree::Hash;
use ic_http_certification::HttpRequest;
use super::Hash;
use crate::{cel::DefaultRequestCertification, HttpCertificationResult, HttpRequest};
use ic_representation_independent_hash::{hash, representation_independent_hash, Value};

/// Calculates the
/// [Representation Independent Hash](https://internetcomputer.org/docs/current/references/ic-interface-spec/#hash-of-map)
/// of [crate::types::Request] according to [crate::types::RequestCertification] returned from
/// [crate::cel::cel_to_certification].
pub fn request_hash(
request: &HttpRequest,
request_certification: &RequestCertification,
) -> ResponseVerificationResult<Hash> {
pub fn request_hash<'a>(
request: &'a HttpRequest,
request_certification: &'a DefaultRequestCertification,
) -> HttpCertificationResult<Hash> {
let mut filtered_headers = get_filtered_headers(&request.headers, request_certification);

filtered_headers.push((
Expand All @@ -37,14 +35,14 @@ pub fn request_hash(

fn get_filtered_headers(
headers: &[(String, String)],
request_certification: &RequestCertification,
request_certification: &DefaultRequestCertification,
) -> Vec<(String, Value)> {
headers
.iter()
.filter_map(|(header_name, header_value)| {
let is_header_included =
request_certification
.certified_request_headers
.headers
.iter()
.any(|header_to_include| {
header_to_include.eq_ignore_ascii_case(&header_name.to_string())
Expand All @@ -62,7 +60,7 @@ fn get_filtered_headers(
.collect()
}

fn get_filtered_query(query: &str, request_certification: &RequestCertification) -> String {
fn get_filtered_query(query: &str, request_certification: &DefaultRequestCertification) -> String {
let filtered_query_string = query
.split('&')
.filter(|query_fragment| {
Expand All @@ -71,11 +69,12 @@ fn get_filtered_query(query: &str, request_certification: &RequestCertification)

query_param_name
.map(|query_param_name| {
request_certification.certified_query_parameters.iter().any(
|query_param_to_include| {
request_certification
.query_parameters
.iter()
.any(|query_param_to_include| {
query_param_to_include.eq_ignore_ascii_case(query_param_name)
},
)
})
})
.unwrap_or(false)
})
Expand All @@ -88,12 +87,13 @@ fn get_filtered_query(query: &str, request_certification: &RequestCertification)
#[cfg(test)]
mod tests {
use super::*;
use std::borrow::Cow;

#[test]
fn request_hash_without_query() {
let request_certification = RequestCertification {
certified_request_headers: vec!["host".into()],
certified_query_parameters: vec![],
let request_certification = DefaultRequestCertification {
headers: Cow::Borrowed(&["host"]),
query_parameters: Cow::Borrowed(&[]),
};
let request = create_request("https://ic0.app");
let expected_hash =
Expand All @@ -107,9 +107,9 @@ mod tests {

#[test]
fn request_hash_with_query() {
let request_certification = RequestCertification {
certified_request_headers: vec!["host".into()],
certified_query_parameters: vec!["q".into(), "name".into()],
let request_certification = DefaultRequestCertification {
headers: Cow::Borrowed(&["host"]),
query_parameters: Cow::Borrowed(&["q", "name"]),
};
let request =
create_request("https://ic0.app?q=hello+world&name=foo&name=bar&color=purple");
Expand All @@ -124,9 +124,9 @@ mod tests {

#[test]
fn request_hash_query_order_matters() {
let request_certification = RequestCertification {
certified_request_headers: vec!["host".into()],
certified_query_parameters: vec!["q".into(), "name".into()],
let request_certification = DefaultRequestCertification {
headers: Cow::Borrowed(&["host"]),
query_parameters: Cow::Borrowed(&["q", "name"]),
};
let request =
create_request("https://ic0.app?q=hello+world&name=foo&name=bar&color=purple");
Expand All @@ -141,9 +141,9 @@ mod tests {

#[test]
fn request_hash_query_with_fragment_does_not_change() {
let request_certification = RequestCertification {
certified_request_headers: vec!["host".into()],
certified_query_parameters: vec!["q".into(), "name".into()],
let request_certification = DefaultRequestCertification {
headers: Cow::Borrowed(&["host"]),
query_parameters: Cow::Borrowed(&["q", "name"]),
};
let request =
create_request("https://ic0.app?q=hello+world&name=foo&name=bar&color=purple");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use crate::types::ResponseCertification;
use ic_certification::hash_tree::Hash;
use ic_http_certification::HttpResponse;
use super::Hash;
use crate::{DefaultResponseCertification, HttpResponse};
use ic_representation_independent_hash::{hash, representation_independent_hash, Value};

const CERTIFICATE_HEADER_NAME: &str = "IC-Certificate";
Expand All @@ -20,19 +19,19 @@ pub struct ResponseHeaders {

/// Filters headers of [crate::types::Response] according to [crate::types::ResponseCertification]
/// returned from [crate::cel::cel_to_certification].
pub fn filter_response_headers(
pub fn filter_response_headers<'a>(
response: &HttpResponse,
response_certification: &ResponseCertification,
response_certification: &DefaultResponseCertification<'a>,
) -> ResponseHeaders {
let headers_filter: Box<dyn Fn(_) -> _> = match response_certification {
ResponseCertification::CertifiedHeaders(headers_to_include) => {
DefaultResponseCertification::CertifiedResponseHeaders(headers_to_include) => {
Box::new(move |header_name: &String| {
headers_to_include.iter().any(|header_to_include| {
header_to_include.eq_ignore_ascii_case(&header_name.to_string())
})
})
}
ResponseCertification::HeaderExclusions(headers_to_exclude) => {
DefaultResponseCertification::ResponseHeaderExclusions(headers_to_exclude) => {
Box::new(move |header_name: &String| {
!headers_to_exclude.iter().any(|header_to_exclude| {
header_to_exclude.eq_ignore_ascii_case(&header_name.to_string())
Expand Down Expand Up @@ -116,7 +115,7 @@ pub fn response_headers_hash(status_code: &u64, response_headers: &ResponseHeade
/// [crate::cel::cel_to_certification].
pub fn response_hash(
response: &HttpResponse,
response_certification: &ResponseCertification,
response_certification: &DefaultResponseCertification,
) -> Hash {
let filtered_headers = filter_response_headers(response, response_certification);
let concatenated_hashes = [
Expand All @@ -131,7 +130,6 @@ pub fn response_hash(
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::test_utils::remove_whitespace;

const HELLO_WORLD_BODY: &[u8] = &[72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33];
const CERTIFICATE: &str = "certificate=:SGVsbG8gQ2VydGlmaWNhdGUh:,tree=:SGVsbG8gVHJlZSE=:";
Expand Down Expand Up @@ -167,7 +165,7 @@ mod tests {
#[test]
fn response_with_certified_headers_without_excluded_headers() {
let response_certification =
ResponseCertification::CertifiedHeaders(vec!["Accept-Encoding".into()]);
DefaultResponseCertification::certified_response_headers(&["Accept-Encoding"]);
let response = create_response(CERTIFIED_HEADERS_CEL_EXPRESSION);
let response_headers = filter_response_headers(&response, &response_certification);

Expand All @@ -179,9 +177,9 @@ mod tests {

#[test]
fn response_with_certified_headers() {
let response_certification = ResponseCertification::CertifiedHeaders(vec![
"Accept-Encoding".into(),
"Cache-Control".into(),
let response_certification = DefaultResponseCertification::certified_response_headers(&[
"Accept-Encoding",
"Cache-Control",
]);
let response = create_response(CERTIFIED_HEADERS_CEL_EXPRESSION);
let response_headers = filter_response_headers(&response, &response_certification);
Expand All @@ -198,9 +196,9 @@ mod tests {

#[test]
fn response_hash_with_certified_headers() {
let response_certification = ResponseCertification::CertifiedHeaders(vec![
"Accept-Encoding".into(),
"Cache-Control".into(),
let response_certification = DefaultResponseCertification::certified_response_headers(&[
"Accept-Encoding",
"Cache-Control",
]);
let response = create_response(CERTIFIED_HEADERS_CEL_EXPRESSION);
let expected_hash =
Expand All @@ -215,7 +213,7 @@ mod tests {
#[test]
fn response_hash_with_certified_headers_without_excluded_headers() {
let response_certification =
ResponseCertification::CertifiedHeaders(vec!["Accept-Encoding".into()]);
DefaultResponseCertification::certified_response_headers(&["Accept-Encoding"]);
let response = create_response(CERTIFIED_HEADERS_CEL_EXPRESSION);
let response_without_excluded_headers = HttpResponse {
status_code: 200,
Expand All @@ -238,9 +236,9 @@ mod tests {

#[test]
fn response_hash_with_header_exclusions() {
let response_certification = ResponseCertification::HeaderExclusions(vec![
"Accept-Encoding".into(),
"Cache-Control".into(),
let response_certification = DefaultResponseCertification::response_header_exclusions(&[
"Accept-Encoding",
"Cache-Control",
]);
let response = create_response(HEADER_EXCLUSIONS_CEL_EXPRESSION);
let expected_hash =
Expand All @@ -255,7 +253,7 @@ mod tests {
#[test]
fn response_hash_with_header_exclusions_without_excluded_headers() {
let response_certification =
ResponseCertification::HeaderExclusions(vec!["Content-Security-Policy".into()]);
DefaultResponseCertification::response_header_exclusions(&["Content-Security-Policy"]);
let response = create_response(HEADER_EXCLUSIONS_CEL_EXPRESSION);
let response_without_excluded_headers = HttpResponse {
status_code: 200,
Expand All @@ -280,9 +278,9 @@ mod tests {

#[test]
fn response_headers_hash_with_certified_headers() {
let response_certification = ResponseCertification::CertifiedHeaders(vec![
"Accept-Encoding".into(),
"Cache-Control".into(),
let response_certification = DefaultResponseCertification::certified_response_headers(&[
"Accept-Encoding",
"Cache-Control",
]);
let response = create_response(CERTIFIED_HEADERS_CEL_EXPRESSION);
let expected_hash =
Expand All @@ -298,7 +296,7 @@ mod tests {
#[test]
fn response_headers_hash_with_certified_headers_without_excluded_headers() {
let response_certification =
ResponseCertification::CertifiedHeaders(vec!["Accept-Encoding".into()]);
DefaultResponseCertification::certified_response_headers(&["Accept-Encoding"]);
let response = create_response(CERTIFIED_HEADERS_CEL_EXPRESSION);
let response_without_excluded_headers = HttpResponse {
status_code: 200,
Expand Down Expand Up @@ -327,9 +325,9 @@ mod tests {

#[test]
fn response_headers_hash_with_header_exclusions() {
let response_certification = ResponseCertification::HeaderExclusions(vec![
"Accept-Encoding".into(),
"Cache-Control".into(),
let response_certification = DefaultResponseCertification::response_header_exclusions(&[
"Accept-Encoding",
"Cache-Control",
]);
let response = create_response(HEADER_EXCLUSIONS_CEL_EXPRESSION);
let expected_hash =
Expand All @@ -345,7 +343,7 @@ mod tests {
#[test]
fn response_headers_hash_with_header_exclusions_without_excluded_headers() {
let response_certification =
ResponseCertification::HeaderExclusions(vec!["Content-Security-Policy".into()]);
DefaultResponseCertification::response_header_exclusions(&["Content-Security-Policy"]);
let response = create_response(HEADER_EXCLUSIONS_CEL_EXPRESSION);
let response_without_excluded_headers = HttpResponse {
status_code: 200,
Expand Down Expand Up @@ -399,4 +397,8 @@ mod tests {
body: HELLO_WORLD_BODY.into(),
}
}

fn remove_whitespace<'a>(s: &'a str) -> String {
s.chars().filter(|c| !c.is_whitespace()).collect()
}
}
4 changes: 3 additions & 1 deletion packages/ic-http-certification/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,9 @@ Typically these requests have been routed through `raw` Internet Computer URLs i
)]

pub mod cel;
pub use cel::{DefaultCelBuilder, DefaultResponseCertification};
pub use cel::{CelExpression, DefaultCelBuilder, DefaultResponseCertification};
pub mod hash;
pub use hash::*;
pub mod error;
pub use error::*;
pub mod http;
Expand Down
3 changes: 2 additions & 1 deletion packages/ic-response-verification-wasm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"browser": "./dist/web/web.js",
"types": "./dist/web/web.d.ts",
"scripts": {
"build": "../../scripts/package.sh . ./dist"
"build": "../../scripts/package.sh . ./dist",
"test": " wasm-pack test --node"
}
}
2 changes: 1 addition & 1 deletion packages/ic-response-verification-wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ pub fn get_max_verification_version() -> u8 {
}

#[wasm_bindgen(start)]
pub fn main() {
pub fn main_js() {
console_error_panic_hook::set_once();
log::set_logger(&wasm_bindgen_console_logger::DEFAULT_LOGGER).unwrap();
log::set_max_level(log::LevelFilter::Trace);
Expand Down
Loading