From b25e4f7a38cbe04ad9ac1f81d52ef75618607b27 Mon Sep 17 00:00:00 2001 From: Putta Khunchalee Date: Sat, 27 Apr 2024 01:15:29 +0700 Subject: [PATCH] Stores generated key to macOS keychain --- Cargo.toml | 3 +++ build.rs | 14 ++++++++++++ src/key/mod.rs | 17 +++++++++++++++ src/key/store/default.m | 44 +++++++++++++++++++++++++++++++++++++ src/key/store/default.rs | 47 +++++++++++++++++++++++++++++++++++++++- 5 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 build.rs create mode 100644 src/key/store/default.m diff --git a/Cargo.toml b/Cargo.toml index ea0c6eb..ae7fff9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,3 +17,6 @@ thiserror = "1.0.58" ureq = "2.9.6" url = { version = "2.5.0", features = ["serde"] } zeroize = "1.7.0" + +[build-dependencies] +cc = "1.0.95" diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..d6eb583 --- /dev/null +++ b/build.rs @@ -0,0 +1,14 @@ +fn main() { + match std::env::var("CARGO_CFG_TARGET_OS").unwrap().as_str() { + "macos" => { + println!("cargo::rustc-link-lib=framework=CoreFoundation"); + println!("cargo::rustc-link-lib=framework=Security"); + println!("cargo::rerun-if-changed=src/key/store/default.m"); + + cc::Build::new() + .file("src/key/store/default.m") + .compile("warpffi") + } + _ => {} + } +} diff --git a/src/key/mod.rs b/src/key/mod.rs index fed7261..c06d80f 100644 --- a/src/key/mod.rs +++ b/src/key/mod.rs @@ -2,6 +2,7 @@ use self::store::{DefaultStore, Keystore}; use crate::config::AppConfig; use crate::home::Home; use std::collections::HashMap; +use std::fmt::{Display, Formatter}; use std::iter::FusedIterator; use std::sync::Arc; use thiserror::Error; @@ -46,6 +47,22 @@ impl KeyMgr { #[derive(Clone, PartialEq, Eq, Hash)] pub struct KeyId([u8; 16]); +impl AsRef<[u8; 16]> for KeyId { + fn as_ref(&self) -> &[u8; 16] { + &self.0 + } +} + +impl Display for KeyId { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + for b in self.0 { + write!(f, "{b:x}")?; + } + + Ok(()) + } +} + /// Key to encrypt/decrypt files in a repository. pub struct Key { id: KeyId, diff --git a/src/key/store/default.m b/src/key/store/default.m new file mode 100644 index 0000000..00932ac --- /dev/null +++ b/src/key/store/default.m @@ -0,0 +1,44 @@ +#import +#import + +#import + +int default_store_store_key(const UInt8 *id, const UInt8 *key, const char *tag) { + CFMutableDictionaryRef attrs; + CFDataRef bin; + CFStringRef str; + OSStatus status; + + // Setup attributes, + attrs = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + + CFDictionarySetValue(attrs, kSecClass, kSecClassKey); + + bin = CFDataCreateWithBytesNoCopy(NULL, key, 16, kCFAllocatorNull); + CFDictionarySetValue(attrs, kSecValueData, bin); + CFRelease(bin); + + str = CFStringCreateWithCStringNoCopy(NULL, "Warp File Key", kCFStringEncodingUTF8, kCFAllocatorNull); + CFDictionarySetValue(attrs, kSecAttrLabel, str); + CFRelease(str); + + bin = CFDataCreateWithBytesNoCopy(NULL, id, 16, kCFAllocatorNull); + CFDictionarySetValue(attrs, kSecAttrApplicationLabel, bin); + CFRelease(bin); + + bin = CFDataCreateWithBytesNoCopy(NULL, (const UInt8 *)tag, strlen(tag), kCFAllocatorNull); + CFDictionarySetValue(attrs, kSecAttrApplicationTag, bin); + CFRelease(bin); + + CFDictionarySetValue(attrs, kSecAttrIsPermanent, kCFBooleanTrue); + CFDictionarySetValue(attrs, kSecAttrSynchronizable, kCFBooleanTrue); + CFDictionarySetValue(attrs, kSecUseDataProtectionKeychain, kCFBooleanTrue); + CFDictionarySetValue(attrs, kSecAttrAccessible, kSecAttrAccessibleAfterFirstUnlock); + CFDictionarySetValue(attrs, kSecAttrKeyClass, kSecAttrKeyClassSymmetric); + + // Store key. + status = SecItemAdd(attrs, nil); + CFRelease(attrs); + + return status; +} diff --git a/src/key/store/default.rs b/src/key/store/default.rs index 39615d6..c6dbdaf 100644 --- a/src/key/store/default.rs +++ b/src/key/store/default.rs @@ -1,12 +1,13 @@ use super::Keystore; use crate::home::Home; -use crate::key::Key; +use crate::key::{Key, KeyId}; use aes::cipher::{BlockEncrypt, KeyInit}; use aes::Aes128; use getrandom::getrandom; use sha3::digest::{ExtendableOutput, Update, XofReader}; use sha3::Shake128; use std::error::Error; +use std::ffi::CStr; use std::ops::{Deref, DerefMut}; use std::sync::Arc; use thiserror::Error; @@ -16,9 +17,35 @@ use zeroize::Zeroizing; pub struct DefaultStore {} impl DefaultStore { + const KEY_TYPE1: &'static CStr = c"HKDF:SHA3:256:AES:CTR:128:HMAC:SHA3:256"; + pub fn new(_: &Home) -> Self { Self {} } + + #[cfg(target_os = "linux")] + fn store(&self, _: &KeyId, _: &[u8; 16]) -> Result<(), GenerateError> { + todo!() + } + + #[cfg(target_os = "macos")] + fn store(&self, id: &KeyId, key: &[u8; 16]) -> Result<(), GenerateError> { + let id = id.as_ref().as_ptr(); + let key = key.as_ptr(); + let tag = Self::KEY_TYPE1.as_ptr(); + let status = unsafe { default_store_store_key(id, key, tag) }; + + if status == 0 { + Ok(()) + } else { + Err(GenerateError::StoreKeyFailed(status)) + } + } + + #[cfg(target_os = "windows")] + fn store(&self, _: &KeyId, _: &[u8; 16]) -> Result<(), GenerateError> { + todo!() + } } impl Keystore for DefaultStore { @@ -53,6 +80,11 @@ impl Keystore for DefaultStore { hasher.update(&kcv); hasher.finalize_xof().read(&mut id); + // Store the key. + let id = KeyId(id); + + self.store(&id, &key)?; + todo!() } } @@ -73,4 +105,17 @@ impl Iterator for KeyList { enum GenerateError { #[error("couldn't generate a new key")] GenerateKeyFailed(#[source] getrandom::Error), + + #[cfg(target_os = "macos")] + #[error("couldn't store the generated key to a keychain (code: {0})")] + StoreKeyFailed(std::ffi::c_int), +} + +#[cfg(target_os = "macos")] +extern "C" { + fn default_store_store_key( + id: *const u8, + key: *const u8, + tag: *const std::ffi::c_char, + ) -> std::ffi::c_int; }