diff --git a/.github/workflows/backend-tests.yml b/.github/workflows/backend-tests.yml index 99f782076a..6a38b4cec9 100644 --- a/.github/workflows/backend-tests.yml +++ b/.github/workflows/backend-tests.yml @@ -25,7 +25,7 @@ jobs: ~/.cargo/registry ~/.cargo/git target - key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}-1 + key: ${{ runner.os }}-cargo-dummy-captcha-${{ hashFiles('**/Cargo.lock') }}-1 - name: Cache ~/.cabal/store uses: actions/cache@v2 @@ -66,7 +66,9 @@ jobs: - name: Build backend canister run: | - cargo build --target wasm32-unknown-unknown --release + # we use the dummy_captcha feature which ensures the captcha string + # is always "a" + cargo build --features dummy_captcha --target wasm32-unknown-unknown --release - name: Run Tests shell: bash diff --git a/.github/workflows/selenium.yml b/.github/workflows/selenium.yml index a909b0b16d..5c6c0b6989 100644 --- a/.github/workflows/selenium.yml +++ b/.github/workflows/selenium.yml @@ -68,6 +68,9 @@ jobs: - name: Deploy Internet Identity run: | export II_ENV=development + # we use the dummy_captcha feature which ensures the captcha string + # is always "a" + export USE_DUMMY_CAPTCHA=1 dfx deploy --no-wallet --argument '(null)' - name: Deploy whoami canister diff --git a/Cargo.lock b/Cargo.lock index d1fe0d4ec0..226906ebf9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,25 +3,42 @@ version = 3 [[package]] -name = "aho-corasick" -version = "0.7.15" +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "adler32" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" + +[[package]] +name = "ahash" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ - "memchr", + "getrandom 0.2.3", + "once_cell", + "version_check", ] [[package]] -name = "anyhow" -version = "1.0.44" +name = "aho-corasick" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61604a8f862e1d5c3229fdd78f8b02c68dcf73a4c4b05fd636d12240aaa242c1" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] [[package]] -name = "arrayref" -version = "0.3.6" +name = "anyhow" +version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" +checksum = "8b26702f315f53b6071259e15dd9d64528213b44d61de1ec926eca7715d62203" [[package]] name = "arrayvec" @@ -31,9 +48,9 @@ checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" [[package]] name = "ascii-canvas" -version = "2.0.0" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff8eb72df928aafb99fe5d37b383f2fe25bd2a765e3e5f7c365916b6f2463a29" +checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" dependencies = [ "term", ] @@ -69,9 +86,9 @@ checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" [[package]] name = "beef" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6736e2428df2ca2848d846c43e88745121a6654696e349ce0054a420815a7409" +checksum = "bed554bd50246729a1ec158d08aa3235d1b69d94ad120ebe187e28894787e736" [[package]] name = "binread" @@ -112,15 +129,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" [[package]] -name = "blake2b_simd" -version = "0.5.11" +name = "bitflags" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587" -dependencies = [ - "arrayref", - "arrayvec", - "constant_time_eq", -] +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "block-buffer" @@ -131,6 +143,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bytemuck" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72957246c41db82b8ef88a5486143830adeb8227ef9837740bdec67724cf2c5b" + [[package]] name = "byteorder" version = "1.4.3" @@ -139,9 +157,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "candid" -version = "0.7.7" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17c06d715bc063c90124f5bdec5029ed537c564f037fc64617c71a67fe107543" +checksum = "1d7577605c33073dcafc17a5ed6373aa0cb7005e7d4e4b7cd40ca01cb2385533" dependencies = [ "anyhow", "binread", @@ -149,7 +167,7 @@ dependencies = [ "candid_derive", "codespan-reporting", "hex", - "ic-types 0.2.1", + "ic-types 0.2.2", "lalrpop", "lalrpop-util", "leb128", @@ -176,6 +194,19 @@ dependencies = [ "syn", ] +[[package]] +name = "captcha" +version = "0.0.8" +source = "git+https://github.com/nmattia/captcha?rev=4751b6fa4e56229c2af5b9a28195ff039551d8e7#4751b6fa4e56229c2af5b9a28195ff039551d8e7" +dependencies = [ + "base64", + "hound", + "image", + "lodepng", + "rand 0.7.3", + "serde_json", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -193,33 +224,69 @@ dependencies = [ ] [[package]] -name = "constant_time_eq" -version = "0.1.5" +name = "color_quant" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" [[package]] -name = "cpuid-bool" -version = "0.1.2" +name = "cpufeatures" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634" +checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" +dependencies = [ + "libc", +] [[package]] name = "crc32fast" -version = "1.2.1" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "738c290dfaea84fc1ca15ad9c168d083b05a714e1efddd8edaab678dc28d2836" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" +checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" dependencies = [ "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "lazy_static", + "memoffset", + "scopeguard", ] [[package]] name = "crossbeam-utils" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7e9d99fa91428effe99c5c6d4634cdeba32b8cf784fc428a2a687f61a952c49" +checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db" dependencies = [ - "autocfg", "cfg-if", "lazy_static", ] @@ -239,9 +306,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.12.3" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9d6ddad5866bb2170686ed03f6839d31a76e5407d80b1c334a2c24618543ffa" +checksum = "757c0ded2af11d8e739c4daea1ac623dd1624b06c844cf3f5a39f1bdbd99bb12" dependencies = [ "darling_core", "darling_macro", @@ -249,9 +316,9 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.12.3" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9ced1fd13dc386d5a8315899de465708cf34ee2a6d9394654515214e67bb846" +checksum = "2c34d8efb62d0c2d7f60ece80f75e5c63c1588ba68032740494b0b9a996466e3" dependencies = [ "fnv", "ident_case", @@ -263,15 +330,25 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.12.3" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a7a1445d54b2f9792e3b31a3e715feabbace393f38dc4ffd49d94ee9bc487d5" +checksum = "ade7bff147130fe5e6d39f089c6bd49ec0250f35d70b2eebf72afdfc919f15cc" dependencies = [ "darling_core", "quote", "syn", ] +[[package]] +name = "deflate" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174" +dependencies = [ + "adler32", + "byteorder", +] + [[package]] name = "derivative" version = "2.2.0" @@ -299,10 +376,20 @@ dependencies = [ ] [[package]] -name = "dirs" -version = "1.0.5" +name = "dirs-next" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" dependencies = [ "libc", "redox_users", @@ -324,12 +411,33 @@ dependencies = [ "log", ] +[[package]] +name = "fallible_collections" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaefd4190151d458f16f0793d3452d7f13aeb3701566a4cefc4c37598876cc00" +dependencies = [ + "hashbrown", +] + [[package]] name = "fixedbitset" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d" +[[package]] +name = "flate2" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f" +dependencies = [ + "cfg-if", + "crc32fast", + "libc", + "miniz_oxide 0.4.4", +] + [[package]] name = "fnv" version = "1.0.7" @@ -359,32 +467,45 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8" +checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" dependencies = [ "cfg-if", "libc", "wasi 0.10.2+wasi-snapshot-preview1", ] +[[package]] +name = "gif" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3a7187e78088aead22ceedeee99779455b23fc231fe13ec443f99bb71694e5b" +dependencies = [ + "color_quant", + "weezl", +] + [[package]] name = "half" -version = "1.7.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62aca2aba2d62b4a7f5b33f3712cb1b0692779a56fb510499d5c0aa594daeaf3" +checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" [[package]] name = "hashbrown" -version = "0.9.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +dependencies = [ + "ahash", +] [[package]] name = "hermit-abi" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" dependencies = [ "libc", ] @@ -414,6 +535,12 @@ dependencies = [ "proc-macro-hack", ] +[[package]] +name = "hound" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a164bb2ceaeff4f42542bdb847c41517c78a60f5649671b2a07312b6e117549" + [[package]] name = "ic-cdk" version = "0.3.3" @@ -452,22 +579,24 @@ dependencies = [ [[package]] name = "ic-types" -version = "0.1.2" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "968dbc1cc04bf93e94c2ffd3cb370c451d2136425dd38e9ed4cc5ca425d737af" +checksum = "94dd8ec75019bc7159efe6ca2de5392e14a8e82c02ec6dae6d1b32af5337acd1" dependencies = [ "base32", "crc32fast", + "hex", "serde", + "serde_bytes", "sha2", "thiserror", ] [[package]] name = "ic-types" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "471541b20b3d2bb26dd81ac0c44c66eabeaa2ba3641e96606b6c66f86a035a27" +checksum = "b2c021c11ae1d716f45d783f5764f418a11f12aea1fdc4fc8a2b2242e0dae708" dependencies = [ "base32", "crc32fast", @@ -484,29 +613,60 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "image" +version = "0.23.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "gif", + "jpeg-decoder", + "num-iter", + "num-rational", + "num-traits", + "png", + "scoped_threadpool", + "tiff", +] + [[package]] name = "indexmap" -version = "1.6.2" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "824845a0bf897a9042383849b02c1bc219c2383772efcd5c6f9766fa4b81aef3" +checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5" dependencies = [ "autocfg", "hashbrown", ] +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + [[package]] name = "internet_identity" version = "0.1.0" dependencies = [ "base64", + "captcha", "cubehash", "hex", "hex-literal", "ic-cdk", "ic-cdk-macros", "ic-certified-map", - "ic-types 0.1.2", - "rand", + "ic-types 0.1.5", + "rand 0.8.4", + "rand_chacha 0.2.2", + "rand_core 0.5.1", "serde", "serde_bytes", "serde_cbor", @@ -516,18 +676,33 @@ dependencies = [ [[package]] name = "itertools" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d572918e350e82412fe766d24b15e6682fb2ed2bbe018280caa810397cb319" +checksum = "69ddb889f9d0d08a67338271fa9b62996bc788c7796a5c18cf057420aaed5eaf" dependencies = [ "either", ] +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + +[[package]] +name = "jpeg-decoder" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "229d53d58899083193af11e15917b5640cd40b29ff475a1fe4ef725deb02d0f2" +dependencies = [ + "rayon", +] + [[package]] name = "lalrpop" -version = "0.19.5" +version = "0.19.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46962a8c71b91c3524b117dfdd70844d4265a173c4c9109f98171aebdcf1195f" +checksum = "b15174f1c529af5bf1283c3bc0058266b483a67156f79589fab2a25e23cf8988" dependencies = [ "ascii-canvas", "atty", @@ -548,9 +723,9 @@ dependencies = [ [[package]] name = "lalrpop-util" -version = "0.19.5" +version = "0.19.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a708007b751af124d09e9c5d97515257902bc6b486a56b40bcafd939e8ff467" +checksum = "d3e58cce361efcc90ba8a0a5f982c741ff86b603495bb15a998412e957dcd278" dependencies = [ "regex", ] @@ -563,15 +738,35 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "leb128" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3576a87f2ba00f6f106fdfcd16db1d698d648a26ad8e0573cad8537c3c362d2a" +checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.93" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9385f66bf6105b241aa65a61cb923ef20efc665cb9f9bb50ac2f0c4b7f378d41" +checksum = "8521a1b57e76b1ec69af7599e75e38e7b7fad6610f037db8c79b127201b5d119" + +[[package]] +name = "lock_api" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "lodepng" +version = "3.4.7" +source = "git+https://github.com/kornelski/lodepng-rust?rev=8ceb5b1ffc9c1ab1f8c7ace9bde5f01a5be0aaa2#8ceb5b1ffc9c1ab1f8c7ace9bde5f01a5be0aaa2" +dependencies = [ + "fallible_collections", + "flate2", + "libc", + "rgb", +] [[package]] name = "log" @@ -608,9 +803,37 @@ dependencies = [ [[package]] name = "memchr" -version = "2.3.4" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + +[[package]] +name = "memoffset" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9" +dependencies = [ + "autocfg", +] + +[[package]] +name = "miniz_oxide" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435" +dependencies = [ + "adler32", +] + +[[package]] +name = "miniz_oxide" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" +checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +dependencies = [ + "adler", + "autocfg", +] [[package]] name = "new_debug_unreachable" @@ -620,9 +843,9 @@ checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" [[package]] name = "num-bigint" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74e768dff5fb39a41b3bcd30bb25cf989706c90d028d1ad71971987aa309d535" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" dependencies = [ "autocfg", "num-integer", @@ -639,6 +862,28 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.14" @@ -648,11 +893,21 @@ dependencies = [ "autocfg", ] +[[package]] +name = "num_cpus" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +dependencies = [ + "hermit-abi", + "libc", +] + [[package]] name = "num_enum" -version = "0.5.1" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "226b45a5c2ac4dd696ed30fa6b94b057ad909c7b7fc2e0d0808192bced894066" +checksum = "3f9bd055fb730c4f8f4f57d45d35cd6b3f0980535b056dc7ff119cee6a66ed6f" dependencies = [ "derivative", "num_enum_derive", @@ -660,9 +915,9 @@ dependencies = [ [[package]] name = "num_enum_derive" -version = "0.5.1" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c0fd9eba1d5db0994a239e09c1be402d35622277e35468ba891aa5e3188ce7e" +checksum = "486ea01961c4a818096de679a8b740b26d9033146ac5291b1c98557658f8cdd9" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -670,17 +925,48 @@ dependencies = [ "syn", ] +[[package]] +name = "once_cell" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" + [[package]] name = "opaque-debug" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi", +] + [[package]] name = "paste" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf547ad0c65e31259204bd90935776d1c693cec2f4ff7abb7a1bbbd40dfe58" +checksum = "0744126afe1a6dd7f394cb50a716dbe086cb06e255e53d8d0185d82828358fb5" [[package]] name = "petgraph" @@ -703,15 +989,27 @@ dependencies = [ [[package]] name = "pico-args" -version = "0.4.0" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db8bcd96cb740d03149cbad5518db9fd87126a10ab519c011893b1754134c468" + +[[package]] +name = "png" +version = "0.16.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d70072c20945e1ab871c472a285fc772aefd4f5407723c206242f2c6f94595d6" +checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6" +dependencies = [ + "bitflags", + "crc32fast", + "deflate", + "miniz_oxide 0.3.7", +] [[package]] name = "ppv-lite86" -version = "0.2.10" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" +checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba" [[package]] name = "precomputed-hash" @@ -731,10 +1029,11 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "0.1.5" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" +checksum = "1ebace6889caf889b4d3f76becee12e90353f2b8c7d875534a71e5742f8f6f83" dependencies = [ + "thiserror", "toml", ] @@ -746,84 +1045,152 @@ checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" [[package]] name = "proc-macro2" -version = "1.0.26" +version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec" +checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43" dependencies = [ "unicode-xid", ] [[package]] name = "quote" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" dependencies = [ "proc-macro2", ] [[package]] name = "rand" -version = "0.8.3" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" dependencies = [ + "getrandom 0.1.16", "libc", - "rand_chacha", - "rand_core", - "rand_hc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc 0.2.0", +] + +[[package]] +name = "rand" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.3", + "rand_hc 0.3.1", ] [[package]] name = "rand_chacha" -version = "0.3.0" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.3", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", ] [[package]] name = "rand_core" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" dependencies = [ - "getrandom 0.2.2", + "getrandom 0.2.3", ] [[package]] name = "rand_hc" -version = "0.3.0" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_hc" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" +checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" dependencies = [ - "rand_core", + "rand_core 0.6.3", +] + +[[package]] +name = "rayon" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90" +dependencies = [ + "autocfg", + "crossbeam-deque", + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "lazy_static", + "num_cpus", ] [[package]] name = "redox_syscall" -version = "0.1.57" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" +checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" +dependencies = [ + "bitflags", +] [[package]] name = "redox_users" -version = "0.3.5" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d" +checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" dependencies = [ - "getrandom 0.1.16", + "getrandom 0.2.3", "redox_syscall", - "rust-argon2", ] [[package]] name = "regex" -version = "1.4.5" +version = "1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957056ecddbeba1b26965114e191d2e8589ce74db242b6ea25fc4062427a5c19" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" dependencies = [ "aho-corasick", "memchr", @@ -832,33 +1199,48 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.23" +version = "0.6.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5f089152e60f62d28b835fbff2cd2e8dc0baf1ac13343bef92ab7eed84548" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" [[package]] -name = "rust-argon2" -version = "0.8.3" +name = "rgb" +version = "0.8.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b18820d944b33caa75a71378964ac46f58517c92b6ae5f762636247c09e78fb" +checksum = "a27fa03bb1e3e2941f52d4a555a395a72bf79b0a85fbbaab79447050c97d978c" dependencies = [ - "base64", - "blake2b_simd", - "constant_time_eq", - "crossbeam-utils", + "bytemuck", ] [[package]] name = "rustversion" -version = "1.0.4" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61b3909d758bb75c79f23d4736fac9433868679d3ad2ea7a61e3c25cfda9a088" + +[[package]] +name = "ryu" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c9613b5a66ab9ba26415184cfc41156594925a9cf3a2057e57f31ff145f6568" + +[[package]] +name = "scoped_threadpool" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" + +[[package]] +name = "scopeguard" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb5d2a036dc6d2d8fd16fde3498b04306e29bd193bf306a57427019b823d5acd" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "serde" -version = "1.0.125" +version = "1.0.130" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171" +checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" dependencies = [ "serde_derive", ] @@ -874,9 +1256,9 @@ dependencies = [ [[package]] name = "serde_cbor" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e18acfa2f90e8b735b2836ab8d538de304cbb6729a7360729ea5a895d15a622" +checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" dependencies = [ "half", "serde", @@ -884,15 +1266,26 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.125" +version = "1.0.130" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d" +checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b" dependencies = [ "proc-macro2", "quote", "syn", ] +[[package]] +name = "serde_json" +version = "1.0.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0ffa0837f2dfa6fb90868c2b5468cad482e175f7dad97e7421951e663f2b527" +dependencies = [ + "itoa", + "ryu", + "serde", +] + [[package]] name = "serde_tokenstream" version = "0.1.2" @@ -906,9 +1299,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "1.8.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e557c650adfb38b32a5aec07082053253c703bc3cec654b27a5dbcf61995bb9b" +checksum = "ad6056b4cb69b6e43e3a0f055def223380baecc99da683884f205bf347f7c4b3" dependencies = [ "rustversion", "serde", @@ -917,9 +1310,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "1.4.1" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e48b35457e9d855d3dc05ef32a73e0df1e2c0fd72c38796a4ee909160c8eeec2" +checksum = "12e47be9471c72889ebafb5e14d5ff930d89ae7a67bbdb5f8abb564f845a927e" dependencies = [ "darling", "proc-macro2", @@ -929,31 +1322,38 @@ dependencies = [ [[package]] name = "sha2" -version = "0.9.3" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa827a14b29ab7f44778d14a88d3cb76e949c45083f7dbfa507d0cb699dc12de" +checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa" dependencies = [ "block-buffer", "cfg-if", - "cpuid-bool", + "cpufeatures", "digest", "opaque-debug", ] [[package]] name = "siphasher" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbce6d4507c7e4a3962091436e56e95290cb71fa302d0d270e32130b75fbff27" +checksum = "533494a8f9b724d33625ab53c6c4800f7cc445895924a8ef649222dcb76e938b" + +[[package]] +name = "smallvec" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" [[package]] name = "string_cache" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ddb1139b5353f96e429e1a5e19fbaf663bddedaa06d1dbd49f82e352601209a" +checksum = "923f0f39b6267d37d23ce71ae7235602134b250ace715dd2c90421998ddac0c6" dependencies = [ "lazy_static", "new_debug_unreachable", + "parking_lot", "phf_shared", "precomputed-hash", ] @@ -966,9 +1366,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "1.0.69" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48fe99c6bd8b1cc636890bcc071842de909d902c81ac7dab53ba33c421ab8ffb" +checksum = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59" dependencies = [ "proc-macro2", "quote", @@ -977,12 +1377,12 @@ dependencies = [ [[package]] name = "term" -version = "0.5.2" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edd106a334b7657c10b7c540a0106114feadeb4dc314513e97df481d5d966f42" +checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" dependencies = [ - "byteorder", - "dirs", + "dirs-next", + "rustversion", "winapi", ] @@ -997,24 +1397,35 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.24" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0f4a65597094d4483ddaed134f409b2cb7c1beccf25201a9f73c719254fa98e" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.24" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7765189610d8241a44529806d6fd1f2e0a08734313a35d5b3a556f92b381f3c0" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" dependencies = [ "proc-macro2", "quote", "syn", ] +[[package]] +name = "tiff" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a53f4706d65497df0c4349241deddf35f84cee19c87ed86ea8ca590f4464437" +dependencies = [ + "jpeg-decoder", + "miniz_oxide 0.4.4", + "weezl", +] + [[package]] name = "tiny-keccak" version = "2.0.2" @@ -1041,21 +1452,21 @@ checksum = "0685c84d5d54d1c26f7d3eb96cd41550adb97baed141a761cf335d3d33bcd0ae" [[package]] name = "typenum" -version = "1.13.0" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06" +checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec" [[package]] name = "unicode-width" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" +checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" [[package]] name = "unicode-xid" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" [[package]] name = "utf8-ranges" @@ -1081,6 +1492,12 @@ version = "0.10.2+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" +[[package]] +name = "weezl" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b77fdfd5a253be4ab714e4ffa3c49caf146b4de743e97510c0656cf90f1e8e" + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index f8e5b533c8..469dd6387d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,3 +7,8 @@ members = [ [profile.release] lto = true opt-level = 'z' + +[patch.crates-io] +# We need a custom lodepng-rust that builds on wasm32-unknown-unknown +# https://github.com/dfinity/internet-identity/issues/471 +lodepng = { git = 'https://github.com/kornelski/lodepng-rust', rev = '8ceb5b1ffc9c1ab1f8c7ace9bde5f01a5be0aaa2' } diff --git a/backend-tests/README.md b/backend-tests/README.md index 051f71c44e..9c29b42e71 100644 --- a/backend-tests/README.md +++ b/backend-tests/README.md @@ -14,6 +14,7 @@ Setup Running ------- + * Build the top-level directory, build the backend canister (`dfx build internet_identity`) * In the present directory, run ``` @@ -27,7 +28,9 @@ By default, this tests the wasm file in ../target/wasm32-unknown-unknown/release/internet_identity.wasm -to use a different one, pass the `--wasm` flag to `backend-tests` +to use a different one, pass the `--wasm` flag to `backend-tests`. The tests +use a preset CAPTCHA value, so you will need to build with the following +environment variable: `USE_DUMMY_CAPTCHA=1`. You can select tests to run using `-p`, e.g. diff --git a/backend-tests/backend-tests.hs b/backend-tests/backend-tests.hs index 60ca578dea..39b67c752c 100644 --- a/backend-tests/backend-tests.hs +++ b/backend-tests/backend-tests.hs @@ -7,6 +7,7 @@ {-# LANGUAGE OverloadedLabels #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TupleSections #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeOperators #-} @@ -97,6 +98,7 @@ type RegisterResponse = [Candid.candidType| variant { registered: record { user_number: nat64; }; canister_full; + bad_challenge; } |] @@ -133,6 +135,11 @@ type ProofOfWork = [Candid.candidType|record { nonce : nat64; }|] +type ChallengeResult = [Candid.candidType|record { + key : text; + chars: text; +}|] + type HttpRequest = [Candid.candidType|record { method : text; url : text; @@ -210,7 +217,6 @@ setCanisterTimeTo cid new_time = modify $ \ic -> ic { canisters = M.adjust (\cs -> cs { time = new_time }) (EntityId cid) (canisters ic) } - callManagement :: forall s a b. HasCallStack => KnownSymbol s => @@ -307,7 +313,7 @@ callIIRejectWith cid user_id l x expectedMessagePattern = do r <- submitAndRun $ CallRequest (EntityId cid) user_id (symbolVal l) (Candid.encode x) case r of - Rejected (_code, msg) -> + Rejected (_code, msg) -> if not (msg =~ expectedMessagePattern) then liftIO $ assertFailure $ printf "expected error matching %s, got: %s" (show expectedMessagePattern) (show msg) else return () @@ -319,21 +325,21 @@ callIIRejectWith cid user_id l x expectedMessagePattern = do -- (e.g. with 'createSecretKeyWebAuthnECDSA'). This ensures the key contents -- are stable. -- See also: https://github.com/dfinity/ic-hs/issues/59 -webauthPK :: PublicKey -webauthPK = "0^0\f\ACK\n+\ACK\SOH\EOT\SOH\131\184C\SOH\SOH\ETXN\NUL\165\SOH\STX\ETX& \SOH!X lR\190\173]\245, \138\155\FS{\224\166\bGW>[\228\172O\224\142\164\128\&6\208\186\GS*\207\"X \179=\174\184;\201\199}\138\215b\253h\227\234\176\134\132\228c\196\147Q\179\171*\DC4\164\NUL\DC3\131\135" -webauthID :: EntityId -webauthID = EntityId $ mkSelfAuthenticatingId webauthPK +webauth1PK :: PublicKey +webauth1PK = "0^0\f\ACK\n+\ACK\SOH\EOT\SOH\131\184C\SOH\SOH\ETXN\NUL\165\SOH\STX\ETX& \SOH!X lR\190\173]\245, \138\155\FS{\224\166\bGW>[\228\172O\224\142\164\128\&6\208\186\GS*\207\"X \179=\174\184;\201\199}\138\215b\253h\227\234\176\134\132\228c\196\147Q\179\171*\DC4\164\NUL\DC3\131\135" +webauth1ID :: EntityId +webauth1ID = EntityId $ mkSelfAuthenticatingId webauth1PK device1 :: DeviceData device1 = empty .+ #alias .== "device1" - .+ #pubkey .== webauthPK + .+ #pubkey .== webauth1PK .+ #credential_id .== Nothing .+ #purpose .== enum #authentication .+ #key_type .== enum #cross_platform webauth2SK :: SecretKey webauth2SK = createSecretKeyWebAuthnRSA "foobar2" --- The content here doesn't matter as long as it's different from webauthPK +-- The content here doesn't matter as long as it's different from webauth1PK webauth2PK = toPublicKey webauth2SK webauth2PK :: PublicKey webauth2ID :: EntityId @@ -376,8 +382,8 @@ addTS :: (a, b, c, d) -> e -> (a, b, c, e) addTS (a,b,c, _) ts = (a,b,c,ts) getAndValidate :: HasCallStack => Blob -> Blob -> Blob -> EntityId -> (Word64, T.Text, Blob, Maybe Word64) -> Word64 -> M () -getAndValidate cid sessionPK userPK webauthID delegationArgs ts = do - sd <- queryII cid webauthID #get_delegation (addTS delegationArgs ts) >>= \case +getAndValidate cid sessionPK userPK webauth1ID delegationArgs ts = do + sd <- queryII cid webauth1ID #get_delegation (addTS delegationArgs ts) >>= \case V.IsJust (V.Label :: Label "signed_delegation") sd -> return sd V.IsJust (V.Label :: Label "no_such_delegation") () -> liftIO $ assertFailure "Got unexpected no_such_delegation" @@ -392,8 +398,8 @@ getAndValidate cid sessionPK userPK webauthID delegationArgs ts = do Right () -> return () getButNotThere :: HasCallStack => Blob -> EntityId -> (Word64, T.Text, Blob, Maybe Word64) -> Word64 -> M () -getButNotThere cid webauthID delegationArgs ts = do - queryII cid webauthID #get_delegation (addTS delegationArgs ts) >>= \case +getButNotThere cid webauth1ID delegationArgs ts = do + queryII cid webauth1ID #get_delegation (addTS delegationArgs ts) >>= \case V.IsJust (V.Label :: Label "signed_delegation") _ -> liftIO $ assertFailure "Unexpected delegation" V.IsJust (V.Label :: Label "no_such_delegation") () -> return () @@ -494,23 +500,24 @@ tests wasm_file = testGroup "Tests" $ upgradeGroups $ , withoutUpgrade $ iiTest "installs and upgrade" $ \ cid -> doUpgrade cid , withoutUpgrade $ iiTest "register with wrong user fails" $ \cid -> do - callIIRejectWith cid dummyUserId #register (device1, powAt cid 1) "[a-z0-9-]+ could not be authenticated against" - , withoutUpgrade $ iiTest "register with bad pow fails" $ \cid -> do - callIIRejectWith cid webauthID #register (device1, invalidPOW) "proof of work hash check failed" - , withoutUpgrade $ iiTest "register with future pow fails" $ \cid -> do - callIIRejectWith cid webauthID #register (device1, powAt cid (20*60*1000_000_000)) "proof of work timestamp [0-9]+ is too far in future, current time: [0-9]+" - , withoutUpgrade $ iiTest "register with past pow fails" $ \cid -> do + challenge <- getChallenge cid dummyUserId (powAt cid 1) + callIIRejectWith cid dummyUserId #register (device1, challenge) "[a-z0-9-]+ could not be authenticated against" + , withoutUpgrade $ iiTest "create_challenge with bad pow fails" $ \cid -> do + callIIRejectWith cid webauth1ID #create_challenge invalidPOW "proof of work hash check failed" + , withoutUpgrade $ iiTest "create_challenge with future pow fails" $ \cid -> do + callIIRejectWith cid webauth1ID #create_challenge (powAt cid (20*60*1000_000_000)) "proof of work timestamp [0-9]+ is too far in future, current time: [0-9]+" + , withoutUpgrade $ iiTest "create_challenge with past pow fails" $ \cid -> do setCanisterTimeTo cid (20*60*1000_000_000) - callIIRejectWith cid webauthID #register (device1, powAt cid 1) "proof of work timestamp [0-9]+ is too old, current time: [0-9]+" - , withoutUpgrade $ iiTest "register with repeated pow fails" $ \cid -> do - _ <- callII cid webauthID #register (device1, powAt cid 1) - callIIRejectWith cid webauthID #register (device1, powAt cid 1) "the combination of timestamp [0-9]+ and nonce [0-9]+ has already been used" + callIIRejectWith cid webauth1ID #create_challenge (powAt cid 1) "proof of work timestamp [0-9]+ is too old, current time: [0-9]+" + , withoutUpgrade $ iiTest "create_challenge with repeated pow fails" $ \cid -> do + _ <- register cid webauth1ID device1 (powAt cid 1) + callIIRejectWith cid webauth1ID #create_challenge (powAt cid 1) "the combination of timestamp [0-9]+ and nonce [0-9]+ has already been used" , withoutUpgrade $ iiTest "get delegation without authorization" $ \cid -> do - user_number <- callII cid webauthID #register (device1, powAt cid 0) >>= mustGetUserNumber + user_number <- register cid webauth1ID device1 (powAt cid 0) >>= mustGetUserNumber let sessionSK = createSecretKeyEd25519 "hohoho" let sessionPK = toPublicKey sessionSK let delegationArgs = (user_number, "front.end.com", sessionPK, Nothing) - (_, ts) <- callII cid webauthID #prepare_delegation delegationArgs + (_, ts) <- callII cid webauth1ID #prepare_delegation delegationArgs queryIIRejectWith cid dummyUserId #get_delegation (addTS delegationArgs ts) "[a-z0-9-]+ could not be authenticated" , withUpgrade $ \should_upgrade -> iiTest "lookup on fresh" $ \cid -> do @@ -520,58 +527,58 @@ tests wasm_file = testGroup "Tests" $ upgradeGroups $ lookupIs cid 123 [] , withUpgrade $ \should_upgrade -> iiTest "register and lookup" $ \cid -> do - user_number <- callII cid webauthID #register (device1, powAt cid 0) >>= mustGetUserNumber + user_number <- register cid webauth1ID device1 (powAt cid 0) >>= mustGetUserNumber assertStats cid 1 when should_upgrade $ doUpgrade cid assertStats cid 1 lookupIs cid user_number [device1] , withUpgrade $ \should_upgrade -> iiTest "register and lookup (with credential id)" $ \cid -> do - user_number <- callII cid webauth2ID #register (device2, powAt cid 0) >>= mustGetUserNumber + user_number <- register cid webauth2ID device2 (powAt cid 0) >>= mustGetUserNumber when should_upgrade $ doUpgrade cid lookupIs cid user_number [device2] , withUpgrade $ \should_upgrade -> iiTest "register add lookup" $ \cid -> do - user_number <- callII cid webauthID #register (device1, powAt cid 0) >>= mustGetUserNumber + user_number <- register cid webauth1ID device1 (powAt cid 0) >>= mustGetUserNumber when should_upgrade $ doUpgrade cid - callII cid webauthID #add (user_number, device2) + callII cid webauth1ID #add (user_number, device2) when should_upgrade $ doUpgrade cid lookupIs cid user_number [device1, device2] , withUpgrade $ \should_upgrade -> iiTest "register and add with wrong user" $ \cid -> do - user_number <- callII cid webauthID #register (device1, powAt cid 0) >>= mustGetUserNumber + user_number <- register cid webauth1ID device1 (powAt cid 0) >>= mustGetUserNumber when should_upgrade $ doUpgrade cid callIIReject cid webauth2ID #add (user_number, device2) lookupIs cid user_number [device1] , withUpgrade $ \should_upgrade -> iiTest "register and get principal with wrong user" $ \cid -> do queryIIReject cid webauth2ID #get_principal (10000, "front.end.com") - user_number <- callII cid webauthID #register (device1, powAt cid 0) >>= mustGetUserNumber + user_number <- register cid webauth1ID device1 (powAt cid 0) >>= mustGetUserNumber when should_upgrade $ doUpgrade cid queryIIReject cid webauth2ID #get_principal (user_number, "front.end.com") , withUpgrade $ \should_upgrade -> iiTest "get delegation and validate" $ \cid -> do - user_number <- callII cid webauthID #register (device1, powAt cid 0) >>= mustGetUserNumber + user_number <- register cid webauth1ID device1 (powAt cid 0) >>= mustGetUserNumber let sessionSK = createSecretKeyEd25519 "hohoho" let sessionPK = toPublicKey sessionSK let delegationArgs = (user_number, "front.end.com", sessionPK, Nothing) -- prepare delegation - (userPK, ts) <- callII cid webauthID #prepare_delegation delegationArgs + (userPK, ts) <- callII cid webauth1ID #prepare_delegation delegationArgs ts <- if should_upgrade then do doUpgrade cid -- after upgrade, no signature is available V.IsJust (V.Label :: Label "no_such_delegation") () - <- queryII cid webauthID #get_delegation (addTS delegationArgs ts) + <- queryII cid webauth1ID #get_delegation (addTS delegationArgs ts) -- so request it again - (userPK', ts') <- callII cid webauthID #prepare_delegation delegationArgs + (userPK', ts') <- callII cid webauth1ID #prepare_delegation delegationArgs lift $ userPK' @?= userPK return ts' else return ts V.IsJust (V.Label :: Label "signed_delegation") sd - <- queryII cid webauthID #get_delegation (addTS delegationArgs ts) + <- queryII cid webauth1ID #get_delegation (addTS delegationArgs ts) let delegation = sd .! #delegation let sig = sd .! #signature lift $ delegation .! #pubkey @?= sessionPK @@ -582,7 +589,7 @@ tests wasm_file = testGroup "Tests" $ upgradeGroups $ Right () -> return () , withUpgrade $ \should_upgrade -> iiTest "get delegation with wrong user" $ \cid -> do - user_number <- callII cid webauthID #register (device1, powAt cid 0) >>= mustGetUserNumber + user_number <- register cid webauth1ID device1 (powAt cid 0) >>= mustGetUserNumber when should_upgrade $ do doUpgrade cid @@ -592,96 +599,96 @@ tests wasm_file = testGroup "Tests" $ upgradeGroups $ callIIRejectWith cid webauth2ID #prepare_delegation delegationArgs "[a-z0-9-]+ could not be authenticated." , withUpgrade $ \should_upgrade -> iiTest "get multiple delegations and validate" $ \cid -> do - user_number <- callII cid webauthID #register (device1, powAt cid 0) >>= mustGetUserNumber + user_number <- register cid webauth1ID device1 (powAt cid 0) >>= mustGetUserNumber let sessionSK = createSecretKeyEd25519 "hohoho" let sessionPK = toPublicKey sessionSK let delegationArgs = (user_number, "front.end.com", sessionPK, Nothing) -- request a few delegations - (userPK, ts1) <- callII cid webauthID #prepare_delegation delegationArgs - getAndValidate cid sessionPK userPK webauthID delegationArgs ts1 + (userPK, ts1) <- callII cid webauth1ID #prepare_delegation delegationArgs + getAndValidate cid sessionPK userPK webauth1ID delegationArgs ts1 - (userPK, ts2) <- callII cid webauthID #prepare_delegation delegationArgs - getAndValidate cid sessionPK userPK webauthID delegationArgs ts1 - getAndValidate cid sessionPK userPK webauthID delegationArgs ts2 + (userPK, ts2) <- callII cid webauth1ID #prepare_delegation delegationArgs + getAndValidate cid sessionPK userPK webauth1ID delegationArgs ts1 + getAndValidate cid sessionPK userPK webauth1ID delegationArgs ts2 when should_upgrade $ do doUpgrade cid - (userPK, ts3) <- callII cid webauthID #prepare_delegation delegationArgs - unless should_upgrade $ getAndValidate cid sessionPK userPK webauthID delegationArgs ts1 - unless should_upgrade $ getAndValidate cid sessionPK userPK webauthID delegationArgs ts2 - getAndValidate cid sessionPK userPK webauthID delegationArgs ts3 + (userPK, ts3) <- callII cid webauth1ID #prepare_delegation delegationArgs + unless should_upgrade $ getAndValidate cid sessionPK userPK webauth1ID delegationArgs ts1 + unless should_upgrade $ getAndValidate cid sessionPK userPK webauth1ID delegationArgs ts2 + getAndValidate cid sessionPK userPK webauth1ID delegationArgs ts3 - (userPK, ts4) <- callII cid webauthID #prepare_delegation delegationArgs - unless should_upgrade $ getAndValidate cid sessionPK userPK webauthID delegationArgs ts1 - unless should_upgrade $ getAndValidate cid sessionPK userPK webauthID delegationArgs ts2 - getAndValidate cid sessionPK userPK webauthID delegationArgs ts3 - getAndValidate cid sessionPK userPK webauthID delegationArgs ts4 + (userPK, ts4) <- callII cid webauth1ID #prepare_delegation delegationArgs + unless should_upgrade $ getAndValidate cid sessionPK userPK webauth1ID delegationArgs ts1 + unless should_upgrade $ getAndValidate cid sessionPK userPK webauth1ID delegationArgs ts2 + getAndValidate cid sessionPK userPK webauth1ID delegationArgs ts3 + getAndValidate cid sessionPK userPK webauth1ID delegationArgs ts4 , withoutUpgrade $ iiTest "get multiple delegations and expire" $ \cid -> do - user_number <- callII cid webauthID #register (device1, powAt cid 0) >>= mustGetUserNumber + user_number <- register cid webauth1ID device1 (powAt cid 0) >>= mustGetUserNumber let sessionSK = createSecretKeyEd25519 "hohoho" let sessionPK = toPublicKey sessionSK let delegationArgs = (user_number, "front.end.com", sessionPK, Nothing) -- request a few delegations - (userPK, ts1) <- callII cid webauthID #prepare_delegation delegationArgs - getAndValidate cid sessionPK userPK webauthID delegationArgs ts1 + (userPK, ts1) <- callII cid webauth1ID #prepare_delegation delegationArgs + getAndValidate cid sessionPK userPK webauth1ID delegationArgs ts1 setCanisterTimeTo cid (30*1000_000_000) - (userPK, ts2) <- callII cid webauthID #prepare_delegation delegationArgs - getAndValidate cid sessionPK userPK webauthID delegationArgs ts1 - getAndValidate cid sessionPK userPK webauthID delegationArgs ts2 + (userPK, ts2) <- callII cid webauth1ID #prepare_delegation delegationArgs + getAndValidate cid sessionPK userPK webauth1ID delegationArgs ts1 + getAndValidate cid sessionPK userPK webauth1ID delegationArgs ts2 setCanisterTimeTo cid (70*1000_000_000) - (userPK, ts3) <- callII cid webauthID #prepare_delegation delegationArgs - getButNotThere cid webauthID delegationArgs ts1 - getAndValidate cid sessionPK userPK webauthID delegationArgs ts2 - getAndValidate cid sessionPK userPK webauthID delegationArgs ts3 + (userPK, ts3) <- callII cid webauth1ID #prepare_delegation delegationArgs + getButNotThere cid webauth1ID delegationArgs ts1 + getAndValidate cid sessionPK userPK webauth1ID delegationArgs ts2 + getAndValidate cid sessionPK userPK webauth1ID delegationArgs ts3 setCanisterTimeTo cid (120*1000_000_000) - (userPK, ts4) <- callII cid webauthID #prepare_delegation delegationArgs - getButNotThere cid webauthID delegationArgs ts1 - getButNotThere cid webauthID delegationArgs ts2 - getAndValidate cid sessionPK userPK webauthID delegationArgs ts3 - getAndValidate cid sessionPK userPK webauthID delegationArgs ts4 + (userPK, ts4) <- callII cid webauth1ID #prepare_delegation delegationArgs + getButNotThere cid webauth1ID delegationArgs ts1 + getButNotThere cid webauth1ID delegationArgs ts2 + getAndValidate cid sessionPK userPK webauth1ID delegationArgs ts3 + getAndValidate cid sessionPK userPK webauth1ID delegationArgs ts4 , withUpgrade $ \should_upgrade -> iiTest "user identities differ" $ \cid -> do - user_number <- callII cid webauthID #register (device1, powAt cid 0) >>= mustGetUserNumber + user_number <- register cid webauth1ID device1 (powAt cid 0) >>= mustGetUserNumber let sessionSK = createSecretKeyEd25519 "hohoho" let sessionPK = toPublicKey sessionSK let delegationArgs1 = (user_number, "front.end.com", sessionPK, Nothing) - (user1PK, _exp) <- callII cid webauthID #prepare_delegation delegationArgs1 - Principal user1Principal <- queryII cid webauthID #get_principal (user_number, "front.end.com") + (user1PK, _exp) <- callII cid webauth1ID #prepare_delegation delegationArgs1 + Principal user1Principal <- queryII cid webauth1ID #get_principal (user_number, "front.end.com") lift $ user1Principal @?= mkSelfAuthenticatingId user1PK when should_upgrade $ do doUpgrade cid let delegationArgs2 = (user_number, "other-front.end.com", sessionPK, Nothing) - (user2PK, _exp) <- callII cid webauthID #prepare_delegation delegationArgs2 - Principal user2Principal <- queryII cid webauthID #get_principal (user_number, "other-front.end.com") + (user2PK, _exp) <- callII cid webauth1ID #prepare_delegation delegationArgs2 + Principal user2Principal <- queryII cid webauth1ID #get_principal (user_number, "other-front.end.com") lift $ user2Principal @?= mkSelfAuthenticatingId user2PK when (user1PK == user2PK) $ lift $ assertFailure "User identities coincide for different frontends" , withUpgrade $ \should_upgrade -> iiTest "remove()" $ \cid -> do - user_number <- callII cid webauthID #register (device1, powAt cid 0) >>= mustGetUserNumber + user_number <- register cid webauth1ID device1 (powAt cid 0) >>= mustGetUserNumber lookupIs cid user_number [device1] - callII cid webauthID #add (user_number, device2) + callII cid webauth1ID #add (user_number, device2) lookupIs cid user_number [device1, device2] -- NB: removing device that is signing this: - callII cid webauthID #remove (user_number, webauthPK) + callII cid webauth1ID #remove (user_number, webauth1PK) lookupIs cid user_number [device2] when should_upgrade $ doUpgrade cid lookupIs cid user_number [device2] callII cid webauth2ID #remove (user_number, webauth2PK) when should_upgrade $ doUpgrade cid lookupIs cid user_number [] - user_number2 <- callII cid webauthID #register (device1, powAt cid 1) >>= mustGetUserNumber + user_number2 <- register cid webauth1ID device1 (powAt cid 1) >>= mustGetUserNumber when should_upgrade $ doUpgrade cid when (user_number == user_number2) $ lift $ assertFailure "Identity Anchor re-used" @@ -691,10 +698,10 @@ tests wasm_file = testGroup "Tests" $ upgradeGroups $ lift $ s .! #assigned_user_number_range @?= (100, 103) assertStats cid 0 - user_number <- callII cid webauthID #register (device1, powAt cid 0) >>= mustGetUserNumber + user_number <- register cid webauth1ID device1 (powAt cid 0) >>= mustGetUserNumber liftIO $ user_number @?= 100 assertStats cid 1 - user_number <- callII cid webauthID #register (device1, powAt cid 1) >>= mustGetUserNumber + user_number <- register cid webauth1ID device1 (powAt cid 1) >>= mustGetUserNumber liftIO $ user_number @?= 101 assertStats cid 2 @@ -706,20 +713,20 @@ tests wasm_file = testGroup "Tests" $ upgradeGroups $ let expected_upper_bound = if should_upgrade then 100 + 3_774_873 else 103 lift $ s .! #assigned_user_number_range @?= (100, expected_upper_bound) - user_number <- callII cid webauthID #register (device1, powAt cid 0) >>= mustGetUserNumber + user_number <- register cid webauth1ID device1 (powAt cid 0) >>= mustGetUserNumber liftIO $ user_number @?= 102 assertStats cid 3 - callIIReject cid webauthID #register (device1, powAt cid 0) + callIIReject cid webauth1ID #create_challenge (powAt cid 0) assertStats cid 3 , withoutUpgrade $ iiTestWithInit "empty init range" (100, 100) $ \cid -> do s <- queryII cid dummyUserId #stats () lift $ s .! #assigned_user_number_range @?= (100, 100) - response <- callII cid webauthID #register (device1, powAt cid 0) + response <- register cid webauth1ID device1 (powAt cid 0) assertVariant #canister_full response , withUpgrade $ \should_upgrade -> iiTest "metrics endpoint" $ \cid -> do - _ <- callII cid webauth2ID #register (device2, powAt cid 1) >>= mustGetUserNumber + _ <- register cid webauth2ID device2 (powAt cid 1) >>= mustGetUserNumber metrics <- callII cid webauth2ID #http_request (httpGet "/metrics") >>= mustParseMetrics assertMetric metrics "internet_identity_user_count" 1.0 @@ -727,13 +734,13 @@ tests wasm_file = testGroup "Tests" $ upgradeGroups $ when should_upgrade $ doUpgrade cid - userNumber <- callII cid webauthID #register (device1, powAt cid 0) >>= mustGetUserNumber + userNumber <- register cid webauth1ID device1 (powAt cid 0) >>= mustGetUserNumber let sessionSK = createSecretKeyEd25519 "hohoho" let sessionPK = toPublicKey sessionSK let delegationArgs = (userNumber, "front.end.com", sessionPK, Nothing) - _ <- callII cid webauthID #prepare_delegation delegationArgs + _ <- callII cid webauth1ID #prepare_delegation delegationArgs - metrics <- callII cid webauthID #http_request (httpGet "/metrics") >>= mustParseMetrics + metrics <- callII cid webauth1ID #http_request (httpGet "/metrics") >>= mustParseMetrics assertMetric metrics "internet_identity_user_count" 2.0 assertMetric metrics "internet_identity_signature_count" 1.0 @@ -775,13 +782,13 @@ tests wasm_file = testGroup "Tests" $ upgradeGroups $ lookupIs cid 10_002 [#alias .== "andrew-mbp" .+ #credential_id .== Just "\SOH\191#%\217u\247\178L-K\182\254\249J.m\187\179_I\ACK\137\244`\163o\SI\150qz\197Hz\214\&8\153\239\213\159\208\RS\243\138\171\138\"\139\173\170\ESC\148\205\129\149ri\\Dn,7\151\146\175\DEL" .+ #pubkey .== "0^0\f\ACK\n+\ACK\SOH\EOT\SOH\131\184C\SOH\SOH\ETXN\NUL\165\SOH\STX\ETX& \SOH!X rMm*\229BDe\SOH4\228u\170\206\216\216-ER\v\166r\217\137,\141\227M*@\230\243\"X \225\248\159\191\242\224Z>\241\163\\\GS\155\178\222\139^\136V\253q\v\SUBSJ\bA\131\\\183\147\170" .+ #purpose .== enum #authentication .+ #key_type .== enum #unknown,#alias .== "andrew phone chrome" .+ #credential_id .== Just ",\235x\NUL\a\140`~\148\248\233C/\177\205\158\ETX0\129\167" .+ #pubkey .== "0^0\f\ACK\n+\ACK\SOH\EOT\SOH\131\184C\SOH\SOH\ETXN\NUL\165\SOH\STX\ETX& \SOH!X \140\169\203@\ETX\CAN\ETB,\177\153\179\223/|`\US\STX\252r\190s(.\188\136\171\SI\181V*\174@\"X \245<\174AbV\225\234\ENQ\146\247\129\245\ACK\200\205\217\250g\219\179)\197\252\164i\172kXh\180\205" .+ #purpose .== enum #authentication .+ #key_type .== enum #unknown] lookupIs cid 10_029 [#alias .== "Pixel" .+ #credential_id .== Just "\SOH\146\238\160b\223\132\205\231b\239\243F\170\163\167\251D\241\170\EM\216\136\174@r\149\183|LuKu[+{\144\217\ETBL\f\244\GS>\179\146\143\RS\179\DLE\227\179\164\188\NULDQy\223\SI\132\183\248\177\219" .+ #pubkey .== "0^0\f\ACK\n+\ACK\SOH\EOT\SOH\131\184C\SOH\SOH\ETXN\NUL\165\SOH\STX\ETX& \SOH!X \200B>\DEL\GS\248\220\145\245\153\221\&6\131\243uAQCAd>\145k\nw\233\&5\218\SUB~_\244\"X O]7\167=n\ESC\SUB\198\235\208\215s\158\191Gz\143\136\237i\146\203\&6\182\196\129\239\238\SOH\180b" .+ #purpose .== enum #authentication .+ #key_type .== enum #unknown] -- This user record has been created manullay with dfx and our test - -- webauthPK has been added, so that we can actually log into this now + -- webauth1PK has been added, so that we can actually log into this now let dfxPK = "0*0\ENQ\ACK\ETX+ep\ETX!\NUL\241\186;\128\206$\243\130\250\&2\253\a#<\235\142\&0]W\218\254j\211\209\192\SO@\DC3\NAKi&1" - lookupIs cid 10_030 [#alias .== "dfx" .+ #credential_id .== Nothing .+ #pubkey .== dfxPK .+ #purpose .== enum #authentication .+ #key_type .== enum #unknown,#alias .== "testkey" .+ #credential_id .== Nothing .+ #pubkey .== webauthPK .+ #purpose .== enum #authentication .+ #key_type .== enum #unknown] - callII cid webauthID #remove (10_030, dfxPK) - lookupIs cid 10_030 [#alias .== "testkey" .+ #credential_id .== Nothing .+ #pubkey .== webauthPK .+ #purpose .== enum #authentication .+ #key_type .== enum #unknown] + lookupIs cid 10_030 [#alias .== "dfx" .+ #credential_id .== Nothing .+ #pubkey .== dfxPK .+ #purpose .== enum #authentication .+ #key_type .== enum #unknown,#alias .== "testkey" .+ #credential_id .== Nothing .+ #pubkey .== webauth1PK .+ #purpose .== enum #authentication .+ #key_type .== enum #unknown] + callII cid webauth1ID #remove (10_030, dfxPK) + lookupIs cid 10_030 [#alias .== "testkey" .+ #credential_id .== Nothing .+ #pubkey .== webauth1PK .+ #purpose .== enum #authentication .+ #key_type .== enum #unknown] let delegationArgs = (10_030, "example.com", "dummykey", Nothing) - (userPK,_) <- callII cid webauthID #prepare_delegation delegationArgs + (userPK,_) <- callII cid webauth1ID #prepare_delegation delegationArgs -- Check that we get the same user key; this proves that the salt was -- recovered from the backup lift $ userPK @?= "0<0\x0c\&\x06\&\x0a\&+\x06\&\x01\&\x04\&\x01\&\x83\&\xb8\&C\x01\&\x02\&\x03\&,\x00\&\x0a\&\x00\&\x00\&\x00\&\x00\&\x00\&\x00\&\x00\&\x07\&\x01\&\x01\&:\x89\&&\x91\&M\xd1\&\xc8\&6\xec\&g\xba\&f\xac\&d%\xc2\&\x1d\&\xff\&\xd3\&\xca\&\x5c\&Yh\x85\&_\x87\&x\x0a\&\x1e\&\xc5\&y\x85\&" @@ -833,6 +840,15 @@ tests wasm_file = testGroup "Tests" $ upgradeGroups $ .+ #wasm_module .== wasm .+ #arg .== Candid.encode () + getChallenge cid webauthID pow = do + challenge <- callII cid webauthID #create_challenge pow + pure $ #key .== challenge .! #challenge_key .+ #chars .== T.pack "a" + + -- Go through a challenge request/registration flow for this device. + -- NOTE: this (dummily) solves the challenge with the string "a", which is + -- returned by the backend when compiled with USE_DUMMY_CAPTCHA. + register cid webauthID device pow = + getChallenge cid webauthID pow >>= callII cid webauthID #register . (device,) asHex :: Blob -> String asHex = T.unpack . H.encodeHex . BS.toStrict diff --git a/src/frontend/generated/internet_identity_idl.js b/src/frontend/generated/internet_identity_idl.js index 4428e4d02c..bed3fb426a 100644 --- a/src/frontend/generated/internet_identity_idl.js +++ b/src/frontend/generated/internet_identity_idl.js @@ -23,9 +23,18 @@ export const idlFactory = ({ IDL }) => { 'purpose' : Purpose, 'credential_id' : IDL.Opt(CredentialId), }); + const Timestamp = IDL.Nat64; + const ProofOfWork = IDL.Record({ + 'nonce' : IDL.Nat64, + 'timestamp' : Timestamp, + }); + const ChallengeKey = IDL.Text; + const Challenge = IDL.Record({ + 'png_base64' : IDL.Text, + 'challenge_key' : ChallengeKey, + }); const FrontendHostname = IDL.Text; const SessionKey = PublicKey; - const Timestamp = IDL.Nat64; const Delegation = IDL.Record({ 'pubkey' : PublicKey, 'targets' : IDL.Opt(IDL.Vec(IDL.Principal)), @@ -68,11 +77,12 @@ export const idlFactory = ({ IDL }) => { 'status_code' : IDL.Nat16, }); const UserKey = PublicKey; - const ProofOfWork = IDL.Record({ - 'nonce' : IDL.Nat64, - 'timestamp' : Timestamp, + const ChallengeResult = IDL.Record({ + 'key' : ChallengeKey, + 'chars' : IDL.Text, }); const RegisterResponse = IDL.Variant({ + 'bad_challenge' : IDL.Null, 'canister_full' : IDL.Null, 'registered' : IDL.Record({ 'user_number' : UserNumber }), }); @@ -82,6 +92,7 @@ export const idlFactory = ({ IDL }) => { }); return IDL.Service({ 'add' : IDL.Func([UserNumber, DeviceData], [], []), + 'create_challenge' : IDL.Func([ProofOfWork], [Challenge], []), 'get_delegation' : IDL.Func( [UserNumber, FrontendHostname, SessionKey, Timestamp], [GetDelegationResponse], @@ -100,7 +111,11 @@ export const idlFactory = ({ IDL }) => { [UserKey, Timestamp], [], ), - 'register' : IDL.Func([DeviceData, ProofOfWork], [RegisterResponse], []), + 'register' : IDL.Func( + [DeviceData, ChallengeResult], + [RegisterResponse], + [], + ), 'remove' : IDL.Func([UserNumber, DeviceKey], [], []), 'stats' : IDL.Func([], [InternetIdentityStats], ['query']), }); diff --git a/src/frontend/generated/internet_identity_types.d.ts b/src/frontend/generated/internet_identity_types.d.ts index 0cf3941f79..5f34b793a3 100644 --- a/src/frontend/generated/internet_identity_types.d.ts +++ b/src/frontend/generated/internet_identity_types.d.ts @@ -1,4 +1,10 @@ import type { Principal } from '@dfinity/principal'; +export interface Challenge { + 'png_base64' : string, + 'challenge_key' : ChallengeKey, +} +export type ChallengeKey = string; +export interface ChallengeResult { 'key' : ChallengeKey, 'chars' : string } export type CredentialId = Array; export interface Delegation { 'pubkey' : PublicKey, @@ -44,7 +50,8 @@ export interface ProofOfWork { 'nonce' : bigint, 'timestamp' : Timestamp } export type PublicKey = Array; export type Purpose = { 'authentication' : null } | { 'recovery' : null }; -export type RegisterResponse = { 'canister_full' : null } | +export type RegisterResponse = { 'bad_challenge' : null } | + { 'canister_full' : null } | { 'registered' : { 'user_number' : UserNumber } }; export type SessionKey = PublicKey; export interface SignedDelegation { @@ -64,6 +71,7 @@ export type UserKey = PublicKey; export type UserNumber = bigint; export interface _SERVICE { 'add' : (arg_0: UserNumber, arg_1: DeviceData) => Promise, + 'create_challenge' : (arg_0: ProofOfWork) => Promise, 'get_delegation' : ( arg_0: UserNumber, arg_1: FrontendHostname, @@ -82,7 +90,7 @@ export interface _SERVICE { arg_2: SessionKey, arg_3: [] | [bigint], ) => Promise<[UserKey, Timestamp]>, - 'register' : (arg_0: DeviceData, arg_1: ProofOfWork) => Promise< + 'register' : (arg_0: DeviceData, arg_1: ChallengeResult) => Promise< RegisterResponse >, 'remove' : (arg_0: UserNumber, arg_1: DeviceKey) => Promise, diff --git a/src/frontend/src/flows/confirmRegister.ts b/src/frontend/src/flows/confirmRegister.ts index 511fed9a06..b5afd15efc 100644 --- a/src/frontend/src/flows/confirmRegister.ts +++ b/src/frontend/src/flows/confirmRegister.ts @@ -1,31 +1,193 @@ import { html, render } from "lit-html"; +import { displayUserNumber } from "./displayUserNumber"; +import { displayError } from "../components/displayError"; +import { setUserNumber } from "../utils/userNumber"; +import { apiResultToLoginResult, LoginResult } from "./loginUnknown"; +import { Challenge } from "../../generated/internet_identity_types"; +import { WebAuthnIdentity } from "@dfinity/identity"; +import getProofOfWork from "../crypto/pow"; +import { Principal } from "@dfinity/principal"; +import { withLoader } from "../components/loader"; +import { + IIConnection, + canisterIdPrincipal, + ChallengeResult, +} from "../utils/iiConnection"; const pageContent = html`

Confirm new device

-

Please confirm to add your device.

- - +
+

+ + +

Please confirm to add your device.

+ + +
`; -export const confirmRegister = async (): Promise => { +export const confirmRegister = ( + captcha: Promise, + identity: WebAuthnIdentity, + alias: string +): Promise => { const container = document.getElementById("pageContent") as HTMLElement; render(pageContent, container); - return init(); + return init(canisterIdPrincipal, identity, alias, captcha); }; -const init = (): Promise => +const tryRegister = ( + identity: WebAuthnIdentity, + alias: string, + challengeResult: ChallengeResult, + func: (result: LoginResult) => void +) => { + withLoader(async () => { + return IIConnection.register(identity, alias, challengeResult); + }).then((result) => { + if (result.kind == "loginSuccess") { + // Write user number to storage + setUserNumber(result.userNumber); + + // Congratulate user + displayUserNumber(result.userNumber).then(() => { + func(apiResultToLoginResult(result)); + }); + } else if (result.kind == "badChallenge") { + const confirmParagraph = document.querySelector( + ".confirm-paragraph" + ) as HTMLElement; + confirmParagraph.innerHTML = + "The value you entered is incorrect. A new challenge is generated."; + requestCaptcha(); + } else { + // Currently if something goes wrong we only tell the user that + // something went wrong and then reload the page. + displayError({ + title: "Something went wrong", + message: + "We could not create an identity anchor. You will find the full error message below. Click 'ok' to reload.", + primaryButton: "Ok", + detail: JSON.stringify(result), + }) + .then(() => { + window.location.reload(); + }) + .then(() => requestCaptcha()); + } + }); +}; + +// Request a captcha and, when received, update the DOM elements accordingly. +const requestCaptcha = (captcha?: Promise): void => { + const form = document.getElementById("confirmForm") as HTMLFormElement; + const captchaStatusText = document.querySelector( + ".captcha-status-text" + ) as HTMLElement; + captchaStatusText.innerHTML = "Creating CAPTCHA challenge…"; + + const captchaInput = document.querySelector( + "#captchaInput" + ) as HTMLFormElement; + captchaInput.disabled = true; + + const confirmRegisterButton = form.querySelector( + "#confirmRegisterButton" + ) as HTMLFormElement; + confirmRegisterButton.disabled = true; + + captcha = captcha || makeCaptcha(); + + captcha.then((captchaResp) => { + const captchaImg = document.querySelector("#captchaImg"); + if (captchaImg) { + captchaImg.setAttribute( + "src", + `data:image/png;base64, ${captchaResp.png_base64}` + ); + confirmRegisterButton.setAttribute( + "data-captcha-key", + `${captchaResp.challenge_key}` + ); + captchaStatusText.innerHTML = "Please type in the characters you see."; + confirmRegisterButton.disabled = false; + captchaInput.disabled = false; + captchaInput.value = ""; + } + }); +}; + +// This computes a PoW and requests a challenge from the II backend. +// NOTE: The Proof-of-Work is computed in one go (one run of the event loop) so +// nothing else will happen during that time. Better have a loading screen +// shown to the user, or have all buttons disabled, because no other javascript +// will run for a few seconds. +export const makeCaptcha = (): Promise => new Promise((resolve) => { - const confirmRegisterButton = document.getElementById( - "confirmRegisterButton" + setTimeout(() => { + const now_in_ns = BigInt(Date.now()) * BigInt(1000000); + const pow = getProofOfWork(now_in_ns, canisterIdPrincipal); + IIConnection.createChallenge(pow).then((cha) => { + resolve(cha); + }); + }); + }); + +const init = ( + canisterIdPrincipal: Principal, + identity: WebAuthnIdentity, + alias: string, + captcha: Promise +): Promise => { + requestCaptcha(captcha); + + // since the index expects to regain control we unfortunately have to wrap + // this whole logic in a promise that then resolves (giving control back to + // the caller) + return new Promise((resolve) => { + const confirmRegisterButton = document.querySelector( + "#confirmRegisterButton" ) as HTMLFormElement; - const cancelButton = document.getElementById( - "cancelButton" + const captchaInput = document.querySelector( + "#captchaInput" + ) as HTMLFormElement; + const cancelButton = document.querySelector( + "#cancelButton" ) as HTMLButtonElement; - cancelButton.onclick = () => resolve(false); - confirmRegisterButton.onclick = () => resolve(true); + cancelButton.onclick = () => { + resolve(null); + }; + + confirmRegisterButton.onclick = (e) => { + e.preventDefault(); + e.stopPropagation(); + + const captchaStatusText = document.querySelector( + ".captcha-status-text" + ) as HTMLElement; + captchaStatusText.innerHTML = "Checking CAPTCHA challenge…"; + confirmRegisterButton.disabled = true; + + const captchaChars = captchaInput.value; + const captchaKey = confirmRegisterButton.dataset.captchaKey; + + if (captchaKey === undefined) { + console.log("Something went wrong: no captcha key found"); + requestCaptcha(); + return; + } + + const challengeResult: ChallengeResult = { + key: captchaKey, + chars: captchaChars, + }; + + tryRegister(identity, alias, challengeResult, resolve); + }; }); +}; diff --git a/src/frontend/src/flows/loginUnknown.ts b/src/frontend/src/flows/loginUnknown.ts index 89731459b2..d794a2f17d 100644 --- a/src/frontend/src/flows/loginUnknown.ts +++ b/src/frontend/src/flows/loginUnknown.ts @@ -217,6 +217,14 @@ export const apiResultToLoginResult = (result: ApiResult): LoginResult => { "Failed to register with Internet Identity, because there is no space left at the moment. We're working on increasing the capacity.", }; } + case "badChallenge": { + return { + tag: "err", + title: "Failed to register", + message: + "Failed to register with Internet Identity, because the CAPTCHA challenge wasn't successful", + }; + } case "seedPhraseFail": { return { tag: "err", diff --git a/src/frontend/src/flows/register.ts b/src/frontend/src/flows/register.ts index fb6e1e86a9..e86d55b018 100644 --- a/src/frontend/src/flows/register.ts +++ b/src/frontend/src/flows/register.ts @@ -1,16 +1,8 @@ import { WebAuthnIdentity } from "@dfinity/identity"; import { html, render } from "lit-html"; -import { withLoader } from "../components/loader"; -import { - IIConnection, - canisterIdPrincipal, - creationOptions, -} from "../utils/iiConnection"; -import { setUserNumber } from "../utils/userNumber"; -import { confirmRegister } from "./confirmRegister"; -import { displayUserNumber } from "./displayUserNumber"; +import { creationOptions } from "../utils/iiConnection"; +import { confirmRegister, makeCaptcha } from "./confirmRegister"; import { apiResultToLoginResult, LoginResult } from "./loginUnknown"; -import getProofOfWork from "../crypto/pow"; import { nextTick } from "process"; import { icLogo } from "../components/icons"; @@ -73,22 +65,12 @@ const init = (): Promise => return 0 as unknown as WebAuthnIdentity; }); await tick(); - // Do PoW before registering. - const now_in_ns = BigInt(Date.now()) * BigInt(1000000); - const pow = getProofOfWork(now_in_ns, canisterIdPrincipal); + // Kick-start both the captcha creation and the identity + const captcha = makeCaptcha(); const identity = await pendingIdentity; - if (await confirmRegister()) { - const result = await withLoader(async () => - IIConnection.register(identity, alias, pow) - ); - if (result.kind === "loginSuccess") { - setUserNumber(result.userNumber); - await displayUserNumber(result.userNumber); - } - resolve(apiResultToLoginResult(result)); - } else { - resolve(null); - } + await captcha; + const result = await confirmRegister(captcha, identity, alias); + resolve(result); } catch (err) { reject(err); } diff --git a/src/frontend/src/styles/main.css b/src/frontend/src/styles/main.css index aef1f16c24..9735c0a052 100644 --- a/src/frontend/src/styles/main.css +++ b/src/frontend/src/styles/main.css @@ -120,8 +120,8 @@ button { letter-spacing: 0.005em; } -button:hover, -button:focus, +button:not([disabled]):hover, +button:not([disabled]):focus, a:focus { opacity: 0.9; box-shadow: 0 0 0 2px #ffffff, 0 0 3px 5px #29abe2; @@ -137,6 +137,13 @@ img { margin: auto; } +#captchaImg { + height: 120px; + width: 220px; + min-width: 0px; + max-width: 220px; +} + ul { width: 100%; padding: 0; @@ -152,6 +159,10 @@ form { flex-direction: column; } +button:disabled.primary { + background-color: grey; +} + button.primary { background-color: var(--primary); color: var(--background-color); diff --git a/src/frontend/src/test-e2e/views.ts b/src/frontend/src/test-e2e/views.ts index a2be0d3a71..4ea4de9ad4 100644 --- a/src/frontend/src/test-e2e/views.ts +++ b/src/frontend/src/test-e2e/views.ts @@ -50,9 +50,18 @@ export class RegisterView extends View { await this.browser .$("#confirmRegisterButton") .waitForDisplayed({ timeout: 25_000 }); + await this.browser.$("#captchaInput").waitForDisplayed({ timeout: 25_000 }); } async confirmRegisterConfirm(): Promise { + await this.browser.$("#captchaInput").waitForEnabled({ timeout: 40_000 }); + // In tests, the captchas are hard-coded to the following string: "a" + await this.browser.$("#captchaInput").setValue("a"); + await this.browser + .$("#confirmRegisterButton") + // this is a huge timeout because generating the captcha takes a while on + // the emulator + .waitForEnabled({ timeout: 30_000 }); await this.browser.$("#confirmRegisterButton").click(); } diff --git a/src/frontend/src/utils/iiConnection.ts b/src/frontend/src/utils/iiConnection.ts index 03946507e6..d5dea37321 100644 --- a/src/frontend/src/utils/iiConnection.ts +++ b/src/frontend/src/utils/iiConnection.ts @@ -11,6 +11,7 @@ import { PublicKey, SessionKey, CredentialId, + Challenge, UserNumber, FrontendHostname, Timestamp, @@ -21,6 +22,7 @@ import { Purpose, KeyType, DeviceKey, + ChallengeResult, } from "../../generated/internet_identity_types"; import { DelegationChain, @@ -55,19 +57,24 @@ export type RegisterResult = | LoginSuccess | AuthFail | ApiError - | RegisterNoSpace; + | RegisterNoSpace + | BadChallenge; type LoginSuccess = { kind: "loginSuccess"; connection: IIConnection; userNumber: bigint; }; + +type BadChallenge = { kind: "badChallenge" }; type UnknownUser = { kind: "unknownUser"; userNumber: bigint }; type AuthFail = { kind: "authFail"; error: Error }; type ApiError = { kind: "apiError"; error: Error }; type RegisterNoSpace = { kind: "registerNoSpace" }; type SeedPhraseFail = { kind: "seedPhraseFail" }; +export type { ChallengeResult } from "../../generated/internet_identity_types"; + export class IIConnection { protected constructor( public identity: SignIdentity, @@ -78,7 +85,7 @@ export class IIConnection { static async register( identity: WebAuthnIdentity, alias: string, - pow: ProofOfWork + challengeResult: ChallengeResult ): Promise { let delegationIdentity: DelegationIdentity; try { @@ -91,9 +98,6 @@ export class IIConnection { const credential_id = Array.from(identity.rawId); const pubkey = Array.from(identity.getPublicKey().toDer()); - console.log( - `register(DeviceData { alias=${alias}, pubkey=${pubkey}, credential_id=${credential_id} }, ProofOfWork { timestamp=${pow.timestamp}, nonce=${pow.nonce})` - ); let registerResponse: RegisterResponse; try { registerResponse = await actor.register( @@ -104,7 +108,7 @@ export class IIConnection { key_type: { unknown: null }, purpose: { authentication: null }, }, - pow + challengeResult ); } catch (error) { return { kind: "apiError", error }; @@ -120,6 +124,8 @@ export class IIConnection { connection: new IIConnection(identity, delegationIdentity, actor), userNumber, }; + } else if (hasOwnProperty(registerResponse, "bad_challenge")) { + return { kind: "badChallenge" }; } else { console.error("unexpected register response", registerResponse); throw Error("unexpected register response"); @@ -207,6 +213,21 @@ export class IIConnection { return await baseActor.lookup(userNumber); } + static async createChallenge(pow: ProofOfWork): Promise { + const agent = new HttpAgent(); + agent.fetchRootKey(); + const actor = Actor.createActor<_SERVICE>(internet_identity_idl, { + agent, + canisterId: canisterId, + }); + console.log( + `createChallenge(ProofOfWork { timestamp=${pow.timestamp}, nonce=${pow.nonce} })` + ); + const challenge = await actor.create_challenge(pow); + console.log("Challenge Created"); + return challenge; + } + static async lookupAuthenticators( userNumber: UserNumber ): Promise { diff --git a/src/internet_identity/Cargo.toml b/src/internet_identity/Cargo.toml index ddfa4c8fbc..d5318304fa 100644 --- a/src/internet_identity/Cargo.toml +++ b/src/internet_identity/Cargo.toml @@ -16,6 +16,11 @@ serde_bytes = "0.11" serde_cbor = "0.11" serde_with = "1.6.2" sha2 = "0.9.1" +rand_core = "0.5.1" +rand_chacha = "0.2.2" +# We need a custom captcha that allows using custom RNGs +# https://github.com/dfinity/internet-identity/issues/472 +captcha = { git = 'https://github.com/nmattia/captcha', rev = '4751b6fa4e56229c2af5b9a28195ff039551d8e7' } [dev-dependencies] hex-literal = "0.2.1" @@ -23,3 +28,8 @@ rand = "0.8.3" [build-dependencies] sha2 = "0.9.1" + +[features] +# the dummy_captcha feature which ensures the captcha string is always "a" +# (needed for tests) +dummy_captcha = [] diff --git a/src/internet_identity/build.sh b/src/internet_identity/build.sh index 66429c35cc..868703bba0 100755 --- a/src/internet_identity/build.sh +++ b/src/internet_identity/build.sh @@ -8,7 +8,25 @@ npm run build II_DIR="$(dirname "$0")" TARGET="wasm32-unknown-unknown" -cargo build --manifest-path "$II_DIR/Cargo.toml" --target $TARGET --release -j1 +cargo_build_args=( + --manifest-path "$II_DIR/Cargo.toml" + --target "$TARGET" + --release + -j1 + ) + +# This enables the "dummy_captcha" feature which makes sure the captcha string +# is always "a". +# WARNING: this MUST be opt-in, because we DO NOT want this in production, +# EVAR. +if [ "${USE_DUMMY_CAPTCHA:-}" == "1" ] +then + cargo_build_args+=( --features dummy_captcha ) +fi + +echo Running cargo build "${cargo_build_args[@]}" + +cargo build "${cargo_build_args[@]}" # keep version in sync with Dockerfile cargo install ic-cdk-optimizer --version 0.3.1 --root "$II_DIR"/../../target diff --git a/src/internet_identity/internet_identity.did b/src/internet_identity/internet_identity.did index cd3ba19dba..eae2cc0c04 100644 --- a/src/internet_identity/internet_identity.did +++ b/src/internet_identity/internet_identity.did @@ -49,6 +49,11 @@ type KeyType = variant { seed_phrase; }; +type Challenge = record { + png_base64: text; + challenge_key: ChallengeKey; +}; + type DeviceData = record { pubkey : DeviceKey; alias : text; @@ -62,6 +67,8 @@ type RegisterResponse = variant { registered: record { user_number: UserNumber; }; // No more registrations are possible in this instance of the II service canister. canister_full; + // The challenge was not successful. + bad_challenge; }; type Delegation = record { @@ -97,9 +104,17 @@ type ProofOfWork = record { nonce : nat64; }; +type ChallengeKey = text; + +type ChallengeResult = record { + key : ChallengeKey; + chars : text; +}; + service : (opt InternetIdentityInit) -> { init_salt: () -> (); - register : (DeviceData, ProofOfWork) -> (RegisterResponse); + create_challenge : (ProofOfWork) -> (Challenge); + register : (DeviceData, ChallengeResult) -> (RegisterResponse); add : (UserNumber, DeviceData) -> (); remove : (UserNumber, DeviceKey) -> (); lookup : (UserNumber) -> (vec DeviceData) query; diff --git a/src/internet_identity/src/main.rs b/src/internet_identity/src/main.rs index d72faceaa2..b2d54874cd 100644 --- a/src/internet_identity/src/main.rs +++ b/src/internet_identity/src/main.rs @@ -14,6 +14,7 @@ use std::cell::{Cell, RefCell}; use std::collections::HashMap; use std::convert::TryInto; use storage::{Salt, Storage}; +use rand_chacha::rand_core::{SeedableRng, RngCore}; mod assets; @@ -21,6 +22,9 @@ const fn secs_to_nanos(secs: u64) -> u64 { secs * 1_000_000_000 } +#[cfg(not(feature = "dummy_captcha"))] +use captcha::filters::{Wave}; + // 30 mins const DEFAULT_EXPIRATION_PERIOD_NS: u64 = secs_to_nanos(30 * 60); // 8 days @@ -29,6 +33,11 @@ const MAX_EXPIRATION_PERIOD_NS: u64 = secs_to_nanos(8 * 24 * 60 * 60); const DEFAULT_SIGNATURE_EXPIRATION_PERIOD_NS: u64 = secs_to_nanos(60); // 5 mins const POW_NONCE_LIFETIME: u64 = secs_to_nanos(300); +// 5 mins +const CAPTCHA_CHALLENGE_LIFETIME: u64 = secs_to_nanos(300); + +// How many captcha challenges we keep in memory (at most) +const MAX_INFLIGHT_CHALLENGES: usize = 500; const LABEL_ASSETS: &[u8] = b"http_assets"; const LABEL_SIG: &[u8] = b"sig"; @@ -40,9 +49,11 @@ type DeviceKey = PublicKey; type UserKey = PublicKey; type SessionKey = PublicKey; type FrontendHostname = String; -type Timestamp = u64; +type Timestamp = u64; // in nanos since epoch type Signature = ByteBuf; +struct Base64(String); + #[derive(Clone, Debug, CandidType, Deserialize)] enum Purpose { #[serde(rename = "recovery")] @@ -143,6 +154,8 @@ enum RegisterResponse { Registered { user_number: UserNumber }, #[serde(rename = "canister_full")] CanisterFull, + #[serde(rename = "bad_challenge")] + BadChallenge, } mod hash; @@ -199,6 +212,9 @@ struct State { sigs: RefCell, asset_hashes: RefCell, last_upgrade_timestamp: Cell, + // note: we COULD persist this through upgrades, although this is currently NOT persisted + // through upgrades + inflight_challenges: RefCell>, } impl Default for State { @@ -213,10 +229,33 @@ impl Default for State { sigs: RefCell::new(SignatureMap::default()), asset_hashes: RefCell::new(AssetHashes::default()), last_upgrade_timestamp: Cell::new(0), + inflight_challenges: RefCell::new(HashMap::new()), } } } +// The challenges we store and check against +struct ChallengeInfo { + created: Timestamp, + chars: String, +} + +type ChallengeKey = String; + +// The user's attempt +#[derive(Clone, Debug, CandidType, Deserialize)] +struct ChallengeAttempt { + chars: String, + key: ChallengeKey +} + +// What we send the user +#[derive(Clone, Debug, CandidType, Deserialize)] +struct Challenge { + png_base64: String, + challenge_key: ChallengeKey, +} + thread_local! { static STATE: State = State::default(); static ASSETS: RefCell, &'static [u8])>> = RefCell::new(HashMap::default()); @@ -248,10 +287,12 @@ async fn init_salt() { } #[update] -async fn register(device_data: DeviceData, pow: ProofOfWork) -> RegisterResponse { +async fn register(device_data: DeviceData, challenge_result: ChallengeAttempt) -> RegisterResponse { + if let Err(()) = check_challenge(challenge_result) { + return RegisterResponse::BadChallenge; + } + check_entry_limits(&device_data); - let now = time() as u64; - check_proof_of_work(&pow, now); if caller() != Principal::self_authenticating(device_data.pubkey.clone()) { ic_cdk::trap(&format!( @@ -264,14 +305,6 @@ async fn register(device_data: DeviceData, pow: ProofOfWork) -> RegisterResponse ensure_salt_set().await; STATE.with(|s| { - let mut nonce_cache = s.nonce_cache.borrow_mut(); - if nonce_cache.contains(pow.timestamp, pow.nonce) { - trap(&format!( - "the combination of timestamp {} and nonce {} has already been used", - pow.timestamp, pow.nonce, - )); - } - nonce_cache.prune_expired(now.saturating_sub(POW_NONCE_LIFETIME)); prune_expired_signatures(&s.asset_hashes.borrow(), &mut s.sigs.borrow_mut()); let mut store = s.storage.borrow_mut(); @@ -282,7 +315,6 @@ async fn register(device_data: DeviceData, pow: ProofOfWork) -> RegisterResponse .unwrap_or_else(|err| { trap(&format!("failed to store user device data: {}", err)) }); - nonce_cache.add(pow.timestamp, pow.nonce); RegisterResponse::Registered { user_number } } None => RegisterResponse::CanisterFull, @@ -367,6 +399,158 @@ async fn remove(user_number: UserNumber, device_key: DeviceKey) { }) } +#[update] +async fn create_challenge(pow: ProofOfWork) -> Challenge { + + let mut rng = make_rng().await; + + let resp = STATE.with(|s| { + let mut nonce_cache = s.nonce_cache.borrow_mut(); + if nonce_cache.contains(pow.timestamp, pow.nonce) { + trap(&format!( + "the combination of timestamp {} and nonce {} has already been used", + pow.timestamp, pow.nonce, + )); + } + + let now = time() as u64; + + nonce_cache.prune_expired(now.saturating_sub(POW_NONCE_LIFETIME)); + prune_expired_signatures(&s.asset_hashes.borrow(), &mut s.sigs.borrow_mut()); + + check_proof_of_work(&pow, now); + + nonce_cache.add(pow.timestamp, pow.nonce); + + + let mut inflight_challenges = s.inflight_challenges.borrow_mut(); + + // Prune old challenges. This drops all challenges that are older than + // CAPTCHA_CHALLENGE_LIFETIME + // TODO: test this + inflight_challenges.retain(|_, v| v.created > now - CAPTCHA_CHALLENGE_LIFETIME); + + // Error out if there are too many inflight challenges + // TODO: test this + if inflight_challenges.len() >= MAX_INFLIGHT_CHALLENGES { + trap("too many inflight captchas"); + } + + // actually create the challenge + + // First, we try to find a new (unique) challenge key. It's unlikely we'll have collisions + // when generating the key, but to err on the safe side we try up to 10 times. + const MAX_TRIES: u8= 10; + + for _ in 0..MAX_TRIES { + let challenge_key = random_string(&mut rng, 10); + if !inflight_challenges.contains_key(&challenge_key) { + // Then we create the CAPTCHA + let (Base64(png_base64), chars) = create_captcha(rng); + + // Finally insert + inflight_challenges.insert(challenge_key.clone(), ChallengeInfo { created: now, chars }); + + return Challenge { png_base64, challenge_key } + } + } + + trap(&format!("Could not find a new key after {} tries", MAX_TRIES)); + }); + + resp +} + +// Generate an n-char long string of random characters. The characters are sampled from the rang +// a-z. +// +// NOTE: The 'rand' crate (currently) does not build on wasm32-unknown-unknown so we have to +// make-do with the RngCore trait (as opposed to Rng), therefore we have to implement this +// ourselves as opposed to using one of rand's distributions. +fn random_string(rng: &mut T, n: usize) -> String { + + let mut chars: Vec = vec![]; + + // The range + let a: u8 = 'a' as u8; + let z: u8 = 'z' as u8; + + // n times, get a random number as u32, then shrink to u8, and finally shrink to the size of + // our range. Finally, offset by the start of our range. + for _ in 0..n { + let next: u8 = rng.next_u32() as u8 % (z - a) + a; + chars.push(next); + } + + return String::from_utf8_lossy(&chars).to_string(); +} + +// Get a random number generator based on 'raw_rand' +async fn make_rng() -> rand_chacha::ChaCha20Rng { + + let raw_rand: Vec = match call(Principal::management_canister(), "raw_rand", ()).await { + Ok((res,)) => res, + Err((_, err)) => trap(&format!("failed to get seed: {}", err)), + }; + let seed: Salt = raw_rand[..].try_into().unwrap_or_else(|_| { + trap(&format!( + "when creating seed from raw_rand output, expected raw randomness to be of length 32, got {}", + raw_rand.len() + )); + }); + + rand_chacha::ChaCha20Rng::from_seed(seed) +} + +#[cfg(feature = "dummy_captcha")] +fn create_captcha(rng: T) -> (Base64, String) { + + let mut captcha = captcha::RngCaptcha::from_rng(rng); + let captcha = captcha.set_chars(&vec!['a']).add_chars(1) + .view(10,10); + + let resp = match captcha.as_base64() { + Some(png_base64) => Base64(png_base64), + None => trap("Could not get base64 of captcha"), + }; + + return (resp, captcha.chars_as_string()); +} + +#[cfg(not(feature = "dummy_captcha"))] +fn create_captcha(rng: T) -> (Base64, String) { + + let mut captcha = captcha::RngCaptcha::from_rng(rng); + let captcha = captcha.add_chars(5) + .apply_filter(Wave::new(2.0, 20.0).horizontal()) + .apply_filter(Wave::new(2.0, 20.0).vertical()) + .view(220, 120); + + let resp = match captcha.as_base64() { + Some(png_base64) => Base64(png_base64), + None => trap("Could not get base64 of captcha"), + }; + + return (resp, captcha.chars_as_string()); +} + +// Check whether the CAPTCHA challenge was solved +fn check_challenge(res: ChallengeAttempt) -> Result<(),()> { + + STATE.with(|s| { + let mut inflight_challenges = s.inflight_challenges.borrow_mut(); + match inflight_challenges.remove(&res.key) { + Some(challenge) => { + if res.chars != challenge.chars { + return Err(()); + } + return Ok(()); + }, + None => Err(()), + } + }) +} + #[query] fn lookup(user_number: UserNumber) -> Vec { STATE.with(|s| { @@ -528,6 +712,11 @@ fn encode_metrics(w: &mut MetricsEncoder>) -> std::io::Result<()> { s.last_upgrade_timestamp.get() as f64, "The most recent IC time (in nanos) when this canister was successfully upgraded.", )?; + w.encode_gauge( + "internet_identity_inflight_challenges", + s.inflight_challenges.borrow().len() as f64, + "The number of inflight CAPTCHA challenges", + )?; Ok(()) }) }