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