diff --git a/ChangeLog b/ChangeLog index 07110fe2..293764a6 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,22 @@ # for this file format description, # see https://github.com/olivierlacan/keep-a-changelog +## [2.30.0] - 2020-02-25 +### Added +- Single threaded SAM +- Experimental support of ECIES-X25519-AEAD-Ratchet crypto type +### Changed +- Minimal MTU size is 1280 for ipv6 +- Use unordered_map instead map for destination's sessions and tags list +- Use std::shuffle instead std::random_shuffle +- SAM is single threaded by default +- Reseeds list +### Fixed +- Correct termination of streaming destination +- Extra ',' in RouterInfo response in I2PControl +- SAM crash on session termination +- Storage for Android 10 + ## [2.29.0] - 2019-10-21 ### Added - Client auth flag for b33 address diff --git a/Win32/installer.iss b/Win32/installer.iss index 736c7038..d4f6f247 100644 --- a/Win32/installer.iss +++ b/Win32/installer.iss @@ -1,5 +1,5 @@ #define I2Pd_AppName "i2pd" -#define I2Pd_ver "2.29.0" +#define I2Pd_ver "2.30.0" #define I2Pd_Publisher "PurpleI2P" [Setup] diff --git a/android/build.gradle b/android/build.gradle index 291d90ea..844615f8 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -30,8 +30,8 @@ android { applicationId "org.purplei2p.i2pd" targetSdkVersion 29 minSdkVersion 14 - versionCode 2290 - versionName "2.29.0" + versionCode 2300 + versionName "2.30.0" ndk { abiFilters 'armeabi-v7a' abiFilters 'x86' diff --git a/appveyor.yml b/appveyor.yml index f6eee9ab..8c4a1a9d 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: 2.29.0.{build} +version: 2.30.0.{build} pull_requests: do_not_increment_build_number: true branches: diff --git a/contrib/certificates/reseed/backup_at_mail.i2p.crt b/contrib/certificates/reseed/backup_at_mail.i2p.crt deleted file mode 100644 index 73b08eaa..00000000 --- a/contrib/certificates/reseed/backup_at_mail.i2p.crt +++ /dev/null @@ -1,32 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIFfTCCA2WgAwIBAgIEOprmhjANBgkqhkiG9w0BAQ0FADBvMQswCQYDVQQGEwJY -WDELMAkGA1UECBMCWFgxCzAJBgNVBAcTAlhYMR4wHAYDVQQKExVJMlAgQW5vbnlt -b3VzIE5ldHdvcmsxDDAKBgNVBAsTA0kyUDEYMBYGA1UEAwwPYmFja3VwQG1haWwu -aTJwMB4XDTEzMTAxMzEzNDQ1NVoXDTIzMTAxMzEzNDQ1NVowbzELMAkGA1UEBhMC -WFgxCzAJBgNVBAgTAlhYMQswCQYDVQQHEwJYWDEeMBwGA1UEChMVSTJQIEFub255 -bW91cyBOZXR3b3JrMQwwCgYDVQQLEwNJMlAxGDAWBgNVBAMMD2JhY2t1cEBtYWls -LmkycDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAIoAkobXwk/Enf1d -roHyqCyvcJfZJVTwb/LgYWAvCBMCr+RGqlSgtk3g69Y3I0xU08fD2kGt3r5Pwsbr -omXIbJAcccyLqmQ5QX6QgL+X9VpMDp9C4h2RogCrqLBAWw4cuZ4RS9VCpP1Yis7H -uejYqENP86p7BsRnuW/4cYnfunAdMpss4LpRGQXt1nTX+kfgCYgnKFbFqwAHt7yV -Ds+Pe6FuBHPlp+sc1amKRcUnSvhXLsv43VicnT7xYL/kUsN83wrtHA3B4aGDx3aA -3/EzuRmIXQB0BlTZILMEyYwG/nc4OsW82QYrvEZ9BIg9A4lF/wS/KZCICPxLF2zo -dGjnmlgkiA4s8eO+va/ElHyELjckVXqmG1eXHhSkEsDvOQJy01IUuwLinvq7cUbJ -HfJBZJllEg+sLDCv3FkEqN+XjBNFfQN4oNew4w6IPY6YH1INVB9LL0Cmdu4DudLv -TY8OcI8eSfez3hmm+pYQ23PJRYYnvRDnRECyIWBegkckWRh8U/WvZUYUvETK6EDl -/0KpTtfzX6MqHA5D6bTAB8Y3ijGMLrZ/B5vj5yCoZbLiGme9X2moR2k1LEhdhtzV -exsqezCpg6dn48FTX7mHjvR5/r4kz2jqBGmdPUWIIxnjFUzDUK3llVQiHihleHpe -jL4LqnhBGKWFRTaVwaIkBG4zAfIzAgMBAAGjITAfMB0GA1UdDgQWBBQNkfW7bSMl -1/4KDbgwrkf9x1Zu/TANBgkqhkiG9w0BAQ0FAAOCAgEAGg3a3rTf0EznQocmio0T -5gCoL0n8h6yKW/PyPAIELrd9wiYjhJFcWvMTcJJJnVqmAL5vpvhaAFVtAfx70MGa -0DZ7FvytK5hEfF4IqOFDyEEVGJR5rIpVK4MeI1nmwEsxdbW+FhODjtRzgYO8XBME -Xj4aY1FWg9vxc3reUj6PSFsZtsB0aLiRgL9JDovJIiRw0Uqr1v2wXBte5yVCxDge -vTREZtpK4cKetoOa68pwSXI32JwKE18j6bfdKVBCcYQKlKP/3gHGduaDrQv3w32S -DRym5s6MREeTUOtAw4wq46KpdOX8yyAqJPrCfMwS6ORd3t+egqOw0PUnsqb97w4O -lUtrRYvb2cOj60SmRx4vJvItyuHbKqIK7o2e1RcUZPXYoAVx2ww4XB2Wk4D7LSAs -cS7nLj8yAqzJ2qqtBzxu+zILJtkVa12dKF0xmS0BxBp4sCYiBtmAVE8AWQqEuSHA -FrMWqoXcjcfdvvyX487FFWWUE7ZBIn0hee2sK9J9+SPtqczJaN7TF3K3nzo65WJG -1epltmq2Ugjb67Gz7v4y7H23DJ/qhm8yLtCHTj69HTta5I08j6Kut924WLZaiMO/ -4YoEL5AE63X0sxYibKFQiq7FW5nUJA280GRlY3xSMFzlB2ggazrUV3YAWVDhfdnI -flpzWXkFM2D36OUaubfe9YY= ------END CERTIFICATE----- diff --git a/contrib/rpm/i2pd-git.spec b/contrib/rpm/i2pd-git.spec index 31b8d4a5..89e3cee9 100644 --- a/contrib/rpm/i2pd-git.spec +++ b/contrib/rpm/i2pd-git.spec @@ -1,7 +1,7 @@ %define git_hash %(git rev-parse HEAD | cut -c -7) Name: i2pd-git -Version: 2.29.0 +Version: 2.30.0 Release: git%{git_hash}%{?dist} Summary: I2P router written in C++ Conflicts: i2pd @@ -110,6 +110,9 @@ getent passwd i2pd >/dev/null || \ %changelog +* Tue Feb 25 2020 orignal - 2.30.0 +- update to 2.30.0 + * Mon Oct 21 2019 orignal - 2.29.0 - update to 2.29.0 diff --git a/contrib/rpm/i2pd.spec b/contrib/rpm/i2pd.spec index 3db99af0..0f91c03b 100644 --- a/contrib/rpm/i2pd.spec +++ b/contrib/rpm/i2pd.spec @@ -1,5 +1,5 @@ Name: i2pd -Version: 2.29.0 +Version: 2.30.0 Release: 1%{?dist} Summary: I2P router written in C++ Conflicts: i2pd-git @@ -108,6 +108,9 @@ getent passwd i2pd >/dev/null || \ %changelog +* Tue Feb 25 2020 orignal - 2.30.0 +- update to 2.30.0 + * Mon Oct 21 2019 orignal - 2.29.0 - update to 2.29.0 diff --git a/daemon/I2PControl.cpp b/daemon/I2PControl.cpp index 572292ee..acf054f1 100644 --- a/daemon/I2PControl.cpp +++ b/daemon/I2PControl.cpp @@ -394,13 +394,15 @@ namespace client void I2PControlService::RouterInfoHandler (const boost::property_tree::ptree& params, std::ostringstream& results) { + bool first = true; for (auto it = params.begin (); it != params.end (); it++) { LogPrint (eLogDebug, "I2PControl: RouterInfo request: ", it->first); auto it1 = m_RouterInfoHandlers.find (it->first); if (it1 != m_RouterInfoHandlers.end ()) { - if (it != params.begin ()) results << ","; + if (!first) results << ","; + else first = false; (this->*(it1->second))(results); } else diff --git a/debian/changelog b/debian/changelog index f5f98680..da177cbd 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +i2pd (2.30.0-1) unstable; urgency=medium + + * updated to version 2.30.0/0.9.45 + + -- orignal Tue, 25 Feb 2020 16:00:00 +0000 + i2pd (2.29.0-1) unstable; urgency=medium * updated to version 2.29.0/0.9.43 diff --git a/libi2pd/Config.cpp b/libi2pd/Config.cpp index e2f4bdd0..b0f7572d 100644 --- a/libi2pd/Config.cpp +++ b/libi2pd/Config.cpp @@ -131,6 +131,7 @@ namespace config { ("sam.enabled", value()->default_value(true), "Enable or disable SAM Application bridge") ("sam.address", value()->default_value("127.0.0.1"), "SAM listen address") ("sam.port", value()->default_value(7656), "SAM listen port") + ("sam.singlethread", value()->default_value(true), "Sessions run in the SAM bridge's thread") ; options_description bob("BOB options"); @@ -190,9 +191,10 @@ namespace config { "https://reseed.i2p-projekt.de/," "https://i2p.mooo.com/netDb/," "https://netdb.i2p2.no/," + "https://reseed.i2p2.no/," + "https://reseed2.i2p2.no/," // "https://us.reseed.i2p2.no:444/," // mamoth's shit // "https://uk.reseed.i2p2.no:444/," // mamoth's shit - "https://download.xxlspeed.com/," "https://reseed-fr.i2pd.xyz/," "https://reseed.memcpy.io/," "https://reseed.onion.im/," diff --git a/libi2pd/Crypto.h b/libi2pd/Crypto.h index 33490c8a..32410daf 100644 --- a/libi2pd/Crypto.h +++ b/libi2pd/Crypto.h @@ -27,8 +27,8 @@ # define X509_getm_notAfter X509_get_notAfter #else # define LEGACY_OPENSSL 0 -# define OPENSSL_HKDF 1 # if (OPENSSL_VERSION_NUMBER >= 0x010101000) // 1.1.1 +# define OPENSSL_HKDF 1 # define OPENSSL_EDDSA 1 # define OPENSSL_X25519 1 # define OPENSSL_SIPHASH 1 diff --git a/libi2pd/CryptoKey.cpp b/libi2pd/CryptoKey.cpp index df5dd38f..878e984a 100644 --- a/libi2pd/CryptoKey.cpp +++ b/libi2pd/CryptoKey.cpp @@ -14,6 +14,7 @@ namespace crypto void ElGamalEncryptor::Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding) { + if (!ctx) return; ElGamalEncrypt (m_PublicKey, data, encrypted, ctx, zeroPadding); } @@ -24,6 +25,7 @@ namespace crypto bool ElGamalDecryptor::Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding) { + if (!ctx) return false; return ElGamalDecrypt (m_PrivateKey, encrypted, data, ctx, zeroPadding); } @@ -151,11 +153,9 @@ namespace crypto memcpy (m_PublicKey, pub, 32); } - void ECIESX25519AEADRatchetEncryptor::Encrypt (const uint8_t * epriv, uint8_t * sharedSecret, BN_CTX * ctx, bool zeroPadding) + void ECIESX25519AEADRatchetEncryptor::Encrypt (const uint8_t *, uint8_t * pub, BN_CTX *, bool) { - X25519Keys ep; - ep.SetPrivateKey (epriv); - ep.Agree (m_PublicKey, sharedSecret); + memcpy (pub, m_PublicKey, 32); } ECIESX25519AEADRatchetDecryptor::ECIESX25519AEADRatchetDecryptor (const uint8_t * priv) diff --git a/libi2pd/CryptoKey.h b/libi2pd/CryptoKey.h index 6412f4d3..701e9482 100644 --- a/libi2pd/CryptoKey.h +++ b/libi2pd/CryptoKey.h @@ -125,8 +125,8 @@ namespace crypto ECIESX25519AEADRatchetEncryptor (const uint8_t * pub); ~ECIESX25519AEADRatchetEncryptor () {}; - void Encrypt (const uint8_t * epriv, uint8_t * sharedSecret, BN_CTX * ctx, bool zeroPadding); - // agree with ephemeral priv and return in sharedSecret (32 bytes) + void Encrypt (const uint8_t *, uint8_t * pub, BN_CTX *, bool); + // copies m_PublicKey to pub private: diff --git a/libi2pd/Destination.cpp b/libi2pd/Destination.cpp index ba74f61f..6544d120 100644 --- a/libi2pd/Destination.cpp +++ b/libi2pd/Destination.cpp @@ -7,15 +7,15 @@ #include "Timestamp.h" #include "NetDb.hpp" #include "Destination.h" -#include "util.h" namespace i2p { namespace client { - LeaseSetDestination::LeaseSetDestination (bool isPublic, const std::map * params): - m_IsRunning (false), m_Thread (nullptr), m_IsPublic (isPublic), - m_PublishReplyToken (0), m_LastSubmissionTime (0), m_PublishConfirmationTimer (m_Service), + LeaseSetDestination::LeaseSetDestination (boost::asio::io_service& service, + bool isPublic, const std::map * params): + m_Service (service), m_IsPublic (isPublic), m_PublishReplyToken (0), + m_LastSubmissionTime (0), m_PublishConfirmationTimer (m_Service), m_PublishVerificationTimer (m_Service), m_PublishDelayTimer (m_Service), m_CleanupTimer (m_Service), m_LeaseSetType (DEFAULT_LEASESET_TYPE), m_AuthType (i2p::data::ENCRYPTED_LEASESET_AUTH_TYPE_NONE) { @@ -123,77 +123,36 @@ namespace client LeaseSetDestination::~LeaseSetDestination () { - if (m_IsRunning) - Stop (); if (m_Pool) i2p::tunnel::tunnels.DeleteTunnelPool (m_Pool); for (auto& it: m_LeaseSetRequests) it.second->Complete (nullptr); } - void LeaseSetDestination::Run () + void LeaseSetDestination::Start () { - while (m_IsRunning) - { - try - { - m_Service.run (); - } - catch (std::exception& ex) - { - LogPrint (eLogError, "Destination: runtime exception: ", ex.what ()); - } - } + if (m_Nickname.empty ()) + m_Nickname = i2p::data::GetIdentHashAbbreviation (GetIdentHash ()); // set default nickname + LoadTags (); + m_Pool->SetLocalDestination (shared_from_this ()); + m_Pool->SetActive (true); + m_CleanupTimer.expires_from_now (boost::posix_time::minutes (DESTINATION_CLEANUP_TIMEOUT)); + m_CleanupTimer.async_wait (std::bind (&LeaseSetDestination::HandleCleanupTimer, + shared_from_this (), std::placeholders::_1)); } - bool LeaseSetDestination::Start () + void LeaseSetDestination::Stop () { - if (!m_IsRunning) + m_CleanupTimer.cancel (); + m_PublishConfirmationTimer.cancel (); + m_PublishVerificationTimer.cancel (); + if (m_Pool) { - if (m_Nickname.empty ()) - m_Nickname = i2p::data::GetIdentHashAbbreviation (GetIdentHash ()); // set default nickname - LoadTags (); - m_IsRunning = true; - m_Pool->SetLocalDestination (shared_from_this ()); - m_Pool->SetActive (true); - m_CleanupTimer.expires_from_now (boost::posix_time::minutes (DESTINATION_CLEANUP_TIMEOUT)); - m_CleanupTimer.async_wait (std::bind (&LeaseSetDestination::HandleCleanupTimer, - shared_from_this (), std::placeholders::_1)); - m_Thread = new std::thread (std::bind (&LeaseSetDestination::Run, shared_from_this ())); - - return true; + m_Pool->SetLocalDestination (nullptr); + i2p::tunnel::tunnels.StopTunnelPool (m_Pool); } - else - return false; - } - - bool LeaseSetDestination::Stop () - { - if (m_IsRunning) - { - m_CleanupTimer.cancel (); - m_PublishConfirmationTimer.cancel (); - m_PublishVerificationTimer.cancel (); - - m_IsRunning = false; - if (m_Pool) - { - m_Pool->SetLocalDestination (nullptr); - i2p::tunnel::tunnels.StopTunnelPool (m_Pool); - } - m_Service.stop (); - if (m_Thread) - { - m_Thread->join (); - delete m_Thread; - m_Thread = 0; - } - SaveTags (); - CleanUp (); // GarlicDestination - return true; - } - else - return false; + SaveTags (); + CleanUp (); // GarlicDestination } bool LeaseSetDestination::Reconfigure(std::map params) @@ -429,7 +388,7 @@ namespace client if (buf[DATABASE_STORE_TYPE_OFFSET] == i2p::data::NETDB_STORE_TYPE_LEASESET) leaseSet = std::make_shared (buf + offset, len - offset); // LeaseSet else - leaseSet = std::make_shared (buf[DATABASE_STORE_TYPE_OFFSET], buf + offset, len - offset); // LeaseSet2 + leaseSet = std::make_shared (buf[DATABASE_STORE_TYPE_OFFSET], buf + offset, len - offset, true, GetEncryptionType ()); // LeaseSet2 if (leaseSet->IsValid () && leaseSet->GetIdentHash () == key) { if (leaseSet->GetIdentHash () != GetIdentHash ()) @@ -453,7 +412,7 @@ namespace client auto it2 = m_LeaseSetRequests.find (key); if (it2 != m_LeaseSetRequests.end () && it2->second->requestedBlindedKey) { - auto ls2 = std::make_shared (buf + offset, len - offset, it2->second->requestedBlindedKey, m_LeaseSetPrivKey ? *m_LeaseSetPrivKey : nullptr); + auto ls2 = std::make_shared (buf + offset, len - offset, it2->second->requestedBlindedKey, m_LeaseSetPrivKey ? *m_LeaseSetPrivKey : nullptr, GetEncryptionType ()); if (ls2->IsValid ()) { m_RemoteLeaseSets[ls2->GetIdentHash ()] = ls2; // ident is not key @@ -863,10 +822,12 @@ namespace client } } - ClientDestination::ClientDestination (const i2p::data::PrivateKeys& keys, bool isPublic, const std::map * params): - LeaseSetDestination (isPublic, params), m_Keys (keys), m_StreamingAckDelay (DEFAULT_INITIAL_ACK_DELAY), + ClientDestination::ClientDestination (boost::asio::io_service& service, const i2p::data::PrivateKeys& keys, + bool isPublic, const std::map * params): + LeaseSetDestination (service, isPublic, params), + m_Keys (keys), m_StreamingAckDelay (DEFAULT_INITIAL_ACK_DELAY), m_DatagramDestination (nullptr), m_RefCounter (0), - m_ReadyChecker(GetService()) + m_ReadyChecker(service) { if (keys.IsOfflineSignature () && GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_LEASESET) SetLeaseSetType (i2p::data::NETDB_STORE_TYPE_STANDARD_LEASESET2); // offline keys can be published with LS2 only @@ -934,43 +895,33 @@ namespace client { } - bool ClientDestination::Start () + void ClientDestination::Start () { - if (LeaseSetDestination::Start ()) - { - m_StreamingDestination = std::make_shared (GetSharedFromThis ()); // TODO: - m_StreamingDestination->Start (); - for (auto& it: m_StreamingDestinationsByPorts) - it.second->Start (); - return true; - } - else - return false; + LeaseSetDestination::Start (); + m_StreamingDestination = std::make_shared (GetSharedFromThis ()); // TODO: + m_StreamingDestination->Start (); + for (auto& it: m_StreamingDestinationsByPorts) + it.second->Start (); } - bool ClientDestination::Stop () + void ClientDestination::Stop () { - if (LeaseSetDestination::Stop ()) + LeaseSetDestination::Stop (); + m_ReadyChecker.cancel(); + m_StreamingDestination->Stop (); + //m_StreamingDestination->SetOwner (nullptr); + m_StreamingDestination = nullptr; + for (auto& it: m_StreamingDestinationsByPorts) { - m_ReadyChecker.cancel(); - m_StreamingDestination->Stop (); - //m_StreamingDestination->SetOwner (nullptr); - m_StreamingDestination = nullptr; - for (auto& it: m_StreamingDestinationsByPorts) - { - it.second->Stop (); - //it.second->SetOwner (nullptr); - } - m_StreamingDestinationsByPorts.clear (); - if (m_DatagramDestination) - { - delete m_DatagramDestination; - m_DatagramDestination = nullptr; - } - return true; + it.second->Stop (); + //it.second->SetOwner (nullptr); + } + m_StreamingDestinationsByPorts.clear (); + if (m_DatagramDestination) + { + delete m_DatagramDestination; + m_DatagramDestination = nullptr; } - else - return false; } #ifdef I2LUA @@ -1239,5 +1190,36 @@ namespace client } } } + + RunnableClientDestination::RunnableClientDestination (const i2p::data::PrivateKeys& keys, bool isPublic, const std::map * params): + RunnableService ("Destination"), + ClientDestination (GetIOService (), keys, isPublic, params) + { + } + + RunnableClientDestination::~RunnableClientDestination () + { + if (IsRunning ()) + Stop (); + } + + void RunnableClientDestination::Start () + { + if (!IsRunning ()) + { + ClientDestination::Start (); + StartIOService (); + } + } + + void RunnableClientDestination::Stop () + { + if (IsRunning ()) + { + ClientDestination::Stop (); + StopIOService (); + } + } + } } diff --git a/libi2pd/Destination.h b/libi2pd/Destination.h index f4483032..ed3abdfb 100644 --- a/libi2pd/Destination.h +++ b/libi2pd/Destination.h @@ -20,6 +20,7 @@ #include "NetDb.hpp" #include "Streaming.h" #include "Datagram.h" +#include "util.h" namespace i2p { @@ -98,18 +99,17 @@ namespace client public: - LeaseSetDestination (bool isPublic, const std::map * params = nullptr); + LeaseSetDestination (boost::asio::io_service& service, bool isPublic, const std::map * params = nullptr); ~LeaseSetDestination (); const std::string& GetNickname () const { return m_Nickname; }; + boost::asio::io_service& GetService () { return m_Service; }; - virtual bool Start (); - virtual bool Stop (); + virtual void Start (); + virtual void Stop (); /** i2cp reconfigure */ virtual bool Reconfigure(std::map i2cpOpts); - bool IsRunning () const { return m_IsRunning; }; - boost::asio::io_service& GetService () { return m_Service; }; std::shared_ptr GetTunnelPool () { return m_Pool; }; bool IsReady () const { return m_LeaseSet && !m_LeaseSet->IsExpired () && m_Pool->GetOutboundTunnels ().size () > 0; }; std::shared_ptr FindLeaseSet (const i2p::data::IdentHash& ident); @@ -146,7 +146,6 @@ namespace client private: - void Run (); void UpdateLeaseSet (); std::shared_ptr GetLeaseSetMt (); void Publish (); @@ -165,9 +164,7 @@ namespace client private: - volatile bool m_IsRunning; - std::thread * m_Thread; - boost::asio::io_service m_Service; + boost::asio::io_service& m_Service; mutable std::mutex m_RemoteLeaseSetsMutex; std::map > m_RemoteLeaseSets; std::map > m_LeaseSetRequests; @@ -206,11 +203,12 @@ namespace client void Ready(ReadyPromise & p); #endif - ClientDestination (const i2p::data::PrivateKeys& keys, bool isPublic, const std::map * params = nullptr); + ClientDestination (boost::asio::io_service& service, const i2p::data::PrivateKeys& keys, + bool isPublic, const std::map * params = nullptr); ~ClientDestination (); - virtual bool Start (); - virtual bool Stop (); + void Start (); + void Stop (); const i2p::data::PrivateKeys& GetPrivateKeys () const { return m_Keys; }; void Sign (const uint8_t * buf, int len, uint8_t * signature) const { m_Keys.Sign (buf, len, signature); }; @@ -284,6 +282,18 @@ namespace client // for HTTP only std::vector > GetAllStreams () const; }; + + class RunnableClientDestination: private i2p::util::RunnableService, public ClientDestination + { + public: + + RunnableClientDestination (const i2p::data::PrivateKeys& keys, bool isPublic, const std::map * params = nullptr); + ~RunnableClientDestination (); + + void Start (); + void Stop (); + }; + } } diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index 96efe5e6..d2d07343 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -5,6 +5,7 @@ #include "Elligator.h" #include "Tag.h" #include "I2PEndian.h" +#include "Timestamp.h" #include "ECIESX25519AEADRatchetSession.h" namespace i2p @@ -12,19 +13,72 @@ namespace i2p namespace garlic { - ECIESX25519AEADRatchetSession::ECIESX25519AEADRatchetSession () + void RatchetTagSet::DHInitialize (const uint8_t * rootKey, const uint8_t * k) { - // TODO : use precalculated hashes - static const char protocolName[41] = "Noise_IKelg2+hs2_25519_ChaChaPoly_SHA256"; // 40 bytes - SHA256 ((const uint8_t *)protocolName, 40, m_H); - memcpy (m_CK, m_H, 32); - SHA256 (m_H, 32, m_H); + // DH_INITIALIZE(rootKey, k) + uint8_t keydata[64]; + i2p::crypto::HKDF (rootKey, k, 32, "KDFDHRatchetStep", keydata); // keydata = HKDF(rootKey, k, "KDFDHRatchetStep", 64) + // nextRootKey = keydata[0:31] + i2p::crypto::HKDF (keydata + 32, nullptr, 0, "TagAndKeyGenKeys", m_KeyData.buf); + // [sessTag_ck, symmKey_ck] = HKDF(keydata[32:63], ZEROLEN, "TagAndKeyGenKeys", 64) + memcpy (m_SymmKeyCK, m_KeyData.buf + 32, 32); + m_NextSymmKeyIndex = 0; + } + + void RatchetTagSet::NextSessionTagRatchet () + { + i2p::crypto::HKDF (m_KeyData.GetSessTagCK (), nullptr, 0, "STInitialization", m_KeyData.buf); // [sessTag_ck, sesstag_constant] = HKDF(sessTag_ck, ZEROLEN, "STInitialization", 64) + memcpy (m_SessTagConstant, m_KeyData.GetSessTagConstant (), 32); + m_NextIndex = 0; + } + + uint64_t RatchetTagSet::GetNextSessionTag () + { + i2p::crypto::HKDF (m_KeyData.GetSessTagCK (), m_SessTagConstant, 32, "SessionTagKeyGen", m_KeyData.buf); // [sessTag_ck, tag] = HKDF(sessTag_chainkey, SESSTAG_CONSTANT, "SessionTagKeyGen", 64) + m_NextIndex++; + return m_KeyData.GetTag (); + } + + const uint8_t * RatchetTagSet::GetSymmKey (int index) + { + // TODO: store intermediate keys + if (m_NextSymmKeyIndex > 0 && index == m_NextSymmKeyIndex) + { + i2p::crypto::HKDF (m_CurrentSymmKeyCK, nullptr, 0, "SymmetricRatchet", m_CurrentSymmKeyCK); + m_NextSymmKeyIndex++; + } + else + CalculateSymmKeyCK (index); + return m_CurrentSymmKeyCK + 32; + } + + void RatchetTagSet::CalculateSymmKeyCK (int index) + { + i2p::crypto::HKDF (m_SymmKeyCK, nullptr, 0, "SymmetricRatchet", m_CurrentSymmKeyCK); // keydata_0 = HKDF(symmKey_ck, SYMMKEY_CONSTANT, "SymmetricRatchet", 64) + for (int i = 0; i < index; i++) + i2p::crypto::HKDF (m_CurrentSymmKeyCK, nullptr, 0, "SymmetricRatchet", m_CurrentSymmKeyCK); // keydata_n = HKDF(symmKey_chainKey_(n-1), SYMMKEY_CONSTANT, "SymmetricRatchet", 64) + m_NextSymmKeyIndex = index + 1; + } + + ECIESX25519AEADRatchetSession::ECIESX25519AEADRatchetSession (GarlicDestination * owner): + GarlicRoutingSession (owner, true) + { + ResetKeys (); } ECIESX25519AEADRatchetSession::~ECIESX25519AEADRatchetSession () { } + void ECIESX25519AEADRatchetSession::ResetKeys () + { + // TODO : use precalculated hashes + static const char protocolName[41] = "Noise_IKelg2+hs2_25519_ChaChaPoly_SHA256"; // 40 bytes + SHA256 ((const uint8_t *)protocolName, 40, m_H); + memcpy (m_CK, m_H, 32); + SHA256 (m_H, 32, m_H); + } + void ECIESX25519AEADRatchetSession::MixHash (const uint8_t * buf, size_t len) { SHA256_CTX ctx; @@ -33,32 +87,58 @@ namespace garlic SHA256_Update (&ctx, buf, len); SHA256_Final (m_H, &ctx); } + + void ECIESX25519AEADRatchetSession::CreateNonce (uint64_t seqn, uint8_t * nonce) + { + memset (nonce, 0, 4); + htole64buf (nonce + 4, seqn); + } - bool ECIESX25519AEADRatchetSession::NewIncomingSession (const i2p::data::LocalDestination& dest, - const uint8_t * buf, size_t len, CloveHandler handleClove) + bool ECIESX25519AEADRatchetSession::GenerateEphemeralKeysAndEncode (uint8_t * buf) { + for (int i = 0; i < 10; i++) + { + m_EphemeralKeys.GenerateKeys (); + if (i2p::crypto::GetElligator ()->Encode (m_EphemeralKeys.GetPublicKey (), buf)) + return true; // success + } + return false; + } + + uint64_t ECIESX25519AEADRatchetSession::CreateNewSessionTag () const + { + uint8_t tagsetKey[32]; + i2p::crypto::HKDF (m_CK, nullptr, 0, "SessionReplyTags", tagsetKey, 32); // tagsetKey = HKDF(chainKey, ZEROLEN, "SessionReplyTags", 32) + // Session Tag Ratchet + RatchetTagSet tagsetNsr; + tagsetNsr.DHInitialize (m_CK, tagsetKey); // tagset_nsr = DH_INITIALIZE(chainKey, tagsetKey) + tagsetNsr.NextSessionTagRatchet (); + return tagsetNsr.GetNextSessionTag (); + } + + bool ECIESX25519AEADRatchetSession::HandleNewIncomingSession (const uint8_t * buf, size_t len) + { + if (!GetOwner ()) return false; // we are Bob // KDF1 - MixHash (dest.GetEncryptionPublicKey (), 32); // h = SHA256(h || bpk) - - uint8_t aepk[32]; // Alice's ephemeral key - if (!i2p::crypto::GetElligator ()->Decode (buf, aepk)) + MixHash (GetOwner ()->GetEncryptionPublicKey (), 32); // h = SHA256(h || bpk) + + if (!i2p::crypto::GetElligator ()->Decode (buf, m_Aepk)) { LogPrint (eLogError, "Garlic: Can't decode elligator"); return false; } buf += 32; len -= 32; - MixHash (aepk, 32); // h = SHA256(h || aepk) + MixHash (m_Aepk, 32); // h = SHA256(h || aepk) - uint8_t sharedSecret[32], keyData[64]; - dest.Decrypt (aepk, sharedSecret, nullptr); // x25519(bsk, aepk) - i2p::crypto::HKDF (m_CK, sharedSecret, 32, "", keyData); // keydata = HKDF(chainKey, sharedSecret, "", 64) - memcpy (m_CK, keyData, 32); // chainKey = keydata[0:31] - + uint8_t sharedSecret[32]; + GetOwner ()->Decrypt (m_Aepk, sharedSecret, nullptr); // x25519(bsk, aepk) + i2p::crypto::HKDF (m_CK, sharedSecret, 32, "", m_CK); // [chainKey, key] = HKDF(chainKey, sharedSecret, "", 64) + // decrypt flags/static uint8_t nonce[12], fs[32]; - memset (nonce, 0, 12); // n = 0 - if (!i2p::crypto::AEADChaCha20Poly1305 (buf, 32, m_H, 32, keyData + 32, nonce, fs, 32, false)) // decrypt + CreateNonce (0, nonce); + if (!i2p::crypto::AEADChaCha20Poly1305 (buf, 32, m_H, 32, m_CK + 32, nonce, fs, 32, false)) // decrypt { LogPrint (eLogWarning, "Garlic: Flags/static section AEAD verification failed "); return false; @@ -73,25 +153,27 @@ namespace garlic if (isStatic) { // static key, fs is apk - dest.Decrypt (fs, sharedSecret, nullptr); // x25519(bsk, apk) - i2p::crypto::HKDF (m_CK, sharedSecret, 32, "", keyData); // keydata = HKDF(chainKey, sharedSecret, "", 64) - memcpy (m_CK, keyData, 32); // chainKey = keydata[0:31] + memcpy (m_RemoteStaticKey, fs, 32); + GetOwner ()->Decrypt (fs, sharedSecret, nullptr); // x25519(bsk, apk) + i2p::crypto::HKDF (m_CK, sharedSecret, 32, "", m_CK); // [chainKey, key] = HKDF(chainKey, sharedSecret, "", 64) } else // all zeros flags - htole64buf (nonce + 4, 1); // n = 1 - if (!i2p::crypto::AEADChaCha20Poly1305 (buf, len - 16, m_H, 32, keyData + 32, nonce, payload.data (), len - 16, false)) // decrypt + CreateNonce (1, nonce); + if (!i2p::crypto::AEADChaCha20Poly1305 (buf, len - 16, m_H, 32, m_CK + 32, nonce, payload.data (), len - 16, false)) // decrypt { LogPrint (eLogWarning, "Garlic: Payload section AEAD verification failed"); return false; } if (isStatic) MixHash (buf, len); // h = SHA256(h || ciphertext) - - HandlePayload (payload.data (), len - 16, handleClove); + m_State = eSessionStateNewSessionReceived; + GetOwner ()->AddECIESx25519Session (m_RemoteStaticKey, shared_from_this ()); + + HandlePayload (payload.data (), len - 16); return true; } - void ECIESX25519AEADRatchetSession::HandlePayload (const uint8_t * buf, size_t len, CloveHandler& handleClove) + void ECIESX25519AEADRatchetSession::HandlePayload (const uint8_t * buf, size_t len) { size_t offset = 0; while (offset < len) @@ -109,7 +191,7 @@ namespace garlic switch (blk) { case eECIESx25519BlkGalicClove: - handleClove (buf + offset, size); + GetOwner ()->HandleECIESx25519GarlicClove (buf + offset, size); break; case eECIESx25519BlkDateTime: LogPrint (eLogDebug, "Garlic: datetime"); @@ -126,6 +208,314 @@ namespace garlic offset += size; } } + + bool ECIESX25519AEADRatchetSession::NewOutgoingSessionMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen) + { + ResetKeys (); + // we are Alice, bpk is m_RemoteStaticKey + size_t offset = 0; + if (!GenerateEphemeralKeysAndEncode (out + offset)) + { + LogPrint (eLogError, "Garlic: Can't encode elligator"); + return false; + } + offset += 32; + + // KDF1 + MixHash (m_RemoteStaticKey, 32); // h = SHA256(h || bpk) + MixHash (m_EphemeralKeys.GetPublicKey (), 32); // h = SHA256(h || aepk) + uint8_t sharedSecret[32]; + m_EphemeralKeys.Agree (m_RemoteStaticKey, sharedSecret); // x25519(aesk, bpk) + i2p::crypto::HKDF (m_CK, sharedSecret, 32, "", m_CK); // [chainKey, key] = HKDF(chainKey, sharedSecret, "", 64) + // encrypt static key section + uint8_t nonce[12]; + CreateNonce (0, nonce); + if (!i2p::crypto::AEADChaCha20Poly1305 (GetOwner ()->GetEncryptionPublicKey (), 32, m_H, 32, m_CK + 32, nonce, out + offset, 48, true)) // encrypt + { + LogPrint (eLogWarning, "Garlic: Static section AEAD encryption failed "); + return false; + } + MixHash (out + offset, 48); // h = SHA256(h || ciphertext) + offset += 48; + // KDF2 + GetOwner ()->Decrypt (m_RemoteStaticKey, sharedSecret, nullptr); // x25519 (ask, bpk) + i2p::crypto::HKDF (m_CK, sharedSecret, 32, "", m_CK); // [chainKey, key] = HKDF(chainKey, sharedSecret, "", 64) + // encrypt payload + if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, m_H, 32, m_CK + 32, nonce, out + offset, len + 16, true)) // encrypt + { + LogPrint (eLogWarning, "Garlic: Payload section AEAD encryption failed"); + return false; + } + MixHash (out + offset, len + 16); // h = SHA256(h || ciphertext) + + m_State = eSessionStateNewSessionSent; + if (GetOwner ()) + GetOwner ()->AddECIESx25519SessionTag (0, CreateNewSessionTag (), shared_from_this ()); + + return true; + } + + bool ECIESX25519AEADRatchetSession::NewSessionReplyMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen) + { + // we are Bob + uint64_t tag = CreateNewSessionTag (); + + size_t offset = 0; + memcpy (out + offset, &tag, 8); + offset += 8; + if (!GenerateEphemeralKeysAndEncode (out + offset)) // bepk + { + LogPrint (eLogError, "Garlic: Can't encode elligator"); + return false; + } + offset += 32; + // KDF for Reply Key Section + MixHash ((const uint8_t *)&tag, 8); // h = SHA256(h || tag) + MixHash (m_EphemeralKeys.GetPublicKey (), 32); // h = SHA256(h || bepk) + uint8_t sharedSecret[32]; + m_EphemeralKeys.Agree (m_Aepk, sharedSecret); // sharedSecret = x25519(besk, aepk) + i2p::crypto::HKDF (m_CK, sharedSecret, 32, "", m_CK, 32); // chainKey = HKDF(chainKey, sharedSecret, "", 32) + m_EphemeralKeys.Agree (m_RemoteStaticKey, sharedSecret); // sharedSecret = x25519(besk, apk) + i2p::crypto::HKDF (m_CK, sharedSecret, 32, "", m_CK); // [chainKey, key] = HKDF(chainKey, sharedSecret, "", 64) + uint8_t nonce[12]; + CreateNonce (0, nonce); + // calulate hash for zero length + if (!i2p::crypto::AEADChaCha20Poly1305 (sharedSecret /* can be anything */, 0, m_H, 32, m_CK + 32, nonce, out + offset, 16, true)) // encrypt, ciphertext = ENCRYPT(k, n, ZEROLEN, ad) + { + LogPrint (eLogWarning, "Garlic: Reply key section AEAD encryption failed"); + return false; + } + MixHash (out + offset, 16); // h = SHA256(h || ciphertext) + out += 16; + // KDF for payload + uint8_t keydata[64]; + i2p::crypto::HKDF (m_CK, nullptr, 0, "", keydata); // keydata = HKDF(chainKey, ZEROLEN, "", 64) + // k_ab = keydata[0:31], k_ba = keydata[32:63] + m_ReceiveTagset.DHInitialize (m_CK, keydata); // tagset_ab = DH_INITIALIZE(chainKey, k_ab) + m_ReceiveTagset.NextSessionTagRatchet (); + m_SendTagset.DHInitialize (m_CK, keydata + 32); // tagset_ba = DH_INITIALIZE(chainKey, k_ba) + m_SendTagset.NextSessionTagRatchet (); + GenerateMoreReceiveTags (GetOwner ()->GetNumTags ()); + i2p::crypto::HKDF (keydata + 32, nullptr, 0, "AttachPayloadKDF", keydata, 32); // k = HKDF(k_ba, ZEROLEN, "AttachPayloadKDF", 32) + // encrypt payload + if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, m_H, 32, keydata, nonce, out + offset, len + 16, true)) // encrypt + { + LogPrint (eLogWarning, "Garlic: Payload section AEAD encryption failed"); + return false; + } + m_State = eSessionStateEstablished; + + return true; + } + + bool ECIESX25519AEADRatchetSession::HandleNewOutgoingSessionReply (const uint8_t * buf, size_t len) + { + // we are Alice + LogPrint (eLogDebug, "Garlic: reply received"); + const uint8_t * tag = buf; + buf += 8; len -= 8; // tag + uint8_t bepk[32]; // Bob's ephemeral key + if (!i2p::crypto::GetElligator ()->Decode (buf, bepk)) + { + LogPrint (eLogError, "Garlic: Can't decode elligator"); + return false; + } + buf += 32; len -= 32; + // KDF for Reply Key Section + MixHash (tag, 8); // h = SHA256(h || tag) + MixHash (bepk, 32); // h = SHA256(h || bepk) + uint8_t sharedSecret[32]; + m_EphemeralKeys.Agree (bepk, sharedSecret); // sharedSecret = x25519(aesk, bepk) + i2p::crypto::HKDF (m_CK, sharedSecret, 32, "", m_CK, 32); // chainKey = HKDF(chainKey, sharedSecret, "", 32) + GetOwner ()->Decrypt (bepk, sharedSecret, nullptr); // x25519 (ask, bepk) + i2p::crypto::HKDF (m_CK, sharedSecret, 32, "", m_CK); // [chainKey, key] = HKDF(chainKey, sharedSecret, "", 64) + uint8_t nonce[12]; + CreateNonce (0, nonce); + // calulate hash for zero length + if (!i2p::crypto::AEADChaCha20Poly1305 (buf, 0, m_H, 32, m_CK + 32, nonce, sharedSecret/* can be anyting */, 0, false)) // decrypt, DECRYPT(k, n, ZEROLEN, ad) verification only + { + LogPrint (eLogWarning, "Garlic: Reply key section AEAD decryption failed"); + return false; + } + MixHash (buf, 16); // h = SHA256(h || ciphertext) + buf += 16; len -= 16; + // KDF for payload + uint8_t keydata[64]; + i2p::crypto::HKDF (m_CK, nullptr, 0, "", keydata); // keydata = HKDF(chainKey, ZEROLEN, "", 64) + // k_ab = keydata[0:31], k_ba = keydata[32:63] + m_SendTagset.DHInitialize (m_CK, keydata); // tagset_ab = DH_INITIALIZE(chainKey, k_ab) + m_SendTagset.NextSessionTagRatchet (); + m_ReceiveTagset.DHInitialize (m_CK, keydata + 32); // tagset_ba = DH_INITIALIZE(chainKey, k_ba) + m_ReceiveTagset.NextSessionTagRatchet (); + GenerateMoreReceiveTags (GetOwner ()->GetNumTags ()); + i2p::crypto::HKDF (keydata + 32, nullptr, 0, "AttachPayloadKDF", keydata, 32); // k = HKDF(k_ba, ZEROLEN, "AttachPayloadKDF", 32) + // decrypt payload + std::vector payload (len - 16); + if (!i2p::crypto::AEADChaCha20Poly1305 (buf, len - 16, m_H, 32, keydata, nonce, payload.data (), len - 16, false)) // decrypt + { + LogPrint (eLogWarning, "Garlic: Payload section AEAD decryption failed"); + return false; + } + + m_State = eSessionStateEstablished; + GetOwner ()->AddECIESx25519Session (m_RemoteStaticKey, shared_from_this ()); + HandlePayload (payload.data (), len - 16); + + return true; + } + + bool ECIESX25519AEADRatchetSession::NewExistingSessionMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen) + { + uint8_t nonce[12]; + auto index = m_SendTagset.GetNextIndex (); + CreateNonce (index, nonce); // tag's index + uint64_t tag = m_SendTagset.GetNextSessionTag (); + memcpy (out, &tag, 8); + // ad = The session tag, 8 bytes + // ciphertext = ENCRYPT(k, n, payload, ad) + if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, out, 8, m_SendTagset.GetSymmKey (index), nonce, out + 8, outLen - 8, true)) // encrypt + { + LogPrint (eLogWarning, "Garlic: Payload section AEAD encryption failed"); + return false; + } + return true; + } + + bool ECIESX25519AEADRatchetSession::HandleExistingSessionMessage (const uint8_t * buf, size_t len, int index) + { + uint8_t nonce[12]; + CreateNonce (index, nonce); // tag's index + len -= 8; // tag + std::vector payload (len - 16); + if (!i2p::crypto::AEADChaCha20Poly1305 (buf + 8, len - 16, buf, 8, m_ReceiveTagset.GetSymmKey (index), nonce, payload.data (), len - 16, false)) // decrypt + { + LogPrint (eLogWarning, "Garlic: Payload section AEAD decryption failed"); + return false; + } + HandlePayload (payload.data (), len - 16); + if (m_NumReceiveTags > 0)m_NumReceiveTags--; + if (m_NumReceiveTags <= GetOwner ()->GetNumTags ()*2/3) + GenerateMoreReceiveTags (GetOwner ()->GetNumTags ()); + return true; + } + + bool ECIESX25519AEADRatchetSession::HandleNextMessage (const uint8_t * buf, size_t len, int index) + { + m_LastActivityTimestamp = i2p::util::GetSecondsSinceEpoch (); + switch (m_State) + { + case eSessionStateEstablished: + return HandleExistingSessionMessage (buf, len, index); + case eSessionStateNew: + return HandleNewIncomingSession (buf, len); + case eSessionStateNewSessionSent: + return HandleNewOutgoingSessionReply (buf, len); + default: + return false; + } + return true; + } + + std::shared_ptr ECIESX25519AEADRatchetSession::WrapSingleMessage (std::shared_ptr msg) + { + auto m = NewI2NPMessage (); + m->Align (12); // in order to get buf aligned to 16 (12 + 4) + uint8_t * buf = m->GetPayload () + 4; // 4 bytes for length + auto payload = CreatePayload (msg); + size_t len = payload.size (); + + switch (m_State) + { + case eSessionStateEstablished: + if (!NewExistingSessionMessage (payload.data (), payload.size (), buf, m->maxLen)) + return nullptr; + len += 24; + break; + case eSessionStateNew: + if (!NewOutgoingSessionMessage (payload.data (), payload.size (), buf, m->maxLen)) + return nullptr; + len += 96; + break; + case eSessionStateNewSessionReceived: + if (!NewSessionReplyMessage (payload.data (), payload.size (), buf, m->maxLen)) + return nullptr; + len += 72; + break; + default: + return nullptr; + } + + htobe32buf (m->GetPayload (), len); + m->len += len + 4; + m->FillI2NPMessageHeader (eI2NPGarlic); + return m; + } + + std::vector ECIESX25519AEADRatchetSession::CreatePayload (std::shared_ptr msg) + { + size_t payloadLen = 7; // datatime + if (msg && m_Destination) + payloadLen += msg->GetPayloadLength () + 13 + 32; + auto leaseSet = CreateDatabaseStoreMsg (GetOwner ()->GetLeaseSet ()); + if (leaseSet) + payloadLen += leaseSet->GetPayloadLength () + 13; + uint8_t paddingSize; + RAND_bytes (&paddingSize, 1); + paddingSize &= 0x0F; paddingSize++; // 1 - 16 + payloadLen += paddingSize + 3; + std::vector v(payloadLen); + size_t offset = 0; + // DateTime + v[offset] = eECIESx25519BlkDateTime; offset++; + htobe16buf (v.data () + offset, 4); offset += 2; + htobe32buf (v.data () + offset, i2p::util::GetSecondsSinceEpoch ()); offset += 4; + // LeaseSet + if (leaseSet) + offset += CreateGarlicClove (leaseSet, v.data () + offset, payloadLen - offset); + // msg + if (msg && m_Destination) + offset += CreateGarlicClove (msg, v.data () + offset, payloadLen - offset, true); + // padding + v[offset] = eECIESx25519BlkPadding; offset++; + htobe16buf (v.data () + offset, paddingSize); offset += 2; + memset (v.data () + offset, 0, paddingSize); offset += paddingSize; + return v; + } + + size_t ECIESX25519AEADRatchetSession::CreateGarlicClove (std::shared_ptr msg, uint8_t * buf, size_t len, bool isDestination) + { + if (!msg) return 0; + uint16_t cloveSize = msg->GetPayloadLength () + 9 + 1; + if (isDestination) cloveSize += 32; + if ((int)len < cloveSize + 3) return 0; + buf[0] = eECIESx25519BlkGalicClove; // clove type + htobe16buf (buf + 1, cloveSize); // size + buf += 3; + if (isDestination) + { + *buf = (eGarlicDeliveryTypeDestination << 5); + memcpy (buf + 1, *m_Destination, 32); buf += 32; + } + else + *buf = 0; + buf++; // flag and delivery instructions + *buf = msg->GetTypeID (); // I2NP msg type + htobe32buf (buf + 1, msg->GetMsgID ()); // msgID + htobe32buf (buf + 5, msg->GetExpiration ()/1000); // expiration in seconds + memcpy (buf + 9, msg->GetPayload (), msg->GetPayloadLength ()); + return cloveSize + 3; + } + + void ECIESX25519AEADRatchetSession::GenerateMoreReceiveTags (int numTags) + { + for (int i = 0; i < numTags; i++) + { + auto index = m_ReceiveTagset.GetNextIndex (); + uint64_t tag = m_ReceiveTagset.GetNextSessionTag (); + GetOwner ()->AddECIESx25519SessionTag (index, tag, shared_from_this ()); + } + m_NumReceiveTags += numTags; + } } } diff --git a/libi2pd/ECIESX25519AEADRatchetSession.h b/libi2pd/ECIESX25519AEADRatchetSession.h index aa482d54..e5836cd4 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.h +++ b/libi2pd/ECIESX25519AEADRatchetSession.h @@ -1,14 +1,49 @@ #ifndef ECIES_X25519_AEAD_RATCHET_SESSION_H__ #define ECIES_X25519_AEAD_RATCHET_SESSION_H__ +#include #include #include +#include +#include #include "Identity.h" +#include "Crypto.h" +#include "Garlic.h" namespace i2p { namespace garlic { + class RatchetTagSet + { + public: + + void DHInitialize (const uint8_t * rootKey, const uint8_t * k); + void NextSessionTagRatchet (); + uint64_t GetNextSessionTag (); + int GetNextIndex () const { return m_NextIndex; }; + const uint8_t * GetSymmKey (int index); + + private: + + void CalculateSymmKeyCK (int index); + + private: + + union + { + uint64_t ll[8]; + uint8_t buf[64]; + + const uint8_t * GetSessTagCK () const { return buf; }; // sessTag_chainKey = keydata[0:31] + const uint8_t * GetSessTagConstant () const { return buf + 32; }; // SESSTAG_CONSTANT = keydata[32:63] + uint64_t GetTag () const { return ll[4]; }; // tag = keydata[32:39] + + } m_KeyData; + uint8_t m_SessTagConstant[32], m_SymmKeyCK[32], m_CurrentSymmKeyCK[64]; + int m_NextIndex, m_NextSymmKeyIndex; + }; + enum ECIESx25519BlockType { eECIESx25519BlkDateTime = 0, @@ -20,27 +55,71 @@ namespace garlic eECIESx25519BlkPadding = 254 }; - class ECIESX25519AEADRatchetSession + + const int ECIESX25519_RESTART_TIMEOUT = 120; // number of second of inactivity we should restart after + const int ECIESX25519_EXPIRATION_TIMEOUT = 600; // in seconds + + class ECIESX25519AEADRatchetSession: public GarlicRoutingSession, public std::enable_shared_from_this { + enum SessionState + { + eSessionStateNew =0, + eSessionStateNewSessionReceived, + eSessionStateNewSessionSent, + eSessionStateEstablished + }; + public: - typedef std::function CloveHandler; - - ECIESX25519AEADRatchetSession (); + ECIESX25519AEADRatchetSession (GarlicDestination * owner); ~ECIESX25519AEADRatchetSession (); - bool NewIncomingSession (const i2p::data::LocalDestination& dest, const uint8_t * buf, size_t len, - CloveHandler handleClove); + bool HandleNextMessage (const uint8_t * buf, size_t len, int index = 0); + std::shared_ptr WrapSingleMessage (std::shared_ptr msg); + + const uint8_t * GetRemoteStaticKey () const { return m_RemoteStaticKey; } + void SetRemoteStaticKey (const uint8_t * key) { memcpy (m_RemoteStaticKey, key, 32); } + + void SetDestination (const i2p::data::IdentHash& dest) // TODO: + { + if (!m_Destination) m_Destination.reset (new i2p::data::IdentHash (dest)); + } + + bool IsExpired (uint64_t ts) const { return ts > m_LastActivityTimestamp + ECIESX25519_EXPIRATION_TIMEOUT; } + bool CanBeRestarted (uint64_t ts) const { return ts > m_LastActivityTimestamp + ECIESX25519_RESTART_TIMEOUT; } private: + void ResetKeys (); void MixHash (const uint8_t * buf, size_t len); + void CreateNonce (uint64_t seqn, uint8_t * nonce); + bool GenerateEphemeralKeysAndEncode (uint8_t * buf); // buf is 32 bytes + uint64_t CreateNewSessionTag () const; - void HandlePayload (const uint8_t * buf, size_t len, CloveHandler& handleClove); + bool HandleNewIncomingSession (const uint8_t * buf, size_t len); + bool HandleNewOutgoingSessionReply (const uint8_t * buf, size_t len); + bool HandleExistingSessionMessage (const uint8_t * buf, size_t len, int index); + void HandlePayload (const uint8_t * buf, size_t len); + bool NewOutgoingSessionMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen); + bool NewSessionReplyMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen); + bool NewExistingSessionMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen); + + std::vector CreatePayload (std::shared_ptr msg); + size_t CreateGarlicClove (std::shared_ptr msg, uint8_t * buf, size_t len, bool isDestination = false); + + void GenerateMoreReceiveTags (int numTags); + private: - uint8_t m_H[32], m_CK[32]; + uint8_t m_H[32], m_CK[64] /* [chainkey, key] */, m_RemoteStaticKey[32]; + uint8_t m_Aepk[32]; // Alice's ephemeral keys TODO: for incoming only + i2p::crypto::X25519Keys m_EphemeralKeys; + SessionState m_State = eSessionStateNew; + uint64_t m_LastActivityTimestamp = 0; // incoming + RatchetTagSet m_SendTagset, m_ReceiveTagset; + int m_NumReceiveTags = 0; + std::unique_ptr m_Destination;// TODO: might not need it }; } } diff --git a/libi2pd/Elligator.cpp b/libi2pd/Elligator.cpp index b9471512..48a5a7ac 100644 --- a/libi2pd/Elligator.cpp +++ b/libi2pd/Elligator.cpp @@ -1,3 +1,4 @@ +#include #include "Crypto.h" #include "Elligator.h" @@ -39,8 +40,8 @@ namespace crypto BN_free (u); BN_free (iu); } - bool Elligator2::Encode (const uint8_t * key, uint8_t * encoded, bool highY) const - { + bool Elligator2::Encode (const uint8_t * key, uint8_t * encoded, bool highY, bool random) const + { bool ret = true; BN_CTX * ctx = BN_CTX_new (); BN_CTX_start (ctx); @@ -61,7 +62,14 @@ namespace crypto BN_mod_mul (uxxA, uxxA, xA, p, ctx); if (Legendre (uxxA, ctx) != -1) - { + { + uint8_t randByte = 0; // random highest bits and high y + if (random) + { + RAND_bytes (&randByte, 1); + highY = randByte & 0x01; + } + BIGNUM * r = BN_CTX_get (ctx); if (highY) { @@ -77,7 +85,9 @@ namespace crypto SquareRoot (r, r, ctx); bn2buf (r, encoded, 32); - + + if (random) + encoded[0] |= (randByte & 0xC0); // copy two highest bits from randByte for (size_t i = 0; i < 16; i++) // To Little Endian { uint8_t tmp = encoded[i]; @@ -105,6 +115,7 @@ namespace crypto encoded1[i] = encoded[31 - i]; encoded1[31 - i] = encoded[i]; } + encoded1[0] &= 0x3F; // drop two highest bits BIGNUM * r = BN_CTX_get (ctx); BN_bin2bn (encoded1, 32, r); diff --git a/libi2pd/Elligator.h b/libi2pd/Elligator.h index ca463568..7cdcbbfe 100644 --- a/libi2pd/Elligator.h +++ b/libi2pd/Elligator.h @@ -17,7 +17,7 @@ namespace crypto Elligator2 (); ~Elligator2 (); - bool Encode (const uint8_t * key, uint8_t * encoded, bool highY = false) const; + bool Encode (const uint8_t * key, uint8_t * encoded, bool highY = false, bool random = true) const; bool Decode (const uint8_t * encoded, uint8_t * key) const; private: diff --git a/libi2pd/Garlic.cpp b/libi2pd/Garlic.cpp index aa715e0c..c7d0e21d 100644 --- a/libi2pd/Garlic.cpp +++ b/libi2pd/Garlic.cpp @@ -18,24 +18,15 @@ namespace i2p { namespace garlic { - GarlicRoutingSession::GarlicRoutingSession (GarlicDestination * owner, - std::shared_ptr destination, int numTags, bool attachLeaseSet): - m_Owner (owner), m_Destination (destination), m_NumTags (numTags), - m_LeaseSetUpdateStatus (attachLeaseSet ? eLeaseSetUpdated : eLeaseSetDoNotSend), + GarlicRoutingSession::GarlicRoutingSession (GarlicDestination * owner, bool attachLeaseSet): + m_Owner (owner), m_LeaseSetUpdateStatus (attachLeaseSet ? eLeaseSetUpdated : eLeaseSetDoNotSend), m_LeaseSetUpdateMsgID (0) { - // create new session tags and session key - RAND_bytes (m_SessionKey, 32); - m_Encryption.SetKey (m_SessionKey); } - GarlicRoutingSession::GarlicRoutingSession (const uint8_t * sessionKey, const SessionTag& sessionTag): - m_Owner (nullptr), m_NumTags (1), m_LeaseSetUpdateStatus (eLeaseSetDoNotSend), m_LeaseSetUpdateMsgID (0) + GarlicRoutingSession::GarlicRoutingSession (): + m_Owner (nullptr), m_LeaseSetUpdateStatus (eLeaseSetDoNotSend), m_LeaseSetUpdateMsgID (0) { - memcpy (m_SessionKey, sessionKey, 32); - m_Encryption.SetKey (m_SessionKey); - m_SessionTags.push_back (sessionTag); - m_SessionTags.back ().creationTime = i2p::util::GetSecondsSinceEpoch (); } GarlicRoutingSession::~GarlicRoutingSession () @@ -67,88 +58,26 @@ namespace garlic m_SharedRoutingPath = path; } - GarlicRoutingSession::UnconfirmedTags * GarlicRoutingSession::GenerateSessionTags () + ElGamalAESSession::ElGamalAESSession (GarlicDestination * owner, + std::shared_ptr destination, int numTags, bool attachLeaseSet): + GarlicRoutingSession (owner, attachLeaseSet), + m_Destination (destination), m_NumTags (numTags) { - auto tags = new UnconfirmedTags (m_NumTags); - tags->tagsCreationTime = i2p::util::GetSecondsSinceEpoch (); - for (int i = 0; i < m_NumTags; i++) - { - RAND_bytes (tags->sessionTags[i], 32); - tags->sessionTags[i].creationTime = tags->tagsCreationTime; - } - return tags; + // create new session tags and session key + RAND_bytes (m_SessionKey, 32); + m_Encryption.SetKey (m_SessionKey); } - void GarlicRoutingSession::MessageConfirmed (uint32_t msgID) + ElGamalAESSession::ElGamalAESSession (const uint8_t * sessionKey, const SessionTag& sessionTag): + m_NumTags(1) { - TagsConfirmed (msgID); - if (msgID == m_LeaseSetUpdateMsgID) - { - m_LeaseSetUpdateStatus = eLeaseSetUpToDate; - m_LeaseSetUpdateMsgID = 0; - LogPrint (eLogInfo, "Garlic: LeaseSet update confirmed"); - } - else - CleanupExpiredTags (); + memcpy (m_SessionKey, sessionKey, 32); + m_Encryption.SetKey (m_SessionKey); + m_SessionTags.push_back (sessionTag); + m_SessionTags.back ().creationTime = i2p::util::GetSecondsSinceEpoch (); } - void GarlicRoutingSession::TagsConfirmed (uint32_t msgID) - { - uint32_t ts = i2p::util::GetSecondsSinceEpoch (); - auto it = m_UnconfirmedTagsMsgs.find (msgID); - if (it != m_UnconfirmedTagsMsgs.end ()) - { - auto& tags = it->second; - if (ts < tags->tagsCreationTime + OUTGOING_TAGS_EXPIRATION_TIMEOUT) - { - for (int i = 0; i < tags->numTags; i++) - m_SessionTags.push_back (tags->sessionTags[i]); - } - m_UnconfirmedTagsMsgs.erase (it); - } - } - - bool GarlicRoutingSession::CleanupExpiredTags () - { - auto ts = i2p::util::GetSecondsSinceEpoch (); - for (auto it = m_SessionTags.begin (); it != m_SessionTags.end ();) - { - if (ts >= it->creationTime + OUTGOING_TAGS_EXPIRATION_TIMEOUT) - it = m_SessionTags.erase (it); - else - ++it; - } - CleanupUnconfirmedTags (); - if (m_LeaseSetUpdateMsgID && ts*1000LL > m_LeaseSetSubmissionTime + LEASET_CONFIRMATION_TIMEOUT) - { - if (m_Owner) - m_Owner->RemoveDeliveryStatusSession (m_LeaseSetUpdateMsgID); - m_LeaseSetUpdateMsgID = 0; - } - return !m_SessionTags.empty () || !m_UnconfirmedTagsMsgs.empty (); - } - - bool GarlicRoutingSession::CleanupUnconfirmedTags () - { - bool ret = false; - uint32_t ts = i2p::util::GetSecondsSinceEpoch (); - // delete expired unconfirmed tags - for (auto it = m_UnconfirmedTagsMsgs.begin (); it != m_UnconfirmedTagsMsgs.end ();) - { - if (ts >= it->second->tagsCreationTime + OUTGOING_TAGS_CONFIRMATION_TIMEOUT) - { - if (m_Owner) - m_Owner->RemoveDeliveryStatusSession (it->first); - it = m_UnconfirmedTagsMsgs.erase (it); - ret = true; - } - else - ++it; - } - return ret; - } - - std::shared_ptr GarlicRoutingSession::WrapSingleMessage (std::shared_ptr msg) + std::shared_ptr ElGamalAESSession::WrapSingleMessage (std::shared_ptr msg) { auto m = NewI2NPMessage (); m->Align (12); // in order to get buf aligned to 16 (12 + 4) @@ -214,10 +143,10 @@ namespace garlic return m; } - size_t GarlicRoutingSession::CreateAESBlock (uint8_t * buf, std::shared_ptr msg) + size_t ElGamalAESSession::CreateAESBlock (uint8_t * buf, std::shared_ptr msg) { size_t blockSize = 0; - bool createNewTags = m_Owner && m_NumTags && ((int)m_SessionTags.size () <= m_NumTags*2/3); + bool createNewTags = GetOwner () && m_NumTags && ((int)m_SessionTags.size () <= m_NumTags*2/3); UnconfirmedTags * newTags = createNewTags ? GenerateSessionTags () : nullptr; htobuf16 (buf, newTags ? htobe16 (newTags->numTags) : 0); // tag count blockSize += 2; @@ -246,7 +175,7 @@ namespace garlic return blockSize; } - size_t GarlicRoutingSession::CreateGarlicPayload (uint8_t * payload, std::shared_ptr msg, UnconfirmedTags * newTags) + size_t ElGamalAESSession::CreateGarlicPayload (uint8_t * payload, std::shared_ptr msg, UnconfirmedTags * newTags) { uint64_t ts = i2p::util::GetMillisecondsSinceEpoch (); uint32_t msgID; @@ -256,17 +185,17 @@ namespace garlic *numCloves = 0; size++; - if (m_Owner) + if (GetOwner ()) { // resubmit non-confirmed LeaseSet - if (m_LeaseSetUpdateStatus == eLeaseSetSubmitted && ts > m_LeaseSetSubmissionTime + LEASET_CONFIRMATION_TIMEOUT) + if (GetLeaseSetUpdateStatus () == eLeaseSetSubmitted && ts > GetLeaseSetSubmissionTime () + LEASET_CONFIRMATION_TIMEOUT) { - m_LeaseSetUpdateStatus = eLeaseSetUpdated; + SetLeaseSetUpdateStatus (eLeaseSetUpdated); SetSharedRoutingPath (nullptr); // invalidate path since leaseset was not confirmed } // attach DeviveryStatus if necessary - if (newTags || m_LeaseSetUpdateStatus == eLeaseSetUpdated) // new tags created or leaseset updated + if (newTags || GetLeaseSetUpdateStatus () == eLeaseSetUpdated) // new tags created or leaseset updated { // clove is DeliveryStatus auto cloveSize = CreateDeliveryStatusClove (payload + size, msgID); @@ -280,20 +209,20 @@ namespace garlic m_UnconfirmedTagsMsgs.insert (std::make_pair(msgID, std::unique_ptr(newTags))); newTags = nullptr; // got acquired } - m_Owner->DeliveryStatusSent (shared_from_this (), msgID); + GetOwner ()->DeliveryStatusSent (shared_from_this (), msgID); } else LogPrint (eLogWarning, "Garlic: DeliveryStatus clove was not created"); } // attach LeaseSet - if (m_LeaseSetUpdateStatus == eLeaseSetUpdated) + if (GetLeaseSetUpdateStatus () == eLeaseSetUpdated) { - if (m_LeaseSetUpdateMsgID) m_Owner->RemoveDeliveryStatusSession (m_LeaseSetUpdateMsgID); // remove previous - m_LeaseSetUpdateStatus = eLeaseSetSubmitted; - m_LeaseSetUpdateMsgID = msgID; - m_LeaseSetSubmissionTime = ts; + if (GetLeaseSetUpdateMsgID ()) GetOwner ()->RemoveDeliveryStatusSession (GetLeaseSetUpdateMsgID ()); // remove previous + SetLeaseSetUpdateStatus (eLeaseSetSubmitted); + SetLeaseSetUpdateMsgID (msgID); + SetLeaseSetSubmissionTime (ts); // clove if our leaseSet must be attached - auto leaseSet = CreateDatabaseStoreMsg (m_Owner->GetLeaseSet ()); + auto leaseSet = CreateDatabaseStoreMsg (GetOwner ()->GetLeaseSet ()); size += CreateGarlicClove (payload + size, leaseSet, false); (*numCloves)++; } @@ -314,7 +243,7 @@ namespace garlic return size; } - size_t GarlicRoutingSession::CreateGarlicClove (uint8_t * buf, std::shared_ptr msg, bool isDestination) + size_t ElGamalAESSession::CreateGarlicClove (uint8_t * buf, std::shared_ptr msg, bool isDestination) { uint64_t ts = i2p::util::GetMillisecondsSinceEpoch () + 8000; // 8 sec size_t size = 0; @@ -344,12 +273,12 @@ namespace garlic return size; } - size_t GarlicRoutingSession::CreateDeliveryStatusClove (uint8_t * buf, uint32_t msgID) + size_t ElGamalAESSession::CreateDeliveryStatusClove (uint8_t * buf, uint32_t msgID) { size_t size = 0; - if (m_Owner) + if (GetOwner ()) { - auto inboundTunnel = m_Owner->GetTunnelPool ()->GetNextInboundTunnel (); + auto inboundTunnel = GetOwner ()->GetTunnelPool ()->GetNextInboundTunnel (); if (inboundTunnel) { buf[size] = eGarlicDeliveryTypeTunnel << 5; // delivery instructions flag tunnel @@ -361,14 +290,14 @@ namespace garlic size += 4; // create msg auto msg = CreateDeliveryStatusMsg (msgID); - if (m_Owner) + if (GetOwner ()) { //encrypt uint8_t key[32], tag[32]; RAND_bytes (key, 32); // random session key RAND_bytes (tag, 32); // random session tag - m_Owner->SubmitSessionKey (key, tag); - GarlicRoutingSession garlic (key, tag); + GetOwner ()->SubmitSessionKey (key, tag); + ElGamalAESSession garlic (key, tag); msg = garlic.WrapSingleMessage (msg); } memcpy (buf + size, msg->GetBuffer (), msg->GetLength ()); @@ -393,6 +322,87 @@ namespace garlic return size; } + ElGamalAESSession::UnconfirmedTags * ElGamalAESSession::GenerateSessionTags () + { + auto tags = new UnconfirmedTags (m_NumTags); + tags->tagsCreationTime = i2p::util::GetSecondsSinceEpoch (); + for (int i = 0; i < m_NumTags; i++) + { + RAND_bytes (tags->sessionTags[i], 32); + tags->sessionTags[i].creationTime = tags->tagsCreationTime; + } + return tags; + } + + void ElGamalAESSession::MessageConfirmed (uint32_t msgID) + { + TagsConfirmed (msgID); + if (msgID == GetLeaseSetUpdateMsgID ()) + { + SetLeaseSetUpdateStatus (eLeaseSetUpToDate); + SetLeaseSetUpdateMsgID (0); + LogPrint (eLogInfo, "Garlic: LeaseSet update confirmed"); + } + else + CleanupExpiredTags (); + } + + void ElGamalAESSession::TagsConfirmed (uint32_t msgID) + { + uint32_t ts = i2p::util::GetSecondsSinceEpoch (); + auto it = m_UnconfirmedTagsMsgs.find (msgID); + if (it != m_UnconfirmedTagsMsgs.end ()) + { + auto& tags = it->second; + if (ts < tags->tagsCreationTime + OUTGOING_TAGS_EXPIRATION_TIMEOUT) + { + for (int i = 0; i < tags->numTags; i++) + m_SessionTags.push_back (tags->sessionTags[i]); + } + m_UnconfirmedTagsMsgs.erase (it); + } + } + + bool ElGamalAESSession::CleanupExpiredTags () + { + auto ts = i2p::util::GetSecondsSinceEpoch (); + for (auto it = m_SessionTags.begin (); it != m_SessionTags.end ();) + { + if (ts >= it->creationTime + OUTGOING_TAGS_EXPIRATION_TIMEOUT) + it = m_SessionTags.erase (it); + else + ++it; + } + CleanupUnconfirmedTags (); + if (GetLeaseSetUpdateMsgID () && ts*1000LL > GetLeaseSetSubmissionTime () + LEASET_CONFIRMATION_TIMEOUT) + { + if (GetOwner ()) + GetOwner ()->RemoveDeliveryStatusSession (GetLeaseSetUpdateMsgID ()); + SetLeaseSetUpdateMsgID (0); + } + return !m_SessionTags.empty () || !m_UnconfirmedTagsMsgs.empty (); + } + + bool ElGamalAESSession::CleanupUnconfirmedTags () + { + bool ret = false; + uint32_t ts = i2p::util::GetSecondsSinceEpoch (); + // delete expired unconfirmed tags + for (auto it = m_UnconfirmedTagsMsgs.begin (); it != m_UnconfirmedTagsMsgs.end ();) + { + if (ts >= it->second->tagsCreationTime + OUTGOING_TAGS_CONFIRMATION_TIMEOUT) + { + if (GetOwner ()) + GetOwner ()->RemoveDeliveryStatusSession (it->first); + it = m_UnconfirmedTagsMsgs.erase (it); + ret = true; + } + else + ++it; + } + return ret; + } + GarlicDestination::GarlicDestination (): m_NumTags (32) // 32 tags by default { m_Ctx = BN_CTX_new (); @@ -408,6 +418,8 @@ namespace garlic m_Sessions.clear (); m_DeliveryStatusSessions.clear (); m_Tags.clear (); + m_ECIESx25519Sessions.clear (); + m_ECIESx25519Tags.clear (); } void GarlicDestination::AddSessionKey (const uint8_t * key, const uint8_t * tag) { @@ -646,21 +658,41 @@ namespace garlic std::shared_ptr GarlicDestination::GetRoutingSession ( std::shared_ptr destination, bool attachLeaseSet) { - GarlicRoutingSessionPtr session; - { - std::unique_lock l(m_SessionsMutex); - auto it = m_Sessions.find (destination->GetIdentHash ()); - if (it != m_Sessions.end ()) - session = it->second; - } - if (!session) - { - session = std::make_shared (this, destination, - attachLeaseSet ? m_NumTags : 4, attachLeaseSet); // specified num tags for connections and 4 for LS requests - std::unique_lock l(m_SessionsMutex); - m_Sessions[destination->GetIdentHash ()] = session; - } - return session; + if (destination->GetEncryptionType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RARCHET && + GetEncryptionType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RARCHET) + { + ECIESX25519AEADRatchetSessionPtr session; + uint8_t staticKey[32]; + destination->Encrypt (nullptr, staticKey, nullptr); // we are supposed to get static key + auto it = m_ECIESx25519Sessions.find (staticKey); + if (it != m_ECIESx25519Sessions.end ()) + session = it->second; + if (!session) + { + session = std::make_shared (this); + session->SetRemoteStaticKey (staticKey); + } + session->SetDestination (destination->GetIdentHash ()); // TODO: remove + return session; + } + else + { + ElGamalAESSessionPtr session; + { + std::unique_lock l(m_SessionsMutex); + auto it = m_Sessions.find (destination->GetIdentHash ()); + if (it != m_Sessions.end ()) + session = it->second; + } + if (!session) + { + session = std::make_shared (this, destination, + attachLeaseSet ? m_NumTags : 4, attachLeaseSet); // specified num tags for connections and 4 for LS requests + std::unique_lock l(m_SessionsMutex); + m_Sessions[destination->GetIdentHash ()] = session; + } + return session; + } } void GarlicDestination::CleanupExpiredTags () @@ -708,6 +740,25 @@ namespace garlic ++it; } } + // ECIESx25519 + for (auto it = m_ECIESx25519Tags.begin (); it != m_ECIESx25519Tags.end ();) + { + if (ts > it->second.creationTime + INCOMING_TAGS_EXPIRATION_TIMEOUT) + it = m_ECIESx25519Tags.erase (it); + else + ++it; + } + + for (auto it = m_ECIESx25519Sessions.begin (); it != m_ECIESx25519Sessions.end ();) + { + if (it->second->IsExpired (ts)) + { + it->second->SetOwner (nullptr); + it = m_ECIESx25519Sessions.erase (it); + } + else + ++it; + } } void GarlicDestination::RemoveDeliveryStatusSession (uint32_t msgID) @@ -716,7 +767,7 @@ namespace garlic m_DeliveryStatusSessions.erase (msgID); } - void GarlicDestination::DeliveryStatusSent (GarlicRoutingSessionPtr session, uint32_t msgID) + void GarlicDestination::DeliveryStatusSent (ElGamalAESSessionPtr session, uint32_t msgID) { std::unique_lock l(m_DeliveryStatusSessionsMutex); m_DeliveryStatusSessions[msgID] = session; @@ -724,7 +775,7 @@ namespace garlic void GarlicDestination::HandleDeliveryStatusMessage (uint32_t msgID) { - GarlicRoutingSessionPtr session; + ElGamalAESSessionPtr session; { std::unique_lock l(m_DeliveryStatusSessionsMutex); auto it = m_DeliveryStatusSessions.find (msgID); @@ -832,9 +883,22 @@ namespace garlic void GarlicDestination::HandleECIESx25519 (const uint8_t * buf, size_t len) { - ECIESX25519AEADRatchetSession session; - session.NewIncomingSession (*this, buf, len, std::bind (&GarlicDestination::HandleECIESx25519GarlicClove, - this, std::placeholders::_1, std::placeholders::_2)); + uint64_t tag; + memcpy (&tag, buf, 8); + ECIESX25519AEADRatchetSessionPtr session; + int index = 0; + auto it = m_ECIESx25519Tags.find (tag); + if (it != m_ECIESx25519Tags.end ()) + { + session = it->second.session; + index = it->second.index; + m_ECIESx25519Tags.erase (tag); + } + else + session = std::make_shared (this); // incoming + + if (!session->HandleNextMessage (buf, len, index)) + LogPrint (eLogError, "Garlic: can't handle ECIES-X25519-AEAD-Ratchet message"); } void GarlicDestination::HandleECIESx25519GarlicClove (const uint8_t * buf, size_t len) @@ -892,5 +956,28 @@ namespace garlic LogPrint (eLogWarning, "Garlic: unexpected delivery type ", (int)deliveryType); } } + + void GarlicDestination::AddECIESx25519SessionTag (int index, uint64_t tag, ECIESX25519AEADRatchetSessionPtr session) + { + m_ECIESx25519Tags.emplace (tag, ECIESX25519AEADRatchetIndexSession{index, session, i2p::util::GetSecondsSinceEpoch ()}); + } + + void GarlicDestination::AddECIESx25519Session (const uint8_t * staticKey, ECIESX25519AEADRatchetSessionPtr session) + { + i2p::data::Tag<32> staticKeyTag (staticKey); + auto it = m_ECIESx25519Sessions.find (staticKeyTag); + if (it != m_ECIESx25519Sessions.end ()) + { + if (it->second->CanBeRestarted (i2p::util::GetSecondsSinceEpoch ())) + m_ECIESx25519Sessions.erase (it); + else + { + LogPrint (eLogInfo, "Garlic: ECIESx25519 session with static key ", staticKeyTag.ToBase64 (), " already exists"); + return; + } + } + m_ECIESx25519Sessions.emplace (staticKeyTag, session); + } + } } diff --git a/libi2pd/Garlic.h b/libi2pd/Garlic.h index 30e6ff4b..9c256b48 100644 --- a/libi2pd/Garlic.h +++ b/libi2pd/Garlic.h @@ -2,7 +2,7 @@ #define GARLIC_H__ #include -#include +#include #include #include #include @@ -85,8 +85,10 @@ namespace garlic }; class GarlicDestination; - class GarlicRoutingSession: public std::enable_shared_from_this + class GarlicRoutingSession { + protected: + enum LeaseSetUpdateStatus { eLeaseSetUpToDate = 0, @@ -95,26 +97,13 @@ namespace garlic eLeaseSetDoNotSend }; - struct UnconfirmedTags - { - UnconfirmedTags (int n): numTags (n), tagsCreationTime (0) { sessionTags = new SessionTag[numTags]; }; - ~UnconfirmedTags () { delete[] sessionTags; }; - uint32_t msgID; - int numTags; - SessionTag * sessionTags; - uint32_t tagsCreationTime; - }; - public: - GarlicRoutingSession (GarlicDestination * owner, std::shared_ptr destination, - int numTags, bool attachLeaseSet); - GarlicRoutingSession (const uint8_t * sessionKey, const SessionTag& sessionTag); // one time encryption - ~GarlicRoutingSession (); - std::shared_ptr WrapSingleMessage (std::shared_ptr msg); - void MessageConfirmed (uint32_t msgID); - bool CleanupExpiredTags (); // returns true if something left - bool CleanupUnconfirmedTags (); // returns true if something has been deleted + GarlicRoutingSession (GarlicDestination * owner, bool attachLeaseSet); + GarlicRoutingSession (); + virtual ~GarlicRoutingSession (); + virtual std::shared_ptr WrapSingleMessage (std::shared_ptr msg) = 0; + virtual bool CleanupUnconfirmedTags () { return false; }; // for I2CP, override in ElGamalAESSession void SetLeaseSetUpdated () { @@ -127,11 +116,61 @@ namespace garlic std::shared_ptr GetSharedRoutingPath (); void SetSharedRoutingPath (std::shared_ptr path); - const GarlicDestination * GetOwner () const { return m_Owner; } + GarlicDestination * GetOwner () const { return m_Owner; } void SetOwner (GarlicDestination * owner) { m_Owner = owner; } + protected: + + LeaseSetUpdateStatus GetLeaseSetUpdateStatus () const { return m_LeaseSetUpdateStatus; } + void SetLeaseSetUpdateStatus (LeaseSetUpdateStatus status) { m_LeaseSetUpdateStatus = status; } + uint32_t GetLeaseSetUpdateMsgID () const { return m_LeaseSetUpdateMsgID; } + void SetLeaseSetUpdateMsgID (uint32_t msgID) { m_LeaseSetUpdateMsgID = msgID; } + void SetLeaseSetSubmissionTime (uint64_t ts) { m_LeaseSetSubmissionTime = ts; } + private: + GarlicDestination * m_Owner; + + LeaseSetUpdateStatus m_LeaseSetUpdateStatus; + uint32_t m_LeaseSetUpdateMsgID; + uint64_t m_LeaseSetSubmissionTime; // in milliseconds + + std::shared_ptr m_SharedRoutingPath; + + public: + // for HTTP only + virtual size_t GetNumOutgoingTags () const { return 0; }; + }; + //using GarlicRoutingSessionPtr = std::shared_ptr; + typedef std::shared_ptr GarlicRoutingSessionPtr; // TODO: replace to using after switch to 4.8 + + class ElGamalAESSession: public GarlicRoutingSession, public std::enable_shared_from_this + { + struct UnconfirmedTags + { + UnconfirmedTags (int n): numTags (n), tagsCreationTime (0) { sessionTags = new SessionTag[numTags]; }; + ~UnconfirmedTags () { delete[] sessionTags; }; + uint32_t msgID; + int numTags; + SessionTag * sessionTags; + uint32_t tagsCreationTime; + }; + + public: + + ElGamalAESSession (GarlicDestination * owner, std::shared_ptr destination, + int numTags, bool attachLeaseSet); + ElGamalAESSession (const uint8_t * sessionKey, const SessionTag& sessionTag); // one time encryption + ~ElGamalAESSession () {}; + + std::shared_ptr WrapSingleMessage (std::shared_ptr msg); + + void MessageConfirmed (uint32_t msgID); + bool CleanupExpiredTags (); // returns true if something left + bool CleanupUnconfirmedTags (); // returns true if something has been deleted + + private: + size_t CreateAESBlock (uint8_t * buf, std::shared_ptr msg); size_t CreateGarlicPayload (uint8_t * payload, std::shared_ptr msg, UnconfirmedTags * newTags); size_t CreateGarlicClove (uint8_t * buf, std::shared_ptr msg, bool isDestination); @@ -139,31 +178,32 @@ namespace garlic void TagsConfirmed (uint32_t msgID); UnconfirmedTags * GenerateSessionTags (); + + private: + + std::shared_ptr m_Destination; - private: - - GarlicDestination * m_Owner; - std::shared_ptr m_Destination; - - i2p::crypto::AESKey m_SessionKey; + i2p::crypto::AESKey m_SessionKey; std::list m_SessionTags; int m_NumTags; std::map > m_UnconfirmedTagsMsgs; // msgID->tags - LeaseSetUpdateStatus m_LeaseSetUpdateStatus; - uint32_t m_LeaseSetUpdateMsgID; - uint64_t m_LeaseSetSubmissionTime; // in milliseconds + i2p::crypto::CBCEncryption m_Encryption; - i2p::crypto::CBCEncryption m_Encryption; - - std::shared_ptr m_SharedRoutingPath; - - public: + public: // for HTTP only - size_t GetNumOutgoingTags () const { return m_SessionTags.size (); }; + size_t GetNumOutgoingTags () const { return m_SessionTags.size (); }; + }; + typedef std::shared_ptr ElGamalAESSessionPtr; + + class ECIESX25519AEADRatchetSession; + typedef std::shared_ptr ECIESX25519AEADRatchetSessionPtr; + struct ECIESX25519AEADRatchetIndexSession + { + int index; + ECIESX25519AEADRatchetSessionPtr session; + uint64_t creationTime; // seconds since epoch }; - //using GarlicRoutingSessionPtr = std::shared_ptr; - typedef std::shared_ptr GarlicRoutingSessionPtr; // TODO: replace to using after switch to 4.8 class GarlicDestination: public i2p::data::LocalDestination { @@ -174,6 +214,7 @@ namespace garlic void CleanUp (); void SetNumTags (int numTags) { m_NumTags = numTags; }; + int GetNumTags () const { return m_NumTags; }; std::shared_ptr GetRoutingSession (std::shared_ptr destination, bool attachLeaseSet); void CleanupExpiredTags (); void RemoveDeliveryStatusSession (uint32_t msgID); @@ -182,7 +223,10 @@ namespace garlic void AddSessionKey (const uint8_t * key, const uint8_t * tag); // one tag virtual bool SubmitSessionKey (const uint8_t * key, const uint8_t * tag); // from different thread - void DeliveryStatusSent (GarlicRoutingSessionPtr session, uint32_t msgID); + void DeliveryStatusSent (ElGamalAESSessionPtr session, uint32_t msgID); + void AddECIESx25519SessionTag (int index, uint64_t tag, ECIESX25519AEADRatchetSessionPtr session); + void AddECIESx25519Session (const uint8_t * staticKey, ECIESX25519AEADRatchetSessionPtr session); + void HandleECIESx25519GarlicClove (const uint8_t * buf, size_t len); virtual void ProcessGarlicMessage (std::shared_ptr msg); virtual void ProcessDeliveryStatusMessage (std::shared_ptr msg); @@ -209,7 +253,6 @@ namespace garlic // ECIES-X25519-AEAD-Ratchet void HandleECIESx25519 (const uint8_t * buf, size_t len); - void HandleECIESx25519GarlicClove (const uint8_t * buf, size_t len); private: @@ -217,12 +260,14 @@ namespace garlic // outgoing sessions int m_NumTags; std::mutex m_SessionsMutex; - std::map m_Sessions; + std::unordered_map m_Sessions; + std::unordered_map, ECIESX25519AEADRatchetSessionPtr> m_ECIESx25519Sessions; // static key -> session // incoming - std::map > m_Tags; + std::unordered_map, std::hash > > m_Tags; + std::unordered_map m_ECIESx25519Tags; // session tag -> session // DeliveryStatus std::mutex m_DeliveryStatusSessionsMutex; - std::map m_DeliveryStatusSessions; // msgID -> session + std::unordered_map m_DeliveryStatusSessions; // msgID -> session public: diff --git a/libi2pd/LeaseSet.cpp b/libi2pd/LeaseSet.cpp index e6373dd9..8d1b9524 100644 --- a/libi2pd/LeaseSet.cpp +++ b/libi2pd/LeaseSet.cpp @@ -251,18 +251,19 @@ namespace data memcpy (m_Buffer, buf, len); } - LeaseSet2::LeaseSet2 (uint8_t storeType, const uint8_t * buf, size_t len, bool storeLeases): - LeaseSet (storeLeases), m_StoreType (storeType) + LeaseSet2::LeaseSet2 (uint8_t storeType, const uint8_t * buf, size_t len, bool storeLeases, CryptoKeyType preferredCrypto): + LeaseSet (storeLeases), m_StoreType (storeType), m_EncryptionType (preferredCrypto) { - SetBuffer (buf, len); + SetBuffer (buf, len); if (storeType == NETDB_STORE_TYPE_ENCRYPTED_LEASESET2) ReadFromBufferEncrypted (buf, len, nullptr, nullptr); else ReadFromBuffer (buf, len); } - LeaseSet2::LeaseSet2 (const uint8_t * buf, size_t len, std::shared_ptr key, const uint8_t * secret): - LeaseSet (true), m_StoreType (NETDB_STORE_TYPE_ENCRYPTED_LEASESET2) + LeaseSet2::LeaseSet2 (const uint8_t * buf, size_t len, std::shared_ptr key, + const uint8_t * secret, CryptoKeyType preferredCrypto): + LeaseSet (true), m_StoreType (NETDB_STORE_TYPE_ENCRYPTED_LEASESET2), m_EncryptionType (preferredCrypto) { ReadFromBufferEncrypted (buf, len, key, secret); } @@ -355,6 +356,8 @@ namespace data offset += propertiesLen; // skip for now. TODO: implement properties if (offset + 1 >= len) return 0; // key sections + CryptoKeyType preferredKeyType = m_EncryptionType; + bool preferredKeyFound = false; int numKeySections = buf[offset]; offset++; for (int i = 0; i < numKeySections; i++) { @@ -362,15 +365,15 @@ namespace data if (offset + 2 >= len) return 0; uint16_t encryptionKeyLen = bufbe16toh (buf + offset); offset += 2; if (offset + encryptionKeyLen >= len) return 0; - if (IsStoreLeases ()) // create encryptor with leases only + if (IsStoreLeases () && !preferredKeyFound) // create encryptor with leases only { - // we pick first valid key, higher key type has higher priority 4-1-0 - // if two keys with of the same type, pick first + // we pick first valid key if preferred not found auto encryptor = i2p::data::IdentityEx::CreateEncryptor (keyType, buf + offset); - if (encryptor && (!m_Encryptor || keyType > m_EncryptionType)) + if (encryptor && (!m_Encryptor || keyType == preferredKeyType)) { m_Encryptor = encryptor; // TODO: atomic m_EncryptionType = keyType; + if (keyType == preferredKeyType) preferredKeyFound = true; } } offset += encryptionKeyLen; diff --git a/libi2pd/LeaseSet.h b/libi2pd/LeaseSet.h index 84d87e17..a51570f7 100644 --- a/libi2pd/LeaseSet.h +++ b/libi2pd/LeaseSet.h @@ -136,8 +136,8 @@ namespace data { public: - LeaseSet2 (uint8_t storeType, const uint8_t * buf, size_t len, bool storeLeases = true); - LeaseSet2 (const uint8_t * buf, size_t len, std::shared_ptr key, const uint8_t * secret = nullptr); // store type 5, called from local netdb only + LeaseSet2 (uint8_t storeType, const uint8_t * buf, size_t len, bool storeLeases = true, CryptoKeyType preferredCrypto = CRYPTO_KEY_TYPE_ELGAMAL); + LeaseSet2 (const uint8_t * buf, size_t len, std::shared_ptr key, const uint8_t * secret = nullptr, CryptoKeyType preferredCrypto = CRYPTO_KEY_TYPE_ELGAMAL); // store type 5, called from local netdb only uint8_t GetStoreType () const { return m_StoreType; }; uint32_t GetPublishedTimestamp () const { return m_PublishedTimestamp; }; bool IsPublic () const { return m_IsPublic; }; @@ -168,7 +168,7 @@ namespace data uint32_t m_PublishedTimestamp = 0; bool m_IsPublic = true, m_IsPublishedEncrypted = false; std::shared_ptr m_TransientVerifier; - CryptoKeyType m_EncryptionType = CRYPTO_KEY_TYPE_ELGAMAL; + CryptoKeyType m_EncryptionType; std::shared_ptr m_Encryptor; // for standardLS2 }; diff --git a/libi2pd/NTCP2.cpp b/libi2pd/NTCP2.cpp index e2c4ae43..034f7e21 100644 --- a/libi2pd/NTCP2.cpp +++ b/libi2pd/NTCP2.cpp @@ -1,12 +1,10 @@ /* -* Copyright (c) 2013-2018, The PurpleI2P Project +* Copyright (c) 2013-2020, 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 * -* Kovri go write your own code -* */ #include @@ -41,15 +39,8 @@ namespace transport void NTCP2Establisher::MixKey (const uint8_t * inputKeyMaterial) { - // temp_key = HMAC-SHA256(ck, input_key_material) - uint8_t tempKey[32]; unsigned int len; - HMAC(EVP_sha256(), m_CK, 32, inputKeyMaterial, 32, tempKey, &len); - // ck = HMAC-SHA256(temp_key, byte(0x01)) - static uint8_t one[1] = { 1 }; - HMAC(EVP_sha256(), tempKey, 32, one, 1, m_CK, &len); - // derived = HMAC-SHA256(temp_key, ck || byte(0x02)) - m_CK[32] = 2; - HMAC(EVP_sha256(), tempKey, 32, m_CK, 33, m_K, &len); + i2p::crypto::HKDF (m_CK, inputKeyMaterial, 32, "", m_CK); + // ck is m_CK[0:31], k is m_CK[32:63] } void NTCP2Establisher::MixHash (const uint8_t * buf, size_t len) @@ -181,7 +172,7 @@ namespace transport // sign and encrypt options, use m_H as AD uint8_t nonce[12]; memset (nonce, 0, 12); // set nonce to zero - i2p::crypto::AEADChaCha20Poly1305 (options, 16, m_H, 32, m_K, nonce, m_SessionRequestBuffer + 32, 32, true); // encrypt + i2p::crypto::AEADChaCha20Poly1305 (options, 16, GetH (), 32, GetK (), nonce, m_SessionRequestBuffer + 32, 32, true); // encrypt } void NTCP2Establisher::CreateSessionCreatedMessage () @@ -204,7 +195,7 @@ namespace transport // sign and encrypt options, use m_H as AD uint8_t nonce[12]; memset (nonce, 0, 12); // set nonce to zero - i2p::crypto::AEADChaCha20Poly1305 (options, 16, m_H, 32, m_K, nonce, m_SessionCreatedBuffer + 32, 32, true); // encrypt + i2p::crypto::AEADChaCha20Poly1305 (options, 16, GetH (), 32, GetK (), nonce, m_SessionCreatedBuffer + 32, 32, true); // encrypt } @@ -217,7 +208,7 @@ namespace transport MixHash (m_SessionCreatedBuffer + 64, paddingLength); // part1 48 bytes - i2p::crypto::AEADChaCha20Poly1305 (i2p::context.GetNTCP2StaticPublicKey (), 32, m_H, 32, m_K, nonce, m_SessionConfirmedBuffer, 48, true); // encrypt + i2p::crypto::AEADChaCha20Poly1305 (i2p::context.GetNTCP2StaticPublicKey (), 32, GetH (), 32, GetK (), nonce, m_SessionConfirmedBuffer, 48, true); // encrypt } void NTCP2Establisher::CreateSessionConfirmedMessagePart2 (const uint8_t * nonce) @@ -228,7 +219,7 @@ namespace transport // encrypt m3p2, it must be filled in SessionRequest KDF3Alice (); uint8_t * m3p2 = m_SessionConfirmedBuffer + 48; - i2p::crypto::AEADChaCha20Poly1305 (m3p2, m3p2Len - 16, m_H, 32, m_K, nonce, m3p2, m3p2Len, true); // encrypt + i2p::crypto::AEADChaCha20Poly1305 (m3p2, m3p2Len - 16, GetH (), 32, GetK (), nonce, m3p2, m3p2Len, true); // encrypt // update h again MixHash (m3p2, m3p2Len); //h = SHA256(h || ciphertext) } @@ -246,7 +237,7 @@ namespace transport // verify MAC and decrypt options block (32 bytes), use m_H as AD uint8_t nonce[12], options[16]; memset (nonce, 0, 12); // set nonce to zero - if (i2p::crypto::AEADChaCha20Poly1305 (m_SessionRequestBuffer + 32, 16, m_H, 32, m_K, nonce, options, 16, false)) // decrypt + if (i2p::crypto::AEADChaCha20Poly1305 (m_SessionRequestBuffer + 32, 16, GetH (), 32, GetK (), nonce, options, 16, false)) // decrypt { // options if (options[0] && options[0] != i2p::context.GetNetID ()) @@ -301,7 +292,7 @@ namespace transport uint8_t payload[16]; uint8_t nonce[12]; memset (nonce, 0, 12); // set nonce to zero - if (i2p::crypto::AEADChaCha20Poly1305 (m_SessionCreatedBuffer + 32, 16, m_H, 32, m_K, nonce, payload, 16, false)) // decrypt + if (i2p::crypto::AEADChaCha20Poly1305 (m_SessionCreatedBuffer + 32, 16, GetH (), 32, GetK (), nonce, payload, 16, false)) // decrypt { // options paddingLen = bufbe16toh(payload + 2); @@ -330,7 +321,7 @@ namespace transport if (paddingLength > 0) MixHash (m_SessionCreatedBuffer + 64, paddingLength); - if (!i2p::crypto::AEADChaCha20Poly1305 (m_SessionConfirmedBuffer, 32, m_H, 32, m_K, nonce, m_RemoteStaticKey, 32, false)) // decrypt S + if (!i2p::crypto::AEADChaCha20Poly1305 (m_SessionConfirmedBuffer, 32, GetH (), 32, GetK (), nonce, m_RemoteStaticKey, 32, false)) // decrypt S { LogPrint (eLogWarning, "NTCP2: SessionConfirmed Part1 AEAD verification failed "); return false; @@ -344,7 +335,7 @@ namespace transport MixHash (m_SessionConfirmedBuffer, 48); KDF3Bob (); - if (i2p::crypto::AEADChaCha20Poly1305 (m_SessionConfirmedBuffer + 48, m3p2Len - 16, m_H, 32, m_K, nonce, m3p2Buf, m3p2Len - 16, false)) // decrypt + if (i2p::crypto::AEADChaCha20Poly1305 (m_SessionConfirmedBuffer + 48, m3p2Len - 16, GetH (), 32, GetK (), nonce, m3p2Buf, m3p2Len - 16, false)) // decrypt { // caclulate new h again for KDF data memcpy (m_SessionConfirmedBuffer + 16, m_H, 32); // h || ciphertext @@ -1150,8 +1141,8 @@ namespace transport } NTCP2Server::NTCP2Server (): - m_IsRunning (false), m_Thread (nullptr), m_Work (m_Service), - m_TerminationTimer (m_Service) + RunnableServiceWithWork ("NTCP2"), + m_TerminationTimer (GetService ()) { } @@ -1162,10 +1153,9 @@ namespace transport void NTCP2Server::Start () { - if (!m_IsRunning) + if (!IsRunning ()) { - m_IsRunning = true; - m_Thread = new std::thread (std::bind (&NTCP2Server::Run, this)); + StartIOService (); auto& addresses = context.GetRouterInfo ().GetAddresses (); for (const auto& address: addresses) { @@ -1176,7 +1166,7 @@ namespace transport { try { - m_NTCP2Acceptor.reset (new boost::asio::ip::tcp::acceptor (m_Service, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), address->port))); + m_NTCP2Acceptor.reset (new boost::asio::ip::tcp::acceptor (GetService (), boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), address->port))); } catch ( std::exception & ex ) { @@ -1190,7 +1180,7 @@ namespace transport } else if (address->host.is_v6() && context.SupportsV6 ()) { - m_NTCP2V6Acceptor.reset (new boost::asio::ip::tcp::acceptor (m_Service)); + m_NTCP2V6Acceptor.reset (new boost::asio::ip::tcp::acceptor (GetService ())); try { m_NTCP2V6Acceptor->open (boost::asio::ip::tcp::v6()); @@ -1225,33 +1215,9 @@ namespace transport } m_NTCP2Sessions.clear (); - if (m_IsRunning) - { - m_IsRunning = false; + if (IsRunning ()) m_TerminationTimer.cancel (); - m_Service.stop (); - if (m_Thread) - { - m_Thread->join (); - delete m_Thread; - m_Thread = nullptr; - } - } - } - - void NTCP2Server::Run () - { - while (m_IsRunning) - { - try - { - m_Service.run (); - } - catch (std::exception& ex) - { - LogPrint (eLogError, "NTCP2: runtime exception: ", ex.what ()); - } - } + StopIOService (); } bool NTCP2Server::AddNTCP2Session (std::shared_ptr session, bool incoming) @@ -1289,11 +1255,11 @@ namespace transport void NTCP2Server::Connect(const boost::asio::ip::address & address, uint16_t port, std::shared_ptr conn) { LogPrint (eLogDebug, "NTCP2: Connecting to ", address ,":", port); - m_Service.post([this, address, port, conn]() + GetService ().post([this, address, port, conn]() { if (this->AddNTCP2Session (conn)) { - auto timer = std::make_shared(m_Service); + auto timer = std::make_shared(GetService ()); auto timeout = NTCP2_CONNECT_TIMEOUT * 5; conn->SetTerminationTimeout(timeout * 2); timer->expires_from_now (boost::posix_time::seconds(timeout)); diff --git a/libi2pd/NTCP2.h b/libi2pd/NTCP2.h index c66ff697..7e3c53e8 100644 --- a/libi2pd/NTCP2.h +++ b/libi2pd/NTCP2.h @@ -1,12 +1,10 @@ /* -* Copyright (c) 2013-2018, The PurpleI2P Project +* Copyright (c) 2013-2020, 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 * -* Kovri go write your own code -* */ #ifndef NTCP2_H__ #define NTCP2_H__ @@ -85,7 +83,7 @@ namespace transport const uint8_t * GetRemotePub () const { return m_RemoteEphemeralPublicKey; }; // Y for Alice and X for Bob uint8_t * GetRemotePub () { return m_RemoteEphemeralPublicKey; }; // to set - const uint8_t * GetK () const { return m_K; }; + const uint8_t * GetK () const { return m_CK + 32; }; const uint8_t * GetCK () const { return m_CK; }; const uint8_t * GetH () const { return m_H; }; @@ -114,7 +112,7 @@ namespace transport i2p::crypto::X25519Keys m_EphemeralKeys; uint8_t m_RemoteEphemeralPublicKey[32]; // x25519 - uint8_t m_RemoteStaticKey[32], m_IV[16], m_H[32] /*h*/, m_CK[33] /*ck*/, m_K[32] /*k*/; + uint8_t m_RemoteStaticKey[32], m_IV[16], m_H[32] /*h*/, m_CK[64] /* [ck, k]*/; i2p::data::IdentHash m_RemoteIdentHash; uint16_t m3p2Len; @@ -218,7 +216,7 @@ namespace transport std::list > m_SendQueue; }; - class NTCP2Server + class NTCP2Server: private i2p::util::RunnableServiceWithWork { public: @@ -227,18 +225,16 @@ namespace transport void Start (); void Stop (); + boost::asio::io_service& GetService () { return GetIOService (); }; bool AddNTCP2Session (std::shared_ptr session, bool incoming = false); void RemoveNTCP2Session (std::shared_ptr session); std::shared_ptr FindNTCP2Session (const i2p::data::IdentHash& ident); - - boost::asio::io_service& GetService () { return m_Service; }; void Connect(const boost::asio::ip::address & address, uint16_t port, std::shared_ptr conn); private: - void Run (); void HandleAccept (std::shared_ptr conn, const boost::system::error_code& error); void HandleAcceptV6 (std::shared_ptr conn, const boost::system::error_code& error); @@ -250,10 +246,6 @@ namespace transport private: - bool m_IsRunning; - std::thread * m_Thread; - boost::asio::io_service m_Service; - boost::asio::io_service::work m_Work; boost::asio::deadline_timer m_TerminationTimer; std::unique_ptr m_NTCP2Acceptor, m_NTCP2V6Acceptor; std::map > m_NTCP2Sessions; diff --git a/libi2pd/NetDb.cpp b/libi2pd/NetDb.cpp index 4461c094..6d69f131 100644 --- a/libi2pd/NetDb.cpp +++ b/libi2pd/NetDb.cpp @@ -950,7 +950,7 @@ namespace data if (numTags) { const i2p::garlic::SessionTag sessionTag(excluded + 33); // take first tag - i2p::garlic::GarlicRoutingSession garlic (sessionKey, sessionTag); + i2p::garlic::ElGamalAESSession garlic (sessionKey, sessionTag); replyMsg = garlic.WrapSingleMessage (replyMsg); if(replyMsg == nullptr) LogPrint(eLogError, "NetDb: failed to wrap message"); } diff --git a/libi2pd/Streaming.cpp b/libi2pd/Streaming.cpp index 4f943912..d666d786 100644 --- a/libi2pd/Streaming.cpp +++ b/libi2pd/Streaming.cpp @@ -86,13 +86,13 @@ namespace stream LogPrint (eLogDebug, "Streaming: Stream deleted"); } - void Stream::Terminate () + void Stream::Terminate () // shoudl be called from StreamingDestination::Stop only { m_AckSendTimer.cancel (); m_ReceiveTimer.cancel (); m_ResendTimer.cancel (); //CleanUp (); /* Need to recheck - broke working on windows */ - m_LocalDestination.DeleteStream (shared_from_this ()); + //m_LocalDestination.DeleteStream (shared_from_this ()); } void Stream::CleanUp () @@ -989,6 +989,8 @@ namespace stream m_PendingIncomingStreams.clear (); { std::unique_lock l(m_StreamsMutex); + for (auto it: m_Streams) + it.second->Terminate (); m_Streams.clear (); m_IncomingStreams.clear (); } diff --git a/libi2pd/Streaming.h b/libi2pd/Streaming.h index 49962507..3424bb2d 100644 --- a/libi2pd/Streaming.h +++ b/libi2pd/Streaming.h @@ -180,7 +180,6 @@ namespace stream int GetWindowSize () const { return m_WindowSize; }; int GetRTT () const { return m_RTT; }; - /** don't call me */ void Terminate (); private: diff --git a/libi2pd/Tag.h b/libi2pd/Tag.h index 8af82241..010b160f 100644 --- a/libi2pd/Tag.h +++ b/libi2pd/Tag.h @@ -93,4 +93,16 @@ private: } // data } // i2p +namespace std +{ + // hash for std::unordered_map + template struct hash > + { + size_t operator()(const i2p::data::Tag& s) const + { + return s.GetLL ()[0]; + } + }; +} + #endif /* TAG_H__ */ diff --git a/libi2pd/Tunnel.cpp b/libi2pd/Tunnel.cpp index 66620717..00b0a12c 100644 --- a/libi2pd/Tunnel.cpp +++ b/libi2pd/Tunnel.cpp @@ -1,5 +1,6 @@ #include #include "I2PEndian.h" +#include #include #include #include @@ -45,7 +46,7 @@ namespace tunnel // shuffle records std::vector recordIndicies; for (int i = 0; i < numRecords; i++) recordIndicies.push_back(i); - std::random_shuffle (recordIndicies.begin(), recordIndicies.end()); + std::shuffle (recordIndicies.begin(), recordIndicies.end(), std::mt19937(std::random_device()())); // create real records uint8_t * records = msg->GetPayload () + 1; diff --git a/libi2pd/TunnelPool.cpp b/libi2pd/TunnelPool.cpp index 4f740a09..7a1325fe 100644 --- a/libi2pd/TunnelPool.cpp +++ b/libi2pd/TunnelPool.cpp @@ -1,4 +1,5 @@ #include +#include #include "I2PEndian.h" #include "Crypto.h" #include "Tunnel.h" @@ -441,7 +442,7 @@ namespace tunnel int size = m_ExplicitPeers->size (); std::vector peerIndicies; for (int i = 0; i < size; i++) peerIndicies.push_back(i); - std::random_shuffle (peerIndicies.begin(), peerIndicies.end()); + std::shuffle (peerIndicies.begin(), peerIndicies.end(), std::mt19937(std::random_device()())); int numHops = isInbound ? m_NumInboundHops : m_NumOutboundHops; for (int i = 0; i < numHops; i++) diff --git a/libi2pd/api.cpp b/libi2pd/api.cpp index ca72ae49..0a76bda7 100644 --- a/libi2pd/api.cpp +++ b/libi2pd/api.cpp @@ -77,7 +77,7 @@ namespace api std::shared_ptr CreateLocalDestination (const i2p::data::PrivateKeys& keys, bool isPublic, const std::map * params) { - auto localDestination = std::make_shared (keys, isPublic, params); + auto localDestination = std::make_shared (keys, isPublic, params); localDestination->Start (); return localDestination; } @@ -86,7 +86,7 @@ namespace api const std::map * params) { i2p::data::PrivateKeys keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType); - auto localDestination = std::make_shared (keys, isPublic, params); + auto localDestination = std::make_shared (keys, isPublic, params); localDestination->Start (); return localDestination; } diff --git a/libi2pd/util.cpp b/libi2pd/util.cpp index 448d1307..c1b741fc 100644 --- a/libi2pd/util.cpp +++ b/libi2pd/util.cpp @@ -57,6 +57,45 @@ namespace i2p { namespace util { + + void RunnableService::StartIOService () + { + if (!m_IsRunning) + { + m_IsRunning = true; + m_Thread.reset (new std::thread (std::bind (& RunnableService::Run, this))); + } + } + + void RunnableService::StopIOService () + { + if (m_IsRunning) + { + m_IsRunning = false; + m_Service.stop (); + if (m_Thread) + { + m_Thread->join (); + m_Thread = nullptr; + } + } + } + + void RunnableService::Run () + { + while (m_IsRunning) + { + try + { + m_Service.run (); + } + catch (std::exception& ex) + { + LogPrint (eLogError, m_Name, ": runtime exception: ", ex.what ()); + } + } + } + namespace net { #ifdef WIN32 @@ -206,10 +245,10 @@ namespace net #else std::string localAddressUniversal = localAddress.to_string(); #endif - - typedef int (* IPN)(int af, const char *src, void *dst); + + typedef int (* IPN)(int af, const char *src, void *dst); IPN inetpton = (IPN)GetProcAddress (GetModuleHandle ("ws2_32.dll"), "InetPton"); - if (!inetpton) inetpton = inet_pton_xp; // use own implementation if not found + if (!inetpton) inetpton = inet_pton_xp; // use own implementation if not found if(localAddress.is_v4()) { @@ -285,7 +324,7 @@ namespace net int GetMTU(const boost::asio::ip::address& localAddress) { - const int fallback = 576; // fallback MTU + int fallback = localAddress.is_v6 () ? 1280 : 620; // fallback MTU #ifdef WIN32 return GetMTUWindows(localAddress, fallback); diff --git a/libi2pd/util.h b/libi2pd/util.h index eccdadf7..2202ccd9 100644 --- a/libi2pd/util.h +++ b/libi2pd/util.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -122,6 +123,43 @@ namespace util std::mutex m_Mutex; }; + class RunnableService + { + protected: + + RunnableService (const std::string& name): m_Name (name), m_IsRunning (false) {} + virtual ~RunnableService () {} + + boost::asio::io_service& GetIOService () { return m_Service; } + bool IsRunning () const { return m_IsRunning; }; + + void StartIOService (); + void StopIOService (); + + private: + + void Run (); + + private: + + std::string m_Name; + volatile bool m_IsRunning; + std::unique_ptr m_Thread; + boost::asio::io_service m_Service; + }; + + class RunnableServiceWithWork: public RunnableService + { + protected: + + RunnableServiceWithWork (const std::string& name): + RunnableService (name), m_Work (GetIOService ()) {} + + private: + + boost::asio::io_service::work m_Work; + }; + namespace net { int GetMTU (const boost::asio::ip::address& localAddress); diff --git a/libi2pd/version.h b/libi2pd/version.h index 2e876fdb..36fbe2ca 100644 --- a/libi2pd/version.h +++ b/libi2pd/version.h @@ -7,7 +7,7 @@ #define MAKE_VERSION(a,b,c) STRINGIZE(a) "." STRINGIZE(b) "." STRINGIZE(c) #define I2PD_VERSION_MAJOR 2 -#define I2PD_VERSION_MINOR 29 +#define I2PD_VERSION_MINOR 30 #define I2PD_VERSION_MICRO 0 #define I2PD_VERSION_PATCH 0 #define I2PD_VERSION MAKE_VERSION(I2PD_VERSION_MAJOR, I2PD_VERSION_MINOR, I2PD_VERSION_MICRO) @@ -21,7 +21,7 @@ #define I2P_VERSION_MAJOR 0 #define I2P_VERSION_MINOR 9 -#define I2P_VERSION_MICRO 44 +#define I2P_VERSION_MICRO 45 #define I2P_VERSION_PATCH 0 #define I2P_VERSION MAKE_VERSION(I2P_VERSION_MAJOR, I2P_VERSION_MINOR, I2P_VERSION_MICRO) diff --git a/libi2pd_client/BOB.cpp b/libi2pd_client/BOB.cpp index aba090dc..c7bbdb46 100644 --- a/libi2pd_client/BOB.cpp +++ b/libi2pd_client/BOB.cpp @@ -743,8 +743,8 @@ namespace client } BOBCommandChannel::BOBCommandChannel (const std::string& address, int port): - m_IsRunning (false), m_Thread (nullptr), - m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(address), port)) + RunnableService ("BOB"), + m_Acceptor (GetIOService (), boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(address), port)) { // command -> handler m_CommandHandlers[BOB_COMMAND_ZAP] = &BOBCommandSession::ZapCommandHandler; @@ -794,7 +794,8 @@ namespace client BOBCommandChannel::~BOBCommandChannel () { - Stop (); + if (IsRunning ()) + Stop (); for (const auto& it: m_Destinations) delete it.second; } @@ -802,38 +803,15 @@ namespace client void BOBCommandChannel::Start () { Accept (); - m_IsRunning = true; - m_Thread = new std::thread (std::bind (&BOBCommandChannel::Run, this)); + StartIOService (); } void BOBCommandChannel::Stop () { - m_IsRunning = false; for (auto& it: m_Destinations) it.second->Stop (); m_Acceptor.cancel (); - m_Service.stop (); - if (m_Thread) - { - m_Thread->join (); - delete m_Thread; - m_Thread = nullptr; - } - } - - void BOBCommandChannel::Run () - { - while (m_IsRunning) - { - try - { - m_Service.run (); - } - catch (std::exception& ex) - { - LogPrint (eLogError, "BOB: runtime exception: ", ex.what ()); - } - } + StopIOService (); } void BOBCommandChannel::AddDestination (const std::string& name, BOBDestination * dest) diff --git a/libi2pd_client/BOB.h b/libi2pd_client/BOB.h index 8f1af185..15a0afaf 100644 --- a/libi2pd_client/BOB.h +++ b/libi2pd_client/BOB.h @@ -7,6 +7,7 @@ #include #include #include +#include "util.h" #include "I2PTunnel.h" #include "I2PService.h" #include "Identity.h" @@ -231,7 +232,7 @@ namespace client }; typedef void (BOBCommandSession::*BOBCommandHandler)(const char * operand, size_t len); - class BOBCommandChannel + class BOBCommandChannel: private i2p::util::RunnableService { public: @@ -241,22 +242,18 @@ namespace client void Start (); void Stop (); - boost::asio::io_service& GetService () { return m_Service; }; + boost::asio::io_service& GetService () { return GetIOService (); }; void AddDestination (const std::string& name, BOBDestination * dest); void DeleteDestination (const std::string& name); BOBDestination * FindDestination (const std::string& name); private: - void Run (); void Accept (); void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr session); private: - bool m_IsRunning; - std::thread * m_Thread; - boost::asio::io_service m_Service; boost::asio::ip::tcp::acceptor m_Acceptor; std::map m_Destinations; std::map m_CommandHandlers; diff --git a/libi2pd_client/ClientContext.cpp b/libi2pd_client/ClientContext.cpp index d14491b2..d72f40a8 100644 --- a/libi2pd_client/ClientContext.cpp +++ b/libi2pd_client/ClientContext.cpp @@ -53,14 +53,19 @@ namespace client // SAM bool sam; i2p::config::GetOption("sam.enabled", sam); - if (sam) { + if (sam) + { std::string samAddr; i2p::config::GetOption("sam.address", samAddr); uint16_t samPort; i2p::config::GetOption("sam.port", samPort); + bool singleThread; i2p::config::GetOption("sam.singlethread", singleThread); LogPrint(eLogInfo, "Clients: starting SAM bridge at ", samAddr, ":", samPort); - try { - m_SamBridge = new SAMBridge (samAddr, samPort); - m_SamBridge->Start (); - } catch (std::exception& e) { + try + { + m_SamBridge = new SAMBridge (samAddr, samPort, singleThread); + m_SamBridge->Start (); + } + catch (std::exception& e) + { LogPrint(eLogError, "Clients: Exception in SAM bridge: ", e.what()); } } @@ -305,21 +310,34 @@ namespace client const std::map * params) { i2p::data::PrivateKeys keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType, cryptoType); - auto localDestination = std::make_shared (keys, isPublic, params); - std::unique_lock l(m_DestinationsMutex); - m_Destinations[localDestination->GetIdentHash ()] = localDestination; - localDestination->Start (); + auto localDestination = std::make_shared (keys, isPublic, params); + AddLocalDestination (localDestination); + return localDestination; + } + + std::shared_ptr ClientContext::CreateNewLocalDestination ( + boost::asio::io_service& service, bool isPublic, + i2p::data::SigningKeyType sigType, i2p::data::CryptoKeyType cryptoType, + const std::map * params) + { + i2p::data::PrivateKeys keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType, cryptoType); + auto localDestination = std::make_shared (service, keys, isPublic, params); + AddLocalDestination (localDestination); return localDestination; } std::shared_ptr ClientContext::CreateNewMatchedTunnelDestination(const i2p::data::PrivateKeys &keys, const std::string & name, const std::map * params) { - MatchedTunnelDestination * cl = new MatchedTunnelDestination(keys, name, params); - auto localDestination = std::shared_ptr(cl); + auto localDestination = std::make_shared(keys, name, params); + AddLocalDestination (localDestination); + return localDestination; + } + + void ClientContext::AddLocalDestination (std::shared_ptr localDestination) + { std::unique_lock l(m_DestinationsMutex); m_Destinations[localDestination->GetIdentHash ()] = localDestination; localDestination->Start (); - return localDestination; } void ClientContext::DeleteLocalDestination (std::shared_ptr destination) @@ -344,14 +362,26 @@ namespace client if (it != m_Destinations.end ()) { LogPrint (eLogWarning, "Clients: Local destination ", m_AddressBook.ToAddress(keys.GetPublic ()->GetIdentHash ()), " exists"); - if (!it->second->IsRunning ()) - it->second->Start (); + it->second->Start (); // make sure to start return it->second; } - auto localDestination = std::make_shared (keys, isPublic, params); - std::unique_lock l(m_DestinationsMutex); - m_Destinations[keys.GetPublic ()->GetIdentHash ()] = localDestination; - localDestination->Start (); + auto localDestination = std::make_shared (keys, isPublic, params); + AddLocalDestination (localDestination); + return localDestination; + } + + std::shared_ptr ClientContext::CreateNewLocalDestination (boost::asio::io_service& service, + const i2p::data::PrivateKeys& keys, bool isPublic, const std::map * params) + { + auto it = m_Destinations.find (keys.GetPublic ()->GetIdentHash ()); + if (it != m_Destinations.end ()) + { + LogPrint (eLogWarning, "Clients: Local destination ", m_AddressBook.ToAddress(keys.GetPublic ()->GetIdentHash ()), " exists"); + it->second->Start (); // make sure to start + return it->second; + } + auto localDestination = std::make_shared (service, keys, isPublic, params); + AddLocalDestination (localDestination); return localDestination; } diff --git a/libi2pd_client/ClientContext.h b/libi2pd_client/ClientContext.h index 94aa8594..a239035f 100644 --- a/libi2pd_client/ClientContext.h +++ b/libi2pd_client/ClientContext.h @@ -68,8 +68,15 @@ namespace client i2p::data::SigningKeyType sigType = i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519, i2p::data::CryptoKeyType cryptoType = i2p::data::CRYPTO_KEY_TYPE_ELGAMAL, const std::map * params = nullptr); // used by SAM only + std::shared_ptr CreateNewLocalDestination (boost::asio::io_service& service, + bool isPublic = false, i2p::data::SigningKeyType sigType = i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519, + i2p::data::CryptoKeyType cryptoType = i2p::data::CRYPTO_KEY_TYPE_ELGAMAL, + const std::map * params = nullptr); // same as previous but on external io_service std::shared_ptr CreateNewLocalDestination (const i2p::data::PrivateKeys& keys, bool isPublic = true, const std::map * params = nullptr); + std::shared_ptr CreateNewLocalDestination (boost::asio::io_service& service, + const i2p::data::PrivateKeys& keys, bool isPublic = true, + const std::map * params = nullptr); // same as previous but on external io_service std::shared_ptr CreateNewMatchedTunnelDestination(const i2p::data::PrivateKeys &keys, const std::string & name, const std::map * params = nullptr); void DeleteLocalDestination (std::shared_ptr destination); std::shared_ptr FindLocalDestination (const i2p::data::IdentHash& destination) const; @@ -107,6 +114,7 @@ namespace client void VisitTunnels (Visitor v); // Visitor: (I2PService *) -> bool, true means retain void CreateNewSharedLocalDestination (); + void AddLocalDestination (std::shared_ptr localDestination); private: diff --git a/libi2pd_client/I2CP.cpp b/libi2pd_client/I2CP.cpp index 78190c89..a14588a8 100644 --- a/libi2pd_client/I2CP.cpp +++ b/libi2pd_client/I2CP.cpp @@ -24,10 +24,35 @@ namespace client { I2CPDestination::I2CPDestination (std::shared_ptr owner, std::shared_ptr identity, bool isPublic, const std::map& params): - LeaseSetDestination (isPublic, ¶ms), m_Owner (owner), m_Identity (identity) + RunnableService ("I2CP"), LeaseSetDestination (GetIOService (), isPublic, ¶ms), + m_Owner (owner), m_Identity (identity), m_EncryptionKeyType (m_Identity->GetCryptoKeyType ()) { } + I2CPDestination::~I2CPDestination () + { + if (IsRunning ()) + Stop (); + } + + void I2CPDestination::Start () + { + if (!IsRunning ()) + { + LeaseSetDestination::Start (); + StartIOService (); + } + } + + void I2CPDestination::Stop () + { + if (IsRunning ()) + { + LeaseSetDestination::Stop (); + StopIOService (); + } + } + void I2CPDestination::SetEncryptionPrivateKey (const uint8_t * key) { memcpy (m_EncryptionPrivateKey, key, 256); @@ -556,7 +581,10 @@ namespace client } // TODO: support multiple keys if (currentKey) + { m_Destination->SetEncryptionPrivateKey (currentKey); + m_Destination->SetEncryptionType (currentKeyType); + } m_Destination->LeaseSet2Created (storeType, ls.GetBuffer (), ls.GetBufferLen ()); } diff --git a/libi2pd_client/I2CP.h b/libi2pd_client/I2CP.h index 0d235161..68b4415a 100644 --- a/libi2pd_client/I2CP.h +++ b/libi2pd_client/I2CP.h @@ -15,6 +15,7 @@ #include #include #include +#include "util.h" #include "Destination.h" namespace i2p @@ -61,19 +62,25 @@ namespace client const char I2CP_PARAM_MESSAGE_RELIABILITY[] = "i2cp.messageReliability"; class I2CPSession; - class I2CPDestination: public LeaseSetDestination + class I2CPDestination: private i2p::util::RunnableService, public LeaseSetDestination { public: I2CPDestination (std::shared_ptr owner, std::shared_ptr identity, bool isPublic, const std::map& params); - + ~I2CPDestination (); + + void Start (); + void Stop (); + void SetEncryptionPrivateKey (const uint8_t * key); + void SetEncryptionType (i2p::data::CryptoKeyType keyType) { m_EncryptionKeyType = keyType; }; void LeaseSetCreated (const uint8_t * buf, size_t len); // called from I2CPSession 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 // implements LocalDestination bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx) const; + i2p::data::CryptoKeyType GetEncryptionType () const { return m_EncryptionKeyType; }; std::shared_ptr GetIdentity () const { return m_Identity; }; protected: @@ -93,6 +100,7 @@ namespace client std::shared_ptr m_Owner; std::shared_ptr m_Identity; uint8_t m_EncryptionPrivateKey[256]; + i2p::data::CryptoKeyType m_EncryptionKeyType; std::shared_ptr m_Decryptor; uint64_t m_LeaseSetExpirationTime; }; diff --git a/libi2pd_client/MatchedDestination.cpp b/libi2pd_client/MatchedDestination.cpp index fa08ec51..3d75a3ca 100644 --- a/libi2pd_client/MatchedDestination.cpp +++ b/libi2pd_client/MatchedDestination.cpp @@ -8,7 +8,7 @@ namespace i2p namespace client { MatchedTunnelDestination::MatchedTunnelDestination(const i2p::data::PrivateKeys & keys, const std::string & remoteName, const std::map * params) - : ClientDestination(keys, false, params), + : RunnableClientDestination(keys, false, params), m_RemoteName(remoteName) {} @@ -45,29 +45,19 @@ namespace client } - bool MatchedTunnelDestination::Start() + void MatchedTunnelDestination::Start() { - if(ClientDestination::Start()) - { - m_ResolveTimer = std::make_shared(GetService()); - GetTunnelPool()->SetCustomPeerSelector(this); - ResolveCurrentLeaseSet(); - return true; - } - else - return false; + ClientDestination::Start(); + m_ResolveTimer = std::make_shared(GetService()); + GetTunnelPool()->SetCustomPeerSelector(this); + ResolveCurrentLeaseSet(); } - bool MatchedTunnelDestination::Stop() + void MatchedTunnelDestination::Stop() { - if(ClientDestination::Stop()) - { - if(m_ResolveTimer) - m_ResolveTimer->cancel(); - return true; - } - else - return false; + ClientDestination::Stop(); + if(m_ResolveTimer) + m_ResolveTimer->cancel(); } diff --git a/libi2pd_client/MatchedDestination.h b/libi2pd_client/MatchedDestination.h index bbeeb503..67b874e6 100644 --- a/libi2pd_client/MatchedDestination.h +++ b/libi2pd_client/MatchedDestination.h @@ -10,12 +10,12 @@ namespace client /** client tunnel that uses same OBEP as IBGW of each remote lease for a remote destination */ - class MatchedTunnelDestination : public ClientDestination, public i2p::tunnel::ITunnelPeerSelector + class MatchedTunnelDestination : public RunnableClientDestination, public i2p::tunnel::ITunnelPeerSelector { public: MatchedTunnelDestination(const i2p::data::PrivateKeys& keys, const std::string & remoteName, const std::map * params = nullptr); - bool Start(); - bool Stop(); + void Start(); + void Stop(); bool SelectPeers(i2p::tunnel::Path & peers, int hops, bool inbound); bool OnBuildResult(const i2p::tunnel::Path & peers, bool inbound, i2p::tunnel::TunnelBuildResult result); diff --git a/libi2pd_client/SAM.cpp b/libi2pd_client/SAM.cpp index 766a1940..d0942221 100644 --- a/libi2pd_client/SAM.cpp +++ b/libi2pd_client/SAM.cpp @@ -1000,10 +1000,10 @@ namespace client } } - SAMBridge::SAMBridge (const std::string& address, int port): - m_IsRunning (false), m_Thread (nullptr), - m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(address), port)), - m_DatagramEndpoint (boost::asio::ip::address::from_string(address), port-1), m_DatagramSocket (m_Service, m_DatagramEndpoint), + SAMBridge::SAMBridge (const std::string& address, int port, bool singleThread): + RunnableService ("SAM"), m_IsSingleThread (singleThread), + m_Acceptor (GetIOService (), boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(address), port)), + m_DatagramEndpoint (boost::asio::ip::address::from_string(address), port-1), m_DatagramSocket (GetIOService (), m_DatagramEndpoint), m_SignatureTypes { {"DSA_SHA1", i2p::data::SIGNING_KEY_TYPE_DSA_SHA1}, @@ -1020,7 +1020,7 @@ namespace client SAMBridge::~SAMBridge () { - if (m_IsRunning) + if (IsRunning ()) Stop (); } @@ -1028,14 +1028,11 @@ namespace client { Accept (); ReceiveDatagram (); - m_IsRunning = true; - m_Thread = new std::thread (std::bind (&SAMBridge::Run, this)); + StartIOService (); } void SAMBridge::Stop () { - m_IsRunning = false; - try { m_Acceptor.cancel (); @@ -1045,31 +1042,13 @@ namespace client LogPrint (eLogError, "SAM: runtime exception: ", ex.what ()); } - for (auto& it: m_Sessions) - it.second->CloseStreams (); - m_Sessions.clear (); - m_Service.stop (); - if (m_Thread) { - m_Thread->join (); - delete m_Thread; - m_Thread = nullptr; - } - } - - void SAMBridge::Run () - { - while (m_IsRunning) - { - try - { - m_Service.run (); - } - catch (std::exception& ex) - { - LogPrint (eLogError, "SAM: runtime exception: ", ex.what ()); - } + std::unique_lock l(m_SessionsMutex); + for (auto& it: m_Sessions) + it.second->CloseStreams (); + m_Sessions.clear (); } + StopIOService (); } void SAMBridge::Accept () @@ -1118,7 +1097,9 @@ namespace client { i2p::data::PrivateKeys keys; if (!keys.FromBase64 (destination)) return nullptr; - localDestination = i2p::client::context.CreateNewLocalDestination (keys, true, params); + localDestination = m_IsSingleThread ? + i2p::client::context.CreateNewLocalDestination (GetIOService (), keys, true, params) : + i2p::client::context.CreateNewLocalDestination (keys, true, params); } else // transient { @@ -1146,7 +1127,9 @@ namespace client } } } - localDestination = i2p::client::context.CreateNewLocalDestination (true, signatureType, cryptoType, params); + localDestination = m_IsSingleThread ? + i2p::client::context.CreateNewLocalDestination (GetIOService (), true, signatureType, cryptoType, params) : + i2p::client::context.CreateNewLocalDestination (true, signatureType, cryptoType, params); } if (localDestination) { @@ -1178,6 +1161,15 @@ namespace client session->localDestination->Release (); session->localDestination->StopAcceptingStreams (); session->CloseStreams (); + if (m_IsSingleThread) + { + auto timer = std::make_shared(GetService ()); + timer->expires_from_now (boost::posix_time::seconds(5)); // postpone destination clean for 5 seconds + timer->async_wait ([timer, session](const boost::system::error_code& ecode) + { + // session's destructor is called here + }); + } } } diff --git a/libi2pd_client/SAM.h b/libi2pd_client/SAM.h index aebdccb6..5a447c06 100644 --- a/libi2pd_client/SAM.h +++ b/libi2pd_client/SAM.h @@ -9,6 +9,7 @@ #include #include #include +#include "util.h" #include "Identity.h" #include "LeaseSet.h" #include "Streaming.h" @@ -174,17 +175,17 @@ namespace client void CloseStreams (); }; - class SAMBridge + class SAMBridge: private i2p::util::RunnableService { public: - SAMBridge (const std::string& address, int port); + SAMBridge (const std::string& address, int port, bool singleThread); ~SAMBridge (); void Start (); void Stop (); - boost::asio::io_service& GetService () { return m_Service; }; + boost::asio::io_service& GetService () { return GetIOService (); }; std::shared_ptr CreateSession (const std::string& id, SAMSessionType type, const std::string& destination, // empty string means transient const std::map * params); void CloseSession (const std::string& id); @@ -201,8 +202,6 @@ namespace client private: - void Run (); - void Accept (); void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr socket); @@ -211,9 +210,7 @@ namespace client private: - bool m_IsRunning; - std::thread * m_Thread; - boost::asio::io_service m_Service; + bool m_IsSingleThread; boost::asio::ip::tcp::acceptor m_Acceptor; boost::asio::ip::udp::endpoint m_DatagramEndpoint, m_SenderEndpoint; boost::asio::ip::udp::socket m_DatagramSocket; diff --git a/qt/i2pd_qt/data/website.i2pd.i2pd.appdata.xml b/qt/i2pd_qt/data/website.i2pd.i2pd.appdata.xml index e6cc81d3..00b6974a 100644 --- a/qt/i2pd_qt/data/website.i2pd.i2pd.appdata.xml +++ b/qt/i2pd_qt/data/website.i2pd.i2pd.appdata.xml @@ -35,6 +35,7 @@ + diff --git a/qt/i2pd_qt/i2pd_qt.pro b/qt/i2pd_qt/i2pd_qt.pro index 15565c5c..caa0bd50 100644 --- a/qt/i2pd_qt/i2pd_qt.pro +++ b/qt/i2pd_qt/i2pd_qt.pro @@ -62,6 +62,8 @@ SOURCES += DaemonQT.cpp mainwindow.cpp \ ../../libi2pd/TunnelGateway.cpp \ ../../libi2pd/TunnelPool.cpp \ ../../libi2pd/util.cpp \ + ../../libi2pd/Elligator.cpp \ + ../../libi2pd/ECIESX25519AEADRatchetSession.cpp \ ../../libi2pd_client/AddressBook.cpp \ ../../libi2pd_client/BOB.cpp \ ../../libi2pd_client/ClientContext.cpp \ @@ -147,6 +149,8 @@ HEADERS += DaemonQT.h mainwindow.h \ ../../libi2pd/TunnelPool.h \ ../../libi2pd/util.h \ ../../libi2pd/version.h \ + ../../libi2pd/Elligator.h \ + ../../libi2pd/ECIESX25519AEADRatchetSession.h \ ../../libi2pd_client/AddressBook.h \ ../../libi2pd_client/BOB.h \ ../../libi2pd_client/ClientContext.h \ @@ -206,6 +210,9 @@ macx { LIBS += $$BOOSTROOT/lib/libboost_filesystem.a LIBS += $$BOOSTROOT/lib/libboost_program_options.a LIBS += $$UPNPROOT/lib/libminiupnpc.a + LIBS += -Wl,-dead_strip + LIBS += -Wl,-dead_strip_dylibs + LIBS += -Wl,-bind_at_load } linux:!android { diff --git a/tests/test-elligator.cpp b/tests/test-elligator.cpp index 94798e80..48c9e31a 100644 --- a/tests/test-elligator.cpp +++ b/tests/test-elligator.cpp @@ -58,14 +58,20 @@ const uint8_t key3[32] = 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55 }; +const uint8_t failed_key[32] = +{ + 0xe6, 0xf6, 0x6f, 0xdf, 0x6e, 0x23, 0x0c, 0x60, 0x3c, 0x5e, 0x6e, 0x59, 0xa2, 0x54, 0xea, 0x14, + 0x76, 0xa1, 0x3e, 0xb9, 0x51, 0x1b, 0x95, 0x49, 0x84, 0x67, 0x81, 0xe1, 0x2e, 0x52, 0x23, 0x0a +}; + int main () { uint8_t buf[32]; i2p::crypto::Elligator2 el; // encoding tests - el.Encode (key, buf); + el.Encode (key, buf, false, false); assert(memcmp (buf, encoded_key, 32) == 0); - el.Encode (key, buf, true); // with highY + el.Encode (key, buf, true, false); // with highY assert(memcmp (buf, encoded_key_high_y, 32) == 0); // decoding tests el.Decode (encoded1, buf); @@ -74,4 +80,6 @@ int main () assert(memcmp (buf, key2, 32) == 0); el.Decode (encoded3, buf); assert(memcmp (buf, key3, 32) == 0); + // encoding fails + assert (!el.Encode (failed_key, buf)); } diff --git a/tests/test-x25519.cpp b/tests/test-x25519.cpp index 9f249dbd..2ab8ad6a 100644 --- a/tests/test-x25519.cpp +++ b/tests/test-x25519.cpp @@ -27,10 +27,13 @@ uint8_t p[32] = int main () { +#if !OPENSSL_X25519 +// we test it for openssl < 1.1.0 uint8_t buf[32]; BN_CTX * ctx = BN_CTX_new (); i2p::crypto::GetEd25519 ()->ScalarMul (u, k, buf, ctx); BN_CTX_free (ctx); assert(memcmp (buf, p, 32) == 0); +#endif }