diff --git a/Cargo.lock b/Cargo.lock index 72d57f2..d17c685 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -487,7 +487,7 @@ dependencies = [ [[package]] name = "card-cli" -version = "1.10.10" +version = "1.10.11" dependencies = [ "authenticator 0.3.1", "base64 0.21.7", diff --git a/Cargo.toml b/Cargo.toml index e6e3dc2..9a714c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "card-cli" -version = "1.10.10" +version = "1.10.11" authors = ["Hatter Jiang "] edition = "2018" diff --git a/src/cmd_ecverify.rs b/src/cmd_ecverify.rs new file mode 100644 index 0000000..8c63a43 --- /dev/null +++ b/src/cmd_ecverify.rs @@ -0,0 +1,79 @@ +use std::collections::BTreeMap; + +use clap::{App, Arg, ArgMatches, SubCommand}; + use rust_util::util_clap::{Command, CommandError}; +use rust_util::util_msg; + +use crate::ecdsautil::EcdsaAlgorithm; +use crate::{argsutil, ecdsautil}; + +pub struct CommandImpl; + +impl Command for CommandImpl { + fn name(&self) -> &str { "ec-verify" } + + fn subcommand<'a>(&self) -> App<'a, 'a> { + SubCommand::with_name(self.name()).about("ECDSA verify subcommand") + .arg(Arg::with_name("public-key-hex").short("k").long("public-key-hex").takes_value(true).help("Public key hex (starts with 04)")) + .arg(Arg::with_name("signature-hex").short("t").long("signature-hex").takes_value(true).help("Signature")) + .arg(Arg::with_name("file").short("f").long("file").takes_value(true).help("Input file")) + .arg(Arg::with_name("input").short("i").long("input").takes_value(true).help("Input")) + .arg(Arg::with_name("hash-hex").short("x").long("hash-hex").takes_value(true).help("Hash")) + .arg(Arg::with_name("json").long("json").help("JSON output")) + } + + fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError { + let json_output = sub_arg_matches.is_present("json"); + if json_output { util_msg::set_logger_std_out(false); } + + let hash_bytes = argsutil::get_sha256_digest_or_hash(sub_arg_matches)?; + let public_key = if let Some(public_key_hex) = sub_arg_matches.value_of("public-key-hex") { + opt_result!(hex::decode(public_key_hex), "Parse public-key-hex failed: {}") + } else { + return simple_error!("--public-hex required."); + }; + let signature = if let Some(signature_hex) = sub_arg_matches.value_of("signature-hex") { + opt_result!(hex::decode(signature_hex), "Parse signature-hex failed: {}") + } else { + return simple_error!("--signature-hex required."); + }; + + let ecdsa_algorithm = match public_key.len() { + 65 => EcdsaAlgorithm::P256, + 97 => EcdsaAlgorithm::P384, + _ => return simple_error!("Invalid public key: {}", hex::encode(&public_key)), + }; + + let mut json = BTreeMap::<&'_ str, String>::new(); + + debugging!("ECDSA public key point: {}", hex::encode(&public_key)); + information!("Pre hash: {}", hex::encode(&hash_bytes)); + debugging!("Signature: {}", hex::encode(&signature)); + if json_output { + json.insert("public_key_hex", hex::encode(&public_key)); + json.insert("hash_hex", hex::encode(&hash_bytes)); + json.insert("signature_hex", hex::encode(&signature)); + } + + match ecdsautil::ecdsaverify(ecdsa_algorithm, &public_key, &hash_bytes, &signature) { + Ok(_) => { + success!("Verify ECDSA succeed."); + if json_output { + json.insert("success", "true".to_string()); + } + } + Err(e) => { + failure!("Verify ECDSA failed: {}", &e); + if json_output { + json.insert("success", "false".to_string()); + json.insert("message", format!("{}", e)); + } + } + } + + if json_output { + println!("{}", serde_json::to_string_pretty(&json).unwrap()); + } + Ok(None) + } +} diff --git a/src/cmd_se_ecsign.rs b/src/cmd_se_ecsign.rs index 1cba876..31d9271 100644 --- a/src/cmd_se_ecsign.rs +++ b/src/cmd_se_ecsign.rs @@ -24,16 +24,17 @@ impl Command for CommandImpl { .help("Key uri"), ) .arg( - Arg::with_name("message") - .long("message") + Arg::with_name("input") + .short("i") + .long("input") .takes_value(true) - .help("Message"), + .help("Input"), ) .arg( - Arg::with_name("message-base64") - .long("message-base64") + Arg::with_name("input-base64") + .long("input-base64") .takes_value(true) - .help("Message in base64"), + .help("Input in base64"), ) .arg(Arg::with_name("json").long("json").help("JSON output")) } @@ -43,12 +44,12 @@ impl Command for CommandImpl { return simple_error!("Secure Enclave is NOT supported."); } let key = sub_arg_matches.value_of("key").unwrap(); - let message_bytes = match sub_arg_matches.value_of("message") { - None => match sub_arg_matches.value_of("message-base64") { - None => return simple_error!("Argument --message or --message-base64 is required"), - Some(message_base64) => base64_decode(message_base64)?, + let input_bytes = match sub_arg_matches.value_of("input") { + None => match sub_arg_matches.value_of("input-base64") { + None => return simple_error!("Argument --input or --input-base64 is required"), + Some(input_base64) => base64_decode(input_base64)?, }, - Some(message) => message.as_bytes().to_vec(), + Some(input) => input.as_bytes().to_vec(), }; let json_output = sub_arg_matches.is_present("json"); if json_output { @@ -58,7 +59,7 @@ impl Command for CommandImpl { let KeyUri::SecureEnclaveKey(se_key_uri) = parse_key_uri(key)?; debugging!("Secure enclave key URI: {:?}", se_key_uri); - let signature = seutil::secure_enclave_p256_sign(&se_key_uri.private_key, &message_bytes)?; + let signature = seutil::secure_enclave_p256_sign(&se_key_uri.private_key, &input_bytes)?; if json_output { let mut json = BTreeMap::<&'_ str, String>::new(); diff --git a/src/main.rs b/src/main.rs index 8ca1e75..1f9aaea 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,6 +7,7 @@ use rust_util::util_clap::{Command, CommandError}; mod argsutil; mod cmd_chall; mod cmd_challconfig; +mod cmd_ecverify; mod cmd_hmac_sha1; mod cmd_list; #[cfg(feature = "with-sequoia-openpgp")] @@ -136,6 +137,7 @@ fn inner_main() -> CommandError { Box::new(cmd_se_ecsign::CommandImpl), #[cfg(feature = "with-secure-enclave")] Box::new(cmd_se_ecdh::CommandImpl), + Box::new(cmd_ecverify::CommandImpl), ]; #[allow(clippy::vec_init_then_push)]