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