Compare commits

..

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

169 changed files with 5478 additions and 10237 deletions

View file

@ -128,13 +128,8 @@ jobs:
cache: true
update: true
- name: Clone MinGW packages repository and revert boost to 1.85.0
run: |
git clone https://github.com/msys2/MINGW-packages
cd MINGW-packages
git checkout 4cbb366edf2f268ac3146174b40ce38604646fc5 mingw-w64-boost
cd mingw-w64-boost
sed -i 's/boostorg.jfrog.io\/artifactory\/main/archives.boost.io/' PKGBUILD
- name: Clone MinGW packages repository
run: git clone https://github.com/msys2/MINGW-packages
# headers
- name: Get headers package version
@ -210,30 +205,26 @@ jobs:
run: |
cd MINGW-packages/mingw-w64-openssl
gpg --recv-keys D894E2CE8B3D79F5
gpg --recv-keys 216094DFD0CB81EF
MINGW_ARCH=mingw32 makepkg-mingw -sCLf --noconfirm --nocheck
- name: Install openssl package
run: pacman --noconfirm -U MINGW-packages/mingw-w64-openssl/mingw-w64-i686-*-any.pkg.tar.zst
# Boost
#- name: Get boost package version
# id: version-boost
# run: |
# echo "version=$(pacman -Si mingw-w64-i686-boost | grep -Po '^Version\s*: \K.+')" >> $GITHUB_OUTPUT
- name: Get boost package version
id: version-boost
run: |
echo "version=$(pacman -Si mingw-w64-i686-boost | grep -Po '^Version\s*: \K.+')" >> $GITHUB_OUTPUT
- name: Cache boost package
uses: actions/cache@v4
id: cache-boost
with:
path: MINGW-packages/mingw-w64-boost/*.zst
key: winxp-boost-1.85.0+crt-${{ steps.version-headers.outputs.version }}+ossl-${{ steps.version-openssl.outputs.version }}
# Rebuild package if packages above has changed
key: winxp-winpthreads-${{ steps.version-boost.outputs.version }}
- name: Build WinXP-capable boost package
if: steps.cache-boost.outputs.cache-hit != 'true'
run: |
cd MINGW-packages/mingw-w64-boost
MINGW_ARCH=mingw32 makepkg-mingw -sCLf --noconfirm --nocheck
- name: Remove boost packages
run: pacman --noconfirm -R mingw-w64-i686-boost mingw-w64-i686-boost-libs
- name: Install boost package
run: pacman --noconfirm -U MINGW-packages/mingw-w64-boost/mingw-w64-i686-*-any.pkg.tar.zst

View file

@ -108,7 +108,7 @@ jobs:
uses: Noelware/docker-manifest-action@master
with:
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
- name: Create and push latest manifest image to GHCR
@ -116,7 +116,7 @@ jobs:
uses: Noelware/docker-manifest-action@master
with:
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
- name: Store release version to env
@ -128,7 +128,7 @@ jobs:
uses: Noelware/docker-manifest-action@master
with:
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
- name: Create and push release manifest to GHCR
@ -136,5 +136,5 @@ jobs:
uses: Noelware/docker-manifest-action@master
with:
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

130
ChangeLog
View file

@ -1,136 +1,6 @@
# for this file format description,
# see https://github.com/olivierlacan/keep-a-changelog
## [2.57.0] - 2025-06-02
### Added
- Local domain sockets for I2PControl
- "keys=shareddest" tunnel param to run on shared local destination
- HTTP and SOCKS proxy through BOB
- Localization to Hebrew and Hindi
- NTCP2 probing resistance
- Support SAM v1 datagram sessions without port
- OpenIndiana support
### Changed
- Don't request LeaseSet until I2CP destination is ready
- Keep receiving new data from I2PTunnel/SAM socket while previous is being sent to stream
- Insert phony record to inbound tunnel build message with real x25519 ephemeral key
- Set min peer test version to 0.9.62
- Increase I2NP message expiration timeout in SSU2
- Cleanup ECIESx25519 new session reply keys on Alice side
- Reduced router profile persist interval to 22 minutes
- SSU2 max padding size to 32 bytes
- Send SSU2 path challenge of 8 bytes. Add datetime and address blocks
- Don't delete trusted routers from netdb
- Disable loss-control in streaming
- Reseeds list
### Fixed
- Crash after SAM stream disconnect
- FORWARD session host handling in SAM
- x86 build for Haiku
- SSU2 session's remote endpoint after receiving path response
## [2.56.0] - 2025-02-11
### Added
- Config params for shared local destination
- AddressBook full addresses cache
- Decline transit tunnel to duplicated router
- Recreate tunnels in random order
### Changed
- Exclude disk operations from SSU2 and NTCP2 threads
- Set minimal version for peer test to 0.9.62
- Send ack requested flag after second SSU2 resend attempt
- Shorter ECIESx25519 ack request interval for datagram and I2CP sessions
- Don't change datagram routing path too often if unidirectional data stream
- Reduce LeaseSet and local RouterInfo publishing confirmation intervals
- Don't delete buffer of connected routers or if an update received
- Smaller RouterInfo request timeout if sent directly
- Persist local RouterInfo in separate thread
- Don't recalculate and process ranges for every SSU2 Ack block
- Reseeds list
### Fixed
- Termination deadlock if SAM session is active
- Race condition at tunnel endpoint
- Inbound tunnel build encryption
## [2.55.0] - 2024-12-30
### Added
- Support boost 1.87
- "i2p.streaming.maxConcurrentStreams" tunnel's param to limit number of simultaneous streams
- Separate thread for tunnel build requests
- Show next peer and connectivity on "Transit tunnels" page
- Tunnel name for local destination thread
- Throttle incoming ECIESx25519 sessions
- Send tunnel data to transport session directly if possible
- Publish 'R' cap for yggdrasil-only routers, and 'U' cap for routers through proxy
- Random tunnel rejection when medium congestion
- Save unreachable router's endpoint to use it next time without introducers
- Recognize symmetric NAT from peer test message 7
- Resend HolePunch and RelayResponse messages
### Changed
- Removed own implementation of AESNI and always use one from openssl
- Renamed main thread to i2pd-daemon
- Set i2p.streaming.profile=2 for shared local destination
- Reduced LeaseSet and RouterInfo lookup timeouts
- Cleanup ECIES sessions and tags more often
- Check LeaseSet expiration time
- Handle NTCP2 session handshakes in separate thread
- Limit last decline time by 1.5 hours in router's profile
- Don't handle RelayRequest and RelayIntro with same nonce twice
- Increased hole punch expiration interval
- Send peer test message 6 with delay if message 4 was received before message 5
- Pre-calculate more x25519 keys for transports in runtime
- Don't request LeaseSet for incoming stream
- Terminate incoming stream right away if no remote LeaseSet
- Handle choked, new RTO and window size calculation and resetting algorithm for streams
### Fixed
- Empty string in addressbook subscriptions
- ECIESx25519 sessions without destination
- Missing RouterInfo buffer in NetDb
- Invalid I2PControl certificate
- Routers disappear from NetDb when offline
- Peer test message 6 sent to unknown endpoint
- Race condition with LeaseSet update
- Excessive CPU usage by streams
- Crash on shutdown
## [2.54.0] - 2024-10-06
### Added
- Maintain recently connected routers list to avoid false-positive peer test
- Limited connectivity mode(through proxy)
- "i2p.streaming.profile" tunnel's param to let tunnel select also low-bandwidth routers
- Limit stream's inbound speed
- Periodic ack requests in ratchets session
- Set congestion cap G immediately if through proxy
- Show tunnel's routers bandwidth caps in web console
- Handle immediate ack requested flag in SSU2 data packets
- Resend and ack peer test and relay messages
- "senduseragent" HTTP proxy's param to pass through user's User-Agent
### Changed
- Exclude 'N' routers from high-bandwidth routers for client tunnels
- C++11 support has been dropped, the minimal requirement is C++17 now, C++20 for some compilers
- Removed dependency from boost::date_time and boost::filesystem
- Set default i2cp.leaseSetEncType to 0,4 and to 4 for server tunnels
- Handle i2cp.inboundlimit and i2cp.outboundlimit params in I2CP
- Publish LeaseSet with new timestamp update if tunnel was replaced in the same second
- Increase max number of generated tags to 800 per tagset
- Routing path expiration by time instead num attempts
- Save timestamp from epoch instead local time to profiles
- Update introducer's iTag if session to introducer was replaced to new one
- RTT, window size and number of NACKs calculation for streaming
- Don't select same peer for tunnel too often
- Use WinApi for data path UTF-8 conversion for Windows
### Fixed
- Jump link crash if address book is disabled
- Race condition if connect through an introducer
- "Date" header in I2PControl response
- Incomplete response from web console
- AEAD verification with LibreSSL
- Number of generated tags and new keys for follow-on tagsets
- Expired leases in LeaseSet
- Attempts to send HolePunch to 0.0.0.0
- Incorrect options size in quick ack streaming packet
- Low bandwidth router appeared as first peer in high-bandwidth client tunnel
## [2.53.1] - 2024-07-29
### Changed
- I2CP performance improvement

View file

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

View file

@ -29,6 +29,7 @@ DAEMON_SRC_DIR := daemon
# import source files lists
include filelist.mk
USE_AESNI := $(or $(USE_AESNI),yes)
USE_STATIC := $(or $(USE_STATIC),no)
USE_UPNP := $(or $(USE_UPNP),no)
DEBUG := $(or $(DEBUG),yes)
@ -69,9 +70,6 @@ else ifneq (, $(findstring freebsd, $(SYS))$(findstring openbsd, $(SYS)))
else ifneq (, $(findstring haiku, $(SYS)))
DAEMON_SRC += $(DAEMON_SRC_DIR)/UnixDaemon.cpp
include Makefile.haiku
else ifneq (, $(findstring solaris, $(SYS)))
DAEMON_SRC += $(DAEMON_SRC_DIR)/UnixDaemon.cpp
include Makefile.solaris
else # not supported
$(error Not supported platform)
endif

View file

@ -1,22 +1,18 @@
CXX = clang++
CXXFLAGS ?= ${CXX_DEBUG} -Wall -Wextra -Wno-unused-parameter -pedantic -Wno-misleading-indentation
DEFINES = -D_GLIBCXX_USE_NANOSLEEP=1
INCFLAGS = -I/usr/include/ -I/usr/local/include/
LDFLAGS = ${LD_DEBUG} -Wl,-rpath,/usr/local/lib -L/usr/local/lib
LDLIBS = -lssl -lcrypto -lz -lpthread -lboost_system -lboost_program_options
## NOTE: NEEDED_CXXFLAGS is here so that custom CXXFLAGS can be specified at build time
## **without** overwriting the CXXFLAGS which we need in order to build.
## For example, when adding 'hardening flags' to the build
## (e.g. -fstack-protector-strong -Wformat -Werror=format-security), we do not want to remove
## -std=c++11. If you want to remove this variable please do so in a way that allows setting
## custom FLAGS to work at build-time.
CXXVER := $(shell $(CXX) -dumpversion|cut -c 1-2)
ifeq (${CXXVER}, "4.") # older clang always returned 4.2.1
$(error Compiler too old)
else ifeq (${CXXVER}, ${filter ${CXXVER},16 17 18 19}) # clang 16 - 19
NEEDED_CXXFLAGS = -std=c++20
else
CXXVER := $(shell $(CXX) -dumpversion)
ifeq (${CXXVER}, "4.2.1") # older clang always returned 4.2.1
NEEDED_CXXFLAGS = -std=c++11
else # newer versions support C++17
NEEDED_CXXFLAGS = -std=c++17
endif
DEFINES = -D_GLIBCXX_USE_NANOSLEEP=1
INCFLAGS = -I/usr/include/ -I/usr/local/include/
LDFLAGS = ${LD_DEBUG} -Wl,-rpath,/usr/local/lib -L/usr/local/lib
LDLIBS = -lcrypto -lssl -lz -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread

View file

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

View file

@ -5,20 +5,20 @@ SSLROOT = ${BREWROOT}/opt/openssl@1.1
UPNPROOT = ${BREWROOT}/opt/miniupnpc
CXXFLAGS ?= ${CXX_DEBUG} -Wall -Wno-overloaded-virtual
NEEDED_CXXFLAGS ?= -std=c++17
NEEDED_CXXFLAGS ?= -std=c++11
INCFLAGS ?= -I${SSLROOT}/include -I${BOOSTROOT}/include
LDFLAGS ?= ${LD_DEBUG}
DEFINES += -DMAC_OSX
ifeq ($(USE_STATIC),yes)
LDLIBS = -lz ${SSLROOT}/lib/libcrypto.a ${SSLROOT}/lib/libssl.a ${BOOSTROOT}/lib/libboost_system.a ${BOOSTROOT}/lib/libboost_filesystem.a ${BOOSTROOT}/lib/libboost_program_options.a
LDLIBS = -lz ${SSLROOT}/lib/libcrypto.a ${SSLROOT}/lib/libssl.a ${BOOSTROOT}/lib/libboost_system.a ${BOOSTROOT}/lib/libboost_date_time.a ${BOOSTROOT}/lib/libboost_filesystem.a ${BOOSTROOT}/lib/libboost_program_options.a
ifeq ($(USE_UPNP),yes)
LDLIBS += ${UPNPROOT}/lib/libminiupnpc.a
endif
LDLIBS += -lpthread -ldl
else
LDFLAGS += -L${SSLROOT}/lib -L${BOOSTROOT}/lib
LDLIBS = -lz -lssl -lcrypto -lboost_system -lboost_filesystem -lboost_program_options -lpthread
LDLIBS = -lz -lcrypto -lssl -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread
ifeq ($(USE_UPNP),yes)
LDFLAGS += -L${UPNPROOT}/lib
LDLIBS += -lminiupnpc
@ -30,6 +30,13 @@ ifeq ($(USE_UPNP),yes)
INCFLAGS += -I${UPNPROOT}/include
endif
ifeq ($(USE_AESNI),yes)
ifneq (, $(findstring i386, $(SYS))$(findstring i686, $(SYS))$(findstring x86_64, $(SYS))) # only x86-based CPU supports that
NEEDED_CXXFLAGS += -maes
DEFINES += -D__AES__
endif
endif
install: all
install -d ${PREFIX}/bin
install -m 755 ${I2PD} ${PREFIX}/bin

View file

@ -9,17 +9,24 @@ LDFLAGS ?= ${LD_DEBUG}
## -std=c++11. If you want to remove this variable please do so in a way that allows setting
## custom FDLAGS to work at build-time.
# detect proper flag for c++17 support by compilers
# detect proper flag for c++11 support by compilers
CXXVER := $(shell $(CXX) -dumpversion)
ifeq ($(shell expr match $(CXX) 'clang'),5)
NEEDED_CXXFLAGS += -std=c++11
else ifeq ($(shell expr match ${CXXVER} "4\.[0-9][0-9]"),4) # gcc >= 4.10
NEEDED_CXXFLAGS += -std=c++11
else ifeq ($(shell expr match ${CXXVER} "4\.[8-9]"),3) # gcc 4.8 - 4.9
NEEDED_CXXFLAGS += -std=c++11 -D_GLIBCXX_USE_NANOSLEEP=1
else ifeq ($(shell expr match ${CXXVER} "[5-6]"),1) # gcc 5 - 6
NEEDED_CXXFLAGS += -std=c++11
LDLIBS = -latomic
else ifeq ($(shell expr match ${CXXVER} "[7-9]"),1) # gcc 7 - 9
NEEDED_CXXFLAGS += -std=c++17
else ifeq ($(shell expr match ${CXXVER} "[8-9]"),1) # gcc 8 - 9
LDLIBS = -latomic
else ifeq ($(shell expr match ${CXXVER} "1[0-9]"),2) # gcc 10+
# NEEDED_CXXFLAGS += -std=c++20
NEEDED_CXXFLAGS += -std=c++17
LDLIBS = -lboost_system -lstdc++fs
else ifeq ($(shell expr match ${CXXVER} "1[0-2]"),2) # gcc 10 - 12
NEEDED_CXXFLAGS += -std=c++17
else ifeq ($(shell expr match ${CXXVER} "1[3-9]"),2) # gcc 13+
NEEDED_CXXFLAGS += -std=c++20
LDLIBS = -latomic
else # not supported
$(error Compiler too old)
endif
@ -31,6 +38,9 @@ ifeq ($(USE_STATIC),yes)
# Using 'getaddrinfo' in statically linked applications requires at runtime
# the shared libraries from the glibc version used for linking
LIBDIR := /usr/lib/$(SYS)
LDLIBS += $(LIBDIR)/libboost_system.a
LDLIBS += $(LIBDIR)/libboost_date_time.a
LDLIBS += $(LIBDIR)/libboost_filesystem.a
LDLIBS += $(LIBDIR)/libboost_program_options.a
LDLIBS += $(LIBDIR)/libssl.a
LDLIBS += $(LIBDIR)/libcrypto.a
@ -40,7 +50,7 @@ ifeq ($(USE_UPNP),yes)
endif
LDLIBS += -lpthread -ldl
else
LDLIBS += -lssl -lcrypto -lz -lboost_program_options -lpthread -latomic
LDLIBS += -lcrypto -lssl -lz -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread
ifeq ($(USE_UPNP),yes)
LDLIBS += -lminiupnpc
endif
@ -51,6 +61,13 @@ ifeq ($(USE_UPNP),yes)
DEFINES += -DUSE_UPNP
endif
ifeq ($(USE_AESNI),yes)
ifneq (, $(findstring i386, $(SYS))$(findstring i686, $(SYS))$(findstring x86_64, $(SYS))) # only x86-based CPU supports that
NEEDED_CXXFLAGS += -maes
DEFINES += -D__AES__
endif
endif
install: all
install -d ${PREFIX}/bin
install -m 755 ${I2PD} ${PREFIX}/bin

View file

@ -7,7 +7,7 @@ CXXFLAGS := $(CXX_DEBUG) -fPIC -msse
INCFLAGS := -I$(DAEMON_SRC_DIR) -IWin32
LDFLAGS := ${LD_DEBUG} -static -fPIC -msse
NEEDED_CXXFLAGS += -std=c++20
NEEDED_CXXFLAGS += -std=c++17
DEFINES += -DWIN32_LEAN_AND_MEAN
# UPNP Support
@ -16,11 +16,9 @@ ifeq ($(USE_UPNP),yes)
LDLIBS = -lminiupnpc
endif
ifeq ($(USE_WINXP_FLAGS), yes)
DEFINES += -DWINVER=0x0501 -D_WIN32_WINNT=0x0501
endif
LDLIBS += \
$(MINGW_PREFIX)/lib/libboost_system-mt.a \
$(MINGW_PREFIX)/lib/libboost_date_time-mt.a \
$(MINGW_PREFIX)/lib/libboost_filesystem-mt.a \
$(MINGW_PREFIX)/lib/libboost_program_options-mt.a \
$(MINGW_PREFIX)/lib/libssl.a \
@ -42,6 +40,16 @@ ifeq ($(USE_WIN32_APP), yes)
DAEMON_OBJS += $(patsubst %.rc,obj/%.o,$(DAEMON_RC))
endif
ifeq ($(USE_WINXP_FLAGS), yes)
DEFINES += -DWINVER=0x0501 -D_WIN32_WINNT=0x0501
endif
ifeq ($(USE_AESNI),yes)
NEEDED_CXXFLAGS += -maes
LDFLAGS += -maes
DEFINES += -D__AES__
endif
ifeq ($(USE_ASLR),yes)
LDFLAGS += -Wl,--nxcompat -Wl,--high-entropy-va -Wl,--dynamicbase,--export-all-symbols
endif

View file

@ -1,5 +1,5 @@
CXX = clang++
CXXFLAGS := ${CXX_DEBUG} -Wall -std=c++17
CXXFLAGS := ${CXX_DEBUG} -Wall -std=c++11
INCFLAGS = -I/usr/local/include
DEFINES := -DMAC_OSX
LDFLAGS := -Wl,-rpath,/usr/local/lib -L/usr/local/lib
@ -7,9 +7,9 @@ LDFLAGS += -Wl,-dead_strip
LDFLAGS += -Wl,-dead_strip_dylibs
ifeq ($(USE_STATIC),yes)
LDLIBS = -lz /usr/local/lib/libssl.a /usr/local/lib/libcrypto.a /usr/local/lib/libboost_system.a /usr/local/lib/libboost_filesystem.a /usr/local/lib/libboost_program_options.a -lpthread
LDLIBS = -lz /usr/local/lib/libcrypto.a /usr/local/lib/libssl.a /usr/local/lib/libboost_system.a /usr/local/lib/libboost_date_time.a /usr/local/lib/libboost_filesystem.a /usr/local/lib/libboost_program_options.a -lpthread
else
LDLIBS = -lz -lssl -lcrypto -lboost_system -lboost_filesystem -lboost_program_options -lpthread
LDLIBS = -lz -lcrypto -lssl -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread
endif
ifeq ($(USE_UPNP),yes)
@ -25,5 +25,9 @@ endif
OSARCH = $(shell uname -p)
ifneq ($(OSARCH),powerpc)
ifeq ($(USE_AESNI),yes)
CXXFLAGS += -D__AES__ -maes
else
CXXFLAGS += -msse
endif
endif

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

@ -313,7 +313,7 @@ namespace win32
}
case ID_DATADIR:
{
std::string datadir(i2p::fs::GetDataDir());
std::string datadir(i2p::fs::GetUTF8DataDir());
ShellExecute(NULL, "explore", datadir.c_str(), NULL, NULL, SW_SHOWNORMAL);
return 0;
}
@ -355,7 +355,9 @@ namespace win32
}
}
}
#if (__cplusplus >= 201703L) // C++ 17 or higher
[[fallthrough]];
#endif
}
case WM_TRAYICON:
{

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2024, The PurpleI2P Project
* Copyright (c) 2013-2020, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2024, The PurpleI2P Project
* Copyright (c) 2013-2020, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*

View file

@ -29,6 +29,7 @@ project(
)
# configurable options
option(WITH_AESNI "Use AES-NI instructions set" ON)
option(WITH_HARDENING "Use hardening compiler flags" OFF)
option(WITH_LIBRARY "Build library" ON)
option(WITH_BINARY "Build binary" ON)
@ -155,6 +156,20 @@ else()
endif()
set(CMAKE_CXX_FLAGS_MINSIZEREL "${CMAKE_CXX_FLAGS_MINSIZEREL} -ffunction-sections -fdata-sections")
set(CMAKE_EXE_LINKER_FLAGS_MINSIZEREL "-Wl,--gc-sections") # -flto is added from above
# check for c++17 & c++11 support
include(CheckCXXCompilerFlag)
CHECK_CXX_COMPILER_FLAG("-std=c++17" CXX17_SUPPORTED)
CHECK_CXX_COMPILER_FLAG("-std=c++11" CXX11_SUPPORTED)
if(CXX17_SUPPORTED)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17")
elseif(CXX11_SUPPORTED)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
else()
message(SEND_ERROR "C++17 nor C++11 standard not seems to be supported by compiler. Too old version?")
endif()
endif()
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
@ -184,6 +199,16 @@ if(UNIX)
endif()
endif()
# Note: AES-NI and AVX is available on x86-based CPU's.
# Here also ARM64 implementation, but currently we don't support it.
# MSVC is not supported due to different ASM processing, so we hope OpenSSL has its own checks to run optimized code.
if(WITH_AESNI AND (ARCHITECTURE MATCHES "x86_64" OR ARCHITECTURE MATCHES "i386"))
if(NOT MSVC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -maes")
endif()
add_definitions(-D__AES__)
endif()
if(WITH_ADDRSANITIZER)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address")
@ -198,10 +223,6 @@ if(WITH_THREADSANITIZER)
endif()
endif()
if (CMAKE_COMPILER_IS_GNUCC AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 10.0 AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 8.0) # gcc 8-9
list(APPEND CMAKE_REQUIRED_LIBRARIES "stdc++fs")
endif()
# Use std::atomic instead of GCC builtins on macOS PowerPC:
# For more information refer to: https://github.com/PurpleI2P/i2pd/issues/1726#issuecomment-1306335111
# This has been fixed in Boost 1.81, nevertheless we retain the setting for the sake of compatibility.
@ -256,14 +277,14 @@ else()
if(NOT MSVC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC")
endif()
add_definitions(-DBOOST_ATOMIC_DYN_LINK -DBOOST_SYSTEM_DYN_LINK -DBOOST_FILESYSTEM_DYN_LINK -DBOOST_PROGRAM_OPTIONS_DYN_LINK)
add_definitions(-DBOOST_ATOMIC_DYN_LINK -DBOOST_SYSTEM_DYN_LINK -DBOOST_FILESYSTEM_DYN_LINK -DBOOST_PROGRAM_OPTIONS_DYN_LINK -DBOOST_DATE_TIME_DYN_LINK -DBOOST_REGEX_DYN_LINK)
if(WIN32)
set(Boost_USE_STATIC_LIBS OFF)
set(Boost_USE_STATIC_RUNTIME OFF)
endif()
endif()
find_package(Boost REQUIRED COMPONENTS system filesystem program_options)
find_package(Boost REQUIRED COMPONENTS system filesystem program_options date_time OPTIONAL_COMPONENTS atomic)
if(NOT DEFINED Boost_FOUND)
message(SEND_ERROR "Boost is not found, or your boost version was below 1.46. Please download Boost!")
endif()
@ -291,26 +312,6 @@ if(ZLIB_FOUND)
link_directories(${ZLIB_ROOT}/lib)
endif()
# C++ standard to use, based on compiler and version of boost
if(NOT MSVC)
# check for c++20 & c++17 support
include(CheckCXXCompilerFlag)
if(Boost_VERSION VERSION_GREATER_EQUAL "1.83") # min boost version for c++20
CHECK_CXX_COMPILER_FLAG("-std=c++20" CXX20_SUPPORTED)
endif()
CHECK_CXX_COMPILER_FLAG("-std=c++17" CXX17_SUPPORTED)
if(CXX20_SUPPORTED)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++20")
elseif(CXX17_SUPPORTED)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17")
else()
message(SEND_ERROR "C++20 nor C++17 standard not seems to be supported by compiler. Too old version?")
endif()
endif()
# load includes
include_directories(SYSTEM ${Boost_INCLUDE_DIRS} ${OPENSSL_INCLUDE_DIR} ${ZLIB_INCLUDE_DIR})
@ -321,9 +322,9 @@ message(STATUS "Compiler vendor : ${CMAKE_CXX_COMPILER_ID}")
message(STATUS "Compiler version : ${CMAKE_CXX_COMPILER_VERSION}")
message(STATUS "Compiler path : ${CMAKE_CXX_COMPILER}")
message(STATUS "Architecture : ${ARCHITECTURE}")
message(STATUS "Compiler flags : ${CMAKE_CXX_FLAGS}")
message(STATUS "Install prefix: : ${CMAKE_INSTALL_PREFIX}")
message(STATUS "Options:")
message(STATUS " AESNI : ${WITH_AESNI}")
message(STATUS " HARDENING : ${WITH_HARDENING}")
message(STATUS " LIBRARY : ${WITH_LIBRARY}")
message(STATUS " BINARY : ${WITH_BINARY}")

View file

@ -8,7 +8,7 @@ INCLUDE(CheckLibraryExists)
function(check_working_cxx_atomics varname)
set(OLD_CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS})
set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -std=c++17")
set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -std=c++11")
CHECK_CXX_SOURCE_COMPILES("
#include <atomic>
std::atomic<int> x;
@ -25,7 +25,7 @@ endfunction(check_working_cxx_atomics)
function(check_working_cxx_atomics64 varname)
set(OLD_CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS})
set(CMAKE_REQUIRED_FLAGS "-std=c++17 ${CMAKE_REQUIRED_FLAGS}")
set(CMAKE_REQUIRED_FLAGS "-std=c++11 ${CMAKE_REQUIRED_FLAGS}")
CHECK_CXX_SOURCE_COMPILES("
#include <atomic>
#include <cstdint>

View file

@ -59,7 +59,7 @@ get_filename_component(_gitdescmoddir ${CMAKE_CURRENT_LIST_FILE} PATH)
# function returns an empty string via _git_dir_var.
#
# Example: Given a path C:/bla/foo/bar and assuming C:/bla/.git exists and
# neither foo nor bar contain a file/directory .git. This will return
# neither foo nor bar contain a file/directory .git. This wil return
# C:/bla/.git
#
function(_git_find_closest_git_dir _start_dir _git_dir_var)

View file

@ -0,0 +1,32 @@
-----BEGIN CERTIFICATE-----
MIIFgTCCA2mgAwIBAgIETWAY1DANBgkqhkiG9w0BAQ0FADBxMQswCQYDVQQGEwJY
WDELMAkGA1UECAwCWFgxCzAJBgNVBAcMAlhYMR4wHAYDVQQKDBVJMlAgQW5vbnlt
b3VzIE5ldHdvcmsxDDAKBgNVBAsMA0kyUDEaMBgGA1UEAwwRaGlkdXNlcjBAbWFp
bC5pMnAwHhcNMjExMjEzMTU0MDI3WhcNMzExMjExMTU0MDI3WjBxMQswCQYDVQQG
EwJYWDELMAkGA1UECAwCWFgxCzAJBgNVBAcMAlhYMR4wHAYDVQQKDBVJMlAgQW5v
bnltb3VzIE5ldHdvcmsxDDAKBgNVBAsMA0kyUDEaMBgGA1UEAwwRaGlkdXNlcjBA
bWFpbC5pMnAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDXnjJ8UQ0f
lHHpfPMiHofBPSuL4sbOJY6fOXwPhSg/h6THh9DS/ZWmJXQ3qRD0glDVtv4/Dr/9
ldGQ5eltF9iCFXCQlMEy2HjQrBKq0nsl7RpYK12cyMaod0kkzCUk9ITLi9CmHM3Z
gQZcmG8TWjFEpDR+idx/QkQt2pcO4vzWlDit3Vh4ivnbX5jGQHbsVjQEMQWxr+pX
dsS+YQpjZ6RBmrooGTPO8QDOOeYLAn0lCjmffc/kzIH9E/p4/O0rOpyhVYbdxUD1
5wkqN9l4yrtxmORG/PudnRQQ0r4TUq8vsxfGY0Euo9IbhgXF2Parel1ZhDxB1WZV
VwWtgLIh9jGA1UMa8SYKnEfp8LWNZ3b3mUUnZb3kMrLk6jGYRWNsHmamhd4mC7AZ
qf/8lOkEIw3bPd3YguCDRVcLui5BwIEZmqXg8uoESxfO/sW3pBrN/8M7MkTex9kN
vjitGDDXvenK27qmNgZxbBlX72yTSfys7XTYTLnxZC8AwdAo2Wz9Z6HhGiPonf2h
vZkc9ZxuE0jFIrsbJra4X7iyjXgi4vV4ARNg/9Ft6F4/OIbECgeDcBQqq4TlT2bZ
EfWVrBbqXoj5vNsLigIkd+AyUNwPYEcB5IFSiiOh98pC7BH3pg0m8U5YBjxe1i+9
EQOOG0Qtx+JigXZHu6bGE0Twy9zy+UzoKQIDAQABoyEwHzAdBgNVHQ4EFgQUGK1b
0DkL6aLalcfBc/Uj/SF08C0wDQYJKoZIhvcNAQENBQADggIBAMpXM82bJDpH1TlH
TvhU3Z7nfZdvEhOQfujaFUYiuNripuEKcFGn948+DvAG0FUN+uNlJoqOVs8D7InD
gWlA9zpqw5Cl5Hij/Wns9QbXuAHJeA23fVUoaM2A6v9ifcIQ1A+rDuRQAo6/64KW
ChTg2e99RBpfGOyqgeh7tLLe0lPPekVpKHFuXabokaKRDuBcVHcUL4tWXe3dcyqa
Ej/PJrrS+nWL0EGZ4q80CEd2LPuDzPxNGCJt/R7ZfadENWajcgcXGceh1QBzozrB
SL/Ya6wF9SrsB7V/r5wX0LM4ZdDaLWbtmUe5Op0h/ZMH25Sa8xAXVz+O9L6sWSoO
FaiYTOvAiyyPz+nsxKa3xYryDHno7eKSt+hGOcaurhxbdZaEFY/CegEc73tCt9xK
e9qF8O/WkDLmixuErw3f5en4IfzGR7p3lJAwW/8WD8C6HS39h/eE7dVZNaWgtQnZ
SgGjgZMTJqTcQ3aZmfuCZefxGFok8w6AIkdbnd1pdMBRjYu8aXgl2hQSB9ZADDE9
R5d3rXi0PkSFLIvsNjVa5KXrZk/tB0Hpfmepq7CufBqjP/LG9TieRoXzLYUKFF74
QRwjP+y7AJ+VDUTpY1NV1P+k+2raubU2bOnLF3zL5DtyoyieGPhyeMMvp0fRIxdg
bSl5VHgPXHNM8mcnndMAuzvl7jEK
-----END CERTIFICATE-----

View file

@ -0,0 +1,33 @@
-----BEGIN CERTIFICATE-----
MIIFxzCCA6+gAwIBAgIQZfqn0yiJL3dGgCjeOeWS6DANBgkqhkiG9w0BAQsFADBw
MQswCQYDVQQGEwJYWDELMAkGA1UEBxMCWFgxCzAJBgNVBAkTAlhYMR4wHAYDVQQK
ExVJMlAgQW5vbnltb3VzIE5ldHdvcmsxDDAKBgNVBAsTA0kyUDEZMBcGA1UEAwwQ
aG90dHVuYUBtYWlsLmkycDAeFw0xNjExMDkwMzE1MzJaFw0yNjExMDkwMzE1MzJa
MHAxCzAJBgNVBAYTAlhYMQswCQYDVQQHEwJYWDELMAkGA1UECRMCWFgxHjAcBgNV
BAoTFUkyUCBBbm9ueW1vdXMgTmV0d29yazEMMAoGA1UECxMDSTJQMRkwFwYDVQQD
DBBob3R0dW5hQG1haWwuaTJwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC
AgEA21Bfgcc9VVH4l2u1YvYlTw2OPUyQb16X2IOW0PzdsUO5W78Loueu974BkiKi
84lQZanLr0OwEopdfutGc6gegSLmwaWx5YCG5uwpLOPkDiObfX+nptH6As/B1cn+
mzejYdVKRnWd7EtHW0iseSsILBK1YbGw4AGpXJ8k18DJSzUt2+spOkpBW6XqectN
8y2JDSTns8yiNxietVeRN/clolDXT9ZwWHkd+QMHTKhgl3Uz1knOffU0L9l4ij4E
oFgPfQo8NL63kLM24hF1hM/At7XvE4iOlObFwPXE+H5EGZpT5+A7Oezepvd/VMzM
tCJ49hM0OlR393tKFONye5GCYeSDJGdPEB6+rBptpRrlch63tG9ktpCRrg2wQWgC
e3aOE1xVRrmwiTZ+jpfsOCbZrrSA/C4Bmp6AfGchyHuDGGkRU/FJwa1YLJe0dkWG
ITLWeh4zeVuAS5mctdv9NQ5wflSGz9S8HjsPBS5+CDOFHh4cexXRG3ITfk6aLhuY
KTMlkIO4SHKmnwAvy1sFlsqj6PbfVjpHPLg625fdNxBpe57TLxtIdBB3C7ccQSRW
+UG6Cmbcmh80PbsSR132NLMlzLhbaOjxeCWWJRo6cLuHBptAFMNwqsXt8xVf9M0N
NdJoKUmblyvjnq0N8aMEqtQ1uGMTaCB39cutHQq+reD/uzsCAwEAAaNdMFswDgYD
VR0PAQH/BAQDAgKEMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAPBgNV
HRMBAf8EBTADAQH/MBkGA1UdDgQSBBBob3R0dW5hQG1haWwuaTJwMA0GCSqGSIb3
DQEBCwUAA4ICAQCibFV8t4pajP176u3jx31x1kgqX6Nd+0YFARPZQjq99kUyoZer
GyHGsMWgM281RxiZkveHxR7Hm7pEd1nkhG3rm+d7GdJ2p2hujr9xUvl0zEqAAqtm
lkYI6uJ13WBjFc9/QuRIdeIeSUN+eazSXNg2nJhoV4pF9n2Q2xDc9dH4GWO93cMX
JPKVGujT3s0b7LWsEguZBPdaPW7wwZd902Cg/M5fE1hZQ8/SIAGUtylb/ZilVeTS
spxWP1gX3NT1SSvv0s6oL7eADCgtggWaMxEjZhi6WMnPUeeFY8X+6trkTlnF9+r/
HiVvvzQKrPPtB3j1xfQCAF6gUKN4iY+2AOExv4rl/l+JJbPhpd/FuvD8AVkLMZ8X
uPe0Ew2xv30cc8JjGDzQvoSpBmVTra4f+xqH+w8UEmxnx97Ye2aUCtnPykACnFte
oT97K5052B1zq+4fu4xaHZnEzPYVK5POzOufNLPgciJsWrR5GDWtHd+ht/ZD37+b
+j1BXpeBWUBQgluFv+lNMVNPJxc2OMELR1EtEwXD7mTuuUEtF5Pi63IerQ5LzD3G
KBvXhMB0XhpE6WG6pBwAvkGf5zVv/CxClJH4BQbdZwj9HYddfEQlPl0z/XFR2M0+
9/8nBfGSPYIt6KeHBCeyQWTdE9gqSzMwTMFsennXmaT8gyc7eKqKF6adqw==
-----END CERTIFICATE-----

View file

@ -1,34 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIF2TCCA8GgAwIBAgIQVpTNnJZlUTDqmZiHRU4wCjANBgkqhkiG9w0BAQsFADB2
MQswCQYDVQQGEwJYWDELMAkGA1UEBxMCWFgxCzAJBgNVBAkTAlhYMR4wHAYDVQQK
ExVJMlAgQW5vbnltb3VzIE5ldHdvcmsxDDAKBgNVBAsTA0kyUDEfMB0GA1UEAwwW
dW5peGVub0BjdWJpY2NoYW9zLm5ldDAeFw0yNTAzMDQxODU5NDZaFw0zNTAzMDQx
ODU5NDZaMHYxCzAJBgNVBAYTAlhYMQswCQYDVQQHEwJYWDELMAkGA1UECRMCWFgx
HjAcBgNVBAoTFUkyUCBBbm9ueW1vdXMgTmV0d29yazEMMAoGA1UECxMDSTJQMR8w
HQYDVQQDDBZ1bml4ZW5vQGN1YmljY2hhb3MubmV0MIICIjANBgkqhkiG9w0BAQEF
AAOCAg8AMIICCgKCAgEAr/JoAzLDtHXoAc7QcP4IxO+xNTeiYs78Wlg/Sl/sa6qz
gJoGaKH/X++z4Xe9lBSZalXCamnO4QMTgsWOIeoMy6XVbGzNTXPl8JUcblTIXwkP
pv848b1nxLfgLHzPRz1mJMpMikBugJ3Iz1sQzDVlUdye2fgbGChWliz9P4ClEODv
A/4+7i6uvJgEZ7A+jx3vBCXhiJppE3wTuz5D9BQqG8NuEwwjwBTBByoCC4oxOe0h
Qu1k7kEr+n4qpSEg/1eJ/RYSm+I8BftK1RUfykTwxlfmyEmKsfLBQWczE8Ca9nUB
5V34UH2bRy1cvavJYcNW3EPsGNf4naRs+Gy8XIFrb315GgWC1Z6+tzk+QFli9YeF
0DgtYEZciqu/407o8ZEURTnPjB7GhLDDp1LAQ7CQRhzaraXjHj0hyO+6rFpFdD0D
mXhvI/Eph3QIldsgnQc7nPhU2csN8Vi6bNDgm0HZ8cdmIBpI2Uxn/acZX/9G40oj
czrhsCBEecu/BluLJsfaWCYg90rvONP8Fc4edHAMonzYZR4r0q4hbv7AM8GmDRDN
J9/DZFi+Qs9NAe06jJC3jSsj7IdIs8TMhw8FX3xWOlXwjmVETAgY/dta/MpLJ6tJ
i+E+TH/Ndntj/D6WUwdQq+LyCR6gqHUWR6rl6EDQz+08DWb7j/72JSLb/DaXrDUC
AwEAAaNjMGEwDgYDVR0PAQH/BAQDAgKEMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggr
BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MB8GA1UdDgQYBBZ1bml4ZW5vQGN1Ymlj
Y2hhb3MubmV0MA0GCSqGSIb3DQEBCwUAA4ICAQBBVoPeuOkmJDUdzIrzmxTmjMyz
gpfrZnjirTQKWhbv53sAWNeJ3kZ9l9m+0YpqEtFDrZPL5LTBXcSci5gGuxPkp+i/
f/axsdcFMKbI9B/M53fyXLLJY0EM4mdhNAWtph1kTowFPhhReefCdqxYIy9uk2pL
gfb6NYJf+w9//fKYFZXb9SsiRchfv81+lbN+PIprnCpV3cTZWmpLRi2hN86pMW20
3rh7rqJ4dPnA/NoyM+JstL10IU/4StqInweEvoo4W44+cC0zYGvfkkrKL4LB8w5S
6DKebtk7NQDtzuw2QnA9Ms4bmqWQpbL6/7uGaauS0+nmF+2gkqi9hcv0W5ZoBb/D
IVRauySnCtp9PbYM7pIJP9a1U6naLj7L1VixqsJGfPQ8V9TCOOi5bDc3RTetI/DX
bXHhAqHYzboakptylCp+Ao5h2hu0+w4rqnG63HwsHDJWcETbdVFQfzlzUmbx53yV
GnBsUxDgMOiHTZdKLkEnH4Q/XI76uc0ntTRlK9ktKWZPSISUlHrFnFl6I5UdeBMy
6vpB9sJO5L5RPRi4945K5Xdennywdi508mNXtMMmNCqrk1SMYbwaY6ZtIvXEGam9
uHQTiTEX9LED/VXzFGqzdyDbG43HgS0PksgzedelHWfVAEnc06U3JX2lqUyihYHa
N4jAXWQ7s5p4GYaf4Q==
-----END CERTIFICATE-----

View file

@ -2,13 +2,13 @@ Description: Enable UPnP usage in package
Author: r4sas <r4sas@i2pmail.org>
Reviewed-By: r4sas <r4sas@i2pmail.org>
Last-Update: 2024-12-30
Last-Update: 2022-03-23
--- i2pd.orig/Makefile
+++ i2pd/Makefile
@@ -31,7 +31,7 @@ # import source files lists
include filelist.mk
@@ -31,7 +31,7 @@ include filelist.mk
USE_AESNI := $(or $(USE_AESNI),yes)
USE_STATIC := $(or $(USE_STATIC),no)
-USE_UPNP := $(or $(USE_UPNP),no)
+USE_UPNP := $(or $(USE_UPNP),yes)

View file

@ -2,13 +2,13 @@ Description: Enable UPnP usage in package
Author: r4sas <r4sas@i2pmail.org>
Reviewed-By: r4sas <r4sas@i2pmail.org>
Last-Update: 2024-12-30
Last-Update: 2022-03-23
--- i2pd.orig/Makefile
+++ i2pd/Makefile
@@ -31,7 +31,7 @@ # import source files lists
include filelist.mk
@@ -31,7 +31,7 @@ include filelist.mk
USE_AESNI := $(or $(USE_AESNI),yes)
USE_STATIC := $(or $(USE_STATIC),no)
-USE_UPNP := $(or $(USE_UPNP),no)
+USE_UPNP := $(or $(USE_UPNP),yes)

View file

@ -243,12 +243,12 @@ verify = true
## Default: reg.i2p at "mainline" I2P Network
# defaulturl = http://shx5vqsw7usdaunyzr2qmes2fq37oumybpudrd4jjj4e4vk4uusa.b32.i2p/hosts.txt
## Optional subscriptions URLs, separated by comma
# subscriptions = http://reg.i2p/hosts.txt,http://identiguy.i2p/hosts.txt,http://stats.i2p/cgi-bin/newhosts.txt
# subscriptions = http://reg.i2p/hosts.txt,http://identiguy.i2p/hosts.txt,http://stats.i2p/cgi-bin/newhosts.txt,http://rus.i2p/hosts.txt
[limits]
## Maximum active transit sessions (default: 10000)
## Maximum active transit sessions (default: 5000)
## This value is doubled if floodfill mode is enabled!
# transittunnels = 10000
# transittunnels = 5000
## Limit number of open file descriptors (0 - use system limit)
# openfiles = 0
## Maximum size of corefile in Kb (0 - use system limit)
@ -277,3 +277,9 @@ verify = true
## Save full addresses on disk (default: true)
# addressbook = true
[cpuext]
## Use CPU AES-NI instructions set when work with cryptography when available (default: true)
# aesni = true
## Force usage of CPU instructions set, even if they not found (default: false)
## DO NOT TOUCH that option if you really don't know what are you doing!
# force = false

View file

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

View file

@ -1,7 +1,7 @@
%define git_hash %(git rev-parse HEAD | cut -c -7)
Name: i2pd-git
Version: 2.57.0
Version: 2.53.1
Release: git%{git_hash}%{?dist}
Summary: I2P router written in C++
Conflicts: i2pd
@ -10,7 +10,11 @@ License: BSD
URL: https://github.com/PurpleI2P/i2pd
Source0: https://github.com/PurpleI2P/i2pd/archive/openssl/i2pd-openssl.tar.gz
%if 0%{?rhel} == 7
BuildRequires: cmake3
%else
BuildRequires: cmake
%endif
BuildRequires: chrpath
BuildRequires: gcc-c++
@ -20,7 +24,7 @@ BuildRequires: openssl-devel
BuildRequires: miniupnpc-devel
BuildRequires: systemd-units
%if 0%{?fedora} == 41
%if 0%{?fedora} > 40 || 0%{?eln}
BuildRequires: openssl-devel-engine
%endif
@ -39,18 +43,26 @@ C++ implementation of I2P.
%build
cd build
%cmake \
%if 0%{?rhel} == 7
%cmake3 \
-DWITH_LIBRARY=OFF \
-DWITH_UPNP=ON \
-DWITH_HARDENING=ON \
%if 0%{?fedora} > 29
-DBUILD_SHARED_LIBS:BOOL=OFF
%else
%cmake \
-DWITH_LIBRARY=OFF \
-DWITH_UPNP=ON \
-DWITH_HARDENING=ON \
%if 0%{?fedora} > 29
-DBUILD_SHARED_LIBS:BOOL=OFF \
.
%else
%else
-DBUILD_SHARED_LIBS:BOOL=OFF
%endif
%endif
%if 0%{?rhel} >= 9 || 0%{?fedora} >= 35 || 0%{?eln}
%if 0%{?rhel} == 9 || 0%{?fedora} >= 35 || 0%{?eln}
pushd redhat-linux-build
%else
%if 0%{?fedora} >= 33
@ -64,7 +76,7 @@ cd build
make %{?_smp_mflags}
%if 0%{?rhel} >= 9 || 0%{?fedora} >= 33 || 0%{?mageia} > 7
%if 0%{?rhel} == 9 || 0%{?fedora} >= 33 || 0%{?mageia} > 7
popd
%endif
@ -72,7 +84,7 @@ make %{?_smp_mflags}
%install
pushd build
%if 0%{?rhel} >= 9 || 0%{?fedora} >= 35 || 0%{?eln}
%if 0%{?rhel} == 9 || 0%{?fedora} >= 35 || 0%{?eln}
pushd redhat-linux-build
%else
%if 0%{?fedora} >= 33
@ -136,18 +148,6 @@ getent passwd i2pd >/dev/null || \
%changelog
* Mon Jun 02 2025 orignal <orignal@i2pmail.org> - 2.57.0
- update to 2.57.0
* Tue Feb 11 2025 orignal <orignal@i2pmail.org> - 2.56.0
- update to 2.56.0
* Mon Dec 30 2024 orignal <orignal@i2pmail.org> - 2.55.0
- update to 2.55.0
* Sun Oct 6 2024 orignal <orignal@i2pmail.org> - 2.54.0
- update to 2.54.0
* Tue Jul 30 2024 orignal <orignal@i2pmail.org> - 2.53.1
- update to 2.53.1

View file

@ -1,5 +1,5 @@
Name: i2pd
Version: 2.57.0
Version: 2.53.1
Release: 1%{?dist}
Summary: I2P router written in C++
Conflicts: i2pd-git
@ -8,7 +8,11 @@ License: BSD
URL: https://github.com/PurpleI2P/i2pd
Source0: https://github.com/PurpleI2P/i2pd/archive/%{version}/%name-%version.tar.gz
%if 0%{?rhel} == 7
BuildRequires: cmake3
%else
BuildRequires: cmake
%endif
BuildRequires: chrpath
BuildRequires: gcc-c++
@ -18,7 +22,7 @@ BuildRequires: openssl-devel
BuildRequires: miniupnpc-devel
BuildRequires: systemd-units
%if 0%{?fedora} == 41
%if 0%{?fedora} > 40 || 0%{?eln}
BuildRequires: openssl-devel-engine
%endif
@ -37,18 +41,26 @@ C++ implementation of I2P.
%build
cd build
%cmake \
%if 0%{?rhel} == 7
%cmake3 \
-DWITH_LIBRARY=OFF \
-DWITH_UPNP=ON \
-DWITH_HARDENING=ON \
%if 0%{?fedora} > 29
-DBUILD_SHARED_LIBS:BOOL=OFF
%else
%cmake \
-DWITH_LIBRARY=OFF \
-DWITH_UPNP=ON \
-DWITH_HARDENING=ON \
%if 0%{?fedora} > 29
-DBUILD_SHARED_LIBS:BOOL=OFF \
.
%else
%else
-DBUILD_SHARED_LIBS:BOOL=OFF
%endif
%endif
%if 0%{?rhel} >= 9 || 0%{?fedora} >= 35 || 0%{?eln}
%if 0%{?rhel} == 9 || 0%{?fedora} >= 35 || 0%{?eln}
pushd redhat-linux-build
%else
%if 0%{?fedora} >= 33
@ -62,7 +74,7 @@ cd build
make %{?_smp_mflags}
%if 0%{?rhel} >= 9 || 0%{?fedora} >= 33 || 0%{?mageia} > 7
%if 0%{?rhel} == 9 || 0%{?fedora} >= 33 || 0%{?mageia} > 7
popd
%endif
@ -70,7 +82,7 @@ make %{?_smp_mflags}
%install
pushd build
%if 0%{?rhel} >= 9 || 0%{?fedora} >= 35 || 0%{?eln}
%if 0%{?rhel} == 9 || 0%{?fedora} >= 35 || 0%{?eln}
pushd redhat-linux-build
%else
%if 0%{?fedora} >= 33
@ -134,18 +146,6 @@ getent passwd i2pd >/dev/null || \
%changelog
* Mon Jun 02 2025 orignal <orignal@i2pmail.org> - 2.57.0
- update to 2.57.0
* Tue Feb 11 2025 orignal <orignal@i2pmail.org> - 2.56.0
- update to 2.56.0
* Mon Dec 30 2024 orignal <orignal@i2pmail.org> - 2.55.0
- update to 2.55.0
* Sun Oct 6 2024 orignal <orignal@i2pmail.org> - 2.54.0
- update to 2.54.0
* Tue Jul 30 2024 orignal <orignal@i2pmail.org> - 2.53.1
- update to 2.53.1

View file

@ -5,7 +5,6 @@ port = 6668
destination = irc.ilita.i2p
destinationport = 6667
keys = irc-keys.dat
i2p.streaming.profile=2
#[IRC-IRC2P]
#type = client

View file

@ -149,10 +149,12 @@ namespace util
LogPrint(eLogDebug, "FS: Certificates directory: ", certsdir);
bool precomputation; i2p::config::GetOption("precomputation.elgamal", precomputation);
bool aesni; i2p::config::GetOption("cpuext.aesni", aesni);
bool forceCpuExt; i2p::config::GetOption("cpuext.force", forceCpuExt);
bool ssu; i2p::config::GetOption("ssu", ssu);
if (!ssu && i2p::config::IsDefault ("precomputation.elgamal"))
precomputation = false; // we don't elgamal table if no ssu, unless it's specified explicitly
i2p::crypto::InitCrypto (precomputation);
i2p::crypto::InitCrypto (precomputation, aesni, forceCpuExt);
i2p::transport::InitAddressFromIface (); // get address4/6 from interfaces

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
*
@ -132,8 +132,7 @@ namespace http {
static void ShowTunnelDetails (std::stringstream& s, enum i2p::tunnel::TunnelState eState, bool explr, int bytes)
{
std::string state;
std::string_view stateText;
std::string state, stateText;
switch (eState)
{
case i2p::tunnel::eTunnelStateBuildReplyReceived :
@ -147,7 +146,7 @@ namespace http {
}
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);
s << "\r\n";
}
@ -172,12 +171,9 @@ namespace http {
auto it = i2p::i18n::languages.find(currLang);
std::string langCode = it->second.ShortCode;
// Right to Left language option
bool rtl = i2p::client::context.GetLanguage ()->GetRTL();
s <<
"<!DOCTYPE html>\r\n"
"<html lang=\"" << langCode << "\"" << (rtl ? " dir=\"rtl\"" : "") << ">\r\n"
"<html lang=\"" << langCode << "\">\r\n"
" <head>\r\n" /* TODO: Find something to parse html/template system. This is horrible. */
" <meta charset=\"UTF-8\">\r\n"
" <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n"
@ -217,7 +213,7 @@ namespace http {
"</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";
}
@ -661,7 +657,7 @@ namespace http {
else
{
ls.reset (new i2p::data::LeaseSet2 (storeType));
ls->Update (leaseSet->GetBuffer(), leaseSet->GetBufferLen(), nullptr, false);
ls->Update (leaseSet->GetBuffer(), leaseSet->GetBufferLen(), false);
}
if (!ls) return;
s << "<div class=\"leaseset listitem";
@ -705,7 +701,6 @@ namespace http {
{
s << "<b>" << tr("Tunnels") << ":</b><br>\r\n";
s << "<b>" << tr("Queue size") << ":</b> " << i2p::tunnel::tunnels.GetQueueSize () << "<br>\r\n<br>\r\n";
s << "<b>" << tr("TBM Queue size") << ":</b> " << i2p::tunnel::tunnels.GetTBMQueueSize () << "<br>\r\n<br>\r\n";
auto ExplPool = i2p::tunnel::tunnels.GetExploratoryPool ();
@ -830,7 +825,7 @@ namespace http {
if (i2p::tunnel::tunnels.CountTransitTunnels())
{
s << "<b>" << tr("Transit Tunnels") << ":</b><br>\r\n";
s << "<table><thead><th>&#8658;</th><th>ID</th><th>&#8658;</th><th>" << tr("Amount") << "</th><th>" << tr("Next") << "</th></thead><tbody class=\"tableitem\">";
s << "<table><thead><th>&#8658;</th><th>ID</th><th>&#8658;</th><th>" << tr("Amount") << "</th></thead><tbody class=\"tableitem\">";
for (const auto& it: i2p::tunnel::tunnels.GetTransitTunnels ())
{
if (std::dynamic_pointer_cast<i2p::tunnel::TransitTunnelGateway>(it))
@ -840,7 +835,7 @@ namespace http {
else
s << "<tr><td>&#8658;</td><td>" << it->GetTunnelID () << "</td><td>&#8658;</td><td>";
ShowTraffic(s, it->GetNumTransmittedBytes ());
s << "</td><td>" << it->GetNextPeerName () << "</td></tr>\r\n";
s << "</td></tr>\r\n";
}
s << "</tbody></table>\r\n";
}
@ -1266,7 +1261,7 @@ namespace http {
ShowLeasesSets(s);
else {
res.code = 400;
ShowError(s, std::string (tr("Unknown page")) + ": " + page); // TODO
ShowError(s, tr("Unknown page") + ": " + page);
return;
}
}
@ -1422,11 +1417,13 @@ namespace http {
{
auto signatureLen = dest->GetIdentity ()->GetSignatureLen ();
uint8_t * signature = new uint8_t[signatureLen];
char * sig = new char[signatureLen*2];
std::stringstream out;
out << name << "=" << dest->GetIdentity ()->ToBase64 ();
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;
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"
@ -1435,6 +1432,7 @@ namespace http {
"<input type=\"submit\" value=\"" << tr("Submit") << "\">\r\n"
"</form>\r\n<br>\r\n";
delete[] signature;
delete[] sig;
}
else
s << "<b>" << tr("ERROR") << "</b>:&nbsp;" << tr("Domain can't end with .b32.i2p") << "\r\n<br>\r\n<br>\r\n";
@ -1463,7 +1461,7 @@ namespace http {
else
{
res.code = 400;
ShowError(s, std::string (tr("Unknown command")) + ": " + cmd); // TODO
ShowError(s, tr("Unknown command") + ": " + cmd);
return;
}
@ -1482,13 +1480,13 @@ namespace http {
reply.body = content;
m_SendBuffer = reply.to_string();
boost::asio::async_write (*m_Socket, boost::asio::buffer(m_SendBuffer), boost::asio::transfer_all (),
boost::asio::async_write (*m_Socket, boost::asio::buffer(m_SendBuffer),
std::bind (&HTTPConnection::Terminate, shared_from_this (), std::placeholders::_1));
}
HTTPServer::HTTPServer (const std::string& address, int port):
m_IsRunning (false), m_Thread (nullptr), m_Work (m_Service.get_executor ()),
m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint (boost::asio::ip::make_address(address), port)),
m_IsRunning (false), m_Thread (nullptr), m_Work (m_Service),
m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint (boost::asio::ip::address::from_string(address), port)),
m_Hostname(address)
{
}

View file

@ -83,8 +83,8 @@ namespace http
bool m_IsRunning;
std::unique_ptr<std::thread> m_Thread;
boost::asio::io_context m_Service;
boost::asio::executor_work_guard<boost::asio::io_context::executor_type> m_Work;
boost::asio::io_service m_Service;
boost::asio::io_service::work m_Work;
boost::asio::ip::tcp::acceptor m_Acceptor;
std::string m_Hostname;
};

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
*
@ -8,13 +8,16 @@
#include <stdio.h>
#include <sstream>
#include <iomanip>
#include <openssl/x509.h>
#include <openssl/pem.h>
// Use global placeholders from boost introduced when local_time.hpp is loaded
#define BOOST_BIND_GLOBAL_PLACEHOLDERS
#include <boost/date_time/local_time/local_time.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <boost/lexical_cast.hpp>
#include "FS.h"
#include "Log.h"
@ -29,24 +32,11 @@ namespace i2p
namespace client
{
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::address::from_string(address), port)),
m_SSLContext (boost::asio::ssl::context::sslv23),
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);
// certificate / keys
@ -57,29 +47,15 @@ namespace client
i2pcp_crt = i2p::fs::DataDirPath(i2pcp_crt);
if (i2pcp_key.at(0) != '/')
i2pcp_key = i2p::fs::DataDirPath(i2pcp_key);
if (!i2p::fs::Exists (i2pcp_crt) || !i2p::fs::Exists (i2pcp_key))
{
if (!i2p::fs::Exists (i2pcp_crt) || !i2p::fs::Exists (i2pcp_key)) {
LogPrint (eLogInfo, "I2PControl: Creating new certificate for control connection");
CreateCertificate (i2pcp_crt.c_str(), i2pcp_key.c_str());
}
else
} else {
LogPrint(eLogDebug, "I2PControl: Using cert from ", i2pcp_crt);
m_SSLContext.set_options (boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::no_sslv2 | boost::asio::ssl::context::single_dh_use);
boost::system::error_code ec;
m_SSLContext.use_certificate_file (i2pcp_crt, boost::asio::ssl::context::pem, ec);
if (!ec)
m_SSLContext.use_private_key_file (i2pcp_key, boost::asio::ssl::context::pem, ec);
if (ec)
{
LogPrint (eLogInfo, "I2PControl: Failed to load ceritifcate: ", ec.message (), ". Recreating");
CreateCertificate (i2pcp_crt.c_str(), i2pcp_key.c_str());
m_SSLContext.use_certificate_file (i2pcp_crt, boost::asio::ssl::context::pem, ec);
if (!ec)
m_SSLContext.use_private_key_file (i2pcp_key, boost::asio::ssl::context::pem, ec);
if (ec)
// give up
LogPrint (eLogError, "I2PControl: Can't load certificates");
}
m_SSLContext.set_options (boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::no_sslv2 | boost::asio::ssl::context::single_dh_use);
m_SSLContext.use_certificate_file (i2pcp_crt, boost::asio::ssl::context::pem);
m_SSLContext.use_private_key_file (i2pcp_key, boost::asio::ssl::context::pem);
// handlers
m_MethodHandlers["Authenticate"] = &I2PControlService::AuthenticateHandler;
@ -110,7 +86,7 @@ namespace client
{
Accept ();
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 +95,12 @@ namespace client
if (m_IsRunning)
{
m_IsRunning = false;
if (m_Acceptor) 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_Acceptor.cancel ();
m_Service.stop ();
if (m_Thread)
{
m_Thread->join ();
delete m_Thread;
m_Thread = nullptr;
}
}
@ -153,60 +122,40 @@ namespace client
void I2PControlService::Accept ()
{
if (m_Acceptor)
{
auto newSocket = std::make_shared<boost::asio::ssl::stream<boost::asio::ip::tcp::socket> > (m_Service, m_SSLContext);
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
auto newSocket = std::make_shared<ssl_socket> (m_Service, m_SSLContext);
m_Acceptor.async_accept (newSocket->lowest_layer(), std::bind (&I2PControlService::HandleAccept, this,
std::placeholders::_1, newSocket));
}
template<typename ssl_socket>
void I2PControlService::HandleAccepted (const boost::system::error_code& ecode,
std::shared_ptr<ssl_socket> newSocket)
void I2PControlService::HandleAccept(const boost::system::error_code& ecode, std::shared_ptr<ssl_socket> socket)
{
if (ecode != boost::asio::error::operation_aborted)
Accept ();
if (ecode)
{
if (ecode) {
LogPrint (eLogError, "I2PControl: Accept error: ", ecode.message ());
return;
}
LogPrint (eLogDebug, "I2PControl: New request from ", newSocket->lowest_layer ().remote_endpoint ());
Handshake (newSocket);
LogPrint (eLogDebug, "I2PControl: New request from ", socket->lowest_layer ().remote_endpoint ());
Handshake (socket);
}
template<typename ssl_socket>
void I2PControlService::Handshake (std::shared_ptr<ssl_socket> socket)
{
socket->async_handshake(boost::asio::ssl::stream_base::server,
[this, socket](const boost::system::error_code& ecode)
{
if (ecode)
std::bind( &I2PControlService::HandleHandshake, this, std::placeholders::_1, socket));
}
void I2PControlService::HandleHandshake (const boost::system::error_code& ecode, std::shared_ptr<ssl_socket> socket)
{
if (ecode) {
LogPrint (eLogError, "I2PControl: Handshake error: ", ecode.message ());
return;
}
//std::this_thread::sleep_for (std::chrono::milliseconds(5));
ReadRequest (socket);
});
}
template<typename ssl_socket>
void I2PControlService::ReadRequest (std::shared_ptr<ssl_socket> socket)
{
auto request = std::make_shared<I2PControlBuffer>();
@ -216,13 +165,10 @@ namespace client
#else
boost::asio::buffer (request->data (), request->size ()),
#endif
[this, socket, request](const boost::system::error_code& ecode, size_t bytes_transferred)
{
HandleRequestReceived (ecode, bytes_transferred, socket, request);
});
std::bind(&I2PControlService::HandleRequestReceived, this,
std::placeholders::_1, std::placeholders::_2, socket, request));
}
template<typename ssl_socket>
void I2PControlService::HandleRequestReceived (const boost::system::error_code& ecode,
size_t bytes_transferred, std::shared_ptr<ssl_socket> socket,
std::shared_ptr<I2PControlBuffer> buf)
@ -300,7 +246,6 @@ namespace client
}
}
template<typename ssl_socket>
void I2PControlService::SendResponse (std::shared_ptr<ssl_socket> socket,
std::shared_ptr<I2PControlBuffer> buf, std::ostringstream& response, bool isHtml)
{
@ -310,12 +255,12 @@ namespace client
std::ostringstream header;
header << "HTTP/1.1 200 OK\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 << "Date: ";
std::time_t t = std::time (nullptr);
std::tm tm = *std::gmtime (&t);
header << std::put_time(&tm, "%a, %d %b %Y %T GMT") << "\r\n";
auto facet = new boost::local_time::local_time_facet ("%a, %d %b %Y %H:%M:%S GMT");
header.imbue(std::locale (header.getloc(), facet));
header << boost::posix_time::second_clock::local_time() << "\r\n";
header << "\r\n";
offset = header.str ().size ();
memcpy (buf->data (), header.str ().c_str (), offset);
@ -323,11 +268,16 @@ namespace client
memcpy (buf->data () + offset, response.str ().c_str (), len);
boost::asio::async_write (*socket, boost::asio::buffer (buf->data (), offset + len),
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 ());
});
}
}
// handlers
@ -437,22 +387,14 @@ namespace client
void I2PControlService::CreateCertificate (const char *crt_path, const char *key_path)
{
FILE *f = NULL;
#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0
EVP_PKEY * pkey = EVP_RSA_gen(4096); // e = 65537
#else
EVP_PKEY * pkey = EVP_PKEY_new ();
RSA * rsa = RSA_new ();
BIGNUM * e = BN_dup (i2p::crypto::GetRSAE ());
RSA_generate_key_ex (rsa, 4096, e, NULL);
BN_free (e);
if (rsa) EVP_PKEY_assign_RSA (pkey, rsa);
else
if (rsa)
{
LogPrint (eLogError, "I2PControl: Can't create RSA key for certificate");
EVP_PKEY_free (pkey);
return;
}
#endif
EVP_PKEY_assign_RSA (pkey, rsa);
X509 * x509 = X509_new ();
ASN1_INTEGER_set (X509_get_serialNumber (x509), 1);
X509_gmtime_adj (X509_getm_notBefore (x509), 0);
@ -463,28 +405,30 @@ namespace client
X509_NAME_add_entry_by_txt (name, "O", MBSTRING_ASC, (unsigned char *)I2P_CONTROL_CERTIFICATE_ORGANIZATION, -1, -1, 0); // organization
X509_NAME_add_entry_by_txt (name, "CN", MBSTRING_ASC, (unsigned char *)I2P_CONTROL_CERTIFICATE_COMMON_NAME, -1, -1, 0); // common name
X509_set_issuer_name (x509, name); // set issuer to ourselves
X509_sign (x509, pkey, EVP_sha1 ()); // sign, last param must be NULL for EdDSA
X509_sign (x509, pkey, EVP_sha1 ()); // sign
// save cert
if ((f = fopen (crt_path, "wb")) != NULL)
{
if ((f = fopen (crt_path, "wb")) != NULL) {
LogPrint (eLogInfo, "I2PControl: Saving new cert to ", crt_path);
PEM_write_X509 (f, x509);
fclose (f);
}
else
} else {
LogPrint (eLogError, "I2PControl: Can't write cert: ", strerror(errno));
X509_free (x509);
}
// save key
if ((f = fopen (key_path, "wb")) != NULL)
{
if ((f = fopen (key_path, "wb")) != NULL) {
LogPrint (eLogInfo, "I2PControl: saving cert key to ", key_path);
PEM_write_PrivateKey (f, pkey, NULL, NULL, 0, NULL, NULL);
fclose (f);
}
else
} else {
LogPrint (eLogError, "I2PControl: Can't write key: ", strerror(errno));
}
X509_free (x509);
} else {
LogPrint (eLogError, "I2PControl: Can't create RSA key for certificate");
}
EVP_PKEY_free (pkey);
}
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2025, The PurpleI2P Project
* Copyright (c) 2013-2022, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -35,6 +35,8 @@ namespace client
class I2PControlService: public I2PControlHandlers
{
typedef boost::asio::ssl::stream<boost::asio::ip::tcp::socket> ssl_socket;
public:
I2PControlService (const std::string& address, int port);
@ -47,18 +49,16 @@ namespace client
void Run ();
void Accept ();
template<typename ssl_socket>
void HandleAccepted (const boost::system::error_code& ecode, std::shared_ptr<ssl_socket> newSocket);
template<typename ssl_socket>
void HandleAccept(const boost::system::error_code& ecode, 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);
template<typename ssl_socket>
void HandleRequestReceived (const boost::system::error_code& ecode, size_t bytes_transferred,
std::shared_ptr<ssl_socket> socket, std::shared_ptr<I2PControlBuffer> buf);
template<typename ssl_socket>
void SendResponse (std::shared_ptr<ssl_socket> socket,
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);
@ -86,13 +86,10 @@ namespace client
std::string m_Password;
bool m_IsRunning;
std::unique_ptr<std::thread> m_Thread;
std::thread * m_Thread;
boost::asio::io_context m_Service;
std::unique_ptr<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::io_service m_Service;
boost::asio::ip::tcp::acceptor m_Acceptor;
boost::asio::ssl::context m_SSLContext;
boost::asio::deadline_timer m_ShutdownTimer;
std::set<std::string> m_Tokens;

View file

@ -52,7 +52,7 @@ namespace transport
{
m_IsRunning = true;
LogPrint(eLogInfo, "UPnP: Starting");
boost::asio::post (m_Service, std::bind (&UPnP::Discover, this));
m_Service.post (std::bind (&UPnP::Discover, this));
std::unique_lock<std::mutex> l(m_StartedMutex);
m_Thread.reset (new std::thread (std::bind (&UPnP::Run, this)));
m_Started.wait_for (l, std::chrono::seconds (5)); // 5 seconds maximum
@ -150,7 +150,7 @@ namespace transport
// UPnP discovered
LogPrint (eLogDebug, "UPnP: ExternalIPAddress is ", m_externalIPAddress);
i2p::context.UpdateAddress (boost::asio::ip::make_address (m_externalIPAddress));
i2p::context.UpdateAddress (boost::asio::ip::address::from_string (m_externalIPAddress));
// port mapping
PortMapping ();
}

View file

@ -67,7 +67,7 @@ namespace transport
std::unique_ptr<std::thread> m_Thread;
std::condition_variable m_Started;
std::mutex m_StartedMutex;
boost::asio::io_context m_Service;
boost::asio::io_service m_Service;
boost::asio::deadline_timer m_Timer;
bool m_upnpUrlsInitialized = false;
struct UPNPUrls m_upnpUrls;

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2024, The PurpleI2P Project
* Copyright (c) 2013-2020, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -25,7 +25,6 @@
#include "RouterContext.h"
#include "ClientContext.h"
#include "Transports.h"
#include "util.h"
void handle_signal(int sig)
{
@ -221,7 +220,6 @@ namespace i2p
void DaemonLinux::run ()
{
i2p::util::SetThreadName ("i2pd-daemon");
while (running)
{
std::this_thread::sleep_for (std::chrono::seconds(1));

24
debian/changelog vendored
View file

@ -1,27 +1,3 @@
i2pd (2.57.0-1) unstable; urgency=medium
* updated to version 2.57.0/0.9.66
-- orignal <orignal@i2pmail.org> Mon, 02 Jun 2025 16:00:00 +0000
i2pd (2.56.0-1) unstable; urgency=medium
* updated to version 2.56.0/0.9.65
-- orignal <orignal@i2pmail.org> Tue, 11 Feb 2025 16:00:00 +0000
i2pd (2.55.0-1) unstable; urgency=medium
* updated to version 2.55.0
-- orignal <orignal@i2pmail.org> Mon, 30 Dec 2024 16:00:00 +0000
i2pd (2.54.0-1) unstable; urgency=medium
* updated to version 2.54.0/0.9.64
-- orignal <orignal@i2pmail.org> Sun, 6 Oct 2024 16:00:00 +0000
i2pd (2.53.1-1) unstable; urgency=medium
* updated to version 2.53.1

View file

@ -2,13 +2,13 @@ Description: Enable UPnP usage in package
Author: r4sas <r4sas@i2pmail.org>
Reviewed-By: r4sas <r4sas@i2pmail.org>
Last-Update: 2024-12-30
Last-Update: 2022-03-23
--- i2pd.orig/Makefile
+++ i2pd/Makefile
@@ -31,7 +31,7 @@ # import source files lists
include filelist.mk
@@ -31,7 +31,7 @@ include filelist.mk
USE_AESNI := $(or $(USE_AESNI),yes)
USE_STATIC := $(or $(USE_STATIC),no)
-USE_UPNP := $(or $(USE_UPNP),no)
+USE_UPNP := $(or $(USE_UPNP),yes)

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
*
@ -29,10 +29,7 @@ namespace afrikaans // language namespace
return n != 1 ? 1 : 0;
}
// Right to Left language?
static bool rtl = false;
static const LocaleStrings strings
static std::map<std::string, std::string> strings
{
{"failed", "Het misluk"},
{"unknown", "onbekend"},
@ -76,7 +73,7 @@ namespace afrikaans // language namespace
std::shared_ptr<const i2p::i18n::Locale> GetLocale()
{
return std::make_shared<i2p::i18n::Locale>(language, rtl, strings, plurals, [] (int n)->int { return plural(n); });
return std::make_shared<i2p::i18n::Locale>(language, strings, plurals, [] (int n)->int { return plural(n); });
}
} // language

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
*
@ -29,10 +29,7 @@ namespace armenian // language namespace
return n != 1 ? 1 : 0;
}
// Right to Left language?
static bool rtl = false;
static const LocaleStrings strings
static std::map<std::string, std::string> strings
{
{"%.2f KiB", "%.2f ԿիԲ"},
{"%.2f MiB", "%.2f ՄիԲ"},
@ -199,7 +196,7 @@ namespace armenian // language namespace
std::shared_ptr<const i2p::i18n::Locale> GetLocale()
{
return std::make_shared<i2p::i18n::Locale>(language, rtl, strings, plurals, [] (int n)->int { return plural(n); });
return std::make_shared<i2p::i18n::Locale>(language, strings, plurals, [] (int n)->int { return plural(n); });
}
} // language

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
*
@ -29,10 +29,7 @@ namespace chinese // language namespace
return 0;
}
// Right to Left language?
static bool rtl = false;
static const LocaleStrings strings
static std::map<std::string, std::string> strings
{
{"%.2f KiB", "%.2f KiB"},
{"%.2f MiB", "%.2f MiB"},
@ -67,7 +64,7 @@ namespace chinese // language namespace
{"Full cone NAT", "全锥型NAT"},
{"No Descriptors", "无描述符"},
{"Uptime", "运行时间"},
{"Network status", "网络状态"},
{"Network status", "IPv4 网络状态"},
{"Network status v6", "IPv6 网络状态"},
{"Stopping in", "距停止还有:"},
{"Family", "家族"},
@ -218,7 +215,7 @@ namespace chinese // language namespace
std::shared_ptr<const i2p::i18n::Locale> GetLocale()
{
return std::make_shared<i2p::i18n::Locale>(language, rtl, strings, plurals, [] (int n)->int { return plural(n); });
return std::make_shared<i2p::i18n::Locale>(language, strings, plurals, [] (int n)->int { return plural(n); });
}
} // language

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
*
@ -29,10 +29,7 @@ namespace czech // language namespace
return (n == 1) ? 0 : (n >= 2 && n <= 4) ? 1 : 2;
}
// Right to Left language?
static bool rtl = false;
static const LocaleStrings strings
static std::map<std::string, std::string> strings
{
{"%.2f KiB", "%.2f KiB"},
{"%.2f MiB", "%.2f MiB"},
@ -218,7 +215,7 @@ namespace czech // language namespace
std::shared_ptr<const i2p::i18n::Locale> GetLocale()
{
return std::make_shared<i2p::i18n::Locale>(language, rtl, strings, plurals, [] (int n)->int { return plural(n); });
return std::make_shared<i2p::i18n::Locale>(language, strings, plurals, [] (int n)->int { return plural(n); });
}
} // language

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
*
@ -30,10 +30,7 @@ namespace english // language namespace
return n != 1 ? 1 : 0;
}
// Right to Left language?
static bool rtl = false;
static const LocaleStrings strings
static std::map<std::string, std::string> strings
{
{"", ""},
};
@ -45,7 +42,7 @@ namespace english // language namespace
std::shared_ptr<const i2p::i18n::Locale> GetLocale()
{
return std::make_shared<i2p::i18n::Locale>(language, rtl, strings, plurals, [] (int n)->int { return plural(n); });
return std::make_shared<i2p::i18n::Locale>(language, strings, plurals, [] (int n)->int { return plural(n); });
}
} // language

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
*
@ -29,10 +29,7 @@ namespace french // language namespace
return n != 1 ? 1 : 0;
}
// Right to Left language?
static bool rtl = false;
static const LocaleStrings strings
static std::map<std::string, std::string> strings
{
{"%.2f KiB", "%.2f Kio"},
{"%.2f MiB", "%.2f Mio"},
@ -47,7 +44,7 @@ namespace french // language namespace
{"<b>i2pd</b> webconsole", "Console web <b>i2pd</b>"},
{"Main page", "Page principale"},
{"Router commands", "Commandes du routeur"},
{"Local Destinations", "Destinatioans localeAlger"},
{"Local Destinations", "Destinations locales"},
{"LeaseSets", "Jeu de baux"},
{"Tunnels", "Tunnels"},
{"Transit Tunnels", "Tunnels transitoires"},
@ -218,7 +215,7 @@ namespace french // language namespace
std::shared_ptr<const i2p::i18n::Locale> GetLocale()
{
return std::make_shared<i2p::i18n::Locale>(language, rtl, strings, plurals, [] (int n)->int { return plural(n); });
return std::make_shared<i2p::i18n::Locale>(language, strings, plurals, [] (int n)->int { return plural(n); });
}
} // language

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
*
@ -29,10 +29,7 @@ namespace german // language namespace
return n != 1 ? 1 : 0;
}
// Right to Left language?
static bool rtl = false;
static const LocaleStrings strings
static std::map<std::string, std::string> strings
{
{"%.2f KiB", "%.2f KiB"},
{"%.2f MiB", "%.2f MiB"},
@ -213,7 +210,7 @@ namespace german // language namespace
std::shared_ptr<const i2p::i18n::Locale> GetLocale()
{
return std::make_shared<i2p::i18n::Locale>(language, rtl, strings, plurals, [] (int n)->int { return plural(n); });
return std::make_shared<i2p::i18n::Locale>(language, strings, plurals, [] (int n)->int { return plural(n); });
}
} // language

View file

@ -1,202 +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 <map>
#include <vector>
#include <string>
#include <memory>
#include "I18N.h"
// Hebrew localization file
namespace i2p
{
namespace i18n
{
namespace hebrew // language namespace
{
// language name in lowercase
static std::string language = "hebrew";
// See for language plural forms here:
// https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html
static int plural (int n) {
return n % 100 == 1 ? 0 : n % 100 == 2 ? 1 : n % 100 == 3 || n % 100 == 4 ? 2 : 3;
}
// Right to Left language?
static bool rtl = true;
static const LocaleStrings strings
{
{"%.2f KiB", "%.2f קי״ב"},
{"%.2f MiB", "%.2f מי״ב"},
{"%.2f GiB", "%.2f קי״ב"},
{"Purple I2P Webconsole", "קונסולת Purple I2P"},
{"<b>i2pd</b> webconsole", "קונסולת <b>i2pd</b>"},
{"Main page", "עמוד ראשי"},
{"Router commands", "פקודות נתב"},
{"Local Destinations", "יעדים מקומיים"},
{"Tunnels", "מנהרות"},
{"Transit Tunnels", "מנהרות מעבר"},
{"Transports", "מובילים"},
{"I2P tunnels", "מנהרות I2P"},
{"SAM sessions", "הפעלות SAM"},
{"Unknown", "לא מוכר"},
{"Proxy", "פרוקסי"},
{"Mesh", "סיבוך"},
{"Clock skew", "לכסון שעון"},
{"Offline", "לא מקוון"},
{"Symmetric NAT", "NAT סימטרי"},
{"Full cone NAT", "NAT חסום לחלוטין"},
{"No Descriptors", "אין מתארים"},
{"Uptime", "זמן הפעלה"},
{"Network status", "מצב רשת תקשורת"},
{"Network status v6", "מצב רשת תקשורת v6"},
{"Stopping in", "מפסיק בעוד"},
{"Family", "משפחה"},
{"Tunnel creation success rate", "שיעור הצלחה של יצירת מנהרות"},
{"Total tunnel creation success rate", "שיעור הצלחה כולל של יצירת מנהרות"},
{"Received", "נתקבל"},
{"%.2f KiB/s", "%.2f קי״ב/ש"},
{"Sent", "נשלח"},
{"Transit", "מעבר"},
{"Data path", "נתיב מידע"},
{"Hidden content. Press on text to see.", "תוכן מוסתר. לחץ על הטקסט כדי לראותו."},
{"Router Ident", "מזהה נתב"},
{"Router Family", "משפחת נתב"},
{"Version", "גרסא"},
{"Our external address", "הכתובת החיצונית שלנו"},
{"supported", "נתמך"},
{"Routers", "נתבים"},
{"Client Tunnels", "מנהרות לקוח"},
{"Services", "שירותים"},
{"Enabled", "מאופשר"},
{"Disabled", "מנוטרל"},
{"Encrypted B33 address", "כתובת B33 מוצפנת"},
{"Address registration line", "שורת רישום כתובת"},
{"Domain", "תחום"},
{"Generate", "צור"},
{"<b>Note:</b> result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", "<b>הערה</b> מחרוזת תוצאה יכולה להיות מועילה רק לצורך רישום תחומים 2LD (example.i2p). לשם רישום תתי-תחום עליך להיוועץ עם i2pd-tools."},
{"Address", "כתובת"},
{"Type", "טיפוס"},
{"Expire LeaseSet", "פקיעת LeaseSet"},
{"Inbound tunnels", "מנהרות פנימיות"},
{"%dms", "מילישניות %d"},
{"Outbound tunnels", "מנהרות חיצוניות"},
{"Tags", "תוויות"},
{"Incoming", "נכנס"},
{"Outgoing", "יוצא"},
{"Destination", "יעד"},
{"Amount", "כמות"},
{"Incoming Tags", "תוויות נכנסות"},
{"Tags sessions", "הפעלות תוויות"},
{"Status", "מצב"},
{"Local Destination", "יעד מקומי"},
{"Streams", "זרמים"},
{"Close stream", "סגור זרם"},
{"Such destination is not found", "יעד כזה לא נמצא"},
{"I2CP session not found", "הפעלת I2CP לא נמצאה"},
{"I2CP is not enabled", "I2CP לא מאופשר"},
{"Invalid", "לא תקין"},
{"Store type", "טיפוס אחסון"},
{"Expires", "פוקע"},
{"Non Expired Leases", "חכירות בלתי פקיעות"},
{"Gateway", "שער-דרך"},
{"TunnelID", "מזהה מנהרה"},
{"EndDate", "תאריך סיום"},
{"floodfill mode is disabled", "מצב floodfill הינו מנוטרל"},
{"Queue size", "גודל תור"},
{"Run peer test", "הרץ בדיקת עמית"},
{"Reload tunnels configuration", "טען מחדש תצורת מנהרות"},
{"Decline transit tunnels", "דחה מנהרות מעבר"},
{"Accept transit tunnels", "קבל מנהרות מעבר"},
{"Cancel graceful shutdown", "בטל כיבוי עדין"},
{"Start graceful shutdown", "התחל כיבוי עדין"},
{"Force shutdown", "כפה כיבוי"},
{"Reload external CSS styles", "טען מחדש סגנונות CSS חיצוניים"},
{"<b>Note:</b> any action done here are not persistent and not changes your config files.", "<b>הערה</b> כל פעולה אשר מבוצעת כאן אינה המשכית ולא משנה את קובצי התצורה שלך."},
{"Logging level", "דרגת רישום יומן"},
{"Transit tunnels limit", "מגבלת מנהרות מעבר"},
{"Change", "שנה"},
{"Change language", "שנה שפה"},
{"no transit tunnels currently built", "אין מנהרות מעבר אשר בנויות כעת"},
{"SAM disabled", "SAM מנוטרל"},
{"no sessions currently running", "אין הפעלה אשר מורצת כעת"},
{"SAM session not found", "הפעלת SAM לא נמצאה"},
{"SAM Session", "הפעלת SAM"},
{"Server Tunnels", "מנהרות שרת"},
{"Unknown page", "עמוד לא מוכר"},
{"Invalid token", "סימן לא תקין"},
{"SUCCESS", "הצלחה"},
{"Stream closed", "זרם סגור"},
{"Stream not found or already was closed", "זרם לא נמצא או שהוא היה כבר סגור"},
{"Destination not found", "יעד לא נמצא"},
{"StreamID can't be null", "מזהה זרם (StreamID) לא יכול להיות אפסי"},
{"Return to destination page", "חזור לעמוד יעד"},
{"You will be redirected in %d seconds", "אתה תכוון מחדש בעוד %d שניות"},
{"LeaseSet expiration time updated", "זמן פקיעה של LeaseSet עודכן"},
{"LeaseSet is not found or already expired", "LeaseSet אינו נמצא או שהוא כבר פקע"},
{"Transit tunnels count must not exceed %d", "אסור לספירת מנהרות מעבר לעלות על %d"},
{"Back to commands list", "חזור לרשימת פקודות"},
{"Register at reg.i2p", "הירשם באתר reg.i2p"},
{"Description", "תיאור"},
{"A bit information about service on domain", "מידע אודות שירות על תחום"},
{"Submit", "שלח"},
{"Domain can't end with .b32.i2p", "תחום לא יכול להסתיים עם &#8206;.b32.i2p"},
{"Domain must end with .i2p", "תחום חייב להסתיים עם &#8206;.i2p"},
{"Unknown command", "פקודה לא מוכרת"},
{"Command accepted", "פקודה נתקבלה"},
{"Proxy error", "שגיאת פרוקסי"},
{"Proxy info", "מידע פרוקסי"},
{"Proxy error: Host not found", "שגיאת פרוקסי: מארח לא נמצא"},
{"Remote host not found in router's addressbook", "ארח מרוחק לא נמצא בתוך הפנקס כתובות של הנתב"},
{"You may try to find this host on jump services below", "באפשרותך לנסות למצוא את מארח זה דרך שירותי קפיצה להלן"},
{"Invalid request", "בקשה לא תקינה"},
{"Proxy unable to parse your request", "פרוקסי לא מסוגל לנתח את בקשתך"},
{"Addresshelper is not supported", "סייען-כתובות אינו נתמך"},
{"Host %s is <font color=red>already in router's addressbook</font>. <b>Be careful: source of this URL may be harmful!</b> Click here to update record: <a href=\"%s%s%s&update=true\">Continue</a>.", "מארח %s is <font color=red>כבר נמצא בפנקס כתובות של הנתב</font>. <b>זהירות: מקור URL זה עלול להזיק!</b> לחץ כאן כדי לעדכן מרשם: <a href=\"%s%s%s&update=true\">המשך</a>."},
{"Addresshelper forced update rejected", "אילוץ עדכון של סייען-כתובות נדחה"},
{"To add host <b>%s</b> in router's addressbook, click here: <a href=\"%s%s%s\">Continue</a>.", "Tכדי להוסיף את מארח <b>%s</b> לפנקס כתובות של הנתב: <a href=\"%s%s%s\">המשך</a>."},
{"Addresshelper request", "בקשת סייען-כתובות"},
{"Host %s added to router's addressbook from helper. Click here to proceed: <a href=\"%s\">Continue</a>.", "מארח %s נתווסף לסייען-כתובות של הנתב דרך סייען. לחץ כאן כדי proceed: <a href=\"%s\">המשך</a>."},
{"Addresshelper adding", "הוספת סייען-כתובות"},
{"Host %s is <font color=red>already in router's addressbook</font>. Click here to update record: <a href=\"%s%s%s&update=true\">Continue</a>.", "מארח %s <font color=red>כבר נמצא בספר כתובות של הנתב</font>. לחץ כאן כדי לעדכן מרשם: <a href=\"%s%s%s&update=true\">המשך</a>."},
{"Addresshelper update", "עדכון סייען-כתובות"},
{"Invalid request URI", "בקשת URI לא תקינה"},
{"Can't detect destination host from request", "לא יכול לאתר יעד מארח מתוך בקשה"},
{"Host %s is not inside I2P network, but outproxy is not enabled", "מארח %s לא נמצא בתוך רשת I2P, אולם outproxy אינו מאופשר"},
{"Hostname is too long", "שם-מארח הינו ארוך מדי"},
{"Cannot negotiate with SOCKS proxy", "לא מסוגל להסדיר פרוקסי SOCKS"},
{"CONNECT error", "שגיאת חיבור"},
{"Failed to connect", "נכשל להתחבר"},
{"SOCKS proxy error", "שגיאת פרוקסי SOCKS"},
{"No reply from SOCKS proxy", "אין מענה מתוך פרוקסי SOCKS"},
{"Cannot connect", "לא מסוגל להתחבר"},
{"Host is down", "מארח הינו מושבת"},
{"Can't create connection to requested host, it may be down. Please try again later.", "לא יכול ליצור חיבור למארח מבוקש, המארח עשוי להיות מושבת. אנא נסה שוב מאוחר יותר."},
{"", ""},
};
static std::map<std::string, std::vector<std::string>> plurals
{
{"%d days", {"יום %d", "יומיים", "ימים %d", "ימים %d"}},
{"%d hours", {"שעה %d", "שעתיים", "שעות %d", "שעות %d"}},
{"%d minutes", {"דקה %d", "שתי דקות", "דקות %d", "דקות %d"}},
{"%d seconds", {"שניה %d", "שתי שניות", "שניות %d", "שניות %d"}},
{"", {"", "", ""}},
};
std::shared_ptr<const i2p::i18n::Locale> GetLocale()
{
return std::make_shared<i2p::i18n::Locale>(language, rtl, strings, plurals, [] (int n)->int { return plural(n); });
}
} // language
} // i18n
} // i2p

View file

@ -1,226 +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 <map>
#include <vector>
#include <string>
#include <memory>
#include "I18N.h"
// Hindi localization file
namespace i2p
{
namespace i18n
{
namespace hindi // language namespace
{
// language name in lowercase
static std::string language = "hindi";
// See for language plural forms here:
// https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html
static int plural (int n) {
return n != 1 ? 1 : 0;
}
// Right to Left language?
static bool rtl = false;
static const LocaleStrings strings
{
{"%.2f KiB", "%.2f कीबी"},
{"%.2f MiB", "%.2f मीबी"},
{"%.2f GiB", "%.2f जीबी"},
{"building", "निर्माण"},
{"failed", "विफल"},
{"expiring", "समाप्त होना"},
{"established", "स्थापित"},
{"unknown", "अज्ञात"},
{"exploratory", "अन्वेषणात्मक"},
{"Purple I2P Webconsole", "पर्पल I2P वेब कंसोल"},
{"<b>i2pd</b> webconsole", "<b>i2pd</b> वेब कंसोल"},
{"Main page", "मुख्य पृष्ठ"},
{"Router commands", "राउटर आदेश"},
{"Local Destinations", "स्थानीय गंतव्य"},
{"LeaseSets", "पट्ट समुच्चय"},
{"Tunnels", "सुरंग"},
{"Transit Tunnels", "संचरण सुरंगें"},
{"Transports", "परिवहन"},
{"I2P tunnels", "I2P सुरंगें"},
{"SAM sessions", "SAM सत्र"},
{"ERROR", "त्रुटि"},
{"OK", "ठीक है"},
{"Testing", "परीक्षण"},
{"Firewalled", "फायरवॉल"},
{"Unknown", "अज्ञात"},
{"Proxy", "प्रॉक्सी"},
{"Mesh", "जाली"},
{"Clock skew", "घड़ी संकेत विचलन"},
{"Offline", "ऑफलाइन"},
{"Symmetric NAT", "सममितीय NAT"},
{"Full cone NAT", "पूर्णकोण NAT"},
{"No Descriptors", "कोई वर्णनकर्त्तृ नहीं हैं"},
{"Uptime", "संचालन समय"},
{"Network status", "संपर्क स्थिति"},
{"Network status v6", "संपर्क स्थिति v6"},
{"Stopping in", "में अवसान प्रारंभ हो रहा है"},
{"Family", "परिवार"},
{"Tunnel creation success rate", "सुरंग निर्माण सफलता दर"},
{"Total tunnel creation success rate", "कुल सुरंग निर्माण सफलता दर"},
{"Received", "प्राप्त हुआ"},
{"%.2f KiB/s", "%.2f कीबी/से"},
{"Sent", "प्रेषित"},
{"Transit", "संचरण"},
{"Data path", "डेटा पथ"},
{"Hidden content. Press on text to see.", "सामग्री छिपाई गई है। देखने हेतु पाठ पर दबाएँ।"},
{"Router Ident", "राउटर परिचय"},
{"Router Family", "राउटर परिवार"},
{"Router Caps", "राउटर कैप्स"},
{"Version", "संस्करण"},
{"Our external address", "हमारा बाह्य पता"},
{"supported", "समर्थित"},
{"Routers", "राउटर"},
{"Floodfills", "पूर्णक संवाहक"},
{"Client Tunnels", "क्लाइंट सुरंगें"},
{"Services", "सेवाएँ"},
{"Enabled", "सक्षम है"},
{"Disabled", "निष्क्रिय है"},
{"Encrypted B33 address", "कूटलिखित B33 पता"},
{"Address registration line", "पता पंजीकरण पंक्ति"},
{"Domain", "डोमेन"},
{"Generate", "सृजित करें"},
{"<b>Note:</b> result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", "<b>नोट:</b> परिणाम स्ट्रिंग का उपयोग केवल 2LD डोमेनों (जैसे example.i2p) को रजिस्टर करने के लिए किया जा सकता है। सबडोमेन रजिस्टर करने के लिए कृपया i2pd-tools का उपयोग करें।"},
{"Address", "पता"},
{"Type", "प्रकार"},
{"EncType", "कूट प्रकार"},
{"Expire LeaseSet", "पट्ट समुच्चय का अवसान करें"},
{"Inbound tunnels", "आगमनशील सुरंगें"},
{"%dms", "%dms"},
{"Outbound tunnels", "प्रस्थानशील सुरंगें"},
{"Tags", "चिन्हित"},
{"Incoming", "आगामी"},
{"Outgoing", "निर्गामी"},
{"Destination", "गंतव्य"},
{"Amount", "मात्रा"},
{"Incoming Tags", "आगामी चिन्हित"},
{"Tags sessions", "चिन्हित सत्र OR सत्र को चिन्हित करें"},
{"Status", "स्थिति"},
{"Local Destination", "स्थानीय गंतव्य"},
{"Streams", "धाराएँ"},
{"Close stream", "प्रवाह समाप्त करें"},
{"Such destination is not found", "ऐसा गंतव्य नहीं मिला"},
{"I2CP session not found", "I2CP सत्र नहीं मिला"},
{"I2CP is not enabled", "I2CP निष्क्रिय है"},
{"Invalid", "अमान्य"},
{"Store type", "भण्डारगार का प्रकार"},
{"Expires", "अवसान होता है"},
{"Non Expired Leases", "अनवसित पट्ट"},
{"Gateway", "प्रवेशद्वार"},
{"TunnelID", "सुरंग ID"},
{"EndDate", "समाप्ति तिथि"},
{"floodfill mode is disabled", "पूर्णक संवाहक विधि निष्क्रिय है"},
{"Queue size", "क्यू आकार"},
{"Run peer test", "सहकर्मी परीक्षण चलाएँ"},
{"Reload tunnels configuration", "सुरंग विन्यास पुनः लोड करें"},
{"Decline transit tunnels", "संचरण सुरंगों को अस्वीकार करें"},
{"Accept transit tunnels", "संचरण सुरंगों को स्वीकार करें"},
{"Cancel graceful shutdown", "सौम्य अवसान निरस्त करें"},
{"Start graceful shutdown", "सौम्य समापन प्रारंभ करें"},
{"Force shutdown", "बाध्य अवसान"},
{"Reload external CSS styles", "बाह्य CSS शैलियों को पुनः लोड करें"},
{"<b>Note:</b> any action done here are not persistent and not changes your config files.", "<b>टिप्पणी:</b> यहाँ किए गए कोई भी क्रियाएँ स्थायी नहीं हैं और आपके विन्यास संचिका में कोई परिवर्तन नहीं करतीं।"},
{"Logging level", "लॉगिंग स्तर"},
{"Transit tunnels limit", "संचरण सुरंगों की सीमा"},
{"Change", "बदलना"},
{"Change language", "भाषा बदलें"},
{"no transit tunnels currently built", "संचरण सुरंगों का निर्माण नहीं हुआ है"},
{"SAM disabled", "SAM निष्क्रिय है"},
{"no sessions currently running", "वर्तमान में कोई सत्र सक्रिय नहीं है"},
{"SAM session not found", "SAM सत्र नहीं मिला"},
{"SAM Session", "SAM सत्र"},
{"Server Tunnels", "सर्वर सुरंग"},
{"Client Forwards", "क्लाइंट फॉरवर्ड्स"},
{"Server Forwards", "सर्वर फॉरवर्ड्स"},
{"Unknown page", "अज्ञात पृष्ठ"},
{"Invalid token", "अमान्य टोकन"},
{"SUCCESS", "सफलता"},
{"Stream closed", "प्रवाह समाप्त हो गया है"},
{"Stream not found or already was closed", "प्रवाह प्राप्त नहीं हुआ अथवा इसका पूर्व में ही समापन हो चुका है"},
{"Destination not found", "गंतव्य नहीं मिला"},
{"StreamID can't be null", "प्रवाह ID शून्य नहीं हो सकता है"},
{"Return to destination page", "गंतव्य पृष्ठ पर पुनः वापस जाएँ"},
{"You will be redirected in %d seconds", "आपको %d सेकंड में पुनर्निर्देशित किया जाएगा"},
{"LeaseSet expiration time updated", "पट्ट समुच्चय की अवसान समय को अद्यतित किया गया है"},
{"LeaseSet is not found or already expired", "पट्ट समुच्चय प्राप्त नहीं हुआ या इसका पूर्वमेव अवसान हो चुका है"},
{"Transit tunnels count must not exceed %d", "संचरण सुरंगों की संख्या %d से अधिक नहीं होनी चाहिए"},
{"Back to commands list", "आदेश सूची पर पुनः लौटें"},
{"Register at reg.i2p", "reg.i2p पर पंजीकरण करें"},
{"Description", "विवरण"},
{"A bit information about service on domain", "डोमेन पर सेवा से संबंधित थोड़ी जानकारी"},
{"Submit", "प्रस्तुत करें"},
{"Domain can't end with .b32.i2p", "डोमेन का अंत .b32.i2p से नहीं हो सकता"},
{"Domain must end with .i2p", "डोमेन का अंत .i2p से होना आवश्यक है"},
{"Unknown command", "अज्ञात आदेश"},
{"Command accepted", "आदेश स्वीकार किया गया"},
{"Proxy error", "प्रॉक्सी त्रुटि"},
{"Proxy info", "प्रॉक्सी जानकारी"},
{"Proxy error: Host not found", "प्रॉक्सी त्रुटि: होस्ट नहीं मिला"},
{"Remote host not found in router's addressbook", "राउटर की पता पुस्तक में दूरस्थ होस्ट नहीं मिला"},
{"You may try to find this host on jump services below", "आप नीचे दिए गए जंप सेवाओं में इस होस्ट को खोजने की कोशिश कर सकते हैं"},
{"Invalid request", "अमान्य अनुरोध"},
{"Proxy unable to parse your request", "प्रॉक्सी आपके अनुरोध को विश्लेषित करने में असमर्थ है"},
{"Addresshelper is not supported", "Addresshelper समर्थित नहीं है"},
{"Host %s is <font color=red>already in router's addressbook</font>. <b>Be careful: source of this URL may be harmful!</b> Click here to update record: <a href=\"%s%s%s&update=true\">Continue</a>.", "होस्ट %s पहले से ही राउटर की पता-पुस्तिका में <font color=red>उपस्थित है</font>। <b>सावधान रहें: इस URL का स्रोत हानिकारक हो सकता है!</b> अभिलेख को अद्यतन करने हेतु यहाँ क्लिक करें: <a href=\"%s%s%s&update=true\">जारी रखें</a>।"},
{"Addresshelper forced update rejected", "Addresshelper का जबरन अद्यतन अस्वीकृत किया गया"},
{"To add host <b>%s</b> in router's addressbook, click here: <a href=\"%s%s%s\">Continue</a>.", "राउटर की पता-पुस्तिका में होस्ट <b>%s</b> को जोड़ने हेतु, कृपया यहाँ क्लिक करें: <a href=\"%s%s%s\">जारी रखें</a>।"},
{"Addresshelper request", "Addresshelper अनुरोध"},
{"Host %s added to router's addressbook from helper. Click here to proceed: <a href=\"%s\">Continue</a>.", "सहायक से होस्ट %s राउटर की पता-पुस्तिका में जोड़ दिया गया है। आगे बढ़ने हेतु यहाँ क्लिक करें: <a href=\"%s\">जारी रखें</a>।"},
{"Addresshelper adding", "Addresshelper जोड़ना"},
{"Host %s is <font color=red>already in router's addressbook</font>. Click here to update record: <a href=\"%s%s%s&update=true\">Continue</a>.", "होस्ट %s पहले से ही राउटर की पता-पुस्तिका में <font color=red>उपस्थित है</font>। अभिलेख को अद्यतन करने हेतु यहाँ क्लिक करें: <a href=\"%s%s%s&update=true\">जारी रखें</a>।"},
{"Addresshelper update", "Addresshelper अद्यतन करना"},
{"Invalid request URI", "अमान्य अनुरोध URI"},
{"Can't detect destination host from request", "अनुरोध से गंतव्य होस्ट का पता नहीं लगा सकते"},
{"Outproxy failure", "आउटप्रॉक्सी विफलता"},
{"Bad outproxy settings", "गलत आउटप्रॉक्सी सेटिंग्स"},
{"Host %s is not inside I2P network, but outproxy is not enabled", "होस्ट %s I2P नेटवर्क के भीतर नहीं है, लेकिन आउटप्रॉक्सी सक्षम नहीं है"},
{"Unknown outproxy URL", "अज्ञात आउटप्रॉक्सी URL"},
{"Cannot resolve upstream proxy", "ऊर्ध्वधारा प्रॉक्सी का समाधान नहीं किया जा सका"},
{"Hostname is too long", "होस्टनाम अत्यधिक लंबा है"},
{"Cannot connect to upstream SOCKS proxy", "उर्ध्वधारा SOCKS प्रॉक्सी से संपर्क स्थापित नहीं हो पा रहा है"},
{"Cannot negotiate with SOCKS proxy", "SOCKS प्रॉक्सी के साथ समन्वयन स्थापित नहीं किया जा सका"},
{"CONNECT error", "संपर्क त्रुटि"},
{"Failed to connect", "संपर्क स्थापित करने में विफल"},
{"SOCKS proxy error", "SOCKS प्रॉक्सी त्रुटि"},
{"Failed to send request to upstream", "ऊर्ध्ववाहिनी को अनुरोध प्रेषित करने में विफलता हुई"},
{"No reply from SOCKS proxy", "SOCKS प्रॉक्सी से कोई प्रत्युत्तर प्राप्त नहीं हुआ"},
{"Cannot connect", "संपर्क नहीं हो पा रहा है"},
{"HTTP out proxy not implemented", "HTTP आउट प्रॉक्सी कार्यान्वित नहीं किया गया है"},
{"Cannot connect to upstream HTTP proxy", "उर्ध्वधारा HTTP प्रॉक्सी से संपर्क स्थापित नहीं हो पा रहा है"},
{"Host is down", "होस्ट अनुपलब्ध है"},
{"Can't create connection to requested host, it may be down. Please try again later.", "अनुरोधित होस्ट से संपर्क स्थापित नहीं किया जा सका। संभवतः वह सक्रिय नहीं है। कृपया बाद में पुनः प्रयास करें।"},
{"", ""},
};
static std::map<std::string, std::vector<std::string>> plurals
{
{"%d days", {"%d दिन", "%d दिन"}},
{"%d hours", {"%d घंटा", "%dघंटे"}},
{"%d minutes", {"%d मिनट", "%d मिनट"}},
{"%d seconds", {"%d सेकंड", "%d सेकंड"}},
{"", {"", "", ""}},
};
std::shared_ptr<const i2p::i18n::Locale> GetLocale()
{
return std::make_shared<i2p::i18n::Locale>(language, rtl, strings, plurals, [] (int n)->int { return plural(n); });
}
} // language
} // i18n
} // i2p

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
*
@ -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);
}
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);
}

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
*
@ -10,7 +10,6 @@
#define __I18N_H__
#include <string>
#include <string_view>
#include <map>
#include <utility>
#include <functional>
@ -19,17 +18,15 @@ namespace i2p
{
namespace i18n
{
typedef std::map<std::string_view, std::string_view> LocaleStrings;
class Locale
{
public:
Locale (
const std::string& language,
const bool& rtl,
const LocaleStrings& strings,
const std::map<std::string, std::string>& strings,
const std::map<std::string, std::vector<std::string>>& plurals,
std::function<int(int)> formula
): m_Language (language), m_RTL (rtl), m_Strings (strings), m_Plurals (plurals), m_Formula (formula) { };
): m_Language (language), m_Strings (strings), m_Plurals (plurals), m_Formula (formula) { };
// Get activated language name for webconsole
std::string GetLanguage() const
@ -37,12 +34,7 @@ namespace i18n
return m_Language;
}
bool GetRTL() const
{
return m_RTL;
}
std::string_view GetString (std::string_view arg) const
std::string GetString (const std::string& arg) const
{
const auto it = m_Strings.find(arg);
if (it == m_Strings.end())
@ -55,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);
if (it == m_Plurals.end()) // not found, fallback to english
@ -71,15 +63,14 @@ namespace i18n
private:
const std::string m_Language;
const bool m_RTL;
const LocaleStrings m_Strings;
const std::map<std::string, std::string> m_Strings;
const std::map<std::string, std::vector<std::string>> m_Plurals;
std::function<int(int)> m_Formula;
};
void SetLanguage(const std::string &lang);
std::string_view translate (std::string_view arg);
std::string translate (const std::string& arg, const std::string& arg2, int n);
std::string translate (const std::string& arg);
std::string translate (const std::string& arg, const std::string& arg2, const int& n);
} // i18n
} // i2p
@ -88,7 +79,7 @@ namespace i18n
* @param arg String with message
*/
template<typename TValue>
std::string_view tr (TValue&& arg)
std::string tr (TValue&& arg)
{
return i2p::i18n::translate(std::forward<TValue>(arg));
}
@ -101,7 +92,7 @@ std::string_view tr (TValue&& arg)
template<typename TValue, typename... TArgs>
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)...);
std::string str(size, 0);
@ -117,7 +108,7 @@ std::string tr (TValue&& arg, TArgs&&... args)
* @param n Integer, used for selection of form
*/
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));
}
@ -130,7 +121,7 @@ std::string ntr (TValue&& arg, TValue2&& arg2, int n)
* @param args Array of arguments for string formatting
*/
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));

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
*
@ -30,8 +30,6 @@ namespace i18n
namespace english { std::shared_ptr<const i2p::i18n::Locale> GetLocale (); }
namespace french { std::shared_ptr<const i2p::i18n::Locale> GetLocale (); }
namespace german { std::shared_ptr<const i2p::i18n::Locale> GetLocale (); }
namespace hebrew { std::shared_ptr<const i2p::i18n::Locale> GetLocale (); }
namespace hindi { std::shared_ptr<const i2p::i18n::Locale> GetLocale (); }
namespace italian { std::shared_ptr<const i2p::i18n::Locale> GetLocale (); }
namespace polish { std::shared_ptr<const i2p::i18n::Locale> GetLocale (); }
namespace portuguese { std::shared_ptr<const i2p::i18n::Locale> GetLocale (); }
@ -55,8 +53,6 @@ namespace i18n
{ "english", {"English", "en", i2p::i18n::english::GetLocale} },
{ "french", {"Français", "fr", i2p::i18n::french::GetLocale} },
{ "german", {"Deutsch", "de", i2p::i18n::german::GetLocale} },
{ "hebrew", {"עִבְרִית‎", "he", i2p::i18n::hebrew::GetLocale} },
{ "hindi", {"हिन्दी", "hi", i2p::i18n::hindi::GetLocale} },
{ "italian", {"Italiano", "it", i2p::i18n::italian::GetLocale} },
{ "polish", {"Polski", "pl", i2p::i18n::polish::GetLocale} },
{ "portuguese", {"Português", "pt", i2p::i18n::portuguese::GetLocale} },

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
*
@ -29,10 +29,7 @@ namespace italian // language namespace
return n != 1 ? 1 : 0;
}
// Right to Left language?
static bool rtl = false;
static const LocaleStrings strings
static std::map<std::string, std::string> strings
{
{"%.2f KiB", "%.2f KiB"},
{"%.2f MiB", "%.2f MiB"},
@ -218,7 +215,7 @@ namespace italian // language namespace
std::shared_ptr<const i2p::i18n::Locale> GetLocale()
{
return std::make_shared<i2p::i18n::Locale>(language, rtl, strings, plurals, [] (int n)->int { return plural(n); });
return std::make_shared<i2p::i18n::Locale>(language, strings, plurals, [] (int n)->int { return plural(n); });
}
} // language

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
*
@ -29,10 +29,7 @@ namespace polish // language namespace
return (n == 1 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2);
}
// Right to Left language?
static bool rtl = false;
static const LocaleStrings strings
static std::map<std::string, std::string> strings
{
{"%.2f KiB", "%.2f KiB"},
{"%.2f MiB", "%.2f MiB"},
@ -218,7 +215,7 @@ namespace polish // language namespace
std::shared_ptr<const i2p::i18n::Locale> GetLocale()
{
return std::make_shared<i2p::i18n::Locale>(language, rtl, strings, plurals, [] (int n)->int { return plural(n); });
return std::make_shared<i2p::i18n::Locale>(language, strings, plurals, [] (int n)->int { return plural(n); });
}
} // language

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
*
@ -29,10 +29,7 @@ namespace portuguese // language namespace
return n != 1 ? 1 : 0;
}
// Right to Left language?
static bool rtl = false;
static const LocaleStrings strings
static std::map<std::string, std::string> strings
{
{"%.2f KiB", "%.2f KiB"},
{"%.2f MiB", "%.2f MiB"},
@ -218,7 +215,7 @@ namespace portuguese // language namespace
std::shared_ptr<const i2p::i18n::Locale> GetLocale()
{
return std::make_shared<i2p::i18n::Locale>(language, rtl, strings, plurals, [] (int n)->int { return plural(n); });
return std::make_shared<i2p::i18n::Locale>(language, strings, plurals, [] (int n)->int { return plural(n); });
}
} // language

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
*
@ -29,10 +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;
}
// Right to Left language?
static bool rtl = false;
static const LocaleStrings strings
static std::map<std::string, std::string> strings
{
{"%.2f KiB", "%.2f КиБ"},
{"%.2f MiB", "%.2f МиБ"},
@ -218,7 +215,7 @@ namespace russian // language namespace
std::shared_ptr<const i2p::i18n::Locale> GetLocale()
{
return std::make_shared<i2p::i18n::Locale>(language, rtl, strings, plurals, [] (int n)->int { return plural(n); });
return std::make_shared<i2p::i18n::Locale>(language, strings, plurals, [] (int n)->int { return plural(n); });
}
} // language

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
*
@ -29,10 +29,7 @@ namespace spanish // language namespace
return n != 1 ? 1 : 0;
}
// Right to Left language?
static bool rtl = false;
static const LocaleStrings strings
static std::map<std::string, std::string> strings
{
{"%.2f KiB", "%.2f KiB"},
{"%.2f MiB", "%.2f MiB"},
@ -199,7 +196,7 @@ namespace spanish // language namespace
std::shared_ptr<const i2p::i18n::Locale> GetLocale()
{
return std::make_shared<i2p::i18n::Locale>(language, rtl, strings, plurals, [] (int n)->int { return plural(n); });
return std::make_shared<i2p::i18n::Locale>(language, strings, plurals, [] (int n)->int { return plural(n); });
}
} // language

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
*
@ -29,10 +29,7 @@ namespace swedish // language namespace
return n != 1 ? 1 : 0;
}
// Right to Left language?
static bool rtl = false;
static const LocaleStrings strings
static std::map<std::string, std::string> strings
{
{"%.2f KiB", "%.2f KiB"},
{"%.2f MiB", "%.2f MiB"},
@ -214,7 +211,7 @@ namespace swedish // language namespace
std::shared_ptr<const i2p::i18n::Locale> GetLocale()
{
return std::make_shared<i2p::i18n::Locale>(language, rtl, strings, plurals, [] (int n)->int { return plural(n); });
return std::make_shared<i2p::i18n::Locale>(language, strings, plurals, [] (int n)->int { return plural(n); });
}
} // language

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
*
@ -29,10 +29,7 @@ namespace turkish // language namespace
return n != 1 ? 1 : 0;
}
// Right to Left language?
static bool rtl = false;
static const LocaleStrings strings
static std::map<std::string, std::string> strings
{
{"%.2f KiB", "%.2f KiB"},
{"%.2f MiB", "%.2f MiB"},
@ -109,7 +106,7 @@ namespace turkish // language namespace
std::shared_ptr<const i2p::i18n::Locale> GetLocale()
{
return std::make_shared<i2p::i18n::Locale>(language, rtl, strings, plurals, [] (int n)->int { return plural(n); });
return std::make_shared<i2p::i18n::Locale>(language, strings, plurals, [] (int n)->int { return plural(n); });
}
} // language

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
*
@ -29,10 +29,7 @@ namespace turkmen // language namespace
return n != 1 ? 1 : 0;
}
// Right to Left language?
static bool rtl = false;
static const LocaleStrings strings
static std::map<std::string, std::string> strings
{
{"%.2f KiB", "%.2f KiB"},
{"%.2f MiB", "%.2f MiB"},
@ -199,7 +196,7 @@ namespace turkmen // language namespace
std::shared_ptr<const i2p::i18n::Locale> GetLocale()
{
return std::make_shared<i2p::i18n::Locale>(language, rtl, strings, plurals, [] (int n)->int { return plural(n); });
return std::make_shared<i2p::i18n::Locale>(language, strings, plurals, [] (int n)->int { return plural(n); });
}
} // language

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
*
@ -29,10 +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;
}
// Right to Left language?
static bool rtl = false;
static const LocaleStrings strings
static std::map<std::string, std::string> strings
{
{"%.2f KiB", "%.2f КіБ"},
{"%.2f MiB", "%.2f МіБ"},
@ -218,7 +215,7 @@ namespace ukrainian // language namespace
std::shared_ptr<const i2p::i18n::Locale> GetLocale()
{
return std::make_shared<i2p::i18n::Locale>(language, rtl, strings, plurals, [] (int n)->int { return plural(n); });
return std::make_shared<i2p::i18n::Locale>(language, strings, plurals, [] (int n)->int { return plural(n); });
}
} // language

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
*
@ -29,10 +29,7 @@ namespace uzbek // language namespace
return n > 1 ? 1 : 0;
}
// Right to Left language?
static bool rtl = false;
static const LocaleStrings strings
static std::map<std::string, std::string> strings
{
{"%.2f KiB", "%.2f KiB"},
{"%.2f MiB", "%.2f MiB"},
@ -218,7 +215,7 @@ namespace uzbek // language namespace
std::shared_ptr<const i2p::i18n::Locale> GetLocale()
{
return std::make_shared<i2p::i18n::Locale>(language, rtl, strings, plurals, [] (int n)->int { return plural(n); });
return std::make_shared<i2p::i18n::Locale>(language, strings, plurals, [] (int n)->int { return plural(n); });
}
} // language

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

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2025, The PurpleI2P Project
* Copyright (c) 2013-2022, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -152,11 +152,11 @@ namespace data
m_BlindedSigType = m_SigType;
}
BlindedPublicKey::BlindedPublicKey (std::string_view b33):
BlindedPublicKey::BlindedPublicKey (const std::string& b33):
m_SigType (0) // 0 means invalid, we can't blind DSA, set it later
{
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.c_str (), b33.length (), addr, 40);
if (l < 32)
{
LogPrint (eLogError, "Blinding: Malformed b33 ", b33);
@ -198,7 +198,7 @@ namespace data
std::string BlindedPublicKey::ToB33 () const
{
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;
if (m_IsClientAuth) flags |= B33_PER_CLIENT_AUTH_FLAG;
addr[0] = flags; // flags
@ -208,7 +208,8 @@ namespace data
uint32_t checksum = crc32 (0, addr + 3, m_PublicKey.size ());
// checksum is Little Endian
addr[0] ^= checksum; addr[1] ^= (checksum >> 8); addr[2] ^= (checksum >> 16);
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

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2025, The PurpleI2P Project
* Copyright (c) 2013-2020, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -11,7 +11,6 @@
#include <inttypes.h>
#include <string>
#include <string_view>
#include <vector>
#include "Identity.h"
@ -24,7 +23,7 @@ namespace data
public:
BlindedPublicKey (std::shared_ptr<const IdentityEx> identity, bool clientAuth = false);
BlindedPublicKey (std::string_view b33); // from b33 without .b32.i2p
BlindedPublicKey (const std::string& b33); // from b33 without .b32.i2p
std::string ToB33 () const;
const uint8_t * GetPublicKey () const { return m_PublicKey.data (); };

68
libi2pd/CPU.cpp Normal file
View file

@ -0,0 +1,68 @@
/*
* Copyright (c) 2013-2023, 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 "CPU.h"
#include "Log.h"
#ifndef bit_AES
#define bit_AES (1 << 25)
#endif
#if defined(__GNUC__) && __GNUC__ < 6 && IS_X86
#include <cpuid.h>
#endif
#ifdef _MSC_VER
#include <intrin.h>
#endif
namespace i2p
{
namespace cpu
{
bool aesni = false;
inline bool cpu_support_aes()
{
#if IS_X86
#if defined(__clang__)
# if (__clang_major__ >= 6)
__builtin_cpu_init();
# endif
return __builtin_cpu_supports("aes");
#elif (defined(__GNUC__) && __GNUC__ >= 6)
__builtin_cpu_init();
return __builtin_cpu_supports("aes");
#elif (defined(__GNUC__) && __GNUC__ < 6)
int cpu_info[4];
bool flag = false;
__cpuid(0, cpu_info[0], cpu_info[1], cpu_info[2], cpu_info[3]);
if (cpu_info[0] >= 0x00000001) {
__cpuid(0x00000001, cpu_info[0], cpu_info[1], cpu_info[2], cpu_info[3]);
flag = ((cpu_info[2] & bit_AES) != 0);
}
return flag;
#elif defined(_MSC_VER)
int cpu_info[4];
__cpuid(cpu_info, 1);
return ((cpu_info[2] & bit_AES) != 0);
#endif
#endif
return false;
}
void Detect(bool AesSwitch, bool force)
{
if ((cpu_support_aes() && AesSwitch) || (AesSwitch && force)) {
aesni = true;
}
LogPrint(eLogInfo, "AESNI ", (aesni ? "enabled" : "disabled"));
}
}
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2024, The PurpleI2P Project
* Copyright (c) 2013-2023, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -21,4 +21,20 @@
# define IS_X86_64 0
#endif
#if defined(__AES__) && !defined(_MSC_VER) && IS_X86
# define SUPPORTS_AES 1
#else
# define SUPPORTS_AES 0
#endif
namespace i2p
{
namespace cpu
{
extern bool aesni;
void Detect(bool AesSwitch, bool force);
}
}
#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
*
@ -117,14 +117,9 @@ namespace config {
("httpproxy.latency.max", value<std::string>()->default_value("0"), "HTTP proxy max latency for tunnels")
("httpproxy.outproxy", value<std::string>()->default_value(""), "HTTP proxy upstream out proxy url")
("httpproxy.addresshelper", value<bool>()->default_value(true), "Enable or disable addresshelper")
("httpproxy.senduseragent", value<bool>()->default_value(false), "Pass through user's User-Agent if enabled. Disabled by default")
("httpproxy.i2cp.leaseSetType", value<std::string>()->default_value("3"), "Local destination's LeaseSet type")
("httpproxy.i2cp.leaseSetEncType", value<std::string>()->default_value("0,4"), "Local destination's LeaseSet encryption type")
("httpproxy.i2cp.leaseSetPrivKey", value<std::string>()->default_value(""), "LeaseSet private key")
("httpproxy.i2p.streaming.maxOutboundSpeed", value<std::string>()->default_value("1730000000"), "Max outbound speed of HTTP proxy stream in bytes/sec")
("httpproxy.i2p.streaming.maxInboundSpeed", value<std::string>()->default_value("1730000000"), "Max inbound speed of HTTP proxy stream in bytes/sec")
("httpproxy.i2p.streaming.profile", value<std::string>()->default_value("1"), "HTTP Proxy bandwidth usage profile. 1 - bulk(high), 2- interactive(low)")
;
options_description socksproxy("SOCKS Proxy options");
@ -149,20 +144,6 @@ namespace config {
("socksproxy.i2cp.leaseSetType", value<std::string>()->default_value("3"), "Local destination's LeaseSet type")
("socksproxy.i2cp.leaseSetEncType", value<std::string>()->default_value("0,4"), "Local destination's LeaseSet encryption type")
("socksproxy.i2cp.leaseSetPrivKey", value<std::string>()->default_value(""), "LeaseSet private key")
("socksproxy.i2p.streaming.maxOutboundSpeed", value<std::string>()->default_value("1730000000"), "Max outbound speed of SOCKS proxy stream in bytes/sec")
("socksproxy.i2p.streaming.maxInboundSpeed", value<std::string>()->default_value("1730000000"), "Max inbound speed of SOCKS proxy stream in bytes/sec")
("socksproxy.i2p.streaming.profile", value<std::string>()->default_value("1"), "SOCKS Proxy bandwidth usage profile. 1 - bulk(high), 2- interactive(low)")
;
options_description shareddest("Shared local destination options");
shareddest.add_options()
("shareddest.inbound.length", value<std::string>()->default_value("3"), "Shared local destination inbound tunnel length")
("shareddest.outbound.length", value<std::string>()->default_value("3"), "Shared local destination outbound tunnel length")
("shareddest.inbound.quantity", value<std::string>()->default_value("3"), "Shared local destination inbound tunnels quantity")
("shareddest.outbound.quantity", value<std::string>()->default_value("3"), "Shared local destination outbound tunnels quantity")
("shareddest.i2cp.leaseSetType", value<std::string>()->default_value("3"), "Shared local destination's LeaseSet type")
("shareddest.i2cp.leaseSetEncType", value<std::string>()->default_value("0,4"), "Shared local destination's LeaseSet encryption type")
("shareddest.i2p.streaming.profile", value<std::string>()->default_value("2"), "Shared local destination bandwidth usage profile. 1 - bulk(high), 2- interactive(low)")
;
options_description sam("SAM bridge options");
@ -234,21 +215,22 @@ namespace config {
"https://reseed2.i2p.net/,"
"https://reseed.diva.exchange/,"
"https://reseed-fr.i2pd.xyz/,"
"https://reseed.memcpy.io/,"
"https://reseed.onion.im/,"
"https://i2pseed.creativecowpat.net:8443/,"
"https://reseed.i2pgit.org/,"
"https://coconut.incognet.io/,"
"https://banana.incognet.io/,"
"https://reseed-pl.i2pd.xyz/,"
"https://www2.mk16.de/,"
"https://i2p.ghativega.in/,"
"https://i2p.novg.net/,"
"https://reseed.stormycloud.org/,"
"https://cubicchaos.net:8443/"
"https://reseed.stormycloud.org/"
), "Reseed URLs, separated by comma")
("reseed.yggurls", value<std::string>()->default_value(
"http://[324:71e:281a:9ed3::ace]:7070/,"
"http://[301:65b9:c7cd:9a36::1]:18801/,"
"http://[320:8936:ec1a:31f1::216]/,"
"http://[306:3834:97b9:a00a::1]/,"
"http://[316:f9e0:f22e:a74f::216]/"
), "Reseed URLs through the Yggdrasil, separated by comma")
;
@ -305,8 +287,6 @@ namespace config {
("ssu2.mtu4", value<uint16_t>()->default_value(0), "MTU for ipv4 address (default: detect)")
("ssu2.mtu6", value<uint16_t>()->default_value(0), "MTU for ipv6 address (default: detect)")
("ssu2.proxy", value<std::string>()->default_value(""), "Socks5 proxy URL for SSU2 transport")
("ssu2.firewalled4", value<bool>()->default_value(false), "Set ipv4 network status to Firewalled even if OK (default: disabled)")
("ssu2.firewalled6", value<bool>()->default_value(false), "Set ipv6 network status to Firewalled even if OK (default: disabled)")
;
options_description nettime("Time sync options");
@ -328,11 +308,11 @@ namespace config {
("persist.addressbook", value<bool>()->default_value(true), "Persist full addresses (default: true)")
;
options_description cpuext("CPU encryption extensions options. Deprecated");
options_description cpuext("CPU encryption extensions options");
cpuext.add_options()
("cpuext.aesni", bool_switch()->default_value(true), "Deprecated option")
("cpuext.aesni", bool_switch()->default_value(true), "Use auto detection for AESNI CPU extensions. If false, AESNI will be not used")
("cpuext.avx", bool_switch()->default_value(false), "Deprecated option")
("cpuext.force", bool_switch()->default_value(false), "Deprecated option")
("cpuext.force", bool_switch()->default_value(false), "Force usage of CPU extensions. Useful when cpuinfo is not available on virtual machines")
;
options_description meshnets("Meshnet transports options");
@ -354,7 +334,6 @@ namespace config {
.add(httpserver)
.add(httpproxy)
.add(socksproxy)
.add(shareddest)
.add(sam)
.add(bob)
.add(i2cp)

File diff suppressed because it is too large Load diff

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
*
@ -25,13 +25,16 @@
#include "Base.h"
#include "Tag.h"
#include "CPU.h"
// recognize openssl version and features
#if (!defined(LIBRESSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER != 0x030000000)) // 3.0.0, regression in SipHash, not implemented in LibreSSL
#if (OPENSSL_VERSION_NUMBER >= 0x010101000) // 1.1.1
# define OPENSSL_HKDF 1
# define OPENSSL_EDDSA 1
# define OPENSSL_X25519 1
# if (!defined(LIBRESSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER != 0x030000000)) // 3.0.0, regression in SipHash, not implemented in LibreSSL
# define OPENSSL_SIPHASH 1
#endif
#if (OPENSSL_VERSION_NUMBER >= 0x030500000) // 3.5.0
# define OPENSSL_PQ 1
# endif
#endif
namespace i2p
@ -41,11 +44,7 @@ namespace crypto
bool bn2buf (const BIGNUM * bn, uint8_t * buf, size_t len);
// DSA
#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0
EVP_PKEY * CreateDSA (BIGNUM * pubKey = nullptr, BIGNUM * privKey = nullptr);
#else
DSA * CreateDSA ();
#endif
// RSA
const BIGNUM * GetRSAE ();
@ -71,8 +70,13 @@ namespace crypto
private:
uint8_t m_PublicKey[32];
#if OPENSSL_X25519
EVP_PKEY_CTX * m_Ctx;
EVP_PKEY * m_Pkey;
#else
BN_CTX * m_Ctx;
uint8_t m_PrivateKey[32];
#endif
bool m_IsElligatorIneligible = false; // true if definitely ineligible
};
@ -87,70 +91,142 @@ namespace crypto
void GenerateECIESKeyPair (const EC_GROUP * curve, BIGNUM *& priv, EC_POINT *& pub);
// AES
typedef i2p::data::Tag<32> AESKey;
class ECBEncryption
struct ChipherBlock
{
public:
uint8_t buf[16];
ECBEncryption ();
~ECBEncryption ();
void SetKey (const uint8_t * key) { m_Key = key; };
void Encrypt(const uint8_t * in, uint8_t * out);
private:
AESKey m_Key;
EVP_CIPHER_CTX * m_Ctx;
void operator^=(const ChipherBlock& other) // XOR
{
if (!(((size_t)buf | (size_t)other.buf) & 0x03)) // multiple of 4 ?
{
for (int i = 0; i < 4; i++)
reinterpret_cast<uint32_t *>(buf)[i] ^= reinterpret_cast<const uint32_t *>(other.buf)[i];
}
else
{
for (int i = 0; i < 16; i++)
buf[i] ^= other.buf[i];
}
}
};
class ECBDecryption
typedef i2p::data::Tag<32> AESKey;
template<size_t sz>
class AESAlignedBuffer // 16 bytes alignment
{
public:
ECBDecryption ();
~ECBDecryption ();
AESAlignedBuffer ()
{
m_Buf = m_UnalignedBuffer;
uint8_t rem = ((size_t)m_Buf) & 0x0f;
if (rem)
m_Buf += (16 - rem);
}
void SetKey (const uint8_t * key) { m_Key = key; };
void Decrypt (const uint8_t * in, uint8_t * out);
operator uint8_t * () { return m_Buf; };
operator const uint8_t * () const { return m_Buf; };
ChipherBlock * GetChipherBlock () { return (ChipherBlock *)m_Buf; };
const ChipherBlock * GetChipherBlock () const { return (const ChipherBlock *)m_Buf; };
private:
AESKey m_Key;
EVP_CIPHER_CTX * m_Ctx;
uint8_t m_UnalignedBuffer[sz + 15]; // up to 15 bytes alignment
uint8_t * m_Buf;
};
#if SUPPORTS_AES
class ECBCryptoAESNI
{
public:
uint8_t * GetKeySchedule () { return m_KeySchedule; };
protected:
void ExpandKey (const AESKey& key);
private:
AESAlignedBuffer<240> m_KeySchedule; // 14 rounds for AES-256, 240 bytes
};
#endif
#if SUPPORTS_AES
class ECBEncryption: public ECBCryptoAESNI
#else
class ECBEncryption
#endif
{
public:
void SetKey (const AESKey& key);
void Encrypt(const ChipherBlock * in, ChipherBlock * out);
private:
AES_KEY m_Key;
};
#if SUPPORTS_AES
class ECBDecryption: public ECBCryptoAESNI
#else
class ECBDecryption
#endif
{
public:
void SetKey (const AESKey& key);
void Decrypt (const ChipherBlock * in, ChipherBlock * out);
private:
AES_KEY m_Key;
};
class CBCEncryption
{
public:
CBCEncryption ();
~CBCEncryption ();
CBCEncryption () { memset ((uint8_t *)m_LastBlock, 0, 16); };
void SetKey (const uint8_t * key) { m_Key = key; }; // 32 bytes
void Encrypt (const uint8_t * in, size_t len, const uint8_t * iv, uint8_t * out);
void SetKey (const AESKey& key) { m_ECBEncryption.SetKey (key); }; // 32 bytes
void SetIV (const uint8_t * iv) { memcpy ((uint8_t *)m_LastBlock, iv, 16); }; // 16 bytes
void GetIV (uint8_t * iv) const { memcpy (iv, (const uint8_t *)m_LastBlock, 16); };
void Encrypt (int numBlocks, const ChipherBlock * in, ChipherBlock * out);
void Encrypt (const uint8_t * in, std::size_t len, uint8_t * out);
void Encrypt (const uint8_t * in, uint8_t * out); // one block
ECBEncryption & ECB() { return m_ECBEncryption; }
private:
AESKey m_Key;
EVP_CIPHER_CTX * m_Ctx;
AESAlignedBuffer<16> m_LastBlock;
ECBEncryption m_ECBEncryption;
};
class CBCDecryption
{
public:
CBCDecryption ();
~CBCDecryption ();
CBCDecryption () { memset ((uint8_t *)m_IV, 0, 16); };
void SetKey (const uint8_t * key) { m_Key = key; }; // 32 bytes
void Decrypt (const uint8_t * in, size_t len, const uint8_t * iv, uint8_t * out);
void SetKey (const AESKey& key) { m_ECBDecryption.SetKey (key); }; // 32 bytes
void SetIV (const uint8_t * iv) { memcpy ((uint8_t *)m_IV, iv, 16); }; // 16 bytes
void GetIV (uint8_t * iv) const { memcpy (iv, (const uint8_t *)m_IV, 16); };
void Decrypt (int numBlocks, const ChipherBlock * in, ChipherBlock * out);
void Decrypt (const uint8_t * in, std::size_t len, uint8_t * out);
void Decrypt (const uint8_t * in, uint8_t * out); // one block
ECBDecryption & ECB() { return m_ECBDecryption; }
private:
AESKey m_Key;
EVP_CIPHER_CTX * m_Ctx;
AESAlignedBuffer<16> m_IV;
ECBDecryption m_ECBDecryption;
};
class TunnelEncryption // with double IV encryption
@ -190,58 +266,13 @@ namespace crypto
};
// AEAD/ChaCha20/Poly1305
bool AEADChaCha20Poly1305 (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen, const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len, bool encrypt); // msgLen is len without tag
class AEADChaCha20Poly1305Encryptor
{
public:
AEADChaCha20Poly1305Encryptor ();
~AEADChaCha20Poly1305Encryptor ();
bool Encrypt (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen,
const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len); // msgLen is len without tag
void Encrypt (const std::vector<std::pair<uint8_t *, size_t> >& bufs, const uint8_t * key, const uint8_t * nonce, uint8_t * mac); // encrypt multiple buffers with zero ad
private:
EVP_CIPHER_CTX * m_Ctx;
};
class AEADChaCha20Poly1305Decryptor
{
public:
AEADChaCha20Poly1305Decryptor ();
~AEADChaCha20Poly1305Decryptor ();
bool Decrypt (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen,
const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len); // msgLen is len without tag
private:
EVP_CIPHER_CTX * m_Ctx;
};
bool AEADChaCha20Poly1305 (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen,
const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len, bool encrypt); // msgLen is len without tag
void AEADChaCha20Poly1305Encrypt (const std::vector<std::pair<uint8_t *, size_t> >& bufs, const uint8_t * key, const uint8_t * nonce, uint8_t * mac); // encrypt multiple buffers with zero ad
// ChaCha20
void ChaCha20 (const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out);
class ChaCha20Context
{
public:
ChaCha20Context ();
~ChaCha20Context ();
void operator ()(const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out);
private:
EVP_CIPHER_CTX * m_Ctx;
};
// HKDF
void HKDF (const uint8_t * salt, const uint8_t * key, size_t keyLen, const std::string& info, uint8_t * out, size_t outLen = 64); // salt - 32, out - 32 or 64, info <= 32
@ -251,16 +282,10 @@ namespace crypto
struct NoiseSymmetricState
{
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 std::vector<std::pair<uint8_t *, size_t> >& bufs);
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)
@ -269,7 +294,7 @@ namespace crypto
void InitNoiseIKState (NoiseSymmetricState& state, const uint8_t * pub); // Noise_IK (ratchets)
// init and terminate
void InitCrypto (bool precomputation);
void InitCrypto (bool precomputation, bool aesni, bool force);
void TerminateCrypto ();
}
}

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
*
@ -181,21 +181,5 @@ namespace crypto
k.GetPrivateKey (priv);
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
*
@ -11,7 +11,6 @@
#include <inttypes.h>
#include "Crypto.h"
#include "Identity.h"
namespace i2p
{
@ -158,50 +157,7 @@ namespace crypto
X25519Keys m_StaticKeys;
};
void CreateECIESX25519AEADRatchetRandomKeys (uint8_t * priv, uint8_t * pub); // including hybrid
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 256; // actual size is 32, but we use 256 for compatibility with old keys files
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 ();
};
void CreateECIESX25519AEADRatchetRandomKeys (uint8_t * priv, uint8_t * pub);
}
}

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
*
@ -57,15 +57,6 @@ namespace datagram
void DatagramDestination::SendDatagram (std::shared_ptr<DatagramSession> session, const uint8_t * payload, size_t len, uint16_t fromPort, uint16_t toPort)
{
if (session)
{
if (session->GetVersion () == eDatagramV3)
{
constexpr uint8_t flags[] = { 0x00, 0x03 }; // datagram3, no options
auto msg = CreateDataMessage ({{m_Owner->GetIdentity ()->GetIdentHash (), 32},
{flags, 2}, {payload, len}}, fromPort, toPort, i2p::client::PROTOCOL_TYPE_DATAGRAM3, false); // datagram3
session->SendMsg(msg);
}
else
{
if (m_Owner->GetIdentity ()->GetSigningKeyType () == i2p::data::SIGNING_KEY_TYPE_DSA_SHA1)
{
@ -77,16 +68,15 @@ namespace datagram
m_Owner->Sign (payload, len, m_Signature.data ());
auto msg = CreateDataMessage ({{m_From.data (), m_From.size ()}, {m_Signature.data (), m_Signature.size ()}, {payload, len}},
fromPort, toPort, i2p::client::PROTOCOL_TYPE_DATAGRAM, !session->IsRatchets ()); // datagram1
fromPort, toPort, false, !session->IsRatchets ()); // datagram
session->SendMsg(msg);
}
}
}
void DatagramDestination::SendRawDatagram (std::shared_ptr<DatagramSession> session, const uint8_t * payload, size_t len, uint16_t fromPort, uint16_t toPort)
{
if (session)
session->SendMsg(CreateDataMessage ({{payload, len}}, fromPort, toPort, i2p::client::PROTOCOL_TYPE_RAW, !session->IsRatchets ())); // raw
session->SendMsg(CreateDataMessage ({{payload, len}}, fromPort, toPort, true, !session->IsRatchets ())); // raw
}
void DatagramDestination::FlushSendQueue (std::shared_ptr<DatagramSession> session)
@ -114,7 +104,8 @@ namespace datagram
if (verified)
{
auto session = ObtainSession (identity.GetIdentHash());
auto h = identity.GetIdentHash();
auto session = ObtainSession(h);
session->Ack();
auto r = FindReceiver(toPort);
if(r)
@ -136,55 +127,6 @@ namespace datagram
LogPrint (eLogWarning, "DatagramDestination: no receiver for raw datagram");
}
void DatagramDestination::HandleDatagram3 (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len,
i2p::garlic::ECIESX25519AEADRatchetSession * from)
{
if (len < 34)
{
LogPrint (eLogWarning, "Datagram: datagram3 is too short ", len);
return;
}
if (from)
{
i2p::data::IdentHash ident(buf);
auto ls = m_Owner->FindLeaseSet (ident);
if (ls)
{
uint8_t staticKey[32];
ls->Encrypt (nullptr, staticKey);
if (!memcmp (from->GetRemoteStaticKey (), staticKey, 32))
{
auto session = ObtainSession (ident);
session->SetVersion (eDatagramV3);
session->SetRemoteLeaseSet (ls);
session->Ack ();
auto r = FindReceiver(toPort);
if (r)
{
uint16_t flags = bufbe16toh (buf + 32);
size_t offset = 34;
if (flags & DATAGRAM3_FLAG_OPTIONS)
offset += bufbe16toh (buf + offset) + 2;
if (offset > len)
{
LogPrint (eLogWarning, "Datagram: datagram3 is too short ", len, " expected ", offset);
return;
}
r(*ls->GetIdentity (), fromPort, toPort, buf + offset, len - offset);
}
else
LogPrint (eLogWarning, "Datagram: no receiver for port ", toPort);
}
else
LogPrint (eLogError, "Datagram: Remote LeaseSet static key mismatch for datagram3 from ", ident.ToBase32 ());
}
else
LogPrint (eLogError, "Datagram: No remote LeaseSet for ", ident.ToBase32 ());
}
else
LogPrint (eLogInfo, "Datagram: datagram3 received from non-ratchets session");
}
void DatagramDestination::SetReceiver (const Receiver& receiver, uint16_t port)
{
std::lock_guard<std::mutex> lock(m_ReceiversMutex);
@ -253,31 +195,17 @@ namespace datagram
return r;
}
void DatagramDestination::HandleDataMessagePayload (uint16_t fromPort, uint16_t toPort,
const uint8_t * buf, size_t len, uint8_t protocolType, i2p::garlic::ECIESX25519AEADRatchetSession * from)
void DatagramDestination::HandleDataMessagePayload (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len, bool isRaw)
{
// unzip it
uint8_t uncompressed[MAX_DATAGRAM_SIZE];
size_t uncompressedLen = m_Inflator.Inflate (buf, len, uncompressed, MAX_DATAGRAM_SIZE);
if (uncompressedLen)
{
switch (protocolType)
{
case i2p::client::PROTOCOL_TYPE_RAW:
if (isRaw)
HandleRawDatagram (fromPort, toPort, uncompressed, uncompressedLen);
break;
case i2p::client::PROTOCOL_TYPE_DATAGRAM3:
HandleDatagram3 (fromPort, toPort, uncompressed, uncompressedLen, from);
break;
case i2p::client::PROTOCOL_TYPE_DATAGRAM:
else
HandleDatagram (fromPort, toPort, uncompressed, uncompressedLen);
break;
case i2p::client::PROTOCOL_TYPE_DATAGRAM2:
// TODO:
break;
default:
LogPrint (eLogInfo, "Datagram: unknown protocol type ", protocolType);
};
}
else
LogPrint (eLogWarning, "Datagram: decompression failed");
@ -285,7 +213,7 @@ namespace datagram
std::shared_ptr<I2NPMessage> DatagramDestination::CreateDataMessage (
const std::vector<std::pair<const uint8_t *, size_t> >& payloads,
uint16_t fromPort, uint16_t toPort, uint8_t protocolType, bool checksum)
uint16_t fromPort, uint16_t toPort, bool isRaw, bool checksum)
{
size_t size;
auto msg = m_I2NPMsgsPool.AcquireShared ();
@ -302,7 +230,7 @@ namespace datagram
htobe32buf (msg->GetPayload (), size); // length
htobe16buf (buf + 4, fromPort); // source port
htobe16buf (buf + 6, toPort); // destination port
buf[9] = protocolType; // raw or datagram protocol
buf[9] = isRaw ? i2p::client::PROTOCOL_TYPE_RAW : i2p::client::PROTOCOL_TYPE_DATAGRAM; // raw or datagram protocol
msg->len += size + 4;
msg->FillI2NPMessageHeader (eI2NPData, 0, checksum);
}
@ -360,8 +288,9 @@ namespace datagram
DatagramSession::DatagramSession(std::shared_ptr<i2p::client::ClientDestination> localDestination,
const i2p::data::IdentHash & remoteIdent) :
m_LocalDestination(localDestination), m_RemoteIdent(remoteIdent),
m_LastUse (0), m_LastFlush (0), m_RequestingLS (false), m_Version (eDatagramV1)
m_LocalDestination(localDestination),
m_RemoteIdent(remoteIdent),
m_RequestingLS(false)
{
}
@ -381,12 +310,8 @@ namespace datagram
if (msg || m_SendQueue.empty ())
m_SendQueue.push_back(msg);
// flush queue right away if full
if (!msg || m_SendQueue.size() >= DATAGRAM_SEND_QUEUE_MAX_SIZE ||
m_LastUse > m_LastFlush + DATAGRAM_MAX_FLUSH_INTERVAL)
{
if (!msg || m_SendQueue.size() >= DATAGRAM_SEND_QUEUE_MAX_SIZE)
FlushSendQueue();
m_LastFlush = m_LastUse;
}
}
DatagramSession::Info DatagramSession::GetSessionInfo() const
@ -419,7 +344,7 @@ namespace datagram
if(path)
path->updateTime = i2p::util::GetSecondsSinceEpoch ();
if (IsRatchets ())
SendMsg (nullptr); // send empty message in case if we don't have some data to send
SendMsg (nullptr); // send empty message in case if we have some data to send
}
std::shared_ptr<i2p::garlic::GarlicRoutingPath> DatagramSession::GetSharedRoutingPath ()
@ -452,19 +377,15 @@ namespace datagram
if (!found)
{
m_RoutingSession = m_LocalDestination->GetRoutingSession(m_RemoteLeaseSet, true);
if (m_RoutingSession)
{
m_RoutingSession->SetAckRequestInterval (DATAGRAM_SESSION_ACK_REQUEST_INTERVAL);
if (!m_RoutingSession->GetOwner () || !m_RoutingSession->IsReadyToSend ())
m_PendingRoutingSessions.push_back (m_RoutingSession);
}
}
}
auto path = m_RoutingSession->GetSharedRoutingPath();
if (path && m_RoutingSession->IsRatchets () && m_RoutingSession->CleanupUnconfirmedTags ())
if (path && m_RoutingSession->IsRatchets () &&
m_LastUse > m_RoutingSession->GetLastActivityTimestamp ()*1000 + DATAGRAM_SESSION_PATH_TIMEOUT)
{
LogPrint (eLogDebug, "Datagram: path reset");
m_RoutingSession->SetSharedRoutingPath (nullptr);
path = nullptr;
}
@ -492,14 +413,7 @@ namespace datagram
auto sz = ls.size();
if (sz)
{
int idx = -1;
if (m_LocalDestination)
{
auto pool = m_LocalDestination->GetTunnelPool ();
if (pool)
idx = pool->GetRng ()() % sz;
}
if (idx < 0) idx = rand () % sz;
auto idx = rand() % sz;
path->remoteLease = ls[idx];
}
else
@ -525,14 +439,7 @@ namespace datagram
auto sz = ls.size();
if (sz)
{
int idx = -1;
if (m_LocalDestination)
{
auto pool = m_LocalDestination->GetTunnelPool ();
if (pool)
idx = pool->GetRng ()() % sz;
}
if (idx < 0) idx = rand () % sz;
auto idx = rand() % sz;
path->remoteLease = ls[idx];
}
else

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
*
@ -20,7 +20,6 @@
#include "LeaseSet.h"
#include "I2NPProtocol.h"
#include "Garlic.h"
#include "ECIESX25519AEADRatchetSession.h"
namespace i2p
{
@ -32,6 +31,8 @@ namespace datagram
{
// milliseconds for max session idle time
const uint64_t DATAGRAM_SESSION_MAX_IDLE = 10 * 60 * 1000;
// milliseconds for how long we try sticking to a dead routing path before trying to switch
const uint64_t DATAGRAM_SESSION_PATH_TIMEOUT = 10 * 1000;
// milliseconds interval a routing path is used before switching
const uint64_t DATAGRAM_SESSION_PATH_SWITCH_INTERVAL = 20 * 60 * 1000;
// milliseconds before lease expire should we try switching leases
@ -42,17 +43,6 @@ namespace datagram
const uint64_t DATAGRAM_SESSION_PATH_MIN_LIFETIME = 5 * 1000;
// max 64 messages buffered in send queue for each datagram session
const size_t DATAGRAM_SEND_QUEUE_MAX_SIZE = 64;
const uint64_t DATAGRAM_MAX_FLUSH_INTERVAL = 5; // in milliseconds
const int DATAGRAM_SESSION_ACK_REQUEST_INTERVAL = 5500; // in milliseconds
enum DatagramVersion
{
eDatagramV1 = 1,
eDatagramV2 = 2,
eDatagramV3 = 3,
};
constexpr uint16_t DATAGRAM3_FLAG_OPTIONS = 0x10;
class DatagramSession : public std::enable_shared_from_this<DatagramSession>
{
@ -75,10 +65,6 @@ namespace datagram
uint64_t LastActivity() const { return m_LastUse; }
bool IsRatchets () const { return m_RoutingSession && m_RoutingSession->IsRatchets (); }
void SetRemoteLeaseSet (std::shared_ptr<const i2p::data::LeaseSet> ls) { m_RemoteLeaseSet = ls; }
DatagramVersion GetVersion () const { return m_Version; }
void SetVersion (DatagramVersion version) { m_Version = version; }
struct Info
{
@ -112,9 +98,8 @@ namespace datagram
std::shared_ptr<i2p::garlic::GarlicRoutingSession> m_RoutingSession;
std::vector<std::shared_ptr<i2p::garlic::GarlicRoutingSession> > m_PendingRoutingSessions;
std::vector<std::shared_ptr<I2NPMessage> > m_SendQueue;
uint64_t m_LastUse, m_LastFlush; // milliseconds
uint64_t m_LastUse;
bool m_RequestingLS;
DatagramVersion m_Version;
};
typedef std::shared_ptr<DatagramSession> DatagramSession_ptr;
@ -139,8 +124,8 @@ namespace datagram
void SendRawDatagram (std::shared_ptr<DatagramSession> session, const uint8_t * payload, size_t len, uint16_t fromPort, uint16_t toPort);
void FlushSendQueue (std::shared_ptr<DatagramSession> session);
void HandleDataMessagePayload (uint16_t fromPort, uint16_t toPort,
const uint8_t * buf, size_t len, uint8_t protocolType, i2p::garlic::ECIESX25519AEADRatchetSession * from);
void HandleDataMessagePayload (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len, bool isRaw = false);
void SetReceiver (const Receiver& receiver, uint16_t port);
void ResetReceiver (uint16_t port);
@ -158,12 +143,10 @@ namespace datagram
std::shared_ptr<DatagramSession> ObtainSession(const i2p::data::IdentHash & ident);
std::shared_ptr<I2NPMessage> CreateDataMessage (const std::vector<std::pair<const uint8_t *, size_t> >& payloads,
uint16_t fromPort, uint16_t toPort, uint8_t protocolType, bool checksum = true);
uint16_t fromPort, uint16_t toPort, bool isRaw = false, bool checksum = true);
void HandleDatagram (uint16_t fromPort, uint16_t toPort, uint8_t *const& buf, size_t len);
void HandleRawDatagram (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len);
void HandleDatagram3 (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len,
i2p::garlic::ECIESX25519AEADRatchetSession * from);
Receiver FindReceiver(uint16_t port);
RawReceiver FindRawReceiver(uint16_t port);

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
*
@ -13,7 +13,6 @@
#include <vector>
#include <boost/algorithm/string.hpp>
#include "Crypto.h"
#include "ECIESX25519AEADRatchetSession.h"
#include "Log.h"
#include "FS.h"
#include "Timestamp.h"
@ -24,7 +23,7 @@ namespace i2p
{
namespace client
{
LeaseSetDestination::LeaseSetDestination (boost::asio::io_context& service,
LeaseSetDestination::LeaseSetDestination (boost::asio::io_service& service,
bool isPublic, const std::map<std::string, std::string> * params):
m_Service (service), m_IsPublic (isPublic), m_PublishReplyToken (0),
m_LastSubmissionTime (0), m_PublishConfirmationTimer (m_Service),
@ -38,7 +37,6 @@ namespace client
int inVar = DEFAULT_INBOUND_TUNNELS_LENGTH_VARIANCE;
int outVar = DEFAULT_OUTBOUND_TUNNELS_LENGTH_VARIANCE;
int numTags = DEFAULT_TAGS_TO_SEND;
bool isHighBandwidth = true;
std::shared_ptr<std::vector<i2p::data::IdentHash> > explicitPeers;
try
{
@ -94,7 +92,7 @@ namespace client
it = params->find (I2CP_PARAM_DONT_PUBLISH_LEASESET);
if (it != params->end ())
{
// override isPublic
// oveeride isPublic
m_IsPublic = (it->second != "true");
}
it = params->find (I2CP_PARAM_LEASESET_TYPE);
@ -123,9 +121,6 @@ namespace client
m_LeaseSetPrivKey.reset (nullptr);
}
}
it = params->find (I2CP_PARAM_STREAMING_PROFILE);
if (it != params->end ())
isHighBandwidth = std::stoi (it->second) != STREAMING_PROFILE_INTERACTIVE;
}
}
catch (std::exception & ex)
@ -133,7 +128,7 @@ namespace client
LogPrint(eLogError, "Destination: Unable to parse parameters for destination: ", ex.what());
}
SetNumTags (numTags);
m_Pool = i2p::tunnel::tunnels.CreateTunnelPool (inLen, outLen, inQty, outQty, inVar, outVar, isHighBandwidth);
m_Pool = i2p::tunnel::tunnels.CreateTunnelPool (inLen, outLen, inQty, outQty, inVar, outVar);
if (explicitPeers)
m_Pool->SetExplicitPeers (explicitPeers);
if(params)
@ -169,7 +164,7 @@ namespace client
LoadTags ();
m_Pool->SetLocalDestination (shared_from_this ());
m_Pool->SetActive (true);
m_CleanupTimer.expires_from_now (boost::posix_time::seconds (DESTINATION_CLEANUP_TIMEOUT));
m_CleanupTimer.expires_from_now (boost::posix_time::minutes (DESTINATION_CLEANUP_TIMEOUT));
m_CleanupTimer.async_wait (std::bind (&LeaseSetDestination::HandleCleanupTimer,
shared_from_this (), std::placeholders::_1));
}
@ -196,7 +191,7 @@ namespace client
m_IsPublic = itr->second != "true";
}
int inLen = 0, outLen = 0, inQuant = 0, outQuant = 0, numTags = 0, minLatency = 0, maxLatency = 0;
int inLen, outLen, inQuant, outQuant, numTags, minLatency, maxLatency;
std::map<std::string, int&> intOpts = {
{I2CP_PARAM_INBOUND_TUNNEL_LENGTH, inLen},
{I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH, outLen},
@ -295,7 +290,7 @@ namespace client
if (m_IsPublic)
{
auto s = shared_from_this ();
boost::asio::post (m_Service, [s](void)
m_Service.post ([s](void)
{
s->m_PublishVerificationTimer.cancel ();
s->Publish ();
@ -323,7 +318,7 @@ namespace client
memcpy (data.k, key, 32);
memcpy (data.t, tag, 32);
auto s = shared_from_this ();
boost::asio::post (m_Service, [s,data](void)
m_Service.post ([s,data](void)
{
s->AddSessionKey (data.k, data.t);
});
@ -340,7 +335,7 @@ namespace client
memcpy (data.k, key, 32);
data.t = tag;
auto s = shared_from_this ();
boost::asio::post (m_Service, [s,data](void)
m_Service.post ([s,data](void)
{
s->AddECIESx25519Key (data.k, data.t);
});
@ -348,47 +343,28 @@ namespace client
void LeaseSetDestination::ProcessGarlicMessage (std::shared_ptr<I2NPMessage> msg)
{
if (!msg) return;
bool empty = false;
{
std::lock_guard<std::mutex> l(m_IncomingMsgsQueueMutex);
empty = m_IncomingMsgsQueue.empty ();
m_IncomingMsgsQueue.push_back (msg);
}
if (empty)
boost::asio::post (m_Service, [s = shared_from_this ()]()
{
std::list<std::shared_ptr<I2NPMessage> > receivedMsgs;
{
std::lock_guard<std::mutex> l(s->m_IncomingMsgsQueueMutex);
s->m_IncomingMsgsQueue.swap (receivedMsgs);
}
for (auto& it: receivedMsgs)
s->HandleGarlicMessage (it);
});
m_Service.post (std::bind (&LeaseSetDestination::HandleGarlicMessage, shared_from_this (), msg));
}
void LeaseSetDestination::ProcessDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg)
{
uint32_t msgID = bufbe32toh (msg->GetPayload () + DELIVERY_STATUS_MSGID_OFFSET);
boost::asio::post (m_Service, std::bind (&LeaseSetDestination::HandleDeliveryStatusMessage, shared_from_this (), msgID));
m_Service.post (std::bind (&LeaseSetDestination::HandleDeliveryStatusMessage, shared_from_this (), msgID));
}
void LeaseSetDestination::HandleI2NPMessage (const uint8_t * buf, size_t len)
{
I2NPMessageType typeID = (I2NPMessageType)(buf[I2NP_HEADER_TYPEID_OFFSET]);
uint32_t msgID = bufbe32toh (buf + I2NP_HEADER_MSGID_OFFSET);
LeaseSetDestination::HandleCloveI2NPMessage (typeID, buf + I2NP_HEADER_SIZE,
GetI2NPMessageLength(buf, len) - I2NP_HEADER_SIZE, msgID, nullptr);
LeaseSetDestination::HandleCloveI2NPMessage (typeID, buf + I2NP_HEADER_SIZE, GetI2NPMessageLength(buf, len) - I2NP_HEADER_SIZE, msgID);
}
bool LeaseSetDestination::HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload,
size_t len, uint32_t msgID, i2p::garlic::ECIESX25519AEADRatchetSession * from)
bool LeaseSetDestination::HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len, uint32_t msgID)
{
switch (typeID)
{
case eI2NPData:
HandleDataMessage (payload, len, from);
HandleDataMessage (payload, len);
break;
case eI2NPDeliveryStatus:
HandleDeliveryStatusMessage (bufbe32toh (payload + DELIVERY_STATUS_MSGID_OFFSET));
@ -398,7 +374,7 @@ namespace client
m_Pool->ProcessTunnelTest (bufbe32toh (payload + TUNNEL_TEST_MSGID_OFFSET), bufbe64toh (payload + TUNNEL_TEST_TIMESTAMP_OFFSET));
break;
case eI2NPDatabaseStore:
HandleDatabaseStoreMessage (payload, len, from);
HandleDatabaseStoreMessage (payload, len);
break;
case eI2NPDatabaseSearchReply:
HandleDatabaseSearchReplyMessage (payload, len);
@ -413,8 +389,7 @@ namespace client
return true;
}
void LeaseSetDestination::HandleDatabaseStoreMessage (const uint8_t * buf, size_t len,
i2p::garlic::ECIESX25519AEADRatchetSession * from)
void LeaseSetDestination::HandleDatabaseStoreMessage (const uint8_t * buf, size_t len)
{
if (len < DATABASE_STORE_HEADER_SIZE)
{
@ -450,7 +425,7 @@ namespace client
leaseSet = it->second;
if (leaseSet->IsNewer (buf + offset, len - offset))
{
leaseSet->Update (buf + offset, len - offset, shared_from_this(), true);
leaseSet->Update (buf + offset, len - offset);
if (leaseSet->IsValid () && leaseSet->GetIdentHash () == key && !leaseSet->IsExpired ())
LogPrint (eLogDebug, "Destination: Remote LeaseSet updated");
else
@ -469,29 +444,13 @@ namespace client
if (buf[DATABASE_STORE_TYPE_OFFSET] == i2p::data::NETDB_STORE_TYPE_LEASESET)
leaseSet = std::make_shared<i2p::data::LeaseSet> (buf + offset, len - offset); // LeaseSet
else
{
leaseSet = std::make_shared<i2p::data::LeaseSet2> (buf[DATABASE_STORE_TYPE_OFFSET],
buf + offset, len - offset, true, shared_from_this (),
from ? from->GetRemoteStaticKeyType () : GetPreferredCryptoType () ); // LeaseSet2
if (from)
{
uint8_t pub[32];
leaseSet->Encrypt (nullptr, pub);
if (memcmp (from->GetRemoteStaticKey (), pub, 32))
{
LogPrint (eLogError, "Destination: Remote LeaseSet static key mismatch");
leaseSet = nullptr;
}
}
}
if (leaseSet && leaseSet->IsValid () && leaseSet->GetIdentHash () == key && !leaseSet->IsExpired ())
leaseSet = std::make_shared<i2p::data::LeaseSet2> (buf[DATABASE_STORE_TYPE_OFFSET], buf + offset, len - offset, true, GetPreferredCryptoType () ); // LeaseSet2
if (leaseSet->IsValid () && leaseSet->GetIdentHash () == key && !leaseSet->IsExpired ())
{
if (leaseSet->GetIdentHash () != GetIdentHash ())
{
LogPrint (eLogDebug, "Destination: New remote LeaseSet added");
m_RemoteLeaseSets.insert_or_assign (key, leaseSet);
if (from)
from->SetDestination (key);
m_RemoteLeaseSets[key] = leaseSet;
}
else
LogPrint (eLogDebug, "Destination: Own remote LeaseSet dropped");
@ -514,8 +473,7 @@ namespace client
if (request->requestedBlindedKey)
{
auto ls2 = std::make_shared<i2p::data::LeaseSet2> (buf + offset, len - offset,
request->requestedBlindedKey, shared_from_this (),
m_LeaseSetPrivKey ? ((const uint8_t *)*m_LeaseSetPrivKey) : nullptr, GetPreferredCryptoType ());
request->requestedBlindedKey, m_LeaseSetPrivKey ? ((const uint8_t *)*m_LeaseSetPrivKey) : nullptr , GetPreferredCryptoType ());
if (ls2->IsValid () && !ls2->IsExpired ())
{
leaseSet = ls2;
@ -618,8 +576,7 @@ namespace client
m_ExcludedFloodfills.clear ();
m_PublishReplyToken = 0;
// schedule verification
m_PublishVerificationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_VERIFICATION_TIMEOUT +
(m_Pool ? m_Pool->GetRng ()() % PUBLISH_VERIFICATION_TIMEOUT_VARIANCE : 0)));
m_PublishVerificationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_VERIFICATION_TIMEOUT));
m_PublishVerificationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishVerificationTimer,
shared_from_this (), std::placeholders::_1));
}
@ -627,11 +584,8 @@ namespace client
i2p::garlic::GarlicDestination::HandleDeliveryStatusMessage (msgID);
}
void LeaseSetDestination::SetLeaseSetUpdated (bool post)
void LeaseSetDestination::SetLeaseSetUpdated ()
{
if (post)
boost::asio::post (m_Service, [s = shared_from_this ()]() { s->UpdateLeaseSet (); });
else
UpdateLeaseSet ();
}
@ -698,8 +652,8 @@ namespace client
m_ExcludedFloodfills.clear ();
m_PublishReplyToken = 1; // dummy non-zero value
// try again after a while
LogPrint (eLogInfo, "Destination: Can't publish LeasetSet because destination is not ready. Try publishing again after ", PUBLISH_CONFIRMATION_TIMEOUT, " milliseconds");
m_PublishConfirmationTimer.expires_from_now (boost::posix_time::milliseconds(PUBLISH_CONFIRMATION_TIMEOUT));
LogPrint (eLogInfo, "Destination: Can't publish LeasetSet because destination is not ready. Try publishing again after ", PUBLISH_CONFIRMATION_TIMEOUT, " seconds");
m_PublishConfirmationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_CONFIRMATION_TIMEOUT));
m_PublishConfirmationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishConfirmationTimer,
shared_from_this (), std::placeholders::_1));
return;
@ -712,13 +666,13 @@ namespace client
auto s = shared_from_this ();
msg->onDrop = [s]()
{
boost::asio::post (s->GetService (), [s]()
s->GetService ().post([s]()
{
s->m_PublishConfirmationTimer.cancel ();
s->HandlePublishConfirmationTimer (boost::system::error_code());
});
};
m_PublishConfirmationTimer.expires_from_now (boost::posix_time::milliseconds(PUBLISH_CONFIRMATION_TIMEOUT));
m_PublishConfirmationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_CONFIRMATION_TIMEOUT));
m_PublishConfirmationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishConfirmationTimer,
shared_from_this (), std::placeholders::_1));
outbound->SendTunnelDataMsgTo (floodfill->GetIdentHash (), 0, msg);
@ -731,10 +685,23 @@ namespace client
{
if (m_PublishReplyToken)
{
LogPrint (eLogWarning, "Destination: Publish confirmation was not received in ", PUBLISH_CONFIRMATION_TIMEOUT, " milliseconds or failed. will try again");
m_PublishReplyToken = 0;
if (GetIdentity ()->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ELGAMAL)
{
LogPrint (eLogWarning, "Destination: Publish confirmation was not received in ", PUBLISH_CONFIRMATION_TIMEOUT, " seconds or failed. will try again");
Publish ();
}
else
{
LogPrint (eLogWarning, "Destination: Publish confirmation was not received in ", PUBLISH_CONFIRMATION_TIMEOUT, " seconds from Java floodfill for crypto type ", (int)GetIdentity ()->GetCryptoKeyType ());
// Java floodfill never sends confirmation back for unknown crypto type
// assume it successive and try to verify
m_PublishVerificationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_VERIFICATION_TIMEOUT));
m_PublishVerificationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishVerificationTimer,
shared_from_this (), std::placeholders::_1));
}
}
}
}
@ -784,10 +751,10 @@ namespace client
if (!m_Pool || !IsReady ())
{
if (requestComplete)
boost::asio::post (m_Service, [requestComplete](void){requestComplete (nullptr);});
m_Service.post ([requestComplete](void){requestComplete (nullptr);});
return false;
}
boost::asio::post (m_Service, std::bind (&LeaseSetDestination::RequestLeaseSet, shared_from_this (), dest, requestComplete, nullptr));
m_Service.post (std::bind (&LeaseSetDestination::RequestLeaseSet, shared_from_this (), dest, requestComplete, nullptr));
return true;
}
@ -796,7 +763,7 @@ namespace client
if (!dest || !m_Pool || !IsReady ())
{
if (requestComplete)
boost::asio::post (m_Service, [requestComplete](void){requestComplete (nullptr);});
m_Service.post ([requestComplete](void){requestComplete (nullptr);});
return false;
}
auto storeHash = dest->GetStoreHash ();
@ -804,17 +771,17 @@ namespace client
if (leaseSet)
{
if (requestComplete)
boost::asio::post (m_Service, [requestComplete, leaseSet](void){requestComplete (leaseSet);});
m_Service.post ([requestComplete, leaseSet](void){requestComplete (leaseSet);});
return true;
}
boost::asio::post (m_Service, std::bind (&LeaseSetDestination::RequestLeaseSet, shared_from_this (), storeHash, requestComplete, dest));
m_Service.post (std::bind (&LeaseSetDestination::RequestLeaseSet, shared_from_this (), storeHash, requestComplete, dest));
return true;
}
void LeaseSetDestination::CancelDestinationRequest (const i2p::data::IdentHash& dest, bool notify)
{
auto s = shared_from_this ();
boost::asio::post (m_Service, [dest, notify, s](void)
m_Service.post ([dest, notify, s](void)
{
auto it = s->m_LeaseSetRequests.find (dest);
if (it != s->m_LeaseSetRequests.end ())
@ -842,7 +809,7 @@ namespace client
request->requestedBlindedKey = requestedBlindedKey; // for encrypted LeaseSet2
if (requestComplete)
request->requestComplete.push_back (requestComplete);
auto ts = i2p::util::GetMillisecondsSinceEpoch ();
auto ts = i2p::util::GetSecondsSinceEpoch ();
auto ret = m_LeaseSetRequests.insert (std::pair<i2p::data::IdentHash, std::shared_ptr<LeaseSetRequest> >(dest,request));
if (ret.second) // inserted
{
@ -912,7 +879,7 @@ namespace client
auto s = shared_from_this ();
msg->onDrop = [s, dest, request]()
{
boost::asio::post (s->GetService (), [s, dest, request]()
s->GetService ().post([s, dest, request]()
{
s->SendNextLeaseSetRequest (dest, request);
});
@ -925,7 +892,7 @@ namespace client
nextFloodfill->GetIdentHash (), 0, msg
}
});
request->requestTimeoutTimer.expires_from_now (boost::posix_time::milliseconds(LEASESET_REQUEST_TIMEOUT));
request->requestTimeoutTimer.expires_from_now (boost::posix_time::seconds(LEASESET_REQUEST_TIMEOUT));
request->requestTimeoutTimer.async_wait (std::bind (&LeaseSetDestination::HandleRequestTimoutTimer,
shared_from_this (), std::placeholders::_1, dest));
}
@ -942,7 +909,7 @@ namespace client
if (it != m_LeaseSetRequests.end ())
{
bool done = false;
uint64_t ts = i2p::util::GetMillisecondsSinceEpoch ();
uint64_t ts = i2p::util::GetSecondsSinceEpoch ();
if (ts < it->second->requestTime + MAX_LEASESET_REQUEST_TIMEOUT)
{
auto floodfill = i2p::data::netdb.GetClosestFloodfill (dest, it->second->excluded);
@ -979,8 +946,7 @@ namespace client
CleanupExpiredTags ();
CleanupRemoteLeaseSets ();
CleanupDestination ();
m_CleanupTimer.expires_from_now (boost::posix_time::seconds (DESTINATION_CLEANUP_TIMEOUT +
(m_Pool ? m_Pool->GetRng ()() % DESTINATION_CLEANUP_TIMEOUT_VARIANCE : 0)));
m_CleanupTimer.expires_from_now (boost::posix_time::minutes (DESTINATION_CLEANUP_TIMEOUT));
m_CleanupTimer.async_wait (std::bind (&LeaseSetDestination::HandleCleanupTimer,
shared_from_this (), std::placeholders::_1));
}
@ -994,7 +960,7 @@ namespace client
{
if (it->second->IsEmpty () || ts > it->second->GetExpirationTime ()) // leaseset expired
{
LogPrint (eLogDebug, "Destination: Remote LeaseSet ", it->second->GetIdentHash ().ToBase64 (), " expired");
LogPrint (eLogWarning, "Destination: Remote LeaseSet ", it->second->GetIdentHash ().ToBase64 (), " expired");
it = m_RemoteLeaseSets.erase (it);
}
else
@ -1002,15 +968,20 @@ namespace client
}
}
ClientDestination::ClientDestination (boost::asio::io_context& service, const i2p::data::PrivateKeys& keys,
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_service& service, const i2p::data::PrivateKeys& keys,
bool isPublic, const std::map<std::string, std::string> * 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_StreamingInboundSpeed (DEFAULT_MAX_INBOUND_SPEED),
m_StreamingMaxConcurrentStreams (DEFAULT_MAX_CONCURRENT_STREAMS),
m_IsStreamingAnswerPings (DEFAULT_ANSWER_PINGS), m_LastPort (0),
m_DatagramDestination (nullptr), m_RefCounter (0), m_LastPublishedTimestamp (0),
m_DatagramDestination (nullptr), m_RefCounter (0),
m_ReadyChecker(service)
{
if (keys.IsOfflineSignature () && GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_LEASESET)
@ -1030,15 +1001,7 @@ namespace client
{
try
{
i2p::data::CryptoKeyType cryptoType = 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);
}
encryptionKeyTypes.insert (std::stoi(it1));
}
catch (std::exception& ex)
{
@ -1049,21 +1012,29 @@ namespace client
}
}
// if no param or valid crypto type use from identity
bool isSingleKey = false;
if (encryptionKeyTypes.empty ())
encryptionKeyTypes.insert ( { GetIdentity ()->GetCryptoKeyType (),
i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD }); // usually 0,4
{
isSingleKey = true;
encryptionKeyTypes.insert (GetIdentity ()->GetCryptoKeyType ());
}
for (auto& it: encryptionKeyTypes)
{
auto encryptionKey = std::make_shared<i2p::crypto::LocalEncryptionKey> (it);
auto encryptionKey = new EncryptionKey (it);
if (IsPublic ())
PersistTemporaryKeys (encryptionKey);
PersistTemporaryKeys (encryptionKey, isSingleKey);
else
encryptionKey->GenerateKeys ();
encryptionKey->CreateDecryptor ();
if (it > i2p::data::CRYPTO_KEY_TYPE_ELGAMAL && GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_LEASESET)
SetLeaseSetType (i2p::data::NETDB_STORE_TYPE_STANDARD_LEASESET2); // Only DSA can use LeaseSet1
m_EncryptionKeys.emplace (it, encryptionKey);
if (it == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)
{
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 ())
@ -1080,11 +1051,6 @@ namespace client
it = params->find (I2CP_PARAM_STREAMING_MAX_OUTBOUND_SPEED);
if (it != params->end ())
m_StreamingOutboundSpeed = std::stoi(it->second);
it = params->find (I2CP_PARAM_STREAMING_MAX_INBOUND_SPEED);
if (it != params->end ())
m_StreamingInboundSpeed = std::stoi(it->second);
if (it != params->end ())
m_StreamingMaxConcurrentStreams = std::stoi(it->second);
it = params->find (I2CP_PARAM_STREAMING_ANSWER_PINGS);
if (it != params->end ())
m_IsStreamingAnswerPings = std::stoi (it->second); // 1 for true
@ -1135,6 +1101,7 @@ namespace client
void ClientDestination::Stop ()
{
LogPrint(eLogDebug, "Destination: Stopping destination ", GetIdentHash().ToBase32(), ".b32.i2p");
LeaseSetDestination::Stop ();
m_ReadyChecker.cancel();
LogPrint(eLogDebug, "Destination: -> Stopping Streaming Destination");
m_StreamingDestination->Stop ();
@ -1156,12 +1123,10 @@ namespace client
delete m_DatagramDestination;
m_DatagramDestination = nullptr;
}
LeaseSetDestination::Stop ();
LogPrint(eLogDebug, "Destination: -> Stopping done");
}
void ClientDestination::HandleDataMessage (const uint8_t * buf, size_t len,
i2p::garlic::ECIESX25519AEADRatchetSession * from)
void ClientDestination::HandleDataMessage (const uint8_t * buf, size_t len)
{
uint32_t length = bufbe32toh (buf);
if(length > len - 4)
@ -1186,21 +1151,25 @@ namespace client
m_LastPort = toPort;
}
if (m_LastStreamingDestination)
m_LastStreamingDestination->HandleDataMessagePayload (buf, length, from);
m_LastStreamingDestination->HandleDataMessagePayload (buf, length);
else
LogPrint (eLogError, "Destination: Missing streaming destination");
}
break;
case PROTOCOL_TYPE_DATAGRAM:
case PROTOCOL_TYPE_RAW:
case PROTOCOL_TYPE_DATAGRAM2:
case PROTOCOL_TYPE_DATAGRAM3:
// datagram protocol
if (m_DatagramDestination)
m_DatagramDestination->HandleDataMessagePayload (fromPort, toPort, buf, length, buf[9], from);
m_DatagramDestination->HandleDataMessagePayload (fromPort, toPort, buf, length);
else
LogPrint (eLogError, "Destination: Missing datagram destination");
break;
case PROTOCOL_TYPE_RAW:
// raw datagram
if (m_DatagramDestination)
m_DatagramDestination->HandleDataMessagePayload (fromPort, toPort, buf, length, true);
else
LogPrint (eLogError, "Destination: Missing raw datagram destination");
break;
default:
LogPrint (eLogError, "Destination: Data: Unexpected protocol ", buf[9]);
}
@ -1217,7 +1186,7 @@ namespace client
if (leaseSet)
{
auto stream = CreateStream (leaseSet, port);
boost::asio::post (GetService (), [streamRequestComplete, stream]()
GetService ().post ([streamRequestComplete, stream]()
{
streamRequestComplete(stream);
});
@ -1410,56 +1379,32 @@ namespace client
return ret;
}
void ClientDestination::PersistTemporaryKeys (std::shared_ptr<i2p::crypto::LocalEncryptionKey> keys)
void ClientDestination::PersistTemporaryKeys (EncryptionKey * keys, bool isSingleKey)
{
if (!keys) return;
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",
isSingleKey ? (ident + ".dat") : (ident + "." + std::to_string (keys->keyType) + ".dat"));
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)
{
char pub[256], 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)
if (f) {
f.read ((char *)keys->pub, 256);
f.read ((char *)keys->priv, 256);
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");
memset (keys->priv.data (), 0, keys->priv.size ());
memset (keys->pub.data (), 0, keys->pub.size ());
LogPrint (eLogInfo, "Destination: Creating new temporary keys of type for address ", ident, ".b32.i2p");
memset (keys->priv, 0, 256);
memset (keys->pub, 0, 256);
keys->GenerateKeys ();
// TODO:: persist crypto key type
std::ofstream f1 (path, std::ofstream::binary | std::ofstream::out);
if (f1)
{
f1.write ((char *)keys->pub.data (), keys->pub.size ());
f1.write ((char *)keys->priv.data (), keys->priv.size ());
if (f1) {
f1.write ((char *)keys->pub, 256);
f1.write ((char *)keys->priv, 256);
return;
}
if (!f1)
LogPrint(eLogError, "Destination: Can't save keys to ", path);
LogPrint(eLogCritical, "Destinations: Can't save keys to ", path);
}
void ClientDestination::CreateNewLeaseSet (const std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> >& tunnels)
@ -1467,10 +1412,9 @@ namespace client
std::shared_ptr<i2p::data::LocalLeaseSet> leaseSet;
if (GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_LEASESET)
{
auto it = m_EncryptionKeys.find (i2p::data::CRYPTO_KEY_TYPE_ELGAMAL);
if (it != m_EncryptionKeys.end ())
if (m_StandardEncryptionKey)
{
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 (leaseSet->GetBuffer (), leaseSet->GetBufferLen () - leaseSet->GetSignatureLen (), leaseSet->GetSignature ());
}
@ -1480,40 +1424,18 @@ namespace client
else
{
// standard LS2 (type 3) first
if (m_EncryptionKeys.empty ())
{
LogPrint (eLogError, "Destinations: No encryption keys");
return;
}
i2p::data::LocalLeaseSet2::KeySections keySections;
if (m_ECIESx25519EncryptionKey)
keySections.push_back ({m_ECIESx25519EncryptionKey->keyType, 32, m_ECIESx25519EncryptionKey->pub} );
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 ();
if (publishedTimestamp <= m_LastPublishedTimestamp)
{
LogPrint (eLogDebug, "Destination: LeaseSet update at the same second");
publishedTimestamp++; // force newer timestamp
}
bool isPublishedEncrypted = GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_ENCRYPTED_LEASESET2;
auto ls2 = std::make_shared<i2p::data::LocalLeaseSet2> (i2p::data::NETDB_STORE_TYPE_STANDARD_LEASESET2,
m_Keys, keySections, tunnels, IsPublic (), publishedTimestamp, isPublishedEncrypted);
m_Keys, keySections, tunnels, IsPublic (), isPublishedEncrypted);
if (isPublishedEncrypted) // encrypt if type 5
ls2 = std::make_shared<i2p::data::LocalEncryptedLeaseSet2> (ls2, m_Keys, GetAuthType (), m_AuthKeys);
leaseSet = ls2;
m_LastPublishedTimestamp = publishedTimestamp;
}
SetLeaseSet (leaseSet);
}
@ -1525,22 +1447,11 @@ namespace client
bool ClientDestination::Decrypt (const uint8_t * encrypted, uint8_t * data, i2p::data::CryptoKeyType preferredCrypto) const
{
std::shared_ptr<i2p::crypto::LocalEncryptionKey> encryptionKey;
if (!m_EncryptionKeys.empty ())
{
if (m_EncryptionKeys.rbegin ()->first == preferredCrypto)
encryptionKey = m_EncryptionKeys.rbegin ()->second;
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);
if (preferredCrypto == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)
if (m_ECIESx25519EncryptionKey && m_ECIESx25519EncryptionKey->decryptor)
return m_ECIESx25519EncryptionKey->decryptor->Decrypt (encrypted, data);
if (m_StandardEncryptionKey && m_StandardEncryptionKey->decryptor)
return m_StandardEncryptionKey->decryptor->Decrypt (encrypted, data);
else
LogPrint (eLogError, "Destinations: Decryptor is not set");
return false;
@ -1548,26 +1459,14 @@ namespace client
bool ClientDestination::SupportsEncryptionType (i2p::data::CryptoKeyType keyType) const
{
#if __cplusplus >= 202002L // C++20
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;
return keyType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD ? (bool)m_ECIESx25519EncryptionKey : (bool)m_StandardEncryptionKey;
}
const uint8_t * ClientDestination::GetEncryptionPublicKey (i2p::data::CryptoKeyType keyType) const
{
auto it = m_EncryptionKeys.find (keyType);
if (it != m_EncryptionKeys.end ())
return it->second->pub.data ();
return nullptr;
if (keyType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)
return m_ECIESx25519EncryptionKey ? m_ECIESx25519EncryptionKey->pub : nullptr;
return m_StandardEncryptionKey ? m_StandardEncryptionKey->pub : nullptr;
}
void ClientDestination::ReadAuthKey (const std::string& group, const std::map<std::string, std::string> * params)
@ -1601,8 +1500,6 @@ namespace client
RunnableService ("Destination"),
ClientDestination (GetIOService (), keys, isPublic, params)
{
if (!GetNickname ().empty ())
RunnableService::SetName (GetNickname ());
}
RunnableClientDestination::~RunnableClientDestination ()

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
*
@ -22,7 +22,6 @@
#include "Identity.h"
#include "TunnelPool.h"
#include "Crypto.h"
#include "CryptoKey.h"
#include "LeaseSet.h"
#include "Garlic.h"
#include "NetDb.hpp"
@ -37,17 +36,13 @@ namespace client
const uint8_t PROTOCOL_TYPE_STREAMING = 6;
const uint8_t PROTOCOL_TYPE_DATAGRAM = 17;
const uint8_t PROTOCOL_TYPE_RAW = 18;
const uint8_t PROTOCOL_TYPE_DATAGRAM2 = 19;
const uint8_t PROTOCOL_TYPE_DATAGRAM3 = 20;
const int PUBLISH_CONFIRMATION_TIMEOUT = 1800; // in milliseconds
const int PUBLISH_VERIFICATION_TIMEOUT = 5; // in seconds after successful publish
const int PUBLISH_VERIFICATION_TIMEOUT_VARIANCE = 3; // in seconds
const int PUBLISH_CONFIRMATION_TIMEOUT = 5; // in seconds
const int PUBLISH_VERIFICATION_TIMEOUT = 10; // in seconds after successful publish
const int PUBLISH_MIN_INTERVAL = 20; // in seconds
const int PUBLISH_REGULAR_VERIFICATION_INTERNAL = 100; // in seconds periodically
const int LEASESET_REQUEST_TIMEOUT = 1600; // in milliseconds
const int MAX_LEASESET_REQUEST_TIMEOUT = 12000; // in milliseconds
const int DESTINATION_CLEANUP_TIMEOUT = 44; // in seconds
const int DESTINATION_CLEANUP_TIMEOUT_VARIANCE = 30; // in seconds
const int LEASESET_REQUEST_TIMEOUT = 5; // in seconds
const int MAX_LEASESET_REQUEST_TIMEOUT = 40; // in seconds
const int DESTINATION_CLEANUP_TIMEOUT = 3; // in minutes
const unsigned int MAX_NUM_FLOODFILLS_PER_REQUEST = 7;
// I2CP
@ -91,16 +86,8 @@ namespace client
const int DEFAULT_INITIAL_ACK_DELAY = 200; // milliseconds
const char I2CP_PARAM_STREAMING_MAX_OUTBOUND_SPEED[] = "i2p.streaming.maxOutboundSpeed"; // bytes/sec
const int DEFAULT_MAX_OUTBOUND_SPEED = 1730000000; // no more than 1.73 Gbytes/s
const char I2CP_PARAM_STREAMING_MAX_INBOUND_SPEED[] = "i2p.streaming.maxInboundSpeed"; // bytes/sec
const int DEFAULT_MAX_INBOUND_SPEED = 1730000000; // no more than 1.73 Gbytes/s
const char I2CP_PARAM_STREAMING_ANSWER_PINGS[] = "i2p.streaming.answerPings";
const int DEFAULT_ANSWER_PINGS = true;
const char I2CP_PARAM_STREAMING_PROFILE[] = "i2p.streaming.profile";
const int STREAMING_PROFILE_BULK = 1; // high bandwidth
const int STREAMING_PROFILE_INTERACTIVE = 2; // low bandwidth
const int DEFAULT_STREAMING_PROFILE = STREAMING_PROFILE_BULK;
const char I2CP_PARAM_STREAMING_MAX_CONCURRENT_STREAMS[] = "i2p.streaming.maxConcurrentStreams";
const int DEFAULT_MAX_CONCURRENT_STREAMS = 2048;
typedef std::function<void (std::shared_ptr<i2p::stream::Stream> stream)> StreamRequestComplete;
@ -111,7 +98,7 @@ namespace client
// leaseSet = nullptr means not found
struct LeaseSetRequest
{
LeaseSetRequest (boost::asio::io_context& service): requestTime (0), requestTimeoutTimer (service) {};
LeaseSetRequest (boost::asio::io_service& service): requestTime (0), requestTimeoutTimer (service) {};
std::unordered_set<i2p::data::IdentHash> excluded;
uint64_t requestTime;
boost::asio::deadline_timer requestTimeoutTimer;
@ -129,10 +116,10 @@ namespace client
public:
LeaseSetDestination (boost::asio::io_context& service, bool isPublic, const std::map<std::string, std::string> * params = nullptr);
LeaseSetDestination (boost::asio::io_service& service, bool isPublic, const std::map<std::string, std::string> * params = nullptr);
~LeaseSetDestination ();
const std::string& GetNickname () const { return m_Nickname; };
auto& GetService () { return m_Service; };
boost::asio::io_service& GetService () { return m_Service; };
virtual void Start ();
virtual void Stop ();
@ -149,15 +136,15 @@ namespace client
void CancelDestinationRequestWithEncryptedLeaseSet (std::shared_ptr<const i2p::data::BlindedPublicKey> dest, bool notify = true);
// implements GarlicDestination
std::shared_ptr<const i2p::data::LocalLeaseSet> GetLeaseSet () override;
std::shared_ptr<i2p::tunnel::TunnelPool> GetTunnelPool () const override { return m_Pool; }
std::shared_ptr<const i2p::data::LocalLeaseSet> GetLeaseSet ();
std::shared_ptr<i2p::tunnel::TunnelPool> GetTunnelPool () const { return m_Pool; }
// override GarlicDestination
bool SubmitSessionKey (const uint8_t * key, const uint8_t * tag) override;
void SubmitECIESx25519Key (const uint8_t * key, uint64_t tag) override;
void ProcessGarlicMessage (std::shared_ptr<I2NPMessage> msg) override;
void ProcessDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg) override;
void SetLeaseSetUpdated (bool post) override;
bool SubmitSessionKey (const uint8_t * key, const uint8_t * tag);
void SubmitECIESx25519Key (const uint8_t * key, uint64_t tag);
void ProcessGarlicMessage (std::shared_ptr<I2NPMessage> msg);
void ProcessDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg);
void SetLeaseSetUpdated ();
bool IsPublic () const { return m_IsPublic; };
void SetPublic (bool pub) { m_IsPublic = pub; };
@ -165,18 +152,16 @@ namespace client
protected:
// implements GarlicDestination
void HandleI2NPMessage (const uint8_t * buf, size_t len) override;
bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload,
size_t len, uint32_t msgID, i2p::garlic::ECIESX25519AEADRatchetSession * from) override;
void HandleI2NPMessage (const uint8_t * buf, size_t len);
bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len, uint32_t msgID);
void SetLeaseSet (std::shared_ptr<const i2p::data::LocalLeaseSet> newLeaseSet);
int GetLeaseSetType () const { return m_LeaseSetType; };
void SetLeaseSetType (int leaseSetType) { m_LeaseSetType = leaseSetType; };
int GetAuthType () const { return m_AuthType; };
virtual void CleanupDestination () {}; // additional clean up in derived classes
virtual i2p::data::CryptoKeyType GetPreferredCryptoType () const = 0;
// I2CP
virtual void HandleDataMessage (const uint8_t * buf, size_t len, i2p::garlic::ECIESX25519AEADRatchetSession * from) = 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;
private:
@ -187,7 +172,7 @@ namespace client
void HandlePublishConfirmationTimer (const boost::system::error_code& ecode);
void HandlePublishVerificationTimer (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 HandleDeliveryStatusMessage (uint32_t msgID);
@ -197,17 +182,15 @@ namespace client
void HandleRequestTimoutTimer (const boost::system::error_code& ecode, const i2p::data::IdentHash& dest);
void HandleCleanupTimer (const boost::system::error_code& ecode);
void CleanupRemoteLeaseSets ();
i2p::data::CryptoKeyType GetPreferredCryptoType () const;
private:
boost::asio::io_context& m_Service;
boost::asio::io_service& m_Service;
mutable std::mutex m_RemoteLeaseSetsMutex;
std::unordered_map<i2p::data::IdentHash, std::shared_ptr<i2p::data::LeaseSet> > m_RemoteLeaseSets;
std::unordered_map<i2p::data::IdentHash, std::shared_ptr<LeaseSetRequest> > m_LeaseSetRequests;
std::list<std::shared_ptr<I2NPMessage> > m_IncomingMsgsQueue;
mutable std::mutex m_IncomingMsgsQueueMutex;
std::shared_ptr<i2p::tunnel::TunnelPool> m_Pool;
std::mutex m_LeaseSetMutex;
std::shared_ptr<const i2p::data::LocalLeaseSet> m_LeaseSet;
@ -233,14 +216,25 @@ namespace client
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:
ClientDestination (boost::asio::io_context& service, const i2p::data::PrivateKeys& keys,
ClientDestination (boost::asio::io_service& service, const i2p::data::PrivateKeys& keys,
bool isPublic, const std::map<std::string, std::string> * params = nullptr);
~ClientDestination ();
void Start () override;
void Stop () override;
void Start ();
void Stop ();
const i2p::data::PrivateKeys& GetPrivateKeys () const { return m_Keys; };
void Sign (const uint8_t * buf, int len, uint8_t * signature) const { m_Keys.Sign (buf, len, signature); };
@ -268,8 +262,6 @@ namespace client
void AcceptOnce (const i2p::stream::StreamingDestination::Acceptor& acceptor);
int GetStreamingAckDelay () const { return m_StreamingAckDelay; }
int GetStreamingOutboundSpeed () const { return m_StreamingOutboundSpeed; }
int GetStreamingInboundSpeed () const { return m_StreamingInboundSpeed; }
int GetStreamingMaxConcurrentStreams () const { return m_StreamingMaxConcurrentStreams; }
bool IsStreamingAnswerPings () const { return m_IsStreamingAnswerPings; }
// datagram
@ -277,28 +269,24 @@ namespace client
i2p::datagram::DatagramDestination * CreateDatagramDestination (bool gzip = true);
// implements LocalDestination
bool Decrypt (const uint8_t * encrypted, uint8_t * data, i2p::data::CryptoKeyType preferredCrypto) const override;
std::shared_ptr<const i2p::data::IdentityEx> GetIdentity () const override { return m_Keys.GetPublic (); };
bool SupportsEncryptionType (i2p::data::CryptoKeyType keyType) const override;
const uint8_t * GetEncryptionPublicKey (i2p::data::CryptoKeyType keyType) 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 { return m_Keys.GetPublic (); };
bool SupportsEncryptionType (i2p::data::CryptoKeyType keyType) const;
const uint8_t * GetEncryptionPublicKey (i2p::data::CryptoKeyType keyType) const;
protected:
// GarlicDestionation
i2p::data::CryptoKeyType GetRatchetsHighestCryptoType () const override;
// LeaseSetDestination
void CleanupDestination () override;
i2p::data::CryptoKeyType GetPreferredCryptoType () const override { return m_PreferredCryptoType; }
void CleanupDestination ();
// I2CP
void HandleDataMessage (const uint8_t * buf, size_t len, i2p::garlic::ECIESX25519AEADRatchetSession * from) override;
void CreateNewLeaseSet (const std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> >& tunnels) override;
void HandleDataMessage (const uint8_t * buf, size_t len);
void CreateNewLeaseSet (const std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> >& tunnels);
private:
std::shared_ptr<ClientDestination> GetSharedFromThis () {
return std::static_pointer_cast<ClientDestination>(shared_from_this ());
}
void PersistTemporaryKeys (std::shared_ptr<i2p::crypto::LocalEncryptionKey> keys);
void PersistTemporaryKeys (EncryptionKey * keys, bool isSingleKey);
void ReadAuthKey (const std::string& group, const std::map<std::string, std::string> * params);
template<typename Dest>
@ -307,17 +295,17 @@ namespace client
private:
i2p::data::PrivateKeys m_Keys;
std::map<i2p::data::CryptoKeyType, std::shared_ptr<i2p::crypto::LocalEncryptionKey> > m_EncryptionKeys; // last is most preferable
i2p::data::CryptoKeyType m_PreferredCryptoType;
std::unique_ptr<EncryptionKey> m_StandardEncryptionKey;
std::unique_ptr<EncryptionKey> m_ECIESx25519EncryptionKey;
int m_StreamingAckDelay,m_StreamingOutboundSpeed, m_StreamingInboundSpeed, m_StreamingMaxConcurrentStreams;
int m_StreamingAckDelay;
int m_StreamingOutboundSpeed;
bool m_IsStreamingAnswerPings;
std::shared_ptr<i2p::stream::StreamingDestination> m_StreamingDestination; // default
std::map<uint16_t, std::shared_ptr<i2p::stream::StreamingDestination> > m_StreamingDestinationsByPorts;
std::shared_ptr<i2p::stream::StreamingDestination> m_LastStreamingDestination; uint16_t m_LastPort; // for server tunnels
i2p::datagram::DatagramDestination * m_DatagramDestination;
int m_RefCounter; // how many clients(tunnels) use this destination
uint64_t m_LastPublishedTimestamp;
boost::asio::deadline_timer m_ReadyChecker;

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
*
@ -11,7 +11,6 @@
#include "Log.h"
#include "util.h"
#include "Crypto.h"
#include "PostQuantum.h"
#include "Elligator.h"
#include "Tag.h"
#include "I2PEndian.h"
@ -95,17 +94,6 @@ namespace garlic
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 ()
{
if (!m_ExpirationTimestamp)
@ -174,12 +162,12 @@ namespace garlic
return false;
}
if (m_Destination)
m_Destination->HandleECIESx25519GarlicClove (buf + offset, size, nullptr);
m_Destination->HandleECIESx25519GarlicClove (buf + offset, size);
return true;
}
ECIESX25519AEADRatchetSession::ECIESX25519AEADRatchetSession (GarlicDestination * owner, bool attachLeaseSetNS):
GarlicRoutingSession (owner, true), m_RemoteStaticKeyType (0)
GarlicRoutingSession (owner, true)
{
if (!attachLeaseSetNS) SetLeaseSetUpdateStatus (eLeaseSetUpToDate);
RAND_bytes (m_PaddingSizes, 32); m_NextPaddingSize = 0;
@ -241,42 +229,12 @@ namespace garlic
tagsetNsr->NextSessionTagRatchet ();
}
bool ECIESX25519AEADRatchetSession::MessageConfirmed (uint32_t msgID)
{
auto ret = GarlicRoutingSession::MessageConfirmed (msgID); // LeaseSet
if (m_AckRequestMsgID && m_AckRequestMsgID == msgID)
{
m_AckRequestMsgID = 0;
m_AckRequestNumAttempts = 0;
ret = true;
}
return ret;
}
bool ECIESX25519AEADRatchetSession::CleanupUnconfirmedTags ()
{
if (m_AckRequestMsgID && m_AckRequestNumAttempts > ECIESX25519_ACK_REQUEST_MAX_NUM_ATTEMPTS)
{
m_AckRequestMsgID = 0;
m_AckRequestNumAttempts = 0;
return true;
}
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)
{
if (!GetOwner ()) return false;
// we are Bob
// KDF1
i2p::crypto::InitNoiseIKState (GetNoiseState (), GetOwner ()->GetEncryptionPublicKey (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)); // bpk
if (!i2p::crypto::GetElligator ()->Decode (buf, m_Aepk))
{
@ -284,61 +242,20 @@ namespace garlic
return false;
}
buf += 32; len -= 32;
MixHash (m_Aepk, 32); // h = SHA256(h || aepk)
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)
{
LogPrint (eLogWarning, "Garlic: Incorrect Alice ephemeral key");
return false;
}
MixKey (sharedSecret);
}
else
{
LogPrint (eLogWarning, "Garlic: No supported encryption type");
return false;
}
}
// decrypt flags/static
uint8_t fs[32];
if (!Decrypt (buf, fs, 32))
uint8_t nonce[12], 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 ");
return false;
@ -351,18 +268,20 @@ namespace garlic
if (isStatic)
{
// static key, fs is apk
SetRemoteStaticKey (cryptoType, fs);
if (!GetOwner ()->Decrypt (fs, sharedSecret, m_RemoteStaticKeyType)) // x25519(bsk, apk)
memcpy (m_RemoteStaticKey, fs, 32);
if (!GetOwner ()->Decrypt (fs, sharedSecret, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) // x25519(bsk, apk)
{
LogPrint (eLogWarning, "Garlic: Incorrect Alice static key");
return false;
}
MixKey (sharedSecret);
}
else // all zeros flags
CreateNonce (1, nonce);
// decrypt payload
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");
return false;
@ -398,7 +317,7 @@ namespace garlic
{
case eECIESx25519BlkGalicClove:
if (GetOwner ())
GetOwner ()->HandleECIESx25519GarlicClove (buf + offset, size, this);
GetOwner ()->HandleECIESx25519GarlicClove (buf + offset, size);
break;
case eECIESx25519BlkNextKey:
LogPrint (eLogDebug, "Garlic: Next key");
@ -416,7 +335,7 @@ namespace garlic
{
uint32_t tagsetid = bufbe16toh (buf + offset1); offset1 += 2; // tagsetid
uint16_t n = bufbe16toh (buf + offset1); offset1 += 2; // N
MessageConfirmed ((tagsetid << 16) + n); // msgid = (tagsetid << 16) + N
MessageConfirmed ((tagsetid << 16) + n); // msgid
}
break;
}
@ -479,6 +398,7 @@ namespace garlic
{
uint16_t keyID = bufbe16toh (buf); buf += 2; // keyID
bool newKey = flag & ECIESX25519_NEXT_KEY_REQUEST_REVERSE_KEY_FLAG;
m_SendReverseKey = true;
if (!m_NextReceiveRatchet)
m_NextReceiveRatchet.reset (new DHRatchet ());
else
@ -490,14 +410,15 @@ namespace garlic
}
m_NextReceiveRatchet->keyID = keyID;
}
int tagsetID = 2*keyID;
if (newKey)
{
m_NextReceiveRatchet->key = i2p::transport::transports.GetNextX25519KeysPair ();
m_NextReceiveRatchet->newKey = true;
tagsetID++;
}
else
m_NextReceiveRatchet->newKey = false;
auto tagsetID = m_NextReceiveRatchet->GetReceiveTagSetID ();
if (flag & ECIESX25519_NEXT_KEY_KEY_PRESENT_FLAG)
memcpy (m_NextReceiveRatchet->remote, buf, 32);
@ -511,9 +432,7 @@ namespace garlic
GenerateMoreReceiveTags (newTagset, (GetOwner () && GetOwner ()->GetNumRatchetInboundTags () > 0) ?
GetOwner ()->GetNumRatchetInboundTags () : ECIESX25519_MAX_NUM_GENERATED_TAGS);
receiveTagset->Expire ();
LogPrint (eLogDebug, "Garlic: Next receive tagset ", tagsetID, " created");
m_SendReverseKey = true;
}
}
@ -550,15 +469,6 @@ namespace garlic
offset += 32;
// 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
MixHash (m_EphemeralKeys->GetPublicKey (), 32); // h = SHA256(h || aepk)
uint8_t sharedSecret[32];
@ -568,32 +478,18 @@ namespace garlic
return false;
}
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
uint8_t nonce[12];
CreateNonce (0, nonce);
const uint8_t * fs;
if (isStatic)
fs = GetOwner ()->GetEncryptionPublicKey (m_RemoteStaticKeyType);
fs = GetOwner ()->GetEncryptionPublicKey (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD);
else
{
memset (out + offset, 0, 32); // all zeros flags section
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 ");
return false;
@ -604,11 +500,13 @@ namespace garlic
// KDF2
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);
}
else
CreateNonce (1, nonce);
// 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");
return false;
@ -657,33 +555,16 @@ namespace garlic
return false;
}
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)
{
LogPrint (eLogWarning, "Garlic: Incorrect Alice static key");
return false;
}
MixKey (sharedSecret);
uint8_t nonce[12];
CreateNonce (0, nonce);
// 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");
return false;
@ -704,7 +585,6 @@ namespace garlic
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)
// 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
{
LogPrint (eLogWarning, "Garlic: NSR payload section AEAD encryption failed");
@ -726,34 +606,16 @@ namespace garlic
memcpy (m_H, m_NSRH, 32);
MixHash ((const uint8_t *)&tag, 8); // h = SHA256(h || tag)
MixHash (m_EphemeralKeys->GetPublicKey (), 32); // h = SHA256(h || bepk)
m_N = 0;
size_t offset = 40;
#if OPENSSL_PQ
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)
uint8_t nonce[12];
CreateNonce (0, nonce);
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)
{
LogPrint (eLogWarning, "Garlic: Reply key section AEAD encryption failed");
return false;
}
MixHash (out + offset, 16); // h = SHA256(h || ciphertext)
MixHash (out + 40, 16); // h = SHA256(h || ciphertext)
// encrypt payload
uint8_t nonce[12]; memset (nonce, 0, 12);
if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, m_H, 32, m_NSRKey, nonce, out + offset + 16, len + 16, true)) // encrypt
if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, m_H, 32, m_NSRKey, nonce, out + 56, len + 16, true)) // encrypt
{
LogPrint (eLogWarning, "Garlic: Next NSR payload section AEAD encryption failed");
return false;
@ -785,30 +647,13 @@ namespace garlic
return false;
}
MixKey (sharedSecret);
#if OPENSSL_PQ
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)
GetOwner ()->Decrypt (bepk, sharedSecret, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD); // x25519 (ask, bepk)
MixKey (sharedSecret);
uint8_t nonce[12];
CreateNonce (0, nonce);
// 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");
return false;
@ -833,7 +678,6 @@ namespace garlic
}
i2p::crypto::HKDF (keydata + 32, nullptr, 0, "AttachPayloadKDF", keydata, 32); // k = HKDF(k_ba, ZEROLEN, "AttachPayloadKDF", 32)
// 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
{
LogPrint (eLogWarning, "Garlic: Payload section AEAD decryption failed");
@ -843,8 +687,7 @@ namespace garlic
if (m_State == eSessionStateNewSessionSent)
{
m_State = eSessionStateEstablished;
// don't delete m_EpehemralKey and m_PQKeys because delayd NSR's migth come
// done in CleanupReceiveNSRKeys called from NSR tagset destructor
//m_EphemeralKeys = nullptr; // TODO: delete after a while
m_SessionCreatedTimestamp = i2p::util::GetSecondsSinceEpoch ();
GetOwner ()->AddECIESx25519Session (m_RemoteStaticKey, shared_from_this ());
}
@ -859,8 +702,6 @@ namespace garlic
bool ECIESX25519AEADRatchetSession::NewExistingSessionMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen)
{
auto owner = GetOwner ();
if (!owner) return false;
uint8_t nonce[12];
auto index = m_SendTagset->GetNextIndex ();
CreateNonce (index, nonce); // tag's index
@ -868,7 +709,8 @@ namespace garlic
if (!tag)
{
LogPrint (eLogError, "Garlic: Can't create new ECIES-X25519-AEAD-Ratchet tag for send tagset");
owner->RemoveECIESx25519Session (m_RemoteStaticKey);
if (GetOwner ())
GetOwner ()->RemoveECIESx25519Session (m_RemoteStaticKey);
return false;
}
memcpy (out, &tag, 8);
@ -876,7 +718,7 @@ namespace garlic
// ciphertext = ENCRYPT(k, n, payload, ad)
uint8_t key[32];
m_SendTagset->GetSymmKey (index, key);
if (!owner->AEADChaCha20Poly1305Encrypt (payload, len, out, 8, key, nonce, out + 8, outLen - 8))
if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, out, 8, key, nonce, out + 8, outLen - 8, true)) // encrypt
{
LogPrint (eLogWarning, "Garlic: Payload section AEAD encryption failed");
return false;
@ -895,27 +737,24 @@ namespace garlic
uint8_t * payload = buf + 8;
uint8_t key[32];
receiveTagset->GetSymmKey (index, key);
auto owner = GetOwner ();
if (!owner) return true; // drop message
if (!owner->AEADChaCha20Poly1305Decrypt (payload, len - 16, buf, 8, key, nonce, payload, len - 16))
if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len - 16, buf, 8, key, nonce, payload, len - 16, false)) // decrypt
{
LogPrint (eLogWarning, "Garlic: Payload section AEAD decryption failed");
return false;
}
HandlePayload (payload, len - 16, receiveTagset, index);
int moreTags = 0;
if (owner->GetNumRatchetInboundTags () > 0) // override in settings?
if (GetOwner ())
{
if (receiveTagset->GetNextIndex () - index < owner->GetNumRatchetInboundTags ()/2)
moreTags = owner->GetNumRatchetInboundTags ();
index -= owner->GetNumRatchetInboundTags (); // trim behind
int moreTags = 0;
if (GetOwner ()->GetNumRatchetInboundTags () > 0) // override in settings?
{
if (receiveTagset->GetNextIndex () - index < GetOwner ()->GetNumRatchetInboundTags ()/2)
moreTags = GetOwner ()->GetNumRatchetInboundTags ();
index -= GetOwner ()->GetNumRatchetInboundTags (); // trim behind
}
else
{
moreTags = (receiveTagset->GetTagSetID () > 0) ? ECIESX25519_MAX_NUM_GENERATED_TAGS : // for non first tagset
(ECIESX25519_MIN_NUM_GENERATED_TAGS + (index >> 1)); // N/2
moreTags = ECIESX25519_MIN_NUM_GENERATED_TAGS + (index >> 2); // N/4
if (moreTags > ECIESX25519_MAX_NUM_GENERATED_TAGS) moreTags = ECIESX25519_MAX_NUM_GENERATED_TAGS;
moreTags -= (receiveTagset->GetNextIndex () - index);
index -= ECIESX25519_MAX_NUM_GENERATED_TAGS; // trim behind
@ -924,6 +763,7 @@ namespace garlic
GenerateMoreReceiveTags (receiveTagset, moreTags);
if (index > 0)
receiveTagset->SetTrimBehind (index);
}
return true;
}
@ -937,14 +777,10 @@ namespace garlic
m_State = eSessionStateEstablished;
m_NSRSendTagset = nullptr;
m_EphemeralKeys = nullptr;
#if OPENSSL_PQ
m_PQKeys = nullptr;
m_NSREncodedPQKey = nullptr;
#endif
#if (__cplusplus >= 201703L) // C++ 17 or higher
[[fallthrough]];
#endif
case eSessionStateEstablished:
if (m_SendReverseKey && receiveTagset->GetTagSetID () == m_NextReceiveRatchet->GetReceiveTagSetID ())
m_SendReverseKey = false; // tag received on new tagset
if (receiveTagset->IsNS ())
{
// our of sequence NSR
@ -971,12 +807,7 @@ namespace garlic
if (!payload) return nullptr;
size_t len = CreatePayload (msg, m_State != eSessionStateEstablished, payload);
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
#endif
m->Align (12); // in order to get buf aligned to 16 (12 + 4)
uint8_t * buf = m->GetPayload () + 4; // 4 bytes for length
@ -991,28 +822,16 @@ namespace garlic
if (!NewOutgoingSessionMessage (payload, len, buf, m->maxLen))
return nullptr;
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;
case eSessionStateNewSessionReceived:
if (!NewSessionReplyMessage (payload, len, buf, m->maxLen))
return nullptr;
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;
case eSessionStateNewSessionReplySent:
if (!NextNewSessionReplyMessage (payload, len, buf, m->maxLen))
return nullptr;
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;
case eSessionStateOneTime:
if (!NewOutgoingSessionMessage (payload, len, buf, m->maxLen, false))
@ -1039,7 +858,6 @@ namespace garlic
{
uint64_t ts = i2p::util::GetMillisecondsSinceEpoch ();
size_t payloadLen = 0;
bool sendAckRequest = false;
if (first) payloadLen += 7;// datatime
if (msg)
{
@ -1058,28 +876,13 @@ namespace garlic
payloadLen += leaseSet->GetBufferLen () + DATABASE_STORE_HEADER_SIZE + 13;
if (!first)
{
// ack request for LeaseSet
m_AckRequestMsgID = m_SendTagset->GetMsgID ();
sendAckRequest = true;
// update LeaseSet status
// ack request
SetLeaseSetUpdateStatus (eLeaseSetSubmitted);
SetLeaseSetUpdateMsgID (m_AckRequestMsgID);
SetLeaseSetUpdateMsgID ((m_SendTagset->GetTagSetID () << 16) + m_SendTagset->GetNextIndex ()); // (tagsetid << 16) + N
SetLeaseSetSubmissionTime (ts);
payloadLen += 4;
}
}
if (!sendAckRequest && !first &&
((!m_AckRequestMsgID && ts > m_LastAckRequestSendTime + m_AckRequestInterval) || // regular request
(m_AckRequestMsgID && ts > m_LastAckRequestSendTime + LEASESET_CONFIRMATION_TIMEOUT))) // previous request failed. try again
{
// not LeaseSet
m_AckRequestMsgID = m_SendTagset->GetMsgID ();
if (m_AckRequestMsgID)
{
m_AckRequestNumAttempts++;
sendAckRequest = true;
}
}
if (sendAckRequest) payloadLen += 4;
if (m_AckRequests.size () > 0)
payloadLen += m_AckRequests.size ()*4 + 3;
if (m_SendReverseKey)
@ -1131,14 +934,15 @@ namespace garlic
}
// LeaseSet
if (leaseSet)
offset += CreateLeaseSetClove (leaseSet, ts, payload + offset, payloadLen - offset);
// ack request
if (sendAckRequest)
{
offset += CreateLeaseSetClove (leaseSet, ts, payload + offset, payloadLen - offset);
if (!first)
{
// ack request
payload[offset] = eECIESx25519BlkAckRequest; offset++;
htobe16buf (payload + offset, 1); offset += 2;
payload[offset] = 0; offset++; // flags
m_LastAckRequestSendTime = ts;
}
}
// msg
if (msg)
@ -1174,6 +978,7 @@ namespace garlic
memcpy (payload + offset, m_NextReceiveRatchet->key->GetPublicKey (), 32);
offset += 32; // public key
}
m_SendReverseKey = false;
}
if (m_SendForwardKey)
{
@ -1269,8 +1074,6 @@ namespace garlic
bool ECIESX25519AEADRatchetSession::CheckExpired (uint64_t ts)
{
CleanupUnconfirmedLeaseSet (ts);
if (!m_Destination && ts > m_LastActivityTimestamp + ECIESX25519_SESSION_CREATE_TIMEOUT) return true; // m_LastActivityTimestamp is NS receive time
if (m_State != eSessionStateEstablished && m_SessionCreatedTimestamp && ts > m_SessionCreatedTimestamp + ECIESX25519_SESSION_ESTABLISH_TIMEOUT) return true;
return ts > m_LastActivityTimestamp + ECIESX25519_RECEIVE_EXPIRATION_TIMEOUT && // seconds
ts*1000 > m_LastSentTimestamp + ECIESX25519_SEND_EXPIRATION_TIMEOUT*1000; // 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
*
@ -14,12 +14,10 @@
#include <functional>
#include <memory>
#include <vector>
#include <array>
#include <list>
#include <unordered_map>
#include "Identity.h"
#include "Crypto.h"
#include "PostQuantum.h"
#include "Garlic.h"
#include "Tag.h"
@ -32,14 +30,10 @@ namespace garlic
const int ECIESX25519_SEND_INACTIVITY_TIMEOUT = 5000; // number of milliseconds we can send empty(pyaload only) packet after
const int ECIESX25519_SEND_EXPIRATION_TIMEOUT = 480; // in seconds
const int ECIESX25519_RECEIVE_EXPIRATION_TIMEOUT = 600; // in seconds
const int ECIESX25519_SESSION_CREATE_TIMEOUT = 3; // in seconds, NSR must be send after NS received
const int ECIESX25519_SESSION_ESTABLISH_TIMEOUT = 15; // in seconds
const int ECIESX25519_PREVIOUS_TAGSET_EXPIRATION_TIMEOUT = 180; // in seconds
const int ECIESX25519_DEFAULT_ACK_REQUEST_INTERVAL = 33000; // in milliseconds
const int ECIESX25519_ACK_REQUEST_MAX_NUM_ATTEMPTS = 3;
const int ECIESX25519_PREVIOUS_TAGSET_EXPIRATION_TIMEOUT = 180; // 180
const int ECIESX25519_TAGSET_MAX_NUM_TAGS = 8192; // number of tags we request new tagset after
const int ECIESX25519_MIN_NUM_GENERATED_TAGS = 24;
const int ECIESX25519_MAX_NUM_GENERATED_TAGS = 800;
const int ECIESX25519_MAX_NUM_GENERATED_TAGS = 320;
const int ECIESX25519_NSR_NUM_GENERATED_TAGS = 12;
const size_t ECIESX25519_OPTIMAL_PAYLOAD_SIZE = 1912; // 1912 = 1956 /* to fit 2 tunnel messages */
@ -63,8 +57,6 @@ namespace garlic
int GetTagSetID () const { return m_TagSetID; };
void SetTagSetID (int tagsetID) { m_TagSetID = tagsetID; };
uint32_t GetMsgID () const { return (m_TagSetID << 16) + m_NextIndex; }; // (tagsetid << 16) + N
private:
i2p::data::Tag<64> m_SessionTagKeyData;
@ -81,8 +73,8 @@ namespace garlic
{
public:
ReceiveRatchetTagSet (std::shared_ptr<ECIESX25519AEADRatchetSession> session, bool isNS = false);
~ReceiveRatchetTagSet () override;
ReceiveRatchetTagSet (std::shared_ptr<ECIESX25519AEADRatchetSession> session, bool isNS = false):
m_Session (session), m_IsNS (isNS) {};
bool IsNS () const { return m_IsNS; };
std::shared_ptr<ECIESX25519AEADRatchetSession> GetSession () { return m_Session; };
@ -157,7 +149,6 @@ namespace garlic
std::shared_ptr<i2p::crypto::X25519Keys> key;
uint8_t remote[32]; // last remote public key
bool newKey = true;
int GetReceiveTagSetID () const { return newKey ? (2*keyID + 1) : 2*keyID; }
};
public:
@ -166,32 +157,26 @@ namespace garlic
~ECIESX25519AEADRatchetSession ();
bool HandleNextMessage (uint8_t * buf, size_t len, std::shared_ptr<ReceiveRatchetTagSet> receiveTagset, int index = 0);
std::shared_ptr<I2NPMessage> WrapSingleMessage (std::shared_ptr<const I2NPMessage> msg) override;
std::shared_ptr<I2NPMessage> WrapSingleMessage (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; }
i2p::data::CryptoKeyType GetRemoteStaticKeyType () const { return m_RemoteStaticKeyType; }
void SetRemoteStaticKey (i2p::data::CryptoKeyType keyType, const uint8_t * key)
{
m_RemoteStaticKeyType = keyType;
memcpy (m_RemoteStaticKey, key, 32);
}
void SetRemoteStaticKey (const uint8_t * key) { memcpy (m_RemoteStaticKey, key, 32); }
void Terminate () { m_IsTerminated = true; }
void SetDestination (const i2p::data::IdentHash& dest)
void SetDestination (const i2p::data::IdentHash& dest) // TODO:
{
if (!m_Destination) m_Destination.reset (new i2p::data::IdentHash (dest));
}
bool CheckExpired (uint64_t ts); // true is expired
bool CanBeRestarted (uint64_t ts) const { return ts > m_SessionCreatedTimestamp + ECIESX25519_RESTART_TIMEOUT; }
bool IsInactive (uint64_t ts) const { return ts > m_LastActivityTimestamp + ECIESX25519_INACTIVITY_TIMEOUT && CanBeRestarted (ts); }
void CleanupReceiveNSRKeys (); // called from ReceiveRatchetTagSet at Alice's side
bool IsRatchets () const override { return true; };
bool IsReadyToSend () const override { return m_State != eSessionStateNewSessionSent; };
bool IsTerminated () const override { return m_IsTerminated; }
uint64_t GetLastActivityTimestamp () const override { return m_LastActivityTimestamp; };
void SetAckRequestInterval (int interval) override { m_AckRequestInterval = interval; };
bool CleanupUnconfirmedTags () override; // return true if unaswered Ack requests, called from I2CP
bool IsRatchets () const { return true; };
bool IsReadyToSend () const { return m_State != eSessionStateNewSessionSent; };
bool IsTerminated () const { return m_IsTerminated; }
uint64_t GetLastActivityTimestamp () const { return m_LastActivityTimestamp; };
protected:
@ -199,7 +184,6 @@ namespace garlic
void SetNoiseState (const i2p::crypto::NoiseSymmetricState& state) { GetNoiseState () = state; };
void CreateNonce (uint64_t seqn, uint8_t * nonce);
void HandlePayload (const uint8_t * buf, size_t len, const std::shared_ptr<ReceiveRatchetTagSet>& receiveTagset, int index);
bool MessageConfirmed (uint32_t msgID) override;
private:
@ -225,30 +209,20 @@ namespace garlic
private:
i2p::data::CryptoKeyType m_RemoteStaticKeyType;
uint8_t m_RemoteStaticKey[32];
uint8_t m_Aepk[32]; // Alice's ephemeral keys, for incoming only
uint8_t m_NSREncodedKey[32], m_NSRH[32], m_NSRKey[32]; // new session reply, for incoming only
std::shared_ptr<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;
uint64_t m_SessionCreatedTimestamp = 0, m_LastActivityTimestamp = 0, // incoming (in seconds)
m_LastSentTimestamp = 0; // in milliseconds
std::shared_ptr<RatchetTagSet> m_SendTagset, m_NSRSendTagset;
std::unique_ptr<i2p::data::IdentHash> m_Destination;// must be set for NS if outgoing and NSR if incoming
std::list<std::pair<uint16_t, int> > m_AckRequests; // incoming (tagsetid, index)
std::unique_ptr<i2p::data::IdentHash> m_Destination;// TODO: might not need it
std::list<std::pair<uint16_t, int> > m_AckRequests; // (tagsetid, index)
bool m_SendReverseKey = false, m_SendForwardKey = false, m_IsTerminated = false;
std::unique_ptr<DHRatchet> m_NextReceiveRatchet, m_NextSendRatchet;
uint8_t m_PaddingSizes[32], m_NextPaddingSize;
uint64_t m_LastAckRequestSendTime = 0; // milliseconds
uint32_t m_AckRequestMsgID = 0;
int m_AckRequestNumAttempts = 0;
int m_AckRequestInterval = ECIESX25519_DEFAULT_ACK_REQUEST_INTERVAL; // milliseconds
public:
// for HTTP only

View file

@ -1,12 +1,12 @@
/*
* 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
*
* See full license text in LICENSE file at top of project tree
*/
#include <openssl/evp.h>
#include <openssl/sha.h>
#include "Log.h"
#include "Crypto.h"
#include "Ed25519.h"
@ -134,27 +134,22 @@ namespace crypto
{
BN_CTX * bnCtx = BN_CTX_new ();
// calculate r
EVP_MD_CTX * ctx = EVP_MD_CTX_create ();
EVP_DigestInit_ex (ctx, EVP_sha512(), NULL);
EVP_DigestUpdate (ctx, expandedPrivateKey + EDDSA25519_PRIVATE_KEY_LENGTH, EDDSA25519_PRIVATE_KEY_LENGTH); // right half of expanded key
EVP_DigestUpdate (ctx, buf, len); // data
SHA512_CTX ctx;
SHA512_Init (&ctx);
SHA512_Update (&ctx, expandedPrivateKey + EDDSA25519_PRIVATE_KEY_LENGTH, EDDSA25519_PRIVATE_KEY_LENGTH); // right half of expanded key
SHA512_Update (&ctx, buf, len); // data
uint8_t digest[64];
unsigned int dl = 64;
EVP_DigestFinal_ex (ctx, digest, &dl);
EVP_MD_CTX_destroy (ctx);
SHA512_Final (digest, &ctx);
BIGNUM * r = DecodeBN<32> (digest); // DecodeBN<64> (digest); // for test vectors
// calculate R
uint8_t R[EDDSA25519_SIGNATURE_LENGTH/2]; // we must use separate buffer because signature might be inside buf
EncodePoint (Normalize (MulB (digest, bnCtx), bnCtx), R); // EncodePoint (Mul (B, r, bnCtx), R); // for test vectors
// calculate S
ctx = EVP_MD_CTX_create ();
EVP_DigestInit_ex (ctx, EVP_sha512(), NULL);
EVP_DigestUpdate (ctx, R, EDDSA25519_SIGNATURE_LENGTH/2); // R
EVP_DigestUpdate (ctx, publicKeyEncoded, EDDSA25519_PUBLIC_KEY_LENGTH); // public key
EVP_DigestUpdate (ctx, buf, len); // data
dl = 64;
EVP_DigestFinal_ex (ctx, digest, &dl);
EVP_MD_CTX_destroy (ctx);
SHA512_Init (&ctx);
SHA512_Update (&ctx, R, EDDSA25519_SIGNATURE_LENGTH/2); // R
SHA512_Update (&ctx, publicKeyEncoded, EDDSA25519_PUBLIC_KEY_LENGTH); // public key
SHA512_Update (&ctx, buf, len); // data
SHA512_Final (digest, &ctx);
BIGNUM * h = DecodeBN<64> (digest);
// S = (r + h*a) % l
BIGNUM * a = DecodeBN<EDDSA25519_PRIVATE_KEY_LENGTH> (expandedPrivateKey); // left half of expanded key
@ -174,15 +169,13 @@ namespace crypto
uint8_t T[80];
RAND_bytes (T, 80);
// calculate r = H*(T || publickey || data)
EVP_MD_CTX * ctx = EVP_MD_CTX_create ();
EVP_DigestInit_ex (ctx, EVP_sha512(), NULL);
EVP_DigestUpdate (ctx, T, 80);
EVP_DigestUpdate (ctx, publicKeyEncoded, 32);
EVP_DigestUpdate (ctx, buf, len); // data
SHA512_CTX ctx;
SHA512_Init (&ctx);
SHA512_Update (&ctx, T, 80);
SHA512_Update (&ctx, publicKeyEncoded, 32);
SHA512_Update (&ctx, buf, len); // data
uint8_t digest[64];
unsigned int dl = 64;
EVP_DigestFinal_ex (ctx, digest, &dl);
EVP_MD_CTX_destroy (ctx);
SHA512_Final (digest, &ctx);
BIGNUM * r = DecodeBN<64> (digest);
BN_mod (r, r, l, bnCtx); // % l
EncodeBN (r, digest, 32);
@ -190,14 +183,11 @@ namespace crypto
uint8_t R[EDDSA25519_SIGNATURE_LENGTH/2]; // we must use separate buffer because signature might be inside buf
EncodePoint (Normalize (MulB (digest, bnCtx), bnCtx), R);
// calculate S
ctx = EVP_MD_CTX_create ();
EVP_DigestInit_ex (ctx, EVP_sha512(), NULL);
EVP_DigestUpdate (ctx, R, EDDSA25519_SIGNATURE_LENGTH/2); // R
EVP_DigestUpdate (ctx, publicKeyEncoded, EDDSA25519_PUBLIC_KEY_LENGTH); // public key
EVP_DigestUpdate (ctx, buf, len); // data
dl = 64;
EVP_DigestFinal_ex (ctx, digest, &dl);
EVP_MD_CTX_destroy (ctx);
SHA512_Init (&ctx);
SHA512_Update (&ctx, R, EDDSA25519_SIGNATURE_LENGTH/2); // R
SHA512_Update (&ctx, publicKeyEncoded, EDDSA25519_PUBLIC_KEY_LENGTH); // public key
SHA512_Update (&ctx, buf, len); // data
SHA512_Final (digest, &ctx);
BIGNUM * h = DecodeBN<64> (digest);
// S = (r + h*a) % l
BIGNUM * a = DecodeBN<EDDSA25519_PRIVATE_KEY_LENGTH> (privateKey);
@ -467,6 +457,86 @@ namespace crypto
}
}
#if !OPENSSL_X25519
BIGNUM * Ed25519::ScalarMul (const BIGNUM * u, const BIGNUM * k, BN_CTX * ctx) const
{
BN_CTX_start (ctx);
auto x1 = BN_CTX_get (ctx); BN_copy (x1, u);
auto x2 = BN_CTX_get (ctx); BN_one (x2);
auto z2 = BN_CTX_get (ctx); BN_zero (z2);
auto x3 = BN_CTX_get (ctx); BN_copy (x3, u);
auto z3 = BN_CTX_get (ctx); BN_one (z3);
auto c121666 = BN_CTX_get (ctx); BN_set_word (c121666, 121666);
auto tmp0 = BN_CTX_get (ctx); auto tmp1 = BN_CTX_get (ctx);
unsigned int swap = 0;
auto bits = BN_num_bits (k);
while(bits)
{
--bits;
auto k_t = BN_is_bit_set(k, bits) ? 1 : 0;
swap ^= k_t;
if (swap)
{
std::swap (x2, x3);
std::swap (z2, z3);
}
swap = k_t;
BN_mod_sub(tmp0, x3, z3, q, ctx);
BN_mod_sub(tmp1, x2, z2, q, ctx);
BN_mod_add(x2, x2, z2, q, ctx);
BN_mod_add(z2, x3, z3, q, ctx);
BN_mod_mul(z3, tmp0, x2, q, ctx);
BN_mod_mul(z2, z2, tmp1, q, ctx);
BN_mod_sqr(tmp0, tmp1, q, ctx);
BN_mod_sqr(tmp1, x2, q, ctx);
BN_mod_add(x3, z3, z2, q, ctx);
BN_mod_sub(z2, z3, z2, q, ctx);
BN_mod_mul(x2, tmp1, tmp0, q, ctx);
BN_mod_sub(tmp1, tmp1, tmp0, q, ctx);
BN_mod_sqr(z2, z2, q, ctx);
BN_mod_mul(z3, tmp1, c121666, q, ctx);
BN_mod_sqr(x3, x3, q, ctx);
BN_mod_add(tmp0, tmp0, z3, q, ctx);
BN_mod_mul(z3, x1, z2, q, ctx);
BN_mod_mul(z2, tmp1, tmp0, q, ctx);
}
if (swap)
{
std::swap (x2, x3);
std::swap (z2, z3);
}
BN_mod_inverse (z2, z2, q, ctx);
BIGNUM * res = BN_new (); // not from ctx
BN_mod_mul(res, x2, z2, q, ctx);
BN_CTX_end (ctx);
return res;
}
void Ed25519::ScalarMul (const uint8_t * p, const uint8_t * e, uint8_t * buf, BN_CTX * ctx) const
{
BIGNUM * p1 = DecodeBN<32> (p);
uint8_t k[32];
memcpy (k, e, 32);
k[0] &= 248; k[31] &= 127; k[31] |= 64;
BIGNUM * n = DecodeBN<32> (k);
BIGNUM * q1 = ScalarMul (p1, n, ctx);
EncodeBN (q1, buf, 32);
BN_free (p1); BN_free (n); BN_free (q1);
}
void Ed25519::ScalarMulB (const uint8_t * e, uint8_t * buf, BN_CTX * ctx) const
{
BIGNUM *p1 = BN_new (); BN_set_word (p1, 9);
uint8_t k[32];
memcpy (k, e, 32);
k[0] &= 248; k[31] &= 127; k[31] |= 64;
BIGNUM * n = DecodeBN<32> (k);
BIGNUM * q1 = ScalarMul (p1, n, ctx);
EncodeBN (q1, buf, 32);
BN_free (p1); BN_free (n); BN_free (q1);
}
#endif
void Ed25519::BlindPublicKey (const uint8_t * pub, const uint8_t * seed, uint8_t * blinded)
{
BN_CTX * ctx = BN_CTX_new ();

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2024, The PurpleI2P Project
* Copyright (c) 2013-2020, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -84,7 +84,10 @@ namespace crypto
EDDSAPoint GeneratePublicKey (const uint8_t * expandedPrivateKey, BN_CTX * ctx) const;
EDDSAPoint DecodePublicKey (const uint8_t * buf, BN_CTX * ctx) const;
void EncodePublicKey (const EDDSAPoint& publicKey, uint8_t * buf, BN_CTX * ctx) const;
#if !OPENSSL_X25519
void ScalarMul (const uint8_t * p, const uint8_t * e, uint8_t * buf, BN_CTX * ctx) const; // p is point, e is number for x25519
void ScalarMulB (const uint8_t * e, uint8_t * buf, BN_CTX * ctx) const;
#endif
void BlindPublicKey (const uint8_t * pub, const uint8_t * seed, uint8_t * blinded); // for encrypted LeaseSet2, pub - 32, seed - 64, blinded - 32
void BlindPrivateKey (const uint8_t * priv, const uint8_t * seed, uint8_t * blindedPriv, uint8_t * blindedPub); // for encrypted LeaseSet2, pub - 32, seed - 64, blinded - 32
@ -112,6 +115,11 @@ namespace crypto
BIGNUM * DecodeBN (const uint8_t * buf) const;
void EncodeBN (const BIGNUM * bn, uint8_t * buf, size_t len) const;
#if !OPENSSL_X25519
// for x25519
BIGNUM * ScalarMul (const BIGNUM * p, const BIGNUM * e, BN_CTX * ctx) const;
#endif
private:
BIGNUM * q, * l, * d, * I;

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
*
@ -7,18 +7,13 @@
*/
#include <algorithm>
#include <boost/filesystem.hpp>
#if defined(MAC_OSX)
#if !STD_FILESYSTEM
#include <boost/system/system_error.hpp>
#endif
#include <TargetConditionals.h>
#endif
#if defined(__HAIKU__)
#include <FindDirectory.h>
#endif
#ifdef _WIN32
#include <shlobj.h>
#include <windows.h>
@ -30,14 +25,6 @@
#include "Log.h"
#include "Garlic.h"
#if STD_FILESYSTEM
#include <filesystem>
namespace fs_lib = std::filesystem;
#else
#include <boost/filesystem.hpp>
namespace fs_lib = boost::filesystem;
#endif
namespace i2p {
namespace fs {
std::string appName = "i2pd";
@ -67,17 +54,15 @@ namespace fs {
const std::string GetUTF8DataDir () {
#ifdef _WIN32
int size = MultiByteToWideChar(CP_ACP, 0,
dataDir.c_str(), dataDir.size(), nullptr, 0);
std::wstring utf16Str(size, L'\0');
MultiByteToWideChar(CP_ACP, 0,
dataDir.c_str(), dataDir.size(), &utf16Str[0], size);
int utf8Size = WideCharToMultiByte(CP_UTF8, 0,
utf16Str.c_str(), utf16Str.size(), nullptr, 0, nullptr, nullptr);
std::string utf8Str(utf8Size, '\0');
WideCharToMultiByte(CP_UTF8, 0,
utf16Str.c_str(), utf16Str.size(), &utf8Str[0], utf8Size, nullptr, nullptr);
return utf8Str;
#if (BOOST_VERSION >= 108500)
boost::filesystem::path path (dataDir);
#else
boost::filesystem::wpath path (dataDir);
#endif
auto loc = boost::filesystem::path::imbue(std::locale( std::locale(), new std::codecvt_utf8_utf16<wchar_t>() ) ); // convert path to UTF-8
auto dataDirUTF8 = path.string();
boost::filesystem::path::imbue(loc); // Return locale settings back
return dataDirUTF8;
#else
return dataDir; // linux, osx, android uses UTF-8 by default
#endif
@ -106,10 +91,10 @@ namespace fs {
}
else
{
#if ((BOOST_VERSION >= 108500) || STD_FILESYSTEM)
dataDir = fs_lib::path(commonAppData).string() + "\\" + appName;
#if (BOOST_VERSION >= 108500)
dataDir = boost::filesystem::path(commonAppData).string() + "\\" + appName;
#else
dataDir = fs_lib::wpath(commonAppData).string() + "\\" + appName;
dataDir = boost::filesystem::wpath(commonAppData).string() + "\\" + appName;
#endif
}
#else
@ -135,14 +120,14 @@ namespace fs {
}
else
{
#if ((BOOST_VERSION >= 108500) || STD_FILESYSTEM)
auto execPath = fs_lib::path(localAppData).parent_path();
#if (BOOST_VERSION >= 108500)
auto execPath = boost::filesystem::path(localAppData).parent_path();
#else
auto execPath = fs_lib::wpath(localAppData).parent_path();
auto execPath = boost::filesystem::wpath(localAppData).parent_path();
#endif
// if config file exists in .exe's folder use it
if(fs_lib::exists(execPath/"i2pd.conf")) // TODO: magic string
if(boost::filesystem::exists(execPath/"i2pd.conf")) // TODO: magic string
{
dataDir = execPath.string ();
} else // otherwise %appdata%
@ -158,10 +143,10 @@ namespace fs {
}
else
{
#if ((BOOST_VERSION >= 108500) || STD_FILESYSTEM)
dataDir = fs_lib::path(localAppData).string() + "\\" + appName;
#if (BOOST_VERSION >= 108500)
dataDir = boost::filesystem::path(localAppData).string() + "\\" + appName;
#else
dataDir = fs_lib::wpath(localAppData).string() + "\\" + appName;
dataDir = boost::filesystem::wpath(localAppData).string() + "\\" + appName;
#endif
}
}
@ -173,17 +158,18 @@ namespace fs {
dataDir += "/Library/Application Support/" + appName;
return;
#elif defined(__HAIKU__)
char home[PATH_MAX]; // /boot/home/config/settings
if (find_directory(B_USER_SETTINGS_DIRECTORY, -1, false, home, PATH_MAX) == B_OK)
dataDir = std::string(home) + "/" + appName;
else
char *home = getenv("HOME");
if (home != NULL && strlen(home) > 0) {
dataDir = std::string(home) + "/config/settings/" + appName;
} else {
dataDir = "/tmp/" + appName;
}
return;
#else /* other unix */
#if defined(ANDROID)
const char * ext = getenv("EXTERNAL_STORAGE");
if (!ext) ext = "/sdcard";
if (fs_lib::exists(ext))
if (boost::filesystem::exists(ext))
{
dataDir = std::string (ext) + "/" + appName;
return;
@ -216,16 +202,16 @@ namespace fs {
}
bool Init() {
if (!fs_lib::exists(dataDir))
fs_lib::create_directory(dataDir);
if (!boost::filesystem::exists(dataDir))
boost::filesystem::create_directory(dataDir);
std::string destinations = DataDirPath("destinations");
if (!fs_lib::exists(destinations))
fs_lib::create_directory(destinations);
if (!boost::filesystem::exists(destinations))
boost::filesystem::create_directory(destinations);
std::string tags = DataDirPath("tags");
if (!fs_lib::exists(tags))
fs_lib::create_directory(tags);
if (!boost::filesystem::exists(tags))
boost::filesystem::create_directory(tags);
else
i2p::garlic::CleanUpTagsFiles ();
@ -233,13 +219,13 @@ namespace fs {
}
bool ReadDir(const std::string & path, std::vector<std::string> & files) {
if (!fs_lib::exists(path))
if (!boost::filesystem::exists(path))
return false;
fs_lib::directory_iterator it(path);
fs_lib::directory_iterator end;
boost::filesystem::directory_iterator it(path);
boost::filesystem::directory_iterator end;
for ( ; it != end; it++) {
if (!fs_lib::is_regular_file(it->status()))
if (!boost::filesystem::is_regular_file(it->status()))
continue;
files.push_back(it->path().string());
}
@ -248,42 +234,29 @@ namespace fs {
}
bool Exists(const std::string & path) {
return fs_lib::exists(path);
return boost::filesystem::exists(path);
}
uint32_t GetLastUpdateTime (const std::string & path)
{
if (!fs_lib::exists(path))
if (!boost::filesystem::exists(path))
return 0;
#if STD_FILESYSTEM
std::error_code ec;
auto t = std::filesystem::last_write_time (path, ec);
if (ec) return 0;
/*#if __cplusplus >= 202002L // C++ 20 or higher
const auto sctp = std::chrono::clock_cast<std::chrono::system_clock>(t);
#else */ // TODO: wait until implemented
const auto sctp = std::chrono::time_point_cast<std::chrono::system_clock::duration>(
t - decltype(t)::clock::now() + std::chrono::system_clock::now());
/*#endif */
return std::chrono::system_clock::to_time_t(sctp);
#else
boost::system::error_code ec;
auto t = boost::filesystem::last_write_time (path, ec);
return ec ? 0 : t;
#endif
}
bool Remove(const std::string & path) {
if (!fs_lib::exists(path))
if (!boost::filesystem::exists(path))
return false;
return fs_lib::remove(path);
return boost::filesystem::remove(path);
}
bool CreateDirectory (const std::string& path)
{
if (fs_lib::exists(path) && fs_lib::is_directory (fs_lib::status (path)))
if (boost::filesystem::exists(path) && boost::filesystem::is_directory (boost::filesystem::status (path)))
return true;
return fs_lib::create_directory(path);
return boost::filesystem::create_directory(path);
}
void HashedStorage::SetPlace(const std::string &path) {
@ -291,18 +264,18 @@ namespace fs {
}
bool HashedStorage::Init(const char * chars, size_t count) {
if (!fs_lib::exists(root)) {
fs_lib::create_directories(root);
if (!boost::filesystem::exists(root)) {
boost::filesystem::create_directories(root);
}
for (size_t i = 0; i < count; i++) {
auto p = root + i2p::fs::dirSep + prefix1 + chars[i];
if (fs_lib::exists(p))
if (boost::filesystem::exists(p))
continue;
#if TARGET_OS_SIMULATOR
// ios simulator fs says it is case sensitive, but it is not
boost::system::error_code ec;
if (fs_lib::create_directory(p, ec))
if (boost::filesystem::create_directory(p, ec))
continue;
switch (ec.value()) {
case boost::system::errc::file_exists:
@ -312,7 +285,7 @@ namespace fs {
throw boost::system::system_error( ec, __func__ );
}
#else
if (fs_lib::create_directory(p))
if (boost::filesystem::create_directory(p))
continue; /* ^ throws exception on failure */
#endif
return false;
@ -335,9 +308,9 @@ namespace fs {
void HashedStorage::Remove(const std::string & ident) {
std::string path = Path(ident);
if (!fs_lib::exists(path))
if (!boost::filesystem::exists(path))
return;
fs_lib::remove(path);
boost::filesystem::remove(path);
}
void HashedStorage::Traverse(std::vector<std::string> & files) {
@ -348,12 +321,12 @@ namespace fs {
void HashedStorage::Iterate(FilenameVisitor v)
{
fs_lib::path p(root);
fs_lib::recursive_directory_iterator it(p);
fs_lib::recursive_directory_iterator end;
boost::filesystem::path p(root);
boost::filesystem::recursive_directory_iterator it(p);
boost::filesystem::recursive_directory_iterator end;
for ( ; it != end; it++) {
if (!fs_lib::is_regular_file( it->status() ))
if (!boost::filesystem::is_regular_file( it->status() ))
continue;
const std::string & t = it->path().string();
v(t);

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2024, The PurpleI2P Project
* Copyright (c) 2013-2020, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -15,16 +15,6 @@
#include <sstream>
#include <functional>
#ifndef STD_FILESYSTEM
# if (_WIN32 && __GNUG__) // MinGW GCC somehow incorrectly converts paths
# define STD_FILESYSTEM 0
# elif (!TARGET_OS_SIMULATOR && __has_include(<filesystem>)) // supports std::filesystem
# define STD_FILESYSTEM 1
# else
# define STD_FILESYSTEM 0
# endif
#endif
namespace i2p {
namespace fs {
extern std::string dirSep;

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
*
@ -7,6 +7,7 @@
*/
#include <string.h>
#include <openssl/evp.h>
#include <openssl/ssl.h>
#include "Crypto.h"
#include "FS.h"
@ -24,8 +25,6 @@ namespace data
Families::~Families ()
{
for (auto it : m_SigningKeys)
if (it.second.first) EVP_PKEY_free (it.second.first);
}
void Families::LoadCertificate (const std::string& filename)
@ -48,29 +47,48 @@ namespace data
cn += 3;
char * family = strstr (cn, ".family");
if (family) family[0] = 0;
auto pkey = X509_get_pubkey (cert);
if (pkey)
{
int curve = 0;
#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0
char groupName[20];
if (EVP_PKEY_get_group_name(pkey, groupName, sizeof(groupName), NULL) == 1)
curve = OBJ_txt2nid (groupName);
else
curve = -1;
#endif
if (!curve || curve == NID_X9_62_prime256v1)
{
if (!m_SigningKeys.emplace (cn, std::make_pair(pkey, (int)m_SigningKeys.size () + 1)).second)
{
EVP_PKEY_free (pkey);
LogPrint (eLogError, "Family: Duplicated family name ", cn);
}
auto pkey = X509_get_pubkey (cert);
int keyType = EVP_PKEY_base_id (pkey);
switch (keyType)
{
case EVP_PKEY_DSA:
// TODO:
break;
case EVP_PKEY_EC:
{
EC_KEY * ecKey = EVP_PKEY_get1_EC_KEY (pkey);
if (ecKey)
{
auto group = EC_KEY_get0_group (ecKey);
if (group)
{
int curve = EC_GROUP_get_curve_name (group);
if (curve == NID_X9_62_prime256v1)
{
uint8_t signingKey[64];
BIGNUM * x = BN_new(), * y = BN_new();
EC_POINT_get_affine_coordinates_GFp (group,
EC_KEY_get0_public_key (ecKey), x, y, NULL);
i2p::crypto::bn2buf (x, signingKey, 32);
i2p::crypto::bn2buf (y, signingKey + 32, 32);
BN_free (x); BN_free (y);
verifier = std::make_shared<i2p::crypto::ECDSAP256Verifier>();
verifier->SetPublicKey (signingKey);
}
else
LogPrint (eLogWarning, "Family: elliptic curve ", curve, " is not supported");
}
EC_KEY_free (ecKey);
}
break;
}
default:
LogPrint (eLogWarning, "Family: Certificate key type ", keyType, " is not supported");
}
EVP_PKEY_free (pkey);
if (verifier && cn)
m_SigningKeys.emplace (cn, std::make_pair(verifier, (int)m_SigningKeys.size () + 1));
}
SSL_free (ssl);
}
@ -103,37 +121,23 @@ namespace data
}
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];
size_t len = family.length ();
size_t len = family.length (), signatureLen = strlen (signature);
if (len + 32 > 100)
{
LogPrint (eLogError, "Family: ", family, " is too long");
return false;
}
auto it = m_SigningKeys.find (family);
if (it != m_SigningKeys.end () && it->second.first)
{
memcpy (buf, family.c_str (), len);
memcpy (buf + len, (const uint8_t *)ident, 32);
len += 32;
auto signatureBufLen = Base64ToByteStream (signature, signatureBuf, 64);
if (signatureBufLen == 64)
{
ECDSA_SIG * sig = ECDSA_SIG_new();
ECDSA_SIG_set0 (sig, BN_bin2bn (signatureBuf, 32, NULL), BN_bin2bn (signatureBuf + 32, 32, NULL));
uint8_t sign[72];
uint8_t * s = sign;
auto l = i2d_ECDSA_SIG (sig, &s);
ECDSA_SIG_free(sig);
EVP_MD_CTX * ctx = EVP_MD_CTX_create ();
EVP_DigestVerifyInit (ctx, NULL, EVP_sha256(), NULL, it->second.first);
auto ret = EVP_DigestVerify (ctx, sign, l, buf, len) == 1;
EVP_MD_CTX_destroy (ctx);
return ret;
}
}
Base64ToByteStream (signature, signatureLen, signatureBuf, 64);
auto it = m_SigningKeys.find (family);
if (it != m_SigningKeys.end ())
return it->second.first->Verify (buf, len, signatureBuf);
// TODO: process key
return true;
}
@ -156,40 +160,34 @@ namespace data
{
SSL * ssl = SSL_new (ctx);
EVP_PKEY * pkey = SSL_get_privatekey (ssl);
int curve = 0;
#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0
char groupName[20];
if (EVP_PKEY_get_group_name(pkey, groupName, sizeof(groupName), NULL) == 1)
curve = OBJ_txt2nid (groupName);
else
curve = -1;
#endif
if (!curve || curve == NID_X9_62_prime256v1)
EC_KEY * ecKey = EVP_PKEY_get1_EC_KEY (pkey);
if (ecKey)
{
uint8_t buf[100], sign[72], signature[64];
auto group = EC_KEY_get0_group (ecKey);
if (group)
{
int curve = EC_GROUP_get_curve_name (group);
if (curve == NID_X9_62_prime256v1)
{
uint8_t signingPrivateKey[32], buf[50], signature[64];
i2p::crypto::bn2buf (EC_KEY_get0_private_key (ecKey), signingPrivateKey, 32);
i2p::crypto::ECDSAP256Signer signer (signingPrivateKey);
size_t len = family.length ();
memcpy (buf, family.c_str (), len);
memcpy (buf + len, (const uint8_t *)ident, 32);
len += 32;
size_t l = 72;
EVP_MD_CTX * mdctx = EVP_MD_CTX_create ();
EVP_DigestSignInit (mdctx, NULL, EVP_sha256(), NULL, pkey);
EVP_DigestSign (mdctx, sign, &l, buf, len);
EVP_MD_CTX_destroy (mdctx);
const uint8_t * s1 = sign;
ECDSA_SIG * sig1 = d2i_ECDSA_SIG (NULL, &s1, l);
const BIGNUM * r, * s;
ECDSA_SIG_get0 (sig1, &r, &s);
i2p::crypto::bn2buf (r, signature, 32);
i2p::crypto::bn2buf (s, signature + 32, 32);
ECDSA_SIG_free(sig1);
sig = ByteStreamToBase64 (signature, 64);
signer.Sign (buf, len, signature);
len = Base64EncodingBufferSize (64);
char * b64 = new char[len+1];
len = ByteStreamToBase64 (signature, 64, b64, len);
b64[len] = 0;
sig = b64;
delete[] b64;
}
else
LogPrint (eLogWarning, "Family: elliptic curve ", curve, " is not supported");
}
}
SSL_free (ssl);
}
else

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2025, The PurpleI2P Project
* Copyright (c) 2013-2022, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -11,9 +11,8 @@
#include <map>
#include <string>
#include <string_view>
#include <memory>
#include <openssl/evp.h>
#include "Signature.h"
#include "Identity.h"
namespace i2p
@ -29,7 +28,7 @@ namespace data
~Families ();
void LoadCertificates ();
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;
private:
@ -38,7 +37,7 @@ namespace data
private:
std::map<std::string, std::pair<EVP_PKEY *, FamilyID> > m_SigningKeys; // family -> (verification pkey, id)
std::map<std::string, std::pair<std::shared_ptr<i2p::crypto::Verifier>, FamilyID> > m_SigningKeys; // family -> (verifier, id)
};
std::string CreateFamilySignature (const std::string& family, const IdentHash& ident);

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
*
@ -160,7 +160,7 @@ namespace garlic
uint8_t iv[32]; // IV is first 16 bytes
SHA256(elGamal.preIV, 32, iv);
m_Destination->Encrypt ((uint8_t *)&elGamal, buf);
m_IV = iv;
m_Encryption.SetIV (iv);
buf += 514;
len += 514;
}
@ -170,7 +170,7 @@ namespace garlic
memcpy (buf, tag, 32);
uint8_t iv[32]; // IV is first 16 bytes
SHA256(tag, 32, iv);
m_IV = iv;
m_Encryption.SetIV (iv);
buf += 32;
len += 32;
}
@ -210,7 +210,7 @@ namespace garlic
size_t rem = blockSize % 16;
if (rem)
blockSize += (16-rem); //padding
m_Encryption.Encrypt(buf, blockSize, m_IV, buf);
m_Encryption.Encrypt(buf, blockSize, buf);
return blockSize;
}
@ -426,8 +426,7 @@ namespace garlic
}
GarlicDestination::GarlicDestination (): m_NumTags (32), // 32 tags by default
m_PayloadBuffer (nullptr), m_LastIncomingSessionTimestamp (0),
m_NumRatchetInboundTags (0) // 0 means standard
m_PayloadBuffer (nullptr), m_NumRatchetInboundTags (0) // 0 means standard
{
}
@ -498,8 +497,7 @@ namespace garlic
buf += 4; // length
bool found = false;
bool supportsRatchets = SupportsRatchets ();
if (supportsRatchets)
if (SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD))
// try ECIESx25519 tag
found = HandleECIESx25519TagMessage (buf, length);
if (!found)
@ -515,7 +513,8 @@ namespace garlic
{
uint8_t iv[32]; // IV is first 16 bytes
SHA256(buf, 32, iv);
decryption->Decrypt (buf + 32, length - 32, iv, buf + 32);
decryption->SetIV (iv);
decryption->Decrypt (buf + 32, length - 32, buf + 32);
HandleAESBlock (buf + 32, length - 32, decryption, msg->from);
found = true;
}
@ -533,23 +532,43 @@ namespace garlic
auto decryption = std::make_shared<AESDecryption>(elGamal.sessionKey);
uint8_t iv[32]; // IV is first 16 bytes
SHA256(elGamal.preIV, 32, iv);
decryption->Decrypt(buf + 514, length - 514, iv, buf + 514);
decryption->SetIV (iv);
decryption->Decrypt(buf + 514, length - 514, buf + 514);
HandleAESBlock (buf + 514, length - 514, decryption, msg->from);
}
else if (supportsRatchets)
else if (SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD))
{
// otherwise ECIESx25519
auto ts = i2p::util::GetMillisecondsSinceEpoch ();
if (ts > m_LastIncomingSessionTimestamp + INCOMING_SESSIONS_MINIMAL_INTERVAL)
{
auto session = std::make_shared<ECIESX25519AEADRatchetSession> (this, false); // incoming
if (session->HandleNextMessage (buf, length, nullptr, 0))
m_LastIncomingSessionTimestamp = ts;
else
if (!session->HandleNextMessage (buf, length, nullptr, 0))
{
// try to generate more tags for last tagset
if (m_LastTagset && (m_LastTagset->GetNextIndex () - m_LastTagset->GetTrimBehind () < 3*ECIESX25519_MAX_NUM_GENERATED_TAGS))
{
uint64_t missingTag; memcpy (&missingTag, buf, 8);
auto maxTags = std::max (m_NumRatchetInboundTags, ECIESX25519_MAX_NUM_GENERATED_TAGS);
LogPrint (eLogWarning, "Garlic: Trying to generate more ECIES-X25519-AEAD-Ratchet tags");
for (int i = 0; i < maxTags; i++)
{
auto nextTag = AddECIESx25519SessionNextTag (m_LastTagset);
if (!nextTag)
{
LogPrint (eLogError, "Garlic: Can't create new ECIES-X25519-AEAD-Ratchet tag for last tagset");
break;
}
if (nextTag == missingTag)
{
LogPrint (eLogDebug, "Garlic: Missing ECIES-X25519-AEAD-Ratchet tag was generated");
if (m_LastTagset->HandleNextMessage (buf, length, m_ECIESx25519Tags[nextTag].index))
found = true;
break;
}
}
if (!found) m_LastTagset = nullptr;
}
if (!found)
LogPrint (eLogError, "Garlic: Can't handle ECIES-X25519-AEAD-Ratchet message");
}
else
LogPrint (eLogWarning, "Garlic: Incoming sessions come too often");
}
else
LogPrint (eLogError, "Garlic: Failed to decrypt message");
@ -564,7 +583,9 @@ namespace garlic
auto it = m_ECIESx25519Tags.find (tag);
if (it != m_ECIESx25519Tags.end ())
{
if (!it->second.tagset || !it->second.tagset->HandleNextMessage (buf, len, it->second.index))
if (it->second.tagset && it->second.tagset->HandleNextMessage (buf, len, it->second.index))
m_LastTagset = it->second.tagset;
else
LogPrint (eLogError, "Garlic: Can't handle ECIES-X25519-AEAD-Ratchet message");
m_ECIESx25519Tags.erase (it);
return true;
@ -745,12 +766,10 @@ namespace garlic
}
std::shared_ptr<GarlicRoutingSession> GarlicDestination::GetRoutingSession (
std::shared_ptr<const i2p::data::RoutingDestination> destination, bool attachLeaseSet,
bool requestNewIfNotFound)
std::shared_ptr<const i2p::data::RoutingDestination> destination, bool attachLeaseSet)
{
if (destination->GetEncryptionType () >= i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)
{
if (SupportsEncryptionType (destination->GetEncryptionType ()))
if (destination->GetEncryptionType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD &&
SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD))
{
ECIESX25519AEADRatchetSessionPtr session;
uint8_t staticKey[32];
@ -762,22 +781,18 @@ namespace garlic
if (session->IsInactive (i2p::util::GetSecondsSinceEpoch ()))
{
LogPrint (eLogDebug, "Garlic: Session restarted");
requestNewIfNotFound = true; // it's not a new session
session = nullptr;
}
}
if (!session && requestNewIfNotFound)
if (!session)
{
session = std::make_shared<ECIESX25519AEADRatchetSession> (this, true);
session->SetRemoteStaticKey (destination->GetEncryptionType (), staticKey);
session->SetRemoteStaticKey (staticKey);
}
if (session && destination->IsDestination ())
session->SetDestination (destination->GetIdentHash ()); // NS or NSR
if (destination->IsDestination ())
session->SetDestination (destination->GetIdentHash ()); // TODO: remove
return session;
}
else
LogPrint (eLogError, "Garlic: Non-supported encryption type ", destination->GetEncryptionType ());
}
else
{
ElGamalAESSessionPtr session;
@ -878,6 +893,8 @@ namespace garlic
}
if (numExpiredTags > 0)
LogPrint (eLogDebug, "Garlic: ", numExpiredTags, " ECIESx25519 tags expired for ", GetIdentHash().ToBase64 ());
if (m_LastTagset && m_LastTagset->IsExpired (ts))
m_LastTagset = nullptr;
}
void GarlicDestination::RemoveDeliveryStatusSession (uint32_t msgID)
@ -911,7 +928,7 @@ namespace garlic
}
}
void GarlicDestination::SetLeaseSetUpdated (bool post)
void GarlicDestination::SetLeaseSetUpdated ()
{
{
std::unique_lock<std::mutex> l(m_SessionsMutex);
@ -1004,8 +1021,7 @@ namespace garlic
i2p::fs::Remove (it);
}
void GarlicDestination::HandleECIESx25519GarlicClove (const uint8_t * buf, size_t len,
ECIESX25519AEADRatchetSession * from)
void GarlicDestination::HandleECIESx25519GarlicClove (const uint8_t * buf, size_t len)
{
const uint8_t * buf1 = buf;
uint8_t flag = buf[0]; buf++; // flag
@ -1015,7 +1031,9 @@ namespace garlic
case eGarlicDeliveryTypeDestination:
LogPrint (eLogDebug, "Garlic: Type destination");
buf += 32; // TODO: check destination
#if (__cplusplus >= 201703L) // C++ 17 or higher
[[fallthrough]];
#endif
// no break here
case eGarlicDeliveryTypeLocal:
{
@ -1025,7 +1043,7 @@ namespace garlic
buf += 4; // expiration
ptrdiff_t offset = buf - buf1;
if (offset <= (int)len)
HandleCloveI2NPMessage (typeID, buf, len - offset, msgID, from);
HandleCloveI2NPMessage (typeID, buf, len - offset, msgID);
else
LogPrint (eLogError, "Garlic: Clove is too long");
break;
@ -1109,17 +1127,5 @@ namespace garlic
m_PayloadBuffer = new uint8_t[I2NP_MAX_MESSAGE_SIZE];
return m_PayloadBuffer;
}
bool GarlicDestination::AEADChaCha20Poly1305Encrypt (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen,
const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len)
{
return m_Encryptor.Encrypt (msg, msgLen, ad, adLen, key, nonce, buf, len);
}
bool GarlicDestination::AEADChaCha20Poly1305Decrypt (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen,
const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len)
{
return m_Decryptor.Decrypt (msg, msgLen, ad, adLen, key, nonce, buf, len);
}
}
}

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
*
@ -52,7 +52,6 @@ namespace garlic
const int OUTGOING_TAGS_CONFIRMATION_TIMEOUT = 10; // 10 seconds
const int LEASESET_CONFIRMATION_TIMEOUT = 4000; // in milliseconds
const int ROUTING_PATH_EXPIRATION_TIMEOUT = 120; // in seconds
const int INCOMING_SESSIONS_MINIMAL_INTERVAL = 200; // in milliseconds
struct SessionTag: public i2p::data::Tag<32>
{
@ -110,13 +109,12 @@ namespace garlic
GarlicRoutingSession ();
virtual ~GarlicRoutingSession ();
virtual std::shared_ptr<I2NPMessage> WrapSingleMessage (std::shared_ptr<const I2NPMessage> msg) = 0;
virtual bool CleanupUnconfirmedTags () { return false; }; // for I2CP, override in ElGamalAESSession and ECIESX25519AEADRatchetSession
virtual bool CleanupUnconfirmedTags () { return false; }; // for I2CP, override in ElGamalAESSession
virtual bool MessageConfirmed (uint32_t msgID);
virtual bool IsRatchets () const { return false; };
virtual bool IsReadyToSend () const { return true; };
virtual bool IsTerminated () const { return !GetOwner (); };
virtual uint64_t GetLastActivityTimestamp () const { return 0; }; // non-zero for rathets only
virtual void SetAckRequestInterval (int interval) {}; // in milliseconds, override in ECIESX25519AEADRatchetSession
void SetLeaseSetUpdated ()
{
@ -206,7 +204,6 @@ namespace garlic
std::map<uint32_t, std::unique_ptr<UnconfirmedTags> > m_UnconfirmedTagsMsgs; // msgID->tags
i2p::crypto::CBCEncryption m_Encryption;
i2p::data::Tag<16> m_IV;
public:
@ -237,18 +234,12 @@ namespace garlic
int GetNumTags () const { return m_NumTags; };
void SetNumRatchetInboundTags (int numTags) { m_NumRatchetInboundTags = numTags; };
int GetNumRatchetInboundTags () const { return m_NumRatchetInboundTags; };
std::shared_ptr<GarlicRoutingSession> GetRoutingSession (std::shared_ptr<const i2p::data::RoutingDestination> destination,
bool attachLeaseSet, bool requestNewIfNotFound = true);
std::shared_ptr<GarlicRoutingSession> GetRoutingSession (std::shared_ptr<const i2p::data::RoutingDestination> destination, bool attachLeaseSet);
void CleanupExpiredTags ();
void RemoveDeliveryStatusSession (uint32_t msgID);
std::shared_ptr<I2NPMessage> WrapMessageForRouter (std::shared_ptr<const i2p::data::RouterInfo> router,
std::shared_ptr<I2NPMessage> msg);
bool AEADChaCha20Poly1305Encrypt (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen,
const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len);
bool AEADChaCha20Poly1305Decrypt (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen,
const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len);
void AddSessionKey (const uint8_t * key, const uint8_t * tag); // one tag
void AddECIESx25519Key (const uint8_t * key, uint64_t tag); // one tag
virtual bool SubmitSessionKey (const uint8_t * key, const uint8_t * tag); // from different thread
@ -257,27 +248,22 @@ namespace garlic
uint64_t AddECIESx25519SessionNextTag (ReceiveRatchetTagSetPtr tagset);
void AddECIESx25519Session (const uint8_t * staticKey, ECIESX25519AEADRatchetSessionPtr session);
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 ();
virtual void ProcessGarlicMessage (std::shared_ptr<I2NPMessage> msg);
virtual void ProcessDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg);
virtual void SetLeaseSetUpdated (bool post = false);
virtual void SetLeaseSetUpdated ();
virtual std::shared_ptr<const i2p::data::LocalLeaseSet> GetLeaseSet () = 0; // TODO
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:
void AddECIESx25519Key (const uint8_t * key, const uint8_t * tag); // one tag
bool HandleECIESx25519TagMessage (uint8_t * buf, size_t len); // return true if found
virtual void HandleI2NPMessage (const uint8_t * buf, size_t len) = 0; // called from clove only
virtual bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload,
size_t len, uint32_t msgID, ECIESX25519AEADRatchetSession * from) = 0;
virtual bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len, uint32_t msgID) = 0;
void HandleGarlicMessage (std::shared_ptr<I2NPMessage> msg);
void HandleDeliveryStatusMessage (uint32_t msgID);
@ -286,7 +272,6 @@ namespace garlic
private:
bool SupportsRatchets () const { return GetRatchetsHighestCryptoType () > 0; }
void HandleAESBlock (uint8_t * buf, size_t len, std::shared_ptr<AESDecryption> decryption,
std::shared_ptr<i2p::tunnel::InboundTunnel> from);
void HandleGarlicPayload (uint8_t * buf, size_t len, std::shared_ptr<i2p::tunnel::InboundTunnel> from);
@ -299,17 +284,14 @@ namespace garlic
std::unordered_map<i2p::data::IdentHash, ElGamalAESSessionPtr> m_Sessions;
std::unordered_map<i2p::data::Tag<32>, ECIESX25519AEADRatchetSessionPtr> m_ECIESx25519Sessions; // static key -> session
uint8_t * m_PayloadBuffer; // for ECIESX25519AEADRatchet
uint64_t m_LastIncomingSessionTimestamp; // in milliseconds
// incoming
int m_NumRatchetInboundTags;
std::unordered_map<SessionTag, std::shared_ptr<AESDecryption>, std::hash<i2p::data::Tag<32> > > m_Tags;
std::unordered_map<uint64_t, ECIESX25519AEADRatchetIndexTagset> m_ECIESx25519Tags; // session tag -> session
ReceiveRatchetTagSetPtr m_LastTagset; // tagset last message came for
// DeliveryStatus
std::mutex m_DeliveryStatusSessionsMutex;
std::unordered_map<uint32_t, GarlicRoutingSessionPtr> m_DeliveryStatusSessions; // msgID -> session
// encryption
i2p::crypto::AEADChaCha20Poly1305Encryptor m_Encryptor;
i2p::crypto::AEADChaCha20Poly1305Decryptor m_Decryptor;
public:

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
*
@ -10,7 +10,6 @@
#include <utility>
#include <stdio.h>
#include <ctime>
#include <charconv>
#include "util.h"
#include "Base.h"
#include "HTTP.h"
@ -19,68 +18,58 @@ namespace i2p
{
namespace http
{
// list of valid HTTP methods
static constexpr std::array<std::string_view, 16> HTTP_METHODS =
{
const std::vector<std::string> HTTP_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
};
// list of valid HTTP versions
static constexpr std::array<std::string_view, 2> HTTP_VERSIONS =
{
const std::vector<std::string> HTTP_VERSIONS = {
"HTTP/1.0", "HTTP/1.1"
};
static constexpr std::array<const char *, 7> weekdays =
{
const std::vector<const char *> weekdays = {
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
};
static constexpr std::array<const char *, 12> months =
{
const std::vector<const char *> months = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"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);
}
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);
}
static void strsplit(std::string_view line, std::vector<std::string_view> &tokens, char delim, std::size_t limit = 0)
{
size_t count = 1, pos;
while ((pos = line.find (delim)) != line.npos)
{
void strsplit(const std::string & line, std::vector<std::string> &tokens, char delim, std::size_t limit = 0) {
std::size_t count = 0;
std::stringstream ss(line);
std::string token;
while (1) {
count++;
if (limit > 0 && count >= limit) delim = '\n'; // reset delimiter
tokens.push_back (line.substr (0, pos));
line = line.substr (pos + 1);
if (limit > 0 && count >= limit)
delim = '\n'; /* reset delimiter */
if (!std::getline(ss, token, delim))
break;
tokens.push_back(token);
}
if (!line.empty ()) tokens.push_back (line);
}
static std::pair<std::string, std::string> parse_header_line(std::string_view line)
static std::pair<std::string, std::string> parse_header_line(const std::string& line)
{
std::size_t pos = 0;
std::size_t len = 1; /*: */
std::size_t max = line.length();
if ((pos = line.find(':', pos)) == std::string::npos)
return std::pair{"", ""}; // no ':' found
return std::make_pair("", ""); // no ':' found
if (pos + 1 < max) // ':' at the end of header is valid
{
while ((pos + len) < max && isspace(line.at(pos + len)))
len++;
if (len == 1)
return std::pair{"", ""}; // no following space, but something else
return std::make_pair("", ""); // no following space, but something else
}
return std::pair{std::string (line.substr(0, pos)), std::string (line.substr(pos + len))};
return std::make_pair(line.substr(0, pos), line.substr(pos + len));
}
void gen_rfc7231_date(std::string & out) {
@ -94,18 +83,15 @@ namespace http
out = buf;
}
bool URL::parse(const char *str, std::size_t len)
{
return parse({str, len ? len : strlen(str)});
bool URL::parse(const char *str, std::size_t len) {
std::string url(str, len ? len : strlen(str));
return parse(url);
}
bool URL::parse(std::string_view url)
{
if (url.empty ()) return false;
bool URL::parse(const std::string& url) {
std::size_t pos_p = 0; /* < current parse position */
std::size_t pos_c = 0; /* < work position */
if(url.at(0) != '/' || pos_p > 0)
{
if(url.at(0) != '/' || pos_p > 0) {
std::size_t pos_s = 0;
/* schema */
@ -155,7 +141,7 @@ namespace http
/* port[/path] */
pos_p = pos_c + 1;
pos_c = url.find('/', pos_p);
std::string_view port_str = (pos_c == std::string::npos)
std::string port_str = (pos_c == std::string::npos)
? url.substr(pos_p, std::string::npos)
: url.substr(pos_p, pos_c - pos_p);
/* stoi throws exception on failure, we don't need it */
@ -209,9 +195,8 @@ namespace http
return true;
}
bool URL::parse_query(std::map<std::string, std::string> & params)
{
std::vector<std::string_view> tokens;
bool URL::parse_query(std::map<std::string, std::string> & params) {
std::vector<std::string> tokens;
strsplit(query, tokens, '&');
params.clear();
@ -268,7 +253,7 @@ namespace http
return host.rfind(".i2p") == ( host.size() - 4 );
}
void HTTPMsg::add_header(const char *name, const std::string & value, bool replace) {
void HTTPMsg::add_header(const char *name, std::string & value, bool replace) {
add_header(name, value.c_str(), replace);
}
@ -287,13 +272,12 @@ namespace http
headers.erase(name);
}
int HTTPReq::parse(const char *buf, size_t len)
{
return parse({buf, len});
int HTTPReq::parse(const char *buf, size_t len) {
std::string str(buf, len);
return parse(str);
}
int HTTPReq::parse(std::string_view str)
{
int HTTPReq::parse(const std::string& str) {
enum { REQ_LINE, HEADER_LINE } expect = REQ_LINE;
std::size_t eoh = str.find(HTTP_EOH); /* request head size */
std::size_t eol = 0, pos = 0;
@ -302,14 +286,11 @@ namespace http
if (eoh == std::string::npos)
return 0; /* str not contains complete request */
while ((eol = str.find(CRLF, pos)) != std::string::npos)
{
if (expect == REQ_LINE)
{
std::string_view line = str.substr(pos, eol - pos);
std::vector<std::string_view> tokens;
while ((eol = str.find(CRLF, pos)) != std::string::npos) {
if (expect == REQ_LINE) {
std::string line = str.substr(pos, eol - pos);
std::vector<std::string> tokens;
strsplit(line, tokens, ' ');
if (tokens.size() != 3)
return -1;
if (!is_http_method(tokens[0]))
@ -326,18 +307,18 @@ namespace http
}
else
{
std::string_view line = str.substr(pos, eol - pos);
std::string line = str.substr(pos, eol - pos);
auto p = parse_header_line(line);
if (p.first.length () > 0)
headers.push_back (p);
else
return -1;
}
pos = eol + CRLF.length();
pos = eol + strlen(CRLF);
if (pos >= eoh)
break;
}
return eoh + HTTP_EOH.length();
return eoh + strlen(HTTP_EOH);
}
void HTTPReq::write(std::ostream & o)
@ -381,7 +362,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)
if (it.first == name)
@ -389,7 +370,7 @@ namespace http
return "";
}
size_t HTTPReq::GetNumHeaders (std::string_view name) const
size_t HTTPReq::GetNumHeaders (const std::string& name) const
{
size_t num = 0;
for (auto& it : headers)
@ -432,13 +413,12 @@ namespace http
return length;
}
int HTTPRes::parse(const char *buf, size_t len)
{
return parse({buf,len});
int HTTPRes::parse(const char *buf, size_t len) {
std::string str(buf, len);
return parse(str);
}
int HTTPRes::parse(std::string_view str)
{
int HTTPRes::parse(const std::string& str) {
enum { RES_LINE, HEADER_LINE } expect = RES_LINE;
std::size_t eoh = str.find(HTTP_EOH); /* request head size */
std::size_t eol = 0, pos = 0;
@ -446,41 +426,35 @@ namespace http
if (eoh == std::string::npos)
return 0; /* str not contains complete request */
while ((eol = str.find(CRLF, pos)) != std::string::npos)
{
if (expect == RES_LINE)
{
std::string_view line = str.substr(pos, eol - pos);
std::vector<std::string_view> tokens;
while ((eol = str.find(CRLF, pos)) != std::string::npos) {
if (expect == RES_LINE) {
std::string line = str.substr(pos, eol - pos);
std::vector<std::string> tokens;
strsplit(line, tokens, ' ', 3);
if (tokens.size() != 3)
return -1;
if (!is_http_version(tokens[0]))
return -1;
auto res = std::from_chars(tokens[1].data (), tokens[1].data() + tokens[1].size(), code);
if (res.ec != std::errc())
return -1;
code = atoi(tokens[1].c_str());
if (code < 100 || code >= 600)
return -1;
/* all ok */
version = tokens[0];
status = tokens[2];
expect = HEADER_LINE;
}
else
{
std::string_view line = str.substr(pos, eol - pos);
} else {
std::string line = str.substr(pos, eol - pos);
auto p = parse_header_line(line);
if (p.first.length () > 0)
headers.insert (p);
else
return -1;
}
pos = eol + CRLF.length();
pos = eol + strlen(CRLF);
if (pos >= eoh)
break;
}
return eoh + HTTP_EOH.length();
return eoh + strlen(HTTP_EOH);
}
std::string HTTPRes::to_string() {
@ -505,11 +479,9 @@ namespace http
return ss.str();
}
std::string_view HTTPCodeToStatus(int code)
{
std::string_view ptr;
switch (code)
{
const char * HTTPCodeToStatus(int code) {
const char *ptr;
switch (code) {
case 105: ptr = "Name Not Resolved"; break;
/* success */
case 200: ptr = "OK"; break;
@ -536,14 +508,14 @@ namespace http
return ptr;
}
std::string UrlDecode(std::string_view data, bool allow_null)
std::string UrlDecode(const std::string& data, bool allow_null)
{
std::string decoded(data);
size_t pos = 0;
while ((pos = decoded.find('%', pos)) != std::string::npos)
{
char c = std::stol(decoded.substr(pos + 1, 2), nullptr, 16);
if (!c && !allow_null)
char c = strtol(decoded.substr(pos + 1, 2).c_str(), NULL, 16);
if (c == '\0' && !allow_null)
{
pos += 3;
continue;

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
*
@ -14,15 +14,16 @@
#include <list>
#include <sstream>
#include <string>
#include <string_view>
#include <vector>
namespace i2p
{
namespace http
{
constexpr std::string_view CRLF = "\r\n"; /**< HTTP line terminator */
constexpr std::string_view HTTP_EOH = "\r\n\r\n"; /**< HTTP end-of-headers mark */
const char CRLF[] = "\r\n"; /**< HTTP line terminator */
const char HTTP_EOH[] = "\r\n\r\n"; /**< HTTP end-of-headers mark */
extern const std::vector<std::string> HTTP_METHODS; /**< list of valid HTTP methods */
extern const std::vector<std::string> HTTP_VERSIONS; /**< list of valid HTTP versions */
struct URL
{
@ -44,7 +45,7 @@ namespace http
* @return true on success, false on invalid url
*/
bool parse (const char *str, std::size_t len = 0);
bool parse (std::string_view url);
bool parse (const std::string& url);
/**
* @brief Parse query part of url to key/value map
@ -68,7 +69,7 @@ namespace http
{
std::map<std::string, std::string> headers;
void add_header(const char *name, const std::string & value, bool replace = false);
void add_header(const char *name, std::string & value, bool replace = false);
void add_header(const char *name, const char *value, bool replace = false);
void del_header(const char *name);
@ -91,7 +92,7 @@ namespace http
* @note Positive return value is a size of header
*/
int parse(const char *buf, size_t len);
int parse(std::string_view buf);
int parse(const std::string& buf);
/** @brief Serialize HTTP request to string */
std::string to_string();
@ -101,8 +102,8 @@ namespace http
void UpdateHeader (const std::string& name, const std::string& value);
void RemoveHeader (const std::string& name, const std::string& exempt); // remove all headers starting with name, but exempt
void RemoveHeader (const std::string& name) { RemoveHeader (name, ""); };
std::string GetHeader (std::string_view name) const;
size_t GetNumHeaders (std::string_view name) const;
std::string GetHeader (const std::string& name) const;
size_t GetNumHeaders (const std::string& name) const;
size_t GetNumHeaders () const { return headers.size (); };
};
@ -127,7 +128,7 @@ namespace http
* @note Positive return value is a size of header
*/
int parse(const char *buf, size_t len);
int parse(const std::string_view buf);
int parse(const std::string& buf);
/**
* @brief Serialize HTTP response to string
@ -152,7 +153,7 @@ namespace http
* @param code HTTP code [100, 599]
* @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
@ -160,7 +161,7 @@ namespace http
* @param null If set to true - decode also %00 sequence, otherwise - skip
* @return Decoded string
*/
std::string UrlDecode(std::string_view data, bool null = false);
std::string UrlDecode(const std::string& data, bool null = false);
/**
* @brief Merge HTTP response content with Transfer-Encoding: chunked

View file

@ -10,15 +10,20 @@
#include <atomic>
#include "Base.h"
#include "Log.h"
#include "Crypto.h"
#include "I2PEndian.h"
#include "Timestamp.h"
#include "RouterContext.h"
#include "NetDb.hpp"
#include "Tunnel.h"
#include "TransitTunnel.h"
#include "Transports.h"
#include "Garlic.h"
#include "ECIESX25519AEADRatchetSession.h"
#include "I2NPProtocol.h"
#include "version.h"
using namespace i2p::transport;
namespace i2p
{
std::shared_ptr<I2NPMessage> NewI2NPMessage ()
@ -371,6 +376,337 @@ namespace i2p
return !msg->GetPayload ()[DATABASE_STORE_TYPE_OFFSET]; // 0- RouterInfo
}
static bool HandleBuildRequestRecords (int num, uint8_t * records, uint8_t * clearText)
{
for (int i = 0; i < num; i++)
{
uint8_t * record = records + i*TUNNEL_BUILD_RECORD_SIZE;
if (!memcmp (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)i2p::context.GetRouterInfo ().GetIdentHash (), 16))
{
LogPrint (eLogDebug, "I2NP: Build request record ", i, " is ours");
if (!i2p::context.DecryptTunnelBuildRecord (record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, clearText))
{
LogPrint (eLogWarning, "I2NP: Failed to decrypt tunnel build record");
return false;
}
if (!memcmp ((const uint8_t *)i2p::context.GetIdentHash (), clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, 32) && // if next ident is now ours
!(clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG)) // and not endpoint
{
LogPrint (eLogWarning, "I2NP: Next ident is ours in tunnel build record");
return false;
}
uint8_t retCode = 0;
// replace record to reply
if (i2p::context.AcceptsTunnels () && i2p::context.GetCongestionLevel (false) < CONGESTION_LEVEL_FULL)
{
auto transitTunnel = i2p::tunnel::CreateTransitTunnel (
bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET),
clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET,
bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET),
clearText + ECIES_BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET,
clearText + ECIES_BUILD_REQUEST_RECORD_IV_KEY_OFFSET,
clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_GATEWAY_FLAG,
clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG);
if (!i2p::tunnel::tunnels.AddTransitTunnel (transitTunnel))
retCode = 30;
}
else
retCode = 30; // always reject with bandwidth reason (30)
memset (record + ECIES_BUILD_RESPONSE_RECORD_OPTIONS_OFFSET, 0, 2); // no options
record[ECIES_BUILD_RESPONSE_RECORD_RET_OFFSET] = retCode;
// encrypt reply
i2p::crypto::CBCEncryption encryption;
for (int j = 0; j < num; j++)
{
uint8_t * reply = records + j*TUNNEL_BUILD_RECORD_SIZE;
if (j == i)
{
uint8_t nonce[12];
memset (nonce, 0, 12);
auto& noiseState = i2p::context.GetCurrentNoiseState ();
if (!i2p::crypto::AEADChaCha20Poly1305 (reply, TUNNEL_BUILD_RECORD_SIZE - 16,
noiseState.m_H, 32, noiseState.m_CK, nonce, reply, TUNNEL_BUILD_RECORD_SIZE, true)) // encrypt
{
LogPrint (eLogWarning, "I2NP: Reply AEAD encryption failed");
return false;
}
}
else
{
encryption.SetKey (clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET);
encryption.SetIV (clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_IV_OFFSET);
encryption.Encrypt(reply, TUNNEL_BUILD_RECORD_SIZE, reply);
}
}
return true;
}
}
return false;
}
static void HandleVariableTunnelBuildMsg (uint32_t replyMsgID, uint8_t * buf, size_t len)
{
int num = buf[0];
LogPrint (eLogDebug, "I2NP: VariableTunnelBuild ", num, " records");
if (num > i2p::tunnel::MAX_NUM_RECORDS)
{
LogPrint (eLogError, "I2NP: Too many records in VaribleTunnelBuild message ", num);
return;
}
if (len < num*TUNNEL_BUILD_RECORD_SIZE + 1)
{
LogPrint (eLogError, "I2NP: VaribleTunnelBuild message of ", num, " records is too short ", len);
return;
}
auto tunnel = i2p::tunnel::tunnels.GetPendingInboundTunnel (replyMsgID);
if (tunnel)
{
// endpoint of inbound tunnel
LogPrint (eLogDebug, "I2NP: VariableTunnelBuild reply for tunnel ", tunnel->GetTunnelID ());
if (tunnel->HandleTunnelBuildResponse (buf, len))
{
LogPrint (eLogInfo, "I2NP: Inbound tunnel ", tunnel->GetTunnelID (), " has been created");
tunnel->SetState (i2p::tunnel::eTunnelStateEstablished);
i2p::tunnel::tunnels.AddInboundTunnel (tunnel);
}
else
{
LogPrint (eLogInfo, "I2NP: Inbound tunnel ", tunnel->GetTunnelID (), " has been declined");
tunnel->SetState (i2p::tunnel::eTunnelStateBuildFailed);
}
}
else
{
uint8_t clearText[ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE];
if (HandleBuildRequestRecords (num, buf + 1, clearText))
{
if (clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG) // we are endpoint of outboud tunnel
{
// so we send it to reply tunnel
transports.SendMessage (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET,
CreateTunnelGatewayMsg (bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET),
eI2NPVariableTunnelBuildReply, buf, len,
bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET)));
}
else
transports.SendMessage (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET,
CreateI2NPMessage (eI2NPVariableTunnelBuild, buf, len,
bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET)));
}
}
}
static void HandleTunnelBuildMsg (uint8_t * buf, size_t len)
{
LogPrint (eLogWarning, "I2NP: TunnelBuild is too old for ECIES router");
}
static void HandleTunnelBuildReplyMsg (uint32_t replyMsgID, uint8_t * buf, size_t len, bool isShort)
{
int num = buf[0];
LogPrint (eLogDebug, "I2NP: TunnelBuildReplyMsg of ", num, " records replyMsgID=", replyMsgID);
if (num > i2p::tunnel::MAX_NUM_RECORDS)
{
LogPrint (eLogError, "I2NP: Too many records in TunnelBuildReply message ", num);
return;
}
size_t recordSize = isShort ? SHORT_TUNNEL_BUILD_RECORD_SIZE : TUNNEL_BUILD_RECORD_SIZE;
if (len < num*recordSize + 1)
{
LogPrint (eLogError, "I2NP: TunnelBuildReply message of ", num, " records is too short ", len);
return;
}
auto tunnel = i2p::tunnel::tunnels.GetPendingOutboundTunnel (replyMsgID);
if (tunnel)
{
// reply for outbound tunnel
if (tunnel->HandleTunnelBuildResponse (buf, len))
{
LogPrint (eLogInfo, "I2NP: Outbound tunnel ", tunnel->GetTunnelID (), " has been created");
tunnel->SetState (i2p::tunnel::eTunnelStateEstablished);
i2p::tunnel::tunnels.AddOutboundTunnel (tunnel);
}
else
{
LogPrint (eLogInfo, "I2NP: Outbound tunnel ", tunnel->GetTunnelID (), " has been declined");
tunnel->SetState (i2p::tunnel::eTunnelStateBuildFailed);
}
}
else
LogPrint (eLogWarning, "I2NP: Pending tunnel for message ", replyMsgID, " not found");
}
static void HandleShortTunnelBuildMsg (uint32_t replyMsgID, uint8_t * buf, size_t len)
{
int num = buf[0];
LogPrint (eLogDebug, "I2NP: ShortTunnelBuild ", num, " records");
if (num > i2p::tunnel::MAX_NUM_RECORDS)
{
LogPrint (eLogError, "I2NP: Too many records in ShortTunnelBuild message ", num);
return;
}
if (len < num*SHORT_TUNNEL_BUILD_RECORD_SIZE + 1)
{
LogPrint (eLogError, "I2NP: ShortTunnelBuild message of ", num, " records is too short ", len);
return;
}
auto tunnel = i2p::tunnel::tunnels.GetPendingInboundTunnel (replyMsgID);
if (tunnel)
{
// endpoint of inbound tunnel
LogPrint (eLogDebug, "I2NP: ShortTunnelBuild reply for tunnel ", tunnel->GetTunnelID ());
if (tunnel->HandleTunnelBuildResponse (buf, len))
{
LogPrint (eLogInfo, "I2NP: Inbound tunnel ", tunnel->GetTunnelID (), " has been created");
tunnel->SetState (i2p::tunnel::eTunnelStateEstablished);
i2p::tunnel::tunnels.AddInboundTunnel (tunnel);
}
else
{
LogPrint (eLogInfo, "I2NP: Inbound tunnel ", tunnel->GetTunnelID (), " has been declined");
tunnel->SetState (i2p::tunnel::eTunnelStateBuildFailed);
}
return;
}
const uint8_t * record = buf + 1;
for (int i = 0; i < num; i++)
{
if (!memcmp (record, (const uint8_t *)i2p::context.GetRouterInfo ().GetIdentHash (), 16))
{
LogPrint (eLogDebug, "I2NP: Short request record ", i, " is ours");
uint8_t clearText[SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE];
if (!i2p::context.DecryptTunnelShortRequestRecord (record + SHORT_REQUEST_RECORD_ENCRYPTED_OFFSET, clearText))
{
LogPrint (eLogWarning, "I2NP: Can't decrypt short request record ", i);
return;
}
if (clearText[SHORT_REQUEST_RECORD_LAYER_ENCRYPTION_TYPE]) // not AES
{
LogPrint (eLogWarning, "I2NP: Unknown layer encryption type ", clearText[SHORT_REQUEST_RECORD_LAYER_ENCRYPTION_TYPE], " in short request record");
return;
}
auto& noiseState = i2p::context.GetCurrentNoiseState ();
uint8_t replyKey[32]; // AEAD/Chacha20/Poly1305
i2p::crypto::AESKey layerKey, ivKey; // AES
i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "SMTunnelReplyKey", noiseState.m_CK);
memcpy (replyKey, noiseState.m_CK + 32, 32);
i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "SMTunnelLayerKey", noiseState.m_CK);
memcpy (layerKey, noiseState.m_CK + 32, 32);
bool isEndpoint = clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG;
if (isEndpoint)
{
i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "TunnelLayerIVKey", noiseState.m_CK);
memcpy (ivKey, noiseState.m_CK + 32, 32);
}
else
{
if (!memcmp ((const uint8_t *)i2p::context.GetIdentHash (), clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, 32)) // if next ident is now ours
{
LogPrint (eLogWarning, "I2NP: Next ident is ours in short request record");
return;
}
memcpy (ivKey, noiseState.m_CK , 32);
}
// check if we accept this tunnel
std::shared_ptr<i2p::tunnel::TransitTunnel> transitTunnel;
uint8_t retCode = 0;
if (!i2p::context.AcceptsTunnels () || i2p::context.GetCongestionLevel (false) >= CONGESTION_LEVEL_FULL)
retCode = 30;
if (!retCode)
{
// create new transit tunnel
transitTunnel = i2p::tunnel::CreateTransitTunnel (
bufbe32toh (clearText + SHORT_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET),
clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET,
bufbe32toh (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET),
layerKey, ivKey,
clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_GATEWAY_FLAG,
clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG);
if (!i2p::tunnel::tunnels.AddTransitTunnel (transitTunnel))
retCode = 30;
}
// encrypt reply
uint8_t nonce[12];
memset (nonce, 0, 12);
uint8_t * reply = buf + 1;
for (int j = 0; j < num; j++)
{
nonce[4] = j; // nonce is record #
if (j == i)
{
memset (reply + SHORT_RESPONSE_RECORD_OPTIONS_OFFSET, 0, 2); // no options
reply[SHORT_RESPONSE_RECORD_RET_OFFSET] = retCode;
if (!i2p::crypto::AEADChaCha20Poly1305 (reply, SHORT_TUNNEL_BUILD_RECORD_SIZE - 16,
noiseState.m_H, 32, replyKey, nonce, reply, SHORT_TUNNEL_BUILD_RECORD_SIZE, true)) // encrypt
{
LogPrint (eLogWarning, "I2NP: Short reply AEAD encryption failed");
return;
}
}
else
i2p::crypto::ChaCha20 (reply, SHORT_TUNNEL_BUILD_RECORD_SIZE, replyKey, nonce, reply);
reply += SHORT_TUNNEL_BUILD_RECORD_SIZE;
}
// send reply
auto onDrop = [transitTunnel]()
{
if (transitTunnel)
{
auto t = transitTunnel->GetCreationTime ();
if (t > i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT)
// make transit tunnel expired
transitTunnel->SetCreationTime (t - i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT);
}
};
if (isEndpoint)
{
auto replyMsg = NewI2NPShortMessage ();
replyMsg->Concat (buf, len);
replyMsg->FillI2NPMessageHeader (eI2NPShortTunnelBuildReply, bufbe32toh (clearText + SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET));
if (transitTunnel) replyMsg->onDrop = onDrop;
if (memcmp ((const uint8_t *)i2p::context.GetIdentHash (),
clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, 32)) // reply IBGW is not local?
{
i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "RGarlicKeyAndTag", noiseState.m_CK);
uint64_t tag;
memcpy (&tag, noiseState.m_CK, 8);
// we send it to reply tunnel
transports.SendMessage (clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET,
CreateTunnelGatewayMsg (bufbe32toh (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET),
i2p::garlic::WrapECIESX25519Message (replyMsg, noiseState.m_CK + 32, tag)));
}
else
{
// IBGW is local
uint32_t tunnelID = bufbe32toh (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET);
auto tunnel = i2p::tunnel::tunnels.GetTunnel (tunnelID);
if (tunnel)
{
tunnel->SendTunnelDataMsg (replyMsg);
tunnel->FlushTunnelDataMsgs ();
}
else
LogPrint (eLogWarning, "I2NP: Tunnel ", tunnelID, " not found for short tunnel build reply");
}
}
else
{
auto msg = CreateI2NPMessage (eI2NPShortTunnelBuild, buf, len,
bufbe32toh (clearText + SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET));
if (transitTunnel) msg->onDrop = onDrop;
transports.SendMessage (clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, msg);
}
return;
}
record += SHORT_TUNNEL_BUILD_RECORD_SIZE;
}
}
std::shared_ptr<I2NPMessage> CreateTunnelDataMsg (const uint8_t * buf)
{
auto msg = NewI2NPTunnelMessage (false);
@ -466,6 +802,41 @@ namespace i2p
return l;
}
void HandleTunnelBuildI2NPMessage (std::shared_ptr<I2NPMessage> msg)
{
if (msg)
{
uint8_t typeID = msg->GetTypeID();
uint32_t msgID = msg->GetMsgID();
LogPrint (eLogDebug, "I2NP: Handling tunnel build message with len=", msg->GetLength(),", type=", (int)typeID, ", msgID=", (unsigned int)msgID);
uint8_t * payload = msg->GetPayload();
auto size = msg->GetPayloadLength();
switch (typeID)
{
case eI2NPVariableTunnelBuild:
HandleVariableTunnelBuildMsg (msgID, payload, size);
break;
case eI2NPShortTunnelBuild:
HandleShortTunnelBuildMsg (msgID, payload, size);
break;
case eI2NPVariableTunnelBuildReply:
HandleTunnelBuildReplyMsg (msgID, payload, size, false);
break;
case eI2NPShortTunnelBuildReply:
HandleTunnelBuildReplyMsg (msgID, payload, size, true);
break;
case eI2NPTunnelBuild:
HandleTunnelBuildMsg (payload, size);
break;
case eI2NPTunnelBuildReply:
// TODO:
break;
default:
LogPrint (eLogError, "I2NP: Unexpected message with type", (int)typeID, " during handling TBM; skipping");
}
}
}
void HandleI2NPMessage (std::shared_ptr<I2NPMessage> msg)
{
if (msg)
@ -561,8 +932,14 @@ namespace i2p
void I2NPMessagesHandler::Flush ()
{
if (!m_TunnelMsgs.empty ())
{
i2p::tunnel::tunnels.PostTunnelData (m_TunnelMsgs);
m_TunnelMsgs.clear ();
}
if (!m_TunnelGatewayMsgs.empty ())
{
i2p::tunnel::tunnels.PostTunnelData (m_TunnelGatewayMsgs);
m_TunnelGatewayMsgs.clear ();
}
}
}

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
*
@ -13,7 +13,6 @@
#include <string.h>
#include <unordered_set>
#include <memory>
#include <list>
#include <functional>
#include "Crypto.h"
#include "I2PEndian.h"
@ -108,6 +107,7 @@ namespace i2p
enum I2NPMessageType
{
eI2NPDummyMsg = 0,
eI2NPDatabaseStore = 1,
eI2NPDatabaseLookup = 2,
eI2NPDatabaseSearchReply = 3,
@ -155,7 +155,6 @@ namespace tunnel
const unsigned int I2NP_MESSAGE_LOCAL_EXPIRATION_TIMEOUT_FACTOR = 3; // multiples of RTT
const unsigned int I2NP_MESSAGE_LOCAL_EXPIRATION_TIMEOUT_MIN = 200000; // in microseconds
const unsigned int I2NP_MESSAGE_LOCAL_EXPIRATION_TIMEOUT_MAX = 2000000; // in microseconds
const unsigned int I2NP_MESSAGE_LOCAL_EXPIRATION_TIMEOUT_TRANSIT = 4000000; // in microseconds
const unsigned int I2NP_MESSAGE_EXPIRATION_TIMEOUT = 8000; // in milliseconds (as initial RTT)
const unsigned int I2NP_MESSAGE_CLOCK_SKEW = 60*1000; // 1 minute in milliseconds
@ -317,6 +316,7 @@ namespace tunnel
std::shared_ptr<I2NPMessage> CreateTunnelGatewayMsg (uint32_t tunnelID, std::shared_ptr<I2NPMessage> msg);
size_t GetI2NPMessageLength (const uint8_t * msg, size_t len);
void HandleTunnelBuildI2NPMessage (std::shared_ptr<I2NPMessage> msg);
void HandleI2NPMessage (std::shared_ptr<I2NPMessage> msg);
class I2NPMessagesHandler
@ -329,7 +329,7 @@ namespace tunnel
private:
std::list<std::shared_ptr<I2NPMessage> > m_TunnelMsgs, m_TunnelGatewayMsgs;
std::vector<std::shared_ptr<I2NPMessage> > m_TunnelMsgs, m_TunnelGatewayMsgs;
};
}

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
*
@ -10,7 +10,6 @@
#include "I2PEndian.h"
#include "Log.h"
#include "Timestamp.h"
#include "CryptoKey.h"
#include "Identity.h"
namespace i2p
@ -28,15 +27,18 @@ namespace data
size_t Identity::FromBuffer (const uint8_t * buf, size_t len)
{
if (len < DEFAULT_IDENTITY_SIZE) return 0; // buffer too small, don't overflow
memcpy (this, buf, DEFAULT_IDENTITY_SIZE);
if ( len < DEFAULT_IDENTITY_SIZE ) {
// buffer too small, don't overflow
return 0;
}
memcpy (publicKey, buf, DEFAULT_IDENTITY_SIZE);
return DEFAULT_IDENTITY_SIZE;
}
IdentHash Identity::Hash () const
{
IdentHash hash;
SHA256((const uint8_t *)this, DEFAULT_IDENTITY_SIZE, hash);
SHA256(publicKey, DEFAULT_IDENTITY_SIZE, hash);
return hash;
}
@ -120,17 +122,6 @@ namespace data
memcpy (m_StandardIdentity.signingKey, signingKey, i2p::crypto::GOSTR3410_512_PUBLIC_KEY_LENGTH);
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);
cryptoType = 0xFF; // crypto key is not used
break;
}
#endif
default:
LogPrint (eLogError, "Identity: Signing key type ", (int)type, " is not supported");
}
@ -143,14 +134,11 @@ namespace data
htobe16buf (m_ExtendedBuffer + 2, cryptoType);
if (excessLen && excessBuf)
{
if (m_ExtendedLen > MAX_EXTENDED_BUFFER_SIZE)
if (excessLen > MAX_EXTENDED_BUFFER_SIZE - 4)
{
auto newBuf = new uint8_t[m_ExtendedLen];
memcpy (newBuf, m_ExtendedBuffer, 4);
memcpy (newBuf + 4, excessBuf, excessLen);
m_ExtendedBufferPtr = newBuf;
LogPrint (eLogError, "Identity: Unexpected excessive signing key len ", excessLen);
excessLen = MAX_EXTENDED_BUFFER_SIZE - 4;
}
else
memcpy (m_ExtendedBuffer + 4, excessBuf, excessLen);
delete[] excessBuf;
}
@ -199,8 +187,6 @@ namespace data
IdentityEx::~IdentityEx ()
{
if (m_ExtendedLen > MAX_EXTENDED_BUFFER_SIZE)
delete[] m_ExtendedBufferPtr;
}
IdentityEx& IdentityEx::operator=(const IdentityEx& other)
@ -208,30 +194,12 @@ namespace data
memcpy (&m_StandardIdentity, &other.m_StandardIdentity, DEFAULT_IDENTITY_SIZE);
m_IdentHash = other.m_IdentHash;
size_t oldLen = m_ExtendedLen;
m_ExtendedLen = other.m_ExtendedLen;
if (m_ExtendedLen > 0)
{
if (m_ExtendedLen > MAX_EXTENDED_BUFFER_SIZE)
{
if (oldLen > MAX_EXTENDED_BUFFER_SIZE)
{
if (m_ExtendedLen > oldLen)
{
delete[] m_ExtendedBufferPtr;
m_ExtendedBufferPtr = new uint8_t[m_ExtendedLen];
}
}
else
m_ExtendedBufferPtr = new uint8_t[m_ExtendedLen];
memcpy (m_ExtendedBufferPtr, other.m_ExtendedBufferPtr, m_ExtendedLen);
}
else
{
if (oldLen > MAX_EXTENDED_BUFFER_SIZE) delete[] m_ExtendedBufferPtr;
if (m_ExtendedLen > MAX_EXTENDED_BUFFER_SIZE) m_ExtendedLen = MAX_EXTENDED_BUFFER_SIZE;
memcpy (m_ExtendedBuffer, other.m_ExtendedBuffer, m_ExtendedLen);
}
}
m_Verifier = nullptr;
CreateVerifier ();
@ -259,27 +227,12 @@ namespace data
}
memcpy (&m_StandardIdentity, buf, DEFAULT_IDENTITY_SIZE);
size_t oldLen = m_ExtendedLen;
m_ExtendedLen = bufbe16toh (m_StandardIdentity.certificate + 1);
if (m_ExtendedLen)
{
if (m_ExtendedLen + DEFAULT_IDENTITY_SIZE <= len)
{
if (m_ExtendedLen > MAX_EXTENDED_BUFFER_SIZE)
{
if (oldLen > MAX_EXTENDED_BUFFER_SIZE)
{
if (m_ExtendedLen > oldLen)
{
delete[] m_ExtendedBufferPtr;
m_ExtendedBufferPtr = new uint8_t[m_ExtendedLen];
}
}
else
m_ExtendedBufferPtr = new uint8_t[m_ExtendedLen];
memcpy (m_ExtendedBufferPtr, buf + DEFAULT_IDENTITY_SIZE, m_ExtendedLen);
}
else
if (m_ExtendedLen > MAX_EXTENDED_BUFFER_SIZE) m_ExtendedLen = MAX_EXTENDED_BUFFER_SIZE;
memcpy (m_ExtendedBuffer, buf + DEFAULT_IDENTITY_SIZE, m_ExtendedLen);
}
else
@ -305,28 +258,27 @@ namespace data
if (fullLen > len) return 0; // buffer is too small and may overflow somewhere else
memcpy (buf, &m_StandardIdentity, DEFAULT_IDENTITY_SIZE);
if (m_ExtendedLen > 0)
{
if (m_ExtendedLen > MAX_EXTENDED_BUFFER_SIZE)
memcpy (buf + DEFAULT_IDENTITY_SIZE, m_ExtendedBufferPtr, m_ExtendedLen);
else
memcpy (buf + DEFAULT_IDENTITY_SIZE, m_ExtendedBuffer, m_ExtendedLen);
}
return fullLen;
}
size_t IdentityEx::FromBase64(std::string_view s)
size_t IdentityEx::FromBase64(const std::string& s)
{
std::vector<uint8_t> buf(s.length ()); // binary data can't exceed base64
auto len = Base64ToByteStream (s, buf.data(), buf.size ());
const size_t slen = s.length();
std::vector<uint8_t> buf(slen); // binary data can't exceed base64
const size_t len = Base64ToByteStream (s.c_str(), slen, buf.data(), slen);
return FromBuffer (buf.data(), len);
}
std::string IdentityEx::ToBase64 () const
{
const size_t bufLen = GetFullLen();
const size_t strLen = Base64EncodingBufferSize(bufLen);
std::vector<uint8_t> buf(bufLen);
std::vector<char> str(strLen);
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
@ -339,7 +291,7 @@ namespace data
const uint8_t * IdentityEx::GetSigningPublicKeyBuffer () const
{
auto keyLen = GetSigningPublicKeyLen ();
if (keyLen > 128) return nullptr; // P521 or PQ
if (keyLen > 128) return nullptr; // P521
return m_StandardIdentity.signingKey + 128 - keyLen;
}
@ -366,7 +318,7 @@ namespace data
SigningKeyType IdentityEx::GetSigningKeyType () const
{
if (m_StandardIdentity.certificate[0] == CERTIFICATE_TYPE_KEY && m_ExtendedLen >= 2)
return bufbe16toh (m_ExtendedLen <= MAX_EXTENDED_BUFFER_SIZE ? m_ExtendedBuffer : m_ExtendedBufferPtr); // signing key
return bufbe16toh (m_ExtendedBuffer); // signing key
return SIGNING_KEY_TYPE_DSA_SHA1;
}
@ -379,7 +331,7 @@ namespace data
CryptoKeyType IdentityEx::GetCryptoKeyType () const
{
if (m_StandardIdentity.certificate[0] == CERTIFICATE_TYPE_KEY && m_ExtendedLen >= 4)
return bufbe16toh (m_ExtendedLen <= MAX_EXTENDED_BUFFER_SIZE ? m_ExtendedBuffer + 2 : m_ExtendedBufferPtr + 2); // crypto key
return bufbe16toh (m_ExtendedBuffer + 2); // crypto key
return CRYPTO_KEY_TYPE_ELGAMAL;
}
@ -403,10 +355,6 @@ namespace data
return new i2p::crypto::GOSTR3410_512_Verifier (i2p::crypto::eGOSTR3410TC26A512);
case SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519:
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_SHA384_3072:
case SIGNING_KEY_TYPE_RSA_SHA512_4096:
@ -428,18 +376,6 @@ namespace data
auto keyLen = verifier->GetPublicKeyLen ();
if (keyLen <= 128)
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, 384);
size_t excessLen = keyLen - 384;
memcpy (signingKey + 384, m_ExtendedBufferPtr + 4, excessLen); // right after signing and crypto key types
verifier->SetPublicKey (signingKey);
delete[] signingKey;
}
#endif
else
{
// for P521
@ -463,14 +399,15 @@ namespace data
return std::make_shared<i2p::crypto::ElGamalEncryptor>(key);
break;
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);
break;
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);
break;
case CRYPTO_KEY_TYPE_ECIES_GOSTR3410_CRYPTO_PRO_A_SHA256_AES256CBC:
return std::make_shared<i2p::crypto::ECIESGOSTR3410Encryptor>(key);
break;
default:
LogPrint (eLogError, "Identity: Unknown crypto key type ", (int)keyType);
};
@ -495,9 +432,7 @@ namespace data
{
m_Public = std::make_shared<IdentityEx>(Identity (keys));
memcpy (m_PrivateKey, keys.privateKey, 256); // 256
size_t keyLen = m_Public->GetSigningPrivateKeyLen ();
m_SigningPrivateKey.resize (keyLen);
memcpy (m_SigningPrivateKey.data (), keys.signingPrivateKey, keyLen);
memcpy (m_SigningPrivateKey, keys.signingPrivateKey, m_Public->GetSigningPrivateKeyLen ());
m_OfflineSignature.resize (0);
m_TransientSignatureLen = 0;
m_TransientSigningPrivateKeyLen = 0;
@ -513,7 +448,7 @@ namespace data
m_OfflineSignature = other.m_OfflineSignature;
m_TransientSignatureLen = other.m_TransientSignatureLen;
m_TransientSigningPrivateKeyLen = other.m_TransientSigningPrivateKeyLen;
m_SigningPrivateKey = other.m_SigningPrivateKey;
memcpy (m_SigningPrivateKey, other.m_SigningPrivateKey, m_TransientSigningPrivateKeyLen > 0 ? m_TransientSigningPrivateKeyLen : m_Public->GetSigningPrivateKeyLen ());
m_Signer = nullptr;
CreateSigner ();
return *this;
@ -536,9 +471,8 @@ namespace data
memcpy (m_PrivateKey, buf + ret, cryptoKeyLen);
ret += cryptoKeyLen;
size_t signingPrivateKeySize = m_Public->GetSigningPrivateKeyLen ();
if (signingPrivateKeySize + ret > len) return 0; // overflow
m_SigningPrivateKey.resize (signingPrivateKeySize);
memcpy (m_SigningPrivateKey.data (), buf + ret, signingPrivateKeySize);
if(signingPrivateKeySize + ret > len || signingPrivateKeySize > 128) return 0; // overflow
memcpy (m_SigningPrivateKey, buf + ret, signingPrivateKeySize);
ret += signingPrivateKeySize;
m_Signer = nullptr;
// check if signing private key is all zeros
@ -579,9 +513,8 @@ namespace data
memcpy (m_OfflineSignature.data (), offlineInfo, offlineInfoLen);
// override signing private key
m_TransientSigningPrivateKeyLen = transientVerifier->GetPrivateKeyLen ();
if (m_TransientSigningPrivateKeyLen + ret > len) return 0;
if (m_TransientSigningPrivateKeyLen > 128) m_SigningPrivateKey.resize (m_TransientSigningPrivateKeyLen);
memcpy (m_SigningPrivateKey.data (), buf + ret, m_TransientSigningPrivateKeyLen);
if (m_TransientSigningPrivateKeyLen + ret > len || m_TransientSigningPrivateKeyLen > 128) return 0;
memcpy (m_SigningPrivateKey, buf + ret, m_TransientSigningPrivateKeyLen);
ret += m_TransientSigningPrivateKeyLen;
CreateSigner (keyType);
}
@ -601,7 +534,7 @@ namespace data
if (IsOfflineSignature ())
memset (buf + ret, 0, signingPrivateKeySize);
else
memcpy (buf + ret, m_SigningPrivateKey.data (), signingPrivateKeySize);
memcpy (buf + ret, m_SigningPrivateKey, signingPrivateKeySize);
ret += signingPrivateKeySize;
if (IsOfflineSignature ())
{
@ -612,24 +545,32 @@ namespace data
ret += offlineSignatureLen;
// transient private key
if (ret + m_TransientSigningPrivateKeyLen > len) return 0;
memcpy (buf + ret, m_SigningPrivateKey.data (), m_TransientSigningPrivateKeyLen);
memcpy (buf + ret, m_SigningPrivateKey, m_TransientSigningPrivateKeyLen);
ret += m_TransientSigningPrivateKeyLen;
}
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 ());
size_t l = i2p::data::Base64ToByteStream (s, buf.data (), buf.size ());
return FromBuffer (buf.data (), l);
uint8_t * buf = new uint8_t[s.length ()];
size_t l = i2p::data::Base64ToByteStream (s.c_str (), s.length (), buf, s.length ());
size_t ret = FromBuffer (buf, l);
delete[] buf;
return ret;
}
std::string PrivateKeys::ToBase64 () const
{
std::vector<uint8_t> buf(GetFullLen ());
size_t l = ToBuffer (buf.data (), buf.size ());
return i2p::data::ByteStreamToBase64 (buf.data (), l);
uint8_t * buf = new uint8_t[GetFullLen ()];
char * str = new char[GetFullLen ()*2];
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
@ -651,13 +592,13 @@ namespace data
{
if (m_Signer) return;
if (keyType == SIGNING_KEY_TYPE_DSA_SHA1)
m_Signer.reset (new i2p::crypto::DSASigner (m_SigningPrivateKey.data (), m_Public->GetStandardIdentity ().signingKey));
m_Signer.reset (new i2p::crypto::DSASigner (m_SigningPrivateKey, m_Public->GetStandardIdentity ().signingKey));
else if (keyType == SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519 && !IsOfflineSignature ())
m_Signer.reset (new i2p::crypto::EDDSA25519Signer (m_SigningPrivateKey.data (), m_Public->GetStandardIdentity ().signingKey + (sizeof(Identity::signingKey) - i2p::crypto::EDDSA25519_PUBLIC_KEY_LENGTH))); // TODO: remove public key check
m_Signer.reset (new i2p::crypto::EDDSA25519Signer (m_SigningPrivateKey, m_Public->GetStandardIdentity ().signingKey + (sizeof(Identity::signingKey) - i2p::crypto::EDDSA25519_PUBLIC_KEY_LENGTH))); // TODO: remove public key check
else
{
// public key is not required
auto signer = CreateSigner (keyType, m_SigningPrivateKey.data ());
auto signer = CreateSigner (keyType, m_SigningPrivateKey);
if (signer) m_Signer.reset (signer);
}
}
@ -692,11 +633,6 @@ namespace data
case SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519:
return new i2p::crypto::RedDSA25519Signer (priv);
break;
#if OPENSSL_PQ
case SIGNING_KEY_TYPE_MLDSA44:
return new i2p::crypto::MLDSA44Signer (priv);
break;
#endif
default:
LogPrint (eLogError, "Identity: Signing key type ", (int)keyType, " is not supported");
}
@ -710,7 +646,8 @@ namespace data
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()
@ -736,14 +673,15 @@ namespace data
return std::make_shared<i2p::crypto::ElGamalDecryptor>(key);
break;
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);
break;
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);
break;
case CRYPTO_KEY_TYPE_ECIES_GOSTR3410_CRYPTO_PRO_A_SHA256_AES256CBC:
return std::make_shared<i2p::crypto::ECIESGOSTR3410Decryptor>(key);
break;
default:
LogPrint (eLogError, "Identity: Unknown crypto key type ", (int)cryptoType);
};
@ -756,10 +694,8 @@ namespace data
{
PrivateKeys keys;
// signature
std::unique_ptr<i2p::crypto::Verifier> verifier (IdentityEx::CreateVerifier (type));
std::vector<uint8_t> signingPublicKey(verifier->GetPublicKeyLen ());
keys.m_SigningPrivateKey.resize (verifier->GetPrivateKeyLen ());
GenerateSigningKeyPair (type, keys.m_SigningPrivateKey.data (), signingPublicKey.data ());
uint8_t signingPublicKey[512]; // signing public key is 512 bytes max
GenerateSigningKeyPair (type, keys.m_SigningPrivateKey, signingPublicKey);
// encryption
uint8_t publicKey[256];
if (isDestination)
@ -767,7 +703,7 @@ namespace data
else
GenerateCryptoKeyPair (cryptoType, keys.m_PrivateKey, publicKey);
// identity
keys.m_Public = std::make_shared<IdentityEx> (isDestination ? nullptr : publicKey, signingPublicKey.data (), type, cryptoType);
keys.m_Public = std::make_shared<IdentityEx> (isDestination ? nullptr : publicKey, signingPublicKey, type, cryptoType);
keys.CreateSigner ();
return keys;
@ -792,7 +728,9 @@ namespace data
case SIGNING_KEY_TYPE_RSA_SHA384_3072:
case SIGNING_KEY_TYPE_RSA_SHA512_4096:
LogPrint (eLogWarning, "Identity: RSA signature type is not supported. Creating EdDSA");
#if (__cplusplus >= 201703L) // C++ 17 or higher
[[fallthrough]];
#endif
// no break here
case SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519:
i2p::crypto::CreateEDDSA25519RandomKeys (priv, pub);
@ -806,11 +744,6 @@ namespace data
case SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519:
i2p::crypto::CreateRedDSA25519RandomKeys (priv, pub);
break;
#if OPENSSL_PQ
case SIGNING_KEY_TYPE_MLDSA44:
i2p::crypto::CreateMLDSA44RandomKeys (priv, pub);
break;
#endif
default:
LogPrint (eLogWarning, "Identity: Signing key type ", (int)type, " is not supported. Create DSA-SHA1");
i2p::crypto::CreateDSARandomKeys (priv, pub); // DSA-SHA1
@ -825,12 +758,13 @@ namespace data
i2p::crypto::GenerateElGamalKeyPair(priv, pub);
break;
case CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC:
case CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC_TEST:
i2p::crypto::CreateECIESP256RandomKeys (priv, pub);
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_MLKEM512_X25519_AEAD:
case CRYPTO_KEY_TYPE_ECIES_MLKEM768_X25519_AEAD:
case CRYPTO_KEY_TYPE_ECIES_MLKEM1024_X25519_AEAD:
i2p::crypto::CreateECIESX25519AEADRatchetRandomKeys (priv, pub);
break;
default:
@ -848,10 +782,9 @@ namespace data
keys.m_TransientSigningPrivateKeyLen = verifier->GetPrivateKeyLen ();
keys.m_TransientSignatureLen = verifier->GetSignatureLen ();
keys.m_OfflineSignature.resize (pubKeyLen + m_Public->GetSignatureLen () + 6);
keys.m_SigningPrivateKey.resize (verifier->GetPrivateKeyLen ());
htobe32buf (keys.m_OfflineSignature.data (), expires); // expires
htobe16buf (keys.m_OfflineSignature.data () + 4, type); // type
GenerateSigningKeyPair (type, keys.m_SigningPrivateKey.data (), keys.m_OfflineSignature.data () + 6); // public key
GenerateSigningKeyPair (type, keys.m_SigningPrivateKey, keys.m_OfflineSignature.data () + 6); // public key
Sign (keys.m_OfflineSignature.data (), pubKeyLen + 6, keys.m_OfflineSignature.data () + 6 + pubKeyLen); // signature
// recreate signer
keys.m_Signer = nullptr;

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
*
@ -12,19 +12,14 @@
#include <inttypes.h>
#include <string.h>
#include <string>
#include <string_view>
#include <memory>
#include <vector>
#include "Base.h"
#include "Signature.h"
#include "CryptoKey.h"
namespace i2p
{
namespace crypto
{
class CryptoKeyEncryptor;
class CryptoKeyDecryptor;
}
namespace data
{
typedef Tag<32> IdentHash;
@ -59,8 +54,6 @@ namespace data
Identity& operator=(const Keys& keys);
size_t FromBuffer (const uint8_t * buf, size_t len);
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 ();
@ -70,9 +63,8 @@ namespace data
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_X25519_AEAD = 4;
const uint16_t CRYPTO_KEY_TYPE_ECIES_MLKEM512_X25519_AEAD = 5;
const uint16_t CRYPTO_KEY_TYPE_ECIES_MLKEM768_X25519_AEAD = 6;
const uint16_t CRYPTO_KEY_TYPE_ECIES_MLKEM1024_X25519_AEAD = 7;
const uint16_t CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC_TEST = 65280; // TODO: remove later
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 SIGNING_KEY_TYPE_DSA_SHA1 = 0;
const uint16_t SIGNING_KEY_TYPE_ECDSA_SHA256_P256 = 1;
@ -82,11 +74,10 @@ namespace data
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_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_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_MLDSA44 = 12;
typedef uint16_t SigningKeyType;
typedef uint16_t CryptoKeyType;
@ -108,7 +99,7 @@ namespace data
size_t FromBuffer (const uint8_t * buf, size_t len);
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;
const Identity& GetStandardIdentity () const { return m_StandardIdentity; };
@ -142,11 +133,7 @@ namespace data
IdentHash m_IdentHash;
std::unique_ptr<i2p::crypto::Verifier> m_Verifier;
size_t m_ExtendedLen;
union
{
uint8_t m_ExtendedBuffer[MAX_EXTENDED_BUFFER_SIZE];
uint8_t * m_ExtendedBufferPtr;
};
};
size_t GetIdentityBufferLen (const uint8_t * buf, size_t len); // return actual identity length in buffer
@ -164,7 +151,7 @@ namespace data
std::shared_ptr<const IdentityEx> GetPublic () const { return m_Public; };
const uint8_t * GetPrivateKey () const { return m_PrivateKey; };
const uint8_t * GetSigningPrivateKey () const { return m_SigningPrivateKey.data (); };
const uint8_t * GetSigningPrivateKey () const { return m_SigningPrivateKey; };
size_t GetSignatureLen () const; // might not match identity
bool IsOfflineSignature () const { return m_TransientSignatureLen > 0; };
uint8_t * GetPadding();
@ -175,7 +162,7 @@ namespace data
size_t FromBuffer (const uint8_t * buf, size_t len);
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::shared_ptr<i2p::crypto::CryptoKeyDecryptor> CreateDecryptor (const uint8_t * key) const;
@ -200,7 +187,7 @@ namespace data
std::shared_ptr<IdentityEx> m_Public;
uint8_t m_PrivateKey[256];
std::vector<uint8_t> m_SigningPrivateKey;
uint8_t m_SigningPrivateKey[128]; // assume private key doesn't exceed 128 bytes
mutable std::unique_ptr<i2p::crypto::Signer> m_Signer;
std::vector<uint8_t> m_OfflineSignature; // non zero length, if applicable
size_t m_TransientSignatureLen = 0;

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
*
@ -14,7 +14,6 @@
#include "Timestamp.h"
#include "NetDb.hpp"
#include "Tunnel.h"
#include "CryptoKey.h"
#include "LeaseSet.h"
namespace i2p
@ -36,7 +35,7 @@ namespace data
ReadFromBuffer ();
}
void LeaseSet::Update (const uint8_t * buf, size_t len, std::shared_ptr<LocalDestination> dest, bool verifySignature)
void LeaseSet::Update (const uint8_t * buf, size_t len, bool verifySignature)
{
SetBuffer (buf, len);
ReadFromBuffer (false, verifySignature);
@ -281,29 +280,28 @@ namespace data
LogPrint (eLogError, "LeaseSet2: Actual buffer size ", int(len) , " exceeds full buffer size ", int(m_BufferLen));
}
LeaseSet2::LeaseSet2 (uint8_t storeType, const uint8_t * buf, size_t len,
bool storeLeases, std::shared_ptr<LocalDestination> dest, CryptoKeyType preferredCrypto):
LeaseSet2::LeaseSet2 (uint8_t storeType, const uint8_t * buf, size_t len, bool storeLeases, CryptoKeyType preferredCrypto):
LeaseSet (storeLeases), m_StoreType (storeType), m_EncryptionType (preferredCrypto)
{
SetBuffer (buf, len);
if (storeType == NETDB_STORE_TYPE_ENCRYPTED_LEASESET2)
ReadFromBufferEncrypted (buf, len, nullptr, dest, nullptr);
ReadFromBufferEncrypted (buf, len, nullptr, nullptr);
else
ReadFromBuffer (buf, len, dest);
ReadFromBuffer (buf, len);
}
LeaseSet2::LeaseSet2 (const uint8_t * buf, size_t len, std::shared_ptr<const BlindedPublicKey> key,
std::shared_ptr<LocalDestination> dest, const uint8_t * secret, CryptoKeyType preferredCrypto):
const uint8_t * secret, CryptoKeyType preferredCrypto):
LeaseSet (true), m_StoreType (NETDB_STORE_TYPE_ENCRYPTED_LEASESET2), m_EncryptionType (preferredCrypto)
{
ReadFromBufferEncrypted (buf, len, key, dest, secret);
ReadFromBufferEncrypted (buf, len, key, secret);
}
void LeaseSet2::Update (const uint8_t * buf, size_t len, std::shared_ptr<LocalDestination> dest, bool verifySignature)
void LeaseSet2::Update (const uint8_t * buf, size_t len, bool verifySignature)
{
SetBuffer (buf, len);
if (GetStoreType () != NETDB_STORE_TYPE_ENCRYPTED_LEASESET2)
ReadFromBuffer (buf, len, dest, false, verifySignature);
ReadFromBuffer (buf, len, false, verifySignature);
// TODO: implement encrypted
}
@ -313,8 +311,7 @@ namespace data
return ExtractPublishedTimestamp (buf, len, expiration) > m_PublishedTimestamp;
}
void LeaseSet2::ReadFromBuffer (const uint8_t * buf, size_t len, std::shared_ptr<LocalDestination> dest,
bool readIdentity, bool verifySignature)
void LeaseSet2::ReadFromBuffer (const uint8_t * buf, size_t len, bool readIdentity, bool verifySignature)
{
// standard LS2 header
std::shared_ptr<const IdentityEx> identity;
@ -352,7 +349,7 @@ namespace data
switch (m_StoreType)
{
case NETDB_STORE_TYPE_STANDARD_LEASESET2:
s = ReadStandardLS2TypeSpecificPart (buf + offset, len - offset, dest);
s = ReadStandardLS2TypeSpecificPart (buf + offset, len - offset);
break;
case NETDB_STORE_TYPE_META_LEASESET2:
s = ReadMetaLS2TypeSpecificPart (buf + offset, len - offset);
@ -394,8 +391,7 @@ namespace data
return verified;
}
size_t LeaseSet2::ReadStandardLS2TypeSpecificPart (const uint8_t * buf, size_t len,
std::shared_ptr<LocalDestination> dest)
size_t LeaseSet2::ReadStandardLS2TypeSpecificPart (const uint8_t * buf, size_t len)
{
size_t offset = 0;
// properties
@ -403,7 +399,6 @@ namespace data
offset += propertiesLen; // skip for now. TODO: implement properties
// key sections
CryptoKeyType preferredKeyType = m_EncryptionType;
m_EncryptionType = 0;
bool preferredKeyFound = false;
if (offset + 1 > len) return 0;
int numKeySections = buf[offset]; offset++;
@ -415,40 +410,21 @@ namespace data
if (offset + encryptionKeyLen > len) return 0;
if (IsStoreLeases () && !preferredKeyFound) // create encryptor with leases only
{
// we pick max key type 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) &&
(!dest || dest->SupportsEncryptionType (keyType)))
{
// we pick first valid key if preferred not found
auto encryptor = i2p::data::IdentityEx::CreateEncryptor (keyType, buf + offset);
if (encryptor)
if (encryptor && (!m_Encryptor || keyType == preferredKeyType))
{
m_Encryptor = encryptor; // TODO: atomic
m_EncryptionType = keyType;
if (keyType == preferredKeyType) preferredKeyFound = true;
}
}
}
}
offset += encryptionKeyLen;
}
// leases
if (offset + 1 > len) return 0;
int numLeases = buf[offset]; offset++;
auto ts = i2p::util::GetMillisecondsSinceEpoch ();
if (GetExpirationTime () > ts + LEASESET_EXPIRATION_TIME_THRESHOLD)
{
LogPrint (eLogWarning, "LeaseSet2: Expiration time is from future ", GetExpirationTime ()/1000LL);
return 0;
}
if (ts > m_PublishedTimestamp*1000LL + LEASESET_EXPIRATION_TIME_THRESHOLD)
{
LogPrint (eLogWarning, "LeaseSet2: Published time is too old ", m_PublishedTimestamp);
return 0;
}
if (IsStoreLeases ())
{
UpdateLeasesBegin ();
@ -459,11 +435,6 @@ namespace data
lease.tunnelGateway = buf + offset; offset += 32; // gateway
lease.tunnelID = bufbe32toh (buf + offset); offset += 4; // tunnel ID
lease.endDate = bufbe32toh (buf + offset)*1000LL; offset += 4; // end date
if (lease.endDate > ts + LEASESET_EXPIRATION_TIME_THRESHOLD)
{
LogPrint (eLogWarning, "LeaseSet2: Lease end date is from future ", lease.endDate);
return 0;
}
UpdateLease (lease, ts);
}
UpdateLeasesEnd ();
@ -502,8 +473,7 @@ namespace data
return offset;
}
void LeaseSet2::ReadFromBufferEncrypted (const uint8_t * buf, size_t len,
std::shared_ptr<const BlindedPublicKey> key, std::shared_ptr<LocalDestination> dest, const uint8_t * secret)
void LeaseSet2::ReadFromBufferEncrypted (const uint8_t * buf, size_t len, std::shared_ptr<const BlindedPublicKey> key, const uint8_t * secret)
{
size_t offset = 0;
// blinded key
@ -606,7 +576,7 @@ namespace data
m_StoreType = innerPlainText[0];
SetBuffer (innerPlainText.data () + 1, lenInnerPlaintext - 1);
// parse and verify Layer 2
ReadFromBuffer (innerPlainText.data () + 1, lenInnerPlaintext - 1, dest);
ReadFromBuffer (innerPlainText.data () + 1, lenInnerPlaintext - 1);
}
else
LogPrint (eLogError, "LeaseSet2: Unexpected LeaseSet type ", (int)innerPlainText[0], " inside encrypted LeaseSet");
@ -758,41 +728,25 @@ namespace data
memset (m_Buffer + offset, 0, signingKeyLen);
offset += signingKeyLen;
// num leases
auto numLeasesPos = offset;
m_Buffer[offset] = num;
offset++;
// leases
m_Leases = m_Buffer + offset;
auto currentTime = i2p::util::GetMillisecondsSinceEpoch ();
int skipped = 0;
for (int i = 0; i < num; i++)
{
uint64_t ts = tunnels[i]->GetCreationTime () + i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT - i2p::tunnel::TUNNEL_EXPIRATION_THRESHOLD; // 1 minute before expiration
ts *= 1000; // in milliseconds
if (ts <= currentTime)
{
// already expired, skip
skipped++;
continue;
}
if (ts > m_ExpirationTime) m_ExpirationTime = ts;
// make sure leaseset is newer than previous, but adding some time to expiration date
ts += (currentTime - tunnels[i]->GetCreationTime ()*1000LL)*2/i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT; // up to 2 secs
memcpy (m_Buffer + offset, tunnels[i]->GetNextIdentHash (), 32);
offset += 32; // gateway id
htobe32buf (m_Buffer + offset, tunnels[i]->GetNextTunnelID ());
offset += 4; // tunnel id
uint64_t ts = tunnels[i]->GetCreationTime () + i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT - i2p::tunnel::TUNNEL_EXPIRATION_THRESHOLD; // 1 minute before expiration
ts *= 1000; // in milliseconds
if (ts > m_ExpirationTime) m_ExpirationTime = ts;
// make sure leaseset is newer than previous, but adding some time to expiration date
ts += (currentTime - tunnels[i]->GetCreationTime ()*1000LL)*2/i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT; // up to 2 secs
htobe64buf (m_Buffer + offset, ts);
offset += 8; // end date
}
if (skipped > 0)
{
// adjust num leases
if (skipped > num) skipped = num;
num -= skipped;
m_BufferLen -= skipped*LEASE_SIZE;
m_Buffer[numLeasesPos] = num;
}
// we don't sign it yet. must be signed later on
}
@ -853,8 +807,8 @@ namespace data
}
LocalLeaseSet2::LocalLeaseSet2 (uint8_t storeType, const i2p::data::PrivateKeys& keys,
const EncryptionKeys& encryptionKeys, const std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> >& tunnels,
bool isPublic, uint64_t publishedTimestamp, bool isPublishedEncrypted):
const KeySections& encryptionKeys, const std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> >& tunnels,
bool isPublic, bool isPublishedEncrypted):
LocalLeaseSet (keys.GetPublic (), nullptr, 0)
{
auto identity = keys.GetPublic ();
@ -863,7 +817,7 @@ namespace data
if (num > MAX_NUM_LEASES) num = MAX_NUM_LEASES;
size_t keySectionsLen = 0;
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*/ +
1/*num keys*/ + keySectionsLen + 1/*num leases*/ + num*LEASE2_SIZE + keys.GetSignatureLen ();
uint16_t flags = 0;
@ -883,7 +837,8 @@ namespace data
m_Buffer[0] = storeType;
// LS2 header
auto offset = identity->ToBuffer (m_Buffer + 1, m_BufferLen) + 1;
htobe32buf (m_Buffer + offset, publishedTimestamp); offset += 4; // published timestamp (seconds)
auto timestamp = i2p::util::GetSecondsSinceEpoch ();
htobe32buf (m_Buffer + offset, timestamp); offset += 4; // published timestamp (seconds)
uint8_t * expiresBuf = m_Buffer + offset; offset += 2; // expires, fill later
htobe16buf (m_Buffer + offset, flags); offset += 2; // flags
if (keys.IsOfflineSignature ())
@ -898,50 +853,35 @@ namespace data
m_Buffer[offset] = encryptionKeys.size (); offset++; // 1 key
for (const auto& it: encryptionKeys)
{
htobe16buf (m_Buffer + offset, it->keyType); offset += 2; // key type
htobe16buf (m_Buffer + offset, it->pub.size()); offset += 2; // key len
memcpy (m_Buffer + offset, it->pub.data(), it->pub.size()); offset += it->pub.size(); // key
htobe16buf (m_Buffer + offset, it.keyType); offset += 2; // key type
htobe16buf (m_Buffer + offset, it.keyLen); offset += 2; // key len
memcpy (m_Buffer + offset, it.encryptionPublicKey, it.keyLen); offset += it.keyLen; // key
}
// leases
uint32_t expirationTime = 0; // in seconds
int skipped = 0; auto numLeasesPos = offset;
m_Buffer[offset] = num; offset++; // num leases
for (int i = 0; i < num; i++)
{
auto ts = tunnels[i]->GetCreationTime () + i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT - i2p::tunnel::TUNNEL_EXPIRATION_THRESHOLD; // in seconds, 1 minute before expiration
if (ts <= publishedTimestamp)
{
// already expired, skip
skipped++;
continue;
}
if (ts > expirationTime) expirationTime = ts;
memcpy (m_Buffer + offset, tunnels[i]->GetNextIdentHash (), 32);
offset += 32; // gateway id
htobe32buf (m_Buffer + offset, tunnels[i]->GetNextTunnelID ());
offset += 4; // tunnel id
auto ts = tunnels[i]->GetCreationTime () + i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT - i2p::tunnel::TUNNEL_EXPIRATION_THRESHOLD; // in seconds, 1 minute before expiration
if (ts > expirationTime) expirationTime = ts;
htobe32buf (m_Buffer + offset, ts);
offset += 4; // end date
}
if (skipped > 0)
{
// adjust num leases
if (skipped > num) skipped = num;
num -= skipped;
m_BufferLen -= skipped*LEASE2_SIZE;
m_Buffer[numLeasesPos] = num;
}
// update expiration
if (expirationTime)
{
SetExpirationTime (expirationTime*1000LL);
auto expires = (int)expirationTime - publishedTimestamp;
auto expires = (int)expirationTime - timestamp;
htobe16buf (expiresBuf, expires > 0 ? expires : 0);
}
else
{
// no tunnels or withdraw
SetExpirationTime (publishedTimestamp*1000LL);
SetExpirationTime (timestamp*1000LL);
memset (expiresBuf, 0, 2); // expires immeditely
}
// sign

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
*
@ -12,14 +12,12 @@
#include <inttypes.h>
#include <string.h>
#include <vector>
#include <list>
#include <set>
#include <memory>
#include "Identity.h"
#include "Timestamp.h"
#include "I2PEndian.h"
#include "Blinding.h"
#include "CryptoKey.h"
namespace i2p
{
@ -59,15 +57,11 @@ namespace data
};
typedef std::function<bool(const Lease & l)> LeaseInspectFunc;
#if OPENSSL_PQ
const size_t MAX_LS_BUFFER_SIZE = 8192;
#else
const size_t MAX_LS_BUFFER_SIZE = 4096;
#endif
const size_t MAX_LS_BUFFER_SIZE = 3072;
const size_t LEASE_SIZE = 44; // 32 + 4 + 8
const size_t LEASE2_SIZE = 40; // 32 + 4 + 4
const uint8_t MAX_NUM_LEASES = 16;
const uint64_t LEASESET_EXPIRATION_TIME_THRESHOLD = 12*60*1000; // in milliseconds
const uint8_t NETDB_STORE_TYPE_LEASESET = 1;
class LeaseSet: public RoutingDestination
@ -76,7 +70,7 @@ namespace data
LeaseSet (const uint8_t * buf, size_t len, bool storeLeases = true);
virtual ~LeaseSet () { delete[] m_EncryptionKey; delete[] m_Buffer; };
virtual void Update (const uint8_t * buf, size_t len, std::shared_ptr<LocalDestination> dest, bool verifySignature);
virtual void Update (const uint8_t * buf, size_t len, bool verifySignature = true);
virtual bool IsNewer (const uint8_t * buf, size_t len) const;
void PopulateLeases (); // from buffer
@ -155,42 +149,38 @@ namespace data
public:
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,
std::shared_ptr<LocalDestination> dest = nullptr, CryptoKeyType preferredCrypto = CRYPTO_KEY_TYPE_ECIES_X25519_AEAD);
LeaseSet2 (const uint8_t * buf, size_t len, std::shared_ptr<const BlindedPublicKey> key,
std::shared_ptr<LocalDestination> dest = nullptr, const uint8_t * secret = nullptr, CryptoKeyType preferredCrypto = CRYPTO_KEY_TYPE_ECIES_X25519_AEAD); // store type 5, called from local netdb only
uint8_t GetStoreType () const override { return m_StoreType; };
uint32_t GetPublishedTimestamp () const override { return m_PublishedTimestamp; };
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_ELGAMAL); // store type 5, called from local netdb only
uint8_t GetStoreType () const { return m_StoreType; };
uint32_t GetPublishedTimestamp () const { return m_PublishedTimestamp; };
bool IsPublic () const { return m_IsPublic; };
bool IsPublishedEncrypted () const override { return m_IsPublishedEncrypted; };
std::shared_ptr<const i2p::crypto::Verifier> GetTransientVerifier () const override { return m_TransientVerifier; };
void Update (const uint8_t * buf, size_t len, std::shared_ptr<LocalDestination> dest, bool verifySignature) override;
bool IsNewer (const uint8_t * buf, size_t len) const override;
bool IsPublishedEncrypted () const { return m_IsPublishedEncrypted; };
std::shared_ptr<const i2p::crypto::Verifier> GetTransientVerifier () const { return m_TransientVerifier; };
void Update (const uint8_t * buf, size_t len, bool verifySignature);
bool IsNewer (const uint8_t * buf, size_t len) const;
// implements RoutingDestination
void Encrypt (const uint8_t * data, uint8_t * encrypted) const override;
CryptoKeyType GetEncryptionType () const override { return m_EncryptionType; };
void Encrypt (const uint8_t * data, uint8_t * encrypted) const;
CryptoKeyType GetEncryptionType () const { return m_EncryptionType; };
private:
void ReadFromBuffer (const uint8_t * buf, size_t len, std::shared_ptr<LocalDestination> dest,
bool readIdentity = true, bool verifySignature = true);
void ReadFromBufferEncrypted (const uint8_t * buf, size_t len, std::shared_ptr<const BlindedPublicKey> key,
std::shared_ptr<LocalDestination> dest, const uint8_t * secret);
size_t ReadStandardLS2TypeSpecificPart (const uint8_t * buf, size_t len, std::shared_ptr<LocalDestination> dest);
void ReadFromBuffer (const uint8_t * buf, size_t len, bool readIdentity = true, bool verifySignature = true);
void ReadFromBufferEncrypted (const uint8_t * buf, size_t len, std::shared_ptr<const BlindedPublicKey> key, const uint8_t * secret);
size_t ReadStandardLS2TypeSpecificPart (const uint8_t * buf, size_t len);
size_t ReadMetaLS2TypeSpecificPart (const uint8_t * buf, size_t len);
template<typename Verifier>
bool VerifySignature (Verifier& verifier, const uint8_t * buf, size_t len, size_t signatureOffset);
uint64_t ExtractExpirationTimestamp (const uint8_t * buf, size_t len) const override;
uint64_t ExtractExpirationTimestamp (const uint8_t * buf, size_t len) const;
uint64_t ExtractPublishedTimestamp (const uint8_t * buf, size_t len, uint64_t& expiration) const;
size_t ExtractClientAuthData (const uint8_t * buf, size_t len, const uint8_t * secret, const uint8_t * subcredential, uint8_t * authCookie) const; // subcredential is subcredential + timestamp, return length of autData without flag
private:
uint8_t m_StoreType;
uint32_t m_PublishedTimestamp = 0; // seconds
uint32_t m_PublishedTimestamp = 0;
bool m_IsPublic = true, m_IsPublishedEncrypted = false;
std::shared_ptr<i2p::crypto::Verifier> m_TransientVerifier;
CryptoKeyType m_EncryptionType;
@ -256,13 +246,17 @@ namespace data
{
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,
const EncryptionKeys& encryptionKeys,
const KeySections& encryptionKeys,
const std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> >& tunnels,
bool isPublic, uint64_t publishedTimestamp,
bool isPublishedEncrypted = false);
bool isPublic, bool isPublishedEncrypted = false);
LocalLeaseSet2 (uint8_t storeType, std::shared_ptr<const IdentityEx> identity, const uint8_t * buf, size_t len); // from I2CP

View file

@ -172,6 +172,16 @@ void LogPrint (std::stringstream& s, TValue&& arg) noexcept
s << std::forward<TValue>(arg);
}
#if (__cplusplus < 201703L) // below C++ 17
/** internal usage only -- folding args array to single string */
template<typename TValue, typename... TArgs>
void LogPrint (std::stringstream& s, TValue&& arg, TArgs&&... args) noexcept
{
LogPrint (s, std::forward<TValue>(arg));
LogPrint (s, std::forward<TArgs>(args)...);
}
#endif
/**
* @brief Create log message and send it to queue
* @param level Message level (eLogError, eLogInfo, ...)
@ -184,7 +194,13 @@ void LogPrint (LogLevel level, TArgs&&... args) noexcept
// fold message to single string
std::stringstream ss;
#if (__cplusplus >= 201703L) // C++ 17 or higher
(LogPrint (ss, std::forward<TArgs>(args)), ...);
#else
LogPrint (ss, std::forward<TArgs>(args)...);
#endif
auto msg = std::make_shared<i2p::log::LogMsg>(level, std::time(nullptr), std::move(ss).str());
msg->tid = std::this_thread::get_id();
i2p::log::Logger().Append(msg);
@ -201,7 +217,11 @@ void ThrowFatal (TArgs&&... args) noexcept
if (!f) return;
// fold message to single string
std::stringstream ss("");
#if (__cplusplus >= 201703L) // C++ 17 or higher
(LogPrint (ss, std::forward<TArgs>(args)), ...);
#else
LogPrint (ss, std::forward<TArgs>(args)...);
#endif
f (ss.str ());
}

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
*
@ -42,29 +42,28 @@ namespace transport
delete[] m_SessionConfirmedBuffer;
}
bool NTCP2Establisher::KeyDerivationFunction1 (const uint8_t * pub, i2p::crypto::X25519Keys& priv, const uint8_t * rs, const uint8_t * epub)
void NTCP2Establisher::KeyDerivationFunction1 (const uint8_t * pub, i2p::crypto::X25519Keys& priv, const uint8_t * rs, const uint8_t * epub)
{
i2p::crypto::InitNoiseXKState (*this, rs);
// h = SHA256(h || epub)
MixHash (epub, 32);
// x25519 between pub and priv
uint8_t inputKeyMaterial[32];
if (!priv.Agree (pub, inputKeyMaterial)) return false;
priv.Agree (pub, inputKeyMaterial);
MixKey (inputKeyMaterial);
return true;
}
bool NTCP2Establisher::KDF1Alice ()
void NTCP2Establisher::KDF1Alice ()
{
return KeyDerivationFunction1 (m_RemoteStaticKey, *m_EphemeralKeys, m_RemoteStaticKey, GetPub ());
KeyDerivationFunction1 (m_RemoteStaticKey, *m_EphemeralKeys, m_RemoteStaticKey, GetPub ());
}
bool NTCP2Establisher::KDF1Bob ()
void NTCP2Establisher::KDF1Bob ()
{
return KeyDerivationFunction1 (GetRemotePub (), i2p::context.GetNTCP2StaticKeys (), i2p::context.GetNTCP2StaticPublicKey (), GetRemotePub ());
KeyDerivationFunction1 (GetRemotePub (), i2p::context.GetNTCP2StaticKeys (), i2p::context.GetNTCP2StaticPublicKey (), GetRemotePub ());
}
bool NTCP2Establisher::KeyDerivationFunction2 (const uint8_t * sessionRequest, size_t sessionRequestLen, const uint8_t * epub)
void NTCP2Establisher::KeyDerivationFunction2 (const uint8_t * sessionRequest, size_t sessionRequestLen, const uint8_t * epub)
{
MixHash (sessionRequest + 32, 32); // encrypted payload
@ -75,35 +74,33 @@ namespace transport
// x25519 between remote pub and ephemaral priv
uint8_t inputKeyMaterial[32];
if (!m_EphemeralKeys->Agree (GetRemotePub (), inputKeyMaterial)) return false;
m_EphemeralKeys->Agree (GetRemotePub (), inputKeyMaterial);
MixKey (inputKeyMaterial);
return true;
}
bool NTCP2Establisher::KDF2Alice ()
void NTCP2Establisher::KDF2Alice ()
{
return KeyDerivationFunction2 (m_SessionRequestBuffer, m_SessionRequestBufferLen, GetRemotePub ());
KeyDerivationFunction2 (m_SessionRequestBuffer, m_SessionRequestBufferLen, GetRemotePub ());
}
bool NTCP2Establisher::KDF2Bob ()
void NTCP2Establisher::KDF2Bob ()
{
return KeyDerivationFunction2 (m_SessionRequestBuffer, m_SessionRequestBufferLen, GetPub ());
KeyDerivationFunction2 (m_SessionRequestBuffer, m_SessionRequestBufferLen, GetPub ());
}
bool NTCP2Establisher::KDF3Alice ()
void NTCP2Establisher::KDF3Alice ()
{
uint8_t inputKeyMaterial[32];
if (!i2p::context.GetNTCP2StaticKeys ().Agree (GetRemotePub (), inputKeyMaterial)) return false;
i2p::context.GetNTCP2StaticKeys ().Agree (GetRemotePub (), inputKeyMaterial);
MixKey (inputKeyMaterial);
return true;
}
bool NTCP2Establisher::KDF3Bob ()
void NTCP2Establisher::KDF3Bob ()
{
uint8_t inputKeyMaterial[32];
if (!m_EphemeralKeys->Agree (m_RemoteStaticKey, inputKeyMaterial)) return false;
m_EphemeralKeys->Agree (m_RemoteStaticKey, inputKeyMaterial);
MixKey (inputKeyMaterial);
return true;
}
void NTCP2Establisher::CreateEphemeralKey ()
@ -111,19 +108,20 @@ namespace transport
m_EphemeralKeys = i2p::transport::transports.GetNextX25519KeysPair ();
}
bool NTCP2Establisher::CreateSessionRequestMessage (std::mt19937& rng)
void NTCP2Establisher::CreateSessionRequestMessage ()
{
// create buffer and fill padding
auto paddingLength = rng () % (NTCP2_SESSION_REQUEST_MAX_SIZE - 64); // message length doesn't exceed 287 bytes
auto paddingLength = rand () % (NTCP2_SESSION_REQUEST_MAX_SIZE - 64); // message length doesn't exceed 287 bytes
m_SessionRequestBufferLen = paddingLength + 64;
RAND_bytes (m_SessionRequestBuffer + 64, paddingLength);
// encrypt X
i2p::crypto::CBCEncryption encryption;
encryption.SetKey (m_RemoteIdentHash);
encryption.Encrypt (GetPub (), 32, m_IV, m_SessionRequestBuffer); // X
memcpy (m_IV, m_SessionRequestBuffer + 16, 16); // save last block as IV for SessionCreated
encryption.SetIV (m_IV);
encryption.Encrypt (GetPub (), 32, m_SessionRequestBuffer); // X
encryption.GetIV (m_IV); // save IV for SessionCreated
// encryption key for next block
if (!KDF1Alice ()) return false;
KDF1Alice ();
// fill options
uint8_t options[32]; // actual options size is 16 bytes
memset (options, 0, 16);
@ -145,40 +143,36 @@ namespace transport
// 2 bytes reserved
htobe32buf (options + 8, (i2p::util::GetMillisecondsSinceEpoch () + 500)/1000); // tsA, rounded to seconds
// 4 bytes reserved
// encrypt options
if (!Encrypt (options, m_SessionRequestBuffer + 32, 16))
{
LogPrint (eLogWarning, "NTCP2: SessionRequest failed to encrypt options");
return false;
}
return true;
// sign and encrypt options, use m_H as AD
uint8_t nonce[12];
memset (nonce, 0, 12); // set nonce to zero
i2p::crypto::AEADChaCha20Poly1305 (options, 16, GetH (), 32, GetK (), nonce, m_SessionRequestBuffer + 32, 32, true); // encrypt
}
bool NTCP2Establisher::CreateSessionCreatedMessage (std::mt19937& rng)
void NTCP2Establisher::CreateSessionCreatedMessage ()
{
auto paddingLen = rng () % (NTCP2_SESSION_CREATED_MAX_SIZE - 64);
auto paddingLen = rand () % (NTCP2_SESSION_CREATED_MAX_SIZE - 64);
m_SessionCreatedBufferLen = paddingLen + 64;
RAND_bytes (m_SessionCreatedBuffer + 64, paddingLen);
// encrypt Y
i2p::crypto::CBCEncryption encryption;
encryption.SetKey (i2p::context.GetIdentHash ());
encryption.Encrypt (GetPub (), 32, m_IV, m_SessionCreatedBuffer); // Y
encryption.SetIV (m_IV);
encryption.Encrypt (GetPub (), 32, m_SessionCreatedBuffer); // Y
// encryption key for next block (m_K)
if (!KDF2Bob ()) return false;
KDF2Bob ();
uint8_t options[16];
memset (options, 0, 16);
htobe16buf (options + 2, paddingLen); // padLen
htobe32buf (options + 8, (i2p::util::GetMillisecondsSinceEpoch () + 500)/1000); // tsB, rounded to seconds
// encrypt options
if (!Encrypt (options, m_SessionCreatedBuffer + 32, 16))
{
LogPrint (eLogWarning, "NTCP2: SessionCreated failed to encrypt options");
return false;
}
return true;
// sign and encrypt options, use m_H as AD
uint8_t nonce[12];
memset (nonce, 0, 12); // set nonce to zero
i2p::crypto::AEADChaCha20Poly1305 (options, 16, GetH (), 32, GetK (), nonce, m_SessionCreatedBuffer + 32, 32, true); // encrypt
}
bool NTCP2Establisher::CreateSessionConfirmedMessagePart1 ()
void NTCP2Establisher::CreateSessionConfirmedMessagePart1 (const uint8_t * nonce)
{
// update AD
MixHash (m_SessionCreatedBuffer + 32, 32); // encrypted payload
@ -186,31 +180,21 @@ namespace transport
if (paddingLength > 0)
MixHash (m_SessionCreatedBuffer + 64, paddingLength);
// part1 48 bytes, n = 1
if (!Encrypt (i2p::context.GetNTCP2StaticPublicKey (), m_SessionConfirmedBuffer, 32))
{
LogPrint (eLogWarning, "NTCP2: SessionConfirmed failed to encrypt part1");
return false;
}
return true;
// part1 48 bytes
i2p::crypto::AEADChaCha20Poly1305 (i2p::context.GetNTCP2StaticPublicKey (), 32, GetH (), 32, GetK (), nonce, m_SessionConfirmedBuffer, 48, true); // encrypt
}
bool NTCP2Establisher::CreateSessionConfirmedMessagePart2 ()
void NTCP2Establisher::CreateSessionConfirmedMessagePart2 (const uint8_t * nonce)
{
// part 2
// update AD again
MixHash (m_SessionConfirmedBuffer, 48);
// encrypt m3p2, it must be filled in SessionRequest
if (!KDF3Alice ()) return false; // MixKey, n = 0
KDF3Alice ();
uint8_t * m3p2 = m_SessionConfirmedBuffer + 48;
if (!Encrypt (m3p2, m3p2, m3p2Len - 16))
{
LogPrint (eLogWarning, "NTCP2: SessionConfirmed failed to encrypt part2");
return false;
}
i2p::crypto::AEADChaCha20Poly1305 (m3p2, m3p2Len - 16, GetH (), 32, GetK (), nonce, m3p2, m3p2Len, true); // encrypt
// update h again
MixHash (m3p2, m3p2Len); //h = SHA256(h || ciphertext)
return true;
}
bool NTCP2Establisher::ProcessSessionRequestMessage (uint16_t& paddingLen, bool& clockSkew)
@ -219,17 +203,15 @@ namespace transport
// decrypt X
i2p::crypto::CBCDecryption decryption;
decryption.SetKey (i2p::context.GetIdentHash ());
decryption.Decrypt (m_SessionRequestBuffer, 32, i2p::context.GetNTCP2IV (), GetRemotePub ());
memcpy (m_IV, m_SessionRequestBuffer + 16, 16); // save last block as IV for SessionCreated
decryption.SetIV (i2p::context.GetNTCP2IV ());
decryption.Decrypt (m_SessionRequestBuffer, 32, GetRemotePub ());
decryption.GetIV (m_IV); // save IV for SessionCreated
// decryption key for next block
if (!KDF1Bob ())
{
LogPrint (eLogWarning, "NTCP2: SessionRequest KDF failed");
return false;
}
// verify MAC and decrypt options block (32 bytes)
uint8_t options[16];
if (Decrypt (m_SessionRequestBuffer + 32, options, 16))
KDF1Bob ();
// verify MAC and decrypt options block (32 bytes), use m_H as AD
uint8_t nonce[12], options[16];
memset (nonce, 0, 12); // set nonce to zero
if (i2p::crypto::AEADChaCha20Poly1305 (m_SessionRequestBuffer + 32, 16, GetH (), 32, GetK (), nonce, options, 16, false)) // decrypt
{
// options
if (options[0] && options[0] != i2p::context.GetNetID ())
@ -277,16 +259,15 @@ namespace transport
// decrypt Y
i2p::crypto::CBCDecryption decryption;
decryption.SetKey (m_RemoteIdentHash);
decryption.Decrypt (m_SessionCreatedBuffer, 32, m_IV, GetRemotePub ());
decryption.SetIV (m_IV);
decryption.Decrypt (m_SessionCreatedBuffer, 32, GetRemotePub ());
// decryption key for next block (m_K)
if (!KDF2Alice ())
{
LogPrint (eLogWarning, "NTCP2: SessionCreated KDF failed");
return false;
}
KDF2Alice ();
// decrypt and verify MAC
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
paddingLen = bufbe16toh(payload + 2);
@ -307,7 +288,7 @@ namespace transport
return true;
}
bool NTCP2Establisher::ProcessSessionConfirmedMessagePart1 ()
bool NTCP2Establisher::ProcessSessionConfirmedMessagePart1 (const uint8_t * nonce)
{
// update AD
MixHash (m_SessionCreatedBuffer + 32, 32); // encrypted payload
@ -315,8 +296,7 @@ namespace transport
if (paddingLength > 0)
MixHash (m_SessionCreatedBuffer + 64, paddingLength);
// decrypt S, n = 1
if (!Decrypt (m_SessionConfirmedBuffer, m_RemoteStaticKey, 32))
if (!i2p::crypto::AEADChaCha20Poly1305 (m_SessionConfirmedBuffer, 32, GetH (), 32, GetK (), nonce, m_RemoteStaticKey, 32, false)) // decrypt S
{
LogPrint (eLogWarning, "NTCP2: SessionConfirmed Part1 AEAD verification failed ");
return false;
@ -324,17 +304,13 @@ namespace transport
return true;
}
bool NTCP2Establisher::ProcessSessionConfirmedMessagePart2 (uint8_t * m3p2Buf)
bool NTCP2Establisher::ProcessSessionConfirmedMessagePart2 (const uint8_t * nonce, uint8_t * m3p2Buf)
{
// update AD again
MixHash (m_SessionConfirmedBuffer, 48);
if (!KDF3Bob ()) // MixKey, n = 0
{
LogPrint (eLogWarning, "NTCP2: SessionConfirmed Part2 KDF failed");
return false;
}
if (Decrypt (m_SessionConfirmedBuffer + 48, m3p2Buf, m3p2Len - 16))
KDF3Bob ();
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
MixHash (m_SessionConfirmedBuffer + 48, m3p2Len); // h = SHA256(h || ciphertext)
else
@ -351,7 +327,6 @@ namespace transport
m_Server (server), m_Socket (m_Server.GetService ()),
m_IsEstablished (false), m_IsTerminated (false),
m_Establisher (new NTCP2Establisher),
m_SendKey (nullptr), m_ReceiveKey (nullptr),
#if OPENSSL_SIPHASH
m_SendMDCtx(nullptr), m_ReceiveMDCtx (nullptr),
#else
@ -374,7 +349,7 @@ namespace transport
LogPrint (eLogWarning, "NTCP2: Missing NTCP2 address");
}
m_NextRouterInfoResendTime = i2p::util::GetSecondsSinceEpoch () + NTCP2_ROUTERINFO_RESEND_INTERVAL +
m_Server.GetRng ()() % NTCP2_ROUTERINFO_RESEND_INTERVAL_THRESHOLD;
rand ()%NTCP2_ROUTERINFO_RESEND_INTERVAL_THRESHOLD;
}
NTCP2Session::~NTCP2Session ()
@ -400,8 +375,6 @@ namespace transport
m_Socket.close ();
transports.PeerDisconnected (shared_from_this ());
m_Server.RemoveNTCP2Session (shared_from_this ());
if (!m_IntermediateQueue.empty ())
m_SendQueue.splice (m_SendQueue.end (), m_IntermediateQueue);
for (auto& it: m_SendQueue)
it->Drop ();
m_SendQueue.clear ();
@ -431,14 +404,14 @@ namespace transport
void NTCP2Session::Done ()
{
boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ()));
m_Server.GetService ().post (std::bind (&NTCP2Session::Terminate, shared_from_this ()));
}
void NTCP2Session::Established ()
{
m_IsEstablished = true;
m_Establisher.reset (nullptr);
SetTerminationTimeout (NTCP2_TERMINATION_TIMEOUT + m_Server.GetRng ()() % NTCP2_TERMINATION_TIMEOUT_VARIANCE);
SetTerminationTimeout (NTCP2_TERMINATION_TIMEOUT);
SendQueue ();
transports.PeerConnected (shared_from_this ());
}
@ -491,12 +464,7 @@ namespace transport
void NTCP2Session::SendSessionRequest ()
{
if (!m_Establisher->CreateSessionRequestMessage (m_Server.GetRng ()))
{
LogPrint (eLogWarning, "NTCP2: Send SessionRequest KDF failed");
boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ()));
return;
}
m_Establisher->CreateSessionRequestMessage ();
// send message
m_HandshakeInterval = i2p::util::GetMillisecondsSinceEpoch ();
boost::asio::async_write (m_Socket, boost::asio::buffer (m_Establisher->m_SessionRequestBuffer, m_Establisher->m_SessionRequestBufferLen), boost::asio::transfer_all (),
@ -521,6 +489,7 @@ namespace transport
void NTCP2Session::HandleSessionRequestReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred)
{
(void) bytes_transferred;
if (ecode)
{
LogPrint (eLogWarning, "NTCP2: SessionRequest read error: ", ecode.message ());
@ -528,18 +497,7 @@ namespace transport
}
else
{
m_Establisher->CreateEphemeralKey ();
boost::asio::post (m_Server.GetEstablisherService (),
[s = shared_from_this (), bytes_transferred] ()
{
s->ProcessSessionRequest (bytes_transferred);;
});
}
}
void NTCP2Session::ProcessSessionRequest (size_t len)
{
LogPrint (eLogDebug, "NTCP2: SessionRequest received ", len);
LogPrint (eLogDebug, "NTCP2: SessionRequest received ", bytes_transferred);
uint16_t paddingLen = 0;
bool clockSkew = false;
if (m_Establisher->ProcessSessionRequestMessage (paddingLen, clockSkew))
@ -548,7 +506,7 @@ namespace transport
{
// we don't care about padding, send SessionCreated and close session
SendSessionCreated ();
boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ()));
m_Server.GetService ().post (std::bind (&NTCP2Session::Terminate, shared_from_this ()));
}
else if (paddingLen > 0)
{
@ -560,14 +518,15 @@ namespace transport
else
{
LogPrint (eLogWarning, "NTCP2: SessionRequest padding length ", (int)paddingLen, " is too long");
boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ()));
Terminate ();
}
}
else
SendSessionCreated ();
}
else
ReadSomethingAndTerminate (); // probing resistance
Terminate ();
}
}
void NTCP2Session::HandleSessionRequestPaddingReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred)
@ -578,23 +537,12 @@ namespace transport
Terminate ();
}
else
{
boost::asio::post (m_Server.GetEstablisherService (),
[s = shared_from_this ()] ()
{
s->SendSessionCreated ();
});
}
SendSessionCreated ();
}
void NTCP2Session::SendSessionCreated ()
{
if (!m_Establisher->CreateSessionCreatedMessage (m_Server.GetRng ()))
{
LogPrint (eLogWarning, "NTCP2: Send SessionCreated KDF failed");
boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ()));
return;
}
m_Establisher->CreateSessionCreatedMessage ();
// send message
m_HandshakeInterval = i2p::util::GetMillisecondsSinceEpoch ();
boost::asio::async_write (m_Socket, boost::asio::buffer (m_Establisher->m_SessionCreatedBuffer, m_Establisher->m_SessionCreatedBufferLen), boost::asio::transfer_all (),
@ -611,17 +559,7 @@ namespace transport
else
{
m_HandshakeInterval = i2p::util::GetMillisecondsSinceEpoch () - m_HandshakeInterval;
boost::asio::post (m_Server.GetEstablisherService (),
[s = shared_from_this (), bytes_transferred] ()
{
s->ProcessSessionCreated (bytes_transferred);
});
}
}
void NTCP2Session::ProcessSessionCreated (size_t len)
{
LogPrint (eLogDebug, "NTCP2: SessionCreated received ", len);
LogPrint (eLogDebug, "NTCP2: SessionCreated received ", bytes_transferred);
uint16_t paddingLen = 0;
if (m_Establisher->ProcessSessionCreatedMessage (paddingLen))
{
@ -635,7 +573,7 @@ namespace transport
else
{
LogPrint (eLogWarning, "NTCP2: SessionCreated padding length ", (int)paddingLen, " is too long");
boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ()));
Terminate ();
}
}
else
@ -645,7 +583,8 @@ namespace transport
{
if (GetRemoteIdentity ())
i2p::data::netdb.SetUnreachable (GetRemoteIdentity ()->GetIdentHash (), true); // assume wrong s key
boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ()));
Terminate ();
}
}
}
@ -659,27 +598,17 @@ namespace transport
else
{
m_Establisher->m_SessionCreatedBufferLen += bytes_transferred;
boost::asio::post (m_Server.GetEstablisherService (),
[s = shared_from_this ()] ()
{
s->SendSessionConfirmed ();
});
SendSessionConfirmed ();
}
}
void NTCP2Session::SendSessionConfirmed ()
{
if (!m_Establisher->CreateSessionConfirmedMessagePart1 ())
{
boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ()));
return;
}
if (!m_Establisher->CreateSessionConfirmedMessagePart2 ())
{
LogPrint (eLogWarning, "NTCP2: Send SessionConfirmed Part2 KDF failed");
boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ()));
return;
}
uint8_t nonce[12];
CreateNonce (1, nonce); // set nonce to 1
m_Establisher->CreateSessionConfirmedMessagePart1 (nonce);
memset (nonce, 0, 12); // set nonce back to 0
m_Establisher->CreateSessionConfirmedMessagePart2 (nonce);
// send message
boost::asio::async_write (m_Socket, boost::asio::buffer (m_Establisher->m_SessionConfirmedBuffer, m_Establisher->m3p2Len + 48), boost::asio::transfer_all (),
std::bind(&NTCP2Session::HandleSessionConfirmedSent, shared_from_this (), std::placeholders::_1, std::placeholders::_2));
@ -731,7 +660,6 @@ namespace transport
void NTCP2Session::HandleSessionConfirmedReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred)
{
(void) bytes_transferred;
if (ecode)
{
LogPrint (eLogWarning, "NTCP2: SessionConfirmed read error: ", ecode.message ());
@ -740,56 +668,17 @@ namespace transport
else
{
m_HandshakeInterval = i2p::util::GetMillisecondsSinceEpoch () - m_HandshakeInterval;
boost::asio::post (m_Server.GetEstablisherService (),
[s = shared_from_this ()] ()
{
s->ProcessSessionConfirmed ();;
});
}
}
void NTCP2Session::ProcessSessionConfirmed ()
{
// run on establisher thread
LogPrint (eLogDebug, "NTCP2: SessionConfirmed received");
// part 1
if (m_Establisher->ProcessSessionConfirmedMessagePart1 ())
uint8_t nonce[12];
CreateNonce (1, nonce);
if (m_Establisher->ProcessSessionConfirmedMessagePart1 (nonce))
{
// part 2
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
std::vector<uint8_t> buf(m_Establisher->m3p2Len - 16); // -MAC
memset (nonce, 0, 12); // set nonce to 0 again
if (m_Establisher->ProcessSessionConfirmedMessagePart2 (nonce, buf.data ()))
{
// payload
// RI block must be first
if ((*buf)[0] != eNTCP2BlkRouterInfo)
{
LogPrint (eLogWarning, "NTCP2: Unexpected block ", (int)(*buf)[0], " in SessionConfirmed");
boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ()));
return;
}
auto size = bufbe16toh (buf->data () + 1);
if (size > buf->size () - 3 || size > i2p::data::MAX_RI_BUFFER_SIZE + 1)
{
LogPrint (eLogError, "NTCP2: Unexpected RouterInfo size ", size, " in SessionConfirmed");
boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ()));
return;
}
boost::asio::post (m_Server.GetService (),
[s = shared_from_this (), buf, size] ()
{
s->EstablishSessionAfterSessionConfirmed (buf, size);
});
}
else
boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ()));
}
else
boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ()));
}
void NTCP2Session::EstablishSessionAfterSessionConfirmed (std::shared_ptr<std::vector<uint8_t> > buf, size_t size)
{
// run on main NTCP2 thread
KeyDerivationFunctionDataPhase ();
// Bob data phase keys
m_SendKey = m_Kba;
@ -797,9 +686,23 @@ namespace transport
SetSipKeys (m_Sipkeysba, m_Sipkeysab);
memcpy (m_ReceiveIV.buf, m_Sipkeysab + 16, 8);
memcpy (m_SendIV.buf, m_Sipkeysba + 16, 8);
// we need to set keys for SendTerminationAndTerminate
// payload
// process RI
if (buf[0] != eNTCP2BlkRouterInfo)
{
LogPrint (eLogWarning, "NTCP2: Unexpected block ", (int)buf[0], " in SessionConfirmed");
Terminate ();
return;
}
auto size = bufbe16toh (buf.data () + 1);
if (size > buf.size () - 3)
{
LogPrint (eLogError, "NTCP2: Unexpected RouterInfo size ", size, " in SessionConfirmed");
Terminate ();
return;
}
// TODO: check flag
i2p::data::RouterInfo ri (buf->data () + 4, size - 1); // 1 byte block type + 2 bytes size + 1 byte flag
i2p::data::RouterInfo ri (buf.data () + 4, size - 1); // 1 byte block type + 2 bytes size + 1 byte flag
if (ri.IsUnreachable ())
{
LogPrint (eLogError, "NTCP2: RouterInfo verification failed in SessionConfirmed from ", GetRemoteEndpoint ());
@ -821,33 +724,8 @@ namespace transport
SendTerminationAndTerminate (eNTCP2Message3Error);
return;
}
// update RouterInfo in netdb
auto ri1 = i2p::data::netdb.AddRouterInfo (ri.GetBuffer (), ri.GetBufferLen ()); // ri1 points to one from netdb now
if (!ri1)
{
LogPrint (eLogError, "NTCP2: Couldn't update RouterInfo from SessionConfirmed in netdb");
Terminate ();
return;
}
bool isOlder = false;
if (ri.GetTimestamp () + i2p::data::NETDB_EXPIRATION_TIMEOUT_THRESHOLD*1000LL < ri1->GetTimestamp ())
{
// received RouterInfo is older than one in netdb
isOlder = true;
if (ri1->HasProfile ())
{
auto profile = i2p::data::GetRouterProfile (ri1->GetIdentHash ()); // retrieve profile
if (profile && profile->IsDuplicated ())
{
SendTerminationAndTerminate (eNTCP2Banned);
return;
}
}
}
auto addr = m_RemoteEndpoint.address ().is_v4 () ? ri1->GetNTCP2V4Address () :
(i2p::util::net::IsYggdrasilAddress (m_RemoteEndpoint.address ()) ? ri1->GetYggdrasilAddress () : ri1->GetNTCP2V6Address ());
auto addr = m_RemoteEndpoint.address ().is_v4 () ? ri.GetNTCP2V4Address () :
(i2p::util::net::IsYggdrasilAddress (m_RemoteEndpoint.address ()) ? ri.GetYggdrasilAddress () : ri.GetNTCP2V6Address ());
if (!addr || memcmp (m_Establisher->m_RemoteStaticKey, addr->s, 32))
{
LogPrint (eLogError, "NTCP2: Wrong static key in SessionConfirmed");
@ -859,21 +737,16 @@ namespace transport
memcmp (m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data () + 1, addr->host.to_v6 ().to_bytes ().data () + 1, 7) : // from the same yggdrasil subnet
memcmp (m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data (), addr->host.to_v6 ().to_bytes ().data (), 8)))) // temporary address
{
if (isOlder) // older router?
i2p::data::UpdateRouterProfile (ri1->GetIdentHash (),
[](std::shared_ptr<i2p::data::RouterProfile> profile)
{
if (profile) profile->Duplicated (); // mark router as duplicated in profile
});
else
LogPrint (eLogInfo, "NTCP2: Host mismatch between published address ", addr->host, " and actual endpoint ", m_RemoteEndpoint.address ());
SendTerminationAndTerminate (eNTCP2Banned);
LogPrint (eLogError, "NTCP2: Host mismatch between published address ", addr->host, " and actual endpoint ", m_RemoteEndpoint.address ());
Terminate ();
return;
}
// TODO: process options block
i2p::data::netdb.PostI2NPMsg (CreateI2NPMessage (eI2NPDummyMsg, buf.data () + 3, size)); // TODO: should insert ri and not parse it twice
// TODO: process options
// ready to communicate
SetRemoteIdentity (ri1->GetRouterIdentity ());
auto existing = i2p::data::netdb.FindRouter (ri.GetRouterIdentity ()->GetIdentHash ()); // check if exists already
SetRemoteIdentity (existing ? existing->GetRouterIdentity () : ri.GetRouterIdentity ());
if (m_Server.AddNTCP2Session (shared_from_this (), true))
{
Established ();
@ -882,6 +755,13 @@ namespace transport
else
Terminate ();
}
else
Terminate ();
}
else
Terminate ();
}
}
void NTCP2Session::SetSipKeys (const uint8_t * sendSipKey, const uint8_t * receiveSipKey)
{
@ -908,17 +788,14 @@ namespace transport
void NTCP2Session::ClientLogin ()
{
m_Establisher->CreateEphemeralKey ();
boost::asio::post (m_Server.GetEstablisherService (),
[s = shared_from_this ()] ()
{
s->SendSessionRequest ();
});
SendSessionRequest ();
}
void NTCP2Session::ServerLogin ()
{
SetTerminationTimeout (NTCP2_ESTABLISH_TIMEOUT);
SetLastActivityTimestamp (i2p::util::GetSecondsSinceEpoch ());
m_Establisher->CreateEphemeralKey ();
boost::asio::async_read (m_Socket, boost::asio::buffer(m_Establisher->m_SessionRequestBuffer, 64), boost::asio::transfer_all (),
std::bind(&NTCP2Session::HandleSessionRequestReceived, shared_from_this (),
std::placeholders::_1, std::placeholders::_2));
@ -1009,7 +886,7 @@ namespace transport
i2p::transport::transports.UpdateReceivedBytes (bytes_transferred + 2);
uint8_t nonce[12];
CreateNonce (m_ReceiveSequenceNumber, nonce); m_ReceiveSequenceNumber++;
if (m_Server.AEADChaCha20Poly1305Decrypt (m_NextReceivedBuffer, m_NextReceivedLen-16, nullptr, 0, m_ReceiveKey, nonce, m_NextReceivedBuffer, m_NextReceivedLen))
if (i2p::crypto::AEADChaCha20Poly1305 (m_NextReceivedBuffer, m_NextReceivedLen-16, nullptr, 0, m_ReceiveKey, nonce, m_NextReceivedBuffer, m_NextReceivedLen, false))
{
LogPrint (eLogDebug, "NTCP2: Received message decrypted");
ProcessNextFrame (m_NextReceivedBuffer, m_NextReceivedLen-16);
@ -1062,19 +939,7 @@ namespace transport
case eNTCP2BlkRouterInfo:
{
LogPrint (eLogDebug, "NTCP2: RouterInfo flag=", (int)frame[offset]);
if (size <= i2p::data::MAX_RI_BUFFER_SIZE + 1)
{
auto newRi = i2p::data::netdb.AddRouterInfo (frame + offset + 1, size - 1);
if (newRi)
{
auto remoteIdentity = GetRemoteIdentity ();
if (remoteIdentity && remoteIdentity->GetIdentHash () == newRi->GetIdentHash ())
// peer's RouterInfo update
SetRemoteIdentity (newRi->GetIdentity ());
}
}
else
LogPrint (eLogInfo, "NTCP2: RouterInfo block is too long ", size);
i2p::data::netdb.PostI2NPMsg (CreateI2NPMessage (eI2NPDummyMsg, frame + offset, size));
break;
}
case eNTCP2BlkI2NPMessage:
@ -1198,7 +1063,7 @@ namespace transport
}
uint8_t nonce[12];
CreateNonce (m_SendSequenceNumber, nonce); m_SendSequenceNumber++;
m_Server.AEADChaCha20Poly1305Encrypt (encryptBufs, m_SendKey, nonce, macBuf); // encrypt buffers
i2p::crypto::AEADChaCha20Poly1305Encrypt (encryptBufs, m_SendKey, nonce, macBuf); // encrypt buffers
SetNextSentFrameLength (totalLen + 16, first->GetNTCP2Header () - 5); // frame length right before first block
// send buffers
@ -1229,7 +1094,7 @@ namespace transport
// encrypt
uint8_t nonce[12];
CreateNonce (m_SendSequenceNumber, nonce); m_SendSequenceNumber++;
m_Server.AEADChaCha20Poly1305Encrypt ({ {m_NextSendBuffer + 2, payloadLen} }, m_SendKey, nonce, m_NextSendBuffer + payloadLen + 2);
i2p::crypto::AEADChaCha20Poly1305Encrypt ({ {m_NextSendBuffer + 2, payloadLen} }, m_SendKey, nonce, m_NextSendBuffer + payloadLen + 2);
SetNextSentFrameLength (payloadLen + 16, m_NextSendBuffer);
// send
m_IsSending = true;
@ -1256,7 +1121,7 @@ namespace transport
if (GetLastActivityTimestamp () > m_NextRouterInfoResendTime)
{
m_NextRouterInfoResendTime += NTCP2_ROUTERINFO_RESEND_INTERVAL +
m_Server.GetRng ()() % NTCP2_ROUTERINFO_RESEND_INTERVAL_THRESHOLD;
rand ()%NTCP2_ROUTERINFO_RESEND_INTERVAL_THRESHOLD;
SendRouterInfo ();
}
else
@ -1309,7 +1174,7 @@ namespace transport
void NTCP2Session::MoveSendQueue (std::shared_ptr<NTCP2Session> other)
{
if (!other || m_SendQueue.empty ()) return;
std::list<std::shared_ptr<I2NPMessage> > msgs;
std::vector<std::shared_ptr<I2NPMessage> > msgs;
auto ts = i2p::util::GetMillisecondsSinceEpoch ();
for (auto it: m_SendQueue)
if (!it->IsExpired (ts))
@ -1318,7 +1183,7 @@ namespace transport
it->Drop ();
m_SendQueue.clear ();
if (!msgs.empty ())
other->SendI2NPMessages (msgs);
other->PostI2NPMessages (msgs);
}
size_t NTCP2Session::CreatePaddingBlock (size_t msgLen, uint8_t * buf, size_t len)
@ -1396,58 +1261,23 @@ namespace transport
void NTCP2Session::SendTerminationAndTerminate (NTCP2TerminationReason reason)
{
SendTermination (reason);
boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ())); // let termination message go
m_Server.GetService ().post (std::bind (&NTCP2Session::Terminate, shared_from_this ())); // let termination message go
}
void NTCP2Session::ReadSomethingAndTerminate ()
void NTCP2Session::SendI2NPMessages (const std::vector<std::shared_ptr<I2NPMessage> >& msgs)
{
size_t len = m_Server.GetRng ()() % NTCP2_SESSION_REQUEST_MAX_SIZE;
if (len > 0 && m_Establisher)
boost::asio::async_read (m_Socket, boost::asio::buffer(m_Establisher->m_SessionRequestBuffer, len), boost::asio::transfer_all (),
[s = shared_from_this()](const boost::system::error_code& ecode, size_t bytes_transferred)
{
s->Terminate ();
});
else
boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ()));
m_Server.GetService ().post (std::bind (&NTCP2Session::PostI2NPMessages, shared_from_this (), msgs));
}
void NTCP2Session::SendI2NPMessages (std::list<std::shared_ptr<I2NPMessage> >& msgs)
{
if (m_IsTerminated || msgs.empty ())
{
msgs.clear ();
return;
}
bool empty = false;
{
std::lock_guard<std::mutex> l(m_IntermediateQueueMutex);
empty = m_IntermediateQueue.empty ();
m_IntermediateQueue.splice (m_IntermediateQueue.end (), msgs);
}
if (empty)
boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::PostI2NPMessages, shared_from_this ()));
}
void NTCP2Session::PostI2NPMessages ()
void NTCP2Session::PostI2NPMessages (std::vector<std::shared_ptr<I2NPMessage> > msgs)
{
if (m_IsTerminated) return;
std::list<std::shared_ptr<I2NPMessage> > msgs;
{
std::lock_guard<std::mutex> l(m_IntermediateQueueMutex);
m_IntermediateQueue.swap (msgs);
}
bool isSemiFull = m_SendQueue.size () > NTCP2_MAX_OUTGOING_QUEUE_SIZE/2;
if (isSemiFull)
{
for (auto it: msgs)
if (it->onDrop)
if (isSemiFull && it->onDrop)
it->Drop (); // drop earlier because we can handle it
else
m_SendQueue.push_back (std::move (it));
}
else
m_SendQueue.splice (m_SendQueue.end (), msgs);
if (!m_IsSending && m_IsEstablished)
SendQueue ();
@ -1463,19 +1293,12 @@ namespace transport
void NTCP2Session::SendLocalRouterInfo (bool update)
{
if (update || !IsOutgoing ()) // we send it in SessionConfirmed for outgoing session
boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::SendRouterInfo, shared_from_this ()));
}
i2p::data::RouterInfo::SupportedTransports NTCP2Session::GetTransportType () const
{
if (m_RemoteEndpoint.address ().is_v4 ()) return i2p::data::RouterInfo::eNTCP2V4;
return i2p::util::net::IsYggdrasilAddress (m_RemoteEndpoint.address ()) ? i2p::data::RouterInfo::eNTCP2V6Mesh : i2p::data::RouterInfo::eNTCP2V6;
m_Server.GetService ().post (std::bind (&NTCP2Session::SendRouterInfo, shared_from_this ()));
}
NTCP2Server::NTCP2Server ():
RunnableServiceWithWork ("NTCP2"), m_TerminationTimer (GetService ()),
m_ProxyType(eNoProxy), m_Resolver(GetService ()),
m_Rng(i2p::util::GetMonotonicMicroseconds ()%1000000LL)
m_ProxyType(eNoProxy), m_Resolver(GetService ())
{
}
@ -1486,7 +1309,6 @@ namespace transport
void NTCP2Server::Start ()
{
m_EstablisherService.Start ();
if (!IsRunning ())
{
StartIOService ();
@ -1494,13 +1316,14 @@ namespace transport
{
LogPrint(eLogInfo, "NTCP2: Using proxy to connect to peers");
// TODO: resolve proxy until it is resolved
boost::asio::ip::tcp::resolver::query q(m_ProxyAddress, std::to_string(m_ProxyPort));
boost::system::error_code e;
auto itr = m_Resolver.resolve(m_ProxyAddress, std::to_string(m_ProxyPort), e);
auto itr = m_Resolver.resolve(q, e);
if(e)
LogPrint(eLogCritical, "NTCP2: Failed to resolve proxy ", e.message());
else
{
m_ProxyEndpoint.reset (new boost::asio::ip::tcp::endpoint(*itr.begin ()));
m_ProxyEndpoint.reset (new boost::asio::ip::tcp::endpoint(*itr));
if (m_ProxyEndpoint)
LogPrint(eLogDebug, "NTCP2: m_ProxyEndpoint ", *m_ProxyEndpoint);
}
@ -1581,7 +1404,6 @@ namespace transport
void NTCP2Server::Stop ()
{
m_EstablisherService.Stop ();
{
// we have to copy it because Terminate changes m_NTCP2Sessions
auto ntcpSessions = m_NTCP2Sessions;
@ -1661,7 +1483,7 @@ namespace transport
}
LogPrint (eLogDebug, "NTCP2: Connecting to ", conn->GetRemoteEndpoint (),
" (", i2p::data::GetIdentHashAbbreviation (conn->GetRemoteIdentity ()->GetIdentHash ()), ")");
boost::asio::post (GetService (), [this, conn]()
GetService ().post([this, conn]()
{
if (this->AddNTCP2Session (conn))
{
@ -1819,8 +1641,7 @@ namespace transport
void NTCP2Server::ScheduleTermination ()
{
m_TerminationTimer.expires_from_now (boost::posix_time::seconds(
NTCP2_TERMINATION_CHECK_TIMEOUT + m_Rng () % NTCP2_TERMINATION_CHECK_TIMEOUT_VARIANCE));
m_TerminationTimer.expires_from_now (boost::posix_time::seconds(NTCP2_TERMINATION_CHECK_TIMEOUT));
m_TerminationTimer.async_wait (std::bind (&NTCP2Server::HandleTerminationTimer,
this, std::placeholders::_1));
}
@ -1882,7 +1703,7 @@ namespace transport
LogPrint (eLogError, "NTCP2: Can't connect to unspecified address");
return;
}
boost::asio::post (GetService(), [this, conn]()
GetService().post([this, conn]()
{
if (this->AddNTCP2Session (conn))
{
@ -1966,7 +1787,7 @@ namespace transport
LogPrint(eLogError, "NTCP2: HTTP proxy write error ", ec.message());
});
auto readbuff = std::make_shared<boost::asio::streambuf>();
boost::asio::streambuf * readbuff = new boost::asio::streambuf;
boost::asio::async_read_until(conn->GetSocket(), *readbuff, "\r\n\r\n",
[readbuff, timer, conn] (const boost::system::error_code & ec, std::size_t transferred)
{
@ -1980,12 +1801,13 @@ namespace transport
{
readbuff->commit(transferred);
i2p::http::HTTPRes res;
if(res.parse(std::string {boost::asio::buffers_begin(readbuff->data ()), boost::asio::buffers_begin(readbuff->data ()) + readbuff->size ()}) > 0)
if(res.parse(boost::asio::buffer_cast<const char*>(readbuff->data()), readbuff->size()) > 0)
{
if(res.code == 200)
{
timer->cancel();
conn->ClientLogin();
delete readbuff;
return;
}
else
@ -1995,6 +1817,7 @@ namespace transport
LogPrint(eLogError, "NTCP2: HTTP proxy gave malformed response");
timer->cancel();
conn->Terminate();
delete readbuff;
}
});
break;
@ -2017,17 +1840,5 @@ namespace transport
else
m_Address4 = addr;
}
void NTCP2Server::AEADChaCha20Poly1305Encrypt (const std::vector<std::pair<uint8_t *, size_t> >& bufs,
const uint8_t * key, const uint8_t * nonce, uint8_t * mac)
{
return m_Encryptor.Encrypt (bufs, key, nonce, mac);
}
bool NTCP2Server::AEADChaCha20Poly1305Decrypt (const uint8_t * msg, size_t msgLen,
const uint8_t * ad, size_t adLen, const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len)
{
return m_Decryptor.Decrypt (msg, msgLen, ad, adLen, key, nonce, buf, len);
}
}
}

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
*
@ -14,7 +14,6 @@
#include <list>
#include <map>
#include <array>
#include <random>
#include <openssl/bn.h>
#include <openssl/evp.h>
#include <boost/asio.hpp>
@ -36,10 +35,8 @@ namespace transport
const int NTCP2_CONNECT_TIMEOUT = 5; // 5 seconds
const int NTCP2_ESTABLISH_TIMEOUT = 10; // 10 seconds
const int NTCP2_TERMINATION_TIMEOUT = 115; // 2 minutes - 5 seconds
const int NTCP2_TERMINATION_TIMEOUT_VARIANCE = 10; // 10 seconds
const int NTCP2_TERMINATION_CHECK_TIMEOUT = 28; // 28 seconds
const int NTCP2_TERMINATION_CHECK_TIMEOUT_VARIANCE = 5; // 5 seconds
const int NTCP2_TERMINATION_TIMEOUT = 120; // 2 minutes
const int NTCP2_TERMINATION_CHECK_TIMEOUT = 30; // 30 seconds
const int NTCP2_RECEIVE_BUFFER_DELETION_TIMEOUT = 3; // 3 seconds
const int NTCP2_ROUTERINFO_RESEND_INTERVAL = 25*60; // 25 minuntes in seconds
const int NTCP2_ROUTERINFO_RESEND_INTERVAL_THRESHOLD = 25*60; // 25 minuntes
@ -91,29 +88,30 @@ namespace transport
const uint8_t * GetRemotePub () const { return m_RemoteEphemeralPublicKey; }; // Y for Alice and X for Bob
uint8_t * GetRemotePub () { return m_RemoteEphemeralPublicKey; }; // to set
const uint8_t * GetK () const { return m_CK + 32; };
const uint8_t * GetCK () const { return m_CK; };
const uint8_t * GetH () const { return m_H; };
bool KDF1Alice ();
bool KDF1Bob ();
bool KDF2Alice ();
bool KDF2Bob ();
bool KDF3Alice (); // for SessionConfirmed part 2
bool KDF3Bob ();
void KDF1Alice ();
void KDF1Bob ();
void KDF2Alice ();
void KDF2Bob ();
void KDF3Alice (); // for SessionConfirmed part 2
void KDF3Bob ();
bool KeyDerivationFunction1 (const uint8_t * pub, i2p::crypto::X25519Keys& priv, const uint8_t * rs, const uint8_t * epub); // for SessionRequest, (pub, priv) for DH
bool KeyDerivationFunction2 (const uint8_t * sessionRequest, size_t sessionRequestLen, const uint8_t * epub); // for SessionCreate
void KeyDerivationFunction1 (const uint8_t * pub, i2p::crypto::X25519Keys& priv, const uint8_t * rs, const uint8_t * epub); // for SessionRequest, (pub, priv) for DH
void KeyDerivationFunction2 (const uint8_t * sessionRequest, size_t sessionRequestLen, const uint8_t * epub); // for SessionCreate
void CreateEphemeralKey ();
bool CreateSessionRequestMessage (std::mt19937& rng);
bool CreateSessionCreatedMessage (std::mt19937& rng);
bool CreateSessionConfirmedMessagePart1 ();
bool CreateSessionConfirmedMessagePart2 ();
void CreateSessionRequestMessage ();
void CreateSessionCreatedMessage ();
void CreateSessionConfirmedMessagePart1 (const uint8_t * nonce);
void CreateSessionConfirmedMessagePart2 (const uint8_t * nonce);
bool ProcessSessionRequestMessage (uint16_t& paddingLen, bool& clockSkew);
bool ProcessSessionCreatedMessage (uint16_t& paddingLen);
bool ProcessSessionConfirmedMessagePart1 ();
bool ProcessSessionConfirmedMessagePart2 (uint8_t * m3p2Buf);
bool ProcessSessionConfirmedMessagePart1 (const uint8_t * nonce);
bool ProcessSessionConfirmedMessagePart2 (const uint8_t * nonce, uint8_t * m3p2Buf);
std::shared_ptr<i2p::crypto::X25519Keys> m_EphemeralKeys;
uint8_t m_RemoteEphemeralPublicKey[32]; // x25519
@ -146,14 +144,13 @@ namespace transport
void SetRemoteEndpoint (const boost::asio::ip::tcp::endpoint& ep) { m_RemoteEndpoint = ep; };
bool IsEstablished () const override { return m_IsEstablished; };
i2p::data::RouterInfo::SupportedTransports GetTransportType () const override;
bool IsTerminated () const { return m_IsTerminated; };
void ClientLogin (); // Alice
void ServerLogin (); // Bob
void SendLocalRouterInfo (bool update) override; // after handshake or by update
void SendI2NPMessages (std::list<std::shared_ptr<I2NPMessage> >& msgs) override;
void SendI2NPMessages (const std::vector<std::shared_ptr<I2NPMessage> >& msgs) override;
void MoveSendQueue (std::shared_ptr<NTCP2Session> other);
private:
@ -172,16 +169,12 @@ namespace transport
void HandleSessionRequestSent (const boost::system::error_code& ecode, std::size_t bytes_transferred);
void HandleSessionRequestReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred);
void ProcessSessionRequest (size_t len);
void HandleSessionRequestPaddingReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred);
void HandleSessionCreatedSent (const boost::system::error_code& ecode, std::size_t bytes_transferred);
void HandleSessionCreatedReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred);
void ProcessSessionCreated (size_t len);
void HandleSessionCreatedPaddingReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred);
void HandleSessionConfirmedSent (const boost::system::error_code& ecode, std::size_t bytes_transferred);
void HandleSessionConfirmedReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred);
void ProcessSessionConfirmed ();
void EstablishSessionAfterSessionConfirmed (std::shared_ptr<std::vector<uint8_t> > buf, size_t size);
// data
void ReceiveLength ();
@ -200,8 +193,7 @@ namespace transport
void SendRouterInfo ();
void SendTermination (NTCP2TerminationReason reason);
void SendTerminationAndTerminate (NTCP2TerminationReason reason);
void ReadSomethingAndTerminate ();
void PostI2NPMessages ();
void PostI2NPMessages (std::vector<std::shared_ptr<I2NPMessage> > msgs);
private:
@ -235,27 +227,12 @@ namespace transport
std::list<std::shared_ptr<I2NPMessage> > m_SendQueue;
uint64_t m_NextRouterInfoResendTime; // seconds since epoch
std::list<std::shared_ptr<I2NPMessage> > m_IntermediateQueue; // from transports
mutable std::mutex m_IntermediateQueueMutex;
uint16_t m_PaddingSizes[16];
int m_NextPaddingSize;
};
class NTCP2Server: private i2p::util::RunnableServiceWithWork
{
private:
class EstablisherService: public i2p::util::RunnableServiceWithWork
{
public:
EstablisherService (): RunnableServiceWithWork ("NTCP2e") {};
auto& GetService () { return GetIOService (); };
void Start () { StartIOService (); };
void Stop () { StopIOService (); };
};
public:
enum ProxyType
@ -270,14 +247,7 @@ namespace transport
void Start ();
void Stop ();
auto& GetService () { return GetIOService (); };
auto& GetEstablisherService () { return m_EstablisherService.GetService (); };
std::mt19937& GetRng () { return m_Rng; };
void AEADChaCha20Poly1305Encrypt (const std::vector<std::pair<uint8_t *, size_t> >& bufs,
const uint8_t * key, const uint8_t * nonce, uint8_t * mac);
bool AEADChaCha20Poly1305Decrypt (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen,
const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len);
boost::asio::io_service& GetService () { return GetIOService (); };
bool AddNTCP2Session (std::shared_ptr<NTCP2Session> session, bool incoming = false);
void RemoveNTCP2Session (std::shared_ptr<NTCP2Session> session);
@ -315,12 +285,7 @@ namespace transport
uint16_t m_ProxyPort;
boost::asio::ip::tcp::resolver m_Resolver;
std::unique_ptr<boost::asio::ip::tcp::endpoint> m_ProxyEndpoint;
std::shared_ptr<boost::asio::ip::tcp::endpoint> m_Address4, m_Address6, m_YggdrasilAddress;
std::mt19937 m_Rng;
EstablisherService m_EstablisherService;
i2p::crypto::AEADChaCha20Poly1305Encryptor m_Encryptor;
i2p::crypto::AEADChaCha20Poly1305Decryptor m_Decryptor;
public:

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
*
@ -10,6 +10,7 @@
#include <fstream>
#include <vector>
#include <map>
#include <random>
#include <boost/asio.hpp>
#include <stdexcept>
@ -39,7 +40,7 @@ namespace data
NetDb::NetDb (): m_IsRunning (false), m_Thread (nullptr), m_Reseeder (nullptr),
m_Storage("netDb", "r", "routerInfo-", "dat"), m_PersistProfiles (true),
m_LastExploratorySelectionUpdateTime (0), m_Rng(i2p::util::GetMonotonicMicroseconds () % 1000000LL)
m_LastExploratorySelectionUpdateTime (0)
{
}
@ -68,7 +69,7 @@ namespace data
{
Reseed ();
}
else if (!GetRandomRouter (i2p::context.GetSharedRouterInfo (), false, false, false))
else if (!GetRandomRouter (i2p::context.GetSharedRouterInfo (), false, false))
Reseed (); // we don't have a router we can connect to. Trying to reseed
auto it = m_RouterInfos.find (i2p::context.GetIdentHash ());
@ -118,22 +119,19 @@ namespace data
i2p::util::SetThreadName("NetDB");
uint64_t lastManage = 0;
uint64_t lastProfilesCleanup = i2p::util::GetMonotonicMilliseconds (),
lastObsoleteProfilesCleanup = lastProfilesCleanup, lastApplyingProfileUpdates = lastProfilesCleanup;
int16_t profilesCleanupVariance = 0, obsoleteProfilesCleanVariance = 0, applyingProfileUpdatesVariance = 0;
uint64_t lastProfilesCleanup = i2p::util::GetMonotonicMilliseconds (), lastObsoleteProfilesCleanup = lastProfilesCleanup;
int16_t profilesCleanupVariance = 0, obsoleteProfilesCleanVariance = 0;
std::list<std::shared_ptr<const I2NPMessage> > msgs;
while (m_IsRunning)
{
try
{
if (m_Queue.Wait (1,0)) // 1 sec
auto msg = m_Queue.GetNextWithTimeout (1000); // 1 sec
if (msg)
{
m_Queue.GetWholeQueue (msgs);
while (!msgs.empty ())
int numMsgs = 0;
while (msg)
{
auto msg = msgs.front (); msgs.pop_front ();
if (!msg) continue;
LogPrint(eLogDebug, "NetDb: Got request with type ", (int) msg->GetTypeID ());
switch (msg->GetTypeID ())
{
@ -143,10 +141,17 @@ namespace data
case eI2NPDatabaseLookup:
HandleDatabaseLookupMsg (msg);
break;
case eI2NPDummyMsg:
// plain RouterInfo from NTCP2 with flags for now
HandleNTCP2RouterInfoMsg (msg);
break;
default: // WTF?
LogPrint (eLogError, "NetDb: Unexpected message type ", (int) msg->GetTypeID ());
//i2p::HandleI2NPMessage (msg);
}
if (numMsgs > 100) break;
msg = m_Queue.Get ();
numMsgs++;
}
}
if (!m_IsRunning) break;
@ -181,7 +186,7 @@ namespace data
LogPrint (eLogWarning, "NetDb: Can't persist profiles. Profiles are being saved to disk");
}
lastProfilesCleanup = mts;
profilesCleanupVariance = m_Rng () % i2p::data::PEER_PROFILE_AUTOCLEAN_VARIANCE;
profilesCleanupVariance = rand () % i2p::data::PEER_PROFILE_AUTOCLEAN_VARIANCE;
}
if (mts >= lastObsoleteProfilesCleanup + (uint64_t)(i2p::data::PEER_PROFILE_OBSOLETE_PROFILES_CLEAN_TIMEOUT + obsoleteProfilesCleanVariance)*1000)
@ -197,20 +202,7 @@ namespace data
else
LogPrint (eLogWarning, "NetDb: Can't delete profiles. Profiles are being deleted from disk");
lastObsoleteProfilesCleanup = mts;
obsoleteProfilesCleanVariance = m_Rng () % i2p::data::PEER_PROFILE_OBSOLETE_PROFILES_CLEAN_VARIANCE;
}
if (mts >= lastApplyingProfileUpdates + i2p::data::PEER_PROFILE_APPLY_POSTPONED_TIMEOUT + applyingProfileUpdatesVariance)
{
bool isApplying = m_ApplyingProfileUpdates.valid ();
if (isApplying && m_ApplyingProfileUpdates.wait_for(std::chrono::seconds(0)) == std::future_status::ready) // still active?
{
m_ApplyingProfileUpdates.get ();
isApplying = false;
}
if (!isApplying)
m_ApplyingProfileUpdates = i2p::data::FlushPostponedRouterProfileUpdates ();
lastApplyingProfileUpdates = mts;
applyingProfileUpdatesVariance = m_Rng () % i2p::data::PEER_PROFILE_APPLY_POSTPONED_TIMEOUT_VARIANCE;
obsoleteProfilesCleanVariance = rand () % i2p::data::PEER_PROFILE_OBSOLETE_PROFILES_CLEAN_VARIANCE;
}
}
catch (std::exception& ex)
@ -294,7 +286,6 @@ namespace data
}
else
{
r->CancelBufferToDelete (); // since an update received
if (CheckLogLevel (eLogDebug))
LogPrint (eLogDebug, "NetDb: RouterInfo is older: ", ident.ToBase64());
updated = false;
@ -308,8 +299,7 @@ namespace data
{
auto mts = i2p::util::GetMillisecondsSinceEpoch ();
isValid = mts + NETDB_EXPIRATION_TIMEOUT_THRESHOLD*1000LL > r->GetTimestamp () && // from future
(mts < r->GetTimestamp () + NETDB_MAX_EXPIRATION_TIMEOUT*1000LL || // too old
context.GetUptime () < NETDB_CHECK_FOR_EXPIRATION_UPTIME/10); // enough uptime
mts < r->GetTimestamp () + NETDB_MAX_EXPIRATION_TIMEOUT*1000LL; // too old
}
if (isValid)
{
@ -361,7 +351,7 @@ namespace data
{
if(it->second->GetExpirationTime() < expires)
{
it->second->Update (buf, len, nullptr, false); // signature is verified already
it->second->Update (buf, len, false); // signature is verified already
if (CheckLogLevel (eLogInfo))
LogPrint (eLogInfo, "NetDb: LeaseSet updated: ", ident.ToBase32());
updated = true;
@ -398,7 +388,8 @@ namespace data
if (it == m_LeaseSets.end () || it->second->GetStoreType () != storeType ||
leaseSet->GetPublishedTimestamp () > it->second->GetPublishedTimestamp ())
{
if (leaseSet->IsPublic () && !leaseSet->IsExpired ())
if (leaseSet->IsPublic () && !leaseSet->IsExpired () &&
i2p::util::GetSecondsSinceEpoch () + NETDB_EXPIRATION_TIMEOUT_THRESHOLD > leaseSet->GetPublishedTimestamp ())
{
// TODO: implement actual update
if (CheckLogLevel (eLogInfo))
@ -493,7 +484,7 @@ namespace data
void NetDb::ReseedFromFloodfill(const RouterInfo & ri, int numRouters, int numFloodfills)
{
LogPrint(eLogInfo, "NetDB: Reseeding from floodfill ", ri.GetIdentHashBase64());
std::list<std::shared_ptr<i2p::I2NPMessage> > requests;
std::vector<std::shared_ptr<i2p::I2NPMessage> > requests;
i2p::data::IdentHash ourIdent = i2p::context.GetIdentHash();
i2p::data::IdentHash ih = ri.GetIdentHash();
@ -516,7 +507,7 @@ namespace data
}
// send them off
i2p::transport::transports.SendMessages(ih, std::move (requests));
i2p::transport::transports.SendMessages(ih, requests);
}
bool NetDb::LoadRouterInfo (const std::string& path, uint64_t ts)
@ -571,7 +562,7 @@ namespace data
while(n > 0)
{
std::lock_guard<std::mutex> lock(m_RouterInfosMutex);
uint32_t idx = m_Rng () % m_RouterInfos.size ();
uint32_t idx = rand () % m_RouterInfos.size ();
uint32_t i = 0;
for (const auto & it : m_RouterInfos) {
if(i >= idx) // are we at the random start point?
@ -651,80 +642,70 @@ namespace data
if (checkForExpiration && uptime > i2p::transport::SSU2_TO_INTRODUCER_SESSION_DURATION) // 1 hour
expirationTimeout = i2p::context.IsFloodfill () ? NETDB_FLOODFILL_EXPIRATION_TIMEOUT*1000LL :
NETDB_MIN_EXPIRATION_TIMEOUT*1000LL + (NETDB_MAX_EXPIRATION_TIMEOUT - NETDB_MIN_EXPIRATION_TIMEOUT)*1000LL*NETDB_MIN_ROUTERS/total;
bool isOffline = checkForExpiration && i2p::transport::transports.GetNumPeers () < NETDB_MIN_TRANSPORTS; // enough routers and uptime, but no transports
std::list<std::pair<std::string, std::shared_ptr<RouterInfo::Buffer> > > saveToDisk;
std::list<std::string> removeFromDisk;
auto own = i2p::context.GetSharedRouterInfo ();
for (auto [ident, r]: m_RouterInfos)
for (auto& it: m_RouterInfos)
{
if (!r || r == own) continue; // skip own
if (r->IsBufferScheduledToDelete ()) // from previous SaveUpdated, we assume m_PersistingRouters complete
if (!it.second || it.second == own) continue; // skip own
std::string ident = it.second->GetIdentHashBase64();
if (it.second->IsUpdated ())
{
std::lock_guard<std::mutex> l(m_RouterInfosMutex); // possible collision between DeleteBuffer and Update
r->DeleteBuffer ();
}
if (r->IsUpdated ())
{
if (r->GetBuffer () && !r->IsUnreachable ())
if (it.second->GetBuffer ())
{
// we have something to save
std::shared_ptr<RouterInfo::Buffer> buffer;
{
std::lock_guard<std::mutex> l(m_RouterInfosMutex); // possible collision between DeleteBuffer and Update
buffer = r->CopyBuffer ();
buffer = it.second->GetSharedBuffer ();
it.second->DeleteBuffer ();
}
if (!i2p::transport::transports.IsConnected (ident))
r->ScheduleBufferToDelete ();
if (buffer)
saveToDisk.emplace_back(ident.ToBase64 (), buffer);
if (buffer && !it.second->IsUnreachable ()) // don't save bad router
saveToDisk.push_back(std::make_pair(ident, buffer));
it.second->SetUnreachable (false);
}
r->SetUpdated (false);
it.second->SetUpdated (false);
updatedCount++;
continue;
}
else if (r->GetBuffer () && ts > r->GetTimestamp () + NETDB_MIN_EXPIRATION_TIMEOUT*1000LL)
// since update was long time ago we assume that router is not connected anymore
r->ScheduleBufferToDelete ();
if (r->HasProfile () && r->GetProfile ()->IsUnreachable ())
r->SetUnreachable (true);
if (it.second->GetProfile ()->IsUnreachable ())
it.second->SetUnreachable (true);
// make router reachable back if too few routers or floodfills
if (r->IsUnreachable () && (total - deletedCount < NETDB_MIN_ROUTERS || isLowRate || isOffline ||
(r->IsFloodfill () && totalFloodfills - deletedFloodfillsCount < NETDB_MIN_FLOODFILLS)))
r->SetUnreachable (false);
if (!r->IsUnreachable ())
if (it.second->IsUnreachable () && (total - deletedCount < NETDB_MIN_ROUTERS || isLowRate ||
(it.second->IsFloodfill () && totalFloodfills - deletedFloodfillsCount < NETDB_MIN_FLOODFILLS)))
it.second->SetUnreachable (false);
if (!it.second->IsUnreachable ())
{
// find & mark expired routers
if (!r->GetCompatibleTransports (true)) // non reachable by any transport
r->SetUnreachable (true);
else if (ts + NETDB_EXPIRATION_TIMEOUT_THRESHOLD*1000LL < r->GetTimestamp ())
if (!it.second->GetCompatibleTransports (true)) // non reachable by any transport
it.second->SetUnreachable (true);
else if (ts + NETDB_EXPIRATION_TIMEOUT_THRESHOLD*1000LL < it.second->GetTimestamp ())
{
LogPrint (eLogWarning, "NetDb: RouterInfo is from future for ", (r->GetTimestamp () - ts)/1000LL, " seconds");
r->SetUnreachable (true);
LogPrint (eLogWarning, "NetDb: RouterInfo is from future for ", (it.second->GetTimestamp () - ts)/1000LL, " seconds");
it.second->SetUnreachable (true);
}
else if (checkForExpiration)
{
if (ts > r->GetTimestamp () + expirationTimeout)
r->SetUnreachable (true);
else if ((ts > r->GetTimestamp () + expirationTimeout/2) && // more than half of expiration
total > NETDB_NUM_ROUTERS_THRESHOLD && !r->IsHighBandwidth() && // low bandwidth
!r->IsFloodfill() && (!i2p::context.IsFloodfill () || // non floodfill
(CreateRoutingKey (ident) ^ i2p::context.GetIdentHash ()).metric[0] >= 0x02)) // different first 7 bits
r->SetUnreachable (true);
if (ts > it.second->GetTimestamp () + expirationTimeout)
it.second->SetUnreachable (true);
else if ((ts > it.second->GetTimestamp () + expirationTimeout/2) && // more than half of expiration
total > NETDB_NUM_ROUTERS_THRESHOLD && !it.second->IsHighBandwidth() && // low bandwidth
!it.second->IsFloodfill() && (!i2p::context.IsFloodfill () || // non floodfill
(CreateRoutingKey (it.second->GetIdentHash ()) ^ i2p::context.GetIdentHash ()).metric[0] >= 0x02)) // different first 7 bits
it.second->SetUnreachable (true);
}
}
// make router reachable back if connected now or trusted router
if (r->IsUnreachable () && (i2p::transport::transports.IsConnected (ident) ||
i2p::transport::transports.IsTrustedRouter (ident)))
r->SetUnreachable (false);
// make router reachable back if connected now
if (it.second->IsUnreachable () && i2p::transport::transports.IsConnected (it.second->GetIdentHash ()))
it.second->SetUnreachable (false);
if (r->IsUnreachable ())
if (it.second->IsUnreachable ())
{
if (r->IsFloodfill ()) deletedFloodfillsCount++;
if (it.second->IsFloodfill ()) deletedFloodfillsCount++;
// delete RI file
removeFromDisk.emplace_back (ident.ToBase64());
removeFromDisk.push_back (ident);
deletedCount++;
if (total - deletedCount < NETDB_MIN_ROUTERS) checkForExpiration = false;
}
@ -782,8 +763,7 @@ namespace data
void NetDb::RequestDestination (const IdentHash& destination, RequestedDestination::RequestComplete requestComplete, bool direct)
{
if (direct && (i2p::transport::transports.RoutesRestricted () || i2p::context.IsLimitedConnectivity ()))
direct = false; // always use tunnels for restricted routes or limited connectivity
if (direct && i2p::transport::transports.RoutesRestricted ()) direct = false; // always use tunnels for restricted routes
if (m_Requests)
m_Requests->PostRequestDestination (destination, requestComplete, direct);
else
@ -951,13 +931,14 @@ namespace data
LogPrint (eLogError, "NetDb: DatabaseLookup for zero ident. Ignored");
return;
}
std::string key;
if (CheckLogLevel (eLogInfo))
key = i2p::data::ByteStreamToBase64 (buf, 32);
char key[48];
int l = i2p::data::ByteStreamToBase64 (buf, 32, key, 48);
key[l] = 0;
IdentHash replyIdent(buf + 32);
uint8_t flag = buf[64];
LogPrint (eLogDebug, "NetDb: DatabaseLookup for ", key, " received flags=", (int)flag);
uint8_t lookupType = flag & DATABASE_LOOKUP_TYPE_FLAGS_MASK;
const uint8_t * excluded = buf + 65;
@ -1152,18 +1133,15 @@ namespace data
}
std::shared_ptr<const RouterInfo> NetDb::GetRandomRouter (std::shared_ptr<const RouterInfo> compatibleWith,
bool reverse, bool endpoint, bool clientTunnel) const
bool reverse, bool endpoint) const
{
bool checkIsReal = clientTunnel && i2p::tunnel::tunnels.GetPreciseTunnelCreationSuccessRate () < NETDB_TUNNEL_CREATION_RATE_THRESHOLD && // too low rate
context.GetUptime () > NETDB_CHECK_FOR_EXPIRATION_UPTIME; // after 10 minutes uptime
return GetRandomRouter (
[compatibleWith, reverse, endpoint, clientTunnel, checkIsReal](std::shared_ptr<const RouterInfo> router)->bool
[compatibleWith, reverse, endpoint](std::shared_ptr<const RouterInfo> router)->bool
{
return !router->IsHidden () && router != compatibleWith &&
(reverse ? (compatibleWith->IsReachableFrom (*router) && router->GetCompatibleTransports (true)):
router->IsReachableFrom (*compatibleWith)) && !router->IsNAT2NATOnly (*compatibleWith) &&
router->IsECIES () && !router->IsHighCongestion (clientTunnel) &&
(!checkIsReal || router->GetProfile ()->IsReal ()) &&
router->IsECIES () && !router->IsHighCongestion (false) &&
(!endpoint || (router->IsV4 () && (!reverse || router->IsPublished (true)))); // endpoint must be ipv4 and published if inbound(reverse)
});
}
@ -1336,8 +1314,12 @@ namespace data
{
// update selection
m_ExploratorySelection.clear ();
#if (__cplusplus >= 201703L) // C++ 17 or higher
std::vector<std::shared_ptr<const RouterInfo> > eligible;
eligible.reserve (m_RouterInfos.size ());
#else
auto& eligible = m_ExploratorySelection;
#endif
{
// collect eligible from current netdb
bool checkIsReal = i2p::tunnel::tunnels.GetPreciseTunnelCreationSuccessRate () < NETDB_TUNNEL_CREATION_RATE_THRESHOLD; // too low rate
@ -1347,13 +1329,22 @@ namespace data
(!checkIsReal || (it.second->HasProfile () && it.second->GetProfile ()->IsReal ())))
eligible.push_back (it.second);
}
#if (__cplusplus >= 201703L) // C++ 17 or higher
if (eligible.size () > NETDB_MAX_EXPLORATORY_SELECTION_SIZE)
{
std::sample (eligible.begin(), eligible.end(), std::back_inserter(m_ExploratorySelection),
NETDB_MAX_EXPLORATORY_SELECTION_SIZE, m_Rng);
NETDB_MAX_EXPLORATORY_SELECTION_SIZE, std::mt19937(ts));
}
else
std::swap (m_ExploratorySelection, eligible);
#else
if (m_ExploratorySelection.size () > NETDB_MAX_EXPLORATORY_SELECTION_SIZE)
{
// reduce number of eligible to max selection size
std::shuffle (m_ExploratorySelection.begin(), m_ExploratorySelection.end(), std::mt19937(ts));
m_ExploratorySelection.resize (NETDB_MAX_EXPLORATORY_SELECTION_SIZE);
}
#endif
m_LastExploratorySelectionUpdateTime = ts;
}

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
*
@ -16,7 +16,6 @@
#include <thread>
#include <mutex>
#include <future>
#include <random>
#include "Base.h"
#include "Gzip.h"
@ -40,8 +39,7 @@ namespace data
{
const int NETDB_MIN_ROUTERS = 90;
const int NETDB_MIN_FLOODFILLS = 5;
const int NETDB_MIN_TRANSPORTS = 10 ; // otherwise assume offline
const int NETDB_NUM_FLOODFILLS_THRESHOLD = 1500;
const int NETDB_NUM_FLOODFILLS_THRESHOLD = 1200;
const int NETDB_NUM_ROUTERS_THRESHOLD = 4*NETDB_NUM_FLOODFILLS_THRESHOLD;
const int NETDB_TUNNEL_CREATION_RATE_THRESHOLD = 10; // in %
const int NETDB_CHECK_FOR_EXPIRATION_UPTIME = 600; // 10 minutes, in seconds
@ -53,7 +51,6 @@ namespace data
const int NETDB_MIN_HIGHBANDWIDTH_VERSION = MAKE_VERSION_NUMBER(0, 9, 58); // 0.9.58
const int NETDB_MIN_FLOODFILL_VERSION = MAKE_VERSION_NUMBER(0, 9, 59); // 0.9.59
const int NETDB_MIN_SHORT_TUNNEL_BUILD_VERSION = MAKE_VERSION_NUMBER(0, 9, 51); // 0.9.51
const int NETDB_MIN_PEER_TEST_VERSION = MAKE_VERSION_NUMBER(0, 9, 62); // 0.9.62
const size_t NETDB_MAX_NUM_SEARCH_REPLY_PEER_HASHES = 16;
const size_t NETDB_MAX_EXPLORATORY_SELECTION_SIZE = 500;
const int NETDB_EXPLORATORY_SELECTION_UPDATE_INTERVAL = 82; // in seconds. for floodfill
@ -90,7 +87,7 @@ namespace data
void RequestDestination (const IdentHash& destination, RequestedDestination::RequestComplete requestComplete = nullptr, bool direct = true);
std::shared_ptr<const RouterInfo> GetRandomRouter () const;
std::shared_ptr<const RouterInfo> GetRandomRouter (std::shared_ptr<const RouterInfo> compatibleWith, bool reverse, bool endpoint, bool clientTunnel) const;
std::shared_ptr<const RouterInfo> GetRandomRouter (std::shared_ptr<const RouterInfo> compatibleWith, bool reverse, bool endpoint) const;
std::shared_ptr<const RouterInfo> GetHighBandwidthRandomRouter (std::shared_ptr<const RouterInfo> compatibleWith, bool reverse, bool endpoint) const;
std::shared_ptr<const RouterInfo> GetRandomSSU2PeerTestRouter (bool v4, const std::unordered_set<IdentHash>& excluded) const;
std::shared_ptr<const RouterInfo> GetRandomSSU2Introducer (bool v4, const std::unordered_set<IdentHash>& excluded) const;
@ -130,12 +127,12 @@ namespace data
}
bool PopulateRouterInfoBuffer (std::shared_ptr<RouterInfo> r);
std::shared_ptr<RouterInfo::Address> NewRouterInfoAddress () { return m_RouterInfoAddressesPool.AcquireSharedMt (); };
RouterInfo::AddressesPtr NewRouterInfoAddresses ()
boost::shared_ptr<RouterInfo::Addresses> NewRouterInfoAddresses ()
{
return RouterInfo::AddressesPtr{m_RouterInfoAddressVectorsPool.AcquireMt (),
return boost::shared_ptr<RouterInfo::Addresses>(m_RouterInfoAddressVectorsPool.AcquireMt (),
std::bind <void (i2p::util::MemoryPoolMt<RouterInfo::Addresses>::*)(RouterInfo::Addresses *)>
(&i2p::util::MemoryPoolMt<RouterInfo::Addresses>::ReleaseMt,
&m_RouterInfoAddressVectorsPool, std::placeholders::_1)};
&m_RouterInfoAddressVectorsPool, std::placeholders::_1));
};
std::shared_ptr<Lease> NewLease (const Lease& lease) { return m_LeasesPool.AcquireSharedMt (lease); };
std::shared_ptr<IdentityEx> NewIdentity (const uint8_t * buf, size_t len) { return m_IdentitiesPool.AcquireSharedMt (buf, len); };
@ -187,11 +184,10 @@ namespace data
std::shared_ptr<NetDbRequests> m_Requests;
bool m_PersistProfiles;
std::future<void> m_SavingProfiles, m_DeletingProfiles, m_ApplyingProfileUpdates, m_PersistingRouters;
std::future<void> m_SavingProfiles, m_DeletingProfiles, m_PersistingRouters;
std::vector<std::shared_ptr<const RouterInfo> > m_ExploratorySelection;
uint64_t m_LastExploratorySelectionUpdateTime; // in monotonic seconds
std::mt19937 m_Rng;
i2p::util::MemoryPoolMt<RouterInfo::Buffer> m_RouterInfoBuffersPool;
i2p::util::MemoryPoolMt<RouterInfo::Address> m_RouterInfoAddressesPool;

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
*
@ -20,10 +20,8 @@ namespace i2p
namespace data
{
RequestedDestination::RequestedDestination (const IdentHash& destination, bool isExploratory, bool direct):
m_Destination (destination), m_IsExploratory (isExploratory), m_IsDirect (direct),
m_IsActive (true), m_IsSentDirectly (false),
m_CreationTime (i2p::util::GetMillisecondsSinceEpoch ()),
m_LastRequestTime (0), m_NumAttempts (0)
m_Destination (destination), m_IsExploratory (isExploratory), m_IsDirect (direct), m_IsActive (true),
m_CreationTime (i2p::util::GetSecondsSinceEpoch ()), m_LastRequestTime (0), m_NumAttempts (0)
{
if (i2p::context.IsFloodfill ())
m_ExcludedPeers.insert (i2p::context.GetIdentHash ()); // exclude self if floodfill
@ -46,9 +44,8 @@ namespace data
msg = i2p::CreateRouterInfoDatabaseLookupMsg(m_Destination, i2p::context.GetIdentHash(), 0, m_IsExploratory, &m_ExcludedPeers);
if(router)
m_ExcludedPeers.insert (router->GetIdentHash ());
m_LastRequestTime = i2p::util::GetMillisecondsSinceEpoch ();
m_LastRequestTime = i2p::util::GetSecondsSinceEpoch ();
m_NumAttempts++;
m_IsSentDirectly = false;
return msg;
}
@ -58,8 +55,7 @@ namespace data
i2p::context.GetRouterInfo ().GetIdentHash () , 0, false, &m_ExcludedPeers);
m_ExcludedPeers.insert (floodfill);
m_NumAttempts++;
m_LastRequestTime = i2p::util::GetMillisecondsSinceEpoch ();
m_IsSentDirectly = true;
m_LastRequestTime = i2p::util::GetSecondsSinceEpoch ();
return msg;
}
@ -183,7 +179,7 @@ namespace data
void NetDbRequests::RequestComplete (const IdentHash& ident, std::shared_ptr<RouterInfo> r)
{
boost::asio::post (GetIOService (), [this, ident, r]()
GetIOService ().post ([this, ident, r]()
{
std::shared_ptr<RequestedDestination> request;
auto it = m_RequestedDestinations.find (ident);
@ -214,7 +210,7 @@ namespace data
void NetDbRequests::ManageRequests ()
{
uint64_t ts = i2p::util::GetMillisecondsSinceEpoch ();
uint64_t ts = i2p::util::GetSecondsSinceEpoch ();
for (auto it = m_RequestedDestinations.begin (); it != m_RequestedDestinations.end ();)
{
auto& dest = it->second;
@ -226,8 +222,7 @@ namespace data
bool done = false;
if (ts < dest->GetCreationTime () + MAX_REQUEST_TIME)
{
if (ts > dest->GetLastRequestTime () + (dest->IsSentDirectly () ? MIN_DIRECT_REQUEST_TIME : MIN_REQUEST_TIME))
// try next floodfill if no response after min interval
if (ts > dest->GetLastRequestTime () + MIN_REQUEST_TIME) // try next floodfill if no response after min interval
done = !SendNextRequest (dest);
}
else // request is expired
@ -272,7 +267,7 @@ namespace data
{
if (dest->IsActive ())
{
boost::asio::post (s->GetIOService (), [s, dest]()
s->GetIOService ().post ([s, dest]()
{
if (dest->IsActive ()) s->SendNextRequest (dest);
});
@ -333,8 +328,7 @@ namespace data
void NetDbRequests::ScheduleManageRequests ()
{
m_ManageRequestsTimer.expires_from_now (boost::posix_time::milliseconds(MANAGE_REQUESTS_INTERVAL +
m_Rng () % MANAGE_REQUESTS_INTERVAL_VARIANCE));
m_ManageRequestsTimer.expires_from_now (boost::posix_time::seconds(MANAGE_REQUESTS_INTERVAL));
m_ManageRequestsTimer.async_wait (std::bind (&NetDbRequests::HandleManageRequestsTimer,
this, std::placeholders::_1));
}
@ -351,7 +345,7 @@ namespace data
void NetDbRequests::PostDatabaseSearchReplyMsg (std::shared_ptr<const I2NPMessage> msg)
{
boost::asio::post (GetIOService (), [this, msg]()
GetIOService ().post ([this, msg]()
{
HandleDatabaseSearchReplyMsg (msg);
});
@ -360,12 +354,11 @@ namespace data
void NetDbRequests::HandleDatabaseSearchReplyMsg (std::shared_ptr<const I2NPMessage> msg)
{
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
if (CheckLogLevel (eLogInfo))
key = i2p::data::ByteStreamToBase64 (buf, 32);
LogPrint (eLogDebug, "NetDbReq: DatabaseSearchReply for ", key, " num=", num);
IdentHash ident (buf);
bool isExploratory = false;
auto dest = FindRequest (ident);
@ -438,7 +431,7 @@ namespace data
void NetDbRequests::PostRequestDestination (const IdentHash& destination,
const RequestedDestination::RequestComplete& requestComplete, bool direct)
{
boost::asio::post (GetIOService (), [this, destination, requestComplete, direct]()
GetIOService ().post ([this, destination, requestComplete, direct]()
{
RequestDestination (destination, requestComplete, direct);
});

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
*
@ -24,17 +24,15 @@ namespace i2p
namespace data
{
const int MAX_NUM_REQUEST_ATTEMPTS = 5;
const uint64_t MANAGE_REQUESTS_INTERVAL = 400; // in milliseconds
const uint64_t MANAGE_REQUESTS_INTERVAL_VARIANCE = 300; // in milliseconds
const uint64_t MIN_REQUEST_TIME = 1200; // in milliseconds
const uint64_t MAX_REQUEST_TIME = MAX_NUM_REQUEST_ATTEMPTS * (MIN_REQUEST_TIME + MANAGE_REQUESTS_INTERVAL + MANAGE_REQUESTS_INTERVAL_VARIANCE);
const uint64_t MIN_DIRECT_REQUEST_TIME = 600; // in milliseconds
const uint64_t MANAGE_REQUESTS_INTERVAL = 1; // in seconds
const uint64_t MIN_REQUEST_TIME = 5; // in seconds
const uint64_t MAX_REQUEST_TIME = MAX_NUM_REQUEST_ATTEMPTS * (MIN_REQUEST_TIME + MANAGE_REQUESTS_INTERVAL);
const uint64_t EXPLORATORY_REQUEST_INTERVAL = 55; // in seconds
const uint64_t EXPLORATORY_REQUEST_INTERVAL_VARIANCE = 170; // in seconds
const uint64_t DISCOVERED_REQUEST_INTERVAL = 360; // in milliseconds
const uint64_t DISCOVERED_REQUEST_INTERVAL_VARIANCE = 540; // in milliseconds
const uint64_t MAX_EXPLORATORY_REQUEST_TIME = 30000; // in milliseconds
const uint64_t REQUEST_CACHE_TIME = MAX_REQUEST_TIME + 40000; // in milliseconds
const uint64_t MAX_EXPLORATORY_REQUEST_TIME = 30; // in seconds
const uint64_t REQUEST_CACHE_TIME = MAX_REQUEST_TIME + 40; // in seconds
const uint64_t REQUESTED_DESTINATIONS_POOL_CLEANUP_INTERVAL = 191; // in seconds
class RequestedDestination
@ -53,7 +51,6 @@ namespace data
bool IsExploratory () const { return m_IsExploratory; };
bool IsDirect () const { return m_IsDirect; };
bool IsActive () const { return m_IsActive; };
bool IsSentDirectly () const { return m_IsSentDirectly; };
bool IsExcluded (const IdentHash& ident) const;
uint64_t GetCreationTime () const { return m_CreationTime; };
uint64_t GetLastRequestTime () const { return m_LastRequestTime; };
@ -72,9 +69,9 @@ namespace data
private:
IdentHash m_Destination;
bool m_IsExploratory, m_IsDirect, m_IsActive, m_IsSentDirectly;
bool m_IsExploratory, m_IsDirect, m_IsActive;
std::unordered_set<IdentHash> m_ExcludedPeers;
uint64_t m_CreationTime, m_LastRequestTime; // in milliseconds
uint64_t m_CreationTime, m_LastRequestTime; // in seconds
std::list<RequestComplete> m_RequestComplete;
int m_NumAttempts;
};
@ -118,9 +115,9 @@ namespace data
private:
i2p::util::MemoryPoolMt<RequestedDestination> m_RequestedDestinationsPool;
std::unordered_map<IdentHash, std::shared_ptr<RequestedDestination> > m_RequestedDestinations;
std::list<IdentHash> m_DiscoveredRouterHashes;
i2p::util::MemoryPoolMt<RequestedDestination> m_RequestedDestinationsPool;
boost::asio::deadline_timer m_ManageRequestsTimer, m_ExploratoryTimer,
m_CleanupTimer, m_DiscoveredRoutersTimer;
std::mt19937 m_Rng;

Some files were not shown because too many files have changed in this diff Show more