Skip to content
Closed
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
4 changes: 4 additions & 0 deletions crates/cmr-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,8 @@ time = "0.3.44"
url = "2.5.7"

[dev-dependencies]
hex = "0.4.3"
hkdf = "0.12.4"
hmac = "0.12.1"
proptest = "1.9.0"
sha2 = "0.10.9"
138 changes: 138 additions & 0 deletions crates/cmr-core/tests/audit_deviations.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
use cmr_core::policy::RoutingPolicy;
use cmr_core::protocol::{CmrMessage, CmrTimestamp, MessageId, Signature, TransportKind};
use cmr_core::router::{CompressionError, CompressionOracle, Router};
use hkdf::Hkdf;
use hmac::{Hmac, Mac};
use rand::RngCore;
use sha2::Sha256;

struct StubOracle;

impl CompressionOracle for StubOracle {
fn compression_distance(&self, _left: &[u8], _right: &[u8]) -> Result<f64, CompressionError> {
Ok(0.5)
}

fn intrinsic_dependence(&self, _data: &[u8], _max_order: i64) -> Result<f64, CompressionError> {
Ok(0.5)
}
}

#[test]
fn verify_hmac_sha256_signature_format() {
let mut key_bytes = [0u8; 32];

Check failure

Code scanning / CodeQL

Hard-coded cryptographic value Critical test

This hard-coded value is used as
a key
.
This hard-coded value is used as
a key
.

Copilot Autofix

AI 2 months ago

Copilot could not generate an autofix suggestion

Copilot could not generate an autofix suggestion for this alert. Try pushing a new commit or if the problem persists contact support.

rand::rng().fill_bytes(&mut key_bytes);
let key = &key_bytes;

let body = b"hello world";
let mut msg = CmrMessage {
signature: Signature::Unsigned,
header: vec![MessageId {
timestamp: CmrTimestamp::parse("2030/01/01 00:00:00").unwrap(),
address: "http://alice".to_owned(),
}],
body: body.to_vec(),
};

// Sign using library function
msg.sign_with_key(key);

// Verify format is Signature::Sha256
let digest = match msg.signature {
Signature::Sha256(d) => d,
Comment on lines +41 to +42
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Borrow signature instead of moving it out of msg

Matching on msg.signature here moves the signature field out of msg, and the test then calls msg.payload_without_signature_line() afterward; this causes a partial-move compile error (use of partially moved value) when building the test target. Borrowing the signature (match &msg.signature) or otherwise avoiding moving it is required for this test to compile.

Useful? React with 👍 / 👎.

_ => panic!("Expected Sha256 signature"),
};

// Calculate HMAC-SHA256 manually
let mut mac = Hmac::<Sha256>::new_from_slice(key).expect("HMAC can take key of any size");
mac.update(&msg.payload_without_signature_line());
let expected_digest = mac.finalize().into_bytes();

assert_eq!(
digest.as_slice(),
expected_digest.as_slice(),
"HMAC-SHA256 verification failed"
);
}

#[test]
fn verify_hkdf_sha256_key_derivation() {
let local = "http://alice";
let remote = "https://bob"; // HTTPS required for ClearKey

let mut clear_key = vec![0u8; 32];
rand::rng().fill_bytes(&mut clear_key);
let label = b"clear";

// Setup router
let policy = RoutingPolicy::default();
let mut router = Router::new(local.to_owned(), policy, StubOracle);

// Simulate Clear Key Exchange message from remote
// Format: Clear key exchange=<hex>.
let key_hex = hex::encode(&clear_key);
let body = format!("Clear key exchange={}.", key_hex).into_bytes();

let msg = CmrMessage {
signature: Signature::Unsigned,
header: vec![MessageId {
timestamp: CmrTimestamp::parse("2030/01/01 00:00:00").unwrap(),
address: remote.to_owned(),
}],
body,
};

// Process incoming message
// TransportKind::Https is required for ClearKey exchange
let outcome = router.process_incoming(
&msg.to_bytes(),
TransportKind::Https,
CmrTimestamp::parse("2030/01/01 00:00:01").unwrap(),
);

assert!(
outcome.accepted,
"Clear key exchange message was rejected: {:?}",
outcome.drop_reason
);
assert!(
outcome.key_exchange_control,
"Not identified as key exchange control"
);

// Retrieve derived key from router
let derived_key = router.shared_key(remote).expect("Key not stored in router");

// Calculate HKDF-SHA256 manually
// Logic from router.rs: derive_exchange_key_from_bytes
// Salt: b"cmr-v1-key-exchange"
// IKM: clear_key
// Info: "cmr\0" + label + "\0" + sorted(local, remote)

let hk = Hkdf::<Sha256>::new(Some(b"cmr-v1-key-exchange"), &clear_key);

let (left, right) = if local <= remote {
(local.as_bytes(), remote.as_bytes())
} else {
(remote.as_bytes(), local.as_bytes())
};

let mut info = Vec::new();
info.extend_from_slice(b"cmr");
info.push(0);
info.extend_from_slice(label);
info.push(0);
info.extend_from_slice(left);
info.push(0);
info.extend_from_slice(right);

let mut expected_key = [0u8; 32];
hk.expand(&info, &mut expected_key)
.expect("HKDF expand failed");

assert_eq!(
derived_key,
expected_key.as_slice(),
"HKDF-SHA256 key derivation mismatch"
);
}
Loading