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, util_env};
4use serde::{Deserialize, de};
5use serde_json::Value;
6use std::process::{Command, Output};
7
8const ENV_SWIFT_SECURE_ENCLAVE_TOOL_CMD_KEY: &str = "SWIFT_SECURE_ENCLAVE_TOOL_CMD";
9const DEFAULT_SWIFT_SECURE_ENCLAVE_TOOL_CMD: &str = "swift-secure-enclave-tool-v2";
10
11#[derive(Debug, Clone, Copy)]
12pub enum KeyPurpose {
13    Signing,
14    KeyAgreement,
15}
16
17#[derive(Debug, Clone, Copy)]
18pub enum KeyMlKem {
19    MlKem768,
20    MlKem1024,
21}
22
23#[derive(Debug, Clone, Copy)]
24pub enum ControlFlag {
25    None,
26    UserPresence,
27    DevicePasscode,
28    BiometryAny,
29    BiometryCurrentSet,
30}
31
32impl ControlFlag {
33    fn to_str(&self) -> &'static str {
34        match self {
35            ControlFlag::None => "none",
36            ControlFlag::UserPresence => "userPresence",
37            ControlFlag::DevicePasscode => "devicePasscode",
38            ControlFlag::BiometryAny => "biometryAny",
39            ControlFlag::BiometryCurrentSet => "biometryCurrentSet",
40        }
41    }
42}
43
44#[derive(Debug, Clone, Copy, Eq, PartialEq)]
45pub enum DigestType {
46    Raw,
47    Sha256,
48    Sha384,
49    Sha512,
50}
51
52impl DigestType {
53    pub fn to_str(&self) -> &'static str {
54        match self {
55            DigestType::Raw => "raw",
56            DigestType::Sha256 => "sha256",
57            DigestType::Sha384 => "sha384",
58            DigestType::Sha512 => "sha512",
59        }
60    }
61
62    pub fn bytes(&self) -> Option<u32> {
63        match self {
64            DigestType::Raw => None,
65            DigestType::Sha256 => Some(256 / 8),
66            DigestType::Sha384 => Some(384 / 8),
67            DigestType::Sha512 => Some(512 / 8),
68        }
69    }
70
71    pub fn parse(t: Option<&str>) -> XResult<DigestType> {
72        Ok(match t {
73            None | Some("") | Some("raw") => DigestType::Raw,
74            Some("sha256") => DigestType::Sha256,
75            Some("sha384") => DigestType::Sha384,
76            Some("sha512") => DigestType::Sha512,
77            Some(other_digest_type) => {
78                return simple_error!("Invalid digest type: {}", other_digest_type);
79            }
80        })
81    }
82}
83
84#[derive(Debug)]
85pub struct KeyMaterial {
86    pub public_key_point: Vec<u8>,
87    pub public_key_der: Vec<u8>,
88    pub private_key_representation: Vec<u8>,
89}
90
91#[derive(Debug, Deserialize)]
92struct ErrorResult {
93    #[allow(dead_code)]
94    pub success: bool,
95    pub error: String,
96}
97
98#[derive(Debug, Deserialize)]
99struct IsSupportSecureEnclaveResult {
100    #[allow(dead_code)]
101    pub success: bool,
102    pub supported: bool,
103}
104
105#[derive(Debug, Deserialize)]
106struct KeyPairResult {
107    #[allow(dead_code)]
108    pub success: bool,
109    pub public_key_point_base64: Option<String>,
110    pub public_key_base64: String,
111    pub data_representation_base64: String,
112}
113
114#[derive(Debug, Deserialize)]
115struct SignResult {
116    #[allow(dead_code)]
117    pub success: bool,
118    pub signature_base64: String,
119}
120
121#[derive(Debug, Deserialize)]
122struct DhResult {
123    #[allow(dead_code)]
124    pub success: bool,
125    pub shared_secret_hex: String,
126}
127
128#[derive(Debug, Deserialize)]
129pub struct ExternalSpec {
130    #[allow(dead_code)]
131    pub success: bool,
132    pub agent: String,
133    pub specification: String,
134}
135
136#[derive(Debug, Deserialize)]
137struct ExternalPublicKey {
138    #[allow(dead_code)]
139    pub success: bool,
140    pub public_key_base64: String,
141}
142
143pub fn is_secure_enclave_supported() -> XResult<bool> {
144    let mut cmd = Command::new(get_swift_secure_enclave_tool_cmd());
145    cmd.arg("is_support_secure_enclave");
146
147    let cmd_stdout = run_command_stdout(cmd)?;
148    if is_success(&cmd_stdout)? {
149        let is_support_se_result: IsSupportSecureEnclaveResult = from_str(&cmd_stdout)?;
150        Ok(is_support_se_result.supported)
151    } else {
152        let error_result: ErrorResult = from_str(&cmd_stdout)?;
153        simple_error!("{}", error_result.error)
154    }
155}
156
157pub fn generate_keypair(
158    key_purpose: KeyPurpose,
159    control_flag: ControlFlag,
160) -> XResult<KeyMaterial> {
161    let mut cmd = Command::new(get_swift_secure_enclave_tool_cmd());
162    cmd.arg(match key_purpose {
163        KeyPurpose::Signing => "generate_p256_ecsign_keypair",
164        KeyPurpose::KeyAgreement => "generate_p256_ecdh_keypair",
165    });
166    cmd.arg("--control-flag");
167    cmd.arg(control_flag.to_str());
168
169    let cmd_stdout = run_command_stdout(cmd)?;
170    parse_keypair_result(&cmd_stdout)
171}
172
173pub fn generate_mlkem_keypair(
174    key_ml_kem: KeyMlKem,
175    control_flag: ControlFlag,
176) -> XResult<KeyMaterial> {
177    let mut cmd = Command::new(get_swift_secure_enclave_tool_cmd());
178    cmd.arg(match key_ml_kem {
179        KeyMlKem::MlKem768 => "generate_mlkem768_ecdh_keypair",
180        KeyMlKem::MlKem1024 => "generate_mlkem1024_ecdh_keypair",
181    });
182    cmd.arg("--control-flag");
183    cmd.arg(control_flag.to_str());
184
185    let cmd_stdout = run_command_stdout(cmd)?;
186    parse_keypair_result(&cmd_stdout)
187}
188
189pub fn recover_keypair(
190    key_purpose: KeyPurpose,
191    private_key_representation: &[u8],
192) -> XResult<KeyMaterial> {
193    let mut cmd = Command::new(get_swift_secure_enclave_tool_cmd());
194    cmd.arg(match key_purpose {
195        KeyPurpose::Signing => "recover_p256_ecsign_public_key",
196        KeyPurpose::KeyAgreement => "recover_p256_ecdh_public_key",
197    });
198    cmd.arg("--private-key");
199    cmd.arg(STANDARD.encode(private_key_representation));
200
201    let cmd_stdout = run_command_stdout(cmd)?;
202    parse_keypair_result(&cmd_stdout)
203}
204
205pub fn recover_mlkem_keypair(
206    key_ml_kem: KeyMlKem,
207    private_key_representation: &[u8],
208) -> XResult<KeyMaterial> {
209    let mut cmd = Command::new(get_swift_secure_enclave_tool_cmd());
210    cmd.arg(match key_ml_kem {
211        KeyMlKem::MlKem768 => "recover_mlkem768_public_key",
212        KeyMlKem::MlKem1024 => "recover_mlkem1024_public_key",
213    });
214    cmd.arg("--private-key");
215    cmd.arg(STANDARD.encode(private_key_representation));
216
217    let cmd_stdout = run_command_stdout(cmd)?;
218    parse_keypair_result(&cmd_stdout)
219}
220
221pub fn private_key_sign(private_key_representation: &[u8], content: &[u8]) -> XResult<Vec<u8>> {
222    private_key_sign_digested(private_key_representation, content, DigestType::Raw)
223}
224
225pub fn private_key_sign_digested(
226    private_key_representation: &[u8],
227    content: &[u8],
228    digest_type: DigestType,
229) -> XResult<Vec<u8>> {
230    let mut cmd = Command::new(get_swift_secure_enclave_tool_cmd());
231    cmd.arg("compute_p256_ecsign");
232    cmd.arg("--private-key");
233    cmd.arg(STANDARD.encode(private_key_representation));
234    cmd.arg("--message-base64");
235    cmd.arg(STANDARD.encode(content));
236    if digest_type != DigestType::Raw {
237        cmd.arg("--message-type");
238        cmd.arg(digest_type.to_str());
239    }
240
241    let cmd_stdout = run_command_stdout(cmd)?;
242    parse_sign_result(&cmd_stdout)
243}
244
245// ephemera_public_key MUST be DER format public key
246pub fn private_key_ecdh(
247    private_key_representation: &[u8],
248    ephemera_public_key: &[u8],
249) -> XResult<Vec<u8>> {
250    let mut cmd = Command::new(get_swift_secure_enclave_tool_cmd());
251    cmd.arg("compute_p256_ecdh");
252    cmd.arg("--private-key");
253    cmd.arg(STANDARD.encode(private_key_representation));
254    cmd.arg("--ephemera-public-key");
255    cmd.arg(STANDARD.encode(ephemera_public_key));
256
257    let cmd_stdout = run_command_stdout(cmd)?;
258    parse_ecdh_result(&cmd_stdout)
259}
260
261pub fn private_key_mlkem_ecdh(
262    key_ml_kem: KeyMlKem,
263    private_key_representation: &[u8],
264    ephemera_public_key: &[u8],
265) -> XResult<Vec<u8>> {
266    let mut cmd = Command::new(get_swift_secure_enclave_tool_cmd());
267    cmd.arg(match key_ml_kem {
268        KeyMlKem::MlKem768 => "compute_mlkem768_ecdh",
269        KeyMlKem::MlKem1024 => "compute_mlkem1024_ecdh",
270    });
271    cmd.arg("--private-key");
272    cmd.arg(STANDARD.encode(private_key_representation));
273    cmd.arg("--ephemera-public-key");
274    cmd.arg(STANDARD.encode(ephemera_public_key));
275
276    let cmd_stdout = run_command_stdout(cmd)?;
277    parse_ecdh_result(&cmd_stdout)
278}
279
280pub fn external_sign(
281    external_command: &str,
282    parameter: &str,
283    alg: &str,
284    content: &[u8],
285) -> XResult<Vec<u8>> {
286    let mut cmd = Command::new(external_command);
287    cmd.arg("external_sign");
288    cmd.arg("--parameter");
289    cmd.arg(parameter);
290    cmd.arg("--alg");
291    cmd.arg(alg);
292    cmd.arg("--message-base64");
293    cmd.arg(STANDARD.encode(content));
294
295    let cmd_stdout = run_command_stdout(cmd)?;
296    parse_sign_result(&cmd_stdout)
297}
298
299pub fn external_ecdh(
300    external_command: &str,
301    parameter: &str,
302    ephemera_public_key: &[u8],
303) -> XResult<Vec<u8>> {
304    let mut cmd = Command::new(external_command);
305    cmd.arg("external_ecdh");
306    cmd.arg("--parameter");
307    cmd.arg(parameter);
308    cmd.arg("--epk");
309    cmd.arg(STANDARD.encode(ephemera_public_key));
310
311    let cmd_stdout = run_command_stdout(cmd)?;
312    parse_ecdh_result(&cmd_stdout)
313}
314
315pub fn external_spec(external_command: &str) -> XResult<ExternalSpec> {
316    let mut cmd = Command::new(external_command);
317    cmd.arg("external_spec");
318
319    let cmd_stdout = run_command_stdout(cmd)?;
320    if is_success(&cmd_stdout)? {
321        let external_spec: ExternalSpec = from_str(&cmd_stdout)?;
322        Ok(external_spec)
323    } else {
324        let error_result: ErrorResult = from_str(&cmd_stdout)?;
325        simple_error!("{}", error_result.error)
326    }
327}
328
329pub fn external_public_key(external_command: &str, parameter: &str) -> XResult<Vec<u8>> {
330    let mut cmd = Command::new(external_command);
331    cmd.arg("external_public_key");
332    cmd.arg("--parameter");
333    cmd.arg(parameter);
334
335    let cmd_stdout = run_command_stdout(cmd)?;
336    if is_success(&cmd_stdout)? {
337        let external_public_key: ExternalPublicKey = from_str(&cmd_stdout)?;
338        Ok(STANDARD.decode(&external_public_key.public_key_base64)?)
339    } else {
340        let error_result: ErrorResult = from_str(&cmd_stdout)?;
341        simple_error!("{}", error_result.error)
342    }
343}
344
345fn run_command_stdout(cmd: Command) -> XResult<String> {
346    let output = run_command(cmd)?;
347    let stdout_text = opt_result!(String::from_utf8(output.stdout), "Parse stdout failed:{}");
348    Ok(stdout_text.trim().to_string())
349}
350
351fn run_command(mut cmd: Command) -> XResult<Output> {
352    debugging!("Run command: {:?}", cmd);
353    let output = cmd.output();
354    match output {
355        Err(e) => simple_error!("Run command failed: {:?}", e),
356        Ok(output) => {
357            debugging!("Output: {:?}", output);
358            if !output.status.success() {
359                let stderr = String::from_utf8_lossy(&output.stderr);
360                let stdout = String::from_utf8_lossy(&output.stdout);
361                simple_error!(
362                    "Run command not success: {:?}\n - stdout: {}\n - stderr: {}",
363                    output.status.code(),
364                    stdout,
365                    stderr
366                )
367            } else {
368                Ok(output)
369            }
370        }
371    }
372}
373
374fn parse_keypair_result(cmd_stdout: &str) -> XResult<KeyMaterial> {
375    if is_success(&cmd_stdout)? {
376        let key_pair_result: KeyPairResult = from_str(&cmd_stdout)?;
377        let public_key_point = match &key_pair_result.public_key_point_base64 {
378            Some(public_key_point_base64) => STANDARD.decode(public_key_point_base64)?,
379            None => vec![],
380        };
381        let public_key_der = STANDARD.decode(&key_pair_result.public_key_base64)?;
382        let private_key_representation =
383            STANDARD.decode(&key_pair_result.data_representation_base64)?;
384        Ok(KeyMaterial {
385            public_key_point,
386            public_key_der,
387            private_key_representation,
388        })
389    } else {
390        let error_result: ErrorResult = from_str(&cmd_stdout)?;
391        simple_error!("{}", error_result.error)
392    }
393}
394
395fn parse_sign_result(stdout: &str) -> XResult<Vec<u8>> {
396    if is_success(stdout)? {
397        let sign_result: SignResult = from_str(stdout)?;
398        Ok(STANDARD.decode(&sign_result.signature_base64)?)
399    } else {
400        let error_result: ErrorResult = from_str(stdout)?;
401        simple_error!("{}", error_result.error)
402    }
403}
404
405fn parse_ecdh_result(stdout: &str) -> XResult<Vec<u8>> {
406    if is_success(stdout)? {
407        let dh_result: DhResult = from_str(stdout)?;
408        Ok(hex::decode(&dh_result.shared_secret_hex)?)
409    } else {
410        let error_result: ErrorResult = from_str(stdout)?;
411        simple_error!("{}", error_result.error)
412    }
413}
414
415pub fn from_str<'a, T>(s: &'a str) -> XResult<T>
416where
417    T: de::Deserialize<'a>,
418{
419    match serde_json::from_str(s) {
420        Ok(result) => Ok(result),
421        Err(e) => simple_error!("Parse JSON: {}, error: {}", s, e),
422    }
423}
424
425fn is_success(cmd_stdout: &str) -> XResult<bool> {
426    let val = opt_result!(
427        serde_json::from_str::<Value>(cmd_stdout),
428        "Parse result: {}, failed: {}",
429        cmd_stdout
430    );
431    if let Value::Object(map) = val {
432        if let Some(success_value) = map.get("success") {
433            if let Value::Bool(result) = success_value {
434                return Ok(*result);
435            }
436        }
437    }
438    simple_error!("Bad result: {}", cmd_stdout)
439}
440
441fn get_swift_secure_enclave_tool_cmd() -> String {
442    util_env::env_var(ENV_SWIFT_SECURE_ENCLAVE_TOOL_CMD_KEY)
443        .unwrap_or_else(|| DEFAULT_SWIFT_SECURE_ENCLAVE_TOOL_CMD.to_string())
444}