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
81#[derive(Debug, Deserialize)]
82pub struct ExternalSpec {
83    #[allow(dead_code)]
84    pub success: bool,
85    pub agent: String,
86    pub specification: String,
87}
88
89#[derive(Debug, Deserialize)]
90struct ExternalPublicKey {
91    #[allow(dead_code)]
92    pub success: bool,
93    pub public_key_base64: String,
94}
95
96pub fn is_secure_enclave_supported() -> XResult<bool> {
97    let mut cmd = Command::new(SWIFT_SECURE_ENCLAVE_TOOL_CMD);
98    cmd.arg("is_support_secure_enclave");
99
100    let cmd_stdout = run_command_stdout(cmd)?;
101    if is_success(&cmd_stdout)? {
102        let is_support_se_result: IsSupportSecureEnclaveResult = from_str(&cmd_stdout)?;
103        Ok(is_support_se_result.supported)
104    } else {
105        let error_result: ErrorResult = from_str(&cmd_stdout)?;
106        simple_error!("{}", error_result.error)
107    }
108}
109
110pub fn generate_keypair(
111    key_purpose: KeyPurpose,
112    control_flag: ControlFlag,
113) -> XResult<KeyMaterial> {
114    let mut cmd = Command::new(SWIFT_SECURE_ENCLAVE_TOOL_CMD);
115    cmd.arg(match key_purpose {
116        KeyPurpose::Signing => "generate_p256_ecsign_keypair",
117        KeyPurpose::KeyAgreement => "generate_p256_ecdh_keypair",
118    });
119    cmd.arg("--control-flag");
120    cmd.arg(control_flag.to_str());
121
122    let cmd_stdout = run_command_stdout(cmd)?;
123    parse_keypair_result(&cmd_stdout)
124}
125
126pub fn recover_keypair(
127    key_purpose: KeyPurpose,
128    private_key_representation: &[u8],
129) -> XResult<KeyMaterial> {
130    let mut cmd = Command::new(SWIFT_SECURE_ENCLAVE_TOOL_CMD);
131    cmd.arg(match key_purpose {
132        KeyPurpose::Signing => "recover_p256_ecsign_public_key",
133        KeyPurpose::KeyAgreement => "recover_p256_ecdh_public_key",
134    });
135    cmd.arg("--private-key");
136    cmd.arg(STANDARD.encode(private_key_representation));
137
138    let cmd_stdout = run_command_stdout(cmd)?;
139    parse_keypair_result(&cmd_stdout)
140}
141
142pub fn private_key_sign(private_key_representation: &[u8], content: &[u8]) -> XResult<Vec<u8>> {
143    let mut cmd = Command::new(SWIFT_SECURE_ENCLAVE_TOOL_CMD);
144    cmd.arg("compute_p256_ecsign");
145    cmd.arg("--private-key");
146    cmd.arg(STANDARD.encode(private_key_representation));
147    cmd.arg("--message-base64");
148    cmd.arg(STANDARD.encode(content));
149
150    let cmd_stdout = run_command_stdout(cmd)?;
151    parse_sign_result(&cmd_stdout)
152}
153
154// ephemera_public_key MUST be DER format public key
155pub fn private_key_ecdh(
156    private_key_representation: &[u8],
157    ephemera_public_key: &[u8],
158) -> XResult<Vec<u8>> {
159    let mut cmd = Command::new(SWIFT_SECURE_ENCLAVE_TOOL_CMD);
160    cmd.arg("compute_p256_ecdh");
161    cmd.arg("--private-key");
162    cmd.arg(STANDARD.encode(private_key_representation));
163    cmd.arg("--ephemera-public-key");
164    cmd.arg(STANDARD.encode(ephemera_public_key));
165
166    let cmd_stdout = run_command_stdout(cmd)?;
167    parse_ecdh_result(&cmd_stdout)
168}
169
170pub fn external_sign(external_command: &str, parameter: &str, alg: &str, content: &[u8]) -> XResult<Vec<u8>> {
171    let mut cmd = Command::new(external_command);
172    cmd.arg("external_sign");
173    cmd.arg("--parameter");
174    cmd.arg(parameter);
175    cmd.arg("--alg");
176    cmd.arg(alg);
177    cmd.arg("--message-base64");
178    cmd.arg(STANDARD.encode(content));
179
180    let cmd_stdout = run_command_stdout(cmd)?;
181    parse_sign_result(&cmd_stdout)
182}
183
184pub fn external_ecdh(external_command: &str, parameter: &str, ephemera_public_key: &[u8]) -> XResult<Vec<u8>> {
185    let mut cmd = Command::new(external_command);
186    cmd.arg("external_ecdh");
187    cmd.arg("--parameter");
188    cmd.arg(parameter);
189    cmd.arg("--epk");
190    cmd.arg(STANDARD.encode(ephemera_public_key));
191
192    let cmd_stdout = run_command_stdout(cmd)?;
193    parse_ecdh_result(&cmd_stdout)
194}
195
196pub fn external_spec(external_command: &str) -> XResult<ExternalSpec> {
197    let mut cmd = Command::new(external_command);
198    cmd.arg("external_spec");
199
200    let cmd_stdout = run_command_stdout(cmd)?;
201    if is_success(&cmd_stdout)? {
202        let external_spec: ExternalSpec = from_str(&cmd_stdout)?;
203        Ok(external_spec)
204    } else {
205        let error_result: ErrorResult = from_str(&cmd_stdout)?;
206        simple_error!("{}", error_result.error)
207    }
208}
209
210pub fn external_public_key(external_command: &str, parameter: &str) -> XResult<Vec<u8>> {
211    let mut cmd = Command::new(external_command);
212    cmd.arg("external_public_key");
213    cmd.arg("--parameter");
214    cmd.arg(parameter);
215
216    let cmd_stdout = run_command_stdout(cmd)?;
217    if is_success(&cmd_stdout)? {
218        let external_public_key: ExternalPublicKey = from_str(&cmd_stdout)?;
219        Ok(STANDARD.decode(&external_public_key.public_key_base64)?)
220    } else {
221        let error_result: ErrorResult = from_str(&cmd_stdout)?;
222        simple_error!("{}", error_result.error)
223    }
224}
225
226fn run_command_stdout(cmd: Command) -> XResult<String> {
227    let output = run_command(cmd)?;
228    let stdout_text = opt_result!(String::from_utf8(output.stdout), "Parse stdout failed:{}");
229    Ok(stdout_text.trim().to_string())
230}
231
232fn run_command(mut cmd: Command) -> XResult<Output> {
233    debugging!("Run command: {:?}", cmd);
234    let output = cmd.output();
235    match output {
236        Err(e) => simple_error!("Run command failed: {:?}", e),
237        Ok(output) => {
238            debugging!("Output: {:?}", output);
239            if !output.status.success() {
240                let stderr = String::from_utf8_lossy(&output.stderr);
241                let stdout = String::from_utf8_lossy(&output.stdout);
242                simple_error!("Run command not success: {:?}\n - stdout: {}\n - stderr: {}", output.status.code(), stdout, stderr)
243            } else {
244                Ok(output)
245            }
246        }
247    }
248}
249
250fn parse_keypair_result(cmd_stdout: &str) -> XResult<KeyMaterial> {
251    if is_success(&cmd_stdout)? {
252        let key_pair_result: KeyPairResult = from_str(&cmd_stdout)?;
253        let public_key_point = STANDARD.decode(&key_pair_result.public_key_point_base64)?;
254        let public_key_der = STANDARD.decode(&key_pair_result.public_key_base64)?;
255        let private_key_representation =
256            STANDARD.decode(&key_pair_result.data_representation_base64)?;
257        Ok(KeyMaterial {
258            public_key_point,
259            public_key_der,
260            private_key_representation,
261        })
262    } else {
263        let error_result: ErrorResult = from_str(&cmd_stdout)?;
264        simple_error!("{}", error_result.error)
265    }
266}
267
268fn parse_sign_result(stdout: &str) -> XResult<Vec<u8>> {
269    if is_success(stdout)? {
270        let sign_result: SignResult = from_str(stdout)?;
271        Ok(STANDARD.decode(&sign_result.signature_base64)?)
272    } else {
273        let error_result: ErrorResult = from_str(stdout)?;
274        simple_error!("{}", error_result.error)
275    }
276}
277
278fn parse_ecdh_result(stdout: &str) -> XResult<Vec<u8>> {
279    if is_success(stdout)? {
280        let dh_result: DhResult = from_str(stdout)?;
281        Ok(hex::decode(&dh_result.shared_secret_hex)?)
282    } else {
283        let error_result: ErrorResult = from_str(stdout)?;
284        simple_error!("{}", error_result.error)
285    }
286}
287
288pub fn from_str<'a, T>(s: &'a str) -> XResult<T>
289where
290    T: de::Deserialize<'a>,
291{
292    match serde_json::from_str(s) {
293        Ok(result) => Ok(result),
294        Err(e) => simple_error!("Parse JSON: {}, error: {}", s, e),
295    }
296}
297
298fn is_success(cmd_stdout: &str) -> XResult<bool> {
299    let val = opt_result!(
300        serde_json::from_str::<Value>(cmd_stdout),
301        "Parse result: {}, failed: {}",
302        cmd_stdout
303    );
304    if let Value::Object(map) = val {
305        if let Some(success_value) = map.get("success") {
306            if let Value::Bool(result) = success_value {
307                return Ok(*result);
308            }
309        }
310    }
311    simple_error!("Bad result: {}", cmd_stdout)
312}