Nilorea Library
C utilities for networking, threading, graphics
Loading...
Searching...
No Matches
ex_network_ssl_hostname.c

netw_ssl_do_handshake checks the NAME on the peer certificate, not only that its chain is trusted.

netw_ssl_do_handshake checks the NAME on the peer certificate, not only that its chain is trusted. SNI says which certificate the server should send. It says nothing about which names are acceptable on it. Without the peer identity that netw_ssl_do_handshake now sets, SSL_VERIFY_PEER validates the chain and stops there, so a certificate issued for any domain the holder controls is accepted for EVERY host: exactly the interception a TLS client exists to detect.

This mints one CA, issues a leaf for "right.test" from it, serves that leaf, and connects twice against the same CA: once asking for "right.test" (accepted) and once asking for "wrong.test" (refused). With verification off the handshake completes either way and the mismatch is reported by netw_ssl_get_verify_result.

Author
Castagnier Mickael
Version
1.0
Date
25/08/2026
/*
* Nilorea Library
* Copyright (C) 2005-2026 Castagnier Mickael
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "nilorea/n_log.h"
#include "nilorea/n_str.h"
#include "nilorea/n_x509.h"
#include <pthread.h>
#include <stdio.h>
#include <string.h>
#define SSL_HOSTNAME_PORT "27433"
#define SSL_HOSTNAME_RIGHT "right.test"
#define SSL_HOSTNAME_WRONG "wrong.test"
typedef struct {
int ready;
static int pick_cert(const char* sni, N_STR** cert_pem, N_STR** key_pem, void* user_data) {
SRV_ARG* sa = (SRV_ARG*)user_data;
(void)sni;
*cert_pem = nstrdup(sa->cert);
*key_pem = nstrdup(sa->key);
return (*cert_pem && *key_pem) ? 0 : -1;
}
static void* server_thread(void* a) {
SRV_ARG* sa = (SRV_ARG*)a;
NETWORK* listen = NULL;
if (netw_make_listening(&listen, "127.0.0.1", (char*)SSL_HOSTNAME_PORT, 4, NETWORK_IPV4) == FALSE) {
sa->ready = -1;
return NULL;
}
sa->ready = 1;
while (sa->ready == 1) {
if (peer)
netw_close(&peer);
}
netw_close(&listen);
return NULL;
}
static int try_connect(const char* ask, const char* ca_path, int verify, int* out_verify_ok) {
NETWORK* cl = NULL;
char err[256] = "";
int shook;
*out_verify_ok = 0;
if (netw_ssl_connect_client_to(&cl, "127.0.0.1", (char*)SSL_HOSTNAME_PORT, NETWORK_IPV4, 2000) == FALSE)
return 0;
netw_ssl_set_ca(cl, ca_path, NULL);
netw_ssl_set_verify(cl, verify);
shook = (netw_ssl_do_handshake(cl, ask) == TRUE) ? 1 : 0;
if (shook)
*out_verify_ok = (netw_ssl_get_verify_result(cl, ask, err, sizeof(err)) == TRUE) ? 1 : 0;
netw_close(&cl);
return shook;
}
int main(void) {
N_STR* ca_cert = NULL;
N_STR* ca_key = NULL;
N_STR* leaf_cert = NULL;
N_STR* leaf_key = NULL;
SRV_ARG sa;
pthread_t th;
const char* ca_path = "/tmp/nilorea_ex_ssl_hostname_ca.pem";
int errors = 0;
int shook, vok;
if (n_x509_generate_ca("Nilorea Test CA", 1, &ca_cert, &ca_key) != 0 ||
n_x509_mint_host_cert(SSL_HOSTNAME_RIGHT, ca_cert, ca_key, 1, &leaf_cert, &leaf_key) != 0) {
n_log(LOG_ERR, "could not mint the test certificates");
return 1;
}
{
FILE* cf = fopen(ca_path, "wb");
if (!cf || fwrite(ca_cert->data, 1, (size_t)ca_cert->written, cf) != (size_t)ca_cert->written) {
if (cf)
fclose(cf);
n_log(LOG_ERR, "could not write the test CA to %s", ca_path);
return 1;
}
fclose(cf);
}
memset(&sa, 0, sizeof(sa));
sa.cert = leaf_cert;
sa.key = leaf_key;
if (pthread_create(&th, NULL, server_thread, &sa) != 0) {
n_log(LOG_ERR, "could not start the listener");
return 1;
}
while (sa.ready == 0)
u_sleep(1000);
if (sa.ready < 0) {
n_log(LOG_ERR, "listener could not bind " SSL_HOSTNAME_PORT);
return 1;
}
/* The contract under test is the HANDSHAKE outcome. With SSL_VERIFY_PEER and the
peer identity set, a completed handshake means the chain AND the name were both
accepted, so no separate identity query is needed (and netw_ssl_get_verify_result
cannot be used for it here: it builds its own store from the system default
paths, so a private test CA never validates through it). */
shook = try_connect(SSL_HOSTNAME_RIGHT, ca_path, 1, &vok);
if (shook) {
n_log(LOG_INFO, "PASS(verify on, right name): the handshake completed");
} else {
n_log(LOG_ERR, "FAIL(verify on, right name): a certificate issued for " SSL_HOSTNAME_RIGHT " was refused for " SSL_HOSTNAME_RIGHT);
errors++;
}
shook = try_connect(SSL_HOSTNAME_WRONG, ca_path, 1, &vok);
if (!shook) {
n_log(LOG_INFO, "PASS(verify on, wrong name): the handshake was refused");
} else {
n_log(LOG_ERR, "FAIL(verify on, wrong name): a certificate for " SSL_HOSTNAME_RIGHT " was accepted for " SSL_HOSTNAME_WRONG);
errors++;
}
/* verification OFF is unaffected: the handshake still completes, so a caller that
deliberately does not verify (a reachability probe, a MITM proxy's upstream leg)
keeps exactly its previous behaviour */
shook = try_connect(SSL_HOSTNAME_WRONG, ca_path, 0, &vok);
if (shook) {
n_log(LOG_INFO, "PASS(verify off, wrong name): the handshake still completed");
} else {
n_log(LOG_ERR, "FAIL(verify off, wrong name): setting the identity broke a non-verifying caller");
errors++;
}
sa.ready = 2;
{
NETWORK* poke = NULL;
if (netw_connect_to(&poke, "127.0.0.1", (char*)SSL_HOSTNAME_PORT, NETWORK_IPV4, 500) == TRUE)
netw_close(&poke);
}
pthread_join(th, NULL);
free_nstr(&ca_cert);
free_nstr(&ca_key);
free_nstr(&leaf_cert);
free_nstr(&leaf_key);
remove(ca_path);
netw_unload_openssl(); /* frees the one-time locking state netw_init_openssl took */
if (errors > 0) {
n_log(LOG_ERR, "ex_network_ssl_hostname: %d check(s) failed", errors);
exit(1);
}
n_log(LOG_INFO, "ex_network_ssl_hostname: all checks passed");
exit(0);
}
int main(void)
Definition ex_atlas.c:117
static void * server_thread(void *arg)
Thread function that runs the server accept loop.
char * cert
#define SSL_HOSTNAME_RIGHT
Name the served leaf certificate is actually issued for.
static int try_connect(const char *ask, const char *ca_path, int verify, int *out_verify_ok)
Connect asking for ask and report what verification concluded.
#define SSL_HOSTNAME_WRONG
Name that certificate does NOT carry.
static int pick_cert(const char *sni, N_STR **cert_pem, N_STR **key_pem, void *user_data)
SNI pick callback: always serve the one leaf this test minted.
#define SSL_HOSTNAME_PORT
Port the local TLS listener binds for this test.
Carries the served leaf certificate to the SNI pick callback.
volatile char key[227]
TRUE while the key is held.
#define n_log(__LEVEL__,...)
Logging function wrapper to get line and func.
Definition n_log.h:113
#define LOG_ERR
error conditions
Definition n_log.h:100
void set_log_level(const int log_level)
Set the global log level value ( static int LOG_LEVEL ).
Definition n_log.c:123
#define LOG_INFO
informational
Definition n_log.h:106
size_t written
number of meaningful bytes in data, excluding the null terminator; the size including the null termin...
Definition n_str.h:68
char * data
the string
Definition n_str.h:63
#define free_nstr(__ptr)
free a N_STR structure and set the pointer to NULL
Definition n_str.h:203
N_STR * nstrdup(N_STR *str)
Duplicate a N_STR.
Definition n_str.c:723
A box including a string and his lenght.
Definition n_str.h:61
void u_sleep(unsigned int usec)
wrapper around usleep for API consistency
Definition n_time.c:54
int n_x509_generate_ca(const char *cn, int days, N_STR **ca_cert_pem, N_STR **ca_key_pem)
Generate a self-signed certificate authority (CA) keypair and cert.
Definition n_x509.c:226
int n_x509_mint_host_cert(const char *host, const N_STR *ca_cert_pem, const N_STR *ca_key_pem, int days, N_STR **leaf_cert_pem, N_STR **leaf_key_pem)
Mint a per-host leaf certificate signed by the given CA.
Definition n_x509.c:285
int netw_ssl_get_verify_result(NETWORK *netw, const char *expected_host, char *errbuf, size_t errsz)
evaluate the peer certificate of a completed TLS client connection
Definition n_network.c:2179
int netw_ssl_set_verify(NETWORK *netw, int enable)
enable or disable SSL peer certificate verification
Definition n_network.c:2053
int netw_ssl_set_ca(NETWORK *netw, const char *ca_file, const char *ca_path)
set custom CA verify location for SSL context
Definition n_network.c:2028
int netw_make_listening(NETWORK **netw, char *addr, char *port, int nbpending, int ip_version)
Make a NETWORK be a Listening network.
Definition n_network.c:3493
int netw_ssl_do_handshake(NETWORK *netw, const char *sni_hostname)
Complete the SSL handshake on an already-connected NETWORK.
Definition n_network.c:2948
#define NETWORK_IPV4
Flag to force IPV4.
Definition n_network.h:50
int netw_unload_openssl(void)
Do not directly use, internal api.
Definition n_network.c:1368
int netw_ssl_connect_client_to(NETWORK **netw, char *host, char *port, int ip_version, int connect_timeout_ms)
Connect as an SSL client without providing a client certificate.
Definition n_network.c:2839
NETWORK * netw_accept_ssl_with_sni_cb(NETWORK *listen, netw_sni_pick_cb pick, void *user_data)
accept a TLS connection, selecting the server certificate per client SNI
Definition n_network.c:2007
int netw_close(NETWORK **netw)
Closing a specified Network, destroy queues, free the structure.
Definition n_network.c:3258
int netw_connect_to(NETWORK **netw, char *host, char *port, int ip_version, int connect_timeout_ms)
Connect a NETWORK with a bounded connection-establishment time.
Definition n_network.c:2803
Structure of a NETWORK.
Definition n_network.h:375
Generic log system.
Network Engine.
N_STR and string function declaration.
X.509 helpers: self-signed CA generation and per-host leaf minting.