From acebcb79478f809fc871f15e87e78ef2b895455d Mon Sep 17 00:00:00 2001 From: John Smith Date: Fri, 23 Jun 2023 21:12:48 -0400 Subject: [PATCH] network keying --- doc/config/sample.config | 3 +- doc/config/veilid-server-config.md | 5 +- veilid-core/src/network_manager/mod.rs | 84 +++++++++++++++++-- .../src/network_manager/native/network_udp.rs | 3 +- .../src/network_manager/network_connection.rs | 4 +- .../src/tests/common/test_veilid_config.rs | 2 + veilid-core/src/veilid_api/tests/fixtures.rs | 1 + veilid-core/src/veilid_config.rs | 2 + veilid-flutter/lib/veilid_config.dart | 4 + veilid-python/poetry_install.sh | 1 + veilid-python/tests/test_routing_context.py | 15 ++-- veilid-python/veilid/config.py | 1 + veilid-server/src/cmdline.rs | 8 ++ veilid-server/src/settings.rs | 7 ++ 14 files changed, 119 insertions(+), 21 deletions(-) diff --git a/doc/config/sample.config b/doc/config/sample.config index 41a6a2f7..b59cd7c2 100644 --- a/doc/config/sample.config +++ b/doc/config/sample.config @@ -48,7 +48,8 @@ core: max_connection_frequency_per_min: 128 client_whitelist_timeout_ms: 300000 reverse_connection_receipt_time_ms: 5000 - hole_punch_receipt_time_ms: 5000 + hole_punch_receipt_time_ms: 5000 + network_key_password: null routing_table: node_id: null node_id_secret: null diff --git a/doc/config/veilid-server-config.md b/doc/config/veilid-server-config.md index a76b328b..116d2dec 100644 --- a/doc/config/veilid-server-config.md +++ b/doc/config/veilid-server-config.md @@ -188,8 +188,9 @@ network: client_whitelist_timeout_ms: 300000 reverse_connection_receipt_time_ms: 5000 hole_punch_receipt_time_ms: 5000 - node_id: '' - node_id_secret: '' + network_key_password: null + node_id: null + node_id_secret: null bootstrap: ['bootstrap.veilid.net'] upnp: true detect_address_changes: true diff --git a/veilid-core/src/network_manager/mod.rs b/veilid-core/src/network_manager/mod.rs index 68623a9e..f410ca31 100644 --- a/veilid-core/src/network_manager/mod.rs +++ b/veilid-core/src/network_manager/mod.rs @@ -160,6 +160,8 @@ struct NetworkManagerUnlockedInner { // Background processes rolling_transfers_task: TickTask, public_address_check_task: TickTask, + // Network Key + network_key: Option, } #[derive(Clone)] @@ -185,6 +187,7 @@ impl NetworkManager { #[cfg(feature="unstable-blockstore")] block_store: BlockStore, crypto: Crypto, + network_key: Option, ) -> NetworkManagerUnlockedInner { NetworkManagerUnlockedInner { config, @@ -199,6 +202,7 @@ impl NetworkManager { update_callback: RwLock::new(None), rolling_transfers_task: TickTask::new(ROLLING_TRANSFERS_INTERVAL_SECS), public_address_check_task: TickTask::new(PUBLIC_ADDRESS_CHECK_TASK_INTERVAL_SECS), + network_key, } } @@ -211,6 +215,34 @@ impl NetworkManager { block_store: BlockStore, crypto: Crypto, ) -> Self { + + // Make the network key + let network_key = { + let c = config.get(); + let network_key_password = if let Some(nkp) = c.network.network_key_password.clone() { + Some(nkp) + } else { + if c.network.routing_table.bootstrap.contains(&"bootstrap.veilid.net".to_owned()) { + None + } else { + Some(c.network.routing_table.bootstrap.join(",")) + } + }; + + let network_key = if let Some(network_key_password) = network_key_password { + + info!("Using network key"); + + let bcs = crypto.best(); + // Yes the use of the salt this way is generally bad, but this just needs to be hashed + Some(bcs.derive_shared_secret(network_key_password.as_bytes(), network_key_password.as_bytes()).expect("failed to derive network key")) + } else { + None + }; + + network_key + }; + let this = Self { inner: Arc::new(Mutex::new(Self::new_inner())), unlocked_inner: Arc::new(Self::new_unlocked_inner( @@ -221,6 +253,7 @@ impl NetworkManager { #[cfg(feature="unstable-blockstore")] block_store, crypto, + network_key, )), }; @@ -819,7 +852,10 @@ impl NetworkManager { }; // Build the envelope to send - let out = self.build_envelope(best_node_id, envelope_version, body)?; + let mut out = self.build_envelope(best_node_id, envelope_version, body)?; + + // Apply network key + self.apply_network_key(&mut out); // Send the envelope via whatever means necessary self.send_data(node_ref, out).await @@ -830,7 +866,7 @@ impl NetworkManager { pub async fn send_out_of_band_receipt( &self, dial_info: DialInfo, - rcpt_data: Vec, + mut rcpt_data: Vec, ) -> EyreResult<()> { // Do we need to validate the outgoing receipt? Probably not // because it is supposed to be opaque and the @@ -838,6 +874,9 @@ impl NetworkManager { // Also, in the case of an old 'version', returning the receipt // should not be subject to our ability to decode it + // Apply network key + self.apply_network_key(&mut rcpt_data); + // Send receipt directly log_net!(debug "send_out_of_band_receipt: dial_info={}", dial_info); network_result_value_or_log!(self @@ -1081,7 +1120,7 @@ impl NetworkManager { // If the node has had lost questions or failures to send, prefer sequencing // to improve reliability. The node may be experiencing UDP fragmentation drops // or other firewalling issues and may perform better with TCP. - let unreliable = target_node_ref.peer_stats().rpc_stats.failed_to_send > 0 || target_node_ref.peer_stats().rpc_stats.recent_lost_answers > 0; + let unreliable = target_node_ref.peer_stats().rpc_stats.failed_to_send > 2 || target_node_ref.peer_stats().rpc_stats.recent_lost_answers > 2; if unreliable && sequencing < Sequencing::PreferOrdered { sequencing = Sequencing::PreferOrdered; } @@ -1258,7 +1297,9 @@ impl NetworkManager { .iter() .filter_map(|nr| nr.make_peer_info(RoutingDomain::PublicInternet)) .collect(); - let json_bytes = serialize_json(bootstrap_peerinfo).as_bytes().to_vec(); + let mut json_bytes = serialize_json(bootstrap_peerinfo).as_bytes().to_vec(); + + self.apply_network_key(&mut json_bytes); // Reply with a chunk of signed routing table match self @@ -1281,8 +1322,12 @@ impl NetworkManager { pub async fn boot_request(&self, dial_info: DialInfo) -> EyreResult> { let timeout_ms = self.with_config(|c| c.network.rpc.timeout_ms); // Send boot magic to requested peer address - let data = BOOT_MAGIC.to_vec(); - let out_data: Vec = network_result_value_or_log!(self + let mut data = BOOT_MAGIC.to_vec(); + + // Apply network key + self.apply_network_key(&mut data); + + let mut out_data: Vec = network_result_value_or_log!(self .net() .send_recv_data_unbound_to_dial_info(dial_info, data, timeout_ms) .await? => @@ -1290,6 +1335,9 @@ impl NetworkManager { return Ok(Vec::new()); }); + // Apply network key + self.apply_network_key(&mut out_data); + let bootstrap_peerinfo: Vec = deserialize_json(std::str::from_utf8(&out_data).wrap_err("bad utf8 in boot peerinfo")?) .wrap_err("failed to deserialize boot peerinfo")?; @@ -1297,13 +1345,28 @@ impl NetworkManager { Ok(bootstrap_peerinfo) } + // Network isolation encryption + fn apply_network_key(&self, data: &mut [u8]) { + if let Some(network_key) = self.unlocked_inner.network_key { + let bcs = self.crypto().best(); + // Nonce abuse, but this is not supposed to be cryptographically sound + // it's just here to keep networks from accidentally bridging. + // A proper nonce would increase the data length here and change the packet sizes on the wire + bcs.crypt_in_place_no_auth( + data, + &network_key.bytes[0..NONCE_LENGTH].try_into().unwrap(), + &network_key, + ) + } + } + // Called when a packet potentially containing an RPC envelope is received by a low-level // network protocol handler. Processes the envelope, authenticates and decrypts the RPC message // and passes it to the RPC handler #[instrument(level = "trace", ret, err, skip(self, data), fields(data.len = data.len()))] async fn on_recv_envelope( &self, - data: &[u8], + mut data: &mut [u8], connection_descriptor: ConnectionDescriptor, ) -> EyreResult { let root = span!( @@ -1352,6 +1415,9 @@ impl NetworkManager { } }; + // Apply network key + self.apply_network_key(&mut data); + // Is this a direct bootstrap request instead of an envelope? if data[0..4] == *BOOT_MAGIC { network_result_value_or_log!(self.handle_boot_request(connection_descriptor).await? => {}); @@ -1445,6 +1511,10 @@ impl NetworkManager { if let Some(relay_nr) = some_relay_nr { // Relay the packet to the desired destination log_net!("relaying {} bytes to {}", data.len(), relay_nr); + + // Apply network key + self.apply_network_key(&mut data); + network_result_value_or_log!(match self.send_data(relay_nr, data.to_vec()) .await { Ok(v) => v, diff --git a/veilid-core/src/network_manager/native/network_udp.rs b/veilid-core/src/network_manager/native/network_udp.rs index 58203c9d..002ccf06 100644 --- a/veilid-core/src/network_manager/native/network_udp.rs +++ b/veilid-core/src/network_manager/native/network_udp.rs @@ -67,7 +67,6 @@ impl Network { { Ok(Ok((size, descriptor))) => { // XXX: Limit the number of packets from the same IP address? - log_net!("UDP packet: {:?}", descriptor); // Network accounting network_manager.stats_packet_rcvd( @@ -77,7 +76,7 @@ impl Network { // Pass it up for processing if let Err(e) = network_manager - .on_recv_envelope(&data[..size], descriptor) + .on_recv_envelope(&mut data[..size], descriptor) .await { log_net!(debug "failed to process received udp envelope: {}", e); diff --git a/veilid-core/src/network_manager/network_connection.rs b/veilid-core/src/network_manager/network_connection.rs index dfd2e326..60d0af4b 100644 --- a/veilid-core/src/network_manager/network_connection.rs +++ b/veilid-core/src/network_manager/network_connection.rs @@ -301,13 +301,13 @@ impl NetworkConnection { match res { Ok(v) => { - let message = network_result_value_or_log!(v => { + let mut message = network_result_value_or_log!(v => { return RecvLoopAction::Finish; }); // Pass received messages up to the network manager for processing if let Err(e) = network_manager - .on_recv_envelope(message.as_slice(), descriptor) + .on_recv_envelope(message.as_mut_slice(), descriptor) .await { log_net!(debug "failed to process received envelope: {}", e); diff --git a/veilid-core/src/tests/common/test_veilid_config.rs b/veilid-core/src/tests/common/test_veilid_config.rs index f7951d6d..ebb4838d 100644 --- a/veilid-core/src/tests/common/test_veilid_config.rs +++ b/veilid-core/src/tests/common/test_veilid_config.rs @@ -196,6 +196,7 @@ fn config_callback(key: String) -> ConfigCallbackReturn { "network.client_whitelist_timeout_ms" => Ok(Box::new(300_000u32)), "network.reverse_connection_receipt_time_ms" => Ok(Box::new(5_000u32)), "network.hole_punch_receipt_time_ms" => Ok(Box::new(5_000u32)), + "network.network_key_password" => Ok(Box::new(Option::::None)), "network.routing_table.node_id" => Ok(Box::new(TypedKeySet::new())), "network.routing_table.node_id_secret" => Ok(Box::new(TypedSecretSet::new())), "network.routing_table.bootstrap" => Ok(Box::new(Vec::::new())), @@ -330,6 +331,7 @@ pub async fn test_config() { assert_eq!(inner.network.client_whitelist_timeout_ms, 300_000u32); assert_eq!(inner.network.reverse_connection_receipt_time_ms, 5_000u32); assert_eq!(inner.network.hole_punch_receipt_time_ms, 5_000u32); + assert_eq!(inner.network.network_key_password, Option::::None); assert_eq!(inner.network.rpc.concurrency, 2u32); assert_eq!(inner.network.rpc.queue_size, 1024u32); assert_eq!(inner.network.rpc.timeout_ms, 5_000u32); diff --git a/veilid-core/src/veilid_api/tests/fixtures.rs b/veilid-core/src/veilid_api/tests/fixtures.rs index bb503260..5373d7dd 100644 --- a/veilid-core/src/veilid_api/tests/fixtures.rs +++ b/veilid-core/src/veilid_api/tests/fixtures.rs @@ -110,6 +110,7 @@ pub fn fix_veilidconfiginner() -> VeilidConfigInner { client_whitelist_timeout_ms: 7000, reverse_connection_receipt_time_ms: 8000, hole_punch_receipt_time_ms: 9000, + network_key_password: None, routing_table: VeilidConfigRoutingTable { node_id: TypedKeySet::new(), node_id_secret: TypedSecretSet::new(), diff --git a/veilid-core/src/veilid_config.rs b/veilid-core/src/veilid_config.rs index 98badf13..07ddf337 100644 --- a/veilid-core/src/veilid_config.rs +++ b/veilid-core/src/veilid_config.rs @@ -384,6 +384,7 @@ pub struct VeilidConfigNetwork { pub client_whitelist_timeout_ms: u32, pub reverse_connection_receipt_time_ms: u32, pub hole_punch_receipt_time_ms: u32, + pub network_key_password: Option, pub routing_table: VeilidConfigRoutingTable, pub rpc: VeilidConfigRPC, pub dht: VeilidConfigDHT, @@ -695,6 +696,7 @@ impl VeilidConfig { get_config!(inner.network.client_whitelist_timeout_ms); get_config!(inner.network.reverse_connection_receipt_time_ms); get_config!(inner.network.hole_punch_receipt_time_ms); + get_config!(inner.network.network_key_password); get_config!(inner.network.routing_table.node_id); get_config!(inner.network.routing_table.node_id_secret); get_config!(inner.network.routing_table.bootstrap); diff --git a/veilid-flutter/lib/veilid_config.dart b/veilid-flutter/lib/veilid_config.dart index 86a32406..e6a5001e 100644 --- a/veilid-flutter/lib/veilid_config.dart +++ b/veilid-flutter/lib/veilid_config.dart @@ -703,6 +703,7 @@ class VeilidConfigNetwork { int clientWhitelistTimeoutMs; int reverseConnectionReceiptTimeMs; int holePunchReceiptTimeMs; + String? networkKeyPassword; VeilidConfigRoutingTable routingTable; VeilidConfigRPC rpc; VeilidConfigDHT dht; @@ -723,6 +724,7 @@ class VeilidConfigNetwork { required this.clientWhitelistTimeoutMs, required this.reverseConnectionReceiptTimeMs, required this.holePunchReceiptTimeMs, + this.networkKeyPassword, required this.routingTable, required this.rpc, required this.dht, @@ -745,6 +747,7 @@ class VeilidConfigNetwork { 'client_whitelist_timeout_ms': clientWhitelistTimeoutMs, 'reverse_connection_receipt_time_ms': reverseConnectionReceiptTimeMs, 'hole_punch_receipt_time_ms': holePunchReceiptTimeMs, + 'network_key_password': networkKeyPassword, 'routing_table': routingTable.toJson(), 'rpc': rpc.toJson(), 'dht': dht.toJson(), @@ -770,6 +773,7 @@ class VeilidConfigNetwork { reverseConnectionReceiptTimeMs = json['reverse_connection_receipt_time_ms'], holePunchReceiptTimeMs = json['hole_punch_receipt_time_ms'], + networkKeyPassword = json['network_key_password'], routingTable = VeilidConfigRoutingTable.fromJson(json['routing_table']), rpc = VeilidConfigRPC.fromJson(json['rpc']), dht = VeilidConfigDHT.fromJson(json['dht']), diff --git a/veilid-python/poetry_install.sh b/veilid-python/poetry_install.sh index c83dad5c..21eb50f6 100755 --- a/veilid-python/poetry_install.sh +++ b/veilid-python/poetry_install.sh @@ -1,3 +1,4 @@ #!/bin/bash +poetry config --local virtualenvs.in-project true export PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring poetry install diff --git a/veilid-python/tests/test_routing_context.py b/veilid-python/tests/test_routing_context.py index 931fa89c..0628ed2c 100644 --- a/veilid-python/tests/test_routing_context.py +++ b/veilid-python/tests/test_routing_context.py @@ -16,11 +16,11 @@ from .conftest import server_info async def test_routing_contexts(api_connection: veilid.VeilidAPI): rc = await api_connection.new_routing_context() async with rc: - rcp = await rc.with_privacy(release = False) + rcp = await rc.with_privacy(release=False) async with rcp: - rcps = await rcp.with_sequencing(veilid.Sequencing.ENSURE_ORDERED, release = False) + rcps = await rcp.with_sequencing(veilid.Sequencing.ENSURE_ORDERED, release=False) async with rcps: - rcpscp = await rcps.with_custom_privacy(veilid.Stability.RELIABLE, release = False) + rcpscp = await rcps.with_custom_privacy(veilid.Stability.RELIABLE, release=False) await rcpscp.release() @@ -44,7 +44,7 @@ async def test_routing_context_app_message_loopback(): # make a routing context that uses a safety route rc = await (await api.new_routing_context()).with_privacy() async with rc: - + # make a new local private route prl, blob = await api.new_private_route() @@ -81,7 +81,7 @@ async def test_routing_context_app_call_loopback(): # make a routing context that uses a safety route rc = await (await api.new_routing_context()).with_privacy() async with rc: - + # make a new local private route prl, blob = await api.new_private_route() @@ -130,9 +130,10 @@ async def test_routing_context_app_message_loopback_big_packets(): await api.debug("purge routes") # make a routing context that uses a safety route + #rc = await (await (await api.new_routing_context()).with_privacy()).with_sequencing(veilid.Sequencing.ENSURE_ORDERED) rc = await (await api.new_routing_context()).with_privacy() async with rc: - + # make a new local private route prl, blob = await api.new_private_route() @@ -143,7 +144,7 @@ async def test_routing_context_app_message_loopback_big_packets(): for _ in range(10): # send a random sized random app message to our own private route - message = random.randbytes(random.randint(0,32768)) + message = random.randbytes(random.randint(0, 32768)) await rc.app_message(prr, message) # we should get the same message back diff --git a/veilid-python/veilid/config.py b/veilid-python/veilid/config.py index 59522c50..9533a64b 100644 --- a/veilid-python/veilid/config.py +++ b/veilid-python/veilid/config.py @@ -203,6 +203,7 @@ class VeilidConfigNetwork(ConfigBase): client_whitelist_timeout_ms: int reverse_connection_receipt_time_ms: int hole_punch_receipt_time_ms: int + network_key_password: Optional[str] routing_table: VeilidConfigRoutingTable rpc: VeilidConfigRPC dht: VeilidConfigDHT diff --git a/veilid-server/src/cmdline.rs b/veilid-server/src/cmdline.rs index bd74babc..4701b791 100644 --- a/veilid-server/src/cmdline.rs +++ b/veilid-server/src/cmdline.rs @@ -152,6 +152,11 @@ fn do_clap_matches(default_config_path: &OsStr) -> Result EyreResult<(Settings, ArgMatches)> { if matches.occurrences_of("new-password") != 0 { settingsrw.core.protected_store.new_device_encryption_key_password = Some(matches.value_of("new-password").unwrap().to_owned()); } + if matches.occurrences_of("network-key") != 0 { + settingsrw.core.network.network_key_password = Some(matches.value_of("new-password").unwrap().to_owned()); + } if matches.occurrences_of("dump-txt-record") != 0 { // Turn off terminal logging so we can be interactive diff --git a/veilid-server/src/settings.rs b/veilid-server/src/settings.rs index 0fc09ce9..772742e9 100644 --- a/veilid-server/src/settings.rs +++ b/veilid-server/src/settings.rs @@ -69,6 +69,7 @@ core: client_whitelist_timeout_ms: 300000 reverse_connection_receipt_time_ms: 5000 hole_punch_receipt_time_ms: 5000 + network_key_password: null routing_table: node_id: null node_id_secret: null @@ -582,6 +583,7 @@ pub struct Network { pub client_whitelist_timeout_ms: u32, pub reverse_connection_receipt_time_ms: u32, pub hole_punch_receipt_time_ms: u32, + pub network_key_password: Option, pub routing_table: RoutingTable, pub rpc: Rpc, pub dht: Dht, @@ -994,6 +996,7 @@ impl Settings { set_config_value!(inner.core.network.client_whitelist_timeout_ms, value); set_config_value!(inner.core.network.reverse_connection_receipt_time_ms, value); set_config_value!(inner.core.network.hole_punch_receipt_time_ms, value); + set_config_value!(inner.core.network.network_key_password, value); set_config_value!(inner.core.network.routing_table.node_id, value); set_config_value!(inner.core.network.routing_table.node_id_secret, value); set_config_value!(inner.core.network.routing_table.bootstrap, value); @@ -1174,6 +1177,9 @@ impl Settings { "network.hole_punch_receipt_time_ms" => { Ok(Box::new(inner.core.network.hole_punch_receipt_time_ms)) } + "network.network_key_password" => { + Ok(Box::new(inner.core.network.network_key_password.clone())) + } "network.routing_table.node_id" => Ok(Box::new( inner .core @@ -1575,6 +1581,7 @@ mod tests { assert_eq!(s.core.network.client_whitelist_timeout_ms, 300_000u32); assert_eq!(s.core.network.reverse_connection_receipt_time_ms, 5_000u32); assert_eq!(s.core.network.hole_punch_receipt_time_ms, 5_000u32); + assert_eq!(s.core.network.network_key_password, None); assert_eq!(s.core.network.routing_table.node_id, None); assert_eq!(s.core.network.routing_table.node_id_secret, None); //