diff --git a/Cargo.toml b/Cargo.toml index 7cd113a27..cec3de321 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ members = [ "venus", "ceres", "libra", + "relay", ] exclude = ["craft"] resolver = "1" diff --git a/gateway/src/git_protocol/ssh.rs b/gateway/src/git_protocol/ssh.rs index c96fbef5e..64ddc4590 100644 --- a/gateway/src/git_protocol/ssh.rs +++ b/gateway/src/git_protocol/ssh.rs @@ -19,7 +19,7 @@ use ceres::protocol::{SmartProtocol, TransportProtocol}; use jupiter::context::Context; type ClientMap = HashMap<(usize, ChannelId), Channel>; - +#[allow(dead_code)] #[derive(Clone)] pub struct SshServer { pub client_pubkey: Arc, diff --git a/gateway/src/model/query.rs b/gateway/src/model/query.rs index 6963ccec3..51a720af8 100644 --- a/gateway/src/model/query.rs +++ b/gateway/src/model/query.rs @@ -1,5 +1,5 @@ use serde::Deserialize; - +#[allow(dead_code)] #[derive(Debug, Deserialize)] pub struct DirectoryQuery { #[serde(default)] // Use default value if not provided in the query string @@ -7,7 +7,7 @@ pub struct DirectoryQuery { #[serde(default = "default_path")] pub repo_path: String, } - +#[allow(dead_code)] #[derive(Debug, Deserialize)] pub struct CodePreviewQuery { #[serde(default)] diff --git a/libra/src/internal/config.rs b/libra/src/internal/config.rs index f45bf4a45..a88e9f770 100644 --- a/libra/src/internal/config.rs +++ b/libra/src/internal/config.rs @@ -16,7 +16,7 @@ pub struct RemoteConfig { pub name: String, pub url: String, } - +#[allow(dead_code)] pub struct BranchConfig { pub name: String, pub merge: String, diff --git a/mercury/src/internal/pack/encode.rs b/mercury/src/internal/pack/encode.rs index 402cfe49d..bdc7b0bbe 100644 --- a/mercury/src/internal/pack/encode.rs +++ b/mercury/src/internal/pack/encode.rs @@ -236,7 +236,7 @@ impl PackEncoder { #[cfg(test)] mod tests { - use std::{io::Cursor, path::PathBuf, usize}; + use std::{io::Cursor, path::PathBuf}; use crate::internal::object::blob::Blob; use crate::internal::pack::Pack; diff --git a/relay/Cargo.toml b/relay/Cargo.toml new file mode 100644 index 000000000..12f11b8ee --- /dev/null +++ b/relay/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "replay" +version = "0.1.0" +edition = "2021" + +[dependencies] +rusty_vault = "0.1.0" +serde_json = "1.0.117" +go-defer = "0.1.0" +openssl = "0.10.63" +hex = "0.4.3" +lazy_static = "1.4.0" +secp256k1 = { version = "0.27.0", features = ["serde", "bitcoin-hashes", "bitcoin-hashes-std", "rand"] } +bs58 = "0.5.1" +serde = { version = "1.0.117", features = ["derive"] } \ No newline at end of file diff --git a/relay/src/lib.rs b/relay/src/lib.rs new file mode 100644 index 000000000..a3ab9f87d --- /dev/null +++ b/relay/src/lib.rs @@ -0,0 +1,44 @@ +use crate::vault::{read_secret, write_secret}; + +pub mod pki; +pub mod vault; +pub mod nostr; + +/// Initialize the Nostr ID if it's not found. +/// - return: `(Nostr ID, secret_key)` +/// - You can get `Public Key` by just `base58::decode(nostr)` +pub fn init() -> (String, String) { + let mut id = read_secret("id").unwrap(); + if id.is_none() { + println!("Nostr ID not found, generating new one..."); + let (nostr, (secret_key, _)) = nostr::generate_nostr_id(); + let data = serde_json::json!({ + "nostr": nostr, + "secret_key": secret_key.display_secret().to_string(), + }) + .as_object() + .unwrap() + .clone(); + write_secret("id", Some(data)).unwrap_or_else(|e| { + panic!("Failed to write Nostr ID: {:?}", e); + }); + id = read_secret("id").unwrap(); + } + let id_data = id.unwrap().data.unwrap(); + ( + id_data["nostr"].as_str().unwrap().to_string(), + id_data["secret_key"].as_str().unwrap().to_string(), + ) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_init() { + let id = init(); + println!("Nostr ID: {:?}", id.0); + println!("Secret Key: {:?}", id.1); // private key + } +} \ No newline at end of file diff --git a/relay/src/main.rs b/relay/src/main.rs new file mode 100644 index 000000000..e7a11a969 --- /dev/null +++ b/relay/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/relay/src/nostr.rs b/relay/src/nostr.rs new file mode 100644 index 000000000..c8a1922cf --- /dev/null +++ b/relay/src/nostr.rs @@ -0,0 +1,34 @@ +use secp256k1::{PublicKey, rand, Secp256k1, SecretKey}; + +pub fn generate_nostr_id() -> (String, (SecretKey, PublicKey)) { + let secp = Secp256k1::new(); + let secret_key = SecretKey::new(&mut rand::thread_rng()); + let public_key = secret_key.public_key(&secp); + let nostr = bs58::encode(public_key.serialize()).into_string(); + + (nostr, (secret_key, public_key)) +} + +#[cfg(test)] +mod tests { + use secp256k1::Message; + use super::*; + + #[test] + fn test_generate_nostr_id() { + let (nostr, keypair) = generate_nostr_id(); + println!("nostr: {:?}", nostr); + println!("keypair: {:?}", keypair); + let secret_key = keypair.0; + let public_key = keypair.1; + + let nostr_decode = bs58::decode(&nostr).into_vec().unwrap(); + assert_eq!(nostr_decode, public_key.serialize().to_vec()); + assert_eq!(PublicKey::from_slice(&nostr_decode).unwrap(), public_key); + // verify + let secp = Secp256k1::new(); + let message = Message::from_slice(&[0xab; 32]).expect("32 bytes"); + let sig = secp.sign_ecdsa(&message, &secret_key); + assert_eq!(secp.verify_ecdsa(&message, &sig, &public_key), Ok(())); + } +} \ No newline at end of file diff --git a/relay/src/pki.rs b/relay/src/pki.rs new file mode 100644 index 000000000..2944f23af --- /dev/null +++ b/relay/src/pki.rs @@ -0,0 +1,483 @@ +use std::cmp::Ordering; +use std::sync::{Arc, RwLock}; +use std::time::{SystemTime, UNIX_EPOCH}; + +use lazy_static::lazy_static; +use openssl::asn1::Asn1Time; +use openssl::x509::X509; +use rusty_vault::core::Core; +use serde_json::{json, Value}; + +use super::vault::{CoreInfo, CORE, read_api, write_api}; + +const ROLE: &str = "test"; +// Automatically initialize CA when you first use it +lazy_static! { + static ref CA: CoreInfo = { + let c = CORE.clone(); + // init CA if not + if read_api(&c.core.read().unwrap(), &c.token, "pki/ca/pem").is_err() { // err = not found + let token = &c.token; + config_ca(c.core.clone(), token); + generate_root(c.core.clone(), token, false); + config_role(c.core.clone(), token, json!({ // TODO You may want to customize this + "ttl": "60d", + "max_ttl": "365d", + "key_type": "rsa", + "key_bits": 4096, + "country": "CN", + "province": "Beijing", + "locality": "Beijing", + "organization": "OpenAtom-Mega", + "no_store": false, + })); + } + c + }; +} + +fn config_ca(core: Arc>, token: &str) { + let core = core.read().unwrap(); + + // mount pki backend to path: pki/ + let mount_data = json!({ + "type": "pki", + }) + .as_object() + .unwrap() + .clone(); + + let resp = write_api(&core, token, "sys/mounts/pki/", Some(mount_data)); + assert!(resp.is_ok()); +} + +/// - `data`: see [RoleEntry](rusty_vault::modules::pki::path_roles) +pub fn config_role(core: Arc>, token: &str, data: Value) { + let core = core.read().unwrap(); + + let role_data = data.as_object() + .expect("`data` must be a JSON object") + .clone(); + + // config role + let result = write_api(&core, token, &format!("pki/roles/{}", ROLE), Some(role_data)); + assert!(result.is_ok()); +} + +/// generate root cert, so that you can read from `pki/ca/pem` +/// - if `exported` is true, then the response will contain `private key` +fn generate_root(core: Arc>, token: &str, exported: bool) { + let core = core.read().unwrap(); + + let key_type = "rsa"; + let key_bits = 4096; + let common_name = "mega-ca"; + let req_data = json!({ + "common_name": common_name, + "ttl": "365d", + "country": "cn", + "key_type": key_type, + "key_bits": key_bits, + }) + .as_object() + .unwrap() + .clone(); + + let resp = write_api( + &core, + token, + format!("pki/root/generate/{}", if exported { "exported" } else { "internal" }).as_str(), + Some(req_data), + ); + assert!(resp.is_ok()); + // let resp_body = resp.unwrap(); + // let data = resp_body.unwrap().data; + // let key_data = data.unwrap(); + // println!("generate root result: {:?}", key_data); + + // let resp_ca_pem = read_api(&core, token, "pki/ca/pem"); + // let resp_ca_pem_cert_data = resp_ca_pem.unwrap().unwrap().data.unwrap(); + // + // println!("resp_ca_pem_cert_data: {:?}", resp_ca_pem_cert_data); +} + +/// issue certificate +/// - `data`: see [issue_path](rusty_vault::modules::pki::path_issue) +/// - return: `(cert_pem, private_key)` +pub fn issue_cert(data: Value) -> (String, String) { + let core = CA.core.read().unwrap(); + let token = &CA.token; + + // let dns_sans = ["test.com", "a.test.com", "b.test.com"]; + let issue_data = data.as_object() + .expect("`data` must be a JSON object") + .clone(); + + // issue cert + let resp = write_api(&core, token, &format!("pki/issue/{}", ROLE), Some(issue_data)); + assert!(resp.is_ok()); + let resp_body = resp.unwrap(); + let cert_data = resp_body.unwrap().data.unwrap(); + // println!("cert_data: {:?}", cert_data); + + ( + cert_data["certificate"].as_str().unwrap().to_owned(), // TODO may add root cert (chain) in it + cert_data["private_key"].as_str().unwrap().to_owned(), + ) +} + +/// Verify certificate: time & signature +pub fn verify_cert(cert_pem: &[u8]) -> bool { + let ca_cert = X509::from_pem(get_root_cert().as_ref()).unwrap(); + + let cert = X509::from_pem(cert_pem).unwrap(); + // verify time + let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() as i64; + let now = Asn1Time::from_unix(now).unwrap(); + let not_before = cert.not_before(); + let not_after = cert.not_after(); + match now.compare(not_before) { + Ok(Ordering::Less) | Err(_) => return false, + _ => {} + } + match now.compare(not_after) { + Ok(Ordering::Greater) | Err(_) => return false, + _ => {} + } + + // verify signature + cert.verify(&ca_cert.public_key().unwrap()).unwrap() +} + +/// Get root certificate of CA +pub fn get_root_cert() -> String { + let core = CA.core.read().unwrap(); + + let resp_ca_pem = read_api(&core, &CA.token, "pki/ca/pem").unwrap().unwrap(); + let ca_data = resp_ca_pem.data.unwrap(); + + ca_data["certificate"].as_str().unwrap().to_owned() +} + +#[cfg(test)] +mod tests { + use std::fs; + use std::io::Write; + use super::*; + + #[test] + fn test_pki_issue() { + let (cert_pem, private_key) = issue_cert(json!({ + "ttl": "10d", + "common_name": "oqpXWgEhXa1WDqMWBnpUW4jvrxGqJKVuJATy4MSPdKNS", //nostr id + // "alt_names": "a.test.com,b.test.com", + })); + + println!("cert_pem: {}", cert_pem); + println!("private_key: {}", private_key); + + assert!(verify_cert(cert_pem.as_ref())); + + let mut file = fs::File::create("/tmp/cert.crt").unwrap(); + file.write_all(cert_pem.as_ref()).unwrap(); + } +} + +#[cfg(test)] +mod tests_raw { + use std::{ + collections::HashMap, + default::Default, + env, fs, + sync::{Arc, RwLock}, + time::{SystemTime, UNIX_EPOCH}, + }; + use std::io::Write; + + use go_defer::defer; + use openssl::{asn1::Asn1Time, ec::EcKey, nid::Nid, pkey::PKey, rsa::Rsa, x509::X509}; + use rusty_vault::{ + core::{Core, SealConfig}, + logical::{Operation, Request}, + storage::{barrier_aes_gcm, physical}, + }; + use rusty_vault::errors::RvError; + use rusty_vault::logical::Response; + use serde_json::{json, Map, Value}; + + fn test_read_api(core: &Core, token: &str, path: &str, is_ok: bool) -> Result, RvError> { + let mut req = Request::new(path); + req.operation = Operation::Read; + req.client_token = token.to_string(); + let resp = core.handle_request(&mut req); + assert_eq!(resp.is_ok(), is_ok); + resp + } + + fn test_write_api( + core: &Core, + token: &str, + path: &str, + is_ok: bool, + data: Option>, + ) -> Result, RvError> { + let mut req = Request::new(path); + req.operation = Operation::Write; + req.client_token = token.to_string(); + req.body = data; + + let resp = core.handle_request(&mut req); + println!("path: {}, req.body: {:?}", path, req.body); + assert_eq!(resp.is_ok(), is_ok); + resp + } + + fn test_pki_config_ca(core: Arc>, token: &str) { + let core = core.read().unwrap(); + + // mount pki backend to path: pki/ + let mount_data = json!({ + "type": "pki", + }) + .as_object() + .unwrap() + .clone(); + + let resp = test_write_api(&core, token, "sys/mounts/pki/", true, Some(mount_data)); + assert!(resp.is_ok()); + } + + fn test_pki_config_role(core: Arc>, token: &str) { + let core = core.read().unwrap(); + + let role_data = json!({ + "ttl": "60d", + "max_ttl": "365d", + "key_type": "rsa", + "key_bits": 4096, + "country": "CN", + "province": "Beijing", + "locality": "Beijing", + "organization": "OpenAtom", + "no_store": false, + }) + .as_object() + .unwrap() + .clone(); + + // config role + assert!(test_write_api(&core, token, "pki/roles/test", true, Some(role_data)).is_ok()); + let resp = test_read_api(&core, token, "pki/roles/test", true); + assert!(resp.as_ref().unwrap().is_some()); + let resp = resp.unwrap(); + assert!(resp.is_some()); + let data = resp.unwrap().data; + assert!(data.is_some()); + let role_data = data.unwrap(); + println!("role_data: {:?}", role_data); + assert_eq!(role_data["ttl"].as_u64().unwrap(), 60 * 24 * 60 * 60); + assert_eq!(role_data["max_ttl"].as_u64().unwrap(), 365 * 24 * 60 * 60); + assert_eq!(role_data["not_before_duration"].as_u64().unwrap(), 30); + assert_eq!(role_data["key_type"].as_str().unwrap(), "rsa"); + assert_eq!(role_data["key_bits"].as_u64().unwrap(), 4096); + assert_eq!(role_data["country"].as_str().unwrap(), "CN"); + assert_eq!(role_data["province"].as_str().unwrap(), "Beijing"); + assert_eq!(role_data["locality"].as_str().unwrap(), "Beijing"); + assert_eq!(role_data["organization"].as_str().unwrap(), "OpenAtom"); + assert!(!role_data["no_store"].as_bool().unwrap()); + } + + fn test_pki_generate_root(core: Arc>, token: &str, exported: bool, is_ok: bool) { + let core = core.read().unwrap(); + + let key_type = "rsa"; + let key_bits = 4096; + let common_name = "test-ca"; + let req_data = json!({ + "common_name": common_name, + "ttl": "365d", + "country": "cn", + "key_type": key_type, + "key_bits": key_bits, + }) + .as_object() + .unwrap() + .clone(); + // println!("generate root req_data: {:?}, is_ok: {}", req_data, is_ok); + let resp = test_write_api( + &core, + token, + format!("pki/root/generate/{}", if exported { "exported" } else { "internal" }).as_str(), + is_ok, + Some(req_data), + ); + if !is_ok { + return; + } + let resp_body = resp.unwrap(); + assert!(resp_body.is_some()); + let data = resp_body.unwrap().data; + assert!(data.is_some()); + let key_data = data.unwrap(); + // println!("generate root result: {:?}", key_data); + + let resp_ca_pem = test_read_api(&core, token, "pki/ca/pem", true); + let resp_ca_pem_cert_data = resp_ca_pem.unwrap().unwrap().data.unwrap(); + + let ca_cert = X509::from_pem(resp_ca_pem_cert_data["certificate"].as_str().unwrap().as_bytes()).unwrap(); + let subject = ca_cert.subject_name(); + let cn = subject.entries_by_nid(Nid::COMMONNAME).next().unwrap(); + assert_eq!(cn.data().as_slice(), common_name.as_bytes()); + + let not_after = Asn1Time::days_from_now(365).unwrap(); + let ttl_diff = ca_cert.not_after().diff(¬_after); + assert!(ttl_diff.is_ok()); + let ttl_diff = ttl_diff.unwrap(); + assert_eq!(ttl_diff.days, 0); + + if exported { + assert!(key_data["private_key_type"].as_str().is_some()); + assert_eq!(key_data["private_key_type"].as_str().unwrap(), key_type); + assert!(key_data["private_key"].as_str().is_some()); + let private_key_pem = key_data["private_key"].as_str().unwrap(); + match key_type { + "rsa" => { + let rsa_key = Rsa::private_key_from_pem(private_key_pem.as_bytes()); + assert!(rsa_key.is_ok()); + assert_eq!(rsa_key.unwrap().size() * 8, key_bits); + } + "ec" => { + let ec_key = EcKey::private_key_from_pem(private_key_pem.as_bytes()); + assert!(ec_key.is_ok()); + assert_eq!(ec_key.unwrap().group().degree(), key_bits); + } + _ => {} + } + } else { + assert!(key_data.get("private_key").is_none()); + } + } + + fn test_pki_issue_cert_by_generate_root(core: Arc>, token: &str) { + let core = core.read().unwrap(); + + let dns_sans = ["test.com", "a.test.com", "b.test.com"]; + let issue_data = json!({ + "ttl": "10d", + "common_name": "test.com", + "alt_names": "a.test.com,b.test.com", + }) + .as_object() + .unwrap() + .clone(); + + // issue cert + let resp = test_write_api(&core, token, "pki/issue/test", true, Some(issue_data)); + assert!(resp.is_ok()); + let resp_body = resp.unwrap(); + assert!(resp_body.is_some()); + let data = resp_body.unwrap().data; + assert!(data.is_some()); + let cert_data = data.unwrap(); + println!("issue cert result: {:?}", cert_data["certificate"]); + + let mut file = fs::File::create("/tmp/cert.crt").unwrap(); + file.write_all(cert_data["certificate"].as_str().unwrap().as_ref()).unwrap(); + + let cert = X509::from_pem(cert_data["certificate"].as_str().unwrap().as_bytes()).unwrap(); + let alt_names = cert.subject_alt_names(); + assert!(alt_names.is_some()); + let alt_names = alt_names.unwrap(); + assert_eq!(alt_names.len(), dns_sans.len()); + for alt_name in alt_names { + assert!(dns_sans.contains(&alt_name.dnsname().unwrap())); + } + assert_eq!(cert_data["private_key_type"].as_str().unwrap(), "rsa"); + let priv_key = PKey::private_key_from_pem(cert_data["private_key"].as_str().unwrap().as_bytes()).unwrap(); + assert_eq!(priv_key.bits(), 4096); + assert!(priv_key.public_eq(&cert.public_key().unwrap())); + let serial_number = cert.serial_number().to_bn().unwrap(); + let serial_number_hex = serial_number.to_hex_str().unwrap(); + assert_eq!( + cert_data["serial_number"].as_str().unwrap().replace(':', "").to_lowercase().as_str(), + serial_number_hex.to_lowercase().as_str() + ); + let expiration_time = Asn1Time::from_unix(cert_data["expiration"].as_i64().unwrap()).unwrap(); + let ttl_compare = cert.not_after().compare(&expiration_time); + assert!(ttl_compare.is_ok()); + assert_eq!(ttl_compare.unwrap(), std::cmp::Ordering::Equal); + let now_timestamp = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(); + let expiration_ttl = cert_data["expiration"].as_u64().unwrap(); + let ttl = expiration_ttl - now_timestamp; + let expect_ttl = 10 * 24 * 60 * 60; + assert!(ttl <= expect_ttl); + assert!((ttl + 10) > expect_ttl); + + let authority_key_id = cert.authority_key_id(); + assert!(authority_key_id.is_some()); + + println!("authority_key_id: {}", hex::encode(authority_key_id.unwrap().as_slice())); + + let resp_ca_pem = test_read_api(&core, token, "pki/ca/pem", true); + let resp_ca_pem_cert_data = resp_ca_pem.unwrap().unwrap().data.unwrap(); + + let ca_cert = X509::from_pem(resp_ca_pem_cert_data["certificate"].as_str().unwrap().as_bytes()).unwrap(); + let subject = ca_cert.subject_name(); + let cn = subject.entries_by_nid(Nid::COMMONNAME).next().unwrap(); + assert_eq!(cn.data().as_slice(), "test-ca".as_bytes()); + println!("ca subject_key_id: {}", hex::encode(ca_cert.subject_key_id().unwrap().as_slice())); + assert_eq!(ca_cert.subject_key_id().unwrap().as_slice(), authority_key_id.unwrap().as_slice()); + } + + #[test] + fn test_pki_module() { + let dir = env::temp_dir().join("rusty_vault_pki_module"); + assert!(fs::create_dir(&dir).is_ok()); + defer! ( + assert!(fs::remove_dir_all(&dir).is_ok()); + ); + + let mut root_token = String::new(); + println!("root_token: {:?}", root_token); + + let mut conf: HashMap = HashMap::new(); + conf.insert("path".to_string(), Value::String(dir.to_string_lossy().into_owned())); + + let backend = physical::new_backend("file", &conf).unwrap(); + let barrier = barrier_aes_gcm::AESGCMBarrier::new(Arc::clone(&backend)); + + let c = Arc::new(RwLock::new(Core { physical: backend, barrier: Arc::new(barrier), ..Default::default() })); + + { + let mut core = c.write().unwrap(); + assert!(core.config(Arc::clone(&c), None).is_ok()); + + let seal_config = SealConfig { secret_shares: 10, secret_threshold: 5 }; + + let result = core.init(&seal_config); + assert!(result.is_ok()); + let init_result = result.unwrap(); + println!("init_result: {:?}", init_result); + + let mut unsealed = false; + for i in 0..seal_config.secret_threshold { + let key = &init_result.secret_shares[i as usize]; + let unseal = core.unseal(key); + assert!(unseal.is_ok()); + unsealed = unseal.unwrap(); + } + + root_token = init_result.root_token; + + assert!(unsealed); + } + + { + println!("root_token: {:?}", root_token); + test_pki_config_ca(Arc::clone(&c), &root_token); + test_pki_generate_root(Arc::clone(&c), &root_token, false, true); + test_pki_config_role(Arc::clone(&c), &root_token); + test_pki_issue_cert_by_generate_root(Arc::clone(&c), &root_token); + } + } +} diff --git a/relay/src/vault.rs b/relay/src/vault.rs new file mode 100644 index 000000000..a9c565ddd --- /dev/null +++ b/relay/src/vault.rs @@ -0,0 +1,156 @@ +use std::collections::HashMap; +use std::fs; +use std::path::PathBuf; +use std::sync::{Arc, RwLock}; +use lazy_static::lazy_static; +use rusty_vault::core::{Core, SealConfig}; +use rusty_vault::errors::RvError; +use rusty_vault::logical::{Operation, Request, Response}; +use rusty_vault::storage::{barrier_aes_gcm, physical}; +use serde::{Deserialize, Serialize}; +use serde_json::{Map, Value}; + +#[derive(Serialize, Deserialize, Debug)] +struct CoreKey { + secret_shares: Vec>, + root_token: String, +} + +#[derive(Clone)] +pub struct CoreInfo { + pub core: Arc>, + pub token: String, +} + +// coding in `lazy_static!` with copilot seems lagging, so using function `init` instead +lazy_static! { + pub static ref CORE: CoreInfo = init(); +} + +/// Initialize the vault core, used in `lazy_static!` +fn init() -> CoreInfo { + const CORE_KEY_FILE: &str = "core_key.json"; // where the core key is stored, like `root_token` + let dir = PathBuf::from("/tmp/rusty_vault_pki_module"); // RustyVault files TODO configurable + let core_key_path = dir.join(CORE_KEY_FILE); + // let dir = env::temp_dir().join("rusty_vault_pki_module"); // TODO: 改成数据库? + + let inited = dir.exists(); + if !inited { + assert!(fs::create_dir(&dir).is_ok()); + } + + let mut conf: HashMap = HashMap::new(); + conf.insert("path".to_string(), Value::String(dir.to_string_lossy().into_owned())); + + let backend = physical::new_backend("file", &conf).unwrap(); // file or database + let barrier = barrier_aes_gcm::AESGCMBarrier::new(Arc::clone(&backend)); + + let c = Arc::new(RwLock::new(Core { physical: backend, barrier: Arc::new(barrier), ..Default::default() })); + + let root_token; + { + let mut core = c.write().unwrap(); + assert!(core.config(Arc::clone(&c), None).is_ok()); + + let seal_config = SealConfig { secret_shares: 10, secret_threshold: 5 }; + + let mut unsealed = false; + if !inited { + let result = core.init(&seal_config); + assert!(result.is_ok()); + let init_result = result.unwrap(); + println!("init_result: {:?}", init_result); + + for i in 0..seal_config.secret_threshold { + let key = &init_result.secret_shares[i as usize]; + let unseal = core.unseal(key); + assert!(unseal.is_ok()); + unsealed = unseal.unwrap(); + } + + root_token = init_result.root_token; + + let core_key = CoreKey { + secret_shares: Vec::from(&init_result.secret_shares[..]), + root_token: root_token.clone(), + }; + let file = fs::File::create(core_key_path).unwrap(); + serde_json::to_writer_pretty(file, &core_key).unwrap(); + } else { + let file = fs::File::open(core_key_path).unwrap(); + let core_key: CoreKey = serde_json::from_reader(file).unwrap(); + root_token = core_key.root_token.clone(); + + for i in 0..seal_config.secret_threshold { + let key = &core_key.secret_shares[i as usize]; + let unseal = core.unseal(key); + assert!(unseal.is_ok()); + unsealed = unseal.unwrap(); + } + } + + assert!(unsealed); + println!("root_token: {:?}", root_token); + } + + CoreInfo { core: c, token: root_token } +} + +pub fn read_api(core: &Core, token: &str, path: &str) -> Result, RvError> { + let mut req = Request::new(path); + req.operation = Operation::Read; + req.client_token = token.to_string(); + core.handle_request(&mut req) +} + +pub fn write_api( + core: &Core, + token: &str, + path: &str, + data: Option>, +) -> Result, RvError> { + let mut req = Request::new(path); + req.operation = Operation::Write; + req.client_token = token.to_string(); + req.body = data; + + let resp = core.handle_request(&mut req); + println!("path: {}, req.body: {:?}", path, req.body); + resp +} + +/// Write a secret to the vault (k-v) +pub fn write_secret(name: &str, data: Option>) -> Result, RvError> { + write_api(&CORE.core.read().unwrap(), &CORE.token, &format!("secret/{}", name), data) +} + +/// Read a secret from the vault (k-v) +pub fn read_secret(name: &str) -> Result, RvError> { + read_api(&CORE.core.read().unwrap(), &CORE.token, &format!("secret/{}", name)) +} + +#[cfg(test)] +mod tests { + use serde_json::json; + use super::*; + + #[test] + fn test_secret() { + // create secret + let kv_data = json!({ + "foo": "bar", + "id": "oqpXWgEhXa1WDqMWBnpUW4jvrxGqJKVuJATy4MSPdKNS", + }) + .as_object() + .unwrap() + .clone(); + write_secret("keyInfo", Some(kv_data.clone())).unwrap(); + + let secret = read_secret("keyInfo").unwrap().unwrap().data; + assert_eq!(secret, Some(kv_data)); + println!("secret: {:?}", secret.unwrap()); + + assert!(read_secret("foo").unwrap().is_none()); + assert!(read_api(&CORE.core.read().unwrap(), &CORE.token, "secret1/foo").is_err()); + } +} \ No newline at end of file