6 ex__network__ssl__hostname_8c
gullradriel edited this page 2026-10-09 00:55:18 +02:00

examples/ex_network_ssl_hostname.c

Structures

Name
struct SRV_ARG
Carries the served leaf certificate to the SNI pick callback.

Functions

Name
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.
void * server_thread(void * a)
Accept TLS connections until asked to stop, serving the same leaf to all.
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.
int main(void )

Defines

Name
SSL_HOSTNAME_WRONG
Name that certificate does NOT carry.
SSL_HOSTNAME_RIGHT
Name the served leaf certificate is actually issued for.
SSL_HOSTNAME_PORT
Port the local TLS listener binds for this test.

Functions Documentation

function try_connect

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.

Parameters:

  • ask Hostname the client asks for (and expects on the certificate).
  • ca_path File holding the CA that issued the served leaf.
  • verify 1 to enable peer verification, 0 to leave it off.
  • out_verify_ok Receives 1 when the identity matched, 0 otherwise.

Return: 1 when the handshake completed, 0 when it did not.

function server_thread

static void * server_thread(
    void * a
)

Accept TLS connections until asked to stop, serving the same leaf to all.

Parameters:

Return: NULL always.

function pick_cert

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.

Parameters:

  • sni The name the client asked for (ignored: the point is that it may differ).
  • cert_pem Receives a copy of the leaf certificate.
  • key_pem Receives a copy of its key.
  • user_data The SRV_ARG.

Return: 0 to proceed with the handshake.

function main

int main(
    void 
)

Macros Documentation

define SSL_HOSTNAME_WRONG

#define SSL_HOSTNAME_WRONG "wrong.test"

Name that certificate does NOT carry.

define SSL_HOSTNAME_RIGHT

#define SSL_HOSTNAME_RIGHT "right.test"

Name the served leaf certificate is actually issued for.

define SSL_HOSTNAME_PORT

#define SSL_HOSTNAME_PORT "27433"

Port the local TLS listener binds for this test.

Source code

/*
 * 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_network.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 {
    N_STR* cert;
    N_STR* key;
    int ready;
} SRV_ARG;

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) {
        NETWORK* peer = netw_accept_ssl_with_sni_cb(listen, pick_cert, sa);
        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;

    set_log_level(LOG_INFO);
    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);
}