feat: external command rs 0.1.0
This commit is contained in:
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
12
.idea/external-command-rs.iml
generated
Normal file
12
.idea/external-command-rs.iml
generated
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="EMPTY_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/examples" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/external-command-rs.iml" filepath="$PROJECT_DIR$/.idea/external-command-rs.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
18
Cargo.toml
Normal file
18
Cargo.toml
Normal file
@@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "external-command-rs"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
authors = ["Hatter Jiang"]
|
||||
repository = "https://git.hatter.ink/hatter/external-command-rs"
|
||||
description = "External tool in Rust"
|
||||
license = "MIT OR Apache-2.0"
|
||||
keywords = ["crypto"]
|
||||
categories = ["cryptography"]
|
||||
|
||||
[dependencies]
|
||||
hex = "0.4.3"
|
||||
base64 = "0.22.1"
|
||||
rust_util = "0.6.47"
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
serde_json = "1.0.140"
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
# external-command-rs
|
||||
|
||||
Specification: https://openwebstandard.org/rfc1
|
||||
|
||||
|
||||
17
examples/simple_test.rs
Normal file
17
examples/simple_test.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
use external_command_rs::{external_public_key, external_sign, external_spec};
|
||||
|
||||
fn main() {
|
||||
let cmd = "/Users/hatterjiang/Code/hattergit/external-signer-pkcs11/external-signer-pkcs11";
|
||||
let spec = external_spec(cmd).unwrap();
|
||||
println!("{:#?}", spec);
|
||||
|
||||
let parameter = "ewogICJsaWJyYXJ5IjogIi91c3IvbG9jYWwvbGliL2xpYnlrY3MxMS5keWxpYiIsCiAgInRva\
|
||||
2VuX2xhYmVsIjogIll1YmlLZXkgUElWICM1MDEwMjIwIiwKICAicGluIjogIiIsCiAgImtleV9sYWJlbCI6ICJQcml2YXRlIGtle\
|
||||
SBmb3IgUElWIEF1dGhlbnRpY2F0aW9uIgp9Cg==";
|
||||
|
||||
let public_key = external_public_key(cmd, parameter).unwrap();
|
||||
println!("{}", hex::encode(public_key));
|
||||
|
||||
let signature = external_sign(cmd, parameter, "ES384", "hello world".as_bytes()).unwrap();
|
||||
println!("{}", hex::encode(signature));
|
||||
}
|
||||
7
justfile
Normal file
7
justfile
Normal file
@@ -0,0 +1,7 @@
|
||||
_:
|
||||
@just --list
|
||||
|
||||
# publish
|
||||
publish:
|
||||
cargo publish --registry crates-io
|
||||
|
||||
183
src/lib.rs
Normal file
183
src/lib.rs
Normal file
@@ -0,0 +1,183 @@
|
||||
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<String>,
|
||||
}
|
||||
|
||||
#[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<ExternalSpecResult> {
|
||||
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<Vec<u8>> {
|
||||
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<Vec<u8>> {
|
||||
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<Vec<u8>> {
|
||||
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<String> {
|
||||
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<Output> {
|
||||
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<Vec<u8>> {
|
||||
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<Vec<u8>> {
|
||||
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<T>
|
||||
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<bool> {
|
||||
let val = opt_result!(
|
||||
serde_json::from_str::<Value>(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)
|
||||
}
|
||||
Reference in New Issue
Block a user