From 8a5bb56de924c26137168f35eacc2e4ab0f0c81c Mon Sep 17 00:00:00 2001 From: Hatter Jiang Date: Sun, 11 Jul 2021 09:46:33 +0800 Subject: [PATCH] feat: add pgp card sign --- README.md | 2 +- src/main.rs | 2 + src/pgpcardsign.rs | 97 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 src/pgpcardsign.rs diff --git a/README.md b/README.md index 2ff2741..3e8d19c 100644 --- a/README.md +++ b/README.md @@ -23,4 +23,4 @@ Related webauthn projects: OpenPGP projects: * https://github.com/solokeys/piv-authenticator * https://gitlab.com/hkos/openpgp-card - +* https://gitlab.com/sequoia-pgp/sequoia diff --git a/src/main.rs b/src/main.rs index dec63da..a175ce2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,6 +8,7 @@ mod register; mod sign; mod pgp; mod pgpcardlist; +mod pgpcardsign; use clap::{App, AppSettings}; use cmd::{Command, CommandError}; @@ -26,6 +27,7 @@ fn inner_main() -> CommandError { Box::new(sign::CommandImpl), Box::new(pgp::CommandImpl), Box::new(pgpcardlist::CommandImpl), + Box::new(pgpcardsign::CommandImpl), ]; let mut app = App::new(env!("CARGO_PKG_NAME")) .version(env!("CARGO_PKG_VERSION")) diff --git a/src/pgpcardsign.rs b/src/pgpcardsign.rs new file mode 100644 index 0000000..70f9e2f --- /dev/null +++ b/src/pgpcardsign.rs @@ -0,0 +1,97 @@ +use clap::{ArgMatches, SubCommand, App, Arg}; +use crate::cmd::{Command, CommandError}; +use openpgp_card::{OpenPGPCard, Hash, OpenPGPCardUser}; +use rust_util::XResult; + +pub struct CommandImpl; + +impl Command for CommandImpl { + fn name(&self) -> &str { "pgp-card-sign" } + + fn subcommand<'a>(&self) -> App<'a, 'a> { + SubCommand::with_name(self.name()).about("OpenPGP Card List subcommand") + .arg(Arg::with_name("pass").long("pass").takes_value(true).default_value("123456").help("OpenPGP card password")) + .arg(Arg::with_name("sha256").long("sha256").takes_value(true).help("Digest SHA256")) + .arg(Arg::with_name("sha384").long("sha384").takes_value(true).help("Digest SHA384")) + .arg(Arg::with_name("sha512").long("sha512").takes_value(true).help("Digest SHA512")) + .arg(Arg::with_name("json").long("json").help("JSON output")) + } + + fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError { + let pass = sub_arg_matches.value_of("pass"); + let pass = match pass { + Some(p) => p, + None => return simple_error!("Pass must be assigned"), + }; + let sha256 = sub_arg_matches.value_of("sha256"); + let sha384 = sub_arg_matches.value_of("sha384"); + let sha512 = sub_arg_matches.value_of("sha512"); + + if sha256.is_none() && sha384.is_none() && sha512.is_none() { + return simple_error!("SHA256, SHA384 or SHA512 must assign one"); + } + + if let Some(sha256) = sha256 { + let user = get_card_user_sw1_81(pass)?; + let sha256_hex = opt_result!(hex::decode(sha256), "Decode sha256 failed: {}"); + let sha256_hex = copy_sha256(&sha256_hex)?; + let sig = user.signature_for_hash(Hash::SHA256(sha256_hex))?; + success!("SHA256 signature: {}", hex::encode(&sig)); + success!("SHA256 signature: {}", base64::encode(&sig)); + } + if let Some(sha384) = sha384 { + let user = get_card_user_sw1_81(pass)?; + let sha384_hex = opt_result!(hex::decode(sha384), "Decode sha384 failed: {}"); + let sha384_hex = copy_sha384(&sha384_hex)?; + let sig = user.signature_for_hash(Hash::SHA384(sha384_hex))?; + success!("SHA384 signature: {}", hex::encode(&sig)); + success!("SHA384 signature: {}", base64::encode(&sig)); + } + if let Some(sha512) = sha512 { + let user = get_card_user_sw1_81(pass)?; + let sha512_hex = opt_result!(hex::decode(sha512), "Decode sha512 failed: {}"); + let sha512_hex = copy_sha512(&sha512_hex)?; + let sig = user.signature_for_hash(Hash::SHA512(sha512_hex))?; + success!("SHA512 signature: {}", hex::encode(&sig)); + success!("SHA512 signature: {}", base64::encode(&sig)); + } + + Ok(()) + } +} + +macro_rules! define_copy_array { + ($fn_name: ident, $len: tt) => ( + fn $fn_name(in_arr: &[u8]) -> XResult<[u8; $len]> { + if in_arr.len() != $len { + return simple_error!("Array length is not: {}, but is: {}", $len, in_arr.len()); + } + let mut out_arr = [0_u8; $len]; + for i in 0..$len { + out_arr[i] = in_arr[i]; + } + Ok(out_arr) + } + ) +} + +define_copy_array!(copy_sha256, 0x20); +define_copy_array!(copy_sha384, 0x30); +define_copy_array!(copy_sha512, 0x40); + +fn get_card_user_sw1_81(pass: &str) -> XResult { + match OpenPGPCard::list_cards() { + Ok(list) => { + // pw1_81 for signature + // openssl dgst -sha256 -verify aa -signature sig LICENSE + if list.is_empty() { + return simple_error!("Cannot find any card"); + } + match list.into_iter().next().unwrap().verify_pw1_81(pass) { + Result::Ok(user) => Ok(user), + Result::Err(_) => simple_error!("Verify pw1_81 OpenPGP card failed"), + } + } + Err(e) => simple_error!("Read OpenPGP card failed: {}", e), + } +}