Browse Source

Merge pull request #1362 from PurpleI2P/openssl

2.26.0
pull/1777/head
orignal 6 years ago committed by GitHub
parent
commit
f0725c9b40
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 15
      ChangeLog
  2. 2
      Win32/installer.iss
  3. 4
      android/build.gradle
  4. 4
      appveyor.yml
  5. 2
      build/CMakeLists.txt
  6. 34
      contrib/certificates/reseed/atomike_at_mail.i2p.crt
  7. 5
      contrib/rpm/i2pd-git.spec
  8. 5
      contrib/rpm/i2pd.spec
  9. 8
      daemon/HTTPServer.cpp
  10. 6
      debian/changelog
  11. 301
      libi2pd/Blinding.cpp
  12. 45
      libi2pd/Blinding.h
  13. 6
      libi2pd/Config.cpp
  14. 1
      libi2pd/Destination.h
  15. 872
      libi2pd/HTTP.cpp
  16. 284
      libi2pd/HTTP.h
  17. 234
      libi2pd/LeaseSet.cpp
  18. 36
      libi2pd/LeaseSet.h
  19. 2
      libi2pd/NTCP2.cpp
  20. 2
      libi2pd/NTCPSession.cpp
  21. 17
      libi2pd/NetDb.cpp
  22. 1
      libi2pd/NetDb.hpp
  23. 181
      libi2pd/RouterContext.cpp
  24. 5
      libi2pd/RouterContext.h
  25. 54
      libi2pd/RouterInfo.cpp
  26. 5
      libi2pd/RouterInfo.h
  27. 14
      libi2pd/Transports.cpp
  28. 2
      libi2pd/version.h
  29. 311
      libi2pd_client/BOB.cpp
  30. 66
      libi2pd_client/BOB.h
  31. 25
      libi2pd_client/HTTPProxy.cpp
  32. 17
      libi2pd_client/WebSocks.cpp
  33. 1
      qt/i2pd_qt/data/website.i2pd.i2pd.appdata.xml
  34. 5
      tests/Makefile
  35. 43
      tests/test-blinding.cpp
  36. 6
      tests/test-gost-sig.cpp

15
ChangeLog

@ -1,6 +1,21 @@ @@ -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

2
Win32/installer.iss

@ -1,5 +1,5 @@ @@ -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]

4
android/build.gradle

@ -29,8 +29,8 @@ android { @@ -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'

4
appveyor.yml

@ -1,4 +1,4 @@ @@ -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: @@ -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"

2
build/CMakeLists.txt

@ -81,6 +81,7 @@ set (LIBI2PD_SRC @@ -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) @@ -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)

34
contrib/certificates/reseed/atomike_at_mail.i2p.crt

@ -1,34 +0,0 @@ @@ -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-----

5
contrib/rpm/i2pd-git.spec

@ -1,7 +1,7 @@ @@ -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 || \ @@ -110,6 +110,9 @@ getent passwd i2pd >/dev/null || \
%changelog
* Fri Jun 7 2019 orignal <i2porignal@yandex.ru> - 2.26.0
- update to 2.26.0
* Thu May 9 2019 orignal <i2porignal@yandex.ru> - 2.25.0
- update to 2.25.0

5
contrib/rpm/i2pd.spec

@ -1,5 +1,5 @@ @@ -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 || \ @@ -108,6 +108,9 @@ getent passwd i2pd >/dev/null || \
%changelog
* Fri Jun 7 2019 orignal <i2porignal@yandex.ru> - 2.26.0
- update to 2.26.0
* Thu May 9 2019 orignal <i2porignal@yandex.ru> - 2.25.0
- update to 2.25.0

8
daemon/HTTPServer.cpp

@ -354,6 +354,14 @@ namespace http { @@ -354,6 +354,14 @@ namespace http {
{
s << "<b>Base64:</b><br>\r\n<textarea readonly=\"readonly\" cols=\"64\" rows=\"11\" wrap=\"on\">";
s << dest->GetIdentity ()->ToBase64 () << "</textarea><br>\r\n<br>\r\n";
if (dest->IsEncryptedLeaseSet ())
{
i2p::data::BlindedPublicKey blinded (dest->GetIdentity ());
s << "<div class='slide'><label for='slide-b33'><b>Encrypted B33 address:</b></label>\r\n<input type='checkbox' id='slide-b33'/>\r\n<p class='content'>\r\n";
s << blinded.ToB33 () << ".b32.i2p<br>\r\n";
s << "</p>\r\n</div>\r\n";
}
if(dest->GetNumRemoteLeaseSets())
{
s << "<div class='slide'><label for='slide-lease'><b>LeaseSets:</b> <i>" << dest->GetNumRemoteLeaseSets () << "</i></label>\r\n<input type='checkbox' id='slide-lease'/>\r\n<p class='content'>\r\n";

6
debian/changelog vendored

@ -1,3 +1,9 @@ @@ -1,3 +1,9 @@
i2pd (2.26.0-1) unstable; urgency=medium
* updated to version 2.26.0
-- orignal <orignal@i2pmail.org> 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

301
libi2pd/Blinding.cpp

@ -0,0 +1,301 @@ @@ -0,0 +1,301 @@
#include <zlib.h> // for crc32
#include <openssl/sha.h>
#include <openssl/hmac.h>
#include <openssl/ec.h>
#include <openssl/bn.h>
#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<typename Fn, typename...Args>
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>(args)...);
EC_GROUP_free (group);
}
return publicKeyLength;
}
BlindedPublicKey::BlindedPublicKey (std::shared_ptr<const IdentityEx> 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<i2p::crypto::Verifier> 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<std::pair<const uint8_t *, size_t> >& 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;
}
}
}

45
libi2pd/Blinding.h

@ -0,0 +1,45 @@ @@ -0,0 +1,45 @@
#ifndef BLINDING_H__
#define BLINDING_H__
#include <inttypes.h>
#include <string>
#include <vector>
#include "Identity.h"
namespace i2p
{
namespace data
{
class BlindedPublicKey // for encrypted LS2
{
public:
BlindedPublicKey (std::shared_ptr<const IdentityEx> 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<std::pair<const uint8_t *, size_t> >& bufs, uint8_t * hash) const;
private:
std::vector<uint8_t> m_PublicKey;
i2p::data::SigningKeyType m_SigType, m_BlindedSigType;
};
}
}
#endif

6
libi2pd/Config.cpp

@ -58,7 +58,7 @@ namespace config { @@ -58,7 +58,7 @@ namespace config {
("floodfill", bool_switch()->default_value(false), "Router will be floodfill (default: disabled)")
("bandwidth", value<std::string>()->default_value(""), "Bandwidth limit: integer in KBps or letters: L (32), O (256), P (2048), X (>9000)")
("share", value<int>()->default_value(100), "Limit of transit traffic from max bandwidth in percents. (default: 100)")
("ntcp", value<bool>()->default_value(true), "Enable NTCP transport (default: enabled)")
("ntcp", value<bool>()->default_value(false), "Enable NTCP transport (default: disabled)")
("ssu", value<bool>()->default_value(true), "Enable SSU transport (default: enabled)")
("ntcpproxy", value<std::string>()->default_value(""), "Proxy URL for NTCP transport")
#ifdef _WIN32
@ -195,10 +195,8 @@ namespace config { @@ -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 { @@ -237,7 +235,7 @@ namespace config {
options_description ntcp2("NTCP2 Options");
ntcp2.add_options()
("ntcp2.enabled", value<bool>()->default_value(true), "Enable NTCP2 (default: enabled)")
("ntcp2.published", value<bool>()->default_value(false), "Publish NTCP2 (default: disabled)")
("ntcp2.published", value<bool>()->default_value(true), "Publish NTCP2 (default: enabled)")
("ntcp2.port", value<uint16_t>()->default_value(0), "Port to listen for incoming NTCP2 connections (default: auto)")
("ntcp2.addressv6", value<std::string>()->default_value("::"), "Address to bind NTCP2 on")
;

1
libi2pd/Destination.h

@ -181,6 +181,7 @@ namespace client @@ -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

872
libi2pd/HTTP.cpp

@ -1,5 +1,5 @@ @@ -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 @@ @@ -15,282 +15,283 @@
namespace i2p {
namespace http {
const std::vector<std::string> HTTP_METHODS = {
"GET", "HEAD", "POST", "PUT", "PATCH",
"DELETE", "OPTIONS", "CONNECT"
};
const std::vector<std::string> HTTP_VERSIONS = {
"HTTP/1.0", "HTTP/1.1"
};
const std::vector<const char *> weekdays = {
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
};
const std::vector<const char *> 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<std::string> &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<std::string, std::string> 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
const std::vector<std::string> HTTP_METHODS = {
"GET", "HEAD", "POST", "PUT", "PATCH",
"DELETE", "OPTIONS", "CONNECT", "PROPFIND"
};
const std::vector<std::string> HTTP_VERSIONS = {
"HTTP/1.0", "HTTP/1.1"
};
const std::vector<const char *> weekdays = {
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
};
const std::vector<const char *> 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<std::string> &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<std::string, std::string> parse_header_line(const std::string& line)
{
while ((pos + len) < max && isspace(line.at(pos + len)))
len++;
if (len == 1) return std::make_pair("", ""); // no following space, but something else
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<std::string, std::string> & params) {
std::vector<std::string> 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<std::string, std::string>(it.substr(0, eq), it.substr(eq + 1));
params.insert(e);
} else {
auto e = std::pair<std::string, std::string>(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<std::string, std::string> & params) {
std::vector<std::string> 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<std::string, std::string>(it.substr(0, eq), it.substr(eq + 1));
params.insert(e);
} else {
auto e = std::pair<std::string, std::string>(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<std::string, std::string>(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<std::string> 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<std::string, std::string>(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<std::string> 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 { @@ -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 { @@ -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<std::string> 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<std::string> 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

284
libi2pd/HTTP.h

@ -1,5 +1,5 @@ @@ -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 @@ -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<std::string> HTTP_METHODS; /**< list of valid HTTP methods */
extern const std::vector<std::string> 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<std::string, std::string> & 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<std::string, std::string> 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<std::pair<std::string, std::string> > 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<std::string> HTTP_METHODS; /**< list of valid HTTP methods */
extern const std::vector<std::string> 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<std::string, std::string> & 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<std::string, std::string> 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<std::pair<std::string, std::string> > 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

234
libi2pd/LeaseSet.cpp

@ -1,11 +1,8 @@ @@ -1,11 +1,8 @@
#include <string.h>
#include <openssl/sha.h>
#include <openssl/hmac.h>
#include <zlib.h> // 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 @@ -254,159 +251,20 @@ namespace data
memcpy (m_Buffer, buf, len);
}
BlindedPublicKey::BlindedPublicKey (std::shared_ptr<const IdentityEx> 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<i2p::crypto::Verifier> 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<std::pair<const uint8_t *, size_t> >& 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<const BlindedPublicKey> key):
LeaseSet2::LeaseSet2 (const uint8_t * buf, size_t len, std::shared_ptr<const BlindedPublicKey> 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 @@ -563,7 +421,7 @@ namespace data
return offset;
}
void LeaseSet2::ReadFromBufferEncrypted (const uint8_t * buf, size_t len, std::shared_ptr<const BlindedPublicKey> key)
void LeaseSet2::ReadFromBufferEncrypted (const uint8_t * buf, size_t len, std::shared_ptr<const BlindedPublicKey> key, const uint8_t * secret)
{
size_t offset = 0;
// blinded key
@ -606,19 +464,24 @@ namespace data @@ -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<uint8_t> 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 @@ -635,17 +498,26 @@ namespace data
std::vector<uint8_t> 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<uint8_t> 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 @@ -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 @@ -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<i2p::crypto::Signer> 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;

36
libi2pd/LeaseSet.h

@ -9,6 +9,7 @@ @@ -9,6 +9,7 @@
#include "Identity.h"
#include "Timestamp.h"
#include "I2PEndian.h"
#include "Blinding.h"
namespace i2p
{
@ -129,42 +130,12 @@ namespace data @@ -129,42 +130,12 @@ namespace data
const uint16_t LEASESET2_FLAG_OFFLINE_KEYS = 0x0001;
class BlindedPublicKey // for encrypted LS2
{
public:
BlindedPublicKey (std::shared_ptr<const IdentityEx> 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<std::pair<const uint8_t *, size_t> >& bufs, uint8_t * hash) const;
private:
std::vector<uint8_t> 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<const BlindedPublicKey> key); // store type 5, called from local netdb only
LeaseSet2 (const uint8_t * buf, size_t len, std::shared_ptr<const BlindedPublicKey> 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 @@ -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<const BlindedPublicKey> key);
void ReadFromBufferEncrypted (const uint8_t * buf, size_t len, std::shared_ptr<const BlindedPublicKey> 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 @@ -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:

2
libi2pd/NTCP2.cpp

@ -1304,8 +1304,6 @@ namespace transport @@ -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 ();
}
}

2
libi2pd/NTCPSession.cpp

@ -1079,8 +1079,6 @@ namespace transport @@ -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 ();
}
}

17
libi2pd/NetDb.cpp

@ -698,14 +698,14 @@ namespace data @@ -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 @@ -1076,6 +1076,15 @@ namespace data
});
}
std::shared_ptr<const RouterInfo> NetDb::GetRandomSSUV6Router () const
{
return GetRandomRouter (
[](std::shared_ptr<const RouterInfo> router)->bool
{
return !router->IsHidden () && router->IsSSUV6 ();
});
}
std::shared_ptr<const RouterInfo> NetDb::GetRandomIntroducer () const
{
return GetRandomRouter (

1
libi2pd/NetDb.hpp

@ -72,6 +72,7 @@ namespace data @@ -72,6 +72,7 @@ namespace data
std::shared_ptr<const RouterInfo> GetRandomRouter (std::shared_ptr<const RouterInfo> compatibleWith) const;
std::shared_ptr<const RouterInfo> GetHighBandwidthRandomRouter (std::shared_ptr<const RouterInfo> compatibleWith) const;
std::shared_ptr<const RouterInfo> GetRandomPeerTestRouter (bool v4only = true) const;
std::shared_ptr<const RouterInfo> GetRandomSSUV6Router () const; // TODO: change to v6 peer test later
std::shared_ptr<const RouterInfo> GetRandomIntroducer () const;
std::shared_ptr<const RouterInfo> GetClosestFloodfill (const IdentHash& destination, const std::set<IdentHash>& excluded, bool closeThanUsOnly = false) const;
std::vector<IdentHash> GetClosestFloodfills (const IdentHash& destination, size_t num,

181
libi2pd/RouterContext.cpp

@ -110,7 +110,17 @@ namespace i2p @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 ();
}

5
libi2pd/RouterContext.h

@ -79,7 +79,7 @@ namespace i2p @@ -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 @@ -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

54
libi2pd/RouterInfo.cpp

@ -38,10 +38,19 @@ namespace data @@ -38,10 +38,19 @@ namespace data
m_IsUpdated (true), m_IsUnreachable (false), m_SupportedTransports (0), m_Caps (0)
{
m_Addresses = boost::make_shared<Addresses>(); // 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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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

5
libi2pd/RouterInfo.h

@ -38,7 +38,7 @@ namespace data @@ -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 @@ -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 @@ -196,7 +197,7 @@ namespace data
std::shared_ptr<RouterProfile> 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;

14
libi2pd/Transports.cpp

@ -552,6 +552,20 @@ namespace transport @@ -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");

2
libi2pd/version.h

@ -7,7 +7,7 @@ @@ -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)

311
libi2pd_client/BOB.cpp

@ -50,7 +50,7 @@ namespace client @@ -50,7 +50,7 @@ namespace client
void BOBI2PInboundTunnel::ReceiveAddress (std::shared_ptr<AddressReceiver> 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 @@ -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<ClientDestination> 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 @@ -154,9 +154,13 @@ namespace client
}
}
BOBDestination::BOBDestination (std::shared_ptr<ClientDestination> localDestination):
BOBDestination::BOBDestination (std::shared_ptr<ClientDestination> 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 @@ -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 @@ -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 @@ -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)
{
*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");
}
std::string line;
std::istream is(&m_ReceiveBuffer);
std::getline(is, line);
m_ReceiveBufferOffset = size - (eol - m_ReceiveBuffer) - 1;
memmove (m_ReceiveBuffer, eol + 1, m_ReceiveBufferOffset);
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())
{
(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 @@ -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 @@ -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);
std::ostream os(&m_SendBuffer);
os << "BOB 00.00.10" << std::endl;
SendReplyOK();
}
void BOBCommandSession::SendData (const char * nickname)
void BOBCommandSession::SendData (const char * data)
{
#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 << "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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 ()

66
libi2pd_client/BOB.h

@ -37,11 +37,29 @@ namespace client @@ -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 <NICKNAME> - Creates a new nickname.";
const char BOB_HELP_GETNICK[] = "getnick <TUNNELNAME> - 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 <BASE64_KEYPAIR> - 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 <HOSTNAME|IP> - Set the outhound hostname or IP.";
const char BOB_HELP_OUTPORT[] = "outport <PORT_NUMBER> - Set the outbound port that nickname contacts.";
const char BOB_HELP_INHOST[] = "inhost <HOSTNAME|IP> - Set the inbound hostname or IP.";
const char BOB_HELP_INPORT[] = "inport <PORT_NUMBER> - Set the inbound port number nickname listens on.";
const char BOB_HELP_QUIET[] = "quiet <True|False> - Wether to send the incoming destination.";
const char BOB_HELP_LOOKUP[] = "lookup <I2P_HOSTNAME> - 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 <KEY>=<VALUE> - Set an option. NOTE: Don't use any spaces.";
const char BOB_HELP_STATUS[] = "status <NICKNAME> - Display status of a nicknamed tunnel.";
const char BOB_HELP_HELP [] = "help <COMMAND> - Get help on a command.";
class BOBI2PTunnel: public I2PService
{
@ -96,7 +114,7 @@ namespace client @@ -96,7 +114,7 @@ namespace client
{
public:
BOBI2POutboundTunnel (const std::string& address, int port, std::shared_ptr<ClientDestination> localDestination, bool quiet);
BOBI2POutboundTunnel (const std::string& outhost, int port, std::shared_ptr<ClientDestination> localDestination, bool quiet);
void Start ();
void Stop ();
@ -119,14 +137,22 @@ namespace client @@ -119,14 +137,22 @@ namespace client
{
public:
BOBDestination (std::shared_ptr<ClientDestination> localDestination);
BOBDestination (std::shared_ptr<ClientDestination> 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<ClientDestination> GetLocalDestination () const { return m_LocalDestination; };
@ -135,6 +161,11 @@ namespace client @@ -135,6 +161,11 @@ namespace client
std::shared_ptr<ClientDestination> 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 @@ -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<std::string, std::string> m_Options;
@ -226,10 +260,12 @@ namespace client @@ -226,10 +260,12 @@ namespace client
boost::asio::ip::tcp::acceptor m_Acceptor;
std::map<std::string, BOBDestination *> m_Destinations;
std::map<std::string, BOBCommandHandler> m_CommandHandlers;
std::map<std::string, std::string> 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; };
};
}

25
libi2pd_client/HTTPProxy.cpp

@ -1,3 +1,11 @@ @@ -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 <cstring>
#include <cassert>
#include <string>
@ -5,6 +13,7 @@ @@ -5,6 +13,7 @@
#include <memory>
#include <set>
#include <boost/asio.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <mutex>
#include "I2PService.h"
@ -211,16 +220,28 @@ namespace proxy { @@ -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");

17
libi2pd_client/WebSocks.cpp

@ -81,11 +81,14 @@ namespace client @@ -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)) {
auto a = addressbook.GetAddress (addr);
if (a && a->IsIdentHash ())
{
// address found
m_Dest->CreateStream(complete, ident, port);
} else {
m_Dest->CreateStream(complete, a->identHash, port);
}
else
{
// not found
complete(nullptr);
}
@ -443,12 +446,12 @@ namespace client @@ -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?

1
qt/i2pd_qt/data/website.i2pd.i2pd.appdata.xml

@ -35,6 +35,7 @@ @@ -35,6 +35,7 @@
<translation type="qt" />
<releases>
<release version="2.26.0" date="2019-06-07" />
<release version="2.25.0" date="2019-05-09" />
<release version="2.24.0" date="2019-03-21" />
<release version="2.23.0" date="2019-01-21" />

5
tests/Makefile

@ -1,6 +1,6 @@ @@ -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 @@ -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

43
tests/test-blinding.cpp

@ -0,0 +1,43 @@ @@ -0,0 +1,43 @@
#include <cassert>
#include <memory>
#include <string.h>
#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<Signer> blindedSigner (PrivateKeys::CreateSigner (sigType, blindedPriv));
uint8_t buf[100], signature[128];
memset (buf, 1, 100);
blindedSigner->Sign (buf, 100, signature);
std::unique_ptr<Verifier> 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);
}

6
tests/test-gost-sig.cpp

@ -21,12 +21,14 @@ int main () @@ -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));
}

Loading…
Cancel
Save