Compare commits
28 Commits
9d0e7548e6
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
120526f6af
|
|||
|
7c760d26df
|
|||
|
a6a6674f30
|
|||
|
|
dc8616d644
|
||
|
|
9b2705966a
|
||
|
|
88154c9397
|
||
|
a8d3f6dadb
|
|||
|
4c7008ce26
|
|||
|
453e064139
|
|||
|
ffdecf0703
|
|||
|
26daa10c23
|
|||
|
c9ccd35053
|
|||
|
660a9e305d
|
|||
|
4796b53aae
|
|||
|
1cad0ee922
|
|||
|
a4ef732a2b
|
|||
|
aff9359172
|
|||
|
87cba2be57
|
|||
|
20ad9e6bd7
|
|||
|
ca0f18f2bc
|
|||
|
7d9f9f6870
|
|||
|
e2f5bc52a2
|
|||
|
b29374ce1d
|
|||
|
c939490f0e
|
|||
|
5e6694c53e
|
|||
|
909ac90eb9
|
|||
|
655f9f5ede
|
|||
|
82b38a2cf1
|
1162
Cargo.lock
generated
1162
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
22
Cargo.toml
22
Cargo.toml
@@ -1,38 +1,44 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "local-mini-kms"
|
name = "local-mini-kms"
|
||||||
version = "1.0.0"
|
version = "1.0.10"
|
||||||
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
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["yubikey"]
|
default = ["yubikey", "harden_process"]
|
||||||
yubikey = ["yubico_manager"]
|
yubikey = ["yubico_manager"]
|
||||||
|
harden_process = ["secmem-proc", "procfs"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
zeroize = "1.8"
|
zeroize = "1.8"
|
||||||
clap = "2.34"
|
clap = "2.34"
|
||||||
hex = "0.4"
|
hex = "0.4"
|
||||||
base64 = "0.21"
|
base64 = "0.22"
|
||||||
sha2 = "0.10"
|
sha2 = "0.10"
|
||||||
lazy_static = "1.5"
|
lazy_static = "1.5"
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
secmem-proc = "0.3"
|
secmem-proc = { version = "0.3", optional = true }
|
||||||
seckey = "0.12"
|
seckey = "0.12"
|
||||||
rust_util = { version = "0.6", features = ["use_clap"] }
|
rust_util = { version = "0.6", features = ["use_clap"] }
|
||||||
tokio = { version = "1.37", features = ["full"] }
|
tokio = { version = "1.45", features = ["full"] }
|
||||||
hyper = { version = "0.14", features = ["client", "server", "tcp", "http1", "http2"] }
|
hyper = { version = "0.14", features = ["client", "server", "tcp", "http1", "http2"] }
|
||||||
rusqlite = "0.31"
|
# use bundled fix musl build core dump on CentOS 6.9
|
||||||
|
rusqlite = { version = "0.32", features = ["bundled"] }
|
||||||
yubico_manager = { version = "0.9", optional = true }
|
yubico_manager = { version = "0.9", optional = true }
|
||||||
rpassword = "7.3"
|
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
rsa = "0.9"
|
rsa = "0.9"
|
||||||
aes-kw = { version = "0.2", features = ["alloc"] }
|
aes-kw = { version = "0.2", features = ["alloc"] }
|
||||||
sha1 = "0.10"
|
sha1 = "0.10"
|
||||||
aes-gcm-stream = "0.2"
|
aes-gcm-stream = "0.2"
|
||||||
jose-jwk = "0.1"
|
jose-jwk = "0.1"
|
||||||
|
log = "0.4"
|
||||||
|
env_logger = "0.11"
|
||||||
|
log4rs = "1.3"
|
||||||
|
pinentry-util = "0.1.1"
|
||||||
|
zeroizing-alloc = "0.1.0"
|
||||||
|
|
||||||
[target.'cfg(target_os = "linux")'.dependencies]
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
procfs = "0.13"
|
procfs = { version = "0.13", optional = true }
|
||||||
|
|||||||
41
README.md
41
README.md
@@ -11,6 +11,7 @@ cargo build --release [--no-default-features]
|
|||||||
## Init
|
## Init
|
||||||
|
|
||||||
New random master key:
|
New random master key:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
head -c 32 /dev/random | base64
|
head -c 32 /dev/random | base64
|
||||||
```
|
```
|
||||||
@@ -18,6 +19,7 @@ head -c 32 /dev/random | base64
|
|||||||
## Generate Yubikey encrypted master key
|
## Generate Yubikey encrypted master key
|
||||||
|
|
||||||
Generate encrypted master key with Yubikey:
|
Generate encrypted master key with Yubikey:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
local-mini-kms yubikey-init-master-key --generate-key [--yubikey-challenge *challenge*]
|
local-mini-kms yubikey-init-master-key --generate-key [--yubikey-challenge *challenge*]
|
||||||
```
|
```
|
||||||
@@ -25,15 +27,23 @@ local-mini-kms yubikey-init-master-key --generate-key [--yubikey-challenge *chal
|
|||||||
## Startup Server
|
## Startup Server
|
||||||
|
|
||||||
Startup without init:
|
Startup without init:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
local-mini-kms serve
|
local-mini-kms serve
|
||||||
```
|
```
|
||||||
|
|
||||||
Init with Yubikey:
|
Init with Yubikey:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
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
|
||||||
@@ -67,6 +77,7 @@ local-mini-kms cli --write --name test --value hello [--force-write] [--comment
|
|||||||
## cURL
|
## cURL
|
||||||
|
|
||||||
Write value:
|
Write value:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
curl -X POST http://127.0.0.1:5567/write \
|
curl -X POST http://127.0.0.1:5567/write \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
@@ -74,6 +85,7 @@ curl -X POST http://127.0.0.1:5567/write \
|
|||||||
```
|
```
|
||||||
|
|
||||||
Read value:
|
Read value:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
curl -X POST http://127.0.0.1:5567/read \
|
curl -X POST http://127.0.0.1:5567/read \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
@@ -81,14 +93,39 @@ curl -X POST http://127.0.0.1:5567/read \
|
|||||||
```
|
```
|
||||||
|
|
||||||
Generate data key:
|
Generate data key:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
curl -X POST http://127.0.0.1:5567/datakey \
|
curl -X POST http://127.0.0.1:5567/datakey \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{"key_type":"aes", "key_spec":"256", "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'
|
||||||
|
```
|
||||||
|
|
||||||
|
```shell
|
||||||
|
xh POST http://127.0.0.1:5567/list type=value name=name limit:=10
|
||||||
|
```
|
||||||
|
|
||||||
|
| Key | Comment |
|
||||||
|
|------------------|------------------------------------------------------|
|
||||||
|
| type | `aes` |
|
||||||
|
| spec | ~~`128`, `192`,~~ `256` if `type` == `aes` |
|
||||||
|
| exportable | <i>[optional]</i> `true` or `false` , default `true` |
|
||||||
|
| return_plaintext | <i>[optional]</i> `true` or `false`, default `false` |
|
||||||
|
| name | <i>[optional]</i> Data key name |
|
||||||
|
| comment | <i>[optional]</i> Data key comment |
|
||||||
|
|
||||||
Upgrade to v3.2
|
Upgrade to v3.2
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
ALTER TABLE keys ADD COLUMN comment TEXT;
|
ALTER TABLE keys
|
||||||
|
ADD COLUMN comment TEXT;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
7
justfile
7
justfile
@@ -1,6 +1,10 @@
|
|||||||
_:
|
_:
|
||||||
@just --list
|
@just --list
|
||||||
|
|
||||||
|
# publish
|
||||||
|
publish:
|
||||||
|
cargo publish --registry crates-io
|
||||||
|
|
||||||
check:
|
check:
|
||||||
cargo check
|
cargo check
|
||||||
|
|
||||||
@@ -10,3 +14,6 @@ lint:
|
|||||||
build:
|
build:
|
||||||
cargo build --release
|
cargo build --release
|
||||||
|
|
||||||
|
build-linux-musl-with-zig:
|
||||||
|
cargo zigbuild --release --target x86_64-unknown-linux-musl --no-default-features
|
||||||
|
|
||||||
|
|||||||
204
src/cli.rs
204
src/cli.rs
@@ -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_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 {
|
||||||
@@ -76,7 +78,24 @@ async fn do_direct_init(_arg_matches: &ArgMatches<'_>, sub_arg_matches: &ArgMatc
|
|||||||
} else if let Some(value_base64) = value_base64 {
|
} else if let Some(value_base64) = value_base64 {
|
||||||
body_map.insert("clear_master_key_base64".to_string(), value_base64.into());
|
body_map.insert("clear_master_key_base64".to_string(), value_base64.into());
|
||||||
} else {
|
} else {
|
||||||
return simple_error!("Requires value hex or value base64");
|
let pin = match pinentry_util::read_pin(
|
||||||
|
Some("Input your clear master key, starts with hex: or base64:"),
|
||||||
|
Some("Clear master key: ")) {
|
||||||
|
Ok(pin) => pin,
|
||||||
|
Err(e) => return simple_error!("Read clear master key failed: {}", e),
|
||||||
|
};
|
||||||
|
let pin_str = pin.get_pin();
|
||||||
|
let clear_master_key = if pin_str.starts_with("hex:") {
|
||||||
|
let hex: String = pin_str.chars().skip(4).collect();
|
||||||
|
hex::decode(&hex)?
|
||||||
|
} else if pin_str.starts_with("base64:") {
|
||||||
|
let base64: String = pin_str.chars().skip(7).collect();
|
||||||
|
STANDARD.decode(&base64)?
|
||||||
|
} else {
|
||||||
|
return simple_error!("Clear master key must starts with hex: or base64:");
|
||||||
|
};
|
||||||
|
|
||||||
|
body_map.insert("clear_master_key_hex".to_string(), hex::encode(&clear_master_key).into());
|
||||||
}
|
}
|
||||||
if let Some(yubikey_challenge) = yubikey_challenge {
|
if let Some(yubikey_challenge) = yubikey_challenge {
|
||||||
body_map.insert("yubikey_challenge".to_string(), yubikey_challenge.into());
|
body_map.insert("yubikey_challenge".to_string(), yubikey_challenge.into());
|
||||||
@@ -87,17 +106,15 @@ 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 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() {
|
||||||
@@ -112,25 +129,98 @@ 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)?;
|
||||||
|
let line = iff!(line.starts_with("hmac_enc:"), card_hmac_decrypt(&line)?, line);
|
||||||
|
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))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn card_hmac_decrypt(ciphertext: &str) -> XResult<String> {
|
||||||
|
let mut c = std::process::Command::new("card-cli");
|
||||||
|
c.args(&["hmac-decrypt", "--ciphertext", &ciphertext, "--json"]);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
let data: Value = serde_json::from_slice(&output.stdout)?;
|
||||||
|
if let Value::Object(data_map) = &data {
|
||||||
|
if let Some(Value::String(plaintext)) = data_map.get("plaintext") {
|
||||||
|
return Ok(plaintext.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
simple_error!("Hmac decrypt without plaintext, data: {:?}", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
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", &STANDARD.encode(body).to_string()]);
|
||||||
|
}
|
||||||
|
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(Value::String(error)) = data_map.get("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 })
|
||||||
@@ -164,7 +254,7 @@ async fn do_write(_arg_matches: &ArgMatches<'_>, sub_arg_matches: &ArgMatches<'_
|
|||||||
return simple_error!("Require one of value, value-hex, value-base64");
|
return simple_error!("Require one of value, value-hex, value-base64");
|
||||||
};
|
};
|
||||||
let data = do_inner_request(sub_arg_matches, "write", &body).await?;
|
let data = do_inner_request(sub_arg_matches, "write", &body).await?;
|
||||||
success!("Value: {}", serde_json::to_string_pretty(&data)?);
|
println!("{}", serde_json::to_string_pretty(&data)?);
|
||||||
Ok(Some(0))
|
Ok(Some(0))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,7 +273,7 @@ async fn do_encrypt(_arg_matches: &ArgMatches<'_>, sub_arg_matches: &ArgMatches<
|
|||||||
return simple_error!("Require one of value, value-hex, value-base64");
|
return simple_error!("Require one of value, value-hex, value-base64");
|
||||||
};
|
};
|
||||||
let data = do_inner_request(sub_arg_matches, "encrypt", &body).await?;
|
let data = do_inner_request(sub_arg_matches, "encrypt", &body).await?;
|
||||||
success!("Value: {}", serde_json::to_string_pretty(&data)?);
|
println!("{}", serde_json::to_string_pretty(&data)?);
|
||||||
Ok(Some(0))
|
Ok(Some(0))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,30 +283,59 @@ async fn do_decrypt(_arg_matches: &ArgMatches<'_>, sub_arg_matches: &ArgMatches<
|
|||||||
let body = json!({ "encrypted_value": value });
|
let body = json!({ "encrypted_value": value });
|
||||||
|
|
||||||
let data = do_inner_request(sub_arg_matches, "decrypt", &body).await?;
|
let data = do_inner_request(sub_arg_matches, "decrypt", &body).await?;
|
||||||
success!("Value: {}", serde_json::to_string_pretty(&data)?);
|
println!("{}", serde_json::to_string_pretty(&data)?);
|
||||||
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)?;
|
||||||
hex::decode(&hex)?
|
let jwk = read_line("Input JWK: ", read_from_pinentry)?;
|
||||||
} else if line.starts_with("base64:") {
|
|
||||||
let base64: String = line.chars().skip(7).collect();
|
let encrypted_master_key = master_key_encrypt(&line, &jwk)?;
|
||||||
STANDARD.decode(&base64)?
|
|
||||||
} else {
|
|
||||||
line.as_bytes().to_vec()
|
|
||||||
};
|
|
||||||
let jwk = read_line("Input JWK: ")?;
|
|
||||||
let rsa_public_key = jwk_to_rsa_pubic_key(&jwk)?;
|
|
||||||
let encrypted_master_key = jose::serialize_jwe_rsa(&master_key, &rsa_public_key)?;
|
|
||||||
|
|
||||||
success!("Encrypted master key: {}", encrypted_master_key);
|
success!("Encrypted master key: {}", encrypted_master_key);
|
||||||
Ok(Some(0))
|
Ok(Some(0))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_line(prompt: &str) -> XResult<String> {
|
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)?
|
||||||
|
} else if master_key.starts_with("base64:") {
|
||||||
|
let base64: String = master_key.chars().skip(7).collect();
|
||||||
|
STANDARD.decode(&base64)?
|
||||||
|
} else if master_key.starts_with("LKMS:") {
|
||||||
|
#[cfg(feature = "yubikey")]
|
||||||
|
{
|
||||||
|
use crate::yubikey_hmac;
|
||||||
|
// Yubikey Hmac encrypted key
|
||||||
|
let challenge = opt_result!(
|
||||||
|
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 (key, _) = jose::deserialize_jwe_aes(master_key, &derived_key)?;
|
||||||
|
key
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "yubikey"))]
|
||||||
|
return simple_error!("Yubikey feature is not enabled.");
|
||||||
|
} else {
|
||||||
|
master_key.as_bytes().to_vec()
|
||||||
|
};
|
||||||
|
let rsa_public_key = jwk_to_rsa_pubic_key(jwk)?;
|
||||||
|
let encrypted_master_key = jose::serialize_jwe_rsa(&master_key, &rsa_public_key)?;
|
||||||
|
Ok(encrypted_master_key)
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
@@ -226,6 +345,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?;
|
||||||
@@ -241,10 +365,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 = Request::builder().method(Method::POST).uri(uri).body(Body::from(body))?;
|
||||||
|
|
||||||
let req_response = client.request(req).await?;
|
let req_response = client.request(req).await?;
|
||||||
if req_response.status() != StatusCode::OK {
|
// if req_response.status() != StatusCode::OK {
|
||||||
let status = req_response.status().as_u16();
|
// let status = req_response.status().as_u16();
|
||||||
let data = response_to_value(req_response).await?;
|
// let data = response_to_value(req_response).await?;
|
||||||
return simple_error!("Server status is not success: {}, response: {}", status, data);
|
// return simple_error!("Server status is not success: {}, response: {}", status, data);
|
||||||
}
|
// }
|
||||||
response_to_value(req_response).await
|
response_to_value(req_response).await
|
||||||
}
|
}
|
||||||
|
|||||||
148
src/db.rs
148
src/db.rs
@@ -1,5 +1,5 @@
|
|||||||
use rusqlite::{Connection, params};
|
use rusqlite::{params, Connection};
|
||||||
use rust_util::{debugging, information, opt_result, simple_error, success, XResult};
|
use rust_util::{opt_result, simple_error, XResult};
|
||||||
|
|
||||||
pub const DEFAULT_MASTER_KEY_VERIFICATION_KEY: &str = "__master_verification_key";
|
pub const DEFAULT_MASTER_KEY_VERIFICATION_KEY: &str = "__master_verification_key";
|
||||||
|
|
||||||
@@ -9,47 +9,111 @@ pub struct Key {
|
|||||||
pub comment: Option<String>,
|
pub comment: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn make_db_key_name(name: &str) -> String {
|
pub struct Keys {
|
||||||
|
pub count: usize,
|
||||||
|
pub keys: Vec<Key>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn make_value_key_name(name: &str) -> String {
|
||||||
format!("value:{}", name)
|
format!("value:{}", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn make_data_key_name(name: &str) -> String {
|
||||||
|
format!("data_key:{}", 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());
|
log::debug!("Db auto commit: {}", con.is_autocommit());
|
||||||
Ok(con)
|
Ok(con)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init_db(conn: &Connection) -> XResult<bool> {
|
pub fn init_db(conn: &Connection) -> XResult<bool> {
|
||||||
let mut stmt = conn.prepare(
|
if let Ok(false) = check_table_keys(conn) {
|
||||||
"SELECT name FROM sqlite_master WHERE type='table' AND name='keys'")?;
|
repair_table_keys(conn)?;
|
||||||
let mut rows = stmt.query(())?;
|
|
||||||
if rows.next()?.is_some() {
|
|
||||||
information!("Table keys exists, skip init");
|
|
||||||
return Ok(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = conn.execute(r##"
|
Ok(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn repair_table_keys(conn: &Connection) -> XResult<()> {
|
||||||
|
let field_names = list_table_fields(conn, "keys")?;
|
||||||
|
let field_names = field_names.iter().map(|n| n.as_str()).collect::<Vec<_>>();
|
||||||
|
|
||||||
|
if !field_names.contains(&"comment") {
|
||||||
|
log::info!("Repair table keys, add column comment");
|
||||||
|
let _ = conn.execute("ALTER TABLE keys ADD COLUMN comment TEXT", ())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn list_table_fields(conn: &Connection, table: &str) -> XResult<Vec<String>> {
|
||||||
|
let mut stmt_query_fields = conn.prepare(&format!("pragma table_info({})", table))?;
|
||||||
|
let mut rows_query_field = stmt_query_fields.query(())?;
|
||||||
|
|
||||||
|
let mut field_names = vec![];
|
||||||
|
let mut next_query_field_opt = rows_query_field.next()?;
|
||||||
|
while let Some(next_query_field) = next_query_field_opt {
|
||||||
|
// cid|name|type|notnull|dflt_value|pk
|
||||||
|
// ^ ^ ^ ^ ^ ^
|
||||||
|
// | | | | | [5] - Is column PK
|
||||||
|
// | | | | [4] Column default value
|
||||||
|
// | | | [3] Is column not null
|
||||||
|
// | | [2] Column type
|
||||||
|
// | [1] Column name
|
||||||
|
// [0] Column index
|
||||||
|
let field_name: String = next_query_field.get(1)?;
|
||||||
|
field_names.push(field_name.to_lowercase());
|
||||||
|
next_query_field_opt = rows_query_field.next()?;
|
||||||
|
}
|
||||||
|
log::trace!("Table {} fields: {:?}", table, field_names);
|
||||||
|
Ok(field_names)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_table_keys(conn: &Connection) -> XResult<bool> {
|
||||||
|
let mut stmt =
|
||||||
|
conn.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='keys'")?;
|
||||||
|
let mut rows = stmt.query(())?;
|
||||||
|
if rows.next()?.is_none() {
|
||||||
|
log::info!("Create table keys");
|
||||||
|
create_table_keys(conn)?;
|
||||||
|
Ok(true)
|
||||||
|
} else {
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_table_keys(conn: &Connection) -> XResult<()> {
|
||||||
|
let _ = conn.execute(
|
||||||
|
r##"
|
||||||
CREATE TABLE keys (
|
CREATE TABLE keys (
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
value TEXT,
|
value TEXT,
|
||||||
comment TEXT
|
comment TEXT
|
||||||
)
|
)"##,
|
||||||
"##, ())?;
|
(),
|
||||||
success!("Table keys created");
|
)?;
|
||||||
Ok(true)
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert_key(conn: &Connection, key: &Key) -> XResult<()> {
|
pub fn insert_key(conn: &Connection, key: &Key) -> XResult<()> {
|
||||||
let default_comment = "".to_string();
|
let default_comment = "".to_string();
|
||||||
|
log::debug!("insert key name={}", &key.name);
|
||||||
let _ = conn.execute(
|
let _ = conn.execute(
|
||||||
"INSERT INTO keys (name, value, comment) VALUES (?1, ?2, ?3)",
|
"INSERT INTO keys (name, value, comment) VALUES (?1, ?2, ?3)",
|
||||||
(&key.name, &key.encrypted_key, key.comment.as_ref().unwrap_or(&default_comment)),
|
(
|
||||||
|
&key.name,
|
||||||
|
&key.encrypted_key,
|
||||||
|
key.comment.as_ref().unwrap_or(&default_comment),
|
||||||
|
),
|
||||||
)?;
|
)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_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 {
|
if let Some(comment) = &key.comment {
|
||||||
let _ = conn.execute(
|
let _ = conn.execute(
|
||||||
"UPDATE keys SET value = ?1, comment = ?2 WHERE name = ?3",
|
"UPDATE keys SET value = ?1, comment = ?2 WHERE name = ?3",
|
||||||
@@ -64,6 +128,48 @@ pub fn update_key(conn: &Connection, key: &Key) -> XResult<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn list_keys(conn: &Connection, ty: &str, search: &str, limit: usize) -> XResult<Keys> {
|
||||||
|
let name = format!("{}:%{}%", ty, search);
|
||||||
|
let mut count_stmt = conn.prepare("SELECT count(*) FROM keys WHERE name like ?1")?;
|
||||||
|
let mut count_iter = count_stmt.query_map(params![name], |row| {
|
||||||
|
let count: usize = row.get(0)?;
|
||||||
|
Ok(count)
|
||||||
|
})?;
|
||||||
|
let count = match count_iter.next() {
|
||||||
|
None => 0,
|
||||||
|
Some(Ok(count)) => count,
|
||||||
|
Some(Err(e)) => return simple_error!("List keys failed: {}", e),
|
||||||
|
};
|
||||||
|
log::debug!("found {} keys via: {}, limit: {}", count, name, limit);
|
||||||
|
let mut keys = vec![];
|
||||||
|
if count > 0 {
|
||||||
|
let mut list_stmt =
|
||||||
|
conn.prepare("SELECT id, name, value, comment FROM keys WHERE name like ?1 LIMIT ?2")?;
|
||||||
|
let mut list_iter = list_stmt.query_map(params![name, limit], |row| {
|
||||||
|
Ok(Key {
|
||||||
|
name: row.get(1)?,
|
||||||
|
encrypted_key: row.get(2)?,
|
||||||
|
comment: row.get(3)?,
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match list_iter.next() {
|
||||||
|
None => {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Some(Ok(r)) => {
|
||||||
|
log::debug!("found key name={}", r.name);
|
||||||
|
keys.push(r);
|
||||||
|
}
|
||||||
|
Some(Err(e)) => return simple_error!("List keys failed: {}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Keys { count, keys })
|
||||||
|
}
|
||||||
|
|
||||||
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, comment FROM keys WHERE name = ?1")?;
|
let mut stmt = conn.prepare("SELECT id, name, value, comment 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| {
|
||||||
@@ -74,8 +180,14 @@ pub fn find_key(conn: &Connection, name: &str) -> XResult<Option<Key>> {
|
|||||||
})
|
})
|
||||||
})?;
|
})?;
|
||||||
match key_iter.next() {
|
match key_iter.next() {
|
||||||
None => Ok(None),
|
None => {
|
||||||
Some(Ok(r)) => Ok(Some(r)),
|
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),
|
Some(Err(e)) => simple_error!("Find key failed: {}", e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
169
src/jose.rs
169
src/jose.rs
@@ -7,7 +7,7 @@ use rand::{random, thread_rng};
|
|||||||
use rsa::pkcs1::LineEnding;
|
use rsa::pkcs1::LineEnding;
|
||||||
use rsa::pkcs8::EncodePublicKey;
|
use rsa::pkcs8::EncodePublicKey;
|
||||||
use rsa::{Oaep, RsaPrivateKey, RsaPublicKey};
|
use rsa::{Oaep, RsaPrivateKey, RsaPublicKey};
|
||||||
use rust_util::{opt_result, simple_error, XResult};
|
use rust_util::{iff, opt_result, simple_error, XResult};
|
||||||
use serde_derive::{Deserialize, Serialize};
|
use serde_derive::{Deserialize, Serialize};
|
||||||
use sha1::Sha1;
|
use sha1::Sha1;
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
@@ -18,12 +18,17 @@ const JWE_ALG_A256KW: &str = "A256KW";
|
|||||||
const JWE_ALG_RSA_OAEP: &str = "RSA-OAEP";
|
const JWE_ALG_RSA_OAEP: &str = "RSA-OAEP";
|
||||||
const JWE_DOT: &str = ".";
|
const JWE_DOT: &str = ".";
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Default, Debug, Serialize, Deserialize)]
|
||||||
pub struct JweHeader {
|
pub struct JweHeader {
|
||||||
pub enc: String,
|
pub enc: String,
|
||||||
pub alg: String,
|
pub alg: String,
|
||||||
pub vendor: String,
|
pub vendor: String,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub version: Option<String>,
|
pub version: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub data_type: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub exportable: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_rsa_key(bits: u32) -> XResult<RsaPrivateKey> {
|
pub fn generate_rsa_key(bits: u32) -> XResult<RsaPrivateKey> {
|
||||||
@@ -45,7 +50,7 @@ pub fn rsa_key_to_pem(rsa_private_key: &RsaPrivateKey) -> XResult<String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn jwk_to_rsa_pubic_key(rsa_jwk: &str) -> XResult<RsaPublicKey> {
|
pub fn jwk_to_rsa_pubic_key(rsa_jwk: &str) -> XResult<RsaPublicKey> {
|
||||||
let rsa: Rsa = opt_result!(serde_json::from_str(&rsa_jwk), "Bad RSA JWK: {}, error: {}", rsa_jwk);
|
let rsa: Rsa = opt_result!(serde_json::from_str(rsa_jwk), "Bad RSA JWK: {}, error: {}", rsa_jwk);
|
||||||
let rsa_public_key = opt_result!(RsaPublicKey::try_from(rsa), "Bad RSA JWK: {}, error: {:?}", rsa_jwk);
|
let rsa_public_key = opt_result!(RsaPublicKey::try_from(rsa), "Bad RSA JWK: {}, error: {:?}", rsa_jwk);
|
||||||
Ok(rsa_public_key)
|
Ok(rsa_public_key)
|
||||||
}
|
}
|
||||||
@@ -55,7 +60,7 @@ pub fn serialize_jwe_rsa(payload: &[u8], rsa_public_key: &RsaPublicKey) -> XResu
|
|||||||
enc: JWE_ENC_A256GCM.to_string(),
|
enc: JWE_ENC_A256GCM.to_string(),
|
||||||
alg: JWE_ALG_RSA_OAEP.to_string(),
|
alg: JWE_ALG_RSA_OAEP.to_string(),
|
||||||
vendor: "local-mini-kms".to_string(),
|
vendor: "local-mini-kms".to_string(),
|
||||||
version: None,
|
..Default::default()
|
||||||
};
|
};
|
||||||
serialize_jwe_fn(&header, payload, |data_key| -> XResult<Vec<u8>> {
|
serialize_jwe_fn(&header, payload, |data_key| -> XResult<Vec<u8>> {
|
||||||
let mut r = thread_rng();
|
let mut r = thread_rng();
|
||||||
@@ -68,24 +73,30 @@ pub fn deserialize_jwe_rsa(jwe: &str, rsa: &RsaPrivateKey) -> XResult<(Vec<u8>,
|
|||||||
if alg != JWE_ALG_RSA_OAEP {
|
if alg != JWE_ALG_RSA_OAEP {
|
||||||
return simple_error!("Invalid JWE header alg: {}", alg);
|
return simple_error!("Invalid JWE header alg: {}", alg);
|
||||||
}
|
}
|
||||||
Ok(opt_result!(rsa.decrypt(Oaep::new::<Sha1>(), &key_wrap), "Unwrap key failed: {}"))
|
Ok(opt_result!(rsa.decrypt(Oaep::new::<Sha1>(), key_wrap), "Unwrap key failed: {}"))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn serialize_jwe_aes(payload: &[u8], key: &[u8]) -> XResult<String> {
|
pub fn serialize_jwe_aes(payload: &[u8], key: &[u8]) -> XResult<String> {
|
||||||
serialize_jwe_aes_32(payload, to_bytes32(key)?)
|
serialize_jwe_aes_32(None, None, payload, to_bytes32(key)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn serialize_jwe_aes_32(payload: &[u8], key: [u8; 32]) -> XResult<String> {
|
pub fn serialize_jwe_aes_with_data_type(data_type: &str, exportable: bool, payload: &[u8], key: &[u8]) -> XResult<String> {
|
||||||
|
serialize_jwe_aes_32(Some(data_type.to_string()), iff!(exportable, None, Some(false)), payload, to_bytes32(key)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn serialize_jwe_aes_32(data_type: Option<String>, exportable: Option<bool>, payload: &[u8], key: [u8; 32]) -> XResult<String> {
|
||||||
let header = JweHeader {
|
let header = JweHeader {
|
||||||
enc: JWE_ENC_A256GCM.to_string(),
|
enc: JWE_ENC_A256GCM.to_string(),
|
||||||
alg: JWE_ALG_A256KW.to_string(),
|
alg: JWE_ALG_A256KW.to_string(),
|
||||||
vendor: "local-mini-kms".to_string(),
|
vendor: "local-mini-kms".to_string(),
|
||||||
version: Some(get_master_key_checksum(&key)),
|
version: Some(get_master_key_checksum(&key)),
|
||||||
|
data_type,
|
||||||
|
exportable,
|
||||||
};
|
};
|
||||||
serialize_jwe_fn(&header, payload, |data_key| -> XResult<Vec<u8>> {
|
serialize_jwe_fn(&header, payload, |data_key| -> XResult<Vec<u8>> {
|
||||||
let kek = Kek::from(key);
|
let kek = Kek::from(key);
|
||||||
Ok(opt_result!(kek.wrap_vec(&data_key[..]), "Wrap key failed: {}"))
|
Ok(opt_result!(kek.wrap_vec(data_key), "Wrap key failed: {}"))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,7 +110,7 @@ pub fn deserialize_jwe_aes_32(jwe: &str, key: [u8; 32]) -> XResult<(Vec<u8>, Jwe
|
|||||||
return simple_error!("Invalid JWE header alg: {}", alg);
|
return simple_error!("Invalid JWE header alg: {}", alg);
|
||||||
}
|
}
|
||||||
let kek = Kek::from(key);
|
let kek = Kek::from(key);
|
||||||
Ok(opt_result!(kek.unwrap_vec(&key_wrap), "Unwrap key failed: {}"))
|
Ok(opt_result!(kek.unwrap_vec(key_wrap), "Unwrap key failed: {}"))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,7 +136,7 @@ where
|
|||||||
LOCAL_KMS_PREFIX,
|
LOCAL_KMS_PREFIX,
|
||||||
header_b64,
|
header_b64,
|
||||||
URL_SAFE_NO_PAD.encode(&cek),
|
URL_SAFE_NO_PAD.encode(&cek),
|
||||||
URL_SAFE_NO_PAD.encode(&iv),
|
URL_SAFE_NO_PAD.encode(iv),
|
||||||
URL_SAFE_NO_PAD.encode(&ciphertext),
|
URL_SAFE_NO_PAD.encode(&ciphertext),
|
||||||
URL_SAFE_NO_PAD.encode(&tag)
|
URL_SAFE_NO_PAD.encode(&tag)
|
||||||
))
|
))
|
||||||
@@ -175,9 +186,7 @@ fn to_bytes32(bytes: &[u8]) -> XResult<[u8; 32]> {
|
|||||||
return simple_error!("Not valid 32 bytes");
|
return simple_error!("Not valid 32 bytes");
|
||||||
}
|
}
|
||||||
let mut ret = [0; 32];
|
let mut ret = [0; 32];
|
||||||
for i in 0..32 {
|
ret.copy_from_slice(&bytes[..32]);
|
||||||
ret[i] = bytes[i];
|
|
||||||
}
|
|
||||||
Ok(ret)
|
Ok(ret)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,3 +207,137 @@ fn get_jwe(jwe: &str) -> String {
|
|||||||
jwe.to_string()
|
jwe.to_string()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_jwe_rsa_01() {
|
||||||
|
let rsa_private_key = RsaPrivateKey::new(&mut thread_rng(), 2048).unwrap();
|
||||||
|
let jwe = serialize_jwe_rsa(b"hello world", &rsa_private_key.to_public_key()).unwrap();
|
||||||
|
let plaintext = deserialize_jwe_rsa(&jwe, &rsa_private_key).unwrap();
|
||||||
|
assert_eq!("hello world", String::from_utf8(plaintext.0).unwrap());
|
||||||
|
assert_eq!("A256GCM", plaintext.1.enc);
|
||||||
|
assert_eq!("RSA-OAEP", plaintext.1.alg);
|
||||||
|
assert_eq!("local-mini-kms", plaintext.1.vendor);
|
||||||
|
assert!(plaintext.1.version.is_none());
|
||||||
|
assert!(plaintext.1.data_type.is_none());
|
||||||
|
assert!(plaintext.1.exportable.is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_jwe_rsa_02() {
|
||||||
|
let test_private_key_pem = "-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCxytLReN50hqYH
|
||||||
|
a5Db87E9QEIg52Q+H0PGSC7ZLmBSeITLUX3fm5QOtUNzGR2cNYiS9XM4G0vjvE6u
|
||||||
|
d3k4xd/YU43fuD+bLGIxXItm0uXF7hDkdgX/bGrbuuyf1aD6I8RI/vb+n3pB0TL6
|
||||||
|
q1aUlxj7lHeUdYYgAl7qsbPvfXkSEWZ712/D6oQzvEZHq7sEtPiHcqckkqlwyP5X
|
||||||
|
0Uu7INm5c9BzYNF7Ni0+xHLMGmoKh3cWrM5FI90fXpY5A/3ylVGaW6DYBh8/Fc5e
|
||||||
|
1gGpUW+LSHufAuIn6YBiPcayGIlcjYkJ6lskNvt94G2ArZq0E5ZiPM8oLSnXJTKs
|
||||||
|
kjHWJxZVAgMBAAECggEAO6cmxvu7//LxoAm6R+Ji9H8r8OhSXPmWft/XQC1sSh9/
|
||||||
|
xswn5K/JpImzANpLcg8QLObH6upVsyqKZ8VUWfUiXu3h609hoAnrRE6dwzk9uQRg
|
||||||
|
jJcA+iuBSwpTvGksIuF/SVKqwtH1bkHnze1RFnf//OFaoegwwWqqOCq5icBar/Gx
|
||||||
|
PJ6rMvPhnwpHcJENUIBOUJ22GyOBm++NeAT/Ad1eWJUJATO+wv/cp/WAV6sER32r
|
||||||
|
HXoep0GLEqD3fE9Gg1+af3u+klli6fffd0p6Whd5f4qRIUDMSn1Pid2Rm+iZMrfa
|
||||||
|
sZ2d341WRuauyT+fltIWOMdORQnoIEVkWkSOwVZo3QKBgQDJIh0wjuBikZj6yvr3
|
||||||
|
xUV7LVXtttdyll5y+HWcfWZkIHdHM1YdgmSs43Xt3H2x3TIthoGd5+lQyBCT7HAD
|
||||||
|
noHnQasdOr3XgKrU8aWRZfmJoqZud2JaGcgSoIJYYm4Dg/cJZZs1khYpAfAy2m2G
|
||||||
|
90A4dPS2m7yR9qIKU0Qzfi0jvwKBgQDiSrjLMn0vNYR3cyNepyMJnjIFaEOVBYxx
|
||||||
|
FuRLKqF8A1VY955icdffqWOWj26SCAg+dG8tgekzT626zR9HVXuyKoOHL/xqrYoH
|
||||||
|
1m9qTqlc/BwQuCpnTWkWB+X6QZ+LAvEI+q3NvX4u0MaEz5k8HoHh/FokBhZCd8Xx
|
||||||
|
CnEJRuI66wKBgQC5iJRwhHuLsU7ymWbkQ78SHwHS5ATdmMPLPlIPZsWauzAMJ9ja
|
||||||
|
I7wGl1PjdK8l8SsmP5s1NAZPFB3mtgWl8QNXdYYI0nToY7Ix+C6Ibw6+3aC06b95
|
||||||
|
6apbNGDIcxOUfpjUvDtuMTHr5fPSlRbPlyhQa+KIyCQsaNuUtraE4XF2NQKBgAjr
|
||||||
|
NwNTNxngOxtWH2PApKHhaUlLRbae5F0ksNTBlmHD495AzC5HHGIFVBaACrSYunJz
|
||||||
|
tloNz7ok/szo/r2aAekQweRXINS3iEQs0HFZLlWq3hROneU6aNTkdMz7PyWEdSmM
|
||||||
|
b8M3H70Kn3hmkjyLW0Uj5A2da80s2VFZvwDFmW8HAoGAQWxSgNONrWfhgiACpE4P
|
||||||
|
SdpE/AHD77rMkahIxb5m1KcRk4Zsk31+XZ2QtmkoYsYuH+D0z09KCia0vNUk/Pkd
|
||||||
|
8OLAbOHe+WdXxdtVq+xWUyNGmiV4ChFTmSUgYB16dLHS771pgtr97DvbZx0QC+4o
|
||||||
|
Ay9yo20HQ4fizpfOIkoW96Q=
|
||||||
|
-----END PRIVATE KEY-----";
|
||||||
|
let jwe = "LKMS:eyJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiUlNBLU9BRVAiLCJ2ZW5kb3IiOiJsb2NhbC1taW5p\
|
||||||
|
LWttcyJ9.rei0aIWvri7nsy-UHJ163r1REqK897DHNVev6OCdQK7h0mH3dmHzaXJZ_CXiF3muylv8WU7nmhHLZsMD_z63rLrmH\
|
||||||
|
_pqH7grS2jTHEfHWgzkjSevFCOQxzSDtFiWJAlXQ_lA2N7y7FvJPM_W_OMZe9dFC2xDlgqXobgDHtrXrUVcjpd2wr2jCqP7RbE\
|
||||||
|
P8fnpIHRbBasC6kZggKRuyfd-YAbCNEjjaMgsZt-v2ridJVEccKFH37fu26m-5zWUbLHnl6afLf105Tkc47BbsLZXuz9gKomBa\
|
||||||
|
8cj6g6XjuZx8p8cOmlr5wnQElvFr_9prNFGFhB9c5mEsnGYbDrkM8ghCg.QmDRJCCzghCQlM7s.UF9RqEH9ULMb2is.wPGBxLP\
|
||||||
|
WkEcrkjsepiZPDA";
|
||||||
|
use rsa::pkcs8::DecodePrivateKey;
|
||||||
|
use serde_json::Value;
|
||||||
|
let rsa_private_key = RsaPrivateKey::from_pkcs8_pem(test_private_key_pem).unwrap();
|
||||||
|
let plaintext = deserialize_jwe_rsa(&jwe, &rsa_private_key).unwrap();
|
||||||
|
assert_eq!("hello world", String::from_utf8(plaintext.0).unwrap());
|
||||||
|
assert_eq!("A256GCM", plaintext.1.enc);
|
||||||
|
assert_eq!("RSA-OAEP", plaintext.1.alg);
|
||||||
|
assert_eq!("local-mini-kms", plaintext.1.vendor);
|
||||||
|
assert!(plaintext.1.version.is_none());
|
||||||
|
assert!(plaintext.1.data_type.is_none());
|
||||||
|
assert!(plaintext.1.exportable.is_none());
|
||||||
|
|
||||||
|
let public_key_pem = rsa_key_to_pem(&rsa_private_key).unwrap();
|
||||||
|
assert_eq!("-----BEGIN PUBLIC KEY-----
|
||||||
|
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAscrS0XjedIamB2uQ2/Ox
|
||||||
|
PUBCIOdkPh9Dxkgu2S5gUniEy1F935uUDrVDcxkdnDWIkvVzOBtL47xOrnd5OMXf
|
||||||
|
2FON37g/myxiMVyLZtLlxe4Q5HYF/2xq27rsn9Wg+iPESP72/p96QdEy+qtWlJcY
|
||||||
|
+5R3lHWGIAJe6rGz7315EhFme9dvw+qEM7xGR6u7BLT4h3KnJJKpcMj+V9FLuyDZ
|
||||||
|
uXPQc2DRezYtPsRyzBpqCod3FqzORSPdH16WOQP98pVRmlug2AYfPxXOXtYBqVFv
|
||||||
|
i0h7nwLiJ+mAYj3GshiJXI2JCepbJDb7feBtgK2atBOWYjzPKC0p1yUyrJIx1icW
|
||||||
|
VQIDAQAB
|
||||||
|
-----END PUBLIC KEY-----
|
||||||
|
", public_key_pem);
|
||||||
|
let jwk = rsa_key_to_jwk(&rsa_private_key).unwrap();
|
||||||
|
let jwk_value: Value = serde_json::from_str(&serde_json::to_string(&jwk.key).unwrap()).unwrap();
|
||||||
|
assert_eq!("AQAB", jwk_value.get("e").unwrap().as_str().unwrap());
|
||||||
|
assert_eq!("RSA", jwk_value.get("kty").unwrap().as_str().unwrap());
|
||||||
|
assert_eq!("scrS0XjedIamB2uQ2_OxPUBCIOdkPh9Dxkgu2S5gUniEy1F935uUDrVDcxkdnDWIkvVzOBtL47xOrnd5O\
|
||||||
|
MXf2FON37g_myxiMVyLZtLlxe4Q5HYF_2xq27rsn9Wg-iPESP72_p96QdEy-qtWlJcY-5R3lHWGIAJe6rGz7315EhFme9dvw-\
|
||||||
|
qEM7xGR6u7BLT4h3KnJJKpcMj-V9FLuyDZuXPQc2DRezYtPsRyzBpqCod3FqzORSPdH16WOQP98pVRmlug2AYfPxXOXtYBqVF\
|
||||||
|
vi0h7nwLiJ-mAYj3GshiJXI2JCepbJDb7feBtgK2atBOWYjzPKC0p1yUyrJIx1icWVQ",
|
||||||
|
jwk_value.get("n").unwrap().as_str().unwrap());
|
||||||
|
|
||||||
|
let rsa_public_key_2 = jwk_to_rsa_pubic_key(&serde_json::to_string(&jwk.key).unwrap()).unwrap();
|
||||||
|
let jwe = serialize_jwe_rsa(b"hello world 2", &rsa_public_key_2).unwrap();
|
||||||
|
let plaintext = deserialize_jwe_rsa(&jwe, &rsa_private_key).unwrap();
|
||||||
|
assert_eq!("hello world 2", String::from_utf8(plaintext.0).unwrap());
|
||||||
|
assert_eq!("A256GCM", plaintext.1.enc);
|
||||||
|
assert_eq!("RSA-OAEP", plaintext.1.alg);
|
||||||
|
assert_eq!("local-mini-kms", plaintext.1.vendor);
|
||||||
|
assert!(plaintext.1.version.is_none());
|
||||||
|
assert!(plaintext.1.data_type.is_none());
|
||||||
|
assert!(plaintext.1.exportable.is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_jwe_aes() {
|
||||||
|
let jwe = serialize_jwe_aes(b"hello world", b"01234567890123456789012345678901").unwrap();
|
||||||
|
let plaintext = deserialize_jwe_aes(&jwe, b"01234567890123456789012345678901").unwrap();
|
||||||
|
assert_eq!("hello world", String::from_utf8(plaintext.0).unwrap());
|
||||||
|
assert_eq!("A256GCM", plaintext.1.enc);
|
||||||
|
assert_eq!("A256KW", plaintext.1.alg);
|
||||||
|
assert_eq!("local-mini-kms", plaintext.1.vendor);
|
||||||
|
assert_eq!("a15a8f066b1af95d", plaintext.1.version.unwrap());
|
||||||
|
assert!(plaintext.1.data_type.is_none());
|
||||||
|
assert!(plaintext.1.exportable.is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_decode_url_safe_no_pad() {
|
||||||
|
assert_eq!("68656c6c6f20776f726c64",
|
||||||
|
hex::encode(decode_url_safe_no_pad(&URL_SAFE_NO_PAD.encode("hello world")).unwrap()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_to_bytes32() {
|
||||||
|
assert!(to_bytes32(b"").is_err());
|
||||||
|
assert!(to_bytes32(b"01234567890123456789012345678901").is_ok());
|
||||||
|
assert_eq!("3031323334353637383930313233343536373839303132333435363738393031",
|
||||||
|
hex::encode(to_bytes32(b"01234567890123456789012345678901").unwrap()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_master_key_checksum() {
|
||||||
|
assert_eq!("685fb69d15d5efe9", get_master_key_checksum(b"test"));
|
||||||
|
assert_eq!("80c5603e00ca3e80", get_master_key_checksum(b"hello_world"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_jwe() {
|
||||||
|
assert_eq!("test", get_jwe("test"));
|
||||||
|
assert_eq!("test", get_jwe("LKMS:test"));
|
||||||
|
}
|
||||||
|
|||||||
10
src/main.rs
10
src/main.rs
@@ -2,6 +2,11 @@ use clap::{App, AppSettings, ArgMatches};
|
|||||||
use rust_util::util_clap::{Command, CommandError};
|
use rust_util::util_clap::{Command, CommandError};
|
||||||
use rust_util::{failure_and_exit, information, success, warning};
|
use rust_util::{failure_and_exit, information, success, warning};
|
||||||
|
|
||||||
|
use zeroizing_alloc::ZeroAlloc;
|
||||||
|
|
||||||
|
#[global_allocator]
|
||||||
|
static ALLOC: ZeroAlloc<std::alloc::System> = ZeroAlloc(std::alloc::System);
|
||||||
|
|
||||||
mod db;
|
mod db;
|
||||||
mod proc;
|
mod proc;
|
||||||
mod jose;
|
mod jose;
|
||||||
@@ -17,6 +22,7 @@ mod yubikey_hmac;
|
|||||||
#[cfg(feature = "yubikey")]
|
#[cfg(feature = "yubikey")]
|
||||||
mod yubikey_init_master_key;
|
mod yubikey_init_master_key;
|
||||||
mod serve_datakey;
|
mod serve_datakey;
|
||||||
|
mod serve_log;
|
||||||
|
|
||||||
pub struct DefaultCommandImpl;
|
pub struct DefaultCommandImpl;
|
||||||
|
|
||||||
@@ -36,6 +42,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "harden_process")]
|
||||||
fn harden_process() {
|
fn harden_process() {
|
||||||
let ignore_harden_process_error = std::env::var("IGNORE_HARDEN_PROCESS_ERROR")
|
let ignore_harden_process_error = std::env::var("IGNORE_HARDEN_PROCESS_ERROR")
|
||||||
.map(|v| &v == "true").unwrap_or_else(|_| false);
|
.map(|v| &v == "true").unwrap_or_else(|_| false);
|
||||||
@@ -61,6 +68,8 @@ fn inner_main() -> CommandError {
|
|||||||
{
|
{
|
||||||
#[cfg(feature = "yubikey")]
|
#[cfg(feature = "yubikey")]
|
||||||
features.push("yubikey".to_string());
|
features.push("yubikey".to_string());
|
||||||
|
#[cfg(feature = "harden_process")]
|
||||||
|
features.push("harden_process".to_string());
|
||||||
}
|
}
|
||||||
let long_about = format!("Local mini KMS, features: [{}]", features.join(", "));
|
let long_about = format!("Local mini KMS, features: [{}]", features.join(", "));
|
||||||
let mut app = App::new(env!("CARGO_PKG_NAME"))
|
let mut app = App::new(env!("CARGO_PKG_NAME"))
|
||||||
@@ -75,6 +84,7 @@ fn inner_main() -> CommandError {
|
|||||||
let matches = app.get_matches();
|
let matches = app.get_matches();
|
||||||
for command in &commands {
|
for command in &commands {
|
||||||
if let Some(sub_cmd_matches) = matches.subcommand_matches(command.name()) {
|
if let Some(sub_cmd_matches) = matches.subcommand_matches(command.name()) {
|
||||||
|
#[cfg(feature = "harden_process")]
|
||||||
if command.name() == "serve" { harden_process(); }
|
if command.name() == "serve" { harden_process(); }
|
||||||
return command.run(&matches, sub_cmd_matches);
|
return command.run(&matches, sub_cmd_matches);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,12 +7,12 @@ pub struct Process {
|
|||||||
pub exec: Option<PathBuf>,
|
pub exec: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_os = "linux"))]
|
#[cfg(not(all(target_os = "linux", feature = "harden_process")))]
|
||||||
pub fn get_process(_port: u16) -> Option<Process> {
|
pub fn get_process(_port: u16) -> Option<Process> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(all(target_os = "linux", feature = "harden_process"))]
|
||||||
pub fn get_process(port: u16) -> Option<Process> {
|
pub fn get_process(port: u16) -> Option<Process> {
|
||||||
let all_procs = match procfs::process::all_processes() {
|
let all_procs = match procfs::process::all_processes() {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
|||||||
196
src/serve.rs
196
src/serve.rs
@@ -1,5 +1,3 @@
|
|||||||
use std::net::SocketAddr;
|
|
||||||
|
|
||||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||||
use hyper::client::HttpConnector;
|
use hyper::client::HttpConnector;
|
||||||
use hyper::server::conn::AddrStream;
|
use hyper::server::conn::AddrStream;
|
||||||
@@ -8,10 +6,11 @@ use hyper::{Body, Client, Method, Request, Response, Server, StatusCode};
|
|||||||
use rust_util::util_clap::{Command, CommandError};
|
use rust_util::util_clap::{Command, CommandError};
|
||||||
use rust_util::{failure_and_exit, information, success, warning, XResult};
|
use rust_util::{failure_and_exit, information, success, warning, XResult};
|
||||||
use serde_json::{json, Value};
|
use serde_json::{json, Value};
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
use std::sync::atomic::{AtomicU64, Ordering};
|
||||||
use tokio::runtime::Runtime;
|
use tokio::runtime::Runtime;
|
||||||
|
|
||||||
use crate::serve_common::{self, GenericError, MemoryKey, Result};
|
use crate::serve_common::{self, GenericError, MemoryKey, Result};
|
||||||
use crate::serve_encrypt_decrypt;
|
|
||||||
use crate::serve_init;
|
use crate::serve_init;
|
||||||
use crate::serve_init::InitRequest;
|
use crate::serve_init::InitRequest;
|
||||||
use crate::serve_read_write;
|
use crate::serve_read_write;
|
||||||
@@ -20,22 +19,77 @@ use crate::serve_status;
|
|||||||
use crate::yubikey_hmac;
|
use crate::yubikey_hmac;
|
||||||
use crate::{db, jose, proc};
|
use crate::{db, jose, proc};
|
||||||
use crate::{do_response, serve_datakey};
|
use crate::{do_response, serve_datakey};
|
||||||
|
use crate::{serve_encrypt_decrypt, serve_log};
|
||||||
|
|
||||||
|
lazy_static::lazy_static! {
|
||||||
|
pub static ref GLOBAL_REQUEST_COUNT: AtomicU64 = AtomicU64::new(0);
|
||||||
|
}
|
||||||
|
|
||||||
pub struct CommandImpl;
|
pub struct CommandImpl;
|
||||||
|
|
||||||
impl Command for CommandImpl {
|
impl Command for CommandImpl {
|
||||||
fn name(&self) -> &str { "serve" }
|
fn name(&self) -> &str {
|
||||||
|
"serve"
|
||||||
|
}
|
||||||
|
|
||||||
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
||||||
SubCommand::with_name(self.name()).about("Local mini KMS serve")
|
SubCommand::with_name(self.name())
|
||||||
.arg(Arg::with_name("listen").long("listen").short("L").takes_value(true).default_value("127.0.0.1:5567").help("Listen"))
|
.about("Local mini KMS serve")
|
||||||
.arg(Arg::with_name("local-db").long("local-db").short("d").takes_value(true).default_value("local-mini-kms.db").help("Local db file"))
|
.arg(
|
||||||
.arg(Arg::with_name("yubikey-challenge").long("yubikey-challenge").short("c").takes_value(true).help("Yubikey challenge"))
|
Arg::with_name("listen")
|
||||||
.arg(Arg::with_name("init-encrypted-master-key").long("init-encrypted-master-key").short("k").takes_value(true).help("Init encrypted mater key"))
|
.long("listen")
|
||||||
|
.short("L")
|
||||||
|
.takes_value(true)
|
||||||
|
.default_value("127.0.0.1:5567")
|
||||||
|
.help("Listen"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("local-db")
|
||||||
|
.long("local-db")
|
||||||
|
.short("d")
|
||||||
|
.takes_value(true)
|
||||||
|
.default_value("local-mini-kms.db")
|
||||||
|
.help("Local db file"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("yubikey-challenge")
|
||||||
|
.long("yubikey-challenge")
|
||||||
|
.short("c")
|
||||||
|
.takes_value(true)
|
||||||
|
.help("Yubikey challenge"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("init-encrypted-master-key")
|
||||||
|
.long("init-encrypted-master-key")
|
||||||
|
.short("k")
|
||||||
|
.takes_value(true)
|
||||||
|
.help("Init encrypted mater key"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("log-level")
|
||||||
|
.long("log-level")
|
||||||
|
.takes_value(true)
|
||||||
|
.help("Log level: trace, debug, info, warn or error"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("log-file")
|
||||||
|
.long("log-file")
|
||||||
|
.takes_value(true)
|
||||||
|
.help("Log file #DEFAULT or config file"),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
||||||
let local_mini_kms_db = sub_arg_matches.value_of("local-db").expect("Get local mini kms db error");
|
let log_level = sub_arg_matches.value_of("log-level");
|
||||||
|
let log_file = sub_arg_matches.value_of("log-file");
|
||||||
|
|
||||||
|
if let Err(e) = serve_log::init_logger(log_level, log_file) {
|
||||||
|
println!("[ERROR] Init logger failed: {}", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
let local_mini_kms_db = sub_arg_matches
|
||||||
|
.value_of("local-db")
|
||||||
|
.expect("Get local mini kms db error");
|
||||||
match init_instance(local_mini_kms_db) {
|
match init_instance(local_mini_kms_db) {
|
||||||
Ok(true) => success!("Init server success"),
|
Ok(true) => success!("Init server success"),
|
||||||
Ok(false) => failure_and_exit!("SHOULD NOT HAPPEN, server already init"),
|
Ok(false) => failure_and_exit!("SHOULD NOT HAPPEN, server already init"),
|
||||||
@@ -46,9 +100,13 @@ impl Command for CommandImpl {
|
|||||||
#[cfg(feature = "yubikey")]
|
#[cfg(feature = "yubikey")]
|
||||||
init_with_yubikey_challenge(&rt, sub_arg_matches);
|
init_with_yubikey_challenge(&rt, sub_arg_matches);
|
||||||
|
|
||||||
let listen = sub_arg_matches.value_of("listen").expect("Get argument listen error");
|
let listen = sub_arg_matches
|
||||||
|
.value_of("listen")
|
||||||
|
.expect("Get argument listen error");
|
||||||
rt.block_on(async {
|
rt.block_on(async {
|
||||||
let addr = listen.parse().unwrap_or_else(|_| panic!("Parse listen error: {}", listen));
|
let addr = listen
|
||||||
|
.parse()
|
||||||
|
.unwrap_or_else(|_| panic!("Parse listen error: {}", listen));
|
||||||
let client = Client::new();
|
let client = Client::new();
|
||||||
let new_service = make_service_fn(move |conn: &AddrStream| {
|
let new_service = make_service_fn(move |conn: &AddrStream| {
|
||||||
let remote_addr = conn.remote_addr();
|
let remote_addr = conn.remote_addr();
|
||||||
@@ -61,6 +119,7 @@ impl Command for CommandImpl {
|
|||||||
});
|
});
|
||||||
let server = Server::bind(&addr).serve(new_service);
|
let server = Server::bind(&addr).serve(new_service);
|
||||||
information!("Listening on http://{}", addr);
|
information!("Listening on http://{}", addr);
|
||||||
|
log::info!("Listening on http://{}", addr);
|
||||||
match server.await {
|
match server.await {
|
||||||
Err(e) => failure_and_exit!("Server error: {}", e),
|
Err(e) => failure_and_exit!("Server error: {}", e),
|
||||||
Ok(_) => success!("Server ended"),
|
Ok(_) => success!("Server ended"),
|
||||||
@@ -77,11 +136,19 @@ async fn response_requests(
|
|||||||
req: Request<Body>,
|
req: Request<Body>,
|
||||||
_client: Client<HttpConnector>,
|
_client: Client<HttpConnector>,
|
||||||
) -> Result<Response<Body>> {
|
) -> Result<Response<Body>> {
|
||||||
|
let request_idx = GLOBAL_REQUEST_COUNT.fetch_add(1, Ordering::Relaxed);
|
||||||
let process = proc::get_process(remote_addr.port());
|
let process = proc::get_process(remote_addr.port());
|
||||||
match process {
|
match process {
|
||||||
None => information!( "Receive request: {}, from: {}", req.uri(), remote_addr ),
|
None => log::info!(
|
||||||
Some(process) => information!(
|
"[{:06}] Receive request: {} {}, from: {}",
|
||||||
"Receive request: {}, from: {}, process: {} {} {:?}",
|
request_idx,
|
||||||
|
req.method(),
|
||||||
|
req.uri(),
|
||||||
|
remote_addr
|
||||||
|
),
|
||||||
|
Some(process) => log::info!(
|
||||||
|
"[{:06}] Receive request: {}, from: {}, process: {} {} {:?}",
|
||||||
|
request_idx,
|
||||||
req.uri(),
|
req.uri(),
|
||||||
remote_addr,
|
remote_addr,
|
||||||
process.pid,
|
process.pid,
|
||||||
@@ -94,17 +161,22 @@ async fn response_requests(
|
|||||||
(&Method::POST, "/update") => update().await,
|
(&Method::POST, "/update") => update().await,
|
||||||
(&Method::POST, "/decrypt") => serve_encrypt_decrypt::decrypt(req).await,
|
(&Method::POST, "/decrypt") => serve_encrypt_decrypt::decrypt(req).await,
|
||||||
(&Method::POST, "/encrypt") => serve_encrypt_decrypt::encrypt(req).await,
|
(&Method::POST, "/encrypt") => serve_encrypt_decrypt::encrypt(req).await,
|
||||||
|
(&Method::POST, "/list") => serve_read_write::list(req).await,
|
||||||
(&Method::POST, "/read") => serve_read_write::read(req).await,
|
(&Method::POST, "/read") => serve_read_write::read(req).await,
|
||||||
(&Method::POST, "/write") => serve_read_write::write(req).await,
|
(&Method::POST, "/write") => serve_read_write::write(req).await,
|
||||||
(&Method::POST, "/datakey") => serve_datakey::generate(req).await,
|
(&Method::POST, "/datakey") => serve_datakey::generate(req).await,
|
||||||
(&Method::GET, "/status") => serve_status::status().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().status(StatusCode::NOT_FOUND).body(
|
||||||
.status(StatusCode::NOT_FOUND)
|
format!(
|
||||||
.body(format!("{}\n", serde_json::to_string_pretty(&json!({
|
"{}\n",
|
||||||
"error": "not_found",
|
serde_json::to_string_pretty(&json!({
|
||||||
}))?).into())?),
|
"error": "not_found",
|
||||||
|
}))?
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
|
)?),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,7 +186,9 @@ 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 = serve_common::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 => {
|
||||||
@@ -130,7 +204,9 @@ 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 = serve_common::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)?;
|
||||||
@@ -146,21 +222,30 @@ async fn update() -> Result<Response<Body>> {
|
|||||||
|
|
||||||
async fn inner_update() -> XResult<(StatusCode, Value)> {
|
async fn inner_update() -> XResult<(StatusCode, Value)> {
|
||||||
let update = update_instance_rsa_key_pair()?;
|
let update = update_instance_rsa_key_pair()?;
|
||||||
Ok((StatusCode::OK, json!({
|
Ok((
|
||||||
"update": update,
|
StatusCode::OK,
|
||||||
})))
|
json!({
|
||||||
|
"update": update,
|
||||||
|
}),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_version() -> Result<Response<Body>> {
|
async fn get_version() -> Result<Response<Body>> {
|
||||||
Ok(Response::builder().body(format!(
|
Ok(Response::builder().body(
|
||||||
"{} - {}\n", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")
|
format!(
|
||||||
).into())?)
|
"{} - {}\n",
|
||||||
|
env!("CARGO_PKG_NAME"),
|
||||||
|
env!("CARGO_PKG_VERSION")
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
|
)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_root() -> Result<Response<Body>> {
|
async fn get_root() -> Result<Response<Body>> {
|
||||||
if std::env::var("LOCAL_MINI_KMS_HELP").is_ok() {
|
if std::env::var("LOCAL_MINI_KMS_HELP").is_ok() {
|
||||||
Ok(Response::builder().body(format!(
|
Ok(Response::builder().body(
|
||||||
r##"{} - {}
|
format!(
|
||||||
|
r##"{} - {}
|
||||||
Supports commands:
|
Supports commands:
|
||||||
- GET /version
|
- GET /version
|
||||||
- GET /status
|
- GET /status
|
||||||
@@ -170,8 +255,12 @@ Supports commands:
|
|||||||
- POST /decrypt
|
- POST /decrypt
|
||||||
- POST /read
|
- POST /read
|
||||||
- POST /write
|
- POST /write
|
||||||
"##, env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")
|
"##,
|
||||||
).into())?)
|
env!("CARGO_PKG_NAME"),
|
||||||
|
env!("CARGO_PKG_VERSION")
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
|
)?)
|
||||||
} else {
|
} else {
|
||||||
Ok(Response::builder().body("Root Not Found\n".into())?)
|
Ok(Response::builder().body("Root Not Found\n".into())?)
|
||||||
}
|
}
|
||||||
@@ -179,29 +268,37 @@ Supports commands:
|
|||||||
|
|
||||||
#[cfg(feature = "yubikey")]
|
#[cfg(feature = "yubikey")]
|
||||||
fn init_with_yubikey_challenge(rt: &Runtime, sub_arg_matches: &ArgMatches) {
|
fn init_with_yubikey_challenge(rt: &Runtime, sub_arg_matches: &ArgMatches) {
|
||||||
let mut yubikey_challenge = sub_arg_matches.value_of("yubikey-challenge").map(ToString::to_string);
|
let mut yubikey_challenge = sub_arg_matches
|
||||||
|
.value_of("yubikey-challenge")
|
||||||
|
.map(ToString::to_string);
|
||||||
let init_encrypted_master_key = sub_arg_matches.value_of("init-encrypted-master-key");
|
let init_encrypted_master_key = sub_arg_matches.value_of("init-encrypted-master-key");
|
||||||
|
|
||||||
if init_encrypted_master_key.is_some() && yubikey_challenge.is_none() {
|
if init_encrypted_master_key.is_some() && yubikey_challenge.is_none() {
|
||||||
yubikey_challenge = rpassword::prompt_password("Yubikey challenge: ").ok();
|
yubikey_challenge =
|
||||||
|
pinentry_util::read_pin(Some("Input yubikey challenge"), Some("Challenge: "))
|
||||||
|
.ok()
|
||||||
|
.map(|p| p.get_pin().to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
let (challenge_key, init_encrypted_master_key) = match (yubikey_challenge, init_encrypted_master_key) {
|
let (challenge_key, init_encrypted_master_key) =
|
||||||
(Some(yubikey_challenge), Some(init_encrypted_master_key)) => {
|
match (yubikey_challenge, init_encrypted_master_key) {
|
||||||
match yubikey_hmac::yubikey_challenge_as_32_bytes(yubikey_challenge.as_bytes()) {
|
(Some(yubikey_challenge), Some(init_encrypted_master_key)) => {
|
||||||
Err(e) => {
|
match yubikey_hmac::yubikey_challenge_as_32_bytes(yubikey_challenge.as_bytes()) {
|
||||||
warning!("Yubikey challenge failed: {}", e);
|
Err(e) => {
|
||||||
return;
|
warning!("Yubikey challenge failed: {}", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Ok(challenge_key) => (challenge_key, init_encrypted_master_key),
|
||||||
}
|
}
|
||||||
Ok(challenge_key) => (challenge_key, init_encrypted_master_key),
|
|
||||||
}
|
}
|
||||||
}
|
(Some(_), None) | (None, Some(_)) => {
|
||||||
(Some(_), None) | (None, Some(_)) => {
|
warning!(
|
||||||
warning!("Arguments yubikey-challenge and init-encrypted-master-key should both assigned.");
|
"Arguments yubikey-challenge and init-encrypted-master-key should both assigned."
|
||||||
return;
|
);
|
||||||
}
|
return;
|
||||||
_ => return,
|
}
|
||||||
};
|
_ => return,
|
||||||
|
};
|
||||||
|
|
||||||
match jose::deserialize_jwe_aes(init_encrypted_master_key, &challenge_key) {
|
match jose::deserialize_jwe_aes(init_encrypted_master_key, &challenge_key) {
|
||||||
Err(e) => warning!("Yubikey seal master key failed: {}", e),
|
Err(e) => warning!("Yubikey seal master key failed: {}", e),
|
||||||
@@ -213,7 +310,8 @@ fn init_with_yubikey_challenge(rt: &Runtime, sub_arg_matches: &ArgMatches) {
|
|||||||
clear_master_key_hex: Some(hex::encode(&key)),
|
clear_master_key_hex: Some(hex::encode(&key)),
|
||||||
clear_master_key_base64: None,
|
clear_master_key_base64: None,
|
||||||
encrypted_master_key: None,
|
encrypted_master_key: None,
|
||||||
}).await
|
})
|
||||||
|
.await
|
||||||
});
|
});
|
||||||
match init_master_key_result {
|
match init_master_key_result {
|
||||||
Err(e) => warning!("Init master key failed: {}", e),
|
Err(e) => warning!("Init master key failed: {}", e),
|
||||||
|
|||||||
@@ -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) => (
|
||||||
@@ -34,11 +55,12 @@ pub fn ok(body: Value) -> XResult<(StatusCode, Value)> {
|
|||||||
Ok((StatusCode::OK, body))
|
Ok((StatusCode::OK, body))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn 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)> {
|
||||||
|
Ok((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)> {
|
||||||
@@ -97,8 +119,20 @@ 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> {
|
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 {
|
||||||
@@ -131,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 initted!"),
|
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: {}"
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,26 @@
|
|||||||
use crate::serve_common::{get_master_key, Result};
|
use crate::db::Key;
|
||||||
use crate::{do_response, jose, serve_common};
|
use crate::serve_common::{open_local_db, parse_request, Result};
|
||||||
|
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::{debugging, iff, XResult};
|
use rust_util::{iff, XResult};
|
||||||
use seckey::SecBytes;
|
use seckey::SecBytes;
|
||||||
use serde_derive::{Deserialize, Serialize};
|
use serde_derive::{Deserialize, Serialize};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use serde_json::{Map, Value};
|
use serde_json::{Map, Value};
|
||||||
|
|
||||||
|
|
||||||
// type:aes, spec:128,192,256
|
// type:aes, spec:128,192,256
|
||||||
// type:rsa, spec:2048,3072,4096
|
// type:rsa, spec:2048,3072,4096
|
||||||
// type:ec, spec:p256,p384,p521,ed25519,cv25519
|
// type:ec, spec:p256,p384,p521,ed25519,cv25519
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
struct DataKeyRequest {
|
struct DataKeyRequest {
|
||||||
key_type: String,
|
r#type: String,
|
||||||
key_spec: String,
|
spec: String,
|
||||||
|
name: Option<String>,
|
||||||
|
comment: Option<String>,
|
||||||
|
exportable: Option<bool>,
|
||||||
return_plaintext: Option<bool>,
|
return_plaintext: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,51 +29,60 @@ 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())?;
|
|
||||||
|
|
||||||
debugging!("Generate data key: {} {}", &request.key_type, &request.key_spec);
|
log::debug!("Generate data key: {} {}", &request.r#type, &request.spec);
|
||||||
let key = match get_master_key() {
|
let key = require_master_key!();
|
||||||
None => return serve_common::error("status_not_ready"),
|
let exportable = request.exportable.unwrap_or(true);
|
||||||
Some(key) => key,
|
let ret_key_plaintext = iff!(!exportable, false, request.return_plaintext.unwrap_or(false));
|
||||||
};
|
|
||||||
let ret_key_plaintext = request.return_plaintext.unwrap_or(false);
|
|
||||||
|
|
||||||
let response_result = match (request.key_type.as_str(), request.key_spec.as_str()) {
|
let response_result = match (request.r#type.as_str(), request.spec.as_str()) {
|
||||||
("aes", "128") => generate_aes("datakey.aes_128:", key, 16, ret_key_plaintext),
|
// ("aes", "128") => generate_aes("datakey:aes-128", exportable, key, 16, ret_key_plaintext),
|
||||||
("aes", "192") => generate_aes("datakey.aes_192:", key, 24, ret_key_plaintext),
|
// ("aes", "192") => generate_aes("datakey:aes-192", exportable, key, 24, ret_key_plaintext),
|
||||||
("aes", "256") => generate_aes("datakey.aes_256:", key, 32, ret_key_plaintext),
|
("aes", "256") => generate_aes("datakey:aes-256", exportable, key, 32, ret_key_plaintext),
|
||||||
// TODO rsa 2048, rsa 3072, rsa 4096
|
// TODO rsa 2048, rsa 3072, rsa 4096
|
||||||
// TODO ec p256, p384, p521, ed25519, cv25519
|
// 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 {
|
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)) => {
|
Ok((key_plaintext, key_ciphertext)) => {
|
||||||
let mut map = Map::new();
|
let mut map = Map::new();
|
||||||
map.insert("key_type".to_string(), Value::String(request.key_type));
|
map.insert("key_type".to_string(), Value::String(request.r#type));
|
||||||
map.insert("key_spec".to_string(), Value::String(request.key_spec));
|
map.insert("key_spec".to_string(), Value::String(request.spec));
|
||||||
if let Some(key_plaintext) = key_plaintext {
|
if let Some(key_plaintext) = key_plaintext {
|
||||||
map.insert("key_plaintext".to_string(), Value::String(STANDARD.encode(&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 {
|
||||||
|
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)))
|
Ok((StatusCode::OK, Value::Object(map)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_aes(prefix: &str, key: SecBytes, len: i32, ret_key_plaintext: bool) -> XResult<(Option<Vec<u8>>, String)> {
|
fn generate_aes(data_key_type: &str, exportable: bool, key: SecBytes, len: i32, ret_key_plaintext: bool) -> XResult<(Option<Vec<u8>>, String)> {
|
||||||
let bytes: [u8; 32] = random();
|
let bytes: [u8; 32] = random();
|
||||||
let value = &bytes[0..len as usize];
|
let value = &bytes[0..len as usize];
|
||||||
let key_plaintext = iff!(ret_key_plaintext, Some(value.to_vec()), None);
|
let key_plaintext = iff!(ret_key_plaintext, Some(value.to_vec()), None);
|
||||||
let key_ciphertext = jose::serialize_jwe_aes(&join_prefix_value(prefix, value), &key.read())?;
|
let key_ciphertext = jose::serialize_jwe_aes_with_data_type(data_key_type, exportable, value, &key.read())?;
|
||||||
Ok((key_plaintext, key_ciphertext))
|
Ok((key_plaintext, key_ciphertext))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn join_prefix_value(prefix: &str, value: &[u8]) -> Vec<u8> {
|
|
||||||
let mut ret = Vec::with_capacity(prefix.len() + value.len());
|
|
||||||
ret.extend_from_slice(prefix.as_bytes());
|
|
||||||
ret.extend_from_slice(value);
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
use hyper::{Body, Request, Response, StatusCode};
|
use hyper::{Body, Request, Response, StatusCode};
|
||||||
use hyper::body::Buf;
|
use rust_util::XResult;
|
||||||
use rust_util::{debugging, XResult};
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::{json, Value};
|
use serde_json::{json, Map, Value};
|
||||||
|
|
||||||
use crate::do_response;
|
|
||||||
use crate::jose;
|
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, parse_request, MultipleViewValue, Result};
|
||||||
|
use crate::{do_response, require_master_key};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
struct DecryptRequest {
|
struct DecryptRequest {
|
||||||
@@ -18,21 +17,32 @@ 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())?;
|
|
||||||
|
|
||||||
debugging!("To be decrypted value: {}", &data.encrypted_value);
|
log::trace!("To be decrypted value: {}", &data.encrypted_value);
|
||||||
let key = match get_master_key() {
|
let key = require_master_key!();
|
||||||
None => return serve_common::error("status_not_ready"),
|
|
||||||
Some(key) => key,
|
|
||||||
};
|
|
||||||
let decrypted_value = jose::deserialize_jwe_aes(&data.encrypted_value, &key.read());
|
let decrypted_value = jose::deserialize_jwe_aes(&data.encrypted_value, &key.read());
|
||||||
drop(key);
|
drop(key);
|
||||||
|
let (data, header) = decrypted_value?;
|
||||||
|
|
||||||
decrypted_value.map(|v| {
|
if let Some(false) = header.exportable {
|
||||||
let map = byte_to_multi_view_map(&v.0, true);
|
return serve_common::client_error("data_not_exportable");
|
||||||
(StatusCode::OK, Value::Object(map))
|
}
|
||||||
})
|
|
||||||
|
let mut map = byte_to_multi_view_map(&data, true);
|
||||||
|
let mut header_map = Map::new();
|
||||||
|
header_map.insert("enc".to_string(), Value::String(header.enc.clone()));
|
||||||
|
header_map.insert("alg".to_string(), Value::String(header.alg.clone()));
|
||||||
|
if let Some(version) = &header.version {
|
||||||
|
header_map.insert("version".to_string(), Value::String(version.to_string()));
|
||||||
|
}
|
||||||
|
if let Some(data_type) = &header.data_type {
|
||||||
|
header_map.insert("data_type".to_string(), Value::String(data_type.to_string()));
|
||||||
|
}
|
||||||
|
if !header_map.is_empty() {
|
||||||
|
map.insert("header".to_string(), Value::Object(header_map));
|
||||||
|
}
|
||||||
|
Ok((StatusCode::OK, Value::Object(map)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn encrypt(req: Request<Body>) -> Result<Response<Body>> {
|
pub async fn encrypt(req: Request<Body>) -> Result<Response<Body>> {
|
||||||
@@ -40,13 +50,9 @@ 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 = match get_master_key() {
|
let key = require_master_key!();
|
||||||
None => return serve_common::error("status_not_ready"),
|
|
||||||
Some(key) => key,
|
|
||||||
};
|
|
||||||
let encrypt_result = jose::serialize_jwe_aes(&value, &key.read());
|
let encrypt_result = jose::serialize_jwe_aes(&value, &key.read());
|
||||||
drop(key);
|
drop(key);
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
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::{debugging, information, opt_result, success, warning, XResult};
|
use rust_util::{opt_result, XResult};
|
||||||
use seckey::SecBytes;
|
use seckey::SecBytes;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::{json, Value};
|
use serde_json::{json, Value};
|
||||||
@@ -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,18 +27,16 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn inner_init_request(init_request: InitRequest) -> 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");
|
let mut startup_rw_lock = serve_common::STATUP_RW_LOCK.lock().expect("Lock read startup rw lock error");
|
||||||
match &*startup_rw_lock {
|
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() {
|
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");
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,19 +45,19 @@ pub async fn inner_init_request(init_request: InitRequest) -> XResult<(StatusCod
|
|||||||
} else if let Some(clear_master_key_hex) = init_request.clear_master_key_hex {
|
} else if let Some(clear_master_key_hex) = init_request.clear_master_key_hex {
|
||||||
hex::decode(clear_master_key_hex)?
|
hex::decode(clear_master_key_hex)?
|
||||||
} else if let Some(encrypted_master_key) = init_request.encrypted_master_key {
|
} else if let Some(encrypted_master_key) = init_request.encrypted_master_key {
|
||||||
debugging!("Received encrypted master key: {}", encrypted_master_key);
|
log::debug!("Received encrypted master key: {}", encrypted_master_key);
|
||||||
if let Some(k) = &*startup_rw_lock {
|
if let Some(k) = &*startup_rw_lock {
|
||||||
let (clear_master_key, _) = jose::deserialize_jwe_rsa(&encrypted_master_key, &k.instance_rsa_key_pair)?;
|
let (clear_master_key, _) = jose::deserialize_jwe_rsa(&encrypted_master_key, &k.instance_rsa_key_pair)?;
|
||||||
clear_master_key
|
clear_master_key
|
||||||
} else {
|
} 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 {
|
} else {
|
||||||
return serve_common::error("master_key_missing");
|
return serve_common::client_error("master_key_missing");
|
||||||
};
|
};
|
||||||
|
|
||||||
if clear_master_key.len() != 32 {
|
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 {
|
if let Some(k) = &mut *startup_rw_lock {
|
||||||
@@ -71,24 +68,24 @@ pub async fn inner_init_request(init_request: InitRequest) -> XResult<(StatusCod
|
|||||||
let key = Key {
|
let key = Key {
|
||||||
name: db::DEFAULT_MASTER_KEY_VERIFICATION_KEY.to_string(),
|
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)?,
|
encrypted_key: jose::serialize_jwe_aes("LOCAL-MINI-KMS:MAGIC-VERIFICATION-KEY".as_bytes(), &clear_master_key)?,
|
||||||
comment: None,
|
comment: Some("local-mini-kms re-init verification".to_string()),
|
||||||
};
|
};
|
||||||
db::insert_key(&conn, &key)?;
|
db::insert_key(&conn, &key)?;
|
||||||
}
|
}
|
||||||
Some(key) => {
|
Some(key) => {
|
||||||
debugging!("Found jwe: {}", &key.encrypted_key);
|
log::trace!("Found jwe: {}", &key.encrypted_key);
|
||||||
let _ = opt_result!(jose::deserialize_jwe_aes(&key.encrypted_key, &clear_master_key), "Deserialize master key verification key failed: {}");
|
let _ = opt_result!(jose::deserialize_jwe_aes(&key.encrypted_key, &clear_master_key), "Deserialize master key verification key failed: {}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
information!("Set master key success");
|
log::info!("Set master key success");
|
||||||
|
|
||||||
#[cfg(feature = "yubikey")]
|
#[cfg(feature = "yubikey")]
|
||||||
if let Some(yubikey_challenge) = &init_request.yubikey_challenge {
|
if let Some(yubikey_challenge) = &init_request.yubikey_challenge {
|
||||||
match yubikey_hmac::yubikey_challenge_as_32_bytes(yubikey_challenge.as_bytes()) {
|
match yubikey_hmac::yubikey_challenge_as_32_bytes(yubikey_challenge.as_bytes()) {
|
||||||
Err(e) => warning!("Yubikey challenge failed: {}", e),
|
Err(e) => log::warn!("Yubikey challenge failed: {}", e),
|
||||||
Ok(challenge_key) => match jose::serialize_jwe_aes(&clear_master_key, &challenge_key) {
|
Ok(challenge_key) => match jose::serialize_jwe_aes(&clear_master_key, &challenge_key) {
|
||||||
Err(e) => warning!("Yubikey seal master key failed: {}", e),
|
Err(e) => log::warn!("Yubikey seal master key failed: {}", e),
|
||||||
Ok(jwe) => success!("Yubikey sealed master key: {}", jwe)
|
Ok(jwe) => log::info!("Yubikey sealed master key: {}", jwe)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
66
src/serve_log.rs
Normal file
66
src/serve_log.rs
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
use log::LevelFilter;
|
||||||
|
use rust_util::{simple_error, XResult};
|
||||||
|
use std::{env, fs};
|
||||||
|
|
||||||
|
const DEFAULT_LOG4RS_CONFIG: &str = r##"refresh_rate: 10 seconds
|
||||||
|
appenders:
|
||||||
|
file:
|
||||||
|
kind: rolling_file
|
||||||
|
path: "log/local_mini_kms_log.log"
|
||||||
|
append: true
|
||||||
|
encoder:
|
||||||
|
pattern: "{d} - {m}{n}"
|
||||||
|
policy:
|
||||||
|
kind: compound
|
||||||
|
trigger:
|
||||||
|
kind: size
|
||||||
|
limit: 100 mb
|
||||||
|
roller:
|
||||||
|
kind: fixed_window
|
||||||
|
pattern: log/local_mini_kms_log.{}.log
|
||||||
|
count: 10
|
||||||
|
root:
|
||||||
|
level: info
|
||||||
|
appenders:
|
||||||
|
- file"##;
|
||||||
|
|
||||||
|
pub fn init_logger(log_level: Option<&str>, log4rs_file: Option<&str>) -> XResult<()> {
|
||||||
|
match log4rs_file {
|
||||||
|
None => {
|
||||||
|
let level = match env::var("LOGGER").ok().as_deref().map(get_log_level_filter) {
|
||||||
|
Some(Ok(level)) => level,
|
||||||
|
_ => log_level.map(get_log_level_filter).unwrap_or(Ok(LevelFilter::Debug))?,
|
||||||
|
};
|
||||||
|
let _ = env_logger::builder().filter_level(level).try_init();
|
||||||
|
}
|
||||||
|
Some(log4rs_file) => {
|
||||||
|
let log4rs_config_file = if log4rs_file == "#DEFAULT" {
|
||||||
|
let default_log4rs_file = "default_log4rs.yaml";
|
||||||
|
let _ = fs::metadata(default_log4rs_file).map_err(|_| {
|
||||||
|
fs::write(default_log4rs_file, DEFAULT_LOG4RS_CONFIG).map_err(|e| {
|
||||||
|
println!("Write file: {} failed: {}", default_log4rs_file, e);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
default_log4rs_file
|
||||||
|
} else {
|
||||||
|
log4rs_file
|
||||||
|
};
|
||||||
|
log4rs::init_file(log4rs_config_file, Default::default())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn get_log_level_filter(log_level: &str) -> XResult<LevelFilter> {
|
||||||
|
Ok(match log_level {
|
||||||
|
"trace" => LevelFilter::Trace,
|
||||||
|
"debug" => LevelFilter::Debug,
|
||||||
|
"info" => LevelFilter::Info,
|
||||||
|
"warn" => LevelFilter::Warn,
|
||||||
|
"error" => LevelFilter::Error,
|
||||||
|
_ => {
|
||||||
|
return simple_error!("invalid log_level");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,12 +1,18 @@
|
|||||||
use crate::db::Key;
|
use crate::db::Key;
|
||||||
use crate::do_response;
|
use crate::serve_common::{self, byte_to_multi_view_map, open_local_db, parse_request, MultipleViewValue, Result};
|
||||||
use crate::serve_common::{self, byte_to_multi_view_map, get_master_key, open_local_db, MultipleViewValue, Result};
|
|
||||||
use crate::{db, jose};
|
use crate::{db, jose};
|
||||||
use hyper::body::Buf;
|
use crate::{do_response, require_master_key};
|
||||||
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, Value};
|
use serde_json::{json, Map, Value};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
struct KeysQuery {
|
||||||
|
r#type: Option<String>,
|
||||||
|
name: Option<String>,
|
||||||
|
limit: Option<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
struct Named {
|
struct Named {
|
||||||
@@ -21,31 +27,54 @@ struct NamedValue {
|
|||||||
comment: Option<String>,
|
comment: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn list(req: Request<Body>) -> Result<Response<Body>> {
|
||||||
|
do_response!(inner_list(req).await)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn inner_list(req: Request<Body>) -> XResult<(StatusCode, Value)> {
|
||||||
|
let keys_query: KeysQuery = parse_request(req).await?;
|
||||||
|
|
||||||
|
let conn = open_local_db()?;
|
||||||
|
let keys = db::list_keys(
|
||||||
|
&conn,
|
||||||
|
keys_query.r#type.as_deref().unwrap_or("%"),
|
||||||
|
keys_query.name.as_deref().unwrap_or(""),
|
||||||
|
keys_query.limit.unwrap_or(10),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let mut map = Map::new();
|
||||||
|
map.insert("count".to_string(), keys.count.into());
|
||||||
|
let keys = Value::Array(
|
||||||
|
keys.keys
|
||||||
|
.iter()
|
||||||
|
.map(|k| k.name.to_string().into())
|
||||||
|
.collect(),
|
||||||
|
);
|
||||||
|
map.insert("keys".to_string(), keys);
|
||||||
|
serve_common::ok(Value::Object(map))
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn read(req: Request<Body>) -> Result<Response<Body>> {
|
pub async fn read(req: Request<Body>) -> Result<Response<Body>> {
|
||||||
do_response!(inner_read(req).await)
|
do_response!(inner_read(req).await)
|
||||||
}
|
}
|
||||||
|
|
||||||
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::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 conn = open_local_db()?;
|
||||||
let db_key = db::find_key(&conn, &db_key_name)?;
|
let db_key = db::find_key(&conn, &db_key_name)?;
|
||||||
|
|
||||||
let db_key_value = match db_key {
|
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,
|
Some(k) => k,
|
||||||
};
|
};
|
||||||
|
|
||||||
let key = match get_master_key() {
|
let key = require_master_key!();
|
||||||
None => return serve_common::error("status_not_ready"),
|
|
||||||
Some(key) => key,
|
|
||||||
};
|
|
||||||
let data = jose::deserialize_jwe_aes(&db_key_value.encrypted_key, &key.read())?;
|
let data = jose::deserialize_jwe_aes(&db_key_value.encrypted_key, &key.read())?;
|
||||||
drop(key);
|
drop(key);
|
||||||
|
|
||||||
@@ -60,27 +89,23 @@ 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::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 force_write = named_value.force_write.unwrap_or(false);
|
||||||
|
|
||||||
let conn = open_local_db()?;
|
let conn = open_local_db()?;
|
||||||
let db_key = db::find_key(&conn, &db_key_name)?;
|
let db_key = db::find_key(&conn, &db_key_name)?;
|
||||||
|
|
||||||
if db_key.is_some() && !force_write {
|
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 value = named_value.value.to_bytes()?;
|
||||||
let key = match get_master_key() {
|
let key = require_master_key!();
|
||||||
None => return serve_common::error("status_not_ready"),
|
|
||||||
Some(key) => key,
|
|
||||||
};
|
|
||||||
let encrypt_value = jose::serialize_jwe_aes(&value, &key.read())?;
|
let encrypt_value = jose::serialize_jwe_aes(&value, &key.read())?;
|
||||||
drop(key);
|
drop(key);
|
||||||
|
|
||||||
|
|||||||
@@ -8,18 +8,46 @@ use rust_util::{failure_and_exit, opt_result, success};
|
|||||||
pub struct CommandImpl;
|
pub struct CommandImpl;
|
||||||
|
|
||||||
impl Command for CommandImpl {
|
impl Command for CommandImpl {
|
||||||
fn name(&self) -> &str { "yubikey-init-master-key" }
|
fn name(&self) -> &str {
|
||||||
|
"yubikey-init-master-key"
|
||||||
|
}
|
||||||
|
|
||||||
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
||||||
SubCommand::with_name(self.name()).about("Local mini KMS init by Yubikey(HMAC)")
|
SubCommand::with_name(self.name())
|
||||||
.arg(Arg::with_name("yubikey-challenge").long("yubikey-challenge").short("c").takes_value(true).help("Yubikey challenge"))
|
.about("Local mini KMS init by Yubikey(HMAC)")
|
||||||
.arg(Arg::with_name("key-hex").long("key-hex").short("x").takes_value(true).help("Key(hex), for encrypt"))
|
.arg(
|
||||||
.arg(Arg::with_name("key-base64").long("key-base64").short("b").takes_value(true).help("Key(base64), for encrypt"))
|
Arg::with_name("yubikey-challenge")
|
||||||
.arg(Arg::with_name("generate-key").long("generate-key").short("K").help("Generate key"))
|
.long("yubikey-challenge")
|
||||||
|
.short("c")
|
||||||
|
.takes_value(true)
|
||||||
|
.help("Yubikey challenge"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("key-hex")
|
||||||
|
.long("key-hex")
|
||||||
|
.short("x")
|
||||||
|
.takes_value(true)
|
||||||
|
.help("Key(hex), for encrypt"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("key-base64")
|
||||||
|
.long("key-base64")
|
||||||
|
.short("b")
|
||||||
|
.takes_value(true)
|
||||||
|
.help("Key(base64), for encrypt"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("generate-key")
|
||||||
|
.long("generate-key")
|
||||||
|
.short("K")
|
||||||
|
.help("Generate key"),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
||||||
let yubikey_challenge_opt = sub_arg_matches.value_of("yubikey-challenge").map(ToString::to_string);
|
let yubikey_challenge_opt = sub_arg_matches
|
||||||
|
.value_of("yubikey-challenge")
|
||||||
|
.map(ToString::to_string);
|
||||||
|
|
||||||
let hex_value_opt = sub_arg_matches.value_of("key-hex");
|
let hex_value_opt = sub_arg_matches.value_of("key-hex");
|
||||||
let base64_value_opt = sub_arg_matches.value_of("key-base64");
|
let base64_value_opt = sub_arg_matches.value_of("key-base64");
|
||||||
@@ -29,12 +57,18 @@ impl Command for CommandImpl {
|
|||||||
failure_and_exit!("--key-hex, --key-base64 or --generate-key must assign one");
|
failure_and_exit!("--key-hex, --key-base64 or --generate-key must assign one");
|
||||||
}
|
}
|
||||||
let clear_master_key = if let Some(hex_value) = hex_value_opt {
|
let clear_master_key = if let Some(hex_value) = hex_value_opt {
|
||||||
opt_result!( hex::decode(hex_value), "Decode key-hex failed: {}")
|
opt_result!(hex::decode(hex_value), "Decode key-hex failed: {}")
|
||||||
} else if let Some(base64_value) = base64_value_opt {
|
} else if let Some(base64_value) = base64_value_opt {
|
||||||
opt_result!(STANDARD.decode(base64_value), "Decode key-base64 failed: {}")
|
opt_result!(
|
||||||
|
STANDARD.decode(base64_value),
|
||||||
|
"Decode key-base64 failed: {}"
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
let clear_master_key: [u8; 32] = random();
|
let clear_master_key: [u8; 32] = random();
|
||||||
success!("Clear master key generated: {}", hex::encode(clear_master_key));
|
success!(
|
||||||
|
"Clear master key generated: {}",
|
||||||
|
hex::encode(clear_master_key)
|
||||||
|
);
|
||||||
clear_master_key.to_vec()
|
clear_master_key.to_vec()
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -42,17 +76,22 @@ impl Command for CommandImpl {
|
|||||||
failure_and_exit!("Master key must be 32 bytes");
|
failure_and_exit!("Master key must be 32 bytes");
|
||||||
}
|
}
|
||||||
|
|
||||||
let yubikey_challenge = yubikey_challenge_opt.unwrap_or_else(
|
let yubikey_challenge = yubikey_challenge_opt.unwrap_or_else(|| {
|
||||||
|| match rpassword::prompt_password("Yubikey challenge: ") {
|
match pinentry_util::read_pin(Some("Input yubikey challenge"), Some("Challenge: ")) {
|
||||||
Ok(yubikey_challenge) => yubikey_challenge,
|
Ok(yubikey_challenge) => yubikey_challenge.get_pin().to_string(),
|
||||||
Err(e) => failure_and_exit!("Read yubikey challenge failed: {}", e),
|
Err(e) => failure_and_exit!("Read yubikey challenge failed: {}", e),
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
let challenge_key = opt_result!(
|
let challenge_key = opt_result!(
|
||||||
yubikey_hmac::yubikey_challenge_as_32_bytes(yubikey_challenge.as_bytes()), "Yubikey challenge failed: {}");
|
yubikey_hmac::yubikey_challenge_as_32_bytes(yubikey_challenge.as_bytes()),
|
||||||
|
"Yubikey challenge failed: {}"
|
||||||
|
);
|
||||||
|
|
||||||
let encrypted_master_key = opt_result!(jose::serialize_jwe_aes(&clear_master_key, &challenge_key), "Encrypt master key failed: {}");
|
let encrypted_master_key = opt_result!(
|
||||||
|
jose::serialize_jwe_aes(&clear_master_key, &challenge_key),
|
||||||
|
"Encrypt master key failed: {}"
|
||||||
|
);
|
||||||
success!("Encrypted master key: {}", encrypted_master_key);
|
success!("Encrypted master key: {}", encrypted_master_key);
|
||||||
|
|
||||||
Ok(Some(0))
|
Ok(Some(0))
|
||||||
|
|||||||
Reference in New Issue
Block a user