persistent wasm client keys

This commit is contained in:
Jędrzej Stuczyński
2023-05-03 14:36:10 +01:00
parent 029c445805
commit 28a965e698
35 changed files with 926 additions and 445 deletions
Generated
+7
View File
@@ -1960,6 +1960,7 @@ version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"serde",
"typenum",
"version_check",
"zeroize",
@@ -3353,6 +3354,7 @@ dependencies = [
"wasm-bindgen-futures",
"wasm-timer",
"wasm-utils",
"zeroize",
]
[[package]]
@@ -3632,6 +3634,7 @@ version = "0.1.0"
dependencies = [
"bs58",
"futures",
"generic-array 0.14.7",
"log",
"nym-coconut-interface",
"nym-credentials",
@@ -3643,6 +3646,7 @@ dependencies = [
"serde_json",
"thiserror",
"tungstenite 0.13.0",
"zeroize",
]
[[package]]
@@ -4092,6 +4096,7 @@ dependencies = [
name = "nym-sphinx-acknowledgements"
version = "0.1.0"
dependencies = [
"generic-array 0.14.7",
"nym-crypto",
"nym-pemstore",
"nym-sphinx-addressing",
@@ -4099,7 +4104,9 @@ dependencies = [
"nym-sphinx-types",
"nym-topology",
"rand 0.7.3",
"serde",
"thiserror",
"zeroize",
]
[[package]]
+2
View File
@@ -125,6 +125,7 @@ cw3 = { version = "=0.13.4" }
cw3-fixed-multisig = { version = "=0.13.4" }
cw4 = { version = "=0.13.4" }
dotenvy = "0.15.6"
generic-array = "0.14.7"
lazy_static = "1.4.0"
log = "0.4"
once_cell = "1.7.2"
@@ -135,3 +136,4 @@ tap = "1.0.1"
thiserror = "1.0.38"
tokio = "1.24.1"
url = "2.2"
zeroize = "1.6.0"
+1
View File
@@ -44,6 +44,7 @@ nym-credentials = { path = "../../common/credentials" }
nym-credential-storage = { path = "../../common/credential-storage" }
nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "serde"] }
nym-sphinx = { path = "../../common/nymsphinx" }
nym-sphinx-acknowledgements = { path = "../../common/nymsphinx/acknowledgements", features = ["serde"]}
nym-topology = { path = "../../common/topology" }
nym-gateway-client = { path = "../../common/client-libs/gateway-client", default-features = false, features = ["wasm"] }
nym-validator-client = { path = "../../common/client-libs/validator-client", default-features = false }
+6 -9
View File
@@ -286,11 +286,11 @@ async function messWithStorage() {
let id1 = "one";
let id2 = "two";
// console.log("making store1 NO-ENC");
// let _storage1 = await ClientStorage.new_unencrypted(id1);
//
// console.log("making store2 ENC")
// let _storage2 = await new ClientStorage(id2, "my-secret-password");
console.log("making store1 NO-ENC");
let _storage1 = await ClientStorage.new_unencrypted(id1);
console.log("making store2 ENC")
let _storage2 = await new ClientStorage(id2, "my-secret-password");
//
//
//
@@ -341,10 +341,7 @@ async function main() {
// await testWithNymClient()
// 'Normal' client setup (to send 'normal' messages)
// await normalNymClientUsage()
await messWithStorage()
await normalNymClientUsage()
}
// Let's get started!
+32 -19
View File
@@ -4,10 +4,10 @@
use self::config::Config;
use crate::client::helpers::{InputSender, NymClientTestRequest, WasmTopologyExt};
use crate::client::response_pusher::ResponsePusher;
use crate::constants::NODE_TESTER_CLIENT_ID;
use crate::error::WasmClientError;
use crate::helpers::{
parse_recipient, parse_sender_tag, setup_new_key_manager, setup_reply_surb_storage_backend,
};
use crate::helpers::{parse_recipient, parse_sender_tag, setup_reply_surb_storage_backend};
use crate::storage::ClientStorage;
use crate::topology::WasmNymTopology;
use js_sys::Promise;
use nym_bandwidth_controller::wasm_mockups::{Client as FakeClient, DirectSigningNyxdClient};
@@ -15,8 +15,8 @@ use nym_bandwidth_controller::BandwidthController;
use nym_client_core::client::base_client::{
BaseClientBuilder, ClientInput, ClientOutput, ClientState, CredentialsToggle,
};
use nym_client_core::client::inbound_messages::InputMessage;
use nym_client_core::client::replies::reply_storage::browser_backend;
use nym_client_core::client::{inbound_messages::InputMessage, key_manager::KeyManager};
use nym_client_core::config::{
CoverTraffic, DebugConfig, GatewayEndpointConfig, Topology, Traffic,
};
@@ -56,9 +56,7 @@ pub struct NymClientBuilder {
config: Config,
custom_topology: Option<NymTopology>,
/// KeyManager object containing smart pointers to all relevant keys used by the client.
key_manager: KeyManager,
storage_passphrase: Option<String>,
reply_surb_storage_backend: browser_backend::Backend,
on_message: js_sys::Function,
@@ -72,13 +70,16 @@ pub struct NymClientBuilder {
#[wasm_bindgen]
impl NymClientBuilder {
#[wasm_bindgen(constructor)]
pub fn new(config: Config, on_message: js_sys::Function) -> Self {
//, key_manager: Option<KeyManager>) {
pub fn new(
config: Config,
on_message: js_sys::Function,
storage_passphrase: Option<String>,
) -> Self {
NymClientBuilder {
reply_surb_storage_backend: setup_reply_surb_storage_backend(config.debug.reply_surbs),
config,
custom_topology: None,
key_manager: setup_new_key_manager(),
storage_passphrase,
on_message,
bandwidth_controller: None,
disabled_credentials: true,
@@ -99,7 +100,7 @@ impl NymClientBuilder {
}
let full_config = Config {
id: "ephemeral-id".to_string(),
id: NODE_TESTER_CLIENT_ID.to_string(),
nym_api_url: None,
disabled_credentials_mode: true,
gateway_endpoint: gateway_config,
@@ -126,12 +127,10 @@ impl NymClientBuilder {
),
config: full_config,
custom_topology: Some(topology.into()),
// TODO: once we make keys persistent, we'll require some kind of `init` method to generate
// a prior shared keypair between the client and the gateway
key_manager: setup_new_key_manager(),
on_message,
bandwidth_controller: None,
disabled_credentials: true,
storage_passphrase: None,
}
}
@@ -162,10 +161,15 @@ impl NymClientBuilder {
Some(endpoint) => vec![endpoint],
None => Vec::new(),
};
// TODO: this will have to be re-used for surbs. but this is a problem for another PR.
let key_store =
ClientStorage::new_async(&self.config.id, self.storage_passphrase.take()).await?;
let mut base_builder = BaseClientBuilder::new(
&self.config.gateway_endpoint,
&self.config.debug,
self.key_manager,
key_store,
self.bandwidth_controller,
self.reply_surb_storage_backend,
disabled_credentials,
@@ -175,8 +179,8 @@ impl NymClientBuilder {
base_builder = base_builder.with_topology_provider(topology_provider);
}
let self_address = base_builder.as_mix_recipient().to_string();
let mut started_client = base_builder.start_base().await?;
let self_address = started_client.address.to_string();
let client_input = started_client.client_input.register_producer();
let client_output = started_client.client_output.register_consumer();
@@ -202,16 +206,25 @@ impl NymClient {
async fn _new(
config: Config,
on_message: js_sys::Function,
storage_passphrase: Option<String>,
) -> Result<NymClient, WasmClientError> {
NymClientBuilder::new(config, on_message)
NymClientBuilder::new(config, on_message, storage_passphrase)
.start_client_async()
.await
}
#[wasm_bindgen(constructor)]
#[allow(clippy::new_ret_no_self)]
pub fn new(config: Config, on_message: js_sys::Function) -> Promise {
future_to_promise(async move { Self::_new(config, on_message).await.into_promise_result() })
pub fn new(
config: Config,
on_message: js_sys::Function,
storage_passphrase: Option<String>,
) -> Promise {
future_to_promise(async move {
Self::_new(config, on_message, storage_passphrase)
.await
.into_promise_result()
})
}
pub fn self_address(&self) -> String {
+5
View File
@@ -0,0 +1,5 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub(crate) const NODE_TESTER_ID: &str = "_nym-node-tester";
pub(crate) const NODE_TESTER_CLIENT_ID: &str = "_nym-node-tester-client";
+3
View File
@@ -85,6 +85,9 @@ pub enum WasmClientError {
#[from]
source: StorageError,
},
#[error("{typ} cryptographic is not available in storage")]
CryptoKeyNotInStorage { typ: String },
}
impl WasmClientError {
+1 -9
View File
@@ -4,24 +4,16 @@
use crate::error::WasmClientError;
use crate::topology::WasmNymTopology;
use js_sys::Promise;
use nym_client_core::client::key_manager::KeyManager;
use nym_client_core::client::replies::reply_storage::browser_backend;
use nym_client_core::config;
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
use nym_topology::NymTopology;
use nym_validator_client::NymApiClient;
use rand::rngs::OsRng;
use url::Url;
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen_futures::future_to_promise;
use wasm_utils::{console_log, PromisableResult};
pub(crate) fn setup_new_key_manager() -> KeyManager {
let mut rng = OsRng;
console_log!("generated new set of keys");
KeyManager::new(&mut rng)
}
use wasm_utils::PromisableResult;
// don't get too excited about the name, under the hood it's just a big fat placeholder
// with no persistence
+2
View File
@@ -22,6 +22,8 @@ pub mod validation;
#[cfg(target_arch = "wasm32")]
mod helpers;
mod constants;
#[wasm_bindgen]
pub fn set_panic_hook() {
// When the `console_error_panic_hook` feature is enabled, we can call the
+108 -12
View File
@@ -4,13 +4,15 @@
use crate::error::WasmClientError;
use async_trait::async_trait;
use js_sys::Promise;
use nym_client_core::client::key_manager::{KeyManager, KeyStore};
use nym_client_core::client::key_manager::{persistence::KeyStore, KeyManager};
use nym_crypto::asymmetric::{encryption, identity};
use nym_gateway_client::SharedKeys;
use nym_sphinx::acknowledgements::AckKey;
use std::sync::Arc;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::future_to_promise;
use wasm_utils::storage::{IdbVersionChangeEvent, WasmStorage};
use wasm_utils::PromisableResult;
use wasm_utils::{console_log, PromisableResult};
use zeroize::Zeroizing;
const STORAGE_NAME_PREFIX: &str = "wasm-client-storage";
@@ -27,12 +29,12 @@ mod v1 {
// TODO: for those we could actually use the subtle crypto storage
pub const AES128CTR_ACK_KEY: &str = "aes128ctr_ack_key";
pub const AES128CTR_BLAKE3_HMAC_GATEWAY_KEYS_PREFIX: &str =
"aes128ctr_blake3_hmac_gateway_keys";
pub const AES128CTR_BLAKE3_HMAC_GATEWAY_KEYS: &str = "aes128ctr_blake3_hmac_gateway_keys";
}
#[wasm_bindgen]
pub struct ClientStorage {
#[allow(dead_code)]
pub(crate) name: String,
pub(crate) inner: Arc<WasmStorage>,
}
@@ -43,7 +45,7 @@ impl ClientStorage {
format!("{STORAGE_NAME_PREFIX}-{client_id}")
}
async fn new_async(
pub(crate) async fn new_async(
client_id: &str,
passphrase: Option<String>,
) -> Result<Self, WasmClientError> {
@@ -53,7 +55,7 @@ impl ClientStorage {
// special care must be taken on JS side to ensure it's correctly used there.
let passphrase = Zeroizing::new(passphrase);
let migrate_fn = (Some(|evt: &IdbVersionChangeEvent| -> Result<(), JsValue> {
let migrate_fn = Some(|evt: &IdbVersionChangeEvent| -> Result<(), JsValue> {
// Even if the web-sys bindings expose the version as a f64, the IndexedDB API
// works with an unsigned integer.
// See <https://github.com/rustwasm/wasm-bindgen/issues/1149>
@@ -67,7 +69,7 @@ impl ClientStorage {
}
Ok(())
}));
});
let inner = WasmStorage::new(
&name,
@@ -101,7 +103,9 @@ impl ClientStorage {
})
}
async fn read_identity_keypair(&self) -> Result<Option<identity::KeyPair>, WasmClientError> {
async fn may_read_identity_keypair(
&self,
) -> Result<Option<identity::KeyPair>, WasmClientError> {
self.inner
.read_value(
v1::KEYS_STORE,
@@ -111,7 +115,7 @@ impl ClientStorage {
.map_err(Into::into)
}
async fn read_encryption_keypair(
async fn may_read_encryption_keypair(
&self,
) -> Result<Option<encryption::KeyPair>, WasmClientError> {
self.inner
@@ -123,6 +127,55 @@ impl ClientStorage {
.map_err(Into::into)
}
async fn may_read_ack_key(&self) -> Result<Option<AckKey>, WasmClientError> {
self.inner
.read_value(v1::KEYS_STORE, JsValue::from_str(v1::AES128CTR_ACK_KEY))
.await
.map_err(Into::into)
}
async fn may_read_gateway_shared_key(&self) -> Result<Option<SharedKeys>, WasmClientError> {
self.inner
.read_value(
v1::KEYS_STORE,
JsValue::from_str(v1::AES128CTR_BLAKE3_HMAC_GATEWAY_KEYS),
)
.await
.map_err(Into::into)
}
async fn must_read_identity_keypair(&self) -> Result<identity::KeyPair, WasmClientError> {
self.may_read_identity_keypair()
.await?
.ok_or(WasmClientError::CryptoKeyNotInStorage {
typ: v1::ED25519_IDENTITY_KEYPAIR.to_string(),
})
}
async fn must_read_encryption_keypair(&self) -> Result<encryption::KeyPair, WasmClientError> {
self.may_read_encryption_keypair()
.await?
.ok_or(WasmClientError::CryptoKeyNotInStorage {
typ: v1::X25519_ENCRYPTION_KEYPAIR.to_string(),
})
}
async fn must_read_ack_key(&self) -> Result<AckKey, WasmClientError> {
self.may_read_ack_key()
.await?
.ok_or(WasmClientError::CryptoKeyNotInStorage {
typ: v1::AES128CTR_ACK_KEY.to_string(),
})
}
async fn must_read_gateway_shared_key(&self) -> Result<SharedKeys, WasmClientError> {
self.may_read_gateway_shared_key()
.await?
.ok_or(WasmClientError::CryptoKeyNotInStorage {
typ: v1::AES128CTR_BLAKE3_HMAC_GATEWAY_KEYS.to_string(),
})
}
async fn store_identity_keypair(
&self,
keypair: &identity::KeyPair,
@@ -150,6 +203,28 @@ impl ClientStorage {
.await
.map_err(Into::into)
}
async fn store_ack_key(&self, key: &AckKey) -> Result<(), WasmClientError> {
self.inner
.store_value(
v1::KEYS_STORE,
JsValue::from_str(v1::AES128CTR_ACK_KEY),
key,
)
.await
.map_err(Into::into)
}
async fn store_gateway_shared_key(&self, key: &SharedKeys) -> Result<(), WasmClientError> {
self.inner
.store_value(
v1::KEYS_STORE,
JsValue::from_str(v1::AES128CTR_BLAKE3_HMAC_GATEWAY_KEYS),
key,
)
.await
.map_err(Into::into)
}
}
#[async_trait(?Send)]
@@ -157,10 +232,31 @@ impl KeyStore for ClientStorage {
type StorageError = WasmClientError;
async fn load_keys(&self) -> Result<KeyManager, Self::StorageError> {
todo!()
console_log!("attempting to load cryptographic keys...");
// all keys implement `ZeroizeOnDrop`, so if we return an Error, whatever was already loaded will be cleared
let identity_keypair = self.must_read_identity_keypair().await?;
let encryption_keypair = self.must_read_encryption_keypair().await?;
let ack_keypair = self.must_read_ack_key().await?;
let gateway_shared_key = self.must_read_gateway_shared_key().await?;
Ok(KeyManager::from_keys(
identity_keypair,
encryption_keypair,
gateway_shared_key,
ack_keypair,
))
}
async fn store_keys(&self, keys: KeyManager) -> Result<(), Self::StorageError> {
todo!()
async fn store_keys(&self, keys: &KeyManager) -> Result<(), Self::StorageError> {
console_log!("attempting to store cryptographic keys...");
self.store_identity_keypair(&keys.identity_keypair())
.await?;
self.store_encryption_keypair(&keys.encryption_keypair())
.await?;
self.store_ack_key(&keys.ack_key()).await?;
self.store_gateway_shared_key(&keys.gateway_shared_key())
.await
}
}
+20 -27
View File
@@ -1,8 +1,10 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::constants::NODE_TESTER_ID;
use crate::error::WasmClientError;
use crate::helpers::{current_network_topology_async, setup_new_key_manager};
use crate::helpers::current_network_topology_async;
use crate::storage::ClientStorage;
use crate::tester::ephemeral_receiver::EphemeralTestReceiver;
use crate::tester::helpers::{
NodeTestResult, ReceivedReceiverWrapper, TestMarker, WasmTestMessageExt,
@@ -12,7 +14,7 @@ use futures::channel::mpsc;
use js_sys::Promise;
use nym_bandwidth_controller::wasm_mockups::{Client as FakeClient, DirectSigningNyxdClient};
use nym_bandwidth_controller::BandwidthController;
use nym_client_core::client::key_manager::KeyManager;
use nym_client_core::client::key_manager::ManagedKeys;
use nym_client_core::config::GatewayEndpointConfig;
use nym_credential_storage::ephemeral_storage::EphemeralStorage;
use nym_crypto::asymmetric::identity;
@@ -33,7 +35,7 @@ use std::time::Duration;
use tokio::sync::Mutex as AsyncMutex;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::future_to_promise;
use wasm_utils::{check_promise_result, console_log, console_warn, PromisableResult};
use wasm_utils::{check_promise_result, console_log, PromisableResult};
mod ephemeral_receiver;
pub(crate) mod helpers;
@@ -74,18 +76,15 @@ pub struct NymNodeTesterBuilder {
base_topology: NymTopology,
/// KeyManager object containing smart pointers to all relevant keys used by the client.
key_manager: KeyManager,
// unimplemented
bandwidth_controller:
Option<BandwidthController<FakeClient<DirectSigningNyxdClient>, EphemeralStorage>>,
}
fn address(keys: &KeyManager, gateway_identity: NodeIdentity) -> Recipient {
fn address(keys: &ManagedKeys, gateway_identity: NodeIdentity) -> Recipient {
Recipient::new(
*keys.identity_keypair().public_key(),
*keys.encryption_keypair().public_key(),
*keys.identity_public_key(),
*keys.encryption_public_key(),
gateway_identity,
)
}
@@ -100,7 +99,6 @@ impl NymNodeTesterBuilder {
NymNodeTesterBuilder {
gateway_config,
base_topology: base_topology.into(),
key_manager: setup_new_key_manager(),
bandwidth_controller: None,
}
}
@@ -122,29 +120,24 @@ impl NymNodeTesterBuilder {
}
async fn _setup_client(mut self) -> Result<NymNodeTester, WasmClientError> {
let rng = OsRng;
let mut rng = OsRng;
let task_manager = TaskManager::default();
let gateway_identity =
identity::PublicKey::from_base58_string(self.gateway_config.gateway_id)
.map_err(|source| WasmClientError::InvalidGatewayIdentity { source })?;
// we **REALLY** need persistence...
let shared_key = if self.key_manager.is_gateway_key_set() {
Some(self.key_manager.gateway_shared_key())
} else {
console_warn!("Gateway key not set - will derive a fresh one.");
None
};
let key_store = ClientStorage::new_async(NODE_TESTER_ID, None).await?;
let mut managed_keys = ManagedKeys::load_or_generate(&mut rng, &key_store).await;
let (mixnet_message_sender, mixnet_message_receiver) = mpsc::unbounded();
let (ack_sender, ack_receiver) = mpsc::unbounded();
let mut gateway_client = GatewayClient::new(
self.gateway_config.gateway_listener,
self.key_manager.identity_keypair(),
managed_keys.identity_keypair(),
gateway_identity,
shared_key,
managed_keys.gateway_shared_key(),
mixnet_message_sender,
ack_sender,
Duration::from_secs(10),
@@ -154,26 +147,26 @@ impl NymNodeTesterBuilder {
gateway_client.set_disabled_credentials_mode(true);
let shared_keys = gateway_client.authenticate_and_start().await?;
// currently pointless but might as well do it for the future ¯\_(ツ)_/¯
self.key_manager.insert_gateway_shared_key(shared_keys);
managed_keys
.deal_with_gateway_key(shared_keys, &key_store)
.await?;
// TODO: make those values configurable later
let tester = NodeTester::new(
rng,
self.base_topology,
Some(address(&self.key_manager, gateway_identity)),
Some(address(&managed_keys, gateway_identity)),
PacketSize::default(),
Duration::from_millis(5),
Duration::from_millis(5),
self.key_manager.ack_key(),
managed_keys.ack_key(),
);
let (processed_sender, processed_receiver) = mpsc::unbounded();
let mut receiver = SimpleMessageReceiver::new_sphinx_receiver(
self.key_manager.encryption_keypair(),
self.key_manager.ack_key(),
managed_keys.encryption_keypair(),
managed_keys.ack_key(),
mixnet_message_receiver,
ack_receiver,
processed_sender,
+1
View File
@@ -23,6 +23,7 @@ url = { version ="2.2", features = ["serde"] }
tungstenite = { version = "0.13.0", default-features = false }
tokio = { version = "1.24.1", features = ["macros"]}
time = "0.3.17"
zeroize = { workspace = true }
# internal
nym-bandwidth-controller = { path = "../bandwidth-controller" }
@@ -4,7 +4,7 @@
use super::received_buffer::ReceivedBufferMessage;
use crate::client::cover_traffic_stream::LoopCoverTrafficStream;
use crate::client::inbound_messages::{InputMessage, InputMessageReceiver, InputMessageSender};
use crate::client::key_manager::KeyManager;
use crate::client::key_manager::ManagedKeys;
use crate::client::mix_traffic::{BatchMixMessageSender, MixTrafficController};
use crate::client::real_messages_control;
use crate::client::real_messages_control::RealMessagesController;
@@ -38,6 +38,7 @@ use nym_sphinx::receiver::{ReconstructedMessage, SphinxMessageReceiver};
use nym_task::connections::{ConnectionCommandReceiver, ConnectionCommandSender, LaneQueueLengths};
use nym_task::{TaskClient, TaskManager};
use nym_topology::provider_trait::TopologyProvider;
use rand::thread_rng;
use std::sync::Arc;
use tap::TapFallible;
use url::Url;
@@ -46,6 +47,7 @@ use nym_credential_storage::storage::Storage;
#[cfg(not(target_arch = "wasm32"))]
use nym_validator_client::nyxd::traits::DkgQueryClient;
use crate::client::key_manager::persistence::KeyStore;
#[cfg(target_arch = "wasm32")]
use nym_bandwidth_controller::wasm_mockups::DkgQueryClient;
@@ -151,31 +153,33 @@ impl From<bool> for CredentialsToggle {
}
}
pub struct BaseClientBuilder<'a, B, C, St: Storage> {
pub struct BaseClientBuilder<'a, B, C, Kst, St: Storage> {
// due to wasm limitations I had to split it like this : (
gateway_config: &'a GatewayEndpointConfig,
debug_config: &'a DebugConfig,
disabled_credentials: bool,
nym_api_endpoints: Vec<Url>,
reply_storage_backend: B,
key_store: Kst,
custom_topology_provider: Option<Box<dyn TopologyProvider>>,
bandwidth_controller: Option<BandwidthController<C, St>>,
key_manager: KeyManager,
managed_keys: ManagedKeys,
}
impl<'a, B, C, St> BaseClientBuilder<'a, B, C, St>
impl<'a, B, C, Kst, St> BaseClientBuilder<'a, B, C, Kst, St>
where
B: ReplyStorageBackend + Send + Sync + 'static,
C: DkgQueryClient + Sync + Send + 'static,
Kst: KeyStore,
St: Storage + 'static,
{
pub fn new_from_base_config<T>(
pub fn new_from_base_config<T, K>(
base_config: &'a Config<T>,
key_manager: KeyManager,
key_store: Kst,
bandwidth_controller: Option<BandwidthController<C, St>>,
reply_storage_backend: B,
) -> BaseClientBuilder<'a, B, C, St> {
) -> BaseClientBuilder<'a, B, C, Kst, St> {
BaseClientBuilder {
gateway_config: base_config.get_gateway_endpoint_config(),
debug_config: base_config.get_debug_config(),
@@ -183,7 +187,8 @@ where
nym_api_endpoints: base_config.get_nym_api_endpoints(),
bandwidth_controller,
reply_storage_backend,
key_manager,
key_store,
managed_keys: ManagedKeys::Invalidated,
custom_topology_provider: None,
}
}
@@ -191,12 +196,12 @@ where
pub fn new(
gateway_config: &'a GatewayEndpointConfig,
debug_config: &'a DebugConfig,
key_manager: KeyManager,
key_store: Kst,
bandwidth_controller: Option<BandwidthController<C, St>>,
reply_storage_backend: B,
credentials_toggle: CredentialsToggle,
nym_api_endpoints: Vec<Url>,
) -> BaseClientBuilder<'a, B, C, St> {
) -> BaseClientBuilder<'a, B, C, Kst, St> {
BaseClientBuilder {
gateway_config,
debug_config,
@@ -205,7 +210,8 @@ where
reply_storage_backend,
custom_topology_provider: None,
bandwidth_controller,
key_manager,
key_store,
managed_keys: ManagedKeys::Invalidated,
}
}
@@ -214,10 +220,12 @@ where
self
}
pub fn as_mix_recipient(&self) -> Recipient {
// note: do **NOT** make this method public as its only valid usage is from within `start_base`
// because it relies on the crypto keys being already loaded
fn as_mix_recipient(&self) -> Recipient {
Recipient::new(
*self.key_manager.identity_keypair().public_key(),
*self.key_manager.encryption_keypair().public_key(),
*self.managed_keys.identity_public_key(),
*self.managed_keys.encryption_public_key(),
// TODO: below only works under assumption that gateway address == gateway id
// (which currently is true)
NodeIdentity::from_base58_string(&self.gateway_config.gateway_id).unwrap(),
@@ -307,7 +315,10 @@ where
mixnet_message_sender: MixnetMessageSender,
ack_sender: AcknowledgementSender,
shutdown: TaskClient,
) -> Result<GatewayClient<C, St>, ClientCoreError> {
) -> Result<GatewayClient<C, St>, ClientCoreError>
where
Kst::StorageError: 'static,
{
let gateway_id = self.gateway_config.gateway_id.clone();
if gateway_id.is_empty() {
return Err(ClientCoreError::GatewayIdUnknown);
@@ -320,19 +331,11 @@ where
let gateway_identity = identity::PublicKey::from_base58_string(gateway_id)
.map_err(ClientCoreError::UnableToCreatePublicKeyFromGatewayId)?;
// disgusting wasm workaround since there's no key persistence there (nor `client init`)
let shared_key = if self.key_manager.is_gateway_key_set() {
Some(self.key_manager.gateway_shared_key())
} else {
log::info!("Gateway key not set! Will proceed anyway.");
None
};
let mut gateway_client = GatewayClient::new(
gateway_address,
self.key_manager.identity_keypair(),
self.managed_keys.identity_keypair(),
gateway_identity,
shared_key,
self.managed_keys.gateway_shared_key(),
mixnet_message_sender,
ack_sender,
self.debug_config
@@ -344,12 +347,20 @@ where
gateway_client.set_disabled_credentials_mode(self.disabled_credentials);
gateway_client
let shared_key = gateway_client
.authenticate_and_start()
.await
.tap_err(|err| {
log::error!("Could not authenticate and start up the gateway connection - {err}")
})?;
self.managed_keys
.deal_with_gateway_key(shared_key, &self.key_store)
.await
.map_err(|source| ClientCoreError::KeyStoreError {
source: Box::new(source),
})?;
Ok(gateway_client)
}
@@ -458,11 +469,20 @@ where
}
}
async fn initial_key_setup(&mut self) {
assert!(!self.managed_keys.is_valid());
let mut rng = thread_rng();
self.managed_keys = ManagedKeys::load_or_generate(&mut rng, &self.key_store).await;
}
pub async fn start_base(mut self) -> Result<BaseClient, ClientCoreError>
where
<B as ReplyStorageBackend>::StorageError: Sync + Send,
Kst::StorageError: 'static,
{
info!("Starting nym client");
self.initial_key_setup().await;
// channels for inter-component communication
// TODO: make the channels be internally created by the relevant components
// rather than creating them here, so say for example the buffer controller would create the request channels
@@ -516,7 +536,7 @@ where
.await?;
Self::start_received_messages_buffer_controller(
self.key_manager.encryption_keypair(),
self.managed_keys.encryption_keypair(),
received_buffer_request_receiver,
mixnet_messages_receiver,
reply_storage.key_storage(),
@@ -541,7 +561,7 @@ where
let controller_config = real_messages_control::Config::new(
self.debug_config,
self.key_manager.ack_key(),
self.managed_keys.ack_key(),
self_address,
);
@@ -566,8 +586,8 @@ where
{
Self::start_cover_traffic_stream(
self.debug_config,
self.key_manager.ack_key(),
self_address,
self.managed_keys.ack_key(),
self_address.clone(),
shared_topology_accessor.clone(),
sphinx_message_sender,
task_manager.subscribe(),
@@ -578,6 +598,7 @@ where
debug!("The address of this client is: {self_address}");
Ok(BaseClient {
address: self_address,
client_input: ClientInputStatus::AwaitingProducer {
client_input: ClientInput {
connection_command_sender: client_connection_tx,
@@ -600,6 +621,7 @@ where
}
pub struct BaseClient {
pub address: Recipient,
pub client_input: ClientInputStatus,
pub client_output: ClientOutputStatus,
pub client_state: ClientState,
@@ -1,255 +0,0 @@
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::persistence::key_pathfinder::ClientKeyPathfinder;
use async_trait::async_trait;
use log::*;
use nym_crypto::asymmetric::{encryption, identity};
use nym_gateway_requests::registration::handshake::SharedKeys;
use nym_sphinx::acknowledgements::AckKey;
use rand::{CryptoRng, RngCore};
use std::io;
use std::sync::Arc;
// Note: to support key rotation in the future, all keys will require adding an extra smart pointer,
// most likely an AtomicCell, or if it doesn't work as I think it does, a Mutex. Although I think
// AtomicCell includes a Mutex implicitly if the underlying type does not work atomically.
// And I guess there will need to be some mechanism for a grace period when you can still
// use the old key after new one was issued.
// Remember that Arc<T> has Deref implementation for T
#[derive(Clone)]
pub struct KeyManager {
/// identity key associated with the client instance.
identity_keypair: Arc<identity::KeyPair>,
/// encryption key associated with the client instance.
encryption_keypair: Arc<encryption::KeyPair>,
/// shared key derived with the gateway during "registration handshake"
gateway_shared_key: Option<Arc<SharedKeys>>,
/// key used for producing and processing acknowledgement packets.
ack_key: Arc<AckKey>,
}
// The expected flow of a KeyManager "lifetime" is as follows:
/*
1. ::new() is called during client-init
2. after gateway registration is completed [in init] ::insert_gateway_shared_key() is called
3. ::store_keys() is called before init finishes execution.
4. ::load_keys() is called at the beginning of each subsequent client-run
5. [not implemented] ::rotate_keys() is called periodically during client-run I presume?
*/
impl KeyManager {
/// Creates new instance of a [`KeyManager`]
pub fn new<R>(rng: &mut R) -> Self
where
R: RngCore + CryptoRng,
{
KeyManager {
identity_keypair: Arc::new(identity::KeyPair::new(rng)),
encryption_keypair: Arc::new(encryption::KeyPair::new(rng)),
gateway_shared_key: None,
ack_key: Arc::new(AckKey::new(rng)),
}
}
pub fn from_keys(
id_keypair: identity::KeyPair,
enc_keypair: encryption::KeyPair,
gateway_shared_key: SharedKeys,
ack_key: AckKey,
) -> Self {
Self {
identity_keypair: Arc::new(id_keypair),
encryption_keypair: Arc::new(enc_keypair),
gateway_shared_key: Some(Arc::new(gateway_shared_key)),
ack_key: Arc::new(ack_key),
}
}
/// Loads previously stored client keys from the disk.
fn load_client_keys(client_pathfinder: &ClientKeyPathfinder) -> io::Result<Self> {
let identity_keypair: identity::KeyPair =
nym_pemstore::load_keypair(&nym_pemstore::KeyPairPath::new(
client_pathfinder.private_identity_key().to_owned(),
client_pathfinder.public_identity_key().to_owned(),
))?;
let encryption_keypair: encryption::KeyPair =
nym_pemstore::load_keypair(&nym_pemstore::KeyPairPath::new(
client_pathfinder.private_encryption_key().to_owned(),
client_pathfinder.public_encryption_key().to_owned(),
))?;
let ack_key: AckKey = nym_pemstore::load_key(client_pathfinder.ack_key())?;
Ok(KeyManager {
identity_keypair: Arc::new(identity_keypair),
encryption_keypair: Arc::new(encryption_keypair),
gateway_shared_key: None,
ack_key: Arc::new(ack_key),
})
}
/// Loads previously stored keys from the disk. Fails if not all, including the shared gateway
/// key, is available.
pub fn load_keys_from_disk(client_pathfinder: &ClientKeyPathfinder) -> io::Result<Self> {
let mut key_manager = Self::load_client_keys(client_pathfinder)?;
let gateway_shared_key: SharedKeys =
nym_pemstore::load_key(client_pathfinder.gateway_shared_key())?;
key_manager.gateway_shared_key = Some(Arc::new(gateway_shared_key));
Ok(key_manager)
}
/// Loads previously stored keys from the disk. Fails if client keys are not available, but the
/// shared gateway key is optional.
pub fn load_keys_from_disk_but_gateway_is_optional(
client_pathfinder: &ClientKeyPathfinder,
) -> io::Result<Self> {
let mut key_manager = Self::load_client_keys(client_pathfinder)?;
let gateway_shared_key: Result<SharedKeys, io::Error> =
nym_pemstore::load_key(client_pathfinder.gateway_shared_key());
// It's ok if the gateway key was not found
let gateway_shared_key = match gateway_shared_key {
Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
Err(err) => Err(err),
Ok(key) => Ok(Some(key)),
}?;
key_manager.gateway_shared_key = gateway_shared_key.map(Arc::new);
Ok(key_manager)
}
/// Stores all available keys on the disk.
// While perhaps there is no much point in storing the `AckKey` on the disk,
// it is done so for the consistency sake so that you wouldn't require an rng instance
// during `load_keys` to generate the said key.
pub fn store_keys_on_disk(&self, client_pathfinder: &ClientKeyPathfinder) -> io::Result<()> {
nym_pemstore::store_keypair(
self.identity_keypair.as_ref(),
&nym_pemstore::KeyPairPath::new(
client_pathfinder.private_identity_key().to_owned(),
client_pathfinder.public_identity_key().to_owned(),
),
)?;
nym_pemstore::store_keypair(
self.encryption_keypair.as_ref(),
&nym_pemstore::KeyPairPath::new(
client_pathfinder.private_encryption_key().to_owned(),
client_pathfinder.public_encryption_key().to_owned(),
),
)?;
nym_pemstore::store_key(self.ack_key.as_ref(), client_pathfinder.ack_key())?;
match self.gateway_shared_key.as_ref() {
None => debug!("No gateway shared key available to store!"),
Some(gate_key) => {
nym_pemstore::store_key(gate_key.as_ref(), client_pathfinder.gateway_shared_key())?
}
}
Ok(())
}
pub fn store_gateway_key_on_disk(
&self,
client_pathfinder: &ClientKeyPathfinder,
) -> io::Result<()> {
match self.gateway_shared_key.as_ref() {
None => {
return Err(io::Error::new(
io::ErrorKind::Other,
"trying to store a non-existing key",
))
}
Some(gate_key) => {
nym_pemstore::store_key(gate_key.as_ref(), client_pathfinder.gateway_shared_key())?
}
}
Ok(())
}
/// Overwrite the existing identity keypair
pub fn set_identity_keypair(&mut self, id_keypair: identity::KeyPair) {
self.identity_keypair = Arc::new(id_keypair);
}
/// Gets an atomically reference counted pointer to [`identity::KeyPair`].
pub fn identity_keypair(&self) -> Arc<identity::KeyPair> {
Arc::clone(&self.identity_keypair)
}
/// Overwrite the existing encryption keypair
pub fn set_encryption_keypair(&mut self, enc_keypair: encryption::KeyPair) {
self.encryption_keypair = Arc::new(enc_keypair);
}
/// Gets an atomically reference counted pointer to [`encryption::KeyPair`].
pub fn encryption_keypair(&self) -> Arc<encryption::KeyPair> {
Arc::clone(&self.encryption_keypair)
}
/// Overwrite the existing ack key
pub fn set_ack_key(&mut self, ack_key: AckKey) {
self.ack_key = Arc::new(ack_key);
}
/// Gets an atomically reference counted pointer to [`AckKey`].
pub fn ack_key(&self) -> Arc<AckKey> {
Arc::clone(&self.ack_key)
}
/// After shared key with the gateway is derived, puts its ownership to this instance of a [`KeyManager`].
pub fn insert_gateway_shared_key(&mut self, gateway_shared_key: Arc<SharedKeys>) {
self.gateway_shared_key = Some(gateway_shared_key)
}
/// Gets an atomically reference counted pointer to [`SharedKey`].
// since this function is not fully public, it is not expected to be used externally and
// hence it's up to us to ensure it's called in correct context
pub fn gateway_shared_key(&self) -> Arc<SharedKeys> {
Arc::clone(
self.gateway_shared_key
.as_ref()
.expect("tried to unwrap empty gateway key!"),
)
}
pub fn is_gateway_key_set(&self) -> bool {
self.gateway_shared_key.is_some()
}
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
pub trait KeyStore {
type StorageError;
async fn load_keys(&self) -> Result<KeyManager, Self::StorageError>;
async fn store_keys(&self, keys: KeyManager) -> Result<(), Self::StorageError>;
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl KeyStore for ClientKeyPathfinder {
type StorageError = io::Error;
async fn load_keys(&self) -> Result<KeyManager, Self::StorageError> {
KeyManager::load_keys_from_disk(self)
}
async fn store_keys(&self, keys: KeyManager) -> Result<(), Self::StorageError> {
keys.store_keys_on_disk(self)
}
}
@@ -0,0 +1,420 @@
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::key_manager::persistence::KeyStore;
use nym_crypto::asymmetric::{encryption, identity};
use nym_gateway_requests::registration::handshake::SharedKeys;
use nym_sphinx::acknowledgements::AckKey;
use rand::{CryptoRng, RngCore};
use std::sync::Arc;
pub mod persistence;
pub enum ManagedKeys {
Initial(KeyManagerBuilder),
FullyDerived(KeyManager),
// I really hate the existence of this variant, but I couldn't come up with a better way to handle
// `Self::deal_with_gateway_key` otherwise.
Invalidated,
}
impl From<KeyManagerBuilder> for ManagedKeys {
fn from(value: KeyManagerBuilder) -> Self {
ManagedKeys::Initial(value)
}
}
impl From<KeyManager> for ManagedKeys {
fn from(value: KeyManager) -> Self {
ManagedKeys::FullyDerived(value)
}
}
impl ManagedKeys {
pub fn is_valid(&self) -> bool {
!matches!(self, ManagedKeys::Invalidated)
}
pub async fn must_load<S: KeyStore>(key_store: &S) -> Result<Self, S::StorageError> {
Ok(ManagedKeys::FullyDerived(
KeyManager::load_keys(key_store).await?,
))
}
pub async fn load_or_generate<R, S>(rng: &mut R, key_store: &S) -> Self
where
R: RngCore + CryptoRng,
S: KeyStore,
{
if let Ok(loaded) = KeyManager::load_keys(key_store).await {
ManagedKeys::FullyDerived(loaded)
} else {
ManagedKeys::Initial(KeyManagerBuilder::new(rng))
}
}
pub fn identity_keypair(&self) -> Arc<identity::KeyPair> {
match self {
ManagedKeys::Initial(keys) => keys.identity_keypair(),
ManagedKeys::FullyDerived(keys) => keys.identity_keypair(),
ManagedKeys::Invalidated => unreachable!("the managed keys got invalidated"),
}
}
pub fn encryption_keypair(&self) -> Arc<encryption::KeyPair> {
match self {
ManagedKeys::Initial(keys) => keys.encryption_keypair(),
ManagedKeys::FullyDerived(keys) => keys.encryption_keypair(),
ManagedKeys::Invalidated => unreachable!("the managed keys got invalidated"),
}
}
pub fn ack_key(&self) -> Arc<AckKey> {
match self {
ManagedKeys::Initial(keys) => keys.ack_key(),
ManagedKeys::FullyDerived(keys) => keys.ack_key(),
ManagedKeys::Invalidated => unreachable!("the managed keys got invalidated"),
}
}
pub fn gateway_shared_key(&self) -> Option<Arc<SharedKeys>> {
match self {
ManagedKeys::Initial(_) => None,
ManagedKeys::FullyDerived(keys) => Some(keys.gateway_shared_key()),
ManagedKeys::Invalidated => unreachable!("the managed keys got invalidated"),
}
}
pub fn identity_public_key(&self) -> &identity::PublicKey {
match self {
ManagedKeys::Initial(keys) => keys.identity_keypair.public_key(),
ManagedKeys::FullyDerived(keys) => keys.identity_keypair.public_key(),
ManagedKeys::Invalidated => unreachable!("the managed keys got invalidated"),
}
}
pub fn encryption_public_key(&self) -> &encryption::PublicKey {
match self {
ManagedKeys::Initial(keys) => keys.encryption_keypair.public_key(),
ManagedKeys::FullyDerived(keys) => keys.encryption_keypair.public_key(),
ManagedKeys::Invalidated => unreachable!("the managed keys got invalidated"),
}
}
pub async fn deal_with_gateway_key<S: KeyStore>(
&mut self,
gateway_shared_key: Arc<SharedKeys>,
key_store: &S,
) -> Result<(), S::StorageError> {
let key_manager = match std::mem::replace(self, ManagedKeys::Invalidated) {
ManagedKeys::Initial(keys) => {
let key_manager = keys.insert_gateway_shared_key(gateway_shared_key);
key_manager.persist_keys(key_store).await?;
key_manager
}
ManagedKeys::FullyDerived(key_manager) => {
if !Arc::ptr_eq(&key_manager.gateway_shared_key, &gateway_shared_key)
|| key_manager.gateway_shared_key != gateway_shared_key
{
// this should NEVER happen thus panic here
panic!("derived fresh gateway shared key whilst already holding one!")
}
key_manager
}
ManagedKeys::Invalidated => unreachable!("the managed keys got invalidated"),
};
*self = ManagedKeys::FullyDerived(key_manager);
Ok(())
}
}
// all of the keys really shouldn't be wrapped in `Arc`, but due to how the gateway client is currently
// constructed, changing that would require more work than what it's worth
pub struct KeyManagerBuilder {
/// identity key associated with the client instance.
identity_keypair: Arc<identity::KeyPair>,
/// encryption key associated with the client instance.
encryption_keypair: Arc<encryption::KeyPair>,
/// key used for producing and processing acknowledgement packets.
ack_key: Arc<AckKey>,
}
impl KeyManagerBuilder {
/// Creates new instance of a [`KeyManager`]
pub fn new<R>(rng: &mut R) -> Self
where
R: RngCore + CryptoRng,
{
KeyManagerBuilder {
identity_keypair: Arc::new(identity::KeyPair::new(rng)),
encryption_keypair: Arc::new(encryption::KeyPair::new(rng)),
ack_key: Arc::new(AckKey::new(rng)),
}
}
pub fn insert_gateway_shared_key(self, gateway_shared_key: Arc<SharedKeys>) -> KeyManager {
KeyManager {
identity_keypair: self.identity_keypair,
encryption_keypair: self.encryption_keypair,
gateway_shared_key,
ack_key: self.ack_key,
}
}
pub fn identity_keypair(&self) -> Arc<identity::KeyPair> {
Arc::clone(&self.identity_keypair)
}
pub fn encryption_keypair(&self) -> Arc<encryption::KeyPair> {
Arc::clone(&self.encryption_keypair)
}
pub fn ack_key(&self) -> Arc<AckKey> {
Arc::clone(&self.ack_key)
}
}
// Note: to support key rotation in the future, all keys will require adding an extra smart pointer,
// most likely an AtomicCell, or if it doesn't work as I think it does, a Mutex. Although I think
// AtomicCell includes a Mutex implicitly if the underlying type does not work atomically.
// And I guess there will need to be some mechanism for a grace period when you can still
// use the old key after new one was issued.
// Remember that Arc<T> has Deref implementation for T
#[derive(Clone)]
pub struct KeyManager {
/// identity key associated with the client instance.
identity_keypair: Arc<identity::KeyPair>,
/// encryption key associated with the client instance.
encryption_keypair: Arc<encryption::KeyPair>,
/// shared key derived with the gateway during "registration handshake"
gateway_shared_key: Arc<SharedKeys>,
/// key used for producing and processing acknowledgement packets.
ack_key: Arc<AckKey>,
}
// The expected flow of a KeyManager "lifetime" is as follows:
/*
1. ::new() is called during client-init
2. after gateway registration is completed [in init] ::insert_gateway_shared_key() is called
3. ::store_keys() is called before init finishes execution.
4. ::load_keys() is called at the beginning of each subsequent client-run
5. [not implemented] ::rotate_keys() is called periodically during client-run I presume?
*/
impl KeyManager {
// /// Creates new instance of a [`KeyManager`]
// pub fn new<R>(rng: &mut R) -> Self
// where
// R: RngCore + CryptoRng,
// {
// KeyManager {
// identity_keypair: Arc::new(identity::KeyPair::new(rng)),
// encryption_keypair: Arc::new(encryption::KeyPair::new(rng)),
// gateway_shared_key: None,
// ack_key: Arc::new(AckKey::new(rng)),
// }
// }
pub fn from_keys(
id_keypair: identity::KeyPair,
enc_keypair: encryption::KeyPair,
gateway_shared_key: SharedKeys,
ack_key: AckKey,
) -> Self {
Self {
identity_keypair: Arc::new(id_keypair),
encryption_keypair: Arc::new(enc_keypair),
gateway_shared_key: Arc::new(gateway_shared_key),
ack_key: Arc::new(ack_key),
}
}
pub async fn load_keys<S: KeyStore>(store: &S) -> Result<Self, S::StorageError> {
store.load_keys().await
}
pub async fn persist_keys<S: KeyStore>(&self, store: &S) -> Result<(), S::StorageError> {
store.store_keys(self).await
}
//
// /// Loads previously stored client keys from the disk.
// fn load_client_keys(client_pathfinder: &ClientKeyPathfinder) -> io::Result<Self> {
// let identity_keypair: identity::KeyPair =
// nym_pemstore::load_keypair(&nym_pemstore::KeyPairPath::new(
// client_pathfinder.private_identity_key().to_owned(),
// client_pathfinder.public_identity_key().to_owned(),
// ))?;
// let encryption_keypair: encryption::KeyPair =
// nym_pemstore::load_keypair(&nym_pemstore::KeyPairPath::new(
// client_pathfinder.private_encryption_key().to_owned(),
// client_pathfinder.public_encryption_key().to_owned(),
// ))?;
//
// let ack_key: AckKey = nym_pemstore::load_key(client_pathfinder.ack_key())?;
//
// Ok(KeyManager {
// identity_keypair: Arc::new(identity_keypair),
// encryption_keypair: Arc::new(encryption_keypair),
// gateway_shared_key: None,
// ack_key: Arc::new(ack_key),
// })
// }
//
// /// Loads previously stored keys from the disk. Fails if not all, including the shared gateway
// /// key, is available.
// pub fn load_keys_from_disk(client_pathfinder: &ClientKeyPathfinder) -> io::Result<Self> {
// let mut key_manager = Self::load_client_keys(client_pathfinder)?;
//
// let gateway_shared_key: SharedKeys =
// nym_pemstore::load_key(client_pathfinder.gateway_shared_key())?;
//
// key_manager.gateway_shared_key = Some(Arc::new(gateway_shared_key));
//
// Ok(key_manager)
// }
//
// /// Loads previously stored keys from the disk. Fails if client keys are not available, but the
// /// shared gateway key is optional.
// pub fn load_keys_from_disk_but_gateway_is_optional(
// client_pathfinder: &ClientKeyPathfinder,
// ) -> io::Result<Self> {
// let mut key_manager = Self::load_client_keys(client_pathfinder)?;
//
// let gateway_shared_key: Result<SharedKeys, io::Error> =
// nym_pemstore::load_key(client_pathfinder.gateway_shared_key());
//
// // It's ok if the gateway key was not found
// let gateway_shared_key = match gateway_shared_key {
// Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
// Err(err) => Err(err),
// Ok(key) => Ok(Some(key)),
// }?;
//
// key_manager.gateway_shared_key = gateway_shared_key.map(Arc::new);
//
// Ok(key_manager)
// }
//
// /// Stores all available keys on the disk.
// // While perhaps there is no much point in storing the `AckKey` on the disk,
// // it is done so for the consistency sake so that you wouldn't require an rng instance
// // during `load_keys` to generate the said key.
// pub fn store_keys_on_disk(&self, client_pathfinder: &ClientKeyPathfinder) -> io::Result<()> {
// nym_pemstore::store_keypair(
// self.identity_keypair.as_ref(),
// &nym_pemstore::KeyPairPath::new(
// client_pathfinder.private_identity_key().to_owned(),
// client_pathfinder.public_identity_key().to_owned(),
// ),
// )?;
// nym_pemstore::store_keypair(
// self.encryption_keypair.as_ref(),
// &nym_pemstore::KeyPairPath::new(
// client_pathfinder.private_encryption_key().to_owned(),
// client_pathfinder.public_encryption_key().to_owned(),
// ),
// )?;
//
// nym_pemstore::store_key(self.ack_key.as_ref(), client_pathfinder.ack_key())?;
//
// match self.gateway_shared_key.as_ref() {
// None => debug!("No gateway shared key available to store!"),
// Some(gate_key) => {
// nym_pemstore::store_key(gate_key.as_ref(), client_pathfinder.gateway_shared_key())?
// }
// }
//
// Ok(())
// }
//
// pub fn store_gateway_key_on_disk(
// &self,
// client_pathfinder: &ClientKeyPathfinder,
// ) -> io::Result<()> {
// match self.gateway_shared_key.as_ref() {
// None => {
// return Err(io::Error::new(
// io::ErrorKind::Other,
// "trying to store a non-existing key",
// ))
// }
// Some(gate_key) => {
// nym_pemstore::store_key(gate_key.as_ref(), client_pathfinder.gateway_shared_key())?
// }
// }
//
// Ok(())
// }
/// Overwrite the existing identity keypair
#[deprecated]
pub fn set_identity_keypair(&mut self, id_keypair: identity::KeyPair) {
self.identity_keypair = Arc::new(id_keypair);
}
/// Gets an atomically reference counted pointer to [`identity::KeyPair`].
pub fn identity_keypair(&self) -> Arc<identity::KeyPair> {
Arc::clone(&self.identity_keypair)
}
/// Overwrite the existing encryption keypair
#[deprecated]
pub fn set_encryption_keypair(&mut self, enc_keypair: encryption::KeyPair) {
self.encryption_keypair = Arc::new(enc_keypair);
}
/// Gets an atomically reference counted pointer to [`encryption::KeyPair`].
pub fn encryption_keypair(&self) -> Arc<encryption::KeyPair> {
Arc::clone(&self.encryption_keypair)
}
/// Overwrite the existing ack key
#[deprecated]
pub fn set_ack_key(&mut self, ack_key: AckKey) {
self.ack_key = Arc::new(ack_key);
}
/// Gets an atomically reference counted pointer to [`AckKey`].
pub fn ack_key(&self) -> Arc<AckKey> {
Arc::clone(&self.ack_key)
}
// /// After shared key with the gateway is derived, puts its ownership to this instance of a [`KeyManager`].
// pub fn insert_gateway_shared_key(&mut self, gateway_shared_key: Arc<SharedKeys>) {
// self.gateway_shared_key = Some(gateway_shared_key)
// }
/// Gets an atomically reference counted pointer to [`SharedKey`].
pub fn gateway_shared_key(&self) -> Arc<SharedKeys> {
Arc::clone(&self.gateway_shared_key)
}
// pub fn is_gateway_key_set(&self) -> bool {
// self.gateway_shared_key.is_some()
// }
}
#[cfg(test)]
mod tests {
use super::*;
use zeroize::ZeroizeOnDrop;
fn assert_zeroize_on_drop<T: ZeroizeOnDrop>() {}
#[test]
fn keys_zeroize_on_drop() {
assert_zeroize_on_drop::<identity::KeyPair>();
assert_zeroize_on_drop::<encryption::KeyPair>();
assert_zeroize_on_drop::<AckKey>();
assert_zeroize_on_drop::<SharedKeys>();
}
}
@@ -0,0 +1,115 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::key_manager::KeyManager;
use async_trait::async_trait;
use std::error::Error;
#[cfg(not(target_arch = "wasm32"))]
use crate::config::persistence::key_pathfinder::ClientKeyPathfinder;
#[cfg(not(target_arch = "wasm32"))]
use nym_crypto::asymmetric::{encryption, identity};
#[cfg(not(target_arch = "wasm32"))]
use nym_gateway_requests::registration::handshake::SharedKeys;
#[cfg(not(target_arch = "wasm32"))]
use nym_sphinx::acknowledgements::AckKey;
// we have to define it as an async trait since wasm storage is async
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
pub trait KeyStore {
type StorageError: Error;
async fn load_keys(&self) -> Result<KeyManager, Self::StorageError>;
async fn store_keys(&self, keys: &KeyManager) -> Result<(), Self::StorageError>;
}
#[cfg(not(target_arch = "wasm32"))]
pub struct OnDiskKeys<'a> {
pathfinder: &'a ClientKeyPathfinder,
}
#[cfg(not(target_arch = "wasm32"))]
impl<'a> From<&'a ClientKeyPathfinder> for OnDiskKeys<'a> {
fn from(pathfinder: &'a ClientKeyPathfinder) -> Self {
OnDiskKeys { pathfinder }
}
}
#[cfg(not(target_arch = "wasm32"))]
impl<'a> OnDiskKeys<'a> {
pub fn new(pathfinder: &'a ClientKeyPathfinder) -> Self {
OnDiskKeys { pathfinder }
}
fn load_keys(&self) -> Result<KeyManager, std::io::Error> {
let identity_keypair: identity::KeyPair =
nym_pemstore::load_keypair(&self.pathfinder.identity_key_pair_path())?;
let encryption_keypair: encryption::KeyPair =
nym_pemstore::load_keypair(&self.pathfinder.encryption_key_pair_path())?;
let ack_key: AckKey = nym_pemstore::load_key(self.pathfinder.ack_key())?;
let gateway_shared_key: SharedKeys =
nym_pemstore::load_key(self.pathfinder.gateway_shared_key())?;
Ok(KeyManager::from_keys(
identity_keypair,
encryption_keypair,
gateway_shared_key,
ack_key,
))
}
fn store_keys(&self, keys: &KeyManager) -> Result<(), std::io::Error> {
nym_pemstore::store_keypair(
keys.identity_keypair.as_ref(),
&self.pathfinder.identity_key_pair_path(),
)?;
nym_pemstore::store_keypair(
keys.encryption_keypair.as_ref(),
&self.pathfinder.encryption_key_pair_path(),
)?;
nym_pemstore::store_key(
keys.gateway_shared_key.as_ref(),
self.pathfinder.gateway_shared_key(),
)?;
nym_pemstore::store_key(keys.ack_key.as_ref(), self.pathfinder.ack_key())?;
Ok(())
}
}
#[cfg(not(target_arch = "wasm32"))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl<'a> KeyStore for OnDiskKeys<'a> {
type StorageError = std::io::Error;
async fn load_keys(&self) -> Result<KeyManager, Self::StorageError> {
self.load_keys()
}
async fn store_keys(&self, keys: &KeyManager) -> Result<(), Self::StorageError> {
self.store_keys(keys)
}
}
pub struct InMemEphemeralKeys;
#[derive(Debug, thiserror::Error)]
#[error("ephemeral keys can't be loaded from storage")]
pub struct EphemeralKeysError;
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl KeyStore for InMemEphemeralKeys {
type StorageError = EphemeralKeysError;
async fn load_keys(&self) -> Result<KeyManager, Self::StorageError> {
Err(EphemeralKeysError)
}
async fn store_keys(&self, _keys: &KeyManager) -> Result<(), Self::StorageError> {
Ok(())
}
}
@@ -40,6 +40,14 @@ impl ClientKeyPathfinder {
}
}
pub fn identity_key_pair_path(&self) -> nym_pemstore::KeyPairPath<&Path> {
nym_pemstore::KeyPairPath::new(self.private_identity_key(), self.public_identity_key())
}
pub fn encryption_key_pair_path(&self) -> nym_pemstore::KeyPairPath<&Path> {
nym_pemstore::KeyPairPath::new(self.private_encryption_key(), self.public_encryption_key())
}
pub fn any_file_exists(&self) -> bool {
matches!(self.identity_public_key.try_exists(), Ok(true))
|| matches!(self.identity_private_key.try_exists(), Ok(true))
+5 -1
View File
@@ -6,6 +6,7 @@ use nym_gateway_client::error::GatewayClientError;
use nym_topology::gateway::GatewayConversionError;
use nym_topology::NymTopologyError;
use nym_validator_client::ValidatorClientError;
use std::error::Error;
#[derive(thiserror::Error, Debug)]
pub enum ClientCoreError {
@@ -41,9 +42,12 @@ pub enum ClientCoreError {
#[error("experienced a failure with our reply surb persistent storage: {source}")]
SurbStorageError {
source: Box<dyn std::error::Error + Send + Sync>,
source: Box<dyn Error + Send + Sync>,
},
#[error("experienced a failure with our cryptographic keys persistent storage: {source}")]
KeyStoreError { source: Box<dyn Error> },
#[error("The gateway id is invalid - {0}")]
UnableToCreatePublicKeyFromGatewayId(Ed25519RecoveryError),
+16 -11
View File
@@ -1,14 +1,10 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{
client::key_manager::KeyManager,
config::{persistence::key_pathfinder::ClientKeyPathfinder, Config},
error::ClientCoreError,
};
use crate::error::ClientCoreError;
use futures::{SinkExt, StreamExt};
use log::{debug, info, trace, warn};
use nym_config::NymConfig;
use nym_credential_storage::storage::Storage;
use nym_crypto::asymmetric::identity;
use nym_gateway_client::GatewayClient;
use nym_gateway_requests::registration::handshake::SharedKeys;
@@ -29,10 +25,17 @@ use tokio::time::Instant;
use tokio_tungstenite::connect_async;
#[cfg(not(target_arch = "wasm32"))]
use tokio_tungstenite::{MaybeTlsStream, WebSocketStream};
#[cfg(not(target_arch = "wasm32"))]
type WsConn = WebSocketStream<MaybeTlsStream<TcpStream>>;
use nym_credential_storage::storage::Storage;
#[cfg(not(target_arch = "wasm32"))]
use crate::client::key_manager::persistence::OnDiskKeys;
#[cfg(not(target_arch = "wasm32"))]
use crate::{
client::key_manager::KeyManager,
config::{persistence::key_pathfinder::ClientKeyPathfinder, Config},
};
#[cfg(not(target_arch = "wasm32"))]
use nym_config::NymConfig;
#[cfg(target_arch = "wasm32")]
use nym_bandwidth_controller::wasm_mockups::DirectSigningNyxdClient;
@@ -246,7 +249,8 @@ pub(super) async fn register_with_gateway<St: Storage>(
Ok(shared_keys)
}
pub(super) fn store_keys<T>(
#[cfg(not(target_arch = "wasm32"))]
pub(super) async fn store_keys_on_disk<T>(
key_manager: &KeyManager,
config: &Config<T>,
) -> Result<(), ClientCoreError>
@@ -255,6 +259,7 @@ where
{
let pathfinder = ClientKeyPathfinder::new_from_config(config);
Ok(key_manager
.store_keys_on_disk(&pathfinder)
.persist_keys(&OnDiskKeys::new(&pathfinder))
.await
.tap_err(|err| log::error!("Failed to generate keys: {err}"))?)
}
+14 -13
View File
@@ -15,7 +15,7 @@ use nym_credential_storage::storage::Storage;
use nym_crypto::asymmetric::{encryption, identity};
use url::Url;
use crate::client::key_manager::KeyManager;
use crate::client::key_manager::{KeyManager, KeyManagerBuilder};
use crate::{
config::{
persistence::key_pathfinder::ClientKeyPathfinder, ClientCoreConfigTrait, Config,
@@ -65,9 +65,9 @@ impl Display for InitResults {
}
/// Create a new set of client keys.
pub fn new_client_keys() -> KeyManager {
pub fn new_client_keys() -> KeyManagerBuilder {
let mut rng = OsRng;
KeyManager::new(&mut rng)
KeyManagerBuilder::new(&mut rng)
}
/// Authenticate and register with a gateway.
@@ -75,23 +75,23 @@ pub fn new_client_keys() -> KeyManager {
/// chosen one if it's among the available ones.
/// The shared key is added to the supplied `KeyManager` and the endpoint details are returned.
pub async fn register_with_gateway<St: Storage>(
key_manager: &mut KeyManager,
key_manager_builder: KeyManagerBuilder,
nym_api_endpoints: Vec<Url>,
chosen_gateway_id: Option<identity::PublicKey>,
by_latency: bool,
) -> Result<GatewayEndpointConfig, ClientCoreError> {
) -> Result<(GatewayEndpointConfig, KeyManager), ClientCoreError> {
// Get the gateway details of the gateway we will use
let gateway =
helpers::query_gateway_details(nym_api_endpoints, chosen_gateway_id, by_latency).await?;
log::debug!("Querying gateway gives: {}", gateway);
log::debug!("Querying gateway gives: {gateway}");
let our_identity = key_manager.identity_keypair();
let our_identity = key_manager_builder.identity_keypair();
// Establish connection, authenticate and generate keys for talking with the gateway
let shared_keys = helpers::register_with_gateway::<St>(&gateway, our_identity).await?;
key_manager.insert_gateway_shared_key(shared_keys);
let key_manager = key_manager_builder.insert_gateway_shared_key(shared_keys);
Ok(gateway.into())
Ok((gateway.into(), key_manager))
}
/// Convenience function for setting up the gateway for a client given a `Config`. Depending on the
@@ -101,6 +101,7 @@ pub async fn register_with_gateway<St: Storage>(
/// b. Create a new gateway configuration but keep existing keys. This assumes that the caller
/// knows what they are doing and that the keys match the requested gateway.
/// c. Create a new gateway configuration with a newly registered gateway and keys.
#[cfg(not(target_arch = "wasm32"))]
pub async fn setup_gateway_from_config<C, T, St>(
register_gateway: bool,
user_chosen_gateway_id: Option<identity::PublicKey>,
@@ -138,17 +139,17 @@ where
}
// Create new keys and derive our identity
let mut key_manager = new_client_keys();
let our_identity = key_manager.identity_keypair();
let key_manager_builder = new_client_keys();
let our_identity = key_manager_builder.identity_keypair();
// Establish connection, authenticate and generate keys for talking with the gateway
eprintln!("Registering with new gateway");
let shared_keys = helpers::register_with_gateway::<St>(&gateway, our_identity).await?;
key_manager.insert_gateway_shared_key(shared_keys);
let key_manager = key_manager_builder.insert_gateway_shared_key(shared_keys);
// Write all keys to storage and just return the gateway endpoint config. It is assumed that we
// will load keys from storage when actually connecting.
helpers::store_keys(&key_manager, config)?;
helpers::store_keys_on_disk(&key_manager, config).await?;
Ok(gateway.into())
}
@@ -79,6 +79,7 @@ where
gateway_address: String,
local_identity: Arc<identity::KeyPair>,
gateway_identity: identity::PublicKey,
// TODO: make it mandatory. if you don't want to pass it, use `new_init`
shared_key: Option<Arc<SharedKeys>>,
mixnet_message_sender: MixnetMessageSender,
ack_sender: AcknowledgementSender,
+3 -2
View File
@@ -1,16 +1,17 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::GatewayClientError;
pub use client::GatewayClient;
use log::warn;
use nym_gateway_requests::registration::handshake::SharedKeys;
use nym_gateway_requests::BinaryResponse;
pub use packet_router::{
AcknowledgementReceiver, AcknowledgementSender, MixnetMessageReceiver, MixnetMessageSender,
};
use tungstenite::{protocol::Message, Error as WsError};
pub use nym_gateway_requests::registration::handshake::SharedKeys;
pub mod client;
pub mod error;
pub mod packet_router;
+3 -3
View File
@@ -13,7 +13,7 @@ bs58 = "0.4.0"
blake3 = { version = "1.3.1", features = ["traits-preview"], optional = true }
ctr = { version = "0.9.1", optional = true }
digest = { version = "0.10.3", optional = true }
generic-array = { version = "0.14", optional = true }
generic-array = { workspace = true, optional = true }
hkdf = { version = "0.12.3", optional = true }
hmac = { version = "0.12.1", optional = true }
cipher = { version = "0.4.3", optional = true }
@@ -24,7 +24,7 @@ serde_bytes = { version = "0.11.6", optional = true }
serde_crate = { version = "1.0", optional = true, default_features = false, features = ["derive"], package = "serde" }
subtle-encoding = { version = "0.5", features = ["bech32-preview"]}
thiserror = "1.0.37"
zeroize = { version = "1.5.7", optional = true, features = ["zeroize_derive"] }
zeroize = { workspace = true, optional = true, features = ["zeroize_derive"] }
# internal
nym-sphinx-types = { path = "../nymsphinx/types", version = "0.2.0" }
@@ -35,6 +35,6 @@ rand_chacha = "0.2"
[features]
serde = ["serde_crate", "serde_bytes", "ed25519-dalek/serde", "x25519-dalek/serde"]
asymmetric = ["x25519-dalek", "ed25519-dalek"]
asymmetric = ["x25519-dalek", "ed25519-dalek", "zeroize"]
hashing = ["blake3", "digest", "hkdf", "hmac", "generic-array"]
symmetric = ["aes", "ctr", "cipher", "generic-array"]
@@ -5,6 +5,7 @@ use nym_pemstore::traits::{PemStorableKey, PemStorableKeyPair};
use std::fmt::{self, Display, Formatter};
use std::str::FromStr;
use thiserror::Error;
use zeroize::{Zeroize, ZeroizeOnDrop};
#[cfg(feature = "rand")]
use rand::{CryptoRng, RngCore};
@@ -41,10 +42,14 @@ pub enum KeyRecoveryError {
},
}
#[derive(Zeroize, ZeroizeOnDrop)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(crate = "serde_crate"))]
pub struct KeyPair {
pub(crate) private_key: PrivateKey,
// nothing secret about public key
#[zeroize(skip)]
pub(crate) public_key: PublicKey,
}
@@ -180,6 +185,7 @@ impl PemStorableKey for PublicKey {
}
}
#[derive(Zeroize, ZeroizeOnDrop)]
pub struct PrivateKey(x25519_dalek::StaticSecret);
impl Display for PrivateKey {
+6 -2
View File
@@ -9,6 +9,7 @@ use nym_sphinx_types::{DestinationAddressBytes, DESTINATION_ADDRESS_LENGTH};
use std::fmt::{self, Display, Formatter};
use std::str::FromStr;
use thiserror::Error;
use zeroize::{Zeroize, ZeroizeOnDrop};
#[cfg(feature = "rand")]
use rand::{CryptoRng, RngCore};
@@ -44,11 +45,14 @@ pub enum Ed25519RecoveryError {
}
/// Keypair for usage in ed25519 EdDSA.
#[derive(Debug)]
#[derive(Debug, Zeroize, ZeroizeOnDrop)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(crate = "serde_crate"))]
pub struct KeyPair {
private_key: PrivateKey,
// nothing secret about public key
#[zeroize(skip)]
public_key: PublicKey,
}
@@ -191,7 +195,7 @@ impl PemStorableKey for PublicKey {
}
/// ed25519 EdDSA Private Key
#[derive(Debug)]
#[derive(Debug, Zeroize, ZeroizeOnDrop)]
pub struct PrivateKey(ed25519_dalek::SecretKey);
impl Display for PrivateKey {
+6 -2
View File
@@ -9,7 +9,10 @@ repository = { workspace = true }
[dependencies]
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
thiserror = "1.0.37"
serde_crate = { version = "1.0", optional = true, default_features = false, features = ["derive"], package = "serde" }
generic-array = { workspace = true, optional = true, features = ["serde"] }
thiserror = { workspace = true }
zeroize = { workspace = true }
nym-crypto = { path = "../../crypto", features = ["symmetric", "rand"] }
nym-sphinx-addressing = { path = "../addressing" }
@@ -18,4 +21,5 @@ nym-sphinx-types = { path = "../types" }
nym-pemstore = { path = "../../pemstore" }
nym-topology = { path = "../../topology" }
[features]
serde = ["serde_crate", "generic-array"]
@@ -6,7 +6,14 @@ use nym_pemstore::traits::PemStorableKey;
use nym_sphinx_params::AckEncryptionAlgorithm;
use rand::{CryptoRng, RngCore};
use std::fmt::{self, Display, Formatter};
use zeroize::{Zeroize, ZeroizeOnDrop};
#[cfg(feature = "serde")]
use serde_crate::{Deserialize, Serialize};
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(crate = "serde_crate"))]
#[derive(Zeroize, ZeroizeOnDrop)]
pub struct AckKey(CipherKey<AckEncryptionAlgorithm>);
#[derive(Debug)]
+21 -17
View File
@@ -1,21 +1,21 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::traits::{PemStorableKey, PemStorableKeyPair};
use pem::{self, Pem};
use std::fs::File;
use std::io::{self, Read, Write};
use std::path::{Path, PathBuf};
use std::path::Path;
pub mod traits;
pub struct KeyPairPath {
private_key_path: PathBuf,
public_key_path: PathBuf,
pub struct KeyPairPath<P: AsRef<Path>> {
private_key_path: P,
public_key_path: P,
}
impl KeyPairPath {
pub fn new(private_key_path: PathBuf, public_key_path: PathBuf) -> Self {
impl<P: AsRef<Path>> KeyPairPath<P> {
pub fn new(private_key_path: P, public_key_path: P) -> Self {
KeyPairPath {
private_key_path,
public_key_path,
@@ -23,26 +23,29 @@ impl KeyPairPath {
}
}
pub fn load_keypair<T>(paths: &KeyPairPath) -> io::Result<T>
pub fn load_keypair<T, P>(paths: &KeyPairPath<P>) -> io::Result<T>
where
T: PemStorableKeyPair,
P: AsRef<Path>,
{
let private = load_key::<T::PrivatePemKey>(&paths.private_key_path)?;
let public = load_key::<T::PublicPemKey>(&paths.public_key_path)?;
let private: T::PrivatePemKey = load_key(&paths.private_key_path)?;
let public: T::PublicPemKey = load_key(&paths.public_key_path)?;
Ok(T::from_keys(private, public))
}
pub fn store_keypair<T>(keypair: &T, paths: &KeyPairPath) -> io::Result<()>
pub fn store_keypair<T, P>(keypair: &T, paths: &KeyPairPath<P>) -> io::Result<()>
where
T: PemStorableKeyPair,
P: AsRef<Path>,
{
store_key(keypair.public_key(), &paths.public_key_path)?;
store_key(keypair.private_key(), &paths.private_key_path)
}
pub fn load_key<T>(path: &Path) -> io::Result<T>
pub fn load_key<T, P>(path: P) -> io::Result<T>
where
T: PemStorableKey,
P: AsRef<Path>,
{
let key_pem = read_pem_file(path)?;
@@ -61,23 +64,24 @@ where
Ok(key)
}
pub fn store_key<T>(key: &T, path: &Path) -> io::Result<()>
pub fn store_key<T, P>(key: &T, path: P) -> io::Result<()>
where
T: PemStorableKey,
P: AsRef<Path>,
{
write_pem_file(path, key.to_bytes(), T::pem_type())
}
fn read_pem_file(filepath: &Path) -> io::Result<Pem> {
fn read_pem_file<P: AsRef<Path>>(filepath: P) -> io::Result<Pem> {
let mut pem_bytes = File::open(filepath)?;
let mut buf = Vec::new();
pem_bytes.read_to_end(&mut buf)?;
pem::parse(&buf).map_err(|e| io::Error::new(io::ErrorKind::Other, e))
}
fn write_pem_file(filepath: &Path, data: Vec<u8>, tag: &str) -> io::Result<()> {
fn write_pem_file<P: AsRef<Path>>(filepath: P, data: Vec<u8>, tag: &str) -> io::Result<()> {
// ensure the whole directory structure exists
if let Some(parent_dir) = filepath.parent() {
if let Some(parent_dir) = filepath.as_ref().parent() {
std::fs::create_dir_all(parent_dir)?;
}
let pem = Pem {
@@ -86,7 +90,7 @@ fn write_pem_file(filepath: &Path, data: Vec<u8>, tag: &str) -> io::Result<()> {
};
let key = pem::encode(&pem);
let mut file = File::create(filepath)?;
let mut file = File::create(filepath.as_ref())?;
file.write_all(key.as_bytes())?;
// note: this is only supported on unix (on different systems, like Windows, it will just
+1 -1
View File
@@ -8,7 +8,7 @@ edition = "2021"
[dependencies]
aes-gcm = { version = "0.10.1" }
argon2 = { version = "0.5.0" }
generic-array = { version = "0.14", features = ["zeroize"] }
generic-array = { workspace = true, features = ["zeroize"] }
rand = "0.8.5"
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true, optional = true }
+3 -1
View File
@@ -12,15 +12,17 @@ edition = "2021"
[dependencies]
bs58 = "0.4.0"
futures = "0.3.15"
generic-array = { workspace = true, features = ["serde"] }
log = { workspace = true }
nym-sphinx = { path = "../../common/nymsphinx" }
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
thiserror = "1.0"
zeroize = { workspace = true }
nym-crypto = { path = "../../common/crypto" }
nym-pemstore = { path = "../../common/pemstore" }
nym-sphinx = { path = "../../common/nymsphinx" }
nym-coconut-interface = { path = "../../common/coconut-interface" }
nym-credentials = { path = "../../common/credentials" }
@@ -1,4 +1,4 @@
// Copyright 2020-2022 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{GatewayMacSize, GatewayRequestsError};
@@ -10,7 +10,9 @@ 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::{GatewayEncryptionAlgorithm, GatewayIntegrityHmacAlgorithm};
use serde::{Deserialize, Serialize};
use thiserror::Error;
use zeroize::{Zeroize, ZeroizeOnDrop};
// shared key is as long as the encryption key and the MAC key combined.
pub type SharedKeySize = Sum<EncryptionKeySize, MacKeySize>;
@@ -22,7 +24,8 @@ type EncryptionKeySize = <GatewayEncryptionAlgorithm as KeySizeUser>::KeySize;
/// Shared key used when computing MAC for messages exchanged between client and its gateway.
pub type MacKey = GenericArray<u8, MacKeySize>;
#[derive(Clone, Copy, Debug)]
// #[derive(Clone, Copy, Debug, PartialEq)]
#[derive(Debug, PartialEq, Serialize, Deserialize, Zeroize, ZeroizeOnDrop)]
pub struct SharedKeys {
encryption_key: CipherKey<GatewayEncryptionAlgorithm>,
mac_key: MacKey,
+15 -14
View File
@@ -21,7 +21,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
dependencies = [
"crypto-common",
"generic-array 0.14.6",
"generic-array 0.14.7",
]
[[package]]
@@ -302,7 +302,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
dependencies = [
"block-padding",
"generic-array 0.14.6",
"generic-array 0.14.7",
]
[[package]]
@@ -311,7 +311,7 @@ version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e"
dependencies = [
"generic-array 0.14.6",
"generic-array 0.14.7",
]
[[package]]
@@ -503,7 +503,7 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7"
dependencies = [
"generic-array 0.14.6",
"generic-array 0.14.7",
]
[[package]]
@@ -814,7 +814,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21"
dependencies = [
"generic-array 0.14.6",
"generic-array 0.14.7",
"rand_core 0.6.4",
"subtle 2.4.1",
"zeroize",
@@ -826,7 +826,7 @@ version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array 0.14.6",
"generic-array 0.14.7",
"rand_core 0.6.4",
"typenum",
]
@@ -847,7 +847,7 @@ version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714"
dependencies = [
"generic-array 0.14.6",
"generic-array 0.14.7",
"subtle 2.4.1",
]
@@ -1081,7 +1081,7 @@ version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
dependencies = [
"generic-array 0.14.6",
"generic-array 0.14.7",
]
[[package]]
@@ -1241,7 +1241,7 @@ dependencies = [
"crypto-bigint",
"der",
"ff",
"generic-array 0.14.6",
"generic-array 0.14.7",
"group",
"rand_core 0.6.4",
"sec1",
@@ -1645,9 +1645,9 @@ dependencies = [
[[package]]
name = "generic-array"
version = "0.14.6"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
@@ -2267,7 +2267,7 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
dependencies = [
"generic-array 0.14.6",
"generic-array 0.14.7",
]
[[package]]
@@ -2913,6 +2913,7 @@ dependencies = [
"subtle-encoding",
"thiserror",
"x25519-dalek",
"zeroize",
]
[[package]]
@@ -3027,7 +3028,7 @@ version = "0.1.0"
dependencies = [
"aes-gcm",
"argon2",
"generic-array 0.14.6",
"generic-array 0.14.7",
"getrandom 0.2.8",
"rand 0.8.5",
"serde",
@@ -4246,7 +4247,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08da66b8b0965a5555b6bd6639e68ccba85e1e2506f5fbb089e93f8a04e1a2d1"
dependencies = [
"der",
"generic-array 0.14.6",
"generic-array 0.14.7",
"pkcs8",
"subtle 2.4.1",
"zeroize",
+3
View File
@@ -76,6 +76,9 @@ pub enum Error {
StorageError {
source: Box<dyn std::error::Error + Send + Sync>,
},
#[error("loaded shared gateway key without providing information about what gateway it corresponds to")]
GatewayWithUnknownEndpoint,
}
pub type Result<T, E = Error> = std::result::Result<T, E>;
+20 -14
View File
@@ -5,6 +5,7 @@ use url::Url;
use nym_bandwidth_controller::BandwidthController;
use nym_client_core::client::base_client::BaseClient;
use nym_client_core::client::key_manager::persistence::OnDiskKeys;
use nym_client_core::config::DebugConfig;
use nym_client_core::{
client::{
@@ -131,6 +132,7 @@ impl MixnetClientBuilder {
self.socks5_config,
storage_paths,
self.custom_topology_provider,
&self.gateway_config,
)
.await?;
@@ -157,9 +159,6 @@ pub struct DisconnectedMixnetClient<B>
where
B: ReplyStorageBackend + Sync + Send + 'static,
{
/// Keys handled by the client
key_manager: KeyManager,
/// Client configuration
config: Config,
@@ -199,6 +198,7 @@ where
socks5_config: Option<Socks5>,
paths: Option<StoragePaths>,
custom_topology_provider: Option<Box<dyn TopologyProvider>>,
gateway_config: &Option<GatewayEndpointConfig>,
) -> Result<DisconnectedMixnetClient<B>>
where
<B as ReplyStorageBackend>::StorageError: Send + Sync,
@@ -219,14 +219,22 @@ where
// If we are provided paths to keys, use them if they are available. And if they are
// not, write the generated keys back to storage.
let key_manager = if let Some(ref paths) = paths {
let state = if let Some(ref paths) = paths {
let path_finder = ClientKeyPathfinder::from(paths.clone());
let keys_store = OnDiskKeys::new(&path_finder);
// Try load keys
match KeyManager::load_keys_from_disk_but_gateway_is_optional(&path_finder) {
match KeyManager::load_keys(&keys_store).await {
Ok(key_manager) => {
log::debug!("Keys loaded");
key_manager
let Some(gateway_endpoint) = gateway_config else {
return Err(Error::GatewayWithUnknownEndpoint)
};
BuilderState::Registered {
derived_keys: key_manager,
gateway_endpoint_config: gateway_endpoint.clone(),
}
}
Err(err) => {
log::debug!("Not loading keys: {err}");
@@ -240,25 +248,23 @@ where
// know this is a bit defensive, but I don't want to overwrite
assert!(!(path_finder.any_file_exists() && paths.operating_mode.is_keep()));
// Create new keys and write to storage
let key_manager = nym_client_core::init::new_client_keys();
// WARN: this will overwrite!
key_manager.store_keys_on_disk(&path_finder)?;
key_manager
// Create new keys (don't write to storage yet!)
let initial_keys = nym_client_core::init::new_client_keys();
BuilderState::New { initial_keys }
}
}
} else {
// Ephemeral keys that we only store in memory
log::debug!("Creating new ephemeral keys");
nym_client_core::init::new_client_keys()
let initial_keys = nym_client_core::init::new_client_keys();
BuilderState::New { initial_keys }
};
Ok(DisconnectedMixnetClient {
key_manager,
config,
socks5_config,
storage_paths: paths,
state: BuilderState::New,
state,
reply_storage_backend,
bandwidth_controller,
custom_topology_provider,
@@ -1,9 +1,16 @@
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_client_core::client::key_manager::{KeyManager, KeyManagerBuilder};
use nym_client_core::config::GatewayEndpointConfig;
#[derive(Debug, PartialEq, Eq)]
pub(super) enum BuilderState {
New,
New {
initial_keys: KeyManagerBuilder,
},
Registered {
derived_keys: KeyManager,
gateway_endpoint_config: GatewayEndpointConfig,
},
}