use clap::{App, Arg, ArgMatches, SubCommand}; use der_parser::ber::BerObjectContent; use pem::Pem; use rust_util::util_clap::{Command, CommandError}; use yubikey::{Key, YubiKey}; use yubikey::piv::{AlgorithmId, sign_data}; use crate::{pinutil, pivutil, util}; use crate::pivutil::{get_algorithm_id_by_certificate, slot_equals, ToStr}; use crate::sshutil::SshVecWriter; pub struct CommandImpl; // https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.sshsig impl Command for CommandImpl { fn name(&self) -> &str { "ssh-piv-sign" } fn subcommand<'a>(&self) -> App<'a, 'a> { SubCommand::with_name(self.name()).about("SSH piv sign subcommand") .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("namespace").short("n").long("namespace").takes_value(true).help("Namespace")) .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 namespace_opt = sub_arg_matches.value_of("namespace"); let namespace = match namespace_opt { None => return simple_error!("Namespace required"), Some(namespace) => namespace, }; let data = util::read_file_or_stdin(sub_arg_matches.value_of("in").unwrap())?; let slot = opt_value_result!(sub_arg_matches.value_of("slot"), "--slot must assigned, e.g. 82, 83 ... 95, 9a, 9c, 9d, 9e"); let mut yk = opt_result!(YubiKey::open(), "YubiKey not found: {}"); let slot_id = pivutil::get_slot_id(slot)?; let pin_opt = sub_arg_matches.value_of("pin"); let pin_opt = pinutil::get_pin(pin_opt); let pin_opt = pin_opt.as_deref(); let mut algorithm_id_opt = None; let mut ec_key_point = vec![]; match Key::list(&mut yk) { Err(e) => warning!("List keys failed: {}", e), Ok(keys) => for k in &keys { let slot_str = format!("{:x}", Into::::into(k.slot())); if slot_equals(&slot_id, &slot_str) { let cert = &k.certificate().cert.tbs_certificate; let certificate = k.certificate(); if let Ok(algorithm_id) = get_algorithm_id_by_certificate(certificate) { match algorithm_id { AlgorithmId::EccP256 | AlgorithmId::EccP384 => { let public_key_bit_string = &cert.subject_public_key_info.subject_public_key; ec_key_point.extend_from_slice(public_key_bit_string.raw_bytes()); algorithm_id_opt = Some(algorithm_id); } _ => return simple_error!("Not P256/384 key: {}", algorithm_id.to_str()), } } } } } let algorithm_id = match algorithm_id_opt { None => return simple_error!("Slot key not found!"), Some(algorithm_id) => algorithm_id, }; let ec_bit_len = iff!(matches!(algorithm_id, AlgorithmId::EccP256), 256, 384); let mut buffer = vec![]; buffer.write_bytes("SSHSIG".as_bytes()); buffer.write_u32(1); let mut public_key = vec![]; public_key.write_string(format!("ecdsa-sha2-nistp{}", ec_bit_len).as_bytes()); public_key.write_string(format!("nistp{}", ec_bit_len).as_bytes()); public_key.write_string(&ec_key_point); buffer.write_string(&public_key); buffer.write_string(namespace.as_bytes()); buffer.write_string("".as_bytes()); // The supported hash algorithms are "sha256" and "sha512". buffer.write_string("sha512".as_bytes()); let mut signature = vec![]; signature.write_string(format!("ecdsa-sha2-nistp{}", ec_bit_len).as_bytes()); let mut sign_message = vec![]; sign_message.write_bytes("SSHSIG".as_bytes()); sign_message.write_string(namespace.as_bytes()); sign_message.write_string("".as_bytes()); sign_message.write_string("sha512".as_bytes()); sign_message.write_string(&crate::digest::sha512_bytes(&data)); let tobe_signed_data = if ec_bit_len == 256 { crate::digest::sha256_bytes(&sign_message) } else { crate::digest::sha384_bytes(&sign_message) }; if let Some(pin) = pin_opt { opt_result!(yk.verify_pin(pin.as_bytes()), "YubiKey verify pin failed: {}"); } let mut signature_value = vec![]; let signed_data = opt_result!(sign_data(&mut yk, &tobe_signed_data, algorithm_id, slot_id), "Sign PIV failed: {}"); let (_, parsed_signature) = opt_result!(der_parser::parse_der(signed_data.as_slice()), "Parse signature failed: {}"); match parsed_signature.content { BerObjectContent::Sequence(seq) => { match &seq[0].content { BerObjectContent::Integer(x) => { signature_value.write_string(x); } _ => return simple_error!("Parse signature failed: [0]not integer"), } match &seq[1].content { BerObjectContent::Integer(y) => { signature_value.write_string(y); } _ => return simple_error!("Parse signature failed: [1]not integer"), } } _ => return simple_error!("Parse signature failed: not sequence"), } signature.write_string(&signature_value); buffer.write_string(&signature); let ssh_sig = Pem::new("SSH SIGNATURE", buffer); let ssh_sig_pem = ssh_sig.to_string(); println!("{}", ssh_sig_pem); Ok(None) } }