external_command_rs/
lib.rs1use base64::Engine;
2use base64::engine::general_purpose::STANDARD;
3use rust_util::{XResult, debugging, opt_result, simple_error};
4use serde::{Deserialize, Serialize, de};
5use serde_json::Value;
6use std::process::{Command, Output};
7
8#[derive(Debug, Serialize, Deserialize)]
9struct ErrorResult {
10 pub success: bool,
11 pub error: String,
12}
13
14#[derive(Debug, Serialize, Deserialize)]
15pub struct ExternalSpecResult {
16 pub success: bool,
17 pub agent: String,
18 pub specification: String,
19 pub commands: Vec<String>,
20}
21
22#[derive(Debug, Serialize, Deserialize)]
23struct ExternalPublicKeyResult {
24 pub success: bool,
25 pub public_key_base64: String,
26}
27
28#[derive(Debug, Serialize, Deserialize)]
29struct ExternalSignResult {
30 pub success: bool,
31 pub signature_base64: String,
32}
33
34#[derive(Debug, Serialize, Deserialize)]
35struct ExternalDhResult {
36 pub success: bool,
37 pub shared_secret_hex: String,
38}
39
40pub struct ExternalCommand {
41 pub external_command: String,
42 pub parameter: String,
43}
44
45impl ExternalCommand {
46 pub fn new(external_command: String, parameter: String) -> ExternalCommand {
47 ExternalCommand {
48 external_command,
49 parameter,
50 }
51 }
52
53 pub fn spec(&self) -> XResult<ExternalSpecResult> {
54 external_spec(&self.external_command)
55 }
56
57 pub fn public_key(&self) -> XResult<Vec<u8>> {
58 external_public_key(&self.external_command, &self.parameter)
59 }
60
61 pub fn sign(&self, alg: &str, content: &[u8]) -> XResult<Vec<u8>> {
62 external_sign(&self.external_command, &self.parameter, alg, content)
63 }
64
65 pub fn ecdh(&self, ephemera_public_key: &[u8]) -> XResult<Vec<u8>> {
66 external_ecdh(&self.external_command, &self.parameter, ephemera_public_key)
67 }
68}
69
70pub fn external_spec(external_command: &str) -> XResult<ExternalSpecResult> {
71 let mut cmd = Command::new(external_command);
72 cmd.arg("external_spec");
73
74 let cmd_stdout = run_command_stdout(cmd)?;
75 if is_success(&cmd_stdout)? {
76 let external_spec: ExternalSpecResult = from_str(&cmd_stdout)?;
77 Ok(external_spec)
78 } else {
79 let error_result: ErrorResult = from_str(&cmd_stdout)?;
80 simple_error!("{}", error_result.error)
81 }
82}
83
84pub fn external_public_key(external_command: &str, parameter: &str) -> XResult<Vec<u8>> {
85 let mut cmd = Command::new(external_command);
86 cmd.arg("external_public_key");
87 cmd.arg("--parameter");
88 cmd.arg(parameter);
89
90 let cmd_stdout = run_command_stdout(cmd)?;
91 if is_success(&cmd_stdout)? {
92 let external_public_key: ExternalPublicKeyResult = from_str(&cmd_stdout)?;
93 Ok(STANDARD.decode(&external_public_key.public_key_base64)?)
94 } else {
95 let error_result: ErrorResult = from_str(&cmd_stdout)?;
96 simple_error!("{}", error_result.error)
97 }
98}
99
100pub fn external_sign(
101 external_command: &str,
102 parameter: &str,
103 alg: &str,
104 content: &[u8],
105) -> XResult<Vec<u8>> {
106 external_sign_digested(external_command, parameter, alg, content, "")
107}
108
109pub fn external_sign_digested(
110 external_command: &str,
111 parameter: &str,
112 alg: &str,
113 content: &[u8],
114 digest_type: &str,
115) -> XResult<Vec<u8>> {
116 let mut cmd = Command::new(external_command);
117 cmd.arg("external_sign");
118 cmd.arg("--parameter");
119 cmd.arg(parameter);
120 cmd.arg("--alg");
121 cmd.arg(alg);
122 cmd.arg("--message-base64");
123 cmd.arg(STANDARD.encode(content));
124 if !digest_type.is_empty() && digest_type != "raw" {
125 cmd.arg("--message-type");
126 cmd.arg(digest_type);
127 }
128
129 let cmd_stdout = run_command_stdout(cmd)?;
130 parse_sign_result(&cmd_stdout)
131}
132
133pub fn external_ecdh(
134 external_command: &str,
135 parameter: &str,
136 ephemera_public_key: &[u8],
137) -> XResult<Vec<u8>> {
138 let mut cmd = Command::new(external_command);
139 cmd.arg("external_ecdh");
140 cmd.arg("--parameter");
141 cmd.arg(parameter);
142 cmd.arg("--epk");
143 cmd.arg(STANDARD.encode(ephemera_public_key));
144
145 let cmd_stdout = run_command_stdout(cmd)?;
146 parse_ecdh_result(&cmd_stdout)
147}
148
149fn run_command_stdout(cmd: Command) -> XResult<String> {
150 let output = run_command(cmd)?;
151 let stdout_text = opt_result!(String::from_utf8(output.stdout), "Parse stdout failed:{}");
152 Ok(stdout_text.trim().to_string())
153}
154
155fn run_command(mut cmd: Command) -> XResult<Output> {
156 debugging!("Run command: {:?}", cmd);
157 let output = cmd.output();
158 match output {
159 Err(e) => simple_error!("Run command failed: {:?}", e),
160 Ok(output) => {
161 debugging!("Output: {:?}", output);
162 if !output.status.success() {
163 let stderr = String::from_utf8_lossy(&output.stderr);
164 let stdout = String::from_utf8_lossy(&output.stdout);
165 simple_error!(
166 "Run command not success: {:?}\n - stdout: {}\n - stderr: {}",
167 output.status.code(),
168 stdout,
169 stderr
170 )
171 } else {
172 Ok(output)
173 }
174 }
175 }
176}
177
178fn parse_sign_result(stdout: &str) -> XResult<Vec<u8>> {
179 if is_success(stdout)? {
180 let sign_result: ExternalSignResult = from_str(stdout)?;
181 Ok(STANDARD.decode(&sign_result.signature_base64)?)
182 } else {
183 let error_result: ErrorResult = from_str(stdout)?;
184 simple_error!("{}", error_result.error)
185 }
186}
187
188fn parse_ecdh_result(stdout: &str) -> XResult<Vec<u8>> {
189 if is_success(stdout)? {
190 let dh_result: ExternalDhResult = from_str(stdout)?;
191 Ok(hex::decode(&dh_result.shared_secret_hex)?)
192 } else {
193 let error_result: ErrorResult = from_str(stdout)?;
194 simple_error!("{}", error_result.error)
195 }
196}
197
198pub fn from_str<'a, T>(s: &'a str) -> XResult<T>
199where
200 T: de::Deserialize<'a>,
201{
202 match serde_json::from_str(s) {
203 Ok(result) => Ok(result),
204 Err(e) => simple_error!("Parse JSON: {}, error: {}", s, e),
205 }
206}
207
208fn is_success(cmd_stdout: &str) -> XResult<bool> {
209 let val = opt_result!(
210 serde_json::from_str::<Value>(cmd_stdout),
211 "Parse result: {}, failed: {}",
212 cmd_stdout
213 );
214 if let Value::Object(map) = val {
215 if let Some(success_value) = map.get("success") {
216 if let Value::Bool(result) = success_value {
217 return Ok(*result);
218 }
219 }
220 }
221 simple_error!("Bad result: {}", cmd_stdout)
222}