Touched up path build message handling

This commit is contained in:
dr7ana 2023-10-16 06:39:57 -07:00
parent a6f901a3a9
commit 6b1e3fbbc0
7 changed files with 211 additions and 146 deletions

View File

@ -45,6 +45,28 @@ namespace llarp
return true;
}
static bool
dh(uint8_t* out,
const uint8_t* client_pk,
const uint8_t* server_pk,
const uint8_t* themPub,
const uint8_t* usSec)
{
llarp::SharedSecret shared;
crypto_generichash_state h;
if (crypto_scalarmult_curve25519(shared.data(), usSec, themPub))
{
return false;
}
crypto_generichash_blake2b_init(&h, nullptr, 0U, shared.size());
crypto_generichash_blake2b_update(&h, client_pk, 32);
crypto_generichash_blake2b_update(&h, server_pk, 32);
crypto_generichash_blake2b_update(&h, shared.data(), 32);
crypto_generichash_blake2b_final(&h, out, shared.size());
return true;
}
static bool
dh_client_priv(
llarp::SharedSecret& shared, const PubKey& pk, const SecretKey& sk, const TunnelNonce& n)
@ -56,6 +78,7 @@ namespace llarp
return crypto_generichash_blake2b(shared.data(), 32, n.data(), 32, dh_result.data(), 32)
!= -1;
}
llarp::LogWarn("crypto::dh_client - dh failed");
return false;
}
@ -65,11 +88,27 @@ namespace llarp
llarp::SharedSecret& shared, const PubKey& pk, const SecretKey& sk, const TunnelNonce& n)
{
llarp::SharedSecret dh_result;
if (dh(dh_result, pk, sk.toPublic(), pk.data(), sk))
{
return crypto_generichash_blake2b(shared.data(), 32, n.data(), 32, dh_result.data(), 32)
!= -1;
}
llarp::LogWarn("crypto::dh_server - dh failed");
return false;
}
static bool
dh_server_priv(uint8_t* shared, const uint8_t* pk, const uint8_t* sk, const uint8_t* nonce)
{
llarp::SharedSecret dh_result;
if (dh(dh_result.data(), pk, sk, pk, sk))
{
return crypto_generichash_blake2b(shared, 32, nonce, 32, dh_result.data(), 32) != -1;
}
llarp::LogWarn("crypto::dh_server - dh failed");
return false;
}
@ -133,6 +172,12 @@ namespace llarp
return crypto_stream_xchacha20_xor(buf, buf, size, n.data(), k.data()) == 0;
}
bool
Crypto::xchacha20(uint8_t* buf, size_t size, const uint8_t* secret, const uint8_t* nonce)
{
return crypto_stream_xchacha20_xor(buf, buf, size, nonce, secret) == 0;
}
bool
Crypto::dh_client(
llarp::SharedSecret& shared, const PubKey& pk, const SecretKey& sk, const TunnelNonce& n)
@ -146,6 +191,15 @@ namespace llarp
{
return dh_server_priv(shared, pk, sk, n);
}
bool
Crypto::dh_server(
uint8_t* shared_secret,
const uint8_t* other_pk,
const uint8_t* local_pk,
const uint8_t* nonce)
{
return dh_server_priv(shared_secret, other_pk, local_pk, nonce);
}
/// transport dh client side
bool
Crypto::transport_dh_client(

View File

@ -30,6 +30,8 @@ namespace llarp
/// xchacha symmetric cipher
bool
xchacha20(uint8_t*, size_t size, const SharedSecret&, const TunnelNonce&);
bool
xchacha20(uint8_t*, size_t size, const uint8_t*, const uint8_t*);
/// path dh creator's side
bool
@ -37,6 +39,12 @@ namespace llarp
/// path dh relay side
bool
dh_server(SharedSecret&, const PubKey&, const SecretKey&, const TunnelNonce&);
bool
dh_server(
uint8_t* shared_secret,
const uint8_t* other_pk,
const uint8_t* local_pk,
const uint8_t* nonce);
/// transport dh client side
bool
transport_dh_client(SharedSecret&, const PubKey&, const SecretKey&, const TunnelNonce&);

View File

@ -3,7 +3,6 @@
#include <llarp/crypto/types.hpp>
#include <llarp/net/ip_packet.hpp>
#include <llarp/path/abstracthophandler.hpp>
#include <llarp/routing/transfer_traffic_message.hpp>
#include <llarp/service/protocol_type.hpp>
#include <llarp/util/time.hpp>

View File

@ -536,7 +536,9 @@ namespace llarp
}
_router.rpc_client()->lookup_ons_hash(
name_hash, [this, msg = std::move(m)](std::optional<service::EncryptedName> maybe) mutable {
name_hash,
[this,
msg = std::move(m)]([[maybe_unused]] std::optional<service::EncryptedName> maybe) mutable {
if (maybe)
msg.respond(serialize_response({{"NAME", maybe->ciphertext}}));
else
@ -1108,77 +1110,93 @@ namespace llarp
{
if (!_router.path_context().AllowingTransit())
{
log::warning("got path build request when not permitting transit");
log::warning(link_cat, "got path build request when not permitting transit");
m.respond(serialize_response({{"STATUS", PathBuildMessage::NO_TRANSIT}}), true);
return;
}
try
{
// not using list_consumer here because we need to move the first (our) frame
// to the end and re-send, unless we're the terminal hop. This could be done
// with list_consumer, but it would involve messy mucking around with the
// encoded list. Revisit if optimization issue (shouldn't be).
auto frame_list = oxenc::bt_deserialize<oxenc::bt_list>(m.body());
if (frames.size() != path::MAX_LEN)
{
log::info(
link_cat,
"Path build request has invalid number of records, ",
frame_list.size(),
"!=",
path::MAX_LEN);
m.respond(serialize_response({{"STATUS", PathBuildMessage::BAD_FRAMES}}), true);
return;
}
const auto& hash = frame_list[0].at("hash");
auto& frame_str = frame_list[0].at("frame");
oxenc::bt_dict_consumer frame_dict{frame_str};
auto hop_info = frame_dict.require<ustring>("encrypted");
TunnelNonce nonce;
nonce.from_string_view(frame_dict.require<std::string_view>("nonce"));
Pubkey otherPubkey;
otherPubkey.from_string_view(frame_dict.require<std::string_view>("pubkey"));
std::string payload{m.body()}, frame_payload;
std::string frame, hash, hop_payload, commkey, rx_id, tx_id, upstream;
ustring other_pubkey, outer_nonce, inner_nonce;
uint64_t lifetime;
auto crypto = CryptoManager::instance();
SharedSecret shared;
// derive shared secret using ephemeral pubkey and our secret key (and nonce)
if (!crypto->dh_server(shared, otherPubkey, _router.identity(), nonce))
try
{
log::info("DH failed during path build.");
m.respond(serialize_response({{"STATUS", PathBuildMessage::BAD_CRYPTO}}), true);
return;
oxenc::bt_list_consumer btlc{payload};
frame_payload = btlc.consume_string();
oxenc::bt_dict_consumer frame_info{frame_payload};
hash = frame_info.require<std::string>("HASH");
frame = frame_info.require<std::string>("FRAME");
oxenc::bt_dict_consumer hop_dict{frame};
hop_payload = frame_info.require<std::string>("ENCRYPTED");
outer_nonce = frame_info.require<ustring>("NONCE");
other_pubkey = frame_info.require<ustring>("PUBKEY");
SharedSecret shared;
// derive shared secret using ephemeral pubkey and our secret key (and nonce)
if (!crypto->dh_server(
shared.data(), other_pubkey.data(), _router.pubkey(), inner_nonce.data()))
{
log::info(link_cat, "DH server initialization failed during path build");
m.respond(serialize_response({{"STATUS", PathBuildMessage::BAD_CRYPTO}}), true);
return;
}
// hash data and check against given hash
ShortHash digest;
if (!crypto->hmac(
digest.data(),
reinterpret_cast<unsigned char*>(frame.data()),
frame.size(),
shared))
{
log::error(link_cat, "HMAC failed on path build request");
m.respond(serialize_response({{"STATUS", PathBuildMessage::BAD_CRYPTO}}), true);
return;
}
if (!std::equal(
digest.begin(), digest.end(), reinterpret_cast<const unsigned char*>(hash.data())))
{
log::info(link_cat, "HMAC mismatch on path build request");
m.respond(serialize_response({{"STATUS", PathBuildMessage::BAD_CRYPTO}}), true);
return;
}
// decrypt frame with our hop info
if (!crypto->xchacha20(
reinterpret_cast<unsigned char*>(hop_payload.data()),
hop_payload.size(),
shared.data(),
outer_nonce.data()))
{
log::info(link_cat, "Decrypt failed on path build request");
m.respond(serialize_response({{"STATUS", PathBuildMessage::BAD_CRYPTO}}), true);
return;
}
oxenc::bt_dict_consumer hop_info{hop_payload};
commkey = hop_info.require<std::string>("COMMKEY");
lifetime = hop_info.require<uint64_t>("LIFETIME");
inner_nonce = hop_info.require<ustring>("NONCE");
rx_id = hop_info.require<std::string>("RX");
tx_id = hop_info.require<std::string>("TX");
upstream = hop_info.require<std::string>("UPSTREAM");
}
catch (...)
{
log::warning(link_cat, "Error: failed to deserialize path build message");
throw;
}
// hash data and check against given hash
ShortHash digest;
if (!crypto->hmac(
digest.data(),
reinterpret_cast<unsigned char*>(frame_str.data()),
frame_str.size(),
shared))
if (frame.empty())
{
log::error(link_cat, "HMAC failed on path build request");
m.respond(serialize_response({{"STATUS", PathBuildMessage::BAD_CRYPTO}}), true);
return;
}
if (!std::equal(
digest.begin(), digest.end(), reinterpret_cast<const unsigned char*>(hash.data())))
{
log::info("HMAC mismatch on path build request");
m.respond(serialize_response({{"STATUS", PathBuildMessage::BAD_CRYPTO}}), true);
return;
}
// decrypt frame with our hop info
if (!crypto->xchacha20(
reinterpret_cast<unsigned char*>(hop_info.data()), hop_info.size(), shared, nonce))
{
log::info("decrypt failed on path build request");
m.respond(serialize_response({{"STATUS", PathBuildMessage::BAD_CRYPTO}}), true);
log::info(link_cat, "Path build request received invalid frame");
m.respond(serialize_response({{"STATUS", PathBuildMessage::BAD_FRAMES}}), true);
return;
}
@ -1189,18 +1207,20 @@ namespace llarp
// TODO: also need downstream for IP / path build limiting clients
auto hop = std::make_shared<path::TransitHop>();
// hop->info.downstream = m.from(); // TODO: RouterID m.from() or similar
auto hop_dict = oxenc::bt_dict_consumer{hop_info};
// extract pathIDs and check if zero or used
hop->info.txID.from_string_view(hop_dict.require<std::string_view>("txid"));
hop->info.rxID.from_string_view(hop_dict.require<std::string_view>("rxid"));
if (info.txID.IsZero() || info.rxID.IsZero())
auto& hop_info = hop->info;
hop_info.txID.from_string_view(tx_id);
hop_info.rxID.from_string_view(rx_id);
if (hop_info.txID.IsZero() || hop_info.rxID.IsZero())
{
log::info("Invalid PathID; PathIDs must be non-zero.");
log::warning(link_cat, "Invalid PathID; PathIDs must be non-zero");
m.respond(serialize_response({{"STATUS", PathBuildMessage::BAD_PATHID}}), true);
return;
}
hop->info.upstream.from_string_view(hop_dict.require<std::string_view>("next");
hop_info.upstream.from_string_view(upstream);
// TODO: need downstream (above), and also the whole transit hop container is garbage.
// namely the PathID uniqueness checking uses the PathIDs and upstream/downstream
@ -1208,19 +1228,17 @@ namespace llarp
// a different upstream, that would be "unique" but we wouldn't know where
// to route messages (nevermind that messages don't currently know the RouterID
// they came from).
if (_router.path_context.HasTransitHop(hop->info))
if (_router.path_context().HasTransitHop(hop_info))
{
log::info("Invalid PathID; PathIDs must be unique.");
log::warning(link_cat, "Invalid PathID; PathIDs must be unique");
m.respond(serialize_response({{"STATUS", PathBuildMessage::BAD_PATHID}}), true);
return;
}
otherPubkey.from_string_view(hop_dict.require<std::string_view>("commkey"));
nonce.from_string_view(hop_dict.require<std::string_view>("nonce"));
if (!crypto->dh_server(hop->pathKey, otherPubkey, _router.identity(), nonce))
if (!crypto->dh_server(
hop->pathKey.data(), other_pubkey.data(), _router.pubkey(), inner_nonce.data()))
{
log::info("DH failed during path build.");
log::warning(link_cat, "DH failed during path build.");
m.respond(serialize_response({{"STATUS", PathBuildMessage::BAD_CRYPTO}}), true);
return;
}
@ -1228,51 +1246,50 @@ namespace llarp
crypto->shorthash(hop->nonceXOR, hop->pathKey.data(), hop->pathKey.size());
// set and check path lifetime
hop->lifetime = 1ms * hop_dict.require<int64_t>("lifetime");
hop->lifetime = 1ms * lifetime;
if (hop->lifetime >= path::DEFAULT_LIFETIME)
{
log::info("path build attempt with too long of a lifetime.");
log::warning(link_cat, "Path build attempt with too long of a lifetime.");
m.respond(serialize_response({{"STATUS", PathBuildMessage::BAD_LIFETIME}}), true);
return;
}
hop->started = _router.now();
_router.persist_connection_until(hop->info.downstream, hop->ExpireTime() + 10s);
if (hop->info.upstream == _router.pubkey())
hop->started = _router.now();
_router.persist_connection_until(hop_info.downstream, hop->ExpireTime() + 10s);
if (hop_info.upstream == _router.pubkey())
{
// we are terminal hop and everything is okay
_router.path_context.PutTransitHop(hop);
_router.path_context().PutTransitHop(hop);
m.respond(serialize_response({{"STATUS", PathBuildMessage::OK}}), false);
return;
}
else
{
// rotate our frame to the end of the list and forward upstream
frame_list.splice(frame_list.end(), frame_list, frame_list.begin());
// rotate our frame to the end of the list and forward upstream
send_control_message(
hop->info.upstream,
"path_build",
bt_serialize(frame_list),
[hop, this, prev_message = std::move(m)](oxen::quic::message response) {
if (response)
{
log::info(
link_cat,
"Upstream returned successful path build response; giving hop info to Router, "
"then relaying response");
_router.path_context.PutTransitHop(hop);
m.respond(response.body_str(), false);
return;
}
else if (response.timed_out)
log::info(link_cat, "Upstream timed out on path build; relaying timeout");
else
log::info(link_cat, "Upstream returned path build failure; relaying response");
auto payload_list = oxenc::bt_deserialize<oxenc::bt_list>(payload);
payload_list.splice(payload_list.end(), payload_list, payload_list.begin());
m.respond(response.body_str(), true);
});
}
send_control_message(
hop->info.upstream,
"path_build",
bt_serialize(payload_list),
[hop, this, prev_message = std::move(m)](oxen::quic::message m) {
if (m)
{
log::info(
link_cat,
"Upstream returned successful path build response; giving hop info to Router, "
"then relaying response");
_router.path_context().PutTransitHop(hop);
}
if (m.timed_out)
log::info(link_cat, "Upstream timed out on path build; relaying timeout");
else
log::info(link_cat, "Upstream returned path build failure; relaying response");
m.respond(m.body_str(), not m);
});
}
catch (const std::exception& e)
{
@ -1410,15 +1427,12 @@ namespace llarp
_router.path_context().GetByUpstream(target, PathID_t{to_usv(tx_id).data()}));
const auto rx_id = transit_hop->info.rxID;
const auto next_seqno = transit_hop->NextSeqNo();
auto success =
(CryptoManager::instance()->verify(pubkey, to_usv(dict_data), sig)
and _router.exitContext().ObtainNewExit(PubKey{pubkey.data()}, rx_id, flag != 0));
m.respond(
ObtainExit::sign_and_serialize_response(_router.identity(), next_seqno, tx_id),
not success);
m.respond(ObtainExit::sign_and_serialize_response(_router.identity(), tx_id), not success);
}
catch (const std::exception& e)
{
@ -1485,16 +1499,13 @@ namespace llarp
auto transit_hop = std::static_pointer_cast<path::TransitHop>(
_router.path_context().GetByUpstream(_router.pubkey(), PathID_t{to_usv(tx_id).data()}));
const auto next_seqno = transit_hop->NextSeqNo();
if (auto exit_ep =
_router.exitContext().FindEndpointForPath(PathID_t{to_usv(path_id).data()}))
{
if (CryptoManager::instance()->verify(exit_ep->PubKey().data(), to_usv(dict_data), sig))
{
(exit_ep->UpdateLocalPath(transit_hop->info.rxID))
? m.respond(
UpdateExit::sign_and_serialize_response(_router.identity(), next_seqno, tx_id))
? m.respond(UpdateExit::sign_and_serialize_response(_router.identity(), tx_id))
: m.respond(serialize_response({{"STATUS", UpdateExit::UPDATE_FAILED}}), true);
}
// If we fail to verify the message, no-op
@ -1573,14 +1584,13 @@ namespace llarp
_router.path_context().GetByUpstream(_router.pubkey(), PathID_t{to_usv(tx_id).data()}));
const auto rx_id = transit_hop->info.rxID;
const auto next_seqno = transit_hop->NextSeqNo();
if (auto exit_ep = router.exitContext().FindEndpointForPath(rx_id))
if (auto exit_ep = router().exitContext().FindEndpointForPath(rx_id))
{
if (CryptoManager::instance()->verify(exit_ep->PubKey().data(), to_usv(dict_data), sig))
{
exit_ep->Close();
m.respond(CloseExit::sign_and_serialize_response(_router.identity(), next_seqno, tx_id));
m.respond(CloseExit::sign_and_serialize_response(_router.identity(), tx_id));
}
}

View File

@ -16,8 +16,7 @@ namespace llarp
// flag: 0 = Exit, 1 = Snode
inline std::string
sign_and_serialize(
SecretKey sk, uint64_t flag, std::string pubkey, uint64_t seq_no, std::string tx_id)
sign_and_serialize(SecretKey sk, uint64_t flag, std::string pubkey, std::string tx_id)
{
oxenc::bt_list_producer btlp;
std::string sig(64, '\0');
@ -27,7 +26,6 @@ namespace llarp
btdp.append("E", flag);
btdp.append("I", pubkey);
btdp.append("S", seq_no);
btdp.append("T", tx_id);
if (not CryptoManager::instance()->sign(
@ -41,7 +39,7 @@ namespace llarp
}
inline std::string
sign_and_serialize_response(SecretKey sk, uint64_t seq_no, std::string_view tx_id)
sign_and_serialize_response(SecretKey sk, std::string_view tx_id)
{
oxenc::bt_list_producer btlp;
std::string sig(64, '\0');
@ -51,7 +49,6 @@ namespace llarp
{
oxenc::bt_dict_producer btdp;
btdp.append("S", seq_no);
btdp.append("T", tx_id);
btdp.append("Y", nonce);
@ -73,7 +70,7 @@ namespace llarp
inline auto UPDATE_FAILED = "EXIT UPDATE FAILED"sv;
inline std::string
sign_and_serialize(SecretKey sk, std::string path_id, std::string seq_no, std::string tx_id)
sign_and_serialize(SecretKey sk, std::string path_id, std::string tx_id)
{
oxenc::bt_list_producer btlp;
std::string sig(64, '\0');
@ -82,7 +79,6 @@ namespace llarp
auto btdp = btlp.append_dict();
btdp.append("P", path_id);
btdp.append("S", seq_no);
btdp.append("T", tx_id);
if (not CryptoManager::instance()->sign(
@ -96,7 +92,7 @@ namespace llarp
}
inline std::string
sign_and_serialize_response(SecretKey sk, uint64_t seq_no, std::string_view tx_id)
sign_and_serialize_response(SecretKey sk, std::string_view tx_id)
{
oxenc::bt_list_producer btlp;
std::string sig(64, '\0');
@ -106,7 +102,6 @@ namespace llarp
{
oxenc::bt_dict_producer btdp;
btdp.append("S", seq_no);
btdp.append("T", tx_id);
btdp.append("Y", nonce);
@ -127,7 +122,7 @@ namespace llarp
inline auto UPDATE_FAILED = "CLOSE EXIT FAILED"sv;
inline std::string
sign_and_serialize(SecretKey sk, std::string seq_no, std::string tx_id)
sign_and_serialize(SecretKey sk, std::string tx_id)
{
oxenc::bt_list_producer btlp;
std::string sig(64, '\0');
@ -137,7 +132,6 @@ namespace llarp
{
auto btdp = btlp.append_dict();
btdp.append("S", seq_no);
btdp.append("T", tx_id);
btdp.append("Y", nonce);
@ -152,7 +146,7 @@ namespace llarp
}
inline std::string
sign_and_serialize_response(SecretKey sk, uint64_t seq_no, std::string_view tx_id)
sign_and_serialize_response(SecretKey sk, std::string_view tx_id)
{
oxenc::bt_list_producer btlp;
std::string sig(64, '\0');
@ -162,7 +156,6 @@ namespace llarp
{
oxenc::bt_dict_producer btdp;
btdp.append("S", seq_no);
btdp.append("T", tx_id);
btdp.append("Y", nonce);

View File

@ -27,7 +27,7 @@ namespace llarp
if (!crypto->dh_client(hop.shared, hop.rc.pubkey, hop.commkey, hop.nonce))
{
auto err = fmt::format("Failed to generate shared key for path build!");
log::error(path_cat, err);
log::warning(path_cat, err);
throw std::runtime_error{std::move(err)};
}
// generate nonceXOR value self->hop->pathKey
@ -46,12 +46,12 @@ namespace llarp
{
oxenc::bt_dict_producer btdp;
btdp.append("lifetime", path::DEFAULT_LIFETIME.count());
btdp.append("txid", hop.txID.ToView());
btdp.append("rxid", hop.rxID.ToView());
btdp.append("nonce", hop.nonce.ToView());
btdp.append("next", hop.upstream.ToView());
btdp.append("commkey", hop.commkey.toPublic().ToView());
btdp.append("COMMKEY", hop.commkey.toPublic().ToView());
btdp.append("LIFETIME", path::DEFAULT_LIFETIME.count());
btdp.append("NONCE", hop.nonce.ToView());
btdp.append("RX", hop.rxID.ToView());
btdp.append("TX", hop.txID.ToView());
btdp.append("UPSTREAM", hop.upstream.ToView());
hop_info = std::move(btdp).str();
}
@ -66,7 +66,7 @@ namespace llarp
// derive (outer) shared key
if (!crypto->dh_client(shared, hop.rc.pubkey, framekey, outer_nonce))
{
log::error(path_cat, "DH client failed during hop info encryption!");
log::warning(path_cat, "DH client failed during hop info encryption!");
throw std::runtime_error{"DH failed during hop info encryption"};
}
@ -77,8 +77,8 @@ namespace llarp
shared,
outer_nonce))
{
log::error(path_cat, "Hop info encryption failed!");
throw std::runtime_error{"Hop info encrypttion failed"};
log::warning(path_cat, "Hop info encryption failed!");
throw std::runtime_error{"Hop info encryption failed"};
}
std::string hashed_data;
@ -86,9 +86,9 @@ namespace llarp
{
oxenc::bt_dict_producer btdp;
btdp.append("encrypted", hop_info);
btdp.append("pubkey", framekey.toPublic().ToView());
btdp.append("nonce", outer_nonce.ToView());
btdp.append("ENCRYPTED", hop_info);
btdp.append("NONCE", outer_nonce.ToView());
btdp.append("PUBKEY", framekey.toPublic().ToView());
hashed_data = std::move(btdp).str();
}
@ -102,14 +102,14 @@ namespace llarp
hashed_data.size(),
shared))
{
log::error(path_cat, "Failed to generate HMAC for hop info");
log::warning(path_cat, "Failed to generate HMAC for hop info");
throw std::runtime_error{"Failed to generate HMAC for hop info"};
}
oxenc::bt_dict_producer btdp;
btdp.append("hash", hash);
btdp.append("frame", hashed_data);
btdp.append("HASH", hash);
btdp.append("FRAME", hashed_data);
return std::move(btdp).str();
}

View File

@ -478,6 +478,7 @@ namespace llarp
path->EnterState(path::ePathFailed, router->now());
}
// TODO: we don't use this concept anymore?
router->persist_connection_until(path->upstream(), path->ExpireTime());
}