feat: v1.0.4, generate data key and save to db

This commit is contained in:
2024-11-22 23:42:04 +08:00
parent 20ad9e6bd7
commit 87cba2be57
10 changed files with 118 additions and 64 deletions

10
Cargo.lock generated
View File

@@ -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",
]

View File

@@ -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

View File

@@ -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;
```

View File

@@ -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
}

View File

@@ -9,10 +9,14 @@ pub struct Key {
pub comment: Option<String>,
}
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<Connection> {
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<bool> {
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<Option<Key>> {
})
})?;
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),
}
}

View File

@@ -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<SecBytes> {
let startup_rw_lock = STATUP_RW_LOCK.lock().expect("Lock read startup rw lock error");
match &*startup_rw_lock {

View File

@@ -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<String>,
comment: Option<String>,
exportable: Option<bool>,
return_plaintext: Option<bool>,
}
@@ -33,24 +34,21 @@ async fn inner_generate(req: Request<Body>) -> 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<Body>) -> 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)))

View File

@@ -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<Body>) -> 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<Body>) -> 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);

View File

@@ -37,9 +37,9 @@ async fn inner_init(req: Request<Body>) -> 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 {

View File

@@ -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<Body>) -> 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<Body>) -> 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);