secret-service-4.0.0/.cargo_vcs_info.json0000644000000001360000000000100137600ustar { "git": { "sha1": "76b9732b0d847b2ab604ee4902d8926d38dff22a" }, "path_in_vcs": "" }secret-service-4.0.0/.github/workflows/ci.yaml000064400000000000000000000075761046102023000174430ustar 00000000000000name: CI permissions: contents: read on: pull_request: push: branches: - master env: CARGO_NET_RETRY: 3 jobs: clippy: name: Clippy runs-on: ubuntu-latest strategy: matrix: rust: - stable feature: - rt-async-io-crypto-rust - rt-async-io-crypto-openssl - rt-tokio-crypto-rust - rt-tokio-crypto-openssl steps: - uses: actions/checkout@v4 with: persist-credentials: false - uses: actions-rust-lang/setup-rust-toolchain@v1 with: toolchain: ${{ matrix.rust }} components: clippy - uses: actions/cache@v4 with: path: | ~/.cargo/registry ~/.cargo/git target key: $clippy-cache-${{ steps.toolchain.outputs.rustc_hash }}-${{ hashFiles('**/Cargo.lock') }} - name: "Clippy ${{ matrix.rust }}" run: cargo clippy --features=${{ matrix.feature }} --all-targets --all -- -D clippy::dbg_macro -D warnings -F unused_must_use fmt: name: Rustfmt runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: persist-credentials: false - uses: actions-rust-lang/setup-rust-toolchain@v1 with: toolchain: stable components: rustfmt - name: "Check formatting" run: cargo fmt --all -- --check test: name: Tests runs-on: ubuntu-latest strategy: matrix: rust: - stable feature: - rt-async-io-crypto-rust - rt-async-io-crypto-openssl - rt-tokio-crypto-rust - rt-tokio-crypto-openssl steps: - uses: actions/checkout@v4 with: persist-credentials: false - uses: actions-rust-lang/setup-rust-toolchain@v1 with: toolchain: ${{ matrix.rust }} - uses: actions/cache@v4 with: path: | ~/.cargo/registry ~/.cargo/git target key: $test-cache-${{ steps.toolchain.outputs.rustc_hash }}-${{ hashFiles('**/Cargo.lock') }} - name: Install gnome-keyring run: sudo apt-get install -y gnome-keyring - name: Start gnome-keyring # run gnome-keyring with 'foobar' as password for the login keyring # this will create a new login keyring and unlock it # the login password doesn't matter, but the keyring must be unlocked for the tests to work run: gnome-keyring-daemon --components=secrets --daemonize --unlock <<< 'foobar' - name: Run tests # run tests single-threaded to avoid race conditions run: cargo test --features=${{ matrix.feature }} -- --test-threads=1 - name: Run example run: cargo run --features=${{ matrix.feature }} --example example # MSRV, influenced by zbus. check_msrv: name: Check MSRV runs-on: ubuntu-latest strategy: matrix: feature: - rt-async-io-crypto-rust - rt-async-io-crypto-openssl - rt-tokio-crypto-rust - rt-tokio-crypto-openssl steps: - uses: actions/checkout@v4 with: persist-credentials: false - uses: actions-rust-lang/setup-rust-toolchain@v1 with: toolchain: "1.75.0" components: clippy - uses: actions/cache@v4 with: path: | ~/.cargo/registry ~/.cargo/git target key: $clippy-cache-${{ steps.toolchain.outputs.rustc_hash }}-${{ hashFiles('**/Cargo.lock') }} - name: Generate lockfile run: | cargo generate-lockfile - name: Clippy MSRV run: cargo clippy --features=${{ matrix.feature }} --all-targets --all -- -D clippy::dbg_macro -D warnings -F unused_must_use secret-service-4.0.0/.gitignore000064400000000000000000000000371046102023000145400ustar 00000000000000bin target Cargo.lock .vscode/secret-service-4.0.0/CHANGELOG.md000064400000000000000000000001621046102023000143600ustar 00000000000000The detailed list of changes in each release can be found at https://github.com/hwchen/secret-service-rs/releases.secret-service-4.0.0/Cargo.lock0000644000001126630000000000100117440ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "addr2line" version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" dependencies = [ "gimli", ] [[package]] name = "adler" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aes" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", "cipher", "cpufeatures", ] [[package]] name = "aho-corasick" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "async-broadcast" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20cd0e2e25ea8e5f7e9df04578dc6cf5c83577fd09b1a46aaf5c85e1c33f2a7e" dependencies = [ "event-listener", "event-listener-strategy", "futures-core", "pin-project-lite", ] [[package]] name = "async-channel" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" dependencies = [ "concurrent-queue", "event-listener-strategy", "futures-core", "pin-project-lite", ] [[package]] name = "async-executor" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8828ec6e544c02b0d6691d21ed9f9218d0384a82542855073c2a3f58304aaf0" dependencies = [ "async-task", "concurrent-queue", "fastrand", "futures-lite", "slab", ] [[package]] name = "async-fs" version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebcd09b382f40fcd159c2d695175b2ae620ffa5f3bd6f664131efff4e8b9e04a" dependencies = [ "async-lock", "blocking", "futures-lite", ] [[package]] name = "async-io" version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d6baa8f0178795da0e71bc42c9e5d13261aac7ee549853162e66a241ba17964" dependencies = [ "async-lock", "cfg-if", "concurrent-queue", "futures-io", "futures-lite", "parking", "polling", "rustix", "slab", "tracing", "windows-sys 0.52.0", ] [[package]] name = "async-lock" version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" dependencies = [ "event-listener", "event-listener-strategy", "pin-project-lite", ] [[package]] name = "async-process" version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7eda79bbd84e29c2b308d1dc099d7de8dcc7035e48f4bf5dc4a531a44ff5e2a" dependencies = [ "async-channel", "async-io", "async-lock", "async-signal", "async-task", "blocking", "cfg-if", "event-listener", "futures-lite", "rustix", "tracing", "windows-sys 0.52.0", ] [[package]] name = "async-recursion" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", "syn 2.0.66", ] [[package]] name = "async-signal" version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "329972aa325176e89114919f2a80fdae4f4c040f66a370b1a1159c6c0f94e7aa" dependencies = [ "async-io", "async-lock", "atomic-waker", "cfg-if", "futures-core", "futures-io", "rustix", "signal-hook-registry", "slab", "windows-sys 0.52.0", ] [[package]] name = "async-task" version = "4.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" version = "0.1.80" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", "syn 2.0.66", ] [[package]] name = "atomic-waker" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "backtrace" version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17c6a35df3749d2e8bb1b7b21a976d82b15548788d2735b9d82f329268f71a11" dependencies = [ "addr2line", "cc", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", ] [[package]] name = "bitflags" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" [[package]] name = "block-buffer" version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] [[package]] name = "block-padding" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" dependencies = [ "generic-array", ] [[package]] name = "blocking" version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" dependencies = [ "async-channel", "async-task", "futures-io", "futures-lite", "piper", ] [[package]] name = "bytes" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "cbc" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" dependencies = [ "cipher", ] [[package]] name = "cc" version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "cfg_aliases" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" [[package]] name = "cipher" version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ "crypto-common", "inout", ] [[package]] name = "concurrent-queue" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" dependencies = [ "crossbeam-utils", ] [[package]] name = "cpufeatures" version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" dependencies = [ "libc", ] [[package]] name = "crossbeam-utils" version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "crypto-common" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", "typenum", ] [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", "subtle", ] [[package]] name = "endi" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" [[package]] name = "enumflags2" version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3278c9d5fb675e0a51dabcf4c0d355f692b064171535ba72361be1528a9d8e8d" dependencies = [ "enumflags2_derive", "serde", ] [[package]] name = "enumflags2_derive" version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c785274071b1b420972453b306eeca06acf4633829db4223b58a2a8c5953bc4" dependencies = [ "proc-macro2", "quote", "syn 2.0.66", ] [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys 0.52.0", ] [[package]] name = "event-listener" version = "5.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" dependencies = [ "concurrent-queue", "parking", "pin-project-lite", ] [[package]] name = "event-listener-strategy" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" dependencies = [ "event-listener", "pin-project-lite", ] [[package]] name = "fastrand" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" [[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 = "futures-core" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] name = "futures-io" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" [[package]] name = "futures-lite" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" dependencies = [ "fastrand", "futures-core", "futures-io", "parking", "pin-project-lite", ] [[package]] name = "futures-macro" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", "syn 2.0.66", ] [[package]] name = "futures-sink" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] name = "futures-task" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-util" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-core", "futures-io", "futures-macro", "futures-sink", "futures-task", "memchr", "pin-project-lite", "pin-utils", "slab", ] [[package]] name = "generic-array" version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", ] [[package]] name = "getrandom" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "gimli" version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" [[package]] name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "hermit-abi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hkdf" version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" dependencies = [ "hmac", ] [[package]] name = "hmac" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ "digest", ] [[package]] name = "indexmap" version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", "hashbrown", ] [[package]] name = "inout" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" dependencies = [ "block-padding", "generic-array", ] [[package]] name = "libc" version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "linux-raw-sys" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "memchr" version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "memoffset" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" dependencies = [ "autocfg", ] [[package]] name = "miniz_oxide" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae" dependencies = [ "adler", ] [[package]] name = "mio" version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "wasi", "windows-sys 0.48.0", ] [[package]] name = "nix" version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" dependencies = [ "bitflags", "cfg-if", "cfg_aliases", "libc", "memoffset", ] [[package]] name = "num" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" dependencies = [ "num-bigint", "num-complex", "num-integer", "num-iter", "num-rational", "num-traits", ] [[package]] name = "num-bigint" version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c165a9ab64cf766f73521c0dd2cfdff64f488b8f0b3e621face3462d3db536d7" 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-integer" version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ "num-traits", ] [[package]] name = "num-iter" version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" dependencies = [ "autocfg", "num-integer", "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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] [[package]] name = "object" version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8ec7ab813848ba4522158d5517a6093db1ded27575b070f4177b8d12b41db5e" dependencies = [ "memchr", ] [[package]] name = "once_cell" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "openssl" version = "0.10.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" dependencies = [ "bitflags", "cfg-if", "foreign-types", "libc", "once_cell", "openssl-macros", "openssl-sys", ] [[package]] name = "openssl-macros" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", "syn 2.0.66", ] [[package]] name = "openssl-sys" version = "0.9.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" dependencies = [ "cc", "libc", "pkg-config", "vcpkg", ] [[package]] name = "ordered-stream" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" dependencies = [ "futures-core", "pin-project-lite", ] [[package]] name = "parking" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" [[package]] name = "pin-project-lite" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pin-utils" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "piper" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae1d5c74c9876f070d3e8fd503d748c7d974c3e48da8f41350fa5222ef9b4391" dependencies = [ "atomic-waker", "fastrand", "futures-io", ] [[package]] name = "pkg-config" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "polling" version = "3.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e6a007746f34ed64099e88783b0ae369eaa3da6392868ba262e2af9b8fbaea1" dependencies = [ "cfg-if", "concurrent-queue", "hermit-abi", "pin-project-lite", "rustix", "tracing", "windows-sys 0.52.0", ] [[package]] name = "ppv-lite86" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro-crate" version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" dependencies = [ "toml_edit", ] [[package]] name = "proc-macro-error" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", "syn 1.0.109", "version_check", ] [[package]] name = "proc-macro-error-attr" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ "proc-macro2", "quote", "version_check", ] [[package]] name = "proc-macro2" version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] [[package]] name = "rand" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", "rand_core", ] [[package]] name = "rand_chacha" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", "rand_core", ] [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom", ] [[package]] name = "regex" version = "1.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" [[package]] name = "rustc-demangle" version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustix" version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", "windows-sys 0.52.0", ] [[package]] name = "secret-service" version = "4.0.0" dependencies = [ "aes", "cbc", "futures-util", "generic-array", "hkdf", "num", "once_cell", "openssl", "rand", "serde", "sha2", "test-with", "tokio", "zbus", ] [[package]] name = "serde" version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", "syn 2.0.66", ] [[package]] name = "serde_repr" version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", "syn 2.0.66", ] [[package]] name = "sha1" version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", "digest", ] [[package]] name = "sha2" version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", "digest", ] [[package]] name = "signal-hook-registry" version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ "libc", ] [[package]] name = "slab" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] [[package]] name = "socket2" version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", "windows-sys 0.52.0", ] [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "subtle" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "syn" version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "syn" version = "2.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "tempfile" version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", "fastrand", "rustix", "windows-sys 0.52.0", ] [[package]] name = "test-with" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e197ba118b465f82678eff95506a11354e70d5b794f95962491a2d6a35f58ed" dependencies = [ "proc-macro-error", "proc-macro2", "quote", "regex", "syn 1.0.109", ] [[package]] name = "tokio" version = "1.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" dependencies = [ "backtrace", "bytes", "libc", "mio", "pin-project-lite", "signal-hook-registry", "socket2", "tokio-macros", "tracing", "windows-sys 0.48.0", ] [[package]] name = "tokio-macros" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" dependencies = [ "proc-macro2", "quote", "syn 2.0.66", ] [[package]] name = "toml_datetime" version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" [[package]] name = "toml_edit" version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" dependencies = [ "indexmap", "toml_datetime", "winnow", ] [[package]] name = "tracing" version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ "pin-project-lite", "tracing-attributes", "tracing-core", ] [[package]] name = "tracing-attributes" version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", "syn 2.0.66", ] [[package]] name = "tracing-core" version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", ] [[package]] name = "typenum" version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "uds_windows" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" dependencies = [ "memoffset", "tempfile", "winapi", ] [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "vcpkg" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ "windows-targets 0.48.5", ] [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets 0.52.5", ] [[package]] name = "windows-targets" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm 0.48.5", "windows_aarch64_msvc 0.48.5", "windows_i686_gnu 0.48.5", "windows_i686_msvc 0.48.5", "windows_x86_64_gnu 0.48.5", "windows_x86_64_gnullvm 0.48.5", "windows_x86_64_msvc 0.48.5", ] [[package]] name = "windows-targets" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" dependencies = [ "windows_aarch64_gnullvm 0.52.5", "windows_aarch64_msvc 0.52.5", "windows_i686_gnu 0.52.5", "windows_i686_gnullvm", "windows_i686_msvc 0.52.5", "windows_x86_64_gnu 0.52.5", "windows_x86_64_gnullvm 0.52.5", "windows_x86_64_msvc 0.52.5", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" [[package]] name = "windows_i686_gnullvm" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" [[package]] name = "winnow" version = "0.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" dependencies = [ "memchr", ] [[package]] name = "xdg-home" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21e5a325c3cb8398ad6cf859c1135b25dd29e186679cf2da7581d9679f63b38e" dependencies = [ "libc", "winapi", ] [[package]] name = "zbus" version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23915fcb26e7a9a9dc05fd93a9870d336d6d032cd7e8cebf1c5c37666489fdd5" dependencies = [ "async-broadcast", "async-executor", "async-fs", "async-io", "async-lock", "async-process", "async-recursion", "async-task", "async-trait", "blocking", "enumflags2", "event-listener", "futures-core", "futures-sink", "futures-util", "hex", "nix", "ordered-stream", "rand", "serde", "serde_repr", "sha1", "static_assertions", "tokio", "tracing", "uds_windows", "windows-sys 0.52.0", "xdg-home", "zbus_macros", "zbus_names", "zvariant", ] [[package]] name = "zbus_macros" version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02bcca0b586d2f8589da32347b4784ba424c4891ed86aa5b50d5e88f6b2c4f5d" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", "syn 2.0.66", "zvariant_utils", ] [[package]] name = "zbus_names" version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c" dependencies = [ "serde", "static_assertions", "zvariant", ] [[package]] name = "zvariant" version = "4.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aa6d31a02fbfb602bfde791de7fedeb9c2c18115b3d00f3a36e489f46ffbbc7" dependencies = [ "endi", "enumflags2", "serde", "static_assertions", "zvariant_derive", ] [[package]] name = "zvariant_derive" version = "4.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "642bf1b6b6d527988b3e8193d20969d53700a36eac734d21ae6639db168701c8" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", "syn 2.0.66", "zvariant_utils", ] [[package]] name = "zvariant_utils" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc242db087efc22bd9ade7aa7809e4ba828132edc312871584a6b4391bdf8786" dependencies = [ "proc-macro2", "quote", "syn 2.0.66", ] secret-service-4.0.0/Cargo.toml0000644000000043210000000000100117560ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.75.0" name = "secret-service" version = "4.0.0" authors = ["Walther Chen "] description = "Library to interface with Secret Service API" homepage = "https://github.com/hwchen/secret-service-rs.git" documentation = "https://docs.rs/secret-service" readme = "README.md" keywords = [ "secret-service", "password", "linux", "keychain", ] license = "MIT OR Apache-2.0" repository = "https://github.com/hwchen/secret-service-rs.git" [package.metadata.docs.rs] features = ["rt-tokio-crypto-rust"] [dependencies.aes] version = "0.8" optional = true [dependencies.cbc] version = "0.1" features = [ "block-padding", "alloc", ] optional = true [dependencies.futures-util] version = "0.3" [dependencies.generic-array] version = "0.14" [dependencies.hkdf] version = "0.12.0" optional = true [dependencies.num] version = "0.4.0" [dependencies.once_cell] version = "1" [dependencies.openssl] version = "^0.10.40" optional = true [dependencies.rand] version = "0.8.1" [dependencies.serde] version = "1.0.103" features = ["derive"] [dependencies.sha2] version = "0.10.0" optional = true [dependencies.zbus] version = "4" default-features = false [dev-dependencies.test-with] version = "0.8" default-features = false [dev-dependencies.tokio] version = "1" features = [ "rt", "macros", ] [features] crypto-openssl = ["dep:openssl"] crypto-rust = [ "dep:aes", "dep:cbc", "dep:sha2", "dep:hkdf", ] rt-async-io-crypto-openssl = [ "zbus/async-io", "crypto-openssl", ] rt-async-io-crypto-rust = [ "zbus/async-io", "crypto-rust", ] rt-tokio-crypto-openssl = [ "zbus/tokio", "crypto-openssl", ] rt-tokio-crypto-rust = [ "zbus/tokio", "crypto-rust", ] secret-service-4.0.0/Cargo.toml.orig000064400000000000000000000030371046102023000154420ustar 00000000000000[package] authors = ["Walther Chen "] description = "Library to interface with Secret Service API" documentation = "https://docs.rs/secret-service" homepage = "https://github.com/hwchen/secret-service-rs.git" keywords = ["secret-service", "password", "linux", "keychain"] license = "MIT OR Apache-2.0" name = "secret-service" repository = "https://github.com/hwchen/secret-service-rs.git" edition = "2021" version = "4.0.0" rust-version = "1.75.0" # The async runtime features mirror those of `zbus` for compatibility. [features] crypto-rust = ["dep:aes", "dep:cbc", "dep:sha2", "dep:hkdf"] crypto-openssl = ["dep:openssl"] rt-async-io-crypto-rust = ["zbus/async-io", "crypto-rust"] rt-async-io-crypto-openssl = ["zbus/async-io", "crypto-openssl"] rt-tokio-crypto-rust = ["zbus/tokio", "crypto-rust"] rt-tokio-crypto-openssl = ["zbus/tokio", "crypto-openssl"] [dependencies] aes = { version = "0.8", optional = true } cbc = { version = "0.1", features = ["block-padding", "alloc"] , optional = true } hkdf = { version = "0.12.0", optional = true } generic-array = "0.14" once_cell = "1" futures-util = "0.3" num = "0.4.0" rand = "0.8.1" serde = { version = "1.0.103", features = ["derive"] } sha2 = { version = "0.10.0", optional = true } zbus = { version = "4", default-features = false } openssl = { version = "^0.10.40", optional = true } [dev-dependencies] tokio = { version = "1", features = ["rt", "macros"] } test-with = { version = "0.8", default-features = false } [package.metadata.docs.rs] features = ["rt-tokio-crypto-rust"] secret-service-4.0.0/LICENSE-APACHE000064400000000000000000000251371046102023000145040ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. secret-service-4.0.0/LICENSE-MIT000064400000000000000000000020551046102023000142060ustar 00000000000000Copyright (c) 2016 secret-service Developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. secret-service-4.0.0/NOTES_ON_PORT.md000064400000000000000000000042101046102023000152170ustar 00000000000000# Notes on porting to zbus Plan: start a section of code that will be easy convert to zbus, while keeping dbus dependency. I think that this would be `Item`, which is a "leaf" struct (meaning it doesn't create other dbus-connected structs). This way I only have to worry about any data being received, and not so much data being returned. Plan, revised: I should still start with Item. However, the place where I'll slide zbus in is a little different then I had thought. This is because my code currently is structured: - With an `Interface` (which should actually be called a `proxy` that talks to the interface) that is a very low-level implementation of basics like method calls. - And then all the details of talking to the service interface are handled directly in `Item`'s methods. With zbus, I would instead have: - A derived `dbus_proxy`, which would set up all the boilerplate for talking to a an interface, and then - Methods in `Item` would generally just be calling the derived `dbus_proxy`. - This means that with zbus, I should be able to get rid of `Interface`. So, my first step should be to replace usage of the `Item` proxy. ## First Pass Creating proxy was nice, and removed a lot of my boilerplate. One issue was lifetimes for using a path in creating a new proxy. I had wanted to store the proxy in the `Item` struct, but I needed to borrow the `item_path`, which wouldn't live long enough. I ended up instantiating a new proxy on each `Item` method, which didn't feel as good. Now, getting some test errors. One is that session doesn't exist. ## Second Pass First finished creating all the proxies. I found `Proxy::new_for_owned` which removed the instantiation of a proxy per-method, now the `Proxy` can be saved in the struct. There's a weird issue where on a derived `property` it allows `ObjectPath` in the return, but on methods it requires `OwnedObjectPath`. Seems to work ok, oh well. Looks like `SecretStruct` needs to be wrapped in another struct in order to fit the dbus signature, not sure what that's about. Otherwise pretty straightforward. Removing the low-level details of creating dbus types allowed me to refactor much more easily. secret-service-4.0.0/README.md000064400000000000000000000075321046102023000140360ustar 00000000000000# Secret Service [![crates.io version](https://img.shields.io/crates/v/secret-service.svg)](https://crates.io/crates/secret-service) [![crate documentation](https://docs.rs/secret-service/badge.svg)](https://docs.rs/secret-service) ![MSRV](https://img.shields.io/badge/rustc-1.75+-blue.svg) [![crates.io downloads](https://img.shields.io/crates/d/secret-service.svg)](https://crates.io/crates/secret-service) ![CI](https://github.com/hwchen/secret-service-rs/workflows/CI/badge.svg) A rust library for interacting with the FreeDesktop Secret Service API through DBus. ### Basic Usage `secret-service` is implemented in pure Rust by default, so it doesn't require any system libraries such as `libdbus-1-dev` or `libdbus-1-3` on Ubuntu. In Cargo.toml: When adding the crate, you must select a feature representing your selected runtime and cryptography backend. For example: ```toml [dependencies] secret-service = { version = "3.0.0", features = ["rt-tokio-crypto-rust"] } ``` Available feature flags: - `rt-async-io-crypto-rust`: Uses the `async-std` runtime and pure Rust crytography via `RustCrypto`. - `rt-async-io-crypto-openssl`: Uses the `async-std` runtime and OpenSSL as the cryptography provider. - `rt-tokio-crypto-rust`: Uses the `tokio` runtime and pure Rust cryptography via `RustCrypto`. - `rt-tokio-crypto-openssl`: Uses the `tokio` runtime and OpenSSL as the cryptography provider. Note that the `-openssl` feature sets require OpenSSL to be available on your system, or the `bundled` feature of `openssl` crate must be activated in your `cargo` dependency tree instead. In source code (below example is for `--bin`, not `--lib`). This example uses `tokio` as the async runtime. ```rust use secret_service::SecretService; use secret_service::EncryptionType; use std::{collections::HashMap, error::Error}; #[tokio::main] async fn main() -> Result<(), Box> { // initialize secret service (dbus connection and encryption session) let ss = SecretService::connect(EncryptionType::Dh).await?; // get default collection let collection = ss.get_default_collection().await?; // create new item collection.create_item( "test_label", // label HashMap::from([("test", "test_value")]), // properties b"test_secret", // secret false, // replace item with same attributes "text/plain" // secret content type ).await?; // search items by properties let search_items = ss.search_items( HashMap::from([("test", "test_value")]) ).await?; let item = search_items.unlocked.first().ok_or("Not found!")?; // retrieve secret from item let secret = item.get_secret().await?; assert_eq!(secret, b"test_secret"); // delete item (deletes the dbus object, not the struct instance) item.delete().await?; Ok(()) } ``` ### Functionality - SecretService: initialize dbus, create plain/encrypted session. - Collections: create, delete, search. - Items: create, delete, search, get/set secret. ### Changelog See [the list of GitHub releases and their release notes](https://github.com/hwchen/secret-service-rs/releases) ### Versioning This library is feature complete, has stabilized its API for the most part. However, as this crate is almost soley reliable on the `zbus` crate, we try and match major version releases with theirs to handle breaking changes and move with the wider `zbus` ecosystem. ## License Licensed under either of * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) at your option. ### Contribution Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. secret-service-4.0.0/examples/example.rs000064400000000000000000000041401046102023000163660ustar 00000000000000//Copyright 2022 secret-service-rs Developers // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. use secret_service::{EncryptionType, SecretService}; use std::{collections::HashMap, str}; #[tokio::main(flavor = "current_thread")] async fn main() { // Initialize secret service let ss = SecretService::connect(EncryptionType::Plain).await.unwrap(); // navigate to default collection let collection = ss.get_default_collection().await.unwrap(); let mut properties = HashMap::new(); properties.insert("test", "test_value"); //create new item collection .create_item( "test_label", // label properties, b"test_secret", //secret false, // replace item with same attributes "text/plain", // secret content type ) .await .unwrap(); //println!("New Item: {:?}", new_item); // search items by properties let mut search_properties = HashMap::new(); search_properties.insert("test", "test_value"); let search_items = ss.search_items(search_properties).await.unwrap(); //println!("Searched Item: {:?}", search_items); // retrieve one item, first by checking the unlocked items let item = match search_items.unlocked.first() { Some(item) => item, None => { // if there aren't any, check the locked items and unlock the first one let locked_item = search_items .locked .first() .expect("Search didn't return any items!"); locked_item.unlock().await.unwrap(); locked_item } }; // retrieve secret from item let secret = item.get_secret().await.unwrap(); println!("Retrieved secret: {:?}", str::from_utf8(&secret).unwrap()); assert_eq!(secret, b"test_secret"); item.delete().await.unwrap(); } secret-service-4.0.0/src/blocking/collection.rs000064400000000000000000000235501046102023000176350ustar 00000000000000// Copyright 2022 secret-service-rs Developers // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. use super::item::Item; use crate::error::Error; use crate::proxy::collection::CollectionProxyBlocking; use crate::proxy::service::ServiceProxyBlocking; use crate::session::Session; use crate::ss::{SS_DBUS_NAME, SS_ITEM_ATTRIBUTES, SS_ITEM_LABEL}; use crate::util::{exec_prompt_blocking, format_secret, lock_or_unlock_blocking, LockAction}; use std::collections::HashMap; use zbus::{ zvariant::{Dict, ObjectPath, OwnedObjectPath, Value}, CacheProperties, }; // Collection struct. // Should always be created from the SecretService entry point, // whether through a new collection or a collection search pub struct Collection<'a> { conn: zbus::blocking::Connection, session: &'a Session, pub collection_path: OwnedObjectPath, collection_proxy: CollectionProxyBlocking<'a>, service_proxy: &'a ServiceProxyBlocking<'a>, } impl<'a> Collection<'a> { pub(crate) fn new( conn: zbus::blocking::Connection, session: &'a Session, service_proxy: &'a ServiceProxyBlocking, collection_path: OwnedObjectPath, ) -> Result { let collection_proxy = CollectionProxyBlocking::builder(&conn) .destination(SS_DBUS_NAME)? .path(collection_path.clone())? .cache_properties(CacheProperties::No) .build()?; Ok(Collection { conn, session, collection_path, collection_proxy, service_proxy, }) } pub fn is_locked(&self) -> Result { Ok(self.collection_proxy.locked()?) } pub fn ensure_unlocked(&self) -> Result<(), Error> { if self.is_locked()? { Err(Error::Locked) } else { Ok(()) } } pub fn unlock(&self) -> Result<(), Error> { lock_or_unlock_blocking( self.conn.clone(), self.service_proxy, &self.collection_path, LockAction::Unlock, ) } pub fn lock(&self) -> Result<(), Error> { lock_or_unlock_blocking( self.conn.clone(), self.service_proxy, &self.collection_path, LockAction::Lock, ) } /// Deletes dbus object, but struct instance still exists (current implementation) pub fn delete(&self) -> Result<(), Error> { // ensure_unlocked handles prompt for unlocking if necessary self.ensure_unlocked()?; let prompt_path = self.collection_proxy.delete()?; // "/" means no prompt necessary if prompt_path.as_str() != "/" { exec_prompt_blocking(self.conn.clone(), &prompt_path)?; } Ok(()) } pub fn get_all_items(&self) -> Result, Error> { let items = self.collection_proxy.items()?; // map array of item paths to Item let res = items .into_iter() .map(|item_path| { Item::new( self.conn.clone(), self.session, self.service_proxy, item_path.into(), ) }) .collect::>()?; Ok(res) } pub fn search_items(&self, attributes: HashMap<&str, &str>) -> Result, Error> { let items = self.collection_proxy.search_items(attributes)?; // map array of item paths to Item let res = items .into_iter() .map(|item_path| { Item::new( self.conn.clone(), self.session, self.service_proxy, item_path, ) }) .collect::>()?; Ok(res) } pub fn get_label(&self) -> Result { Ok(self.collection_proxy.label()?) } pub fn set_label(&self, new_label: &str) -> Result<(), Error> { Ok(self.collection_proxy.set_label(new_label)?) } pub fn create_item( &self, label: &str, attributes: HashMap<&str, &str>, secret: &[u8], replace: bool, content_type: &str, ) -> Result { let secret_struct = format_secret(self.session, secret, content_type)?; let mut properties: HashMap<&str, Value> = HashMap::new(); let attributes: Dict = attributes.into(); properties.insert(SS_ITEM_LABEL, label.into()); properties.insert(SS_ITEM_ATTRIBUTES, attributes.into()); let created_item = self .collection_proxy .create_item(properties, secret_struct, replace)?; // This prompt handling is practically identical to create_collection let item_path: ObjectPath = { // Get path of created object let created_path = created_item.item; // Check if that path is "/", if so should execute a prompt if created_path.as_str() == "/" { let prompt_path = created_item.prompt; // Exec prompt and parse result let prompt_res = exec_prompt_blocking(self.conn.clone(), &prompt_path)?; prompt_res.try_into()? } else { // if not, just return created path created_path.into() } }; Item::new( self.conn.clone(), self.session, self.service_proxy, item_path.into(), ) } } #[cfg(test)] mod test { use crate::blocking::*; #[test] fn should_create_collection_struct() { let ss = SecretService::connect(EncryptionType::Plain).unwrap(); let _ = ss.get_default_collection().unwrap(); // tested under SecretService struct } #[test] fn should_check_if_collection_locked() { let ss = SecretService::connect(EncryptionType::Plain).unwrap(); let collection = ss.get_default_collection().unwrap(); let _ = collection.is_locked().unwrap(); } #[test] #[ignore] // should unignore this test this manually, otherwise will constantly prompt during tests. fn should_lock_and_unlock() { let ss = SecretService::connect(EncryptionType::Plain).unwrap(); let collection = ss.get_default_collection().unwrap(); let locked = collection.is_locked().unwrap(); if locked { collection.unlock().unwrap(); collection.ensure_unlocked().unwrap(); assert!(!collection.is_locked().unwrap()); collection.lock().unwrap(); assert!(collection.is_locked().unwrap()); } else { collection.lock().unwrap(); assert!(collection.is_locked().unwrap()); collection.unlock().unwrap(); collection.ensure_unlocked().unwrap(); assert!(!collection.is_locked().unwrap()); } } #[test] #[ignore] fn should_delete_collection() { let ss = SecretService::connect(EncryptionType::Plain).unwrap(); let collections = ss.get_all_collections().unwrap(); let count_before = collections.len(); for collection in collections { let collection_path = &*collection.collection_path; if collection_path.contains("Test") { collection.unlock().unwrap(); collection.delete().unwrap(); } } //double check after let collections = ss.get_all_collections().unwrap(); assert!( collections.len() < count_before, "collections before delete {count_before}", ) } #[test] fn should_get_all_items() { let ss = SecretService::connect(EncryptionType::Plain).unwrap(); let collection = ss.get_default_collection().unwrap(); collection.get_all_items().unwrap(); } #[test] fn should_search_items() { let ss = SecretService::connect(EncryptionType::Plain).unwrap(); let collection = ss.get_default_collection().unwrap(); // Create an item let item = collection .create_item( "test", HashMap::from([("test_attributes_in_collection", "test")]), b"test_secret", false, "text/plain", ) .unwrap(); // handle empty vec search collection.search_items(HashMap::new()).unwrap(); // handle no result let bad_search = collection .search_items(HashMap::from([("test_bad", "test")])) .unwrap(); assert_eq!(bad_search.len(), 0); // handle correct search for item and compare let search_item = collection .search_items(HashMap::from([("test_attributes_in_collection", "test")])) .unwrap(); assert_eq!(item.item_path, search_item[0].item_path); item.delete().unwrap(); } #[test] #[ignore] fn should_get_and_set_collection_label() { let ss = SecretService::connect(EncryptionType::Plain).unwrap(); let collection = ss.get_default_collection().unwrap(); let label = collection.get_label().unwrap(); assert_eq!(label, "Login"); // Set label to test and check collection.unlock().unwrap(); collection.set_label("Test").unwrap(); let label = collection.get_label().unwrap(); assert_eq!(label, "Test"); // Reset label to original and test collection.unlock().unwrap(); collection.set_label("Login").unwrap(); let label = collection.get_label().unwrap(); assert_eq!(label, "Login"); collection.lock().unwrap(); } } secret-service-4.0.0/src/blocking/item.rs000064400000000000000000000314131046102023000164350ustar 00000000000000//Copyright 2022 secret-service-rs Developers // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. use crate::error::Error; use crate::proxy::item::ItemProxyBlocking; use crate::proxy::service::ServiceProxyBlocking; use crate::session::decrypt; use crate::session::Session; use crate::ss::SS_DBUS_NAME; use crate::util::{exec_prompt_blocking, format_secret, lock_or_unlock_blocking, LockAction}; use std::collections::HashMap; use zbus::{zvariant::OwnedObjectPath, CacheProperties}; pub struct Item<'a> { conn: zbus::blocking::Connection, session: &'a Session, pub item_path: OwnedObjectPath, item_proxy: ItemProxyBlocking<'a>, service_proxy: &'a ServiceProxyBlocking<'a>, } impl<'a> Item<'a> { pub(crate) fn new( conn: zbus::blocking::Connection, session: &'a Session, service_proxy: &'a ServiceProxyBlocking<'a>, item_path: OwnedObjectPath, ) -> Result { let item_proxy = ItemProxyBlocking::builder(&conn) .destination(SS_DBUS_NAME)? .path(item_path.clone())? .cache_properties(CacheProperties::No) .build()?; Ok(Item { conn, session, item_path, item_proxy, service_proxy, }) } pub fn is_locked(&self) -> Result { Ok(self.item_proxy.locked()?) } pub fn ensure_unlocked(&self) -> Result<(), Error> { if self.is_locked()? { Err(Error::Locked) } else { Ok(()) } } pub fn unlock(&self) -> Result<(), Error> { lock_or_unlock_blocking( self.conn.clone(), self.service_proxy, &self.item_path, LockAction::Unlock, ) } pub fn lock(&self) -> Result<(), Error> { lock_or_unlock_blocking( self.conn.clone(), self.service_proxy, &self.item_path, LockAction::Lock, ) } pub fn get_attributes(&self) -> Result, Error> { Ok(self.item_proxy.attributes()?) } pub fn set_attributes(&self, attributes: HashMap<&str, &str>) -> Result<(), Error> { Ok(self.item_proxy.set_attributes(attributes)?) } pub fn get_label(&self) -> Result { Ok(self.item_proxy.label()?) } pub fn set_label(&self, new_label: &str) -> Result<(), Error> { Ok(self.item_proxy.set_label(new_label)?) } /// Deletes dbus object, but struct instance still exists (current implementation) pub fn delete(&self) -> Result<(), Error> { // ensure_unlocked handles prompt for unlocking if necessary self.ensure_unlocked()?; let prompt_path = self.item_proxy.delete()?; // "/" means no prompt necessary if prompt_path.as_str() != "/" { exec_prompt_blocking(self.conn.clone(), &prompt_path)?; } Ok(()) } pub fn get_secret(&self) -> Result, Error> { let secret_struct = self.item_proxy.get_secret(&self.session.object_path)?; let secret = secret_struct.value; if let Some(session_key) = self.session.get_aes_key() { // get "param" (aes_iv) field out of secret struct let aes_iv = secret_struct.parameters; // decrypt let decrypted_secret = decrypt(&secret, session_key, &aes_iv)?; Ok(decrypted_secret) } else { Ok(secret) } } pub fn get_secret_content_type(&self) -> Result { let secret_struct = self.item_proxy.get_secret(&self.session.object_path)?; let content_type = secret_struct.content_type; Ok(content_type) } pub fn set_secret(&self, secret: &[u8], content_type: &str) -> Result<(), Error> { let secret_struct = format_secret(self.session, secret, content_type)?; Ok(self.item_proxy.set_secret(secret_struct)?) } pub fn get_created(&self) -> Result { Ok(self.item_proxy.created()?) } pub fn get_modified(&self) -> Result { Ok(self.item_proxy.modified()?) } } impl<'a> Eq for Item<'a> {} impl<'a> PartialEq for Item<'a> { fn eq(&self, other: &Item) -> bool { self.item_path == other.item_path && self.get_attributes().unwrap() == other.get_attributes().unwrap() } } #[cfg(test)] mod test { use crate::blocking::*; fn create_test_default_item<'a>(collection: &'a Collection<'_>) -> Item<'a> { collection .create_item("Test", HashMap::new(), b"test", false, "text/plain") .unwrap() } #[test] fn should_create_and_delete_item() { let ss = SecretService::connect(EncryptionType::Plain).unwrap(); let collection = ss.get_default_collection().unwrap(); let item = create_test_default_item(&collection); item.delete().unwrap(); // Random operation to prove that path no longer exists if item.get_label().is_ok() { panic!("item still existed"); } } #[test] fn should_check_if_item_locked() { let ss = SecretService::connect(EncryptionType::Plain).unwrap(); let collection = ss.get_default_collection().unwrap(); let item = create_test_default_item(&collection); item.is_locked().unwrap(); item.delete().unwrap(); } #[test] #[ignore] fn should_lock_and_unlock() { let ss = SecretService::connect(EncryptionType::Plain).unwrap(); let collection = ss.get_default_collection().unwrap(); let item = create_test_default_item(&collection); let locked = item.is_locked().unwrap(); if locked { item.unlock().unwrap(); item.ensure_unlocked().unwrap(); assert!(!item.is_locked().unwrap()); item.lock().unwrap(); assert!(item.is_locked().unwrap()); } else { item.lock().unwrap(); assert!(item.is_locked().unwrap()); item.unlock().unwrap(); item.ensure_unlocked().unwrap(); assert!(!item.is_locked().unwrap()); } item.delete().unwrap(); } #[test] fn should_get_and_set_item_label() { let ss = SecretService::connect(EncryptionType::Plain).unwrap(); let collection = ss.get_default_collection().unwrap(); let item = create_test_default_item(&collection); // Set label to test and check item.set_label("Tester").unwrap(); let label = item.get_label().unwrap(); assert_eq!(label, "Tester"); item.delete().unwrap(); } #[test] fn should_create_with_item_attributes() { let ss = SecretService::connect(EncryptionType::Plain).unwrap(); let collection = ss.get_default_collection().unwrap(); let item = collection .create_item( "Test", HashMap::from([("test_attributes_in_item", "test")]), b"test", false, "text/plain", ) .unwrap(); let attributes = item.get_attributes().unwrap(); // We do not compare exact attributes, since the secret service provider could add its own // at any time. Instead, we only check that the ones we provided are returned back. assert_eq!( attributes .get("test_attributes_in_item") .map(String::as_str), Some("test") ); item.delete().unwrap(); } #[test] fn should_get_and_set_item_attributes() { let ss = SecretService::connect(EncryptionType::Plain).unwrap(); let collection = ss.get_default_collection().unwrap(); let item = create_test_default_item(&collection); // Also test empty array handling item.set_attributes(HashMap::new()).unwrap(); item.set_attributes(HashMap::from([("test_attributes_in_item_get", "test")])) .unwrap(); let attributes = item.get_attributes().unwrap(); // We do not compare exact attributes, since the secret service provider could add its own // at any time. Instead, we only check that the ones we provided are returned back. assert_eq!( attributes .get("test_attributes_in_item_get") .map(String::as_str), Some("test") ); item.delete().unwrap(); } #[test] fn should_get_modified_created_props() { let ss = SecretService::connect(EncryptionType::Plain).unwrap(); let collection = ss.get_default_collection().unwrap(); let item = create_test_default_item(&collection); item.set_label("Tester").unwrap(); let _created = item.get_created().unwrap(); let _modified = item.get_modified().unwrap(); item.delete().unwrap(); } #[test] fn should_create_and_get_secret() { let ss = SecretService::connect(EncryptionType::Plain).unwrap(); let collection = ss.get_default_collection().unwrap(); let item = create_test_default_item(&collection); let secret = item.get_secret().unwrap(); item.delete().unwrap(); assert_eq!(secret, b"test"); } #[test] fn should_create_and_get_secret_encrypted() { let ss = SecretService::connect(EncryptionType::Dh).unwrap(); let collection = ss.get_default_collection().unwrap(); let item = create_test_default_item(&collection); let secret = item.get_secret().unwrap(); item.delete().unwrap(); assert_eq!(secret, b"test"); } #[test] fn should_get_secret_content_type() { let ss = SecretService::connect(EncryptionType::Plain).unwrap(); let collection = ss.get_default_collection().unwrap(); let item = create_test_default_item(&collection); let content_type = item.get_secret_content_type().unwrap(); item.delete().unwrap(); assert_eq!(content_type, "text/plain".to_owned()); } #[test] fn should_set_secret() { let ss = SecretService::connect(EncryptionType::Plain).unwrap(); let collection = ss.get_default_collection().unwrap(); let item = create_test_default_item(&collection); item.set_secret(b"new_test", "text/plain").unwrap(); let secret = item.get_secret().unwrap(); item.delete().unwrap(); assert_eq!(secret, b"new_test"); } #[test] fn should_create_encrypted_item() { let ss = SecretService::connect(EncryptionType::Dh).unwrap(); let collection = ss.get_default_collection().unwrap(); let item = collection .create_item( "Test", HashMap::new(), b"test_encrypted", false, "text/plain", ) .expect("Error on item creation"); let secret = item.get_secret().unwrap(); item.delete().unwrap(); assert_eq!(secret, b"test_encrypted"); } #[test] fn should_create_encrypted_item_from_empty_secret() { let ss = SecretService::connect(EncryptionType::Dh).unwrap(); let collection = ss.get_default_collection().unwrap(); let item = collection .create_item("Test", HashMap::new(), b"", false, "text/plain") .expect("Error on item creation"); let secret = item.get_secret().unwrap(); item.delete().unwrap(); assert_eq!(secret, b""); } #[test] fn should_get_encrypted_secret_across_dbus_connections() { { let ss = SecretService::connect(EncryptionType::Dh).unwrap(); let collection = ss.get_default_collection().unwrap(); let item = collection .create_item( "Test", HashMap::from([("test_attributes_in_item_encrypt", "test")]), b"test_encrypted", false, "text/plain", ) .expect("Error on item creation"); let secret = item.get_secret().unwrap(); assert_eq!(secret, b"test_encrypted"); } { let ss = SecretService::connect(EncryptionType::Dh).unwrap(); let collection = ss.get_default_collection().unwrap(); let search_item = collection .search_items(HashMap::from([("test_attributes_in_item_encrypt", "test")])) .unwrap(); let item = search_item.first().unwrap(); assert_eq!(item.get_secret().unwrap(), b"test_encrypted"); item.delete().unwrap(); } } } secret-service-4.0.0/src/blocking/mod.rs000064400000000000000000000230571046102023000162630ustar 00000000000000// Copyright 2022 secret-service-rs Developers // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! A blocking secret service API. //! //! This `SecretService` will block the current thread when making requests to the //! secret service server instead of returning futures. //! //! It is important to not call this these functions in an async context or otherwise the runtime //! may stall. See [zbus's blocking documentation] for more details. If you are in an async context, //! you should use the [async `SecretService`] instead. //! //! [zbus's blocking documentation]: https://docs.rs/zbus/latest/zbus/blocking/index.html //! [async `SecretService`]: crate::SecretService use crate::session::Session; use crate::ss::SS_COLLECTION_LABEL; use crate::util; use crate::{proxy::service::ServiceProxyBlocking, util::exec_prompt_blocking}; use crate::{EncryptionType, Error, SearchItemsResult}; use std::collections::HashMap; use zbus::zvariant::{ObjectPath, Value}; mod collection; pub use collection::Collection; mod item; pub use item::Item; /// Secret Service Struct. /// /// This the main entry point for usage of the library. /// /// Creating a new [SecretService] will also initialize dbus /// and negotiate a new cryptographic session /// ([EncryptionType::Plain] or [EncryptionType::Dh]) pub struct SecretService<'a> { conn: zbus::blocking::Connection, session: Session, service_proxy: ServiceProxyBlocking<'a>, } impl<'a> SecretService<'a> { /// Create a new `SecretService` instance pub fn connect(encryption: EncryptionType) -> Result { let conn = zbus::blocking::Connection::session().map_err(util::handle_conn_error)?; let service_proxy = ServiceProxyBlocking::new(&conn).map_err(util::handle_conn_error)?; let session = Session::new_blocking(&service_proxy, encryption)?; Ok(SecretService { conn, session, service_proxy, }) } /// Get all collections pub fn get_all_collections(&self) -> Result, Error> { let collections = self.service_proxy.collections()?; collections .into_iter() .map(|object_path| { Collection::new( self.conn.clone(), &self.session, &self.service_proxy, object_path.into(), ) }) .collect() } /// Get collection by alias. /// /// Most common would be the `default` alias, but there /// is also a specific method for getting the collection /// by default alias. pub fn get_collection_by_alias(&self, alias: &str) -> Result { let object_path = self.service_proxy.read_alias(alias)?; if object_path.as_str() == "/" { Err(Error::NoResult) } else { Ok(Collection::new( self.conn.clone(), &self.session, &self.service_proxy, object_path, )?) } } /// Get default collection. /// (The collection whos alias is `default`) pub fn get_default_collection(&self) -> Result { self.get_collection_by_alias("default") } /// Get any collection. /// First tries `default` collection, then `session` /// collection, then the first collection when it /// gets all collections. pub fn get_any_collection(&self) -> Result { // default first, then session, then first self.get_default_collection() .or_else(|_| self.get_collection_by_alias("session")) .or_else(|_| { let mut collections = self.get_all_collections()?; if collections.is_empty() { Err(Error::NoResult) } else { Ok(collections.swap_remove(0)) } }) } /// Creates a new collection with a label and an alias. pub fn create_collection(&self, label: &str, alias: &str) -> Result { let mut properties: HashMap<&str, Value> = HashMap::new(); properties.insert(SS_COLLECTION_LABEL, label.into()); let created_collection = self.service_proxy.create_collection(properties, alias)?; // This prompt handling is practically identical to create_collection let collection_path: ObjectPath = { // Get path of created object let created_path = created_collection.collection; // Check if that path is "/", if so should execute a prompt if created_path.as_str() == "/" { let prompt_path = created_collection.prompt; // Exec prompt and parse result let prompt_res = util::exec_prompt_blocking(self.conn.clone(), &prompt_path)?; prompt_res.try_into()? } else { // if not, just return created path created_path.into() } }; Collection::new( self.conn.clone(), &self.session, &self.service_proxy, collection_path.into(), ) } /// Searches all items by attributes pub fn search_items( &self, attributes: HashMap<&str, &str>, ) -> Result, Error> { let items = self.service_proxy.search_items(attributes)?; let object_paths_to_items = |items: Vec<_>| { items .into_iter() .map(|item_path| { Item::new( self.conn.clone(), &self.session, &self.service_proxy, item_path, ) }) .collect::>() }; Ok(SearchItemsResult { unlocked: object_paths_to_items(items.unlocked)?, locked: object_paths_to_items(items.locked)?, }) } /// Unlock all items in a batch pub fn unlock_all(&self, items: &[&Item<'_>]) -> Result<(), Error> { let objects = items.iter().map(|i| &*i.item_path).collect(); let lock_action_res = self.service_proxy.unlock(objects)?; if lock_action_res.object_paths.is_empty() { exec_prompt_blocking(self.conn.clone(), &lock_action_res.prompt)?; } Ok(()) } } #[cfg(test)] mod test { use super::*; use std::convert::TryFrom; use zbus::zvariant::ObjectPath; #[test] fn should_create_secret_service() { SecretService::connect(EncryptionType::Plain).unwrap(); } #[test] fn should_get_all_collections() { // Assumes that there will always be a default // collection let ss = SecretService::connect(EncryptionType::Plain).unwrap(); let collections = ss.get_all_collections().unwrap(); assert!(!collections.is_empty(), "no collections found"); } #[test] fn should_get_collection_by_alias() { let ss = SecretService::connect(EncryptionType::Plain).unwrap(); ss.get_collection_by_alias("session").unwrap(); } #[test] fn should_return_error_if_collection_doesnt_exist() { let ss = SecretService::connect(EncryptionType::Plain).unwrap(); match ss.get_collection_by_alias("definitely_defintely_does_not_exist") { Err(Error::NoResult) => {} _ => panic!(), }; } #[test] fn should_get_default_collection() { let ss = SecretService::connect(EncryptionType::Plain).unwrap(); ss.get_default_collection().unwrap(); } #[test] fn should_get_any_collection() { let ss = SecretService::connect(EncryptionType::Plain).unwrap(); let _ = ss.get_any_collection().unwrap(); } #[test_with::no_env(GITHUB_ACTIONS)] #[test] fn should_create_and_delete_collection() { let ss = SecretService::connect(EncryptionType::Plain).unwrap(); let test_collection = ss.create_collection("Test", "").unwrap(); assert_eq!( ObjectPath::from(test_collection.collection_path.clone()), ObjectPath::try_from("/org/freedesktop/secrets/collection/Test").unwrap() ); test_collection.delete().unwrap(); } #[test] fn should_search_items() { let ss = SecretService::connect(EncryptionType::Dh).unwrap(); let collection = ss.get_default_collection().unwrap(); // Create an item let item = collection .create_item( "test", HashMap::from([("test_attribute_in_ss", "test_value")]), b"test_secret", false, "text/plain", ) .unwrap(); // handle empty vec search ss.search_items(HashMap::new()).unwrap(); // handle no result let bad_search = ss.search_items(HashMap::from([("test", "test")])).unwrap(); assert_eq!(bad_search.unlocked.len(), 0); assert_eq!(bad_search.locked.len(), 0); // handle correct search for item and compare let search_item = ss .search_items(HashMap::from([("test_attribute_in_ss", "test_value")])) .unwrap(); assert_eq!(item.item_path, search_item.unlocked[0].item_path); assert_eq!(search_item.locked.len(), 0); item.delete().unwrap(); } } secret-service-4.0.0/src/collection.rs000064400000000000000000000244701046102023000160470ustar 00000000000000// Copyright 2022 secret-service-rs Developers // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. use crate::proxy::collection::CollectionProxy; use crate::proxy::service::ServiceProxy; use crate::session::Session; use crate::ss::{SS_DBUS_NAME, SS_ITEM_ATTRIBUTES, SS_ITEM_LABEL}; use crate::util::{exec_prompt, format_secret, lock_or_unlock, LockAction}; use crate::Error; use crate::Item; use std::collections::HashMap; use zbus::{ zvariant::{Dict, ObjectPath, OwnedObjectPath, Value}, CacheProperties, }; // Collection struct. // Should always be created from the SecretService entry point, // whether through a new collection or a collection search pub struct Collection<'a> { conn: zbus::Connection, session: &'a Session, pub collection_path: OwnedObjectPath, collection_proxy: CollectionProxy<'a>, service_proxy: &'a ServiceProxy<'a>, } impl<'a> Collection<'a> { pub(crate) async fn new( conn: zbus::Connection, session: &'a Session, service_proxy: &'a ServiceProxy<'_>, collection_path: OwnedObjectPath, ) -> Result, Error> { let collection_proxy = CollectionProxy::builder(&conn) .destination(SS_DBUS_NAME)? .path(collection_path.clone())? .cache_properties(CacheProperties::No) .build() .await?; Ok(Collection { conn, session, collection_path, collection_proxy, service_proxy, }) } pub async fn is_locked(&self) -> Result { Ok(self.collection_proxy.locked().await?) } pub async fn ensure_unlocked(&self) -> Result<(), Error> { if self.is_locked().await? { Err(Error::Locked) } else { Ok(()) } } pub async fn unlock(&self) -> Result<(), Error> { lock_or_unlock( self.conn.clone(), self.service_proxy, &self.collection_path, LockAction::Unlock, ) .await } pub async fn lock(&self) -> Result<(), Error> { lock_or_unlock( self.conn.clone(), self.service_proxy, &self.collection_path, LockAction::Lock, ) .await } /// Deletes dbus object, but struct instance still exists (current implementation) pub async fn delete(&self) -> Result<(), Error> { // ensure_unlocked handles prompt for unlocking if necessary self.ensure_unlocked().await?; let prompt_path = self.collection_proxy.delete().await?; // "/" means no prompt necessary if prompt_path.as_str() != "/" { exec_prompt(self.conn.clone(), &prompt_path).await?; } Ok(()) } pub async fn get_all_items(&self) -> Result>, Error> { let items = self.collection_proxy.items().await?; // map array of item paths to Item futures_util::future::join_all(items.into_iter().map(|item_path| { Item::new( self.conn.clone(), self.session, self.service_proxy, item_path.into(), ) })) .await .into_iter() .collect::>() } pub async fn search_items( &self, attributes: HashMap<&str, &str>, ) -> Result>, Error> { let items = self.collection_proxy.search_items(attributes).await?; // map array of item paths to Item futures_util::future::join_all(items.into_iter().map(|item_path| { Item::new( self.conn.clone(), self.session, self.service_proxy, item_path, ) })) .await .into_iter() .collect::>() } pub async fn get_label(&self) -> Result { Ok(self.collection_proxy.label().await?) } pub async fn set_label(&self, new_label: &str) -> Result<(), Error> { Ok(self.collection_proxy.set_label(new_label).await?) } pub async fn create_item( &self, label: &str, attributes: HashMap<&str, &str>, secret: &[u8], replace: bool, content_type: &str, ) -> Result, Error> { let secret_struct = format_secret(self.session, secret, content_type)?; let mut properties: HashMap<&str, Value> = HashMap::new(); let attributes: Dict = attributes.into(); properties.insert(SS_ITEM_LABEL, label.into()); properties.insert(SS_ITEM_ATTRIBUTES, attributes.into()); let created_item = self .collection_proxy .create_item(properties, secret_struct, replace) .await?; // This prompt handling is practically identical to create_collection let item_path: ObjectPath = { // Get path of created object let created_path = created_item.item; // Check if that path is "/", if so should execute a prompt if created_path.as_str() == "/" { let prompt_path = created_item.prompt; // Exec prompt and parse result let prompt_res = exec_prompt(self.conn.clone(), &prompt_path).await?; prompt_res.try_into()? } else { // if not, just return created path created_path.into() } }; Item::new( self.conn.clone(), self.session, self.service_proxy, item_path.into(), ) .await } } #[cfg(test)] mod test { use crate::*; #[tokio::test] async fn should_create_collection_struct() { let ss = SecretService::connect(EncryptionType::Plain).await.unwrap(); let _ = ss.get_default_collection().await.unwrap(); // tested under SecretService struct } #[tokio::test] async fn should_check_if_collection_locked() { let ss = SecretService::connect(EncryptionType::Plain).await.unwrap(); let collection = ss.get_default_collection().await.unwrap(); let _ = collection.is_locked().await.unwrap(); } #[tokio::test] #[ignore] // should unignore this test this manually, otherwise will constantly prompt during tests. async fn should_lock_and_unlock() { let ss = SecretService::connect(EncryptionType::Plain).await.unwrap(); let collection = ss.get_default_collection().await.unwrap(); let locked = collection.is_locked().await.unwrap(); if locked { collection.unlock().await.unwrap(); collection.ensure_unlocked().await.unwrap(); assert!(!collection.is_locked().await.unwrap()); collection.lock().await.unwrap(); assert!(collection.is_locked().await.unwrap()); } else { collection.lock().await.unwrap(); assert!(collection.is_locked().await.unwrap()); collection.unlock().await.unwrap(); collection.ensure_unlocked().await.unwrap(); assert!(!collection.is_locked().await.unwrap()); } } #[tokio::test] #[ignore] async fn should_delete_collection() { let ss = SecretService::connect(EncryptionType::Plain).await.unwrap(); let collections = ss.get_all_collections().await.unwrap(); let count_before = collections.len(); for collection in collections { let collection_path = &*collection.collection_path; if collection_path.contains("Test") { collection.unlock().await.unwrap(); collection.delete().await.unwrap(); } } //double check after let collections = ss.get_all_collections().await.unwrap(); assert!( collections.len() < count_before, "collections before delete {count_before}" ); } #[tokio::test] async fn should_get_all_items() { let ss = SecretService::connect(EncryptionType::Plain).await.unwrap(); let collection = ss.get_default_collection().await.unwrap(); collection.get_all_items().await.unwrap(); } #[tokio::test] async fn should_search_items() { let ss = SecretService::connect(EncryptionType::Plain).await.unwrap(); let collection = ss.get_default_collection().await.unwrap(); // Create an item let item = collection .create_item( "test", HashMap::from([("test_attributes_in_collection", "test")]), b"test_secret", false, "text/plain", ) .await .unwrap(); // handle empty vec search collection.search_items(HashMap::new()).await.unwrap(); // handle no result let bad_search = collection .search_items(HashMap::from([("test_bad", "test")])) .await .unwrap(); assert_eq!(bad_search.len(), 0); // handle correct search for item and compare let search_item = collection .search_items(HashMap::from([("test_attributes_in_collection", "test")])) .await .unwrap(); assert_eq!(item.item_path, search_item[0].item_path); item.delete().await.unwrap(); } #[tokio::test] #[ignore] async fn should_get_and_set_collection_label() { let ss = SecretService::connect(EncryptionType::Plain).await.unwrap(); let collection = ss.get_default_collection().await.unwrap(); let label = collection.get_label().await.unwrap(); assert_eq!(label, "Login"); // Set label to test and check collection.unlock().await.unwrap(); collection.set_label("Test").await.unwrap(); let label = collection.get_label().await.unwrap(); assert_eq!(label, "Test"); // Reset label to original and test collection.unlock().await.unwrap(); collection.set_label("Login").await.unwrap(); let label = collection.get_label().await.unwrap(); assert_eq!(label, "Login"); collection.lock().await.unwrap(); } } secret-service-4.0.0/src/error.rs000064400000000000000000000051371046102023000150440ustar 00000000000000// Copyright 2022 secret-service-rs Developers // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. use std::{error, fmt}; use zbus::zvariant; /// An error that could occur interacting with the secret service dbus interface. #[derive(Debug)] #[non_exhaustive] pub enum Error { /// An error occured decrypting a response message. Crypto(&'static str), /// A call into the secret service provider failed. Zbus(zbus::Error), /// A call into a standard dbus interface failed. ZbusFdo(zbus::fdo::Error), /// Serializing or deserializing a dbus message failed. Zvariant(zvariant::Error), /// A secret service interface was locked and can't return any /// information about its contents. Locked, /// No object was found in the object for the request. NoResult, /// An authorization prompt was dismissed, but is required to continue. Prompt, /// A secret service provider, or a session to connect to one, was found /// on the system. Unavailable, } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Error::Crypto(err) => write!(f, "Crypto error: {err}"), Error::Zbus(err) => write!(f, "zbus error: {err}"), Error::ZbusFdo(err) => write!(f, "zbus fdo error: {err}"), Error::Zvariant(err) => write!(f, "zbus serde error: {err}"), Error::Locked => f.write_str("SS Error: object locked"), Error::NoResult => f.write_str("SS error: result not returned from SS API"), Error::Prompt => f.write_str("SS error: prompt dismissed"), Error::Unavailable => f.write_str("no secret service provider or dbus session found"), } } } impl error::Error for Error { fn source(&self) -> Option<&(dyn error::Error + 'static)> { match *self { Error::Zbus(ref err) => Some(err), Error::ZbusFdo(ref err) => Some(err), Error::Zvariant(ref err) => Some(err), _ => None, } } } impl From for Error { fn from(err: zbus::Error) -> Error { Error::Zbus(err) } } impl From for Error { fn from(err: zbus::fdo::Error) -> Error { Error::ZbusFdo(err) } } impl From for Error { fn from(err: zvariant::Error) -> Error { Error::Zvariant(err) } } secret-service-4.0.0/src/item.rs000064400000000000000000000336741046102023000146600ustar 00000000000000//Copyright 2022 secret-service-rs Developers // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. use crate::error::Error; use crate::proxy::item::ItemProxy; use crate::proxy::service::ServiceProxy; use crate::session::decrypt; use crate::session::Session; use crate::ss::SS_DBUS_NAME; use crate::util::{exec_prompt, format_secret, lock_or_unlock, LockAction}; use std::collections::HashMap; use zbus::{zvariant::OwnedObjectPath, CacheProperties}; pub struct Item<'a> { conn: zbus::Connection, session: &'a Session, pub item_path: OwnedObjectPath, item_proxy: ItemProxy<'a>, service_proxy: &'a ServiceProxy<'a>, } impl<'a> Item<'a> { pub(crate) async fn new( conn: zbus::Connection, session: &'a Session, service_proxy: &'a ServiceProxy<'a>, item_path: OwnedObjectPath, ) -> Result, Error> { let item_proxy = ItemProxy::builder(&conn) .destination(SS_DBUS_NAME)? .path(item_path.clone())? .cache_properties(CacheProperties::No) .build() .await?; Ok(Item { conn, session, item_path, item_proxy, service_proxy, }) } pub async fn is_locked(&self) -> Result { Ok(self.item_proxy.locked().await?) } pub async fn ensure_unlocked(&self) -> Result<(), Error> { if self.is_locked().await? { Err(Error::Locked) } else { Ok(()) } } pub async fn unlock(&self) -> Result<(), Error> { lock_or_unlock( self.conn.clone(), self.service_proxy, &self.item_path, LockAction::Unlock, ) .await } pub async fn lock(&self) -> Result<(), Error> { lock_or_unlock( self.conn.clone(), self.service_proxy, &self.item_path, LockAction::Lock, ) .await } pub async fn get_attributes(&self) -> Result, Error> { Ok(self.item_proxy.attributes().await?) } pub async fn set_attributes(&self, attributes: HashMap<&str, &str>) -> Result<(), Error> { Ok(self.item_proxy.set_attributes(attributes).await?) } pub async fn get_label(&self) -> Result { Ok(self.item_proxy.label().await?) } pub async fn set_label(&self, new_label: &str) -> Result<(), Error> { Ok(self.item_proxy.set_label(new_label).await?) } /// Deletes dbus object, but struct instance still exists (current implementation) pub async fn delete(&self) -> Result<(), Error> { // ensure_unlocked handles prompt for unlocking if necessary self.ensure_unlocked().await?; let prompt_path = self.item_proxy.delete().await?; // "/" means no prompt necessary if prompt_path.as_str() != "/" { exec_prompt(self.conn.clone(), &prompt_path).await?; } Ok(()) } pub async fn get_secret(&self) -> Result, Error> { let secret_struct = self .item_proxy .get_secret(&self.session.object_path) .await?; let secret = secret_struct.value; if let Some(session_key) = self.session.get_aes_key() { // get "param" (aes_iv) field out of secret struct let aes_iv = secret_struct.parameters; // decrypt let decrypted_secret = decrypt(&secret, session_key, &aes_iv)?; Ok(decrypted_secret) } else { Ok(secret) } } pub async fn get_secret_content_type(&self) -> Result { let secret_struct = self .item_proxy .get_secret(&self.session.object_path) .await?; let content_type = secret_struct.content_type; Ok(content_type) } pub async fn set_secret(&self, secret: &[u8], content_type: &str) -> Result<(), Error> { let secret_struct = format_secret(self.session, secret, content_type)?; Ok(self.item_proxy.set_secret(secret_struct).await?) } pub async fn get_created(&self) -> Result { Ok(self.item_proxy.created().await?) } pub async fn get_modified(&self) -> Result { Ok(self.item_proxy.modified().await?) } /// Returns if an item is equal to `other`. /// /// This is the equivalent of the `PartialEq` trait, but `async`. pub async fn equal_to(&self, other: &Item<'_>) -> Result { let this_attrs = self.get_attributes().await?; let other_attrs = other.get_attributes().await?; Ok(self.item_path == other.item_path && this_attrs == other_attrs) } } #[cfg(test)] mod test { use crate::*; async fn create_test_default_item<'a>(collection: &'a Collection<'_>) -> Item<'a> { collection .create_item("Test", HashMap::new(), b"test", false, "text/plain") .await .unwrap() } #[tokio::test] async fn should_create_and_delete_item() { let ss = SecretService::connect(EncryptionType::Plain).await.unwrap(); let collection = ss.get_default_collection().await.unwrap(); let item = create_test_default_item(&collection).await; item.delete().await.unwrap(); // Random operation to prove that path no longer exists if item.get_label().await.is_ok() { panic!("item still existed"); } } #[tokio::test] async fn should_check_if_item_locked() { let ss = SecretService::connect(EncryptionType::Plain).await.unwrap(); let collection = ss.get_default_collection().await.unwrap(); let item = create_test_default_item(&collection).await; item.is_locked().await.unwrap(); item.delete().await.unwrap(); } #[tokio::test] #[ignore] async fn should_lock_and_unlock() { let ss = SecretService::connect(EncryptionType::Plain).await.unwrap(); let collection = ss.get_default_collection().await.unwrap(); let item = create_test_default_item(&collection).await; let locked = item.is_locked().await.unwrap(); if locked { item.unlock().await.unwrap(); item.ensure_unlocked().await.unwrap(); assert!(!item.is_locked().await.unwrap()); item.lock().await.unwrap(); assert!(item.is_locked().await.unwrap()); } else { item.lock().await.unwrap(); assert!(item.is_locked().await.unwrap()); item.unlock().await.unwrap(); item.ensure_unlocked().await.unwrap(); assert!(!item.is_locked().await.unwrap()); } item.delete().await.unwrap(); } #[tokio::test] async fn should_get_and_set_item_label() { let ss = SecretService::connect(EncryptionType::Plain).await.unwrap(); let collection = ss.get_default_collection().await.unwrap(); let item = create_test_default_item(&collection).await; // Set label to test and check item.set_label("Tester").await.unwrap(); let label = item.get_label().await.unwrap(); assert_eq!(label, "Tester"); item.delete().await.unwrap(); } #[tokio::test] async fn should_create_with_item_attributes() { let ss = SecretService::connect(EncryptionType::Plain).await.unwrap(); let collection = ss.get_default_collection().await.unwrap(); let item = collection .create_item( "Test", HashMap::from([("test_attributes_in_item", "test")]), b"test", false, "text/plain", ) .await .unwrap(); let attributes = item.get_attributes().await.unwrap(); // We do not compare exact attributes, since the secret service provider could add its own // at any time. Instead, we only check that the ones we provided are returned back. assert_eq!( attributes .get("test_attributes_in_item") .map(String::as_str), Some("test") ); item.delete().await.unwrap(); } #[tokio::test] async fn should_get_and_set_item_attributes() { let ss = SecretService::connect(EncryptionType::Plain).await.unwrap(); let collection = ss.get_default_collection().await.unwrap(); let item = create_test_default_item(&collection).await; // Also test empty array handling item.set_attributes(HashMap::new()).await.unwrap(); item.set_attributes(HashMap::from([("test_attributes_in_item_get", "test")])) .await .unwrap(); let attributes = item.get_attributes().await.unwrap(); // We do not compare exact attributes, since the secret service provider could add its own // at any time. Instead, we only check that the ones we provided are returned back. assert_eq!( attributes .get("test_attributes_in_item_get") .map(String::as_str), Some("test") ); item.delete().await.unwrap(); } #[tokio::test] async fn should_get_modified_created_props() { let ss = SecretService::connect(EncryptionType::Plain).await.unwrap(); let collection = ss.get_default_collection().await.unwrap(); let item = create_test_default_item(&collection).await; item.set_label("Tester").await.unwrap(); let _created = item.get_created().await.unwrap(); let _modified = item.get_modified().await.unwrap(); item.delete().await.unwrap(); } #[tokio::test] async fn should_create_and_get_secret() { let ss = SecretService::connect(EncryptionType::Plain).await.unwrap(); let collection = ss.get_default_collection().await.unwrap(); let item = create_test_default_item(&collection).await; let secret = item.get_secret().await.unwrap(); item.delete().await.unwrap(); assert_eq!(secret, b"test"); } #[tokio::test] async fn should_create_and_get_secret_encrypted() { let ss = SecretService::connect(EncryptionType::Dh).await.unwrap(); let collection = ss.get_default_collection().await.unwrap(); let item = create_test_default_item(&collection).await; let secret = item.get_secret().await.unwrap(); item.delete().await.unwrap(); assert_eq!(secret, b"test"); } #[tokio::test] async fn should_get_secret_content_type() { let ss = SecretService::connect(EncryptionType::Plain).await.unwrap(); let collection = ss.get_default_collection().await.unwrap(); let item = create_test_default_item(&collection).await; let content_type = item.get_secret_content_type().await.unwrap(); item.delete().await.unwrap(); assert_eq!(content_type, "text/plain".to_owned()); } #[tokio::test] async fn should_set_secret() { let ss = SecretService::connect(EncryptionType::Plain).await.unwrap(); let collection = ss.get_default_collection().await.unwrap(); let item = create_test_default_item(&collection).await; item.set_secret(b"new_test", "text/plain").await.unwrap(); let secret = item.get_secret().await.unwrap(); item.delete().await.unwrap(); assert_eq!(secret, b"new_test"); } #[tokio::test] async fn should_create_encrypted_item() { let ss = SecretService::connect(EncryptionType::Dh).await.unwrap(); let collection = ss.get_default_collection().await.unwrap(); let item = collection .create_item( "Test", HashMap::new(), b"test_encrypted", false, "text/plain", ) .await .expect("Error on item creation"); let secret = item.get_secret().await.unwrap(); item.delete().await.unwrap(); assert_eq!(secret, b"test_encrypted"); } #[tokio::test] async fn should_create_encrypted_item_from_empty_secret() { //empty string let ss = SecretService::connect(EncryptionType::Dh).await.unwrap(); let collection = ss.get_default_collection().await.unwrap(); let item = collection .create_item("Test", HashMap::new(), b"", false, "text/plain") .await .expect("Error on item creation"); let secret = item.get_secret().await.unwrap(); item.delete().await.unwrap(); assert_eq!(secret, b""); } #[tokio::test] async fn should_get_encrypted_secret_across_dbus_connections() { { let ss = SecretService::connect(EncryptionType::Dh).await.unwrap(); let collection = ss.get_default_collection().await.unwrap(); let item = collection .create_item( "Test", HashMap::from([("test_attributes_in_item_encrypt", "test")]), b"test_encrypted", false, "text/plain", ) .await .expect("Error on item creation"); let secret = item.get_secret().await.unwrap(); assert_eq!(secret, b"test_encrypted"); } { let ss = SecretService::connect(EncryptionType::Dh).await.unwrap(); let collection = ss.get_default_collection().await.unwrap(); let search_item = collection .search_items(HashMap::from([("test_attributes_in_item_encrypt", "test")])) .await .unwrap(); let item = search_item.first().unwrap(); assert_eq!(item.get_secret().await.unwrap(), b"test_encrypted"); item.delete().await.unwrap(); } } } secret-service-4.0.0/src/lib.rs000064400000000000000000000341531046102023000144610ustar 00000000000000//Copyright 2022 secret-service-rs Developers // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! # Secret Service libary //! //! This library implements a rust interface to the Secret Service API which is implemented //! in Linux. //! //! ## About Secret Service API //! //! //! Secret Service provides a secure place to store secrets. //! Gnome keyring and KWallet implement the Secret Service API. //! //! ## Basic Usage //! ``` //! use secret_service::SecretService; //! use secret_service::EncryptionType; //! use std::collections::HashMap; //! //! #[tokio::main(flavor = "current_thread")] //! async fn main() { //! // initialize secret service (dbus connection and encryption session) //! let ss = SecretService::connect(EncryptionType::Dh).await.unwrap(); //! //! // get default collection //! let collection = ss.get_default_collection().await.unwrap(); //! //! let mut properties = HashMap::new(); //! properties.insert("test", "test_value"); //! //! //create new item //! collection.create_item( //! "test_label", // label //! properties, //! b"test_secret", //secret //! false, // replace item with same attributes //! "text/plain" // secret content type //! ).await.unwrap(); //! //! // search items by properties //! let search_items = ss.search_items( //! HashMap::from([("test", "test_value")]) //! ).await.unwrap(); //! //! // retrieve one item, first by checking the unlocked items //! let item = match search_items.unlocked.first() { //! Some(item) => item, //! None => { //! // if there aren't any, check the locked items and unlock the first one //! let locked_item = search_items //! .locked //! .first() //! .expect("Search didn't return any items!"); //! locked_item.unlock().await.unwrap(); //! locked_item //! } //! }; //! //! // retrieve secret from item //! let secret = item.get_secret().await.unwrap(); //! assert_eq!(secret, b"test_secret"); //! //! // delete item (deletes the dbus object, not the struct instance) //! item.delete().await.unwrap() //! } //! ``` //! //! ## Overview of this library: //! ### Entry point //! The entry point for this library is the `SecretService` struct. A new instance of //! `SecretService` will initialize the dbus connection and negotiate an encryption session. //! //! ``` //! # use secret_service::SecretService; //! # use secret_service::EncryptionType; //! # async fn call() { //! SecretService::connect(EncryptionType::Plain).await.unwrap(); //! # } //! ``` //! //! or //! //! ``` //! # use secret_service::SecretService; //! # use secret_service::EncryptionType; //! # async fn call() { //! SecretService::connect(EncryptionType::Dh).await.unwrap(); //! # } //! ``` //! //! Once the SecretService struct is initialized, it can be used to navigate to a collection. //! Items can also be directly searched for without getting a collection first. //! //! ### Collections and Items //! The Secret Service API organizes secrets into collections, and holds each secret //! in an item. //! //! Items consist of a label, attributes, and the secret. The most common way to find //! an item is a search by attributes. //! //! While it's possible to create new collections, most users will simply create items //! within the default collection. //! //! ### Actions overview //! The most common supported actions are `create`, `get`, `search`, and `delete` for //! `Collections` and `Items`. For more specifics and exact method names, please see //! each struct's documentation. //! //! In addition, `set` and `get` actions are available for secrets contained in an `Item`. //! //! ### Crypto //! Specifics in SecretService API Draft Proposal: //! //! //! ### Async //! //! This crate, following `zbus`, is async by default. If you want a synchronous interface //! that blocks, see the [blocking] module instead. // // Util currently has interfaces (dbus method namespace) to make it easier to call methods. // Util contains function to execute prompts (used in many collection and item methods, like // delete) pub mod blocking; mod error; mod proxy; mod session; mod ss; mod util; mod collection; pub use collection::Collection; pub use error::Error; mod item; pub use item::Item; pub use session::EncryptionType; use crate::proxy::service::ServiceProxy; use crate::session::Session; use crate::ss::SS_COLLECTION_LABEL; use crate::util::exec_prompt; use futures_util::TryFutureExt; use std::collections::HashMap; use zbus::zvariant::{ObjectPath, Value}; /// Secret Service Struct. /// /// This the main entry point for usage of the library. /// /// Creating a new [SecretService] will also initialize dbus /// and negotiate a new cryptographic session /// ([EncryptionType::Plain] or [EncryptionType::Dh]) pub struct SecretService<'a> { conn: zbus::Connection, session: Session, service_proxy: ServiceProxy<'a>, } /// Used to indicate locked and unlocked items in the /// return value of [SecretService::search_items] /// and [blocking::SecretService::search_items]. pub struct SearchItemsResult { pub unlocked: Vec, pub locked: Vec, } impl<'a> SecretService<'a> { /// Create a new `SecretService` instance. pub async fn connect(encryption: EncryptionType) -> Result, Error> { let conn = zbus::Connection::session() .await .map_err(util::handle_conn_error)?; let service_proxy = ServiceProxy::new(&conn) .await .map_err(util::handle_conn_error)?; let session = Session::new(&service_proxy, encryption).await?; Ok(SecretService { conn, session, service_proxy, }) } /// Get all collections pub async fn get_all_collections(&self) -> Result>, Error> { let collections = self.service_proxy.collections().await?; futures_util::future::join_all(collections.into_iter().map(|object_path| { Collection::new( self.conn.clone(), &self.session, &self.service_proxy, object_path.into(), ) })) .await .into_iter() .collect::>() } /// Get collection by alias. /// /// Most common would be the `default` alias, but there /// is also a specific method for getting the collection /// by default alias. pub async fn get_collection_by_alias(&self, alias: &str) -> Result, Error> { let object_path = self.service_proxy.read_alias(alias).await?; if object_path.as_str() == "/" { Err(Error::NoResult) } else { Collection::new( self.conn.clone(), &self.session, &self.service_proxy, object_path, ) .await } } /// Get default collection. /// (The collection whos alias is `default`) pub async fn get_default_collection(&self) -> Result, Error> { self.get_collection_by_alias("default").await } /// Get any collection. /// First tries `default` collection, then `session` /// collection, then the first collection when it /// gets all collections. pub async fn get_any_collection(&self) -> Result, Error> { // default first, then session, then first self.get_default_collection() .or_else(|_| self.get_collection_by_alias("session")) .or_else(|_| async { let mut collections = self.get_all_collections().await?; if collections.is_empty() { Err(Error::NoResult) } else { Ok(collections.swap_remove(0)) } }) .await } /// Creates a new collection with a label and an alias. pub async fn create_collection( &self, label: &str, alias: &str, ) -> Result, Error> { let mut properties: HashMap<&str, Value> = HashMap::new(); properties.insert(SS_COLLECTION_LABEL, label.into()); let created_collection = self .service_proxy .create_collection(properties, alias) .await?; // This prompt handling is practically identical to create_collection let collection_path: ObjectPath = { // Get path of created object let created_path = created_collection.collection; // Check if that path is "/", if so should execute a prompt if created_path.as_str() == "/" { let prompt_path = created_collection.prompt; // Exec prompt and parse result let prompt_res = exec_prompt(self.conn.clone(), &prompt_path).await?; prompt_res.try_into()? } else { // if not, just return created path created_path.into() } }; Collection::new( self.conn.clone(), &self.session, &self.service_proxy, collection_path.into(), ) .await } /// Searches all items by attributes pub async fn search_items( &self, attributes: HashMap<&str, &str>, ) -> Result>, Error> { let items = self.service_proxy.search_items(attributes).await?; let object_paths_to_items = |items: Vec<_>| { futures_util::future::join_all(items.into_iter().map(|item_path| { Item::new( self.conn.clone(), &self.session, &self.service_proxy, item_path, ) })) }; Ok(SearchItemsResult { unlocked: object_paths_to_items(items.unlocked) .await .into_iter() .collect::>()?, locked: object_paths_to_items(items.locked) .await .into_iter() .collect::>()?, }) } /// Unlock all items in a batch pub async fn unlock_all(&self, items: &[&Item<'_>]) -> Result<(), Error> { let objects = items.iter().map(|i| &*i.item_path).collect(); let lock_action_res = self.service_proxy.unlock(objects).await?; if lock_action_res.object_paths.is_empty() { exec_prompt(self.conn.clone(), &lock_action_res.prompt).await?; } Ok(()) } } #[cfg(test)] mod test { use super::*; use std::convert::TryFrom; use zbus::zvariant::ObjectPath; #[tokio::test] async fn should_create_secret_service() { SecretService::connect(EncryptionType::Plain).await.unwrap(); } #[tokio::test] async fn should_get_all_collections() { // Assumes that there will always be a default collection let ss = SecretService::connect(EncryptionType::Plain).await.unwrap(); let collections = ss.get_all_collections().await.unwrap(); assert!(!collections.is_empty(), "no collections found"); } #[tokio::test] async fn should_get_collection_by_alias() { let ss = SecretService::connect(EncryptionType::Plain).await.unwrap(); ss.get_collection_by_alias("session").await.unwrap(); } #[tokio::test] async fn should_return_error_if_collection_doesnt_exist() { let ss = SecretService::connect(EncryptionType::Plain).await.unwrap(); match ss .get_collection_by_alias("definitely_defintely_does_not_exist") .await { Err(Error::NoResult) => {} _ => panic!(), }; } #[tokio::test] async fn should_get_default_collection() { let ss = SecretService::connect(EncryptionType::Plain).await.unwrap(); ss.get_default_collection().await.unwrap(); } #[tokio::test] async fn should_get_any_collection() { let ss = SecretService::connect(EncryptionType::Plain).await.unwrap(); let _ = ss.get_any_collection().await.unwrap(); } #[test_with::no_env(GITHUB_ACTIONS)] #[tokio::test] async fn should_create_and_delete_collection() { let ss = SecretService::connect(EncryptionType::Plain).await.unwrap(); let test_collection = ss.create_collection("Test", "").await.unwrap(); assert_eq!( ObjectPath::from(test_collection.collection_path.clone()), ObjectPath::try_from("/org/freedesktop/secrets/collection/Test").unwrap() ); test_collection.delete().await.unwrap(); } #[tokio::test] async fn should_search_items() { let ss = SecretService::connect(EncryptionType::Plain).await.unwrap(); let collection = ss.get_default_collection().await.unwrap(); // Create an item let item = collection .create_item( "test", HashMap::from([("test_attribute_in_ss", "test_value")]), b"test_secret", false, "text/plain", ) .await .unwrap(); // handle empty vec search ss.search_items(HashMap::new()).await.unwrap(); // handle no result let bad_search = ss .search_items(HashMap::from([("test", "test")])) .await .unwrap(); assert_eq!(bad_search.unlocked.len(), 0); assert_eq!(bad_search.locked.len(), 0); // handle correct search for item and compare let search_item = ss .search_items(HashMap::from([("test_attribute_in_ss", "test_value")])) .await .unwrap(); assert_eq!(item.item_path, search_item.unlocked[0].item_path); assert_eq!(search_item.locked.len(), 0); item.delete().await.unwrap(); } } secret-service-4.0.0/src/proxy/collection.rs000064400000000000000000000036041046102023000172240ustar 00000000000000//Copyright 2022 secret-service-rs Developers // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! A dbus proxy for speaking with secret service's `Collection` Interface. use serde::{Deserialize, Serialize}; use std::collections::HashMap; use zbus::zvariant::{ObjectPath, OwnedObjectPath, Type, Value}; use super::SecretStruct; /// A dbus proxy for speaking with secret service's `Collection` Interface. /// /// This will derive CollectionProxy /// /// Note that `Value` in the method signatures corresponds to `VARIANT` dbus type. #[zbus::proxy( interface = "org.freedesktop.Secret.Collection", default_service = "org.freedesktop.Secret.Collection" )] trait Collection { /// Returns prompt: ObjectPath fn delete(&self) -> zbus::Result; fn search_items(&self, attributes: HashMap<&str, &str>) -> zbus::Result>; fn create_item( &self, properties: HashMap<&str, Value<'_>>, secret: SecretStruct, replace: bool, ) -> zbus::Result; #[zbus(property)] fn items(&self) -> zbus::fdo::Result>>; #[zbus(property)] fn label(&self) -> zbus::fdo::Result; #[zbus(property)] fn set_label(&self, new_label: &str) -> zbus::fdo::Result<()>; #[zbus(property)] fn locked(&self) -> zbus::fdo::Result; #[zbus(property)] fn created(&self) -> zbus::fdo::Result; #[zbus(property)] fn modified(&self) -> zbus::fdo::Result; } #[derive(Debug, Serialize, Deserialize, Type)] pub struct CreateItemResult { pub(crate) item: OwnedObjectPath, pub(crate) prompt: OwnedObjectPath, } secret-service-4.0.0/src/proxy/item.rs000064400000000000000000000030531046102023000160250ustar 00000000000000//Copyright 2022 secret-service-rs Developers // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! A dbus proxy for speaking with secret service's `Item` Interface. use std::collections::HashMap; use zbus::zvariant::{ObjectPath, OwnedObjectPath}; use super::SecretStruct; /// A dbus proxy for speaking with secret service's `Item` Interface. /// /// This will derive ItemProxy #[zbus::proxy( interface = "org.freedesktop.Secret.Item", default_service = "org.freedesktop.Secret.Item" )] trait Item { fn delete(&self) -> zbus::Result; /// returns `Secret` fn get_secret(&self, session: &ObjectPath<'_>) -> zbus::Result; fn set_secret(&self, secret: SecretStruct) -> zbus::Result<()>; #[zbus(property)] fn locked(&self) -> zbus::fdo::Result; #[zbus(property)] fn attributes(&self) -> zbus::fdo::Result>; #[zbus(property)] fn set_attributes(&self, attributes: HashMap<&str, &str>) -> zbus::fdo::Result<()>; #[zbus(property)] fn label(&self) -> zbus::fdo::Result; #[zbus(property)] fn set_label(&self, new_label: &str) -> zbus::fdo::Result<()>; #[zbus(property)] fn created(&self) -> zbus::fdo::Result; #[zbus(property)] fn modified(&self) -> zbus::fdo::Result; } secret-service-4.0.0/src/proxy/mod.rs000064400000000000000000000013131046102023000156430ustar 00000000000000//Copyright 2022 secret-service-rs Developers // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. pub mod collection; pub mod item; pub mod prompt; pub mod service; use serde::{Deserialize, Serialize}; use zbus::zvariant::{OwnedObjectPath, Type}; #[derive(Debug, Serialize, Deserialize, Type)] pub struct SecretStruct { pub(crate) session: OwnedObjectPath, pub(crate) parameters: Vec, pub(crate) value: Vec, pub(crate) content_type: String, } secret-service-4.0.0/src/proxy/prompt.rs000064400000000000000000000017301046102023000164100ustar 00000000000000//Copyright 2022 secret-service-rs Developers // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! A dbus proxy for speaking with secret service's `Prompt` Interface. use zbus::zvariant::Value; /// A dbus proxy for speaking with secret service's `Prompt` Interface. /// /// This will derive PromptProxy /// /// Note that `Value` in the method signatures corresponds to `VARIANT` dbus type. #[zbus::proxy( interface = "org.freedesktop.Secret.Prompt", default_service = "org.freedesktop.Secret.Prompt" )] trait Prompt { fn prompt(&self, window_id: &str) -> zbus::Result<()>; fn dismiss(&self) -> zbus::Result<()>; #[zbus(signal)] fn completed(&self, dismissed: bool, result: Value<'_>) -> zbus::Result<()>; } secret-service-4.0.0/src/proxy/service.rs000064400000000000000000000047651046102023000165420ustar 00000000000000//Copyright 2022 secret-service-rs Developers // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! A dbus proxy for speaking with secret service's `Service` Interface. use super::SecretStruct; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use zbus::zvariant::{ObjectPath, OwnedObjectPath, OwnedValue, Type, Value}; /// A dbus proxy for speaking with secret service's `Service` Interface. /// /// This will derive ServiceProxy /// /// Note that `Value` in the method signatures corresponds to `VARIANT` dbus type. #[zbus::proxy( interface = "org.freedesktop.Secret.Service", default_service = "org.freedesktop.secrets", default_path = "/org/freedesktop/secrets" )] trait Service { fn open_session(&self, algorithm: &str, input: Value<'_>) -> zbus::Result; fn create_collection( &self, properties: HashMap<&str, Value<'_>>, alias: &str, ) -> zbus::Result; fn search_items(&self, attributes: HashMap<&str, &str>) -> zbus::Result; fn unlock(&self, objects: Vec<&ObjectPath<'_>>) -> zbus::Result; fn lock(&self, objects: Vec<&ObjectPath<'_>>) -> zbus::Result; fn get_secrets( &self, objects: Vec>, ) -> zbus::Result>; fn read_alias(&self, name: &str) -> zbus::Result; fn set_alias(&self, name: &str, collection: ObjectPath<'_>) -> zbus::Result<()>; #[zbus(property)] fn collections(&self) -> zbus::fdo::Result>>; } #[derive(Debug, Serialize, Deserialize, Type)] pub struct OpenSessionResult { pub(crate) output: OwnedValue, pub(crate) result: OwnedObjectPath, } #[derive(Debug, Serialize, Deserialize, Type)] pub struct CreateCollectionResult { pub(crate) collection: OwnedObjectPath, pub(crate) prompt: OwnedObjectPath, } #[derive(Debug, Serialize, Deserialize, Type)] pub struct SearchItemsResult { pub(crate) unlocked: Vec, pub(crate) locked: Vec, } #[derive(Debug, Serialize, Deserialize, Type)] pub struct LockActionResult { pub(crate) object_paths: Vec, pub(crate) prompt: OwnedObjectPath, } secret-service-4.0.0/src/session.rs000064400000000000000000000261051046102023000153740ustar 00000000000000//Copyright 2022 secret-service-rs Developers // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. // key exchange and crypto for session: // 1. Before session negotiation (openSession), set private key and public key using DH method. // 2. In session negotiation, send public key. // 3. As result of session negotiation, get object path for session, which (I think // it means that it uses the same server public key to create an aes key which is used // to decode the encoded secret using the aes seed that's sent with the secret). // 4. As result of session negotition, get server public key. // 5. Use server public key, my private key, to set an aes key using HKDF. // 6. Format Secret: aes iv is random seed, in secret struct it's the parameter (Array(Byte)) // 7. Format Secret: encode the secret value for the value field in secret struct. // This encoding uses the aes_key from the associated Session. use crate::proxy::service::{OpenSessionResult, ServiceProxy, ServiceProxyBlocking}; use crate::ss::{ALGORITHM_DH, ALGORITHM_PLAIN}; use crate::Error; use generic_array::{typenum::U16, GenericArray}; use num::{ bigint::BigUint, integer::Integer, traits::{One, Zero}, FromPrimitive, }; use once_cell::sync::Lazy; use rand::{rngs::OsRng, Rng}; use zbus::zvariant::OwnedObjectPath; use std::ops::{Mul, Rem, Shr}; // for key exchange static DH_GENERATOR: Lazy = Lazy::new(|| BigUint::from_u64(0x2).unwrap()); static DH_PRIME: Lazy = Lazy::new(|| { BigUint::from_bytes_be(&[ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2, 0x21, 0x68, 0xC2, 0x34, 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, 0x02, 0x0B, 0xBE, 0xA6, 0x3B, 0x13, 0x9B, 0x22, 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD, 0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, 0x30, 0x2B, 0x0A, 0x6D, 0xF2, 0x5F, 0x14, 0x37, 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45, 0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6, 0xF4, 0x4C, 0x42, 0xE9, 0xA6, 0x37, 0xED, 0x6B, 0x0B, 0xFF, 0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED, 0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5, 0xAE, 0x9F, 0x24, 0x11, 0x7C, 0x4B, 0x1F, 0xE6, 0x49, 0x28, 0x66, 0x51, 0xEC, 0xE6, 0x53, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, ]) }); #[allow(unused_macros)] macro_rules! feature_needed { () => { compile_error!("Please enable a feature to pick a runtime (such as rt-async-io-crypto-rust or rt-tokio-crypto-rust) for the secret-service crate") } } type AesKey = GenericArray; #[derive(Debug, Eq, PartialEq)] pub enum EncryptionType { Plain, Dh, } struct Keypair { private: BigUint, public: BigUint, } impl Keypair { fn generate() -> Self { let mut rng = OsRng {}; let mut private_key_bytes = [0; 128]; rng.fill(&mut private_key_bytes); let private_key = BigUint::from_bytes_be(&private_key_bytes); let public_key = powm(&DH_GENERATOR, &private_key, &DH_PRIME); Self { private: private_key, public: public_key, } } fn derive_shared(&self, server_public_key: &BigUint) -> AesKey { // Derive the shared secret the server and us. let common_secret = powm(server_public_key, &self.private, &DH_PRIME); let mut common_secret_bytes = common_secret.to_bytes_be(); let mut common_secret_padded = vec![0; 128 - common_secret_bytes.len()]; common_secret_padded.append(&mut common_secret_bytes); // hkdf // input keying material let ikm = common_secret_padded; let salt = None; // output keying material let mut okm = [0; 16]; hkdf(ikm, salt, &mut okm); GenericArray::clone_from_slice(&okm) } } #[cfg(feature = "crypto-openssl")] fn hkdf(ikm: Vec, salt: Option<&[u8]>, okm: &mut [u8]) { let mut ctx = openssl::pkey_ctx::PkeyCtx::new_id(openssl::pkey::Id::HKDF) .expect("hkdf context should not fail"); ctx.derive_init().expect("hkdf derive init should not fail"); ctx.set_hkdf_md(openssl::md::Md::sha256()) .expect("hkdf set md should not fail"); ctx.set_hkdf_key(&ikm) .expect("hkdf set key should not fail"); if let Some(salt) = salt { ctx.set_hkdf_salt(salt) .expect("hkdf set salt should not fail"); } ctx.add_hkdf_info(&[]).unwrap(); ctx.derive(Some(okm)) .expect("hkdf expand should never fail"); } #[cfg(feature = "crypto-rust")] fn hkdf(ikm: Vec, salt: Option<&[u8]>, okm: &mut [u8]) { use hkdf::Hkdf; use sha2::Sha256; let info = []; let (_, hk) = Hkdf::::extract(salt, &ikm); hk.expand(&info, okm) .expect("hkdf expand should never fail"); } #[cfg(all(not(feature = "crypto-rust"), not(feature = "crypto-openssl")))] fn hkdf(ikm: Vec, salt: Option<&[u8]>, okm: &mut [u8]) { feature_needed!() } pub struct Session { pub object_path: OwnedObjectPath, aes_key: Option, } impl Session { fn encrypted_session(keypair: &Keypair, session: OpenSessionResult) -> Result { let server_public_key = session .output .try_into() .map(|key: Vec| BigUint::from_bytes_be(&key))?; let aes_key = keypair.derive_shared(&server_public_key); Ok(Session { object_path: session.result, aes_key: Some(aes_key), }) } pub fn new_blocking( service_proxy: &ServiceProxyBlocking, encryption: EncryptionType, ) -> Result { match encryption { EncryptionType::Plain => { let session = service_proxy.open_session(ALGORITHM_PLAIN, "".into())?; let session_path = session.result; Ok(Session { object_path: session_path, aes_key: None, }) } EncryptionType::Dh => { let keypair = Keypair::generate(); let session = service_proxy .open_session(ALGORITHM_DH, keypair.public.to_bytes_be().into())?; Self::encrypted_session(&keypair, session) } } } pub async fn new( service_proxy: &ServiceProxy<'_>, encryption: EncryptionType, ) -> Result { match encryption { EncryptionType::Plain => { let session = service_proxy .open_session(ALGORITHM_PLAIN, "".into()) .await?; let session_path = session.result; Ok(Session { object_path: session_path, aes_key: None, }) } EncryptionType::Dh => { let keypair = Keypair::generate(); let session = service_proxy .open_session(ALGORITHM_DH, keypair.public.to_bytes_be().into()) .await?; Self::encrypted_session(&keypair, session) } } } pub fn get_aes_key(&self) -> Option<&AesKey> { self.aes_key.as_ref() } } /// from https://github.com/plietar/librespot/blob/master/core/src/util/mod.rs#L53 fn powm(base: &BigUint, exp: &BigUint, modulus: &BigUint) -> BigUint { let mut base = base.clone(); let mut exp = exp.clone(); let mut result: BigUint = One::one(); while !exp.is_zero() { if exp.is_odd() { result = result.mul(&base).rem(modulus); } exp = exp.shr(1); base = (&base).mul(&base).rem(modulus); } result } #[cfg(feature = "crypto-rust")] pub fn encrypt(data: &[u8], key: &AesKey, iv: &[u8]) -> Vec { use aes::cipher::block_padding::Pkcs7; use aes::cipher::{BlockEncryptMut, KeyIvInit}; type Aes128CbcEnc = cbc::Encryptor; let iv = GenericArray::from_slice(iv); Aes128CbcEnc::new(key, iv).encrypt_padded_vec_mut::(data) } #[cfg(feature = "crypto-rust")] pub fn decrypt(encrypted_data: &[u8], key: &AesKey, iv: &[u8]) -> Result, Error> { use aes::cipher::block_padding::Pkcs7; use aes::cipher::{BlockDecryptMut, KeyIvInit}; type Aes128CbcDec = cbc::Decryptor; let iv = GenericArray::from_slice(iv); Aes128CbcDec::new(key, iv) .decrypt_padded_vec_mut::(encrypted_data) .map_err(|_| Error::Crypto("message decryption failed")) } #[cfg(feature = "crypto-openssl")] pub fn encrypt(data: &[u8], key: &AesKey, iv: &[u8]) -> Vec { use openssl::cipher::Cipher; use openssl::cipher_ctx::CipherCtx; let mut ctx = CipherCtx::new().expect("cipher creation should not fail"); ctx.encrypt_init(Some(Cipher::aes_128_cbc()), Some(key), Some(iv)) .expect("cipher init should not fail"); let mut output = vec![]; ctx.cipher_update_vec(data, &mut output) .expect("cipher update should not fail"); ctx.cipher_final_vec(&mut output) .expect("cipher final should not fail"); output } #[cfg(feature = "crypto-openssl")] pub fn decrypt(encrypted_data: &[u8], key: &AesKey, iv: &[u8]) -> Result, Error> { use openssl::cipher::Cipher; use openssl::cipher_ctx::CipherCtx; let mut ctx = CipherCtx::new().expect("cipher creation should not fail"); ctx.decrypt_init(Some(Cipher::aes_128_cbc()), Some(key), Some(iv)) .expect("cipher init should not fail"); let mut output = vec![]; ctx.cipher_update_vec(encrypted_data, &mut output) .map_err(|_| Error::Crypto("message decryption failed"))?; ctx.cipher_final_vec(&mut output) .map_err(|_| Error::Crypto("message decryption failed"))?; Ok(output) } #[cfg(all(not(feature = "crypto-rust"), not(feature = "crypto-openssl")))] pub fn encrypt(data: &[u8], key: &AesKey, iv: &[u8]) -> Vec { feature_needed!() } #[cfg(all(not(feature = "crypto-rust"), not(feature = "crypto-openssl")))] pub fn decrypt(encrypted_data: &[u8], key: &AesKey, iv: &[u8]) -> Result, Error> { feature_needed!() } #[cfg(test)] mod test { use super::*; // There is no async test because this tests that an encryption session can be made, nothing more. #[test] fn should_create_plain_session() { let conn = zbus::blocking::Connection::session().unwrap(); let service_proxy = ServiceProxyBlocking::new(&conn).unwrap(); let session = Session::new_blocking(&service_proxy, EncryptionType::Plain).unwrap(); assert!(session.get_aes_key().is_none()); } #[test] fn should_create_encrypted_session() { let conn = zbus::blocking::Connection::session().unwrap(); let service_proxy = ServiceProxyBlocking::new(&conn).unwrap(); let session = Session::new_blocking(&service_proxy, EncryptionType::Dh).unwrap(); assert!(session.get_aes_key().is_some()); } } secret-service-4.0.0/src/ss.rs000064400000000000000000000015471046102023000143410ustar 00000000000000//Copyright 2022 secret-service-rs Developers // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. // Definitions for secret service interactions // DBus Name pub const SS_DBUS_NAME: &str = "org.freedesktop.secrets"; // Item Properties pub const SS_ITEM_LABEL: &str = "org.freedesktop.Secret.Item.Label"; pub const SS_ITEM_ATTRIBUTES: &str = "org.freedesktop.Secret.Item.Attributes"; // Algorithm Names pub const ALGORITHM_PLAIN: &str = "plain"; pub const ALGORITHM_DH: &str = "dh-ietf1024-sha256-aes128-cbc-pkcs7"; // Collection properties pub const SS_COLLECTION_LABEL: &str = "org.freedesktop.Secret.Collection.Label"; secret-service-4.0.0/src/util.rs000064400000000000000000000111001046102023000146530ustar 00000000000000//Copyright 2016 secret-service-rs Developers // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! Contains helpers for: //! locking/unlocking //! exec_prompt //! formatting secrets use crate::error::Error; use crate::proxy::prompt::{Completed, PromptProxy, PromptProxyBlocking}; use crate::proxy::service::{ServiceProxy, ServiceProxyBlocking}; use crate::proxy::SecretStruct; use crate::session::encrypt; use crate::session::Session; use crate::ss::SS_DBUS_NAME; use rand::{rngs::OsRng, Rng}; use zbus::export::ordered_stream::OrderedStreamExt; use zbus::{ zvariant::{self, ObjectPath}, CacheProperties, }; // Helper enum for locking pub(crate) enum LockAction { Lock, Unlock, } pub(crate) async fn lock_or_unlock( conn: zbus::Connection, service_proxy: &ServiceProxy<'_>, object_path: &ObjectPath<'_>, lock_action: LockAction, ) -> Result<(), Error> { let objects = vec![object_path]; let lock_action_res = match lock_action { LockAction::Lock => service_proxy.lock(objects).await?, LockAction::Unlock => service_proxy.unlock(objects).await?, }; if lock_action_res.object_paths.is_empty() { exec_prompt(conn, &lock_action_res.prompt).await?; } Ok(()) } pub(crate) fn lock_or_unlock_blocking( conn: zbus::blocking::Connection, service_proxy: &ServiceProxyBlocking, object_path: &ObjectPath, lock_action: LockAction, ) -> Result<(), Error> { let objects = vec![object_path]; let lock_action_res = match lock_action { LockAction::Lock => service_proxy.lock(objects)?, LockAction::Unlock => service_proxy.unlock(objects)?, }; if lock_action_res.object_paths.is_empty() { exec_prompt_blocking(conn, &lock_action_res.prompt)?; } Ok(()) } pub(crate) fn format_secret( session: &Session, secret: &[u8], content_type: &str, ) -> Result { let content_type = content_type.to_owned(); if let Some(session_key) = session.get_aes_key() { let mut rng = OsRng {}; let mut aes_iv = [0; 16]; rng.fill(&mut aes_iv); let encrypted_secret = encrypt(secret, session_key, &aes_iv); // Construct secret struct let parameters = aes_iv.to_vec(); let value = encrypted_secret; Ok(SecretStruct { session: session.object_path.clone(), parameters, value, content_type, }) } else { // just Plain for now let parameters = Vec::new(); let value = secret.to_vec(); Ok(SecretStruct { session: session.object_path.clone(), parameters, value, content_type, }) } } // TODO: Users could pass their own window ID in. const NO_WINDOW_ID: &str = ""; pub(crate) async fn exec_prompt( conn: zbus::Connection, prompt: &ObjectPath<'_>, ) -> Result { let prompt_proxy = PromptProxy::builder(&conn) .destination(SS_DBUS_NAME)? .path(prompt)? .cache_properties(CacheProperties::No) .build() .await?; let mut receive_completed_iter = prompt_proxy.receive_completed().await?; prompt_proxy.prompt(NO_WINDOW_ID).await?; handle_signal(receive_completed_iter.next().await.unwrap()) } pub(crate) fn exec_prompt_blocking( conn: zbus::blocking::Connection, prompt: &ObjectPath, ) -> Result { let prompt_proxy = PromptProxyBlocking::builder(&conn) .destination(SS_DBUS_NAME)? .path(prompt)? .cache_properties(CacheProperties::No) .build()?; let mut receive_completed_iter = prompt_proxy.receive_completed()?; prompt_proxy.prompt(NO_WINDOW_ID)?; handle_signal(receive_completed_iter.next().unwrap()) } fn handle_signal(signal: Completed) -> Result { let args = signal.args()?; if args.dismissed { Err(Error::Prompt) } else { zvariant::OwnedValue::try_from(args.result).map_err(From::from) } } pub(crate) fn handle_conn_error(e: zbus::Error) -> Error { match e { zbus::Error::InterfaceNotFound | zbus::Error::Address(_) => Error::Unavailable, zbus::Error::InputOutput(e) if e.kind() == std::io::ErrorKind::NotFound => { Error::Unavailable } e => e.into(), } }