swift_secure_enclave_tool_rs/
lib.rs1use 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
245pub 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}