diff --git a/external/oxen-libquic b/external/oxen-libquic index f64ecf62f0..b01fd9e349 160000 --- a/external/oxen-libquic +++ b/external/oxen-libquic @@ -1 +1 @@ -Subproject commit f64ecf62f00534fb09ef371322d276bc58549b9e +Subproject commit b01fd9e349515034d2412eb3bc1e5784287caf95 diff --git a/llarp/crypto/crypto.cpp b/llarp/crypto/crypto.cpp index 10bc5a09be..da583b2d1d 100644 --- a/llarp/crypto/crypto.cpp +++ b/llarp/crypto/crypto.cpp @@ -262,9 +262,12 @@ namespace llarp } void crypto::derive_decrypt_outer_wrapping( - const Ed25519SecretKey& local_sk, const PubKey& remote, const SymmNonce& nonce, uspan encrypted) + const Ed25519SecretKey& local_sk, + SharedSecret& shared, + const PubKey& remote, + const SymmNonce& nonce, + uspan encrypted) { - SharedSecret shared; // derive shared secret using ephemeral pubkey and our secret key (and nonce) if (!crypto::dh_server(shared, remote, local_sk, nonce)) { diff --git a/llarp/crypto/crypto.hpp b/llarp/crypto/crypto.hpp index efbcabbb16..e0faaf3776 100644 --- a/llarp/crypto/crypto.hpp +++ b/llarp/crypto/crypto.hpp @@ -73,7 +73,11 @@ namespace llarp /// pubkey and the provided nonce. The encrypted payload is mutated in-place. Will throw on failure of either /// the server DH derivation or the xchacha20 payload mutation void derive_decrypt_outer_wrapping( - const Ed25519SecretKey& local, const PubKey& remote, const SymmNonce& nonce, uspan encrypted); + const Ed25519SecretKey& local, + SharedSecret& shared, + const PubKey& remote, + const SymmNonce& nonce, + uspan encrypted); bool make_scalar(AlignedBuffer<32>& out, const PubKey& k, uint64_t i); diff --git a/llarp/handlers/session.cpp b/llarp/handlers/session.cpp index 851f6516b9..66380c3ecb 100644 --- a/llarp/handlers/session.cpp +++ b/llarp/handlers/session.cpp @@ -36,6 +36,12 @@ namespace llarp::handlers _running = false; + if (_cc_publisher) + { + log::debug(logcat, "ClientContact publish ticker stopped!"); + _cc_publisher->stop(); + } + Lock_t l{paths_mutex}; _sessions.stop_sessions(send_close); @@ -145,7 +151,7 @@ namespace llarp::handlers true); } else - log::debug(logcat, "SessionEndpoint configured to NOT publish ClientContact..."); + log::info(logcat, "SessionEndpoint configured to NOT publish ClientContact..."); } void SessionEndpoint::resolve_ons_mappings() diff --git a/llarp/link/link_manager.cpp b/llarp/link/link_manager.cpp index dbf831b73e..73d6a23572 100644 --- a/llarp/link/link_manager.cpp +++ b/llarp/link/link_manager.cpp @@ -713,7 +713,7 @@ namespace llarp try { oxenc::bt_dict_consumer btdc{message}; - std::tie(hop_id_str, nonce, payload) = Onion::deserialize(btdc); + std::tie(hop_id_str, nonce, payload) = ONION::deserialize_hop(btdc); } catch (const std::exception& e) { @@ -739,7 +739,7 @@ namespace llarp try { oxenc::bt_dict_consumer btdc{payload}; - std::tie(sender, data) = PathData::deserialize(btdc); + std::tie(sender, data) = PATH::DATA::deserialize(btdc); if (auto session = _router.session_endpoint()->get_session(sender)) { @@ -763,7 +763,7 @@ namespace llarp const auto& next_id = hop_is_rx ? hop->txid() : hop->rxid(); const auto& next_router = hop_is_rx ? hop->upstream() : hop->downstream(); - std::string new_payload = Onion::serialize(symmnonce, next_id, payload); + std::string new_payload = ONION::serialize_hop(next_id.to_view(), symmnonce, payload); send_data_message(next_router, std::move(new_payload)); } @@ -1249,34 +1249,34 @@ namespace llarp if (!_router.path_context()->is_transit_allowed()) { log::warning(logcat, "got path build request when not permitting transit"); - m.respond(PathBuildMessage::NO_TRANSIT, true); + m.respond(PATH::BUILD::NO_TRANSIT, true); return; } try { - auto frames = Frames::deserialize(m.body()); + auto frames = ONION::deserialize_frames(m.body()); auto n_frames = frames.size(); if (n_frames != path::MAX_LEN) { log::info(logcat, "Path build message with wrong number of frames: {}", frames.size()); - return m.respond(PathBuildMessage::BAD_FRAMES, true); + return m.respond(PATH::BUILD::BAD_FRAMES, true); } log::trace(logcat, "Deserializing frame: {}", buffer_printer{frames.front()}); SymmNonce nonce; - PubKey remote_pk; ustring hop_payload; + SharedSecret shared; - std::tie(nonce, remote_pk, hop_payload) = - PathBuildMessage::deserialize_hop(oxenc::bt_dict_consumer{frames.front()}, _router.identity()); + std::tie(nonce, shared, hop_payload) = + PATH::BUILD::deserialize_hop(oxenc::bt_dict_consumer{frames.front()}, _router.identity()); log::trace(logcat, "Deserializing hop payload: {}", buffer_printer{hop_payload}); auto hop = path::TransitHop::deserialize_hop( - oxenc::bt_dict_consumer{hop_payload}, from, _router, remote_pk, nonce); + oxenc::bt_dict_consumer{hop_payload}, from, _router, std::move(shared)); hop->started = _router.now(); set_conn_persist(hop->downstream(), hop->expiry_time() + 10s); @@ -1321,7 +1321,7 @@ namespace llarp send_control_message( std::move(upstream), "path_build", - Frames::serialize(std::move(frames)), + ONION::serialize_frames(std::move(frames)), [this, transit_hop = std::move(hop), prev_message = std::move(m)](oxen::quic::message m) mutable { if (m) { @@ -1348,6 +1348,173 @@ namespace llarp } } + void LinkManager::handle_path_control(oxen::quic::message m, const RouterID& /* from */) + { + ustring nonce, hop_id_str, payload; + + try + { + oxenc::bt_dict_consumer btdc{m.body()}; + std::tie(hop_id_str, nonce, payload) = ONION::deserialize_hop(btdc); + } + catch (const std::exception& e) + { + log::warning(logcat, "Exception: {}", e.what()); + return; + } + + auto symmnonce = SymmNonce{nonce.data()}; + HopID hopid{hop_id_str.data()}; + auto hop = _router.path_context()->get_transit_hop(hopid); + + // TODO: use "path_control" for both directions? If not, drop message on + // floor if we don't have the path_id in question; if we decide to make this + // bidirectional, will need to check if we have a Path with path_id. + if (not hop) + return; + + symmnonce = crypto::onion(payload.data(), payload.size(), hop->shared, symmnonce, hop->nonceXOR); + + // if terminal hop, payload should contain a request (e.g. "ons_resolve"); handle and respond. + if (hop->terminal_hop) + { + handle_inner_request( + std::move(m), + std::string{reinterpret_cast(payload.data()), payload.size()}, + std::move(hop)); + return; + } + + auto hop_is_rx = hop->rxid() == hopid; + + const auto& next_id = hop_is_rx ? hop->txid() : hop->rxid(); + const auto& next_router = hop_is_rx ? hop->upstream() : hop->downstream(); + + std::string new_payload = ONION::serialize_hop(next_id.to_view(), symmnonce, payload); + + send_control_message( + next_router, + "path_control"s, + std::move(new_payload), + [hop_weak = hop->weak_from_this(), hopid, prev_message = std::move(m)]( + oxen::quic::message response) mutable { + auto hop = hop_weak.lock(); + + if (not hop) + return; + + if (response.timed_out) + log::debug(logcat, "Path control message timed out!"); + + ustring hop_id, nonce, payload; + + try + { + oxenc::bt_dict_consumer btdc{response.body()}; + std::tie(hop_id, nonce, payload) = ONION::deserialize_hop(btdc); + } + catch (const std::exception& e) + { + log::warning(logcat, "Exception: {}", e.what()); + return; + } + + auto symmnonce = SymmNonce{nonce.data()}; + auto resp_payload = ONION::serialize_hop(hop_id, symmnonce, payload); + prev_message.respond(std::move(resp_payload), false); + }); + } + + void LinkManager::handle_inner_request( + oxen::quic::message m, std::string payload, std::shared_ptr hop) + { + std::string endpoint, body; + + try + { + oxenc::bt_dict_consumer btdc{payload}; + std::tie(endpoint, body) = PATH::CONTROL::deserialize(btdc); + } + catch (const std::exception& e) + { + log::warning(logcat, "Exception: {}", e.what()); + return; + } + + // If a handler exists for "method", call it; else drop request on the floor. + auto itr = path_requests.find(endpoint); + + if (itr == path_requests.end()) + { + log::info(logcat, "Received path control request \"{}\", which has no handler.", endpoint); + return; + } + + auto respond = [m = std::move(m), hop_weak = hop->weak_from_this()](std::string response) mutable { + auto hop = hop_weak.lock(); + if (not hop) + return; // transit hop gone, drop response + + auto n = SymmNonce::make_random(); + m.respond(ONION::serialize_hop(hop->rxid().to_view(), n, response), false); + }; + + std::invoke(itr->second, this, std::move(body), std::move(respond)); + } + + void LinkManager::handle_initiate_session(oxen::quic::message m) + { + if (not m) + { + log::info(logcat, "Initiate session message timed out!"); + return; + } + + NetworkAddress initiator; + service::SessionTag tag; + HopID pivot_txid; + bool use_tun; + std::optional maybe_auth = std::nullopt; + std::shared_ptr path_ptr; + + try + { + oxenc::bt_dict_consumer btdc{m.body()}; + + std::tie(initiator, pivot_txid, tag, use_tun, maybe_auth) = + InitiateSession::decrypt_deserialize(btdc, _router.identity()); + + if (not _router.session_endpoint()->validate(initiator, maybe_auth)) + { + log::warning(logcat, "Failed to authenticate session initiation request from remote:{}", initiator); + return m.respond(InitiateSession::AUTH_DENIED, true); + } + + path_ptr = _router.path_context()->get_path(pivot_txid); + + if (not path_ptr) + { + log::warning(logcat, "Failed to find local path corresponding to session over pivot: {}", pivot_txid); + return m.respond(messages::ERROR_RESPONSE, true); + } + + if (_router.session_endpoint()->prefigure_session( + std::move(initiator), std::move(tag), std::move(path_ptr), use_tun)) + { + return m.respond(messages::OK_RESPONSE); + } + + log::warning(logcat, "Failed to configure InboundSession!"); + } + catch (const std::exception& e) + { + log::warning(logcat, "Exception: {}", e.what()); + } + + _router.path_context()->drop_path(path_ptr); + m.respond(messages::ERROR_RESPONSE, true); + } + void LinkManager::handle_path_latency(oxen::quic::message m) { try @@ -1681,173 +1848,6 @@ namespace llarp // path_ptr->mark_exit_closed(); } - void LinkManager::handle_path_control(oxen::quic::message m, const RouterID& /* from */) - { - ustring nonce, hop_id_str, payload; - - try - { - oxenc::bt_dict_consumer btdc{m.body()}; - std::tie(hop_id_str, nonce, payload) = Onion::deserialize(btdc); - } - catch (const std::exception& e) - { - log::warning(logcat, "Exception: {}", e.what()); - return; - } - - auto symmnonce = SymmNonce{nonce.data()}; - HopID hopid{hop_id_str.data()}; - auto hop = _router.path_context()->get_transit_hop(hopid); - - // TODO: use "path_control" for both directions? If not, drop message on - // floor if we don't have the path_id in question; if we decide to make this - // bidirectional, will need to check if we have a Path with path_id. - if (not hop) - return; - - symmnonce = crypto::onion(payload.data(), payload.size(), hop->shared, symmnonce, hop->nonceXOR); - - // if terminal hop, payload should contain a request (e.g. "ons_resolve"); handle and respond. - if (hop->terminal_hop) - { - handle_inner_request( - std::move(m), - std::string{reinterpret_cast(payload.data()), payload.size()}, - std::move(hop)); - return; - } - - auto hop_is_rx = hop->rxid() == hopid; - - const auto& next_id = hop_is_rx ? hop->txid() : hop->rxid(); - const auto& next_router = hop_is_rx ? hop->upstream() : hop->downstream(); - - std::string new_payload = Onion::serialize(symmnonce, next_id, payload); - - send_control_message( - next_router, - "path_control"s, - std::move(new_payload), - [hop_weak = hop->weak_from_this(), hopid, prev_message = std::move(m)]( - oxen::quic::message response) mutable { - auto hop = hop_weak.lock(); - - if (not hop) - return; - - if (response.timed_out) - log::debug(logcat, "Path control message timed out!"); - - ustring hop_id, nonce, payload; - - try - { - oxenc::bt_dict_consumer btdc{response.body()}; - std::tie(hop_id, nonce, payload) = Onion::deserialize(btdc); - } - catch (const std::exception& e) - { - log::warning(logcat, "Exception: {}", e.what()); - return; - } - - auto symmnonce = SymmNonce{nonce.data()}; - auto resp_payload = Onion::serialize(symmnonce, HopID{hop_id.data()}, payload); - prev_message.respond(std::move(resp_payload), false); - }); - } - - void LinkManager::handle_inner_request( - oxen::quic::message m, std::string payload, std::shared_ptr hop) - { - std::string endpoint, body; - - try - { - oxenc::bt_dict_consumer btdc{payload}; - std::tie(endpoint, body) = PathControl::deserialize(btdc); - } - catch (const std::exception& e) - { - log::warning(logcat, "Exception: {}", e.what()); - return; - } - - // If a handler exists for "method", call it; else drop request on the floor. - auto itr = path_requests.find(endpoint); - - if (itr == path_requests.end()) - { - log::info(logcat, "Received path control request \"{}\", which has no handler.", endpoint); - return; - } - - auto respond = [m = std::move(m), hop_weak = hop->weak_from_this()](std::string response) mutable { - auto hop = hop_weak.lock(); - if (not hop) - return; // transit hop gone, drop response - - auto n = SymmNonce::make_random(); - m.respond(Onion::serialize(n, hop->rxid(), response), false); - }; - - std::invoke(itr->second, this, std::move(body), std::move(respond)); - } - - void LinkManager::handle_initiate_session(oxen::quic::message m) - { - if (not m) - { - log::info(logcat, "Initiate session message timed out!"); - return; - } - - NetworkAddress initiator; - service::SessionTag tag; - HopID pivot_txid; - bool use_tun; - std::optional maybe_auth = std::nullopt; - std::shared_ptr path_ptr; - - try - { - oxenc::bt_dict_consumer btdc{m.body()}; - - std::tie(initiator, pivot_txid, tag, use_tun, maybe_auth) = - InitiateSession::decrypt_deserialize(btdc, _router.identity()); - - if (not _router.session_endpoint()->validate(initiator, maybe_auth)) - { - log::warning(logcat, "Failed to authenticate session initiation request from remote:{}", initiator); - return m.respond(InitiateSession::AUTH_DENIED, true); - } - - path_ptr = _router.path_context()->get_path(pivot_txid); - - if (not path_ptr) - { - log::warning(logcat, "Failed to find local path corresponding to session over pivot: {}", pivot_txid); - return m.respond(messages::ERROR_RESPONSE, true); - } - - if (_router.session_endpoint()->prefigure_session( - std::move(initiator), std::move(tag), std::move(path_ptr), use_tun)) - { - return m.respond(messages::OK_RESPONSE); - } - - log::warning(logcat, "Failed to configure InboundSession!"); - } - catch (const std::exception& e) - { - log::warning(logcat, "Exception: {}", e.what()); - } - - _router.path_context()->drop_path(path_ptr); - m.respond(messages::ERROR_RESPONSE, true); - } - void LinkManager::handle_convo_intro(oxen::quic::message m) { if (m.timed_out) diff --git a/llarp/messages/common.hpp b/llarp/messages/common.hpp index 48f37d01a2..386d29a3f1 100644 --- a/llarp/messages/common.hpp +++ b/llarp/messages/common.hpp @@ -10,68 +10,18 @@ #include -namespace llarp +namespace llarp::messages { - namespace messages - { - static auto logcat = log::Cat("messages"); - - inline std::string serialize_response(oxenc::bt_dict supplement = {}) - { - return oxenc::bt_serialize(supplement); - } - - // ideally STATUS is the first key in a bt-dict, so use a single, early ascii char - inline const auto STATUS_KEY = "!"s; - inline const auto TIMEOUT_RESPONSE = serialize_response({{STATUS_KEY, "TIMEOUT"}}); - inline const auto ERROR_RESPONSE = serialize_response({{STATUS_KEY, "ERROR"}}); - inline const auto OK_RESPONSE = serialize_response({{STATUS_KEY, "OK"}}); - } // namespace messages + static auto logcat = log::Cat("messages"); - namespace Onion + inline std::string serialize_response(oxenc::bt_dict supplement = {}) { - static auto logcat = llarp::log::Cat("onion"); - - /** Bt-encoded contents: - - 'h' : HopID of the next layer of the onion - - 'n' : Symmetric nonce used to encrypt the layer - - 'x' : Encrypted payload transmitted to next recipient - */ - inline static std::string serialize( - const SymmNonce& nonce, const HopID& hop_id, const std::string_view& payload) - { - oxenc::bt_dict_producer btdp; - btdp.append("h", hop_id.to_view()); - btdp.append("n", nonce.to_view()); - btdp.append("x", payload); - - return std::move(btdp).str(); - } - - inline static std::string serialize(const SymmNonce& nonce, const HopID& hop_id, const ustring_view& payload) - { - return serialize( - nonce, hop_id, std::string_view{reinterpret_cast(payload.data()), payload.size()}); - } - - inline static std::tuple deserialize(oxenc::bt_dict_consumer& btdc) - { - ustring hopid, nonce, payload; - - try - { - hopid = btdc.require("h"); - nonce = btdc.require("n"); - payload = btdc.require("x"); - } - catch (const std::exception& e) - { - log::warning(logcat, "Exception caught deserializing onion data:{}", e.what()); - throw; - } - - return {std::move(hopid), std::move(nonce), std::move(payload)}; - } - } // namespace Onion - -} // namespace llarp + return oxenc::bt_serialize(supplement); + } + + // ideally STATUS is the first key in a bt-dict, so use a single, early ascii char + inline const auto STATUS_KEY = "!"s; + inline const auto TIMEOUT_RESPONSE = serialize_response({{STATUS_KEY, "TIMEOUT"}}); + inline const auto ERROR_RESPONSE = serialize_response({{STATUS_KEY, "ERROR"}}); + inline const auto OK_RESPONSE = serialize_response({{STATUS_KEY, "OK"}}); +} // namespace llarp::messages diff --git a/llarp/messages/path.hpp b/llarp/messages/path.hpp index 1b5efa2435..155327763d 100644 --- a/llarp/messages/path.hpp +++ b/llarp/messages/path.hpp @@ -9,220 +9,244 @@ namespace llarp { using namespace oxenc::literals; - namespace Frames + namespace ONION { - static auto logcat = llarp::log::Cat("path-build-frames"); - - inline static std::string serialize(std::vector&& frames) + inline static std::string serialize_frames(std::vector&& frames) { return oxenc::bt_serialize(std::move(frames)); } - inline static std::vector deserialize(std::string_view&& buf) + inline static std::vector deserialize_frames(std::string_view&& buf) { return oxenc::bt_deserialize>(buf); } - } // namespace Frames - - namespace PathData - { - static auto logcat = llarp::log::Cat("path-data"); - /** Fields for transmitting Path Data: - - 'b' : request/command body - - 's' : RouterID of sender - NOTE: more fields may be added later as needed, hence the namespacing + /** Bt-encoded contents: + - 'k' : Next upstream HopID (path messages) OR shared pubkey (path builds) + - 'n' : Symmetric nonce used to encrypt the layer + - 'x' : Encrypted payload transmitted to next recipient */ - inline static std::string serialize(std::string body, const RouterID& local) + template + inline static std::string serialize_hop(K key, const SymmNonce& nonce, T encrypted) { oxenc::bt_dict_producer btdp; - btdp.append("b", body); - btdp.append("s", local.to_view()); - return std::move(btdp).str(); - } - - inline static std::tuple deserialize(oxenc::bt_dict_consumer& btdc) - { - RouterID remote; - bstring body; - - try - { - body = btdc.require("b"); - remote.from_string(btdc.require("s")); - auto sender = NetworkAddress::from_pubkey(remote, true); - - return {std::move(sender), std::move(body)}; - } - catch (const std::exception& e) - { - log::warning(logcat, "Exception caught deserializing path data:{}", e.what()); - throw; - } - } - } // namespace PathData + btdp.append("k", key); + btdp.append("n", nonce.to_view()); + btdp.append("x", encrypted); - namespace PathControl - { - static auto logcat = llarp::log::Cat("path-control"); - - /** Fields for transmitting Path Control: - - 'e' : request endpoint being invoked - - 'r' : request body - */ - inline static std::string serialize(std::string endpoint, std::string body) - { - oxenc::bt_dict_producer btdp; - btdp.append("e", endpoint); - btdp.append("r", body); return std::move(btdp).str(); } - inline static std::tuple deserialize(oxenc::bt_dict_consumer& btdc) + inline static std::tuple deserialize_hop(oxenc::bt_dict_consumer& btdc) { - std::string endpoint, body; + ustring hopid, nonce, payload; try { - endpoint = btdc.require("e"); - body = btdc.require("r"); + hopid = btdc.require("k"); + nonce = btdc.require("n"); + payload = btdc.require("x"); } catch (const std::exception& e) { - log::warning(logcat, "Exception caught deserializing path control:{}", e.what()); - throw; + throw std::runtime_error{"Exception caught deserializing onion data:{}"_format(e.what())}; } - return {std::move(endpoint), std::move(body)}; + return {std::move(hopid), std::move(nonce), std::move(payload)}; } - } // namespace PathControl + } // namespace ONION - namespace PathBuildMessage + namespace PATH { - static auto logcat = llarp::log::Cat("path-build"); - - inline constexpr auto bad_frames = "BAD_FRAMES"sv; - inline constexpr auto bad_crypto = "BAD_CRYPTO"sv; - inline constexpr auto no_transit = "NOT ALLOWING TRANSIT"sv; - inline constexpr auto bad_pathid = "BAD PATH ID"sv; - inline constexpr auto bad_lifetime = "BAD PATH LIFETIME (TOO LONG)"sv; - - inline const auto NO_TRANSIT = messages::serialize_response({{messages::STATUS_KEY, no_transit}}); - inline const auto BAD_LIFETIME = messages::serialize_response({{messages::STATUS_KEY, bad_lifetime}}); - inline const auto BAD_FRAMES = messages::serialize_response({{messages::STATUS_KEY, bad_frames}}); - inline const auto BAD_PATHID = messages::serialize_response({{messages::STATUS_KEY, bad_pathid}}); - inline const auto BAD_CRYPTO = messages::serialize_response({{messages::STATUS_KEY, bad_crypto}}); - - /** For each hop: - - Generate an Ed keypair for the hop (`shared_key`) - - Generate a symmetric nonce for subsequent DH - - Derive the shared secret (`hop.shared`) for DH key-exchange using the ED keypair, hop pubkey, and - symmetric nonce - - Encrypt the hop info in-place using `hop.shared` and the generated symmetric nonce from DH - - Generate the XOR nonce by hashing the symmetric key from DH (`hop.shared`) and truncating - - Bt-encoded contents: - - 'n' : symmetric nonce used for DH key-exchange - - 's' : shared pubkey used to derive symmetric key - - 'x' : encrypted payload - - 'l' : path lifetime - - 'r' : rxID (the path ID for messages going *to* the hop) - - 't' : txID (the path ID for messages coming *from* the client/path origin) - - 'u' : upstream hop RouterID - - All of these 'frames' are inserted sequentially into the list and padded with any needed dummy frames - */ - inline static std::string serialize_hop(path::PathHopConfig& hop) + namespace BUILD { - std::string hop_payload; - + static auto logcat = llarp::log::Cat("path-build"); + + inline constexpr auto bad_frames = "BAD_FRAMES"sv; + inline constexpr auto bad_crypto = "BAD_CRYPTO"sv; + inline constexpr auto no_transit = "NOT ALLOWING TRANSIT"sv; + inline constexpr auto bad_pathid = "BAD PATH ID"sv; + inline constexpr auto bad_lifetime = "BAD PATH LIFETIME (TOO LONG)"sv; + + inline const auto NO_TRANSIT = messages::serialize_response({{messages::STATUS_KEY, no_transit}}); + inline const auto BAD_LIFETIME = messages::serialize_response({{messages::STATUS_KEY, bad_lifetime}}); + inline const auto BAD_FRAMES = messages::serialize_response({{messages::STATUS_KEY, bad_frames}}); + inline const auto BAD_PATHID = messages::serialize_response({{messages::STATUS_KEY, bad_pathid}}); + inline const auto BAD_CRYPTO = messages::serialize_response({{messages::STATUS_KEY, bad_crypto}}); + + /** For each hop: + - Generate an Ed keypair for the hop (`shared_key`) + - Generate a symmetric nonce for subsequent DH + - Derive the shared secret (`hop.shared`) for DH key-exchange using the Ed keypair, hop pubkey, and + symmetric nonce + - Encrypt the hop info in-place using `hop.shared` and the generated symmetric nonce from DH + - Generate the XOR nonce by hashing the symmetric key from DH (`hop.shared`) and truncating + + Bt-encoded contents: + - 'k' : shared pubkey used to derive symmetric key + - 'n' : symmetric nonce used for DH key-exchange + - 'x' : encrypted payload + - 'l' : path lifetime + - 'r' : rxID (the path ID for messages going *to* the hop) + - 't' : txID (the path ID for messages coming *from* the client/path origin) + - 'u' : upstream hop RouterID + + All of these 'frames' are inserted sequentially into the list and padded with any needed dummy frames + */ + inline static std::string serialize_hop(path::PathHopConfig& hop) { - oxenc::bt_dict_producer btdp; + std::string hop_payload; - btdp.append("l", path::DEFAULT_LIFETIME.count()); - btdp.append("r", hop.rxID.to_view()); - btdp.append("t", hop.txID.to_view()); - btdp.append("u", hop.upstream.to_view()); + { + oxenc::bt_dict_producer btdp; - hop_payload = std::move(btdp).str(); - } + btdp.append("l", path::DEFAULT_LIFETIME.count()); + btdp.append("r", hop.rxID.to_view()); + btdp.append("t", hop.txID.to_view()); + btdp.append("u", hop.upstream.to_view()); - Ed25519SecretKey ephemeral_key; - crypto::identity_keygen(ephemeral_key); + hop_payload = std::move(btdp).str(); + } - hop.nonce = SymmNonce::make_random(); + Ed25519SecretKey ephemeral_key; + crypto::identity_keygen(ephemeral_key); - crypto::derive_encrypt_outer_wrapping( - ephemeral_key, hop.shared, hop.nonce, hop.rc.router_id(), to_uspan(hop_payload)); + hop.nonce = SymmNonce::make_random(); - // generate nonceXOR value self->hop->pathKey - ShortHash xor_hash; - crypto::shorthash(xor_hash, hop.shared.data(), hop.shared.size()); + crypto::derive_encrypt_outer_wrapping( + ephemeral_key, hop.shared, hop.nonce, hop.rc.router_id(), to_uspan(hop_payload)); - hop.nonceXOR = xor_hash.data(); // nonceXOR is 24 bytes, ShortHash is 32; this will truncate + // generate nonceXOR value self->hop->pathKey + ShortHash xor_hash; + crypto::shorthash(xor_hash, hop.shared.data(), hop.shared.size()); - log::trace( - logcat, - "Hop serialized; nonce: {}, remote router_id: {}, shared pk: {}, shared secret: {}, payload: {}", - hop.nonce.to_string(), - hop.rc.router_id().to_string(), - ephemeral_key.to_pubkey().to_string(), - hop.shared.to_string(), - buffer_printer{hop_payload}); + hop.nonceXOR = xor_hash.data(); // nonceXOR is 24 bytes, ShortHash is 32; this will truncate - oxenc::bt_dict_producer btdp; + log::trace( + logcat, + "Hop serialized; nonce: {}, remote router_id: {}, shared pk: {}, shared secret: {}, payload: {}", + hop.nonce.to_string(), + hop.rc.router_id().to_string(), + ephemeral_key.to_pubkey().to_string(), + hop.shared.to_string(), + buffer_printer{hop_payload}); - btdp.append("n", hop.nonce.to_view()); - btdp.append("s", ephemeral_key.to_pubkey().to_view()); - btdp.append("x", hop_payload); + return ONION::serialize_hop(ephemeral_key.to_pubkey().to_view(), hop.nonce, hop_payload); + } - return std::move(btdp).str(); - } + inline static std::tuple deserialize_hop( + oxenc::bt_dict_consumer&& btdc, const Ed25519SecretKey& local_sk) + { + SymmNonce nonce; + PubKey remote_pk; + ustring hop_payload; + SharedSecret shared; + + try + { + remote_pk.from_string(btdc.require("k")); + nonce.from_string(btdc.require("n")); + hop_payload = btdc.require("x"); + } + catch (const std::exception& e) + { + log::warning(logcat, "Exception caught deserializing hop dict:{}", e.what()); + throw; + } + + log::trace( + logcat, + "Hop deserialized; nonce: {}, remote pk: {}, payload: {}", + nonce.to_string(), + remote_pk.to_string(), + buffer_printer{hop_payload}); + + try + { + crypto::derive_decrypt_outer_wrapping(local_sk, shared, remote_pk, nonce, to_uspan(hop_payload)); + } + catch (...) + { + log::info(logcat, "Failed to derive and decrypt outer wrapping!"); + throw std::runtime_error{BAD_CRYPTO}; + } + + log::trace( + logcat, + "Hop decrypted; nonce: {}, remote pk: {}, payload: {}", + nonce.to_string(), + remote_pk.to_string(), + buffer_printer{hop_payload}); + + return {std::move(nonce), std::move(shared), std::move(hop_payload)}; + } + } // namespace BUILD - inline static std::tuple deserialize_hop( - oxenc::bt_dict_consumer&& btdc, const Ed25519SecretKey& local_sk) + namespace CONTROL { - SymmNonce nonce; - PubKey remote_pk; - ustring hop_payload; - - try + /** Fields for transmitting Path Control: + - 'e' : request endpoint being invoked + - 'r' : request body + */ + inline static std::string serialize(std::string endpoint, std::string body) { - nonce.from_string(btdc.require("n")); - remote_pk.from_string(btdc.require("s")); - hop_payload = btdc.require("x"); + oxenc::bt_dict_producer btdp; + btdp.append("e", endpoint); + btdp.append("r", body); + return std::move(btdp).str(); } - catch (const std::exception& e) + + inline static std::tuple deserialize(oxenc::bt_dict_consumer& btdc) { - log::warning(logcat, "Exception caught deserializing hop dict:{}", e.what()); - throw; + std::string endpoint, body; + + try + { + endpoint = btdc.require("e"); + body = btdc.require("r"); + } + catch (const std::exception& e) + { + throw std::runtime_error{"Exception caught deserializing path control:{}"_format(e.what())}; + } + + return {std::move(endpoint), std::move(body)}; } + } // namespace CONTROL - log::trace( - logcat, - "Hop deserialized; nonce: {}, remote pk: {}, payload: {}", - nonce.to_string(), - remote_pk.to_string(), - buffer_printer{hop_payload}); - - try + namespace DATA + { + /** Fields for transmitting Path Data: + - 'b' : request/command body + - 's' : RouterID of sender + NOTE: more fields may be added later as needed, hence the namespacing + */ + inline static std::string serialize(std::string body, const RouterID& local) { - crypto::derive_decrypt_outer_wrapping(local_sk, remote_pk, nonce, to_uspan(hop_payload)); + oxenc::bt_dict_producer btdp; + btdp.append("b", body); + btdp.append("s", local.to_view()); + return std::move(btdp).str(); } - catch (...) + + inline static std::tuple deserialize(oxenc::bt_dict_consumer& btdc) { - log::info(logcat, "Failed to derive and decrypt outer wrapping!"); - throw std::runtime_error{BAD_CRYPTO}; + RouterID remote; + bstring body; + + try + { + body = btdc.require("b"); + remote.from_string(btdc.require("s")); + auto sender = NetworkAddress::from_pubkey(remote, true); + + return {std::move(sender), std::move(body)}; + } + catch (const std::exception& e) + { + throw std::runtime_error{"Exception caught deserializing path data:{}"_format(e.what())}; + } } - - log::trace( - logcat, - "Hop decrypted; nonce: {}, remote pk: {}, payload: {}", - nonce.to_string(), - remote_pk.to_string(), - buffer_printer{hop_payload}); - - return {std::move(nonce), std::move(remote_pk), std::move(hop_payload)}; - } - } // namespace PathBuildMessage + } // namespace DATA + } // namespace PATH } // namespace llarp diff --git a/llarp/messages/session.hpp b/llarp/messages/session.hpp index 60db08131f..11a0049191 100644 --- a/llarp/messages/session.hpp +++ b/llarp/messages/session.hpp @@ -82,6 +82,7 @@ namespace llarp SymmNonce nonce; RouterID shared_pubkey; ustring payload; + SharedSecret shared; try { @@ -89,7 +90,7 @@ namespace llarp shared_pubkey = RouterID{btdc.require("s")}; payload = btdc.require("x"); - crypto::derive_decrypt_outer_wrapping(local, shared_pubkey, nonce, to_uspan(payload)); + crypto::derive_decrypt_outer_wrapping(local, shared, shared_pubkey, nonce, to_uspan(payload)); { RouterID remote; diff --git a/llarp/path/path.cpp b/llarp/path/path.cpp index 9541fa6c45..cfc17f3f83 100644 --- a/llarp/path/path.cpp +++ b/llarp/path/path.cpp @@ -144,30 +144,39 @@ namespace llarp::path // _role &= ePathRoleExit; } - std::string Path::make_outer_payload(char* data, size_t len) + std::string Path::make_path_message(std::string&& inner_payload) { - auto nonce = SymmNonce::make_random(); - // chacha and mutate nonce for each hop - for (const auto& hop : hops) + int n_hops = static_cast(hops.size()); + std::string payload{std::move(inner_payload)}; + + // Working from final hop to hop 1, we onion encrypt the message payload with each + // hop's shared secret (this was derived via DH KEM in path building). The encrypted + // payload will then be bt-serialized, and then encrypted/serialized for the next hop. + for (int i = n_hops - 1; i >= 0; --i) { - nonce = crypto::onion(reinterpret_cast(data), len, hop.shared, nonce, hop.nonceXOR); + auto& hop = hops[i]; + + crypto::onion( + reinterpret_cast(payload.data()), payload.size(), hop.shared, hop.nonce, hop.nonceXOR); + + payload = ONION::serialize_hop(hop.upstream.to_view(), hop.nonce, payload); } - return Onion::serialize(nonce, upstream_txid(), {data, len}); + return payload; } bool Path::send_path_data_message(std::string data) { - auto payload = PathData::serialize(std::move(data), _router.local_rid()); - auto outer_payload = make_outer_payload(payload.data(), payload.size()); + auto inner_payload = PATH::DATA::serialize(std::move(data), _router.local_rid()); + auto outer_payload = make_path_message(std::move(inner_payload)); return _router.send_data_message(upstream_rid(), std::move(outer_payload)); } bool Path::send_path_control_message(std::string endpoint, std::string body, std::function func) { - auto inner_payload = PathControl::serialize(std::move(endpoint), std::move(body)); - auto outer_payload = make_outer_payload(inner_payload.data(), inner_payload.size()); + auto inner_payload = PATH::CONTROL::serialize(std::move(endpoint), std::move(body)); + auto outer_payload = make_path_message(std::move(inner_payload)); return _router.send_control_message( upstream_rid(), @@ -190,7 +199,7 @@ namespace llarp::path try { oxenc::bt_dict_consumer btdc{m.body()}; - std::tie(hop_id_str, symmnonce, payload) = Onion::deserialize(btdc); + std::tie(hop_id_str, symmnonce, payload) = ONION::deserialize_hop(btdc); } catch (const std::exception& e) { @@ -219,11 +228,9 @@ namespace llarp::path }); } - bool Path::is_ready() const + bool Path::is_ready(std::chrono::milliseconds now) const { - // if (is_expired(llarp::time_now_ms())) - // return false; - return _established; + return _established ? is_expired(now) : false; } RouterID Path::upstream_rid() diff --git a/llarp/path/path.hpp b/llarp/path/path.hpp index b135dffbda..70c5978652 100644 --- a/llarp/path/path.hpp +++ b/llarp/path/path.hpp @@ -126,7 +126,7 @@ namespace llarp bool send_path_data_message(std::string body); - bool is_ready() const; + bool is_ready(std::chrono::milliseconds now = llarp::time_now_ms()) const; RouterID upstream_rid(); const RouterID& upstream_rid() const; @@ -161,7 +161,7 @@ namespace llarp static constexpr bool to_string_formattable = true; private: - std::string make_outer_payload(char* data, size_t len); + std::string make_path_message(std::string&& payload); bool SendLatencyMessage(Router* r); diff --git a/llarp/path/path_context.cpp b/llarp/path/path_context.cpp index 56267f4af2..86cf03108f 100644 --- a/llarp/path/path_context.cpp +++ b/llarp/path/path_context.cpp @@ -57,22 +57,6 @@ namespace llarp::path } } - intro_set PathContext::get_recent_ccs() const - { - Lock_t l{paths_mutex}; - - intro_set intros; - auto now = llarp::time_now_ms(); - - for (auto& [_, p] : _path_map) - { - if (p->is_ready() and not p->is_expired(now)) - intros.emplace(p->intro); - } - - return intros; - } - void PathContext::drop_path(const std::shared_ptr& path) { Lock_t l{paths_mutex}; diff --git a/llarp/path/path_context.hpp b/llarp/path/path_context.hpp index fc54a36bf9..90321369de 100644 --- a/llarp/path/path_context.hpp +++ b/llarp/path/path_context.hpp @@ -47,8 +47,6 @@ namespace llarp::path void drop_paths(std::vector> droplist); - intro_set get_recent_ccs() const; - private: const RouterID _local_rid; diff --git a/llarp/path/path_handler.cpp b/llarp/path/path_handler.cpp index 48d4682304..10166002f4 100644 --- a/llarp/path/path_handler.cpp +++ b/llarp/path/path_handler.cpp @@ -263,7 +263,7 @@ namespace llarp::path for (const auto& [_, p] : _paths) { - if (p->is_ready() and not p->intro.is_expired(now)) + if (p and p->is_ready(now)) intros.emplace(p->intro); } @@ -350,7 +350,8 @@ namespace llarp::path for (auto& [_, p] : _paths) { - dissociate_hop_ids(p); + if (p) + dissociate_hop_ids(p); } _paths.clear(); @@ -555,13 +556,13 @@ namespace llarp::path // the same entity from knowing they are part of the same path // (unless they're adjacent in the path; nothing we can do about that obviously). - // i from n_hops downto 0 + // i from n_hops down to 0 for (int i = n_hops - 1; i >= 0; --i) { const auto& next_rid = i == n_hops - 1 ? path_hops[i].rc.router_id() : path_hops[i + 1].rc.router_id(); path_hops[i].upstream = next_rid; - frames[i] = PathBuildMessage::serialize_hop(path_hops[i]); + frames[i] = PATH::BUILD::serialize_hop(path_hops[i]); if (last_len and frames[i].size() != last_len) { @@ -593,7 +594,7 @@ namespace llarp::path _build_stats.attempts++; - return Frames::serialize(std::move(frames)); + return ONION::serialize_frames(std::move(frames)); } bool PathHandler::build3(RouterID upstream, std::string payload, std::function handler) diff --git a/llarp/path/path_handler.hpp b/llarp/path/path_handler.hpp index 3b82e07212..aca8398578 100644 --- a/llarp/path/path_handler.hpp +++ b/llarp/path/path_handler.hpp @@ -130,7 +130,7 @@ namespace llarp Router& _router; size_t num_hops; std::chrono::milliseconds last_build{0s}; - std::chrono::milliseconds build_interval_limit = MIN_PATH_BUILD_INTERVAL; + std::chrono::milliseconds build_interval_limit{MIN_PATH_BUILD_INTERVAL}; std::set snode_blacklist; diff --git a/llarp/path/path_types.hpp b/llarp/path/path_types.hpp index 1956834300..3f17d63b41 100644 --- a/llarp/path/path_types.hpp +++ b/llarp/path/path_types.hpp @@ -31,7 +31,7 @@ namespace llarp /// next hop's router id RouterID upstream; // lifetime - std::chrono::milliseconds lifetime = DEFAULT_LIFETIME; + std::chrono::milliseconds lifetime{DEFAULT_LIFETIME}; nlohmann::json ExtractStatus() const; @@ -51,8 +51,8 @@ namespace llarp }; // milliseconds waiting between builds on a path per router - static constexpr auto MIN_PATH_BUILD_INTERVAL = 500ms; - static constexpr auto PATH_BUILD_RATE = 100ms; + static constexpr auto MIN_PATH_BUILD_INTERVAL{500ms}; + static constexpr auto PATH_BUILD_RATE{100ms}; } // namespace path } // namespace llarp diff --git a/llarp/path/transit_hop.cpp b/llarp/path/transit_hop.cpp index dc46efd374..407808bd71 100644 --- a/llarp/path/transit_hop.cpp +++ b/llarp/path/transit_hop.cpp @@ -10,7 +10,7 @@ namespace llarp::path static auto logcat = log::Cat("transit-hop"); std::shared_ptr TransitHop::deserialize_hop( - oxenc::bt_dict_consumer&& btdc, const RouterID& src, Router& r, const PubKey& remote_pk, const SymmNonce& nonce) + oxenc::bt_dict_consumer&& btdc, const RouterID& src, Router& r, SharedSecret secret) { auto hop = std::make_shared(); @@ -28,19 +28,16 @@ namespace llarp::path } if (hop->rxid().is_zero() || hop->txid().is_zero()) - throw std::runtime_error{PathBuildMessage::BAD_PATHID}; + throw std::runtime_error{PATH::BUILD::BAD_PATHID}; if (hop->lifetime > path::DEFAULT_LIFETIME) - throw std::runtime_error{PathBuildMessage::BAD_LIFETIME}; + throw std::runtime_error{PATH::BUILD::BAD_LIFETIME}; hop->downstream() = src; + hop->shared = std::move(secret); if (r.path_context()->has_transit_hop(hop)) - throw std::runtime_error{PathBuildMessage::BAD_PATHID}; - - // TODO: get this from the first dh - if (!crypto::dh_server(hop->shared, remote_pk, r.identity(), nonce)) - throw std::runtime_error{PathBuildMessage::BAD_CRYPTO}; + throw std::runtime_error{PATH::BUILD::BAD_PATHID}; // generate hash of hop key for nonce mutation ShortHash xor_hash; diff --git a/llarp/path/transit_hop.hpp b/llarp/path/transit_hop.hpp index 13da518f30..d651c0903d 100644 --- a/llarp/path/transit_hop.hpp +++ b/llarp/path/transit_hop.hpp @@ -27,19 +27,15 @@ namespace llarp // This static factory function is used in path-build logic. The exceptions thrown are the exact response // bodies passed to message::respond(...) function static std::shared_ptr deserialize_hop( - oxenc::bt_dict_consumer&& btdc, - const RouterID& src, - Router& r, - const PubKey& remote_pk, - const SymmNonce& nonce); + oxenc::bt_dict_consumer&& btdc, const RouterID& src, Router& r, SharedSecret secret); SharedSecret shared; SymmNonce nonceXOR; - std::chrono::milliseconds started = 0s; + std::chrono::milliseconds started{0s}; // 10 minutes default - std::chrono::milliseconds lifetime = DEFAULT_LIFETIME; + std::chrono::milliseconds lifetime{DEFAULT_LIFETIME}; uint8_t version; - std::chrono::milliseconds _last_activity = 0s; + std::chrono::milliseconds _last_activity{0s}; bool terminal_hop{false}; RouterID& upstream() { return _upstream; } diff --git a/llarp/router/router.cpp b/llarp/router/router.cpp index b4696c71d7..951cff1a18 100644 --- a/llarp/router/router.cpp +++ b/llarp/router/router.cpp @@ -218,7 +218,7 @@ namespace llarp } else { - _session_endpoint->start_tickers(); + // _session_endpoint->start_tickers(); // Resolve needed ONS values now that we have the necessary things prefigured _session_endpoint->resolve_ons_mappings(); }