external_command_rs/
lib.rs

1use 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}