summaryrefslogtreecommitdiff
path: root/src/client.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/client.rs')
-rw-r--r--src/client.rs181
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),
+ },
+ }
+}