diff --git a/SSUSession.cpp b/SSUSession.cpp new file mode 100644 index 00000000..1cb3c162 --- /dev/null +++ b/SSUSession.cpp @@ -0,0 +1,1012 @@ +#include +#include +#include +#include "CryptoConst.h" +#include "hmac.h" +#include "Log.h" +#include "Timestamp.h" +#include "RouterContext.h" +#include "Transports.h" +#include "SSU.h" +#include "SSUSession.h" + +namespace i2p +{ +namespace transport +{ + SSUSession::SSUSession (SSUServer& server, boost::asio::ip::udp::endpoint& remoteEndpoint, + const i2p::data::RouterInfo * router, bool peerTest ): TransportSession (router), + m_Server (server), m_RemoteEndpoint (remoteEndpoint), + m_Timer (m_Server.GetService ()), m_PeerTest (peerTest), + m_State (eSessionStateUnknown), m_IsSessionKey (false), m_RelayTag (0), + m_Data (*this), m_NumSentBytes (0), m_NumReceivedBytes (0) + { + m_CreationTime = i2p::util::GetSecondsSinceEpoch (); + if (!router) // incoming session + ScheduleConnectTimer (); + } + + SSUSession::~SSUSession () + { + } + + void SSUSession::CreateAESandMacKey (const uint8_t * pubKey) + { + CryptoPP::DH dh (i2p::crypto::elgp, i2p::crypto::elgg); + uint8_t sharedKey[256]; + if (!dh.Agree (sharedKey, m_DHKeysPair->privateKey, pubKey)) + { + LogPrint (eLogError, "Couldn't create shared key"); + return; + }; + + if (sharedKey[0] & 0x80) + { + m_SessionKey[0] = 0; + memcpy (m_SessionKey + 1, sharedKey, 31); + memcpy (m_MacKey, sharedKey + 31, 32); + } + else if (sharedKey[0]) + { + memcpy (m_SessionKey, sharedKey, 32); + memcpy (m_MacKey, sharedKey + 32, 32); + } + else + { + // find first non-zero byte + uint8_t * nonZero = sharedKey + 1; + while (!*nonZero) + { + nonZero++; + if (nonZero - sharedKey > 32) + { + LogPrint ("First 32 bytes of shared key is all zeros. Ignored"); + return; + } + } + + memcpy (m_SessionKey, nonZero, 32); + CryptoPP::SHA256().CalculateDigest(m_MacKey, nonZero, 64 - (nonZero - sharedKey)); + } + m_IsSessionKey = true; + m_SessionKeyEncryption.SetKey (m_SessionKey); + m_SessionKeyDecryption.SetKey (m_SessionKey); + } + + void SSUSession::ProcessNextMessage (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint) + { + m_NumReceivedBytes += len; + if (m_State == eSessionStateIntroduced) + { + // HolePunch received + LogPrint ("SSU HolePunch of ", len, " bytes received"); + m_State = eSessionStateUnknown; + Connect (); + } + else + { + if (!len) return; // ignore zero-length packets + if (m_State == eSessionStateEstablished) + ScheduleTermination (); + + if (m_IsSessionKey && Validate (buf, len, m_MacKey)) // try session key first + DecryptSessionKey (buf, len); + else + { + // try intro key depending on side + auto introKey = GetIntroKey (); + if (introKey && Validate (buf, len, introKey)) + Decrypt (buf, len, introKey); + else + { + // try own intro key + auto address = i2p::context.GetRouterInfo ().GetSSUAddress (); + if (!address) + { + LogPrint (eLogError, "SSU is not supported"); + return; + } + if (Validate (buf, len, address->key)) + Decrypt (buf, len, address->key); + else + { + LogPrint (eLogError, "MAC verification failed ", len, " bytes from ", senderEndpoint); + m_Server.DeleteSession (this); + return; + } + } + } + // successfully decrypted + ProcessMessage (buf, len, senderEndpoint); + } + } + + void SSUSession::ProcessMessage (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint) + { + SSUHeader * header = (SSUHeader *)buf; + switch (header->GetPayloadType ()) + { + case PAYLOAD_TYPE_DATA: + LogPrint (eLogDebug, "SSU data received"); + ProcessData (buf + sizeof (SSUHeader), len - sizeof (SSUHeader)); + break; + case PAYLOAD_TYPE_SESSION_REQUEST: + ProcessSessionRequest (buf, len, senderEndpoint); + break; + case PAYLOAD_TYPE_SESSION_CREATED: + ProcessSessionCreated (buf, len); + break; + case PAYLOAD_TYPE_SESSION_CONFIRMED: + ProcessSessionConfirmed (buf, len); + break; + case PAYLOAD_TYPE_PEER_TEST: + LogPrint (eLogDebug, "SSU peer test received"); + ProcessPeerTest (buf + sizeof (SSUHeader), len - sizeof (SSUHeader), senderEndpoint); + break; + case PAYLOAD_TYPE_SESSION_DESTROYED: + { + LogPrint (eLogDebug, "SSU session destroy received"); + m_Server.DeleteSession (this); // delete this + break; + } + case PAYLOAD_TYPE_RELAY_RESPONSE: + ProcessRelayResponse (buf, len); + if (m_State != eSessionStateEstablished) + m_Server.DeleteSession (this); + break; + case PAYLOAD_TYPE_RELAY_REQUEST: + LogPrint (eLogDebug, "SSU relay request received"); + ProcessRelayRequest (buf + sizeof (SSUHeader), len - sizeof (SSUHeader), senderEndpoint); + break; + case PAYLOAD_TYPE_RELAY_INTRO: + LogPrint (eLogDebug, "SSU relay intro received"); + ProcessRelayIntro (buf + sizeof (SSUHeader), len - sizeof (SSUHeader)); + break; + default: + LogPrint (eLogWarning, "Unexpected SSU payload type ", (int)header->GetPayloadType ()); + } + } + + void SSUSession::ProcessSessionRequest (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint) + { + LogPrint (eLogDebug, "Session request received"); + m_RemoteEndpoint = senderEndpoint; + if (!m_DHKeysPair) + m_DHKeysPair = transports.GetNextDHKeysPair (); + CreateAESandMacKey (buf + sizeof (SSUHeader)); + SendSessionCreated (buf + sizeof (SSUHeader)); + } + + void SSUSession::ProcessSessionCreated (uint8_t * buf, size_t len) + { + if (!m_RemoteRouter || !m_DHKeysPair) + { + LogPrint (eLogWarning, "Unsolicited session created message"); + return; + } + + LogPrint (eLogDebug, "Session created received"); + m_Timer.cancel (); // connect timer + SignedData s; // x,y, our IP, our port, remote IP, remote port, relayTag, signed on time + uint8_t * payload = buf + sizeof (SSUHeader); + uint8_t * y = payload; + CreateAESandMacKey (y); + s.Insert (m_DHKeysPair->publicKey, 256); // x + s.Insert (y, 256); // y + payload += 256; + uint8_t addressSize = *payload; + payload += 1; // size + uint8_t * ourAddress = payload; + boost::asio::ip::address ourIP; + if (addressSize == 4) // v4 + { + boost::asio::ip::address_v4::bytes_type bytes; + memcpy (bytes.data (), ourAddress, 4); + ourIP = boost::asio::ip::address_v4 (bytes); + } + else // v6 + { + boost::asio::ip::address_v6::bytes_type bytes; + memcpy (bytes.data (), ourAddress, 16); + ourIP = boost::asio::ip::address_v6 (bytes); + } + s.Insert (ourAddress, addressSize); // our IP + payload += addressSize; // address + uint16_t ourPort = be16toh (*(uint16_t *)payload); + s.Insert (payload, 2); // our port + payload += 2; // port + LogPrint ("Our external address is ", ourIP.to_string (), ":", ourPort); + i2p::context.UpdateAddress (ourIP); + if (m_RemoteEndpoint.address ().is_v4 ()) + s.Insert (m_RemoteEndpoint.address ().to_v4 ().to_bytes ().data (), 4); // remote IP v4 + else + s.Insert (m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data (), 16); // remote IP v6 + s.Insert (htobe16 (m_RemoteEndpoint.port ())); // remote port + s.Insert (payload, 8); // relayTag and signed on time + m_RelayTag = be32toh (*(uint32_t *)payload); + payload += 4; // relayTag + payload += 4; // signed on time + // decrypt signature + size_t signatureLen = m_RemoteIdentity.GetSignatureLen (); + size_t paddingSize = signatureLen & 0x0F; // %16 + if (paddingSize > 0) signatureLen += (16 - paddingSize); + m_SessionKeyDecryption.SetIV (((SSUHeader *)buf)->iv); + m_SessionKeyDecryption.Decrypt (payload, signatureLen, payload); + // verify + if (!s.Verify (m_RemoteIdentity, payload)) + LogPrint (eLogError, "SSU signature verification failed"); + + SendSessionConfirmed (y, ourAddress, addressSize + 2); + } + + void SSUSession::ProcessSessionConfirmed (uint8_t * buf, size_t len) + { + LogPrint (eLogDebug, "Session confirmed received"); + uint8_t * payload = buf + sizeof (SSUHeader); + payload++; // identity fragment info + uint16_t identitySize = be16toh (*(uint16_t *)payload); + payload += 2; // size of identity fragment + m_RemoteIdentity.FromBuffer (payload, identitySize); + m_Data.UpdatePacketSize (m_RemoteIdentity.GetIdentHash ()); + payload += identitySize; // identity + payload += 4; // signed-on time + size_t paddingSize = (payload - buf) + m_RemoteIdentity.GetSignatureLen (); + paddingSize &= 0x0F; // %16 + if (paddingSize > 0) paddingSize = 16 - paddingSize; + payload += paddingSize; + // TODO: verify signature (need data from session request), payload points to signature + SendI2NPMessage (CreateDeliveryStatusMsg (0)); + Established (); + } + + void SSUSession::SendSessionRequest () + { + auto introKey = GetIntroKey (); + if (!introKey) + { + LogPrint (eLogError, "SSU is not supported"); + return; + } + + uint8_t buf[320 + 18]; // 304 bytes for ipv4, 320 for ipv6 + uint8_t * payload = buf + sizeof (SSUHeader); + memcpy (payload, m_DHKeysPair->publicKey, 256); // x + bool isV4 = m_RemoteEndpoint.address ().is_v4 (); + if (isV4) + { + payload[256] = 4; + memcpy (payload + 257, m_RemoteEndpoint.address ().to_v4 ().to_bytes ().data(), 4); + } + else + { + payload[256] = 16; + memcpy (payload + 257, m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data(), 16); + } + + uint8_t iv[16]; + CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator (); + rnd.GenerateBlock (iv, 16); // random iv + FillHeaderAndEncrypt (PAYLOAD_TYPE_SESSION_REQUEST, buf, isV4 ? 304 : 320, introKey, iv, introKey); + m_Server.Send (buf, isV4 ? 304 : 320, m_RemoteEndpoint); + } + + void SSUSession::SendRelayRequest (uint32_t iTag, const uint8_t * iKey) + { + auto address = i2p::context.GetRouterInfo ().GetSSUAddress (); + if (!address) + { + LogPrint (eLogError, "SSU is not supported"); + return; + } + + uint8_t buf[96 + 18]; + uint8_t * payload = buf + sizeof (SSUHeader); + *(uint32_t *)payload = htobe32 (iTag); + payload += 4; + *payload = 0; // no address + payload++; + *(uint16_t *)payload = 0; // port = 0 + payload += 2; + *payload = 0; // challenge + payload++; + memcpy (payload, (const uint8_t *)address->key, 32); + payload += 32; + CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator (); + *(uint32_t *)payload = htobe32 (rnd.GenerateWord32 ()); // nonce + + uint8_t iv[16]; + rnd.GenerateBlock (iv, 16); // random iv + if (m_State == eSessionStateEstablished) + FillHeaderAndEncrypt (PAYLOAD_TYPE_RELAY_REQUEST, buf, 96, m_SessionKey, iv, m_MacKey); + else + FillHeaderAndEncrypt (PAYLOAD_TYPE_RELAY_REQUEST, buf, 96, iKey, iv, iKey); + m_Server.Send (buf, 96, m_RemoteEndpoint); + } + + void SSUSession::SendSessionCreated (const uint8_t * x) + { + auto introKey = GetIntroKey (); + auto address = IsV6 () ? i2p::context.GetRouterInfo ().GetSSUV6Address () : + i2p::context.GetRouterInfo ().GetSSUAddress (true); //v4 only + if (!introKey || !address) + { + LogPrint (eLogError, "SSU is not supported"); + return; + } + CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator (); + SignedData s; // x,y, remote IP, remote port, our IP, our port, relayTag, signed on time + s.Insert (x, 256); // x + + uint8_t buf[384 + 18]; + uint8_t * payload = buf + sizeof (SSUHeader); + memcpy (payload, m_DHKeysPair->publicKey, 256); + s.Insert (payload, 256); // y + payload += 256; + if (m_RemoteEndpoint.address ().is_v4 ()) + { + // ipv4 + *payload = 4; + payload++; + memcpy (payload, m_RemoteEndpoint.address ().to_v4 ().to_bytes ().data(), 4); + s.Insert (payload, 4); // remote endpoint IP V4 + payload += 4; + } + else + { + // ipv6 + *payload = 16; + payload++; + memcpy (payload, m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data(), 16); + s.Insert (payload, 16); // remote endpoint IP V6 + payload += 16; + } + *(uint16_t *)(payload) = htobe16 (m_RemoteEndpoint.port ()); + s.Insert (payload, 2); // remote port + payload += 2; + if (address->host.is_v4 ()) + s.Insert (address->host.to_v4 ().to_bytes ().data (), 4); // our IP V4 + else + s.Insert (address->host.to_v6 ().to_bytes ().data (), 16); // our IP V6 + s.Insert (htobe16 (address->port)); // our port + uint32_t relayTag = 0; + if (i2p::context.GetRouterInfo ().IsIntroducer ()) + { + relayTag = rnd.GenerateWord32 (); + if (!relayTag) relayTag = 1; + m_Server.AddRelay (relayTag, m_RemoteEndpoint); + } + *(uint32_t *)(payload) = htobe32 (relayTag); + payload += 4; // relay tag + *(uint32_t *)(payload) = htobe32 (i2p::util::GetSecondsSinceEpoch ()); // signed on time + payload += 4; + s.Insert (payload - 8, 8); // relayTag and signed on time + s.Sign (i2p::context.GetPrivateKeys (), payload); // DSA signature + // TODO: fill padding with random data + + uint8_t iv[16]; + rnd.GenerateBlock (iv, 16); // random iv + // encrypt signature and padding with newly created session key + size_t signatureLen = i2p::context.GetIdentity ().GetSignatureLen (); + size_t paddingSize = signatureLen & 0x0F; // %16 + if (paddingSize > 0) signatureLen += (16 - paddingSize); + m_SessionKeyEncryption.SetIV (iv); + m_SessionKeyEncryption.Encrypt (payload, signatureLen, payload); + payload += signatureLen; + size_t msgLen = payload - buf; + + // encrypt message with intro key + FillHeaderAndEncrypt (PAYLOAD_TYPE_SESSION_CREATED, buf, msgLen, introKey, iv, introKey); + Send (buf, msgLen); + } + + void SSUSession::SendSessionConfirmed (const uint8_t * y, const uint8_t * ourAddress, size_t ourAddressLen) + { + uint8_t buf[512 + 18]; + uint8_t * payload = buf + sizeof (SSUHeader); + *payload = 1; // 1 fragment + payload++; // info + size_t identLen = i2p::context.GetIdentity ().GetFullLen (); // 387+ bytes + *(uint16_t *)(payload) = htobe16 (identLen); + payload += 2; // cursize + i2p::context.GetIdentity ().ToBuffer (payload, identLen); + payload += identLen; + uint32_t signedOnTime = i2p::util::GetSecondsSinceEpoch (); + *(uint32_t *)(payload) = htobe32 (signedOnTime); // signed on time + payload += 4; + auto signatureLen = i2p::context.GetIdentity ().GetSignatureLen (); + size_t paddingSize = ((payload - buf) + signatureLen)%16; + if (paddingSize > 0) paddingSize = 16 - paddingSize; + // TODO: fill padding + payload += paddingSize; // padding size + + // signature + SignedData s; // x,y, our IP, our port, remote IP, remote port, relayTag, our signed on time + s.Insert (m_DHKeysPair->publicKey, 256); // x + s.Insert (y, 256); // y + s.Insert (ourAddress, ourAddressLen); // our address/port as seem by party + if (m_RemoteEndpoint.address ().is_v4 ()) + s.Insert (m_RemoteEndpoint.address ().to_v4 ().to_bytes ().data (), 4); // remote IP V4 + else + s.Insert (m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data (), 16); // remote IP V6 + s.Insert (htobe16 (m_RemoteEndpoint.port ())); // remote port + s.Insert (htobe32 (m_RelayTag)); // relay tag + s.Insert (htobe32 (signedOnTime)); // signed on time + s.Sign (i2p::context.GetPrivateKeys (), payload); // DSA signature + payload += signatureLen; + + size_t msgLen = payload - buf; + uint8_t iv[16]; + CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator (); + rnd.GenerateBlock (iv, 16); // random iv + // encrypt message with session key + FillHeaderAndEncrypt (PAYLOAD_TYPE_SESSION_CONFIRMED, buf, msgLen, m_SessionKey, iv, m_MacKey); + Send (buf, msgLen); + } + + void SSUSession::ProcessRelayRequest (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& from) + { + uint32_t relayTag = be32toh (*(uint32_t *)buf); + auto session = m_Server.FindRelaySession (relayTag); + if (session) + { + buf += 4; // relay tag + uint8_t size = *buf; + buf++; // size + buf += size; // address + buf += 2; // port + uint8_t challengeSize = *buf; + buf++; // challenge size + buf += challengeSize; + uint8_t * introKey = buf; + buf += 32; // introkey + uint32_t nonce = be32toh (*(uint32_t *)buf); + SendRelayResponse (nonce, from, introKey, session->m_RemoteEndpoint); + SendRelayIntro (session, from); + } + } + + void SSUSession::SendRelayResponse (uint32_t nonce, const boost::asio::ip::udp::endpoint& from, + const uint8_t * introKey, const boost::asio::ip::udp::endpoint& to) + { + uint8_t buf[80 + 18]; // 64 Alice's ipv4 and 80 Alice's ipv6 + uint8_t * payload = buf + sizeof (SSUHeader); + // Charlie's address always v4 + if (!to.address ().is_v4 ()) + { + LogPrint (eLogError, "Charlie's IP must be v4"); + return; + } + *payload = 4; + payload++; // size + *(uint32_t *)payload = htobe32 (to.address ().to_v4 ().to_ulong ()); // Charlie's IP + payload += 4; // address + *(uint16_t *)payload = htobe16 (to.port ()); // Charlie's port + payload += 2; // port + // Alice + bool isV4 = from.address ().is_v4 (); // Alice's + if (isV4) + { + *payload = 4; + payload++; // size + memcpy (payload, from.address ().to_v4 ().to_bytes ().data (), 4); // Alice's IP V4 + payload += 4; // address + } + else + { + *payload = 16; + payload++; // size + memcpy (payload, from.address ().to_v6 ().to_bytes ().data (), 16); // Alice's IP V6 + payload += 16; // address + } + *(uint16_t *)payload = htobe16 (from.port ()); // Alice's port + payload += 2; // port + *(uint32_t *)payload = htobe32 (nonce); + + if (m_State == eSessionStateEstablished) + { + // encrypt with session key + FillHeaderAndEncrypt (PAYLOAD_TYPE_RELAY_RESPONSE, buf, isV4 ? 64 : 80); + Send (buf, isV4 ? 64 : 80); + } + else + { + // ecrypt with Alice's intro key + uint8_t iv[16]; + CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator (); + rnd.GenerateBlock (iv, 16); // random iv + FillHeaderAndEncrypt (PAYLOAD_TYPE_RELAY_RESPONSE, buf, isV4 ? 64 : 80, introKey, iv, introKey); + m_Server.Send (buf, isV4 ? 64 : 80, from); + } + LogPrint (eLogDebug, "SSU relay response sent"); + } + + void SSUSession::SendRelayIntro (SSUSession * session, const boost::asio::ip::udp::endpoint& from) + { + if (!session) return; + // Alice's address always v4 + if (!from.address ().is_v4 ()) + { + LogPrint (eLogError, "Alice's IP must be v4"); + return; + } + uint8_t buf[48 + 18]; + uint8_t * payload = buf + sizeof (SSUHeader); + *payload = 4; + payload++; // size + *(uint32_t *)payload = htobe32 (from.address ().to_v4 ().to_ulong ()); // Alice's IP + payload += 4; // address + *(uint16_t *)payload = htobe16 (from.port ()); // Alice's port + payload += 2; // port + *payload = 0; // challenge size + uint8_t iv[16]; + CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator (); + rnd.GenerateBlock (iv, 16); // random iv + FillHeaderAndEncrypt (PAYLOAD_TYPE_RELAY_INTRO, buf, 48, session->m_SessionKey, iv, session->m_MacKey); + m_Server.Send (buf, 48, session->m_RemoteEndpoint); + LogPrint (eLogDebug, "SSU relay intro sent"); + } + + void SSUSession::ProcessRelayResponse (uint8_t * buf, size_t len) + { + LogPrint (eLogDebug, "Relay response received"); + uint8_t * payload = buf + sizeof (SSUHeader); + uint8_t remoteSize = *payload; + payload++; // remote size + //boost::asio::ip::address_v4 remoteIP (be32toh (*(uint32_t* )(payload))); + payload += remoteSize; // remote address + //uint16_t remotePort = be16toh (*(uint16_t *)(payload)); + payload += 2; // remote port + uint8_t ourSize = *payload; + payload++; // our size + boost::asio::ip::address ourIP; + if (ourSize == 4) + { + boost::asio::ip::address_v4::bytes_type bytes; + memcpy (bytes.data (), payload, 4); + ourIP = boost::asio::ip::address_v4 (bytes); + } + else + { + boost::asio::ip::address_v6::bytes_type bytes; + memcpy (bytes.data (), payload, 16); + ourIP = boost::asio::ip::address_v6 (bytes); + } + payload += ourSize; // our address + uint16_t ourPort = be16toh (*(uint16_t *)(payload)); + payload += 2; // our port + LogPrint ("Our external address is ", ourIP.to_string (), ":", ourPort); + i2p::context.UpdateAddress (ourIP); + } + + void SSUSession::ProcessRelayIntro (uint8_t * buf, size_t len) + { + uint8_t size = *buf; + if (size == 4) + { + 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, 0, boost::asio::ip::udp::endpoint (address, port)); + } + else + LogPrint (eLogWarning, "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) + { + if (len < sizeof (SSUHeader)) + { + LogPrint (eLogError, "Unexpected SSU packet length ", len); + return; + } + SSUHeader * header = (SSUHeader *)buf; + memcpy (header->iv, iv, 16); + header->flag = payloadType << 4; // MSB is 0 + header->time = htobe32 (i2p::util::GetSecondsSinceEpoch ()); + uint8_t * encrypted = &header->flag; + uint16_t encryptedLen = len - (encrypted - buf); + i2p::crypto::CBCEncryption encryption; + encryption.SetKey (aesKey); + encryption.SetIV (iv); + encryption.Encrypt (encrypted, encryptedLen, encrypted); + // assume actual buffer size is 18 (16 + 2) bytes more + memcpy (buf + len, iv, 16); + *(uint16_t *)(buf + len + 16) = htobe16 (encryptedLen); + i2p::crypto::HMACMD5Digest (encrypted, encryptedLen + 18, macKey, header->mac); + } + + void SSUSession::FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * buf, size_t len) + { + if (len < sizeof (SSUHeader)) + { + LogPrint (eLogError, "Unexpected SSU packet length ", len); + return; + } + SSUHeader * header = (SSUHeader *)buf; + i2p::context.GetRandomNumberGenerator ().GenerateBlock (header->iv, 16); // random iv + m_SessionKeyEncryption.SetIV (header->iv); + header->flag = payloadType << 4; // MSB is 0 + header->time = htobe32 (i2p::util::GetSecondsSinceEpoch ()); + uint8_t * encrypted = &header->flag; + uint16_t encryptedLen = len - (encrypted - buf); + m_SessionKeyEncryption.Encrypt (encrypted, encryptedLen, encrypted); + // assume actual buffer size is 18 (16 + 2) bytes more + memcpy (buf + len, header->iv, 16); + *(uint16_t *)(buf + len + 16) = htobe16 (encryptedLen); + i2p::crypto::HMACMD5Digest (encrypted, encryptedLen + 18, m_MacKey, header->mac); + } + + void SSUSession::Decrypt (uint8_t * buf, size_t len, const uint8_t * aesKey) + { + if (len < sizeof (SSUHeader)) + { + LogPrint (eLogError, "Unexpected SSU packet length ", len); + return; + } + SSUHeader * header = (SSUHeader *)buf; + uint8_t * encrypted = &header->flag; + uint16_t encryptedLen = len - (encrypted - buf); + i2p::crypto::CBCDecryption decryption; + decryption.SetKey (aesKey); + decryption.SetIV (header->iv); + decryption.Decrypt (encrypted, encryptedLen, encrypted); + } + + void SSUSession::DecryptSessionKey (uint8_t * buf, size_t len) + { + if (len < sizeof (SSUHeader)) + { + LogPrint (eLogError, "Unexpected SSU packet length ", len); + return; + } + SSUHeader * header = (SSUHeader *)buf; + uint8_t * encrypted = &header->flag; + uint16_t encryptedLen = len - (encrypted - buf); + if (encryptedLen > 0) + { + m_SessionKeyDecryption.SetIV (header->iv); + m_SessionKeyDecryption.Decrypt (encrypted, encryptedLen, encrypted); + } + } + + bool SSUSession::Validate (uint8_t * buf, size_t len, const uint8_t * macKey) + { + if (len < sizeof (SSUHeader)) + { + LogPrint (eLogError, "Unexpected SSU packet length ", len); + return false; + } + SSUHeader * header = (SSUHeader *)buf; + uint8_t * encrypted = &header->flag; + uint16_t encryptedLen = len - (encrypted - buf); + // assume actual buffer size is 18 (16 + 2) bytes more + memcpy (buf + len, header->iv, 16); + *(uint16_t *)(buf + len + 16) = htobe16 (encryptedLen); + uint8_t digest[16]; + i2p::crypto::HMACMD5Digest (encrypted, encryptedLen + 18, macKey, digest); + return !memcmp (header->mac, digest, 16); + } + + void SSUSession::Connect () + { + if (m_State == eSessionStateUnknown) + { + // set connect timer + ScheduleConnectTimer (); + m_DHKeysPair = transports.GetNextDHKeysPair (); + SendSessionRequest (); + } + } + + void SSUSession::ScheduleConnectTimer () + { + m_Timer.cancel (); + m_Timer.expires_from_now (boost::posix_time::seconds(SSU_CONNECT_TIMEOUT)); + m_Timer.async_wait (boost::bind (&SSUSession::HandleConnectTimer, + this, boost::asio::placeholders::error)); +} + + void SSUSession::HandleConnectTimer (const boost::system::error_code& ecode) + { + if (!ecode) + { + // timeout expired + LogPrint ("SSU session was not established after ", SSU_CONNECT_TIMEOUT, " second"); + Failed (); + } + } + + void SSUSession::Introduce (uint32_t iTag, const uint8_t * iKey) + { + if (m_State == eSessionStateUnknown) + { + // set connect timer + m_Timer.expires_from_now (boost::posix_time::seconds(SSU_CONNECT_TIMEOUT)); + m_Timer.async_wait (boost::bind (&SSUSession::HandleConnectTimer, + this, boost::asio::placeholders::error)); + } + SendRelayRequest (iTag, iKey); + } + + void SSUSession::WaitForIntroduction () + { + m_State = eSessionStateIntroduced; + // set connect timer + m_Timer.expires_from_now (boost::posix_time::seconds(SSU_CONNECT_TIMEOUT)); + m_Timer.async_wait (boost::bind (&SSUSession::HandleConnectTimer, + this, boost::asio::placeholders::error)); + } + + void SSUSession::Close () + { + SendSesionDestroyed (); + if (!m_DelayedMessages.empty ()) + { + for (auto it :m_DelayedMessages) + DeleteI2NPMessage (it); + m_DelayedMessages.clear (); + } + } + + void SSUSession::Established () + { + m_State = eSessionStateEstablished; + if (m_DHKeysPair) + { + delete m_DHKeysPair; + m_DHKeysPair = nullptr; + } + SendI2NPMessage (CreateDatabaseStoreMsg ()); + if (!m_DelayedMessages.empty ()) + { + for (auto it :m_DelayedMessages) + m_Data.Send (it); + m_DelayedMessages.clear (); + } + if (m_PeerTest && (m_RemoteRouter && m_RemoteRouter->IsPeerTesting ())) + SendPeerTest (); + ScheduleTermination (); + } + + void SSUSession::Failed () + { + if (m_State != eSessionStateFailed) + { + m_State = eSessionStateFailed; + 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) + { + // we are client + auto address = m_RemoteRouter->GetSSUAddress (); + return address ? (const uint8_t *)address->key : nullptr; + } + else + { + // we are server + auto address = i2p::context.GetRouterInfo ().GetSSUAddress (); + return address ? (const uint8_t *)address->key : nullptr; + } + } + + void SSUSession::SendI2NPMessage (I2NPMessage * msg) + { + m_Server.GetService ().post (boost::bind (&SSUSession::PostI2NPMessage, this, msg)); + } + + void SSUSession::PostI2NPMessage (I2NPMessage * msg) + { + if (msg) + { + if (m_State == eSessionStateEstablished) + m_Data.Send (msg); + else + m_DelayedMessages.push_back (msg); + } + } + + void SSUSession::ProcessData (uint8_t * buf, size_t len) + { + m_Data.ProcessMessage (buf, len); + } + + + 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 + uint32_t address = (size == 4) ? *(uint32_t *)buf : 0; // use it as is + buf += size; // address + uint16_t port = *(uint16_t *)buf; // use it as is + buf += 2; // port + uint8_t * introKey = buf; + if (port && !address) + { + LogPrint (eLogWarning, "Address of ", size, " bytes not supported"); + return; + } + if (m_PeerTestNonces.count (nonce) > 0) + { + // existing test + if (m_PeerTest) + { + LogPrint (eLogDebug, "SSU peer test from Bob. We are Alice"); + m_PeerTestNonces.erase (nonce); + m_PeerTest = false; + } + else if (port) + { + LogPrint (eLogDebug, "SSU peer test from Charlie. We are Bob"); + boost::asio::ip::udp::endpoint ep (boost::asio::ip::address_v4 (be32toh (address)), be16toh (port)); // Alice's address/port + auto session = m_Server.FindSession (ep); // find session with Alice + if (session) + session->Send (PAYLOAD_TYPE_PEER_TEST, buf1, len); // back to Alice + } + else + { + LogPrint (eLogDebug, "SSU peer test from Alice. We are Charlie"); + SendPeerTest (nonce, senderEndpoint.address ().to_v4 ().to_ulong (), + senderEndpoint.port (), introKey); // to Alice + } + } + else + { + if (m_State == eSessionStateEstablished) + { + // new test + m_PeerTestNonces.insert (nonce); + if (port) + { + LogPrint (eLogDebug, "SSU peer test from Bob. We are Charlie"); + Send (PAYLOAD_TYPE_PEER_TEST, buf1, len); // back to Bob + SendPeerTest (nonce, be32toh (address), be16toh (port), introKey); // to Alice + } + else + { + LogPrint (eLogDebug, "SSU peer test from Alice. We are Bob"); + auto session = m_Server.GetRandomEstablishedSession (this); // charlie + if (session) + session->SendPeerTest (nonce, senderEndpoint.address ().to_v4 ().to_ulong (), + senderEndpoint.port (), introKey, false); + } + } + else + LogPrint (eLogDebug, "SSU peer test from Charlie. We are Alice"); + } + } + + void SSUSession::SendPeerTest (uint32_t nonce, uint32_t address, uint16_t port, + const uint8_t * introKey, bool toAddress) + { + uint8_t buf[80 + 18]; + uint8_t iv[16]; + uint8_t * payload = buf + sizeof (SSUHeader); + *(uint32_t *)payload = htobe32 (nonce); + payload += 4; // nonce + if (address) + { + *payload = 4; + payload++; // size + *(uint32_t *)payload = htobe32 (address); + payload += 4; // address + } + else + { + *payload = 0; + payload++; //size + } + *(uint16_t *)payload = htobe16 (port); + payload += 2; // port + memcpy (payload, introKey, 32); // intro key + + CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator (); + rnd.GenerateBlock (iv, 16); // random iv + if (toAddress) + { + // 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); + } + else + { + // encrypt message with session key + FillHeaderAndEncrypt (PAYLOAD_TYPE_PEER_TEST, buf, 80); + Send (buf, 80); + } + } + + void SSUSession::SendPeerTest () + { + LogPrint (eLogDebug, "SSU sending peer test"); + auto address = i2p::context.GetRouterInfo ().GetSSUAddress (); + if (!address) + { + LogPrint (eLogError, "SSU is not supported. Can't send peer test"); + return; + } + uint32_t nonce = i2p::context.GetRandomNumberGenerator ().GenerateWord32 (); + if (!nonce) nonce = 1; + m_PeerTestNonces.insert (nonce); + SendPeerTest (nonce, 0, 0, address->key, false); // address and port always zero for Alice + } + + void SSUSession::SendKeepAlive () + { + if (m_State == eSessionStateEstablished) + { + uint8_t buf[48 + 18]; + uint8_t * payload = buf + sizeof (SSUHeader); + *payload = 0; // flags + payload++; + *payload = 0; // num fragments + // encrypt message with session key + FillHeaderAndEncrypt (PAYLOAD_TYPE_DATA, buf, 48); + Send (buf, 48); + LogPrint (eLogDebug, "SSU keep-alive sent"); + ScheduleTermination (); + } + } + + void SSUSession::SendSesionDestroyed () + { + if (m_IsSessionKey) + { + uint8_t buf[48 + 18]; + // encrypt message with session key + FillHeaderAndEncrypt (PAYLOAD_TYPE_SESSION_DESTROYED, buf, 48); + Send (buf, 48); + LogPrint (eLogDebug, "SSU session destroyed sent"); + } + } + + void SSUSession::Send (uint8_t type, const uint8_t * payload, size_t len) + { + uint8_t buf[SSU_MTU_V4 + 18]; + size_t msgSize = len + sizeof (SSUHeader); + if (msgSize > SSU_MTU_V4) + { + LogPrint (eLogWarning, "SSU payload size ", msgSize, " exceeds MTU"); + return; + } + memcpy (buf + sizeof (SSUHeader), payload, len); + // encrypt message with session key + FillHeaderAndEncrypt (type, buf, msgSize); + Send (buf, msgSize); + } + + void SSUSession::Send (const uint8_t * buf, size_t size) + { + m_NumSentBytes += size; + m_Server.Send (buf, size, m_RemoteEndpoint); + } +} +} + diff --git a/SSUSession.h b/SSUSession.h new file mode 100644 index 00000000..b6b4c97d --- /dev/null +++ b/SSUSession.h @@ -0,0 +1,143 @@ +#ifndef SSU_SESSION_H__ +#define SSU_SESSION_H__ + +#include +#include +#include +#include +#include "aes.h" +#include "I2NPProtocol.h" +#include "TransportSession.h" +#include "SSUData.h" + +namespace i2p +{ +namespace transport +{ +#pragma pack(1) + struct SSUHeader + { + uint8_t mac[16]; + uint8_t iv[16]; + uint8_t flag; + uint32_t time; + + uint8_t GetPayloadType () const { return flag >> 4; }; + }; +#pragma pack() + + 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; + const uint8_t PAYLOAD_TYPE_SESSION_CREATED = 1; + const uint8_t PAYLOAD_TYPE_SESSION_CONFIRMED = 2; + const uint8_t PAYLOAD_TYPE_RELAY_REQUEST = 3; + 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_PEER_TEST = 7; + const uint8_t PAYLOAD_TYPE_SESSION_DESTROYED = 8; + + enum SessionState + { + eSessionStateUnknown, + eSessionStateIntroduced, + eSessionStateEstablished, + eSessionStateFailed + }; + + class SSUServer; + class SSUSession: public TransportSession + { + public: + + SSUSession (SSUServer& server, boost::asio::ip::udp::endpoint& remoteEndpoint, + 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 (); + + void Connect (); + void Introduce (uint32_t iTag, const uint8_t * iKey); + void WaitForIntroduction (); + void Close (); + boost::asio::ip::udp::endpoint& GetRemoteEndpoint () { return m_RemoteEndpoint; }; + bool IsV6 () const { return m_RemoteEndpoint.address ().is_v6 (); }; + void SendI2NPMessage (I2NPMessage * msg); + void SendPeerTest (); // Alice + + SessionState GetState () const { return m_State; }; + size_t GetNumSentBytes () const { return m_NumSentBytes; }; + size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; }; + + void SendKeepAlive (); + uint32_t GetRelayTag () const { return m_RelayTag; }; + uint32_t GetCreationTime () const { return m_CreationTime; }; + + private: + + void CreateAESandMacKey (const uint8_t * pubKey); + + void PostI2NPMessage (I2NPMessage * msg); + void ProcessMessage (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint); // call for established session + void ProcessSessionRequest (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint); + void SendSessionRequest (); + void SendRelayRequest (uint32_t iTag, const uint8_t * iKey); + 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, size_t ourAddressLen); + void ProcessRelayRequest (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& from); + void SendRelayResponse (uint32_t nonce, const boost::asio::ip::udp::endpoint& from, + const uint8_t * introKey, const boost::asio::ip::udp::endpoint& to); + void SendRelayIntro (SSUSession * session, const boost::asio::ip::udp::endpoint& from); + void ProcessRelayResponse (uint8_t * buf, size_t len); + void ProcessRelayIntro (uint8_t * buf, size_t len); + void Established (); + void Failed (); + void ScheduleConnectTimer (); + void HandleConnectTimer (const boost::system::error_code& ecode); + 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, const uint8_t * introKey, bool toAddress = true); + void ProcessData (uint8_t * buf, size_t len); + void SendSesionDestroyed (); + void Send (uint8_t type, const uint8_t * payload, size_t len); // with session key + void Send (const uint8_t * buf, size_t size); + + void FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * buf, size_t len, const uint8_t * aesKey, const uint8_t * iv, const uint8_t * macKey); + void FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * buf, size_t len); // with session key + void Decrypt (uint8_t * buf, size_t len, const uint8_t * aesKey); + void DecryptSessionKey (uint8_t * buf, size_t len); + 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: + + friend class SSUData; // TODO: change in later + SSUServer& m_Server; + boost::asio::ip::udp::endpoint m_RemoteEndpoint; + boost::asio::deadline_timer m_Timer; + bool m_PeerTest; + SessionState m_State; + bool m_IsSessionKey; + uint32_t m_RelayTag; + std::set m_PeerTestNonces; + i2p::crypto::CBCEncryption m_SessionKeyEncryption; + i2p::crypto::CBCDecryption m_SessionKeyDecryption; + uint8_t m_SessionKey[32], m_MacKey[32]; + std::list m_DelayedMessages; + SSUData m_Data; + size_t m_NumSentBytes, m_NumReceivedBytes; + uint32_t m_CreationTime; // seconds since epoch + }; + + +} +} + +#endif +