├── keytar-sys ├── README.md ├── src │ ├── lib.h │ ├── lib.rs │ └── lib.cc ├── Cargo.toml ├── LICENSE └── build.rs ├── .gitignore ├── .gitmodules ├── Cargo.toml ├── src └── lib.rs ├── README.md ├── tests └── test.rs └── .github └── workflows └── build.yml /keytar-sys/README.md: -------------------------------------------------------------------------------- 1 | node-keytar bindings 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /node_modules 3 | /Cargo.lock -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "keytar-sys/node-keytar"] 2 | path = keytar-sys/node-keytar 3 | url = https://github.com/atom/node-keytar.git 4 | -------------------------------------------------------------------------------- /keytar-sys/src/lib.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "rust/cxx.h" 3 | #include "node-keytar/src/keytar.h" 4 | 5 | struct Password; 6 | 7 | void set_password(rust::Str service, rust::Str account, rust::Str password); 8 | Password get_password(rust::Str service, rust::Str account); 9 | bool delete_password(rust::Str service, rust::Str account); 10 | Password find_password(rust::Str service); 11 | -------------------------------------------------------------------------------- /keytar-sys/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "keytar-sys" 3 | description = "cxx bindings to keytar" 4 | version = "0.1.6" 5 | authors = ["stoically "] 6 | keywords = ["keytar", "libsecret", "keychain", "credential-vault"] 7 | edition = "2018" 8 | repository = "https://github.com/stoically/keytar-rs" 9 | readme = "README.md" 10 | license = "MIT" 11 | 12 | [dependencies] 13 | cxx = "1.0.0" 14 | 15 | [build-dependencies] 16 | pkg-config = "0.3.17" 17 | cc = "1.0.54" 18 | cxx-build = "1.0.0" 19 | 20 | [features] 21 | docs-rs = [] 22 | 23 | [package.metadata.docs.rs] 24 | features = ["docs-rs"] 25 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "keytar" 3 | description = "keytar bindings to safely interact with operating system keychains" 4 | version = "0.1.6" 5 | authors = ["stoically "] 6 | keywords = ["keytar", "libsecret", "keychain", "credential-vault"] 7 | edition = "2018" 8 | repository = "https://github.com/stoically/keytar-rs" 9 | readme = "README.md" 10 | license = "MIT" 11 | 12 | [dependencies] 13 | keytar-sys = { path = "keytar-sys", version = "0.1.6" } 14 | 15 | [features] 16 | docs-rs = ["keytar-sys/docs-rs"] 17 | 18 | [package.metadata.docs.rs] 19 | features = ["docs-rs"] 20 | 21 | [workspace] 22 | members = [ 23 | "keytar-sys" 24 | ] 25 | -------------------------------------------------------------------------------- /keytar-sys/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2020 stoically@protonmail.com 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! [keytar](https://github.com/atom/node-keytar) bindings for Rust 2 | //! 3 | //! A native Node module to get, add, replace, and delete passwords in system's keychain. 4 | //! On macOS the passwords are managed by the Keychain, on Linux they are managed by 5 | //! the Secret Service API/libsecret, and on Windows they are managed by Credential Vault. 6 | //! 7 | //! ``` 8 | //! let service = "service"; 9 | //! let account = "account"; 10 | //! let password = "password"; 11 | //! 12 | //! # { 13 | //! # let service = service; 14 | //! # let account = account; 15 | //! # let password = password; 16 | //! keytar::set_password(service, account, password).unwrap(); 17 | //! # } 18 | //! # 19 | //! # keytar::delete_password(service, account).unwrap(); 20 | //! ``` 21 | //! 22 | //! ## Linux Requirement 23 | //! 24 | //! Currently this library uses `libsecret`. Depending on your distribution, 25 | //! you will need to install the appropriate package, e.g.: 26 | //! 27 | //! - Debian/Ubuntu: `sudo apt-get install libsecret-1-dev` 28 | //! - Red Hat-based: `sudo yum install libsecret-devel` 29 | //! - Arch Linux: `sudo pacman -S libsecret` 30 | 31 | pub use keytar_sys::ffi::*; 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # keytar-rs 2 | 3 | [![crates.io page](https://img.shields.io/crates/v/keytar.svg)](https://crates.io/crates/keytar) 4 | [![docs.rs page](https://docs.rs/keytar/badge.svg)](https://docs.rs/keytar/) 5 | ![build](https://github.com/stoically/keytar-rs/workflows/build/badge.svg) 6 | ![license: MIT](https://img.shields.io/crates/l/keytar.svg) 7 | 8 | [keytar](https://github.com/atom/node-keytar) bindings for Rust 9 | 10 | **Note:** You might want to consider using the [keyring](https://crates.io/crates/keyring) crate instead, which is fully written in Rust. 11 | 12 | A native Node module to get, add, replace, and delete passwords in system's keychain. On macOS the passwords are managed by the Keychain, on Linux they are managed by the Secret Service API/libsecret, and on Windows they are managed by Credential Vault. 13 | 14 | ```rust 15 | let service = "service"; 16 | let account = "account"; 17 | let password = "password"; 18 | 19 | keytar::set_password(service, account, password).unwrap(); 20 | ``` 21 | 22 | ## Linux Requirement 23 | 24 | Currently this library uses `libsecret`. Depending on your distribution, 25 | you will need to install the appropriate package, e.g.: 26 | 27 | - Debian/Ubuntu: `sudo apt-get install libsecret-1-dev` 28 | - Red Hat-based: `sudo yum install libsecret-devel` 29 | - Arch Linux: `sudo pacman -S libsecret` 30 | -------------------------------------------------------------------------------- /keytar-sys/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cxx::bridge] 2 | pub mod ffi { 3 | /// Workaround until `cxx` supports `Option`s 4 | /// https://github.com/dtolnay/cxx/issues/87 5 | pub struct Password { 6 | pub success: bool, 7 | pub password: String, 8 | } 9 | 10 | unsafe extern "C++" { 11 | include!("src/lib.h"); 12 | 13 | /// Save the password for the service and account to the keychain. Adds a new entry if 14 | /// necessary, or updates an existing entry if one exists. 15 | /// 16 | /// Equivalent to [`setPassword`](https://github.com/atom/node-keytar#setpasswordservice-account-password) 17 | pub fn set_password(service: &str, account: &str, password: &str) -> Result<()>; 18 | 19 | /// Get the stored password for the service and account. 20 | /// 21 | /// Equivalent to [`getPassword`](https://github.com/atom/node-keytar#getpasswordservice-account) 22 | pub fn get_password(service: &str, account: &str) -> Result; 23 | 24 | /// Delete the stored password for the service and account. 25 | /// 26 | /// Equivalent to [`deletePassword`](https://github.com/atom/node-keytar#deletepasswordservice-account) 27 | pub fn delete_password(service: &str, account: &str) -> Result; 28 | 29 | /// Find a password for the service in the keychain. 30 | /// 31 | /// Equivalent to [`findPassword`](https://github.com/atom/node-keytar#findpasswordservice) 32 | pub fn find_password(service: &str) -> Result; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/test.rs: -------------------------------------------------------------------------------- 1 | #[test] 2 | fn set_password() { 3 | let service = "ktr_service"; 4 | let account = "ktr_account"; 5 | let password_in = "ktr_password"; 6 | keytar::set_password(service, account, password_in).unwrap(); 7 | 8 | let password_out = keytar::get_password(service, account).unwrap(); 9 | assert_eq!(true, password_out.success); 10 | assert_eq!(password_in, password_out.password); 11 | 12 | keytar::delete_password(service, account).unwrap(); 13 | } 14 | 15 | #[test] 16 | fn get_not_existent_password() { 17 | let password_out = keytar::get_password("doesn't", "exist").unwrap(); 18 | assert_eq!(false, password_out.success); 19 | } 20 | 21 | #[test] 22 | fn delete_password() { 23 | let service = "ktr_del_service"; 24 | let account = "ktr_del_account"; 25 | let password_in = "ktr_del_password"; 26 | keytar::set_password(service, account, password_in).unwrap(); 27 | 28 | keytar::delete_password(service, account).unwrap(); 29 | 30 | let password_out = keytar::get_password(service, account).unwrap(); 31 | assert_eq!(false, password_out.success); 32 | } 33 | 34 | #[test] 35 | fn find_password() { 36 | let service = "ktr_find_service"; 37 | let account = "ktr_find_account"; 38 | let password_in = "ktr_find_password"; 39 | keytar::set_password(service, account, password_in).unwrap(); 40 | 41 | let password_out = keytar::find_password(service).unwrap(); 42 | assert_eq!(true, password_out.success); 43 | assert_eq!(password_in, password_out.password); 44 | 45 | keytar::delete_password(service, account).unwrap(); 46 | } 47 | -------------------------------------------------------------------------------- /keytar-sys/build.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "docs-rs")] 2 | fn main() {} 3 | 4 | #[cfg(not(feature = "docs-rs"))] 5 | fn main() { 6 | use std::env; 7 | 8 | let mut cxx = cxx_build::bridge("src/lib.rs"); 9 | 10 | let out_dir = env::var("OUT_DIR").unwrap(); 11 | let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); 12 | let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap(); 13 | match target_os.as_str() { 14 | "linux" => { 15 | let lib = pkg_config::probe_library("libsecret-1").unwrap(); 16 | for dir in lib.include_paths.into_iter() { 17 | cxx.include(dir); 18 | } 19 | 20 | cxx.file("node-keytar/src/keytar_posix.cc") 21 | .flag("-fexceptions") 22 | .flag("-Wno-missing-field-initializers") 23 | .flag("-Wno-deprecated-declarations"); 24 | } 25 | "macos" => { 26 | println!("cargo:rustc-link-lib=framework=AppKit"); 27 | println!("cargo:rustc-link-lib=framework=Security"); 28 | 29 | cxx.file("node-keytar/src/keytar_mac.cc"); 30 | } 31 | "windows" => { 32 | cxx.file("node-keytar/src/keytar_win.cc"); 33 | } 34 | _ => panic!("unsupported TARGET_OS: {}", target_os), 35 | } 36 | 37 | cxx.file("src/lib.cc") 38 | .include(manifest_dir) 39 | .include(out_dir) 40 | .flag_if_supported("-std=c++14") 41 | .compile("keytar-sys"); 42 | 43 | println!("cargo:rerun-if-changed=src/lib.rs"); 44 | println!("cargo:rerun-if-changed=src/lib.h"); 45 | println!("cargo:rerun-if-changed=src/lib.cc"); 46 | } 47 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | name: build 8 | runs-on: ${{ matrix.config.os }} 9 | strategy: 10 | matrix: 11 | config: 12 | - { os: ubuntu-latest } 13 | - { os: macos-latest } 14 | - { os: windows-latest } 15 | 16 | steps: 17 | - uses: actions/checkout@v1 18 | - name: ubuntu 19 | if: matrix.config.os == 'ubuntu-latest' 20 | run: | 21 | sudo apt update 22 | sudo apt install libsecret-1-dev xvfb dbus-x11 gnome-keyring 23 | 24 | # `secret-tool` (https://gitlab.gnome.org/GNOME/libsecret) does not support creating keyrings, 25 | # so we need to fall back to the deprecated `python-gnomekeyring` 26 | sudo add-apt-repository 'deb http://archive.ubuntu.com/ubuntu xenial main restricted universe multiverse' 27 | sudo apt-key adv --keyserver keyserver.ubuntu.com --recv 40976EAF437D05B5 3B4FE6ACC0B21F32 28 | sudo apt update 29 | sudo apt install -t xenial python-gnomekeyring 30 | dbus-launch /usr/bin/python -c "import gnomekeyring;gnomekeyring.create_sync('login', '');" 31 | 32 | Xvfb :99 & 33 | 34 | - name: install rust 35 | uses: actions-rs/toolchain@v1 36 | with: 37 | toolchain: 1.48.0 38 | default: true 39 | 40 | - name: build 41 | shell: bash 42 | run: | 43 | # enable symlinking for windows 44 | # (required because of patched cxx crate) 45 | export MSYS=winsymlinks:nativestrict 46 | git config --global core.symlinks true 47 | 48 | export DISPLAY=":99" 49 | git submodule update --init 50 | cargo test 51 | cargo build 52 | -------------------------------------------------------------------------------- /keytar-sys/src/lib.cc: -------------------------------------------------------------------------------- 1 | #include "src/lib.h" 2 | #include "keytar-sys/src/lib.rs.h" 3 | #include 4 | 5 | void set_password(rust::Str service, rust::Str account, rust::Str password) 6 | { 7 | std::string error; 8 | keytar::KEYTAR_OP_RESULT result = keytar::SetPassword(std::string(service), 9 | std::string(account), 10 | std::string(password), 11 | &error); 12 | 13 | if (result == keytar::FAIL_ERROR) 14 | { 15 | throw std::logic_error(error); 16 | } 17 | } 18 | 19 | Password get_password(rust::Str service, rust::Str account) 20 | { 21 | std::string error; 22 | std::string password; 23 | keytar::KEYTAR_OP_RESULT result = keytar::GetPassword(std::string(service), 24 | std::string(account), 25 | &password, 26 | &error); 27 | 28 | if (result == keytar::FAIL_ERROR) 29 | { 30 | throw std::logic_error(error); 31 | } 32 | else if (result == keytar::FAIL_NONFATAL) 33 | { 34 | return Password{false, rust::String("")}; 35 | } 36 | else 37 | { 38 | return Password{true, rust::String(password)}; 39 | } 40 | } 41 | 42 | bool delete_password(rust::Str service, rust::Str account) 43 | { 44 | std::string error; 45 | keytar::KEYTAR_OP_RESULT result = keytar::DeletePassword(std::string(service), std::string(account), &error); 46 | 47 | if (result == keytar::FAIL_ERROR) 48 | { 49 | throw std::logic_error(error); 50 | } 51 | else if (result == keytar::FAIL_NONFATAL) 52 | { 53 | return false; 54 | } 55 | else 56 | { 57 | return true; 58 | } 59 | } 60 | 61 | Password find_password(rust::Str service) 62 | { 63 | std::string error; 64 | std::string password; 65 | keytar::KEYTAR_OP_RESULT result = keytar::FindPassword(std::string(service), 66 | &password, 67 | &error); 68 | if (result == keytar::FAIL_ERROR) 69 | { 70 | throw std::logic_error(error); 71 | } 72 | else if (result == keytar::FAIL_NONFATAL) 73 | { 74 | return Password{false, rust::String("")}; 75 | } 76 | else 77 | { 78 | return Password{true, rust::String(password)}; 79 | } 80 | } 81 | --------------------------------------------------------------------------------