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
81pub fn is_secure_enclave_supported() -> XResult<bool> {
82 let mut cmd = Command::new(SWIFT_SECURE_ENCLAVE_TOOL_CMD);
83 cmd.arg("is_support_secure_enclave");
84
85 let cmd_stdout = run_command_stdout(cmd)?;
86 if is_success(&cmd_stdout)? {
87 let is_support_se_result: IsSupportSecureEnclaveResult = from_str(&cmd_stdout)?;
88 Ok(is_support_se_result.supported)
89 } else {
90 let error_result: ErrorResult = from_str(&cmd_stdout)?;
91 simple_error!("{}", error_result.error)
92 }
93}
94
95pub fn generate_keypair(
96 key_purpose: KeyPurpose,
97 control_flag: ControlFlag,
98) -> XResult<KeyMaterial> {
99 let mut cmd = Command::new(SWIFT_SECURE_ENCLAVE_TOOL_CMD);
100 cmd.arg(match key_purpose {
101 KeyPurpose::Signing => "generate_p256_ecsign_keypair",
102 KeyPurpose::KeyAgreement => "generate_p256_ecdh_keypair",
103 });
104 cmd.arg("--control-flag");
105 cmd.arg(control_flag.to_str());
106
107 let cmd_stdout = run_command_stdout(cmd)?;
108 parse_keypair_result(&cmd_stdout)
109}
110
111pub fn recover_keypair(
112 key_purpose: KeyPurpose,
113 private_key_representation: &[u8],
114) -> XResult<KeyMaterial> {
115 let mut cmd = Command::new(SWIFT_SECURE_ENCLAVE_TOOL_CMD);
116 cmd.arg(match key_purpose {
117 KeyPurpose::Signing => "recover_p256_ecsign_public_key",
118 KeyPurpose::KeyAgreement => "recover_p256_ecdh_public_key",
119 });
120 cmd.arg("--private-key");
121 cmd.arg(STANDARD.encode(private_key_representation));
122
123 let cmd_stdout = run_command_stdout(cmd)?;
124 parse_keypair_result(&cmd_stdout)
125}
126
127pub fn private_key_sign(private_key_representation: &[u8], content: &[u8]) -> XResult<Vec<u8>> {
128 let mut cmd = Command::new(SWIFT_SECURE_ENCLAVE_TOOL_CMD);
129 cmd.arg("compute_p256_ecsign");
130 cmd.arg("--private-key");
131 cmd.arg(STANDARD.encode(private_key_representation));
132 cmd.arg("--message-base64");
133 cmd.arg(STANDARD.encode(content));
134
135 let cmd_stdout = run_command_stdout(cmd)?;
136 parse_sign_result(&cmd_stdout)
137}
138
139pub fn private_key_ecdh(
141 private_key_representation: &[u8],
142 ephemera_public_key: &[u8],
143) -> XResult<Vec<u8>> {
144 let mut cmd = Command::new(SWIFT_SECURE_ENCLAVE_TOOL_CMD);
145 cmd.arg("compute_p256_ecdh");
146 cmd.arg("--private-key");
147 cmd.arg(STANDARD.encode(private_key_representation));
148 cmd.arg("--ephemera-public-key");
149 cmd.arg(STANDARD.encode(ephemera_public_key));
150
151 let cmd_stdout = run_command_stdout(cmd)?;
152 parse_dh_result(&cmd_stdout)
153}
154
155pub fn external_sign(external_command: &str, parameter: &str, alg: &str, content: &[u8]) -> XResult<Vec<u8>> {
156 let mut cmd = Command::new(external_command);
157 cmd.arg("external_sign");
158 cmd.arg("--parameter");
159 cmd.arg(parameter);
160 cmd.arg("--alg");
161 cmd.arg(alg);
162 cmd.arg("--message-base64");
163 cmd.arg(STANDARD.encode(content));
164
165 let cmd_stdout = run_command_stdout(cmd)?;
166 parse_sign_result(&cmd_stdout)
167}
168
169pub fn external_ecdh(external_command: &str, parameter: &str, ephemera_public_key: &[u8]) -> XResult<Vec<u8>> {
170 let mut cmd = Command::new(external_command);
171 cmd.arg("external_ecdh");
172 cmd.arg("--parameter");
173 cmd.arg(parameter);
174 cmd.arg("--epk");
175 cmd.arg(STANDARD.encode(ephemera_public_key));
176
177 let cmd_stdout = run_command_stdout(cmd)?;
178 parse_dh_result(&cmd_stdout)
179}
180
181fn parse_sign_result(stdout: &str) -> XResult<Vec<u8>> {
182 if is_success(stdout)? {
183 let sign_result: SignResult = from_str(stdout)?;
184 Ok(STANDARD.decode(&sign_result.signature_base64)?)
185 } else {
186 let error_result: ErrorResult = from_str(stdout)?;
187 simple_error!("{}", error_result.error)
188 }
189}
190
191fn parse_dh_result(stdout: &str) -> XResult<Vec<u8>> {
192 if is_success(stdout)? {
193 let dh_result: DhResult = from_str(stdout)?;
194 Ok(hex::decode(&dh_result.shared_secret_hex)?)
195 } else {
196 let error_result: ErrorResult = from_str(stdout)?;
197 simple_error!("{}", error_result.error)
198 }
199}
200
201fn run_command_stdout(cmd: Command) -> XResult<String> {
202 let output = run_command(cmd)?;
203 let stdout_text = opt_result!(String::from_utf8(output.stdout), "Parse stdout failed:{}");
204 Ok(stdout_text.trim().to_string())
205}
206
207fn run_command(mut cmd: Command) -> XResult<Output> {
208 debugging!("Run command: {:?}", cmd);
209 let output = cmd.output();
210 match output {
211 Err(e) => simple_error!("Run command failed: {:?}", e),
212 Ok(output) => {
213 debugging!("Output: {:?}", output);
214 if !output.status.success() {
215 let stderr = String::from_utf8_lossy(&output.stderr);
216 let stdout = String::from_utf8_lossy(&output.stdout);
217 simple_error!("Run command not success: {:?}\n - stdout: {}\n - stderr: {}", output.status.code(), stdout, stderr)
218 } else {
219 Ok(output)
220 }
221 }
222 }
223}
224
225fn parse_keypair_result(cmd_stdout: &str) -> XResult<KeyMaterial> {
226 if is_success(&cmd_stdout)? {
227 let key_pair_result: KeyPairResult = from_str(&cmd_stdout)?;
228 let public_key_point = STANDARD.decode(&key_pair_result.public_key_point_base64)?;
229 let public_key_der = STANDARD.decode(&key_pair_result.public_key_base64)?;
230 let private_key_representation =
231 STANDARD.decode(&key_pair_result.data_representation_base64)?;
232 Ok(KeyMaterial {
233 public_key_point,
234 public_key_der,
235 private_key_representation,
236 })
237 } else {
238 let error_result: ErrorResult = from_str(&cmd_stdout)?;
239 simple_error!("{}", error_result.error)
240 }
241}
242
243pub fn from_str<'a, T>(s: &'a str) -> XResult<T>
244where
245 T: de::Deserialize<'a>,
246{
247 match serde_json::from_str(s) {
248 Ok(result) => Ok(result),
249 Err(e) => simple_error!("Parse JSON: {}, error: {}", s, e),
250 }
251}
252
253fn is_success(cmd_stdout: &str) -> XResult<bool> {
254 let val = opt_result!(
255 serde_json::from_str::<Value>(cmd_stdout),
256 "Parse result: {}, failed: {}",
257 cmd_stdout
258 );
259 if let Value::Object(map) = val {
260 if let Some(success_value) = map.get("success") {
261 if let Value::Bool(result) = success_value {
262 return Ok(*result);
263 }
264 }
265 }
266 simple_error!("Bad result: {}", cmd_stdout)
267}