Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0507637286 | |||
| 0d64e342da | |||
| 46c7eaf4e5 | |||
| a9bbd2e346 | |||
| bcb7319bb1 | |||
| 703b3dad13 | |||
| cfa17f1ed1 |
Generated
+2
-2
@@ -993,9 +993,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "blake3"
|
||||
version = "1.8.2"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0"
|
||||
checksum = "b17679a8d69b6d7fd9cd9801a536cec9fa5e5970b69f9d4747f70b39b031f5e7"
|
||||
dependencies = [
|
||||
"arrayref",
|
||||
"arrayvec",
|
||||
|
||||
+3
-1
@@ -229,7 +229,9 @@ base85rs = "0.1.3"
|
||||
bincode = "1.3.3"
|
||||
bip39 = { version = "2.0.0", features = ["zeroize"] }
|
||||
bitvec = "1.0.0"
|
||||
blake3 = "1.7.0"
|
||||
# Pin: blake3 1.8+ depends on digest 0.11 for `traits-preview`, while workspace hmac/digest stay on 0.10.
|
||||
# Unpin after upgrading workspace `digest` + `hmac` + `sha2` (and dependents) to the 0.11 stack.
|
||||
blake3 = { version = "=1.7.0", default-features = true }
|
||||
bloomfilter = "3.0.1"
|
||||
bs58 = "0.5.1"
|
||||
bytecodec = "0.4.15"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use nym_crypto::{blake3, hmac::hmac::digest::ExtendableOutput};
|
||||
use nym_crypto::blake3;
|
||||
|
||||
use crate::error::{
|
||||
MaskedByteError,
|
||||
@@ -37,7 +37,8 @@ impl MaskedByte {
|
||||
hasher.update(mask);
|
||||
// avoid zero update
|
||||
hasher.update(&[0xFF, byte]);
|
||||
hasher.finalize_xof_into(&mut output);
|
||||
let mut xof = hasher.finalize_xof();
|
||||
xof.fill(&mut output);
|
||||
|
||||
Self(output)
|
||||
}
|
||||
@@ -66,7 +67,8 @@ impl MaskedByte {
|
||||
for i in supported_versions {
|
||||
let mut t_hasher = hasher.clone();
|
||||
t_hasher.update(&[*i]);
|
||||
t_hasher.finalize_xof_into(&mut buf);
|
||||
let mut xof = t_hasher.finalize_xof();
|
||||
xof.fill(&mut buf);
|
||||
if buf == self.0 {
|
||||
return Ok(*i);
|
||||
}
|
||||
|
||||
@@ -1,24 +1,78 @@
|
||||
// Copyright 2023-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use nym_ip_packet_requests::response_helpers::IprResponseError;
|
||||
use nym_sdk::mixnet::ReconstructedMessage;
|
||||
use tracing::debug;
|
||||
|
||||
use crate::{current::VERSION as CURRENT_VERSION, error::Result};
|
||||
use crate::error::{Error, Result};
|
||||
|
||||
pub(crate) fn check_ipr_message_version(message: &ReconstructedMessage) -> Result<()> {
|
||||
nym_ip_packet_requests::response_helpers::check_ipr_message_version(
|
||||
&message.message,
|
||||
CURRENT_VERSION,
|
||||
)
|
||||
.map_err(|e| match e {
|
||||
IprResponseError::NoVersionByte => crate::Error::NoVersionInMessage,
|
||||
IprResponseError::VersionMismatch { expected, received } if received < expected => {
|
||||
crate::Error::ReceivedResponseWithOldVersion { expected, received }
|
||||
/// Minimum wire version accepted from the IPR.
|
||||
const MIN_ACCEPTED_VERSION: u8 = 8;
|
||||
/// Maximum wire version accepted from the IPR.
|
||||
const MAX_ACCEPTED_VERSION: u8 = 9;
|
||||
|
||||
fn check_ipr_wire_reply_version(version: u8) -> Result<()> {
|
||||
if version >= MIN_ACCEPTED_VERSION && version <= MAX_ACCEPTED_VERSION {
|
||||
if version == MIN_ACCEPTED_VERSION {
|
||||
// v8 reply: IPR exit is on the older protocol version, still compatible.
|
||||
debug!("Received IPR response with wire version v{version} (accepting v8 and v9)");
|
||||
}
|
||||
IprResponseError::VersionMismatch { expected, received } => {
|
||||
crate::Error::ReceivedResponseWithNewVersion { expected, received }
|
||||
}
|
||||
_ => crate::Error::NoVersionInMessage,
|
||||
return Ok(());
|
||||
}
|
||||
if version < MIN_ACCEPTED_VERSION {
|
||||
return Err(Error::ReceivedResponseWithOldVersion {
|
||||
expected: MIN_ACCEPTED_VERSION,
|
||||
received: version,
|
||||
});
|
||||
}
|
||||
Err(Error::ReceivedResponseWithNewVersion {
|
||||
expected: MAX_ACCEPTED_VERSION,
|
||||
received: version,
|
||||
})
|
||||
}
|
||||
|
||||
/// IPR responses on the wire may be v8 or v9 (identical payload layout; version byte differs).
|
||||
pub(crate) fn check_ipr_message_version(message: &ReconstructedMessage) -> Result<()> {
|
||||
let version = message
|
||||
.message
|
||||
.first()
|
||||
.copied()
|
||||
.ok_or(Error::NoVersionInMessage)?;
|
||||
check_ipr_wire_reply_version(version)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{MAX_ACCEPTED_VERSION, MIN_ACCEPTED_VERSION, check_ipr_wire_reply_version};
|
||||
use crate::Error;
|
||||
|
||||
#[test]
|
||||
fn wire_reply_accepts_v8_and_v9() {
|
||||
assert!(check_ipr_wire_reply_version(8).is_ok());
|
||||
assert!(check_ipr_wire_reply_version(9).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wire_reply_rejects_older_than_v8() {
|
||||
let err = check_ipr_wire_reply_version(7).unwrap_err();
|
||||
match err {
|
||||
Error::ReceivedResponseWithOldVersion { expected, received } => {
|
||||
assert_eq!(expected, MIN_ACCEPTED_VERSION);
|
||||
assert_eq!(received, 7);
|
||||
}
|
||||
_ => panic!("unexpected error: {err:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wire_reply_rejects_newer_than_v9() {
|
||||
let err = check_ipr_wire_reply_version(10).unwrap_err();
|
||||
match err {
|
||||
Error::ReceivedResponseWithNewVersion { expected, received } => {
|
||||
assert_eq!(expected, MAX_ACCEPTED_VERSION);
|
||||
assert_eq!(received, 10);
|
||||
}
|
||||
_ => panic!("unexpected error: {err:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
// Copyright 2023-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
//! Anonymous IPR connect uses **v8** on the wire so exits that reject non-stream v9 still answer.
|
||||
//! **v9** is re-exported for code paths that use LP Stream framing. Incoming IPR responses may be **v8 or v9** (same bincode shape).
|
||||
|
||||
mod connect;
|
||||
mod error;
|
||||
mod helpers;
|
||||
@@ -10,5 +13,7 @@ pub use connect::IprClientConnect;
|
||||
pub use error::Error;
|
||||
pub use listener::{IprListener, MixnetMessageOutcome};
|
||||
|
||||
// Re-export the currently used version
|
||||
pub use nym_ip_packet_requests::v9 as current;
|
||||
pub use nym_ip_packet_requests::v8;
|
||||
pub use nym_ip_packet_requests::v9;
|
||||
|
||||
pub use v8 as current;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
use bytes::Bytes;
|
||||
use futures::StreamExt;
|
||||
use nym_ip_packet_requests::{codec::MultiIpPacketCodec, v8::response::ControlResponse};
|
||||
use nym_ip_packet_requests::codec::MultiIpPacketCodec;
|
||||
use nym_sdk::mixnet::ReconstructedMessage;
|
||||
use tokio_util::codec::FramedRead;
|
||||
use tracing::{debug, error, info, warn};
|
||||
@@ -11,7 +11,7 @@ use tracing::{debug, error, info, warn};
|
||||
use crate::{
|
||||
current::{
|
||||
request::{ControlRequest, IpPacketRequest, IpPacketRequestData},
|
||||
response::{InfoLevel, IpPacketResponse, IpPacketResponseData},
|
||||
response::{ControlResponse, InfoLevel, IpPacketResponse, IpPacketResponseData},
|
||||
},
|
||||
helpers::check_ipr_message_version,
|
||||
};
|
||||
|
||||
@@ -10,6 +10,7 @@ use nym_ip_packet_requests::{
|
||||
IpPair, v6::request::IpPacketRequest as IpPacketRequestV6,
|
||||
v7::request::IpPacketRequest as IpPacketRequestV7,
|
||||
v8::request::IpPacketRequest as IpPacketRequestV8,
|
||||
v9::request::IpPacketRequest as IpPacketRequestV9,
|
||||
};
|
||||
use nym_sdk::mixnet::ReconstructedMessage;
|
||||
use nym_service_provider_requests_common::{Protocol, ServiceProviderType};
|
||||
@@ -131,14 +132,14 @@ impl TryFrom<&ReconstructedMessage> for IpPacketRequest {
|
||||
Ok(IpPacketRequest::from((request_v8, sender_tag)))
|
||||
}
|
||||
9 => {
|
||||
let request_v8 = IpPacketRequestV8::from_reconstructed_message(reconstructed)
|
||||
let request_v9 = IpPacketRequestV9::from_reconstructed_message(reconstructed)
|
||||
.map_err(
|
||||
|source| IpPacketRouterError::FailedToDeserializeTaggedPacket { source },
|
||||
)?;
|
||||
let sender_tag = reconstructed
|
||||
.sender_tag
|
||||
.ok_or(IpPacketRouterError::MissingSenderTag)?;
|
||||
Ok(v9::convert(request_v8, sender_tag))
|
||||
Ok(v9::convert(request_v9, sender_tag))
|
||||
}
|
||||
_ => {
|
||||
log::info!("Received packet with invalid version: v{request_version}");
|
||||
|
||||
@@ -130,7 +130,11 @@ impl VersionedResponse {
|
||||
ClientVersion::V6 => IpPacketResponseV6::try_from(self)?.to_bytes(),
|
||||
ClientVersion::V7 => IpPacketResponseV7::try_from(self)?.to_bytes(),
|
||||
ClientVersion::V8 => IpPacketResponseV8::try_from(self)?.to_bytes(),
|
||||
ClientVersion::V9 => IpPacketResponseV8::try_from(self)?.to_bytes(),
|
||||
ClientVersion::V9 => {
|
||||
let mut resp = IpPacketResponseV8::try_from(self)?;
|
||||
resp.version = nym_ip_packet_requests::v9::VERSION;
|
||||
resp.to_bytes()
|
||||
}
|
||||
}
|
||||
.map_err(|err| IpPacketRouterError::FailedToSerializeResponsePacket { source: err })
|
||||
}
|
||||
|
||||
@@ -37,6 +37,15 @@ type TunDevice = crate::non_linux_dummy::DummyDevice;
|
||||
#[cfg(target_os = "linux")]
|
||||
type TunDevice = tokio_tun::Tun;
|
||||
|
||||
/// v9+ on non-stream sphinx is limited to [`ControlRequest::DynamicConnect`] (handshake). Other
|
||||
/// v9+ payloads must be sent inside LP Stream frames (`stream_id` is `Some` on dispatch).
|
||||
fn allows_non_stream_v9_ipr_request(request: &IpPacketRequest) -> bool {
|
||||
matches!(
|
||||
request,
|
||||
IpPacketRequest::Control(ControlRequest::DynamicConnect(_))
|
||||
)
|
||||
}
|
||||
|
||||
// #[cfg(target_os = "linux")]
|
||||
pub(crate) struct MixnetListener {
|
||||
// The configuration for the mixnet listener
|
||||
@@ -560,9 +569,9 @@ impl MixnetListener {
|
||||
/// # Version / transport enforcement
|
||||
///
|
||||
/// - LP Stream frames (`stream_id` is `Some`) **must** carry v9+ payloads.
|
||||
/// - Non-stream messages (`stream_id` is `None`) **must** be v8 or lower.
|
||||
///
|
||||
/// Messages that violate these rules are dropped.
|
||||
/// - Non-stream sphinx (`stream_id` is `None`): v8 or lower for all messages **except**
|
||||
/// [`ControlRequest::DynamicConnect`], which may use v9 for the anonymous handshake.
|
||||
/// - Other non-stream v9+ payloads are dropped.
|
||||
async fn on_ipr_message(
|
||||
&mut self,
|
||||
reconstructed: ReconstructedMessage,
|
||||
@@ -577,16 +586,14 @@ impl MixnetListener {
|
||||
req => req,
|
||||
}?;
|
||||
|
||||
// Enforce version/transport consistency:
|
||||
// - LP Stream frames must carry v9+ payloads
|
||||
// - Non-stream messages must be v8 or lower
|
||||
// Enforce version/transport consistency (see `on_ipr_message` doc).
|
||||
let version_num = request.version().into_u8();
|
||||
|
||||
if stream_id.is_some() && version_num < 9 {
|
||||
log::warn!("LP Stream frame contains v{version_num} payload, expected v9+; dropping",);
|
||||
return Ok(vec![]);
|
||||
}
|
||||
if stream_id.is_none() && version_num >= 9 {
|
||||
if stream_id.is_none() && version_num >= 9 && !allows_non_stream_v9_ipr_request(&request) {
|
||||
log::warn!("Non-stream message claims v{version_num}, expected v8 or lower; dropping",);
|
||||
return Ok(vec![]);
|
||||
}
|
||||
@@ -693,6 +700,45 @@ pub(crate) type PacketHandleResult = Result<Option<VersionedResponse>>;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
|
||||
|
||||
use super::allows_non_stream_v9_ipr_request;
|
||||
use crate::{
|
||||
clients::ConnectedClientId,
|
||||
messages::{
|
||||
ClientVersion,
|
||||
request::{
|
||||
ControlRequest, DataRequest, DynamicConnectRequest, IpPacketRequest, PingRequest,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn non_stream_v9_allowed_for_dynamic_connect_only() {
|
||||
let sent_by = ConnectedClientId::from(AnonymousSenderTag::from_bytes([9u8; 16]));
|
||||
let dynamic_connect =
|
||||
IpPacketRequest::Control(ControlRequest::DynamicConnect(DynamicConnectRequest {
|
||||
version: ClientVersion::V9,
|
||||
request_id: 1,
|
||||
sent_by,
|
||||
buffer_timeout: None,
|
||||
}));
|
||||
assert!(allows_non_stream_v9_ipr_request(&dynamic_connect));
|
||||
|
||||
let data = IpPacketRequest::Data(DataRequest {
|
||||
version: ClientVersion::V9,
|
||||
ip_packets: bytes::Bytes::new(),
|
||||
});
|
||||
assert!(!allows_non_stream_v9_ipr_request(&data));
|
||||
|
||||
let ping = IpPacketRequest::Control(ControlRequest::Ping(PingRequest {
|
||||
version: ClientVersion::V9,
|
||||
request_id: 2,
|
||||
sent_by: ConnectedClientId::from(AnonymousSenderTag::from_bytes([1u8; 16])),
|
||||
}));
|
||||
assert!(!allows_non_stream_v9_ipr_request(&ping));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lp_stream_frame_detected() {
|
||||
use bytes::BytesMut;
|
||||
|
||||
Reference in New Issue
Block a user