Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change windows implementation to use windows-rs and change error to this error #58

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,18 @@ secret-service = "1.1.1"

[target.'cfg(target_os = "windows")'.dependencies]
byteorder = "1.2.1"
winapi = { version = "0.3", features = ["wincred", "minwindef"] }
windows = "0.6.0"

[dev-dependencies]
clap = "2.0.5"
rpassword = "2.0.0"

[dependencies]
thiserror = "1.0.24"

[target.'cfg(target_os = "macos")'.dev-dependencies]
keychain-services = "0.1.1"
tempfile = "3.1.0"

[target.'cfg(target_os = "windows")'.build-dependencies]
windows = "0.4.0"
6 changes: 6 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
fn main() {
#[cfg(target_os = "windows")]
windows::build!(
windows::security::credentials::*,
);
}
102 changes: 32 additions & 70 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -1,86 +1,48 @@
use std::string::{FromUtf16Error, FromUtf8Error};

use thiserror::Error;

#[cfg(target_os = "linux")]
use secret_service::SsError;
use secret_service::SsError as OsError;
#[cfg(target_os = "macos")]
use security_framework::base::Error as SfError;
use std::error;
use std::fmt;
use std::string::FromUtf8Error;
use security_framework::base::Error as OsError;
#[cfg(target_os = "windows")]
use windows::Error as OsError;

pub type Result<T> = ::std::result::Result<T, KeyringError>;

#[derive(Debug)]
#[derive(Debug, Error)]
pub enum KeyringError {
#[cfg(target_os = "macos")]
MacOsKeychainError(SfError),
#[cfg(target_os = "linux")]
SecretServiceError(SsError),
#[cfg(target_os = "windows")]
WindowsVaultError,
#[error("OS Error message {:?}", os_error(.0))]
OsError(#[from] OsError),
#[error("No Backend found")]
NoBackendFound,
#[error("No Password found")]
NoPasswordFound,
Parse(FromUtf8Error),
#[error("Parsing error")]
Parse(#[from] ParseError),
}

impl fmt::Display for KeyringError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
#[cfg(target_os = "macos")]
KeyringError::MacOsKeychainError(ref err) => {
write!(f, "Mac Os Keychain Error: {}", err)
}
#[cfg(target_os = "linux")]
KeyringError::SecretServiceError(ref err) => write!(f, "Secret Service Error: {}", err),
#[cfg(target_os = "windows")]
KeyringError::WindowsVaultError => write!(f, "Windows Vault Error"),
KeyringError::NoBackendFound => write!(f, "Keyring error: No Backend Found"),
KeyringError::NoPasswordFound => write!(f, "Keyring Error: No Password Found"),
KeyringError::Parse(ref err) => write!(f, "Keyring Parse Error: {}", err),
}
}
}

impl error::Error for KeyringError {
fn description(&self) -> &str {
match *self {
#[cfg(target_os = "macos")]
KeyringError::MacOsKeychainError(ref err) => err.description(),
#[cfg(target_os = "linux")]
KeyringError::SecretServiceError(ref err) => err.description(),
#[cfg(target_os = "windows")]
KeyringError::WindowsVaultError => "Windows Vault Error",
KeyringError::NoBackendFound => "No Backend Found",
KeyringError::NoPasswordFound => "No Password Found",
KeyringError::Parse(ref err) => err.description(),
}
}

fn cause(&self) -> Option<&dyn error::Error> {
match *self {
#[cfg(target_os = "linux")]
KeyringError::SecretServiceError(ref err) => Some(err),
#[cfg(target_os = "macos")]
KeyringError::MacOsKeychainError(ref err) => Some(err),
_ => None,
}
}
#[derive(Debug, Error)]
pub enum ParseError {
#[error("from utf8")]
Utf8(#[from] FromUtf8Error),
#[error("from utf16")]
Utf16(#[from] FromUtf16Error),
}

#[cfg(target_os = "linux")]
impl From<SsError> for KeyringError {
fn from(err: SsError) -> KeyringError {
KeyringError::SecretServiceError(err)
fn os_error(e: &OsError) -> String {
#[cfg(target_os = "macos")]
{
e.message().unwrap_or_else(|| "no message".to_string())
}
}

#[cfg(target_os = "macos")]
impl From<SfError> for KeyringError {
fn from(err: SfError) -> KeyringError {
KeyringError::MacOsKeychainError(err)
#[cfg(target_os = "linux")]
{
let _e = e;
"no message".to_string()
}
}

impl From<FromUtf8Error> for KeyringError {
fn from(err: FromUtf8Error) -> KeyringError {
KeyringError::Parse(err)
#[cfg(target_os = "windows")]
{
e.message()
}
}
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ pub use linux::Keyring;
#[cfg(target_os = "windows")]
mod windows;
#[cfg(target_os = "windows")]
pub use windows::Keyring;
pub use crate::windows::Keyring;

// Configure for OSX
#[cfg(target_os = "macos")]
Expand Down
5 changes: 3 additions & 2 deletions src/linux.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::error::{KeyringError, Result};
use secret_service::{EncryptionType, SecretService};

use crate::error::{KeyringError, ParseError, Result};

pub struct Keyring<'a> {
attributes: Vec<(&'a str, &'a str)>,
service: &'a str,
Expand Down Expand Up @@ -46,7 +47,7 @@ impl<'a> Keyring<'a> {
let search = collection.search_items(self.attributes.clone())?;
let item = search.get(0).ok_or(KeyringError::NoPasswordFound)?;
let secret_bytes = item.get_secret()?;
let secret = String::from_utf8(secret_bytes)?;
let secret = String::from_utf8(secret_bytes).map_err(ParseError::Utf8)?;
Ok(secret)
}

Expand Down
44 changes: 31 additions & 13 deletions src/macos.rs
Original file line number Diff line number Diff line change
@@ -1,29 +1,39 @@
use crate::error::Result;
use std::path::Path;

use security_framework::os::macos::keychain::SecKeychain;
use security_framework::os::macos::passwords::find_generic_password;

use std::path::Path;
use crate::error::{ParseError, Result};

pub struct Keyring<'a> {
service: &'a str,
username: &'a str,
path: Option<&'a Path>
path: Option<&'a Path>,
}

// Eventually try to get collection into the Keyring struct?
impl<'a> Keyring<'a> {
pub fn new(service: &'a str, username: &'a str) -> Keyring<'a> {
Keyring { service, username, path: None }
Keyring {
service,
username,
path: None,
}
}

#[cfg(feature = "macos-specify-keychain")]
pub fn use_keychain(service: &'a str, username: &'a str, path: &'a Path) -> Keyring<'a> {
Keyring { service, username, path: Some(path) }
Keyring {
service,
username,
path: Some(path),
}
}

fn get_keychain(&self) -> security_framework::base::Result<SecKeychain> {
match self.path {
Some(path) => SecKeychain::open(path),
_ => SecKeychain::default()
_ => SecKeychain::default(),
}
}

Expand All @@ -38,19 +48,21 @@ impl<'a> Keyring<'a> {
}

pub fn get_password(&self) -> Result<String> {
let (password_bytes, _) = find_generic_password(Some(&[self.get_keychain()?]), self.service, self.username)?;
let (password_bytes, _) =
find_generic_password(Some(&[self.get_keychain()?]), self.service, self.username)?;

// Mac keychain allows non-UTF8 values, but this library only supports adding UTF8 items
// to the keychain, so this should only fail if we are trying to retrieve a non-UTF8
// password that was added to the keychain by another library

let password = String::from_utf8(password_bytes.to_vec())?;
let password = String::from_utf8(password_bytes.to_vec()).map_err(ParseError::Utf8)?;

Ok(password)
}

pub fn delete_password(&self) -> Result<()> {
let (_, item) = find_generic_password(Some(&[self.get_keychain()?]), self.service, self.username)?;
let (_, item) =
find_generic_password(Some(&[self.get_keychain()?]), self.service, self.username)?;

item.delete();

Expand All @@ -62,9 +74,11 @@ impl<'a> Keyring<'a> {
mod test {
use super::*;

use keychain_services::keychain::Keychain;
use security_framework::os::macos::keychain;
use tempfile::{tempdir, TempDir};
#[cfg(feature = "macos-specify-keychain")]
mod specific {
pub use security_framework::os::macos::keychain;
pub use tempfile::tempdir;
}

#[test]
fn test_basic() {
Expand All @@ -90,14 +104,18 @@ mod test {
#[ignore]
#[cfg(feature = "macos-specify-keychain")]
fn test_basic_with_features() {
use specific::*;

let password_1 = "大根";
let password_2 = "0xE5A4A7E6A0B9"; // Above in hex string

let dir = tempdir().unwrap();
let temp_keychain_path = dir.path().join("Temporary.keychain");
dbg!(&temp_keychain_path);
let temp_keychain = keychain::CreateOptions::new();
temp_keychain.create(&temp_keychain_path).expect("Could not create temp keychain");
temp_keychain
.create(&temp_keychain_path)
.expect("Could not create temp keychain");
let keyring = Keyring::use_keychain("testservice", "testuser", &temp_keychain_path);

keyring.set_password(password_1).unwrap();
Expand Down
Loading