feat: v1.5.0, store secure enclave private key to keychain
This commit is contained in:
@@ -37,6 +37,7 @@ use crate::util_digest::DigestWrite;
|
||||
use crate::util_keychainkey;
|
||||
#[cfg(feature = "macos")]
|
||||
use crate::util_keychainstatic;
|
||||
use crate::util_keychainstatic::KeychainKey;
|
||||
use crate::util_progress::Progress;
|
||||
use crate::wrap_key::WrapKey;
|
||||
|
||||
@@ -542,10 +543,16 @@ fn try_decrypt_se_key_ecdh(config: &Option<TinyEncryptConfig>,
|
||||
if config_envelop_args.is_empty() {
|
||||
return simple_error!("Not enough arguments for: {}", &envelop.kid);
|
||||
}
|
||||
let private_key_base64 = &config_envelop_args[0];
|
||||
|
||||
let private_key_base64 = if let Ok(keychain_key) = KeychainKey::parse(&config_envelop_args[0]) {
|
||||
let key = opt_value_result!(keychain_key.get_password()?, "Key: {} not found", &keychain_key.to_str());
|
||||
opt_result!(String::from_utf8(key), "Parse key failed: {}")
|
||||
} else {
|
||||
config_envelop_args[0].clone()
|
||||
};
|
||||
|
||||
let shared_secret = opt_result!(util_keychainkey::decrypt_data(
|
||||
private_key_base64,
|
||||
&private_key_base64,
|
||||
&e_pub_key_bytes
|
||||
), "Decrypt via secure enclave failed: {}");
|
||||
let key = util::simple_kdf(shared_secret.as_slice());
|
||||
@@ -595,13 +602,18 @@ fn try_decrypt_key_ecdh_static_x25519(config: &Option<TinyEncryptConfig>, envelo
|
||||
let config_envelop = opt_value_result!(
|
||||
config.find_by_kid(&envelop.kid), "Cannot find config for: {}", &envelop.kid);
|
||||
let config_envelop_args = opt_value_result!(&config_envelop.args, "No arguments found for: {}", &envelop.kid);
|
||||
if config_envelop_args.len() < 3 {
|
||||
if config_envelop_args.len() != 1 && config_envelop_args.len() != 3 {
|
||||
return simple_error!("Not enough arguments for: {}", &envelop.kid);
|
||||
}
|
||||
let service_name = &config_envelop_args[1];
|
||||
let key_name = &config_envelop_args[2];
|
||||
|
||||
let keychain_key = if config_envelop_args.len() == 1 {
|
||||
KeychainKey::parse(&config_envelop_args[0])?
|
||||
} else {
|
||||
KeychainKey::from(&config_envelop_args[0], &config_envelop_args[1], &config_envelop_args[2])
|
||||
};
|
||||
|
||||
let shared_secret = opt_result!(
|
||||
util_keychainstatic::decrypt_data(service_name, key_name, &e_pub_key_bytes), "Decrypt static x25519 failed: {}");
|
||||
util_keychainstatic::decrypt_data(&keychain_key, &e_pub_key_bytes), "Decrypt static x25519 failed: {}");
|
||||
|
||||
let key = util::simple_kdf(shared_secret.as_slice());
|
||||
let key_nonce = KeyNonce { k: &key, n: &wrap_key.nonce };
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
use clap::Args;
|
||||
use rust_util::{debugging, information, opt_result, opt_value_result, simple_error, success, warning, XResult};
|
||||
use security_framework::os::macos::keychain::SecKeychain;
|
||||
use rust_util::{debugging, information, opt_result, simple_error, success, warning, XResult};
|
||||
|
||||
use crate::config::TinyEncryptConfigEnvelop;
|
||||
use crate::spec::TinyEncryptEnvelopType;
|
||||
#[cfg(feature = "secure-enclave")]
|
||||
use crate::util_keychainkey;
|
||||
use crate::util_keychainstatic;
|
||||
use crate::util_keychainstatic::X25519StaticSecret;
|
||||
use crate::util_keychainstatic::{KeychainKey, X25519StaticSecret};
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
pub struct CmdInitKeychain {
|
||||
@@ -22,7 +21,7 @@ pub struct CmdInitKeychain {
|
||||
pub server_name: Option<String>,
|
||||
/// Key name
|
||||
#[arg(long, short = 'n')]
|
||||
pub key_name: Option<String>,
|
||||
pub key_name: String,
|
||||
}
|
||||
|
||||
const DEFAULT_SERVICE_NAME: &str = "tiny-encrypt";
|
||||
@@ -43,18 +42,23 @@ pub fn keychain_key_se(cmd_init_keychain: CmdInitKeychain) -> XResult<()> {
|
||||
if !util_keychainkey::is_support_se() {
|
||||
return simple_error!("Secure enclave is not supported.");
|
||||
}
|
||||
|
||||
let service_name = cmd_init_keychain.server_name.as_deref().unwrap_or(DEFAULT_SERVICE_NAME);
|
||||
let key_name = &cmd_init_keychain.key_name;
|
||||
let keychain_key = KeychainKey::from("", service_name, key_name);
|
||||
|
||||
let (public_key_hex, private_key_base64) = util_keychainkey::generate_se_p256_keypair()?;
|
||||
let public_key_compressed_hex = public_key_hex.chars()
|
||||
.skip(2).take(public_key_hex.len() / 2 - 1).collect::<String>();
|
||||
|
||||
keychain_key.set_password(private_key_base64.as_bytes())?;
|
||||
|
||||
let config_envelop = TinyEncryptConfigEnvelop {
|
||||
r#type: TinyEncryptEnvelopType::KeyP256,
|
||||
sid: cmd_init_keychain.key_name.clone(),
|
||||
sid: Some(cmd_init_keychain.key_name.clone()),
|
||||
kid: format!("keychain:02{}", &public_key_compressed_hex),
|
||||
desc: Some("Keychain Secure Enclave".to_string()),
|
||||
args: Some(vec![
|
||||
private_key_base64
|
||||
]),
|
||||
args: Some(vec![keychain_key.to_str()]),
|
||||
public_part: public_key_hex,
|
||||
};
|
||||
|
||||
@@ -65,20 +69,20 @@ pub fn keychain_key_se(cmd_init_keychain: CmdInitKeychain) -> XResult<()> {
|
||||
|
||||
pub fn keychain_key_static(cmd_init_keychain: CmdInitKeychain) -> XResult<()> {
|
||||
let service_name = cmd_init_keychain.server_name.as_deref().unwrap_or(DEFAULT_SERVICE_NAME);
|
||||
let sec_keychain = opt_result!(SecKeychain::default(), "Get keychain failed: {}");
|
||||
let key_name = opt_value_result!(&cmd_init_keychain.key_name, "Key name is required.");
|
||||
let key_name = &cmd_init_keychain.key_name;
|
||||
let keychain_key = KeychainKey::from("", service_name, key_name);
|
||||
|
||||
let public_key = match sec_keychain.find_generic_password(service_name, key_name) {
|
||||
Ok((static_x25519, _)) => {
|
||||
let public_key = match keychain_key.get_password()? {
|
||||
Some(static_x25519) => {
|
||||
warning!("Key already exists: {}.{}", service_name, key_name);
|
||||
let x25519_static_secret = X25519StaticSecret::parse_bytes(static_x25519.as_ref())?;
|
||||
x25519_static_secret.to_public_key()?
|
||||
}
|
||||
Err(_) => {
|
||||
let (keychain_key, public_key) = util_keychainstatic::generate_static_x25519_secret();
|
||||
debugging!("Keychain key : {}", keychain_key);
|
||||
None => {
|
||||
let (keychain_key_bytes, public_key) = util_keychainstatic::generate_static_x25519_secret();
|
||||
debugging!("Keychain key : {}", keychain_key_bytes);
|
||||
opt_result!(
|
||||
sec_keychain.set_generic_password(service_name, key_name, keychain_key.as_bytes()),
|
||||
keychain_key.set_password(keychain_key_bytes.as_bytes()),
|
||||
"Write static x25519 failed: {}"
|
||||
);
|
||||
public_key
|
||||
@@ -94,11 +98,7 @@ pub fn keychain_key_static(cmd_init_keychain: CmdInitKeychain) -> XResult<()> {
|
||||
sid: Some(key_name.clone()),
|
||||
kid: format!("keychain:{}", &public_key_hex),
|
||||
desc: Some("Keychain static".to_string()),
|
||||
args: Some(vec![
|
||||
"".to_string(),
|
||||
service_name.to_string(),
|
||||
key_name.clone(),
|
||||
]),
|
||||
args: Some(vec![keychain_key.to_str()]),
|
||||
public_part: public_key_hex,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
use rust_util::{opt_result, simple_error, XResult};
|
||||
use rust_util::{debugging, opt_result, opt_value_result, simple_error, XResult};
|
||||
use security_framework::os::macos::keychain::SecKeychain;
|
||||
use x25519_dalek::{PublicKey, StaticSecret};
|
||||
use zeroize::Zeroize;
|
||||
|
||||
const X2559_PLAIN_PREFIX: &str = "x25519-plain:";
|
||||
const KEYCHAIN_KEY_PREFIX: &str = "keychain:";
|
||||
|
||||
pub struct KeychainKey {
|
||||
pub keychain_name: String,
|
||||
pub service_name: String,
|
||||
pub key_name: String,
|
||||
}
|
||||
|
||||
|
||||
pub struct X25519StaticSecret {
|
||||
pub secret: Vec<u8>,
|
||||
@@ -60,12 +68,79 @@ impl X25519StaticSecret {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn decrypt_data(service_name: &str, key_name: &str, ephemeral_public_key_bytes: &[u8]) -> XResult<Vec<u8>> {
|
||||
let sec_keychain = opt_result!(SecKeychain::default(), "Get keychain failed: {}");
|
||||
let (static_x25519, _) = opt_result!(sec_keychain.find_generic_password(service_name, key_name),
|
||||
"Cannot find static x25519 {}.{}: {}", service_name, key_name);
|
||||
impl KeychainKey {
|
||||
pub fn from(keychain_name: &str, service_name: &str, key_name: &str) -> Self {
|
||||
Self {
|
||||
keychain_name: keychain_name.to_string(),
|
||||
service_name: service_name.to_string(),
|
||||
key_name: key_name.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
let x25519_static_secret = X25519StaticSecret::parse_bytes(static_x25519.as_ref())?;
|
||||
pub fn parse(keychain_key: &str) -> XResult<Self> {
|
||||
if !keychain_key.starts_with(KEYCHAIN_KEY_PREFIX) {
|
||||
return simple_error!("Not a valid keychain key: {}", keychain_key);
|
||||
}
|
||||
//keychain:keychain_name:service_name:key_name
|
||||
let keychain_key_parts = keychain_key.split(':').collect::<Vec<_>>();
|
||||
if keychain_key_parts.len() != 4 {
|
||||
return simple_error!("Not a valid keychain key: {}", keychain_key);
|
||||
}
|
||||
Ok(Self {
|
||||
keychain_name: keychain_key_parts[1].to_string(),
|
||||
service_name: keychain_key_parts[2].to_string(),
|
||||
key_name: keychain_key_parts[3].to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn to_str(&self) -> String {
|
||||
let mut s = String::new();
|
||||
s.push_str(KEYCHAIN_KEY_PREFIX);
|
||||
s.push_str(&self.keychain_name);
|
||||
s.push_str(":");
|
||||
s.push_str(&self.service_name);
|
||||
s.push_str(":");
|
||||
s.push_str(&self.key_name);
|
||||
s
|
||||
}
|
||||
|
||||
pub fn get_password(&self) -> XResult<Option<Vec<u8>>> {
|
||||
let sec_keychain = self.get_keychain()?;
|
||||
match sec_keychain.find_generic_password(&self.service_name, &self.key_name) {
|
||||
Ok((item_password, _keychain_item)) => {
|
||||
Ok(Some(item_password.as_ref().to_vec()))
|
||||
}
|
||||
Err(e) => {
|
||||
debugging!("Get password: {} failed: {}", &self.to_str(), e);
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_password(&self, password: &[u8]) -> XResult<()> {
|
||||
let sec_keychain = self.get_keychain()?;
|
||||
if let Ok(_) = sec_keychain.find_generic_password(&self.service_name, &self.key_name) {
|
||||
return simple_error!("Password {}.{} exists", &self.service_name, &self.key_name);
|
||||
}
|
||||
opt_result!(
|
||||
sec_keychain.set_generic_password(&self.service_name, &self.key_name, password),
|
||||
"Set password {}.{} error: {}", &self.service_name, &self.key_name
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_keychain(&self) -> XResult<SecKeychain> {
|
||||
if !self.keychain_name.is_empty() {
|
||||
return simple_error!("Keychain name must be empty.");
|
||||
}
|
||||
Ok(opt_result!(SecKeychain::default(), "Get keychain failed: {}"))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn decrypt_data(keychain_key: &KeychainKey, ephemeral_public_key_bytes: &[u8]) -> XResult<Vec<u8>> {
|
||||
let static_x25519 = opt_value_result!(keychain_key.get_password()?, "Static X25519 not found: {}", &keychain_key.to_str());
|
||||
|
||||
let x25519_static_secret = X25519StaticSecret::parse_bytes(&static_x25519)?;
|
||||
let static_secret = x25519_static_secret.to_static_secret()?;
|
||||
let inner_ephemeral_public_key: [u8; 32] = opt_result!(
|
||||
ephemeral_public_key_bytes.try_into(), "X25519 public key error: {}");
|
||||
|
||||
Reference in New Issue
Block a user