diff --git a/Cargo.lock b/Cargo.lock index b3cd96f..4c37bcd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -742,7 +742,7 @@ checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "local-mini-kms" -version = "0.3.5" +version = "0.3.6" dependencies = [ "base64 0.21.7", "clap", diff --git a/Cargo.toml b/Cargo.toml index a608fc4..4695fa2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "local-mini-kms" -version = "0.3.5" +version = "0.3.6" 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 4305342..a936151 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,13 @@ curl -X POST http://127.0.0.1:5567/read \ -d '{"name":"test"}' ``` +Generate data key: +```shell +curl -X POST http://127.0.0.1:5567/datakey \ + -H "Content-Type: application/json" \ + -d '{"key_type":"aes", "key_spec":"256", "return_plaintext": true}' +``` + Upgrade to v3.2 ```sql ALTER TABLE keys ADD COLUMN comment TEXT; diff --git a/src/main.rs b/src/main.rs index 79c3ecb..913a8b3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,6 +14,7 @@ mod serve_init; mod serve_encrypt_decrypt; mod serve_read_write; mod yubikey_init_master_key; +mod serve_datakey; pub struct DefaultCommandImpl; diff --git a/src/serve.rs b/src/serve.rs index 0bda39d..ce29172 100644 --- a/src/serve.rs +++ b/src/serve.rs @@ -10,7 +10,6 @@ use rust_util::{failure_and_exit, information, success, warning, XResult}; use serde_json::{json, Value}; use tokio::runtime::Runtime; -use crate::do_response; use crate::serve_common::{self, GenericError, MemoryKey, Result}; use crate::serve_encrypt_decrypt; use crate::serve_init; @@ -19,6 +18,7 @@ use crate::serve_read_write; use crate::serve_status; use crate::yubikey_hmac; use crate::{db, jose, proc}; +use crate::{do_response, serve_datakey}; pub struct CommandImpl; @@ -94,6 +94,7 @@ async fn response_requests( (&Method::POST, "/encrypt") => serve_encrypt_decrypt::encrypt(req).await, (&Method::POST, "/read") => serve_read_write::read(req).await, (&Method::POST, "/write") => serve_read_write::write(req).await, + (&Method::POST, "/datakey") => serve_datakey::generate(req).await, (&Method::GET, "/status") => serve_status::status().await, (&Method::GET, "/version") => get_version().await, (&Method::GET, "/") => get_root().await, diff --git a/src/serve_common.rs b/src/serve_common.rs index 7f2d1cb..15083e9 100644 --- a/src/serve_common.rs +++ b/src/serve_common.rs @@ -1,7 +1,7 @@ use std::sync::Mutex; -use base64::Engine; use base64::engine::general_purpose::STANDARD; +use base64::Engine; use hyper::StatusCode; use josekit::jwk::alg::rsa::RsaKeyPair; use rusqlite::Connection; @@ -74,6 +74,14 @@ impl MultipleViewValue { } } + pub fn from_without_value(v: &[u8]) -> Self { + Self { + value: None, + value_hex: Some(hex::encode(v)), + value_base64: Some(STANDARD.encode(v)), + } + } + pub fn to_bytes(&self) -> XResult> { if let Some(v) = &self.value { Ok(v.as_bytes().to_vec()) @@ -103,8 +111,12 @@ pub fn get_master_key() -> Option { } } -pub fn byte_to_multi_view_map(bytes: &[u8]) -> Map { - let v = MultipleViewValue::from(bytes); +pub fn byte_to_multi_view_map(bytes: &[u8], with_value: bool) -> Map { + let v = if with_value { + MultipleViewValue::from(bytes) + } else { + MultipleViewValue::from_without_value(bytes) + }; let mut map = Map::new(); if let Some(v) = &v.value { map.insert("value".to_string(), Value::String(v.to_string())); diff --git a/src/serve_datakey.rs b/src/serve_datakey.rs new file mode 100644 index 0000000..8664ca1 --- /dev/null +++ b/src/serve_datakey.rs @@ -0,0 +1,77 @@ +use crate::serve_common::{get_master_key, Result}; +use crate::{do_response, jose, serve_common}; +use base64::engine::general_purpose::STANDARD; +use base64::Engine; +use hyper::body::Buf; +use hyper::{Body, Request, Response, StatusCode}; +use rand::random; +use rust_util::{debugging, iff, XResult}; +use seckey::SecBytes; +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 +#[derive(Serialize, Deserialize)] +struct DataKeyRequest { + key_type: String, + key_spec: String, + return_plaintext: Option, +} + +pub async fn generate(req: Request) -> Result> { + do_response!(inner_generate(req).await) +} + +async fn inner_generate(req: Request) -> XResult<(StatusCode, Value)> { + let whole_body = hyper::body::aggregate(req).await?; + let request: DataKeyRequest = serde_json::from_reader(whole_body.reader())?; + + debugging!("Generate data key: {} {}", &request.key_type, &request.key_spec); + let key = match get_master_key() { + None => return serve_common::error("status_not_ready"), + Some(key) => key, + }; + let ret_key_plaintext = request.return_plaintext.unwrap_or(false); + + let response_result = match (request.key_type.as_str(), request.key_spec.as_str()) { + ("aes", "128") => generate_aes("datakey.aes_128:", key, 16, ret_key_plaintext), + ("aes", "192") => generate_aes("datakey.aes_192:", key, 24, ret_key_plaintext), + ("aes", "256") => generate_aes("datakey.aes_256:", 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"), + }; + + match response_result { + Err(e) => serve_common::error(&format!("internal error: {}", e)), + Ok((key_plaintext, key_ciphertext)) => { + let mut map = Map::new(); + map.insert("key_type".to_string(), Value::String(request.key_type)); + map.insert("key_spec".to_string(), Value::String(request.key_spec)); + 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)); + Ok((StatusCode::OK, Value::Object(map))) + } + } +} + +fn generate_aes(prefix: &str, key: SecBytes, len: i32, ret_key_plaintext: bool) -> XResult<(Option>, String)> { + let bytes: [u8; 32] = random(); + let value = &bytes[0..len as usize]; + let key_plaintext = iff!(ret_key_plaintext, Some(value.to_vec()), None); + let key_ciphertext = jose::serialize_jwe_aes(&join_prefix_value(prefix, value), &key.read())?; + Ok((key_plaintext, key_ciphertext)) +} + +fn join_prefix_value(prefix: &str, value: &[u8]) -> Vec { + let mut ret = Vec::with_capacity(prefix.len() + value.len()); + ret.extend_from_slice(prefix.as_bytes()); + ret.extend_from_slice(value); + ret +} \ No newline at end of file diff --git a/src/serve_encrypt_decrypt.rs b/src/serve_encrypt_decrypt.rs index 743ff13..a2e6a5c 100644 --- a/src/serve_encrypt_decrypt.rs +++ b/src/serve_encrypt_decrypt.rs @@ -13,9 +13,7 @@ struct DecryptRequest { encrypted_value: String, } -pub - -async fn decrypt(req: Request) -> Result> { +pub async fn decrypt(req: Request) -> Result> { do_response!(inner_decrypt(req).await) } @@ -32,7 +30,7 @@ async fn inner_decrypt(req: Request) -> XResult<(StatusCode, Value)> { drop(key); decrypted_value.map(|v| { - let map = byte_to_multi_view_map(&v.0); + let map = byte_to_multi_view_map(&v.0, true); (StatusCode::OK, Value::Object(map)) }) } diff --git a/src/serve_read_write.rs b/src/serve_read_write.rs index 90cbac0..b67de3d 100644 --- a/src/serve_read_write.rs +++ b/src/serve_read_write.rs @@ -1,14 +1,13 @@ -use hyper::{Body, Request, Response, StatusCode}; +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::{db, jose}; use hyper::body::Buf; +use hyper::{Body, Request, Response, StatusCode}; use rust_util::XResult; use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; -use crate::{db, jose}; -use crate::db::Key; -use crate::do_response; -use crate::serve_common::{self, byte_to_multi_view_map, get_master_key, MultipleViewValue, open_local_db, Result}; - #[derive(Serialize, Deserialize)] struct Named { name: String, @@ -50,7 +49,7 @@ async fn inner_read(req: Request) -> XResult<(StatusCode, Value)> { let data = jose::deserialize_jwe_aes(&db_key_value.encrypted_key, &key.read())?; drop(key); - let mut map = byte_to_multi_view_map(&data.0); + let mut map = byte_to_multi_view_map(&data.0, true); map.insert("name".to_string(), name.as_str().into()); map.insert("comment".to_string(), db_key_value.comment.into()); serve_common::ok(Value::Object(map)) diff --git a/src/yubikey_init_master_key.rs b/src/yubikey_init_master_key.rs index 835b16d..cc1af9e 100644 --- a/src/yubikey_init_master_key.rs +++ b/src/yubikey_init_master_key.rs @@ -34,7 +34,7 @@ impl Command for CommandImpl { 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)); + success!("Clear master key generated: {}", hex::encode(clear_master_key)); clear_master_key.to_vec() };