From 8a4d030d82d568aca556594f3b2252eade3dbb04 Mon Sep 17 00:00:00 2001 From: Hatter Jiang Date: Wed, 3 Jul 2024 00:58:30 +0800 Subject: [PATCH] feat: add ssh-parse-sign --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/cmd_sshparsesign.rs | 119 ++++++++++++++++++++++++++++++++++++++++ src/main.rs | 2 + src/util.rs | 9 +++ 5 files changed, 132 insertions(+), 2 deletions(-) create mode 100644 src/cmd_sshparsesign.rs diff --git a/Cargo.lock b/Cargo.lock index f8d067e..a09e11f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -368,7 +368,7 @@ dependencies = [ [[package]] name = "card-cli" -version = "1.9.7" +version = "1.9.8" dependencies = [ "authenticator", "base64 0.21.7", diff --git a/Cargo.toml b/Cargo.toml index 9cb26fd..a32d15f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "card-cli" -version = "1.9.7" +version = "1.9.8" authors = ["Hatter Jiang "] edition = "2018" diff --git a/src/cmd_sshparsesign.rs b/src/cmd_sshparsesign.rs new file mode 100644 index 0000000..5a505ae --- /dev/null +++ b/src/cmd_sshparsesign.rs @@ -0,0 +1,119 @@ +use std::io::{Cursor, Read}; +use std::str::FromStr; + +use clap::{App, Arg, ArgMatches, SubCommand}; +use pem::Pem; +use rust_util::util_clap::{Command, CommandError}; +use rust_util::XResult; + +use crate::util; + +trait CursorReader { + fn read_bytes(&mut self, len: usize) -> XResult>; + fn read_u32(&mut self) -> XResult; + fn read_string(&mut self) -> XResult>; +} + +impl CursorReader for Cursor> { + fn read_bytes(&mut self, len: usize) -> XResult> { + let mut buff = vec![0_u8; len]; + Cursor::read_exact(self, &mut buff)?; + Ok(buff) + } + + fn read_u32(&mut self) -> XResult { + let mut num = [0_u8; 4]; + num.copy_from_slice(&self.read_bytes(4)?); + Ok(u32::from_be_bytes(num)) + } + + fn read_string(&mut self) -> XResult> { + let len = self.read_u32()?; + if len == 0 { + return Ok(Vec::new()); + } + self.read_bytes(len as usize) + } +} + +pub struct CommandImpl; + +// https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.sshsig +impl Command for CommandImpl { + fn name(&self) -> &str { "ssh-parse-sign" } + + fn subcommand<'a>(&self) -> App<'a, 'a> { + SubCommand::with_name(self.name()).about("SSH parse sign subcommand") + .arg(Arg::with_name("in").long("in").required(true).takes_value(true).help("In file, - for stdin")) + } + + fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError { + let file_in = sub_arg_matches.value_of("in").unwrap(); + let bytes_in = util::read_file_or_stdin(file_in)?; + let pem_in = opt_result!(String::from_utf8(bytes_in), "Parse SSH sign failed: {}"); + + let pem = opt_result!( Pem::from_str(&pem_in), "Parse SSH sign pem failed: {}"); + debugging!("PEM: {:?}", pem); + + if pem.tag() != "SSH SIGNATURE" { + return simple_error!("Not SSH signature file."); + } + + let ssh_signature = pem.contents().to_vec(); + let mut cursor = Cursor::new(ssh_signature); + + let magic_preamble = String::from_utf8(cursor.read_bytes(6)?)?; + if magic_preamble != "SSHSIG" { + return simple_error!("Bad SSH signature file: magic"); + } + let ssh_signature_version = cursor.read_u32()?; + if ssh_signature_version != 1 { + return simple_error!("Bad SSH signature file: version"); + } + let public_key = cursor.read_string()?; + debugging!("Public key: {}", hex::encode(&public_key)); + let namespace = String::from_utf8(cursor.read_string()?)?; + debugging!("Namespace: {}", namespace); + let reserved = cursor.read_string()?; + debugging!("Reserved: {}", hex::encode(&reserved)); + let hash_algorithm = String::from_utf8(cursor.read_string()?)?; + debugging!("Hash algorithm: {}", hash_algorithm); + let signature = cursor.read_string()?; + debugging!("Signature: {}", hex::encode(&signature)); + + let mut public_key_cursor = Cursor::new(public_key); + let public_key_algorithm = String::from_utf8(public_key_cursor.read_string()?)?; + debugging!("Public key algorithm: {}", public_key_algorithm); + let public_key_algorithm2 = String::from_utf8(public_key_cursor.read_string()?)?; + debugging!("Public key algorithm(2): {}", public_key_algorithm2); + let public_key_value = public_key_cursor.read_string()?; + debugging!("Public key value: {}", hex::encode(&public_key_value)); + + let mut signature_cursor = Cursor::new(signature); + let signature_algorithm = String::from_utf8(signature_cursor.read_string()?)?; + debugging!("Signature algorithm: {}", signature_algorithm); + let signature_value = signature_cursor.read_string()?; + debugging!("Signature value: {}", hex::encode(&signature_value)); + + println!("Public Key:\n> {}", public_key_algorithm); + println!(" > {}", public_key_algorithm2); + println!(" > {}", hex::encode(public_key_value)); + println!("Namespace: {}", namespace); + println!("Reserved: {}", hex::encode(&reserved)); + println!("Hash Algorithm: {}", &hash_algorithm); + println!("Signature:\n> {}", signature_algorithm); + if signature_algorithm.starts_with("ecdsa-") { + let mut signature_value_cursor = Cursor::new(signature_value); + let signature_value_x = signature_value_cursor.read_string()?; + let signature_value_y = signature_value_cursor.read_string()?; + println!(" > {}", hex::encode(signature_value_x)); + println!(" > {}", hex::encode(signature_value_y)); + } else if signature_algorithm.starts_with("rsa-") { + println!(" > {}", hex::encode(signature_value)); + } else { + failure!("Unknown signature algorithm: {}", signature_algorithm); + } + + Ok(None) + } +} diff --git a/src/main.rs b/src/main.rs index e681625..f5661e6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -40,6 +40,7 @@ mod cmd_hmac_sha1; mod cmd_chall; mod cmd_challconfig; mod cmd_sshagent; +mod cmd_sshparsesign; mod cmd_pgpageaddress; mod cmd_signjwt; mod cmd_signfile; @@ -99,6 +100,7 @@ fn inner_main() -> CommandError { Box::new(cmd_u2fregister::CommandImpl), Box::new(cmd_u2fsign::CommandImpl), Box::new(cmd_sshagent::CommandImpl), + Box::new(cmd_sshparsesign::CommandImpl), Box::new(cmd_pgpageaddress::CommandImpl), Box::new(cmd_signjwt::CommandImpl), Box::new(cmd_signfile::CommandImpl), diff --git a/src/util.rs b/src/util.rs index 8c4a889..a5ba079 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,3 +1,4 @@ +use std::fs; use std::io::Read; use base64::{DecodeError, Engine}; @@ -32,3 +33,11 @@ pub fn read_stdin() -> XResult> { opt_result!(stdin.read_to_end(&mut buffer), "Read stdin failed: {}"); Ok(buffer) } + +pub fn read_file_or_stdin(file: &str) -> XResult> { + if file == "-" { + read_stdin() + } else { + Ok(opt_result!(fs::read(file), "Read file: {} failed: {}", file)) + } +}