From 04bb0047fde369b09ce74b2d6f4ead9c89453a90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20DOUIN?= Date: Wed, 18 Dec 2024 11:45:54 +0100 Subject: [PATCH] add Keyring std enum for convenience --- .github/workflows/examples.yml | 106 ++--- Cargo.toml | 32 +- examples/apple-native-std.rs | 61 --- examples/secret-service-dbus-openssl-std.rs | 80 ---- .../secret-service-dbus-rust-crypto-std.rs | 80 ---- examples/secret-service-zbus-openssl-std.rs | 81 ---- .../secret-service-zbus-rust-crypto-std.rs | 81 ---- examples/std.rs | 77 +++ examples/windows-native-std.rs | 61 --- src/apple/mod.rs | 2 +- src/apple/std.rs | 10 +- src/flow.rs | 1 + src/lib.rs | 8 +- src/secret_service/crypto/mod.rs | 22 + src/std.rs | 448 ++++++++++++++++++ src/windows/mod.rs | 2 +- 16 files changed, 605 insertions(+), 547 deletions(-) delete mode 100644 examples/apple-native-std.rs delete mode 100644 examples/secret-service-dbus-openssl-std.rs delete mode 100644 examples/secret-service-dbus-rust-crypto-std.rs delete mode 100644 examples/secret-service-zbus-openssl-std.rs delete mode 100644 examples/secret-service-zbus-rust-crypto-std.rs create mode 100644 examples/std.rs delete mode 100644 examples/windows-native-std.rs create mode 100644 src/std.rs diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index b655bbe..3842adc 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -4,84 +4,48 @@ on: push: jobs: - examples: - runs-on: ${{ matrix.os }} + secret-service-std: + runs-on: ubuntu-24.04 strategy: fail-fast: false matrix: - include: - # Secret Service + D-Bus + std (blocking) - - - os: ubuntu-24.04 - features: secret-service-dbus-std,secret-service-openssl-std - example: secret-service-dbus-openssl-std - - os: ubuntu-24.04 - features: secret-service-dbus-std,secret-service-rust-crypto-std - example: secret-service-dbus-rust-crypto-std - - # Secret Service + D-Bus + tokio - - - os: ubuntu-24.04 - features: secret-service-dbus-tokio,secret-service-openssl-std - example: secret-service-dbus-openssl-tokio - - os: ubuntu-24.04 - features: secret-service-dbus-tokio,secret-service-rust-crypto-std - example: secret-service-dbus-rust-crypto-tokio - - # Secret Service + Z-Bus + std (blocking) - - - os: ubuntu-24.04 - features: secret-service-zbus-std,secret-service-openssl-std - example: secret-service-zbus-openssl-std - - os: ubuntu-24.04 - features: secret-service-zbus-std,secret-service-rust-crypto-std - example: secret-service-zbus-rust-crypto-std - - # Secret Service + Z-Bus + async-std - - - os: ubuntu-24.04 - features: secret-service-zbus-async-std,secret-service-openssl-std - example: secret-service-zbus-openssl-async-std - - os: ubuntu-24.04 - features: secret-service-zbus-async-std,secret-service-rust-crypto-std - example: secret-service-zbus-rust-crypto-async-std - - # Secret Service + Z-Bus + tokio - - - os: ubuntu-24.04 - features: secret-service-zbus-tokio,secret-service-openssl-std - example: secret-service-zbus-openssl-tokio - - os: ubuntu-24.04 - features: secret-service-zbus-tokio,secret-service-rust-crypto-std - example: secret-service-zbus-rust-crypto-tokio - - # MacOS/iOS Keychain - - - os: macos-13 - features: apple-native-std - example: apple-native-std - - os: macos-14 - features: apple-native-std - example: apple-native-std - - os: macos-15 - features: apple-native-std - example: apple-native-std - - os: macos-13 - features: apple-native-std - example: apple-native-std - - # Windows Credentials - - - os: windows-latest - features: windows-native-std - example: windows-native-std + keyring: [dbus, zbus] + crypto: [openssl, rust-crypto] + encryption: [plain, dh] + env: + KEY: ubuntu-24.04-ss-${{ matrix.keyring }}-${{ matrix.crypto }}-${{ matrix.encryption }}-std + KEYRING_PROVIDER: ${{ matrix.keyring }}-secret-service + SS_CRYPTO_PROVIDER: ${{ matrix.crypto }} + SS_CRYPTO_ALGORITHM: ${{ matrix.encryption }} steps: - uses: actions/checkout@v4 - uses: awalsh128/cache-apt-pkgs-action@latest - if: matrix.os == 'ubuntu-24.04' with: packages: libdbus-1-dev libssl-dev gnome-keyring - uses: actions-rust-lang/setup-rust-toolchain@v1 - run: gnome-keyring-daemon --components=secrets --daemonize --unlock <<< 'password' - if: matrix.os == 'ubuntu-24.04' - - run: cargo run --features ${{ matrix.features }} --example ${{ matrix.example }} + - run: cargo run --features secret-service-${{ matrix.keyring }}-std,secret-service-${{ matrix.crypto }}-std --example std + + apple-keychain-std: + runs-on: ${{ matrix.macos }} + strategy: + fail-fast: false + matrix: + macos: [macos-13, macos-14, macos-15] + env: + KEY: ${{ matrix.macos }}-std + KEYRING_PROVIDER: apple-keychain + steps: + - uses: actions/checkout@v4 + - uses: actions-rust-lang/setup-rust-toolchain@v1 + - run: cargo run --features apple-keychain-std --example std + + windows-credentials-std: + runs-on: windows-latest + env: + KEY: windows-latest-std + KEYRING_PROVIDER: windows-credentials + steps: + - uses: actions/checkout@v4 + - uses: actions-rust-lang/setup-rust-toolchain@v1 + - run: cargo run --features windows-credentials-std --example std diff --git a/Cargo.toml b/Cargo.toml index 5aff9bb..5820a70 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,8 +21,8 @@ name = "keyring" [features] default = [] full = [ - "apple-native-std", - "windows-native-std", + "apple-keychain-std", + "windows-credentials-std", "secret-service-dbus-std", "secret-service-dbus-tokio", @@ -61,13 +61,13 @@ secret-service-rust-crypto-std = ["dep:aes", "dep:block-padding", "dep:cbc", "de ## MacOS/iOS Keychain # -apple-native = [] -apple-native-std = ["dep:security-framework", "apple-native"] +apple-keychain = [] +apple-keychain-std = ["dep:security-framework", "apple-keychain"] ## Windows Credentials # -windows-native = [] -windows-native-std = ["dep:byteorder", "dep:windows-sys", "windows-native"] +windows-credentials = [] +windows-credentials-std = ["dep:byteorder", "dep:windows-sys", "windows-credentials"] ## Vendored # @@ -81,15 +81,10 @@ tokio = { version = "1", features = ["full"] } dbus-codegen = { version = "0.12", default-features = false, optional = true } [dependencies] -secrecy = "0.10" -serde = { version = "1", optional = true } -thiserror = "2" -tracing = "0.1" - -[target.'cfg(target_os = "linux")'.dependencies] aes = { version = "0.8", optional = true } async-std = { version = "1", optional = true } block-padding = { version = "0.3", features = ["std"], optional = true } +byteorder = { version = "1.2", optional = true } cbc = { version = "0.1", features = ["block-padding", "alloc"], optional = true } dbus = { version = "0.9", optional = true } dbus-tokio = { version = "0.7", optional = true } @@ -98,13 +93,12 @@ num = { version = "0.4", optional = true } once_cell = { version = "1", optional = true } openssl = { version = "0.10", optional = true } rand = { version = "0.8", optional = true } +secrecy = "0.10" +security-framework = { version = "3", default-features = false, optional = true } +serde = { version = "1", optional = true } sha2 = { version = "0.10", optional = true } +thiserror = "2" tokio = { version = "1", features = ["rt"], optional = true } -zbus = { version = "5", default-features = false, features = ["async-io"], optional = true } - -[target.'cfg(target_vendor = "apple")'.dependencies] -security-framework = { version = "3", default-features = false, optional = true } - -[target.'cfg(target_os = "windows")'.dependencies] -byteorder = { version = "1.2", optional = true } +tracing = "0.1" windows-sys = { version = "0.59", features = ["Win32_Foundation", "Win32_Security_Credentials"], optional = true } +zbus = { version = "5", default-features = false, features = ["async-io"], optional = true } diff --git a/examples/apple-native-std.rs b/examples/apple-native-std.rs deleted file mode 100644 index 0c5681f..0000000 --- a/examples/apple-native-std.rs +++ /dev/null @@ -1,61 +0,0 @@ -#![cfg(target_vendor = "apple")] -#![cfg(feature = "apple-native-std")] - -use std::env; - -use keyring::{ - apple::std::IoConnector as Keychain, DeleteEntryFlow, Io, ReadEntryFlow, TakeSecret, - WriteEntryFlow, -}; -use secrecy::ExposeSecret; - -fn main() { - const SECRET: &str = "apple-native-std"; - - let service = env::var("SERVICE").unwrap_or(String::from("test-service")); - println!("using service name: {service:?}"); - - let key = env::var("KEY").unwrap_or(String::from("test-key")); - println!("using entry key: {key:?}"); - - let keychain = Keychain::new(&service); - - println!("write secret {SECRET:?} to entry {service}:{key}"); - let mut flow = WriteEntryFlow::new(&key, SECRET.as_bytes().to_vec()); - while let Some(io) = flow.next() { - match io { - Io::Write => { - keychain.write(&mut flow).unwrap(); - } - _ => { - unreachable!(); - } - } - } - - let mut flow = ReadEntryFlow::new(&key); - while let Some(io) = flow.next() { - match io { - Io::Read => { - keychain.read(&mut flow).unwrap(); - } - _ => unreachable!(), - } - } - - let secret = flow.take_secret().unwrap(); - let secret = secret.expose_secret(); - let secret = String::from_utf8_lossy(&secret); - println!("read secret {secret:?} from entry {service}:{key}"); - - let mut flow = DeleteEntryFlow::new(&key); - while let Some(io) = flow.next() { - match io { - Io::Delete => { - keychain.delete(&mut flow).unwrap(); - } - _ => unreachable!(), - } - } - println!("delete secret from entry {service}:{key}"); -} diff --git a/examples/secret-service-dbus-openssl-std.rs b/examples/secret-service-dbus-openssl-std.rs deleted file mode 100644 index 25910d3..0000000 --- a/examples/secret-service-dbus-openssl-std.rs +++ /dev/null @@ -1,80 +0,0 @@ -#![cfg(target_os = "linux")] -#![cfg(feature = "secret-service-dbus-std")] -#![cfg(feature = "secret-service-openssl-std")] - -use std::env; - -use keyring::{ - secret_service::{ - self, - crypto::{self, algorithm::Algorithm, openssl::std::IoConnector as CryptoIoConnector}, - dbus::blocking::std::IoConnector as DbusIoConnector, - flow::{DeleteEntryFlow, ReadEntryFlow, WriteEntryFlow}, - }, - Io, TakeSecret, -}; -use secrecy::ExposeSecret; - -fn main() { - const SECRET: &str = "secret-service-dbus-openssl-std"; - - let service = env::var("SERVICE").unwrap_or(String::from("test-service")); - println!("using service name: {service:?}"); - - let key = env::var("KEY").unwrap_or(String::from("test-key")); - println!("using entry key: {key:?}"); - - let encryption = match env::var("ENCRYPTION") { - Ok(alg) if alg.trim().eq_ignore_ascii_case("dh") => Algorithm::Dh, - _ => Algorithm::Plain, - }; - println!("using encryption algorithm: {encryption:?}"); - - let mut dbus = DbusIoConnector::new(&service, encryption.clone()).unwrap(); - let mut crypto = CryptoIoConnector::new(dbus.session()).unwrap(); - - println!("write secret {SECRET:?} to entry {service}:{key}"); - let mut flow = WriteEntryFlow::new(&key, SECRET.as_bytes().to_vec(), encryption.clone()); - while let Some(io) = flow.next() { - match io { - secret_service::Io::Crypto(crypto::Io::Encrypt) => { - crypto.encrypt(&mut flow).unwrap(); - } - secret_service::Io::Entry(Io::Write) => { - dbus.write(&mut flow).unwrap(); - } - _ => { - unreachable!(); - } - } - } - - let mut flow = ReadEntryFlow::new(&key, encryption); - while let Some(io) = flow.next() { - match io { - secret_service::Io::Entry(Io::Read) => { - dbus.read(&mut flow).unwrap(); - } - secret_service::Io::Crypto(crypto::Io::Decrypt) => { - crypto.decrypt(&mut flow).unwrap(); - } - _ => unreachable!(), - } - } - - let secret = flow.take_secret().unwrap(); - let secret = secret.expose_secret(); - let secret = String::from_utf8_lossy(&secret); - println!("read secret {secret:?} from entry {service}:{key}"); - - let mut flow = DeleteEntryFlow::new(&key); - while let Some(io) = flow.next() { - match io { - secret_service::Io::Entry(Io::Delete) => { - dbus.delete(&mut flow).unwrap(); - } - _ => unreachable!(), - } - } - println!("delete secret from entry {service}:{key}"); -} diff --git a/examples/secret-service-dbus-rust-crypto-std.rs b/examples/secret-service-dbus-rust-crypto-std.rs deleted file mode 100644 index 898216b..0000000 --- a/examples/secret-service-dbus-rust-crypto-std.rs +++ /dev/null @@ -1,80 +0,0 @@ -#![cfg(target_os = "linux")] -#![cfg(feature = "secret-service-dbus-std")] -#![cfg(feature = "secret-service-rust-crypto-std")] - -use std::env; - -use keyring::{ - secret_service::{ - self, - crypto::{self, algorithm::Algorithm, rust_crypto::std::IoConnector as CryptoIoConnector}, - dbus::blocking::std::IoConnector as DbusIoConnector, - flow::{DeleteEntryFlow, ReadEntryFlow, WriteEntryFlow}, - }, - Io, TakeSecret, -}; -use secrecy::ExposeSecret; - -fn main() { - const SECRET: &str = "secret-service-dbus-rust-crypto-std"; - - let service = env::var("SERVICE").unwrap_or(String::from("test-service")); - println!("using service name: {service:?}"); - - let key = env::var("KEY").unwrap_or(String::from("test-key")); - println!("using entry key: {key:?}"); - - let encryption = match env::var("ENCRYPTION") { - Ok(alg) if alg.trim().eq_ignore_ascii_case("dh") => Algorithm::Dh, - _ => Algorithm::Plain, - }; - println!("using encryption algorithm: {encryption:?}"); - - let mut dbus = DbusIoConnector::new(&service, encryption.clone()).unwrap(); - let mut crypto = CryptoIoConnector::new(dbus.session()).unwrap(); - - println!("write secret {SECRET:?} to entry {service}:{key}"); - let mut flow = WriteEntryFlow::new(&key, SECRET.as_bytes().to_vec(), encryption.clone()); - while let Some(io) = flow.next() { - match io { - secret_service::Io::Crypto(crypto::Io::Encrypt) => { - crypto.encrypt(&mut flow).unwrap(); - } - secret_service::Io::Entry(Io::Write) => { - dbus.write(&mut flow).unwrap(); - } - _ => { - unreachable!(); - } - } - } - - let mut flow = ReadEntryFlow::new(&key, encryption); - while let Some(io) = flow.next() { - match io { - secret_service::Io::Entry(Io::Read) => { - dbus.read(&mut flow).unwrap(); - } - secret_service::Io::Crypto(crypto::Io::Decrypt) => { - crypto.decrypt(&mut flow).unwrap(); - } - _ => unreachable!(), - } - } - - let secret = flow.take_secret().unwrap(); - let secret = secret.expose_secret(); - let secret = String::from_utf8_lossy(&secret); - println!("read secret {secret:?} from entry {service}:{key}"); - - let mut flow = DeleteEntryFlow::new(&key); - while let Some(io) = flow.next() { - match io { - secret_service::Io::Entry(Io::Delete) => { - dbus.delete(&mut flow).unwrap(); - } - _ => unreachable!(), - } - } - println!("delete secret from entry {service}:{key}"); -} diff --git a/examples/secret-service-zbus-openssl-std.rs b/examples/secret-service-zbus-openssl-std.rs deleted file mode 100644 index 04e1be7..0000000 --- a/examples/secret-service-zbus-openssl-std.rs +++ /dev/null @@ -1,81 +0,0 @@ -#![cfg(target_os = "linux")] -#![cfg(feature = "secret-service-zbus-std")] -#![cfg(feature = "secret-service-openssl-std")] - -use std::env; - -use keyring::{ - secret_service::{ - self, - crypto::{self, algorithm::Algorithm, openssl::std::IoConnector as CryptoIoConnector}, - flow::{DeleteEntryFlow, ReadEntryFlow, WriteEntryFlow}, - zbus::std::IoConnector as ZbusIoConnector, - }, - Io, TakeSecret, -}; -use secrecy::ExposeSecret; - -#[async_std::main] -async fn main() { - const SECRET: &str = "secret-service-zbus-openssl-std"; - - let service = env::var("SERVICE").unwrap_or(String::from("test-service")); - println!("using service name: {service:?}"); - - let key = env::var("KEY").unwrap_or(String::from("test-key")); - println!("using entry key: {key:?}"); - - let encryption = match env::var("ENCRYPTION") { - Ok(alg) if alg.trim().eq_ignore_ascii_case("dh") => Algorithm::Dh, - _ => Algorithm::Plain, - }; - println!("using encryption algorithm: {encryption:?}"); - - let mut zbus = ZbusIoConnector::new(&service, encryption.clone()).unwrap(); - let mut crypto = CryptoIoConnector::new(zbus.session()).unwrap(); - - println!("write secret {SECRET:?} to entry {service}:{key}"); - let mut flow = WriteEntryFlow::new(&key, SECRET.as_bytes().to_vec(), encryption.clone()); - while let Some(io) = flow.next() { - match io { - secret_service::Io::Crypto(crypto::Io::Encrypt) => { - crypto.encrypt(&mut flow).unwrap(); - } - secret_service::Io::Entry(Io::Write) => { - zbus.write(&mut flow).unwrap(); - } - _ => { - unreachable!(); - } - } - } - - let mut flow = ReadEntryFlow::new(&key, encryption); - while let Some(io) = flow.next() { - match io { - secret_service::Io::Entry(Io::Read) => { - zbus.read(&mut flow).unwrap(); - } - secret_service::Io::Crypto(crypto::Io::Decrypt) => { - crypto.decrypt(&mut flow).unwrap(); - } - _ => unreachable!(), - } - } - - let secret = flow.take_secret().unwrap(); - let secret = secret.expose_secret(); - let secret = String::from_utf8_lossy(&secret); - println!("read secret {secret:?} from entry {service}:{key}"); - - let mut flow = DeleteEntryFlow::new(&key); - while let Some(io) = flow.next() { - match io { - secret_service::Io::Entry(Io::Delete) => { - zbus.delete(&mut flow).unwrap(); - } - _ => unreachable!(), - } - } - println!("delete secret from entry {service}:{key}"); -} diff --git a/examples/secret-service-zbus-rust-crypto-std.rs b/examples/secret-service-zbus-rust-crypto-std.rs deleted file mode 100644 index 1124013..0000000 --- a/examples/secret-service-zbus-rust-crypto-std.rs +++ /dev/null @@ -1,81 +0,0 @@ -#![cfg(target_os = "linux")] -#![cfg(feature = "secret-service-zbus-std")] -#![cfg(feature = "secret-service-rust-crypto-std")] - -use std::env; - -use keyring::{ - secret_service::{ - self, - crypto::{self, algorithm::Algorithm, rust_crypto::std::IoConnector as CryptoIoConnector}, - flow::{DeleteEntryFlow, ReadEntryFlow, WriteEntryFlow}, - zbus::std::IoConnector as ZbusIoConnector, - }, - Io, TakeSecret, -}; -use secrecy::ExposeSecret; - -#[async_std::main] -async fn main() { - const SECRET: &str = "secret-service-zbus-rust-crypto-std"; - - let service = env::var("SERVICE").unwrap_or(String::from("test-service")); - println!("using service name: {service:?}"); - - let key = env::var("KEY").unwrap_or(String::from("test-key")); - println!("using entry key: {key:?}"); - - let encryption = match env::var("ENCRYPTION") { - Ok(alg) if alg.trim().eq_ignore_ascii_case("dh") => Algorithm::Dh, - _ => Algorithm::Plain, - }; - println!("using encryption algorithm: {encryption:?}"); - - let mut zbus = ZbusIoConnector::new(&service, encryption.clone()).unwrap(); - let mut crypto = CryptoIoConnector::new(zbus.session()).unwrap(); - - println!("write secret {SECRET:?} to entry {service}:{key}"); - let mut flow = WriteEntryFlow::new(&key, SECRET.as_bytes().to_vec(), encryption.clone()); - while let Some(io) = flow.next() { - match io { - secret_service::Io::Crypto(crypto::Io::Encrypt) => { - crypto.encrypt(&mut flow).unwrap(); - } - secret_service::Io::Entry(Io::Write) => { - zbus.write(&mut flow).unwrap(); - } - _ => { - unreachable!(); - } - } - } - - let mut flow = ReadEntryFlow::new(&key, encryption); - while let Some(io) = flow.next() { - match io { - secret_service::Io::Entry(Io::Read) => { - zbus.read(&mut flow).unwrap(); - } - secret_service::Io::Crypto(crypto::Io::Decrypt) => { - crypto.decrypt(&mut flow).unwrap(); - } - _ => unreachable!(), - } - } - - let secret = flow.take_secret().unwrap(); - let secret = secret.expose_secret(); - let secret = String::from_utf8_lossy(&secret); - println!("read secret {secret:?} from entry {service}:{key}"); - - let mut flow = DeleteEntryFlow::new(&key); - while let Some(io) = flow.next() { - match io { - secret_service::Io::Entry(Io::Delete) => { - zbus.delete(&mut flow).unwrap(); - } - _ => unreachable!(), - } - } - println!("delete secret from entry {service}:{key}"); -} diff --git a/examples/std.rs b/examples/std.rs new file mode 100644 index 0000000..b41d2cb --- /dev/null +++ b/examples/std.rs @@ -0,0 +1,77 @@ +use std::env; + +#[cfg(feature = "secret-service")] +use keyring::secret_service::crypto::{Algorithm, Provider}; +use keyring::std::Keyring; +use secrecy::ExposeSecret; + +fn main() { + let service = env::var("SERVICE").unwrap_or(String::from("test-service")); + let key = env::var("KEY").unwrap_or(String::from("test-key")); + let val = env::var("VAL").unwrap_or(String::from("test-val")); + + println!("using service name: {service:?}"); + println!("using entry key: {key:?}"); + println!("using entry value: {val:?}"); + + #[cfg(feature = "secret-service")] + let ss_crypto_algorithm = match env::var("SS_CRYPTO_ALGORITHM") { + Ok(crypto) if crypto.trim().eq_ignore_ascii_case("plain") => Algorithm::Plain, + Ok(crypto) if crypto.trim().eq_ignore_ascii_case("dh") => Algorithm::Dh, + _ => Algorithm::Plain, + }; + + #[cfg(feature = "secret-service")] + let ss_crypto_provider = + match env::var("SS_CRYPTO_PROVIDER").expect("missing SS_CRYPTO_PROVIDER env var") { + #[cfg(feature = "secret-service-openssl-std")] + var if var.trim().eq_ignore_ascii_case("openssl") => { + Provider::Openssl(ss_crypto_algorithm.clone()) + } + #[cfg(feature = "secret-service-rust-crypto-std")] + var if var.trim().eq_ignore_ascii_case("rust-crypto") => { + Provider::RustCrypto(ss_crypto_algorithm.clone()) + } + _ => panic!("cannot select std secret service crypto provider"), + }; + + let mut keyring = match env::var("KEYRING_PROVIDER").expect("missing KEYRING_PROVIDER env var") + { + #[cfg(feature = "apple-keychain-std")] + var if var.trim().eq_ignore_ascii_case("apple-keychain") => { + println!("using Apple Keychain"); + Keyring::apple_keychain(&service) + } + #[cfg(feature = "windows-credentials-std")] + var if var.trim().eq_ignore_ascii_case("windows-credentials") => { + println!("using Windows Credentials"); + Keyring::windows_credentials(&service) + } + #[cfg(feature = "secret-service-dbus-std")] + var if var.trim().eq_ignore_ascii_case("dbus-secret-service") => { + println!("using Secret Service with D-Bus"); + println!("using Secret Service crypto provider: {ss_crypto_provider:?}"); + Keyring::dbus_secret_service(&service, ss_crypto_provider).unwrap() + } + #[cfg(feature = "secret-service-zbus-std")] + var if var.trim().eq_ignore_ascii_case("zbus-secret-service") => { + println!("using Secret Service with Z-Bus"); + println!("using Secret Service crypto provider: {ss_crypto_provider:?}"); + Keyring::zbus_secret_service(&service, ss_crypto_provider).unwrap() + } + _ => panic!("cannot select std keyring provider"), + }; + + keyring.write(&key, val.as_bytes().to_vec()).unwrap(); + println!("write secret {val:?} to entry {service}:{key}"); + + let secret = keyring.read(&key).unwrap(); + let secret = String::from_utf8_lossy(secret.expose_secret()); + println!("read secret {secret:?} from entry {service}:{key}"); + + keyring.delete(&key).unwrap(); + println!("delete entry {service}:{key}"); + + let err = keyring.read(&key).unwrap_err(); + println!("cannot read secret anymore: {err:?}"); +} diff --git a/examples/windows-native-std.rs b/examples/windows-native-std.rs deleted file mode 100644 index e672ba2..0000000 --- a/examples/windows-native-std.rs +++ /dev/null @@ -1,61 +0,0 @@ -#![cfg(target_os = "windows")] -#![cfg(feature = "windows-native-std")] - -use std::env; - -use keyring::{ - windows::std::IoConnector as Credentials, DeleteEntryFlow, Io, ReadEntryFlow, TakeSecret, - WriteEntryFlow, -}; -use secrecy::ExposeSecret; - -fn main() { - const SECRET: &str = "windows-native-std"; - - let service = env::var("SERVICE").unwrap_or(String::from("test-service")); - println!("using service name: {service:?}"); - - let key = env::var("KEY").unwrap_or(String::from("test-key")); - println!("using entry key: {key:?}"); - - let credentials = Credentials::new(&service); - - println!("write secret {SECRET:?} to entry {service}:{key}"); - let mut flow = WriteEntryFlow::new(&key, SECRET.as_bytes().to_vec()); - while let Some(io) = flow.next() { - match io { - Io::Write => { - credentials.write(&mut flow).unwrap(); - } - _ => { - unreachable!(); - } - } - } - - let mut flow = ReadEntryFlow::new(&key); - while let Some(io) = flow.next() { - match io { - Io::Read => { - credentials.read(&mut flow).unwrap(); - } - _ => unreachable!(), - } - } - - let secret = flow.take_secret().unwrap(); - let secret = secret.expose_secret(); - let secret = String::from_utf8_lossy(&secret); - println!("read secret {secret:?} from entry {service}:{key}"); - - let mut flow = DeleteEntryFlow::new(&key); - while let Some(io) = flow.next() { - match io { - Io::Delete => { - credentials.delete(&mut flow).unwrap(); - } - _ => unreachable!(), - } - } - println!("delete secret from entry {service}:{key}"); -} diff --git a/src/apple/mod.rs b/src/apple/mod.rs index 94222af..5d687f8 100644 --- a/src/apple/mod.rs +++ b/src/apple/mod.rs @@ -1,2 +1,2 @@ -#[cfg(feature = "apple-native-std")] +#[cfg(feature = "apple-keychain-std")] pub mod std; diff --git a/src/apple/std.rs b/src/apple/std.rs index a64f66f..ba1c538 100644 --- a/src/apple/std.rs +++ b/src/apple/std.rs @@ -34,23 +34,21 @@ impl IoConnector { } pub fn read(&self, flow: &mut impl PutSecret) -> Result<()> { - let key = flow.key(); - let secret = get_generic_password(&self.service, key).map_err(Error::ReadSecretError)?; + let secret = + get_generic_password(&self.service, flow.key()).map_err(Error::ReadSecretError)?; flow.put_secret(secret.into()); Ok(()) } pub fn write(&self, flow: &mut impl TakeSecret) -> Result<()> { let secret = flow.take_secret().ok_or(Error::WriteUndefinedSecretError)?; - let key = flow.key(); let secret = secret.expose_secret(); - set_generic_password(&self.service, key, secret).map_err(Error::WriteSecretError)?; + set_generic_password(&self.service, flow.key(), secret).map_err(Error::WriteSecretError)?; Ok(()) } pub fn delete(&self, flow: &mut impl Flow) -> Result<()> { - let key = flow.key(); - delete_generic_password(&self.service, key).map_err(Error::DeleteSecretError)?; + delete_generic_password(&self.service, flow.key()).map_err(Error::DeleteSecretError)?; Ok(()) } } diff --git a/src/flow.rs b/src/flow.rs index c83f3e6..36888fe 100644 --- a/src/flow.rs +++ b/src/flow.rs @@ -120,6 +120,7 @@ impl Iterator for DeleteEntryFlow { if self.deleted { None } else { + self.deleted = true; Some(Io::Delete) } } diff --git a/src/lib.rs b/src/lib.rs index 2848bf3..816edee 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,16 +1,14 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #![doc = include_str!("../README.md")] -#[cfg(target_vendor = "apple")] -#[cfg(feature = "apple-native")] +#[cfg(feature = "apple-keychain")] pub mod apple; pub mod flow; pub mod io; -#[cfg(target_os = "linux")] #[cfg(feature = "secret-service")] pub mod secret_service; -#[cfg(target_os = "windows")] -#[cfg(feature = "windows-native")] +pub mod std; +#[cfg(feature = "windows-credentials")] pub mod windows; pub use self::{flow::*, io::*}; diff --git a/src/secret_service/crypto/mod.rs b/src/secret_service/crypto/mod.rs index 7c6ffcd..4baf954 100644 --- a/src/secret_service/crypto/mod.rs +++ b/src/secret_service/crypto/mod.rs @@ -14,3 +14,25 @@ pub use self::{ flow::{PutSalt, TakeSalt}, io::Io, }; + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub enum Provider { + #[default] + None, + #[cfg(feature = "secret-service-openssl-std")] + Openssl(Algorithm), + #[cfg(feature = "secret-service-rust-crypto-std")] + RustCrypto(Algorithm), +} + +impl Provider { + pub fn algorithm(&self) -> Algorithm { + match self { + Self::None => Algorithm::Plain, + #[cfg(feature = "secret-service-openssl-std")] + Self::Openssl(algorithm) => algorithm.clone(), + #[cfg(feature = "secret-service-rust-crypto-std")] + Self::RustCrypto(algorithm) => algorithm.clone(), + } + } +} diff --git a/src/std.rs b/src/std.rs new file mode 100644 index 0000000..7b14b9c --- /dev/null +++ b/src/std.rs @@ -0,0 +1,448 @@ +use secrecy::SecretSlice; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum Error { + #[error("cannot read secret: missing keyring provider")] + ReadMissingKeyringProviderError, + #[error("cannot write secret: missing keyring provider")] + WriteMissingKeyringProviderError, + #[error("cannot delete entry: missing keyring provider")] + DeleteMissingKeyringProviderError, + + #[error("cannot encrypt: missing crypto provider")] + EncryptMissingCryptoProviderError, + #[error("cannot decrypt: missing crypto provider")] + DecryptMissingCryptoProviderError, + + #[cfg(feature = "apple-keychain-std")] + #[error(transparent)] + AppleKeychainError(#[from] crate::apple::std::Error), + #[cfg(feature = "windows-credentials-std")] + #[error(transparent)] + WindowsCredentialsError(#[from] crate::windows::std::Error), + #[cfg(feature = "secret-service-dbus-std")] + #[error(transparent)] + SecretServiceDbusError(#[from] crate::secret_service::dbus::blocking::std::Error), + #[cfg(feature = "secret-service-zbus-std")] + #[error(transparent)] + SecretServiceZbusError(#[from] crate::secret_service::zbus::std::Error), + #[cfg(any( + feature = "secret-service-openssl-std", + feature = "secret-service-rust-crypto-std", + ))] + #[error(transparent)] + SecretServiceCryptoError(#[from] crate::secret_service::crypto::Error), +} + +pub type Result = std::result::Result; + +#[cfg(feature = "secret-service")] +pub enum Crypto

{ + Undefined(std::marker::PhantomData

), + #[cfg(feature = "secret-service-openssl-std")] + Openssl( + crate::secret_service::crypto::openssl::std::IoConnector

, + crate::secret_service::crypto::Algorithm, + ), + #[cfg(feature = "secret-service-rust-crypto-std")] + RustCrypto( + crate::secret_service::crypto::rust_crypto::std::IoConnector

, + crate::secret_service::crypto::Algorithm, + ), +} + +#[cfg(feature = "secret-service")] +impl

Default for Crypto

{ + fn default() -> Self { + Self::Undefined(Default::default()) + } +} + +#[cfg(feature = "secret-service")] +impl

Crypto

{ + pub fn decrypt(&mut self, flow: &mut F) -> Result<()> + where + F: crate::TakeSecret + crate::PutSecret + crate::secret_service::crypto::TakeSalt, + { + match self { + Self::Undefined(_) => Err(Error::DecryptMissingCryptoProviderError), + #[cfg(feature = "secret-service-openssl-std")] + Self::Openssl(crypto, _) => Ok(crypto.decrypt(flow)?), + #[cfg(feature = "secret-service-rust-crypto-std")] + Self::RustCrypto(crypto, _) => Ok(crypto.decrypt(flow)?), + } + } + + pub fn encrypt(&mut self, flow: &mut F) -> Result<()> + where + F: crate::TakeSecret + crate::PutSecret + crate::secret_service::crypto::PutSalt, + { + match self { + Self::Undefined(_) => Err(Error::EncryptMissingCryptoProviderError), + #[cfg(feature = "secret-service-openssl-std")] + Self::Openssl(crypto, _) => Ok(crypto.encrypt(flow)?), + #[cfg(feature = "secret-service-rust-crypto-std")] + Self::RustCrypto(crypto, _) => Ok(crypto.encrypt(flow)?), + } + } + + pub fn algorithm(&self) -> crate::secret_service::crypto::Algorithm { + match self { + Self::Undefined(_) => crate::secret_service::crypto::Algorithm::Plain, + #[cfg(feature = "secret-service-openssl-std")] + Self::Openssl(_, algorithm) => algorithm.clone(), + #[cfg(feature = "secret-service-rust-crypto-std")] + Self::RustCrypto(_, algorithm) => algorithm.clone(), + } + } +} + +pub enum Keyring { + Undefined, + #[cfg(feature = "apple-keychain-std")] + AppleKeychain(crate::apple::std::IoConnector), + #[cfg(feature = "windows-credentials-std")] + WindowsCredentials(crate::windows::std::IoConnector), + #[cfg(feature = "secret-service-dbus-std")] + DbusSecretService( + crate::secret_service::dbus::blocking::std::IoConnector, + Crypto>, + ), + #[cfg(feature = "secret-service-zbus-std")] + ZbusSecretService( + crate::secret_service::zbus::std::IoConnector, + Crypto, + ), +} + +impl Keyring { + #[cfg(feature = "apple-keychain-std")] + pub fn apple_keychain(service: impl ToString) -> Self { + use crate::apple::std::IoConnector; + Self::AppleKeychain(IoConnector::new(service)) + } + + #[cfg(feature = "windows-credentials-std")] + pub fn windows_credentials(service: impl ToString) -> Self { + use crate::windows::std::IoConnector; + Self::WindowsCredentials(IoConnector::new(service)) + } + + #[cfg(feature = "secret-service-dbus-std")] + pub fn dbus_secret_service( + service: impl ToString, + crypto: crate::secret_service::crypto::Provider, + ) -> crate::secret_service::dbus::blocking::std::Result { + use crate::secret_service::{crypto::Provider, dbus::blocking::std::IoConnector}; + let algorithm = crypto.algorithm(); + let mut dbus = IoConnector::new(service, algorithm.clone())?; + match crypto { + Provider::None => panic!(), + #[cfg(feature = "secret-service-openssl-std")] + Provider::Openssl(_) => { + use crate::secret_service::crypto::openssl; + let crypto = openssl::std::IoConnector::new(dbus.session())?; + let crypto = Crypto::Openssl(crypto, algorithm); + Ok(Self::DbusSecretService(dbus, crypto)) + } + #[cfg(feature = "secret-service-rust-crypto-std")] + Provider::RustCrypto(_) => { + use crate::secret_service::crypto::rust_crypto; + let crypto = rust_crypto::std::IoConnector::new(dbus.session())?; + let crypto = Crypto::RustCrypto(crypto, algorithm); + Ok(Self::DbusSecretService(dbus, crypto)) + } + } + } + + #[cfg(target_os = "linux")] + #[cfg(feature = "secret-service-zbus-std")] + pub fn zbus_secret_service( + service: impl ToString, + crypto: crate::secret_service::crypto::Provider, + ) -> Result { + use crate::secret_service::{crypto::Provider, zbus::std::IoConnector}; + let algorithm = crypto.algorithm(); + let mut zbus = IoConnector::new(service, algorithm.clone())?; + match crypto { + Provider::None => panic!(), + #[cfg(feature = "secret-service-openssl-std")] + Provider::Openssl(_) => { + use crate::secret_service::crypto::openssl; + let crypto = openssl::std::IoConnector::new(zbus.session())?; + let crypto = Crypto::Openssl(crypto, algorithm); + Ok(Self::ZbusSecretService(zbus, crypto)) + } + #[cfg(feature = "secret-service-rust-crypto-std")] + Provider::RustCrypto(_) => { + use crate::secret_service::crypto::rust_crypto; + let crypto = rust_crypto::std::IoConnector::new(zbus.session())?; + let crypto = Crypto::RustCrypto(crypto, algorithm); + Ok(Self::ZbusSecretService(zbus, crypto)) + } + } + } + + pub fn read(&mut self, key: impl AsRef) -> Result> { + match self { + Self::Undefined => Err(Error::ReadMissingKeyringProviderError), + #[cfg(feature = "apple-keychain-std")] + Self::AppleKeychain(keychain) => { + use crate::{Io, ReadEntryFlow, TakeSecret}; + + let mut flow = ReadEntryFlow::new(key.as_ref()); + + while let Some(io) = flow.next() { + match io { + Io::Read => { + keychain.read(&mut flow)?; + } + _ => (), + } + } + + Ok(flow.take_secret().unwrap()) + } + #[cfg(feature = "windows-credentials-std")] + Self::WindowsCredentials(creds) => { + use crate::{Io, ReadEntryFlow, TakeSecret}; + + let mut flow = ReadEntryFlow::new(key.as_ref()); + + while let Some(io) = flow.next() { + match io { + Io::Read => { + creds.read(&mut flow)?; + } + _ => (), + } + } + + Ok(flow.take_secret().unwrap()) + } + #[cfg(feature = "secret-service-dbus-std")] + Self::DbusSecretService(dbus, crypto) => { + use crate::{ + secret_service::{self, crypto, flow::ReadEntryFlow}, + Io, TakeSecret, + }; + + let mut flow = ReadEntryFlow::new(key.as_ref(), crypto.algorithm()); + + while let Some(io) = flow.next() { + match io { + secret_service::Io::Entry(Io::Read) => { + dbus.read(&mut flow)?; + } + secret_service::Io::Crypto(crypto::Io::Decrypt) => { + crypto.decrypt(&mut flow)?; + } + _ => (), + } + } + + Ok(flow.take_secret().unwrap()) + } + #[cfg(feature = "secret-service-zbus-std")] + Self::ZbusSecretService(zbus, crypto) => { + use crate::{ + secret_service::{self, crypto, flow::ReadEntryFlow}, + Io, TakeSecret, + }; + + let mut flow = ReadEntryFlow::new(key.as_ref(), crypto.algorithm()); + + while let Some(io) = flow.next() { + match io { + secret_service::Io::Entry(Io::Read) => { + zbus.read(&mut flow)?; + } + secret_service::Io::Crypto(crypto::Io::Decrypt) => { + crypto.decrypt(&mut flow)?; + } + _ => (), + } + } + + Ok(flow.take_secret().unwrap()) + } + } + } + + pub fn write( + &mut self, + key: impl AsRef, + secret: impl Into>, + ) -> Result<()> { + match self { + Self::Undefined => Err(Error::WriteMissingKeyringProviderError), + #[cfg(feature = "apple-keychain-std")] + Self::AppleKeychain(keychain) => { + use crate::{Io, WriteEntryFlow}; + + let mut flow = WriteEntryFlow::new(key.as_ref(), secret); + + while let Some(io) = flow.next() { + match io { + Io::Write => { + keychain.write(&mut flow)?; + } + _ => (), + } + } + + Ok(()) + } + #[cfg(feature = "windows-credentials-std")] + Self::WindowsCredentials(creds) => { + use crate::{Io, WriteEntryFlow}; + + let mut flow = WriteEntryFlow::new(key.as_ref(), secret); + + while let Some(io) = flow.next() { + match io { + Io::Write => { + creds.write(&mut flow)?; + } + _ => (), + } + } + + Ok(()) + } + #[cfg(feature = "secret-service-dbus-std")] + Self::DbusSecretService(dbus, crypto) => { + use crate::{ + secret_service::{self, crypto, flow::WriteEntryFlow}, + Io, + }; + + let mut flow = WriteEntryFlow::new(key.as_ref(), secret, crypto.algorithm()); + + while let Some(io) = flow.next() { + match io { + secret_service::Io::Crypto(crypto::Io::Encrypt) => { + crypto.encrypt(&mut flow)?; + } + secret_service::Io::Entry(Io::Write) => { + dbus.write(&mut flow)?; + } + _ => (), + } + } + + Ok(()) + } + #[cfg(feature = "secret-service-zbus-std")] + Self::ZbusSecretService(zbus, crypto) => { + use crate::{ + secret_service::{self, crypto, flow::WriteEntryFlow}, + Io, + }; + + let mut flow = WriteEntryFlow::new(key.as_ref(), secret, crypto.algorithm()); + + while let Some(io) = flow.next() { + match io { + secret_service::Io::Crypto(crypto::Io::Encrypt) => { + crypto.encrypt(&mut flow)?; + } + secret_service::Io::Entry(Io::Write) => { + zbus.write(&mut flow)?; + } + _ => (), + } + } + + Ok(()) + } + } + } + + pub fn delete(&mut self, key: impl AsRef) -> Result<()> { + match self { + Self::Undefined => Err(Error::DeleteMissingKeyringProviderError), + #[cfg(feature = "apple-keychain-std")] + Self::AppleKeychain(keychain) => { + use crate::{DeleteEntryFlow, Io}; + + let mut flow = DeleteEntryFlow::new(key.as_ref()); + + while let Some(io) = flow.next() { + match io { + Io::Delete => { + keychain.delete(&mut flow)?; + } + _ => (), + } + } + + Ok(()) + } + #[cfg(feature = "windows-credentials-std")] + Self::WindowsCredentials(creds) => { + use crate::{DeleteEntryFlow, Io}; + + let mut flow = DeleteEntryFlow::new(key.as_ref()); + + while let Some(io) = flow.next() { + match io { + Io::Delete => { + creds.delete(&mut flow)?; + } + _ => (), + } + } + + Ok(()) + } + #[cfg(feature = "secret-service-dbus-std")] + Self::DbusSecretService(dbus, _) => { + use crate::{ + secret_service::{self, flow::DeleteEntryFlow}, + Io, + }; + + let mut flow = DeleteEntryFlow::new(key.as_ref()); + + while let Some(io) = flow.next() { + match io { + secret_service::Io::Entry(Io::Delete) => { + dbus.delete(&mut flow)?; + } + _ => (), + } + } + + Ok(()) + } + #[cfg(feature = "secret-service-zbus-std")] + Self::ZbusSecretService(zbus, _) => { + use crate::{ + secret_service::{self, flow::DeleteEntryFlow}, + Io, + }; + + let mut flow = DeleteEntryFlow::new(key.as_ref()); + + while let Some(io) = flow.next() { + match io { + secret_service::Io::Entry(Io::Delete) => { + zbus.delete(&mut flow)?; + } + _ => (), + } + } + + Ok(()) + } + } + } +} + +impl Default for Keyring { + fn default() -> Self { + Self::Undefined + } +} diff --git a/src/windows/mod.rs b/src/windows/mod.rs index 159336f..1077574 100644 --- a/src/windows/mod.rs +++ b/src/windows/mod.rs @@ -1,2 +1,2 @@ -#[cfg(feature = "windows-native-std")] +#[cfg(feature = "windows-credentials-std")] pub mod std;