feat: 1.13.12, support external command
This commit is contained in:
17
Cargo.lock
generated
17
Cargo.lock
generated
@@ -508,7 +508,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "card-cli"
|
name = "card-cli"
|
||||||
version = "1.13.11"
|
version = "1.13.12"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes-gcm-stream",
|
"aes-gcm-stream",
|
||||||
"authenticator 0.3.1",
|
"authenticator 0.3.1",
|
||||||
@@ -520,6 +520,7 @@ dependencies = [
|
|||||||
"digest 0.10.7",
|
"digest 0.10.7",
|
||||||
"ecdsa 0.16.9",
|
"ecdsa 0.16.9",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
|
"external-command-rs",
|
||||||
"hex",
|
"hex",
|
||||||
"jwt",
|
"jwt",
|
||||||
"openpgp-card",
|
"openpgp-card",
|
||||||
@@ -530,6 +531,7 @@ dependencies = [
|
|||||||
"p384 0.13.1",
|
"p384 0.13.1",
|
||||||
"p521",
|
"p521",
|
||||||
"pem",
|
"pem",
|
||||||
|
"percent-encoding",
|
||||||
"pinentry",
|
"pinentry",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
"regex",
|
"regex",
|
||||||
@@ -1133,6 +1135,19 @@ dependencies = [
|
|||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "external-command-rs"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0a71b1b81e312b732ee75580d0b7176e13c276e748307b9640d936f93718397f"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.22.1",
|
||||||
|
"hex",
|
||||||
|
"rust_util",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fastrand"
|
name = "fastrand"
|
||||||
version = "2.3.0"
|
version = "2.3.0"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "card-cli"
|
name = "card-cli"
|
||||||
version = "1.13.11"
|
version = "1.13.12"
|
||||||
authors = ["Hatter Jiang <jht5945@gmail.com>"]
|
authors = ["Hatter Jiang <jht5945@gmail.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
@@ -60,6 +60,8 @@ u2f-hatter-fork = "0.2"
|
|||||||
security-framework = { version = "3.0", features = ["OSX_10_15"] }
|
security-framework = { version = "3.0", features = ["OSX_10_15"] }
|
||||||
rsa = "0.9.8"
|
rsa = "0.9.8"
|
||||||
which = "7.0.3"
|
which = "7.0.3"
|
||||||
|
percent-encoding = "2.3.1"
|
||||||
|
external-command-rs = "0.1.1"
|
||||||
#lazy_static = "1.4.0"
|
#lazy_static = "1.4.0"
|
||||||
#ssh-key = "0.4.0"
|
#ssh-key = "0.4.0"
|
||||||
#ctap-hid-fido2 = "2.1.3"
|
#ctap-hid-fido2 = "2.1.3"
|
||||||
|
|||||||
@@ -128,5 +128,9 @@ pub fn ecdh(
|
|||||||
simple_error!("Invalid algorithm: {}", key.algorithm.to_str())
|
simple_error!("Invalid algorithm: {}", key.algorithm.to_str())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
KeyUri::ExternalCommandKey(key) => {
|
||||||
|
let parameter = cmd_hmac_decrypt::try_decrypt(&mut None, &key.parameter)?;
|
||||||
|
external_command_rs::external_ecdh(&key.external_command, ¶meter, ephemeral_public_key_bytes)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,5 +98,9 @@ fn fetch_public_key(parameter: &str, serial_opt: &Option<&str>) -> XResult<Vec<u
|
|||||||
simple_error!("Invalid algorithm: {}", key.algorithm.to_str())
|
simple_error!("Invalid algorithm: {}", key.algorithm.to_str())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
KeyUri::ExternalCommandKey(key) => {
|
||||||
|
let parameter = cmd_hmac_decrypt::try_decrypt(&mut None, &key.parameter)?;
|
||||||
|
external_command_rs::external_public_key(&key.external_command, ¶meter)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -122,6 +122,12 @@ pub fn sign(alg: &str, message: &[u8], key_uri: KeyUri, sub_arg_matches: &ArgMat
|
|||||||
simple_error!("Invalid algorithm: {}", key.algorithm.to_str())
|
simple_error!("Invalid algorithm: {}", key.algorithm.to_str())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
KeyUri::ExternalCommandKey(key) => {
|
||||||
|
let parameter = cmd_hmac_decrypt::try_decrypt(&mut None, &key.parameter)?;
|
||||||
|
let alg = key.algorithm.to_jwa_name();
|
||||||
|
let signature = external_command_rs::external_sign(&key.external_command, ¶meter, alg, message)?;
|
||||||
|
Ok(signature)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::util;
|
use crate::util;
|
||||||
use clap::{App, ArgMatches, SubCommand};
|
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||||
use rust_util::util_clap::{Command, CommandError};
|
use rust_util::util_clap::{Command, CommandError};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
@@ -14,19 +14,27 @@ impl Command for CommandImpl {
|
|||||||
|
|
||||||
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
||||||
SubCommand::with_name(self.name()).about("External spec subcommand")
|
SubCommand::with_name(self.name()).about("External spec subcommand")
|
||||||
|
.arg(Arg::with_name("external-command").long("external-command").takes_value(true).help("External command"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(&self, _arg_matches: &ArgMatches, _sub_arg_matches: &ArgMatches) -> CommandError {
|
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
||||||
let mut json = BTreeMap::new();
|
let external_command_opt = sub_arg_matches.value_of("external-command");
|
||||||
json.insert("success", Value::Bool(true));
|
|
||||||
json.insert(
|
|
||||||
"agent",
|
|
||||||
format!("card-external-provider/{}", env!("CARGO_PKG_VERSION")).into(),
|
|
||||||
);
|
|
||||||
json.insert("specification", "External/1.0.0-alpha".into());
|
|
||||||
json.insert("commands", vec!["external_public_key", "external_sign", "external_ecdh"].into());
|
|
||||||
|
|
||||||
util::print_pretty_json(&json);
|
if let Some(external_command) = external_command_opt {
|
||||||
|
let spec = external_command_rs::external_spec(external_command)?;
|
||||||
|
util::print_pretty_json(&spec);
|
||||||
|
} else {
|
||||||
|
let mut json = BTreeMap::new();
|
||||||
|
json.insert("success", Value::Bool(true));
|
||||||
|
json.insert(
|
||||||
|
"agent",
|
||||||
|
format!("card-external-provider/{}", env!("CARGO_PKG_VERSION")).into(),
|
||||||
|
);
|
||||||
|
json.insert("specification", "External/1.0.0-alpha".into());
|
||||||
|
json.insert("commands", vec!["external_public_key", "external_sign", "external_ecdh"].into());
|
||||||
|
|
||||||
|
util::print_pretty_json(&json);
|
||||||
|
}
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use jwt::AlgorithmType;
|
|
||||||
use crate::pivutil::{FromStr, ToStr};
|
use crate::pivutil::{FromStr, ToStr};
|
||||||
|
use jwt::AlgorithmType;
|
||||||
|
use percent_encoding::NON_ALPHANUMERIC;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use rust_util::XResult;
|
use rust_util::XResult;
|
||||||
use yubikey::piv::{AlgorithmId, SlotId};
|
use yubikey::piv::{AlgorithmId, SlotId};
|
||||||
@@ -10,6 +11,7 @@ pub enum KeyUri {
|
|||||||
SecureEnclaveKey(SecureEnclaveKey),
|
SecureEnclaveKey(SecureEnclaveKey),
|
||||||
YubikeyPivKey(YubikeyPivKey),
|
YubikeyPivKey(YubikeyPivKey),
|
||||||
YubikeyHmacEncSoftKey(YubikeyHmacEncSoftKey),
|
YubikeyHmacEncSoftKey(YubikeyHmacEncSoftKey),
|
||||||
|
ExternalCommandKey(ExternalCommandKey),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl KeyUri {
|
impl KeyUri {
|
||||||
@@ -25,10 +27,13 @@ impl KeyUri {
|
|||||||
KeyUri::SecureEnclaveKey(_) => return AlgorithmType::Es256,
|
KeyUri::SecureEnclaveKey(_) => return AlgorithmType::Es256,
|
||||||
KeyUri::YubikeyPivKey(key) => key.algorithm,
|
KeyUri::YubikeyPivKey(key) => key.algorithm,
|
||||||
KeyUri::YubikeyHmacEncSoftKey(key) => key.algorithm,
|
KeyUri::YubikeyHmacEncSoftKey(key) => key.algorithm,
|
||||||
|
KeyUri::ExternalCommandKey(key) => key.algorithm,
|
||||||
};
|
};
|
||||||
match algorithm_id {
|
match algorithm_id {
|
||||||
KeyAlgorithmId::Rsa1024 | KeyAlgorithmId::Rsa2048
|
KeyAlgorithmId::Rsa1024
|
||||||
| KeyAlgorithmId::Rsa3072 | KeyAlgorithmId::Rsa4096 => AlgorithmType::Rs256,
|
| KeyAlgorithmId::Rsa2048
|
||||||
|
| KeyAlgorithmId::Rsa3072
|
||||||
|
| KeyAlgorithmId::Rsa4096 => AlgorithmType::Rs256,
|
||||||
KeyAlgorithmId::EccP256 => AlgorithmType::Es256,
|
KeyAlgorithmId::EccP256 => AlgorithmType::Es256,
|
||||||
KeyAlgorithmId::EccP384 => AlgorithmType::Es384,
|
KeyAlgorithmId::EccP384 => AlgorithmType::Es384,
|
||||||
KeyAlgorithmId::EccP521 => AlgorithmType::Es512,
|
KeyAlgorithmId::EccP521 => AlgorithmType::Es512,
|
||||||
@@ -66,6 +71,17 @@ impl ToString for KeyUri {
|
|||||||
key_uri.push_str("::");
|
key_uri.push_str("::");
|
||||||
key_uri.push_str(key.hmac_enc_private_key.as_str());
|
key_uri.push_str(key.hmac_enc_private_key.as_str());
|
||||||
}
|
}
|
||||||
|
// key://external-command-file-name:external_command/p256::parameter
|
||||||
|
KeyUri::ExternalCommandKey(key) => {
|
||||||
|
let encoded_external_command =
|
||||||
|
percent_encoding::utf8_percent_encode(&key.external_command, NON_ALPHANUMERIC)
|
||||||
|
.to_string();
|
||||||
|
key_uri.push_str(&encoded_external_command);
|
||||||
|
key_uri.push_str(":external_command/");
|
||||||
|
key_uri.push_str(key.algorithm.to_str());
|
||||||
|
key_uri.push_str("::");
|
||||||
|
key_uri.push_str(&key.parameter);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
key_uri
|
key_uri
|
||||||
}
|
}
|
||||||
@@ -106,19 +122,35 @@ impl KeyAlgorithmId {
|
|||||||
|
|
||||||
pub fn is_rsa(&self) -> bool {
|
pub fn is_rsa(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
KeyAlgorithmId::Rsa1024 | KeyAlgorithmId::Rsa2048
|
KeyAlgorithmId::Rsa1024
|
||||||
| KeyAlgorithmId::Rsa3072 | KeyAlgorithmId::Rsa4096 => true,
|
| KeyAlgorithmId::Rsa2048
|
||||||
|
| KeyAlgorithmId::Rsa3072
|
||||||
|
| KeyAlgorithmId::Rsa4096 => true,
|
||||||
KeyAlgorithmId::EccP256 | KeyAlgorithmId::EccP384 | KeyAlgorithmId::EccP521 => false,
|
KeyAlgorithmId::EccP256 | KeyAlgorithmId::EccP384 | KeyAlgorithmId::EccP521 => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_ecc(&self) -> bool {
|
pub fn is_ecc(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
KeyAlgorithmId::Rsa1024 | KeyAlgorithmId::Rsa2048
|
KeyAlgorithmId::Rsa1024
|
||||||
| KeyAlgorithmId::Rsa3072 | KeyAlgorithmId::Rsa4096 => false,
|
| KeyAlgorithmId::Rsa2048
|
||||||
|
| KeyAlgorithmId::Rsa3072
|
||||||
|
| KeyAlgorithmId::Rsa4096 => false,
|
||||||
KeyAlgorithmId::EccP256 | KeyAlgorithmId::EccP384 | KeyAlgorithmId::EccP521 => true,
|
KeyAlgorithmId::EccP256 | KeyAlgorithmId::EccP384 | KeyAlgorithmId::EccP521 => true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn to_jwa_name(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
KeyAlgorithmId::Rsa1024
|
||||||
|
| KeyAlgorithmId::Rsa2048
|
||||||
|
| KeyAlgorithmId::Rsa3072
|
||||||
|
| KeyAlgorithmId::Rsa4096 => "RS256",
|
||||||
|
KeyAlgorithmId::EccP256 => "ES256,",
|
||||||
|
KeyAlgorithmId::EccP384 => "ES384",
|
||||||
|
KeyAlgorithmId::EccP521 => "ES512",
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for KeyAlgorithmId {
|
impl FromStr for KeyAlgorithmId {
|
||||||
@@ -206,6 +238,14 @@ pub struct YubikeyHmacEncSoftKey {
|
|||||||
pub hmac_enc_private_key: String,
|
pub hmac_enc_private_key: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ExternalCommandKey {
|
||||||
|
pub external_command: String,
|
||||||
|
pub algorithm: KeyAlgorithmId,
|
||||||
|
pub parameter: String,
|
||||||
|
}
|
||||||
|
|
||||||
pub fn parse_key_uri(key_uri: &str) -> XResult<KeyUri> {
|
pub fn parse_key_uri(key_uri: &str) -> XResult<KeyUri> {
|
||||||
let regex = Regex::new(r##"^key://([0-9a-zA-Z\-\._]*):(\w+)/(\w+):((?:\w+)?):(.*)$"##).unwrap();
|
let regex = Regex::new(r##"^key://([0-9a-zA-Z\-\._]*):(\w+)/(\w+):((?:\w+)?):(.*)$"##).unwrap();
|
||||||
let captures = match regex.captures(key_uri) {
|
let captures = match regex.captures(key_uri) {
|
||||||
@@ -277,6 +317,28 @@ pub fn parse_key_uri(key_uri: &str) -> XResult<KeyUri> {
|
|||||||
debugging!("Parsed key uri: {:?}", parsed_key_uri);
|
debugging!("Parsed key uri: {:?}", parsed_key_uri);
|
||||||
Ok(parsed_key_uri)
|
Ok(parsed_key_uri)
|
||||||
}
|
}
|
||||||
|
"external_command" => {
|
||||||
|
if "" != usage {
|
||||||
|
return simple_error!("Key uri's usage must be empty.");
|
||||||
|
}
|
||||||
|
let external_command = opt_result!(
|
||||||
|
percent_encoding::percent_decode_str(host_or_name).decode_utf8(),
|
||||||
|
"Decode external command failed: {}"
|
||||||
|
);
|
||||||
|
let algorithm = opt_value_result!(
|
||||||
|
KeyAlgorithmId::from_str(algorithm),
|
||||||
|
"Invalid algorithm id: {}",
|
||||||
|
algorithm
|
||||||
|
);
|
||||||
|
let parameter = left_part.to_string();
|
||||||
|
let parsed_key_uri = KeyUri::ExternalCommandKey(ExternalCommandKey {
|
||||||
|
external_command: external_command.to_string(),
|
||||||
|
algorithm,
|
||||||
|
parameter,
|
||||||
|
});
|
||||||
|
debugging!("Parsed key uri: {:?}", parsed_key_uri);
|
||||||
|
Ok(parsed_key_uri)
|
||||||
|
}
|
||||||
_ => simple_error!("Key uri's module must be se."),
|
_ => simple_error!("Key uri's module must be se."),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user