Compare commits

..

No commits in common. "openssl" and "2.56.0" have entirely different histories.

88 changed files with 1168 additions and 2439 deletions

View file

@ -108,7 +108,7 @@ jobs:
uses: Noelware/docker-manifest-action@master uses: Noelware/docker-manifest-action@master
with: with:
inputs: purplei2p/i2pd:latest inputs: purplei2p/i2pd:latest
tags: purplei2p/i2pd:latest-amd64,purplei2p/i2pd:latest-i386,purplei2p/i2pd:latest-arm64,purplei2p/i2pd:latest-armv7 images: purplei2p/i2pd:latest-amd64,purplei2p/i2pd:latest-i386,purplei2p/i2pd:latest-arm64,purplei2p/i2pd:latest-armv7
push: true push: true
- name: Create and push latest manifest image to GHCR - name: Create and push latest manifest image to GHCR
@ -116,7 +116,7 @@ jobs:
uses: Noelware/docker-manifest-action@master uses: Noelware/docker-manifest-action@master
with: with:
inputs: ghcr.io/purplei2p/i2pd:latest inputs: ghcr.io/purplei2p/i2pd:latest
tags: ghcr.io/purplei2p/i2pd:latest-amd64,ghcr.io/purplei2p/i2pd:latest-i386,ghcr.io/purplei2p/i2pd:latest-arm64,ghcr.io/purplei2p/i2pd:latest-armv7 images: ghcr.io/purplei2p/i2pd:latest-amd64,ghcr.io/purplei2p/i2pd:latest-i386,ghcr.io/purplei2p/i2pd:latest-arm64,ghcr.io/purplei2p/i2pd:latest-armv7
push: true push: true
- name: Store release version to env - name: Store release version to env
@ -128,7 +128,7 @@ jobs:
uses: Noelware/docker-manifest-action@master uses: Noelware/docker-manifest-action@master
with: with:
inputs: purplei2p/i2pd:latest,purplei2p/i2pd:latest-release,purplei2p/i2pd:release-${{ env.RELEASE_VERSION }} inputs: purplei2p/i2pd:latest,purplei2p/i2pd:latest-release,purplei2p/i2pd:release-${{ env.RELEASE_VERSION }}
tags: purplei2p/i2pd:latest-amd64,purplei2p/i2pd:latest-i386,purplei2p/i2pd:latest-arm64,purplei2p/i2pd:latest-armv7 images: purplei2p/i2pd:latest-amd64,purplei2p/i2pd:latest-i386,purplei2p/i2pd:latest-arm64,purplei2p/i2pd:latest-armv7
push: true push: true
- name: Create and push release manifest to GHCR - name: Create and push release manifest to GHCR
@ -136,5 +136,5 @@ jobs:
uses: Noelware/docker-manifest-action@master uses: Noelware/docker-manifest-action@master
with: with:
inputs: ghcr.io/purplei2p/i2pd:latest,ghcr.io/purplei2p/i2pd:latest-release,ghcr.io/purplei2p/i2pd:release-${{ env.RELEASE_VERSION }} inputs: ghcr.io/purplei2p/i2pd:latest,ghcr.io/purplei2p/i2pd:latest-release,ghcr.io/purplei2p/i2pd:release-${{ env.RELEASE_VERSION }}
tags: ghcr.io/purplei2p/i2pd:latest-amd64,ghcr.io/purplei2p/i2pd:latest-i386,ghcr.io/purplei2p/i2pd:latest-arm64,ghcr.io/purplei2p/i2pd:latest-armv7 images: ghcr.io/purplei2p/i2pd:latest-amd64,ghcr.io/purplei2p/i2pd:latest-i386,ghcr.io/purplei2p/i2pd:latest-arm64,ghcr.io/purplei2p/i2pd:latest-armv7
push: true push: true

View file

@ -1,4 +1,4 @@
Copyright (c) 2013-2025, The PurpleI2P Project Copyright (c) 2013-2023, The PurpleI2P Project
All rights reserved. All rights reserved.

View file

@ -69,9 +69,6 @@ else ifneq (, $(findstring freebsd, $(SYS))$(findstring openbsd, $(SYS)))
else ifneq (, $(findstring haiku, $(SYS))) else ifneq (, $(findstring haiku, $(SYS)))
DAEMON_SRC += $(DAEMON_SRC_DIR)/UnixDaemon.cpp DAEMON_SRC += $(DAEMON_SRC_DIR)/UnixDaemon.cpp
include Makefile.haiku include Makefile.haiku
else ifneq (, $(findstring solaris, $(SYS)))
DAEMON_SRC += $(DAEMON_SRC_DIR)/UnixDaemon.cpp
include Makefile.solaris
else # not supported else # not supported
$(error Not supported platform) $(error Not supported platform)
endif endif

View file

@ -1,12 +1,8 @@
ifeq ($(shell $(CXX) -dumpmachine | cut -c 1-4), i586)
CXX = g++-x86
else
CXX = g++ CXX = g++
endif CXXFLAGS := -Wall -std=c++17
CXXFLAGS := -Wall -std=c++20
INCFLAGS = -I/system/develop/headers INCFLAGS = -I/system/develop/headers
DEFINES = -D_DEFAULT_SOURCE -D_GNU_SOURCE DEFINES = -D_DEFAULT_SOURCE -D_GNU_SOURCE
LDLIBS = -lbe -lbsd -lnetwork -lz -lssl -lcrypto -lboost_program_options -lpthread LDLIBS = -lbe -lbsd -lnetwork -lz -lssl -lcrypto -lboost_system -lboost_program_options -lpthread
ifeq ($(USE_UPNP),yes) ifeq ($(USE_UPNP),yes)
DEFINES += -DUSE_UPNP DEFINES += -DUSE_UPNP

View file

@ -1,9 +0,0 @@
CXX = g++
INCFLAGS = -I/usr/openssl/3/include
CXXFLAGS := -Wall -std=c++20
LDLIBS = -L/usr/openssl/3/lib/64 -lssl -lcrypto -lboost_program_options -lz -lpthread -lsocket
ifeq ($(USE_UPNP),yes)
DEFINES += -DUSE_UPNP
LDLIBS += -lminiupnpc
endif

View file

@ -1,8 +1,7 @@
[Unit] [Unit]
Description=I2P Router written in C++ Description=I2P Router written in C++
Documentation=man:i2pd(1) https://i2pd.readthedocs.io/en/latest/ Documentation=man:i2pd(1) https://i2pd.readthedocs.io/en/latest/
Wants=network.target After=network.target
After=network.target network-online.target
[Service] [Service]
User=i2pd User=i2pd

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -132,8 +132,7 @@ namespace http {
static void ShowTunnelDetails (std::stringstream& s, enum i2p::tunnel::TunnelState eState, bool explr, int bytes) static void ShowTunnelDetails (std::stringstream& s, enum i2p::tunnel::TunnelState eState, bool explr, int bytes)
{ {
std::string state; std::string state, stateText;
std::string_view stateText;
switch (eState) switch (eState)
{ {
case i2p::tunnel::eTunnelStateBuildReplyReceived : case i2p::tunnel::eTunnelStateBuildReplyReceived :
@ -147,7 +146,7 @@ namespace http {
} }
if (stateText.empty ()) stateText = tr(state); if (stateText.empty ()) stateText = tr(state);
s << "<span class=\"tunnel " << state << "\"> " << stateText << ((explr) ? " (" + std::string(tr("exploratory")) + ")" : "") << "</span>, "; // TODO: s << "<span class=\"tunnel " << state << "\"> " << stateText << ((explr) ? " (" + tr("exploratory") + ")" : "") << "</span>, ";
ShowTraffic(s, bytes); ShowTraffic(s, bytes);
s << "\r\n"; s << "\r\n";
} }
@ -214,7 +213,7 @@ namespace http {
"</html>\r\n"; "</html>\r\n";
} }
static void ShowError(std::stringstream& s, std::string_view string) static void ShowError(std::stringstream& s, const std::string& string)
{ {
s << "<b>" << tr("ERROR") << ":</b>&nbsp;" << string << "<br>\r\n"; s << "<b>" << tr("ERROR") << ":</b>&nbsp;" << string << "<br>\r\n";
} }
@ -1263,7 +1262,7 @@ namespace http {
ShowLeasesSets(s); ShowLeasesSets(s);
else { else {
res.code = 400; res.code = 400;
ShowError(s, std::string (tr("Unknown page")) + ": " + page); // TODO ShowError(s, tr("Unknown page") + ": " + page);
return; return;
} }
} }
@ -1419,11 +1418,13 @@ namespace http {
{ {
auto signatureLen = dest->GetIdentity ()->GetSignatureLen (); auto signatureLen = dest->GetIdentity ()->GetSignatureLen ();
uint8_t * signature = new uint8_t[signatureLen]; uint8_t * signature = new uint8_t[signatureLen];
char * sig = new char[signatureLen*2];
std::stringstream out; std::stringstream out;
out << name << "=" << dest->GetIdentity ()->ToBase64 (); out << name << "=" << dest->GetIdentity ()->ToBase64 ();
dest->Sign ((uint8_t *)out.str ().c_str (), out.str ().length (), signature); dest->Sign ((uint8_t *)out.str ().c_str (), out.str ().length (), signature);
auto sig = i2p::data::ByteStreamToBase64 (signature, signatureLen); auto len = i2p::data::ByteStreamToBase64 (signature, signatureLen, sig, signatureLen*2);
sig[len] = 0;
out << "#!sig=" << sig; out << "#!sig=" << sig;
s << "<b>" << tr("SUCCESS") << "</b>:<br>\r\n<form action=\"http://shx5vqsw7usdaunyzr2qmes2fq37oumybpudrd4jjj4e4vk4uusa.b32.i2p/add\" method=\"post\" rel=\"noreferrer\" target=\"_blank\">\r\n" s << "<b>" << tr("SUCCESS") << "</b>:<br>\r\n<form action=\"http://shx5vqsw7usdaunyzr2qmes2fq37oumybpudrd4jjj4e4vk4uusa.b32.i2p/add\" method=\"post\" rel=\"noreferrer\" target=\"_blank\">\r\n"
"<textarea readonly name=\"record\" cols=\"80\" rows=\"10\">" << out.str () << "</textarea>\r\n<br>\r\n<br>\r\n" "<textarea readonly name=\"record\" cols=\"80\" rows=\"10\">" << out.str () << "</textarea>\r\n<br>\r\n<br>\r\n"
@ -1432,6 +1433,7 @@ namespace http {
"<input type=\"submit\" value=\"" << tr("Submit") << "\">\r\n" "<input type=\"submit\" value=\"" << tr("Submit") << "\">\r\n"
"</form>\r\n<br>\r\n"; "</form>\r\n<br>\r\n";
delete[] signature; delete[] signature;
delete[] sig;
} }
else else
s << "<b>" << tr("ERROR") << "</b>:&nbsp;" << tr("Domain can't end with .b32.i2p") << "\r\n<br>\r\n<br>\r\n"; s << "<b>" << tr("ERROR") << "</b>:&nbsp;" << tr("Domain can't end with .b32.i2p") << "\r\n<br>\r\n<br>\r\n";
@ -1460,7 +1462,7 @@ namespace http {
else else
{ {
res.code = 400; res.code = 400;
ShowError(s, std::string (tr("Unknown command")) + ": " + cmd); // TODO ShowError(s, tr("Unknown command") + ": " + cmd);
return; return;
} }

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -15,6 +15,7 @@
// Use global placeholders from boost introduced when local_time.hpp is loaded // Use global placeholders from boost introduced when local_time.hpp is loaded
#define BOOST_BIND_GLOBAL_PLACEHOLDERS #define BOOST_BIND_GLOBAL_PLACEHOLDERS
#include <boost/property_tree/json_parser.hpp> #include <boost/property_tree/json_parser.hpp>
#include <boost/lexical_cast.hpp>
#include "FS.h" #include "FS.h"
#include "Log.h" #include "Log.h"
@ -29,24 +30,11 @@ namespace i2p
namespace client namespace client
{ {
I2PControlService::I2PControlService (const std::string& address, int port): I2PControlService::I2PControlService (const std::string& address, int port):
m_IsRunning (false), m_IsRunning (false), m_Thread (nullptr),
m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint(boost::asio::ip::make_address(address), port)),
m_SSLContext (boost::asio::ssl::context::sslv23), m_SSLContext (boost::asio::ssl::context::sslv23),
m_ShutdownTimer (m_Service) m_ShutdownTimer (m_Service)
{ {
if (port)
m_Acceptor = std::make_unique<boost::asio::ip::tcp::acceptor>(m_Service,
boost::asio::ip::tcp::endpoint(boost::asio::ip::make_address(address), port));
else
#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS)
{
std::remove (address.c_str ()); // just in case
m_LocalAcceptor = std::make_unique<boost::asio::local::stream_protocol::acceptor>(m_Service,
boost::asio::local::stream_protocol::endpoint(address));
}
#else
LogPrint(eLogError, "I2PControl: Local sockets are not supported");
#endif
i2p::config::GetOption("i2pcontrol.password", m_Password); i2p::config::GetOption("i2pcontrol.password", m_Password);
// certificate / keys // certificate / keys
@ -110,7 +98,7 @@ namespace client
{ {
Accept (); Accept ();
m_IsRunning = true; m_IsRunning = true;
m_Thread = std::make_unique<std::thread>(std::bind (&I2PControlService::Run, this)); m_Thread = new std::thread (std::bind (&I2PControlService::Run, this));
} }
} }
@ -119,19 +107,12 @@ namespace client
if (m_IsRunning) if (m_IsRunning)
{ {
m_IsRunning = false; m_IsRunning = false;
if (m_Acceptor) m_Acceptor->cancel (); m_Acceptor.cancel ();
#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS)
if (m_LocalAcceptor)
{
auto path = m_LocalAcceptor->local_endpoint().path();
m_LocalAcceptor->cancel ();
std::remove (path.c_str ());
}
#endif
m_Service.stop (); m_Service.stop ();
if (m_Thread) if (m_Thread)
{ {
m_Thread->join (); m_Thread->join ();
delete m_Thread;
m_Thread = nullptr; m_Thread = nullptr;
} }
} }
@ -153,60 +134,40 @@ namespace client
void I2PControlService::Accept () void I2PControlService::Accept ()
{ {
if (m_Acceptor) auto newSocket = std::make_shared<ssl_socket> (m_Service, m_SSLContext);
{ m_Acceptor.async_accept (newSocket->lowest_layer(), std::bind (&I2PControlService::HandleAccept, this,
auto newSocket = std::make_shared<boost::asio::ssl::stream<boost::asio::ip::tcp::socket> > (m_Service, m_SSLContext); std::placeholders::_1, newSocket));
m_Acceptor->async_accept (newSocket->lowest_layer(),
[this, newSocket](const boost::system::error_code& ecode)
{
HandleAccepted (ecode, newSocket);
});
}
#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS)
else if (m_LocalAcceptor)
{
auto newSocket = std::make_shared<boost::asio::ssl::stream<boost::asio::local::stream_protocol::socket> > (m_Service, m_SSLContext);
m_LocalAcceptor->async_accept (newSocket->lowest_layer(),
[this, newSocket](const boost::system::error_code& ecode)
{
HandleAccepted (ecode, newSocket);
});
}
#endif
} }
template<typename ssl_socket> void I2PControlService::HandleAccept(const boost::system::error_code& ecode, std::shared_ptr<ssl_socket> socket)
void I2PControlService::HandleAccepted (const boost::system::error_code& ecode,
std::shared_ptr<ssl_socket> newSocket)
{ {
if (ecode != boost::asio::error::operation_aborted) if (ecode != boost::asio::error::operation_aborted)
Accept (); Accept ();
if (ecode) if (ecode) {
{
LogPrint (eLogError, "I2PControl: Accept error: ", ecode.message ()); LogPrint (eLogError, "I2PControl: Accept error: ", ecode.message ());
return; return;
} }
LogPrint (eLogDebug, "I2PControl: New request from ", newSocket->lowest_layer ().remote_endpoint ()); LogPrint (eLogDebug, "I2PControl: New request from ", socket->lowest_layer ().remote_endpoint ());
Handshake (newSocket); Handshake (socket);
} }
template<typename ssl_socket>
void I2PControlService::Handshake (std::shared_ptr<ssl_socket> socket) void I2PControlService::Handshake (std::shared_ptr<ssl_socket> socket)
{ {
socket->async_handshake(boost::asio::ssl::stream_base::server, socket->async_handshake(boost::asio::ssl::stream_base::server,
[this, socket](const boost::system::error_code& ecode) std::bind( &I2PControlService::HandleHandshake, this, std::placeholders::_1, socket));
{ }
if (ecode)
void I2PControlService::HandleHandshake (const boost::system::error_code& ecode, std::shared_ptr<ssl_socket> socket)
{ {
if (ecode) {
LogPrint (eLogError, "I2PControl: Handshake error: ", ecode.message ()); LogPrint (eLogError, "I2PControl: Handshake error: ", ecode.message ());
return; return;
} }
//std::this_thread::sleep_for (std::chrono::milliseconds(5));
ReadRequest (socket); ReadRequest (socket);
});
} }
template<typename ssl_socket>
void I2PControlService::ReadRequest (std::shared_ptr<ssl_socket> socket) void I2PControlService::ReadRequest (std::shared_ptr<ssl_socket> socket)
{ {
auto request = std::make_shared<I2PControlBuffer>(); auto request = std::make_shared<I2PControlBuffer>();
@ -216,13 +177,10 @@ namespace client
#else #else
boost::asio::buffer (request->data (), request->size ()), boost::asio::buffer (request->data (), request->size ()),
#endif #endif
[this, socket, request](const boost::system::error_code& ecode, size_t bytes_transferred) std::bind(&I2PControlService::HandleRequestReceived, this,
{ std::placeholders::_1, std::placeholders::_2, socket, request));
HandleRequestReceived (ecode, bytes_transferred, socket, request);
});
} }
template<typename ssl_socket>
void I2PControlService::HandleRequestReceived (const boost::system::error_code& ecode, void I2PControlService::HandleRequestReceived (const boost::system::error_code& ecode,
size_t bytes_transferred, std::shared_ptr<ssl_socket> socket, size_t bytes_transferred, std::shared_ptr<ssl_socket> socket,
std::shared_ptr<I2PControlBuffer> buf) std::shared_ptr<I2PControlBuffer> buf)
@ -300,7 +258,6 @@ namespace client
} }
} }
template<typename ssl_socket>
void I2PControlService::SendResponse (std::shared_ptr<ssl_socket> socket, void I2PControlService::SendResponse (std::shared_ptr<ssl_socket> socket,
std::shared_ptr<I2PControlBuffer> buf, std::ostringstream& response, bool isHtml) std::shared_ptr<I2PControlBuffer> buf, std::ostringstream& response, bool isHtml)
{ {
@ -310,7 +267,7 @@ namespace client
std::ostringstream header; std::ostringstream header;
header << "HTTP/1.1 200 OK\r\n"; header << "HTTP/1.1 200 OK\r\n";
header << "Connection: close\r\n"; header << "Connection: close\r\n";
header << "Content-Length: " << std::to_string(len) << "\r\n"; header << "Content-Length: " << boost::lexical_cast<std::string>(len) << "\r\n";
header << "Content-Type: application/json\r\n"; header << "Content-Type: application/json\r\n";
header << "Date: "; header << "Date: ";
std::time_t t = std::time (nullptr); std::time_t t = std::time (nullptr);
@ -323,11 +280,16 @@ namespace client
memcpy (buf->data () + offset, response.str ().c_str (), len); memcpy (buf->data () + offset, response.str ().c_str (), len);
boost::asio::async_write (*socket, boost::asio::buffer (buf->data (), offset + len), boost::asio::async_write (*socket, boost::asio::buffer (buf->data (), offset + len),
boost::asio::transfer_all (), boost::asio::transfer_all (),
[socket, buf](const boost::system::error_code& ecode, std::size_t bytes_transferred) std::bind(&I2PControlService::HandleResponseSent, this,
std::placeholders::_1, std::placeholders::_2, socket, buf));
}
void I2PControlService::HandleResponseSent (const boost::system::error_code& ecode, std::size_t bytes_transferred,
std::shared_ptr<ssl_socket> socket, std::shared_ptr<I2PControlBuffer> buf)
{ {
if (ecode) if (ecode) {
LogPrint (eLogError, "I2PControl: Write error: ", ecode.message ()); LogPrint (eLogError, "I2PControl: Write error: ", ecode.message ());
}); }
} }
// handlers // handlers

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -35,6 +35,8 @@ namespace client
class I2PControlService: public I2PControlHandlers class I2PControlService: public I2PControlHandlers
{ {
typedef boost::asio::ssl::stream<boost::asio::ip::tcp::socket> ssl_socket;
public: public:
I2PControlService (const std::string& address, int port); I2PControlService (const std::string& address, int port);
@ -47,18 +49,16 @@ namespace client
void Run (); void Run ();
void Accept (); void Accept ();
template<typename ssl_socket> void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr<ssl_socket> socket);
void HandleAccepted (const boost::system::error_code& ecode, std::shared_ptr<ssl_socket> newSocket);
template<typename ssl_socket>
void Handshake (std::shared_ptr<ssl_socket> socket); void Handshake (std::shared_ptr<ssl_socket> socket);
template<typename ssl_socket> void HandleHandshake (const boost::system::error_code& ecode, std::shared_ptr<ssl_socket> socket);
void ReadRequest (std::shared_ptr<ssl_socket> socket); void ReadRequest (std::shared_ptr<ssl_socket> socket);
template<typename ssl_socket>
void HandleRequestReceived (const boost::system::error_code& ecode, size_t bytes_transferred, void HandleRequestReceived (const boost::system::error_code& ecode, size_t bytes_transferred,
std::shared_ptr<ssl_socket> socket, std::shared_ptr<I2PControlBuffer> buf); std::shared_ptr<ssl_socket> socket, std::shared_ptr<I2PControlBuffer> buf);
template<typename ssl_socket>
void SendResponse (std::shared_ptr<ssl_socket> socket, void SendResponse (std::shared_ptr<ssl_socket> socket,
std::shared_ptr<I2PControlBuffer> buf, std::ostringstream& response, bool isHtml); std::shared_ptr<I2PControlBuffer> buf, std::ostringstream& response, bool isHtml);
void HandleResponseSent (const boost::system::error_code& ecode, std::size_t bytes_transferred,
std::shared_ptr<ssl_socket> socket, std::shared_ptr<I2PControlBuffer> buf);
void CreateCertificate (const char *crt_path, const char *key_path); void CreateCertificate (const char *crt_path, const char *key_path);
@ -86,13 +86,10 @@ namespace client
std::string m_Password; std::string m_Password;
bool m_IsRunning; bool m_IsRunning;
std::unique_ptr<std::thread> m_Thread; std::thread * m_Thread;
boost::asio::io_context m_Service; boost::asio::io_context m_Service;
std::unique_ptr<boost::asio::ip::tcp::acceptor> m_Acceptor; boost::asio::ip::tcp::acceptor m_Acceptor;
#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS)
std::unique_ptr<boost::asio::local::stream_protocol::acceptor> m_LocalAcceptor;
#endif
boost::asio::ssl::context m_SSLContext; boost::asio::ssl::context m_SSLContext;
boost::asio::deadline_timer m_ShutdownTimer; boost::asio::deadline_timer m_ShutdownTimer;
std::set<std::string> m_Tokens; std::set<std::string> m_Tokens;

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2021-2025, The PurpleI2P Project * Copyright (c) 2021, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -29,7 +29,7 @@ namespace afrikaans // language namespace
return n != 1 ? 1 : 0; return n != 1 ? 1 : 0;
} }
static const LocaleStrings strings static std::map<std::string, std::string> strings
{ {
{"failed", "Het misluk"}, {"failed", "Het misluk"},
{"unknown", "onbekend"}, {"unknown", "onbekend"},

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2021-2025, The PurpleI2P Project * Copyright (c) 2021-2023, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -29,7 +29,7 @@ namespace armenian // language namespace
return n != 1 ? 1 : 0; return n != 1 ? 1 : 0;
} }
static const LocaleStrings strings static std::map<std::string, std::string> strings
{ {
{"%.2f KiB", "%.2f ԿիԲ"}, {"%.2f KiB", "%.2f ԿիԲ"},
{"%.2f MiB", "%.2f ՄիԲ"}, {"%.2f MiB", "%.2f ՄիԲ"},

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2022-2025, The PurpleI2P Project * Copyright (c) 2022-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -29,7 +29,7 @@ namespace chinese // language namespace
return 0; return 0;
} }
static const LocaleStrings strings static std::map<std::string, std::string> strings
{ {
{"%.2f KiB", "%.2f KiB"}, {"%.2f KiB", "%.2f KiB"},
{"%.2f MiB", "%.2f MiB"}, {"%.2f MiB", "%.2f MiB"},

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2022-2025, The PurpleI2P Project * Copyright (c) 2022-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -29,7 +29,7 @@ namespace czech // language namespace
return (n == 1) ? 0 : (n >= 2 && n <= 4) ? 1 : 2; return (n == 1) ? 0 : (n >= 2 && n <= 4) ? 1 : 2;
} }
static const LocaleStrings strings static std::map<std::string, std::string> strings
{ {
{"%.2f KiB", "%.2f KiB"}, {"%.2f KiB", "%.2f KiB"},
{"%.2f MiB", "%.2f MiB"}, {"%.2f MiB", "%.2f MiB"},

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2021-2025, The PurpleI2P Project * Copyright (c) 2021, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -30,7 +30,7 @@ namespace english // language namespace
return n != 1 ? 1 : 0; return n != 1 ? 1 : 0;
} }
static const LocaleStrings strings static std::map<std::string, std::string> strings
{ {
{"", ""}, {"", ""},
}; };

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2022-2025, The PurpleI2P Project * Copyright (c) 2022-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -29,7 +29,7 @@ namespace french // language namespace
return n != 1 ? 1 : 0; return n != 1 ? 1 : 0;
} }
static const LocaleStrings strings static std::map<std::string, std::string> strings
{ {
{"%.2f KiB", "%.2f Kio"}, {"%.2f KiB", "%.2f Kio"},
{"%.2f MiB", "%.2f Mio"}, {"%.2f MiB", "%.2f Mio"},

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2022-2025, The PurpleI2P Project * Copyright (c) 2022-2023, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -29,7 +29,7 @@ namespace german // language namespace
return n != 1 ? 1 : 0; return n != 1 ? 1 : 0;
} }
static const LocaleStrings strings static std::map<std::string, std::string> strings
{ {
{"%.2f KiB", "%.2f KiB"}, {"%.2f KiB", "%.2f KiB"},
{"%.2f MiB", "%.2f MiB"}, {"%.2f MiB", "%.2f MiB"},

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2021-2025, The PurpleI2P Project * Copyright (c) 2021-2023, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -30,12 +30,12 @@ namespace i18n
} }
} }
std::string_view translate (std::string_view arg) std::string translate (const std::string& arg)
{ {
return i2p::client::context.GetLanguage ()->GetString (arg); return i2p::client::context.GetLanguage ()->GetString (arg);
} }
std::string translate (const std::string& arg, const std::string& arg2, const int n) std::string translate (const std::string& arg, const std::string& arg2, const int& n)
{ {
return i2p::client::context.GetLanguage ()->GetPlural (arg, arg2, n); return i2p::client::context.GetLanguage ()->GetPlural (arg, arg2, n);
} }

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2021-2025, The PurpleI2P Project * Copyright (c) 2021-2023, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -10,7 +10,6 @@
#define __I18N_H__ #define __I18N_H__
#include <string> #include <string>
#include <string_view>
#include <map> #include <map>
#include <utility> #include <utility>
#include <functional> #include <functional>
@ -19,13 +18,12 @@ namespace i2p
{ {
namespace i18n namespace i18n
{ {
typedef std::map<std::string_view, std::string_view> LocaleStrings;
class Locale class Locale
{ {
public: public:
Locale ( Locale (
const std::string& language, const std::string& language,
const LocaleStrings& strings, const std::map<std::string, std::string>& strings,
const std::map<std::string, std::vector<std::string>>& plurals, const std::map<std::string, std::vector<std::string>>& plurals,
std::function<int(int)> formula std::function<int(int)> formula
): m_Language (language), m_Strings (strings), m_Plurals (plurals), m_Formula (formula) { }; ): m_Language (language), m_Strings (strings), m_Plurals (plurals), m_Formula (formula) { };
@ -36,7 +34,7 @@ namespace i18n
return m_Language; return m_Language;
} }
std::string_view GetString (std::string_view arg) const std::string GetString (const std::string& arg) const
{ {
const auto it = m_Strings.find(arg); const auto it = m_Strings.find(arg);
if (it == m_Strings.end()) if (it == m_Strings.end())
@ -49,7 +47,7 @@ namespace i18n
} }
} }
std::string GetPlural (const std::string& arg, const std::string& arg2, int n) const std::string GetPlural (const std::string& arg, const std::string& arg2, const int& n) const
{ {
const auto it = m_Plurals.find(arg2); const auto it = m_Plurals.find(arg2);
if (it == m_Plurals.end()) // not found, fallback to english if (it == m_Plurals.end()) // not found, fallback to english
@ -65,14 +63,14 @@ namespace i18n
private: private:
const std::string m_Language; const std::string m_Language;
const LocaleStrings m_Strings; const std::map<std::string, std::string> m_Strings;
const std::map<std::string, std::vector<std::string>> m_Plurals; const std::map<std::string, std::vector<std::string>> m_Plurals;
std::function<int(int)> m_Formula; std::function<int(int)> m_Formula;
}; };
void SetLanguage(const std::string &lang); void SetLanguage(const std::string &lang);
std::string_view translate (std::string_view arg); std::string translate (const std::string& arg);
std::string translate (const std::string& arg, const std::string& arg2, int n); std::string translate (const std::string& arg, const std::string& arg2, const int& n);
} // i18n } // i18n
} // i2p } // i2p
@ -81,7 +79,7 @@ namespace i18n
* @param arg String with message * @param arg String with message
*/ */
template<typename TValue> template<typename TValue>
std::string_view tr (TValue&& arg) std::string tr (TValue&& arg)
{ {
return i2p::i18n::translate(std::forward<TValue>(arg)); return i2p::i18n::translate(std::forward<TValue>(arg));
} }
@ -94,7 +92,7 @@ std::string_view tr (TValue&& arg)
template<typename TValue, typename... TArgs> template<typename TValue, typename... TArgs>
std::string tr (TValue&& arg, TArgs&&... args) std::string tr (TValue&& arg, TArgs&&... args)
{ {
std::string tr_str = std::string (i2p::i18n::translate(std::forward<TValue>(arg))); // TODO: std::string tr_str = i2p::i18n::translate(std::forward<TValue>(arg));
size_t size = std::snprintf(NULL, 0, tr_str.c_str(), std::forward<TArgs>(args)...); size_t size = std::snprintf(NULL, 0, tr_str.c_str(), std::forward<TArgs>(args)...);
std::string str(size, 0); std::string str(size, 0);
@ -110,7 +108,7 @@ std::string tr (TValue&& arg, TArgs&&... args)
* @param n Integer, used for selection of form * @param n Integer, used for selection of form
*/ */
template<typename TValue, typename TValue2> template<typename TValue, typename TValue2>
std::string ntr (TValue&& arg, TValue2&& arg2, int n) std::string ntr (TValue&& arg, TValue2&& arg2, int& n)
{ {
return i2p::i18n::translate(std::forward<TValue>(arg), std::forward<TValue2>(arg2), std::forward<int>(n)); return i2p::i18n::translate(std::forward<TValue>(arg), std::forward<TValue2>(arg2), std::forward<int>(n));
} }
@ -123,7 +121,7 @@ std::string ntr (TValue&& arg, TValue2&& arg2, int n)
* @param args Array of arguments for string formatting * @param args Array of arguments for string formatting
*/ */
template<typename TValue, typename TValue2, typename... TArgs> template<typename TValue, typename TValue2, typename... TArgs>
std::string ntr (TValue&& arg, TValue2&& arg2, int n, TArgs&&... args) std::string ntr (TValue&& arg, TValue2&& arg2, int& n, TArgs&&... args)
{ {
std::string tr_str = i2p::i18n::translate(std::forward<TValue>(arg), std::forward<TValue2>(arg2), std::forward<int>(n)); std::string tr_str = i2p::i18n::translate(std::forward<TValue>(arg), std::forward<TValue2>(arg2), std::forward<int>(n));

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2022-2025, The PurpleI2P Project * Copyright (c) 2022-2023, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -29,7 +29,7 @@ namespace italian // language namespace
return n != 1 ? 1 : 0; return n != 1 ? 1 : 0;
} }
static const LocaleStrings strings static std::map<std::string, std::string> strings
{ {
{"%.2f KiB", "%.2f KiB"}, {"%.2f KiB", "%.2f KiB"},
{"%.2f MiB", "%.2f MiB"}, {"%.2f MiB", "%.2f MiB"},

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2023-2025, The PurpleI2P Project * Copyright (c) 2023-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -29,7 +29,7 @@ namespace polish // language namespace
return (n == 1 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2); return (n == 1 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2);
} }
static const LocaleStrings strings static std::map<std::string, std::string> strings
{ {
{"%.2f KiB", "%.2f KiB"}, {"%.2f KiB", "%.2f KiB"},
{"%.2f MiB", "%.2f MiB"}, {"%.2f MiB", "%.2f MiB"},

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2023-2025, The PurpleI2P Project * Copyright (c) 2023-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -29,7 +29,7 @@ namespace portuguese // language namespace
return n != 1 ? 1 : 0; return n != 1 ? 1 : 0;
} }
static const LocaleStrings strings static std::map<std::string, std::string> strings
{ {
{"%.2f KiB", "%.2f KiB"}, {"%.2f KiB", "%.2f KiB"},
{"%.2f MiB", "%.2f MiB"}, {"%.2f MiB", "%.2f MiB"},

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2021-2025, The PurpleI2P Project * Copyright (c) 2021-2023, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -29,7 +29,7 @@ namespace russian // language namespace
return n % 10 == 1 && n % 100 != 11 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2; return n % 10 == 1 && n % 100 != 11 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2;
} }
static const LocaleStrings strings static std::map<std::string, std::string> strings
{ {
{"%.2f KiB", "%.2f КиБ"}, {"%.2f KiB", "%.2f КиБ"},
{"%.2f MiB", "%.2f МиБ"}, {"%.2f MiB", "%.2f МиБ"},

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2022-2025, The PurpleI2P Project * Copyright (c) 2022-2023, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -29,7 +29,7 @@ namespace spanish // language namespace
return n != 1 ? 1 : 0; return n != 1 ? 1 : 0;
} }
static const LocaleStrings strings static std::map<std::string, std::string> strings
{ {
{"%.2f KiB", "%.2f KiB"}, {"%.2f KiB", "%.2f KiB"},
{"%.2f MiB", "%.2f MiB"}, {"%.2f MiB", "%.2f MiB"},

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2023-2025, The PurpleI2P Project * Copyright (c) 2023, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -29,7 +29,7 @@ namespace swedish // language namespace
return n != 1 ? 1 : 0; return n != 1 ? 1 : 0;
} }
static const LocaleStrings strings static std::map<std::string, std::string> strings
{ {
{"%.2f KiB", "%.2f KiB"}, {"%.2f KiB", "%.2f KiB"},
{"%.2f MiB", "%.2f MiB"}, {"%.2f MiB", "%.2f MiB"},

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2023-2025, The PurpleI2P Project * Copyright (c) 2023, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -29,7 +29,7 @@ namespace turkish // language namespace
return n != 1 ? 1 : 0; return n != 1 ? 1 : 0;
} }
static const LocaleStrings strings static std::map<std::string, std::string> strings
{ {
{"%.2f KiB", "%.2f KiB"}, {"%.2f KiB", "%.2f KiB"},
{"%.2f MiB", "%.2f MiB"}, {"%.2f MiB", "%.2f MiB"},

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2021-2025, The PurpleI2P Project * Copyright (c) 2021-2023, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -29,7 +29,7 @@ namespace turkmen // language namespace
return n != 1 ? 1 : 0; return n != 1 ? 1 : 0;
} }
static const LocaleStrings strings static std::map<std::string, std::string> strings
{ {
{"%.2f KiB", "%.2f KiB"}, {"%.2f KiB", "%.2f KiB"},
{"%.2f MiB", "%.2f MiB"}, {"%.2f MiB", "%.2f MiB"},

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2021-2025, The PurpleI2P Project * Copyright (c) 2021-2023, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -29,7 +29,7 @@ namespace ukrainian // language namespace
return n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2; return n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;
} }
static const LocaleStrings strings static std::map<std::string, std::string> strings
{ {
{"%.2f KiB", "%.2f КіБ"}, {"%.2f KiB", "%.2f КіБ"},
{"%.2f MiB", "%.2f МіБ"}, {"%.2f MiB", "%.2f МіБ"},

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2021-2025, The PurpleI2P Project * Copyright (c) 2021-2023, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -29,7 +29,7 @@ namespace uzbek // language namespace
return n > 1 ? 1 : 0; return n > 1 ? 1 : 0;
} }
static const LocaleStrings strings static std::map<std::string, std::string> strings
{ {
{"%.2f KiB", "%.2f KiB"}, {"%.2f KiB", "%.2f KiB"},
{"%.2f MiB", "%.2f MiB"}, {"%.2f MiB", "%.2f MiB"},

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2023, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -15,7 +15,7 @@ namespace i2p
{ {
namespace data namespace data
{ {
static constexpr char T32[32] = static const char T32[32] =
{ {
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
@ -28,6 +28,11 @@ namespace data
return T32; return T32;
} }
bool IsBase32 (char ch)
{
return (ch >= 'a' && ch <= 'z') || (ch >= '2' && ch <= '7');
}
static void iT64Build(void); static void iT64Build(void);
/* /*
@ -38,7 +43,7 @@ namespace data
* Direct Substitution Table * Direct Substitution Table
*/ */
static constexpr char T64[64] = static const char T64[64] =
{ {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
@ -55,16 +60,23 @@ namespace data
return T64; return T64;
} }
bool IsBase64 (char ch)
{
return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || ch == '-' || ch == '~';
}
/* /*
* Reverse Substitution Table (built in run time) * Reverse Substitution Table (built in run time)
*/ */
static char iT64[256]; static char iT64[256];
static int isFirstTime = 1; static int isFirstTime = 1;
/* /*
* Padding * Padding
*/ */
static constexpr char P64 = '=';
static char P64 = '=';
/* /*
* *
@ -74,69 +86,78 @@ namespace data
* Converts binary encoded data to BASE64 format. * Converts binary encoded data to BASE64 format.
* *
*/ */
std::string ByteStreamToBase64 (// base64 encoded string
const uint8_t * InBuffer, // Input buffer, binary data size_t ByteStreamToBase64 ( /* Number of bytes in the encoded buffer */
size_t InCount // Number of bytes in the input buffer const uint8_t * InBuffer, /* Input buffer, binary data */
size_t InCount, /* Number of bytes in the input buffer */
char * OutBuffer, /* output buffer */
size_t len /* length of output buffer */
) )
{ {
unsigned char * ps; unsigned char * ps;
unsigned char * pd;
unsigned char acc_1; unsigned char acc_1;
unsigned char acc_2; unsigned char acc_2;
int i; int i;
int n; int n;
int m; int m;
size_t outCount;
ps = (unsigned char *)InBuffer; ps = (unsigned char *)InBuffer;
n = InCount / 3; n = InCount / 3;
m = InCount % 3; m = InCount % 3;
size_t outCount = m ? (4 * (n + 1)) : (4 * n); if (!m)
outCount = 4 * n;
else
outCount = 4 * (n + 1);
std::string out; if (outCount > len) return 0;
out.reserve (outCount);
pd = (unsigned char *)OutBuffer;
for ( i = 0; i < n; i++ ) for ( i = 0; i < n; i++ )
{ {
acc_1 = *ps++; acc_1 = *ps++;
acc_2 = (acc_1 << 4) & 0x30; acc_2 = (acc_1 << 4) & 0x30;
acc_1 >>= 2; // base64 digit #1 acc_1 >>= 2; /* base64 digit #1 */
out.push_back (T64[acc_1]); *pd++ = T64[acc_1];
acc_1 = *ps++; acc_1 = *ps++;
acc_2 |= acc_1 >> 4; // base64 digit #2 acc_2 |= acc_1 >> 4; /* base64 digit #2 */
out.push_back (T64[acc_2]); *pd++ = T64[acc_2];
acc_1 &= 0x0f; acc_1 &= 0x0f;
acc_1 <<= 2; acc_1 <<= 2;
acc_2 = *ps++; acc_2 = *ps++;
acc_1 |= acc_2 >> 6; // base64 digit #3 acc_1 |= acc_2 >> 6; /* base64 digit #3 */
out.push_back (T64[acc_1]); *pd++ = T64[acc_1];
acc_2 &= 0x3f; // base64 digit #4 acc_2 &= 0x3f; /* base64 digit #4 */
out.push_back (T64[acc_2]); *pd++ = T64[acc_2];
} }
if ( m == 1 ) if ( m == 1 )
{ {
acc_1 = *ps++; acc_1 = *ps++;
acc_2 = (acc_1 << 4) & 0x3f; // base64 digit #2 acc_2 = (acc_1 << 4) & 0x3f; /* base64 digit #2 */
acc_1 >>= 2; // base64 digit #1 acc_1 >>= 2; /* base64 digit #1 */
out.push_back (T64[acc_1]); *pd++ = T64[acc_1];
out.push_back (T64[acc_2]); *pd++ = T64[acc_2];
out.push_back (P64); *pd++ = P64;
out.push_back (P64); *pd++ = P64;
} }
else if ( m == 2 ) else if ( m == 2 )
{ {
acc_1 = *ps++; acc_1 = *ps++;
acc_2 = (acc_1 << 4) & 0x3f; acc_2 = (acc_1 << 4) & 0x3f;
acc_1 >>= 2; // base64 digit #1 acc_1 >>= 2; /* base64 digit #1 */
out.push_back (T64[acc_1]); *pd++ = T64[acc_1];
acc_1 = *ps++; acc_1 = *ps++;
acc_2 |= acc_1 >> 4; // base64 digit #2 acc_2 |= acc_1 >> 4; /* base64 digit #2 */
out.push_back (T64[acc_2]); *pd++ = T64[acc_2];
acc_1 &= 0x0f; acc_1 &= 0x0f;
acc_1 <<= 2; // base64 digit #3 acc_1 <<= 2; /* base64 digit #3 */
out.push_back (T64[acc_1]); *pd++ = T64[acc_1];
out.push_back (P64); *pd++ = P64;
} }
return out; return outCount;
} }
/* /*
@ -144,42 +165,55 @@ namespace data
* Base64ToByteStream * Base64ToByteStream
* ------------------ * ------------------
* *
* Converts BASE64 encoded string to binary format. If input buffer is * Converts BASE64 encoded data to binary format. If input buffer is
* not properly padded, buffer of negative length is returned * not properly padded, buffer of negative length is returned
* *
*/ */
size_t Base64ToByteStream ( // Number of output bytes
std::string_view base64Str, // BASE64 encoded string size_t Base64ToByteStream ( /* Number of output bytes */
uint8_t * OutBuffer, // output buffer length const char * InBuffer, /* BASE64 encoded buffer */
size_t len // length of output buffer size_t InCount, /* Number of input bytes */
uint8_t * OutBuffer, /* output buffer length */
size_t len /* length of output buffer */
) )
{ {
unsigned char * ps;
unsigned char * pd; unsigned char * pd;
unsigned char acc_1; unsigned char acc_1;
unsigned char acc_2; unsigned char acc_2;
int i;
int n;
int m;
size_t outCount; size_t outCount;
if (base64Str.empty () || base64Str[0] == P64) return 0; if (isFirstTime)
auto d = std::div (base64Str.length (), 4); iT64Build();
if (!d.rem)
outCount = 3 * d.quot; n = InCount / 4;
m = InCount % 4;
if (InCount && !m)
outCount = 3 * n;
else else
return 0; return 0;
if (isFirstTime) iT64Build(); if(*InBuffer == P64)
return 0;
auto pos = base64Str.find_last_not_of (P64); ps = (unsigned char *)(InBuffer + InCount - 1);
if (pos == base64Str.npos) return 0; while ( *ps-- == P64 )
outCount -= (base64Str.length () - pos - 1); outCount--;
if (outCount > len) return 0; ps = (unsigned char *)InBuffer;
if (outCount > len)
return 0;
auto ps = base64Str.begin ();
pd = OutBuffer; pd = OutBuffer;
auto endOfOutBuffer = OutBuffer + outCount; auto endOfOutBuffer = OutBuffer + outCount;
for (int i = 0; i < d.quot; i++) for ( i = 0; i < n; i++ )
{ {
acc_1 = iT64[int(*ps++)]; acc_1 = iT64[*ps++];
acc_2 = iT64[int(*ps++)]; acc_2 = iT64[*ps++];
acc_1 <<= 2; acc_1 <<= 2;
acc_1 |= acc_2 >> 4; acc_1 |= acc_2 >> 4;
*pd++ = acc_1; *pd++ = acc_1;
@ -187,13 +221,13 @@ namespace data
break; break;
acc_2 <<= 4; acc_2 <<= 4;
acc_1 = iT64[int(*ps++)]; acc_1 = iT64[*ps++];
acc_2 |= acc_1 >> 2; acc_2 |= acc_1 >> 2;
*pd++ = acc_2; *pd++ = acc_2;
if (pd >= endOfOutBuffer) if (pd >= endOfOutBuffer)
break; break;
acc_2 = iT64[int(*ps++)]; acc_2 = iT64[*ps++];
acc_2 |= acc_1 << 6; acc_2 |= acc_1 << 6;
*pd++ = acc_2; *pd++ = acc_2;
} }
@ -201,16 +235,31 @@ namespace data
return outCount; return outCount;
} }
std::string ToBase64Standard (std::string_view in) size_t Base64EncodingBufferSize (const size_t input_size)
{ {
auto str = ByteStreamToBase64 ((const uint8_t *)in.data (), in.length ()); auto d = div (input_size, 3);
if (d.rem)
d.quot++;
return 4 * d.quot;
}
std::string ToBase64Standard (const std::string& in)
{
auto len = Base64EncodingBufferSize (in.length ());
char * str = new char[len + 1];
auto l = ByteStreamToBase64 ((const uint8_t *)in.c_str (), in.length (), str, len);
str[l] = 0;
// replace '-' by '+' and '~' by '/' // replace '-' by '+' and '~' by '/'
for (auto& ch: str) for (size_t i = 0; i < l; i++)
if (ch == '-') if (str[i] == '-')
ch = '+'; str[i] = '+';
else if (ch == '~') else if (str[i] == '~')
ch = '/'; str[i] = '/';
return str;
std::string s(str);
delete[] str;
return s;
} }
/* /*
@ -231,12 +280,13 @@ namespace data
iT64[(int)P64] = 0; iT64[(int)P64] = 0;
} }
size_t Base32ToByteStream (std::string_view base32Str, uint8_t * outBuf, size_t outLen) size_t Base32ToByteStream (const char * inBuf, size_t len, uint8_t * outBuf, size_t outLen)
{ {
unsigned int tmp = 0, bits = 0; unsigned int tmp = 0, bits = 0;
size_t ret = 0; size_t ret = 0;
for (auto ch: base32Str) for (size_t i = 0; i < len; i++)
{ {
char ch = inBuf[i];
if (ch >= '2' && ch <= '7') // digit if (ch >= '2' && ch <= '7') // digit
ch = (ch - '2') + 26; // 26 means a-z ch = (ch - '2') + 26; // 26 means a-z
else if (ch >= 'a' && ch <= 'z') else if (ch >= 'a' && ch <= 'z')
@ -258,13 +308,11 @@ namespace data
return ret; return ret;
} }
std::string ByteStreamToBase32 (const uint8_t * inBuf, size_t len) size_t ByteStreamToBase32 (const uint8_t * inBuf, size_t len, char * outBuf, size_t outLen)
{ {
std::string out; size_t ret = 0, pos = 1;
out.reserve ((len * 8 + 4) / 5);
size_t pos = 1;
unsigned int bits = 8, tmp = inBuf[0]; unsigned int bits = 8, tmp = inBuf[0];
while (bits > 0 || pos < len) while (ret < outLen && (bits > 0 || pos < len))
{ {
if (bits < 5) if (bits < 5)
{ {
@ -284,9 +332,10 @@ namespace data
bits -= 5; bits -= 5;
int ind = (tmp >> bits) & 0x1F; int ind = (tmp >> bits) & 0x1F;
out.push_back ((ind < 26) ? (ind + 'a') : ((ind - 26) + '2')); outBuf[ret] = (ind < 26) ? (ind + 'a') : ((ind - 26) + '2');
ret++;
} }
return out; return ret;
} }
} }
} }

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2023, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -11,41 +11,26 @@
#include <inttypes.h> #include <inttypes.h>
#include <string> #include <string>
#include <string_view> #include <iostream>
#include <cstdlib>
namespace i2p
{
namespace data
{
std::string ByteStreamToBase64 (const uint8_t * InBuffer, size_t InCount);
size_t Base64ToByteStream (std::string_view base64Str, uint8_t * OutBuffer, size_t len);
namespace i2p {
namespace data {
size_t ByteStreamToBase64 (const uint8_t * InBuffer, size_t InCount, char * OutBuffer, size_t len);
size_t Base64ToByteStream (const char * InBuffer, size_t InCount, uint8_t * OutBuffer, size_t len );
const char * GetBase32SubstitutionTable (); const char * GetBase32SubstitutionTable ();
const char * GetBase64SubstitutionTable (); const char * GetBase64SubstitutionTable ();
constexpr bool IsBase64 (char ch) bool IsBase64 (char ch);
{
return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || ch == '-' || ch == '~';
}
size_t Base32ToByteStream (std::string_view base32Str, uint8_t * outBuf, size_t outLen); size_t Base32ToByteStream (const char * inBuf, size_t len, uint8_t * outBuf, size_t outLen);
std::string ByteStreamToBase32 (const uint8_t * inBuf, size_t len); size_t ByteStreamToBase32 (const uint8_t * InBuf, size_t len, char * outBuf, size_t outLen);
constexpr bool IsBase32 (char ch) bool IsBase32 (char ch);
{
return (ch >= 'a' && ch <= 'z') || (ch >= '2' && ch <= '7');
}
/** /**
* Compute the size for a buffer to contain encoded base64 given that the size of the input is input_size bytes * Compute the size for a buffer to contain encoded base64 given that the size of the input is input_size bytes
*/ */
inline size_t Base64EncodingBufferSize(size_t input_size) size_t Base64EncodingBufferSize(const size_t input_size);
{
auto d = std::div (input_size, 3);
if (d.rem) d.quot++;
return 4 * d.quot;
}
std::string ToBase64Standard (std::string_view in); // using standard table, for Proxy-Authorization std::string ToBase64Standard (const std::string& in); // using standard table, for Proxy-Authorization
} // data } // data
} // i2p } // i2p

View file

@ -156,7 +156,7 @@ namespace data
m_SigType (0) // 0 means invalid, we can't blind DSA, set it later m_SigType (0) // 0 means invalid, we can't blind DSA, set it later
{ {
uint8_t addr[40]; // TODO: define length from b33 uint8_t addr[40]; // TODO: define length from b33
size_t l = i2p::data::Base32ToByteStream (b33, addr, 40); size_t l = i2p::data::Base32ToByteStream (b33.data (), b33.length (), addr, 40);
if (l < 32) if (l < 32)
{ {
LogPrint (eLogError, "Blinding: Malformed b33 ", b33); LogPrint (eLogError, "Blinding: Malformed b33 ", b33);
@ -198,7 +198,7 @@ namespace data
std::string BlindedPublicKey::ToB33 () const std::string BlindedPublicKey::ToB33 () const
{ {
if (m_PublicKey.size () > 32) return ""; // assume 25519 if (m_PublicKey.size () > 32) return ""; // assume 25519
uint8_t addr[35]; uint8_t addr[35]; char str[60]; // TODO: define actual length
uint8_t flags = 0; uint8_t flags = 0;
if (m_IsClientAuth) flags |= B33_PER_CLIENT_AUTH_FLAG; if (m_IsClientAuth) flags |= B33_PER_CLIENT_AUTH_FLAG;
addr[0] = flags; // flags addr[0] = flags; // flags
@ -208,7 +208,8 @@ namespace data
uint32_t checksum = crc32 (0, addr + 3, m_PublicKey.size ()); uint32_t checksum = crc32 (0, addr + 3, m_PublicKey.size ());
// checksum is Little Endian // checksum is Little Endian
addr[0] ^= checksum; addr[1] ^= (checksum >> 8); addr[2] ^= (checksum >> 16); addr[0] ^= checksum; addr[1] ^= (checksum >> 8); addr[2] ^= (checksum >> 16);
return ByteStreamToBase32 (addr, m_PublicKey.size () + 3); auto l = ByteStreamToBase32 (addr, m_PublicKey.size () + 3, str, 60);
return std::string (str, str + l);
} }
void BlindedPublicKey::GetCredential (uint8_t * credential) const void BlindedPublicKey::GetCredential (uint8_t * credential) const

View file

@ -19,10 +19,6 @@
#if OPENSSL_HKDF #if OPENSSL_HKDF
#include <openssl/kdf.h> #include <openssl/kdf.h>
#endif #endif
#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0
#include <openssl/param_build.h>
#include <openssl/core_names.h>
#endif
#include "CPU.h" #include "CPU.h"
#include "Crypto.h" #include "Crypto.h"
#include "Ed25519.h" #include "Ed25519.h"
@ -33,7 +29,7 @@ namespace i2p
{ {
namespace crypto namespace crypto
{ {
constexpr uint8_t elgp_[256]= const uint8_t elgp_[256]=
{ {
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2, 0x21, 0x68, 0xC2, 0x34, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2, 0x21, 0x68, 0xC2, 0x34,
0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74,
@ -53,9 +49,9 @@ namespace crypto
0x15, 0x72, 0x8E, 0x5A, 0x8A, 0xAC, 0xAA, 0x68, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF 0x15, 0x72, 0x8E, 0x5A, 0x8A, 0xAC, 0xAA, 0x68, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
}; };
constexpr int elgg_ = 2; const int elgg_ = 2;
constexpr uint8_t dsap_[128]= const uint8_t dsap_[128]=
{ {
0x9c, 0x05, 0xb2, 0xaa, 0x96, 0x0d, 0x9b, 0x97, 0xb8, 0x93, 0x19, 0x63, 0xc9, 0xcc, 0x9e, 0x8c, 0x9c, 0x05, 0xb2, 0xaa, 0x96, 0x0d, 0x9b, 0x97, 0xb8, 0x93, 0x19, 0x63, 0xc9, 0xcc, 0x9e, 0x8c,
0x30, 0x26, 0xe9, 0xb8, 0xed, 0x92, 0xfa, 0xd0, 0xa6, 0x9c, 0xc8, 0x86, 0xd5, 0xbf, 0x80, 0x15, 0x30, 0x26, 0xe9, 0xb8, 0xed, 0x92, 0xfa, 0xd0, 0xa6, 0x9c, 0xc8, 0x86, 0xd5, 0xbf, 0x80, 0x15,
@ -67,13 +63,13 @@ namespace crypto
0x28, 0x5d, 0x4c, 0xf2, 0x95, 0x38, 0xd9, 0xe3, 0xb6, 0x05, 0x1f, 0x5b, 0x22, 0xcc, 0x1c, 0x93 0x28, 0x5d, 0x4c, 0xf2, 0x95, 0x38, 0xd9, 0xe3, 0xb6, 0x05, 0x1f, 0x5b, 0x22, 0xcc, 0x1c, 0x93
}; };
constexpr uint8_t dsaq_[20]= const uint8_t dsaq_[20]=
{ {
0xa5, 0xdf, 0xc2, 0x8f, 0xef, 0x4c, 0xa1, 0xe2, 0x86, 0x74, 0x4c, 0xd8, 0xee, 0xd9, 0xd2, 0x9d, 0xa5, 0xdf, 0xc2, 0x8f, 0xef, 0x4c, 0xa1, 0xe2, 0x86, 0x74, 0x4c, 0xd8, 0xee, 0xd9, 0xd2, 0x9d,
0x68, 0x40, 0x46, 0xb7 0x68, 0x40, 0x46, 0xb7
}; };
constexpr uint8_t dsag_[128]= const uint8_t dsag_[128]=
{ {
0x0c, 0x1f, 0x4d, 0x27, 0xd4, 0x00, 0x93, 0xb4, 0x29, 0xe9, 0x62, 0xd7, 0x22, 0x38, 0x24, 0xe0, 0x0c, 0x1f, 0x4d, 0x27, 0xd4, 0x00, 0x93, 0xb4, 0x29, 0xe9, 0x62, 0xd7, 0x22, 0x38, 0x24, 0xe0,
0xbb, 0xc4, 0x7e, 0x7c, 0x83, 0x2a, 0x39, 0x23, 0x6f, 0xc6, 0x83, 0xaf, 0x84, 0x88, 0x95, 0x81, 0xbb, 0xc4, 0x7e, 0x7c, 0x83, 0x2a, 0x39, 0x23, 0x6f, 0xc6, 0x83, 0xaf, 0x84, 0x88, 0x95, 0x81,
@ -85,7 +81,7 @@ namespace crypto
0xb3, 0xdb, 0xb1, 0x4a, 0x90, 0x5e, 0x7b, 0x2b, 0x3e, 0x93, 0xbe, 0x47, 0x08, 0xcb, 0xcc, 0x82 0xb3, 0xdb, 0xb1, 0x4a, 0x90, 0x5e, 0x7b, 0x2b, 0x3e, 0x93, 0xbe, 0x47, 0x08, 0xcb, 0xcc, 0x82
}; };
constexpr int rsae_ = 65537; const int rsae_ = 65537;
struct CryptoConstants struct CryptoConstants
{ {
@ -150,37 +146,6 @@ namespace crypto
#define dsap GetCryptoConstants ().dsap #define dsap GetCryptoConstants ().dsap
#define dsaq GetCryptoConstants ().dsaq #define dsaq GetCryptoConstants ().dsaq
#define dsag GetCryptoConstants ().dsag #define dsag GetCryptoConstants ().dsag
#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0
EVP_PKEY * CreateDSA (BIGNUM * pubKey, BIGNUM * privKey)
{
EVP_PKEY * pkey = nullptr;
int selection = EVP_PKEY_KEY_PARAMETERS;
auto bld = OSSL_PARAM_BLD_new();
OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_FFC_P, dsap);
OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_FFC_Q, dsaq);
OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_FFC_G, dsag);
if (pubKey)
{
OSSL_PARAM_BLD_push_BN (bld, OSSL_PKEY_PARAM_PUB_KEY, pubKey);
selection = EVP_PKEY_PUBLIC_KEY;
}
if (privKey)
{
OSSL_PARAM_BLD_push_BN (bld, OSSL_PKEY_PARAM_PRIV_KEY, privKey);
selection = EVP_PKEY_KEYPAIR;
}
auto params = OSSL_PARAM_BLD_to_param(bld);
EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name (NULL, "DSA", NULL);
EVP_PKEY_fromdata_init(ctx);
EVP_PKEY_fromdata(ctx, &pkey, selection, params);
EVP_PKEY_CTX_free(ctx);
OSSL_PARAM_free(params);
OSSL_PARAM_BLD_free(bld);
return pkey;
}
#else
DSA * CreateDSA () DSA * CreateDSA ()
{ {
DSA * dsa = DSA_new (); DSA * dsa = DSA_new ();
@ -188,7 +153,6 @@ namespace crypto
DSA_set0_key (dsa, NULL, NULL); DSA_set0_key (dsa, NULL, NULL);
return dsa; return dsa;
} }
#endif
// DH/ElGamal // DH/ElGamal
@ -821,18 +785,6 @@ namespace crypto
// Noise // Noise
void NoiseSymmetricState::Init (const uint8_t * ck, const uint8_t * hh, const uint8_t * pub)
{
// pub is Bob's public static key, hh = SHA256(h)
memcpy (m_CK, ck, 32);
SHA256_CTX ctx;
SHA256_Init (&ctx);
SHA256_Update (&ctx, hh, 32);
SHA256_Update (&ctx, pub, 32);
SHA256_Final (m_H, &ctx); // h = MixHash(pub) = SHA256(hh || pub)
m_N = 0;
}
void NoiseSymmetricState::MixHash (const uint8_t * buf, size_t len) void NoiseSymmetricState::MixHash (const uint8_t * buf, size_t len)
{ {
SHA256_CTX ctx; SHA256_CTX ctx;
@ -856,93 +808,74 @@ namespace crypto
{ {
HKDF (m_CK, sharedSecret, 32, "", m_CK); HKDF (m_CK, sharedSecret, 32, "", m_CK);
// new ck is m_CK[0:31], key is m_CK[32:63] // new ck is m_CK[0:31], key is m_CK[32:63]
m_N = 0;
} }
bool NoiseSymmetricState::Encrypt (const uint8_t * in, uint8_t * out, size_t len) static void InitNoiseState (NoiseSymmetricState& state, const uint8_t * ck,
const uint8_t * hh, const uint8_t * pub)
{ {
uint8_t nonce[12]; // pub is Bob's public static key, hh = SHA256(h)
if (m_N) memcpy (state.m_CK, ck, 32);
{ SHA256_CTX ctx;
memset (nonce, 0, 4); SHA256_Init (&ctx);
htole64buf (nonce + 4, m_N); SHA256_Update (&ctx, hh, 32);
} SHA256_Update (&ctx, pub, 32);
else SHA256_Final (state.m_H, &ctx); // h = MixHash(pub) = SHA256(hh || pub)
memset (nonce, 0, 12);
auto ret = AEADChaCha20Poly1305 (in, len, m_H, 32, m_CK + 32, nonce, out, len + 16, true);
if (ret) m_N++;
return ret;
}
bool NoiseSymmetricState::Decrypt (const uint8_t * in, uint8_t * out, size_t len)
{
uint8_t nonce[12];
if (m_N)
{
memset (nonce, 0, 4);
htole64buf (nonce + 4, m_N);
}
else
memset (nonce, 0, 12);
auto ret = AEADChaCha20Poly1305 (in, len, m_H, 32, m_CK + 32, nonce, out, len, false);
if (ret) m_N++;
return ret;
} }
void InitNoiseNState (NoiseSymmetricState& state, const uint8_t * pub) void InitNoiseNState (NoiseSymmetricState& state, const uint8_t * pub)
{ {
static constexpr char protocolName[] = "Noise_N_25519_ChaChaPoly_SHA256"; // 31 chars static const char protocolName[] = "Noise_N_25519_ChaChaPoly_SHA256"; // 31 chars
static constexpr uint8_t hh[32] = static const uint8_t hh[32] =
{ {
0x69, 0x4d, 0x52, 0x44, 0x5a, 0x27, 0xd9, 0xad, 0xfa, 0xd2, 0x9c, 0x76, 0x32, 0x39, 0x5d, 0xc1, 0x69, 0x4d, 0x52, 0x44, 0x5a, 0x27, 0xd9, 0xad, 0xfa, 0xd2, 0x9c, 0x76, 0x32, 0x39, 0x5d, 0xc1,
0xe4, 0x35, 0x4c, 0x69, 0xb4, 0xf9, 0x2e, 0xac, 0x8a, 0x1e, 0xe4, 0x6a, 0x9e, 0xd2, 0x15, 0x54 0xe4, 0x35, 0x4c, 0x69, 0xb4, 0xf9, 0x2e, 0xac, 0x8a, 0x1e, 0xe4, 0x6a, 0x9e, 0xd2, 0x15, 0x54
}; // hh = SHA256(protocol_name || 0) }; // hh = SHA256(protocol_name || 0)
state.Init ((const uint8_t *)protocolName, hh, pub); // ck = protocol_name || 0 InitNoiseState (state, (const uint8_t *)protocolName, hh, pub); // ck = protocol_name || 0
} }
void InitNoiseXKState (NoiseSymmetricState& state, const uint8_t * pub) void InitNoiseXKState (NoiseSymmetricState& state, const uint8_t * pub)
{ {
static constexpr uint8_t protocolNameHash[32] = static const uint8_t protocolNameHash[32] =
{ {
0x72, 0xe8, 0x42, 0xc5, 0x45, 0xe1, 0x80, 0x80, 0xd3, 0x9c, 0x44, 0x93, 0xbb, 0x91, 0xd7, 0xed, 0x72, 0xe8, 0x42, 0xc5, 0x45, 0xe1, 0x80, 0x80, 0xd3, 0x9c, 0x44, 0x93, 0xbb, 0x91, 0xd7, 0xed,
0xf2, 0x28, 0x98, 0x17, 0x71, 0x21, 0x8c, 0x1f, 0x62, 0x4e, 0x20, 0x6f, 0x28, 0xd3, 0x2f, 0x71 0xf2, 0x28, 0x98, 0x17, 0x71, 0x21, 0x8c, 0x1f, 0x62, 0x4e, 0x20, 0x6f, 0x28, 0xd3, 0x2f, 0x71
}; // SHA256 ("Noise_XKaesobfse+hs2+hs3_25519_ChaChaPoly_SHA256") }; // SHA256 ("Noise_XKaesobfse+hs2+hs3_25519_ChaChaPoly_SHA256")
static constexpr uint8_t hh[32] = static const uint8_t hh[32] =
{ {
0x49, 0xff, 0x48, 0x3f, 0xc4, 0x04, 0xb9, 0xb2, 0x6b, 0x11, 0x94, 0x36, 0x72, 0xff, 0x05, 0xb5, 0x49, 0xff, 0x48, 0x3f, 0xc4, 0x04, 0xb9, 0xb2, 0x6b, 0x11, 0x94, 0x36, 0x72, 0xff, 0x05, 0xb5,
0x61, 0x27, 0x03, 0x31, 0xba, 0x89, 0xb8, 0xfc, 0x33, 0x15, 0x93, 0x87, 0x57, 0xdd, 0x3d, 0x1e 0x61, 0x27, 0x03, 0x31, 0xba, 0x89, 0xb8, 0xfc, 0x33, 0x15, 0x93, 0x87, 0x57, 0xdd, 0x3d, 0x1e
}; // SHA256 (protocolNameHash) }; // SHA256 (protocolNameHash)
state.Init (protocolNameHash, hh, pub); InitNoiseState (state, protocolNameHash, hh, pub);
} }
void InitNoiseXKState1 (NoiseSymmetricState& state, const uint8_t * pub) void InitNoiseXKState1 (NoiseSymmetricState& state, const uint8_t * pub)
{ {
static constexpr uint8_t protocolNameHash[32] = static const uint8_t protocolNameHash[32] =
{ {
0xb1, 0x37, 0x22, 0x81, 0x74, 0x23, 0xa8, 0xfd, 0xf4, 0x2d, 0xf2, 0xe6, 0x0e, 0xd1, 0xed, 0xf4, 0xb1, 0x37, 0x22, 0x81, 0x74, 0x23, 0xa8, 0xfd, 0xf4, 0x2d, 0xf2, 0xe6, 0x0e, 0xd1, 0xed, 0xf4,
0x1b, 0x93, 0x07, 0x1d, 0xb1, 0xec, 0x24, 0xa3, 0x67, 0xf7, 0x84, 0xec, 0x27, 0x0d, 0x81, 0x32 0x1b, 0x93, 0x07, 0x1d, 0xb1, 0xec, 0x24, 0xa3, 0x67, 0xf7, 0x84, 0xec, 0x27, 0x0d, 0x81, 0x32
}; // SHA256 ("Noise_XKchaobfse+hs1+hs2+hs3_25519_ChaChaPoly_SHA256") }; // SHA256 ("Noise_XKchaobfse+hs1+hs2+hs3_25519_ChaChaPoly_SHA256")
static constexpr uint8_t hh[32] = static const uint8_t hh[32] =
{ {
0xdc, 0x85, 0xe6, 0xaf, 0x7b, 0x02, 0x65, 0x0c, 0xf1, 0xf9, 0x0d, 0x71, 0xfb, 0xc6, 0xd4, 0x53, 0xdc, 0x85, 0xe6, 0xaf, 0x7b, 0x02, 0x65, 0x0c, 0xf1, 0xf9, 0x0d, 0x71, 0xfb, 0xc6, 0xd4, 0x53,
0xa7, 0xcf, 0x6d, 0xbf, 0xbd, 0x52, 0x5e, 0xa5, 0xb5, 0x79, 0x1c, 0x47, 0xb3, 0x5e, 0xbc, 0x33 0xa7, 0xcf, 0x6d, 0xbf, 0xbd, 0x52, 0x5e, 0xa5, 0xb5, 0x79, 0x1c, 0x47, 0xb3, 0x5e, 0xbc, 0x33
}; // SHA256 (protocolNameHash) }; // SHA256 (protocolNameHash)
state.Init (protocolNameHash, hh, pub); InitNoiseState (state, protocolNameHash, hh, pub);
} }
void InitNoiseIKState (NoiseSymmetricState& state, const uint8_t * pub) void InitNoiseIKState (NoiseSymmetricState& state, const uint8_t * pub)
{ {
static constexpr uint8_t protocolNameHash[32] = static const uint8_t protocolNameHash[32] =
{ {
0x4c, 0xaf, 0x11, 0xef, 0x2c, 0x8e, 0x36, 0x56, 0x4c, 0x53, 0xe8, 0x88, 0x85, 0x06, 0x4d, 0xba, 0x4c, 0xaf, 0x11, 0xef, 0x2c, 0x8e, 0x36, 0x56, 0x4c, 0x53, 0xe8, 0x88, 0x85, 0x06, 0x4d, 0xba,
0xac, 0xbe, 0x00, 0x54, 0xad, 0x17, 0x8f, 0x80, 0x79, 0xa6, 0x46, 0x82, 0x7e, 0x6e, 0xe4, 0x0c 0xac, 0xbe, 0x00, 0x54, 0xad, 0x17, 0x8f, 0x80, 0x79, 0xa6, 0x46, 0x82, 0x7e, 0x6e, 0xe4, 0x0c
}; // SHA256("Noise_IKelg2+hs2_25519_ChaChaPoly_SHA256"), 40 bytes }; // SHA256("Noise_IKelg2+hs2_25519_ChaChaPoly_SHA256"), 40 bytes
static constexpr uint8_t hh[32] = static const uint8_t hh[32] =
{ {
0x9c, 0xcf, 0x85, 0x2c, 0xc9, 0x3b, 0xb9, 0x50, 0x44, 0x41, 0xe9, 0x50, 0xe0, 0x1d, 0x52, 0x32, 0x9c, 0xcf, 0x85, 0x2c, 0xc9, 0x3b, 0xb9, 0x50, 0x44, 0x41, 0xe9, 0x50, 0xe0, 0x1d, 0x52, 0x32,
0x2e, 0x0d, 0x47, 0xad, 0xd1, 0xe9, 0xa5, 0x55, 0xf7, 0x55, 0xb5, 0x69, 0xae, 0x18, 0x3b, 0x5c 0x2e, 0x0d, 0x47, 0xad, 0xd1, 0xe9, 0xa5, 0x55, 0xf7, 0x55, 0xb5, 0x69, 0xae, 0x18, 0x3b, 0x5c
}; // SHA256 (protocolNameHash) }; // SHA256 (protocolNameHash)
state.Init (protocolNameHash, hh, pub); InitNoiseState (state, protocolNameHash, hh, pub);
} }
// init and terminate // init and terminate

View file

@ -33,9 +33,6 @@
# if (!defined(LIBRESSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER != 0x030000000)) // 3.0.0, regression in SipHash, not implemented in LibreSSL # if (!defined(LIBRESSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER != 0x030000000)) // 3.0.0, regression in SipHash, not implemented in LibreSSL
# define OPENSSL_SIPHASH 1 # define OPENSSL_SIPHASH 1
# endif # endif
# if (OPENSSL_VERSION_NUMBER >= 0x030500000) // 3.5.0
# define OPENSSL_PQ 1
# endif
#endif #endif
namespace i2p namespace i2p
@ -45,11 +42,7 @@ namespace crypto
bool bn2buf (const BIGNUM * bn, uint8_t * buf, size_t len); bool bn2buf (const BIGNUM * bn, uint8_t * buf, size_t len);
// DSA // DSA
#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0
EVP_PKEY * CreateDSA (BIGNUM * pubKey = nullptr, BIGNUM * privKey = nullptr);
#else
DSA * CreateDSA (); DSA * CreateDSA ();
#endif
// RSA // RSA
const BIGNUM * GetRSAE (); const BIGNUM * GetRSAE ();
@ -255,16 +248,10 @@ namespace crypto
struct NoiseSymmetricState struct NoiseSymmetricState
{ {
uint8_t m_H[32] /*h*/, m_CK[64] /*[ck, k]*/; uint8_t m_H[32] /*h*/, m_CK[64] /*[ck, k]*/;
uint64_t m_N;
void Init (const uint8_t * ck, const uint8_t * hh, const uint8_t * pub);
void MixHash (const uint8_t * buf, size_t len); void MixHash (const uint8_t * buf, size_t len);
void MixHash (const std::vector<std::pair<uint8_t *, size_t> >& bufs); void MixHash (const std::vector<std::pair<uint8_t *, size_t> >& bufs);
void MixKey (const uint8_t * sharedSecret); void MixKey (const uint8_t * sharedSecret);
bool Encrypt (const uint8_t * in, uint8_t * out, size_t len); // out length = len + 16
bool Decrypt (const uint8_t * in, uint8_t * out, size_t len); // len without 16 bytes tag
}; };
void InitNoiseNState (NoiseSymmetricState& state, const uint8_t * pub); // Noise_N (tunnels, router) void InitNoiseNState (NoiseSymmetricState& state, const uint8_t * pub); // Noise_N (tunnels, router)

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2021, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -181,21 +181,5 @@ namespace crypto
k.GetPrivateKey (priv); k.GetPrivateKey (priv);
memcpy (pub, k.GetPublicKey (), 32); memcpy (pub, k.GetPublicKey (), 32);
} }
LocalEncryptionKey::LocalEncryptionKey (i2p::data::CryptoKeyType t): keyType(t)
{
pub.resize (GetCryptoPublicKeyLen (keyType));
priv.resize (GetCryptoPrivateKeyLen (keyType));
}
void LocalEncryptionKey::GenerateKeys ()
{
i2p::data::PrivateKeys::GenerateCryptoKeyPair (keyType, priv.data (), pub.data ());
}
void LocalEncryptionKey::CreateDecryptor ()
{
decryptor = i2p::data::PrivateKeys::CreateDecryptor (keyType, priv.data ());
}
} }
} }

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2021, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -11,7 +11,6 @@
#include <inttypes.h> #include <inttypes.h>
#include "Crypto.h" #include "Crypto.h"
#include "Identity.h"
namespace i2p namespace i2p
{ {
@ -158,50 +157,7 @@ namespace crypto
X25519Keys m_StaticKeys; X25519Keys m_StaticKeys;
}; };
void CreateECIESX25519AEADRatchetRandomKeys (uint8_t * priv, uint8_t * pub); // including hybrid void CreateECIESX25519AEADRatchetRandomKeys (uint8_t * priv, uint8_t * pub);
constexpr size_t GetCryptoPrivateKeyLen (i2p::data::CryptoKeyType type)
{
switch (type)
{
case i2p::data::CRYPTO_KEY_TYPE_ELGAMAL: return 256;
case i2p::data::CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC: return 32;
case i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD: return 32;
// ML-KEM hybrid
case i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM512_X25519_AEAD:
case i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM768_X25519_AEAD:
case i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM1024_X25519_AEAD:
return 32;
};
return 0;
}
constexpr size_t GetCryptoPublicKeyLen (i2p::data::CryptoKeyType type)
{
switch (type)
{
case i2p::data::CRYPTO_KEY_TYPE_ELGAMAL: return 256;
case i2p::data::CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC: return 32;
case i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD: return 32;
// ML-KEM hybrid
case i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM512_X25519_AEAD:
case i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM768_X25519_AEAD:
case i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM1024_X25519_AEAD:
return 32;
};
return 0;
}
struct LocalEncryptionKey
{
std::vector<uint8_t> pub, priv;
i2p::data::CryptoKeyType keyType;
std::shared_ptr<CryptoKeyDecryptor> decryptor;
LocalEncryptionKey (i2p::data::CryptoKeyType t);
void GenerateKeys ();
void CreateDecryptor ();
};
} }
} }

View file

@ -13,7 +13,6 @@
#include <vector> #include <vector>
#include <boost/algorithm/string.hpp> #include <boost/algorithm/string.hpp>
#include "Crypto.h" #include "Crypto.h"
#include "ECIESX25519AEADRatchetSession.h"
#include "Log.h" #include "Log.h"
#include "FS.h" #include "FS.h"
#include "Timestamp.h" #include "Timestamp.h"
@ -378,12 +377,10 @@ namespace client
{ {
I2NPMessageType typeID = (I2NPMessageType)(buf[I2NP_HEADER_TYPEID_OFFSET]); I2NPMessageType typeID = (I2NPMessageType)(buf[I2NP_HEADER_TYPEID_OFFSET]);
uint32_t msgID = bufbe32toh (buf + I2NP_HEADER_MSGID_OFFSET); uint32_t msgID = bufbe32toh (buf + I2NP_HEADER_MSGID_OFFSET);
LeaseSetDestination::HandleCloveI2NPMessage (typeID, buf + I2NP_HEADER_SIZE, LeaseSetDestination::HandleCloveI2NPMessage (typeID, buf + I2NP_HEADER_SIZE, GetI2NPMessageLength(buf, len) - I2NP_HEADER_SIZE, msgID);
GetI2NPMessageLength(buf, len) - I2NP_HEADER_SIZE, msgID, nullptr);
} }
bool LeaseSetDestination::HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, bool LeaseSetDestination::HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len, uint32_t msgID)
size_t len, uint32_t msgID, i2p::garlic::ECIESX25519AEADRatchetSession * from)
{ {
switch (typeID) switch (typeID)
{ {
@ -398,7 +395,7 @@ namespace client
m_Pool->ProcessTunnelTest (bufbe32toh (payload + TUNNEL_TEST_MSGID_OFFSET), bufbe64toh (payload + TUNNEL_TEST_TIMESTAMP_OFFSET)); m_Pool->ProcessTunnelTest (bufbe32toh (payload + TUNNEL_TEST_MSGID_OFFSET), bufbe64toh (payload + TUNNEL_TEST_TIMESTAMP_OFFSET));
break; break;
case eI2NPDatabaseStore: case eI2NPDatabaseStore:
HandleDatabaseStoreMessage (payload, len, from); HandleDatabaseStoreMessage (payload, len);
break; break;
case eI2NPDatabaseSearchReply: case eI2NPDatabaseSearchReply:
HandleDatabaseSearchReplyMessage (payload, len); HandleDatabaseSearchReplyMessage (payload, len);
@ -413,8 +410,7 @@ namespace client
return true; return true;
} }
void LeaseSetDestination::HandleDatabaseStoreMessage (const uint8_t * buf, size_t len, void LeaseSetDestination::HandleDatabaseStoreMessage (const uint8_t * buf, size_t len)
i2p::garlic::ECIESX25519AEADRatchetSession * from)
{ {
if (len < DATABASE_STORE_HEADER_SIZE) if (len < DATABASE_STORE_HEADER_SIZE)
{ {
@ -469,8 +465,7 @@ namespace client
if (buf[DATABASE_STORE_TYPE_OFFSET] == i2p::data::NETDB_STORE_TYPE_LEASESET) if (buf[DATABASE_STORE_TYPE_OFFSET] == i2p::data::NETDB_STORE_TYPE_LEASESET)
leaseSet = std::make_shared<i2p::data::LeaseSet> (buf + offset, len - offset); // LeaseSet leaseSet = std::make_shared<i2p::data::LeaseSet> (buf + offset, len - offset); // LeaseSet
else else
leaseSet = std::make_shared<i2p::data::LeaseSet2> (buf[DATABASE_STORE_TYPE_OFFSET], leaseSet = std::make_shared<i2p::data::LeaseSet2> (buf[DATABASE_STORE_TYPE_OFFSET], buf + offset, len - offset, true, GetPreferredCryptoType () ); // LeaseSet2
buf + offset, len - offset, true, from ? from->GetRemoteStaticKeyType () : GetPreferredCryptoType () ); // LeaseSet2
if (leaseSet->IsValid () && leaseSet->GetIdentHash () == key && !leaseSet->IsExpired ()) if (leaseSet->IsValid () && leaseSet->GetIdentHash () == key && !leaseSet->IsExpired ())
{ {
if (leaseSet->GetIdentHash () != GetIdentHash ()) if (leaseSet->GetIdentHash () != GetIdentHash ())
@ -499,8 +494,7 @@ namespace client
if (request->requestedBlindedKey) if (request->requestedBlindedKey)
{ {
auto ls2 = std::make_shared<i2p::data::LeaseSet2> (buf + offset, len - offset, auto ls2 = std::make_shared<i2p::data::LeaseSet2> (buf + offset, len - offset,
request->requestedBlindedKey, m_LeaseSetPrivKey ? ((const uint8_t *)*m_LeaseSetPrivKey) : nullptr, request->requestedBlindedKey, m_LeaseSetPrivKey ? ((const uint8_t *)*m_LeaseSetPrivKey) : nullptr , GetPreferredCryptoType ());
from ? from->GetRemoteStaticKeyType () : GetPreferredCryptoType ());
if (ls2->IsValid () && !ls2->IsExpired ()) if (ls2->IsValid () && !ls2->IsExpired ())
{ {
leaseSet = ls2; leaseSet = ls2;
@ -1000,10 +994,17 @@ namespace client
} }
} }
i2p::data::CryptoKeyType LeaseSetDestination::GetPreferredCryptoType () const
{
if (SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD))
return i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD;
return i2p::data::CRYPTO_KEY_TYPE_ELGAMAL;
}
ClientDestination::ClientDestination (boost::asio::io_context& service, const i2p::data::PrivateKeys& keys, ClientDestination::ClientDestination (boost::asio::io_context& service, const i2p::data::PrivateKeys& keys,
bool isPublic, const std::map<std::string, std::string> * params): bool isPublic, const std::map<std::string, std::string> * params):
LeaseSetDestination (service, isPublic, params), LeaseSetDestination (service, isPublic, params),
m_Keys (keys), m_PreferredCryptoType (0), m_StreamingAckDelay (DEFAULT_INITIAL_ACK_DELAY), m_Keys (keys), m_StreamingAckDelay (DEFAULT_INITIAL_ACK_DELAY),
m_StreamingOutboundSpeed (DEFAULT_MAX_OUTBOUND_SPEED), m_StreamingOutboundSpeed (DEFAULT_MAX_OUTBOUND_SPEED),
m_StreamingInboundSpeed (DEFAULT_MAX_INBOUND_SPEED), m_StreamingInboundSpeed (DEFAULT_MAX_INBOUND_SPEED),
m_StreamingMaxConcurrentStreams (DEFAULT_MAX_CONCURRENT_STREAMS), m_StreamingMaxConcurrentStreams (DEFAULT_MAX_CONCURRENT_STREAMS),
@ -1028,15 +1029,7 @@ namespace client
{ {
try try
{ {
i2p::data::CryptoKeyType cryptoType = std::stoi(it1); encryptionKeyTypes.insert (std::stoi(it1));
#if !OPENSSL_PQ
if (cryptoType <= i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) // skip PQ keys if not supported
#endif
{
if (!m_PreferredCryptoType && cryptoType)
m_PreferredCryptoType = cryptoType; // first non-zero in the list
encryptionKeyTypes.insert (cryptoType);
}
} }
catch (std::exception& ex) catch (std::exception& ex)
{ {
@ -1053,15 +1046,20 @@ namespace client
for (auto& it: encryptionKeyTypes) for (auto& it: encryptionKeyTypes)
{ {
auto encryptionKey = std::make_shared<i2p::crypto::LocalEncryptionKey> (it); auto encryptionKey = new EncryptionKey (it);
if (IsPublic ()) if (IsPublic ())
PersistTemporaryKeys (encryptionKey); PersistTemporaryKeys (encryptionKey);
else else
encryptionKey->GenerateKeys (); encryptionKey->GenerateKeys ();
encryptionKey->CreateDecryptor (); encryptionKey->CreateDecryptor ();
if (it > i2p::data::CRYPTO_KEY_TYPE_ELGAMAL && GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_LEASESET) if (it == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)
SetLeaseSetType (i2p::data::NETDB_STORE_TYPE_STANDARD_LEASESET2); // Only DSA can use LeaseSet1 {
m_EncryptionKeys.emplace (it, encryptionKey); m_ECIESx25519EncryptionKey.reset (encryptionKey);
if (GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_LEASESET)
SetLeaseSetType (i2p::data::NETDB_STORE_TYPE_STANDARD_LEASESET2); // Rathets must use LeaseSet2
}
else
m_StandardEncryptionKey.reset (encryptionKey);
} }
if (IsPublic ()) if (IsPublic ())
@ -1411,56 +1409,31 @@ namespace client
return ret; return ret;
} }
void ClientDestination::PersistTemporaryKeys (std::shared_ptr<i2p::crypto::LocalEncryptionKey> keys) void ClientDestination::PersistTemporaryKeys (EncryptionKey * keys)
{ {
if (!keys) return; if (!keys) return;
std::string ident = GetIdentHash().ToBase32(); std::string ident = GetIdentHash().ToBase32();
std::string path = i2p::fs::DataDirPath("destinations", ident + "." + std::to_string (keys->keyType) + ".dat"); std::string path = i2p::fs::DataDirPath("destinations", ident + "." + std::to_string (keys->keyType) + ".dat");
std::ifstream f(path, std::ifstream::binary); std::ifstream f(path, std::ifstream::binary);
if (f)
{
size_t len = 0;
if (keys->keyType == i2p::data::CRYPTO_KEY_TYPE_ELGAMAL)
len = 512;
else if (keys->keyType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)
{
f.seekg (0, std::ios::end);
len = f.tellg();
f.seekg (0, std::ios::beg);
}
if (len == 512) if (f) {
{ f.read ((char *)keys->pub, 256);
char pub[256], priv[256]; f.read ((char *)keys->priv, 256);
f.read (pub, 256);
memcpy (keys->pub.data(), pub, keys->pub.size());
f.read (priv, 256);
memcpy (keys->priv.data (), priv, keys->priv.size ());
}
else
{
f.read ((char *)keys->pub.data(), keys->pub.size());
f.read ((char *)keys->priv.data(), keys->priv.size());
}
if (f)
return; return;
else
LogPrint(eLogWarning, "Destination: Can't read keys from ", path);
} }
LogPrint (eLogInfo, "Destination: Creating new temporary keys of type ", keys->keyType, " for address ", ident, ".b32.i2p"); LogPrint (eLogInfo, "Destination: Creating new temporary keys of type for address ", ident, ".b32.i2p");
memset (keys->priv.data (), 0, keys->priv.size ()); memset (keys->priv, 0, 256);
memset (keys->pub.data (), 0, keys->pub.size ()); memset (keys->pub, 0, 256);
keys->GenerateKeys (); keys->GenerateKeys ();
// TODO:: persist crypto key type
std::ofstream f1 (path, std::ofstream::binary | std::ofstream::out); std::ofstream f1 (path, std::ofstream::binary | std::ofstream::out);
if (f1) if (f1) {
{ f1.write ((char *)keys->pub, 256);
f1.write ((char *)keys->pub.data (), keys->pub.size ()); f1.write ((char *)keys->priv, 256);
f1.write ((char *)keys->priv.data (), keys->priv.size ()); return;
} }
if (!f1) LogPrint(eLogCritical, "Destinations: Can't save keys to ", path);
LogPrint(eLogError, "Destination: Can't save keys to ", path);
} }
void ClientDestination::CreateNewLeaseSet (const std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> >& tunnels) void ClientDestination::CreateNewLeaseSet (const std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> >& tunnels)
@ -1468,10 +1441,9 @@ namespace client
std::shared_ptr<i2p::data::LocalLeaseSet> leaseSet; std::shared_ptr<i2p::data::LocalLeaseSet> leaseSet;
if (GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_LEASESET) if (GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_LEASESET)
{ {
auto it = m_EncryptionKeys.find (i2p::data::CRYPTO_KEY_TYPE_ELGAMAL); if (m_StandardEncryptionKey)
if (it != m_EncryptionKeys.end ())
{ {
leaseSet = std::make_shared<i2p::data::LocalLeaseSet> (GetIdentity (), it->second->pub.data (), tunnels); leaseSet = std::make_shared<i2p::data::LocalLeaseSet> (GetIdentity (), m_StandardEncryptionKey->pub, tunnels);
// sign // sign
Sign (leaseSet->GetBuffer (), leaseSet->GetBufferLen () - leaseSet->GetSignatureLen (), leaseSet->GetSignature ()); Sign (leaseSet->GetBuffer (), leaseSet->GetBufferLen () - leaseSet->GetSignatureLen (), leaseSet->GetSignature ());
} }
@ -1481,26 +1453,11 @@ namespace client
else else
{ {
// standard LS2 (type 3) first // standard LS2 (type 3) first
if (m_EncryptionKeys.empty ()) i2p::data::LocalLeaseSet2::KeySections keySections;
{ if (m_ECIESx25519EncryptionKey)
LogPrint (eLogError, "Destinations: No encryption keys"); keySections.push_back ({m_ECIESx25519EncryptionKey->keyType, 32, m_ECIESx25519EncryptionKey->pub} );
return; if (m_StandardEncryptionKey)
} keySections.push_back ({m_StandardEncryptionKey->keyType, (uint16_t)m_StandardEncryptionKey->decryptor->GetPublicKeyLen (), m_StandardEncryptionKey->pub} );
i2p::data::LocalLeaseSet2::EncryptionKeys keySections;
std::shared_ptr<const i2p::crypto::LocalEncryptionKey> preferredSection;
if (m_EncryptionKeys.size () == 1)
preferredSection = m_EncryptionKeys.begin ()->second; // only key
else
{
for (const auto& it: m_EncryptionKeys)
if (it.first == m_PreferredCryptoType)
preferredSection = it.second;
else
keySections.push_back (it.second);
}
if (preferredSection)
keySections.push_front (preferredSection); // make preferred first
auto publishedTimestamp = i2p::util::GetSecondsSinceEpoch (); auto publishedTimestamp = i2p::util::GetSecondsSinceEpoch ();
if (publishedTimestamp <= m_LastPublishedTimestamp) if (publishedTimestamp <= m_LastPublishedTimestamp)
@ -1526,22 +1483,11 @@ namespace client
bool ClientDestination::Decrypt (const uint8_t * encrypted, uint8_t * data, i2p::data::CryptoKeyType preferredCrypto) const bool ClientDestination::Decrypt (const uint8_t * encrypted, uint8_t * data, i2p::data::CryptoKeyType preferredCrypto) const
{ {
std::shared_ptr<i2p::crypto::LocalEncryptionKey> encryptionKey; if (preferredCrypto == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)
if (!m_EncryptionKeys.empty ()) if (m_ECIESx25519EncryptionKey && m_ECIESx25519EncryptionKey->decryptor)
{ return m_ECIESx25519EncryptionKey->decryptor->Decrypt (encrypted, data);
if (m_EncryptionKeys.rbegin ()->first == preferredCrypto) if (m_StandardEncryptionKey && m_StandardEncryptionKey->decryptor)
encryptionKey = m_EncryptionKeys.rbegin ()->second; return m_StandardEncryptionKey->decryptor->Decrypt (encrypted, data);
else
{
auto it = m_EncryptionKeys.find (preferredCrypto);
if (it != m_EncryptionKeys.end ())
encryptionKey = it->second;
}
if (!encryptionKey)
encryptionKey = m_EncryptionKeys.rbegin ()->second;
}
if (encryptionKey)
return encryptionKey->decryptor->Decrypt (encrypted, data);
else else
LogPrint (eLogError, "Destinations: Decryptor is not set"); LogPrint (eLogError, "Destinations: Decryptor is not set");
return false; return false;
@ -1549,26 +1495,14 @@ namespace client
bool ClientDestination::SupportsEncryptionType (i2p::data::CryptoKeyType keyType) const bool ClientDestination::SupportsEncryptionType (i2p::data::CryptoKeyType keyType) const
{ {
#if __cplusplus >= 202002L // C++20 return keyType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD ? (bool)m_ECIESx25519EncryptionKey : (bool)m_StandardEncryptionKey;
return m_EncryptionKeys.contains (keyType);
#else
return m_EncryptionKeys.count (keyType) > 0;
#endif
}
i2p::data::CryptoKeyType ClientDestination::GetRatchetsHighestCryptoType () const
{
if (m_EncryptionKeys.empty ()) return 0;
auto cryptoType = m_EncryptionKeys.rbegin ()->first;
return cryptoType >= i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD ? cryptoType : 0;
} }
const uint8_t * ClientDestination::GetEncryptionPublicKey (i2p::data::CryptoKeyType keyType) const const uint8_t * ClientDestination::GetEncryptionPublicKey (i2p::data::CryptoKeyType keyType) const
{ {
auto it = m_EncryptionKeys.find (keyType); if (keyType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)
if (it != m_EncryptionKeys.end ()) return m_ECIESx25519EncryptionKey ? m_ECIESx25519EncryptionKey->pub : nullptr;
return it->second->pub.data (); return m_StandardEncryptionKey ? m_StandardEncryptionKey->pub : nullptr;
return nullptr;
} }
void ClientDestination::ReadAuthKey (const std::string& group, const std::map<std::string, std::string> * params) void ClientDestination::ReadAuthKey (const std::string& group, const std::map<std::string, std::string> * params)

View file

@ -22,7 +22,6 @@
#include "Identity.h" #include "Identity.h"
#include "TunnelPool.h" #include "TunnelPool.h"
#include "Crypto.h" #include "Crypto.h"
#include "CryptoKey.h"
#include "LeaseSet.h" #include "LeaseSet.h"
#include "Garlic.h" #include "Garlic.h"
#include "NetDb.hpp" #include "NetDb.hpp"
@ -164,15 +163,13 @@ namespace client
// implements GarlicDestination // implements GarlicDestination
void HandleI2NPMessage (const uint8_t * buf, size_t len) override; void HandleI2NPMessage (const uint8_t * buf, size_t len) override;
bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len, uint32_t msgID) override;
size_t len, uint32_t msgID, i2p::garlic::ECIESX25519AEADRatchetSession * from) override;
void SetLeaseSet (std::shared_ptr<const i2p::data::LocalLeaseSet> newLeaseSet); void SetLeaseSet (std::shared_ptr<const i2p::data::LocalLeaseSet> newLeaseSet);
int GetLeaseSetType () const { return m_LeaseSetType; }; int GetLeaseSetType () const { return m_LeaseSetType; };
void SetLeaseSetType (int leaseSetType) { m_LeaseSetType = leaseSetType; }; void SetLeaseSetType (int leaseSetType) { m_LeaseSetType = leaseSetType; };
int GetAuthType () const { return m_AuthType; }; int GetAuthType () const { return m_AuthType; };
virtual void CleanupDestination () {}; // additional clean up in derived classes virtual void CleanupDestination () {}; // additional clean up in derived classes
virtual i2p::data::CryptoKeyType GetPreferredCryptoType () const = 0;
// I2CP // I2CP
virtual void HandleDataMessage (const uint8_t * buf, size_t len) = 0; virtual void HandleDataMessage (const uint8_t * buf, size_t len) = 0;
virtual void CreateNewLeaseSet (const std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> >& tunnels) = 0; virtual void CreateNewLeaseSet (const std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> >& tunnels) = 0;
@ -185,7 +182,7 @@ namespace client
void HandlePublishConfirmationTimer (const boost::system::error_code& ecode); void HandlePublishConfirmationTimer (const boost::system::error_code& ecode);
void HandlePublishVerificationTimer (const boost::system::error_code& ecode); void HandlePublishVerificationTimer (const boost::system::error_code& ecode);
void HandlePublishDelayTimer (const boost::system::error_code& ecode); void HandlePublishDelayTimer (const boost::system::error_code& ecode);
void HandleDatabaseStoreMessage (const uint8_t * buf, size_t len, i2p::garlic::ECIESX25519AEADRatchetSession * from); void HandleDatabaseStoreMessage (const uint8_t * buf, size_t len);
void HandleDatabaseSearchReplyMessage (const uint8_t * buf, size_t len); void HandleDatabaseSearchReplyMessage (const uint8_t * buf, size_t len);
void HandleDeliveryStatusMessage (uint32_t msgID); void HandleDeliveryStatusMessage (uint32_t msgID);
@ -195,6 +192,7 @@ namespace client
void HandleRequestTimoutTimer (const boost::system::error_code& ecode, const i2p::data::IdentHash& dest); void HandleRequestTimoutTimer (const boost::system::error_code& ecode, const i2p::data::IdentHash& dest);
void HandleCleanupTimer (const boost::system::error_code& ecode); void HandleCleanupTimer (const boost::system::error_code& ecode);
void CleanupRemoteLeaseSets (); void CleanupRemoteLeaseSets ();
i2p::data::CryptoKeyType GetPreferredCryptoType () const;
private: private:
@ -231,14 +229,25 @@ namespace client
class ClientDestination: public LeaseSetDestination class ClientDestination: public LeaseSetDestination
{ {
struct EncryptionKey
{
uint8_t pub[256], priv[256];
i2p::data::CryptoKeyType keyType;
std::shared_ptr<i2p::crypto::CryptoKeyDecryptor> decryptor;
EncryptionKey (i2p::data::CryptoKeyType t):keyType(t) { memset (pub, 0, 256); memset (priv, 0, 256); };
void GenerateKeys () { i2p::data::PrivateKeys::GenerateCryptoKeyPair (keyType, priv, pub); };
void CreateDecryptor () { decryptor = i2p::data::PrivateKeys::CreateDecryptor (keyType, priv); };
};
public: public:
ClientDestination (boost::asio::io_context& service, const i2p::data::PrivateKeys& keys, ClientDestination (boost::asio::io_context& service, const i2p::data::PrivateKeys& keys,
bool isPublic, const std::map<std::string, std::string> * params = nullptr); bool isPublic, const std::map<std::string, std::string> * params = nullptr);
~ClientDestination (); ~ClientDestination ();
void Start () override; void Start ();
void Stop () override; void Stop ();
const i2p::data::PrivateKeys& GetPrivateKeys () const { return m_Keys; }; const i2p::data::PrivateKeys& GetPrivateKeys () const { return m_Keys; };
void Sign (const uint8_t * buf, int len, uint8_t * signature) const { m_Keys.Sign (buf, len, signature); }; void Sign (const uint8_t * buf, int len, uint8_t * signature) const { m_Keys.Sign (buf, len, signature); };
@ -275,28 +284,24 @@ namespace client
i2p::datagram::DatagramDestination * CreateDatagramDestination (bool gzip = true); i2p::datagram::DatagramDestination * CreateDatagramDestination (bool gzip = true);
// implements LocalDestination // implements LocalDestination
bool Decrypt (const uint8_t * encrypted, uint8_t * data, i2p::data::CryptoKeyType preferredCrypto) const override; bool Decrypt (const uint8_t * encrypted, uint8_t * data, i2p::data::CryptoKeyType preferredCrypto) const;
std::shared_ptr<const i2p::data::IdentityEx> GetIdentity () const override { return m_Keys.GetPublic (); }; std::shared_ptr<const i2p::data::IdentityEx> GetIdentity () const { return m_Keys.GetPublic (); };
bool SupportsEncryptionType (i2p::data::CryptoKeyType keyType) const override; bool SupportsEncryptionType (i2p::data::CryptoKeyType keyType) const;
const uint8_t * GetEncryptionPublicKey (i2p::data::CryptoKeyType keyType) const override; const uint8_t * GetEncryptionPublicKey (i2p::data::CryptoKeyType keyType) const;
protected: protected:
// GarlicDestionation void CleanupDestination ();
i2p::data::CryptoKeyType GetRatchetsHighestCryptoType () const override;
// LeaseSetDestination
void CleanupDestination () override;
i2p::data::CryptoKeyType GetPreferredCryptoType () const override { return m_PreferredCryptoType; }
// I2CP // I2CP
void HandleDataMessage (const uint8_t * buf, size_t len) override; void HandleDataMessage (const uint8_t * buf, size_t len);
void CreateNewLeaseSet (const std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> >& tunnels) override; void CreateNewLeaseSet (const std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> >& tunnels);
private: private:
std::shared_ptr<ClientDestination> GetSharedFromThis () { std::shared_ptr<ClientDestination> GetSharedFromThis () {
return std::static_pointer_cast<ClientDestination>(shared_from_this ()); return std::static_pointer_cast<ClientDestination>(shared_from_this ());
} }
void PersistTemporaryKeys (std::shared_ptr<i2p::crypto::LocalEncryptionKey> keys); void PersistTemporaryKeys (EncryptionKey * keys);
void ReadAuthKey (const std::string& group, const std::map<std::string, std::string> * params); void ReadAuthKey (const std::string& group, const std::map<std::string, std::string> * params);
template<typename Dest> template<typename Dest>
@ -305,8 +310,8 @@ namespace client
private: private:
i2p::data::PrivateKeys m_Keys; i2p::data::PrivateKeys m_Keys;
std::map<i2p::data::CryptoKeyType, std::shared_ptr<i2p::crypto::LocalEncryptionKey> > m_EncryptionKeys; // last is most preferable std::unique_ptr<EncryptionKey> m_StandardEncryptionKey;
i2p::data::CryptoKeyType m_PreferredCryptoType; std::unique_ptr<EncryptionKey> m_ECIESx25519EncryptionKey;
int m_StreamingAckDelay,m_StreamingOutboundSpeed, m_StreamingInboundSpeed, m_StreamingMaxConcurrentStreams; int m_StreamingAckDelay,m_StreamingOutboundSpeed, m_StreamingInboundSpeed, m_StreamingMaxConcurrentStreams;
bool m_IsStreamingAnswerPings; bool m_IsStreamingAnswerPings;

View file

@ -11,7 +11,6 @@
#include "Log.h" #include "Log.h"
#include "util.h" #include "util.h"
#include "Crypto.h" #include "Crypto.h"
#include "PostQuantum.h"
#include "Elligator.h" #include "Elligator.h"
#include "Tag.h" #include "Tag.h"
#include "I2PEndian.h" #include "I2PEndian.h"
@ -95,17 +94,6 @@ namespace garlic
m_ItermediateSymmKeys.erase (index); m_ItermediateSymmKeys.erase (index);
} }
ReceiveRatchetTagSet::ReceiveRatchetTagSet (std::shared_ptr<ECIESX25519AEADRatchetSession> session, bool isNS):
m_Session (session), m_IsNS (isNS)
{
}
ReceiveRatchetTagSet::~ReceiveRatchetTagSet ()
{
if (m_IsNS && m_Session)
m_Session->CleanupReceiveNSRKeys ();
}
void ReceiveRatchetTagSet::Expire () void ReceiveRatchetTagSet::Expire ()
{ {
if (!m_ExpirationTimestamp) if (!m_ExpirationTimestamp)
@ -174,12 +162,12 @@ namespace garlic
return false; return false;
} }
if (m_Destination) if (m_Destination)
m_Destination->HandleECIESx25519GarlicClove (buf + offset, size, nullptr); m_Destination->HandleECIESx25519GarlicClove (buf + offset, size);
return true; return true;
} }
ECIESX25519AEADRatchetSession::ECIESX25519AEADRatchetSession (GarlicDestination * owner, bool attachLeaseSetNS): ECIESX25519AEADRatchetSession::ECIESX25519AEADRatchetSession (GarlicDestination * owner, bool attachLeaseSetNS):
GarlicRoutingSession (owner, true), m_RemoteStaticKeyType (0) GarlicRoutingSession (owner, true)
{ {
if (!attachLeaseSetNS) SetLeaseSetUpdateStatus (eLeaseSetUpToDate); if (!attachLeaseSetNS) SetLeaseSetUpdateStatus (eLeaseSetUpToDate);
RAND_bytes (m_PaddingSizes, 32); m_NextPaddingSize = 0; RAND_bytes (m_PaddingSizes, 32); m_NextPaddingSize = 0;
@ -264,19 +252,12 @@ namespace garlic
return false; return false;
} }
void ECIESX25519AEADRatchetSession::CleanupReceiveNSRKeys ()
{
m_EphemeralKeys = nullptr;
#if OPENSSL_PQ
m_PQKeys = nullptr;
#endif
}
bool ECIESX25519AEADRatchetSession::HandleNewIncomingSession (const uint8_t * buf, size_t len) bool ECIESX25519AEADRatchetSession::HandleNewIncomingSession (const uint8_t * buf, size_t len)
{ {
if (!GetOwner ()) return false; if (!GetOwner ()) return false;
// we are Bob // we are Bob
// KDF1 // KDF1
i2p::crypto::InitNoiseIKState (GetNoiseState (), GetOwner ()->GetEncryptionPublicKey (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)); // bpk
if (!i2p::crypto::GetElligator ()->Decode (buf, m_Aepk)) if (!i2p::crypto::GetElligator ()->Decode (buf, m_Aepk))
{ {
@ -284,61 +265,20 @@ namespace garlic
return false; return false;
} }
buf += 32; len -= 32; buf += 32; len -= 32;
MixHash (m_Aepk, 32); // h = SHA256(h || aepk)
uint8_t sharedSecret[32]; uint8_t sharedSecret[32];
bool decrypted = false;
auto cryptoType = GetOwner ()->GetRatchetsHighestCryptoType ();
#if OPENSSL_PQ
if (cryptoType > i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) // we support post quantum
{
i2p::crypto::InitNoiseIKStateMLKEM (GetNoiseState (), cryptoType, GetOwner ()->GetEncryptionPublicKey (cryptoType)); // bpk
MixHash (m_Aepk, 32); // h = SHA256(h || aepk)
if (GetOwner ()->Decrypt (m_Aepk, sharedSecret, cryptoType)) // x25519(bsk, aepk)
{
MixKey (sharedSecret);
auto keyLen = i2p::crypto::GetMLKEMPublicKeyLen (cryptoType);
std::vector<uint8_t> encapsKey(keyLen);
if (Decrypt (buf, encapsKey.data (), keyLen))
{
decrypted = true; // encaps section has right hash
MixHash (buf, keyLen + 16);
buf += keyLen + 16;
len -= keyLen + 16;
m_PQKeys = i2p::crypto::CreateMLKEMKeys (cryptoType);
m_PQKeys->SetPublicKey (encapsKey.data ());
}
}
}
#endif
if (!decrypted)
{
if (cryptoType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD ||
GetOwner ()->SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD))
{
cryptoType = i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD;
i2p::crypto::InitNoiseIKState (GetNoiseState (), GetOwner ()->GetEncryptionPublicKey (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)); // bpk
MixHash (m_Aepk, 32); // h = SHA256(h || aepk)
if (!GetOwner ()->Decrypt (m_Aepk, sharedSecret, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) // x25519(bsk, aepk) if (!GetOwner ()->Decrypt (m_Aepk, sharedSecret, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) // x25519(bsk, aepk)
{ {
LogPrint (eLogWarning, "Garlic: Incorrect Alice ephemeral key"); LogPrint (eLogWarning, "Garlic: Incorrect Alice ephemeral key");
return false; return false;
} }
MixKey (sharedSecret); MixKey (sharedSecret);
}
else
{
LogPrint (eLogWarning, "Garlic: No supported encryption type");
return false;
}
}
// decrypt flags/static // decrypt flags/static
uint8_t fs[32]; uint8_t nonce[12], fs[32];
if (!Decrypt (buf, fs, 32)) CreateNonce (0, nonce);
if (!i2p::crypto::AEADChaCha20Poly1305 (buf, 32, m_H, 32, m_CK + 32, nonce, fs, 32, false)) // decrypt
{ {
LogPrint (eLogWarning, "Garlic: Flags/static section AEAD verification failed "); LogPrint (eLogWarning, "Garlic: Flags/static section AEAD verification failed ");
return false; return false;
@ -351,18 +291,20 @@ namespace garlic
if (isStatic) if (isStatic)
{ {
// static key, fs is apk // static key, fs is apk
SetRemoteStaticKey (cryptoType, fs); memcpy (m_RemoteStaticKey, fs, 32);
if (!GetOwner ()->Decrypt (fs, sharedSecret, m_RemoteStaticKeyType)) // x25519(bsk, apk) if (!GetOwner ()->Decrypt (fs, sharedSecret, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) // x25519(bsk, apk)
{ {
LogPrint (eLogWarning, "Garlic: Incorrect Alice static key"); LogPrint (eLogWarning, "Garlic: Incorrect Alice static key");
return false; return false;
} }
MixKey (sharedSecret); MixKey (sharedSecret);
} }
else // all zeros flags
CreateNonce (1, nonce);
// decrypt payload // decrypt payload
std::vector<uint8_t> payload (len - 16); // we must save original ciphertext std::vector<uint8_t> payload (len - 16); // we must save original ciphertext
if (!Decrypt (buf, payload.data (), len - 16)) if (!i2p::crypto::AEADChaCha20Poly1305 (buf, len - 16, m_H, 32, m_CK + 32, nonce, payload.data (), len - 16, false)) // decrypt
{ {
LogPrint (eLogWarning, "Garlic: Payload section AEAD verification failed"); LogPrint (eLogWarning, "Garlic: Payload section AEAD verification failed");
return false; return false;
@ -398,7 +340,7 @@ namespace garlic
{ {
case eECIESx25519BlkGalicClove: case eECIESx25519BlkGalicClove:
if (GetOwner ()) if (GetOwner ())
GetOwner ()->HandleECIESx25519GarlicClove (buf + offset, size, this); GetOwner ()->HandleECIESx25519GarlicClove (buf + offset, size);
break; break;
case eECIESx25519BlkNextKey: case eECIESx25519BlkNextKey:
LogPrint (eLogDebug, "Garlic: Next key"); LogPrint (eLogDebug, "Garlic: Next key");
@ -550,15 +492,6 @@ namespace garlic
offset += 32; offset += 32;
// KDF1 // KDF1
#if OPENSSL_PQ
if (m_RemoteStaticKeyType >= i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM512_X25519_AEAD)
{
i2p::crypto::InitNoiseIKStateMLKEM (GetNoiseState (), m_RemoteStaticKeyType, m_RemoteStaticKey); // bpk
m_PQKeys = i2p::crypto::CreateMLKEMKeys (m_RemoteStaticKeyType);
m_PQKeys->GenerateKeys ();
}
else
#endif
i2p::crypto::InitNoiseIKState (GetNoiseState (), m_RemoteStaticKey); // bpk i2p::crypto::InitNoiseIKState (GetNoiseState (), m_RemoteStaticKey); // bpk
MixHash (m_EphemeralKeys->GetPublicKey (), 32); // h = SHA256(h || aepk) MixHash (m_EphemeralKeys->GetPublicKey (), 32); // h = SHA256(h || aepk)
uint8_t sharedSecret[32]; uint8_t sharedSecret[32];
@ -568,32 +501,18 @@ namespace garlic
return false; return false;
} }
MixKey (sharedSecret); MixKey (sharedSecret);
#if OPENSSL_PQ
if (m_RemoteStaticKeyType >= i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM512_X25519_AEAD)
{
auto keyLen = i2p::crypto::GetMLKEMPublicKeyLen (m_RemoteStaticKeyType);
std::vector<uint8_t> encapsKey(keyLen);
m_PQKeys->GetPublicKey (encapsKey.data ());
// encrypt encapsKey
if (!Encrypt (encapsKey.data (), out + offset, keyLen))
{
LogPrint (eLogWarning, "Garlic: ML-KEM encap_key section AEAD encryption failed ");
return false;
}
MixHash (out + offset, keyLen + 16); // h = SHA256(h || ciphertext)
offset += keyLen + 16;
}
#endif
// encrypt flags/static key section // encrypt flags/static key section
uint8_t nonce[12];
CreateNonce (0, nonce);
const uint8_t * fs; const uint8_t * fs;
if (isStatic) if (isStatic)
fs = GetOwner ()->GetEncryptionPublicKey (m_RemoteStaticKeyType); fs = GetOwner ()->GetEncryptionPublicKey (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD);
else else
{ {
memset (out + offset, 0, 32); // all zeros flags section memset (out + offset, 0, 32); // all zeros flags section
fs = out + offset; fs = out + offset;
} }
if (!Encrypt (fs, out + offset, 32)) if (!i2p::crypto::AEADChaCha20Poly1305 (fs, 32, m_H, 32, m_CK + 32, nonce, out + offset, 48, true)) // encrypt
{ {
LogPrint (eLogWarning, "Garlic: Flags/static section AEAD encryption failed "); LogPrint (eLogWarning, "Garlic: Flags/static section AEAD encryption failed ");
return false; return false;
@ -604,11 +523,13 @@ namespace garlic
// KDF2 // KDF2
if (isStatic) if (isStatic)
{ {
GetOwner ()->Decrypt (m_RemoteStaticKey, sharedSecret, m_RemoteStaticKeyType); // x25519 (ask, bpk) GetOwner ()->Decrypt (m_RemoteStaticKey, sharedSecret, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD); // x25519 (ask, bpk)
MixKey (sharedSecret); MixKey (sharedSecret);
} }
else
CreateNonce (1, nonce);
// encrypt payload // encrypt payload
if (!Encrypt (payload, out + offset, len)) if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, m_H, 32, m_CK + 32, nonce, out + offset, len + 16, true)) // encrypt
{ {
LogPrint (eLogWarning, "Garlic: Payload section AEAD encryption failed"); LogPrint (eLogWarning, "Garlic: Payload section AEAD encryption failed");
return false; return false;
@ -657,33 +578,16 @@ namespace garlic
return false; return false;
} }
MixKey (sharedSecret); MixKey (sharedSecret);
#if OPENSSL_PQ
if (m_PQKeys)
{
size_t cipherTextLen = i2p::crypto::GetMLKEMCipherTextLen (m_RemoteStaticKeyType);
std::vector<uint8_t> kemCiphertext(cipherTextLen);
m_PQKeys->Encaps (kemCiphertext.data (), sharedSecret);
if (!Encrypt (kemCiphertext.data (), out + offset, cipherTextLen))
{
LogPrint (eLogWarning, "Garlic: NSR ML-KEM ciphertext section AEAD encryption failed");
return false;
}
m_NSREncodedPQKey = std::make_unique<std::vector<uint8_t> > (cipherTextLen + 16);
memcpy (m_NSREncodedPQKey->data (), out + offset, cipherTextLen + 16);
MixHash (out + offset, cipherTextLen + 16);
MixKey (sharedSecret);
offset += cipherTextLen + 16;
}
#endif
if (!m_EphemeralKeys->Agree (m_RemoteStaticKey, sharedSecret)) // sharedSecret = x25519(besk, apk) if (!m_EphemeralKeys->Agree (m_RemoteStaticKey, sharedSecret)) // sharedSecret = x25519(besk, apk)
{ {
LogPrint (eLogWarning, "Garlic: Incorrect Alice static key"); LogPrint (eLogWarning, "Garlic: Incorrect Alice static key");
return false; return false;
} }
MixKey (sharedSecret); MixKey (sharedSecret);
uint8_t nonce[12];
CreateNonce (0, nonce);
// calculate hash for zero length // calculate hash for zero length
if (!Encrypt (sharedSecret /* can be anything */, out + offset, 0)) // encrypt, ciphertext = ENCRYPT(k, n, ZEROLEN, ad) if (!i2p::crypto::AEADChaCha20Poly1305 (nonce /* can be anything */, 0, m_H, 32, m_CK + 32, nonce, out + offset, 16, true)) // encrypt, ciphertext = ENCRYPT(k, n, ZEROLEN, ad)
{ {
LogPrint (eLogWarning, "Garlic: Reply key section AEAD encryption failed"); LogPrint (eLogWarning, "Garlic: Reply key section AEAD encryption failed");
return false; return false;
@ -704,7 +608,6 @@ namespace garlic
GetOwner ()->GetNumRatchetInboundTags () : ECIESX25519_MIN_NUM_GENERATED_TAGS); GetOwner ()->GetNumRatchetInboundTags () : ECIESX25519_MIN_NUM_GENERATED_TAGS);
i2p::crypto::HKDF (keydata + 32, nullptr, 0, "AttachPayloadKDF", m_NSRKey, 32); // k = HKDF(k_ba, ZEROLEN, "AttachPayloadKDF", 32) i2p::crypto::HKDF (keydata + 32, nullptr, 0, "AttachPayloadKDF", m_NSRKey, 32); // k = HKDF(k_ba, ZEROLEN, "AttachPayloadKDF", 32)
// encrypt payload // encrypt payload
uint8_t nonce[12]; memset (nonce, 0, 12); // seqn = 0
if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, m_H, 32, m_NSRKey, nonce, out + offset, len + 16, true)) // encrypt if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, m_H, 32, m_NSRKey, nonce, out + offset, len + 16, true)) // encrypt
{ {
LogPrint (eLogWarning, "Garlic: NSR payload section AEAD encryption failed"); LogPrint (eLogWarning, "Garlic: NSR payload section AEAD encryption failed");
@ -726,34 +629,16 @@ namespace garlic
memcpy (m_H, m_NSRH, 32); memcpy (m_H, m_NSRH, 32);
MixHash ((const uint8_t *)&tag, 8); // h = SHA256(h || tag) MixHash ((const uint8_t *)&tag, 8); // h = SHA256(h || tag)
MixHash (m_EphemeralKeys->GetPublicKey (), 32); // h = SHA256(h || bepk) MixHash (m_EphemeralKeys->GetPublicKey (), 32); // h = SHA256(h || bepk)
m_N = 0; uint8_t nonce[12];
size_t offset = 40; CreateNonce (0, nonce);
#if OPENSSL_PQ if (!i2p::crypto::AEADChaCha20Poly1305 (nonce /* can be anything */, 0, m_H, 32, m_CK + 32, nonce, out + 40, 16, true)) // encrypt, ciphertext = ENCRYPT(k, n, ZEROLEN, ad)
if (m_PQKeys)
{
if (m_NSREncodedPQKey)
{
size_t cipherTextLen = i2p::crypto::GetMLKEMCipherTextLen (m_RemoteStaticKeyType);
memcpy (out + offset, m_NSREncodedPQKey->data (), cipherTextLen + 16);
MixHash (out + offset, cipherTextLen + 16);
offset += cipherTextLen + 16;
}
else
{
LogPrint (eLogWarning, "Garlic: No stored ML-KEM keys");
return false;
}
}
#endif
if (!Encrypt (m_NSRH /* can be anything */, out + offset, 0)) // encrypt, ciphertext = ENCRYPT(k, n, ZEROLEN, ad)
{ {
LogPrint (eLogWarning, "Garlic: Reply key section AEAD encryption failed"); LogPrint (eLogWarning, "Garlic: Reply key section AEAD encryption failed");
return false; return false;
} }
MixHash (out + offset, 16); // h = SHA256(h || ciphertext) MixHash (out + 40, 16); // h = SHA256(h || ciphertext)
// encrypt payload // encrypt payload
uint8_t nonce[12]; memset (nonce, 0, 12); if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, m_H, 32, m_NSRKey, nonce, out + 56, len + 16, true)) // encrypt
if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, m_H, 32, m_NSRKey, nonce, out + offset + 16, len + 16, true)) // encrypt
{ {
LogPrint (eLogWarning, "Garlic: Next NSR payload section AEAD encryption failed"); LogPrint (eLogWarning, "Garlic: Next NSR payload section AEAD encryption failed");
return false; return false;
@ -785,30 +670,13 @@ namespace garlic
return false; return false;
} }
MixKey (sharedSecret); MixKey (sharedSecret);
#if OPENSSL_PQ GetOwner ()->Decrypt (bepk, sharedSecret, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD); // x25519 (ask, bepk)
if (m_RemoteStaticKeyType >= i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM512_X25519_AEAD)
{
// decrypt kem_ciphertext section
size_t cipherTextLen = i2p::crypto::GetMLKEMCipherTextLen (m_RemoteStaticKeyType);
std::vector<uint8_t> kemCiphertext(cipherTextLen);
if (!Decrypt (buf, kemCiphertext.data (), cipherTextLen))
{
LogPrint (eLogWarning, "Garlic: Reply ML-KEM ciphertext section AEAD decryption failed");
return false;
}
MixHash (buf, cipherTextLen + 16);
buf += cipherTextLen + 16;
len -= cipherTextLen + 16;
// decaps
m_PQKeys->Decaps (kemCiphertext.data (), sharedSecret);
MixKey (sharedSecret);
}
#endif
GetOwner ()->Decrypt (bepk, sharedSecret, m_RemoteStaticKeyType); // x25519 (ask, bepk)
MixKey (sharedSecret); MixKey (sharedSecret);
uint8_t nonce[12];
CreateNonce (0, nonce);
// calculate hash for zero length // calculate hash for zero length
if (!Decrypt (buf, sharedSecret/* can be anything */, 0)) // decrypt, DECRYPT(k, n, ZEROLEN, ad) verification only if (!i2p::crypto::AEADChaCha20Poly1305 (buf, 0, m_H, 32, m_CK + 32, nonce, sharedSecret/* can be anything */, 0, false)) // decrypt, DECRYPT(k, n, ZEROLEN, ad) verification only
{ {
LogPrint (eLogWarning, "Garlic: Reply key section AEAD decryption failed"); LogPrint (eLogWarning, "Garlic: Reply key section AEAD decryption failed");
return false; return false;
@ -833,7 +701,6 @@ namespace garlic
} }
i2p::crypto::HKDF (keydata + 32, nullptr, 0, "AttachPayloadKDF", keydata, 32); // k = HKDF(k_ba, ZEROLEN, "AttachPayloadKDF", 32) i2p::crypto::HKDF (keydata + 32, nullptr, 0, "AttachPayloadKDF", keydata, 32); // k = HKDF(k_ba, ZEROLEN, "AttachPayloadKDF", 32)
// decrypt payload // decrypt payload
uint8_t nonce[12]; memset (nonce, 0, 12); // seqn = 0
if (!i2p::crypto::AEADChaCha20Poly1305 (buf, len - 16, m_H, 32, keydata, nonce, buf, len - 16, false)) // decrypt if (!i2p::crypto::AEADChaCha20Poly1305 (buf, len - 16, m_H, 32, keydata, nonce, buf, len - 16, false)) // decrypt
{ {
LogPrint (eLogWarning, "Garlic: Payload section AEAD decryption failed"); LogPrint (eLogWarning, "Garlic: Payload section AEAD decryption failed");
@ -843,8 +710,7 @@ namespace garlic
if (m_State == eSessionStateNewSessionSent) if (m_State == eSessionStateNewSessionSent)
{ {
m_State = eSessionStateEstablished; m_State = eSessionStateEstablished;
// don't delete m_EpehemralKey and m_PQKeys because delayd NSR's migth come //m_EphemeralKeys = nullptr; // TODO: delete after a while
// done in CleanupReceiveNSRKeys called from NSR tagset destructor
m_SessionCreatedTimestamp = i2p::util::GetSecondsSinceEpoch (); m_SessionCreatedTimestamp = i2p::util::GetSecondsSinceEpoch ();
GetOwner ()->AddECIESx25519Session (m_RemoteStaticKey, shared_from_this ()); GetOwner ()->AddECIESx25519Session (m_RemoteStaticKey, shared_from_this ());
} }
@ -937,10 +803,6 @@ namespace garlic
m_State = eSessionStateEstablished; m_State = eSessionStateEstablished;
m_NSRSendTagset = nullptr; m_NSRSendTagset = nullptr;
m_EphemeralKeys = nullptr; m_EphemeralKeys = nullptr;
#if OPENSSL_PQ
m_PQKeys = nullptr;
m_NSREncodedPQKey = nullptr;
#endif
[[fallthrough]]; [[fallthrough]];
case eSessionStateEstablished: case eSessionStateEstablished:
if (m_SendReverseKey && receiveTagset->GetTagSetID () == m_NextReceiveRatchet->GetReceiveTagSetID ()) if (m_SendReverseKey && receiveTagset->GetTagSetID () == m_NextReceiveRatchet->GetReceiveTagSetID ())
@ -971,12 +833,7 @@ namespace garlic
if (!payload) return nullptr; if (!payload) return nullptr;
size_t len = CreatePayload (msg, m_State != eSessionStateEstablished, payload); size_t len = CreatePayload (msg, m_State != eSessionStateEstablished, payload);
if (!len) return nullptr; if (!len) return nullptr;
#if OPENSSL_PQ
auto m = NewI2NPMessage (len + (m_State == eSessionStateEstablished ? 28 :
i2p::crypto::GetMLKEMPublicKeyLen (m_RemoteStaticKeyType) + 116));
#else
auto m = NewI2NPMessage (len + 100); // 96 + 4 auto m = NewI2NPMessage (len + 100); // 96 + 4
#endif
m->Align (12); // in order to get buf aligned to 16 (12 + 4) m->Align (12); // in order to get buf aligned to 16 (12 + 4)
uint8_t * buf = m->GetPayload () + 4; // 4 bytes for length uint8_t * buf = m->GetPayload () + 4; // 4 bytes for length
@ -991,28 +848,16 @@ namespace garlic
if (!NewOutgoingSessionMessage (payload, len, buf, m->maxLen)) if (!NewOutgoingSessionMessage (payload, len, buf, m->maxLen))
return nullptr; return nullptr;
len += 96; len += 96;
#if OPENSSL_PQ
if (m_RemoteStaticKeyType >= i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM512_X25519_AEAD)
len += i2p::crypto::GetMLKEMPublicKeyLen (m_RemoteStaticKeyType) + 16;
#endif
break; break;
case eSessionStateNewSessionReceived: case eSessionStateNewSessionReceived:
if (!NewSessionReplyMessage (payload, len, buf, m->maxLen)) if (!NewSessionReplyMessage (payload, len, buf, m->maxLen))
return nullptr; return nullptr;
len += 72; len += 72;
#if OPENSSL_PQ
if (m_RemoteStaticKeyType >= i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM512_X25519_AEAD)
len += i2p::crypto::GetMLKEMCipherTextLen (m_RemoteStaticKeyType) + 16;
#endif
break; break;
case eSessionStateNewSessionReplySent: case eSessionStateNewSessionReplySent:
if (!NextNewSessionReplyMessage (payload, len, buf, m->maxLen)) if (!NextNewSessionReplyMessage (payload, len, buf, m->maxLen))
return nullptr; return nullptr;
len += 72; len += 72;
#if OPENSSL_PQ
if (m_RemoteStaticKeyType >= i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM512_X25519_AEAD)
len += i2p::crypto::GetMLKEMCipherTextLen (m_RemoteStaticKeyType) + 16;
#endif
break; break;
case eSessionStateOneTime: case eSessionStateOneTime:
if (!NewOutgoingSessionMessage (payload, len, buf, m->maxLen, false)) if (!NewOutgoingSessionMessage (payload, len, buf, m->maxLen, false))

View file

@ -14,12 +14,10 @@
#include <functional> #include <functional>
#include <memory> #include <memory>
#include <vector> #include <vector>
#include <array>
#include <list> #include <list>
#include <unordered_map> #include <unordered_map>
#include "Identity.h" #include "Identity.h"
#include "Crypto.h" #include "Crypto.h"
#include "PostQuantum.h"
#include "Garlic.h" #include "Garlic.h"
#include "Tag.h" #include "Tag.h"
@ -81,8 +79,8 @@ namespace garlic
{ {
public: public:
ReceiveRatchetTagSet (std::shared_ptr<ECIESX25519AEADRatchetSession> session, bool isNS = false); ReceiveRatchetTagSet (std::shared_ptr<ECIESX25519AEADRatchetSession> session, bool isNS = false):
~ReceiveRatchetTagSet () override; m_Session (session), m_IsNS (isNS) {};
bool IsNS () const { return m_IsNS; }; bool IsNS () const { return m_IsNS; };
std::shared_ptr<ECIESX25519AEADRatchetSession> GetSession () { return m_Session; }; std::shared_ptr<ECIESX25519AEADRatchetSession> GetSession () { return m_Session; };
@ -170,21 +168,17 @@ namespace garlic
std::shared_ptr<I2NPMessage> WrapOneTimeMessage (std::shared_ptr<const I2NPMessage> msg); std::shared_ptr<I2NPMessage> WrapOneTimeMessage (std::shared_ptr<const I2NPMessage> msg);
const uint8_t * GetRemoteStaticKey () const { return m_RemoteStaticKey; } const uint8_t * GetRemoteStaticKey () const { return m_RemoteStaticKey; }
i2p::data::CryptoKeyType GetRemoteStaticKeyType () const { return m_RemoteStaticKeyType; } void SetRemoteStaticKey (const uint8_t * key) { memcpy (m_RemoteStaticKey, key, 32); }
void SetRemoteStaticKey (i2p::data::CryptoKeyType keyType, const uint8_t * key)
{
m_RemoteStaticKeyType = keyType;
memcpy (m_RemoteStaticKey, key, 32);
}
void Terminate () { m_IsTerminated = true; } void Terminate () { m_IsTerminated = true; }
void SetDestination (const i2p::data::IdentHash& dest) void SetDestination (const i2p::data::IdentHash& dest)
{ {
if (!m_Destination) m_Destination.reset (new i2p::data::IdentHash (dest)); if (!m_Destination) m_Destination.reset (new i2p::data::IdentHash (dest));
} }
bool CheckExpired (uint64_t ts); // true is expired bool CheckExpired (uint64_t ts); // true is expired
bool CanBeRestarted (uint64_t ts) const { return ts > m_SessionCreatedTimestamp + ECIESX25519_RESTART_TIMEOUT; } bool CanBeRestarted (uint64_t ts) const { return ts > m_SessionCreatedTimestamp + ECIESX25519_RESTART_TIMEOUT; }
bool IsInactive (uint64_t ts) const { return ts > m_LastActivityTimestamp + ECIESX25519_INACTIVITY_TIMEOUT && CanBeRestarted (ts); } bool IsInactive (uint64_t ts) const { return ts > m_LastActivityTimestamp + ECIESX25519_INACTIVITY_TIMEOUT && CanBeRestarted (ts); }
void CleanupReceiveNSRKeys (); // called from ReceiveRatchetTagSet at Alice's side
bool IsRatchets () const override { return true; }; bool IsRatchets () const override { return true; };
bool IsReadyToSend () const override { return m_State != eSessionStateNewSessionSent; }; bool IsReadyToSend () const override { return m_State != eSessionStateNewSessionSent; };
@ -225,15 +219,10 @@ namespace garlic
private: private:
i2p::data::CryptoKeyType m_RemoteStaticKeyType;
uint8_t m_RemoteStaticKey[32]; uint8_t m_RemoteStaticKey[32];
uint8_t m_Aepk[32]; // Alice's ephemeral keys, for incoming only uint8_t m_Aepk[32]; // Alice's ephemeral keys, for incoming only
uint8_t m_NSREncodedKey[32], m_NSRH[32], m_NSRKey[32]; // new session reply, for incoming only uint8_t m_NSREncodedKey[32], m_NSRH[32], m_NSRKey[32]; // new session reply, for incoming only
std::shared_ptr<i2p::crypto::X25519Keys> m_EphemeralKeys; std::shared_ptr<i2p::crypto::X25519Keys> m_EphemeralKeys;
#if OPENSSL_PQ
std::unique_ptr<i2p::crypto::MLKEMKeys> m_PQKeys;
std::unique_ptr<std::vector<uint8_t> > m_NSREncodedPQKey;
#endif
SessionState m_State = eSessionStateNew; SessionState m_State = eSessionStateNew;
uint64_t m_SessionCreatedTimestamp = 0, m_LastActivityTimestamp = 0, // incoming (in seconds) uint64_t m_SessionCreatedTimestamp = 0, m_LastActivityTimestamp = 0, // incoming (in seconds)
m_LastSentTimestamp = 0; // in milliseconds m_LastSentTimestamp = 0; // in milliseconds

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -15,10 +15,6 @@
#include <TargetConditionals.h> #include <TargetConditionals.h>
#endif #endif
#if defined(__HAIKU__)
#include <FindDirectory.h>
#endif
#ifdef _WIN32 #ifdef _WIN32
#include <shlobj.h> #include <shlobj.h>
#include <windows.h> #include <windows.h>
@ -173,11 +169,12 @@ namespace fs {
dataDir += "/Library/Application Support/" + appName; dataDir += "/Library/Application Support/" + appName;
return; return;
#elif defined(__HAIKU__) #elif defined(__HAIKU__)
char home[PATH_MAX]; // /boot/home/config/settings char *home = getenv("HOME");
if (find_directory(B_USER_SETTINGS_DIRECTORY, -1, false, home, PATH_MAX) == B_OK) if (home != NULL && strlen(home) > 0) {
dataDir = std::string(home) + "/" + appName; dataDir = std::string(home) + "/config/settings/" + appName;
else } else {
dataDir = "/tmp/" + appName; dataDir = "/tmp/" + appName;
}
return; return;
#else /* other unix */ #else /* other unix */
#if defined(ANDROID) #if defined(ANDROID)

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -90,10 +90,10 @@ namespace data
} }
bool Families::VerifyFamily (const std::string& family, const IdentHash& ident, bool Families::VerifyFamily (const std::string& family, const IdentHash& ident,
std::string_view signature, const char * key) const const char * signature, const char * key) const
{ {
uint8_t buf[100], signatureBuf[64]; uint8_t buf[100], signatureBuf[64];
size_t len = family.length (); size_t len = family.length (), signatureLen = strlen (signature);
if (len + 32 > 100) if (len + 32 > 100)
{ {
LogPrint (eLogError, "Family: ", family, " is too long"); LogPrint (eLogError, "Family: ", family, " is too long");
@ -105,7 +105,7 @@ namespace data
memcpy (buf, family.c_str (), len); memcpy (buf, family.c_str (), len);
memcpy (buf + len, (const uint8_t *)ident, 32); memcpy (buf + len, (const uint8_t *)ident, 32);
len += 32; len += 32;
auto signatureBufLen = Base64ToByteStream (signature, signatureBuf, 64); auto signatureBufLen = Base64ToByteStream (signature, signatureLen, signatureBuf, 64);
if (signatureBufLen) if (signatureBufLen)
{ {
EVP_MD_CTX * ctx = EVP_MD_CTX_create (); EVP_MD_CTX * ctx = EVP_MD_CTX_create ();
@ -154,7 +154,12 @@ namespace data
memcpy (buf + len, (const uint8_t *)ident, 32); memcpy (buf + len, (const uint8_t *)ident, 32);
len += 32; len += 32;
signer.Sign (buf, len, signature); signer.Sign (buf, len, signature);
sig = ByteStreamToBase64 (signature, 64); len = Base64EncodingBufferSize (64);
char * b64 = new char[len+1];
len = ByteStreamToBase64 (signature, 64, b64, len);
b64[len] = 0;
sig = b64;
delete[] b64;
} }
else else
LogPrint (eLogWarning, "Family: elliptic curve ", curve, " is not supported"); LogPrint (eLogWarning, "Family: elliptic curve ", curve, " is not supported");

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -11,7 +11,6 @@
#include <map> #include <map>
#include <string> #include <string>
#include <string_view>
#include <memory> #include <memory>
#include <openssl/evp.h> #include <openssl/evp.h>
#include "Identity.h" #include "Identity.h"
@ -29,7 +28,7 @@ namespace data
~Families (); ~Families ();
void LoadCertificates (); void LoadCertificates ();
bool VerifyFamily (const std::string& family, const IdentHash& ident, bool VerifyFamily (const std::string& family, const IdentHash& ident,
std::string_view signature, const char * key = nullptr) const; const char * signature, const char * key = nullptr) const;
FamilyID GetFamilyID (const std::string& family) const; FamilyID GetFamilyID (const std::string& family) const;
private: private:

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -498,8 +498,7 @@ namespace garlic
buf += 4; // length buf += 4; // length
bool found = false; bool found = false;
bool supportsRatchets = SupportsRatchets (); if (SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD))
if (supportsRatchets)
// try ECIESx25519 tag // try ECIESx25519 tag
found = HandleECIESx25519TagMessage (buf, length); found = HandleECIESx25519TagMessage (buf, length);
if (!found) if (!found)
@ -536,7 +535,7 @@ namespace garlic
decryption->Decrypt(buf + 514, length - 514, iv, buf + 514); decryption->Decrypt(buf + 514, length - 514, iv, buf + 514);
HandleAESBlock (buf + 514, length - 514, decryption, msg->from); HandleAESBlock (buf + 514, length - 514, decryption, msg->from);
} }
else if (supportsRatchets) else if (SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD))
{ {
// otherwise ECIESx25519 // otherwise ECIESx25519
auto ts = i2p::util::GetMillisecondsSinceEpoch (); auto ts = i2p::util::GetMillisecondsSinceEpoch ();
@ -748,9 +747,8 @@ namespace garlic
std::shared_ptr<const i2p::data::RoutingDestination> destination, bool attachLeaseSet, std::shared_ptr<const i2p::data::RoutingDestination> destination, bool attachLeaseSet,
bool requestNewIfNotFound) bool requestNewIfNotFound)
{ {
if (destination->GetEncryptionType () >= i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) if (destination->GetEncryptionType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD &&
{ SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD))
if (SupportsEncryptionType (destination->GetEncryptionType ()))
{ {
ECIESX25519AEADRatchetSessionPtr session; ECIESX25519AEADRatchetSessionPtr session;
uint8_t staticKey[32]; uint8_t staticKey[32];
@ -769,15 +767,12 @@ namespace garlic
if (!session && requestNewIfNotFound) if (!session && requestNewIfNotFound)
{ {
session = std::make_shared<ECIESX25519AEADRatchetSession> (this, true); session = std::make_shared<ECIESX25519AEADRatchetSession> (this, true);
session->SetRemoteStaticKey (destination->GetEncryptionType (), staticKey); session->SetRemoteStaticKey (staticKey);
} }
if (session && destination->IsDestination ()) if (session && destination->IsDestination ())
session->SetDestination (destination->GetIdentHash ()); // NS or NSR session->SetDestination (destination->GetIdentHash ()); // NS or NSR
return session; return session;
} }
else
LogPrint (eLogError, "Garlic: Non-supported encryption type ", destination->GetEncryptionType ());
}
else else
{ {
ElGamalAESSessionPtr session; ElGamalAESSessionPtr session;
@ -1004,8 +999,7 @@ namespace garlic
i2p::fs::Remove (it); i2p::fs::Remove (it);
} }
void GarlicDestination::HandleECIESx25519GarlicClove (const uint8_t * buf, size_t len, void GarlicDestination::HandleECIESx25519GarlicClove (const uint8_t * buf, size_t len)
ECIESX25519AEADRatchetSession * from)
{ {
const uint8_t * buf1 = buf; const uint8_t * buf1 = buf;
uint8_t flag = buf[0]; buf++; // flag uint8_t flag = buf[0]; buf++; // flag
@ -1025,7 +1019,7 @@ namespace garlic
buf += 4; // expiration buf += 4; // expiration
ptrdiff_t offset = buf - buf1; ptrdiff_t offset = buf - buf1;
if (offset <= (int)len) if (offset <= (int)len)
HandleCloveI2NPMessage (typeID, buf, len - offset, msgID, from); HandleCloveI2NPMessage (typeID, buf, len - offset, msgID);
else else
LogPrint (eLogError, "Garlic: Clove is too long"); LogPrint (eLogError, "Garlic: Clove is too long");
break; break;

View file

@ -257,7 +257,7 @@ namespace garlic
uint64_t AddECIESx25519SessionNextTag (ReceiveRatchetTagSetPtr tagset); uint64_t AddECIESx25519SessionNextTag (ReceiveRatchetTagSetPtr tagset);
void AddECIESx25519Session (const uint8_t * staticKey, ECIESX25519AEADRatchetSessionPtr session); void AddECIESx25519Session (const uint8_t * staticKey, ECIESX25519AEADRatchetSessionPtr session);
void RemoveECIESx25519Session (const uint8_t * staticKey); void RemoveECIESx25519Session (const uint8_t * staticKey);
void HandleECIESx25519GarlicClove (const uint8_t * buf, size_t len, ECIESX25519AEADRatchetSession * from); void HandleECIESx25519GarlicClove (const uint8_t * buf, size_t len);
uint8_t * GetPayloadBuffer (); uint8_t * GetPayloadBuffer ();
virtual void ProcessGarlicMessage (std::shared_ptr<I2NPMessage> msg); virtual void ProcessGarlicMessage (std::shared_ptr<I2NPMessage> msg);
@ -266,18 +266,13 @@ namespace garlic
virtual std::shared_ptr<const i2p::data::LocalLeaseSet> GetLeaseSet () = 0; // TODO virtual std::shared_ptr<const i2p::data::LocalLeaseSet> GetLeaseSet () = 0; // TODO
virtual std::shared_ptr<i2p::tunnel::TunnelPool> GetTunnelPool () const = 0; virtual std::shared_ptr<i2p::tunnel::TunnelPool> GetTunnelPool () const = 0;
virtual i2p::data::CryptoKeyType GetRatchetsHighestCryptoType () const
{
return GetIdentity ()->GetCryptoKeyType () >= i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD ? GetIdentity ()->GetCryptoKeyType () : 0;
}
protected: protected:
void AddECIESx25519Key (const uint8_t * key, const uint8_t * tag); // one tag void AddECIESx25519Key (const uint8_t * key, const uint8_t * tag); // one tag
bool HandleECIESx25519TagMessage (uint8_t * buf, size_t len); // return true if found bool HandleECIESx25519TagMessage (uint8_t * buf, size_t len); // return true if found
virtual void HandleI2NPMessage (const uint8_t * buf, size_t len) = 0; // called from clove only virtual void HandleI2NPMessage (const uint8_t * buf, size_t len) = 0; // called from clove only
virtual bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, virtual bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len, uint32_t msgID) = 0;
size_t len, uint32_t msgID, ECIESX25519AEADRatchetSession * from) = 0;
void HandleGarlicMessage (std::shared_ptr<I2NPMessage> msg); void HandleGarlicMessage (std::shared_ptr<I2NPMessage> msg);
void HandleDeliveryStatusMessage (uint32_t msgID); void HandleDeliveryStatusMessage (uint32_t msgID);
@ -286,7 +281,6 @@ namespace garlic
private: private:
bool SupportsRatchets () const { return GetRatchetsHighestCryptoType () > 0; }
void HandleAESBlock (uint8_t * buf, size_t len, std::shared_ptr<AESDecryption> decryption, void HandleAESBlock (uint8_t * buf, size_t len, std::shared_ptr<AESDecryption> decryption,
std::shared_ptr<i2p::tunnel::InboundTunnel> from); std::shared_ptr<i2p::tunnel::InboundTunnel> from);
void HandleGarlicPayload (uint8_t * buf, size_t len, std::shared_ptr<i2p::tunnel::InboundTunnel> from); void HandleGarlicPayload (uint8_t * buf, size_t len, std::shared_ptr<i2p::tunnel::InboundTunnel> from);

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -10,7 +10,6 @@
#include <utility> #include <utility>
#include <stdio.h> #include <stdio.h>
#include <ctime> #include <ctime>
#include <charconv>
#include "util.h" #include "util.h"
#include "Base.h" #include "Base.h"
#include "HTTP.h" #include "HTTP.h"
@ -19,51 +18,54 @@ namespace i2p
{ {
namespace http namespace http
{ {
// list of valid HTTP methods const std::vector<std::string> HTTP_METHODS = {
static constexpr std::array<std::string_view, 16> HTTP_METHODS =
{
"GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "CONNECT", // HTTP basic methods "GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "CONNECT", // HTTP basic methods
"COPY", "LOCK", "MKCOL", "MOVE", "PROPFIND", "PROPPATCH", "UNLOCK", "SEARCH" // WebDAV methods, for SEARCH see rfc5323 "COPY", "LOCK", "MKCOL", "MOVE", "PROPFIND", "PROPPATCH", "UNLOCK", "SEARCH" // WebDAV methods, for SEARCH see rfc5323
}; };
const std::vector<std::string> HTTP_VERSIONS = {
// list of valid HTTP versions
static constexpr std::array<std::string_view, 2> HTTP_VERSIONS =
{
"HTTP/1.0", "HTTP/1.1" "HTTP/1.0", "HTTP/1.1"
}; };
const std::vector<const char *> weekdays = {
static constexpr std::array<const char *, 7> weekdays =
{
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
}; };
const std::vector<const char *> months = {
static constexpr std::array<const char *, 12> months =
{
"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec" "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
}; };
static inline bool is_http_version(std::string_view str) inline bool is_http_version(const std::string & str) {
{
return std::find(HTTP_VERSIONS.begin(), HTTP_VERSIONS.end(), str) != std::end(HTTP_VERSIONS); return std::find(HTTP_VERSIONS.begin(), HTTP_VERSIONS.end(), str) != std::end(HTTP_VERSIONS);
} }
static inline bool is_http_method(std::string_view str) inline bool is_http_method(const std::string & str) {
{
return std::find(HTTP_METHODS.begin(), HTTP_METHODS.end(), str) != std::end(HTTP_METHODS); return std::find(HTTP_METHODS.begin(), HTTP_METHODS.end(), str) != std::end(HTTP_METHODS);
} }
static void strsplit(std::string_view line, std::vector<std::string_view> &tokens, char delim, std::size_t limit = 0) static void strsplit(std::stringstream& ss, std::vector<std::string> &tokens, char delim, std::size_t limit = 0)
{ {
size_t count = 0, pos; std::size_t count = 0;
while ((pos = line.find (delim)) != line.npos) std::string token;
while (1)
{ {
count++; count++;
if (limit > 0 && count >= limit) delim = '\n'; // reset delimiter if (limit > 0 && count >= limit)
tokens.push_back (line.substr (0, pos)); delim = '\n'; /* reset delimiter */
line = line.substr (pos + 1); if (!std::getline(ss, token, delim))
break;
tokens.push_back(token);
} }
if (!line.empty ()) tokens.push_back (line); }
static void strsplit(const std::string & line, std::vector<std::string> &tokens, char delim, std::size_t limit = 0)
{
std::stringstream ss{line};
strsplit (ss, tokens, delim, limit);
}
static void strsplit(std::string_view line, std::vector<std::string> &tokens, char delim, std::size_t limit = 0)
{
std::stringstream ss{std::string(line)};
strsplit (ss, tokens, delim, limit);
} }
static std::pair<std::string, std::string> parse_header_line(std::string_view line) static std::pair<std::string, std::string> parse_header_line(std::string_view line)
@ -209,9 +211,8 @@ namespace http
return true; return true;
} }
bool URL::parse_query(std::map<std::string, std::string> & params) bool URL::parse_query(std::map<std::string, std::string> & params) {
{ std::vector<std::string> tokens;
std::vector<std::string_view> tokens;
strsplit(query, tokens, '&'); strsplit(query, tokens, '&');
params.clear(); params.clear();
@ -307,9 +308,8 @@ namespace http
if (expect == REQ_LINE) if (expect == REQ_LINE)
{ {
std::string_view line = str.substr(pos, eol - pos); std::string_view line = str.substr(pos, eol - pos);
std::vector<std::string_view> tokens; std::vector<std::string> tokens;
strsplit(line, tokens, ' '); strsplit(line, tokens, ' ');
if (tokens.size() != 3) if (tokens.size() != 3)
return -1; return -1;
if (!is_http_method(tokens[0])) if (!is_http_method(tokens[0]))
@ -333,11 +333,11 @@ namespace http
else else
return -1; return -1;
} }
pos = eol + CRLF.length(); pos = eol + strlen(CRLF);
if (pos >= eoh) if (pos >= eoh)
break; break;
} }
return eoh + HTTP_EOH.length(); return eoh + strlen(HTTP_EOH);
} }
void HTTPReq::write(std::ostream & o) void HTTPReq::write(std::ostream & o)
@ -381,7 +381,7 @@ namespace http
} }
} }
std::string HTTPReq::GetHeader (std::string_view name) const std::string HTTPReq::GetHeader (const std::string& name) const
{ {
for (auto& it : headers) for (auto& it : headers)
if (it.first == name) if (it.first == name)
@ -389,7 +389,7 @@ namespace http
return ""; return "";
} }
size_t HTTPReq::GetNumHeaders (std::string_view name) const size_t HTTPReq::GetNumHeaders (const std::string& name) const
{ {
size_t num = 0; size_t num = 0;
for (auto& it : headers) for (auto& it : headers)
@ -451,15 +451,13 @@ namespace http
if (expect == RES_LINE) if (expect == RES_LINE)
{ {
std::string_view line = str.substr(pos, eol - pos); std::string_view line = str.substr(pos, eol - pos);
std::vector<std::string_view> tokens; std::vector<std::string> tokens;
strsplit(line, tokens, ' ', 3); strsplit(line, tokens, ' ', 3);
if (tokens.size() != 3) if (tokens.size() != 3)
return -1; return -1;
if (!is_http_version(tokens[0])) if (!is_http_version(tokens[0]))
return -1; return -1;
auto res = std::from_chars(tokens[1].data (), tokens[1].data() + tokens[1].size(), code); code = atoi(tokens[1].c_str());
if (res.ec != std::errc())
return -1;
if (code < 100 || code >= 600) if (code < 100 || code >= 600)
return -1; return -1;
/* all ok */ /* all ok */
@ -476,11 +474,11 @@ namespace http
else else
return -1; return -1;
} }
pos = eol + CRLF.length(); pos = eol + strlen(CRLF);
if (pos >= eoh) if (pos >= eoh)
break; break;
} }
return eoh + HTTP_EOH.length(); return eoh + strlen(HTTP_EOH);
} }
std::string HTTPRes::to_string() { std::string HTTPRes::to_string() {
@ -505,11 +503,9 @@ namespace http
return ss.str(); return ss.str();
} }
std::string_view HTTPCodeToStatus(int code) const char * HTTPCodeToStatus(int code) {
{ const char *ptr;
std::string_view ptr; switch (code) {
switch (code)
{
case 105: ptr = "Name Not Resolved"; break; case 105: ptr = "Name Not Resolved"; break;
/* success */ /* success */
case 200: ptr = "OK"; break; case 200: ptr = "OK"; break;

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -21,8 +21,10 @@ namespace i2p
{ {
namespace http namespace http
{ {
constexpr std::string_view CRLF = "\r\n"; /**< HTTP line terminator */ const char CRLF[] = "\r\n"; /**< HTTP line terminator */
constexpr std::string_view HTTP_EOH = "\r\n\r\n"; /**< HTTP end-of-headers mark */ 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 struct URL
{ {
@ -101,8 +103,8 @@ namespace http
void UpdateHeader (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, const std::string& exempt); // remove all headers starting with name, but exempt
void RemoveHeader (const std::string& name) { RemoveHeader (name, ""); }; void RemoveHeader (const std::string& name) { RemoveHeader (name, ""); };
std::string GetHeader (std::string_view name) const; std::string GetHeader (const std::string& name) const;
size_t GetNumHeaders (std::string_view name) const; size_t GetNumHeaders (const std::string& name) const;
size_t GetNumHeaders () const { return headers.size (); }; size_t GetNumHeaders () const { return headers.size (); };
}; };
@ -152,7 +154,7 @@ namespace http
* @param code HTTP code [100, 599] * @param code HTTP code [100, 599]
* @return Immutable string with status * @return Immutable string with status
*/ */
std::string_view HTTPCodeToStatus(int code); const char * HTTPCodeToStatus(int code);
/** /**
* @brief Replaces %-encoded characters in string with their values * @brief Replaces %-encoded characters in string with their values

View file

@ -10,7 +10,6 @@
#include "I2PEndian.h" #include "I2PEndian.h"
#include "Log.h" #include "Log.h"
#include "Timestamp.h" #include "Timestamp.h"
#include "CryptoKey.h"
#include "Identity.h" #include "Identity.h"
namespace i2p namespace i2p
@ -120,16 +119,6 @@ namespace data
memcpy (m_StandardIdentity.signingKey, signingKey, i2p::crypto::GOSTR3410_512_PUBLIC_KEY_LENGTH); memcpy (m_StandardIdentity.signingKey, signingKey, i2p::crypto::GOSTR3410_512_PUBLIC_KEY_LENGTH);
break; break;
} }
#if OPENSSL_PQ
case SIGNING_KEY_TYPE_MLDSA44:
{
memcpy (m_StandardIdentity, signingKey, 384);
excessLen = i2p::crypto::MLDSA44_PUBLIC_KEY_LENGTH - 384;
excessBuf = new uint8_t[excessLen];
memcpy (excessBuf, signingKey + 384, excessLen);
break;
}
#endif
default: default:
LogPrint (eLogError, "Identity: Signing key type ", (int)type, " is not supported"); LogPrint (eLogError, "Identity: Signing key type ", (int)type, " is not supported");
} }
@ -272,17 +261,21 @@ namespace data
size_t IdentityEx::FromBase64(std::string_view s) size_t IdentityEx::FromBase64(std::string_view s)
{ {
std::vector<uint8_t> buf(s.length ()); // binary data can't exceed base64 const size_t slen = s.length();
auto len = Base64ToByteStream (s, buf.data(), buf.size ()); std::vector<uint8_t> buf(slen); // binary data can't exceed base64
const size_t len = Base64ToByteStream (s.data(), slen, buf.data(), slen);
return FromBuffer (buf.data(), len); return FromBuffer (buf.data(), len);
} }
std::string IdentityEx::ToBase64 () const std::string IdentityEx::ToBase64 () const
{ {
const size_t bufLen = GetFullLen(); const size_t bufLen = GetFullLen();
const size_t strLen = Base64EncodingBufferSize(bufLen);
std::vector<uint8_t> buf(bufLen); std::vector<uint8_t> buf(bufLen);
std::vector<char> str(strLen);
size_t l = ToBuffer (buf.data(), bufLen); size_t l = ToBuffer (buf.data(), bufLen);
return i2p::data::ByteStreamToBase64 (buf.data(), l); size_t l1 = i2p::data::ByteStreamToBase64 (buf.data(), l, str.data(), strLen);
return std::string (str.data(), l1);
} }
size_t IdentityEx::GetSigningPublicKeyLen () const size_t IdentityEx::GetSigningPublicKeyLen () const
@ -359,10 +352,6 @@ namespace data
return new i2p::crypto::GOSTR3410_512_Verifier (i2p::crypto::eGOSTR3410TC26A512); return new i2p::crypto::GOSTR3410_512_Verifier (i2p::crypto::eGOSTR3410TC26A512);
case SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519: case SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519:
return new i2p::crypto::RedDSA25519Verifier (); return new i2p::crypto::RedDSA25519Verifier ();
#if OPENSSL_PQ
case SIGNING_KEY_TYPE_MLDSA44:
return new i2p::crypto::MLDSA44Verifier ();
#endif
case SIGNING_KEY_TYPE_RSA_SHA256_2048: case SIGNING_KEY_TYPE_RSA_SHA256_2048:
case SIGNING_KEY_TYPE_RSA_SHA384_3072: case SIGNING_KEY_TYPE_RSA_SHA384_3072:
case SIGNING_KEY_TYPE_RSA_SHA512_4096: case SIGNING_KEY_TYPE_RSA_SHA512_4096:
@ -384,18 +373,6 @@ namespace data
auto keyLen = verifier->GetPublicKeyLen (); auto keyLen = verifier->GetPublicKeyLen ();
if (keyLen <= 128) if (keyLen <= 128)
verifier->SetPublicKey (m_StandardIdentity.signingKey + 128 - keyLen); verifier->SetPublicKey (m_StandardIdentity.signingKey + 128 - keyLen);
#if OPENSSL_PQ
else if (keyLen > 384)
{
// for post-quantum
uint8_t * signingKey = new uint8_t[keyLen];
memcpy (signingKey, m_StandardIdentity.signingKey, 384);
size_t excessLen = keyLen - 384;
memcpy (signingKey + 384, m_ExtendedBuffer + 4, excessLen); // right after signing and crypto key types
verifier->SetPublicKey (signingKey);
delete[] signingKey;
}
#endif
else else
{ {
// for P521 // for P521
@ -419,14 +396,15 @@ namespace data
return std::make_shared<i2p::crypto::ElGamalEncryptor>(key); return std::make_shared<i2p::crypto::ElGamalEncryptor>(key);
break; break;
case CRYPTO_KEY_TYPE_ECIES_X25519_AEAD: case CRYPTO_KEY_TYPE_ECIES_X25519_AEAD:
case CRYPTO_KEY_TYPE_ECIES_MLKEM512_X25519_AEAD:
case CRYPTO_KEY_TYPE_ECIES_MLKEM768_X25519_AEAD:
case CRYPTO_KEY_TYPE_ECIES_MLKEM1024_X25519_AEAD:
return std::make_shared<i2p::crypto::ECIESX25519AEADRatchetEncryptor>(key); return std::make_shared<i2p::crypto::ECIESX25519AEADRatchetEncryptor>(key);
break; break;
case CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC: case CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC:
case CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC_TEST:
return std::make_shared<i2p::crypto::ECIESP256Encryptor>(key); return std::make_shared<i2p::crypto::ECIESP256Encryptor>(key);
break; break;
case CRYPTO_KEY_TYPE_ECIES_GOSTR3410_CRYPTO_PRO_A_SHA256_AES256CBC:
return std::make_shared<i2p::crypto::ECIESGOSTR3410Encryptor>(key);
break;
default: default:
LogPrint (eLogError, "Identity: Unknown crypto key type ", (int)keyType); LogPrint (eLogError, "Identity: Unknown crypto key type ", (int)keyType);
}; };
@ -570,18 +548,26 @@ namespace data
return ret; return ret;
} }
size_t PrivateKeys::FromBase64(std::string_view s) size_t PrivateKeys::FromBase64(const std::string& s)
{ {
std::vector<uint8_t> buf(s.length ()); uint8_t * buf = new uint8_t[s.length ()];
size_t l = i2p::data::Base64ToByteStream (s, buf.data (), buf.size ()); size_t l = i2p::data::Base64ToByteStream (s.c_str (), s.length (), buf, s.length ());
return FromBuffer (buf.data (), l); size_t ret = FromBuffer (buf, l);
delete[] buf;
return ret;
} }
std::string PrivateKeys::ToBase64 () const std::string PrivateKeys::ToBase64 () const
{ {
std::vector<uint8_t> buf(GetFullLen ()); uint8_t * buf = new uint8_t[GetFullLen ()];
size_t l = ToBuffer (buf.data (), buf.size ()); char * str = new char[GetFullLen ()*2];
return i2p::data::ByteStreamToBase64 (buf.data (), l); size_t l = ToBuffer (buf, GetFullLen ());
size_t l1 = i2p::data::ByteStreamToBase64 (buf, l, str, GetFullLen ()*2);
str[l1] = 0;
delete[] buf;
std::string ret(str);
delete[] str;
return ret;
} }
void PrivateKeys::Sign (const uint8_t * buf, int len, uint8_t * signature) const void PrivateKeys::Sign (const uint8_t * buf, int len, uint8_t * signature) const
@ -644,11 +630,6 @@ namespace data
case SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519: case SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519:
return new i2p::crypto::RedDSA25519Signer (priv); return new i2p::crypto::RedDSA25519Signer (priv);
break; break;
#if OPENSSL_PQ
case SIGNING_KEY_TYPE_MLDSA44:
return new i2p::crypto::MLDSA44Signer (priv);
break;
#endif
default: default:
LogPrint (eLogError, "Identity: Signing key type ", (int)keyType, " is not supported"); LogPrint (eLogError, "Identity: Signing key type ", (int)keyType, " is not supported");
} }
@ -662,7 +643,8 @@ namespace data
size_t PrivateKeys::GetPrivateKeyLen () const size_t PrivateKeys::GetPrivateKeyLen () const
{ {
return i2p::crypto::GetCryptoPrivateKeyLen (m_Public->GetCryptoKeyType ()); // private key length always 256, but type 4
return (m_Public->GetCryptoKeyType () == CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) ? 32 : 256;
} }
uint8_t * PrivateKeys::GetPadding() uint8_t * PrivateKeys::GetPadding()
@ -688,14 +670,15 @@ namespace data
return std::make_shared<i2p::crypto::ElGamalDecryptor>(key); return std::make_shared<i2p::crypto::ElGamalDecryptor>(key);
break; break;
case CRYPTO_KEY_TYPE_ECIES_X25519_AEAD: case CRYPTO_KEY_TYPE_ECIES_X25519_AEAD:
case CRYPTO_KEY_TYPE_ECIES_MLKEM512_X25519_AEAD:
case CRYPTO_KEY_TYPE_ECIES_MLKEM768_X25519_AEAD:
case CRYPTO_KEY_TYPE_ECIES_MLKEM1024_X25519_AEAD:
return std::make_shared<i2p::crypto::ECIESX25519AEADRatchetDecryptor>(key); return std::make_shared<i2p::crypto::ECIESX25519AEADRatchetDecryptor>(key);
break; break;
case CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC: case CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC:
case CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC_TEST:
return std::make_shared<i2p::crypto::ECIESP256Decryptor>(key); return std::make_shared<i2p::crypto::ECIESP256Decryptor>(key);
break; break;
case CRYPTO_KEY_TYPE_ECIES_GOSTR3410_CRYPTO_PRO_A_SHA256_AES256CBC:
return std::make_shared<i2p::crypto::ECIESGOSTR3410Decryptor>(key);
break;
default: default:
LogPrint (eLogError, "Identity: Unknown crypto key type ", (int)cryptoType); LogPrint (eLogError, "Identity: Unknown crypto key type ", (int)cryptoType);
}; };
@ -756,11 +739,6 @@ namespace data
case SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519: case SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519:
i2p::crypto::CreateRedDSA25519RandomKeys (priv, pub); i2p::crypto::CreateRedDSA25519RandomKeys (priv, pub);
break; break;
#if OPENSSL_PQ
case SIGNING_KEY_TYPE_MLDSA44:
i2p::crypto::CreateMLDSA44RandomKeys (priv, pub);
break;
#endif
default: default:
LogPrint (eLogWarning, "Identity: Signing key type ", (int)type, " is not supported. Create DSA-SHA1"); LogPrint (eLogWarning, "Identity: Signing key type ", (int)type, " is not supported. Create DSA-SHA1");
i2p::crypto::CreateDSARandomKeys (priv, pub); // DSA-SHA1 i2p::crypto::CreateDSARandomKeys (priv, pub); // DSA-SHA1
@ -775,12 +753,13 @@ namespace data
i2p::crypto::GenerateElGamalKeyPair(priv, pub); i2p::crypto::GenerateElGamalKeyPair(priv, pub);
break; break;
case CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC: case CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC:
case CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC_TEST:
i2p::crypto::CreateECIESP256RandomKeys (priv, pub); i2p::crypto::CreateECIESP256RandomKeys (priv, pub);
break; break;
case CRYPTO_KEY_TYPE_ECIES_GOSTR3410_CRYPTO_PRO_A_SHA256_AES256CBC:
i2p::crypto::CreateECIESGOSTR3410RandomKeys (priv, pub);
break;
case CRYPTO_KEY_TYPE_ECIES_X25519_AEAD: case CRYPTO_KEY_TYPE_ECIES_X25519_AEAD:
case CRYPTO_KEY_TYPE_ECIES_MLKEM512_X25519_AEAD:
case CRYPTO_KEY_TYPE_ECIES_MLKEM768_X25519_AEAD:
case CRYPTO_KEY_TYPE_ECIES_MLKEM1024_X25519_AEAD:
i2p::crypto::CreateECIESX25519AEADRatchetRandomKeys (priv, pub); i2p::crypto::CreateECIESX25519AEADRatchetRandomKeys (priv, pub);
break; break;
default: default:

View file

@ -17,14 +17,10 @@
#include <vector> #include <vector>
#include "Base.h" #include "Base.h"
#include "Signature.h" #include "Signature.h"
#include "CryptoKey.h"
namespace i2p namespace i2p
{ {
namespace crypto
{
class CryptoKeyEncryptor;
class CryptoKeyDecryptor;
}
namespace data namespace data
{ {
typedef Tag<32> IdentHash; typedef Tag<32> IdentHash;
@ -59,8 +55,6 @@ namespace data
Identity& operator=(const Keys& keys); Identity& operator=(const Keys& keys);
size_t FromBuffer (const uint8_t * buf, size_t len); size_t FromBuffer (const uint8_t * buf, size_t len);
IdentHash Hash () const; IdentHash Hash () const;
operator uint8_t * () { return reinterpret_cast<uint8_t *>(this); }
operator const uint8_t * () const { return reinterpret_cast<const uint8_t *>(this); }
}; };
Keys CreateRandomKeys (); Keys CreateRandomKeys ();
@ -70,9 +64,8 @@ namespace data
const uint16_t CRYPTO_KEY_TYPE_ELGAMAL = 0; const uint16_t CRYPTO_KEY_TYPE_ELGAMAL = 0;
const uint16_t CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC = 1; const uint16_t CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC = 1;
const uint16_t CRYPTO_KEY_TYPE_ECIES_X25519_AEAD = 4; const uint16_t CRYPTO_KEY_TYPE_ECIES_X25519_AEAD = 4;
const uint16_t CRYPTO_KEY_TYPE_ECIES_MLKEM512_X25519_AEAD = 5; const uint16_t CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC_TEST = 65280; // TODO: remove later
const uint16_t CRYPTO_KEY_TYPE_ECIES_MLKEM768_X25519_AEAD = 6; const uint16_t CRYPTO_KEY_TYPE_ECIES_GOSTR3410_CRYPTO_PRO_A_SHA256_AES256CBC = 65281; // TODO: use GOST R 34.11 instead SHA256 and GOST 28147-89 instead AES
const uint16_t CRYPTO_KEY_TYPE_ECIES_MLKEM1024_X25519_AEAD = 7;
const uint16_t SIGNING_KEY_TYPE_DSA_SHA1 = 0; const uint16_t SIGNING_KEY_TYPE_DSA_SHA1 = 0;
const uint16_t SIGNING_KEY_TYPE_ECDSA_SHA256_P256 = 1; const uint16_t SIGNING_KEY_TYPE_ECDSA_SHA256_P256 = 1;
@ -82,11 +75,10 @@ namespace data
const uint16_t SIGNING_KEY_TYPE_RSA_SHA384_3072 = 5; const uint16_t SIGNING_KEY_TYPE_RSA_SHA384_3072 = 5;
const uint16_t SIGNING_KEY_TYPE_RSA_SHA512_4096 = 6; const uint16_t SIGNING_KEY_TYPE_RSA_SHA512_4096 = 6;
const uint16_t SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519 = 7; const uint16_t SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519 = 7;
const uint16_t SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519ph = 8; // since openssl 3.0.0 const uint16_t SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519ph = 8; // not implemented
const uint16_t SIGNING_KEY_TYPE_GOSTR3410_CRYPTO_PRO_A_GOSTR3411_256 = 9; const uint16_t SIGNING_KEY_TYPE_GOSTR3410_CRYPTO_PRO_A_GOSTR3411_256 = 9;
const uint16_t SIGNING_KEY_TYPE_GOSTR3410_TC26_A_512_GOSTR3411_512 = 10; // approved by FSB const uint16_t SIGNING_KEY_TYPE_GOSTR3410_TC26_A_512_GOSTR3411_512 = 10; // approved by FSB
const uint16_t SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519 = 11; // for LeaseSet2 only const uint16_t SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519 = 11; // for LeaseSet2 only
const uint16_t SIGNING_KEY_TYPE_MLDSA44 = 12;
typedef uint16_t SigningKeyType; typedef uint16_t SigningKeyType;
typedef uint16_t CryptoKeyType; typedef uint16_t CryptoKeyType;
@ -142,7 +134,7 @@ namespace data
IdentHash m_IdentHash; IdentHash m_IdentHash;
std::unique_ptr<i2p::crypto::Verifier> m_Verifier; std::unique_ptr<i2p::crypto::Verifier> m_Verifier;
size_t m_ExtendedLen; size_t m_ExtendedLen;
uint8_t m_ExtendedBuffer[MAX_EXTENDED_BUFFER_SIZE]; // TODO: support PQ keys uint8_t m_ExtendedBuffer[MAX_EXTENDED_BUFFER_SIZE];
}; };
size_t GetIdentityBufferLen (const uint8_t * buf, size_t len); // return actual identity length in buffer size_t GetIdentityBufferLen (const uint8_t * buf, size_t len); // return actual identity length in buffer
@ -171,7 +163,7 @@ namespace data
size_t FromBuffer (const uint8_t * buf, size_t len); size_t FromBuffer (const uint8_t * buf, size_t len);
size_t ToBuffer (uint8_t * buf, size_t len) const; size_t ToBuffer (uint8_t * buf, size_t len) const;
size_t FromBase64(std::string_view s); size_t FromBase64(const std::string& s);
std::string ToBase64 () const; std::string ToBase64 () const;
std::shared_ptr<i2p::crypto::CryptoKeyDecryptor> CreateDecryptor (const uint8_t * key) const; std::shared_ptr<i2p::crypto::CryptoKeyDecryptor> CreateDecryptor (const uint8_t * key) const;

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -14,7 +14,6 @@
#include "Timestamp.h" #include "Timestamp.h"
#include "NetDb.hpp" #include "NetDb.hpp"
#include "Tunnel.h" #include "Tunnel.h"
#include "CryptoKey.h"
#include "LeaseSet.h" #include "LeaseSet.h"
namespace i2p namespace i2p
@ -400,7 +399,6 @@ namespace data
offset += propertiesLen; // skip for now. TODO: implement properties offset += propertiesLen; // skip for now. TODO: implement properties
// key sections // key sections
CryptoKeyType preferredKeyType = m_EncryptionType; CryptoKeyType preferredKeyType = m_EncryptionType;
m_EncryptionType = 0;
bool preferredKeyFound = false; bool preferredKeyFound = false;
if (offset + 1 > len) return 0; if (offset + 1 > len) return 0;
int numKeySections = buf[offset]; offset++; int numKeySections = buf[offset]; offset++;
@ -412,23 +410,15 @@ namespace data
if (offset + encryptionKeyLen > len) return 0; if (offset + encryptionKeyLen > len) return 0;
if (IsStoreLeases () && !preferredKeyFound) // create encryptor with leases only if (IsStoreLeases () && !preferredKeyFound) // create encryptor with leases only
{ {
// we pick max key type if preferred not found // we pick first valid key if preferred not found
#if !OPENSSL_PQ
if (keyType <= i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) // skip PQ keys if not supported
#endif
{
if (keyType == preferredKeyType || !m_Encryptor || keyType > m_EncryptionType)
{
auto encryptor = i2p::data::IdentityEx::CreateEncryptor (keyType, buf + offset); auto encryptor = i2p::data::IdentityEx::CreateEncryptor (keyType, buf + offset);
if (encryptor) if (encryptor && (!m_Encryptor || keyType == preferredKeyType))
{ {
m_Encryptor = encryptor; // TODO: atomic m_Encryptor = encryptor; // TODO: atomic
m_EncryptionType = keyType; m_EncryptionType = keyType;
if (keyType == preferredKeyType) preferredKeyFound = true; if (keyType == preferredKeyType) preferredKeyFound = true;
} }
} }
}
}
offset += encryptionKeyLen; offset += encryptionKeyLen;
} }
// leases // leases
@ -848,7 +838,7 @@ namespace data
} }
LocalLeaseSet2::LocalLeaseSet2 (uint8_t storeType, const i2p::data::PrivateKeys& keys, LocalLeaseSet2::LocalLeaseSet2 (uint8_t storeType, const i2p::data::PrivateKeys& keys,
const EncryptionKeys& encryptionKeys, const std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> >& tunnels, const KeySections& encryptionKeys, const std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> >& tunnels,
bool isPublic, uint64_t publishedTimestamp, bool isPublishedEncrypted): bool isPublic, uint64_t publishedTimestamp, bool isPublishedEncrypted):
LocalLeaseSet (keys.GetPublic (), nullptr, 0) LocalLeaseSet (keys.GetPublic (), nullptr, 0)
{ {
@ -858,7 +848,7 @@ namespace data
if (num > MAX_NUM_LEASES) num = MAX_NUM_LEASES; if (num > MAX_NUM_LEASES) num = MAX_NUM_LEASES;
size_t keySectionsLen = 0; size_t keySectionsLen = 0;
for (const auto& it: encryptionKeys) for (const auto& it: encryptionKeys)
keySectionsLen += 2/*key type*/ + 2/*key len*/ + it->pub.size()/*key*/; keySectionsLen += 2/*key type*/ + 2/*key len*/ + it.keyLen/*key*/;
m_BufferLen = identity->GetFullLen () + 4/*published*/ + 2/*expires*/ + 2/*flag*/ + 2/*properties len*/ + m_BufferLen = identity->GetFullLen () + 4/*published*/ + 2/*expires*/ + 2/*flag*/ + 2/*properties len*/ +
1/*num keys*/ + keySectionsLen + 1/*num leases*/ + num*LEASE2_SIZE + keys.GetSignatureLen (); 1/*num keys*/ + keySectionsLen + 1/*num leases*/ + num*LEASE2_SIZE + keys.GetSignatureLen ();
uint16_t flags = 0; uint16_t flags = 0;
@ -893,9 +883,9 @@ namespace data
m_Buffer[offset] = encryptionKeys.size (); offset++; // 1 key m_Buffer[offset] = encryptionKeys.size (); offset++; // 1 key
for (const auto& it: encryptionKeys) for (const auto& it: encryptionKeys)
{ {
htobe16buf (m_Buffer + offset, it->keyType); offset += 2; // key type htobe16buf (m_Buffer + offset, it.keyType); offset += 2; // key type
htobe16buf (m_Buffer + offset, it->pub.size()); offset += 2; // key len htobe16buf (m_Buffer + offset, it.keyLen); offset += 2; // key len
memcpy (m_Buffer + offset, it->pub.data(), it->pub.size()); offset += it->pub.size(); // key memcpy (m_Buffer + offset, it.encryptionPublicKey, it.keyLen); offset += it.keyLen; // key
} }
// leases // leases
uint32_t expirationTime = 0; // in seconds uint32_t expirationTime = 0; // in seconds

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -12,14 +12,12 @@
#include <inttypes.h> #include <inttypes.h>
#include <string.h> #include <string.h>
#include <vector> #include <vector>
#include <list>
#include <set> #include <set>
#include <memory> #include <memory>
#include "Identity.h" #include "Identity.h"
#include "Timestamp.h" #include "Timestamp.h"
#include "I2PEndian.h" #include "I2PEndian.h"
#include "Blinding.h" #include "Blinding.h"
#include "CryptoKey.h"
namespace i2p namespace i2p
{ {
@ -152,8 +150,8 @@ namespace data
public: public:
LeaseSet2 (uint8_t storeType): LeaseSet (true), m_StoreType (storeType) {}; // for update LeaseSet2 (uint8_t storeType): LeaseSet (true), m_StoreType (storeType) {}; // for update
LeaseSet2 (uint8_t storeType, const uint8_t * buf, size_t len, bool storeLeases = true, CryptoKeyType preferredCrypto = CRYPTO_KEY_TYPE_ECIES_X25519_AEAD); LeaseSet2 (uint8_t storeType, const uint8_t * buf, size_t len, bool storeLeases = true, CryptoKeyType preferredCrypto = CRYPTO_KEY_TYPE_ELGAMAL);
LeaseSet2 (const uint8_t * buf, size_t len, std::shared_ptr<const BlindedPublicKey> key, const uint8_t * secret = nullptr, CryptoKeyType preferredCrypto = CRYPTO_KEY_TYPE_ECIES_X25519_AEAD); // 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, CryptoKeyType preferredCrypto = CRYPTO_KEY_TYPE_ELGAMAL); // store type 5, called from local netdb only
uint8_t GetStoreType () const { return m_StoreType; }; uint8_t GetStoreType () const { return m_StoreType; };
uint32_t GetPublishedTimestamp () const { return m_PublishedTimestamp; }; uint32_t GetPublishedTimestamp () const { return m_PublishedTimestamp; };
bool IsPublic () const { return m_IsPublic; }; bool IsPublic () const { return m_IsPublic; };
@ -249,10 +247,15 @@ namespace data
{ {
public: public:
typedef std::list<std::shared_ptr<const i2p::crypto::LocalEncryptionKey> > EncryptionKeys; struct KeySection
{
uint16_t keyType, keyLen;
const uint8_t * encryptionPublicKey;
};
typedef std::vector<KeySection> KeySections;
LocalLeaseSet2 (uint8_t storeType, const i2p::data::PrivateKeys& keys, LocalLeaseSet2 (uint8_t storeType, const i2p::data::PrivateKeys& keys,
const EncryptionKeys& encryptionKeys, const KeySections& encryptionKeys,
const std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> >& tunnels, const std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> >& tunnels,
bool isPublic, uint64_t publishedTimestamp, bool isPublic, uint64_t publishedTimestamp,
bool isPublishedEncrypted = false); bool isPublishedEncrypted = false);

View file

@ -145,12 +145,10 @@ namespace transport
// 2 bytes reserved // 2 bytes reserved
htobe32buf (options + 8, (i2p::util::GetMillisecondsSinceEpoch () + 500)/1000); // tsA, rounded to seconds htobe32buf (options + 8, (i2p::util::GetMillisecondsSinceEpoch () + 500)/1000); // tsA, rounded to seconds
// 4 bytes reserved // 4 bytes reserved
// encrypt options // sign and encrypt options, use m_H as AD
if (!Encrypt (options, m_SessionRequestBuffer + 32, 16)) uint8_t nonce[12];
{ memset (nonce, 0, 12); // set nonce to zero
LogPrint (eLogWarning, "NTCP2: SessionRequest failed to encrypt options"); i2p::crypto::AEADChaCha20Poly1305 (options, 16, GetH (), 32, GetK (), nonce, m_SessionRequestBuffer + 32, 32, true); // encrypt
return false;
}
return true; return true;
} }
@ -169,16 +167,14 @@ namespace transport
memset (options, 0, 16); memset (options, 0, 16);
htobe16buf (options + 2, paddingLen); // padLen htobe16buf (options + 2, paddingLen); // padLen
htobe32buf (options + 8, (i2p::util::GetMillisecondsSinceEpoch () + 500)/1000); // tsB, rounded to seconds htobe32buf (options + 8, (i2p::util::GetMillisecondsSinceEpoch () + 500)/1000); // tsB, rounded to seconds
// encrypt options // sign and encrypt options, use m_H as AD
if (!Encrypt (options, m_SessionCreatedBuffer + 32, 16)) uint8_t nonce[12];
{ memset (nonce, 0, 12); // set nonce to zero
LogPrint (eLogWarning, "NTCP2: SessionCreated failed to encrypt options"); i2p::crypto::AEADChaCha20Poly1305 (options, 16, GetH (), 32, GetK (), nonce, m_SessionCreatedBuffer + 32, 32, true); // encrypt
return false;
}
return true; return true;
} }
bool NTCP2Establisher::CreateSessionConfirmedMessagePart1 () void NTCP2Establisher::CreateSessionConfirmedMessagePart1 (const uint8_t * nonce)
{ {
// update AD // update AD
MixHash (m_SessionCreatedBuffer + 32, 32); // encrypted payload MixHash (m_SessionCreatedBuffer + 32, 32); // encrypted payload
@ -186,28 +182,19 @@ namespace transport
if (paddingLength > 0) if (paddingLength > 0)
MixHash (m_SessionCreatedBuffer + 64, paddingLength); MixHash (m_SessionCreatedBuffer + 64, paddingLength);
// part1 48 bytes, n = 1 // part1 48 bytes
if (!Encrypt (i2p::context.GetNTCP2StaticPublicKey (), m_SessionConfirmedBuffer, 32)) i2p::crypto::AEADChaCha20Poly1305 (i2p::context.GetNTCP2StaticPublicKey (), 32, GetH (), 32, GetK (), nonce, m_SessionConfirmedBuffer, 48, true); // encrypt
{
LogPrint (eLogWarning, "NTCP2: SessionConfirmed failed to encrypt part1");
return false;
}
return true;
} }
bool NTCP2Establisher::CreateSessionConfirmedMessagePart2 () bool NTCP2Establisher::CreateSessionConfirmedMessagePart2 (const uint8_t * nonce)
{ {
// part 2 // part 2
// update AD again // update AD again
MixHash (m_SessionConfirmedBuffer, 48); MixHash (m_SessionConfirmedBuffer, 48);
// encrypt m3p2, it must be filled in SessionRequest // encrypt m3p2, it must be filled in SessionRequest
if (!KDF3Alice ()) return false; // MixKey, n = 0 if (!KDF3Alice ()) return false;
uint8_t * m3p2 = m_SessionConfirmedBuffer + 48; uint8_t * m3p2 = m_SessionConfirmedBuffer + 48;
if (!Encrypt (m3p2, m3p2, m3p2Len - 16)) i2p::crypto::AEADChaCha20Poly1305 (m3p2, m3p2Len - 16, GetH (), 32, GetK (), nonce, m3p2, m3p2Len, true); // encrypt
{
LogPrint (eLogWarning, "NTCP2: SessionConfirmed failed to encrypt part2");
return false;
}
// update h again // update h again
MixHash (m3p2, m3p2Len); //h = SHA256(h || ciphertext) MixHash (m3p2, m3p2Len); //h = SHA256(h || ciphertext)
return true; return true;
@ -227,9 +214,10 @@ namespace transport
LogPrint (eLogWarning, "NTCP2: SessionRequest KDF failed"); LogPrint (eLogWarning, "NTCP2: SessionRequest KDF failed");
return false; return false;
} }
// verify MAC and decrypt options block (32 bytes) // verify MAC and decrypt options block (32 bytes), use m_H as AD
uint8_t options[16]; uint8_t nonce[12], options[16];
if (Decrypt (m_SessionRequestBuffer + 32, options, 16)) memset (nonce, 0, 12); // set nonce to zero
if (i2p::crypto::AEADChaCha20Poly1305 (m_SessionRequestBuffer + 32, 16, GetH (), 32, GetK (), nonce, options, 16, false)) // decrypt
{ {
// options // options
if (options[0] && options[0] != i2p::context.GetNetID ()) if (options[0] && options[0] != i2p::context.GetNetID ())
@ -286,7 +274,9 @@ namespace transport
} }
// decrypt and verify MAC // decrypt and verify MAC
uint8_t payload[16]; uint8_t payload[16];
if (Decrypt (m_SessionCreatedBuffer + 32, payload, 16)) uint8_t nonce[12];
memset (nonce, 0, 12); // set nonce to zero
if (i2p::crypto::AEADChaCha20Poly1305 (m_SessionCreatedBuffer + 32, 16, GetH (), 32, GetK (), nonce, payload, 16, false)) // decrypt
{ {
// options // options
paddingLen = bufbe16toh(payload + 2); paddingLen = bufbe16toh(payload + 2);
@ -307,7 +297,7 @@ namespace transport
return true; return true;
} }
bool NTCP2Establisher::ProcessSessionConfirmedMessagePart1 () bool NTCP2Establisher::ProcessSessionConfirmedMessagePart1 (const uint8_t * nonce)
{ {
// update AD // update AD
MixHash (m_SessionCreatedBuffer + 32, 32); // encrypted payload MixHash (m_SessionCreatedBuffer + 32, 32); // encrypted payload
@ -315,8 +305,7 @@ namespace transport
if (paddingLength > 0) if (paddingLength > 0)
MixHash (m_SessionCreatedBuffer + 64, paddingLength); MixHash (m_SessionCreatedBuffer + 64, paddingLength);
// decrypt S, n = 1 if (!i2p::crypto::AEADChaCha20Poly1305 (m_SessionConfirmedBuffer, 32, GetH (), 32, GetK (), nonce, m_RemoteStaticKey, 32, false)) // decrypt S
if (!Decrypt (m_SessionConfirmedBuffer, m_RemoteStaticKey, 32))
{ {
LogPrint (eLogWarning, "NTCP2: SessionConfirmed Part1 AEAD verification failed "); LogPrint (eLogWarning, "NTCP2: SessionConfirmed Part1 AEAD verification failed ");
return false; return false;
@ -324,17 +313,17 @@ namespace transport
return true; return true;
} }
bool NTCP2Establisher::ProcessSessionConfirmedMessagePart2 (uint8_t * m3p2Buf) bool NTCP2Establisher::ProcessSessionConfirmedMessagePart2 (const uint8_t * nonce, uint8_t * m3p2Buf)
{ {
// update AD again // update AD again
MixHash (m_SessionConfirmedBuffer, 48); MixHash (m_SessionConfirmedBuffer, 48);
if (!KDF3Bob ()) // MixKey, n = 0 if (!KDF3Bob ())
{ {
LogPrint (eLogWarning, "NTCP2: SessionConfirmed Part2 KDF failed"); LogPrint (eLogWarning, "NTCP2: SessionConfirmed Part2 KDF failed");
return false; return false;
} }
if (Decrypt (m_SessionConfirmedBuffer + 48, m3p2Buf, m3p2Len - 16)) if (i2p::crypto::AEADChaCha20Poly1305 (m_SessionConfirmedBuffer + 48, m3p2Len - 16, GetH (), 32, GetK (), nonce, m3p2Buf, m3p2Len - 16, false)) // decrypt
// calculate new h again for KDF data // calculate new h again for KDF data
MixHash (m_SessionConfirmedBuffer + 48, m3p2Len); // h = SHA256(h || ciphertext) MixHash (m_SessionConfirmedBuffer + 48, m3p2Len); // h = SHA256(h || ciphertext)
else else
@ -668,12 +657,11 @@ namespace transport
void NTCP2Session::SendSessionConfirmed () void NTCP2Session::SendSessionConfirmed ()
{ {
if (!m_Establisher->CreateSessionConfirmedMessagePart1 ()) uint8_t nonce[12];
{ CreateNonce (1, nonce); // set nonce to 1
boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ())); m_Establisher->CreateSessionConfirmedMessagePart1 (nonce);
return; memset (nonce, 0, 12); // set nonce back to 0
} if (!m_Establisher->CreateSessionConfirmedMessagePart2 (nonce))
if (!m_Establisher->CreateSessionConfirmedMessagePart2 ())
{ {
LogPrint (eLogWarning, "NTCP2: Send SessionConfirmed Part2 KDF failed"); LogPrint (eLogWarning, "NTCP2: Send SessionConfirmed Part2 KDF failed");
boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ())); boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ()));
@ -752,11 +740,14 @@ namespace transport
// run on establisher thread // run on establisher thread
LogPrint (eLogDebug, "NTCP2: SessionConfirmed received"); LogPrint (eLogDebug, "NTCP2: SessionConfirmed received");
// part 1 // part 1
if (m_Establisher->ProcessSessionConfirmedMessagePart1 ()) uint8_t nonce[12];
CreateNonce (1, nonce);
if (m_Establisher->ProcessSessionConfirmedMessagePart1 (nonce))
{ {
// part 2 // part 2
auto buf = std::make_shared<std::vector<uint8_t> > (m_Establisher->m3p2Len - 16); // -MAC auto buf = std::make_shared<std::vector<uint8_t> > (m_Establisher->m3p2Len - 16); // -MAC
if (m_Establisher->ProcessSessionConfirmedMessagePart2 (buf->data ())) // TODO:handle in establisher thread memset (nonce, 0, 12); // set nonce to 0 again
if (m_Establisher->ProcessSessionConfirmedMessagePart2 (nonce, buf->data ())) // TODO:handle in establisher thread
{ {
// payload // payload
// RI block must be first // RI block must be first

View file

@ -91,6 +91,7 @@ namespace transport
const uint8_t * GetRemotePub () const { return m_RemoteEphemeralPublicKey; }; // Y for Alice and X for Bob const uint8_t * GetRemotePub () const { return m_RemoteEphemeralPublicKey; }; // Y for Alice and X for Bob
uint8_t * GetRemotePub () { return m_RemoteEphemeralPublicKey; }; // to set uint8_t * GetRemotePub () { return m_RemoteEphemeralPublicKey; }; // to set
const uint8_t * GetK () const { return m_CK + 32; };
const uint8_t * GetCK () const { return m_CK; }; const uint8_t * GetCK () const { return m_CK; };
const uint8_t * GetH () const { return m_H; }; const uint8_t * GetH () const { return m_H; };
@ -107,13 +108,13 @@ namespace transport
bool CreateSessionRequestMessage (std::mt19937& rng); bool CreateSessionRequestMessage (std::mt19937& rng);
bool CreateSessionCreatedMessage (std::mt19937& rng); bool CreateSessionCreatedMessage (std::mt19937& rng);
bool CreateSessionConfirmedMessagePart1 (); void CreateSessionConfirmedMessagePart1 (const uint8_t * nonce);
bool CreateSessionConfirmedMessagePart2 (); bool CreateSessionConfirmedMessagePart2 (const uint8_t * nonce);
bool ProcessSessionRequestMessage (uint16_t& paddingLen, bool& clockSkew); bool ProcessSessionRequestMessage (uint16_t& paddingLen, bool& clockSkew);
bool ProcessSessionCreatedMessage (uint16_t& paddingLen); bool ProcessSessionCreatedMessage (uint16_t& paddingLen);
bool ProcessSessionConfirmedMessagePart1 (); bool ProcessSessionConfirmedMessagePart1 (const uint8_t * nonce);
bool ProcessSessionConfirmedMessagePart2 (uint8_t * m3p2Buf); bool ProcessSessionConfirmedMessagePart2 (const uint8_t * nonce, uint8_t * m3p2Buf);
std::shared_ptr<i2p::crypto::X25519Keys> m_EphemeralKeys; std::shared_ptr<i2p::crypto::X25519Keys> m_EphemeralKeys;
uint8_t m_RemoteEphemeralPublicKey[32]; // x25519 uint8_t m_RemoteEphemeralPublicKey[32]; // x25519

View file

@ -688,7 +688,7 @@ namespace data
// since update was long time ago we assume that router is not connected anymore // since update was long time ago we assume that router is not connected anymore
r->ScheduleBufferToDelete (); r->ScheduleBufferToDelete ();
if (r->HasProfile () && r->GetProfile ()->IsUnreachable ()) if (r->GetProfile ()->IsUnreachable ())
r->SetUnreachable (true); r->SetUnreachable (true);
// make router reachable back if too few routers or floodfills // make router reachable back if too few routers or floodfills
if (r->IsUnreachable () && (total - deletedCount < NETDB_MIN_ROUTERS || isLowRate || isOffline || if (r->IsUnreachable () && (total - deletedCount < NETDB_MIN_ROUTERS || isLowRate || isOffline ||
@ -715,9 +715,8 @@ namespace data
r->SetUnreachable (true); r->SetUnreachable (true);
} }
} }
// make router reachable back if connected now or trusted router // make router reachable back if connected now
if (r->IsUnreachable () && (i2p::transport::transports.IsConnected (ident) || if (r->IsUnreachable () && i2p::transport::transports.IsConnected (ident))
i2p::transport::transports.IsTrustedRouter (ident)))
r->SetUnreachable (false); r->SetUnreachable (false);
if (r->IsUnreachable ()) if (r->IsUnreachable ())
@ -951,13 +950,14 @@ namespace data
LogPrint (eLogError, "NetDb: DatabaseLookup for zero ident. Ignored"); LogPrint (eLogError, "NetDb: DatabaseLookup for zero ident. Ignored");
return; return;
} }
std::string key; char key[48];
if (CheckLogLevel (eLogInfo)) int l = i2p::data::ByteStreamToBase64 (buf, 32, key, 48);
key = i2p::data::ByteStreamToBase64 (buf, 32); key[l] = 0;
IdentHash replyIdent(buf + 32); IdentHash replyIdent(buf + 32);
uint8_t flag = buf[64]; uint8_t flag = buf[64];
LogPrint (eLogDebug, "NetDb: DatabaseLookup for ", key, " received flags=", (int)flag); LogPrint (eLogDebug, "NetDb: DatabaseLookup for ", key, " received flags=", (int)flag);
uint8_t lookupType = flag & DATABASE_LOOKUP_TYPE_FLAGS_MASK; uint8_t lookupType = flag & DATABASE_LOOKUP_TYPE_FLAGS_MASK;
const uint8_t * excluded = buf + 65; const uint8_t * excluded = buf + 65;

View file

@ -360,12 +360,11 @@ namespace data
void NetDbRequests::HandleDatabaseSearchReplyMsg (std::shared_ptr<const I2NPMessage> msg) void NetDbRequests::HandleDatabaseSearchReplyMsg (std::shared_ptr<const I2NPMessage> msg)
{ {
const uint8_t * buf = msg->GetPayload (); const uint8_t * buf = msg->GetPayload ();
std::string key; char key[48];
int l = i2p::data::ByteStreamToBase64 (buf, 32, key, 48);
key[l] = 0;
size_t num = buf[32]; // num size_t num = buf[32]; // num
if (CheckLogLevel (eLogInfo))
key = i2p::data::ByteStreamToBase64 (buf, 32);
LogPrint (eLogDebug, "NetDbReq: DatabaseSearchReply for ", key, " num=", num); LogPrint (eLogDebug, "NetDbReq: DatabaseSearchReply for ", key, " num=", num);
IdentHash ident (buf); IdentHash ident (buf);
bool isExploratory = false; bool isExploratory = false;
auto dest = FindRequest (ident); auto dest = FindRequest (ident);

View file

@ -1,160 +0,0 @@
/*
* Copyright (c) 2025, 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 "Log.h"
#include "PostQuantum.h"
#if OPENSSL_PQ
#include <openssl/param_build.h>
#include <openssl/core_names.h>
namespace i2p
{
namespace crypto
{
MLKEMKeys::MLKEMKeys (MLKEMTypes type):
m_Name (std::get<0>(MLKEMS[type])), m_KeyLen (std::get<1>(MLKEMS[type])),
m_CTLen (std::get<2>(MLKEMS[type])), m_Pkey (nullptr)
{
}
MLKEMKeys::~MLKEMKeys ()
{
if (m_Pkey) EVP_PKEY_free (m_Pkey);
}
void MLKEMKeys::GenerateKeys ()
{
if (m_Pkey) EVP_PKEY_free (m_Pkey);
m_Pkey = EVP_PKEY_Q_keygen(NULL, NULL, m_Name.c_str ());
}
void MLKEMKeys::GetPublicKey (uint8_t * pub) const
{
if (m_Pkey)
{
size_t len = m_KeyLen;
EVP_PKEY_get_octet_string_param (m_Pkey, OSSL_PKEY_PARAM_PUB_KEY, pub, m_KeyLen, &len);
}
}
void MLKEMKeys::SetPublicKey (const uint8_t * pub)
{
if (m_Pkey)
{
EVP_PKEY_free (m_Pkey);
m_Pkey = nullptr;
}
OSSL_PARAM params[] =
{
OSSL_PARAM_octet_string (OSSL_PKEY_PARAM_PUB_KEY, (uint8_t *)pub, m_KeyLen),
OSSL_PARAM_END
};
EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name (NULL, m_Name.c_str (), NULL);
if (ctx)
{
EVP_PKEY_fromdata_init (ctx);
EVP_PKEY_fromdata (ctx, &m_Pkey, OSSL_KEYMGMT_SELECT_PUBLIC_KEY, params);
EVP_PKEY_CTX_free (ctx);
}
else
LogPrint (eLogError, "MLKEM can't create PKEY context");
}
void MLKEMKeys::Encaps (uint8_t * ciphertext, uint8_t * shared)
{
if (!m_Pkey) return;
auto ctx = EVP_PKEY_CTX_new_from_pkey (NULL, m_Pkey, NULL);
if (ctx)
{
EVP_PKEY_encapsulate_init (ctx, NULL);
size_t len = m_CTLen, sharedLen = 32;
EVP_PKEY_encapsulate (ctx, ciphertext, &len, shared, &sharedLen);
EVP_PKEY_CTX_free (ctx);
}
else
LogPrint (eLogError, "MLKEM can't create PKEY context");
}
void MLKEMKeys::Decaps (const uint8_t * ciphertext, uint8_t * shared)
{
if (!m_Pkey) return;
auto ctx = EVP_PKEY_CTX_new_from_pkey (NULL, m_Pkey, NULL);
if (ctx)
{
EVP_PKEY_decapsulate_init (ctx, NULL);
size_t sharedLen = 32;
EVP_PKEY_decapsulate (ctx, shared, &sharedLen, ciphertext, m_CTLen);
EVP_PKEY_CTX_free (ctx);
}
else
LogPrint (eLogError, "MLKEM can't create PKEY context");
}
std::unique_ptr<MLKEMKeys> CreateMLKEMKeys (i2p::data::CryptoKeyType type)
{
if (type <= i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD ||
type - i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD > (int)MLKEMS.size ()) return nullptr;
return std::make_unique<MLKEMKeys>((MLKEMTypes)(type - i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD - 1));
}
static constexpr std::array<std::pair<std::array<uint8_t, 32>, std::array<uint8_t, 32> >, 3> NoiseIKInitMLKEMKeys =
{
std::make_pair
(
std::array<uint8_t, 32>
{
0xb0, 0x8f, 0xb1, 0x73, 0x92, 0x66, 0xc9, 0x90, 0x45, 0x7f, 0xdd, 0xc6, 0x4e, 0x55, 0x40, 0xd8,
0x0a, 0x37, 0x99, 0x06, 0x92, 0x2a, 0x78, 0xc4, 0xb1, 0xef, 0x86, 0x06, 0xd0, 0x15, 0x9f, 0x4d
}, // SHA256("Noise_IKhfselg2_25519+MLKEM512_ChaChaPoly_SHA256")
std::array<uint8_t, 32>
{
0x95, 0x8d, 0xf6, 0x6c, 0x95, 0xce, 0xa9, 0xf7, 0x42, 0xfc, 0xfa, 0x62, 0x71, 0x36, 0x1e, 0xa7,
0xdc, 0x7a, 0xc0, 0x75, 0x01, 0xcf, 0xf9, 0xfc, 0x9f, 0xdb, 0x4c, 0x68, 0x3a, 0x53, 0x49, 0xeb
} // SHA256 (first)
),
std::make_pair
(
std::array<uint8_t, 32>
{
0x36, 0x03, 0x90, 0x2d, 0xf9, 0xa2, 0x2a, 0x5e, 0xc9, 0x3d, 0xdb, 0x8f, 0xa8, 0x1b, 0xdb, 0x4b,
0xae, 0x9d, 0x93, 0x9c, 0xdf, 0xaf, 0xde, 0x55, 0x49, 0x13, 0xfe, 0x98, 0xf8, 0x4a, 0xd4, 0xbd
}, // SHA256("Noise_IKhfselg2_25519+MLKEM768_ChaChaPoly_SHA256")
std::array<uint8_t, 32>
{
0x15, 0x44, 0x89, 0xbf, 0x30, 0xf0, 0xc9, 0x77, 0x66, 0x10, 0xcb, 0xb1, 0x57, 0x3f, 0xab, 0x68,
0x79, 0x57, 0x39, 0x57, 0x0a, 0xe7, 0xc0, 0x31, 0x8a, 0xa2, 0x96, 0xef, 0xbf, 0xa9, 0x6a, 0xbb
} // SHA256 (first)
),
std::make_pair
(
std::array<uint8_t, 32>
{
0x86, 0xa5, 0x36, 0x44, 0xc6, 0x12, 0xd5, 0x71, 0xa1, 0x2d, 0xd8, 0xb6, 0x0a, 0x00, 0x9f, 0x2c,
0x1a, 0xa8, 0x7d, 0x22, 0xa4, 0xff, 0x2b, 0xcd, 0x61, 0x34, 0x97, 0x6d, 0xa1, 0x49, 0xeb, 0x4a
}, // SHA256("Noise_IKhfselg2_25519+MLKEM1024_ChaChaPoly_SHA256")
std::array<uint8_t, 32>
{
0x42, 0x0d, 0xc2, 0x1c, 0x7b, 0x18, 0x61, 0xb7, 0x4a, 0x04, 0x3d, 0xae, 0x0f, 0xdc, 0xf2, 0x71,
0xb9, 0xba, 0x19, 0xbb, 0xbd, 0x5f, 0xd4, 0x9c, 0x3f, 0x4b, 0x01, 0xed, 0x6d, 0x13, 0x1d, 0xa2
} // SHA256 (first)
)
};
void InitNoiseIKStateMLKEM (NoiseSymmetricState& state, i2p::data::CryptoKeyType type, const uint8_t * pub)
{
if (type <= i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD ||
type - i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD > (int)NoiseIKInitMLKEMKeys.size ()) return;
auto ind = type - i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD - 1;
state.Init (NoiseIKInitMLKEMKeys[ind].first.data(), NoiseIKInitMLKEMKeys[ind].second.data(), pub);
}
}
}
#endif

View file

@ -1,88 +0,0 @@
/*
* Copyright (c) 2025, 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
*/
#ifndef POST_QUANTUM_H__
#define POST_QUANTUM_H__
#include <memory>
#include <string_view>
#include <array>
#include <tuple>
#include "Crypto.h"
#include "Identity.h"
#if OPENSSL_PQ
namespace i2p
{
namespace crypto
{
enum MLKEMTypes
{
eMLKEM512 = 0,
eMLKEM768,
eMLKEM1024
};
constexpr size_t MLKEM512_KEY_LENGTH = 800;
constexpr size_t MLKEM512_CIPHER_TEXT_LENGTH = 768;
constexpr size_t MLKEM768_KEY_LENGTH = 1184;
constexpr size_t MLKEM768_CIPHER_TEXT_LENGTH = 1088;
constexpr size_t MLKEM1024_KEY_LENGTH = 1568;
constexpr size_t MLKEM1024_CIPHER_TEXT_LENGTH = 1568;
constexpr std::array<std::tuple<std::string_view, size_t, size_t>, 3> MLKEMS =
{
std::make_tuple ("ML-KEM-512", MLKEM512_KEY_LENGTH, MLKEM512_CIPHER_TEXT_LENGTH),
std::make_tuple ("ML-KEM-768", MLKEM768_KEY_LENGTH, MLKEM768_CIPHER_TEXT_LENGTH),
std::make_tuple ("ML-KEM-1024", MLKEM1024_KEY_LENGTH, MLKEM1024_CIPHER_TEXT_LENGTH)
};
constexpr size_t GetMLKEMPublicKeyLen (i2p::data::CryptoKeyType type)
{
if (type <= i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD ||
type - i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD > (int)MLKEMS.size ()) return 0;
return std::get<1>(MLKEMS[type - i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD - 1]);
}
constexpr size_t GetMLKEMCipherTextLen (i2p::data::CryptoKeyType type)
{
if (type <= i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD ||
type - i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD > (int)MLKEMS.size ()) return 0;
return std::get<2>(MLKEMS[type - i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD - 1]);
}
class MLKEMKeys
{
public:
MLKEMKeys (MLKEMTypes type);
~MLKEMKeys ();
void GenerateKeys ();
void GetPublicKey (uint8_t * pub) const;
void SetPublicKey (const uint8_t * pub);
void Encaps (uint8_t * ciphertext, uint8_t * shared);
void Decaps (const uint8_t * ciphertext, uint8_t * shared);
private:
const std::string m_Name;
const size_t m_KeyLen, m_CTLen;
EVP_PKEY * m_Pkey;
};
std::unique_ptr<MLKEMKeys> CreateMLKEMKeys (i2p::data::CryptoKeyType type);
void InitNoiseIKStateMLKEM (NoiseSymmetricState& state, i2p::data::CryptoKeyType type, const uint8_t * pub); // Noise_IK (ratchets) PQ ML-KEM5
}
}
#endif
#endif

View file

@ -310,7 +310,7 @@ namespace data
{ {
if (it->second->IsUpdated () && ts > it->second->GetLastPersistTime () + PEER_PROFILE_PERSIST_INTERVAL) if (it->second->IsUpdated () && ts > it->second->GetLastPersistTime () + PEER_PROFILE_PERSIST_INTERVAL)
{ {
tmp.push_back (*it); tmp.push_back (std::make_pair (it->first, it->second));
it->second->SetLastPersistTime (ts); it->second->SetLastPersistTime (ts);
it->second->SetUpdated (false); it->second->SetUpdated (false);
} }

View file

@ -41,7 +41,7 @@ namespace data
const int PEER_PROFILE_OBSOLETE_PROFILES_CLEAN_VARIANCE = 2400; // in seconds (40 minutes) const int PEER_PROFILE_OBSOLETE_PROFILES_CLEAN_VARIANCE = 2400; // in seconds (40 minutes)
const int PEER_PROFILE_DECLINED_RECENTLY_INTERVAL = 330; // in seconds (5.5 minutes) const int PEER_PROFILE_DECLINED_RECENTLY_INTERVAL = 330; // in seconds (5.5 minutes)
const int PEER_PROFILE_MAX_DECLINED_INTERVAL = 4400; // in second (1.5 hours) const int PEER_PROFILE_MAX_DECLINED_INTERVAL = 4400; // in second (1.5 hours)
const int PEER_PROFILE_PERSIST_INTERVAL = 1320; // in seconds (22 minutes) const int PEER_PROFILE_PERSIST_INTERVAL = 3300; // in seconds (55 minutes)
const int PEER_PROFILE_UNREACHABLE_INTERVAL = 480; // in seconds (8 minutes) const int PEER_PROFILE_UNREACHABLE_INTERVAL = 480; // in seconds (8 minutes)
const int PEER_PROFILE_USEFUL_THRESHOLD = 3; const int PEER_PROFILE_USEFUL_THRESHOLD = 3;
const int PEER_PROFILE_ALWAYS_DECLINING_NUM = 5; // num declines in row to consider always declined const int PEER_PROFILE_ALWAYS_DECLINING_NUM = 5; // num declines in row to consider always declined

View file

@ -22,7 +22,6 @@
#include "ECIESX25519AEADRatchetSession.h" #include "ECIESX25519AEADRatchetSession.h"
#include "Transports.h" #include "Transports.h"
#include "Tunnel.h" #include "Tunnel.h"
#include "CryptoKey.h"
#include "RouterContext.h" #include "RouterContext.h"
namespace i2p namespace i2p
@ -676,11 +675,11 @@ namespace i2p
void RouterContext::SetBandwidth (int limit) void RouterContext::SetBandwidth (int limit)
{ {
if (limit > (int)i2p::data::EXTRA_BANDWIDTH_LIMIT) { SetBandwidth('X'); } if (limit > 2000) { SetBandwidth('X'); }
else if (limit > (int)i2p::data::HIGH_BANDWIDTH_LIMIT) { SetBandwidth('P'); } else if (limit > 256) { SetBandwidth('P'); }
else if (limit > 128) { SetBandwidth('O'); } else if (limit > 128) { SetBandwidth('O'); }
else if (limit > 64) { SetBandwidth('N'); } else if (limit > 64) { SetBandwidth('N'); }
else if (limit > (int)i2p::data::LOW_BANDWIDTH_LIMIT) { SetBandwidth('M'); } else if (limit > 48) { SetBandwidth('M'); }
else if (limit > 12) { SetBandwidth('L'); } else if (limit > 12) { SetBandwidth('L'); }
else { SetBandwidth('K'); } else { SetBandwidth('K'); }
m_BandwidthLimit = limit; // set precise limit m_BandwidthLimit = limit; // set precise limit
@ -1194,8 +1193,7 @@ namespace i2p
i2p::HandleI2NPMessage (CreateI2NPMessage (buf, GetI2NPMessageLength (buf, len))); i2p::HandleI2NPMessage (CreateI2NPMessage (buf, GetI2NPMessageLength (buf, len)));
} }
bool RouterContext::HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, bool RouterContext::HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len, uint32_t msgID)
size_t len, uint32_t msgID, i2p::garlic::ECIESX25519AEADRatchetSession * from)
{ {
if (typeID == eI2NPTunnelTest) if (typeID == eI2NPTunnelTest)
{ {

View file

@ -204,8 +204,7 @@ namespace garlic
// implements GarlicDestination // implements GarlicDestination
void HandleI2NPMessage (const uint8_t * buf, size_t len) override; void HandleI2NPMessage (const uint8_t * buf, size_t len) override;
bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len, uint32_t msgID) override;
size_t len, uint32_t msgID, i2p::garlic::ECIESX25519AEADRatchetSession * from) override;
private: private:

View file

@ -11,7 +11,7 @@
#include "I2PEndian.h" #include "I2PEndian.h"
#include <fstream> #include <fstream>
#include <memory> #include <memory>
#include <charconv> #include <boost/lexical_cast.hpp>
#include <boost/algorithm/string.hpp> // for boost::to_lower #include <boost/algorithm/string.hpp> // for boost::to_lower
#ifndef __cpp_lib_atomic_shared_ptr #ifndef __cpp_lib_atomic_shared_ptr
#include <boost/atomic.hpp> #include <boost/atomic.hpp>
@ -25,7 +25,6 @@
#include "Transports.h" #include "Transports.h"
#include "NetDb.hpp" #include "NetDb.hpp"
#include "RouterContext.h" #include "RouterContext.h"
#include "CryptoKey.h"
#include "RouterInfo.h" #include "RouterInfo.h"
namespace i2p namespace i2p
@ -107,7 +106,8 @@ namespace data
// skip identity // skip identity
size_t identityLen = m_RouterIdentity->GetFullLen (); size_t identityLen = m_RouterIdentity->GetFullLen ();
// read new RI // read new RI
ReadFromBuffer (buf + identityLen, len - identityLen); std::stringstream str (std::string ((char *)buf + identityLen, len - identityLen));
ReadFromStream (str);
if (!m_IsUnreachable) if (!m_IsUnreachable)
UpdateBuffer (buf, len); // save buffer UpdateBuffer (buf, len); // save buffer
// don't delete buffer until saved to the file // don't delete buffer until saved to the file
@ -195,34 +195,39 @@ namespace data
} }
} }
// parse RI // parse RI
if (!ReadFromBuffer (m_Buffer->data () + identityLen, bufferLen - identityLen)) std::stringstream str;
str.write ((const char *)m_Buffer->data () + identityLen, bufferLen - identityLen);
ReadFromStream (str);
if (!str)
{ {
LogPrint (eLogError, "RouterInfo: Malformed message"); LogPrint (eLogError, "RouterInfo: Malformed message");
m_IsUnreachable = true; m_IsUnreachable = true;
} }
} }
bool RouterInfo::ReadFromBuffer (const uint8_t * buf, size_t len) void RouterInfo::ReadFromStream (std::istream& s)
{ {
if (len < 9) return false; if (!s) return;
m_Caps = 0; m_Congestion = eLowCongestion; m_Caps = 0; m_Congestion = eLowCongestion;
m_Timestamp = bufbe64toh (buf); s.read ((char *)&m_Timestamp, sizeof (m_Timestamp));
size_t offset = 8; // timestamp m_Timestamp = be64toh (m_Timestamp);
// read addresses // read addresses
auto addresses = NewAddresses (); auto addresses = NewAddresses ();
uint8_t numAddresses = buf[offset]; offset++; uint8_t numAddresses;
s.read ((char *)&numAddresses, sizeof (numAddresses));
for (int i = 0; i < numAddresses; i++) for (int i = 0; i < numAddresses; i++)
{ {
if (offset + 9 > len) return false; // 1 byte cost + 8 bytes date
uint8_t supportedTransports = 0; uint8_t supportedTransports = 0;
auto address = NewAddress (); auto address = NewAddress ();
offset++; // cost, ignore uint8_t cost; // ignore
address->date = bufbe64toh (buf + offset); offset += 8; // date s.read ((char *)&cost, sizeof (cost));
s.read ((char *)&address->date, sizeof (address->date));
bool isHost = false, isStaticKey = false, isV2 = false, isIntroKey = false; bool isHost = false, isStaticKey = false, isV2 = false, isIntroKey = false;
auto transportStyle = ExtractString (buf + offset, len - offset); offset += transportStyle.length () + 1; char transportStyle[6];
if (!transportStyle.compare (0, 4, "NTCP")) // NTCP or NTCP2 ReadString (transportStyle, 6, s);
if (!strncmp (transportStyle, "NTCP", 4)) // NTCP or NTCP2
address->transportStyle = eTransportNTCP2; address->transportStyle = eTransportNTCP2;
else if (!transportStyle.compare (0, 3, "SSU")) // SSU or SSU2 else if (!strncmp (transportStyle, "SSU", 3)) // SSU or SSU2
{ {
address->transportStyle = eTransportSSU2; address->transportStyle = eTransportSSU2;
address->ssu.reset (new SSUExt ()); address->ssu.reset (new SSUExt ());
@ -232,22 +237,24 @@ namespace data
address->transportStyle = eTransportUnknown; address->transportStyle = eTransportUnknown;
address->caps = 0; address->caps = 0;
address->port = 0; address->port = 0;
if (offset + 2 > len) return false; uint16_t size, r = 0;
uint16_t size = bufbe16toh (buf + offset); offset += 2; // size s.read ((char *)&size, sizeof (size)); if (!s) return;
if (offset + size >= len) return false; size = be16toh (size);
if (address->transportStyle == eTransportUnknown) if (address->transportStyle == eTransportUnknown)
{ {
// skip unknown address // skip unknown address
offset += size; s.seekg (size, std::ios_base::cur);
continue; if (s) continue; else return;
} }
size_t r = 0;
while (r < size) while (r < size)
{ {
auto [key, value, sz] = ExtractParam (buf + offset, len - offset); char key[255], value[255];
r += sz; offset += sz; r += ReadString (key, 255, s);
if (key.empty ()) continue; s.seekg (1, std::ios_base::cur); r++; // =
if (key == "host") r += ReadString (value, 255, s);
s.seekg (1, std::ios_base::cur); r++; // ;
if (!s) return;
if (!strcmp (key, "host"))
{ {
boost::system::error_code ecode; boost::system::error_code ecode;
address->host = boost::asio::ip::make_address (value, ecode); address->host = boost::asio::ip::make_address (value, ecode);
@ -261,53 +268,63 @@ namespace data
address->transportStyle = eTransportUnknown; address->transportStyle = eTransportUnknown;
} }
} }
else if (key == "port") else if (!strcmp (key, "port"))
{ {
auto res = std::from_chars(value.data(), value.data() + value.size(), address->port); try
if (res.ec != std::errc()) {
LogPrint (eLogWarning, "RouterInfo: 'port' conversion error: ", std::make_error_code (res.ec).message ()); address->port = boost::lexical_cast<int>(value);
} }
else if (key == "mtu") catch (std::exception& ex)
{
LogPrint (eLogWarning, "RouterInfo: 'port' exception ", ex.what ());
}
}
else if (!strcmp (key, "mtu"))
{ {
if (address->ssu) if (address->ssu)
{ {
auto res = std::from_chars(value.data(), value.data() + value.size(), address->ssu->mtu); try
if (res.ec != std::errc()) {
LogPrint (eLogWarning, "RouterInfo: 'mtu' conversion error: ", std::make_error_code (res.ec).message ()); address->ssu->mtu = boost::lexical_cast<int>(value);
}
catch (std::exception& ex)
{
LogPrint (eLogWarning, "RouterInfo: 'mtu' exception ", ex.what ());
}
} }
else else
LogPrint (eLogWarning, "RouterInfo: Unexpected field 'mtu' for NTCP2"); LogPrint (eLogWarning, "RouterInfo: Unexpected field 'mtu' for NTCP2");
} }
else if (key == "caps") else if (!strcmp (key, "caps"))
address->caps = ExtractAddressCaps (value); address->caps = ExtractAddressCaps (value);
else if (key == "s") // ntcp2 or ssu2 static key else if (!strcmp (key, "s")) // ntcp2 or ssu2 static key
{ {
if (Base64ToByteStream (value, address->s, 32) == 32 && if (Base64ToByteStream (value, strlen (value), address->s, 32) == 32 &&
!(address->s[31] & 0x80)) // check if x25519 public key !(address->s[31] & 0x80)) // check if x25519 public key
isStaticKey = true; isStaticKey = true;
else else
address->transportStyle = eTransportUnknown; // invalid address address->transportStyle = eTransportUnknown; // invalid address
} }
else if (key == "i") // ntcp2 iv or ssu2 intro else if (!strcmp (key, "i")) // ntcp2 iv or ssu2 intro
{ {
if (address->IsNTCP2 ()) if (address->IsNTCP2 ())
{ {
if (Base64ToByteStream (value, address->i, 16) == 16) if (Base64ToByteStream (value, strlen (value), address->i, 16) == 16)
address->published = true; // presence of "i" means "published" NTCP2 address->published = true; // presence of "i" means "published" NTCP2
else else
address->transportStyle = eTransportUnknown; // invalid address address->transportStyle = eTransportUnknown; // invalid address
} }
else if (address->IsSSU2 ()) else if (address->IsSSU2 ())
{ {
if (Base64ToByteStream (value, address->i, 32) == 32) if (Base64ToByteStream (value, strlen (value), address->i, 32) == 32)
isIntroKey = true; isIntroKey = true;
else else
address->transportStyle = eTransportUnknown; // invalid address address->transportStyle = eTransportUnknown; // invalid address
} }
} }
else if (key == "v") else if (!strcmp (key, "v"))
{ {
if (value == "2") if (!strcmp (value, "2"))
isV2 = true; isV2 = true;
else else
{ {
@ -323,11 +340,13 @@ namespace data
LogPrint (eLogError, "RouterInfo: Introducer is presented for non-SSU address. Skipped"); LogPrint (eLogError, "RouterInfo: Introducer is presented for non-SSU address. Skipped");
continue; continue;
} }
unsigned char index = key[key.length () - 1] - '0'; // TODO: size_t l = strlen(key);
unsigned char index = key[l-1] - '0'; // TODO:
key[l-1] = 0;
if (index > 9) if (index > 9)
{ {
LogPrint (eLogError, "RouterInfo: Unexpected introducer's index ", index, " skipped"); LogPrint (eLogError, "RouterInfo: Unexpected introducer's index ", index, " skipped");
continue; if (s) continue; else return;
} }
if (index >= address->ssu->introducers.size ()) if (index >= address->ssu->introducers.size ())
{ {
@ -336,23 +355,34 @@ namespace data
address->ssu->introducers.resize (index + 1); address->ssu->introducers.resize (index + 1);
} }
Introducer& introducer = address->ssu->introducers.at (index); Introducer& introducer = address->ssu->introducers.at (index);
auto key1 = key.substr(0, key.length () - 1); if (!strcmp (key, "itag"))
if (key1 == "itag")
{ {
auto res = std::from_chars(value.data(), value.data() + value.size(), introducer.iTag); try
if (res.ec != std::errc())
LogPrint (eLogWarning, "RouterInfo: 'itag' conversion error: ", std::make_error_code (res.ec).message ());
}
else if (key1 == "ih")
Base64ToByteStream (value, introducer.iH, 32);
else if (key1 == "iexp")
{ {
auto res = std::from_chars(value.data(), value.data() + value.size(), introducer.iExp); introducer.iTag = boost::lexical_cast<uint32_t>(value);
if (res.ec != std::errc()) }
LogPrint (eLogWarning, "RouterInfo: 'iexp' conversion error: ", std::make_error_code (res.ec).message ()); catch (std::exception& ex)
{
LogPrint (eLogWarning, "RouterInfo: 'itag' exception ", ex.what ());
}
}
else if (!strcmp (key, "ih"))
Base64ToByteStream (value, strlen (value), introducer.iH, 32);
else if (!strcmp (key, "iexp"))
{
try
{
introducer.iExp = boost::lexical_cast<uint32_t>(value);
}
catch (std::exception& ex)
{
LogPrint (eLogWarning, "RouterInfo: 'iexp' exception ", ex.what ());
} }
} }
} }
if (!s) return;
}
if (address->transportStyle == eTransportNTCP2) if (address->transportStyle == eTransportNTCP2)
{ {
if (isStaticKey) if (isStaticKey)
@ -416,41 +446,45 @@ namespace data
boost::atomic_store (&m_Addresses, addresses); boost::atomic_store (&m_Addresses, addresses);
#endif #endif
// read peers // read peers
if (offset + 1 > len) return false; uint8_t numPeers;
uint8_t numPeers = buf[offset]; offset++; // num peers s.read ((char *)&numPeers, sizeof (numPeers)); if (!s) return;
offset += numPeers*32; // TODO: read peers s.seekg (numPeers*32, std::ios_base::cur); // TODO: read peers
// read properties // read properties
if (offset + 2 > len) return false;
m_Version = 0; m_Version = 0;
bool isNetId = false; bool isNetId = false;
std::string family; std::string family;
uint16_t size = bufbe16toh (buf + offset); offset += 2; // size uint16_t size, r = 0;
if (offset + size > len) return false; s.read ((char *)&size, sizeof (size)); if (!s) return;
size_t r = 0; size = be16toh (size);
while (r < size) while (r < size)
{ {
auto [key, value, sz] = ExtractParam (buf + offset, len - offset); char key[255], value[255];
r += sz; offset += sz; r += ReadString (key, 255, s);
if (key.empty ()) continue; s.seekg (1, std::ios_base::cur); r++; // =
r += ReadString (value, 255, s);
s.seekg (1, std::ios_base::cur); r++; // ;
if (!s) return;
SetProperty (key, value); SetProperty (key, value);
// extract caps // extract caps
if (key == "caps") if (!strcmp (key, "caps"))
{ {
ExtractCaps (value); ExtractCaps (value);
m_IsFloodfill = IsDeclaredFloodfill (); m_IsFloodfill = IsDeclaredFloodfill ();
} }
// extract version // extract version
else if (key == ROUTER_INFO_PROPERTY_VERSION) else if (!strcmp (key, ROUTER_INFO_PROPERTY_VERSION))
{ {
m_Version = 0; m_Version = 0;
for (auto ch: value) char * ch = value;
while (*ch)
{ {
if (ch >= '0' && ch <= '9') if (*ch >= '0' && *ch <= '9')
{ {
m_Version *= 10; m_Version *= 10;
m_Version += (ch - '0'); m_Version += (*ch - '0');
} }
ch++;
} }
if (m_Version < NETDB_MIN_PEER_TEST_VERSION && (m_SupportedTransports & (eSSU2V4 | eSSU2V6))) if (m_Version < NETDB_MIN_PEER_TEST_VERSION && (m_SupportedTransports & (eSSU2V4 | eSSU2V6)))
{ {
@ -463,26 +497,24 @@ namespace data
} }
} }
// check netId // check netId
else if (key == ROUTER_INFO_PROPERTY_NETID) else if (!strcmp (key, ROUTER_INFO_PROPERTY_NETID))
{ {
isNetId = true; isNetId = true;
int netID; if (atoi (value) != i2p::context.GetNetID ())
auto res = std::from_chars(value.data(), value.data() + value.size(), netID);
if (res.ec != std::errc() || netID != i2p::context.GetNetID ())
{ {
LogPrint (eLogError, "RouterInfo: Unexpected ", ROUTER_INFO_PROPERTY_NETID, "=", value); LogPrint (eLogError, "RouterInfo: Unexpected ", ROUTER_INFO_PROPERTY_NETID, "=", value);
m_IsUnreachable = true; m_IsUnreachable = true;
} }
} }
// family // family
else if (key == ROUTER_INFO_PROPERTY_FAMILY) else if (!strcmp (key, ROUTER_INFO_PROPERTY_FAMILY))
{ {
family = value; family = value;
boost::to_lower (family); boost::to_lower (family);
} }
else if (key == ROUTER_INFO_PROPERTY_FAMILY_SIG) else if (!strcmp (key, ROUTER_INFO_PROPERTY_FAMILY_SIG))
{ {
if (netdb.GetFamilies ().VerifyFamily (family, GetIdentHash (), value)) // TODO if (netdb.GetFamilies ().VerifyFamily (family, GetIdentHash (), value))
m_FamilyID = netdb.GetFamilies ().GetFamilyID (family); m_FamilyID = netdb.GetFamilies ().GetFamilyID (family);
else else
{ {
@ -490,12 +522,12 @@ namespace data
SetUnreachable (true); SetUnreachable (true);
} }
} }
if (!s) return;
} }
if (!m_SupportedTransports || !isNetId || !m_Version) if (!m_SupportedTransports || !isNetId || !m_Version)
SetUnreachable (true); SetUnreachable (true);
return true;
} }
bool RouterInfo::IsFamily (FamilyID famid) const bool RouterInfo::IsFamily (FamilyID famid) const
@ -503,11 +535,12 @@ namespace data
return m_FamilyID == famid; return m_FamilyID == famid;
} }
void RouterInfo::ExtractCaps (std::string_view value) void RouterInfo::ExtractCaps (const char * value)
{ {
for (auto cap: value) const char * cap = value;
while (*cap)
{ {
switch (cap) switch (*cap)
{ {
case CAPS_FLAG_FLOODFILL: case CAPS_FLAG_FLOODFILL:
m_Caps |= Caps::eFloodfill; m_Caps |= Caps::eFloodfill;
@ -516,16 +549,16 @@ namespace data
case CAPS_FLAG_LOW_BANDWIDTH2: case CAPS_FLAG_LOW_BANDWIDTH2:
case CAPS_FLAG_LOW_BANDWIDTH3: case CAPS_FLAG_LOW_BANDWIDTH3:
case CAPS_FLAG_LOW_BANDWIDTH4: case CAPS_FLAG_LOW_BANDWIDTH4:
m_BandwidthCap = cap; m_BandwidthCap = *cap;
break; break;
case CAPS_FLAG_HIGH_BANDWIDTH: case CAPS_FLAG_HIGH_BANDWIDTH:
m_Caps |= Caps::eHighBandwidth; m_Caps |= Caps::eHighBandwidth;
m_BandwidthCap = cap; m_BandwidthCap = *cap;
break; break;
case CAPS_FLAG_EXTRA_BANDWIDTH1: case CAPS_FLAG_EXTRA_BANDWIDTH1:
case CAPS_FLAG_EXTRA_BANDWIDTH2: case CAPS_FLAG_EXTRA_BANDWIDTH2:
m_Caps |= Caps::eExtraBandwidth | Caps::eHighBandwidth; m_Caps |= Caps::eExtraBandwidth | Caps::eHighBandwidth;
m_BandwidthCap = cap; m_BandwidthCap = *cap;
break; break;
case CAPS_FLAG_HIDDEN: case CAPS_FLAG_HIDDEN:
m_Caps |= Caps::eHidden; m_Caps |= Caps::eHidden;
@ -547,15 +580,17 @@ namespace data
break; break;
default: ; default: ;
} }
cap++;
} }
} }
uint8_t RouterInfo::ExtractAddressCaps (std::string_view value) const uint8_t RouterInfo::ExtractAddressCaps (const char * value) const
{ {
uint8_t caps = 0; uint8_t caps = 0;
for (auto cap: value) const char * cap = value;
while (*cap)
{ {
switch (cap) switch (*cap)
{ {
case CAPS_FLAG_V4: case CAPS_FLAG_V4:
caps |= AddressCaps::eV4; caps |= AddressCaps::eV4;
@ -571,6 +606,7 @@ namespace data
break; break;
default: ; default: ;
} }
cap++;
} }
return caps; return caps;
} }
@ -634,39 +670,23 @@ namespace data
return SaveToFile (fullPath, m_Buffer); return SaveToFile (fullPath, m_Buffer);
} }
std::string_view RouterInfo::ExtractString (const uint8_t * buf, size_t len) const size_t RouterInfo::ReadString (char * str, size_t len, std::istream& s) const
{ {
uint8_t l = buf[0]; uint8_t l;
if (l > len) s.read ((char *)&l, 1);
if (l < len)
{
s.read (str, l);
if (!s) l = 0; // failed, return empty string
str[l] = 0;
}
else
{ {
LogPrint (eLogWarning, "RouterInfo: String length ", (int)l, " exceeds buffer size ", len); LogPrint (eLogWarning, "RouterInfo: String length ", (int)l, " exceeds buffer size ", len);
l = len; s.seekg (l, std::ios::cur); // skip
str[0] = 0;
} }
return { (const char *)(buf + 1), l }; return l+1;
}
std::tuple<std::string_view, std::string_view, size_t> RouterInfo::ExtractParam (const uint8_t * buf, size_t len) const
{
auto key = ExtractString (buf, len);
size_t offset = key.length () + 1;
if (offset >= len) return { std::string_view(), std::string_view(), len };
if (buf[offset] != '=')
{
LogPrint (eLogWarning, "RouterInfo: Unexpected character ", buf[offset], " instead '=' after ", key);
key = std::string_view();
}
offset++;
if (offset >= len) return { key, std::string_view(), len };
auto value = ExtractString (buf + offset, len - offset);
offset += value.length () + 1;
if (offset >= len) return { key, std::string_view(), len };
if (buf[offset] != ';')
{
LogPrint (eLogWarning, "RouterInfo: Unexpected character ", buf[offset], " instead ';' after ", value);
value = std::string_view();
}
offset++;
return { key, value, offset };
} }
void RouterInfo::AddNTCP2Address (const uint8_t * staticKey, const uint8_t * iv,int port, uint8_t caps) void RouterInfo::AddNTCP2Address (const uint8_t * staticKey, const uint8_t * iv,int port, uint8_t caps)
@ -1382,9 +1402,9 @@ namespace data
if (!introducer.iTag) continue; if (!introducer.iTag) continue;
if (introducer.iExp) // expiration is specified if (introducer.iExp) // expiration is specified
{ {
WriteString ("iexp" + std::to_string(i), properties); WriteString ("iexp" + boost::lexical_cast<std::string>(i), properties);
properties << '='; properties << '=';
WriteString (std::to_string(introducer.iExp), properties); WriteString (boost::lexical_cast<std::string>(introducer.iExp), properties);
properties << ';'; properties << ';';
} }
i++; i++;
@ -1393,9 +1413,11 @@ namespace data
for (const auto& introducer: address.ssu->introducers) for (const auto& introducer: address.ssu->introducers)
{ {
if (!introducer.iTag) continue; if (!introducer.iTag) continue;
WriteString ("ih" + std::to_string(i), properties); WriteString ("ih" + boost::lexical_cast<std::string>(i), properties);
properties << '='; properties << '=';
auto value = ByteStreamToBase64 (introducer.iH, 32); char value[64];
size_t l = ByteStreamToBase64 (introducer.iH, 32, value, 64);
value[l] = 0;
WriteString (value, properties); WriteString (value, properties);
properties << ';'; properties << ';';
i++; i++;
@ -1404,9 +1426,9 @@ namespace data
for (const auto& introducer: address.ssu->introducers) for (const auto& introducer: address.ssu->introducers)
{ {
if (!introducer.iTag) continue; if (!introducer.iTag) continue;
WriteString ("itag" + std::to_string(i), properties); WriteString ("itag" + boost::lexical_cast<std::string>(i), properties);
properties << '='; properties << '=';
WriteString (std::to_string(introducer.iTag), properties); WriteString (boost::lexical_cast<std::string>(introducer.iTag), properties);
properties << ';'; properties << ';';
i++; i++;
} }
@ -1420,7 +1442,7 @@ namespace data
{ {
WriteString ("mtu", properties); WriteString ("mtu", properties);
properties << '='; properties << '=';
WriteString (std::to_string(address.ssu->mtu), properties); WriteString (boost::lexical_cast<std::string>(address.ssu->mtu), properties);
properties << ';'; properties << ';';
} }
} }
@ -1428,7 +1450,7 @@ namespace data
{ {
WriteString ("port", properties); WriteString ("port", properties);
properties << '='; properties << '=';
WriteString (std::to_string(address.port), properties); WriteString (boost::lexical_cast<std::string>(address.port), properties);
properties << ';'; properties << ';';
} }
if (address.IsNTCP2 () || address.IsSSU2 ()) if (address.IsNTCP2 () || address.IsSSU2 ())
@ -1463,11 +1485,9 @@ namespace data
s.write (properties.str ().c_str (), properties.str ().size ()); s.write (properties.str ().c_str (), properties.str ().size ());
} }
void LocalRouterInfo::SetProperty (std::string_view key, std::string_view value) void LocalRouterInfo::SetProperty (const std::string& key, const std::string& value)
{ {
auto [it, inserted] = m_Properties.emplace (key, value); m_Properties[key] = value;
if (!inserted)
it->second = value;
} }
void LocalRouterInfo::DeleteProperty (const std::string& key) void LocalRouterInfo::DeleteProperty (const std::string& key)

View file

@ -11,8 +11,6 @@
#include <inttypes.h> #include <inttypes.h>
#include <string> #include <string>
#include <string_view>
#include <tuple>
#include <map> #include <map>
#include <vector> #include <vector>
#include <array> #include <array>
@ -210,8 +208,8 @@ namespace data
typedef boost::shared_ptr<Addresses> AddressesPtr; typedef boost::shared_ptr<Addresses> AddressesPtr;
#endif #endif
RouterInfo (const std::string& fullPath); RouterInfo (const std::string& fullPath);
RouterInfo (const RouterInfo& ) = delete; RouterInfo (const RouterInfo& ) = default;
RouterInfo& operator=(const RouterInfo& ) = delete; RouterInfo& operator=(const RouterInfo& ) = default;
RouterInfo (std::shared_ptr<Buffer>&& buf, size_t len); RouterInfo (std::shared_ptr<Buffer>&& buf, size_t len);
RouterInfo (const uint8_t * buf, size_t len); RouterInfo (const uint8_t * buf, size_t len);
virtual ~RouterInfo (); virtual ~RouterInfo ();
@ -221,7 +219,7 @@ namespace data
std::string GetIdentHashBase64 () const { return GetIdentHash ().ToBase64 (); }; std::string GetIdentHashBase64 () const { return GetIdentHash ().ToBase64 (); };
uint64_t GetTimestamp () const { return m_Timestamp; }; uint64_t GetTimestamp () const { return m_Timestamp; };
int GetVersion () const { return m_Version; }; int GetVersion () const { return m_Version; };
virtual void SetProperty (std::string_view key, std::string_view value) {}; virtual void SetProperty (const std::string& key, const std::string& value) {};
virtual void ClearProperties () {}; virtual void ClearProperties () {};
AddressesPtr GetAddresses () const; // should be called for local RI only, otherwise must return shared_ptr AddressesPtr GetAddresses () const; // should be called for local RI only, otherwise must return shared_ptr
std::shared_ptr<const Address> GetNTCP2V4Address () const; std::shared_ptr<const Address> GetNTCP2V4Address () const;
@ -335,12 +333,11 @@ namespace data
bool LoadFile (const std::string& fullPath); bool LoadFile (const std::string& fullPath);
void ReadFromFile (const std::string& fullPath); void ReadFromFile (const std::string& fullPath);
bool ReadFromBuffer (const uint8_t * buf, size_t len); // return false if malformed void ReadFromStream (std::istream& s);
void ReadFromBuffer (bool verifySignature); void ReadFromBuffer (bool verifySignature);
std::string_view ExtractString (const uint8_t * buf, size_t len) const; size_t ReadString (char* str, size_t len, std::istream& s) const;
std::tuple<std::string_view, std::string_view, size_t> ExtractParam (const uint8_t * buf, size_t len) const; void ExtractCaps (const char * value);
void ExtractCaps (std::string_view value); uint8_t ExtractAddressCaps (const char * value) const;
uint8_t ExtractAddressCaps (std::string_view value) const;
void UpdateIntroducers (std::shared_ptr<Address> address, uint64_t ts); void UpdateIntroducers (std::shared_ptr<Address> address, uint64_t ts);
template<typename Filter> template<typename Filter>
std::shared_ptr<const Address> GetAddress (Filter filter) const; std::shared_ptr<const Address> GetAddress (Filter filter) const;
@ -382,7 +379,7 @@ namespace data
void UpdateCaps (uint8_t caps); void UpdateCaps (uint8_t caps);
bool UpdateCongestion (Congestion c); // returns true if updated bool UpdateCongestion (Congestion c); // returns true if updated
void SetProperty (std::string_view key, std::string_view value) override; void SetProperty (const std::string& key, const std::string& value) override;
void DeleteProperty (const std::string& key); void DeleteProperty (const std::string& key);
std::string GetProperty (const std::string& key) const; std::string GetProperty (const std::string& key) const;
void ClearProperties () override { m_Properties.clear (); }; void ClearProperties () override { m_Properties.clear (); };

View file

@ -1251,21 +1251,18 @@ namespace transport
} }
uint64_t token; uint64_t token;
RAND_bytes ((uint8_t *)&token, 8); RAND_bytes ((uint8_t *)&token, 8);
if (!token) token = 1; // token can't be zero m_IncomingTokens.emplace (ep, std::make_pair (token, uint32_t(ts + SSU2_TOKEN_EXPIRATION_TIMEOUT)));
m_IncomingTokens.try_emplace (ep, token, uint32_t(ts + SSU2_TOKEN_EXPIRATION_TIMEOUT));
return token; return token;
} }
std::pair<uint64_t, uint32_t> SSU2Server::NewIncomingToken (const boost::asio::ip::udp::endpoint& ep) std::pair<uint64_t, uint32_t> SSU2Server::NewIncomingToken (const boost::asio::ip::udp::endpoint& ep)
{ {
m_IncomingTokens.erase (ep); // drop previous
uint64_t token; uint64_t token;
RAND_bytes ((uint8_t *)&token, 8); RAND_bytes ((uint8_t *)&token, 8);
if (!token) token = 1; // token can't be zero auto ret = std::make_pair (token, uint32_t(i2p::util::GetSecondsSinceEpoch () + SSU2_NEXT_TOKEN_EXPIRATION_TIMEOUT));
uint32_t expires = i2p::util::GetSecondsSinceEpoch () + SSU2_NEXT_TOKEN_EXPIRATION_TIMEOUT; m_IncomingTokens.emplace (ep, ret);
auto [it, inserted] = m_IncomingTokens.try_emplace (ep, token, expires); return ret;
if (!inserted)
it->second = { token, expires }; // override
return it->second;
} }
std::vector<std::shared_ptr<SSU2Session> > SSU2Server::FindIntroducers (int maxNumIntroducers, std::vector<std::shared_ptr<SSU2Session> > SSU2Server::FindIntroducers (int maxNumIntroducers,

View file

@ -191,7 +191,12 @@ namespace transport
void SSU2PeerTestSession::SendPeerTest (uint8_t msg, const uint8_t * signedData, size_t signedDataLen, bool delayed) void SSU2PeerTestSession::SendPeerTest (uint8_t msg, const uint8_t * signedData, size_t signedDataLen, bool delayed)
{ {
#if __cplusplus >= 202002L // C++20
m_SignedData.assign (signedData, signedData + signedDataLen); m_SignedData.assign (signedData, signedData + signedDataLen);
#else
m_SignedData.resize (signedDataLen);
memcpy (m_SignedData.data (), signedData, signedDataLen);
#endif
if (!delayed) if (!delayed)
SendPeerTest (msg); SendPeerTest (msg);
// schedule resend for msgs 5 or 6 // schedule resend for msgs 5 or 6
@ -252,7 +257,7 @@ namespace transport
{ {
// we are Charlie // we are Charlie
uint64_t destConnID = htobe64 (((uint64_t)nonce << 32) | nonce); // dest id uint64_t destConnID = htobe64 (((uint64_t)nonce << 32) | nonce); // dest id
uint64_t sourceConnID = ~destConnID; uint32_t sourceConnID = ~destConnID;
SetSourceConnID (sourceConnID); SetSourceConnID (sourceConnID);
SetDestConnID (destConnID); SetDestConnID (destConnID);
SetState (eSSU2SessionStateHolePunch); SetState (eSSU2SessionStateHolePunch);
@ -308,7 +313,12 @@ namespace transport
void SSU2HolePunchSession::SendHolePunch (const uint8_t * relayResponseBlock, size_t relayResponseBlockLen) void SSU2HolePunchSession::SendHolePunch (const uint8_t * relayResponseBlock, size_t relayResponseBlockLen)
{ {
#if __cplusplus >= 202002L // C++20
m_RelayResponseBlock.assign (relayResponseBlock, relayResponseBlock + relayResponseBlockLen); m_RelayResponseBlock.assign (relayResponseBlock, relayResponseBlock + relayResponseBlockLen);
#else
m_RelayResponseBlock.resize (relayResponseBlockLen);
memcpy (m_RelayResponseBlock.data (), relayResponseBlock, relayResponseBlockLen);
#endif
SendHolePunch (); SendHolePunch ();
ScheduleResend (); ScheduleResend ();
} }

View file

@ -189,7 +189,7 @@ namespace transport
if (!asz) return false; if (!asz) return false;
payload[17] = asz; payload[17] = asz;
packet->payloadSize = asz + 18; packet->payloadSize = asz + 18;
SignedData<128> s; SignedData s;
s.Insert ((const uint8_t *)"RelayRequestData", 16); // prologue s.Insert ((const uint8_t *)"RelayRequestData", 16); // prologue
s.Insert (GetRemoteIdentity ()->GetIdentHash (), 32); // bhash s.Insert (GetRemoteIdentity ()->GetIdentHash (), 32); // bhash
s.Insert (session->GetRemoteIdentity ()->GetIdentHash (), 32); // chash s.Insert (session->GetRemoteIdentity ()->GetIdentHash (), 32); // chash
@ -1498,11 +1498,11 @@ namespace transport
ResendHandshakePacket (); // assume we receive ResendHandshakePacket (); // assume we receive
return; return;
} }
if (from != m_RemoteEndpoint && !i2p::transport::transports.IsInReservedRange (from.address ()) && if (from != m_RemoteEndpoint && !i2p::transport::transports.IsInReservedRange (from.address ()))
(!m_PathChallenge || from != m_PathChallenge->second)) // path challenge was not sent to this endpoint yet
{ {
LogPrint (eLogInfo, "SSU2: Remote endpoint update ", m_RemoteEndpoint, "->", from); LogPrint (eLogInfo, "SSU2: Remote endpoint update ", m_RemoteEndpoint, "->", from);
SendPathChallenge (from); m_RemoteEndpoint = from;
SendPathChallenge ();
} }
if (len < 32) if (len < 32)
{ {
@ -1660,12 +1660,11 @@ namespace transport
LogPrint (eLogDebug, "SSU2: Path response"); LogPrint (eLogDebug, "SSU2: Path response");
if (m_PathChallenge) if (m_PathChallenge)
{ {
if (buf64toh (buf + offset) == m_PathChallenge->first) i2p::data::IdentHash hash;
{ SHA256 (buf + offset, size, hash);
m_RemoteEndpoint = m_PathChallenge->second; if (hash == *m_PathChallenge)
m_PathChallenge.reset (nullptr); m_PathChallenge.reset (nullptr);
} }
}
break; break;
} }
case eSSU2BlkFirstPacketNumber: case eSSU2BlkFirstPacketNumber:
@ -1966,7 +1965,6 @@ namespace transport
void SSU2Session::HandleRelayRequest (const uint8_t * buf, size_t len) void SSU2Session::HandleRelayRequest (const uint8_t * buf, size_t len)
{ {
// we are Bob // we are Bob
if (len < 9) return;
auto mts = i2p::util::GetMillisecondsSinceEpoch (); auto mts = i2p::util::GetMillisecondsSinceEpoch ();
uint32_t nonce = bufbe32toh (buf + 1); // nonce uint32_t nonce = bufbe32toh (buf + 1); // nonce
uint32_t relayTag = bufbe32toh (buf + 5); // relay tag uint32_t relayTag = bufbe32toh (buf + 5); // relay tag
@ -2015,24 +2013,18 @@ namespace transport
void SSU2Session::HandleRelayIntro (const uint8_t * buf, size_t len, int attempts) void SSU2Session::HandleRelayIntro (const uint8_t * buf, size_t len, int attempts)
{ {
// we are Charlie // we are Charlie
if (len < 47) return;
SSU2RelayResponseCode code = eSSU2RelayResponseCodeAccept; SSU2RelayResponseCode code = eSSU2RelayResponseCodeAccept;
boost::asio::ip::udp::endpoint ep; boost::asio::ip::udp::endpoint ep;
std::shared_ptr<const i2p::data::RouterInfo::Address> addr; std::shared_ptr<const i2p::data::RouterInfo::Address> addr;
auto r = i2p::data::netdb.FindRouter (buf + 1); // Alice auto r = i2p::data::netdb.FindRouter (buf + 1); // Alice
if (r) if (r)
{ {
SignedData<128> s; SignedData s;
s.Insert ((const uint8_t *)"RelayRequestData", 16); // prologue s.Insert ((const uint8_t *)"RelayRequestData", 16); // prologue
s.Insert (GetRemoteIdentity ()->GetIdentHash (), 32); // bhash s.Insert (GetRemoteIdentity ()->GetIdentHash (), 32); // bhash
s.Insert (i2p::context.GetIdentHash (), 32); // chash s.Insert (i2p::context.GetIdentHash (), 32); // chash
s.Insert (buf + 33, 14); // nonce, relay tag, timestamp, ver, asz s.Insert (buf + 33, 14); // nonce, relay tag, timestamp, ver, asz
uint8_t asz = buf[46]; uint8_t asz = buf[46];
if (asz + 47 + r->GetIdentity ()->GetSignatureLen () > len)
{
LogPrint (eLogWarning, "SSU2: Malformed RelayIntro len=", len);
return;
}
s.Insert (buf + 47, asz); // Alice Port, Alice IP s.Insert (buf + 47, asz); // Alice Port, Alice IP
if (s.Verify (r->GetIdentity (), buf + 47 + asz)) if (s.Verify (r->GetIdentity (), buf + 47 + asz))
{ {
@ -2121,7 +2113,6 @@ namespace transport
void SSU2Session::HandleRelayResponse (const uint8_t * buf, size_t len) void SSU2Session::HandleRelayResponse (const uint8_t * buf, size_t len)
{ {
if (len < 6) return;
uint32_t nonce = bufbe32toh (buf + 2); uint32_t nonce = bufbe32toh (buf + 2);
if (m_State == eSSU2SessionStateIntroduced) if (m_State == eSSU2SessionStateIntroduced)
{ {
@ -2142,9 +2133,7 @@ namespace transport
auto it = m_RelaySessions.find (nonce); auto it = m_RelaySessions.find (nonce);
if (it != m_RelaySessions.end ()) if (it != m_RelaySessions.end ())
{ {
auto relaySession = it->second.first; if (it->second.first && it->second.first->IsEstablished ())
m_RelaySessions.erase (it);
if (relaySession && relaySession->IsEstablished ())
{ {
// we are Bob, message from Charlie // we are Bob, message from Charlie
auto packet = m_Server.GetSentPacketsPool ().AcquireShared (); auto packet = m_Server.GetSentPacketsPool ().AcquireShared ();
@ -2154,12 +2143,12 @@ namespace transport
memcpy (payload + 3, buf, len); // forward to Alice as is memcpy (payload + 3, buf, len); // forward to Alice as is
packet->payloadSize = len + 3; packet->payloadSize = len + 3;
packet->payloadSize += CreatePaddingBlock (payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize); packet->payloadSize += CreatePaddingBlock (payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize);
uint32_t packetNum = relaySession->SendData (packet->payload, packet->payloadSize); uint32_t packetNum = it->second.first->SendData (packet->payload, packet->payloadSize);
if (m_RemoteVersion >= SSU2_MIN_RELAY_RESPONSE_RESEND_VERSION) if (m_RemoteVersion >= SSU2_MIN_RELAY_RESPONSE_RESEND_VERSION)
{ {
// sometimes Alice doesn't ack this RelayResponse in older versions // sometimes Alice doesn't ack this RelayResponse in older versions
packet->sendTime = i2p::util::GetMillisecondsSinceEpoch (); packet->sendTime = i2p::util::GetMillisecondsSinceEpoch ();
relaySession->m_SentPackets.emplace (packetNum, packet); it->second.first->m_SentPackets.emplace (packetNum, packet);
} }
} }
else else
@ -2168,31 +2157,25 @@ namespace transport
if (!buf[1]) // status code accepted? if (!buf[1]) // status code accepted?
{ {
// verify signature // verify signature
uint8_t csz = (len >= 12) ? buf[11] : 0; uint8_t csz = buf[11];
if (csz + 12 + relaySession->GetRemoteIdentity ()->GetSignatureLen () > len) SignedData s;
{
LogPrint (eLogWarning, "SSU2: Malformed RelayResponse len=", len);
relaySession->Done ();
return;
}
SignedData<128> s;
s.Insert ((const uint8_t *)"RelayAgreementOK", 16); // prologue s.Insert ((const uint8_t *)"RelayAgreementOK", 16); // prologue
s.Insert (GetRemoteIdentity ()->GetIdentHash (), 32); // bhash s.Insert (GetRemoteIdentity ()->GetIdentHash (), 32); // bhash
s.Insert (buf + 2, 10 + csz); // nonce, timestamp, ver, csz and Charlie's endpoint s.Insert (buf + 2, 10 + csz); // nonce, timestamp, ver, csz and Charlie's endpoint
if (s.Verify (relaySession->GetRemoteIdentity (), buf + 12 + csz)) if (s.Verify (it->second.first->GetRemoteIdentity (), buf + 12 + csz))
{ {
if (relaySession->m_State == eSSU2SessionStateIntroduced) // HolePunch not received yet if (it->second.first->m_State == eSSU2SessionStateIntroduced) // HolePunch not received yet
{ {
// update Charlie's endpoint // update Charlie's endpoint
if (ExtractEndpoint (buf + 12, csz, relaySession->m_RemoteEndpoint)) if (ExtractEndpoint (buf + 12, csz, it->second.first->m_RemoteEndpoint))
{ {
// update token // update token
uint64_t token; uint64_t token;
memcpy (&token, buf + len - 8, 8); memcpy (&token, buf + len - 8, 8);
m_Server.UpdateOutgoingToken (relaySession->m_RemoteEndpoint, m_Server.UpdateOutgoingToken (it->second.first->m_RemoteEndpoint,
token, i2p::util::GetSecondsSinceEpoch () + SSU2_TOKEN_EXPIRATION_TIMEOUT); token, i2p::util::GetSecondsSinceEpoch () + SSU2_TOKEN_EXPIRATION_TIMEOUT);
// connect to Charlie, HolePunch will be ignored // connect to Charlie, HolePunch will be ignored
relaySession->ConnectAfterIntroduction (); it->second.first->ConnectAfterIntroduction ();
} }
else else
LogPrint (eLogWarning, "SSU2: RelayResponse can't extract endpoint"); LogPrint (eLogWarning, "SSU2: RelayResponse can't extract endpoint");
@ -2201,15 +2184,16 @@ namespace transport
else else
{ {
LogPrint (eLogWarning, "SSU2: RelayResponse signature verification failed"); LogPrint (eLogWarning, "SSU2: RelayResponse signature verification failed");
relaySession->Done (); it->second.first->Done ();
} }
} }
else else
{ {
LogPrint (eLogInfo, "SSU2: RelayResponse status code=", (int)buf[1], " nonce=", bufbe32toh (buf + 2)); LogPrint (eLogInfo, "SSU2: RelayResponse status code=", (int)buf[1], " nonce=", bufbe32toh (buf + 2));
relaySession->Done (); it->second.first->Done ();
} }
} }
m_RelaySessions.erase (it);
} }
else else
LogPrint (eLogDebug, "SSU2: RelayResponse unknown nonce ", bufbe32toh (buf + 2)); LogPrint (eLogDebug, "SSU2: RelayResponse unknown nonce ", bufbe32toh (buf + 2));
@ -2278,13 +2262,10 @@ namespace transport
case 2: // Charlie from Bob case 2: // Charlie from Bob
{ {
// sign with Charlie's key // sign with Charlie's key
if (len < offset + 9) return;
uint8_t asz = buf[offset + 9]; uint8_t asz = buf[offset + 9];
size_t l = asz + 10 + i2p::context.GetIdentity ()->GetSignatureLen (); std::vector<uint8_t> newSignedData (asz + 10 + i2p::context.GetIdentity ()->GetSignatureLen ());
if (len < offset + l) return;
std::vector<uint8_t> newSignedData (l);
memcpy (newSignedData.data (), buf + offset, asz + 10); memcpy (newSignedData.data (), buf + offset, asz + 10);
SignedData<128> s; SignedData s;
s.Insert ((const uint8_t *)"PeerTestValidate", 16); // prologue s.Insert ((const uint8_t *)"PeerTestValidate", 16); // prologue
s.Insert (GetRemoteIdentity ()->GetIdentHash (), 32); // bhash s.Insert (GetRemoteIdentity ()->GetIdentHash (), 32); // bhash
s.Insert (buf + 3, 32); // ahash s.Insert (buf + 3, 32); // ahash
@ -2392,16 +2373,10 @@ namespace transport
if (GetRouterStatus () == eRouterStatusUnknown) if (GetRouterStatus () == eRouterStatusUnknown)
SetTestingState (true); SetTestingState (true);
auto r = i2p::data::netdb.FindRouter (buf + 3); // find Charlie auto r = i2p::data::netdb.FindRouter (buf + 3); // find Charlie
if (r && len >= offset + 9) if (r)
{ {
uint8_t asz = buf[offset + 9]; uint8_t asz = buf[offset + 9];
if (len < offset + asz + 10 + r->GetIdentity ()->GetSignatureLen ()) SignedData s;
{
LogPrint (eLogWarning, "Malformed PeerTest 4 len=", len);
session->Done ();
return;
}
SignedData<128> s;
s.Insert ((const uint8_t *)"PeerTestValidate", 16); // prologue s.Insert ((const uint8_t *)"PeerTestValidate", 16); // prologue
s.Insert (GetRemoteIdentity ()->GetIdentHash (), 32); // bhash s.Insert (GetRemoteIdentity ()->GetIdentHash (), 32); // bhash
s.Insert (i2p::context.GetIdentity ()->GetIdentHash (), 32); // ahash s.Insert (i2p::context.GetIdentity ()->GetIdentHash (), 32); // ahash
@ -2560,19 +2535,17 @@ namespace transport
return nullptr; return nullptr;
} }
void SSU2Session::AdjustMaxPayloadSize (size_t maxMtu) void SSU2Session::AdjustMaxPayloadSize ()
{ {
auto addr = FindLocalAddress (); auto addr = FindLocalAddress ();
if (addr && addr->ssu) if (addr && addr->ssu)
{ {
int mtu = addr->ssu->mtu; int mtu = addr->ssu->mtu;
if (!mtu && addr->IsV4 ()) mtu = SSU2_MAX_PACKET_SIZE; if (!mtu && addr->IsV4 ()) mtu = SSU2_MAX_PACKET_SIZE;
if (mtu > (int)maxMtu) mtu = maxMtu;
if (m_Address && m_Address->ssu && (!mtu || m_Address->ssu->mtu < mtu)) if (m_Address && m_Address->ssu && (!mtu || m_Address->ssu->mtu < mtu))
mtu = m_Address->ssu->mtu; mtu = m_Address->ssu->mtu;
if (mtu) if (mtu)
{ {
if (mtu > (int)SSU2_MAX_PACKET_SIZE) mtu = SSU2_MAX_PACKET_SIZE;
if (mtu < (int)SSU2_MIN_PACKET_SIZE) mtu = SSU2_MIN_PACKET_SIZE; if (mtu < (int)SSU2_MIN_PACKET_SIZE) mtu = SSU2_MIN_PACKET_SIZE;
m_MaxPayloadSize = mtu - (addr->IsV6 () ? IPV6_HEADER_SIZE: IPV4_HEADER_SIZE) - UDP_HEADER_SIZE - 32; m_MaxPayloadSize = mtu - (addr->IsV6 () ? IPV6_HEADER_SIZE: IPV4_HEADER_SIZE) - UDP_HEADER_SIZE - 32;
LogPrint (eLogDebug, "SSU2: Session MTU=", mtu, ", max payload size=", m_MaxPayloadSize); LogPrint (eLogDebug, "SSU2: Session MTU=", mtu, ", max payload size=", m_MaxPayloadSize);
@ -2789,7 +2762,7 @@ namespace transport
size_t SSU2Session::CreatePaddingBlock (uint8_t * buf, size_t len, size_t minSize) size_t SSU2Session::CreatePaddingBlock (uint8_t * buf, size_t len, size_t minSize)
{ {
if (len < 3 || len < minSize) return 0; if (len < 3 || len < minSize) return 0;
size_t paddingSize = m_Server.GetRng ()() & 0x1F; // 0 - 31 size_t paddingSize = m_Server.GetRng ()() & 0x0F; // 0 - 15
if (paddingSize + 3 > len) paddingSize = len - 3; if (paddingSize + 3 > len) paddingSize = len - 3;
else if (paddingSize + 3 < minSize) paddingSize = minSize - 3; else if (paddingSize + 3 < minSize) paddingSize = minSize - 3;
buf[0] = eSSU2BlkPadding; buf[0] = eSSU2BlkPadding;
@ -2891,7 +2864,7 @@ namespace transport
LogPrint (eLogError, "SSU2: Buffer for RelayResponse signature is too small ", len); LogPrint (eLogError, "SSU2: Buffer for RelayResponse signature is too small ", len);
return 0; return 0;
} }
SignedData<128> s; SignedData s;
s.Insert ((const uint8_t *)"RelayAgreementOK", 16); // prologue s.Insert ((const uint8_t *)"RelayAgreementOK", 16); // prologue
if (code == eSSU2RelayResponseCodeAccept || code >= 64) // Charlie if (code == eSSU2RelayResponseCodeAccept || code >= 64) // Charlie
s.Insert (GetRemoteIdentity ()->GetIdentHash (), 32); // bhash s.Insert (GetRemoteIdentity ()->GetIdentHash (), 32); // bhash
@ -2953,7 +2926,7 @@ namespace transport
size_t asz = CreateEndpoint (signedData + 10, 86, boost::asio::ip::udp::endpoint (localAddress->host, localAddress->port)); size_t asz = CreateEndpoint (signedData + 10, 86, boost::asio::ip::udp::endpoint (localAddress->host, localAddress->port));
signedData[9] = asz; signedData[9] = asz;
// signature // signature
SignedData<128> s; SignedData s;
s.Insert ((const uint8_t *)"PeerTestValidate", 16); // prologue s.Insert ((const uint8_t *)"PeerTestValidate", 16); // prologue
s.Insert (GetRemoteIdentity ()->GetIdentHash (), 32); // bhash s.Insert (GetRemoteIdentity ()->GetIdentHash (), 32); // bhash
s.Insert (signedData, 10 + asz); // ver, nonce, ts, asz, Alice's endpoint s.Insert (signedData, 10 + asz); // ver, nonce, ts, asz, Alice's endpoint
@ -3058,67 +3031,38 @@ namespace transport
void SSU2Session::SendPathResponse (const uint8_t * data, size_t len) void SSU2Session::SendPathResponse (const uint8_t * data, size_t len)
{ {
uint8_t payload[SSU2_MAX_PACKET_SIZE]; if (len > m_MaxPayloadSize - 3)
size_t payloadSize = 0;
// datetime block
payload[0] = eSSU2BlkDateTime;
htobe16buf (payload + 1, 4);
htobe32buf (payload + 3, (i2p::util::GetMillisecondsSinceEpoch () + 500)/1000);
payloadSize += 7;
// address block
payloadSize += CreateAddressBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize, m_RemoteEndpoint);
// path response
if (payloadSize + len > m_MaxPayloadSize)
{ {
LogPrint (eLogWarning, "SSU2: Incorrect data size for path response ", len); LogPrint (eLogWarning, "SSU2: Incorrect data size for path response ", len);
return; return;
} }
payload[payloadSize] = eSSU2BlkPathResponse; uint8_t payload[SSU2_MAX_PACKET_SIZE];
htobe16buf (payload + payloadSize + 1, len); payload[0] = eSSU2BlkPathResponse;
memcpy (payload + payloadSize + 3, data, len); htobe16buf (payload + 1, len);
payloadSize += len + 3; memcpy (payload + 3, data, len);
// ack block size_t payloadSize = len + 3;
if (payloadSize < m_MaxPayloadSize) if (payloadSize < m_MaxPayloadSize)
payloadSize += CreateAckBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize); payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize, payloadSize < 8 ? 8 : 0);
// padding
if (payloadSize < m_MaxPayloadSize)
payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize);
SendData (payload, payloadSize); SendData (payload, payloadSize);
} }
void SSU2Session::SendPathChallenge (const boost::asio::ip::udp::endpoint& to) void SSU2Session::SendPathChallenge ()
{ {
AdjustMaxPayloadSize (SSU2_MIN_PACKET_SIZE); // reduce to minimum
m_WindowSize = SSU2_MIN_WINDOW_SIZE; // reduce window to minimum
uint8_t payload[SSU2_MAX_PACKET_SIZE]; uint8_t payload[SSU2_MAX_PACKET_SIZE];
size_t payloadSize = 0; payload[0] = eSSU2BlkPathChallenge;
// datetime block size_t len = m_Server.GetRng ()() % (m_MaxPayloadSize - 3);
payload[0] = eSSU2BlkDateTime; htobe16buf (payload + 1, len);
htobe16buf (payload + 1, 4); if (len > 0)
htobe32buf (payload + 3, (i2p::util::GetMillisecondsSinceEpoch () + 500)/1000); {
payloadSize += 7; RAND_bytes (payload + 3, len);
// address block with new address i2p::data::IdentHash * hash = new i2p::data::IdentHash ();
payloadSize += CreateAddressBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize, to); SHA256 (payload + 3, len, *hash);
// path challenge block m_PathChallenge.reset (hash);
payload[payloadSize] = eSSU2BlkPathChallenge; }
uint64_t challenge; len += 3;
RAND_bytes ((uint8_t *)&challenge, 8); if (len < m_MaxPayloadSize)
htobe16buf (payload + payloadSize + 1, 8); // always 8 bytes len += CreatePaddingBlock (payload + len, m_MaxPayloadSize - len, len < 8 ? 8 : 0);
htobuf64 (payload + payloadSize + 3, challenge); SendData (payload, len);
payloadSize += 11;
m_PathChallenge = std::make_unique<std::pair<uint64_t, boost::asio::ip::udp::endpoint> >(challenge, to);
// ack block
if (payloadSize < m_MaxPayloadSize)
payloadSize += CreateAckBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize);
// padding block
if (payloadSize < m_MaxPayloadSize)
payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize);
// send to new endpoint
auto existing = m_RemoteEndpoint;
m_RemoteEndpoint = to; // send path challenge to new endpoint
SendData (payload, payloadSize);
m_RemoteEndpoint = existing; // restore endpoint back until path response received
} }
void SSU2Session::CleanUp (uint64_t ts) void SSU2Session::CleanUp (uint64_t ts)

View file

@ -327,7 +327,7 @@ namespace transport
void SendQuickAck (); void SendQuickAck ();
void SendTermination (); void SendTermination ();
void SendPathResponse (const uint8_t * data, size_t len); void SendPathResponse (const uint8_t * data, size_t len);
void SendPathChallenge (const boost::asio::ip::udp::endpoint& to); void SendPathChallenge ();
void HandleDateTime (const uint8_t * buf, size_t len); void HandleDateTime (const uint8_t * buf, size_t len);
void HandleRouterInfo (const uint8_t * buf, size_t len); void HandleRouterInfo (const uint8_t * buf, size_t len);
@ -336,7 +336,7 @@ namespace transport
virtual void HandleAddress (const uint8_t * buf, size_t len); virtual void HandleAddress (const uint8_t * buf, size_t len);
size_t CreateEndpoint (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& ep); size_t CreateEndpoint (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& ep);
std::shared_ptr<const i2p::data::RouterInfo::Address> FindLocalAddress () const; std::shared_ptr<const i2p::data::RouterInfo::Address> FindLocalAddress () const;
void AdjustMaxPayloadSize (size_t maxMtu = SSU2_MAX_PACKET_SIZE); void AdjustMaxPayloadSize ();
bool GetTestingState () const; bool GetTestingState () const;
void SetTestingState(bool testing) const; void SetTestingState(bool testing) const;
std::shared_ptr<const i2p::data::RouterInfo> ExtractRouterInfo (const uint8_t * buf, size_t size); std::shared_ptr<const i2p::data::RouterInfo> ExtractRouterInfo (const uint8_t * buf, size_t size);
@ -394,7 +394,7 @@ namespace transport
boost::asio::deadline_timer m_ConnectTimer; boost::asio::deadline_timer m_ConnectTimer;
SSU2TerminationReason m_TerminationReason; SSU2TerminationReason m_TerminationReason;
size_t m_MaxPayloadSize; size_t m_MaxPayloadSize;
std::unique_ptr<std::pair<uint64_t, boost::asio::ip::udp::endpoint> > m_PathChallenge; std::unique_ptr<i2p::data::IdentHash> m_PathChallenge;
std::unordered_map<uint32_t, uint32_t> m_ReceivedI2NPMsgIDs; // msgID -> timestamp in seconds std::unordered_map<uint32_t, uint32_t> m_ReceivedI2NPMsgIDs; // msgID -> timestamp in seconds
uint64_t m_LastResendTime, m_LastResendAttemptTime; // in milliseconds uint64_t m_LastResendTime, m_LastResendAttemptTime; // in milliseconds
int m_NumRanges; int m_NumRanges;

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2023, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -7,10 +7,6 @@
*/ */
#include <memory> #include <memory>
#include <openssl/evp.h>
#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0
#include <openssl/core_names.h>
#endif
#include "Log.h" #include "Log.h"
#include "Signature.h" #include "Signature.h"
@ -18,163 +14,6 @@ namespace i2p
{ {
namespace crypto namespace crypto
{ {
#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0
DSAVerifier::DSAVerifier ():
m_PublicKey (nullptr)
{
}
DSAVerifier::~DSAVerifier ()
{
if (m_PublicKey)
EVP_PKEY_free (m_PublicKey);
}
void DSAVerifier::SetPublicKey (const uint8_t * signingKey)
{
if (m_PublicKey)
EVP_PKEY_free (m_PublicKey);
BIGNUM * pub = BN_bin2bn (signingKey, DSA_PUBLIC_KEY_LENGTH, NULL);
m_PublicKey = CreateDSA (pub);
BN_free (pub);
}
bool DSAVerifier::Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const
{
// calculate SHA1 digest
uint8_t digest[20], sign[48];
SHA1 (buf, len, digest);
// signature
DSA_SIG * sig = DSA_SIG_new();
DSA_SIG_set0 (sig, BN_bin2bn (signature, DSA_SIGNATURE_LENGTH/2, NULL), BN_bin2bn (signature + DSA_SIGNATURE_LENGTH/2, DSA_SIGNATURE_LENGTH/2, NULL));
// to DER format
uint8_t * s = sign;
auto l = i2d_DSA_SIG (sig, &s);
DSA_SIG_free(sig);
// verify
auto ctx = EVP_PKEY_CTX_new (m_PublicKey, NULL);
EVP_PKEY_verify_init(ctx);
EVP_PKEY_CTX_set_signature_md(ctx, EVP_sha1());
bool ret = EVP_PKEY_verify(ctx, sign, l, digest, 20);
EVP_PKEY_CTX_free(ctx);
return ret;
}
DSASigner::DSASigner (const uint8_t * signingPrivateKey, const uint8_t * signingPublicKey)
{
BIGNUM * priv = BN_bin2bn (signingPrivateKey, DSA_PRIVATE_KEY_LENGTH, NULL);
m_PrivateKey = CreateDSA (nullptr, priv);
BN_free (priv);
}
DSASigner::~DSASigner ()
{
if (m_PrivateKey)
EVP_PKEY_free (m_PrivateKey);
}
void DSASigner::Sign (const uint8_t * buf, int len, uint8_t * signature) const
{
uint8_t digest[20], sign[48];
SHA1 (buf, len, digest);
auto ctx = EVP_PKEY_CTX_new (m_PrivateKey, NULL);
EVP_PKEY_sign_init(ctx);
EVP_PKEY_CTX_set_signature_md(ctx, EVP_sha1());
size_t l = 48;
EVP_PKEY_sign(ctx, sign, &l, digest, 20);
const uint8_t * s1 = sign;
DSA_SIG * sig = d2i_DSA_SIG (NULL, &s1, l);
const BIGNUM * r, * s;
DSA_SIG_get0 (sig, &r, &s);
bn2buf (r, signature, DSA_SIGNATURE_LENGTH/2);
bn2buf (s, signature + DSA_SIGNATURE_LENGTH/2, DSA_SIGNATURE_LENGTH/2);
DSA_SIG_free(sig);
EVP_PKEY_CTX_free(ctx);
}
void CreateDSARandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey)
{
EVP_PKEY * paramskey = CreateDSA();
EVP_PKEY_CTX * ctx = EVP_PKEY_CTX_new_from_pkey(NULL, paramskey, NULL);
EVP_PKEY_keygen_init(ctx);
EVP_PKEY * pkey = nullptr;
EVP_PKEY_keygen(ctx, &pkey);
BIGNUM * pub = NULL, * priv = NULL;
EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_PUB_KEY, &pub);
bn2buf (pub, signingPublicKey, DSA_PUBLIC_KEY_LENGTH);
EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_PRIV_KEY, &priv);
bn2buf (priv, signingPrivateKey, DSA_PRIVATE_KEY_LENGTH);
BN_free (pub); BN_free (priv);
EVP_PKEY_free (pkey);
EVP_PKEY_free (paramskey);
EVP_PKEY_CTX_free (ctx);
}
#else
DSAVerifier::DSAVerifier ()
{
m_PublicKey = CreateDSA ();
}
DSAVerifier::~DSAVerifier ()
{
DSA_free (m_PublicKey);
}
void DSAVerifier::SetPublicKey (const uint8_t * signingKey)
{
DSA_set0_key (m_PublicKey, BN_bin2bn (signingKey, DSA_PUBLIC_KEY_LENGTH, NULL), NULL);
}
bool DSAVerifier::Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const
{
// calculate SHA1 digest
uint8_t digest[20];
SHA1 (buf, len, digest);
// signature
DSA_SIG * sig = DSA_SIG_new();
DSA_SIG_set0 (sig, BN_bin2bn (signature, DSA_SIGNATURE_LENGTH/2, NULL), BN_bin2bn (signature + DSA_SIGNATURE_LENGTH/2, DSA_SIGNATURE_LENGTH/2, NULL));
// DSA verification
int ret = DSA_do_verify (digest, 20, sig, m_PublicKey);
DSA_SIG_free(sig);
return ret;
}
DSASigner::DSASigner (const uint8_t * signingPrivateKey, const uint8_t * signingPublicKey)
{
m_PrivateKey = CreateDSA ();
DSA_set0_key (m_PrivateKey, BN_bin2bn (signingPublicKey, DSA_PUBLIC_KEY_LENGTH, NULL), BN_bin2bn (signingPrivateKey, DSA_PRIVATE_KEY_LENGTH, NULL));
}
DSASigner::~DSASigner ()
{
DSA_free (m_PrivateKey);
}
void DSASigner::Sign (const uint8_t * buf, int len, uint8_t * signature) const
{
uint8_t digest[20];
SHA1 (buf, len, digest);
DSA_SIG * sig = DSA_do_sign (digest, 20, m_PrivateKey);
const BIGNUM * r, * s;
DSA_SIG_get0 (sig, &r, &s);
bn2buf (r, signature, DSA_SIGNATURE_LENGTH/2);
bn2buf (s, signature + DSA_SIGNATURE_LENGTH/2, DSA_SIGNATURE_LENGTH/2);
DSA_SIG_free(sig);
}
void CreateDSARandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey)
{
DSA * dsa = CreateDSA ();
DSA_generate_key (dsa);
const BIGNUM * pub_key, * priv_key;
DSA_get0_key(dsa, &pub_key, &priv_key);
bn2buf (priv_key, signingPrivateKey, DSA_PRIVATE_KEY_LENGTH);
bn2buf (pub_key, signingPublicKey, DSA_PUBLIC_KEY_LENGTH);
DSA_free (dsa);
}
#endif
#if OPENSSL_EDDSA #if OPENSSL_EDDSA
EDDSA25519Verifier::EDDSA25519Verifier (): EDDSA25519Verifier::EDDSA25519Verifier ():
m_Pkey (nullptr) m_Pkey (nullptr)
@ -310,178 +149,5 @@ namespace crypto
LogPrint (eLogError, "EdDSA signing key is not set"); LogPrint (eLogError, "EdDSA signing key is not set");
} }
#endif #endif
#if (OPENSSL_VERSION_NUMBER >= 0x030000000)
static const OSSL_PARAM EDDSA25519phParams[] =
{
OSSL_PARAM_utf8_string ("instance", (char *)"Ed25519ph", 9),
OSSL_PARAM_END
};
bool EDDSA25519phVerifier::Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const
{
auto pkey = GetPkey ();
if (pkey)
{
uint8_t digest[64];
SHA512 (buf, len, digest);
EVP_MD_CTX * ctx = EVP_MD_CTX_create ();
EVP_DigestVerifyInit_ex (ctx, NULL, NULL, NULL, NULL, pkey, EDDSA25519phParams);
auto ret = EVP_DigestVerify (ctx, signature, 64, digest, 64);
EVP_MD_CTX_destroy (ctx);
return ret;
}
else
LogPrint (eLogError, "EdDSA verification key is not set");
return false;
}
EDDSA25519phSigner::EDDSA25519phSigner (const uint8_t * signingPrivateKey):
EDDSA25519Signer (signingPrivateKey)
{
}
void EDDSA25519phSigner::Sign (const uint8_t * buf, int len, uint8_t * signature) const
{
auto pkey = GetPkey ();
if (pkey)
{
uint8_t digest[64];
SHA512 (buf, len, digest);
EVP_MD_CTX * ctx = EVP_MD_CTX_create ();
size_t l = 64;
uint8_t sig[64];
EVP_DigestSignInit_ex (ctx, NULL, NULL, NULL, NULL, pkey, EDDSA25519phParams);
if (!EVP_DigestSign (ctx, sig, &l, digest, 64))
LogPrint (eLogError, "EdDSA signing failed");
memcpy (signature, sig, 64);
EVP_MD_CTX_destroy (ctx);
}
else
LogPrint (eLogError, "EdDSA signing key is not set");
}
#endif
#if OPENSSL_PQ
MLDSA44Verifier::MLDSA44Verifier ():
m_Pkey (nullptr)
{
}
MLDSA44Verifier::~MLDSA44Verifier ()
{
EVP_PKEY_free (m_Pkey);
}
void MLDSA44Verifier::SetPublicKey (const uint8_t * signingKey)
{
if (m_Pkey)
{
EVP_PKEY_free (m_Pkey);
m_Pkey = nullptr;
}
OSSL_PARAM params[] =
{
OSSL_PARAM_octet_string (OSSL_PKEY_PARAM_PUB_KEY, (uint8_t *)signingKey, GetPublicKeyLen ()),
OSSL_PARAM_END
};
EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name (NULL, "ML-DSA-44", NULL);
if (ctx)
{
EVP_PKEY_fromdata_init (ctx);
EVP_PKEY_fromdata (ctx, &m_Pkey, OSSL_KEYMGMT_SELECT_PUBLIC_KEY, params);
EVP_PKEY_CTX_free (ctx);
}
else
LogPrint (eLogError, "MLDSA44 can't create PKEY context");
}
bool MLDSA44Verifier::Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const
{
bool ret = false;
if (m_Pkey)
{
EVP_PKEY_CTX * vctx = EVP_PKEY_CTX_new_from_pkey (NULL, m_Pkey, NULL);
if (vctx)
{
EVP_SIGNATURE * sig = EVP_SIGNATURE_fetch (NULL, "ML-DSA-44", NULL);
if (sig)
{
int encode = 1;
OSSL_PARAM params[] =
{
OSSL_PARAM_int(OSSL_SIGNATURE_PARAM_MESSAGE_ENCODING, &encode),
OSSL_PARAM_END
};
EVP_PKEY_verify_message_init (vctx, sig, params);
ret = EVP_PKEY_verify (vctx, signature, GetSignatureLen (), buf, len);
EVP_SIGNATURE_free (sig);
}
EVP_PKEY_CTX_free (vctx);
}
else
LogPrint (eLogError, "MLDSA44 can't obtain context from PKEY");
}
else
LogPrint (eLogError, "MLDSA44 verification key is not set");
return ret;
}
MLDSA44Signer::MLDSA44Signer (const uint8_t * signingPrivateKey):
m_Pkey (nullptr)
{
OSSL_PARAM params[] =
{
OSSL_PARAM_octet_string (OSSL_PKEY_PARAM_PRIV_KEY, (uint8_t *)signingPrivateKey, MLDSA44_PRIVATE_KEY_LENGTH),
OSSL_PARAM_END
};
EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name (NULL, "ML-DSA-44", NULL);
if (ctx)
{
EVP_PKEY_fromdata_init (ctx);
EVP_PKEY_fromdata (ctx, &m_Pkey, OSSL_KEYMGMT_SELECT_PRIVATE_KEY, params);
EVP_PKEY_CTX_free (ctx);
}
else
LogPrint (eLogError, "MLDSA44 can't create PKEY context");
}
MLDSA44Signer::~MLDSA44Signer ()
{
if (m_Pkey) EVP_PKEY_free (m_Pkey);
}
void MLDSA44Signer::Sign (const uint8_t * buf, int len, uint8_t * signature) const
{
if (m_Pkey)
{
EVP_PKEY_CTX * sctx = EVP_PKEY_CTX_new_from_pkey (NULL, m_Pkey, NULL);
if (sctx)
{
EVP_SIGNATURE * sig = EVP_SIGNATURE_fetch (NULL, "ML-DSA-44", NULL);
if (sig)
{
int encode = 1;
OSSL_PARAM params[] =
{
OSSL_PARAM_int(OSSL_SIGNATURE_PARAM_MESSAGE_ENCODING, &encode),
OSSL_PARAM_END
};
EVP_PKEY_sign_message_init (sctx, sig, params);
size_t siglen = MLDSA44_SIGNATURE_LENGTH;
EVP_PKEY_sign (sctx, signature, &siglen, buf, len);
EVP_SIGNATURE_free (sig);
}
EVP_PKEY_CTX_free (sctx);
}
else
LogPrint (eLogError, "MLDSA44 can't obtain context from PKEY");
}
else
LogPrint (eLogError, "MLDSA44 signing key is not set");
}
#endif
} }
} }

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2023, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -43,7 +43,6 @@ namespace crypto
virtual void Sign (const uint8_t * buf, int len, uint8_t * signature) const = 0; virtual void Sign (const uint8_t * buf, int len, uint8_t * signature) const = 0;
}; };
// DSA
const size_t DSA_PUBLIC_KEY_LENGTH = 128; const size_t DSA_PUBLIC_KEY_LENGTH = 128;
const size_t DSA_SIGNATURE_LENGTH = 40; const size_t DSA_SIGNATURE_LENGTH = 40;
const size_t DSA_PRIVATE_KEY_LENGTH = DSA_SIGNATURE_LENGTH/2; const size_t DSA_PRIVATE_KEY_LENGTH = DSA_SIGNATURE_LENGTH/2;
@ -51,47 +50,87 @@ namespace crypto
{ {
public: public:
DSAVerifier (); DSAVerifier ()
~DSAVerifier (); {
m_PublicKey = CreateDSA ();
}
// implements Verifier void SetPublicKey (const uint8_t * signingKey)
void SetPublicKey (const uint8_t * signingKey) override; {
bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const override; DSA_set0_key (m_PublicKey, BN_bin2bn (signingKey, DSA_PUBLIC_KEY_LENGTH, NULL), NULL);
size_t GetPublicKeyLen () const override { return DSA_PUBLIC_KEY_LENGTH; }; }
size_t GetSignatureLen () const override { return DSA_SIGNATURE_LENGTH; };
~DSAVerifier ()
{
DSA_free (m_PublicKey);
}
bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const
{
// calculate SHA1 digest
uint8_t digest[20];
SHA1 (buf, len, digest);
// signature
DSA_SIG * sig = DSA_SIG_new();
DSA_SIG_set0 (sig, BN_bin2bn (signature, DSA_SIGNATURE_LENGTH/2, NULL), BN_bin2bn (signature + DSA_SIGNATURE_LENGTH/2, DSA_SIGNATURE_LENGTH/2, NULL));
// DSA verification
int ret = DSA_do_verify (digest, 20, sig, m_PublicKey);
DSA_SIG_free(sig);
return ret;
}
size_t GetPublicKeyLen () const { return DSA_PUBLIC_KEY_LENGTH; };
size_t GetSignatureLen () const { return DSA_SIGNATURE_LENGTH; };
private: private:
#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0
EVP_PKEY * m_PublicKey;
#else
DSA * m_PublicKey; DSA * m_PublicKey;
#endif
}; };
class DSASigner: public Signer class DSASigner: public Signer
{ {
public: public:
DSASigner (const uint8_t * signingPrivateKey, const uint8_t * signingPublicKey); DSASigner (const uint8_t * signingPrivateKey, const uint8_t * signingPublicKey)
// openssl 1.1 always requires DSA public key even for signing // openssl 1.1 always requires DSA public key even for signing
~DSASigner (); {
m_PrivateKey = CreateDSA ();
DSA_set0_key (m_PrivateKey, BN_bin2bn (signingPublicKey, DSA_PUBLIC_KEY_LENGTH, NULL), BN_bin2bn (signingPrivateKey, DSA_PRIVATE_KEY_LENGTH, NULL));
}
// implements Signer ~DSASigner ()
void Sign (const uint8_t * buf, int len, uint8_t * signature) const override; {
DSA_free (m_PrivateKey);
}
void Sign (const uint8_t * buf, int len, uint8_t * signature) const
{
uint8_t digest[20];
SHA1 (buf, len, digest);
DSA_SIG * sig = DSA_do_sign (digest, 20, m_PrivateKey);
const BIGNUM * r, * s;
DSA_SIG_get0 (sig, &r, &s);
bn2buf (r, signature, DSA_SIGNATURE_LENGTH/2);
bn2buf (s, signature + DSA_SIGNATURE_LENGTH/2, DSA_SIGNATURE_LENGTH/2);
DSA_SIG_free(sig);
}
private: private:
#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0
EVP_PKEY * m_PrivateKey;
#else
DSA * m_PrivateKey; DSA * m_PrivateKey;
#endif
}; };
void CreateDSARandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey); inline void CreateDSARandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey)
{
DSA * dsa = CreateDSA ();
DSA_generate_key (dsa);
const BIGNUM * pub_key, * priv_key;
DSA_get0_key(dsa, &pub_key, &priv_key);
bn2buf (priv_key, signingPrivateKey, DSA_PRIVATE_KEY_LENGTH);
bn2buf (pub_key, signingPublicKey, DSA_PUBLIC_KEY_LENGTH);
DSA_free (dsa);
}
// ECDSA
struct SHA256Hash struct SHA256Hash
{ {
static void CalculateHash (const uint8_t * buf, size_t len, uint8_t * digest) static void CalculateHash (const uint8_t * buf, size_t len, uint8_t * digest)
@ -122,6 +161,7 @@ namespace crypto
enum { hashLen = 64 }; enum { hashLen = 64 };
}; };
// EcDSA
template<typename Hash, int curve, size_t keyLen> template<typename Hash, int curve, size_t keyLen>
class ECDSAVerifier: public Verifier class ECDSAVerifier: public Verifier
{ {
@ -264,27 +304,13 @@ namespace crypto
private: private:
#if OPENSSL_EDDSA #if OPENSSL_EDDSA
EVP_PKEY * m_Pkey; EVP_PKEY * m_Pkey;
protected:
EVP_PKEY * GetPkey () const { return m_Pkey; };
#else #else
EDDSAPoint m_PublicKey; EDDSAPoint m_PublicKey;
uint8_t m_PublicKeyEncoded[EDDSA25519_PUBLIC_KEY_LENGTH]; uint8_t m_PublicKeyEncoded[EDDSA25519_PUBLIC_KEY_LENGTH];
#endif #endif
}; };
#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0
class EDDSA25519phVerifier: public EDDSA25519Verifier
{
public:
bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const;
};
#endif
class EDDSA25519SignerCompat: public Signer class EDDSA25519SignerCompat: public Signer
{ {
public: public:
@ -313,10 +339,6 @@ namespace crypto
void Sign (const uint8_t * buf, int len, uint8_t * signature) const; void Sign (const uint8_t * buf, int len, uint8_t * signature) const;
protected:
EVP_PKEY * GetPkey () const { return m_Pkey; };
private: private:
EVP_PKEY * m_Pkey; EVP_PKEY * m_Pkey;
@ -326,18 +348,6 @@ namespace crypto
typedef EDDSA25519SignerCompat EDDSA25519Signer; typedef EDDSA25519SignerCompat EDDSA25519Signer;
#endif
#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0
class EDDSA25519phSigner: public EDDSA25519Signer
{
public:
EDDSA25519phSigner (const uint8_t * signingPrivateKey);
void Sign (const uint8_t * buf, int len, uint8_t * signature) const;
};
#endif #endif
inline void CreateEDDSA25519RandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey) inline void CreateEDDSA25519RandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey)
@ -520,57 +530,6 @@ namespace crypto
RedDSA25519Signer signer (signingPrivateKey); RedDSA25519Signer signer (signingPrivateKey);
memcpy (signingPublicKey, signer.GetPublicKey (), EDDSA25519_PUBLIC_KEY_LENGTH); memcpy (signingPublicKey, signer.GetPublicKey (), EDDSA25519_PUBLIC_KEY_LENGTH);
} }
#if OPENSSL_PQ
#include <openssl/core_names.h>
// Post-Quantum
const size_t MLDSA44_PUBLIC_KEY_LENGTH = 1312;
const size_t MLDSA44_SIGNATURE_LENGTH = 2420;
const size_t MLDSA44_PRIVATE_KEY_LENGTH = 2560;
class MLDSA44Verifier: public Verifier
{
public:
MLDSA44Verifier ();
void SetPublicKey (const uint8_t * signingKey);
~MLDSA44Verifier ();
bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const;
size_t GetPublicKeyLen () const { return MLDSA44_PUBLIC_KEY_LENGTH; };
size_t GetSignatureLen () const { return MLDSA44_SIGNATURE_LENGTH; };
size_t GetPrivateKeyLen () const { return MLDSA44_PRIVATE_KEY_LENGTH; };
private:
EVP_PKEY * m_Pkey;
};
class MLDSA44Signer: public Signer
{
public:
MLDSA44Signer (const uint8_t * signingPrivateKey);
~MLDSA44Signer ();
void Sign (const uint8_t * buf, int len, uint8_t * signature) const;
private:
EVP_PKEY * m_Pkey;
};
inline void CreateMLDSA44RandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey)
{
EVP_PKEY * pkey = EVP_PKEY_Q_keygen (NULL, NULL, "ML-DSA-44");
size_t len = MLDSA44_PUBLIC_KEY_LENGTH;
EVP_PKEY_get_octet_string_param (pkey, OSSL_PKEY_PARAM_PUB_KEY, signingPublicKey, MLDSA44_PUBLIC_KEY_LENGTH, &len);
len = MLDSA44_PRIVATE_KEY_LENGTH;
EVP_PKEY_get_octet_string_param (pkey, OSSL_PKEY_PARAM_PRIV_KEY, signingPrivateKey, MLDSA44_PRIVATE_KEY_LENGTH, &len);
EVP_PKEY_free (pkey);
}
#endif
} }
} }

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -19,33 +19,18 @@ namespace i2p
{ {
namespace stream namespace stream
{ {
void SendBufferQueue::Add (std::shared_ptr<SendBuffer>&& buf) void SendBufferQueue::Add (std::shared_ptr<SendBuffer> buf)
{ {
if (buf) if (buf)
{ {
m_Buffers.push_back (buf);
m_Size += buf->len; m_Size += buf->len;
m_Buffers.push_back (std::move (buf));
} }
} }
size_t SendBufferQueue::Get (uint8_t * buf, size_t len) size_t SendBufferQueue::Get (uint8_t * buf, size_t len)
{ {
if (!m_Size) return 0;
size_t offset = 0; size_t offset = 0;
if (len >= m_Size)
{
for (auto& it: m_Buffers)
{
auto rem = it->GetRemainingSize ();
memcpy (buf + offset, it->GetRemaningBuffer (), rem);
offset += rem;
}
m_Buffers.clear ();
m_Size = 0;
return offset;
}
else
{
while (!m_Buffers.empty () && offset < len) while (!m_Buffers.empty () && offset < len)
{ {
auto nextBuffer = m_Buffers.front (); auto nextBuffer = m_Buffers.front ();
@ -67,7 +52,6 @@ namespace stream
} }
} }
m_Size -= offset; m_Size -= offset;
}
return offset; return offset;
} }
@ -75,7 +59,7 @@ namespace stream
{ {
if (!m_Buffers.empty ()) if (!m_Buffers.empty ())
{ {
for (auto& it: m_Buffers) for (auto it: m_Buffers)
it->Cancel (); it->Cancel ();
m_Buffers.clear (); m_Buffers.clear ();
m_Size = 0; m_Size = 0;
@ -96,7 +80,7 @@ namespace stream
m_WindowDropTargetSize (0), m_WindowIncCounter (0), m_RTO (INITIAL_RTO), m_WindowDropTargetSize (0), m_WindowIncCounter (0), m_RTO (INITIAL_RTO),
m_AckDelay (local.GetOwner ()->GetStreamingAckDelay ()), m_PrevRTTSample (INITIAL_RTT), m_WindowSizeTail (0), m_AckDelay (local.GetOwner ()->GetStreamingAckDelay ()), m_PrevRTTSample (INITIAL_RTT), m_WindowSizeTail (0),
m_Jitter (0), m_MinPacingTime (0), m_Jitter (0), m_MinPacingTime (0),
m_PacingTime (INITIAL_PACING_TIME), m_PacingTimeRem (0), m_LastSendTime (0), m_LastACKRecieveTime (0), m_ACKRecieveInterval (local.GetOwner ()->GetStreamingAckDelay ()), m_RemoteLeaseChangeTime (0), m_LastWindowIncTime (0), m_PacingTime (INITIAL_PACING_TIME), m_PacingTimeRem (0), m_LastSendTime (0), m_LastACKRecieveTime (0), m_ACKRecieveInterval (local.GetOwner ()->GetStreamingAckDelay ()), m_RemoteLeaseChangeTime (0),
m_LastACKSendTime (0), m_PacketACKInterval (1), m_PacketACKIntervalRem (0), // for limit inbound speed m_LastACKSendTime (0), m_PacketACKInterval (1), m_PacketACKIntervalRem (0), // for limit inbound speed
m_NumResendAttempts (0), m_NumPacketsToSend (0), m_MTU (STREAMING_MTU) m_NumResendAttempts (0), m_NumPacketsToSend (0), m_MTU (STREAMING_MTU)
{ {
@ -123,7 +107,7 @@ namespace stream
m_WindowSize (INITIAL_WINDOW_SIZE), m_LastWindowDropSize (0), m_WindowDropTargetSize (0), m_WindowIncCounter (0), m_WindowSize (INITIAL_WINDOW_SIZE), m_LastWindowDropSize (0), m_WindowDropTargetSize (0), m_WindowIncCounter (0),
m_RTO (INITIAL_RTO), m_AckDelay (local.GetOwner ()->GetStreamingAckDelay ()), m_RTO (INITIAL_RTO), m_AckDelay (local.GetOwner ()->GetStreamingAckDelay ()),
m_PrevRTTSample (INITIAL_RTT), m_WindowSizeTail (0), m_Jitter (0), m_MinPacingTime (0), m_PrevRTTSample (INITIAL_RTT), m_WindowSizeTail (0), m_Jitter (0), m_MinPacingTime (0),
m_PacingTime (INITIAL_PACING_TIME), m_PacingTimeRem (0), m_LastSendTime (0), m_LastACKRecieveTime (0), m_ACKRecieveInterval (local.GetOwner ()->GetStreamingAckDelay ()), m_RemoteLeaseChangeTime (0), m_LastWindowIncTime (0), m_PacingTime (INITIAL_PACING_TIME), m_PacingTimeRem (0), m_LastSendTime (0), m_LastACKRecieveTime (0), m_ACKRecieveInterval (local.GetOwner ()->GetStreamingAckDelay ()), m_RemoteLeaseChangeTime (0),
m_LastACKSendTime (0), m_PacketACKInterval (1), m_PacketACKIntervalRem (0), // for limit inbound speed m_LastACKSendTime (0), m_PacketACKInterval (1), m_PacketACKIntervalRem (0), // for limit inbound speed
m_NumResendAttempts (0), m_NumPacketsToSend (0), m_MTU (STREAMING_MTU) m_NumResendAttempts (0), m_NumPacketsToSend (0), m_MTU (STREAMING_MTU)
{ {
@ -629,8 +613,10 @@ namespace stream
if (wasInitial) if (wasInitial)
ScheduleResend (); ScheduleResend ();
} }
if (m_IsClientChoked && ackThrough >= m_DropWindowDelaySequenceNumber) if (m_IsClientChoked && ackThrough > m_DropWindowDelaySequenceNumber)
{
m_IsClientChoked = false; m_IsClientChoked = false;
}
if (m_IsWinDropped && ackThrough > m_DropWindowDelaySequenceNumber) if (m_IsWinDropped && ackThrough > m_DropWindowDelaySequenceNumber)
{ {
m_IsFirstRttSample = true; m_IsFirstRttSample = true;
@ -733,10 +719,10 @@ namespace stream
else if (handler) else if (handler)
handler(boost::system::error_code ()); handler(boost::system::error_code ());
auto s = shared_from_this (); auto s = shared_from_this ();
boost::asio::post (m_Service, [s, buffer = std::move(buffer)]() mutable boost::asio::post (m_Service, [s, buffer]()
{ {
if (buffer) if (buffer)
s->m_SendBuffer.Add (std::move(buffer)); s->m_SendBuffer.Add (buffer);
s->SendBuffer (); s->SendBuffer ();
}); });
} }
@ -1311,14 +1297,8 @@ namespace stream
m_NumPacketsToSend = 1; m_PacingTimeRem = 0; m_NumPacketsToSend = 1; m_PacingTimeRem = 0;
} }
m_IsSendTime = true; m_IsSendTime = true;
if (m_WindowIncCounter && (m_WindowSize < MAX_WINDOW_SIZE || m_WindowDropTargetSize) && !m_SendBuffer.IsEmpty () && m_PacingTime > m_MinPacingTime && m_RTT <= m_SlowRTT) if (m_WindowIncCounter && (m_WindowSize < MAX_WINDOW_SIZE || m_WindowDropTargetSize) && !m_SendBuffer.IsEmpty () && m_PacingTime > m_MinPacingTime)
{ {
float winSize = m_WindowSize;
if (m_WindowDropTargetSize)
winSize = m_WindowDropTargetSize;
float maxWinSize = MAX_WINDOW_SIZE;
if (m_LastWindowIncTime)
maxWinSize = (ts - m_LastWindowIncTime) / (m_RTT / MAX_WINDOW_SIZE_INC_PER_RTT) + winSize;
for (int i = 0; i < m_NumPacketsToSend; i++) for (int i = 0; i < m_NumPacketsToSend; i++)
{ {
if (m_WindowIncCounter) if (m_WindowIncCounter)
@ -1327,17 +1307,12 @@ namespace stream
{ {
if (m_LastWindowDropSize && (m_LastWindowDropSize >= m_WindowDropTargetSize)) if (m_LastWindowDropSize && (m_LastWindowDropSize >= m_WindowDropTargetSize))
m_WindowDropTargetSize += 1 - (1 / ((m_LastWindowDropSize + PREV_SPEED_KEEP_TIME_COEFF) / m_WindowDropTargetSize)); // some magic here m_WindowDropTargetSize += 1 - (1 / ((m_LastWindowDropSize + PREV_SPEED_KEEP_TIME_COEFF) / m_WindowDropTargetSize)); // some magic here
else if (m_LastWindowDropSize && (m_LastWindowDropSize < m_WindowDropTargetSize)) else if (m_LastWindowDropSize && (m_LastWindowDropSize < m_WindowSize))
m_WindowDropTargetSize += (m_WindowDropTargetSize - (m_LastWindowDropSize - PREV_SPEED_KEEP_TIME_COEFF)) / m_WindowDropTargetSize; // some magic here m_WindowDropTargetSize += (m_WindowDropTargetSize - (m_LastWindowDropSize - PREV_SPEED_KEEP_TIME_COEFF)) / m_WindowDropTargetSize; // some magic here
else else
m_WindowDropTargetSize += (m_WindowDropTargetSize - (1 - PREV_SPEED_KEEP_TIME_COEFF)) / m_WindowDropTargetSize; m_WindowDropTargetSize += (m_WindowDropTargetSize - (1 - PREV_SPEED_KEEP_TIME_COEFF)) / m_WindowDropTargetSize;
if (m_WindowDropTargetSize > MAX_WINDOW_SIZE) m_WindowDropTargetSize = MAX_WINDOW_SIZE; if (m_WindowDropTargetSize > MAX_WINDOW_SIZE) m_WindowDropTargetSize = MAX_WINDOW_SIZE;
m_WindowIncCounter--; m_WindowIncCounter--;
if (m_WindowDropTargetSize >= maxWinSize)
{
m_WindowDropTargetSize = maxWinSize;
break;
}
} }
else else
{ {
@ -1349,17 +1324,11 @@ namespace stream
m_WindowSize += (m_WindowSize - (1 - PREV_SPEED_KEEP_TIME_COEFF)) / m_WindowSize; m_WindowSize += (m_WindowSize - (1 - PREV_SPEED_KEEP_TIME_COEFF)) / m_WindowSize;
if (m_WindowSize > MAX_WINDOW_SIZE) m_WindowSize = MAX_WINDOW_SIZE; if (m_WindowSize > MAX_WINDOW_SIZE) m_WindowSize = MAX_WINDOW_SIZE;
m_WindowIncCounter--; m_WindowIncCounter--;
if (m_WindowSize >= maxWinSize)
{
m_WindowSize = maxWinSize;
break;
}
} }
} }
else else
break; break;
} }
m_LastWindowIncTime = ts;
UpdatePacingTime (); UpdatePacingTime ();
} }
else if (m_WindowIncCounter && m_WindowSize == MAX_WINDOW_SIZE && !m_SendBuffer.IsEmpty () && m_PacingTime > m_MinPacingTime) else if (m_WindowIncCounter && m_WindowSize == MAX_WINDOW_SIZE && !m_SendBuffer.IsEmpty () && m_PacingTime > m_MinPacingTime)
@ -1677,22 +1646,14 @@ namespace stream
void Stream::ProcessWindowDrop () void Stream::ProcessWindowDrop ()
{ {
if (m_WindowDropTargetSize) if (m_WindowSize > m_LastWindowDropSize)
m_WindowDropTargetSize = (m_WindowDropTargetSize / 2) * 0.75; // congestion window size and -25% to drain queue
else
{ {
if (m_WindowSize < m_LastWindowDropSize) m_LastWindowDropSize = (m_LastWindowDropSize + m_WindowSize + m_WindowSizeTail) / 2;
{
m_LastWindowDropSize = std::max ((m_WindowSize - MAX_WINDOW_SIZE_INC_PER_RTT), (m_WindowSize - (m_LastWindowDropSize - m_WindowSize)));
if (m_LastWindowDropSize < MIN_WINDOW_SIZE) m_LastWindowDropSize = MIN_WINDOW_SIZE;
}
else
{
m_LastWindowDropSize = std::max ((m_WindowSize - MAX_WINDOW_SIZE_INC_PER_RTT), ((m_LastWindowDropSize + m_WindowSize + m_WindowSizeTail) / 2));
if (m_LastWindowDropSize > MAX_WINDOW_SIZE) m_LastWindowDropSize = MAX_WINDOW_SIZE; if (m_LastWindowDropSize > MAX_WINDOW_SIZE) m_LastWindowDropSize = MAX_WINDOW_SIZE;
} }
m_WindowDropTargetSize = m_LastWindowDropSize * 0.75; // -25% to drain queue else
} m_LastWindowDropSize = m_WindowSize;
m_WindowDropTargetSize = m_LastWindowDropSize - (m_LastWindowDropSize / 4); // -25%;
if (m_WindowDropTargetSize < MIN_WINDOW_SIZE) if (m_WindowDropTargetSize < MIN_WINDOW_SIZE)
m_WindowDropTargetSize = MIN_WINDOW_SIZE; m_WindowDropTargetSize = MIN_WINDOW_SIZE;
m_WindowIncCounter = 0; // disable window growth m_WindowIncCounter = 0; // disable window growth

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -57,7 +57,6 @@ namespace stream
const int INITIAL_WINDOW_SIZE = 10; const int INITIAL_WINDOW_SIZE = 10;
const int MIN_WINDOW_SIZE = 3; const int MIN_WINDOW_SIZE = 3;
const int MAX_WINDOW_SIZE = 512; const int MAX_WINDOW_SIZE = 512;
const int MAX_WINDOW_SIZE_INC_PER_RTT = 16;
const double RTT_EWMA_ALPHA = 0.25; const double RTT_EWMA_ALPHA = 0.25;
const double SLOWRTT_EWMA_ALPHA = 0.05; const double SLOWRTT_EWMA_ALPHA = 0.05;
const double PREV_SPEED_KEEP_TIME_COEFF = 0.35; // 0.1 - 1 // how long will the window size stay around the previous drop level, less is longer const double PREV_SPEED_KEEP_TIME_COEFF = 0.35; // 0.1 - 1 // how long will the window size stay around the previous drop level, less is longer
@ -150,7 +149,7 @@ namespace stream
SendBufferQueue (): m_Size (0) {}; SendBufferQueue (): m_Size (0) {};
~SendBufferQueue () { CleanUp (); }; ~SendBufferQueue () { CleanUp (); };
void Add (std::shared_ptr<SendBuffer>&& buf); void Add (std::shared_ptr<SendBuffer> buf);
size_t Get (uint8_t * buf, size_t len); size_t Get (uint8_t * buf, size_t len);
size_t GetSize () const { return m_Size; }; size_t GetSize () const { return m_Size; };
bool IsEmpty () const { return m_Buffers.empty (); }; bool IsEmpty () const { return m_Buffers.empty (); };
@ -300,7 +299,7 @@ namespace stream
int m_WindowIncCounter, m_RTO, m_AckDelay, m_PrevRTTSample, m_WindowSizeTail; int m_WindowIncCounter, m_RTO, m_AckDelay, m_PrevRTTSample, m_WindowSizeTail;
double m_Jitter; double m_Jitter;
uint64_t m_MinPacingTime, m_PacingTime, m_PacingTimeRem, // microseconds uint64_t m_MinPacingTime, m_PacingTime, m_PacingTimeRem, // microseconds
m_LastSendTime, m_LastACKRecieveTime, m_ACKRecieveInterval, m_RemoteLeaseChangeTime, m_LastWindowIncTime; // milliseconds m_LastSendTime, m_LastACKRecieveTime, m_ACKRecieveInterval, m_RemoteLeaseChangeTime; // milliseconds
uint64_t m_LastACKSendTime, m_PacketACKInterval, m_PacketACKIntervalRem; // for limit inbound speed uint64_t m_LastACKSendTime, m_PacketACKInterval, m_PacketACKIntervalRem; // for limit inbound speed
int m_NumResendAttempts, m_NumPacketsToSend; int m_NumResendAttempts, m_NumPacketsToSend;
size_t m_MTU; size_t m_MTU;

View file

@ -62,22 +62,26 @@ namespace data
std::string ToBase64 (size_t len = sz) const std::string ToBase64 (size_t len = sz) const
{ {
return i2p::data::ByteStreamToBase64 (m_Buf, len); char str[sz*2];
size_t l = i2p::data::ByteStreamToBase64 (m_Buf, len, str, sz*2);
return std::string (str, str + l);
} }
std::string ToBase32 (size_t len = sz) const std::string ToBase32 (size_t len = sz) const
{ {
return i2p::data::ByteStreamToBase32 (m_Buf, len); char str[sz*2];
size_t l = i2p::data::ByteStreamToBase32 (m_Buf, len, str, sz*2);
return std::string (str, str + l);
} }
size_t FromBase32 (std::string_view s) size_t FromBase32 (std::string_view s)
{ {
return i2p::data::Base32ToByteStream (s, m_Buf, sz); return i2p::data::Base32ToByteStream (s.data (), s.length (), m_Buf, sz);
} }
size_t FromBase64 (std::string_view s) size_t FromBase64 (std::string_view s)
{ {
return i2p::data::Base64ToByteStream (s, m_Buf, sz); return i2p::data::Base64ToByteStream (s.data (), s.length (), m_Buf, sz);
} }
uint8_t GetBit (int i) const uint8_t GetBit (int i) const

View file

@ -131,35 +131,27 @@ namespace tunnel
LogPrint (eLogDebug, "TransitTunnel: handle msg for endpoint ", GetTunnelID ()); LogPrint (eLogDebug, "TransitTunnel: handle msg for endpoint ", GetTunnelID ());
std::lock_guard<std::mutex> l(m_HandleMutex); std::lock_guard<std::mutex> l(m_HandleMutex);
if (!m_Endpoint) m_Endpoint = std::make_unique<TunnelEndpoint>(false); // transit endpoint is always outbound m_Endpoint.HandleDecryptedTunnelDataMsg (newMsg);
m_Endpoint->HandleDecryptedTunnelDataMsg (newMsg);
} }
void TransitTunnelEndpoint::FlushTunnelDataMsgs () void TransitTunnelEndpoint::FlushTunnelDataMsgs ()
{
if (m_Endpoint)
{ {
std::lock_guard<std::mutex> l(m_HandleMutex); std::lock_guard<std::mutex> l(m_HandleMutex);
m_Endpoint->FlushI2NPMsgs (); m_Endpoint.FlushI2NPMsgs ();
}
} }
void TransitTunnelEndpoint::Cleanup () void TransitTunnelEndpoint::Cleanup ()
{
if (m_Endpoint)
{ {
std::lock_guard<std::mutex> l(m_HandleMutex); std::lock_guard<std::mutex> l(m_HandleMutex);
m_Endpoint->Cleanup (); m_Endpoint.Cleanup ();
}
} }
std::string TransitTunnelEndpoint::GetNextPeerName () const std::string TransitTunnelEndpoint::GetNextPeerName () const
{ {
if (!m_Endpoint) return ""; auto hash = m_Endpoint.GetCurrentHash ();
auto hash = m_Endpoint->GetCurrentHash ();
if (hash) if (hash)
{ {
const auto& sender = m_Endpoint->GetSender (); const auto& sender = m_Endpoint.GetSender ();
if (sender) if (sender)
{ {
auto transport = sender->GetCurrentTransport (); auto transport = sender->GetCurrentTransport ();

View file

@ -97,19 +97,20 @@ namespace tunnel
TransitTunnelEndpoint (uint32_t receiveTunnelID, TransitTunnelEndpoint (uint32_t receiveTunnelID,
const i2p::data::IdentHash& nextIdent, uint32_t nextTunnelID, const i2p::data::IdentHash& nextIdent, uint32_t nextTunnelID,
const i2p::crypto::AESKey& layerKey, const i2p::crypto::AESKey& ivKey): const i2p::crypto::AESKey& layerKey, const i2p::crypto::AESKey& ivKey):
TransitTunnel (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey) {}; TransitTunnel (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey),
m_Endpoint (false) {}; // transit endpoint is always outbound
void Cleanup () override; void Cleanup () override;
void HandleTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage>&& tunnelMsg) override; void HandleTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage>&& tunnelMsg) override;
void FlushTunnelDataMsgs () override; void FlushTunnelDataMsgs () override;
size_t GetNumTransmittedBytes () const override { return m_Endpoint ? m_Endpoint->GetNumReceivedBytes () : 0; } size_t GetNumTransmittedBytes () const override { return m_Endpoint.GetNumReceivedBytes (); }
std::string GetNextPeerName () const override; std::string GetNextPeerName () const override;
private: private:
std::mutex m_HandleMutex; std::mutex m_HandleMutex;
std::unique_ptr<TunnelEndpoint> m_Endpoint; TunnelEndpoint m_Endpoint;
}; };
std::shared_ptr<TransitTunnel> CreateTransitTunnel (uint32_t receiveTunnelID, std::shared_ptr<TransitTunnel> CreateTransitTunnel (uint32_t receiveTunnelID,

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -10,7 +10,7 @@
#define TRANSPORT_SESSION_H__ #define TRANSPORT_SESSION_H__
#include <inttypes.h> #include <inttypes.h>
#include <string.h> #include <iostream>
#include <memory> #include <memory>
#include <vector> #include <vector>
#include <mutex> #include <mutex>
@ -28,51 +28,45 @@ namespace transport
const size_t IPV6_HEADER_SIZE = 40; const size_t IPV6_HEADER_SIZE = 40;
const size_t UDP_HEADER_SIZE = 8; const size_t UDP_HEADER_SIZE = 8;
template<size_t sz>
class SignedData class SignedData
{ {
public: public:
SignedData (): m_Size(0) {} SignedData () {}
SignedData (const SignedData& other) SignedData (const SignedData& other)
{ {
m_Size = other.m_Size; m_Stream << other.m_Stream.rdbuf ();
memcpy (m_Buf, other.m_Buf, m_Size);
} }
void Reset () void Reset ()
{ {
m_Size = 0; m_Stream.str("");
} }
size_t Insert (const uint8_t * buf, size_t len) void Insert (const uint8_t * buf, size_t len)
{ {
if (m_Size + len > sz) len = sz - m_Size; m_Stream.write ((char *)buf, len);
memcpy (m_Buf + m_Size, buf, len);
m_Size += len;
return len;
} }
template<typename T> template<typename T>
void Insert (T t) void Insert (T t)
{ {
Insert ((const uint8_t *)&t, sizeof (T)); m_Stream.write ((char *)&t, sizeof (T));
} }
bool Verify (std::shared_ptr<const i2p::data::IdentityEx> ident, const uint8_t * signature) const bool Verify (std::shared_ptr<const i2p::data::IdentityEx> ident, const uint8_t * signature) const
{ {
return ident->Verify (m_Buf, m_Size, signature); return ident->Verify ((const uint8_t *)m_Stream.str ().c_str (), m_Stream.str ().size (), signature);
} }
void Sign (const i2p::data::PrivateKeys& keys, uint8_t * signature) const void Sign (const i2p::data::PrivateKeys& keys, uint8_t * signature) const
{ {
keys.Sign (m_Buf, m_Size, signature); keys.Sign ((const uint8_t *)m_Stream.str ().c_str (), m_Stream.str ().size (), signature);
} }
private: private:
uint8_t m_Buf[sz]; std::stringstream m_Stream;
size_t m_Size;
}; };
const int64_t TRANSPORT_SESSION_SLOWNESS_THRESHOLD = 500; // in milliseconds const int64_t TRANSPORT_SESSION_SLOWNESS_THRESHOLD = 500; // in milliseconds

View file

@ -680,21 +680,8 @@ namespace transport
auto directTransports = compatibleTransports & peer->router->GetPublishedTransports (); auto directTransports = compatibleTransports & peer->router->GetPublishedTransports ();
peer->numAttempts = 0; peer->numAttempts = 0;
peer->priority.clear (); peer->priority.clear ();
bool isReal = peer->router->GetProfile ()->IsReal ();
std::shared_ptr<RouterProfile> profile; bool ssu2 = isReal ? (m_Rng () & 1) : false; // try NTCP2 if router is not confirmed real
if (peer->router->HasProfile ()) profile = peer->router->GetProfile (); // only if in memory
bool ssu2 = false; // NTCP2 by default
bool isReal = profile ? profile->IsReal () : true;
if (isReal)
{
ssu2 = m_Rng () & 1; // 1/2
if (ssu2 && !profile)
{
profile = peer->router->GetProfile (); // load profile if necessary
isReal = profile->IsReal ();
if (!isReal) ssu2 = false; // try NTCP2 if router is not confirmed real
}
}
const auto& priority = ssu2 ? ssu2Priority : ntcp2Priority; const auto& priority = ssu2 ? ssu2Priority : ntcp2Priority;
if (directTransports) if (directTransports)
{ {
@ -1195,7 +1182,7 @@ namespace transport
std::lock_guard<std::mutex> lock(m_TrustedRoutersMutex); std::lock_guard<std::mutex> lock(m_TrustedRoutersMutex);
m_TrustedRouters.clear(); m_TrustedRouters.clear();
for (const auto & ri : routers ) for (const auto & ri : routers )
m_TrustedRouters.insert(ri); m_TrustedRouters.push_back(ri);
} }
bool Transports::RoutesRestricted() const bool Transports::RoutesRestricted() const
@ -1236,8 +1223,9 @@ namespace transport
auto sz = m_TrustedRouters.size(); auto sz = m_TrustedRouters.size();
if (sz) if (sz)
{ {
if(sz == 1)
return i2p::data::netdb.FindRouter(m_TrustedRouters[0]);
auto it = m_TrustedRouters.begin(); auto it = m_TrustedRouters.begin();
if(sz > 1)
std::advance(it, m_Rng() % sz); std::advance(it, m_Rng() % sz);
return i2p::data::netdb.FindRouter(*it); return i2p::data::netdb.FindRouter(*it);
} }
@ -1245,23 +1233,13 @@ namespace transport
return nullptr; return nullptr;
} }
bool Transports::IsTrustedRouter (const i2p::data::IdentHash& ih) const
{
if (m_TrustedRouters.empty ()) return false;
std::lock_guard<std::mutex> l(m_TrustedRoutersMutex);
#if __cplusplus >= 202002L // C++20
if (m_TrustedRouters.contains (ih))
#else
if (m_TrustedRouters.count (ih) > 0)
#endif
return true;
return false;
}
bool Transports::IsRestrictedPeer(const i2p::data::IdentHash & ih) const bool Transports::IsRestrictedPeer(const i2p::data::IdentHash & ih) const
{ {
if (IsTrustedRouter (ih)) return true; {
std::lock_guard<std::mutex> l(m_TrustedRoutersMutex);
for (const auto & r : m_TrustedRouters )
if ( r == ih ) return true;
}
{ {
std::lock_guard<std::mutex> l(m_FamilyMutex); std::lock_guard<std::mutex> l(m_FamilyMutex);
auto ri = i2p::data::netdb.FindRouter(ih); auto ri = i2p::data::netdb.FindRouter(ih);

View file

@ -15,7 +15,6 @@
#include <condition_variable> #include <condition_variable>
#include <functional> #include <functional>
#include <unordered_map> #include <unordered_map>
#include <unordered_set>
#include <vector> #include <vector>
#include <queue> #include <queue>
#include <string> #include <string>
@ -179,8 +178,7 @@ namespace transport
/** restrict routes to use only these routers for first hops */ /** restrict routes to use only these routers for first hops */
void RestrictRoutesToRouters(const std::set<i2p::data::IdentHash>& routers); void RestrictRoutesToRouters(const std::set<i2p::data::IdentHash>& routers);
bool IsTrustedRouter (const i2p::data::IdentHash& ih) const; bool IsRestrictedPeer(const i2p::data::IdentHash & ident) const;
bool IsRestrictedPeer(const i2p::data::IdentHash& ih) const;
void PeerTest (bool ipv4 = true, bool ipv6 = true); void PeerTest (bool ipv4 = true, bool ipv6 = true);
@ -239,7 +237,7 @@ namespace transport
mutable std::mutex m_FamilyMutex; mutable std::mutex m_FamilyMutex;
/** which routers for first hop to trust */ /** which routers for first hop to trust */
std::unordered_set<i2p::data::IdentHash> m_TrustedRouters; std::vector<i2p::data::IdentHash> m_TrustedRouters;
mutable std::mutex m_TrustedRoutersMutex; mutable std::mutex m_TrustedRoutersMutex;
i2p::I2NPMessagesHandler m_LoopbackHandler; i2p::I2NPMessagesHandler m_LoopbackHandler;

View file

@ -351,13 +351,10 @@ namespace tunnel
{ {
it.second.first->SetState (eTunnelStateFailed); it.second.first->SetState (eTunnelStateFailed);
std::unique_lock<std::mutex> l(m_OutboundTunnelsMutex); std::unique_lock<std::mutex> l(m_OutboundTunnelsMutex);
if (m_OutboundTunnels.size () > 1) // don't fail last tunnel if (m_OutboundTunnels.size () > 1 || m_NumOutboundTunnels <= 1) // don't fail last tunnel
m_OutboundTunnels.erase (it.second.first); m_OutboundTunnels.erase (it.second.first);
else else
{
it.second.first->SetState (eTunnelStateTestFailed); it.second.first->SetState (eTunnelStateTestFailed);
CreateOutboundTunnel (); // create new tunnel immediately because last one failed
}
} }
else if (it.second.first->GetState () != eTunnelStateExpiring) else if (it.second.first->GetState () != eTunnelStateExpiring)
it.second.first->SetState (eTunnelStateTestFailed); it.second.first->SetState (eTunnelStateTestFailed);
@ -371,16 +368,13 @@ namespace tunnel
bool failed = false; bool failed = false;
{ {
std::unique_lock<std::mutex> l(m_InboundTunnelsMutex); std::unique_lock<std::mutex> l(m_InboundTunnelsMutex);
if (m_InboundTunnels.size () > 1) // don't fail last tunnel if (m_InboundTunnels.size () > 1 || m_NumInboundTunnels <= 1) // don't fail last tunnel
{ {
m_InboundTunnels.erase (it.second.second); m_InboundTunnels.erase (it.second.second);
failed = true; failed = true;
} }
else else
{
it.second.second->SetState (eTunnelStateTestFailed); it.second.second->SetState (eTunnelStateTestFailed);
CreateInboundTunnel (); // create new tunnel immediately because last one failed
}
} }
if (failed && m_LocalDestination) if (failed && m_LocalDestination)
m_LocalDestination->SetLeaseSetUpdated (true); m_LocalDestination->SetLeaseSetUpdated (true);

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -8,7 +8,6 @@
#include <cstdlib> #include <cstdlib>
#include <string> #include <string>
#include <array>
#include <unordered_set> #include <unordered_set>
#include <boost/asio.hpp> #include <boost/asio.hpp>
@ -124,8 +123,8 @@ const char *inet_ntop_xp(int af, const void *src, char *dst, socklen_t size)
#endif #endif
#endif #endif
#define address_pair_v4(a,b) std::pair{ boost::asio::ip::make_address (a).to_v4 ().to_uint (), boost::asio::ip::make_address(b).to_v4 ().to_uint () } #define address_pair_v4(a,b) { boost::asio::ip::make_address (a).to_v4 ().to_uint (), boost::asio::ip::make_address(b).to_v4 ().to_uint () }
#define address_pair_v6(a,b) std::pair{ boost::asio::ip::make_address (a).to_v6 ().to_bytes (), boost::asio::ip::make_address(b).to_v6 ().to_bytes () } #define address_pair_v6(a,b) { boost::asio::ip::make_address (a).to_v6 ().to_bytes (), boost::asio::ip::make_address(b).to_v6 ().to_bytes () }
namespace i2p namespace i2p
{ {
@ -648,8 +647,7 @@ namespace net
if (host.is_unspecified ()) return false; if (host.is_unspecified ()) return false;
if (host.is_v4()) if (host.is_v4())
{ {
static const std::array<std::pair<uint32_t, uint32_t>, 14> reservedIPv4Ranges static const std::vector< std::pair<uint32_t, uint32_t> > reservedIPv4Ranges {
{
address_pair_v4("0.0.0.0", "0.255.255.255"), address_pair_v4("0.0.0.0", "0.255.255.255"),
address_pair_v4("10.0.0.0", "10.255.255.255"), address_pair_v4("10.0.0.0", "10.255.255.255"),
address_pair_v4("100.64.0.0", "100.127.255.255"), address_pair_v4("100.64.0.0", "100.127.255.255"),
@ -674,8 +672,7 @@ namespace net
} }
if (host.is_v6()) if (host.is_v6())
{ {
static const std::array<std::pair<boost::asio::ip::address_v6::bytes_type, boost::asio::ip::address_v6::bytes_type>, 7> reservedIPv6Ranges static const std::vector< std::pair<boost::asio::ip::address_v6::bytes_type, boost::asio::ip::address_v6::bytes_type> > reservedIPv6Ranges {
{
address_pair_v6("64:ff9b::", "64:ff9b:ffff:ffff:ffff:ffff:ffff:ffff"), // NAT64 address_pair_v6("64:ff9b::", "64:ff9b:ffff:ffff:ffff:ffff:ffff:ffff"), // NAT64
address_pair_v6("2001:db8::", "2001:db8:ffff:ffff:ffff:ffff:ffff:ffff"), address_pair_v6("2001:db8::", "2001:db8:ffff:ffff:ffff:ffff:ffff:ffff"),
address_pair_v6("fc00::", "fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"), address_pair_v6("fc00::", "fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"),

View file

@ -444,11 +444,9 @@ namespace client
return addr->IsValid () ? addr : nullptr; return addr->IsValid () ? addr : nullptr;
} }
else else
#if __cplusplus >= 202002L // C++20 {
if (address.ends_with (".i2p")) pos = address.find (".i2p");
#else if (pos != std::string::npos)
if (address.find (".i2p") != std::string::npos)
#endif
{ {
if (!m_IsEnabled) return nullptr; if (!m_IsEnabled) return nullptr;
auto addr = FindAddress (address); auto addr = FindAddress (address);
@ -456,6 +454,7 @@ namespace client
LookupAddress (address); // TODO: LookupAddress (address); // TODO:
return addr; return addr;
} }
}
// if not .b32 we assume full base64 address // if not .b32 we assume full base64 address
i2p::data::IdentityEx dest; i2p::data::IdentityEx dest;
if (!dest.FromBase64 (address)) if (!dest.FromBase64 (address))
@ -567,35 +566,29 @@ namespace client
if (pos != std::string::npos) if (pos != std::string::npos)
{ {
std::string_view name = std::string_view(s).substr(0, pos++); std::string name = s.substr(0, pos++);
std::string_view addr = std::string_view(s).substr(pos); std::string addr = s.substr(pos);
size_t pos = addr.find('#'); size_t pos = addr.find('#');
if (pos != addr.npos) if (pos != std::string::npos)
addr = addr.substr(0, pos); // remove comments addr = addr.substr(0, pos); // remove comments
#if __cplusplus >= 202002L // C++20
if (name.ends_with (".b32.i2p")) pos = name.find(".b32.i2p");
#else if (pos != std::string::npos)
if (name.find(".b32.i2p") != name.npos)
#endif
{ {
LogPrint (eLogError, "Addressbook: Skipped adding of b32 address: ", name); LogPrint (eLogError, "Addressbook: Skipped adding of b32 address: ", name);
continue; continue;
} }
#if __cplusplus >= 202002L // C++20 pos = name.find(".i2p");
if (!name.ends_with (".i2p")) if (pos == std::string::npos)
#else
if (name.find(".i2p") == name.npos)
#endif
{ {
LogPrint (eLogError, "Addressbook: Malformed domain: ", name); LogPrint (eLogError, "Addressbook: Malformed domain: ", name);
continue; continue;
} }
auto ident = std::make_shared<i2p::data::IdentityEx> (); auto ident = std::make_shared<i2p::data::IdentityEx> ();
if (!ident->FromBase64(addr)) if (!ident->FromBase64(addr)) {
{
LogPrint (eLogError, "Addressbook: Malformed address ", addr, " for ", name); LogPrint (eLogError, "Addressbook: Malformed address ", addr, " for ", name);
incomplete = f.eof (); incomplete = f.eof ();
continue; continue;

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -16,24 +16,6 @@ namespace i2p
{ {
namespace client namespace client
{ {
void BOBI2PTunnelIncomingConnection::Established ()
{
if (m_IsQuiet)
StreamReceive ();
else
{
// send destination first like received from I2P
std::string dest = GetStream ()->GetRemoteIdentity ()->ToBase64 ();
dest += "\n";
if (dest.size() <= I2P_TUNNEL_CONNECTION_BUFFER_SIZE)
memcpy (GetStreamBuffer (), dest.c_str (), dest.size ());
else
memset (GetStreamBuffer (), 0, I2P_TUNNEL_CONNECTION_BUFFER_SIZE);
HandleStreamReceive (boost::system::error_code (), dest.size ());
}
Receive ();
}
BOBI2PInboundTunnel::BOBI2PInboundTunnel (const boost::asio::ip::tcp::endpoint& ep, std::shared_ptr<ClientDestination> localDestination): BOBI2PInboundTunnel::BOBI2PInboundTunnel (const boost::asio::ip::tcp::endpoint& ep, std::shared_ptr<ClientDestination> localDestination):
BOBI2PTunnel (localDestination), m_Acceptor (localDestination->GetService (), ep) BOBI2PTunnel (localDestination), m_Acceptor (localDestination->GetService (), ep)
{ {
@ -174,7 +156,7 @@ namespace client
{ {
if (stream) if (stream)
{ {
auto conn = std::make_shared<BOBI2PTunnelIncomingConnection> (this, stream, m_Endpoint, m_IsQuiet); auto conn = std::make_shared<I2PTunnelConnection> (this, stream, m_Endpoint, m_IsQuiet);
AddHandler (conn); AddHandler (conn);
conn->Connect (); conn->Connect ();
} }

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -71,23 +71,6 @@ namespace client
const char BOB_HELP_STATUS[] = "status <NICKNAME> - Display status of a nicknamed tunnel."; 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."; const char BOB_HELP_HELP [] = "help <COMMAND> - Get help on a command.";
class BOBI2PTunnelIncomingConnection: public I2PTunnelConnection
{
public:
BOBI2PTunnelIncomingConnection (I2PService * owner, std::shared_ptr<i2p::stream::Stream> stream,
const boost::asio::ip::tcp::endpoint& target, bool quiet):
I2PTunnelConnection (owner, stream, target), m_IsQuiet (quiet) {};
protected:
void Established () override;
private:
bool m_IsQuiet; // don't send destination
};
class BOBI2PTunnel: public I2PService class BOBI2PTunnel: public I2PService
{ {
public: public:

View file

@ -265,15 +265,11 @@ namespace client
} }
} }
bool ClientContext::LoadPrivateKeys (i2p::data::PrivateKeys& keys, std::string_view filename, bool ClientContext::LoadPrivateKeys (i2p::data::PrivateKeys& keys, const std::string& filename,
i2p::data::SigningKeyType sigType, i2p::data::CryptoKeyType cryptoType) i2p::data::SigningKeyType sigType, i2p::data::CryptoKeyType cryptoType)
{ {
#if __cplusplus >= 202002L // C++20 static const std::string transient("transient");
if (filename.starts_with ("transient"))
#else
std::string_view transient("transient");
if (!filename.compare (0, transient.length (), transient)) // starts with transient if (!filename.compare (0, transient.length (), transient)) // starts with transient
#endif
{ {
keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType, cryptoType, true); keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType, cryptoType, true);
LogPrint (eLogInfo, "Clients: New transient keys address ", m_AddressBook.ToAddress(keys.GetPublic ()->GetIdentHash ()), " created"); LogPrint (eLogInfo, "Clients: New transient keys address ", m_AddressBook.ToAddress(keys.GetPublic ()->GetIdentHash ()), " created");
@ -301,7 +297,7 @@ namespace client
} }
else else
{ {
LogPrint (eLogInfo, "Clients: Can't open file ", fullPath, " Creating new one with signature type ", sigType, " crypto type ", cryptoType); LogPrint (eLogCritical, "Clients: Can't open file ", fullPath, " Creating new one with signature type ", sigType, " crypto type ", cryptoType);
keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType, cryptoType, true); keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType, cryptoType, true);
std::ofstream f (fullPath, std::ofstream::binary | std::ofstream::out); std::ofstream f (fullPath, std::ofstream::binary | std::ofstream::out);
size_t len = keys.GetFullLen (); size_t len = keys.GetFullLen ();
@ -547,11 +543,7 @@ namespace client
{ {
for (auto& it: files) for (auto& it: files)
{ {
#if __cplusplus >= 202002L // C++20
if (!it.ends_with (".conf")) continue;
#else
if (it.substr(it.size() - 5) != ".conf") continue; // skip files which not ends with ".conf" if (it.substr(it.size() - 5) != ".conf") continue; // skip files which not ends with ".conf"
#endif
LogPrint(eLogDebug, "Clients: Tunnels extra config file: ", it); LogPrint(eLogDebug, "Clients: Tunnels extra config file: ", it);
ReadTunnels (it, numClientTunnels, numServerTunnels); ReadTunnels (it, numClientTunnels, numServerTunnels);
} }
@ -607,9 +599,7 @@ namespace client
options[I2CP_PARAM_OUTBOUND_NICKNAME] = name; options[I2CP_PARAM_OUTBOUND_NICKNAME] = name;
std::shared_ptr<ClientDestination> localDestination = nullptr; std::shared_ptr<ClientDestination> localDestination = nullptr;
if (keys == "shareddest") if (keys.length () > 0)
localDestination = m_SharedLocalDestination;
else if (keys.length () > 0)
{ {
auto it = destinations.find (keys); auto it = destinations.find (keys);
if (it != destinations.end ()) if (it != destinations.end ())
@ -768,10 +758,6 @@ namespace client
options[I2CP_PARAM_INBOUND_NICKNAME] = name; options[I2CP_PARAM_INBOUND_NICKNAME] = name;
std::shared_ptr<ClientDestination> localDestination = nullptr; std::shared_ptr<ClientDestination> localDestination = nullptr;
if (keys == "shareddest")
localDestination = m_SharedLocalDestination;
else
{
auto it = destinations.find (keys); auto it = destinations.find (keys);
if (it != destinations.end ()) if (it != destinations.end ())
{ {
@ -792,7 +778,6 @@ namespace client
else else
localDestination->SetPublic (true); localDestination->SetPublic (true);
} }
}
if (type == I2P_TUNNELS_SECTION_TYPE_UDPSERVER) if (type == I2P_TUNNELS_SECTION_TYPE_UDPSERVER)
{ {
// udp server tunnel // udp server tunnel
@ -886,7 +871,7 @@ namespace client
} }
else else
LogPrint (eLogError, "Clients: Unknown section type = ", type, " of ", name, " in ", tunConf); LogPrint (eLogWarning, "Clients: Unknown section type = ", type, " of ", name, " in ", tunConf);
} }
catch (std::exception& ex) catch (std::exception& ex)
{ {
@ -912,12 +897,7 @@ namespace client
i2p::config::GetOption("addressbook.enabled", httpAddresshelper); // addresshelper is not supported without address book i2p::config::GetOption("addressbook.enabled", httpAddresshelper); // addresshelper is not supported without address book
i2p::data::SigningKeyType sigType; i2p::config::GetOption("httpproxy.signaturetype", sigType); i2p::data::SigningKeyType sigType; i2p::config::GetOption("httpproxy.signaturetype", sigType);
LogPrint(eLogInfo, "Clients: Starting HTTP Proxy at ", httpProxyAddr, ":", httpProxyPort); LogPrint(eLogInfo, "Clients: Starting HTTP Proxy at ", httpProxyAddr, ":", httpProxyPort);
if (httpProxyKeys == "shareddest") if (httpProxyKeys.length () > 0)
{
localDestination = m_SharedLocalDestination;
localDestination->Acquire ();
}
else if (httpProxyKeys.length () > 0)
{ {
i2p::data::PrivateKeys keys; i2p::data::PrivateKeys keys;
if(LoadPrivateKeys (keys, httpProxyKeys, sigType)) if(LoadPrivateKeys (keys, httpProxyKeys, sigType))
@ -961,12 +941,7 @@ namespace client
uint16_t socksOutProxyPort; i2p::config::GetOption("socksproxy.outproxyport", socksOutProxyPort); uint16_t socksOutProxyPort; i2p::config::GetOption("socksproxy.outproxyport", socksOutProxyPort);
i2p::data::SigningKeyType sigType; i2p::config::GetOption("socksproxy.signaturetype", sigType); i2p::data::SigningKeyType sigType; i2p::config::GetOption("socksproxy.signaturetype", sigType);
LogPrint(eLogInfo, "Clients: Starting SOCKS Proxy at ", socksProxyAddr, ":", socksProxyPort); LogPrint(eLogInfo, "Clients: Starting SOCKS Proxy at ", socksProxyAddr, ":", socksProxyPort);
if (socksProxyKeys == "shareddest") if (httpProxyKeys == socksProxyKeys && m_HttpProxy)
{
localDestination = m_SharedLocalDestination;
localDestination->Acquire ();
}
else if (httpProxyKeys == socksProxyKeys && m_HttpProxy)
{ {
localDestination = m_HttpProxy->GetLocalDestination (); localDestination = m_HttpProxy->GetLocalDestination ();
localDestination->Acquire (); localDestination->Acquire ();

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -92,7 +92,7 @@ namespace client
const std::string & name, const std::map<std::string, std::string> * params = nullptr); const std::string & name, const std::map<std::string, std::string> * params = nullptr);
void DeleteLocalDestination (std::shared_ptr<ClientDestination> destination); void DeleteLocalDestination (std::shared_ptr<ClientDestination> destination);
std::shared_ptr<ClientDestination> FindLocalDestination (const i2p::data::IdentHash& destination) const; std::shared_ptr<ClientDestination> FindLocalDestination (const i2p::data::IdentHash& destination) const;
bool LoadPrivateKeys (i2p::data::PrivateKeys& keys, std::string_view filename, bool LoadPrivateKeys (i2p::data::PrivateKeys& keys, const std::string& filename,
i2p::data::SigningKeyType sigType = i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519, i2p::data::SigningKeyType sigType = i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519,
i2p::data::CryptoKeyType cryptoType = i2p::data::CRYPTO_KEY_TYPE_ELGAMAL); i2p::data::CryptoKeyType cryptoType = i2p::data::CRYPTO_KEY_TYPE_ELGAMAL);

View file

@ -29,15 +29,13 @@ namespace client
const std::map<std::string, std::string>& params): const std::map<std::string, std::string>& params):
LeaseSetDestination (service, isPublic, &params), LeaseSetDestination (service, isPublic, &params),
m_Owner (owner), m_Identity (identity), m_EncryptionKeyType (m_Identity->GetCryptoKeyType ()), m_Owner (owner), m_Identity (identity), m_EncryptionKeyType (m_Identity->GetCryptoKeyType ()),
m_IsCreatingLeaseSet (false), m_IsSameThread (isSameThread), m_IsCreatingLeaseSet (false), m_IsSameThread (isSameThread), m_LeaseSetCreationTimer (service)
m_LeaseSetCreationTimer (service), m_ReadinessCheckTimer (service)
{ {
} }
void I2CPDestination::Stop () void I2CPDestination::Stop ()
{ {
m_LeaseSetCreationTimer.cancel (); m_LeaseSetCreationTimer.cancel ();
m_ReadinessCheckTimer.cancel ();
LeaseSetDestination::Stop (); LeaseSetDestination::Stop ();
m_Owner = nullptr; m_Owner = nullptr;
} }
@ -79,12 +77,6 @@ namespace client
return keyType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD ? (bool)m_ECIESx25519Decryptor : m_EncryptionKeyType == keyType; return keyType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD ? (bool)m_ECIESx25519Decryptor : m_EncryptionKeyType == keyType;
} }
i2p::data::CryptoKeyType I2CPDestination::GetPreferredCryptoType () const
{
if (m_ECIESx25519Decryptor)
return i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD;
return i2p::data::CRYPTO_KEY_TYPE_ELGAMAL;
}
void I2CPDestination::HandleDataMessage (const uint8_t * buf, size_t len) void I2CPDestination::HandleDataMessage (const uint8_t * buf, size_t len)
{ {
@ -96,7 +88,7 @@ namespace client
void I2CPDestination::CreateNewLeaseSet (const std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> >& tunnels) void I2CPDestination::CreateNewLeaseSet (const std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> >& tunnels)
{ {
boost::asio::post (GetService (), std::bind (&I2CPDestination::PostCreateNewLeaseSet, GetSharedFromThis (), tunnels)); boost::asio::post (GetService (), std::bind (&I2CPDestination::PostCreateNewLeaseSet, this, tunnels));
} }
void I2CPDestination::PostCreateNewLeaseSet (std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> > tunnels) void I2CPDestination::PostCreateNewLeaseSet (std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> > tunnels)
@ -106,20 +98,6 @@ namespace client
LogPrint (eLogInfo, "I2CP: LeaseSet is being created"); LogPrint (eLogInfo, "I2CP: LeaseSet is being created");
return; return;
} }
m_ReadinessCheckTimer.cancel ();
auto pool = GetTunnelPool ();
if (!pool || pool->GetOutboundTunnels ().empty ())
{
// try again later
m_ReadinessCheckTimer.expires_from_now (boost::posix_time::seconds(I2CP_DESTINATION_READINESS_CHECK_INTERVAL));
m_ReadinessCheckTimer.async_wait(
[s=GetSharedFromThis (), tunnels=std::move(tunnels)](const boost::system::error_code& ecode)
{
if (ecode != boost::asio::error::operation_aborted)
s->PostCreateNewLeaseSet (tunnels);
});
return;
}
uint8_t priv[256] = {0}; uint8_t priv[256] = {0};
i2p::data::LocalLeaseSet ls (m_Identity, priv, tunnels); // we don't care about encryption key, we need leases only i2p::data::LocalLeaseSet ls (m_Identity, priv, tunnels); // we don't care about encryption key, we need leases only
m_LeaseSetExpirationTime = ls.GetExpirationTime (); m_LeaseSetExpirationTime = ls.GetExpirationTime ();
@ -533,7 +511,7 @@ namespace client
if (sendBuf) if (sendBuf)
{ {
if (m_SendQueue.GetSize () < I2CP_MAX_SEND_QUEUE_SIZE) if (m_SendQueue.GetSize () < I2CP_MAX_SEND_QUEUE_SIZE)
m_SendQueue.Add (std::move(sendBuf)); m_SendQueue.Add (sendBuf);
else else
{ {
LogPrint (eLogWarning, "I2CP: Send queue size exceeds ", I2CP_MAX_SEND_QUEUE_SIZE); LogPrint (eLogWarning, "I2CP: Send queue size exceeds ", I2CP_MAX_SEND_QUEUE_SIZE);
@ -577,7 +555,7 @@ namespace client
m_IsSending = false; m_IsSending = false;
} }
std::string_view I2CPSession::ExtractString (const uint8_t * buf, size_t len) const std::string_view I2CPSession::ExtractString (const uint8_t * buf, size_t len)
{ {
uint8_t l = buf[0]; uint8_t l = buf[0];
if (l > len) l = len; if (l > len) l = len;
@ -594,7 +572,7 @@ namespace client
return l + 1; return l + 1;
} }
void I2CPSession::ExtractMapping (const uint8_t * buf, size_t len, std::map<std::string, std::string>& mapping) const void I2CPSession::ExtractMapping (const uint8_t * buf, size_t len, std::map<std::string, std::string>& mapping)
// TODO: move to Base.cpp // TODO: move to Base.cpp
{ {
size_t offset = 0; size_t offset = 0;
@ -1079,7 +1057,7 @@ namespace client
if (sendBuf) if (sendBuf)
{ {
if (m_SendQueue.GetSize () < I2CP_MAX_SEND_QUEUE_SIZE) if (m_SendQueue.GetSize () < I2CP_MAX_SEND_QUEUE_SIZE)
m_SendQueue.Add (std::move(sendBuf)); m_SendQueue.Add (sendBuf);
else else
{ {
LogPrint (eLogWarning, "I2CP: Send queue size exceeds ", I2CP_MAX_SEND_QUEUE_SIZE); LogPrint (eLogWarning, "I2CP: Send queue size exceeds ", I2CP_MAX_SEND_QUEUE_SIZE);

View file

@ -21,7 +21,6 @@
#include "util.h" #include "util.h"
#include "Destination.h" #include "Destination.h"
#include "Streaming.h" #include "Streaming.h"
#include "CryptoKey.h"
namespace i2p namespace i2p
{ {
@ -32,7 +31,6 @@ namespace client
const size_t I2CP_MAX_MESSAGE_LENGTH = 65535; const size_t I2CP_MAX_MESSAGE_LENGTH = 65535;
const size_t I2CP_MAX_SEND_QUEUE_SIZE = 1024*1024; // in bytes, 1M const size_t I2CP_MAX_SEND_QUEUE_SIZE = 1024*1024; // in bytes, 1M
const int I2CP_LEASESET_CREATION_TIMEOUT = 10; // in seconds const int I2CP_LEASESET_CREATION_TIMEOUT = 10; // in seconds
const int I2CP_DESTINATION_READINESS_CHECK_INTERVAL = 5; // in seconds
const int I2CP_SESSION_ACK_REQUEST_INTERVAL = 12100; // in milliseconds const int I2CP_SESSION_ACK_REQUEST_INTERVAL = 12100; // in milliseconds
const size_t I2CP_HEADER_LENGTH_OFFSET = 0; const size_t I2CP_HEADER_LENGTH_OFFSET = 0;
@ -90,7 +88,7 @@ namespace client
const std::map<std::string, std::string>& params); const std::map<std::string, std::string>& params);
~I2CPDestination () {}; ~I2CPDestination () {};
void Stop () override; void Stop ();
void SetEncryptionPrivateKey (const uint8_t * key); void SetEncryptionPrivateKey (const uint8_t * key);
void SetEncryptionType (i2p::data::CryptoKeyType keyType) { m_EncryptionKeyType = keyType; }; void SetEncryptionType (i2p::data::CryptoKeyType keyType) { m_EncryptionKeyType = keyType; };
@ -101,24 +99,17 @@ namespace client
bool SendMsg (const uint8_t * payload, size_t len, std::shared_ptr<i2p::garlic::GarlicRoutingSession> remoteSession, uint32_t nonce); bool SendMsg (const uint8_t * payload, size_t len, std::shared_ptr<i2p::garlic::GarlicRoutingSession> remoteSession, uint32_t nonce);
// implements LocalDestination // implements LocalDestination
bool Decrypt (const uint8_t * encrypted, uint8_t * data, i2p::data::CryptoKeyType preferredCrypto) const override; bool Decrypt (const uint8_t * encrypted, uint8_t * data, i2p::data::CryptoKeyType preferredCrypto) const;
bool SupportsEncryptionType (i2p::data::CryptoKeyType keyType) const override; bool SupportsEncryptionType (i2p::data::CryptoKeyType keyType) const;
const uint8_t * GetEncryptionPublicKey (i2p::data::CryptoKeyType keyType) const override; // for 4 only const uint8_t * GetEncryptionPublicKey (i2p::data::CryptoKeyType keyType) const; // for 4 only
std::shared_ptr<const i2p::data::IdentityEx> GetIdentity () const override { return m_Identity; }; std::shared_ptr<const i2p::data::IdentityEx> GetIdentity () const { return m_Identity; };
protected: protected:
// GarlicDestination void CleanupDestination ();
i2p::data::CryptoKeyType GetRatchetsHighestCryptoType () const override
{
return m_ECIESx25519Decryptor ? i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD : 0;
}
// LeaseSetDestination
void CleanupDestination () override;
i2p::data::CryptoKeyType GetPreferredCryptoType () const override;
// I2CP // I2CP
void HandleDataMessage (const uint8_t * buf, size_t len) override; void HandleDataMessage (const uint8_t * buf, size_t len);
void CreateNewLeaseSet (const std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> >& tunnels) override; void CreateNewLeaseSet (const std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> >& tunnels);
private: private:
@ -140,7 +131,7 @@ namespace client
uint8_t m_ECIESx25519PrivateKey[32]; uint8_t m_ECIESx25519PrivateKey[32];
uint64_t m_LeaseSetExpirationTime; uint64_t m_LeaseSetExpirationTime;
bool m_IsCreatingLeaseSet, m_IsSameThread; bool m_IsCreatingLeaseSet, m_IsSameThread;
boost::asio::deadline_timer m_LeaseSetCreationTimer, m_ReadinessCheckTimer; boost::asio::deadline_timer m_LeaseSetCreationTimer;
i2p::util::MemoryPoolMt<I2NPMessageBuffer<I2NP_MAX_MESSAGE_SIZE> > m_I2NPMsgsPool; i2p::util::MemoryPoolMt<I2NPMessageBuffer<I2NP_MAX_MESSAGE_SIZE> > m_I2NPMsgsPool;
}; };
@ -202,9 +193,9 @@ namespace client
void HandleI2CPMessageSent (const boost::system::error_code& ecode, std::size_t bytes_transferred); void HandleI2CPMessageSent (const boost::system::error_code& ecode, std::size_t bytes_transferred);
std::string_view ExtractString (const uint8_t * buf, size_t len) const; std::string_view ExtractString (const uint8_t * buf, size_t len);
size_t PutString (uint8_t * buf, size_t len, std::string_view str); size_t PutString (uint8_t * buf, size_t len, std::string_view str);
void ExtractMapping (const uint8_t * buf, size_t len, std::map<std::string, std::string>& mapping) const; void ExtractMapping (const uint8_t * buf, size_t len, std::map<std::string, std::string>& mapping);
void SendSessionStatusMessage (I2CPSessionStatus status); void SendSessionStatusMessage (I2CPSessionStatus status);
void SendHostReplyMessage (uint32_t requestID, std::shared_ptr<const i2p::data::IdentityEx> identity); void SendHostReplyMessage (uint32_t requestID, std::shared_ptr<const i2p::data::IdentityEx> identity);

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -32,7 +32,8 @@ namespace client
I2PTunnelConnection::I2PTunnelConnection (I2PService * owner, std::shared_ptr<boost::asio::ip::tcp::socket> socket, I2PTunnelConnection::I2PTunnelConnection (I2PService * owner, std::shared_ptr<boost::asio::ip::tcp::socket> socket,
std::shared_ptr<const i2p::data::LeaseSet> leaseSet, uint16_t port): std::shared_ptr<const i2p::data::LeaseSet> leaseSet, uint16_t port):
I2PServiceHandler(owner), m_Socket (socket), m_RemoteEndpoint (socket->remote_endpoint ()) I2PServiceHandler(owner), m_Socket (socket), m_RemoteEndpoint (socket->remote_endpoint ()),
m_IsQuiet (true)
{ {
m_Stream = GetOwner()->GetLocalDestination ()->CreateStream (leaseSet, port); m_Stream = GetOwner()->GetLocalDestination ()->CreateStream (leaseSet, port);
} }
@ -40,13 +41,14 @@ namespace client
I2PTunnelConnection::I2PTunnelConnection (I2PService * owner, I2PTunnelConnection::I2PTunnelConnection (I2PService * owner,
std::shared_ptr<boost::asio::ip::tcp::socket> socket, std::shared_ptr<i2p::stream::Stream> stream): std::shared_ptr<boost::asio::ip::tcp::socket> socket, std::shared_ptr<i2p::stream::Stream> stream):
I2PServiceHandler(owner), m_Socket (socket), m_Stream (stream), I2PServiceHandler(owner), m_Socket (socket), m_Stream (stream),
m_RemoteEndpoint (socket->remote_endpoint ()) m_RemoteEndpoint (socket->remote_endpoint ()), m_IsQuiet (true)
{ {
} }
I2PTunnelConnection::I2PTunnelConnection (I2PService * owner, std::shared_ptr<i2p::stream::Stream> stream, I2PTunnelConnection::I2PTunnelConnection (I2PService * owner, std::shared_ptr<i2p::stream::Stream> stream,
const boost::asio::ip::tcp::endpoint& target,std::shared_ptr<boost::asio::ssl::context> sslCtx): const boost::asio::ip::tcp::endpoint& target, bool quiet,
I2PServiceHandler(owner), m_Stream (stream), m_RemoteEndpoint (target) std::shared_ptr<boost::asio::ssl::context> sslCtx):
I2PServiceHandler(owner), m_Stream (stream), m_RemoteEndpoint (target), m_IsQuiet (quiet)
{ {
m_Socket = std::make_shared<boost::asio::ip::tcp::socket> (owner->GetService ()); m_Socket = std::make_shared<boost::asio::ip::tcp::socket> (owner->GetService ());
if (sslCtx) if (sslCtx)
@ -290,7 +292,18 @@ namespace client
void I2PTunnelConnection::Established () void I2PTunnelConnection::Established ()
{ {
if (m_IsQuiet)
StreamReceive (); StreamReceive ();
else
{
// send destination first like received from I2P
std::string dest = m_Stream->GetRemoteIdentity ()->ToBase64 ();
dest += "\n";
if(sizeof(m_StreamBuffer) >= dest.size()) {
memcpy (m_StreamBuffer, dest.c_str (), dest.size ());
}
HandleStreamReceive (boost::system::error_code (), dest.size ());
}
Receive (); Receive ();
} }
@ -364,7 +377,7 @@ namespace client
I2PServerTunnelConnectionHTTP::I2PServerTunnelConnectionHTTP (I2PService * owner, std::shared_ptr<i2p::stream::Stream> stream, I2PServerTunnelConnectionHTTP::I2PServerTunnelConnectionHTTP (I2PService * owner, std::shared_ptr<i2p::stream::Stream> stream,
const boost::asio::ip::tcp::endpoint& target, const std::string& host, const std::string& XI2P, const boost::asio::ip::tcp::endpoint& target, const std::string& host, const std::string& XI2P,
std::shared_ptr<boost::asio::ssl::context> sslCtx): std::shared_ptr<boost::asio::ssl::context> sslCtx):
I2PTunnelConnection (owner, stream, target, sslCtx), m_Host (host), m_XI2P (XI2P), I2PTunnelConnection (owner, stream, target, true, sslCtx), m_Host (host), m_XI2P (XI2P),
m_HeaderSent (false), m_ResponseHeaderSent (false) m_HeaderSent (false), m_ResponseHeaderSent (false)
{ {
if (sslCtx) if (sslCtx)
@ -391,7 +404,7 @@ namespace client
else else
{ {
// strip up some headers // strip up some headers
static const std::array<std::string_view, 2> excluded // list of excluded headers static const std::vector<std::string> excluded // list of excluded headers
{ {
"Keep-Alive:", "X-I2P" "Keep-Alive:", "X-I2P"
}; };
@ -474,7 +487,7 @@ namespace client
if (line == "\r") endOfHeader = true; if (line == "\r") endOfHeader = true;
else else
{ {
static const std::array<std::string_view, 5> excluded // list of excluded headers static const std::vector<std::string> excluded // list of excluded headers
{ {
"Server:", "Date:", "X-Runtime:", "X-Powered-By:", "Proxy" "Server:", "Date:", "X-Runtime:", "X-Powered-By:", "Proxy"
}; };
@ -515,7 +528,7 @@ namespace client
I2PTunnelConnectionIRC::I2PTunnelConnectionIRC (I2PService * owner, std::shared_ptr<i2p::stream::Stream> stream, I2PTunnelConnectionIRC::I2PTunnelConnectionIRC (I2PService * owner, std::shared_ptr<i2p::stream::Stream> stream,
const boost::asio::ip::tcp::endpoint& target, const std::string& webircpass, const boost::asio::ip::tcp::endpoint& target, const std::string& webircpass,
std::shared_ptr<boost::asio::ssl::context> sslCtx): std::shared_ptr<boost::asio::ssl::context> sslCtx):
I2PTunnelConnection (owner, stream, target, sslCtx), m_From (stream->GetRemoteIdentity ()), I2PTunnelConnection (owner, stream, target, true, sslCtx), m_From (stream->GetRemoteIdentity ()),
m_NeedsWebIrc (webircpass.length() ? true : false), m_WebircPass (webircpass) m_NeedsWebIrc (webircpass.length() ? true : false), m_WebircPass (webircpass)
{ {
} }
@ -844,7 +857,7 @@ namespace client
std::shared_ptr<I2PTunnelConnection> I2PServerTunnel::CreateI2PConnection (std::shared_ptr<i2p::stream::Stream> stream) std::shared_ptr<I2PTunnelConnection> I2PServerTunnel::CreateI2PConnection (std::shared_ptr<i2p::stream::Stream> stream)
{ {
return std::make_shared<I2PTunnelConnection> (this, stream, GetEndpoint (), m_SSLCtx); return std::make_shared<I2PTunnelConnection> (this, stream, GetEndpoint (), true, m_SSLCtx);
} }

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -27,7 +27,7 @@ namespace i2p
{ {
namespace client namespace client
{ {
const size_t I2P_TUNNEL_CONNECTION_BUFFER_SIZE = 16384; const size_t I2P_TUNNEL_CONNECTION_BUFFER_SIZE = 65536;
const int I2P_TUNNEL_CONNECTION_MAX_IDLE = 3600; // in seconds const int I2P_TUNNEL_CONNECTION_MAX_IDLE = 3600; // in seconds
const int I2P_TUNNEL_DESTINATION_REQUEST_TIMEOUT = 10; // in seconds const int I2P_TUNNEL_DESTINATION_REQUEST_TIMEOUT = 10; // in seconds
// for HTTP tunnels // for HTTP tunnels
@ -45,7 +45,7 @@ namespace client
I2PTunnelConnection (I2PService * owner, std::shared_ptr<boost::asio::ip::tcp::socket> socket, I2PTunnelConnection (I2PService * owner, std::shared_ptr<boost::asio::ip::tcp::socket> socket,
std::shared_ptr<i2p::stream::Stream> stream); // to I2P using simplified API std::shared_ptr<i2p::stream::Stream> stream); // to I2P using simplified API
I2PTunnelConnection (I2PService * owner, std::shared_ptr<i2p::stream::Stream> stream, I2PTunnelConnection (I2PService * owner, std::shared_ptr<i2p::stream::Stream> stream,
const boost::asio::ip::tcp::endpoint& target, const boost::asio::ip::tcp::endpoint& target, bool quiet = true,
std::shared_ptr<boost::asio::ssl::context> sslCtx = nullptr); // from I2P std::shared_ptr<boost::asio::ssl::context> sslCtx = nullptr); // from I2P
~I2PTunnelConnection (); ~I2PTunnelConnection ();
void I2PConnect (const uint8_t * msg = nullptr, size_t len = 0); void I2PConnect (const uint8_t * msg = nullptr, size_t len = 0);
@ -54,26 +54,24 @@ namespace client
protected: protected:
virtual void Established ();
void Terminate (); void Terminate ();
void Receive (); void Receive ();
void StreamReceive (); void StreamReceive ();
void HandleStreamReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred);
virtual void Write (const uint8_t * buf, size_t len); // can be overloaded virtual void Write (const uint8_t * buf, size_t len); // can be overloaded
virtual void WriteToStream (const uint8_t * buf, size_t len); // can be overloaded virtual void WriteToStream (const uint8_t * buf, size_t len); // can be overloaded
std::shared_ptr<boost::asio::ip::tcp::socket> GetSocket () const { return m_Socket; }; std::shared_ptr<boost::asio::ip::tcp::socket> GetSocket () const { return m_Socket; };
std::shared_ptr<i2p::stream::Stream> GetStream () const { return m_Stream; };
std::shared_ptr<boost::asio::ssl::stream<boost::asio::ip::tcp::socket&> > GetSSL () const { return m_SSL; }; std::shared_ptr<boost::asio::ssl::stream<boost::asio::ip::tcp::socket&> > GetSSL () const { return m_SSL; };
uint8_t * GetStreamBuffer () { return m_StreamBuffer; };
private: private:
void HandleConnect (const boost::system::error_code& ecode); void HandleConnect (const boost::system::error_code& ecode);
void HandleHandshake (const boost::system::error_code& ecode); void HandleHandshake (const boost::system::error_code& ecode);
void Established ();
void HandleReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred); void HandleReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred);
void HandleWrite (const boost::system::error_code& ecode); void HandleWrite (const boost::system::error_code& ecode);
void HandleStreamReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred);
private: private:
@ -82,6 +80,7 @@ namespace client
std::shared_ptr<boost::asio::ssl::stream<boost::asio::ip::tcp::socket&> > m_SSL; std::shared_ptr<boost::asio::ssl::stream<boost::asio::ip::tcp::socket&> > m_SSL;
std::shared_ptr<i2p::stream::Stream> m_Stream; std::shared_ptr<i2p::stream::Stream> m_Stream;
boost::asio::ip::tcp::endpoint m_RemoteEndpoint; boost::asio::ip::tcp::endpoint m_RemoteEndpoint;
bool m_IsQuiet; // don't send destination
}; };
class I2PClientTunnelConnectionHTTP: public I2PTunnelConnection class I2PClientTunnelConnectionHTTP: public I2PTunnelConnection
@ -95,7 +94,7 @@ namespace client
protected: protected:
void Write (const uint8_t * buf, size_t len) override; void Write (const uint8_t * buf, size_t len);
private: private:
@ -113,8 +112,8 @@ namespace client
protected: protected:
void Write (const uint8_t * buf, size_t len) override; void Write (const uint8_t * buf, size_t len);
void WriteToStream (const uint8_t * buf, size_t len) override; void WriteToStream (const uint8_t * buf, size_t len);
private: private:
@ -133,7 +132,7 @@ namespace client
protected: protected:
void Write (const uint8_t * buf, size_t len) override; void Write (const uint8_t * buf, size_t len);
private: private:

View file

@ -415,7 +415,7 @@ namespace client
{ {
session->UDPEndpoint = forward; session->UDPEndpoint = forward;
auto dest = session->GetLocalDestination ()->CreateDatagramDestination (); auto dest = session->GetLocalDestination ()->CreateDatagramDestination ();
auto port = forward ? std::stoi(params[SAM_PARAM_PORT]) : 0; auto port = std::stoi(params[SAM_PARAM_PORT]);
if (type == eSAMSessionTypeDatagram) if (type == eSAMSessionTypeDatagram)
dest->SetReceiver (std::bind (&SAMSocket::HandleI2PDatagramReceive, shared_from_this (), dest->SetReceiver (std::bind (&SAMSocket::HandleI2PDatagramReceive, shared_from_this (),
std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5), std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5),
@ -470,11 +470,15 @@ namespace client
auto session = m_Owner.FindSession(m_ID); auto session = m_Owner.FindSession(m_ID);
if (session) if (session)
{ {
std::string priv = session->GetLocalDestination ()->GetPrivateKeys ().ToBase64 (); uint8_t buf[1024];
char priv[1024];
size_t l = session->GetLocalDestination ()->GetPrivateKeys ().ToBuffer (buf, 1024);
size_t l1 = i2p::data::ByteStreamToBase64 (buf, l, priv, 1024);
priv[l1] = 0;
#ifdef _MSC_VER #ifdef _MSC_VER
size_t l2 = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_SESSION_CREATE_REPLY_OK, priv.c_str ()); size_t l2 = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_SESSION_CREATE_REPLY_OK, priv);
#else #else
size_t l2 = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_SESSION_CREATE_REPLY_OK, priv.c_str ()); size_t l2 = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_SESSION_CREATE_REPLY_OK, priv);
#endif #endif
SendMessageReply (m_Buffer, l2, false); SendMessageReply (m_Buffer, l2, false);
} }
@ -1112,19 +1116,18 @@ namespace client
} }
} }
if (!m_IsSilent) if (!m_IsSilent)
{
if (m_SocketType != eSAMSocketTypeTerminated)
{ {
// get remote peer address // get remote peer address
auto ident = std::make_shared<std::string>(stream->GetRemoteIdentity()->ToBase64 ()); // we need to keep it until sent auto ident_ptr = stream->GetRemoteIdentity();
ident->push_back ('\n'); const size_t ident_len = ident_ptr->GetFullLen();
// send remote peer address back to client like received from stream uint8_t* ident = new uint8_t[ident_len];
boost::asio::async_write (m_Socket, boost::asio::buffer (ident->data (), ident->size ()), boost::asio::transfer_all(),
[ident, s = shared_from_this ()](const boost::system::error_code& ecode, size_t bytes_transferred) // send remote peer address as base64
{ const size_t l = ident_ptr->ToBuffer (ident, ident_len);
s->HandleWriteI2PData (ecode, bytes_transferred); const size_t l1 = i2p::data::ByteStreamToBase64 (ident, l, (char *)m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE);
}); delete[] ident;
} m_StreamBuffer[l1] = '\n';
HandleI2PReceive (boost::system::error_code (), l1 +1); // we send identity like it has been received from stream
} }
else else
I2PReceive (); I2PReceive ();