First working alpha, version a0.4 #7
@@ -69,5 +69,6 @@ namespace ColumnLynx::Net::TCP {
|
|||||||
std::chrono::steady_clock::time_point mLastHeartbeatReceived;
|
std::chrono::steady_clock::time_point mLastHeartbeatReceived;
|
||||||
std::chrono::steady_clock::time_point mLastHeartbeatSent;
|
std::chrono::steady_clock::time_point mLastHeartbeatSent;
|
||||||
int mMissedHeartbeats = 0;
|
int mMissedHeartbeats = 0;
|
||||||
|
bool mIsHostDomain;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
#include <openssl/x509.h>
|
#include <openssl/x509.h>
|
||||||
#include <openssl/x509_vfy.h>
|
#include <openssl/x509_vfy.h>
|
||||||
#include <openssl/pem.h>
|
#include <openssl/pem.h>
|
||||||
|
#include <openssl/x509v3.h>
|
||||||
|
|
||||||
namespace ColumnLynx {
|
namespace ColumnLynx {
|
||||||
using PublicKey = std::array<uint8_t, crypto_sign_PUBLICKEYBYTES>; // Ed25519
|
using PublicKey = std::array<uint8_t, crypto_sign_PUBLICKEYBYTES>; // Ed25519
|
||||||
@@ -222,6 +223,57 @@ namespace ColumnLynx::Utils {
|
|||||||
return result == 1;
|
return result == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline std::vector<std::string> getCertificateHostname(const std::vector<uint8_t>& cert_der) {
|
||||||
|
std::vector<std::string> names;
|
||||||
|
|
||||||
|
if (cert_der.empty())
|
||||||
|
return names;
|
||||||
|
|
||||||
|
// Parse DER certificate
|
||||||
|
const unsigned char* p = cert_der.data();
|
||||||
|
X509* cert = d2i_X509(nullptr, &p, cert_der.size());
|
||||||
|
if (!cert)
|
||||||
|
return names;
|
||||||
|
|
||||||
|
// --- Subject Alternative Names (SAN) ---
|
||||||
|
GENERAL_NAMES* san_names =
|
||||||
|
(GENERAL_NAMES*)X509_get_ext_d2i(cert, NID_subject_alt_name, nullptr, nullptr);
|
||||||
|
|
||||||
|
if (san_names) {
|
||||||
|
int san_count = sk_GENERAL_NAME_num(san_names);
|
||||||
|
for (int i = 0; i < san_count; i++) {
|
||||||
|
const GENERAL_NAME* current = sk_GENERAL_NAME_value(san_names, i);
|
||||||
|
if (current->type == GEN_DNS) {
|
||||||
|
const char* dns_name = (const char*)ASN1_STRING_get0_data(current->d.dNSName);
|
||||||
|
// Safety: ensure no embedded nulls
|
||||||
|
if (ASN1_STRING_length(current->d.dNSName) == (int)std::strlen(dns_name)) {
|
||||||
|
names.emplace_back(dns_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
GENERAL_NAMES_free(san_names);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Fallback: Common Name (CN) ---
|
||||||
|
if (names.empty()) {
|
||||||
|
X509_NAME* subject = X509_get_subject_name(cert);
|
||||||
|
if (subject) {
|
||||||
|
int idx = X509_NAME_get_index_by_NID(subject, NID_commonName, -1);
|
||||||
|
if (idx >= 0) {
|
||||||
|
X509_NAME_ENTRY* entry = X509_NAME_get_entry(subject, idx);
|
||||||
|
ASN1_STRING* cn_asn1 = X509_NAME_ENTRY_get_data(entry);
|
||||||
|
const char* cn_str = (const char*)ASN1_STRING_get0_data(cn_asn1);
|
||||||
|
if (ASN1_STRING_length(cn_asn1) == (int)std::strlen(cn_str)) {
|
||||||
|
names.emplace_back(cn_str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
X509_free(cert);
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::array<uint8_t, crypto_sign_PUBLICKEYBYTES> mPublicKey;
|
std::array<uint8_t, crypto_sign_PUBLICKEYBYTES> mPublicKey;
|
||||||
std::array<uint8_t, crypto_sign_SECRETKEYBYTES> mPrivateKey;
|
std::array<uint8_t, crypto_sign_SECRETKEYBYTES> mPrivateKey;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
// Distributed under the terms of the GNU General Public License, either version 2 only or version 3. See LICENSES/ for details.
|
// Distributed under the terms of the GNU General Public License, either version 2 only or version 3. See LICENSES/ for details.
|
||||||
|
|
||||||
#include <columnlynx/client/net/tcp/tcp_client.hpp>
|
#include <columnlynx/client/net/tcp/tcp_client.hpp>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
|
||||||
namespace ColumnLynx::Net::TCP {
|
namespace ColumnLynx::Net::TCP {
|
||||||
void TCPClient::start() {
|
void TCPClient::start() {
|
||||||
@@ -24,6 +25,11 @@ namespace ColumnLynx::Net::TCP {
|
|||||||
// Init connection handshake
|
// Init connection handshake
|
||||||
Utils::log("Sending handshake init to server.");
|
Utils::log("Sending handshake init to server.");
|
||||||
|
|
||||||
|
// Check if hostname or IPv4/IPv6
|
||||||
|
sockaddr_in addr4{};
|
||||||
|
sockaddr_in6 addr6{};
|
||||||
|
self->mIsHostDomain = inet_pton(AF_INET, mHost.c_str(), (void*)(&addr4)) != 1 && inet_pton(AF_INET6, mHost.c_str(), (void*)(&addr6)) != 1;
|
||||||
|
|
||||||
std::vector<uint8_t> payload;
|
std::vector<uint8_t> payload;
|
||||||
payload.reserve(1 + crypto_box_PUBLICKEYBYTES);
|
payload.reserve(1 + crypto_box_PUBLICKEYBYTES);
|
||||||
payload.push_back(Utils::protocolVersion());
|
payload.push_back(Utils::protocolVersion());
|
||||||
@@ -136,7 +142,6 @@ namespace ColumnLynx::Net::TCP {
|
|||||||
std::vector<uint8_t> serverPublicKeyVec(std::begin(mServerPublicKey), std::end(mServerPublicKey));
|
std::vector<uint8_t> serverPublicKeyVec(std::begin(mServerPublicKey), std::end(mServerPublicKey));
|
||||||
|
|
||||||
// Verify server public key
|
// Verify server public key
|
||||||
// TODO: Verify / Match hostname of public key to hostname of server
|
|
||||||
if (!Utils::LibSodiumWrapper::verifyCertificateWithSystemCAs(serverPublicKeyVec)) {
|
if (!Utils::LibSodiumWrapper::verifyCertificateWithSystemCAs(serverPublicKeyVec)) {
|
||||||
if (!(*mInsecureMode)) {
|
if (!(*mInsecureMode)) {
|
||||||
Utils::error("Server public key verification failed. Terminating connection.");
|
Utils::error("Server public key verification failed. Terminating connection.");
|
||||||
@@ -144,7 +149,29 @@ namespace ColumnLynx::Net::TCP {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Utils::log("Warning: Server public key verification failed, but continuing due to insecure mode.");
|
Utils::warn("Warning: Server public key verification failed, but continuing due to insecure mode.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract and verify hostname from certificate if not IP
|
||||||
|
if (mIsHostDomain) {
|
||||||
|
std::vector<std::string> certHostnames = Utils::LibSodiumWrapper::getCertificateHostname(serverPublicKeyVec);
|
||||||
|
|
||||||
|
// Temp: print extracted hostnames if any
|
||||||
|
for (const auto& hostname : certHostnames) {
|
||||||
|
Utils::log("Extracted hostname from certificate: " + hostname);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (certHostnames.empty() || std::find(certHostnames.begin(), certHostnames.end(), mHost) == certHostnames.end()) {
|
||||||
|
if (!(*mInsecureMode)) {
|
||||||
|
Utils::error("Server hostname verification failed. Terminating connection.");
|
||||||
|
disconnect();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Utils::warn("Warning: Server hostname verification failed, but continuing due to insecure mode.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Utils::warn("Connecting via IP address, I can't verify the server's identity! You might be getting MITM'd!");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate and send challenge
|
// Generate and send challenge
|
||||||
|
|||||||
Reference in New Issue
Block a user