From a9684ceec25a98e94296538ceca4a26946c55fc2 Mon Sep 17 00:00:00 2001 From: Hatter Jiang Date: Sat, 27 Sep 2025 20:25:16 +0800 Subject: [PATCH] feat: v1.2.4, ML-KEM ECDH --- Cargo.toml | 2 +- examples/generate_mlkem_keypair.rs | 94 ++++++++++++++++++++++++++++++ src/lib.rs | 64 +++++++++++++++++++- 3 files changed, 157 insertions(+), 3 deletions(-) create mode 100644 examples/generate_mlkem_keypair.rs diff --git a/Cargo.toml b/Cargo.toml index bdfd9c8..0beb948 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "swift-secure-enclave-tool-rs" -version = "1.2.3" +version = "1.2.4" edition = "2024" authors = ["Hatter Jiang"] repository = "https://git.hatter.ink/hatter/swift-secure-enclave-tool-rs" diff --git a/examples/generate_mlkem_keypair.rs b/examples/generate_mlkem_keypair.rs new file mode 100644 index 0000000..242657e --- /dev/null +++ b/examples/generate_mlkem_keypair.rs @@ -0,0 +1,94 @@ +use base64::engine::general_purpose::STANDARD; +use base64::Engine; +use swift_secure_enclave_tool_rs::{generate_mlkem_keypair, is_secure_enclave_supported, private_key_mlkem_ecdh, recover_mlkem_keypair, ControlFlag, KeyMaterial, KeyMlKem}; + +fn main() { + println!( + "Secure Enclave supported: {}", + is_secure_enclave_supported().unwrap() + ); + + let mlkem_key_material = generate_mlkem_keypair(KeyMlKem::MlKem768, ControlFlag::None).unwrap(); + print_key_material("Signing key [none]", &mlkem_key_material); + + let recover_mlkem_key_material = recover_mlkem_keypair( + KeyMlKem::MlKem768, + &mlkem_key_material.private_key_representation, + ) + .unwrap(); + print_key_material("Signing key [none]", &recover_mlkem_key_material); + + // --------------------------------------------------------------------------------------------- + + let mlkem_key_material = + generate_mlkem_keypair(KeyMlKem::MlKem1024, ControlFlag::None).unwrap(); + print_key_material("Signing key [none]", &mlkem_key_material); + + // --------------------------------------------------------------------------------------------- + // ONLY runs on MacBook M4 Air + use base64::engine::general_purpose::STANDARD; + let shared_secret = private_key_mlkem_ecdh(KeyMlKem::MlKem768, + &STANDARD.decode("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\ + JDARvc2duAQEBMAcMAm9hAQEBMAgMA29rZAEBAQ==").unwrap(), + &STANDARD.decode("h47sMRkMBJ4XCF932REsgU6245kAZtLl8OlrtxCKpTxf+g8td\ + VHrXhVGAQyA2cP/YdpI3KdhCnn3xMqAR73Iu6nc4GoEvDHxvI/E3CvBEI+/gfqACWb0VTrX6H\ + ks4tRZ3vDhzkTCwdjXV7udO62eN4JHLUoH3ml5O9rPLZHwP1dEkXIwfWuXY2fjLxpcTuoWhw3\ + 3K3mS4NeGSm1WAJdFojVo8sRiH5NuOI/GXFHY+Hm1CKRou/kQCS+1wN+5qXDDYAf1sVIR7ZdF\ + bSnaWPijzkvVJEHR1cQmerpHThcd+zjrYGBCo6Rd4LlN8cfTuad+d88yTcUwLZfMhL6akTZTu\ + AVQllFMhdxIQQnCsK2iI8/MRCG0tJJm91YKz2voUDOZcQBetULSFzVnsi8fIJ8hhP1yzHJkyU\ + bKiYiJW5uZBJ1ye9SEYvWBTWQdpmEMKuM55vUuZq9tGYkja2oT9rkhrI1bGMy5RN0mauOsHCM\ + 8OvCe8TeggZeAD8INv9sCkBgm9Hk4gjUkIV6e//pshbwSXasgdzB02teP/K5OfmELP+IK75Pz\ + sgPSPWuTfMWlCK4xjE9seCZjqCB8lQ0/gQE6TnKGPPYEjFHP6q89HgDUMDqa4h8YXV2XHWLro\ + LMXrWG/lLkPIPI1vJT3+Hij4eON/3IMK713nEUKzNVwaANtIqP+Mp3zIRORSOFI+KRjdfdGBS\ + 3ciBtVr7rIc+38/uQQKXLl1ELLmc6r569UXgelKy+H4W8koFdMOrJXb1YH7uM+zCV2hfZ8D+R\ + SOETd9NZEjGOHVCLDALbuxwgbwRgBNBDIoS7eIRFo3lIQO0kRlsS5n47UeyqeL2/vKtblGP/p\ + t6OUprgM21JISHLvc9Ezl3UQhl9QHjCdHEiL1W3vzncxluu9Z39bwf7JX9vLafImrPoWKPAu/\ + V89mf6cX6ly/stGzzp3tCxuGUopsNWJ760rXzPkxHqSNOPU9tWxKD94bNJgtztGGMBp7/HGFI\ + 2ftdZE5JU/a+e8R2VLTXAeG6ByhrTmD008KPiKK72QBRYuwC7MvxHLyIn2BhnoHO6KDcUhpLJ\ + sPPWc/odxWLU72XAg2MD3yZnsQodYphVbeQiFH/qUscTTcjSYp0xCeiPoXDwtqyCySKkRHvr6\ + xtbECTTS6M0Q+Ahf7A2vZrEAUKAktPmX15F0ctl13/XMH1766uf8ptSns0vTeO6RaS5Fv1SOh\ + Yc7WXoPhink1SE4VQe47bvWskJ3QJZC7AsEe8dZjoifFCNwK1VYBhvzQXs3ZYcVsjtArjXJOd\ + TJiVKHhz2fcoMjtGIa2UtNRt/YVhfBvwa1LQJWko8wV5YNUuRxx+YNcTEs+rjXfpY1l+9laNs\ + vTCYrPODXud9wFKR8R7zAA1+thSyUvLgdwTylgBp38G74tNBxk70Fh8E9C09D809Clipj7dib\ + vpz1ANsgnqx9yv0=").unwrap() + ).unwrap(); + println!("Shared secret: {}", hex::encode(&shared_secret)); +} + +fn print_key_material(prefix: &str, key_material: &KeyMaterial) { + println!("\n{}", prefix); + println!( + "\nPublic key:\n{}", + STANDARD.encode(&key_material.public_key_der) + ); + println!( + "\nPrivate key representation:\n{}", + STANDARD.encode(&key_material.private_key_representation) + ); +} diff --git a/src/lib.rs b/src/lib.rs index 41e8a27..c5cc636 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,12 @@ pub enum KeyPurpose { KeyAgreement, } +#[derive(Debug, Clone, Copy)] +pub enum KeyMlKem { + MlKem768, + MlKem1024, +} + #[derive(Debug, Clone, Copy)] pub enum ControlFlag { None, @@ -100,7 +106,7 @@ struct IsSupportSecureEnclaveResult { struct KeyPairResult { #[allow(dead_code)] pub success: bool, - pub public_key_point_base64: String, + pub public_key_point_base64: Option, pub public_key_base64: String, pub data_representation_base64: String, } @@ -164,6 +170,22 @@ pub fn generate_keypair( parse_keypair_result(&cmd_stdout) } +pub fn generate_mlkem_keypair( + key_ml_kem: KeyMlKem, + control_flag: ControlFlag, +) -> XResult { + let mut cmd = Command::new(get_swift_secure_enclave_tool_cmd()); + cmd.arg(match key_ml_kem { + KeyMlKem::MlKem768 => "generate_mlkem768_ecdh_keypair", + KeyMlKem::MlKem1024 => "generate_mlkem1024_ecdh_keypair", + }); + cmd.arg("--control-flag"); + cmd.arg(control_flag.to_str()); + + let cmd_stdout = run_command_stdout(cmd)?; + parse_keypair_result(&cmd_stdout) +} + pub fn recover_keypair( key_purpose: KeyPurpose, private_key_representation: &[u8], @@ -180,6 +202,22 @@ pub fn recover_keypair( parse_keypair_result(&cmd_stdout) } +pub fn recover_mlkem_keypair( + key_ml_kem: KeyMlKem, + private_key_representation: &[u8], +) -> XResult { + let mut cmd = Command::new(get_swift_secure_enclave_tool_cmd()); + cmd.arg(match key_ml_kem { + KeyMlKem::MlKem768 => "recover_mlkem768_public_key", + KeyMlKem::MlKem1024 => "recover_mlkem1024_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) +} + pub fn private_key_sign(private_key_representation: &[u8], content: &[u8]) -> XResult> { private_key_sign_digested(private_key_representation, content, DigestType::Raw) } @@ -220,6 +258,25 @@ pub fn private_key_ecdh( parse_ecdh_result(&cmd_stdout) } +pub fn private_key_mlkem_ecdh( + key_ml_kem: KeyMlKem, + private_key_representation: &[u8], + ephemera_public_key: &[u8], +) -> XResult> { + let mut cmd = Command::new(get_swift_secure_enclave_tool_cmd()); + cmd.arg(match key_ml_kem { + KeyMlKem::MlKem768 => "compute_mlkem768_ecdh", + KeyMlKem::MlKem1024 => "compute_mlkem1024_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)?; + parse_ecdh_result(&cmd_stdout) +} + pub fn external_sign( external_command: &str, parameter: &str, @@ -317,7 +374,10 @@ fn run_command(mut cmd: Command) -> XResult { fn parse_keypair_result(cmd_stdout: &str) -> XResult { 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_point = match &key_pair_result.public_key_point_base64 { + Some(public_key_point_base64) => STANDARD.decode(public_key_point_base64)?, + None => vec![], + }; let public_key_der = STANDARD.decode(&key_pair_result.public_key_base64)?; let private_key_representation = STANDARD.decode(&key_pair_result.data_representation_base64)?;