/* * Copyright (c) 2013-2024, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * * See full license text in LICENSE file at top of project tree */ #include #include "I2PEndian.h" #include "Crypto.h" #include "Log.h" #include "RouterContext.h" #include "I2NPProtocol.h" #include "Garlic.h" #include "ECIESX25519AEADRatchetSession.h" #include "Tunnel.h" #include "Transports.h" #include "TransitTunnel.h" namespace i2p { namespace tunnel { TransitTunnel::TransitTunnel (uint32_t receiveTunnelID, const i2p::data::IdentHash& nextIdent, uint32_t nextTunnelID, const i2p::crypto::AESKey& layerKey, const i2p::crypto::AESKey& ivKey): TunnelBase (receiveTunnelID, nextTunnelID, nextIdent), m_LayerKey (layerKey), m_IVKey (ivKey) { } void TransitTunnel::EncryptTunnelMsg (std::shared_ptr in, std::shared_ptr out) { if (!m_Encryption) { m_Encryption.reset (new i2p::crypto::TunnelEncryption); m_Encryption->SetKeys (m_LayerKey, m_IVKey); } m_Encryption->Encrypt (in->GetPayload () + 4, out->GetPayload () + 4); i2p::transport::transports.UpdateTotalTransitTransmittedBytes (TUNNEL_DATA_MSG_SIZE); } TransitTunnelParticipant::~TransitTunnelParticipant () { } void TransitTunnelParticipant::HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg) { EncryptTunnelMsg (tunnelMsg, tunnelMsg); m_NumTransmittedBytes += tunnelMsg->GetLength (); htobe32buf (tunnelMsg->GetPayload (), GetNextTunnelID ()); tunnelMsg->FillI2NPMessageHeader (eI2NPTunnelData); m_TunnelDataMsgs.push_back (tunnelMsg); } void TransitTunnelParticipant::FlushTunnelDataMsgs () { if (!m_TunnelDataMsgs.empty ()) { auto num = m_TunnelDataMsgs.size (); if (num > 1) LogPrint (eLogDebug, "TransitTunnel: ", GetTunnelID (), "->", GetNextTunnelID (), " ", num); i2p::transport::transports.SendMessages (GetNextIdentHash (), m_TunnelDataMsgs); // send and clear } } void TransitTunnel::SendTunnelDataMsg (std::shared_ptr msg) { LogPrint (eLogError, "TransitTunnel: We are not a gateway for ", GetTunnelID ()); } void TransitTunnel::HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg) { LogPrint (eLogError, "TransitTunnel: Incoming tunnel message is not supported ", GetTunnelID ()); } void TransitTunnelGateway::SendTunnelDataMsg (std::shared_ptr msg) { TunnelMessageBlock block; block.deliveryType = eDeliveryTypeLocal; block.data = msg; std::unique_lock l(m_SendMutex); m_Gateway.PutTunnelDataMsg (block); } void TransitTunnelGateway::FlushTunnelDataMsgs () { std::unique_lock l(m_SendMutex); m_Gateway.SendBuffer (); } void TransitTunnelEndpoint::HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg) { auto newMsg = CreateEmptyTunnelDataMsg (true); EncryptTunnelMsg (tunnelMsg, newMsg); LogPrint (eLogDebug, "TransitTunnel: handle msg for endpoint ", GetTunnelID ()); m_Endpoint.HandleDecryptedTunnelDataMsg (newMsg); } std::shared_ptr CreateTransitTunnel (uint32_t receiveTunnelID, const i2p::data::IdentHash& nextIdent, uint32_t nextTunnelID, const i2p::crypto::AESKey& layerKey, const i2p::crypto::AESKey& ivKey, bool isGateway, bool isEndpoint) { if (isEndpoint) { LogPrint (eLogDebug, "TransitTunnel: endpoint ", receiveTunnelID, " created"); return std::make_shared (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey); } else if (isGateway) { LogPrint (eLogInfo, "TransitTunnel: gateway ", receiveTunnelID, " created"); return std::make_shared (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey); } else { LogPrint (eLogDebug, "TransitTunnel: ", receiveTunnelID, "->", nextTunnelID, " created"); return std::make_shared (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey); } } void HandleShortTransitTunnelBuildMsg (std::shared_ptr msg) { if (!msg) return; uint8_t * buf = msg->GetPayload(); size_t len = msg->GetPayloadLength(); int num = buf[0]; LogPrint (eLogDebug, "TransitTunnel: ShortTunnelBuild ", num, " records"); if (num > i2p::tunnel::MAX_NUM_RECORDS) { LogPrint (eLogError, "TransitTunnel: Too many records in ShortTunnelBuild message ", num); return; } if (len < num*SHORT_TUNNEL_BUILD_RECORD_SIZE + 1) { LogPrint (eLogError, "TransitTunnel: ShortTunnelBuild message of ", num, " records is too short ", len); return; } const uint8_t * record = buf + 1; for (int i = 0; i < num; i++) { if (!memcmp (record, (const uint8_t *)i2p::context.GetRouterInfo ().GetIdentHash (), 16)) { LogPrint (eLogDebug, "TransitTunnel: Short request record ", i, " is ours"); uint8_t clearText[SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE]; if (!i2p::context.DecryptTunnelShortRequestRecord (record + SHORT_REQUEST_RECORD_ENCRYPTED_OFFSET, clearText)) { LogPrint (eLogWarning, "TransitTunnel: Can't decrypt short request record ", i); return; } if (clearText[SHORT_REQUEST_RECORD_LAYER_ENCRYPTION_TYPE]) // not AES { LogPrint (eLogWarning, "TransitTunnel: Unknown layer encryption type ", clearText[SHORT_REQUEST_RECORD_LAYER_ENCRYPTION_TYPE], " in short request record"); return; } auto& noiseState = i2p::context.GetCurrentNoiseState (); uint8_t replyKey[32]; // AEAD/Chacha20/Poly1305 i2p::crypto::AESKey layerKey, ivKey; // AES i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "SMTunnelReplyKey", noiseState.m_CK); memcpy (replyKey, noiseState.m_CK + 32, 32); i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "SMTunnelLayerKey", noiseState.m_CK); memcpy (layerKey, noiseState.m_CK + 32, 32); bool isEndpoint = clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG; if (isEndpoint) { i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "TunnelLayerIVKey", noiseState.m_CK); memcpy (ivKey, noiseState.m_CK + 32, 32); } else { if (!memcmp ((const uint8_t *)i2p::context.GetIdentHash (), clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, 32)) // if next ident is now ours { LogPrint (eLogWarning, "TransitTunnel: Next ident is ours in short request record"); return; } memcpy (ivKey, noiseState.m_CK , 32); } // check if we accept this tunnel std::shared_ptr transitTunnel; uint8_t retCode = 0; if (!i2p::context.AcceptsTunnels () || i2p::context.GetCongestionLevel (false) >= CONGESTION_LEVEL_FULL) retCode = 30; if (!retCode) { // create new transit tunnel transitTunnel = i2p::tunnel::CreateTransitTunnel ( bufbe32toh (clearText + SHORT_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET), clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, bufbe32toh (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), layerKey, ivKey, clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_GATEWAY_FLAG, clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG); if (!i2p::tunnel::tunnels.AddTransitTunnel (transitTunnel)) retCode = 30; } // encrypt reply uint8_t nonce[12]; memset (nonce, 0, 12); uint8_t * reply = buf + 1; for (int j = 0; j < num; j++) { nonce[4] = j; // nonce is record # if (j == i) { memset (reply + SHORT_RESPONSE_RECORD_OPTIONS_OFFSET, 0, 2); // no options reply[SHORT_RESPONSE_RECORD_RET_OFFSET] = retCode; if (!i2p::crypto::AEADChaCha20Poly1305 (reply, SHORT_TUNNEL_BUILD_RECORD_SIZE - 16, noiseState.m_H, 32, replyKey, nonce, reply, SHORT_TUNNEL_BUILD_RECORD_SIZE, true)) // encrypt { LogPrint (eLogWarning, "TransitTunnel: Short reply AEAD encryption failed"); return; } } else i2p::crypto::ChaCha20 (reply, SHORT_TUNNEL_BUILD_RECORD_SIZE, replyKey, nonce, reply); reply += SHORT_TUNNEL_BUILD_RECORD_SIZE; } // send reply auto onDrop = [transitTunnel]() { if (transitTunnel) { auto t = transitTunnel->GetCreationTime (); if (t > i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT) // make transit tunnel expired transitTunnel->SetCreationTime (t - i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT); } }; if (isEndpoint) { auto replyMsg = NewI2NPShortMessage (); replyMsg->Concat (buf, len); replyMsg->FillI2NPMessageHeader (eI2NPShortTunnelBuildReply, bufbe32toh (clearText + SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET)); if (transitTunnel) replyMsg->onDrop = onDrop; if (memcmp ((const uint8_t *)i2p::context.GetIdentHash (), clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, 32)) // reply IBGW is not local? { i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "RGarlicKeyAndTag", noiseState.m_CK); uint64_t tag; memcpy (&tag, noiseState.m_CK, 8); // we send it to reply tunnel i2p::transport::transports.SendMessage (clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, CreateTunnelGatewayMsg (bufbe32toh (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), i2p::garlic::WrapECIESX25519Message (replyMsg, noiseState.m_CK + 32, tag))); } else { // IBGW is local uint32_t tunnelID = bufbe32toh (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET); auto tunnel = i2p::tunnel::tunnels.GetTunnel (tunnelID); if (tunnel) { tunnel->SendTunnelDataMsg (replyMsg); tunnel->FlushTunnelDataMsgs (); } else LogPrint (eLogWarning, "I2NP: Tunnel ", tunnelID, " not found for short tunnel build reply"); } } else { auto msg = CreateI2NPMessage (eI2NPShortTunnelBuild, buf, len, bufbe32toh (clearText + SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET)); if (transitTunnel) msg->onDrop = onDrop; i2p::transport::transports.SendMessage (clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, msg); } return; } record += SHORT_TUNNEL_BUILD_RECORD_SIZE; } } static bool HandleBuildRequestRecords (int num, uint8_t * records, uint8_t * clearText) { for (int i = 0; i < num; i++) { uint8_t * record = records + i*TUNNEL_BUILD_RECORD_SIZE; if (!memcmp (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)i2p::context.GetRouterInfo ().GetIdentHash (), 16)) { LogPrint (eLogDebug, "TransitTunnel: Build request record ", i, " is ours"); if (!i2p::context.DecryptTunnelBuildRecord (record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, clearText)) { LogPrint (eLogWarning, "TransitTunnel: Failed to decrypt tunnel build record"); return false; } if (!memcmp ((const uint8_t *)i2p::context.GetIdentHash (), clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, 32) && // if next ident is now ours !(clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG)) // and not endpoint { LogPrint (eLogWarning, "TransitTunnel: Next ident is ours in tunnel build record"); return false; } uint8_t retCode = 0; // decide if we should accept tunnel bool accept = i2p::context.AcceptsTunnels (); if (accept) { auto congestionLevel = i2p::context.GetCongestionLevel (false); if (congestionLevel >= CONGESTION_LEVEL_MEDIUM) { if (congestionLevel < CONGESTION_LEVEL_FULL) { // random reject depending on congestion level int level = i2p::tunnel::tunnels.GetRng ()() % (CONGESTION_LEVEL_FULL - CONGESTION_LEVEL_MEDIUM) + CONGESTION_LEVEL_MEDIUM; if (congestionLevel > level) accept = false; } else accept = false; } } // replace record to reply if (accept) { auto transitTunnel = i2p::tunnel::CreateTransitTunnel ( bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET), clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), clearText + ECIES_BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET, clearText + ECIES_BUILD_REQUEST_RECORD_IV_KEY_OFFSET, clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_GATEWAY_FLAG, clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG); if (!i2p::tunnel::tunnels.AddTransitTunnel (transitTunnel)) retCode = 30; } else retCode = 30; // always reject with bandwidth reason (30) memset (record + ECIES_BUILD_RESPONSE_RECORD_OPTIONS_OFFSET, 0, 2); // no options record[ECIES_BUILD_RESPONSE_RECORD_RET_OFFSET] = retCode; // encrypt reply i2p::crypto::CBCEncryption encryption; for (int j = 0; j < num; j++) { uint8_t * reply = records + j*TUNNEL_BUILD_RECORD_SIZE; if (j == i) { uint8_t nonce[12]; memset (nonce, 0, 12); auto& noiseState = i2p::context.GetCurrentNoiseState (); if (!i2p::crypto::AEADChaCha20Poly1305 (reply, TUNNEL_BUILD_RECORD_SIZE - 16, noiseState.m_H, 32, noiseState.m_CK, nonce, reply, TUNNEL_BUILD_RECORD_SIZE, true)) // encrypt { LogPrint (eLogWarning, "TransitTunnel: Reply AEAD encryption failed"); return false; } } else { encryption.SetKey (clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET); encryption.SetIV (clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_IV_OFFSET); encryption.Encrypt(reply, TUNNEL_BUILD_RECORD_SIZE, reply); } } return true; } } return false; } void HandleVariableTransitTunnelBuildMsg (std::shared_ptr msg) { if (!msg) return; uint8_t * buf = msg->GetPayload(); size_t len = msg->GetPayloadLength(); int num = buf[0]; LogPrint (eLogDebug, "TransitTunnel: VariableTunnelBuild ", num, " records"); if (num > i2p::tunnel::MAX_NUM_RECORDS) { LogPrint (eLogError, "TransitTunnle: Too many records in VaribleTunnelBuild message ", num); return; } if (len < num*TUNNEL_BUILD_RECORD_SIZE + 1) { LogPrint (eLogError, "TransitTunnel: VaribleTunnelBuild message of ", num, " records is too short ", len); return; } uint8_t clearText[ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE]; if (HandleBuildRequestRecords (num, buf + 1, clearText)) { if (clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG) // we are endpoint of outboud tunnel { // so we send it to reply tunnel i2p::transport::transports.SendMessage (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, CreateTunnelGatewayMsg (bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), eI2NPVariableTunnelBuildReply, buf, len, bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); } else i2p::transport::transports.SendMessage (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, CreateI2NPMessage (eI2NPVariableTunnelBuild, buf, len, bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); } } } }