diff --git a/common/client-core/gateways-storage/fs_gateways_migrations/20250227120000_remove_aes128_ctr_key.sql b/common/client-core/gateways-storage/fs_gateways_migrations/20250227120000_remove_aes128_ctr_key.sql new file mode 100644 index 00000000000..dac94817820 --- /dev/null +++ b/common/client-core/gateways-storage/fs_gateways_migrations/20250227120000_remove_aes128_ctr_key.sql @@ -0,0 +1,24 @@ +/* + * Copyright 2025 - Nym Technologies SA + * 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; \ No newline at end of file diff --git a/common/client-core/gateways-storage/src/backend/fs_backend/manager.rs b/common/client-core/gateways-storage/src/backend/fs_backend/manager.rs index d2d19431014..512427ba88a 100644 --- a/common/client-core/gateways-storage/src/backend/fs_backend/manager.rs +++ b/common/client-core/gateways-storage/src/backend/fs_backend/manager.rs @@ -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, + 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, diff --git a/common/client-core/gateways-storage/src/backend/fs_backend/mod.rs b/common/client-core/gateways-storage/src/backend/fs_backend/mod.rs index db89a3b83fa..d99407c117a 100644 --- a/common/client-core/gateways-storage/src/backend/fs_backend/mod.rs +++ b/common/client-core/gateways-storage/src/backend/fs_backend/mod.rs @@ -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> { diff --git a/common/client-core/gateways-storage/src/backend/mem_backend.rs b/common/client-core/gateways-storage/src/backend/mem_backend.rs index 48235aaf1ff..d099eee0941 100644 --- a/common/client-core/gateways-storage/src/backend/mem_backend.rs +++ b/common/client-core/gateways-storage/src/backend/mem_backend.rs @@ -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() { diff --git a/common/client-core/gateways-storage/src/lib.rs b/common/client-core/gateways-storage/src/lib.rs index 1351059afd3..2ed51a96f9c 100644 --- a/common/client-core/gateways-storage/src/lib.rs +++ b/common/client-core/gateways-storage/src/lib.rs @@ -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>; } diff --git a/common/client-core/gateways-storage/src/types.rs b/common/client-core/gateways-storage/src/types.rs index 4f5f17843d4..8a989d0597c 100644 --- a/common/client-core/gateways-storage/src/types.rs +++ b/common/client-core/gateways-storage/src/types.rs @@ -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 for GatewayRegistration { impl GatewayDetails { pub fn new_remote( gateway_id: identity::PublicKey, - shared_key: Arc, + shared_key: Arc, gateway_owner_address: Option, 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, - pub derived_aes256_gcm_siv_key: Option>, + pub derived_aes256_gcm_siv_key: Vec, pub gateway_owner_address: Option, pub gateway_listener: String, } @@ -186,35 +184,11 @@ impl TryFrom 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 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, + pub shared_key: Arc, pub gateway_owner_address: Option, diff --git a/common/client-core/src/client/base_client/mod.rs b/common/client-core/src/client/base_client/mod.rs index 38c976097f7..9504f5cf90e 100644 --- a/common/client-core/src/client/base_client/mod.rs +++ b/common/client-core/src/client/base_client/mod.rs @@ -394,7 +394,6 @@ where config: &Config, initialisation_result: InitialisationResult, bandwidth_controller: Option>, - details_store: &S::GatewaysDetailsStore, packet_router: PacketRouter, stats_reporter: ClientStatsSender, #[cfg(unix)] connection_fd_callback: Option>, @@ -403,7 +402,6 @@ where where ::StorageError: Send + Sync + 'static, ::StorageError: Send + Sync + 'static, - ::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>, - details_store: &S::GatewaysDetailsStore, packet_router: PacketRouter, stats_reporter: ClientStatsSender, #[cfg(unix)] connection_fd_callback: Option>, @@ -510,7 +489,6 @@ where where ::StorageError: Send + Sync + 'static, ::StorageError: Send + Sync + 'static, - ::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)] diff --git a/common/client-core/src/client/base_client/storage/migration_helpers.rs b/common/client-core/src/client/base_client/storage/migration_helpers.rs index 3ac62132f0f..e1539bf8254 100644 --- a/common/client-core/src/client/base_client/storage/migration_helpers.rs +++ b/common/client-core/src/client/base_client/storage/migration_helpers.rs @@ -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>(path: P) -> Result { - // 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 { - 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 { - 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, + _old_storage_paths: &CommonClientPathsV1_1_33, + _new_storage_paths: &CommonClientPaths, + _preloaded_config: Option, ) -> 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(), + )) } } diff --git a/common/client-core/src/client/key_manager/mod.rs b/common/client-core/src/client/key_manager/mod.rs index d67ff80a3e9..5df321830b9 100644 --- a/common/client-core/src/client/key_manager/mod.rs +++ b/common/client-core/src/client/key_manager/mod.rs @@ -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::(); _assert_zeroize_on_drop::(); _assert_zeroize_on_drop::(); - _assert_zeroize_on_drop::(); _assert_zeroize_on_drop::(); - _assert_zeroize_on_drop::(); } diff --git a/common/client-core/src/error.rs b/common/client-core/src/error.rs index 76add9d71d6..fa8c0ccecf0 100644 --- a/common/client-core/src/error.rs +++ b/common/client-core/src/error.rs @@ -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), diff --git a/common/client-core/src/init/helpers.rs b/common/client-core/src/init/helpers.rs index f29983bbf7e..d32512540c1 100644 --- a/common/client-core/src/init/helpers.rs +++ b/common/client-core/src/init/helpers.rs @@ -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, diff --git a/common/client-core/src/init/types.rs b/common/client-core/src/init/types.rs index 13830225612..eec5a207c63 100644 --- a/common/client-core/src/init/types.rs +++ b/common/client-core/src/init/types.rs @@ -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, + pub shared_keys: Arc, pub authenticated_ephemeral_client: InitGatewayClient, } diff --git a/common/client-libs/gateway-client/src/client/mod.rs b/common/client-libs/gateway-client/src/client/mod.rs index 4a3ebc60659..0374b6e8a62 100644 --- a/common/client-libs/gateway-client/src/client/mod.rs +++ b/common/client-libs/gateway-client/src/client/mod.rs @@ -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, - pub requires_key_upgrade: bool, + pub initial_shared_key: Arc, } // TODO: this should be refactored into a state machine that keeps track of its authentication state @@ -95,7 +92,7 @@ pub struct GatewayClient { gateway_address: String, gateway_identity: identity::PublicKey, local_identity: Arc, - shared_key: Option>, + shared_key: Option>, connection: SocketState, packet_router: PacketRouter, bandwidth_controller: Option>, @@ -119,7 +116,7 @@ impl GatewayClient { gateway_config: GatewayConfig, local_identity: Arc, // TODO: make it mandatory. if you don't want to pass it, use `new_init` - shared_key: Option>, + shared_key: Option>, packet_router: PacketRouter, bandwidth_controller: Option>, stats_reporter: ClientStatsSender, @@ -149,7 +146,7 @@ impl GatewayClient { self.gateway_identity } - pub fn shared_key(&self) -> Option> { + pub fn shared_key(&self) -> Option> { self.shared_key.clone() } @@ -271,7 +268,7 @@ impl GatewayClient { 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 { @@ -413,8 +410,10 @@ impl GatewayClient { fn check_gateway_protocol(&self, gateway_protocol: u8) -> Result<(), GatewayClientError> { debug!("gateway protocol: {gateway_protocol:?}, ours: {CURRENT_PROTOCOL_VERSION}"); - // client should reject any gateways that do not indicate they support auth v2 - if !gateway_protocol.supports_authenticate_v2() { + // 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, @@ -435,20 +434,12 @@ impl GatewayClient { } } - 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; @@ -460,7 +451,6 @@ impl GatewayClient { 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(), ) @@ -493,72 +483,6 @@ impl GatewayClient { Ok(()) } - pub async fn upgrade_key_authenticated( - &mut self, - ) -> Result, 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, @@ -622,18 +546,20 @@ impl GatewayClient { // 1. check gateway's protocol version // if we failed to get this request resolved, it means the gateway is on an old version - // that definitely does not support auth v2, so we bail + // 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_aes_gcm_siv { + error!("this gateway is on an old version that doesn't support authentication v2"); } - if !supports_auth_v2 { - warn!("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, @@ -646,7 +572,6 @@ impl GatewayClient { 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) @@ -660,17 +585,14 @@ impl GatewayClient { // 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(); @@ -679,7 +601,6 @@ impl GatewayClient { // so no upgrades are required Ok(AuthenticationResponse { initial_shared_key: Arc::clone(shared_key), - requires_key_upgrade: false, }) } } diff --git a/common/client-libs/gateway-client/src/lib.rs b/common/client-libs/gateway-client/src/lib.rs index e8d992027a5..e4cf9bfa1ec 100644 --- a/common/client-libs/gateway-client/src/lib.rs +++ b/common/client-libs/gateway-client/src/lib.rs @@ -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, - shared_keys: &SharedGatewayKey, + shared_keys: &SharedSymmetricKey, ) -> Option> { match BinaryResponse::try_from_encrypted_tagged_bytes(bin_msg, shared_keys) { Ok(bin_response) => match bin_response { diff --git a/common/client-libs/gateway-client/src/socket_state.rs b/common/client-libs/gateway-client/src/socket_state.rs index 319a7bb624d..49e4f32a381 100644 --- a/common/client-libs/gateway-client/src/socket_state.rs +++ b/common/client-libs/gateway-client/src/socket_state.rs @@ -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, + shared_key: Arc, client_bandwidth: ClientBandwidth, stream_return: SplitStreamSender, @@ -73,7 +73,7 @@ struct PartiallyDelegatedRouter { impl PartiallyDelegatedRouter { fn new( packet_router: PacketRouter, - shared_key: Arc, + shared_key: Arc, 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, + shared_key: Arc, client_bandwidth: ClientBandwidth, shutdown: TaskClient, ) -> Self { diff --git a/common/gateway-requests/src/lib.rs b/common/gateway-requests/src/lib.rs index e45d8ca02b8..90b155c242f 100644 --- a/common/gateway-requests/src/lib.rs +++ b/common/gateway-requests/src/lib.rs @@ -2,8 +2,6 @@ // SPDX-License-Identifier: Apache-2.0 pub use nym_crypto::generic_array; -use nym_crypto::OutputSizeUser; -use nym_sphinx::params::GatewayIntegrityHmacAlgorithm; pub use types::*; @@ -12,11 +10,7 @@ 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; @@ -32,27 +26,11 @@ 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 = ::OutputSize; - pub trait GatewayProtocolVersionExt { fn supports_aes256_gcm_siv(&self) -> bool; fn supports_authenticate_v2(&self) -> bool; } -impl GatewayProtocolVersionExt for Option { - fn supports_aes256_gcm_siv(&self) -> bool { - let Some(protocol) = *self else { return false }; - protocol.supports_aes256_gcm_siv() - } - - fn supports_authenticate_v2(&self) -> bool { - let Some(protocol) = *self else { return false }; - protocol.supports_authenticate_v2() - } -} - impl GatewayProtocolVersionExt for u8 { fn supports_aes256_gcm_siv(&self) -> bool { *self >= AES_GCM_SIV_PROTOCOL_VERSION diff --git a/common/gateway-requests/src/registration/handshake/client.rs b/common/gateway-requests/src/registration/handshake/client.rs index 549cddca39a..1dd897aa2e8 100644 --- a/common/gateway-requests/src/registration/handshake/client.rs +++ b/common/gateway-requests/src/registration/handshake/client.rs @@ -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 State<'_, S, R> { S: Stream + Sink + 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 State<'_, S, R> { // 3. derive shared keys locally // hkdf::::(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 State<'_, S, R> { pub(crate) async fn perform_client_handshake( mut self, - ) -> Result + ) -> Result where S: Stream + Sink + Unpin, R: CryptoRng + RngCore, diff --git a/common/gateway-requests/src/registration/handshake/gateway.rs b/common/gateway-requests/src/registration/handshake/gateway.rs index 5fec717c465..7f2f851672f 100644 --- a/common/gateway-requests/src/registration/handshake/gateway.rs +++ b/common/gateway-requests/src/registration/handshake/gateway.rs @@ -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 State<'_, S, R> { where S: Stream + Sink + 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::::(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 State<'_, S, R> { pub(crate) async fn perform_gateway_handshake( mut self, raw_init_message: Vec, - ) -> Result + ) -> Result where S: Stream + Sink + Unpin, { diff --git a/common/gateway-requests/src/registration/handshake/messages.rs b/common/gateway-requests/src/registration/handshake/messages.rs index 10dda7edaf9..efe205d2ddf 100644 --- a/common/gateway-requests/src/registration/handshake/messages.rs +++ b/common/gateway-requests/src/registration/handshake/messages.rs @@ -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>, -} - -impl Initialisation { - #[cfg(not(target_arch = "wasm32"))] - pub fn is_legacy(&self) -> bool { - self.initiator_salt.is_none() - } + pub initiator_salt: Vec, } #[derive(Debug)] pub struct MaterialExchange { pub signature_ciphertext: Vec, - pub nonce: Option>, + pub nonce: Nonce, } 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 { - 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 { - 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 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::() + nonce_size::(); - 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::(); - ( - bytes[..ciphertext_len].to_vec(), - Some(bytes[ciphertext_len..].to_vec()), - ) - } else { - (bytes.to_vec(), None) - }; + let ciphertext_len = ed25519::SIGNATURE_LENGTH + tag_size::(); + let signature_ciphertext = bytes[..ciphertext_len].to_vec(); + + // SAFETY: we know the bytes have correct length + let nonce = Nonce::::clone_from_slice(&bytes[ciphertext_len..]); Ok(MaterialExchange { signature_ciphertext, diff --git a/common/gateway-requests/src/registration/handshake/mod.rs b/common/gateway-requests/src/registration/handshake/mod.rs index 4a6f44b7d7d..5648bb570a9 100644 --- a/common/gateway-requests/src/registration/handshake/mod.rs +++ b/common/gateway-requests/src/registration/handshake/mod.rs @@ -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>, + handshake_future: BoxFuture<'a, Result>, } impl Future for GatewayHandshake<'_> { - type Output = Result; + type Output = Result; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 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()), diff --git a/common/gateway-requests/src/registration/handshake/state.rs b/common/gateway-requests/src/registration/handshake/state.rs index ab7ae4d97f8..6ab6ad72cae 100644 --- a/common/gateway-requests/src/registration/handshake/state.rs +++ b/common/gateway-requests/src/registration/handshake/state.rs @@ -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, + derived_shared_keys: Option, /// 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> + pub(crate) fn generate_initiator_salt(&mut self) -> Vec 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>) -> Initialisation { + pub(crate) fn init_message(&self, initiator_salt: Vec) -> 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::( - 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::(&mut rng).to_vec()) - } else { - None - }; + let mut rng = thread_rng(); + let nonce = random_nonce::(&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( @@ -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() } diff --git a/common/gateway-requests/src/shared_key/helpers.rs b/common/gateway-requests/src/shared_key/helpers.rs deleted file mode 100644 index ef034f9752a..00000000000 --- a/common/gateway-requests/src/shared_key/helpers.rs +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright 2024 - Nym Technologies SA -// 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; - - fn encrypt( - &self, - plaintext: &[u8], - nonce: Option<&[u8]>, - ) -> Result, SharedKeyUsageError>; - - fn decrypt( - &self, - ciphertext: &[u8], - nonce: Option<&[u8]>, - ) -> Result, SharedKeyUsageError>; -} - -impl SymmetricKey for SharedGatewayKey { - fn random_nonce_or_iv(&self) -> Vec { - self.random_nonce_or_iv() - } - - fn encrypt( - &self, - plaintext: &[u8], - nonce: Option<&[u8]>, - ) -> Result, SharedKeyUsageError> { - self.encrypt(plaintext, nonce) - } - - fn decrypt( - &self, - ciphertext: &[u8], - nonce: Option<&[u8]>, - ) -> Result, SharedKeyUsageError> { - self.decrypt(ciphertext, nonce) - } -} - -impl SymmetricKey for SharedSymmetricKey { - fn random_nonce_or_iv(&self) -> Vec { - let mut rng = thread_rng(); - - random_nonce::(&mut rng).to_vec() - } - - fn encrypt( - &self, - plaintext: &[u8], - nonce: Option<&[u8]>, - ) -> Result, SharedKeyUsageError> { - let nonce = SharedGatewayKey::validate_aead_nonce(nonce)?; - self.encrypt(plaintext, &nonce) - } - - fn decrypt( - &self, - ciphertext: &[u8], - nonce: Option<&[u8]>, - ) -> Result, SharedKeyUsageError> { - let nonce = SharedGatewayKey::validate_aead_nonce(nonce)?; - self.decrypt(ciphertext, &nonce) - } -} - -impl SymmetricKey for LegacySharedKeys { - fn random_nonce_or_iv(&self) -> Vec { - let mut rng = thread_rng(); - - random_iv::(&mut rng).to_vec() - } - - fn encrypt( - &self, - plaintext: &[u8], - nonce: Option<&[u8]>, - ) -> Result, SharedKeyUsageError> { - let iv = SharedGatewayKey::validate_cipher_iv(nonce)?; - Ok(self.encrypt_and_tag(plaintext, iv)) - } - - fn decrypt( - &self, - ciphertext: &[u8], - nonce: Option<&[u8]>, - ) -> Result, SharedKeyUsageError> { - let iv = SharedGatewayKey::validate_cipher_iv(nonce)?; - self.decrypt_tagged(ciphertext, iv) - } -} diff --git a/common/gateway-requests/src/shared_key/legacy.rs b/common/gateway-requests/src/shared_key/legacy.rs deleted file mode 100644 index 8fcf2866973..00000000000 --- a/common/gateway-requests/src/shared_key/legacy.rs +++ /dev/null @@ -1,241 +0,0 @@ -// Copyright 2020-2023 - Nym Technologies SA -// 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; - -// we're using 16 byte long key in sphinx, so let's use the same one here -type MacKeySize = U16; -type EncryptionKeySize = ::KeySize; - -/// Shared key used when computing MAC for messages exchanged between client and its gateway. -pub type MacKey = GenericArray; - -#[derive(Debug, PartialEq, Serialize, Deserialize, Zeroize, ZeroizeOnDrop)] -pub struct LegacySharedKeys { - encryption_key: CipherKey, - mac_key: MacKey, -} - -impl LegacySharedKeys { - pub fn upgrade(&self) -> (SharedSymmetricKey, Vec) { - 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::( - 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 { - let legacy_bytes = Zeroizing::new(self.to_bytes()); - let okm = hkdf::extract_then_expand::( - 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 { - 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>, - ) -> Vec { - match iv { - Some(iv) => stream_cipher::encrypt::( - self.encryption_key(), - iv, - data, - ), - None => { - let zero_iv = stream_cipher::zero_iv::(); - stream_cipher::encrypt::( - 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>, - ) -> Vec { - let ciphertext = self.encrypt_without_tagging(data, iv); - let mac = compute_keyed_hmac::( - 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>, - ) -> Result, SharedKeyUsageError> { - let zero_iv = stream_cipher::zero_iv::(); - let iv = iv.unwrap_or(&zero_iv); - Ok(stream_cipher::decrypt::( - self.encryption_key(), - iv, - ciphertext, - )) - } - - pub fn decrypt_tagged( - &self, - enc_data: &[u8], - iv: Option<&IV>, - ) -> Result, 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::( - 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::(); - let iv = iv.unwrap_or(&zero_iv); - stream_cipher::decrypt_in_place::( - self.encryption_key(), - iv, - &mut message_bytes_mut, - ); - Ok(message_bytes_mut) - } - - pub fn encryption_key(&self) -> &CipherKey { - &self.encryption_key - } - - pub fn mac_key(&self) -> &MacKey { - &self.mac_key - } - - pub fn to_bytes(&self) -> Vec { - self.encryption_key - .iter() - .copied() - .chain(self.mac_key.iter().copied()) - .collect() - } - - pub fn try_from_base58_string>( - val: S, - ) -> Result { - 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 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 { - self.to_bytes() - } - - fn from_bytes(bytes: &[u8]) -> Result { - Self::try_from_bytes(bytes) - } -} diff --git a/common/gateway-requests/src/shared_key/mod.rs b/common/gateway-requests/src/shared_key/mod.rs index b424fd49bf5..3a7ebc03382 100644 --- a/common/gateway-requests/src/shared_key/mod.rs +++ b/common/gateway-requests/src/shared_key/mod.rs @@ -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 = ::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> { - match self { - SharedGatewayKey::Current(_) => None, - SharedGatewayKey::Legacy(key) => Some(Zeroizing::new(key.to_base58_string())), - } - } - - pub fn aes256_gcm_siv(&self) -> Option>> { - 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 { - let mut rng = thread_rng(); - - if self.is_legacy() { - random_iv::(&mut rng).to_vec() - } else { - random_nonce::(&mut rng).to_vec() - } - } - - pub fn random_nonce_or_zero_iv(&self) -> Option> { - if self.is_legacy() { - None - } else { - let mut rng = thread_rng(); - Some(random_nonce::(&mut rng).to_vec()) - } - } - - pub fn nonce_size(&self) -> usize { - match self { - SharedGatewayKey::Current(_) => nonce_size::(), - SharedGatewayKey::Legacy(_) => iv_size::(), - } - } -} - -impl From for SharedGatewayKey { - fn from(keys: LegacySharedKeys) -> Self { - SharedGatewayKey::Legacy(keys) - } -} - -impl From 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, SharedKeyUsageError> { - let Some(raw) = raw else { - return Err(SharedKeyUsageError::MissingAeadNonce); - }; - if raw.len() != nonce_size::() { - return Err(SharedKeyUsageError::MalformedNonce); - } - Ok(Nonce::::clone_from_slice(raw)) - } - - fn validate_cipher_iv( - raw: Option<&[u8]>, - ) -> Result>, SharedKeyUsageError> { - let Some(raw) = raw else { return Ok(None) }; - let iv = if raw.is_empty() { - None - } else { - if raw.len() != iv_size::() { - return Err(SharedKeyUsageError::MalformedNonce); - } - Some(IV::::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, 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, 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, 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, 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); @@ -230,6 +44,36 @@ pub enum SharedKeyConversionError { } impl SharedSymmetricKey { + pub fn random_nonce(&self) -> Nonce { + let mut rng = thread_rng(); + random_nonce::(&mut rng) + } + + pub fn nonce_size(&self) -> usize { + nonce_size::() + } + + pub fn decode_bs58_nonce>( + raw: I, + ) -> Result, 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, SharedKeyUsageError> { + if raw.len() != nonce_size::() { + return Err(SharedKeyUsageError::MalformedNonce); + } + Ok(Nonce::::clone_from_slice(raw)) + } + pub fn try_from_bytes(bytes: &[u8]) -> Result { if bytes.len() != KeySize::to_usize() { return Err(SharedKeyConversionError::InvalidSharedKeysSize { diff --git a/common/gateway-requests/src/types/binary_request.rs b/common/gateway-requests/src/types/binary_request.rs index 17eab6c9cb2..f9da2f5ab5a 100644 --- a/common/gateway-requests/src/types/binary_request.rs +++ b/common/gateway-requests/src/types/binary_request.rs @@ -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, - shared_key: &SharedGatewayKey, + shared_key: &SharedSymmetricKey, ) -> Result { BinaryData::from_raw(&bytes, shared_key)?.into_request(shared_key) } pub fn into_encrypted_tagged_bytes( self, - shared_key: &SharedGatewayKey, + shared_key: &SharedSymmetricKey, ) -> Result, GatewayRequestsError> { let kind = self.kind(); @@ -66,7 +66,7 @@ impl BinaryRequest { pub fn into_ws_message( self, - shared_key: &SharedGatewayKey, + shared_key: &SharedSymmetricKey, ) -> Result { // all variants are currently encrypted let blob = match self { diff --git a/common/gateway-requests/src/types/binary_response.rs b/common/gateway-requests/src/types/binary_response.rs index d3652b64e4f..69ea76c23e2 100644 --- a/common/gateway-requests/src/types/binary_response.rs +++ b/common/gateway-requests/src/types/binary_response.rs @@ -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, - shared_key: &SharedGatewayKey, + shared_key: &SharedSymmetricKey, ) -> Result { BinaryData::from_raw(&bytes, shared_key)?.into_response(shared_key) } pub fn into_encrypted_tagged_bytes( self, - shared_key: &SharedGatewayKey, + shared_key: &SharedSymmetricKey, ) -> Result, GatewayRequestsError> { let kind = self.kind(); @@ -58,7 +58,7 @@ impl BinaryResponse { pub fn into_ws_message( self, - shared_key: &SharedGatewayKey, + shared_key: &SharedSymmetricKey, ) -> Result { // all variants are currently encrypted let blob = match self { diff --git a/common/gateway-requests/src/types/error.rs b/common/gateway-requests/src/types/error.rs index 0a785e60ecd..36debb3c562 100644 --- a/common/gateway-requests/src/types/error.rs +++ b/common/gateway-requests/src/types/error.rs @@ -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), diff --git a/common/gateway-requests/src/types/helpers.rs b/common/gateway-requests/src/types/helpers.rs index f3d7ac3a1c5..c915598523f 100644 --- a/common/gateway-requests/src/types/helpers.rs +++ b/common/gateway-requests/src/types/helpers.rs @@ -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 { - if legacy { - return self.data.to_vec(); - } - + pub fn into_raw(self) -> Vec { 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 { - // 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, 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 { 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 { 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 }; diff --git a/common/gateway-requests/src/types/text_request/authenticate.rs b/common/gateway-requests/src/types/text_request/authenticate.rs index 6015beb8ad4..7dbfb572f4e 100644 --- a/common/gateway-requests/src/types/text_request/authenticate.rs +++ b/common/gateway-requests/src/types/text_request/authenticate.rs @@ -1,7 +1,7 @@ // Copyright 2025 - Nym Technologies SA // 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 { 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 { - 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... }) } diff --git a/common/gateway-requests/src/types/text_request/mod.rs b/common/gateway-requests/src/types/text_request/mod.rs index 06fa992086b..3808cc238dd 100644 --- a/common/gateway-requests/src/types/text_request/mod.rs +++ b/common/gateway-requests/src/types/text_request/mod.rs @@ -3,9 +3,7 @@ use crate::models::CredentialSpendingRequest; use crate::text_request::authenticate::AuthenticateRequest; -use crate::{ - GatewayRequestsError, SharedGatewayKey, SymmetricKey, AUTHENTICATE_V2_PROTOCOL_VERSION, -}; +use crate::{GatewayRequestsError, SharedSymmetricKey, AUTHENTICATE_V2_PROTOCOL_VERSION}; use nym_credentials_interface::CredentialSpendingData; use nym_crypto::asymmetric::ed25519; use serde::{Deserialize, Serialize}; @@ -18,20 +16,13 @@ pub mod authenticate; #[derive(Serialize, Deserialize, Debug, Clone)] #[non_exhaustive] pub enum ClientRequest { - UpgradeKey { - hkdf_salt: Vec, - derived_key_digest: Vec, - }, - ForgetMe { - client: bool, - stats: bool, - }, + ForgetMe { client: bool, stats: bool }, } impl ClientRequest { - pub fn encrypt( + pub fn encrypt( &self, - key: &S, + key: &SharedSymmetricKey, ) -> Result { // we're using json representation for few reasons: // - ease of re-implementation in other languages (compared to for example bincode) @@ -40,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( + pub fn decrypt( ciphertext: &[u8], nonce: &[u8], - key: &S, + key: &SharedSymmetricKey, ) -> Result { - 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 }) } @@ -71,7 +66,8 @@ pub enum ClientControlRequest { EcashCredential { enc_credential: Vec, - iv: Vec, + #[serde(alias = "iv")] + nonce: Vec, }, ClaimFreeTestnetBandwidth, EncryptedRequest { @@ -102,10 +98,11 @@ pub enum ClientControlRequest { impl ClientControlRequest { pub fn new_authenticate_v2( - shared_key: &SharedGatewayKey, + shared_key: &SharedSymmetricKey, identity_keys: &ed25519::KeyPair, ) -> Result { // 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( @@ -135,26 +132,27 @@ impl ClientControlRequest { pub fn new_enc_ecash_credential( credential: CredentialSpendingData, - shared_key: &SharedGatewayKey, + shared_key: &SharedSymmetricKey, ) -> Result { 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, - shared_key: &SharedGatewayKey, - iv: Vec, + shared_key: &SharedSymmetricKey, + nonce: Vec, ) -> Result { - 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) } diff --git a/common/gateway-requests/src/types/text_response.rs b/common/gateway-requests/src/types/text_response.rs index 8f1de487651..a173c92c0f5 100644 --- a/common/gateway-requests/src/types/text_response.rs +++ b/common/gateway-requests/src/types/text_response.rs @@ -1,7 +1,7 @@ // Copyright 2024 - Nym Technologies SA // 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( + pub fn encrypt( &self, - key: &S, + key: &SharedSymmetricKey, ) -> Result { // 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( + pub fn decrypt( ciphertext: &[u8], nonce: &[u8], - key: &S, + key: &SharedSymmetricKey, ) -> Result { - 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 }) } diff --git a/common/gateway-storage/migrations/20250227120000_remove_aes128ctr_key.sql b/common/gateway-storage/migrations/20250227120000_remove_aes128ctr_key.sql new file mode 100644 index 00000000000..a1917086839 --- /dev/null +++ b/common/gateway-storage/migrations/20250227120000_remove_aes128ctr_key.sql @@ -0,0 +1,22 @@ +/* + * Copyright 2025 - Nym Technologies SA + * 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; \ No newline at end of file diff --git a/common/gateway-storage/src/lib.rs b/common/gateway-storage/src/lib.rs index 2d05d43fe74..fbc57d3d82d 100644 --- a/common/gateway-storage/src/lib.rs +++ b/common/gateway-storage/src/lib.rs @@ -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 { 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) diff --git a/common/gateway-storage/src/models.rs b/common/gateway-storage/src/models.rs index 665b8fee472..b9b331b966e 100644 --- a/common/gateway-storage/src/models.rs +++ b/common/gateway-storage/src/models.rs @@ -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, - pub derived_aes256_gcm_siv_key: Option>, + pub derived_aes256_gcm_siv_key: Vec, pub last_used_authentication: Option, } -impl TryFrom for SharedGatewayKey { +impl TryFrom for SharedSymmetricKey { type Error = GatewayStorageError; fn try_from(value: PersistedSharedKeys) -> Result { - 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())) } } diff --git a/common/gateway-storage/src/shared_keys.rs b/common/gateway-storage/src/shared_keys.rs index a7ae832cac2..8654562369d 100644 --- a/common/gateway-storage/src/shared_keys.rs +++ b/common/gateway-storage/src/shared_keys.rs @@ -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>, + derived_aes256_gcm_siv_key: &Vec, ) -> 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?; diff --git a/common/nymsphinx/params/src/lib.rs b/common/nymsphinx/params/src/lib.rs index f5d3fd7afbe..f2e679e5bb9 100644 --- a/common/nymsphinx/params/src/lib.rs +++ b/common/nymsphinx/params/src/lib.rs @@ -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 diff --git a/common/wasm/client-core/src/storage/core_client_traits.rs b/common/wasm/client-core/src/storage/core_client_traits.rs index e5f661f866d..b0af966c0b1 100644 --- a/common/wasm/client-core/src/storage/core_client_traits.rs +++ b/common/wasm/client-core/src/storage/core_client_traits.rs @@ -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 } diff --git a/common/wasm/client-core/src/storage/types.rs b/common/wasm/client-core/src/storage/types.rs index b94f04f6514..43422cc2f18 100644 --- a/common/wasm/client-core/src/storage/types.rs +++ b/common/wasm/client-core/src/storage/types.rs @@ -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, - #[serde(default)] - pub derived_aes256_gcm_siv_key: Option>, + pub derived_aes256_gcm_siv_key: Vec, pub gateway_owner_address: Option, @@ -35,8 +31,6 @@ impl TryFrom 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 diff --git a/common/wasm/client-core/src/storage/wasm_client_traits.rs b/common/wasm/client-core/src/storage/wasm_client_traits.rs index 1353112885e..e870623c850 100644 --- a/common/wasm/client-core/src/storage/wasm_client_traits.rs +++ b/common/wasm/client-core/src/storage/wasm_client_traits.rs @@ -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<(), ::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, diff --git a/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs b/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs index d0588b716d2..f71ddbbac04 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs @@ -349,35 +349,6 @@ impl AuthenticatedHandler { Ok(SensitiveServerResponse::ForgetMeAck {}.encrypt(&self.client.shared_keys)?) } - async fn handle_key_upgrade( - &mut self, - hkdf_salt: Vec, - client_key_digest: Vec, - ) -> Result { - 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, @@ -388,10 +359,6 @@ impl AuthenticatedHandler { }; 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,9 +392,10 @@ impl AuthenticatedHandler { 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(), diff --git a/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs b/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs index e960bfdcfaa..0994ecccdc9 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs @@ -20,8 +20,8 @@ use nym_gateway_requests::authenticate::AuthenticateRequest; use nym_gateway_requests::{ registration::handshake::{error::HandshakeError, gateway_handshake}, types::{ClientControlRequest, ServerResponse}, - AuthenticationFailure, BinaryResponse, GatewayProtocolVersionExt, SharedGatewayKey, - CURRENT_PROTOCOL_VERSION, + AuthenticationFailure, BinaryResponse, GatewayProtocolVersionExt, SharedKeyUsageError, + SharedSymmetricKey, CURRENT_PROTOCOL_VERSION, }; use nym_gateway_storage::error::GatewayStorageError; use nym_node_metrics::events::MetricsEvent; @@ -48,6 +48,9 @@ pub(crate) enum InitialAuthenticationError { #[error("attempted to overwrite client session with a stale authentication")] StaleSessionOverwrite, + #[error(transparent)] + KeyUsageFailure(#[from] SharedKeyUsageError), + #[error("Internal gateway storage error")] StorageError(#[from] GatewayStorageError), @@ -173,7 +176,7 @@ impl FreshHandler { async fn perform_registration_handshake( &mut self, init_msg: Vec, - ) -> Result + ) -> Result where S: AsyncRead + AsyncWrite + Unpin + Send, R: CryptoRng + RngCore + Send, @@ -255,7 +258,7 @@ impl FreshHandler { /// * `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>, ) -> Result<(), WsError> where @@ -313,7 +316,7 @@ impl FreshHandler { 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, @@ -371,16 +374,18 @@ impl FreshHandler { &self, client_protocol: u8, ) -> Result { + let incompatible_err = InitialAuthenticationError::IncompatibleProtocol { + client: client_protocol, + current: CURRENT_PROTOCOL_VERSION, + }; + debug!("client protocol: {client_protocol}, ours: {CURRENT_PROTOCOL_VERSION}"); - // gateway will reject any requests from clients that do not support auth v2 - if !client_protocol.supports_authenticate_v2() { - let err = InitialAuthenticationError::IncompatibleProtocol { - client: client_protocol, - current: CURRENT_PROTOCOL_VERSION, - }; - error!("{err}"); - return Err(err); + // 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 @@ -389,12 +394,8 @@ impl FreshHandler { 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) } } @@ -582,7 +583,7 @@ impl FreshHandler { async fn register_client( &mut self, client_address: DestinationAddressBytes, - client_shared_keys: &SharedGatewayKey, + client_shared_keys: &SharedSymmetricKey, ) -> Result where S: AsyncRead + AsyncWrite + Unpin, diff --git a/gateway/src/node/client_handling/websocket/connection_handler/helpers.rs b/gateway/src/node/client_handling/websocket/connection_handler/helpers.rs index 5a5d24c5d61..11de82a382e 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/helpers.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/helpers.rs @@ -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, } @@ -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, diff --git a/gateway/src/node/client_handling/websocket/connection_handler/mod.rs b/gateway/src/node/client_handling/websocket/connection_handler/mod.rs index 6d079827bba..d0321ca263c 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/mod.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/mod.rs @@ -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 SocketStream { 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 { diff --git a/nym-api/src/network_monitor/monitor/sender.rs b/nym-api/src/network_monitor/monitor/sender.rs index fa6ba454c9e..22ddfc47b80 100644 --- a/nym-api/src/network_monitor/monitor/sender.rs +++ b/nym-api/src/network_monitor/monitor/sender.rs @@ -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, disabled_credentials_mode: bool, - gateways_key_cache: DashMap>, + gateways_key_cache: DashMap>, } impl FreshGatewayClientData { @@ -267,7 +267,7 @@ impl PacketSender { connection_timeout: Duration, bandwidth_claim_timeout: Duration, client: &mut GatewayClientHandle, - ) -> Option> { + ) -> Option> { let gateway_identity = client.gateway_identity(); // 1. attempt to authenticate diff --git a/sdk/rust/nym-sdk/examples/manually_handle_storage.rs b/sdk/rust/nym-sdk/examples/manually_handle_storage.rs index 05ce8ebf018..f327ea47c2c 100644 --- a/sdk/rust/nym-sdk/examples/manually_handle_storage.rs +++ b/sdk/rust/nym-sdk/examples/manually_handle_storage.rs @@ -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"); diff --git a/wasm/node-tester/src/tester.rs b/wasm/node-tester/src/tester.rs index e0ef0d43505..6ddd6c9c2ea 100644 --- a/wasm/node-tester/src/tester.rs +++ b/wasm/node-tester/src/tester.rs @@ -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()?;