From 8894a2156aacf2ea17aca43cb3bf661b6da1842b Mon Sep 17 00:00:00 2001 From: Hatter Jiang Date: Mon, 5 May 2025 11:22:28 +0800 Subject: [PATCH] feat: v1.12.6 --- Cargo.lock | 2 +- Cargo.toml | 2 +- examples/rsa.rs | 26 ++++++++ src/cmd_external_public_key.rs | 41 ++++++++---- src/cmd_external_sign.rs | 62 ++++++++++++----- src/cmd_keypair_generate.rs | 61 ++++++++++------- src/cmd_piv_meta.rs | 4 +- src/cmd_sign_jwt.rs | 1 + src/ecdsautil.rs | 5 +- src/keyutil.rs | 118 +++++++++++++++++++++++++++++---- src/rsautil.rs | 56 +++++++++++++++- 11 files changed, 302 insertions(+), 76 deletions(-) create mode 100644 examples/rsa.rs diff --git a/Cargo.lock b/Cargo.lock index 4f4f754..73323f1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -508,7 +508,7 @@ dependencies = [ [[package]] name = "card-cli" -version = "1.12.5" +version = "1.12.6" dependencies = [ "aes-gcm-stream", "authenticator 0.3.1", diff --git a/Cargo.toml b/Cargo.toml index cffdb6b..4cc694c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "card-cli" -version = "1.12.5" +version = "1.12.6" authors = ["Hatter Jiang "] edition = "2018" diff --git a/examples/rsa.rs b/examples/rsa.rs new file mode 100644 index 0000000..85a358c --- /dev/null +++ b/examples/rsa.rs @@ -0,0 +1,26 @@ +use base64::Engine; +use rand::rngs::OsRng; +use rsa::pkcs1::LineEnding; +use rsa::pkcs8::DecodePrivateKey; +use rsa::pkcs8::EncodePrivateKey; +use rsa::traits::PublicKeyParts; +use rsa::RsaPrivateKey; +use spki::EncodePublicKey; + +fn main() { + let key = RsaPrivateKey::new(&mut OsRng, 1024).unwrap(); + let pem = key.to_pkcs8_pem(LineEnding::LF).unwrap(); + println!("{}", pem.as_str()); + + let key2 = RsaPrivateKey::from_pkcs8_pem(pem.as_ref()).unwrap(); + + let pub_key = key2.to_public_key(); + let public_key_pem = pub_key.to_public_key_pem(LineEnding::LF).unwrap(); + println!("{}", public_key_pem); + + let n = pub_key.n(); + let e = pub_key.e(); + let url_safe = base64::engine::general_purpose::URL_SAFE_NO_PAD; + println!("n: {}", url_safe.encode(&n.to_bytes_be())); + println!("e: {}", url_safe.encode(&e.to_bytes_be())); +} diff --git a/src/cmd_external_public_key.rs b/src/cmd_external_public_key.rs index 8db06c4..2f64d5d 100644 --- a/src/cmd_external_public_key.rs +++ b/src/cmd_external_public_key.rs @@ -1,5 +1,5 @@ use crate::keyutil::{parse_key_uri, KeyUri, KeyUsage}; -use crate::util::base64_encode; +use crate::util::{base64_decode, base64_encode}; use crate::yubikeyutil::find_key_or_error; use crate::{cmdutil, ecdsautil, hmacutil, seutil, util, yubikeyutil}; use clap::{App, ArgMatches, SubCommand}; @@ -8,7 +8,10 @@ use rust_util::util_clap::{Command, CommandError}; use rust_util::XResult; use serde_json::Value; use std::collections::BTreeMap; +use rsa::RsaPrivateKey; +use spki::EncodePublicKey; use x509_parser::parse_x509_certificate; +use crate::pivutil::ToStr; pub struct CommandImpl; @@ -68,21 +71,31 @@ fn fetch_public_key(parameter: &str, serial_opt: &Option<&str>) -> XResult { - let private_key = hmacutil::try_hmac_decrypt_to_string(&key.hmac_enc_private_key)?; - let p256_public_key = ecdsautil::parse_p256_private_key_to_public_key(&private_key).ok(); - let p384_public_key = ecdsautil::parse_p384_private_key_to_public_key(&private_key).ok(); - let p521_public_key = ecdsautil::parse_p521_private_key_to_public_key(&private_key).ok(); + if key.algorithm.is_ecc() { + let private_key = hmacutil::try_hmac_decrypt_to_string(&key.hmac_enc_private_key)?; + let p256_public_key = ecdsautil::parse_p256_private_key_to_public_key(&private_key).ok(); + let p384_public_key = ecdsautil::parse_p384_private_key_to_public_key(&private_key).ok(); + let p521_public_key = ecdsautil::parse_p521_private_key_to_public_key(&private_key).ok(); - if let Some(p256_public_key) = p256_public_key { - return Ok(p256_public_key); + if let Some(p256_public_key) = p256_public_key { + return Ok(p256_public_key); + } + if let Some(p384_public_key) = p384_public_key { + return Ok(p384_public_key); + } + if let Some(p521_public_key) = p521_public_key { + return Ok(p521_public_key); + } + simple_error!("Invalid hmac enc private key") + } else if key.algorithm.is_rsa() { + use rsa::pkcs8::DecodePrivateKey; + let private_key = hmacutil::try_hmac_decrypt_to_string(&key.hmac_enc_private_key)?; + let private_key_der = base64_decode(&private_key)?; + let rsa_private_key = RsaPrivateKey::from_pkcs8_der(&private_key_der)?; + Ok(rsa_private_key.to_public_key().to_public_key_der()?.to_vec()) + } else { + simple_error!("Invalid algorithm: {}", key.algorithm.to_str()) } - if let Some(p384_public_key) = p384_public_key { - return Ok(p384_public_key); - } - if let Some(p521_public_key) = p521_public_key { - return Ok(p521_public_key); - } - simple_error!("Invalid hmac enc private key") } } } diff --git a/src/cmd_external_sign.rs b/src/cmd_external_sign.rs index 7c3616b..5314e7f 100644 --- a/src/cmd_external_sign.rs +++ b/src/cmd_external_sign.rs @@ -1,17 +1,19 @@ use crate::cmd_sign_jwt::digest_by_jwt_algorithm; -use crate::keyutil::{parse_key_uri, KeyUri, KeyUsage, YubikeyPivKey}; +use crate::cmd_sign_jwt_soft::{convert_jwt_algorithm_to_ecdsa_algorithm, parse_ecdsa_private_key}; +use crate::ecdsautil::EcdsaSignType; +use crate::keyutil::{parse_key_uri, KeyAlgorithmId, KeyUri, KeyUsage, YubikeyPivKey}; use crate::pivutil::ToStr; +use crate::rsautil::RsaSignAlgorithm; use crate::util::{base64_decode, base64_encode}; -use crate::{cmdutil, ecdsautil, hmacutil, pivutil, seutil, util, yubikeyutil}; +use crate::{cmdutil, ecdsautil, hmacutil, pivutil, rsautil, seutil, util, yubikeyutil}; use clap::{App, ArgMatches, SubCommand}; use jwt::AlgorithmType; +use rsa::RsaPrivateKey; use rust_util::util_clap::{Command, CommandError}; use rust_util::XResult; use serde_json::Value; use std::collections::BTreeMap; -use yubikey::piv::{sign_data, AlgorithmId}; -use crate::cmd_sign_jwt_soft::{convert_jwt_algorithm_to_ecdsa_algorithm, parse_ecdsa_private_key}; -use crate::ecdsautil::EcdsaSignType; +use yubikey::piv::sign_data; pub struct CommandImpl; @@ -79,22 +81,45 @@ fn sign(sub_arg_matches: &ArgMatches) -> XResult> { ); } + let algorithm = opt_value_result!( + KeyAlgorithmId::to_algorithm_id(key.algorithm), + "Yubikey not supported algorithm: {}", + key.algorithm.to_str() + ); let raw_in = digest_by_jwt_algorithm(jwt_algorithm, &message_bytes)?; let signed_data = opt_result!( - sign_data(&mut yk, &raw_in, key.algorithm, key.slot), + sign_data(&mut yk, &raw_in, algorithm, key.slot), "Sign YubiKey failed: {}" ); Ok(signed_data.to_vec()) } KeyUri::YubikeyHmacEncSoftKey(key) => { - let private_key = hmacutil::try_hmac_decrypt_to_string(&key.hmac_enc_private_key)?; - let (jwt_algorithm, private_key_d) = parse_ecdsa_private_key(&private_key)?; + if key.algorithm.is_ecc() { + let private_key = hmacutil::try_hmac_decrypt_to_string(&key.hmac_enc_private_key)?; + let (jwt_algorithm, private_key_d) = parse_ecdsa_private_key(&private_key)?; - let raw_in = digest_by_jwt_algorithm(jwt_algorithm, &message_bytes)?; - let ecdsa_algorithm = convert_jwt_algorithm_to_ecdsa_algorithm(jwt_algorithm)?; - let signed_data = ecdsautil::ecdsa_sign(ecdsa_algorithm, &private_key_d, &raw_in, EcdsaSignType::Der)?; + let raw_in = digest_by_jwt_algorithm(jwt_algorithm, &message_bytes)?; + let ecdsa_algorithm = convert_jwt_algorithm_to_ecdsa_algorithm(jwt_algorithm)?; + let signed_data = ecdsautil::ecdsa_sign( + ecdsa_algorithm, + &private_key_d, + &raw_in, + EcdsaSignType::Der, + )?; - Ok(signed_data) + Ok(signed_data) + } else if key.algorithm.is_rsa() { + use rsa::pkcs8::DecodePrivateKey; + let private_key = hmacutil::try_hmac_decrypt_to_string(&key.hmac_enc_private_key)?; + let private_key_der = base64_decode(&private_key)?; + let rsa_private_key = RsaPrivateKey::from_pkcs8_der(&private_key_der)?; + + let rsa_sign_algorithm = + opt_value_result!(RsaSignAlgorithm::from_str(alg), "Invalid --alg: {}", alg); + rsautil::sign(&rsa_private_key, rsa_sign_algorithm, &message_bytes) + } else { + simple_error!("Invalid algorithm: {}", key.algorithm.to_str()) + } } } } @@ -103,20 +128,23 @@ fn get_jwt_algorithm(key: &YubikeyPivKey, alg: &str) -> XResult { let jwt_algorithm = match alg { "ES256" => AlgorithmType::Es256, "ES384" => AlgorithmType::Es384, + "ES512" => AlgorithmType::Es512, "RS256" => AlgorithmType::Rs256, _ => return simple_error!("Invalid alg: {}", alg), }; - if key.algorithm == AlgorithmId::Rsa1024 { + if key.algorithm == KeyAlgorithmId::Rsa1024 { return simple_error!("Invalid algorithm: RSA1024"); } let is_p256_mismatch = - key.algorithm == AlgorithmId::EccP256 && jwt_algorithm != AlgorithmType::Es256; + key.algorithm == KeyAlgorithmId::EccP256 && jwt_algorithm != AlgorithmType::Es256; let is_p384_mismatch = - key.algorithm == AlgorithmId::EccP384 && jwt_algorithm != AlgorithmType::Es384; + key.algorithm == KeyAlgorithmId::EccP384 && jwt_algorithm != AlgorithmType::Es384; + let is_p521_mismatch = + key.algorithm == KeyAlgorithmId::EccP521 && jwt_algorithm != AlgorithmType::Es512; let is_rsa_mismatch = - key.algorithm == AlgorithmId::Rsa2048 && jwt_algorithm != AlgorithmType::Rs256; + key.algorithm == KeyAlgorithmId::Rsa2048 && jwt_algorithm != AlgorithmType::Rs256; - if is_p256_mismatch || is_p384_mismatch || is_rsa_mismatch { + if is_p256_mismatch || is_p384_mismatch || is_p521_mismatch || is_rsa_mismatch { return simple_error!("Invalid algorithm: {} vs {}", key.algorithm.to_str(), alg); } Ok(jwt_algorithm) diff --git a/src/cmd_keypair_generate.rs b/src/cmd_keypair_generate.rs index cb8f1b0..ab3bab1 100644 --- a/src/cmd_keypair_generate.rs +++ b/src/cmd_keypair_generate.rs @@ -1,12 +1,11 @@ +use crate::ecdsautil::EcdsaAlgorithm; use crate::keychain::{KeychainKey, KeychainKeyValue}; -use crate::{cmdutil, ecdsautil, hmacutil, util, yubikeyutil}; +use crate::keyutil::{KeyAlgorithmId, KeyUri, YubikeyHmacEncSoftKey}; +use crate::util::base64_encode; +use crate::{cmdutil, ecdsautil, hmacutil, rsautil, util, yubikeyutil}; use clap::{App, Arg, ArgMatches, SubCommand}; use rust_util::util_clap::{Command, CommandError}; use std::collections::BTreeMap; -use yubikey::piv::AlgorithmId; -use crate::ecdsautil::EcdsaAlgorithm; -use crate::keyutil::{KeyUri, YubikeyHmacEncSoftKey}; -use crate::util::base64_encode; pub struct CommandImpl; @@ -23,7 +22,7 @@ impl Command for CommandImpl { .long("type") .required(true) .takes_value(true) - .help("Key type (e.g. p256, p384, p521)"), + .help("Key type (e.g. p256, p384, p521, rsa1024, rsa2048)"), ) .arg( Arg::with_name("with-hmac-encrypt") @@ -49,20 +48,26 @@ impl Command for CommandImpl { } let ecdsa_algorithm = match key_type.as_str() { - "p256" => EcdsaAlgorithm::P256, - "p384" => EcdsaAlgorithm::P384, - "p521" => EcdsaAlgorithm::P521, - _ => { - return simple_error!("Key type must be p256 or p384"); - } + "p256" => Some(EcdsaAlgorithm::P256), + "p384" => Some(EcdsaAlgorithm::P384), + "p521" => Some(EcdsaAlgorithm::P521), + _ => None, + }; + let rsa_bit_size: Option = match key_type.as_str() { + "rsa1024" => Some(1024), + "rsa2048" => Some(2048), + _ => None, }; - let (pkcs8_base64, - secret_key_pem, - public_key_pem, - public_key_der, - jwk_ec_key - ) = ecdsautil::generate_ecdsa_keypair(ecdsa_algorithm)?; + let ( + pkcs8_base64, secret_key_pem, public_key_pem, public_key_der, jwk_key + ) = if let Some(ecdsa_algorithm) = ecdsa_algorithm { + ecdsautil::generate_ecdsa_keypair(ecdsa_algorithm)? + } else if let Some(rsa_bit_size) = rsa_bit_size { + rsautil::generate_rsa_keypair(rsa_bit_size)? + } else { + return simple_error!("Unsupported key type: {}", key_type); + }; let (pkcs8_base64, secret_key_pem) = if with_hmac_encrypt { ( @@ -80,7 +85,7 @@ impl Command for CommandImpl { pkcs8_base64: pkcs8_base64.clone(), secret_key_pem: secret_key_pem.clone(), public_key_pem: public_key_pem.clone(), - public_key_jwk: jwk_ec_key.to_string(), + public_key_jwk: jwk_key.clone(), }; let keychain_key_value_json = serde_json::to_string(&keychain_key_value)?; @@ -98,18 +103,24 @@ impl Command for CommandImpl { json.insert("private_key_base64", pkcs8_base64.clone()); json.insert("private_key_pem", secret_key_pem); let algorithm_id = match key_type.as_str() { - "p256" => Some(AlgorithmId::EccP256), - "p384" => Some(AlgorithmId::EccP384), + "p256" => Some(KeyAlgorithmId::EccP256), + "p384" => Some(KeyAlgorithmId::EccP384), + "p521" => Some(KeyAlgorithmId::EccP521), + "rsa1024" => Some(KeyAlgorithmId::Rsa1024), + "rsa2048" => Some(KeyAlgorithmId::Rsa2048), _ => None, }; if let (true, Some(algorithm_id)) = (with_hmac_encrypt, algorithm_id) { let yk = yubikeyutil::open_yubikey()?; let yubikey_hmac_enc_soft_key = YubikeyHmacEncSoftKey { - key_name: format!("yubikey{}-{}", yk.version().major, yk.serial().0), + key_name: format!("yubikey{}-{}", yk.version().major, yk.serial().0), algorithm: algorithm_id, hmac_enc_private_key: pkcs8_base64, }; - json.insert("key_uri", KeyUri::YubikeyHmacEncSoftKey(yubikey_hmac_enc_soft_key).to_string()); + json.insert( + "key_uri", + KeyUri::YubikeyHmacEncSoftKey(yubikey_hmac_enc_soft_key).to_string(), + ); } } Some(keychain_key_uri) => { @@ -118,7 +129,7 @@ impl Command for CommandImpl { } json.insert("public_key_pem", public_key_pem); json.insert("public_key_base64", public_key_base64); - json.insert("public_key_jwk", jwk_ec_key.to_string()); + json.insert("public_key_jwk", jwk_key); util::print_pretty_json(&json); } else { @@ -133,7 +144,7 @@ impl Command for CommandImpl { } information!("Public key PEM:\n{}", public_key_pem); information!("Public key Base64:\n{}\n", public_key_base64); - information!("Public key JWK:\n{}", jwk_ec_key.to_string()); + information!("Public key JWK:\n{}", jwk_key); } Ok(None) diff --git a/src/cmd_piv_meta.rs b/src/cmd_piv_meta.rs index e9df05f..65eb89a 100644 --- a/src/cmd_piv_meta.rs +++ b/src/cmd_piv_meta.rs @@ -10,7 +10,7 @@ use yubikey::Key; use yubikey::piv::{AlgorithmId, metadata}; use crate::{cmdutil, pivutil, util, yubikeyutil}; -use crate::keyutil::{KeyUri, YubikeyPivKey}; +use crate::keyutil::{KeyAlgorithmId, KeyUri, YubikeyPivKey}; use crate::pivutil::{get_algorithm_id_by_certificate, slot_equals, ToStr}; use crate::pkiutil::bytes_to_pem; use crate::sshutil::SshVecWriter; @@ -106,7 +106,7 @@ impl Command for CommandImpl { let yubikey_piv_key = YubikeyPivKey { key_name: format!("yubikey{}-{}", yk.version().major, yk.serial().0), - algorithm: algorithm_id, + algorithm: KeyAlgorithmId::from_algorithm_id(algorithm_id), slot: slot_id, }; json.insert("key_uri", KeyUri::YubikeyPivKey(yubikey_piv_key).to_string()); diff --git a/src/cmd_sign_jwt.rs b/src/cmd_sign_jwt.rs index 755cc3f..b5bae08 100644 --- a/src/cmd_sign_jwt.rs +++ b/src/cmd_sign_jwt.rs @@ -133,6 +133,7 @@ pub fn digest_by_jwt_algorithm(jwt_algorithm: AlgorithmType, tobe_signed: &[u8]) } AlgorithmType::Es256 => digestutil::sha256_bytes(tobe_signed), AlgorithmType::Es384 => digestutil::sha384_bytes(tobe_signed), + AlgorithmType::Es512 => digestutil::sha512_bytes(tobe_signed), _ => return simple_error!("SHOULD NOT HAPPEN: {:?}", jwt_algorithm), }) } diff --git a/src/ecdsautil.rs b/src/ecdsautil.rs index a95ff30..f857049 100644 --- a/src/ecdsautil.rs +++ b/src/ecdsautil.rs @@ -3,7 +3,6 @@ use ecdsa::elliptic_curve::pkcs8::LineEnding; use ecdsa::VerifyingKey; use p256::NistP256; use p256::ecdsa::signature::hazmat::PrehashVerifier; -use p256::elliptic_curve::JwkEcKey; use p384::NistP384; use p256::pkcs8::EncodePrivateKey; use p521::NistP521; @@ -78,12 +77,12 @@ macro_rules! generate_inner_ecdsa_keypair { 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)?; let public_key_der = secret_key.public_key().to_public_key_der()?.to_vec(); - let jwk_ec_key = secret_key.public_key().to_jwk(); + let jwk_ec_key = secret_key.public_key().to_jwk().to_string(); Ok((secret_key_der_base64, secret_key_pem, public_key_pem, public_key_der, jwk_ec_key)) }) } -pub fn generate_ecdsa_keypair(algo: EcdsaAlgorithm) -> XResult<(String, String, String, Vec, JwkEcKey)> { +pub fn generate_ecdsa_keypair(algo: EcdsaAlgorithm) -> XResult<(String, String, String, Vec, String)> { match algo { EcdsaAlgorithm::P256 => generate_inner_ecdsa_keypair!(p256), EcdsaAlgorithm::P384 => generate_inner_ecdsa_keypair!(p384), diff --git a/src/keyutil.rs b/src/keyutil.rs index 735f46c..affc36a 100644 --- a/src/keyutil.rs +++ b/src/keyutil.rs @@ -1,7 +1,7 @@ +use crate::pivutil::{FromStr, ToStr}; use regex::Regex; use rust_util::XResult; use yubikey::piv::{AlgorithmId, SlotId}; -use crate::pivutil::{ToStr, FromStr}; // reference: https://git.hatter.ink/hatter/card-cli/issues/6 #[derive(Debug)] @@ -55,6 +55,78 @@ impl ToString for KeyUri { } } +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum KeyAlgorithmId { + Rsa1024, + Rsa2048, + EccP256, + EccP384, + EccP521, +} + +impl KeyAlgorithmId { + pub fn from_algorithm_id(algorithm_id: AlgorithmId) -> Self { + match algorithm_id { + AlgorithmId::Rsa1024 => Self::Rsa1024, + AlgorithmId::Rsa2048 => Self::Rsa2048, + AlgorithmId::EccP256 => Self::EccP256, + AlgorithmId::EccP384 => Self::EccP384, + } + } + + pub fn to_algorithm_id(self) -> Option { + match self { + KeyAlgorithmId::Rsa1024 => Some(AlgorithmId::Rsa1024), + KeyAlgorithmId::Rsa2048 => Some(AlgorithmId::Rsa2048), + KeyAlgorithmId::EccP256 => Some(AlgorithmId::EccP256), + KeyAlgorithmId::EccP384 => Some(AlgorithmId::EccP384), + KeyAlgorithmId::EccP521 => None, + } + } + + pub fn is_rsa(&self) -> bool { + match self { + KeyAlgorithmId::Rsa1024 | KeyAlgorithmId::Rsa2048 => true, + KeyAlgorithmId::EccP256 | KeyAlgorithmId::EccP384 | KeyAlgorithmId::EccP521 => false, + } + } + + pub fn is_ecc(&self) -> bool { + match self { + KeyAlgorithmId::Rsa1024 | KeyAlgorithmId::Rsa2048 => false, + KeyAlgorithmId::EccP256 | KeyAlgorithmId::EccP384 | KeyAlgorithmId::EccP521 => true, + } + } +} + +impl FromStr for KeyAlgorithmId { + fn from_str(s: &str) -> Option + where + Self: Sized, + { + match s { + "rsa1024" => Some(KeyAlgorithmId::Rsa1024), + "rsa2048" => Some(KeyAlgorithmId::Rsa2048), + "p256" => Some(KeyAlgorithmId::EccP256), + "p384" => Some(KeyAlgorithmId::EccP384), + "p521" => Some(KeyAlgorithmId::EccP521), + _ => None, + } + } +} + +impl ToStr for KeyAlgorithmId { + fn to_str(&self) -> &str { + match self { + KeyAlgorithmId::Rsa1024 => "rsa1024", + KeyAlgorithmId::Rsa2048 => "rsa2048", + KeyAlgorithmId::EccP256 => "p256", + KeyAlgorithmId::EccP384 => "p384", + KeyAlgorithmId::EccP521 => "p521", + } + } +} + #[derive(Debug, PartialEq, Eq)] pub enum KeyUsage { Any, @@ -78,8 +150,9 @@ impl ToString for KeyUsage { match self { KeyUsage::Any => "*", KeyUsage::Singing => "signing", - KeyUsage::KeyAgreement => "key_agreement" - }.to_string() + KeyUsage::KeyAgreement => "key_agreement", + } + .to_string() } } @@ -95,7 +168,7 @@ pub struct SecureEnclaveKey { #[derive(Debug)] pub struct YubikeyPivKey { pub key_name: String, - pub algorithm: AlgorithmId, + pub algorithm: KeyAlgorithmId, pub slot: SlotId, } @@ -103,7 +176,7 @@ pub struct YubikeyPivKey { #[derive(Debug)] pub struct YubikeyHmacEncSoftKey { pub key_name: String, - pub algorithm: AlgorithmId, + pub algorithm: KeyAlgorithmId, pub hmac_enc_private_key: String, } @@ -142,8 +215,16 @@ pub fn parse_key_uri(key_uri: &str) -> XResult { if "" != usage { return simple_error!("Key uri's usage must be empty."); } - let algorithm = opt_value_result!(AlgorithmId::from_str(algorithm), "Invalid algorithm id: {}", algorithm); - let slot = opt_value_result!(SlotId::from_str(left_part), "Invalid slot id: {}", left_part); + let algorithm = opt_value_result!( + KeyAlgorithmId::from_str(algorithm), + "Invalid algorithm id: {}", + algorithm + ); + let slot = opt_value_result!( + SlotId::from_str(left_part), + "Invalid slot id: {}", + left_part + ); let parsed_key_uri = KeyUri::YubikeyPivKey(YubikeyPivKey { key_name: host_or_name.to_string(), algorithm, @@ -156,7 +237,11 @@ pub fn parse_key_uri(key_uri: &str) -> XResult { if "" != usage { return simple_error!("Key uri's usage must be empty."); } - let algorithm = opt_value_result!(AlgorithmId::from_str(algorithm), "Invalid algorithm id: {}", algorithm); + let algorithm = opt_value_result!( + KeyAlgorithmId::from_str(algorithm), + "Invalid algorithm id: {}", + algorithm + ); let hmac_enc_private_key = left_part.to_string(); let parsed_key_uri = KeyUri::YubikeyHmacEncSoftKey(YubikeyHmacEncSoftKey { key_name: host_or_name.to_string(), @@ -174,7 +259,10 @@ pub fn parse_key_uri(key_uri: &str) -> XResult { fn test_parse_key_uri_01() { let se_key_uri = parse_key_uri("key://hatter-mac-pro:se/p256:signing:BASE64(dataRepresentation)").unwrap(); - assert_eq!("key://hatter-mac-pro:se/p256:signing:BASE64(dataRepresentation)", se_key_uri.to_string()); + assert_eq!( + "key://hatter-mac-pro:se/p256:signing:BASE64(dataRepresentation)", + se_key_uri.to_string() + ); match se_key_uri { KeyUri::SecureEnclaveKey(se_key_uri) => { assert_eq!("hatter-mac-pro", se_key_uri.host); @@ -192,7 +280,10 @@ fn test_parse_key_uri_02() { let se_key_uri = parse_key_uri("key://hatter-mac-m1:se/p256:key_agreement:BASE64(dataRepresentation)") .unwrap(); - assert_eq!("key://hatter-mac-m1:se/p256:key_agreement:BASE64(dataRepresentation)", se_key_uri.to_string()); + assert_eq!( + "key://hatter-mac-m1:se/p256:key_agreement:BASE64(dataRepresentation)", + se_key_uri.to_string() + ); match se_key_uri { KeyUri::SecureEnclaveKey(se_key_uri) => { assert_eq!("hatter-mac-m1", se_key_uri.host); @@ -208,11 +299,14 @@ fn test_parse_key_uri_02() { #[test] fn test_parse_key_uri_03() { let se_key_uri = parse_key_uri("key://yubikey-5n:piv/p256::9a").unwrap(); - assert_eq!("key://yubikey-5n:piv/p256::authentication", se_key_uri.to_string()); + assert_eq!( + "key://yubikey-5n:piv/p256::authentication", + se_key_uri.to_string() + ); match se_key_uri { KeyUri::YubikeyPivKey(piv_key_uri) => { assert_eq!("yubikey-5n", piv_key_uri.key_name); - assert_eq!(AlgorithmId::EccP256, piv_key_uri.algorithm); + assert_eq!(KeyAlgorithmId::EccP256, piv_key_uri.algorithm); assert_eq!(SlotId::Authentication, piv_key_uri.slot); } _ => { diff --git a/src/rsautil.rs b/src/rsautil.rs index 78c4e58..2e9e6bf 100644 --- a/src/rsautil.rs +++ b/src/rsautil.rs @@ -1,15 +1,66 @@ use std::collections::HashMap; +use ecdsa::elliptic_curve::rand_core::OsRng; use openssl::bn::{BigNum, BigNumContext}; use openssl::pkey::PKey; use openssl::rsa::{Padding, Rsa}; -use rsa::RsaPublicKey; +use rsa::{Pkcs1v15Sign, RsaPrivateKey, RsaPublicKey}; use rust_util::{util_msg, XResult}; use rust_util::util_msg::MessageType; use spki::DecodePublicKey; use rsa::pkcs1::DecodeRsaPublicKey; use rsa::traits::PublicKeyParts; +use spki::EncodePublicKey; +use rsa::pkcs1::LineEnding; +use rsa::pkcs8::EncodePrivateKey; +use sha2::{Sha256, Sha384, Sha512}; +use crate::digestutil; use crate::util::{base64_decode, base64_encode}; +pub enum RsaSignAlgorithm { + Rs256, + Rs384, + Rs512, +} + +impl RsaSignAlgorithm { + pub fn from_str(alg: &str) -> Option { + match alg { + "RS256" => Some(RsaSignAlgorithm::Rs256), + "RS384" => Some(RsaSignAlgorithm::Rs384), + "RS512" => Some(RsaSignAlgorithm::Rs512), + _ => None + } + } +} + +pub fn sign(rsa_private_key: &RsaPrivateKey, rsa_sign_algorithm: RsaSignAlgorithm, message: &[u8]) -> XResult> { + match rsa_sign_algorithm { + RsaSignAlgorithm::Rs256 => { + let raw_in = digestutil::sha256_bytes(&message); + Ok(rsa_private_key.sign(Pkcs1v15Sign::new::(), &raw_in)?) + } + RsaSignAlgorithm::Rs384 => { + let raw_in = digestutil::sha384_bytes(&message); + Ok(rsa_private_key.sign(Pkcs1v15Sign::new::(), &raw_in)?) + } + RsaSignAlgorithm::Rs512 => { + let raw_in = digestutil::sha512_bytes(&message); + Ok(rsa_private_key.sign(Pkcs1v15Sign::new::(), &raw_in)?) + } + } +} + +pub fn generate_rsa_keypair(bit_size: usize) -> XResult<(String, String, String, Vec, String)> { + let rsa_private_key = opt_result!(RsaPrivateKey::new(&mut OsRng, bit_size), "Generate RSA private key failed: {}"); + let rsa_public_key = rsa_private_key.to_public_key(); + let secret_key_der_base64 = base64_encode(rsa_private_key.to_pkcs8_der()?.as_bytes()); + let secret_key_pem = rsa_private_key.to_pkcs8_pem(LineEnding::LF)?.to_string(); + let public_key_pem = rsa_public_key.to_public_key_pem(LineEnding::LF)?; + let public_key_der = rsa_public_key.to_public_key_der()?.to_vec(); + let jwk_ec_key = rsa_public_key_to_jwk(&rsa_public_key)?; + Ok((secret_key_der_base64, secret_key_pem, public_key_pem, public_key_der, jwk_ec_key)) +} + #[derive(Debug)] pub struct RsaCrt { // n = p * q @@ -160,7 +211,10 @@ fn pkcs1_padding_for_sign(bs: &[u8], bit_len: usize) -> XResult> { pub fn convert_rsa_to_jwk(public_key: &str) -> XResult { let rsa_public_key = try_parse_rsa(public_key)?; + rsa_public_key_to_jwk(&rsa_public_key) +} +pub fn rsa_public_key_to_jwk(rsa_public_key: &RsaPublicKey) -> XResult { let e_bytes = rsa_public_key.e().to_bytes_be(); let n_bytes = rsa_public_key.n().to_bytes_be();