diff --git a/.gitignore b/.gitignore index 0eabc4c..ad19215 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.idea/ # ---> Rust # Generated by Cargo # will have compiled files and executables diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..a26a3b4 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "swift-secure-enclave-tool-rs" +version = "0.1.0" +edition = "2024" +authors = ["Hatter Jiang"] +repository = "https://git.hatter.ink/hatter/swift-secure-enclave-tool-rs" +description = "Swift secure enclave tool in Rust" +license = "MIT OR Apache-2.0" +keywords = ["crypto", "ecdsa", "ecdh", "secure-enclave"] +categories = ["cryptography"] + +[dependencies] +base64 = "0.22.1" +rust_util = "0.6.47" diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..33c0221 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,133 @@ +use base64::engine::general_purpose::STANDARD; +use base64::Engine; +use rust_util::{debugging, opt_result, simple_error, XResult}; +use std::process::{Command, Output}; + +const SWIFT_SECURE_ENCLAVE_TOOL_CMD: &str = "swift-secure-enclave-tool"; + +#[derive(Debug, Clone, Copy)] +pub enum KeyPurpose { + Signing, + KeyAgreement, +} + +#[derive(Debug)] +pub struct KeyMaterial { + pub public_key_point: Vec, + pub public_key_der: Vec, + pub private_key_representation: Vec, +} + +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") + } else { + simple_error!("Invalid is_secure_enclave_supported result: {}", cmd_stdout) + } +} + +pub fn generate_ecdsa_keypair(key_purpose: KeyPurpose, require_bio: bool) -> 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)); + + let cmd_stdout = run_command_stdout(cmd)?; + parse_keypair_result(&cmd_stdout) +} + +pub fn recover_ecdsa_keypair( + key_purpose: KeyPurpose, + private_key_representation: &[u8], +) -> XResult { + let mut cmd = Command::new(SWIFT_SECURE_ENCLAVE_TOOL_CMD); + cmd.arg(match key_purpose { + KeyPurpose::Signing => "recover_p256_ecsign_public_key", + KeyPurpose::KeyAgreement => "recover_p256_ecdh_public_key", + }); + cmd.arg(&STANDARD.encode(private_key_representation)); + + let cmd_stdout = run_command_stdout(cmd)?; + parse_keypair_result(&cmd_stdout) +} + +pub fn private_key_ecdsa_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(&STANDARD.encode(private_key_representation)); + 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)?) + } else { + simple_error!("Invalid private_key_ecdsa_sign result: {}", cmd_stdout) + } +} + +pub fn private_key_ecdh( + private_key_representation: &[u8], + ephemera_public_key: &[u8], +) -> XResult> { + let mut cmd = Command::new(SWIFT_SECURE_ENCLAVE_TOOL_CMD); + cmd.arg("compute_p256_ecdh"); + cmd.arg(&STANDARD.encode(private_key_representation)); + cmd.arg(&STANDARD.encode(ephemera_public_key)); + + 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)?) + } else { + simple_error!("Invalid compute_p256_ecdh result: {}", cmd_stdout) + } +} + +fn run_command_stdout(cmd: Command) -> XResult { + let output = run_command(cmd)?; + let stdout_text = opt_result!(String::from_utf8(output.stdout), "Parse stdout failed:{}"); + Ok(stdout_text) +} + +fn run_command(mut cmd: Command) -> XResult { + debugging!("Run command: {:?}", cmd); + let output = cmd.output(); + match output { + Err(e) => simple_error!("Run command failed: {:?}", e), + Ok(output) => { + if !output.status.success() { + simple_error!("Run command not success: {:?}", output.status.code()) + } else { + Ok(output) + } + } + } +} + +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])?; + Ok(KeyMaterial { + public_key_point, + public_key_der, + private_key_representation, + }) + } else { + simple_error!("Invalid result: {}", cmd_stdout) + } +}