swift_secure_enclave_tool_rs/
lib.rs

1use base64::Engine;
2use base64::engine::general_purpose::STANDARD;
3use rust_util::{XResult, debugging, opt_result, simple_error};
4use serde::{Deserialize, de};
5use serde_json::Value;
6use std::process::{Command, Output};
7
8const SWIFT_SECURE_ENCLAVE_TOOL_CMD: &str = "swift-secure-enclave-tool-v2";
9
10#[derive(Debug, Clone, Copy)]
11pub enum KeyPurpose {
12    Signing,
13    KeyAgreement,
14}
15
16#[derive(Debug, Clone, Copy)]
17pub enum ControlFlag {
18    None,
19    UserPresence,
20    DevicePasscode,
21    BiometryAny,
22    BiometryCurrentSet,
23}
24
25impl ControlFlag {
26    fn to_str(&self) -> &'static str {
27        match self {
28            ControlFlag::None => "none",
29            ControlFlag::UserPresence => "userPresence",
30            ControlFlag::DevicePasscode => "devicePasscode",
31            ControlFlag::BiometryAny => "biometryAny",
32            ControlFlag::BiometryCurrentSet => "biometryCurrentSet",
33        }
34    }
35}
36
37#[derive(Debug)]
38pub struct KeyMaterial {
39    pub public_key_point: Vec<u8>,
40    pub public_key_der: Vec<u8>,
41    pub private_key_representation: Vec<u8>,
42}
43
44#[derive(Debug, Deserialize)]
45struct ErrorResult {
46    #[allow(dead_code)]
47    pub success: bool,
48    pub error: String,
49}
50
51#[derive(Debug, Deserialize)]
52struct IsSupportSecureEnclaveResult {
53    #[allow(dead_code)]
54    pub success: bool,
55    pub supported: bool,
56}
57
58#[derive(Debug, Deserialize)]
59struct KeyPairResult {
60    #[allow(dead_code)]
61    pub success: bool,
62    pub public_key_point_base64: String,
63    pub public_key_base64: String,
64    pub data_representation_base64: String,
65}
66
67#[derive(Debug, Deserialize)]
68struct SignResult {
69    #[allow(dead_code)]
70    pub success: bool,
71    pub signature_base64: String,
72}
73
74#[derive(Debug, Deserialize)]
75struct DhResult {
76    #[allow(dead_code)]
77    pub success: bool,
78    pub shared_secret_hex: String,
79}
80
81pub fn is_secure_enclave_supported() -> XResult<bool> {
82    let mut cmd = Command::new(SWIFT_SECURE_ENCLAVE_TOOL_CMD);
83    cmd.arg("is_support_secure_enclave");
84
85    let cmd_stdout = run_command_stdout(cmd)?;
86    if is_success(&cmd_stdout)? {
87        let is_support_se_result: IsSupportSecureEnclaveResult = from_str(&cmd_stdout)?;
88        Ok(is_support_se_result.supported)
89    } else {
90        let error_result: ErrorResult = from_str(&cmd_stdout)?;
91        simple_error!("{}", error_result.error)
92    }
93}
94
95pub fn generate_keypair(
96    key_purpose: KeyPurpose,
97    control_flag: ControlFlag,
98) -> XResult<KeyMaterial> {
99    let mut cmd = Command::new(SWIFT_SECURE_ENCLAVE_TOOL_CMD);
100    cmd.arg(match key_purpose {
101        KeyPurpose::Signing => "generate_p256_ecsign_keypair",
102        KeyPurpose::KeyAgreement => "generate_p256_ecdh_keypair",
103    });
104    cmd.arg("--control-flag");
105    cmd.arg(control_flag.to_str());
106
107    let cmd_stdout = run_command_stdout(cmd)?;
108    parse_keypair_result(&cmd_stdout)
109}
110
111pub fn recover_keypair(
112    key_purpose: KeyPurpose,
113    private_key_representation: &[u8],
114) -> XResult<KeyMaterial> {
115    let mut cmd = Command::new(SWIFT_SECURE_ENCLAVE_TOOL_CMD);
116    cmd.arg(match key_purpose {
117        KeyPurpose::Signing => "recover_p256_ecsign_public_key",
118        KeyPurpose::KeyAgreement => "recover_p256_ecdh_public_key",
119    });
120    cmd.arg("--private-key");
121    cmd.arg(STANDARD.encode(private_key_representation));
122
123    let cmd_stdout = run_command_stdout(cmd)?;
124    parse_keypair_result(&cmd_stdout)
125}
126
127pub fn private_key_sign(private_key_representation: &[u8], content: &[u8]) -> XResult<Vec<u8>> {
128    let mut cmd = Command::new(SWIFT_SECURE_ENCLAVE_TOOL_CMD);
129    cmd.arg("compute_p256_ecsign");
130    cmd.arg("--private-key");
131    cmd.arg(STANDARD.encode(private_key_representation));
132    cmd.arg("--message-base64");
133    cmd.arg(STANDARD.encode(content));
134
135    let cmd_stdout = run_command_stdout(cmd)?;
136    parse_sign_result(&cmd_stdout)
137}
138
139// ephemera_public_key MUST be DER format public key
140pub fn private_key_ecdh(
141    private_key_representation: &[u8],
142    ephemera_public_key: &[u8],
143) -> XResult<Vec<u8>> {
144    let mut cmd = Command::new(SWIFT_SECURE_ENCLAVE_TOOL_CMD);
145    cmd.arg("compute_p256_ecdh");
146    cmd.arg("--private-key");
147    cmd.arg(STANDARD.encode(private_key_representation));
148    cmd.arg("--ephemera-public-key");
149    cmd.arg(STANDARD.encode(ephemera_public_key));
150
151    let cmd_stdout = run_command_stdout(cmd)?;
152    parse_dh_result(&cmd_stdout)
153}
154
155pub fn external_sign(external_command: &str, parameter: &str, alg: &str, content: &[u8]) -> XResult<Vec<u8>> {
156    let mut cmd = Command::new(external_command);
157    cmd.arg("external_sign");
158    cmd.arg("--parameter");
159    cmd.arg(parameter);
160    cmd.arg("--alg");
161    cmd.arg(alg);
162    cmd.arg("--message-base64");
163    cmd.arg(STANDARD.encode(content));
164
165    let cmd_stdout = run_command_stdout(cmd)?;
166    parse_sign_result(&cmd_stdout)
167}
168
169pub fn external_ecdh(external_command: &str, parameter: &str, ephemera_public_key: &[u8]) -> XResult<Vec<u8>> {
170    let mut cmd = Command::new(external_command);
171    cmd.arg("external_ecdh");
172    cmd.arg("--parameter");
173    cmd.arg(parameter);
174    cmd.arg("--epk");
175    cmd.arg(STANDARD.encode(ephemera_public_key));
176
177    let cmd_stdout = run_command_stdout(cmd)?;
178    parse_dh_result(&cmd_stdout)
179}
180
181fn parse_sign_result(stdout: &str) -> XResult<Vec<u8>> {
182    if is_success(stdout)? {
183        let sign_result: SignResult = from_str(stdout)?;
184        Ok(STANDARD.decode(&sign_result.signature_base64)?)
185    } else {
186        let error_result: ErrorResult = from_str(stdout)?;
187        simple_error!("{}", error_result.error)
188    }
189}
190
191fn parse_dh_result(stdout: &str) -> XResult<Vec<u8>> {
192    if is_success(stdout)? {
193        let dh_result: DhResult = from_str(stdout)?;
194        Ok(hex::decode(&dh_result.shared_secret_hex)?)
195    } else {
196        let error_result: ErrorResult = from_str(stdout)?;
197        simple_error!("{}", error_result.error)
198    }
199}
200
201fn run_command_stdout(cmd: Command) -> XResult<String> {
202    let output = run_command(cmd)?;
203    let stdout_text = opt_result!(String::from_utf8(output.stdout), "Parse stdout failed:{}");
204    Ok(stdout_text.trim().to_string())
205}
206
207fn run_command(mut cmd: Command) -> XResult<Output> {
208    debugging!("Run command: {:?}", cmd);
209    let output = cmd.output();
210    match output {
211        Err(e) => simple_error!("Run command failed: {:?}", e),
212        Ok(output) => {
213            debugging!("Output: {:?}", output);
214            if !output.status.success() {
215                let stderr = String::from_utf8_lossy(&output.stderr);
216                let stdout = String::from_utf8_lossy(&output.stdout);
217                simple_error!("Run command not success: {:?}\n - stdout: {}\n - stderr: {}", output.status.code(), stdout, stderr)
218            } else {
219                Ok(output)
220            }
221        }
222    }
223}
224
225fn parse_keypair_result(cmd_stdout: &str) -> XResult<KeyMaterial> {
226    if is_success(&cmd_stdout)? {
227        let key_pair_result: KeyPairResult = from_str(&cmd_stdout)?;
228        let public_key_point = STANDARD.decode(&key_pair_result.public_key_point_base64)?;
229        let public_key_der = STANDARD.decode(&key_pair_result.public_key_base64)?;
230        let private_key_representation =
231            STANDARD.decode(&key_pair_result.data_representation_base64)?;
232        Ok(KeyMaterial {
233            public_key_point,
234            public_key_der,
235            private_key_representation,
236        })
237    } else {
238        let error_result: ErrorResult = from_str(&cmd_stdout)?;
239        simple_error!("{}", error_result.error)
240    }
241}
242
243pub fn from_str<'a, T>(s: &'a str) -> XResult<T>
244where
245    T: de::Deserialize<'a>,
246{
247    match serde_json::from_str(s) {
248        Ok(result) => Ok(result),
249        Err(e) => simple_error!("Parse JSON: {}, error: {}", s, e),
250    }
251}
252
253fn is_success(cmd_stdout: &str) -> XResult<bool> {
254    let val = opt_result!(
255        serde_json::from_str::<Value>(cmd_stdout),
256        "Parse result: {}, failed: {}",
257        cmd_stdout
258    );
259    if let Value::Object(map) = val {
260        if let Some(success_value) = map.get("success") {
261            if let Value::Bool(result) = success_value {
262                return Ok(*result);
263            }
264        }
265    }
266    simple_error!("Bad result: {}", cmd_stdout)
267}