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