feat: v1.13.5
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -508,7 +508,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "card-cli"
|
||||
version = "1.13.4"
|
||||
version = "1.13.5"
|
||||
dependencies = [
|
||||
"aes-gcm-stream",
|
||||
"authenticator 0.3.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "card-cli"
|
||||
version = "1.13.4"
|
||||
version = "1.13.5"
|
||||
authors = ["Hatter Jiang <jht5945@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
|
||||
132
src/cmd_external_ecdh.rs
Normal file
132
src/cmd_external_ecdh.rs
Normal file
@@ -0,0 +1,132 @@
|
||||
use crate::keyutil::{parse_key_uri, KeyAlgorithmId, KeyUri, KeyUsage};
|
||||
use crate::pivutil::ToStr;
|
||||
use crate::{cmd_hmac_decrypt, cmd_se_ecdh, cmdutil, ecdhutil, pivutil, seutil, util, yubikeyutil};
|
||||
use clap::{App, ArgMatches, SubCommand};
|
||||
use rust_util::util_clap::{Command, CommandError};
|
||||
use rust_util::XResult;
|
||||
use serde_json::Value;
|
||||
use std::collections::BTreeMap;
|
||||
use yubikey::piv::{decrypt_data, AlgorithmId};
|
||||
use crate::util::try_decode;
|
||||
|
||||
pub struct CommandImpl;
|
||||
|
||||
impl Command for CommandImpl {
|
||||
fn name(&self) -> &str {
|
||||
"external_ecdh"
|
||||
}
|
||||
|
||||
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
||||
SubCommand::with_name(self.name())
|
||||
.about("External ECDH subcommand")
|
||||
.arg(cmdutil::build_parameter_arg())
|
||||
.arg(cmdutil::build_epk_arg())
|
||||
.arg(cmdutil::build_pin_arg())
|
||||
.arg(cmdutil::build_serial_arg())
|
||||
}
|
||||
|
||||
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
||||
let parameter = sub_arg_matches.value_of("parameter").unwrap();
|
||||
let epk = sub_arg_matches.value_of("epk").unwrap();
|
||||
let ephemeral_public_key_der_bytes = cmd_se_ecdh::parse_epk(epk)?;
|
||||
|
||||
let mut json = BTreeMap::new();
|
||||
let key_uri = parse_key_uri(parameter)?;
|
||||
|
||||
match ecdh(&ephemeral_public_key_der_bytes, key_uri, sub_arg_matches) {
|
||||
Ok(shared_secret_bytes) => {
|
||||
json.insert("success", Value::Bool(true));
|
||||
json.insert(
|
||||
"shared_secret_hex",
|
||||
hex::encode(&shared_secret_bytes).into(),
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
json.insert("success", Value::Bool(false));
|
||||
json.insert("error", e.to_string().into());
|
||||
}
|
||||
}
|
||||
|
||||
util::print_pretty_json(&json);
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ecdh(
|
||||
ephemeral_public_key_bytes: &[u8],
|
||||
key_uri: KeyUri,
|
||||
sub_arg_matches: &ArgMatches,
|
||||
) -> XResult<Vec<u8>> {
|
||||
match key_uri {
|
||||
KeyUri::SecureEnclaveKey(key) => {
|
||||
if key.usage != KeyUsage::Singing {
|
||||
return simple_error!("Not singing key");
|
||||
}
|
||||
let private_key = cmd_hmac_decrypt::try_decrypt(&mut None, &key.private_key)?;
|
||||
seutil::secure_enclave_p256_dh(&private_key, ephemeral_public_key_bytes)
|
||||
}
|
||||
KeyUri::YubikeyPivKey(key) => {
|
||||
let mut yk = yubikeyutil::open_yubikey_with_args(sub_arg_matches)?;
|
||||
let pin_opt = pivutil::check_read_pin(&mut yk, key.slot, sub_arg_matches);
|
||||
|
||||
if let Some(pin) = pin_opt {
|
||||
opt_result!(
|
||||
yk.verify_pin(pin.as_bytes()),
|
||||
"YubiKey verify pin failed: {}"
|
||||
);
|
||||
}
|
||||
|
||||
let algorithm = opt_value_result!(
|
||||
KeyAlgorithmId::to_algorithm_id(key.algorithm),
|
||||
"Yubikey not supported algorithm: {}",
|
||||
key.algorithm.to_str()
|
||||
);
|
||||
|
||||
let epk_bytes = match algorithm {
|
||||
AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => {
|
||||
return simple_error!("Algorithm is not supported: {:?}", algorithm)
|
||||
}
|
||||
AlgorithmId::EccP256 => {
|
||||
use p256::{elliptic_curve::sec1::ToEncodedPoint, PublicKey};
|
||||
use spki::DecodePublicKey;
|
||||
let public_key = opt_result!(PublicKey::from_public_key_der(
|
||||
ephemeral_public_key_bytes),"Parse P256 ephemeral public key failed: {}");
|
||||
public_key.to_encoded_point(false).as_bytes().to_vec()
|
||||
}
|
||||
AlgorithmId::EccP384 => {
|
||||
use p384::{elliptic_curve::sec1::ToEncodedPoint, PublicKey};
|
||||
use spki::DecodePublicKey;
|
||||
let public_key = opt_result!(PublicKey::from_public_key_der(
|
||||
ephemeral_public_key_bytes), "Parse P384 ephemeral public key failed: {}");
|
||||
public_key.to_encoded_point(false).as_bytes().to_vec()
|
||||
}
|
||||
};
|
||||
let decrypted_shared_secret = opt_result!(
|
||||
decrypt_data(&mut yk, &epk_bytes, algorithm, key.slot,),
|
||||
"Decrypt piv failed: {}"
|
||||
);
|
||||
|
||||
Ok(decrypted_shared_secret.to_vec())
|
||||
}
|
||||
KeyUri::YubikeyHmacEncSoftKey(key) => {
|
||||
if key.algorithm.is_ecc() {
|
||||
let private_key = cmd_hmac_decrypt::try_decrypt(&mut None, &key.hmac_enc_private_key)?;
|
||||
let private_key_bytes = try_decode(&private_key)?;
|
||||
|
||||
if let Ok(shared_secret) = ecdhutil::parse_p256_private_and_ecdh(&private_key_bytes, ephemeral_public_key_bytes) {
|
||||
return Ok(shared_secret.to_vec());
|
||||
}
|
||||
if let Ok(shared_secret) = ecdhutil::parse_p384_private_and_ecdh(&private_key_bytes, ephemeral_public_key_bytes) {
|
||||
return Ok(shared_secret.to_vec());
|
||||
}
|
||||
if let Ok(shared_secret) = ecdhutil::parse_p521_private_and_ecdh(&private_key_bytes, ephemeral_public_key_bytes) {
|
||||
return Ok(shared_secret.to_vec());
|
||||
}
|
||||
|
||||
simple_error!("Invalid private key and/or ephemeral public key")
|
||||
} else {
|
||||
simple_error!("Invalid algorithm: {}", key.algorithm.to_str())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
use crate::keyutil::parse_key_uri;
|
||||
use crate::{cmd_hmac_decrypt, cmdutil, seutil, util};
|
||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||
use clap::{App, ArgMatches, SubCommand};
|
||||
use p256::elliptic_curve::sec1::FromEncodedPoint;
|
||||
use p256::{EncodedPoint, PublicKey};
|
||||
use rust_util::util_clap::{Command, CommandError};
|
||||
use spki::EncodePublicKey;
|
||||
use std::collections::BTreeMap;
|
||||
use rust_util::XResult;
|
||||
use crate::util::base64_decode;
|
||||
|
||||
pub struct CommandImpl;
|
||||
|
||||
@@ -18,13 +20,7 @@ impl Command for CommandImpl {
|
||||
SubCommand::with_name(self.name())
|
||||
.about("Secure Enclave ECDH subcommand")
|
||||
.arg(cmdutil::build_key_uri_arg())
|
||||
.arg(
|
||||
Arg::with_name("epk")
|
||||
.long("epk")
|
||||
.required(true)
|
||||
.takes_value(true)
|
||||
.help("E-Public key"),
|
||||
)
|
||||
.arg(cmdutil::build_epk_arg())
|
||||
.arg(cmdutil::build_json_arg())
|
||||
}
|
||||
|
||||
@@ -39,24 +35,7 @@ impl Command for CommandImpl {
|
||||
let se_key_uri = key_uri.as_secure_enclave_key()?;
|
||||
debugging!("Secure enclave key URI: {:?}", se_key_uri);
|
||||
|
||||
let ephemeral_public_key_der_bytes = if epk.starts_with("04") {
|
||||
let ephemeral_public_key_point_bytes = opt_result!(
|
||||
hex::decode(epk),
|
||||
"Decode public key point from hex failed: {}"
|
||||
);
|
||||
let encoded_point = opt_result!(
|
||||
EncodedPoint::from_bytes(ephemeral_public_key_point_bytes),
|
||||
"Parse public key point failed: {}"
|
||||
);
|
||||
let public_key_opt = PublicKey::from_encoded_point(&encoded_point);
|
||||
if public_key_opt.is_none().into() {
|
||||
return simple_error!("Parse public key failed.");
|
||||
}
|
||||
let public_key = public_key_opt.unwrap();
|
||||
public_key.to_public_key_der()?.as_bytes().to_vec()
|
||||
} else {
|
||||
opt_result!(hex::decode(epk), "Decode public key from hex failed: {}")
|
||||
};
|
||||
let ephemeral_public_key_der_bytes = parse_epk(epk)?;
|
||||
|
||||
let private_key = cmd_hmac_decrypt::try_decrypt(&mut None, &se_key_uri.private_key)?;
|
||||
let dh = seutil::secure_enclave_p256_dh(
|
||||
@@ -77,3 +56,30 @@ impl Command for CommandImpl {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_epk(epk: &str) -> XResult<Vec<u8>> {
|
||||
if epk.starts_with("04") {
|
||||
let ephemeral_public_key_point_bytes = opt_result!(
|
||||
hex::decode(epk),
|
||||
"Decode public key point from hex failed: {}"
|
||||
);
|
||||
let encoded_point = opt_result!(
|
||||
EncodedPoint::from_bytes(ephemeral_public_key_point_bytes),
|
||||
"Parse public key point failed: {}"
|
||||
);
|
||||
let public_key_opt = PublicKey::from_encoded_point(&encoded_point);
|
||||
if public_key_opt.is_none().into() {
|
||||
return simple_error!("Parse public key failed.");
|
||||
}
|
||||
let public_key = public_key_opt.unwrap();
|
||||
Ok(public_key.to_public_key_der()?.as_bytes().to_vec())
|
||||
} else {
|
||||
match hex::decode(epk) {
|
||||
Ok(epk_bytes) => Ok(epk_bytes),
|
||||
Err(e) => match base64_decode(&epk) {
|
||||
Ok(epk_bytes) => Ok(epk_bytes),
|
||||
Err(_) => simple_error!("Decode public key from hex failed: {}", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,10 @@ pub fn build_parameter_arg() -> Arg<'static, 'static> {
|
||||
Arg::with_name("parameter").long("parameter").takes_value(true).required(true).help("Parameter")
|
||||
}
|
||||
|
||||
pub fn build_epk_arg() -> Arg<'static, 'static> {
|
||||
Arg::with_name("epk").long("epk").required(true).takes_value(true).help("E-Public key")
|
||||
}
|
||||
|
||||
pub fn build_message_arg() -> Arg<'static, 'static> {
|
||||
Arg::with_name("message-base64").long("message-base64").takes_value(true).required(true).help("Message in base64")
|
||||
}
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
macro_rules! piv_ecdh {
|
||||
($p_algo: tt, $public_key_pem_opt: expr, $sub_arg_matches: expr, $json: expr, $json_output: expr) => ({
|
||||
use $p_algo::elliptic_curve::sec1::{FromEncodedPoint, ToEncodedPoint};
|
||||
use $p_algo::ecdh::EphemeralSecret;
|
||||
use $p_algo::{EncodedPoint, PublicKey};
|
||||
use $p_algo::{EncodedPoint, PublicKey, ecdh::EphemeralSecret};
|
||||
let public_key;
|
||||
if let Some(public_key_pem) = $public_key_pem_opt {
|
||||
public_key = opt_result!(public_key_pem.parse::<PublicKey>(), "Parse public key failed: {}");
|
||||
@@ -35,4 +34,30 @@ macro_rules! piv_ecdh {
|
||||
})
|
||||
}
|
||||
|
||||
macro_rules! parse_private_and_ecdh {
|
||||
($algo: tt, $private_key_bytes: tt, $ephemeral_public_key_bytes: tt) => ({
|
||||
use $algo::{SecretKey, PublicKey, ecdh::diffie_hellman, pkcs8::DecodePrivateKey};
|
||||
use spki::DecodePublicKey;
|
||||
let secret_key= SecretKey::from_pkcs8_der($private_key_bytes)?;
|
||||
let public_key = opt_result!(PublicKey::from_public_key_der(
|
||||
$ephemeral_public_key_bytes),"Parse ephemeral public key failed: {}");
|
||||
|
||||
let shared_secret = diffie_hellman(secret_key.to_nonzero_scalar(), public_key.as_affine());
|
||||
Ok(shared_secret.raw_secret_bytes().to_vec())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse_p256_private_and_ecdh(private_key_bytes: &[u8], ephemeral_public_key_bytes: &[u8]) -> XResult<Vec<u8>> {
|
||||
parse_private_and_ecdh!(p256, private_key_bytes, ephemeral_public_key_bytes)
|
||||
}
|
||||
|
||||
pub fn parse_p384_private_and_ecdh(private_key_bytes: &[u8], ephemeral_public_key_bytes: &[u8]) -> XResult<Vec<u8>> {
|
||||
parse_private_and_ecdh!(p384, private_key_bytes, ephemeral_public_key_bytes)
|
||||
}
|
||||
|
||||
pub fn parse_p521_private_and_ecdh(private_key_bytes: &[u8], ephemeral_public_key_bytes: &[u8]) -> XResult<Vec<u8>> {
|
||||
parse_private_and_ecdh!(p521, private_key_bytes, ephemeral_public_key_bytes)
|
||||
}
|
||||
|
||||
use rust_util::XResult;
|
||||
pub(crate) use piv_ecdh;
|
||||
@@ -97,8 +97,7 @@ pub fn generate_ecdsa_keypair(algo: EcdsaAlgorithm) -> XResult<(String, String,
|
||||
|
||||
macro_rules! parse_ecdsa_private_key_to_public_key {
|
||||
($algo: tt, $parse_ecdsa_private_key: tt) => ({
|
||||
use $algo::pkcs8::DecodePrivateKey;
|
||||
use $algo::SecretKey;
|
||||
use $algo::{SecretKey, pkcs8::DecodePrivateKey};
|
||||
|
||||
let secret_key = match SecretKey::from_pkcs8_pem($parse_ecdsa_private_key) {
|
||||
Ok(secret_key) => secret_key,
|
||||
@@ -130,8 +129,7 @@ pub fn parse_p521_private_key_to_public_key(private_key_pkcs8: &str) -> XResult<
|
||||
|
||||
macro_rules! parse_ecdsa_private_key {
|
||||
($algo: tt, $parse_ecdsa_private_key: tt) => ({
|
||||
use $algo::pkcs8::DecodePrivateKey;
|
||||
use $algo::SecretKey;
|
||||
use $algo::{SecretKey, pkcs8::DecodePrivateKey};
|
||||
|
||||
let secret_key = match SecretKey::from_pkcs8_pem($parse_ecdsa_private_key) {
|
||||
Ok(secret_key) => secret_key,
|
||||
@@ -162,8 +160,7 @@ pub fn parse_p521_private_key(private_key_pkcs8: &str) -> XResult<Vec<u8>> {
|
||||
|
||||
macro_rules! sign_ecdsa_rs_or_der {
|
||||
($algo: tt, $private_key_d: tt, $pre_hash: tt, $is_rs: tt) => ({
|
||||
use $algo::ecdsa::{SigningKey, Signature};
|
||||
use $algo::ecdsa::signature::hazmat::PrehashSigner;
|
||||
use $algo::ecdsa::{SigningKey, Signature, signature::hazmat::PrehashSigner};
|
||||
|
||||
let signing_key = SigningKey::from_slice($private_key_d)?;
|
||||
let signature: Signature = signing_key.sign_prehash($pre_hash)?;
|
||||
|
||||
@@ -10,6 +10,7 @@ mod cmd_chall_config;
|
||||
mod cmd_convert_jwk_to_pem;
|
||||
mod cmd_convert_pem_to_jwk;
|
||||
mod cmd_ec_verify;
|
||||
mod cmd_external_ecdh;
|
||||
mod cmd_external_public_key;
|
||||
mod cmd_external_sign;
|
||||
mod cmd_external_spec;
|
||||
@@ -164,6 +165,7 @@ fn inner_main() -> CommandError {
|
||||
Box::new(cmd_external_spec::CommandImpl),
|
||||
Box::new(cmd_external_public_key::CommandImpl),
|
||||
Box::new(cmd_external_sign::CommandImpl),
|
||||
Box::new(cmd_external_ecdh::CommandImpl),
|
||||
];
|
||||
|
||||
#[allow(clippy::vec_init_then_push)]
|
||||
|
||||
Reference in New Issue
Block a user