diff options
Diffstat (limited to 'src/client.rs')
| -rw-r--r-- | src/client.rs | 181 |
1 files changed, 181 insertions, 0 deletions
diff --git a/src/client.rs b/src/client.rs new file mode 100644 index 0000000..85a1221 --- /dev/null +++ b/src/client.rs @@ -0,0 +1,181 @@ +use base64::Engine; +use base64::prelude::BASE64_STANDARD; +use chrono::{DateTime, Utc}; +use ed25519_dalek::{Signer, SigningKey}; +use rand::TryRng; +use rand::distr::{Alphanumeric, SampleString}; +use rand::rngs::SysRng; +use std::fs::File; +use std::io; +use std::io::{BufReader, Read, Write}; +use ureq::Error::StatusCode; +use ureq::SendBody; +use crate::config::{read_config, ClientConfig}; + +struct AuthExchange { + nonce: String, + timestamp: i64, + signature: String, +} + +fn generate_nonce() -> String { + Alphanumeric.sample_string(&mut rand::rng(), 32) +} + +fn create_auth(config: &ClientConfig) -> AuthExchange { + let mut buffer = [0u8; 44]; + File::open(config.private_key.clone()) + .expect("Failed to open private key file") + .read_exact(&mut buffer) + .expect("Failed to read private key file"); + let decoded = BASE64_STANDARD + .decode(buffer) + .expect("Malformed private key"); + let signing_key = SigningKey::from_bytes(&decoded.try_into().expect("Malformed private key")); + + let nonce = generate_nonce(); + let timestamp = Utc::now().timestamp(); + let signature = BASE64_STANDARD.encode( + signing_key + .sign(format!("{}{}", nonce, timestamp).as_bytes()) + .to_bytes(), + ); + + AuthExchange { + nonce, + timestamp, + signature, + } +} + +pub fn gen_pubkey() { + let mut buffer = [0u8; 44]; + io::stdin().read_exact(&mut buffer).unwrap(); + + let decoded = BASE64_STANDARD + .decode(buffer) + .expect("Malformed private key"); + + let signing_key = SigningKey::from_bytes(&decoded.try_into().expect("Malformed private key")); + let verifying_key = signing_key.verifying_key(); + let encoded = BASE64_STANDARD.encode(verifying_key); + println!("{}", encoded); +} + +pub fn gen_privkey() { + let mut seed = [0u8; 32]; + SysRng + .try_fill_bytes(&mut seed) + .expect("Failed to generate random seed"); + let signing_key = SigningKey::from_bytes(&seed); + let encoded = BASE64_STANDARD.encode(signing_key.to_bytes()); + println!("{}", encoded); +} + +pub fn upload_vault() { + let config: ClientConfig = read_config().expect("Failed to read config"); + let server_url = &config.server_url; + let file = File::open(&config.db_path).expect("Failed to open database file"); + let reader = BufReader::new(file); + + let auth = create_auth(&config); + + let code = ureq::post(format!("{}/vaults", server_url)) + .content_type("application/x-keepass2") + .header("X-Signature", auth.signature) + .header("X-Nonce", auth.nonce) + .header("X-Timestamp", auth.timestamp) + .send(SendBody::from_owned_reader(reader)) + .expect("Failed to send file") + .status(); + if code == 200 { + println!("Successfully uploaded vault file"); + } else { + println!("Failed to upload vault file: {}", code); + } +} + +pub fn ls_vaults(long: bool) { + let config: ClientConfig = read_config().expect("Failed to read config"); + let server_url = &config.server_url; + + let auth = create_auth(&config); + + let result = ureq::get(format!("{}/vaults", server_url)) + .header("X-Signature", auth.signature) + .header("X-Nonce", auth.nonce) + .header("X-Timestamp", auth.timestamp) + .call() + .expect("Failed to get from server") + .body_mut() + .read_to_string() + .expect("Failed to read server response"); + + let files = result.split("\n"); + for file in files { + if file.is_empty() { + break; + } + let mut parts = file.split("_"); + + let vault_id = parts.next().unwrap(); + + let timestamp: i64 = parts + .next() + .expect("Malformed response") + .parse() + .expect("Malformed response"); + let dt = DateTime::from_timestamp(timestamp, 0).expect("Failed to parse timestamp"); + + if long { + println!( + "{} \t [Created: {}]", + vault_id, + dt.format("%m-%d-%Y %H:%M:%S") + ); + } else { + println!( + "{} \t [Created: {}]", + &vault_id[0..8], + dt.format("%m-%d-%Y %H:%M:%S") + ); + } + } +} + +pub fn download_vault(id: String, install: bool, file: String) { + let config: ClientConfig = read_config().expect("Failed to read config"); + let server_url = &config.server_url; + + let auth = create_auth(&config); + let response = ureq::get(format!("{}/vaults/{}", server_url, id)) + .header("X-Signature", auth.signature) + .header("X-Nonce", auth.nonce) + .header("X-Timestamp", auth.timestamp) + .call(); + match response { + Ok(mut response) => { + let result = response + .body_mut() + .read_to_vec() + .expect("Failed to read server response"); + + let mut file = File::create(if install { config.db_path } else { file }) + .expect("Failed to open database file"); + + file.write_all(&result) + .expect("Failed to write to database file"); + } + Err(error) => match error { + StatusCode(stat) => { + if stat == 300 { + println!("There are multiple candidates available for download."); + println!("** Rerun the command with a long ID.") + } else { + println!("An internal server error occurred!"); + } + } + _ => eprintln!("An error occurred: {}", error), + }, + } +} |
