feat: v1.12.6

This commit is contained in:
2025-05-05 11:22:28 +08:00
parent e52e42d48c
commit 8894a2156a
11 changed files with 302 additions and 76 deletions

2
Cargo.lock generated
View File

@@ -508,7 +508,7 @@ dependencies = [
[[package]] [[package]]
name = "card-cli" name = "card-cli"
version = "1.12.5" version = "1.12.6"
dependencies = [ dependencies = [
"aes-gcm-stream", "aes-gcm-stream",
"authenticator 0.3.1", "authenticator 0.3.1",

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "card-cli" name = "card-cli"
version = "1.12.5" version = "1.12.6"
authors = ["Hatter Jiang <jht5945@gmail.com>"] authors = ["Hatter Jiang <jht5945@gmail.com>"]
edition = "2018" edition = "2018"

26
examples/rsa.rs Normal file
View File

@@ -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()));
}

View File

@@ -1,5 +1,5 @@
use crate::keyutil::{parse_key_uri, KeyUri, KeyUsage}; 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::yubikeyutil::find_key_or_error;
use crate::{cmdutil, ecdsautil, hmacutil, seutil, util, yubikeyutil}; use crate::{cmdutil, ecdsautil, hmacutil, seutil, util, yubikeyutil};
use clap::{App, ArgMatches, SubCommand}; use clap::{App, ArgMatches, SubCommand};
@@ -8,7 +8,10 @@ use rust_util::util_clap::{Command, CommandError};
use rust_util::XResult; use rust_util::XResult;
use serde_json::Value; use serde_json::Value;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use rsa::RsaPrivateKey;
use spki::EncodePublicKey;
use x509_parser::parse_x509_certificate; use x509_parser::parse_x509_certificate;
use crate::pivutil::ToStr;
pub struct CommandImpl; pub struct CommandImpl;
@@ -68,21 +71,31 @@ fn fetch_public_key(parameter: &str, serial_opt: &Option<&str>) -> XResult<Vec<u
simple_error!("Slot {} not found", key.slot) simple_error!("Slot {} not found", key.slot)
} }
KeyUri::YubikeyHmacEncSoftKey(key) => { KeyUri::YubikeyHmacEncSoftKey(key) => {
let private_key = hmacutil::try_hmac_decrypt_to_string(&key.hmac_enc_private_key)?; if key.algorithm.is_ecc() {
let p256_public_key = ecdsautil::parse_p256_private_key_to_public_key(&private_key).ok(); let private_key = hmacutil::try_hmac_decrypt_to_string(&key.hmac_enc_private_key)?;
let p384_public_key = ecdsautil::parse_p384_private_key_to_public_key(&private_key).ok(); let p256_public_key = ecdsautil::parse_p256_private_key_to_public_key(&private_key).ok();
let p521_public_key = ecdsautil::parse_p521_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 { if let Some(p256_public_key) = p256_public_key {
return Ok(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")
} }
} }
} }

View File

@@ -1,17 +1,19 @@
use crate::cmd_sign_jwt::digest_by_jwt_algorithm; 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::pivutil::ToStr;
use crate::rsautil::RsaSignAlgorithm;
use crate::util::{base64_decode, base64_encode}; 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 clap::{App, ArgMatches, SubCommand};
use jwt::AlgorithmType; use jwt::AlgorithmType;
use rsa::RsaPrivateKey;
use rust_util::util_clap::{Command, CommandError}; use rust_util::util_clap::{Command, CommandError};
use rust_util::XResult; use rust_util::XResult;
use serde_json::Value; use serde_json::Value;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use yubikey::piv::{sign_data, AlgorithmId}; use yubikey::piv::sign_data;
use crate::cmd_sign_jwt_soft::{convert_jwt_algorithm_to_ecdsa_algorithm, parse_ecdsa_private_key};
use crate::ecdsautil::EcdsaSignType;
pub struct CommandImpl; pub struct CommandImpl;
@@ -79,22 +81,45 @@ fn sign(sub_arg_matches: &ArgMatches) -> XResult<Vec<u8>> {
); );
} }
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 raw_in = digest_by_jwt_algorithm(jwt_algorithm, &message_bytes)?;
let signed_data = opt_result!( 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: {}" "Sign YubiKey failed: {}"
); );
Ok(signed_data.to_vec()) Ok(signed_data.to_vec())
} }
KeyUri::YubikeyHmacEncSoftKey(key) => { KeyUri::YubikeyHmacEncSoftKey(key) => {
let private_key = hmacutil::try_hmac_decrypt_to_string(&key.hmac_enc_private_key)?; if key.algorithm.is_ecc() {
let (jwt_algorithm, private_key_d) = parse_ecdsa_private_key(&private_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)?;
let raw_in = digest_by_jwt_algorithm(jwt_algorithm, &message_bytes)?; let raw_in = digest_by_jwt_algorithm(jwt_algorithm, &message_bytes)?;
let ecdsa_algorithm = convert_jwt_algorithm_to_ecdsa_algorithm(jwt_algorithm)?; 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 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<AlgorithmType> {
let jwt_algorithm = match alg { let jwt_algorithm = match alg {
"ES256" => AlgorithmType::Es256, "ES256" => AlgorithmType::Es256,
"ES384" => AlgorithmType::Es384, "ES384" => AlgorithmType::Es384,
"ES512" => AlgorithmType::Es512,
"RS256" => AlgorithmType::Rs256, "RS256" => AlgorithmType::Rs256,
_ => return simple_error!("Invalid alg: {}", alg), _ => return simple_error!("Invalid alg: {}", alg),
}; };
if key.algorithm == AlgorithmId::Rsa1024 { if key.algorithm == KeyAlgorithmId::Rsa1024 {
return simple_error!("Invalid algorithm: RSA1024"); return simple_error!("Invalid algorithm: RSA1024");
} }
let is_p256_mismatch = let is_p256_mismatch =
key.algorithm == AlgorithmId::EccP256 && jwt_algorithm != AlgorithmType::Es256; key.algorithm == KeyAlgorithmId::EccP256 && jwt_algorithm != AlgorithmType::Es256;
let is_p384_mismatch = 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 = 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); return simple_error!("Invalid algorithm: {} vs {}", key.algorithm.to_str(), alg);
} }
Ok(jwt_algorithm) Ok(jwt_algorithm)

View File

@@ -1,12 +1,11 @@
use crate::ecdsautil::EcdsaAlgorithm;
use crate::keychain::{KeychainKey, KeychainKeyValue}; 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 clap::{App, Arg, ArgMatches, SubCommand};
use rust_util::util_clap::{Command, CommandError}; use rust_util::util_clap::{Command, CommandError};
use std::collections::BTreeMap; 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; pub struct CommandImpl;
@@ -23,7 +22,7 @@ impl Command for CommandImpl {
.long("type") .long("type")
.required(true) .required(true)
.takes_value(true) .takes_value(true)
.help("Key type (e.g. p256, p384, p521)"), .help("Key type (e.g. p256, p384, p521, rsa1024, rsa2048)"),
) )
.arg( .arg(
Arg::with_name("with-hmac-encrypt") Arg::with_name("with-hmac-encrypt")
@@ -49,20 +48,26 @@ impl Command for CommandImpl {
} }
let ecdsa_algorithm = match key_type.as_str() { let ecdsa_algorithm = match key_type.as_str() {
"p256" => EcdsaAlgorithm::P256, "p256" => Some(EcdsaAlgorithm::P256),
"p384" => EcdsaAlgorithm::P384, "p384" => Some(EcdsaAlgorithm::P384),
"p521" => EcdsaAlgorithm::P521, "p521" => Some(EcdsaAlgorithm::P521),
_ => { _ => None,
return simple_error!("Key type must be p256 or p384"); };
} let rsa_bit_size: Option<usize> = match key_type.as_str() {
"rsa1024" => Some(1024),
"rsa2048" => Some(2048),
_ => None,
}; };
let (pkcs8_base64, let (
secret_key_pem, pkcs8_base64, secret_key_pem, public_key_pem, public_key_der, jwk_key
public_key_pem, ) = if let Some(ecdsa_algorithm) = ecdsa_algorithm {
public_key_der, ecdsautil::generate_ecdsa_keypair(ecdsa_algorithm)?
jwk_ec_key } else if let Some(rsa_bit_size) = rsa_bit_size {
) = ecdsautil::generate_ecdsa_keypair(ecdsa_algorithm)?; 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 { let (pkcs8_base64, secret_key_pem) = if with_hmac_encrypt {
( (
@@ -80,7 +85,7 @@ impl Command for CommandImpl {
pkcs8_base64: pkcs8_base64.clone(), pkcs8_base64: pkcs8_base64.clone(),
secret_key_pem: secret_key_pem.clone(), secret_key_pem: secret_key_pem.clone(),
public_key_pem: public_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)?; 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_base64", pkcs8_base64.clone());
json.insert("private_key_pem", secret_key_pem); json.insert("private_key_pem", secret_key_pem);
let algorithm_id = match key_type.as_str() { let algorithm_id = match key_type.as_str() {
"p256" => Some(AlgorithmId::EccP256), "p256" => Some(KeyAlgorithmId::EccP256),
"p384" => Some(AlgorithmId::EccP384), "p384" => Some(KeyAlgorithmId::EccP384),
"p521" => Some(KeyAlgorithmId::EccP521),
"rsa1024" => Some(KeyAlgorithmId::Rsa1024),
"rsa2048" => Some(KeyAlgorithmId::Rsa2048),
_ => None, _ => None,
}; };
if let (true, Some(algorithm_id)) = (with_hmac_encrypt, algorithm_id) { if let (true, Some(algorithm_id)) = (with_hmac_encrypt, algorithm_id) {
let yk = yubikeyutil::open_yubikey()?; let yk = yubikeyutil::open_yubikey()?;
let yubikey_hmac_enc_soft_key = YubikeyHmacEncSoftKey { 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, algorithm: algorithm_id,
hmac_enc_private_key: pkcs8_base64, 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) => { Some(keychain_key_uri) => {
@@ -118,7 +129,7 @@ impl Command for CommandImpl {
} }
json.insert("public_key_pem", public_key_pem); json.insert("public_key_pem", public_key_pem);
json.insert("public_key_base64", public_key_base64); 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); util::print_pretty_json(&json);
} else { } else {
@@ -133,7 +144,7 @@ impl Command for CommandImpl {
} }
information!("Public key PEM:\n{}", public_key_pem); information!("Public key PEM:\n{}", public_key_pem);
information!("Public key Base64:\n{}\n", public_key_base64); 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) Ok(None)

View File

@@ -10,7 +10,7 @@ use yubikey::Key;
use yubikey::piv::{AlgorithmId, metadata}; use yubikey::piv::{AlgorithmId, metadata};
use crate::{cmdutil, pivutil, util, yubikeyutil}; 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::pivutil::{get_algorithm_id_by_certificate, slot_equals, ToStr};
use crate::pkiutil::bytes_to_pem; use crate::pkiutil::bytes_to_pem;
use crate::sshutil::SshVecWriter; use crate::sshutil::SshVecWriter;
@@ -106,7 +106,7 @@ impl Command for CommandImpl {
let yubikey_piv_key = YubikeyPivKey { let yubikey_piv_key = YubikeyPivKey {
key_name: format!("yubikey{}-{}", yk.version().major, yk.serial().0), key_name: format!("yubikey{}-{}", yk.version().major, yk.serial().0),
algorithm: algorithm_id, algorithm: KeyAlgorithmId::from_algorithm_id(algorithm_id),
slot: slot_id, slot: slot_id,
}; };
json.insert("key_uri", KeyUri::YubikeyPivKey(yubikey_piv_key).to_string()); json.insert("key_uri", KeyUri::YubikeyPivKey(yubikey_piv_key).to_string());

View File

@@ -133,6 +133,7 @@ pub fn digest_by_jwt_algorithm(jwt_algorithm: AlgorithmType, tobe_signed: &[u8])
} }
AlgorithmType::Es256 => digestutil::sha256_bytes(tobe_signed), AlgorithmType::Es256 => digestutil::sha256_bytes(tobe_signed),
AlgorithmType::Es384 => digestutil::sha384_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), _ => return simple_error!("SHOULD NOT HAPPEN: {:?}", jwt_algorithm),
}) })
} }

View File

@@ -3,7 +3,6 @@ use ecdsa::elliptic_curve::pkcs8::LineEnding;
use ecdsa::VerifyingKey; use ecdsa::VerifyingKey;
use p256::NistP256; use p256::NistP256;
use p256::ecdsa::signature::hazmat::PrehashVerifier; use p256::ecdsa::signature::hazmat::PrehashVerifier;
use p256::elliptic_curve::JwkEcKey;
use p384::NistP384; use p384::NistP384;
use p256::pkcs8::EncodePrivateKey; use p256::pkcs8::EncodePrivateKey;
use p521::NistP521; 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 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_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 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)) 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<u8>, JwkEcKey)> { pub fn generate_ecdsa_keypair(algo: EcdsaAlgorithm) -> XResult<(String, String, String, Vec<u8>, String)> {
match algo { match algo {
EcdsaAlgorithm::P256 => generate_inner_ecdsa_keypair!(p256), EcdsaAlgorithm::P256 => generate_inner_ecdsa_keypair!(p256),
EcdsaAlgorithm::P384 => generate_inner_ecdsa_keypair!(p384), EcdsaAlgorithm::P384 => generate_inner_ecdsa_keypair!(p384),

View File

@@ -1,7 +1,7 @@
use crate::pivutil::{FromStr, ToStr};
use regex::Regex; use regex::Regex;
use rust_util::XResult; use rust_util::XResult;
use yubikey::piv::{AlgorithmId, SlotId}; use yubikey::piv::{AlgorithmId, SlotId};
use crate::pivutil::{ToStr, FromStr};
// reference: https://git.hatter.ink/hatter/card-cli/issues/6 // reference: https://git.hatter.ink/hatter/card-cli/issues/6
#[derive(Debug)] #[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<AlgorithmId> {
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<Self>
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)] #[derive(Debug, PartialEq, Eq)]
pub enum KeyUsage { pub enum KeyUsage {
Any, Any,
@@ -78,8 +150,9 @@ impl ToString for KeyUsage {
match self { match self {
KeyUsage::Any => "*", KeyUsage::Any => "*",
KeyUsage::Singing => "signing", KeyUsage::Singing => "signing",
KeyUsage::KeyAgreement => "key_agreement" KeyUsage::KeyAgreement => "key_agreement",
}.to_string() }
.to_string()
} }
} }
@@ -95,7 +168,7 @@ pub struct SecureEnclaveKey {
#[derive(Debug)] #[derive(Debug)]
pub struct YubikeyPivKey { pub struct YubikeyPivKey {
pub key_name: String, pub key_name: String,
pub algorithm: AlgorithmId, pub algorithm: KeyAlgorithmId,
pub slot: SlotId, pub slot: SlotId,
} }
@@ -103,7 +176,7 @@ pub struct YubikeyPivKey {
#[derive(Debug)] #[derive(Debug)]
pub struct YubikeyHmacEncSoftKey { pub struct YubikeyHmacEncSoftKey {
pub key_name: String, pub key_name: String,
pub algorithm: AlgorithmId, pub algorithm: KeyAlgorithmId,
pub hmac_enc_private_key: String, pub hmac_enc_private_key: String,
} }
@@ -142,8 +215,16 @@ pub fn parse_key_uri(key_uri: &str) -> XResult<KeyUri> {
if "" != usage { if "" != usage {
return simple_error!("Key uri's usage must be empty."); 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!(
let slot = opt_value_result!(SlotId::from_str(left_part), "Invalid slot id: {}", left_part); 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 { let parsed_key_uri = KeyUri::YubikeyPivKey(YubikeyPivKey {
key_name: host_or_name.to_string(), key_name: host_or_name.to_string(),
algorithm, algorithm,
@@ -156,7 +237,11 @@ pub fn parse_key_uri(key_uri: &str) -> XResult<KeyUri> {
if "" != usage { if "" != usage {
return simple_error!("Key uri's usage must be empty."); 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 hmac_enc_private_key = left_part.to_string();
let parsed_key_uri = KeyUri::YubikeyHmacEncSoftKey(YubikeyHmacEncSoftKey { let parsed_key_uri = KeyUri::YubikeyHmacEncSoftKey(YubikeyHmacEncSoftKey {
key_name: host_or_name.to_string(), key_name: host_or_name.to_string(),
@@ -174,7 +259,10 @@ pub fn parse_key_uri(key_uri: &str) -> XResult<KeyUri> {
fn test_parse_key_uri_01() { fn test_parse_key_uri_01() {
let se_key_uri = let se_key_uri =
parse_key_uri("key://hatter-mac-pro:se/p256:signing:BASE64(dataRepresentation)").unwrap(); 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 { match se_key_uri {
KeyUri::SecureEnclaveKey(se_key_uri) => { KeyUri::SecureEnclaveKey(se_key_uri) => {
assert_eq!("hatter-mac-pro", se_key_uri.host); assert_eq!("hatter-mac-pro", se_key_uri.host);
@@ -192,7 +280,10 @@ fn test_parse_key_uri_02() {
let se_key_uri = let se_key_uri =
parse_key_uri("key://hatter-mac-m1:se/p256:key_agreement:BASE64(dataRepresentation)") parse_key_uri("key://hatter-mac-m1:se/p256:key_agreement:BASE64(dataRepresentation)")
.unwrap(); .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 { match se_key_uri {
KeyUri::SecureEnclaveKey(se_key_uri) => { KeyUri::SecureEnclaveKey(se_key_uri) => {
assert_eq!("hatter-mac-m1", se_key_uri.host); assert_eq!("hatter-mac-m1", se_key_uri.host);
@@ -208,11 +299,14 @@ fn test_parse_key_uri_02() {
#[test] #[test]
fn test_parse_key_uri_03() { fn test_parse_key_uri_03() {
let se_key_uri = parse_key_uri("key://yubikey-5n:piv/p256::9a").unwrap(); 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 { match se_key_uri {
KeyUri::YubikeyPivKey(piv_key_uri) => { KeyUri::YubikeyPivKey(piv_key_uri) => {
assert_eq!("yubikey-5n", piv_key_uri.key_name); 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); assert_eq!(SlotId::Authentication, piv_key_uri.slot);
} }
_ => { _ => {

View File

@@ -1,15 +1,66 @@
use std::collections::HashMap; use std::collections::HashMap;
use ecdsa::elliptic_curve::rand_core::OsRng;
use openssl::bn::{BigNum, BigNumContext}; use openssl::bn::{BigNum, BigNumContext};
use openssl::pkey::PKey; use openssl::pkey::PKey;
use openssl::rsa::{Padding, Rsa}; use openssl::rsa::{Padding, Rsa};
use rsa::RsaPublicKey; use rsa::{Pkcs1v15Sign, RsaPrivateKey, RsaPublicKey};
use rust_util::{util_msg, XResult}; use rust_util::{util_msg, XResult};
use rust_util::util_msg::MessageType; use rust_util::util_msg::MessageType;
use spki::DecodePublicKey; use spki::DecodePublicKey;
use rsa::pkcs1::DecodeRsaPublicKey; use rsa::pkcs1::DecodeRsaPublicKey;
use rsa::traits::PublicKeyParts; 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}; use crate::util::{base64_decode, base64_encode};
pub enum RsaSignAlgorithm {
Rs256,
Rs384,
Rs512,
}
impl RsaSignAlgorithm {
pub fn from_str(alg: &str) -> Option<RsaSignAlgorithm> {
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<Vec<u8>> {
match rsa_sign_algorithm {
RsaSignAlgorithm::Rs256 => {
let raw_in = digestutil::sha256_bytes(&message);
Ok(rsa_private_key.sign(Pkcs1v15Sign::new::<Sha256>(), &raw_in)?)
}
RsaSignAlgorithm::Rs384 => {
let raw_in = digestutil::sha384_bytes(&message);
Ok(rsa_private_key.sign(Pkcs1v15Sign::new::<Sha384>(), &raw_in)?)
}
RsaSignAlgorithm::Rs512 => {
let raw_in = digestutil::sha512_bytes(&message);
Ok(rsa_private_key.sign(Pkcs1v15Sign::new::<Sha512>(), &raw_in)?)
}
}
}
pub fn generate_rsa_keypair(bit_size: usize) -> XResult<(String, String, String, Vec<u8>, 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)] #[derive(Debug)]
pub struct RsaCrt { pub struct RsaCrt {
// n = p * q // n = p * q
@@ -160,7 +211,10 @@ fn pkcs1_padding_for_sign(bs: &[u8], bit_len: usize) -> XResult<Vec<u8>> {
pub fn convert_rsa_to_jwk(public_key: &str) -> XResult<String> { pub fn convert_rsa_to_jwk(public_key: &str) -> XResult<String> {
let rsa_public_key = try_parse_rsa(public_key)?; 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<String> {
let e_bytes = rsa_public_key.e().to_bytes_be(); let e_bytes = rsa_public_key.e().to_bytes_be();
let n_bytes = rsa_public_key.n().to_bytes_be(); let n_bytes = rsa_public_key.n().to_bytes_be();