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