feat: v1.5.0, store secure enclave private key to keychain
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -1700,7 +1700,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tiny-encrypt"
|
name = "tiny-encrypt"
|
||||||
version = "1.4.5"
|
version = "1.5.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes-gcm-stream",
|
"aes-gcm-stream",
|
||||||
"base64",
|
"base64",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "tiny-encrypt"
|
name = "tiny-encrypt"
|
||||||
version = "1.4.5"
|
version = "1.5.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
description = "A simple and tiny file encrypt tool"
|
description = "A simple and tiny file encrypt tool"
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ use crate::util_digest::DigestWrite;
|
|||||||
use crate::util_keychainkey;
|
use crate::util_keychainkey;
|
||||||
#[cfg(feature = "macos")]
|
#[cfg(feature = "macos")]
|
||||||
use crate::util_keychainstatic;
|
use crate::util_keychainstatic;
|
||||||
|
use crate::util_keychainstatic::KeychainKey;
|
||||||
use crate::util_progress::Progress;
|
use crate::util_progress::Progress;
|
||||||
use crate::wrap_key::WrapKey;
|
use crate::wrap_key::WrapKey;
|
||||||
|
|
||||||
@@ -542,10 +543,16 @@ fn try_decrypt_se_key_ecdh(config: &Option<TinyEncryptConfig>,
|
|||||||
if config_envelop_args.is_empty() {
|
if config_envelop_args.is_empty() {
|
||||||
return simple_error!("Not enough arguments for: {}", &envelop.kid);
|
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(
|
let shared_secret = opt_result!(util_keychainkey::decrypt_data(
|
||||||
private_key_base64,
|
&private_key_base64,
|
||||||
&e_pub_key_bytes
|
&e_pub_key_bytes
|
||||||
), "Decrypt via secure enclave failed: {}");
|
), "Decrypt via secure enclave failed: {}");
|
||||||
let key = util::simple_kdf(shared_secret.as_slice());
|
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!(
|
let config_envelop = opt_value_result!(
|
||||||
config.find_by_kid(&envelop.kid), "Cannot find config for: {}", &envelop.kid);
|
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);
|
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);
|
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!(
|
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 = util::simple_kdf(shared_secret.as_slice());
|
||||||
let key_nonce = KeyNonce { k: &key, n: &wrap_key.nonce };
|
let key_nonce = KeyNonce { k: &key, n: &wrap_key.nonce };
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
use clap::Args;
|
use clap::Args;
|
||||||
use rust_util::{debugging, information, opt_result, opt_value_result, simple_error, success, warning, XResult};
|
use rust_util::{debugging, information, opt_result, simple_error, success, warning, XResult};
|
||||||
use security_framework::os::macos::keychain::SecKeychain;
|
|
||||||
|
|
||||||
use crate::config::TinyEncryptConfigEnvelop;
|
use crate::config::TinyEncryptConfigEnvelop;
|
||||||
use crate::spec::TinyEncryptEnvelopType;
|
use crate::spec::TinyEncryptEnvelopType;
|
||||||
#[cfg(feature = "secure-enclave")]
|
#[cfg(feature = "secure-enclave")]
|
||||||
use crate::util_keychainkey;
|
use crate::util_keychainkey;
|
||||||
use crate::util_keychainstatic;
|
use crate::util_keychainstatic;
|
||||||
use crate::util_keychainstatic::X25519StaticSecret;
|
use crate::util_keychainstatic::{KeychainKey, X25519StaticSecret};
|
||||||
|
|
||||||
#[derive(Debug, Args)]
|
#[derive(Debug, Args)]
|
||||||
pub struct CmdInitKeychain {
|
pub struct CmdInitKeychain {
|
||||||
@@ -22,7 +21,7 @@ pub struct CmdInitKeychain {
|
|||||||
pub server_name: Option<String>,
|
pub server_name: Option<String>,
|
||||||
/// Key name
|
/// Key name
|
||||||
#[arg(long, short = 'n')]
|
#[arg(long, short = 'n')]
|
||||||
pub key_name: Option<String>,
|
pub key_name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_SERVICE_NAME: &str = "tiny-encrypt";
|
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() {
|
if !util_keychainkey::is_support_se() {
|
||||||
return simple_error!("Secure enclave is not supported.");
|
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_hex, private_key_base64) = util_keychainkey::generate_se_p256_keypair()?;
|
||||||
let public_key_compressed_hex = public_key_hex.chars()
|
let public_key_compressed_hex = public_key_hex.chars()
|
||||||
.skip(2).take(public_key_hex.len() / 2 - 1).collect::<String>();
|
.skip(2).take(public_key_hex.len() / 2 - 1).collect::<String>();
|
||||||
|
|
||||||
|
keychain_key.set_password(private_key_base64.as_bytes())?;
|
||||||
|
|
||||||
let config_envelop = TinyEncryptConfigEnvelop {
|
let config_envelop = TinyEncryptConfigEnvelop {
|
||||||
r#type: TinyEncryptEnvelopType::KeyP256,
|
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),
|
kid: format!("keychain:02{}", &public_key_compressed_hex),
|
||||||
desc: Some("Keychain Secure Enclave".to_string()),
|
desc: Some("Keychain Secure Enclave".to_string()),
|
||||||
args: Some(vec![
|
args: Some(vec![keychain_key.to_str()]),
|
||||||
private_key_base64
|
|
||||||
]),
|
|
||||||
public_part: public_key_hex,
|
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<()> {
|
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 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 = &cmd_init_keychain.key_name;
|
||||||
let key_name = opt_value_result!(&cmd_init_keychain.key_name, "Key name is required.");
|
let keychain_key = KeychainKey::from("", service_name, key_name);
|
||||||
|
|
||||||
let public_key = match sec_keychain.find_generic_password(service_name, key_name) {
|
let public_key = match keychain_key.get_password()? {
|
||||||
Ok((static_x25519, _)) => {
|
Some(static_x25519) => {
|
||||||
warning!("Key already exists: {}.{}", service_name, key_name);
|
warning!("Key already exists: {}.{}", service_name, key_name);
|
||||||
let x25519_static_secret = X25519StaticSecret::parse_bytes(static_x25519.as_ref())?;
|
let x25519_static_secret = X25519StaticSecret::parse_bytes(static_x25519.as_ref())?;
|
||||||
x25519_static_secret.to_public_key()?
|
x25519_static_secret.to_public_key()?
|
||||||
}
|
}
|
||||||
Err(_) => {
|
None => {
|
||||||
let (keychain_key, public_key) = util_keychainstatic::generate_static_x25519_secret();
|
let (keychain_key_bytes, public_key) = util_keychainstatic::generate_static_x25519_secret();
|
||||||
debugging!("Keychain key : {}", keychain_key);
|
debugging!("Keychain key : {}", keychain_key_bytes);
|
||||||
opt_result!(
|
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: {}"
|
"Write static x25519 failed: {}"
|
||||||
);
|
);
|
||||||
public_key
|
public_key
|
||||||
@@ -94,11 +98,7 @@ pub fn keychain_key_static(cmd_init_keychain: CmdInitKeychain) -> XResult<()> {
|
|||||||
sid: Some(key_name.clone()),
|
sid: Some(key_name.clone()),
|
||||||
kid: format!("keychain:{}", &public_key_hex),
|
kid: format!("keychain:{}", &public_key_hex),
|
||||||
desc: Some("Keychain static".to_string()),
|
desc: Some("Keychain static".to_string()),
|
||||||
args: Some(vec![
|
args: Some(vec![keychain_key.to_str()]),
|
||||||
"".to_string(),
|
|
||||||
service_name.to_string(),
|
|
||||||
key_name.clone(),
|
|
||||||
]),
|
|
||||||
public_part: public_key_hex,
|
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 security_framework::os::macos::keychain::SecKeychain;
|
||||||
use x25519_dalek::{PublicKey, StaticSecret};
|
use x25519_dalek::{PublicKey, StaticSecret};
|
||||||
use zeroize::Zeroize;
|
use zeroize::Zeroize;
|
||||||
|
|
||||||
const X2559_PLAIN_PREFIX: &str = "x25519-plain:";
|
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 struct X25519StaticSecret {
|
||||||
pub secret: Vec<u8>,
|
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>> {
|
impl KeychainKey {
|
||||||
let sec_keychain = opt_result!(SecKeychain::default(), "Get keychain failed: {}");
|
pub fn from(keychain_name: &str, service_name: &str, key_name: &str) -> Self {
|
||||||
let (static_x25519, _) = opt_result!(sec_keychain.find_generic_password(service_name, key_name),
|
Self {
|
||||||
"Cannot find static x25519 {}.{}: {}", service_name, key_name);
|
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 static_secret = x25519_static_secret.to_static_secret()?;
|
||||||
let inner_ephemeral_public_key: [u8; 32] = opt_result!(
|
let inner_ephemeral_public_key: [u8; 32] = opt_result!(
|
||||||
ephemeral_public_key_bytes.try_into(), "X25519 public key error: {}");
|
ephemeral_public_key_bytes.try_into(), "X25519 public key error: {}");
|
||||||
|
|||||||
Reference in New Issue
Block a user