aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJesse Luehrs <doy@tozt.net>2020-04-18 01:28:48 -0400
committerJesse Luehrs <doy@tozt.net>2020-04-18 01:28:48 -0400
commit777b810db4675305854c373dcd57aedc40061e5d (patch)
treeed35c77e39416073fb5222aebdb5ef484c238dd3
parent3babc8801c0d09b7868be9be7751f7cfdf32c8bb (diff)
downloadrbw-777b810db4675305854c373dcd57aedc40061e5d.tar.gz
rbw-777b810db4675305854c373dcd57aedc40061e5d.zip
add encryption to the agent protocol
-rw-r--r--src/bin/rbw-agent/actions.rs33
-rw-r--r--src/bin/rbw-agent/agent.rs4
-rw-r--r--src/bin/rbw/actions.rs20
-rw-r--r--src/cipherstring.rs49
-rw-r--r--src/protocol.rs2
5 files changed, 108 insertions, 0 deletions
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<tokio::sync::RwLock<crate::agent::State>>,
+ 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<String> {
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<String> {
}
}
+pub fn encrypt(plaintext: &str) -> anyhow::Result<String> {
+ 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<Self> {
+ 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::<sha2::Sha256>::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<Vec<u8>> {
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<bool> {
let mut digest = hmac::Hmac::<sha2::Sha256>::new_varkey(mac_key)
.map_err(|_| Error::InvalidMacKey)?;
@@ -115,3 +157,10 @@ fn macs_equal(mac1: &[u8], mac2: &[u8], mac_key: &[u8]) -> Result<bool> {
Ok(hmac1 == hmac2)
}
+
+fn random_iv() -> Vec<u8> {
+ 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 },
}