diff --git a/Cargo.lock b/Cargo.lock index 957cdc7..51e4879 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -487,7 +487,7 @@ dependencies = [ [[package]] name = "card-cli" -version = "1.10.18" +version = "1.10.19" dependencies = [ "authenticator 0.3.1", "base64 0.21.7", diff --git a/Cargo.toml b/Cargo.toml index 9cb830e..674ddc5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "card-cli" -version = "1.10.18" +version = "1.10.19" authors = ["Hatter Jiang "] edition = "2018" diff --git a/src/cmd_generatekeypair.rs b/src/cmd_generatekeypair.rs new file mode 100644 index 0000000..33e9cb4 --- /dev/null +++ b/src/cmd_generatekeypair.rs @@ -0,0 +1,62 @@ +use crate::ecdsautil; +use crate::keyutil::{parse_key_uri, KeyUri}; +use clap::{App, Arg, ArgMatches, SubCommand}; +use p256::elliptic_curve::sec1::FromEncodedPoint; +use p256::{EncodedPoint, PublicKey}; +use rust_util::util_clap::{Command, CommandError}; +use rust_util::util_msg; +use spki::EncodePublicKey; +use std::collections::BTreeMap; + +pub struct CommandImpl; + +impl Command for CommandImpl { + fn name(&self) -> &str { + "generate-keypair" + } + + fn subcommand<'a>(&self) -> App<'a, 'a> { + SubCommand::with_name(self.name()) + .about("Generate software keypair") + .arg( + Arg::with_name("type") + .long("type") + .required(true) + .takes_value(true) + .help("Key type (e.g. p256, p384)"), + ) + .arg(Arg::with_name("json").long("json").help("JSON output")) + } + + fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError { + let key_type = sub_arg_matches.value_of("type").unwrap().to_lowercase(); + + let json_output = sub_arg_matches.is_present("json"); + if json_output { + util_msg::set_logger_std_out(false); + } + + let (pkcs8_base64, secret_key_pem, public_key_pem) = match key_type.as_str() { + "p256" => ecdsautil::generate_p256_keypair()?, + "p384" => ecdsautil::generate_p384_keypair()?, + _ => { + return simple_error!("Key type must be p256 or p384"); + } + }; + + if json_output { + let mut json = BTreeMap::<&'_ str, String>::new(); + json.insert("private_key_base64", pkcs8_base64); + json.insert("private_key_pem", secret_key_pem); + json.insert("public_key_pem", public_key_pem); + + println!("{}", serde_json::to_string_pretty(&json).unwrap()); + } else { + information!("Private key base64:\n{}\n", pkcs8_base64); + information!("Private key PEM:\n{}\n", secret_key_pem); + information!("Public key PEM:\n{}", public_key_pem); + } + + Ok(None) + } +} diff --git a/src/ecdsautil.rs b/src/ecdsautil.rs index a5ac615..d72748e 100644 --- a/src/ecdsautil.rs +++ b/src/ecdsautil.rs @@ -1,10 +1,14 @@ use der_parser::ber::BerObjectContent; +use ecdsa::elliptic_curve::pkcs8::LineEnding; use ecdsa::VerifyingKey; use p256::NistP256; use p256::ecdsa::signature::hazmat::PrehashVerifier; use p384::NistP384; use ecdsa::Signature; +use p256::pkcs8::EncodePrivateKey; use rust_util::XResult; +use spki::EncodePublicKey; +use crate::util::base64_encode; #[derive(Copy, Clone)] pub enum EcdsaAlgorithm { @@ -52,6 +56,22 @@ pub fn parse_ecdsa_r_and_s(signature_der: &[u8]) -> XResult<(Vec, Vec)> Ok((vec_r, vec_s)) } +pub fn generate_p256_keypair() -> XResult<(String, String, String)> { + let secret_key = p256::SecretKey::random(&mut rand::thread_rng()); + let secret_key_der_base64 = base64_encode(secret_key.to_pkcs8_der()?.as_bytes()); + let secret_key_pem = secret_key.to_pkcs8_pem(LineEnding::LF)?.to_string(); + let public_key_pem = secret_key.public_key().to_public_key_pem(LineEnding::LF)?; + Ok((secret_key_der_base64, secret_key_pem, public_key_pem)) +} + +pub fn generate_p384_keypair() -> XResult<(String, String, String)> { + let secret_key = p384::SecretKey::random(&mut rand::thread_rng()); + let secret_key_der_base64 = base64_encode(secret_key.to_pkcs8_der()?.as_bytes()); + let secret_key_pem = secret_key.to_pkcs8_pem(LineEnding::LF)?.to_string(); + let public_key_pem = secret_key.public_key().to_public_key_pem(LineEnding::LF)?; + Ok((secret_key_der_base64, secret_key_pem, public_key_pem)) +} + macro_rules! ecdsa_verify_signature { ($algo: tt, $pk_point: tt, $prehash: tt, $signature: tt) => ({ let verifying_key: VerifyingKey<$algo> = opt_result!(VerifyingKey::<$algo>::from_sec1_bytes($pk_point), "Parse public key failed: {}"); diff --git a/src/main.rs b/src/main.rs index aaf2811..81b7f6c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -53,6 +53,7 @@ mod cmd_u2fregister; mod cmd_u2fsign; mod cmd_verifyfile; mod cmd_parseecdsasignature; +mod cmd_generatekeypair; mod digest; mod ecdhutil; mod ecdsautil; @@ -144,6 +145,7 @@ fn inner_main() -> CommandError { Box::new(cmd_se_ecdh::CommandImpl), Box::new(cmd_ecverify::CommandImpl), Box::new(cmd_parseecdsasignature::CommandImpl), + Box::new(cmd_generatekeypair::CommandImpl), ]; #[allow(clippy::vec_init_then_push)]