Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a2437ebe3e | |||
| e72103bbe6 | |||
| f7ebddf84b | |||
| c8e825bd1e |
Generated
+2
-3
@@ -9428,9 +9428,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sphinx-packet"
|
||||
version = "0.3.1"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "535f2c430778bf59c22249fcc1ed6d384129eb2f0f694706015d636c688f9ac6"
|
||||
checksum = "ec6b227c9167ef91b9c426d7a894e9937d30efbfab0b22f31ff1c28c78c5b6f6"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"arrayref",
|
||||
@@ -9439,7 +9439,6 @@ dependencies = [
|
||||
"byteorder",
|
||||
"chacha",
|
||||
"ctr",
|
||||
"curve25519-dalek 4.1.3",
|
||||
"digest 0.10.7",
|
||||
"hkdf",
|
||||
"hmac",
|
||||
|
||||
+1
-1
@@ -321,7 +321,7 @@ serde_with = "3.9.0"
|
||||
serde_yaml = "0.9.25"
|
||||
sha2 = "0.10.8"
|
||||
si-scale = "0.2.3"
|
||||
sphinx-packet = "0.3.1"
|
||||
sphinx-packet = "=0.4.0" # make sure to use version 0.4.0 (or higher) that has removed backwards compatibility
|
||||
sqlx = "0.7.4"
|
||||
strum = "0.26"
|
||||
strum_macros = "0.26"
|
||||
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
-- make aes256gcm column non-nullable and drop any gateways that still use the legacy keys
|
||||
-- (since they'd be unusable after this change)
|
||||
CREATE TABLE remote_gateway_details_tmp
|
||||
(
|
||||
gateway_id_bs58 TEXT NOT NULL UNIQUE PRIMARY KEY REFERENCES registered_gateway (gateway_id_bs58),
|
||||
derived_aes256_gcm_siv_key BLOB NOT NULL,
|
||||
gateway_owner_address TEXT,
|
||||
gateway_listener TEXT NOT NULL
|
||||
);
|
||||
|
||||
INSERT INTO remote_gateway_details_tmp (gateway_id_bs58, derived_aes256_gcm_siv_key, gateway_owner_address,
|
||||
gateway_listener)
|
||||
SELECT gateway_id_bs58, derived_aes256_gcm_siv_key, gateway_owner_address, gateway_listener
|
||||
FROM remote_gateway_details
|
||||
WHERE derived_aes256_gcm_siv_key IS NOT NULL;
|
||||
|
||||
DROP TABLE remote_gateway_details;
|
||||
ALTER TABLE remote_gateway_details_tmp
|
||||
RENAME TO remote_gateway_details;
|
||||
@@ -156,48 +156,26 @@ impl StorageManager {
|
||||
|
||||
pub(crate) async fn set_remote_gateway_details(
|
||||
&self,
|
||||
remote: &RawRemoteGatewayDetails,
|
||||
gateway_id_bs58: String,
|
||||
derived_aes256_gcm_siv_key: &[u8],
|
||||
gateway_owner_address: Option<String>,
|
||||
gateway_listener: String,
|
||||
) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO remote_gateway_details(gateway_id_bs58, derived_aes128_ctr_blake3_hmac_keys_bs58, derived_aes256_gcm_siv_key, gateway_owner_address, gateway_listener)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
INSERT INTO remote_gateway_details(gateway_id_bs58, derived_aes256_gcm_siv_key, gateway_owner_address, gateway_listener)
|
||||
VALUES (?, ?, ?, ?)
|
||||
"#,
|
||||
remote.gateway_id_bs58,
|
||||
remote.derived_aes128_ctr_blake3_hmac_keys_bs58,
|
||||
remote.derived_aes256_gcm_siv_key,
|
||||
remote.gateway_owner_address,
|
||||
remote.gateway_listener,
|
||||
gateway_id_bs58,
|
||||
derived_aes256_gcm_siv_key,
|
||||
gateway_owner_address,
|
||||
gateway_listener,
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn update_remote_gateway_key(
|
||||
&self,
|
||||
gateway_id_bs58: &str,
|
||||
derived_aes128_ctr_blake3_hmac_keys_bs58: Option<&str>,
|
||||
derived_aes256_gcm_siv_key: Option<&[u8]>,
|
||||
) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
UPDATE remote_gateway_details
|
||||
SET
|
||||
derived_aes128_ctr_blake3_hmac_keys_bs58 = ?,
|
||||
derived_aes256_gcm_siv_key = ?
|
||||
WHERE gateway_id_bs58 = ?
|
||||
"#,
|
||||
derived_aes128_ctr_blake3_hmac_keys_bs58,
|
||||
derived_aes256_gcm_siv_key,
|
||||
gateway_id_bs58
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn remove_remote_gateway_details(
|
||||
&self,
|
||||
gateway_id: &str,
|
||||
|
||||
@@ -8,7 +8,6 @@ use crate::{
|
||||
use async_trait::async_trait;
|
||||
use manager::StorageManager;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_gateway_requests::SharedSymmetricKey;
|
||||
use std::path::Path;
|
||||
|
||||
pub mod error;
|
||||
@@ -119,9 +118,16 @@ impl GatewaysDetailsStore for OnDiskGatewaysDetails {
|
||||
|
||||
match &details.details {
|
||||
GatewayDetails::Remote(remote_details) => {
|
||||
let raw_details = remote_details.into();
|
||||
self.manager
|
||||
.set_remote_gateway_details(&raw_details)
|
||||
.set_remote_gateway_details(
|
||||
remote_details.gateway_id.to_base58_string(),
|
||||
remote_details.shared_key.as_bytes(),
|
||||
remote_details
|
||||
.gateway_owner_address
|
||||
.as_ref()
|
||||
.map(|o| o.to_string()),
|
||||
remote_details.gateway_listener.to_string(),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
GatewayDetails::Custom(custom_details) => {
|
||||
@@ -134,21 +140,6 @@ impl GatewaysDetailsStore for OnDiskGatewaysDetails {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn upgrade_stored_remote_gateway_key(
|
||||
&self,
|
||||
gateway_id: ed25519::PublicKey,
|
||||
updated_key: &SharedSymmetricKey,
|
||||
) -> Result<(), Self::StorageError> {
|
||||
self.manager
|
||||
.update_remote_gateway_key(
|
||||
&gateway_id.to_base58_string(),
|
||||
None,
|
||||
Some(updated_key.as_bytes()),
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ideally all of those should be run under a storage tx to ensure storage consistency,
|
||||
// but at that point it's fine
|
||||
async fn remove_gateway_details(&self, gateway_id: &str) -> Result<(), Self::StorageError> {
|
||||
|
||||
@@ -2,10 +2,8 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::types::{ActiveGateway, GatewayRegistration};
|
||||
use crate::{BadGateway, GatewayDetails, GatewaysDetailsStore};
|
||||
use crate::{BadGateway, GatewaysDetailsStore};
|
||||
use async_trait::async_trait;
|
||||
use nym_crypto::asymmetric::ed25519::PublicKey;
|
||||
use nym_gateway_requests::{SharedGatewayKey, SharedSymmetricKey};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use thiserror::Error;
|
||||
@@ -96,29 +94,6 @@ impl GatewaysDetailsStore for InMemGatewaysDetails {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn upgrade_stored_remote_gateway_key(
|
||||
&self,
|
||||
gateway_id: PublicKey,
|
||||
updated_key: &SharedSymmetricKey,
|
||||
) -> Result<(), Self::StorageError> {
|
||||
let mut guard = self.inner.write().await;
|
||||
|
||||
#[allow(clippy::unwrap_used)]
|
||||
if let Some(target) = guard.gateways.get_mut(&gateway_id.to_string()) {
|
||||
let GatewayDetails::Remote(details) = &mut target.details else {
|
||||
return Ok(());
|
||||
};
|
||||
assert_eq!(Arc::strong_count(&details.shared_key), 1);
|
||||
|
||||
// eh. that's nasty, but it's only ever used for ephemeral clients so should be fine for now...
|
||||
details.shared_key = Arc::new(SharedGatewayKey::Current(
|
||||
SharedSymmetricKey::try_from_bytes(updated_key.as_bytes()).unwrap(),
|
||||
))
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn remove_gateway_details(&self, gateway_id: &str) -> Result<(), Self::StorageError> {
|
||||
let mut guard = self.inner.write().await;
|
||||
if let Some(active) = guard.active_gateway.as_ref() {
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
use async_trait::async_trait;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_gateway_requests::SharedSymmetricKey;
|
||||
use std::error::Error;
|
||||
|
||||
pub mod backend;
|
||||
@@ -62,12 +61,6 @@ pub trait GatewaysDetailsStore {
|
||||
details: &GatewayRegistration,
|
||||
) -> Result<(), Self::StorageError>;
|
||||
|
||||
async fn upgrade_stored_remote_gateway_key(
|
||||
&self,
|
||||
gateway_id: identity::PublicKey,
|
||||
updated_key: &SharedSymmetricKey,
|
||||
) -> Result<(), Self::StorageError>;
|
||||
|
||||
/// Remove given gateway details from the underlying store.
|
||||
async fn remove_gateway_details(&self, gateway_id: &str) -> Result<(), Self::StorageError>;
|
||||
}
|
||||
|
||||
@@ -4,10 +4,9 @@
|
||||
use crate::BadGateway;
|
||||
use cosmrs::AccountId;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_gateway_requests::shared_key::{LegacySharedKeys, SharedGatewayKey, SharedSymmetricKey};
|
||||
use nym_gateway_requests::shared_key::SharedSymmetricKey;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::ops::Deref;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use time::OffsetDateTime;
|
||||
@@ -65,7 +64,7 @@ impl From<GatewayDetails> for GatewayRegistration {
|
||||
impl GatewayDetails {
|
||||
pub fn new_remote(
|
||||
gateway_id: identity::PublicKey,
|
||||
shared_key: Arc<SharedGatewayKey>,
|
||||
shared_key: Arc<SharedSymmetricKey>,
|
||||
gateway_owner_address: Option<AccountId>,
|
||||
gateway_listener: Url,
|
||||
) -> Self {
|
||||
@@ -88,7 +87,7 @@ impl GatewayDetails {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn shared_key(&self) -> Option<&SharedGatewayKey> {
|
||||
pub fn shared_key(&self) -> Option<&SharedSymmetricKey> {
|
||||
match self {
|
||||
GatewayDetails::Remote(details) => Some(&details.shared_key),
|
||||
GatewayDetails::Custom(_) => None,
|
||||
@@ -168,8 +167,7 @@ pub struct RegisteredGateway {
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
|
||||
pub struct RawRemoteGatewayDetails {
|
||||
pub gateway_id_bs58: String,
|
||||
pub derived_aes128_ctr_blake3_hmac_keys_bs58: Option<String>,
|
||||
pub derived_aes256_gcm_siv_key: Option<Vec<u8>>,
|
||||
pub derived_aes256_gcm_siv_key: Vec<u8>,
|
||||
pub gateway_owner_address: Option<String>,
|
||||
pub gateway_listener: String,
|
||||
}
|
||||
@@ -186,35 +184,11 @@ impl TryFrom<RawRemoteGatewayDetails> for RemoteGatewayDetails {
|
||||
}
|
||||
})?;
|
||||
|
||||
let shared_key =
|
||||
match (
|
||||
&value.derived_aes256_gcm_siv_key,
|
||||
&value.derived_aes128_ctr_blake3_hmac_keys_bs58,
|
||||
) {
|
||||
(None, None) => {
|
||||
return Err(BadGateway::MissingSharedKey {
|
||||
gateway_id: value.gateway_id_bs58.clone(),
|
||||
})
|
||||
}
|
||||
(Some(aes256gcm_siv), _) => {
|
||||
let current_key =
|
||||
SharedSymmetricKey::try_from_bytes(aes256gcm_siv).map_err(|source| {
|
||||
BadGateway::MalformedSharedKeys {
|
||||
gateway_id: value.gateway_id_bs58.clone(),
|
||||
source,
|
||||
}
|
||||
})?;
|
||||
SharedGatewayKey::Current(current_key)
|
||||
}
|
||||
(None, Some(aes128ctr_hmac)) => {
|
||||
let legacy_key = LegacySharedKeys::try_from_base58_string(aes128ctr_hmac)
|
||||
.map_err(|source| BadGateway::MalformedSharedKeys {
|
||||
gateway_id: value.gateway_id_bs58.clone(),
|
||||
source,
|
||||
})?;
|
||||
SharedGatewayKey::Legacy(legacy_key)
|
||||
}
|
||||
};
|
||||
let shared_key = SharedSymmetricKey::try_from_bytes(&value.derived_aes256_gcm_siv_key)
|
||||
.map_err(|source| BadGateway::MalformedSharedKeys {
|
||||
gateway_id: value.gateway_id_bs58.clone(),
|
||||
source,
|
||||
})?;
|
||||
|
||||
let gateway_owner_address = value
|
||||
.gateway_owner_address
|
||||
@@ -247,29 +221,11 @@ impl TryFrom<RawRemoteGatewayDetails> for RemoteGatewayDetails {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a RemoteGatewayDetails> for RawRemoteGatewayDetails {
|
||||
fn from(value: &'a RemoteGatewayDetails) -> Self {
|
||||
let (derived_aes128_ctr_blake3_hmac_keys_bs58, derived_aes256_gcm_siv_key) =
|
||||
match value.shared_key.deref() {
|
||||
SharedGatewayKey::Current(key) => (None, Some(key.to_bytes())),
|
||||
SharedGatewayKey::Legacy(key) => (Some(key.to_base58_string()), None),
|
||||
};
|
||||
|
||||
RawRemoteGatewayDetails {
|
||||
gateway_id_bs58: value.gateway_id.to_base58_string(),
|
||||
derived_aes128_ctr_blake3_hmac_keys_bs58,
|
||||
derived_aes256_gcm_siv_key,
|
||||
gateway_owner_address: value.gateway_owner_address.as_ref().map(|o| o.to_string()),
|
||||
gateway_listener: value.gateway_listener.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RemoteGatewayDetails {
|
||||
pub gateway_id: identity::PublicKey,
|
||||
|
||||
pub shared_key: Arc<SharedGatewayKey>,
|
||||
pub shared_key: Arc<SharedSymmetricKey>,
|
||||
|
||||
pub gateway_owner_address: Option<AccountId>,
|
||||
|
||||
|
||||
@@ -394,7 +394,6 @@ where
|
||||
config: &Config,
|
||||
initialisation_result: InitialisationResult,
|
||||
bandwidth_controller: Option<BandwidthController<C, S::CredentialStore>>,
|
||||
details_store: &S::GatewaysDetailsStore,
|
||||
packet_router: PacketRouter,
|
||||
stats_reporter: ClientStatsSender,
|
||||
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
||||
@@ -403,7 +402,6 @@ where
|
||||
where
|
||||
<S::KeyStore as KeyStore>::StorageError: Send + Sync + 'static,
|
||||
<S::CredentialStore as CredentialStorage>::StorageError: Send + Sync + 'static,
|
||||
<S::GatewaysDetailsStore as GatewaysDetailsStore>::StorageError: Sync + Send,
|
||||
{
|
||||
let managed_keys = initialisation_result.client_keys;
|
||||
let GatewayDetails::Remote(details) = initialisation_result.gateway_registration.details
|
||||
@@ -458,31 +456,13 @@ where
|
||||
// we need to:
|
||||
// - perform handshake (reg or auth)
|
||||
// - check for key upgrade
|
||||
// - maybe perform another upgrade handshake
|
||||
// - check for bandwidth
|
||||
// - start background tasks
|
||||
let auth_res = gateway_client
|
||||
let _auth_res = gateway_client
|
||||
.perform_initial_authentication()
|
||||
.await
|
||||
.map_err(gateway_failure)?;
|
||||
|
||||
if auth_res.requires_key_upgrade {
|
||||
// drop the shared_key arc because we don't need it and we can't hold it for the purposes of upgrade
|
||||
drop(auth_res);
|
||||
|
||||
let updated_key = gateway_client
|
||||
.upgrade_key_authenticated()
|
||||
.await
|
||||
.map_err(gateway_failure)?;
|
||||
|
||||
details_store
|
||||
.upgrade_stored_remote_gateway_key(gateway_client.gateway_identity(), &updated_key)
|
||||
.await.map_err(|err| {
|
||||
error!("failed to store upgraded gateway key! this connection might be forever broken now: {err}");
|
||||
ClientCoreError::GatewaysDetailsStoreError { source: Box::new(err) }
|
||||
})?
|
||||
}
|
||||
|
||||
gateway_client
|
||||
.claim_initial_bandwidth()
|
||||
.await
|
||||
@@ -501,7 +481,6 @@ where
|
||||
config: &Config,
|
||||
initialisation_result: InitialisationResult,
|
||||
bandwidth_controller: Option<BandwidthController<C, S::CredentialStore>>,
|
||||
details_store: &S::GatewaysDetailsStore,
|
||||
packet_router: PacketRouter,
|
||||
stats_reporter: ClientStatsSender,
|
||||
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
||||
@@ -510,7 +489,6 @@ where
|
||||
where
|
||||
<S::KeyStore as KeyStore>::StorageError: Send + Sync + 'static,
|
||||
<S::CredentialStore as CredentialStorage>::StorageError: Send + Sync + 'static,
|
||||
<S::GatewaysDetailsStore as GatewaysDetailsStore>::StorageError: Sync + Send,
|
||||
{
|
||||
// if we have setup custom gateway sender and persisted details agree with it, return it
|
||||
if let Some(mut custom_gateway_transceiver) = custom_gateway_transceiver {
|
||||
@@ -533,7 +511,6 @@ where
|
||||
config,
|
||||
initialisation_result,
|
||||
bandwidth_controller,
|
||||
details_store,
|
||||
packet_router,
|
||||
stats_reporter,
|
||||
#[cfg(unix)]
|
||||
@@ -744,8 +721,7 @@ where
|
||||
)
|
||||
.await?;
|
||||
|
||||
let (reply_storage_backend, credential_store, details_store) =
|
||||
self.client_store.into_runtime_stores();
|
||||
let (reply_storage_backend, credential_store, _) = self.client_store.into_runtime_stores();
|
||||
|
||||
// channels for inter-component communication
|
||||
// TODO: make the channels be internally created by the relevant components
|
||||
@@ -826,7 +802,6 @@ where
|
||||
&self.config,
|
||||
init_res,
|
||||
bandwidth_controller,
|
||||
&details_store,
|
||||
gateway_packet_router,
|
||||
stats_reporter.clone(),
|
||||
#[cfg(unix)]
|
||||
|
||||
@@ -2,24 +2,11 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod v1_1_33 {
|
||||
use crate::client::base_client::{
|
||||
non_wasm_helpers::setup_fs_gateways_storage,
|
||||
storage::helpers::{set_active_gateway, store_gateway_details},
|
||||
};
|
||||
use crate::config::disk_persistence::old_v1_1_33::CommonClientPathsV1_1_33;
|
||||
use crate::config::disk_persistence::CommonClientPaths;
|
||||
use crate::config::old_config_v1_1_33::OldGatewayEndpointConfigV1_1_33;
|
||||
use crate::error::ClientCoreError;
|
||||
use nym_client_core_gateways_storage::{
|
||||
CustomGatewayDetails, GatewayDetails, GatewayRegistration, RemoteGatewayDetails,
|
||||
};
|
||||
use nym_gateway_requests::shared_key::LegacySharedKeys;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::{digest::Digest, Sha256};
|
||||
use std::ops::Deref;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
mod base64 {
|
||||
use base64::{engine::general_purpose::STANDARD, Engine as _};
|
||||
@@ -57,155 +44,18 @@ pub mod v1_1_33 {
|
||||
details: OldGatewayEndpointConfigV1_1_33,
|
||||
}
|
||||
|
||||
impl PersistedGatewayConfig {
|
||||
fn verify(&self, shared_key: &LegacySharedKeys) -> bool {
|
||||
let key_bytes = Zeroizing::new(shared_key.to_bytes());
|
||||
|
||||
let mut key_hasher = Sha256::new();
|
||||
key_hasher.update(&key_bytes);
|
||||
let key_hash = key_hasher.finalize();
|
||||
|
||||
self.key_hash == key_hash.deref()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
struct PersistedCustomGatewayDetails {
|
||||
gateway_id: String,
|
||||
}
|
||||
|
||||
fn load_shared_key<P: AsRef<Path>>(path: P) -> Result<LegacySharedKeys, ClientCoreError> {
|
||||
// the shared key was a simple pem file
|
||||
Ok(nym_pemstore::load_key(path)?)
|
||||
}
|
||||
|
||||
fn gateway_details_from_raw(
|
||||
gateway_id: String,
|
||||
gateway_owner: String,
|
||||
gateway_listener: String,
|
||||
gateway_shared_key: LegacySharedKeys,
|
||||
) -> Result<GatewayDetails, ClientCoreError> {
|
||||
Ok(GatewayDetails::Remote(RemoteGatewayDetails {
|
||||
gateway_id: gateway_id
|
||||
.parse()
|
||||
.map_err(|err| ClientCoreError::UpgradeFailure {
|
||||
message: format!("the stored gateway id was malformed: {err}"),
|
||||
})?,
|
||||
shared_key: Arc::new(gateway_shared_key.into()),
|
||||
gateway_owner_address: Some(gateway_owner.parse().map_err(|err| {
|
||||
ClientCoreError::UpgradeFailure {
|
||||
message: format!("the stored gateway owner address was malformed: {err}"),
|
||||
}
|
||||
})?),
|
||||
gateway_listener: gateway_listener.parse().map_err(|err| {
|
||||
ClientCoreError::UpgradeFailure {
|
||||
message: format!("the stored gateway listener address was malformed: {err}"),
|
||||
}
|
||||
})?,
|
||||
}))
|
||||
}
|
||||
|
||||
// helper to extract shared key and gateway details into the new GatewayRegistration
|
||||
fn extract_gateway_registration(
|
||||
storage_paths: &CommonClientPathsV1_1_33,
|
||||
) -> Result<GatewayRegistration, ClientCoreError> {
|
||||
let details_file = std::fs::File::open(&storage_paths.gateway_details).map_err(|err| {
|
||||
ClientCoreError::UpgradeFailure {
|
||||
message: format!(
|
||||
"failed to open gateway details file at {}: {err}",
|
||||
storage_paths.gateway_details.display()
|
||||
),
|
||||
}
|
||||
})?;
|
||||
|
||||
// in v1.1.33 of the clients, the gateway details struct was saved as json
|
||||
let details: PersistedGatewayDetails =
|
||||
serde_json::from_reader(details_file).map_err(|err| {
|
||||
ClientCoreError::UpgradeFailure {
|
||||
message: format!(
|
||||
"failed to deserialize gateway details from {}: {err}",
|
||||
storage_paths.gateway_details.display()
|
||||
),
|
||||
}
|
||||
})?;
|
||||
|
||||
let details = match details {
|
||||
PersistedGatewayDetails::Default(config) => {
|
||||
let gateway_shared_key =
|
||||
load_shared_key(&storage_paths.keys.gateway_shared_key_file)?;
|
||||
if !config.verify(&gateway_shared_key) {
|
||||
return Err(ClientCoreError::UpgradeFailure {
|
||||
message: "failed to verify consistency of the existing gateway details"
|
||||
.to_string(),
|
||||
});
|
||||
}
|
||||
gateway_details_from_raw(
|
||||
config.details.gateway_id,
|
||||
config.details.gateway_owner,
|
||||
config.details.gateway_listener,
|
||||
gateway_shared_key,
|
||||
)?
|
||||
}
|
||||
PersistedGatewayDetails::Custom(custom) => {
|
||||
GatewayDetails::Custom(CustomGatewayDetails {
|
||||
gateway_id: custom.gateway_id.parse().map_err(|err| {
|
||||
ClientCoreError::UpgradeFailure {
|
||||
message: format!("the stored gateway id was malformed: {err}"),
|
||||
}
|
||||
})?,
|
||||
data: None,
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
Ok(details.into())
|
||||
}
|
||||
|
||||
// it's responsibility of the caller to ensure this is called **after** new registration has already been saved
|
||||
fn remove_old_gateway_details(storage_paths: &CommonClientPathsV1_1_33) -> std::io::Result<()> {
|
||||
std::fs::remove_file(&storage_paths.gateway_details)?;
|
||||
|
||||
if storage_paths.keys.gateway_shared_key_file.exists() {
|
||||
std::fs::remove_file(&storage_paths.keys.gateway_shared_key_file)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn migrate_gateway_details(
|
||||
old_storage_paths: &CommonClientPathsV1_1_33,
|
||||
new_storage_paths: &CommonClientPaths,
|
||||
preloaded_config: Option<OldGatewayEndpointConfigV1_1_33>,
|
||||
_old_storage_paths: &CommonClientPathsV1_1_33,
|
||||
_new_storage_paths: &CommonClientPaths,
|
||||
_preloaded_config: Option<OldGatewayEndpointConfigV1_1_33>,
|
||||
) -> Result<(), ClientCoreError> {
|
||||
let gateway_registration = match preloaded_config {
|
||||
Some(config) => {
|
||||
let gateway_shared_key =
|
||||
load_shared_key(&old_storage_paths.keys.gateway_shared_key_file)?;
|
||||
gateway_details_from_raw(
|
||||
config.gateway_id,
|
||||
config.gateway_owner,
|
||||
config.gateway_listener,
|
||||
gateway_shared_key,
|
||||
)?
|
||||
.into()
|
||||
}
|
||||
None => extract_gateway_registration(old_storage_paths)?,
|
||||
};
|
||||
|
||||
// since we're migrating to a brand new store, the store should be empty
|
||||
// and thus set the 'new' gateway as the active one
|
||||
let details_store =
|
||||
setup_fs_gateways_storage(&new_storage_paths.gateway_registrations).await?;
|
||||
store_gateway_details(&details_store, &gateway_registration).await?;
|
||||
set_active_gateway(
|
||||
&details_store,
|
||||
&gateway_registration.details.gateway_id().to_base58_string(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
remove_old_gateway_details(old_storage_paths).map_err(|err| {
|
||||
ClientCoreError::UpgradeFailure {
|
||||
message: format!("failed to remove old data: {err}"),
|
||||
}
|
||||
})
|
||||
Err(ClientCoreError::UnsupportedMigration(
|
||||
"migration of legacy keys has been removed and is no longer supported".into(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ use nym_crypto::{
|
||||
asymmetric::{encryption, identity},
|
||||
hkdf::{DerivationMaterial, InvalidLength},
|
||||
};
|
||||
use nym_gateway_requests::shared_key::{LegacySharedKeys, SharedGatewayKey, SharedSymmetricKey};
|
||||
use nym_gateway_requests::shared_key::SharedSymmetricKey;
|
||||
use nym_sphinx::acknowledgements::AckKey;
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use std::sync::Arc;
|
||||
@@ -106,7 +106,5 @@ fn _assert_keys_zeroize_on_drop() {
|
||||
_assert_zeroize_on_drop::<identity::KeyPair>();
|
||||
_assert_zeroize_on_drop::<encryption::KeyPair>();
|
||||
_assert_zeroize_on_drop::<AckKey>();
|
||||
_assert_zeroize_on_drop::<LegacySharedKeys>();
|
||||
_assert_zeroize_on_drop::<SharedSymmetricKey>();
|
||||
_assert_zeroize_on_drop::<SharedGatewayKey>();
|
||||
}
|
||||
|
||||
@@ -12,6 +12,9 @@ use std::path::PathBuf;
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum ClientCoreError {
|
||||
#[error("could not perform the state migration: {0}")]
|
||||
UnsupportedMigration(String),
|
||||
|
||||
#[error("I/O error: {0}")]
|
||||
IoError(#[from] std::io::Error),
|
||||
|
||||
|
||||
@@ -335,14 +335,6 @@ pub(super) async fn register_with_gateway(
|
||||
}
|
||||
})?;
|
||||
|
||||
// this should NEVER happen, if it did, it means the function was misused,
|
||||
// because for any fresh **registration**, the derived key is always up to date
|
||||
if auth_response.requires_key_upgrade {
|
||||
return Err(ClientCoreError::UnexpectedKeyUpgrade {
|
||||
gateway_id: gateway_id.to_base58_string(),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(RegistrationResult {
|
||||
shared_keys: auth_response.initial_shared_key,
|
||||
authenticated_ephemeral_client: gateway_client,
|
||||
|
||||
@@ -11,7 +11,7 @@ use nym_client_core_gateways_storage::{
|
||||
};
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_gateway_client::client::InitGatewayClient;
|
||||
use nym_gateway_requests::shared_key::SharedGatewayKey;
|
||||
use nym_gateway_requests::shared_key::SharedSymmetricKey;
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_topology::node::RoutingNode;
|
||||
use nym_validator_client::client::IdentityKey;
|
||||
@@ -96,7 +96,7 @@ impl SelectedGateway {
|
||||
/// - shared keys derived between ourselves and the node
|
||||
/// - an authenticated handle of an ephemeral handle created for the purposes of registration
|
||||
pub struct RegistrationResult {
|
||||
pub shared_keys: Arc<SharedGatewayKey>,
|
||||
pub shared_keys: Arc<SharedSymmetricKey>,
|
||||
pub authenticated_ephemeral_client: InitGatewayClient,
|
||||
}
|
||||
|
||||
|
||||
@@ -20,9 +20,8 @@ use nym_credentials_interface::TicketType;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_gateway_requests::registration::handshake::client_handshake;
|
||||
use nym_gateway_requests::{
|
||||
BinaryRequest, ClientControlRequest, ClientRequest, GatewayProtocolVersionExt,
|
||||
SensitiveServerResponse, ServerResponse, SharedGatewayKey, SharedSymmetricKey,
|
||||
CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION, CURRENT_PROTOCOL_VERSION,
|
||||
BinaryRequest, ClientControlRequest, ClientRequest, GatewayProtocolVersionExt, ServerResponse,
|
||||
SharedSymmetricKey, CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION, CURRENT_PROTOCOL_VERSION,
|
||||
};
|
||||
use nym_sphinx::forwarding::packet::MixPacket;
|
||||
use nym_statistics_common::clients::connection::ConnectionStatsEvent;
|
||||
@@ -47,7 +46,6 @@ use std::os::raw::c_int as RawFd;
|
||||
use wasm_utils::websocket::JSWebsocket;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasmtimer::tokio::sleep;
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
pub mod config;
|
||||
|
||||
@@ -82,8 +80,7 @@ impl GatewayConfig {
|
||||
#[must_use]
|
||||
#[derive(Debug)]
|
||||
pub struct AuthenticationResponse {
|
||||
pub initial_shared_key: Arc<SharedGatewayKey>,
|
||||
pub requires_key_upgrade: bool,
|
||||
pub initial_shared_key: Arc<SharedSymmetricKey>,
|
||||
}
|
||||
|
||||
// TODO: this should be refactored into a state machine that keeps track of its authentication state
|
||||
@@ -95,7 +92,7 @@ pub struct GatewayClient<C, St = EphemeralCredentialStorage> {
|
||||
gateway_address: String,
|
||||
gateway_identity: identity::PublicKey,
|
||||
local_identity: Arc<identity::KeyPair>,
|
||||
shared_key: Option<Arc<SharedGatewayKey>>,
|
||||
shared_key: Option<Arc<SharedSymmetricKey>>,
|
||||
connection: SocketState,
|
||||
packet_router: PacketRouter,
|
||||
bandwidth_controller: Option<BandwidthController<C, St>>,
|
||||
@@ -119,7 +116,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
gateway_config: GatewayConfig,
|
||||
local_identity: Arc<identity::KeyPair>,
|
||||
// TODO: make it mandatory. if you don't want to pass it, use `new_init`
|
||||
shared_key: Option<Arc<SharedGatewayKey>>,
|
||||
shared_key: Option<Arc<SharedSymmetricKey>>,
|
||||
packet_router: PacketRouter,
|
||||
bandwidth_controller: Option<BandwidthController<C, St>>,
|
||||
stats_reporter: ClientStatsSender,
|
||||
@@ -149,7 +146,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
self.gateway_identity
|
||||
}
|
||||
|
||||
pub fn shared_key(&self) -> Option<Arc<SharedGatewayKey>> {
|
||||
pub fn shared_key(&self) -> Option<Arc<SharedSymmetricKey>> {
|
||||
self.shared_key.clone()
|
||||
}
|
||||
|
||||
@@ -271,7 +268,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
message: ClientRequest,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
if let Some(shared_key) = self.shared_key() {
|
||||
let encrypted = message.encrypt(&*shared_key)?;
|
||||
let encrypted = message.encrypt(&shared_key)?;
|
||||
Box::pin(self.send_websocket_message(encrypted)).await?;
|
||||
Ok(())
|
||||
} else {
|
||||
@@ -410,49 +407,39 @@ 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 or aes256gcm-siv
|
||||
if !gateway_protocol.supports_authenticate_v2()
|
||||
|| !gateway_protocol.supports_aes256_gcm_siv()
|
||||
{
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
async fn register(
|
||||
&mut self,
|
||||
derive_aes256_gcm_siv_key: bool,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
async fn register(&mut self) -> Result<(), GatewayClientError> {
|
||||
if !self.connection.is_established() {
|
||||
return Err(GatewayClientError::ConnectionNotEstablished);
|
||||
}
|
||||
|
||||
debug_assert!(self.connection.is_available());
|
||||
log::debug!(
|
||||
"registering with gateway. using legacy key derivation: {}",
|
||||
!derive_aes256_gcm_siv_key
|
||||
);
|
||||
|
||||
// it's fine to instantiate it here as it's only used once (during authentication or registration)
|
||||
// and putting it into the GatewayClient struct would be a hassle
|
||||
let mut rng = OsRng;
|
||||
@@ -464,7 +451,6 @@ impl<C, St> GatewayClient<C, St> {
|
||||
self.local_identity.as_ref(),
|
||||
self.gateway_identity,
|
||||
self.cfg.bandwidth.require_tickets,
|
||||
derive_aes256_gcm_siv_key,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
self.task_client.clone(),
|
||||
)
|
||||
@@ -492,77 +478,11 @@ 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(())
|
||||
}
|
||||
|
||||
pub async fn upgrade_key_authenticated(
|
||||
&mut self,
|
||||
) -> Result<Zeroizing<SharedSymmetricKey>, GatewayClientError> {
|
||||
info!("*** STARTING AES128CTR-HMAC KEY UPGRADE INTO AES256GCM-SIV***");
|
||||
|
||||
if !self.connection.is_established() {
|
||||
return Err(GatewayClientError::ConnectionNotEstablished);
|
||||
}
|
||||
|
||||
if !self.authenticated {
|
||||
return Err(GatewayClientError::NotAuthenticated);
|
||||
}
|
||||
|
||||
let Some(shared_key) = self.shared_key.as_ref() else {
|
||||
return Err(GatewayClientError::NoSharedKeyAvailable);
|
||||
};
|
||||
|
||||
if !shared_key.is_legacy() {
|
||||
return Err(GatewayClientError::KeyAlreadyUpgraded);
|
||||
}
|
||||
|
||||
// make sure we have the only reference, so we could safely swap it
|
||||
if Arc::strong_count(shared_key) != 1 {
|
||||
return Err(GatewayClientError::KeyAlreadyInUse);
|
||||
}
|
||||
|
||||
assert!(shared_key.is_legacy());
|
||||
let legacy_key = shared_key.unwrap_legacy();
|
||||
let (updated_key, hkdf_salt) = legacy_key.upgrade();
|
||||
let derived_key_digest = updated_key.digest();
|
||||
|
||||
let upgrade_request = ClientRequest::UpgradeKey {
|
||||
hkdf_salt,
|
||||
derived_key_digest,
|
||||
}
|
||||
.encrypt(legacy_key)?;
|
||||
|
||||
info!("sending upgrade request and awaiting the acknowledgement back");
|
||||
let (ciphertext, nonce) = match self.send_websocket_message(upgrade_request).await? {
|
||||
ServerResponse::EncryptedResponse { ciphertext, nonce } => (ciphertext, nonce),
|
||||
ServerResponse::Error { message } => {
|
||||
return Err(GatewayClientError::GatewayError(message))
|
||||
}
|
||||
other => return Err(GatewayClientError::UnexpectedResponse { name: other.name() }),
|
||||
};
|
||||
|
||||
// attempt to decrypt it using NEW key
|
||||
let Ok(response) = SensitiveServerResponse::decrypt(&ciphertext, &nonce, &updated_key)
|
||||
else {
|
||||
return Err(GatewayClientError::FatalKeyUpgradeFailure);
|
||||
};
|
||||
|
||||
match response {
|
||||
SensitiveServerResponse::KeyUpgradeAck { .. } => {
|
||||
info!("received key upgrade acknowledgement")
|
||||
}
|
||||
_ => return Err(GatewayClientError::FatalKeyUpgradeFailure),
|
||||
}
|
||||
|
||||
// perform in memory swap and make a copy for updating storage
|
||||
let zeroizing_updated_key = updated_key.zeroizing_clone();
|
||||
self.shared_key = Some(Arc::new(updated_key.into()));
|
||||
|
||||
Ok(zeroizing_updated_key)
|
||||
}
|
||||
|
||||
async fn send_authenticate_request_and_handle_response(
|
||||
&mut self,
|
||||
msg: ClientControlRequest,
|
||||
@@ -577,7 +497,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 +510,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 +521,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,24 +545,26 @@ 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 or aes256gcm, 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();
|
||||
|
||||
if !supports_aes_gcm_siv {
|
||||
warn!("this gateway is on an old version that doesn't support AES256-GCM-SIV");
|
||||
error!("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")
|
||||
if !supports_aes_gcm_siv {
|
||||
error!("this gateway is on an old version that doesn't support authentication v2");
|
||||
}
|
||||
|
||||
if !supports_auth_v2 || !supports_aes_gcm_siv {
|
||||
// we can't continue
|
||||
return Err(GatewayClientError::IncompatibleProtocol {
|
||||
gateway: gw_protocol,
|
||||
current: CURRENT_PROTOCOL_VERSION,
|
||||
});
|
||||
}
|
||||
|
||||
if self.authenticated {
|
||||
@@ -675,7 +572,6 @@ impl<C, St> GatewayClient<C, St> {
|
||||
return if let Some(shared_key) = &self.shared_key {
|
||||
Ok(AuthenticationResponse {
|
||||
initial_shared_key: Arc::clone(shared_key),
|
||||
requires_key_upgrade: shared_key.is_legacy() && supports_aes_gcm_siv,
|
||||
})
|
||||
} else {
|
||||
Err(GatewayClientError::AuthenticationFailureWithPreexistingSharedKey)
|
||||
@@ -683,23 +579,20 @@ 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
|
||||
let shared_key = self.shared_key.as_ref().unwrap();
|
||||
|
||||
let requires_key_upgrade = shared_key.is_legacy() && supports_aes_gcm_siv;
|
||||
|
||||
Ok(AuthenticationResponse {
|
||||
initial_shared_key: Arc::clone(shared_key),
|
||||
requires_key_upgrade,
|
||||
})
|
||||
} else {
|
||||
Err(GatewayClientError::AuthenticationFailure)
|
||||
}
|
||||
} else {
|
||||
self.register(supports_aes_gcm_siv).await?;
|
||||
self.register().await?;
|
||||
|
||||
// if registration didn't return an error, we MUST have an associated shared key
|
||||
let shared_key = self.shared_key.as_ref().unwrap();
|
||||
@@ -708,7 +601,6 @@ impl<C, St> GatewayClient<C, St> {
|
||||
// so no upgrades are required
|
||||
Ok(AuthenticationResponse {
|
||||
initial_shared_key: Arc::clone(shared_key),
|
||||
requires_key_upgrade: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1016,8 +908,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?"
|
||||
|
||||
@@ -7,9 +7,7 @@ use tracing::{error, warn};
|
||||
use tungstenite::{protocol::Message, Error as WsError};
|
||||
|
||||
pub use client::{config::GatewayClientConfig, GatewayClient, GatewayConfig};
|
||||
pub use nym_gateway_requests::shared_key::{
|
||||
LegacySharedKeys, SharedGatewayKey, SharedSymmetricKey,
|
||||
};
|
||||
pub use nym_gateway_requests::shared_key::SharedSymmetricKey;
|
||||
pub use packet_router::{
|
||||
AcknowledgementReceiver, AcknowledgementSender, MixnetMessageReceiver, MixnetMessageSender,
|
||||
PacketRouter,
|
||||
@@ -47,7 +45,7 @@ pub(crate) fn cleanup_socket_messages(
|
||||
|
||||
pub(crate) fn try_decrypt_binary_message(
|
||||
bin_msg: Vec<u8>,
|
||||
shared_keys: &SharedGatewayKey,
|
||||
shared_keys: &SharedSymmetricKey,
|
||||
) -> Option<Vec<u8>> {
|
||||
match BinaryResponse::try_from_encrypted_tagged_bytes(bin_msg, shared_keys) {
|
||||
Ok(bin_response) => match bin_response {
|
||||
|
||||
@@ -9,7 +9,7 @@ use crate::{cleanup_socket_messages, try_decrypt_binary_message};
|
||||
use futures::channel::oneshot;
|
||||
use futures::stream::{SplitSink, SplitStream};
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use nym_gateway_requests::shared_key::SharedGatewayKey;
|
||||
use nym_gateway_requests::shared_key::SharedSymmetricKey;
|
||||
use nym_gateway_requests::{ServerResponse, SimpleGatewayRequestsError};
|
||||
use nym_task::TaskClient;
|
||||
use si_scale::helpers::bibytes2;
|
||||
@@ -63,7 +63,7 @@ pub(crate) struct PartiallyDelegatedHandle {
|
||||
|
||||
struct PartiallyDelegatedRouter {
|
||||
packet_router: PacketRouter,
|
||||
shared_key: Arc<SharedGatewayKey>,
|
||||
shared_key: Arc<SharedSymmetricKey>,
|
||||
client_bandwidth: ClientBandwidth,
|
||||
|
||||
stream_return: SplitStreamSender,
|
||||
@@ -73,7 +73,7 @@ struct PartiallyDelegatedRouter {
|
||||
impl PartiallyDelegatedRouter {
|
||||
fn new(
|
||||
packet_router: PacketRouter,
|
||||
shared_key: Arc<SharedGatewayKey>,
|
||||
shared_key: Arc<SharedSymmetricKey>,
|
||||
client_bandwidth: ClientBandwidth,
|
||||
stream_return: SplitStreamSender,
|
||||
stream_return_requester: oneshot::Receiver<()>,
|
||||
@@ -253,7 +253,7 @@ impl PartiallyDelegatedHandle {
|
||||
pub(crate) fn split_and_listen_for_mixnet_messages(
|
||||
conn: WsConn,
|
||||
packet_router: PacketRouter,
|
||||
shared_key: Arc<SharedGatewayKey>,
|
||||
shared_key: Arc<SharedSymmetricKey>,
|
||||
client_bandwidth: ClientBandwidth,
|
||||
shutdown: TaskClient,
|
||||
) -> Self {
|
||||
|
||||
@@ -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;
|
||||
@@ -2,22 +2,15 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub use nym_crypto::generic_array;
|
||||
use nym_crypto::OutputSizeUser;
|
||||
use nym_sphinx::params::GatewayIntegrityHmacAlgorithm;
|
||||
|
||||
pub use types::*;
|
||||
|
||||
pub mod authentication;
|
||||
pub mod models;
|
||||
pub mod registration;
|
||||
pub mod shared_key;
|
||||
pub mod types;
|
||||
|
||||
pub use shared_key::helpers::SymmetricKey;
|
||||
pub use shared_key::legacy::{LegacySharedKeySize, LegacySharedKeys};
|
||||
pub use shared_key::{
|
||||
SharedGatewayKey, SharedKeyConversionError, SharedKeyUsageError, SharedSymmetricKey,
|
||||
};
|
||||
pub use shared_key::{SharedKeyConversionError, SharedKeyUsageError, SharedSymmetricKey};
|
||||
|
||||
pub const CURRENT_PROTOCOL_VERSION: u8 = AUTHENTICATE_V2_PROTOCOL_VERSION;
|
||||
|
||||
@@ -33,23 +26,17 @@ pub const CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION: u8 = 2;
|
||||
pub const AES_GCM_SIV_PROTOCOL_VERSION: u8 = 3;
|
||||
pub const AUTHENTICATE_V2_PROTOCOL_VERSION: u8 = 4;
|
||||
|
||||
// TODO: could using `Mac` trait here for OutputSize backfire?
|
||||
// Should hmac itself be exposed, imported and used instead?
|
||||
pub type LegacyGatewayMacSize = <GatewayIntegrityHmacAlgorithm as OutputSizeUser>::OutputSize;
|
||||
|
||||
pub trait GatewayProtocolVersionExt {
|
||||
fn supports_aes256_gcm_siv(&self) -> bool;
|
||||
fn supports_authenticate_v2(&self) -> bool;
|
||||
}
|
||||
|
||||
impl GatewayProtocolVersionExt for Option<u8> {
|
||||
impl GatewayProtocolVersionExt for u8 {
|
||||
fn supports_aes256_gcm_siv(&self) -> bool {
|
||||
let Some(protocol) = *self else { return false };
|
||||
protocol >= AES_GCM_SIV_PROTOCOL_VERSION
|
||||
*self >= AES_GCM_SIV_PROTOCOL_VERSION
|
||||
}
|
||||
|
||||
fn supports_authenticate_v2(&self) -> bool {
|
||||
let Some(protocol) = *self else { return false };
|
||||
protocol >= AUTHENTICATE_V2_PROTOCOL_VERSION
|
||||
*self >= AUTHENTICATE_V2_PROTOCOL_VERSION
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
use crate::registration::handshake::messages::{Finalization, GatewayMaterialExchange};
|
||||
use crate::registration::handshake::state::State;
|
||||
use crate::registration::handshake::SharedGatewayKey;
|
||||
use crate::registration::handshake::SharedSymmetricKey;
|
||||
use crate::registration::handshake::{error::HandshakeError, WsItem};
|
||||
use futures::{Sink, Stream};
|
||||
use rand::{CryptoRng, RngCore};
|
||||
@@ -15,12 +15,12 @@ impl<S, R> State<'_, S, R> {
|
||||
S: Stream<Item = WsItem> + Sink<WsMessage> + Unpin,
|
||||
R: CryptoRng + RngCore,
|
||||
{
|
||||
// 1. if we're using non-legacy, i.e. aes256gcm-siv derivation, generate initiator salt for kdf
|
||||
let maybe_hkdf_salt = self.maybe_generate_initiator_salt();
|
||||
// 1. generate initiator salt for kdf
|
||||
let hkdf_salt = self.generate_initiator_salt();
|
||||
|
||||
// 1. send ed25519 pubkey alongside ephemeral x25519 pubkey and a hkdf salt if we're using non-legacy client
|
||||
// 1. send ed25519 pubkey alongside ephemeral x25519 pubkey and a hkdf salt
|
||||
// LOCAL_ID_PUBKEY || EPHEMERAL_KEY || MAYBE_SALT
|
||||
let init_message = self.init_message(maybe_hkdf_salt.clone());
|
||||
let init_message = self.init_message(hkdf_salt.clone());
|
||||
self.send_handshake_data(init_message).await?;
|
||||
|
||||
// 2. wait for response with remote x25519 pubkey as well as encrypted signature
|
||||
@@ -31,7 +31,7 @@ impl<S, R> State<'_, S, R> {
|
||||
|
||||
// 3. derive shared keys locally
|
||||
// hkdf::<blake3>::(g^xy)
|
||||
self.derive_shared_key(&mid_res.ephemeral_dh, maybe_hkdf_salt.as_deref());
|
||||
self.derive_shared_key(&mid_res.ephemeral_dh, &hkdf_salt);
|
||||
|
||||
// 4. verify the received signature using the locally derived keys
|
||||
self.verify_remote_key_material(&mid_res.materials, &mid_res.ephemeral_dh)?;
|
||||
@@ -49,7 +49,7 @@ impl<S, R> State<'_, S, R> {
|
||||
|
||||
pub(crate) async fn perform_client_handshake(
|
||||
mut self,
|
||||
) -> Result<SharedGatewayKey, HandshakeError>
|
||||
) -> Result<SharedSymmetricKey, HandshakeError>
|
||||
where
|
||||
S: Stream<Item = WsItem> + Sink<WsMessage> + Unpin,
|
||||
R: CryptoRng + RngCore,
|
||||
|
||||
@@ -5,7 +5,7 @@ use crate::registration::handshake::messages::{
|
||||
HandshakeMessage, Initialisation, MaterialExchange,
|
||||
};
|
||||
use crate::registration::handshake::state::State;
|
||||
use crate::registration::handshake::SharedGatewayKey;
|
||||
use crate::registration::handshake::SharedSymmetricKey;
|
||||
use crate::registration::handshake::{error::HandshakeError, WsItem};
|
||||
use futures::{Sink, Stream};
|
||||
use tungstenite::Message as WsMessage;
|
||||
@@ -18,18 +18,14 @@ impl<S, R> State<'_, S, R> {
|
||||
where
|
||||
S: Stream<Item = WsItem> + Sink<WsMessage> + Unpin,
|
||||
{
|
||||
// 1. receive remote ed25519 pubkey alongside ephemeral x25519 pubkey and maybe a flag indicating non-legacy client
|
||||
// LOCAL_ID_PUBKEY || EPHEMERAL_KEY || MAYBE_NON_LEGACY
|
||||
// 1. receive remote ed25519 pubkey alongside ephemeral x25519 pubkey and initiator salt
|
||||
// LOCAL_ID_PUBKEY || EPHEMERAL_KEY || INITIATOR_SALT
|
||||
let init_message = Initialisation::try_from_bytes(&raw_init_message)?;
|
||||
self.update_remote_identity(init_message.identity);
|
||||
self.set_aes256_gcm_siv_key_derivation(!init_message.is_legacy());
|
||||
|
||||
// 2. derive shared keys locally
|
||||
// hkdf::<blake3>::(g^xy)
|
||||
self.derive_shared_key(
|
||||
&init_message.ephemeral_dh,
|
||||
init_message.initiator_salt.as_deref(),
|
||||
);
|
||||
self.derive_shared_key(&init_message.ephemeral_dh, &init_message.initiator_salt);
|
||||
|
||||
// 3. send ephemeral x25519 pubkey alongside the encrypted signature
|
||||
// g^y || AES(k, sig(gate_priv, (g^y || g^x))
|
||||
@@ -54,7 +50,7 @@ impl<S, R> State<'_, S, R> {
|
||||
pub(crate) async fn perform_gateway_handshake(
|
||||
mut self,
|
||||
raw_init_message: Vec<u8>,
|
||||
) -> Result<SharedGatewayKey, HandshakeError>
|
||||
) -> Result<SharedSymmetricKey, HandshakeError>
|
||||
where
|
||||
S: Stream<Item = WsItem> + Sink<WsMessage> + Unpin,
|
||||
{
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
use crate::registration::handshake::error::HandshakeError;
|
||||
use crate::registration::handshake::KDF_SALT_LENGTH;
|
||||
use nym_crypto::asymmetric::{ed25519, x25519};
|
||||
use nym_crypto::symmetric::aead::{nonce_size, tag_size};
|
||||
use nym_crypto::symmetric::aead::{nonce_size, tag_size, Nonce};
|
||||
use nym_sphinx::params::GatewayEncryptionAlgorithm;
|
||||
|
||||
// it is vital nobody changes the serialisation implementation unless you have an EXTREMELY good reason,
|
||||
@@ -21,20 +21,13 @@ pub trait HandshakeMessage {
|
||||
pub struct Initialisation {
|
||||
pub identity: ed25519::PublicKey,
|
||||
pub ephemeral_dh: x25519::PublicKey,
|
||||
pub initiator_salt: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl Initialisation {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn is_legacy(&self) -> bool {
|
||||
self.initiator_salt.is_none()
|
||||
}
|
||||
pub initiator_salt: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MaterialExchange {
|
||||
pub signature_ciphertext: Vec<u8>,
|
||||
pub nonce: Option<Vec<u8>>,
|
||||
pub nonce: Nonce<GatewayEncryptionAlgorithm>,
|
||||
}
|
||||
|
||||
impl MaterialExchange {
|
||||
@@ -72,17 +65,12 @@ impl HandshakeMessage for Initialisation {
|
||||
// Eventually the ID_PUBKEY prefix will get removed and recipient will know
|
||||
// initializer's identity from another source.
|
||||
fn into_bytes(self) -> Vec<u8> {
|
||||
let bytes = self
|
||||
.identity
|
||||
self.identity
|
||||
.to_bytes()
|
||||
.into_iter()
|
||||
.chain(self.ephemeral_dh.to_bytes());
|
||||
|
||||
if let Some(salt) = self.initiator_salt {
|
||||
bytes.chain(salt).collect()
|
||||
} else {
|
||||
bytes.collect()
|
||||
}
|
||||
.chain(self.ephemeral_dh.to_bytes())
|
||||
.chain(self.initiator_salt)
|
||||
.collect()
|
||||
}
|
||||
|
||||
// this will need to be adjusted when REMOTE_ID_PUBKEY is removed
|
||||
@@ -90,9 +78,8 @@ impl HandshakeMessage for Initialisation {
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let legacy_len = ed25519::PUBLIC_KEY_LENGTH + x25519::PUBLIC_KEY_SIZE;
|
||||
let current_len = legacy_len + KDF_SALT_LENGTH;
|
||||
if bytes.len() != legacy_len && bytes.len() != current_len {
|
||||
let current_len = ed25519::PUBLIC_KEY_LENGTH + x25519::PUBLIC_KEY_SIZE + KDF_SALT_LENGTH;
|
||||
if bytes.len() != current_len {
|
||||
return Err(HandshakeError::MalformedRequest);
|
||||
}
|
||||
|
||||
@@ -101,14 +88,13 @@ impl HandshakeMessage for Initialisation {
|
||||
|
||||
// this can only fail if the provided bytes have len different from encryption::PUBLIC_KEY_SIZE
|
||||
// which is impossible
|
||||
let ephemeral_dh =
|
||||
x25519::PublicKey::from_bytes(&bytes[ed25519::PUBLIC_KEY_LENGTH..legacy_len]).unwrap();
|
||||
let ephemeral_dh = x25519::PublicKey::from_bytes(
|
||||
&bytes
|
||||
[ed25519::PUBLIC_KEY_LENGTH..ed25519::PUBLIC_KEY_LENGTH + x25519::PUBLIC_KEY_SIZE],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let initiator_salt = if bytes.len() == legacy_len {
|
||||
None
|
||||
} else {
|
||||
Some(bytes[legacy_len..].to_vec())
|
||||
};
|
||||
let initiator_salt = bytes[ed25519::PUBLIC_KEY_LENGTH + x25519::PUBLIC_KEY_SIZE..].to_vec();
|
||||
|
||||
Ok(Initialisation {
|
||||
identity,
|
||||
@@ -121,43 +107,31 @@ impl HandshakeMessage for Initialisation {
|
||||
impl HandshakeMessage for MaterialExchange {
|
||||
// AES(k, SIG(PRIV_GATE, G^y || G^x))
|
||||
fn into_bytes(self) -> Vec<u8> {
|
||||
if let Some(nonce) = self.nonce {
|
||||
self.signature_ciphertext
|
||||
.iter()
|
||||
.cloned()
|
||||
.chain(nonce)
|
||||
.collect()
|
||||
} else {
|
||||
self.signature_ciphertext.to_vec()
|
||||
}
|
||||
self.signature_ciphertext
|
||||
.iter()
|
||||
.cloned()
|
||||
.chain(self.nonce)
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn try_from_bytes(bytes: &[u8]) -> Result<Self, HandshakeError>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
// we expect to receive either:
|
||||
// LEGACY: ed25519 signature ciphertext (64 bytes)
|
||||
// CURRENT: ed25519 signature ciphertext (+ tag) + AES256-GCM-SIV nonce (76 bytes)
|
||||
let legacy_len = ed25519::SIGNATURE_LENGTH;
|
||||
let current_len = legacy_len
|
||||
let current_len = ed25519::SIGNATURE_LENGTH
|
||||
+ tag_size::<GatewayEncryptionAlgorithm>()
|
||||
+ nonce_size::<GatewayEncryptionAlgorithm>();
|
||||
|
||||
if bytes.len() != legacy_len && bytes.len() != current_len {
|
||||
if bytes.len() != current_len {
|
||||
return Err(HandshakeError::MalformedResponse);
|
||||
}
|
||||
|
||||
let (signature_ciphertext, nonce) = if bytes.len() == current_len {
|
||||
let ciphertext_len =
|
||||
ed25519::SIGNATURE_LENGTH + tag_size::<GatewayEncryptionAlgorithm>();
|
||||
(
|
||||
bytes[..ciphertext_len].to_vec(),
|
||||
Some(bytes[ciphertext_len..].to_vec()),
|
||||
)
|
||||
} else {
|
||||
(bytes.to_vec(), None)
|
||||
};
|
||||
let ciphertext_len = ed25519::SIGNATURE_LENGTH + tag_size::<GatewayEncryptionAlgorithm>();
|
||||
let signature_ciphertext = bytes[..ciphertext_len].to_vec();
|
||||
|
||||
// SAFETY: we know the bytes have correct length
|
||||
let nonce = Nonce::<GatewayEncryptionAlgorithm>::clone_from_slice(&bytes[ciphertext_len..]);
|
||||
|
||||
Ok(MaterialExchange {
|
||||
signature_ciphertext,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
use self::error::HandshakeError;
|
||||
use crate::registration::handshake::state::State;
|
||||
use crate::SharedGatewayKey;
|
||||
use crate::SharedSymmetricKey;
|
||||
use futures::future::BoxFuture;
|
||||
use futures::{Sink, Stream};
|
||||
use nym_crypto::asymmetric::identity;
|
||||
@@ -34,11 +34,11 @@ pub const KDF_SALT_LENGTH: usize = 16;
|
||||
// we do not need to worry about that.
|
||||
|
||||
pub struct GatewayHandshake<'a> {
|
||||
handshake_future: BoxFuture<'a, Result<SharedGatewayKey, HandshakeError>>,
|
||||
handshake_future: BoxFuture<'a, Result<SharedSymmetricKey, HandshakeError>>,
|
||||
}
|
||||
|
||||
impl Future for GatewayHandshake<'_> {
|
||||
type Output = Result<SharedGatewayKey, HandshakeError>;
|
||||
type Output = Result<SharedSymmetricKey, HandshakeError>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
Pin::new(&mut self.handshake_future).poll(cx)
|
||||
@@ -51,7 +51,6 @@ pub fn client_handshake<'a, S, R>(
|
||||
identity: &'a identity::KeyPair,
|
||||
gateway_pubkey: identity::PublicKey,
|
||||
expects_credential_usage: bool,
|
||||
derive_aes256_gcm_siv_key: bool,
|
||||
#[cfg(not(target_arch = "wasm32"))] shutdown: TaskClient,
|
||||
) -> GatewayHandshake<'a>
|
||||
where
|
||||
@@ -66,8 +65,7 @@ where
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
shutdown,
|
||||
)
|
||||
.with_credential_usage(expects_credential_usage)
|
||||
.with_aes256_gcm_siv_key(derive_aes256_gcm_siv_key);
|
||||
.with_credential_usage(expects_credential_usage);
|
||||
|
||||
GatewayHandshake {
|
||||
handshake_future: Box::pin(state.perform_client_handshake()),
|
||||
|
||||
@@ -5,12 +5,9 @@ use crate::registration::handshake::error::HandshakeError;
|
||||
use crate::registration::handshake::messages::{
|
||||
HandshakeMessage, Initialisation, MaterialExchange,
|
||||
};
|
||||
use crate::registration::handshake::{SharedGatewayKey, WsItem, KDF_SALT_LENGTH};
|
||||
use crate::registration::handshake::{WsItem, KDF_SALT_LENGTH};
|
||||
use crate::shared_key::SharedKeySize;
|
||||
use crate::{
|
||||
types, LegacySharedKeySize, LegacySharedKeys, SharedSymmetricKey, AES_GCM_SIV_PROTOCOL_VERSION,
|
||||
CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION, INITIAL_PROTOCOL_VERSION,
|
||||
};
|
||||
use crate::{types, SharedSymmetricKey, AES_GCM_SIV_PROTOCOL_VERSION};
|
||||
use futures::{Sink, SinkExt, Stream, StreamExt};
|
||||
use nym_crypto::asymmetric::{ed25519, x25519};
|
||||
use nym_crypto::symmetric::aead::random_nonce;
|
||||
@@ -52,7 +49,7 @@ pub(crate) struct State<'a, S, R> {
|
||||
ephemeral_keypair: x25519::KeyPair,
|
||||
|
||||
/// The derived shared key using the ephemeral keys of both parties.
|
||||
derived_shared_keys: Option<SharedGatewayKey>,
|
||||
derived_shared_keys: Option<SharedSymmetricKey>,
|
||||
|
||||
/// The known or received public identity key of the remote.
|
||||
/// Ideally it would always be known before the handshake was initiated.
|
||||
@@ -62,9 +59,6 @@ pub(crate) struct State<'a, S, R> {
|
||||
// in order to establish correct protocol for backwards compatibility reasons
|
||||
expects_credential_usage: bool,
|
||||
|
||||
/// Specifies whether the end product should be an AES128Ctr + blake3 HMAC keys (legacy) or AES256-GCM-SIV (current)
|
||||
derive_aes256_gcm_siv_key: bool,
|
||||
|
||||
// channel to receive shutdown signal
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
shutdown: TaskClient,
|
||||
@@ -91,7 +85,6 @@ impl<'a, S, R> State<'a, S, R> {
|
||||
derived_shared_keys: None,
|
||||
// later on this should become the default
|
||||
expects_credential_usage: false,
|
||||
derive_aes256_gcm_siv_key: false,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
shutdown,
|
||||
}
|
||||
@@ -102,38 +95,24 @@ impl<'a, S, R> State<'a, S, R> {
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn with_aes256_gcm_siv_key(mut self, derive_aes256_gcm_siv_key: bool) -> Self {
|
||||
self.derive_aes256_gcm_siv_key = derive_aes256_gcm_siv_key;
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) fn set_aes256_gcm_siv_key_derivation(&mut self, derive_aes256_gcm_siv_key: bool) {
|
||||
self.derive_aes256_gcm_siv_key = derive_aes256_gcm_siv_key;
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) fn local_ephemeral_key(&self) -> &encryption::PublicKey {
|
||||
self.ephemeral_keypair.public_key()
|
||||
}
|
||||
|
||||
pub(crate) fn maybe_generate_initiator_salt(&mut self) -> Option<Vec<u8>>
|
||||
pub(crate) fn generate_initiator_salt(&mut self) -> Vec<u8>
|
||||
where
|
||||
R: CryptoRng + RngCore,
|
||||
{
|
||||
if self.derive_aes256_gcm_siv_key {
|
||||
let mut salt = vec![0u8; KDF_SALT_LENGTH];
|
||||
self.rng.fill_bytes(&mut salt);
|
||||
Some(salt)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
let mut salt = vec![0u8; KDF_SALT_LENGTH];
|
||||
self.rng.fill_bytes(&mut salt);
|
||||
salt
|
||||
}
|
||||
|
||||
// LOCAL_ID_PUBKEY || EPHEMERAL_KEY || MAYBE_SALT
|
||||
// Eventually the ID_PUBKEY prefix will get removed and recipient will know
|
||||
// initializer's identity from another source.
|
||||
pub(crate) fn init_message(&self, initiator_salt: Option<Vec<u8>>) -> Initialisation {
|
||||
pub(crate) fn init_message(&self, initiator_salt: Vec<u8>) -> Initialisation {
|
||||
Initialisation {
|
||||
identity: *self.identity.public_key(),
|
||||
ephemeral_dh: *self.ephemeral_keypair.public_key(),
|
||||
@@ -151,37 +130,27 @@ impl<'a, S, R> State<'a, S, R> {
|
||||
pub(crate) fn derive_shared_key(
|
||||
&mut self,
|
||||
remote_ephemeral_key: &encryption::PublicKey,
|
||||
initiator_salt: Option<&[u8]>,
|
||||
initiator_salt: &[u8],
|
||||
) {
|
||||
let dh_result = self
|
||||
.ephemeral_keypair
|
||||
.private_key()
|
||||
.diffie_hellman(remote_ephemeral_key);
|
||||
|
||||
let key_size = if self.derive_aes256_gcm_siv_key {
|
||||
SharedKeySize::to_usize()
|
||||
} else {
|
||||
LegacySharedKeySize::to_usize()
|
||||
};
|
||||
let key_size = SharedKeySize::to_usize();
|
||||
|
||||
// there is no reason for this to fail as our okm is expected to be only 16 bytes
|
||||
let okm = hkdf::extract_then_expand::<GatewaySharedKeyHkdfAlgorithm>(
|
||||
initiator_salt,
|
||||
Some(initiator_salt),
|
||||
&dh_result,
|
||||
None,
|
||||
key_size,
|
||||
)
|
||||
.expect("somehow too long okm was provided");
|
||||
|
||||
let shared_key = if self.derive_aes256_gcm_siv_key {
|
||||
let current_key = SharedSymmetricKey::try_from_bytes(&okm)
|
||||
.expect("okm was expanded to incorrect length!");
|
||||
SharedGatewayKey::Current(current_key)
|
||||
} else {
|
||||
let legacy_key = LegacySharedKeys::try_from_bytes(&okm)
|
||||
.expect("okm was expanded to incorrect length!");
|
||||
SharedGatewayKey::Legacy(legacy_key)
|
||||
};
|
||||
let shared_key = SharedSymmetricKey::try_from_bytes(&okm)
|
||||
.expect("okm was expanded to incorrect length!");
|
||||
|
||||
self.derived_shared_keys = Some(shared_key)
|
||||
}
|
||||
|
||||
@@ -200,19 +169,15 @@ impl<'a, S, R> State<'a, S, R> {
|
||||
.collect();
|
||||
let signature = self.identity.private_key().sign(plaintext);
|
||||
|
||||
let nonce = if self.derive_aes256_gcm_siv_key {
|
||||
let mut rng = thread_rng();
|
||||
Some(random_nonce::<GatewayEncryptionAlgorithm, _>(&mut rng).to_vec())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let mut rng = thread_rng();
|
||||
let nonce = random_nonce::<GatewayEncryptionAlgorithm, _>(&mut rng);
|
||||
|
||||
// SAFETY: this function is only called after the local key has already been derived
|
||||
let signature_ciphertext = self
|
||||
.derived_shared_keys
|
||||
.as_ref()
|
||||
.expect("shared key was not derived!")
|
||||
.encrypt_naive(&signature.to_bytes(), nonce.as_deref())?;
|
||||
.encrypt(&signature.to_bytes(), &nonce)?;
|
||||
|
||||
Ok(MaterialExchange {
|
||||
signature_ciphertext,
|
||||
@@ -231,15 +196,10 @@ impl<'a, S, R> State<'a, S, R> {
|
||||
.as_ref()
|
||||
.expect("shared key was not derived!");
|
||||
|
||||
// if the [client] init message contained non-legacy flag, the associated nonce MUST be present
|
||||
if self.derive_aes256_gcm_siv_key && remote_response.nonce.is_none() {
|
||||
return Err(HandshakeError::MissingNonceForCurrentKey);
|
||||
}
|
||||
|
||||
// first decrypt received data
|
||||
let decrypted_signature = derived_shared_key.decrypt_naive(
|
||||
let decrypted_signature = derived_shared_key.decrypt(
|
||||
&remote_response.signature_ciphertext,
|
||||
remote_response.nonce.as_deref(),
|
||||
&remote_response.nonce,
|
||||
)?;
|
||||
|
||||
// now verify signature itself
|
||||
@@ -367,13 +327,7 @@ impl<'a, S, R> State<'a, S, R> {
|
||||
}
|
||||
|
||||
fn request_protocol_version(&self) -> u8 {
|
||||
if self.derive_aes256_gcm_siv_key {
|
||||
AES_GCM_SIV_PROTOCOL_VERSION
|
||||
} else if self.expects_credential_usage {
|
||||
CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION
|
||||
} else {
|
||||
INITIAL_PROTOCOL_VERSION
|
||||
}
|
||||
AES_GCM_SIV_PROTOCOL_VERSION
|
||||
}
|
||||
|
||||
pub(crate) async fn send_handshake_data<M>(
|
||||
@@ -398,7 +352,7 @@ impl<'a, S, R> State<'a, S, R> {
|
||||
|
||||
/// Finish the handshake, yielding the derived shared key and implicitly dropping all borrowed
|
||||
/// values.
|
||||
pub(crate) fn finalize_handshake(self) -> SharedGatewayKey {
|
||||
pub(crate) fn finalize_handshake(self) -> SharedSymmetricKey {
|
||||
self.derived_shared_keys.unwrap()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::{LegacySharedKeys, SharedGatewayKey, SharedKeyUsageError, SharedSymmetricKey};
|
||||
use nym_crypto::symmetric::aead::random_nonce;
|
||||
use nym_crypto::symmetric::stream_cipher::random_iv;
|
||||
use nym_sphinx::params::{GatewayEncryptionAlgorithm, LegacyGatewayEncryptionAlgorithm};
|
||||
use rand::thread_rng;
|
||||
|
||||
pub trait SymmetricKey {
|
||||
fn random_nonce_or_iv(&self) -> Vec<u8>;
|
||||
|
||||
fn encrypt(
|
||||
&self,
|
||||
plaintext: &[u8],
|
||||
nonce: Option<&[u8]>,
|
||||
) -> Result<Vec<u8>, SharedKeyUsageError>;
|
||||
|
||||
fn decrypt(
|
||||
&self,
|
||||
ciphertext: &[u8],
|
||||
nonce: Option<&[u8]>,
|
||||
) -> Result<Vec<u8>, SharedKeyUsageError>;
|
||||
}
|
||||
|
||||
impl SymmetricKey for SharedGatewayKey {
|
||||
fn random_nonce_or_iv(&self) -> Vec<u8> {
|
||||
self.random_nonce_or_iv()
|
||||
}
|
||||
|
||||
fn encrypt(
|
||||
&self,
|
||||
plaintext: &[u8],
|
||||
nonce: Option<&[u8]>,
|
||||
) -> Result<Vec<u8>, SharedKeyUsageError> {
|
||||
self.encrypt(plaintext, nonce)
|
||||
}
|
||||
|
||||
fn decrypt(
|
||||
&self,
|
||||
ciphertext: &[u8],
|
||||
nonce: Option<&[u8]>,
|
||||
) -> Result<Vec<u8>, SharedKeyUsageError> {
|
||||
self.decrypt(ciphertext, nonce)
|
||||
}
|
||||
}
|
||||
|
||||
impl SymmetricKey for SharedSymmetricKey {
|
||||
fn random_nonce_or_iv(&self) -> Vec<u8> {
|
||||
let mut rng = thread_rng();
|
||||
|
||||
random_nonce::<GatewayEncryptionAlgorithm, _>(&mut rng).to_vec()
|
||||
}
|
||||
|
||||
fn encrypt(
|
||||
&self,
|
||||
plaintext: &[u8],
|
||||
nonce: Option<&[u8]>,
|
||||
) -> Result<Vec<u8>, SharedKeyUsageError> {
|
||||
let nonce = SharedGatewayKey::validate_aead_nonce(nonce)?;
|
||||
self.encrypt(plaintext, &nonce)
|
||||
}
|
||||
|
||||
fn decrypt(
|
||||
&self,
|
||||
ciphertext: &[u8],
|
||||
nonce: Option<&[u8]>,
|
||||
) -> Result<Vec<u8>, SharedKeyUsageError> {
|
||||
let nonce = SharedGatewayKey::validate_aead_nonce(nonce)?;
|
||||
self.decrypt(ciphertext, &nonce)
|
||||
}
|
||||
}
|
||||
|
||||
impl SymmetricKey for LegacySharedKeys {
|
||||
fn random_nonce_or_iv(&self) -> Vec<u8> {
|
||||
let mut rng = thread_rng();
|
||||
|
||||
random_iv::<LegacyGatewayEncryptionAlgorithm, _>(&mut rng).to_vec()
|
||||
}
|
||||
|
||||
fn encrypt(
|
||||
&self,
|
||||
plaintext: &[u8],
|
||||
nonce: Option<&[u8]>,
|
||||
) -> Result<Vec<u8>, SharedKeyUsageError> {
|
||||
let iv = SharedGatewayKey::validate_cipher_iv(nonce)?;
|
||||
Ok(self.encrypt_and_tag(plaintext, iv))
|
||||
}
|
||||
|
||||
fn decrypt(
|
||||
&self,
|
||||
ciphertext: &[u8],
|
||||
nonce: Option<&[u8]>,
|
||||
) -> Result<Vec<u8>, SharedKeyUsageError> {
|
||||
let iv = SharedGatewayKey::validate_cipher_iv(nonce)?;
|
||||
self.decrypt_tagged(ciphertext, iv)
|
||||
}
|
||||
}
|
||||
@@ -1,241 +0,0 @@
|
||||
// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::registration::handshake::KDF_SALT_LENGTH;
|
||||
use crate::shared_key::SharedSymmetricKey;
|
||||
use crate::shared_key::{SharedKeyConversionError, SharedKeySize, SharedKeyUsageError};
|
||||
use crate::LegacyGatewayMacSize;
|
||||
use nym_crypto::generic_array::{
|
||||
typenum::{Sum, Unsigned, U16},
|
||||
GenericArray,
|
||||
};
|
||||
use nym_crypto::hkdf;
|
||||
use nym_crypto::hmac::{compute_keyed_hmac, recompute_keyed_hmac_and_verify_tag};
|
||||
use nym_crypto::symmetric::stream_cipher::{self, CipherKey, KeySizeUser, IV};
|
||||
use nym_pemstore::traits::PemStorableKey;
|
||||
use nym_sphinx::params::{
|
||||
GatewayIntegrityHmacAlgorithm, GatewaySharedKeyHkdfAlgorithm, LegacyGatewayEncryptionAlgorithm,
|
||||
};
|
||||
use rand::{thread_rng, RngCore};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
|
||||
|
||||
// shared key is as long as the encryption key and the MAC key combined.
|
||||
pub type LegacySharedKeySize = Sum<EncryptionKeySize, MacKeySize>;
|
||||
|
||||
// we're using 16 byte long key in sphinx, so let's use the same one here
|
||||
type MacKeySize = U16;
|
||||
type EncryptionKeySize = <LegacyGatewayEncryptionAlgorithm as KeySizeUser>::KeySize;
|
||||
|
||||
/// Shared key used when computing MAC for messages exchanged between client and its gateway.
|
||||
pub type MacKey = GenericArray<u8, MacKeySize>;
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize, Zeroize, ZeroizeOnDrop)]
|
||||
pub struct LegacySharedKeys {
|
||||
encryption_key: CipherKey<LegacyGatewayEncryptionAlgorithm>,
|
||||
mac_key: MacKey,
|
||||
}
|
||||
|
||||
impl LegacySharedKeys {
|
||||
pub fn upgrade(&self) -> (SharedSymmetricKey, Vec<u8>) {
|
||||
let mut rng = thread_rng();
|
||||
let mut salt = vec![0u8; KDF_SALT_LENGTH];
|
||||
rng.fill_bytes(&mut salt);
|
||||
|
||||
let legacy_bytes = Zeroizing::new(self.to_bytes());
|
||||
let okm = hkdf::extract_then_expand::<GatewaySharedKeyHkdfAlgorithm>(
|
||||
Some(&salt),
|
||||
&legacy_bytes,
|
||||
None,
|
||||
SharedKeySize::to_usize(),
|
||||
)
|
||||
.expect("somehow too long okm was provided");
|
||||
|
||||
let key = SharedSymmetricKey::try_from_bytes(&okm)
|
||||
.expect("okm was expanded to incorrect length!");
|
||||
(key, salt)
|
||||
}
|
||||
|
||||
pub fn upgrade_verify(
|
||||
&self,
|
||||
salt: &[u8],
|
||||
expected_digest: &[u8],
|
||||
) -> Option<SharedSymmetricKey> {
|
||||
let legacy_bytes = Zeroizing::new(self.to_bytes());
|
||||
let okm = hkdf::extract_then_expand::<GatewaySharedKeyHkdfAlgorithm>(
|
||||
Some(salt),
|
||||
&legacy_bytes,
|
||||
None,
|
||||
SharedKeySize::to_usize(),
|
||||
)
|
||||
.expect("somehow too long okm was provided");
|
||||
let key = SharedSymmetricKey::try_from_bytes(&okm)
|
||||
.expect("okm was expanded to incorrect length!");
|
||||
if key.digest() != expected_digest {
|
||||
// no need to zeroize that key since it's malformed and we won't be using it anyway
|
||||
None
|
||||
} else {
|
||||
Some(key)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_from_bytes(bytes: &[u8]) -> Result<Self, SharedKeyConversionError> {
|
||||
if bytes.len() != LegacySharedKeySize::to_usize() {
|
||||
return Err(SharedKeyConversionError::InvalidSharedKeysSize {
|
||||
received: bytes.len(),
|
||||
expected: LegacySharedKeySize::to_usize(),
|
||||
});
|
||||
}
|
||||
|
||||
let encryption_key =
|
||||
GenericArray::clone_from_slice(&bytes[..EncryptionKeySize::to_usize()]);
|
||||
let mac_key = GenericArray::clone_from_slice(&bytes[EncryptionKeySize::to_usize()..]);
|
||||
|
||||
Ok(LegacySharedKeys {
|
||||
encryption_key,
|
||||
mac_key,
|
||||
})
|
||||
}
|
||||
|
||||
/// Encrypts the provided data using the optionally provided initialisation vector,
|
||||
/// or a 0 value if nothing was given.
|
||||
/// It does **NOT** attach any integrity macs on the produced ciphertext
|
||||
pub fn encrypt_without_tagging(
|
||||
&self,
|
||||
data: &[u8],
|
||||
iv: Option<&IV<LegacyGatewayEncryptionAlgorithm>>,
|
||||
) -> Vec<u8> {
|
||||
match iv {
|
||||
Some(iv) => stream_cipher::encrypt::<LegacyGatewayEncryptionAlgorithm>(
|
||||
self.encryption_key(),
|
||||
iv,
|
||||
data,
|
||||
),
|
||||
None => {
|
||||
let zero_iv = stream_cipher::zero_iv::<LegacyGatewayEncryptionAlgorithm>();
|
||||
stream_cipher::encrypt::<LegacyGatewayEncryptionAlgorithm>(
|
||||
self.encryption_key(),
|
||||
&zero_iv,
|
||||
data,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Encrypts the provided data using the optionally provided initialisation vector,
|
||||
/// or a 0 value if nothing was given. Then it computes an integrity mac and concatenates it
|
||||
/// with the previously produced ciphertext.
|
||||
pub fn encrypt_and_tag(
|
||||
&self,
|
||||
data: &[u8],
|
||||
iv: Option<&IV<LegacyGatewayEncryptionAlgorithm>>,
|
||||
) -> Vec<u8> {
|
||||
let ciphertext = self.encrypt_without_tagging(data, iv);
|
||||
let mac = compute_keyed_hmac::<GatewayIntegrityHmacAlgorithm>(
|
||||
self.mac_key().as_slice(),
|
||||
&ciphertext,
|
||||
);
|
||||
|
||||
mac.into_bytes().into_iter().chain(ciphertext).collect()
|
||||
}
|
||||
|
||||
pub fn decrypt_without_tag(
|
||||
&self,
|
||||
ciphertext: &[u8],
|
||||
iv: Option<&IV<LegacyGatewayEncryptionAlgorithm>>,
|
||||
) -> Result<Vec<u8>, SharedKeyUsageError> {
|
||||
let zero_iv = stream_cipher::zero_iv::<LegacyGatewayEncryptionAlgorithm>();
|
||||
let iv = iv.unwrap_or(&zero_iv);
|
||||
Ok(stream_cipher::decrypt::<LegacyGatewayEncryptionAlgorithm>(
|
||||
self.encryption_key(),
|
||||
iv,
|
||||
ciphertext,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn decrypt_tagged(
|
||||
&self,
|
||||
enc_data: &[u8],
|
||||
iv: Option<&IV<LegacyGatewayEncryptionAlgorithm>>,
|
||||
) -> Result<Vec<u8>, SharedKeyUsageError> {
|
||||
let mac_size = LegacyGatewayMacSize::to_usize();
|
||||
if enc_data.len() < mac_size {
|
||||
return Err(SharedKeyUsageError::TooShortRequest);
|
||||
}
|
||||
|
||||
let mac_tag = &enc_data[..mac_size];
|
||||
let message_bytes = &enc_data[mac_size..];
|
||||
|
||||
if !recompute_keyed_hmac_and_verify_tag::<GatewayIntegrityHmacAlgorithm>(
|
||||
self.mac_key().as_slice(),
|
||||
message_bytes,
|
||||
mac_tag,
|
||||
) {
|
||||
return Err(SharedKeyUsageError::InvalidMac);
|
||||
}
|
||||
|
||||
// couldn't have made the first borrow mutable as you can't have an immutable borrow
|
||||
// together with a mutable one
|
||||
let mut message_bytes_mut = message_bytes.to_vec();
|
||||
|
||||
let zero_iv = stream_cipher::zero_iv::<LegacyGatewayEncryptionAlgorithm>();
|
||||
let iv = iv.unwrap_or(&zero_iv);
|
||||
stream_cipher::decrypt_in_place::<LegacyGatewayEncryptionAlgorithm>(
|
||||
self.encryption_key(),
|
||||
iv,
|
||||
&mut message_bytes_mut,
|
||||
);
|
||||
Ok(message_bytes_mut)
|
||||
}
|
||||
|
||||
pub fn encryption_key(&self) -> &CipherKey<LegacyGatewayEncryptionAlgorithm> {
|
||||
&self.encryption_key
|
||||
}
|
||||
|
||||
pub fn mac_key(&self) -> &MacKey {
|
||||
&self.mac_key
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
self.encryption_key
|
||||
.iter()
|
||||
.copied()
|
||||
.chain(self.mac_key.iter().copied())
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn try_from_base58_string<S: Into<String>>(
|
||||
val: S,
|
||||
) -> Result<Self, SharedKeyConversionError> {
|
||||
let decoded = bs58::decode(val.into()).into_vec()?;
|
||||
LegacySharedKeys::try_from_bytes(&decoded)
|
||||
}
|
||||
|
||||
pub fn to_base58_string(&self) -> String {
|
||||
bs58::encode(self.to_bytes()).into_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LegacySharedKeys> for String {
|
||||
fn from(keys: LegacySharedKeys) -> Self {
|
||||
keys.to_base58_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl PemStorableKey for LegacySharedKeys {
|
||||
type Error = SharedKeyConversionError;
|
||||
|
||||
fn pem_type() -> &'static str {
|
||||
// TODO: If common\nymsphinx\params\src\lib::GatewayIntegrityHmacAlgorithm changes
|
||||
// the pem type needs updating!
|
||||
"AES-128-CTR + HMAC-BLAKE3 GATEWAY SHARED KEYS"
|
||||
}
|
||||
|
||||
fn to_bytes(&self) -> Vec<u8> {
|
||||
self.to_bytes()
|
||||
}
|
||||
|
||||
fn from_bytes(bytes: &[u8]) -> Result<Self, Self::Error> {
|
||||
Self::try_from_bytes(bytes)
|
||||
}
|
||||
}
|
||||
@@ -7,213 +7,27 @@ use nym_crypto::generic_array::{typenum::Unsigned, GenericArray};
|
||||
use nym_crypto::symmetric::aead::{
|
||||
self, nonce_size, random_nonce, AeadError, AeadKey, KeySizeUser, Nonce,
|
||||
};
|
||||
use nym_crypto::symmetric::stream_cipher::{iv_size, random_iv, IV};
|
||||
use nym_pemstore::traits::PemStorableKey;
|
||||
use nym_sphinx::params::{GatewayEncryptionAlgorithm, LegacyGatewayEncryptionAlgorithm};
|
||||
use nym_sphinx::params::GatewayEncryptionAlgorithm;
|
||||
use rand::thread_rng;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
|
||||
|
||||
pub use legacy::LegacySharedKeys;
|
||||
|
||||
pub mod helpers;
|
||||
pub mod legacy;
|
||||
|
||||
pub type SharedKeySize = <GatewayEncryptionAlgorithm as KeySizeUser>::KeySize;
|
||||
|
||||
#[derive(Debug, PartialEq, Zeroize, ZeroizeOnDrop)]
|
||||
pub enum SharedGatewayKey {
|
||||
Current(SharedSymmetricKey),
|
||||
Legacy(LegacySharedKeys),
|
||||
}
|
||||
|
||||
impl SharedGatewayKey {
|
||||
pub fn is_legacy(&self) -> bool {
|
||||
matches!(self, SharedGatewayKey::Legacy(..))
|
||||
}
|
||||
|
||||
pub fn aes128_ctr_hmac_bs58(&self) -> Option<Zeroizing<String>> {
|
||||
match self {
|
||||
SharedGatewayKey::Current(_) => None,
|
||||
SharedGatewayKey::Legacy(key) => Some(Zeroizing::new(key.to_base58_string())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn aes256_gcm_siv(&self) -> Option<Zeroizing<Vec<u8>>> {
|
||||
match self {
|
||||
SharedGatewayKey::Current(key) => Some(Zeroizing::new(key.to_bytes())),
|
||||
SharedGatewayKey::Legacy(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unwrap_legacy(&self) -> &LegacySharedKeys {
|
||||
match self {
|
||||
SharedGatewayKey::Current(_) => panic!("expected legacy key"),
|
||||
SharedGatewayKey::Legacy(key) => key,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn random_nonce_or_iv(&self) -> Vec<u8> {
|
||||
let mut rng = thread_rng();
|
||||
|
||||
if self.is_legacy() {
|
||||
random_iv::<LegacyGatewayEncryptionAlgorithm, _>(&mut rng).to_vec()
|
||||
} else {
|
||||
random_nonce::<GatewayEncryptionAlgorithm, _>(&mut rng).to_vec()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn random_nonce_or_zero_iv(&self) -> Option<Vec<u8>> {
|
||||
if self.is_legacy() {
|
||||
None
|
||||
} else {
|
||||
let mut rng = thread_rng();
|
||||
Some(random_nonce::<GatewayEncryptionAlgorithm, _>(&mut rng).to_vec())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn nonce_size(&self) -> usize {
|
||||
match self {
|
||||
SharedGatewayKey::Current(_) => nonce_size::<GatewayEncryptionAlgorithm>(),
|
||||
SharedGatewayKey::Legacy(_) => iv_size::<LegacyGatewayEncryptionAlgorithm>(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LegacySharedKeys> for SharedGatewayKey {
|
||||
fn from(keys: LegacySharedKeys) -> Self {
|
||||
SharedGatewayKey::Legacy(keys)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SharedSymmetricKey> for SharedGatewayKey {
|
||||
fn from(keys: SharedSymmetricKey) -> Self {
|
||||
SharedGatewayKey::Current(keys)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum SharedKeyUsageError {
|
||||
#[error("the request is too short")]
|
||||
TooShortRequest,
|
||||
|
||||
#[error("provided MAC is invalid")]
|
||||
InvalidMac,
|
||||
|
||||
#[error("the provided nonce (or legacy IV) did not have the expected length")]
|
||||
#[error("the provided nonce did not have the expected length or was malformed")]
|
||||
MalformedNonce,
|
||||
|
||||
#[error("did not provide a valid nonce for aead encryption")]
|
||||
MissingAeadNonce,
|
||||
|
||||
#[error("failed to either encrypt or decrypt provided message")]
|
||||
AeadFailure(#[from] AeadError),
|
||||
}
|
||||
|
||||
impl SharedGatewayKey {
|
||||
fn validate_aead_nonce(
|
||||
raw: Option<&[u8]>,
|
||||
) -> Result<Nonce<GatewayEncryptionAlgorithm>, SharedKeyUsageError> {
|
||||
let Some(raw) = raw else {
|
||||
return Err(SharedKeyUsageError::MissingAeadNonce);
|
||||
};
|
||||
if raw.len() != nonce_size::<GatewayEncryptionAlgorithm>() {
|
||||
return Err(SharedKeyUsageError::MalformedNonce);
|
||||
}
|
||||
Ok(Nonce::<GatewayEncryptionAlgorithm>::clone_from_slice(raw))
|
||||
}
|
||||
|
||||
fn validate_cipher_iv(
|
||||
raw: Option<&[u8]>,
|
||||
) -> Result<Option<&IV<LegacyGatewayEncryptionAlgorithm>>, SharedKeyUsageError> {
|
||||
let Some(raw) = raw else { return Ok(None) };
|
||||
let iv = if raw.is_empty() {
|
||||
None
|
||||
} else {
|
||||
if raw.len() != iv_size::<LegacyGatewayEncryptionAlgorithm>() {
|
||||
return Err(SharedKeyUsageError::MalformedNonce);
|
||||
}
|
||||
Some(IV::<LegacyGatewayEncryptionAlgorithm>::from_slice(raw))
|
||||
};
|
||||
Ok(iv)
|
||||
}
|
||||
|
||||
pub fn encrypt(
|
||||
&self,
|
||||
plaintext: &[u8],
|
||||
// the best common denominator for converting into 'IV' and 'Nonce' types
|
||||
raw_nonce: Option<&[u8]>,
|
||||
) -> Result<Vec<u8>, SharedKeyUsageError> {
|
||||
match self {
|
||||
SharedGatewayKey::Current(aes_gcm_siv) => {
|
||||
let nonce = Self::validate_aead_nonce(raw_nonce)?;
|
||||
aes_gcm_siv.encrypt(plaintext, &nonce)
|
||||
}
|
||||
SharedGatewayKey::Legacy(aes_ctr) => {
|
||||
let iv = Self::validate_cipher_iv(raw_nonce)?;
|
||||
Ok(aes_ctr.encrypt_and_tag(plaintext, iv))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn decrypt(
|
||||
&self,
|
||||
ciphertext: &[u8],
|
||||
// the best common denominator for converting into 'IV' and 'Nonce' types
|
||||
raw_nonce: Option<&[u8]>,
|
||||
) -> Result<Vec<u8>, SharedKeyUsageError> {
|
||||
match self {
|
||||
SharedGatewayKey::Current(aes_gcm_siv) => {
|
||||
let nonce = Self::validate_aead_nonce(raw_nonce)?;
|
||||
aes_gcm_siv.decrypt(ciphertext, &nonce)
|
||||
}
|
||||
SharedGatewayKey::Legacy(aes_ctr) => {
|
||||
let iv = Self::validate_cipher_iv(raw_nonce)?;
|
||||
aes_ctr.decrypt_tagged(ciphertext, iv)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// for the legacy keys do not use integrity MAC
|
||||
pub fn encrypt_naive(
|
||||
&self,
|
||||
plaintext: &[u8],
|
||||
// the best common denominator for converting into 'IV' and 'Nonce' types
|
||||
raw_nonce: Option<&[u8]>,
|
||||
) -> Result<Vec<u8>, SharedKeyUsageError> {
|
||||
match self {
|
||||
SharedGatewayKey::Current(aes_gcm_siv) => {
|
||||
let nonce = Self::validate_aead_nonce(raw_nonce)?;
|
||||
aes_gcm_siv.encrypt(plaintext, &nonce)
|
||||
}
|
||||
SharedGatewayKey::Legacy(aes_ctr) => {
|
||||
let iv = Self::validate_cipher_iv(raw_nonce)?;
|
||||
Ok(aes_ctr.encrypt_without_tagging(plaintext, iv))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// for the legacy keys do not use integrity MAC
|
||||
pub fn decrypt_naive(
|
||||
&self,
|
||||
ciphertext: &[u8],
|
||||
// the best common denominator for converting into 'IV' and 'Nonce' types
|
||||
raw_nonce: Option<&[u8]>,
|
||||
) -> Result<Vec<u8>, SharedKeyUsageError> {
|
||||
match self {
|
||||
SharedGatewayKey::Current(aes_gcm_siv) => {
|
||||
let nonce = Self::validate_aead_nonce(raw_nonce)?;
|
||||
aes_gcm_siv.decrypt(ciphertext, &nonce)
|
||||
}
|
||||
SharedGatewayKey::Legacy(aes_ctr) => {
|
||||
let iv = Self::validate_cipher_iv(raw_nonce)?;
|
||||
aes_ctr.decrypt_without_tag(ciphertext, iv)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize, Zeroize, ZeroizeOnDrop)]
|
||||
pub struct SharedSymmetricKey(AeadKey<GatewayEncryptionAlgorithm>);
|
||||
|
||||
@@ -230,6 +44,36 @@ pub enum SharedKeyConversionError {
|
||||
}
|
||||
|
||||
impl SharedSymmetricKey {
|
||||
pub fn random_nonce(&self) -> Nonce<GatewayEncryptionAlgorithm> {
|
||||
let mut rng = thread_rng();
|
||||
random_nonce::<GatewayEncryptionAlgorithm, _>(&mut rng)
|
||||
}
|
||||
|
||||
pub fn nonce_size(&self) -> usize {
|
||||
nonce_size::<GatewayEncryptionAlgorithm>()
|
||||
}
|
||||
|
||||
pub fn decode_bs58_nonce<I: AsRef<[u8]>>(
|
||||
raw: I,
|
||||
) -> Result<Nonce<GatewayEncryptionAlgorithm>, SharedKeyUsageError> {
|
||||
// 1. decode bytes from encoding
|
||||
let decoded = bs58::decode(raw)
|
||||
.into_vec()
|
||||
.map_err(|_| SharedKeyUsageError::MalformedNonce)?;
|
||||
|
||||
// 2. validate length and convert into the proper type
|
||||
Self::validate_aead_nonce(&decoded)
|
||||
}
|
||||
|
||||
pub fn validate_aead_nonce(
|
||||
raw: &[u8],
|
||||
) -> Result<Nonce<GatewayEncryptionAlgorithm>, SharedKeyUsageError> {
|
||||
if raw.len() != nonce_size::<GatewayEncryptionAlgorithm>() {
|
||||
return Err(SharedKeyUsageError::MalformedNonce);
|
||||
}
|
||||
Ok(Nonce::<GatewayEncryptionAlgorithm>::clone_from_slice(raw))
|
||||
}
|
||||
|
||||
pub fn try_from_bytes(bytes: &[u8]) -> Result<Self, SharedKeyConversionError> {
|
||||
if bytes.len() != KeySize::to_usize() {
|
||||
return Err(SharedKeyConversionError::InvalidSharedKeysSize {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::types::helpers::BinaryData;
|
||||
use crate::{GatewayRequestsError, SharedGatewayKey};
|
||||
use crate::{GatewayRequestsError, SharedSymmetricKey};
|
||||
use nym_sphinx::forwarding::packet::MixPacket;
|
||||
use strum::FromRepr;
|
||||
use tungstenite::Message;
|
||||
@@ -46,14 +46,14 @@ impl BinaryRequest {
|
||||
|
||||
pub fn try_from_encrypted_tagged_bytes(
|
||||
bytes: Vec<u8>,
|
||||
shared_key: &SharedGatewayKey,
|
||||
shared_key: &SharedSymmetricKey,
|
||||
) -> Result<Self, GatewayRequestsError> {
|
||||
BinaryData::from_raw(&bytes, shared_key)?.into_request(shared_key)
|
||||
}
|
||||
|
||||
pub fn into_encrypted_tagged_bytes(
|
||||
self,
|
||||
shared_key: &SharedGatewayKey,
|
||||
shared_key: &SharedSymmetricKey,
|
||||
) -> Result<Vec<u8>, GatewayRequestsError> {
|
||||
let kind = self.kind();
|
||||
|
||||
@@ -66,7 +66,7 @@ impl BinaryRequest {
|
||||
|
||||
pub fn into_ws_message(
|
||||
self,
|
||||
shared_key: &SharedGatewayKey,
|
||||
shared_key: &SharedSymmetricKey,
|
||||
) -> Result<Message, GatewayRequestsError> {
|
||||
// all variants are currently encrypted
|
||||
let blob = match self {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::types::helpers::BinaryData;
|
||||
use crate::{GatewayRequestsError, SharedGatewayKey};
|
||||
use crate::{GatewayRequestsError, SharedSymmetricKey};
|
||||
use strum::FromRepr;
|
||||
use tungstenite::Message;
|
||||
|
||||
@@ -38,14 +38,14 @@ impl BinaryResponse {
|
||||
|
||||
pub fn try_from_encrypted_tagged_bytes(
|
||||
bytes: Vec<u8>,
|
||||
shared_key: &SharedGatewayKey,
|
||||
shared_key: &SharedSymmetricKey,
|
||||
) -> Result<Self, GatewayRequestsError> {
|
||||
BinaryData::from_raw(&bytes, shared_key)?.into_response(shared_key)
|
||||
}
|
||||
|
||||
pub fn into_encrypted_tagged_bytes(
|
||||
self,
|
||||
shared_key: &SharedGatewayKey,
|
||||
shared_key: &SharedSymmetricKey,
|
||||
) -> Result<Vec<u8>, GatewayRequestsError> {
|
||||
let kind = self.kind();
|
||||
|
||||
@@ -58,7 +58,7 @@ impl BinaryResponse {
|
||||
|
||||
pub fn into_ws_message(
|
||||
self,
|
||||
shared_key: &SharedGatewayKey,
|
||||
shared_key: &SharedSymmetricKey,
|
||||
) -> Result<Message, GatewayRequestsError> {
|
||||
// all variants are currently encrypted
|
||||
let blob = match self {
|
||||
|
||||
@@ -8,7 +8,6 @@ use nym_sphinx::addressing::nodes::NymNodeRoutingAddressError;
|
||||
use nym_sphinx::forwarding::packet::MixPacketFormattingError;
|
||||
use nym_sphinx::params::packet_sizes::PacketSize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::string::FromUtf8Error;
|
||||
use thiserror::Error;
|
||||
|
||||
// specific errors (that should not be nested!!) for clients to match on
|
||||
@@ -51,9 +50,6 @@ pub enum GatewayRequestsError {
|
||||
#[error("the request is too short")]
|
||||
TooShortRequest,
|
||||
|
||||
#[error("provided MAC is invalid")]
|
||||
InvalidMac,
|
||||
|
||||
#[error("address field was incorrectly encoded: {source}")]
|
||||
IncorrectlyEncodedAddress {
|
||||
#[from]
|
||||
@@ -69,30 +65,15 @@ pub enum GatewayRequestsError {
|
||||
]
|
||||
RequestOfInvalidSize(usize),
|
||||
|
||||
#[error("received sphinx packet was malformed")]
|
||||
MalformedSphinxPacket,
|
||||
|
||||
#[error("failed to serialise created sphinx packet: {0}")]
|
||||
SphinxSerialisationFailure(#[from] MixPacketFormattingError),
|
||||
|
||||
#[error("the received encrypted data was malformed")]
|
||||
MalformedEncryption,
|
||||
|
||||
#[error("provided packet mode is invalid")]
|
||||
InvalidPacketMode,
|
||||
|
||||
#[error("failed to deserialize provided credential: {0}")]
|
||||
EcashCredentialDeserializationFailure(#[from] CompactEcashError),
|
||||
|
||||
#[error("failed to deserialize provided credential: EOF")]
|
||||
CredentialDeserializationFailureEOF,
|
||||
|
||||
#[error("failed to deserialize provided credential: malformed string: {0}")]
|
||||
CredentialDeserializationFailureMalformedString(#[from] FromUtf8Error),
|
||||
|
||||
#[error("the provided [v1] credential has invalid number of parameters - {0}")]
|
||||
InvalidNumberOfEmbededParameters(u32),
|
||||
|
||||
#[error("failed to authenticate the client: {0}")]
|
||||
Authentication(#[from] AuthenticationFailure),
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
use crate::{
|
||||
BinaryRequest, BinaryRequestKind, BinaryResponse, BinaryResponseKind, GatewayRequestsError,
|
||||
SharedGatewayKey,
|
||||
SharedSymmetricKey,
|
||||
};
|
||||
use std::iter::once;
|
||||
|
||||
@@ -22,11 +22,7 @@ pub struct BinaryData<'a> {
|
||||
|
||||
impl<'a> BinaryData<'a> {
|
||||
// serialises possibly encrypted data into bytes to be put on the wire
|
||||
pub fn into_raw(self, legacy: bool) -> Vec<u8> {
|
||||
if legacy {
|
||||
return self.data.to_vec();
|
||||
}
|
||||
|
||||
pub fn into_raw(self) -> Vec<u8> {
|
||||
let i = once(self.kind).chain(once(if self.encrypted { 1 } else { 0 }));
|
||||
if let Some(nonce) = self.maybe_nonce {
|
||||
i.chain(nonce.iter().copied())
|
||||
@@ -40,19 +36,8 @@ impl<'a> BinaryData<'a> {
|
||||
// attempts to perform basic parsing on bytes received from the wire
|
||||
pub fn from_raw(
|
||||
raw: &'a [u8],
|
||||
available_key: &SharedGatewayKey,
|
||||
available_key: &SharedSymmetricKey,
|
||||
) -> Result<Self, GatewayRequestsError> {
|
||||
// if we're using legacy key, it's quite simple:
|
||||
// it's always encrypted with no nonce and the request/response kind is always 1
|
||||
if available_key.is_legacy() {
|
||||
return Ok(BinaryData {
|
||||
kind: 1,
|
||||
encrypted: true,
|
||||
maybe_nonce: None,
|
||||
data: raw,
|
||||
});
|
||||
}
|
||||
|
||||
if raw.len() < 2 {
|
||||
return Err(GatewayRequestsError::TooShortRequest);
|
||||
}
|
||||
@@ -83,30 +68,33 @@ impl<'a> BinaryData<'a> {
|
||||
pub fn make_encrypted_blob(
|
||||
kind: u8,
|
||||
plaintext: &[u8],
|
||||
key: &SharedGatewayKey,
|
||||
key: &SharedSymmetricKey,
|
||||
) -> Result<Vec<u8>, GatewayRequestsError> {
|
||||
let maybe_nonce = key.random_nonce_or_zero_iv();
|
||||
let nonce = key.random_nonce();
|
||||
|
||||
let ciphertext = key.encrypt(plaintext, maybe_nonce.as_deref())?;
|
||||
let ciphertext = key.encrypt(plaintext, &nonce)?;
|
||||
Ok(BinaryData {
|
||||
kind,
|
||||
encrypted: true,
|
||||
maybe_nonce: maybe_nonce.as_deref(),
|
||||
maybe_nonce: Some(&nonce),
|
||||
data: &ciphertext,
|
||||
}
|
||||
.into_raw(key.is_legacy()))
|
||||
.into_raw())
|
||||
}
|
||||
|
||||
// attempts to parse previously recovered bytes into a [`BinaryRequest`]
|
||||
pub fn into_request(
|
||||
self,
|
||||
key: &SharedGatewayKey,
|
||||
key: &SharedSymmetricKey,
|
||||
) -> Result<BinaryRequest, GatewayRequestsError> {
|
||||
let kind = BinaryRequestKind::from_repr(self.kind)
|
||||
.ok_or(GatewayRequestsError::UnknownRequestKind { kind: self.kind })?;
|
||||
|
||||
let plaintext = if self.encrypted {
|
||||
&*key.decrypt(self.data, self.maybe_nonce)?
|
||||
let raw_nonce = self.maybe_nonce.unwrap_or(&[]);
|
||||
let nonce = SharedSymmetricKey::validate_aead_nonce(raw_nonce)?;
|
||||
|
||||
&*key.decrypt(self.data, &nonce)?
|
||||
} else {
|
||||
self.data
|
||||
};
|
||||
@@ -117,13 +105,16 @@ impl<'a> BinaryData<'a> {
|
||||
// attempts to parse previously recovered bytes into a [`BinaryResponse`]
|
||||
pub fn into_response(
|
||||
self,
|
||||
key: &SharedGatewayKey,
|
||||
key: &SharedSymmetricKey,
|
||||
) -> Result<BinaryResponse, GatewayRequestsError> {
|
||||
let kind = BinaryResponseKind::from_repr(self.kind)
|
||||
.ok_or(GatewayRequestsError::UnknownResponseKind { kind: self.kind })?;
|
||||
|
||||
let plaintext = if self.encrypted {
|
||||
&*key.decrypt(self.data, self.maybe_nonce)?
|
||||
let raw_nonce = self.maybe_nonce.unwrap_or(&[]);
|
||||
let nonce = SharedSymmetricKey::validate_aead_nonce(raw_nonce)?;
|
||||
|
||||
&*key.decrypt(self.data, &nonce)?
|
||||
} else {
|
||||
self.data
|
||||
};
|
||||
|
||||
@@ -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!"),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::{AuthenticationFailure, GatewayRequestsError, SharedGatewayKey};
|
||||
use crate::{AuthenticationFailure, GatewayRequestsError, SharedSymmetricKey};
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::iter;
|
||||
@@ -21,7 +21,7 @@ pub struct AuthenticateRequest {
|
||||
impl AuthenticateRequest {
|
||||
pub fn new(
|
||||
protocol_version: u8,
|
||||
shared_key: &SharedGatewayKey,
|
||||
shared_key: &SharedSymmetricKey,
|
||||
identity_keys: &ed25519::KeyPair,
|
||||
) -> Result<AuthenticateRequest, GatewayRequestsError> {
|
||||
let content = AuthenticateRequestContent::new(
|
||||
@@ -61,14 +61,14 @@ impl AuthenticateRequest {
|
||||
|
||||
pub fn verify_ciphertext(
|
||||
&self,
|
||||
shared_key: &SharedGatewayKey,
|
||||
shared_key: &SharedSymmetricKey,
|
||||
) -> Result<(), AuthenticationFailure> {
|
||||
let expected = shared_key.encrypt(
|
||||
self.content
|
||||
.client_identity
|
||||
.derive_destination_address()
|
||||
.as_bytes_ref(),
|
||||
Some(&self.content.nonce),
|
||||
&SharedSymmetricKey::validate_aead_nonce(&self.content.nonce)?,
|
||||
)?;
|
||||
|
||||
if !bool::from(expected.ct_eq(&self.content.address_ciphertext)) {
|
||||
@@ -106,20 +106,19 @@ pub struct AuthenticateRequestContent {
|
||||
impl AuthenticateRequestContent {
|
||||
fn new(
|
||||
protocol_version: u8,
|
||||
shared_key: &SharedGatewayKey,
|
||||
shared_key: &SharedSymmetricKey,
|
||||
client_identity: ed25519::PublicKey,
|
||||
) -> Result<AuthenticateRequestContent, GatewayRequestsError> {
|
||||
let nonce = shared_key.random_nonce_or_iv();
|
||||
let nonce = shared_key.random_nonce();
|
||||
let destination_address = client_identity.derive_destination_address();
|
||||
|
||||
let address_ciphertext =
|
||||
shared_key.encrypt(destination_address.as_bytes_ref(), Some(&nonce))?;
|
||||
let address_ciphertext = shared_key.encrypt(destination_address.as_bytes_ref(), &nonce)?;
|
||||
let now = OffsetDateTime::now_utc();
|
||||
Ok(AuthenticateRequestContent {
|
||||
protocol_version,
|
||||
client_identity,
|
||||
address_ciphertext,
|
||||
nonce,
|
||||
nonce: nonce.to_vec(),
|
||||
request_unix_timestamp: now.unix_timestamp() as u64, // SAFETY: we're running this in post 1970...
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,14 +3,9 @@
|
||||
|
||||
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,
|
||||
};
|
||||
use crate::{GatewayRequestsError, SharedSymmetricKey, 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;
|
||||
@@ -21,20 +16,13 @@ pub mod authenticate;
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[non_exhaustive]
|
||||
pub enum ClientRequest {
|
||||
UpgradeKey {
|
||||
hkdf_salt: Vec<u8>,
|
||||
derived_key_digest: Vec<u8>,
|
||||
},
|
||||
ForgetMe {
|
||||
client: bool,
|
||||
stats: bool,
|
||||
},
|
||||
ForgetMe { client: bool, stats: bool },
|
||||
}
|
||||
|
||||
impl ClientRequest {
|
||||
pub fn encrypt<S: SymmetricKey>(
|
||||
pub fn encrypt(
|
||||
&self,
|
||||
key: &S,
|
||||
key: &SharedSymmetricKey,
|
||||
) -> Result<ClientControlRequest, GatewayRequestsError> {
|
||||
// we're using json representation for few reasons:
|
||||
// - ease of re-implementation in other languages (compared to for example bincode)
|
||||
@@ -43,17 +31,21 @@ impl ClientRequest {
|
||||
|
||||
// SAFETY: the trait has been derived correctly with no weird variants
|
||||
let plaintext = serde_json::to_vec(self).unwrap();
|
||||
let nonce = key.random_nonce_or_iv();
|
||||
let ciphertext = key.encrypt(&plaintext, Some(&nonce))?;
|
||||
Ok(ClientControlRequest::EncryptedRequest { ciphertext, nonce })
|
||||
let nonce = key.random_nonce();
|
||||
let ciphertext = key.encrypt(&plaintext, &nonce)?;
|
||||
Ok(ClientControlRequest::EncryptedRequest {
|
||||
ciphertext,
|
||||
nonce: nonce.to_vec(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn decrypt<S: SymmetricKey>(
|
||||
pub fn decrypt(
|
||||
ciphertext: &[u8],
|
||||
nonce: &[u8],
|
||||
key: &S,
|
||||
key: &SharedSymmetricKey,
|
||||
) -> Result<Self, GatewayRequestsError> {
|
||||
let plaintext = key.decrypt(ciphertext, Some(nonce))?;
|
||||
let nonce = SharedSymmetricKey::validate_aead_nonce(nonce)?;
|
||||
let plaintext = key.decrypt(ciphertext, &nonce)?;
|
||||
serde_json::from_slice(&plaintext)
|
||||
.map_err(|source| GatewayRequestsError::MalformedRequest { source })
|
||||
}
|
||||
@@ -64,35 +56,18 @@ 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>,
|
||||
#[serde(alias = "iv")]
|
||||
nonce: Vec<u8>,
|
||||
},
|
||||
ClaimFreeTestnetBandwidth,
|
||||
EncryptedRequest {
|
||||
@@ -101,41 +76,33 @@ 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,
|
||||
shared_key: &SharedSymmetricKey,
|
||||
identity_keys: &ed25519::KeyPair,
|
||||
) -> Result<Self, GatewayRequestsError> {
|
||||
// if we're using v2 authentication, we must announce at least that protocol version
|
||||
// (which also implicitly implies usage of AES256-GCM-SIV
|
||||
let protocol_version = AUTHENTICATE_V2_PROTOCOL_VERSION;
|
||||
|
||||
Ok(ClientControlRequest::AuthenticateV2(Box::new(
|
||||
@@ -165,26 +132,27 @@ impl ClientControlRequest {
|
||||
|
||||
pub fn new_enc_ecash_credential(
|
||||
credential: CredentialSpendingData,
|
||||
shared_key: &SharedGatewayKey,
|
||||
shared_key: &SharedSymmetricKey,
|
||||
) -> Result<Self, GatewayRequestsError> {
|
||||
let cred = CredentialSpendingRequest::new(credential);
|
||||
let serialized_credential = cred.to_bytes();
|
||||
|
||||
let nonce = shared_key.random_nonce_or_iv();
|
||||
let enc_credential = shared_key.encrypt(&serialized_credential, Some(&nonce))?;
|
||||
let nonce = shared_key.random_nonce();
|
||||
let enc_credential = shared_key.encrypt(&serialized_credential, &nonce)?;
|
||||
|
||||
Ok(ClientControlRequest::EcashCredential {
|
||||
enc_credential,
|
||||
iv: nonce,
|
||||
nonce: nonce.to_vec(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn try_from_enc_ecash_credential(
|
||||
enc_credential: Vec<u8>,
|
||||
shared_key: &SharedGatewayKey,
|
||||
iv: Vec<u8>,
|
||||
shared_key: &SharedSymmetricKey,
|
||||
nonce: Vec<u8>,
|
||||
) -> Result<CredentialSpendingRequest, GatewayRequestsError> {
|
||||
let credential_bytes = shared_key.decrypt(&enc_credential, Some(&iv))?;
|
||||
let nonce = SharedSymmetricKey::validate_aead_nonce(&nonce)?;
|
||||
let credential_bytes = shared_key.decrypt(&enc_credential, &nonce)?;
|
||||
CredentialSpendingRequest::try_from_bytes(credential_bytes.as_slice())
|
||||
.map_err(|_| GatewayRequestsError::MalformedEncryption)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::{GatewayRequestsError, SimpleGatewayRequestsError, SymmetricKey};
|
||||
use crate::{GatewayRequestsError, SharedSymmetricKey, SimpleGatewayRequestsError};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tungstenite::Message;
|
||||
|
||||
@@ -15,9 +15,9 @@ pub enum SensitiveServerResponse {
|
||||
}
|
||||
|
||||
impl SensitiveServerResponse {
|
||||
pub fn encrypt<S: SymmetricKey>(
|
||||
pub fn encrypt(
|
||||
&self,
|
||||
key: &S,
|
||||
key: &SharedSymmetricKey,
|
||||
) -> Result<ServerResponse, GatewayRequestsError> {
|
||||
// we're using json representation for few reasons:
|
||||
// - ease of re-implementation in other languages (compared to for example bincode)
|
||||
@@ -26,17 +26,21 @@ impl SensitiveServerResponse {
|
||||
|
||||
// SAFETY: the trait has been derived correctly with no weird variants
|
||||
let plaintext = serde_json::to_vec(self).unwrap();
|
||||
let nonce = key.random_nonce_or_iv();
|
||||
let ciphertext = key.encrypt(&plaintext, Some(&nonce))?;
|
||||
Ok(ServerResponse::EncryptedResponse { ciphertext, nonce })
|
||||
let nonce = key.random_nonce();
|
||||
let ciphertext = key.encrypt(&plaintext, &nonce)?;
|
||||
Ok(ServerResponse::EncryptedResponse {
|
||||
ciphertext,
|
||||
nonce: nonce.to_vec(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn decrypt<S: SymmetricKey>(
|
||||
pub fn decrypt(
|
||||
ciphertext: &[u8],
|
||||
nonce: &[u8],
|
||||
key: &S,
|
||||
key: &SharedSymmetricKey,
|
||||
) -> Result<Self, GatewayRequestsError> {
|
||||
let plaintext = key.decrypt(ciphertext, Some(nonce))?;
|
||||
let nonce = SharedSymmetricKey::validate_aead_nonce(nonce)?;
|
||||
let plaintext = key.decrypt(ciphertext, &nonce)?;
|
||||
serde_json::from_slice(&plaintext)
|
||||
.map_err(|source| GatewayRequestsError::MalformedRequest { source })
|
||||
}
|
||||
@@ -47,14 +51,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 {
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
|
||||
-- make aes256gcm column non-nullable and drop any clients that still use the legacy keys
|
||||
CREATE TABLE shared_keys_tmp
|
||||
(
|
||||
client_id INTEGER NOT NULL PRIMARY KEY REFERENCES clients (id),
|
||||
client_address_bs58 TEXT NOT NULL UNIQUE,
|
||||
derived_aes256_gcm_siv_key BLOB NOT NULL
|
||||
);
|
||||
|
||||
INSERT INTO shared_keys_tmp (client_id, client_address_bs58, derived_aes256_gcm_siv_key)
|
||||
SELECT client_id, client_address_bs58, derived_aes256_gcm_siv_key
|
||||
FROM shared_keys
|
||||
WHERE derived_aes256_gcm_siv_key IS NOT NULL;
|
||||
|
||||
DROP TABLE shared_keys;
|
||||
ALTER TABLE shared_keys_tmp
|
||||
RENAME TO shared_keys;
|
||||
@@ -8,7 +8,7 @@ use models::{
|
||||
VerifiedTicket, WireguardPeer,
|
||||
};
|
||||
use nym_credentials_interface::ClientTicket;
|
||||
use nym_gateway_requests::shared_key::SharedGatewayKey;
|
||||
use nym_gateway_requests::shared_key::SharedSymmetricKey;
|
||||
use nym_sphinx::DestinationAddressBytes;
|
||||
use shared_keys::SharedKeysManager;
|
||||
use sqlx::{
|
||||
@@ -152,7 +152,7 @@ impl GatewayStorage {
|
||||
pub async fn insert_shared_keys(
|
||||
&self,
|
||||
client_address: DestinationAddressBytes,
|
||||
shared_keys: &SharedGatewayKey,
|
||||
shared_keys: &SharedSymmetricKey,
|
||||
) -> Result<i64, GatewayStorageError> {
|
||||
let client_address_bs58 = client_address.as_base58_string();
|
||||
let client_id = match self
|
||||
@@ -171,8 +171,7 @@ impl GatewayStorage {
|
||||
.insert_shared_keys(
|
||||
client_id,
|
||||
client_address_bs58,
|
||||
shared_keys.aes128_ctr_hmac_bs58().as_deref(),
|
||||
shared_keys.aes256_gcm_siv().as_deref(),
|
||||
shared_keys.to_bytes().as_ref(),
|
||||
)
|
||||
.await?;
|
||||
Ok(client_id)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
use crate::error::GatewayStorageError;
|
||||
use nym_credentials_interface::{AvailableBandwidth, ClientTicket, CredentialSpendingData};
|
||||
use nym_gateway_requests::shared_key::{LegacySharedKeys, SharedGatewayKey, SharedSymmetricKey};
|
||||
use nym_gateway_requests::shared_key::SharedSymmetricKey;
|
||||
use sqlx::FromRow;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
@@ -18,33 +18,16 @@ pub struct PersistedSharedKeys {
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub client_address_bs58: String,
|
||||
pub derived_aes128_ctr_blake3_hmac_keys_bs58: Option<String>,
|
||||
pub derived_aes256_gcm_siv_key: Option<Vec<u8>>,
|
||||
pub derived_aes256_gcm_siv_key: Vec<u8>,
|
||||
pub last_used_authentication: Option<OffsetDateTime>,
|
||||
}
|
||||
|
||||
impl TryFrom<PersistedSharedKeys> for SharedGatewayKey {
|
||||
impl TryFrom<PersistedSharedKeys> for SharedSymmetricKey {
|
||||
type Error = GatewayStorageError;
|
||||
|
||||
fn try_from(value: PersistedSharedKeys) -> Result<Self, Self::Error> {
|
||||
match (
|
||||
&value.derived_aes256_gcm_siv_key,
|
||||
&value.derived_aes128_ctr_blake3_hmac_keys_bs58,
|
||||
) {
|
||||
(None, None) => Err(GatewayStorageError::MissingSharedKey {
|
||||
id: value.client_id,
|
||||
}),
|
||||
(Some(aes256gcm_siv), _) => {
|
||||
let current_key = SharedSymmetricKey::try_from_bytes(aes256gcm_siv)
|
||||
.map_err(|source| GatewayStorageError::DataCorruption(source.to_string()))?;
|
||||
Ok(SharedGatewayKey::Current(current_key))
|
||||
}
|
||||
(None, Some(aes128ctr_hmac)) => {
|
||||
let legacy_key = LegacySharedKeys::try_from_base58_string(aes128ctr_hmac)
|
||||
.map_err(|source| GatewayStorageError::DataCorruption(source.to_string()))?;
|
||||
Ok(SharedGatewayKey::Legacy(legacy_key))
|
||||
}
|
||||
}
|
||||
SharedSymmetricKey::try_from_bytes(&value.derived_aes256_gcm_siv_key)
|
||||
.map_err(|source| GatewayStorageError::DataCorruption(source.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,31 +37,27 @@ impl SharedKeysManager {
|
||||
///
|
||||
/// * `client_id`: The client id for which the shared keys are stored
|
||||
/// * `client_address_bs58`: base58-encoded address of the client
|
||||
/// * `derived_aes128_ctr_blake3_hmac_keys_bs58`: shared encryption (AES128CTR) and mac (hmac-blake3) derived shared keys to store.
|
||||
/// * `derived_aes256_gcm_siv_key`: shared encryption (AES256GCM_SIV) derived shared keys to store.
|
||||
pub(crate) async fn insert_shared_keys(
|
||||
&self,
|
||||
client_id: i64,
|
||||
client_address_bs58: String,
|
||||
derived_aes128_ctr_blake3_hmac_keys_bs58: Option<&String>,
|
||||
derived_aes256_gcm_siv_key: Option<&Vec<u8>>,
|
||||
derived_aes256_gcm_siv_key: &Vec<u8>,
|
||||
) -> Result<(), sqlx::Error> {
|
||||
// https://stackoverflow.com/a/20310838
|
||||
// we don't want to be using `INSERT OR REPLACE INTO` due to the foreign key on `available_bandwidth` if the entry already exists
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT OR IGNORE INTO shared_keys(client_id, client_address_bs58, derived_aes128_ctr_blake3_hmac_keys_bs58, derived_aes256_gcm_siv_key) VALUES (?, ?, ?, ?);
|
||||
INSERT OR IGNORE INTO shared_keys(client_id, client_address_bs58, derived_aes256_gcm_siv_key) VALUES (?, ?, ?);
|
||||
|
||||
UPDATE shared_keys
|
||||
SET
|
||||
derived_aes128_ctr_blake3_hmac_keys_bs58 = ?,
|
||||
derived_aes256_gcm_siv_key = ?
|
||||
WHERE client_address_bs58 = ?
|
||||
"#,
|
||||
client_id,
|
||||
client_address_bs58,
|
||||
derived_aes128_ctr_blake3_hmac_keys_bs58,
|
||||
derived_aes256_gcm_siv_key,
|
||||
derived_aes128_ctr_blake3_hmac_keys_bs58,
|
||||
derived_aes256_gcm_siv_key,
|
||||
client_address_bs58,
|
||||
).execute(&self.connection_pool).await?;
|
||||
|
||||
@@ -44,11 +44,6 @@ pub type GatewaySharedKeyHkdfAlgorithm = blake3::Hasher;
|
||||
/// Hashing algorithm used when computing digest of a reply SURB encryption key.
|
||||
pub type ReplySurbKeyDigestAlgorithm = blake3::Hasher;
|
||||
|
||||
/// Hashing algorithm used when computing integrity (H)Mac for message exchanged between client and gateway.
|
||||
// TODO: if updated, the pem type defined in gateway\gateway-requests\src\registration\handshake\legacy_shared_key
|
||||
// needs updating!
|
||||
pub type GatewayIntegrityHmacAlgorithm = blake3::Hasher;
|
||||
|
||||
/// Encryption algorithm used for encrypting acknowledgement messages.
|
||||
// TODO: if updated:
|
||||
// - PacketSize::ACK_PACKET_SIZE needs to be manually updated (if nonce/iv size differs);
|
||||
@@ -56,12 +51,6 @@ pub type GatewayIntegrityHmacAlgorithm = blake3::Hasher;
|
||||
// - the pem type defined in nym\common\nymsphinx\acknowledgements\src\key needs updating!
|
||||
pub type AckEncryptionAlgorithm = Aes128Ctr;
|
||||
|
||||
/// Legacy encryption algorithm used for end-to-end encryption of messages exchanged between clients
|
||||
/// and their gateways.
|
||||
// TODO: if updated, the pem type defined in gateway\gateway-requests\src\registration\handshake\legacy_shared_key
|
||||
// needs updating!
|
||||
pub type LegacyGatewayEncryptionAlgorithm = Aes128Ctr;
|
||||
|
||||
/// Encryption algorithm used for end-to-end encryption of messages exchanged between clients
|
||||
/// and their gateways.
|
||||
// NOTE: if updated, the pem type defined in gateway\gateway-requests\src\registration\handshake\shared_key
|
||||
|
||||
@@ -30,7 +30,6 @@ pub use sphinx_packet::{
|
||||
route::{Destination, DestinationAddressBytes, Node, NodeAddressBytes, SURBIdentifier},
|
||||
surb::{SURBMaterial, SURB},
|
||||
version::Version,
|
||||
version::UPDATED_LEGACY_VERSION,
|
||||
Error as SphinxError, ProcessedPacket, ProcessedPacketData,
|
||||
};
|
||||
|
||||
@@ -91,12 +90,8 @@ impl NymPacket {
|
||||
destination: &Destination,
|
||||
delays: &[Delay],
|
||||
) -> Result<NymPacket, NymPacketError> {
|
||||
// FIXME:
|
||||
// for now explicitly use the legacy version until sufficient number of nodes
|
||||
// understand both variants
|
||||
Ok(NymPacket::Sphinx(
|
||||
SphinxPacketBuilder::new()
|
||||
.with_version(UPDATED_LEGACY_VERSION)
|
||||
.with_payload_size(size)
|
||||
.build_packet(message, route, destination, delays)?,
|
||||
))
|
||||
|
||||
@@ -15,8 +15,6 @@ use nym_client_core::client::key_manager::persistence::KeyStore;
|
||||
use nym_client_core::client::key_manager::ClientKeys;
|
||||
use nym_client_core::client::replies::reply_storage::browser_backend;
|
||||
use nym_credential_storage::ephemeral_storage::EphemeralStorage as EphemeralCredentialStorage;
|
||||
use nym_crypto::asymmetric::ed25519::PublicKey;
|
||||
use nym_gateway_client::SharedSymmetricKey;
|
||||
use wasm_utils::console_log;
|
||||
|
||||
// temporary until other variants are properly implemented (probably it should get changed into `ClientStorage`
|
||||
@@ -158,19 +156,6 @@ impl GatewaysDetailsStore for ClientStorage {
|
||||
self.store_registered_gateway(&raw_registration).await
|
||||
}
|
||||
|
||||
async fn upgrade_stored_remote_gateway_key(
|
||||
&self,
|
||||
gateway_id: PublicKey,
|
||||
updated_key: &SharedSymmetricKey,
|
||||
) -> Result<(), Self::StorageError> {
|
||||
self.update_remote_gateway_key(
|
||||
&gateway_id.to_base58_string(),
|
||||
None,
|
||||
Some(updated_key.as_bytes()),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn remove_gateway_details(&self, gateway_id: &str) -> Result<(), Self::StorageError> {
|
||||
self.remove_registered_gateway(gateway_id).await
|
||||
}
|
||||
|
||||
@@ -4,9 +4,7 @@
|
||||
use nym_client_core::client::base_client::storage::gateways_storage::{
|
||||
BadGateway, GatewayDetails, GatewayRegistration, RawRemoteGatewayDetails, RemoteGatewayDetails,
|
||||
};
|
||||
use nym_gateway_client::SharedGatewayKey;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::ops::Deref;
|
||||
use time::OffsetDateTime;
|
||||
use zeroize::Zeroize;
|
||||
|
||||
@@ -18,10 +16,8 @@ pub struct WasmRawRegisteredGateway {
|
||||
#[zeroize(skip)]
|
||||
pub registration_timestamp: OffsetDateTime,
|
||||
|
||||
pub derived_aes128_ctr_blake3_hmac_keys_bs58: Option<String>,
|
||||
|
||||
#[serde(default)]
|
||||
pub derived_aes256_gcm_siv_key: Option<Vec<u8>>,
|
||||
pub derived_aes256_gcm_siv_key: Vec<u8>,
|
||||
|
||||
pub gateway_owner_address: Option<String>,
|
||||
|
||||
@@ -35,8 +31,6 @@ impl TryFrom<WasmRawRegisteredGateway> for GatewayRegistration {
|
||||
// offload some parsing to an existing impl
|
||||
let raw_remote = RawRemoteGatewayDetails {
|
||||
gateway_id_bs58: value.gateway_id_bs58,
|
||||
derived_aes128_ctr_blake3_hmac_keys_bs58: value
|
||||
.derived_aes128_ctr_blake3_hmac_keys_bs58,
|
||||
derived_aes256_gcm_siv_key: value.derived_aes256_gcm_siv_key,
|
||||
gateway_owner_address: value.gateway_owner_address,
|
||||
gateway_listener: value.gateway_listener,
|
||||
@@ -56,16 +50,11 @@ impl<'a> From<&'a GatewayRegistration> for WasmRawRegisteredGateway {
|
||||
panic!("somehow obtained custom gateway registration in wasm!")
|
||||
};
|
||||
|
||||
let (derived_aes128_ctr_blake3_hmac_keys_bs58, derived_aes256_gcm_siv_key) =
|
||||
match remote_details.shared_key.deref() {
|
||||
SharedGatewayKey::Current(key) => (None, Some(key.to_bytes())),
|
||||
SharedGatewayKey::Legacy(key) => (Some(key.to_base58_string()), None),
|
||||
};
|
||||
let derived_aes256_gcm_siv_key = remote_details.shared_key.to_bytes().to_vec();
|
||||
|
||||
WasmRawRegisteredGateway {
|
||||
gateway_id_bs58: remote_details.gateway_id.to_string(),
|
||||
registration_timestamp: value.registration_timestamp,
|
||||
derived_aes128_ctr_blake3_hmac_keys_bs58,
|
||||
derived_aes256_gcm_siv_key,
|
||||
gateway_listener: remote_details.gateway_listener.to_string(),
|
||||
gateway_owner_address: remote_details
|
||||
|
||||
@@ -10,7 +10,6 @@ use std::error::Error;
|
||||
use thiserror::Error;
|
||||
use wasm_bindgen::JsValue;
|
||||
use wasm_storage::traits::BaseWasmStorage;
|
||||
use zeroize::Zeroize;
|
||||
|
||||
// v1 tables
|
||||
pub(crate) mod v1 {
|
||||
@@ -238,23 +237,6 @@ pub trait WasmClientStorage: BaseWasmStorage {
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn update_remote_gateway_key(
|
||||
&self,
|
||||
gateway_id_bs58: &str,
|
||||
derived_aes128_ctr_blake3_hmac_keys_bs58: Option<&str>,
|
||||
derived_aes256_gcm_siv_key: Option<&[u8]>,
|
||||
) -> Result<(), <Self as WasmClientStorage>::StorageError> {
|
||||
if let Some(mut current) = self.maybe_get_registered_gateway(gateway_id_bs58).await? {
|
||||
current.derived_aes128_ctr_blake3_hmac_keys_bs58 =
|
||||
derived_aes128_ctr_blake3_hmac_keys_bs58.map(|k| k.to_string());
|
||||
current.derived_aes256_gcm_siv_key = derived_aes256_gcm_siv_key.map(|k| k.to_vec());
|
||||
self.store_registered_gateway(¤t).await?;
|
||||
current.zeroize();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn remove_registered_gateway(
|
||||
&self,
|
||||
gateway_id: &str,
|
||||
|
||||
@@ -349,35 +349,6 @@ impl<R, S> AuthenticatedHandler<R, S> {
|
||||
Ok(SensitiveServerResponse::ForgetMeAck {}.encrypt(&self.client.shared_keys)?)
|
||||
}
|
||||
|
||||
async fn handle_key_upgrade(
|
||||
&mut self,
|
||||
hkdf_salt: Vec<u8>,
|
||||
client_key_digest: Vec<u8>,
|
||||
) -> Result<ServerResponse, RequestHandlingError> {
|
||||
if !self.client.shared_keys.is_legacy() {
|
||||
return Ok(ServerResponse::new_error(
|
||||
"the connection is already using an aes256-gcm-siv key",
|
||||
));
|
||||
}
|
||||
let legacy_key = self.client.shared_keys.unwrap_legacy();
|
||||
let Some(upgraded_key) = legacy_key.upgrade_verify(&hkdf_salt, &client_key_digest) else {
|
||||
return Ok(ServerResponse::new_error(
|
||||
"failed to derive matching aes256-gcm-siv key",
|
||||
));
|
||||
};
|
||||
|
||||
let updated_key = upgraded_key.into();
|
||||
self.inner
|
||||
.shared_state
|
||||
.storage
|
||||
.insert_shared_keys(self.client.address, &updated_key)
|
||||
.await?;
|
||||
|
||||
// swap the in-memory key
|
||||
self.client.shared_keys = updated_key;
|
||||
Ok(SensitiveServerResponse::KeyUpgradeAck {}.encrypt(&self.client.shared_keys)?)
|
||||
}
|
||||
|
||||
async fn handle_encrypted_text_request(
|
||||
&mut self,
|
||||
ciphertext: Vec<u8>,
|
||||
@@ -388,10 +359,6 @@ impl<R, S> AuthenticatedHandler<R, S> {
|
||||
};
|
||||
|
||||
match req {
|
||||
ClientRequest::UpgradeKey {
|
||||
hkdf_salt,
|
||||
derived_key_digest,
|
||||
} => self.handle_key_upgrade(hkdf_salt, derived_key_digest).await,
|
||||
ClientRequest::ForgetMe { client, stats } => self.handle_forget_me(client, stats).await,
|
||||
_ => Err(RequestHandlingError::UnknownEncryptedTextRequest),
|
||||
}
|
||||
@@ -425,8 +392,14 @@ impl<R, S> AuthenticatedHandler<R, S> {
|
||||
ClientControlRequest::EncryptedRequest { ciphertext, nonce } => {
|
||||
self.handle_encrypted_text_request(ciphertext, nonce).await
|
||||
}
|
||||
ClientControlRequest::EcashCredential { enc_credential, iv } => {
|
||||
self.handle_ecash_bandwidth(enc_credential, iv).await
|
||||
ClientControlRequest::EcashCredential {
|
||||
enc_credential,
|
||||
nonce,
|
||||
} => self.handle_ecash_bandwidth(enc_credential, nonce).await,
|
||||
ClientControlRequest::Authenticate { .. } => {
|
||||
Err(RequestHandlingError::IllegalRequest {
|
||||
additional_context: "authentication v1 is no longer supported".into(),
|
||||
})
|
||||
}
|
||||
ClientControlRequest::BandwidthCredential { .. } => {
|
||||
Err(RequestHandlingError::IllegalRequest {
|
||||
@@ -446,7 +419,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, SharedKeyUsageError,
|
||||
SharedSymmetricKey, CURRENT_PROTOCOL_VERSION,
|
||||
};
|
||||
use nym_gateway_storage::error::GatewayStorageError;
|
||||
use nym_node_metrics::events::MetricsEvent;
|
||||
@@ -45,9 +42,15 @@ 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,
|
||||
|
||||
#[error(transparent)]
|
||||
KeyUsageFailure(#[from] SharedKeyUsageError),
|
||||
|
||||
#[error("Internal gateway storage error")]
|
||||
StorageError(#[from] GatewayStorageError),
|
||||
|
||||
@@ -63,19 +66,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 +79,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 {
|
||||
@@ -183,7 +176,7 @@ impl<R, S> FreshHandler<R, S> {
|
||||
async fn perform_registration_handshake(
|
||||
&mut self,
|
||||
init_msg: Vec<u8>,
|
||||
) -> Result<SharedGatewayKey, HandshakeError>
|
||||
) -> Result<SharedSymmetricKey, HandshakeError>
|
||||
where
|
||||
S: AsyncRead + AsyncWrite + Unpin + Send,
|
||||
R: CryptoRng + RngCore + Send,
|
||||
@@ -265,7 +258,7 @@ impl<R, S> FreshHandler<R, S> {
|
||||
/// * `packets`: unwrapped packets that are to be pushed back to the client.
|
||||
pub(crate) async fn push_packets_to_client(
|
||||
&mut self,
|
||||
shared_keys: &SharedGatewayKey,
|
||||
shared_keys: &SharedSymmetricKey,
|
||||
packets: Vec<Vec<u8>>,
|
||||
) -> Result<(), WsError>
|
||||
where
|
||||
@@ -323,7 +316,7 @@ impl<R, S> FreshHandler<R, S> {
|
||||
async fn push_stored_messages_to_client(
|
||||
&mut self,
|
||||
client_address: DestinationAddressBytes,
|
||||
shared_keys: &SharedGatewayKey,
|
||||
shared_keys: &SharedSymmetricKey,
|
||||
) -> Result<(), InitialAuthenticationError>
|
||||
where
|
||||
S: AsyncRead + AsyncWrite + Unpin,
|
||||
@@ -377,67 +370,32 @@ 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);
|
||||
let incompatible_err = InitialAuthenticationError::IncompatibleProtocol {
|
||||
client: client_protocol,
|
||||
current: 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);
|
||||
}
|
||||
debug!("client protocol: {client_protocol}, ours: {CURRENT_PROTOCOL_VERSION}");
|
||||
|
||||
// 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 or aes256gcm
|
||||
if !client_protocol.supports_authenticate_v2() || !client_protocol.supports_aes256_gcm_siv()
|
||||
{
|
||||
error!("{incompatible_err}");
|
||||
return Err(incompatible_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 {
|
||||
let err = InitialAuthenticationError::IncompatibleProtocol {
|
||||
client: client_protocol,
|
||||
current: CURRENT_PROTOCOL_VERSION,
|
||||
};
|
||||
error!("{err}");
|
||||
Err(err)
|
||||
error!("{incompatible_err}");
|
||||
Err(incompatible_err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -528,96 +486,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 +496,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 +565,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,
|
||||
},
|
||||
@@ -715,7 +583,7 @@ impl<R, S> FreshHandler<R, S> {
|
||||
async fn register_client(
|
||||
&mut self,
|
||||
client_address: DestinationAddressBytes,
|
||||
client_shared_keys: &SharedGatewayKey,
|
||||
client_shared_keys: &SharedSymmetricKey,
|
||||
) -> Result<i64, InitialAuthenticationError>
|
||||
where
|
||||
S: AsyncRead + AsyncWrite + Unpin,
|
||||
@@ -759,7 +627,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 +666,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 +701,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 {
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::node::client_handling::websocket::connection_handler::fresh::InitialAuthenticationError;
|
||||
use nym_gateway_requests::SharedGatewayKey;
|
||||
use nym_gateway_requests::SharedSymmetricKey;
|
||||
use nym_gateway_storage::models::PersistedSharedKeys;
|
||||
use nym_sphinx::DestinationAddressBytes;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
pub(crate) struct KeyWithAuthTimestamp {
|
||||
pub(crate) client_id: i64,
|
||||
pub(crate) key: SharedGatewayKey,
|
||||
pub(crate) key: SharedSymmetricKey,
|
||||
pub(crate) last_used_authentication: Option<OffsetDateTime>,
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ impl KeyWithAuthTimestamp {
|
||||
let last_used_authentication = stored_shared_keys.last_used_authentication;
|
||||
let client_id = stored_shared_keys.client_id;
|
||||
|
||||
let key = SharedGatewayKey::try_from(stored_shared_keys).map_err(|source| {
|
||||
let key = SharedSymmetricKey::try_from(stored_shared_keys).map_err(|source| {
|
||||
InitialAuthenticationError::MalformedStoredSharedKey {
|
||||
client_id: client.as_base58_string(),
|
||||
source,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
use crate::config::Config;
|
||||
use nym_credential_verification::BandwidthFlushingBehaviourConfig;
|
||||
use nym_gateway_requests::shared_key::SharedGatewayKey;
|
||||
use nym_gateway_requests::shared_key::SharedSymmetricKey;
|
||||
use nym_gateway_requests::ServerResponse;
|
||||
use nym_sphinx::DestinationAddressBytes;
|
||||
use rand::{CryptoRng, Rng};
|
||||
@@ -44,7 +44,7 @@ impl<S> SocketStream<S> {
|
||||
pub(crate) struct ClientDetails {
|
||||
pub(crate) address: DestinationAddressBytes,
|
||||
pub(crate) id: i64,
|
||||
pub(crate) shared_keys: SharedGatewayKey,
|
||||
pub(crate) shared_keys: SharedSymmetricKey,
|
||||
// note, this does **NOT ALWAYS** indicate timestamp of when client connected
|
||||
// it is (for v2 auth) timestamp the client **signed** when it created the request
|
||||
pub(crate) session_request_timestamp: OffsetDateTime,
|
||||
@@ -54,7 +54,7 @@ impl ClientDetails {
|
||||
pub(crate) fn new(
|
||||
id: i64,
|
||||
address: DestinationAddressBytes,
|
||||
shared_keys: SharedGatewayKey,
|
||||
shared_keys: SharedSymmetricKey,
|
||||
session_request_timestamp: OffsetDateTime,
|
||||
) -> Self {
|
||||
ClientDetails {
|
||||
@@ -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
|
||||
|
||||
@@ -17,6 +17,7 @@ use time::Date;
|
||||
use tracing::trace;
|
||||
use utoipa::{IntoParams, ToSchema};
|
||||
|
||||
#[allow(deprecated)]
|
||||
pub(crate) fn issued_routes() -> Router<AppState> {
|
||||
Router::new()
|
||||
.route(
|
||||
@@ -76,6 +77,7 @@ async fn issued_ticketbooks_for(
|
||||
(status = 400, body = String, description = "this nym-api is not an ecash signer in the current epoch"),
|
||||
)
|
||||
)]
|
||||
#[deprecated]
|
||||
async fn issued_ticketbooks_challenge(
|
||||
State(state): State<Arc<EcashState>>,
|
||||
Json(challenge): Json<IssuedTicketbooksChallengeRequest>,
|
||||
|
||||
@@ -17,7 +17,7 @@ use nym_gateway_client::client::config::GatewayClientConfig;
|
||||
use nym_gateway_client::client::GatewayConfig;
|
||||
use nym_gateway_client::error::GatewayClientError;
|
||||
use nym_gateway_client::{
|
||||
AcknowledgementReceiver, GatewayClient, MixnetMessageReceiver, PacketRouter, SharedGatewayKey,
|
||||
AcknowledgementReceiver, GatewayClient, MixnetMessageReceiver, PacketRouter, SharedSymmetricKey,
|
||||
};
|
||||
use nym_sphinx::forwarding::packet::MixPacket;
|
||||
use pin_project::pin_project;
|
||||
@@ -94,7 +94,7 @@ struct FreshGatewayClientData {
|
||||
gateway_response_timeout: Duration,
|
||||
bandwidth_controller: BandwidthController<nyxd::Client, PersistentStorage>,
|
||||
disabled_credentials_mode: bool,
|
||||
gateways_key_cache: DashMap<ed25519::PublicKey, Arc<SharedGatewayKey>>,
|
||||
gateways_key_cache: DashMap<ed25519::PublicKey, Arc<SharedSymmetricKey>>,
|
||||
}
|
||||
|
||||
impl FreshGatewayClientData {
|
||||
@@ -267,7 +267,7 @@ impl PacketSender {
|
||||
connection_timeout: Duration,
|
||||
bandwidth_claim_timeout: Duration,
|
||||
client: &mut GatewayClientHandle,
|
||||
) -> Option<Arc<SharedGatewayKey>> {
|
||||
) -> Option<Arc<SharedSymmetricKey>> {
|
||||
let gateway_identity = client.gateway_identity();
|
||||
|
||||
// 1. attempt to authenticate
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
use nym_crypto::asymmetric::ed25519::PublicKey;
|
||||
use nym_gateway_requests::SharedSymmetricKey;
|
||||
use nym_sdk::mixnet::{
|
||||
self, ActiveGateway, BadGateway, ClientKeys, EmptyReplyStorage, EphemeralCredentialStorage,
|
||||
GatewayRegistration, GatewaysDetailsStore, KeyStore, MixnetClientStorage, MixnetMessageSender,
|
||||
@@ -166,16 +164,6 @@ impl GatewaysDetailsStore for MockGatewayDetailsStore {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn upgrade_stored_remote_gateway_key(
|
||||
&self,
|
||||
gateway_id: PublicKey,
|
||||
_updated_key: &SharedSymmetricKey,
|
||||
) -> Result<(), Self::StorageError> {
|
||||
println!("upgrading gateway key for {gateway_id}");
|
||||
|
||||
Err(MyError)
|
||||
}
|
||||
|
||||
async fn remove_gateway_details(&self, _gateway_id: &str) -> Result<(), Self::StorageError> {
|
||||
println!("removing gateway details");
|
||||
|
||||
|
||||
@@ -27,7 +27,6 @@ use tsify::Tsify;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen_futures::future_to_promise;
|
||||
use wasm_client_core::client::base_client::storage::gateways_storage::GatewayDetails;
|
||||
use wasm_client_core::client::base_client::storage::GatewaysDetailsStore;
|
||||
use wasm_client_core::client::mix_traffic::transceiver::PacketRouter;
|
||||
use wasm_client_core::helpers::{
|
||||
current_network_topology_async, setup_from_topology, EphemeralCredentialStorage,
|
||||
@@ -219,13 +218,7 @@ impl NymNodeTesterBuilder {
|
||||
)
|
||||
};
|
||||
|
||||
let auth_res = gateway_client.perform_initial_authentication().await?;
|
||||
if auth_res.requires_key_upgrade {
|
||||
let updated_key = gateway_client.upgrade_key_authenticated().await?;
|
||||
client_store
|
||||
.upgrade_stored_remote_gateway_key(gateway_identity, &updated_key)
|
||||
.await?;
|
||||
}
|
||||
let _ = gateway_client.perform_initial_authentication().await?;
|
||||
gateway_client.claim_initial_bandwidth().await?;
|
||||
gateway_client.start_listening_for_mixnet_messages()?;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user