diff --git a/Cargo.toml b/Cargo.toml index cbcbf86..23b973f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,13 +16,9 @@ path = "src/utility.rs" dbus = "0.8.4" dbus-crossroads = "0.2.1" parsec-client = "0.11.0" -ring = { version = "0.16.15", features = ["std"] } anyhow = "1.0.32" -rsa = "0.5.0" -pkcs1 = "0.2.4" -sha2 = "0.9.1" +openssl = "0.10" hex = "0.4.0" -rand = "0.8" [build-dependencies] dbus-codegen = "0.5.0" diff --git a/src/agent/control.rs b/src/agent/control.rs index 4a23b6c..e961af3 100644 --- a/src/agent/control.rs +++ b/src/agent/control.rs @@ -79,20 +79,19 @@ impl dbus_parsec_control_server::ComGithubPuiterwijkDBusPARSECControl for Agent } Some(key_id) => key_id, }; - let (wrapkey_path, contents_path) = - self.get_secret_file_paths(&secret_type, secret_group, secret_name)?; + let (wrapkey_path, contents_path) = self + .get_secret_file_paths(&secret_type, secret_group, secret_name) + .map_err(|e| MethodErr::failed(&format!("{}", e)))?; // Try to decrypt the secret to ensure we're not being passed nonsense - match self.decrypt_secret( + self.decrypt_secret( &secret_type, secret_group, secret_name, &wrapper_key, &secret_value, - ) { - Some(_) => {} - None => return Err(MethodErr::failed("Unable to decrypt secret")), - }; + ) + .map_err(|_| MethodErr::failed("Unable to decrypt secret"))?; let err_failed = MethodErr::failed("Unable to write secret"); diff --git a/src/agent/mod.rs b/src/agent/mod.rs index cb7e8c4..cb28591 100644 --- a/src/agent/mod.rs +++ b/src/agent/mod.rs @@ -15,22 +15,13 @@ use std::fs; use std::path; +use anyhow::{bail, Context, Result}; use parsec_client::core::interface::operations::psa_algorithm::{AsymmetricEncryption, Hash}; use parsec_client::core::interface::operations::psa_key_attributes::{ Attributes, Lifetime, Policy, Type, UsageFlags, }; use parsec_client::BasicClient; -use crate::utils; - -use ring::aead::Aad; -use ring::aead::BoundKey; -use ring::aead::OpeningKey; -use ring::aead::UnboundKey; -use ring::aead::AES_256_GCM; - -use dbus::tree::MethodErr; - mod control; mod networkmanager; @@ -108,9 +99,9 @@ impl Agent { secret_type: &KeyType, secret_group: &str, secret_name: &str, - ) -> Result<(path::PathBuf, path::PathBuf), MethodErr> { + ) -> Result<(path::PathBuf, path::PathBuf)> { if !is_valid_identifier(secret_group) || !is_valid_identifier(secret_name) { - Err(MethodErr::failed("Invalid secret identifiers")) + bail!("Invalid secret identifiers"); } else { let file_base = format!( "secret{}{}{}{}{}{}", @@ -169,27 +160,14 @@ impl Agent { secret_type: &KeyType, secret_group: &str, secret_name: &str, - ) -> Option> { - let (wrapkey_path, contents_path) = - match self.get_secret_file_paths(secret_type, secret_group, secret_name) { - Ok(res) => res, - Err(_) => return None, - }; - let wrapkey = match fs::read(&wrapkey_path) { - Ok(res) => res, - Err(err) => { - eprintln!("Error reading wrapkey {:?}: {}", wrapkey_path, err); - return None; - } - }; - let contents = match fs::read(&contents_path) { - Ok(res) => res, - Err(err) => { - eprintln!("Error reading contents {:?}: {}", contents_path, err); - return None; - } - }; + ) -> Result> { + let (wrapkey_path, contents_path) = self + .get_secret_file_paths(secret_type, secret_group, secret_name) + .context("Error determining secret file path")?; + let wrapkey = fs::read(&wrapkey_path).context("Error reading wrapkey")?; + let contents = fs::read(&contents_path).context("Error reading secret contents")?; self.decrypt_secret(secret_type, secret_group, secret_name, &wrapkey, &contents) + .context("Error decrypting secret") } fn decrypt_secret( @@ -199,48 +177,24 @@ impl Agent { secret_name: &str, wrapkey: &[u8], value: &[u8], - ) -> Option> { + ) -> Result> { let key_name = self.key_name(secret_type, secret_group); let asym_enc_algo = AsymmetricEncryption::RsaOaep { hash_alg: Hash::Sha256, }; - let wrapkey = - match self - .parsec_client - .psa_asymmetric_decrypt(key_name, asym_enc_algo, wrapkey, None) - { - Ok(key) => key, - Err(err) => { - eprintln!("Error decrypting wrapper key: {}", err); - return None; - } - }; - - let aad = format!("{};{};{}", secret_type.to_type(), secret_group, secret_name); - let aad = Aad::from(aad); - - let wrapkey = match UnboundKey::new(&AES_256_GCM, &wrapkey) { - Ok(key) => key, - Err(err) => { - eprintln!("Wrapkey invalid: {}", err); - return None; - } - }; - - let mut in_out = value.to_vec(); - - let mut wrapkey: OpeningKey = - BoundKey::new(wrapkey, utils::CounterNonce::new()); - - let plaintext = match wrapkey.open_in_place(aad, &mut in_out) { - Ok(pt) => pt, - Err(err) => { - eprintln!("Error decrypting inner contents: {}", err); - return None; - } - }; - - Some(plaintext.to_vec()) + let wrapkey = self + .parsec_client + .psa_asymmetric_decrypt(key_name, asym_enc_algo, wrapkey, None) + .context("Error decrypting wrapkey")?; + + crate::crypto::decrypt_secret( + wrapkey, + &secret_type.to_type(), + secret_group, + secret_name, + value, + ) + .context("Error decrypting secret") } } diff --git a/src/agent/networkmanager.rs b/src/agent/networkmanager.rs index 46e3802..c8f5c3a 100644 --- a/src/agent/networkmanager.rs +++ b/src/agent/networkmanager.rs @@ -111,9 +111,16 @@ impl<'a> NMConnectionInfo<'a> for &NMConnection<'a> { self.get("vpn")?.get("service-type")?.0.as_str()?, )), "wireguard" => Some(NetworkManagerConnectionType::Wireguard), - "802-11-wireless" => match self.get("802-11-wireless-security")?.get("key-mgmt")?.0.as_str()? { + "802-11-wireless" => match self + .get("802-11-wireless-security")? + .get("key-mgmt")? + .0 + .as_str()? + { "wpa-psk" => Some(NetworkManagerConnectionType::Wireless(NMWirelessType::PSK)), - _ => Some(NetworkManagerConnectionType::Wireless(NMWirelessType::Enterprise)), + _ => Some(NetworkManagerConnectionType::Wireless( + NMWirelessType::Enterprise, + )), }, _ => None, } @@ -201,7 +208,7 @@ impl nm_secretagent::OrgFreedesktopNetworkManagerSecretAgent for Agent { self.retrieve_secret(&KeyType::NetworkManager, conn_name, secret_name), ) }) - .filter(|x| x.1.is_some()) + .filter(|x| x.1.is_ok()) .map(|x| (x.0.to_string(), String::from_utf8(x.1.unwrap()))) .filter(|x| x.1.is_ok()) .map(|x| (x.0, str_refarg(x.1.unwrap()))) diff --git a/src/crypto.rs b/src/crypto.rs new file mode 100644 index 0000000..7ae32aa --- /dev/null +++ b/src/crypto.rs @@ -0,0 +1,134 @@ +use anyhow::{Context, Result}; +use openssl::{ + rsa::RsaRef, + symm::{decrypt_aead, encrypt_aead, Cipher}, +}; + +#[allow(unused)] +pub(crate) struct EncryptResult { + pub wrapped_wrapkey: Vec, + pub secret: Vec, +} + +#[allow(unused)] +pub(crate) fn encrypt_secret( + public_key: &RsaRef, + secret_type: &str, + secret_group: &str, + secret_name: &str, + secret_value: Vec, +) -> Result +where + T: openssl::pkey::HasPublic, +{ + // Generate a wrapper key + let mut wrapkey: [u8; 32] = [0; 32]; + openssl::rand::rand_bytes(&mut wrapkey).context("Unable to generate random wrapper key")?; + let wrapkey = wrapkey; + + // Encrypt the wrapper key + let mut wrapped_wrapkey = Vec::with_capacity(public_key.size() as usize); + wrapped_wrapkey.resize(public_key.size() as usize, 0); + public_key + .public_encrypt( + &wrapkey, + &mut wrapped_wrapkey, + openssl::rsa::Padding::PKCS1_OAEP, + ) + .context("Unable to encrypt wrapper key")?; + + // Encrypt the secret + let mut tag = vec![0; 16]; + let mut nonce = [0; 12]; + nonce[0] = 1; + let aad = format!("{};{};{}", &secret_type, &secret_group, &secret_name); + let mut secret = encrypt_aead( + Cipher::aes_256_gcm(), + &wrapkey, + Some(&nonce), + &aad.as_bytes(), + &secret_value, + &mut tag, + ) + .context("Unable to encrypt secret")?; + secret.extend_from_slice(&tag); + + Ok(EncryptResult { + wrapped_wrapkey, + secret, + }) +} + +#[allow(unused)] +pub(crate) fn decrypt_secret( + wrapkey: Vec, + secret_type: &str, + secret_group: &str, + secret_name: &str, + ciphertext: &[u8], +) -> Result> { + let aad = format!("{};{};{}", secret_type, secret_group, secret_name); + + let (value, tag) = ciphertext.split_at(ciphertext.len() - 16); + let mut nonce = [0; 12]; + nonce[0] = 1; + + decrypt_aead( + Cipher::aes_256_gcm(), + &wrapkey, + Some(&nonce), + aad.as_bytes(), + value, + &tag, + ) + .context("Error decrypting secret") + .map(|res| res.to_vec()) +} + +#[cfg(test)] +mod test { + use openssl::{ + rand::rand_bytes, + rsa::{Padding, Rsa}, + }; + + #[test] + fn test_encrypt_decrypt() { + let privkey = Rsa::generate(2048).unwrap(); + let mut test_contents = vec![0; 128]; + + // Generate random bytes to test with + rand_bytes(&mut test_contents).unwrap(); + + let encrypt_result = super::encrypt_secret( + &privkey, + "test_type", + "test_group", + "test_name", + test_contents.clone(), + ) + .unwrap(); + + let mut decrypted_wrapkey = Vec::with_capacity(privkey.size() as usize); + decrypted_wrapkey.resize(privkey.size() as usize, 0); + let decrypted_wrapkey_size = privkey + .private_decrypt( + &encrypt_result.wrapped_wrapkey, + &mut decrypted_wrapkey, + Padding::PKCS1_OAEP, + ) + .unwrap(); + decrypted_wrapkey.truncate(decrypted_wrapkey_size); + + let decrypted_contents = super::decrypt_secret( + decrypted_wrapkey, + "test_type", + "test_group", + "test_name", + &encrypt_result.secret, + ) + .unwrap(); + + assert_eq!(test_contents, decrypted_contents); + } +} diff --git a/src/daemon.rs b/src/daemon.rs index 6bbd8c8..a186744 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -20,11 +20,10 @@ use dbus::tree; use dbus::tree::{Factory, Interface, MTFn}; use parsec_client::core::interface::requests::Opcode; -use parsec_client::core::secrecy::Secret; use parsec_client::BasicClient; mod agent; -mod utils; +mod crypto; mod nm_secretagent { include!(concat!(env!("OUT_DIR"), "/nm_secretagent.rs")); diff --git a/src/utility.rs b/src/utility.rs index c94afc7..cf5f356 100644 --- a/src/utility.rs +++ b/src/utility.rs @@ -2,16 +2,10 @@ static DBUS_PARSEC_CONTROL_OBJ_PATH: &str = "/com/github/puiterwijk/DBusPARSEC/C static DBUS_NAME: &str = "com.github.puiterwijk.dbus_parsec"; use anyhow::{ensure, Context, Result}; - -use pkcs1::FromRsaPublicKey; -use rsa::{PaddingScheme, PublicKey, RsaPublicKey}; - -use rand::rngs::OsRng; - -use ring::aead; -use ring::rand::{SecureRandom, SystemRandom}; - -use sha2::{Digest, Sha256}; +use openssl::{ + hash::{hash, MessageDigest}, + rsa::Rsa, +}; use std::env; use std::io::{self, Read}; @@ -21,17 +15,15 @@ use std::time::Duration; use dbus_parsec_control::ComGithubPuiterwijkDBusPARSECControl; -mod utils; - +mod crypto; mod dbus_parsec_control { include!(concat!(env!("OUT_DIR"), "/dbus_parsec_control_client.rs")); } -fn sha256_hex(inp: &[u8]) -> String { - let mut hasher = Sha256::new(); - hasher.update(inp); - let result = hasher.finalize(); - hex::encode(result) +fn sha256_hex(inp: &[u8]) -> Result { + hash(MessageDigest::sha256(), inp) + .context("Error computing sha256") + .map(hex::encode) } fn read_input_token() -> Result { @@ -53,7 +45,7 @@ fn main() -> Result<()> { ); let (secret_type, secret_group, secret_name) = (&args[2], &args[3], &args[4]); let secret = read_input_token().with_context(|| "Error getting secret contents")?; - let mut secret = secret.into_bytes(); + let secret = secret.into_bytes(); let conn = Connection::new_system().with_context(|| "Unable to connect to DBus System Bus")?; let proxy = conn.with_proxy( @@ -62,10 +54,6 @@ fn main() -> Result<()> { Duration::from_millis(5000), ); - // We need multiple random sources... - let sysrand = SystemRandom::new(); - let mut rsarand = OsRng; - // Let's get the pubkey first, so we know the control interface is reachable let pubkey = proxy .get_public_key(&secret_type, &secret_group) @@ -75,33 +63,12 @@ fn main() -> Result<()> { &secret_type, &secret_group ) })?; - println!("Public key sha256: {}", sha256_hex(&pubkey)); - let pubkey = RsaPublicKey::from_pkcs1_der(&pubkey) - .with_context(|| "Unable to parse retrieved public key")?; - - // Generate a wrapper key - let mut wrapkey: [u8; 32] = [0; 32]; - sysrand - .fill(&mut wrapkey) - .with_context(|| "Unable to generate random wrapper key")?; - let wrapkey = wrapkey; - println!("Wrapper key sha256: {}", sha256_hex(&wrapkey)); - - // Encrypt the wrapper key - let wrapped_wrapkey = pubkey - .encrypt(&mut rsarand, PaddingScheme::new_oaep::(), &wrapkey) - .with_context(|| "Unable to encrypt wrapper key")?; + println!("Public key sha256: {}", sha256_hex(&pubkey)?); + let pubkey = Rsa::public_key_from_der_pkcs1(&pubkey).context("Unable to parse public key")?; - // Encrypt the secret - let wrapkey = aead::UnboundKey::new(&aead::AES_256_GCM, &wrapkey) - .with_context(|| "Unable to generate UnboundKey")?; - let mut wrapkey: aead::SealingKey = - aead::BoundKey::new(wrapkey, utils::CounterNonce::new()); - let aad = format!("{};{};{}", &secret_type, &secret_group, &secret_name); - let aad = aead::Aad::from(&aad); - wrapkey - .seal_in_place_append_tag(aad, &mut secret) - .with_context(|| "Unable to seal with generated wrapkey")?; + let encrypt_result = + crate::crypto::encrypt_secret(&pubkey, &secret_type, &secret_group, &secret_name, secret) + .context("Error encrypting secret")?; // Store the secret finally proxy @@ -109,8 +76,8 @@ fn main() -> Result<()> { secret_type, secret_group, secret_name, - wrapped_wrapkey, - secret, + encrypt_result.wrapped_wrapkey, + encrypt_result.secret, ) .with_context(|| format!("Failed to store secret {}", &secret_name))?; diff --git a/src/utils.rs b/src/utils.rs deleted file mode 100644 index 9b8bd65..0000000 --- a/src/utils.rs +++ /dev/null @@ -1,26 +0,0 @@ -use ring::aead; - -pub struct CounterNonce { - counter: u8, -} - -impl CounterNonce { - pub fn new() -> Self { - CounterNonce { counter: 0 } - } -} - -impl aead::NonceSequence for CounterNonce { - fn advance(&mut self) -> Result { - self.counter += 1; - - if self.counter >= u8::MAX { - return Err(ring::error::Unspecified {}); - } - - let mut nonce = [0; 12]; - nonce[0] = self.counter; - - aead::Nonce::try_assume_unique_for_key(&nonce) - } -}