dbus-secret-service-4.0.2/.cargo_vcs_info.json0000644000000001360000000000100147150ustar { "git": { "sha1": "305f79918c4e1105e2f14bcda1970fd600a11f99" }, "path_in_vcs": "" }dbus-secret-service-4.0.2/.gitignore000064400000000000000000000000371046102023000154750ustar 00000000000000bin target Cargo.lock .vscode/dbus-secret-service-4.0.2/CHANGELOG.md000064400000000000000000000001621046102023000153150ustar 00000000000000The detailed list of changes in each release can be found at https://github.com/hwchen/secret-service-rs/releases.dbus-secret-service-4.0.2/Cargo.lock0000644000000372220000000000100126760ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[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 = "autocfg" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "bitflags" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[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 = "cbc" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" dependencies = [ "cipher", ] [[package]] name = "cc" version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2aba8f4e9906c7ce3c73463f62a7f0c65183ada1a2d47e397cc8810827f9694f" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[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 = "cpufeatures" version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" dependencies = [ "libc", ] [[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 = "dbus" version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bb21987b9fb1613058ba3843121dd18b163b254d8a6e797e144cbac14d96d1b" dependencies = [ "libc", "libdbus-sys", "winapi", ] [[package]] name = "dbus-secret-service" version = "4.0.2" dependencies = [ "aes", "block-padding", "cbc", "dbus", "futures-util", "hkdf", "num", "once_cell", "openssl", "rand", "sha2", "test-with", ] [[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 = "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-macro" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", "syn 2.0.72", ] [[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-macro", "futures-task", "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 = "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 = "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 = "libdbus-sys" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06085512b750d640299b79be4bad3d2fa90a9c00b1fd9e1b46364f66f0485c72" dependencies = [ "cc", "pkg-config", ] [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[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.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ "num-integer", "num-traits", ] [[package]] name = "num-complex" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" dependencies = [ "num-traits", ] [[package]] name = "num-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 = "once_cell" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "openssl" version = "0.10.66" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" 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.72", ] [[package]] name = "openssl-src" version = "300.3.1+3.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7259953d42a81bf137fbbd73bd30a8e1914d6dce43c2b90ed575783a22608b91" dependencies = [ "cc", ] [[package]] name = "openssl-sys" version = "0.9.103" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" dependencies = [ "cc", "libc", "openssl-src", "pkg-config", "vcpkg", ] [[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 = "pkg-config" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "ppv-lite86" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[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.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" 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.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[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 = "slab" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] [[package]] name = "subtle" version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "unicode-ident", ] [[package]] name = "syn" version = "2.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "test-with" version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3a0c1b477619de2a1bf72990195561a06f7b68bbf272cea676236ad7cfb9e8" dependencies = [ "proc-macro-error", "proc-macro2", "quote", "regex", "syn 2.0.72", ] [[package]] name = "typenum" version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[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" dbus-secret-service-4.0.2/Cargo.toml0000644000000036500000000000100127170ustar # 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.70" name = "dbus-secret-service" version = "4.0.2" authors = ["Daniel Brotsky "] exclude = [".github/"] description = "Library to interface with Secret Service API over DBUS" homepage = "https://github.com/brotskydotcom/dbus-secret-service" documentation = "https://docs.rs/dbus-secret-service" readme = "README.md" keywords = [ "secret-service", "password", "linux", "dbus", ] license = "MIT OR Apache-2.0" repository = "https://github.com/brotskydotcom/dbus-secret-service.git" [package.metadata.docs.rs] features = ["crypto-rust"] [dependencies.aes] version = "0.8" optional = true [dependencies.block-padding] version = "0.3" features = ["std"] optional = true [dependencies.cbc] version = "0.1" features = [ "block-padding", "alloc", ] optional = true [dependencies.dbus] version = "0.9" [dependencies.futures-util] version = "0.3" [dependencies.hkdf] version = "0.12" optional = true [dependencies.num] version = "0.4" [dependencies.once_cell] version = "1" [dependencies.openssl] version = "0.10.55" optional = true [dependencies.rand] version = "0.8" [dependencies.sha2] version = "0.10" optional = true [dev-dependencies.test-with] version = "0.12" default-features = false [features] crypto-openssl = ["dep:openssl"] crypto-rust = [ "dep:aes", "dep:block-padding", "dep:cbc", "dep:sha2", "dep:hkdf", ] vendored = [ "dbus/vendored", "openssl?/vendored", ] dbus-secret-service-4.0.2/Cargo.toml.orig000064400000000000000000000023531046102023000163770ustar 00000000000000[package] authors = ["Daniel Brotsky "] description = "Library to interface with Secret Service API over DBUS" documentation = "https://docs.rs/dbus-secret-service" homepage = "https://github.com/brotskydotcom/dbus-secret-service" repository = "https://github.com/brotskydotcom/dbus-secret-service.git" keywords = ["secret-service", "password", "linux", "dbus"] license = "MIT OR Apache-2.0" name = "dbus-secret-service" version = "4.0.2" edition = "2021" rust-version = "1.70" exclude = [".github/"] [features] vendored = ["dbus/vendored", "openssl?/vendored"] crypto-rust = ["dep:aes", "dep:block-padding", "dep:cbc", "dep:sha2", "dep:hkdf"] crypto-openssl = ["dep:openssl"] [dependencies] aes = { version = "0.8", optional = true } block-padding = { version = "0.3", features = ["std"], optional = true } cbc = { version = "0.1", features = ["block-padding", "alloc"], optional = true } dbus = "0.9" futures-util = "0.3" hkdf = { version = "0.12", optional = true } num = "0.4" once_cell = "1" openssl = { version = "0.10.55", optional = true } rand = "0.8" sha2 = { version = "0.10", optional = true } [dev-dependencies] test-with = { version = "0.12", default-features = false } [package.metadata.docs.rs] features = ["crypto-rust"] dbus-secret-service-4.0.2/LICENSE-APACHE000064400000000000000000000251371046102023000154410ustar 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. dbus-secret-service-4.0.2/LICENSE-MIT000064400000000000000000000020551046102023000151430ustar 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. dbus-secret-service-4.0.2/NOTES_ON_PORT.md000064400000000000000000000042101046102023000161540ustar 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. dbus-secret-service-4.0.2/README.md000064400000000000000000000065761046102023000150020ustar 00000000000000# dbus-secret-service [![build](https://github.com/brotskydotcom/dbus-secret-service/actions/workflows/ci.yaml/badge.svg)](https://github.com/brotskydotcom/dbus-secret-service/actions) [![dependencies](https://deps.rs/repo/github/brotskydotcom/dbus-secret-service/status.svg)](https://deps.rs/repo/github/brotskydotcom/dbus-secret-service) [![crates.io](https://img.shields.io/crates/v/dbus-secret-service.svg?style=flat-square)](https://crates.io/crates/dbus-secret-service) [![docs.rs](https://docs.rs/dbus-secret-service/badge.svg)](https://docs.rs/dbus-secret-service) This crate is a knock-off of the [hwchen/secret-service](https://crates.io/crates/secret-service) crate, which is currently at version 4 and uses [zbus](https://crates.io/crates/zbus) to access the secret service. The basic collection, item and search APIs in this crate are meant to work the same as the blocking APIs in the zbus-based crate. If they don't, please file a bug. Why do a knock-off? So that folks who write synchronous Rust apps that access the secret service (typically through the [hwchen/keyring](https://crates.io/crates/keyring) crate) are not required to add an async runtime. Because this knock-off uses lib-dbus, it doesn't require an async runtime. Why is this crate starting at version 4? Since its API matches a particular version of the dbus-based crate, I figured it would be clearest if its version number matched that version as well. ## Usage For code usage examples, see the [documentation](https://docs.rs/dbus-secret-service). This crate has no default features, and requires no features to run. If you need your secrets to be encrypted on their way to and from the secret service, then add one of the crypto features: * `crypto-rust` uses pure Rust crates for encryption. * `crypto-openssl` uses the openssl libraries for encryption (which must be installed). See the [documentation](https://docs.rs/dbus-secret-service) for details on how to specify use of an encrypted session. To _build_ a project that uses this crate, your development machine will need to have the dbus development headers installed, and the openssl development headers for the `crypto-openssl` feature. To _run_ an application that uses this crate, your machine will need to have `libdbus` installed (almost all do), and the openssl libraries for the `crypto-openssl` feature. If you want to avoid this runtime requirement, you can specify the `vendored` feature at build time: this will statically link the needed libraries with your executable. ### Functionality - SecretService: initialize dbus, create plain/encrypted session. - Collections: create, delete, search. - Items: create, delete, search, get/set secret. ## Changelog v4.0.0: first release, same API as secret-service v4.0. ## License The copyright to all material in this repository belongs to the collective of contributors who have checked material into this repository. All material is this repository is 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. dbus-secret-service-4.0.2/examples/example.rs000064400000000000000000000040141046102023000173230ustar 00000000000000// Copyright 2016-2024 dbus-secret-service Contributors // // 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 dbus_secret_service::{EncryptionType, SecretService}; use std::{collections::HashMap, str}; fn main() { // Initialize secret service let ss = SecretService::connect(EncryptionType::Plain).unwrap(); // navigate to default collection let collection = ss.get_default_collection().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 ) .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).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().unwrap(); locked_item } }; // retrieve secret from item let secret = item.get_secret().unwrap(); println!("Retrieved secret: {:?}", str::from_utf8(&secret).unwrap()); assert_eq!(secret, b"test_secret"); item.delete().unwrap(); } dbus-secret-service-4.0.2/justfile000064400000000000000000000001131046102023000152500ustar 00000000000000test filter = '': cargo watch -x 'test {{filter}} -- --test-threads=1' dbus-secret-service-4.0.2/src/collection.rs000064400000000000000000000177001046102023000170020ustar 00000000000000// Copyright 2016-2024 dbus-secret-service Contributors // // 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::collections::HashMap; use dbus::{ arg::{PropMap, RefArg, Variant}, blocking::{Connection, Proxy}, strings::Path, }; use crate::{ proxy::collection::Collection as ProxyCollection, proxy::new_proxy, ss::{SS_ITEM_ATTRIBUTES, SS_ITEM_LABEL}, Error, Item, LockAction, SecretService, }; /// Represents a Secret Service collection of items. /// /// Collections are retrieved from and created by a /// [`SecretService`] instance and cannot outlive it. pub struct Collection<'a> { service: &'a SecretService, pub(crate) path: Path<'static>, } impl<'a> Collection<'a> { pub(crate) fn new(service: &'a SecretService, path: Path<'static>) -> Collection<'a> { Collection { service, path } } fn proxy(&self) -> Proxy<&Connection> { new_proxy(&self.service.connection, &self.path) } pub fn is_locked(&self) -> Result { Ok(self.proxy().locked()?) } pub fn ensure_unlocked(&self) -> Result<(), Error> { if self.is_locked()? { self.unlock() } else { Ok(()) } } pub fn unlock(&self) -> Result<(), Error> { let paths = vec![self.path.clone()]; self.service.lock_unlock_all(LockAction::Unlock, paths) } pub fn lock(&self) -> Result<(), Error> { let paths = vec![self.path.clone()]; self.service.lock_unlock_all(LockAction::Lock, paths) } /// Delete the underlying dbus collection pub fn delete(&self) -> Result<(), Error> { let p_path = self.proxy().delete()?; if p_path != Path::new("/").unwrap() { self.service.prompt_for_lock_unlock_delete(&p_path) } else { Ok(()) } } pub fn get_all_items(&self) -> Result>, Error> { let paths = self.proxy().items()?; let result = paths .into_iter() .map(|path| Item::new(self.service, path)) .collect(); Ok(result) } pub fn search_items(&self, attributes: HashMap<&str, &str>) -> Result>, Error> { let paths = self.proxy().search_items(attributes)?; let result = paths .into_iter() .map(|path| Item::new(self.service, path)) .collect(); Ok(result) } pub fn get_label(&self) -> Result { Ok(self.proxy().label()?) } pub fn set_label(&self, new_label: &str) -> Result<(), Error> { Ok(self.proxy().set_label(new_label.to_string())?) } pub fn create_item( &self, label: &str, attributes: HashMap<&str, &str>, secret: &[u8], replace: bool, content_type: &str, ) -> Result, Error> { let encrypted = self.service.session.encrypt_secret(secret, content_type); let attributes: HashMap = attributes .iter() .map(|(k, v)| (k.to_string(), v.to_string())) .collect(); let attributes = Box::new(attributes) as Box; let label = Box::new(label.to_string()) as Box; let mut properties: PropMap = PropMap::new(); properties.insert(SS_ITEM_LABEL.to_string(), Variant(label)); properties.insert(SS_ITEM_ATTRIBUTES.to_string(), Variant(attributes)); let (c_path, p_path) = self.proxy() .create_item(properties, encrypted.to_dbus(), replace)?; let created = { if c_path == Path::new("/")? { // no creation path, so prompt self.service.prompt_for_create(&p_path)? } else { c_path } }; Ok(Item::new(self.service, created)) } } #[cfg(test)] mod test { use crate::*; #[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_with::no_env(GITHUB_ACTIONS)] // can't run headless - prompts fn should_lock_and_unlock() { let ss = SecretService::connect(EncryptionType::Plain).unwrap(); let collection = ss .create_collection("TestCollectionLockUnlock", "") .unwrap(); collection.ensure_unlocked().unwrap(); collection.lock().unwrap(); assert!(collection.is_locked().unwrap()); collection.delete().unwrap(); } #[test_with::no_env(GITHUB_ACTIONS)] // can't run headless - prompts fn should_delete_collection() { let ss = SecretService::connect(EncryptionType::Plain).unwrap(); ss.create_collection("TestDelete", "").unwrap(); let collections = ss.get_all_collections().unwrap(); let count_before = collections.len(); for collection in collections { let collection_path = &collection.path; if collection_path.contains("/Test") { collection.delete().unwrap(); } } //double check after let collections = ss.get_all_collections().unwrap(); assert!( collections.len() < count_before, "collections before delete {count_before} after delete {}", collections.len() ); } #[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.path, search_item[0].path); item.delete().unwrap(); } #[test_with::no_env(GITHUB_ACTIONS)] // can't run headless - prompts fn should_get_and_set_collection_label() { let ss = SecretService::connect(EncryptionType::Plain).unwrap(); let collection = ss.create_collection("TestGetSetLabel", "").unwrap(); let label = collection.get_label().unwrap(); assert_eq!(label, "TestGetSetLabel"); // Set label to test and check collection.ensure_unlocked().unwrap(); collection.set_label("DoubleTest").unwrap(); let label = collection.get_label().unwrap(); assert_eq!(label, "DoubleTest"); // Reset label to original and test collection.ensure_unlocked().unwrap(); collection.set_label("Test").unwrap(); let label = collection.get_label().unwrap(); assert_eq!(label, "Test"); collection.delete().unwrap(); } } dbus-secret-service-4.0.2/src/error.rs000064400000000000000000000063021046102023000157740ustar 00000000000000// Copyright 2016-2024 dbus-secret-service Contributors // // 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}; /// An error that could occur interacting with the secret service dbus interface. #[derive(Debug)] #[non_exhaustive] pub enum Error { /// An error occurred decrypting a response message. /// The type of the error will depend on which crypto is being used. Crypto(Box), /// A bad path was handed to the secret service. Path(String), /// The response value of a secret service call couldn't be parsed. Parse, /// A call into the secret service provider failed. Dbus(dbus::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 not found on the system. Unavailable, /// The provided secret was not text/plain UnsupportedSecretFormat, } 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::Path(err) => write!(f, "DBus object path error: {err}"), Error::Dbus(err) => write!(f, "DBus error: {err}"), Error::Locked => f.write_str("Secret Service: object locked"), Error::NoResult => f.write_str("Secret Service: no result found"), Error::Prompt => f.write_str("Secret Service: unlock prompt was dismissed"), Error::Unavailable => f.write_str("No DBus session or Secret Service provider found"), Error::UnsupportedSecretFormat => f.write_str("Secrets must have MIME type text/plain"), _ => write!(f, "Unexpected Error: {self:?}"), } } } impl error::Error for Error { fn source(&self) -> Option<&(dyn error::Error + 'static)> { match *self { Error::Dbus(ref err) => Some(err), _ => None, } } } impl From for Error { fn from(err: dbus::Error) -> Error { Error::Dbus(err) } } impl From for Error { // dbus parse errors return strings fn from(s: String) -> Error { Error::Path(s) } } #[cfg(feature = "crypto-rust")] impl From for Error { fn from(err: aes::cipher::block_padding::UnpadError) -> Error { Error::Crypto(Box::new(err)) } } #[cfg(feature = "crypto-openssl")] impl From for Error { fn from(err: openssl::error::ErrorStack) -> Error { Error::Crypto(Box::new(err)) } } #[cfg(feature = "crypto-openssl")] impl From for Error { fn from(err: openssl::error::Error) -> Error { Error::Crypto(Box::new(err)) } } dbus-secret-service-4.0.2/src/item.rs000064400000000000000000000301571046102023000156060ustar 00000000000000// Copyright 2016-2024 dbus-secret-service Contributors // // 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::collections::HashMap; use dbus::{ blocking::{Connection, Proxy}, strings::Path, }; use crate::{ error::Error, proxy::{item::Item as ProxyItem, new_proxy}, session::EncryptedSecret, LockAction, SecretService, }; /// Represents a Secret Service item that has key/value attributes and a secret. /// /// Item lifetimes are tied to the [`SecretService`] instance they were retrieved /// from or created by (whether directly or via a [`crate::Collection`] object), and they /// cannot outlive that instance. pub struct Item<'a> { service: &'a SecretService, pub(crate) path: Path<'static>, } impl<'a> Item<'a> { pub(crate) fn new(service: &'a SecretService, path: Path<'static>) -> Item<'a> { Item { service, path } } fn proxy(&self) -> Proxy<&Connection> { new_proxy(&self.service.connection, &self.path) } pub fn is_locked(&self) -> Result { Ok(self.proxy().locked()?) } pub fn ensure_unlocked(&self) -> Result<(), Error> { if self.is_locked()? { self.unlock() } else { Ok(()) } } pub fn unlock(&self) -> Result<(), Error> { let paths = vec![self.path.clone()]; self.service.lock_unlock_all(LockAction::Unlock, paths) } pub fn lock(&self) -> Result<(), Error> { let paths = vec![self.path.clone()]; self.service.lock_unlock_all(LockAction::Lock, paths) } pub fn get_attributes(&self) -> Result, Error> { Ok(self.proxy().attributes()?) } pub fn set_attributes(&self, attributes: HashMap<&str, &str>) -> Result<(), Error> { let attributes = attributes .into_iter() .map(|(k, v)| (k.to_string(), v.to_string())) .collect(); Ok(self.proxy().set_attributes(attributes)?) } pub fn get_label(&self) -> Result { Ok(self.proxy().label()?) } pub fn set_label(&self, new_label: &str) -> Result<(), Error> { Ok(self.proxy().set_label(new_label.to_string())?) } /// Delete the underlying dbus item pub fn delete(&self) -> Result<(), Error> { let p_path = self.proxy().delete()?; if p_path != Path::new("/").unwrap() { self.service.prompt_for_lock_unlock_delete(&p_path) } else { Ok(()) } } pub fn get_secret(&self) -> Result, Error> { let tuple = self.proxy().get_secret(self.service.session.path.clone())?; let encrypted = EncryptedSecret::from_dbus(tuple); let decrypted = self.service.session.decrypt_secret(encrypted)?; Ok(decrypted) } pub fn get_secret_content_type(&self) -> Result { let tuple = self.proxy().get_secret(self.service.session.path.clone())?; let encrypted = EncryptedSecret::from_dbus(tuple); let mime = encrypted.mime.clone(); let _ = self.service.session.decrypt_secret(encrypted)?; Ok(mime) } pub fn set_secret(&self, secret: &[u8], content_type: &str) -> Result<(), Error> { let encrypted = self.service.session.encrypt_secret(secret, content_type); Ok(self.proxy().set_secret(encrypted.to_dbus())?) } pub fn get_created(&self) -> Result { Ok(self.proxy().created()?) } pub fn get_modified(&self) -> Result { Ok(self.proxy().modified()?) } /// Compare items to see if they refer to the same secret service object. pub fn equal_to(&self, other: &Item<'_>) -> Result { Ok(self.path == other.path) } } #[cfg(test)] mod test { use crate::*; 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_with::no_env(GITHUB_ACTIONS)] // can't run headless - prompts fn should_lock_and_unlock() { let ss = SecretService::connect(EncryptionType::Plain).unwrap(); let collection = ss.create_collection("TestItemLockUnlock", "").unwrap(); let item = create_test_default_item(&collection); item.ensure_unlocked().unwrap(); item.lock().unwrap(); assert!(item.is_locked().unwrap()); item.ensure_unlocked().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"); } #[cfg(any(feature = "crypto-rust", feature = "crypto-openssl"))] #[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"); } #[cfg(any(feature = "crypto-rust", feature = "crypto-openssl"))] #[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"); } #[cfg(any(feature = "crypto-rust", feature = "crypto-openssl"))] #[test] fn should_create_encrypted_item_from_empty_secret() { //empty string 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""); } #[cfg(any(feature = "crypto-rust", feature = "crypto-openssl"))] #[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(); } } } dbus-secret-service-4.0.2/src/lib.rs000064400000000000000000000342461046102023000154210ustar 00000000000000// Copyright 2016-2024 dbus-secret-service Contributors // // 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. #![allow(clippy::needless_doctest_main)] //! # Dbus-Based access to the Secret Service //! //! This library implements a rust wrapper that uses libdbus to access the Secret Service. //! Its use requires that a session `DBus` is available on the target machine. //! //! ## About the Secret Service //! //! //! //! The Secret Service provides a secure mechanism for persistent storage of data. //! Both the Gnome keyring and the KWallet implement the Secret Service API. //! //! ## Basic Usage //! //! ``` //! use dbus_secret_service::SecretService; //! use dbus_secret_service::EncryptionType; //! use std::collections::HashMap; //! //! fn main() { //! // initialize secret service (dbus connection and encryption session) //! let ss = SecretService::connect(EncryptionType::Plain).unwrap(); //! //! // get default collection //! let collection = ss.get_default_collection().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 //! ).unwrap(); //! //! // search items by properties //! let search_items = ss.search_items( //! HashMap::from([("test", "test_value")]) //! ).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().unwrap(); //! locked_item //! } //! }; //! //! // retrieve secret from item //! let secret = item.get_secret().unwrap(); //! assert_eq!(secret, b"test_secret"); //! //! // delete item (deletes the dbus object, not the struct instance) //! item.delete().unwrap() //! } //! ``` //! //! ## Overview of this library: //! ### Entry point //! The entry point for this library is the [`SecretService`] struct. Creating an instance //! of this structure will initialize the dbus connection and create a session with the //! Secret Service. //! //! ``` //! # use dbus_secret_service::SecretService; //! # use dbus_secret_service::EncryptionType; //! # fn call() { //! SecretService::connect(EncryptionType::Plain).unwrap(); //! # } //! ``` //! A session started with `EncryptionType::Plain` does not obscure the content //! of secrets in memory when sending them to and from the Secret Service. These //! secrets _are_ encrypted by the Secret Service when put into its secure store. //! //! If you have specified a crypto feature (`crypto-rust` or `crypto-openssl`), //! then you can use `EncryptionType:Dh` to force Diffie-Hellman shared key encryption //! of secrets in memory when they are being sent to and received from the Secret Service. //! //! Once you have created a `SecretService` struct, you can use it to search for items, //! connect to the default collection of items, and to create new collections. The lifetimes //! of all the collection and item objects you retrieve from the service are tied to //! the service, so they cannot outlive the service instance. This restriction will //! be enforced by the Rust compiler. //! //! ### 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 structure's documentation. //! //! In addition, `set` and `get` actions are available for secrets contained in an `Item`. //! use std::collections::HashMap; pub use collection::Collection; use dbus::arg::RefArg; use dbus::{ arg::{PropMap, Variant}, blocking::{Connection, Proxy}, strings::Path, }; pub use error::Error; pub use item::Item; use proxy::{new_proxy, service::Service}; pub use session::EncryptionType; use session::Session; use ss::{SS_COLLECTION_LABEL, SS_DBUS_PATH}; mod collection; mod error; mod item; mod prompt; mod proxy; mod session; mod ss; /// Encapsulates a session connected to the Secret Service. pub struct SecretService { connection: Connection, session: Session, timeout: Option, } /// Represents the results of doing a service-wide search. /// /// The returned items are organized in two vectors: one /// holds unlocked items and the other holds locked items. /// (Reading or writing the secret of a locked item requires /// prompting the user interactively for permission. This /// prompting is done by the Secret Service itself.) pub struct SearchItemsResult { pub unlocked: Vec, pub locked: Vec, } pub(crate) enum LockAction { Lock, Unlock, } impl SecretService { /// Connect to the DBus and return a new [SecretService] instance. /// /// If this service instance needs to prompt a user for permission to /// access a locked item or collection, it will block indefinitely waiting for /// the user's response See [connect_with_timeout] if you want /// different behavior. pub fn connect(encryption: EncryptionType) -> Result { let connection = Connection::new_session()?; let session = Session::new(new_proxy(&connection, SS_DBUS_PATH), encryption)?; Ok(SecretService { connection, session, timeout: None, }) } /// Connect to the DBus and return a new [SecretService] instance. /// /// If this service instance needs to prompt a user for permission to /// access a locked item or collection, /// it will only block for the given number of seconds, /// after which it will dismiss the prompt and cancel the operation. /// (Specifying 0 for the number of seconds will prevent the prompt /// from appearing at all: the operation will immediately be cancelled.) pub fn connect_with_max_prompt_timeout( encryption: EncryptionType, seconds: u64, ) -> Result { let mut service = Self::connect(encryption)?; service.timeout = Some(seconds); Ok(service) } /// Get the service proxy (internal) fn proxy(&self) -> Proxy<'_, &Connection> { new_proxy(&self.connection, SS_DBUS_PATH) } /// Get all collections pub fn get_all_collections(&self) -> Result, Error> { let paths = self.proxy().collections()?; let collections = paths .into_iter() .map(|path| Collection::new(self, path)) .collect(); Ok(collections) } /// 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 path = self.proxy().read_alias(alias)?; if path == Path::new("/")? { Err(Error::NoResult) } else { Ok(Collection::new(self, path)) } } /// Get default collection. /// (The collection whose alias is `default`) pub fn get_default_collection(&self) -> Result, Error> { 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, Error> { 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, Error> { let mut properties: PropMap = HashMap::new(); properties.insert( SS_COLLECTION_LABEL.to_string(), Variant(Box::new(label.to_string()) as Box), ); // create collection returning collection path and prompt path let (c_path, p_path) = self.proxy().create_collection(properties, alias)?; let created = { if c_path == Path::new("/")? { // no creation path, so prompt self.prompt_for_create(&p_path)? } else { c_path } }; Ok(Collection::new(self, created)) } /// Searches all items by attributes pub fn search_items( &self, attributes: HashMap<&str, &str>, ) -> Result>, Error> { let (unlocked, locked) = self.proxy().search_items(attributes)?; let result = SearchItemsResult { unlocked: unlocked.into_iter().map(|p| Item::new(self, p)).collect(), locked: locked.into_iter().map(|p| Item::new(self, p)).collect(), }; Ok(result) } /// Unlock all items in a batch pub fn unlock_all(&self, items: &[&Item<'_>]) -> Result<(), Error> { let paths = items.iter().map(|i| i.path.clone()).collect(); self.lock_unlock_all(LockAction::Unlock, paths) } pub(crate) fn lock_unlock_all( &self, action: LockAction, paths: Vec, ) -> Result<(), Error> { let (_, p_path) = match action { LockAction::Lock => self.proxy().lock(paths)?, LockAction::Unlock => self.proxy().unlock(paths)?, }; if p_path == Path::new("/")? { Ok(()) } else { self.prompt_for_lock_unlock_delete(&p_path) } } } #[cfg(test)] mod test { use super::*; #[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_definitely_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)] // can't run headless - prompts fn should_create_and_delete_collection() { let ss = SecretService::connect(EncryptionType::Plain).unwrap(); let test_collection = ss.create_collection("TestCreateDelete", "").unwrap(); assert!(test_collection .path .starts_with("/org/freedesktop/secrets/collection/Test")); test_collection.delete().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_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.path, search_item.unlocked[0].path); assert_eq!(search_item.locked.len(), 0); item.delete().unwrap(); } #[test_with::no_env(GITHUB_ACTIONS)] // can't run headless - prompts fn should_lock_and_unlock() { // Assumes that there will always be at least one collection let ss = SecretService::connect(EncryptionType::Plain).unwrap(); let collections = ss.get_all_collections().unwrap(); assert!(!collections.is_empty(), "no collections found"); let paths: Vec = collections.iter().map(|c| c.path.clone()).collect(); ss.lock_unlock_all(LockAction::Lock, paths.clone()).unwrap(); ss.lock_unlock_all(LockAction::Unlock, paths).unwrap(); } } dbus-secret-service-4.0.2/src/prompt.rs000064400000000000000000000062511046102023000161670ustar 00000000000000// Copyright 2016-2024 dbus-secret-service Contributors // // 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::sync::mpsc::{channel, Receiver, Sender, TryRecvError}; use std::time::Duration; use dbus::arg::RefArg; use dbus::{arg::cast, blocking::Connection, strings::Path, Message}; use crate::proxy::prompt::{Prompt, PromptCompleted}; use crate::{Error, SecretService}; const SYSTEM_WINDOW: &str = ""; const ONE_YEAR_SECONDS: u64 = 365 * 24 * 60 * 60; impl SecretService { pub(crate) fn prompt_for_create(&self, path: &Path) -> Result, Error> { self.execute_prompt(path, handle_prompt_for_create) } pub(crate) fn prompt_for_lock_unlock_delete(&self, path: &Path) -> Result<(), Error> { self.execute_prompt(path, handle_prompt_for_lock_unlock_delete) } fn execute_prompt( &self, path: &Path, handler: fn(PromptCompleted) -> Result, ) -> Result { // set up handler #[allow(clippy::type_complexity)] let (tx, rx): (Sender>, Receiver>) = channel(); let internal_handler = move |signal: PromptCompleted, _: &Connection, _: &Message| { tx.send(handler(signal)).unwrap(); false }; // execute handler let timeout = self.timeout.unwrap_or(ONE_YEAR_SECONDS); if timeout == 0 { return Err(Error::Prompt); } let one_second = Duration::from_millis(1000); let proxy = super::new_proxy(&self.connection, path); let token = proxy.match_signal(internal_handler)?; proxy.prompt(SYSTEM_WINDOW)?; let mut result = Err(Error::Prompt); for _ in 0..timeout { match self.connection.process(one_second) { Ok(false) => continue, Ok(true) => match rx.try_recv() { Ok(res) => { result = res; break; } Err(TryRecvError::Empty) => continue, Err(TryRecvError::Disconnected) => break, }, _ => break, } } proxy.match_stop(token, true)?; result } } fn handle_prompt_for_create(signal: PromptCompleted) -> Result, Error> { if signal.dismissed { Err(Error::Prompt) } else if let Some(first) = signal.result.as_static_inner(0) { if let Some(path) = cast::>(first) { Ok(path.clone().into_static()) } else { println!("Cast to path failed: {first:?}"); Err(Error::Parse) } } else { println!("Can't understand prompt result: {:?}", signal.result); Err(Error::Parse) } } fn handle_prompt_for_lock_unlock_delete(signal: PromptCompleted) -> Result<(), Error> { if signal.dismissed { Err(Error::Prompt) } else { Ok(()) } } dbus-secret-service-4.0.2/src/proxy/collection.rs000064400000000000000000000135621046102023000201650ustar 00000000000000// Copyright 2016-2024 dbus-secret-service Contributors // // 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. // This code generated by dbus-codegen-rust from xml generated by // using dbus-send to introspect a collection object path. // Generation command without --file argument: // `dbus-codegen-rust -i org.freedesktop.Secret. --interfaces=org.freedesktop.Secret.Collection -c blocking` // See https://github.com/diwic/dbus-rs use dbus; #[allow(unused_imports)] use dbus::arg; use dbus::blocking; #[allow(dead_code)] pub trait Collection { fn delete(&self) -> Result, dbus::Error>; fn search_items( &self, attributes: ::std::collections::HashMap<&str, &str>, ) -> Result>, dbus::Error>; fn create_item( &self, properties: arg::PropMap, secret: (dbus::Path, Vec, Vec, &str), replace: bool, ) -> Result<(dbus::Path<'static>, dbus::Path<'static>), dbus::Error>; fn items(&self) -> Result>, dbus::Error>; fn label(&self) -> Result; fn set_label(&self, value: String) -> Result<(), dbus::Error>; fn locked(&self) -> Result; fn created(&self) -> Result; fn modified(&self) -> Result; } #[derive(Debug)] pub struct CollectionItemCreated { pub item: dbus::Path<'static>, } impl arg::AppendAll for CollectionItemCreated { fn append(&self, i: &mut arg::IterAppend) { arg::RefArg::append(&self.item, i); } } impl arg::ReadAll for CollectionItemCreated { fn read(i: &mut arg::Iter) -> Result { Ok(CollectionItemCreated { item: i.read()? }) } } impl dbus::message::SignalArgs for CollectionItemCreated { const NAME: &'static str = "ItemCreated"; const INTERFACE: &'static str = "org.freedesktop.Secret.Collection"; } #[derive(Debug)] pub struct CollectionItemDeleted { pub item: dbus::Path<'static>, } impl arg::AppendAll for CollectionItemDeleted { fn append(&self, i: &mut arg::IterAppend) { arg::RefArg::append(&self.item, i); } } impl arg::ReadAll for CollectionItemDeleted { fn read(i: &mut arg::Iter) -> Result { Ok(CollectionItemDeleted { item: i.read()? }) } } impl dbus::message::SignalArgs for CollectionItemDeleted { const NAME: &'static str = "ItemDeleted"; const INTERFACE: &'static str = "org.freedesktop.Secret.Collection"; } #[derive(Debug)] pub struct CollectionItemChanged { pub item: dbus::Path<'static>, } impl arg::AppendAll for CollectionItemChanged { fn append(&self, i: &mut arg::IterAppend) { arg::RefArg::append(&self.item, i); } } impl arg::ReadAll for CollectionItemChanged { fn read(i: &mut arg::Iter) -> Result { Ok(CollectionItemChanged { item: i.read()? }) } } impl dbus::message::SignalArgs for CollectionItemChanged { const NAME: &'static str = "ItemChanged"; const INTERFACE: &'static str = "org.freedesktop.Secret.Collection"; } impl<'a, T: blocking::BlockingSender, C: ::std::ops::Deref> Collection for blocking::Proxy<'a, C> { fn delete(&self) -> Result, dbus::Error> { #![allow(clippy::bind_instead_of_map)] self.method_call("org.freedesktop.Secret.Collection", "Delete", ()) .and_then(|r: (dbus::Path<'static>,)| Ok(r.0)) } fn search_items( &self, attributes: ::std::collections::HashMap<&str, &str>, ) -> Result>, dbus::Error> { #![allow(clippy::bind_instead_of_map)] self.method_call( "org.freedesktop.Secret.Collection", "SearchItems", (attributes,), ) .and_then(|r: (Vec>,)| Ok(r.0)) } fn create_item( &self, properties: arg::PropMap, secret: (dbus::Path, Vec, Vec, &str), replace: bool, ) -> Result<(dbus::Path<'static>, dbus::Path<'static>), dbus::Error> { self.method_call( "org.freedesktop.Secret.Collection", "CreateItem", (properties, secret, replace), ) } fn items(&self) -> Result>, dbus::Error> { ::get( self, "org.freedesktop.Secret.Collection", "Items", ) } fn label(&self) -> Result { ::get( self, "org.freedesktop.Secret.Collection", "Label", ) } fn locked(&self) -> Result { ::get( self, "org.freedesktop.Secret.Collection", "Locked", ) } fn created(&self) -> Result { ::get( self, "org.freedesktop.Secret.Collection", "Created", ) } fn modified(&self) -> Result { ::get( self, "org.freedesktop.Secret.Collection", "Modified", ) } fn set_label(&self, value: String) -> Result<(), dbus::Error> { ::set( self, "org.freedesktop.Secret.Collection", "Label", value, ) } } dbus-secret-service-4.0.2/src/proxy/item.rs000064400000000000000000000114261046102023000167650ustar 00000000000000// Copyright 2016-2024 dbus-secret-service Contributors // // 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. // This code generated by dbus-codegen-rust from xml generated by // using dbus-send to introspect an item object path. // Generation command without `--file` argument: // `dbus-codegen-rust -i org.freedesktop.Secret. --interfaces=org.freedesktop.Secret.Item -c blocking` // See https://github.com/diwic/dbus-rs use dbus; #[allow(unused_imports)] use dbus::arg; use dbus::blocking; #[allow(dead_code)] pub trait Item { fn delete(&self) -> Result, dbus::Error>; fn get_secret( &self, session: dbus::Path, ) -> Result<(dbus::Path<'static>, Vec, Vec, String), dbus::Error>; fn set_secret(&self, secret: (dbus::Path, Vec, Vec, &str)) -> Result<(), dbus::Error>; fn locked(&self) -> Result; fn attributes(&self) -> Result<::std::collections::HashMap, dbus::Error>; fn set_attributes( &self, value: ::std::collections::HashMap, ) -> Result<(), dbus::Error>; fn label(&self) -> Result; fn set_label(&self, value: String) -> Result<(), dbus::Error>; fn type_(&self) -> Result; fn set_type(&self, value: String) -> Result<(), dbus::Error>; fn created(&self) -> Result; fn modified(&self) -> Result; } impl<'a, T: blocking::BlockingSender, C: ::std::ops::Deref> Item for blocking::Proxy<'a, C> { fn delete(&self) -> Result, dbus::Error> { #![allow(clippy::bind_instead_of_map)] self.method_call("org.freedesktop.Secret.Item", "Delete", ()) .and_then(|r: (dbus::Path<'static>,)| Ok(r.0)) } fn get_secret( &self, session: dbus::Path, ) -> Result<(dbus::Path<'static>, Vec, Vec, String), dbus::Error> { #![allow(clippy::bind_instead_of_map)] self.method_call("org.freedesktop.Secret.Item", "GetSecret", (session,)) .and_then(|r: ((dbus::Path<'static>, Vec, Vec, String),)| Ok(r.0)) } fn set_secret(&self, secret: (dbus::Path, Vec, Vec, &str)) -> Result<(), dbus::Error> { self.method_call("org.freedesktop.Secret.Item", "SetSecret", (secret,)) } fn locked(&self) -> Result { ::get( self, "org.freedesktop.Secret.Item", "Locked", ) } fn attributes(&self) -> Result<::std::collections::HashMap, dbus::Error> { ::get( self, "org.freedesktop.Secret.Item", "Attributes", ) } fn label(&self) -> Result { ::get( self, "org.freedesktop.Secret.Item", "Label", ) } fn type_(&self) -> Result { ::get( self, "org.freedesktop.Secret.Item", "Type", ) } fn created(&self) -> Result { ::get( self, "org.freedesktop.Secret.Item", "Created", ) } fn modified(&self) -> Result { ::get( self, "org.freedesktop.Secret.Item", "Modified", ) } fn set_attributes( &self, value: ::std::collections::HashMap, ) -> Result<(), dbus::Error> { ::set( self, "org.freedesktop.Secret.Item", "Attributes", value, ) } fn set_label(&self, value: String) -> Result<(), dbus::Error> { ::set( self, "org.freedesktop.Secret.Item", "Label", value, ) } fn set_type(&self, value: String) -> Result<(), dbus::Error> { ::set( self, "org.freedesktop.Secret.Item", "Type", value, ) } } dbus-secret-service-4.0.2/src/proxy/mod.rs000064400000000000000000000012441046102023000166030ustar 00000000000000// Copyright 2016-2024 dbus-secret-service Contributors // // 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 dbus::blocking::{Connection, Proxy}; use std::time::Duration; pub fn new_proxy<'a, 'b>(connection: &'b Connection, path: &'a str) -> Proxy<'a, &'b Connection> { connection.with_proxy(crate::ss::SS_DBUS_DEST, path, Duration::from_millis(2000)) } dbus-secret-service-4.0.2/src/proxy/prompt.rs000064400000000000000000000037461046102023000173560ustar 00000000000000// Copyright 2016-2024 dbus-secret-service Contributors // // 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. // This code generated by dbus-codegen-rust from xml generated by // using dbus-send to introspect a prompt object path. // Generation command without --file argument: // `dbus-codegen-rust -i org.freedesktop.Secret. --interfaces=org.freedesktop.Secret.Prompt -c blocking` // See https://github.com/diwic/dbus-rs use dbus; #[allow(unused_imports)] use dbus::arg; use dbus::blocking; #[allow(dead_code)] pub trait Prompt { fn prompt(&self, window_id: &str) -> Result<(), dbus::Error>; fn dismiss(&self) -> Result<(), dbus::Error>; } #[derive(Debug)] pub struct PromptCompleted { pub dismissed: bool, pub result: arg::Variant>, } impl arg::AppendAll for PromptCompleted { fn append(&self, i: &mut arg::IterAppend) { arg::RefArg::append(&self.dismissed, i); arg::RefArg::append(&self.result, i); } } impl arg::ReadAll for PromptCompleted { fn read(i: &mut arg::Iter) -> Result { Ok(PromptCompleted { dismissed: i.read()?, result: i.read()?, }) } } impl dbus::message::SignalArgs for PromptCompleted { const NAME: &'static str = "Completed"; const INTERFACE: &'static str = "org.freedesktop.Secret.Prompt"; } impl<'a, T: blocking::BlockingSender, C: ::std::ops::Deref> Prompt for blocking::Proxy<'a, C> { fn prompt(&self, window_id: &str) -> Result<(), dbus::Error> { self.method_call("org.freedesktop.Secret.Prompt", "Prompt", (window_id,)) } fn dismiss(&self) -> Result<(), dbus::Error> { self.method_call("org.freedesktop.Secret.Prompt", "Dismiss", ()) } } dbus-secret-service-4.0.2/src/proxy/service.rs000064400000000000000000000175661046102023000175020ustar 00000000000000// Copyright 2016-2024 dbus-secret-service Contributors // // 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. // This code generated by dbus-codegen-rust from xml generated by // using dbus-send to introspect the /org/freedesktop/secrets object path. // Generation command without --file argument: // `dbus-codegen-rust -i org.freedesktop.Secret. --interfaces=org.freedesktop.Secret.Service -c blocking` // See https://github.com/diwic/dbus-rs use dbus; #[allow(unused_imports)] use dbus::arg; use dbus::blocking; #[allow(dead_code)] pub trait Service { fn open_session( &self, algorithm: &str, input: arg::Variant>, ) -> Result< ( arg::Variant>, dbus::Path<'static>, ), dbus::Error, >; fn create_collection( &self, properties: arg::PropMap, alias: &str, ) -> Result<(dbus::Path<'static>, dbus::Path<'static>), dbus::Error>; fn search_items( &self, attributes: ::std::collections::HashMap<&str, &str>, ) -> Result<(Vec>, Vec>), dbus::Error>; fn unlock( &self, objects: Vec, ) -> Result<(Vec>, dbus::Path<'static>), dbus::Error>; fn lock( &self, objects: Vec, ) -> Result<(Vec>, dbus::Path<'static>), dbus::Error>; fn lock_service(&self) -> Result<(), dbus::Error>; fn change_lock(&self, collection: dbus::Path) -> Result, dbus::Error>; #[allow(clippy::type_complexity)] fn get_secrets( &self, items: Vec, session: dbus::Path, ) -> Result< ::std::collections::HashMap< dbus::Path<'static>, (dbus::Path<'static>, Vec, Vec, String), >, dbus::Error, >; fn read_alias(&self, name: &str) -> Result, dbus::Error>; fn set_alias(&self, name: &str, collection: dbus::Path) -> Result<(), dbus::Error>; fn collections(&self) -> Result>, dbus::Error>; } #[derive(Debug)] pub struct ServiceCollectionCreated { pub collection: dbus::Path<'static>, } impl arg::AppendAll for ServiceCollectionCreated { fn append(&self, i: &mut arg::IterAppend) { arg::RefArg::append(&self.collection, i); } } impl arg::ReadAll for ServiceCollectionCreated { fn read(i: &mut arg::Iter) -> Result { Ok(ServiceCollectionCreated { collection: i.read()?, }) } } impl dbus::message::SignalArgs for ServiceCollectionCreated { const NAME: &'static str = "CollectionCreated"; const INTERFACE: &'static str = "org.freedesktop.Secret.Service"; } #[derive(Debug)] pub struct ServiceCollectionDeleted { pub collection: dbus::Path<'static>, } impl arg::AppendAll for ServiceCollectionDeleted { fn append(&self, i: &mut arg::IterAppend) { arg::RefArg::append(&self.collection, i); } } impl arg::ReadAll for ServiceCollectionDeleted { fn read(i: &mut arg::Iter) -> Result { Ok(ServiceCollectionDeleted { collection: i.read()?, }) } } impl dbus::message::SignalArgs for ServiceCollectionDeleted { const NAME: &'static str = "CollectionDeleted"; const INTERFACE: &'static str = "org.freedesktop.Secret.Service"; } #[derive(Debug)] pub struct ServiceCollectionChanged { pub collection: dbus::Path<'static>, } impl arg::AppendAll for ServiceCollectionChanged { fn append(&self, i: &mut arg::IterAppend) { arg::RefArg::append(&self.collection, i); } } impl arg::ReadAll for ServiceCollectionChanged { fn read(i: &mut arg::Iter) -> Result { Ok(ServiceCollectionChanged { collection: i.read()?, }) } } impl dbus::message::SignalArgs for ServiceCollectionChanged { const NAME: &'static str = "CollectionChanged"; const INTERFACE: &'static str = "org.freedesktop.Secret.Service"; } impl<'a, T: blocking::BlockingSender, C: ::std::ops::Deref> Service for blocking::Proxy<'a, C> { fn open_session( &self, algorithm: &str, input: arg::Variant>, ) -> Result< ( arg::Variant>, dbus::Path<'static>, ), dbus::Error, > { self.method_call( "org.freedesktop.Secret.Service", "OpenSession", (algorithm, input), ) } fn create_collection( &self, properties: arg::PropMap, alias: &str, ) -> Result<(dbus::Path<'static>, dbus::Path<'static>), dbus::Error> { self.method_call( "org.freedesktop.Secret.Service", "CreateCollection", (properties, alias), ) } fn search_items( &self, attributes: ::std::collections::HashMap<&str, &str>, ) -> Result<(Vec>, Vec>), dbus::Error> { self.method_call( "org.freedesktop.Secret.Service", "SearchItems", (attributes,), ) } fn unlock( &self, objects: Vec, ) -> Result<(Vec>, dbus::Path<'static>), dbus::Error> { self.method_call("org.freedesktop.Secret.Service", "Unlock", (objects,)) } fn lock( &self, objects: Vec, ) -> Result<(Vec>, dbus::Path<'static>), dbus::Error> { self.method_call("org.freedesktop.Secret.Service", "Lock", (objects,)) } fn lock_service(&self) -> Result<(), dbus::Error> { self.method_call("org.freedesktop.Secret.Service", "LockService", ()) } fn change_lock(&self, collection: dbus::Path) -> Result, dbus::Error> { #![allow(clippy::bind_instead_of_map)] self.method_call( "org.freedesktop.Secret.Service", "ChangeLock", (collection,), ) .and_then(|r: (dbus::Path<'static>,)| Ok(r.0)) } #[allow(clippy::type_complexity)] fn get_secrets( &self, items: Vec, session: dbus::Path, ) -> Result< ::std::collections::HashMap< dbus::Path<'static>, (dbus::Path<'static>, Vec, Vec, String), >, dbus::Error, > { #![allow(clippy::bind_instead_of_map)] self.method_call( "org.freedesktop.Secret.Service", "GetSecrets", (items, session), ) .and_then( |r: ( ::std::collections::HashMap< dbus::Path<'static>, (dbus::Path<'static>, Vec, Vec, String), >, )| Ok(r.0), ) } fn read_alias(&self, name: &str) -> Result, dbus::Error> { #![allow(clippy::bind_instead_of_map)] self.method_call("org.freedesktop.Secret.Service", "ReadAlias", (name,)) .and_then(|r: (dbus::Path<'static>,)| Ok(r.0)) } fn set_alias(&self, name: &str, collection: dbus::Path) -> Result<(), dbus::Error> { self.method_call( "org.freedesktop.Secret.Service", "SetAlias", (name, collection), ) } fn collections(&self) -> Result>, dbus::Error> { ::get( self, "org.freedesktop.Secret.Service", "Collections", ) } } dbus-secret-service-4.0.2/src/session.rs000064400000000000000000000357251046102023000163410ustar 00000000000000// Copyright 2016-2024 dbus-secret-service Contributors // // 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. In session negotiation, exchange my public key for server's public key. // 4. Use server public key and my private key to derive a shared AES key using HKDF. // 5. Format Secret: aes iv is random seed, in secret struct it's the parameter (Array(Byte)) // 6. Format Secret: encode the secret value for the value field in secret struct. // This encoding uses the aes_key from the associated Session. use dbus::{ arg::{RefArg, Variant}, blocking::{Connection, Proxy}, Path, }; use crate::Error; #[cfg(all(feature = "crypto-rust", feature = "crypto-openssl"))] compile_error!("You cannot specify both feature \"crypto-rust\" and feature \"crypto-openssl\""); /// The algorithms that can be used for encryption-in-transit. /// /// If you are writing an ultra-secure program that accesses the secret service, /// and you want to be sure that your secrets are encrypted while being sent to /// or retrieved from the service, you can specify either the "crypto-rust" or /// the "crypto-openssl" feature to this crate and tell it to use Diffie-Hellman /// shared key encryption when passing secrets. If you don't specify one of those /// features, then your only choice is to use no encryption. #[derive(Debug, Eq, PartialEq)] pub enum EncryptionType { /// Use no encryption when sending/receiving secrets Plain, #[cfg(any(feature = "crypto-rust", feature = "crypto-openssl"))] /// Use Diffie-Hellman shared key encryption when sending/receiving secrets Dh, } pub(crate) struct EncryptedSecret { path: Path<'static>, // the session path salt: Vec, // the salt for the encrypted data data: Vec, // the encrypted data pub(crate) mime: String, // the mime type of the decrypted data } impl EncryptedSecret { pub(crate) fn from_dbus(value: (Path<'static>, Vec, Vec, String)) -> Self { Self { path: value.0, salt: value.1, data: value.2, mime: value.3.to_string(), } } pub(crate) fn to_dbus(&self) -> (Path<'static>, Vec, Vec, &str) { ( self.path.clone(), self.salt.clone(), self.data.clone(), &self.mime, ) } } pub struct Session { pub(crate) path: Path<'static>, encryption: EncryptionType, #[cfg(any(feature = "crypto-rust", feature = "crypto-openssl"))] shared_key: Option, } impl std::fmt::Debug for Session { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Session") .field("path", &self.path) .field( "secrets", if self.is_encrypted() { &"(Hidden)" } else { &"None" }, ) .finish() } } impl Session { pub fn new(p: Proxy<'_, &'_ Connection>, encryption: EncryptionType) -> Result { use crate::proxy::service::Service; match encryption { EncryptionType::Plain => { use crate::ss::ALGORITHM_PLAIN; // in rust 1.70, this lint applies here even though it shouldn't // because we need an explicit string to interpret as a RefArg #[allow(clippy::box_default)] let bytes_arg = Box::new(String::new()) as Box; let (_, path) = p.open_session(ALGORITHM_PLAIN, Variant(bytes_arg))?; Ok(Session { path, encryption, #[cfg(any(feature = "crypto-rust", feature = "crypto-openssl"))] shared_key: None, }) } #[cfg(any(feature = "crypto-rust", feature = "crypto-openssl"))] EncryptionType::Dh => { use crate::ss::ALGORITHM_DH; use dbus::arg::cast; // crypto: create private and public key let keypair = crypto::Keypair::generate(); // send our public key with algorithm to service let public_bytes = keypair.public.to_bytes_be(); let bytes_arg = Variant(Box::new(public_bytes) as Box); let (out, path) = p.open_session(ALGORITHM_DH, bytes_arg)?; // get service public key back and create shared key from it if let Some(server_public_key_bytes) = cast::>(&out.0) { let shared_key = keypair.derive_shared(server_public_key_bytes); Ok(Session { path, encryption, #[cfg(any(feature = "crypto-rust", feature = "crypto-openssl"))] shared_key: Some(shared_key), }) } else { Err(Error::Parse) } } } } pub fn is_encrypted(&self) -> bool { match self.encryption { EncryptionType::Plain => false, #[cfg(any(feature = "crypto-rust", feature = "crypto-openssl"))] EncryptionType::Dh => true, } } pub(crate) fn encrypt_secret(&self, data: &[u8], mime: &str) -> EncryptedSecret { match self.encryption { EncryptionType::Plain => EncryptedSecret { path: self.path.clone(), salt: vec![], data: data.to_vec(), mime: mime.to_string(), }, #[cfg(any(feature = "crypto-rust", feature = "crypto-openssl"))] EncryptionType::Dh => { // encrypt the secret with the data let (encrypted, salt) = crypto::encrypt(data, &self.shared_key.unwrap()); EncryptedSecret { path: self.path.clone(), salt, data: encrypted, mime: mime.to_string(), } } } } pub(crate) fn decrypt_secret(&self, secret: EncryptedSecret) -> Result, Error> { match self.encryption { EncryptionType::Plain => Ok(secret.data), #[cfg(any(feature = "crypto-rust", feature = "crypto-openssl"))] EncryptionType::Dh => { let clear = crypto::decrypt(&secret.data, &self.shared_key.unwrap(), &secret.salt)?; Ok(clear) } } } } #[cfg(any(feature = "crypto-rust", feature = "crypto-openssl"))] mod crypto { use std::ops::{Mul, Rem, Shr}; use num::{ bigint::BigUint, integer::Integer, traits::{One, Zero}, FromPrimitive, }; use once_cell::sync::Lazy; use rand::{rngs::OsRng, Rng}; #[cfg(feature = "crypto-rust")] pub(super) fn encrypt(data: &[u8], key: &AesKey) -> (Vec, Vec) { use aes::cipher::block_padding::Pkcs7; use aes::cipher::generic_array::GenericArray; use aes::cipher::{BlockEncryptMut, KeyIvInit}; type Aes128CbcEnc = cbc::Encryptor; // create the salt for the encryption let mut aes_iv = [0; 16]; OsRng.fill(&mut aes_iv); // convert key and salt to input parameter form let key = GenericArray::from_slice(key); let iv = GenericArray::from_slice(&aes_iv); // return encrypted data and salt ( Aes128CbcEnc::new(key, iv).encrypt_padded_vec_mut::(data), aes_iv.to_vec(), ) } #[cfg(feature = "crypto-rust")] pub(super) fn decrypt( encrypted_data: &[u8], key: &AesKey, iv: &[u8], ) -> Result, crate::Error> { use aes::cipher::block_padding::Pkcs7; use aes::cipher::generic_array::GenericArray; use aes::cipher::{BlockDecryptMut, KeyIvInit}; type Aes128CbcDec = cbc::Decryptor; let key = GenericArray::from_slice(key); let iv = GenericArray::from_slice(iv); let output = Aes128CbcDec::new(key, iv).decrypt_padded_vec_mut::(encrypted_data)?; Ok(output) } #[cfg(all(feature = "crypto-openssl", not(feature = "crypto-rust")))] pub(super) fn encrypt(data: &[u8], key: &AesKey) -> (Vec, Vec) { use openssl::cipher::Cipher; use openssl::cipher_ctx::CipherCtx; // create the salt for the encryption let mut aes_iv = [0u8; 16]; OsRng.fill(&mut aes_iv); let mut ctx = CipherCtx::new().expect("cipher creation should not fail"); ctx.encrypt_init(Some(Cipher::aes_128_cbc()), Some(key), Some(&aes_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, aes_iv.to_vec()) } #[cfg(all(feature = "crypto-openssl", not(feature = "crypto-rust")))] pub(super) fn decrypt( encrypted_data: &[u8], key: &AesKey, iv: &[u8], ) -> Result, crate::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)?; ctx.cipher_final_vec(&mut output)?; Ok(output) } // 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, ]) }); pub(super) type AesKey = [u8; 16]; #[derive(Clone)] pub(super) struct Keypair { pub(super) private: BigUint, pub(super) public: BigUint, } impl Keypair { pub(super) 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 = pow_base_exp_mod(&DH_GENERATOR, &private_key, &DH_PRIME); Self { private: private_key, public: public_key, } } pub(super) fn derive_shared(&self, server_public_key_bytes: &[u8]) -> AesKey { // Derive the shared secret the server and us. let server_public_key = BigUint::from_bytes_be(server_public_key_bytes); let common_secret = pow_base_exp_mod(&server_public_key, &self.private, &DH_PRIME); let common_secret_bytes = common_secret.to_bytes_be(); let mut common_secret_padded = vec![0; 128 - common_secret_bytes.len()]; common_secret_padded.extend(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); okm } } #[cfg(all(feature = "crypto-openssl", not(feature = "crypto-rust")))] pub(super) 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")] pub(super) fn hkdf(ikm: Vec, salt: Option<&[u8]>, okm: &mut [u8]) { use sha2::Sha256; let info = []; let (_, hk) = hkdf::Hkdf::::extract(salt, &ikm); hk.expand(&info, okm) .expect("hkdf expand should never fail"); } /// from https://github.com/plietar/librespot/blob/master/core/src/util/mod.rs#L53 pub(super) fn pow_base_exp_mod(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(test)] mod test { use dbus::blocking::Connection; use crate::proxy::new_proxy; use crate::ss::SS_DBUS_PATH; use super::*; #[test] fn should_create_plain_session() { let connection = Connection::new_session().unwrap(); let proxy = new_proxy(&connection, SS_DBUS_PATH); let session = Session::new(proxy, EncryptionType::Plain).unwrap(); assert!(!session.is_encrypted()); } #[cfg(any(feature = "crypto-rust", feature = "crypto-openssl"))] #[test] fn should_create_encrypted_session() { let connection = Connection::new_session().unwrap(); let proxy = new_proxy(&connection, SS_DBUS_PATH); let session = Session::new(proxy, EncryptionType::Dh).unwrap(); assert!(session.is_encrypted()); } } dbus-secret-service-4.0.2/src/ss.rs000064400000000000000000000017551046102023000152770ustar 00000000000000// Copyright 2016-2024 dbus-secret-service Contributors // // 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_DEST: &str = "org.freedesktop.secrets"; pub const SS_DBUS_PATH: &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"; #[cfg(any(feature = "crypto-rust", feature = "crypto-openssl"))] pub const ALGORITHM_DH: &str = "dh-ietf1024-sha256-aes128-cbc-pkcs7"; // Collection properties pub const SS_COLLECTION_LABEL: &str = "org.freedesktop.Secret.Collection.Label";