feat: 1.10.21, add hmac-encrypt, hmac-decrypt
This commit is contained in:
@@ -1,10 +1,6 @@
|
||||
use std::ops::Deref;
|
||||
|
||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||
use rust_util::util_clap::{Command, CommandError};
|
||||
use rust_util::util_msg;
|
||||
use yubico_manager::config::{Config, Mode, Slot};
|
||||
use yubico_manager::Yubico;
|
||||
|
||||
use crate::hmacutil;
|
||||
|
||||
@@ -29,28 +25,8 @@ impl Command for CommandImpl {
|
||||
if json_output { util_msg::set_logger_std_out(false); }
|
||||
|
||||
let challenge_bytes = hmacutil::get_challenge_bytes(sub_arg_matches)?;
|
||||
|
||||
let mut yubi = Yubico::new();
|
||||
let device = match yubi.find_yubikey() {
|
||||
Ok(device) => device,
|
||||
Err(_) => {
|
||||
warning!("YubiKey not found");
|
||||
return Ok(Some(1));
|
||||
}
|
||||
};
|
||||
|
||||
success!("Found key, Vendor ID: {:?}, Product ID: {:?}", device.vendor_id, device.product_id);
|
||||
let config = Config::default()
|
||||
.set_vendor_id(device.vendor_id)
|
||||
.set_product_id(device.product_id)
|
||||
.set_variable_size(true)
|
||||
.set_mode(Mode::Sha1)
|
||||
.set_slot(Slot::Slot2);
|
||||
|
||||
// In HMAC Mode, the result will always be the SAME for the SAME provided challenge
|
||||
let hmac_result = opt_result!(yubi.challenge_response_hmac(&challenge_bytes, config), "Challenge HMAC failed: {}");
|
||||
|
||||
hmacutil::output_hmac_result(sub_arg_matches, json_output, challenge_bytes, hmac_result.deref());
|
||||
let hmac_result = hmacutil::compute_yubikey_hmac(&challenge_bytes)?;
|
||||
hmacutil::output_hmac_result(sub_arg_matches, json_output, challenge_bytes, &hmac_result);
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
49
src/cmd_hmacdecrypt.rs
Normal file
49
src/cmd_hmacdecrypt.rs
Normal file
@@ -0,0 +1,49 @@
|
||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||
use rust_util::util_clap::{Command, CommandError};
|
||||
use rust_util::util_msg;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use crate::hmacutil;
|
||||
|
||||
pub struct CommandImpl;
|
||||
|
||||
impl Command for CommandImpl {
|
||||
fn name(&self) -> &str {
|
||||
"hmac-decrypt"
|
||||
}
|
||||
|
||||
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
||||
SubCommand::with_name(self.name())
|
||||
.about("Yubikey HMAC decrypt")
|
||||
.arg(
|
||||
Arg::with_name("ciphertext")
|
||||
.long("ciphertext")
|
||||
.takes_value(true)
|
||||
.help("Ciphertext"),
|
||||
)
|
||||
.arg(Arg::with_name("json").long("json").help("JSON output"))
|
||||
}
|
||||
|
||||
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
||||
let json_output = sub_arg_matches.is_present("json");
|
||||
if json_output {
|
||||
util_msg::set_logger_std_out(false);
|
||||
}
|
||||
|
||||
let ciphertext = sub_arg_matches.value_of("ciphertext").unwrap();
|
||||
let plaintext = hmacutil::hmac_decrypt_to_string(&ciphertext)?;
|
||||
|
||||
if json_output {
|
||||
let mut json = BTreeMap::<&'_ str, String>::new();
|
||||
json.insert("plaintext", plaintext);
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(&json).expect("Convert to JSON failed!")
|
||||
);
|
||||
} else {
|
||||
success!("Plaintext: {}", plaintext);
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
49
src/cmd_hmacencrypt.rs
Normal file
49
src/cmd_hmacencrypt.rs
Normal file
@@ -0,0 +1,49 @@
|
||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||
use rust_util::util_clap::{Command, CommandError};
|
||||
use rust_util::util_msg;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use crate::hmacutil;
|
||||
|
||||
pub struct CommandImpl;
|
||||
|
||||
impl Command for CommandImpl {
|
||||
fn name(&self) -> &str {
|
||||
"hmac-encrypt"
|
||||
}
|
||||
|
||||
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
||||
SubCommand::with_name(self.name())
|
||||
.about("Yubikey HMAC encrypt")
|
||||
.arg(
|
||||
Arg::with_name("plaintext")
|
||||
.long("plaintext")
|
||||
.takes_value(true)
|
||||
.help("Plaintext"),
|
||||
)
|
||||
.arg(Arg::with_name("json").long("json").help("JSON output"))
|
||||
}
|
||||
|
||||
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
||||
let json_output = sub_arg_matches.is_present("json");
|
||||
if json_output {
|
||||
util_msg::set_logger_std_out(false);
|
||||
}
|
||||
|
||||
let plaintext = sub_arg_matches.value_of("plaintext").unwrap();
|
||||
let hmac_encrypt_ciphertext = hmacutil::hmac_encrypt_from_string(plaintext)?;
|
||||
|
||||
if json_output {
|
||||
let mut json = BTreeMap::<&'_ str, String>::new();
|
||||
json.insert("ciphertext", hmac_encrypt_ciphertext);
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(&json).expect("Convert to JSON failed!")
|
||||
);
|
||||
} else {
|
||||
success!("HMAC encrypt ciphertext: {}", hmac_encrypt_ciphertext);
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ use yubikey::piv::{sign_data, AlgorithmId, SlotId};
|
||||
use yubikey::{Certificate, YubiKey};
|
||||
|
||||
use crate::ecdsautil::parse_ecdsa_to_rs;
|
||||
use crate::{cmd_signjwt, digest, ecdsautil, pivutil, rsautil, util};
|
||||
use crate::{cmd_signjwt, digest, ecdsautil, hmacutil, pivutil, rsautil, util};
|
||||
|
||||
const SEPARATOR: &str = ".";
|
||||
|
||||
@@ -45,6 +45,7 @@ impl Command for CommandImpl {
|
||||
sub_arg_matches.value_of("private-key"),
|
||||
"Private key PKCS#8 DER base64 encoded or PEM"
|
||||
);
|
||||
let private_key = hmacutil::try_hmac_decrypt_to_string(private_key)?;
|
||||
|
||||
let key_id = sub_arg_matches.value_of("key-id");
|
||||
let claims = sub_arg_matches.values_of("claims");
|
||||
@@ -117,7 +118,7 @@ impl Command for CommandImpl {
|
||||
}
|
||||
}
|
||||
|
||||
let token_string = sign_jwt(private_key, header, &payload, &jwt_claims)?;
|
||||
let token_string = sign_jwt(&private_key, header, &payload, &jwt_claims)?;
|
||||
debugging!("Singed JWT: {}", token_string);
|
||||
if json_output {
|
||||
json.insert("token", token_string.clone());
|
||||
|
||||
@@ -1,9 +1,100 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use clap::ArgMatches;
|
||||
use rust_util::XResult;
|
||||
use std::collections::BTreeMap;
|
||||
use std::ops::Deref;
|
||||
use aes_gcm_stream::{Aes256GcmStreamDecryptor, Aes256GcmStreamEncryptor};
|
||||
use rand::random;
|
||||
use yubico_manager::config::{Config, Mode, Slot};
|
||||
use yubico_manager::hmacmode::HmacKey;
|
||||
use yubico_manager::sec::hmac_sha1;
|
||||
use yubico_manager::Yubico;
|
||||
use crate::digest::{copy_sha256, sha256, sha256_bytes};
|
||||
use crate::util::{base64_decode, base64_encode};
|
||||
|
||||
const HMAC_ENC_PREFIX: &str = "hmac_enc:";
|
||||
|
||||
// hmac encrypt, format: hmac_enc:<HMAC-NONCE>:<AES-GCM-NONCE>:<ENCRYPTED>
|
||||
pub fn hmac_encrypt_from_string(plaintext: &str) -> XResult<String> {
|
||||
hmac_encrypt(plaintext.as_bytes())
|
||||
}
|
||||
|
||||
pub fn hmac_encrypt(plaintext: &[u8]) -> XResult<String> {
|
||||
let hmac_nonce: [u8; 8] = random();
|
||||
let aes_gcm_nonce: [u8; 16] = random();
|
||||
|
||||
let hmac_key = compute_yubikey_hmac(&hmac_nonce)?;
|
||||
let key = copy_sha256(&sha256_bytes(&hmac_key))?;
|
||||
|
||||
let mut encryptor = Aes256GcmStreamEncryptor::new(key, &aes_gcm_nonce);
|
||||
let mut ciphertext = encryptor.update(plaintext);
|
||||
let (final_part, tag) = encryptor.finalize();
|
||||
ciphertext.extend_from_slice(&final_part);
|
||||
ciphertext.extend_from_slice(&tag);
|
||||
|
||||
Ok(format!("{}{}:{}:{}",
|
||||
HMAC_ENC_PREFIX,
|
||||
hex::encode(&hmac_nonce),
|
||||
hex::encode(&aes_gcm_nonce),
|
||||
base64_encode(&ciphertext)
|
||||
))
|
||||
}
|
||||
|
||||
pub fn is_hmac_encrypted(ciphertext: &str) -> bool {
|
||||
ciphertext.starts_with(HMAC_ENC_PREFIX)
|
||||
}
|
||||
|
||||
pub fn try_hmac_decrypt_to_string(ciphertext: &str) -> XResult<String> {
|
||||
if is_hmac_encrypted(ciphertext) {
|
||||
hmac_decrypt_to_string(ciphertext)
|
||||
} else {
|
||||
Ok(ciphertext.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hmac_decrypt_to_string(ciphertext: &str) -> XResult<String> {
|
||||
let plaintext = hmac_decrypt(ciphertext)?;
|
||||
Ok(String::from_utf8(plaintext)?)
|
||||
}
|
||||
|
||||
pub fn hmac_decrypt(ciphertext: &str) -> XResult<Vec<u8>> {
|
||||
if !is_hmac_encrypted(ciphertext) {
|
||||
return simple_error!("Invalid ciphertext: {}", ciphertext);
|
||||
}
|
||||
let parts = ciphertext.split(":").collect::<Vec<_>>();
|
||||
let hmac_nonce = hex::decode(parts[1])?;
|
||||
let aes_gcm_nonce = hex::decode(parts[2])?;
|
||||
let ciphertext = base64_decode(parts[3])?;
|
||||
|
||||
let hmac_key = compute_yubikey_hmac(&hmac_nonce)?;
|
||||
let key = copy_sha256(&sha256_bytes(&hmac_key))?;
|
||||
|
||||
let mut decryptor = Aes256GcmStreamDecryptor::new(key, &aes_gcm_nonce);
|
||||
let mut plaintext = decryptor.update(&ciphertext);
|
||||
let final_part = decryptor.finalize()?;
|
||||
plaintext.extend_from_slice(&final_part);
|
||||
|
||||
Ok(plaintext)
|
||||
}
|
||||
|
||||
pub fn compute_yubikey_hmac(challenge_bytes: &[u8]) -> XResult<Vec<u8>> {
|
||||
let mut yubi = Yubico::new();
|
||||
let device = match yubi.find_yubikey() {
|
||||
Ok(device) => device,
|
||||
Err(_) => {
|
||||
return simple_error!("YubiKey not found");
|
||||
}
|
||||
};
|
||||
debugging!("Found key, Vendor ID: {:?}, Product ID: {:?}", device.vendor_id, device.product_id);
|
||||
let config = Config::default()
|
||||
.set_vendor_id(device.vendor_id)
|
||||
.set_product_id(device.product_id)
|
||||
.set_variable_size(true)
|
||||
.set_mode(Mode::Sha1)
|
||||
.set_slot(Slot::Slot2);
|
||||
|
||||
let hmac_result = opt_result!(yubi.challenge_response_hmac(&challenge_bytes, config), "Challenge HMAC failed: {}");
|
||||
Ok(hmac_result.deref().to_vec())
|
||||
}
|
||||
|
||||
pub fn get_challenge_bytes(sub_arg_matches: &ArgMatches) -> XResult<Vec<u8>> {
|
||||
let challenge_bytes: Vec<u8> = if let Some(challenge) = sub_arg_matches.value_of("challenge") {
|
||||
|
||||
@@ -9,6 +9,8 @@ mod cmd_chall;
|
||||
mod cmd_challconfig;
|
||||
mod cmd_ecverify;
|
||||
mod cmd_hmac_sha1;
|
||||
mod cmd_hmacencrypt;
|
||||
mod cmd_hmacdecrypt;
|
||||
mod cmd_list;
|
||||
#[cfg(feature = "with-sequoia-openpgp")]
|
||||
mod cmd_pgp;
|
||||
@@ -101,6 +103,8 @@ fn inner_main() -> CommandError {
|
||||
Box::new(cmd_list::CommandImpl),
|
||||
Box::new(cmd_chall::CommandImpl),
|
||||
Box::new(cmd_hmac_sha1::CommandImpl),
|
||||
Box::new(cmd_hmacencrypt::CommandImpl),
|
||||
Box::new(cmd_hmacdecrypt::CommandImpl),
|
||||
Box::new(cmd_challconfig::CommandImpl),
|
||||
Box::new(cmd_rsaencrypt::CommandImpl),
|
||||
Box::new(cmd_rsadecrypt::CommandImpl),
|
||||
|
||||
Reference in New Issue
Block a user