Skip to content

Commit

Permalink
Stores generated key to macOS keychain
Browse files Browse the repository at this point in the history
  • Loading branch information
ultimaweapon committed Apr 26, 2024
1 parent 608b75b commit b25e4f7
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 1 deletion.
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
14 changes: 14 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
@@ -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")
}
_ => {}
}
}
17 changes: 17 additions & 0 deletions src/key/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down
44 changes: 44 additions & 0 deletions src/key/store/default.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#import <CoreFoundation/CoreFoundation.h>
#import <Security/Security.h>

#import <string.h>

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;
}
47 changes: 46 additions & 1 deletion src/key/store/default.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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 {
Expand Down Expand Up @@ -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!()
}
}
Expand All @@ -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;
}

0 comments on commit b25e4f7

Please sign in to comment.