use base64::Engine; use base64::engine::general_purpose::STANDARD; use rust_util::{XResult, debugging, opt_result, simple_error}; use serde::{Deserialize, de}; use serde_json::Value; use std::process::{Command, Output}; #[derive(Debug, Deserialize)] struct ErrorResult { #[allow(dead_code)] pub success: bool, pub error: String, } #[derive(Debug, Deserialize)] pub struct ExternalSpecResult { #[allow(dead_code)] pub success: bool, pub agent: String, pub specification: String, pub commands: Vec, } #[derive(Debug, Deserialize)] struct ExternalPublicKeyResult { #[allow(dead_code)] pub success: bool, pub public_key_base64: String, } #[derive(Debug, Deserialize)] struct ExternalSignResult { #[allow(dead_code)] pub success: bool, pub signature_base64: String, } #[derive(Debug, Deserialize)] struct ExternalDhResult { #[allow(dead_code)] pub success: bool, pub shared_secret_hex: String, } pub fn external_spec(external_command: &str) -> XResult { let mut cmd = Command::new(external_command); cmd.arg("external_spec"); let cmd_stdout = run_command_stdout(cmd)?; if is_success(&cmd_stdout)? { let external_spec: ExternalSpecResult = from_str(&cmd_stdout)?; Ok(external_spec) } else { let error_result: ErrorResult = from_str(&cmd_stdout)?; simple_error!("{}", error_result.error) } } pub fn external_public_key(external_command: &str, parameter: &str) -> XResult> { let mut cmd = Command::new(external_command); cmd.arg("external_public_key"); cmd.arg("--parameter"); cmd.arg(parameter); let cmd_stdout = run_command_stdout(cmd)?; if is_success(&cmd_stdout)? { let external_public_key: ExternalPublicKeyResult = from_str(&cmd_stdout)?; Ok(STANDARD.decode(&external_public_key.public_key_base64)?) } else { let error_result: ErrorResult = from_str(&cmd_stdout)?; simple_error!("{}", error_result.error) } } pub fn external_sign( external_command: &str, parameter: &str, alg: &str, content: &[u8], ) -> XResult> { let mut cmd = Command::new(external_command); cmd.arg("external_sign"); cmd.arg("--parameter"); cmd.arg(parameter); cmd.arg("--alg"); cmd.arg(alg); cmd.arg("--message-base64"); cmd.arg(STANDARD.encode(content)); let cmd_stdout = run_command_stdout(cmd)?; parse_sign_result(&cmd_stdout) } pub fn external_ecdh( external_command: &str, parameter: &str, ephemera_public_key: &[u8], ) -> XResult> { let mut cmd = Command::new(external_command); cmd.arg("external_ecdh"); cmd.arg("--parameter"); cmd.arg(parameter); cmd.arg("--epk"); cmd.arg(STANDARD.encode(ephemera_public_key)); let cmd_stdout = run_command_stdout(cmd)?; parse_ecdh_result(&cmd_stdout) } fn run_command_stdout(cmd: Command) -> XResult { let output = run_command(cmd)?; let stdout_text = opt_result!(String::from_utf8(output.stdout), "Parse stdout failed:{}"); Ok(stdout_text.trim().to_string()) } fn run_command(mut cmd: Command) -> XResult { debugging!("Run command: {:?}", cmd); let output = cmd.output(); match output { Err(e) => simple_error!("Run command failed: {:?}", e), Ok(output) => { debugging!("Output: {:?}", output); if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); let stdout = String::from_utf8_lossy(&output.stdout); simple_error!( "Run command not success: {:?}\n - stdout: {}\n - stderr: {}", output.status.code(), stdout, stderr ) } else { Ok(output) } } } } fn parse_sign_result(stdout: &str) -> XResult> { if is_success(stdout)? { let sign_result: ExternalSignResult = from_str(stdout)?; Ok(STANDARD.decode(&sign_result.signature_base64)?) } else { let error_result: ErrorResult = from_str(stdout)?; simple_error!("{}", error_result.error) } } fn parse_ecdh_result(stdout: &str) -> XResult> { if is_success(stdout)? { let dh_result: ExternalDhResult = from_str(stdout)?; Ok(hex::decode(&dh_result.shared_secret_hex)?) } else { let error_result: ErrorResult = from_str(stdout)?; simple_error!("{}", error_result.error) } } pub fn from_str<'a, T>(s: &'a str) -> XResult where T: de::Deserialize<'a>, { match serde_json::from_str(s) { Ok(result) => Ok(result), Err(e) => simple_error!("Parse JSON: {}, error: {}", s, e), } } fn is_success(cmd_stdout: &str) -> XResult { let val = opt_result!( serde_json::from_str::(cmd_stdout), "Parse result: {}, failed: {}", cmd_stdout ); if let Value::Object(map) = val { if let Some(success_value) = map.get("success") { if let Value::Bool(result) = success_value { return Ok(*result); } } } simple_error!("Bad result: {}", cmd_stdout) }