From f3bc06cc21e53d4b52f63d34642b3a89cc82c551 Mon Sep 17 00:00:00 2001 From: Hatter Jiang Date: Mon, 12 May 2025 23:18:46 +0800 Subject: [PATCH] feat: v1.0.0 --- Cargo.toml | 4 +- examples/generate_keypair.rs | 14 +-- src/lib.rs | 170 +++++++++++++++++++++++++---------- 3 files changed, 137 insertions(+), 51 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8ba5040..ff8e09a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "swift-secure-enclave-tool-rs" -version = "0.1.1" +version = "1.0.0" edition = "2024" authors = ["Hatter Jiang"] repository = "https://git.hatter.ink/hatter/swift-secure-enclave-tool-rs" @@ -13,3 +13,5 @@ categories = ["cryptography"] hex = "0.4.3" base64 = "0.22.1" rust_util = "0.6.47" +serde = { version = "1.0.219", features = ["derive"] } +serde_json = "1.0.140" diff --git a/examples/generate_keypair.rs b/examples/generate_keypair.rs index 11cb37b..b3d9511 100644 --- a/examples/generate_keypair.rs +++ b/examples/generate_keypair.rs @@ -1,7 +1,7 @@ use base64::engine::general_purpose::STANDARD; use base64::Engine; use swift_secure_enclave_tool_rs::{ - generate_keypair, is_secure_enclave_supported, KeyMaterial, KeyPurpose, + generate_keypair, is_secure_enclave_supported, ControlFlag, KeyMaterial, KeyPurpose, }; fn main() { @@ -10,19 +10,23 @@ fn main() { is_secure_enclave_supported().unwrap() ); - let ecdsa_key_material_require_bio = generate_keypair(KeyPurpose::Signing, true).unwrap(); + let ecdsa_key_material_require_bio = + generate_keypair(KeyPurpose::Signing, ControlFlag::None).unwrap(); print_key_material("Signing key [require bio]", &ecdsa_key_material_require_bio); - let ecdsa_key_material_no_bio = generate_keypair(KeyPurpose::Signing, true).unwrap(); + let ecdsa_key_material_no_bio = + generate_keypair(KeyPurpose::Signing, ControlFlag::None).unwrap(); print_key_material("Signing key [no bio]", &ecdsa_key_material_no_bio); - let ecdsa_key_material_require_bio = generate_keypair(KeyPurpose::KeyAgreement, true).unwrap(); + let ecdsa_key_material_require_bio = + generate_keypair(KeyPurpose::KeyAgreement, ControlFlag::None).unwrap(); print_key_material( "Key agreement key [require bio]", &ecdsa_key_material_require_bio, ); - let ecdsa_key_material_no_bio = generate_keypair(KeyPurpose::KeyAgreement, true).unwrap(); + let ecdsa_key_material_no_bio = + generate_keypair(KeyPurpose::KeyAgreement, ControlFlag::None).unwrap(); print_key_material("Key agreement key [no bio]", &ecdsa_key_material_no_bio); } diff --git a/src/lib.rs b/src/lib.rs index 4e92918..ba336fd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,11 @@ -use base64::engine::general_purpose::STANDARD; use base64::Engine; -use rust_util::{debugging, opt_result, simple_error, XResult}; +use base64::engine::general_purpose::STANDARD; +use rust_util::{XResult, debugging, opt_result, simple_error}; +use serde::{Deserialize, de}; +use serde_json::Value; use std::process::{Command, Output}; -const SWIFT_SECURE_ENCLAVE_TOOL_CMD: &str = "swift-secure-enclave-tool"; +const SWIFT_SECURE_ENCLAVE_TOOL_CMD: &str = "swift-secure-enclave-tool-v2"; #[derive(Debug, Clone, Copy)] pub enum KeyPurpose { @@ -11,6 +13,27 @@ pub enum KeyPurpose { KeyAgreement, } +#[derive(Debug, Clone, Copy)] +pub enum ControlFlag { + None, + UserPresence, + DevicePasscode, + BiometryAny, + BiometryCurrentSet, +} + +impl ControlFlag { + fn to_str(&self) -> &'static str { + match self { + ControlFlag::None => "none", + ControlFlag::UserPresence => "userPresence", + ControlFlag::DevicePasscode => "devicePasscode", + ControlFlag::BiometryAny => "biometryAny", + ControlFlag::BiometryCurrentSet => "biometryCurrentSet", + } + } +} + #[derive(Debug)] pub struct KeyMaterial { pub public_key_point: Vec, @@ -18,44 +41,73 @@ pub struct KeyMaterial { pub private_key_representation: Vec, } +#[derive(Debug, Deserialize)] +struct ErrorResult { + #[allow(dead_code)] + pub success: bool, + pub error: String, +} + +#[derive(Debug, Deserialize)] +struct IsSupportSecureEnclaveResult { + #[allow(dead_code)] + pub success: bool, + pub supported: bool, +} + +#[derive(Debug, Deserialize)] +struct KeyPairResult { + #[allow(dead_code)] + pub success: bool, + pub public_key_point_base64: String, + pub public_key_base64: String, + pub data_representation_base64: String, +} + +#[derive(Debug, Deserialize)] +struct SignResult { + #[allow(dead_code)] + pub success: bool, + pub signature_base64: String, +} + +#[derive(Debug, Deserialize)] +struct DhResult { + #[allow(dead_code)] + pub success: bool, + pub shared_secret_hex: String, +} + pub fn is_secure_enclave_supported() -> XResult { let mut cmd = Command::new(SWIFT_SECURE_ENCLAVE_TOOL_CMD); cmd.arg("is_support_secure_enclave"); let cmd_stdout = run_command_stdout(cmd)?; - if cmd_stdout.starts_with("ok:") { - let result = cmd_stdout.chars().skip(3).collect::(); - Ok(result == "true") + if is_success(&cmd_stdout)? { + let is_support_se_result: IsSupportSecureEnclaveResult = from_str(&cmd_stdout)?; + Ok(is_support_se_result.supported) } else { - simple_error!("Invalid is_secure_enclave_supported result: {}", cmd_stdout) + let error_result: ErrorResult = from_str(&cmd_stdout)?; + simple_error!("{}", error_result.error) } } -#[deprecated] -pub fn generate_ecdsa_keypair(key_purpose: KeyPurpose, require_bio: bool) -> XResult { - generate_keypair(key_purpose, require_bio) -} - -pub fn generate_keypair(key_purpose: KeyPurpose, require_bio: bool) -> XResult { +pub fn generate_keypair( + key_purpose: KeyPurpose, + control_flag: ControlFlag, +) -> XResult { let mut cmd = Command::new(SWIFT_SECURE_ENCLAVE_TOOL_CMD); cmd.arg(match key_purpose { KeyPurpose::Signing => "generate_p256_ecsign_keypair", KeyPurpose::KeyAgreement => "generate_p256_ecdh_keypair", }); - cmd.arg(format!("{}", require_bio)); + cmd.arg("--control-flag"); + cmd.arg(control_flag.to_str()); let cmd_stdout = run_command_stdout(cmd)?; parse_keypair_result(&cmd_stdout) } -#[deprecated] -pub fn recover_ecdsa_keypair( - key_purpose: KeyPurpose, - private_key_representation: &[u8], -) -> XResult { - recover_keypair(key_purpose, private_key_representation) -} - pub fn recover_keypair( key_purpose: KeyPurpose, private_key_representation: &[u8], @@ -65,32 +117,29 @@ pub fn recover_keypair( KeyPurpose::Signing => "recover_p256_ecsign_public_key", KeyPurpose::KeyAgreement => "recover_p256_ecdh_public_key", }); + cmd.arg("--private-key"); cmd.arg(STANDARD.encode(private_key_representation)); let cmd_stdout = run_command_stdout(cmd)?; parse_keypair_result(&cmd_stdout) } -#[deprecated] -pub fn private_key_ecdsa_sign( - private_key_representation: &[u8], - content: &[u8], -) -> XResult> { - private_key_sign(private_key_representation, content) -} - pub fn private_key_sign(private_key_representation: &[u8], content: &[u8]) -> XResult> { let mut cmd = Command::new(SWIFT_SECURE_ENCLAVE_TOOL_CMD); cmd.arg("compute_p256_ecsign"); + cmd.arg("--private-key"); cmd.arg(STANDARD.encode(private_key_representation)); + cmd.arg("--message-base64"); cmd.arg(STANDARD.encode(content)); let cmd_stdout = run_command_stdout(cmd)?; - if cmd_stdout.starts_with("ok:") { - let result = cmd_stdout.chars().skip(3).collect::(); - Ok(STANDARD.decode(result)?) + + if is_success(&cmd_stdout)? { + let sign_result: SignResult = from_str(&cmd_stdout)?; + Ok(STANDARD.decode(&sign_result.signature_base64)?) } else { - simple_error!("Invalid private_key_ecdsa_sign result: {}", cmd_stdout) + let error_result: ErrorResult = from_str(&cmd_stdout)?; + simple_error!("{}", error_result.error) } } @@ -101,15 +150,19 @@ pub fn private_key_ecdh( ) -> XResult> { let mut cmd = Command::new(SWIFT_SECURE_ENCLAVE_TOOL_CMD); cmd.arg("compute_p256_ecdh"); + cmd.arg("--private-key"); cmd.arg(STANDARD.encode(private_key_representation)); + cmd.arg("--ephemera-public-key"); cmd.arg(STANDARD.encode(ephemera_public_key)); let cmd_stdout = run_command_stdout(cmd)?; - if cmd_stdout.starts_with("ok:SharedSecret:") { - let result = cmd_stdout.chars().skip(16).collect::(); - Ok(hex::decode(result.trim())?) + + if is_success(&cmd_stdout)? { + let dh_result: DhResult = from_str(&cmd_stdout)?; + Ok(hex::decode(&dh_result.shared_secret_hex)?) } else { - simple_error!("Invalid compute_p256_ecdh result: {}", cmd_stdout) + let error_result: ErrorResult = from_str(&cmd_stdout)?; + simple_error!("{}", error_result.error) } } @@ -136,18 +189,45 @@ fn run_command(mut cmd: Command) -> XResult { } fn parse_keypair_result(cmd_stdout: &str) -> XResult { - if cmd_stdout.starts_with("ok:") { - let result = cmd_stdout.chars().skip(3).collect::(); - let parts = result.split(",").collect::>(); - let public_key_point = STANDARD.decode(parts[0])?; - let public_key_der = STANDARD.decode(parts[1])?; - let private_key_representation = STANDARD.decode(parts[2])?; + if is_success(&cmd_stdout)? { + let key_pair_result: KeyPairResult = from_str(&cmd_stdout)?; + let public_key_point = STANDARD.decode(&key_pair_result.public_key_point_base64)?; + let public_key_der = STANDARD.decode(&key_pair_result.public_key_base64)?; + let private_key_representation = + STANDARD.decode(&key_pair_result.data_representation_base64)?; Ok(KeyMaterial { public_key_point, public_key_der, private_key_representation, }) } else { - simple_error!("Invalid result: {}", cmd_stdout) + let error_result: ErrorResult = from_str(&cmd_stdout)?; + simple_error!("{}", error_result.error) } } + +pub fn from_str<'a, T>(s: &'a str) -> XResult +where + T: de::Deserialize<'a>, +{ + match serde_json::from_str(s) { + Ok(result) => Ok(result), + Err(e) => simple_error!("Parse JSON: {}, error: {}", s, e), + } +} + +fn is_success(cmd_stdout: &str) -> XResult { + let val = opt_result!( + serde_json::from_str::(cmd_stdout), + "Parse result: {}, failed: {}", + cmd_stdout + ); + if let Value::Object(map) = val { + if let Some(success_value) = map.get("success") { + if let Value::Bool(result) = success_value { + return Ok(*result); + } + } + } + simple_error!("Bad result: {}", cmd_stdout) +}