feat: v1.12.0

This commit is contained in:
2025-05-01 00:22:42 +08:00
parent 3af863762f
commit c270c2e369
14 changed files with 383 additions and 103 deletions

View File

@@ -1,8 +1,16 @@
use crate::util;
use clap::{App, Arg, ArgMatches, SubCommand};
use crate::cmd_sign_jwt::digest_by_jwt_algorithm;
use crate::keyutil::{parse_key_uri, KeyUri, KeyUsage};
use crate::pivutil::ToStr;
use crate::util::{base64_decode, base64_encode};
use crate::{cmdutil, pivutil, seutil, util};
use clap::{App, ArgMatches, SubCommand};
use jwt::AlgorithmType;
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 yubikey::YubiKey;
pub struct CommandImpl;
@@ -14,41 +22,85 @@ impl Command for CommandImpl {
fn subcommand<'a>(&self) -> App<'a, 'a> {
SubCommand::with_name(self.name())
.about("External sign subcommand")
.arg(
Arg::with_name("alg")
.long("alg")
.takes_value(true)
.required(true)
.help("Algorithm, e.g. RS256, RS384, RS512, ES256, ES384, ES512"),
)
.arg(
Arg::with_name("parameter")
.long("parameter")
.takes_value(true)
.required(true)
.help("Parameter"),
)
.arg(
Arg::with_name("message-base64")
.long("message-base64")
.takes_value(true)
.required(true)
.help("Message in base64"),
)
.arg(cmdutil::build_alg_arg())
.arg(cmdutil::build_parameter_arg())
.arg(cmdutil::build_message_arg())
.arg(cmdutil::build_pin_arg())
}
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
let _alg = sub_arg_matches.value_of("alg").unwrap();
let _parameter = sub_arg_matches.value_of("parameter").unwrap();
let _message_base64 = sub_arg_matches.value_of("message-base64").unwrap();
// TODO do sign
let mut json = BTreeMap::new();
json.insert("success", Value::Bool(true));
json.insert("signature_base64", "**".into());
match sign(sub_arg_matches) {
Ok(signature_bytes) => {
json.insert("success", Value::Bool(true));
json.insert("signature_base64", base64_encode(&signature_bytes).into());
}
Err(e) => {
json.insert("success", Value::Bool(false));
json.insert("error", e.to_string().into());
}
}
util::print_pretty_json(&json);
Ok(None)
}
}
fn sign(sub_arg_matches: &ArgMatches) -> XResult<Vec<u8>> {
let alg = sub_arg_matches.value_of("alg").unwrap();
let parameter = sub_arg_matches.value_of("parameter").unwrap();
let message_base64 = sub_arg_matches.value_of("message-base64").unwrap();
let key_uri = parse_key_uri(parameter)?;
let message_bytes = base64_decode(message_base64)?;
match key_uri {
KeyUri::SecureEnclaveKey(key) => {
if key.usage != KeyUsage::Singing {
simple_error!("Not singing key")
} else {
Ok(seutil::secure_enclave_p256_sign(
&key.private_key,
&message_bytes,
)?)
}
}
KeyUri::YubikeyPivKey(key) => {
let mut yk = opt_result!(YubiKey::open(), "Find YubiKey failed: {}");
let pin_opt = pivutil::check_read_pin(&mut yk, key.slot, sub_arg_matches);
// FIXME Check Yubikey slot algorithm
let jwt_algorithm = match alg {
"ES256" => AlgorithmType::Es256,
"ES384" => AlgorithmType::Es384,
"RS256" => AlgorithmType::Rs256,
_ => return simple_error!("Invalid alg: {}", alg),
};
let is_p256_mismatch =
key.algorithm == AlgorithmId::EccP256 && jwt_algorithm != AlgorithmType::Es256;
let is_p384_mismatch =
key.algorithm == AlgorithmId::EccP384 && jwt_algorithm != AlgorithmType::Es384;
let is_rsa =
key.algorithm == AlgorithmId::Rsa1024 || key.algorithm == AlgorithmId::Rsa2048;
let is_rsa_mismatch = is_rsa && jwt_algorithm != AlgorithmType::Rs256;
if is_p256_mismatch || is_p384_mismatch || is_rsa_mismatch {
return simple_error!("Invalid algorithm: {} vs {}", key.algorithm.to_str(), alg);
}
let raw_in = digest_by_jwt_algorithm(jwt_algorithm, &message_bytes)?;
if let Some(pin) = pin_opt {
opt_result!(
yk.verify_pin(pin.as_bytes()),
"YubiKey verify pin failed: {}"
);
}
let signed_data = opt_result!(
sign_data(&mut yk, &raw_in, key.algorithm, key.slot),
"Sign YubiKey failed: {}"
);
Ok(signed_data.to_vec())
}
}
}