persistent wasm client keys
This commit is contained in:
Generated
+7
@@ -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]]
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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!
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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";
|
||||
@@ -85,6 +85,9 @@ pub enum WasmClientError {
|
||||
#[from]
|
||||
source: StorageError,
|
||||
},
|
||||
|
||||
#[error("{typ} cryptographic is not available in storage")]
|
||||
CryptoKeyNotInStorage { typ: String },
|
||||
}
|
||||
|
||||
impl WasmClientError {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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),
|
||||
|
||||
|
||||
@@ -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}"))?)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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,
|
||||
|
||||
Generated
+15
-14
@@ -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",
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user