use base64::engine::general_purpose::STANDARD; use base64::Engine; use clap::{App, Arg, ArgMatches, SubCommand}; use jwt::{AlgorithmType, Header, ToBase64}; use rust_util::util_clap::{Command, CommandError}; use rust_util::{util_msg, XResult}; use serde_json::{Map, Value}; use std::collections::BTreeMap; use crate::cmd_sign_jwt::{build_jwt_parts, merge_header_claims, merge_payload_claims}; use crate::ecdsautil::parse_ecdsa_to_rs; use crate::keyutil::{parse_key_uri, KeyUri}; use crate::{hmacutil, util}; const SEPARATOR: &str = "."; pub struct CommandImpl; impl Command for CommandImpl { fn name(&self) -> &str { "sign-jwt-se" } fn subcommand<'a>(&self) -> App<'a, 'a> { SubCommand::with_name(self.name()).about("Sign JWT subcommand") .arg(Arg::with_name("key").long("key").required(true).takes_value(true).help("Key uri")) .arg(Arg::with_name("key-id").short("K").long("key-id").takes_value(true).help("Header key ID")) .arg(Arg::with_name("claims").short("C").long("claims").takes_value(true).multiple(true).help("Claims, key:value")) .arg(Arg::with_name("payload").short("P").long("payload").takes_value(true).help("Claims in JSON")) .arg(Arg::with_name("jti").long("jti").help("Claims jti")) .arg(Arg::with_name("validity").long("validity").takes_value(true).help("Claims validity period e.g. 10m means 10 minutes (s - second, m - minute, h - hour, d - day)")) .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 mut json = BTreeMap::<&'_ str, String>::new(); let private_key = opt_value_result!( sub_arg_matches.value_of("key"), "Private key PKCS#8 DER base64 encoded or PEM" ); let private_key = hmacutil::try_hmac_decrypt_to_string(private_key)?; let KeyUri::SecureEnclaveKey(se_key_uri) = parse_key_uri(&private_key)?; debugging!("Secure enclave key URI: {:?}", se_key_uri); let (header, payload, jwt_claims) = build_jwt_parts(sub_arg_matches)?; let token_string = sign_jwt(&se_key_uri.private_key, header, &payload, &jwt_claims)?; debugging!("Singed JWT: {}", token_string); if json_output { json.insert("token", token_string.clone()); } else { success!("Singed JWT: {}", token_string); } if json_output { println!("{}", serde_json::to_string_pretty(&json).unwrap()); } Ok(None) } } fn sign_jwt( private_key: &str, mut header: Header, payload: &Option, claims: &Map, ) -> XResult { header.algorithm = AlgorithmType::Es256; debugging!("Header: {:?}", header); debugging!("Claims: {:?}", claims); let header = opt_result!(header.to_base64(), "Header to base64 failed: {}"); let claims = merge_payload_claims(payload, claims)?; let tobe_signed = merge_header_claims(header.as_bytes(), claims.as_bytes()); let private_key_representation = STANDARD.decode(private_key)?; let signed_data_der = swift_secure_enclave_tool_rs::private_key_sign(&private_key_representation, &tobe_signed)?; let signed_data = parse_ecdsa_to_rs(signed_data_der.as_slice())?; let signature = util::base64_encode_url_safe_no_pad(&signed_data); Ok([&*header, &*claims, &signature].join(SEPARATOR)) }