feat: v1.0.0
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
170
src/lib.rs
170
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<u8>,
|
||||
@@ -18,44 +41,73 @@ pub struct KeyMaterial {
|
||||
pub private_key_representation: Vec<u8>,
|
||||
}
|
||||
|
||||
#[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<bool> {
|
||||
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::<String>();
|
||||
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<KeyMaterial> {
|
||||
generate_keypair(key_purpose, require_bio)
|
||||
}
|
||||
|
||||
pub fn generate_keypair(key_purpose: KeyPurpose, require_bio: bool) -> XResult<KeyMaterial> {
|
||||
pub fn generate_keypair(
|
||||
key_purpose: KeyPurpose,
|
||||
control_flag: ControlFlag,
|
||||
) -> XResult<KeyMaterial> {
|
||||
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<KeyMaterial> {
|
||||
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<Vec<u8>> {
|
||||
private_key_sign(private_key_representation, content)
|
||||
}
|
||||
|
||||
pub fn private_key_sign(private_key_representation: &[u8], content: &[u8]) -> XResult<Vec<u8>> {
|
||||
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::<String>();
|
||||
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<Vec<u8>> {
|
||||
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::<String>();
|
||||
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<Output> {
|
||||
}
|
||||
|
||||
fn parse_keypair_result(cmd_stdout: &str) -> XResult<KeyMaterial> {
|
||||
if cmd_stdout.starts_with("ok:") {
|
||||
let result = cmd_stdout.chars().skip(3).collect::<String>();
|
||||
let parts = result.split(",").collect::<Vec<_>>();
|
||||
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<T>
|
||||
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<bool> {
|
||||
let val = opt_result!(
|
||||
serde_json::from_str::<Value>(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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user