Nick Zana 12 months ago
parent cf792b97cb
commit 1daffb7db7

597
Cargo.lock generated

@ -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"

@ -3,3 +3,6 @@
members = [
"crates/*",
]
[patch.crates-io]
serde = { path = "../serde/serde" }

@ -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;

@ -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"]

@ -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<make::Response, make::Error> {
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<u8>)> {
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<get::Response, get::Error> {
todo!()
}
fn get_info(&self) -> device::Info {
todo!()
}
fn client_pin(request: client_pin::Request) -> Result<client_pin::Response, client_pin::Error> {
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
}

@ -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);

@ -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<extensions::Identifier, &'a [u8]>>,
/// > Parameters to influence authenticator operation.
#[serde(rename = 0x05)]
pub options: Option<&'a BTreeMap<OptionKey, bool>>,
#[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<AuthProtocolVersion>,
}
/// 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<u8>,
/// > 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<u8>,
/// > [`public_key::UserEntity`] structure containing the user account
/// > information
#[serde(rename = 0x04)]
pub user: Option<public_key::UserEntity>,
/// > 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<usize>,
/// > 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<bool>,
/// > 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<Bytes>"), serde(rename = 0x07))]
pub large_blob_key: Option<Vec<u8>>,
}

@ -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 <https://github.com/serde-rs/serde/pull/2056> is merged
// PR: ( Integer/boolean tags for internally/adjacently tagged enums #2056 )
#[cfg(feature = "serde")]
impl Serialize for AuthProtocolVersion {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_u8(match self {
AuthProtocolVersion::One => 1,
AuthProtocolVersion::Two => 2,
})
}
}
// workaround until <https://github.com/serde-rs/serde/pull/2056> is merged
// PR: ( Integer/boolean tags for internally/adjacently tagged enums #2056 )
#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for AuthProtocolVersion {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
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,

@ -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<u8>, Vec<u8>>, /* 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?
},

@ -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<extensions::Identifier, Vec<u8>>>,
#[builder(default, setter(strip_option))]
#[cfg_attr(feature = "serde", serde(rename = 0x06))]
pub extensions: Option<&'a BTreeMap<extensions::Identifier, Vec<u8>>>,
#[builder(default, setter(strip_option))]
#[cfg_attr(feature = "serde", serde(rename = 0x07))]
pub options: Option<&'a BTreeMap<OptionKey, bool>>,
pub pin_uv_auth_param: &'a [u8],
#[builder(default, setter(strip_option))]
#[cfg_attr(
feature = "serde",
serde(rename = 0x08),
serde_as(as = "Option<Bytes>")
)]
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<client_pin::AuthProtocolVersion>,
/// > 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<attestation::enterprise::Kind>,
}
#[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<attestation::Statement>,
/// > 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<bool>,
/// > Contains the `largeBlobKey` for the credential, if requested with the
/// > `largeBlobKey` extension.
#[cfg_attr(feature = "serde", serde(rename = 0x05))]
pub large_blob_key: Option<Vec<u8>>,
/// > 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<BTreeMap<extensions::Identifier, Vec<u8>>>,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
struct AttestationStatement {}

@ -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
/// > cant 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<Version>,
/// > List of supported extensions.
#[cfg_attr(feature = "serde", serde(rename = 0x02))]
pub extensions: Option<BTreeSet<extensions::Identifier>>,
/// > 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<BTreeMap<OptionId, bool>>,
/// > Maximum message size supported by the authenticator.
#[cfg_attr(feature = "serde", serde(rename = 0x05))]
pub max_message_size: Option<usize>,
/// > List of supported PIN/UV auth protocols in order of decreasing
/// > authenticator preference. MUST NOT contain duplicate values...
pub pin_uv_auth_protocols: Option<BoundedVec<AuthProtocolVersion, 1, { usize::MAX }>>,
// Cannot be empty if present
#[cfg_attr(feature = "serde", serde(rename = 0x06))]
pub pin_uv_auth_protocols: Option<Vec<AuthProtocolVersion>>,
/// > 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<NonZeroUsize>,
/// > Maximum Credential ID Length supported by the authenticator.
#[cfg_attr(feature = "serde", serde(rename = 0x08))]
pub max_credential_id_length: Option<NonZeroUsize>,
/// > List of supported transports.
#[cfg_attr(feature = "serde", serde(rename = 0x09))]
pub transports: Option<BTreeSet<Transport>>,
/// > 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<BoundedVec<public_key::Parameters, 1, { usize::MAX }>>,
#[cfg_attr(feature = "serde", serde(rename = 0x0A))]
// Cannot be empty if present
pub algorithms: Option<Vec<public_key::Parameters>>,
/// > 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<UsizeN<1024>>,
/// > 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<bool>,
/// > 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<usize>,
/// > Indicates the firmware version of the authenticator model identified
/// > by AAGUID.
#[cfg_attr(feature = "serde", serde(rename = 0x0E))]
pub firmware_version: Option<usize>,
/// > 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<UsizeN<32>>,
/// > 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<usize>,
/// > 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<NonZeroUsize>,
/// > 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<BTreeSet<registry::UserVerify>>,
/// > This specifies a list of authenticator certifications.
#[cfg_attr(feature = "serde", serde(rename = 0x13))]
pub certifications: Option<BTreeSet<Certification>>,
/// > 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<usize>,
/// > 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<BTreeSet<usize>>,
/// > List of supported attestation formats.
#[cfg_attr(feature = "serde", serde(rename = 0x16))]
pub attestation_formats: Option<BTreeSet<attestation::FormatIdentifier>>,
/// > 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<usize>,
/// > If present the authenticator requires a 10 second touch for reset.
#[cfg_attr(feature = "serde", serde(rename = 0x18))]
pub long_touch_for_reset: Option<bool>,
}

@ -8,4 +8,3 @@ pub mod credential;
pub mod device;
pub mod reset;
pub mod selection;

@ -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<get::Response, get::Error>;
fn get_assertion(&mut self, request: get::Request) -> Result<get::Response, get::Error>;
/// > 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,
}

@ -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"]

@ -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<u8>,
#[cfg_attr(feature = "serde", serde_as(as = "Vec<Bytes>"), serde(rename = "x5c"))]
attestation_certificate_chain: Vec<Vec<u8>>, // TODO: Parse X.509 certs
},
Unregistered {
identifier: String,
data: Vec<u8>,
},
}
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<u8>,
pub id: Vec<u8>, // 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<S: Serializer>(key: &coset::CoseKey, s: S) -> Result<S::Ok, S::Error> {
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<Self, Self::Error> {
// 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<coset::CoseKey, D::Error>
where
D: serde::de::Deserializer<'de>,
{
todo!()
impl<'de> Deserialize<'de> for CredentialData {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de> {
let data = Vec::<u8>::deserialize(deserializer)?;
// TODO: Improve error handling
CredentialData::try_from(data.as_slice()).map_err(serde::de::Error::custom)
}
}

@ -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,
}

@ -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<attestation::CredentialData>,
pub extensions: Option<BTreeMap<extensions::Identifier, Vec<u8>>>,
}
impl Data {
fn try_from(value: &[u8]) -> Option<Self> {
// 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<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de;
let data = Vec::<u8>::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<attestation::CredentialData> =
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, Self::Error> {
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,
})
}
}

@ -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,
}

@ -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<S>(algorithm: &Algorithm, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let i = algorithm.to_i64();
i64::serialize(&i, serializer)
}
pub(crate) fn deserialize<'de, D>(deserializer: D) -> Result<Algorithm, D::Error>
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<coset::iana::Algorithm, D::Error> 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<S: serde::Serializer>(
// algorithm: &coset::iana::Algorithm,
// s: S,
// ) -> Result<S::Ok, S::Error> {
// 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<u8, 16, 1023>,
// Bounds: [16, 1023] bytes
#[cfg_attr(feature = "serde", serde_as(as = "Bytes"))]
pub id: Vec<u8>,
/// > 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 <https://github.com/w3c/webauthn/pull/1337/>.
#[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<u8, 0, 64>,
// Bounds: [0, 64] bytes
#[cfg_attr(feature = "serde", serde_as(as = "Bytes"))]
pub id: Vec<u8>,
/// > 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

@ -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,
}

@ -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;

@ -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"

@ -0,0 +1,109 @@
use ctap2_proto::Command;
use device::{DeviceInfo, HidDevice};
pub struct HidAuthenticator<D: ctaphid::HidDevice = HidDevice>(ctaphid::Device<D>);
impl TryFrom<hidapi::HidDevice> for HidAuthenticator<HidDevice> {
type Error = ctaphid::error::Error;
fn try_from(device: hidapi::HidDevice) -> Result<Self, Self::Error> {
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<std::time::Duration>,
) -> 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<D: ctaphid::HidDevice> HidAuthenticator<D> {
fn send_raw(&self, command: Command, bytes: &[u8]) -> Result<Vec<u8>, ctaphid::error::Error> {
self.0.ctap2(command as u8, bytes)
}
pub fn send<Req, Res>(&self, command: Command, req: Req) -> Result<Res, ctaphid::error::Error>
where
Req: serde::Serialize,
Res: for<'de> serde::Deserialize<'de>,
{
let mut data: Vec<u8> = 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!()
}
}
}
}

@ -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<D: ctaphid::HidDevice>(ctaphid::Device<D>);
use ctap2_proto::{prelude::*, Ctap2_2Authenticator};
use hid::HidAuthenticator;
impl<D: ctaphid::HidDevice> Device<D> {
fn send_raw(&self, command: Command, bytes: &[u8]) -> Result<Vec<u8>, ctaphid::error::Error> {
self.0.ctap2(command.into(), bytes)
}
pub mod hid;
fn send<Req, Res>(&self, req: Req) -> Result<Res, ctaphid::error::Error>
where
Req: serde::Serialize,
Res: for<'de> serde::Deserialize<'de>,
{
let command = Command::Cbor;
let mut data: Vec<u8> = 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<D> Ctap2_2Authenticator for Device<D>
where
D: ctaphid::HidDevice,
{
impl<D: ctaphid::HidDevice> Ctap2_2Authenticator for HidAuthenticator<D> {
fn make_credential(&mut self, request: make::Request) -> Result<make::Response, make::Error> {
// 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<get::Response, get::Error> {
todo!()
fn get_assertion(&mut self, request: get::Request) -> Result<get::Response, get::Error> {
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::<device::Info, _>(info.as_slice()).unwrap();
info
self.send(Command::AuthenticatorGetInfo, ()).unwrap()
}
fn client_pin(request: client_pin::Request) -> Result<client_pin::Response, client_pin::Error> {
fn client_pin(
_request: client_pin::Request,
) -> Result<client_pin::Response, client_pin::Error> {
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<Mutex<Option<HidAuthenticator>>> =
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<std::time::Duration>,
) -> 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<HidAuthenticator> {
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<u8> = 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:#?}");
}
}

@ -32,18 +32,3 @@ pub struct Data<const TYPE: DataType> {
/// > absence indicates that the client doesnt support token binding.
pub token_binding: Option<token::Binding>,
}
#[cfg(feature = "serde")]
impl<const TYPE: DataType> Serialize for Data<TYPE> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
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!()
}
}

@ -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,
// }

Loading…
Cancel
Save