diff --git a/Cargo.lock b/Cargo.lock index 1762f7c..8dc293e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,30 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aho-corasick" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" +dependencies = [ + "memchr", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + [[package]] name = "base64" version = "0.21.0" @@ -14,6 +38,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24a6904aef64d73cf10ab17ebace7befb918b82164785cb89907993be7f83813" + [[package]] name = "bounded-integer" version = "0.5.6" @@ -24,14 +54,10 @@ dependencies = [ ] [[package]] -name = "bounded-vec" -version = "0.7.1" +name = "bumpalo" +version = "3.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68534a48cbf63a4b1323c433cf21238c9ec23711e0df13b08c33e5c2082663ce" -dependencies = [ - "serde", - "thiserror", -] +checksum = "3c6ed94e98ecff0c12dd1b04c15ec0d7d9458ca8fe806cea6f12954efe74c63b" [[package]] name = "cc" @@ -45,6 +71,19 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b" +dependencies = [ + "iana-time-zone", + "num-integer", + "num-traits", + "serde", + "winapi", +] + [[package]] name = "ciborium" version = "0.2.1" @@ -72,6 +111,12 @@ dependencies = [ "half", ] +[[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + [[package]] name = "coset" version = "0.3.4" @@ -91,10 +136,11 @@ name = "ctap2-proto" version = "0.1.0" dependencies = [ "bounded-integer", - "bounded-vec", "coset", "fido-common", "serde", + "serde_with", + "typed-builder", ] [[package]] @@ -116,19 +162,101 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4714cdd86d5134532b9decaa6774db0a6851ecd07e96a2f239332ae1f3239350" dependencies = [ - "bitflags", + "bitflags 1.3.2", +] + +[[package]] +name = "darling" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0558d22a7b463ed0241e993f76f09f30b126687447751a8638587b864e4b3944" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab8bfa2e259f8ee1ce5e97824a3c55ec4404a0d772ca7fa96bf19f0752a046eb" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.16", +] + +[[package]] +name = "darling_macro" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29a358ff9f12ec09c3e61fef9b5a9902623a695a46a917b07f269bff1445611a" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.16", +] + +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + +[[package]] +name = "env_logger" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "errno" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", ] [[package]] name = "fido-common" version = "0.1.0" dependencies = [ - "bounded-vec", + "bitflags 2.2.1", "ciborium", "coset", "serde", + "serde_with", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "getrandom" version = "0.2.9" @@ -146,6 +274,18 @@ version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + [[package]] name = "hex" version = "0.4.3" @@ -160,15 +300,17 @@ dependencies = [ "ciborium-io", "ctap2-proto", "ctaphid", + "env_logger", "hidapi", + "rand", "serde", ] [[package]] name = "hidapi" -version = "1.5.0" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "798154e4b6570af74899d71155fb0072d5b17e6aa12f39c8ef22c60fb8ec99e7" +checksum = "88c57421a3f575970573c2d8bedcaf4c9fe1a560c6944651c7256d408ee091b1" dependencies = [ "cc", "libc", @@ -176,12 +318,102 @@ dependencies = [ "winapi", ] +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "iana-time-zone" +version = "0.1.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0722cd7114b7de04316e7ea5456a0bbb20e4adb46fd27a3697adb812cff0f37c" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown", + "serde", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys", +] + +[[package]] +name = "is-terminal" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" +dependencies = [ + "hermit-abi", + "io-lifetimes", + "rustix", + "windows-sys", +] + +[[package]] +name = "itoa" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" + +[[package]] +name = "js-sys" +version = "0.3.62" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68c16e1bfd491478ab155fd8b4896b86f9ede344949b641e61501e07c2b8b4d5" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "libc" version = "0.2.144" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" +[[package]] +name = "linux-raw-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ece97ea872ece730aed82664c424eb4c8291e1ff2480247ccf7409044bc6479f" + [[package]] name = "log" version = "0.4.17" @@ -191,12 +423,49 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" + [[package]] name = "pkg-config" version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + [[package]] name = "proc-macro2" version = "1.0.56" @@ -215,6 +484,27 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + [[package]] name = "rand_core" version = "0.6.4" @@ -225,30 +515,116 @@ dependencies = [ ] [[package]] -name = "serde" -version = "1.0.163" +name = "regex" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" +checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c" + +[[package]] +name = "rustix" +version = "0.37.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" +dependencies = [ + "bitflags 1.3.2", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "ryu" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" + +[[package]] +name = "serde" +version = "1.0.170" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.163" +version = "1.0.136" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "serde_json" +version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" +checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "3.0.0" +dependencies = [ + "base64", + "chrono", + "doc-comment", + "hex", + "indexmap", + "serde", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.0.0" +dependencies = [ + "darling", "proc-macro2", "quote", - "syn", + "syn 2.0.16", ] +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "syn" -version = "2.0.15" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6f671d4b5ffdb8eadec19c0ae67fe2639df8684bd7bc4b83d986b8db549cf01" dependencies = [ "proc-macro2", "quote", @@ -262,23 +638,50 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] -name = "thiserror" -version = "1.0.40" +name = "termcolor" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "time" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +checksum = "8f3403384eaacbca9923fa06940178ac13e4edb725486d70e8e15881d0c836cc" dependencies = [ - "thiserror-impl", + "itoa", + "serde", + "time-core", + "time-macros", ] [[package]] -name = "thiserror-impl" -version = "1.0.40" +name = "time-core" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" + +[[package]] +name = "time-macros" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b" +dependencies = [ + "time-core", +] + +[[package]] +name = "typed-builder" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64cba322cb9b7bc6ca048de49e83918223f35e7a86311267013afff257004870" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -293,6 +696,60 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasm-bindgen" +version = "0.2.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b6cb788c4e39112fbe1822277ef6fb3c55cd86b95cb3d3c4c1c9597e4ac74b4" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35e522ed4105a9d626d885b35d62501b30d9666283a5c8be12c14a8bdafe7822" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.16", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "358a79a0cb89d21db8120cbfb91392335913e4890665b1a7981d9e956903b434" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4783ce29f09b9d93134d41297aded3a712b7b979e9c6f28c32cb88c973a94869" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.16", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a901d592cafaa4d711bc324edfaff879ac700b19c3dfd60058d2b445be2691eb" + [[package]] name = "webauthn3-proto" version = "0.1.0" @@ -319,8 +776,92 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" diff --git a/Cargo.toml b/Cargo.toml index f53b0ef..0835a9a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,3 +3,6 @@ members = [ "crates/*", ] + +[patch.crates-io] +serde = { path = "../serde/serde" } diff --git a/crates/credential-management-proto/src/lib.rs b/crates/credential-management-proto/src/lib.rs index 2c8db97..190b98c 100644 --- a/crates/credential-management-proto/src/lib.rs +++ b/crates/credential-management-proto/src/lib.rs @@ -1,6 +1,6 @@ -#![feature(associated_const_equality, async_fn_in_trait)] -#![allow(clippy::missing_errors_doc, incomplete_features)] - -pub mod credential; -pub mod discovery; -pub mod mediation; +// #![feature(associated_const_equality, async_fn_in_trait)] +// #![allow(clippy::missing_errors_doc, incomplete_features)] +// +// pub mod credential; +// pub mod discovery; +// pub mod mediation; diff --git a/crates/ctap2-proto/Cargo.toml b/crates/ctap2-proto/Cargo.toml index 3623e10..69b4840 100644 --- a/crates/ctap2-proto/Cargo.toml +++ b/crates/ctap2-proto/Cargo.toml @@ -7,10 +7,11 @@ edition = "2021" [dependencies] fido-common = { path = "../fido-common" } -bounded-integer = { version = "0.5.3", features = ["types", "std", "serde1"] } -bounded-vec = "0.7.1" +bounded-integer = { version = "0.5.3", features = ["types", "std"] } coset = "0.3.3" serde = { version = "1.0", features = ["derive"], optional = true } +typed-builder = { version = "0.14.0", default-features = false } +serde_with = { path = "../../../serde_with/serde_with", optional = true } [features] -serde = ["dep:serde", "bounded-vec/serde", "fido-common/serde"] +serde = ["dep:serde", "dep:serde_with", "fido-common/serde", "bounded-integer/serde1"] diff --git a/crates/ctap2-proto/examples/libfido2.rs b/crates/ctap2-proto/examples/libfido2.rs deleted file mode 100644 index 715afbd..0000000 --- a/crates/ctap2-proto/examples/libfido2.rs +++ /dev/null @@ -1,187 +0,0 @@ -use std::collections::BTreeMap; -use std::ops::Deref; - -use ctap2_proto::extensions; -use ctap2_proto::prelude::*; -use ctap2_proto::Ctap2_2Authenticator; - -extern crate ctap2_proto; - -struct FidoKey(ctap_hid_fido2::FidoKeyHid); - -impl Deref for FidoKey { - type Target = ctap_hid_fido2::FidoKeyHid; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl Ctap2_2Authenticator for FidoKey { - fn make_credential(&mut self, request: make::Request) -> Result { - let args = MakeCredentialArgsBuilder::new( - &request.relying_party.id, - request.client_data_hash.as_ref(), - ) - .build(); - - let attestation = match self.make_credential_with_args(&args) { - Ok(attestation) => attestation, - Err(e) => { - todo!("unhandled error: {e}") // anyhow::Error requires manually - // mapping - } - }; - - let format = if !attestation.flags_attested_credential_data_included { - FormatIdentifier::None - } else { - unimplemented!("do not support attestation yet") - }; - - let authenticator_data = attestation.auth_data.as_slice().try_into().unwrap(); - - let unsigned_extension_outputs: BTreeMap<_, _> = attestation - .extensions - .into_iter() - .filter_map(|extension| -> Option<(extensions::Identifier, Vec)> { - match extension { - CredentialExtension::CredBlob(_) => { - todo!() - } - CredentialExtension::CredProtect(_) => { - todo!() - } - CredentialExtension::HmacSecret(_) => { - todo!() - } - CredentialExtension::LargeBlobKey(_) => { - todo!() - } - CredentialExtension::MinPinLength(_) => { - todo!() - } - } - }) - .collect(); - - let unsigned_extension_outputs = if !unsigned_extension_outputs.is_empty() { - Some(unsigned_extension_outputs) - } else { - None - }; - - Ok(make::Response { - format, - authenticator_data, - enterprise_attestation: None, - large_blob_key: None, - unsigned_extension_outputs, - }) - } - - fn get_assertion(request: get::Request) -> Result { - todo!() - } - - fn get_info(&self) -> device::Info { - todo!() - } - - fn client_pin(request: client_pin::Request) -> Result { - todo!() - } - - fn reset() -> Result<(), reset::Error> { - todo!() - } - - fn selection() -> Result<(), ctap2_proto::authenticator::selection::Error> { - todo!() - } -} - -use ctap_hid_fido2::fidokey::CredentialExtension; -use ctap_hid_fido2::{ - fidokey::{GetAssertionArgsBuilder, MakeCredentialArgsBuilder}, - verifier, Cfg, FidoKeyHidFactory, -}; -use fido_common::attestation::FormatIdentifier; - -fn main() { - let rpid = "reg-auth-example-app"; - let pin = get_input_with_message("input PIN:"); - - println!("Register"); - // create `challenge` - let challenge = verifier::create_challenge(); - - // create `MakeCredentialArgs` - let make_credential_args = MakeCredentialArgsBuilder::new(rpid, &challenge) - .pin(&pin) - .build(); - - let mut cfg = Cfg::init(); - cfg.enable_log = false; - - // create `FidoKeyHid` - let device = FidoKeyHidFactory::create(&cfg).unwrap(); - - if device.get_info().unwrap().force_pin_change { - device.set_new_pin("1234").unwrap(); - } - - // get `Attestation` Object - let attestation = device - .make_credential_with_args(&make_credential_args) - .unwrap(); - println!("- Register Success"); - - // verify `Attestation` Object - let verify_result = verifier::verify_attestation(rpid, &challenge, &attestation); - if !verify_result.is_success { - println!("- ! Verify Failed"); - return; - } - - // store Credential Id and Publickey - let userdata_credential_id = verify_result.credential_id; - let userdata_credential_public_key = verify_result.credential_public_key; - - println!("Authenticate"); - // create `challenge` - let challenge = verifier::create_challenge(); - - // create `GetAssertionArgs` - let get_assertion_args = GetAssertionArgsBuilder::new(rpid, &challenge) - .pin(&pin) - .credential_id(&userdata_credential_id) - .build(); - - // get `Assertion` Object - let assertions = device.get_assertion_with_args(&get_assertion_args).unwrap(); - println!("- Authenticate Success"); - - // verify `Assertion` Object - if !verifier::verify_assertion( - rpid, - &userdata_credential_public_key, - &challenge, - &assertions[0], - ) { - println!("- ! Verify Assertion Failed"); - } -} - -pub fn get_input() -> String { - let mut word = String::new(); - std::io::stdin().read_line(&mut word).ok(); - return word.trim().to_string(); -} - -pub fn get_input_with_message(message: &str) -> String { - println!("{}", message); - let input = get_input(); - println!(); - input -} diff --git a/crates/ctap2-proto/examples/mock.rs b/crates/ctap2-proto/examples/mock.rs index 118ad58..d814e09 100644 --- a/crates/ctap2-proto/examples/mock.rs +++ b/crates/ctap2-proto/examples/mock.rs @@ -1,14 +1,9 @@ #![feature(let_chains)] extern crate ctap2_proto; -<<<<<<< Updated upstream -use std::collections::HashMap; -======= use std::collections::BTreeMap; ->>>>>>> Stashed changes -use bounded_vec::BoundedVec; -use ctap2_proto::authenticator::Sha256Hash; +use ctap2_proto::authenticator::Transport; // Recommended use ctap2_proto::prelude::*; @@ -24,7 +19,6 @@ use fido_common::{ attestation, credential::{self, public_key::Parameters}, registry::algorithms::Signature, - Transport, }; const AAGUID: Aaguid = Aaguid::from(*b"\xed\xeeZow\xdc\xf5\xadZ\xe3\xd7\xb8\xf5\xf6\xf7\xd7"); @@ -230,13 +224,13 @@ fn main() { output: std::io::stdout(), }; - let client_data_hash = Sha256Hash([0; 32]); + let client_data_hash = [0; 32]; let relying_party = RelyingPartyEntity { id: "example.com".into(), name: Some("Example Inc.".into()), }; let user_entity = UserEntity { - id: BoundedVec::from_vec([1u8; 64].into()).unwrap(), + id: [1u8; 64].into(), name: Some("user@example.com".into()), display_name: Some("Example User".into()), }; @@ -245,7 +239,7 @@ fn main() { let info = authenticator.get_info(); // Make a new discoverable credential - let options = HashMap::from([ + let options = BTreeMap::from([ (make::OptionKey::UserVerification, true), (make::OptionKey::Discoverable, true), (make::OptionKey::UserPresence, false), @@ -258,8 +252,8 @@ fn main() { exclude_list: None, extensions: None, options: Some(&options), - pin_uv_auth_param: (), - pin_uv_auth_protocol_version: (), + pin_uv_auth_param: todo!(), + pin_uv_auth_protocol_version: todo!(), enterprise_attestation: None, }; authenticator.make_credential(request); diff --git a/crates/ctap2-proto/src/authenticator/assertion/get.rs b/crates/ctap2-proto/src/authenticator/assertion/get.rs index c25ddd9..7dcbe78 100644 --- a/crates/ctap2-proto/src/authenticator/assertion/get.rs +++ b/crates/ctap2-proto/src/authenticator/assertion/get.rs @@ -1,9 +1,14 @@ -use crate::{authenticator::client_pin::AuthProtocolVersion, extensions}; use crate::Sha256Hash; -use bounded_vec::BoundedVec; +use crate::{authenticator::client_pin::AuthProtocolVersion, extensions}; use fido_common::credential::public_key; use std::{collections::BTreeMap, usize}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; +#[cfg(feature = "serde")] +use serde_with::{serde_as, skip_serializing_none, Bytes}; + +#[derive(Debug)] pub enum Error { OperationDenied, PinNotSet, @@ -21,63 +26,92 @@ pub enum Error { /// > The following option keys are defined for use in /// > [`assertion::get::Request`]'s `options` parameter. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum OptionKey { /// > user presence: Instructs the authenticator to require user consent /// > to complete the operation. - UserPrecense, + #[serde(rename = "up")] + UserPresence, /// > user verification: If true, instructs the authenticator to require /// > a user-verifying gesture in order to complete the request. /// > Examples of such gestures are fingerprint scan or a PIN. + #[serde(rename = "uv")] UserVerification, } /// Request parameters for [`Ctap2Device::get_assertion`] operation. -#[derive(Clone, Copy)] +#[cfg_eval] +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "serde", serde_as, skip_serializing_none, derive(Serialize))] pub struct Request<'a> { /// > relying party identifier + #[serde(rename = 0x01)] pub relying_party_id: &'a str, /// > Hash of the serialized client data collected by the host. + #[cfg_attr(feature = "serde", serde_as(as = "Bytes"), serde(rename = 0x02))] pub client_data_hash: &'a Sha256Hash, /// > An array of [`public_key::Descriptor`] structures, each denoting a /// > credential, as specified in `WebAuthn`... If this parameter is present /// > the authenticator MUST only generate a assertion using one of the /// > denoted credentials. - pub allow_list: Option<&'a BoundedVec<&'a public_key::Descriptor, 1, { usize::MAX }>>, + // Cannot be empty if present + #[serde(rename = 0x03)] + pub allow_list: Option<&'a Vec<&'a public_key::Descriptor>>, /// > Parameters to influence authenticator operation. These parameters /// > might be authenticator specific. + #[serde(rename = 0x04)] pub extensions: Option<&'a BTreeMap>, /// > Parameters to influence authenticator operation. + #[serde(rename = 0x05)] pub options: Option<&'a BTreeMap>, + #[serde(rename = 0x06)] pub pin_uv_auth_param: Option<&'a [u8]>, /// > PIN/UV protocol version selected by platform. + #[serde(rename = 0x07)] pub pin_uv_auth_protocol_version: Option, } /// Response structure for [`Ctap2Device::get_assertion`] operation. +#[cfg_eval] +#[derive(Debug, Clone)] +#[cfg_attr( + feature = "serde", + serde_as, + skip_serializing_none, + derive(Deserialize) +)] pub struct Response { /// > PublicKeyCredentialDescriptor structure containing the credential /// > identifier whose private key was used to generate the assertion. + #[serde(rename = 0x01)] pub credential: public_key::Descriptor, /// > The signed-over contextual bindings made by the authenticator, as /// > specified in [WebAuthn]. + #[cfg_attr(feature = "serde", serde_as(as = "Bytes"), serde(rename = 0x02))] pub auth_data: Vec, /// > The assertion signature produced by the authenticator, as /// > specified in [WebAuthn]. + #[cfg_attr(feature = "serde", serde_as(as = "Bytes"), serde(rename = 0x03))] pub signature: Vec, /// > [`public_key::UserEntity`] structure containing the user account /// > information + #[serde(rename = 0x04)] pub user: Option, /// > Total number of account credentials for the RP. Optional; defaults /// > to one. This member is required when more than one credential is /// > found for an RP, and the authenticator does not have a display or /// > the UV & UP flags are false. + #[serde(rename = 0x05)] pub number_of_credentials: Option, /// > Indicates that a credential was selected by the user via /// > interaction directly with the authenticator, and thus the platform /// > does not need to confirm the credential. + #[serde(rename = 0x06)] pub user_selected: Option, /// > The contents of the associated `largeBlobKey` if present for the /// > asserted credential, and if `largeBlobKey` was true in the /// > extensions input. + #[cfg_attr(feature = "serde", serde_as(as = "Option"), serde(rename = 0x07))] pub large_blob_key: Option>, } diff --git a/crates/ctap2-proto/src/authenticator/client_pin/mod.rs b/crates/ctap2-proto/src/authenticator/client_pin/mod.rs index b6e9dc2..22b9815 100644 --- a/crates/ctap2-proto/src/authenticator/client_pin/mod.rs +++ b/crates/ctap2-proto/src/authenticator/client_pin/mod.rs @@ -5,12 +5,47 @@ use std::collections::BTreeSet; use serde::{Deserialize, Serialize}; #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum AuthProtocolVersion { One, Two, } +// workaround until is merged +// PR: ( Integer/boolean tags for internally/adjacently tagged enums #2056 ) +#[cfg(feature = "serde")] +impl Serialize for AuthProtocolVersion { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_u8(match self { + AuthProtocolVersion::One => 1, + AuthProtocolVersion::Two => 2, + }) + } +} + +// workaround until is merged +// PR: ( Integer/boolean tags for internally/adjacently tagged enums #2056 ) +#[cfg(feature = "serde")] +impl<'de> Deserialize<'de> for AuthProtocolVersion { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + use serde::de; + + match u8::deserialize(deserializer)? { + 1 => Ok(Self::One), + 2 => Ok(Self::Two), + i => Err(de::Error::invalid_value( + de::Unexpected::Unsigned(i.into()), + &"1 or 2", + )), + } + } +} + pub enum Subcommand { GetPinRetries, GetKeyAgreement, diff --git a/crates/ctap2-proto/src/authenticator/config.rs b/crates/ctap2-proto/src/authenticator/config.rs index 8acdd5c..4a6e00b 100644 --- a/crates/ctap2-proto/src/authenticator/config.rs +++ b/crates/ctap2-proto/src/authenticator/config.rs @@ -1,4 +1,4 @@ -use std::collections::{BTreeMap}; +use std::collections::BTreeMap; use super::client_pin::AuthProtocolVersion; @@ -38,7 +38,7 @@ pub enum Request<'a> { VendorPrototype { vendor_command_id: usize, params: &'a BTreeMap, Vec>, /* TODO: Is the character space of keys - * restricted to UTF-8? */ + * restricted to UTF-8? */ pin_uv_auth_protocol: AuthProtocolVersion, pin_uv_auth_param: &'a [u8], // TODO: Is using a more specific type possible? }, diff --git a/crates/ctap2-proto/src/authenticator/credential/make.rs b/crates/ctap2-proto/src/authenticator/credential/make.rs index 88b2c4e..27d6259 100644 --- a/crates/ctap2-proto/src/authenticator/credential/make.rs +++ b/crates/ctap2-proto/src/authenticator/credential/make.rs @@ -3,11 +3,15 @@ use crate::{ extensions, Sha256Hash, }; use fido_common::{attestation, credential::public_key}; -use std::collections::{BTreeMap, HashMap}; +use std::collections::BTreeMap; +use typed_builder::TypedBuilder; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; +#[cfg(feature = "serde")] +use serde_with::{serde_as, skip_serializing_none, Bytes}; +#[derive(Debug)] pub enum Error { OperationDenied, PinNotSet, @@ -27,38 +31,46 @@ pub enum Error { /// > The following option keys are defined for use in /// > `authenticatorMakeCredential`'s `options` parameter. -#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum OptionKey { /// > Specifies whether this credential is to be discoverable or /// > not. + #[cfg_attr(feature = "serde", serde(rename = "rk"))] Discoverable, /// > user presence: Instructs the authenticator to require user /// > consent /// > to complete the operation. + #[cfg_attr(feature = "serde", serde(rename = "up"))] UserPresence, /// > user verification: If true, instructs the authenticator to require a /// > user-verifying gesture in order to complete the request. Examples of /// > such gestures are fingerprint scan or a PIN. + #[cfg_attr(feature = "serde", serde(rename = "uv"))] UserVerification, } +#[cfg_eval] /// Input parameters for [`Ctap2Device::make_credential`] operation. -#[derive(Clone, Copy)] -#[cfg_attr(feature = "serde", derive(Serialize))] +#[cfg_attr(feature = "serde", serde_as, skip_serializing_none, derive(Serialize))] +#[derive(Debug, Clone, Copy, TypedBuilder)] pub struct Request<'a> { /// > Hash of the ClientData contextual binding specified by host. + #[cfg_attr(feature = "serde", serde(rename = 0x01), serde_as(as = "Bytes"))] pub client_data_hash: &'a Sha256Hash, /// > This PublicKeyCredentialRpEntity data structure describes a /// > Relying Party with which the new public key credential will be /// > associated. + #[cfg_attr(feature = "serde", serde(rename = 0x02))] pub relying_party: &'a public_key::RelyingPartyEntity, /// > ... describes the user account to which the new public key /// > credential will be associated at the RP. + #[cfg_attr(feature = "serde", serde(rename = 0x03))] pub user: &'a public_key::UserEntity, /// > List of supported algorithms for credential generation, as /// > specified in [WebAuthn]. The array is ordered from most preferred /// > to least preferred and MUST NOT include duplicate entries. + #[cfg_attr(feature = "serde", serde(rename = 0x04))] pub public_key_credential_params: &'a [public_key::Parameters], // TODO: BTreeSet? BTreeMap // with preference as key? /// > An array of PublicKeyCredentialDescriptor structures, as specified @@ -66,13 +78,27 @@ pub struct Request<'a> { /// > authenticator already contains one of the credentials enumerated /// > in this array. This allows RPs to limit the creation of multiple /// > credentials for the same account on a single authenticator. + #[builder(default, setter(strip_option))] + #[cfg_attr(feature = "serde", serde(rename = 0x05))] pub exclude_list: Option<&'a [&'a public_key::Descriptor]>, /// > Parameters to influence authenticator operation, as specified in /// > [WebAuthn]. These parameters might be authenticator specific. - pub extensions: Option<&'a HashMap>>, + #[builder(default, setter(strip_option))] + #[cfg_attr(feature = "serde", serde(rename = 0x06))] + pub extensions: Option<&'a BTreeMap>>, + #[builder(default, setter(strip_option))] + #[cfg_attr(feature = "serde", serde(rename = 0x07))] pub options: Option<&'a BTreeMap>, - pub pin_uv_auth_param: &'a [u8], + #[builder(default, setter(strip_option))] + #[cfg_attr( + feature = "serde", + serde(rename = 0x08), + serde_as(as = "Option") + )] + pub pin_uv_auth_param: Option<&'a [u8]>, /// > PIN/UV protocol version selected by platform. + #[builder(default, setter(strip_option))] + #[cfg_attr(feature = "serde", serde(rename = 0x09))] pub pin_uv_auth_protocol_version: Option, /// > An authenticator supporting this enterprise attestation feature is /// > enterprise attestation capable and signals its support via the `ep` @@ -86,22 +112,36 @@ pub struct Request<'a> { /// > attestation batching may not apply to the results of this operation /// > and the platform is requesting an enterprise attestation that includes /// > uniquely identifying information. + #[builder(default, setter(strip_option))] + #[cfg_attr(feature = "serde", serde(rename = 0x0A))] pub enterprise_attestation: Option, } -#[cfg_attr(feature = "serde", derive(Deserialize))] +#[cfg_eval] +#[cfg_attr(feature = "serde", serde_as, derive(Deserialize))] +#[derive(Debug)] pub struct Response { + #[cfg_attr(feature = "serde", serde(rename = 0x01))] pub format: fido_common::attestation::FormatIdentifier, + #[cfg_attr(feature = "serde", serde(rename = 0x02))] pub authenticator_data: authenticator::Data, + #[cfg_attr(feature = "serde", serde(rename = 0x03))] + pub attestation_statement: Option, /// > Indicates whether an enterprise attestation was returned for this /// > credential. If `epAtt` is absent or present and set to false, then an /// > enterprise attestation was not returned. If `epAtt` is present and set /// > to true, then an enterprise attestation was returned. + #[cfg_attr(feature = "serde", serde(rename = 0x04))] pub enterprise_attestation: Option, /// > Contains the `largeBlobKey` for the credential, if requested with the /// > `largeBlobKey` extension. + #[cfg_attr(feature = "serde", serde(rename = 0x05))] pub large_blob_key: Option>, /// > A map, keyed by extension identifiers, to unsigned outputs of /// > extensions, if any. + #[cfg_attr(feature = "serde", serde(rename = 0x06))] pub unsigned_extension_outputs: Option>>, } + +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +struct AttestationStatement {} diff --git a/crates/ctap2-proto/src/authenticator/device.rs b/crates/ctap2-proto/src/authenticator/device.rs index 363991d..260cb0f 100644 --- a/crates/ctap2-proto/src/authenticator/device.rs +++ b/crates/ctap2-proto/src/authenticator/device.rs @@ -1,9 +1,8 @@ use crate::authenticator::client_pin::AuthProtocolVersion; use crate::authenticator::Transport; use crate::extensions; -use bounded_vec::BoundedVec; use fido_common::credential::public_key; -use fido_common::registry; +use fido_common::{attestation, registry}; use std::collections::{BTreeMap, BTreeSet}; use std::num::NonZeroUsize; use std::usize; @@ -24,9 +23,13 @@ pub type Aaguid = [u8; 16]; #[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum Version { + #[cfg_attr(feature = "serde", serde(rename = "FIDO_2_1"))] Fido2_1, + #[cfg_attr(feature = "serde", serde(rename = "FIDO_2_0"))] Fido2_0, + #[cfg_attr(feature = "serde", serde(rename = "FIDO_2_1_PRE"))] Fido2_1Preview, + #[cfg_attr(feature = "serde", serde(rename = "U2F_V2"))] U2fV2, } @@ -126,10 +129,12 @@ pub enum FidoLevel { pub enum OptionId { /// > Indicates that the device is attached to the client and therefore /// > can’t be removed and used on another client. + #[cfg_attr(feature = "serde", serde(rename = "plat"))] PlatformDevice, /// > Specifies whether this authenticator can create discoverable /// > credentials, and therefore can satisfy `authenticatorGetAssertion` /// > requests with the `allowList` parameter omitted. + #[cfg_attr(feature = "serde", serde(rename = "rk"))] DiscoverableCredentials, /// > ClientPIN feature support: /// > If present and set to true, it indicates that the device is capable of @@ -140,8 +145,10 @@ pub enum OptionId { /// > /// > If absent, it indicates that the device is not capable of accepting a /// > PIN from the client. + #[cfg_attr(feature = "serde", serde(rename = "clientPin"))] ClientPin, /// > Indicates that the device is capable of testing user presence. + #[cfg_attr(feature = "serde", serde(rename = "up"))] UserPresence, /// > Indicates that the authenticator supports a built-in user verification /// > method. For example, devices with UI, biometrics fall into this @@ -166,7 +173,9 @@ pub enum OptionId { /// > If a device is capable of both built-in user verification and Client /// > PIN, the authenticator will return both the "uv" and the "clientPin" /// > option ids. + #[cfg_attr(feature = "serde", serde(rename = "uv"))] UserVerification, + #[cfg_attr(feature = "serde", serde(rename = "pinUvAuthToken"))] PinUvAuthToken, /// > If this noMcGaPermissionsWithClientPin is: /// > - present and set to true: A `pinUvAuthToken` obtained via @@ -184,17 +193,31 @@ pub enum OptionId { /// > /// > Note: `noMcGaPermissionsWithClientPin` MUST only be present if the /// > `clientPin` option ID is present. + #[cfg_attr(feature = "serde", serde(rename = "noMcGaPermissionsWithClientPin"))] NoMcGaPermissionsWithClientPin, + #[cfg_attr(feature = "serde", serde(rename = "largeBlobs"))] LargeBlobs, + #[cfg_attr(feature = "serde", serde(rename = "ep"))] EnterpriseAttestation, + #[cfg_attr(feature = "serde", serde(rename = "bioEnroll"))] BiometricEnroll, + #[cfg_attr(feature = "serde", serde(rename = "userVerificationMgmtPreview"))] UvManagementPreview, + #[cfg_attr(feature = "serde", serde(rename = "uvBioEnroll"))] UvBiometricEnroll, + #[cfg_attr(feature = "serde", serde(rename = "authnrCfg"))] AuthenticatorConfig, + #[cfg_attr(feature = "serde", serde(rename = "uvAcfg"))] UvAuthenticatorConfig, + #[cfg_attr(feature = "serde", serde(rename = "credMgmt"))] CredentialManagement, + #[cfg_attr(feature = "serde", serde(rename = "credentialMgmtPreview"))] + CredentialManagementPreview, + #[cfg_attr(feature = "serde", serde(rename = "setMinPINLength"))] SetMinPinLength, + #[cfg_attr(feature = "serde", serde(rename = "makeCredUvNotRqd"))] MakeCredentialUvNotRequired, + #[cfg_attr(feature = "serde", serde(rename = "alwaysUv"))] AlwaysRequireUv, } @@ -206,51 +229,68 @@ pub enum OptionId { #[derive(Debug)] pub struct Info { /// > List of supported CTAP versions. + #[cfg_attr(feature = "serde", serde(rename = 0x01))] pub versions: BTreeSet, /// > List of supported extensions. + #[cfg_attr(feature = "serde", serde(rename = 0x02))] pub extensions: Option>, /// > The claimed AAGUID. + #[cfg_attr(feature = "serde", serde(rename = 0x03))] pub aaguid: Aaguid, /// > List of supported options. + #[cfg_attr(feature = "serde", serde(rename = 0x04))] pub options: Option>, /// > Maximum message size supported by the authenticator. + #[cfg_attr(feature = "serde", serde(rename = 0x05))] pub max_message_size: Option, /// > List of supported PIN/UV auth protocols in order of decreasing /// > authenticator preference. MUST NOT contain duplicate values... - pub pin_uv_auth_protocols: Option>, + // Cannot be empty if present + #[cfg_attr(feature = "serde", serde(rename = 0x06))] + pub pin_uv_auth_protocols: Option>, /// > Maximum number of credentials supported in credentialID list at a time /// > by the authenticator. + #[cfg_attr(feature = "serde", serde(rename = 0x07))] pub max_credential_count_in_list: Option, /// > Maximum Credential ID Length supported by the authenticator. + #[cfg_attr(feature = "serde", serde(rename = 0x08))] pub max_credential_id_length: Option, /// > List of supported transports. + #[cfg_attr(feature = "serde", serde(rename = 0x09))] pub transports: Option>, /// > List of supported algorithms for credential generation... The array is /// > ordered from most preferred to least preferred and MUST NOT include /// > duplicate entries... - pub algorithms: Option>, + #[cfg_attr(feature = "serde", serde(rename = 0x0A))] + // Cannot be empty if present + pub algorithms: Option>, /// > The maximum size, in bytes, of the serialized large-blob array that /// > this authenticator can store. If the `authenticatorLargeBlobs` command /// > is supported, this MUST be specified. Otherwise it MUST NOT be. + #[cfg_attr(feature = "serde", serde(rename = 0x0B))] pub max_serialized_large_blob_array_size: Option>, /// > If this member is: /// > - present and set to true: `getPinToken` and /// > `getPinUvAuthTokenUsingPinWithPermissions` will return errors until /// > after a successful PIN Change. /// > - present and set to false, or absent: no PIN Change is required. + #[cfg_attr(feature = "serde", serde(rename = 0x0C))] pub force_pin_change: Option, /// > This specifies the current minimum PIN length, in Unicode code points, /// > the authenticator enforces for ClientPIN. This is applicable for /// > ClientPIN only: the minPINLength member MUST be absent if the /// > clientPin option ID is absent; it MUST be present if the authenticator /// > supports authenticatorClientPIN. + #[cfg_attr(feature = "serde", serde(rename = 0x0D))] pub min_pin_length: Option, /// > Indicates the firmware version of the authenticator model identified /// > by AAGUID. + #[cfg_attr(feature = "serde", serde(rename = 0x0E))] pub firmware_version: Option, /// > Maximum credBlob length in bytes supported by the authenticator. Must /// > be present if, and only if, credBlob is included in the supported /// > extensions list. + #[cfg_attr(feature = "serde", serde(rename = 0x0F))] pub max_cred_blob_length: Option>, /// > This specifies the max number of RP IDs that authenticator can set via /// > `setMinPINLength` subcommand. This is in addition to pre-configured @@ -258,12 +298,14 @@ pub struct Info { /// > adding additional RP IDs, its value is 0. This MUST ONLY be present /// > if, and only if, the authenticator supports the `setMinPINLength` /// > subcommand. + #[cfg_attr(feature = "serde", serde(rename = 0x10))] pub max_rpids_for_set_min_pin_length: Option, /// > This specifies the preferred number of invocations of the /// > `getPinUvAuthTokenUsingUvWithPermissions` subCommand the platform may /// > attempt before falling back to the /// > `getPinUvAuthTokenUsingPinWithPermissions` subCommand or displaying an /// > error. + #[cfg_attr(feature = "serde", serde(rename = 0x11))] pub preferred_platform_uv_attempts: Option, /// > This specifies the user verification modality supported by the /// > authenticator via `authenticatorClientPIN`'s @@ -271,8 +313,10 @@ pub struct Info { /// > to help the platform construct user dialogs. If `clientPin` /// > is supported it MUST NOT be included in the bit-flags, as `clientPIN` /// > is not a built-in user verification method. + #[cfg_attr(feature = "serde", serde(rename = 0x12))] pub uv_modality: Option>, /// > This specifies a list of authenticator certifications. + #[cfg_attr(feature = "serde", serde(rename = 0x13))] pub certifications: Option>, /// > If this member is present it indicates the estimated number of /// > additional discoverable credentials that can be stored. If this value @@ -290,10 +334,22 @@ pub struct Info { /// > user.id that match an existing discoverable credential and thus /// > overwrite it, but this value should be set assuming that will not /// > happen. + #[cfg_attr(feature = "serde", serde(rename = 0x14))] pub remaining_discoverable_credentials: Option, /// > If present the authenticator supports the `authenticatorConfig` /// > `vendorPrototype` subcommand, and its value is a list of /// > `authenticatorConfig` `vendorCommandId` values supported, which MAY be /// > empty. + #[cfg_attr(feature = "serde", serde(rename = 0x15))] pub vendor_prototype_config_commands: Option>, + /// > List of supported attestation formats. + #[cfg_attr(feature = "serde", serde(rename = 0x16))] + pub attestation_formats: Option>, + /// > If present the number of internal User Verification operations since + /// > the last pin entry including all failed attempts. + #[cfg_attr(feature = "serde", serde(rename = 0x17))] + pub uv_count_since_last_pin_entry: Option, + /// > If present the authenticator requires a 10 second touch for reset. + #[cfg_attr(feature = "serde", serde(rename = 0x18))] + pub long_touch_for_reset: Option, } diff --git a/crates/ctap2-proto/src/authenticator/mod.rs b/crates/ctap2-proto/src/authenticator/mod.rs index 5a1dbbc..ef27303 100644 --- a/crates/ctap2-proto/src/authenticator/mod.rs +++ b/crates/ctap2-proto/src/authenticator/mod.rs @@ -8,4 +8,3 @@ pub mod credential; pub mod device; pub mod reset; pub mod selection; - diff --git a/crates/ctap2-proto/src/lib.rs b/crates/ctap2-proto/src/lib.rs index 842d3d6..bc095de 100644 --- a/crates/ctap2-proto/src/lib.rs +++ b/crates/ctap2-proto/src/lib.rs @@ -1,3 +1,5 @@ +#![feature(cfg_eval)] + pub mod prelude { pub use crate::{ authenticator::{ @@ -6,16 +8,15 @@ pub mod prelude { credential::{make, management}, device, reset, selection, }, - Ctap2_2Authenticator, + Command, Ctap2_2Authenticator, }; - pub use fido_common::Sha256Hash; + pub use fido_common::*; } +use prelude::*; pub mod authenticator; pub mod extensions; -use prelude::*; - /// Defines the raw CTAP operations pub trait Ctap2_2Authenticator { #[allow(clippy::missing_errors_doc)] @@ -28,7 +29,7 @@ pub trait Ctap2_2Authenticator { /// > authentication as well as user consent to a given transaction, using a /// > previously generated credential that is bound to the authenticator and /// > relying party identifier. - fn get_assertion(request: get::Request) -> Result; + fn get_assertion(&mut self, request: get::Request) -> Result; /// > Using this method, platforms can request that the authenticator report /// > a list of its supported protocol versions and extensions, its AAGUID, @@ -73,3 +74,20 @@ pub trait Ctap2_2Authenticator { // fn authenticator_config(request: config::Request) -> Result<(), // config::Error>; } + +#[repr(u8)] +pub enum Command { + AuthenticatorMakeCredential = 0x01, + AuthenticatorGetAssertion = 0x02, + AuthenticatorGetNextAssertion = 0x08, + AuthenticatorGetInfo = 0x04, + AuthenticatorClientPin = 0x06, + AuthenticatorReset = 0x07, + AuthenticatorBioEnrollment = 0x09, + AuthenticatorCredentialManagement = 0x0A, + AuthenticatorSelection = 0x0B, + AuthenticatorLargeBlobs = 0x0C, + AuthenticatorConfig = 0x0D, + PrototypeAuthenticatorBioEnrollment = 0x40, + PrototypeAuthenticatorCredentialmanagement = 0x41, +} diff --git a/crates/fido-common/Cargo.toml b/crates/fido-common/Cargo.toml index 3af9167..ae8bf23 100644 --- a/crates/fido-common/Cargo.toml +++ b/crates/fido-common/Cargo.toml @@ -6,10 +6,11 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -bounded-vec = { version = "0.7.1", features = ["serde"] } ciborium = { version = "0.2.1", default-features = false, optional = true } coset = { version = "0.3.4", default-features = false } serde = { version = "1", features = ["derive"], optional = true } +serde_with = { path = "../../../serde_with/serde_with", optional = true } +bitflags = { version = "2.2.1", default-featuers = false, optional = true } [features] -serde = ["dep:serde", "bounded-vec/serde", "dep:ciborium"] +serde = ["dep:serde", "dep:serde_with", "dep:bitflags", "dep:ciborium"] diff --git a/crates/fido-common/src/attestation.rs b/crates/fido-common/src/attestation.rs index f8bd76d..caa92eb 100644 --- a/crates/fido-common/src/attestation.rs +++ b/crates/fido-common/src/attestation.rs @@ -1,6 +1,11 @@ -use serde::ser::Serializer; +#[cfg(feature = "serde")] +use crate::credential::public_key::algorithm; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; +#[cfg(feature = "serde")] +use serde_with::{serde_as, Bytes}; + +pub mod enterprise; /// > Attestation statement formats are identified by a string, called an /// > attestation statement format identifier, chosen by the author of the @@ -36,7 +41,7 @@ use serde::{Deserialize, Serialize}; /// > of registered `WebAuthn` Extensions is maintained in the IANA "WebAuthn /// > Attestation Statement Format Identifiers" registry /// > [IANA-WebAuthn-Registries] established by [RFC8809]. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum FormatIdentifier { /// > The "packed" attestation statement format is a WebAuthn-optimized @@ -71,72 +76,93 @@ pub enum FormatIdentifier { None, } -pub mod enterprise { - #[cfg(feature = "serde")] - use serde::{Deserialize, Serialize}; - - #[repr(usize)] - #[derive(Clone, Copy)] - #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] - pub enum Kind { - /// > In this case, an enterprise attestation capable authenticator, on - /// > which enterprise attestation is enabled, upon receiving the - /// > enterpriseAttestation parameter with a value of 1 (or 2, see Note - /// > below) on a authenticatorMakeCredential command, will provide - /// > enterprise attestation to a non-updateable pre-configured RP ID - /// > list, as identified by the enterprise and provided to the - /// > authenticator vendor, which is "burned into" the authenticator by - /// > the vendor. - /// > If enterprise attestation is requested for any RP ID other than - /// > the pre-configured RP ID(s), the attestation returned along with - /// > the new credential is a regular privacy-preserving attestation, - /// > i.e., NOT an enterprise attestation. - VendorFacilitated = 1, - /// > In this case, an enterprise attestation capable authenticator on - /// > which enterprise attestation is enabled, upon receiving the - /// > enterpriseAttestation parameter with a value of 2 on a - /// > authenticatorMakeCredential command, will return an enterprise - /// > attestation. The platform is enterprise-managed and has already - /// > performed the necessary vetting of the RP ID. - PlatformManaged = 2, - } +#[cfg_eval] +#[derive(Debug)] +#[cfg_attr( + feature = "serde", + serde_as, + derive(Serialize, Deserialize), + // TODO: Workaround until serde can use integer keys as tag, since "fmt" is CBOR key 0x01. + serde(untagged) +)] +pub enum Statement { + #[cfg_attr(feature = "serde", serde(rename = "packed"))] + Packed { + #[cfg_attr(feature = "serde", serde(rename = "alg", with = "algorithm"))] + algorithm: coset::iana::Algorithm, + #[cfg_attr(feature = "serde", serde_as(as = "Bytes"), serde(rename = "sig"))] + signature: Vec, + #[cfg_attr(feature = "serde", serde_as(as = "Vec"), serde(rename = "x5c"))] + attestation_certificate_chain: Vec>, // TODO: Parse X.509 certs + }, + Unregistered { + identifier: String, + data: Vec, + }, } -use coset::CborSerializable; /// > Attested credential data is a variable-length byte array added to the /// > authenticator data when generating an attestation object for a given /// > credential. -#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +#[derive(Debug)] pub struct CredentialData { /// > The AAGUID of the authenticator. pub aaguid: [u8; 16], /// The ID of the credential. - pub id: Vec, + pub id: Vec, // TODO: Make no_std compatible with the credential id length? /// The public key of the credential. - #[cfg_attr( - feature = "serde", - serde( - deserialize_with = "deserialize_coset_key", - serialize_with = "serialize_cose_key" - ) - )] pub public_key: coset::CoseKey, } -pub fn serialize_cose_key(key: &coset::CoseKey, s: S) -> Result { - use serde::ser::Error; - let bytes = key - .clone() - .to_vec() - .map_err(|e| Error::custom(e.to_string()))?; +impl TryFrom<&[u8]> for CredentialData { + // TODO: Custom error type? + type Error = coset::CoseError; - s.serialize_bytes(&bytes) + fn try_from(data: &[u8]) -> Result { + // aaguid: 16 Bytes + // SAFETY: Validate that data.len >= 16 for aaguid bytes + if data.len() < 16 { + return Err(coset::CoseError::DecodeFailed(ciborium::de::Error::Io( + coset::EndOfFile, + ))); + } + let (&aaguid, data) = data.split_array_ref::<16>(); + + // credentialIdLengh: 2 Bytes + // > Byte length L of credentialId, 16-bit unsigned big-endian integer. Value + // > MUST be ≤ 1023. + // SAFETY: Validate that there are 2 bytes for u16 + if data.len() < 2 { + return Err(coset::CoseError::DecodeFailed(ciborium::de::Error::Io( + coset::EndOfFile, + ))); + } + let (&credential_id_length, mut data) = data.split_array_ref::<2>(); + let credential_id_length = u16::from_be_bytes(credential_id_length); + if credential_id_length > 1023 { + return Err(coset::CoseError::UnexpectedItem( + "a credentialIdLength (L) of greater than 1023", + "a 16-bit unsigned big-endian integer less than or equal to 1023", + )); + } + + // credentialId: L (credential_id_length) Bytes + let credential_id: &[u8] = data.take(..credential_id_length as usize) + .ok_or(coset::CoseError::DecodeFailed(ciborium::de::Error::Io( + coset::EndOfFile, + )))?; + + Ok(Self { aaguid, id: credential_id.to_vec(), public_key: Default::default() }) + } } #[cfg(feature = "serde")] -fn deserialize_coset_key<'de, D>(deserializer: D) -> Result -where - D: serde::de::Deserializer<'de>, -{ - todo!() +impl<'de> Deserialize<'de> for CredentialData { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de> { + let data = Vec::::deserialize(deserializer)?; + // TODO: Improve error handling + CredentialData::try_from(data.as_slice()).map_err(serde::de::Error::custom) + } } diff --git a/crates/fido-common/src/attestation/enterprise.rs b/crates/fido-common/src/attestation/enterprise.rs new file mode 100644 index 0000000..297bc29 --- /dev/null +++ b/crates/fido-common/src/attestation/enterprise.rs @@ -0,0 +1,28 @@ +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +#[repr(usize)] +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum Kind { + /// > In this case, an enterprise attestation capable authenticator, on + /// > which enterprise attestation is enabled, upon receiving the + /// > enterpriseAttestation parameter with a value of 1 (or 2, see Note + /// > below) on a authenticatorMakeCredential command, will provide + /// > enterprise attestation to a non-updateable pre-configured RP ID + /// > list, as identified by the enterprise and provided to the + /// > authenticator vendor, which is "burned into" the authenticator by + /// > the vendor. + /// > If enterprise attestation is requested for any RP ID other than + /// > the pre-configured RP ID(s), the attestation returned along with + /// > the new credential is a regular privacy-preserving attestation, + /// > i.e., NOT an enterprise attestation. + VendorFacilitated = 1, + /// > In this case, an enterprise attestation capable authenticator on + /// > which enterprise attestation is enabled, upon receiving the + /// > enterpriseAttestation parameter with a value of 2 on a + /// > authenticatorMakeCredential command, will return an enterprise + /// > attestation. The platform is enterprise-managed and has already + /// > performed the necessary vetting of the RP ID. + PlatformManaged = 2, +} diff --git a/crates/fido-common/src/authenticator.rs b/crates/fido-common/src/authenticator.rs index 902ef49..e448336 100644 --- a/crates/fido-common/src/authenticator.rs +++ b/crates/fido-common/src/authenticator.rs @@ -1,10 +1,27 @@ -use crate::{attestation, extensions, Sha256Hash}; +use crate::{ + attestation, + credential::{BackupEligibility, BackupState}, + extensions, Sha256Hash, +}; use std::collections::BTreeMap; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; -pub enum Flags {} +#[cfg(feature = "serde")] +use bitflags::bitflags; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum UserPresence { + Present, + NotPresent, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum UserVerification { + Verified, + NotVerified, +} /// > The authenticator data structure encodes contextual bindings made by the /// > authenticator. These bindings are controlled by the authenticator itself, @@ -16,33 +33,130 @@ pub enum Flags {} /// > software, connected to the client over a secure channel. In both cases, /// > the Relying Party receives the authenticator data in the same format, and /// > uses its knowledge of the authenticator to make trust decisions. -#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +#[derive(Debug)] pub struct Data { /// > SHA-256 hash of the RP ID the credential is scoped to. pub relying_party_id_hash: Sha256Hash, - pub user_is_present: bool, - pub user_is_verified: bool, + pub user_presence: UserPresence, + pub user_verification: UserVerification, + pub backup_eligibility: BackupEligibility, + pub backup_state: BackupState, pub signature_counter: u32, pub attested_credential_data: Option, pub extensions: Option>>, } -impl Data { - fn try_from(value: &[u8]) -> Option { - // 32 bytes: RP id hash - let rp_id = value.get(0..32)?.as_ref(); - // - let flags = value.get(32)?; +bitflags! { + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] + // > Flags (bit 0 is the least significant bit): + struct DataFlags: u8 { + // > Bit 0: User Present (UP) result. + // > 1 means the user is present. + const USER_PRESENCE = 0b1 << 0; + // > Bit 2: User Verified (UV) result. + // > 1 means the user is verified. + const USER_VERIFIED = 0b1 << 2; + // > Bit 3: Backup Eligibility (BE). + // > 1 means the public key credential source is backup eligible. + const BACKUP_ELIGIBLE = 0b1 << 3; + // > Bit 4: Backup State (BS). + // > 1 means the public key credential source is currently backed up. + const BACKUP_STATE = 0b1 << 4; + // > Bit 6: Attested credential data included (AT). + // > Indicates whether the authenticator added attested credential data. + const ATTESTED_CREDENTIAL_DATA = 0b1 << 6; + // > Bit 7: Extension data included (ED). + // > Indicates if the authenticator data has extensions. + const EXTENSION_DATA_INCLUDED = 0b1 << 7; + } +} + +impl DataFlags { + fn user_presence(&self) -> UserPresence { + if self.contains(DataFlags::USER_PRESENCE) { + UserPresence::Present + } else { + UserPresence::NotPresent + } + } + + fn user_verification(&self) -> UserVerification { + if self.contains(DataFlags::USER_VERIFIED) { + UserVerification::Verified + } else { + UserVerification::NotVerified + } + } - None + fn backup_eligibility(&self) -> BackupEligibility { + if self.contains(DataFlags::BACKUP_ELIGIBLE) { + BackupEligibility::Eligible + } else { + BackupEligibility::Ineligible + } + } + + fn backup_state(&self) -> BackupState { + if self.contains(DataFlags::BACKUP_STATE) { + BackupState::BackedUp + } else { + BackupState::NotBackedUp + } + } + + fn has_attested_credential_data(&self) -> bool { + self.contains(DataFlags::ATTESTED_CREDENTIAL_DATA) } } -impl TryFrom<&[u8]> for Data { - type Error = (); +#[cfg(feature = "serde")] +impl<'de> Deserialize<'de> for Data { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + use serde::de; + + let data = Vec::::deserialize(deserializer)?; + + // The authenticator data structure is a byte array of 37 bytes or more + if data.len() < 37 { + return Err(de::Error::invalid_length(data.len(), &"at least 37 bytes")); + } + + // SAFETY: split_array_ref panics if const param is out of bounds for slice. + // data.len() guard protects against out of bounds indicies. + + // rpIdHash: 32 Bytes + // > SHA-256 hash of the RP ID the credential is scoped to. + let (&relying_party_id_hash, data): (&Sha256Hash, _) = data.split_array_ref::<32>(); + + // flags: 1 Byte + let (&[flags], data): (&[u8; 1], _) = data.split_array_ref::<1>(); + let flags = DataFlags::from_bits_truncate(flags); + + // signCount: 4 Bytes + // > Signature counter, 32-bit unsigned big-endian integer. + let (&counter_be_bytes, data) = data.split_array_ref::<4>(); + let signature_counter = u32::from_be_bytes(counter_be_bytes); + + let attested_credential_data: Option = + if flags.has_attested_credential_data() { + Some(attestation::CredentialData::try_from(data).map_err(de::Error::custom)?) + } else { + None + }; - fn try_from(value: &[u8]) -> Result { - Self::try_from(value).ok_or(()) + Ok(Self { + relying_party_id_hash, + user_presence: flags.user_presence(), + user_verification: flags.user_verification(), + backup_eligibility: flags.backup_eligibility(), + backup_state: flags.backup_state(), + signature_counter, + attested_credential_data, + extensions: None, + }) } } diff --git a/crates/fido-common/src/credential/mod.rs b/crates/fido-common/src/credential/mod.rs index b814f65..c4d15c1 100644 --- a/crates/fido-common/src/credential/mod.rs +++ b/crates/fido-common/src/credential/mod.rs @@ -16,3 +16,15 @@ pub enum Type { #[cfg_attr(feature = "serde", serde(rename = "public-key"))] PublicKey, } + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum BackupState { + BackedUp = 0b0, + NotBackedUp = 0b1, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum BackupEligibility { + Eligible, + Ineligible, +} diff --git a/crates/fido-common/src/credential/public_key.rs b/crates/fido-common/src/credential/public_key.rs index d0aff71..976dd79 100644 --- a/crates/fido-common/src/credential/public_key.rs +++ b/crates/fido-common/src/credential/public_key.rs @@ -1,13 +1,39 @@ -use crate::registry::algorithms; use crate::{authenticator::Transport, credential}; -use bounded_vec::BoundedVec; use std::collections::BTreeSet; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; +#[cfg(feature = "serde")] +use serde_with::{serde_as, skip_serializing_none, Bytes}; + +#[cfg(feature = "serde")] +pub(crate) mod algorithm { + use coset::iana::{Algorithm, EnumI64}; + use serde::{Deserialize, Serialize}; + + pub(crate) fn serialize(algorithm: &Algorithm, serializer: S) -> Result + where + S: serde::Serializer, + { + let i = algorithm.to_i64(); + i64::serialize(&i, serializer) + } + + pub(crate) fn deserialize<'de, D>(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let i = i64::deserialize(deserializer)?; + coset::iana::Algorithm::from_i64(i).ok_or(serde::de::Error::invalid_value( + serde::de::Unexpected::Signed(i), + &"an IANA-registered COSE algorithm value", + )) + } +} /// > This dictionary is used to supply additional parameters when /// > creating a new credential. +#[cfg_eval] #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Parameters { @@ -20,13 +46,41 @@ pub struct Parameters { /// > algorithm with which the newly generated credential will /// > be used, and thus also the type of asymmetric key pair to /// > be generated, e.g., RSA or Elliptic Curve. - #[cfg_attr(feature = "serde", serde(rename = "alg"))] - pub algorithm: algorithms::Signature, + #[cfg_attr(feature = "serde", serde(rename = "alg", with = "algorithm"))] + pub algorithm: coset::iana::Algorithm, } +// #[cfg(feature = "serde")] +// pub(crate) fn deserialize_algorithm<'de, D>(deserializer: D) -> +// Result where +// D: serde::Deserializer<'de>, +// { +// use serde::de; +// +// let i = i64::deserialize(deserializer)?; +// coset::iana::Algorithm::from_i64(i).ok_or(de::Error::invalid_value( +// de::Unexpected::Signed(i), +// &"an IANA-registered COSE algorithm value", +// )) +// } +// +// #[cfg(feature = "serde")] +// pub(crate) fn serialize_algorithm( +// algorithm: &coset::iana::Algorithm, +// s: S, +// ) -> Result { +// s.serialize_i64(algorithm.to_i64()) +// } + /// > This dictionary identifies a specific public key credential. -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug)] +#[cfg_eval] +#[derive(Debug, Clone)] +#[cfg_attr( + feature = "serde", + serde_as, + skip_serializing_none, + derive(Serialize, Deserialize) +)] pub struct Descriptor { /// > This member contains the type of the public key credential /// > the caller is referring to. @@ -35,7 +89,9 @@ pub struct Descriptor { /// > A probabilistically-unique byte sequence identifying a /// > public key credential source and its authentication /// > assertions. - pub id: BoundedVec, + // Bounds: [16, 1023] bytes + #[cfg_attr(feature = "serde", serde_as(as = "Bytes"))] + pub id: Vec, /// > This... member contains a hint as to how the client might /// > communicate with the managing authenticator of the public /// > key credential the caller is referring to. @@ -46,9 +102,12 @@ pub struct Descriptor { /// > account to which the new public key credential will be associated at /// > the RP. /// Due to deprecation, the `icon` URL is omitted. See . -#[derive(Debug)] +#[cfg_eval] +#[derive(Debug, Clone)] #[cfg_attr( feature = "serde", + serde_as, + skip_serializing_none, derive(Serialize, Deserialize), serde(rename_all = "camelCase") )] @@ -64,7 +123,9 @@ pub struct UserEntity { // // WebAuthn says that "The user handle MUST NOT be empty." To maximimize compatibility, the // definition from the CTAP specs is used. - pub id: BoundedVec, + // Bounds: [0, 64] bytes + #[cfg_attr(feature = "serde", serde_as(as = "Bytes"))] + pub id: Vec, /// > a human-palatable identifier for a user account. It is intended /// > only for display, i.e., aiding the user in determining the /// > difference between user accounts with similar displayNames. For diff --git a/crates/fido-common/src/extensions.rs b/crates/fido-common/src/extensions.rs index d997622..e124dfb 100644 --- a/crates/fido-common/src/extensions.rs +++ b/crates/fido-common/src/extensions.rs @@ -24,20 +24,36 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum Identifier { + #[cfg_attr(feature = "serde", serde(rename = "appid"))] AppId, + #[cfg_attr(feature = "serde", serde(rename = "txAuthSimple"))] TransactionAuthSimple, + #[cfg_attr(feature = "serde", serde(rename = "txAuthGeneric"))] TransactionAuthGeneric, + #[cfg_attr(feature = "serde", serde(rename = "authnSel"))] AuthenticationSelection, + #[cfg_attr(feature = "serde", serde(rename = "exts"))] Extensions, + #[cfg_attr(feature = "serde", serde(rename = "uvi"))] UserVerificationIndex, + #[cfg_attr(feature = "serde", serde(rename = "loc"))] Location, + #[cfg_attr(feature = "serde", serde(rename = "uvm"))] UserVerificationMethod, + #[cfg_attr(feature = "serde", serde(rename = "credProtect"))] CredentialProtection, + #[cfg_attr(feature = "serde", serde(rename = "credBlob"))] CredentialBlob, + #[cfg_attr(feature = "serde", serde(rename = "largeBlobKey"))] LargeBlobKey, + #[cfg_attr(feature = "serde", serde(rename = "minPinLength"))] MinPinLength, + #[cfg_attr(feature = "serde", serde(rename = "hmac-secret"))] HmacSecret, + #[cfg_attr(feature = "serde", serde(rename = "appidExclude"))] AppIdExclude, + #[cfg_attr(feature = "serde", serde(rename = "credProps"))] CredentialProperties, + #[cfg_attr(feature = "serde", serde(rename = "largeBlob"))] LargeBlob, } diff --git a/crates/fido-common/src/lib.rs b/crates/fido-common/src/lib.rs index 621c756..9f605e7 100644 --- a/crates/fido-common/src/lib.rs +++ b/crates/fido-common/src/lib.rs @@ -1,5 +1,7 @@ -pub mod authenticator; +#![feature(cfg_eval, split_array, slice_take)] + pub mod attestation; +pub mod authenticator; pub mod credential; pub mod extensions; pub mod registry; diff --git a/crates/hid-ctap2-proto/Cargo.toml b/crates/hid-ctap2-proto/Cargo.toml index 12c6185..4147b31 100644 --- a/crates/hid-ctap2-proto/Cargo.toml +++ b/crates/hid-ctap2-proto/Cargo.toml @@ -9,8 +9,10 @@ edition = "2021" ciborium = "0.2.1" ciborium-io = "0.2.1" ctap2-proto = { path = "../ctap2-proto", features = ["serde"] } +hidapi = { version = "2.3.2" } ctaphid = { version = "0.3.1", default_features = false } serde = "1.0.163" [dev-dependencies] -hidapi = { version = "^1.2.6", default-features = false, features = ["linux-shared-hidraw"] } +rand = "0.8.5" +env_logger = "0.10.0" diff --git a/crates/hid-ctap2-proto/src/hid.rs b/crates/hid-ctap2-proto/src/hid.rs new file mode 100644 index 0000000..c988033 --- /dev/null +++ b/crates/hid-ctap2-proto/src/hid.rs @@ -0,0 +1,109 @@ +use ctap2_proto::Command; +use device::{DeviceInfo, HidDevice}; + +pub struct HidAuthenticator(ctaphid::Device); + +impl TryFrom for HidAuthenticator { + type Error = ctaphid::error::Error; + + fn try_from(device: hidapi::HidDevice) -> Result { + let info = device + .get_device_info() + .map_err(|e| ctaphid::error::RequestError::PacketSendingFailed(Box::new(e)))?; + let device = ctaphid::Device::new(HidDevice(device), DeviceInfo(info))?; + Ok(Self(device)) + } +} + +mod device { + //! Provides wrapper types for `hidapi` device types that implement required + //! `ctaphid` interfaces. + use std::borrow::Cow; + + pub struct HidDevice(pub hidapi::HidDevice); + #[derive(Debug)] + pub struct DeviceInfo(pub hidapi::DeviceInfo); + + impl ctaphid::HidDevice for HidDevice { + type Info = DeviceInfo; + + fn send(&self, data: &[u8]) -> Result<(), ctaphid::error::RequestError> { + self.0 + .write(data) + .map_err(|e| ctaphid::error::RequestError::PacketSendingFailed(e.into()))?; + Ok(()) + } + + fn receive<'a>( + &self, + buffer: &'a mut [u8], + timeout: Option, + ) -> Result<&'a [u8], ctaphid::error::ResponseError> { + let timeout_millis: i32 = match timeout { + Some(timeout) => timeout.as_millis() as i32, + None => -1, + }; + let count = self + .0 + .read_timeout(buffer, timeout_millis) + .map_err(|e| match e { + hidapi::HidError::HidApiError { message } => { + ctaphid::error::ResponseError::PacketReceivingFailed(message.into()) + } + _ => todo!(), + })?; + + Ok(&buffer[..count]) + } + } + + impl ctaphid::HidDeviceInfo for DeviceInfo { + fn vendor_id(&self) -> u16 { + self.0.vendor_id() + } + + fn product_id(&self) -> u16 { + self.0.product_id() + } + + fn path(&self) -> std::borrow::Cow<'_, str> { + let path = self + .0 + .path() + .to_str() + .expect("Device path must be valid UTF-8."); + Cow::from(path) + } + } +} + +impl HidAuthenticator { + fn send_raw(&self, command: Command, bytes: &[u8]) -> Result, ctaphid::error::Error> { + self.0.ctap2(command as u8, bytes) + } + + pub fn send(&self, command: Command, req: Req) -> Result + where + Req: serde::Serialize, + Res: for<'de> serde::Deserialize<'de>, + { + let mut data: Vec = Vec::new(); + ciborium::ser::into_writer(&req, &mut data).map_err(|e| match e { + ciborium::ser::Error::Io(_) => ctaphid::error::RequestError::IncompleteWrite, + ciborium::ser::Error::Value(desc) => { + ctaphid::error::RequestError::PacketSendingFailed(desc.into()) + } + })?; + + let response = self.send_raw(command, &data)?; + + match ciborium::de::from_reader(response.as_slice()) { + Ok(response) => Ok(response), + // TODO: Improve error handling + Err(e) => { + println!("ERROR: {e}"); + todo!() + } + } + } +} diff --git a/crates/hid-ctap2-proto/src/lib.rs b/crates/hid-ctap2-proto/src/lib.rs index b8110cd..e15be0f 100644 --- a/crates/hid-ctap2-proto/src/lib.rs +++ b/crates/hid-ctap2-proto/src/lib.rs @@ -1,86 +1,30 @@ -use ctap2_proto::{prelude::*, Ctap2_2Authenticator}; -use ctaphid::types::Command; +#![cfg_attr(test, feature(split_array, lazy_cell))] -pub struct Device(ctaphid::Device); +use ctap2_proto::{prelude::*, Ctap2_2Authenticator}; +use hid::HidAuthenticator; -impl Device { - fn send_raw(&self, command: Command, bytes: &[u8]) -> Result, ctaphid::error::Error> { - self.0.ctap2(command.into(), bytes) - } +pub mod hid; - fn send(&self, req: Req) -> Result - where - Req: serde::Serialize, - Res: for<'de> serde::Deserialize<'de>, - { - let command = Command::Cbor; - let mut data: Vec = Vec::new(); - ciborium::ser::into_writer(&req, &mut data).map_err(|e| match e { - ciborium::ser::Error::Io(_) => ctaphid::error::RequestError::IncompleteWrite, - ciborium::ser::Error::Value(desc) => { - ctaphid::error::RequestError::PacketSendingFailed(desc.into()) - } - })?; - - let response = self.0.ctap2(command.into(), &data)?; - - match ciborium::de::from_reader(response.as_slice()) { - Ok(res) => Ok(res), - Err(e) => match e { - ciborium::de::Error::Io(_) => todo!(), - ciborium::de::Error::Syntax(_) => todo!(), - ciborium::de::Error::Semantic(_, _) => todo!(), - ciborium::de::Error::RecursionLimitExceeded => todo!(), - }, - } - } -} - -impl Ctap2_2Authenticator for Device -where - D: ctaphid::HidDevice, -{ +impl Ctap2_2Authenticator for HidAuthenticator { fn make_credential(&mut self, request: make::Request) -> Result { - // TODO: How the heck am i supposed to handle errors??? - self.send(&request).map_err(|e| match e { - ctaphid::error::Error::CommandError(e) => match e { - ctaphid::error::CommandError::CborError(_) => todo!(), - ctaphid::error::CommandError::InvalidPingData => todo!(), - ctaphid::error::CommandError::NotSupported(_) => todo!(), - }, - ctaphid::error::Error::RequestError(e) => match e { - ctaphid::error::RequestError::IncompleteWrite => todo!(), - ctaphid::error::RequestError::MessageFragmentationFailed(_) => todo!(), - ctaphid::error::RequestError::PacketSendingFailed(_) => todo!(), - ctaphid::error::RequestError::PacketSerializationFailed(_) => todo!(), - }, - ctaphid::error::Error::ResponseError(e) => match e { - ctaphid::error::ResponseError::CommandFailed(_) => todo!(), - ctaphid::error::ResponseError::MessageDefragmentationFailed(_) => todo!(), - ctaphid::error::ResponseError::PacketParsingFailed(_) => todo!(), - ctaphid::error::ResponseError::PacketReceivingFailed(_) => todo!(), - ctaphid::error::ResponseError::Timeout => todo!(), - ctaphid::error::ResponseError::MissingErrorCode => todo!(), - ctaphid::error::ResponseError::UnexpectedCommand { expected, actual } => todo!(), - ctaphid::error::ResponseError::UnexpectedKeepAlive(_) => todo!(), - ctaphid::error::ResponseError::UnexpectedResponseData(_) => todo!(), - }, - }) + Ok(self + .send(Command::AuthenticatorMakeCredential, request) + .unwrap()) // TODO: Properly parse/convert errors } - fn get_assertion(request: get::Request) -> Result { - todo!() + fn get_assertion(&mut self, request: get::Request) -> Result { + Ok(self + .send(Command::AuthenticatorGetAssertion, request) + .unwrap()) } fn get_info(&self) -> device::Info { - let info = self.send_raw(Command::Cbor, &[1u8, 4]).unwrap(); - println!("info: {info:#?}"); - let info: device::Info = - ciborium::de::from_reader::(info.as_slice()).unwrap(); - info + self.send(Command::AuthenticatorGetInfo, ()).unwrap() } - fn client_pin(request: client_pin::Request) -> Result { + fn client_pin( + _request: client_pin::Request, + ) -> Result { todo!() } @@ -95,91 +39,103 @@ where #[cfg(test)] mod tests { - use std::ffi::CStr; - - use ctap2_proto::Ctap2_2Authenticator; - use ctaphid::{ - error::{RequestError, ResponseError}, - types::Command, - }; - struct HidDevice(hidapi::HidDevice); - #[derive(Debug)] - struct HidDeviceInfoMy(hidapi::DeviceInfo); - - impl ctaphid::HidDeviceInfo for HidDeviceInfoMy { - fn vendor_id(&self) -> u16 { - hidapi::DeviceInfo::vendor_id(&self.0) - } + extern crate hidapi; - fn product_id(&self) -> u16 { - hidapi::DeviceInfo::product_id(&self.0) - } + use std::sync::{LazyLock, Mutex}; - fn path(&self) -> std::borrow::Cow<'_, str> { - let cstr: &CStr = hidapi::DeviceInfo::path(&self.0); - let s = cstr.to_str().unwrap(); - std::borrow::Cow::from(s) - } - } + use crate::hid::HidAuthenticator; + use ctap2_proto::prelude::{credential::public_key, *}; + use rand::{distributions, Rng}; - impl ctaphid::HidDevice for HidDevice { - type Info = HidDeviceInfoMy; + static AUTHENTICATOR: LazyLock>> = + LazyLock::new(|| Mutex::new(get_authenticator())); - fn send(&self, data: &[u8]) -> Result<(), ctaphid::error::RequestError> { - println!("sending bytes: {data:#?}"); - hidapi::HidDevice::write(&self.0, data) - .map_err(|e| RequestError::PacketSendingFailed(e.into()))?; - Ok(()) - } + fn init() { + let _ = env_logger::builder().is_test(true).try_init(); + } - fn receive<'a>( - &self, - buffer: &'a mut [u8], - timeout: Option, - ) -> Result<&'a [u8], ctaphid::error::ResponseError> { - println!("reading bytes"); - let duration = if let Some(timeout) = timeout { - i32::try_from(timeout.as_millis()) - .map_err(|err| ResponseError::PacketReceivingFailed(err.into()))? - } else { - -1 + fn get_authenticator() -> Option { + let hidapi = hidapi::HidApi::new().unwrap(); + for info in hidapi.device_list() { + let Ok(device) = info.open_device(&hidapi) else { + continue; }; - let n = self - .0 - .read_timeout(buffer, duration) - .map_err(|err| ResponseError::PacketReceivingFailed(err.into()))?; - if n == buffer.len() { - Ok(&buffer[1..n]) - } else if n == 0 { - Err(ResponseError::Timeout) - } else { - Ok(&buffer[..n]) - } + let Ok(authenticator) = device.try_into() else { continue }; + return Some(authenticator); } + + None } #[test] - fn get_info() { - let hidapi = hidapi::HidApi::new().unwrap(); - let devices = hidapi.device_list(); - for device_info in devices { - let hid_device = hidapi::DeviceInfo::open_device(&device_info, &hidapi); - let hid_device = match hid_device { - Ok(hid_device) => hid_device, - Err(e) => { - println!("error: {e:#?}"); - continue; - } - }; - let device = HidDevice(hid_device); - let device = - ctaphid::Device::new(device, HidDeviceInfoMy(device_info.to_owned())).unwrap(); - let device = super::Device(device); - println!("info: {:#?}", device.0.ctap2(Command::Cbor.into(), &[])); - } - assert!(false); + fn test_get_info() { + init(); + + let guard = AUTHENTICATOR.lock().unwrap(); + let authenticator = guard.as_ref().unwrap(); + + let info = authenticator.get_info(); + println!("deserialized: {:#?}", info); } #[test] - fn quickstart() {} + fn make_credential() { + init(); + + let mut guard = AUTHENTICATOR.lock().unwrap(); + let authenticator = guard.as_mut().unwrap(); + + let info = authenticator.get_info(); + + let client_data_hash: Vec = rand::thread_rng() + .sample_iter(&distributions::Standard) + .take(32) + .collect(); + + let user_id = rand::thread_rng() + .sample_iter(&distributions::Standard) + .take(32) + .collect(); + + let rp = public_key::RelyingPartyEntity { + id: "com.example".to_string(), + name: Some("Example Inc.".into()), + }; + + let user = public_key::UserEntity { + id: user_id, + name: Some("example_user".to_string()), + display_name: Some("Example User".to_string()), + }; + + let pub_key_params = info.algorithms.unwrap(); + + let options = [(make::OptionKey::Discoverable, true)].into(); + + let req = make::Request::builder() + .client_data_hash(&client_data_hash.split_array_ref::<32>().0) + .relying_party(&rp) + .user(&user) + .public_key_credential_params(pub_key_params.as_slice()) + .options(&options) + .build(); + + println!("request: {req:#?}"); + let response = authenticator.make_credential(req); + println!("response: {response:#?}"); + + let req = get::Request { + relying_party_id: &rp.id, + client_data_hash: client_data_hash.as_slice().try_into().unwrap(), + allow_list: None, + extensions: None, + options: None, + pin_uv_auth_param: None, + pin_uv_auth_protocol_version: None, + }; + + println!("request: {req:#?}"); + let response = authenticator.get_assertion(req); + println!("response: {response:#?}"); + } } diff --git a/crates/webauthn3-proto/src/client.rs b/crates/webauthn3-proto/src/client.rs index 1cb9379..d2a7ee4 100644 --- a/crates/webauthn3-proto/src/client.rs +++ b/crates/webauthn3-proto/src/client.rs @@ -32,18 +32,3 @@ pub struct Data { /// > absence indicates that the client doesn’t support token binding. pub token_binding: Option, } - -#[cfg(feature = "serde")] -impl Serialize for Data { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - // Keys are: "type", "challenge", "origin", "topOrigin", "crossOrigin" - const LEN: usize = 5; - let mut map = serializer.serialize_map(Some(LEN))?; - - // map.serialize_entry("type", value) - todo!() - } -} diff --git a/crates/webauthn3-proto/src/lib.rs b/crates/webauthn3-proto/src/lib.rs index 883f9e3..5c83334 100644 --- a/crates/webauthn3-proto/src/lib.rs +++ b/crates/webauthn3-proto/src/lib.rs @@ -1,34 +1,34 @@ -#![feature(async_fn_in_trait, adt_const_params, associated_const_equality)] -#![allow(incomplete_features, clippy::unused_async, clippy::doc_markdown)] - -pub mod attestation; -pub mod authenticator; -pub mod client; -pub mod public_key; -pub mod token; - -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, Copy)] -/// > A WebAuthn Relying Party may require user verification for some of its -/// > operations but not for others, and may use this type to express its needs. -pub enum UserVerificationRequirement { - /// > The Relying Party requires user verification for the operation and - /// > will fail the overall ceremony if the response does not have the UV - /// > flag set. The client MUST return an error if user verification cannot - /// > be performed. - #[cfg_attr(feature = "serde", serde(rename = "required"))] - Required, - /// > The Relying Party prefers user verification for the operation if - /// > possible, but will not fail the operation if the response does not - /// > have the UV flag set. - #[cfg_attr(feature = "serde", serde(rename = "preferred"))] - Preferred, - /// > The Relying Party does not want user verification employed during the - /// > operation (e.g., in the interest of minimizing disruption to the user - /// > interaction flow). - #[cfg_attr(feature = "serde", serde(rename = "discouraged"))] - Discouraged, -} +// #![feature(async_fn_in_trait, adt_const_params, associated_const_equality)] +// #![allow(incomplete_features, clippy::unused_async, clippy::doc_markdown)] +// +// pub mod attestation; +// pub mod authenticator; +// pub mod client; +// pub mod public_key; +// pub mod token; +// +// #[cfg(feature = "serde")] +// use serde::{Deserialize, Serialize}; +// +// #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +// #[derive(Debug, Clone, Copy)] +// > A WebAuthn Relying Party may require user verification for some of its +// > operations but not for others, and may use this type to express its needs. +// pub enum UserVerificationRequirement { +// > The Relying Party requires user verification for the operation and +// > will fail the overall ceremony if the response does not have the UV +// > flag set. The client MUST return an error if user verification cannot +// > be performed. +// #[cfg_attr(feature = "serde", serde(rename = "required"))] +// Required, +// > The Relying Party prefers user verification for the operation if +// > possible, but will not fail the operation if the response does not +// > have the UV flag set. +// #[cfg_attr(feature = "serde", serde(rename = "preferred"))] +// Preferred, +// > The Relying Party does not want user verification employed during the +// > operation (e.g., in the interest of minimizing disruption to the user +// > interaction flow). +// #[cfg_attr(feature = "serde", serde(rename = "discouraged"))] +// Discouraged, +// }