feat: v1.5.0, store secure enclave private key to keychain

This commit is contained in:
2023-12-14 21:56:30 +08:00
parent c30d061f53
commit 5bdc4c69e6
5 changed files with 122 additions and 35 deletions

View File

@@ -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 };

View File

@@ -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,
};

View File

@@ -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: {}");