From c7d89c1703c6dc580b2ef2cbb66b0df0b1e72b50 Mon Sep 17 00:00:00 2001 From: Conrad Ludgate Date: Mon, 17 Apr 2023 21:12:02 +0100 Subject: chore: uuhhhhhh crypto lol (#805) * chore: uuhhhhhh crypto lol * remove dead code * fix key decoding * use inplace encryption --- atuin-client/Cargo.toml | 5 ++- atuin-client/src/api_client.rs | 6 ++-- atuin-client/src/encryption.rs | 69 +++++++++++++++++++++++++----------------- 3 files changed, 48 insertions(+), 32 deletions(-) (limited to 'atuin-client') diff --git a/atuin-client/Cargo.toml b/atuin-client/Cargo.toml index 6c5d04b9..3542510c 100644 --- a/atuin-client/Cargo.toml +++ b/atuin-client/Cargo.toml @@ -15,12 +15,13 @@ repository = { workspace = true } default = ["sync"] sync = [ "urlencoding", - "sodiumoxide", "reqwest", "sha2", "hex", "rmp-serde", "base64", + "generic-array", + "xsalsa20poly1305", ] [dependencies] @@ -60,6 +61,8 @@ rmp-serde = { version = "1.1.1", optional = true } base64 = { workspace = true, optional = true } tokio = { workspace = true } semver = { workspace = true } +xsalsa20poly1305 = { version = "0.9.0", optional = true } +generic-array = { version = "0.14", optional = true, features = ["serde"] } [dev-dependencies] tokio = { version = "1", features = ["full"] } diff --git a/atuin-client/src/api_client.rs b/atuin-client/src/api_client.rs index dee98613..80051ba6 100644 --- a/atuin-client/src/api_client.rs +++ b/atuin-client/src/api_client.rs @@ -7,13 +7,13 @@ use reqwest::{ header::{HeaderMap, AUTHORIZATION, USER_AGENT}, StatusCode, Url, }; -use sodiumoxide::crypto::secretbox; use atuin_common::api::{ AddHistoryRequest, CountResponse, DeleteHistoryRequest, ErrorResponse, IndexResponse, LoginRequest, LoginResponse, RegisterResponse, StatusResponse, SyncHistoryResponse, }; use semver::Version; +use xsalsa20poly1305::Key; use crate::{ encryption::{decode_key, decrypt}, @@ -28,7 +28,7 @@ static APP_USER_AGENT: &str = concat!("atuin/", env!("CARGO_PKG_VERSION"),); pub struct Client<'a> { sync_addr: &'a str, - key: secretbox::Key, + key: Key, client: reqwest::Client, } @@ -182,7 +182,7 @@ impl<'a> Client<'a> { .iter() // TODO: handle deletion earlier in this chain .map(|h| serde_json::from_str(h).expect("invalid base64")) - .map(|h| decrypt(&h, &self.key).expect("failed to decrypt history! check your key")) + .map(|h| decrypt(h, &self.key).expect("failed to decrypt history! check your key")) .map(|mut h| { if deleted.contains(&h.id) { h.deleted_at = Some(chrono::Utc::now()); diff --git a/atuin-client/src/encryption.rs b/atuin-client/src/encryption.rs index fe19ce9b..9145a11d 100644 --- a/atuin-client/src/encryption.rs +++ b/atuin-client/src/encryption.rs @@ -14,7 +14,11 @@ use base64::prelude::{Engine, BASE64_STANDARD}; use eyre::{eyre, Context, Result}; use fs_err as fs; use serde::{Deserialize, Serialize}; -use sodiumoxide::crypto::secretbox; +pub use xsalsa20poly1305::Key; +use xsalsa20poly1305::{ + aead::{Nonce, OsRng}, + AeadInPlace, KeyInit, XSalsa20Poly1305, +}; use crate::{ history::{History, HistoryWithoutDelete}, @@ -24,14 +28,14 @@ use crate::{ #[derive(Debug, Serialize, Deserialize)] pub struct EncryptedHistory { pub ciphertext: Vec, - pub nonce: secretbox::Nonce, + pub nonce: Nonce, } -pub fn new_key(settings: &Settings) -> Result { +pub fn new_key(settings: &Settings) -> Result { let path = settings.key_path.as_str(); - let key = secretbox::gen_key(); - let encoded = encode_key(key.clone())?; + let key = XSalsa20Poly1305::generate_key(&mut OsRng); + let encoded = encode_key(&key)?; let mut file = fs::File::create(path)?; file.write_all(encoded.as_bytes())?; @@ -40,7 +44,7 @@ pub fn new_key(settings: &Settings) -> Result { } // Loads the secret key, will create + save if it doesn't exist -pub fn load_key(settings: &Settings) -> Result { +pub fn load_key(settings: &Settings) -> Result { let path = settings.key_path.as_str(); let key = if PathBuf::from(path).exists() { @@ -60,8 +64,8 @@ pub fn load_encoded_key(settings: &Settings) -> Result { let key = fs::read_to_string(path)?; Ok(key) } else { - let key = secretbox::gen_key(); - let encoded = encode_key(key)?; + let key = XSalsa20Poly1305::generate_key(&mut OsRng); + let encoded = encode_key(&key)?; let mut file = fs::File::create(path)?; file.write_all(encoded.as_bytes())?; @@ -70,38 +74,47 @@ pub fn load_encoded_key(settings: &Settings) -> Result { } } -pub type Key = secretbox::Key; -pub fn encode_key(key: secretbox::Key) -> Result { - let buf = rmp_serde::to_vec(&key).wrap_err("could not encode key to message pack")?; +pub fn encode_key(key: &Key) -> Result { + let buf = rmp_serde::to_vec(key.as_slice()).wrap_err("could not encode key to message pack")?; let buf = BASE64_STANDARD.encode(buf); Ok(buf) } -pub fn decode_key(key: String) -> Result { +pub fn decode_key(key: String) -> Result { let buf = BASE64_STANDARD .decode(key.trim_end()) .wrap_err("encryption key is not a valid base64 encoding")?; - let buf: secretbox::Key = rmp_serde::from_slice(&buf) + let buf: &[u8] = rmp_serde::from_slice(&buf) .wrap_err("encryption key is not a valid message pack encoding")?; - Ok(buf) + Ok(*Key::from_slice(buf)) } -pub fn encrypt(history: &History, key: &secretbox::Key) -> Result { +pub fn encrypt(history: &History, key: &Key) -> Result { // serialize with msgpack - let buf = rmp_serde::to_vec(history)?; - - let nonce = secretbox::gen_nonce(); + let mut buf = rmp_serde::to_vec(history)?; - let ciphertext = secretbox::seal(&buf, &nonce, key); + let nonce = XSalsa20Poly1305::generate_nonce(&mut OsRng); + XSalsa20Poly1305::new(key) + .encrypt_in_place(&nonce, &[], &mut buf) + .map_err(|_| eyre!("could not encrypt"))?; - Ok(EncryptedHistory { ciphertext, nonce }) + Ok(EncryptedHistory { + ciphertext: buf, + nonce, + }) } -pub fn decrypt(encrypted_history: &EncryptedHistory, key: &secretbox::Key) -> Result { - let plaintext = secretbox::open(&encrypted_history.ciphertext, &encrypted_history.nonce, key) - .map_err(|_| eyre!("failed to open secretbox - invalid key?"))?; +pub fn decrypt(mut encrypted_history: EncryptedHistory, key: &Key) -> Result { + XSalsa20Poly1305::new(key) + .decrypt_in_place( + &encrypted_history.nonce, + &[], + &mut encrypted_history.ciphertext, + ) + .map_err(|_| eyre!("could not encrypt"))?; + let plaintext = encrypted_history.ciphertext; let history = rmp_serde::from_slice(&plaintext); @@ -126,7 +139,7 @@ pub fn decrypt(encrypted_history: &EncryptedHistory, key: &secretbox::Key) -> Re #[cfg(test)] mod test { - use sodiumoxide::crypto::secretbox; + use xsalsa20poly1305::{aead::OsRng, KeyInit, XSalsa20Poly1305}; use crate::history::History; @@ -134,8 +147,8 @@ mod test { #[test] fn test_encrypt_decrypt() { - let key1 = secretbox::gen_key(); - let key2 = secretbox::gen_key(); + let key1 = XSalsa20Poly1305::generate_key(&mut OsRng); + let key2 = XSalsa20Poly1305::generate_key(&mut OsRng); let history = History::new( chrono::Utc::now(), @@ -156,12 +169,12 @@ mod test { // test decryption works // this should pass - match decrypt(&e1, &key1) { + match decrypt(e1, &key1) { Err(e) => panic!("failed to decrypt, got {}", e), Ok(h) => assert_eq!(h, history), }; // this should err - let _ = decrypt(&e2, &key1).expect_err("expected an error decrypting with invalid key"); + let _ = decrypt(e2, &key1).expect_err("expected an error decrypting with invalid key"); } } -- cgit v1.3.1