diff --git a/ChangeLog b/ChangeLog index 08f78224..a8a382ca 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,21 @@ # for this file format description, # see https://github.com/olivierlacan/keep-a-changelog +## [2.26.0] - 2019-06-07 +### Added +- HTTP method "PROFIND" +- Detection of external ipv6 address through the SSU +- NTCP2 publishing depends on network status +### Changed +- ntcp is disabled by default, ntcp2 is published by default +- Response to BOB's "list" command +- ipv6 address is not longer NTCP's local endpoint's address +- Reseeds list +### Fixed +- Check and handle incorrect BOB input +- Ignore introducers for NTCP or NTCP2 addresses +- RouterInfo check from NTCP2 + ## [2.25.0] - 2019-05-09 ### Added - Create, publish and handle encrypted LeaseSet2 diff --git a/Win32/installer.iss b/Win32/installer.iss index b215b91c..28df9ea8 100644 --- a/Win32/installer.iss +++ b/Win32/installer.iss @@ -1,5 +1,5 @@ #define I2Pd_AppName "i2pd" -#define I2Pd_ver "2.25.0" +#define I2Pd_ver "2.26.0" #define I2Pd_Publisher "PurpleI2P" [Setup] diff --git a/android/build.gradle b/android/build.gradle index e68c160f..327d3ec1 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -29,8 +29,8 @@ android { applicationId "org.purplei2p.i2pd" targetSdkVersion 28 minSdkVersion 14 - versionCode 2250 - versionName "2.25.0" + versionCode 2260 + versionName "2.26.0" ndk { abiFilters 'armeabi-v7a' abiFilters 'x86' diff --git a/appveyor.yml b/appveyor.yml index a7cf22c3..ddcd651a 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: 2.25.0.{build} +version: 2.26.0.{build} pull_requests: do_not_increment_build_number: true branches: @@ -17,7 +17,7 @@ environment: - MSYSTEM: MINGW32 install: -- c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Rns gcc-fortran gcc" +- c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Rns gcc-fortran gcc mingw-w64-{i686,x86_64}-gcc-ada mingw-w64-{i686,x86_64}-gcc-objc" - c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Syuu --force" - c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Syuu --force" diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index fa92da05..949f6a46 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -81,6 +81,7 @@ set (LIBI2PD_SRC "${LIBI2PD_SRC_DIR}/Poly1305.cpp" "${LIBI2PD_SRC_DIR}/Ed25519.cpp" "${LIBI2PD_SRC_DIR}/NTCP2.cpp" + "${LIBI2PD_SRC_DIR}/Blinding.cpp" ) if (WITH_WEBSOCKETS) @@ -469,6 +470,7 @@ if (WITH_BINARY) if (WITH_STATIC) set(DL_LIB ${CMAKE_DL_LIBS}) endif() + target_link_libraries(libi2pd ${Boost_LIBRARIES} ${ZLIB_LIBRARY}) target_link_libraries( "${PROJECT_NAME}" libi2pd libi2pdclient ${DL_LIB} ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARY} ${CMAKE_THREAD_LIBS_INIT} ${MINGW_EXTRA} ${DL_LIB} ${CMAKE_REQUIRED_LIBRARIES}) install(TARGETS "${PROJECT_NAME}" RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT Runtime) diff --git a/contrib/certificates/reseed/atomike_at_mail.i2p.crt b/contrib/certificates/reseed/atomike_at_mail.i2p.crt deleted file mode 100644 index 1e724f00..00000000 --- a/contrib/certificates/reseed/atomike_at_mail.i2p.crt +++ /dev/null @@ -1,34 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIF5TCCA82gAwIBAgIRANFIiHpTaRY2Z30TQOiuqFcwDQYJKoZIhvcNAQELBQAw -cDELMAkGA1UEBhMCWFgxCzAJBgNVBAcTAlhYMQswCQYDVQQJEwJYWDEeMBwGA1UE -ChMVSTJQIEFub255bW91cyBOZXR3b3JrMQwwCgYDVQQLEwNJMlAxGTAXBgNVBAMM -EGF0b21pa2VAbWFpbC5pMnAwHhcNMTYwODAyMTQyNDEyWhcNMjYwODAyMTQyNDEy -WjBwMQswCQYDVQQGEwJYWDELMAkGA1UEBxMCWFgxCzAJBgNVBAkTAlhYMR4wHAYD -VQQKExVJMlAgQW5vbnltb3VzIE5ldHdvcmsxDDAKBgNVBAsTA0kyUDEZMBcGA1UE -AwwQYXRvbWlrZUBtYWlsLmkycDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC -ggIBAMLRmxclaAvm405JLHNNiniUi0aZaBoLJ+afwn2LGfTDUhTD5Y8lW6V9o90n -eTNOCaiid7bWpVBkA1M4gZ9TdUnP0POa99jXZbj4PHFRl1l8k4Ap12PUO3hgwtH7 -7j7j+UPaIuE2y+U7hJbmyQ0v7r8yjGWSTtSqs+exNhyr4Mh7DvacZySZ+oqQdXYA -vnfDpBX1dKlN1Nb4XloG0uE1OK1YfJoC+p+v8qXjKagIdZgThdmsWcQ82EGI+Q9u -VfrE4m3CNwJy0X86wMNYqHej88wBHnJMmTm+cZtFLVmZsRqnuLAQL1wrfCbGSltR -zhVQHTysLwMz9+llTXtzMf+R2kcEAYWiPc5IRVU+LvkN/610r5fuHW+OcQ9ZgRVn -PMqlv5PDG2ZxdIOAQQsOd7fH0r5q3MhqlVstVE45Rl33uA+M7wjJK2cvnOoSioxp -szn2GIZliXQXo4dJczgfN2U4PLBGRBGmrB1R2S1YsG6CrSJuMCX14VKJP69Nfm8a -EDA5GKNke+ZpXCszPLaNMB70LVFQc9FmMhsOgLIIoJBgd61uMgokMJJMLaWN0RaK -w1ZduxYGUmg2T2pi/clIkVzZmlcHKViUn0sMcKD+ibEPOvQIB/3HPEEt6iIkanc/ -da5IFzikkaykt/Tu6o8rreeEu65HkIxFaCHegSXLHSyxj00BAgMBAAGjejB4MA4G -A1UdDwEB/wQEAwIChDAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwDwYD -VR0TAQH/BAUwAwEB/zAZBgNVHQ4EEgQQYXRvbWlrZUBtYWlsLmkycDAbBgNVHSME -FDASgBBhdG9taWtlQG1haWwuaTJwMA0GCSqGSIb3DQEBCwUAA4ICAQAA0MdWfN/N -1q5CdJqDyw4JQwzdYkA27Wr02qIcmwnqjcCEDPl4uDTyqN9gbEpJ48AcsdXRa6GE -lLh/qJ67I6YDe63LuhndzRULNgxGHVMGS8kBJIssQehb2rOFnbUTp0gMR+0QpXXe -omase4kL90c9uuYX1vXaO/ADssY2/QX49prwJO+UY/jGhcX4YheFI/teA85u6Qko -ero437Shqhl0kbdK+eBkOFf9a7mGxpMT73KE1jFS6433W4fFOkybQ1dcS0qStaUM -3qKC0EQCbAl1seAp3AGuG46swHZB0rZ1WCKVAr5yqCWSWMYO+fL6FosNg9z/VDVh -g6FFfoGrv19yaVFa9AvQsk1ATZ+bwtHProNx2Xet9pnAI30dT16+C5wCctoR6RVf -iOHl6CGqadjOycbMDVvOfJhypNDgWW3gBaCfXiAocJTLpR7hKNZ2bnvcP2xyXH1j -Qz/kiMJoZ3+TV1yC/x/maAHsUIQHqqd6ZRj7x5MgJq0UBdITo2ZQVfXYI0ZGIeNm -fMu+P5448+NdpASa9QoqS8kPFeUaHJMzMFHBKhrr8lTJeZ82hKBXt5jD3Tbef5Ck -n5auKu2D0IjvrzsdIpNMQAhuBPT06TW/LzN/MvardZcaLcBmcutefw6Z7RsedHvj -cGpnw4a2u9sHZIUNHzoGq32+7UWXsBI5Ow== ------END CERTIFICATE----- diff --git a/contrib/rpm/i2pd-git.spec b/contrib/rpm/i2pd-git.spec index ffc87519..098084d7 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.25.0 +Version: 2.26.0 Release: git%{git_hash}%{?dist} Summary: I2P router written in C++ Conflicts: i2pd @@ -110,6 +110,9 @@ getent passwd i2pd >/dev/null || \ %changelog +* Fri Jun 7 2019 orignal - 2.26.0 +- update to 2.26.0 + * Thu May 9 2019 orignal - 2.25.0 - update to 2.25.0 diff --git a/contrib/rpm/i2pd.spec b/contrib/rpm/i2pd.spec index 7932629f..84fad674 100644 --- a/contrib/rpm/i2pd.spec +++ b/contrib/rpm/i2pd.spec @@ -1,5 +1,5 @@ Name: i2pd -Version: 2.25.0 +Version: 2.26.0 Release: 1%{?dist} Summary: I2P router written in C++ Conflicts: i2pd-git @@ -108,6 +108,9 @@ getent passwd i2pd >/dev/null || \ %changelog +* Fri Jun 7 2019 orignal - 2.26.0 +- update to 2.26.0 + * Thu May 9 2019 orignal - 2.25.0 - update to 2.25.0 diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp index 20c9f3aa..38b53ff3 100644 --- a/daemon/HTTPServer.cpp +++ b/daemon/HTTPServer.cpp @@ -354,6 +354,14 @@ namespace http { { s << "Base64:
\r\n
\r\n
\r\n"; + if (dest->IsEncryptedLeaseSet ()) + { + i2p::data::BlindedPublicKey blinded (dest->GetIdentity ()); + s << "
\r\n\r\n

\r\n"; + s << blinded.ToB33 () << ".b32.i2p
\r\n"; + s << "

\r\n
\r\n"; + } + if(dest->GetNumRemoteLeaseSets()) { s << "
\r\n\r\n

\r\n"; diff --git a/debian/changelog b/debian/changelog index a0ad2cd5..1bd32f03 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +i2pd (2.26.0-1) unstable; urgency=medium + + * updated to version 2.26.0 + + -- orignal Fri, 7 Jun 2019 16:00:00 +0000 + i2pd (2.25.0-1) unstable; urgency=medium * updated to version 2.25.0/0.9.40 diff --git a/libi2pd/Blinding.cpp b/libi2pd/Blinding.cpp new file mode 100644 index 00000000..14852340 --- /dev/null +++ b/libi2pd/Blinding.cpp @@ -0,0 +1,301 @@ +#include // for crc32 +#include +#include +#include +#include +#include "Base.h" +#include "Crypto.h" +#include "Log.h" +#include "Timestamp.h" +#include "I2PEndian.h" +#include "Ed25519.h" +#include "Signature.h" +#include "Blinding.h" + +namespace i2p +{ +namespace data +{ + static EC_POINT * BlindPublicKeyECDSA (const EC_GROUP * group, const EC_POINT * pub, const uint8_t * seed) + { + BN_CTX * ctx = BN_CTX_new (); + BN_CTX_start (ctx); + BIGNUM * q = BN_CTX_get (ctx); + EC_GROUP_get_order (group, q, ctx); + // calculate alpha = seed mod q + BIGNUM * alpha = BN_CTX_get (ctx); + BN_bin2bn (seed, 64, alpha); // seed is in BigEndian + BN_mod (alpha, alpha, q, ctx); // % q + // A' = BLIND_PUBKEY(A, alpha) = A + DERIVE_PUBLIC(alpha) + auto p = EC_POINT_new (group); + EC_POINT_mul (group, p, alpha, nullptr, nullptr, ctx); // B*alpha + EC_POINT_add (group, p, pub, p, ctx); // pub + B*alpha + BN_CTX_end (ctx); + BN_CTX_free (ctx); + return p; + } + + static void BlindPrivateKeyECDSA (const EC_GROUP * group, const BIGNUM * priv, const uint8_t * seed, BIGNUM * blindedPriv) + { + BN_CTX * ctx = BN_CTX_new (); + BN_CTX_start (ctx); + BIGNUM * q = BN_CTX_get (ctx); + EC_GROUP_get_order (group, q, ctx); + // calculate alpha = seed mod q + BIGNUM * alpha = BN_CTX_get (ctx); + BN_bin2bn (seed, 64, alpha); // seed is in BigEndian + BN_mod (alpha, alpha, q, ctx); // % q + BN_add (alpha, alpha, priv); // alpha = alpha + priv + // a' = BLIND_PRIVKEY(a, alpha) = (a + alpha) mod q + BN_mod (blindedPriv, alpha, q, ctx); // % q + BN_CTX_end (ctx); + BN_CTX_free (ctx); + } + + static void BlindEncodedPublicKeyECDSA (size_t publicKeyLen, const EC_GROUP * group, const uint8_t * pub, const uint8_t * seed, uint8_t * blindedPub) + { + BIGNUM * x = BN_bin2bn (pub, publicKeyLen/2, NULL); + BIGNUM * y = BN_bin2bn (pub + publicKeyLen/2, publicKeyLen/2, NULL); + EC_POINT * p = EC_POINT_new (group); + EC_POINT_set_affine_coordinates_GFp (group, p, x, y, NULL); + EC_POINT * p1 = BlindPublicKeyECDSA (group, p, seed); + EC_POINT_free (p); + EC_POINT_get_affine_coordinates_GFp (group, p1, x, y, NULL); + EC_POINT_free (p1); + i2p::crypto::bn2buf (x, blindedPub, publicKeyLen/2); + i2p::crypto::bn2buf (y, blindedPub + publicKeyLen/2, publicKeyLen/2); + BN_free (x); BN_free (y); + } + + static void BlindEncodedPrivateKeyECDSA (size_t publicKeyLen, const EC_GROUP * group, const uint8_t * priv, const uint8_t * seed, uint8_t * blindedPriv, uint8_t * blindedPub) + { + BIGNUM * a = BN_bin2bn (priv, publicKeyLen/2, NULL); + BIGNUM * a1 = BN_new (); + BlindPrivateKeyECDSA (group, a, seed, a1); + BN_free (a); + i2p::crypto::bn2buf (a1, blindedPriv, publicKeyLen/2); + auto p = EC_POINT_new (group); + BN_CTX * ctx = BN_CTX_new (); + EC_POINT_mul (group, p, a1, nullptr, nullptr, ctx); // B*a1 + BN_CTX_free (ctx); + BN_free (a1); + BIGNUM * x = BN_new(), * y = BN_new(); + EC_POINT_get_affine_coordinates_GFp (group, p, x, y, NULL); + EC_POINT_free (p); + i2p::crypto::bn2buf (x, blindedPub, publicKeyLen/2); + i2p::crypto::bn2buf (y, blindedPub + publicKeyLen/2, publicKeyLen/2); + BN_free (x); BN_free (y); + } + + template + static size_t BlindECDSA (i2p::data::SigningKeyType sigType, const uint8_t * key, const uint8_t * seed, Fn blind, Args&&...args) + // blind is BlindEncodedPublicKeyECDSA or BlindEncodedPrivateKeyECDSA + { + size_t publicKeyLength = 0; + EC_GROUP * group = nullptr; + switch (sigType) + { + case i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256: + { + publicKeyLength = i2p::crypto::ECDSAP256_KEY_LENGTH; + group = EC_GROUP_new_by_curve_name (NID_X9_62_prime256v1); + break; + } + case i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA384_P384: + { + publicKeyLength = i2p::crypto::ECDSAP384_KEY_LENGTH; + group = EC_GROUP_new_by_curve_name (NID_secp384r1); + break; + } + case i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA512_P521: + { + publicKeyLength = i2p::crypto::ECDSAP521_KEY_LENGTH; + group = EC_GROUP_new_by_curve_name (NID_secp521r1); + break; + } + default: + LogPrint (eLogError, "Blinding: signature type ", (int)sigType, " is not ECDSA"); + } + if (group) + { + blind (publicKeyLength, group, key, seed, std::forward(args)...); + EC_GROUP_free (group); + } + return publicKeyLength; + } + + + BlindedPublicKey::BlindedPublicKey (std::shared_ptr identity) + { + if (!identity) return; + auto len = identity->GetSigningPublicKeyLen (); + m_PublicKey.resize (len); + memcpy (m_PublicKey.data (), identity->GetSigningPublicKeyBuffer (), len); + m_SigType = identity->GetSigningKeyType (); + m_BlindedSigType = m_SigType; + } + + BlindedPublicKey::BlindedPublicKey (const std::string& b33) + { + uint8_t addr[40]; // TODO: define length from b33 + size_t l = i2p::data::Base32ToByteStream (b33.c_str (), b33.length (), addr, 40); + uint32_t checksum = crc32 (0, addr + 3, l - 3); + // checksum is Little Endian + addr[0] ^= checksum; addr[1] ^= (checksum >> 8); addr[2] ^= (checksum >> 16); + uint8_t flag = addr[0]; + size_t offset = 1; + if (flag & 0x01) // two bytes signatures + { + m_SigType = bufbe16toh (addr + offset); offset += 2; + m_BlindedSigType = bufbe16toh (addr + offset); offset += 2; + } + else // one byte sig + { + m_SigType = addr[offset]; offset++; + m_BlindedSigType = addr[offset]; offset++; + } + std::unique_ptr blindedVerifier (i2p::data::IdentityEx::CreateVerifier (m_SigType)); + if (blindedVerifier) + { + auto len = blindedVerifier->GetPublicKeyLen (); + if (offset + len <= l) + { + m_PublicKey.resize (len); + memcpy (m_PublicKey.data (), addr + offset, len); + } + else + LogPrint (eLogError, "Blinding: public key in b33 address is too short for signature type ", (int)m_SigType); + } + else + LogPrint (eLogError, "Blinding: unknown signature type ", (int)m_SigType, " in b33"); + } + + std::string BlindedPublicKey::ToB33 () const + { + if (m_PublicKey.size () > 32) return ""; // assume 25519 + uint8_t addr[35]; char str[60]; // TODO: define actual length + addr[0] = 0; // flags + addr[1] = m_SigType; // sig type + addr[2] = m_BlindedSigType; // blinded sig type + memcpy (addr + 3, m_PublicKey.data (), m_PublicKey.size ()); + uint32_t checksum = crc32 (0, addr + 3, m_PublicKey.size ()); + // checksum is Little Endian + addr[0] ^= checksum; addr[1] ^= (checksum >> 8); addr[2] ^= (checksum >> 16); + auto l = ByteStreamToBase32 (addr, m_PublicKey.size () + 3, str, 60); + return std::string (str, str + l); + } + + void BlindedPublicKey::GetCredential (uint8_t * credential) const + { + // A = destination's signing public key + // stA = signature type of A, 2 bytes big endian + uint16_t stA = htobe16 (GetSigType ()); + // stA1 = signature type of blinded A, 2 bytes big endian + uint16_t stA1 = htobe16 (GetBlindedSigType ()); + // credential = H("credential", A || stA || stA1) + H ("credential", { {GetPublicKey (), GetPublicKeyLen ()}, {(const uint8_t *)&stA, 2}, {(const uint8_t *)&stA1, 2} }, credential); + } + + void BlindedPublicKey::GetSubcredential (const uint8_t * blinded, size_t len, uint8_t * subcredential) const + { + uint8_t credential[32]; + GetCredential (credential); + // subcredential = H("subcredential", credential || blindedPublicKey) + H ("subcredential", { {credential, 32}, {blinded, len} }, subcredential); + } + + void BlindedPublicKey::GenerateAlpha (const char * date, uint8_t * seed) const + { + uint16_t stA = htobe16 (GetSigType ()), stA1 = htobe16 (GetBlindedSigType ()); + uint8_t salt[32]; + //seed = HKDF(H("I2PGenerateAlpha", keydata), datestring || secret, "i2pblinding1", 64) + H ("I2PGenerateAlpha", { {GetPublicKey (), GetPublicKeyLen ()}, {(const uint8_t *)&stA, 2}, {(const uint8_t *)&stA1, 2} }, salt); + i2p::crypto::HKDF (salt, (const uint8_t *)date, 8, "i2pblinding1", seed); + } + + size_t BlindedPublicKey::GetBlindedKey (const char * date, uint8_t * blindedKey) const + { + uint8_t seed[64]; + GenerateAlpha (date, seed); + + size_t publicKeyLength = 0; + switch (m_SigType) + { + case i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256: + case i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA384_P384: + case i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA512_P521: + publicKeyLength = BlindECDSA (m_SigType, GetPublicKey (), seed, BlindEncodedPublicKeyECDSA, blindedKey); + break; + case i2p::data::SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519: + case i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519: + i2p::crypto::GetEd25519 ()->BlindPublicKey (GetPublicKey (), seed, blindedKey); + publicKeyLength = i2p::crypto::EDDSA25519_PUBLIC_KEY_LENGTH; + break; + default: + LogPrint (eLogError, "Blinding: can't blind signature type ", (int)m_SigType); + } + return publicKeyLength; + } + + size_t BlindedPublicKey::BlindPrivateKey (const uint8_t * priv, const char * date, uint8_t * blindedPriv, uint8_t * blindedPub) const + { + uint8_t seed[64]; + GenerateAlpha (date, seed); + size_t publicKeyLength = 0; + switch (m_SigType) + { + case i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256: + case i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA384_P384: + case i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA512_P521: + publicKeyLength = BlindECDSA (m_SigType, priv, seed, BlindEncodedPrivateKeyECDSA, blindedPriv, blindedPub); + break; + case i2p::data::SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519: + i2p::crypto::GetEd25519 ()->BlindPrivateKey (priv, seed, blindedPriv, blindedPub); + publicKeyLength = i2p::crypto::EDDSA25519_PUBLIC_KEY_LENGTH; + break; + default: + LogPrint (eLogError, "Blinding: can't blind signature type ", (int)m_SigType); + } + return publicKeyLength; + } + + void BlindedPublicKey::H (const std::string& p, const std::vector >& bufs, uint8_t * hash) const + { + SHA256_CTX ctx; + SHA256_Init (&ctx); + SHA256_Update (&ctx, p.c_str (), p.length ()); + for (const auto& it: bufs) + SHA256_Update (&ctx, it.first, it.second); + SHA256_Final (hash, &ctx); + } + + i2p::data::IdentHash BlindedPublicKey::GetStoreHash (const char * date) const + { + i2p::data::IdentHash hash; + uint8_t blinded[128]; + size_t publicKeyLength = 0; + if (date) + publicKeyLength = GetBlindedKey (date, blinded); + else + { + char currentDate[9]; + i2p::util::GetCurrentDate (currentDate); + publicKeyLength = GetBlindedKey (currentDate, blinded); + } + if (publicKeyLength) + { + auto stA1 = htobe16 (m_BlindedSigType); + SHA256_CTX ctx; + SHA256_Init (&ctx); + SHA256_Update (&ctx, (const uint8_t *)&stA1, 2); + SHA256_Update (&ctx, blinded, publicKeyLength); + SHA256_Final ((uint8_t *)hash, &ctx); + } + else + LogPrint (eLogError, "Blinding: blinded key type ", (int)m_BlindedSigType, " is not supported"); + return hash; + } + +} +} + diff --git a/libi2pd/Blinding.h b/libi2pd/Blinding.h new file mode 100644 index 00000000..9a3fe633 --- /dev/null +++ b/libi2pd/Blinding.h @@ -0,0 +1,45 @@ +#ifndef BLINDING_H__ +#define BLINDING_H__ + +#include +#include +#include +#include "Identity.h" + +namespace i2p +{ +namespace data +{ + class BlindedPublicKey // for encrypted LS2 + { + public: + + BlindedPublicKey (std::shared_ptr identity); + BlindedPublicKey (const std::string& b33); // from b33 without .b32.i2p + std::string ToB33 () const; + + const uint8_t * GetPublicKey () const { return m_PublicKey.data (); }; + size_t GetPublicKeyLen () const { return m_PublicKey.size (); }; + SigningKeyType GetSigType () const { return m_SigType; }; + SigningKeyType GetBlindedSigType () const { return m_BlindedSigType; }; + + void GetSubcredential (const uint8_t * blinded, size_t len, uint8_t * subcredential) const; // 32 bytes + size_t GetBlindedKey (const char * date, uint8_t * blindedKey) const; // date is 8 chars "YYYYMMDD", return public key length + size_t BlindPrivateKey (const uint8_t * priv, const char * date, uint8_t * blindedPriv, uint8_t * blindedPub) const; // date is 8 chars "YYYYMMDD", return public key length + i2p::data::IdentHash GetStoreHash (const char * date = nullptr) const; // date is 8 chars "YYYYMMDD", use current if null + + private: + + void GetCredential (uint8_t * credential) const; // 32 bytes + void GenerateAlpha (const char * date, uint8_t * seed) const; // 64 bytes, date is 8 chars "YYYYMMDD" + void H (const std::string& p, const std::vector >& bufs, uint8_t * hash) const; + + private: + + std::vector m_PublicKey; + i2p::data::SigningKeyType m_SigType, m_BlindedSigType; + }; +} +} + +#endif diff --git a/libi2pd/Config.cpp b/libi2pd/Config.cpp index 52f40089..e6007d5f 100644 --- a/libi2pd/Config.cpp +++ b/libi2pd/Config.cpp @@ -58,7 +58,7 @@ namespace config { ("floodfill", bool_switch()->default_value(false), "Router will be floodfill (default: disabled)") ("bandwidth", value()->default_value(""), "Bandwidth limit: integer in KBps or letters: L (32), O (256), P (2048), X (>9000)") ("share", value()->default_value(100), "Limit of transit traffic from max bandwidth in percents. (default: 100)") - ("ntcp", value()->default_value(true), "Enable NTCP transport (default: enabled)") + ("ntcp", value()->default_value(false), "Enable NTCP transport (default: disabled)") ("ssu", value()->default_value(true), "Enable SSU transport (default: enabled)") ("ntcpproxy", value()->default_value(""), "Proxy URL for NTCP transport") #ifdef _WIN32 @@ -195,10 +195,8 @@ namespace config { "https://reseed.i2p.net.in/," "https://download.xxlspeed.com/," "https://reseed-fr.i2pd.xyz/," - "https://reseed.atomike.ninja/," "https://reseed.memcpy.io/," "https://reseed.onion.im/," - "https://itoopie.atomike.ninja/," "https://i2pseed.creativecowpat.net:8443/," "https://i2p.novg.net/" ), "Reseed URLs, separated by comma") @@ -237,7 +235,7 @@ namespace config { options_description ntcp2("NTCP2 Options"); ntcp2.add_options() ("ntcp2.enabled", value()->default_value(true), "Enable NTCP2 (default: enabled)") - ("ntcp2.published", value()->default_value(false), "Publish NTCP2 (default: disabled)") + ("ntcp2.published", value()->default_value(true), "Publish NTCP2 (default: enabled)") ("ntcp2.port", value()->default_value(0), "Port to listen for incoming NTCP2 connections (default: auto)") ("ntcp2.addressv6", value()->default_value("::"), "Address to bind NTCP2 on") ; diff --git a/libi2pd/Destination.h b/libi2pd/Destination.h index b55a7490..64dd0b58 100644 --- a/libi2pd/Destination.h +++ b/libi2pd/Destination.h @@ -181,6 +181,7 @@ namespace client // for HTTP only int GetNumRemoteLeaseSets () const { return m_RemoteLeaseSets.size (); }; const decltype(m_RemoteLeaseSets)& GetLeaseSets () const { return m_RemoteLeaseSets; }; + bool IsEncryptedLeaseSet () const { return m_LeaseSetType == i2p::data::NETDB_STORE_TYPE_ENCRYPTED_LEASESET2; }; }; class ClientDestination: public LeaseSetDestination diff --git a/libi2pd/HTTP.cpp b/libi2pd/HTTP.cpp index c1a46e73..64acb6ea 100644 --- a/libi2pd/HTTP.cpp +++ b/libi2pd/HTTP.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2017, The PurpleI2P Project +* Copyright (c) 2013-2019, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -15,282 +15,283 @@ namespace i2p { namespace http { - const std::vector HTTP_METHODS = { - "GET", "HEAD", "POST", "PUT", "PATCH", - "DELETE", "OPTIONS", "CONNECT" - }; - const std::vector HTTP_VERSIONS = { - "HTTP/1.0", "HTTP/1.1" - }; - const std::vector weekdays = { - "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" - }; - const std::vector months = { - "Jan", "Feb", "Mar", "Apr", "May", "Jun", - "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" - }; - - inline bool is_http_version(const std::string & str) { - return std::find(HTTP_VERSIONS.begin(), HTTP_VERSIONS.end(), str) != std::end(HTTP_VERSIONS); - } - - inline bool is_http_method(const std::string & str) { - return std::find(HTTP_METHODS.begin(), HTTP_METHODS.end(), str) != std::end(HTTP_METHODS); - } - - void strsplit(const std::string & line, std::vector &tokens, char delim, std::size_t limit = 0) { - std::size_t count = 0; - std::stringstream ss(line); - std::string token; - while (1) { - count++; - if (limit > 0 && count >= limit) - delim = '\n'; /* reset delimiter */ - if (!std::getline(ss, token, delim)) - break; - tokens.push_back(token); - } - } - - static std::pair parse_header_line(const std::string& line) - { - std::size_t pos = 0; - std::size_t len = 1; /*: */ - std::size_t max = line.length(); - if ((pos = line.find(':', pos)) == std::string::npos) - return std::make_pair("", ""); // no ':' found - if (pos + 1 < max) // ':' at the end of header is valid - { - while ((pos + len) < max && isspace(line.at(pos + len))) - len++; - if (len == 1) return std::make_pair("", ""); // no following space, but something else + const std::vector HTTP_METHODS = { + "GET", "HEAD", "POST", "PUT", "PATCH", + "DELETE", "OPTIONS", "CONNECT", "PROPFIND" + }; + const std::vector HTTP_VERSIONS = { + "HTTP/1.0", "HTTP/1.1" + }; + const std::vector weekdays = { + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" + }; + const std::vector months = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" + }; + + inline bool is_http_version(const std::string & str) { + return std::find(HTTP_VERSIONS.begin(), HTTP_VERSIONS.end(), str) != std::end(HTTP_VERSIONS); + } + + inline bool is_http_method(const std::string & str) { + return std::find(HTTP_METHODS.begin(), HTTP_METHODS.end(), str) != std::end(HTTP_METHODS); + } + + void strsplit(const std::string & line, std::vector &tokens, char delim, std::size_t limit = 0) { + std::size_t count = 0; + std::stringstream ss(line); + std::string token; + while (1) { + count++; + if (limit > 0 && count >= limit) + delim = '\n'; /* reset delimiter */ + if (!std::getline(ss, token, delim)) + break; + tokens.push_back(token); + } + } + + static std::pair parse_header_line(const std::string& line) + { + std::size_t pos = 0; + std::size_t len = 1; /*: */ + std::size_t max = line.length(); + if ((pos = line.find(':', pos)) == std::string::npos) + return std::make_pair("", ""); // no ':' found + if (pos + 1 < max) // ':' at the end of header is valid + { + while ((pos + len) < max && isspace(line.at(pos + len))) + len++; + if (len == 1) + return std::make_pair("", ""); // no following space, but something else + } + return std::make_pair(line.substr(0, pos), line.substr(pos + len)); + } + + void gen_rfc7231_date(std::string & out) { + std::time_t now = std::time(nullptr); + char buf[128]; + std::tm *tm = std::gmtime(&now); + snprintf(buf, sizeof(buf), "%s, %02d %s %d %02d:%02d:%02d GMT", + weekdays[tm->tm_wday], tm->tm_mday, months[tm->tm_mon], + tm->tm_year + 1900, tm->tm_hour, tm->tm_min, tm->tm_sec + ); + out = buf; + } + + bool URL::parse(const char *str, std::size_t len) { + std::string url(str, len ? len : strlen(str)); + return parse(url); + } + + bool URL::parse(const std::string& url) { + std::size_t pos_p = 0; /* < current parse position */ + std::size_t pos_c = 0; /* < work position */ + if(url.at(0) != '/' || pos_p > 0) { + std::size_t pos_s = 0; + /* schema */ + pos_c = url.find("://"); + if (pos_c != std::string::npos) { + schema = url.substr(0, pos_c); + pos_p = pos_c + 3; + } + /* user[:pass] */ + pos_s = url.find('/', pos_p); /* find first slash */ + pos_c = url.find('@', pos_p); /* find end of 'user' or 'user:pass' part */ + if (pos_c != std::string::npos && (pos_s == std::string::npos || pos_s > pos_c)) { + std::size_t delim = url.find(':', pos_p); + if (delim && delim != std::string::npos && delim < pos_c) { + user = url.substr(pos_p, delim - pos_p); + delim += 1; + pass = url.substr(delim, pos_c - delim); + } else if(delim) { + user = url.substr(pos_p, pos_c - pos_p); + } + pos_p = pos_c + 1; + } + /* hostname[:port][/path] */ + pos_c = url.find_first_of(":/", pos_p); + if (pos_c == std::string::npos) { + /* only hostname, without post and path */ + host = url.substr(pos_p, std::string::npos); + return true; + } else if (url.at(pos_c) == ':') { + host = url.substr(pos_p, pos_c - pos_p); + /* port[/path] */ + pos_p = pos_c + 1; + pos_c = url.find('/', pos_p); + std::string port_str = (pos_c == std::string::npos) + ? url.substr(pos_p, std::string::npos) + : url.substr(pos_p, pos_c - pos_p); + /* stoi throws exception on failure, we don't need it */ + for (char c : port_str) { + if (c < '0' || c > '9') + return false; + port *= 10; + port += c - '0'; + } + if (pos_c == std::string::npos) + return true; /* no path part */ + pos_p = pos_c; + } else { + /* start of path part found */ + host = url.substr(pos_p, pos_c - pos_p); + pos_p = pos_c; + } + } + + /* pos_p now at start of path part */ + pos_c = url.find_first_of("?#", pos_p); + if (pos_c == std::string::npos) { + /* only path, without fragment and query */ + path = url.substr(pos_p, std::string::npos); + return true; + } else if (url.at(pos_c) == '?') { + /* found query part */ + path = url.substr(pos_p, pos_c - pos_p); + pos_p = pos_c + 1; + pos_c = url.find('#', pos_p); + if (pos_c == std::string::npos) { + /* no fragment */ + query = url.substr(pos_p, std::string::npos); + return true; + } else { + query = url.substr(pos_p, pos_c - pos_p); + pos_p = pos_c + 1; + } + } else { + /* found fragment part */ + path = url.substr(pos_p, pos_c - pos_p); + pos_p = pos_c + 1; + } + + /* pos_p now at start of fragment part */ + frag = url.substr(pos_p, std::string::npos); + return true; + } + + bool URL::parse_query(std::map & params) { + std::vector tokens; + strsplit(query, tokens, '&'); + + params.clear(); + for (const auto& it : tokens) { + std::size_t eq = it.find ('='); + if (eq != std::string::npos) { + auto e = std::pair(it.substr(0, eq), it.substr(eq + 1)); + params.insert(e); + } else { + auto e = std::pair(it, ""); + params.insert(e); + } + } + return true; + } + + std::string URL::to_string() { + std::string out = ""; + if (schema != "") { + out = schema + "://"; + if (user != "" && pass != "") { + out += user + ":" + pass + "@"; + } else if (user != "") { + out += user + "@"; + } + if (port) { + out += host + ":" + std::to_string(port); + } else { + out += host; + } + } + out += path; + if (query != "") + out += "?" + query; + if (frag != "") + out += "#" + frag; + return out; } - return std::make_pair(line.substr(0, pos), line.substr(pos + len)); - } - - void gen_rfc7231_date(std::string & out) { - std::time_t now = std::time(nullptr); - char buf[128]; - std::tm *tm = std::gmtime(&now); - snprintf(buf, sizeof(buf), "%s, %02d %s %d %02d:%02d:%02d GMT", - weekdays[tm->tm_wday], tm->tm_mday, months[tm->tm_mon], - tm->tm_year + 1900, tm->tm_hour, tm->tm_min, tm->tm_sec - ); - out = buf; - } - - bool URL::parse(const char *str, std::size_t len) { - std::string url(str, len ? len : strlen(str)); - return parse(url); - } - - bool URL::parse(const std::string& url) { - std::size_t pos_p = 0; /* < current parse position */ - std::size_t pos_c = 0; /* < work position */ - if(url.at(0) != '/' || pos_p > 0) { - std::size_t pos_s = 0; - /* schema */ - pos_c = url.find("://"); - if (pos_c != std::string::npos) { - schema = url.substr(0, pos_c); - pos_p = pos_c + 3; - } - /* user[:pass] */ - pos_s = url.find('/', pos_p); /* find first slash */ - pos_c = url.find('@', pos_p); /* find end of 'user' or 'user:pass' part */ - if (pos_c != std::string::npos && (pos_s == std::string::npos || pos_s > pos_c)) { - std::size_t delim = url.find(':', pos_p); - if (delim && delim != std::string::npos && delim < pos_c) { - user = url.substr(pos_p, delim - pos_p); - delim += 1; - pass = url.substr(delim, pos_c - delim); - } else if(delim) { - user = url.substr(pos_p, pos_c - pos_p); - } - pos_p = pos_c + 1; - } - /* hostname[:port][/path] */ - pos_c = url.find_first_of(":/", pos_p); - if (pos_c == std::string::npos) { - /* only hostname, without post and path */ - host = url.substr(pos_p, std::string::npos); - return true; - } else if (url.at(pos_c) == ':') { - host = url.substr(pos_p, pos_c - pos_p); - /* port[/path] */ - pos_p = pos_c + 1; - pos_c = url.find('/', pos_p); - std::string port_str = (pos_c == std::string::npos) - ? url.substr(pos_p, std::string::npos) - : url.substr(pos_p, pos_c - pos_p); - /* stoi throws exception on failure, we don't need it */ - for (char c : port_str) { - if (c < '0' || c > '9') - return false; - port *= 10; - port += c - '0'; - } - if (pos_c == std::string::npos) - return true; /* no path part */ - pos_p = pos_c; - } else { - /* start of path part found */ - host = url.substr(pos_p, pos_c - pos_p); - pos_p = pos_c; - } - } - - /* pos_p now at start of path part */ - pos_c = url.find_first_of("?#", pos_p); - if (pos_c == std::string::npos) { - /* only path, without fragment and query */ - path = url.substr(pos_p, std::string::npos); - return true; - } else if (url.at(pos_c) == '?') { - /* found query part */ - path = url.substr(pos_p, pos_c - pos_p); - pos_p = pos_c + 1; - pos_c = url.find('#', pos_p); - if (pos_c == std::string::npos) { - /* no fragment */ - query = url.substr(pos_p, std::string::npos); - return true; - } else { - query = url.substr(pos_p, pos_c - pos_p); - pos_p = pos_c + 1; - } - } else { - /* found fragment part */ - path = url.substr(pos_p, pos_c - pos_p); - pos_p = pos_c + 1; - } - - /* pos_p now at start of fragment part */ - frag = url.substr(pos_p, std::string::npos); - return true; - } - - bool URL::parse_query(std::map & params) { - std::vector tokens; - strsplit(query, tokens, '&'); - - params.clear(); - for (const auto& it : tokens) { - std::size_t eq = it.find ('='); - if (eq != std::string::npos) { - auto e = std::pair(it.substr(0, eq), it.substr(eq + 1)); - params.insert(e); - } else { - auto e = std::pair(it, ""); - params.insert(e); - } - } - return true; - } - - std::string URL::to_string() { - std::string out = ""; - if (schema != "") { - out = schema + "://"; - if (user != "" && pass != "") { - out += user + ":" + pass + "@"; - } else if (user != "") { - out += user + "@"; - } - if (port) { - out += host + ":" + std::to_string(port); - } else { - out += host; - } - } - out += path; - if (query != "") - out += "?" + query; - if (frag != "") - out += "#" + frag; - return out; - } bool URL::is_i2p() const { return host.rfind(".i2p") == ( host.size() - 4 ); } - void HTTPMsg::add_header(const char *name, std::string & value, bool replace) { - add_header(name, value.c_str(), replace); - } - - void HTTPMsg::add_header(const char *name, const char *value, bool replace) { - std::size_t count = headers.count(name); - if (count && !replace) - return; - if (count) { - headers[name] = value; - return; - } - headers.insert(std::pair(name, value)); - } - - void HTTPMsg::del_header(const char *name) { - headers.erase(name); - } - - int HTTPReq::parse(const char *buf, size_t len) { - std::string str(buf, len); - return parse(str); - } - - int HTTPReq::parse(const std::string& str) { - enum { REQ_LINE, HEADER_LINE } expect = REQ_LINE; - std::size_t eoh = str.find(HTTP_EOH); /* request head size */ - std::size_t eol = 0, pos = 0; - URL url; - - if (eoh == std::string::npos) - return 0; /* str not contains complete request */ - - while ((eol = str.find(CRLF, pos)) != std::string::npos) { - if (expect == REQ_LINE) { - std::string line = str.substr(pos, eol - pos); - std::vector tokens; - strsplit(line, tokens, ' '); - if (tokens.size() != 3) - return -1; - if (!is_http_method(tokens[0])) - return -1; - if (!is_http_version(tokens[2])) - return -1; - if (!url.parse(tokens[1])) - return -1; - /* all ok */ - method = tokens[0]; - uri = tokens[1]; - version = tokens[2]; - expect = HEADER_LINE; - } - else - { - std::string line = str.substr(pos, eol - pos); - auto p = parse_header_line(line); - if (p.first.length () > 0) - headers.push_back (p); - else - return -1; - } - pos = eol + strlen(CRLF); - if (pos >= eoh) - break; - } - return eoh + strlen(HTTP_EOH); - } - - void HTTPReq::write(std::ostream & o) - { - o << method << " " << uri << " " << version << CRLF; - for (auto & h : headers) - o << h.first << ": " << h.second << CRLF; - o << CRLF; - } + void HTTPMsg::add_header(const char *name, std::string & value, bool replace) { + add_header(name, value.c_str(), replace); + } + + void HTTPMsg::add_header(const char *name, const char *value, bool replace) { + std::size_t count = headers.count(name); + if (count && !replace) + return; + if (count) { + headers[name] = value; + return; + } + headers.insert(std::pair(name, value)); + } + + void HTTPMsg::del_header(const char *name) { + headers.erase(name); + } + + int HTTPReq::parse(const char *buf, size_t len) { + std::string str(buf, len); + return parse(str); + } + + int HTTPReq::parse(const std::string& str) { + enum { REQ_LINE, HEADER_LINE } expect = REQ_LINE; + std::size_t eoh = str.find(HTTP_EOH); /* request head size */ + std::size_t eol = 0, pos = 0; + URL url; + + if (eoh == std::string::npos) + return 0; /* str not contains complete request */ + + while ((eol = str.find(CRLF, pos)) != std::string::npos) { + if (expect == REQ_LINE) { + std::string line = str.substr(pos, eol - pos); + std::vector tokens; + strsplit(line, tokens, ' '); + if (tokens.size() != 3) + return -1; + if (!is_http_method(tokens[0])) + return -1; + if (!is_http_version(tokens[2])) + return -1; + if (!url.parse(tokens[1])) + return -1; + /* all ok */ + method = tokens[0]; + uri = tokens[1]; + version = tokens[2]; + expect = HEADER_LINE; + } + else + { + std::string line = str.substr(pos, eol - pos); + auto p = parse_header_line(line); + if (p.first.length () > 0) + headers.push_back (p); + else + return -1; + } + pos = eol + strlen(CRLF); + if (pos >= eoh) + break; + } + return eoh + strlen(HTTP_EOH); + } + + void HTTPReq::write(std::ostream & o) + { + o << method << " " << uri << " " << version << CRLF; + for (auto & h : headers) + o << h.first << ": " << h.second << CRLF; + o << CRLF; + } std::string HTTPReq::to_string() { @@ -306,7 +307,7 @@ namespace http { void HTTPReq::UpdateHeader (const std::string& name, const std::string& value) { - for (auto& it : headers) + for (auto& it : headers) if (it.first == name) { it.second = value; @@ -327,177 +328,176 @@ namespace http { std::string HTTPReq::GetHeader (const std::string& name) const { - for (auto& it : headers) + for (auto& it : headers) if (it.first == name) return it.second; return ""; } - bool HTTPRes::is_chunked() const - { - auto it = headers.find("Transfer-Encoding"); - if (it == headers.end()) - return false; - if (it->second.find("chunked") == std::string::npos) - return true; - return false; - } - - bool HTTPRes::is_gzipped(bool includingI2PGzip) const - { - auto it = headers.find("Content-Encoding"); - if (it == headers.end()) - return false; /* no header */ - if (it->second.find("gzip") != std::string::npos) - return true; /* gotcha! */ - if (includingI2PGzip && it->second.find("x-i2p-gzip") != std::string::npos) - return true; - return false; - } - - long int HTTPMsg::content_length() const - { - unsigned long int length = 0; - auto it = headers.find("Content-Length"); - if (it == headers.end()) - return -1; - errno = 0; - length = std::strtoul(it->second.c_str(), (char **) NULL, 10); - if (errno != 0) - return -1; - return length; - } - - int HTTPRes::parse(const char *buf, size_t len) { - std::string str(buf, len); - return parse(str); - } - - int HTTPRes::parse(const std::string& str) { - enum { RES_LINE, HEADER_LINE } expect = RES_LINE; - std::size_t eoh = str.find(HTTP_EOH); /* request head size */ - std::size_t eol = 0, pos = 0; - - if (eoh == std::string::npos) - return 0; /* str not contains complete request */ - - while ((eol = str.find(CRLF, pos)) != std::string::npos) { - if (expect == RES_LINE) { - std::string line = str.substr(pos, eol - pos); - std::vector tokens; - strsplit(line, tokens, ' ', 3); - if (tokens.size() != 3) - return -1; - if (!is_http_version(tokens[0])) - return -1; - code = atoi(tokens[1].c_str()); - if (code < 100 || code >= 600) - return -1; - /* all ok */ - version = tokens[0]; - status = tokens[2]; - expect = HEADER_LINE; - } else { - std::string line = str.substr(pos, eol - pos); - auto p = parse_header_line(line); - if (p.first.length () > 0) - headers.insert (p); - else - return -1; - } - pos = eol + strlen(CRLF); - if (pos >= eoh) - break; - } - - return eoh + strlen(HTTP_EOH); - } - - std::string HTTPRes::to_string() { - if (version == "HTTP/1.1" && headers.count("Date") == 0) { - std::string date; - gen_rfc7231_date(date); - add_header("Date", date.c_str()); - } - if (status == "OK" && code != 200) - status = HTTPCodeToStatus(code); // update - if (body.length() > 0 && headers.count("Content-Length") == 0) - add_header("Content-Length", std::to_string(body.length()).c_str()); - /* build response */ - std::stringstream ss; - ss << version << " " << code << " " << status << CRLF; - for (auto & h : headers) { - ss << h.first << ": " << h.second << CRLF; - } - ss << CRLF; - if (body.length() > 0) - ss << body; - return ss.str(); - } - - const char * HTTPCodeToStatus(int code) { - const char *ptr; - switch (code) { - case 105: ptr = "Name Not Resolved"; break; - /* success */ - case 200: ptr = "OK"; break; - case 206: ptr = "Partial Content"; break; - /* redirect */ - case 301: ptr = "Moved Permanently"; break; - case 302: ptr = "Found"; break; - case 304: ptr = "Not Modified"; break; - case 307: ptr = "Temporary Redirect"; break; - /* client error */ - case 400: ptr = "Bad Request"; break; - case 401: ptr = "Unauthorized"; break; - case 403: ptr = "Forbidden"; break; - case 404: ptr = "Not Found"; break; - case 407: ptr = "Proxy Authentication Required"; break; - case 408: ptr = "Request Timeout"; break; - /* server error */ - case 500: ptr = "Internal Server Error"; break; - case 502: ptr = "Bad Gateway"; break; - case 503: ptr = "Not Implemented"; break; - case 504: ptr = "Gateway Timeout"; break; - default: ptr = "Unknown Status"; break; - } - return ptr; - } - - std::string UrlDecode(const std::string& data, bool allow_null) { + bool HTTPRes::is_chunked() const + { + auto it = headers.find("Transfer-Encoding"); + if (it == headers.end()) + return false; + if (it->second.find("chunked") == std::string::npos) + return true; + return false; + } + + bool HTTPRes::is_gzipped(bool includingI2PGzip) const + { + auto it = headers.find("Content-Encoding"); + if (it == headers.end()) + return false; /* no header */ + if (it->second.find("gzip") != std::string::npos) + return true; /* gotcha! */ + if (includingI2PGzip && it->second.find("x-i2p-gzip") != std::string::npos) + return true; + return false; + } + + long int HTTPMsg::content_length() const + { + unsigned long int length = 0; + auto it = headers.find("Content-Length"); + if (it == headers.end()) + return -1; + errno = 0; + length = std::strtoul(it->second.c_str(), (char **) NULL, 10); + if (errno != 0) + return -1; + return length; + } + + int HTTPRes::parse(const char *buf, size_t len) { + std::string str(buf, len); + return parse(str); + } + + int HTTPRes::parse(const std::string& str) { + enum { RES_LINE, HEADER_LINE } expect = RES_LINE; + std::size_t eoh = str.find(HTTP_EOH); /* request head size */ + std::size_t eol = 0, pos = 0; + + if (eoh == std::string::npos) + return 0; /* str not contains complete request */ + + while ((eol = str.find(CRLF, pos)) != std::string::npos) { + if (expect == RES_LINE) { + std::string line = str.substr(pos, eol - pos); + std::vector tokens; + strsplit(line, tokens, ' ', 3); + if (tokens.size() != 3) + return -1; + if (!is_http_version(tokens[0])) + return -1; + code = atoi(tokens[1].c_str()); + if (code < 100 || code >= 600) + return -1; + /* all ok */ + version = tokens[0]; + status = tokens[2]; + expect = HEADER_LINE; + } else { + std::string line = str.substr(pos, eol - pos); + auto p = parse_header_line(line); + if (p.first.length () > 0) + headers.insert (p); + else + return -1; + } + pos = eol + strlen(CRLF); + if (pos >= eoh) + break; + } + return eoh + strlen(HTTP_EOH); + } + + std::string HTTPRes::to_string() { + if (version == "HTTP/1.1" && headers.count("Date") == 0) { + std::string date; + gen_rfc7231_date(date); + add_header("Date", date.c_str()); + } + if (status == "OK" && code != 200) + status = HTTPCodeToStatus(code); // update + if (body.length() > 0 && headers.count("Content-Length") == 0) + add_header("Content-Length", std::to_string(body.length()).c_str()); + /* build response */ + std::stringstream ss; + ss << version << " " << code << " " << status << CRLF; + for (auto & h : headers) { + ss << h.first << ": " << h.second << CRLF; + } + ss << CRLF; + if (body.length() > 0) + ss << body; + return ss.str(); + } + + const char * HTTPCodeToStatus(int code) { + const char *ptr; + switch (code) { + case 105: ptr = "Name Not Resolved"; break; + /* success */ + case 200: ptr = "OK"; break; + case 206: ptr = "Partial Content"; break; + /* redirect */ + case 301: ptr = "Moved Permanently"; break; + case 302: ptr = "Found"; break; + case 304: ptr = "Not Modified"; break; + case 307: ptr = "Temporary Redirect"; break; + /* client error */ + case 400: ptr = "Bad Request"; break; + case 401: ptr = "Unauthorized"; break; + case 403: ptr = "Forbidden"; break; + case 404: ptr = "Not Found"; break; + case 407: ptr = "Proxy Authentication Required"; break; + case 408: ptr = "Request Timeout"; break; + /* server error */ + case 500: ptr = "Internal Server Error"; break; + case 502: ptr = "Bad Gateway"; break; + case 503: ptr = "Not Implemented"; break; + case 504: ptr = "Gateway Timeout"; break; + default: ptr = "Unknown Status"; break; + } + return ptr; + } + + std::string UrlDecode(const std::string& data, bool allow_null) { std::string decoded(data); - size_t pos = 0; - while ((pos = decoded.find('%', pos)) != std::string::npos) { - char c = strtol(decoded.substr(pos + 1, 2).c_str(), NULL, 16); - if (c == '\0' && !allow_null) { - pos += 3; - continue; - } - decoded.replace(pos, 3, 1, c); - pos++; - } - return decoded; - } - - bool MergeChunkedResponse (std::istream& in, std::ostream& out) { - std::string hexLen; - while (!in.eof ()) { - std::getline (in, hexLen); - errno = 0; - long int len = strtoul(hexLen.c_str(), (char **) NULL, 16); - if (errno != 0) - return false; /* conversion error */ - if (len == 0) - return true; /* end of stream */ - if (len < 0 || len > 10 * 1024 * 1024) /* < 10Mb */ - return false; /* too large chunk */ - char * buf = new char[len]; - in.read (buf, len); - out.write (buf, len); - delete[] buf; - std::getline (in, hexLen); // read \r\n after chunk - } - return true; - } + size_t pos = 0; + while ((pos = decoded.find('%', pos)) != std::string::npos) { + char c = strtol(decoded.substr(pos + 1, 2).c_str(), NULL, 16); + if (c == '\0' && !allow_null) { + pos += 3; + continue; + } + decoded.replace(pos, 3, 1, c); + pos++; + } + return decoded; + } + + bool MergeChunkedResponse (std::istream& in, std::ostream& out) { + std::string hexLen; + while (!in.eof ()) { + std::getline (in, hexLen); + errno = 0; + long int len = strtoul(hexLen.c_str(), (char **) NULL, 16); + if (errno != 0) + return false; /* conversion error */ + if (len == 0) + return true; /* end of stream */ + if (len < 0 || len > 10 * 1024 * 1024) /* < 10Mb */ + return false; /* too large chunk */ + char * buf = new char[len]; + in.read (buf, len); + out.write (buf, len); + delete[] buf; + std::getline (in, hexLen); // read \r\n after chunk + } + return true; + } } // http } // i2p diff --git a/libi2pd/HTTP.h b/libi2pd/HTTP.h index 837faf01..f156fef0 100644 --- a/libi2pd/HTTP.h +++ b/libi2pd/HTTP.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2016, The PurpleI2P Project +* Copyright (c) 2013-2019, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -20,152 +20,152 @@ namespace i2p { namespace http { - const char CRLF[] = "\r\n"; /**< HTTP line terminator */ - const char HTTP_EOH[] = "\r\n\r\n"; /**< HTTP end-of-headers mark */ - extern const std::vector HTTP_METHODS; /**< list of valid HTTP methods */ - extern const std::vector HTTP_VERSIONS; /**< list of valid HTTP versions */ - - struct URL - { - std::string schema; - std::string user; - std::string pass; - std::string host; - unsigned short int port; - std::string path; - std::string query; - std::string frag; - - URL(): schema(""), user(""), pass(""), host(""), port(0), path(""), query(""), frag("") {}; - - /** - * @brief Tries to parse url from string - * @return true on success, false on invalid url - */ - bool parse (const char *str, std::size_t len = 0); - bool parse (const std::string& url); - - /** - * @brief Parse query part of url to key/value map - * @note Honestly, this should be implemented with std::multimap - */ - bool parse_query(std::map & params); - - /** - * @brief Serialize URL structure to url - * @note Returns relative url if schema if empty, absolute url otherwise - */ - std::string to_string (); - - /** - * @brief return true if the host is inside i2p - */ - bool is_i2p() const; - }; - - struct HTTPMsg - { - std::map headers; - - void add_header(const char *name, std::string & value, bool replace = false); - void add_header(const char *name, const char *value, bool replace = false); - void del_header(const char *name); - - /** @brief Returns declared message length or -1 if unknown */ - long int content_length() const; - }; - - struct HTTPReq - { - std::list > headers; - std::string version; - std::string method; - std::string uri; - - HTTPReq (): version("HTTP/1.0"), method("GET"), uri("/") {}; - - /** - * @brief Tries to parse HTTP request from string - * @return -1 on error, 0 on incomplete query, >0 on success - * @note Positive return value is a size of header - */ - int parse(const char *buf, size_t len); - int parse(const std::string& buf); - - /** @brief Serialize HTTP request to string */ - std::string to_string(); + const char CRLF[] = "\r\n"; /**< HTTP line terminator */ + const char HTTP_EOH[] = "\r\n\r\n"; /**< HTTP end-of-headers mark */ + extern const std::vector HTTP_METHODS; /**< list of valid HTTP methods */ + extern const std::vector HTTP_VERSIONS; /**< list of valid HTTP versions */ + + struct URL + { + std::string schema; + std::string user; + std::string pass; + std::string host; + unsigned short int port; + std::string path; + std::string query; + std::string frag; + + URL(): schema(""), user(""), pass(""), host(""), port(0), path(""), query(""), frag("") {}; + + /** + * @brief Tries to parse url from string + * @return true on success, false on invalid url + */ + bool parse (const char *str, std::size_t len = 0); + bool parse (const std::string& url); + + /** + * @brief Parse query part of url to key/value map + * @note Honestly, this should be implemented with std::multimap + */ + bool parse_query(std::map & params); + + /** + * @brief Serialize URL structure to url + * @note Returns relative url if schema if empty, absolute url otherwise + */ + std::string to_string (); + + /** + * @brief return true if the host is inside i2p + */ + bool is_i2p() const; + }; + + struct HTTPMsg + { + std::map headers; + + void add_header(const char *name, std::string & value, bool replace = false); + void add_header(const char *name, const char *value, bool replace = false); + void del_header(const char *name); + + /** @brief Returns declared message length or -1 if unknown */ + long int content_length() const; + }; + + struct HTTPReq + { + std::list > headers; + std::string version; + std::string method; + std::string uri; + + HTTPReq (): version("HTTP/1.0"), method("GET"), uri("/") {}; + + /** + * @brief Tries to parse HTTP request from string + * @return -1 on error, 0 on incomplete query, >0 on success + * @note Positive return value is a size of header + */ + int parse(const char *buf, size_t len); + int parse(const std::string& buf); + + /** @brief Serialize HTTP request to string */ + std::string to_string(); void write(std::ostream & o); - void AddHeader (const std::string& name, const std::string& value); - void UpdateHeader (const std::string& name, const std::string& value); - void RemoveHeader (const std::string& name, const std::string& exempt); // remove all headers starting with name, but exempt - void RemoveHeader (const std::string& name) { RemoveHeader (name, ""); }; - std::string GetHeader (const std::string& name) const; - }; - - struct HTTPRes : HTTPMsg { - std::string version; - std::string status; - unsigned short int code; - /** - * @brief Simplifies response generation - * - * If this variable is set, on @a to_string() call: - * * Content-Length header will be added if missing, - * * contents of @a body will be included in generated response - */ - std::string body; - - HTTPRes (): version("HTTP/1.1"), status("OK"), code(200) {} - - /** - * @brief Tries to parse HTTP response from string - * @return -1 on error, 0 on incomplete query, >0 on success - * @note Positive return value is a size of header - */ - int parse(const char *buf, size_t len); - int parse(const std::string& buf); - - /** - * @brief Serialize HTTP response to string - * @note If @a version is set to HTTP/1.1, and Date header is missing, - * it will be generated based on current time and added to headers - * @note If @a body is set and Content-Length header is missing, - * this header will be added, based on body's length - */ - std::string to_string(); + void AddHeader (const std::string& name, const std::string& value); + void UpdateHeader (const std::string& name, const std::string& value); + void RemoveHeader (const std::string& name, const std::string& exempt); // remove all headers starting with name, but exempt + void RemoveHeader (const std::string& name) { RemoveHeader (name, ""); }; + std::string GetHeader (const std::string& name) const; + }; + + struct HTTPRes : HTTPMsg { + std::string version; + std::string status; + unsigned short int code; + /** + * @brief Simplifies response generation + * + * If this variable is set, on @a to_string() call: + * * Content-Length header will be added if missing, + * * contents of @a body will be included in generated response + */ + std::string body; + + HTTPRes (): version("HTTP/1.1"), status("OK"), code(200) {} + + /** + * @brief Tries to parse HTTP response from string + * @return -1 on error, 0 on incomplete query, >0 on success + * @note Positive return value is a size of header + */ + int parse(const char *buf, size_t len); + int parse(const std::string& buf); + + /** + * @brief Serialize HTTP response to string + * @note If @a version is set to HTTP/1.1, and Date header is missing, + * it will be generated based on current time and added to headers + * @note If @a body is set and Content-Length header is missing, + * this header will be added, based on body's length + */ + std::string to_string(); void write(std::ostream & o); - /** @brief Checks that response declared as chunked data */ - bool is_chunked() const ; - - /** @brief Checks that response contains compressed data */ - bool is_gzipped(bool includingI2PGzip = true) const; - }; - - /** - * @brief returns HTTP status string by integer code - * @param code HTTP code [100, 599] - * @return Immutable string with status - */ - const char * HTTPCodeToStatus(int code); - - /** - * @brief Replaces %-encoded characters in string with their values - * @param data Source string - * @param null If set to true - decode also %00 sequence, otherwise - skip - * @return Decoded string - */ - std::string UrlDecode(const std::string& data, bool null = false); - - /** - * @brief Merge HTTP response content with Transfer-Encoding: chunked - * @param in Input stream - * @param out Output stream - * @return true on success, false otherwise - */ - bool MergeChunkedResponse (std::istream& in, std::ostream& out); + /** @brief Checks that response declared as chunked data */ + bool is_chunked() const ; + + /** @brief Checks that response contains compressed data */ + bool is_gzipped(bool includingI2PGzip = true) const; + }; + + /** + * @brief returns HTTP status string by integer code + * @param code HTTP code [100, 599] + * @return Immutable string with status + */ + const char * HTTPCodeToStatus(int code); + + /** + * @brief Replaces %-encoded characters in string with their values + * @param data Source string + * @param null If set to true - decode also %00 sequence, otherwise - skip + * @return Decoded string + */ + std::string UrlDecode(const std::string& data, bool null = false); + + /** + * @brief Merge HTTP response content with Transfer-Encoding: chunked + * @param in Input stream + * @param out Output stream + * @return true on success, false otherwise + */ + bool MergeChunkedResponse (std::istream& in, std::ostream& out); } // http } // i2p diff --git a/libi2pd/LeaseSet.cpp b/libi2pd/LeaseSet.cpp index 8b9edb79..101f17d9 100644 --- a/libi2pd/LeaseSet.cpp +++ b/libi2pd/LeaseSet.cpp @@ -1,11 +1,8 @@ #include -#include -#include -#include // for crc32 #include "I2PEndian.h" #include "Crypto.h" -#include "Ed25519.h" #include "Log.h" +#include "Tag.h" #include "Timestamp.h" #include "NetDb.hpp" #include "Tunnel.h" @@ -254,159 +251,20 @@ namespace data memcpy (m_Buffer, buf, len); } - BlindedPublicKey::BlindedPublicKey (std::shared_ptr identity, SigningKeyType blindedKeyType): - m_BlindedSigType (blindedKeyType) - { - if (!identity) return; - auto len = identity->GetSigningPublicKeyLen (); - m_PublicKey.resize (len); - memcpy (m_PublicKey.data (), identity->GetSigningPublicKeyBuffer (), len); - m_SigType = identity->GetSigningKeyType (); - } - - BlindedPublicKey::BlindedPublicKey (const std::string& b33) - { - uint8_t addr[40]; // TODO: define length from b33 - size_t l = i2p::data::Base32ToByteStream (b33.c_str (), b33.length (), addr, 40); - uint32_t checksum = crc32 (0, addr + 3, l - 3); - // checksum is Little Endian - addr[0] ^= checksum; addr[1] ^= (checksum >> 8); addr[2] ^= (checksum >> 16); - uint8_t flag = addr[0]; - size_t offset = 1; - if (flag & 0x01) // two bytes signatures - { - m_SigType = bufbe16toh (addr + offset); offset += 2; - m_BlindedSigType = bufbe16toh (addr + offset); offset += 2; - } - else // one byte sig - { - m_SigType = addr[offset]; offset++; - m_BlindedSigType = addr[offset]; offset++; - } - std::unique_ptr blindedVerifier (i2p::data::IdentityEx::CreateVerifier (m_SigType)); - if (blindedVerifier) - { - auto len = blindedVerifier->GetPublicKeyLen (); - if (offset + len <= l) - { - m_PublicKey.resize (len); - memcpy (m_PublicKey.data (), addr + offset, len); - } - else - LogPrint (eLogError, "LeaseSet2: public key in b33 address is too short for signature type ", (int)m_SigType); - } - else - LogPrint (eLogError, "LeaseSet2: unknown signature type ", (int)m_SigType, " in b33"); - } - - std::string BlindedPublicKey::ToB33 () const - { - if (m_PublicKey.size () > 32) return ""; // assume 25519 - uint8_t addr[35]; char str[60]; // TODO: define actual length - addr[0] = 0; // flags - addr[1] = m_SigType; // sig type - addr[2] = m_BlindedSigType; // blinded sig type - memcpy (addr + 3, m_PublicKey.data (), m_PublicKey.size ()); - uint32_t checksum = crc32 (0, addr + 3, m_PublicKey.size ()); - // checksum is Little Endian - addr[0] ^= checksum; addr[1] ^= (checksum >> 8); addr[2] ^= (checksum >> 16); - auto l = ByteStreamToBase32 (addr, m_PublicKey.size () + 3, str, 60); - return std::string (str, str + l); - } - - void BlindedPublicKey::GetCredential (uint8_t * credential) const - { - // A = destination's signing public key - // stA = signature type of A, 2 bytes big endian - uint16_t stA = htobe16 (GetSigType ()); - // stA1 = signature type of blinded A, 2 bytes big endian - uint16_t stA1 = htobe16 (GetBlindedSigType ()); - // credential = H("credential", A || stA || stA1) - H ("credential", { {GetPublicKey (), GetPublicKeyLen ()}, {(const uint8_t *)&stA, 2}, {(const uint8_t *)&stA1, 2} }, credential); - } - - void BlindedPublicKey::GetSubcredential (const uint8_t * blinded, size_t len, uint8_t * subcredential) const - { - uint8_t credential[32]; - GetCredential (credential); - // subcredential = H("subcredential", credential || blindedPublicKey) - H ("subcredential", { {credential, 32}, {blinded, len} }, subcredential); - } - - void BlindedPublicKey::GenerateAlpha (const char * date, uint8_t * seed) const - { - uint16_t stA = htobe16 (GetSigType ()), stA1 = htobe16 (GetBlindedSigType ()); - uint8_t salt[32]; - //seed = HKDF(H("I2PGenerateAlpha", keydata), datestring || secret, "i2pblinding1", 64) - H ("I2PGenerateAlpha", { {GetPublicKey (), GetPublicKeyLen ()}, {(const uint8_t *)&stA, 2}, {(const uint8_t *)&stA1, 2} }, salt); - i2p::crypto::HKDF (salt, (const uint8_t *)date, 8, "i2pblinding1", seed); - } - - void BlindedPublicKey::GetBlindedKey (const char * date, uint8_t * blindedKey) const - { - uint8_t seed[64]; - GenerateAlpha (date, seed); - i2p::crypto::GetEd25519 ()->BlindPublicKey (GetPublicKey (), seed, blindedKey); - } - - void BlindedPublicKey::BlindPrivateKey (const uint8_t * priv, const char * date, uint8_t * blindedPriv, uint8_t * blindedPub) const - { - uint8_t seed[64]; - GenerateAlpha (date, seed); - i2p::crypto::GetEd25519 ()->BlindPrivateKey (priv, seed, blindedPriv, blindedPub); - } - - void BlindedPublicKey::H (const std::string& p, const std::vector >& bufs, uint8_t * hash) const - { - SHA256_CTX ctx; - SHA256_Init (&ctx); - SHA256_Update (&ctx, p.c_str (), p.length ()); - for (const auto& it: bufs) - SHA256_Update (&ctx, it.first, it.second); - SHA256_Final (hash, &ctx); - } - - i2p::data::IdentHash BlindedPublicKey::GetStoreHash (const char * date) const - { - i2p::data::IdentHash hash; - if (m_BlindedSigType == i2p::data::SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519 || - m_BlindedSigType == SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519) - { - uint8_t blinded[32]; - if (date) - GetBlindedKey (date, blinded); - else - { - char currentDate[9]; - i2p::util::GetCurrentDate (currentDate); - GetBlindedKey (currentDate, blinded); - } - auto stA1 = htobe16 (m_BlindedSigType); - SHA256_CTX ctx; - SHA256_Init (&ctx); - SHA256_Update (&ctx, (const uint8_t *)&stA1, 2); - SHA256_Update (&ctx, blinded, 32); - SHA256_Final ((uint8_t *)hash, &ctx); - } - else - LogPrint (eLogError, "LeaseSet2: blinded key type ", (int)m_BlindedSigType, " is not supported"); - return hash; - } - LeaseSet2::LeaseSet2 (uint8_t storeType, const uint8_t * buf, size_t len, bool storeLeases): LeaseSet (storeLeases), m_StoreType (storeType), m_OrigStoreType (storeType) { SetBuffer (buf, len); if (storeType == NETDB_STORE_TYPE_ENCRYPTED_LEASESET2) - ReadFromBufferEncrypted (buf, len, nullptr); + ReadFromBufferEncrypted (buf, len, nullptr, nullptr); else ReadFromBuffer (buf, len); } - LeaseSet2::LeaseSet2 (const uint8_t * buf, size_t len, std::shared_ptr key): + 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), m_OrigStoreType (NETDB_STORE_TYPE_ENCRYPTED_LEASESET2) { - ReadFromBufferEncrypted (buf, len, key); + ReadFromBufferEncrypted (buf, len, key, secret); } void LeaseSet2::Update (const uint8_t * buf, size_t len, bool verifySignature) @@ -563,7 +421,7 @@ namespace data return offset; } - void LeaseSet2::ReadFromBufferEncrypted (const uint8_t * buf, size_t len, std::shared_ptr key) + void LeaseSet2::ReadFromBufferEncrypted (const uint8_t * buf, size_t len, std::shared_ptr key, const uint8_t * secret) { size_t offset = 0; // blinded key @@ -606,19 +464,24 @@ namespace data if (verified && key && lenOuterCiphertext >= 32) { SetIsValid (false); // we must verify it again in Layer 2 - if (blindedKeyType == i2p::data::SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519) + if (blindedKeyType == key->GetBlindedSigType ()) { // verify blinding char date[9]; i2p::util::GetDateString (m_PublishedTimestamp, date); - uint8_t blinded[32]; - key->GetBlindedKey (date, blinded); - if (memcmp (blindedPublicKey, blinded, 32)) + std::vector blinded (blindedKeyLen); + key->GetBlindedKey (date, blinded.data ()); + if (memcmp (blindedPublicKey, blinded.data (), blindedKeyLen)) { LogPrint (eLogError, "LeaseSet2: blinded public key doesn't match"); return; } } + else + { + LogPrint (eLogError, "LeaseSet2: Unexpected blinded key type ", blindedKeyType, " instread ", key->GetBlindedSigType ()); + return; + } // outer key // outerInput = subcredential || publishedTimestamp uint8_t subcredential[36]; @@ -635,17 +498,26 @@ namespace data std::vector outerPlainText (lenOuterPlaintext); i2p::crypto::ChaCha20 (outerCiphertext + 32, lenOuterPlaintext, keys, keys + 32, outerPlainText.data ()); // inner key - // innerInput = authCookie || subcredential || publishedTimestamp, TODO: non-empty authCookie + // innerInput = authCookie || subcredential || publishedTimestamp // innerSalt = innerCiphertext[0:32] // keys = HKDF(innerSalt, innerInput, "ELS2_L2K", 44) - // skip 1 byte flags - i2p::crypto::HKDF (outerPlainText.data () + 1, subcredential, 36, "ELS2_L2K", keys); // no authCookie + uint8_t innerInput[68]; + size_t authDataLen = ExtractClientAuthData (outerPlainText.data (), lenOuterPlaintext, secret, subcredential, innerInput); + if (authDataLen > 0) + { + memcpy (innerInput + 32, subcredential, 36); + i2p::crypto::HKDF (outerPlainText.data () + 1, innerInput, 68, "ELS2_L2K", keys); + } + else + // no authData presented, innerInput = subcredential || publishedTimestamp + // skip 1 byte flags + i2p::crypto::HKDF (outerPlainText.data () + 1, subcredential, 36, "ELS2_L2K", keys); // no authCookie // decrypt Layer 2 // innerKey = keys[0:31] // innerIV = keys[32:43] - size_t lenInnerPlaintext = lenOuterPlaintext - 32 - 1; + size_t lenInnerPlaintext = lenOuterPlaintext - 32 - 1 - authDataLen; std::vector innerPlainText (lenInnerPlaintext); - i2p::crypto::ChaCha20 (outerPlainText.data () + 32 + 1, lenInnerPlaintext, keys, keys + 32, innerPlainText.data ()); + i2p::crypto::ChaCha20 (outerPlainText.data () + 32 + 1 + authDataLen, lenInnerPlaintext, keys, keys + 32, innerPlainText.data ()); if (innerPlainText[0] == NETDB_STORE_TYPE_STANDARD_LEASESET2 || innerPlainText[0] == NETDB_STORE_TYPE_META_LEASESET2) { // override store type and buffer @@ -659,6 +531,50 @@ namespace data } } + size_t LeaseSet2::ExtractClientAuthData (const uint8_t * buf, size_t len, const uint8_t * secret, const uint8_t * subcredential, uint8_t * authCookie) const + { + size_t offset = 0; + uint8_t flag = buf[offset]; offset++; // flag + if (flag & 0x01) // client auth + { + if (flag & 0x02) // PSK, bit 1 is set to 1 + { + const uint8_t * authSalt = buf + offset; offset += 32; // authSalt + uint16_t numClients = bufbe16toh (buf + offset); offset += 2; // clients + const uint8_t * authClients = buf + offset; offset += numClients*40; // authClients + // calculate authCookie + if (secret) + { + uint8_t authInput[68]; + memcpy (authInput, secret, 32); + memcpy (authInput, subcredential, 36); + uint8_t okm[64]; // 52 actual data + i2p::crypto::HKDF (authSalt, authInput, 68, "ELS2PSKA", okm); + // try to find clientCookie_i for clientID_i = okm[44:51] + bool found = false; + for (int i = 0; i < numClients; i++) + { + if (!memcmp (okm + 44, authClients + i*40, 8)) // clientID_i + { + // clientKey_i = okm[0:31] + // clientIV_i = okm[32:43] + i2p::crypto::ChaCha20 (authClients + i*40 + 8, 32, okm, okm + 32, authCookie); // clientCookie_i + found = true; + break; + } + } + if (!found) + LogPrint (eLogError, "LeaseSet2: Client cookie not found"); + } + else + LogPrint (eLogError, "LeaseSet2: Can't calculate authCookie: psk_i is not provided"); + } + else + LogPrint (eLogError, "LeaseSet2: unknown client auth type ", (int)flag); + } + return offset - 1; + } + void LeaseSet2::Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx) const { auto encryptor = m_Encryptor; // TODO: atomic @@ -870,12 +786,12 @@ namespace data auto timestamp = i2p::util::GetSecondsSinceEpoch (); char date[9]; i2p::util::GetDateString (timestamp, date); - uint8_t blindedPriv[32], blindedPub[32]; - blindedKey.BlindPrivateKey (keys.GetSigningPrivateKey (), date, blindedPriv, blindedPub); + uint8_t blindedPriv[64], blindedPub[128]; // 64 and 128 max + size_t publicKeyLen = blindedKey.BlindPrivateKey (keys.GetSigningPrivateKey (), date, blindedPriv, blindedPub); std::unique_ptr blindedSigner (i2p::data::PrivateKeys::CreateSigner (blindedKeyType, blindedPriv)); auto offset = 1; htobe16buf (m_Buffer + offset, blindedKeyType); offset += 2; // Blinded Public Key Sig Type - memcpy (m_Buffer + offset, blindedPub, 32); offset += 32; // Blinded Public Key + memcpy (m_Buffer + offset, blindedPub, publicKeyLen); offset += publicKeyLen; // Blinded Public Key htobe32buf (m_Buffer + offset, timestamp); offset += 4; // published timestamp (seconds) auto nextMidnight = (timestamp/86400LL + 1)*86400LL; // 86400 = 24*3600 seconds auto expirationTime = ls->GetExpirationTime ()/1000LL; diff --git a/libi2pd/LeaseSet.h b/libi2pd/LeaseSet.h index 94a19b1b..a2ed6fba 100644 --- a/libi2pd/LeaseSet.h +++ b/libi2pd/LeaseSet.h @@ -9,6 +9,7 @@ #include "Identity.h" #include "Timestamp.h" #include "I2PEndian.h" +#include "Blinding.h" namespace i2p { @@ -128,43 +129,13 @@ namespace data const uint8_t NETDB_STORE_TYPE_META_LEASESET2 = 7; const uint16_t LEASESET2_FLAG_OFFLINE_KEYS = 0x0001; - - class BlindedPublicKey // for encrypted LS2 - { - public: - - BlindedPublicKey (std::shared_ptr identity, SigningKeyType blindedKeyType = i2p::data::SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519); - BlindedPublicKey (const std::string& b33); // from b33 without .b32.i2p - std::string ToB33 () const; - - const uint8_t * GetPublicKey () const { return m_PublicKey.data (); }; - size_t GetPublicKeyLen () const { return m_PublicKey.size (); }; - SigningKeyType GetSigType () const { return m_SigType; }; - SigningKeyType GetBlindedSigType () const { return m_BlindedSigType; }; - - void GetSubcredential (const uint8_t * blinded, size_t len, uint8_t * subcredential) const; // 32 bytes - void GetBlindedKey (const char * date, uint8_t * blindedKey) const; // blinded key 32 bytes, date is 8 chars "YYYYMMDD" - void BlindPrivateKey (const uint8_t * priv, const char * date, uint8_t * blindedPriv, uint8_t * blindedPub) const; // blinded key 32 bytes, date is 8 chars "YYYYMMDD" - i2p::data::IdentHash GetStoreHash (const char * date = nullptr) const; // date is 8 chars "YYYYMMDD", use current if null - - private: - - void GetCredential (uint8_t * credential) const; // 32 bytes - void GenerateAlpha (const char * date, uint8_t * seed) const; // 64 bytes, date is 8 chars "YYYYMMDD" - void H (const std::string& p, const std::vector >& bufs, uint8_t * hash) const; - - private: - - std::vector m_PublicKey; - i2p::data::SigningKeyType m_SigType, m_BlindedSigType; - }; class LeaseSet2: public LeaseSet { 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); // store type 5, called from local netdb only + 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 uint8_t GetStoreType () const { return m_StoreType; }; uint8_t GetOrigStoreType () const { return m_OrigStoreType; }; uint32_t GetPublishedTimestamp () const { return m_PublishedTimestamp; }; @@ -177,7 +148,7 @@ namespace data private: void ReadFromBuffer (const uint8_t * buf, size_t len, bool readIdentity = true, bool verifySignature = true); - void ReadFromBufferEncrypted (const uint8_t * buf, size_t len, std::shared_ptr key); + void ReadFromBufferEncrypted (const uint8_t * buf, size_t len, std::shared_ptr key, const uint8_t * secret); size_t ReadStandardLS2TypeSpecificPart (const uint8_t * buf, size_t len); size_t ReadMetaLS2TypeSpecificPart (const uint8_t * buf, size_t len); @@ -185,6 +156,7 @@ namespace data bool VerifySignature (Verifier& verifier, const uint8_t * buf, size_t len, size_t signatureOffset); uint64_t ExtractTimestamp (const uint8_t * buf, size_t len) const; + size_t ExtractClientAuthData (const uint8_t * buf, size_t len, const uint8_t * secret, const uint8_t * subcredential, uint8_t * authCookie) const; // subcredential is subcredential + timestamp, return length of autData without flag private: diff --git a/libi2pd/NTCP2.cpp b/libi2pd/NTCP2.cpp index 0976720e..5b5073f9 100644 --- a/libi2pd/NTCP2.cpp +++ b/libi2pd/NTCP2.cpp @@ -1304,8 +1304,6 @@ namespace transport else { LogPrint (eLogDebug, "NTCP2: Connected to ", conn->GetSocket ().remote_endpoint ()); - if (conn->GetSocket ().local_endpoint ().protocol () == boost::asio::ip::tcp::v6()) // ipv6 - context.UpdateNTCP2V6Address (conn->GetSocket ().local_endpoint ().address ()); conn->ClientLogin (); } } diff --git a/libi2pd/NTCPSession.cpp b/libi2pd/NTCPSession.cpp index 68a4133f..7e8ecd15 100644 --- a/libi2pd/NTCPSession.cpp +++ b/libi2pd/NTCPSession.cpp @@ -1079,8 +1079,6 @@ namespace transport else { LogPrint (eLogDebug, "NTCP: Connected to ", conn->GetSocket ().remote_endpoint ()); - if (conn->GetSocket ().local_endpoint ().protocol () == boost::asio::ip::tcp::v6()) // ipv6 - context.UpdateNTCPV6Address (conn->GetSocket ().local_endpoint ().address ()); conn->ClientLogin (); } } diff --git a/libi2pd/NetDb.cpp b/libi2pd/NetDb.cpp index 02ae2ae8..952c8017 100644 --- a/libi2pd/NetDb.cpp +++ b/libi2pd/NetDb.cpp @@ -698,14 +698,14 @@ namespace data LogPrint (eLogDebug, "NetDb: store request: RouterInfo"); size_t size = bufbe16toh (buf + offset); offset += 2; - if (size > 2048 || size > len - offset) + if (size > MAX_RI_BUFFER_SIZE || size > len - offset) { LogPrint (eLogError, "NetDb: invalid RouterInfo length ", (int)size); return; } - uint8_t uncompressed[2048]; - size_t uncompressedSize = m_Inflator.Inflate (buf + offset, size, uncompressed, 2048); - if (uncompressedSize && uncompressedSize < 2048) + uint8_t uncompressed[MAX_RI_BUFFER_SIZE]; + size_t uncompressedSize = m_Inflator.Inflate (buf + offset, size, uncompressed, MAX_RI_BUFFER_SIZE); + if (uncompressedSize && uncompressedSize < MAX_RI_BUFFER_SIZE) updated = AddRouterInfo (ident, uncompressed, uncompressedSize); else { @@ -1076,6 +1076,15 @@ namespace data }); } + std::shared_ptr NetDb::GetRandomSSUV6Router () const + { + return GetRandomRouter ( + [](std::shared_ptr router)->bool + { + return !router->IsHidden () && router->IsSSUV6 (); + }); + } + std::shared_ptr NetDb::GetRandomIntroducer () const { return GetRandomRouter ( diff --git a/libi2pd/NetDb.hpp b/libi2pd/NetDb.hpp index 93e9e48f..d9e10504 100644 --- a/libi2pd/NetDb.hpp +++ b/libi2pd/NetDb.hpp @@ -72,6 +72,7 @@ namespace data std::shared_ptr GetRandomRouter (std::shared_ptr compatibleWith) const; std::shared_ptr GetHighBandwidthRandomRouter (std::shared_ptr compatibleWith) const; std::shared_ptr GetRandomPeerTestRouter (bool v4only = true) const; + std::shared_ptr GetRandomSSUV6Router () const; // TODO: change to v6 peer test later std::shared_ptr GetRandomIntroducer () const; std::shared_ptr GetClosestFloodfill (const IdentHash& destination, const std::set& excluded, bool closeThanUsOnly = false) const; std::vector GetClosestFloodfills (const IdentHash& destination, size_t num, diff --git a/libi2pd/RouterContext.cpp b/libi2pd/RouterContext.cpp index 4a229546..d228b626 100644 --- a/libi2pd/RouterContext.cpp +++ b/libi2pd/RouterContext.cpp @@ -110,7 +110,17 @@ namespace i2p { bool published; i2p::config::GetOption("ntcp2.published", published); if (published) + { PublishNTCP2Address (port, true); + if (ipv6) + { + // add NTCP2 ipv6 address + std::string host = "::1"; + if (!i2p::config::IsDefault ("ntcp2.addressv6")) + i2p::config::GetOption ("ntcp2.addressv6", host); + m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, boost::asio::ip::address_v6::from_string (host), port); + } + } } } } @@ -170,7 +180,7 @@ namespace i2p UpdateRouterInfo (); } - void RouterContext::PublishNTCP2Address (int port, bool publish) + void RouterContext::PublishNTCP2Address (int port, bool publish, bool v4only) { if (!m_NTCP2Keys) return; if (!port) @@ -181,7 +191,7 @@ namespace i2p bool updated = false; for (auto& address : m_RouterInfo.GetAddresses ()) { - if (address->IsNTCP2 () && (address->port != port || address->ntcp2->isPublished != publish)) + if (address->IsNTCP2 () && (address->port != port || address->ntcp2->isPublished != publish) && (!v4only || address->host.is_v4 ())) { address->port = port; address->cost = publish ? 3 : 14; @@ -228,6 +238,20 @@ namespace i2p if (address->host != host && address->IsCompatible (host)) { address->host = host; + if (host.is_v6 () && address->transportStyle == i2p::data::RouterInfo::eTransportSSU) + { + // update MTU + auto mtu = i2p::util::net::GetMTU (host); + if (mtu) + { + LogPrint (eLogDebug, "Router: Our v6 MTU=", mtu); + if (mtu > 1472) { // TODO: magic constant + mtu = 1472; + LogPrint(eLogWarning, "Router: MTU dropped to upper limit of 1472 bytes"); + } + if (address->ssu) address->ssu->mtu = mtu; + } + } updated = true; } } @@ -397,13 +421,25 @@ namespace i2p caps &= ~i2p::data::RouterInfo::eFloodfill; // can't be floodfill caps &= ~i2p::data::RouterInfo::eSSUIntroducer; // can't be introducer m_RouterInfo.SetCaps (caps); - // remove NTCP v4 address - PublishNTCPAddress (false); + uint16_t port = 0; // delete previous introducers auto& addresses = m_RouterInfo.GetAddresses (); for (auto& addr : addresses) if (addr->ssu) + { addr->ssu->introducers.clear (); + port = addr->port; + } + // remove NTCP or NTCP2 v4 address + bool ntcp; i2p::config::GetOption("ntcp", ntcp); + if (ntcp) + PublishNTCPAddress (false); + else + { + bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); + if (ntcp2) + PublishNTCP2Address (port, false, true); + } // update UpdateRouterInfo (); } @@ -418,15 +454,34 @@ namespace i2p if (m_IsFloodfill) caps |= i2p::data::RouterInfo::eFloodfill; m_RouterInfo.SetCaps (caps); - // insert NTCP back - bool ntcp; i2p::config::GetOption("ntcp", ntcp); - if (ntcp) - PublishNTCPAddress (true); + uint16_t port = 0; // delete previous introducers auto& addresses = m_RouterInfo.GetAddresses (); for (auto& addr : addresses) if (addr->ssu) + { addr->ssu->introducers.clear (); + port = addr->port; + } + // insert NTCP or NTCP2 back + bool ntcp; i2p::config::GetOption("ntcp", ntcp); + if (ntcp) + PublishNTCPAddress (true); + else + { + // ntcp2 + bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); + if (ntcp2) + { + bool published; i2p::config::GetOption ("ntcp2.published", published); + if (published) + { + uint16_t ntcp2Port; i2p::config::GetOption ("ntcp2.port", ntcp2Port); + if (!ntcp2Port) ntcp2Port = port; + PublishNTCP2Address (ntcp2Port, true, true); + } + } + } // update UpdateRouterInfo (); } @@ -434,7 +489,66 @@ namespace i2p void RouterContext::SetSupportsV6 (bool supportsV6) { if (supportsV6) + { m_RouterInfo.EnableV6 (); + // insert v6 addresses if necessary + bool foundSSU = false, foundNTCP = false, foundNTCP2 = false; + uint16_t port = 0; + auto& addresses = m_RouterInfo.GetAddresses (); + for (auto& addr: addresses) + { + if (addr->host.is_v6 ()) + { + if (addr->transportStyle == i2p::data::RouterInfo::eTransportSSU) + foundSSU = true; + else if (addr->IsNTCP2 ()) + { + if (addr->IsPublishedNTCP2 ()) foundNTCP2 = true; + } + else + foundNTCP = true; + } + port = addr->port; + } + if (!port) i2p::config::GetOption("port", port); + // SSU + if (!foundSSU) + { + bool ssu; i2p::config::GetOption("ssu", ssu); + if (ssu) + { + std::string host = "::1"; // TODO: read host + m_RouterInfo.AddSSUAddress (host.c_str (), port, GetIdentHash ()); + } + } + // NTCP2 + if (!foundNTCP2) + { + bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); + bool ntcp2Published; i2p::config::GetOption("ntcp2.published", ntcp2Published); + if (ntcp2 && ntcp2Published) + { + std::string ntcp2Host; + if (!i2p::config::IsDefault ("ntcp2.addressv6")) + i2p::config::GetOption ("ntcp2.addressv6", ntcp2Host); + else + ntcp2Host = "::1"; + uint16_t ntcp2Port; i2p::config::GetOption ("ntcp2.port", ntcp2Port); + if (!ntcp2Port) ntcp2Port = port; + m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, boost::asio::ip::address::from_string (ntcp2Host), ntcp2Port); + } + } + // NTCP + if (!foundNTCP) + { + bool ntcp; i2p::config::GetOption("ntcp", ntcp); + if (ntcp) + { + std::string host = "::1"; + m_RouterInfo.AddNTCPAddress (host.c_str (), port); + } + } + } else m_RouterInfo.DisableV6 (); UpdateRouterInfo (); @@ -449,50 +563,9 @@ namespace i2p UpdateRouterInfo (); } - - void RouterContext::UpdateNTCPV6Address (const boost::asio::ip::address& host) - { - bool updated = false, found = false; - int port = 0; - auto& addresses = m_RouterInfo.GetAddresses (); - for (auto& addr: addresses) - { - if (addr->host.is_v6 () && addr->transportStyle == i2p::data::RouterInfo::eTransportNTCP) - { - if (addr->host != host) - { - addr->host = host; - updated = true; - } - found = true; - } - else - port = addr->port; - } - if (!found) - { - // create new address - m_RouterInfo.AddNTCPAddress (host.to_string ().c_str (), port); - auto mtu = i2p::util::net::GetMTU (host); - if (mtu) - { - LogPrint (eLogDebug, "Router: Our v6 MTU=", mtu); - if (mtu > 1472) { // TODO: magic constant - mtu = 1472; - LogPrint(eLogWarning, "Router: MTU dropped to upper limit of 1472 bytes"); - } - } - m_RouterInfo.AddSSUAddress (host.to_string ().c_str (), port, GetIdentHash (), mtu ? mtu : 1472); // TODO - updated = true; - } - if (updated) - UpdateRouterInfo (); - } - void RouterContext::UpdateNTCP2V6Address (const boost::asio::ip::address& host) { - bool updated = false, found = false; - int port = 0; + bool updated = false; auto& addresses = m_RouterInfo.GetAddresses (); for (auto& addr: addresses) { @@ -505,19 +578,11 @@ namespace i2p addr->host = host; updated = true; } - found = true; break; } - else - port = addr->port; // NTCP2 v4 } } - if (!found && port) // we have found NTCP2 v4 but not v6 - { - m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, host, port); - updated = true; - } if (updated) UpdateRouterInfo (); } diff --git a/libi2pd/RouterContext.h b/libi2pd/RouterContext.h index 19fe08b2..e680eff8 100644 --- a/libi2pd/RouterContext.h +++ b/libi2pd/RouterContext.h @@ -79,7 +79,7 @@ namespace i2p void UpdatePort (int port); // called from Daemon void UpdateAddress (const boost::asio::ip::address& host); // called from SSU or Daemon - void PublishNTCP2Address (int port, bool publish = true); + void PublishNTCP2Address (int port, bool publish = true, bool v4only = false); void UpdateNTCP2Address (bool enable); void PublishNTCPAddress (bool publish, bool v4only = true); bool AddIntroducer (const i2p::data::RouterInfo::Introducer& introducer); @@ -101,8 +101,7 @@ namespace i2p void SetSupportsV6 (bool supportsV6); void SetSupportsV4 (bool supportsV4); - void UpdateNTCPV6Address (const boost::asio::ip::address& host); // called from NTCP session - void UpdateNTCP2V6Address (const boost::asio::ip::address& host); // called from NTCP2 session + void UpdateNTCP2V6Address (const boost::asio::ip::address& host); // called from Daemon. TODO: remove void UpdateStats (); void UpdateTimestamp (uint64_t ts); // in seconds, called from NetDb before publishing void CleanupDestination (); // garlic destination diff --git a/libi2pd/RouterInfo.cpp b/libi2pd/RouterInfo.cpp index 6b520943..37c2af7e 100644 --- a/libi2pd/RouterInfo.cpp +++ b/libi2pd/RouterInfo.cpp @@ -38,10 +38,19 @@ namespace data m_IsUpdated (true), m_IsUnreachable (false), m_SupportedTransports (0), m_Caps (0) { m_Addresses = boost::make_shared(); // create empty list - m_Buffer = new uint8_t[MAX_RI_BUFFER_SIZE]; - memcpy (m_Buffer, buf, len); - m_BufferLen = len; - ReadFromBuffer (true); + if (len <= MAX_RI_BUFFER_SIZE) + { + m_Buffer = new uint8_t[MAX_RI_BUFFER_SIZE]; + memcpy (m_Buffer, buf, len); + m_BufferLen = len; + ReadFromBuffer (true); + } + else + { + LogPrint (eLogError, "RouterInfo: Buffer is too long ", len, ". Ignored"); + m_Buffer = nullptr; + m_IsUnreachable = true; + } } RouterInfo::~RouterInfo () @@ -49,8 +58,14 @@ namespace data delete[] m_Buffer; } - void RouterInfo::Update (const uint8_t * buf, int len) + void RouterInfo::Update (const uint8_t * buf, size_t len) { + if (len > MAX_RI_BUFFER_SIZE) + { + LogPrint (eLogError, "RouterInfo: Buffer is too long ", len); + m_IsUnreachable = true; + return; + } // verify signature since we have identity already int l = len - m_RouterIdentity->GetSignatureLen (); if (m_RouterIdentity->Verify (buf, l, buf + l)) @@ -100,8 +115,7 @@ namespace data return false; } s.seekg(0, std::ios::beg); - if (!m_Buffer) - m_Buffer = new uint8_t[MAX_RI_BUFFER_SIZE]; + if (!m_Buffer) m_Buffer = new uint8_t[MAX_RI_BUFFER_SIZE]; s.read((char *)m_Buffer, m_BufferLen); } else @@ -259,6 +273,11 @@ namespace data else if (key[0] == 'i') { // introducers + if (!address->ssu) + { + LogPrint (eLogError, "RouterInfo: Introducer is presented for non-SSU address. Skipped"); + continue; + } introducers = true; size_t l = strlen(key); unsigned char index = key[l-1] - '0'; // TODO: @@ -602,15 +621,21 @@ namespace data std::stringstream s; uint8_t ident[1024]; auto identLen = privateKeys.GetPublic ()->ToBuffer (ident, 1024); + auto signatureLen = privateKeys.GetPublic ()->GetSignatureLen (); s.write ((char *)ident, identLen); WriteToStream (s); m_BufferLen = s.str ().size (); if (!m_Buffer) m_Buffer = new uint8_t[MAX_RI_BUFFER_SIZE]; - memcpy (m_Buffer, s.str ().c_str (), m_BufferLen); - // signature - privateKeys.Sign ((uint8_t *)m_Buffer, m_BufferLen, (uint8_t *)m_Buffer + m_BufferLen); - m_BufferLen += privateKeys.GetPublic ()->GetSignatureLen (); + if (m_BufferLen + signatureLen < MAX_RI_BUFFER_SIZE) + { + memcpy (m_Buffer, s.str ().c_str (), m_BufferLen); + // signature + privateKeys.Sign ((uint8_t *)m_Buffer, m_BufferLen, (uint8_t *)m_Buffer + m_BufferLen); + m_BufferLen += signatureLen; + } + else + LogPrint (eLogError, "RouterInfo: Our RouterInfo is too long ", m_BufferLen + signatureLen); } bool RouterInfo::SaveToFile (const std::string& fullPath) @@ -784,6 +809,11 @@ namespace data return m_SupportedTransports & (eSSUV4 | eSSUV6); } + bool RouterInfo::IsSSUV6 () const + { + return m_SupportedTransports & eSSUV6; + } + bool RouterInfo::IsNTCP2 (bool v4only) const { if (v4only) @@ -794,7 +824,7 @@ namespace data bool RouterInfo::IsV6 () const { - return m_SupportedTransports & (eNTCPV6 | eSSUV6); + return m_SupportedTransports & (eNTCPV6 | eSSUV6 | eNTCP2V6); } bool RouterInfo::IsV4 () const diff --git a/libi2pd/RouterInfo.h b/libi2pd/RouterInfo.h index 084ad8a7..b23625e0 100644 --- a/libi2pd/RouterInfo.h +++ b/libi2pd/RouterInfo.h @@ -38,7 +38,7 @@ namespace data const char CAPS_FLAG_SSU_TESTING = 'B'; const char CAPS_FLAG_SSU_INTRODUCER = 'C'; - const int MAX_RI_BUFFER_SIZE = 2048; + const int MAX_RI_BUFFER_SIZE = 2048; // if RouterInfo exceeds 2048 we consider it as malformed, might be changed later class RouterInfo: public RoutingDestination { public: @@ -161,6 +161,7 @@ namespace data bool IsReachable () const { return m_Caps & Caps::eReachable; }; bool IsNTCP (bool v4only = true) const; bool IsSSU (bool v4only = true) const; + bool IsSSUV6 () const; bool IsNTCP2 (bool v4only = true) const; bool IsV6 () const; bool IsV4 () const; @@ -196,7 +197,7 @@ namespace data std::shared_ptr GetProfile () const; void SaveProfile () { if (m_Profile) m_Profile->Save (GetIdentHash ()); }; - void Update (const uint8_t * buf, int len); + void Update (const uint8_t * buf, size_t len); void DeleteBuffer () { delete[] m_Buffer; m_Buffer = nullptr; }; bool IsNewer (const uint8_t * buf, size_t len) const; diff --git a/libi2pd/Transports.cpp b/libi2pd/Transports.cpp index c00e5d52..b76d9312 100644 --- a/libi2pd/Transports.cpp +++ b/libi2pd/Transports.cpp @@ -552,6 +552,20 @@ namespace transport m_SSUServer->CreateSession (router); // no peer test } } + if (i2p::context.SupportsV6 ()) + { + // try to connect to few v6 addresses to get our address back + for (int i = 0; i < 3; i++) + { + auto router = i2p::data::netdb.GetRandomSSUV6Router (); + if (router) + { + auto addr = router->GetSSUV6Address (); + if (addr) + m_SSUServer->CreateDirectSession (router, { addr->host, (uint16_t)addr->port }, false); + } + } + } } else LogPrint (eLogError, "Transports: Can't detect external IP. SSU is not available"); diff --git a/libi2pd/version.h b/libi2pd/version.h index 4fdb5f27..4ac7a7b0 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 25 +#define I2PD_VERSION_MINOR 26 #define I2PD_VERSION_MICRO 0 #define I2PD_VERSION_PATCH 0 #define I2PD_VERSION MAKE_VERSION(I2PD_VERSION_MAJOR, I2PD_VERSION_MINOR, I2PD_VERSION_MICRO) diff --git a/libi2pd_client/BOB.cpp b/libi2pd_client/BOB.cpp index 94d74726..023be3b4 100644 --- a/libi2pd_client/BOB.cpp +++ b/libi2pd_client/BOB.cpp @@ -50,7 +50,7 @@ namespace client void BOBI2PInboundTunnel::ReceiveAddress (std::shared_ptr receiver) { receiver->socket->async_read_some (boost::asio::buffer( - receiver->buffer + receiver->bufferOffset, + receiver->buffer + receiver->bufferOffset, BOB_COMMAND_BUFFER_SIZE - receiver->bufferOffset), std::bind(&BOBI2PInboundTunnel::HandleReceivedAddress, this, std::placeholders::_1, std::placeholders::_2, receiver)); @@ -119,9 +119,9 @@ namespace client connection->I2PConnect (receiver->data, receiver->dataLen); } - BOBI2POutboundTunnel::BOBI2POutboundTunnel (const std::string& address, int port, + BOBI2POutboundTunnel::BOBI2POutboundTunnel (const std::string& outhost, int port, std::shared_ptr localDestination, bool quiet): BOBI2PTunnel (localDestination), - m_Endpoint (boost::asio::ip::address::from_string (address), port), m_IsQuiet (quiet) + m_Endpoint (boost::asio::ip::address::from_string (outhost), port), m_IsQuiet (quiet) { } @@ -154,9 +154,13 @@ namespace client } } - BOBDestination::BOBDestination (std::shared_ptr localDestination): + BOBDestination::BOBDestination (std::shared_ptr localDestination, + const std::string &nickname, const std::string &inhost, const std::string &outhost, + const int inport, const int outport, const bool quiet): m_LocalDestination (localDestination), - m_OutboundTunnel (nullptr), m_InboundTunnel (nullptr) + m_OutboundTunnel (nullptr), m_InboundTunnel (nullptr), + m_Nickname(nickname), m_InHost(inhost), m_OutHost(outhost), + m_InPort(inport), m_OutPort(outport), m_Quiet(quiet) { } @@ -195,15 +199,18 @@ namespace client } } - void BOBDestination::CreateInboundTunnel (int port, const std::string& address) + void BOBDestination::CreateInboundTunnel (int port, const std::string& inhost) { if (!m_InboundTunnel) { + // update inport and inhost (user can stop tunnel and change) + m_InPort = port; + m_InHost = inhost; boost::asio::ip::tcp::endpoint ep(boost::asio::ip::tcp::v4(), port); - if (!address.empty ()) + if (!inhost.empty ()) { boost::system::error_code ec; - auto addr = boost::asio::ip::address::from_string (address, ec); + auto addr = boost::asio::ip::address::from_string (inhost, ec); if (!ec) ep.address (addr); else @@ -213,15 +220,21 @@ namespace client } } - void BOBDestination::CreateOutboundTunnel (const std::string& address, int port, bool quiet) + void BOBDestination::CreateOutboundTunnel (const std::string& outhost, int port, bool quiet) { if (!m_OutboundTunnel) - m_OutboundTunnel = new BOBI2POutboundTunnel (address, port, m_LocalDestination, quiet); + { + // update outport and outhost (user can stop tunnel and change) + m_OutPort = port; + m_OutHost = outhost; + m_OutboundTunnel = new BOBI2POutboundTunnel (outhost, port, m_LocalDestination, quiet); + } } BOBCommandSession::BOBCommandSession (BOBCommandChannel& owner): m_Owner (owner), m_Socket (m_Owner.GetService ()), - m_ReceiveBufferOffset (0), m_IsOpen (true), m_IsQuiet (false), m_IsActive (false), + m_ReceiveBuffer(BOB_COMMAND_BUFFER_SIZE + 1), m_SendBuffer(BOB_COMMAND_BUFFER_SIZE + 1), + m_IsOpen (true), m_IsQuiet (false), m_IsActive (false), m_InPort (0), m_OutPort (0), m_CurrentDestination (nullptr) { } @@ -238,65 +251,48 @@ namespace client void BOBCommandSession::Receive () { - m_Socket.async_read_some (boost::asio::buffer(m_ReceiveBuffer + m_ReceiveBufferOffset, BOB_COMMAND_BUFFER_SIZE - m_ReceiveBufferOffset), - std::bind(&BOBCommandSession::HandleReceived, shared_from_this (), - std::placeholders::_1, std::placeholders::_2)); + boost::asio::async_read_until(m_Socket, m_ReceiveBuffer, '\n', + std::bind(&BOBCommandSession::HandleReceivedLine, shared_from_this(), + std::placeholders::_1, std::placeholders::_2)); } - - void BOBCommandSession::HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred) + + void BOBCommandSession::HandleReceivedLine(const boost::system::error_code& ecode, std::size_t bytes_transferred) { - if (ecode) + if(ecode) { - LogPrint (eLogError, "BOB: command channel read error: ", ecode.message ()); + LogPrint (eLogError, "BOB: command channel read error: ", ecode.message()); if (ecode != boost::asio::error::operation_aborted) Terminate (); } else { - size_t size = m_ReceiveBufferOffset + bytes_transferred; - m_ReceiveBuffer[size] = 0; - char * eol = strchr (m_ReceiveBuffer, '\n'); - if (eol) + std::string line; + + std::istream is(&m_ReceiveBuffer); + std::getline(is, line); + + std::string command, operand; + std::istringstream iss(line); + iss >> command >> operand; + + // process command + auto& handlers = m_Owner.GetCommandHandlers(); + auto it = handlers.find(command); + if(it != handlers.end()) { - *eol = 0; - char * operand = strchr (m_ReceiveBuffer, ' '); - if (operand) - { - *operand = 0; - operand++; - } - else - operand = eol; - // process command - auto& handlers = m_Owner.GetCommandHandlers (); - auto it = handlers.find (m_ReceiveBuffer); - if (it != handlers.end ()) - (this->*(it->second))(operand, eol - operand); - else - { - LogPrint (eLogError, "BOB: unknown command ", m_ReceiveBuffer); - SendReplyError ("unknown command"); - } - - m_ReceiveBufferOffset = size - (eol - m_ReceiveBuffer) - 1; - memmove (m_ReceiveBuffer, eol + 1, m_ReceiveBufferOffset); + (this->*(it->second))(operand.c_str(), operand.length()); } else { - if (size < BOB_COMMAND_BUFFER_SIZE) - m_ReceiveBufferOffset = size; - else - { - LogPrint (eLogError, "BOB: Malformed input of the command channel"); - Terminate (); - } + LogPrint (eLogError, "BOB: unknown command ", command.c_str()); + SendReplyError ("unknown command"); } } } - void BOBCommandSession::Send (size_t len) + void BOBCommandSession::Send () { - boost::asio::async_write (m_Socket, boost::asio::buffer (m_SendBuffer, len), + boost::asio::async_write (m_Socket, m_SendBuffer, boost::asio::transfer_all (), std::bind(&BOBCommandSession::HandleSent, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); @@ -305,7 +301,7 @@ namespace client void BOBCommandSession::HandleSent (const boost::system::error_code& ecode, std::size_t bytes_transferred) { if (ecode) - { + { LogPrint (eLogError, "BOB: command channel send error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate (); @@ -321,39 +317,65 @@ namespace client void BOBCommandSession::SendReplyOK (const char * msg) { -#ifdef _MSC_VER - size_t len = sprintf_s (m_SendBuffer, BOB_COMMAND_BUFFER_SIZE, BOB_REPLY_OK, msg); -#else - size_t len = snprintf (m_SendBuffer, BOB_COMMAND_BUFFER_SIZE, BOB_REPLY_OK, msg); -#endif - Send (len); + std::ostream os(&m_SendBuffer); + os << "OK"; + if(msg) + { + os << " " << msg; + } + os << std::endl; + Send (); } void BOBCommandSession::SendReplyError (const char * msg) { -#ifdef _MSC_VER - size_t len = sprintf_s (m_SendBuffer, BOB_COMMAND_BUFFER_SIZE, BOB_REPLY_ERROR, msg); -#else - size_t len = snprintf (m_SendBuffer, BOB_COMMAND_BUFFER_SIZE, BOB_REPLY_ERROR, msg); -#endif - Send (len); + std::ostream os(&m_SendBuffer); + os << "ERROR " << msg << std::endl; + Send (); } void BOBCommandSession::SendVersion () { - size_t len = strlen (BOB_VERSION); - memcpy (m_SendBuffer, BOB_VERSION, len); - Send (len); - } - - void BOBCommandSession::SendData (const char * nickname) - { -#ifdef _MSC_VER - size_t len = sprintf_s (m_SendBuffer, BOB_COMMAND_BUFFER_SIZE, BOB_DATA, nickname); -#else - size_t len = snprintf (m_SendBuffer, BOB_COMMAND_BUFFER_SIZE, BOB_DATA, nickname); -#endif - Send (len); + std::ostream os(&m_SendBuffer); + os << "BOB 00.00.10" << std::endl; + SendReplyOK(); + } + + void BOBCommandSession::SendData (const char * data) + { + std::ostream os(&m_SendBuffer); + os << "DATA " << data << std::endl; + } + + void BOBCommandSession::BuildStatusLine(bool currentTunnel, BOBDestination *dest, std::string &out) + { + // helper lambdas + const auto isset = [](const std::string &str) { return str.empty() ? "not_set" : str; }; // for inhost, outhost + const auto issetNum = [&isset](const int p) { return isset(p == 0 ? "" : std::to_string(p)); }; // for inport, outport + const auto destExists = [](const BOBDestination * const dest) { return dest != nullptr; }; + const auto destReady = [](const BOBDestination * const dest) { return dest->GetLocalDestination()->IsReady(); }; + const auto bool_str = [](const bool v) { return v ? "true" : "false"; }; // bool -> str + + // tunnel info + const std::string nickname = currentTunnel ? m_Nickname : dest->GetNickname(); + const bool quiet = currentTunnel ? m_IsQuiet : dest->GetQuiet(); + const std::string inhost = isset(currentTunnel ? m_InHost : dest->GetInHost()); + const std::string outhost = isset(currentTunnel ? m_OutHost : dest->GetOutHost()); + const std::string inport = issetNum(currentTunnel ? m_InPort : dest->GetInPort()); + const std::string outport = issetNum(currentTunnel ? m_OutPort : dest->GetOutPort()); + const bool keys = destExists(dest); // key must exist when destination is created + const bool starting = destExists(dest) && !destReady(dest); + const bool running = destExists(dest) && destReady(dest); + const bool stopping = false; + + // build line + std::stringstream ss; + ss << "NICKNAME: " << nickname << " " << "STARTING: " << bool_str(starting) << " " + << "RUNNING: " << bool_str(running) << " " << "STOPPING: " << bool_str(stopping) << " " + << "KEYS: " << bool_str(keys) << " " << "QUIET: " << bool_str(quiet) << " " + << "INPORT: " << inport << " " << "INHOST: " << inhost << " " + << "OUTPORT: " << outport << " " << "OUTHOST: " << outhost; + out = ss.str(); } void BOBCommandSession::ZapCommandHandler (const char * operand, size_t len) @@ -377,15 +399,50 @@ namespace client SendReplyError ("tunnel is active"); return; } + if (!m_Keys.GetPublic ()) // keys are set ? + { + SendReplyError("Keys must be set."); + return; + } + if (m_InPort == 0 + && m_OutHost.empty() && m_OutPort == 0) + { + SendReplyError("(inhost):inport or outhost:outport must be set."); + return; + } + if(!m_InHost.empty()) + { + // TODO: FIXME: temporary validation, until hostname support is added + boost::system::error_code ec; + boost::asio::ip::address::from_string(m_InHost, ec); + if (ec) + { + SendReplyError("inhost must be a valid IPv4 address."); + return; + } + } + if(!m_OutHost.empty()) + { + // TODO: FIXME: temporary validation, until hostname support is added + boost::system::error_code ec; + boost::asio::ip::address::from_string(m_OutHost, ec); + if (ec) + { + SendReplyError("outhost must be a IPv4 address."); + return; + } + } + if (!m_CurrentDestination) { - m_CurrentDestination = new BOBDestination (i2p::client::context.CreateNewLocalDestination (m_Keys, true, &m_Options)); + m_CurrentDestination = new BOBDestination (i2p::client::context.CreateNewLocalDestination (m_Keys, true, &m_Options), // deleted in clear command + m_Nickname, m_InHost, m_OutHost, m_InPort, m_OutPort, m_IsQuiet); m_Owner.AddDestination (m_Nickname, m_CurrentDestination); } if (m_InPort) - m_CurrentDestination->CreateInboundTunnel (m_InPort, m_Address); - if (m_OutPort && !m_Address.empty ()) - m_CurrentDestination->CreateOutboundTunnel (m_Address, m_OutPort, m_IsQuiet); + m_CurrentDestination->CreateInboundTunnel (m_InPort, m_InHost); + if (m_OutPort && !m_OutHost.empty ()) + m_CurrentDestination->CreateOutboundTunnel (m_OutHost, m_OutPort, m_IsQuiet); m_CurrentDestination->Start (); SendReplyOK ("Tunnel starting"); m_IsActive = true; @@ -496,7 +553,7 @@ namespace client void BOBCommandSession::OuthostCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: outhost ", operand); - m_Address = operand; + m_OutHost = operand; SendReplyOK ("outhost set"); } @@ -513,7 +570,7 @@ namespace client void BOBCommandSession::InhostCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: inhost ", operand); - m_Address = operand; + m_InHost = operand; SendReplyOK ("inhost set"); } @@ -591,9 +648,23 @@ namespace client void BOBCommandSession::ListCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: list"); + std::string statusLine; + bool sentCurrent = false; const auto& destinations = m_Owner.GetDestinations (); for (const auto& it: destinations) - SendData (it.first.c_str ()); + { + BuildStatusLine(false, it.second, statusLine); + SendData (statusLine.c_str()); + if(m_Nickname.compare(it.second->GetNickname()) == 0) + sentCurrent = true; + } + if(!sentCurrent && !m_Nickname.empty()) + { + // add the current tunnel to the list + BuildStatusLine(true, m_CurrentDestination, statusLine); + LogPrint(eLogError, statusLine); + SendData(statusLine.c_str()); + } SendReplyOK ("Listing done"); } @@ -619,34 +690,53 @@ namespace client void BOBCommandSession::StatusCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: status ", operand); + std::string statusLine; if (m_Nickname == operand) { - std::stringstream s; - s << "DATA"; s << " NICKNAME: "; s << m_Nickname; - if (m_CurrentDestination) + // check current tunnel + BuildStatusLine(true, nullptr, statusLine); + SendReplyOK(statusLine.c_str()); + } + else + { + // check other + std::string name = operand; + auto ptr = m_Owner.FindDestination(name); + if(ptr != nullptr) { - if (m_CurrentDestination->GetLocalDestination ()->IsReady ()) - s << " STARTING: false RUNNING: true STOPPING: false"; - else - s << " STARTING: true RUNNING: false STOPPING: false"; + BuildStatusLine(false, ptr, statusLine); + SendReplyOK(statusLine.c_str()); } else - s << " STARTING: false RUNNING: false STOPPING: false"; - s << " KEYS: true"; s << " QUIET: "; s << (m_IsQuiet ? "true":"false"); - if (m_InPort) { - s << " INPORT: " << m_InPort; - s << " INHOST: " << (m_Address.length () > 0 ? m_Address : "127.0.0.1"); + SendReplyError("no nickname has been set"); } - if (m_OutPort) + } + } + void BOBCommandSession::HelpCommandHandler (const char * operand, size_t len) + { + auto helpStrings = m_Owner.GetHelpStrings(); + if(len == 0) + { + std::stringstream ss; + ss << "COMMANDS:"; + for (auto const& x : helpStrings) { - s << " OUTPORT: " << m_OutPort; - s << " OUTHOST: " << (m_Address.length () > 0 ? m_Address : "127.0.0.1"); + ss << " " << x.first; } - SendReplyOK (s.str().c_str()); + const std::string &str = ss.str(); + SendReplyOK(str.c_str()); } else - SendReplyError ("no nickname has been set"); + { + auto it = helpStrings.find(operand); + if (it != helpStrings.end ()) + { + SendReplyOK(it->second.c_str()); + return; + } + SendReplyError("No such command"); + } } BOBCommandChannel::BOBCommandChannel (const std::string& address, int port): @@ -674,6 +764,29 @@ namespace client m_CommandHandlers[BOB_COMMAND_LIST] = &BOBCommandSession::ListCommandHandler; m_CommandHandlers[BOB_COMMAND_OPTION] = &BOBCommandSession::OptionCommandHandler; m_CommandHandlers[BOB_COMMAND_STATUS] = &BOBCommandSession::StatusCommandHandler; + m_CommandHandlers[BOB_COMMAND_HELP] = &BOBCommandSession::HelpCommandHandler; + // command -> help string + m_HelpStrings[BOB_COMMAND_ZAP] = BOB_HELP_ZAP; + m_HelpStrings[BOB_COMMAND_QUIT] = BOB_HELP_QUIT; + m_HelpStrings[BOB_COMMAND_START] = BOB_HELP_START; + m_HelpStrings[BOB_COMMAND_STOP] = BOB_HELP_STOP; + m_HelpStrings[BOB_COMMAND_SETNICK] = BOB_HELP_SETNICK; + m_HelpStrings[BOB_COMMAND_GETNICK] = BOB_HELP_GETNICK; + m_HelpStrings[BOB_COMMAND_NEWKEYS] = BOB_HELP_NEWKEYS; + m_HelpStrings[BOB_COMMAND_GETKEYS] = BOB_HELP_GETKEYS; + m_HelpStrings[BOB_COMMAND_SETKEYS] = BOB_HELP_SETKEYS; + m_HelpStrings[BOB_COMMAND_GETDEST] = BOB_HELP_GETDEST; + m_HelpStrings[BOB_COMMAND_OUTHOST] = BOB_HELP_OUTHOST; + m_HelpStrings[BOB_COMMAND_OUTPORT] = BOB_HELP_OUTPORT; + m_HelpStrings[BOB_COMMAND_INHOST] = BOB_HELP_INHOST; + m_HelpStrings[BOB_COMMAND_INPORT] = BOB_HELP_INPORT; + m_HelpStrings[BOB_COMMAND_QUIET] = BOB_HELP_QUIET; + m_HelpStrings[BOB_COMMAND_LOOKUP] = BOB_HELP_LOOKUP; + m_HelpStrings[BOB_COMMAND_CLEAR] = BOB_HELP_CLEAR; + m_HelpStrings[BOB_COMMAND_LIST] = BOB_HELP_LIST; + m_HelpStrings[BOB_COMMAND_OPTION] = BOB_HELP_OPTION; + m_HelpStrings[BOB_COMMAND_STATUS] = BOB_HELP_STATUS; + m_HelpStrings[BOB_COMMAND_HELP] = BOB_HELP_HELP; } BOBCommandChannel::~BOBCommandChannel () diff --git a/libi2pd_client/BOB.h b/libi2pd_client/BOB.h index a2a24164..d531a13d 100644 --- a/libi2pd_client/BOB.h +++ b/libi2pd_client/BOB.h @@ -37,11 +37,29 @@ namespace client const char BOB_COMMAND_LIST[] = "list"; const char BOB_COMMAND_OPTION[] = "option"; const char BOB_COMMAND_STATUS[] = "status"; - - const char BOB_VERSION[] = "BOB 00.00.10\nOK\n"; - const char BOB_REPLY_OK[] = "OK %s\n"; - const char BOB_REPLY_ERROR[] = "ERROR %s\n"; - const char BOB_DATA[] = "NICKNAME %s\n"; + const char BOB_COMMAND_HELP[] = "help"; + + const char BOB_HELP_ZAP[] = "zap - Shuts down BOB."; + const char BOB_HELP_QUIT[] = "quit - Quits this session with BOB."; + const char BOB_HELP_START[] = "start - Starts the current nicknamed tunnel."; + const char BOB_HELP_STOP[] = "stop - Stops the current nicknamed tunnel."; + const char BOB_HELP_SETNICK[] = "setnick - Creates a new nickname."; + const char BOB_HELP_GETNICK[] = "getnick - Sets the nickname from the database."; + const char BOB_HELP_NEWKEYS[] = "newkeys - Generate a new keypair for the current nickname."; + const char BOB_HELP_GETKEYS[] = "getkeys - Return the keypair for the current nickname."; + const char BOB_HELP_SETKEYS[] = "setkeys - Sets the keypair for the current nickname."; + const char BOB_HELP_GETDEST[] = "getdest - Return the destination for the current nickname."; + const char BOB_HELP_OUTHOST[] = "outhost - Set the outhound hostname or IP."; + const char BOB_HELP_OUTPORT[] = "outport - Set the outbound port that nickname contacts."; + const char BOB_HELP_INHOST[] = "inhost - Set the inbound hostname or IP."; + const char BOB_HELP_INPORT[] = "inport - Set the inbound port number nickname listens on."; + const char BOB_HELP_QUIET[] = "quiet - Wether to send the incoming destination."; + const char BOB_HELP_LOOKUP[] = "lookup - Look up an I2P hostname."; + const char BOB_HELP_CLEAR[] = "clear - Clear the current nickname out of the list."; + const char BOB_HELP_LIST[] = "list - List all tunnels."; + const char BOB_HELP_OPTION[] = "option = - Set an option. NOTE: Don't use any spaces."; + const char BOB_HELP_STATUS[] = "status - Display status of a nicknamed tunnel."; + const char BOB_HELP_HELP [] = "help - Get help on a command."; class BOBI2PTunnel: public I2PService { @@ -96,7 +114,7 @@ namespace client { public: - BOBI2POutboundTunnel (const std::string& address, int port, std::shared_ptr localDestination, bool quiet); + BOBI2POutboundTunnel (const std::string& outhost, int port, std::shared_ptr localDestination, bool quiet); void Start (); void Stop (); @@ -119,14 +137,22 @@ namespace client { public: - BOBDestination (std::shared_ptr localDestination); + BOBDestination (std::shared_ptr localDestination, + const std::string &nickname, const std::string &inhost, const std::string &outhost, + const int inport, const int outport, const bool quiet); ~BOBDestination (); void Start (); void Stop (); void StopTunnels (); - void CreateInboundTunnel (int port, const std::string& address); - void CreateOutboundTunnel (const std::string& address, int port, bool quiet); + void CreateInboundTunnel (int port, const std::string& inhost); + void CreateOutboundTunnel (const std::string& outhost, int port, bool quiet); + const std::string& GetNickname() const { return m_Nickname; } + const std::string& GetInHost() const { return m_InHost; } + const std::string& GetOutHost() const { return m_OutHost; } + int GetInPort() const { return m_InPort; } + int GetOutPort() const { return m_OutPort; } + bool GetQuiet() const { return m_Quiet; } const i2p::data::PrivateKeys& GetKeys () const { return m_LocalDestination->GetPrivateKeys (); }; std::shared_ptr GetLocalDestination () const { return m_LocalDestination; }; @@ -135,6 +161,11 @@ namespace client std::shared_ptr m_LocalDestination; BOBI2POutboundTunnel * m_OutboundTunnel; BOBI2PInboundTunnel * m_InboundTunnel; + + std::string m_Nickname; + std::string m_InHost, m_OutHost; + int m_InPort, m_OutPort; + bool m_Quiet; }; class BOBCommandChannel; @@ -170,26 +201,29 @@ namespace client void ListCommandHandler (const char * operand, size_t len); void OptionCommandHandler (const char * operand, size_t len); void StatusCommandHandler (const char * operand, size_t len); + void HelpCommandHandler (const char * operand, size_t len); private: void Receive (); + void HandleReceivedLine(const boost::system::error_code& ecode, std::size_t bytes_transferred); void HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred); - void Send (size_t len); + void Send (); void HandleSent (const boost::system::error_code& ecode, std::size_t bytes_transferred); - void SendReplyOK (const char * msg); + void SendReplyOK (const char * msg = nullptr); void SendReplyError (const char * msg); - void SendData (const char * nickname); + void SendData (const char * data); + + void BuildStatusLine(bool currentTunnel, BOBDestination *destination, std::string &out); private: BOBCommandChannel& m_Owner; boost::asio::ip::tcp::socket m_Socket; - char m_ReceiveBuffer[BOB_COMMAND_BUFFER_SIZE + 1], m_SendBuffer[BOB_COMMAND_BUFFER_SIZE + 1]; - size_t m_ReceiveBufferOffset; + boost::asio::streambuf m_ReceiveBuffer, m_SendBuffer; bool m_IsOpen, m_IsQuiet, m_IsActive; - std::string m_Nickname, m_Address; + std::string m_Nickname, m_InHost, m_OutHost; int m_InPort, m_OutPort; i2p::data::PrivateKeys m_Keys; std::map m_Options; @@ -226,10 +260,12 @@ namespace client boost::asio::ip::tcp::acceptor m_Acceptor; std::map m_Destinations; std::map m_CommandHandlers; + std::map m_HelpStrings; public: const decltype(m_CommandHandlers)& GetCommandHandlers () const { return m_CommandHandlers; }; + const decltype(m_HelpStrings)& GetHelpStrings () const { return m_HelpStrings; }; const decltype(m_Destinations)& GetDestinations () const { return m_Destinations; }; }; } diff --git a/libi2pd_client/HTTPProxy.cpp b/libi2pd_client/HTTPProxy.cpp index df4895a4..af4d2913 100644 --- a/libi2pd_client/HTTPProxy.cpp +++ b/libi2pd_client/HTTPProxy.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2019, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include #include #include @@ -5,6 +13,7 @@ #include #include #include +#include #include #include "I2PService.h" @@ -211,16 +220,28 @@ namespace proxy { void HTTPReqHandler::SanitizeHTTPRequest(i2p::http::HTTPReq & req) { /* drop common headers */ - req.RemoveHeader("Referrer"); req.RemoveHeader("Via"); req.RemoveHeader("From"); req.RemoveHeader("Forwarded"); req.RemoveHeader("Accept", "Accept-Encoding"); // Accept*, but Accept-Encoding /* drop proxy-disclosing headers */ req.RemoveHeader("X-Forwarded"); - req.RemoveHeader("Proxy-"); // Proxy-* + req.RemoveHeader("Proxy-"); // Proxy-* /* replace headers */ req.UpdateHeader("User-Agent", "MYOB/6.66 (AN/ON)"); + + /** + * according to i2p ticket #1862: + * leave Referrer if requested URL with same schema, host and port, + * otherwise, drop it. + */ + if(req.GetHeader("Referrer") != "") { + i2p::http::URL reqURL; reqURL.parse(req.uri); + i2p::http::URL refURL; refURL.parse(req.GetHeader("Referrer")); + if(!boost::iequals(reqURL.schema, refURL.schema) || !boost::iequals(reqURL.host, refURL.host) || reqURL.port != refURL.port) + req.RemoveHeader("Referrer"); + } + /* add headers */ /* close connection, if not Connection: (U|u)pgrade (for websocket) */ auto h = req.GetHeader ("Connection"); diff --git a/libi2pd_client/WebSocks.cpp b/libi2pd_client/WebSocks.cpp index ee64001d..c80feac8 100644 --- a/libi2pd_client/WebSocks.cpp +++ b/libi2pd_client/WebSocks.cpp @@ -81,11 +81,14 @@ namespace client void CreateStreamTo(const std::string & addr, int port, StreamConnectFunc complete) { auto & addressbook = i2p::client::context.GetAddressBook(); - i2p::data::IdentHash ident; - if(addressbook.GetIdentHash(addr, ident)) { - // address found - m_Dest->CreateStream(complete, ident, port); - } else { + auto a = addressbook.GetAddress (addr); + if (a && a->IsIdentHash ()) + { + // address found + m_Dest->CreateStream(complete, a->identHash, port); + } + else + { // not found complete(nullptr); } @@ -443,12 +446,12 @@ namespace client addr = line.substr(0, itr); port = std::atoi(line.substr(itr+1).c_str()); } - i2p::data::IdentHash ident; - if(addressbook.GetIdentHash(addr, ident)) + auto a = addressbook.GetAddress (addr); + if (a && a->IsIdentHash ()) { const char * data = payload.c_str() + idx + 1; size_t len = payload.size() - (1 + line.size()); - m_Datagram->SendDatagramTo((const uint8_t*)data, len, ident, m_RemotePort, port); + m_Datagram->SendDatagramTo((const uint8_t*)data, len, a->identHash, m_RemotePort, port); } } else { // wtf? diff --git a/qt/i2pd_qt/data/website.i2pd.i2pd.appdata.xml b/qt/i2pd_qt/data/website.i2pd.i2pd.appdata.xml index e9f35676..efb8a4d6 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/tests/Makefile b/tests/Makefile index 498cff17..2a12472a 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -1,6 +1,6 @@ CXXFLAGS += -Wall -Wextra -pedantic -O0 -g -std=c++11 -D_GLIBCXX_USE_NANOSLEEP=1 -I../libi2pd/ -pthread -Wl,--unresolved-symbols=ignore-in-object-files -TESTS = test-gost test-gost-sig test-base-64 test-x25519 test-aeadchacha20poly1305 +TESTS = test-gost test-gost-sig test-base-64 test-x25519 test-aeadchacha20poly1305 test-blinding all: $(TESTS) run @@ -22,6 +22,9 @@ test-x25519: ../libi2pd/Ed25519.cpp ../libi2pd/I2PEndian.cpp ../libi2pd/Log.cpp test-aeadchacha20poly1305: ../libi2pd/Crypto.cpp ../libi2pd/ChaCha20.cpp ../libi2pd/Poly1305.cpp test-aeadchacha20poly1305.cpp $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) -o $@ $^ -lcrypto -lssl -lboost_system +test-blinding: ../libi2pd/Crypto.cpp ../libi2pd/Blinding.cpp ../libi2pd/Ed25519.cpp ../libi2pd/I2PEndian.cpp ../libi2pd/Log.cpp ../libi2pd/util.cpp ../libi2pd/Identity.cpp ../libi2pd/Signature.cpp ../libi2pd/Timestamp.cpp test-blinding.cpp + $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) -o $@ $^ -lcrypto -lssl -lboost_system + run: $(TESTS) @for TEST in $(TESTS); do ./$$TEST ; done diff --git a/tests/test-blinding.cpp b/tests/test-blinding.cpp new file mode 100644 index 00000000..5490acd4 --- /dev/null +++ b/tests/test-blinding.cpp @@ -0,0 +1,43 @@ +#include +#include +#include +#include "Blinding.h" +#include "Identity.h" +#include "Timestamp.h" + +using namespace i2p::data; +using namespace i2p::util; +using namespace i2p::crypto; + +void BlindTest (SigningKeyType sigType) +{ + auto keys = PrivateKeys::CreateRandomKeys (sigType); + BlindedPublicKey blindedKey (keys.GetPublic ()); + auto timestamp = GetSecondsSinceEpoch (); + char date[9]; + GetDateString (timestamp, date); + uint8_t blindedPriv[64], blindedPub[128]; + auto publicKeyLen = blindedKey.BlindPrivateKey (keys.GetSigningPrivateKey (), date, blindedPriv, blindedPub); + uint8_t blindedPub1[128]; + blindedKey.GetBlindedKey (date, blindedPub1); + // check if public key produced from private blinded key matches blided public key + assert (!memcmp (blindedPub, blindedPub1, publicKeyLen)); + // try to sign and verify + std::unique_ptr blindedSigner (PrivateKeys::CreateSigner (sigType, blindedPriv)); + uint8_t buf[100], signature[128]; + memset (buf, 1, 100); + blindedSigner->Sign (buf, 100, signature); + std::unique_ptr blindedVerifier (IdentityEx::CreateVerifier (sigType)); + blindedVerifier->SetPublicKey (blindedPub1); + assert (blindedVerifier->Verify (buf, 100, signature)); +} + +int main () +{ + // RedDSA test + BlindTest (SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519); + // P256 test + BlindTest (SIGNING_KEY_TYPE_ECDSA_SHA256_P256); + // P384 test + BlindTest (SIGNING_KEY_TYPE_ECDSA_SHA384_P384); +} diff --git a/tests/test-gost-sig.cpp b/tests/test-gost-sig.cpp index 55cc2d1b..1348af54 100644 --- a/tests/test-gost-sig.cpp +++ b/tests/test-gost-sig.cpp @@ -21,12 +21,14 @@ int main () i2p::crypto::CreateGOSTR3410RandomKeys (i2p::crypto::eGOSTR3410TC26A512, priv, pub); i2p::crypto::GOSTR3410_512_Signer signer (i2p::crypto::eGOSTR3410TC26A512, priv); signer.Sign (example2, 72, signature); - i2p::crypto::GOSTR3410_512_Verifier verifier (i2p::crypto::eGOSTR3410TC26A512, pub); + i2p::crypto::GOSTR3410_512_Verifier verifier (i2p::crypto::eGOSTR3410TC26A512); + verifier.SetPublicKey (pub); assert (verifier.Verify (example2, 72, signature)); i2p::crypto::CreateGOSTR3410RandomKeys (i2p::crypto::eGOSTR3410CryptoProA, priv, pub); i2p::crypto::GOSTR3410_256_Signer signer1 (i2p::crypto::eGOSTR3410CryptoProA, priv); signer1.Sign (example2, 72, signature); - i2p::crypto::GOSTR3410_256_Verifier verifier1 (i2p::crypto::eGOSTR3410CryptoProA, pub); + i2p::crypto::GOSTR3410_256_Verifier verifier1 (i2p::crypto::eGOSTR3410CryptoProA); + verifier1.SetPublicKey (pub); assert (verifier1.Verify (example2, 72, signature)); }