Skip to content

Commit

Permalink
Move from ring et al to openssl
Browse files Browse the repository at this point in the history
We move from ring, sha2 and rsa crates to openssl, for FIPS
certification reasons.

Fixes: #6
Signed-off-by: Patrick Uiterwijk <[email protected]>
  • Loading branch information
puiterwijk committed Apr 14, 2022
1 parent 8e94df7 commit 9ebcdf3
Show file tree
Hide file tree
Showing 8 changed files with 193 additions and 163 deletions.
6 changes: 1 addition & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
13 changes: 6 additions & 7 deletions src/agent/control.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");

Expand Down
94 changes: 24 additions & 70 deletions src/agent/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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{}{}{}{}{}{}",
Expand Down Expand Up @@ -169,27 +160,14 @@ impl Agent {
secret_type: &KeyType,
secret_group: &str,
secret_name: &str,
) -> Option<Vec<u8>> {
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<Vec<u8>> {
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(
Expand All @@ -199,48 +177,24 @@ impl Agent {
secret_name: &str,
wrapkey: &[u8],
value: &[u8],
) -> Option<Vec<u8>> {
) -> Result<Vec<u8>> {
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<utils::CounterNonce> =
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")
}
}
13 changes: 10 additions & 3 deletions src/agent/networkmanager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
Expand Down Expand Up @@ -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())))
Expand Down
134 changes: 134 additions & 0 deletions src/crypto.rs
Original file line number Diff line number Diff line change
@@ -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<u8>,
pub secret: Vec<u8>,
}

#[allow(unused)]
pub(crate) fn encrypt_secret<T>(
public_key: &RsaRef<T>,
secret_type: &str,
secret_group: &str,
secret_name: &str,
secret_value: Vec<u8>,
) -> Result<EncryptResult>
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<u8>,
secret_type: &str,
secret_group: &str,
secret_name: &str,
ciphertext: &[u8],
) -> Result<Vec<u8>> {
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);
}
}
3 changes: 1 addition & 2 deletions src/daemon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"));
Expand Down
Loading

0 comments on commit 9ebcdf3

Please sign in to comment.