diff --git a/AddressBook.cpp b/AddressBook.cpp index 75ca5761..dfd788bf 100644 --- a/AddressBook.cpp +++ b/AddressBook.cpp @@ -80,7 +80,7 @@ void AddressBook::LoadHosts () getline(f, s); if (!s.length()) - break; + continue; // skip empty line size_t pos = s.find('='); @@ -90,8 +90,11 @@ void AddressBook::LoadHosts () std::string addr = s.substr(pos); Identity ident; - Base64ToByteStream (addr.c_str(), addr.length(), (uint8_t *)&ident, sizeof (ident)); - m_Addresses[name] = CalculateIdentHash (ident); + if (!ident.FromBase64(addr)) { + LogPrint ("hosts.txt: ignore ", name); + continue; + } + m_Addresses[name] = ident.Hash(); numAddresses++; } } diff --git a/Identity.cpp b/Identity.cpp index 387eef50..c7c3ed0e 100644 --- a/Identity.cpp +++ b/Identity.cpp @@ -6,6 +6,7 @@ #include #include "CryptoConst.h" #include "Identity.h" +#include "base64.h" namespace i2p { @@ -17,6 +18,19 @@ namespace data memcpy (publicKey, keys.publicKey, sizeof (publicKey) + sizeof (signingKey)); memset (certificate, 0, sizeof (certificate)); return *this; + } + + bool Identity::FromBase64 (const std::string& s) + { + size_t count = Base64ToByteStream (s.c_str(), s.length(), reinterpret_cast (this), sizeof (Identity)); + return count == sizeof(Identity); + } + + IdentHash Identity::Hash() + { + IdentHash hash; + CryptoPP::SHA256().CalculateDigest(reinterpret_cast(&hash), reinterpret_cast (this), sizeof (Identity)); + return hash; } PrivateKeys& PrivateKeys::operator=(const Keys& keys) @@ -26,13 +40,6 @@ namespace data return *this; } - IdentHash CalculateIdentHash (const Identity& identity) - { - IdentHash hash; - CryptoPP::SHA256().CalculateDigest((uint8_t *)hash, (uint8_t *)&identity, sizeof (Identity)); - return hash; - } - Keys CreateRandomKeys () { Keys keys; diff --git a/Identity.h b/Identity.h index 1dafdef4..ff35a7db 100644 --- a/Identity.h +++ b/Identity.h @@ -9,6 +9,8 @@ namespace i2p { namespace data { + class IdentHash; + #pragma pack(1) struct DHKeysPair // transient keys for transport sessions @@ -32,6 +34,8 @@ namespace data uint8_t certificate[3]; Identity& operator=(const Keys& keys); + bool FromBase64(const std::string&); + IdentHash Hash(); }; struct PrivateKeys // for eepsites @@ -75,7 +79,6 @@ namespace data uint8_t m_Hash[32]; }; - IdentHash CalculateIdentHash (const Identity& identity); Keys CreateRandomKeys (); void CreateRandomDHKeysPair (DHKeysPair * keys); // for transport sessions @@ -103,7 +106,7 @@ namespace data public: RoutingDestination (): m_ElGamalEncryption (nullptr) {}; - virtual ~RoutingDestination () { delete m_ElGamalEncryption; }; + virtual ~RoutingDestination () { if (m_ElGamalEncryption) delete m_ElGamalEncryption; }; virtual const IdentHash& GetIdentHash () const = 0; virtual const uint8_t * GetEncryptionPublicKey () const = 0; @@ -125,6 +128,7 @@ namespace data { public: + virtual ~LocalDestination() {}; virtual const IdentHash& GetIdentHash () const = 0; virtual const uint8_t * GetEncryptionPrivateKey () const = 0; virtual const uint8_t * GetEncryptionPublicKey () const = 0; diff --git a/LeaseSet.cpp b/LeaseSet.cpp index 1383792e..7a7ebbc0 100644 --- a/LeaseSet.cpp +++ b/LeaseSet.cpp @@ -25,7 +25,7 @@ namespace data const H * header = (const H *)buf; m_Identity = header->destination; - m_IdentHash = CalculateIdentHash (m_Identity); + m_IdentHash = m_Identity.Hash(); memcpy (m_EncryptionKey, header->encryptionKey, 256); LogPrint ("LeaseSet num=", (int)header->num); diff --git a/NTCPSession.cpp b/NTCPSession.cpp index e21e8d18..ad71e3b0 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -519,7 +519,7 @@ namespace ntcp void NTCPSession::ScheduleTermination () { m_TerminationTimer.cancel (); - m_TerminationTimer.expires_from_now (boost::posix_time::seconds(TERMINATION_TIMEOUT)); + m_TerminationTimer.expires_from_now (boost::posix_time::seconds(NTCP_TERMINATION_TIMEOUT)); m_TerminationTimer.async_wait (boost::bind (&NTCPSession::HandleTerminationTimer, this, boost::asio::placeholders::error)); } @@ -528,7 +528,7 @@ namespace ntcp { if (ecode != boost::asio::error::operation_aborted) { - LogPrint ("No activity fo ", TERMINATION_TIMEOUT, " seconds"); + LogPrint ("No activity fo ", NTCP_TERMINATION_TIMEOUT, " seconds"); m_Socket.close (); } } diff --git a/NTCPSession.h b/NTCPSession.h index 4202c5c9..83396e11 100644 --- a/NTCPSession.h +++ b/NTCPSession.h @@ -61,7 +61,7 @@ namespace ntcp #pragma pack() - const int TERMINATION_TIMEOUT = 120; // 2 minutes + const int NTCP_TERMINATION_TIMEOUT = 120; // 2 minutes class NTCPSession { public: diff --git a/RouterInfo.cpp b/RouterInfo.cpp index 735453c8..9a4f8f97 100644 --- a/RouterInfo.cpp +++ b/RouterInfo.cpp @@ -34,7 +34,7 @@ namespace data void RouterInfo::SetRouterIdentity (const Identity& identity) { m_RouterIdentity = identity; - m_IdentHash = CalculateIdentHash (m_RouterIdentity); + m_IdentHash = m_RouterIdentity.Hash (); UpdateIdentHashBase64 (); UpdateRoutingKey (); m_Timestamp = i2p::util::GetMillisecondsSinceEpoch (); @@ -129,6 +129,8 @@ namespace data address.port = boost::lexical_cast(value); else if (!strcmp (key, "key")) Base64ToByteStream (value, strlen (value), address.key, 32); + else if (!strcmp (key, "caps")) + ExtractCaps (value); else if (key[0] == 'i') { // introducers @@ -191,7 +193,6 @@ namespace data void RouterInfo::ExtractCaps (const char * value) { - m_Caps = 0; const char * cap = value; while (*cap) { @@ -208,6 +209,12 @@ namespace data case 'R': m_Caps |= Caps::eReachable; break; + case 'B': + m_Caps |= Caps::eSSUTesting; + break; + case 'C': + m_Caps |= Caps::eSSUIntroducer; + break; default: ; } cap++; @@ -249,7 +256,10 @@ namespace data // caps WriteString ("caps", properties); properties << '='; - WriteString ("B", properties); // TODO: should be 'BC' for introducers + std::string caps; + if (IsPeerTesting ()) caps += 'B'; + if (IsIntroducer ()) caps += 'C'; + WriteString (caps, properties); properties << ';'; } else @@ -344,11 +354,12 @@ namespace data addr.host = boost::asio::ip::address::from_string (host); addr.port = port; addr.transportStyle = eTransportSSU; - addr.cost = 10; // NTCP should have prioprity over SSU + addr.cost = 10; // NTCP should have priority over SSU addr.date = 0; memcpy (addr.key, key, 32); m_Addresses.push_back(addr); m_SupportedTransports |= eSSUV4; + m_Caps |= eSSUTesting; // TODO } void RouterInfo::SetProperty (const char * key, const char * value) diff --git a/RouterInfo.h b/RouterInfo.h index 19521a6d..d730c5f8 100644 --- a/RouterInfo.h +++ b/RouterInfo.h @@ -29,7 +29,9 @@ namespace data { eFloodfill = 0x01, eHighBandwidth = 0x02, - eReachable = 0x04 + eReachable = 0x04, + eSSUTesting = 0x08, + eSSUIntroducer = 0x10 }; enum TransportStyle @@ -84,6 +86,8 @@ namespace data bool IsSSU (bool v4only = true) const; bool IsCompatible (const RouterInfo& other) const { return m_SupportedTransports & other.m_SupportedTransports; }; bool UsesIntroducer () const; + bool IsIntroducer () const { return m_Caps & eSSUIntroducer; }; + bool IsPeerTesting () const { return m_Caps & eSSUTesting; }; uint8_t GetCaps () const { return m_Caps; }; void SetUnreachable (bool unreachable) { m_IsUnreachable = unreachable; }; diff --git a/SSU.cpp b/SSU.cpp index a43e04f6..a036d1ff 100644 --- a/SSU.cpp +++ b/SSU.cpp @@ -16,8 +16,10 @@ namespace ssu { SSUSession::SSUSession (SSUServer& server, boost::asio::ip::udp::endpoint& remoteEndpoint, - const i2p::data::RouterInfo * router): m_Server (server), m_RemoteEndpoint (remoteEndpoint), - m_RemoteRouter (router), m_Timer (m_Server.GetService ()), m_State (eSessionStateUnknown) + const i2p::data::RouterInfo * router, bool peerTest ): + m_Server (server), m_RemoteEndpoint (remoteEndpoint), m_RemoteRouter (router), + m_Timer (m_Server.GetService ()), m_PeerTest (peerTest), m_State (eSessionStateUnknown), + m_RelayTag (0) { m_DHKeysPair = i2p::transports.GetNextDHKeysPair (); } @@ -57,16 +59,14 @@ namespace ssu case eSessionStateConfirmedSent: case eSessionStateEstablished: // most common case - ProcessMessage (buf, len); + ScheduleTermination (); + ProcessMessage (buf, len, senderEndpoint); break; - // establishing + // establishing or testing case eSessionStateUnknown: - // session request - ProcessSessionRequest (buf, len, senderEndpoint); - break; case eSessionStateRequestSent: - // session created - ProcessSessionCreated (buf, len); + // we must use intro key + ProcessIntroKeyMessage (buf, len, senderEndpoint); break; case eSessionStateCreatedSent: // session confirmed @@ -92,21 +92,21 @@ namespace ssu } } - void SSUSession::ProcessMessage (uint8_t * buf, size_t len) + void SSUSession::ProcessMessage (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint) { if (Validate (buf, len, m_MacKey)) { Decrypt (buf, len, m_SessionKey); SSUHeader * header = (SSUHeader *)buf; - uint8_t payloadType = header->flag >> 4; - switch (payloadType) + switch (header->GetPayloadType ()) { case PAYLOAD_TYPE_DATA: LogPrint ("SSU data received"); ProcessData (buf + sizeof (SSUHeader), len - sizeof (SSUHeader)); break; - case PAYLOAD_TYPE_TEST: - LogPrint ("SSU test received"); + case PAYLOAD_TYPE_PEER_TEST: + LogPrint ("SSU peer test received"); + ProcessPeerTest (buf + sizeof (SSUHeader), len - sizeof (SSUHeader), senderEndpoint); break; case PAYLOAD_TYPE_SESSION_DESTROYED: { @@ -116,10 +116,10 @@ namespace ssu } case PAYLOAD_TYPE_RELAY_INTRO: LogPrint ("SSU relay intro received"); - // TODO: + ProcessRelayIntro (buf + sizeof (SSUHeader), len - sizeof (SSUHeader)); break; default: - LogPrint ("Unexpected SSU payload type ", (int)payloadType); + LogPrint ("Unexpected SSU payload type ", (int)header->GetPayloadType ()); } } else @@ -139,67 +139,92 @@ namespace ssu } } + void SSUSession::ProcessIntroKeyMessage (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint) + { + auto introKey = GetIntroKey (); + if (!introKey) + { + LogPrint ("SSU is not supported"); + return; + } + // use intro key for verification and decryption + if (!Validate (buf, len, introKey)) + { + LogPrint ("MAC verification intro key failed"); + Failed (); + return; + } + + Decrypt (buf, len, introKey); + CreateAESandMacKey (buf + sizeof (SSUHeader), m_SessionKey, m_MacKey); + SSUHeader * header = (SSUHeader *)buf; + switch (header->GetPayloadType ()) + { + case PAYLOAD_TYPE_SESSION_REQUEST: + ProcessSessionRequest (buf, len, senderEndpoint); + break; + case PAYLOAD_TYPE_SESSION_CREATED: + ProcessSessionCreated (buf, len); + break; + case PAYLOAD_TYPE_PEER_TEST: + LogPrint ("SSU peer test received"); + // TODO: + break; + default: ; + } + } + void SSUSession::ProcessSessionRequest (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint) { - LogPrint ("Process session request"); - // use our intro key - if (ProcessIntroKeyEncryptedMessage (PAYLOAD_TYPE_SESSION_REQUEST, buf, len)) - { - m_State = eSessionStateRequestReceived; - LogPrint ("Session request received"); - m_RemoteEndpoint = senderEndpoint; - SendSessionCreated (buf + sizeof (SSUHeader)); - } + m_State = eSessionStateRequestReceived; + LogPrint ("Session request received"); + m_RemoteEndpoint = senderEndpoint; + SendSessionCreated (buf + sizeof (SSUHeader)); } void SSUSession::ProcessSessionCreated (uint8_t * buf, size_t len) { - LogPrint ("Process session created"); if (!m_RemoteRouter) { LogPrint ("Unsolicited session created message"); return; } - // use remote intro key - if (ProcessIntroKeyEncryptedMessage (PAYLOAD_TYPE_SESSION_CREATED, buf, len)) - { - m_State = eSessionStateCreatedReceived; - LogPrint ("Session created received"); - m_Timer.cancel (); // connect timer - uint8_t signedData[532]; // x,y, our IP, our port, remote IP, remote port, relayTag, signed on time - uint8_t * payload = buf + sizeof (SSUHeader); - uint8_t * y = payload; - memcpy (signedData, m_DHKeysPair->publicKey, 256); // x - memcpy (signedData + 256, y, 256); // y - payload += 256; - payload += 1; // size, assume 4 - uint8_t * ourAddress = payload; - boost::asio::ip::address_v4 ourIP (be32toh (*(uint32_t* )ourAddress)); - payload += 4; // address - uint16_t ourPort = be16toh (*(uint16_t *)payload); - payload += 2; // port - memcpy (signedData + 512, ourAddress, 6); // our IP and port - LogPrint ("Our external address is ", ourIP.to_string (), ":", ourPort); - i2p::context.UpdateAddress (ourIP.to_string ().c_str ()); - *(uint32_t *)(signedData + 518) = htobe32 (m_RemoteEndpoint.address ().to_v4 ().to_ulong ()); // remote IP - *(uint16_t *)(signedData + 522) = htobe16 (m_RemoteEndpoint.port ()); // remote port - memcpy (signedData + 524, payload, 8); // relayTag and signed on time - uint32_t relayTag = be32toh (*(uint32_t *)payload); - payload += 4; // relayTag - payload += 4; // signed on time - // decrypt DSA signature - m_Decryption.SetKeyWithIV (m_SessionKey, 32, ((SSUHeader *)buf)->iv); - m_Decryption.ProcessData (payload, payload, 48); - // verify - CryptoPP::DSA::PublicKey pubKey; - pubKey.Initialize (i2p::crypto::dsap, i2p::crypto::dsaq, i2p::crypto::dsag, CryptoPP::Integer (m_RemoteRouter->GetRouterIdentity ().signingKey, 128)); - CryptoPP::DSA::Verifier verifier (pubKey); - if (!verifier.VerifyMessage (signedData, 532, payload, 40)) - LogPrint ("SSU signature verification failed"); - - SendSessionConfirmed (y, ourAddress, relayTag); - } + m_State = eSessionStateCreatedReceived; + LogPrint ("Session created received"); + m_Timer.cancel (); // connect timer + uint8_t signedData[532]; // x,y, our IP, our port, remote IP, remote port, relayTag, signed on time + uint8_t * payload = buf + sizeof (SSUHeader); + uint8_t * y = payload; + memcpy (signedData, m_DHKeysPair->publicKey, 256); // x + memcpy (signedData + 256, y, 256); // y + payload += 256; + payload += 1; // size, assume 4 + uint8_t * ourAddress = payload; + boost::asio::ip::address_v4 ourIP (be32toh (*(uint32_t* )ourAddress)); + payload += 4; // address + uint16_t ourPort = be16toh (*(uint16_t *)payload); + payload += 2; // port + memcpy (signedData + 512, ourAddress, 6); // our IP and port + LogPrint ("Our external address is ", ourIP.to_string (), ":", ourPort); + i2p::context.UpdateAddress (ourIP.to_string ().c_str ()); + *(uint32_t *)(signedData + 518) = htobe32 (m_RemoteEndpoint.address ().to_v4 ().to_ulong ()); // remote IP + *(uint16_t *)(signedData + 522) = htobe16 (m_RemoteEndpoint.port ()); // remote port + memcpy (signedData + 524, payload, 8); // relayTag and signed on time + m_RelayTag = be32toh (*(uint32_t *)payload); + payload += 4; // relayTag + payload += 4; // signed on time + // decrypt DSA signature + m_Decryption.SetKeyWithIV (m_SessionKey, 32, ((SSUHeader *)buf)->iv); + m_Decryption.ProcessData (payload, payload, 48); + // verify + CryptoPP::DSA::PublicKey pubKey; + pubKey.Initialize (i2p::crypto::dsap, i2p::crypto::dsaq, i2p::crypto::dsag, CryptoPP::Integer (m_RemoteRouter->GetRouterIdentity ().signingKey, 128)); + CryptoPP::DSA::Verifier verifier (pubKey); + if (!verifier.VerifyMessage (signedData, 532, payload, 40)) + LogPrint ("SSU signature verification failed"); + + SendSessionConfirmed (y, ourAddress); } void SSUSession::ProcessSessionConfirmed (uint8_t * buf, size_t len) @@ -209,7 +234,7 @@ namespace ssu { Decrypt (buf, len, m_SessionKey); SSUHeader * header = (SSUHeader *)buf; - if ((header->flag >> 4) == PAYLOAD_TYPE_SESSION_CONFIRMED) + if (header->GetPayloadType () == PAYLOAD_TYPE_SESSION_CONFIRMED) { m_State = eSessionStateConfirmedReceived; LogPrint ("Session confirmed received"); @@ -326,7 +351,7 @@ namespace ssu m_Server.Send (buf, 368, m_RemoteEndpoint); } - void SSUSession::SendSessionConfirmed (const uint8_t * y, const uint8_t * ourAddress, uint32_t relayTag) + void SSUSession::SendSessionConfirmed (const uint8_t * y, const uint8_t * ourAddress) { uint8_t buf[480 + 18]; uint8_t * payload = buf + sizeof (SSUHeader); @@ -352,7 +377,7 @@ namespace ssu memcpy (signedData + 512, ourAddress, 6); // our address/port as seem by party *(uint32_t *)(signedData + 518) = htobe32 (m_RemoteEndpoint.address ().to_v4 ().to_ulong ()); // remote IP *(uint16_t *)(signedData + 522) = htobe16 (m_RemoteEndpoint.port ()); // remote port - *(uint32_t *)(signedData + 524) = htobe32 (relayTag); // relay tag + *(uint32_t *)(signedData + 524) = htobe32 (m_RelayTag); // relay tag *(uint32_t *)(signedData + 528) = htobe32 (signedOnTime); // signed on time i2p::context.Sign (signedData, 532, payload); // DSA signature @@ -406,34 +431,21 @@ namespace ssu } } - bool SSUSession::ProcessIntroKeyEncryptedMessage (uint8_t expectedPayloadType, uint8_t * buf, size_t len) + void SSUSession::ProcessRelayIntro (uint8_t * buf, size_t len) { - auto introKey = GetIntroKey (); - if (introKey) + uint8_t size = *buf; + if (size == 4) { - // use intro key for verification and decryption - if (Validate (buf, len, introKey)) - { - Decrypt (buf, len, introKey); - SSUHeader * header = (SSUHeader *)buf; - if ((header->flag >> 4) == expectedPayloadType) - { - CreateAESandMacKey (buf + sizeof (SSUHeader), m_SessionKey, m_MacKey); - return true; - } - else - LogPrint ("Unexpected payload type ", (int)(header->flag >> 4)); - } - else - { - LogPrint ("MAC verification failed"); - Failed (); - } + buf++; // size + boost::asio::ip::address_v4 address (be32toh (*(uint32_t* )buf)); + buf += 4; // address + uint16_t port = be16toh (*(uint16_t *)buf); + // send hole punch of 1 byte + m_Server.Send (buf, 1, boost::asio::ip::udp::endpoint (address, port)); } else - LogPrint ("SSU is not supported"); - return false; - } + LogPrint ("Address size ", size, " is not supported"); + } void SSUSession::FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * buf, size_t len, const uint8_t * aesKey, const uint8_t * iv, const uint8_t * macKey) @@ -543,7 +555,10 @@ namespace ssu for (auto it :m_DelayedMessages) Send (it); m_DelayedMessages.clear (); - } + } + if (m_PeerTest) + SendPeerTest (); + ScheduleTermination (); } void SSUSession::Failed () @@ -555,7 +570,24 @@ namespace ssu m_Server.DeleteSession (this); // delete this } } - + + void SSUSession::ScheduleTermination () + { + m_Timer.cancel (); + m_Timer.expires_from_now (boost::posix_time::seconds(SSU_TERMINATION_TIMEOUT)); + m_Timer.async_wait (boost::bind (&SSUSession::HandleTerminationTimer, + this, boost::asio::placeholders::error)); + } + + void SSUSession::HandleTerminationTimer (const boost::system::error_code& ecode) + { + if (ecode != boost::asio::error::operation_aborted) + { + LogPrint ("SSU no activity fo ", SSU_TERMINATION_TIMEOUT, " seconds"); + Failed (); + } + } + const uint8_t * SSUSession::GetIntroKey () const { if (m_RemoteRouter) @@ -678,6 +710,87 @@ namespace ssu } } + + void SSUSession::ProcessPeerTest (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint) + { + uint8_t * buf1 = buf; + uint32_t nonce = be32toh (*(uint32_t *)buf); + buf += 4; // nonce + uint8_t size = *buf; + buf++; // size + uint8_t * address = (size == 4) ? buf : nullptr; + buf += size; // address + uint16_t port = *(uint16_t *)buf; // use it as is + buf += 2; // port + uint8_t * introKey = buf; + if (port) + { + LogPrint ("SSU peer test. We are Charlie"); + Send (PAYLOAD_TYPE_PEER_TEST, buf1, len); // back to Bob + if (address) + SendPeerTest (nonce, be32toh (*(uint32_t *)address), be16toh (port), introKey); // to Alice + else + LogPrint ("Address of ", size, " bytes not supported"); + } + else + { + LogPrint ("SSU peer test. We are Bob"); + // TODO: + } + } + + void SSUSession::SendPeerTest (uint32_t nonce, uint32_t address, uint16_t port, uint8_t * introKey) + { + uint8_t buf[80 + 18]; + uint8_t iv[16]; + uint8_t * payload = buf + sizeof (SSUHeader); + *(uint32_t *)payload = htobe32 (nonce); + payload += 4; // nonce + *payload = 4; + payload++; // size + *(uint32_t *)payload = htobe32 (address); + payload += 4; // address + *(uint16_t *)payload = htobe32 (port); + payload += 2; // port + memcpy (payload, introKey, 32); // intro key + + CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator (); + rnd.GenerateBlock (iv, 16); // random iv + // encrypt message with specified intro key + FillHeaderAndEncrypt (PAYLOAD_TYPE_PEER_TEST, buf, 80, introKey, iv, introKey); + boost::asio::ip::udp::endpoint e (boost::asio::ip::address_v4 (address), port); + m_Server.Send (buf, 80, e); + } + + void SSUSession::SendPeerTest () + { + LogPrint ("SSU sending peer test"); + auto address = i2p::context.GetRouterInfo ().GetSSUAddress (); + if (!address) + { + LogPrint ("SSU is not supported. Can't send peer test"); + return; + } + auto introKey = address->key; + uint8_t buf[80 + 18]; + uint8_t * payload = buf + sizeof (SSUHeader); + CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator (); + uint32_t nonce = 0; + rnd.GenerateWord32 (nonce); + *(uint32_t *)payload = htobe32 (nonce); + payload += 4; // nonce + *payload = 4; + payload++; // size + memset (payload, 0, 6); // address and port always zero for Alice + payload += 6; // address and port + memcpy (payload, introKey, 32); // intro key + uint8_t iv[16]; + rnd.GenerateBlock (iv, 16); // random iv + // encrypt message with session key + FillHeaderAndEncrypt (PAYLOAD_TYPE_PEER_TEST, buf, 80, m_SessionKey, iv, m_MacKey); + m_Server.Send (buf, 80, m_RemoteEndpoint); + } + void SSUSession::SendMsgAck (uint32_t msgID) { uint8_t buf[48 + 18]; // actual length is 44 = 37 + 7 but pad it to multiple of 16 @@ -704,21 +817,11 @@ namespace ssu CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator (); rnd.GenerateBlock (iv, 16); // random iv if (m_State == eSessionStateEstablished) + { // encrypt message with session key FillHeaderAndEncrypt (PAYLOAD_TYPE_SESSION_DESTROYED, buf, 48, m_SessionKey, iv, m_MacKey); - else - { - auto introKey = GetIntroKey (); - if (introKey) - // encrypt message with intro key - FillHeaderAndEncrypt (PAYLOAD_TYPE_SESSION_DESTROYED, buf, 48, introKey, iv, introKey); - else - { - LogPrint ("SSU: can't send SessionDestroyed message"); - return; - } + m_Server.Send (buf, 48, m_RemoteEndpoint); } - m_Server.Send (buf, 48, m_RemoteEndpoint); } void SSUSession::Send (i2p::I2NPMessage * msg) @@ -769,8 +872,26 @@ namespace ssu len = 0; fragmentNum++; } - } - + } + + void SSUSession::Send (uint8_t type, const uint8_t * payload, size_t len) + { + uint8_t buf[SSU_MTU + 18]; + uint8_t iv[16]; + size_t msgSize = len + sizeof (SSUHeader); + if (msgSize > SSU_MTU) + { + LogPrint ("SSU payload size ", msgSize, " exceeds MTU"); + return; + } + memcpy (buf + sizeof (SSUHeader), payload, len); + CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator (); + rnd.GenerateBlock (iv, 16); // random iv + // encrypt message with session key + FillHeaderAndEncrypt (type, buf, msgSize, m_SessionKey, iv, m_MacKey); + m_Server.Send (buf, msgSize, m_RemoteEndpoint); + } + SSUServer::SSUServer (boost::asio::io_service& service, int port): m_Endpoint (boost::asio::ip::udp::v4 (), port), m_Socket (service, m_Endpoint) { @@ -841,7 +962,7 @@ namespace ssu return nullptr; } - SSUSession * SSUServer::GetSession (const i2p::data::RouterInfo * router) + SSUSession * SSUServer::GetSession (const i2p::data::RouterInfo * router, bool peerTest) { SSUSession * session = nullptr; if (router) @@ -859,10 +980,10 @@ namespace ssu if (!router->UsesIntroducer ()) { // connect directly - session = new SSUSession (*this, remoteEndpoint, router); + session = new SSUSession (*this, remoteEndpoint, router, peerTest); m_Sessions[remoteEndpoint] = session; - LogPrint ("New SSU session to [", router->GetIdentHashAbbreviation (), "] ", - remoteEndpoint.address ().to_string (), ":", remoteEndpoint.port (), " created"); + LogPrint ("Creating new SSU session to [", router->GetIdentHashAbbreviation (), "] ", + remoteEndpoint.address ().to_string (), ":", remoteEndpoint.port ()); session->Connect (); } else @@ -872,11 +993,20 @@ namespace ssu { auto& introducer = address->introducers[0]; // TODO: boost::asio::ip::udp::endpoint introducerEndpoint (introducer.iHost, introducer.iPort); - session = new SSUSession (*this, introducerEndpoint, router); - m_Sessions[introducerEndpoint] = session; - LogPrint ("New SSU session to [", router->GetIdentHashAbbreviation (), - "] through introducer ", introducerEndpoint.address ().to_string (), ":", introducerEndpoint.port ()); - session->ConnectThroughIntroducer (introducer); + it = m_Sessions.find (introducerEndpoint); + if (it == m_Sessions.end ()) + { + session = new SSUSession (*this, introducerEndpoint, router); + m_Sessions[introducerEndpoint] = session; + LogPrint ("Creating new SSU session to [", router->GetIdentHashAbbreviation (), + "] through introducer ", introducerEndpoint.address ().to_string (), ":", introducerEndpoint.port ()); + session->ConnectThroughIntroducer (introducer); + } + else + { + LogPrint ("Session to introducer already exists"); + // TODO: + } } else LogPrint ("Router is unreachable, but not introducers presentd. Ignored"); @@ -917,7 +1047,7 @@ namespace ssu auto session = it->second; m_Sessions.erase (it); m_Sessions[newEndpoint] = session; - LogPrint ("SSU session ressigned from ", oldEndpoint.address ().to_string (), ":", oldEndpoint.port (), + LogPrint ("SSU session reassigned from ", oldEndpoint.address ().to_string (), ":", oldEndpoint.port (), " to ", newEndpoint.address ().to_string (), ":", newEndpoint.port ()); } } diff --git a/SSU.h b/SSU.h index e528d30b..ab60c71b 100644 --- a/SSU.h +++ b/SSU.h @@ -23,11 +23,14 @@ namespace ssu uint8_t iv[16]; uint8_t flag; uint32_t time; + + uint8_t GetPayloadType () const { return flag >> 4; }; }; #pragma pack() - const int SSU_MTU = 1484; + const size_t SSU_MTU = 1484; const int SSU_CONNECT_TIMEOUT = 5; // 5 seconds + const int SSU_TERMINATION_TIMEOUT = 330; // 5.5 minutes // payload types (4 bits) const uint8_t PAYLOAD_TYPE_SESSION_REQUEST = 0; @@ -37,7 +40,7 @@ namespace ssu const uint8_t PAYLOAD_TYPE_RELAY_RESPONSE = 4; const uint8_t PAYLOAD_TYPE_RELAY_INTRO = 5; const uint8_t PAYLOAD_TYPE_DATA = 6; - const uint8_t PAYLOAD_TYPE_TEST = 7; + const uint8_t PAYLOAD_TYPE_PEER_TEST = 7; const uint8_t PAYLOAD_TYPE_SESSION_DESTROYED = 8; // data flags @@ -70,7 +73,7 @@ namespace ssu public: SSUSession (SSUServer& server, boost::asio::ip::udp::endpoint& remoteEndpoint, - const i2p::data::RouterInfo * router = nullptr); + const i2p::data::RouterInfo * router = nullptr, bool peerTest = false); void ProcessNextMessage (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint); ~SSUSession (); @@ -80,34 +83,43 @@ namespace ssu boost::asio::ip::udp::endpoint& GetRemoteEndpoint () { return m_RemoteEndpoint; }; const i2p::data::RouterInfo * GetRemoteRouter () const { return m_RemoteRouter; }; void SendI2NPMessage (I2NPMessage * msg); - + void SendPeerTest (); // Alice + private: void CreateAESandMacKey (uint8_t * pubKey, uint8_t * aesKey, uint8_t * macKey); - void ProcessMessage (uint8_t * buf, size_t len); // call for established session + void ProcessMessage (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint); // call for established session + void ProcessIntroKeyMessage (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint); // call for non-established session + void ProcessSessionRequest (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint); void SendSessionRequest (); void SendRelayRequest (const i2p::data::RouterInfo::Introducer& introducer); void ProcessSessionCreated (uint8_t * buf, size_t len); void SendSessionCreated (const uint8_t * x); void ProcessSessionConfirmed (uint8_t * buf, size_t len); - void SendSessionConfirmed (const uint8_t * y, const uint8_t * ourAddress, uint32_t relayTag); + void SendSessionConfirmed (const uint8_t * y, const uint8_t * ourAddress); void ProcessRelayResponse (uint8_t * buf, size_t len); + void ProcessRelayIntro (uint8_t * buf, size_t len); void Established (); void Failed (); void HandleConnectTimer (const boost::system::error_code& ecode); - void ProcessData (uint8_t * buf, size_t len); + void ProcessPeerTest (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint); + void SendPeerTest (uint32_t nonce, uint32_t address, uint16_t port, uint8_t * introKey); // Charlie to Alice + void ProcessData (uint8_t * buf, size_t len); void SendMsgAck (uint32_t msgID); void SendSesionDestroyed (); void Send (i2p::I2NPMessage * msg); + void Send (uint8_t type, const uint8_t * payload, size_t len); // with session key - bool ProcessIntroKeyEncryptedMessage (uint8_t expectedPayloadType, uint8_t * buf, size_t len); void FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * buf, size_t len, const uint8_t * aesKey, const uint8_t * iv, const uint8_t * macKey); void Decrypt (uint8_t * buf, size_t len, const uint8_t * aesKey); bool Validate (uint8_t * buf, size_t len, const uint8_t * macKey); const uint8_t * GetIntroKey () const; + void ScheduleTermination (); + void HandleTerminationTimer (const boost::system::error_code& ecode); + private: SSUServer& m_Server; @@ -115,7 +127,9 @@ namespace ssu const i2p::data::RouterInfo * m_RemoteRouter; boost::asio::deadline_timer m_Timer; i2p::data::DHKeysPair * m_DHKeysPair; // X - for client and Y - for server - SessionState m_State; + bool m_PeerTest; + SessionState m_State; + uint32_t m_RelayTag; CryptoPP::CBC_Mode::Encryption m_Encryption; CryptoPP::CBC_Mode::Decryption m_Decryption; uint8_t m_SessionKey[32], m_MacKey[32]; @@ -131,7 +145,7 @@ namespace ssu ~SSUServer (); void Start (); void Stop (); - SSUSession * GetSession (const i2p::data::RouterInfo * router); + SSUSession * GetSession (const i2p::data::RouterInfo * router, bool peerTest = false); SSUSession * FindSession (const i2p::data::RouterInfo * router); void DeleteSession (SSUSession * session); void DeleteAllSessions (); diff --git a/Streaming.cpp b/Streaming.cpp index 9291235a..090a0d04 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -345,7 +345,7 @@ namespace stream StreamingDestination::StreamingDestination (): m_LeaseSet (nullptr) { m_Keys = i2p::data::CreateRandomKeys (); - m_IdentHash = i2p::data::CalculateIdentHash (m_Keys.pub); + m_IdentHash = m_Keys.pub.Hash (); m_SigningPrivateKey.Initialize (i2p::crypto::dsap, i2p::crypto::dsaq, i2p::crypto::dsag, CryptoPP::Integer (m_Keys.signingPrivateKey, 20)); CryptoPP::DH dh (i2p::crypto::elgp, i2p::crypto::elgg); diff --git a/Transports.cpp b/Transports.cpp index 9d86d605..d7bdd634 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -92,7 +92,6 @@ namespace i2p m_DHKeysPairSupplier.Start (); m_IsRunning = true; m_Thread = new std::thread (std::bind (&Transports::Run, this)); - m_Timer = new boost::asio::deadline_timer (m_Service); // create acceptors auto addresses = context.GetRouterInfo ().GetAddresses (); for (auto& address : addresses) @@ -124,22 +123,16 @@ namespace i2p void Transports::Stop () { - for (auto session: m_NTCPSessions) - delete session.second; - m_NTCPSessions.clear (); - delete m_NTCPAcceptor; - - if (m_Timer) - { - m_Timer->cancel (); - delete m_Timer; - } - if (m_SSUServer) { m_SSUServer->Stop (); delete m_SSUServer; - } + } + + for (auto session: m_NTCPSessions) + delete session.second; + m_NTCPSessions.clear (); + delete m_NTCPAcceptor; m_DHKeysPairSupplier.Stop (); m_IsRunning = false; @@ -270,24 +263,10 @@ namespace i2p { auto router = i2p::data::netdb.GetRandomRouter (); if (router && router->IsSSU () && m_SSUServer) - m_SSUServer->GetSession (router); - } - if (m_Timer) - { - m_Timer->expires_from_now (boost::posix_time::seconds(5)); // 5 seconds - m_Timer->async_wait (boost::bind (&Transports::HandleTimer, this, boost::asio::placeholders::error)); + m_SSUServer->GetSession (router, true); // peer test } } - void Transports::HandleTimer (const boost::system::error_code& ecode) - { - if (ecode != boost::asio::error::operation_aborted) - { - // end of external IP detection - if (m_SSUServer) - m_SSUServer->DeleteAllSessions (); - } - } i2p::data::DHKeysPair * Transports::GetNextDHKeysPair () { diff --git a/Transports.h b/Transports.h index bcec7690..b0ff8455 100644 --- a/Transports.h +++ b/Transports.h @@ -71,7 +71,6 @@ namespace i2p void PostMessage (const i2p::data::IdentHash& ident, i2p::I2NPMessage * msg); void DetectExternalIP (); - void HandleTimer (const boost::system::error_code& ecode); private: @@ -83,7 +82,6 @@ namespace i2p std::map m_NTCPSessions; i2p::ssu::SSUServer * m_SSUServer; - boost::asio::deadline_timer * m_Timer; DHKeysPairSupplier m_DHKeysPairSupplier; diff --git a/TunnelPool.cpp b/TunnelPool.cpp index 33f6cbda..875a51f6 100644 --- a/TunnelPool.cpp +++ b/TunnelPool.cpp @@ -169,14 +169,14 @@ namespace tunnel *m_OutboundTunnels.begin () : tunnels.GetNextOutboundTunnel (); LogPrint ("Creating destination inbound tunnel..."); auto firstHop = i2p::data::netdb.GetRandomRouter (outboundTunnel ? outboundTunnel->GetEndpointRouter () : nullptr); - auto secondHop = i2p::data::netdb.GetRandomRouter (firstHop); + auto secondHop = outboundTunnel ? outboundTunnel->GetTunnelConfig ()->GetFirstHop ()->router : nullptr; + if (!secondHop || secondHop->GetIdentHash () == i2p::context.GetIdentHash ()) + secondHop = i2p::data::netdb.GetRandomRouter (firstHop); auto * tunnel = tunnels.CreateTunnel ( new TunnelConfig (std::vector { firstHop, secondHop - // TODO: switch to 3-hops later - /*i2p::data::netdb.GetRandomRouter (secondHop) */ }), outboundTunnel); tunnel->SetTunnelPool (this); diff --git a/util.cpp b/util.cpp index 40e585aa..8b95e2e3 100644 --- a/util.cpp +++ b/util.cpp @@ -249,6 +249,11 @@ namespace http } } + std::string url::portstr_ = "80"; + unsigned int url::port_ = 80; + std::string url::user_ = ""; + std::string url::pass_ = ""; + url::url(const std::string& url_s) { parse(url_s); diff --git a/util.h b/util.h index 5f8a2b13..8766a516 100644 --- a/util.h +++ b/util.h @@ -41,10 +41,10 @@ namespace util void parse(const std::string& url_s); public: std::string protocol_, host_, path_, query_; - std::string portstr_ = "80"; - unsigned int port_ = 80; - std::string user_ = ""; - std::string pass_ = ""; + static std::string portstr_; + static unsigned int port_; + static std::string user_; + static std::string pass_; }; } }