Verify signature if present

This commit is contained in:
Jon Häggblad
2024-05-28 12:22:39 +02:00
parent 70766b6d3e
commit d4d3041816
7 changed files with 161 additions and 43 deletions
Generated
+1
View File
@@ -4781,6 +4781,7 @@ dependencies = [
"bincode",
"bytes",
"nym-bin-common",
"nym-crypto",
"nym-sphinx",
"rand 0.8.5",
"serde",
+1
View File
@@ -12,6 +12,7 @@ license.workspace = true
bincode = { workspace = true }
bytes = { workspace = true }
nym-bin-common = { path = "../bin-common" }
nym-crypto = { path = "../crypto" }
nym-sphinx = { path = "../nymsphinx" }
rand = "0.8.5"
serde = { workspace = true, features = ["derive"] }
+1
View File
@@ -1,5 +1,6 @@
pub mod conversion;
pub mod request;
pub mod response;
pub mod signature;
const VERSION: u8 = 7;
+62 -1
View File
@@ -1,10 +1,14 @@
use nym_crypto::asymmetric::identity;
use nym_sphinx::addressing::clients::Recipient;
use serde::{Deserialize, Serialize};
use time::OffsetDateTime;
use crate::{make_bincode_serializer, IpPair};
use super::VERSION;
use super::{
signature::{SignatureError, SignedRequest},
VERSION,
};
fn generate_random() -> u64 {
use rand::RngCore;
@@ -236,6 +240,25 @@ pub struct SignedStaticConnectRequest {
pub signature: Option<Vec<u8>>,
}
impl SignedRequest for SignedStaticConnectRequest {
fn identity(&self) -> &identity::PublicKey {
self.request.reply_to.identity()
}
fn request(&self) -> Result<Vec<u8>, SignatureError> {
self.request
.to_bytes()
.map_err(|error| SignatureError::RequestSerializationError {
message: "failed to serialize request to binary".to_string(),
error,
})
}
fn signature(&self) -> Option<&Vec<u8>> {
self.signature.as_ref()
}
}
// A dynamic connect request is when the client does not provide the internal IP address it will use
// on the ip packet router, and instead requests one to be assigned to it.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
@@ -274,6 +297,25 @@ pub struct SignedDynamicConnectRequest {
pub signature: Option<Vec<u8>>,
}
impl SignedRequest for SignedDynamicConnectRequest {
fn identity(&self) -> &identity::PublicKey {
self.request.reply_to.identity()
}
fn request(&self) -> Result<Vec<u8>, SignatureError> {
self.request
.to_bytes()
.map_err(|error| SignatureError::RequestSerializationError {
message: "failed to serialize request to binary".to_string(),
error,
})
}
fn signature(&self) -> Option<&Vec<u8>> {
self.signature.as_ref()
}
}
// A disconnect request is when the client wants to disconnect from the ip packet router and free
// up the allocated IP address.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
@@ -300,6 +342,25 @@ pub struct SignedDisconnectRequest {
pub signature: Option<Vec<u8>>,
}
impl SignedRequest for SignedDisconnectRequest {
fn identity(&self) -> &identity::PublicKey {
self.request.reply_to.identity()
}
fn request(&self) -> Result<Vec<u8>, SignatureError> {
self.request
.to_bytes()
.map_err(|error| SignatureError::RequestSerializationError {
message: "failed to serialize request to binary".to_string(),
error,
})
}
fn signature(&self) -> Option<&Vec<u8>> {
self.signature.as_ref()
}
}
// A data request is when the client wants to send an IP packet to a destination.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct DataRequest {
@@ -0,0 +1,54 @@
use nym_crypto::asymmetric::{ed25519::Ed25519RecoveryError, identity};
#[derive(thiserror::Error, Debug)]
pub enum SignatureError {
#[error("signature is missing")]
MissingSignature,
#[error("failed to parse signature: {error}")]
SignatureParseError {
message: String,
error: Ed25519RecoveryError,
},
#[error("failed to serialize request to binary: {message}")]
RequestSerializationError {
message: String,
error: Box<bincode::ErrorKind>,
},
#[error("signature verification failed")]
VerificationFailed {
message: String,
error: identity::SignatureError,
},
}
pub trait SignedRequest {
fn identity(&self) -> &identity::PublicKey;
fn request(&self) -> Result<Vec<u8>, SignatureError>;
fn signature(&self) -> Option<&Vec<u8>>;
fn verify(&self) -> Result<(), SignatureError> {
if let Some(signature) = self.signature() {
let request_as_bytes = self.request()?;
let signature = identity::Signature::from_bytes(signature).map_err(|error| {
SignatureError::SignatureParseError {
message: "failed to parse signature".to_string(),
error,
}
})?;
self.identity()
.verify(request_as_bytes, &signature)
.map_err(|error| SignatureError::VerificationFailed {
message: "signature verification failed".to_string(),
error,
})
} else {
Err(SignatureError::MissingSignature)
}
}
}
@@ -91,6 +91,11 @@ pub enum IpPacketRouterError {
#[error("received empty packet")]
EmptyPacket,
#[error("failed to verify request: {source}")]
FailedToVerifyRequest {
source: nym_ip_packet_requests::v7::signature::SignatureError,
},
}
pub type Result<T> = std::result::Result<T, IpPacketRouterError>;
@@ -4,16 +4,19 @@ use std::{collections::HashMap, net::SocketAddr};
use bytes::{Bytes, BytesMut};
use futures::StreamExt;
use nym_ip_packet_requests::v7::request::IpPacketRequestData;
use nym_ip_packet_requests::v7::signature::SignatureError;
use nym_ip_packet_requests::{
codec::MultiIpPacketCodec,
v6::response::{
DynamicConnectFailureReason, InfoLevel, InfoResponseReply, IpPacketResponse,
StaticConnectFailureReason,
},
v7::request::{
DataRequest, DisconnectRequest, DynamicConnectRequest, IpPacketRequest,
StaticConnectRequest,
v7::{
request::{
DataRequest, DisconnectRequest, DynamicConnectRequest, IpPacketRequest,
IpPacketRequestData, StaticConnectRequest,
},
signature::SignedRequest,
},
IpPair,
};
@@ -543,67 +546,59 @@ impl MixnetListener {
reconstructed.sender_tag
);
// Check version of request
// let version = if let Some(version) = reconstructed.message.first() {
// // The idea is that in the future we can add logic here to parse older versions to stay
// // backwards compatible.
// if *version != nym_ip_packet_requests::CURRENT_VERSION
// && *version != nym_ip_packet_requests::CURRENT_VERSION + 1
// {
// log::info!("Received packet with invalid version: v{version}");
// return Ok(vec![self.on_version_mismatch(*version, &reconstructed)]);
// }
// *version
// };
// Make sure the current version is set to something that we can handle
const _: () = assert!(
nym_ip_packet_requests::CURRENT_VERSION == 6
|| nym_ip_packet_requests::CURRENT_VERSION == 7
);
let version = *reconstructed
let request_version = *reconstructed
.message
.first()
.ok_or(IpPacketRouterError::EmptyPacket)?;
// Check version of request
let request = if version == 6 {
nym_ip_packet_requests::v6::request::IpPacketRequest::from_reconstructed_message(
// Check version of the request and convert to the latest version if necessary
let request = match request_version {
6 => nym_ip_packet_requests::v6::request::IpPacketRequest::from_reconstructed_message(
&reconstructed,
)
.map_err(|err| IpPacketRouterError::FailedToDeserializeTaggedPacket { source: err })?
.into()
} else if version == 7 {
nym_ip_packet_requests::v7::request::IpPacketRequest::from_reconstructed_message(
.into(),
7 => nym_ip_packet_requests::v7::request::IpPacketRequest::from_reconstructed_message(
&reconstructed,
)
.map_err(|err| IpPacketRouterError::FailedToDeserializeTaggedPacket { source: err })?
} else {
log::info!("Received packet with invalid version: v{version}");
return Ok(vec![self.on_version_mismatch(version, &reconstructed)]);
.map_err(|err| IpPacketRouterError::FailedToDeserializeTaggedPacket { source: err })?,
_ => {
log::info!("Received packet with invalid version: v{request_version}");
return Ok(vec![
self.on_version_mismatch(request_version, &reconstructed)
]);
}
};
// Once we start to require clients to send v7 requests, we will enfore checking
// signatures. Until then, we only check if they are present.
match request.data {
IpPacketRequestData::StaticConnect(signed_connect_request) => {
if let Some(_signature) = signed_connect_request.signature {
// TODO: verify the signature
if let Err(err) = signed_connect_request.verify() {
if !matches!(err, SignatureError::MissingSignature) {
return Err(IpPacketRouterError::FailedToVerifyRequest { source: err });
}
}
let connect_request = signed_connect_request.request;
Ok(vec![self.on_static_connect_request(connect_request).await])
}
IpPacketRequestData::DynamicConnect(signed_connect_request) => {
if let Some(_signature) = signed_connect_request.signature {
// TODO: verify the signature
if let Err(err) = signed_connect_request.verify() {
if !matches!(err, SignatureError::MissingSignature) {
return Err(IpPacketRouterError::FailedToVerifyRequest { source: err });
}
}
let connect_request = signed_connect_request.request;
Ok(vec![self.on_dynamic_connect_request(connect_request).await])
}
IpPacketRequestData::Disconnect(disconnect_request) => {
if let Some(_signature) = disconnect_request.signature {
// TODO: verify the signature
IpPacketRequestData::Disconnect(signed_disconnect_request) => {
if let Err(err) = signed_disconnect_request.verify() {
if !matches!(err, SignatureError::MissingSignature) {
return Err(IpPacketRouterError::FailedToVerifyRequest { source: err });
}
}
let disconnect_request = disconnect_request.request;
let disconnect_request = signed_disconnect_request.request;
Ok(vec![self.on_disconnect_request(disconnect_request)])
}
IpPacketRequestData::Data(data_request) => self.on_data_request(data_request).await,