From 02a9e72a8a0840a7f488c8d66ee79e97958fa3d0 Mon Sep 17 00:00:00 2001 From: Hatter Jiang Date: Tue, 5 Apr 2022 23:08:55 +0800 Subject: [PATCH] feat: pgp card make --- src/cmd_pgpcardadmin.rs | 2 +- src/cmd_pgpcardmake.rs | 166 ++++++++++++++++++++++++++++++++++++++++ src/main.rs | 2 + 3 files changed, 169 insertions(+), 1 deletion(-) create mode 100644 src/cmd_pgpcardmake.rs diff --git a/src/cmd_pgpcardadmin.rs b/src/cmd_pgpcardadmin.rs index f2be6cf..cda50f6 100644 --- a/src/cmd_pgpcardadmin.rs +++ b/src/cmd_pgpcardadmin.rs @@ -20,7 +20,7 @@ impl Command for CommandImpl { fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError { let pin_opt = sub_arg_matches.value_of("pass").or_else(|| sub_arg_matches.value_of("pin")); - let pin = opt_value_result!(pin_opt, "Pass must be assigned"); + let pin = opt_value_result!(pin_opt, "Pin must be assigned"); if pin.len() < 8 { return simple_error!("Admin pin length:{}, must >= 8!", pin.len()); } let mut card = crate::pgpcardutil::get_card()?; diff --git a/src/cmd_pgpcardmake.rs b/src/cmd_pgpcardmake.rs new file mode 100644 index 0000000..9697001 --- /dev/null +++ b/src/cmd_pgpcardmake.rs @@ -0,0 +1,166 @@ +use clap::{App, Arg, ArgMatches, SubCommand}; +use openpgp::crypto::mpi::ProtectedMPI; +use openpgp::crypto::mpi::PublicKey as MpiPublicKey; +use openpgp::crypto::Password; +use openpgp::Fingerprint; +use openpgp::KeyID; +use openpgp::Packet; +use openpgp::packet::Key; +use openpgp::packet::key::{SecretKeyMaterial, SecretParts}; +use openpgp::packet::key::SubordinateRole; +use openpgp::packet::Signature; +use openpgp::packet::signature::subpacket::{SubpacketTag, SubpacketValue}; +use openpgp::parse::{PacketParser, PacketParserResult, Parse}; +use openpgp_card::OpenPgp; +use openssl::bn::BigNum; +use rust_util::util_clap::{Command, CommandError}; +use rust_util::XResult; +use sequoia_openpgp as openpgp; + +use crate::rsautil::RsaCrt; + +#[derive(Debug)] +struct PgpRsaPrivateKeySet { + signing: Option, + encryption: Option, + authentication: Option, +} + +impl PgpRsaPrivateKeySet { + fn new() -> Self { + PgpRsaPrivateKeySet { + signing: None, + encryption: None, + authentication: None, + } + } +} + +#[derive(Debug)] +struct PgpRsaPrivateKey { + key_id: KeyID, + fingerprint: Fingerprint, + rsa_private_key: RsaCrt, +} + +pub struct CommandImpl; + +impl Command for CommandImpl { + fn name(&self) -> &str { "pgp-card-make" } + + fn subcommand<'a>(&self) -> App<'a, 'a> { + SubCommand::with_name(self.name()).about("OpenPGP Card Make subcommand") + // .arg(Arg::with_name("pin").short("p").long("pin").takes_value(true).default_value("12345678").help("OpenPGP card admin pin")) + .arg(Arg::with_name("pass").long("pass").takes_value(true).required(false).help("Password for PGP secret key")) + .arg(Arg::with_name("in").long("in").takes_value(true).required(false).help("PGP file in")) + .arg(Arg::with_name("force-make").long("force-make").help("Force make OpenPGP card")) + } + + fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError { + let pin_opt = sub_arg_matches.value_of("pin"); + let pin = opt_value_result!(pin_opt, "Pin must be assigned"); + if pin.len() < 8 { return simple_error!("Admin pin length:{}, must >= 8!", pin.len()); } + + let pass = opt_value_result!(sub_arg_matches.value_of("pass"), "Pass must assigned!"); + let password = Password::from(pass); + let pgp_in_file = opt_value_result!(sub_arg_matches.value_of("in"), "PGP in file must assigned!"); + + let mut pgp_rsa_private_key_set = PgpRsaPrivateKeySet::new(); + let mut ppr = PacketParser::from_file(pgp_in_file)?; + let mut last_pgp_rsa_private_key = None; + while let PacketParserResult::Some(pp) = ppr { + if let Packet::SecretKey(key) = &pp.packet { + let key = key.role_as_subordinate(); + let pgp_rsa_private_key = parse_security_sub_key_to_pgp_rsa_private_key(key, &password)?; + if last_pgp_rsa_private_key.is_some() { return simple_error!("Last PGP RSA private key is not none"); } + last_pgp_rsa_private_key.replace(pgp_rsa_private_key); + } else if let Packet::SecretSubkey(key) = &pp.packet { + let pgp_rsa_private_key = parse_security_sub_key_to_pgp_rsa_private_key(key, &password)?; + if last_pgp_rsa_private_key.is_some() { return simple_error!("Last PGP RSA private key is not none"); } + last_pgp_rsa_private_key.replace(pgp_rsa_private_key); + } else if let Packet::Signature(signature) = &pp.packet { + if let Signature::V4(signature_v4) = signature { + if let Some(sub_package) = signature_v4.hashed_area().subpacket(SubpacketTag::KeyFlags) { + if let SubpacketValue::KeyFlags(key_flags) = sub_package.value() { + if last_pgp_rsa_private_key.is_none() { + return simple_error!("Last PGP RSA private key is none, for signature flag: {:?}", key_flags); + } + if key_flags.for_certification() || key_flags.for_signing() { + pgp_rsa_private_key_set.signing = last_pgp_rsa_private_key.take(); + } else if key_flags.for_transport_encryption() || key_flags.for_storage_encryption() { + pgp_rsa_private_key_set.encryption = last_pgp_rsa_private_key.take(); + } else if key_flags.for_authentication() { + pgp_rsa_private_key_set.authentication = last_pgp_rsa_private_key.take(); + } else { + return simple_error!("Unknown signature flags: {:?}", key_flags); + } + } + } + } + } + // Start parsing the next packet, recursing. + ppr = pp.recurse()?.1; + } + + debugging!("Found PGP RSA private key set: {:?}", pgp_rsa_private_key_set); + + // TODO success logging + if pgp_rsa_private_key_set.signing.is_none() || pgp_rsa_private_key_set.encryption.is_none() || pgp_rsa_private_key_set.authentication.is_none() { + warning!("");// TODO ... + } + + let force_make = sub_arg_matches.is_present("force-make"); + if !force_make { + warning!("Force make is OFF, skip write private keys to card!"); + return Ok(None); + } + + warning!("Force make is ON, try to write private keys to card!"); + let mut card = crate::pgpcardutil::get_card()?; + let mut pgp = OpenPgp::new(&mut card); + let mut trans = opt_result!(pgp.transaction(), "Open card failed: {}"); + + opt_result!(trans.verify_pw3(pin.as_ref()), "Admin pin verify failed: {}"); + success!("Admin pin verify success!"); + + // trans.key_import() + + Ok(None) + } +} + +fn parse_security_sub_key_to_pgp_rsa_private_key(key: &Key, password: &Password) -> XResult { + information!("Public key, key id: {}, fingerprint: {}", key.keyid(), key.fingerprint()); + let e = if let MpiPublicKey::RSA { e, n: _ } = key.mpis() { + e.clone() + } else { + return simple_error!("Not RSA public key"); + }; + // default PGP implementation SHOULD encrypt private keys + let private_key = opt_result!(key.clone().decrypt_secret(&password), "Decrypt private key failed: {}"); + let (p, q) = if let Key::V4(private_key4) = private_key { + if let SecretKeyMaterial::Unencrypted(unencrypted) = private_key4.secret() { + unencrypted.map(|f| { + let p_and_q_result: XResult<(ProtectedMPI, ProtectedMPI)> = if let openpgp::crypto::mpi::SecretKeyMaterial::RSA { d: _, p, q, u: _ } = f { + Ok((p.clone(), q.clone())) + } else { + simple_error!("Not RSA private key") + }; + p_and_q_result + })? + } else { + return simple_error!("Not unencrypted private key"); + } + } else { + return simple_error!("Not Key::V4 private key"); + }; + let p = BigNum::from_slice(p.value()).unwrap(); + let q = BigNum::from_slice(q.value()).unwrap(); + let e = BigNum::from_slice(e.value()).unwrap(); + let rsa_crt = opt_result!(RsaCrt::from(p, q, e), "Parse RSA crt failed: {}"); + Ok(PgpRsaPrivateKey { + key_id: key.keyid(), + fingerprint: key.fingerprint(), + rsa_private_key: rsa_crt, + }) +} diff --git a/src/main.rs b/src/main.rs index 0ee224f..41e272d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,6 +17,7 @@ mod cmd_pgpcardadmin; mod cmd_pgpcardlist; mod cmd_pgpcardsign; mod cmd_pgpcarddecrypt; +mod cmd_pgpcardmake; mod cmd_piv; mod cmd_pivsign; mod cmd_chall; @@ -49,6 +50,7 @@ fn inner_main() -> CommandError { Box::new(cmd_pgpcardlist::CommandImpl), Box::new(cmd_pgpcardsign::CommandImpl), Box::new(cmd_pgpcarddecrypt::CommandImpl), + Box::new(cmd_pgpcardmake::CommandImpl), Box::new(cmd_piv::CommandImpl), Box::new(cmd_pivsign::CommandImpl), Box::new(cmd_u2fregister::CommandImpl),