diff --git a/Cargo.lock b/Cargo.lock index 0cf29c04ae..1bd928393c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -88,8 +88,8 @@ dependencies = [ "candid_parser", "canister_tests", "hex", - "ic-cdk", - "ic-cdk-macros", + "ic-cdk 0.13.5", + "ic-cdk-macros 0.13.2", "ic-cdk-timers", "ic-metrics-encoder", "ic-stable-structures 0.6.4", @@ -139,8 +139,8 @@ version = "0.1.0" dependencies = [ "base64 0.21.7", "candid", - "ic-cdk", - "ic-cdk-macros", + "ic-cdk 0.13.5", + "ic-cdk-macros 0.13.2", "ic-certification 2.5.0", "ic-representation-independent-hash", "include_dir", @@ -153,9 +153,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.80" +version = "0.1.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" +checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1" dependencies = [ "proc-macro2", "quote", @@ -179,6 +179,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "autocfg" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dde43e75fd43e8a1bf86103336bc699aa8d17ad1be60c76c0bdfd4828e19b78" +dependencies = [ + "autocfg 1.3.0", +] + [[package]] name = "autocfg" version = "1.3.0" @@ -337,6 +346,20 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bls12_381" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3c196a77437e7cc2fb515ce413a6401291578b5afc8ecb29a3c7ab957f05941" +dependencies = [ + "digest 0.9.0", + "ff 0.12.1", + "group 0.12.1", + "pairing 0.22.0", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "bls12_381_plus" version = "0.8.15" @@ -498,24 +521,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "canister_sig_util" -version = "0.1.0" -dependencies = [ - "assert_matches", - "candid", - "hex", - "ic-cdk", - "ic-certification 2.5.0", - "ic-representation-independent-hash", - "lazy_static", - "rand", - "serde", - "serde_bytes", - "serde_cbor", - "sha2 0.10.8", -] - [[package]] name = "canister_tests" version = "0.1.0" @@ -524,8 +529,9 @@ dependencies = [ "candid", "flate2", "hex", - "ic-cdk", + "ic-cdk 0.13.5", "ic-representation-independent-hash", + "ic-verifiable-credentials", "identity_jose", "internet_identity_interface", "lazy_static", @@ -536,7 +542,6 @@ dependencies = [ "serde_cbor", "sha2 0.10.8", "url", - "vc_util", ] [[package]] @@ -548,7 +553,7 @@ dependencies = [ "hound", "image", "lodepng", - "rand", + "rand 0.8.5", "serde_json", ] @@ -659,6 +664,15 @@ dependencies = [ "os_str_bytes", ] +[[package]] +name = "cloudabi" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "codespan-reporting" version = "0.11.1" @@ -1234,6 +1248,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + [[package]] name = "funty" version = "2.0.0" @@ -1704,6 +1724,25 @@ dependencies = [ "serde_bytes", ] +[[package]] +name = "ic-canister-sig-creation" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5db33deb06e0edb366d8d86ef67d7bc1e1759bc7046b0323a33b85b21b8d8d87" +dependencies = [ + "candid", + "hex", + "ic-cdk 0.14.1", + "ic-certification 2.5.0", + "ic-representation-independent-hash", + "lazy_static", + "serde", + "serde_bytes", + "serde_cbor", + "sha2 0.10.8", + "thiserror", +] + [[package]] name = "ic-cbor" version = "2.5.0" @@ -1724,8 +1763,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b1da6a25b045f9da3c9459c0cb2b0700ac368ee16382975a17185a23b9c18ab" dependencies = [ "candid", - "ic-cdk-macros", - "ic0", + "ic-cdk-macros 0.13.2", + "ic0 0.21.1", + "serde", + "serde_bytes", +] + +[[package]] +name = "ic-cdk" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cff1a3c3db565e3384c9c9d6d676b0a3f89a0886f4f787294d9c946d844369f" +dependencies = [ + "candid", + "ic-cdk-macros 0.14.0", + "ic0 0.23.0", "serde", "serde_bytes", ] @@ -1740,10 +1792,24 @@ dependencies = [ "proc-macro2", "quote", "serde", - "serde_tokenstream", + "serde_tokenstream 0.1.7", "syn 1.0.109", ] +[[package]] +name = "ic-cdk-macros" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01dc6bc425ec048d6ac4137c7c0f2cfbd6f8b0be8efc568feae2b265f566117c" +dependencies = [ + "candid", + "proc-macro2", + "quote", + "serde", + "serde_tokenstream 0.2.2", + "syn 2.0.66", +] + [[package]] name = "ic-cdk-timers" version = "0.7.0" @@ -1751,8 +1817,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "054727a3a1c486528b96349817d54290ff70df6addf417def456ea708a16f7fb" dependencies = [ "futures", - "ic-cdk", - "ic0", + "ic-cdk 0.13.5", + "ic0 0.21.1", "serde", "serde_bytes", "slotmap", @@ -1818,7 +1884,7 @@ dependencies = [ "lazy_static", "num-bigint", "pem", - "rand", + "rand 0.8.5", "simple_asn1", "zeroize", ] @@ -1833,8 +1899,8 @@ dependencies = [ "num-bigint", "p256", "pem", - "rand", - "rand_chacha", + "rand 0.8.5", + "rand_chacha 0.3.1", "simple_asn1", "zeroize", ] @@ -1909,7 +1975,7 @@ dependencies = [ "ic-crypto-secrets-containers", "ic-types", "p256", - "rand", + "rand 0.8.5", "serde", "serde_bytes", "simple_asn1", @@ -1931,8 +1997,8 @@ dependencies = [ "ic-crypto-secrets-containers", "ic-protobuf", "ic-types", - "rand", - "rand_chacha", + "rand 0.8.5", + "rand_chacha 0.3.1", "serde", "simple_asn1", "zeroize", @@ -1987,8 +2053,8 @@ dependencies = [ "lazy_static", "pairing 0.22.0", "paste", - "rand", - "rand_chacha", + "rand 0.8.5", + "rand_chacha 0.3.1", "sha2 0.9.9", "subtle", "zeroize", @@ -2002,8 +2068,8 @@ dependencies = [ "hex", "ic-crypto-sha2", "ic-types", - "rand", - "rand_chacha", + "rand 0.8.5", + "rand_chacha 0.3.1", "serde", "zeroize", ] @@ -2033,8 +2099,8 @@ dependencies = [ "ic-types", "lazy_static", "parking_lot", - "rand", - "rand_chacha", + "rand 0.8.5", + "rand_chacha 0.3.1", "serde", "serde_bytes", "serde_cbor", @@ -2240,6 +2306,22 @@ dependencies = [ "urlencoding", ] +[[package]] +name = "ic-signature-verification" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d8a8115bcd1bfa672e2213a66326b3c93483d3a7a29e33dff6e381c7786c091" +dependencies = [ + "ic-canister-sig-creation", + "ic-certification 2.5.0", + "ic-verify-bls-signature", + "ic_principal", + "serde", + "serde_bytes", + "serde_cbor", + "sha2 0.10.8", +] + [[package]] name = "ic-stable-structures" version = "0.5.6" @@ -2317,18 +2399,57 @@ dependencies = [ "libc", "nix", "prost", - "rand", + "rand 0.8.5", "scoped_threadpool", "serde", "thiserror", ] +[[package]] +name = "ic-verifiable-credentials" +version = "0.1.0" +source = "git+https://github.com/dfinity/verifiable-credentials-sdk?rev=2bc90b2ce7355ba68001fcc484bdc31f2fe8e820#2bc90b2ce7355ba68001fcc484bdc31f2fe8e820" +dependencies = [ + "candid", + "ic-canister-sig-creation", + "ic-certification 2.5.0", + "ic-signature-verification", + "identity_core", + "identity_credential", + "identity_jose", + "serde", + "serde_bytes", + "serde_cbor", + "serde_json", + "sha2 0.10.8", +] + +[[package]] +name = "ic-verify-bls-signature" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0f1f8d75f50002970cc2136f909287bf1d59024fbc80ebffbee871afcbd237" +dependencies = [ + "bls12_381", + "hex", + "lazy_static", + "pairing 0.22.0", + "rand 0.6.5", + "sha2 0.9.9", +] + [[package]] name = "ic0" version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a54b5297861c651551676e8c43df805dad175cc33bc97dbd992edbbb85dcbcdf" +[[package]] +name = "ic0" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de254dd67bbd58073e23dc1c8553ba12fa1dc610a19de94ad2bbcd0460c067f" + [[package]] name = "ic_bls12_381" version = "0.8.0" @@ -2369,7 +2490,7 @@ name = "identity_core" version = "1.3.1" source = "git+https://github.com/dfinity/identity.rs.git?rev=aa510ef7f441848d6c78058fe51ad4ad1d9bd5d8#aa510ef7f441848d6c78058fe51ad4ad1d9bd5d8" dependencies = [ - "ic-cdk", + "ic-cdk 0.13.5", "iota-crypto", "multibase", "serde", @@ -2509,7 +2630,7 @@ version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ - "autocfg", + "autocfg 1.3.0", "hashbrown 0.12.3", ] @@ -2541,27 +2662,28 @@ dependencies = [ "base64 0.21.7", "candid", "candid_parser", - "canister_sig_util", "canister_tests", "captcha", "getrandom 0.2.15", "hex", "hex-literal", - "ic-cdk", - "ic-cdk-macros", + "ic-canister-sig-creation", + "ic-cdk 0.13.5", + "ic-cdk-macros 0.13.2", "ic-certification 2.5.0", "ic-http-certification", "ic-metrics-encoder", "ic-response-verification", "ic-stable-structures 0.6.4", + "ic-verifiable-credentials", "identity_jose", "include_dir", "internet_identity_interface", "lazy_static", "lodepng", "pocket-ic", - "rand", - "rand_chacha", + "rand 0.8.5", + "rand_chacha 0.3.1", "rand_core 0.6.4", "regex", "serde", @@ -2569,7 +2691,6 @@ dependencies = [ "serde_cbor", "serde_json", "sha2 0.10.8", - "vc_util", ] [[package]] @@ -2577,7 +2698,7 @@ name = "internet_identity_interface" version = "0.1.0" dependencies = [ "candid", - "ic-cdk", + "ic-cdk 0.13.5", "serde", "serde_bytes", ] @@ -2588,7 +2709,7 @@ version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5db0e2d85e258d6d0db66f4a6bf1e8bdf5b10c3353aa87d98b168778d13fdc1" dependencies = [ - "autocfg", + "autocfg 1.3.0", "digest 0.10.7", "k256", "serde", @@ -2769,7 +2890,7 @@ version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ - "autocfg", + "autocfg 1.3.0", "scopeguard", ] @@ -2851,7 +2972,7 @@ version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" dependencies = [ - "autocfg", + "autocfg 1.3.0", ] [[package]] @@ -2976,7 +3097,7 @@ dependencies = [ "num-integer", "num-iter", "num-traits", - "rand", + "rand 0.8.5", "smallvec", "zeroize", ] @@ -3002,7 +3123,7 @@ version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" dependencies = [ - "autocfg", + "autocfg 1.3.0", "num-integer", "num-traits", ] @@ -3013,7 +3134,7 @@ version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ - "autocfg", + "autocfg 1.3.0", "libm", ] @@ -3249,7 +3370,7 @@ dependencies = [ "base64 0.13.1", "candid", "hex", - "ic-cdk", + "ic-cdk 0.13.5", "reqwest", "schemars", "serde", @@ -3400,7 +3521,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba92fb39ec7ad06ca2582c0ca834dfeadcaf06ddfc8e635c80aa7e1c05315fdd" dependencies = [ "bytes", - "rand", + "rand 0.8.5", "ring", "rustc-hash", "rustls", @@ -3438,6 +3559,25 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" +[[package]] +name = "rand" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" +dependencies = [ + "autocfg 0.1.8", + "libc", + "rand_chacha 0.1.1", + "rand_core 0.4.2", + "rand_hc", + "rand_isaac", + "rand_jitter", + "rand_os", + "rand_pcg", + "rand_xorshift", + "winapi", +] + [[package]] name = "rand" version = "0.8.5" @@ -3445,10 +3585,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", + "rand_chacha 0.3.1", "rand_core 0.6.4", ] +[[package]] +name = "rand_chacha" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" +dependencies = [ + "autocfg 0.1.8", + "rand_core 0.3.1", +] + [[package]] name = "rand_chacha" version = "0.3.1" @@ -3459,6 +3609,21 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + [[package]] name = "rand_core" version = "0.5.1" @@ -3477,6 +3642,77 @@ dependencies = [ "getrandom 0.2.15", ] +[[package]] +name = "rand_hc" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "rand_isaac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "rand_jitter" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" +dependencies = [ + "libc", + "rand_core 0.4.2", + "winapi", +] + +[[package]] +name = "rand_os" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" +dependencies = [ + "cloudabi", + "fuchsia-cprng", + "libc", + "rand_core 0.4.2", + "rdrand", + "winapi", +] + +[[package]] +name = "rand_pcg" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" +dependencies = [ + "autocfg 0.1.8", + "rand_core 0.4.2", +] + +[[package]] +name = "rand_xorshift" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", +] + [[package]] name = "redox_syscall" version = "0.5.1" @@ -3917,6 +4153,18 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "serde_tokenstream" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64060d864397305347a78851c51588fd283767e7e7589829e8121d65512340f1" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "syn 2.0.66", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -4000,8 +4248,8 @@ version = "1.0.0" dependencies = [ "assert_matches", "candid", - "canister_sig_util", "hex", + "ic-canister-sig-creation", "ic-crypto-standalone-sig-verifier", "ic-types", "serde", @@ -4058,7 +4306,7 @@ version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ - "autocfg", + "autocfg 1.3.0", ] [[package]] @@ -4633,26 +4881,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" -[[package]] -name = "vc_util" -version = "0.1.0" -dependencies = [ - "assert_matches", - "candid", - "canister_sig_util", - "ic-certification 2.5.0", - "ic-crypto-standalone-sig-verifier", - "ic-types", - "identity_core", - "identity_credential", - "identity_jose", - "serde", - "serde_bytes", - "serde_cbor", - "serde_json", - "sha2 0.10.8", -] - [[package]] name = "version_check" version = "0.9.4" @@ -5055,7 +5283,7 @@ dependencies = [ "hex", "hkdf", "log", - "rand", + "rand 0.8.5", "serde", "serde_json", "sha2 0.10.8", diff --git a/Cargo.toml b/Cargo.toml index cb722c1b90..c16cf75c1f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,11 +2,9 @@ members = [ "src/asset_util", "src/internet_identity", - "src/canister_sig_util", "src/canister_tests", "src/internet_identity_interface", "src/archive", - "src/vc_util", "src/sig-verifier-js" ] resolver = "2" @@ -20,10 +18,8 @@ opt-level = 'z' [workspace.dependencies] # local dependencies asset_util = { path = "src/asset_util" } -canister_sig_util = { path = "src/canister_sig_util" } canister_tests = { path = "src/canister_tests" } internet_identity_interface = { path = "src/internet_identity_interface" } -vc_util = { path = "src/vc_util" } # ic dependencies candid = "0.10" @@ -37,7 +33,9 @@ ic-metrics-encoder = "1" ic-representation-independent-hash = "2.2" ic-response-verification = "2.2" ic-stable-structures = "0.6" +ic-verifiable-credentials = {git = "https://github.com/dfinity/verifiable-credentials-sdk", rev = "2bc90b2ce7355ba68001fcc484bdc31f2fe8e820"} ic-crypto-standalone-sig-verifier = { git = "https://github.com/dfinity/ic", rev = "e69bcc7b319cbb3ebc22ec55af35287741244db6" } +ic-canister-sig-creation = "1.1" ic-types = { git = "https://github.com/dfinity/ic", rev = "e69bcc7b319cbb3ebc22ec55af35287741244db6" } pocket-ic = "4.0" diff --git a/Dockerfile b/Dockerfile index 79b76ebe13..d7dec59a22 100644 --- a/Dockerfile +++ b/Dockerfile @@ -49,8 +49,6 @@ COPY src/internet_identity/Cargo.toml src/internet_identity/Cargo.toml COPY src/internet_identity_interface/Cargo.toml src/internet_identity_interface/Cargo.toml COPY src/archive/Cargo.toml src/archive/Cargo.toml COPY src/canister_tests/Cargo.toml src/canister_tests/Cargo.toml -COPY src/canister_sig_util/Cargo.toml src/canister_sig_util/Cargo.toml -COPY src/vc_util/Cargo.toml src/vc_util/Cargo.toml COPY src/sig-verifier-js/Cargo.toml src/sig-verifier-js/Cargo.toml COPY src/asset_util/Cargo.toml src/asset_util/Cargo.toml ENV CARGO_TARGET_DIR=/cargo_target @@ -63,10 +61,6 @@ RUN mkdir -p src/internet_identity/src \ && touch src/archive/src/lib.rs \ && mkdir -p src/canister_tests/src \ && touch src/canister_tests/src/lib.rs \ - && mkdir -p src/canister_sig_util/src \ - && touch src/canister_sig_util/src/lib.rs \ - && mkdir -p src/vc_util/src \ - && touch src/vc_util/src/lib.rs \ && mkdir -p src/sig-verifier-js/src \ && touch src/sig-verifier-js/src/lib.rs \ && mkdir -p src/asset_util/src \ diff --git a/demos/vc_issuer/Cargo.lock b/demos/vc_issuer/Cargo.lock index 6c260e2fef..1e7d03cd2f 100644 --- a/demos/vc_issuer/Cargo.lock +++ b/demos/vc_issuer/Cargo.lock @@ -94,9 +94,9 @@ checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" [[package]] name = "arrayvec" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "ascii-canvas" @@ -119,8 +119,8 @@ version = "0.1.0" dependencies = [ "base64 0.21.7", "candid", - "ic-cdk", - "ic-cdk-macros", + "ic-cdk 0.13.2", + "ic-cdk-macros 0.13.2", "ic-certification 2.5.0", "ic-representation-independent-hash", "include_dir", @@ -159,6 +159,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "autocfg" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dde43e75fd43e8a1bf86103336bc699aa8d17ad1be60c76c0bdfd4828e19b78" +dependencies = [ + "autocfg 1.3.0", +] + [[package]] name = "autocfg" version = "1.3.0" @@ -317,6 +326,20 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bls12_381" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3c196a77437e7cc2fb515ce413a6401291578b5afc8ecb29a3c7ab957f05941" +dependencies = [ + "digest 0.9.0", + "ff 0.12.1", + "group 0.12.1", + "pairing 0.22.0", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "bls12_381_plus" version = "0.8.15" @@ -472,22 +495,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "canister_sig_util" -version = "0.1.0" -dependencies = [ - "candid", - "hex", - "ic-cdk", - "ic-certification 2.5.0", - "ic-representation-independent-hash", - "lazy_static", - "serde", - "serde_bytes", - "serde_cbor", - "sha2 0.10.8", -] - [[package]] name = "canister_tests" version = "0.1.0" @@ -496,8 +503,9 @@ dependencies = [ "candid", "flate2", "hex", - "ic-cdk", + "ic-cdk 0.13.2", "ic-representation-independent-hash", + "ic-verifiable-credentials", "identity_jose", "internet_identity_interface", "lazy_static", @@ -508,7 +516,6 @@ dependencies = [ "serde_cbor", "sha2 0.10.8", "url", - "vc_util", ] [[package]] @@ -618,6 +625,15 @@ dependencies = [ "os_str_bytes", ] +[[package]] +name = "cloudabi" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "codespan-reporting" version = "0.11.1" @@ -1169,6 +1185,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + [[package]] name = "funty" version = "2.0.0" @@ -1591,6 +1613,25 @@ dependencies = [ "serde_bytes", ] +[[package]] +name = "ic-canister-sig-creation" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5db33deb06e0edb366d8d86ef67d7bc1e1759bc7046b0323a33b85b21b8d8d87" +dependencies = [ + "candid", + "hex", + "ic-cdk 0.14.1", + "ic-certification 2.5.0", + "ic-representation-independent-hash", + "lazy_static", + "serde", + "serde_bytes", + "serde_cbor", + "sha2 0.10.8", + "thiserror", +] + [[package]] name = "ic-cbor" version = "2.5.0" @@ -1611,8 +1652,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8859bc2b863a77750acf199e1fb7e3fc403e1b475855ba13f59cb4e4036d238" dependencies = [ "candid", - "ic-cdk-macros", - "ic0", + "ic-cdk-macros 0.13.2", + "ic0 0.21.1", + "serde", + "serde_bytes", +] + +[[package]] +name = "ic-cdk" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cff1a3c3db565e3384c9c9d6d676b0a3f89a0886f4f787294d9c946d844369f" +dependencies = [ + "candid", + "ic-cdk-macros 0.14.0", + "ic0 0.23.0", "serde", "serde_bytes", ] @@ -1627,10 +1681,24 @@ dependencies = [ "proc-macro2", "quote", "serde", - "serde_tokenstream", + "serde_tokenstream 0.1.7", "syn 1.0.109", ] +[[package]] +name = "ic-cdk-macros" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01dc6bc425ec048d6ac4137c7c0f2cfbd6f8b0be8efc568feae2b265f566117c" +dependencies = [ + "candid", + "proc-macro2", + "quote", + "serde", + "serde_tokenstream 0.2.2", + "syn 2.0.66", +] + [[package]] name = "ic-certificate-verification" version = "2.4.0" @@ -1691,7 +1759,7 @@ dependencies = [ "lazy_static", "num-bigint", "pem", - "rand", + "rand 0.8.5", "simple_asn1", "zeroize", ] @@ -1706,8 +1774,8 @@ dependencies = [ "num-bigint", "p256", "pem", - "rand", - "rand_chacha", + "rand 0.8.5", + "rand_chacha 0.3.1", "simple_asn1", "zeroize", ] @@ -1782,7 +1850,7 @@ dependencies = [ "ic-crypto-secrets-containers", "ic-types", "p256", - "rand", + "rand 0.8.5", "serde", "serde_bytes", "simple_asn1", @@ -1804,8 +1872,8 @@ dependencies = [ "ic-crypto-secrets-containers", "ic-protobuf", "ic-types", - "rand", - "rand_chacha", + "rand 0.8.5", + "rand_chacha 0.3.1", "serde", "simple_asn1", "zeroize", @@ -1860,8 +1928,8 @@ dependencies = [ "lazy_static", "pairing 0.22.0", "paste", - "rand", - "rand_chacha", + "rand 0.8.5", + "rand_chacha 0.3.1", "sha2 0.9.9", "subtle", "zeroize", @@ -1875,8 +1943,8 @@ dependencies = [ "hex", "ic-crypto-sha2", "ic-types", - "rand", - "rand_chacha", + "rand 0.8.5", + "rand_chacha 0.3.1", "serde", "zeroize", ] @@ -1906,8 +1974,8 @@ dependencies = [ "ic-types", "lazy_static", "parking_lot", - "rand", - "rand_chacha", + "rand 0.8.5", + "rand_chacha 0.3.1", "serde", "serde_bytes", "serde_cbor", @@ -1929,7 +1997,7 @@ name = "ic-crypto-internal-types" version = "0.9.0" source = "git+https://github.com/dfinity/ic?rev=e69bcc7b319cbb3ebc22ec55af35287741244db6#e69bcc7b319cbb3ebc22ec55af35287741244db6" dependencies = [ - "arrayvec 0.7.4", + "arrayvec 0.7.6", "hex", "ic-protobuf", "phantom_newtype", @@ -2107,6 +2175,22 @@ dependencies = [ "urlencoding", ] +[[package]] +name = "ic-signature-verification" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d8a8115bcd1bfa672e2213a66326b3c93483d3a7a29e33dff6e381c7786c091" +dependencies = [ + "ic-canister-sig-creation", + "ic-certification 2.5.0", + "ic-verify-bls-signature", + "ic_principal", + "serde", + "serde_bytes", + "serde_cbor", + "sha2 0.10.8", +] + [[package]] name = "ic-stable-structures" version = "0.5.6" @@ -2184,18 +2268,57 @@ dependencies = [ "libc", "nix", "prost", - "rand", + "rand 0.8.5", "scoped_threadpool", "serde", "thiserror", ] +[[package]] +name = "ic-verifiable-credentials" +version = "0.1.0" +source = "git+https://github.com/dfinity/verifiable-credentials-sdk?rev=2bc90b2ce7355ba68001fcc484bdc31f2fe8e820#2bc90b2ce7355ba68001fcc484bdc31f2fe8e820" +dependencies = [ + "candid", + "ic-canister-sig-creation", + "ic-certification 2.5.0", + "ic-signature-verification", + "identity_core", + "identity_credential", + "identity_jose", + "serde", + "serde_bytes", + "serde_cbor", + "serde_json", + "sha2 0.10.8", +] + +[[package]] +name = "ic-verify-bls-signature" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0f1f8d75f50002970cc2136f909287bf1d59024fbc80ebffbee871afcbd237" +dependencies = [ + "bls12_381", + "hex", + "lazy_static", + "pairing 0.22.0", + "rand 0.6.5", + "sha2 0.9.9", +] + [[package]] name = "ic0" version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a54b5297861c651551676e8c43df805dad175cc33bc97dbd992edbbb85dcbcdf" +[[package]] +name = "ic0" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de254dd67bbd58073e23dc1c8553ba12fa1dc610a19de94ad2bbcd0460c067f" + [[package]] name = "ic_bls12_381" version = "0.8.0" @@ -2236,7 +2359,7 @@ name = "identity_core" version = "1.3.1" source = "git+https://github.com/dfinity/identity.rs.git?rev=aa510ef7f441848d6c78058fe51ad4ad1d9bd5d8#aa510ef7f441848d6c78058fe51ad4ad1d9bd5d8" dependencies = [ - "ic-cdk", + "ic-cdk 0.13.2", "iota-crypto", "multibase", "serde", @@ -2363,7 +2486,7 @@ version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ - "autocfg", + "autocfg 1.3.0", "hashbrown 0.12.3", ] @@ -2392,7 +2515,7 @@ name = "internet_identity_interface" version = "0.1.0" dependencies = [ "candid", - "ic-cdk", + "ic-cdk 0.13.2", "serde", "serde_bytes", ] @@ -2403,7 +2526,7 @@ version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5db0e2d85e258d6d0db66f4a6bf1e8bdf5b10c3353aa87d98b168778d13fdc1" dependencies = [ - "autocfg", + "autocfg 1.3.0", "digest 0.10.7", "k256", "serde", @@ -2584,7 +2707,7 @@ version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ - "autocfg", + "autocfg 1.3.0", "scopeguard", ] @@ -2653,7 +2776,7 @@ version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" dependencies = [ - "autocfg", + "autocfg 1.3.0", ] [[package]] @@ -2777,7 +2900,7 @@ dependencies = [ "num-integer", "num-iter", "num-traits", - "rand", + "rand 0.8.5", "smallvec", "zeroize", ] @@ -2803,7 +2926,7 @@ version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" dependencies = [ - "autocfg", + "autocfg 1.3.0", "num-integer", "num-traits", ] @@ -2814,7 +2937,7 @@ version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ - "autocfg", + "autocfg 1.3.0", "libm", ] @@ -3037,7 +3160,7 @@ dependencies = [ "base64 0.13.1", "candid", "hex", - "ic-cdk", + "ic-cdk 0.13.2", "reqwest", "schemars", "serde", @@ -3148,7 +3271,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" dependencies = [ "anyhow", - "itertools 0.12.1", + "itertools 0.11.0", "proc-macro2", "quote", "syn 2.0.66", @@ -3188,7 +3311,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba92fb39ec7ad06ca2582c0ca834dfeadcaf06ddfc8e635c80aa7e1c05315fdd" dependencies = [ "bytes", - "rand", + "rand 0.8.5", "ring", "rustc-hash", "rustls", @@ -3226,6 +3349,25 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" +[[package]] +name = "rand" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" +dependencies = [ + "autocfg 0.1.8", + "libc", + "rand_chacha 0.1.1", + "rand_core 0.4.2", + "rand_hc", + "rand_isaac", + "rand_jitter", + "rand_os", + "rand_pcg", + "rand_xorshift", + "winapi", +] + [[package]] name = "rand" version = "0.8.5" @@ -3233,10 +3375,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", + "rand_chacha 0.3.1", "rand_core 0.6.4", ] +[[package]] +name = "rand_chacha" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" +dependencies = [ + "autocfg 0.1.8", + "rand_core 0.3.1", +] + [[package]] name = "rand_chacha" version = "0.3.1" @@ -3247,6 +3399,21 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + [[package]] name = "rand_core" version = "0.5.1" @@ -3265,6 +3432,77 @@ dependencies = [ "getrandom 0.2.15", ] +[[package]] +name = "rand_hc" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "rand_isaac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "rand_jitter" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" +dependencies = [ + "libc", + "rand_core 0.4.2", + "winapi", +] + +[[package]] +name = "rand_os" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" +dependencies = [ + "cloudabi", + "fuchsia-cprng", + "libc", + "rand_core 0.4.2", + "rdrand", + "winapi", +] + +[[package]] +name = "rand_pcg" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" +dependencies = [ + "autocfg 0.1.8", + "rand_core 0.4.2", +] + +[[package]] +name = "rand_xorshift" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", +] + [[package]] name = "redox_syscall" version = "0.5.1" @@ -3696,6 +3934,18 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "serde_tokenstream" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64060d864397305347a78851c51588fd283767e7e7589829e8121d65512340f1" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "syn 2.0.66", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -3816,7 +4066,7 @@ version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ - "autocfg", + "autocfg 1.3.0", ] [[package]] @@ -4396,15 +4646,17 @@ dependencies = [ "asset_util", "candid", "candid_parser", - "canister_sig_util", "canister_tests", "hex", - "ic-cdk", - "ic-cdk-macros", + "ic-canister-sig-creation", + "ic-cdk 0.13.2", + "ic-cdk-macros 0.13.2", "ic-certification 2.5.0", + "ic-crypto-standalone-sig-verifier", "ic-http-certification", "ic-response-verification", "ic-stable-structures 0.6.4", + "ic-verifiable-credentials", "include_dir", "internet_identity_interface", "lazy_static", @@ -4415,26 +4667,6 @@ dependencies = [ "serde_json", "sha2 0.10.8", "strfmt", - "vc_util", -] - -[[package]] -name = "vc_util" -version = "0.1.0" -dependencies = [ - "candid", - "canister_sig_util", - "ic-certification 2.5.0", - "ic-crypto-standalone-sig-verifier", - "ic-types", - "identity_core", - "identity_credential", - "identity_jose", - "serde", - "serde_bytes", - "serde_cbor", - "serde_json", - "sha2 0.10.8", ] [[package]] @@ -4839,7 +5071,7 @@ dependencies = [ "hex", "hkdf", "log", - "rand", + "rand 0.8.5", "serde", "serde_json", "sha2 0.10.8", diff --git a/demos/vc_issuer/Cargo.toml b/demos/vc_issuer/Cargo.toml index 4cfb4be04e..43ae17475e 100644 --- a/demos/vc_issuer/Cargo.toml +++ b/demos/vc_issuer/Cargo.toml @@ -8,16 +8,17 @@ edition = "2021" [dependencies] # local dependencies -canister_sig_util = { path = "../../src/canister_sig_util" } internet_identity_interface = { path = "../../src/internet_identity_interface" } -vc_util = { path = "../../src/vc_util" } asset_util = { path = "../../src/asset_util" } # ic dependencies candid = "0.10" +ic-canister-sig-creation = "1.1" ic-cdk = "0.13" ic-cdk-macros = "0.13" ic-certification = "2.2" +ic-crypto-standalone-sig-verifier = { git = "https://github.com/dfinity/ic", rev = "e69bcc7b319cbb3ebc22ec55af35287741244db6" } ic-stable-structures = "0.6.0" +ic-verifiable-credentials = {git = "https://github.com/dfinity/verifiable-credentials-sdk", rev = "2bc90b2ce7355ba68001fcc484bdc31f2fe8e820"} # other dependencies hex = "0.4" diff --git a/demos/vc_issuer/src/consent_message.rs b/demos/vc_issuer/src/consent_message.rs index 6d193ff14c..4dfa53ad6e 100644 --- a/demos/vc_issuer/src/consent_message.rs +++ b/demos/vc_issuer/src/consent_message.rs @@ -2,13 +2,13 @@ use crate::verify_credential_spec; use crate::SupportedCredentialType; +use ic_verifiable_credentials::issuer_api::{ + CredentialSpec, Icrc21ConsentInfo, Icrc21ConsentPreferences, Icrc21Error, Icrc21ErrorInfo, +}; use lazy_static::lazy_static; use std::collections::HashMap; use std::fmt::{Display, Formatter}; use strfmt::strfmt; -use vc_util::issuer_api::{ - CredentialSpec, Icrc21ConsentInfo, Icrc21ConsentPreferences, Icrc21Error, Icrc21ErrorInfo, -}; use SupportedLanguage::{English, German}; const EMPLOYMENT_VC_DESCRIPTION_EN: &str = r###"# {employer} Employment Credential diff --git a/demos/vc_issuer/src/main.rs b/demos/vc_issuer/src/main.rs index 794f7c680b..1f60693a4c 100644 --- a/demos/vc_issuer/src/main.rs +++ b/demos/vc_issuer/src/main.rs @@ -1,28 +1,30 @@ use crate::consent_message::{get_vc_consent_message, SupportedLanguage}; use candid::{candid_method, CandidType, Deserialize, Principal}; -use canister_sig_util::signature_map::{SignatureMap, LABEL_SIG}; -use canister_sig_util::{extract_raw_root_pk_from_der, CanisterSigPublicKey, IC_ROOT_PUBLIC_KEY}; +use ic_canister_sig_creation::signature_map::{CanisterSigInputs, SignatureMap, LABEL_SIG}; +use ic_canister_sig_creation::{ + extract_raw_root_pk_from_der, CanisterSigPublicKey, IC_ROOT_PUBLIC_KEY, +}; use ic_cdk::api::{caller, set_certified_data, time}; use ic_cdk_macros::{init, query, update}; use ic_certification::{fork_hash, labeled_hash, pruned, Hash}; use ic_stable_structures::storable::Bound; use ic_stable_structures::{DefaultMemoryImpl, RestrictedMemory, StableCell, Storable}; -use include_dir::{include_dir, Dir}; -use serde_bytes::ByteBuf; -use sha2::{Digest, Sha256}; -use std::borrow::Cow; -use std::cell::RefCell; -use std::collections::HashSet; -use vc_util::issuer_api::{ +use ic_verifiable_credentials::issuer_api::{ ArgumentValue, CredentialSpec, DerivationOriginData, DerivationOriginError, DerivationOriginRequest, GetCredentialRequest, Icrc21ConsentInfo, Icrc21Error, Icrc21VcConsentMessageRequest, IssueCredentialError, IssuedCredentialData, PrepareCredentialRequest, PreparedCredentialData, SignedIdAlias, }; -use vc_util::{ +use ic_verifiable_credentials::{ build_credential_jwt, did_for_principal, get_verified_id_alias_from_jws, vc_jwt_to_jws, - vc_signing_input, vc_signing_input_hash, AliasTuple, CredentialParams, + vc_signing_input, AliasTuple, CredentialParams, VC_SIGNING_INPUT_DOMAIN, }; +use include_dir::{include_dir, Dir}; +use serde_bytes::ByteBuf; +use sha2::{Digest, Sha256}; +use std::borrow::Cow; +use std::cell::RefCell; +use std::collections::HashSet; use SupportedCredentialType::{UniversityDegree, VerifiedAdult, VerifiedEmployee}; use asset_util::{collect_assets, Asset, CertifiedAssets, ContentEncoding, ContentType}; @@ -215,11 +217,14 @@ async fn prepare_credential( }; let signing_input = vc_signing_input(&credential_jwt, &CANISTER_SIG_PK).expect("failed getting signing_input"); - let msg_hash = vc_signing_input_hash(&signing_input); SIGNATURES.with(|sigs| { let mut sigs = sigs.borrow_mut(); - sigs.add_signature(&CANISTER_SIG_SEED, msg_hash); + sigs.add_signature(&CanisterSigInputs { + domain: VC_SIGNING_INPUT_DOMAIN, + seed: &CANISTER_SIG_SEED, + message: &signing_input, + }); }); update_root_hash(); Ok(PreparedCredentialData { @@ -270,13 +275,15 @@ fn get_credential(req: GetCredentialRequest) -> Result = - extract_raw_root_pk_from_der(IC_ROOT_PK_DER).expect("Failed decoding IC root key."); -} - -/// A public key of canister signatures, -/// see https://internetcomputer.org/docs/current/references/ic-interface-spec#canister-signatures -#[derive(Clone, Eq, PartialEq, Debug)] -pub struct CanisterSigPublicKey { - pub canister_id: Principal, - pub seed: Vec, -} - -impl TryFrom<&[u8]> for CanisterSigPublicKey { - type Error = String; - - fn try_from(pk_der: &[u8]) -> Result { - let pk_raw = extract_raw_canister_sig_pk_from_der(pk_der)?; - Self::try_from_raw(pk_raw.as_slice()) - } -} - -impl CanisterSigPublicKey { - /// Constructs a new canister signatures public key. - pub fn new(canister_id: Principal, seed: Vec) -> Self { - CanisterSigPublicKey { canister_id, seed } - } - - pub fn try_from_raw(pk_raw: &[u8]) -> Result { - let canister_id_len: usize = if !pk_raw.is_empty() { - usize::from(pk_raw[0]) - } else { - return Err("empty raw canister sig pk".to_string()); - }; - if pk_raw.len() < (1 + canister_id_len) { - return Err("canister sig pk too short".to_string()); - } - let canister_id_raw = &pk_raw[1..(1 + canister_id_len)]; - let seed = &pk_raw[canister_id_len + 1..]; - let canister_id = Principal::try_from_slice(canister_id_raw) - .map_err(|e| format!("invalid canister id in canister sig pk: {}", e))?; - Ok(CanisterSigPublicKey { - canister_id, - seed: seed.to_vec(), - }) - } - - /// Returns a byte vector with DER-encoding of this key, see - /// https://internetcomputer.org/docs/current/references/ic-interface-spec#canister-signatures - pub fn to_der(&self) -> Vec { - let raw_pk = self.to_raw(); - - let mut der_pk: Vec = vec![]; - // sequence of length 17 + the bit string length - der_pk.push(0x30); - der_pk.push(17 + raw_pk.len() as u8); - der_pk.extend(CANISTER_SIG_PK_DER_OID); - // BIT string of given length - der_pk.push(0x03); - der_pk.push(1 + raw_pk.len() as u8); - der_pk.push(0x00); - der_pk.extend(raw_pk); - der_pk - } - - /// Returns a byte vector with raw encoding of this key (i.e. a bit string with - /// canister id length, canister id, and seed, without the DER-envelope) - /// https://internetcomputer.org/docs/current/references/ic-interface-spec#canister-signatures - pub fn to_raw(&self) -> Vec { - let mut raw_pk: Vec = vec![]; - raw_pk.push(self.canister_id.as_ref().len() as u8); - raw_pk.extend(self.canister_id.as_ref()); - raw_pk.extend(self.seed.as_slice()); - raw_pk - } -} - -/// Verifies the structure given public key in DER-format, and returns raw bytes of the key. -pub fn extract_raw_root_pk_from_der(pk_der: &[u8]) -> Result, 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.to_vec()) -} - -/// Verifies the structure given public key in DER-format, and returns raw bytes of the key. -pub fn extract_raw_canister_sig_pk_from_der(pk_der: &[u8]) -> Result, String> { - let oid_part = &pk_der[2..(CANISTER_SIG_PK_DER_OID.len() + 2)]; - if oid_part[..] != CANISTER_SIG_PK_DER_OID[..] { - return Err(String::from("invalid OID of canister sig pk")); - } - let bitstring_offset: usize = CANISTER_SIG_PK_DER_PREFIX_LENGTH; - let canister_id_len: usize = if pk_der.len() > bitstring_offset { - usize::from(pk_der[bitstring_offset]) - } else { - return Err(String::from("canister sig pk shorter than DER prefix")); - }; - if pk_der.len() < (bitstring_offset + 1 + canister_id_len) { - return Err(String::from("canister sig pk too short")); - } - Ok(pk_der[(bitstring_offset)..].to_vec()) -} - -pub fn hash_bytes(value: impl AsRef<[u8]>) -> Hash { - let mut hasher = Sha256::new(); - hasher.update(value.as_ref()); - hasher.finalize().into() -} - -pub fn delegation_signature_msg( - pubkey: &[u8], - expiration: u64, - targets: Option<&Vec>>, -) -> Vec { - let mut m: Vec<(String, Value)> = vec![]; - m.push(("pubkey".into(), Value::Bytes(pubkey.to_vec()))); - m.push(("expiration".into(), Value::Number(expiration))); - if let Some(targets) = targets.as_ref() { - let mut arr = Vec::with_capacity(targets.len()); - for t in targets.iter() { - arr.push(Value::Bytes(t.to_vec())); - } - m.push(("targets".into(), Value::Array(arr))); - } - let map_hash = representation_independent_hash(m.as_slice()); - msg_with_domain(b"ic-request-auth-delegation", &map_hash) -} - -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(Serialize)] -struct CanisterSig { - certificate: ByteBuf, - tree: HashTree, -} - -#[cfg(test)] -mod tests { - use super::*; - use assert_matches::assert_matches; - - const TEST_SIGNING_CANISTER_ID: &str = "rwlgt-iiaaa-aaaaa-aaaaa-cai"; - const TEST_SEED: [u8; 3] = [42, 72, 44]; - - const CANISTER_SIG_PK_DER: &[u8; 33] = b"\x30\x1f\x30\x0c\x06\x0a\x2b\x06\x01\x04\x01\x83\xb8\x43\x01\x02\x03\x0f\x00\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x2a\x48\x2c"; - - #[test] - fn should_der_encode_canister_sig_pk() { - let canister_id = Principal::from_text(TEST_SIGNING_CANISTER_ID).expect("wrong principal"); - let cs_pk = CanisterSigPublicKey::new(canister_id, TEST_SEED.to_vec()); - let cs_pk_der = cs_pk.to_der(); - assert_eq!(CANISTER_SIG_PK_DER.as_slice(), cs_pk_der.as_slice()); - } - - #[test] - fn should_raw_encode_canister_sig_pk() { - let canister_id = Principal::from_text(TEST_SIGNING_CANISTER_ID).expect("wrong principal"); - let cs_pk = CanisterSigPublicKey::new(canister_id, TEST_SEED.to_vec()); - let cs_pk_raw = cs_pk.to_raw(); - assert_eq!( - &CANISTER_SIG_PK_DER.as_slice()[CANISTER_SIG_PK_DER_PREFIX_LENGTH..], - cs_pk_raw.as_slice() - ); - } - - #[test] - fn should_parse_canister_sig_pk_from_der() { - let cs_pk = CanisterSigPublicKey::try_from(CANISTER_SIG_PK_DER.as_slice()) - .expect("Failed parsing canister sig pk DER"); - let canister_id = Principal::from_text(TEST_SIGNING_CANISTER_ID).expect("wrong principal"); - - assert_eq!(cs_pk.canister_id, canister_id); - assert_eq!(cs_pk.seed.as_slice(), TEST_SEED.as_slice()); - assert_eq!(cs_pk.to_der().as_slice(), CANISTER_SIG_PK_DER.as_slice()); - } - - #[test] - fn should_fail_parsing_canister_sig_pk_from_bad_oid_der() { - let mut bad_oid_der = *CANISTER_SIG_PK_DER; - bad_oid_der[2] += 42; - let result = CanisterSigPublicKey::try_from(bad_oid_der.as_slice()); - assert_matches!(result, Err(e) if e.contains("invalid OID")); - } - - #[test] - fn should_fail_parsing_canister_sig_pk_from_short_der() { - let result = CanisterSigPublicKey::try_from(CANISTER_SIG_PK_DER[..25].to_vec().as_slice()); - assert_matches!(result, Err(e) if e.contains("pk too short")); - } - - #[test] - fn should_parse_canister_sig_pk_from_raw() { - let cs_pk = CanisterSigPublicKey::try_from_raw( - &CANISTER_SIG_PK_DER.as_slice()[CANISTER_SIG_PK_DER_PREFIX_LENGTH..], - ) - .expect("Failed parsing canister sig pk DER"); - let canister_id = Principal::from_text(TEST_SIGNING_CANISTER_ID).expect("wrong principal"); - - assert_eq!(cs_pk.canister_id, canister_id); - assert_eq!(cs_pk.seed.as_slice(), TEST_SEED.as_slice()); - assert_eq!(cs_pk.to_der().as_slice(), CANISTER_SIG_PK_DER.as_slice()); - } - - #[test] - fn should_fail_parsing_canister_sig_pk_from_short_raw() { - let result = CanisterSigPublicKey::try_from_raw( - &CANISTER_SIG_PK_DER.as_slice() - [CANISTER_SIG_PK_DER_PREFIX_LENGTH..(CANISTER_SIG_PK_DER_PREFIX_LENGTH + 10)], - ); - assert_matches!(result, Err(e) if e.contains("pk too short")); - } - - #[test] - fn should_extract_raw_canister_sig_pk_from_der() { - let raw_pk = extract_raw_canister_sig_pk_from_der(CANISTER_SIG_PK_DER) - .expect("Wrong DER canister sig pk"); - assert_eq!( - raw_pk.as_slice(), - &(*CANISTER_SIG_PK_DER)[CANISTER_SIG_PK_DER_PREFIX_LENGTH..] - ) - } - - #[test] - fn should_fail_extract_raw_canister_sig_pk_from_bad_oid_der() { - let mut bad_oid_der = *CANISTER_SIG_PK_DER; - bad_oid_der[2] += 42; - let result = extract_raw_canister_sig_pk_from_der(&bad_oid_der); - assert_matches!(result, Err(e) if e.contains("invalid OID")); - } - - #[test] - fn should_fail_extract_raw_canister_sig_pk_from_short_der() { - let result = extract_raw_canister_sig_pk_from_der(&CANISTER_SIG_PK_DER[..25]); - assert_matches!(result, Err(e) if e.contains("pk too short")); - } - - #[test] - fn should_extract_raw_root_pk_from_der() { - let raw_pk = - extract_raw_root_pk_from_der(IC_ROOT_PK_DER).expect("Failed decoding IC root key."); - assert_eq!(IC_ROOT_PK_LENGTH, raw_pk.len()); - assert_eq!( - raw_pk.as_slice(), - &(*IC_ROOT_PK_DER)[IC_ROOT_PK_DER_PREFIX.len()..] - ) - } - - #[test] - fn should_fail_extract_raw_root_pk_from_bad_oid_der() { - let mut bad_oid_der = *IC_ROOT_PK_DER; - bad_oid_der[2] += 42; - let result = extract_raw_root_pk_from_der(&bad_oid_der); - assert_matches!(result, Err(e) if e.contains("invalid OID")); - } - - #[test] - fn should_fail_extract_raw_root_pk_from_short_der() { - let result = extract_raw_root_pk_from_der(&IC_ROOT_PK_DER[..42]); - assert_matches!(result, Err(e) if e.contains("invalid root pk length")); - } -} diff --git a/src/canister_sig_util/src/signature_map.rs b/src/canister_sig_util/src/signature_map.rs deleted file mode 100644 index 3cf6bb5508..0000000000 --- a/src/canister_sig_util/src/signature_map.rs +++ /dev/null @@ -1,196 +0,0 @@ -//! Maintains anchor signatures and expirations. -use crate::{hash_bytes, CanisterSig}; -use ic_cdk::api::{data_certificate, time}; -use ic_certification::{ - fork, labeled, leaf, leaf_hash, pruned, AsHashTree, Hash, HashTree, RbTree, -}; -use serde::Serialize; -use serde_bytes::ByteBuf; -use std::borrow::Cow; -use std::collections::BinaryHeap; - -const MINUTE_NS: u64 = 60 * 1_000_000_000; - -// The expiration used for signatures. -#[allow(clippy::identity_op)] -const SIGNATURE_EXPIRATION_PERIOD_NS: u64 = 1 * MINUTE_NS; -const MAX_SIGS_TO_PRUNE: usize = 50; -pub const LABEL_SIG: &[u8] = b"sig"; -#[derive(Default)] -struct Unit; - -impl AsHashTree for Unit { - fn root_hash(&self) -> Hash { - leaf_hash(&b""[..]) - } - fn as_hash_tree(&self) -> HashTree { - leaf(Cow::from(&b""[..])) - } -} - -#[derive(PartialEq, Eq)] -struct SigExpiration { - expires_at: u64, - seed_hash: Hash, - msg_hash: Hash, -} - -impl Ord for SigExpiration { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - // BinaryHeap is a max heap, but we want expired entries - // first, hence the inversed order. - other.expires_at.cmp(&self.expires_at) - } -} - -impl PartialOrd for SigExpiration { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -#[derive(Default)] -pub struct SignatureMap { - certified_map: RbTree>, - expiration_queue: BinaryHeap, -} - -impl SignatureMap { - fn put(&mut self, seed: &[u8], message_hash: Hash, signature_expires_at: u64) { - let seed_hash = hash_bytes(seed); - if self.certified_map.get(&seed_hash[..]).is_none() { - let mut submap = RbTree::new(); - submap.insert(message_hash, Unit); - self.certified_map.insert(seed_hash, submap); - } else { - self.certified_map.modify(&seed_hash[..], |submap| { - submap.insert(message_hash, Unit); - }); - } - self.expiration_queue.push(SigExpiration { - seed_hash, - msg_hash: message_hash, - expires_at: signature_expires_at, - }); - } - - pub fn delete(&mut self, seed_hash: Hash, message_hash: Hash) { - let mut is_empty = false; - self.certified_map.modify(&seed_hash[..], |m| { - m.delete(&message_hash[..]); - is_empty = m.is_empty(); - }); - if is_empty { - self.certified_map.delete(&seed_hash[..]); - } - } - - /// Removes a batch of expired signatures from the signature map. - /// - /// This function piggy-backs on update calls that create new signatures to - /// amortize the cost of tree pruning. Each operation on the signature map - /// will prune at most [MAX_SIGS_TO_PRUNE] other signatures. - /// - /// Pruning the signature map also requires updating the `certified_data` - /// with the new root hash. Therefore this function is only called by [add_signature] - /// which requires updating the `certified_data` as well. This avoids the risk - /// of clients forgetting to update `certified_data` as it would be a bug even - /// without pruning. - fn prune_expired(&mut self, now: u64) -> usize { - let mut num_pruned = 0; - - for _step in 0..MAX_SIGS_TO_PRUNE { - if let Some(expiration) = self.expiration_queue.peek() { - if expiration.expires_at > now { - return num_pruned; - } - } - if let Some(expiration) = self.expiration_queue.pop() { - self.delete(expiration.seed_hash, expiration.msg_hash); - } - num_pruned += 1; - } - - num_pruned - } - - /// Retrieves from this map and returns canister signature for the specified `seed` and `message_hash`. - /// If the canister uses - /// [certified_data](https://internetcomputer.org/docs/current/references/ic-interface-spec/#system-api-certified-data) - /// to manage certified assets, the caller should provide also the root hash of the assets subtree - /// (cf. `asset_util`-crate). - /// The returned value (if found) is a CBOR-serialised `CanisterSig`. - pub fn get_signature_as_cbor( - &self, - seed: &[u8], - message_hash: Hash, - maybe_certified_assets_root_hash: Option, - ) -> Result, String> { - let certificate = data_certificate() - .ok_or("data certificate is only available in query calls".to_string())?; - let witness = self - .witness(seed, message_hash) - .ok_or("missing witness".to_string())?; - - let witness_hash = witness.digest(); - let root_hash = self.root_hash(); - if witness_hash != root_hash { - return Err(format!( - "internal error: signature map computed an invalid hash tree, witness hash is {}, root hash is {}", - hex::encode(witness_hash), - hex::encode(root_hash) - )); - } - - let sigs_tree = labeled(LABEL_SIG, witness); - let tree = match maybe_certified_assets_root_hash { - Some(certified_assets_root_hash) => fork(pruned(certified_assets_root_hash), sigs_tree), - None => sigs_tree, - }; - - let sig = CanisterSig { - certificate: ByteBuf::from(certificate), - tree, - }; - - let mut cbor = serde_cbor::ser::Serializer::new(Vec::new()); - cbor.self_describe().unwrap(); - sig.serialize(&mut cbor).unwrap(); - Ok(cbor.into_inner()) - } - - /// Adds to this map a canister signature for the specified `seed` and `message_hash` - pub fn add_signature(&mut self, seed: &[u8], message_hash: Hash) { - let now = time(); - - self.prune_expired(now); - let expires_at = now.saturating_add(SIGNATURE_EXPIRATION_PERIOD_NS); - self.put(seed, message_hash, expires_at); - } - - pub fn len(&self) -> usize { - self.expiration_queue.len() - } - - pub fn is_empty(&self) -> bool { - self.expiration_queue.is_empty() - } - - pub fn root_hash(&self) -> Hash { - self.certified_map.root_hash() - } - - pub fn witness(&self, seed: &[u8], message_hash: Hash) -> Option { - let seed_hash = hash_bytes(seed); - self.certified_map - .get(&seed_hash[..])? - .get(&message_hash[..])?; - let witness = self - .certified_map - .nested_witness(&seed_hash[..], |nested| nested.witness(&message_hash[..])); - Some(witness) - } -} - -#[cfg(test)] -mod test; diff --git a/src/canister_sig_util/src/signature_map/test.rs b/src/canister_sig_util/src/signature_map/test.rs deleted file mode 100644 index 9719404b2e..0000000000 --- a/src/canister_sig_util/src/signature_map/test.rs +++ /dev/null @@ -1,109 +0,0 @@ -use super::*; -use ic_certification::Hash; -use sha2::{Digest, Sha256}; - -fn hash_bytes(value: impl AsRef<[u8]>) -> Hash { - let mut hasher = Sha256::new(); - hasher.update(value.as_ref()); - hasher.finalize().into() -} - -fn seed(x: u64) -> Hash { - hash_bytes(x.to_be_bytes()) -} - -fn message(x: u64) -> Hash { - hash_bytes(x.to_le_bytes()) -} - -#[test] -fn test_signature_lookup() { - let mut map = SignatureMap::default(); - map.put(&seed(1), message(1), 10); - assert_eq!( - map.witness(&seed(1), message(1)) - .expect("failed to get a witness") - .digest(), - map.root_hash() - ); - assert!(map.witness(&seed(1), message(2)).is_none()); - assert!(map.witness(&seed(2), message(1)).is_none()); - - map.delete(hash_bytes(seed(1)), message(1)); - assert!(map.witness(&seed(1), message(1)).is_none()); -} - -#[test] -fn test_signature_expiration() { - let mut map = SignatureMap::default(); - - map.put(&seed(1), message(1), 10); - map.put(&seed(1), message(2), 20); - map.put(&seed(2), message(1), 15); - map.put(&seed(2), message(2), 25); - - assert_eq!(2, map.prune_expired(/*time now*/ 19)); - assert!(map.witness(&seed(1), message(1)).is_none()); - assert!(map.witness(&seed(2), message(1)).is_none()); - - assert!(map.witness(&seed(1), message(2)).is_some()); - assert!(map.witness(&seed(2), message(2)).is_some()); -} - -#[test] -fn test_signature_expiration_limit() { - let mut map = SignatureMap::default(); - - for i in 0..100 { - map.put(&seed(i), message(i), 10 + i); - } - - assert_eq!(50, map.prune_expired(/*time now*/ 100)); - - for i in 0..50 { - assert!(map.witness(&seed(i), message(i)).is_none()); - } - for i in 50..100 { - assert!(map.witness(&seed(i), message(i)).is_some()); - } -} - -#[test] -fn test_random_modifications() { - use rand::prelude::*; - - let mut map = SignatureMap::default(); - let mut rng = rand::thread_rng(); - let window_size = 5; - - let mut pairs = Vec::new(); - - for round in 1..100 { - let n_seeds = rng.gen_range(0..5); - for _i in 0..n_seeds { - let mut seed = Hash::default(); - rng.fill_bytes(&mut seed); - - let n_messages = rng.gen_range(0..5); - for _k in 0..n_messages { - let mut message_hash = Hash::default(); - rng.fill_bytes(&mut message_hash); - - pairs.push((seed, message_hash)); - map.put(seed.as_slice(), message_hash, round); - } - } - - map.prune_expired(round.saturating_sub(window_size)); - - for (k, v) in pairs.iter() { - if let Some(witness) = map.witness(k, *v) { - assert_eq!( - witness.digest(), - map.root_hash(), - "produced a bad witness: {witness:?}" - ); - } - } - } -} diff --git a/src/canister_tests/Cargo.toml b/src/canister_tests/Cargo.toml index 6a17342d8d..8cef19e255 100644 --- a/src/canister_tests/Cargo.toml +++ b/src/canister_tests/Cargo.toml @@ -15,12 +15,12 @@ serde_bytes.workspace = true sha2.workspace = true internet_identity_interface.workspace = true -vc_util.workspace = true identity_jose = { git = "https://github.com/dfinity/identity.rs.git", rev = "aa510ef7f441848d6c78058fe51ad4ad1d9bd5d8", default-features = false} # All IC deps candid.workspace = true ic-cdk.workspace = true ic-representation-independent-hash.workspace = true +ic-verifiable-credentials.workspace = true pocket-ic.workspace = true url = "2.5.0" diff --git a/src/internet_identity/Cargo.toml b/src/internet_identity/Cargo.toml index a7a03a2d66..1c179b83c2 100644 --- a/src/internet_identity/Cargo.toml +++ b/src/internet_identity/Cargo.toml @@ -33,10 +33,8 @@ ic-cdk-macros.workspace = true ic-certification.workspace = true ic-metrics-encoder.workspace = true ic-stable-structures.workspace = true - -# VC deps -canister_sig_util.workspace = true -vc_util.workspace = true +ic-canister-sig-creation.workspace = true +ic-verifiable-credentials.workspace = true [target.'cfg(all(target_arch = "wasm32", target_vendor = "unknown", target_os = "unknown"))'.dependencies] getrandom = { version = "0.2", features = ["custom"] } diff --git a/src/internet_identity/src/delegation.rs b/src/internet_identity/src/delegation.rs index af36da5a09..7769f7902d 100644 --- a/src/internet_identity/src/delegation.rs +++ b/src/internet_identity/src/delegation.rs @@ -2,16 +2,18 @@ use crate::ii_domain::IIDomain; use crate::stats::event_stats::{ update_event_based_stats, Event, EventData, PrepareDelegationEvent, }; -use crate::{hash, state, update_root_hash, DAY_NS, MINUTE_NS}; +use crate::{state, update_root_hash, DAY_NS, MINUTE_NS}; use candid::Principal; -use canister_sig_util::signature_map::SignatureMap; -use canister_sig_util::CanisterSigPublicKey; +use ic_canister_sig_creation::signature_map::{CanisterSigInputs, SignatureMap}; +use ic_canister_sig_creation::{ + delegation_signature_msg, CanisterSigPublicKey, DELEGATION_SIG_DOMAIN, +}; use ic_cdk::api::time; use ic_cdk::{id, trap}; use ic_certification::Hash; use internet_identity_interface::internet_identity::types::*; use serde_bytes::ByteBuf; -use std::collections::HashMap; +use sha2::{Digest, Sha256}; use std::net::IpAddr; // The expiration used for delegations if none is specified @@ -103,16 +105,12 @@ pub fn get_delegation( check_frontend_length(&frontend); state::assets_and_signatures(|certified_assets, sigs| { - let message_hash = delegation_signature_msg_hash(&Delegation { - pubkey: session_key.clone(), - expiration, - targets: None, - }); - match sigs.get_signature_as_cbor( - &calculate_seed(anchor_number, &frontend), - message_hash, - Some(certified_assets.root_hash()), - ) { + let inputs = CanisterSigInputs { + domain: DELEGATION_SIG_DOMAIN, + seed: &calculate_seed(anchor_number, &frontend), + message: &delegation_signature_msg(&session_key, expiration, None), + }; + match sigs.get_signature_as_cbor(&inputs, Some(certified_assets.root_hash())) { Ok(signature) => GetDelegationResponse::SignedDelegation(SignedDelegation { delegation: Delegation { pubkey: session_key, @@ -149,7 +147,13 @@ fn calculate_seed(anchor_number: AnchorNumber, frontend: &FrontendHostname) -> H blob.push(frontend.bytes().len() as u8); blob.extend(frontend.bytes()); - hash::hash_bytes(blob) + hash_bytes(blob) +} + +fn hash_bytes(value: impl AsRef<[u8]>) -> Hash { + let mut hasher = Sha256::new(); + hasher.update(value.as_ref()); + hasher.finalize().into() } pub(crate) fn der_encode_canister_sig_key(seed: Vec) -> Vec { @@ -157,35 +161,18 @@ pub(crate) fn der_encode_canister_sig_key(seed: Vec) -> Vec { CanisterSigPublicKey::new(my_canister_id, seed).to_der() } -fn delegation_signature_msg_hash(d: &Delegation) -> Hash { - use hash::Value; - - let mut m = HashMap::new(); - m.insert("pubkey", Value::Bytes(d.pubkey.as_slice())); - m.insert("expiration", Value::U64(d.expiration)); - if let Some(targets) = d.targets.as_ref() { - let mut arr = Vec::with_capacity(targets.len()); - for t in targets.iter() { - arr.push(Value::Bytes(t.as_ref())); - } - m.insert("targets", Value::Array(arr)); - } - let map_hash = hash::hash_of_map(m); - hash::hash_with_domain(b"ic-request-auth-delegation", &map_hash) -} - fn add_delegation_signature( sigs: &mut SignatureMap, pk: PublicKey, seed: &[u8], expiration: Timestamp, ) { - let msg_hash = delegation_signature_msg_hash(&Delegation { - pubkey: pk, - expiration, - targets: None, - }); - sigs.add_signature(seed, msg_hash); + let inputs = CanisterSigInputs { + domain: DELEGATION_SIG_DOMAIN, + seed, + message: &delegation_signature_msg(&pk, expiration, None), + }; + sigs.add_signature(&inputs); } pub(crate) fn check_frontend_length(frontend: &FrontendHostname) { diff --git a/src/internet_identity/src/hash.rs b/src/internet_identity/src/hash.rs deleted file mode 100644 index cb0cfe0e26..0000000000 --- a/src/internet_identity/src/hash.rs +++ /dev/null @@ -1,266 +0,0 @@ -//! Provides helper functions to calculate the representation independent hash -//! of structured data. -use ic_certification::Hash; -use serde::{Deserialize, Serialize}; -use sha2::{Digest, Sha256}; -use std::collections::HashMap; -use std::convert::AsRef; - -#[derive(Clone, Serialize, Deserialize)] -pub enum Value<'a> { - Bytes(#[serde(with = "serde_bytes")] &'a [u8]), - String(&'a str), - U64(u64), - Array(Vec>), -} - -pub fn hash_of_map>(map: HashMap) -> Hash { - let mut hashes: Vec> = Vec::new(); - for (key, val) in map.into_iter() { - hashes.push(hash_key_val(key.as_ref(), val)); - } - - // Computes hash by first sorting by "field name" hash, which is the - // same as sorting by concatenation of H(field name) · H(field value) - // (although in practice it's actually more stable in the presence of - // duplicated field names). Then concatenate all the hashes. - hashes.sort(); - - let mut hasher = Sha256::new(); - for hash in hashes { - hasher.update(&hash); - } - - hasher.finalize().into() -} - -pub fn hash_with_domain(sep: &[u8], bytes: &[u8]) -> Hash { - let mut hasher = Sha256::new(); - let buf = [sep.len() as u8]; - hasher.update(buf); - hasher.update(sep); - hasher.update(bytes); - hasher.finalize().into() -} - -fn hash_key_val(key: &str, val: Value<'_>) -> Vec { - let mut key_hash = hash_string(key).to_vec(); - let val_hash = hash_val(val); - key_hash.extend_from_slice(&val_hash[..]); - key_hash -} - -pub fn hash_string(value: &str) -> Hash { - hash_bytes(value.as_bytes()) -} - -pub fn hash_bytes(value: impl AsRef<[u8]>) -> Hash { - let mut hasher = Sha256::new(); - hasher.update(value.as_ref()); - hasher.finalize().into() -} - -fn hash_u64(value: u64) -> Hash { - // We need at most ⌈ 64 / 7 ⌉ = 10 bytes to encode a 64 bit - // integer in LEB128. - let mut buf = [0u8; 10]; - let mut n = value; - let mut i = 0; - - loop { - let byte = (n & 0x7f) as u8; - n >>= 7; - - if n == 0 { - buf[i] = byte; - break; - } else { - buf[i] = byte | 0x80; - i += 1; - } - } - - hash_bytes(&buf[..=i]) -} - -// Arrays encoded as the concatenation of the hashes of the encodings of the -// array elements. -fn hash_array(elements: Vec>) -> Hash { - let mut hasher = Sha256::new(); - elements - .into_iter() - // Hash the encoding of all the array elements. - .for_each(|e| hasher.update(&hash_val(e)[..])); - hasher.finalize().into() // hash the concatenation of the hashes. -} - -fn hash_val(val: Value<'_>) -> Hash { - match val { - Value::String(string) => hash_string(string), - Value::Bytes(bytes) => hash_bytes(bytes), - Value::U64(integer) => hash_u64(integer), - Value::Array(elements) => hash_array(elements), - } -} - -#[cfg(test)] -mod tests { - use super::*; - use hex_literal::hex; - - #[test] - fn message_id_icf_key_val_reference_1() { - assert_eq!( - hash_key_val("request_type", Value::String("call")), - hex!( - " - 769e6f87bdda39c859642b74ce9763cdd37cb1cd672733e8c54efaa33ab78af9 - 7edb360f06acaef2cc80dba16cf563f199d347db4443da04da0c8173e3f9e4ed - " - ) - .to_vec() - ); - } - - #[test] - fn message_id_u64_id_reference() { - assert_eq!( - // LEB128: 0x00 - hash_u64(0), - hex!("6e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d"), - ); - - assert_eq!( - // LEB128: 0xd2 0x09 - hash_u64(1234), - hex!("8b37fd3ebbe6396a89ed8563dd0cc55927ac90138950460c77cffeb55cf63810"), - ); - - assert_eq!( - // LEB128 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0x01 - hash_u64(0xffff_ffff_ffff_ffff), - hex!("51672ea45f3539654bf9193f4ff763d90022eee7df5f5b76353d6f11a9eaccec"), - ) - } - - #[test] - fn message_id_string_reference_1() { - assert_eq!( - hash_string("request_type"), - hex!("769e6f87bdda39c859642b74ce9763cdd37cb1cd672733e8c54efaa33ab78af9"), - ); - } - - #[test] - fn message_id_string_reference_2() { - assert_eq!( - hash_string("call"), - hex!("7edb360f06acaef2cc80dba16cf563f199d347db4443da04da0c8173e3f9e4ed"), - ); - } - - #[test] - fn message_id_string_reference_3() { - assert_eq!( - hash_string("callee"), - hex!("92ca4c0ced628df1e7b9f336416ead190bd0348615b6f71a64b21d1b68d4e7e2"), - ); - } - - #[test] - fn message_id_string_reference_4() { - assert_eq!( - hash_string("method_name"), - hex!("293536232cf9231c86002f4ee293176a0179c002daa9fc24be9bb51acdd642b6"), - ); - } - - #[test] - fn message_id_string_reference_5() { - assert_eq!( - hash_string("hello"), - hex!("2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"), - ); - } - - #[test] - fn message_id_string_reference_6() { - assert_eq!( - hash_string("arg"), - hex!("b25f03dedd69be07f356a06fe35c1b0ddc0de77dcd9066c4be0c6bbde14b23ff"), - ); - } - - #[test] - fn message_id_array_reference_1() { - assert_eq!( - hash_array(vec![Value::String("a")]), - // hash(hash("a")) - hex!("bf5d3affb73efd2ec6c36ad3112dd933efed63c4e1cbffcfa88e2759c144f2d8"), - ); - } - - #[test] - fn message_id_array_reference_2() { - assert_eq!( - hash_array(vec![Value::String("a"), Value::String("b"),]), - // hash(concat(hash("a"), hash("b")) - hex!("e5a01fee14e0ed5c48714f22180f25ad8365b53f9779f79dc4a3d7e93963f94a"), - ); - } - - #[test] - fn message_id_array_reference_3() { - assert_eq!( - hash_array(vec![ - Value::Bytes(&[97][..]), // "a" as a byte string. - Value::String("b"), - ]), - // hash(concat(hash("a"), hash("b")) - hex!("e5a01fee14e0ed5c48714f22180f25ad8365b53f9779f79dc4a3d7e93963f94a"), - ); - } - - #[test] - fn message_id_array_reference_4() { - assert_eq!( - hash_array(vec![Value::Array(vec![Value::String("a")])]), - // hash(hash(hash("a")) - hex!("eb48bdfa15fc43dbea3aabb1ee847b6e69232c0f0d9705935e50d60cce77877f"), - ); - } - - #[test] - fn message_id_array_reference_5() { - assert_eq!( - hash_array(vec![Value::Array(vec![ - Value::String("a"), - Value::String("b") - ])]), - // hash(hash(concat(hash("a"), hash("b"))) - hex!("029fd80ca2dd66e7c527428fc148e812a9d99a5e41483f28892ef9013eee4a19"), - ); - } - - #[test] - fn message_id_array_reference_6() { - assert_eq!( - hash_array(vec![ - Value::Array(vec![Value::String("a"), Value::String("b")]), - Value::Bytes(&[97][..]), // "a" in bytes - ]), - // hash(concat(hash(concat(hash("a"), hash("b")), hash(100)) - hex!("aec3805593d9ec6df50da070597f73507050ce098b5518d0456876701ada7bb7"), - ); - } - - #[test] - fn message_id_bytes_reference() { - assert_eq!( - // D I D L \0 \253 *" - // 68 73 68 76 0 253 42 - hash_bytes(&[68, 73, 68, 76, 0, 253, 42][..]), - hex!("6c0b2ae49718f6995c02ac5700c9c789d7b7862a0d53e6d40a73f1fcd2f70189") - ); - } -} diff --git a/src/internet_identity/src/http.rs b/src/internet_identity/src/http.rs index a74df67c5c..2c3f0e73e0 100644 --- a/src/internet_identity/src/http.rs +++ b/src/internet_identity/src/http.rs @@ -1,6 +1,6 @@ use crate::http::metrics::metrics; use crate::state; -use canister_sig_util::signature_map::LABEL_SIG; +use ic_canister_sig_creation::signature_map::LABEL_SIG; use ic_certification::{labeled_hash, pruned}; use internet_identity_interface::http_gateway::{HeaderField, HttpRequest, HttpResponse}; use serde_bytes::ByteBuf; diff --git a/src/internet_identity/src/main.rs b/src/internet_identity/src/main.rs index 3f2975ddf2..563ca0a9bf 100644 --- a/src/internet_identity/src/main.rs +++ b/src/internet_identity/src/main.rs @@ -9,7 +9,7 @@ use authz_utils::{ anchor_operation_with_authz_check, check_authorization, check_authz_and_record_activity, }; use candid::{candid_method, Principal}; -use canister_sig_util::signature_map::LABEL_SIG; +use ic_canister_sig_creation::signature_map::LABEL_SIG; use ic_cdk::api::{caller, set_certified_data, trap}; use ic_cdk::call; use ic_cdk_macros::{init, post_upgrade, pre_upgrade, query, update}; @@ -32,7 +32,6 @@ mod authz_utils; /// Type conversions between internal and external types. mod conversions; mod delegation; -mod hash; mod http; mod ii_domain; mod state; diff --git a/src/internet_identity/src/state.rs b/src/internet_identity/src/state.rs index f6daa8f36e..b254682795 100644 --- a/src/internet_identity/src/state.rs +++ b/src/internet_identity/src/state.rs @@ -10,7 +10,7 @@ use crate::storage::MAX_ENTRIES; use crate::{random_salt, Storage}; use asset_util::CertifiedAssets; use candid::{CandidType, Deserialize}; -use canister_sig_util::signature_map::SignatureMap; +use ic_canister_sig_creation::signature_map::SignatureMap; use ic_cdk::trap; use ic_stable_structures::DefaultMemoryImpl; use internet_identity_interface::internet_identity::types::*; diff --git a/src/internet_identity/src/vc_mvp.rs b/src/internet_identity/src/vc_mvp.rs index 182a12b883..185c69d1e2 100644 --- a/src/internet_identity/src/vc_mvp.rs +++ b/src/internet_identity/src/vc_mvp.rs @@ -1,23 +1,23 @@ use crate::delegation::check_frontend_length; use crate::{delegation, random_salt, state, update_root_hash, MINUTE_NS}; -use std::collections::HashMap; - use candid::Principal; -use canister_sig_util::CanisterSigPublicKey; +use ic_canister_sig_creation::signature_map::CanisterSigInputs; +use ic_canister_sig_creation::CanisterSigPublicKey; use ic_cdk::api::time; use ic_certification::Hash; +use ic_verifiable_credentials::issuer_api::{ArgumentValue, CredentialSpec}; +use ic_verifiable_credentials::{ + build_credential_jwt, canister_sig_pk_from_vc_signing_input, did_for_principal, + vc_signing_input, vc_signing_input_to_jws, AliasTuple, CredentialParams, + II_CREDENTIAL_URL_PREFIX, II_ISSUER_URL, VC_SIGNING_INPUT_DOMAIN, +}; use internet_identity_interface::internet_identity::types::vc_mvp::{ GetIdAliasError, IdAliasCredentials, PreparedIdAlias, SignedIdAlias, }; use internet_identity_interface::internet_identity::types::{FrontendHostname, IdentityNumber}; use serde_bytes::ByteBuf; use sha2::{Digest, Sha256}; -use vc_util::issuer_api::{ArgumentValue, CredentialSpec}; -use vc_util::{ - build_credential_jwt, canister_sig_pk_from_vc_signing_input, did_for_principal, - vc_signing_input, vc_signing_input_hash, vc_signing_input_to_jws, AliasTuple, CredentialParams, - II_CREDENTIAL_URL_PREFIX, II_ISSUER_URL, -}; +use std::collections::HashMap; // The expiration of id_alias verifiable credentials. const ID_ALIAS_VC_EXPIRATION_PERIOD_NS: u64 = 15 * MINUTE_NS; @@ -54,8 +54,16 @@ pub async fn prepare_id_alias( vc_signing_input(&id_alias_credential_jwt(&issuer_tuple), &canister_sig_pk) .expect("failed getting signing_input"); state::signature_map_mut(|sigs| { - sigs.add_signature(seed.as_ref(), vc_signing_input_hash(&rp_signing_input)); - sigs.add_signature(seed.as_ref(), vc_signing_input_hash(&issuer_signing_input)); + sigs.add_signature(&CanisterSigInputs { + domain: VC_SIGNING_INPUT_DOMAIN, + seed: &seed, + message: &rp_signing_input, + }); + sigs.add_signature(&CanisterSigInputs { + domain: VC_SIGNING_INPUT_DOMAIN, + seed: &seed, + message: &issuer_signing_input, + }); }); update_root_hash(); @@ -88,22 +96,26 @@ pub fn get_id_alias( let id_rp = delegation::get_principal(identity_number, dapps.relying_party.clone()); let id_issuer = delegation::get_principal(identity_number, dapps.issuer.clone()); - let rp_alias_msg_hash = vc_signing_input_hash(rp_id_alias_jwt.as_bytes()); + let rp_sig_inputs = CanisterSigInputs { + domain: VC_SIGNING_INPUT_DOMAIN, + seed, + message: rp_id_alias_jwt.as_bytes(), + }; let rp_sig = sigs - .get_signature_as_cbor(seed, rp_alias_msg_hash, Some(cert_assets.root_hash())) + .get_signature_as_cbor(&rp_sig_inputs, Some(cert_assets.root_hash())) .map_err(|err| { GetIdAliasError::NoSuchCredentials(format!("rp_sig not found: {}", err)) })?; let rp_jws = vc_signing_input_to_jws(rp_id_alias_jwt.as_bytes(), &rp_sig) .expect("failed constructing rp JWS"); - let issuer_id_alias_msg_hash = vc_signing_input_hash(issuer_id_alias_jwt.as_bytes()); + let issuer_sig_inputs = CanisterSigInputs { + domain: VC_SIGNING_INPUT_DOMAIN, + seed, + message: issuer_id_alias_jwt.as_bytes(), + }; let issuer_sig = sigs - .get_signature_as_cbor( - seed, - issuer_id_alias_msg_hash, - Some(cert_assets.root_hash()), - ) + .get_signature_as_cbor(&issuer_sig_inputs, Some(cert_assets.root_hash())) .map_err(|err| { GetIdAliasError::NoSuchCredentials(format!("issuer_sig not found: {}", err)) })?; diff --git a/src/internet_identity/tests/integration/vc_mvp.rs b/src/internet_identity/tests/integration/vc_mvp.rs index 3c66e636b8..7e5c08aab5 100644 --- a/src/internet_identity/tests/integration/vc_mvp.rs +++ b/src/internet_identity/tests/integration/vc_mvp.rs @@ -1,8 +1,9 @@ //! Tests related to prepare_id_alias and get_id_alias canister calls. -use canister_sig_util::{extract_raw_root_pk_from_der, CanisterSigPublicKey}; use canister_tests::api::internet_identity as api; use canister_tests::flows; use canister_tests::framework::*; +use ic_canister_sig_creation::{extract_raw_root_pk_from_der, CanisterSigPublicKey}; +use ic_verifiable_credentials::verify_credential_jws_with_canister_id; use identity_jose::jwk::JwkType; use identity_jose::jws::Decoder; use identity_jose::jwu::encode_b64; @@ -12,7 +13,6 @@ use internet_identity_interface::internet_identity::types::vc_mvp::{ use internet_identity_interface::internet_identity::types::FrontendHostname; use pocket_ic::CallError; use std::ops::Deref; -use vc_util::verify_credential_jws_with_canister_id; fn verify_canister_sig_pk(credential_jws: &str, canister_sig_pk_der: &[u8]) { let decoder: Decoder = Decoder::new(); diff --git a/src/sig-verifier-js/Cargo.toml b/src/sig-verifier-js/Cargo.toml index 2afb9d1f2c..ef73684add 100644 --- a/src/sig-verifier-js/Cargo.toml +++ b/src/sig-verifier-js/Cargo.toml @@ -8,10 +8,10 @@ edition = "2021" crate-type = ["cdylib", "rlib"] [dependencies] +ic-canister-sig-creation.workspace = true ic-crypto-standalone-sig-verifier.workspace = true ic-types.workspace = true candid = "0.10" -canister_sig_util = { path = "../canister_sig_util" } hex = { version = "0.4", features = ["serde"] } serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0.107", features = ["std"] } diff --git a/src/sig-verifier-js/src/lib.rs b/src/sig-verifier-js/src/lib.rs index d72e708f9e..5818ad6e01 100644 --- a/src/sig-verifier-js/src/lib.rs +++ b/src/sig-verifier-js/src/lib.rs @@ -1,5 +1,7 @@ use candid::Principal; -use canister_sig_util::{delegation_signature_msg, hash_bytes, CanisterSigPublicKey}; +use ic_canister_sig_creation::{ + delegation_signature_msg, hash_bytes, CanisterSigPublicKey, DELEGATION_SIG_DOMAIN, +}; use ic_crypto_standalone_sig_verifier as ic_sig_ver; use ic_crypto_standalone_sig_verifier::KeyBytesContentType; use ic_types::crypto::threshold_sig::IcRootOfTrust; @@ -136,10 +138,13 @@ pub fn validate_delegation_and_get_principal( // `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 root_of_trust = root_public_key_from_bytes(ic_root_public_key_raw)?; - let message = delegation_signature_msg( - delegation.pubkey.as_slice(), - delegation.expiration(), - delegation.targets.as_ref(), + let message = msg_with_domain( + DELEGATION_SIG_DOMAIN, + &delegation_signature_msg( + delegation.pubkey.as_slice(), + delegation.expiration(), + delegation.targets.as_ref(), + ), ); println!("signed bytes: {}", hex::encode(message.as_slice())); println!( @@ -157,6 +162,13 @@ pub fn validate_delegation_and_get_principal( Ok(Principal::self_authenticating(signed_delegation_chain.publicKey.as_slice()).to_text()) } +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 +} + // A custom definition of Delegation, as we need to parse from hex-values provided in JSON from JS. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] pub struct Delegation { @@ -203,7 +215,7 @@ fn root_public_key_from_bytes(ic_root_public_key: &[u8]) -> Result, -} - -#[derive(Clone, Debug, CandidType, Deserialize, Eq, PartialEq)] -pub struct PreparedCredentialData { - pub prepared_context: Option, -} - -#[derive(Clone, Debug, CandidType, Deserialize, Eq, PartialEq)] -pub struct IssuedCredentialData { - pub vc_jws: String, -} - -#[derive(Eq, PartialEq, Clone, Debug, CandidType, Deserialize)] -pub enum ArgumentValue { - String(String), - Int(i32), -} - -impl Display for ArgumentValue { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match &self { - ArgumentValue::String(s) => write!(f, "'{}'", s), - ArgumentValue::Int(i) => write!(f, "{}", i), - } - } -} - -impl From for Value { - fn from(argument_value: ArgumentValue) -> Self { - match argument_value { - ArgumentValue::String(s) => Value::String(s), - ArgumentValue::Int(i) => Value::Number(Number::from(i)), - } - } -} - -impl PartialEq for ArgumentValue { - fn eq(&self, other: &Value) -> bool { - match self { - ArgumentValue::String(ls) => { - if let Some(rs) = other.as_str() { - ls.eq(rs) - } else { - false - } - } - ArgumentValue::Int(li) => { - if let Some(ri) = other.as_i64() { - (*li as i64) == ri - } else { - false - } - } - } - } -} - -#[derive(Clone, Debug, CandidType, Deserialize, Eq, PartialEq)] -pub struct CredentialSpec { - pub credential_type: String, - pub arguments: Option>, -} - -#[derive(Clone, Debug, CandidType, Deserialize, Eq, PartialEq)] -pub struct ManifestRequest {} - -#[derive(Clone, Debug, CandidType, Deserialize, Eq, PartialEq)] -pub enum ManifestResponse { - Ok(ManifestData), - Err(String), -} - -#[derive(Clone, Debug, CandidType, Deserialize, Eq, PartialEq)] -pub struct ManifestData {} - -#[derive(Clone, Debug, CandidType, Deserialize, Eq, PartialEq)] -pub struct Icrc21VcConsentMessageRequest { - pub credential_spec: CredentialSpec, - pub preferences: Icrc21ConsentPreferences, -} - -#[derive(Clone, Debug, CandidType, Deserialize, Eq, PartialEq)] -pub struct Icrc21ConsentPreferences { - pub language: String, -} - -#[derive(Clone, Debug, CandidType, Deserialize, Eq, PartialEq)] -pub struct Icrc21ErrorInfo { - pub description: String, -} - -#[derive(Clone, Debug, CandidType, Deserialize, Eq, PartialEq)] -pub enum Icrc21Error { - UnsupportedCanisterCall(Icrc21ErrorInfo), - ConsentMessageUnavailable(Icrc21ErrorInfo), - GenericError { - error_code: Nat, - description: String, - }, -} - -#[derive(Clone, Debug, CandidType, Deserialize, Eq, PartialEq)] -pub struct Icrc21ConsentInfo { - pub consent_message: String, - pub language: String, -} - -#[derive(Clone, Debug, CandidType, Deserialize, Eq, PartialEq)] -pub struct DerivationOriginRequest { - pub frontend_hostname: String, -} - -#[derive(Clone, Debug, CandidType, Deserialize, Eq, PartialEq)] -pub struct DerivationOriginData { - pub origin: String, -} - -#[derive(Clone, Debug, CandidType, Deserialize, Eq, PartialEq)] -pub enum DerivationOriginError { - UnsupportedOrigin(String), - Internal(String), -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn should_display_argument_values() { - assert_eq!("42", format!("{}", ArgumentValue::Int(42))); - assert_eq!("0", format!("{}", ArgumentValue::Int(0))); - assert_eq!("-7", format!("{}", ArgumentValue::Int(-7))); - assert_eq!("''", format!("{}", ArgumentValue::String("".to_string()))); - assert_eq!( - "'some string'", - format!("{}", ArgumentValue::String("some string".to_string())) - ); - } - - #[test] - fn should_correctly_compare_argument_values() { - assert_eq!(ArgumentValue::Int(42), Value::from(42)); - assert_eq!(ArgumentValue::Int(123456789), Value::from(123456789)); - assert_eq!(ArgumentValue::Int(0), Value::from(0)); - - assert_ne!(ArgumentValue::Int(42), Value::from(11)); - assert_ne!(ArgumentValue::Int(42), Value::from("some string")); - assert_ne!(ArgumentValue::Int(42), Value::from(true)); - assert_ne!(ArgumentValue::Int(42), Value::from(vec![1, 2, 3])); - - assert_eq!( - ArgumentValue::String("same string".to_string()), - Value::from("same string") - ); - let long_string = "this is a bit longer string just for testing purposes"; - assert_eq!( - ArgumentValue::String(long_string.to_string()), - Value::from(long_string) - ); - assert_eq!(ArgumentValue::String("".to_string()), Value::from("")); - - assert_ne!( - ArgumentValue::String("some string".to_string()), - Value::from("different") - ); - assert_ne!( - ArgumentValue::String("a string".to_string()), - Value::from(42) - ); - assert_ne!( - ArgumentValue::String("a string".to_string()), - Value::from(true) - ); - assert_ne!( - ArgumentValue::String("a string".to_string()), - Value::from(vec![1, 2, 3]) - ); - } -} diff --git a/src/vc_util/src/lib.rs b/src/vc_util/src/lib.rs deleted file mode 100644 index f3adc16fc0..0000000000 --- a/src/vc_util/src/lib.rs +++ /dev/null @@ -1,1528 +0,0 @@ -use crate::issuer_api::CredentialSpec; -use candid::Principal; -use canister_sig_util::{extract_raw_canister_sig_pk_from_der, CanisterSigPublicKey}; -use ic_certification::Hash; -use ic_crypto_standalone_sig_verifier::verify_canister_sig; -use ic_types::crypto::threshold_sig::IcRootOfTrust; -use identity_core::common::{Timestamp, Url}; -use identity_core::convert::FromJson; -use identity_credential::credential::{Credential, CredentialBuilder, Jwt, Subject}; -use identity_credential::error::Error as JwtVcError; -use identity_credential::presentation::{ - JwtPresentationOptions, Presentation, PresentationBuilder, PresentationJwtClaims, -}; -use identity_credential::validator::JwtValidationError; -use identity_jose::jwk::{Jwk, JwkParams, JwkParamsOct, JwkType}; -use identity_jose::jws::{ - CompactJwsEncoder, Decoder, JwsAlgorithm, JwsHeader, SignatureVerificationError, - SignatureVerificationErrorKind, -}; -use identity_jose::jwt::JwtClaims; -use identity_jose::jwu::{decode_b64, encode_b64}; -use serde_json::{json, Map, Value}; -use sha2::{Digest, Sha256}; -use std::ops::{Add, Deref, DerefMut}; - -pub mod issuer_api; - -pub const II_CREDENTIAL_URL_PREFIX: &str = "data:text/plain;charset=UTF-8,"; -pub const II_ISSUER_URL: &str = "https://identity.ic0.app/"; -pub const VC_SIGNING_INPUT_DOMAIN: &[u8; 26] = b"iccs_verifiable_credential"; -pub const DID_ICP_PREFIX: &str = "did:icp:"; - -/// A pair of identities, that denote the same user. -/// Used in attribute sharing flow to maintain II's unlinkability of identities. -#[derive(Debug, Eq, PartialEq)] -pub struct AliasTuple { - /// A temporary identity, used in attribute sharing flow. - pub id_alias: Principal, - /// An identity under which a user is known to a dapp. - pub id_dapp: Principal, -} - -#[derive(Debug, Eq, PartialEq)] -/// Parties that signed credentials contained in a verifiable presentation. -pub struct VcFlowSigners { - pub ii_canister_id: Principal, - pub ii_origin: String, - pub issuer_canister_id: Principal, - pub issuer_origin: String, -} - -#[derive(Debug)] -pub enum CredentialVerificationError { - InvalidJws(SignatureVerificationError), - InvalidClaims(JwtValidationError), -} - -#[derive(Debug)] -pub enum PresentationVerificationError { - InvalidPresentationJwt(String), - InvalidIdAliasCredential(CredentialVerificationError), - InvalidRequestedCredential(CredentialVerificationError), - Unknown(String), -} - -/// Returns the effective bytes that will be signed when computing a canister signature for -/// the given JWT-credential, verifiable via the specified public key. -pub fn vc_signing_input( - credential_jwt: &str, - canister_sig_pk: &CanisterSigPublicKey, -) -> Result, String> { - let encoder = jws_encoder(credential_jwt, canister_sig_pk)?; - Ok(encoder.signing_input().to_vec()) -} - -/// Computes and returns SHA-256 hash of the given `signing_input` prefixed with -/// `length(VC_SIGNING_INPUT_DOMAIN) · VC_SIGNING_INPUT_DOMAIN` -/// (for domain separation), where `length(a)` is the length of byte-array `a`, -/// and `·` denotes concatenation of bytes. -pub fn vc_signing_input_hash(signing_input: &[u8]) -> Hash { - let mut hasher = Sha256::new(); - let buf = [VC_SIGNING_INPUT_DOMAIN.len() as u8]; - hasher.update(buf); - hasher.update(VC_SIGNING_INPUT_DOMAIN); - hasher.update(signing_input); - hasher.finalize().into() -} - -/// Constructs and returns a JWS (a signed JWT) from the given components. -/// Specifically, it constructs a JWS-header with the given `canister_sig_pk`, and -/// packages `credential_jwt`, the header, and the signature `sig` into a JWS. -/// The given signature should be created over the bytes returned by `vc_signing_input()`. -/// Note: the validity of the signature is not checked. -pub fn vc_jwt_to_jws( - credential_jwt: &str, - canister_sig_pk: &CanisterSigPublicKey, - sig: &[u8], -) -> Result { - let encoder = jws_encoder(credential_jwt, canister_sig_pk)?; - Ok(encoder.into_jws(sig)) -} - -/// Constructs and returns a JWS (a signed JWT) from the given components. -/// The given `signing_input` should be a value returned by `vc_signing_input()` -/// (which already contains a header with canister signatures public key), and -/// `sig` should be valid canister signature over `signing_input`. -/// Note: the validity of the signature is not checked. -pub fn vc_signing_input_to_jws(signing_input: &[u8], sig: &[u8]) -> Result { - let decoder = Decoder::new(); - let bytes_with_separators = [signing_input, &[b'.']].concat(); - let parsed_signing_input = decoder - .decode_compact_serialization(&bytes_with_separators, None) - .unwrap(); - let header = parsed_signing_input - .protected_header() - .expect("internal: failed getting protected header"); - - let encoder: CompactJwsEncoder = CompactJwsEncoder::new(parsed_signing_input.claims(), header) - .map_err(|e| format!("internal: failed creating JWS encoder: {:?}", e))?; - Ok(encoder.into_jws(sig)) -} - -/// Extracts the canister signature public key from the given signing_input, which is the -/// effective byte array that is signed when creating a JWS from a JWT. -/// (essentially, it is a serialized JWT with JWS header, yet without a signature, -/// cf. `vc_signing_input()`-function above). -pub fn canister_sig_pk_from_vc_signing_input( - signing_input: &[u8], -) -> Result { - let decoder = Decoder::new(); - let bytes_with_separators = [signing_input, &[b'.']].concat(); - let parsed_signing_input = decoder - .decode_compact_serialization(&bytes_with_separators, None) - .map_err(|e| format!("internal: failed parsing signing_input: {:?}", e))?; - let header = parsed_signing_input - .protected_header() - .expect("internal: failed getting protected header"); - let canister_sig_pk_raw = get_canister_sig_pk_raw(header) - .map_err(|e| format!("internal: failed getting canister_sig_pk_raw: {:?}", e))?; - CanisterSigPublicKey::try_from_raw(&canister_sig_pk_raw) - .map_err(|e| format!("internal: failed parsing canister_sig_pk: {}", e)) -} - -/// Returns a DID for the given `principal`. -pub fn did_for_principal(principal: Principal) -> String { - DID_ICP_PREFIX.to_string().add(&principal.to_string()) -} - -/// Returns a `principal` for the given DID. -pub fn principal_for_did(did: &str) -> Result { - if !did.starts_with(DID_ICP_PREFIX) { - return Err(format!( - "invalid DID: {}, expected prefix {}", - did, DID_ICP_PREFIX - )); - } - Principal::from_text(did.trim_start_matches(DID_ICP_PREFIX)) - .map_err(|e| format!("failed to parse DID: {}", e)) -} - -/// Verifies the given JWS-credential as an id_alias-VC and extracts the alias tuple. -/// Performs both the cryptographic verification of the credential, and the semantic -/// validation of the claims in the VC. -pub fn get_verified_id_alias_from_jws( - credential_jws: &str, - expected_vc_subject: &Principal, - signing_canister_id: &Principal, - root_pk_raw: &[u8], - current_time_ns: u128, -) -> Result { - let claims = verify_credential_jws_with_canister_id( - credential_jws, - signing_canister_id, - root_pk_raw, - current_time_ns, - ) - .map_err(CredentialVerificationError::InvalidJws)?; - validate_claim("iss", II_ISSUER_URL, claims.iss()) - .map_err(CredentialVerificationError::InvalidClaims)?; - let alias_tuple = - extract_id_alias(&claims).map_err(CredentialVerificationError::InvalidClaims)?; - if *expected_vc_subject != alias_tuple.id_dapp { - return Err(CredentialVerificationError::InvalidClaims( - inconsistent_jwt_claims("unexpected vc subject"), - )); - } - Ok(alias_tuple) -} - -/// Verifies the specified JWS credential cryptographically and checks that the signature was -/// created by the provided canister. -/// DOES NOT perform semantic validation of the claims in the credential. -pub fn verify_credential_jws_with_canister_id( - credential_jws: &str, - signing_canister_id: &Principal, - root_pk_raw: &[u8], - current_time_ns: u128, -) -> Result, SignatureVerificationError> { - ///// Decode JWS. - let decoder: Decoder = Decoder::new(); - let jws = decoder - .decode_compact_serialization(credential_jws.as_ref(), None) - .map_err(|e| invalid_signature_err(&format!("credential JWS parsing error: {}", e)))?; - let signature = jws.decoded_signature(); - let message = signing_input_with_prefix(jws.signing_input()); - let jws_header = jws - .protected_header() - .ok_or(invalid_signature_err("missing JWS header"))?; - let canister_sig_pk_raw = get_canister_sig_pk_raw(jws_header)?; - let canister_sig_pk = CanisterSigPublicKey::try_from_raw(canister_sig_pk_raw.as_slice()) - .map_err(|e| key_decoding_err(&format!("invalid canister sig public key: {}", e)))?; - - if signing_canister_id != &canister_sig_pk.canister_id { - return Err(invalid_signature_err(&format!( - "canister sig canister id does not match provided canister id: expected {}, got {}", - signing_canister_id.to_text(), - canister_sig_pk.canister_id.to_text() - ))); - } - - let root_pk_bytes: [u8; 96] = root_pk_raw - .try_into() - .map_err(|e| key_decoding_err(&format!("invalid root public key: {}", e)))?; - let root_pk = IcRootOfTrust::from(root_pk_bytes); - verify_canister_sig(&message, signature, canister_sig_pk_raw.as_slice(), root_pk) - .map_err(|e| invalid_signature_err(&format!("signature verification error: {}", e)))?; - - let claims: JwtClaims = serde_json::from_slice(jws.claims()) - .map_err(|e| invalid_signature_err(&format!("failed parsing JSON JWT claims: {}", e)))?; - validate_expiration(claims.exp(), current_time_ns) - .map_err(|e| invalid_signature_err(&format!("credential expired: {}", e)))?; - Ok(claims) -} - -fn parse_verifiable_presentation_jwt(vp_jwt: &str) -> Result, String> { - let decoder = Decoder::new(); - let jwt = decoder - .decode_compact_serialization(vp_jwt.as_ref(), None) - .map_err(|_| "failed decoding compact jwt serialization")?; - let presentation_claims = PresentationJwtClaims::from_json_slice(&jwt.claims()) - .map_err(|_| "failed parsing presentation claims")?; - Ok(presentation_claims - .try_into_presentation() - .map_err(|_| "failed exporting presentation")?) -} - -/// Verifies the specified JWT presentation cryptographically, which should contain exactly -/// two verifiable credentials (in the order specified): -/// 1. An "Id alias" credential which links the effective subject of the VP to a temporary id_alias. -/// This credential should be signed by canister vc_flow_parties.ii_canister_id. -/// 2. An actual credential requested by a user. The subject of this credential is id_alias, -/// and it should be signed by canister vc_flow_parties.issuer_canister_id -/// -/// Verifies that the subject of the first credential matches `effective_vc_subject`. -/// Returns the verified `effective_vc_subject` with id_alias, and the claims from the requested credential. -/// DOES NOT perform semantic validation of the returned claims. -pub fn verify_ii_presentation_jwt_with_canister_ids( - vp_jwt: &str, - effective_vc_subject: Principal, - vc_flow_signers: &VcFlowSigners, - root_pk_raw: &[u8], - current_time_ns: u128, -) -> Result<(AliasTuple, JwtClaims), PresentationVerificationError> { - let presentation = parse_verifiable_presentation_jwt(vp_jwt) - .map_err(PresentationVerificationError::InvalidPresentationJwt)?; - if presentation.verifiable_credential.len() != 2 { - return Err(PresentationVerificationError::InvalidPresentationJwt( - "expected exactly two verifiable credentials".to_string(), - )); - } - let id_alias_vc_jws = presentation.verifiable_credential.first().ok_or( - PresentationVerificationError::Unknown("missing id_alias vc".to_string()), - )?; - let alias_tuple = get_verified_id_alias_from_jws( - id_alias_vc_jws.as_str(), - &effective_vc_subject, - &vc_flow_signers.ii_canister_id, - root_pk_raw, - current_time_ns, - ) - .map_err(PresentationVerificationError::InvalidIdAliasCredential)?; - let requested_vc_jws = - presentation - .verifiable_credential - .get(1) - .ok_or(PresentationVerificationError::Unknown( - "missing requested vc".to_string(), - ))?; - let claims = verify_credential_jws_with_canister_id( - requested_vc_jws.as_str(), - &vc_flow_signers.issuer_canister_id, - root_pk_raw, - current_time_ns, - ) - .map_err(|e| { - PresentationVerificationError::InvalidRequestedCredential( - CredentialVerificationError::InvalidJws(e), - ) - })?; - let requested_vc_subject = extract_subject(&claims).map_err(|e| { - PresentationVerificationError::InvalidRequestedCredential( - CredentialVerificationError::InvalidClaims(e), - ) - })?; - if requested_vc_subject != alias_tuple.id_alias { - return Err(PresentationVerificationError::InvalidPresentationJwt( - format!( - "subject does not match id_alias: expected {}, got {}", - alias_tuple.id_alias, requested_vc_subject - ) - .to_string(), - )); - } - Ok((alias_tuple, claims)) -} - -fn extract_vc_claims(claims: &JwtClaims) -> Result, JwtValidationError> { - let vc_claims = claims - .custom() - .ok_or(inconsistent_jwt_claims( - "missing custom claims in JWT claims", - ))? - .as_object() - .ok_or(inconsistent_jwt_claims( - "malformed custom claims in JWT claims", - ))? - .get("vc") - .ok_or(inconsistent_jwt_claims( - "missing vc claims in JWT custom claims", - ))? - .as_object() - .ok_or(inconsistent_jwt_claims( - "malformed vc claims in JWT custom claims", - ))?; - Ok(vc_claims.clone()) -} - -/// Validates the provided presentation `vp_jwt`, both cryptographically and semantically: -/// - verifies the cryptographic consistency via `verify_ii_presentation_jwt_with_canister_ids(...)`. -/// - checks that the claims from the presentation match the credential spec `vc_spec`. -pub fn validate_ii_presentation_and_claims( - vp_jwt: &str, - effective_vc_subject: Principal, - vc_flow_signers: &VcFlowSigners, - vc_spec: &CredentialSpec, - root_pk_raw: &[u8], - current_time_ns: u128, -) -> Result<(), PresentationVerificationError> { - let (_alias_tuple, claims) = verify_ii_presentation_jwt_with_canister_ids( - vp_jwt, - effective_vc_subject, - vc_flow_signers, - root_pk_raw, - current_time_ns, - )?; - validate_claim("iss", &vc_flow_signers.issuer_origin, claims.iss()) - .map_err(invalid_requested_vc)?; - let vc_claims = extract_vc_claims(&claims).map_err(invalid_requested_vc)?; - validate_claims_match_spec(&vc_claims, vc_spec).map_err(invalid_requested_vc)?; - Ok(()) -} - -pub struct CredentialParams { - pub spec: CredentialSpec, - pub subject_id: String, - pub credential_id_url: String, - pub issuer_url: String, - pub expiration_timestamp_s: u32, -} - -/// Builds a verifiable credential with the given parameters and returns the credential as a JWT-string. -pub fn build_credential_jwt(params: CredentialParams) -> String { - let mut subject_json = json!({"id": params.subject_id}); - subject_json.as_object_mut().unwrap().insert( - params.spec.credential_type.clone(), - credential_spec_args_to_json(¶ms.spec), - ); - let subject = Subject::from_json_value(subject_json).unwrap(); - let expiration_date = Timestamp::from_unix(params.expiration_timestamp_s as i64) - .expect("internal: failed computing expiration timestamp"); - let credential: Credential = CredentialBuilder::default() - .id(Url::parse(params.credential_id_url).unwrap()) - .issuer(Url::parse(params.issuer_url).unwrap()) - .type_(params.spec.credential_type) - .subject(subject) - .expiration_date(expiration_date) - .build() - .unwrap(); - credential.serialize_jwt(None).unwrap() -} - -/// Builds from the given parameters a Verifiable Presentation as returned by II -/// to the relying party during a successful VC flow. Specifically, the returned JWT -/// * contains the two given VCs (`id_alias_vc_jws` and `requested_vc_jws`, in that order), -/// * contains the specified `holder`, which should match the subject of `id_alias_vc_jws`, -/// * does not contain a signature, -/// -/// This function is not used by II directly (as the returned presentation is built by II-frontend), -/// but it is useful for testing RPs that should validate the presentations obtained from II. -/// -/// NOTE: The given VCs are treated as opaque strings, and are NOT validated for syntax or contents, -/// i.e. the returned JWT can contain invalid information (if the parameters are invalid or inconsistent). -/// See also `verify_ii_presentation_jwt_with_canister_ids` for validation conditions. -pub fn build_ii_verifiable_presentation_jwt( - holder: Principal, - id_alias_vc_jws: String, - requested_vc_jws: String, -) -> Result { - construct_verifiable_presentation_jwt(holder, vec![id_alias_vc_jws, requested_vc_jws]) -} - -fn credential_spec_args_to_json(spec: &CredentialSpec) -> serde_json::Value { - let mut args_map = serde_json::Map::new(); - if let Some(args) = spec.arguments.as_ref() { - for arg in args { - args_map.insert(arg.0.clone(), arg.1.clone().into()); - } - } - serde_json::Value::Object(args_map) -} - -/// Returns the given `signing_input` prefixed with -/// length(VC_SIGNING_INPUT_DOMAIN) || VC_SIGNING_INPUT_DOMAIN -/// (for domain separation). -fn signing_input_with_prefix(signing_input: &[u8]) -> Vec { - let mut result = Vec::from([VC_SIGNING_INPUT_DOMAIN.len() as u8]); - result.extend_from_slice(VC_SIGNING_INPUT_DOMAIN); - result.extend_from_slice(signing_input); - result -} - -fn invalid_requested_vc(e: JwtValidationError) -> PresentationVerificationError { - PresentationVerificationError::InvalidRequestedCredential( - CredentialVerificationError::InvalidClaims(e), - ) -} -fn extract_subject(claims: &JwtClaims) -> Result { - let Some(sub) = claims.sub() else { - return Err(JwtValidationError::CredentialStructure( - JwtVcError::MissingSubject, - )); - }; - let subject = principal_for_did(sub) - .map_err(|_| JwtValidationError::CredentialStructure(JwtVcError::InvalidSubject))?; - Ok(subject) -} - -fn extract_id_alias(claims: &JwtClaims) -> Result { - let id_dapp = extract_subject(claims)?; - let vc = extract_vc_claims(claims)?; - let subject_value = vc.get("credentialSubject").ok_or(inconsistent_jwt_claims( - "missing \"credentialSubject\" claim in id_alias JWT vc", - ))?; - let subject = Subject::from_json_value(subject_value.clone()).map_err(|_| { - inconsistent_jwt_claims("malformed \"credentialSubject\" claim in id_alias JWT vc") - })?; - let Value::Object(ref spec) = subject.properties["InternetIdentityIdAlias"] else { - return Err(inconsistent_jwt_claims( - "missing \"InternetIdentityIdAlias\" claim in id_alias JWT vc", - )); - }; - let alias_value = spec.get("hasIdAlias").ok_or(inconsistent_jwt_claims( - "missing \"hasIdAlias\" parameter in id_alias JWT vc", - ))?; - let Value::String(alias) = alias_value else { - return Err(inconsistent_jwt_claims( - "wrong type of \"hasIdAlias\" value in id_alias JWT vc", - )); - }; - let id_alias = Principal::from_text(alias).map_err(|_| { - inconsistent_jwt_claims("malformed \"hasIdAlias\"-value claim in id_alias JWT vc") - })?; - Ok(AliasTuple { id_alias, id_dapp }) -} - -fn validate_claim + std::fmt::Display, S: std::fmt::Display>( - label: &str, - expected: T, - actual: Option, -) -> Result<(), JwtValidationError> { - if let Some(actual) = actual { - if expected == actual { - Ok(()) - } else { - println!( - "inconsistent claim [{}] in VC:: expected: {}, actual: {}", - label, expected, actual - ); - Err(inconsistent_jwt_claims("inconsistent claim in VC")) - } - } else { - println!("missing claim [{}] in VC", label); - Err(inconsistent_jwt_claims("missing claim in VC")) - } -} - -fn validate_expiration( - maybe_expiration_s: Option, - current_time_ns: u128, -) -> Result<(), JwtValidationError> { - if let Some(expiration_s) = maybe_expiration_s { - let expiration_ns: u128 = (expiration_s * 1_000_000_000) - .try_into() - .map_err(|_| JwtValidationError::ExpirationDate)?; - if expiration_ns > current_time_ns { - Ok(()) - } else { - Err(JwtValidationError::ExpirationDate) - } - } else { - Err(JwtValidationError::CredentialStructure( - JwtVcError::MissingExpirationDate, - )) - } -} - -// Validates that provided `vc_claims` are consistent and match the given `spec`: -// - `vc_claims` contain "type"-claim that contains `spec.credential_type` -// - `vc_claims` contain claim named `spec.credential_type` with arguments that match `spec.arguments`, -// cf. a convention at https://github.com/dfinity/internet-identity/blob/main/docs/vc-spec.md#recommended-convention-connecting-credential-specification-with-the-returned-credentials -pub fn validate_claims_match_spec( - vc_claims: &Map, - spec: &CredentialSpec, -) -> Result<(), JwtValidationError> { - let credential_type = &spec.credential_type; - - // Check that type-claim contains spec.credential_type. - let vc_type_entry = vc_claims - .get("type") - .ok_or(inconsistent_jwt_claims("missing type-claim"))?; - let types = vc_type_entry - .as_array() - .ok_or(inconsistent_jwt_claims("malformed types-claim"))?; - if !types.contains(&Value::String(credential_type.clone())) { - return Err(inconsistent_jwt_claims( - "missing credential_type in type-claim", - )); - }; - - // Check that credentialSubject-claim contains spec.credential_type entry with matching arguments. - let credential_subject = vc_claims - .get("credentialSubject") - .ok_or(inconsistent_jwt_claims("missing credentialSubject-claim"))?; - let subject = Subject::from_json_value(credential_subject.clone()) - .map_err(|_| inconsistent_jwt_claims("malformed credentialSubject-claim"))?; - let verified_claim_arguments = subject - .properties - .get(credential_type) - .ok_or(inconsistent_jwt_claims("missing credential_type claim"))? - .as_object() - .ok_or(inconsistent_jwt_claims( - "malformed credential_type arguments", - ))?; - let spec_arguments_count = spec.arguments.as_ref().map_or(0, |args| args.len()); - if spec_arguments_count != verified_claim_arguments.len() { - return Err(inconsistent_jwt_claims( - "wrong number of credential_type arguments", - )); - } - if let Some(spec_arguments) = spec.arguments.as_ref() { - for (key, value) in spec_arguments.iter() { - if let Some(v) = verified_claim_arguments.get(key) { - if value != v { - return Err(inconsistent_jwt_claims( - "wrong value in credential_type argument", - )); - } - } else { - return Err(inconsistent_jwt_claims( - "missing key in credential_type arguments", - )); - } - } - } - Ok(()) -} - -// Per https://datatracker.ietf.org/doc/html/rfc7518#section-6.4, -// JwkParamsOct are for symmetric keys or another key whose value is a single octet sequence. -fn canister_sig_pk_jwk(canister_sig_pk_der: &[u8]) -> Result { - let mut cspk_jwk = Jwk::new(JwkType::Oct); - cspk_jwk.set_alg("IcCs"); - cspk_jwk - .set_params(JwkParams::Oct(JwkParamsOct { - k: encode_b64(canister_sig_pk_der), - })) - .map_err(|e| format!("internal: failed creating JWK: {:?}", e))?; - Ok(cspk_jwk) -} - -fn jws_encoder<'a>( - credential_jwt: &'a str, - canister_sig_pk: &CanisterSigPublicKey, -) -> Result, String> { - let mut header: JwsHeader = JwsHeader::new(); - header.set_alg(JwsAlgorithm::IcCs); - let kid = did_for_principal(canister_sig_pk.canister_id); - let jwk = canister_sig_pk_jwk(&canister_sig_pk.to_der())?; - header.set_kid(kid); - header.deref_mut().set_jwk(jwk); - - let encoder: CompactJwsEncoder = CompactJwsEncoder::new(credential_jwt.as_ref(), &header) - .map_err(|e| format!("internal: failed creating JWS encoder: {:?}", e))?; - Ok(encoder) -} - -fn unsupported_alg_err(custom_message: &str) -> SignatureVerificationError { - let err: SignatureVerificationError = SignatureVerificationErrorKind::UnsupportedAlg.into(); - err.with_custom_message(custom_message.to_string()) -} - -fn key_decoding_err(custom_message: &str) -> SignatureVerificationError { - let err: SignatureVerificationError = SignatureVerificationErrorKind::KeyDecodingFailure.into(); - err.with_custom_message(custom_message.to_string()) -} - -fn invalid_signature_err(custom_message: &str) -> SignatureVerificationError { - let err: SignatureVerificationError = SignatureVerificationErrorKind::InvalidSignature.into(); - err.with_custom_message(custom_message.to_string()) -} - -fn inconsistent_jwt_claims(custom_message: &'static str) -> JwtValidationError { - JwtValidationError::CredentialStructure(JwtVcError::InconsistentCredentialJwtClaims( - custom_message, - )) -} - -/// Extracts and returns raw canister sig public key (without DER-prefix) from the given header. -pub fn get_canister_sig_pk_raw( - jws_header: &JwsHeader, -) -> Result, SignatureVerificationError> { - let jwk = jws_header - .deref() - .jwk() - .ok_or(key_decoding_err("missing JWK in JWS header"))?; - if jwk.alg() != Some("IcCs") { - return Err(unsupported_alg_err("expected IcCs")); - } - // Per https://datatracker.ietf.org/doc/html/rfc7518#section-6.4, - // JwkParamsOct are for symmetric keys or another key whose value is a single octet sequence. - if jwk.kty() != JwkType::Oct { - return Err(unsupported_alg_err("expected JWK of type oct")); - } - let jwk_params = jwk - .try_oct_params() - .map_err(|_| key_decoding_err("missing JWK oct params"))?; - let pk_der = decode_b64(jwk_params.k.as_bytes()) - .map_err(|_| key_decoding_err("invalid base64url encoding"))?; - let pk_raw = extract_raw_canister_sig_pk_from_der(pk_der.as_slice()) - .map_err(|e| key_decoding_err(&e.to_string()))?; - Ok(pk_raw) -} - -fn construct_verifiable_presentation_jwt( - holder: Principal, - vcs_jws: Vec, -) -> Result { - let holder_url = Url::parse(did_for_principal(holder)).map_err(|_| "Invalid holder")?; - let mut builder = PresentationBuilder::new(holder_url, Default::default()); - for vc in vcs_jws { - builder = builder.credential(Jwt::from(vc)); - } - let presentation: Presentation = builder - .build() - .map_err(|_| "failed building presentation")?; - presentation_to_compact_jwt(&presentation) -} - -fn presentation_to_compact_jwt(presentation: &Presentation) -> Result { - let mut header: JwsHeader = JwsHeader::new(); - header.set_typ("JWT"); - header.set_alg(JwsAlgorithm::NONE); - let vp_jwt = presentation - .serialize_jwt(&JwtPresentationOptions { - expiration_date: None, - issuance_date: None, - audience: None, - custom_claims: None, - }) - .map_err(|_| "failed serializing presentation")?; - let encoder: CompactJwsEncoder = CompactJwsEncoder::new(vp_jwt.as_ref(), &header) - .map_err(|_| "internal error: JWS encoder failed")?; - Ok(encoder.into_jws(&[])) -} -#[cfg(test)] -mod tests { - use super::*; - use crate::issuer_api::ArgumentValue; - use assert_matches::assert_matches; - use canister_sig_util::{extract_raw_root_pk_from_der, IC_ROOT_PK_DER_PREFIX}; - use identity_core::common::Url; - use std::collections::HashMap; - - const TEST_IC_ROOT_PK_B64URL: &str = "MIGCMB0GDSsGAQQBgtx8BQMBAgEGDCsGAQQBgtx8BQMCAQNhAK32VjilMFayIiyRuyRXsCdLypUZilrL2t_n_XIXjwab3qjZnpR52Ah6Job8gb88SxH-J1Vw1IHxaY951Giv4OV6zB4pj4tpeY2nqJG77Blwk-xfR1kJkj1Iv-1oQ9vtHw"; - const ID_ALIAS_CREDENTIAL_JWS: &str = "eyJqd2siOnsia3R5Ijoib2N0IiwiYWxnIjoiSWNDcyIsImsiOiJNRHd3REFZS0t3WUJCQUdEdUVNQkFnTXNBQW9BQUFBQUFBQUFBQUVCN2ZMb3hfUUJraHpseExERE94Tk1iZW5fd19yRGVNSy1kQUt4RGRpcll5QSJ9LCJraWQiOiJkaWQ6aWNwOnJ3bGd0LWlpYWFhLWFhYWFhLWFhYWFhLWNhaSIsImFsZyI6IkljQ3MifQ.eyJleHAiOjE2MjAzMjk1MzAsImlzcyI6Imh0dHBzOi8vaWRlbnRpdHkuaWMwLmFwcC8iLCJuYmYiOjE2MjAzMjg2MzAsImp0aSI6ImRhdGE6dGV4dC9wbGFpbjtjaGFyc2V0PVVURi04LHRpbWVzdGFtcF9uczoxNjIwMzI4NjMwMDAwMDAwMDAwLGFsaWFzX2hhc2g6YjIwYjJhMGQ3MGExZWZjZGVmZWYwNGExYmY4YjMwOTVkMmJhNDIzYWM0ZDI1MzY3ZjI1YTkyZjNjYTc3NDY4NSIsInN1YiI6ImRpZDppY3A6bjZjbmktcGE0amctaHdheWQtcXlhbXUtNWU1bmYtbHlkcTctcnN1YnQtdmZ2eHYtb3Q0ejMtbnZ4NnEtZmFlIiwidmMiOnsiQGNvbnRleHQiOiJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJJbnRlcm5ldElkZW50aXR5SWRBbGlhcyJdLCJjcmVkZW50aWFsU3ViamVjdCI6eyJJbnRlcm5ldElkZW50aXR5SWRBbGlhcyI6eyJoYXNJZEFsaWFzIjoiZGF0ZXctZGp4eWQteXF1enYtZjRhazUtamZ0d3EtNHJ5ZW8tdXpldzYtNDc0bm0taW5neG8tMmlveHUtNGFlIn19fX0.2dn3omtjZXJ0aWZpY2F0ZVkBsdnZ96JkdHJlZYMBgwGDAYMCSGNhbmlzdGVygwGDAkoAAAAAAAAAAAEBgwGDAYMBgwJOY2VydGlmaWVkX2RhdGGCA1ggFdNWmGHa91EhiMxR0EKhKjvqyJ4dHR86sWNM7VOOeoGCBFgg0sz_P8xdqTDewOhKJUHmWFFrS7FQHnDotBDmmGoFfWCCBFgg_KZ0TVqubo_EGWoMUPA35BYZ4B5ZRkR_zDfNIQCwa46CBFggDxSoL5vzjhHDgnrdmgRhclanMmjjpWYL41-us6gEU6mCBFggXAzCWvb9h4qsVs41IUJBABzjSqAZ8DIzF_ghGHpGmHGCBFggkSLnf3jYhPk65LUQsbOYiY0865rVZ_oGp9dgdQUI4zyCBFgg1DPpoHCCNK_LriesDVYlyoELR3hdXKdRfBLgOtNMuDqDAYIEWCA1U_ZYHVOz3Sdkb2HIsNoLDDiBuFfG3DxH6miIwRPra4MCRHRpbWWCA0mAuK7U3YmkvhZpc2lnbmF0dXJlWDCLSH_C_yh8NAElCHNRow7HVmn54FwWH2XKLlSTJlV7fWrcwOn0fUbtK9d5kkl9qBBkdHJlZYMBggRYIOGnlc_3yXPTVrEJ1p3dKX5HxkMOziUnpA1HeXiQW4O8gwJDc2lngwJYINga350VgbIt7J_tUkjxMijAfGnQAzfpL0LK-FNuYswHgwGDAlggKKAoHHmtclkvybvAtkqZyuyA-2IwxSm_PeIejRWVZjGCA0CCBFggHSycl7DEAOARm38YMr9aY5NWJYpzg2U8V9isy2zgccQ"; - // ALIAS_PRINCIPAL and DAPP_PRINCIPAL match ID_ALIAS_CREDENTIAL_JWS defined above. - const ALIAS_PRINCIPAL: &str = "datew-djxyd-yquzv-f4ak5-jftwq-4ryeo-uzew6-474nm-ingxo-2ioxu-4ae"; - const DAPP_PRINCIPAL: &str = "n6cni-pa4jg-hwayd-qyamu-5e5nf-lydq7-rsubt-vfvxv-ot4z3-nvx6q-fae"; - const EXPIRY_NS: u128 = 1620329530 * 1_000_000_000; // from ID_ALIAS_CREDENTIAL_JWS - const MINUTE_NS: u128 = 60 * 1_000_000_000; - const CURRENT_TIME_AFTER_EXPIRY_NS: u128 = EXPIRY_NS + MINUTE_NS; - const CURRENT_TIME_BEFORE_EXPIRY_NS: u128 = EXPIRY_NS - MINUTE_NS; - - const ID_ALIAS_CREDENTIAL_JWS_NO_JWK: &str = "eyJraWQiOiJkaWQ6aWM6aWktY2FuaXN0ZXIiLCJhbGciOiJJY0NzIn0.eyJpc3MiOiJodHRwczovL2ludGVybmV0Y29tcHV0ZXIub3JnL2lzc3VlcnMvaW50ZXJuZXQtaWRlbml0eSIsIm5iZiI6MTYyMDMyODYzMCwianRpIjoiaHR0cHM6Ly9pbnRlcm5ldGNvbXB1dGVyLm9yZy9jcmVkZW50aWFsL2ludGVybmV0LWlkZW5pdHkiLCJzdWIiOiJkaWQ6d2ViOmNwZWhxLTU0aGVmLW9kamp0LWJvY2tsLTNsZHRnLWpxbGU0LXlzaTVyLTZiZmFoLXY2bHNhLXhwcmR2LXBxZSIsInZjIjp7IkBjb250ZXh0IjoiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiSW50ZXJuZXRJZGVudGl0eUlkQWxpYXMiXSwiY3JlZGVudGlhbFN1YmplY3QiOnsiaGFzX2lkX2FsaWFzIjoiZGlkOndlYjpzMzNxYy1jdG5wNS11Ynl6NC1rdWJxby1wMnRlbS1oZTRscy02ajIzai1od3diYS0zN3pibC10Mmx2My1wYWUifX19.2dn3omtjZXJ0aWZpY2F0ZVkBi9nZ96JkdHJlZYMBgwGDAYMCSGNhbmlzdGVygwJKAAAAAAAAAAABAYMBgwGDAYMCTmNlcnRpZmllZF9kYXRhggNYIG3uU_jutBtXB-of0uEA3RkCrcunK6D8QFPtX-gDSwDeggRYINLM_z_MXakw3sDoSiVB5lhRa0uxUB5w6LQQ5phqBX1gggRYIMULjwe1N6XomH10SEyc2r_uc7mGf1aSadeDaid9cUrkggRYIDw__VW2PgWMFp6mK-GmPG-7Fc90q58oK_wjcJ3IrkToggRYIAQTcQAtnxsa93zbfZEZV0f28OhiXL5Wp1OAyDHNI_x4ggRYINkQ8P9zGUvsVi3XbQ2bs6V_3kAiN8UNM6yPgeXfmArEgwGCBFggNVP2WB1Ts90nZG9hyLDaCww4gbhXxtw8R-poiMET62uDAkR0aW1lggNJgLiu1N2JpL4WaXNpZ25hdHVyZVgwqHrYoUsNvSEaSShbW8barx0_ODXD5ZBEl9nKOdkNy_fBmGErE_C7ILbC91_fyZ7CZHRyZWWDAYIEWCB223o-sI97tc3LwJL3LRxQ4If6v_IvfC1fwIGYYQ9vroMCQ3NpZ4MCWCA6UuW6rWVPRqQn_k-pP9kMNe6RKs1gj7QVCsaG4Bx2OYMBgwJYIHszMLDS2VadioIaHajRY5iJzroqMs63lVrs_Uj42j0sggNAggRYICm0w_XxGEw4fDPoYcojCILEi0qdH4-4Zw7klzdaPNOC"; - const TEST_CREDENTIAL_JWS_NO_EXPIRY: &str = "eyJqd2siOnsia3R5Ijoib2N0IiwiYWxnIjoiSWNDcyIsImsiOiJNRHd3REFZS0t3WUJCQUdEdUVNQkFnTXNBQW9BQUFBQUFBQUFBUUVCeUk3dlEyOGVybHFnVjVMck03dTNIOUlaeGVwcUxzQkdnSjFyTldaX0tfQSJ9LCJraWQiOiJkaWQ6aWNwOnJya2FoLWZxYWFhLWFhYWFhLWFhYWFxLWNhaSIsImFsZyI6IkljQ3MifQ.eyJpc3MiOiJodHRwczovL2VtcGxveW1lbnQuaW5mby8iLCJuYmYiOjE2MjAzMjg2MzAsImp0aSI6Imh0dHBzOi8vZW1wbG95bWVudC5pbmZvL2NyZWRlbnRpYWxzLzQyIiwic3ViIjoiZGlkOmljcDp2aGJpYi1tNGhtNi1ocHZ5Yy03cHJkMi1zaWl2by1uYmQ3ci02N281eC1uM2F3aC1xc21xei13em5qZi10cWUiLCJ2YyI6eyJAY29udGV4dCI6Imh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL3YxIiwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIlZlcmlmaWVkRW1wbG95ZWUiXSwiY3JlZGVudGlhbFN1YmplY3QiOnsiZW1wbG95ZWVfb2YiOnsiZW1wbG95ZXJJZCI6ImRpZDp3ZWI6ZGZpbml0eS5vcmciLCJlbXBsb3llck5hbWUiOiJERklOSVRZIEZvdW5kYXRpb24ifX19fQ.2dn3omtjZXJ0aWZpY2F0ZVkBsdnZ96JkdHJlZYMBgwGDAYMCSGNhbmlzdGVygwGCBFggq7DruGSK9j0nNpVYlgkE4OtYMHWfxzrqB0D-tTp77umDAkoAAAAAAAAAAQEBgwGDAYMBgwJOY2VydGlmaWVkX2RhdGGCA1ggc8y0K3LKbNnsixDTg2Ux51vwu6b9Kqm2NFykuHVtd06CBFgg0sz_P8xdqTDewOhKJUHmWFFrS7FQHnDotBDmmGoFfWCCBFggTwA0M58_LFASzZLk1ju6zhwQ6qzeDSZsYyc8Ak-WWGCCBFgg7bPsepWtwANz_eF2pBaMOy-a-UEVj8ojdMRGhxyIODqCBFggEflcBBzJzouB9GoAqyMJiiexVT1w7LIv72CbckA15-SCBFggFtwxSFgot33A2BgPFXCOTj9gM8Z0ORDn-YD1tYNW2wmDAYIEWCA1U_ZYHVOz3Sdkb2HIsNoLDDiBuFfG3DxH6miIwRPra4MCRHRpbWWCA0mAuK7U3YmkvhZpc2lnbmF0dXJlWDCisy0ljDwwuPOxJn72Y8qqxgxDRgP0srKPvFkEgygNfVHoEGnwseMBdMMrYzIStrNkdHJlZYMBggRYIAvQZNP5TRQHV7AavT2jNGPPLcQBzfQvva5hEybHvbw8gwJDc2lngwJYIHGZW4y0kE1oq6oGYkhXj36h1sNPmG2jwFX6tPGiRkfXgwJYICslyEcSADtGlWLKMBsBJAlXe8en4eGCuE9yuAnuqRBOggNA"; - const TEST_SIGNING_CANISTER_ID: &str = "rwlgt-iiaaa-aaaaa-aaaaa-cai"; - const TEST_SEED: [u8; 32] = [ - 142, 84, 220, 222, 130, 185, 65, 67, 145, 152, 171, 78, 191, 101, 41, 107, 108, 94, 2, 122, - 56, 7, 17, 80, 17, 183, 249, 81, 212, 200, 233, 231, - ]; - const TEST_CREDENTIAL_JWT: &str = r#"{"iss":"https://employment.info/","nbf":1620328630,"jti":"https://employment.info/credentials/42","sub":"did:icp:igfpm-3fhrp-syqme-4i4xk-o4pgd-5xdh4-fbbgw-jnxm5-bvou4-ljt52-kqe","vc":{"@context":"https://www.w3.org/2018/credentials/v1","type":["VerifiableCredential","VerifiedEmployee"],"credentialSubject":{"employee_of":{"employerId":"did:web:dfinity.org","employerName":"DFINITY Foundation"}}}}"#; - - // Test data used for verifiable presentation tests. - const AGE_VERIFIER_URL: &str = "https://age_verifier.info/"; - const TEST_ISSUER_SIGNING_CANISTER_ID: &str = "rrkah-fqaaa-aaaaa-aaaaq-cai"; - const ID_ALIAS_FOR_VP: &str = "jkk22-zqdxc-kgpez-6sv2m-5pby4-wi4t2-prmoq-gf2ih-i2qtc-v37ac-5ae"; - const ID_RP_FOR_VP: &str = "p2nlc-3s5ul-lcu74-t6pn2-ui5im-i4a5f-a4tga-e6znf-tnvlh-wkmjs-dqe"; - const ID_ALIAS_VC_FOR_VP_JWS: &str = "eyJqd2siOnsia3R5Ijoib2N0IiwiYWxnIjoiSWNDcyIsImsiOiJNRHd3REFZS0t3WUJCQUdEdUVNQkFnTXNBQW9BQUFBQUFBQUFBQUVCMGd6TTVJeXFMYUhyMDhtQTRWd2J5SmRxQTFyRVFUX2xNQnVVbmN5UDVVYyJ9LCJraWQiOiJkaWQ6aWNwOnJ3bGd0LWlpYWFhLWFhYWFhLWFhYWFhLWNhaSIsImFsZyI6IkljQ3MifQ.eyJleHAiOjE2MjAzMjk1MzAsImlzcyI6Imh0dHBzOi8vaWRlbnRpdHkuaWMwLmFwcC8iLCJuYmYiOjE2MjAzMjg2MzAsImp0aSI6ImRhdGE6dGV4dC9wbGFpbjtjaGFyc2V0PVVURi04LHRpbWVzdGFtcF9uczoxNjIwMzI4NjMwMDAwMDAwMDAwLGFsaWFzX2hhc2g6NThiYzcxMmYyMjFhOTJmMGE5OTRhZDZmN2JmOWVjNjc0MzBmMGFkMzNmYWVlZDAzZmUzZDU2NTYyMTliMjQ2MiIsInN1YiI6ImRpZDppY3A6cDJubGMtM3M1dWwtbGN1NzQtdDZwbjItdWk1aW0taTRhNWYtYTR0Z2EtZTZ6bmYtdG52bGgtd2ttanMtZHFlIiwidmMiOnsiQGNvbnRleHQiOiJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJJbnRlcm5ldElkZW50aXR5SWRBbGlhcyJdLCJjcmVkZW50aWFsU3ViamVjdCI6eyJJbnRlcm5ldElkZW50aXR5SWRBbGlhcyI6eyJoYXNJZEFsaWFzIjoiamtrMjItenFkeGMta2dwZXotNnN2Mm0tNXBieTQtd2k0dDItcHJtb3EtZ2YyaWgtaTJxdGMtdjM3YWMtNWFlIn19fX0.2dn3omtjZXJ0aWZpY2F0ZVkBsdnZ96JkdHJlZYMBgwGDAYMCSGNhbmlzdGVygwGDAkoAAAAAAAAAAAEBgwGDAYMBgwJOY2VydGlmaWVkX2RhdGGCA1ggvlJBTZDgK1_9Vb3-18dWKIfy28WTjZ1YqdjFWWAIX96CBFgg0sz_P8xdqTDewOhKJUHmWFFrS7FQHnDotBDmmGoFfWCCBFgg_KZ0TVqubo_EGWoMUPA35BYZ4B5ZRkR_zDfNIQCwa46CBFggDxSoL5vzjhHDgnrdmgRhclanMmjjpWYL41-us6gEU6mCBFggXAzCWvb9h4qsVs41IUJBABzjSqAZ8DIzF_ghGHpGmHGCBFggRbE3sOaqi_9kL-Uz1Kmf_pCWt4FSRaHU9KLSFTT3eceCBFggQERIfN1eHBUYfQr2fOyI_nTKHS71uqu-wOAdYwqyUX-DAYIEWCA1U_ZYHVOz3Sdkb2HIsNoLDDiBuFfG3DxH6miIwRPra4MCRHRpbWWCA0mAuK7U3YmkvhZpc2lnbmF0dXJlWDCm_9R-rt9zbE2eP_WbCyFqO7txO86wNfBS1lyyJJ6gxy1D2Wnw5kNo2XUKUBmu9q5kdHJlZYMBggRYIOGnlc_3yXPTVrEJ1p3dKX5HxkMOziUnpA1HeXiQW4O8gwJDc2lngwJYIIOQR7wl3Ws9Jb8VP4rhIb37XKLMkkZ2P7WaZ5we60WGgwGDAlgg3DSOKS3cc99bdJqFjiOcs13PNpGSR8_5-UJsP23Ud0KCA0CCBFgg6wJlRmEtuY-LCp6ieeEdd6tO8_Hlct7H8VrW9DH7EaI"; - const VERIFIED_ADULT_VC_FOR_VP_JWS: &str = "eyJqd2siOnsia3R5Ijoib2N0IiwiYWxnIjoiSWNDcyIsImsiOiJNRHd3REFZS0t3WUJCQUdEdUVNQkFnTXNBQW9BQUFBQUFBQUFBUUVCOEVpSWoyNkJxRWhic2ZQUW44TF9CNDJxc0JOeUdiT3ZLdlNENE9OUGhsSSJ9LCJraWQiOiJkaWQ6aWNwOnJya2FoLWZxYWFhLWFhYWFhLWFhYWFxLWNhaSIsImFsZyI6IkljQ3MifQ.eyJleHAiOjE2MjAzMjk1MzAsImlzcyI6Imh0dHBzOi8vYWdlX3ZlcmlmaWVyLmluZm8vIiwibmJmIjoxNjIwMzI4NjMwLCJqdGkiOiJodHRwczovL2FnZV92ZXJpZmllci5pbmZvL2NyZWRlbnRpYWxzLzQyIiwic3ViIjoiZGlkOmljcDpqa2syMi16cWR4Yy1rZ3Blei02c3YybS01cGJ5NC13aTR0Mi1wcm1vcS1nZjJpaC1pMnF0Yy12MzdhYy01YWUiLCJ2YyI6eyJAY29udGV4dCI6Imh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL3YxIiwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIlZlcmlmaWVkQWR1bHQiXSwiY3JlZGVudGlhbFN1YmplY3QiOnsiVmVyaWZpZWRBZHVsdCI6eyJtaW5BZ2UiOjE4fX19fQ.2dn3omtjZXJ0aWZpY2F0ZVkBsdnZ96JkdHJlZYMBgwGDAYMCSGNhbmlzdGVygwGCBFggOnw-lESEpV-y1s0Lh9p1aY-XfYKBYzyHL_fmcTqp6PeDAkoAAAAAAAAAAQEBgwGDAYMBgwJOY2VydGlmaWVkX2RhdGGCA1ggO8I7YzRNmk_XVakRhuaOq1rdEj3vhLFt07YEWKwrfBSCBFgg0sz_P8xdqTDewOhKJUHmWFFrS7FQHnDotBDmmGoFfWCCBFggsN7BWldXUVrLUx_990beUdGHvTn5XEjFcgTxb8oXZZCCBFggNtRXohqxK3P8d6uyzQLSdJLBe5kv-Ng0gEHSR-OUmryCBFggiOn-4gDlCnp9jkq0VFtcJQPETxg1HnHwdHOddTpIlzWCBFggjFoCQNnMC4FEG3e2zATPdOyzWTcfRqu16bVgC18EQiCDAYIEWCA1U_ZYHVOz3Sdkb2HIsNoLDDiBuFfG3DxH6miIwRPra4MCRHRpbWWCA0mAuK7U3YmkvhZpc2lnbmF0dXJlWDCrcgY2ne3OillJ6fz8uv6dhCykfT-u0ZSKyvXZVYS1zOtRCMOSYZju2k-LERBCmLNkdHJlZYMBggRYIJIlUxoU2qt4aTwzz90fB43OK9EFDzVls4N8OHepeuLpgwJDc2lngwJYIKhCHifwHS5DiNAL6bducWQ2AShCc2bN-TzPsBEl3ov2gwGCBFggCr5Roa_ACiP36lIIHDtA47bq8L7C_nH3Z0GGJrLnE6uDAYMCWCCzsKpLUCoF4k5X0pGLjWSca9QaCMj6-oXkkFtUO7kYtoIDQIIEWCBrqYIFsKJT6MmiyQ79ksiXynSLIxl4HdOrpgsXm4TVBw"; - const VERIFIED_EMPLOYEE_VC_FOR_VP_JWS: &str = "eyJqd2siOnsia3R5Ijoib2N0IiwiYWxnIjoiSWNDcyIsImsiOiJNRHd3REFZS0t3WUJCQUdEdUVNQkFnTXNBQW9BQUFBQUFBQUFBUUVCOEVpSWoyNkJxRWhic2ZQUW44TF9CNDJxc0JOeUdiT3ZLdlNENE9OUGhsSSJ9LCJraWQiOiJkaWQ6aWNwOnJya2FoLWZxYWFhLWFhYWFhLWFhYWFxLWNhaSIsImFsZyI6IkljQ3MifQ.eyJleHAiOjE2MjAzMjk1MzAsImlzcyI6Imh0dHBzOi8vZW1wbG95bWVudC5pbmZvLyIsIm5iZiI6MTYyMDMyODYzMCwianRpIjoiaHR0cHM6Ly9lbXBsb3ltZW50LmluZm8vY3JlZGVudGlhbHMvNDIiLCJzdWIiOiJkaWQ6aWNwOmprazIyLXpxZHhjLWtncGV6LTZzdjJtLTVwYnk0LXdpNHQyLXBybW9xLWdmMmloLWkycXRjLXYzN2FjLTVhZSIsInZjIjp7IkBjb250ZXh0IjoiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiVmVyaWZpZWRFbXBsb3llZSJdLCJjcmVkZW50aWFsU3ViamVjdCI6eyJWZXJpZmllZEVtcGxveWVlIjp7ImVtcGxveWVyTmFtZSI6IkRGSU5JVFkgRm91bmRhdGlvbiJ9fX19.2dn3omtjZXJ0aWZpY2F0ZVkBsdnZ96JkdHJlZYMBgwGDAYMCSGNhbmlzdGVygwGCBFggOnw-lESEpV-y1s0Lh9p1aY-XfYKBYzyHL_fmcTqp6PeDAkoAAAAAAAAAAQEBgwGDAYMBgwJOY2VydGlmaWVkX2RhdGGCA1ggRugFe6Ck7NbMysgwHrks4lNkVUqkKsicdx67D59fgr-CBFgg0sz_P8xdqTDewOhKJUHmWFFrS7FQHnDotBDmmGoFfWCCBFggsN7BWldXUVrLUx_990beUdGHvTn5XEjFcgTxb8oXZZCCBFggNtRXohqxK3P8d6uyzQLSdJLBe5kv-Ng0gEHSR-OUmryCBFggYufTG35dpmNH5pv3wq__HC7kJTW1cCcRWSdFQ__KV0-CBFggon3xKa1joPZ19Djixh8oDlBboiMi5lmgnM1HKeyZ4siDAYIEWCA1U_ZYHVOz3Sdkb2HIsNoLDDiBuFfG3DxH6miIwRPra4MCRHRpbWWCA0mAuK7U3YmkvhZpc2lnbmF0dXJlWDCGPK8MNU0Mc5oSLu9IKas2ASm40jNnz_iCOP3IByDW8AqG6cfRU9ZGP4IrFg9ECKhkdHJlZYMBggRYIJIlUxoU2qt4aTwzz90fB43OK9EFDzVls4N8OHepeuLpgwJDc2lngwJYIKhCHifwHS5DiNAL6bducWQ2AShCc2bN-TzPsBEl3ov2gwJYINHB_8EbKcY_Lfj5XYVCaN8msEm9ABXR6E3wqY7msxqrggNA"; - - fn test_ic_root_pk_raw() -> Vec { - let pk_der = decode_b64(TEST_IC_ROOT_PK_B64URL).expect("failure decoding canister pk"); - extract_raw_root_pk_from_der(pk_der.as_slice()) - .expect("failure extracting root pk from DER") - } - - fn test_canister_sig_pk() -> CanisterSigPublicKey { - CanisterSigPublicKey::new( - Principal::from_text(TEST_SIGNING_CANISTER_ID).expect("wrong principal"), - TEST_SEED.to_vec(), - ) - } - - fn test_issuer_canister_sig_pk() -> CanisterSigPublicKey { - CanisterSigPublicKey::new( - Principal::from_text(TEST_ISSUER_SIGNING_CANISTER_ID).expect("wrong principal"), - TEST_SEED.to_vec(), - ) - } - - fn alias_principal() -> Principal { - Principal::from_text(ALIAS_PRINCIPAL).expect("wrong principal") - } - - fn dapp_principal() -> Principal { - Principal::from_text(DAPP_PRINCIPAL).expect("wrong principal") - } - - fn claims_from_jws(credential_jws: &str) -> JwtClaims { - let decoder: Decoder = Decoder::new(); - let jws = decoder - .decode_compact_serialization(credential_jws.as_ref(), None) - .expect("failed JWS parsing"); - let claims: JwtClaims = - serde_json::from_slice(jws.claims()).expect("failed parsing JSON JWT claims"); - claims - } - - #[test] - fn should_compute_domain_separated_signing_input_hash() { - let signing_input = b"some bytes to sign"; - let signing_input_with_prefix = signing_input_with_prefix(signing_input.as_slice()); - assert_eq!(26, signing_input_with_prefix[0]); - assert_eq!( - b"iccs_verifiable_credential".as_slice(), - signing_input_with_prefix[1..27].to_vec().as_slice() - ); - let util_hash = vc_signing_input_hash(signing_input); - let mut hasher = Sha256::new(); - hasher.update(signing_input_with_prefix); - let manual_hash: Hash = hasher.finalize().into(); - assert_eq!(util_hash, manual_hash); - } - - #[test] - fn should_construct_correct_jws() { - let canister_id = Principal::from_text(TEST_SIGNING_CANISTER_ID).expect("wrong principal"); - let canister_sig_pk = CanisterSigPublicKey::new(canister_id, TEST_SEED.to_vec()); - let dummy_sig: &str = "some signature"; - let credential_jwt = String::from_utf8(TEST_CREDENTIAL_JWT.into()).expect("wrong JWT"); - let credential_jws = vc_jwt_to_jws(&credential_jwt, &canister_sig_pk, dummy_sig.as_bytes()) - .expect("failed constructing JWS"); - let signing_input = vc_signing_input(&credential_jwt, &canister_sig_pk) - .expect("failed constructing signing input"); - let credential_jws_from_signing_input = - vc_signing_input_to_jws(signing_input.as_slice(), dummy_sig.as_bytes()) - .expect("failed constructing JWS"); - assert_eq!(credential_jws_from_signing_input, credential_jws); - - let decoder: Decoder = Decoder::new(); - let jws = decoder - .decode_compact_serialization(credential_jws.as_ref(), None) - .expect("Failed parsing constructed JWS"); - assert_eq!(dummy_sig.as_bytes(), jws.decoded_signature()); - let jws_header = jws.protected_header().expect("JWS without header"); - let canister_sig_pk_from_jws = - get_canister_sig_pk_raw(jws_header).expect("JWS header without pk"); - let canister_sig_pk_raw = - extract_raw_canister_sig_pk_from_der(canister_sig_pk.to_der().as_slice()) - .expect("wrong canister sig pk"); - assert_eq!(canister_sig_pk_from_jws, canister_sig_pk_raw); - assert_eq!(jws.claims(), TEST_CREDENTIAL_JWT.as_bytes()); - } - - #[test] - fn should_extract_canister_sig_pk_from_signing_input() { - let canister_id = Principal::from_text(TEST_SIGNING_CANISTER_ID).expect("wrong principal"); - let canister_sig_pk = CanisterSigPublicKey::new(canister_id, TEST_SEED.to_vec()); - let credential_jwt = String::from_utf8(TEST_CREDENTIAL_JWT.into()).expect("wrong JWT"); - let signing_input = vc_signing_input(&credential_jwt, &canister_sig_pk) - .expect("failed constructing signing input"); - let extracted_pk = canister_sig_pk_from_vc_signing_input(signing_input.as_slice()) - .expect("failed extracting pk"); - assert_eq!(extracted_pk, canister_sig_pk); - } - - #[test] - fn should_compute_icp_did() { - let principal = dapp_principal(); - let did = did_for_principal(principal); - assert!(did.starts_with("did:icp:")); - assert!(did.ends_with(&principal.to_string())); - assert_eq!(did.len(), "did:icp:".len() + principal.to_string().len()); - } - - #[test] - fn should_validate_id_alias_claims() { - let claims = claims_from_jws(ID_ALIAS_CREDENTIAL_JWS); - validate_claim("iss", II_ISSUER_URL, claims.iss()) - .expect("Failed validating id_alias claims"); - } - - #[test] - fn should_verify_credential_jws() { - verify_credential_jws_with_canister_id( - ID_ALIAS_CREDENTIAL_JWS, - &test_canister_sig_pk().canister_id, - &test_ic_root_pk_raw(), - CURRENT_TIME_BEFORE_EXPIRY_NS, - ) - .expect("JWS verification failed"); - } - - #[test] - fn should_fail_verify_credential_jws_if_expired() { - let result = verify_credential_jws_with_canister_id( - ID_ALIAS_CREDENTIAL_JWS, - &test_canister_sig_pk().canister_id, - &test_ic_root_pk_raw(), - CURRENT_TIME_AFTER_EXPIRY_NS, - ); - assert_matches!(result, Err(e) if e.to_string().contains("credential expired")); - } - - #[test] - fn should_fail_verify_credential_jws_if_no_expiry() { - let result = verify_credential_jws_with_canister_id( - TEST_CREDENTIAL_JWS_NO_EXPIRY, - &test_issuer_canister_sig_pk().canister_id, - &test_ic_root_pk_raw(), - CURRENT_TIME_AFTER_EXPIRY_NS, - ); - assert_matches!(result, Err(e) if e.to_string().contains("structure is not semantically correct")); - } - - #[test] - fn should_fail_verify_credential_jws_without_canister_pk() { - let result = verify_credential_jws_with_canister_id( - ID_ALIAS_CREDENTIAL_JWS_NO_JWK, - &test_canister_sig_pk().canister_id, - &test_ic_root_pk_raw(), - CURRENT_TIME_BEFORE_EXPIRY_NS, - ); - assert_matches!(result, Err(e) if e.to_string().contains("missing JWK in JWS header")); - } - - #[test] - fn should_fail_verify_credential_jws_with_wrong_canister_sig_pk() { - let wrong_canister_sig_pk = CanisterSigPublicKey::new(alias_principal(), vec![1, 2, 3]); - let result = verify_credential_jws_with_canister_id( - ID_ALIAS_CREDENTIAL_JWS, - &wrong_canister_sig_pk.canister_id, - &test_ic_root_pk_raw(), - CURRENT_TIME_BEFORE_EXPIRY_NS, - ); - assert_matches!(result, Err(e) if e.to_string().contains("canister sig canister id does not match provided canister id")); - } - - #[test] - fn should_fail_verify_credential_jws_with_wrong_root_pk() { - let mut ic_root_pk = test_ic_root_pk_raw(); - ic_root_pk[IC_ROOT_PK_DER_PREFIX.len()] += 1; // change the root pk value - let result = verify_credential_jws_with_canister_id( - ID_ALIAS_CREDENTIAL_JWS, - &test_canister_sig_pk().canister_id, - &ic_root_pk, - CURRENT_TIME_BEFORE_EXPIRY_NS, - ); - assert_matches!(result, Err(e) if { let err_msg = e.to_string(); - err_msg.contains("invalid signature") && - err_msg.contains("Malformed ThresBls12_381 public key") }); - } - - #[test] - fn should_verify_and_extract_id_alias_credential_jws() { - let alias_tuple = get_verified_id_alias_from_jws( - ID_ALIAS_CREDENTIAL_JWS, - &dapp_principal(), - &test_canister_sig_pk().canister_id, - &test_ic_root_pk_raw(), - CURRENT_TIME_BEFORE_EXPIRY_NS, - ) - .expect("JWS verification failed"); - assert_eq!( - alias_tuple, - AliasTuple { - id_alias: alias_principal(), - id_dapp: dapp_principal(), - } - ) - } - - #[test] - fn should_parse_verifiable_presentation() { - let id_alias_vc_jws = "a dummy id_alias_vc_jws".to_string(); - let requested_vc_jws = "a dummy requested_vc_jws".to_string(); - let holder = dapp_principal(); - let vp_jwt = build_ii_verifiable_presentation_jwt( - holder, - id_alias_vc_jws.clone(), - requested_vc_jws.clone(), - ) - .expect("vp-creation failed"); - let presentation: Presentation = - parse_verifiable_presentation_jwt(&vp_jwt).expect("failed jwt parsing"); - - assert!(presentation - .verifiable_credential - .contains(&Jwt::from(id_alias_vc_jws))); - assert!(presentation - .verifiable_credential - .contains(&Jwt::from(requested_vc_jws))); - assert_eq!( - Url::parse(did_for_principal(holder)).expect("bad url"), - presentation.holder - ); - } - - fn default_test_vc_flow_signers() -> VcFlowSigners { - VcFlowSigners { - ii_canister_id: test_canister_sig_pk().canister_id, - ii_origin: II_ISSUER_URL.to_string(), - issuer_canister_id: test_issuer_canister_sig_pk().canister_id, - issuer_origin: AGE_VERIFIER_URL.to_string(), - } - } - - #[test] - fn should_verify_ii_presentation() { - let id_alias = Principal::from_text(ID_ALIAS_FOR_VP).expect("wrong principal"); - let id_dapp = Principal::from_text(ID_RP_FOR_VP).expect("wrong principal"); - let vp_jwt = build_ii_verifiable_presentation_jwt( - id_dapp, - ID_ALIAS_VC_FOR_VP_JWS.to_string(), - VERIFIED_ADULT_VC_FOR_VP_JWS.to_string(), - ) - .expect("vp creation failed"); - let (alias_tuple_from_jws, _claims) = verify_ii_presentation_jwt_with_canister_ids( - &vp_jwt, - id_dapp, - &default_test_vc_flow_signers(), - &test_ic_root_pk_raw(), - CURRENT_TIME_BEFORE_EXPIRY_NS, - ) - .expect("vp verification failed"); - assert_eq!(id_alias, alias_tuple_from_jws.id_alias); - assert_eq!(id_dapp, alias_tuple_from_jws.id_dapp); - } - - #[test] - fn should_fail_verify_ii_presentation_if_expired() { - let id_dapp = Principal::from_text(ID_RP_FOR_VP).expect("wrong principal"); - let vp_jwt = build_ii_verifiable_presentation_jwt( - id_dapp, - ID_ALIAS_VC_FOR_VP_JWS.to_string(), - VERIFIED_ADULT_VC_FOR_VP_JWS.to_string(), - ) - .expect("vp creation failed"); - let result = verify_ii_presentation_jwt_with_canister_ids( - &vp_jwt, - id_dapp, - &default_test_vc_flow_signers(), - &test_ic_root_pk_raw(), - CURRENT_TIME_AFTER_EXPIRY_NS, - ); - assert_matches!(result, Err(e) if format!("{:?}", e).contains("credential expired")); - } - - #[test] - fn should_fail_verify_ii_presentation_with_extra_vc() { - let id_dapp = Principal::from_text(ID_RP_FOR_VP).expect("wrong principal"); - let vp_jwt = construct_verifiable_presentation_jwt( - id_dapp, - vec![ - ID_ALIAS_VC_FOR_VP_JWS.to_string(), - VERIFIED_ADULT_VC_FOR_VP_JWS.to_string(), - "an extra vc".to_string(), - ], - ) - .expect("vp creation failed"); - let result = verify_ii_presentation_jwt_with_canister_ids( - &vp_jwt, - id_dapp, - &default_test_vc_flow_signers(), - &test_ic_root_pk_raw(), - CURRENT_TIME_BEFORE_EXPIRY_NS, - ); - assert_matches!(result, Err(e) if format!("{:?}", e).contains("expected exactly two verifiable credentials")); - } - - #[test] - fn should_fail_verify_ii_presentation_with_missing_vc() { - let id_dapp = Principal::from_text(ID_RP_FOR_VP).expect("wrong principal"); - let vp_jwt = construct_verifiable_presentation_jwt( - id_dapp, - vec![ID_ALIAS_VC_FOR_VP_JWS.to_string()], - ) - .expect("vp creation failed"); - let result = verify_ii_presentation_jwt_with_canister_ids( - &vp_jwt, - id_dapp, - &default_test_vc_flow_signers(), - &test_ic_root_pk_raw(), - CURRENT_TIME_BEFORE_EXPIRY_NS, - ); - assert_matches!(result, Err(e) if format!("{:?}", e).contains("expected exactly two verifiable credentials")); - } - - #[test] - fn should_fail_verify_ii_presentation_with_wrong_effective_subject() { - let wrong_subject = dapp_principal(); // does not match ID_ALIAS_VC_FOR_VP_JWS - let vp_jwt = build_ii_verifiable_presentation_jwt( - wrong_subject, - ID_ALIAS_VC_FOR_VP_JWS.to_string(), - VERIFIED_ADULT_VC_FOR_VP_JWS.to_string(), - ) - .expect("vp creation failed"); - let result = verify_ii_presentation_jwt_with_canister_ids( - &vp_jwt, - wrong_subject, - &default_test_vc_flow_signers(), - &test_ic_root_pk_raw(), - CURRENT_TIME_BEFORE_EXPIRY_NS, - ); - assert_matches!(result, Err(e) if format!("{:?}", e).contains("unexpected vc subject")); - } - - #[test] - fn should_fail_verify_ii_presentation_with_non_matching_id_alias_in_vcs() { - let id_dapp = dapp_principal(); // does match ID_ALIAS_CREDENTIAL_JWS - - // ID_ALIAS_CREDENTIAL_JWS does not match REQUESTED_VC_FOR_VP_JWS - let vp_jwt = build_ii_verifiable_presentation_jwt( - id_dapp, - ID_ALIAS_CREDENTIAL_JWS.to_string(), - VERIFIED_ADULT_VC_FOR_VP_JWS.to_string(), - ) - .expect("vp creation failed"); - let result = verify_ii_presentation_jwt_with_canister_ids( - &vp_jwt, - id_dapp, - &default_test_vc_flow_signers(), - &test_ic_root_pk_raw(), - CURRENT_TIME_BEFORE_EXPIRY_NS, - ); - assert_matches!(result, Err(e) if format!("{:?}", e).contains("subject does not match id_alias")); - } - - #[test] - fn should_fail_verify_ii_presentation_with_invalid_id_alias_vc() { - let id_dapp = Principal::from_text(ID_RP_FOR_VP).expect("wrong principal"); - - let mut bad_id_alias_vc = ID_ALIAS_VC_FOR_VP_JWS.to_string(); - bad_id_alias_vc.insert(42, 'a'); - let vp_jwt = build_ii_verifiable_presentation_jwt( - id_dapp, - bad_id_alias_vc, - VERIFIED_ADULT_VC_FOR_VP_JWS.to_string(), - ) - .expect("vp creation failed"); - let result = verify_ii_presentation_jwt_with_canister_ids( - &vp_jwt, - id_dapp, - &default_test_vc_flow_signers(), - &test_ic_root_pk_raw(), - CURRENT_TIME_BEFORE_EXPIRY_NS, - ); - assert_matches!(result, Err(e) if format!("{:?}", e).contains("InvalidSignature")); - } - - #[test] - fn should_fail_verify_ii_presentation_with_invalid_requested_vc() { - let id_dapp = Principal::from_text(ID_RP_FOR_VP).expect("wrong principal"); - - let mut bad_requested_vc = VERIFIED_ADULT_VC_FOR_VP_JWS.to_string(); - bad_requested_vc.insert(42, 'a'); - let vp_jwt = build_ii_verifiable_presentation_jwt( - id_dapp, - ID_ALIAS_VC_FOR_VP_JWS.to_string(), - bad_requested_vc, - ) - .expect("vp creation failed"); - let result = verify_ii_presentation_jwt_with_canister_ids( - &vp_jwt, - id_dapp, - &default_test_vc_flow_signers(), - &test_ic_root_pk_raw(), - CURRENT_TIME_BEFORE_EXPIRY_NS, - ); - assert_matches!(result, Err(e) if format!("{:?}", e).contains("InvalidSignature")); - } - - #[test] - fn should_fail_verify_ii_presentation_with_wrong_ii_canister_id() { - let id_dapp = Principal::from_text(ID_RP_FOR_VP).expect("wrong principal"); - - let vp_jwt = build_ii_verifiable_presentation_jwt( - id_dapp, - ID_ALIAS_VC_FOR_VP_JWS.to_string(), - VERIFIED_ADULT_VC_FOR_VP_JWS.to_string(), - ) - .expect("vp creation failed"); - let result = verify_ii_presentation_jwt_with_canister_ids( - &vp_jwt, - id_dapp, - &VcFlowSigners { - ii_canister_id: test_issuer_canister_sig_pk().canister_id, - ..default_test_vc_flow_signers() - }, - &test_ic_root_pk_raw(), - CURRENT_TIME_BEFORE_EXPIRY_NS, - ); - assert_matches!(result, Err(e) if format!("{:?}", e).contains("canister id does not match")); - } - - #[test] - fn should_fail_verify_ii_presentation_with_wrong_issuer_canister_id() { - let id_dapp = Principal::from_text(ID_RP_FOR_VP).expect("wrong principal"); - - let vp_jwt = build_ii_verifiable_presentation_jwt( - id_dapp, - ID_ALIAS_VC_FOR_VP_JWS.to_string(), - VERIFIED_ADULT_VC_FOR_VP_JWS.to_string(), - ) - .expect("vp creation failed"); - let result = verify_ii_presentation_jwt_with_canister_ids( - &vp_jwt, - id_dapp, - &VcFlowSigners { - issuer_canister_id: test_canister_sig_pk().canister_id, - ..default_test_vc_flow_signers() - }, - &test_ic_root_pk_raw(), - CURRENT_TIME_BEFORE_EXPIRY_NS, - ); - assert_matches!(result, Err(e) if format!("{:?}", e).contains("canister id does not match")); - } - - #[test] - fn should_fail_verify_ii_presentation_with_wrong_order_of_vcs() { - let id_dapp = Principal::from_text(ID_RP_FOR_VP).expect("wrong principal"); - - // Swap the order of the VCs - let vp_jwt = build_ii_verifiable_presentation_jwt( - id_dapp, - VERIFIED_ADULT_VC_FOR_VP_JWS.to_string(), - ID_ALIAS_VC_FOR_VP_JWS.to_string(), - ) - .expect("vp creation failed"); - let result = verify_ii_presentation_jwt_with_canister_ids( - &vp_jwt, - id_dapp, - &VcFlowSigners { - // Swap also the order of the canister ids, so that they match the VCs - ii_canister_id: test_issuer_canister_sig_pk().canister_id, - issuer_canister_id: test_canister_sig_pk().canister_id, - ..default_test_vc_flow_signers() - }, - &test_ic_root_pk_raw(), - CURRENT_TIME_BEFORE_EXPIRY_NS, - ); - assert_matches!(result, Err(e) if format!("{:?}", e).contains("inconsistent claim in VC")); - } - - fn credential_spec_with_0_args() -> CredentialSpec { - CredentialSpec { - credential_type: "vcWithoutArgs".to_string(), - arguments: None, - } - } - - fn credential_spec_with_1_arg() -> CredentialSpec { - let mut args = HashMap::new(); - args.insert( - "firstArg".to_string(), - ArgumentValue::String("string arg value".to_string()), - ); - CredentialSpec { - credential_type: "vcWithOneArg".to_string(), - arguments: Some(args), - } - } - - fn credential_spec_with_2_args() -> CredentialSpec { - let mut args = HashMap::new(); - args.insert( - "anotherFirstArg".to_string(), - ArgumentValue::String("string arg value".to_string()), - ); - args.insert("secondArg".to_string(), ArgumentValue::Int(42)); - CredentialSpec { - credential_type: "vcWithTwoArgs".to_string(), - arguments: Some(args), - } - } - - fn credential_specs_for_test() -> Vec { - vec![ - credential_spec_with_0_args(), - credential_spec_with_1_arg(), - credential_spec_with_2_args(), - ] - } - - fn vc_claims_for_spec(spec: &CredentialSpec) -> Map { - let mut claims = Map::new(); - let types = vec![ - Value::String("VerifiableCredential".to_string()), - Value::String(spec.credential_type.to_string()), - ]; - claims.insert("type".to_string(), Value::Array(types)); - let mut arguments = Map::new(); - if let Some(args) = spec.arguments.as_ref() { - for arg in args { - arguments.insert(arg.0.clone(), arg.1.clone().into()); - } - } - let mut subject = Map::new(); - subject.insert(spec.credential_type.clone(), Value::Object(arguments)); - claims.insert("credentialSubject".to_string(), Value::Object(subject)); - claims - } - - #[test] - fn should_validate_claims_match_spec() { - for spec in credential_specs_for_test() { - let claims = vc_claims_for_spec(&spec); - validate_claims_match_spec(&claims, &spec) - .unwrap_or_else(|_| panic!("failed for spec: {:?}", spec)); - } - } - - #[test] - fn should_fail_validate_claims_match_spec_if_wrong_type() { - for spec in credential_specs_for_test() { - // Construct claims with wrong "type" entry. - let mut claims = vc_claims_for_spec(&spec); - claims.insert( - "type".to_string(), - Value::Array(vec![Value::String("WrongType".to_string())]), - ); - let result = validate_claims_match_spec(&claims, &spec); - assert_matches!(result, Err(e) if format!("{:?}", e).contains("missing credential_type in type-claim")); - } - } - - #[test] - fn should_fail_validate_claims_match_spec_if_missing_credential_type_claim() { - for spec in credential_specs_for_test() { - // Construct claims without "credential_type"-claim. - let mut claims = vc_claims_for_spec(&spec); - claims - .get_mut("credentialSubject") - .expect("missing credentialSubject") - .as_object_mut() - .expect("wrong credentialSubject") - .remove(&spec.credential_type) - .expect("missing credential_type claim"); - let result = validate_claims_match_spec(&claims, &spec); - assert_matches!(result, Err(e) if format!("{:?}", e).contains("missing credential_type claim")); - } - } - - #[test] - fn should_fail_validate_claims_match_spec_with_extra_args_in_credential_type_claim() { - for spec in credential_specs_for_test() { - // Construct claims with extra arg in "credential_type"-claim. - let mut claims = vc_claims_for_spec(&spec); - claims - .get_mut("credentialSubject") - .expect("missing credentialSubject") - .as_object_mut() - .expect("wrong credentialSubject") - .get_mut(&spec.credential_type) - .expect("missing credential_type claim") - .as_object_mut() - .expect("wrong credential_type claim") - .insert("extraArg".to_string(), Value::Null); - let result = validate_claims_match_spec(&claims, &spec); - assert_matches!(result, Err(e) if format!("{:?}", e).contains("wrong number of credential_type arguments")); - } - } - - #[test] - fn should_fail_validate_claims_match_spec_with_missing_args_in_credential_type_claim() { - for spec in [credential_spec_with_1_arg(), credential_spec_with_2_args()] { - // Construct claims with extra arg in "credential_type"-claim. - let mut claims = vc_claims_for_spec(&spec); - let arg_name = spec.arguments.as_ref().unwrap().keys().last().unwrap(); - claims - .get_mut("credentialSubject") - .expect("missing credentialSubject") - .as_object_mut() - .expect("wrong credentialSubject") - .get_mut(&spec.credential_type) - .expect("missing credential_type claim") - .as_object_mut() - .expect("wrong credential_type claim") - .remove(arg_name); - let result = validate_claims_match_spec(&claims, &spec); - assert_matches!(result, Err(e) if format!("{:?}", e).contains("wrong number of credential_type arguments")); - } - } - - #[test] - fn should_fail_validate_claims_match_spec_with_wrong_arg_value_in_credential_type_claim() { - for spec in [credential_spec_with_1_arg(), credential_spec_with_2_args()] { - // Construct claims with extra arg in "credential_type"-claim. - let mut claims = vc_claims_for_spec(&spec); - let arg_name = spec.arguments.as_ref().unwrap().keys().last().unwrap(); - claims - .get_mut("credentialSubject") - .expect("missing credentialSubject") - .as_object_mut() - .expect("wrong credentialSubject") - .get_mut(&spec.credential_type) - .expect("missing credential_type claim") - .as_object_mut() - .expect("wrong credential_type claim") - .insert(arg_name.clone(), Value::String("a wrong value".to_string())); - let result = validate_claims_match_spec(&claims, &spec); - assert_matches!(result, Err(e) if format!("{:?}", e).contains("wrong value in credential_type argument")); - } - } - - fn verified_adult_vc_spec() -> CredentialSpec { - let mut args = HashMap::new(); - args.insert("minAge".to_string(), ArgumentValue::Int(18)); - CredentialSpec { - credential_type: "VerifiedAdult".to_string(), - arguments: Some(args), - } - } - - #[test] - fn should_validate_ii_presentation_and_claims() { - let id_dapp = Principal::from_text(ID_RP_FOR_VP).expect("wrong principal"); - let vp_jwt = build_ii_verifiable_presentation_jwt( - id_dapp, - ID_ALIAS_VC_FOR_VP_JWS.to_string(), - VERIFIED_ADULT_VC_FOR_VP_JWS.to_string(), - ) - .expect("vp-creation failed"); - validate_ii_presentation_and_claims( - &vp_jwt, - id_dapp, - &default_test_vc_flow_signers(), - &verified_adult_vc_spec(), - &test_ic_root_pk_raw(), - CURRENT_TIME_BEFORE_EXPIRY_NS, - ) - .expect("VP verification failed"); - } - - #[test] - fn should_fail_validate_ii_presentation_and_claims_if_wrong_vc_flow_signers() { - let id_dapp = Principal::from_text(ID_RP_FOR_VP).expect("wrong principal"); - let vp_jwt = build_ii_verifiable_presentation_jwt( - id_dapp, - ID_ALIAS_VC_FOR_VP_JWS.to_string(), - VERIFIED_ADULT_VC_FOR_VP_JWS.to_string(), - ) - .expect("vp-creation failed"); - - // wrong ii_canister_id - let result = validate_ii_presentation_and_claims( - &vp_jwt, - id_dapp, - &VcFlowSigners { - ii_canister_id: test_issuer_canister_sig_pk().canister_id, - ..default_test_vc_flow_signers() - }, - &verified_adult_vc_spec(), - &test_ic_root_pk_raw(), - CURRENT_TIME_BEFORE_EXPIRY_NS, - ); - assert_matches!(result, Err(e) if format!("{:?}", e).to_string().contains("InvalidSignature")); - - // wrong issuer_canister_id - let result = validate_ii_presentation_and_claims( - &vp_jwt, - id_dapp, - &VcFlowSigners { - issuer_canister_id: test_canister_sig_pk().canister_id, - ..default_test_vc_flow_signers() - }, - &verified_adult_vc_spec(), - &test_ic_root_pk_raw(), - CURRENT_TIME_BEFORE_EXPIRY_NS, - ); - assert_matches!(result, Err(e) if format!("{:?}", e).to_string().contains("InvalidSignature")); - - // wrong issuer_origin - let result = validate_ii_presentation_and_claims( - &vp_jwt, - id_dapp, - &VcFlowSigners { - issuer_origin: "https://wrong.origin.com".to_string(), - ..default_test_vc_flow_signers() - }, - &verified_adult_vc_spec(), - &test_ic_root_pk_raw(), - CURRENT_TIME_BEFORE_EXPIRY_NS, - ); - assert_matches!(result, Err(e) if format!("{:?}", e).to_string().contains("InconsistentCredentialJwtClaims")); - } - - #[test] - fn should_fail_validate_ii_presentation_and_claims_if_wrong_effective_subject() { - let id_alias = Principal::from_text(ID_ALIAS_FOR_VP).expect("wrong principal"); - let id_dapp = Principal::from_text(ID_RP_FOR_VP).expect("wrong principal"); - let vp_jwt = build_ii_verifiable_presentation_jwt( - id_dapp, - ID_ALIAS_VC_FOR_VP_JWS.to_string(), - VERIFIED_ADULT_VC_FOR_VP_JWS.to_string(), - ) - .expect("vp-creation failed"); - let result = validate_ii_presentation_and_claims( - &vp_jwt, - id_alias, // wrong effective subject - &default_test_vc_flow_signers(), - &verified_adult_vc_spec(), - &test_ic_root_pk_raw(), - CURRENT_TIME_BEFORE_EXPIRY_NS, - ); - assert_matches!(result, Err(e) if format!("{:?}", e).to_string().contains("unexpected vc subject")); - } - - #[test] - fn should_fail_validate_ii_presentation_and_claims_if_expired() { - let id_dapp = Principal::from_text(ID_RP_FOR_VP).expect("wrong principal"); - let vp_jwt = build_ii_verifiable_presentation_jwt( - id_dapp, - ID_ALIAS_VC_FOR_VP_JWS.to_string(), - VERIFIED_ADULT_VC_FOR_VP_JWS.to_string(), - ) - .expect("vp-creation failed"); - let result = validate_ii_presentation_and_claims( - &vp_jwt, - id_dapp, - &default_test_vc_flow_signers(), - &verified_adult_vc_spec(), - &test_ic_root_pk_raw(), - CURRENT_TIME_AFTER_EXPIRY_NS, - ); - assert_matches!(result, Err(e) if format!("{:?}", e).to_string().contains("credential expired")); - } - - #[test] - fn should_fail_validate_ii_presentation_and_claims_if_wrong_vcs() { - let id_dapp = Principal::from_text(ID_RP_FOR_VP).expect("wrong principal"); - let vp_jwt = build_ii_verifiable_presentation_jwt( - id_dapp, - ID_ALIAS_VC_FOR_VP_JWS.to_string(), - VERIFIED_EMPLOYEE_VC_FOR_VP_JWS.to_string(), - ) - .expect("vp-creation failed"); - let result = validate_ii_presentation_and_claims( - &vp_jwt, - id_dapp, - &default_test_vc_flow_signers(), - &verified_adult_vc_spec(), - &test_ic_root_pk_raw(), - CURRENT_TIME_BEFORE_EXPIRY_NS, - ); - assert_matches!(result, Err(e) if format!("{:?}", e).to_string().contains("InconsistentCredentialJwtClaims")); - } - - // Removes nbf-entry from the given VC-JWT. - fn remove_nbf(vc_jwt: &str) -> String { - let mut ret = vc_jwt.to_string(); - let nbf_start = vc_jwt.find("\"nbf\"").unwrap(); - let nbf_end = vc_jwt.find("\"jti\"").unwrap(); - ret.replace_range(nbf_start..nbf_end, ""); - ret - } - - #[test] - fn should_build_credential_jwt() { - let example_jwt = "{\"exp\":1620329470,\"iss\":\"https://age_verifier.info/\",\"nbf\":1707817485,\"jti\":\"https://age_verifier.info/credentials/42\",\"sub\":\"did:icp:p2nlc-3s5ul-lcu74-t6pn2-ui5im-i4a5f-a4tga-e6znf-tnvlh-wkmjs-dqe\",\"vc\":{\"@context\":\"https://www.w3.org/2018/credentials/v1\",\"type\":[\"VerifiableCredential\",\"VerifiedAdult\"],\"credentialSubject\":{\"VerifiedAdult\":{\"minAge\":18}}}}"; - let example_jwt_without_nbf = "{\"exp\":1620329470,\"iss\":\"https://age_verifier.info/\",\"jti\":\"https://age_verifier.info/credentials/42\",\"sub\":\"did:icp:p2nlc-3s5ul-lcu74-t6pn2-ui5im-i4a5f-a4tga-e6znf-tnvlh-wkmjs-dqe\",\"vc\":{\"@context\":\"https://www.w3.org/2018/credentials/v1\",\"type\":[\"VerifiableCredential\",\"VerifiedAdult\"],\"credentialSubject\":{\"VerifiedAdult\":{\"minAge\":18}}}}"; - let id_dapp = Principal::from_text(ID_RP_FOR_VP).expect("wrong principal"); - let params = CredentialParams { - spec: verified_adult_vc_spec(), - subject_id: did_for_principal(id_dapp), - credential_id_url: "https://age_verifier.info/credentials/42".to_string(), - issuer_url: "https://age_verifier.info".to_string(), - expiration_timestamp_s: (CURRENT_TIME_BEFORE_EXPIRY_NS / 1_000_000_000) as u32, - }; - let credential = build_credential_jwt(params); - assert_eq!(credential.len(), example_jwt.len()); - // First check that the built credential differs from the example one (they have different nbf-entries). - assert_ne!(credential, example_jwt); - // After the removal of the nbf-entries, all the remaining information should be identical. - assert_eq!(remove_nbf(credential.as_str()), example_jwt_without_nbf); - } -}