feat: add read/write
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
|||||||
|
LOCAL_README.md
|
||||||
*_gitignore.*
|
*_gitignore.*
|
||||||
local-mini-kms.db
|
local-mini-kms.db
|
||||||
.idea/
|
.idea/
|
||||||
|
|||||||
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -653,7 +653,7 @@ checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "local-mini-kms"
|
name = "local-mini-kms"
|
||||||
version = "0.2.3"
|
version = "0.3.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.21.0",
|
"base64 0.21.0",
|
||||||
"clap",
|
"clap",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "local-mini-kms"
|
name = "local-mini-kms"
|
||||||
version = "0.2.3"
|
version = "0.3.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|||||||
15
README.md
15
README.md
@@ -17,3 +17,18 @@ Mini-KMS runs local written by Rust
|
|||||||
```shell
|
```shell
|
||||||
./local-mini-kms cli --offline-init
|
./local-mini-kms cli --offline-init
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Write value:
|
||||||
|
```shell
|
||||||
|
curl -X POST http://127.0.0.1:5567/write \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"name":"test","value":{"value":"hello"}}'
|
||||||
|
```
|
||||||
|
|
||||||
|
Read value:
|
||||||
|
```shell
|
||||||
|
curl -X POST http://127.0.0.1:5567/read \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"name":"test"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
|||||||
12
src/db.rs
12
src/db.rs
@@ -8,6 +8,10 @@ pub struct Key {
|
|||||||
pub encrypted_key: String,
|
pub encrypted_key: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn make_db_key_name(name: &str) -> String {
|
||||||
|
format!("value:{}", name)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn open_db(db: &str) -> XResult<Connection> {
|
pub fn open_db(db: &str) -> XResult<Connection> {
|
||||||
let con = opt_result!(Connection::open(db), "Open sqlite db: {}, failed: {}", db);
|
let con = opt_result!(Connection::open(db), "Open sqlite db: {}, failed: {}", db);
|
||||||
debugging!("Db auto commit: {}", con.is_autocommit());
|
debugging!("Db auto commit: {}", con.is_autocommit());
|
||||||
@@ -41,6 +45,14 @@ pub fn insert_key(conn: &Connection, key: &Key) -> XResult<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn update_key(conn: &Connection, key: &Key) -> XResult<()> {
|
||||||
|
let _ = conn.execute(
|
||||||
|
"UPDATE keys SET value = ?1 WHERE name = ?2",
|
||||||
|
(&key.encrypted_key, &key.name),
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn find_key(conn: &Connection, name: &str) -> XResult<Option<Key>> {
|
pub fn find_key(conn: &Connection, name: &str) -> XResult<Option<Key>> {
|
||||||
let mut stmt = conn.prepare("SELECT id, name, value FROM keys WHERE name = ?1")?;
|
let mut stmt = conn.prepare("SELECT id, name, value FROM keys WHERE name = ?1")?;
|
||||||
let mut key_iter = stmt.query_map(params![name], |row| {
|
let mut key_iter = stmt.query_map(params![name], |row| {
|
||||||
|
|||||||
@@ -7,6 +7,11 @@ mod proc;
|
|||||||
mod jose;
|
mod jose;
|
||||||
mod cli;
|
mod cli;
|
||||||
mod serve;
|
mod serve;
|
||||||
|
mod serve_common;
|
||||||
|
mod serve_status;
|
||||||
|
mod serve_init;
|
||||||
|
mod serve_encrypt_decrypt;
|
||||||
|
mod serve_read_write;
|
||||||
|
|
||||||
pub struct DefaultCommandImpl;
|
pub struct DefaultCommandImpl;
|
||||||
|
|
||||||
|
|||||||
262
src/serve.rs
262
src/serve.rs
@@ -1,29 +1,24 @@
|
|||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use std::sync::Mutex;
|
|
||||||
use base64::Engine;
|
|
||||||
use base64::engine::general_purpose::STANDARD;
|
|
||||||
|
|
||||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||||
use hyper::{Body, Client, Method, Request, Response, Server, StatusCode};
|
use hyper::{Body, Client, Method, Request, Response, Server, StatusCode};
|
||||||
use hyper::body::Buf;
|
|
||||||
use hyper::client::HttpConnector;
|
use hyper::client::HttpConnector;
|
||||||
use hyper::server::conn::AddrStream;
|
use hyper::server::conn::AddrStream;
|
||||||
use hyper::service::{make_service_fn, service_fn};
|
use hyper::service::{make_service_fn, service_fn};
|
||||||
use josekit::jwk::alg::rsa::RsaKeyPair;
|
use rust_util::{failure_and_exit, information, success, XResult};
|
||||||
use josekit::jwk::KeyPair;
|
|
||||||
use rust_util::{debugging, failure_and_exit, information, opt_result, simple_error, success, XResult};
|
|
||||||
use rust_util::util_clap::{Command, CommandError};
|
use rust_util::util_clap::{Command, CommandError};
|
||||||
use seckey::SecBytes;
|
use serde_json::{json, Value};
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use serde_json::{json, Map, Value};
|
|
||||||
use zeroize::Zeroize;
|
|
||||||
|
|
||||||
use crate::{db, jose, proc};
|
use crate::{db, jose, proc};
|
||||||
use crate::db::Key;
|
use crate::do_response;
|
||||||
|
use crate::serve_common;
|
||||||
type GenericError = Box<dyn std::error::Error + Send + Sync>;
|
use crate::serve_common::GenericError;
|
||||||
type Result<T> = std::result::Result<T, GenericError>;
|
use crate::serve_common::MemoryKey;
|
||||||
|
use crate::serve_common::Result;
|
||||||
|
use crate::serve_encrypt_decrypt;
|
||||||
|
use crate::serve_init;
|
||||||
|
use crate::serve_read_write;
|
||||||
|
use crate::serve_status;
|
||||||
|
|
||||||
pub struct CommandImpl;
|
pub struct CommandImpl;
|
||||||
|
|
||||||
@@ -79,11 +74,13 @@ async fn response_requests(
|
|||||||
let process = proc::get_process(remote_addr.port());
|
let process = proc::get_process(remote_addr.port());
|
||||||
information!("Receive request: {}, from: {}, process: {:?}", req.uri(), remote_addr, process);
|
information!("Receive request: {}, from: {}, process: {:?}", req.uri(), remote_addr, process);
|
||||||
match (req.method(), req.uri().path()) {
|
match (req.method(), req.uri().path()) {
|
||||||
(&Method::POST, "/init") => init(req).await,
|
(&Method::POST, "/init") => serve_init::init(req).await,
|
||||||
(&Method::POST, "/update") => update().await,
|
(&Method::POST, "/update") => update().await,
|
||||||
(&Method::POST, "/decrypt") => decrypt(req).await,
|
(&Method::POST, "/decrypt") => serve_encrypt_decrypt::decrypt(req).await,
|
||||||
(&Method::POST, "/encrypt") => encrypt(req).await,
|
(&Method::POST, "/encrypt") => serve_encrypt_decrypt::encrypt(req).await,
|
||||||
(&Method::GET, "/status") => status().await,
|
(&Method::POST, "/read") => serve_read_write::read(req).await,
|
||||||
|
(&Method::POST, "/write") => serve_read_write::write(req).await,
|
||||||
|
(&Method::GET, "/status") => serve_status::status().await,
|
||||||
(&Method::GET, "/version") => get_version().await,
|
(&Method::GET, "/version") => get_version().await,
|
||||||
(&Method::GET, "/") => get_root().await,
|
(&Method::GET, "/") => get_root().await,
|
||||||
_ => Ok(Response::builder()
|
_ => Ok(Response::builder()
|
||||||
@@ -94,37 +91,13 @@ async fn response_requests(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! do_response {
|
|
||||||
($ex: expr) => (
|
|
||||||
match $ex {
|
|
||||||
Ok((status_code, body)) => Ok(Response::builder().status(status_code).body((serde_json::to_string_pretty(&body)? + "\n").into())?),
|
|
||||||
Err(e) => Ok(Response::builder().status(StatusCode::INTERNAL_SERVER_ERROR).body(
|
|
||||||
format!("{}\n", serde_json::to_string_pretty(&json!({
|
|
||||||
"error": "internal_error",
|
|
||||||
"error_message": format!("{}", e),
|
|
||||||
}))?).into()
|
|
||||||
)?),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
struct MemoryKey {
|
|
||||||
database_file: String,
|
|
||||||
instance_rsa_key_pair: RsaKeyPair,
|
|
||||||
master_key: Option<SecBytes>,
|
|
||||||
}
|
|
||||||
|
|
||||||
lazy_static::lazy_static! {
|
|
||||||
static ref STATUP_RW_LOCK: Mutex<Option<MemoryKey>> = Mutex::new(None);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init_instance(db: &str) -> XResult<bool> {
|
fn init_instance(db: &str) -> XResult<bool> {
|
||||||
let conn = db::open_db(db)?;
|
let conn = db::open_db(db)?;
|
||||||
db::init_db(&conn)?;
|
db::init_db(&conn)?;
|
||||||
|
|
||||||
let mut startup_rw_lock = STATUP_RW_LOCK.lock().expect("Lock write startup rw lock error");
|
let mut startup_rw_lock = serve_common::STATUP_RW_LOCK.lock().expect("Lock write startup rw lock error");
|
||||||
match &*startup_rw_lock {
|
match &*startup_rw_lock {
|
||||||
Some(_) => Ok(false),
|
Some(_) => Ok(false),
|
||||||
None => {
|
None => {
|
||||||
@@ -140,7 +113,7 @@ fn init_instance(db: &str) -> XResult<bool> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn update_instance_rsa_key_pair() -> XResult<bool> {
|
fn update_instance_rsa_key_pair() -> XResult<bool> {
|
||||||
let mut startup_rw_lock = STATUP_RW_LOCK.lock().expect("Lock write startup rw lock error");
|
let mut startup_rw_lock = serve_common::STATUP_RW_LOCK.lock().expect("Lock write startup rw lock error");
|
||||||
match &mut *startup_rw_lock {
|
match &mut *startup_rw_lock {
|
||||||
Some(k) => {
|
Some(k) => {
|
||||||
k.instance_rsa_key_pair = jose::generate_rsa_key(4096)?;
|
k.instance_rsa_key_pair = jose::generate_rsa_key(4096)?;
|
||||||
@@ -150,97 +123,6 @@ fn update_instance_rsa_key_pair() -> XResult<bool> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
struct MultipleViewValue {
|
|
||||||
value: Option<String>,
|
|
||||||
value_hex: Option<String>,
|
|
||||||
value_base64: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MultipleViewValue {
|
|
||||||
fn from(v: &[u8]) -> Self {
|
|
||||||
Self {
|
|
||||||
value: Some(String::from_utf8_lossy(v).to_string()),
|
|
||||||
value_hex: Some(hex::encode(v)),
|
|
||||||
value_base64: Some(STANDARD.encode(v)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn to_bytes(&self) -> XResult<Vec<u8>> {
|
|
||||||
if let Some(v) = &self.value {
|
|
||||||
Ok(v.as_bytes().to_vec())
|
|
||||||
} else if let Some(v) = &self.value_hex {
|
|
||||||
let v = opt_result!(hex::decode(v), "Decode hex failed: {}");
|
|
||||||
Ok(v)
|
|
||||||
} else if let Some(v) = &self.value_base64 {
|
|
||||||
let v = opt_result!(STANDARD.decode(v), "Decode base64 failed: {}");
|
|
||||||
Ok(v)
|
|
||||||
} else {
|
|
||||||
simple_error!("Multiple view value is all empty")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
struct DecryptRequest {
|
|
||||||
encrypted_value: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn decrypt(req: Request<Body>) -> Result<Response<Body>> {
|
|
||||||
do_response!(inner_decrypt(req).await)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn inner_decrypt(req: Request<Body>) -> XResult<(StatusCode, Value)> {
|
|
||||||
let whole_body = hyper::body::aggregate(req).await?;
|
|
||||||
let data: DecryptRequest = serde_json::from_reader(whole_body.reader())?;
|
|
||||||
|
|
||||||
debugging!("To be decrypted value: {}", &data.encrypted_value);
|
|
||||||
let key = match get_master_key() {
|
|
||||||
None => return Ok((StatusCode::BAD_REQUEST, json!({ "error": "status_not_ready" }))),
|
|
||||||
Some(key) => key,
|
|
||||||
};
|
|
||||||
let decrypted_value = jose::deserialize_jwe_aes(&data.encrypted_value, &*key.read());
|
|
||||||
drop(key);
|
|
||||||
|
|
||||||
decrypted_value.map(|v| {
|
|
||||||
let v = MultipleViewValue::from(&v.0);
|
|
||||||
let mut map = Map::new();
|
|
||||||
if let Some(v) = &v.value {
|
|
||||||
map.insert("value".to_string(), Value::String(v.to_string()));
|
|
||||||
}
|
|
||||||
if let Some(v) = &v.value_hex {
|
|
||||||
map.insert("value_hex".to_string(), Value::String(v.to_string()));
|
|
||||||
}
|
|
||||||
if let Some(v) = &v.value_base64 {
|
|
||||||
map.insert("value_base64".to_string(), Value::String(v.to_string()));
|
|
||||||
}
|
|
||||||
(StatusCode::OK, Value::Object(map))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn encrypt(req: Request<Body>) -> Result<Response<Body>> {
|
|
||||||
do_response!(inner_encrypt(req).await)
|
|
||||||
}
|
|
||||||
|
|
||||||
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 Ok((StatusCode::BAD_REQUEST, json!({ "error": "status_not_ready" }))),
|
|
||||||
Some(key) => key,
|
|
||||||
};
|
|
||||||
let encrypt_result = jose::serialize_jwe_aes(&value, &*key.read());
|
|
||||||
drop(key);
|
|
||||||
|
|
||||||
encrypt_result.map(|e| {
|
|
||||||
(StatusCode::OK, json!({
|
|
||||||
"encrypted_value": e,
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn update() -> Result<Response<Body>> {
|
async fn update() -> Result<Response<Body>> {
|
||||||
do_response!(inner_update().await)
|
do_response!(inner_update().await)
|
||||||
}
|
}
|
||||||
@@ -252,98 +134,6 @@ async fn inner_update() -> XResult<(StatusCode, Value)> {
|
|||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn init(req: Request<Body>) -> Result<Response<Body>> {
|
|
||||||
do_response!(inner_init(req).await)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
struct InitRequest {
|
|
||||||
clear_master_key_hex: Option<String>,
|
|
||||||
clear_master_key_base64: Option<String>,
|
|
||||||
encrypted_master_key: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn inner_init(req: Request<Body>) -> XResult<(StatusCode, Value)> {
|
|
||||||
let whole_body = hyper::body::aggregate(req).await?;
|
|
||||||
let init_request: InitRequest = serde_json::from_reader(whole_body.reader())?;
|
|
||||||
|
|
||||||
let mut startup_rw_lock = 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 " }))),
|
|
||||||
Some(memory_key) => match memory_key.master_key {
|
|
||||||
Some(_) => return Ok((StatusCode::BAD_REQUEST, json!({ "error": "bad_request", "error_message": "already init " }))),
|
|
||||||
None => {}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
let clear_master_key = if let Some(clear_master_key_base64) = &init_request.clear_master_key_base64 {
|
|
||||||
STANDARD.decode(clear_master_key_base64)?
|
|
||||||
} else if let Some(clear_master_key_hex) = init_request.clear_master_key_hex {
|
|
||||||
hex::decode(clear_master_key_hex)?
|
|
||||||
} else if let Some(encrypted_master_key) = init_request.encrypted_master_key {
|
|
||||||
debugging!("Received encrypted master key: {}", encrypted_master_key);
|
|
||||||
if let Some(k) = &*startup_rw_lock {
|
|
||||||
let (clear_master_key, _) = jose::deserialize_jwe_rsa(&encrypted_master_key, &k.instance_rsa_key_pair.to_jwk_private_key())?;
|
|
||||||
clear_master_key
|
|
||||||
} else {
|
|
||||||
return Ok((StatusCode::INTERNAL_SERVER_ERROR, json!({ "error": "internal_error", "error_message": "not init " })));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Ok((StatusCode::BAD_REQUEST, json!({ "error": "bad_request", "error_message": "master key is not assigned" })));
|
|
||||||
};
|
|
||||||
|
|
||||||
if clear_master_key.len() != 32 {
|
|
||||||
return Ok((StatusCode::BAD_REQUEST, json!({ "error": "bad_request", "error_message": "bad clear_master_key length" })));
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(k) = &mut *startup_rw_lock {
|
|
||||||
let conn = opt_result!(db::open_db(&k.database_file), "Open db failed: {}");
|
|
||||||
let default_master_key_verification_key = db::find_key(&conn, db::DEFAULT_MASTER_KEY_VERIFICATION_KEY)?;
|
|
||||||
match default_master_key_verification_key {
|
|
||||||
None => {
|
|
||||||
let key = Key {
|
|
||||||
name: db::DEFAULT_MASTER_KEY_VERIFICATION_KEY.to_string(),
|
|
||||||
encrypted_key: jose::serialize_jwe_aes("LOCAL-MINI-KMS:MAGIC-VERIFICATION-KEY".as_bytes(), &clear_master_key)?,
|
|
||||||
};
|
|
||||||
db::insert_key(&conn, &key)?;
|
|
||||||
}
|
|
||||||
Some(key) => {
|
|
||||||
debugging!("Found jwe: {}", &key.encrypted_key);
|
|
||||||
let _ = opt_result!(jose::deserialize_jwe_aes(&key.encrypted_key, &clear_master_key), "Deserialize master key verification key failed: {}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
information!("Set master key success");
|
|
||||||
let sec_bytes = SecBytes::with(clear_master_key.len(), |buf| buf.copy_from_slice(&clear_master_key.as_slice()[..]));
|
|
||||||
let mut clear_master_key = clear_master_key;
|
|
||||||
clear_master_key.zeroize();
|
|
||||||
k.master_key = Some(sec_bytes);
|
|
||||||
k.instance_rsa_key_pair = jose::generate_rsa_key(4096)?;
|
|
||||||
}
|
|
||||||
Ok((StatusCode::OK, json!({})))
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn status() -> Result<Response<Body>> {
|
|
||||||
do_response!(inner_status().await)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn inner_status() -> XResult<(StatusCode, Value)> {
|
|
||||||
let startup_rw_lock = STATUP_RW_LOCK.lock().expect("Lock read startup rw lock error");
|
|
||||||
let body = match &*startup_rw_lock {
|
|
||||||
None => json!({ "status": "n/a" }),
|
|
||||||
Some(memory_key) => match memory_key.master_key {
|
|
||||||
None => json!({
|
|
||||||
"status": "not-ready",
|
|
||||||
"instance_public_key_jwk": memory_key.instance_rsa_key_pair.to_jwk_key_pair().to_public_key()?,
|
|
||||||
"instance_public_key_pem": String::from_utf8_lossy(&memory_key.instance_rsa_key_pair.to_pem_public_key()).to_string(),
|
|
||||||
}),
|
|
||||||
Some(_) => json!({
|
|
||||||
"status": "ready",
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ok((StatusCode::OK, body))
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_version() -> Result<Response<Body>> {
|
async fn get_version() -> Result<Response<Body>> {
|
||||||
Ok(Response::builder().body(format!(
|
Ok(Response::builder().body(format!(
|
||||||
"{} - {}\n", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")
|
"{} - {}\n", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")
|
||||||
@@ -367,17 +157,3 @@ Supports commands:
|
|||||||
Ok(Response::builder().body("Root Not Found\n".into())?)
|
Ok(Response::builder().body("Root Not Found\n".into())?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
|
||||||
None => None,
|
|
||||||
Some(k) => match &k.master_key {
|
|
||||||
None => None,
|
|
||||||
Some(k) => {
|
|
||||||
let k = &*k.read();
|
|
||||||
Some(SecBytes::with(k.len(), |buf| buf.copy_from_slice(k)))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
110
src/serve_common.rs
Normal file
110
src/serve_common.rs
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
use base64::Engine;
|
||||||
|
use base64::engine::general_purpose::STANDARD;
|
||||||
|
use josekit::jwk::alg::rsa::RsaKeyPair;
|
||||||
|
use rusqlite::Connection;
|
||||||
|
use rust_util::{opt_result, simple_error, XResult};
|
||||||
|
use seckey::SecBytes;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_json::{Map, Value};
|
||||||
|
|
||||||
|
use crate::db;
|
||||||
|
|
||||||
|
pub type GenericError = Box<dyn std::error::Error + Send + Sync>;
|
||||||
|
pub type Result<T> = std::result::Result<T, GenericError>;
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! do_response {
|
||||||
|
($ex: expr) => (
|
||||||
|
match $ex {
|
||||||
|
Ok((status_code, body)) => Ok(Response::builder().status(status_code).body((serde_json::to_string_pretty(&body)? + "\n").into())?),
|
||||||
|
Err(e) => Ok(Response::builder().status(StatusCode::INTERNAL_SERVER_ERROR).body(
|
||||||
|
format!("{}\n", serde_json::to_string_pretty(&json!({
|
||||||
|
"error": "internal_error",
|
||||||
|
"error_message": format!("{}", e),
|
||||||
|
}))?).into()
|
||||||
|
)?),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MemoryKey {
|
||||||
|
pub database_file: String,
|
||||||
|
pub instance_rsa_key_pair: RsaKeyPair,
|
||||||
|
pub master_key: Option<SecBytes>,
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy_static::lazy_static! {
|
||||||
|
pub static ref STATUP_RW_LOCK: Mutex<Option<MemoryKey>> = Mutex::new(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct MultipleViewValue {
|
||||||
|
pub value: Option<String>,
|
||||||
|
pub value_hex: Option<String>,
|
||||||
|
pub value_base64: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MultipleViewValue {
|
||||||
|
pub fn from(v: &[u8]) -> Self {
|
||||||
|
Self {
|
||||||
|
value: Some(String::from_utf8_lossy(v).to_string()),
|
||||||
|
value_hex: Some(hex::encode(v)),
|
||||||
|
value_base64: Some(STANDARD.encode(v)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_bytes(&self) -> XResult<Vec<u8>> {
|
||||||
|
if let Some(v) = &self.value {
|
||||||
|
Ok(v.as_bytes().to_vec())
|
||||||
|
} else if let Some(v) = &self.value_hex {
|
||||||
|
let v = opt_result!(hex::decode(v), "Decode hex failed: {}");
|
||||||
|
Ok(v)
|
||||||
|
} else if let Some(v) = &self.value_base64 {
|
||||||
|
let v = opt_result!(STANDARD.decode(v), "Decode base64 failed: {}");
|
||||||
|
Ok(v)
|
||||||
|
} else {
|
||||||
|
simple_error!("Multiple view value is all empty")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
None => None,
|
||||||
|
Some(k) => match &k.master_key {
|
||||||
|
None => None,
|
||||||
|
Some(k) => {
|
||||||
|
let k = &*k.read();
|
||||||
|
Some(SecBytes::with(k.len(), |buf| buf.copy_from_slice(k)))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn byte_to_multi_view_map(bytes: &[u8]) -> Map<String, Value> {
|
||||||
|
let v = MultipleViewValue::from(bytes);
|
||||||
|
let mut map = Map::new();
|
||||||
|
if let Some(v) = &v.value {
|
||||||
|
map.insert("value".to_string(), Value::String(v.to_string()));
|
||||||
|
}
|
||||||
|
if let Some(v) = &v.value_hex {
|
||||||
|
map.insert("value_hex".to_string(), Value::String(v.to_string()));
|
||||||
|
}
|
||||||
|
if let Some(v) = &v.value_base64 {
|
||||||
|
map.insert("value_base64".to_string(), Value::String(v.to_string()));
|
||||||
|
}
|
||||||
|
map
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn open_local_db() -> XResult<Connection> {
|
||||||
|
let startup_rw_lock = STATUP_RW_LOCK.lock().expect("Lock read startup rw lock error");
|
||||||
|
match &*startup_rw_lock {
|
||||||
|
None => simple_error!("Db is not initted!"),
|
||||||
|
Some(k) => {
|
||||||
|
Ok(opt_result!(db::open_db(& k.database_file), "Open db failed: {}"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
59
src/serve_encrypt_decrypt.rs
Normal file
59
src/serve_encrypt_decrypt.rs
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
use hyper::{Body, Request, Response, StatusCode};
|
||||||
|
use hyper::body::Buf;
|
||||||
|
use rust_util::{debugging, XResult};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_json::{json, Value};
|
||||||
|
|
||||||
|
use crate::do_response;
|
||||||
|
use crate::jose;
|
||||||
|
use crate::serve_common::{byte_to_multi_view_map, get_master_key, MultipleViewValue};
|
||||||
|
use crate::serve_common::Result;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
struct DecryptRequest {
|
||||||
|
encrypted_value: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn decrypt(req: Request<Body>) -> Result<Response<Body>> {
|
||||||
|
do_response!(inner_decrypt(req).await)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn inner_decrypt(req: Request<Body>) -> XResult<(StatusCode, Value)> {
|
||||||
|
let whole_body = hyper::body::aggregate(req).await?;
|
||||||
|
let data: DecryptRequest = serde_json::from_reader(whole_body.reader())?;
|
||||||
|
|
||||||
|
debugging!("To be decrypted value: {}", &data.encrypted_value);
|
||||||
|
let key = match get_master_key() {
|
||||||
|
None => return Ok((StatusCode::BAD_REQUEST, json!({ "error": "status_not_ready" }))),
|
||||||
|
Some(key) => key,
|
||||||
|
};
|
||||||
|
let decrypted_value = jose::deserialize_jwe_aes(&data.encrypted_value, &*key.read());
|
||||||
|
drop(key);
|
||||||
|
|
||||||
|
decrypted_value.map(|v| {
|
||||||
|
let map = byte_to_multi_view_map(&v.0);
|
||||||
|
(StatusCode::OK, Value::Object(map))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn encrypt(req: Request<Body>) -> Result<Response<Body>> {
|
||||||
|
do_response!(inner_encrypt(req).await)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 Ok((StatusCode::BAD_REQUEST, json!({ "error": "status_not_ready" }))),
|
||||||
|
Some(key) => key,
|
||||||
|
};
|
||||||
|
let encrypt_result = jose::serialize_jwe_aes(&value, &*key.read());
|
||||||
|
drop(key);
|
||||||
|
|
||||||
|
encrypt_result.map(|e| {
|
||||||
|
(StatusCode::OK, json!({
|
||||||
|
"encrypted_value": e,
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
}
|
||||||
85
src/serve_init.rs
Normal file
85
src/serve_init.rs
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
use base64::Engine;
|
||||||
|
use base64::engine::general_purpose::STANDARD;
|
||||||
|
use hyper::{Body, Request, Response, StatusCode};
|
||||||
|
use hyper::body::Buf;
|
||||||
|
use rust_util::{debugging, information, opt_result, XResult};
|
||||||
|
use seckey::SecBytes;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_json::{json, Value};
|
||||||
|
use zeroize::Zeroize;
|
||||||
|
|
||||||
|
use crate::{db, jose};
|
||||||
|
use crate::db::Key;
|
||||||
|
use crate::do_response;
|
||||||
|
use crate::serve_common;
|
||||||
|
use crate::serve_common::Result;
|
||||||
|
|
||||||
|
pub async fn init(req: Request<Body>) -> Result<Response<Body>> {
|
||||||
|
do_response!(inner_init(req).await)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
struct InitRequest {
|
||||||
|
clear_master_key_hex: Option<String>,
|
||||||
|
clear_master_key_base64: Option<String>,
|
||||||
|
encrypted_master_key: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn inner_init(req: Request<Body>) -> XResult<(StatusCode, Value)> {
|
||||||
|
let whole_body = hyper::body::aggregate(req).await?;
|
||||||
|
let init_request: InitRequest = serde_json::from_reader(whole_body.reader())?;
|
||||||
|
|
||||||
|
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 " }))),
|
||||||
|
Some(memory_key) => match memory_key.master_key {
|
||||||
|
Some(_) => return Ok((StatusCode::BAD_REQUEST, json!({ "error": "bad_request", "error_message": "already init " }))),
|
||||||
|
None => {}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
let clear_master_key = if let Some(clear_master_key_base64) = &init_request.clear_master_key_base64 {
|
||||||
|
STANDARD.decode(clear_master_key_base64)?
|
||||||
|
} else if let Some(clear_master_key_hex) = init_request.clear_master_key_hex {
|
||||||
|
hex::decode(clear_master_key_hex)?
|
||||||
|
} else if let Some(encrypted_master_key) = init_request.encrypted_master_key {
|
||||||
|
debugging!("Received encrypted master key: {}", encrypted_master_key);
|
||||||
|
if let Some(k) = &*startup_rw_lock {
|
||||||
|
let (clear_master_key, _) = jose::deserialize_jwe_rsa(&encrypted_master_key, &k.instance_rsa_key_pair.to_jwk_private_key())?;
|
||||||
|
clear_master_key
|
||||||
|
} else {
|
||||||
|
return Ok((StatusCode::INTERNAL_SERVER_ERROR, json!({ "error": "internal_error", "error_message": "not init " })));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Ok((StatusCode::BAD_REQUEST, json!({ "error": "bad_request", "error_message": "master key is not assigned" })));
|
||||||
|
};
|
||||||
|
|
||||||
|
if clear_master_key.len() != 32 {
|
||||||
|
return Ok((StatusCode::BAD_REQUEST, json!({ "error": "bad_request", "error_message": "bad clear_master_key length" })));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(k) = &mut *startup_rw_lock {
|
||||||
|
let conn = opt_result!(db::open_db(&k.database_file), "Open db failed: {}");
|
||||||
|
let default_master_key_verification_key = db::find_key(&conn, db::DEFAULT_MASTER_KEY_VERIFICATION_KEY)?;
|
||||||
|
match default_master_key_verification_key {
|
||||||
|
None => {
|
||||||
|
let key = Key {
|
||||||
|
name: db::DEFAULT_MASTER_KEY_VERIFICATION_KEY.to_string(),
|
||||||
|
encrypted_key: jose::serialize_jwe_aes("LOCAL-MINI-KMS:MAGIC-VERIFICATION-KEY".as_bytes(), &clear_master_key)?,
|
||||||
|
};
|
||||||
|
db::insert_key(&conn, &key)?;
|
||||||
|
}
|
||||||
|
Some(key) => {
|
||||||
|
debugging!("Found jwe: {}", &key.encrypted_key);
|
||||||
|
let _ = opt_result!(jose::deserialize_jwe_aes(&key.encrypted_key, &clear_master_key), "Deserialize master key verification key failed: {}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
information!("Set master key success");
|
||||||
|
let sec_bytes = SecBytes::with(clear_master_key.len(), |buf| buf.copy_from_slice(&clear_master_key.as_slice()[..]));
|
||||||
|
let mut clear_master_key = clear_master_key;
|
||||||
|
clear_master_key.zeroize();
|
||||||
|
k.master_key = Some(sec_bytes);
|
||||||
|
k.instance_rsa_key_pair = jose::generate_rsa_key(4096)?;
|
||||||
|
}
|
||||||
|
Ok((StatusCode::OK, json!({})))
|
||||||
|
}
|
||||||
102
src/serve_read_write.rs
Normal file
102
src/serve_read_write.rs
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
use hyper::{Body, Request, Response, StatusCode};
|
||||||
|
use hyper::body::Buf;
|
||||||
|
use rust_util::{simple_error, 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::{byte_to_multi_view_map, get_master_key, MultipleViewValue, open_local_db};
|
||||||
|
use crate::serve_common::Result;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
struct Named {
|
||||||
|
name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
struct NamedValue {
|
||||||
|
name: String,
|
||||||
|
force_write: Option<bool>,
|
||||||
|
value: MultipleViewValue,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn read(req: Request<Body>) -> Result<Response<Body>> {
|
||||||
|
do_response!(inner_read(req).await)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn inner_read(req: Request<Body>) -> XResult<(StatusCode, Value)> {
|
||||||
|
let whole_body = hyper::body::aggregate(req).await?;
|
||||||
|
let named: Named = serde_json::from_reader(whole_body.reader())?;
|
||||||
|
let name = &named.name;
|
||||||
|
if name.is_empty() {
|
||||||
|
return simple_error!("Key name cannot be empty");
|
||||||
|
}
|
||||||
|
let db_key_name = db::make_db_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 simple_error!("Key '{}' already exists", name),
|
||||||
|
Some(k) => k,
|
||||||
|
};
|
||||||
|
|
||||||
|
let key = match get_master_key() {
|
||||||
|
None => return Ok((StatusCode::BAD_REQUEST, json!({ "error": "status_not_ready" }))),
|
||||||
|
Some(key) => key,
|
||||||
|
};
|
||||||
|
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);
|
||||||
|
map.insert("name".to_string(), Value::String(name.to_string()));
|
||||||
|
Ok((StatusCode::OK, Value::Object(map)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn write(req: Request<Body>) -> Result<Response<Body>> {
|
||||||
|
do_response!(inner_write(req).await)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn inner_write(req: Request<Body>) -> XResult<(StatusCode, Value)> {
|
||||||
|
let whole_body = hyper::body::aggregate(req).await?;
|
||||||
|
let named_value: NamedValue = serde_json::from_reader(whole_body.reader())?;
|
||||||
|
let name = &named_value.name;
|
||||||
|
if name.is_empty() {
|
||||||
|
return simple_error!("Key name cannot be empty");
|
||||||
|
}
|
||||||
|
let db_key_name = db::make_db_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 simple_error!("Key '{}' already exists", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
let value = named_value.value.to_bytes()?;
|
||||||
|
let key = match get_master_key() {
|
||||||
|
None => return Ok((StatusCode::BAD_REQUEST, json!({ "error": "status_not_ready" }))),
|
||||||
|
Some(key) => key,
|
||||||
|
};
|
||||||
|
let encrypt_value = jose::serialize_jwe_aes(&value, &*key.read())?;
|
||||||
|
drop(key);
|
||||||
|
|
||||||
|
let new_db_key = Key {
|
||||||
|
name: db_key_name,
|
||||||
|
encrypted_key: encrypt_value.clone(),
|
||||||
|
};
|
||||||
|
if db_key.is_some() {
|
||||||
|
db::update_key(&conn, &new_db_key)?;
|
||||||
|
} else {
|
||||||
|
db::insert_key(&conn, &new_db_key)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((StatusCode::OK, json!({
|
||||||
|
"name": name.to_string(),
|
||||||
|
"override": db_key.is_some(),
|
||||||
|
"encrypted_value": encrypt_value,
|
||||||
|
})))
|
||||||
|
}
|
||||||
29
src/serve_status.rs
Normal file
29
src/serve_status.rs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
use hyper::{Body, Response, StatusCode};
|
||||||
|
use rust_util::XResult;
|
||||||
|
use serde_json::{json, Value};
|
||||||
|
|
||||||
|
use crate::do_response;
|
||||||
|
use crate::serve_common;
|
||||||
|
use crate::serve_common::Result;
|
||||||
|
|
||||||
|
pub async fn status() -> Result<Response<Body>> {
|
||||||
|
do_response!(inner_status().await)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn inner_status() -> XResult<(StatusCode, Value)> {
|
||||||
|
let startup_rw_lock = serve_common::STATUP_RW_LOCK.lock().expect("Lock read startup rw lock error");
|
||||||
|
let body = match &*startup_rw_lock {
|
||||||
|
None => json!({ "status": "n/a" }),
|
||||||
|
Some(memory_key) => match memory_key.master_key {
|
||||||
|
None => json!({
|
||||||
|
"status": "not-ready",
|
||||||
|
"instance_public_key_jwk": memory_key.instance_rsa_key_pair.to_jwk_key_pair().to_public_key()?,
|
||||||
|
"instance_public_key_pem": String::from_utf8_lossy(&memory_key.instance_rsa_key_pair.to_pem_public_key()).to_string(),
|
||||||
|
}),
|
||||||
|
Some(_) => json!({
|
||||||
|
"status": "ready",
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok((StatusCode::OK, body))
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user