From bc97e5b657aafad58f231330c51a67db7beb8d0d Mon Sep 17 00:00:00 2001 From: Frederik Rothenberger Date: Mon, 11 Dec 2023 10:43:21 +0100 Subject: [PATCH] Fix issuer certification (#2130) * Fix issuer certification Also adds a test to check that the certification is valid. * Revert unrelated line change * Fix test comment --- demos/vc_issuer/Cargo.lock | 91 +++++++++++++++++++++++ demos/vc_issuer/Cargo.toml | 1 + demos/vc_issuer/src/main.rs | 8 +- demos/vc_issuer/tests/issue_credential.rs | 69 ++++++++++++++++- 4 files changed, 164 insertions(+), 5 deletions(-) diff --git a/demos/vc_issuer/Cargo.lock b/demos/vc_issuer/Cargo.lock index d65e64fded..875a695c8a 100644 --- a/demos/vc_issuer/Cargo.lock +++ b/demos/vc_issuer/Cargo.lock @@ -995,6 +995,17 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "http" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "iana-time-zone" version = "0.1.58" @@ -1062,6 +1073,19 @@ dependencies = [ "serde_bytes", ] +[[package]] +name = "ic-cbor" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10633e3e4f112f370be0d5458cd0bbb5b42a89fd7aaec71da7ba60e659fa0a83" +dependencies = [ + "candid", + "ic-certification 1.3.0", + "leb128", + "nom", + "thiserror", +] + [[package]] name = "ic-cdk" version = "0.10.0" @@ -1089,6 +1113,21 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ic-certificate-verification" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6f369eef7b7e6691afb9f33510d9cabc91eb36a0cb8a1fbc699221b3686c5c6" +dependencies = [ + "candid", + "ic-cbor", + "ic-certification 1.3.0", + "leb128", + "miracl_core_bls12381", + "nom", + "thiserror", +] + [[package]] name = "ic-certification" version = "0.8.0" @@ -1506,6 +1545,29 @@ dependencies = [ "sha2 0.10.8", ] +[[package]] +name = "ic-response-verification" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc617be90b43f31195c86226d1c7df0b1a5cc0d0bcfe737a5611649f360eed52" +dependencies = [ + "base64 0.21.4", + "candid", + "flate2", + "hex", + "http", + "ic-cbor", + "ic-certificate-verification", + "ic-certification 1.3.0", + "ic-representation-independent-hash", + "leb128", + "log", + "nom", + "sha2 0.10.8", + "thiserror", + "urlencoding", +] + [[package]] name = "ic-stable-structures" version = "0.5.6" @@ -1986,6 +2048,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.7.1" @@ -1995,6 +2063,12 @@ dependencies = [ "adler", ] +[[package]] +name = "miracl_core_bls12381" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07cbe42e2a8dd41df582fb8e00fc24d920b5561cc301fcb6d14e2e0434b500f" + [[package]] name = "multibase" version = "0.9.1" @@ -2024,6 +2098,16 @@ dependencies = [ "memoffset", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "num-bigint" version = "0.4.4" @@ -3036,6 +3120,12 @@ dependencies = [ "serde", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "utf8-width" version = "0.1.6" @@ -3055,6 +3145,7 @@ dependencies = [ "ic-cdk", "ic-cdk-macros", "ic-certification 1.3.0", + "ic-response-verification", "ic-stable-structures 0.6.0", "ic-test-state-machine-client", "identity_core", diff --git a/demos/vc_issuer/Cargo.toml b/demos/vc_issuer/Cargo.toml index 8a285b4296..d0c1a423fa 100644 --- a/demos/vc_issuer/Cargo.toml +++ b/demos/vc_issuer/Cargo.toml @@ -37,4 +37,5 @@ include_dir = "0.7" [dev-dependencies] assert_matches = "1.5.0" ic-test-state-machine-client = "3" +ic-response-verification = "1.3" canister_tests = { path = "../../src/canister_tests" } diff --git a/demos/vc_issuer/src/main.rs b/demos/vc_issuer/src/main.rs index ba6fead31f..3d86a08a42 100644 --- a/demos/vc_issuer/src/main.rs +++ b/demos/vc_issuer/src/main.rs @@ -394,9 +394,11 @@ fn add_adult(adult_id: Principal) -> String { pub fn http_request(req: HttpRequest) -> HttpResponse { let parts: Vec<&str> = req.url.split('?').collect(); let path = parts[0]; - let sigs = SIGNATURES.with_borrow(|sigs| pruned(sigs.root_hash())); - let maybe_asset = ASSETS - .with_borrow(|assets| assets.certified_asset(path, req.certificate_version, Some(sigs))); + let sigs_root_hash = + SIGNATURES.with_borrow(|sigs| pruned(labeled_hash(LABEL_SIG, &sigs.root_hash()))); + let maybe_asset = ASSETS.with_borrow(|assets| { + assets.certified_asset(path, req.certificate_version, Some(sigs_root_hash)) + }); let mut headers = static_headers(); match maybe_asset { diff --git a/demos/vc_issuer/tests/issue_credential.rs b/demos/vc_issuer/tests/issue_credential.rs index 2b7135ffcc..744ae262bf 100644 --- a/demos/vc_issuer/tests/issue_credential.rs +++ b/demos/vc_issuer/tests/issue_credential.rs @@ -3,22 +3,27 @@ use assert_matches::assert_matches; use candid::{CandidType, Deserialize, Principal}; use canister_sig_util::{extract_raw_root_pk_from_der, CanisterSigPublicKey}; +use canister_tests::api::http_request; use canister_tests::api::internet_identity::vc_mvp as ii_api; -use canister_tests::framework::{env, get_wasm_path, principal_1, test_principal, II_WASM}; +use canister_tests::framework::{env, get_wasm_path, principal_1, test_principal, time, II_WASM}; use canister_tests::{flows, match_value}; use ic_cdk::api::management_canister::provisional::CanisterId; +use ic_response_verification::types::{Request, Response, VerificationInfo}; +use ic_response_verification::verify_request_response_pair; use ic_test_state_machine_client::{call_candid, call_candid_as}; use ic_test_state_machine_client::{query_candid_as, CallError, StateMachine}; use identity_core::common::Value; use identity_jose::jwt::JwtClaims; +use internet_identity_interface::http_gateway::{HttpRequest, HttpResponse}; use internet_identity_interface::internet_identity::types::vc_mvp::{ GetIdAliasRequest, GetIdAliasResponse, PrepareIdAliasRequest, PrepareIdAliasResponse, }; use internet_identity_interface::internet_identity::types::FrontendHostname; use lazy_static::lazy_static; +use serde_bytes::ByteBuf; use std::collections::HashMap; use std::path::PathBuf; -use std::time::UNIX_EPOCH; +use std::time::{Duration, UNIX_EPOCH}; use vc_util::issuer_api::{ ArgumentValue, CredentialSpec, GetCredentialRequest, GetCredentialResponse, Icrc21ConsentMessageResponse, Icrc21ConsentPreferences, Icrc21Error, @@ -699,3 +704,63 @@ fn should_configure() { let issuer_id = install_canister(&env, VC_ISSUER_WASM.clone()); api::configure(&env, issuer_id, &DUMMY_ISSUER_INIT).expect("API call failed"); } + +/// Verifies that the expected assets is delivered and certified. +#[test] +fn issuer_canister_serves_http_assets() -> Result<(), CallError> { + fn verify_response_certification( + env: &StateMachine, + canister_id: CanisterId, + request: HttpRequest, + http_response: HttpResponse, + min_certification_version: u16, + ) -> VerificationInfo { + verify_request_response_pair( + Request { + method: request.method, + url: request.url, + headers: request.headers, + body: request.body.into_vec(), + }, + Response { + status_code: http_response.status_code, + headers: http_response.headers, + body: http_response.body.into_vec(), + }, + canister_id.as_slice(), + time(env) as u128, + Duration::from_secs(300).as_nanos(), + &env.root_key(), + min_certification_version as u8, + ) + .unwrap_or_else(|e| panic!("validation failed: {e}")) + } + + let env = env(); + let canister_id = install_canister(&env, VC_ISSUER_WASM.clone()); + + // for each asset and certification version, fetch the asset, check the HTTP status code, headers and certificate. + + for certification_version in 1..=2 { + let request = HttpRequest { + method: "GET".to_string(), + url: "/".to_string(), + headers: vec![], + body: ByteBuf::new(), + certificate_version: Some(certification_version), + }; + let http_response = http_request(&env, canister_id, &request)?; + assert_eq!(http_response.status_code, 200); + + let result = verify_response_certification( + &env, + canister_id, + request, + http_response, + certification_version, + ); + assert_eq!(result.verification_version, certification_version); + } + + Ok(()) +}