From 7483f5c26c3a746d36e9bd39216eb0280e0311cd Mon Sep 17 00:00:00 2001 From: ilbertt Date: Mon, 29 Dec 2025 11:06:57 +0100 Subject: [PATCH] feat: ic-http package --- Cargo.lock | 135 +----- Cargo.toml | 15 +- examples/todo-app/src/backend/Cargo.toml | 2 +- examples/todo-app/src/backend/src/lib.rs | 18 +- examples/todo-app/src/backend/src/root_key.rs | 16 - .../src/backend/src/todo/todo_routes.rs | 64 +-- packages/ic-http-auth/Cargo.toml | 38 -- packages/ic-http-auth/canbench.yml | 5 - packages/ic-http-auth/canbench_results.yml | 72 --- packages/ic-http-auth/src/base64.rs | 43 -- packages/ic-http-auth/src/bench/golden.rs | 31 -- packages/ic-http-auth/src/bench/mod.rs | 1 - packages/ic-http-auth/src/delegation.rs | 116 ----- packages/ic-http-auth/src/error.rs | 70 --- packages/ic-http-auth/src/http_signature.rs | 430 ------------------ packages/ic-http-auth/src/lib.rs | 12 - packages/ic-http-auth/src/parse_utils.rs | 110 ----- packages/ic-http-auth/src/root_key.rs | 17 - packages/ic-http/Cargo.toml | 23 + packages/{ic-http-auth => ic-http}/README.md | 6 +- packages/ic-http/src/http.rs | 61 +++ packages/ic-http/src/lib.rs | 3 + 22 files changed, 133 insertions(+), 1155 deletions(-) delete mode 100644 examples/todo-app/src/backend/src/root_key.rs delete mode 100644 packages/ic-http-auth/Cargo.toml delete mode 100644 packages/ic-http-auth/canbench.yml delete mode 100644 packages/ic-http-auth/canbench_results.yml delete mode 100644 packages/ic-http-auth/src/base64.rs delete mode 100644 packages/ic-http-auth/src/bench/golden.rs delete mode 100644 packages/ic-http-auth/src/bench/mod.rs delete mode 100644 packages/ic-http-auth/src/delegation.rs delete mode 100644 packages/ic-http-auth/src/error.rs delete mode 100644 packages/ic-http-auth/src/http_signature.rs delete mode 100644 packages/ic-http-auth/src/lib.rs delete mode 100644 packages/ic-http-auth/src/parse_utils.rs delete mode 100644 packages/ic-http-auth/src/root_key.rs create mode 100644 packages/ic-http/Cargo.toml rename packages/{ic-http-auth => ic-http}/README.md (63%) create mode 100644 packages/ic-http/src/http.rs create mode 100644 packages/ic-http/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index bda029c..3eb26b9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -608,29 +608,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ade8366b8bd5ba243f0a58f036cc0ca8a2f069cff1a2351ef1cac6b083e16fc0" -[[package]] -name = "canbench-rs" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2669351aae7fe4fe2208838b370484dd3310cc99648500edc384cca245a3fff" -dependencies = [ - "canbench-rs-macros", - "candid", - "ic-cdk", - "serde", -] - -[[package]] -name = "canbench-rs-macros" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "041885a6642bbc6096aba629814c0b15a96191e427986631d95105139702ca73" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "candid" version = "0.10.20" @@ -1266,7 +1243,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ "base16ct", - "base64ct", "crypto-bigint", "digest 0.10.7", "ff", @@ -1276,8 +1252,6 @@ dependencies = [ "pkcs8", "rand_core 0.6.4", "sec1", - "serde_json", - "serdect", "subtle", "zeroize", ] @@ -2037,10 +2011,10 @@ dependencies = [ "http", "http-body", "http-body-util", - "ic-certification 3.0.3", + "ic-certification", "ic-ed25519", "ic-transport-types 0.44.3", - "ic-verify-bls-signature 0.5.0", + "ic-verify-bls-signature", "k256", "leb128", "p256", @@ -2072,7 +2046,7 @@ checksum = "c43805022a5e4f408de44ca26396128697a2c39f83f4fdaf33f8aa3ac653d78e" dependencies = [ "globset", "http", - "ic-certification 3.0.3", + "ic-certification", "ic-http-certification", "thiserror 1.0.69", ] @@ -2200,25 +2174,6 @@ dependencies = [ "uuid", ] -[[package]] -name = "ic-canister-sig-creation" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b2a507d1bebb111fc8c76098468e4de9c745e9b9ef160a079568ae1d6f7fef" -dependencies = [ - "candid", - "hex", - "ic-certification 3.0.3", - "ic-representation-independent-hash", - "ic0", - "lazy_static", - "serde", - "serde_bytes", - "serde_cbor", - "sha2 0.10.9", - "thiserror 2.0.17", -] - [[package]] name = "ic-cbor" version = "3.0.3" @@ -2226,7 +2181,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0efada960a6c9fb023f45ed95801b757a033dafba071e4f386c6c112ca186d9" dependencies = [ "candid", - "ic-certification 3.0.3", + "ic-certification", "leb128", "nom 7.1.3", "thiserror 1.0.69", @@ -2282,7 +2237,7 @@ dependencies = [ "cached 0.54.0", "candid", "ic-cbor", - "ic-certification 3.0.3", + "ic-certification", "lazy_static", "leb128", "miracl_core_bls12381", @@ -2292,18 +2247,6 @@ dependencies = [ "thiserror 1.0.69", ] -[[package]] -name = "ic-certification" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e64ee3d8b6e81b51f245716d3e0badb63c283c00f3c9fb5d5219afc30b5bf821" -dependencies = [ - "hex", - "serde", - "serde_bytes", - "sha2 0.10.9", -] - [[package]] name = "ic-certification" version = "3.0.3" @@ -2503,23 +2446,11 @@ dependencies = [ ] [[package]] -name = "ic-http-auth" +name = "ic-http" version = "0.0.0" dependencies = [ - "base64 0.22.1", - "canbench-rs", - "candid", - "hex", - "ic-canister-sig-creation", - "ic-cdk", + "bhttp", "ic-http-certification", - "ic-signature-verification", - "nom 8.0.0", - "p256", - "serde", - "serde_json", - "sha2 0.10.9", - "thiserror 2.0.17", ] [[package]] @@ -2531,7 +2462,7 @@ dependencies = [ "base64 0.22.1", "candid", "http", - "ic-certification 3.0.3", + "ic-certification", "ic-representation-independent-hash", "serde", "serde_cbor", @@ -2617,7 +2548,7 @@ dependencies = [ "http", "ic-cbor", "ic-certificate-verification", - "ic-certification 3.0.3", + "ic-certification", "ic-http-certification", "ic-representation-independent-hash", "leb128", @@ -2628,22 +2559,6 @@ dependencies = [ "urlencoding", ] -[[package]] -name = "ic-signature-verification" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9de5065430a9b1a61934f7e4a65474a7a11658db84a8b5b0c42baced6c33752" -dependencies = [ - "ic-canister-sig-creation", - "ic-certification 2.6.0", - "ic-verify-bls-signature 0.6.0", - "ic_principal", - "serde", - "serde_bytes", - "serde_cbor", - "sha2 0.10.9", -] - [[package]] name = "ic-transport-types" version = "0.40.1" @@ -2652,7 +2567,7 @@ checksum = "a2e7706e55836e8104c98149ec0796d20d5213fef972ac01b544657d410f1883" dependencies = [ "candid", "hex", - "ic-certification 3.0.3", + "ic-certification", "leb128", "serde", "serde_bytes", @@ -2670,7 +2585,7 @@ checksum = "d5fec6355d0a542bfe484eb36343b6570047394124918cb8b435e22241c65427" dependencies = [ "candid", "hex", - "ic-certification 3.0.3", + "ic-certification", "leb128", "serde", "serde_bytes", @@ -2716,19 +2631,6 @@ dependencies = [ "sha2 0.10.9", ] -[[package]] -name = "ic-verify-bls-signature" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd6c4261586eb473fe1219de63186a98e554985d5fd6f3488036c8fb82452e27" -dependencies = [ - "hex", - "ic_bls12_381", - "lazy_static", - "pairing", - "sha2 0.10.9", -] - [[package]] name = "ic0" version = "1.0.1" @@ -3792,7 +3694,7 @@ dependencies = [ "candid", "flate2", "hex", - "ic-certification 3.0.3", + "ic-certification", "ic-management-canister-types 0.5.0", "ic-transport-types 0.40.1", "reqwest", @@ -4571,7 +4473,6 @@ dependencies = [ "der", "generic-array", "pkcs8", - "serdect", "subtle", "zeroize", ] @@ -4774,16 +4675,6 @@ dependencies = [ "unsafe-libyaml", ] -[[package]] -name = "serdect" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177" -dependencies = [ - "base16ct", - "serde", -] - [[package]] name = "sha1" version = "0.10.6" @@ -5344,7 +5235,7 @@ dependencies = [ "hex", "ic-asset-certification", "ic-cdk", - "ic-http-auth", + "ic-http", "ic-http-certification", "include_dir", "matchit", diff --git a/Cargo.toml b/Cargo.toml index aa7830f..bd5d1df 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [workspace] resolver = "2" -members = ["examples/todo-app/src/backend", "packages/ic-http-auth", "packages/local-replica"] +members = ["examples/todo-app/src/backend", "packages/ic-http", "packages/local-replica"] [workspace.package] version = "0.0.0" @@ -25,35 +25,24 @@ panic = "unwind" # Keep unwinding for better debugging (optional) [workspace.dependencies] -ic-http-auth = { path = "./packages/ic-http-auth", version = "0.0.0" } +ic-http = { path = "./packages/ic-http", version = "0.0.0" } candid = "0.10" ic-cdk = "0.18" ic-http-certification = "3" ic-asset-certification = "3" -ic-canister-sig-creation = "1" -ic-signature-verification = "0.2" -thiserror = "2" -base64 = "0.22" serde = { version = "1", features = ["derive"] } serde_json = "1" once_cell = "1" matchit = "=0.8.4" -sha2 = "0.10" -nom = "8" include_dir = { version = "0.7", features = ["glob"] } # debugging hex = "0.4" -[workspace.dependencies.p256] -version = "0.13" -default-features = false -features = ["ecdsa", "jwk", "pkcs8"] - [workspace.lints.rust] warnings = "deny" diff --git a/examples/todo-app/src/backend/Cargo.toml b/examples/todo-app/src/backend/Cargo.toml index 09cab64..0c80bb0 100644 --- a/examples/todo-app/src/backend/Cargo.toml +++ b/examples/todo-app/src/backend/Cargo.toml @@ -12,7 +12,7 @@ candid.workspace = true ic-cdk.workspace = true ic-http-certification.workspace = true ic-asset-certification.workspace = true -ic-http-auth.workspace = true +ic-http.workspace = true include_dir.workspace = true hex.workspace = true diff --git a/examples/todo-app/src/backend/src/lib.rs b/examples/todo-app/src/backend/src/lib.rs index aa3bce1..9fc716d 100644 --- a/examples/todo-app/src/backend/src/lib.rs +++ b/examples/todo-app/src/backend/src/lib.rs @@ -1,6 +1,5 @@ mod api; mod assets; -mod root_key; mod router; mod todo; @@ -10,24 +9,24 @@ use ic_cdk::*; use ic_http_certification::{HttpRequest, HttpResponse}; use matchit::Router; use once_cell::sync::OnceCell; -use root_key::init_root_key; use router::MethodRouter; use todo::*; #[init] fn init() { - init_root_key(); certify_all_assets(); } #[post_upgrade] fn post_upgrade() { - init_root_key(); certify_all_assets(); } -#[query] -fn http_request(req: HttpRequest) -> HttpResponse { +#[query( + decode_with = "ic_http::decode_args", + encode_with = "ic_http::encode_result" +)] +fn http_request_v2(req: HttpRequest) -> HttpResponse<'static> { let path = req.get_path().expect("Failed to parse request path"); if path.starts_with("/api") { @@ -38,8 +37,11 @@ fn http_request(req: HttpRequest) -> HttpResponse { serve_asset(&req) } -#[update] -fn http_request_update(req: HttpRequest) -> HttpResponse { +#[update( + decode_with = "ic_http::decode_args", + encode_with = "ic_http::encode_result" +)] +fn http_request_update_v2(req: HttpRequest) -> HttpResponse<'static> { let path = req.get_path().expect("Failed to parse request path"); if path.starts_with("/api") { diff --git a/examples/todo-app/src/backend/src/root_key.rs b/examples/todo-app/src/backend/src/root_key.rs deleted file mode 100644 index a55ca84..0000000 --- a/examples/todo-app/src/backend/src/root_key.rs +++ /dev/null @@ -1,16 +0,0 @@ -use std::sync::OnceLock; - -use ic_cdk::api; - -static ROOT_KEY: OnceLock> = OnceLock::new(); - -pub fn get_root_key() -> &'static [u8] { - // Unwrap is safe because the root key is initialized in init_root_key - // during canister initialization and upgrade - ROOT_KEY.get().unwrap() -} - -pub fn init_root_key() { - let root_key = api::root_key(); - ROOT_KEY.set(root_key).unwrap(); -} diff --git a/examples/todo-app/src/backend/src/todo/todo_routes.rs b/examples/todo-app/src/backend/src/todo/todo_routes.rs index fb2d1e0..213a5f9 100644 --- a/examples/todo-app/src/backend/src/todo/todo_routes.rs +++ b/examples/todo-app/src/backend/src/todo/todo_routes.rs @@ -3,10 +3,8 @@ use super::todo_types::{ ListTodosResponse, ListTodosResponseBody, TodoItem, UpdateTodoItemRequest, UpdateTodoItemResponse, }; -use crate::{api::json_decode, root_key::get_root_key}; -use candid::Encode; -use ic_cdk::println; -use ic_http_auth::validate_http_signature_headers; +use crate::api::json_decode; +use ic_cdk::api::msg_caller; use ic_http_certification::{HttpRequest, HttpResponse}; use matchit::Params; use once_cell::sync::OnceCell; @@ -28,16 +26,10 @@ fn todos() -> &'static Mutex { } pub fn get_todo_item_handler(req: &HttpRequest, params: &Params) -> HttpResponse<'static> { - let root_key = get_root_key(); + ic_cdk::println!("[get_todo_item_handler] Processing request: {:?}", req); + let user_principal = msg_caller(); - println!("[get_todo_item_handler] Processing request: {:?}", req); - println!( - "[get_todo_item_handler] Candid-serialized hex-encoded request: {}", - hex::encode(Encode!(&req).unwrap()) - ); - let jwt = validate_http_signature_headers(req, root_key).unwrap(); - - println!("[get_todo_item_handler] User principal: {}", jwt.principal); + ic_cdk::println!("[get_todo_item_handler] User principal: {}", user_principal); let Some(id_str) = params.get("id") else { ic_cdk::println!("[get_todo_item_handler] Missing ID parameter"); @@ -47,7 +39,7 @@ pub fn get_todo_item_handler(req: &HttpRequest, params: &Params) -> HttpResponse ic_cdk::println!("[get_todo_item_handler] Invalid ID format: {}", id_str); return HttpResponse::bad_request(b"Invalid ID format", vec![]).build(); }; - let user_id = jwt.principal.to_text(); + let user_id = user_principal.to_text(); let all_todos = todos().lock().unwrap(); @@ -63,15 +55,13 @@ pub fn get_todo_item_handler(req: &HttpRequest, params: &Params) -> HttpResponse HttpResponse::not_found(b"Todo item not found", vec![]).build() } -pub fn list_todo_items_handler(req: &HttpRequest, _params: &Params) -> HttpResponse<'static> { - let root_key = get_root_key(); - - let jwt = validate_http_signature_headers(req, root_key).unwrap(); +pub fn list_todo_items_handler(_req: &HttpRequest, _params: &Params) -> HttpResponse<'static> { + let user_principal = msg_caller(); let mut all_todos = todos().lock().unwrap(); let user_todos = all_todos - .entry(jwt.principal.to_text()) + .entry(user_principal.to_text()) .or_default() .values() .cloned() @@ -79,21 +69,15 @@ pub fn list_todo_items_handler(req: &HttpRequest, _params: &Params) -> HttpRespo let data = ListTodosResponseBody { todos: user_todos, - user_principal: jwt.principal, + user_principal, }; ListTodosResponse::ok(data) } pub fn create_todo_item_handler(req: &HttpRequest, _params: &Params) -> HttpResponse<'static> { - let root_key = get_root_key(); - - println!("[create_todo_item_handler] Processing request: {:?}", req); - println!( - "[create_todo_item_handler] Candid-serialized hex-encoded request: {}", - hex::encode(Encode!(&req).unwrap()) - ); - let jwt = validate_http_signature_headers(req, root_key).unwrap(); + ic_cdk::println!("[create_todo_item_handler] Processing request: {:?}", req); + let user_principal = msg_caller(); let req_body: CreateTodoItemRequest = json_decode(req.body()); @@ -110,7 +94,7 @@ pub fn create_todo_item_handler(req: &HttpRequest, _params: &Params) -> HttpResp }; let mut all_todos = todos().lock().unwrap(); all_todos - .entry(jwt.principal.to_text()) + .entry(user_principal.to_text()) .or_default() .insert(id, todo_item.clone()); @@ -118,8 +102,6 @@ pub fn create_todo_item_handler(req: &HttpRequest, _params: &Params) -> HttpResp } pub fn update_todo_item_handler(req: &HttpRequest, params: &Params) -> HttpResponse<'static> { - let root_key = get_root_key(); - ic_cdk::println!("[update_todo_item_handler] Starting handler"); ic_cdk::println!( "[update_todo_item_handler] Method: {}, Path: {}", @@ -128,16 +110,10 @@ pub fn update_todo_item_handler(req: &HttpRequest, params: &Params) -> HttpRespo ); ic_cdk::println!("[update_todo_item_handler] All Params: {:?}", params); - // Validate the JWT first - let jwt_result = validate_http_signature_headers(req, root_key); - if let Err(e) = jwt_result { - ic_cdk::println!("[update_todo_item_handler] JWT validation error: {:?}", e); - return HttpResponse::unauthorized(b"Unauthorized", vec![]).build(); - } - let jwt = jwt_result.unwrap(); + let user_principal = msg_caller(); ic_cdk::println!( "[update_todo_item_handler] User principal: {}", - jwt.principal.to_text() + user_principal.to_text() ); // Parse the request body @@ -172,12 +148,12 @@ pub fn update_todo_item_handler(req: &HttpRequest, params: &Params) -> HttpRespo ic_cdk::println!("[update_todo_item_handler] Todo ID: {}", id); let mut all_todos = todos().lock().unwrap(); - let user_todos = all_todos.get_mut(&jwt.principal.to_text()); + let user_todos = all_todos.get_mut(&user_principal.to_text()); if user_todos.is_none() { ic_cdk::println!( "[update_todo_item_handler] No todos found for user: {}", - jwt.principal.to_text() + user_principal.to_text() ); return HttpResponse::not_found(b"Todo item not found", vec![]).build(); } @@ -203,8 +179,6 @@ pub fn update_todo_item_handler(req: &HttpRequest, params: &Params) -> HttpRespo } pub fn delete_todo_item_handler(req: &HttpRequest, params: &Params) -> HttpResponse<'static> { - let root_key = get_root_key(); - ic_cdk::println!("[delete_todo_item_handler] Starting handler"); ic_cdk::println!( "[delete_todo_item_handler] Method: {}, Path: {}", @@ -213,13 +187,13 @@ pub fn delete_todo_item_handler(req: &HttpRequest, params: &Params) -> HttpRespo ); ic_cdk::println!("[delete_todo_item_handler] All Params: {:?}", params); - let jwt = validate_http_signature_headers(req, root_key).unwrap(); + let user_principal = msg_caller(); let id: u32 = params.get("id").unwrap().parse().unwrap(); let mut all_todos = todos().lock().unwrap(); all_todos - .get_mut(&jwt.principal.to_text()) + .get_mut(&user_principal.to_text()) .and_then(|todos| todos.remove(&id)); DeleteTodoItemResponse::ok(()) diff --git a/packages/ic-http-auth/Cargo.toml b/packages/ic-http-auth/Cargo.toml deleted file mode 100644 index ada084d..0000000 --- a/packages/ic-http-auth/Cargo.toml +++ /dev/null @@ -1,38 +0,0 @@ -[package] -name = "ic-http-auth" -description = "Canister side support for ICP HTTP authentication" -readme = "README.md" -documentation = "https://docs.rs/ic-http-auth" -categories = ["api-bindings", "algorithms", "cryptography::cryptocurrencies"] -keywords = ["internet-computer", "icp", "dfinity", "http", "authentication"] -include = ["src", "Cargo.toml", "README.md"] - -version.workspace = true -authors.workspace = true -edition.workspace = true -repository.workspace = true -license.workspace = true -homepage.workspace = true - -[dependencies] -ic-http-certification.workspace = true -ic-canister-sig-creation.workspace = true -ic-signature-verification.workspace = true -candid.workspace = true -thiserror.workspace = true -base64.workspace = true -serde.workspace = true -serde_json.workspace = true -nom.workspace = true -sha2.workspace = true -p256.workspace = true - -canbench-rs = { version = "0.3.0", optional = true } -ic-cdk = { workspace = true, optional = true } -hex = { workspace = true, optional = true } - -[features] -canbench-rs = ["dep:ic-cdk", "dep:canbench-rs", "dep:hex"] - -[lints] -workspace = true diff --git a/packages/ic-http-auth/canbench.yml b/packages/ic-http-auth/canbench.yml deleted file mode 100644 index 69f4de2..0000000 --- a/packages/ic-http-auth/canbench.yml +++ /dev/null @@ -1,5 +0,0 @@ -build_cmd: - cargo rustc --release --target wasm32-unknown-unknown -p ic-http-auth --features canbench-rs --crate-type=cdylib - -wasm_path: - ../../target/wasm32-unknown-unknown/release/ic_http_auth.wasm diff --git a/packages/ic-http-auth/canbench_results.yml b/packages/ic-http-auth/canbench_results.yml deleted file mode 100644 index 2a67211..0000000 --- a/packages/ic-http-auth/canbench_results.yml +++ /dev/null @@ -1,72 +0,0 @@ -benches: - parse_http_signature_headers_http_get: - total: - calls: 1 - instructions: 1544039 - heap_increase: 0 - stable_memory_increase: 0 - scopes: {} - parse_http_signature_headers_http_post: - total: - calls: 1 - instructions: 1543278 - heap_increase: 0 - stable_memory_increase: 0 - scopes: {} - validate_delegation_and_get_principal_http_get: - total: - calls: 1 - instructions: 1564822966 - heap_increase: 0 - stable_memory_increase: 0 - scopes: {} - validate_delegation_and_get_principal_http_post: - total: - calls: 1 - instructions: 1564822690 - heap_increase: 0 - stable_memory_increase: 0 - scopes: {} - validate_http_signature_headers_http_get_no_delegation: - total: - calls: 1 - instructions: 128215594 - heap_increase: 0 - stable_memory_increase: 0 - scopes: {} - validate_http_signature_headers_http_get_with_delegation: - total: - calls: 1 - instructions: 1692476033 - heap_increase: 0 - stable_memory_increase: 0 - scopes: {} - validate_http_signature_headers_http_post_no_delegation: - total: - calls: 1 - instructions: 128133781 - heap_increase: 0 - stable_memory_increase: 0 - scopes: {} - validate_http_signature_headers_http_post_with_delegation: - total: - calls: 1 - instructions: 1692393944 - heap_increase: 0 - stable_memory_increase: 0 - scopes: {} - verify_sig_http_get: - total: - calls: 1 - instructions: 126109140 - heap_increase: 0 - stable_memory_increase: 0 - scopes: {} - verify_sig_http_post: - total: - calls: 1 - instructions: 126028088 - heap_increase: 0 - stable_memory_increase: 0 - scopes: {} -version: 0.3.0 diff --git a/packages/ic-http-auth/src/base64.rs b/packages/ic-http-auth/src/base64.rs deleted file mode 100644 index 11d838b..0000000 --- a/packages/ic-http-auth/src/base64.rs +++ /dev/null @@ -1,43 +0,0 @@ -use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD}; -use serde::{Deserialize, Deserializer}; - -pub(crate) fn base64_decode(input: &str) -> Result, String> { - URL_SAFE_NO_PAD.decode(input).map_err(|e| e.to_string()) -} - -/// Deserializes a base64 encoded JSON field to a byte vector. -pub(crate) fn deserialize_base64_string_to_bytes<'de, D>( - deserializer: D, -) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - struct HexBytesVisitor; - - impl<'de> serde::de::Visitor<'de> for HexBytesVisitor { - type Value = Vec; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a hex string representing bytes") - } - - fn visit_str(self, v: &str) -> Result - where - E: serde::de::Error, - { - // Convert hex string to bytes - let bytes = base64_decode(v).map_err(|_| E::custom("Invalid base64 string"))?; - Ok(bytes) - } - - fn visit_seq(self, seq: A) -> Result - where - A: serde::de::SeqAccess<'de>, - { - // Also handle direct byte arrays - Deserialize::deserialize(serde::de::value::SeqAccessDeserializer::new(seq)) - } - } - - deserializer.deserialize_any(HexBytesVisitor) -} diff --git a/packages/ic-http-auth/src/bench/golden.rs b/packages/ic-http-auth/src/bench/golden.rs deleted file mode 100644 index ea9e5c1..0000000 --- a/packages/ic-http-auth/src/bench/golden.rs +++ /dev/null @@ -1,31 +0,0 @@ -use candid::{Decode, Principal}; -use ic_http_certification::HttpRequest; - -/// The root key of the local replica with which the requests below were made. -pub static ROOT_KEY: &[u8] = b"\x30\x81\x82\x30\x1d\x06\x0d\x2b\x06\x01\x04\x01\x82\xdc\x7c\x05\x03\x01\x02\x01\x06\x0c\x2b\x06\x01\x04\x01\x82\xdc\x7c\x05\x03\x02\x01\x03\x61\x00\x8b\x52\xb4\x99\x4f\x94\xc7\xce\x4b\xe1\xc1\x54\x2d\x7c\x81\xdc\x79\xfe\xa1\x7d\x49\xef\xe8\xfa\x42\xe8\x56\x63\x73\x58\x1d\x4b\x96\x9c\x4a\x59\xe9\x6a\x0e\xf5\x1b\x71\x1f\xe5\x02\x7e\xc0\x16\x01\x18\x25\x19\xd0\xa7\x88\xf4\xbf\xe3\x88\xe5\x93\xb9\x7c\xd1\xd7\xe4\x49\x04\xde\x79\x42\x24\x30\xbc\xa6\x86\xac\x8c\x21\x30\x5b\x33\x97\xb5\xba\x4d\x70\x37\xd1\x78\x77\x31\x2f\xb7\xee\x34"; - -/// The principal of the user that will be used to test the golden requests. -pub fn user_principal() -> Principal { - Principal::from_text("vs2q3-6i6vp-wysiu-fu76x-vreek-hqgsg-hve4p-iug2b-7aloe-kpngt-6ae").unwrap() -} - -/// Represents the following request, Candid-serialized and hex-encoded: -/// ``` -/// HttpRequest { method: MethodWrapper(GET), url: "/api/todos/0", headers: [("host", "uxrrr-q7777-77774-qaaaq-cai.localhost:8000"), ("connection", "keep-alive"), ("signature-input", "sig=(\"@method\" \"@path\" \"@query\" \"content-digest\");keyid=\"header:signature-key\";alg=\"ecdsa-p256-sha256\";created=1747056951;expires=1747057251;nonce=\"Yz8lgddrEk0X6xHh_267lDnVYMErREiV-AaeDFT9P8A\""), ("sec-ch-ua-platform", "\"macOS\""), ("signature", "sig=:4kiZ11mEuUshMal7wza2cumQiex0N1L8Wrl1iW4_ZoJSVHrmt9BfX7hkH9Zl4ZjLmQvydR2mdMoMsvrYkITAjA:"), ("signature-key", "sig=:eyJwdWJLZXkiOiJNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUVNU24yZlpIZ3JQTmxzSVpoMGhsU2xVX3NoWFExOWpMVjMzRjhUbHNBQ2xXSzhuMzgybW9RWUp0dWx5ME5vSU5TODZ6Y3F1LWhWSVZISi13UDNRTnFGZyIsImRlbGVnYXRpb25DaGFpbiI6eyJwdWJLZXkiOiJNRHd3REFZS0t3WUJCQUdEdUVNQkFnTXNBQW9BQUFBQUFBQUFCd0VCS0huZEEtdmJOVGdzakdBS0RXQ2g3ZUtRVWV4MWV6RXRUQ1c5WEYxa3hDbyIsImRlbGVnYXRpb25zIjpbeyJkZWxlZ2F0aW9uIjp7InB1YktleSI6Ik1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRU1TbjJmWkhnclBObHNJWmgwaGxTbFVfc2hYUTE5akxWMzNGOFRsc0FDbFdLOG4zODJtb1FZSnR1bHkwTm9JTlM4NnpjcXUtaFZJVkhKLXdQM1FOcUZnIiwiZXhwaXJhdGlvbiI6IjE3NDcwODU3Mzc2NjMzNTEwMDAifSwic2lnIjoiMmRuM29tdGpaWEowYVdacFkyRjBaVmtENHRuWjk2TmtkSEpsWllNQmd3R0RBWUlFV0NBbWZSQ0xnbEZKeUlraWZ5SU1vZ0VUak9ZaHd4eDhQUEYwRVg1d0ZSRUx0NE1DU0dOaGJtbHpkR1Z5Z3dKS0FBQUFBQUFBQUFjQkFZTUJnd0dEQWs1alpYSjBhV1pwWldSZlpHRjBZWUlEV0NEWGgyb0ZnLWdlSjVJak5mTTlyS0hxYUZ5QnlHR2c0aGRjN3lLZ2dTMFRNb0lFV0NEdTY5eXBacmxibDg3TjlpVGdCWFdLRWJYUU1iVjBnWVZsLXFxSl9Gb2xpNElFV0NCeG9ZS25Nblh5NEhLenhPMkYyLVc0SHNGekdiTV9aQlI0R3Y3bk1PRnA4b0lFV0NELXJYSVk5aFlmaDdaeUpGWUtvMkV4VzBUQzliNFUySFhkMXlQeVZuVUNUb01CZ2dSWUlOSkNZWExZeWZjMWh4V1lSZV8yUHA3UGYxMGZLVjU0OEdDQmVyWER2NVB4Z3dKRWRHbHRaWUlEU2REZWdwWDM0TEtmR0dsemFXZHVZWFIxY21WWU1KSE5pRnpPN0ZaVldzTWlaQUE1YVZuRERIRkxhMW00Y2tHVHZ2ZHd3VzJmaXFlUmNoUXpHNHhCcW9zLVFCWXctbXBrWld4bFoyRjBhVzl1b21semRXSnVaWFJmYVdSWUhVX0x3Q0I4Y2lBOWNJU2Qzdk1ZSm1lb0xOaEp1ZzhnNEtEVFRlRUNhMk5sY25ScFptbGpZWFJsV1FJNTJkbjNvbVIwY21WbGd3R0NCRmdnZUtYZks0WTNaMUtzSW1nZWdxNzlGUTZsQ2VRXzg5NEdsTFFEWUtobFdYMkRBWU1CZ2dSWUlPNm1aNEhEYmR2Z2YwYmhlT0Z0eTBiQ2xseHVrRUlOaS1obldZQ2VqVzlfZ3dKR2MzVmlibVYwZ3dHREFZTUJnd0pZSFVfTHdDQjhjaUE5Y0lTZDN2TVlKbWVvTE5oSnVnOGc0S0RUVGVFQ2d3R0RBazlqWVc1cGMzUmxjbDl5WVc1blpYT0NBMWhKMmRuM2c0SktBQUFBQUFBQUFBY0JBVW9BQUFBQUFBQUFCd0VCZ2tvQUFBQUFBaEFBQUFFQlNnQUFBQUFDSF9fX0FRR0NTdl9fX19fX3dBQUFBUUZLX19fX19fX1BfXzhCQVlNQ1NuQjFZbXhwWTE5clpYbUNBMWlGTUlHQ01CMEdEU3NHQVFRQmd0eDhCUU1CQWdFR0RDc0dBUVFCZ3R4OEJRTUNBUU5oQUtLS3Zha1Jud2JEUDFGbGlPLUpNbWs0cWVNLVhNTklTNXlJemdMVWdxMXlaaHdBVDJ1bDY5RGdXYk4tQ0hxUHh4Z2dNZDZvMm5Ja0xTbEsxR2U5S3lXRkJMTExIT01jYXpwb2lwazBrLW8xWWJUdGJ2Und5NFd2OUM3MW9pTmV4NElFV0NDX1g5dGdHWEhuSUVfZHpMWUNfeGJoRlBVbG1qdkx3cERKVEdLV0tPNWczb0lFV0NCVU9kbDZDWFJXR3FXQlJsNmdobkR2TFNoUWt0SklLX2x4N1hjQWR0Nm9rNElFV0NDdzJYa0pWSkk1dE9INmlzMDI4OExxOXNESWNJa3VyV1ZPeFBzZGtHelZsNE1DUkhScGJXV0NBMG5RM29LVjktQ3lueGhwYzJsbmJtRjBkWEpsV0RDc2tuckFkdk9KeTBqRUp2UkwxOVNTOG9wOWJIemVWdEFVSDBvNE5CMmtFaVE0Wk13VHpMOGtiS1l0R3ZuNlhpNWtkSEpsWllNQmdnUllJSHo5TGg3aElrUGlKZ0tVNDN1b1ZYX1I3d0R1WVpiSWVscUtNZTViYnNoVmd3SkRjMmxuZ3dKWUlEOXBnWDl2SGNPSXNoSzluT0RSSmZiSjBHRnMxQ05wMnZyTWRUNlFMSDRSZ3dKWUlCYTBkekJLZ3RjTy0xOGVIUi14QVRET3NBSzBQM0hObU9TYkFzcTBnaGwwZ2dOQSJ9XX19:"), ("sec-ch-ua", "\"Chromium\";v=\"136\", \"Brave\";v=\"136\", \"Not.A/Brand\";v=\"99\""), ("sec-ch-ua-mobile", "?0"), ("content-digest", "SHA-256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU"), ("user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36"), ("accept", "*/*"), ("sec-gpc", "1"), ("accept-language", "en-US,en;q=0.9"), ("sec-fetch-site", "same-origin"), ("sec-fetch-mode", "cors"), ("sec-fetch-dest", "empty"), ("accept-encoding", "gzip, deflate, br, zstd, identity")], body: [], certificate_version: None } -/// ``` -/// -/// The `get_todo_item_handler` handler in the `examples/todo-app/src/backend/src/todo/todo_routes.rs` has the logic to print such request in the proper format. -pub static HTTP_REQUEST_GET: &str = "4449444c056c05efd6e40271e1edeb4a71a2f5ed880401c6a4a1980602b0f1b99806046d7b6d036c02007101716e7a01000c2f6170692f746f646f732f3003474554001104686f73742a75787272722d71373737372d37373737342d71616161712d6361692e6c6f63616c686f73743a383030300a636f6e6e656374696f6e0a6b6565702d616c6976650f7369676e61747572652d696e707574c0017369673d2822406d6574686f642220224070617468222022407175657279222022636f6e74656e742d64696765737422293b6b657969643d226865616465723a7369676e61747572652d6b6579223b616c673d2265636473612d703235362d736861323536223b637265617465643d313734373035363935313b657870697265733d313734373035373235313b6e6f6e63653d22597a386c67646472456b3058367848685f3236376c446e56594d4572524569562d4161654446543950384122127365632d63682d75612d706c6174666f726d07226d61634f5322097369676e61747572655c7369673d3a346b695a31316d45755573684d616c37777a613263756d51696578304e314c3857726c316957345f5a6f4a535648726d743942665837686b48395a6c345a6a4c6d5176796452326d644d6f4d737672596b4954416a413a0d7369676e61747572652d6b6579d2147369673d3a65794a7764574a4c5a586b694f694a4e526d74335258645a53457476576b6c36616a42445156465a53557476576b6c36616a42455156466a5246466e5155564e553234795a6c70495a334a51546d787a5356706f4d4768735532785658334e6f574645784f57704d566a4d7a526a685562484e4251327858537a68754d7a67796257395257557030645778354d453576535535544f445a36593346314c57685753565a495369313355444e52546e46475a794973496d526c6247566e5958527062323544614746706269493665794a7764574a4c5a586b694f694a4e524864335245465a5330743357554a43515564456455564e516b466e54584e425157394251554642515546425155464364305643533068755a454574646d4a4f5647647a616b644253305258513267335a557452565756344d5756365258525551316335574559786133684462794973496d526c6247566e595852706232357a496a706265794a6b5a57786c5a32463061573975496a7037496e4231596b746c65534936496b31476133644664316c4953323961535870714d454e4255566c4a53323961535870714d45524255574e455557644252553154626a4a6d576b686e636c424f62484e4a576d677761477854624656666332685955544535616b78574d7a4e474f465273633046446246644c4f47347a4f444a746231465a536e523162486b77546d394a546c4d344e6e706a6358557461465a4a566b684b4c5864514d31464f63555a6e496977695a58687761584a6864476c7662694936496a45334e4463774f4455334d7a63324e6a4d7a4e5445774d4441696653776963326c6e496a6f694d6d52754d3239746447706157456f775956646163466b79526a4261566d74454e485275576a6b32546d746b53457073576c6c4e516d64335230524257556c4656304e4262575a535130786e62455a4b65556c7261575a35535531765a305655616b395a6148643465446851554559775256673164305a53525578304e4531445530644f61474a746248706b52315a355a33644b5330464251554642515546425155466a516b465a54554a6e6430644551577331616c7059536a426856317077576c64535a6c7048526a425a57556c4556304e455747677962305a6e4c57646c536a564a616b356d54546c795330687859555a35516e6c48523263306147526a4e336c4c5a3264544d46524e62306c4656304e456454593565584261636d786962446733546a6c70564764435746644c52574a5955553169566a426e57565a734c584678536c3947623278704e456c4656304e436547395a5332354e626c68354e45684c656e68504d6b59794c56633053484e47656b646954563961516c493052335933626b3150526e413462306c4656304e454c584a5953566b3561466c6d6144646165557047575574764d6b5634567a4255517a6c694e4655795346686b4d586c5165565a7556554e55623031435a32645357556c4f536b4e5a5745785a65575a6a4d57683456316c535a56387955484133554759784d475a4c566a55304f456444516d5679574552324e5642345a33644b525752486248526157556c45553252455a57647757444d305445746d52306473656d46585a48565a57464978593231575755314b53453570526e70504e305a61566c647a54576c615155453159565a7552455249526b78684d5730305932744856485a325a486433567a4a6d6158466c556d4e6f555870484e4868436357397a4c5646435758637462584272576c643462466f79526a4268567a6c3162323173656d5258536e566157464a6d5956645357556856583078335130493459326c424f574e4a5532517a646b315a536d316c6230784f614570315a7a686e4e4574455646526c52554e684d6b357359323553634670746247705a57464a735631464a4e544a6b626a4e7662564977593231576247643352304e43526d646e5a5574595a6b733057544e614d55747a5357316e5a5764784e7a6c4755545a7351325652587a67354e456473544646455755746f624664594d6b5242575531435a32645357556c504e6d31614e456845596d52325a325977596d686c54305a3065544269513278736548567252556c4f6153316f626c645a51325671567a6c665a33644b52324d7a566d6c69625659775a3364485245465a54554a6e6430705a5346566654486444516a686a6155453559306c545a444e3254566c4b625756765445356f536e566e4f47633053305255564756465132643352305242617a6c715756633163474d7a556d786a62446c3557566331626c705954304e424d57684b4d6d52754d326330536b744251554642515546425155464259304a4256573942515546425155464251554643643056435a32747651554642515546426145464251554646516c4e6e5155464251554644534639665830465252304e54646c396658313966583364425155464255555a4c583139665831396658314266587a684351566c4e51314e75516a465a6258687757544535636c705962554e424d576c4754556c48513031434d45644555334e4851564652516d64306544684355553143515764465230524463306442555646435a3352344f454a5254554e425555356f5155744c53335a6861314a7564324a455544464762476c504c55704e625773306357564e4c56684e546b6c544e586c4a656d644d565764784d586c616148644256444a316244593552476458596b347451306878554868345a32644e5a445a764d6d354a61307854624573785232553553336c58526b4a4d544578495430316a5958707762326c77617a42724c57387857574a5564474a32556e64354e4664324f554d334d573970546d56344e456c4656304e44583167356447644857456875535556665a48704d57554e6665474a6f526c425662473171646b78336345524b5645644c563074504e57637a62306c4656304e435655396b62445a4457464a5852334658516c4a734e6d646f626b523254464e6f55577430536b6c4c583278344e31686a515752304e6d39724e456c4656304e44647a4a5961307057536b6b31644539494e6d6c7a4d4449344f4578784f584e4553574e4a6133567956315a506546427a5a477448656c5a734e453144556b685363474a5856304e424d4735524d32394c566a6b7451336c7565476877597a4a73626d4a74526a426b574570735630524463327475636b466b646b394b6554427152557032556b77784f564e544f4739774f574a49656d565764454656534442764e4535434d6d744661564530576b31335648704d4f47746953316c3052335a754e6c68704e57746b53457073576c6c4e516d646e556c6c4a53486f355447673361456c7255476c4b5a3074564e444e3162315a59583149336430523157567069535756736355744e5a545669596e4e6f566d6433536b526a4d6d78755a33644b57556c454f58426e57446c3253474e5053584e6f537a6c7554305253536d5a69536a4248526e4d78513035774d6e5a79545752554e6c464d534452535a33644b57556c435954426b656b4a4c5a33526a547930784f475649556931345156524554334e42537a42514d30684f62553954596b467a6354426e614777775a32644f51534a39585831393a097365632d63682d756139224368726f6d69756d223b763d22313336222c20224272617665223b763d22313336222c20224e6f742e412f4272616e64223b763d22393922107365632d63682d75612d6d6f62696c65023f300e636f6e74656e742d646967657374335348412d3235363d3437444551706a38484253612d5f54496d572d354a4365755165526b6d354e4d704a575a473368537546550a757365722d6167656e74754d6f7a696c6c612f352e3020284d6163696e746f73683b20496e74656c204d6163204f5320582031305f31355f3729204170706c655765624b69742f3533372e333620284b48544d4c2c206c696b65204765636b6f29204368726f6d652f3133362e302e302e30205361666172692f3533372e333606616363657074032a2f2a077365632d67706301310f6163636570742d6c616e67756167650e656e2d55532c656e3b713d302e390e7365632d66657463682d736974650b73616d652d6f726967696e0e7365632d66657463682d6d6f646504636f72730e7365632d66657463682d6465737405656d7074790f6163636570742d656e636f64696e6721677a69702c206465666c6174652c2062722c207a7374642c206964656e7469747900"; - -/// Represents the following request, Candid-serialized and hex-encoded: -/// ``` -/// HttpRequest { method: MethodWrapper(POST), url: "/api/todos", headers: [("host", "uxrrr-q7777-77774-qaaaq-cai.localhost:8000"), ("connection", "keep-alive"), ("content-length", "26"), ("signature-input", "sig=(\"@method\" \"@path\" \"@query\" \"content-digest\");keyid=\"header:signature-key\";alg=\"ecdsa-p256-sha256\";created=1747057655;expires=1747057955;nonce=\"CVXBHwEVZFnGcvzK0U6peB0BVMpwJMtym5IeUh8du4w\""), ("sec-ch-ua-platform", "\"macOS\""), ("signature", "sig=:PJL5aJPyRKncqeEpv4mTAEcTnPhMCZQjxKRgE86AHZKa6uTXeFGDtz2z0aQdTGW6HXt-SMcIPiHabboFui0yug:"), ("signature-key", "sig=:eyJwdWJLZXkiOiJNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUVNU24yZlpIZ3JQTmxzSVpoMGhsU2xVX3NoWFExOWpMVjMzRjhUbHNBQ2xXSzhuMzgybW9RWUp0dWx5ME5vSU5TODZ6Y3F1LWhWSVZISi13UDNRTnFGZyIsImRlbGVnYXRpb25DaGFpbiI6eyJwdWJLZXkiOiJNRHd3REFZS0t3WUJCQUdEdUVNQkFnTXNBQW9BQUFBQUFBQUFCd0VCS0huZEEtdmJOVGdzakdBS0RXQ2g3ZUtRVWV4MWV6RXRUQ1c5WEYxa3hDbyIsImRlbGVnYXRpb25zIjpbeyJkZWxlZ2F0aW9uIjp7InB1YktleSI6Ik1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRU1TbjJmWkhnclBObHNJWmgwaGxTbFVfc2hYUTE5akxWMzNGOFRsc0FDbFdLOG4zODJtb1FZSnR1bHkwTm9JTlM4NnpjcXUtaFZJVkhKLXdQM1FOcUZnIiwiZXhwaXJhdGlvbiI6IjE3NDcwODU3Mzc2NjMzNTEwMDAifSwic2lnIjoiMmRuM29tdGpaWEowYVdacFkyRjBaVmtENHRuWjk2TmtkSEpsWllNQmd3R0RBWUlFV0NBbWZSQ0xnbEZKeUlraWZ5SU1vZ0VUak9ZaHd4eDhQUEYwRVg1d0ZSRUx0NE1DU0dOaGJtbHpkR1Z5Z3dKS0FBQUFBQUFBQUFjQkFZTUJnd0dEQWs1alpYSjBhV1pwWldSZlpHRjBZWUlEV0NEWGgyb0ZnLWdlSjVJak5mTTlyS0hxYUZ5QnlHR2c0aGRjN3lLZ2dTMFRNb0lFV0NEdTY5eXBacmxibDg3TjlpVGdCWFdLRWJYUU1iVjBnWVZsLXFxSl9Gb2xpNElFV0NCeG9ZS25Nblh5NEhLenhPMkYyLVc0SHNGekdiTV9aQlI0R3Y3bk1PRnA4b0lFV0NELXJYSVk5aFlmaDdaeUpGWUtvMkV4VzBUQzliNFUySFhkMXlQeVZuVUNUb01CZ2dSWUlOSkNZWExZeWZjMWh4V1lSZV8yUHA3UGYxMGZLVjU0OEdDQmVyWER2NVB4Z3dKRWRHbHRaWUlEU2REZWdwWDM0TEtmR0dsemFXZHVZWFIxY21WWU1KSE5pRnpPN0ZaVldzTWlaQUE1YVZuRERIRkxhMW00Y2tHVHZ2ZHd3VzJmaXFlUmNoUXpHNHhCcW9zLVFCWXctbXBrWld4bFoyRjBhVzl1b21semRXSnVaWFJmYVdSWUhVX0x3Q0I4Y2lBOWNJU2Qzdk1ZSm1lb0xOaEp1ZzhnNEtEVFRlRUNhMk5sY25ScFptbGpZWFJsV1FJNTJkbjNvbVIwY21WbGd3R0NCRmdnZUtYZks0WTNaMUtzSW1nZWdxNzlGUTZsQ2VRXzg5NEdsTFFEWUtobFdYMkRBWU1CZ2dSWUlPNm1aNEhEYmR2Z2YwYmhlT0Z0eTBiQ2xseHVrRUlOaS1obldZQ2VqVzlfZ3dKR2MzVmlibVYwZ3dHREFZTUJnd0pZSFVfTHdDQjhjaUE5Y0lTZDN2TVlKbWVvTE5oSnVnOGc0S0RUVGVFQ2d3R0RBazlqWVc1cGMzUmxjbDl5WVc1blpYT0NBMWhKMmRuM2c0SktBQUFBQUFBQUFBY0JBVW9BQUFBQUFBQUFCd0VCZ2tvQUFBQUFBaEFBQUFFQlNnQUFBQUFDSF9fX0FRR0NTdl9fX19fX3dBQUFBUUZLX19fX19fX1BfXzhCQVlNQ1NuQjFZbXhwWTE5clpYbUNBMWlGTUlHQ01CMEdEU3NHQVFRQmd0eDhCUU1CQWdFR0RDc0dBUVFCZ3R4OEJRTUNBUU5oQUtLS3Zha1Jud2JEUDFGbGlPLUpNbWs0cWVNLVhNTklTNXlJemdMVWdxMXlaaHdBVDJ1bDY5RGdXYk4tQ0hxUHh4Z2dNZDZvMm5Ja0xTbEsxR2U5S3lXRkJMTExIT01jYXpwb2lwazBrLW8xWWJUdGJ2Und5NFd2OUM3MW9pTmV4NElFV0NDX1g5dGdHWEhuSUVfZHpMWUNfeGJoRlBVbG1qdkx3cERKVEdLV0tPNWczb0lFV0NCVU9kbDZDWFJXR3FXQlJsNmdobkR2TFNoUWt0SklLX2x4N1hjQWR0Nm9rNElFV0NDdzJYa0pWSkk1dE9INmlzMDI4OExxOXNESWNJa3VyV1ZPeFBzZGtHelZsNE1DUkhScGJXV0NBMG5RM29LVjktQ3lueGhwYzJsbmJtRjBkWEpsV0RDc2tuckFkdk9KeTBqRUp2UkwxOVNTOG9wOWJIemVWdEFVSDBvNE5CMmtFaVE0Wk13VHpMOGtiS1l0R3ZuNlhpNWtkSEpsWllNQmdnUllJSHo5TGg3aElrUGlKZ0tVNDN1b1ZYX1I3d0R1WVpiSWVscUtNZTViYnNoVmd3SkRjMmxuZ3dKWUlEOXBnWDl2SGNPSXNoSzluT0RSSmZiSjBHRnMxQ05wMnZyTWRUNlFMSDRSZ3dKWUlCYTBkekJLZ3RjTy0xOGVIUi14QVRET3NBSzBQM0hObU9TYkFzcTBnaGwwZ2dOQSJ9XX19:"), ("sec-ch-ua", "\"Chromium\";v=\"136\", \"Brave\";v=\"136\", \"Not.A/Brand\";v=\"99\""), ("sec-ch-ua-mobile", "?0"), ("content-digest", "SHA-256=OXGcJ3f8sTR-eX_CPjZ6NibZ5UAPK5UJo73gLn8bgEw"), ("user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36"), ("content-type", "application/json"), ("accept", "*/*"), ("sec-gpc", "1"), ("accept-language", "en-US,en;q=0.9"), ("origin", "http://uxrrr-q7777-77774-qaaaq-cai.localhost:8000"), ("sec-fetch-site", "same-origin"), ("sec-fetch-mode", "cors"), ("sec-fetch-dest", "empty"), ("accept-encoding", "gzip, deflate, br, zstd, identity")], body: [123, 34, 116, 105, 116, 108, 101, 34, 58, 34, 116, 104, 105, 115, 32, 105, 115, 32, 97, 32, 116, 101, 115, 116, 34, 125], certificate_version: None } -/// ``` -/// -/// The `create_todo_item_handler` handler in the `examples/todo-app/src/backend/src/todo/todo_routes.rs` has the logic to print such request in the proper format. -pub static HTTP_REQUEST_POST: &str = "4449444c056c05efd6e40271e1edeb4a71a2f5ed880401c6a4a1980602b0f1b99806046d7b6d036c02007101716e7a01000a2f6170692f746f646f7304504f53541a7b227469746c65223a227468697320697320612074657374227d1404686f73742a75787272722d71373737372d37373737342d71616161712d6361692e6c6f63616c686f73743a383030300a636f6e6e656374696f6e0a6b6565702d616c6976650e636f6e74656e742d6c656e6774680232360f7369676e61747572652d696e707574c0017369673d2822406d6574686f642220224070617468222022407175657279222022636f6e74656e742d64696765737422293b6b657969643d226865616465723a7369676e61747572652d6b6579223b616c673d2265636473612d703235362d736861323536223b637265617465643d313734373035373635353b657870697265733d313734373035373935353b6e6f6e63653d2243565842487745565a466e4763767a4b3055367065423042564d70774a4d74796d3549655568386475347722127365632d63682d75612d706c6174666f726d07226d61634f5322097369676e61747572655c7369673d3a504a4c35614a5079524b6e637165457076346d54414563546e50684d435a516a784b526745383641485a4b613675545865464744747a327a30615164544757364858742d534d63495069486162626f467569307975673a0d7369676e61747572652d6b6579d2147369673d3a65794a7764574a4c5a586b694f694a4e526d74335258645a53457476576b6c36616a42445156465a53557476576b6c36616a42455156466a5246466e5155564e553234795a6c70495a334a51546d787a5356706f4d4768735532785658334e6f574645784f57704d566a4d7a526a685562484e4251327858537a68754d7a67796257395257557030645778354d453576535535544f445a36593346314c57685753565a495369313355444e52546e46475a794973496d526c6247566e5958527062323544614746706269493665794a7764574a4c5a586b694f694a4e524864335245465a5330743357554a43515564456455564e516b466e54584e425157394251554642515546425155464364305643533068755a454574646d4a4f5647647a616b644253305258513267335a557452565756344d5756365258525551316335574559786133684462794973496d526c6247566e595852706232357a496a706265794a6b5a57786c5a32463061573975496a7037496e4231596b746c65534936496b31476133644664316c4953323961535870714d454e4255566c4a53323961535870714d45524255574e455557644252553154626a4a6d576b686e636c424f62484e4a576d677761477854624656666332685955544535616b78574d7a4e474f465273633046446246644c4f47347a4f444a746231465a536e523162486b77546d394a546c4d344e6e706a6358557461465a4a566b684b4c5864514d31464f63555a6e496977695a58687761584a6864476c7662694936496a45334e4463774f4455334d7a63324e6a4d7a4e5445774d4441696653776963326c6e496a6f694d6d52754d3239746447706157456f775956646163466b79526a4261566d74454e485275576a6b32546d746b53457073576c6c4e516d64335230524257556c4656304e4262575a535130786e62455a4b65556c7261575a35535531765a305655616b395a6148643465446851554559775256673164305a53525578304e4531445530644f61474a746248706b52315a355a33644b5330464251554642515546425155466a516b465a54554a6e6430644551577331616c7059536a426856317077576c64535a6c7048526a425a57556c4556304e455747677962305a6e4c57646c536a564a616b356d54546c795330687859555a35516e6c48523263306147526a4e336c4c5a3264544d46524e62306c4656304e456454593565584261636d786962446733546a6c70564764435746644c52574a5955553169566a426e57565a734c584678536c3947623278704e456c4656304e436547395a5332354e626c68354e45684c656e68504d6b59794c56633053484e47656b646954563961516c493052335933626b3150526e413462306c4656304e454c584a5953566b3561466c6d6144646165557047575574764d6b5634567a4255517a6c694e4655795346686b4d586c5165565a7556554e55623031435a32645357556c4f536b4e5a5745785a65575a6a4d57683456316c535a56387955484133554759784d475a4c566a55304f456444516d5679574552324e5642345a33644b525752486248526157556c45553252455a57647757444d305445746d52306473656d46585a48565a57464978593231575755314b53453570526e70504e305a61566c647a54576c615155453159565a7552455249526b78684d5730305932744856485a325a486433567a4a6d6158466c556d4e6f555870484e4868436357397a4c5646435758637462584272576c643462466f79526a4268567a6c3162323173656d5258536e566157464a6d5956645357556856583078335130493459326c424f574e4a5532517a646b315a536d316c6230784f614570315a7a686e4e4574455646526c52554e684d6b357359323553634670746247705a57464a735631464a4e544a6b626a4e7662564977593231576247643352304e43526d646e5a5574595a6b733057544e614d55747a5357316e5a5764784e7a6c4755545a7351325652587a67354e456473544646455755746f624664594d6b5242575531435a32645357556c504e6d31614e456845596d52325a325977596d686c54305a3065544269513278736548567252556c4f6153316f626c645a51325671567a6c665a33644b52324d7a566d6c69625659775a3364485245465a54554a6e6430705a5346566654486444516a686a6155453559306c545a444e3254566c4b625756765445356f536e566e4f47633053305255564756465132643352305242617a6c715756633163474d7a556d786a62446c3557566331626c705954304e424d57684b4d6d52754d326330536b744251554642515546425155464259304a4256573942515546425155464251554643643056435a32747651554642515546426145464251554646516c4e6e5155464251554644534639665830465252304e54646c396658313966583364425155464255555a4c583139665831396658314266587a684351566c4e51314e75516a465a6258687757544535636c705962554e424d576c4754556c48513031434d45644555334e4851564652516d64306544684355553143515764465230524463306442555646435a3352344f454a5254554e425555356f5155744c53335a6861314a7564324a455544464762476c504c55704e625773306357564e4c56684e546b6c544e586c4a656d644d565764784d586c616148644256444a316244593552476458596b347451306878554868345a32644e5a445a764d6d354a61307854624573785232553553336c58526b4a4d544578495430316a5958707762326c77617a42724c57387857574a5564474a32556e64354e4664324f554d334d573970546d56344e456c4656304e44583167356447644857456875535556665a48704d57554e6665474a6f526c425662473171646b78336345524b5645644c563074504e57637a62306c4656304e435655396b62445a4457464a5852334658516c4a734e6d646f626b523254464e6f55577430536b6c4c583278344e31686a515752304e6d39724e456c4656304e44647a4a5961307057536b6b31644539494e6d6c7a4d4449344f4578784f584e4553574e4a6133567956315a506546427a5a477448656c5a734e453144556b685363474a5856304e424d4735524d32394c566a6b7451336c7565476877597a4a73626d4a74526a426b574570735630524463327475636b466b646b394b6554427152557032556b77784f564e544f4739774f574a49656d565764454656534442764e4535434d6d744661564530576b31335648704d4f47746953316c3052335a754e6c68704e57746b53457073576c6c4e516d646e556c6c4a53486f355447673361456c7255476c4b5a3074564e444e3162315a59583149336430523157567069535756736355744e5a545669596e4e6f566d6433536b526a4d6d78755a33644b57556c454f58426e57446c3253474e5053584e6f537a6c7554305253536d5a69536a4248526e4d78513035774d6e5a79545752554e6c464d534452535a33644b57556c435954426b656b4a4c5a33526a547930784f475649556931345156524554334e42537a42514d30684f62553954596b467a6354426e614777775a32644f51534a39585831393a097365632d63682d756139224368726f6d69756d223b763d22313336222c20224272617665223b763d22313336222c20224e6f742e412f4272616e64223b763d22393922107365632d63682d75612d6d6f62696c65023f300e636f6e74656e742d646967657374335348412d3235363d4f5847634a3366387354522d65585f43506a5a364e69625a355541504b35554a6f3733674c6e38626745770a757365722d6167656e74754d6f7a696c6c612f352e3020284d6163696e746f73683b20496e74656c204d6163204f5320582031305f31355f3729204170706c655765624b69742f3533372e333620284b48544d4c2c206c696b65204765636b6f29204368726f6d652f3133362e302e302e30205361666172692f3533372e33360c636f6e74656e742d74797065106170706c69636174696f6e2f6a736f6e06616363657074032a2f2a077365632d67706301310f6163636570742d6c616e67756167650e656e2d55532c656e3b713d302e39066f726967696e31687474703a2f2f75787272722d71373737372d37373737342d71616161712d6361692e6c6f63616c686f73743a383030300e7365632d66657463682d736974650b73616d652d6f726967696e0e7365632d66657463682d6d6f646504636f72730e7365632d66657463682d6465737405656d7074790f6163636570742d656e636f64696e6721677a69702c206465666c6174652c2062722c207a7374642c206964656e7469747900"; - -pub fn parse_request(req_raw: &'_ str) -> HttpRequest<'_> { - let request_bytes = hex::decode(req_raw).unwrap(); - Decode!(&request_bytes, HttpRequest).unwrap() -} diff --git a/packages/ic-http-auth/src/bench/mod.rs b/packages/ic-http-auth/src/bench/mod.rs deleted file mode 100644 index cf6b5da..0000000 --- a/packages/ic-http-auth/src/bench/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod golden; diff --git a/packages/ic-http-auth/src/delegation.rs b/packages/ic-http-auth/src/delegation.rs deleted file mode 100644 index 8047ba9..0000000 --- a/packages/ic-http-auth/src/delegation.rs +++ /dev/null @@ -1,116 +0,0 @@ -use candid::Principal; -use ic_canister_sig_creation::{ - CanisterSigPublicKey, DELEGATION_SIG_DOMAIN, delegation_signature_msg, -}; -use serde::{Deserialize, Serialize}; - -use crate::{base64::base64_decode, root_key::extract_raw_root_pk_from_der}; - -/// Verifies the validity of the given signed delegation chain wrt. the challenge, and the other parameters. -/// Specifically: -/// * `signed_delegation_chain` contains exactly one delegation, denoted below as `delegations[0]` -/// * `delegations[0].pubkey` equals `challenge` (i.e. challenge is the "session key") -/// * `signed_delegation_chain.publicKey` is a public key for canister signatures of `ii_canister_id` -/// * `current_time_ns` denotes point in time before `delegations[0].expiration` -/// * TODO: `current_time_ns` denotes point in time that is not more than 5min after signature creation time -/// (as specified in the certified tree of the Certificate embedded in the signature) -/// * `delegations[0].signature` is a valid canister signature on a representation-independent hash of `delegations[0]`, -/// wrt. `signed_delegation_chain.publicKey` and `ic_root_public_key_raw` -/// -/// On success returns textual representation of the self-authenticating Principal determined by -/// public key `signed_delegation_chain.publicKey` (which identifies the user). -pub(crate) fn validate_delegation_and_get_principal( - delegation_chain: &DelegationChain, - // current_time_ns: u64, - ii_canister_id: &str, // textural representation of the principal - ic_root_public_key_raw: &[u8], -) -> Result { - // Signed delegation chain contains exactly one delegation. - - if delegation_chain.delegations.len() != 1 { - return Err("Expected exactly one signed delegation".to_string()); - } - - // `delegation[0].pubkey` equals `challenge` - let signed_delegation = &delegation_chain.delegations[0]; - let delegation_sig = base64_decode(&signed_delegation.sig).unwrap(); - let delegation_pub_key = base64_decode(&signed_delegation.delegation.pub_key).unwrap(); - - let pub_key = base64_decode(&delegation_chain.pub_key).unwrap(); - // `signed_delegation_chain.publicKey` is a public key for canister signatures of `ii_canister_id` - let cs_pk = CanisterSigPublicKey::try_from(pub_key.as_slice()) - .map_err(|e| format!("Invalid publicKey in delegation chain: {}", e))?; - let expected_ii_canister_id = Principal::from_text(ii_canister_id) - .map_err(|e| format!("Invalid ii_canister_id: {}", e))?; - if cs_pk.canister_id != expected_ii_canister_id { - return Err(format!( - "Delegation's signing canister {} does not match II canister id {}", - cs_pk.canister_id, expected_ii_canister_id - )); - } - - // `current_time_ns` denotes point in time before `delegations[0].expiration` - // if signed_delegation.delegation.expiration() < current_time_ns { - // return Err(format!("delegation expired at {}", signed_delegation.delegation.expiration())); - // }; - - // `current_time_ns` denotes point in time that is not more than 5min after signature creation time - // (as specified in the certified tree of the Certificate embedded in the signature) - // TODO - - // `delegations[0].signature` is a valid canister signature on a representation-independent hash of `delegations[0]`, - // wrt. `signed_delegation_chain.publicKey` and `ic_root_public_key_raw`. - - let message = msg_with_domain( - DELEGATION_SIG_DOMAIN, - &delegation_signature_msg( - delegation_pub_key.as_slice(), - signed_delegation.delegation.expiration(), - signed_delegation.delegation.targets.as_ref(), - ), - ); - let ic_root_public_key = extract_raw_root_pk_from_der(ic_root_public_key_raw)?; - ic_signature_verification::verify_canister_sig( - message.as_slice(), - delegation_sig.as_slice(), - &cs_pk.to_der(), - ic_root_public_key, - ) - .map_err(|e| format!("Invalid canister signature: {}", e))?; - - Ok(Principal::self_authenticating(pub_key)) -} - -fn msg_with_domain(sep: &[u8], bytes: &[u8]) -> Vec { - let mut msg = vec![sep.len() as u8]; - msg.append(&mut sep.to_vec()); - msg.append(&mut bytes.to_vec()); - msg -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct DelegationChain { - #[serde(rename = "pubKey")] - pub pub_key: String, - pub delegations: Vec, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct SignedDelegation { - delegation: Delegation, - sig: String, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct Delegation { - #[serde(rename = "pubKey")] - pub pub_key: String, - pub expiration: String, - pub targets: Option>>, -} - -impl Delegation { - fn expiration(&self) -> u64 { - self.expiration.parse::().unwrap() - } -} diff --git a/packages/ic-http-auth/src/error.rs b/packages/ic-http-auth/src/error.rs deleted file mode 100644 index eea8b7a..0000000 --- a/packages/ic-http-auth/src/error.rs +++ /dev/null @@ -1,70 +0,0 @@ -use ic_http_certification::HttpCertificationError; -use thiserror::Error; - -pub type HttpAuthResult = Result; - -#[derive(Error, Debug, Clone)] -pub enum HttpAuthError { - #[error(r#"The "Authorization" header is missing from the provided HTTP request."#)] - MissingAuthorizationHeader, - - #[error(r#""#)] - MissingSignatureHeader, - - #[error(r#""#)] - MissingSignatureInputHeader, - - #[error(r#""#)] - MissingSignatureKeyHeader, - - #[error(r#"HTTP message signature mismatch, expected "{expected}", but got "{actual}"."#)] - HttpSignatureMismatch { expected: String, actual: String }, - - #[error(r#"JWT signature verification failed: {0}."#)] - JwtSignatureVerificationFailed(String), - - #[error(r#"Failed to parse the "http_sig" claim of the provided JWT: {0}."#)] - MalformedHttpSig(String), - - #[error(r#"Failed to parse "http_sig_input" claim of the provided JWT: {0}."#)] - MalformedHttpSigInput(String), - - #[error(r#"Failed to parse the "signature-key" header: {0}."#)] - MalformedHttpSigKey(String), - - #[error(r#"The "{0}" header field was listed in the HTTP signature input, but was not found in the request."#)] - MissingHeaderField(String), - - #[error(r#"The provided JWT is missing the required header component."#)] - MissingJwtHeaderComponent, - - #[error(r#"The provided JWT's header component is not base64 encoded correctly."#)] - MalformedJwtHeaderBase64Encoding, - - #[error(r#"The provided JWT's header component is not JSON encoded correctly: {0}."#)] - MalformedJwtHeaderJsonEncoding(String), - - #[error(r#"The provided JWT is missing the required claims component."#)] - MissingJwtClaimsComponent, - - #[error(r#"The provided JWT's claim component is not base64 encoded correctly."#)] - MalformedJwtClaimsBase64Encoding, - - #[error(r#"The provided JWT's claim component is not JSON encoded correctly: {0}."#)] - MalformedJwtClaimsJsonEncoding(String), - - #[error(r#"The provided JWT is missing the required signature component."#)] - MissingJwtSignatureComponent, - - #[error(r#"The provided JWT's signature component is not base64 encoded correctly."#)] - MalformedJwtSignatureBase64Encoding, - - #[error(r#"The provided JWT's JWK is not a valid ECDSA public key."#)] - MalformedEcdsaPublicKey, - - #[error(r#"The provided JWT's signature is not a valid ECDSA signature."#)] - MalformedEcdsaSignature, - - #[error(transparent)] - HttpCertificationError(#[from] HttpCertificationError), -} diff --git a/packages/ic-http-auth/src/http_signature.rs b/packages/ic-http-auth/src/http_signature.rs deleted file mode 100644 index 1af19d5..0000000 --- a/packages/ic-http-auth/src/http_signature.rs +++ /dev/null @@ -1,430 +0,0 @@ -use crate::{ - HttpAuthError, HttpAuthResult, - base64::{base64_decode, deserialize_base64_string_to_bytes}, - delegation::{DelegationChain, validate_delegation_and_get_principal}, - parse_utils::{parse_http_sig, parse_http_sig_input, parse_http_sig_key}, -}; -use candid::Principal; -use ic_http_certification::{HeaderField, HttpRequest}; -use p256::{ - PublicKey, - ecdsa::{Signature, VerifyingKey, signature::Verifier}, - pkcs8::{DecodePublicKey, EncodePublicKey}, -}; -use serde::{Deserialize, Serialize}; - -const SIGNATURE_HEADER_NAME: &str = "signature"; -const SIGNATURE_KEY_HEADER_NAME: &str = "signature-key"; -const SIGNATURE_INPUT_HEADER_NAME: &str = "signature-input"; - -pub struct HttpSignatureValidationData { - pub principal: Principal, -} - -/// The `Signature-Key` header value. -#[derive(Debug, Serialize, Deserialize)] -pub struct SignatureKeyHeader { - /// The DER-encoded public key. - #[serde( - rename = "pubKey", - deserialize_with = "deserialize_base64_string_to_bytes" - )] - pub pub_key: Vec, - #[serde(rename = "delegationChain")] - pub delegation_chain: Option, -} - -impl TryFrom<&[HeaderField]> for SignatureKeyHeader { - type Error = HttpAuthError; - - fn try_from(headers: &[HeaderField]) -> HttpAuthResult { - let sig_key_header_str = find_header(headers, SIGNATURE_KEY_HEADER_NAME) - .ok_or(HttpAuthError::MissingSignatureKeyHeader)?; - - let (_, http_sig_key) = parse_http_sig_key(sig_key_header_str)?; - - let sig_key_bytes = base64_decode(http_sig_key) - .map_err(|err| HttpAuthError::MalformedHttpSigKey(format!("{:?}", err)))?; - let sig_key_header = serde_json::from_slice::(&sig_key_bytes) - .map_err(|err| HttpAuthError::MalformedHttpSigKey(format!("{:?}", err)))?; - - Ok(sig_key_header) - } -} - -impl SignatureKeyHeader { - fn signature_pub_key_der(&self) -> HttpAuthResult> { - let public_key = PublicKey::from_public_key_der(&self.pub_key) - .map_err(|_| HttpAuthError::MalformedEcdsaPublicKey) - .unwrap(); - let public_key_der = public_key - .to_public_key_der() - .map_err(|_| HttpAuthError::MalformedEcdsaPublicKey) - .unwrap() - .to_vec(); - - Ok(public_key_der) - } -} - -pub fn validate_http_signature_headers( - req: &HttpRequest, - ic_root_key_raw: &[u8], -) -> HttpAuthResult { - let validation_input = HttpSignatureValidationInput::try_from(req)?; - - verify_sig( - &validation_input.payload, - &validation_input.signature, - validation_input.signature_pub_key(), - )?; - - if let Some(delegation_chain) = validation_input.delegation_chain() { - let principal = validate_delegation_and_get_principal( - delegation_chain, - "rdmx6-jaaaa-aaaaa-aaadq-cai", - ic_root_key_raw, - ) - .unwrap(); - - return Ok(HttpSignatureValidationData { principal }); - } - - let public_key_der = validation_input.signature_pub_key_der()?; - - Ok(HttpSignatureValidationData { - principal: Principal::self_authenticating(public_key_der), - }) -} - -struct HttpSignatureValidationInput { - /// The [SignatureKeyHeader] parsed from the `Signature-Key` header. - signature_key_header: SignatureKeyHeader, - /// The signature parsed from the `Signature` header. - signature: Vec, - /// The payload parsed from the `Signature-Input` header. - payload: Vec, -} - -impl TryFrom<&HttpRequest<'_>> for HttpSignatureValidationInput { - type Error = HttpAuthError; - - fn try_from(req: &HttpRequest) -> HttpAuthResult { - let headers = req.headers(); - - let signature = get_http_sig_bytes(headers)?; - let signature_key_header = SignatureKeyHeader::try_from(headers)?; - let payload = get_http_sig_input_payload(req, headers)?; - - Ok(Self { - signature_key_header, - signature, - payload, - }) - } -} - -impl HttpSignatureValidationInput { - /// Returns the signature's public key parsed from the `Signature-Key` header value, as bytes. - fn signature_pub_key(&self) -> &[u8] { - self.signature_key_header.pub_key.as_slice() - } - - /// Returns the signature's public key parsed from the `Signature-Key` header value, in DER format. - fn signature_pub_key_der(&self) -> HttpAuthResult> { - self.signature_key_header.signature_pub_key_der() - } - - /// Returns the delegation chain parsed from the `Signature-Key` header value, if it exists. - fn delegation_chain(&self) -> Option<&DelegationChain> { - self.signature_key_header.delegation_chain.as_ref() - } -} - -fn calculate_http_sig( - req: &HttpRequest, - req_headers: &[HeaderField], - http_sig_input: &str, - http_sig_input_elems: Vec<&str>, -) -> HttpAuthResult> { - let mut calculated_http_sig = String::from(""); - - for elem in http_sig_input_elems { - let value = match elem { - "@method" => req.method().to_string().to_uppercase(), - "@path" => req.get_path()?, - "@query" => req - .get_query()? - .map(|query| { - let mut q = String::from("?"); - q.push_str(&query); - q - }) - .unwrap_or_default(), - _ => find_header(req_headers, elem) - .ok_or_else(|| HttpAuthError::MissingHeaderField(elem.to_string()))? - .to_string(), - }; - - calculated_http_sig.push('"'); - calculated_http_sig.push_str(elem); - calculated_http_sig.push_str("\": "); - calculated_http_sig.push_str(&value); - calculated_http_sig.push('\n'); - } - - calculated_http_sig.push_str("\"@signature-params\": "); - calculated_http_sig.push_str(http_sig_input); - calculated_http_sig.push('\n'); - - Ok(calculated_http_sig.as_bytes().to_vec()) -} - -fn verify_sig(payload: &[u8], sig: &[u8], public_key: &[u8]) -> HttpAuthResult { - let sig = Signature::from_slice(sig).map_err(|_| HttpAuthError::MalformedEcdsaSignature)?; - - let public_key = PublicKey::from_public_key_der(public_key) - .map_err(|_| HttpAuthError::MalformedEcdsaPublicKey) - .unwrap(); - let verifying_key = VerifyingKey::from(public_key); - - verifying_key - .verify(payload, &sig) - .map_err(|e| HttpAuthError::JwtSignatureVerificationFailed(e.to_string())) -} - -fn get_http_sig_bytes(req_headers: &[HeaderField]) -> HttpAuthResult> { - let sig_header_str = find_header(req_headers, SIGNATURE_HEADER_NAME) - .ok_or(HttpAuthError::MissingSignatureHeader)?; - - let (_, http_sig) = parse_http_sig(sig_header_str)?; - let http_sig_bytes = base64_decode(http_sig) - .map_err(|err| HttpAuthError::MalformedHttpSig(format!("{:?}", err)))?; - - Ok(http_sig_bytes) -} - -fn get_http_sig_input_payload( - req: &HttpRequest, - req_headers: &[HeaderField], -) -> HttpAuthResult> { - let sig_input_header = find_header(req_headers, SIGNATURE_INPUT_HEADER_NAME) - .ok_or(HttpAuthError::MissingSignatureInputHeader)?; - - let (_, http_sig_input, http_sig_input_elems) = parse_http_sig_input(sig_input_header)?; - let payload = calculate_http_sig(req, req_headers, http_sig_input, http_sig_input_elems)?; - - Ok(payload) -} - -fn find_header<'a>(headers: &'a [HeaderField], key: &'_ str) -> Option<&'a str> { - headers - .iter() - .find(|(k, _)| *k == key) - .map(|(_, value)| value.as_str()) -} - -#[cfg(feature = "canbench-rs")] -mod benches { - use std::hint::black_box; - - use super::*; - use crate::bench::golden::{ - HTTP_REQUEST_GET, HTTP_REQUEST_POST, ROOT_KEY, parse_request, user_principal, - }; - - use canbench_rs::bench; - use ic_http_certification::{HttpRequest, Method}; - - fn assert_valid_request(request: &HttpRequest, root_key: &[u8], expected_method: Method) { - assert_eq!(request.method(), expected_method); - let validation_res = validate_http_signature_headers(request, root_key).unwrap(); - assert_eq!(validation_res.principal, user_principal()); - } - - /// Same as [validate_http_signature_headers], but it skips the delegation chain validation. - /// - /// TODO: remove this once we have a way to create HTTP requests manually for each test. - fn validate_http_signature_headers_no_delegation( - req: &HttpRequest, - _root_key: &[u8], - ) -> HttpAuthResult { - let validation_input = HttpSignatureValidationInput::try_from(req)?; - - verify_sig( - &validation_input.payload, - &validation_input.signature, - validation_input.signature_pub_key(), - )?; - - // artificially skip the delegation chain validation - - let public_key_der = validation_input.signature_pub_key_der().unwrap(); - - Ok(HttpSignatureValidationData { - principal: Principal::self_authenticating(public_key_der), - }) - } - - #[bench(raw)] - fn validate_http_signature_headers_http_get_with_delegation() -> canbench_rs::BenchResult { - let request = parse_request(HTTP_REQUEST_GET); - - let bench_result = canbench_rs::bench_fn(|| { - black_box(validate_http_signature_headers( - black_box(&request), - black_box(ROOT_KEY), - )) - .unwrap(); - }); - - assert_valid_request(&request, ROOT_KEY, Method::GET); - - bench_result - } - - #[bench(raw)] - fn validate_http_signature_headers_http_post_with_delegation() -> canbench_rs::BenchResult { - let request = parse_request(HTTP_REQUEST_POST); - - let bench_result = canbench_rs::bench_fn(|| { - black_box(validate_http_signature_headers( - black_box(&request), - black_box(ROOT_KEY), - )) - .unwrap(); - }); - - assert_valid_request(&request, ROOT_KEY, Method::POST); - - bench_result - } - - #[bench(raw)] - fn validate_http_signature_headers_http_get_no_delegation() -> canbench_rs::BenchResult { - let request = parse_request(HTTP_REQUEST_GET); - - let bench_result = canbench_rs::bench_fn(|| { - black_box(validate_http_signature_headers_no_delegation( - black_box(&request), - black_box(ROOT_KEY), - )) - .unwrap(); - }); - - assert_valid_request(&request, ROOT_KEY, Method::GET); - - bench_result - } - - #[bench(raw)] - fn validate_http_signature_headers_http_post_no_delegation() -> canbench_rs::BenchResult { - let request = parse_request(HTTP_REQUEST_POST); - - let bench_result = canbench_rs::bench_fn(|| { - black_box(validate_http_signature_headers_no_delegation( - black_box(&request), - black_box(ROOT_KEY), - )) - .unwrap(); - }); - - assert_valid_request(&request, ROOT_KEY, Method::POST); - - bench_result - } - - #[bench(raw)] - fn verify_sig_http_get() -> canbench_rs::BenchResult { - let request = parse_request(HTTP_REQUEST_GET); - - let validation_input = HttpSignatureValidationInput::try_from(&request).unwrap(); - let signature_pub_key = validation_input.signature_pub_key(); - - canbench_rs::bench_fn(|| { - black_box(verify_sig( - black_box(&validation_input.payload), - black_box(&validation_input.signature), - black_box(signature_pub_key), - )) - .unwrap(); - }) - } - - #[bench(raw)] - fn verify_sig_http_post() -> canbench_rs::BenchResult { - let request = parse_request(HTTP_REQUEST_POST); - - let validation_input = HttpSignatureValidationInput::try_from(&request).unwrap(); - let signature_pub_key = validation_input.signature_pub_key(); - - canbench_rs::bench_fn(|| { - black_box(verify_sig( - black_box(&validation_input.payload), - black_box(&validation_input.signature), - black_box(signature_pub_key), - )) - .unwrap(); - }) - } - - #[bench(raw)] - fn validate_delegation_and_get_principal_http_get() -> canbench_rs::BenchResult { - let request = parse_request(HTTP_REQUEST_GET); - - let validation_input = HttpSignatureValidationInput::try_from(&request).unwrap(); - let delegation_chain = validation_input.delegation_chain().unwrap(); - - let bench_result = canbench_rs::bench_fn(|| { - black_box(validate_delegation_and_get_principal( - black_box(delegation_chain), - black_box("rdmx6-jaaaa-aaaaa-aaadq-cai"), - black_box(ROOT_KEY), - )) - .unwrap(); - }); - - assert_valid_request(&request, ROOT_KEY, Method::GET); - - bench_result - } - - #[bench(raw)] - fn validate_delegation_and_get_principal_http_post() -> canbench_rs::BenchResult { - let request = parse_request(HTTP_REQUEST_POST); - - let validation_input = HttpSignatureValidationInput::try_from(&request).unwrap(); - let delegation_chain = validation_input.delegation_chain().unwrap(); - - let bench_result = canbench_rs::bench_fn(|| { - black_box(validate_delegation_and_get_principal( - black_box(delegation_chain), - black_box("rdmx6-jaaaa-aaaaa-aaadq-cai"), - black_box(ROOT_KEY), - )) - .unwrap(); - }); - - assert_valid_request(&request, ROOT_KEY, Method::POST); - - bench_result - } - - #[bench(raw)] - fn parse_http_signature_headers_http_get() -> canbench_rs::BenchResult { - let request = parse_request(HTTP_REQUEST_GET); - - canbench_rs::bench_fn(|| { - black_box(HttpSignatureValidationInput::try_from(black_box(&request))).unwrap(); - }) - } - - #[bench(raw)] - fn parse_http_signature_headers_http_post() -> canbench_rs::BenchResult { - let request = parse_request(HTTP_REQUEST_POST); - - canbench_rs::bench_fn(|| { - black_box(HttpSignatureValidationInput::try_from(black_box(&request))).unwrap(); - }) - } -} diff --git a/packages/ic-http-auth/src/lib.rs b/packages/ic-http-auth/src/lib.rs deleted file mode 100644 index fa1853e..0000000 --- a/packages/ic-http-auth/src/lib.rs +++ /dev/null @@ -1,12 +0,0 @@ -mod base64; -mod delegation; -mod error; -mod http_signature; -mod parse_utils; -mod root_key; - -#[cfg(feature = "canbench-rs")] -pub(crate) mod bench; - -pub use error::*; -pub use http_signature::*; diff --git a/packages/ic-http-auth/src/parse_utils.rs b/packages/ic-http-auth/src/parse_utils.rs deleted file mode 100644 index 9c14199..0000000 --- a/packages/ic-http-auth/src/parse_utils.rs +++ /dev/null @@ -1,110 +0,0 @@ -use nom::{ - IResult, Parser, - bytes::complete::{tag, take_until, take_while}, - character::complete::char, - combinator::{cut, eof}, - error::{ContextError, ParseError, context}, - multi::many0, - sequence::{preceded, terminated}, -}; - -use crate::{HttpAuthError, HttpAuthResult}; - -pub(crate) fn parse_http_sig(header_field: &str) -> HttpAuthResult<(&str, &str)> { - fn extract(i: &str) -> IResult<&str, (&str, &str)> { - let (i, sig_name) = until_terminated("=").parse(i)?; - let (i, sig) = drop_separators(':', ':', take_until(":")).parse(i)?; - - eof(i)?; - - Ok((i, (sig_name, sig))) - } - - extract(header_field) - .map(|(_, e)| e) - .map_err(|e| HttpAuthError::MalformedHttpSig(e.to_string())) -} - -pub(crate) fn parse_http_sig_input( - http_sig_input: &str, -) -> HttpAuthResult<(&str, &str, Vec<&str>)> { - fn extract(i: &str) -> IResult<&str, (&str, &str, Vec<&str>)> { - let (sig_params, sig_name) = until_terminated("=").parse(i)?; - let (i, parsed_sig_params) = - drop_separators('(', ')', many0(drop_separators('"', '"', take_until("\"")))) - .parse(sig_params)?; - - // [TODO] - continue parsing the signature inputs: keyid, alg, created, expires, nonce, etc. - // eof(i)?; - - Ok((i, (sig_name, sig_params, parsed_sig_params))) - } - - extract(http_sig_input) - .map(|(_, e)| e) - .map_err(|e| HttpAuthError::MalformedHttpSigInput(e.to_string())) -} - -pub(crate) fn parse_http_sig_key(http_sig_key: &str) -> HttpAuthResult<(&str, &str)> { - fn extract(i: &str) -> IResult<&str, (&str, &str)> { - let (i, sig_name) = until_terminated("=").parse(i)?; - let (i, sig) = drop_separators(':', ':', take_until(":")).parse(i)?; - - eof(i)?; - - Ok((i, (sig_name, sig))) - } - - extract(http_sig_key) - .map(|(_, e)| e) - .map_err(|e| HttpAuthError::MalformedHttpSigKey(e.to_string())) -} - -fn whitespace<'a, E>(i: &'a str) -> IResult<&'a str, &'a str, E> -where - E: ParseError<&'a str> + ContextError<&'a str>, -{ - let chars = " \t\r\n"; - - context("whitespace", take_while(move |c| chars.contains(c))).parse(i) -} - -fn trim_whitespace<'a, O, E>( - parser: impl Parser<&'a str, Output = O, Error = E>, -) -> impl Parser<&'a str, Output = O, Error = E> -where - E: ParseError<&'a str> + ContextError<&'a str>, -{ - context("trim_whitespace", preceded(whitespace, parser)) -} - -fn trimmed_char<'a, E>(v: char) -> impl Parser<&'a str, Output = char, Error = E> -where - E: ParseError<&'a str> + ContextError<&'a str>, -{ - context("trimmed_char", trim_whitespace(char(v))) -} - -fn drop_separators<'a, O, E>( - opening_separator: char, - closing_separator: char, - parser: impl Parser<&'a str, Output = O, Error = E>, -) -> impl Parser<&'a str, Output = O, Error = E> -where - E: ParseError<&'a str> + ContextError<&'a str>, -{ - context( - "drop_separators", - preceded( - trimmed_char(opening_separator), - cut(terminated(parser, trimmed_char(closing_separator))), - ), - ) -} - -fn until_terminated<'a, E>(v: &'a str) -> impl Parser<&'a str, Output = &'a str, Error = E> -where - E: ParseError<&'a str> + ContextError<&'a str>, -{ - terminated(take_until(v), tag(v)) -} diff --git a/packages/ic-http-auth/src/root_key.rs b/packages/ic-http-auth/src/root_key.rs deleted file mode 100644 index 114c1a4..0000000 --- a/packages/ic-http-auth/src/root_key.rs +++ /dev/null @@ -1,17 +0,0 @@ -const IC_ROOT_PK_DER_PREFIX: &[u8; 37] = b"\x30\x81\x82\x30\x1d\x06\x0d\x2b\x06\x01\x04\x01\x82\xdc\x7c\x05\x03\x01\x02\x01\x06\x0c\x2b\x06\x01\x04\x01\x82\xdc\x7c\x05\x03\x02\x01\x03\x61\x00"; -const IC_ROOT_PK_LENGTH: usize = 96; - -pub(crate) fn extract_raw_root_pk_from_der(pk_der: &[u8]) -> Result<&[u8], String> { - let expected_length = IC_ROOT_PK_DER_PREFIX.len() + IC_ROOT_PK_LENGTH; - if pk_der.len() != expected_length { - return Err(String::from("invalid root pk length")); - } - - let prefix = &pk_der[0..IC_ROOT_PK_DER_PREFIX.len()]; - if prefix[..] != IC_ROOT_PK_DER_PREFIX[..] { - return Err(String::from("invalid OID")); - } - - let key = &pk_der[IC_ROOT_PK_DER_PREFIX.len()..]; - Ok(key) -} diff --git a/packages/ic-http/Cargo.toml b/packages/ic-http/Cargo.toml new file mode 100644 index 0000000..26550dc --- /dev/null +++ b/packages/ic-http/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "ic-http" +description = "Canister side support for ICP HTTP Protocol" +readme = "README.md" +documentation = "https://docs.rs/ic-http" +categories = ["api-bindings", "algorithms", "cryptography::cryptocurrencies"] +keywords = ["internet-computer", "icp", "dfinity", "http", "authentication"] +include = ["src", "Cargo.toml", "README.md"] + +version.workspace = true +authors.workspace = true +edition.workspace = true +repository.workspace = true +license.workspace = true +homepage.workspace = true + +[dependencies] +ic-http-certification.workspace = true + +bhttp = "0.7.0" + +[lints] +workspace = true diff --git a/packages/ic-http-auth/README.md b/packages/ic-http/README.md similarity index 63% rename from packages/ic-http-auth/README.md rename to packages/ic-http/README.md index efe37f5..bfbda6a 100644 --- a/packages/ic-http-auth/README.md +++ b/packages/ic-http/README.md @@ -1,11 +1,7 @@ -# ic-http-auth +# ic-http This canister side library is used to verify HTTP Message Signatures. ## Usage See the [`todo_routes.rs`](../../examples/todo-app/src/backend/src/todo/todo_routes.rs) file in the todo app example for a usage example. - -## Benchmarks - -The [`canbench_results.yml`](./canbench_results.yml) file contains the results of the benchmarks. diff --git a/packages/ic-http/src/http.rs b/packages/ic-http/src/http.rs new file mode 100644 index 0000000..0fe9d2a --- /dev/null +++ b/packages/ic-http/src/http.rs @@ -0,0 +1,61 @@ +use std::{io::Cursor, str::FromStr}; + +use bhttp::{Message, Mode, StatusCode}; +use ic_http_certification::{HttpRequest, HttpResponse, Method}; + +pub fn decode_args<'a>(bytes: Vec) -> HttpRequest<'a> { + let mut cursor = Cursor::new(bytes); + let msg = Message::read_bhttp(&mut cursor).unwrap(); + let content = msg.content().to_vec(); + + let control = msg.control(); + let (method_bytes, path_bytes) = match control { + bhttp::ControlData::Request { + method, + scheme: _, + authority: _, + path, + } => (method, path), + _ => panic!("Expected request, got response"), + }; + + let method_str = String::from_utf8_lossy(method_bytes); + let path_str = String::from_utf8_lossy(path_bytes); + + let headers: Vec<(String, String)> = msg + .header() + .iter() + .map(|field| { + ( + String::from_utf8_lossy(field.name()).to_string(), + String::from_utf8_lossy(field.value()).to_string(), + ) + }) + .collect(); + + HttpRequest::builder() + .with_url(path_str.to_string()) + .with_method(Method::from_str(&method_str).unwrap()) + .with_headers(headers) + .with_body(content) + .build() +} + +pub fn encode_result(res: HttpResponse) -> Vec { + let status = StatusCode::try_from(res.status_code().as_u16()).unwrap(); + let mut msg = Message::response(status); + + for (header_name, header_value) in res.headers() { + msg.put_header(header_name.as_bytes(), header_value.as_bytes()); + } + + if res.upgrade().unwrap_or(false) { + msg.put_header(b"ic-upgrade", b"true"); + } + + msg.write_content(res.body()); + + let mut encoded = Vec::new(); + msg.write_bhttp(Mode::KnownLength, &mut encoded).unwrap(); + encoded +} diff --git a/packages/ic-http/src/lib.rs b/packages/ic-http/src/lib.rs new file mode 100644 index 0000000..8e81530 --- /dev/null +++ b/packages/ic-http/src/lib.rs @@ -0,0 +1,3 @@ +mod http; + +pub use http::*;