feat: v1.0.9, add init via ssh

This commit is contained in:
Hatter Jiang
2025-06-28 12:12:16 +08:00
parent a8d3f6dadb
commit 88154c9397
10 changed files with 167 additions and 73 deletions

2
Cargo.lock generated
View File

@@ -1039,7 +1039,7 @@ checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
[[package]] [[package]]
name = "local-mini-kms" name = "local-mini-kms"
version = "1.0.8" version = "1.0.9"
dependencies = [ dependencies = [
"aes-gcm-stream", "aes-gcm-stream",
"aes-kw", "aes-kw",

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "local-mini-kms" name = "local-mini-kms"
version = "1.0.8" version = "1.0.9"
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

View File

@@ -38,6 +38,12 @@ Init with Yubikey:
local-mini-kms serve [--init-encrypted-master-key LKMS:*** [--yubikey-challenge *challenge*]] local-mini-kms serve [--init-encrypted-master-key LKMS:*** [--yubikey-challenge *challenge*]]
``` ```
## Local Client init via SSH
```shell
local-mini-kms cli --init --ssh-remote root@example.com [--read-from-pinentry]
```
## Local Client ## Local Client
```shell ```shell

View File

@@ -6,7 +6,7 @@ use clap::{App, Arg, ArgMatches, SubCommand};
use hyper::body::Buf; use hyper::body::Buf;
use hyper::{Body, Client, Method, Request, Response, StatusCode}; use hyper::{Body, Client, Method, Request, Response, StatusCode};
use rust_util::util_clap::{Command, CommandError}; use rust_util::util_clap::{Command, CommandError};
use rust_util::{debugging, opt_result, opt_value_result, simple_error, success, XResult}; use rust_util::{debugging, iff, opt_result, opt_value_result, simple_error, success, XResult};
use serde_json::{json, Map, Value}; use serde_json::{json, Map, Value};
use crate::jose; use crate::jose;
@@ -34,6 +34,8 @@ impl Command for CommandImpl {
.arg(Arg::with_name("yubikey-challenge").long("yubikey-challenge").short("c").takes_value(true).help("Yubikey challenge")) .arg(Arg::with_name("yubikey-challenge").long("yubikey-challenge").short("c").takes_value(true).help("Yubikey challenge"))
.arg(Arg::with_name("comment").long("comment").takes_value(true).help("Comment")) .arg(Arg::with_name("comment").long("comment").takes_value(true).help("Comment"))
.arg(Arg::with_name("force-write").long("force-write").short("F").help("Force write value")) .arg(Arg::with_name("force-write").long("force-write").short("F").help("Force write value"))
.arg(Arg::with_name("read-from-pinentry").long("read-from-pinentry").help("Read from pin-entry"))
.arg(Arg::with_name("ssh-remote").long("ssh-remote").takes_value(true).help("SSH remote, root@example or localhost"))
} }
fn run(&self, arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError { fn run(&self, arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
@@ -104,17 +106,16 @@ async fn do_direct_init(_arg_matches: &ArgMatches<'_>, sub_arg_matches: &ArgMatc
} }
async fn do_init(_arg_matches: &ArgMatches<'_>, sub_arg_matches: &ArgMatches<'_>) -> CommandError { async fn do_init(_arg_matches: &ArgMatches<'_>, sub_arg_matches: &ArgMatches<'_>) -> CommandError {
let ssh_remote = sub_arg_matches.value_of("ssh-remote").map(|s| s.to_string());
let connect = sub_arg_matches.value_of("connect").expect("Get argument listen error"); let connect = sub_arg_matches.value_of("connect").expect("Get argument listen error");
let read_from_pinentry = sub_arg_matches.is_present("read-from-pinentry");
let client = Client::new(); // let client = Client::new();
let uri = format!("http://{}/status", connect); let uri = format!("http://{}/status", connect);
debugging!("Request uri: {}", &uri); debugging!("Request uri: {}", &uri);
let req = Request::builder().method(Method::GET).uri(uri).body(Body::empty())?;
let req_response = client.request(req).await?; let data = send_kms_request_with_ssh_enabled(&ssh_remote, true, &uri, &None).await?;
if req_response.status() != StatusCode::OK {
return simple_error!("Server status is not success: {}", req_response.status().as_u16());
}
let data = response_to_value(req_response).await?;
debugging!("Get status: {}", &data); debugging!("Get status: {}", &data);
let status = &data["status"]; let status = &data["status"];
if let Some(status) = status.as_str() { if let Some(status) = status.as_str() {
@@ -129,25 +130,81 @@ async fn do_init(_arg_matches: &ArgMatches<'_>, sub_arg_matches: &ArgMatches<'_>
let instance_public_key_jwk = &data["instance_public_key_jwk"]; let instance_public_key_jwk = &data["instance_public_key_jwk"];
println!("Instance server public key JWK: {}", instance_public_key_jwk); println!("Instance server public key JWK: {}", instance_public_key_jwk);
let line = read_line("Input encrypted master key: ")?; let line = {
let line = read_line("Input clear(starts with hex: or base64:) or encrypted master key: ", read_from_pinentry)?;
if line.starts_with("hex:") || line.starts_with("base64:") {
let jwk = opt_result!(serde_json::to_string(&instance_public_key_jwk), "Serialize instance server public key JWK: {} failed");
master_key_encrypt(&line, &jwk)?
} else {
line
}
};
let uri = format!("http://{}/init", connect); let uri = format!("http://{}/init", connect);
debugging!("Request uri: {}", &uri); debugging!("Request uri: {}", &uri);
let body = json!({ let body = json!({
"encrypted_master_key": line, "encrypted_master_key": line,
}); });
let body = serde_json::to_string(&body)?; let body = serde_json::to_string(&body)?;
let req = Request::builder().method(Method::POST).uri(uri).body(Body::from(body))?;
let req_response = client.request(req).await?; let _ = send_kms_request_with_ssh_enabled(&ssh_remote, false, &uri, &Some(body)).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);
}
success!("Init finished"); success!("Init finished");
Ok(Some(0)) Ok(Some(0))
} }
async fn send_kms_request_with_ssh_enabled(ssh_remote: &Option<String>, get_request: bool, uri: &str, body: &Option<String>) -> XResult<Value> {
match ssh_remote {
None => {
let client = Client::new();
let method = iff!(get_request, Method::GET, Method::POST);
let request_body = match body {
None => Body::empty(),
Some(body) => Body::from(body.clone()),
};
let req = Request::builder().method(method).uri(uri).body(request_body)?;
let req_response = client.request(req).await?;
if req_response.status() != StatusCode::OK {
return simple_error!("Server status is not success: {}", req_response.status().as_u16());
}
let data = response_to_value(req_response).await?;
Ok(data)
}
Some(ssh_remote) => {
let mut c;
if ssh_remote == "localhost" {
c = std::process::Command::new("curl");
} else {
c = std::process::Command::new("ssh");
c.args([ssh_remote, "curl"]);
}
c.arg("-s");
if !get_request {
c.args(["-X", "POST"]);
}
if let Some(body) = body {
c.args(["-H", "x-body-based64-encoded:1"]);
c.args(["--data-raw", &format!("{}", STANDARD.encode(&body))]);
}
c.arg(uri);
debugging!("Run command: {:?}", c);
let output = opt_result!(c.output(), "Call: {:?} failed: {}", c);
if !output.status.success() {
return simple_error!("Call: {:?} exit with error", output);
}
debugging!("Output: {:?}", output);
let data: Value = serde_json::from_slice(&output.stdout)?;
if let Value::Object(data_map) = &data {
if let Some(error) = data_map.get("error") {
if let Value::String(error) = error {
return simple_error!("Get error: {}, details: {}", error, data);
}
}
}
Ok(data)
}
}
}
async fn do_read(_arg_matches: &ArgMatches<'_>, sub_arg_matches: &ArgMatches<'_>) -> CommandError { async fn do_read(_arg_matches: &ArgMatches<'_>, sub_arg_matches: &ArgMatches<'_>) -> CommandError {
let body = if let Some(name) = sub_arg_matches.value_of("name") { let body = if let Some(name) = sub_arg_matches.value_of("name") {
json!({ "name": name }) json!({ "name": name })
@@ -214,15 +271,26 @@ async fn do_decrypt(_arg_matches: &ArgMatches<'_>, sub_arg_matches: &ArgMatches<
Ok(Some(0)) Ok(Some(0))
} }
fn do_offline_init(_arg_matches: &ArgMatches<'_>, _sub_arg_matches: &ArgMatches<'_>) -> CommandError { fn do_offline_init(_arg_matches: &ArgMatches<'_>, sub_arg_matches: &ArgMatches<'_>) -> CommandError {
let line = read_line("Input master key: ")?; let read_from_pinentry = sub_arg_matches.is_present("read-from-pinentry");
let master_key = if line.starts_with("hex:") {
let hex: String = line.chars().skip(4).collect(); let line = read_line("Input master key: ", read_from_pinentry)?;
let jwk = read_line("Input JWK: ", read_from_pinentry)?;
let encrypted_master_key = master_key_encrypt(&line, &jwk)?;
success!("Encrypted master key: {}", encrypted_master_key);
Ok(Some(0))
}
fn master_key_encrypt(master_key: &str, jwk: &str) -> XResult<String> {
let master_key = if master_key.starts_with("hex:") {
let hex: String = master_key.chars().skip(4).collect();
hex::decode(&hex)? hex::decode(&hex)?
} else if line.starts_with("base64:") { } else if master_key.starts_with("base64:") {
let base64: String = line.chars().skip(7).collect(); let base64: String = master_key.chars().skip(7).collect();
STANDARD.decode(&base64)? STANDARD.decode(&base64)?
} else if line.starts_with("LKMS:") { } else if master_key.starts_with("LKMS:") {
#[cfg(feature = "yubikey")] #[cfg(feature = "yubikey")]
{ {
use crate::yubikey_hmac; use crate::yubikey_hmac;
@@ -230,23 +298,28 @@ fn do_offline_init(_arg_matches: &ArgMatches<'_>, _sub_arg_matches: &ArgMatches<
let challenge = opt_result!( let challenge = opt_result!(
pinentry_util::read_pin(Some("Input yubikey challenge"), Some("Challenge: ")), "Read challenge failed: {}"); pinentry_util::read_pin(Some("Input yubikey challenge"), Some("Challenge: ")), "Read challenge failed: {}");
let derived_key = yubikey_hmac::yubikey_challenge_as_32_bytes(challenge.get_pin().as_bytes())?; let derived_key = yubikey_hmac::yubikey_challenge_as_32_bytes(challenge.get_pin().as_bytes())?;
let (key, _) = jose::deserialize_jwe_aes(&line, &derived_key)?; let (key, _) = jose::deserialize_jwe_aes(&master_key, &derived_key)?;
key key
} }
#[cfg(not(feature = "yubikey"))] #[cfg(not(feature = "yubikey"))]
return simple_error!("Yubikey feature is not enabled."); return simple_error!("Yubikey feature is not enabled.");
} else { } else {
line.as_bytes().to_vec() master_key.as_bytes().to_vec()
}; };
let jwk = read_line("Input JWK: ")?;
let rsa_public_key = jwk_to_rsa_pubic_key(&jwk)?; let rsa_public_key = jwk_to_rsa_pubic_key(&jwk)?;
let encrypted_master_key = jose::serialize_jwe_rsa(&master_key, &rsa_public_key)?; let encrypted_master_key = jose::serialize_jwe_rsa(&master_key, &rsa_public_key)?;
Ok(encrypted_master_key)
success!("Encrypted master key: {}", encrypted_master_key);
Ok(Some(0))
} }
fn read_line(prompt: &str) -> XResult<String> { fn read_line(prompt: &str, pinentry: bool) -> XResult<String> {
if pinentry {
read_line_from_pinentry_util(prompt)
} else {
read_line_from_terminal(prompt)
}
}
fn read_line_from_terminal(prompt: &str) -> XResult<String> {
std::io::stdout().write_all(prompt.as_bytes()).ok(); std::io::stdout().write_all(prompt.as_bytes()).ok();
std::io::stdout().flush().ok(); std::io::stdout().flush().ok();
let mut line = String::new(); let mut line = String::new();
@@ -256,6 +329,11 @@ fn read_line(prompt: &str) -> XResult<String> {
Ok(line.trim().to_string()) Ok(line.trim().to_string())
} }
fn read_line_from_pinentry_util(prompt: &str) -> XResult<String> {
let pin = opt_result!(pinentry_util::read_pin(Some(prompt.to_string()), Some("PIN:".to_string())), "Read from pin-entry failed: {}");
Ok(pin.get_pin().to_string())
}
async fn response_to_value(response: Response<Body>) -> XResult<Value> { async fn response_to_value(response: Response<Body>) -> XResult<Value> {
let req_body = response.into_body(); let req_body = response.into_body();
let whole_body = hyper::body::aggregate(req_body).await?; let whole_body = hyper::body::aggregate(req_body).await?;

View File

@@ -140,8 +140,9 @@ async fn response_requests(
let process = proc::get_process(remote_addr.port()); let process = proc::get_process(remote_addr.port());
match process { match process {
None => log::info!( None => log::info!(
"[{:06}] Receive request: {}, from: {}", "[{:06}] Receive request: {} {}, from: {}",
request_idx, request_idx,
req.method(),
req.uri(), req.uri(),
remote_addr remote_addr
), ),

View File

@@ -1,20 +1,41 @@
use std::io::{Cursor, Read};
use std::sync::Mutex; use std::sync::Mutex;
use base64::engine::general_purpose::STANDARD; use base64::engine::general_purpose::STANDARD;
use base64::Engine; use base64::Engine;
use hyper::StatusCode; use hyper::body::Buf;
use hyper::{Body, Request, StatusCode};
use rsa::RsaPrivateKey; use rsa::RsaPrivateKey;
use rusqlite::Connection; use rusqlite::Connection;
use rust_util::{opt_result, simple_error, XResult}; use rust_util::{opt_result, simple_error, XResult};
use seckey::SecBytes; use seckey::SecBytes;
use serde::{Deserialize, Serialize}; use serde::{de, Deserialize, Serialize};
use serde_json::{json, Map, Value}; use serde_json::{json, Map, Value};
use zeroize::Zeroize;
use crate::db; use crate::db;
pub type GenericError = Box<dyn std::error::Error + Send + Sync>; pub type GenericError = Box<dyn std::error::Error + Send + Sync>;
pub type Result<T> = std::result::Result<T, GenericError>; pub type Result<T> = std::result::Result<T, GenericError>;
pub async fn parse_request<T>(req: Request<Body>) -> XResult<T>
where
T: de::DeserializeOwned,
{
let based64_encoded = req.headers().get("x-body-based64-encoded").is_some();
let whole_body = hyper::body::aggregate(req).await?;
let mut body = Vec::<u8>::new();
whole_body.reader().read_to_end(&mut body)?;
if based64_encoded {
let mut based64_decoded = opt_result!(STANDARD.decode(&body), "Decode request body base64 failed: {}");
body.clear();
body.extend_from_slice(&based64_decoded);
based64_decoded.zeroize();
}
let req_object = serde_json::from_reader(Cursor::new(&body))?;
body.zeroize();
Ok(req_object)
}
#[macro_export] #[macro_export]
macro_rules! do_response { macro_rules! do_response {
($ex: expr) => ( ($ex: expr) => (
@@ -35,17 +56,11 @@ pub fn ok(body: Value) -> XResult<(StatusCode, Value)> {
} }
pub fn client_error(error: &str) -> XResult<(StatusCode, Value)> { pub fn client_error(error: &str) -> XResult<(StatusCode, Value)> {
Ok(( Ok((StatusCode::BAD_REQUEST, json!({ "error": error })))
StatusCode::BAD_REQUEST,
json!({ "error": error })
))
} }
pub fn server_error(error: &str) -> XResult<(StatusCode, Value)> { pub fn server_error(error: &str) -> XResult<(StatusCode, Value)> {
Ok(( Ok((StatusCode::INTERNAL_SERVER_ERROR, json!({ "error": error })))
StatusCode::INTERNAL_SERVER_ERROR,
json!({ "error": error })
))
} }
// pub fn bad_request(error: &str, error_message: &str) -> XResult<(StatusCode, Value)> { // pub fn bad_request(error: &str, error_message: &str) -> XResult<(StatusCode, Value)> {
@@ -104,7 +119,8 @@ impl MultipleViewValue {
} }
} }
#[macro_export] macro_rules! require_master_key { #[macro_export]
macro_rules! require_master_key {
() => { () => {
match $crate::serve_common::get_master_key() { match $crate::serve_common::get_master_key() {
None => return $crate::serve_common::client_error("status_not_ready"), None => return $crate::serve_common::client_error("status_not_ready"),
@@ -114,7 +130,9 @@ impl MultipleViewValue {
} }
pub fn get_master_key() -> Option<SecBytes> { pub fn get_master_key() -> Option<SecBytes> {
let startup_rw_lock = STATUP_RW_LOCK.lock().expect("Lock read startup rw lock error"); let startup_rw_lock = STATUP_RW_LOCK
.lock()
.expect("Lock read startup rw lock error");
match &*startup_rw_lock { match &*startup_rw_lock {
None => None, None => None,
Some(k) => match &k.master_key { Some(k) => match &k.master_key {
@@ -147,11 +165,14 @@ pub fn byte_to_multi_view_map(bytes: &[u8], with_value: bool) -> Map<String, Val
} }
pub fn open_local_db() -> XResult<Connection> { pub fn open_local_db() -> XResult<Connection> {
let startup_rw_lock = STATUP_RW_LOCK.lock().expect("Lock read startup rw lock error"); let startup_rw_lock = STATUP_RW_LOCK
.lock()
.expect("Lock read startup rw lock error");
match &*startup_rw_lock { match &*startup_rw_lock {
None => simple_error!("Db is not initiated!"), None => simple_error!("Db is not initiated!"),
Some(k) => { Some(k) => Ok(opt_result!(
Ok(opt_result!(db::open_db(& k.database_file), "Open db failed: {}")) db::open_db(&k.database_file),
} "Open db failed: {}"
)),
} }
} }

View File

@@ -1,9 +1,8 @@
use crate::db::Key; use crate::db::Key;
use crate::serve_common::{open_local_db, Result}; use crate::serve_common::{open_local_db, parse_request, Result};
use crate::{db, do_response, jose, require_master_key, serve_common}; use crate::{db, do_response, jose, require_master_key, serve_common};
use base64::engine::general_purpose::STANDARD; use base64::engine::general_purpose::STANDARD;
use base64::Engine; use base64::Engine;
use hyper::body::Buf;
use hyper::{Body, Request, Response, StatusCode}; use hyper::{Body, Request, Response, StatusCode};
use rand::random; use rand::random;
use rust_util::{iff, XResult}; use rust_util::{iff, XResult};
@@ -30,8 +29,7 @@ pub async fn generate(req: Request<Body>) -> Result<Response<Body>> {
} }
async fn inner_generate(req: Request<Body>) -> XResult<(StatusCode, Value)> { async fn inner_generate(req: Request<Body>) -> XResult<(StatusCode, Value)> {
let whole_body = hyper::body::aggregate(req).await?; let request: DataKeyRequest = parse_request(req).await?;
let request: DataKeyRequest = serde_json::from_reader(whole_body.reader())?;
log::debug!("Generate data key: {} {}", &request.r#type, &request.spec); log::debug!("Generate data key: {} {}", &request.r#type, &request.spec);
let key = require_master_key!(); let key = require_master_key!();

View File

@@ -1,11 +1,10 @@
use hyper::body::Buf;
use hyper::{Body, Request, Response, StatusCode}; use hyper::{Body, Request, Response, StatusCode};
use rust_util::XResult; use rust_util::XResult;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::{json, Map, Value}; use serde_json::{json, Map, Value};
use crate::jose; use crate::jose;
use crate::serve_common::{self, byte_to_multi_view_map, MultipleViewValue, Result}; use crate::serve_common::{self, byte_to_multi_view_map, parse_request, MultipleViewValue, Result};
use crate::{do_response, require_master_key}; use crate::{do_response, require_master_key};
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
@@ -18,8 +17,7 @@ pub async fn decrypt(req: Request<Body>) -> Result<Response<Body>> {
} }
async fn inner_decrypt(req: Request<Body>) -> XResult<(StatusCode, Value)> { async fn inner_decrypt(req: Request<Body>) -> XResult<(StatusCode, Value)> {
let whole_body = hyper::body::aggregate(req).await?; let data: DecryptRequest = parse_request(req).await?;
let data: DecryptRequest = serde_json::from_reader(whole_body.reader())?;
log::trace!("To be decrypted value: {}", &data.encrypted_value); log::trace!("To be decrypted value: {}", &data.encrypted_value);
let key = require_master_key!(); let key = require_master_key!();
@@ -52,8 +50,7 @@ pub async fn encrypt(req: Request<Body>) -> Result<Response<Body>> {
} }
async fn inner_encrypt(req: Request<Body>) -> XResult<(StatusCode, Value)> { async fn inner_encrypt(req: Request<Body>) -> XResult<(StatusCode, Value)> {
let whole_body = hyper::body::aggregate(req).await?; let data: MultipleViewValue = parse_request(req).await?;
let data: MultipleViewValue = serde_json::from_reader(whole_body.reader())?;
let value = data.to_bytes()?; let value = data.to_bytes()?;
let key = require_master_key!(); let key = require_master_key!();
let encrypt_result = jose::serialize_jwe_aes(&value, &key.read()); let encrypt_result = jose::serialize_jwe_aes(&value, &key.read());

View File

@@ -1,6 +1,5 @@
use base64::engine::general_purpose::STANDARD; use base64::engine::general_purpose::STANDARD;
use base64::Engine; use base64::Engine;
use hyper::body::Buf;
use hyper::{Body, Request, Response, StatusCode}; use hyper::{Body, Request, Response, StatusCode};
use rust_util::{opt_result, XResult}; use rust_util::{opt_result, XResult};
use seckey::SecBytes; use seckey::SecBytes;
@@ -10,7 +9,7 @@ use zeroize::Zeroize;
use crate::db::Key; use crate::db::Key;
use crate::do_response; use crate::do_response;
use crate::serve_common::{self, Result}; use crate::serve_common::{self, parse_request, Result};
#[cfg(feature = "yubikey")] #[cfg(feature = "yubikey")]
use crate::yubikey_hmac; use crate::yubikey_hmac;
use crate::{db, jose}; use crate::{db, jose};
@@ -28,9 +27,7 @@ pub struct InitRequest {
} }
async fn inner_init(req: Request<Body>) -> XResult<(StatusCode, Value)> { async fn inner_init(req: Request<Body>) -> XResult<(StatusCode, Value)> {
let whole_body = hyper::body::aggregate(req).await?; let init_request: InitRequest = parse_request(req).await?;
let init_request: InitRequest = serde_json::from_reader(whole_body.reader())?;
inner_init_request(init_request).await inner_init_request(init_request).await
} }

View File

@@ -1,8 +1,7 @@
use crate::db::Key; use crate::db::Key;
use crate::serve_common::{self, byte_to_multi_view_map, open_local_db, MultipleViewValue, Result}; use crate::serve_common::{self, byte_to_multi_view_map, open_local_db, parse_request, MultipleViewValue, Result};
use crate::{db, jose}; use crate::{db, jose};
use crate::{do_response, require_master_key}; use crate::{do_response, require_master_key};
use hyper::body::Buf;
use hyper::{Body, Request, Response, StatusCode}; use hyper::{Body, Request, Response, StatusCode};
use rust_util::XResult; use rust_util::XResult;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -33,8 +32,7 @@ pub async fn list(req: Request<Body>) -> Result<Response<Body>> {
} }
async fn inner_list(req: Request<Body>) -> XResult<(StatusCode, Value)> { async fn inner_list(req: Request<Body>) -> XResult<(StatusCode, Value)> {
let whole_body = hyper::body::aggregate(req).await?; let keys_query: KeysQuery = parse_request(req).await?;
let keys_query: KeysQuery = serde_json::from_reader(whole_body.reader())?;
let conn = open_local_db()?; let conn = open_local_db()?;
let keys = db::list_keys( let keys = db::list_keys(
@@ -61,8 +59,7 @@ pub async fn read(req: Request<Body>) -> Result<Response<Body>> {
} }
async fn inner_read(req: Request<Body>) -> XResult<(StatusCode, Value)> { async fn inner_read(req: Request<Body>) -> XResult<(StatusCode, Value)> {
let whole_body = hyper::body::aggregate(req).await?; let named: Named = parse_request(req).await?;
let named: Named = serde_json::from_reader(whole_body.reader())?;
let name = &named.name; let name = &named.name;
if name.is_empty() { if name.is_empty() {
return serve_common::client_error("name_is_empty"); return serve_common::client_error("name_is_empty");
@@ -92,8 +89,7 @@ pub async fn write(req: Request<Body>) -> Result<Response<Body>> {
} }
async fn inner_write(req: Request<Body>) -> XResult<(StatusCode, Value)> { async fn inner_write(req: Request<Body>) -> XResult<(StatusCode, Value)> {
let whole_body = hyper::body::aggregate(req).await?; let named_value: NamedValue = parse_request(req).await?;
let named_value: NamedValue = serde_json::from_reader(whole_body.reader())?;
let name = &named_value.name; let name = &named_value.name;
if name.is_empty() { if name.is_empty() {
return serve_common::client_error("name_is_empty"); return serve_common::client_error("name_is_empty");