Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
49ac7b9dfc | ||
|
|
dfe1edf706 | ||
|
|
f56a5102a9 | ||
|
|
01e5afbb60
|
||
|
|
d046cb3412
|
||
|
|
11d207d58f
|
||
|
|
09ae813c76
|
@@ -11,6 +11,7 @@ compile_commands.json
|
|||||||
CTestTestfile.cmake
|
CTestTestfile.cmake
|
||||||
_deps
|
_deps
|
||||||
CMakeUserPresets.json
|
CMakeUserPresets.json
|
||||||
|
build/
|
||||||
|
|
||||||
# ---> Emacs
|
# ---> Emacs
|
||||||
# -*- mode: gitignore; -*-
|
# -*- mode: gitignore; -*-
|
||||||
@@ -97,3 +98,4 @@ flycheck_*.el
|
|||||||
*.out
|
*.out
|
||||||
*.app
|
*.app
|
||||||
|
|
||||||
|
.cache
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
cmake_minimum_required(VERSION 4.3.4)
|
||||||
|
project(Sabalty)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 23)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||||
|
include(FetchContent)
|
||||||
|
|
||||||
|
option(BUILD_TESTING "Build tests" OFF)
|
||||||
|
if(BUILD_TESTING)
|
||||||
|
enable_testing()
|
||||||
|
|
||||||
|
FetchContent_Declare(
|
||||||
|
Catch2
|
||||||
|
GIT_REPOSITORY https://github.com/catchorg/Catch2.git
|
||||||
|
GIT_TAG v3.15.3
|
||||||
|
)
|
||||||
|
FetchContent_MakeAvailable(Catch2)
|
||||||
|
|
||||||
|
include(CTest)
|
||||||
|
include(Catch)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
FetchContent_Declare(Sodium
|
||||||
|
GIT_REPOSITORY https://github.com/robinlinden/libsodium-cmake.git
|
||||||
|
GIT_TAG cfebfd3da486d5a86c644c8b47067e5411c7599c # HEAD, last updated at 2026-02-28
|
||||||
|
)
|
||||||
|
set(SODIUM_DISABLE_TESTS ON)
|
||||||
|
FetchContent_MakeAvailable(Sodium)
|
||||||
|
|
||||||
|
add_subdirectory(Server)
|
||||||
|
add_subdirectory(Protocol)
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
add_subdirectory(src)
|
||||||
|
|
||||||
|
if(BUILD_TESTING)
|
||||||
|
add_subdirectory(tests)
|
||||||
|
endif()
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
add_library(Protocol STATIC)
|
||||||
|
|
||||||
|
target_sources(Protocol
|
||||||
|
PRIVATE
|
||||||
|
protocol.cxx
|
||||||
|
PUBLIC
|
||||||
|
FILE_SET HEADERS
|
||||||
|
BASE_DIRS include
|
||||||
|
FILES include/protocol/protocol.hxx
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(Protocol
|
||||||
|
PRIVATE
|
||||||
|
sodium
|
||||||
|
)
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "sodium/crypto_hash_sha256.h"
|
||||||
|
#include "sodium/crypto_sign.h"
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
namespace Protocol {
|
||||||
|
|
||||||
|
struct SignedMessage {
|
||||||
|
unsigned char sender_key_fingerprint[crypto_hash_sha256_BYTES];
|
||||||
|
std::string plaintext;
|
||||||
|
int64_t timestamp;
|
||||||
|
unsigned char signature[crypto_sign_BYTES]; // signs fields 1-3
|
||||||
|
|
||||||
|
// Fills the fields and signs the plaintext
|
||||||
|
SignedMessage(unsigned char sender_pub_key[crypto_sign_PUBLICKEYBYTES],
|
||||||
|
unsigned char sender_priv_key[crypto_sign_SECRETKEYBYTES],
|
||||||
|
std::string plaintext);
|
||||||
|
|
||||||
|
std::vector<uint8_t> serialize();
|
||||||
|
bool valid(unsigned char pub_key[crypto_sign_PUBLICKEYBYTES]);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct StunReplay {
|
||||||
|
uint32_t addr; // ipv4 only support!
|
||||||
|
uint16_t port;
|
||||||
|
bool is_behind_symmetric_nat = false;
|
||||||
|
|
||||||
|
StunReplay(std::string ip, uint16_t port);
|
||||||
|
StunReplay(uint32_t ip, uint16_t port);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Protocol
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
#include <chrono>
|
||||||
|
#include <cstring>
|
||||||
|
#include <protocol/protocol.hxx>
|
||||||
|
#include <ranges>
|
||||||
|
#include <sodium.h>
|
||||||
|
|
||||||
|
namespace Protocol {
|
||||||
|
|
||||||
|
SignedMessage::SignedMessage(
|
||||||
|
unsigned char sender_pub_key[crypto_sign_PUBLICKEYBYTES],
|
||||||
|
unsigned char sender_priv_key[crypto_sign_SECRETKEYBYTES],
|
||||||
|
std::string plaintext)
|
||||||
|
: plaintext(plaintext) {
|
||||||
|
if (sodium_init() < 0) {
|
||||||
|
throw std::runtime_error("Failed to init sodium!");
|
||||||
|
}
|
||||||
|
|
||||||
|
timestamp = std::chrono::system_clock::now().time_since_epoch().count();
|
||||||
|
crypto_hash_sha256(sender_key_fingerprint, sender_pub_key,
|
||||||
|
crypto_sign_PUBLICKEYBYTES);
|
||||||
|
|
||||||
|
auto to_be_signed = serialize();
|
||||||
|
|
||||||
|
crypto_sign_detached(signature, nullptr, to_be_signed.data(),
|
||||||
|
to_be_signed.size(), sender_priv_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> SignedMessage::serialize() {
|
||||||
|
std::vector<uint8_t> bytes;
|
||||||
|
bytes.reserve(sizeof(sender_key_fingerprint) + plaintext.size() +
|
||||||
|
sizeof(timestamp));
|
||||||
|
bytes.insert(bytes.cend(), sender_key_fingerprint,
|
||||||
|
sender_key_fingerprint + sizeof(sender_key_fingerprint));
|
||||||
|
bytes.insert(bytes.cend(), plaintext.data(),
|
||||||
|
plaintext.data() + plaintext.length());
|
||||||
|
auto timestamp_bytes = reinterpret_cast<const uint8_t *>(×tamp);
|
||||||
|
bytes.insert(bytes.cend(), timestamp_bytes,
|
||||||
|
timestamp_bytes + sizeof(timestamp));
|
||||||
|
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SignedMessage::valid(unsigned char pub_key[crypto_sign_PUBLICKEYBYTES]) {
|
||||||
|
unsigned char pub_key_fingerprint[crypto_hash_sha256_BYTES];
|
||||||
|
crypto_hash_sha256(pub_key_fingerprint, pub_key,
|
||||||
|
crypto_sign_PUBLICKEYBYTES);
|
||||||
|
|
||||||
|
if (std::memcmp(pub_key_fingerprint, sender_key_fingerprint,
|
||||||
|
crypto_sign_PUBLICKEYBYTES) != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto serialized_data = serialize();
|
||||||
|
if (crypto_sign_verify_detached(signature, serialized_data.data(),
|
||||||
|
serialized_data.size(), pub_key) != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
StunReplay::StunReplay(std::string ip, uint16_t port) : port(port) {
|
||||||
|
auto parts =
|
||||||
|
ip | std::views::split('.') | std::views::transform([](auto &&range) {
|
||||||
|
return std::string_view(range.cbegin(), range.cend());
|
||||||
|
});
|
||||||
|
if (std::ranges::distance(parts) != 4) {
|
||||||
|
goto invalid_addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto part : parts) {
|
||||||
|
auto val = std::atoi(part.data());
|
||||||
|
if (val > 255 || val < 1) {
|
||||||
|
goto invalid_addr;
|
||||||
|
}
|
||||||
|
addr = (addr << 8) | static_cast<uint32_t>(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
goto end;
|
||||||
|
invalid_addr:
|
||||||
|
throw std::runtime_error("Invalid ipv4 string");
|
||||||
|
end:
|
||||||
|
}
|
||||||
|
|
||||||
|
StunReplay::StunReplay(uint32_t ip, uint16_t port) : addr(ip), port(port) {}
|
||||||
|
|
||||||
|
} // namespace Protocol
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
add_executable(Protocol_tests
|
||||||
|
test_protocol.cxx
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(Protocol_tests
|
||||||
|
PRIVATE
|
||||||
|
Protocol
|
||||||
|
Catch2::Catch2WithMain
|
||||||
|
sodium
|
||||||
|
)
|
||||||
|
|
||||||
|
catch_discover_tests(Protocol_tests)
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
#include "sodium/crypto_sign.h"
|
||||||
|
#include <catch2/catch_test_macros.hpp>
|
||||||
|
#include <protocol/protocol.hxx>
|
||||||
|
#include <sodium.h>
|
||||||
|
|
||||||
|
TEST_CASE("Protocol::StunReplay", "[protocol]") {
|
||||||
|
SECTION("str to ipv4") {
|
||||||
|
auto replay = Protocol::StunReplay("1.1.255.255", 1337);
|
||||||
|
REQUIRE(replay.addr == 16908287);
|
||||||
|
}
|
||||||
|
SECTION("invalid ipv4 str (range)") {
|
||||||
|
REQUIRE_THROWS_AS(Protocol::StunReplay("1.1.255.256", 1337),
|
||||||
|
std::runtime_error);
|
||||||
|
REQUIRE_THROWS_AS(Protocol::StunReplay("0.1.255.255", 1337),
|
||||||
|
std::runtime_error);
|
||||||
|
}
|
||||||
|
SECTION("invalid ipv4 str (len)") {
|
||||||
|
REQUIRE_THROWS_AS(Protocol::StunReplay("1.1.1", 1337),
|
||||||
|
std::runtime_error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Protocol::SignedMessage", "[protocol]") {
|
||||||
|
REQUIRE(sodium_init() >= 0);
|
||||||
|
|
||||||
|
unsigned char pub_key[crypto_sign_PUBLICKEYBYTES];
|
||||||
|
unsigned char priv_key[crypto_sign_SECRETKEYBYTES];
|
||||||
|
crypto_sign_keypair(pub_key, priv_key);
|
||||||
|
|
||||||
|
auto msg = Protocol::SignedMessage(pub_key, priv_key, "Hello, world!");
|
||||||
|
|
||||||
|
SECTION("valid signed message") { REQUIRE(msg.valid(pub_key)); }
|
||||||
|
|
||||||
|
SECTION("tampered invalid signed message") {
|
||||||
|
SECTION("plaintext") {
|
||||||
|
msg.plaintext = "Hello, world111111";
|
||||||
|
REQUIRE_FALSE(msg.valid(pub_key));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("timestamp") {
|
||||||
|
msg.timestamp = std::chrono::system_clock::now()
|
||||||
|
.time_since_epoch()
|
||||||
|
.count();
|
||||||
|
REQUIRE_FALSE(msg.valid(pub_key));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("fingerprint") {
|
||||||
|
std::memset(msg.sender_key_fingerprint, 'H',
|
||||||
|
crypto_hash_sha256_BYTES);
|
||||||
|
REQUIRE_FALSE(msg.valid(pub_key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
add_subdirectory(Stun)
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
add_subdirectory(src)
|
||||||
|
|
||||||
|
if(BUILD_TESTING)
|
||||||
|
add_subdirectory(tests)
|
||||||
|
endif()
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
add_library(Stun STATIC)
|
||||||
|
|
||||||
|
target_sources(Stun
|
||||||
|
PRIVATE
|
||||||
|
stun.cxx
|
||||||
|
PUBLIC
|
||||||
|
FILE_SET HEADERS
|
||||||
|
BASE_DIRS include
|
||||||
|
FILES include/stun/stun.hxx
|
||||||
|
)
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace stun {
|
||||||
|
|
||||||
|
int add_numbers(int, int);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
#include <stun/stun.hxx>
|
||||||
|
|
||||||
|
int stun::add_numbers(int x, int y) { return (x + y); }
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
add_executable(Stun_tests
|
||||||
|
test_stun.cxx
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(Stun_tests
|
||||||
|
PRIVATE
|
||||||
|
Stun
|
||||||
|
Catch2::Catch2WithMain
|
||||||
|
)
|
||||||
|
|
||||||
|
catch_discover_tests(Stun_tests)
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
#include <catch2/catch_test_macros.hpp>
|
||||||
|
#include <stun/stun.hxx>
|
||||||
|
|
||||||
|
TEST_CASE("add_numbers works", "[stun]") {
|
||||||
|
SECTION("adding 1 to 2") { REQUIRE(stun::add_numbers(1, 2) == 3); }
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user