Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions enclave/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ aws-lc-rs = { version = "=1.15.2", default-features = false }
cel-interpreter = { version = "=0.10.0", default-features = false, features = ["json", "chrono"] }
chrono = { version = "=0.4.42", default-features = false, features = ["now"] }
data-encoding = { version = "=2.9.0", default-features = false, features = ["alloc"] }
rayon = { version = "=1.10.0", default-features = false }
serde = { version = "=1.0.228", default-features = false, features = ["derive"] }
serde_json = { version = "=1.0.145", default-features = false }
rustls = { version = "=0.23.35", default-features = false, features = ["aws_lc_rs", "prefer-post-quantum"] }
Expand Down
5 changes: 5 additions & 0 deletions enclave/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@

pub const ENCLAVE_PORT: u32 = 5050;

/// Maximum concurrent connections to prevent resource exhaustion DoS attacks.
/// Each connection spawns a thread (~8KB stack minimum), so this limits memory usage.
/// With 32 connections and 10MB max message size, worst case is ~320MB memory.
pub const MAX_CONCURRENT_CONNECTIONS: usize = 32;

/// Maximum allowed message size (10 MB) to prevent memory exhaustion DoS attacks
pub const MAX_MESSAGE_SIZE: u64 = 10 * 1024 * 1024;

Expand Down
141 changes: 80 additions & 61 deletions enclave/src/expressions.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: MIT-0

use std::collections::BTreeMap;
use std::collections::HashMap;

use anyhow::{Result, anyhow, bail};
use cel_interpreter::Value as celValue;
Expand All @@ -12,9 +12,9 @@ use crate::constants::MAX_EXPRESSION_LENGTH;
use crate::functions;

pub fn execute_expressions(
fields: &BTreeMap<String, Value>,
expressions: &BTreeMap<String, String>,
) -> Result<BTreeMap<String, Value>> {
fields: &HashMap<String, Value>,
expressions: &HashMap<String, String>,
) -> Result<HashMap<String, Value>> {
if expressions.is_empty() {
return Ok(fields.clone());
}
Expand Down Expand Up @@ -51,7 +51,8 @@ pub fn execute_expressions(
context.add_function("date", functions::date);
context.add_function("age", functions::age);

let mut transformed: BTreeMap<String, Value> = BTreeMap::new();
let mut transformed: HashMap<String, Value> =
HashMap::with_capacity(fields.len() + expressions.len());

for (field, decrypted_value) in fields {
context
Expand Down Expand Up @@ -92,7 +93,7 @@ pub fn execute_expressions(
mod tests {
use super::*;
use proptest::prelude::*;
use std::collections::BTreeMap;
use std::collections::HashMap;

// **Feature: enclave-improvements, Property 5: Expression failure fallback**
// **Validates: Requirements 8.2**
Expand All @@ -111,9 +112,9 @@ mod tests {
/// Simulates the fallback behavior from main.rs:
/// When execute_expressions returns Err, return the original fields unchanged.
fn execute_with_fallback(
fields: &BTreeMap<String, Value>,
expressions: &BTreeMap<String, String>,
) -> BTreeMap<String, Value> {
fields: &HashMap<String, Value>,
expressions: &HashMap<String, String>,
) -> HashMap<String, Value> {
match execute_expressions(fields, expressions) {
Ok(result) => result,
Err(_) => fields.clone(),
Expand All @@ -133,7 +134,7 @@ mod tests {
invalid_expr_type in 0usize..3
) {
// Create original fields
let mut fields: BTreeMap<String, Value> = BTreeMap::new();
let mut fields: HashMap<String, Value> = HashMap::new();
fields.insert(field_name.clone(), Value::String(field_value.clone()));

// Create an invalid expression that will fail to execute gracefully
Expand All @@ -144,7 +145,7 @@ mod tests {
_ => "undefined_var.to_uppercase()".to_string(),
};

let mut expressions: BTreeMap<String, String> = BTreeMap::new();
let mut expressions: HashMap<String, String> = HashMap::new();
expressions.insert("result".to_string(), invalid_expression);

// Execute with fallback (simulating main.rs behavior)
Expand Down Expand Up @@ -173,7 +174,7 @@ mod tests {
use std::hash::{Hash, Hasher};

// Generate deterministic field names and values based on seed
let mut fields: BTreeMap<String, Value> = BTreeMap::new();
let mut fields: HashMap<String, Value> = HashMap::new();
for i in 0..num_fields {
let mut hasher = DefaultHasher::new();
(field_seed, i).hash(&mut hasher);
Expand All @@ -184,7 +185,7 @@ mod tests {
}

// Create an expression that references an undefined variable
let mut expressions: BTreeMap<String, String> = BTreeMap::new();
let mut expressions: HashMap<String, String> = HashMap::new();
expressions.insert("computed".to_string(), "undefined_var.to_uppercase()".to_string());

// Execute with fallback
Expand Down Expand Up @@ -212,10 +213,10 @@ mod tests {
field_name in "[a-z][a-z0-9_]{0,10}",
field_value in "[a-zA-Z0-9 ]{1,20}"
) {
let mut fields: BTreeMap<String, Value> = BTreeMap::new();
let mut fields: HashMap<String, Value> = HashMap::new();
fields.insert(field_name.clone(), Value::String(field_value.clone()));

let expressions: BTreeMap<String, String> = BTreeMap::new();
let expressions: HashMap<String, String> = HashMap::new();

let result = execute_expressions(&fields, &expressions).unwrap();

Expand All @@ -233,11 +234,11 @@ mod tests {
// Generate lowercase string to test to_uppercase
field_value in "[a-z]{1,10}"
) {
let mut fields: BTreeMap<String, Value> = BTreeMap::new();
let mut fields: HashMap<String, Value> = HashMap::new();
fields.insert(field_name.clone(), Value::String(field_value.clone()));

// Create expression to uppercase the field
let mut expressions: BTreeMap<String, String> = BTreeMap::new();
let mut expressions: HashMap<String, String> = HashMap::new();
expressions.insert(field_name.clone(), format!("{}.to_uppercase()", field_name));

let result = execute_expressions(&fields, &expressions).unwrap();
Expand All @@ -253,67 +254,65 @@ mod tests {

#[test]
fn test_skip_expressions() {
let expressions = BTreeMap::new();
let expressions = HashMap::new();

let expected: BTreeMap<String, Value> =
BTreeMap::from([("first_name".to_string(), "Bob".into())]);
let expected: HashMap<String, Value> =
HashMap::from([("first_name".to_string(), "Bob".into())]);

let actual = execute_expressions(&expected, &expressions).unwrap();
assert_eq!(actual, expected);
}

#[test]
fn test_execute_transforms() {
let expressions: BTreeMap<String, String> = BTreeMap::from([(
let expressions: HashMap<String, String> = HashMap::from([(
"first_name".to_string(),
"first_name.to_uppercase()".to_string(),
)]);

let fields: BTreeMap<String, Value> =
BTreeMap::from([("first_name".to_string(), "Bob".into())]);
let fields: HashMap<String, Value> =
HashMap::from([("first_name".to_string(), "Bob".into())]);

let expected: BTreeMap<String, Value> =
BTreeMap::from([("first_name".to_string(), "BOB".into())]);
let expected: HashMap<String, Value> =
HashMap::from([("first_name".to_string(), "BOB".into())]);

let actual = execute_expressions(&fields, &expressions).unwrap();
assert_eq!(actual, expected);
}

#[test]
fn test_base64() {
let expressions: BTreeMap<String, String> = BTreeMap::from([(
let expressions: HashMap<String, String> = HashMap::from([(
"first_name".into(),
"first_name.base64_encode().base64_decode()".into(),
)]);

let fields: BTreeMap<String, Value> = BTreeMap::from([("first_name".into(), "Bob".into())]);
let fields: HashMap<String, Value> = HashMap::from([("first_name".into(), "Bob".into())]);

let expected: BTreeMap<String, Value> =
BTreeMap::from([("first_name".into(), "Bob".into())]);
let expected: HashMap<String, Value> = HashMap::from([("first_name".into(), "Bob".into())]);

let actual = execute_expressions(&fields, &expressions).unwrap();
assert_eq!(actual, expected);
}

#[test]
fn test_hex() {
let expressions: BTreeMap<String, String> = BTreeMap::from([(
let expressions: HashMap<String, String> = HashMap::from([(
"first_name".into(),
"first_name.hex_encode().hex_decode()".into(),
)]);

let fields: BTreeMap<String, Value> = BTreeMap::from([("first_name".into(), "Bob".into())]);
let fields: HashMap<String, Value> = HashMap::from([("first_name".into(), "Bob".into())]);

let expected: BTreeMap<String, Value> =
BTreeMap::from([("first_name".into(), "Bob".into())]);
let expected: HashMap<String, Value> = HashMap::from([("first_name".into(), "Bob".into())]);

let actual = execute_expressions(&fields, &expressions).unwrap();
assert_eq!(actual, expected);
}

#[test]
fn test_functions() {
let expressions: BTreeMap<String, String> = BTreeMap::from([
let expressions: HashMap<String, String> = HashMap::from([
("is_empty".into(), "''.is_empty() == true".into()),
("to_lowercase".into(), "'Bob'.to_lowercase()".into()),
("to_uppercase".into(), "'Bob'.to_uppercase()".into()),
Expand All @@ -327,43 +326,63 @@ mod tests {
("date".into(), "date('1979-04-05')".into()),
]);

let fields = BTreeMap::default();
let expected: BTreeMap<String, Value> =
BTreeMap::from([
("is_empty".into(), true.into()),
("to_lowercase".into(), "bob".into()),
("to_uppercase".into(), "BOB".into()),
("sha256".into(), "cd9fb1e148ccd8442e5aa74904cc73bf6fb54d1d54d333bd596aa9bb4bb4e961".into()),
("sha384".into(), "b7808c5991933fa578a7d41a177b013f2f745a2c4fac90d1e8631a1ce21918dc5fee092a290a6443e47649989ec9871f".into()),
("sha512".into(), "0c3e99453b4ae505617a3c9b6ce73fc3cd13ddc3b2e2237459710a57f8ec6d26d056db144ff7c71b00ed4e4c39716e9e2099c8076e604423dd74554d4db1e649".into()),
("hex_encode".into(), "426f62".into()),
("hex_decode".into(), "Bob".into()),
("base64_encode".into(), "Qm9i".into()),
("base64_decode".into(), "Bob".into()),
("date".into(), "1979-04-05T00:00:00+00:00".into()),
]);

let fields = HashMap::default();
// Note: Using Vec for comparison since HashMap ordering is non-deterministic
let actual = execute_expressions(&fields, &expressions).unwrap();
assert_eq!(actual, expected);

assert_eq!(actual.get("is_empty"), Some(&Value::Bool(true)));
assert_eq!(
actual.get("to_lowercase"),
Some(&Value::String("bob".into()))
);
assert_eq!(
actual.get("to_uppercase"),
Some(&Value::String("BOB".into()))
);
assert_eq!(
actual.get("sha256"),
Some(&Value::String(
"cd9fb1e148ccd8442e5aa74904cc73bf6fb54d1d54d333bd596aa9bb4bb4e961".into()
))
);
assert_eq!(actual.get("sha384"), Some(&Value::String("b7808c5991933fa578a7d41a177b013f2f745a2c4fac90d1e8631a1ce21918dc5fee092a290a6443e47649989ec9871f".into())));
assert_eq!(actual.get("sha512"), Some(&Value::String("0c3e99453b4ae505617a3c9b6ce73fc3cd13ddc3b2e2237459710a57f8ec6d26d056db144ff7c71b00ed4e4c39716e9e2099c8076e604423dd74554d4db1e649".into())));
assert_eq!(
actual.get("hex_encode"),
Some(&Value::String("426f62".into()))
);
assert_eq!(actual.get("hex_decode"), Some(&Value::String("Bob".into())));
assert_eq!(
actual.get("base64_encode"),
Some(&Value::String("Qm9i".into()))
);
assert_eq!(
actual.get("base64_decode"),
Some(&Value::String("Bob".into()))
);
assert_eq!(
actual.get("date"),
Some(&Value::String("1979-04-05T00:00:00+00:00".into()))
);
}

#[test]
fn test_complex() {
let expressions: BTreeMap<String, String> =
BTreeMap::from([("age".into(), "date(birth_date).age()".into())]);
let expressions: HashMap<String, String> =
HashMap::from([("age".into(), "date(birth_date).age()".into())]);

let fields: BTreeMap<String, Value> = BTreeMap::from([
let fields: HashMap<String, Value> = HashMap::from([
("first_name".into(), "Bob".into()),
("birth_date".into(), "1979-01-01".into()),
]);

let expected: BTreeMap<String, Value> = BTreeMap::from([
("first_name".into(), "Bob".into()),
("birth_date".into(), "1979-01-01".into()),
("age".into(), 46.into()),
]);

let actual = execute_expressions(&fields, &expressions).unwrap();
assert_eq!(actual, expected);

assert_eq!(actual.get("first_name"), Some(&Value::String("Bob".into())));
assert_eq!(
actual.get("birth_date"),
Some(&Value::String("1979-01-01".into()))
);
assert_eq!(actual.get("age"), Some(&Value::Number(46.into())));
}
}
Loading