diff --git a/.commitlintrc.json b/.commitlintrc.json index d3fcfc22..38c7b200 100644 --- a/.commitlintrc.json +++ b/.commitlintrc.json @@ -12,6 +12,7 @@ "docs", "feat", "fix", + "merge", "refactor", "release", "revert", diff --git a/.github/workflows/aur-publish.yml b/.github/workflows/aur-publish.yml index af313403..677040ef 100644 --- a/.github/workflows/aur-publish.yml +++ b/.github/workflows/aur-publish.yml @@ -43,8 +43,7 @@ jobs: | tar xvz -C /usr/local/bin .ci/generate-pkgbuild.rh ${{ matrix.package.pkgbuild }} - - name: Publish AUR package - uses: KSXGitHub/github-actions-deploy-aur@v4.1.3 + - uses: KSXGitHub/github-actions-deploy-aur@v4.1.3 with: pkgname: ${{ matrix.package.name }} pkgbuild: ./${{ matrix.package.pkgbuild }} diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index aab1c740..5ed06155 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -49,7 +49,7 @@ jobs: - uses: clechasseur/rs-clippy-check@v6.0.4 with: - args: --all-features -- --deny warnings + args: --features tpm -- --deny warnings build: strategy: @@ -73,19 +73,24 @@ jobs: - uses: Swatinem/rust-cache@v2 - name: Install Rust toolchain - uses: actions-rs/toolchain@v1 + uses: dtolnay/rust-toolchain@stable + with: + target: ${{ matrix.job.target }} + if: matrix.job.os != 'ubuntu-22.04' + + - name: Install Rust toolchain (pinned for older glibc) + uses: dtolnay/rust-toolchain@1.93.0 with: - toolchain: stable target: ${{ matrix.job.target }} - override: true + if: matrix.job.os == 'ubuntu-22.04' - name: Build shell: bash - run: cargo build --verbose + run: cargo build --verbose --features tpm - name: Run tests shell: bash - run: cargo test --verbose + run: cargo test --verbose --features tpm - name: Extract crate information shell: bash diff --git a/Cargo.lock b/Cargo.lock index 6e3cea4a..f1493f08 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,13 +2,19 @@ # It is not intended for manual editing. version = 4 +[[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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ - "crypto-common 0.1.7", + "crypto-common 0.1.6", "generic-array", ] @@ -18,16 +24,16 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ - "cfg-if", + "cfg-if 1.0.4", "cipher 0.4.4", "cpufeatures 0.2.17", ] [[package]] name = "aes" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66bd29a732b644c0431c6140f370d097879203d79b80c94a6747ba0872adaef8" +checksum = "f1fc76eaeac4c9164506c466d4ffdd8ec9d0c5bf57ee97177c4d8eceb3a0e138" dependencies = [ "cipher 0.5.2", "cpubits", @@ -57,6 +63,24 @@ dependencies = [ "memchr", ] +[[package]] +name = "aligned" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee4508988c62edf04abd8d92897fca0c2995d907ce1dfeaf369dac3716a40685" +dependencies = [ + "as-slice", +] + +[[package]] +name = "aligned-vec" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b" +dependencies = [ + "equator", +] + [[package]] name = "anstream" version = "1.0.0" @@ -74,9 +98,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" @@ -113,6 +137,47 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" + +[[package]] +name = "arg_enum_proc_macro" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "as-slice" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516" +dependencies = [ + "stable_deref_trait", +] + [[package]] name = "async-broadcast" version = "0.7.2" @@ -158,13 +223,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" dependencies = [ "autocfg", - "cfg-if", + "cfg-if 1.0.4", "concurrent-queue", "futures-io", "futures-lite", "parking", "polling", - "rustix", + "rustix 1.1.4", "slab", "windows-sys 0.61.2", ] @@ -192,10 +257,10 @@ dependencies = [ "async-signal", "async-task", "blocking", - "cfg-if", + "cfg-if 1.0.4", "event-listener", "futures-lite", - "rustix", + "rustix 1.1.4", ] [[package]] @@ -211,17 +276,17 @@ dependencies = [ [[package]] name = "async-signal" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43c070bbf59cd3570b6b2dd54cd772527c7c3620fce8be898406dd3ed6adc64c" +checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485" dependencies = [ "async-io", "async-lock", "atomic-waker", - "cfg-if", + "cfg-if 1.0.4", "futures-core", "futures-io", - "rustix", + "rustix 1.1.4", "signal-hook-registry", "slab", "windows-sys 0.61.2", @@ -263,9 +328,52 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.5.0" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "av-scenechange" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f321d77c20e19b92c39e7471cf986812cbb46659d2af674adc4331ef3f18394" +dependencies = [ + "aligned", + "anyhow", + "arg_enum_proc_macro", + "arrayvec", + "log", + "num-rational", + "num-traits", + "pastey", + "rayon", + "thiserror 2.0.18", + "v_frame", + "y4m", +] + +[[package]] +name = "av1-grain" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cfddb07216410377231960af4fcab838eaa12e013417781b78bd95ee22077f8" +dependencies = [ + "anyhow", + "arrayvec", + "log", + "nom 8.0.0", + "num-rational", + "v_frame", +] + +[[package]] +name = "avif-serialize" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "e7178fe5f7d460b13895ebb9dcb28a3a6216d2df2574a0806cb51b555d297f38" +dependencies = [ + "arrayvec", +] [[package]] name = "base16ct" @@ -285,6 +393,35 @@ version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" +[[package]] +name = "bindgen" +version = "0.65.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfdf7b466f9a4903edc73f95d6d2bcd5baf8ae620638762244d3f60143643cc5" +dependencies = [ + "bitflags 1.3.2", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex 1.3.0", + "syn 2.0.117", + "which 4.4.2", +] + +[[package]] +name = "bit_field" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" + [[package]] name = "bitfield" version = "0.19.4" @@ -307,9 +444,30 @@ dependencies = [ [[package]] name = "bitflags" -version = "2.11.0" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" + +[[package]] +name = "bitstream-io" +version = "4.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eff00be299a18769011411c9def0d827e8f2d7bf0c3dbf53633147a8867fd1f" +dependencies = [ + "no_std_io2", +] + +[[package]] +name = "block" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" [[package]] name = "block-buffer" @@ -360,11 +518,66 @@ dependencies = [ "piper", ] +[[package]] +name = "bon" +version = "3.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f47dbe92550676ee653353c310dfb9cf6ba17ee70396e1f7cf0a2020ad49b2fe" +dependencies = [ + "bon-macros", + "rustversion", +] + +[[package]] +name = "bon-macros" +version = "3.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "519bd3116aeeb42d5372c29d982d16d0170d3d4a5ed85fc7dd91642ffff3c67c" +dependencies = [ + "darling", + "ident_case", + "prettyplease", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.117", +] + +[[package]] +name = "built" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c0e531d93d39c34eef561e929e8a7f86d77a5af08aac4f6d6e39976c51858e9" + [[package]] name = "bumpalo" -version = "3.20.2" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + +[[package]] +name = "bytes" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "cbc" @@ -386,16 +599,31 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.56" +version = "1.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" +checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f" dependencies = [ "find-msvc-tools", "jobserver", "libc", - "shlex", + "shlex 2.0.1", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom 7.1.3", ] +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + [[package]] name = "cfg-if" version = "1.0.4" @@ -441,7 +669,7 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ - "crypto-common 0.1.7", + "crypto-common 0.1.6", "inout 0.1.4", ] @@ -455,6 +683,17 @@ dependencies = [ "inout 0.2.2", ] +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "clap" version = "4.6.1" @@ -522,21 +761,55 @@ dependencies = [ [[package]] name = "clap_lex" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "cmov" -version = "0.5.2" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" + +[[package]] +name = "cocoa" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c49e86fc36d5704151f5996b7b3795385f50ce09e3be0f47a0cfde869681cf8" +dependencies = [ + "bitflags 1.3.2", + "block", + "core-foundation 0.7.0", + "core-graphics", + "foreign-types", + "libc", + "objc", +] + +[[package]] +name = "cocoa-foundation" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81411967c50ee9a1fc11365f8c585f863a22a9697c89239c452292c40ba79b0d" +dependencies = [ + "bitflags 2.11.1", + "block", + "core-foundation 0.10.1", + "core-graphics-types", + "objc", +] + +[[package]] +name = "color_quant" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de0758edba32d61d1fd9f4d69491b47604b91ee2f7e6b33de7e54ca4ebe55dc3" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" [[package]] name = "colorchoice" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "concurrent-queue" @@ -547,6 +820,18 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "console" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d64e8af5551369d19cf50138de61f1c42074ab970f74e99be916646777f8fc87" +dependencies = [ + "encode_unicode", + "libc", + "unicode-width", + "windows-sys 0.61.2", +] + [[package]] name = "const-oid" version = "0.9.6" @@ -561,11 +846,12 @@ checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" [[package]] name = "const_format" -version = "0.2.35" +version = "0.2.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7faa7469a93a566e9ccc1c73fe783b4a65c274c5ace346038dca9c39fe0030ad" +checksum = "4481a617ad9a412be3b97c5d403fef8ed023103368908b9c50af598ff467cc1e" dependencies = [ "const_format_proc_macros", + "konst", ] [[package]] @@ -580,109 +866,256 @@ dependencies = [ ] [[package]] -name = "cpubits" -version = "0.1.1" +name = "cookie" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] [[package]] -name = "cpufeatures" -version = "0.2.17" +name = "cookie_store" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +checksum = "15b2c103cf610ec6cae3da84a766285b42fd16aad564758459e6ecf128c75206" dependencies = [ - "libc", + "cookie", + "document-features", + "idna", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "time", + "url", ] [[package]] -name = "cpufeatures" -version = "0.3.0" +name = "core-foundation" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +checksum = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171" dependencies = [ + "core-foundation-sys 0.7.0", "libc", ] [[package]] -name = "crossbeam-utils" -version = "0.8.21" +name = "core-foundation" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys 0.8.7", + "libc", +] [[package]] -name = "crunchy" -version = "0.2.4" +name = "core-foundation" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys 0.8.7", + "libc", +] [[package]] -name = "crypto-bigint" -version = "0.5.5" +name = "core-foundation-sys" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" -dependencies = [ - "generic-array", - "rand_core", - "subtle", - "zeroize", -] +checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" [[package]] -name = "crypto-common" -version = "0.1.7" +name = "core-foundation-sys" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" -dependencies = [ - "generic-array", - "rand_core", - "typenum", -] +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] -name = "crypto-common" -version = "0.2.2" +name = "core-graphics" +version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +checksum = "b3889374e6ea6ab25dba90bb5d96202f61108058361f6dc72e8b03e6f8bbe923" dependencies = [ - "hybrid-array", + "bitflags 1.3.2", + "core-foundation 0.7.0", + "foreign-types", + "libc", ] [[package]] -name = "ctr" -version = "0.9.2" +name = "core-graphics-types" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" dependencies = [ - "cipher 0.4.4", + "bitflags 2.11.1", + "core-foundation 0.10.1", + "libc", ] [[package]] -name = "ctrlc" -version = "3.5.2" +name = "core-media-sys" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0b1fab2ae45819af2d0731d60f2afe17227ebb1a1538a236da84c93e9a60162" +checksum = "273bf3fc5bf51fd06a7766a84788c1540b6527130a0bce39e00567d6ab9f31f1" dependencies = [ - "dispatch2", - "nix 0.31.3", - "windows-sys 0.61.2", + "cfg-if 0.1.10", + "core-foundation-sys 0.7.0", + "libc", ] [[package]] -name = "ctutils" -version = "0.4.0" +name = "core-video-sys" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1005a6d4446f5120ef475ad3d2af2b30c49c2c9c6904258e3bb30219bebed5e4" +checksum = "34ecad23610ad9757664d644e369246edde1803fcb43ed72876565098a5d3828" dependencies = [ - "cmov", + "cfg-if 0.1.10", + "core-foundation-sys 0.7.0", + "core-graphics", + "libc", + "metal", + "objc", ] [[package]] -name = "curve25519-dalek" -version = "4.1.3" +name = "cpubits" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if 1.0.4", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "typenum", +] + +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher 0.4.4", +] + +[[package]] +name = "ctrlc" +version = "3.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0b1fab2ae45819af2d0731d60f2afe17227ebb1a1538a236da84c93e9a60162" +dependencies = [ + "dispatch2", + "nix 0.31.3", + "windows-sys 0.61.2", +] + +[[package]] +name = "ctutils" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" +dependencies = [ + "cmov", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" dependencies = [ - "cfg-if", + "cfg-if 1.0.4", "cpufeatures 0.2.17", "curve25519-dalek-derive", "digest 0.10.7", @@ -744,7 +1177,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ "const-oid 0.9.6", - "pem-rfc7468", + "pem-rfc7468 0.7.0", + "zeroize", +] + +[[package]] +name = "der" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71fd89660b2dc699704064e59e9dba0147b903e85319429e131620d022be411b" +dependencies = [ + "pem-rfc7468 1.0.0", "zeroize", ] @@ -765,15 +1208,15 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", "const-oid 0.9.6", - "crypto-common 0.1.7", + "crypto-common 0.1.6", "subtle", ] [[package]] name = "digest" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4850db49bf08e663084f7fb5c87d202ef91a3907271aff24a94eb97ff039153c" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" dependencies = [ "block-buffer 0.12.0", "const-oid 0.10.2", @@ -808,7 +1251,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" dependencies = [ - "bitflags", + "bitflags 2.11.1", "block2", "libc", "objc2", @@ -816,22 +1259,37 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", "syn 2.0.117", ] +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "ecdsa" version = "0.16.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ - "der", + "der 0.7.10", "digest 0.10.7", "elliptic-curve", "rfc6979", @@ -863,6 +1321,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "either" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" + [[package]] name = "elliptic-curve" version = "0.13.8" @@ -876,14 +1340,29 @@ dependencies = [ "generic-array", "group", "hkdf 0.12.4", - "pem-rfc7468", + "pem-rfc7468 0.7.0", "pkcs8", - "rand_core", + "rand_core 0.6.4", "sec1", "subtle", "zeroize", ] +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if 1.0.4", +] + [[package]] name = "endi" version = "1.1.1" @@ -913,14 +1392,20 @@ dependencies = [ [[package]] name = "env_filter" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a1c3cc8e57274ec99de65301228b537f1e4eedc1b8e0f9411c6caac8ae7308f" +checksum = "32e90c2accc4b07a8456ea0debdc2e7587bdd890680d71173a15d4ae604f6eef" dependencies = [ "log", "regex", ] +[[package]] +name = "env_home" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe" + [[package]] name = "env_logger" version = "0.11.10" @@ -934,6 +1419,26 @@ dependencies = [ "log", ] +[[package]] +name = "equator" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc" +dependencies = [ + "equator-macro", +] + +[[package]] +name = "equator-macro" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -947,7 +1452,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -971,11 +1476,60 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "exr" +version = "1.74.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4300e043a56aa2cb633c01af81ca8f699a321879a7854d3896a0ba89056363be" +dependencies = [ + "bit_field", + "half 2.7.1", + "lebe", + "miniz_oxide", + "rayon-core", + "smallvec", + "zune-inflate", +] + +[[package]] +name = "face_id" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f54a3d8f37f8cb28471200c0b927848570018cb55fc3777d23c8e90fe067a8d" +dependencies = [ + "bon", + "hdbscan", + "hf-hub", + "image", + "nalgebra", + "ndarray", + "ort", + "rayon", + "serde", + "serde_json", + "thiserror 2.0.18", +] + [[package]] name = "fastrand" -version = "2.3.0" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + +[[package]] +name = "fax" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf1079563223d5d59d83c85886a56e586cfd5c1a26292e971a0fa266531ac5a" + +[[package]] +name = "fdeflate" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] [[package]] name = "ff" @@ -983,7 +1537,7 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" dependencies = [ - "rand_core", + "rand_core 0.6.4", "subtle", ] @@ -999,12 +1553,55 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "flume" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +dependencies = [ + "futures-core", + "futures-sink", + "nanorand", + "spin 0.9.8", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "foldhash" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -1014,12 +1611,48 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + [[package]] name = "futures-core" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + [[package]] name = "futures-io" version = "0.3.32" @@ -1040,34 +1673,76 @@ dependencies = [ ] [[package]] -name = "generic-array" -version = "0.14.7" +name = "futures-macro" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ - "typenum", - "version_check", - "zeroize", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] -name = "getrandom" -version = "0.2.17" +name = "futures-sink" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] -name = "getrandom" -version = "0.3.4" +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if 1.0.4", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ - "cfg-if", + "cfg-if 1.0.4", "libc", "r-efi 5.3.0", "wasip2", @@ -1079,7 +1754,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ - "cfg-if", + "cfg-if 1.0.4", "libc", "r-efi 6.0.0", "wasip2", @@ -1096,6 +1771,16 @@ dependencies = [ "polyval", ] +[[package]] +name = "gif" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee8cfcc411d9adbbaba82fb72661cc1bcca13e8bba98b364e62b2dba8f960159" +dependencies = [ + "color_quant", + "weezl", +] + [[package]] name = "git-state" version = "0.1.0" @@ -1108,7 +1793,7 @@ version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b88256088d75a56f8ecfa070513a775dd9107f6530ef14919dac831af9cfe2b" dependencies = [ - "bitflags", + "bitflags 2.11.1", "libc", "libgit2-sys", "log", @@ -1121,12 +1806,126 @@ version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddddbf932745a6be37109b6112d3ee09696106f848449069d3a57bba937ab82e" dependencies = [ - "bitflags", + "bitflags 2.11.1", "libc", "libgit2-sys", "log", ] +[[package]] +name = "glam" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "333928d5eb103c5d4050533cec0384302db6be8ef7d3cebd30ec6a35350353da" + +[[package]] +name = "glam" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3abb554f8ee44336b72d522e0a7fe86a29e09f839a36022fa869a7dfe941a54b" + +[[package]] +name = "glam" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4126c0479ccf7e8664c36a2d719f5f2c140fbb4f9090008098d2c291fa5b3f16" + +[[package]] +name = "glam" +version = "0.17.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01732b97afd8508eee3333a541b9f7610f454bb818669e66e90f5f57c93a776" + +[[package]] +name = "glam" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525a3e490ba77b8e326fb67d4b44b4bd2f920f44d4cc73ccec50adc68e3bee34" + +[[package]] +name = "glam" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b8509e6791516e81c1a630d0bd7fbac36d2fa8712a9da8662e716b52d5051ca" + +[[package]] +name = "glam" +version = "0.20.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43e957e744be03f5801a55472f593d43fabdebf25a4585db250f04d86b1675f" + +[[package]] +name = "glam" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "518faa5064866338b013ff9b2350dc318e14cc4fcd6cb8206d7e7c9886c98815" + +[[package]] +name = "glam" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f597d56c1bd55a811a1be189459e8fad2bbc272616375602443bdfb37fa774" + +[[package]] +name = "glam" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e4afd9ad95555081e109fe1d21f2a30c691b5f0919c67dfa690a2e1eb6bd51c" + +[[package]] +name = "glam" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5418c17512bdf42730f9032c74e1ae39afc408745ebb2acf72fbc4691c17945" + +[[package]] +name = "glam" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "151665d9be52f9bb40fc7966565d39666f2d1e69233571b71b87791c7e0528b3" + +[[package]] +name = "glam" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e05e7e6723e3455f4818c7b26e855439f7546cf617ef669d1adedb8669e5cb9" + +[[package]] +name = "glam" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "779ae4bf7e8421cf91c0b3b64e7e8b40b862fba4d393f59150042de7c4965a94" + +[[package]] +name = "glam" +version = "0.29.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8babf46d4c1c9d92deac9f7be466f76dfc4482b6452fc5024b5e8daf6ffeb3ee" + +[[package]] +name = "glam" +version = "0.30.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19fc433e8437a212d1b6f1e68c7824af3aed907da60afa994e7f542d18d12aa9" + +[[package]] +name = "glam" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556f6b2ea90b8d15a74e0e7bb41671c9bdf38cd9f78c284d750b9ce58a2b5be7" + +[[package]] +name = "glam" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f70749695b063ecbf6b62949ccccde2e733ec3ecbbd71d467dca4e5c6c97cca0" + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + [[package]] name = "group" version = "0.13.0" @@ -1134,10 +1933,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ "ff", - "rand_core", + "rand_core 0.6.4", "subtle", ] +[[package]] +name = "h2" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "half" version = "1.8.3" @@ -1150,7 +1968,7 @@ version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ - "cfg-if", + "cfg-if 1.0.4", "crunchy", "zerocopy", ] @@ -1166,9 +1984,19 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.16.1" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "hdbscan" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "466aa8bfaa22c81efcf67c84e6ad046de13a05c6cc9d41a5094361bff464247a" +dependencies = [ + "kdtree", + "num-traits", +] [[package]] name = "heck" @@ -1197,14 +2025,38 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hf-hub" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef3982638978efa195ff11b305f51f1f22f4f0a6cabee7af79b383ebee6a213" +dependencies = [ + "dirs", + "futures", + "http", + "indicatif", + "libc", + "log", + "native-tls", + "num_cpus", + "rand 0.9.4", + "reqwest", + "serde", + "serde_json", + "thiserror 2.0.18", + "tokio", + "ureq", + "windows-sys 0.61.2", +] + [[package]] name = "hidapi" -version = "2.6.5" +version = "2.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1b71e1f4791fb9e93b9d7ee03d70b501ab48f6151432fbcadeabc30fe15396e" +checksum = "c78dadfc12f865bc3fcac3897e64533b930737ceb9ef245c8277de98d0b010e9" dependencies = [ "cc", - "cfg-if", + "cfg-if 1.0.4", "libc", "pkg-config", "windows-sys 0.61.2", @@ -1243,7 +2095,22 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" dependencies = [ - "digest 0.11.2", + "digest 0.11.3", +] + +[[package]] +name = "hmac-sha256" +version = "1.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec9d92d097f4749b64e8cc33d924d9f40a2d4eb91402b458014b781f5733d60f" + +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", ] [[package]] @@ -1252,23 +2119,140 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f558a64ac9af88b5ba400d99b579451af0d39c6d360980045b91aac966d705e2" +[[package]] +name = "http" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be7462df143984c4598a256ef469b251d7d7f9e271135073e78fc535414f3d0" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + [[package]] name = "hybrid-array" -version = "0.4.8" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8655f91cd07f2b9d0c24137bd650fe69617773435ee5ec83022377777ce65ef1" +checksum = "9155a582abd142abc056962c29e3ce5ff2ad5469f4246b537ed42c5deba857da" dependencies = [ "typenum", ] +[[package]] +name = "hyper" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb92f162bf56536459fc83c79b974bb12837acfed43d6bc370a7916d0ae15ecc" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "system-configuration", + "tokio", + "tower-service", + "tracing", + "windows-registry", +] + [[package]] name = "icu_collections" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" dependencies = [ "displaydoc", "potential_utf", + "utf8_iter", "yoke", "zerofrom", "zerovec", @@ -1276,9 +2260,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" dependencies = [ "displaydoc", "litemap", @@ -1289,9 +2273,9 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" dependencies = [ "icu_collections", "icu_normalizer_data", @@ -1303,15 +2287,15 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" [[package]] name = "icu_properties" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" dependencies = [ "icu_collections", "icu_locale_core", @@ -1323,15 +2307,15 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" [[package]] name = "icu_provider" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" dependencies = [ "displaydoc", "icu_locale_core", @@ -1367,26 +2351,79 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" dependencies = [ "icu_normalizer", "icu_properties", ] +[[package]] +name = "image" +version = "0.25.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" +dependencies = [ + "bytemuck", + "byteorder-lite", + "color_quant", + "exr", + "gif", + "image-webp", + "moxcms", + "num-traits", + "png", + "qoi", + "ravif", + "rayon", + "rgb", + "tiff", + "zune-core", + "zune-jpeg", +] + +[[package]] +name = "image-webp" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3" +dependencies = [ + "byteorder-lite", + "quick-error", +] + +[[package]] +name = "imgref" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40fac9d56ed6437b198fddba683305e8e2d651aa42647f00f5ae542e7f5c94a2" + [[package]] name = "indexmap" -version = "2.13.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.16.1", + "hashbrown 0.17.1", "serde", "serde_core", ] +[[package]] +name = "indicatif" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25470f23803092da7d239834776d653104d551bc4d7eacaf31e6837854b8e9eb" +dependencies = [ + "console", + "portable-atomic", + "unicode-width", + "unit-prefix", + "web-time", +] + [[package]] name = "inout" version = "0.1.4" @@ -1406,6 +2443,23 @@ dependencies = [ "hybrid-array", ] +[[package]] +name = "interpolate_name" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + [[package]] name = "is_debug" version = "1.1.0" @@ -1418,17 +2472,26 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jiff" -version = "0.2.23" +version = "0.2.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a3546dc96b6d42c5f24902af9e2538e82e39ad350b0c766eb3fbf2d8f3d8359" +checksum = "4603d3033e49e2b0e31229fcab20a5d40089c607d975cd9c80551dc69eed9102" dependencies = [ "jiff-static", "jiff-tzdb-platform", @@ -1436,14 +2499,14 @@ dependencies = [ "portable-atomic", "portable-atomic-util", "serde_core", - "windows-sys 0.52.0", + "windows-link 0.2.1", ] [[package]] name = "jiff-static" -version = "0.2.23" +version = "0.2.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4" +checksum = "782d32378dddf207193ac91cefb848ad41abb58195c95168e1291227a0832b47" dependencies = [ "proc-macro2", "quote", @@ -1477,37 +2540,86 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.91" +version = "0.3.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" +checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11" dependencies = [ + "cfg-if 1.0.4", + "futures-util", "once_cell", "wasm-bindgen", ] +[[package]] +name = "kdtree" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f0a0e9f770b65bac9aad00f97a67ab5c5319effed07f6da385da3c2115e47ba" +dependencies = [ + "num-traits", + "thiserror 1.0.69", +] + +[[package]] +name = "konst" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "128133ed7824fcd73d6e7b17957c5eb7bacb885649bd8c69708b2331a10bcefb" +dependencies = [ + "konst_macro_rules", +] + +[[package]] +name = "konst_macro_rules" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4933f3f57a8e9d9da04db23fb153356ecaf00cbd14aee46279c33dc80925c37" + [[package]] name = "lazy_static" 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 = "leb128fmt" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" +[[package]] +name = "lebe" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" + [[package]] name = "libc" version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +[[package]] +name = "libfuzzer-sys" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f12a681b7dd8ce12bff52488013ba614b869148d54dd79836ab85aafdd53f08d" +dependencies = [ + "arbitrary", + "cc", +] + [[package]] name = "libgit2-sys" -version = "0.18.4+1.9.3" +version = "0.18.5+1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b26f66f35e1871b22efcf7191564123d2a446ca0538cde63c23adfefa9b15b7" +checksum = "005d6ae6eac1912906073e069f7db60b1fa98e052a68227824afe3e3a1c59ca2" dependencies = [ "cc", "libc", @@ -1515,20 +2627,30 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if 1.0.4", + "windows-link 0.2.1", +] + [[package]] name = "libredox" -version = "0.1.14" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a" +checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3" dependencies = [ "libc", ] [[package]] name = "libz-sys" -version = "1.1.25" +version = "1.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52f4c29e2a68ac30c9087e1b772dc9f44a2b66ed44edf2266cf2be9b03dafc1" +checksum = "fc3a226e576f50782b3305c5ccf458698f92798987f551c6a02efe8276721e22" dependencies = [ "cc", "libc", @@ -1536,6 +2658,12 @@ dependencies = [ "vcpkg", ] +[[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.12.1" @@ -1544,9 +2672,15 @@ checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" [[package]] name = "lock_api" @@ -1563,6 +2697,21 @@ version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5" +[[package]] +name = "loop9" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" +dependencies = [ + "imgref", +] + +[[package]] +name = "lzma-rust2" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e20f57f9918e5bd7bc58c22cdd70a6afc7375d4dd9683af5f2b34bd3d2bba619" + [[package]] name = "mac-notification-sys" version = "0.6.12" @@ -1576,54 +2725,369 @@ dependencies = [ ] [[package]] -name = "mbox" -version = "0.7.1" +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "matrixmultiply" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06de3016e9fae57a36fd14dba131fccf49f74b40b7fbdb472f96e361ec71a08" +dependencies = [ + "autocfg", + "rawpointer", +] + +[[package]] +name = "maybe-rayon" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" +dependencies = [ + "cfg-if 1.0.4", + "rayon", +] + +[[package]] +name = "mbox" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d142aeadbc4e8c679fc6d93fbe7efe1c021fa7d80629e615915b519e3bc6de" +dependencies = [ + "libc", + "stable_deref_trait", +] + +[[package]] +name = "memchr" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "metal" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e198a0ee42bdbe9ef2c09d0b9426f3b2b47d90d93a4a9b0395c4cea605e92dc0" +dependencies = [ + "bitflags 1.3.2", + "block", + "cocoa", + "core-graphics", + "foreign-types", + "log", + "objc", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "minimal-lexical" +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", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "moxcms" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b" +dependencies = [ + "num-traits", + "pxfm", +] + +[[package]] +name = "mozjpeg" +version = "0.10.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7891b80aaa86097d38d276eb98b3805d6280708c4e0a1e6f6aed9380c51fec9" +dependencies = [ + "arrayvec", + "bytemuck", + "libc", + "mozjpeg-sys", + "rgb", +] + +[[package]] +name = "mozjpeg-sys" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f0dc668bf9bf888c88e2fb1ab16a406d2c380f1d082b20d51dd540ab2aa70c1" +dependencies = [ + "cc", + "dunce", + "libc", + "nasm-rs", +] + +[[package]] +name = "nalgebra" +version = "0.34.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df76ea0ff5c7e6b88689085804d6132ded0ddb9de5ca5b8aeb9eeadc0508a70a" +dependencies = [ + "approx", + "glam 0.14.0", + "glam 0.15.2", + "glam 0.16.0", + "glam 0.17.3", + "glam 0.18.0", + "glam 0.19.0", + "glam 0.20.5", + "glam 0.21.3", + "glam 0.22.0", + "glam 0.23.0", + "glam 0.24.2", + "glam 0.25.0", + "glam 0.27.0", + "glam 0.28.0", + "glam 0.29.3", + "glam 0.30.10", + "glam 0.31.1", + "glam 0.32.1", + "matrixmultiply", + "nalgebra-macros", + "num-complex", + "num-rational", + "num-traits", + "simba", + "typenum", +] + +[[package]] +name = "nalgebra-macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "973e7178a678cfd059ccec50887658d482ce16b0aa9da3888ddeab5cd5eb4889" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "nanorand" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "nasm-rs" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "706bf8a5e8c8ddb99128c3291d31bd21f4bcde17f0f4c20ec678d85c74faa149" +dependencies = [ + "jobserver", + "log", +] + +[[package]] +name = "native-tls" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "ndarray" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520080814a7a6b4a6e9070823bb24b4531daac8c4627e08ba5de8c5ef2f2752d" +dependencies = [ + "matrixmultiply", + "num-complex", + "num-integer", + "num-traits", + "portable-atomic", + "portable-atomic-util", + "rawpointer", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags 2.11.1", + "cfg-if 1.0.4", + "cfg_aliases", + "libc", +] + +[[package]] +name = "nix" +version = "0.31.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" +dependencies = [ + "bitflags 2.11.1", + "cfg-if 1.0.4", + "cfg_aliases", + "libc", +] + +[[package]] +name = "no_std_io2" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418abd1b6d34fbf6cae440dc874771b0525a604428704c76e48b29a5e67b8003" +dependencies = [ + "memchr", +] + +[[package]] +name = "nokhwa" +version = "0.10.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d63f10b450319a0ace7aa8e0e25477d1fdb345313a97e220e886175539a1dbb" +dependencies = [ + "flume", + "image", + "nokhwa-bindings-linux", + "nokhwa-bindings-macos", + "nokhwa-bindings-windows", + "nokhwa-core", + "paste", + "thiserror 2.0.18", +] + +[[package]] +name = "nokhwa-bindings-linux" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb67e22201a53322291740ca064b20eaaade7222ef0349f312d9b37b004e1984" +dependencies = [ + "libc", + "nokhwa-core", + "v4l", +] + +[[package]] +name = "nokhwa-bindings-macos" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d142aeadbc4e8c679fc6d93fbe7efe1c021fa7d80629e615915b519e3bc6de" +checksum = "f70d3908ea68324e44a6b3a0f885aa59e433fb1f6678839d09e0df7d226fb42d" dependencies = [ - "libc", - "stable_deref_trait", + "block", + "cocoa-foundation", + "core-foundation 0.10.1", + "core-media-sys", + "core-video-sys", + "flume", + "nokhwa-core", + "objc", + "once_cell", ] [[package]] -name = "memchr" -version = "2.8.0" +name = "nokhwa-bindings-windows" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "5be28886bad8abcec3655c1f24b965b4cb596a72b23164c910c54439ce55d2a4" +dependencies = [ + "nokhwa-core", + "once_cell", + "windows 0.62.2", +] [[package]] -name = "memoffset" -version = "0.9.1" +name = "nokhwa-core" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +checksum = "b1cba20bebd3bd9ae22f9273ade5bbe49da3e047c8512b53fbaf8b4b9c80d496" dependencies = [ - "autocfg", + "bytes", + "image", + "mozjpeg", + "thiserror 2.0.18", ] [[package]] -name = "nix" -version = "0.30.1" +name = "nom" +version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ - "bitflags", - "cfg-if", - "cfg_aliases", - "libc", + "memchr", + "minimal-lexical", ] [[package]] -name = "nix" -version = "0.31.3" +name = "nom" +version = "8.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" dependencies = [ - "bitflags", - "cfg-if", - "cfg_aliases", - "libc", + "memchr", ] +[[package]] +name = "noop_proc_macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" + [[package]] name = "notify-rust" version = "4.17.0" @@ -1638,11 +3102,30 @@ dependencies = [ "zbus", ] +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + [[package]] name = "num-conv" -version = "0.2.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] name = "num-derive" @@ -1655,6 +3138,26 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -1664,6 +3167,26 @@ dependencies = [ "autocfg", ] +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi 0.5.2", + "libc", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", + "objc_exception", +] + [[package]] name = "objc2" version = "0.6.4" @@ -1679,7 +3202,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "bitflags", + "bitflags 2.11.1", "dispatch2", "objc2", ] @@ -1696,13 +3219,22 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ - "bitflags", + "bitflags 2.11.1", "block2", "libc", "objc2", "objc2-core-foundation", ] +[[package]] +name = "objc_exception" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" +dependencies = [ + "cc", +] + [[package]] name = "oid" version = "0.2.1" @@ -1714,9 +3246,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "once_cell_polyfill" @@ -1730,6 +3262,49 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +[[package]] +name = "openssl" +version = "0.10.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a45fa2aa886c42762255da344f0a0d313e254066c46aad76f300c3d3da62d967" +dependencies = [ + "bitflags 2.11.1", + "cfg-if 1.0.4", + "foreign-types", + "libc", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "openssl-sys" +version = "0.9.116" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28a22dc7140cda5f096e5e7724a6962ca81a7f8bfd2979f9b18c11af56318c4" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "option-ext" version = "0.2.0" @@ -1746,6 +3321,30 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "ort" +version = "2.0.0-rc.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7de3af33d24a745ffb8fab904b13478438d1cd52868e6f17735ef6e1f8bf133" +dependencies = [ + "ndarray", + "ort-sys", + "smallvec", + "tracing", + "ureq", +] + +[[package]] +name = "ort-sys" +version = "2.0.0-rc.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7b497d21a8b6fbb4b5a544f8fadb77e801a09ae0add9e411d31c6f89e3c1e90" +dependencies = [ + "hmac-sha256", + "lzma-rust2", + "ureq", +] + [[package]] name = "p256" version = "0.13.2" @@ -1787,7 +3386,7 @@ dependencies = [ "passless-config-doc", "serde", "soft-fido2", - "thiserror", + "thiserror 2.0.18", "toml", ] @@ -1802,17 +3401,22 @@ dependencies = [ "clap", "clap_complete", "ctrlc", + "dirs", "env_logger", + "face_id", + "futures-util", "git2 0.21.0", "hex", "hmac 0.13.0", + "image", "libc", "log", "nix 0.31.3", + "nokhwa", "notify-rust", "passless-core", "prs-lib", - "rand", + "rand 0.8.6", "rpassword", "serde", "serde_bytes", @@ -1824,10 +3428,32 @@ dependencies = [ "soft-fido2-ctap", "soft-fido2-transport", "tempfile", + "thiserror 2.0.18", + "tokio", "tss-esapi", + "which 7.0.3", + "zbus", "zeroize", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pastey" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" + +[[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" @@ -1837,6 +3463,15 @@ dependencies = [ "base64ct", ] +[[package]] +name = "pem-rfc7468" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6305423e0e7738146434843d1694d621cce767262b2a86910beab705e4493d9" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.2" @@ -1901,15 +3536,28 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ - "der", + "der 0.7.10", "spki", ] [[package]] name = "pkg-config" -version = "0.3.32" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "png" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" +dependencies = [ + "bitflags 2.11.1", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] [[package]] name = "polling" @@ -1917,11 +3565,11 @@ version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" dependencies = [ - "cfg-if", + "cfg-if 1.0.4", "concurrent-queue", "hermit-abi 0.5.2", "pin-project-lite", - "rustix", + "rustix 1.1.4", "windows-sys 0.61.2", ] @@ -1931,7 +3579,7 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" dependencies = [ - "cfg-if", + "cfg-if 1.0.4", "cpufeatures 0.2.17", "opaque-debug", "universal-hash", @@ -1945,18 +3593,18 @@ checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "portable-atomic-util" -version = "0.2.5" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a9db96d7fa8782dd8c15ce32ffe8680bbd1e978a43bf51a34d39483540495f5" +checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" dependencies = [ "portable-atomic", ] [[package]] name = "potential_utf" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" dependencies = [ "zerovec", ] @@ -2019,9 +3667,9 @@ version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25485360a54d6861439d60facef26de713b1e126bf015ec8f98239467a2b82f7" dependencies = [ - "bitflags", + "bitflags 2.11.1", "procfs-core", - "rustix", + "rustix 1.1.4", ] [[package]] @@ -2030,10 +3678,29 @@ version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6401bf7b6af22f78b563665d15a22e9aef27775b79b149a66ca022468a4e405" dependencies = [ - "bitflags", + "bitflags 2.11.1", "hex", ] +[[package]] +name = "profiling" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d595e54a326bc53c1c197b32d295e14b169e3cfeaa8dc82b529f947fba6bcf5" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4488a4a36b9a4ba6b9334a32a39971f77c1436ec82c38707bce707699cc3bbcb" +dependencies = [ + "quote", + "syn 2.0.117", +] + [[package]] name = "prs-lib" version = "0.5.7" @@ -2048,14 +3715,35 @@ dependencies = [ "regex", "secstr", "shellexpand", - "shlex", - "thiserror", + "shlex 1.3.0", + "thiserror 2.0.18", "version-compare", "walkdir", - "which", + "which 8.0.2", "zeroize", ] +[[package]] +name = "pxfm" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0c5ccf5294c6ccd63a74f1565028353830a9c2f5eb0c682c355c471726a6e3f" + +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + [[package]] name = "quick-xml" version = "0.37.5" @@ -2088,13 +3776,23 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "rand" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", ] [[package]] @@ -2104,7 +3802,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", ] [[package]] @@ -2116,6 +3824,91 @@ dependencies = [ "getrandom 0.2.17", ] +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rav1e" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b6dd56e85d9483277cde964fd1bdb0428de4fec5ebba7540995639a21cb32b" +dependencies = [ + "aligned-vec", + "arbitrary", + "arg_enum_proc_macro", + "arrayvec", + "av-scenechange", + "av1-grain", + "bitstream-io", + "built", + "cfg-if 1.0.4", + "interpolate_name", + "itertools", + "libc", + "libfuzzer-sys", + "log", + "maybe-rayon", + "new_debug_unreachable", + "noop_proc_macro", + "num-derive", + "num-traits", + "paste", + "profiling", + "rand 0.9.4", + "rand_chacha 0.9.0", + "simd_helpers", + "thiserror 2.0.18", + "v_frame", + "wasm-bindgen", +] + +[[package]] +name = "ravif" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e52310197d971b0f5be7fe6b57530dcd27beb35c1b013f29d66c1ad73fbbcc45" +dependencies = [ + "avif-serialize", + "imgref", + "loop9", + "quick-error", + "rav1e", + "rayon", + "rgb", +] + +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + +[[package]] +name = "rayon" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "redox_users" version = "0.5.2" @@ -2124,7 +3917,7 @@ checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ "getrandom 0.2.17", "libredox", - "thiserror", + "thiserror 2.0.18", ] [[package]] @@ -2140,30 +3933,96 @@ dependencies = [ ] [[package]] -name = "regex-automata" -version = "0.4.14" +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "js-sys", + "log", + "mime", + "native-tls", + "percent-encoding", + "pin-project-lite", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-native-tls", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", +] + +[[package]] +name = "rfc6979" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", + "hmac 0.12.1", + "subtle", ] [[package]] -name = "regex-syntax" -version = "0.8.10" +name = "rgb" +version = "0.8.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4" +dependencies = [ + "bytemuck", +] [[package]] -name = "rfc6979" -version = "0.4.0" +name = "ring" +version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ - "hmac 0.12.1", - "subtle", + "cc", + "cfg-if 1.0.4", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", ] [[package]] @@ -2179,14 +4038,20 @@ dependencies = [ [[package]] name = "rtoolbox" -version = "0.0.3" +version = "0.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7cc970b249fbe527d6e02e0a227762c9108b2f49d81094fe357ffc6d14d7f6f" +checksum = "50a0e551c1e27e1731aba276dbeaeac73f53c7cd34d1bda485d02bd1e0f36844" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc_version" version = "0.4.1" @@ -2196,17 +4061,65 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.11.1", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + [[package]] name = "rustix" version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags", + "bitflags 2.11.1", "errno", "libc", - "linux-raw-sys", - "windows-sys 0.52.0", + "linux-raw-sys 0.12.1", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" +dependencies = [ + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", ] [[package]] @@ -2215,6 +4128,21 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "safe_arch" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b02de82ddbe1b636e6170c21be622223aea188ef2e139be0a5b219ec215323" +dependencies = [ + "bytemuck", +] + [[package]] name = "same-file" version = "1.0.6" @@ -2224,6 +4152,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -2237,7 +4174,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ "base16ct", - "der", + "der 0.7.10", "generic-array", "pkcs8", "subtle", @@ -2253,11 +4190,34 @@ dependencies = [ "libc", ] +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags 2.11.1", + "core-foundation 0.10.1", + "core-foundation-sys 0.8.7", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys 0.8.7", + "libc", +] + [[package]] name = "semver" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "serde" @@ -2342,13 +4302,25 @@ dependencies = [ "serde_core", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "sha2" version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ - "cfg-if", + "cfg-if 1.0.4", "cpufeatures 0.2.17", "digest 0.10.7", ] @@ -2359,9 +4331,9 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" dependencies = [ - "cfg-if", + "cfg-if 1.0.4", "cpufeatures 0.3.0", - "digest 0.11.2", + "digest 0.11.3", ] [[package]] @@ -2391,6 +4363,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + [[package]] name = "signal-hook-registry" version = "1.4.8" @@ -2408,7 +4386,35 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest 0.10.7", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "simba" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c99284beb21666094ba2b75bbceda012e610f5479dfcc2d6e2426f53197ffd95" +dependencies = [ + "approx", + "num-complex", + "num-traits", + "paste", + "wide", +] + +[[package]] +name = "simd-adler32" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + +[[package]] +name = "simd_helpers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" +dependencies = [ + "quote", ] [[package]] @@ -2426,20 +4432,41 @@ dependencies = [ "serde", ] +[[package]] +name = "socket2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "socks" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c3dbbd9ae980613c6dd8e28a9407b50509d3803b57624d5dfe8315218cd58b" +dependencies = [ + "byteorder", + "libc", + "winapi", +] + [[package]] name = "soft-fido2" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85952b3d0bb48adee88e7233af66d196b139076dcb7a82db945426e5421f2fab" dependencies = [ - "aes 0.9.0", + "aes 0.9.1", "cbc", "cbor4ii", "hex", "hkdf 0.13.0", "hmac 0.13.0", "p256", - "rand", + "rand 0.8.6", "secstr", "serde", "serde_bytes", @@ -2448,7 +4475,7 @@ dependencies = [ "soft-fido2-crypto", "soft-fido2-ctap", "soft-fido2-transport", - "spin", + "spin 0.11.0", "zeroize", ] @@ -2458,16 +4485,16 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f74259700b8ce2efd348482ae9369cb2a00ae95cffecdd3714eb8d28a66f0b6" dependencies = [ - "aes 0.9.0", + "aes 0.9.1", "cbc", "ed25519-dalek", "hkdf 0.13.0", "hmac 0.13.0", "p256", - "rand", + "rand 0.8.6", "sha2 0.11.0", "subtle", - "thiserror", + "thiserror 2.0.18", "zeroize", ] @@ -2478,7 +4505,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27838bfc975c317e6925ffeb1a85fcab642b14d6b8359ced25ee5d1dd9f06a4d" dependencies = [ "cbor4ii", - "rand", + "rand 0.8.6", "secstr", "serde", "serde_bytes", @@ -2486,7 +4513,7 @@ dependencies = [ "smallvec", "soft-fido2-crypto", "subtle", - "thiserror", + "thiserror 2.0.18", "zeroize", ] @@ -2503,6 +4530,15 @@ dependencies = [ "smallvec", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + [[package]] name = "spin" version = "0.11.0" @@ -2519,7 +4555,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ "base64ct", - "der", + "der 0.7.10", ] [[package]] @@ -2562,6 +4598,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + [[package]] name = "synstructure" version = "0.13.2" @@ -2573,6 +4618,27 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "system-configuration" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +dependencies = [ + "bitflags 2.11.1", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys 0.8.7", + "libc", +] + [[package]] name = "target-lexicon" version = "0.12.16" @@ -2586,8 +4652,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b1e66e07de489fe43a46678dd0b8df65e0c973909df1b60ba33874e297ba9b9" dependencies = [ "quick-xml", - "thiserror", - "windows", + "thiserror 2.0.18", + "windows 0.61.3", "windows-version", ] @@ -2600,8 +4666,17 @@ dependencies = [ "fastrand", "getrandom 0.4.2", "once_cell", - "rustix", - "windows-sys 0.52.0", + "rustix 1.1.4", + "windows-sys 0.61.2", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", ] [[package]] @@ -2610,7 +4685,18 @@ version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] @@ -2624,6 +4710,20 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "tiff" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52" +dependencies = [ + "fax", + "flate2", + "half 2.7.1", + "quick-error", + "weezl", + "zune-jpeg", +] + [[package]] name = "time" version = "0.3.47" @@ -2631,10 +4731,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", + "itoa", "num-conv", "powerfmt", "serde_core", "time-core", + "time-macros", ] [[package]] @@ -2643,16 +4745,85 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tinystr" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" dependencies = [ "displaydoc", "zerovec", ] +[[package]] +name = "tokio" +version = "1.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + [[package]] name = "toml" version = "1.1.2+spec-1.1.0" @@ -2665,7 +4836,7 @@ dependencies = [ "toml_datetime", "toml_parser", "toml_writer", - "winnow 1.0.0", + "winnow", ] [[package]] @@ -2678,31 +4849,76 @@ dependencies = [ ] [[package]] -name = "toml_edit" -version = "0.25.4+spec-1.1.0" +name = "toml_edit" +version = "0.25.12+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_writer" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7193cbd0ce53dc966037f54351dbbcf0d5a642c7f0038c382ef9e677ce8c13f2" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ - "indexmap", - "toml_datetime", - "toml_parser", - "winnow 0.7.15", + "bitflags 2.11.1", + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "url", ] [[package]] -name = "toml_parser" -version = "1.1.2+spec-1.1.0" +name = "tower-layer" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" -dependencies = [ - "winnow 1.0.0", -] +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" [[package]] -name = "toml_writer" -version = "1.1.1+spec-1.1.0" +name = "tower-service" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" @@ -2735,6 +4951,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "tss-esapi" version = "7.7.0" @@ -2770,15 +4992,15 @@ dependencies = [ [[package]] name = "typenum" -version = "1.19.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] name = "uds_windows" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51b70b87d15e91f553711b40df3048faf27a7a04e01e0ddc0cf9309f0af7c2ca" +checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" dependencies = [ "memoffset", "tempfile", @@ -2791,22 +5013,76 @@ version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + [[package]] name = "unicode-xid" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "unit-prefix" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81e544489bf3d8ef66c953931f56617f423cd4b5494be343d9b9d3dda037b9a3" + [[package]] name = "universal-hash" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" dependencies = [ - "crypto-common 0.1.7", + "crypto-common 0.1.6", "subtle", ] +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "ureq" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dea7109cdcd5864d4eeb1b58a1648dc9bf520360d7af16ec26d0a9354bafcfc0" +dependencies = [ + "base64", + "cookie_store", + "der 0.8.0", + "flate2", + "log", + "native-tls", + "percent-encoding", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "socks", + "ureq-proto", + "utf8-zero", + "webpki-root-certs", + "webpki-roots", +] + +[[package]] +name = "ureq-proto" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e994ba84b0bd1b1b0cf92878b7ef898a5c1760108fe7b6010327e274917a808c" +dependencies = [ + "base64", + "http", + "httparse", + "log", +] + [[package]] name = "url" version = "2.5.8" @@ -2819,6 +5095,12 @@ dependencies = [ "serde", ] +[[package]] +name = "utf8-zero" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8c0a043c9540bae7c578c88f91dda8bd82e59ae27c21baca69c8b191aaf5a6e" + [[package]] name = "utf8_iter" version = "1.0.4" @@ -2833,15 +5115,46 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.22.0" +version = "1.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" +checksum = "d258b83ceec21034727ecee8c382cfa6c3e133699b0742c64571814fb420c9f7" dependencies = [ "js-sys", "serde_core", "wasm-bindgen", ] +[[package]] +name = "v4l" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8fbfea44a46799d62c55323f3c55d06df722fbe577851d848d328a1041c3403" +dependencies = [ + "bitflags 1.3.2", + "libc", + "v4l2-sys-mit", +] + +[[package]] +name = "v4l2-sys-mit" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6779878362b9bacadc7893eac76abe69612e8837ef746573c4a5239daf11990b" +dependencies = [ + "bindgen", +] + +[[package]] +name = "v_frame" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "666b7727c8875d6ab5db9533418d7c764233ac9c0cff1d469aec8fa127597be2" +dependencies = [ + "aligned-vec", + "num-traits", + "wasm-bindgen", +] + [[package]] name = "vcpkg" version = "0.2.15" @@ -2870,6 +5183,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -2878,11 +5200,11 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.2+wasi-0.2.9" +version = "1.0.3+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.57.1", ] [[package]] @@ -2891,27 +5213,37 @@ version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.51.0", ] [[package]] name = "wasm-bindgen" -version = "0.2.114" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" +checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409" dependencies = [ - "cfg-if", + "cfg-if 1.0.4", "once_cell", "rustversion", "wasm-bindgen-macro", "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9473dbd2991ae90b6291c3c32c30c6187ac49aa32f9905d1cce280ec1e110b0f" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "wasm-bindgen-macro" -version = "0.2.114" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" +checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2919,9 +5251,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.114" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" +checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e" dependencies = [ "bumpalo", "proc-macro2", @@ -2932,9 +5264,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.114" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" +checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437" dependencies = [ "unicode-ident", ] @@ -2961,18 +5293,99 @@ dependencies = [ "wasmparser", ] +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "wasmparser" version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags", + "bitflags 2.11.1", "hashbrown 0.15.5", "indexmap", "semver", ] +[[package]] +name = "web-sys" +version = "0.3.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621441cfc37b84979402712047321980c178f299193a3589d05b99e8763436" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-root-certs" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "webpki-roots" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "weezl" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" + +[[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 = "which" +version = "7.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d643ce3fd3e5b54854602a080f34fb10ab75e0b813ee32d00ca2b44fa74762" +dependencies = [ + "either", + "env_home", + "rustix 1.1.4", + "winsafe", +] + [[package]] name = "which" version = "8.0.2" @@ -2982,6 +5395,16 @@ dependencies = [ "libc", ] +[[package]] +name = "wide" +version = "0.7.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce5da8ecb62bcd8ec8b7ea19f69a51275e91299be594ea5cc6ef7819e16cd03" +dependencies = [ + "bytemuck", + "safe_arch", +] + [[package]] name = "winapi" version = "0.3.9" @@ -3004,7 +5427,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -3019,11 +5442,23 @@ version = "0.61.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" dependencies = [ - "windows-collections", - "windows-core", - "windows-future", + "windows-collections 0.2.0", + "windows-core 0.61.2", + "windows-future 0.2.1", "windows-link 0.1.3", - "windows-numerics", + "windows-numerics 0.2.0", +] + +[[package]] +name = "windows" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" +dependencies = [ + "windows-collections 0.3.2", + "windows-core 0.62.2", + "windows-future 0.3.2", + "windows-numerics 0.3.1", ] [[package]] @@ -3032,7 +5467,16 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" dependencies = [ - "windows-core", + "windows-core 0.61.2", +] + +[[package]] +name = "windows-collections" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" +dependencies = [ + "windows-core 0.62.2", ] [[package]] @@ -3044,8 +5488,21 @@ dependencies = [ "windows-implement", "windows-interface", "windows-link 0.1.3", - "windows-result", - "windows-strings", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", ] [[package]] @@ -3054,9 +5511,20 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" dependencies = [ - "windows-core", + "windows-core 0.61.2", "windows-link 0.1.3", - "windows-threading", + "windows-threading 0.1.0", +] + +[[package]] +name = "windows-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core 0.62.2", + "windows-link 0.2.1", + "windows-threading 0.2.1", ] [[package]] @@ -3099,10 +5567,31 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" dependencies = [ - "windows-core", + "windows-core 0.61.2", "windows-link 0.1.3", ] +[[package]] +name = "windows-numerics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" +dependencies = [ + "windows-core 0.62.2", + "windows-link 0.2.1", +] + +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + [[package]] name = "windows-result" version = "0.3.4" @@ -3112,6 +5601,15 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link 0.2.1", +] + [[package]] name = "windows-strings" version = "0.4.2" @@ -3121,6 +5619,15 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link 0.2.1", +] + [[package]] name = "windows-sys" version = "0.52.0" @@ -3130,6 +5637,15 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.61.2" @@ -3164,6 +5680,15 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-threading" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +dependencies = [ + "windows-link 0.2.1", +] + [[package]] name = "windows-version" version = "0.1.7" @@ -3223,18 +5748,18 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.7.15" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" dependencies = [ "memchr", ] [[package]] -name = "winnow" -version = "1.0.0" +name = "winsafe" +version = "0.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8" +checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" [[package]] name = "wit-bindgen" @@ -3245,6 +5770,12 @@ dependencies = [ "wit-bindgen-rust-macro", ] +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + [[package]] name = "wit-bindgen-core" version = "0.51.0" @@ -3294,7 +5825,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags", + "bitflags 2.11.1", "indexmap", "log", "serde", @@ -3326,15 +5857,21 @@ dependencies = [ [[package]] name = "writeable" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "y4m" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5a4b21e1a62b67a2970e6831bc091d7b87e119e7f9791aef9702e3bef04448" [[package]] name = "yoke" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -3343,9 +5880,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", @@ -3355,9 +5892,9 @@ dependencies = [ [[package]] name = "zbus" -version = "5.14.0" +version = "5.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca82f95dbd3943a40a53cfded6c2d0a2ca26192011846a1810c4256ef92c60bc" +checksum = "eee682d202a77e4a9f3b2c2bdf48a7b28af5c08c34ddf66f98c93e5e39464285" dependencies = [ "async-broadcast", "async-executor", @@ -3375,14 +5912,14 @@ dependencies = [ "hex", "libc", "ordered-stream", - "rustix", + "rustix 1.1.4", "serde", "serde_repr", "tracing", "uds_windows", "uuid", "windows-sys 0.61.2", - "winnow 0.7.15", + "winnow", "zbus_macros", "zbus_names", "zvariant", @@ -3390,9 +5927,9 @@ dependencies = [ [[package]] name = "zbus_macros" -version = "5.14.0" +version = "5.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897e79616e84aac4b2c46e9132a4f63b93105d54fe8c0e8f6bffc21fa8d49222" +checksum = "adf1bd45a81a103745b1757754762a26e8cd01e4532e4d6c8ec431624b80d1d6" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -3405,29 +5942,29 @@ dependencies = [ [[package]] name = "zbus_names" -version = "4.3.1" +version = "4.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffd8af6d5b78619bab301ff3c560a5bd22426150253db278f164d6cf3b72c50f" +checksum = "7074f3e50b894eac91750142016d30d0a89be8e67dbfd9704fb875825760e52d" dependencies = [ "serde", - "winnow 0.7.15", + "winnow", "zvariant", ] [[package]] name = "zerocopy" -version = "0.8.42" +version = "0.8.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2578b716f8a7a858b7f02d5bd870c14bf4ddbbcf3a4c05414ba6503640505e3" +checksum = "bce33a6288fa3f072a8c2c7d0f2fdbb90e28298f0135c1f99b96c3db2efcc60b" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.42" +version = "0.8.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f" +checksum = "8fd425244944f4ab65ccff928e7323354c5a018c75838362fdce749dfad2ee1e" dependencies = [ "proc-macro2", "quote", @@ -3436,18 +5973,18 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", @@ -3477,9 +6014,9 @@ dependencies = [ [[package]] name = "zerotrie" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" dependencies = [ "displaydoc", "yoke", @@ -3488,9 +6025,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" dependencies = [ "yoke", "zerofrom", @@ -3499,9 +6036,9 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", @@ -3514,25 +6051,49 @@ version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" +[[package]] +name = "zune-core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9" + +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "zune-jpeg" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296" +dependencies = [ + "zune-core", +] + [[package]] name = "zvariant" -version = "5.10.0" +version = "5.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5708299b21903bbe348e94729f22c49c55d04720a004aa350f1f9c122fd2540b" +checksum = "a192a0bde63360d77a7523c833d4b4ce6070a927e2c53246e4c540b1a3e27be0" dependencies = [ "endi", "enumflags2", "serde", - "winnow 0.7.15", + "winnow", "zvariant_derive", "zvariant_utils", ] [[package]] name = "zvariant_derive" -version = "5.10.0" +version = "5.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b59b012ebe9c46656f9cc08d8da8b4c726510aef12559da3e5f1bf72780752c" +checksum = "90bc6cde9c01c511074be97f7ccb6c19d0da89e3f8662e812e999dcfd4638737" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -3543,13 +6104,13 @@ dependencies = [ [[package]] name = "zvariant_utils" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f75c23a64ef8f40f13a6989991e643554d9bef1d682a281160cf0c1bc389c5e9" +checksum = "1e8535915cfa75547e559d8c68e8139909a4aeee076831e4ef7fc59d8172c4d6" dependencies = [ "proc-macro2", "quote", "serde", "syn 2.0.117", - "winnow 0.7.15", + "winnow", ] diff --git a/Cargo.toml b/Cargo.toml index a92e2f35..0aace11c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,14 @@ zeroize = { version = "1.8", features = ["zeroize_derive"] } hex = "0.4" shadow-rs = "2.0" +# UV provider dependencies +zbus = "5" +nokhwa = { version = "0.10", features = ["input-native"] } +face_id = "0.4" +image = "0.25" +tokio = { version = "1", features = ["rt-multi-thread", "macros", "time"] } +which = "7" + # Proc macro dependencies syn = { version = "2.0", features = ["full", "extra-traits"] } quote = "1.0" diff --git a/Makefile b/Makefile index 26536773..bdb68224 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,8 @@ CARGO_TARGET_DIR ?= target CARGO_TARGET ?= x86_64-unknown-linux-gnu PKG_BASE_NAME ?= passless-${CARGO_TARGET} PROJECT_VERSION := $(shell sed -n 's/^version = "\(.*\)"/\1/p' ./Cargo.toml | head -n1) +# Default to tpm feature only, face feature requires glibc 2.38+ +CARGO_FEATURES ?= tpm .DEFAULT: help .PHONY: help @@ -77,7 +79,7 @@ update-changelog: ## automatically update changelog based on commits release: ## generate vendor.tar.gz, $(PKG_BASE_NAME).tar.gz, and completions cargo vendor tar -czf vendor.tar.gz vendor - cargo build --frozen --release --all-features --target ${CARGO_TARGET} +cargo build --frozen --release --features ${CARGO_FEATURES} --target ${CARGO_TARGET} tar -czf $(PKG_BASE_NAME).tar.gz -C $(CARGO_TARGET_DIR)/$(CARGO_TARGET)/release passless @# Create completions tarball @COMPLETION_DIR=$$(find $(CARGO_TARGET_DIR)/$(CARGO_TARGET)/release/build/passless-*/out/completions -type d 2>/dev/null | head -1); \ diff --git a/cmd/passless/Cargo.toml b/cmd/passless/Cargo.toml index 0f6fecf4..4bab8db1 100644 --- a/cmd/passless/Cargo.toml +++ b/cmd/passless/Cargo.toml @@ -17,6 +17,7 @@ path = "src/main.rs" [features] default = [] tpm = ["passless-core/tpm", "tss-esapi"] +face = ["dep:nokhwa", "dep:face_id", "dep:image"] [dependencies] passless-core.workspace = true @@ -40,12 +41,25 @@ clap.workspace = true zeroize.workspace = true hex.workspace = true shadow-rs.workspace = true +dirs.workspace = true +thiserror.workspace = true hmac = "0.13" rpassword = "7.3" atty = "0.2" serde_bytes = "0.11.19" tss-esapi = { version = "7.6.0", optional = true } +# UV provider dependencies +zbus.workspace = true +tokio.workspace = true +which.workspace = true +futures-util = "0.3" + +# Face recognition (optional, heavy dependencies) +nokhwa = { workspace = true, optional = true } +face_id = { workspace = true, optional = true } +image = { workspace = true, optional = true } + [dev-dependencies] base64 = "0.22" ciborium = "0.2" diff --git a/cmd/passless/src/authenticator.rs b/cmd/passless/src/authenticator.rs index 6fd51b37..388f6345 100644 --- a/cmd/passless/src/authenticator.rs +++ b/cmd/passless/src/authenticator.rs @@ -1,9 +1,15 @@ -use crate::notification::show_verification_notification; use crate::pin_storage::PinStorage; use crate::storage::{CredentialFilter, CredentialStorage}; use crate::util::bytes_to_hex; +use crate::uv::{ + CommandProvider, FprintdProvider, NotificationProvider, UserVerificationManager, + UserVerificationProvider, VerificationContext, VerificationResult, +}; + +#[cfg(feature = "face")] +use crate::uv::FaceIdProvider; -use passless_core::config::{PinConfig, PinEnforcement, SecurityConfig}; +use passless_core::config::{PinConfig, PinEnforcement, SecurityConfig, UvConfig, UvProviderType}; use soft_fido2::{ Authenticator, AuthenticatorCallbacks, AuthenticatorConfig, AuthenticatorOptions, Credential, @@ -44,6 +50,7 @@ pub struct PasslessCallbacks { pin_storage: Option>>, security_config: SecurityConfig, pin_config: PinConfig, + uv_manager: UserVerificationManager, } impl PasslessCallbacks { @@ -52,29 +59,35 @@ impl PasslessCallbacks { pin_storage: Option>>, security_config: SecurityConfig, pin_config: PinConfig, + uv_config: UvConfig, ) -> Self { + let uv_manager = build_uv_manager(&uv_config); + Self { storage, pin_storage, security_config, pin_config, + uv_manager, } } -} -impl AuthenticatorCallbacks for PasslessCallbacks { - fn request_up(&self, info: &str, user: Option<&str>, rp: &str) -> Result { - // Check for E2E test mode (only available in debug builds) + fn do_user_verification( + &self, + operation: &str, + user: Option<&str>, + rp: &str, + ) -> Result { #[cfg(debug_assertions)] { if std::env::var("PASSLESS_E2E_AUTO_ACCEPT_UV").is_ok() { info!("E2E test mode: Auto-accepting user verification"); - return Ok(UpResult::Accepted); + return Ok(VerificationResult::Accepted); } } - let is_registration = info.to_lowercase().contains("registration") - && !info.to_lowercase().contains("credential excluded"); + let is_registration = operation.to_lowercase().contains("registration") + && !operation.to_lowercase().contains("credential excluded"); let should_verify = if is_registration { self.security_config.user_verification_registration @@ -91,8 +104,8 @@ impl AuthenticatorCallbacks for PasslessCal }; if storage.disable_user_verification() && !is_registration && !should_verify { - debug!("User verification handled by backend (e.g., GPG): {}", info); - return Ok(UpResult::Accepted); + debug!("User verification handled by backend (e.g., GPG): {}", operation); + return Ok(VerificationResult::Accepted); } if !should_verify { @@ -103,28 +116,75 @@ impl AuthenticatorCallbacks for PasslessCal } else { "authentication" }, - info + operation ); - return Ok(UpResult::Accepted); + return Ok(VerificationResult::Accepted); } - match show_verification_notification( - info, - Some(rp), - user, - self.security_config.notification_timeout, - ) { - Ok(crate::notification::NotificationResult::Accepted) => Ok(UpResult::Accepted), - Ok(crate::notification::NotificationResult::Denied) => Ok(UpResult::Denied), - Err(e) => { - error!("Failed to show notification: {}", e); - Err(soft_fido2::Error::Other) - } + let context = VerificationContext::new(operation) + .with_relying_party(rp) + .with_user(user.unwrap_or("")) + .with_timeout(self.security_config.notification_timeout); + + self.uv_manager.verify(&context).map_err(|e| { + warn!("User verification failed: {}", e); + soft_fido2::Error::Other + }) + } +} + +fn build_uv_manager(config: &UvConfig) -> UserVerificationManager { + let mut providers: Vec> = Vec::new(); + + match config.provider { + UvProviderType::Auto => { + providers.extend(FprintdProvider::from_config(config.fprintd.enabled)); + #[cfg(feature = "face")] + providers.extend(FaceIdProvider::from_config( + config.face.enabled, + config.face.camera_index, + config.face.threshold, + )); + providers.push(NotificationProvider::from_config(config.timeout_seconds)); + } + UvProviderType::Fprintd => { + providers.extend(FprintdProvider::from_config(config.fprintd.enabled)); + } + UvProviderType::Face => { + #[cfg(feature = "face")] + providers.extend(FaceIdProvider::from_config( + config.face.enabled, + config.face.camera_index, + config.face.threshold, + )); + #[cfg(not(feature = "face"))] + log::warn!("Face provider requested but 'face' feature not enabled"); + } + UvProviderType::Notification => { + providers.push(NotificationProvider::from_config(config.timeout_seconds)); + } + UvProviderType::Command => { + providers.extend(CommandProvider::from_config( + config.command.enabled, + config.command.command.clone(), + config.timeout_seconds, + )); + } + } + + UserVerificationManager::new(providers) +} + +impl AuthenticatorCallbacks for PasslessCallbacks { + fn request_up(&self, info: &str, user: Option<&str>, rp: &str) -> Result { + match self.do_user_verification(info, user, rp)? { + VerificationResult::Accepted => Ok(UpResult::Accepted), + VerificationResult::Denied => Ok(UpResult::Denied), + VerificationResult::Timeout => Ok(UpResult::Denied), } } fn request_uv(&self, info: &str, user: Option<&str>, rp: &str) -> Result { - // Check for E2E test mode (only available in debug builds) #[cfg(debug_assertions)] { if std::env::var("PASSLESS_E2E_AUTO_ACCEPT_UV").is_ok() { @@ -133,7 +193,6 @@ impl AuthenticatorCallbacks for PasslessCal } } - // Check if PIN is set and apply enforcement policy if let Some(pin_storage) = &self.pin_storage && let storage = pin_storage.lock().map_err(|_| soft_fido2::Error::Other)? && let Ok(state) = storage.load_pin_state() @@ -152,34 +211,29 @@ impl AuthenticatorCallbacks for PasslessCal return Ok(UvResult::Denied); } info!( - "PIN is set, always_uv=false, enforcement=optional, using notification fallback" + "PIN is set, always_uv=false, enforcement=optional, using biometric/notification fallback" ); } PinEnforcement::Never => { - info!("PIN is set but enforcement=never, using notification fallback"); + info!( + "PIN is set but enforcement=never, using biometric/notification fallback" + ); } } } - // No PIN set or enforcement allows notification fallback - // Return AcceptedWithUp since notification-based UV also captures user presence - match show_verification_notification( - info, - Some(rp), - user, - self.security_config.notification_timeout, - ) { - Ok(crate::notification::NotificationResult::Accepted) => { - info!("User verification via notification: accepted"); + match self.do_user_verification(info, user, rp)? { + VerificationResult::Accepted => { + info!("User verification accepted"); Ok(UvResult::AcceptedWithUp) } - Ok(crate::notification::NotificationResult::Denied) => { - warn!("User verification via notification: denied"); + VerificationResult::Denied => { + warn!("User verification denied"); Ok(UvResult::Denied) } - Err(e) => { - error!("Failed to show notification: {}", e); - Err(soft_fido2::Error::Other) + VerificationResult::Timeout => { + warn!("User verification timed out"); + Ok(UvResult::Denied) } } } @@ -395,8 +449,13 @@ pub struct AuthenticatorService { impl AuthenticatorService { /// Create a new authenticator service without PIN storage #[allow(dead_code)] - pub fn new(storage: S, security_config: SecurityConfig, pin_config: PinConfig) -> Result { - Self::with_pin_storage(storage, None, security_config, pin_config) + pub fn new( + storage: S, + security_config: SecurityConfig, + pin_config: PinConfig, + uv_config: UvConfig, + ) -> Result { + Self::with_pin_storage(storage, None, security_config, pin_config, uv_config) } } @@ -407,27 +466,25 @@ impl AuthenticatorServi pin_storage: Option>>, security_config: SecurityConfig, pin_config: PinConfig, + uv_config: UvConfig, ) -> Result { - // Hardcoded authenticator options (FIDO2 spec compliant) - // These are platform authenticator defaults and shouldn't need configuration let options = AuthenticatorOptions { - rk: true, // Resident keys (passkeys) - up: true, // User presence - uv: Some(true), // Notification-based user verification - plat: true, // Platform authenticator - client_pin: Some(true), // Client PIN capability - pin_uv_auth_token: Some(true), // PIN/UV auth token support - cred_mgmt: Some(true), // Credential management - bio_enroll: None, // No biometric enrollment - large_blobs: None, // No large blob storage - ep: None, // Enterprise attestation not enabled + rk: true, + up: true, + uv: Some(true), + plat: true, + client_pin: Some(true), + pin_uv_auth_token: Some(true), + cred_mgmt: Some(true), + bio_enroll: None, + large_blobs: None, + ep: None, always_uv: Some(security_config.always_uv), make_cred_uv_not_required: Some(true), }; let config = AuthenticatorConfig::builder() .aaguid([ - // "fido.passless.rs" 0x66, 0x69, 0x64, 0x6F, 0x2E, 0x70, 0x61, 0x73, 0x73, 0x6C, 0x65, 0x73, 0x73, 0x2E, 0x72, 0x73, ]) @@ -455,6 +512,7 @@ impl AuthenticatorServi pin_storage.clone(), security_config, pin_config, + uv_config, ); let authenticator = if let Some(ps) = pin_storage { @@ -470,7 +528,6 @@ impl AuthenticatorServi } /// Process a CTAP request and generate a response - /// Ok(()) on success or an error pub fn handle(&mut self, request: &[u8], response_buffer: &mut Vec) -> Result<()> { self.authenticator.handle(request, response_buffer)?; Ok(()) @@ -530,11 +587,81 @@ mod tests { }; let pin_config = PinConfig::default(); + let uv_config = UvConfig::default(); - let service = AuthenticatorService::new(storage, security_config, pin_config); + let service = AuthenticatorService::new(storage, security_config, pin_config, uv_config); assert!(service.is_ok(), "Service creation should succeed"); - // Cleanup + let _ = std::fs::remove_dir_all(temp_dir); + } + + #[test] + fn test_service_with_custom_uv_config() { + let temp_dir = std::env::temp_dir().join("test_passless_uv"); + if let Err(e) = std::fs::create_dir_all(&temp_dir) { + panic!("Failed to create temp directory: {}", e); + } + let storage = match LocalStorageAdapter::new(temp_dir.clone()) { + Ok(s) => s, + Err(e) => panic!("Failed to create local storage: {}", e), + }; + + let security_config = SecurityConfig { + check_mlock: false, + disable_core_dumps: false, + constant_signature_counter: false, + always_uv: false, + user_verification_registration: false, + user_verification_authentication: false, + notification_timeout: 30, + }; + + let pin_config = PinConfig::default(); + let uv_config = UvConfig { + provider: passless_core::config::UvProviderType::Notification, + timeout_seconds: 60, + ..Default::default() + }; + + let service = AuthenticatorService::new(storage, security_config, pin_config, uv_config); + assert!( + service.is_ok(), + "Service creation with custom UV config should succeed" + ); + + let _ = std::fs::remove_dir_all(temp_dir); + } + + #[test] + fn test_service_with_uv_disabled() { + let temp_dir = std::env::temp_dir().join("test_passless_no_uv"); + if let Err(e) = std::fs::create_dir_all(&temp_dir) { + panic!("Failed to create temp directory: {}", e); + } + let storage = match LocalStorageAdapter::new(temp_dir.clone()) { + Ok(s) => s, + Err(e) => panic!("Failed to create local storage: {}", e), + }; + + let security_config = SecurityConfig { + check_mlock: false, + disable_core_dumps: false, + constant_signature_counter: false, + always_uv: false, + user_verification_registration: false, + user_verification_authentication: false, + notification_timeout: 30, + }; + + let pin_config = PinConfig::default(); + let uv_config = UvConfig::default(); + + let service = AuthenticatorService::new(storage, security_config, pin_config, uv_config); + assert!( + service.is_ok(), + "Service creation with UV disabled should succeed" + ); + let _ = std::fs::remove_dir_all(temp_dir); } } diff --git a/cmd/passless/src/commands/client.rs b/cmd/passless/src/commands/client.rs index 5c31eb0f..41952a63 100644 --- a/cmd/passless/src/commands/client.rs +++ b/cmd/passless/src/commands/client.rs @@ -326,6 +326,7 @@ fn authenticate_for_credential_management( } } } +} /// Try to authenticate for credential management, returning None on failure /// @@ -345,6 +346,7 @@ fn authenticate_for_credential_management_opt( } } } + /// Fallback to PIN authentication when UV fails fn fallback_to_pin_auth( transport: &mut Transport, @@ -847,7 +849,7 @@ pub fn show(output: OutputFormat, device: Option<&str>, credential_id_hex: &str) let mut transport = open_authenticator(device)?; - let pin_uv_auth = authenticate_for_credential_management_opt(&mut transport, output); +let pin_uv_auth = authenticate_for_credential_management_opt(&mut transport, output); // Decode credential ID let credential_id = hex::decode(credential_id_hex) @@ -997,7 +999,7 @@ pub fn delete(output: OutputFormat, device: Option<&str>, credential_id_hex: &st println!("Deleting credential: {}\n", credential_id_hex); } - let mut transport = open_authenticator(device)?; +let mut transport = open_authenticator(device)?; let pin_uv_auth = authenticate_for_credential_management_opt(&mut transport, output); @@ -1053,7 +1055,7 @@ pub fn rename( println!("Renaming credential: {}\n", credential_id_hex); } - let mut transport = open_authenticator(device)?; +let mut transport = open_authenticator(device)?; let pin_uv_auth = authenticate_for_credential_management_opt(&mut transport, output); diff --git a/cmd/passless/src/commands/custom.rs b/cmd/passless/src/commands/custom.rs index bfe59048..0a5c9a6f 100644 --- a/cmd/passless/src/commands/custom.rs +++ b/cmd/passless/src/commands/custom.rs @@ -2,8 +2,6 @@ /// /// This module provides compatibility with the Yubikey credential management variant (0x41). /// The standard credential management (0x0a) is handled by soft-fido2's built-in implementation, -/// which properly calls the AuthenticatorCallbacks methods (enumerate_rps, list_credentials, etc.) -/// that read from the actual storage backend. use crate::authenticator::AuthenticatorService; use crate::pin_storage::PinStorage; use crate::storage::CredentialStorage; @@ -50,7 +48,7 @@ mod tests { #[test] fn test_register_yubikey_command() { - use passless_core::config::{PinConfig, SecurityConfig}; + use passless_core::config::{PinConfig, SecurityConfig, UvConfig}; let temp_dir = std::env::temp_dir().join("test_passless_custom"); if let Err(e) = std::fs::create_dir_all(&temp_dir) { @@ -61,8 +59,12 @@ mod tests { Err(e) => panic!("Failed to create local storage: {}", e), }; - let service = - AuthenticatorService::new(storage, SecurityConfig::default(), PinConfig::default()); + let service = AuthenticatorService::new( + storage, + SecurityConfig::default(), + PinConfig::default(), + UvConfig::default(), + ); assert!(service.is_ok(), "Service creation should succeed"); register_yubikey_credential_mgmt(&mut service.unwrap()); diff --git a/cmd/passless/src/main.rs b/cmd/passless/src/main.rs index d47296b7..78d63255 100644 --- a/cmd/passless/src/main.rs +++ b/cmd/passless/src/main.rs @@ -4,6 +4,7 @@ mod notification; mod pin_storage; mod storage; mod util; +mod uv; use passless_core::{ AppConfig, Args, BackendConfig, ClientAction, Commands, ConfigAction, Error, PinAction, Result, @@ -136,6 +137,7 @@ fn run_with_service( } debug!("Response sent successfully"); } + } Ok(None) => { std::thread::sleep(std::time::Duration::from_millis(10)); continue; @@ -251,6 +253,12 @@ fn run() -> Result<()> { // Load config: CLI args + config file + defaults (CLI takes precedence) let config = AppConfig::load(&mut args); + // Validate configuration + if let Err(e) = config.validate() { + error!("Configuration validation failed: {}", e); + process::exit(1); + } + if config.verbose && log_level != log::LevelFilter::Debug { info!("Enabling verbose logging..."); log::set_max_level(log::LevelFilter::Debug); @@ -313,6 +321,7 @@ fn run() -> Result<()> { // Get PIN config let pin_config = config.pin_config(); + let uv_config = config.uv_config(); match config.backend().map_err(|e| { error!("Failed to load backend config: {}", e); @@ -327,6 +336,7 @@ fn run() -> Result<()> { Some(pin_storage), security_config, pin_config, + uv_config, )?; run_with_service(service, uhid, shutdown) } @@ -348,6 +358,7 @@ fn run() -> Result<()> { Some(pin_storage), security_config, pin_config, + uv_config, )?; run_with_service(service, uhid, shutdown) } @@ -361,6 +372,7 @@ fn run() -> Result<()> { Some(pin_storage), security_config, pin_config, + uv_config, )?; run_with_service(service, uhid, shutdown) } diff --git a/cmd/passless/src/notification.rs b/cmd/passless/src/notification.rs index 572eec71..14cd316b 100644 --- a/cmd/passless/src/notification.rs +++ b/cmd/passless/src/notification.rs @@ -48,6 +48,7 @@ fn requires_default_action() -> bool { } /// Show a user verification notification and wait for response +#[allow(dead_code)] pub fn show_verification_notification( operation: &str, relying_party: Option<&str>, diff --git a/cmd/passless/src/pin_storage/tpm.rs b/cmd/passless/src/pin_storage/tpm.rs index 085e4b41..24a2490c 100644 --- a/cmd/passless/src/pin_storage/tpm.rs +++ b/cmd/passless/src/pin_storage/tpm.rs @@ -42,7 +42,6 @@ impl PinStorage for TpmPinStorage { "Loading PIN state from TPM storage: {}", self.path.display() ); - let sealed_data = std::fs::read(&self.path).map_err(|e| { warn!("Failed to read sealed PIN state: {}", e); StatusCode::Other @@ -100,7 +99,7 @@ impl TpmPinStorage { use std::str::FromStr; use aes_gcm::Aes256Gcm; - use aes_gcm::Nonce; +use aes_gcm::Nonce; use aes_gcm::aead::{Aead, KeyInit, OsRng}; use rand::RngCore; use tss_esapi::attributes::ObjectAttributesBuilder; @@ -227,7 +226,7 @@ impl TpmPinStorage { let mut nonce_bytes = [0u8; 12]; OsRng.fill_bytes(&mut nonce_bytes); - let nonce = Nonce::from_slice(&nonce_bytes); +let nonce = Nonce::from_slice(&nonce_bytes); let cipher = Aes256Gcm::new_from_slice(&aes_key).map_err(|e| { warn!("Failed to create AES cipher: {:?}", e); diff --git a/cmd/passless/src/storage/tpm/mod.rs b/cmd/passless/src/storage/tpm/mod.rs index 3464adf9..f80055d3 100644 --- a/cmd/passless/src/storage/tpm/mod.rs +++ b/cmd/passless/src/storage/tpm/mod.rs @@ -244,7 +244,7 @@ impl TpmStorageAdapter { // Generate a random 96-bit nonce for AES-GCM let mut nonce_bytes = [0u8; 12]; OsRng.fill_bytes(&mut nonce_bytes); - let nonce = Nonce::from_slice(&nonce_bytes); +let nonce = Nonce::from_slice(&nonce_bytes); // Encrypt the credential data with AES-GCM let cipher = Aes256Gcm::new_from_slice(&aes_key).map_err(|e| { diff --git a/cmd/passless/src/uv/command.rs b/cmd/passless/src/uv/command.rs new file mode 100644 index 00000000..9f82f895 --- /dev/null +++ b/cmd/passless/src/uv/command.rs @@ -0,0 +1,273 @@ +//! Command-based User Verification Provider +//! +//! Executes a user-defined command/script for verification. +//! +//! # Configuration +//! +//! The command should: +//! - Exit with code 0 for accepted +//! - Exit with non-zero code for denied +//! - Receive operation context via environment variables +//! +//! # Environment Variables +//! +//! - `PASSLESS_UV_OPERATION`: Operation type (registration/authentication) +//! - `PASSLESS_UV_RELYING_PARTY`: Relying party ID (if available) +//! - `PASSLESS_UV_USER`: User identifier (if available) +//! - `PASSLESS_UV_TIMEOUT`: Timeout in seconds + +use super::{ + UserVerificationProvider, VerificationContext, VerificationError, VerificationResult, priority, +}; + +use log::{debug, info}; + +use std::process::Command; + +/// Command-based user verification provider +pub struct CommandProvider { + command: Vec, + #[allow(dead_code)] + timeout_seconds: u32, +} + +impl CommandProvider { + /// Create a new command provider + /// + /// # Arguments + /// + /// * `command` - Command and arguments to execute + /// * `timeout_seconds` - Timeout for the command + pub fn new(command: Vec, timeout_seconds: u32) -> Self { + Self { + command, + timeout_seconds, + } + } + + /// Create a provider if enabled and configured + pub fn from_config( + enabled: bool, + command: Vec, + timeout_seconds: u32, + ) -> Option> { + if enabled && !command.is_empty() { + Some(Box::new(Self::new(command, timeout_seconds))) + } else { + None + } + } + + /// Execute the verification command + fn execute( + &self, + context: &VerificationContext, + ) -> Result { + if self.command.is_empty() { + return Err(VerificationError::NotAvailable( + "No command configured".into(), + )); + } + + let program = &self.command[0]; + let args = &self.command[1..]; + + debug!("Executing UV command: {} {:?}", program, args); + + let mut cmd = Command::new(program); + cmd.args(args); + + cmd.env("PASSLESS_UV_OPERATION", &context.operation); + + if let Some(ref rp) = context.relying_party { + cmd.env("PASSLESS_UV_RELYING_PARTY", rp); + } + + if let Some(ref user) = context.user { + cmd.env("PASSLESS_UV_USER", user); + } + + cmd.env("PASSLESS_UV_TIMEOUT", context.timeout_seconds.to_string()); + + let result = cmd.output().map_err(|e| { + if e.kind() == std::io::ErrorKind::NotFound { + VerificationError::NotAvailable(format!("Command not found: {}", program)) + } else { + VerificationError::DeviceError(format!("Failed to execute command: {}", e)) + } + })?; + + debug!( + "Command exit code: {}, stdout: {}, stderr: {}", + result.status.code().unwrap_or(-1), + String::from_utf8_lossy(&result.stdout).trim(), + String::from_utf8_lossy(&result.stderr).trim() + ); + + if result.status.success() { + info!("User verification accepted via command"); + Ok(VerificationResult::Accepted) + } else { + info!( + "User verification denied via command (exit code: {:?})", + result.status.code() + ); + Ok(VerificationResult::Denied) + } + } +} + +impl UserVerificationProvider for CommandProvider { + fn name(&self) -> &str { + "command" + } + + fn available(&self) -> bool { + if self.command.is_empty() { + return false; + } + + let program = &self.command[0]; + + if program.contains('/') { + std::path::Path::new(program).exists() + } else { + which::which(program).is_ok() + } + } + + fn verify( + &self, + context: &VerificationContext, + ) -> Result { + self.execute(context) + } + + fn priority(&self) -> u8 { + priority::COMMAND + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_command_provider_name() { + let provider = CommandProvider::new(vec!["echo".to_string()], 30); + assert_eq!(provider.name(), "command"); + } + + #[test] + fn test_command_provider_priority() { + let provider = CommandProvider::new(vec!["echo".to_string()], 30); + assert_eq!(provider.priority(), priority::COMMAND); + } + + #[test] + fn test_command_provider_empty_unavailable() { + let provider = CommandProvider::new(vec![], 30); + assert!(!provider.available()); + } + + #[test] + fn test_command_provider_empty_verify_fails() { + let provider = CommandProvider::new(vec![], 30); + let ctx = VerificationContext::new("test"); + let result = provider.verify(&ctx); + assert!(result.is_err()); + match result.unwrap_err() { + VerificationError::NotAvailable(_) => {} + _ => panic!("Expected NotAvailable error"), + } + } + + #[test] + fn test_command_provider_echo() { + let provider = CommandProvider::new(vec!["echo".to_string(), "accepted".to_string()], 30); + assert!(provider.available()); + + let ctx = VerificationContext::new("test"); + let result = provider.verify(&ctx); + assert_eq!(result.unwrap(), VerificationResult::Accepted); + } + + #[test] + fn test_command_provider_false() { + let provider = CommandProvider::new(vec!["false".to_string()], 30); + + if !provider.available() { + return; + } + + let ctx = VerificationContext::new("test"); + let result = provider.verify(&ctx); + assert_eq!(result.unwrap(), VerificationResult::Denied); + } + + #[test] + fn test_command_provider_exit_1_denied() { + let provider = CommandProvider::new( + vec!["sh".to_string(), "-c".to_string(), "exit 1".to_string()], + 30, + ); + + if !provider.available() { + return; + } + + let ctx = VerificationContext::new("test"); + let result = provider.verify(&ctx); + assert_eq!(result.unwrap(), VerificationResult::Denied); + } + + #[test] + fn test_command_provider_nonexistent() { + let provider = CommandProvider::new(vec!["nonexistent_command_12345".to_string()], 30); + assert!(!provider.available()); + + let ctx = VerificationContext::new("test"); + let result = provider.verify(&ctx); + assert!(result.is_err()); + match result.unwrap_err() { + VerificationError::NotAvailable(_) => {} + _ => panic!("Expected NotAvailable error"), + } + } + + #[test] + fn test_command_provider_with_context() { + let provider = CommandProvider::new( + vec![ + "sh".to_string(), + "-c".to_string(), + "test \"$PASSLESS_UV_OPERATION\" = 'registration' && test -n \"$PASSLESS_UV_RELYING_PARTY\"".to_string(), + ], + 30, + ); + + if !provider.available() { + return; + } + + let ctx = VerificationContext::new("registration") + .with_relying_party("example.com") + .with_user("alice") + .with_timeout(60); + + let result = provider.verify(&ctx); + assert_eq!(result.unwrap(), VerificationResult::Accepted); + } + + #[test] + fn test_command_provider_absolute_path() { + let provider = CommandProvider::new(vec!["/bin/true".to_string()], 30); + + if std::path::Path::new("/bin/true").exists() { + assert!(provider.available()); + let ctx = VerificationContext::new("test"); + let result = provider.verify(&ctx); + assert_eq!(result.unwrap(), VerificationResult::Accepted); + } + } +} diff --git a/cmd/passless/src/uv/face.rs b/cmd/passless/src/uv/face.rs new file mode 100644 index 00000000..74198664 --- /dev/null +++ b/cmd/passless/src/uv/face.rs @@ -0,0 +1,462 @@ +//! Face Recognition-based User Verification Provider +//! +//! Uses webcam and face recognition for user verification. +//! +//! # Requirements +//! +//! - Webcam device (V4L2 on Linux) +//! - face_id crate for detection and recognition +//! - ONNX Runtime (downloaded automatically) +//! +//! # Enrollment +//! +//! During enrollment, multiple face embeddings are captured and averaged +//! to create a robust template. The template is stored securely. + +use super::{ + UserVerificationProvider, VerificationContext, VerificationError, VerificationResult, priority, +}; + +use log::{debug, info, warn}; + +use std::path::PathBuf; +use std::sync::{Arc, Mutex}; + +use image::DynamicImage; +use nokhwa::Camera; +use nokhwa::pixel_format::RgbFormat; +use nokhwa::utils::{CameraIndex, RequestedFormat, RequestedFormatType}; + +#[allow(dead_code)] +const DEFAULT_THRESHOLD: f32 = 0.6; +#[allow(dead_code)] +const ENROLLMENT_SAMPLES: usize = 5; + +/// Face recognition provider using webcam +pub struct FaceIdProvider { + camera_index: usize, + threshold: f32, + embeddings_path: PathBuf, + enrolled: Arc>>, + analyzer: Arc>>, +} + +impl FaceIdProvider { + /// Create a new face recognition provider + pub fn new(camera_index: usize, threshold: f32, embeddings_path: Option) -> Self { + let embeddings_path = embeddings_path.unwrap_or_else(|| { + dirs::data_local_dir() + .unwrap_or_else(|| PathBuf::from(".")) + .join("passless") + .join("face_embeddings") + }); + + Self { + camera_index, + threshold, + embeddings_path, + enrolled: Arc::new(Mutex::new(None)), + analyzer: Arc::new(Mutex::new(None)), + } + } + + /// Create a provider if enabled in config + pub fn from_config( + enabled: bool, + camera_index: usize, + threshold: f32, + ) -> Option> { + if enabled { + Some(Box::new(Self::new(camera_index, threshold, None))) + } else { + None + } + } + + /// Get path to the stored embeddings file + fn embeddings_file(&self) -> PathBuf { + self.embeddings_path.join("face_template.bin") + } + + /// Check if a camera is available + fn check_camera_available(&self) -> bool { + let index = CameraIndex::Index(self.camera_index as u32); + let format = + RequestedFormat::new::(RequestedFormatType::AbsoluteHighestFrameRate); + + Camera::new(index, format).is_ok() + } + + /// Initialize the face analyzer (downloads models if needed) + fn init_analyzer(&self) -> Result<(), VerificationError> { + let mut analyzer_guard = self.analyzer.lock().unwrap(); + + if analyzer_guard.is_some() { + return Ok(()); + } + + debug!("Initializing face analyzer (downloading models if needed)..."); + + let rt = tokio::runtime::Runtime::new().map_err(|e| { + VerificationError::DeviceError(format!("Failed to create runtime: {}", e)) + })?; + + let analyzer = rt.block_on(async { + face_id::analyzer::FaceAnalyzer::from_hf() + .build() + .await + .map_err(|e| { + VerificationError::DeviceError(format!( + "Failed to initialize face analyzer: {}", + e + )) + }) + })?; + + *analyzer_guard = Some(analyzer); + info!("Face analyzer initialized successfully"); + Ok(()) + } + + /// Capture a single frame from the camera + fn capture_frame(&self) -> Result { + debug!("Capturing frame from camera {}", self.camera_index); + + let index = CameraIndex::Index(self.camera_index as u32); + let format = + RequestedFormat::new::(RequestedFormatType::AbsoluteHighestFrameRate); + + let mut camera = Camera::new(index, format) + .map_err(|e| VerificationError::DeviceError(format!("Failed to open camera: {}", e)))?; + + camera.open_stream().map_err(|e| { + VerificationError::DeviceError(format!("Failed to start camera stream: {}", e)) + })?; + + let buffer = camera.frame().map_err(|e| { + VerificationError::DeviceError(format!("Failed to capture frame: {}", e)) + })?; + + let decoded = buffer.decode_image::().map_err(|e| { + VerificationError::DeviceError(format!("Failed to decode frame: {}", e)) + })?; + + camera + .stop_stream() + .map_err(|e| warn!("Failed to stop camera stream: {}", e)) + .ok(); + + Ok(DynamicImage::ImageRgb8(decoded)) + } + + /// Extract face embedding from an image + fn extract_embedding(&self, image: &DynamicImage) -> Result, VerificationError> { + self.init_analyzer()?; + + let analyzer_guard = self.analyzer.lock().unwrap(); + let analyzer = analyzer_guard.as_ref().unwrap(); + + debug!("Analyzing image for face"); + + let results = analyzer + .analyze(image) + .map_err(|e| VerificationError::DeviceError(format!("Face analysis failed: {}", e)))?; + + if results.is_empty() { + return Err(VerificationError::DeviceError("No face detected".into())); + } + + if results.len() > 1 { + warn!("Multiple faces detected, using the first one"); + } + + let face = &results[0]; + debug!( + "Extracted embedding with {} dimensions", + face.embedding.len() + ); + Ok(face.embedding.clone()) + } + + /// Load stored embeddings template + fn load_template(&self) -> Result, VerificationError> { + let path = self.embeddings_file(); + if !path.exists() { + return Err(VerificationError::NotEnrolled); + } + + let data = std::fs::read(&path).map_err(|e| { + VerificationError::DeviceError(format!("Failed to read template: {}", e)) + })?; + + let embedding_len = u64::from_le_bytes( + data.get(0..8) + .ok_or_else(|| VerificationError::DeviceError("Invalid template format".into()))? + .try_into() + .unwrap(), + ) as usize; + + let embedding: Vec = data[8..] + .chunks_exact(4) + .map(|chunk| f32::from_le_bytes(chunk.try_into().unwrap())) + .collect(); + + if embedding.len() != embedding_len { + return Err(VerificationError::DeviceError( + "Template length mismatch".into(), + )); + } + + Ok(embedding) + } + + /// Save embeddings template + #[allow(dead_code)] + fn save_template(&self, embedding: &[f32]) -> Result<(), VerificationError> { + std::fs::create_dir_all(&self.embeddings_path).map_err(|e| { + VerificationError::DeviceError(format!("Failed to create embeddings directory: {}", e)) + })?; + + let path = self.embeddings_file(); + + let mut data = Vec::with_capacity(8 + embedding.len() * 4); + data.extend_from_slice(&(embedding.len() as u64).to_le_bytes()); + for &val in embedding { + data.extend_from_slice(&val.to_le_bytes()); + } + + std::fs::write(&path, &data).map_err(|e| { + VerificationError::DeviceError(format!("Failed to write template: {}", e)) + })?; + + info!("Saved face template to {}", path.display()); + Ok(()) + } + + /// Calculate cosine similarity between two embeddings + fn cosine_similarity(a: &[f32], b: &[f32]) -> f32 { + if a.len() != b.len() { + return 0.0; + } + + let dot_product: f32 = a.iter().zip(b.iter()).map(|(x, y)| x * y).sum(); + let mag_a: f32 = a.iter().map(|x| x * x).sum::().sqrt(); + let mag_b: f32 = b.iter().map(|x| x * x).sum::().sqrt(); + + if mag_a == 0.0 || mag_b == 0.0 { + return 0.0; + } + + dot_product / (mag_a * mag_b) + } + + /// Perform face verification + fn do_verification(&self) -> Result { + let template = self.load_template()?; + + let frame = self.capture_frame()?; + let embedding = self.extract_embedding(&frame)?; + + let similarity = Self::cosine_similarity(&template, &embedding); + debug!("Face similarity score: {}", similarity); + + if similarity >= self.threshold { + info!("Face matched (similarity: {})", similarity); + Ok(VerificationResult::Accepted) + } else { + info!( + "Face not matched (similarity: {} < {})", + similarity, self.threshold + ); + Ok(VerificationResult::Denied) + } + } +} + +impl UserVerificationProvider for FaceIdProvider { + fn name(&self) -> &str { + "face" + } + + fn available(&self) -> bool { + self.check_camera_available() + } + + fn verify( + &self, + _context: &VerificationContext, + ) -> Result { + self.do_verification() + } + + fn priority(&self) -> u8 { + priority::FACE + } + + fn supports_enrollment(&self) -> bool { + true + } + + fn requires_enrollment(&self) -> bool { + true + } + + fn is_enrolled(&self) -> bool { + if let Some(enrolled) = *self.enrolled.lock().unwrap() { + return enrolled; + } + + let enrolled = self.embeddings_file().exists(); + *self.enrolled.lock().unwrap() = Some(enrolled); + enrolled + } + + fn enroll(&self) -> Result<(), VerificationError> { + info!( + "Starting face enrollment (capturing {} samples)", + ENROLLMENT_SAMPLES + ); + + self.init_analyzer()?; + + let mut embeddings: Vec> = Vec::with_capacity(ENROLLMENT_SAMPLES); + + for i in 0..ENROLLMENT_SAMPLES { + debug!( + "Capturing enrollment sample {}/{}", + i + 1, + ENROLLMENT_SAMPLES + ); + + let frame = self.capture_frame()?; + let embedding = self.extract_embedding(&frame)?; + embeddings.push(embedding); + + if i < ENROLLMENT_SAMPLES - 1 { + std::thread::sleep(std::time::Duration::from_millis(500)); + } + } + + let avg_embedding: Vec = { + let len = embeddings[0].len(); + let mut sum = vec![0.0f32; len]; + + for embedding in &embeddings { + for (i, &val) in embedding.iter().enumerate() { + sum[i] += val; + } + } + + let n = embeddings.len() as f32; + sum.into_iter().map(|x| x / n).collect() + }; + + self.save_template(&avg_embedding)?; + + *self.enrolled.lock().unwrap() = Some(true); + + info!("Face enrollment completed successfully"); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_face_provider_name() { + let provider = FaceIdProvider::new(0, DEFAULT_THRESHOLD, None); + assert_eq!(provider.name(), "face"); + } + + #[test] + fn test_face_provider_priority() { + let provider = FaceIdProvider::new(0, DEFAULT_THRESHOLD, None); + assert_eq!(provider.priority(), priority::FACE); + } + + #[test] + fn test_face_provider_supports_enrollment() { + let provider = FaceIdProvider::new(0, DEFAULT_THRESHOLD, None); + assert!(provider.supports_enrollment()); + } + + #[test] + fn test_face_provider_requires_enrollment() { + let provider = FaceIdProvider::new(0, DEFAULT_THRESHOLD, None); + assert!(provider.requires_enrollment()); + } + + #[test] + fn test_face_provider_not_enrolled_initially() { + let temp_dir = tempfile::tempdir().unwrap(); + let provider = FaceIdProvider::new( + 0, + DEFAULT_THRESHOLD, + Some(temp_dir.path().join("face_embeddings")), + ); + assert!(!provider.is_enrolled()); + } + + #[test] + fn test_cosine_similarity_identical() { + let a = vec![1.0, 0.0, 0.0]; + let b = vec![1.0, 0.0, 0.0]; + let sim = FaceIdProvider::cosine_similarity(&a, &b); + assert!((sim - 1.0).abs() < 0.001); + } + + #[test] + fn test_cosine_similarity_orthogonal() { + let a = vec![1.0, 0.0, 0.0]; + let b = vec![0.0, 1.0, 0.0]; + let sim = FaceIdProvider::cosine_similarity(&a, &b); + assert!(sim.abs() < 0.001); + } + + #[test] + fn test_cosine_similarity_opposite() { + let a = vec![1.0, 0.0, 0.0]; + let b = vec![-1.0, 0.0, 0.0]; + let sim = FaceIdProvider::cosine_similarity(&a, &b); + assert!((sim - (-1.0)).abs() < 0.001); + } + + #[test] + fn test_cosine_similarity_partial() { + let a = vec![1.0, 0.0, 0.0]; + let b = vec![1.0, 1.0, 0.0]; + let sim = FaceIdProvider::cosine_similarity(&a, &b); + let expected = std::f32::consts::FRAC_1_SQRT_2; + assert!((sim - expected).abs() < 0.01); + } + + #[test] + fn test_cosine_similarity_different_lengths() { + let a = vec![1.0, 0.0, 0.0]; + let b = vec![1.0, 0.0]; + let sim = FaceIdProvider::cosine_similarity(&a, &b); + assert_eq!(sim, 0.0); + } + + #[test] + fn test_cosine_similarity_zero_vectors() { + let a = vec![0.0, 0.0, 0.0]; + let b = vec![1.0, 0.0, 0.0]; + let sim = FaceIdProvider::cosine_similarity(&a, &b); + assert_eq!(sim, 0.0); + } + + #[test] + fn test_cosine_similarity_high_dim() { + let a: Vec = (0..512) + .map(|i| if i % 2 == 0 { 1.0 } else { 0.0 }) + .collect(); + let b: Vec = (0..512) + .map(|i| if i % 2 == 0 { 1.0 } else { 0.5 }) + .collect(); + let sim = FaceIdProvider::cosine_similarity(&a, &b); + assert!(sim > 0.8 && sim < 1.0); + } +} diff --git a/cmd/passless/src/uv/fprintd.rs b/cmd/passless/src/uv/fprintd.rs new file mode 100644 index 00000000..bfec51f4 --- /dev/null +++ b/cmd/passless/src/uv/fprintd.rs @@ -0,0 +1,333 @@ +//! fprintd-based User Verification Provider +//! +//! Uses the fprintd D-Bus service for fingerprint verification. +//! +//! # Requirements +//! +//! - fprintd service must be running +//! - A fingerprint reader must be connected +//! - User must have enrolled fingerprints + +use super::{ + UserVerificationProvider, VerificationContext, VerificationError, VerificationResult, priority, +}; + +use log::{debug, error, info, warn}; + +use std::sync::Arc; +use std::sync::Mutex; + +/// fprintd-based fingerprint verification provider +pub struct FprintdProvider { + available_cache: Mutex>, + runtime: Mutex>>, +} + +impl FprintdProvider { + /// Create a new fprintd provider + pub fn new() -> Self { + Self { + available_cache: Mutex::new(None), + runtime: Mutex::new(None), + } + } + + /// Create a provider if enabled in config + pub fn from_config(enabled: bool) -> Option> { + if enabled { + Some(Box::new(Self::new())) + } else { + None + } + } + + /// Get or create the tokio runtime + fn get_runtime(&self) -> Result, VerificationError> { + let mut runtime_guard = self.runtime.lock().unwrap(); + if runtime_guard.is_none() { + *runtime_guard = Some(Arc::new(tokio::runtime::Runtime::new().map_err(|e| { + VerificationError::DeviceError(format!("Failed to create runtime: {}", e)) + })?)); + } + Ok(runtime_guard.as_ref().unwrap().clone()) + } + + async fn do_verify(timeout_secs: u32) -> Result { + use zbus::Connection; + + debug!("Starting fprintd verification"); + + let conn = Connection::system().await.map_err(|e| { + VerificationError::DeviceError(format!("D-Bus connection failed: {}", e)) + })?; + + let device_path: zbus::zvariant::OwnedObjectPath = conn + .call_method( + Some("net.reactivated.Fprint"), + "/net/reactivated/Fprint/Manager", + Some("net.reactivated.Fprint.Manager"), + "GetDefaultDevice", + &(), + ) + .await + .map_err(|e| VerificationError::NotAvailable(format!("No fingerprint device: {}", e)))? + .body() + .deserialize() + .map_err(|e| VerificationError::DeviceError(format!("Invalid response: {}", e)))?; + + debug!("Found device at path: {:?}", device_path); + + conn.call_method( + Some("net.reactivated.Fprint"), + device_path.as_str(), + Some("net.reactivated.Fprint.Device"), + "Claim", + &"", + ) + .await + .map_err(|e| VerificationError::DeviceError(format!("Failed to claim device: {}", e)))?; + + let result = Self::wait_for_verification(&conn, &device_path, timeout_secs).await; + + let _ = conn + .call_method( + Some("net.reactivated.Fprint"), + device_path.as_str(), + Some("net.reactivated.Fprint.Device"), + "Release", + &(), + ) + .await; + + result + } + + async fn wait_for_verification( + conn: &zbus::Connection, + device_path: &zbus::zvariant::OwnedObjectPath, + timeout_secs: u32, + ) -> Result { + use futures_util::StreamExt; + use zbus::MessageStream; + + conn.call_method( + Some("net.reactivated.Fprint"), + device_path.as_str(), + Some("net.reactivated.Fprint.Device"), + "VerifyStart", + &"any", + ) + .await + .map_err(|e| { + VerificationError::DeviceError(format!("Failed to start verification: {}", e)) + })?; + + let mut stream = MessageStream::from(conn); + + let timeout = tokio::time::sleep(std::time::Duration::from_secs(timeout_secs as u64)); + tokio::pin!(timeout); + + let mut result = VerificationResult::Timeout; + + loop { + tokio::select! { + _ = &mut timeout => { + warn!("Fingerprint verification timed out"); + break; + } + msg = stream.next() => { + match msg { + Some(Ok(msg)) => { + let header = msg.header(); + + if header.interface().map(|i| i.as_str()) != Some("net.reactivated.Fprint.Device") { + continue; + } + if header.member().map(|m| m.as_str()) != Some("VerifyStatus") { + continue; + } + if header.path().map(|p| p.as_str()) != Some(device_path.as_str()) { + continue; + } + + match msg.body().deserialize::<(&str, bool)>() { + Ok((status, _done)) => { + debug!("fprintd status: {}", status); + + match status { + "verify-match" => { + info!("Fingerprint matched"); + result = VerificationResult::Accepted; + break; + } + "verify-no-match" => { + info!("Fingerprint not matched"); + result = VerificationResult::Denied; + break; + } + "verify-retry-scan" + | "verify-swipe-too-short" + | "verify-finger-not-entered" + | "verify-remove-and-retry" => { + debug!("Retry requested: {}", status); + continue; + } + "verify-disconnected" => { + error!("Device disconnected"); + result = VerificationResult::Denied; + break; + } + "verify-unknown-error" => { + error!("Unknown error"); + result = VerificationResult::Denied; + break; + } + _ => { + warn!("Unknown status: {}", status); + continue; + } + } + } + Err(e) => { + error!("Failed to parse signal: {}", e); + continue; + } + } + } + Some(Err(e)) => { + error!("Error receiving message: {}", e); + continue; + } + None => { + error!("Signal stream closed"); + break; + } + } + } + } + } + + let _ = conn + .call_method( + Some("net.reactivated.Fprint"), + device_path.as_str(), + Some("net.reactivated.Fprint.Device"), + "VerifyStop", + &(), + ) + .await; + + Ok(result) + } + + fn check_available(&self) -> bool { + let rt = match self.get_runtime() { + Ok(rt) => rt, + Err(_) => return false, + }; + + rt.block_on(async { + use zbus::Connection; + + match Connection::system().await { + Ok(conn) => { + match conn + .call_method( + Some("net.reactivated.Fprint"), + "/net/reactivated/Fprint/Manager", + Some("net.reactivated.Fprint.Manager"), + "GetDefaultDevice", + &(), + ) + .await + { + Ok(reply) => { + if let Ok(path) = reply + .body() + .deserialize::() + { + info!("fprintd device available at: {:?}", path); + true + } else { + false + } + } + Err(_) => false, + } + } + Err(_) => false, + } + }) + } +} + +impl Default for FprintdProvider { + fn default() -> Self { + Self::new() + } +} + +impl UserVerificationProvider for FprintdProvider { + fn name(&self) -> &str { + "fprintd" + } + + fn available(&self) -> bool { + if let Some(cached) = *self.available_cache.lock().unwrap() { + return cached; + } + + let available = self.check_available(); + *self.available_cache.lock().unwrap() = Some(available); + available + } + + fn verify( + &self, + context: &VerificationContext, + ) -> Result { + let timeout_secs = context.timeout_seconds; + let rt = self.get_runtime()?; + + rt.block_on(async { Self::do_verify(timeout_secs).await }) + } + + fn priority(&self) -> u8 { + priority::FPRINTD + } + + fn supports_enrollment(&self) -> bool { + true + } + + fn requires_enrollment(&self) -> bool { + true + } + + fn is_enrolled(&self) -> bool { + true + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_fprintd_provider_name() { + let provider = FprintdProvider::new(); + assert_eq!(provider.name(), "fprintd"); + } + + #[test] + fn test_fprintd_provider_priority() { + let provider = FprintdProvider::new(); + assert_eq!(provider.priority(), priority::FPRINTD); + } + + #[test] + fn test_fprintd_provider_supports_enrollment() { + let provider = FprintdProvider::new(); + assert!(provider.supports_enrollment()); + } +} diff --git a/cmd/passless/src/uv/manager.rs b/cmd/passless/src/uv/manager.rs new file mode 100644 index 00000000..e1e3b4f8 --- /dev/null +++ b/cmd/passless/src/uv/manager.rs @@ -0,0 +1,319 @@ +//! User Verification Manager +//! +//! Manages a chain of UV providers with automatic fallback. + +use super::{UserVerificationProvider, VerificationContext, VerificationError, VerificationResult}; + +use log::{debug, info, warn}; +use std::cmp::Reverse; + +/// Manages user verification providers with fallback chain +pub struct UserVerificationManager { + providers: Vec>, +} + +impl UserVerificationManager { + /// Create a new manager with the given providers + /// + /// Providers are sorted by priority (highest first) and will be tried in order. + pub fn new(providers: Vec>) -> Self { + let mut providers = providers; + providers.sort_by_key(|p| Reverse(p.priority())); + Self { providers } + } + + /// Create a manager with default providers (empty) + #[allow(dead_code)] + pub fn empty() -> Self { + Self { + providers: Vec::new(), + } + } + + /// Add a provider to the chain + #[allow(dead_code)] + pub fn add_provider(&mut self, provider: Box) { + self.providers.push(provider); + self.providers.sort_by_key(|p| Reverse(p.priority())); + } + + /// Get list of available providers + pub fn available_providers(&self) -> Vec<&dyn UserVerificationProvider> { + self.providers + .iter() + .filter(|p| p.available()) + .map(|p| p.as_ref()) + .collect() + } + + /// Perform user verification with automatic fallback + /// + /// Tries providers in priority order until one succeeds. + /// Falls back to next provider on error or timeout. + /// Returns first non-error result (Accepted, Denied, or Timeout from last provider). + pub fn verify( + &self, + context: &VerificationContext, + ) -> Result { + let available: Vec<_> = self.available_providers(); + + if available.is_empty() { + warn!("No UV providers available"); + return Err(VerificationError::NotAvailable( + "No user verification providers available".into(), + )); + } + + info!( + "Starting user verification with {} available provider(s)", + available.len() + ); + + let mut last_error: Option = None; + + for provider in &available { + debug!( + "Trying UV provider: {} (priority {})", + provider.name(), + provider.priority() + ); + + // Check enrollment + if provider.requires_enrollment() && !provider.is_enrolled() { + debug!("Provider {} not enrolled, skipping", provider.name()); + last_error = Some(VerificationError::NotEnrolled); + continue; + } + + match provider.verify(context) { + Ok(result @ VerificationResult::Accepted) => { + info!("User verification accepted via {}", provider.name()); + return Ok(result); + } + Ok(result @ VerificationResult::Denied) => { + info!("User verification denied via {}", provider.name()); + return Ok(result); + } + Ok(VerificationResult::Timeout) => { + warn!("UV provider {} timed out, trying next", provider.name()); + last_error = Some(VerificationError::Other("Timeout".into())); + continue; + } + Err(e) => { + warn!("UV provider {} failed: {}, trying next", provider.name(), e); + last_error = Some(e); + continue; + } + } + } + + // All providers failed + let error = last_error.unwrap_or_else(|| { + VerificationError::Other("All verification providers failed".into()) + }); + Err(error) + } + + /// Get a provider by name for enrollment + #[allow(dead_code)] + pub fn get_provider(&self, name: &str) -> Option<&dyn UserVerificationProvider> { + self.providers + .iter() + .find(|p| p.name() == name) + .map(|p| p.as_ref()) + } + + /// List all providers (for CLI status command) + #[allow(dead_code)] + pub fn list_providers(&self) -> &[Box] { + &self.providers + } +} + +#[cfg(test)] +mod tests { + use super::*; + + struct MockProvider { + name: &'static str, + priority: u8, + available: bool, + result: VerificationResult, + requires_enrollment: bool, + is_enrolled: bool, + } + + impl MockProvider { + fn new( + name: &'static str, + priority: u8, + available: bool, + result: VerificationResult, + ) -> Self { + Self { + name, + priority, + available, + result, + requires_enrollment: false, + is_enrolled: true, + } + } + + fn with_enrollment(mut self, requires: bool, enrolled: bool) -> Self { + self.requires_enrollment = requires; + self.is_enrolled = enrolled; + self + } + } + + impl UserVerificationProvider for MockProvider { + fn name(&self) -> &str { + self.name + } + + fn available(&self) -> bool { + self.available + } + + fn verify( + &self, + _context: &VerificationContext, + ) -> Result { + if !self.available { + return Err(VerificationError::NotAvailable("Not available".into())); + } + Ok(self.result) + } + + fn priority(&self) -> u8 { + self.priority + } + + fn requires_enrollment(&self) -> bool { + self.requires_enrollment + } + + fn is_enrolled(&self) -> bool { + self.is_enrolled + } + } + + #[test] + fn test_provider_ordering_by_priority() { + let low = MockProvider::new("low", 10, true, VerificationResult::Accepted); + let high = MockProvider::new("high", 100, true, VerificationResult::Accepted); + + let manager = UserVerificationManager::new(vec![Box::new(low), Box::new(high)]); + + assert_eq!(manager.list_providers()[0].name(), "high"); + assert_eq!(manager.list_providers()[1].name(), "low"); + } + + #[test] + fn test_fallback_on_timeout() { + let fail = MockProvider::new("fail", 100, true, VerificationResult::Timeout); + let succeed = MockProvider::new("succeed", 50, true, VerificationResult::Accepted); + + let manager = UserVerificationManager::new(vec![Box::new(fail), Box::new(succeed)]); + + let ctx = VerificationContext::new("test"); + let result = manager.verify(&ctx); + assert_eq!(result.unwrap(), VerificationResult::Accepted); + } + + #[test] + fn test_no_available_providers() { + let unavailable = + MockProvider::new("unavailable", 100, false, VerificationResult::Accepted); + + let manager = UserVerificationManager::new(vec![Box::new(unavailable)]); + let ctx = VerificationContext::new("test"); + let result = manager.verify(&ctx); + + assert!(result.is_err()); + match result.unwrap_err() { + VerificationError::NotAvailable(_) => {} + _ => panic!("Expected NotAvailable error"), + } + } + + #[test] + fn test_denied_returns_immediately() { + let deny = MockProvider::new("deny", 100, true, VerificationResult::Denied); + let accept = MockProvider::new("accept", 50, true, VerificationResult::Accepted); + + let manager = UserVerificationManager::new(vec![Box::new(deny), Box::new(accept)]); + + let ctx = VerificationContext::new("test"); + let result = manager.verify(&ctx); + assert_eq!(result.unwrap(), VerificationResult::Denied); + } + + #[test] + fn test_skip_unenrolled_provider() { + let unenrolled = MockProvider::new("unenrolled", 100, true, VerificationResult::Accepted) + .with_enrollment(true, false); + let fallback = MockProvider::new("fallback", 50, true, VerificationResult::Accepted); + + let manager = UserVerificationManager::new(vec![Box::new(unenrolled), Box::new(fallback)]); + + let ctx = VerificationContext::new("test"); + let result = manager.verify(&ctx); + assert_eq!(result.unwrap(), VerificationResult::Accepted); + } + + #[test] + fn test_all_providers_fail() { + let fail1 = MockProvider::new("fail1", 100, true, VerificationResult::Timeout); + let fail2 = MockProvider::new("fail2", 50, true, VerificationResult::Timeout); + + let manager = UserVerificationManager::new(vec![Box::new(fail1), Box::new(fail2)]); + + let ctx = VerificationContext::new("test"); + let result = manager.verify(&ctx); + assert!(result.is_err()); + } + + #[test] + fn test_available_providers_filter() { + let available = MockProvider::new("available", 100, true, VerificationResult::Accepted); + let unavailable = MockProvider::new("unavailable", 50, false, VerificationResult::Accepted); + + let manager = + UserVerificationManager::new(vec![Box::new(available), Box::new(unavailable)]); + + let available_list = manager.available_providers(); + assert_eq!(available_list.len(), 1); + assert_eq!(available_list[0].name(), "available"); + } + + #[test] + fn test_get_provider_by_name() { + let provider = MockProvider::new("test_provider", 100, true, VerificationResult::Accepted); + + let manager = UserVerificationManager::new(vec![Box::new(provider)]); + + assert!(manager.get_provider("test_provider").is_some()); + assert!(manager.get_provider("nonexistent").is_none()); + } + + #[test] + fn test_empty_manager() { + let manager = UserVerificationManager::empty(); + assert!(manager.list_providers().is_empty()); + + let ctx = VerificationContext::new("test"); + let result = manager.verify(&ctx); + assert!(result.is_err()); + } + + #[test] + fn test_add_provider() { + let mut manager = UserVerificationManager::empty(); + let provider = MockProvider::new("test", 100, true, VerificationResult::Accepted); + + manager.add_provider(Box::new(provider)); + assert_eq!(manager.list_providers().len(), 1); + } +} diff --git a/cmd/passless/src/uv/mod.rs b/cmd/passless/src/uv/mod.rs new file mode 100644 index 00000000..a97ff104 --- /dev/null +++ b/cmd/passless/src/uv/mod.rs @@ -0,0 +1,280 @@ +//! User Verification (UV) module for FIDO2 authenticator +//! +//! This module provides a pluggable user verification system with multiple providers: +//! - [`FprintdProvider`]: Fingerprint verification via D-Bus +//! - [`FaceIdProvider`]: Face recognition via webcam (requires "face" feature) +//! - [`NotificationProvider`]: Desktop notification (fallback) +//! - [`CommandProvider`]: Custom command/script execution +//! +//! # Architecture +//! +//! Providers implement the [`UserVerificationProvider`] trait and are managed by +//! [`UserVerificationManager`], which handles priority ordering and automatic fallback. +//! +//! # Example +//! +//! ```ignore +//! use passless::uv::{ +//! UserVerificationManager, FprintdProvider, NotificationProvider, +//! VerificationContext, UserVerificationProvider, +//! }; +//! +//! let manager = UserVerificationManager::new(vec![ +//! Box::new(FprintdProvider::new()), +//! Box::new(NotificationProvider::new(30)), +//! ]); +//! +//! let ctx = VerificationContext::new("authentication") +//! .with_relying_party("example.com"); +//! +//! match manager.verify(&ctx) { +//! Ok(VerificationResult::Accepted) => { /* success */ } +//! Ok(VerificationResult::Denied) => { /* denied */ } +//! Ok(VerificationResult::Timeout) => { /* timeout */ } +//! Err(e) => { /* error */ } +//! } +//! ``` + +mod command; +mod fprintd; +mod manager; +mod notification; + +#[cfg(feature = "face")] +mod face; + +pub use command::CommandProvider; +#[cfg(feature = "face")] +pub use face::FaceIdProvider; +pub use fprintd::FprintdProvider; +pub use manager::UserVerificationManager; +pub use notification::NotificationProvider; + +/// Default timeout for user verification (seconds) +pub const DEFAULT_TIMEOUT_SECONDS: u32 = 30; + +/// Provider priority constants (higher = preferred) +pub mod priority { + /// Fingerprint provider priority (highest - most secure biometric) + pub const FPRINTD: u8 = 100; + /// Face recognition provider priority + #[cfg(feature = "face")] + pub const FACE: u8 = 90; + /// Custom command provider priority (configurable) + pub const COMMAND: u8 = 50; + /// Notification provider priority (lowest - fallback) + pub const NOTIFICATION: u8 = 10; +} + +/// Result of a user verification attempt +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum VerificationResult { + /// User successfully verified + Accepted, + /// User explicitly denied verification + Denied, + /// Verification timed out + Timeout, +} + +/// Error during user verification +#[derive(Debug, Clone, thiserror::Error)] +pub enum VerificationError { + /// Provider is not available (not installed, no device, etc.) + #[error("Not available: {0}")] + NotAvailable(String), + /// Device or system error during verification + #[error("Device error: {0}")] + DeviceError(String), + /// User cancelled the operation + #[error("User cancelled")] + #[allow(dead_code)] + UserCancelled, + /// Provider is not enrolled (no biometric data registered) + #[error("Not enrolled")] + NotEnrolled, + /// Unknown error + #[error("Error: {0}")] + Other(String), +} + +impl From for VerificationError { + fn from(err: std::io::Error) -> Self { + VerificationError::DeviceError(err.to_string()) + } +} + +impl From for VerificationError { + fn from(err: zbus::Error) -> Self { + match err { + zbus::Error::MethodError(_, _, _) => { + VerificationError::DeviceError(format!("D-Bus method failed: {}", err)) + } + zbus::Error::InterfaceNotFound => { + VerificationError::NotAvailable("D-Bus interface not found".into()) + } + zbus::Error::FDO(e) => { + VerificationError::DeviceError(format!("D-Bus freedesktop error: {}", e)) + } + _ => VerificationError::DeviceError(format!("D-Bus error: {}", err)), + } + } +} + +impl From for VerificationError { + fn from(err: zbus::zvariant::Error) -> Self { + VerificationError::DeviceError(format!("D-Bus variant error: {}", err)) + } +} + +/// Context for user verification request +#[derive(Debug, Clone)] +pub struct VerificationContext { + /// Type of operation (e.g., "registration", "authentication") + pub operation: String, + /// Relying party ID (domain) + pub relying_party: Option, + /// User identifier + pub user: Option, + /// Timeout in seconds + pub timeout_seconds: u32, +} + +impl VerificationContext { + /// Create a new verification context + pub fn new(operation: impl Into) -> Self { + Self { + operation: operation.into(), + relying_party: None, + user: None, + timeout_seconds: DEFAULT_TIMEOUT_SECONDS, + } + } + + /// Set the relying party + pub fn with_relying_party(mut self, rp: impl Into) -> Self { + self.relying_party = Some(rp.into()); + self + } + + /// Set the user + pub fn with_user(mut self, user: impl Into) -> Self { + self.user = Some(user.into()); + self + } + + /// Set the timeout + pub fn with_timeout(mut self, seconds: u32) -> Self { + self.timeout_seconds = seconds; + self + } +} + +/// Trait for user verification providers +/// +/// Each provider implements biometric or other verification methods. +/// Providers are tried in order of priority until one succeeds. +pub trait UserVerificationProvider: Send + Sync { + /// Unique name for this provider + fn name(&self) -> &str; + + /// Check if this provider is available (device present, service running, etc.) + fn available(&self) -> bool; + + /// Perform user verification + /// + /// Returns: + /// - `Ok(Accepted)` - User successfully verified + /// - `Ok(Denied)` - User explicitly denied + /// - `Ok(Timeout)` - Verification timed out + /// - `Err(_)` - Error occurred, try next provider + fn verify( + &self, + context: &VerificationContext, + ) -> Result; + + /// Priority of this provider (higher = preferred) + /// + /// See [`priority`] module for standard values. + fn priority(&self) -> u8 { + priority::COMMAND + } + + /// Whether this provider supports enrollment + fn supports_enrollment(&self) -> bool { + false + } + + /// Enroll user for verification (e.g., capture face, fingerprint) + #[allow(dead_code)] + fn enroll(&self) -> Result<(), VerificationError> { + Err(VerificationError::NotAvailable( + "Enrollment not supported".into(), + )) + } + + /// Whether this provider requires enrollment before use + fn requires_enrollment(&self) -> bool { + self.supports_enrollment() + } + + /// Check if the user is enrolled + fn is_enrolled(&self) -> bool { + true + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_context_new() { + let ctx = VerificationContext::new("registration"); + assert_eq!(ctx.operation, "registration"); + assert_eq!(ctx.relying_party, None); + assert_eq!(ctx.user, None); + assert_eq!(ctx.timeout_seconds, DEFAULT_TIMEOUT_SECONDS); + } + + #[test] + fn test_context_builder() { + let ctx = VerificationContext::new("authentication") + .with_relying_party("example.com") + .with_user("alice") + .with_timeout(60); + + assert_eq!(ctx.operation, "authentication"); + assert_eq!(ctx.relying_party, Some("example.com".to_string())); + assert_eq!(ctx.user, Some("alice".to_string())); + assert_eq!(ctx.timeout_seconds, 60); + } + + #[test] + fn test_verification_error_display() { + assert_eq!( + VerificationError::NotAvailable("test".into()).to_string(), + "Not available: test" + ); + assert_eq!( + VerificationError::DeviceError("failed".into()).to_string(), + "Device error: failed" + ); + assert_eq!( + VerificationError::UserCancelled.to_string(), + "User cancelled" + ); + assert_eq!(VerificationError::NotEnrolled.to_string(), "Not enrolled"); + assert_eq!( + VerificationError::Other("oops".into()).to_string(), + "Error: oops" + ); + } + + #[test] + fn test_verification_error_from_io() { + let err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found"); + let uv_err: VerificationError = err.into(); + assert!(matches!(uv_err, VerificationError::DeviceError(_))); + } +} diff --git a/cmd/passless/src/uv/notification.rs b/cmd/passless/src/uv/notification.rs new file mode 100644 index 00000000..745fc4ac --- /dev/null +++ b/cmd/passless/src/uv/notification.rs @@ -0,0 +1,171 @@ +//! Notification-based User Verification Provider +//! +//! Uses desktop notifications for user verification. +//! This is the fallback provider when no biometric methods are available. + +use super::{ + UserVerificationProvider, VerificationContext, VerificationError, VerificationResult, priority, +}; + +use log::debug; + +use std::sync::{Arc, Mutex}; + +use notify_rust::{Notification, Timeout}; + +/// Check if the notification server requires special handling +fn requires_default_action() -> bool { + notify_rust::get_server_information() + .map(|info| { + let server_name = info.name.to_lowercase(); + debug!( + "Notification server: {} (version: {})", + info.name, info.version + ); + + match (server_name.as_str(), info.version.as_str()) { + ("notify-osd", "1.0") | ("mako", "0.0.0") => { + debug!("Detected {} - using default action mode", server_name); + true + } + _ => false, + } + }) + .unwrap_or_else(|e| { + debug!("Failed to get notification server info: {}", e); + false + }) +} + +/// Notification-based user verification provider +pub struct NotificationProvider { + #[allow(dead_code)] + timeout_seconds: u32, +} + +impl NotificationProvider { + /// Create a new notification provider + pub fn new(timeout_seconds: u32) -> Self { + Self { timeout_seconds } + } + + /// Create a provider with timeout + pub fn from_config(timeout_seconds: u32) -> Box { + Box::new(Self::new(timeout_seconds)) + } + + /// Show verification notification and wait for response + fn show_notification( + &self, + context: &VerificationContext, + ) -> Result { + let mut message = format!("Operation: {}", context.operation); + if let Some(rp) = &context.relying_party { + message.push_str(&format!("\nRelying Party: {}", rp)); + } + if let Some(user) = &context.user { + message.push_str(&format!("\nUser: {}", user)); + } + + debug!("Showing user verification notification"); + + let default_means_accept = requires_default_action(); + let action_result = Arc::new(Mutex::new(None)); + let action_result_clone = action_result.clone(); + + let mut notification = Notification::new(); + notification + .summary("User Verification Required") + .body(&message) + .icon("security-high") + .timeout(Timeout::Milliseconds(context.timeout_seconds * 1000)); + + if default_means_accept { + notification.action("default", ""); + } else { + notification.action("approve", "Accept"); + notification.action("deny", "Deny"); + } + + let handle = notification.show().map_err(|e| { + VerificationError::DeviceError(format!("Failed to show notification: {}", e)) + })?; + + handle.wait_for_action(|action| { + debug!("User action received: {}", action); + let mut result = action_result_clone + .lock() + .expect("Failed to lock action result"); + *result = Some(action.to_string()); + }); + + let action = action_result + .lock() + .expect("Failed to lock action result") + .clone() + .unwrap_or_else(|| "__closed".to_string()); + + let accepted = match action.as_str() { + "approve" => true, + "deny" => false, + "default" => default_means_accept, + "__closed" => false, + other => { + debug!("Unknown action '{}' - treating as denied", other); + false + } + }; + + if accepted { + debug!("User verification accepted via notification"); + Ok(VerificationResult::Accepted) + } else { + debug!("User verification denied or notification closed"); + Ok(VerificationResult::Denied) + } + } +} + +impl UserVerificationProvider for NotificationProvider { + fn name(&self) -> &str { + "notification" + } + + fn available(&self) -> bool { + true + } + + fn verify( + &self, + context: &VerificationContext, + ) -> Result { + self.show_notification(context) + } + + fn priority(&self) -> u8 { + priority::NOTIFICATION + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_notification_provider_always_available() { + let provider = NotificationProvider::new(30); + assert!(provider.available()); + } + + #[test] + fn test_notification_provider_priority() { + let provider = NotificationProvider::new(30); + assert_eq!(provider.priority(), priority::NOTIFICATION); + } + + #[test] + fn test_notification_provider_name() { + let provider = NotificationProvider::new(30); + assert_eq!(provider.name(), "notification"); + } +} diff --git a/docs/adr/001-biometric-user-verification.md b/docs/adr/001-biometric-user-verification.md new file mode 100644 index 00000000..71694182 --- /dev/null +++ b/docs/adr/001-biometric-user-verification.md @@ -0,0 +1,104 @@ +# ADR 001: Biometric User Verification + +## Status + +Accepted + +## Context + +Passless currently uses desktop notifications for user verification (UV) in FIDO2 operations. This is a "something you have" factor (the device with passless running). To provide true multi-factor authentication, we need "something you are" - biometric verification. + +Linux provides several options for biometric authentication: + +1. **fprintd** - Standard Linux fingerprint daemon with D-Bus interface +2. **Webcam face recognition** - Using modern ML models via ONNX +3. **External commands** - Allow users to integrate any biometric tool + +## Decision + +We will implement a pluggable user verification system with multiple providers: + +### Architecture + +``` +UserVerificationManager (chain of responsibility) +├── FprintdProvider (fingerprint via D-Bus) +├── FaceIdProvider (webcam + face recognition) +├── NotificationProvider (fallback, current behavior) +└── CommandProvider (user-defined script) +``` + +### Technology Choices + +1. **D-Bus Integration**: Use `zbus` crate (pure Rust, async-native) +2. **Webcam Capture**: Use `nokhwa` crate (cross-platform, no OpenCV dependency) +3. **Face Recognition**: Use `face_id` crate (SCRFD detection + ArcFace embeddings via ONNX Runtime) +4. **Provider Selection**: Configurable chain with automatic fallback + +### Configuration + +```toml +[uv] +providers = ["fprintd", "face", "notification"] # Try in order + +[uv.fprintd] +timeout_seconds = 30 + +[uv.face] +enabled = true +camera_index = 0 +threshold = 0.6 +``` + +## Consequences + +### Positive + +- True biometric verification for FIDO2 operations +- Multiple provider options for different hardware +- Automatic fallback ensures users aren't locked out +- Modern Rust stack (zbus, face_id) with minimal C dependencies + +### Negative + +- Increased binary size and dependencies +- face_id downloads ONNX models from HuggingFace on first use +- Webcam access requires proper permissions +- fprintd requires system-level setup + +### Neutral + +- Face recognition provider is feature-gated (`--features face`) to reduce binary size +- Other providers (fprintd, notification, command) are always included +- Face embeddings stored locally in user's storage backend + +## Implementation + +1. Create `uv/` module with `UserVerificationProvider` trait +2. Implement provider chain in `UserVerificationManager` +3. Implement each provider (fprintd, face, notification, command) +4. Update authenticator to use manager instead of direct notifications + +### Build Options + +```bash +# Standard build (fprintd + notification providers) +cargo build + +# With face recognition support (adds ~50MB+ due to ONNX Runtime) +cargo build --features face +``` + +## Alternatives Considered + +1. **All providers feature-gated**: Rejected - fprintd and notification are lightweight enough to include by default +2. **Howdy integration**: Rejected - Python-based, command provider allows users to integrate it themselves +3. **OpenCV for face recognition**: Rejected - heavy C++ dependency, face_id uses ONNX Runtime instead +4. **PAM integration**: Rejected - indirect, requires PAM conversation setup + +## References + +- [fprintd D-Bus API](https://fprint.freedesktop.org/) +- [nokhwa crate](https://crates.io/crates/nokhwa) +- [face_id crate](https://crates.io/crates/face_id) +- [zbus crate](https://crates.io/crates/zbus) diff --git a/passless-core/src/config.rs b/passless-core/src/config.rs index 6657828c..45ca1140 100644 --- a/passless-core/src/config.rs +++ b/passless-core/src/config.rs @@ -283,6 +283,206 @@ pub struct PinConfig { pub auto_lock_timeout: u32, } +/// User verification provider type +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)] +#[serde(rename_all = "lowercase")] +pub enum UvProviderType { + /// Fingerprint verification via fprintd D-Bus service + Fprintd, + /// Face recognition via webcam + Face, + /// Desktop notification (fallback) + Notification, + /// Custom command/script + Command, + /// Use all available providers in priority order + #[default] + Auto, +} + +impl std::str::FromStr for UvProviderType { + type Err = String; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "fprintd" | "fingerprint" => Ok(UvProviderType::Fprintd), + "face" | "faceid" => Ok(UvProviderType::Face), + "notification" | "notify" => Ok(UvProviderType::Notification), + "command" | "cmd" => Ok(UvProviderType::Command), + "auto" | "all" => Ok(UvProviderType::Auto), + _ => Err(format!( + "Invalid UV provider '{}'. Must be: fprintd, face, notification, command, or auto", + s + )), + } + } +} + +impl std::fmt::Display for UvProviderType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + UvProviderType::Fprintd => write!(f, "fprintd"), + UvProviderType::Face => write!(f, "face"), + UvProviderType::Notification => write!(f, "notification"), + UvProviderType::Command => write!(f, "command"), + UvProviderType::Auto => write!(f, "auto"), + } + } +} + +/// fprintd provider configuration +#[derive(ClapSerde, Debug, Clone, Serialize, Deserialize, ConfigDoc)] +#[group(id = "uv-fprintd")] +pub struct FprintdConfig { + /// Enable fprintd provider + #[arg( + long = "fprintd-enabled", + id = "fprintd-enabled", + env = "PASSLESS_FPRINTD_ENABLED", + action = ArgAction::Set, + require_equals = true, + num_args = 0..=1, + default_missing_value = "true" + )] + #[serde(default)] + #[default(true)] + pub enabled: bool, +} + +/// Face recognition provider configuration +#[derive(ClapSerde, Debug, Clone, Serialize, Deserialize, ConfigDoc)] +#[group(id = "uv-face")] +pub struct FaceConfig { + /// Enable face recognition provider + #[arg( + long = "face-enabled", + id = "face-enabled", + env = "PASSLESS_FACE_ENABLED", + action = ArgAction::Set, + require_equals = true, + num_args = 0..=1, + default_missing_value = "true" + )] + #[serde(default)] + #[default(true)] + pub enabled: bool, + + /// Camera device index (0 = first camera) + #[arg( + long = "face-camera-index", + id = "face-camera-index", + env = "PASSLESS_FACE_CAMERA_INDEX", + value_name = "INDEX" + )] + #[serde(default)] + #[default(0)] + pub camera_index: usize, + + /// Face matching threshold (0.0-1.0, higher = stricter) + #[arg( + long = "face-threshold", + id = "face-threshold", + env = "PASSLESS_FACE_THRESHOLD", + value_name = "THRESHOLD" + )] + #[serde(default)] + #[default(0.6)] + pub threshold: f32, +} + +impl FaceConfig { + /// Validate the configuration + pub fn validate(&self) -> Result<(), String> { + if self.threshold < 0.0 || self.threshold > 1.0 { + return Err(format!( + "Face threshold must be between 0.0 and 1.0, got {}", + self.threshold + )); + } + Ok(()) + } +} + +/// Command provider configuration +#[derive(ClapSerde, Debug, Clone, Serialize, Deserialize, ConfigDoc)] +#[group(id = "uv-cmd")] +pub struct CommandConfig { + /// Enable command provider + #[arg( + long = "uv-command-enabled", + id = "uv-command-enabled", + env = "PASSLESS_UV_COMMAND_ENABLED", + action = ArgAction::Set, + require_equals = true, + num_args = 0..=1, + default_missing_value = "false" + )] + #[serde(default)] + #[default(false)] + pub enabled: bool, + + /// Command to execute for verification (exit 0 = accept, non-zero = deny) + /// Command receives context via environment variables: + /// PASSLESS_UV_OPERATION, PASSLESS_UV_RELYING_PARTY, PASSLESS_UV_USER + #[arg( + long = "uv-command", + id = "uv-command-arg", + env = "PASSLESS_UV_COMMAND", + value_name = "COMMAND", + value_delimiter = ' ' + )] + #[serde(default)] + pub command: Vec, +} + +/// User verification configuration +#[derive(ClapSerde, Debug, Clone, Serialize, Deserialize, ConfigDoc)] +#[group(id = "uv")] +pub struct UvConfig { + /// User verification provider selection + /// - "auto": Use all available providers in priority order (default) + /// - "fprintd": Fingerprint only + /// - "face": Face recognition only + /// - "notification": Desktop notification only + /// - "command": Custom command only + #[arg( + long = "uv-provider", + env = "PASSLESS_UV_PROVIDER", + value_name = "PROVIDER" + )] + #[serde(default)] + #[default(UvProviderType::Auto)] + pub provider: UvProviderType, + + /// Default timeout for verification (seconds) + #[arg( + long = "uv-timeout", + env = "PASSLESS_UV_TIMEOUT", + value_name = "SECONDS" + )] + #[serde(default)] + #[default(30)] + pub timeout_seconds: u32, + + /// fprintd configuration + #[clap_serde] + #[serde(default)] + #[command(flatten)] + pub fprintd: FprintdConfig, + + /// Face recognition configuration + #[clap_serde] + #[serde(default)] + #[command(flatten)] + pub face: FaceConfig, + + /// Command provider configuration + #[clap_serde] + #[serde(default)] + #[command(flatten)] + pub command: CommandConfig, +} + impl SecurityConfig { /// Apply security hardening measures pub fn apply_hardening(&self) -> Result<(), Box> { @@ -322,7 +522,7 @@ impl SecurityConfig { } else { log::warn!( "mlock capability probe failed - memory locking may not be available.\n\ - Hint: grant CAP_IPC_LOCK to the binary with: 'sudo setcap cap_ipc_lock=+ep $(which passless)'" + Hint: grant CAP_IPC_LOCK to the binary with: 'sudo setcaps cap_ipc_lock=+ep $(which passless)'" ); } Ok(()) @@ -384,6 +584,12 @@ pub struct AppConfig { #[serde(default)] #[command(flatten)] pub pin: PinConfig, + + /// User verification configuration + #[clap_serde] + #[serde(default)] + #[command(flatten)] + pub uv: UvConfig, } /// Backend-specific configuration @@ -471,6 +677,17 @@ impl AppConfig { pub fn pin_config(&self) -> PinConfig { self.pin.clone() } + + /// Get user verification configuration + pub fn uv_config(&self) -> UvConfig { + self.uv.clone() + } + + /// Validate the configuration + pub fn validate(&self) -> Result<(), String> { + self.uv.face.validate()?; + Ok(()) + } } /// CLI arguments structure