1 ex__network__tls__defaults_8c
gullradriel edited this page 2026-10-25 10:46:33 +01:00

examples/ex_network_tls_defaults.c

Functions

Name
void process_args(int argc, char ** argv)
read -V LOG_LEVEL
int main(int argc, char ** argv)
run the checks

Functions Documentation

function process_args

static void process_args(
    int argc,
    char ** argv
)

read -V LOG_LEVEL

Parameters:

  • argc argument count
  • argv arguments

function main

int main(
    int argc,
    char ** argv
)

run the checks

Parameters:

  • argc argument count
  • argv arguments

Return: 0 when every check passes

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_common.h"
#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 <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <openssl/err.h>
#include <openssl/ssl.h>

static void process_args(int argc, char** argv) {
    int opt = 0;
    while ((opt = getopt(argc, argv, "V:")) != EOF) {
        if (opt == 'V') {
            if (!strncmp("LOG_NULL", optarg, 8))
                set_log_level(LOG_NULL);
            else if (!strncmp("LOG_NOTICE", optarg, 10))
                set_log_level(LOG_NOTICE);
            else if (!strncmp("LOG_INFO", optarg, 8))
                set_log_level(LOG_INFO);
            else if (!strncmp("LOG_ERR", optarg, 7))
                set_log_level(LOG_ERR);
            else if (!strncmp("LOG_DEBUG", optarg, 9))
                set_log_level(LOG_DEBUG);
        }
    }
}

/* The checks need the OpenSSL 1.1.1 API (version bounds, TLS 1.3, security
 * levels); older OpenSSL and LibreSSL builds compile a stub that says so. */
#if N_OSSL_OPENSSL_ATLEAST(0x10101000L)

static int failures = 0;

static void expect_true(const char* what, int ok) {
    if (!ok) {
        n_log(LOG_ERR, "FAIL: %s", what);
        failures++;
    } else {
        n_log(LOG_INFO, "ok: %s", what);
    }
}

typedef struct {
    const char* what;     
    int max_version;      
    const char* ciphers;  
    int expect_ok;        
    const char* expect_v; 
} client_case;

static const client_case cases[] = {
    {"TLS 1.1 client refused", TLS1_1_VERSION, NULL, 0, NULL},
    {"TLS 1.2 RSA key exchange (no forward secrecy) refused", TLS1_2_VERSION, "AES128-GCM-SHA256:AES256-GCM-SHA384", 0, NULL},
    {"TLS 1.2 CBC cipher refused", TLS1_2_VERSION, "ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA", 0, NULL},
    {"TLS 1.2 ECDHE-GCM accepted", TLS1_2_VERSION, "ECDHE-RSA-AES128-GCM-SHA256", 1, "TLSv1.2"},
    {"default client gets TLS 1.3", 0, NULL, 1, "TLSv1.3"},
};

static SSL_CTX* client_ctx(const client_case* c) {
    SSL_CTX* ctx = SSL_CTX_new(TLS_client_method());
    if (!ctx)
        return NULL;
    /* the client side of this test must be able to offer what the server
     * refuses, whatever the system openssl.cnf says */
    SSL_CTX_set_security_level(ctx, 0);
    SSL_CTX_set_min_proto_version(ctx, TLS1_VERSION);
    if (c->max_version)
        SSL_CTX_set_max_proto_version(ctx, c->max_version);
    if (c->ciphers)
        SSL_CTX_set_cipher_list(ctx, c->ciphers);
    SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL);
    return ctx;
}

static int bio_handshake(SSL_CTX* sctx, SSL_CTX* cctx, char* version, size_t vsz) {
    SSL* s = SSL_new(sctx);
    SSL* c = SSL_new(cctx);
    BIO *bs = NULL, *bc = NULL;
    int i, done_s = 0, done_c = 0, ok = 0;
    if (!s || !c || BIO_new_bio_pair(&bs, 0, &bc, 0) != 1)
        goto out;
    SSL_set_bio(s, bs, bs);
    SSL_set_bio(c, bc, bc);
    SSL_set_accept_state(s);
    SSL_set_connect_state(c);
    for (i = 0; i < 64 && !(done_s && done_c); i++) {
        int rc = SSL_do_handshake(c), rs;
        if (rc == 1)
            done_c = 1;
        else if (SSL_get_error(c, rc) != SSL_ERROR_WANT_READ && SSL_get_error(c, rc) != SSL_ERROR_WANT_WRITE)
            break;
        rs = SSL_do_handshake(s);
        if (rs == 1)
            done_s = 1;
        else if (SSL_get_error(s, rs) != SSL_ERROR_WANT_READ && SSL_get_error(s, rs) != SSL_ERROR_WANT_WRITE)
            break;
    }
    ok = done_s && done_c;
    if (ok)
        snprintf(version, vsz, "%s", SSL_get_version(c));
out:
    ERR_clear_error();
    SSL_free(s);
    SSL_free(c);
    return ok;
}

static N_STR *ca_cert = NULL, *ca_key = NULL, *leaf_cert = NULL, *leaf_key = NULL;

static void test_pem_context(void) {
    NETWORK* srv = NULL;
    char port[16];
    int p;
    for (p = 18400; p < 18500 && !srv; p++) {
        snprintf(port, sizeof(port), "%d", p);
        if (netw_make_listening(&srv, "127.0.0.1", port, 5, NETWORK_IPALL) != TRUE && srv)
            netw_close(&srv);
    }
    expect_true("listening socket", srv != NULL);
    if (!srv)
        return;
    expect_true("netw_set_crypto_pem", netw_set_crypto_pem(srv, leaf_key->data, leaf_cert->data) == TRUE);
    if (srv->ctx) {
        STACK_OF(SSL_CIPHER)* list = SSL_CTX_get_ciphers(srv->ctx);
        int i, only_good = 1;
        expect_true("minimum version is TLS 1.2", SSL_CTX_get_min_proto_version(srv->ctx) == TLS1_2_VERSION);
        expect_true("no compression", (SSL_CTX_get_options(srv->ctx) & SSL_OP_NO_COMPRESSION) != 0);
        expect_true("no session tickets", (SSL_CTX_get_options(srv->ctx) & SSL_OP_NO_TICKET) != 0);
        expect_true("server cipher order", (SSL_CTX_get_options(srv->ctx) & SSL_OP_CIPHER_SERVER_PREFERENCE) != 0);
#ifdef SSL_OP_NO_RENEGOTIATION
        expect_true("no renegotiation", (SSL_CTX_get_options(srv->ctx) & SSL_OP_NO_RENEGOTIATION) != 0);
#endif
        for (i = 0; list && i < sk_SSL_CIPHER_num(list); i++) {
            const char* name = SSL_CIPHER_get_name(sk_SSL_CIPHER_value(list, i));
            int tls13 = strncmp(name, "TLS_", 4) == 0;
            int good12 = strncmp(name, "ECDHE-", 6) == 0 && (strstr(name, "GCM") || strstr(name, "CHACHA20"));
            if (!tls13 && !good12) {
                n_log(LOG_ERR, "unexpected cipher enabled: %s", name);
                only_good = 0;
            }
        }
        expect_true("only forward-secret AEAD ciphers", only_good && list && sk_SSL_CIPHER_num(list) > 0);
        for (i = 0; i < (int)(sizeof(cases) / sizeof(cases[0])); i++) {
            char v[32] = "";
            SSL_CTX* cctx = client_ctx(&cases[i]);
            int ok = cctx && bio_handshake(srv->ctx, cctx, v, sizeof(v));
            expect_true(cases[i].what, ok == cases[i].expect_ok && (!ok || !cases[i].expect_v || strcmp(v, cases[i].expect_v) == 0));
            SSL_CTX_free(cctx);
        }
    }
    netw_close(&srv);
}

typedef struct {
    NETWORK* listen; 
    int accepted;    
} sni_srv;

static int pick_cb(const char* sni, N_STR** cert_pem, N_STR** key_pem, void* user_data) {
    (void)sni;
    (void)user_data;
    *cert_pem = nstrdup(leaf_cert);
    *key_pem = nstrdup(leaf_key);
    return (*cert_pem && *key_pem) ? 0 : -1;
}

static void* sni_server(void* p) {
    sni_srv* s = (sni_srv*)p;
    NETWORK* c = netw_accept_ssl_with_sni_cb(s->listen, pick_cb, NULL);
    s->accepted = c != NULL;
    if (c) {
        char b[4];
        /* wait for the client to close, so it reads the whole handshake */
        SSL_read(c->ssl, b, sizeof(b));
        netw_close(&c);
    }
    return NULL;
}

static void test_sni_path(void) {
    NETWORK* listen = NULL;
    char port[16];
    int p, i;
    for (p = 18500; p < 18600 && !listen; p++) {
        snprintf(port, sizeof(port), "%d", p);
        if (netw_make_listening(&listen, "127.0.0.1", port, 5, NETWORK_IPALL) != TRUE && listen)
            netw_close(&listen);
    }
    expect_true("SNI listening socket", listen != NULL);
    if (!listen)
        return;
    for (i = 0; i < (int)(sizeof(cases) / sizeof(cases[0])); i++) {
        sni_srv s = {listen, 0};
        pthread_t th;
        NETWORK* cli = NULL;
        SSL_CTX* cctx = client_ctx(&cases[i]);
        int ok = 0;
        char v[32] = "";
        pthread_create(&th, NULL, sni_server, &s);
        if (cctx && netw_connect(&cli, "127.0.0.1", port, NETWORK_IPALL) == TRUE && cli) {
            SSL* ssl = SSL_new(cctx);
            SSL_set_fd(ssl, (int)cli->link.sock);
            SSL_set_tlsext_host_name(ssl, "localhost");
            ok = SSL_connect(ssl) == 1;
            if (ok)
                snprintf(v, sizeof(v), "%s", SSL_get_version(ssl));
            SSL_shutdown(ssl);
            SSL_free(ssl);
            ERR_clear_error();
        }
        netw_close(&cli);
        pthread_join(th, NULL);
        {
            char what[160];
            snprintf(what, sizeof(what), "SNI path: %s", cases[i].what);
            expect_true(what, ok == cases[i].expect_ok && s.accepted == cases[i].expect_ok &&
                                  (!ok || !cases[i].expect_v || strcmp(v, cases[i].expect_v) == 0));
        }
        SSL_CTX_free(cctx);
    }
    netw_close(&listen);
}

#endif /* N_OSSL_OPENSSL_ATLEAST(0x10101000L) */

int main(int argc, char** argv) {
    set_log_level(LOG_NOTICE);
    process_args(argc, argv);
#if !N_OSSL_OPENSSL_ATLEAST(0x10101000L)
    n_log(LOG_NOTICE, "ex_network_tls_defaults: skipped, needs OpenSSL 1.1.1 or later");
    return 0;
#else
    netw_init_openssl();

    if (n_x509_generate_ca("Nilorea TLS Defaults Test CA", 30, &ca_cert, &ca_key) != 0 ||
        n_x509_mint_host_cert("localhost", ca_cert, ca_key, 30, &leaf_cert, &leaf_key) != 0) {
        n_log(LOG_ERR, "could not mint the test certificates");
        return 1;
    }
    test_pem_context();
    test_sni_path();

    free_nstr(&ca_cert);
    free_nstr(&ca_key);
    free_nstr(&leaf_cert);
    free_nstr(&leaf_key);
    netw_unload_openssl();

    if (failures) {
        n_log(LOG_ERR, "ex_network_tls_defaults: %d failure(s)", failures);
        return 1;
    }
    n_log(LOG_NOTICE, "ex_network_tls_defaults: all checks passed");
    return 0;
#endif
}