feat: v1.0.4, generate data key and save to db
This commit is contained in:
10
Cargo.lock
generated
10
Cargo.lock
generated
@@ -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",
|
||||
]
|
||||
|
||||
@@ -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
|
||||
|
||||
31
README.md
31
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;
|
||||
```
|
||||
|
||||
|
||||
10
src/cli.rs
10
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
|
||||
}
|
||||
|
||||
18
src/db.rs
18
src/db.rs
@@ -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),
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)))
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user