diff --git a/tools/ui/Cargo.lock b/tools/ui/Cargo.lock index 1ef92a17..6222ebcf 100644 --- a/tools/ui/Cargo.lock +++ b/tools/ui/Cargo.lock @@ -44,6 +44,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "beef" version = "0.5.2" @@ -115,9 +121,15 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "bytes" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" + [[package]] name = "candid" -version = "0.10.4" +version = "0.10.10" dependencies = [ "anyhow", "binread", @@ -240,11 +252,16 @@ checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" name = "didjs" version = "0.1.0" dependencies = [ + "base64", "candid", "candid_parser", "ic-cdk", + "ic-certification", + "ic-http-certification", + "ic-representation-independent-hash", "serde", "serde_bytes", + "serde_cbor", ] [[package]] @@ -332,6 +349,12 @@ dependencies = [ "wasi", ] +[[package]] +name = "half" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b43ede17f21864e81be2fa654110bf1e793774238d86ef8555c37e6519c0403" + [[package]] name = "hashbrown" version = "0.14.3" @@ -344,6 +367,17 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "ic-cdk" version = "0.13.1" @@ -371,6 +405,40 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ic-certification" +version = "2.6.0" +source = "git+https://github.com/dfinity/response-verification?rev=da70db93832f88ecc556ae082612aedec47d3816#da70db93832f88ecc556ae082612aedec47d3816" +dependencies = [ + "hex", + "serde", + "serde_bytes", + "sha2", +] + +[[package]] +name = "ic-http-certification" +version = "2.6.0" +source = "git+https://github.com/dfinity/response-verification?rev=da70db93832f88ecc556ae082612aedec47d3816#da70db93832f88ecc556ae082612aedec47d3816" +dependencies = [ + "candid", + "http", + "ic-certification", + "ic-representation-independent-hash", + "serde", + "thiserror", + "urlencoding", +] + +[[package]] +name = "ic-representation-independent-hash" +version = "2.6.0" +source = "git+https://github.com/dfinity/response-verification?rev=da70db93832f88ecc556ae082612aedec47d3816#da70db93832f88ecc556ae082612aedec47d3816" +dependencies = [ + "leb128", + "sha2", +] + [[package]] name = "ic0" version = "0.21.1" @@ -408,6 +476,12 @@ dependencies = [ "either", ] +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + [[package]] name = "lalrpop" version = "0.20.2" @@ -756,6 +830,16 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_cbor" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" +dependencies = [ + "half", + "serde", +] + [[package]] name = "serde_derive" version = "1.0.197" @@ -934,6 +1018,12 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "version_check" version = "0.9.4" diff --git a/tools/ui/src/didjs/Cargo.toml b/tools/ui/src/didjs/Cargo.toml index 2ac00da4..aa3d5a23 100644 --- a/tools/ui/src/didjs/Cargo.toml +++ b/tools/ui/src/didjs/Cargo.toml @@ -8,9 +8,13 @@ path = "lib.rs" crate-type = ["cdylib"] [dependencies] -ic-cdk = "0.13" +base64 = "0.22.1" candid = "0.10" candid_parser = "0.1" +ic-cdk = "0.13" +ic-certification = { git = "https://github.com/dfinity/response-verification", rev = "da70db93832f88ecc556ae082612aedec47d3816" } +ic-http-certification = { git = "https://github.com/dfinity/response-verification", rev = "da70db93832f88ecc556ae082612aedec47d3816" } +ic-representation-independent-hash = { git = "https://github.com/dfinity/response-verification", rev = "da70db93832f88ecc556ae082612aedec47d3816" } serde = "1.0" serde_bytes = "0.11" - +serde_cbor = "0.11.2" diff --git a/tools/ui/src/didjs/lib.rs b/tools/ui/src/didjs/lib.rs index 6109052c..9aa60f5e 100644 --- a/tools/ui/src/didjs/lib.rs +++ b/tools/ui/src/didjs/lib.rs @@ -1,5 +1,13 @@ +use base64::engine::general_purpose::STANDARD as BASE64; +use base64::Engine; use candid::types::{subtype, Type, TypeInner}; use candid_parser::{check_prog, CandidType, Deserialize, IDLProg, TypeEnv}; +use ic_cdk::api::{data_certificate, set_certified_data}; +use ic_cdk::{init, post_upgrade}; +use ic_certification::{labeled, leaf, HashTree}; +use ic_http_certification::DefaultCelBuilder; +use ic_representation_independent_hash::hash; +use serde::Serialize; #[derive(CandidType, Deserialize)] pub struct HeaderField(pub String, pub String); @@ -97,7 +105,7 @@ fn get_path(url: &str) -> Option<&str> { fn http_request(request: HttpRequest) -> HttpResponse { //TODO add /canister_id/ as endpoint when ICQC is available. let path = get_path(request.url.as_str()).unwrap_or("/"); - if let Some((content_type, bytes)) = retrieve(path) { + let mut response = if let Some((content_type, bytes)) = retrieve(path) { HttpResponse { status_code: 200, headers: vec![ @@ -113,7 +121,69 @@ fn http_request(request: HttpRequest) -> HttpResponse { headers: Vec::new(), body: path.as_bytes().to_vec(), } - } + }; + add_certification_headers(&mut response); + response +} + +// Certify that frontend asset certification is skipped for this canister. + +const IC_CERTIFICATE_HEADER: &str = "IC-Certificate"; +const IC_CERTIFICATE_EXPRESSION_HEADER: &str = "IC-CertificateExpression"; + +fn skip_certification_cel_expr() -> String { + DefaultCelBuilder::skip_certification().to_string() +} + +fn skip_certification_asset_tree() -> HashTree { + let cel_expr_hash = hash(skip_certification_cel_expr().as_bytes()); + labeled( + "http_expr", + labeled("<*>", labeled(cel_expr_hash, leaf(vec![]))), + ) +} + +fn add_certification_headers(response: &mut HttpResponse) { + let certified_data = data_certificate().expect("No data certificate available"); + let witness = cbor_encode(&skip_certification_asset_tree()); + let expr_path = ["http_expr", "<*>"]; + let expr_path = cbor_encode(&expr_path); + + response.headers.push(HeaderField( + IC_CERTIFICATE_EXPRESSION_HEADER.to_string(), + skip_certification_cel_expr(), + )); + response.headers.push(HeaderField( + IC_CERTIFICATE_HEADER.to_string(), + format!( + "certificate=:{}:, tree=:{}:, expr_path=:{}:, version=2", + BASE64.encode(certified_data), + BASE64.encode(witness), + BASE64.encode(expr_path) + ), + )); +} + +// Encoding +fn cbor_encode(value: &impl Serialize) -> Vec { + let mut serializer = serde_cbor::Serializer::new(Vec::new()); + serializer + .self_describe() + .expect("Failed to self describe CBOR"); + value + .serialize(&mut serializer) + .expect("Failed to serialize value"); + serializer.into_inner() +} + +#[init] +fn init() { + set_certified_data(&skip_certification_asset_tree().digest()); +} + +#[post_upgrade] +fn post_upgrade() { + init(); } ic_cdk::export_candid!();