Skip to content

Commit

Permalink
Compute message authentication code from map.
Browse files Browse the repository at this point in the history
  • Loading branch information
gibbz00 committed Dec 18, 2023
1 parent ed45b3c commit 754f139
Show file tree
Hide file tree
Showing 8 changed files with 183 additions and 1 deletion.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ rand = "0.8"
serde = { version = "1", features = ["derive"] }
serde_with = "3"
serde_yaml = "0.9"
sha2 = "0.10.8"
strum = { version = "0.25", features = ["derive"] }
textwrap = "0.16"
thiserror = "1"
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,8 @@ hashicorp_kv | `X` | `X`

(Con: will skip initialization vector reuse for unchanged value.)

- `--ignore-mac` flag. Why?

- [Integrated formatting configuration](https://github.com/getsops/sops#32json-and-json_binary-indentation)

- [Integrated secrets publishing](https://github.com/getsops/sops#219using-the-publish-command)
Expand Down
5 changes: 5 additions & 0 deletions crates/lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ age-test-utils = ["age", "test-utils", "dep:indoc", "dep:textwrap"]
yaml = ["dep:serde_yaml"]
# Ciphers
aes-gcm = ["dep:aes-gcm"]
# Hashers
sha2 = ["dep:sha2"]
# Test utils
test-utils = ["dep:pretty_assertions"]

Expand Down Expand Up @@ -39,5 +41,8 @@ serde_yaml = { workspace = true, optional = true }
# AES_GCM
aes-gcm = { workspace = true, optional = true }

# SHA2
sha2 = { workspace = true, optional = true }

# TEST_UTILS
pretty_assertions = { workspace = true, optional = true }
42 changes: 42 additions & 0 deletions crates/lib/src/cryptography/hasher/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
pub use core::Hasher;
mod core {
use generic_array::{ArrayLength, GenericArray};

pub trait Hasher {
type OutputSize: ArrayLength<u8>;

fn new() -> Self;

fn update(&mut self, input: impl AsRef<[u8]>);

fn finalize(self) -> GenericArray<u8, Self::OutputSize>;
}
}

#[cfg(feature = "sha2")]
pub use sha512::SHA512;
#[cfg(feature = "sha2")]
mod sha512 {
use sha2::{digest::OutputSizeUser, Digest, Sha512};

use crate::*;

#[derive(Debug)]
pub struct SHA512(Sha512);

impl Hasher for SHA512 {
type OutputSize = <Sha512 as OutputSizeUser>::OutputSize;

fn new() -> Self {
Self(Sha512::new())
}

fn update(&mut self, input: impl AsRef<[u8]>) {
self.0.update(input)
}

fn finalize(self) -> generic_array::GenericArray<u8, Self::OutputSize> {
self.0.finalize()
}
}
}
3 changes: 3 additions & 0 deletions crates/lib/src/cryptography/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,8 @@ pub use encrypted_data::EncryptedData;
mod cipher;
pub use cipher::*;

mod hasher;
pub use hasher::*;

mod integration;
pub use integration::*;
125 changes: 125 additions & 0 deletions crates/lib/src/rops_file/mac.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
use std::ops::Add;

use generic_array::{typenum::Sum, ArrayLength, GenericArray};

use crate::*;

const MAC_ENCRYPTED_ONLY_INIT_BYTES: [u8; 32] = [
0x8a, 0x3f, 0xd2, 0xad, 0x54, 0xce, 0x66, 0x52, 0x7b, 0x10, 0x34, 0xf3, 0xd1, 0x47, 0xbe, 0xb, 0xb, 0x97, 0x5b, 0x3b, 0xf4, 0x4f, 0x72,
0xc6, 0xfd, 0xad, 0xec, 0x81, 0x76, 0xf2, 0x7d, 0x69,
];

#[derive(Debug)]
pub struct Mac<H: Hasher>(GenericArray<u8, Sum<H::OutputSize, H::OutputSize>>)
where
H::OutputSize: Add<H::OutputSize>,
Sum<H::OutputSize, H::OutputSize>: ArrayLength<u8>;

impl<H: Hasher> Mac<H>
where
H::OutputSize: Add<H::OutputSize>,
Sum<H::OutputSize, H::OutputSize>: ArrayLength<u8>,
{
pub fn compute(from_encrypted_values_only: bool, decrypted_map: &RopsMap<Decrypted>) -> Self {
let mut hasher = H::new();
if from_encrypted_values_only {
hasher.update(MAC_ENCRYPTED_ONLY_INIT_BYTES);
}

traverse_map(&mut hasher, from_encrypted_values_only, decrypted_map);

// IMPROVEMENT: would be nice if the heap allocation could be avoided.
return Mac(GenericArray::from_iter(format!("{:X}", hasher.finalize()).into_bytes()));

fn traverse_map<Ha: Hasher>(hasher: &mut Ha, hash_encrypted_values_only: bool, map: &RopsMap<Decrypted>) {
traverse_map_recursive(hasher, hash_encrypted_values_only, map);

fn traverse_map_recursive<H: Hasher>(hasher: &mut H, hash_encrypted_values_only: bool, map: &RopsMap<Decrypted>) {
for (_, tree) in map.iter() {
traverse_tree_recursive(hasher, hash_encrypted_values_only, tree)
}
}

fn traverse_tree_recursive<H: Hasher>(hasher: &mut H, hash_encrypted_values_only: bool, tree: &RopsTree<Decrypted>) {
match tree {
RopsTree::Sequence(sequence) => sequence
.iter()
.for_each(|sub_tree| traverse_tree_recursive(hasher, hash_encrypted_values_only, sub_tree)),
RopsTree::Map(map) => traverse_map_recursive(hasher, hash_encrypted_values_only, map),
RopsTree::Null => (),
RopsTree::Leaf(value) => {
// TODO: use hash_encrypted_only once partial encryption is added
hasher.update(value.as_bytes())
}
}
}
}
}

pub fn encrypt<C: Cipher>(
self,
data_key: &DataKey,
last_modified_date_time: &LastModifiedDateTime,
) -> Result<EncryptedRopsValue<C>, C::Error> {
let mut in_place_buffer = self.0;
let nonce = Nonce::new();
let authorization_tag = C::encrypt(
&nonce,
data_key,
in_place_buffer.as_mut_slice(),
last_modified_date_time.as_ref().to_rfc3339().as_bytes(),
)?;

Ok(EncryptedRopsValue {
data: in_place_buffer.to_vec().into(),
authorization_tag,
nonce,
value_variant: RopsValueVariant::String,
})
}
}

// WORKAROUND: derive proc macro struggles with trait bounds
impl<H: Hasher> PartialEq for Mac<H>
where
H::OutputSize: Add<H::OutputSize>,
Sum<H::OutputSize, H::OutputSize>: ArrayLength<u8>,
{
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}

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

#[cfg(feature = "sha2")]
mod sha2 {
use super::*;

impl<H: Hasher> MockTestUtil for Mac<H>
where
H::OutputSize: Add<H::OutputSize>,
Sum<H::OutputSize, H::OutputSize>: ArrayLength<u8>,
{
fn mock() -> Self {
Self(GenericArray::from_slice(b"A0FBBFF515AC1EF88827C911653675DE4155901880355C59BA4FE4043395A0DE5EA77762EB3CAC54CC6F2B37EDDD916127A32566E810B0A5DADFA2F60B061331").to_owned())
}
}
}
}

#[cfg(test)]
mod tests {
#[cfg(feature = "sha2")]
mod sha2 {

use crate::*;

#[test]
fn computes_mac() {
assert_eq!(Mac::mock(), Mac::<SHA512>::compute(false, &RopsMap::mock()))
}
}
}
3 changes: 2 additions & 1 deletion crates/lib/src/rops_file/metadata/last_modified.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use std::fmt::Display;

use chrono::{DateTime, Utc};
use derive_more::AsRef;
use serde::{Deserialize, Serialize};

#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[derive(Debug, PartialEq, Serialize, Deserialize, AsRef)]
pub struct LastModifiedDateTime(DateTime<Utc>);

impl LastModifiedDateTime {
Expand Down
3 changes: 3 additions & 0 deletions crates/lib/src/rops_file/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,6 @@ pub use state::{Decrypted, Encrypted, RopsFileState};

mod format;
pub use format::*;

mod mac;
pub use mac::Mac;

0 comments on commit 754f139

Please sign in to comment.