From 777b810db4675305854c373dcd57aedc40061e5d Mon Sep 17 00:00:00 2001 From: Jesse Luehrs Date: Sat, 18 Apr 2020 01:28:48 -0400 Subject: add encryption to the agent protocol --- src/bin/rbw-agent/actions.rs | 33 +++++++++++++++++++++++++++++ src/bin/rbw-agent/agent.rs | 4 ++++ src/bin/rbw/actions.rs | 20 ++++++++++++++++++ src/cipherstring.rs | 49 ++++++++++++++++++++++++++++++++++++++++++++ src/protocol.rs | 2 ++ 5 files changed, 108 insertions(+) diff --git a/src/bin/rbw-agent/actions.rs b/src/bin/rbw-agent/actions.rs index 2cb012c..7289a25 100644 --- a/src/bin/rbw-agent/actions.rs +++ b/src/bin/rbw-agent/actions.rs @@ -192,6 +192,28 @@ pub async fn decrypt( Ok(()) } +pub async fn encrypt( + sock: &mut crate::sock::Sock, + state: std::sync::Arc>, + plaintext: &str, +) -> anyhow::Result<()> { + let state = state.read().await; + let keys = if let Some(keys) = &state.priv_key { + keys + } else { + return Err(anyhow::anyhow!( + "failed to find encryption keys in in-memory state" + )); + }; + let cipherstring = + rbw::cipherstring::CipherString::encrypt(keys, plaintext.as_bytes()) + .context("failed to encrypt plaintext secret")?; + + respond_encrypt(sock, cipherstring.to_string()).await?; + + Ok(()) +} + async fn respond_ack(sock: &mut crate::sock::Sock) -> anyhow::Result<()> { sock.send(&rbw::protocol::Response::Ack) .await @@ -211,6 +233,17 @@ async fn respond_decrypt( Ok(()) } +async fn respond_encrypt( + sock: &mut crate::sock::Sock, + cipherstring: String, +) -> anyhow::Result<()> { + sock.send(&rbw::protocol::Response::Encrypt { cipherstring }) + .await + .context("failed to send response")?; + + Ok(()) +} + async fn config_email() -> anyhow::Result { let config = rbw::config::Config::load_async() .await diff --git a/src/bin/rbw-agent/agent.rs b/src/bin/rbw-agent/agent.rs index 21600c3..e80e1c6 100644 --- a/src/bin/rbw-agent/agent.rs +++ b/src/bin/rbw-agent/agent.rs @@ -142,6 +142,10 @@ async fn handle_request( .await?; true } + rbw::protocol::Action::Encrypt { plaintext } => { + crate::actions::encrypt(sock, state.clone(), &plaintext).await?; + true + } rbw::protocol::Action::Quit => std::process::exit(0), }; diff --git a/src/bin/rbw/actions.rs b/src/bin/rbw/actions.rs index 5951185..b3a9f1b 100644 --- a/src/bin/rbw/actions.rs +++ b/src/bin/rbw/actions.rs @@ -55,6 +55,26 @@ pub fn decrypt(cipherstring: &str) -> anyhow::Result { } } +pub fn encrypt(plaintext: &str) -> anyhow::Result { + let mut sock = crate::sock::Sock::connect() + .context("failed to connect to rbw-agent")?; + sock.send(&rbw::protocol::Request { + tty: std::env::var("TTY").ok(), + action: rbw::protocol::Action::Encrypt { + plaintext: plaintext.to_string(), + }, + })?; + + let res = sock.recv()?; + match res { + rbw::protocol::Response::Encrypt { cipherstring } => Ok(cipherstring), + rbw::protocol::Response::Error { error } => { + Err(anyhow::anyhow!("failed to encrypt: {}", error)) + } + _ => Err(anyhow::anyhow!("unexpected message: {:?}", res)), + } +} + fn simple_action( action: rbw::protocol::Action, desc: &str, diff --git a/src/cipherstring.rs b/src/cipherstring.rs index 9f2c261..2398b07 100644 --- a/src/cipherstring.rs +++ b/src/cipherstring.rs @@ -2,6 +2,7 @@ use crate::prelude::*; use block_modes::BlockMode as _; use hmac::Mac as _; +use rand::RngCore as _; pub struct CipherString { ty: u8, @@ -51,6 +52,34 @@ impl CipherString { }) } + pub fn encrypt( + keys: &crate::locked::Keys, + plaintext: &[u8], + ) -> Result { + let iv = random_iv(); + + let cipher = block_modes::Cbc::< + aes::Aes256, + block_modes::block_padding::Pkcs7, + >::new_var(keys.enc_key(), &iv) + .context(crate::error::CreateBlockMode)?; + let ciphertext = cipher.encrypt_vec(plaintext); + + let mut digest = + hmac::Hmac::::new_varkey(keys.mac_key()) + .map_err(|_| Error::InvalidMacKey)?; + digest.input(&iv); + digest.input(&ciphertext); + let mac = digest.result().code().to_vec(); + + Ok(Self { + ty: 2, + iv, + ciphertext, + mac: Some(mac), + }) + } + pub fn decrypt(&self, keys: &crate::locked::Keys) -> Result> { let cipher = self.decrypt_common(keys)?; cipher @@ -102,6 +131,19 @@ impl CipherString { } } +impl std::fmt::Display for CipherString { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + let iv = base64::encode(&self.iv); + let ciphertext = base64::encode(&self.ciphertext); + if let Some(mac) = &self.mac { + let mac = base64::encode(&mac); + write!(f, "{}.{}|{}|{}", self.ty, iv, ciphertext, mac) + } else { + write!(f, "{}.{}|{}", self.ty, iv, ciphertext) + } + } +} + fn macs_equal(mac1: &[u8], mac2: &[u8], mac_key: &[u8]) -> Result { let mut digest = hmac::Hmac::::new_varkey(mac_key) .map_err(|_| Error::InvalidMacKey)?; @@ -115,3 +157,10 @@ fn macs_equal(mac1: &[u8], mac2: &[u8], mac_key: &[u8]) -> Result { Ok(hmac1 == hmac2) } + +fn random_iv() -> Vec { + let mut iv = vec![0_u8; 16]; + let mut rng = rand::thread_rng(); + rng.fill_bytes(&mut iv); + iv +} diff --git a/src/protocol.rs b/src/protocol.rs index 8d7cd69..5d476ca 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -12,6 +12,7 @@ pub enum Action { Lock, Sync, Decrypt { cipherstring: String }, + Encrypt { plaintext: String }, // add // update // remove @@ -24,4 +25,5 @@ pub enum Response { Ack, Error { error: String }, Decrypt { plaintext: String }, + Encrypt { cipherstring: String }, } -- cgit v1.2.3-54-g00ecf