Skip to content

Commit

Permalink
AES256_GCM encrypted value display.
Browse files Browse the repository at this point in the history
  • Loading branch information
gibbz00 committed Dec 10, 2023
1 parent a52c448 commit aeb472f
Show file tree
Hide file tree
Showing 20 changed files with 483 additions and 79 deletions.
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@ members = [ "crates/*" ]

[workspace.dependencies]
age = "0.9"
aes-gcm = "0.10"
base64 = "0.21"
derive_more = "0.99"
indoc = "2"
rand = "0.8"
serde = { version = "1", features = ["derive"] }
serde_with = "3"
serde_yaml = "0.9"
strum = { version = "0.25.0", features = ["derive"] }
textwrap = "0.16"
thiserror = "1"

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
- Flag: `--mac-only-encrypted`.
- `.rops.yaml`: `partial_encryption.mac_only_encrypted: true`.
- [ ] Last modified metadata
- [ ] File comment encryption

### Integrations:

Expand Down
8 changes: 8 additions & 0 deletions crates/lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,18 @@ age = ["dep:age"]
age-test-utils = ["age", "test-utils", "dep:indoc", "dep:textwrap"]
# File formats:
yaml = ["dep:serde_yaml"]
# Ciphers
aes-gcm = ["dep:aes-gcm"]

test-utils = []

[dependencies]
base64.workspace = true
derive_more.workspace = true
rand.workspace = true
serde.workspace = true
serde_with.workspace = true
strum.workspace = true
thiserror.workspace = true

# AGE
Expand All @@ -27,3 +32,6 @@ indoc = { workspace = true, optional = true }

# YAML
serde_yaml = { workspace = true, optional = true }

# AES_GCM
aes-gcm = { workspace = true, optional = true }
16 changes: 16 additions & 0 deletions crates/lib/src/base64_utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use base64::{engine::general_purpose, Engine};

pub trait Base64Utils
where
Self: AsRef<[u8]>,
{
fn encode_base64(&self) -> String {
general_purpose::STANDARD.encode(self.as_ref())
}

fn decode_base64(&self) -> Result<Vec<u8>, base64::DecodeError> {
general_purpose::STANDARD.decode(self.as_ref())
}
}

impl<T: AsRef<[u8]>> Base64Utils for T {}
9 changes: 9 additions & 0 deletions crates/lib/src/cipher/aes256_gcm.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use crate::*;

pub struct AES256GCM;

impl Cipher for AES256GCM {
fn authorization_tag_size(&self) -> usize {
16
}
}
3 changes: 3 additions & 0 deletions crates/lib/src/cipher/core.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub trait Cipher {
fn authorization_tag_size(&self) -> usize;
}
10 changes: 10 additions & 0 deletions crates/lib/src/cipher/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
mod core;
pub use core::Cipher;

mod variant;
pub use variant::CipherVariant;

#[cfg(feature = "aes-gcm")]
mod aes256_gcm;
#[cfg(feature = "aes-gcm")]
pub use aes256_gcm::AES256GCM;
37 changes: 37 additions & 0 deletions crates/lib/src/cipher/variant.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use strum::{AsRefStr, EnumString};

use crate::*;

#[derive(Debug, PartialEq, AsRefStr, EnumString)]
pub enum CipherVariant {
#[cfg(feature = "aes-gcm")]
#[strum(serialize = "AES256_GCM")]
AES256GCM,
}

impl CipherVariant {
pub fn cipher(&self) -> &dyn Cipher {
match self {
#[cfg(feature = "aes-gcm")]
CipherVariant::AES256GCM => &AES256GCM,
}
}
}

#[cfg(test)]
mod tests {
#[cfg(feature = "aes-gcm")]
mod aes_gcm {
use crate::*;

#[test]
fn displays_aes256_gcm_cipher() {
assert_eq!("AES256_GCM", CipherVariant::AES256GCM.as_ref())
}

#[test]
fn parses_aes256_gcm_cipher() {
assert_eq!(CipherVariant::AES256GCM, "AES256_GCM".parse::<CipherVariant>().unwrap())
}
}
}
64 changes: 22 additions & 42 deletions crates/lib/src/data_key.rs
Original file line number Diff line number Diff line change
@@ -1,53 +1,43 @@
use rand::RngCore;
use derive_more::{AsMut, AsRef};

pub const DATA_KEY_BYTE_SIZE: usize = 32;
use crate::*;

const DATA_KEY_SIZE: usize = 32;

// FIXME: zeroize upon drop?
#[derive(Debug, PartialEq)]
pub struct DataKey([u8; DATA_KEY_BYTE_SIZE]);
#[derive(Debug, PartialEq, AsRef, AsMut)]
#[as_ref(forward)]
#[as_mut(forward)]
pub struct DataKey(RngKey<DATA_KEY_SIZE>);

impl DataKey {
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
// Assumed to be cryptographically secure. Uses ChaCha12 as
// the PRNG with OS provided RNG (e.g getrandom) for the
// initial seed.
//
// https://docs.rs/rand/latest/rand/rngs/struct.ThreadRng.html
let mut rand = rand::thread_rng();

let mut inner = [0u8; DATA_KEY_BYTE_SIZE];
rand.fill_bytes(&mut inner);
Self(inner)
}

pub fn empty() -> Self {
Self([0; DATA_KEY_BYTE_SIZE])
Self(RngKey::new())
}
}

impl AsRef<[u8]> for DataKey {
fn as_ref(&self) -> &[u8] {
self.0.as_slice()
pub const fn empty() -> Self {
Self(RngKey::empty())
}
}

impl AsMut<[u8]> for DataKey {
fn as_mut(&mut self) -> &mut [u8] {
self.0.as_mut_slice()
pub const fn byte_size() -> usize {
RngKey::<{ DATA_KEY_SIZE }>::byte_size()
}
}

#[cfg(feature = "test-utils")]
mod test_utils {
mod mock {
use crate::*;

impl MockTestUtil for DataKey {
fn mock() -> Self {
Self([
67, 11, 25, 39, 242, 246, 79, 131, 60, 80, 226, 83, 115, 116, 50, 131, 39, 148, 220, 226, 136, 158, 165, 19, 155, 218, 16,
53, 47, 24, 192, 26,
])
Self(
[
254, 79, 93, 103, 195, 165, 169, 238, 35, 187, 236, 95, 222, 243, 40, 26, 130, 128, 59, 176, 15, 195, 55, 93, 129, 212,
57, 80, 15, 181, 72, 114,
]
.into(),
)
}
}
}
Expand All @@ -58,16 +48,6 @@ mod tests {

#[test]
fn data_key_is_256_bits() {
assert_eq!(256, DATA_KEY_BYTE_SIZE * 8)
}

#[test]
fn new_data_key_not_zeroed() {
assert_ne!([0; DATA_KEY_BYTE_SIZE], DataKey::new().as_ref())
}

#[test]
fn seemingly_random() {
assert_ne!(DataKey::new(), DataKey::new())
assert_eq!(256, DATA_KEY_SIZE * 8)
}
}
56 changes: 56 additions & 0 deletions crates/lib/src/encrypted_value/data.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
use derive_more::AsRef;

use crate::*;

#[derive(Debug, PartialEq)]
pub struct EncryptedValueData(Vec<u8>);

#[derive(AsRef)]
#[as_ref(forward)]
pub struct EncryptedValueDataAuthorizationTag<'a>(&'a [u8]);

#[derive(AsRef)]
#[as_ref(forward)]
pub struct EncryptedValueDataExceptTag<'a>(&'a [u8]);

impl EncryptedValueData {
pub fn tag(&self, cipher: &dyn Cipher) -> EncryptedValueDataAuthorizationTag {
EncryptedValueDataAuthorizationTag(&self.0[self.cipher_authorization_tag_start_index(cipher)..])
}

pub fn except_tag(&self, cipher: &dyn Cipher) -> EncryptedValueDataExceptTag {
EncryptedValueDataExceptTag(&self.0[..self.cipher_authorization_tag_start_index(cipher)])
}

fn cipher_authorization_tag_start_index(&self, cipher: &dyn Cipher) -> usize {
self.0
.len()
.checked_sub(cipher.authorization_tag_size())
.expect("minimum encrypted value length less than cipher authorization tag size")
}
}

#[cfg(feature = "test-utils")]
mod mock {
use super::*;

impl MockStringTestUtil for EncryptedValueDataExceptTag<'_> {
fn mock_string() -> String {
"3S1E9am/".to_string()
}
}

impl MockStringTestUtil for EncryptedValueDataAuthorizationTag<'_> {
fn mock_string() -> String {
"nQUDkuh0OR1cjR5hGC5jOw==".to_string()
}
}

impl MockTestUtil for EncryptedValueData {
fn mock() -> Self {
Self(vec![
221, 45, 68, 245, 169, 191, 157, 5, 3, 146, 232, 116, 57, 29, 92, 141, 30, 97, 24, 46, 99, 59,
])
}
}
}
23 changes: 23 additions & 0 deletions crates/lib/src/encrypted_value/metadata.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use crate::*;

#[derive(Debug, PartialEq)]
pub struct EncryptedValueMetaData {
pub cipher_variant: CipherVariant,
pub initial_value: InitialValue,
pub value_type: ValueType,
}

#[cfg(feature = "test-utils")]
mod mock {
use super::*;

impl MockTestUtil for EncryptedValueMetaData {
fn mock() -> Self {
Self {
cipher_variant: CipherVariant::AES256GCM,
initial_value: MockTestUtil::mock(),
value_type: ValueType::String,
}
}
}
}
8 changes: 8 additions & 0 deletions crates/lib/src/encrypted_value/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
mod data;
pub use data::{EncryptedValueData, EncryptedValueDataAuthorizationTag, EncryptedValueDataExceptTag};

mod metadata;
pub use metadata::EncryptedValueMetaData;

mod value;
pub use value::EncryptedValue;
79 changes: 79 additions & 0 deletions crates/lib/src/encrypted_value/value.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// GOAL: serialize age into
// ENC[AES256_GCM,data:EjRPNlhx,iv:XmS4b2ZqB39Qjpl/IQRm36KLclV8wXuBjuZsw4yekcU=,tag:
// SWK3XZBBUA49muEyeqld4g==,type:str]

use std::fmt::{Display, Formatter};

use crate::*;

#[derive(Debug, PartialEq)]
pub struct EncryptedValue {
data: EncryptedValueData,
metadata: EncryptedValueMetaData,
}

impl Display for EncryptedValue {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"ENC[{},data:{},iv:{},tag:{},type:{}]",
self.metadata.cipher_variant.as_ref(),
self.data.except_tag(self.metadata.cipher_variant.cipher()).encode_base64(),
self.metadata.initial_value.encode_base64(),
self.data.tag(self.metadata.cipher_variant.cipher()).encode_base64(),
self.metadata.value_type.as_ref(),
)
}
}

mod base64 {
use base64::{engine::general_purpose, Engine};

pub trait Base64
where
Self: AsRef<[u8]>,
{
fn as_base64(&self) -> String {
general_purpose::STANDARD.encode(self.as_ref())
}
}

impl<T: AsRef<[u8]>> Base64 for T {}
}

#[cfg(feature = "test-utils")]
mod mock {
use super::*;

impl MockTestUtil for EncryptedValue {
fn mock() -> Self {
Self {
data: MockTestUtil::mock(),
metadata: MockTestUtil::mock(),
}
}
}

impl MockStringTestUtil for EncryptedValue {
fn mock_string() -> String {
format!(
"ENC[AES256_GCM,data:{},iv:kwtVOk4u/wLHMovHYG2ngLv+uM8U9UJrIxjS6zCKmVY=,tag:{},type:str]",
EncryptedValueDataExceptTag::mock_string(),
EncryptedValueDataAuthorizationTag::mock_string()
)
}
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn displays_value_encryption_content() {
DisplayTestUtils::assert_display::<EncryptedValue>()
}

#[test]
fn parses_value_encryption_content() {}
}
Loading

0 comments on commit aeb472f

Please sign in to comment.