From d4a550660f9af5fdcb33d8e5934cb01a329e72bd Mon Sep 17 00:00:00 2001 From: NathanosDev Date: Tue, 5 Dec 2023 12:58:05 +0100 Subject: [PATCH] feat: move request and response hashing from ic-response-verification to ic-http-certification BREAKING CHANGE: --- Cargo.lock | 3 + packages/ic-http-certification/Cargo.toml | 3 + .../src/cel/cel_types.rs | 8 +- .../src/hash/mod.rs | 3 + .../src/hash/request_hash.rs | 54 ++-- .../src/hash/response_hash.rs | 60 ++-- packages/ic-http-certification/src/lib.rs | 4 +- .../package.json | 3 +- .../ic-response-verification-wasm/src/lib.rs | 2 +- .../src/cel/ast_mapping.rs | 74 +++-- .../ic-response-verification/src/cel/error.rs | 2 +- .../ic-response-verification/src/cel/mod.rs | 12 +- .../src/cel/parser.rs | 48 ++-- .../ic-response-verification/src/cel/tests.rs | 112 ++++++++ packages/ic-response-verification/src/lib.rs | 1 - .../src/types/certification.rs | 28 -- .../ic-response-verification/src/types/mod.rs | 4 - .../src/validation/v2_validation.rs | 24 +- .../src/verification/body.rs | 13 +- .../verify_request_response_pair.rs | 256 +++++++----------- .../tests/cel-parsing.rs | 92 ------- 21 files changed, 380 insertions(+), 426 deletions(-) rename packages/{ic-response-verification => ic-http-certification}/src/hash/mod.rs (84%) rename packages/{ic-response-verification => ic-http-certification}/src/hash/request_hash.rs (77%) rename packages/{ic-response-verification => ic-http-certification}/src/hash/response_hash.rs (88%) create mode 100644 packages/ic-response-verification/src/cel/tests.rs delete mode 100644 packages/ic-response-verification/src/types/certification.rs delete mode 100644 packages/ic-response-verification/tests/cel-parsing.rs diff --git a/Cargo.lock b/Cargo.lock index ee674a92..3b70bed4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1492,7 +1492,10 @@ name = "ic-http-certification" version = "1.3.0" dependencies = [ "candid 0.9.8", + "hex", "http", + "ic-certification 1.3.0", + "ic-representation-independent-hash", "rstest", "serde", "thiserror", diff --git a/packages/ic-http-certification/Cargo.toml b/packages/ic-http-certification/Cargo.toml index 7b7f12c0..9afb508e 100644 --- a/packages/ic-http-certification/Cargo.toml +++ b/packages/ic-http-certification/Cargo.toml @@ -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 diff --git a/packages/ic-http-certification/src/cel/cel_types.rs b/packages/ic-http-certification/src/cel/cel_types.rs index 8d768a6a..86b0c38c 100644 --- a/packages/ic-http-certification/src/cel/cel_types.rs +++ b/packages/ic-http-certification/src/cel/cel_types.rs @@ -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. @@ -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. /// @@ -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. /// @@ -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. /// diff --git a/packages/ic-response-verification/src/hash/mod.rs b/packages/ic-http-certification/src/hash/mod.rs similarity index 84% rename from packages/ic-response-verification/src/hash/mod.rs rename to packages/ic-http-certification/src/hash/mod.rs index 023bbf84..50add5ef 100644 --- a/packages/ic-response-verification/src/hash/mod.rs +++ b/packages/ic-http-certification/src/hash/mod.rs @@ -7,3 +7,6 @@ pub use request_hash::*; mod response_hash; pub use response_hash::*; + +/// Sha256 Digest: 32 bytes +pub type Hash = [u8; 32]; diff --git a/packages/ic-response-verification/src/hash/request_hash.rs b/packages/ic-http-certification/src/hash/request_hash.rs similarity index 77% rename from packages/ic-response-verification/src/hash/request_hash.rs rename to packages/ic-http-certification/src/hash/request_hash.rs index b71e74cc..85a17976 100644 --- a/packages/ic-response-verification/src/hash/request_hash.rs +++ b/packages/ic-http-certification/src/hash/request_hash.rs @@ -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 { +pub fn request_hash<'a>( + request: &'a HttpRequest, + request_certification: &'a DefaultRequestCertification, +) -> HttpCertificationResult { let mut filtered_headers = get_filtered_headers(&request.headers, request_certification); filtered_headers.push(( @@ -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()) @@ -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| { @@ -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) }) @@ -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 = @@ -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"); @@ -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"); @@ -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"); diff --git a/packages/ic-response-verification/src/hash/response_hash.rs b/packages/ic-http-certification/src/hash/response_hash.rs similarity index 88% rename from packages/ic-response-verification/src/hash/response_hash.rs rename to packages/ic-http-certification/src/hash/response_hash.rs index a15faadd..4d691200 100644 --- a/packages/ic-response-verification/src/hash/response_hash.rs +++ b/packages/ic-http-certification/src/hash/response_hash.rs @@ -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"; @@ -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 _> = 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()) @@ -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 = [ @@ -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=:"; @@ -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); @@ -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); @@ -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 = @@ -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, @@ -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 = @@ -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, @@ -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 = @@ -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, @@ -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 = @@ -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, @@ -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() + } } diff --git a/packages/ic-http-certification/src/lib.rs b/packages/ic-http-certification/src/lib.rs index aa4da61a..0a356e95 100644 --- a/packages/ic-http-certification/src/lib.rs +++ b/packages/ic-http-certification/src/lib.rs @@ -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; diff --git a/packages/ic-response-verification-wasm/package.json b/packages/ic-response-verification-wasm/package.json index 99ee2199..954d4c70 100644 --- a/packages/ic-response-verification-wasm/package.json +++ b/packages/ic-response-verification-wasm/package.json @@ -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" } } diff --git a/packages/ic-response-verification-wasm/src/lib.rs b/packages/ic-response-verification-wasm/src/lib.rs index 57725a74..e12896c2 100644 --- a/packages/ic-response-verification-wasm/src/lib.rs +++ b/packages/ic-response-verification-wasm/src/lib.rs @@ -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); diff --git a/packages/ic-response-verification/src/cel/ast_mapping.rs b/packages/ic-response-verification/src/cel/ast_mapping.rs index 57e79cf4..c4a62843 100644 --- a/packages/ic-response-verification/src/cel/ast_mapping.rs +++ b/packages/ic-response-verification/src/cel/ast_mapping.rs @@ -1,12 +1,16 @@ use crate::cel::error::{CelParserError, CelParserResult}; use crate::cel::parser::CelValue; -use crate::types::{Certification, RequestCertification, ResponseCertification}; +use ic_http_certification::cel::{ + CelExpression, DefaultCertification, DefaultRequestCertification, +}; +use ic_http_certification::DefaultResponseCertification; +use std::borrow::Cow; use std::collections::HashMap; fn validate_object<'a>( - cel: &'a CelValue, + cel: &'a CelValue<'a>, name: &str, -) -> CelParserResult<&'a HashMap> { +) -> CelParserResult<&'a HashMap<&'a str, CelValue<'a>>> { let CelValue::Object(object_name, object_value) = cel else { return Err(CelParserError::UnexpectedNodeType { node_name: name.into(), @@ -15,18 +19,21 @@ fn validate_object<'a>( }); }; - if object_name != name { + if *object_name != name { return Err(CelParserError::UnexpectedNodeName { node_type: "Object".into(), expected_name: name.into(), - found_name: object_name.into(), + found_name: (*object_name).into(), }); } Ok(object_value) } -fn validate_function<'a>(cel: &'a CelValue, name: &str) -> CelParserResult<&'a Vec> { +fn validate_function<'a>( + cel: &'a CelValue<'a>, + name: &'a str, +) -> CelParserResult<&'a Vec>> { let CelValue::Function(function_name, function_value) = cel else { return Err(CelParserError::UnexpectedNodeType { node_name: name.into(), @@ -35,18 +42,21 @@ fn validate_function<'a>(cel: &'a CelValue, name: &str) -> CelParserResult<&'a V }); }; - if function_name != name { + if *function_name != name { return Err(CelParserError::UnexpectedNodeName { node_type: "Function".into(), expected_name: name.into(), - found_name: function_name.into(), + found_name: (*function_name).into(), }); } Ok(function_value) } -fn validate_string_array(cel: &CelValue, name: &str) -> CelParserResult> { +fn validate_string_array<'a>( + cel: &'a CelValue<'a>, + name: &'a str, +) -> CelParserResult> { let CelValue::Array(array) = cel else { return Err(CelParserError::UnexpectedNodeType { node_name: name.into(), @@ -66,16 +76,16 @@ fn validate_string_array(cel: &CelValue, name: &str) -> CelParserResult>()?; Ok(elements) } -fn validate_request_certification( - certification: &HashMap, -) -> CelParserResult> { +fn validate_request_certification<'a>( + certification: &'a HashMap<&'a str, CelValue<'a>>, +) -> CelParserResult>> { let no_request_certification = certification.get("no_request_certification"); let request_certification = certification.get("request_certification"); @@ -105,17 +115,17 @@ fn validate_request_certification( let certified_query_parameters = validate_string_array(certified_query_parameters, "certified_query_parameters")?; - Ok(Some(RequestCertification { - certified_request_headers, - certified_query_parameters, + Ok(Some(DefaultRequestCertification { + headers: Cow::Owned(certified_request_headers), + query_parameters: Cow::Owned(certified_query_parameters), })) } }; } -fn validate_response_certification( - certification: &HashMap, -) -> CelParserResult { +fn validate_response_certification<'a>( + certification: &'a HashMap<&'a str, CelValue<'a>>, +) -> CelParserResult> { let Some(response_certification) = certification.get("response_certification") else { return Err(CelParserError::MissingObjectProperty { object_name: "RequestCertification".into(), @@ -125,7 +135,7 @@ fn validate_response_certification( let response_certification = validate_object(response_certification, "ResponseCertification")?; let get_response_certification_headers = - |property_name| -> CelParserResult>> { + |property_name| -> CelParserResult>> { response_certification .get(property_name) .map(|certified_response_headers| { @@ -146,13 +156,17 @@ fn validate_response_certification( match (certified_response_headers, response_header_exclusions) { (Some(_), Some(_)) => Err(CelParserError::ExtraneousResponseCertificationProperty), (None, None) => Err(CelParserError::MissingResponseCertificationProperty), - (Some(headers), None) => Ok(ResponseCertification::CertifiedHeaders(headers)), - (None, Some(headers)) => Ok(ResponseCertification::HeaderExclusions(headers)), + (Some(headers), None) => Ok(DefaultResponseCertification::CertifiedResponseHeaders( + Cow::Owned(headers), + )), + (None, Some(headers)) => Ok(DefaultResponseCertification::ResponseHeaderExclusions( + Cow::Owned(headers), + )), } } -pub fn map_cel_ast(cel: CelValue) -> CelParserResult> { - let default_certification = validate_function(&cel, "default_certification")?; +pub(crate) fn map_cel_ast<'a>(cel: &'a CelValue<'a>) -> CelParserResult> { + let default_certification = validate_function(cel, "default_certification")?; let Some(validation_args) = default_certification.get(0) else { return Err(CelParserError::MissingFunctionParameter { @@ -171,7 +185,7 @@ pub fn map_cel_ast(cel: CelValue) -> CelParserResult> { match (no_certification, certification) { (Some(_), Some(_)) => Err(CelParserError::ExtraneousValidationArgsProperty), (None, None) => Err(CelParserError::MissingValidationArgsProperty), - (Some(_), None) => Ok(None), + (Some(_), None) => Ok(CelExpression::DefaultCertification(None)), (None, Some(certification)) => { let certification = validate_object(certification, "Certification")?; @@ -179,10 +193,12 @@ pub fn map_cel_ast(cel: CelValue) -> CelParserResult> { let response_certification = validate_response_certification(certification)?; - Ok(Some(Certification { - request_certification, - response_certification, - })) + Ok(CelExpression::DefaultCertification(Some( + DefaultCertification { + request_certification, + response_certification, + }, + ))) } } } diff --git a/packages/ic-response-verification/src/cel/error.rs b/packages/ic-response-verification/src/cel/error.rs index d31eafac..b6fe2179 100644 --- a/packages/ic-response-verification/src/cel/error.rs +++ b/packages/ic-response-verification/src/cel/error.rs @@ -1,4 +1,4 @@ -pub type CelParserResult = Result; +pub(crate) type CelParserResult = Result; /// CEL expression parsing error. #[derive(thiserror::Error, Debug)] diff --git a/packages/ic-response-verification/src/cel/mod.rs b/packages/ic-response-verification/src/cel/mod.rs index 17cb80ac..7001a3e4 100644 --- a/packages/ic-response-verification/src/cel/mod.rs +++ b/packages/ic-response-verification/src/cel/mod.rs @@ -6,12 +6,8 @@ pub use error::*; mod ast_mapping; mod parser; -use crate::cel::ast_mapping::map_cel_ast; -use crate::cel::error::CelParserResult; -use crate::cel::parser::parse_cel_expression; -use crate::types::Certification; +pub(crate) use ast_mapping::map_cel_ast; +pub(crate) use parser::parse_cel_expression; -/// Parses a CEL expression string into a [Certification] object. -pub fn cel_to_certification(cel: &str) -> CelParserResult> { - parse_cel_expression(cel).and_then(map_cel_ast) -} +#[cfg(test)] +mod tests; diff --git a/packages/ic-response-verification/src/cel/parser.rs b/packages/ic-response-verification/src/cel/parser.rs index 09cb2aab..5a3b54b1 100644 --- a/packages/ic-response-verification/src/cel/parser.rs +++ b/packages/ic-response-verification/src/cel/parser.rs @@ -12,14 +12,14 @@ use std::collections::HashMap; use std::fmt; #[derive(Debug, Eq, PartialEq)] -pub enum CelValue { - String(String), - Array(Vec), - Object(String, HashMap), - Function(String, Vec), +pub(crate) enum CelValue<'a> { + String(&'a str), + Array(Vec>), + Object(&'a str, HashMap<&'a str, CelValue<'a>>), + Function(&'a str, Vec>), } -impl fmt::Display for CelValue { +impl<'a> fmt::Display for CelValue<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Debug::fmt(self, f) } @@ -53,47 +53,39 @@ where fn ident<'a, E: ParseError<&'a str> + ContextError<&'a str>>( i: &'a str, -) -> IResult<&'a str, String, E> { +) -> IResult<&'a str, &'a str, E> { let acceptable_special_chars = "_"; context( "parse_ident", - map( - take_while(move |e| acceptable_special_chars.contains(e) || is_alphanumeric(e as u8)), - String::from, - ), + take_while(move |e| acceptable_special_chars.contains(e) || is_alphanumeric(e as u8)), )(i) } fn parse_str<'a, E: ParseError<&'a str> + ContextError<&'a str>>( i: &'a str, -) -> IResult<&'a str, String, E> { +) -> IResult<&'a str, &'a str, E> { let acceptable_special_chars = "-"; context( "parse_str", - map( - escaped( - take_while(move |e| { - acceptable_special_chars.contains(e) || is_alphanumeric(e as u8) - }), - '\\', - one_of("\"n\\"), - ), - String::from, + escaped( + take_while(move |e| acceptable_special_chars.contains(e) || is_alphanumeric(e as u8)), + '\\', + one_of("\"n\\"), ), )(i) } fn string<'a, E: ParseError<&'a str> + ContextError<&'a str>>( i: &'a str, -) -> IResult<&'a str, String, E> { +) -> IResult<&'a str, &'a str, E> { context("string", drop_separators('\"', '\"', parse_str))(i) } fn array<'a, E: ParseError<&'a str> + ContextError<&'a str>>( i: &'a str, -) -> IResult<&'a str, Vec, E> { +) -> IResult<&'a str, Vec>, E> { context( "array", drop_separators( @@ -106,7 +98,7 @@ fn array<'a, E: ParseError<&'a str> + ContextError<&'a str>>( fn key_value<'a, E: ParseError<&'a str> + ContextError<&'a str>>( i: &'a str, -) -> IResult<&'a str, (String, CelValue), E> { +) -> IResult<&'a str, (&'a str, CelValue<'a>), E> { context( "key_value", separated_pair( @@ -119,7 +111,7 @@ fn key_value<'a, E: ParseError<&'a str> + ContextError<&'a str>>( fn object<'a, E: ParseError<&'a str> + ContextError<&'a str>>( i: &'a str, -) -> IResult<&'a str, (String, HashMap), E> { +) -> IResult<&'a str, (&'a str, HashMap<&'a str, CelValue<'a>>), E> { context( "object", tuple(( @@ -138,7 +130,7 @@ fn object<'a, E: ParseError<&'a str> + ContextError<&'a str>>( fn function<'a, E: ParseError<&'a str> + ContextError<&'a str>>( i: &'a str, -) -> IResult<&'a str, (String, Vec), E> { +) -> IResult<&'a str, (&'a str, Vec>), E> { context( "function", tuple(( @@ -157,7 +149,7 @@ fn function<'a, E: ParseError<&'a str> + ContextError<&'a str>>( fn cel_value<'a, E: ParseError<&'a str> + ContextError<&'a str>>( i: &'a str, -) -> IResult<&'a str, CelValue, E> { +) -> IResult<&'a str, CelValue<'a>, E> { context( "cel_value", trim_whitespace(alt(( @@ -169,7 +161,7 @@ fn cel_value<'a, E: ParseError<&'a str> + ContextError<&'a str>>( )(i) } -pub fn parse_cel_expression(i: &str) -> CelParserResult { +pub(crate) fn parse_cel_expression(i: &str) -> CelParserResult { #[cfg(feature = "debug")] let result = cel_value::>(i); diff --git a/packages/ic-response-verification/src/cel/tests.rs b/packages/ic-response-verification/src/cel/tests.rs new file mode 100644 index 00000000..ec6bf9f3 --- /dev/null +++ b/packages/ic-response-verification/src/cel/tests.rs @@ -0,0 +1,112 @@ +use crate::cel::{map_cel_ast, parse_cel_expression}; +use ic_http_certification::{ + cel::{CelExpression, DefaultCertification, DefaultRequestCertification}, + DefaultResponseCertification, +}; +use ic_response_verification_test_utils::remove_whitespace; +use std::borrow::Cow; + +#[test] +fn parses_no_certification_expression() { + let cel_expression = r#" + default_certification ( + ValidationArgs { + no_certification: Empty { } + } + ) + "# + .to_string(); + let expected_result = CelExpression::DefaultCertification(None); + + let parsed_cel_expr = parse_cel_expression(&cel_expression).unwrap(); + let result = map_cel_ast(&parsed_cel_expr).unwrap(); + + let minified_cel_expression = remove_whitespace(&cel_expression); + let parsed_min_cel_expr = parse_cel_expression(&minified_cel_expression).unwrap(); + let minified_result = map_cel_ast(&parsed_min_cel_expr).unwrap(); + + assert_eq!(&result, &expected_result); + assert_eq!(&minified_result, &expected_result); +} + +#[test] +fn parses_no_request_certification_expression() { + let cel_expression = r#" + default_certification ( + ValidationArgs { + certification: Certification { + no_request_certification: Empty {}, + response_certification: ResponseCertification { + response_header_exclusions: ResponseHeaderList { + headers: ["Server","Date","X-Cache-Status"] + } + } + } + } + ) + "# + .to_string(); + let expected_result = CelExpression::DefaultCertification(Some(DefaultCertification { + request_certification: None, + response_certification: DefaultResponseCertification::response_header_exclusions(&[ + "Server", + "Date", + "X-Cache-Status", + ]), + })); + + let parsed_cel_expr = parse_cel_expression(&cel_expression).unwrap(); + let result = map_cel_ast(&parsed_cel_expr).unwrap(); + + let minified_cel_expression = remove_whitespace(&cel_expression); + let parsed_min_cel_expr = parse_cel_expression(&minified_cel_expression).unwrap(); + let minified_result = map_cel_ast(&parsed_min_cel_expr).unwrap(); + + assert_eq!(&result, &expected_result); + assert_eq!(&minified_result, &expected_result); +} + +#[test] +fn parses_full_certification_expression() { + let cel_expression = r#" + default_certification ( + ValidationArgs { + certification: Certification { + request_certification: RequestCertification { + certified_request_headers: ["host"], + certified_query_parameters: ["filter"] + }, + response_certification: ResponseCertification { + response_header_exclusions: ResponseHeaderList { + headers: ["Content-Type","X-Frame-Options","Content-Security-Policy","Strict-Transport-Security","Referrer-Policy","Permissions-Policy"] + } + } + } + } + ) + "#.to_string(); + let expected_result = CelExpression::DefaultCertification(Some(DefaultCertification { + request_certification: Some(DefaultRequestCertification { + headers: Cow::Borrowed(&["host"]), + query_parameters: Cow::Borrowed(&["filter"]), + }), + response_certification: DefaultResponseCertification::response_header_exclusions(&[ + "Content-Type", + "X-Frame-Options", + "Content-Security-Policy", + "Strict-Transport-Security", + "Referrer-Policy", + "Permissions-Policy", + ]), + })); + + let parsed_cel_expr = parse_cel_expression(&cel_expression).unwrap(); + let result = map_cel_ast(&parsed_cel_expr).unwrap(); + + let minified_cel_expression = remove_whitespace(&cel_expression); + let parsed_min_cel_expr = parse_cel_expression(&minified_cel_expression).unwrap(); + let minified_result = map_cel_ast(&parsed_min_cel_expr).unwrap(); + + assert_eq!(&result, &expected_result); + assert_eq!(&minified_result, &expected_result); +} diff --git a/packages/ic-response-verification/src/lib.rs b/packages/ic-response-verification/src/lib.rs index 485b5546..7bde83cd 100644 --- a/packages/ic-response-verification/src/lib.rs +++ b/packages/ic-response-verification/src/lib.rs @@ -13,7 +13,6 @@ mod error; pub use error::*; pub mod cel; -pub mod hash; pub mod types; mod base64; diff --git a/packages/ic-response-verification/src/types/certification.rs b/packages/ic-response-verification/src/types/certification.rs deleted file mode 100644 index 38959df6..00000000 --- a/packages/ic-response-verification/src/types/certification.rs +++ /dev/null @@ -1,28 +0,0 @@ -/// Parsed request certification CEL expression parameters. -#[derive(Debug, Eq, PartialEq)] -pub struct RequestCertification { - /// Request headers to include in certification. - pub certified_request_headers: Vec, - /// Request query parameters to include in certification. - pub certified_query_parameters: Vec, -} - -/// Parsed response certification CEL expression parameters. Can either include headers using -/// [ResponseCertification::CertifiedHeaders] or exclude them using -/// [ResponseCertification::HeaderExclusions]. -#[derive(Debug, Eq, PartialEq)] -pub enum ResponseCertification { - /// Response headers to exclude from certification. - HeaderExclusions(Vec), - /// Response headers to include in certification. - CertifiedHeaders(Vec), -} - -/// Parsed request/response pair certification CEL expression. -#[derive(Debug, Eq, PartialEq)] -pub struct Certification { - /// Optional rust representation of the request certification CEL expression parameters. - pub request_certification: Option, - /// Rust representation of the response certification CEL expression parameters. - pub response_certification: ResponseCertification, -} diff --git a/packages/ic-response-verification/src/types/mod.rs b/packages/ic-response-verification/src/types/mod.rs index a764b982..33610ea9 100644 --- a/packages/ic-response-verification/src/types/mod.rs +++ b/packages/ic-response-verification/src/types/mod.rs @@ -1,9 +1,5 @@ //! Public types used for response verification. -/// Types to represent parsed CEL expressions. -mod certification; -pub use certification::*; - /// Types to represent the result of verifying a request/response pair's certification. mod verification_result; pub use verification_result::*; diff --git a/packages/ic-response-verification/src/validation/v2_validation.rs b/packages/ic-response-verification/src/validation/v2_validation.rs index 7d248df8..9e1fa013 100644 --- a/packages/ic-response-verification/src/validation/v2_validation.rs +++ b/packages/ic-response-verification/src/validation/v2_validation.rs @@ -1,6 +1,6 @@ -use crate::types::Certification; use ic_certification::hash_tree::HashTreeNode; use ic_certification::{hash_tree::Hash, HashTree, Label, SubtreeLookupResult}; +use ic_http_certification::cel::DefaultCertification; fn path_from_parts(parts: &[T]) -> Vec