chore: remove legacy gateway authentication (#5553)
* remove legacy gateway authentication * fixed test code
This commit is contained in:
committed by
GitHub
parent
8c6f84b3fe
commit
c8e825bd1e
@@ -410,32 +410,28 @@ impl<C, St> GatewayClient<C, St> {
|
||||
}
|
||||
}
|
||||
|
||||
fn check_gateway_protocol(
|
||||
&self,
|
||||
gateway_protocol: Option<u8>,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
fn check_gateway_protocol(&self, gateway_protocol: u8) -> Result<(), GatewayClientError> {
|
||||
debug!("gateway protocol: {gateway_protocol:?}, ours: {CURRENT_PROTOCOL_VERSION}");
|
||||
|
||||
// right now there are no failure cases here, but this might change in the future
|
||||
match gateway_protocol {
|
||||
None => {
|
||||
warn!("the gateway we're connected to has not specified its protocol version. It's probably running version < 1.1.X, but that's still fine for now. It will become a hard error in 1.2.0");
|
||||
// note: in +1.2.0 we will have to return a hard error here
|
||||
Ok(())
|
||||
}
|
||||
Some(v) if v > CURRENT_PROTOCOL_VERSION => {
|
||||
let err = GatewayClientError::IncompatibleProtocol {
|
||||
gateway: Some(v),
|
||||
current: CURRENT_PROTOCOL_VERSION,
|
||||
};
|
||||
error!("{err}");
|
||||
Err(err)
|
||||
}
|
||||
// client should reject any gateways that do not indicate they support auth v2
|
||||
if !gateway_protocol.supports_authenticate_v2() {
|
||||
return Err(GatewayClientError::IncompatibleProtocol {
|
||||
gateway: gateway_protocol,
|
||||
current: CURRENT_PROTOCOL_VERSION,
|
||||
});
|
||||
}
|
||||
|
||||
Some(_) => {
|
||||
debug!("the gateway is using exactly the same (or older) protocol version as we are. We're good to continue!");
|
||||
Ok(())
|
||||
}
|
||||
// we can't handle gateways with higher protocol than ours
|
||||
if gateway_protocol <= CURRENT_PROTOCOL_VERSION {
|
||||
debug!("the gateway is using exactly the same (or older) protocol version as we are. We're good to continue!");
|
||||
Ok(())
|
||||
} else {
|
||||
let err = GatewayClientError::IncompatibleProtocol {
|
||||
gateway: gateway_protocol,
|
||||
current: CURRENT_PROTOCOL_VERSION,
|
||||
};
|
||||
error!("{err}");
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -492,7 +488,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
}
|
||||
|
||||
// populate the negotiated protocol for future uses
|
||||
self.negotiated_protocol = gateway_protocol;
|
||||
self.negotiated_protocol = Some(gateway_protocol);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -577,7 +573,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
self.authenticated = status;
|
||||
self.bandwidth.update_and_maybe_log(bandwidth_remaining);
|
||||
|
||||
self.negotiated_protocol = protocol_version;
|
||||
self.negotiated_protocol = Some(protocol_version);
|
||||
log::debug!("authenticated: {status}, bandwidth remaining: {bandwidth_remaining}");
|
||||
|
||||
self.task_client.send_status_msg(Box::new(
|
||||
@@ -590,27 +586,6 @@ impl<C, St> GatewayClient<C, St> {
|
||||
}
|
||||
}
|
||||
|
||||
async fn authenticate_v1(&mut self) -> Result<(), GatewayClientError> {
|
||||
debug!("using v1 authentication");
|
||||
|
||||
let Some(shared_key) = self.shared_key.as_ref() else {
|
||||
return Err(GatewayClientError::NoSharedKeyAvailable);
|
||||
};
|
||||
|
||||
let self_address = self
|
||||
.local_identity
|
||||
.public_key()
|
||||
.derive_destination_address();
|
||||
|
||||
let msg = ClientControlRequest::new_authenticate(
|
||||
self_address,
|
||||
shared_key,
|
||||
self.cfg.bandwidth.require_tickets,
|
||||
)?;
|
||||
self.send_authenticate_request_and_handle_response(msg)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn authenticate_v2(&mut self) -> Result<(), GatewayClientError> {
|
||||
debug!("using v2 authentication");
|
||||
let Some(shared_key) = self.shared_key.as_ref() else {
|
||||
@@ -622,17 +597,13 @@ impl<C, St> GatewayClient<C, St> {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn authenticate(&mut self, use_v2: bool) -> Result<(), GatewayClientError> {
|
||||
async fn authenticate(&mut self) -> Result<(), GatewayClientError> {
|
||||
if !self.connection.is_established() {
|
||||
return Err(GatewayClientError::ConnectionNotEstablished);
|
||||
}
|
||||
debug!("authenticating with gateway");
|
||||
|
||||
if use_v2 {
|
||||
self.authenticate_v2().await
|
||||
} else {
|
||||
self.authenticate_v1().await
|
||||
}
|
||||
self.authenticate_v2().await
|
||||
}
|
||||
|
||||
/// Helper method to either call register or authenticate based on self.shared_key value
|
||||
@@ -650,15 +621,9 @@ impl<C, St> GatewayClient<C, St> {
|
||||
}
|
||||
|
||||
// 1. check gateway's protocol version
|
||||
let gw_protocol = match self.get_gateway_protocol().await {
|
||||
Ok(protocol) => Some(protocol),
|
||||
Err(_) => {
|
||||
// if we failed to send the request, it means the gateway is running the old binary,
|
||||
// so it has reset our connection - we have to reconnect
|
||||
self.establish_connection().await?;
|
||||
None
|
||||
}
|
||||
};
|
||||
// if we failed to get this request resolved, it means the gateway is on an old version
|
||||
// that definitely does not support auth v2, so we bail
|
||||
let gw_protocol = self.get_gateway_protocol().await?;
|
||||
|
||||
let supports_aes_gcm_siv = gw_protocol.supports_aes256_gcm_siv();
|
||||
let supports_auth_v2 = gw_protocol.supports_authenticate_v2();
|
||||
@@ -667,7 +632,13 @@ impl<C, St> GatewayClient<C, St> {
|
||||
warn!("this gateway is on an old version that doesn't support AES256-GCM-SIV");
|
||||
}
|
||||
if !supports_auth_v2 {
|
||||
warn!("this gateway is on an old version that doesn't support authentication v2")
|
||||
warn!("this gateway is on an old version that doesn't support authentication v2");
|
||||
|
||||
// we can't continue
|
||||
return Err(GatewayClientError::IncompatibleProtocol {
|
||||
gateway: gw_protocol,
|
||||
current: CURRENT_PROTOCOL_VERSION,
|
||||
});
|
||||
}
|
||||
|
||||
if self.authenticated {
|
||||
@@ -683,7 +654,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
}
|
||||
|
||||
if self.shared_key.is_some() {
|
||||
self.authenticate(supports_auth_v2).await?;
|
||||
self.authenticate().await?;
|
||||
|
||||
if self.authenticated {
|
||||
// if we are authenticated it means we MUST have an associated shared_key
|
||||
@@ -1016,8 +987,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
}
|
||||
|
||||
// if we're reconnecting, because we lost connection, we need to re-authenticate the connection
|
||||
self.authenticate(self.negotiated_protocol.supports_authenticate_v2())
|
||||
.await?;
|
||||
self.authenticate().await?;
|
||||
|
||||
// this call is NON-blocking
|
||||
self.start_listening_for_mixnet_messages()?;
|
||||
|
||||
@@ -114,7 +114,7 @@ pub enum GatewayClientError {
|
||||
MixnetMsgSenderFailedToSend,
|
||||
|
||||
#[error("Attempted to negotiate connection with gateway using incompatible protocol version. Ours is {current} and the gateway reports {gateway:?}")]
|
||||
IncompatibleProtocol { gateway: Option<u8>, current: u8 },
|
||||
IncompatibleProtocol { gateway: u8, current: u8 },
|
||||
|
||||
#[error(
|
||||
"The packet router hasn't been set - are you sure you started up the client correctly?"
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
// Copyright 2020-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::shared_key::{SharedGatewayKey, SharedKeyUsageError};
|
||||
use nym_sphinx::DestinationAddressBytes;
|
||||
use thiserror::Error;
|
||||
|
||||
/// Replacement for what used to be an `AuthToken`.
|
||||
///
|
||||
/// Replacement for what used to be an `AuthToken`. We used to be generating an `AuthToken` based on
|
||||
/// local secret and remote address in order to allow for authentication. Due to changes in registration
|
||||
/// and the fact we are deriving a shared key, we are encrypting remote's address with the previously
|
||||
/// derived shared key. If the value is as expected, then authentication is successful.
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
|
||||
// this is no longer constant size due to the differences in ciphertext between aes128ctr and aes256gcm-siv (inclusion of tag)
|
||||
pub struct EncryptedAddressBytes(Vec<u8>);
|
||||
|
||||
impl From<Vec<u8>> for EncryptedAddressBytes {
|
||||
fn from(encrypted_address: Vec<u8>) -> Self {
|
||||
EncryptedAddressBytes(encrypted_address)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum EncryptedAddressConversionError {
|
||||
#[error("Failed to decode the encrypted address - {0}")]
|
||||
DecodeError(#[from] bs58::decode::Error),
|
||||
}
|
||||
|
||||
impl EncryptedAddressBytes {
|
||||
pub fn new(
|
||||
address: &DestinationAddressBytes,
|
||||
key: &SharedGatewayKey,
|
||||
nonce: &[u8],
|
||||
) -> Result<Self, SharedKeyUsageError> {
|
||||
let ciphertext = key.encrypt_naive(address.as_bytes_ref(), Some(nonce))?;
|
||||
|
||||
Ok(EncryptedAddressBytes(ciphertext))
|
||||
}
|
||||
|
||||
pub fn verify(
|
||||
&self,
|
||||
address: &DestinationAddressBytes,
|
||||
key: &SharedGatewayKey,
|
||||
nonce: &[u8],
|
||||
) -> bool {
|
||||
let Ok(reconstructed) = Self::new(address, key, nonce) else {
|
||||
return false;
|
||||
};
|
||||
self == &reconstructed
|
||||
}
|
||||
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
&self.0
|
||||
}
|
||||
|
||||
pub fn try_from_base58_string<S: Into<String>>(
|
||||
val: S,
|
||||
) -> Result<Self, EncryptedAddressConversionError> {
|
||||
let decoded = bs58::decode(val.into()).into_vec()?;
|
||||
Ok(EncryptedAddressBytes(decoded))
|
||||
}
|
||||
|
||||
pub fn to_base58_string(self) -> String {
|
||||
bs58::encode(self.0).into_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<EncryptedAddressBytes> for String {
|
||||
fn from(val: EncryptedAddressBytes) -> Self {
|
||||
val.to_base58_string()
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
// Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod encrypted_address;
|
||||
@@ -7,7 +7,6 @@ use nym_sphinx::params::GatewayIntegrityHmacAlgorithm;
|
||||
|
||||
pub use types::*;
|
||||
|
||||
pub mod authentication;
|
||||
pub mod models;
|
||||
pub mod registration;
|
||||
pub mod shared_key;
|
||||
@@ -45,11 +44,21 @@ pub trait GatewayProtocolVersionExt {
|
||||
impl GatewayProtocolVersionExt for Option<u8> {
|
||||
fn supports_aes256_gcm_siv(&self) -> bool {
|
||||
let Some(protocol) = *self else { return false };
|
||||
protocol >= AES_GCM_SIV_PROTOCOL_VERSION
|
||||
protocol.supports_aes256_gcm_siv()
|
||||
}
|
||||
|
||||
fn supports_authenticate_v2(&self) -> bool {
|
||||
let Some(protocol) = *self else { return false };
|
||||
protocol >= AUTHENTICATE_V2_PROTOCOL_VERSION
|
||||
protocol.supports_authenticate_v2()
|
||||
}
|
||||
}
|
||||
|
||||
impl GatewayProtocolVersionExt for u8 {
|
||||
fn supports_aes256_gcm_siv(&self) -> bool {
|
||||
*self >= AES_GCM_SIV_PROTOCOL_VERSION
|
||||
}
|
||||
|
||||
fn supports_authenticate_v2(&self) -> bool {
|
||||
*self >= AUTHENTICATE_V2_PROTOCOL_VERSION
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,25 +76,7 @@ mod tests {
|
||||
protocol_version,
|
||||
data,
|
||||
} => {
|
||||
assert_eq!(protocol_version, Some(42));
|
||||
assert_eq!(data, handshake_data)
|
||||
}
|
||||
_ => unreachable!("this branch shouldn't have been reached!"),
|
||||
}
|
||||
|
||||
let handshake_payload_without_protocol = RegistrationHandshake::HandshakePayload {
|
||||
protocol_version: None,
|
||||
data: handshake_data.clone(),
|
||||
};
|
||||
let serialized = serde_json::to_string(&handshake_payload_without_protocol).unwrap();
|
||||
let deserialized = ClientControlRequest::try_from(serialized).unwrap();
|
||||
|
||||
match deserialized {
|
||||
ClientControlRequest::RegisterHandshakeInitRequest {
|
||||
protocol_version,
|
||||
data,
|
||||
} => {
|
||||
assert!(protocol_version.is_none());
|
||||
assert_eq!(protocol_version, 42);
|
||||
assert_eq!(data, handshake_data)
|
||||
}
|
||||
_ => unreachable!("this branch shouldn't have been reached!"),
|
||||
|
||||
@@ -4,13 +4,10 @@
|
||||
use crate::models::CredentialSpendingRequest;
|
||||
use crate::text_request::authenticate::AuthenticateRequest;
|
||||
use crate::{
|
||||
GatewayRequestsError, SharedGatewayKey, SymmetricKey, AES_GCM_SIV_PROTOCOL_VERSION,
|
||||
AUTHENTICATE_V2_PROTOCOL_VERSION, CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION,
|
||||
INITIAL_PROTOCOL_VERSION,
|
||||
GatewayRequestsError, SharedGatewayKey, SymmetricKey, AUTHENTICATE_V2_PROTOCOL_VERSION,
|
||||
};
|
||||
use nym_credentials_interface::CredentialSpendingData;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_sphinx::DestinationAddressBytes;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::str::FromStr;
|
||||
use tungstenite::Message;
|
||||
@@ -64,32 +61,14 @@ impl ClientRequest {
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
#[non_exhaustive]
|
||||
pub enum ClientControlRequest {
|
||||
// TODO: should this also contain a MAC considering that at this point we already
|
||||
// have the shared key derived?
|
||||
Authenticate {
|
||||
#[serde(default)]
|
||||
protocol_version: Option<u8>,
|
||||
address: String,
|
||||
enc_address: String,
|
||||
iv: String,
|
||||
},
|
||||
|
||||
AuthenticateV2(Box<AuthenticateRequest>),
|
||||
|
||||
#[serde(alias = "handshakePayload")]
|
||||
RegisterHandshakeInitRequest {
|
||||
#[serde(default)]
|
||||
protocol_version: Option<u8>,
|
||||
protocol_version: u8,
|
||||
data: Vec<u8>,
|
||||
},
|
||||
BandwidthCredential {
|
||||
enc_credential: Vec<u8>,
|
||||
iv: Vec<u8>,
|
||||
},
|
||||
BandwidthCredentialV2 {
|
||||
enc_credential: Vec<u8>,
|
||||
iv: Vec<u8>,
|
||||
},
|
||||
|
||||
EcashCredential {
|
||||
enc_credential: Vec<u8>,
|
||||
iv: Vec<u8>,
|
||||
@@ -101,36 +80,27 @@ pub enum ClientControlRequest {
|
||||
},
|
||||
SupportedProtocol {},
|
||||
// if you're adding new variants here, consider putting them inside `ClientRequest` instead
|
||||
|
||||
// NO LONGER SUPPORTED:
|
||||
Authenticate {
|
||||
#[serde(default)]
|
||||
protocol_version: Option<u8>,
|
||||
address: String,
|
||||
enc_address: String,
|
||||
iv: String,
|
||||
},
|
||||
|
||||
BandwidthCredential {
|
||||
enc_credential: Vec<u8>,
|
||||
iv: Vec<u8>,
|
||||
},
|
||||
BandwidthCredentialV2 {
|
||||
enc_credential: Vec<u8>,
|
||||
iv: Vec<u8>,
|
||||
},
|
||||
}
|
||||
|
||||
impl ClientControlRequest {
|
||||
pub fn new_authenticate(
|
||||
address: DestinationAddressBytes,
|
||||
shared_key: &SharedGatewayKey,
|
||||
uses_credentials: bool,
|
||||
) -> Result<Self, GatewayRequestsError> {
|
||||
// if we're encrypting with non-legacy key, the remote must support AES256-GCM-SIV
|
||||
let protocol_version = if !shared_key.is_legacy() {
|
||||
Some(AES_GCM_SIV_PROTOCOL_VERSION)
|
||||
} else if uses_credentials {
|
||||
Some(CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION)
|
||||
} else {
|
||||
// if we're not going to be using credentials, advertise lower protocol version to allow connection
|
||||
// to wider range of gateways
|
||||
Some(INITIAL_PROTOCOL_VERSION)
|
||||
};
|
||||
|
||||
let nonce = shared_key.random_nonce_or_iv();
|
||||
let ciphertext = shared_key.encrypt_naive(address.as_bytes_ref(), Some(&nonce))?;
|
||||
|
||||
Ok(ClientControlRequest::Authenticate {
|
||||
protocol_version,
|
||||
address: address.as_base58_string(),
|
||||
enc_address: bs58::encode(&ciphertext).into_string(),
|
||||
iv: bs58::encode(&nonce).into_string(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new_authenticate_v2(
|
||||
shared_key: &SharedGatewayKey,
|
||||
identity_keys: &ed25519::KeyPair,
|
||||
|
||||
@@ -47,14 +47,12 @@ impl SensitiveServerResponse {
|
||||
#[non_exhaustive]
|
||||
pub enum ServerResponse {
|
||||
Authenticate {
|
||||
#[serde(default)]
|
||||
protocol_version: Option<u8>,
|
||||
protocol_version: u8,
|
||||
status: bool,
|
||||
bandwidth_remaining: i64,
|
||||
},
|
||||
Register {
|
||||
#[serde(default)]
|
||||
protocol_version: Option<u8>,
|
||||
protocol_version: u8,
|
||||
status: bool,
|
||||
},
|
||||
EncryptedResponse {
|
||||
|
||||
@@ -428,6 +428,11 @@ impl<R, S> AuthenticatedHandler<R, S> {
|
||||
ClientControlRequest::EcashCredential { enc_credential, iv } => {
|
||||
self.handle_ecash_bandwidth(enc_credential, iv).await
|
||||
}
|
||||
ClientControlRequest::Authenticate { .. } => {
|
||||
Err(RequestHandlingError::IllegalRequest {
|
||||
additional_context: "authentication v1 is no longer supported".into(),
|
||||
})
|
||||
}
|
||||
ClientControlRequest::BandwidthCredential { .. } => {
|
||||
Err(RequestHandlingError::IllegalRequest {
|
||||
additional_context: "coconut credential are not longer supported".into(),
|
||||
@@ -446,7 +451,7 @@ impl<R, S> AuthenticatedHandler<R, S> {
|
||||
ClientControlRequest::SupportedProtocol { .. } => {
|
||||
Ok(self.inner.handle_supported_protocol_request())
|
||||
}
|
||||
other @ ClientControlRequest::Authenticate { .. } => {
|
||||
other @ ClientControlRequest::AuthenticateV2 { .. } => {
|
||||
Err(RequestHandlingError::IllegalRequest {
|
||||
additional_context: format!(
|
||||
"received illegal message of type {} in an authenticated client",
|
||||
|
||||
@@ -17,14 +17,11 @@ use nym_credentials_interface::AvailableBandwidth;
|
||||
use nym_crypto::aes::cipher::crypto_common::rand_core::RngCore;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_gateway_requests::authenticate::AuthenticateRequest;
|
||||
use nym_gateway_requests::authentication::encrypted_address::{
|
||||
EncryptedAddressBytes, EncryptedAddressConversionError,
|
||||
};
|
||||
use nym_gateway_requests::{
|
||||
registration::handshake::{error::HandshakeError, gateway_handshake},
|
||||
types::{ClientControlRequest, ServerResponse},
|
||||
AuthenticationFailure, BinaryResponse, SharedGatewayKey, CURRENT_PROTOCOL_VERSION,
|
||||
INITIAL_PROTOCOL_VERSION,
|
||||
AuthenticationFailure, BinaryResponse, GatewayProtocolVersionExt, SharedGatewayKey,
|
||||
CURRENT_PROTOCOL_VERSION,
|
||||
};
|
||||
use nym_gateway_storage::error::GatewayStorageError;
|
||||
use nym_node_metrics::events::MetricsEvent;
|
||||
@@ -45,6 +42,9 @@ pub(crate) enum InitialAuthenticationError {
|
||||
#[error(transparent)]
|
||||
AuthenticationFailure(#[from] AuthenticationFailure),
|
||||
|
||||
#[error("the legacy authentication method is no longer supported. please update your client")]
|
||||
UnsupportedLegacyAuthentication,
|
||||
|
||||
#[error("attempted to overwrite client session with a stale authentication")]
|
||||
StaleSessionOverwrite,
|
||||
|
||||
@@ -63,19 +63,9 @@ pub(crate) enum InitialAuthenticationError {
|
||||
#[error("Failed to perform registration handshake: {0}")]
|
||||
HandshakeError(#[from] HandshakeError),
|
||||
|
||||
#[error("Provided client address is malformed: {0}")]
|
||||
// sphinx error is not used here directly as its messaging might be confusing to people
|
||||
MalformedClientAddress(String),
|
||||
|
||||
#[error("Provided encrypted client address is malformed: {0}")]
|
||||
MalformedEncryptedAddress(#[from] EncryptedAddressConversionError),
|
||||
|
||||
#[error("There is already an open connection to this client")]
|
||||
DuplicateConnection,
|
||||
|
||||
#[error("provided authentication IV is malformed: {0}")]
|
||||
MalformedIV(bs58::decode::Error),
|
||||
|
||||
#[error("Only 'Register' or 'Authenticate' requests are allowed")]
|
||||
InvalidRequest,
|
||||
|
||||
@@ -86,7 +76,7 @@ pub(crate) enum InitialAuthenticationError {
|
||||
ConnectionError(#[from] WsError),
|
||||
|
||||
#[error("Attempted to negotiate connection with client using incompatible protocol version. Ours is {current} and the client reports {client:?}")]
|
||||
IncompatibleProtocol { client: Option<u8>, current: u8 },
|
||||
IncompatibleProtocol { client: u8, current: u8 },
|
||||
|
||||
#[error("failed to send authentication response: {source}")]
|
||||
ResponseSendFailure {
|
||||
@@ -377,58 +367,25 @@ impl<R, S> FreshHandler<R, S> {
|
||||
Ok(Some(keys))
|
||||
}
|
||||
|
||||
/// Checks whether the stored shared keys match the received data, i.e. whether the upon decryption
|
||||
/// the provided encrypted address matches the expected unencrypted address.
|
||||
///
|
||||
/// Returns the retrieved shared keys if the check was successful.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `client_address`: address of the client.
|
||||
/// * `encrypted_address`: encrypted address of the client, presumably encrypted using the shared keys.
|
||||
/// * `iv`: nonce/iv created for this particular encryption.
|
||||
async fn auth_v1_verify_stored_shared_key(
|
||||
&self,
|
||||
client_address: DestinationAddressBytes,
|
||||
encrypted_address: EncryptedAddressBytes,
|
||||
nonce: &[u8],
|
||||
) -> Result<Option<KeyWithAuthTimestamp>, InitialAuthenticationError> {
|
||||
let Some(keys) = self.retrieve_shared_key(client_address).await? else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
// LEGACY ISSUE: we're not verifying HMAC key
|
||||
if encrypted_address.verify(&client_address, &keys.key, nonce) {
|
||||
Ok(Some(keys))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
fn negotiate_client_protocol(
|
||||
&self,
|
||||
client_protocol: Option<u8>,
|
||||
client_protocol: u8,
|
||||
) -> Result<u8, InitialAuthenticationError> {
|
||||
debug!("client protocol: {client_protocol:?}, ours: {CURRENT_PROTOCOL_VERSION}");
|
||||
let Some(client_protocol_version) = client_protocol else {
|
||||
warn!("the client we're connected to has not specified its protocol version. It's probably running version < 1.1.X, but that's still fine for now. It will become a hard error in 1.2.0");
|
||||
// note: in +1.2.0 we will have to return a hard error here
|
||||
return Ok(INITIAL_PROTOCOL_VERSION);
|
||||
};
|
||||
debug!("client protocol: {client_protocol}, ours: {CURRENT_PROTOCOL_VERSION}");
|
||||
|
||||
// a v2 gateway will understand v1 requests, but v1 client will not understand v2 responses
|
||||
if client_protocol_version == 1 {
|
||||
return Ok(1);
|
||||
}
|
||||
|
||||
// a v3 gateway will understand v2 requests (legacy keys)
|
||||
if client_protocol_version == 2 {
|
||||
return Ok(2);
|
||||
// gateway will reject any requests from clients that do not support auth v2
|
||||
if !client_protocol.supports_authenticate_v2() {
|
||||
let err = InitialAuthenticationError::IncompatibleProtocol {
|
||||
client: client_protocol,
|
||||
current: CURRENT_PROTOCOL_VERSION,
|
||||
};
|
||||
error!("{err}");
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
// we can't handle clients with higher protocol than ours
|
||||
// (perhaps we could try to negotiate downgrade on our end? sounds like a nice future improvement)
|
||||
if client_protocol_version <= CURRENT_PROTOCOL_VERSION {
|
||||
if client_protocol <= CURRENT_PROTOCOL_VERSION {
|
||||
debug!("the client is using exactly the same (or older) protocol version as we are. We're good to continue!");
|
||||
Ok(CURRENT_PROTOCOL_VERSION)
|
||||
} else {
|
||||
@@ -528,96 +485,6 @@ impl<R, S> FreshHandler<R, S> {
|
||||
Ok(available_bandwidth)
|
||||
}
|
||||
|
||||
/// Tries to handle the received authentication request by checking correctness of the received data.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `client_address`: address of the client wishing to authenticate.
|
||||
/// * `encrypted_address`: ciphertext of the address of the client wishing to authenticate.
|
||||
/// * `iv`: fresh IV received with the request.
|
||||
#[instrument(skip_all
|
||||
fields(
|
||||
address = %address,
|
||||
)
|
||||
)]
|
||||
async fn handle_legacy_authenticate(
|
||||
&mut self,
|
||||
client_protocol_version: Option<u8>,
|
||||
address: String,
|
||||
enc_address: String,
|
||||
raw_nonce: String,
|
||||
) -> Result<InitialAuthResult, InitialAuthenticationError>
|
||||
where
|
||||
S: AsyncRead + AsyncWrite + Unpin,
|
||||
{
|
||||
debug!("handling client authentication (v1)");
|
||||
|
||||
let negotiated_protocol = self.negotiate_client_protocol(client_protocol_version)?;
|
||||
// populate the negotiated protocol for future uses
|
||||
self.negotiated_protocol = Some(negotiated_protocol);
|
||||
|
||||
let address = DestinationAddressBytes::try_from_base58_string(address)
|
||||
.map_err(|err| InitialAuthenticationError::MalformedClientAddress(err.to_string()))?;
|
||||
let encrypted_address = EncryptedAddressBytes::try_from_base58_string(enc_address)?;
|
||||
let nonce = bs58::decode(&raw_nonce)
|
||||
.into_vec()
|
||||
.map_err(InitialAuthenticationError::MalformedIV)?;
|
||||
|
||||
// validate the shared key
|
||||
let Some(shared_keys) = self
|
||||
.auth_v1_verify_stored_shared_key(address, encrypted_address, &nonce)
|
||||
.await?
|
||||
else {
|
||||
// it feels weird to be returning an 'Ok' here, but I didn't want to change the existing behaviour
|
||||
return Ok(InitialAuthResult::new_failed(Some(negotiated_protocol)));
|
||||
};
|
||||
|
||||
// in v1 we don't have explicit data so we have to use current timestamp
|
||||
// (which does nothing but just allows us to use the same codepath)
|
||||
let session_request_start = OffsetDateTime::now_utc();
|
||||
|
||||
// Check for duplicate clients
|
||||
if let Some(remote_client_data) = self
|
||||
.shared_state
|
||||
.active_clients_store
|
||||
.get_remote_client(address)
|
||||
{
|
||||
warn!("Detected duplicate connection for client: {address}");
|
||||
self.handle_duplicate_client(address, remote_client_data, session_request_start)
|
||||
.await?;
|
||||
}
|
||||
|
||||
let client_id = shared_keys.client_id;
|
||||
|
||||
// if applicable, push stored messages
|
||||
self.push_stored_messages_to_client(address, &shared_keys.key)
|
||||
.await?;
|
||||
|
||||
// check the bandwidth
|
||||
let available_bandwidth = self.get_registered_available_bandwidth(client_id).await?;
|
||||
|
||||
let bandwidth_remaining = if available_bandwidth.expired() {
|
||||
self.shared_state.storage.reset_bandwidth(client_id).await?;
|
||||
0
|
||||
} else {
|
||||
available_bandwidth.bytes
|
||||
};
|
||||
|
||||
Ok(InitialAuthResult::new(
|
||||
Some(ClientDetails::new(
|
||||
client_id,
|
||||
address,
|
||||
shared_keys.key,
|
||||
session_request_start,
|
||||
)),
|
||||
ServerResponse::Authenticate {
|
||||
protocol_version: Some(negotiated_protocol),
|
||||
status: true,
|
||||
bandwidth_remaining,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
async fn handle_authenticate_v2(
|
||||
&mut self,
|
||||
request: Box<AuthenticateRequest>,
|
||||
@@ -628,7 +495,7 @@ impl<R, S> FreshHandler<R, S> {
|
||||
debug!("handling client authentication (v2)");
|
||||
|
||||
let negotiated_protocol =
|
||||
self.negotiate_client_protocol(Some(request.content.protocol_version))?;
|
||||
self.negotiate_client_protocol(request.content.protocol_version)?;
|
||||
// populate the negotiated protocol for future uses
|
||||
self.negotiated_protocol = Some(negotiated_protocol);
|
||||
|
||||
@@ -697,7 +564,7 @@ impl<R, S> FreshHandler<R, S> {
|
||||
session_request_start,
|
||||
)),
|
||||
ServerResponse::Authenticate {
|
||||
protocol_version: Some(negotiated_protocol),
|
||||
protocol_version: negotiated_protocol,
|
||||
status: true,
|
||||
bandwidth_remaining,
|
||||
},
|
||||
@@ -759,7 +626,7 @@ impl<R, S> FreshHandler<R, S> {
|
||||
/// * `init_data`: init payload of the registration handshake.
|
||||
async fn handle_register(
|
||||
&mut self,
|
||||
client_protocol_version: Option<u8>,
|
||||
client_protocol_version: u8,
|
||||
init_data: Vec<u8>,
|
||||
) -> Result<InitialAuthResult, InitialAuthenticationError>
|
||||
where
|
||||
@@ -798,7 +665,7 @@ impl<R, S> FreshHandler<R, S> {
|
||||
Ok(InitialAuthResult::new(
|
||||
Some(client_details),
|
||||
ServerResponse::Register {
|
||||
protocol_version: Some(negotiated_protocol),
|
||||
protocol_version: negotiated_protocol,
|
||||
status: true,
|
||||
},
|
||||
))
|
||||
@@ -833,14 +700,8 @@ impl<R, S> FreshHandler<R, S> {
|
||||
{
|
||||
// we can handle stateless client requests without prior authentication, like `ClientControlRequest::SupportedProtocol`
|
||||
let auth_result = match request {
|
||||
ClientControlRequest::Authenticate {
|
||||
protocol_version,
|
||||
address,
|
||||
enc_address,
|
||||
iv,
|
||||
} => {
|
||||
self.handle_legacy_authenticate(protocol_version, address, enc_address, iv)
|
||||
.await
|
||||
ClientControlRequest::Authenticate { .. } => {
|
||||
return Err(InitialAuthenticationError::UnsupportedLegacyAuthentication)
|
||||
}
|
||||
ClientControlRequest::AuthenticateV2(req) => self.handle_authenticate_v2(req).await,
|
||||
ClientControlRequest::RegisterHandshakeInitRequest {
|
||||
|
||||
@@ -78,17 +78,6 @@ impl InitialAuthResult {
|
||||
server_response,
|
||||
}
|
||||
}
|
||||
|
||||
fn new_failed(protocol_version: Option<u8>) -> Self {
|
||||
InitialAuthResult {
|
||||
client_details: None,
|
||||
server_response: ServerResponse::Authenticate {
|
||||
protocol_version,
|
||||
status: false,
|
||||
bandwidth_remaining: 0,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// imo there's no point in including the peer address in anything higher than debug
|
||||
|
||||
Reference in New Issue
Block a user