diff --git a/Cargo.lock b/Cargo.lock index 472384c..fb0ab95 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -365,9 +365,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ca741a962e1b0bff6d724a1a0958b686406e853bb14061f218562e1896f95e6" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" dependencies = [ "libc", ] @@ -997,7 +997,7 @@ checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "local-mini-kms" -version = "1.0.3" +version = "1.0.4" dependencies = [ "aes-gcm-stream", "aes-kw", @@ -1334,9 +1334,9 @@ checksum = "7be55bf0ae1635f4d7c7ddd6efc05c631e98a82104a73d35550bbc52db960027" [[package]] name = "proc-macro2" -version = "1.0.91" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "307e3004becf10f5a6e0d59d20f3cd28231b0e0827a96cd3e0ce6d14bc1e4bb3" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] diff --git a/Cargo.toml b/Cargo.toml index 16d0904..32aff43 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "local-mini-kms" -version = "1.0.3" +version = "1.0.4" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/README.md b/README.md index f60afa1..45b184e 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ cargo build --release [--no-default-features] ## Init New random master key: + ```shell head -c 32 /dev/random | base64 ``` @@ -18,6 +19,7 @@ head -c 32 /dev/random | base64 ## Generate Yubikey encrypted master key Generate encrypted master key with Yubikey: + ```shell local-mini-kms yubikey-init-master-key --generate-key [--yubikey-challenge *challenge*] ``` @@ -25,11 +27,13 @@ local-mini-kms yubikey-init-master-key --generate-key [--yubikey-challenge *chal ## 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*]] ``` @@ -67,6 +71,7 @@ local-mini-kms cli --write --name test --value hello [--force-write] [--comment ## cURL Write value: + ```shell curl -X POST http://127.0.0.1:5567/write \ -H "Content-Type: application/json" \ @@ -74,6 +79,7 @@ curl -X POST http://127.0.0.1:5567/write \ ``` Read value: + ```shell curl -X POST http://127.0.0.1:5567/read \ -H "Content-Type: application/json" \ @@ -81,14 +87,35 @@ curl -X POST http://127.0.0.1:5567/read \ ``` Generate data key: + ```shell curl -X POST http://127.0.0.1:5567/datakey \ -H "Content-Type: application/json" \ - -d '{"type":"aes", "spec":"256", "exportable": true, "return_plaintext": true}' + -d '{"type":"aes", "spec":"256", "exportable": true, "return_plaintext": true, "name": "key001", "comment": "the comment"}' ``` +```shell +xh POST http://127.0.0.1:5567/datakey \ + type=aes \ + spec=256 \ + exportable:=false \ + name=testkey01 \ + comment='this is a test key 01' +``` + +| Key | Comment | +|------------------|--------------------------------------------| +| type | `aes` | +| spec | ~~`128`, `192`,~~ `256` if `type` == `aes` | +| exportable | `true` or `false` | +| return_plaintext | `true` or `false` | +| name | Data key name | +| comment | Data key comment | + Upgrade to v3.2 + ```sql -ALTER TABLE keys ADD COLUMN comment TEXT; +ALTER TABLE keys + ADD COLUMN comment TEXT; ``` diff --git a/src/cli.rs b/src/cli.rs index 71e5355..a269601 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -241,10 +241,10 @@ async fn do_inner_request(sub_arg_matches: &ArgMatches<'_>, action: &str, body: let req = Request::builder().method(Method::POST).uri(uri).body(Body::from(body))?; let req_response = client.request(req).await?; - if req_response.status() != StatusCode::OK { - let status = req_response.status().as_u16(); - let data = response_to_value(req_response).await?; - return simple_error!("Server status is not success: {}, response: {}", status, data); - } + // if req_response.status() != StatusCode::OK { + // let status = req_response.status().as_u16(); + // let data = response_to_value(req_response).await?; + // return simple_error!("Server status is not success: {}, response: {}", status, data); + // } response_to_value(req_response).await } diff --git a/src/db.rs b/src/db.rs index 04d3c75..081595e 100644 --- a/src/db.rs +++ b/src/db.rs @@ -9,10 +9,14 @@ pub struct Key { pub comment: Option, } -pub fn make_db_key_name(name: &str) -> String { +pub fn make_value_key_name(name: &str) -> String { format!("value:{}", name) } +pub fn make_data_key_name(name: &str) -> String { + format!("data_key:{}", name) +} + pub fn open_db(db: &str) -> XResult { let con = opt_result!(Connection::open(db), "Open sqlite db: {}, failed: {}", db); log::debug!("Db auto commit: {}", con.is_autocommit()); @@ -42,6 +46,7 @@ pub fn init_db(conn: &Connection) -> XResult { pub fn insert_key(conn: &Connection, key: &Key) -> XResult<()> { let default_comment = "".to_string(); + log::debug!("insert key name={}", &key.name); let _ = conn.execute( "INSERT INTO keys (name, value, comment) VALUES (?1, ?2, ?3)", (&key.name, &key.encrypted_key, key.comment.as_ref().unwrap_or(&default_comment)), @@ -50,6 +55,7 @@ pub fn insert_key(conn: &Connection, key: &Key) -> XResult<()> { } pub fn update_key(conn: &Connection, key: &Key) -> XResult<()> { + log::debug!("update key name={}", &key.name); if let Some(comment) = &key.comment { let _ = conn.execute( "UPDATE keys SET value = ?1, comment = ?2 WHERE name = ?3", @@ -74,8 +80,14 @@ pub fn find_key(conn: &Connection, name: &str) -> XResult> { }) })?; match key_iter.next() { - None => Ok(None), - Some(Ok(r)) => Ok(Some(r)), + None => { + log::debug!("key name={} not exists", name); + Ok(None) + } + Some(Ok(r)) => { + log::debug!("found key name={}", name); + Ok(Some(r)) + } Some(Err(e)) => simple_error!("Find key failed: {}", e), } } \ No newline at end of file diff --git a/src/serve_common.rs b/src/serve_common.rs index 503fe81..3c40418 100644 --- a/src/serve_common.rs +++ b/src/serve_common.rs @@ -34,13 +34,20 @@ pub fn ok(body: Value) -> XResult<(StatusCode, Value)> { Ok((StatusCode::OK, body)) } -pub fn error(error: &str) -> XResult<(StatusCode, Value)> { +pub fn client_error(error: &str) -> XResult<(StatusCode, Value)> { Ok(( StatusCode::BAD_REQUEST, json!({ "error": error }) )) } +pub fn server_error(error: &str) -> XResult<(StatusCode, Value)> { + Ok(( + StatusCode::INTERNAL_SERVER_ERROR, + json!({ "error": error }) + )) +} + // pub fn bad_request(error: &str, error_message: &str) -> XResult<(StatusCode, Value)> { // Ok(( // StatusCode::BAD_REQUEST, @@ -97,6 +104,15 @@ impl MultipleViewValue { } } +#[macro_export] macro_rules! require_master_key { + () => { + match $crate::serve_common::get_master_key() { + None => return $crate::serve_common::client_error("status_not_ready"), + Some(key) => key, + } + }; +} + pub fn get_master_key() -> Option { let startup_rw_lock = STATUP_RW_LOCK.lock().expect("Lock read startup rw lock error"); match &*startup_rw_lock { diff --git a/src/serve_datakey.rs b/src/serve_datakey.rs index 6320fa8..35bfc7a 100644 --- a/src/serve_datakey.rs +++ b/src/serve_datakey.rs @@ -1,5 +1,6 @@ -use crate::serve_common::{get_master_key, Result}; -use crate::{do_response, jose, serve_common}; +use crate::db::Key; +use crate::serve_common::{open_local_db, Result}; +use crate::{db, do_response, jose, require_master_key, serve_common}; use base64::engine::general_purpose::STANDARD; use base64::Engine; use hyper::body::Buf; @@ -11,7 +12,6 @@ use serde_derive::{Deserialize, Serialize}; use serde_json::json; use serde_json::{Map, Value}; - // type:aes, spec:128,192,256 // type:rsa, spec:2048,3072,4096 // type:ec, spec:p256,p384,p521,ed25519,cv25519 @@ -20,6 +20,7 @@ struct DataKeyRequest { r#type: String, spec: String, name: Option, + comment: Option, exportable: Option, return_plaintext: Option, } @@ -33,24 +34,21 @@ async fn inner_generate(req: Request) -> XResult<(StatusCode, Value)> { let request: DataKeyRequest = serde_json::from_reader(whole_body.reader())?; log::debug!("Generate data key: {} {}", &request.r#type, &request.spec); - let key = match get_master_key() { - None => return serve_common::error("status_not_ready"), - Some(key) => key, - }; + let key = require_master_key!(); let exportable = request.exportable.unwrap_or(true); let ret_key_plaintext = iff!(!exportable, false, request.return_plaintext.unwrap_or(false)); let response_result = match (request.r#type.as_str(), request.spec.as_str()) { - ("aes", "128") => generate_aes("datakey:aes-128", exportable, key, 16, ret_key_plaintext), - ("aes", "192") => generate_aes("datakey:aes-192", exportable, key, 24, ret_key_plaintext), + // ("aes", "128") => generate_aes("datakey:aes-128", exportable, key, 16, ret_key_plaintext), + // ("aes", "192") => generate_aes("datakey:aes-192", exportable, key, 24, ret_key_plaintext), ("aes", "256") => generate_aes("datakey:aes-256", exportable, key, 32, ret_key_plaintext), // TODO rsa 2048, rsa 3072, rsa 4096 // TODO ec p256, p384, p521, ed25519, cv25519 - _ => return serve_common::error("invalid key_type or key_spec"), + _ => return serve_common::client_error("invalid key_type or key_spec"), }; match response_result { - Err(e) => serve_common::error(&format!("internal error: {}", e)), + Err(e) => serve_common::server_error(&format!("internal error: {}", e)), Ok((key_plaintext, key_ciphertext)) => { let mut map = Map::new(); map.insert("key_type".to_string(), Value::String(request.r#type)); @@ -58,11 +56,24 @@ async fn inner_generate(req: Request) -> XResult<(StatusCode, Value)> { if let Some(key_plaintext) = key_plaintext { map.insert("key_plaintext".to_string(), Value::String(STANDARD.encode(&key_plaintext))); } - map.insert("key_ciphertext".to_string(), Value::String(key_ciphertext)); + map.insert("key_ciphertext".to_string(), Value::String(key_ciphertext.clone())); - if let Some(_name) = &request.name { - // TODO write datakey to db - // TODO let data_key_name = format!("datakey:{}", name); + if let Some(name) = &request.name { + if name.is_empty() { + return serve_common::client_error("name_is_empty"); + } + let conn = open_local_db()?; + let db_key_name = db::make_data_key_name(name); + let db_key = db::find_key(&conn, &db_key_name)?; + if db_key.is_some() { + return serve_common::client_error("name_exists"); + } + let key = Key { + name: db_key_name, + encrypted_key: key_ciphertext, + comment: request.comment, + }; + db::insert_key(&conn, &key)?; } Ok((StatusCode::OK, Value::Object(map))) diff --git a/src/serve_encrypt_decrypt.rs b/src/serve_encrypt_decrypt.rs index e09e2fb..1334739 100644 --- a/src/serve_encrypt_decrypt.rs +++ b/src/serve_encrypt_decrypt.rs @@ -4,9 +4,9 @@ use rust_util::XResult; use serde::{Deserialize, Serialize}; use serde_json::{json, Map, Value}; -use crate::do_response; use crate::jose; -use crate::serve_common::{self, byte_to_multi_view_map, get_master_key, MultipleViewValue, Result}; +use crate::serve_common::{self, byte_to_multi_view_map, MultipleViewValue, Result}; +use crate::{do_response, require_master_key}; #[derive(Serialize, Deserialize)] struct DecryptRequest { @@ -22,16 +22,13 @@ async fn inner_decrypt(req: Request) -> XResult<(StatusCode, Value)> { let data: DecryptRequest = serde_json::from_reader(whole_body.reader())?; log::debug!("To be decrypted value: {}", &data.encrypted_value); - let key = match get_master_key() { - None => return serve_common::error("status_not_ready"), - Some(key) => key, - }; + let key = require_master_key!(); let decrypted_value = jose::deserialize_jwe_aes(&data.encrypted_value, &key.read()); drop(key); let (data, header) = decrypted_value?; if let Some(false) = header.exportable { - return serve_common::error("data_not_exportable"); + return serve_common::client_error("data_not_exportable"); } let mut map = byte_to_multi_view_map(&data, true); @@ -58,10 +55,7 @@ async fn inner_encrypt(req: Request) -> XResult<(StatusCode, Value)> { let whole_body = hyper::body::aggregate(req).await?; let data: MultipleViewValue = serde_json::from_reader(whole_body.reader())?; let value = data.to_bytes()?; - let key = match get_master_key() { - None => return serve_common::error("status_not_ready"), - Some(key) => key, - }; + let key = require_master_key!(); let encrypt_result = jose::serialize_jwe_aes(&value, &key.read()); drop(key); diff --git a/src/serve_init.rs b/src/serve_init.rs index f13794f..faf6ab2 100644 --- a/src/serve_init.rs +++ b/src/serve_init.rs @@ -37,9 +37,9 @@ async fn inner_init(req: Request) -> XResult<(StatusCode, Value)> { pub async fn inner_init_request(init_request: InitRequest) -> XResult<(StatusCode, Value)> { let mut startup_rw_lock = serve_common::STATUP_RW_LOCK.lock().expect("Lock read startup rw lock error"); match &*startup_rw_lock { - None => return Ok((StatusCode::INTERNAL_SERVER_ERROR, json!({ "error": "internal_error", "error_message": "not init " }))), + None => return serve_common::server_error("instant_key_pair_not_initialized"), Some(memory_key) => if memory_key.master_key.is_some() { - return Ok((StatusCode::BAD_REQUEST, json!({ "error": "bad_request", "error_message": "already init " }))); + return serve_common::client_error("already_initialized"); }, } @@ -53,14 +53,14 @@ pub async fn inner_init_request(init_request: InitRequest) -> XResult<(StatusCod let (clear_master_key, _) = jose::deserialize_jwe_rsa(&encrypted_master_key, &k.instance_rsa_key_pair)?; clear_master_key } else { - return Ok((StatusCode::INTERNAL_SERVER_ERROR, json!({ "error": "internal_error", "error_message": "not init " }))); + return serve_common::server_error("instant_key_pair_not_initialized"); } } else { - return serve_common::error("master_key_missing"); + return serve_common::client_error("master_key_missing"); }; if clear_master_key.len() != 32 { - return serve_common::error("bad_master_key_length"); + return serve_common::client_error("bad_master_key_length"); } if let Some(k) = &mut *startup_rw_lock { diff --git a/src/serve_read_write.rs b/src/serve_read_write.rs index b67de3d..e4f11f4 100644 --- a/src/serve_read_write.rs +++ b/src/serve_read_write.rs @@ -1,7 +1,7 @@ use crate::db::Key; -use crate::do_response; -use crate::serve_common::{self, byte_to_multi_view_map, get_master_key, open_local_db, MultipleViewValue, Result}; +use crate::serve_common::{self, byte_to_multi_view_map, open_local_db, MultipleViewValue, Result}; use crate::{db, jose}; +use crate::{do_response, require_master_key}; use hyper::body::Buf; use hyper::{Body, Request, Response, StatusCode}; use rust_util::XResult; @@ -30,22 +30,19 @@ async fn inner_read(req: Request) -> XResult<(StatusCode, Value)> { let named: Named = serde_json::from_reader(whole_body.reader())?; let name = &named.name; if name.is_empty() { - return serve_common::error("key_name_is_empty"); + return serve_common::client_error("name_is_empty"); } - let db_key_name = db::make_db_key_name(name); + let db_key_name = db::make_value_key_name(name); let conn = open_local_db()?; let db_key = db::find_key(&conn, &db_key_name)?; let db_key_value = match db_key { - None => return serve_common::error("key_name_not_exists"), + None => return serve_common::client_error("name_not_exists"), Some(k) => k, }; - let key = match get_master_key() { - None => return serve_common::error("status_not_ready"), - Some(key) => key, - }; + let key = require_master_key!(); let data = jose::deserialize_jwe_aes(&db_key_value.encrypted_key, &key.read())?; drop(key); @@ -64,23 +61,20 @@ async fn inner_write(req: Request) -> XResult<(StatusCode, Value)> { let named_value: NamedValue = serde_json::from_reader(whole_body.reader())?; let name = &named_value.name; if name.is_empty() { - return serve_common::error("key_name_is_empty"); + return serve_common::client_error("name_is_empty"); } - let db_key_name = db::make_db_key_name(name); + let db_key_name = db::make_value_key_name(name); let force_write = named_value.force_write.unwrap_or(false); let conn = open_local_db()?; let db_key = db::find_key(&conn, &db_key_name)?; if db_key.is_some() && !force_write { - return serve_common::error("key_name_exists"); + return serve_common::client_error("name_exists"); } let value = named_value.value.to_bytes()?; - let key = match get_master_key() { - None => return serve_common::error("status_not_ready"), - Some(key) => key, - }; + let key = require_master_key!(); let encrypt_value = jose::serialize_jwe_aes(&value, &key.read())?; drop(key);