From 116abd276b60524cd5d4288df06dec108f96c2cf Mon Sep 17 00:00:00 2001 From: Hatter Jiang Date: Fri, 17 Nov 2023 22:57:10 +0800 Subject: [PATCH] feat: works --- src/cmd_signjwt.rs | 120 ++++++++++++++++++++++++--------------------- 1 file changed, 63 insertions(+), 57 deletions(-) diff --git a/src/cmd_signjwt.rs b/src/cmd_signjwt.rs index b3fd305..0224605 100644 --- a/src/cmd_signjwt.rs +++ b/src/cmd_signjwt.rs @@ -1,7 +1,8 @@ use std::collections::BTreeMap; use clap::{App, Arg, ArgMatches, SubCommand}; -use jwt::{AlgorithmType, Error, SigningAlgorithm, SignWithKey}; +use jwt::{AlgorithmType, Header, ToBase64}; +use rust_util::{util_msg, XResult}; use rust_util::util_clap::{Command, CommandError}; use yubikey::piv::{AlgorithmId, sign_data}; use yubikey::YubiKey; @@ -10,51 +11,6 @@ use crate::{digest, pivutil, util}; const SEPARATOR: &str = "."; -// TODO pending ... -struct XSign {} - -impl SigningAlgorithm for XSign { - fn algorithm_type(&self) -> AlgorithmType { - AlgorithmType::Es256 - } - - fn sign(&self, header: &str, claims: &str) -> Result { - let mut tobe_signed = vec![]; - tobe_signed.extend_from_slice(header.as_bytes()); - tobe_signed.extend_from_slice(SEPARATOR.as_bytes()); - tobe_signed.extend_from_slice(claims.as_bytes()); - let sha256 = digest::sha256_bytes(&tobe_signed); - - let slot = "82"; - let mut yk = match YubiKey::open() { - Ok(yk) => yk, - Err(e) => { - failure!("Find YubiKey failed: {}", e); - return Err(Error::NoSignatureComponent); - } - }; - let slot_id = match pivutil::get_slot_id(slot) { - Ok(slot_id) => slot_id, - Err(e) => { - failure!("Find slot id failed: {}", e); - return Err(Error::NoSignatureComponent); - } - }; - let algorithm = AlgorithmId::EccP256; - - let signed_data = match sign_data(&mut yk, &sha256, algorithm, slot_id) { - Ok(signed_data) => signed_data, - Err(e) => { - failure!("Find YubiKey failed: {}", e); - return Err(Error::NoSignatureComponent); - } - }; - - let sign = util::base64_encode_url_safe_no_pad(signed_data); - Ok(sign) - } -} - pub struct CommandImpl; impl Command for CommandImpl { @@ -62,21 +18,71 @@ impl Command for CommandImpl { fn subcommand<'a>(&self) -> App<'a, 'a> { SubCommand::with_name(self.name()).about("Sign JWT subcommand") - // .arg(Arg::with_name("pub-key-in").long("pub-key-in").takes_value(true).help("Public key in")) - // .arg(Arg::with_name("signature").long("signature").takes_value(true).help("Signature HEX")) - // .arg(Arg::with_name("in").short("i").long("in").takes_value(true).help("File in")) - // .arg(Arg::with_name("hash").long("hash").takes_value(true).possible_values(&[ - // "sha256", "sha384", "sha512" - // ]).default_value("sha256").help("Hash")) + .arg(Arg::with_name("pin").short("p").long("pin").takes_value(true).help("PIV card user pin")) + .arg(Arg::with_name("slot").short("s").long("slot").takes_value(true).help("PIV slot, e.g. 82, 83 ... 95, 9a, 9c, 9d, 9e")) .arg(Arg::with_name("json").long("json").help("JSON output")) } - fn run(&self, _arg_matches: &ArgMatches, _sub_arg_matches: &ArgMatches) -> CommandError { - let mut claims = BTreeMap::new(); - claims.insert("sub", "someone"); - let j = claims.sign_with_key(&XSign {}).unwrap(); - println!("{}", j); + 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 mut json = BTreeMap::<&'_ str, String>::new(); + + let pin_opt = sub_arg_matches.value_of("pin"); + let slot = opt_value_result!( + sub_arg_matches.value_of("slot"), "--slot must assigned, e.g. 82, 83 ... 95, 9a, 9c, 9d, 9e"); + + // TODO custom header + let header = Header { + ..Default::default() + }; + let mut claims = BTreeMap::new(); + claims.insert("sub".to_string(), "someone".to_string()); + + let token_string = sign_jwt(slot, &pin_opt, header, &claims)?; + + success!("Singed JWT: {}", token_string); + if json_output { + json.insert("token", token_string.clone()); + } + + if json_output { + println!("{}", serde_json::to_string_pretty(&json).unwrap()); + } Ok(None) } +} + + +fn sign_jwt(slot: &str, pin_opt: &Option<&str>, mut header: Header, claims: &BTreeMap) -> XResult { + let mut yk = opt_result!(YubiKey::open(), "Find YubiKey failed: {}"); + let slot_id = opt_result!(pivutil::get_slot_id(slot), "Get slot id failed: {}"); + + if let Some(pin) = pin_opt { + opt_result!(yk.verify_pin(pin.as_bytes()), "YubiKey verify pin failed: {}"); + } + + // TODO set algorithm + let (jwt_algorithm, yk_algorithm) = (AlgorithmType::Es256, AlgorithmId::EccP256); + + header.algorithm = jwt_algorithm; + debugging!("Header: {:?}", header); + debugging!("Claims: {:?}", claims); + + let header = opt_result!(header.to_base64(), "Header to base64 failed: {}"); + let claims = opt_result!(claims.to_base64(), "Claims to base64 failed: {}"); + + let mut tobe_signed = vec![]; + tobe_signed.extend_from_slice(header.as_bytes()); + tobe_signed.extend_from_slice(SEPARATOR.as_bytes()); + tobe_signed.extend_from_slice(claims.as_bytes()); + let sha256 = digest::sha256_bytes(&tobe_signed); + + let signed_data = opt_result!( + sign_data(&mut yk, &sha256, yk_algorithm, slot_id), "Sign YubiKey failed: {}"); + + let signature = util::base64_encode_url_safe_no_pad(signed_data); + + Ok([&*header, &*claims, &signature].join(SEPARATOR)) } \ No newline at end of file