From b188a2bc1ec4e276fab0ccbb7d676a0f91c49cd8 Mon Sep 17 00:00:00 2001 From: Hatter Jiang Date: Tue, 3 Sep 2024 23:19:17 +0800 Subject: [PATCH] feat: v0.3.5, add subcommand yubikey-init-master-key --- Cargo.lock | 28 +++++++--------- Cargo.toml | 15 +++++---- README.md | 13 ++++++++ src/main.rs | 2 ++ src/yubikey_init_master_key.rs | 60 ++++++++++++++++++++++++++++++++++ 5 files changed, 94 insertions(+), 24 deletions(-) create mode 100644 src/yubikey_init_master_key.rs diff --git a/Cargo.lock b/Cargo.lock index 78a4b6e..b3cd96f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -56,12 +56,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "allocator-api2" -version = "0.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" - [[package]] name = "android-tzdata" version = "0.1.1" @@ -385,9 +379,9 @@ dependencies = [ [[package]] name = "fallible-iterator" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" [[package]] name = "fallible-streaming-iterator" @@ -518,14 +512,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ "ahash", - "allocator-api2", ] [[package]] name = "hashlink" -version = "0.8.4" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" +checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" dependencies = [ "hashbrown", ] @@ -715,9 +708,9 @@ dependencies = [ [[package]] name = "libsqlite3-sys" -version = "0.25.2" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29f835d03d717946d28b1d1ed632eb6f0e24a299388ee623d0c23118d3e8a7fa" +checksum = "0c10584274047cb335c23d3e61bcef8e323adae7c5c8c760540f73610177fc3f" dependencies = [ "pkg-config", "vcpkg", @@ -749,7 +742,7 @@ checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "local-mini-kms" -version = "0.3.4" +version = "0.3.5" dependencies = [ "base64 0.21.7", "clap", @@ -758,6 +751,7 @@ dependencies = [ "josekit", "lazy_static", "procfs", + "rand", "rpassword", "rusqlite", "rust_util", @@ -1131,11 +1125,11 @@ dependencies = [ [[package]] name = "rusqlite" -version = "0.28.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01e213bc3ecb39ac32e81e51ebe31fd888a940515173e3a18a35f8c6e896422a" +checksum = "b838eba278d213a8beaf485bd313fd580ca4505a00d5871caeb1457c55322cae" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.6.0", "fallible-iterator", "fallible-streaming-iterator", "hashlink", diff --git a/Cargo.toml b/Cargo.toml index ddca4cd..a608fc4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,17 +1,17 @@ [package] name = "local-mini-kms" -version = "0.3.4" +version = "0.3.5" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -zeroize = "1.5" -clap = "2.33" +zeroize = "1.8" +clap = "2.34" hex = "0.4" base64 = "0.21" sha2 = "0.10" -lazy_static = "1.4" +lazy_static = "1.5" serde_derive = "1.0" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" @@ -19,11 +19,12 @@ josekit = "0.8" secmem-proc = "0.3" seckey = "0.12" rust_util = { version = "0.6", features = ["use_clap"] } -tokio = { version = "1.19", features = ["full"] } +tokio = { version = "1.37", features = ["full"] } hyper = { version = "0.14", features = ["client", "server", "tcp", "http1", "http2"] } -rusqlite = "0.28" +rusqlite = "0.31" yubico_manager = "0.9" -rpassword = "7.2" +rpassword = "7.3" +rand = "0.8" [target.'cfg(target_os = "linux")'.dependencies] procfs = "0.13" diff --git a/README.md b/README.md index e3a7bae..4305342 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,21 @@ Mini-KMS runs local written by Rust +## Generate Yubikey encrypted master key + +Generate encrypted master key with Yubikey: +```shell +local-mini-kms yubikey-init-master-key --generate-key [--yubikey-challenge *challenge*] +``` + ## Startup Server +Startup without init: +```shell +local-mini-kms serve +``` + +Init with Yubikey: ```shell local-mini-kms serve [--init-encrypted-master-key LKMS:*** [--yubikey-challenge *challenge*]] ``` diff --git a/src/main.rs b/src/main.rs index 07feb1d..79c3ecb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,6 +13,7 @@ mod serve_status; mod serve_init; mod serve_encrypt_decrypt; mod serve_read_write; +mod yubikey_init_master_key; pub struct DefaultCommandImpl; @@ -46,6 +47,7 @@ fn inner_main() -> CommandError { let commands: Vec> = vec![ Box::new(cli::CommandImpl), Box::new(serve::CommandImpl), + Box::new(yubikey_init_master_key::CommandImpl), ]; let mut app = App::new(env!("CARGO_PKG_NAME")) .version(env!("CARGO_PKG_VERSION")) diff --git a/src/yubikey_init_master_key.rs b/src/yubikey_init_master_key.rs new file mode 100644 index 0000000..835b16d --- /dev/null +++ b/src/yubikey_init_master_key.rs @@ -0,0 +1,60 @@ +use crate::{jose, yubikey_hmac}; +use base64::engine::general_purpose::STANDARD; +use base64::Engine; +use clap::{App, Arg, ArgMatches, SubCommand}; +use rand::random; +use rust_util::util_clap::{Command, CommandError}; +use rust_util::{failure_and_exit, opt_result, success}; +pub struct CommandImpl; + +impl Command for CommandImpl { + fn name(&self) -> &str { "yubikey-init-master-key" } + + fn subcommand<'a>(&self) -> App<'a, 'a> { + SubCommand::with_name(self.name()).about("Local mini KMS init yubikey") + .arg(Arg::with_name("yubikey-challenge").long("yubikey-challenge").short("c").takes_value(true).help("Yubikey challenge")) + .arg(Arg::with_name("key-hex").long("key-hex").short("x").takes_value(true).help("Key(hex), for encrypt")) + .arg(Arg::with_name("key-base64").long("key-base64").short("b").takes_value(true).help("Key(base64), for encrypt")) + .arg(Arg::with_name("generate-key").long("generate-key").short("K").help("Generate key")) + } + + fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError { + let yubikey_challenge_opt = sub_arg_matches.value_of("yubikey-challenge").map(ToString::to_string); + + let hex_value_opt = sub_arg_matches.value_of("key-hex"); + let base64_value_opt = sub_arg_matches.value_of("key-base64"); + let generate_key_present = sub_arg_matches.is_present("generate-key"); + + if hex_value_opt.is_none() && base64_value_opt.is_none() && !generate_key_present { + failure_and_exit!("--key-hex, --key-base64 or --generate-key must assign one"); + } + let clear_master_key = if let Some(hex_value) = hex_value_opt { + opt_result!( hex::decode(hex_value), "Decode key-hex failed: {}") + } else if let Some(base64_value) = base64_value_opt { + opt_result!(STANDARD.decode(base64_value), "Decode key-base64 failed: {}") + } else { + let clear_master_key: [u8; 32] = random(); + success!("Clear master key generated: {}", hex::encode(&clear_master_key)); + clear_master_key.to_vec() + }; + + if clear_master_key.len() != 32 { + failure_and_exit!("Master key must be 32 bytes"); + } + + let yubikey_challenge = yubikey_challenge_opt.unwrap_or_else( + || match rpassword::prompt_password("Yubikey challenge: ") { + Ok(yubikey_challenge) => yubikey_challenge, + Err(e) => failure_and_exit!("Read yubikey challenge failed: {}", e), + } + ); + + let challenge_key = opt_result!( + yubikey_hmac::yubikey_challenge_as_32_bytes(yubikey_challenge.as_bytes()), "Yubikey challenge failed: {}"); + + let encrypted_master_key = opt_result!(jose::serialize_jwe_aes(&clear_master_key, &challenge_key), "Encrypt master key failed: {}"); + success!("Encrypted master key: {}", encrypted_master_key); + + Ok(Some(0)) + } +} \ No newline at end of file