diff --git a/libwebauthn/Cargo.lock b/libwebauthn/Cargo.lock index 2386b8b..f4e6b95 100644 --- a/libwebauthn/Cargo.lock +++ b/libwebauthn/Cargo.lock @@ -2,21 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "addr2line" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" - [[package]] name = "aead" version = "0.5.2" @@ -55,18 +40,18 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] [[package]] name = "anstream" -version = "0.6.20" +version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" dependencies = [ "anstyle", "anstyle-parse", @@ -79,9 +64,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.11" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] name = "anstyle-parse" @@ -94,22 +79,22 @@ dependencies = [ [[package]] name = "anstyle-query" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.10" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -162,7 +147,7 @@ dependencies = [ "nom", "num-traits", "rusticata-macros", - "thiserror 2.0.16", + "thiserror 2.0.18", "time", ] @@ -174,7 +159,7 @@ checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", "synstructure 0.13.2", ] @@ -186,7 +171,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -197,7 +182,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -215,21 +200,6 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" -[[package]] -name = "backtrace" -version = "0.3.75" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-targets 0.52.6", -] - [[package]] name = "base16ct" version = "0.2.0" @@ -244,58 +214,55 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64-url" -version = "3.0.0" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38e2b6c78c06f7288d5e3c3d683bde35a79531127c83b087e5d0d77c974b4b28" +checksum = "f5b428e9fb429c6fda7316e9b006f993e6b4c33005e4659339fb5214479dddec" dependencies = [ "base64", ] [[package]] name = "base64ct" -version = "1.8.0" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" [[package]] name = "bindgen" -version = "0.65.1" +version = "0.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfdf7b466f9a4903edc73f95d6d2bcd5baf8ae620638762244d3f60143643cc5" +checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.10.0", "cexpr", "clang-sys", - "lazy_static", - "lazycell", - "log", - "peeking_take_while", - "prettyplease", + "itertools 0.13.0", "proc-macro2", "quote", "regex", - "rustc-hash", + "rustc-hash 1.1.0", "shlex", - "syn 2.0.106", - "which", + "syn 2.0.114", ] [[package]] name = "bindgen" -version = "0.70.1" +version = "0.72.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "cexpr", "clang-sys", "itertools 0.13.0", + "log", + "prettyplease", "proc-macro2", "quote", "regex", - "rustc-hash", + "rustc-hash 2.1.1", "shlex", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -306,9 +273,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.4" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "blake2" @@ -361,7 +328,7 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84ae4213cc2a8dc663acecac67bbdad05142be4d8ef372b6903abf878b0c690a" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "bluez-generated", "dbus", "dbus-tokio", @@ -370,7 +337,7 @@ dependencies = [ "log", "serde", "serde-xml-rs", - "thiserror 2.0.16", + "thiserror 2.0.18", "tokio", "uuid", ] @@ -391,7 +358,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9a11621cb2c8c024e444734292482b1ad86fb50ded066cf46252e46643c8748" dependencies = [ "async-trait", - "bitflags 2.9.4", + "bitflags 2.10.0", "bluez-async", "dashmap 6.1.0", "dbus", @@ -404,7 +371,7 @@ dependencies = [ "objc2-foundation", "once_cell", "static_assertions", - "thiserror 2.0.16", + "thiserror 2.0.18", "tokio", "tokio-stream", "uuid", @@ -414,15 +381,15 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.0" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" [[package]] name = "bytemuck" -version = "1.23.2" +version = "1.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3995eaeebcdf32f91f980d360f78732ddc061097ab4e39991ae7a6ace9194677" +checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" [[package]] name = "byteorder" @@ -438,9 +405,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] name = "bytes" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" [[package]] name = "cbc" @@ -453,21 +420,21 @@ dependencies = [ [[package]] name = "cbor-smol" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e087b31faa4ad4ba21c9bd0209204eef424dae6424195aafc7242006b69fc8d" +checksum = "0b6dd31f7069836e87169bc5910212571b873cebe389c7c7f2d8b1fb3e55c80d" dependencies = [ "delog", "heapless", "heapless-bytes", - "serde", + "serde_core", ] [[package]] name = "cc" -version = "1.2.38" +version = "1.2.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80f41ae168f955c12fb8960b057d70d0ca153fb83182b57d86380443527be7e9" +checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583" dependencies = [ "find-msvc-tools", "shlex", @@ -490,9 +457,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "chacha20" @@ -642,9 +609,9 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", "typenum", @@ -763,7 +730,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -795,15 +762,15 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" [[package]] name = "dbus" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190b6255e8ab55a7b568df5a883e9497edc3e4821c06396612048b430e5ad1e9" +checksum = "21b3aa68d7e7abee336255bd7248ea965cc393f3e70411135a6f6a4b651345d4" dependencies = [ "futures-channel", "futures-util", @@ -881,9 +848,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.5.3" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d630bccd429a5bb5a64b5e94f693bfc48c9f8566418fda4c494cc94f911f87cc" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" dependencies = [ "powerfmt", ] @@ -926,7 +893,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -1016,9 +983,9 @@ dependencies = [ [[package]] name = "env_filter" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" dependencies = [ "log", ] @@ -1048,7 +1015,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.0", + "windows-sys 0.61.2", ] [[package]] @@ -1109,9 +1076,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.2" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ced73b1dacfc750a6db6c0a0c3a3853c8b41997e2e2c563dc90804ae6867959" +checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" [[package]] name = "find-winsdk" @@ -1143,16 +1110,10 @@ checksum = "e9985657bc39bae5cb7f4e686b46c67b1ea37390d82fe4bab56d7cd1933429d7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", "synstructure 0.13.2", ] -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - [[package]] name = "fragile" version = "2.0.1" @@ -1215,7 +1176,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -1274,25 +1235,25 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "libc", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", ] [[package]] name = "getrandom" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", "r-efi", - "wasi 0.14.7+wasi-0.2.4", + "wasip2", ] [[package]] @@ -1305,12 +1266,6 @@ dependencies = [ "polyval", ] -[[package]] -name = "gimli" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" - [[package]] name = "glob" version = "0.3.3" @@ -1341,12 +1296,13 @@ dependencies = [ [[package]] name = "half" -version = "2.6.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ "cfg-if", "crunchy", + "zerocopy", ] [[package]] @@ -1366,9 +1322,9 @@ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "hashbrown" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" [[package]] name = "heapless" @@ -1409,9 +1365,9 @@ checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" [[package]] name = "hidapi" -version = "2.6.3" +version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03b876ecf37e86b359573c16c8366bc3eba52b689884a0fc42ba3f67203d2a8b" +checksum = "565dd4c730b8f8b2c0fb36df6be12e5470ae10895ddcc4e9dcfbfb495de202b0" dependencies = [ "cc", "cfg-if", @@ -1448,23 +1404,13 @@ dependencies = [ "digest 0.10.7", ] -[[package]] -name = "home" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" -dependencies = [ - "windows-sys 0.59.0", -] - [[package]] name = "http" -version = "1.3.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" dependencies = [ "bytes", - "fnv", "itoa", ] @@ -1476,9 +1422,9 @@ checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "image" -version = "0.25.8" +version = "0.25.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "529feb3e6769d234375c4cf1ee2ce713682b8e76538cb13f9fc23e1400a591e7" +checksum = "e6506c6c10786659413faa717ceebcb8f70731c0a60cbae39795fdf114519c1a" dependencies = [ "bytemuck", "byteorder-lite", @@ -1488,12 +1434,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.11.4" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", - "hashbrown 0.16.0", + "hashbrown 0.16.1", ] [[package]] @@ -1515,22 +1461,11 @@ dependencies = [ "loom", ] -[[package]] -name = "io-uring" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" -dependencies = [ - "bitflags 2.9.4", - "cfg-if", - "libc", -] - [[package]] name = "is_terminal_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "iso7816" @@ -1561,9 +1496,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "jni" @@ -1602,9 +1537,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.80" +version = "0.3.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852f13bec5eba4ba9afbeb93fd7c13fe56147f055939ae21c43a29a0ecb2702e" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" dependencies = [ "once_cell", "wasm-bindgen", @@ -1616,35 +1551,29 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -[[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" - [[package]] name = "libc" -version = "0.2.175" +version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" [[package]] name = "libdbus-sys" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cbe856efeb50e4681f010e9aaa2bf0a644e10139e54cde10fc83a307c23bd9f" +checksum = "328c4789d42200f1eeec05bd86c9c13c7f091d2ba9a6ea35acdf51f31bc0f043" dependencies = [ "pkg-config", ] [[package]] name = "libloading" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" dependencies = [ "cfg-if", - "windows-targets 0.53.3", + "windows-link 0.2.1", ] [[package]] @@ -1656,7 +1585,7 @@ dependencies = [ "apdu-core", "async-trait", "base64-url", - "bitflags 2.9.4", + "bitflags 2.10.0", "btleplug", "byteorder", "cbc", @@ -1693,13 +1622,14 @@ dependencies = [ "serde_bytes", "serde_cbor_2", "serde_derive", + "serde_json", "serde_repr", "sha2 0.10.9", "snow", "tempfile", "test-log", "text_io", - "thiserror 2.0.16", + "thiserror 2.0.18", "time", "tokio", "tokio-stream", @@ -1713,12 +1643,6 @@ dependencies = [ "x509-parser", ] -[[package]] -name = "linux-raw-sys" -version = "0.4.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" - [[package]] name = "linux-raw-sys" version = "0.11.0" @@ -1740,11 +1664,11 @@ dependencies = [ [[package]] name = "littlefs2-core" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a81a4745d38b288b7583fe8ea3736897628df81f4d0f1d0314fa5a3af570de4" +checksum = "0dd2d1cad5532f5e11b0fd871b8a981885efd3fdaec0427c0d637322ae09efbf" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "heapless-bytes", "serde", ] @@ -1761,19 +1685,18 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "autocfg", "scopeguard", ] [[package]] name = "log" -version = "0.4.28" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "loom" @@ -1805,9 +1728,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.5" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "minimal-lexical" @@ -1815,24 +1738,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" -[[package]] -name = "miniz_oxide" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" -dependencies = [ - "adler2", -] - [[package]] name = "mio" -version = "1.0.4" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" dependencies = [ "libc", - "wasi 0.11.1+wasi-snapshot-preview1", - "windows-sys 0.59.0", + "wasi", + "windows-sys 0.61.2", ] [[package]] @@ -1858,14 +1772,14 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] name = "moxcms" -version = "0.7.5" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd32fa8935aeadb8a8a6b6b351e40225570a37c43de67690383d87ef170cd08" +checksum = "ac9557c559cd6fc9867e122e20d2cbefc9ca29d80d027a8e39310920ed2f0a97" dependencies = [ "num-traits", "pxfm", @@ -1888,11 +1802,11 @@ dependencies = [ [[package]] name = "nfc1-sys" -version = "0.3.9" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6652c6cdf52433ff143439595ffb4b945afafbe5f27cec8d2fc5dfb5832796e8" +checksum = "5e41b61907c3ee9730969de23b433428ea9e934874320d1605e1214760f06cfd" dependencies = [ - "bindgen 0.65.1", + "bindgen 0.72.1", "cc", "find-winsdk", "pkg-config", @@ -1911,11 +1825,11 @@ dependencies = [ [[package]] name = "nu-ansi-term" -version = "0.50.1" +version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -1930,9 +1844,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" [[package]] name = "num-derive" @@ -1942,7 +1856,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -1965,9 +1879,9 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" +checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" dependencies = [ "num_enum_derive", "rustversion", @@ -1975,14 +1889,14 @@ dependencies = [ [[package]] name = "num_enum_derive" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" +checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -2007,7 +1921,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a644b62ffb826a5277f536cf0f701493de420b13d40e700c452c36567771111" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "objc2", "objc2-foundation", ] @@ -2024,21 +1938,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "block2", "libc", "objc2", ] -[[package]] -name = "object" -version = "0.36.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" -dependencies = [ - "memchr", -] - [[package]] name = "oid-registry" version = "0.8.1" @@ -2056,9 +1961,9 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "once_cell_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "opaque-debug" @@ -2068,9 +1973,9 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "openssl-probe" -version = "0.1.6" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "p256" @@ -2124,9 +2029,9 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", @@ -2134,15 +2039,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.11" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets 0.52.6", + "windows-link 0.2.1", ] [[package]] @@ -2151,7 +2056,7 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd833ecf8967e65934c49d3521a175929839bf6d0e497f3bd0d3a2ca08943da" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "pcsc-sys", ] @@ -2164,12 +2069,6 @@ dependencies = [ "pkg-config", ] -[[package]] -name = "peeking_take_while" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" - [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -2295,7 +2194,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -2319,18 +2218,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.101" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "pxfm" -version = "0.1.23" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55f4fedc84ed39cb7a489322318976425e42a147e2be79d8f878e2884f94e84" +checksum = "7186d3822593aa4393561d186d1393b3923e9d6163d3fbfd6e825e3e6cf3e6a8" dependencies = [ "num-traits", ] @@ -2346,9 +2245,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.40" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" dependencies = [ "proc-macro2", ] @@ -2377,7 +2276,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -2397,7 +2296,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -2406,25 +2305,25 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", ] [[package]] name = "rand_core" -version = "0.9.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", ] [[package]] name = "redox_syscall" -version = "0.5.17" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", ] [[package]] @@ -2435,9 +2334,9 @@ checksum = "09c30c54dffee5b40af088d5d50aa3455c91a0127164b51f0215efc4cb28fb3c" [[package]] name = "regex" -version = "1.11.2" +version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" dependencies = [ "aho-corasick", "memchr", @@ -2447,9 +2346,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.10" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", @@ -2458,9 +2357,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "rfc6979" @@ -2480,23 +2379,23 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.16", + "getrandom 0.2.17", "libc", "untrusted", "windows-sys 0.52.0", ] [[package]] -name = "rustc-demangle" -version = "0.1.26" +name = "rustc-hash" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustc-hash" -version = "1.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] name = "rustc_version" @@ -2518,35 +2417,22 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" -dependencies = [ - "bitflags 2.9.4", - "errno", - "libc", - "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", -] - -[[package]] -name = "rustix" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "errno", "libc", - "linux-raw-sys 0.11.0", - "windows-sys 0.61.0", + "linux-raw-sys", + "windows-sys 0.61.2", ] [[package]] name = "rustls" -version = "0.23.31" +version = "0.23.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" +checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" dependencies = [ "once_cell", "ring", @@ -2558,9 +2444,9 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.8.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" dependencies = [ "openssl-probe", "rustls-pki-types", @@ -2570,18 +2456,18 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.12.0" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" dependencies = [ "zeroize", ] [[package]] name = "rustls-webpki" -version = "0.103.6" +version = "0.103.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8572f3c2cb9934231157b45499fc41e1f58c589fdfb81a844ba873265e80f8eb" +checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" dependencies = [ "ring", "rustls-pki-types", @@ -2621,7 +2507,7 @@ version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" dependencies = [ - "windows-sys 0.61.0", + "windows-sys 0.61.2", ] [[package]] @@ -2653,11 +2539,11 @@ dependencies = [ [[package]] name = "security-framework" -version = "3.4.0" +version = "3.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b369d18893388b345804dc0007963c99b7d665ae71d275812d828c6f089640" +checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "core-foundation", "core-foundation-sys", "libc", @@ -2682,9 +2568,9 @@ checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" [[package]] name = "serde" -version = "1.0.225" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6c24dee235d0da097043389623fb913daddf92c76e9f5a1db88607a0bcbd1d" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", "serde_derive", @@ -2707,7 +2593,7 @@ checksum = "fca2da10b1f1623f47130256065e05e94fd7a98dbd26a780a4c5de831b21e5c2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -2718,19 +2604,19 @@ checksum = "8f68cf7478db8b81abcf71b6d195a34a4891bd3d39868731c4d73194d74ec7a3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] name = "serde-xml-rs" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53630160a98edebde0123eb4dfd0fce6adff091b2305db3154a9e920206eb510" +checksum = "cc2215ce3e6a77550b80a1c37251b7d294febaf42e36e21b7b411e0bf54d540d" dependencies = [ "log", "serde", - "thiserror 1.0.69", - "xml-rs", + "thiserror 2.0.18", + "xml", ] [[package]] @@ -2755,22 +2641,35 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.225" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "659356f9a0cb1e529b24c01e43ad2bdf520ec4ceaf83047b83ddcc2251f96383" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.225" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea936adf78b1f766949a4977b91d2f5595825bd6ec079aa9543ad2685fc4516" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", ] [[package]] @@ -2781,7 +2680,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -2857,10 +2756,11 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.6" +version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ + "errno", "libc", ] @@ -2906,7 +2806,7 @@ dependencies = [ "blake2", "chacha20poly1305", "curve25519-dalek", - "getrandom 0.3.3", + "getrandom 0.3.4", "p256 0.13.2", "ring", "rustc_version", @@ -2916,12 +2816,12 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.0" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -2945,9 +2845,9 @@ dependencies = [ [[package]] name = "stable_deref_trait" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "static_assertions" @@ -2974,9 +2874,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.106" +version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ "proc-macro2", "quote", @@ -3003,7 +2903,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -3014,15 +2914,15 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.22.0" +version = "3.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84fa4d11fadde498443cca10fd3ac23c951f0dc59e080e9f4b93d4df4e4eea53" +checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" dependencies = [ "fastrand", - "getrandom 0.3.3", + "getrandom 0.3.4", "once_cell", - "rustix 1.1.2", - "windows-sys 0.61.0", + "rustix", + "windows-sys 0.61.2", ] [[package]] @@ -3033,9 +2933,9 @@ checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" [[package]] name = "test-log" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e33b98a582ea0be1168eba097538ee8dd4bbe0f2b01b22ac92ea30054e5be7b" +checksum = "37d53ac171c92a39e4769491c4b4dde7022c60042254b5fc044ae409d34a24d4" dependencies = [ "env_logger", "test-log-macros", @@ -3044,13 +2944,13 @@ dependencies = [ [[package]] name = "test-log-macros" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "451b374529930d7601b1eef8d32bc79ae870b6079b069401709c2a8bf9e75f36" +checksum = "be35209fd0781c5401458ab66e4f98accf63553e8fae7425503e92fdd319783b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -3070,11 +2970,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.16" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.16", + "thiserror-impl 2.0.18", ] [[package]] @@ -3085,18 +2985,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] name = "thiserror-impl" -version = "2.0.16" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -3110,30 +3010,30 @@ dependencies = [ [[package]] name = "time" -version = "0.3.44" +version = "0.3.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" +checksum = "9da98b7d9b7dad93488a84b8248efc35352b0b2657397d4167e7ad67e5d535e5" dependencies = [ "deranged", "itoa", "num-conv", "powerfmt", - "serde", + "serde_core", "time-core", "time-macros", ] [[package]] name = "time-core" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" [[package]] name = "time-macros" -version = "0.2.24" +version = "0.2.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" +checksum = "78cc610bac2dcee56805c99642447d4c5dbde4d01f752ffea0199aee1f601dc4" dependencies = [ "num-conv", "time-core", @@ -3141,40 +3041,37 @@ dependencies = [ [[package]] name = "tokio" -version = "1.47.1" +version = "1.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" dependencies = [ - "backtrace", "bytes", - "io-uring", "libc", "mio", "parking_lot", "pin-project-lite", "signal-hook-registry", - "slab", "socket2", "tokio-macros", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] name = "tokio-rustls" -version = "0.26.3" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f63835928ca123f1bef57abbcd23bb2ba0ac9ae1235f1e65bda0d06e7786bd" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ "rustls", "tokio", @@ -3182,9 +3079,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" dependencies = [ "futures-core", "pin-project-lite", @@ -3210,9 +3107,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.16" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" dependencies = [ "bytes", "futures-core", @@ -3223,18 +3120,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.2" +version = "0.7.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f1085dec27c2b6632b04c80b3bb1b4300d6495d1e129693bdda7d91e72eec1" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" dependencies = [ "serde_core", ] [[package]] name = "toml_edit" -version = "0.23.6" +version = "0.23.10+spec-1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3effe7c0e86fdff4f69cdd2ccc1b96f933e24811c5441d44904e8683e27184b" +checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" dependencies = [ "indexmap", "toml_datetime", @@ -3244,18 +3141,18 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.3" +version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cf893c33be71572e0e9aa6dd15e6677937abd686b066eac3f8cd3531688a627" +checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" dependencies = [ "winnow", ] [[package]] name = "tracing" -version = "0.1.41" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "pin-project-lite", "tracing-attributes", @@ -3264,20 +3161,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] name = "tracing-core" -version = "0.1.34" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", "valuable", @@ -3296,9 +3193,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.20" +version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" +checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" dependencies = [ "matchers", "nu-ansi-term", @@ -3318,7 +3215,7 @@ version = "0.1.0" source = "git+https://github.com/trussed-dev/trussed.git?rev=024e0eca5fb7dbd2457831f7c7bffe4341e08775#024e0eca5fb7dbd2457831f7c7bffe4341e08775" dependencies = [ "aes", - "bitflags 2.9.4", + "bitflags 2.10.0", "cbc", "cbor-smol", "cfg-if", @@ -3431,21 +3328,21 @@ dependencies = [ "rustls", "rustls-pki-types", "sha1", - "thiserror 2.0.16", + "thiserror 2.0.18", "utf-8", ] [[package]] name = "typenum" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "unicode-ident" -version = "1.0.19" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "unicode-xid" @@ -3483,13 +3380,13 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.18.1" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" +checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", "js-sys", - "serde", + "serde_core", "wasm-bindgen", ] @@ -3527,29 +3424,20 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" -[[package]] -name = "wasi" -version = "0.14.7+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" -dependencies = [ - "wasip2", -] - [[package]] name = "wasip2" -version = "1.0.1+wasi-0.2.4" +version = "1.0.2+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" dependencies = [ "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.103" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab10a69fbd0a177f5f649ad4d8d3305499c42bab9aef2f7ff592d0ec8f833819" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" dependencies = [ "cfg-if", "once_cell", @@ -3558,25 +3446,11 @@ dependencies = [ "wasm-bindgen-shared", ] -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.103" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb702423545a6007bbc368fde243ba47ca275e549c8a28617f56f6ba53b1d1c" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn 2.0.106", - "wasm-bindgen-shared", -] - [[package]] name = "wasm-bindgen-macro" -version = "0.2.103" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc65f4f411d91494355917b605e1480033152658d71f722a90647f56a70c88a0" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3584,38 +3458,26 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.103" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffc003a991398a8ee604a401e194b6b3a39677b3173d6e74495eb51b82e99a32" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" dependencies = [ + "bumpalo", "proc-macro2", "quote", - "syn 2.0.106", - "wasm-bindgen-backend", + "syn 2.0.114", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.103" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "293c37f4efa430ca14db3721dfbe48d8c33308096bd44d80ebaa775ab71ba1cf" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" dependencies = [ "unicode-ident", ] -[[package]] -name = "which" -version = "4.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" -dependencies = [ - "either", - "home", - "once_cell", - "rustix 0.38.44", -] - [[package]] name = "winapi" version = "0.3.9" @@ -3638,7 +3500,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.0", + "windows-sys 0.61.2", ] [[package]] @@ -3704,24 +3566,24 @@ dependencies = [ [[package]] name = "windows-implement" -version = "0.60.0" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] name = "windows-interface" -version = "0.59.1" +version = "0.59.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -3732,9 +3594,9 @@ checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] name = "windows-link" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-numerics" @@ -3797,16 +3659,16 @@ version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.3", + "windows-targets 0.53.5", ] [[package]] name = "windows-sys" -version = "0.61.0" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-link 0.2.0", + "windows-link 0.2.1", ] [[package]] @@ -3842,19 +3704,19 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.3" +version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "windows-link 0.1.3", - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", + "windows-link 0.2.1", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", ] [[package]] @@ -3880,9 +3742,9 @@ checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] name = "windows_aarch64_msvc" @@ -3898,9 +3760,9 @@ checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_aarch64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] name = "windows_i686_gnu" @@ -3916,9 +3778,9 @@ checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" [[package]] name = "windows_i686_gnullvm" @@ -3928,9 +3790,9 @@ checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] name = "windows_i686_msvc" @@ -3946,9 +3808,9 @@ checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_i686_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] name = "windows_x86_64_gnu" @@ -3964,9 +3826,9 @@ checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" [[package]] name = "windows_x86_64_gnullvm" @@ -3982,9 +3844,9 @@ checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] name = "windows_x86_64_msvc" @@ -4000,15 +3862,15 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "windows_x86_64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" -version = "0.7.13" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" dependencies = [ "memchr", ] @@ -4025,9 +3887,9 @@ dependencies = [ [[package]] name = "wit-bindgen" -version = "0.46.0" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[package]] name = "x509-parser" @@ -4042,52 +3904,58 @@ dependencies = [ "nom", "oid-registry", "rusticata-macros", - "thiserror 2.0.16", + "thiserror 2.0.18", "time", ] [[package]] -name = "xml-rs" -version = "0.8.27" +name = "xml" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fd8403733700263c6eb89f192880191f1b83e332f7a20371ddcf421c4a337c7" +checksum = "b8aa498d22c9bbaf482329839bc5620c46be275a19a812e9a22a2b07529a642a" [[package]] name = "zerocopy" -version = "0.8.27" +version = "0.8.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +checksum = "71ddd76bcebeed25db614f82bf31a9f4222d3fbba300e6fb6c00afa26cbd4d9d" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.27" +version = "0.8.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +checksum = "d8187381b52e32220d50b255276aa16a084ec0a9017a0ca2152a1f55c539758d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] name = "zeroize" -version = "1.8.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" dependencies = [ "zeroize_derive", ] [[package]] name = "zeroize_derive" -version = "1.4.2" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] + +[[package]] +name = "zmij" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02aae0f83f69aafc94776e879363e9771d7ecbffe2c7fbb6c14c5e00dfe88439" diff --git a/libwebauthn/Cargo.toml b/libwebauthn/Cargo.toml index f75d6b9..ee0a6e2 100644 --- a/libwebauthn/Cargo.toml +++ b/libwebauthn/Cargo.toml @@ -71,10 +71,11 @@ snow = { version = "0.10", features = ["use-p256"] } ctap-types = { version = "0.4.0" } btleplug = "0.11.7" thiserror = "2.0.12" +serde_json = "1.0.141" apdu-core = { version = "0.4.0", optional = true } apdu = { version = "0.4.0", optional = true } pcsc = { version = "2.9.0", optional = true } -nfc1 = { version = "0.6.0", optional = true, default-features = false } +nfc1 = { version = "=0.6.0", optional = true, default-features = false } nfc1-sys = { version = "0.3.9", optional = true, default-features = false } [dev-dependencies] diff --git a/libwebauthn/examples/prf_test.rs b/libwebauthn/examples/prf_test.rs index 5d0178e..f4e81cd 100644 --- a/libwebauthn/examples/prf_test.rs +++ b/libwebauthn/examples/prf_test.rs @@ -14,7 +14,7 @@ use tracing_subscriber::{self, EnvFilter}; use libwebauthn::ops::webauthn::{ GetAssertionHmacOrPrfInput, GetAssertionRequest, GetAssertionRequestExtensions, PRFValue, - UserVerificationRequirement, + PrfInput, UserVerificationRequirement, }; use libwebauthn::pin::PinRequestReason; use libwebauthn::proto::ctap2::{Ctap2PublicKeyCredentialDescriptor, Ctap2PublicKeyCredentialType}; @@ -126,10 +126,11 @@ pub async fn main() -> Result<(), Box> { }); let eval_by_credential = HashMap::new(); - let hmac_or_prf = GetAssertionHmacOrPrfInput::Prf { + let hmac_or_prf = GetAssertionHmacOrPrfInput::Prf(PrfInput { eval, eval_by_credential, - }; + }); + run_success_test( &mut channel, &credential, @@ -155,7 +156,7 @@ async fn run_success_test( allow: vec![credential.clone()], user_verification: UserVerificationRequirement::Preferred, extensions: Some(GetAssertionRequestExtensions { - hmac_or_prf, + hmac_or_prf: Some(hmac_or_prf), ..Default::default() }), timeout: TIMEOUT, diff --git a/libwebauthn/examples/webauthn_cable.rs b/libwebauthn/examples/webauthn_cable.rs index f6ae857..5bcb770 100644 --- a/libwebauthn/examples/webauthn_cable.rs +++ b/libwebauthn/examples/webauthn_cable.rs @@ -19,7 +19,8 @@ use tokio::time::sleep; use tracing_subscriber::{self, EnvFilter}; use libwebauthn::ops::webauthn::{ - GetAssertionRequest, MakeCredentialRequest, ResidentKeyRequirement, UserVerificationRequirement, + GetAssertionRequest, GetAssertionRequestExtensions, MakeCredentialRequest, + ResidentKeyRequirement, UserVerificationRequirement, }; use libwebauthn::proto::ctap2::{ Ctap2CredentialType, Ctap2PublicKeyCredentialDescriptor, Ctap2PublicKeyCredentialRpEntity, @@ -161,7 +162,7 @@ pub async fn main() -> Result<(), Box> { hash: Vec::from(challenge), allow: vec![credential], user_verification: UserVerificationRequirement::Discouraged, - extensions: None, + extensions: Some(GetAssertionRequestExtensions::default()), timeout: TIMEOUT, }; diff --git a/libwebauthn/examples/webauthn_extensions_hid.rs b/libwebauthn/examples/webauthn_extensions_hid.rs index 22f6bdd..1feef5f 100644 --- a/libwebauthn/examples/webauthn_extensions_hid.rs +++ b/libwebauthn/examples/webauthn_extensions_hid.rs @@ -11,8 +11,7 @@ use tracing_subscriber::{self, EnvFilter}; use libwebauthn::ops::webauthn::{ CredentialProtectionExtension, CredentialProtectionPolicy, GetAssertionHmacOrPrfInput, - GetAssertionRequest, GetAssertionRequestExtensions, HMACGetSecretInput, - MakeCredentialHmacOrPrfInput, MakeCredentialLargeBlobExtension, MakeCredentialRequest, + GetAssertionRequest, GetAssertionRequestExtensions, HMACGetSecretInput, MakeCredentialRequest, MakeCredentialsRequestExtensions, ResidentKeyRequirement, UserVerificationRequirement, }; use libwebauthn::pin::PinRequestReason; @@ -88,10 +87,11 @@ pub async fn main() -> Result<(), Box> { policy: CredentialProtectionPolicy::UserVerificationRequired, enforce_policy: true, }), - cred_blob: Some(r"My own little blob".into()), - large_blob: MakeCredentialLargeBlobExtension::None, + cred_blob: Some("My own little blob".as_bytes().into()), + large_blob: None, min_pin_length: Some(true), - hmac_or_prf: MakeCredentialHmacOrPrfInput::HmacGetSecret, + hmac_create_secret: Some(true), + prf: None, cred_props: Some(true), }; @@ -148,11 +148,13 @@ pub async fn main() -> Result<(), Box> { allow: vec![credential], user_verification: UserVerificationRequirement::Discouraged, extensions: Some(GetAssertionRequestExtensions { - cred_blob: Some(true), - hmac_or_prf: GetAssertionHmacOrPrfInput::HmacGetSecret(HMACGetSecretInput { - salt1: [1; 32], - salt2: None, - }), + cred_blob: true, + hmac_or_prf: Some(GetAssertionHmacOrPrfInput::HmacGetSecret( + HMACGetSecretInput { + salt1: [1; 32], + salt2: None, + }, + )), ..Default::default() }), timeout: TIMEOUT, diff --git a/libwebauthn/examples/webauthn_hid.rs b/libwebauthn/examples/webauthn_hid.rs index e1dd6e2..d05fb46 100644 --- a/libwebauthn/examples/webauthn_hid.rs +++ b/libwebauthn/examples/webauthn_hid.rs @@ -10,7 +10,8 @@ use tokio::sync::broadcast::Receiver; use tracing_subscriber::{self, EnvFilter}; use libwebauthn::ops::webauthn::{ - GetAssertionRequest, MakeCredentialRequest, ResidentKeyRequirement, UserVerificationRequirement, + GetAssertionRequest, GetAssertionRequestExtensions, MakeCredentialRequest, + ResidentKeyRequirement, UserVerificationRequirement, }; use libwebauthn::pin::PinRequestReason; use libwebauthn::proto::ctap2::{ @@ -128,7 +129,7 @@ pub async fn main() -> Result<(), Box> { hash: Vec::from(challenge), allow: vec![credential], user_verification: UserVerificationRequirement::Discouraged, - extensions: None, + extensions: Some(GetAssertionRequestExtensions::default()), timeout: TIMEOUT, }; diff --git a/libwebauthn/examples/webauthn_json_hid.rs b/libwebauthn/examples/webauthn_json_hid.rs new file mode 100644 index 0000000..8005e04 --- /dev/null +++ b/libwebauthn/examples/webauthn_json_hid.rs @@ -0,0 +1,165 @@ +use std::error::Error; +use std::io::{self, Write}; +use std::time::Duration; + +use libwebauthn::UvUpdate; +use text_io::read; +use tokio::sync::broadcast::Receiver; +use tracing_subscriber::{self, EnvFilter}; + +use libwebauthn::ops::webauthn::{ + GetAssertionRequest, MakeCredentialRequest, RelyingPartyId, WebAuthnIDL as _, +}; +use libwebauthn::pin::PinRequestReason; +use libwebauthn::transport::hid::list_devices; +use libwebauthn::transport::{Channel as _, Device}; +use libwebauthn::webauthn::{Error as WebAuthnError, WebAuthn}; + +const TIMEOUT: Duration = Duration::from_secs(10); + +fn setup_logging() { + tracing_subscriber::fmt() + .with_env_filter(EnvFilter::from_default_env()) + .without_time() + .init(); +} + +async fn handle_updates(mut state_recv: Receiver) { + while let Ok(update) = state_recv.recv().await { + match update { + UvUpdate::PresenceRequired => println!("Please touch your device!"), + UvUpdate::UvRetry { attempts_left } => { + print!("UV failed."); + if let Some(attempts_left) = attempts_left { + print!(" You have {attempts_left} attempts left."); + } + } + UvUpdate::PinRequired(update) => { + let mut attempts_str = String::new(); + if let Some(attempts) = update.attempts_left { + attempts_str = format!(". You have {attempts} attempts left!"); + }; + + match update.reason { + PinRequestReason::RelyingPartyRequest => println!("RP required a PIN."), + PinRequestReason::AuthenticatorPolicy => { + println!("Your device requires a PIN.") + } + PinRequestReason::FallbackFromUV => { + println!("UV failed too often and is blocked. Falling back to PIN.") + } + } + print!("PIN: Please enter the PIN for your authenticator{attempts_str}: "); + io::stdout().flush().unwrap(); + let pin_raw: String = read!("{}\n"); + + if pin_raw.is_empty() { + println!("PIN: No PIN provided, cancelling operation."); + update.cancel(); + } else { + let _ = update.send_pin(&pin_raw); + } + } + } + } +} + +#[tokio::main] +pub async fn main() -> Result<(), Box> { + setup_logging(); + + let devices = list_devices().await.unwrap(); + println!("Devices found: {:?}", devices); + + for mut device in devices { + println!("Selected HID authenticator: {}", &device); + let mut channel = device.channel().await?; + channel.wink(TIMEOUT).await?; + + let rpid = RelyingPartyId("example.org".to_owned()); + let request_json = r#" + { + "rp": { + "id": "example.org", + "name": "Example Relying Party" + }, + "user": { + "id": "MTIzNDU2NzgxMjM0NTY3ODEyMzQ1Njc4MTIzNDU2Nzg", + "name": "Mario Rossi", + "displayName": "Mario Rossi" + }, + "challenge": "MTIzNDU2NzgxMjM0NTY3ODEyMzQ1Njc4MTIzNDU2Nzg", + "pubKeyCredParams": [ + {"type": "public-key", "alg": -7} + ], + "timeout": 60000, + "excludeCredentials": [], + "authenticatorSelection": { + "residentKey": "discouraged", + "userVerification": "preferred" + }, + "attestation": "none" + } + "#; + let make_credentials_request: MakeCredentialRequest = + MakeCredentialRequest::from_json(&rpid, request_json) + .expect("Failed to parse request JSON"); + println!( + "WebAuthn MakeCredential request: {:?}", + make_credentials_request + ); + + let state_recv = channel.get_ux_update_receiver(); + tokio::spawn(handle_updates(state_recv)); + + let response = loop { + match channel + .webauthn_make_credential(&make_credentials_request) + .await + { + Ok(response) => break Ok(response), + Err(WebAuthnError::Ctap(ctap_error)) => { + if ctap_error.is_retryable_user_error() { + println!("Oops, try again! Error: {}", ctap_error); + continue; + } + break Err(WebAuthnError::Ctap(ctap_error)); + } + Err(err) => break Err(err), + }; + } + .unwrap(); + println!("WebAuthn MakeCredential response: {:?}", response); + + let request_json = r#" + { + "challenge": "Y3JlZGVudGlhbHMtZm9yLWxpbnV4L2xpYndlYmF1dGhu", + "timeout": 30000, + "rpId": "example.org", + "userVerification": "discouraged" + } + "#; + let get_assertion: GetAssertionRequest = + GetAssertionRequest::from_json(&rpid, request_json) + .expect("Failed to parse request JSON"); + println!("WebAuthn GetAssertion request: {:?}", get_assertion); + + let response = loop { + match channel.webauthn_get_assertion(&get_assertion).await { + Ok(response) => break Ok(response), + Err(WebAuthnError::Ctap(ctap_error)) => { + if ctap_error.is_retryable_user_error() { + println!("Oops, try again! Error: {}", ctap_error); + continue; + } + break Err(WebAuthnError::Ctap(ctap_error)); + } + Err(err) => break Err(err), + }; + } + .unwrap(); + println!("WebAuthn GetAssertion response: {:?}", response); + } + + Ok(()) +} diff --git a/libwebauthn/examples/webauthn_preflight_hid.rs b/libwebauthn/examples/webauthn_preflight_hid.rs index 98d7165..97f876d 100644 --- a/libwebauthn/examples/webauthn_preflight_hid.rs +++ b/libwebauthn/examples/webauthn_preflight_hid.rs @@ -11,8 +11,8 @@ use tokio::sync::broadcast::Receiver; use tracing_subscriber::{self, EnvFilter}; use libwebauthn::ops::webauthn::{ - GetAssertionRequest, GetAssertionResponse, MakeCredentialRequest, ResidentKeyRequirement, - UserVerificationRequirement, + GetAssertionRequest, GetAssertionRequestExtensions, GetAssertionResponse, + MakeCredentialRequest, ResidentKeyRequirement, UserVerificationRequirement, }; use libwebauthn::pin::PinRequestReason; use libwebauthn::proto::ctap2::{ @@ -202,7 +202,7 @@ async fn get_assertion_call( hash: Vec::from(challenge), allow: allow_list, user_verification: UserVerificationRequirement::Discouraged, - extensions: None, + extensions: Some(GetAssertionRequestExtensions::default()), timeout: TIMEOUT, }; diff --git a/libwebauthn/examples/webauthn_prf_hid.rs b/libwebauthn/examples/webauthn_prf_hid.rs index 35d34a7..6ae845a 100644 --- a/libwebauthn/examples/webauthn_prf_hid.rs +++ b/libwebauthn/examples/webauthn_prf_hid.rs @@ -13,8 +13,8 @@ use tracing_subscriber::{self, EnvFilter}; use libwebauthn::ops::webauthn::{ GetAssertionHmacOrPrfInput, GetAssertionRequest, GetAssertionRequestExtensions, - MakeCredentialHmacOrPrfInput, MakeCredentialRequest, MakeCredentialsRequestExtensions, - PRFValue, ResidentKeyRequirement, UserVerificationRequirement, + MakeCredentialPrfInput, MakeCredentialRequest, MakeCredentialsRequestExtensions, PRFValue, + PrfInput, ResidentKeyRequirement, UserVerificationRequirement, }; use libwebauthn::pin::PinRequestReason; use libwebauthn::proto::ctap2::{ @@ -85,7 +85,7 @@ pub async fn main() -> Result<(), Box> { let challenge: [u8; 32] = thread_rng().gen(); let extensions = MakeCredentialsRequestExtensions { - hmac_or_prf: MakeCredentialHmacOrPrfInput::Prf, + prf: Some(MakeCredentialPrfInput { _eval: None }), ..Default::default() }; @@ -148,10 +148,10 @@ pub async fn main() -> Result<(), Box> { second: None, }, ); - let hmac_or_prf = GetAssertionHmacOrPrfInput::Prf { + let hmac_or_prf = GetAssertionHmacOrPrfInput::Prf(PrfInput { eval, eval_by_credential, - }; + }); run_success_test( &mut channel, &credential, @@ -175,10 +175,10 @@ pub async fn main() -> Result<(), Box> { second: None, }, ); - let hmac_or_prf = GetAssertionHmacOrPrfInput::Prf { + let hmac_or_prf = GetAssertionHmacOrPrfInput::Prf(PrfInput { eval, eval_by_credential, - }; + }); run_success_test( &mut channel, &credential, @@ -195,10 +195,10 @@ pub async fn main() -> Result<(), Box> { }); let eval_by_credential = HashMap::new(); - let hmac_or_prf = GetAssertionHmacOrPrfInput::Prf { + let hmac_or_prf = GetAssertionHmacOrPrfInput::Prf(PrfInput { eval, eval_by_credential, - }; + }); run_success_test( &mut channel, &credential, @@ -243,10 +243,10 @@ pub async fn main() -> Result<(), Box> { second: None, }, ); - let hmac_or_prf = GetAssertionHmacOrPrfInput::Prf { + let hmac_or_prf = GetAssertionHmacOrPrfInput::Prf(PrfInput { eval, eval_by_credential, - }; + }); run_success_test( &mut channel, &credential, @@ -284,10 +284,10 @@ pub async fn main() -> Result<(), Box> { second: Some([8; 32]), }, ); - let hmac_or_prf = GetAssertionHmacOrPrfInput::Prf { + let hmac_or_prf = GetAssertionHmacOrPrfInput::Prf(PrfInput { eval, eval_by_credential, - }; + }); run_success_test( &mut channel, &credential, @@ -322,10 +322,10 @@ pub async fn main() -> Result<(), Box> { second: Some([8; 32]), }, ); - let hmac_or_prf = GetAssertionHmacOrPrfInput::Prf { + let hmac_or_prf = GetAssertionHmacOrPrfInput::Prf(PrfInput { eval, eval_by_credential, - }; + }); run_success_test( &mut channel, &credential, @@ -349,10 +349,10 @@ pub async fn main() -> Result<(), Box> { second: None, }, ); - let hmac_or_prf = GetAssertionHmacOrPrfInput::Prf { + let hmac_or_prf = GetAssertionHmacOrPrfInput::Prf(PrfInput { eval, eval_by_credential, - }; + }); run_failed_test( &mut channel, Some(&credential), @@ -373,10 +373,10 @@ pub async fn main() -> Result<(), Box> { second: None, }, ); - let hmac_or_prf = GetAssertionHmacOrPrfInput::Prf { + let hmac_or_prf = GetAssertionHmacOrPrfInput::Prf(PrfInput { eval, eval_by_credential, - }; + }); run_failed_test( &mut channel, Some(&credential), @@ -397,10 +397,10 @@ pub async fn main() -> Result<(), Box> { second: None, }, ); - let hmac_or_prf = GetAssertionHmacOrPrfInput::Prf { + let hmac_or_prf = GetAssertionHmacOrPrfInput::Prf(PrfInput { eval, eval_by_credential, - }; + }); run_failed_test( &mut channel, None, @@ -427,7 +427,7 @@ async fn run_success_test( allow: vec![credential.clone()], user_verification: UserVerificationRequirement::Discouraged, extensions: Some(GetAssertionRequestExtensions { - hmac_or_prf, + hmac_or_prf: Some(hmac_or_prf), ..Default::default() }), timeout: TIMEOUT, @@ -469,7 +469,7 @@ async fn run_failed_test( allow: credential.map(|x| vec![x.clone()]).unwrap_or_default(), user_verification: UserVerificationRequirement::Discouraged, extensions: Some(GetAssertionRequestExtensions { - hmac_or_prf, + hmac_or_prf: Some(hmac_or_prf), ..Default::default() }), timeout: TIMEOUT, diff --git a/libwebauthn/src/fido.rs b/libwebauthn/src/fido.rs index 55ce46b..532a08a 100644 --- a/libwebauthn/src/fido.rs +++ b/libwebauthn/src/fido.rs @@ -273,11 +273,10 @@ mod tests { 0x86, 0xce, 0x19, 0x47, ]; let flag_bits = 0b1100_0101; - let flags = - AuthenticatorDataFlags::USER_PRESENT | - AuthenticatorDataFlags::USER_VERIFIED | - AuthenticatorDataFlags::ATTESTED_CREDENTIALS | - AuthenticatorDataFlags::EXTENSION_DATA; + let flags = AuthenticatorDataFlags::USER_PRESENT + | AuthenticatorDataFlags::USER_VERIFIED + | AuthenticatorDataFlags::ATTESTED_CREDENTIALS + | AuthenticatorDataFlags::EXTENSION_DATA; assert_eq!(flag_bits, flags.bits()); let signature_count = 0; let aaguid = [ @@ -316,7 +315,7 @@ mod tests { flags, signature_count, attested_credential: Some(attested_credential.clone()), - extensions: Some(extensions.clone()) + extensions: Some(extensions.clone()), }; let webauthn_auth_data = auth_data.to_response_bytes().unwrap(); assert_eq!(rp_id_hash, &webauthn_auth_data[..32]); @@ -341,14 +340,8 @@ mod tests { let authdata_wrapped = cbor::to_vec(&ByteBuf::from(webauthn_auth_data)).unwrap(); let auth_data_reparsed: AuthenticatorData = cbor::from_slice(authdata_wrapped.as_slice()).unwrap(); - assert_eq!( - auth_data.rp_id_hash, - auth_data_reparsed.rp_id_hash - ); - assert_eq!( - auth_data.flags.bits(), - auth_data_reparsed.flags.bits() - ); + assert_eq!(auth_data.rp_id_hash, auth_data_reparsed.rp_id_hash); + assert_eq!(auth_data.flags.bits(), auth_data_reparsed.flags.bits()); assert_eq!( auth_data.signature_count, auth_data_reparsed.signature_count @@ -366,9 +359,6 @@ mod tests { attested_credential.credential_public_key, attested_credential_reparsed.credential_public_key ); - assert_eq!( - extensions, - auth_data_reparsed.extensions.unwrap() - ); + assert_eq!(extensions, auth_data_reparsed.extensions.unwrap()); } } diff --git a/libwebauthn/src/ops/u2f.rs b/libwebauthn/src/ops/u2f.rs index c442b81..f21dd6a 100644 --- a/libwebauthn/src/ops/u2f.rs +++ b/libwebauthn/src/ops/u2f.rs @@ -9,7 +9,8 @@ use x509_parser::nom::AsBytes; use super::webauthn::MakeCredentialRequest; use crate::fido::{AttestedCredentialData, AuthenticatorData, AuthenticatorDataFlags}; use crate::ops::webauthn::{ - GetAssertionRequest, GetAssertionResponse, MakeCredentialResponse, UserVerificationRequirement, + GetAssertionRequest, GetAssertionResponse, + MakeCredentialResponse, UserVerificationRequirement, }; use crate::proto::ctap1::{Ctap1RegisterRequest, Ctap1SignRequest}; use crate::proto::ctap1::{Ctap1RegisterResponse, Ctap1SignResponse}; diff --git a/libwebauthn/src/ops/webauthn/client_data.rs b/libwebauthn/src/ops/webauthn/client_data.rs new file mode 100644 index 0000000..2c614b7 --- /dev/null +++ b/libwebauthn/src/ops/webauthn/client_data.rs @@ -0,0 +1,38 @@ +use crate::ops::webauthn::Operation; + +use serde::Deserialize; +use sha2::{Digest, Sha256}; + +#[derive(Debug, Clone, PartialEq, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ClientData { + pub operation: Operation, + pub challenge: Vec, + pub origin: String, + pub cross_origin: Option, + /// The origin of the top-level document, if in an iframe. + /// https://www.w3.org/TR/webauthn-3/#dom-collectedclientdata-toporigin + pub top_origin: Option, +} + +impl ClientData { + pub fn hash(&self) -> Vec { + let op_str = match &self.operation { + Operation::MakeCredential => "webauthn.create", + Operation::GetAssertion => "webauthn.get", + }; + let challenge_str = base64_url::encode(&self.challenge); + let origin_str = &self.origin; + let cross_origin_str = if self.cross_origin.unwrap_or(false) { + "true" + } else { + "false" + }; + let json = + format!("{{\"type\":\"{op_str}\",\"challenge\":\"{challenge_str}\",\"origin\":\"{origin_str}\",\"crossOrigin\":{cross_origin_str}}}"); + + let mut hasher = Sha256::new(); + hasher.update(json.as_bytes()); + hasher.finalize().to_vec() + } +} diff --git a/libwebauthn/src/ops/webauthn/get_assertion.rs b/libwebauthn/src/ops/webauthn/get_assertion.rs index 336ff00..af1a990 100644 --- a/libwebauthn/src/ops/webauthn/get_assertion.rs +++ b/libwebauthn/src/ops/webauthn/get_assertion.rs @@ -6,6 +6,17 @@ use tracing::{debug, error, trace}; use crate::{ fido::AuthenticatorData, + ops::webauthn::{ + client_data::ClientData, + idl::{ + get::{ + HmacGetSecretInputJson, LargeBlobInputJson, PrfInputJson, + PublicKeyCredentialRequestOptionsJSON, + }, + FromInnerModel, JsonError, + }, + Operation, WebAuthnIDL, + }, pin::PinUvAuthProtocol, proto::ctap2::{ Ctap2AttestationStatement, Ctap2GetAssertionResponseExtensions, @@ -14,9 +25,10 @@ use crate::{ webauthn::CtapError, }; -use super::{DowngradableRequest, SignRequest, UserVerificationRequirement}; +use super::timeout::DEFAULT_TIMEOUT; +use super::{DowngradableRequest, RelyingPartyId, SignRequest, UserVerificationRequirement}; -#[derive(Debug, Default, Clone, Serialize)] +#[derive(Debug, Default, Clone, Serialize, PartialEq)] pub struct PRFValue { #[serde(with = "serde_bytes")] pub first: [u8; 32], @@ -24,7 +36,7 @@ pub struct PRFValue { pub second: Option<[u8; 32]>, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct GetAssertionRequest { pub relying_party_id: String, pub hash: Vec, @@ -34,15 +46,167 @@ pub struct GetAssertionRequest { pub timeout: Duration, } -#[derive(Debug, Default, Clone)] +#[derive(thiserror::Error, Debug)] +pub enum GetAssertionRequestParsingError { + /// The client must throw an "EncodingError" DOMException. + #[error("Invalid JSON format: {0}")] + EncodingError(#[from] JsonError), + + #[error("Unexpected length for {0}: {1}")] + UnexpectedLengthError(String, usize), + + #[error("Not supported: {0}")] + NotSupported(String), +} + +impl WebAuthnIDL for GetAssertionRequest { + type Error = GetAssertionRequestParsingError; + type InnerModel = PublicKeyCredentialRequestOptionsJSON; +} + +/** dictionary PublicKeyCredentialRequestOptionsJSON { + required Base64URLString challenge; + unsigned long timeout; + DOMString rpId; + sequence allowCredentials = []; + DOMString userVerification = "preferred"; + sequence hints = []; + AuthenticationExtensionsClientInputsJSON extensions; +}; */ + +impl FromInnerModel + for GetAssertionRequest +{ + fn from_inner_model( + rpid: &RelyingPartyId, + inner: PublicKeyCredentialRequestOptionsJSON, + ) -> Result { + let hmac_or_prf = match inner.extensions.clone() { + Some(ext) => { + if let Some(prf) = ext.prf { + let prf_input = PrfInput::try_from(prf)?; + Some(GetAssertionHmacOrPrfInput::Prf(prf_input)) + } else if let Some(hmac) = ext.hmac_get_secret { + let hmac_input = HMACGetSecretInput::try_from(hmac)?; + Some(GetAssertionHmacOrPrfInput::HmacGetSecret(hmac_input)) + } else { + None + } + } + None => None, + }; + + let extensions = + inner + .extensions + .as_ref() + .map(|extensions_opt| GetAssertionRequestExtensions { + cred_blob: extensions_opt.cred_blob.unwrap_or(false), + large_blob: extensions_opt + .large_blob + .clone() + .and_then(|lb| GetAssertionLargeBlobExtension::try_from(lb).ok()), + hmac_or_prf: hmac_or_prf.clone(), + }); + + let timeout: Duration = inner + .timeout + .map(|s| Duration::from_millis(s.into())) + .unwrap_or(DEFAULT_TIMEOUT); + + let client_data_json = ClientData { + operation: Operation::GetAssertion, + challenge: inner.challenge.to_vec(), + origin: rpid.to_string(), + cross_origin: None, + top_origin: None, + }; + + Ok(GetAssertionRequest { + relying_party_id: rpid.to_string(), + hash: client_data_json.hash(), + allow: inner + .allow_credentials + .into_iter() + .map(|c| c.into()) + .collect(), + extensions, + user_verification: inner.user_verification, + timeout, + }) + } +} + +#[derive(Debug, Clone, PartialEq)] pub enum GetAssertionHmacOrPrfInput { - #[default] - None, HmacGetSecret(HMACGetSecretInput), - Prf { - eval: Option, - eval_by_credential: HashMap, - }, + Prf(PrfInput), +} + +#[derive(Debug, Clone, PartialEq)] +pub struct PrfInput { + pub eval: Option, + pub eval_by_credential: HashMap, +} + +impl TryFrom for PrfInput { + type Error = GetAssertionRequestParsingError; + + fn try_from(value: PrfInputJson) -> Result { + let eval = match value.eval { + Some(value) => Some(PRFValue { + first: value.first.as_slice().try_into().map_err(|_| { + GetAssertionRequestParsingError::UnexpectedLengthError( + "extensions.prf.eval.first".to_string(), + value.first.as_slice().len(), + ) + })?, + second: match value.second { + Some(s) => Some(s.as_slice().try_into().map_err(|_| { + GetAssertionRequestParsingError::UnexpectedLengthError( + "extensions.prf.eval.second".to_string(), + s.as_slice().len(), + ) + })?), + None => None, + }, + }), + None => None, + }; + let eval_by_credential = match value.eval_by_credential { + Some(map) => map + .into_iter() + .map(|(k, v)| { + Ok(( + k, + PRFValue { + first: v.first.as_slice().try_into().map_err(|_| { + GetAssertionRequestParsingError::UnexpectedLengthError( + "extensions.prf.eval_by_credential[i].first".to_string(), + v.first.as_slice().len(), + ) + })?, + second: match v.second { + Some(s) => Some(s.as_slice().try_into().map_err(|_| { + GetAssertionRequestParsingError::UnexpectedLengthError( + "extensions.prf.eval_by_credential[i].second".to_string(), + s.as_slice().len(), + ) + })?), + None => None, + }, + }, + )) + }) + .collect::, GetAssertionRequestParsingError>>()?, + None => HashMap::new(), + }; + + Ok(PrfInput { + eval, + eval_by_credential, + }) + } } #[derive(Debug, Default, Clone, Serialize)] @@ -57,15 +221,52 @@ pub struct HMACGetSecretInput { pub salt2: Option<[u8; 32]>, } -#[derive(Debug, Default, Clone, PartialEq, Eq)] +impl TryFrom for HMACGetSecretInput { + type Error = GetAssertionRequestParsingError; + + fn try_from(value: HmacGetSecretInputJson) -> Result { + let salt1 = value.salt1.as_slice().try_into().map_err(|_| { + GetAssertionRequestParsingError::UnexpectedLengthError( + "extensions.hmacCreateSecret.salt1".to_string(), + value.salt1.as_slice().len(), + ) + })?; + let salt2 = match value.salt2 { + Some(s) => Some(s.as_slice().try_into().map_err(|_| { + GetAssertionRequestParsingError::UnexpectedLengthError( + "extensions.hmacCreateSecret.salt2".to_string(), + s.as_slice().len(), + ) + })?), + None => None, + }; + Ok(HMACGetSecretInput { salt1, salt2 }) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] pub enum GetAssertionLargeBlobExtension { - #[default] - None, Read, // Not yet supported // Write(Vec), } +impl TryFrom for GetAssertionLargeBlobExtension { + type Error = GetAssertionRequestParsingError; + + fn try_from(value: LargeBlobInputJson) -> Result { + match value.read { + Some(true) => Ok(GetAssertionLargeBlobExtension::Read), + Some(false) => Err(GetAssertionRequestParsingError::NotSupported( + "largeBlob writes not supported".to_string(), + )), + None => Err(GetAssertionRequestParsingError::NotSupported( + "largeBlob read not requested".to_string(), + )), + } + } +} + #[derive(Debug, Default, Clone, PartialEq, Eq, Serialize)] pub struct GetAssertionLargeBlobExtensionOutput { #[serde(skip_serializing_if = "Option::is_none")] @@ -75,11 +276,11 @@ pub struct GetAssertionLargeBlobExtensionOutput { // pub written: Option, } -#[derive(Debug, Default, Clone)] +#[derive(Debug, Default, Clone, PartialEq)] pub struct GetAssertionRequestExtensions { - pub cred_blob: Option, - pub hmac_or_prf: GetAssertionHmacOrPrfInput, - pub large_blob: GetAssertionLargeBlobExtension, + pub cred_blob: bool, + pub hmac_or_prf: Option, + pub large_blob: Option, } #[derive(Clone, Debug, Default, Serialize)] @@ -227,3 +428,163 @@ impl DowngradableRequest> for GetAssertionRequest { Ok(downgraded_requests) } } + +#[cfg(test)] +mod tests { + use std::time::Duration; + + use serde_bytes::ByteBuf; + + use crate::ops::webauthn::GetAssertionRequest; + use crate::ops::webauthn::RelyingPartyId; + use crate::proto::ctap2::Ctap2PublicKeyCredentialType; + + use super::*; + + pub const REQUEST_BASE_JSON: &str = r#" + { + "challenge": "Y3JlZGVudGlhbHMtZm9yLWxpbnV4L2xpYndlYmF1dGhu", + "timeout": 30000, + "rpId": "example.org", + "allowCredentials": [ + { + "type": "public-key", + "id": "bXktY3JlZGVudGlhbC1pZA" + } + ], + "userVerification": "preferred" + } + "#; + + fn request_base() -> GetAssertionRequest { + let client_data_json = ClientData { + operation: Operation::GetAssertion, + challenge: base64_url::decode("Y3JlZGVudGlhbHMtZm9yLWxpbnV4L2xpYndlYmF1dGhu").unwrap(), + origin: "example.org".to_string(), + cross_origin: None, + top_origin: None, + }; + GetAssertionRequest { + relying_party_id: "example.org".to_owned(), + hash: client_data_json.hash(), + allow: vec![Ctap2PublicKeyCredentialDescriptor { + r#type: Ctap2PublicKeyCredentialType::PublicKey, + id: ByteBuf::from(base64_url::decode("bXktY3JlZGVudGlhbC1pZA").unwrap()), + transports: None, + }], + extensions: None, // No extensions key in the base JSON + user_verification: UserVerificationRequirement::Preferred, + timeout: Duration::from_secs(30), + } + } + + fn json_field_add(str: &str, field: &str, value: &str) -> String { + let mut v: serde_json::Value = serde_json::from_str(str).unwrap(); + v.as_object_mut() + .unwrap() + .insert(field.to_owned(), serde_json::from_str(value).unwrap()); + serde_json::to_string(&v).unwrap() + } + + fn json_field_rm(str: &str, field: &str) -> String { + let mut v: serde_json::Value = serde_json::from_str(str).unwrap(); + v.as_object_mut().unwrap().remove(field); + serde_json::to_string(&v).unwrap() + } + + #[test] + fn test_request_from_json_base() { + let rpid = RelyingPartyId::try_from("example.org").unwrap(); + let req: GetAssertionRequest = + GetAssertionRequest::from_json(&rpid, REQUEST_BASE_JSON).unwrap(); + assert_eq!(req, request_base()); + } + + #[test] + fn test_request_from_json_ignore_missing_rp_id() { + let rpid = RelyingPartyId::try_from("example.org").unwrap(); + let req_json = json_field_rm(REQUEST_BASE_JSON, "rpId"); + + let req: GetAssertionRequest = GetAssertionRequest::from_json(&rpid, &req_json).unwrap(); + assert_eq!(req, request_base()); + } + + #[test] + fn test_request_from_json_ignore_request_rp_id() { + let rpid = RelyingPartyId::try_from("example.org").unwrap(); + let req_json = json_field_rm(REQUEST_BASE_JSON, "rpId"); + let req_json = json_field_add(&req_json, "rpId", r#""another-example.org""#); + + let req: GetAssertionRequest = GetAssertionRequest::from_json(&rpid, &req_json).unwrap(); + assert_eq!(req, request_base()); + } + + #[test] + fn test_request_from_json_ignore_missing_allow_credentials() { + let rpid = RelyingPartyId::try_from("example.org").unwrap(); + let req_json = json_field_rm(REQUEST_BASE_JSON, "allowCredentials"); + + let req: GetAssertionRequest = GetAssertionRequest::from_json(&rpid, &req_json).unwrap(); + assert_eq!( + req, + GetAssertionRequest { + allow: vec![], + ..request_base() + } + ); + } + + #[test] + fn test_request_from_json_default_timeout() { + let rpid = RelyingPartyId::try_from("example.org").unwrap(); + let req_json = json_field_rm(REQUEST_BASE_JSON, "timeout"); + + let req: GetAssertionRequest = GetAssertionRequest::from_json(&rpid, &req_json).unwrap(); + assert_eq!(req.timeout, DEFAULT_TIMEOUT); + } + + #[test] + fn test_request_from_json_empty_extensions() { + // Test that "extensions": {} results in Some(default) not None + // This is important for strict portals that distinguish between + // no extensions key vs empty extensions object + let rpid = RelyingPartyId::try_from("example.org").unwrap(); + let req_json = json_field_add(REQUEST_BASE_JSON, "extensions", r#"{}"#); + + let req: GetAssertionRequest = GetAssertionRequest::from_json(&rpid, &req_json).unwrap(); + assert_eq!( + req.extensions, + Some(GetAssertionRequestExtensions::default()) + ); + } + + #[test] + #[ignore] // FIXME(#134) allow arbitrary size input + fn test_request_from_json_prf_extension() { + let rpid = RelyingPartyId::try_from("example.org").unwrap(); + let req_json = json_field_add( + REQUEST_BASE_JSON, + "extensions", + r#"{"prf":{"eval":{"first": "second"}}}"#, + ); + + let req: GetAssertionRequest = GetAssertionRequest::from_json(&rpid, &req_json).unwrap(); + if let Some(GetAssertionRequestExtensions { + hmac_or_prf: + Some(GetAssertionHmacOrPrfInput::Prf(PrfInput { + eval: Some(ref prf_value), + .. + })), + .. + }) = &req.extensions + { + assert_eq!(&prf_value.first[..], b"first"); + assert_eq!( + prf_value.second.as_ref().map(|s| &s[..]), + Some(&b"second"[..]) + ); + } else { + panic!("Expected PRF extension with correct values"); + } + } +} diff --git a/libwebauthn/src/ops/webauthn/idl/base64url.rs b/libwebauthn/src/ops/webauthn/idl/base64url.rs new file mode 100644 index 0000000..ebd805f --- /dev/null +++ b/libwebauthn/src/ops/webauthn/idl/base64url.rs @@ -0,0 +1,67 @@ +use std::ops::Deref; + +use base64_url; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq)] +pub struct Base64UrlString(pub Vec); + +impl From> for Base64UrlString { + fn from(bytes: Vec) -> Self { + Base64UrlString(bytes) + } +} + +impl From<&[u8]> for Base64UrlString { + fn from(bytes: &[u8]) -> Self { + Base64UrlString(bytes.to_vec()) + } +} + +impl Deref for Base64UrlString { + type Target = [u8]; + + fn deref(&self) -> &[u8] { + &self.0 + } +} + +impl AsRef<[u8]> for Base64UrlString { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +impl<'de> Deserialize<'de> for Base64UrlString { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let s: String = Deserialize::deserialize(deserializer)?; + base64_url::decode(&s) + .map_err(serde::de::Error::custom) + .map(|bytes| Base64UrlString(bytes)) + } +} + +impl Serialize for Base64UrlString { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let encoded = base64_url::encode(&self.0); + serializer.serialize_str(&encoded) + } +} + +impl From for Vec { + fn from(b64: Base64UrlString) -> Vec { + b64.0 + } +} + +impl Base64UrlString { + pub fn as_slice(&self) -> &[u8] { + &self.0 + } +} diff --git a/libwebauthn/src/ops/webauthn/idl/create.rs b/libwebauthn/src/ops/webauthn/idl/create.rs new file mode 100644 index 0000000..fb5444d --- /dev/null +++ b/libwebauthn/src/ops/webauthn/idl/create.rs @@ -0,0 +1,55 @@ +use super::Base64UrlString; +use crate::{ + ops::webauthn::{ + MakeCredentialsRequestExtensions, ResidentKeyRequirement, UserVerificationRequirement, + }, + proto::ctap2::{ + Ctap2CredentialType, Ctap2PublicKeyCredentialDescriptor, Ctap2PublicKeyCredentialRpEntity, + }, +}; + +use serde::Deserialize; + +/** + * https://www.w3.org/TR/webauthn-3/#sctn-parseCreationOptionsFromJSON + */ + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AuthenticatorSelectionCriteria { + pub authenticator_attachment: Option, + pub resident_key: Option, + #[serde(default)] + pub require_resident_key: bool, + #[serde(default = "default_user_verification")] + pub user_verification: UserVerificationRequirement, +} + +fn default_user_verification() -> UserVerificationRequirement { + UserVerificationRequirement::Preferred +} + +#[derive(Debug, Clone, PartialEq, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PublicKeyCredentialUserEntity { + pub id: Base64UrlString, + pub name: String, + pub display_name: String, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PublicKeyCredentialCreationOptionsJSON { + pub rp: Ctap2PublicKeyCredentialRpEntity, + pub user: PublicKeyCredentialUserEntity, + pub challenge: Base64UrlString, + #[serde(rename = "pubKeyCredParams")] + pub params: Vec, + pub timeout: Option, + pub exclude_credentials: Vec, + pub authenticator_selection: Option, + pub hints: Option>, + pub attestation: Option, + pub attestation_formats: Option>, + pub extensions: Option, +} diff --git a/libwebauthn/src/ops/webauthn/idl/get.rs b/libwebauthn/src/ops/webauthn/idl/get.rs new file mode 100644 index 0000000..83eaf63 --- /dev/null +++ b/libwebauthn/src/ops/webauthn/idl/get.rs @@ -0,0 +1,80 @@ +use std::collections::HashMap; + +use serde::Deserialize; +use serde_bytes::ByteBuf; + +use crate::{ + ops::webauthn::{Base64UrlString, UserVerificationRequirement}, + proto::ctap2::{ + Ctap2PublicKeyCredentialDescriptor, Ctap2PublicKeyCredentialType, Ctap2Transport, + }, +}; + +#[derive(Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct PublicKeyCredentialRequestOptionsJSON { + pub challenge: Base64UrlString, + pub timeout: Option, + #[serde(rename = "rpId")] + pub relying_party_id: Option, + #[serde(default)] + pub allow_credentials: Vec, + #[serde(rename = "userVerification", default)] + pub user_verification: UserVerificationRequirement, + #[serde(default)] + pub hints: Vec, + pub extensions: Option, +} + +#[derive(Debug, Clone, Deserialize, PartialEq)] +pub struct PublicKeyCredentialDescriptorJSON { + pub id: Base64UrlString, + pub r#type: Ctap2PublicKeyCredentialType, + + #[serde(skip_serializing_if = "Option::is_none")] + pub transports: Option>, +} + +impl From for Ctap2PublicKeyCredentialDescriptor { + fn from(value: PublicKeyCredentialDescriptorJSON) -> Self { + Ctap2PublicKeyCredentialDescriptor { + r#type: value.r#type, + id: ByteBuf::from(value.id), + transports: value.transports, + } + } +} + +#[derive(Debug, Clone, Default, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GetAssertionRequestExtensionsJSON { + #[serde(rename = "getCredBlob")] + pub cred_blob: Option, + #[serde(rename = "largeBlobKey")] + pub large_blob: Option, + pub hmac_get_secret: Option, + pub prf: Option, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct LargeBlobInputJson { + pub read: Option, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct PrfInputJson { + pub eval: Option, + pub eval_by_credential: Option>, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct PrfValuesJson { + pub first: Base64UrlString, + pub second: Option, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct HmacGetSecretInputJson { + pub salt1: Base64UrlString, + pub salt2: Option, +} diff --git a/libwebauthn/src/ops/webauthn/idl/mod.rs b/libwebauthn/src/ops/webauthn/idl/mod.rs new file mode 100644 index 0000000..c392756 --- /dev/null +++ b/libwebauthn/src/ops/webauthn/idl/mod.rs @@ -0,0 +1,39 @@ +mod base64url; +pub mod create; +pub mod get; +pub mod rpid; + +pub use base64url::Base64UrlString; + +use rpid::RelyingPartyId; + +use serde::de::DeserializeOwned; +use serde_json; + +pub type JsonError = serde_json::Error; + +pub trait WebAuthnIDL: Sized +where + E: std::error::Error, // Validation error type. + Self: FromInnerModel, +{ + /// An error type that can be returned when deserializing from JSON, including + /// JSON parsing errors and any additional validation errors. + type Error: std::error::Error + From + From; + + /// The JSON model that this IDL can deserialize from. + type InnerModel: DeserializeOwned; + + fn from_json(rpid: &RelyingPartyId, json: &str) -> Result { + let inner_model: Self::InnerModel = serde_json::from_str(json)?; + Self::from_inner_model(rpid, inner_model).map_err(From::from) + } +} + +pub trait FromInnerModel: Sized +where + T: DeserializeOwned, + E: std::error::Error, +{ + fn from_inner_model(rpid: &RelyingPartyId, inner: T) -> Result; +} diff --git a/libwebauthn/src/ops/webauthn/idl/rpid.rs b/libwebauthn/src/ops/webauthn/idl/rpid.rs new file mode 100644 index 0000000..b568714 --- /dev/null +++ b/libwebauthn/src/ops/webauthn/idl/rpid.rs @@ -0,0 +1,49 @@ +use serde::Deserialize; +use std::{convert::TryFrom, ops::Deref}; + +#[derive(Clone, Debug)] +pub struct RelyingPartyId(pub String); + +impl Deref for RelyingPartyId { + type Target = str; + + fn deref(&self) -> &str { + &self.0 + } +} + +impl From for String { + fn from(rpid: RelyingPartyId) -> String { + rpid.0 + } +} + +#[derive(thiserror::Error, Debug, Clone)] +// TODO(#137): Validate RelyingPartyId +pub enum Error { + #[error("Empty Relying Party ID is not allowed")] + EmptyRelyingPartyId, +} + +impl TryFrom<&str> for RelyingPartyId { + type Error = Error; + + fn try_from(value: &str) -> Result { + // TODO(#137): Validate RelyingPartyId, including IDNA normalization + // and checking for valid characters. + match value { + "" => Err(Error::EmptyRelyingPartyId), + _ => Ok(RelyingPartyId(value.to_string())), + } + } +} + +impl<'de> Deserialize<'de> for RelyingPartyId { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + RelyingPartyId::try_from(s.as_str()).map_err(serde::de::Error::custom) + } +} diff --git a/libwebauthn/src/ops/webauthn/make_credential.rs b/libwebauthn/src/ops/webauthn/make_credential.rs index 1526c98..8bbb7ec 100644 --- a/libwebauthn/src/ops/webauthn/make_credential.rs +++ b/libwebauthn/src/ops/webauthn/make_credential.rs @@ -2,11 +2,20 @@ use std::time::Duration; use ctap_types::ctap2::credential_management::CredentialProtectionPolicy as Ctap2CredentialProtectionPolicy; use serde::{Deserialize, Serialize}; +use serde_json::{self, Value as JsonValue}; use sha2::{Digest, Sha256}; use tracing::{debug, instrument, trace}; use crate::{ fido::AuthenticatorData, + ops::webauthn::{ + client_data::ClientData, + idl::{ + create::PublicKeyCredentialCreationOptionsJSON, Base64UrlString, FromInnerModel, + JsonError, WebAuthnIDL, + }, + Operation, RelyingPartyId, + }, proto::{ ctap1::{Ctap1RegisteredKey, Ctap1Version}, ctap2::{ @@ -18,6 +27,7 @@ use crate::{ }, }; +use super::timeout::DEFAULT_TIMEOUT; use super::{DowngradableRequest, RegisterRequest, UserVerificationRequirement}; #[derive(Debug, Clone)] @@ -62,22 +72,18 @@ impl MakeCredentialsResponseUnsignedExtensions { let mut hmac_create_secret = None; let mut prf = None; if let Some(signed_extensions) = signed_extensions { - (hmac_create_secret, prf) = if let Some(incoming_ext) = &request.extensions { - match &incoming_ext.hmac_or_prf { - MakeCredentialHmacOrPrfInput::None => (None, None), - MakeCredentialHmacOrPrfInput::HmacGetSecret => { - (signed_extensions.hmac_secret, None) - } - MakeCredentialHmacOrPrfInput::Prf => ( - None, - Some(MakeCredentialPrfOutput { - enabled: signed_extensions.hmac_secret, - }), - ), + if let Some(incoming_ext) = &request.extensions { + // hmacCreateSecret and prf can both be requested and returned independently. + // Both map to the same underlying CTAP2 hmac-secret extension. + if incoming_ext.hmac_create_secret.is_some() { + hmac_create_secret = signed_extensions.hmac_secret; } - } else { - (None, None) - }; + if incoming_ext.prf.is_some() { + prf = Some(MakeCredentialPrfOutput { + enabled: signed_extensions.hmac_secret, + }); + } + } } // credProps extension @@ -126,7 +132,12 @@ impl MakeCredentialsResponseUnsignedExtensions { // largeBlob extension // https://www.w3.org/TR/webauthn-3/#sctn-large-blob-extension - let large_blob = match &request.extensions.as_ref().map(|x| &x.large_blob) { + let large_blob = match &request + .extensions + .as_ref() + .and_then(|x| x.large_blob.as_ref()) + .map(|x| x.support) + { None | Some(MakeCredentialLargeBlobExtension::None) => None, // Not requested, so we don't give an answer Some(MakeCredentialLargeBlobExtension::Preferred) | Some(MakeCredentialLargeBlobExtension::Required) => { @@ -149,14 +160,16 @@ impl MakeCredentialsResponseUnsignedExtensions { } } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] pub enum ResidentKeyRequirement { Required, Preferred, + #[serde(other)] Discouraged, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct MakeCredentialRequest { pub hash: Vec, pub origin: String, @@ -175,22 +188,85 @@ pub struct MakeCredentialRequest { pub timeout: Duration, } -#[derive(Debug, Default, Clone)] -pub enum MakeCredentialHmacOrPrfInput { - #[default] - None, - HmacGetSecret, - Prf, - // The spec tells us that in theory, we could hand in - // an `eval` here, IF the CTAP2 would get an additional - // extension to handle that. There is no such CTAP-extension - // right now, so we don't expose it for now, as it would just - // be ignored anyways. - // https://w3c.github.io/webauthn/#prf - // "If eval is present and a future extension to [FIDO-CTAP] permits evaluation of the PRF at creation time, configure hmac-secret inputs accordingly: .." - // Prf { - // eval: Option, - // }, +impl FromInnerModel + for MakeCredentialRequest +{ + fn from_inner_model( + rpid: &RelyingPartyId, + inner: PublicKeyCredentialCreationOptionsJSON, + ) -> Result { + let resident_key = if inner + .authenticator_selection + .as_ref() + .map(|s| s.require_resident_key) + == Some(true) + { + Some(ResidentKeyRequirement::Required) + } else { + inner + .authenticator_selection + .as_ref() + .and_then(|s| s.resident_key) + }; + + let user_verification = inner + .authenticator_selection + .as_ref() + .map_or(UserVerificationRequirement::Discouraged, |s| { + s.user_verification + }); + + let timeout: Duration = inner + .timeout + .map(|s| Duration::from_millis(s.into())) + .unwrap_or(DEFAULT_TIMEOUT); + + let client_data_json = ClientData { + operation: Operation::MakeCredential, + challenge: inner.challenge.to_vec(), + origin: rpid.to_string(), + cross_origin: None, + top_origin: None, + }; + + Ok(Self { + hash: client_data_json.hash(), + origin: rpid.to_owned().into(), + relying_party: inner.rp, + user: inner.user.into(), + resident_key, + user_verification, + algorithms: inner.params, + exclude: if inner.exclude_credentials.is_empty() { + None + } else { + Some(inner.exclude_credentials) + }, + extensions: inner.extensions, + timeout: timeout, + }) + } +} + +#[derive(thiserror::Error, Debug)] +pub enum MakeCredentialRequestParsingError { + /// The client must throw an "EncodingError" DOMException. + #[error("Invalid JSON format: {0}")] + EncodingError(#[from] JsonError), +} + +impl WebAuthnIDL for MakeCredentialRequest { + type Error = MakeCredentialRequestParsingError; + type InnerModel = PublicKeyCredentialCreationOptionsJSON; +} + +#[derive(Debug, Clone, Deserialize, PartialEq)] +pub struct MakeCredentialPrfInput { + /// The `eval` field is parsed but not used during credential creation. + /// PRF evaluation only occurs during assertion (getAssertion), not registration. + /// We parse it here to accept valid WebAuthn JSON input without errors. + #[serde(rename = "eval")] + pub _eval: Option, } #[derive(Debug, Default, Clone, Serialize, PartialEq)] @@ -199,19 +275,19 @@ pub struct MakeCredentialPrfOutput { pub enabled: Option, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] pub struct CredentialProtectionExtension { pub policy: CredentialProtectionPolicy, pub enforce_policy: bool, } #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] pub enum CredentialProtectionPolicy { - #[serde(rename = "userVerificationOptional")] UserVerificationOptional = 1, #[serde(rename = "userVerificationOptionalWithCredentialIDList")] UserVerificationOptionalWithCredentialIDList = 2, - #[serde(rename = "userVerificationRequired")] UserVerificationRequired = 3, } @@ -254,13 +330,19 @@ pub struct CredentialPropsExtension { pub rk: Option, } -#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, Eq)] +#[derive(Debug, Default, Clone, Deserialize, PartialEq)] +pub struct MakeCredentialLargeBlobExtensionInput { + pub support: MakeCredentialLargeBlobExtension, +} + +#[derive(Debug, Default, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub enum MakeCredentialLargeBlobExtension { - #[default] - None, Preferred, Required, + #[default] + #[serde(other)] + None, } #[derive(Debug, Default, Clone, PartialEq, Eq, Serialize)] @@ -269,14 +351,16 @@ pub struct MakeCredentialLargeBlobExtensionOutput { pub supported: Option, } -#[derive(Debug, Default, Clone)] +#[derive(Debug, Default, Clone, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] pub struct MakeCredentialsRequestExtensions { pub cred_props: Option, pub cred_protect: Option, - pub cred_blob: Option>, - pub large_blob: MakeCredentialLargeBlobExtension, + pub cred_blob: Option, + pub large_blob: Option, pub min_pin_length: Option, - pub hmac_or_prf: MakeCredentialHmacOrPrfInput, + pub hmac_create_secret: Option, + pub prf: Option, } pub type MakeCredentialsResponseExtensions = Ctap2MakeCredentialsResponseExtensions; @@ -367,3 +451,179 @@ impl DowngradableRequest for MakeCredentialRequest { Ok(downgraded) } } + +#[cfg(test)] +mod tests { + use std::time::Duration; + + use crate::ops::webauthn::MakeCredentialRequest; + use crate::ops::webauthn::RelyingPartyId; + use crate::proto::ctap2::Ctap2PublicKeyCredentialType; + + use super::*; + + pub const REQUEST_BASE_JSON: &str = r#" + { + "rp": { + "id": "example.org", + "name": "example.org" + }, + "user": { + "id": "dXNlcmlk", + "name": "mario.rossi", + "displayName": "Mario Rossi" + }, + "challenge": "Y3JlZGVudGlhbHMtZm9yLWxpbnV4L2xpYndlYmF1dGhu", + "pubKeyCredParams": [ + { + "type": "public-key", + "alg": -7 + } + ], + "timeout": 30000, + "excludeCredentials": [], + "authenticatorSelection": { + "residentKey": "discouraged", + "userVerification": "preferred" + }, + "attestation": "none", + "attestationFormats": ["packed", "fido-u2f"] + } + "#; + + fn request_base() -> MakeCredentialRequest { + MakeCredentialRequest { + origin: "example.org".to_string(), + hash: ClientData { + operation: Operation::MakeCredential, + challenge: base64_url::decode("Y3JlZGVudGlhbHMtZm9yLWxpbnV4L2xpYndlYmF1dGhu") + .unwrap(), + origin: "example.org".to_string(), + cross_origin: None, + top_origin: None, + } + .hash(), + relying_party: Ctap2PublicKeyCredentialRpEntity::new("example.org", "example.org"), + user: Ctap2PublicKeyCredentialUserEntity::new(b"userid", "mario.rossi", "Mario Rossi"), + resident_key: Some(ResidentKeyRequirement::Discouraged), + user_verification: UserVerificationRequirement::Preferred, + algorithms: vec![Ctap2CredentialType::default()], + exclude: None, + extensions: None, + timeout: Duration::from_secs(30), + } + } + + fn json_field_add(str: &str, field: &str, value: &str) -> String { + let mut v: serde_json::Value = serde_json::from_str(str).unwrap(); + v.as_object_mut() + .unwrap() + .insert(field.to_owned(), serde_json::from_str(value).unwrap()); + serde_json::to_string(&v).unwrap() + } + + fn json_field_rm(str: &str, field: &str) -> String { + let mut v: serde_json::Value = serde_json::from_str(str).unwrap(); + v.as_object_mut().unwrap().remove(field); + serde_json::to_string(&v).unwrap() + } + + fn test_request_from_json_required_field(field: &str) { + let rpid = RelyingPartyId::try_from("example.org").unwrap(); + let req_json = json_field_rm(REQUEST_BASE_JSON, field); + + let result = MakeCredentialRequest::from_json(&rpid, &req_json); + assert!(matches!( + result, + Err(MakeCredentialRequestParsingError::EncodingError(_)) + )); + } + + #[test] + fn test_request_from_json_base() { + let rpid = RelyingPartyId::try_from("example.org").unwrap(); + let req: MakeCredentialRequest = + MakeCredentialRequest::from_json(&rpid, REQUEST_BASE_JSON).unwrap(); + assert_eq!(req, request_base()); + } + + #[test] + fn test_request_from_json_require_rp() { + test_request_from_json_required_field("rp"); + } + + #[test] + fn test_request_from_json_require_user() { + test_request_from_json_required_field("user"); + } + + #[test] + fn test_request_from_json_require_pub_key_cred_params() { + test_request_from_json_required_field("pubKeyCredParams"); + } + + #[test] + fn test_request_from_json_require_challenge() { + test_request_from_json_required_field("challenge"); + } + + #[test] + #[ignore] // FIXME(#134): Add validation for challenges + fn test_request_from_json_challenge_empty() { + let rpid = RelyingPartyId::try_from("example.org").unwrap(); + let req_json: String = json_field_rm(REQUEST_BASE_JSON, "challenge"); + let req_json = json_field_add(&req_json, "challenge", r#""""#); + + let result = MakeCredentialRequest::from_json(&rpid, &req_json); + assert!(matches!( + result, + Err(MakeCredentialRequestParsingError::EncodingError(_)) + )); + } + + #[test] + fn test_request_from_json_prf_extension() { + let rpid = RelyingPartyId::try_from("example.org").unwrap(); + let req_json = json_field_add( + REQUEST_BASE_JSON, + "extensions", + r#"{"prf": {"eval": {"first": "second"}}}"#, + ); + + let req: MakeCredentialRequest = + MakeCredentialRequest::from_json(&rpid, &req_json).unwrap(); + assert!(matches!( + req.extensions, + Some(MakeCredentialsRequestExtensions { prf: Some(_), .. }) + )); + } + + #[test] + fn test_request_from_json_unknown_pub_key_cred_params() { + let rpid = RelyingPartyId::try_from("example.org").unwrap(); + let req_json = json_field_add( + REQUEST_BASE_JSON, + "pubKeyCredParams", + r#"[{"type": "something", "alg": -12345}]"#, + ); + let req: MakeCredentialRequest = + MakeCredentialRequest::from_json(&rpid, &req_json).unwrap(); + assert_eq!( + req.algorithms, + vec![Ctap2CredentialType { + algorithm: Ctap2COSEAlgorithmIdentifier::Unknown, // FIXME(#148): Passhtrough unknown algorithms + public_key_type: Ctap2PublicKeyCredentialType::Unknown, + }] + ); + } + + #[test] + fn test_request_from_json_default_timeout() { + let rpid = RelyingPartyId::try_from("example.org").unwrap(); + let req_json = json_field_rm(REQUEST_BASE_JSON, "timeout"); + + let req: MakeCredentialRequest = + MakeCredentialRequest::from_json(&rpid, &req_json).unwrap(); + assert_eq!(req.timeout, DEFAULT_TIMEOUT); + } +} diff --git a/libwebauthn/src/ops/webauthn.rs b/libwebauthn/src/ops/webauthn/mod.rs similarity index 83% rename from libwebauthn/src/ops/webauthn.rs rename to libwebauthn/src/ops/webauthn/mod.rs index 5566576..b05b645 100644 --- a/libwebauthn/src/ops/webauthn.rs +++ b/libwebauthn/src/ops/webauthn/mod.rs @@ -1,5 +1,8 @@ +mod client_data; mod get_assertion; +pub mod idl; mod make_credential; +mod timeout; use super::u2f::{RegisterRequest, SignRequest}; use crate::webauthn::CtapError; @@ -8,21 +11,32 @@ pub use get_assertion::{ GetAssertionLargeBlobExtension, GetAssertionLargeBlobExtensionOutput, GetAssertionPrfOutput, GetAssertionRequest, GetAssertionRequestExtensions, GetAssertionResponse, GetAssertionResponseExtensions, GetAssertionResponseUnsignedExtensions, HMACGetSecretInput, - HMACGetSecretOutput, PRFValue, + HMACGetSecretOutput, PRFValue, PrfInput, }; +pub use idl::{rpid::RelyingPartyId, Base64UrlString, WebAuthnIDL}; pub use make_credential::{ CredentialPropsExtension, CredentialProtectionExtension, CredentialProtectionPolicy, - MakeCredentialHmacOrPrfInput, MakeCredentialLargeBlobExtension, - MakeCredentialLargeBlobExtensionOutput, MakeCredentialPrfOutput, MakeCredentialRequest, - MakeCredentialResponse, MakeCredentialsRequestExtensions, MakeCredentialsResponseExtensions, + MakeCredentialLargeBlobExtension, MakeCredentialLargeBlobExtensionOutput, + MakeCredentialPrfInput, MakeCredentialPrfOutput, MakeCredentialRequest, MakeCredentialResponse, + MakeCredentialsRequestExtensions, MakeCredentialsResponseExtensions, MakeCredentialsResponseUnsignedExtensions, ResidentKeyRequirement, }; +use serde::Deserialize; -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, Deserialize, PartialEq)] +pub enum Operation { + MakeCredential, + GetAssertion, +} + +#[derive(Debug, Clone, Copy, Default, Deserialize, PartialEq)] +#[serde(rename_all = "lowercase")] pub enum UserVerificationRequirement { Required, - Preferred, Discouraged, + #[default] + #[serde(other)] + Preferred, } impl UserVerificationRequirement { diff --git a/libwebauthn/src/ops/webauthn/timeout.rs b/libwebauthn/src/ops/webauthn/timeout.rs new file mode 100644 index 0000000..05ccdb0 --- /dev/null +++ b/libwebauthn/src/ops/webauthn/timeout.rs @@ -0,0 +1,3 @@ +use std::time::Duration; + +pub const DEFAULT_TIMEOUT: Duration = Duration::from_secs(60); diff --git a/libwebauthn/src/proto/ctap2/model.rs b/libwebauthn/src/proto/ctap2/model.rs index ebf219b..84373b4 100644 --- a/libwebauthn/src/proto/ctap2/model.rs +++ b/libwebauthn/src/proto/ctap2/model.rs @@ -1,5 +1,5 @@ -use crate::pin::PinUvAuthProtocol; use crate::proto::ctap1::Ctap1Transport; +use crate::{ops::webauthn::idl::create::PublicKeyCredentialUserEntity, pin::PinUvAuthProtocol}; use num_enum::{IntoPrimitive, TryFromPrimitive}; use serde_bytes::ByteBuf; @@ -116,6 +116,16 @@ impl Ctap2PublicKeyCredentialUserEntity { } } +impl From for Ctap2PublicKeyCredentialUserEntity { + fn from(user: PublicKeyCredentialUserEntity) -> Self { + Self { + id: ByteBuf::from(user.id), + name: Some(user.name), + display_name: Some(user.display_name), + } + } +} + #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] pub enum Ctap2PublicKeyCredentialType { #[serde(rename = "public-key")] @@ -146,7 +156,7 @@ impl From<&Ctap1Transport> for Ctap2Transport { } } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct Ctap2PublicKeyCredentialDescriptor { pub id: ByteBuf, pub r#type: Ctap2PublicKeyCredentialType, diff --git a/libwebauthn/src/proto/ctap2/model/get_assertion.rs b/libwebauthn/src/proto/ctap2/model/get_assertion.rs index 775d520..60996af 100644 --- a/libwebauthn/src/proto/ctap2/model/get_assertion.rs +++ b/libwebauthn/src/proto/ctap2/model/get_assertion.rs @@ -152,18 +152,19 @@ impl Ctap2GetAssertionRequest { ) -> Result { // Cloning it, so we can modify it let mut req = req.clone(); - if let Some(ext) = req.extensions.as_mut() { - // LargeBlob (NOTE: Not to be confused with LargeBlobKey) - // https://w3c.github.io/webauthn/#sctn-large-blob-extension - // If read is present and has the value true: - // [..] - // 3. If successful, set blob to the result. - // - // So we silently drop the extension if the device does not support it. - if !info.option_enabled("largeBlobs") { - ext.large_blob = GetAssertionLargeBlobExtension::None; + // LargeBlob (NOTE: Not to be confused with LargeBlobKey) + // https://w3c.github.io/webauthn/#sctn-large-blob-extension + // If read is present and has the value true: + // [..] + // 3. If successful, set blob to the result. + // + // So we silently drop the extension if the device does not support it. + if !info.option_enabled("largeBlobs") { + if let Some(ref mut ext) = req.extensions { + ext.large_blob = None; } } + Ok(Ctap2GetAssertionRequest::from(req)) } } @@ -174,7 +175,7 @@ impl From for Ctap2GetAssertionRequest { relying_party_id: op.relying_party_id, client_data_hash: ByteBuf::from(op.hash), allow: op.allow, - extensions: op.extensions.map(|x| x.into()), + extensions: op.extensions.map(|ext| ext.into()), options: Some(Ctap2GetAssertionOptions { require_user_presence: true, require_user_verification: op.user_verification.is_required(), @@ -185,11 +186,11 @@ impl From for Ctap2GetAssertionRequest { } } -#[derive(Debug, Default, Clone, Serialize)] +#[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub struct Ctap2GetAssertionRequestExtensions { - #[serde(skip_serializing_if = "Option::is_none")] - pub cred_blob: Option, + #[serde(skip_serializing_if = "std::ops::Not::not")] + pub cred_blob: bool, // Thanks, FIDO-spec for this consistent naming scheme... #[serde(rename = "hmac-secret", skip_serializing_if = "Option::is_none")] pub hmac_secret: Option, @@ -197,16 +198,16 @@ pub struct Ctap2GetAssertionRequestExtensions { #[serde(skip_serializing_if = "Option::is_none")] pub large_blob_key: Option, #[serde(skip)] - pub hmac_or_prf: GetAssertionHmacOrPrfInput, + pub hmac_or_prf: Option, } impl From for Ctap2GetAssertionRequestExtensions { fn from(other: GetAssertionRequestExtensions) -> Self { Ctap2GetAssertionRequestExtensions { cred_blob: other.cred_blob, - hmac_secret: None, // Get's calculated later + hmac_secret: None, // Gets calculated later hmac_or_prf: other.hmac_or_prf, - large_blob_key: if other.large_blob == GetAssertionLargeBlobExtension::Read { + large_blob_key: if other.large_blob == Some(GetAssertionLargeBlobExtension::Read) { Some(true) } else { None @@ -217,7 +218,10 @@ impl From for Ctap2GetAssertionRequestExtensions impl Ctap2GetAssertionRequestExtensions { pub fn skip_serializing(&self) -> bool { - self.cred_blob.is_none() && self.hmac_secret.is_none() + !self.cred_blob + && self.hmac_secret.is_none() + && self.large_blob_key.is_none() + && self.hmac_or_prf.is_none() } pub fn calculate_hmac( @@ -226,14 +230,13 @@ impl Ctap2GetAssertionRequestExtensions { auth_data: &AuthTokenData, ) -> Result<(), Error> { let input = match &self.hmac_or_prf { - GetAssertionHmacOrPrfInput::None => None, - GetAssertionHmacOrPrfInput::HmacGetSecret(hmacget_secret_input) => { - Some(hmacget_secret_input.clone()) + None => None, + Some(GetAssertionHmacOrPrfInput::HmacGetSecret(hmac_get_secret_input)) => { + Some(hmac_get_secret_input.clone()) + } + Some(GetAssertionHmacOrPrfInput::Prf(prf_input)) => { + Self::prf_to_hmac_input(&prf_input.eval, &prf_input.eval_by_credential, allow_list)? } - GetAssertionHmacOrPrfInput::Prf { - eval, - eval_by_credential, - } => Self::prf_to_hmac_input(eval, eval_by_credential, allow_list)?, }; let input = match input { @@ -451,7 +454,7 @@ impl Ctap2UserVerifiableRequest for Ctap2GetAssertionRequest { let hmac_requested = self .extensions .as_ref() - .map(|e| !matches!(e.hmac_or_prf, GetAssertionHmacOrPrfInput::None)) + .map(|e| e.hmac_or_prf.is_some()) .unwrap_or_default(); hmac_requested && hmac_supported } @@ -506,44 +509,37 @@ impl Ctap2GetAssertionResponseExtensions { response: &Ctap2GetAssertionResponse, auth_data: Option<&AuthTokenData>, ) -> GetAssertionResponseUnsignedExtensions { - let (hmac_get_secret, prf) = if let Some(orig_ext) = &request.extensions { - // Decrypt the raw HMAC extension - let decrypted_hmac = self.hmac_secret.as_ref().and_then(|x| { - if let Some(auth_data) = auth_data { - let uv_proto = auth_data.protocol_version.create_protocol_object(); - x.decrypt_output(&auth_data.shared_secret, &uv_proto) - } else { - None - } - }); - if let Some(decrypted) = decrypted_hmac { - // Repackaging it into output - match &orig_ext.hmac_or_prf { - GetAssertionHmacOrPrfInput::None => (None, None), - GetAssertionHmacOrPrfInput::HmacGetSecret(..) => (Some(decrypted), None), - GetAssertionHmacOrPrfInput::Prf { .. } => ( - None, - Some(GetAssertionPrfOutput { - results: Some(PRFValue { - first: decrypted.output1, - second: decrypted.output2, - }), - }), - ), - } + let decrypted_hmac = self.hmac_secret.as_ref().and_then(|x| { + if let Some(auth_data) = auth_data { + let uv_proto = auth_data.protocol_version.create_protocol_object(); + x.decrypt_output(&auth_data.shared_secret, &uv_proto) } else { - (None, None) + None + } + }); + + let (hmac_get_secret, prf) = if let Some(decrypted) = decrypted_hmac { + match request.extensions.as_ref().and_then(|ext| ext.hmac_or_prf.as_ref()) { + None => (None, None), + Some(GetAssertionHmacOrPrfInput::HmacGetSecret(..)) => (Some(decrypted), None), + Some(GetAssertionHmacOrPrfInput::Prf(..)) => ( + None, + Some(GetAssertionPrfOutput { + results: Some(PRFValue { + first: decrypted.output1, + second: decrypted.output2, + }), + }), + ), } } else { (None, None) }; // LargeBlobs was requested - let large_blob = request - .extensions - .as_ref() - .filter(|x| x.large_blob != GetAssertionLargeBlobExtension::None) - .map(|_| { + let large_blob = match request.extensions.as_ref().and_then(|ext| ext.large_blob.as_ref()) { + None => None, + Some(GetAssertionLargeBlobExtension::Read) => { Some(GetAssertionLargeBlobExtensionOutput { blob: response .large_blob_key @@ -552,8 +548,8 @@ impl Ctap2GetAssertionResponseExtensions { // Not yet supported // written: None, }) - }) - .unwrap_or_default(); + } + }; GetAssertionResponseUnsignedExtensions { hmac_get_secret, diff --git a/libwebauthn/src/proto/ctap2/model/make_credential.rs b/libwebauthn/src/proto/ctap2/model/make_credential.rs index afc2d02..739dc9f 100644 --- a/libwebauthn/src/proto/ctap2/model/make_credential.rs +++ b/libwebauthn/src/proto/ctap2/model/make_credential.rs @@ -7,8 +7,8 @@ use super::{ use crate::{ fido::AuthenticatorData, ops::webauthn::{ - CredentialProtectionPolicy, MakeCredentialHmacOrPrfInput, MakeCredentialLargeBlobExtension, - MakeCredentialRequest, MakeCredentialResponse, MakeCredentialsRequestExtensions, + CredentialProtectionPolicy, MakeCredentialLargeBlobExtension, MakeCredentialRequest, + MakeCredentialResponse, MakeCredentialsRequestExtensions, MakeCredentialsResponseUnsignedExtensions, ResidentKeyRequirement, }, pin::PinUvAuthProtocol, @@ -225,8 +225,12 @@ impl Ctap2MakeCredentialsRequestExtensions { // LargeBlob (NOTE: Not to be confused with LargeBlobKey. LargeBlob has "Preferred" as well) // https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API/WebAuthn_extensions#largeblob // - let large_blob_key = match requested_extensions.large_blob { - MakeCredentialLargeBlobExtension::Required => { + let large_blob_key = match requested_extensions + .large_blob + .as_ref() + .map(|info| info.support) + { + Some(MakeCredentialLargeBlobExtension::Required) => { // "required": The credential will be created with an authenticator to store blobs. The create() call will fail if this is impossible. if !info.option_enabled("largeBlobs") { warn!("This request will potentially fail. Large blob extension required, but device does not support it."); @@ -235,7 +239,7 @@ impl Ctap2MakeCredentialsRequestExtensions { // We only add a warning for easier debugging. Some(true) } - MakeCredentialLargeBlobExtension::Preferred => { + Some(MakeCredentialLargeBlobExtension::Preferred) => { if info.option_enabled("largeBlobs") { Some(true) } else { @@ -244,19 +248,23 @@ impl Ctap2MakeCredentialsRequestExtensions { None } } - MakeCredentialLargeBlobExtension::None => None, + _ => None, }; // HMAC Secret - let hmac_secret = match requested_extensions.hmac_or_prf { - MakeCredentialHmacOrPrfInput::None => None, - MakeCredentialHmacOrPrfInput::HmacGetSecret | MakeCredentialHmacOrPrfInput::Prf => { - Some(true) - } + let hmac_secret = if requested_extensions.hmac_create_secret == Some(true) + || requested_extensions.prf.is_some() + { + Some(true) + } else { + None }; Ok(Ctap2MakeCredentialsRequestExtensions { - cred_blob: requested_extensions.cred_blob.clone(), + cred_blob: requested_extensions + .cred_blob + .as_ref() + .map(|inner| inner.0.clone()), hmac_secret, cred_protect: requested_extensions .cred_protect diff --git a/libwebauthn/src/tests/basic_ctap2.rs b/libwebauthn/src/tests/basic_ctap2.rs index c134410..c383826 100644 --- a/libwebauthn/src/tests/basic_ctap2.rs +++ b/libwebauthn/src/tests/basic_ctap2.rs @@ -1,6 +1,6 @@ use std::time::Duration; -use crate::ops::webauthn::GetAssertionRequest; +use crate::ops::webauthn::{GetAssertionRequest, GetAssertionRequestExtensions}; use crate::proto::ctap2::Ctap2PublicKeyCredentialDescriptor; use crate::transport::hid::get_virtual_device; use crate::transport::{Channel, Device}; @@ -66,7 +66,7 @@ async fn test_webauthn_basic_ctap2() { hash: Vec::from(challenge), allow: vec![credential], user_verification: UserVerificationRequirement::Discouraged, - extensions: None, + extensions: Some(GetAssertionRequestExtensions::default()), timeout: TIMEOUT, }; diff --git a/libwebauthn/src/tests/prf.rs b/libwebauthn/src/tests/prf.rs index 97f135a..95666b4 100644 --- a/libwebauthn/src/tests/prf.rs +++ b/libwebauthn/src/tests/prf.rs @@ -3,8 +3,8 @@ use std::time::Duration; use crate::ops::webauthn::{ GetAssertionHmacOrPrfInput, GetAssertionRequest, GetAssertionRequestExtensions, - MakeCredentialHmacOrPrfInput, MakeCredentialPrfOutput, MakeCredentialsRequestExtensions, - PRFValue, + MakeCredentialPrfInput, MakeCredentialPrfOutput, MakeCredentialsRequestExtensions, PRFValue, + PrfInput, }; use crate::pin::PinManagement; use crate::proto::ctap2::{Ctap2PinUvAuthProtocol, Ctap2PublicKeyCredentialDescriptor}; @@ -101,7 +101,7 @@ async fn run_test_battery(channel: &mut HidChannel<'_>, using_pin: bool) { let challenge: [u8; 32] = thread_rng().gen(); let extensions = MakeCredentialsRequestExtensions { - hmac_or_prf: MakeCredentialHmacOrPrfInput::Prf, + prf: Some(MakeCredentialPrfInput { _eval: None }), ..Default::default() }; @@ -192,10 +192,10 @@ async fn run_test_battery(channel: &mut HidChannel<'_>, using_pin: bool) { second: None, }, ); - let hmac_or_prf = GetAssertionHmacOrPrfInput::Prf { + let hmac_or_prf = GetAssertionHmacOrPrfInput::Prf(PrfInput { eval, eval_by_credential, - }; + }); run_success_test( channel, &credential, @@ -221,10 +221,10 @@ async fn run_test_battery(channel: &mut HidChannel<'_>, using_pin: bool) { second: None, }, ); - let hmac_or_prf = GetAssertionHmacOrPrfInput::Prf { + let hmac_or_prf = GetAssertionHmacOrPrfInput::Prf(PrfInput { eval, eval_by_credential, - }; + }); run_success_test( channel, &credential, @@ -243,10 +243,10 @@ async fn run_test_battery(channel: &mut HidChannel<'_>, using_pin: bool) { }); let eval_by_credential = HashMap::new(); - let hmac_or_prf = GetAssertionHmacOrPrfInput::Prf { + let hmac_or_prf = GetAssertionHmacOrPrfInput::Prf(PrfInput { eval, eval_by_credential, - }; + }); run_success_test( channel, &credential, @@ -293,10 +293,10 @@ async fn run_test_battery(channel: &mut HidChannel<'_>, using_pin: bool) { second: Some([7; 32]), }, ); - let hmac_or_prf = GetAssertionHmacOrPrfInput::Prf { + let hmac_or_prf = GetAssertionHmacOrPrfInput::Prf(PrfInput { eval, eval_by_credential, - }; + }); run_success_test( channel, &credential, @@ -336,10 +336,10 @@ async fn run_test_battery(channel: &mut HidChannel<'_>, using_pin: bool) { second: Some([8; 32]), }, ); - let hmac_or_prf = GetAssertionHmacOrPrfInput::Prf { + let hmac_or_prf = GetAssertionHmacOrPrfInput::Prf(PrfInput { eval, eval_by_credential, - }; + }); run_success_test( channel, &credential, @@ -376,10 +376,10 @@ async fn run_test_battery(channel: &mut HidChannel<'_>, using_pin: bool) { second: Some([8; 32]), }, ); - let hmac_or_prf = GetAssertionHmacOrPrfInput::Prf { + let hmac_or_prf = GetAssertionHmacOrPrfInput::Prf(PrfInput { eval, eval_by_credential, - }; + }); run_success_test( channel, &credential, @@ -405,10 +405,10 @@ async fn run_test_battery(channel: &mut HidChannel<'_>, using_pin: bool) { second: None, }, ); - let hmac_or_prf = GetAssertionHmacOrPrfInput::Prf { + let hmac_or_prf = GetAssertionHmacOrPrfInput::Prf(PrfInput { eval, eval_by_credential, - }; + }); run_failed_test( channel, Some(&credential), @@ -429,10 +429,10 @@ async fn run_test_battery(channel: &mut HidChannel<'_>, using_pin: bool) { second: None, }, ); - let hmac_or_prf = GetAssertionHmacOrPrfInput::Prf { + let hmac_or_prf = GetAssertionHmacOrPrfInput::Prf(PrfInput { eval, eval_by_credential, - }; + }); run_failed_test( channel, Some(&credential), @@ -453,10 +453,10 @@ async fn run_test_battery(channel: &mut HidChannel<'_>, using_pin: bool) { second: None, }, ); - let hmac_or_prf = GetAssertionHmacOrPrfInput::Prf { + let hmac_or_prf = GetAssertionHmacOrPrfInput::Prf(PrfInput { eval, eval_by_credential, - }; + }); run_failed_test( channel, None, @@ -487,7 +487,7 @@ async fn run_success_test( allow: vec![credential.clone()], user_verification: UserVerificationRequirement::Preferred, extensions: Some(GetAssertionRequestExtensions { - hmac_or_prf, + hmac_or_prf: Some(hmac_or_prf), ..Default::default() }), timeout: TIMEOUT, @@ -552,7 +552,7 @@ async fn run_failed_test( allow: credential.map(|x| vec![x.clone()]).unwrap_or_default(), user_verification: UserVerificationRequirement::Discouraged, extensions: Some(GetAssertionRequestExtensions { - hmac_or_prf, + hmac_or_prf: Some(hmac_or_prf), ..Default::default() }), timeout: TIMEOUT, diff --git a/libwebauthn/src/webauthn/pin_uv_auth_token.rs b/libwebauthn/src/webauthn/pin_uv_auth_token.rs index 86bd16d..0072bf7 100644 --- a/libwebauthn/src/webauthn/pin_uv_auth_token.rs +++ b/libwebauthn/src/webauthn/pin_uv_auth_token.rs @@ -610,10 +610,12 @@ mod test { info_extensions.as_deref(), UserVerificationRequirement::Discouraged, Some(GetAssertionRequestExtensions { - hmac_or_prf: GetAssertionHmacOrPrfInput::HmacGetSecret(HMACGetSecretInput { - salt1: [0; 32], - salt2: None, - }), + hmac_or_prf: Some(GetAssertionHmacOrPrfInput::HmacGetSecret( + HMACGetSecretInput { + salt1: [0; 32], + salt2: None, + }, + )), ..Default::default() }), Ok(UsedPinUvAuthToken::None), @@ -653,10 +655,12 @@ mod test { Some(&["hmac-secret"]), UserVerificationRequirement::Preferred, Some(GetAssertionRequestExtensions { - hmac_or_prf: GetAssertionHmacOrPrfInput::HmacGetSecret(HMACGetSecretInput { - salt1: [0; 32], - salt2: None, - }), + hmac_or_prf: Some(GetAssertionHmacOrPrfInput::HmacGetSecret( + HMACGetSecretInput { + salt1: [0; 32], + salt2: None, + }, + )), ..Default::default() }), Ok(UsedPinUvAuthToken::LegacyUV), @@ -722,10 +726,12 @@ mod test { for (info_options, uv_requirement) in testcases { let extensions = Some(GetAssertionRequestExtensions { - hmac_or_prf: GetAssertionHmacOrPrfInput::HmacGetSecret(HMACGetSecretInput { - salt1: [0; 32], - salt2: None, - }), + hmac_or_prf: Some(GetAssertionHmacOrPrfInput::HmacGetSecret( + HMACGetSecretInput { + salt1: [0; 32], + salt2: None, + }, + )), ..Default::default() }); @@ -789,10 +795,12 @@ mod test { for (info_options, uv_requirement) in testcases { let extensions = Some(GetAssertionRequestExtensions { - hmac_or_prf: GetAssertionHmacOrPrfInput::HmacGetSecret(HMACGetSecretInput { - salt1: [0; 32], - salt2: None, - }), + hmac_or_prf: Some(GetAssertionHmacOrPrfInput::HmacGetSecret( + HMACGetSecretInput { + salt1: [0; 32], + salt2: None, + }, + )), ..Default::default() }); @@ -903,10 +911,12 @@ mod test { for (info_options, uv_requirement) in testcases { let extensions = Some(GetAssertionRequestExtensions { - hmac_or_prf: GetAssertionHmacOrPrfInput::HmacGetSecret(HMACGetSecretInput { - salt1: [0; 32], - salt2: None, - }), + hmac_or_prf: Some(GetAssertionHmacOrPrfInput::HmacGetSecret( + HMACGetSecretInput { + salt1: [0; 32], + salt2: None, + }, + )), ..Default::default() });