feat: add constant-time credential comparison, rate limiting, SSL hostname verification, and update coverage threshold to 69%

This commit is contained in:
2025-12-28 04:16:15 +00:00
parent 155df9ad3e
commit ca8bdcf2cd
49 changed files with 114 additions and 32 deletions
Regular → Executable
+53 -14
View File
@@ -4,11 +4,16 @@
#include <string.h>
#include <openssl/evp.h>
#include <openssl/rand.h>
#include <openssl/crypto.h>
static char g_dashboard_username[128] = "";
static char g_dashboard_password_hash[256] = "";
static int g_auth_enabled = 0;
static int constant_time_compare(const char *a, const char *b, size_t len) {
return CRYPTO_memcmp(a, b, len) == 0;
}
static void compute_sha256(const char *input, char *output, size_t output_size) {
EVP_MD_CTX *ctx = EVP_MD_CTX_new();
if (!ctx) return;
@@ -48,12 +53,19 @@ int auth_check_credentials(const char *username, const char *password) {
if (!g_auth_enabled) return 1;
if (!username || !password) return 0;
if (strcmp(username, g_dashboard_username) != 0) return 0;
char password_hash[256];
compute_sha256(password, password_hash, sizeof(password_hash));
return strcmp(password_hash, g_dashboard_password_hash) == 0;
size_t username_len = strlen(username);
size_t expected_username_len = strlen(g_dashboard_username);
int username_match = (username_len == expected_username_len) &&
constant_time_compare(username, g_dashboard_username, username_len);
int hash_match = constant_time_compare(password_hash, g_dashboard_password_hash, 64);
memset(password_hash, 0, sizeof(password_hash));
return username_match && hash_match;
}
static int base64_decode_char(char c) {
@@ -108,8 +120,18 @@ int auth_check_basic_auth(const char *auth_header, char *error_msg, size_t error
return 0;
}
const char *encoded = auth_header + 6;
size_t encoded_len = strlen(encoded);
if (encoded_len > 680) {
if (error_msg && error_size > 0) {
strncpy(error_msg, "Credentials too long", error_size - 1);
}
return 0;
}
char decoded[512];
if (base64_decode(auth_header + 6, decoded, sizeof(decoded)) < 0) {
int decoded_len = base64_decode(encoded, decoded, sizeof(decoded));
if (decoded_len < 0) {
if (error_msg && error_size > 0) {
strncpy(error_msg, "Invalid credentials format", error_size - 1);
}
@@ -128,7 +150,10 @@ int auth_check_basic_auth(const char *auth_header, char *error_msg, size_t error
const char *username = decoded;
const char *password = colon + 1;
if (!auth_check_credentials(username, password)) {
int result = auth_check_credentials(username, password);
memset(decoded, 0, sizeof(decoded));
if (!result) {
if (error_msg && error_size > 0) {
strncpy(error_msg, "Invalid username or password", error_size - 1);
}
@@ -155,8 +180,18 @@ int auth_check_route_basic_auth(const route_config_t *route, const char *auth_he
return 0;
}
const char *encoded = auth_header + 6;
size_t encoded_len = strlen(encoded);
if (encoded_len > 680) {
if (error_msg && error_size > 0) {
strncpy(error_msg, "Credentials too long", error_size - 1);
}
return 0;
}
char decoded[512];
if (base64_decode(auth_header + 6, decoded, sizeof(decoded)) < 0) {
int decoded_len = base64_decode(encoded, decoded, sizeof(decoded));
if (decoded_len < 0) {
if (error_msg && error_size > 0) {
strncpy(error_msg, "Invalid credentials format", error_size - 1);
}
@@ -165,6 +200,7 @@ int auth_check_route_basic_auth(const route_config_t *route, const char *auth_he
char *colon = strchr(decoded, ':');
if (!colon) {
memset(decoded, 0, sizeof(decoded));
if (error_msg && error_size > 0) {
strncpy(error_msg, "Invalid credentials format", error_size - 1);
}
@@ -175,17 +211,20 @@ int auth_check_route_basic_auth(const route_config_t *route, const char *auth_he
const char *username = decoded;
const char *password = colon + 1;
if (strcmp(username, route->username) != 0) {
if (error_msg && error_size > 0) {
strncpy(error_msg, "Invalid username or password", error_size - 1);
}
return 0;
}
char password_hash[256];
compute_sha256(password, password_hash, sizeof(password_hash));
if (strcmp(password_hash, route->password_hash) != 0) {
size_t username_len = strlen(username);
size_t expected_username_len = strlen(route->username);
int username_match = (username_len == expected_username_len) &&
constant_time_compare(username, route->username, username_len);
int hash_match = constant_time_compare(password_hash, route->password_hash, 64);
memset(decoded, 0, sizeof(decoded));
memset(password_hash, 0, sizeof(password_hash));
if (!username_match || !hash_match) {
if (error_msg && error_size > 0) {
strncpy(error_msg, "Invalid username or password", error_size - 1);
}
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
+11 -2
View File
@@ -8,6 +8,7 @@
#include "dashboard.h"
#include "auth.h"
#include "patch.h"
#include "rate_limit.h"
#include <stdio.h>
#include <stdlib.h>
@@ -204,6 +205,8 @@ void connection_accept(int listener_fd) {
conn->state = CLIENT_STATE_READING_HEADERS;
conn->fd = client_fd;
conn->last_activity = cached_time;
strncpy(conn->client_ip, inet_ntoa(client_addr.sin_addr), sizeof(conn->client_ip) - 1);
conn->client_ip[sizeof(conn->client_ip) - 1] = '\0';
if (buffer_init(&conn->read_buf, CHUNK_SIZE) < 0 ||
buffer_init(&conn->write_buf, CHUNK_SIZE) < 0) {
@@ -213,7 +216,7 @@ void connection_accept(int listener_fd) {
monitor.active_connections++;
log_debug("New connection on fd %d from %s, total: %d",
client_fd, inet_ntoa(client_addr.sin_addr), monitor.active_connections);
client_fd, conn->client_ip, monitor.active_connections);
}
}
@@ -656,7 +659,7 @@ void connection_connect_to_upstream(connection_t *client, const char *data, size
}
const char *sni_hostname = route->rewrite_host ? route->upstream_host : client->request.host;
SSL_set_tlsext_host_name(up->ssl, sni_hostname);
ssl_set_hostname(up->ssl, sni_hostname);
SSL_set_fd(up->ssl, up_fd);
SSL_set_connect_state(up->ssl);
@@ -739,6 +742,12 @@ static void handle_client_read(connection_t *conn) {
return;
}
if (!rate_limit_check(conn->client_ip)) {
log_info("[RATE-LIMIT] Request blocked for %s from %s", conn->request.host, conn->client_ip);
connection_send_error_response(conn, 429, "Too Many Requests", "Rate limit exceeded. Please try again later.");
return;
}
long long body_len = (conn->request.content_length > 0) ? conn->request.content_length : 0;
size_t total_request_len = headers_len + body_len;
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
+2 -1
View File
@@ -74,7 +74,8 @@ int rate_limit_check(const char *client_ip) {
rate_limit_entry_t *new_entry = calloc(1, sizeof(rate_limit_entry_t));
if (!new_entry) {
return 1;
log_error("Rate limit entry allocation failed, denying request for safety");
return 0;
}
strncpy(new_entry->client_ip, client_ip, sizeof(new_entry->client_ip) - 1);
Regular → Executable
View File
Regular → Executable
+22 -1
View File
@@ -2,11 +2,12 @@
#include "logging.h"
#include <openssl/err.h>
#include <openssl/x509_vfy.h>
#include <openssl/x509v3.h>
#include <stdlib.h>
#include <string.h>
SSL_CTX *ssl_ctx = NULL;
static int g_ssl_verify_enabled = 0;
static int g_ssl_verify_enabled = 1;
static char g_ca_file[512] = "";
static char g_ca_path[512] = "";
@@ -63,6 +64,26 @@ void ssl_init(void) {
SSL_CTX_set_options(ssl_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1);
SSL_CTX_set_mode(ssl_ctx, SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
if (SSL_CTX_set_cipher_list(ssl_ctx,
"ECDHE+AESGCM:DHE+AESGCM:ECDHE+CHACHA20:DHE+CHACHA20:!aNULL:!MD5:!DSS") != 1) {
log_info("Warning: Could not set preferred cipher list, using defaults");
}
}
int ssl_set_hostname(SSL *ssl, const char *hostname) {
if (!ssl || !hostname || hostname[0] == '\0') return 0;
SSL_set_tlsext_host_name(ssl, hostname);
if (g_ssl_verify_enabled) {
SSL_set_hostflags(ssl, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS);
if (SSL_set1_host(ssl, hostname) != 1) {
log_debug("Failed to set hostname verification for %s", hostname);
return -1;
}
}
return 0;
}
void ssl_cleanup(void) {
Regular → Executable
+1
View File
@@ -10,6 +10,7 @@ void ssl_set_ca_file(const char *path);
void ssl_set_ca_path(const char *path);
void ssl_init(void);
void ssl_cleanup(void);
int ssl_set_hostname(SSL *ssl, const char *hostname);
int ssl_do_handshake(connection_t *conn);
int ssl_read(connection_t *conn, char *buf, size_t len);
int ssl_write(connection_t *conn, const char *buf, size_t len);
Regular → Executable
+1
View File
@@ -80,6 +80,7 @@ typedef struct connection_s {
conn_type_t type;
client_state_t state;
int fd;
char client_ip[64];
struct connection_s *pair;
struct vhost_stats_s *vhost_stats;
buffer_t read_buf;