diff --git a/Cargo.lock b/Cargo.lock index c8b0964..affb1cf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -114,6 +114,67 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +[[package]] +name = "asn1-rs" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5493c3bedbacf7fd7382c6346bbd66687d12bbaad3a89a2d2c303ee6cf20b048" +dependencies = [ + "asn1-rs-derive", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror 1.0.69", + "time", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "async-trait" version = "0.1.89" @@ -201,6 +262,12 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "022dfe9eb35f19ebbcb51e0b40a5ab759f46ad60cadf7297e0bd085afb50e076" +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + [[package]] name = "base64" version = "0.22.1" @@ -213,6 +280,37 @@ version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e050f626429857a27ddccb31e0aca21356bfa709c04041aefddac081a8f068a" +[[package]] +name = "base64urlsafedata" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f7f6be94fa637132933fd0a68b9140bcb60e3d46164cb68e82a2bb8d102b3a" +dependencies = [ + "base64 0.21.7", + "pastey", + "serde", +] + +[[package]] +name = "bindgen" +version = "0.72.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +dependencies = [ + "bitflags 2.10.0", + "cexpr", + "clang-sys", + "itertools", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -289,6 +387,15 @@ dependencies = [ "shlex", ] +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + [[package]] name = "cfg-if" version = "1.0.4" @@ -311,6 +418,17 @@ dependencies = [ "inout", ] +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "clap" version = "4.5.53" @@ -433,6 +551,12 @@ dependencies = [ "libc", ] +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + [[package]] name = "crypto-bigint" version = "0.5.5" @@ -507,6 +631,29 @@ dependencies = [ "zeroize", ] +[[package]] +name = "der-parser" +version = "9.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cd0a5c643689626bec213c4d8bd4d96acc8ffdb4ad4bb6bc16abf27d5f4b553" +dependencies = [ + "asn1-rs", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", +] + [[package]] name = "digest" version = "0.10.7" @@ -602,6 +749,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + [[package]] name = "elliptic-curve" version = "0.13.8" @@ -688,6 +841,31 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" +[[package]] +name = "fido-hid-rs" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ba2a273ad2118e8491a7803b6c88d3d7dce276edf1e3aca3b8c72c627c29b4d" +dependencies = [ + "async-trait", + "bindgen", + "bitflags 2.10.0", + "core-foundation", + "futures", + "lazy_static", + "libc", + "mach2 0.6.0", + "nix", + "num-derive", + "num-traits", + "thiserror 1.0.69", + "tokio", + "tokio-stream", + "tracing", + "udev", + "windows", +] + [[package]] name = "find-msvc-tools" version = "0.1.5" @@ -700,6 +878,21 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -846,6 +1039,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + [[package]] name = "group" version = "0.13.0" @@ -857,6 +1056,17 @@ dependencies = [ "subtle", ] +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + [[package]] name = "hashbrown" version = "0.16.1" @@ -869,12 +1079,24 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + [[package]] name = "hermit-abi" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "hkdf" version = "0.12.4" @@ -989,7 +1211,7 @@ version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "futures-channel", "futures-core", @@ -1129,6 +1351,17 @@ dependencies = [ "generic-array", ] +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi 0.3.9", + "libc", + "windows-sys 0.48.0", +] + [[package]] name = "ipnet" version = "2.11.0" @@ -1160,7 +1393,7 @@ version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ - "hermit-abi", + "hermit-abi 0.5.2", "libc", "windows-sys 0.61.2", ] @@ -1181,6 +1414,15 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.16" @@ -1236,6 +1478,16 @@ version = "0.2.178" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + [[package]] name = "libm" version = "0.2.15" @@ -1252,6 +1504,16 @@ dependencies = [ "libc", ] +[[package]] +name = "libudev-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324" +dependencies = [ + "libc", + "pkg-config", +] + [[package]] name = "linux-raw-sys" version = "0.11.0" @@ -1294,6 +1556,12 @@ dependencies = [ "libc", ] +[[package]] +name = "mach2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dae608c151f68243f2b000364e1f7b186d9c29845f7d2d85bd31b9ad77ad552b" + [[package]] name = "matchit" version = "0.8.4" @@ -1329,6 +1597,18 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags 2.10.0", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "nom" version = "7.1.3" @@ -1339,6 +1619,16 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + [[package]] name = "num-bigint-dig" version = "0.8.6" @@ -1355,6 +1645,23 @@ dependencies = [ "zeroize", ] +[[package]] +name = "num-conv" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "num-integer" version = "0.1.46" @@ -1458,6 +1765,15 @@ dependencies = [ "objc2-core-foundation", ] +[[package]] +name = "oid-registry" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d8034d9489cdaf79228eb9f6a3b8d7bb32ba00d6645ebd48eef4077ceb5bd9" +dependencies = [ + "asn1-rs", +] + [[package]] name = "once_cell" version = "1.21.3" @@ -1481,12 +1797,50 @@ dependencies = [ "pathdiff", ] +[[package]] +name = "openssl" +version = "0.10.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38c4372413cdaaf3cc79dd92d29d7d9f5ab09b51b10dded508fb90bb70b9222" +dependencies = [ + "bitflags 2.10.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "openssl-probe" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" +[[package]] +name = "openssl-sys" +version = "0.9.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13ce1245cd07fcc4cfdb438f7507b0c7e4f3849a69fd84d52374c66d83741bb6" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "option-ext" version = "0.2.0" @@ -1575,6 +1929,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "pastey" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" + [[package]] name = "pathdiff" version = "0.2.3" @@ -1679,6 +2039,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -1688,6 +2054,16 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "primeorder" version = "0.13.6" @@ -1864,7 +2240,8 @@ dependencies = [ "arrayvec", "axum", "base32", - "base64", + "base64 0.22.1", + "base64urlsafedata", "block-padding", "cbc", "clap", @@ -1914,6 +2291,9 @@ dependencies = [ "url", "urlencoding", "uuid", + "webauthn-authenticator-rs", + "webauthn-rs", + "webauthn-rs-proto", "zeroize", ] @@ -1974,7 +2354,7 @@ checksum = "e6b6ebd13bc009aef9cd476c1310d49ac354d36e240cf1bd753290f3dc7199a7" dependencies = [ "bitflags 1.3.2", "libc", - "mach2", + "mach2 0.4.3", "windows-sys 0.52.0", ] @@ -1984,7 +2364,7 @@ version = "0.12.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "futures-channel", "futures-core", @@ -2096,6 +2476,15 @@ dependencies = [ "semver", ] +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom", +] + [[package]] name = "rustix" version = "1.1.3" @@ -2245,6 +2634,26 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde_bytes" +version = "0.11.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde_cbor_2" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aec2709de9078e077090abd848e967abab63c9fb3fdb5d4799ad359d8d482c" +dependencies = [ + "half", + "serde", +] + [[package]] name = "serde_core" version = "1.0.228" @@ -2600,6 +3009,37 @@ dependencies = [ "syn", ] +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tinystr" version = "0.8.2" @@ -2672,6 +3112,7 @@ dependencies = [ "futures-core", "pin-project-lite", "tokio", + "tokio-util", ] [[package]] @@ -2770,9 +3211,21 @@ checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "log", "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tracing-core" version = "0.1.36" @@ -2826,6 +3279,18 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +[[package]] +name = "udev" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af4e37e9ea4401fc841ff54b9ddfc9be1079b1e89434c1a6a865dd68980f7e9f" +dependencies = [ + "io-lifetimes", + "libc", + "libudev-sys", + "pkg-config", +] + [[package]] name = "unicode-ident" version = "1.0.22" @@ -2838,6 +3303,15 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + [[package]] name = "unicode-width" version = "0.2.2" @@ -2894,9 +3368,16 @@ checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" dependencies = [ "getrandom 0.3.4", "js-sys", + "serde_core", "wasm-bindgen", ] +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.5" @@ -3075,12 +3556,138 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webauthn-attestation-ca" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fafcf13f7dc1fb292ed4aea22cdd3757c285d7559e9748950ee390249da4da6b" +dependencies = [ + "base64urlsafedata", + "openssl", + "openssl-sys", + "serde", + "tracing", + "uuid", +] + +[[package]] +name = "webauthn-authenticator-rs" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b41ed08aba475a969094226ae0691a286686210ae497bb2c5d0ed722d8d526" +dependencies = [ + "async-stream", + "async-trait", + "base64 0.21.7", + "base64urlsafedata", + "bitflags 1.3.2", + "fido-hid-rs", + "futures", + "hex", + "nom", + "num-derive", + "num-traits", + "openssl", + "openssl-sys", + "serde", + "serde_bytes", + "serde_cbor_2", + "serde_json", + "thiserror 1.0.69", + "tokio", + "tokio-stream", + "tracing", + "unicode-normalization", + "url", + "uuid", + "webauthn-rs-core", + "webauthn-rs-proto", +] + +[[package]] +name = "webauthn-rs" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b24d082d3360258fefb6ffe56123beef7d6868c765c779f97b7a2fcf06727f8" +dependencies = [ + "base64urlsafedata", + "serde", + "tracing", + "url", + "uuid", + "webauthn-rs-core", +] + +[[package]] +name = "webauthn-rs-core" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15784340a24c170ce60567282fb956a0938742dbfbf9eff5df793a686a009b8b" +dependencies = [ + "base64 0.21.7", + "base64urlsafedata", + "der-parser", + "hex", + "nom", + "openssl", + "openssl-sys", + "rand 0.9.2", + "rand_chacha 0.9.0", + "serde", + "serde_cbor_2", + "serde_json", + "thiserror 1.0.69", + "tracing", + "url", + "uuid", + "webauthn-attestation-ca", + "webauthn-rs-proto", + "x509-parser", +] + +[[package]] +name = "webauthn-rs-proto" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16a1fb2580ce73baa42d3011a24de2ceab0d428de1879ece06e02e8c416e497c" +dependencies = [ + "base64 0.21.7", + "base64urlsafedata", + "serde", + "serde_json", + "url", +] + +[[package]] +name = "windows" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3ed69de2c1f8d0524a8a3417a80a85dd316a071745fbfdf5eb028b310058ab" +dependencies = [ + "windows_aarch64_gnullvm 0.41.0", + "windows_aarch64_msvc 0.41.0", + "windows_i686_gnu 0.41.0", + "windows_i686_msvc 0.41.0", + "windows_x86_64_gnu 0.41.0", + "windows_x86_64_gnullvm 0.41.0", + "windows_x86_64_msvc 0.41.0", +] + [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + [[package]] name = "windows-sys" version = "0.52.0" @@ -3108,6 +3715,21 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -3141,6 +3763,18 @@ dependencies = [ "windows_x86_64_msvc 0.53.1", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "163d2761774f2278ecb4e6719e80b2b5e92e5a2be73a7bcd3ef624dd5e3091fd" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -3153,6 +3787,18 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" +[[package]] +name = "windows_aarch64_msvc" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef005ff2bceb00d3b84166a359cc19084f9459754fd3fe5a504dee3dddcd0a0c" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -3165,6 +3811,18 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" +[[package]] +name = "windows_i686_gnu" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b4df2d51e32f03f8b4b228e487828c03bcb36d97b216fc5463bcea5bb1440b" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -3189,6 +3847,18 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" +[[package]] +name = "windows_i686_msvc" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "568a966834571f2f3267f07dd72b4d8507381f25e53d056808483b2637385ef7" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -3201,6 +3871,18 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" +[[package]] +name = "windows_x86_64_gnu" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc395dac1adf444e276d096d933ae7961361c8cda3245cffef7a9b3a70a8f994" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -3213,6 +3895,18 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90e8ec22b715d5b436e1d59c8adad6c744dc20cd984710121d5836b4e8dbb5e0" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -3225,6 +3919,18 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" +[[package]] +name = "windows_x86_64_msvc" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9761f0216b669019df1512f6e25e5ee779bf61c5cdc43c7293858e7efd7926" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -3284,6 +3990,23 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" +[[package]] +name = "x509-parser" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcbc162f30700d6f3f82a24bf7cc62ffe7caea42c0b2cba8bf7f3ae50cf51f69" +dependencies = [ + "asn1-rs", + "data-encoding", + "der-parser", + "lazy_static", + "nom", + "oid-registry", + "rusticata-macros", + "thiserror 1.0.69", + "time", +] + [[package]] name = "yoke" version = "0.8.1" diff --git a/Cargo.toml b/Cargo.toml index d348fe0..b00beea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -88,9 +88,20 @@ arboard = { version = "3.6.1", default-features = false, features = [ "wayland-data-control", ], optional = true } +base64urlsafedata = { version = "0.5.4", optional = true } +webauthn-rs = { version = "0.5.4", optional = true } +webauthn-rs-proto = { version = "0.5.4", optional = true } +webauthn-authenticator-rs = { version = "0.5.4", default-features = false, features = ["usb"], optional = true } + [features] default = ["clipboard"] clipboard = ["arboard"] +webauthn = [ + "dep:base64urlsafedata", + "dep:webauthn-rs", + "dep:webauthn-rs-proto", + "dep:webauthn-authenticator-rs", +] [lints.clippy] cargo = { level = "warn", priority = -1 } diff --git a/src/api.rs b/src/api.rs index a817fb2..26a65a9 100644 --- a/src/api.rs +++ b/src/api.rs @@ -6,7 +6,14 @@ use crate::prelude::*; use rand::distr::SampleString as _; use sha2::Digest as _; +use std::collections::HashMap; use tokio::io::AsyncReadExt as _; +#[cfg(feature = "webauthn")] +pub use webauthn_rs_proto::PublicKeyCredentialRequestOptions; + +#[cfg(not(feature = "webauthn"))] +#[derive(serde::Deserialize, Debug, Clone)] +pub struct PublicKeyCredentialRequestOptions {} use crate::json::{ DeserializeJsonWithPath as _, DeserializeJsonWithPathAsync as _, @@ -47,7 +54,7 @@ impl std::fmt::Display for UriMatchType { } } -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum TwoFactorProviderType { Authenticator = 0, Email = 1, @@ -65,6 +72,7 @@ impl TwoFactorProviderType { Self::Authenticator => "Enter the 6 digit verification code from your authenticator app.", Self::Yubikey => "Insert your Yubikey and push the button.", Self::Email => "Enter the PIN you received via email.", + Self::WebAuthn => "Enter your security key PIN.", _ => "Enter the code." } } @@ -74,6 +82,7 @@ impl TwoFactorProviderType { Self::Authenticator => "Authenticator App", Self::Yubikey => "Yubikey", Self::Email => "Email Code", + Self::WebAuthn => "Security Key PIN", _ => "Two Factor Authentication", } } @@ -349,6 +358,15 @@ struct ConnectErrorRes { error_model: Option, #[serde(rename = "TwoFactorProviders", alias = "twoFactorProviders")] two_factor_providers: Option>, + // TwoFactorProviders2 is the only place the WebAuthn challenge is + // delivered; for non-WebAuthn methods the value is `null` and we just + // ignore it. Deserialized independently of `two_factor_providers` so + // a server that only emits the legacy array (or a malformed challenge + // shape) still drives the TOTP/Yubikey/Email paths correctly. + #[serde(default, rename = "TwoFactorProviders2", alias = "twoFactorProviders2")] + two_factor_providers_data: Option< + HashMap>, + >, #[serde( rename = "SsoEmail2faSessionToken", alias = "ssoEmail2faSessionToken" @@ -1724,6 +1742,10 @@ fn classify_login_error(error_res: &ConnectErrorRes, code: u16) -> Error { { return Error::TwoFactorRequired { providers: providers.clone(), + providers_data: error_res + .two_factor_providers_data + .clone() + .unwrap_or_default(), sso_email_2fa_session_token: error_res .sso_email_2fa_session_token .clone(), diff --git a/src/bin/rbw-agent/actions.rs b/src/bin/rbw-agent/actions.rs index 9ddd2ad..422cdaf 100644 --- a/src/bin/rbw-agent/actions.rs +++ b/src/bin/rbw-agent/actions.rs @@ -1,5 +1,9 @@ use anyhow::Context as _; +#[cfg(not(feature = "webauthn"))] +use rbw::api::PublicKeyCredentialRequestOptions; use sha2::Digest as _; +#[cfg(feature = "webauthn")] +use webauthn_rs_proto::PublicKeyCredentialRequestOptions; pub async fn register( sock: &mut crate::sock::Sock, @@ -143,9 +147,16 @@ pub async fn login( } Err(rbw::error::Error::TwoFactorRequired { providers, + #[cfg_attr( + not(feature = "webauthn"), + allow(unused_variables) + )] + providers_data, sso_email_2fa_session_token, }) => { let supported_types = vec![ + #[cfg(feature = "webauthn")] + rbw::api::TwoFactorProviderType::WebAuthn, rbw::api::TwoFactorProviderType::Authenticator, rbw::api::TwoFactorProviderType::Yubikey, rbw::api::TwoFactorProviderType::Email, @@ -153,6 +164,15 @@ pub async fn login( for provider in supported_types { if providers.contains(&provider) { + #[cfg(feature = "webauthn")] + let provider_data = providers_data + .get(&provider) + .cloned() + .flatten(); + #[cfg(not(feature = "webauthn"))] + let provider_data: Option< + rbw::api::PublicKeyCredentialRequestOptions, + > = None; if provider == rbw::api::TwoFactorProviderType::Email { @@ -179,6 +199,7 @@ pub async fn login( &email, password.clone(), provider, + provider_data, ) .await?; login_success( @@ -229,6 +250,8 @@ async fn two_factor( email: &str, password: rbw::locked::Password, provider: rbw::api::TwoFactorProviderType, + #[cfg_attr(not(feature = "webauthn"), allow(unused_variables))] + provider_data: Option, ) -> anyhow::Result<( String, String, @@ -247,17 +270,32 @@ async fn two_factor( } else { None }; - let code = rbw::pinentry::getpin( - &config_pinentry().await?, - provider.header(), - provider.message(), - err.as_deref(), - environment, - provider.grab(), - ) - .await - .context("failed to read code from pinentry")?; - let code = std::str::from_utf8(code.password()) + let token = match provider { + #[cfg(feature = "webauthn")] + rbw::api::TwoFactorProviderType::WebAuthn => { + let challenge = provider_data.clone().context( + "webauthn challenge missing from server response", + )?; + rbw::webauthn::webauthn( + challenge, + config_pinentry().await?, + environment.clone(), + ) + .await + .context("webauthn authentication failed")? + } + _ => rbw::pinentry::getpin( + &config_pinentry().await?, + provider.header(), + provider.message(), + err.as_deref(), + environment, + provider.grab(), + ) + .await + .context("failed to read code from pinentry")?, + }; + let code = std::str::from_utf8(token.password()) .context("code was not valid utf8")?; match rbw::actions::login( email, diff --git a/src/error.rs b/src/error.rs index b7789a9..5f2313d 100644 --- a/src/error.rs +++ b/src/error.rs @@ -232,6 +232,10 @@ pub enum Error { #[error("two factor required")] TwoFactorRequired { providers: Vec, + providers_data: std::collections::HashMap< + crate::api::TwoFactorProviderType, + Option, + >, sso_email_2fa_session_token: Option, }, diff --git a/src/lib.rs b/src/lib.rs index b0f7c78..7521027 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,4 +14,6 @@ pub mod pinentry; mod prelude; pub mod protocol; pub mod pwgen; +#[cfg(feature = "webauthn")] +pub mod webauthn; pub mod wordlist; diff --git a/src/webauthn.rs b/src/webauthn.rs new file mode 100644 index 0000000..a5e9437 --- /dev/null +++ b/src/webauthn.rs @@ -0,0 +1,196 @@ +use anyhow::Context as _; +use futures::StreamExt as _; +use webauthn_authenticator_rs::{ + ctap2::CtapAuthenticator, + transport::{AnyTransport, TokenEvent, Transport as _}, + types::{CableRequestType, CableState, EnrollSampleStatus}, + ui::UiCallback, + AuthenticatorBackend as _, +}; +use webauthn_rs_proto::PublicKeyCredentialRequestOptions; + +use crate::locked::Password; + +pub async fn webauthn( + challenge: PublicKeyCredentialRequestOptions, + pinentry: String, + environment: crate::protocol::Environment, +) -> anyhow::Result { + let transport = AnyTransport::new() + .await + .context("failed to set up webauthn transport")?; + + let ui = Ui { + pinentry, + environment, + }; + + let mut events = transport + .watch() + .await + .context("failed to watch webauthn transport")?; + + let mut authenticator = loop { + match events.next().await { + Some(TokenEvent::Added(token)) => { + if let Some(auth) = CtapAuthenticator::new(token, &ui).await { + break auth; + } + } + Some(TokenEvent::EnumerationComplete) => { + log::info!( + "rbw: connect a FIDO2 security key to continue" + ); + } + Some(TokenEvent::Removed(_)) => {} + None => { + anyhow::bail!( + "webauthn transport closed before a token connected" + ); + } + } + }; + + // Derive the origin from the challenge's rp_id rather than rbw's + // configured vault URL: the authenticator enforces that origin's host + // matches (or is a subdomain of) rp_id, and Bitwarden registers + // credentials against the web vault host. Using rp_id directly avoids + // mismatches on self-hosted setups where the user's configured ui_url + // doesn't line up with the host the credential was registered against. + let origin = reqwest::Url::parse(&format!("https://{}", challenge.rp_id)) + .context("failed to construct webauthn origin from rp_id")?; + + // perform_auth is synchronous and blocks for up to the timeout waiting + // on USB HID. Use block_in_place so it doesn't stall other tasks on + // the tokio worker. The authenticator borrows from `ui`, so we can't + // move it across a spawn_blocking boundary. + let result = tokio::task::block_in_place(|| { + authenticator.perform_auth(origin, challenge, u32::MAX) + }) + .map_err(|e| anyhow::anyhow!("webauthn authentication failed: {e:?}"))?; + + let out = serde_json::to_string(&BitwardenAssertion::from(result)) + .context("failed to serialize webauthn assertion")?; + + let mut buf = crate::locked::Vec::new(); + buf.extend(out.as_bytes().iter().copied()); + Ok(Password::new(buf)) +} + +// Bitwarden's server expects a slightly different shape than what +// webauthn-rs-proto serializes by default: the response field is camelCase +// `clientDataJson` rather than the W3C-spec `clientDataJSON`, and the +// extensions object must use a non-nullable `appid: bool`. We build a +// dedicated wire type instead of munging the JSON. +#[derive(serde::Serialize)] +struct BitwardenAssertion { + id: String, + #[serde(rename = "rawId")] + raw_id: base64urlsafedata::Base64UrlSafeData, + response: BitwardenAssertionResponse, + extensions: BitwardenAssertionExtensions, + #[serde(rename = "type")] + type_: String, +} + +#[derive(serde::Serialize)] +#[serde(rename_all = "camelCase")] +struct BitwardenAssertionResponse { + authenticator_data: base64urlsafedata::Base64UrlSafeData, + client_data_json: base64urlsafedata::Base64UrlSafeData, + signature: base64urlsafedata::Base64UrlSafeData, + user_handle: Option, +} + +#[derive(serde::Serialize)] +struct BitwardenAssertionExtensions { + appid: bool, +} + +impl From for BitwardenAssertion { + fn from(c: webauthn_rs_proto::PublicKeyCredential) -> Self { + Self { + id: c.id, + raw_id: c.raw_id, + response: BitwardenAssertionResponse { + authenticator_data: c.response.authenticator_data, + client_data_json: c.response.client_data_json, + signature: c.response.signature, + user_handle: c.response.user_handle, + }, + extensions: BitwardenAssertionExtensions { + appid: c.extensions.appid.unwrap_or(false), + }, + type_: c.type_, + } + } +} + +#[derive(Debug)] +struct Ui { + pinentry: String, + environment: crate::protocol::Environment, +} + +impl UiCallback for Ui { + // The library calls this synchronously from inside `perform_auth` only + // when the authenticator actually demands a PIN (Required/Preferred + // policy, or the key has client_pin set). Bridge into async pinentry + // by spawning on the current runtime and waiting on a sync channel — + // safe under `block_in_place`, which is how `perform_auth` is invoked. + fn request_pin(&self) -> Option { + let pinentry = self.pinentry.clone(); + let environment = self.environment.clone(); + let (tx, rx) = std::sync::mpsc::channel(); + tokio::runtime::Handle::current().spawn(async move { + let provider = crate::api::TwoFactorProviderType::WebAuthn; + let res = crate::pinentry::getpin( + &pinentry, + provider.header(), + provider.message(), + None, + &environment, + provider.grab(), + ) + .await; + let _ = tx.send(res); + }); + match rx.recv().ok()? { + Ok(pw) => std::str::from_utf8(pw.password()) + .ok() + .map(str::to_string), + Err(e) => { + log::warn!("webauthn: pinentry failed: {e}"); + None + } + } + } + + fn request_touch(&self) { + log::debug!("webauthn: waiting for user presence (touch the key)"); + } + + fn fingerprint_enrollment_feedback( + &self, + _remaining_samples: u32, + _feedback: Option, + ) { + log::warn!("webauthn: fingerprint_enrollment_feedback unimplemented"); + } + + fn cable_qr_code(&self, _request_type: CableRequestType, _url: String) { + log::warn!("webauthn: cable_qr_code unimplemented"); + } + + fn dismiss_qr_code(&self) { + log::warn!("webauthn: dismiss_qr_code unimplemented"); + } + + fn cable_status_update(&self, _state: CableState) { + log::warn!("webauthn: cable_status_update unimplemented"); + } + + fn processing(&self) { + log::debug!("webauthn: processing..."); + } +}