diff --git a/libi2pd/Destination.cpp b/libi2pd/Destination.cpp index 458a29e5..f823416a 100644 --- a/libi2pd/Destination.cpp +++ b/libi2pd/Destination.cpp @@ -437,7 +437,9 @@ namespace client { uint8_t pub[32]; leaseSet->Encrypt (nullptr, pub); - if (memcmp (from->GetRemoteStaticKey (), pub, 32)) + if (!memcmp (from->GetRemoteStaticKey (), pub, 32)) + from->SetDestination (leaseSet->GetIdentHash ()); + else { LogPrint (eLogError, "Destination: Remote LeaseSet static key mismatch"); leaseSet = nullptr; diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index ebc6369c..aa88a0a0 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -459,6 +459,8 @@ namespace garlic } offset += size; } + if (IsResponseRequired () && GetOwner ()) + GetOwner ()->ScheduleSessionResponseTimer (shared_from_this()); } void ECIESX25519AEADRatchetSession::HandleNextKey (const uint8_t * buf, size_t len, const std::shared_ptr& receiveTagset) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.h b/libi2pd/ECIESX25519AEADRatchetSession.h index ed89bdfd..fccac49e 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.h +++ b/libi2pd/ECIESX25519AEADRatchetSession.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2026, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -33,7 +33,7 @@ namespace garlic const int ECIESX25519_SEND_EXPIRATION_TIMEOUT = 480; // in seconds const int ECIESX25519_RECEIVE_EXPIRATION_TIMEOUT = 600; // in seconds const int ECIESX25519_SESSION_CREATE_TIMEOUT = 3; // in seconds, NSR must be send after NS received - const int ECIESX25519_SESSION_ESTABLISH_TIMEOUT = 15; // in seconds + const int ECIESX25519_SESSION_ESTABLISH_TIMEOUT = 15; // in seconds const int ECIESX25519_PREVIOUS_TAGSET_EXPIRATION_TIMEOUT = 180; // in seconds const int ECIESX25519_DEFAULT_ACK_REQUEST_INTERVAL = 33000; // in milliseconds const int ECIESX25519_ACK_REQUEST_MAX_NUM_ATTEMPTS = 3; @@ -64,7 +64,7 @@ namespace garlic void SetTagSetID (int tagsetID) { m_TagSetID = tagsetID; }; uint32_t GetMsgID () const { return (m_TagSetID << 16) + m_NextIndex; }; // (tagsetid << 16) + N - + private: i2p::data::Tag<64> m_SessionTagKeyData; @@ -95,7 +95,7 @@ namespace garlic virtual bool IsIndexExpired (int index) const; virtual bool HandleNextMessage (uint8_t * buf, size_t len, int index); virtual bool IsSessionTerminated () const; - + private: int m_TrimBehindIndex = 0; @@ -113,7 +113,7 @@ namespace garlic bool IsIndexExpired (int index) const override { return false; }; bool HandleNextMessage (uint8_t * buf, size_t len, int index) override; bool IsSessionTerminated () const override { return false; } - + private: GarlicDestination * m_Destination; @@ -172,29 +172,30 @@ namespace garlic const uint8_t * GetRemoteStaticKey () const { return m_RemoteStaticKey; } i2p::data::CryptoKeyType GetRemoteStaticKeyType () const { return m_RemoteStaticKeyType; } - void SetRemoteStaticKey (i2p::data::CryptoKeyType keyType, const uint8_t * key) - { + void SetRemoteStaticKey (i2p::data::CryptoKeyType keyType, const uint8_t * key) + { m_RemoteStaticKeyType = keyType; - memcpy (m_RemoteStaticKey, key, 32); + memcpy (m_RemoteStaticKey, key, 32); } void Terminate () { m_IsTerminated = true; } void SetDestination (const i2p::data::IdentHash& dest) { if (!m_Destination) m_Destination.reset (new i2p::data::IdentHash (dest)); } - const i2p::data::IdentHash * GetDestinationPtr () const { return m_Destination ? m_Destination.get () : nullptr; }; // for pongs + const i2p::data::IdentHash * GetDestinationPtr () const { return m_Destination ? m_Destination.get () : nullptr; }; // for pongs bool CheckExpired (uint64_t ts); // true is expired bool CanBeRestarted (uint64_t ts) const { return ts > m_SessionCreatedTimestamp + ECIESX25519_RESTART_TIMEOUT; } bool IsInactive (uint64_t ts) const { return ts > m_LastActivityTimestamp + ECIESX25519_INACTIVITY_TIMEOUT && CanBeRestarted (ts); } void CleanupReceiveNSRKeys (); // called from ReceiveRatchetTagSet at Alice's side - + bool IsResponseRequired () const { return m_State == eSessionStateNewSessionReceived || m_SendReverseKey || !m_AckRequests.empty (); }; + bool IsRatchets () const override { return true; }; bool IsReadyToSend () const override { return m_State != eSessionStateNewSessionSent; }; bool IsTerminated () const override { return m_IsTerminated; } uint64_t GetLastActivityTimestamp () const override { return m_LastActivityTimestamp; }; void SetAckRequestInterval (int interval) override { m_AckRequestInterval = interval; }; bool CleanupUnconfirmedTags () override; // return true if unaswered Ack requests, called from I2CP - + protected: i2p::crypto::NoiseSymmetricState& GetNoiseState () { return *this; }; @@ -202,7 +203,7 @@ namespace garlic void CreateNonce (uint64_t seqn, uint8_t * nonce); void HandlePayload (const uint8_t * buf, size_t len, const std::shared_ptr& receiveTagset, int index); bool MessageConfirmed (uint32_t msgID) override; - + private: bool GenerateEphemeralKeysAndEncode (uint8_t * buf); // buf is 32 bytes @@ -222,13 +223,13 @@ namespace garlic size_t CreateGarlicClove (std::shared_ptr msg, uint8_t * buf, size_t len, bool alwaysLocal = false); size_t CreateLeaseSetClove (std::shared_ptr ls, uint64_t ts, uint8_t * buf, size_t len); size_t CreatePaddingClove (uint8_t paddingSize, uint8_t * buf, size_t len); - + void GenerateMoreReceiveTags (std::shared_ptr receiveTagset, int numTags); void NewNextSendRatchet (); std::shared_ptr WrapPayload (const uint8_t * payload, size_t len); uint8_t GetNextPaddingSize (size_t payloadLen); - + private: i2p::data::CryptoKeyType m_RemoteStaticKeyType; @@ -236,10 +237,10 @@ namespace garlic uint8_t m_Aepk[32]; // Alice's ephemeral keys, for incoming only uint8_t m_NSREncodedKey[32], m_NSRH[32], m_NSRKey[32]; // new session reply, for incoming only std::shared_ptr m_EphemeralKeys; -#if OPENSSL_PQ +#if OPENSSL_PQ std::unique_ptr m_PQKeys; std::unique_ptr > m_NSREncodedPQKey; -#endif +#endif SessionState m_State = eSessionStateNew; uint64_t m_SessionCreatedTimestamp = 0, m_LastActivityTimestamp = 0, // incoming (in seconds) m_LastSentTimestamp = 0; // in milliseconds @@ -250,10 +251,10 @@ namespace garlic std::unique_ptr m_NextReceiveRatchet, m_NextSendRatchet; uint64_t m_LastAckRequestSendTime = 0; // milliseconds - uint32_t m_AckRequestMsgID = 0; + uint32_t m_AckRequestMsgID = 0; int m_AckRequestNumAttempts = 0; int m_AckRequestInterval = ECIESX25519_DEFAULT_ACK_REQUEST_INTERVAL; // milliseconds - + public: // for HTTP only diff --git a/libi2pd/Garlic.h b/libi2pd/Garlic.h index 506d9316..fe74b264 100644 --- a/libi2pd/Garlic.h +++ b/libi2pd/Garlic.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2026, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -120,7 +120,7 @@ namespace garlic virtual uint64_t GetLastActivityTimestamp () const { return 0; }; // non-zero for rathets only virtual void SetAckRequestInterval (int interval) {}; // in milliseconds, override in ECIESX25519AEADRatchetSession virtual std::vector > WrapMultipleMessages (const std::vector >& msgs); - + void SetLeaseSetUpdated () { if (m_LeaseSetUpdateStatus != eLeaseSetDoNotSend) m_LeaseSetUpdateStatus = eLeaseSetUpdated; @@ -138,7 +138,7 @@ namespace garlic int NumSentPackets () const { return m_NumSentPackets; } void SetNumSentPackets (int numSentPackets) { m_NumSentPackets = numSentPackets; } - + GarlicDestination * GetOwner () const { return m_Owner; } void SetOwner (GarlicDestination * owner) { m_Owner = owner; } @@ -255,11 +255,11 @@ namespace garlic void RemoveDeliveryStatusSession (uint32_t msgID); std::shared_ptr WrapMessageForRouter (std::shared_ptr router, std::shared_ptr msg); - + bool AEADChaCha20Poly1305Encrypt (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen, - const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len); + const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len); bool AEADChaCha20Poly1305Decrypt (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen, - const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len); + const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len); void AddSessionKey (const uint8_t * key, const uint8_t * tag); // one tag void AddECIESx25519Key (const uint8_t * key, uint64_t tag); // one tag @@ -278,24 +278,26 @@ namespace garlic virtual std::shared_ptr GetLeaseSet () = 0; // TODO virtual std::shared_ptr GetTunnelPool () const = 0; - virtual i2p::data::CryptoKeyType GetRatchetsHighestCryptoType () const + virtual i2p::data::CryptoKeyType GetRatchetsHighestCryptoType () const { return GetIdentity ()->GetCryptoKeyType () >= i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD ? GetIdentity ()->GetCryptoKeyType () : 0; - } + } + + virtual void ScheduleSessionResponseTimer (std::shared_ptr session) {}; protected: void AddECIESx25519Key (const uint8_t * key, const uint8_t * tag); // one tag bool HandleECIESx25519TagMessage (uint8_t * buf, size_t len); // return true if found virtual void HandleI2NPMessage (const uint8_t * buf, size_t len) = 0; // called from clove only - virtual bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, + virtual bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len, uint32_t msgID, ECIESX25519AEADRatchetSession * from) = 0; void HandleGarlicMessage (std::shared_ptr msg); void HandleDeliveryStatusMessage (uint32_t msgID); void SaveTags (); void LoadTags (); - + private: bool SupportsRatchets () const { return GetRatchetsHighestCryptoType () > 0; } @@ -323,7 +325,7 @@ namespace garlic // encryption i2p::crypto::AEADChaCha20Poly1305Encryptor m_Encryptor; i2p::crypto::AEADChaCha20Poly1305Decryptor m_Decryptor; - + public: // for HTTP only diff --git a/libi2pd_client/I2CP.cpp b/libi2pd_client/I2CP.cpp index 08de8cea..11bb966a 100644 --- a/libi2pd_client/I2CP.cpp +++ b/libi2pd_client/I2CP.cpp @@ -128,6 +128,38 @@ namespace client return cryptoType >= i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD ? cryptoType : 0; } + void I2CPDestination::ScheduleSessionResponseTimer (std::shared_ptr session) + { + if (!session) return; + auto timer = std::make_shared(GetService ()); + timer->expires_after (std::chrono::milliseconds(I2CP_RATCHETS_RESPONSE_TIMEOUT)); + timer->async_wait ([session, s = GetSharedFromThis (), timer](const boost::system::error_code& ecode) + { + if (ecode != boost::asio::error::operation_aborted && + !session->IsTerminated () && session->IsResponseRequired () && // response still required + session->GetDestinationPtr ()) // we know ident hash + { + auto pool = s->GetTunnelPool (); + if (!pool) return; + auto ls = s->FindLeaseSet (*session->GetDestinationPtr ()); + if (!ls) return; + auto garlic = session->WrapSingleMessage (nullptr); // no data messages + if (!garlic) return; + auto leases = ls->GetNonExpiredLeases (false); + if (!leases.empty ()) + { + auto remoteLease = leases[s->GetRng ()() % leases.size ()]; + auto outboundTunnel = pool->GetNextOutboundTunnel (); + if (outboundTunnel) + { + LogPrint (eLogDebug, "I2CP: Sending ratchets session response"); + s->SendMsg (garlic, outboundTunnel, remoteLease); + } + } + } + }); + } + void I2CPDestination::HandleDataMessage (const uint8_t * buf, size_t len, i2p::garlic::ECIESX25519AEADRatchetSession * from) { diff --git a/libi2pd_client/I2CP.h b/libi2pd_client/I2CP.h index 66e35bf1..1c50f846 100644 --- a/libi2pd_client/I2CP.h +++ b/libi2pd_client/I2CP.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2026, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -34,6 +34,7 @@ namespace client const int I2CP_LEASESET_CREATION_TIMEOUT = 10; // in seconds const int I2CP_DESTINATION_READINESS_CHECK_INTERVAL = 5; // in seconds const int I2CP_SESSION_ACK_REQUEST_INTERVAL = 12100; // in milliseconds + const int I2CP_RATCHETS_RESPONSE_TIMEOUT = 1000; // in milliseconds const size_t I2CP_HEADER_LENGTH_OFFSET = 0; const size_t I2CP_HEADER_TYPE_OFFSET = I2CP_HEADER_LENGTH_OFFSET + 4; @@ -86,7 +87,7 @@ namespace client public: I2CPDestination (boost::asio::io_context& service, std::shared_ptr owner, - std::shared_ptr identity, bool isPublic, bool isSameThread, + std::shared_ptr identity, bool isPublic, bool isSameThread, const i2p::util::Mapping& params); ~I2CPDestination () {}; @@ -98,7 +99,7 @@ namespace client void LeaseSet2Created (uint8_t storeType, const uint8_t * buf, size_t len); // called from I2CPSession void SendMsgTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash& ident, uint32_t nonce); // called from I2CPSession bool SendMsg (const uint8_t * payload, size_t len, std::shared_ptr remoteSession, uint32_t nonce); - + // implements LocalDestination bool Decrypt (const uint8_t * encrypted, uint8_t * data, i2p::data::CryptoKeyType preferredCrypto) const override; bool SupportsEncryptionType (i2p::data::CryptoKeyType keyType) const override; @@ -109,21 +110,22 @@ namespace client // GarlicDestination i2p::data::CryptoKeyType GetRatchetsHighestCryptoType () const override; + void ScheduleSessionResponseTimer (std::shared_ptr session) override; // LeaseSetDestination void CleanupDestination () override; i2p::data::CryptoKeyType GetPreferredCryptoType () const override; // I2CP void HandleDataMessage (const uint8_t * buf, size_t len, i2p::garlic::ECIESX25519AEADRatchetSession * from) override; void CreateNewLeaseSet (const std::vector >& tunnels) override; - + private: std::shared_ptr GetSharedFromThis () { return std::static_pointer_cast(shared_from_this ()); } bool SendMsg (std::shared_ptr msg, std::shared_ptr remote); - bool SendMsg (std::shared_ptr garlic, + bool SendMsg (std::shared_ptr garlic, std::shared_ptr outboundTunnel, std::shared_ptr remoteLease); - + void PostCreateNewLeaseSet (std::vector > tunnels); private: @@ -197,7 +199,7 @@ namespace client void SendSessionStatusMessage (I2CPSessionStatus status); void SendHostReplyMessage (uint32_t requestID, std::shared_ptr identity); - + private: I2CPServer& m_Owner;