diff --git a/Cargo.lock b/Cargo.lock index 9015d5b..ccc05b4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + [[package]] name = "ahash" version = "0.7.6" @@ -13,6 +19,15 @@ dependencies = [ "version_check", ] +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + [[package]] name = "ansi_term" version = "0.12.1" @@ -22,6 +37,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "anyhow" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb07d2053ccdbe10e2af2995a2f116c1330396493dc1269f6a91d0ae82e19704" + [[package]] name = "atty" version = "0.2.14" @@ -39,6 +60,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + [[package]] name = "bitflags" version = "1.3.2" @@ -51,6 +78,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" + [[package]] name = "cfg-if" version = "1.0.0" @@ -72,6 +105,15 @@ dependencies = [ "vec_map", ] +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + [[package]] name = "dirs-next" version = "2.0.0" @@ -105,12 +147,37 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" +[[package]] +name = "flate2" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "futures-channel" version = "0.3.21" @@ -287,6 +354,24 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" +[[package]] +name = "josekit" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee6af62ad98bdf699ad2ecc8323479a1fdc7aa5faa6043d93119d83f6c5fca8" +dependencies = [ + "anyhow", + "base64", + "flate2", + "once_cell", + "openssl", + "regex", + "serde", + "serde_json", + "thiserror", + "time", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -313,9 +398,11 @@ dependencies = [ name = "local-mini-kms" version = "0.1.0" dependencies = [ + "base64", "clap", "hex", "hyper", + "josekit", "lazy_static", "rusqlite", "rust_util", @@ -350,6 +437,15 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +[[package]] +name = "miniz_oxide" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" +dependencies = [ + "adler", +] + [[package]] name = "mio" version = "0.8.4" @@ -372,12 +468,60 @@ dependencies = [ "libc", ] +[[package]] +name = "num_threads" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +dependencies = [ + "libc", +] + [[package]] name = "once_cell" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" +[[package]] +name = "openssl" +version = "0.10.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "618febf65336490dfcf20b73f885f5651a0c89c64c2d4a8c3662585a70bf5bd0" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-sys" +version = "0.9.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5f9bd0c2710541a3cda73d6f9ac4f1b240de4ae261065d309dbe73d9dceb42f" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "parking_lot" version = "0.12.1" @@ -457,6 +601,23 @@ dependencies = [ "thiserror", ] +[[package]] +name = "regex" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" + [[package]] name = "rusqlite" version = "0.28.0" @@ -528,6 +689,7 @@ version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7" dependencies = [ + "indexmap", "itoa", "ryu", "serde", @@ -634,6 +796,16 @@ dependencies = [ "syn", ] +[[package]] +name = "time" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72c91f41dcb2f096c05f0873d667dceec1087ce5bcf984ec8ffb19acddbb3217" +dependencies = [ + "libc", + "num_threads", +] + [[package]] name = "tokio" version = "1.19.2" diff --git a/Cargo.toml b/Cargo.toml index b13a612..c0da46f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,10 +8,12 @@ edition = "2021" [dependencies] clap = "2.33" hex = "0.4" +base64 = "0.13.0" lazy_static = "1.4.0" serde_derive = "1.0" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" +josekit = "0.8.1" rust_util = { version = "0.6", features = ["use_clap"] } tokio = { version = "1.19", features = ["full"] } hyper = { version = "0.14.20", features = ["client", "server", "tcp", "http1", "http2"] } diff --git a/src/db.rs b/src/db.rs index 001d7d8..992c6c9 100644 --- a/src/db.rs +++ b/src/db.rs @@ -1,5 +1,12 @@ use rusqlite::{Connection, params}; -use rust_util::{debugging, information, opt_result, success, XResult}; +use rust_util::{debugging, information, opt_result, simple_error, success, XResult}; + +pub const DEFAULT_MASTER_KEY_VERIFICATION_KEY: &'static str = "__master_verification_key"; + +pub struct Key { + name: String, + encrypted_key: String, +} pub fn open_db(db: &str) -> XResult { let con = opt_result!(Connection::open(db), "Open sqlite db: {}, failed: {}", db); @@ -24,4 +31,27 @@ pub fn init_db(conn: &Connection) -> XResult { "##, ())?; success!("Table keys created"); Ok(true) +} + +pub fn insert_key(conn: &Connection, key: &Key) -> XResult<()> { + let _ = conn.execute( + "INSERT INTO keys (name, value) VALUES (?1, ?2)", + (&key.name, &key.encrypted_key), + )?; + Ok(()) +} + +pub fn find_key(conn: &Connection, name: &str) -> XResult> { + let mut stmt = conn.prepare("SELECT id, name, value FROM keys WHERE name = ?1")?; + let mut key_iter = stmt.query_map(params![name], |row| { + Ok(Key { + name: row.get(1)?, + encrypted_key: row.get(2)?, + }) + })?; + match key_iter.next() { + None => Ok(None), + Some(Ok(r)) => Ok(Some(r)), + Some(Err(e)) => simple_error!("Find key failed: {}", e), + } } \ No newline at end of file diff --git a/src/jose.rs b/src/jose.rs new file mode 100644 index 0000000..326e63f --- /dev/null +++ b/src/jose.rs @@ -0,0 +1,57 @@ +use josekit::jwe; +use josekit::jwe::alg::aeskw::AeskwJweAlgorithm; +use josekit::jwe::alg::rsaes::RsaesJweAlgorithm; +use josekit::jwe::JweHeader; +use josekit::jwk::{Jwk, KeyPair}; +use josekit::jwk::alg::rsa::RsaKeyPair; +use rust_util::XResult; + +pub fn generate_rsa_key(bits: u32) -> XResult { + Ok(RsaKeyPair::generate(bits)?) +} + +pub fn serialize_jwe_rsa(payload: &[u8], jwk: &Jwk) -> XResult { + let mut header = JweHeader::new(); + header.set_content_encryption("A256GCM"); + let encrypter = RsaesJweAlgorithm::RsaOaep.encrypter_from_jwk(&jwk)?; + Ok(jwe::serialize_compact(payload, &header, &encrypter)?) +} + +pub fn deserialize_jwe_rsa(jwe: &str, jwk: &Jwk) -> XResult<(Vec, JweHeader)> { + let decrypter = RsaesJweAlgorithm::RsaOaep.decrypter_from_jwk(jwk)?; + Ok(jwe::deserialize_json(jwe, &decrypter)?) +} + +pub fn serialize_jwe_aes(payload: &[u8], key: &[u8]) -> XResult { + let mut header = JweHeader::new(); + header.set_content_encryption("A256GCM"); + let encrypter = AeskwJweAlgorithm::A256kw.encrypter_from_bytes(key)?; + Ok(jwe::serialize_compact(payload, &header, &encrypter)?) +} + +pub fn deserialize_jwe_aes(jwe: &str, key: &[u8]) -> XResult<(Vec, JweHeader)> { + let decrypter = AeskwJweAlgorithm::A192kw.decrypter_from_bytes(key)?; + Ok(jwe::deserialize_json(jwe, &decrypter)?) +} + +#[test] +fn a() { + let k = generate_rsa_key(4096).unwrap(); + let k = k.to_jwk_key_pair(); + let kk = k.to_public_key().unwrap(); + + println!("{:?}", k); + println!("{:?}", kk); + + let mut header = JweHeader::new(); + header.set_content_encryption("A256GCM"); + let encrypter = RsaesJweAlgorithm::RsaOaep.encrypter_from_jwk(&kk).unwrap(); + let payload = "helloworld"; + let r = jwe::serialize_compact(payload.as_bytes(), &header, &encrypter); + + println!("{:?}", r); + + let k = "abcdefghijklmnopqrstuvwxyz123456"; + let t = serialize_jwe_aes(payload.as_bytes(), k.as_bytes()); + println!("{:?}", t); +} diff --git a/src/main.rs b/src/main.rs index 341eade..4015375 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ use rust_util::{failure_and_exit, information}; use rust_util::util_clap::{Command, CommandError}; mod db; +mod jose; mod cli; mod serve; diff --git a/src/serve.rs b/src/serve.rs index ad02f0b..cbf8891 100644 --- a/src/serve.rs +++ b/src/serve.rs @@ -1,11 +1,17 @@ use std::sync::RwLock; + use clap::{App, Arg, ArgMatches, SubCommand}; -use hyper::{Body, Client, Method, Request, Response, Server, StatusCode}; +use hyper::{Body, Client, header, Method, Request, Response, Server, StatusCode}; +use hyper::body::Buf; use hyper::client::HttpConnector; use hyper::service::{make_service_fn, service_fn}; -use rust_util::{failure_and_exit, iff, information, success}; +use josekit::jwk::alg::rsa::RsaKeyPair; +use rust_util::{failure_and_exit, iff, information, success, XResult}; use rust_util::util_clap::{Command, CommandError}; -use crate::db; +use serde::{Deserialize, Serialize}; +use serde_json::{json, Value}; + +use crate::jose; type GenericError = Box; type Result = std::result::Result; @@ -23,6 +29,12 @@ impl Command for CommandImpl { } fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError { + match init_instance() { + Ok(true) => success!("Init server success"), + Ok(false) => failure_and_exit!("SHOULD NOT HAPPEN, server already init"), + Err(e) => failure_and_exit!("Init server failed: {}", e), + } + let listen = sub_arg_matches.value_of("listen").expect("Get argument listen error"); let rt = tokio::runtime::Runtime::new().expect("Create tokio runtime error"); rt.block_on(async { @@ -48,41 +60,131 @@ impl Command for CommandImpl { } // ref: https://github.com/hyperium/hyper/blob/master/examples/web_api.rs +// ref: https://crates.io/crates/rusqlite async fn response_requests( req: Request, _client: Client, ) -> Result> { match (req.method(), req.uri().path()) { - // (&Method::GET, "/") | (&Method::GET, "/index.html") => Ok(Response::new(INDEX.into())), - // (&Method::GET, "/test.html") => client_request_response(&client).await, + (&Method::POST, "/init") => init(req).await, + (&Method::POST, "/decrypt") => decrypt(req).await, + (&Method::POST, "/encrypt") => encrypt(req).await, (&Method::GET, "/status") => status().await, (&Method::GET, "/version") => get_version().await, - _ => { - Ok(Response::builder() - .status(StatusCode::NOT_FOUND).body(NOTFOUND.into()) - .expect("Response not found error")) - } + _ => Ok(Response::builder().status(StatusCode::NOT_FOUND).body(NOTFOUND.into())?), } } // ------------------------------------------------------------------------------------------------- struct MemoryKey { - master_key: Vec, + instance_rsa_key_pair: RsaKeyPair, + master_key: Option>, } lazy_static::lazy_static! { - static ref STATUS_RW_LOCK: RwLock = RwLock::new(false); + static ref STATUP_RW_LOCK: RwLock> = RwLock::new(None); +} + +fn init_instance() -> XResult { + let mut startup_rw_lock = STATUP_RW_LOCK.write().expect("Lock write startup rw lock error"); + match &*startup_rw_lock { + Some(_) => Ok(false), + None => { + let memory_key = MemoryKey { + instance_rsa_key_pair: jose::generate_rsa_key(4096)?, + master_key: None, + }; + *startup_rw_lock = Some(memory_key); + Ok(true) + } + } +} + +async fn decrypt(req: Request) -> Result> { + let whole_body = hyper::body::aggregate(req).await?; + let data: serde_json::Value = serde_json::from_reader(whole_body.reader())?; + Ok(Response::builder().body(format!("{}", data).into())?) +} + +async fn encrypt(req: Request) -> Result> { + let whole_body = hyper::body::aggregate(req).await?; + let data: serde_json::Value = serde_json::from_reader(whole_body.reader())?; + Ok(Response::builder().body(format!("{}", data).into())?) +} + +#[derive(Serialize, Deserialize)] +struct InitRequest { + clear_master_key_hex: Option, + clear_master_key_base64: Option, + encrypted_master_key: Option, +} + +async fn init(req: Request) -> Result> { + let whole_body = hyper::body::aggregate(req).await?; + let init_request: InitRequest = serde_json::from_reader(whole_body.reader())?; + + let mut startup_rw_lock = STATUP_RW_LOCK.write().expect("Lock read startup rw lock error"); + let (status_code, body) = match &*startup_rw_lock { + None => (StatusCode::INTERNAL_SERVER_ERROR, json!({ "error": "internal_error", "error_message": "not init " })), + Some(memory_key) => match memory_key.master_key { + Some(_) => (StatusCode::BAD_REQUEST, json!({ "error": "bad_request", "error_message": "already init " })), + None => (StatusCode::OK, Value::Null), + }, + }; + if status_code != StatusCode::OK { + return Ok(Response::builder().status(status_code).body(serde_json::to_string_pretty(&body)?.into())?); + } + + let (status_code, body) = if let Some(clear_master_key_base64) = init_request.clear_master_key_base64 { + let clear_master_key = base64::decode(clear_master_key_base64)?; + if clear_master_key.len() != 32 { + (StatusCode::BAD_REQUEST, json!({ "error": "bad_request", "error_message": "bad clear_master_key_hex length" })) + } else { + if let Some(k) = &mut *startup_rw_lock { + k.master_key = Some(clear_master_key); + } + (StatusCode::OK, json!({})) + } + } else if let Some(clear_master_key_hex) = init_request.clear_master_key_hex { + let clear_master_key = hex::decode(clear_master_key_hex)?; + if clear_master_key.len() != 32 { + (StatusCode::BAD_REQUEST, json!({ "error": "bad_request", "error_message": "bad clear_master_key_hex length" })) + } else { + if let Some(k) = &mut *startup_rw_lock { + k.master_key = Some(clear_master_key); + } + (StatusCode::OK, json!({})) + } + } else if let Some(encrypted_master_key) = init_request.encrypted_master_key { + // TODO ... + (StatusCode::BAD_REQUEST, json!({ "error": "not_implement", "error_message": "not_implement" })) + } else { + (StatusCode::BAD_REQUEST, json!({ "error": "bad_request", "error_message": "master key is not provided" })) + }; + Ok(Response::builder().status(status_code).body(serde_json::to_string_pretty(&body)?.into())?) } async fn status() -> Result> { - let status_rw_lock = STATUS_RW_LOCK.read().expect("Lock read status rw lock error"); - let status_rw_lock_value = *status_rw_lock; - Ok(Response::builder().body(format!("{}\n", iff!(status_rw_lock_value, "init", "uninit")).into()).expect("x")) + let startup_rw_lock = STATUP_RW_LOCK.read().expect("Lock read startup rw lock error"); + let body = match &*startup_rw_lock { + None => json!({ "status": "n/a" }), + Some(memory_key) => match memory_key.master_key { + None => json!({ + "status": "not-ready", + "instance_public_key_jwk": memory_key.instance_rsa_key_pair.to_jwk_key_pair().to_public_key()?, + }), + Some(_) => json!({ + "status": "ready", + "instance_public_key_jwk": memory_key.instance_rsa_key_pair.to_jwk_key_pair().to_public_key()?, + }), + } + }; + Ok(Response::builder().body(serde_json::to_string_pretty(&body)?.into())?) } async fn get_version() -> Result> { Ok(Response::builder().body(format!( "{} - {}\n", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION") - ).into()).expect("Response get_version error")) + ).into())?) }