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

server TLS defaults: a context from netw_set_crypto_pem() and the per-connection context of the SNI accept path both refuse TLS 1.1, ciphers without forward secrecy and CBC ciphers, and accept TLS 1.2 ECDHE-GCM and TLS 1.3

server TLS defaults: a context from netw_set_crypto_pem() and the per-connection context of the SNI accept path both refuse TLS 1.1, ciphers without forward secrecy and CBC ciphers, and accept TLS 1.2 ECDHE-GCM and TLS 1.3

Author
Castagnier Mickael
Version
1.0
Date
23/10/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 <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))
else if (!strncmp("LOG_NOTICE", optarg, 10))
else if (!strncmp("LOG_INFO", optarg, 8))
else if (!strncmp("LOG_ERR", optarg, 7))
else if (!strncmp("LOG_DEBUG", optarg, 9))
}
}
}
/* 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);
} 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;
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));
}
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) {
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
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);
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
}
void process_args(int argc, char **argv)
static int failures
Definition ex_atlas.c:51
int main(void)
Definition ex_atlas.c:117
static void expect_true(const char *label, int cond)
Definition ex_atlas.c:53
static int pick_cb(const char *sni, N_STR **cert_pem, N_STR **key_pem, void *user_data)
char * port
#define n_log(__LEVEL__,...)
Logging function wrapper to get line and func.
Definition n_log.h:113
#define LOG_DEBUG
debug-level messages
Definition n_log.h:108
#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_NOTICE
normal but significant condition
Definition n_log.h:104
#define LOG_NULL
no log output
Definition n_log.h:70
#define LOG_INFO
informational
Definition n_log.h:106
#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
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
N_SOCKET link
networking socket
Definition n_network.h:454
SOCKET sock
a normal socket
Definition n_network.h:356
SSL_CTX * ctx
SSL context holder.
Definition n_network.h:444
SSL * ssl
SSL handle.
Definition n_network.h:446
int netw_set_crypto_pem(NETWORK *netw, const char *key_pem, const char *cert_pem)
activate SSL encryption using PEM-formatted key and certificate strings loaded from memory
Definition n_network.c:1597
int netw_init_openssl(void)
Do not directly use, internal api.
Definition n_network.c:1335
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:3602
int netw_unload_openssl(void)
Do not directly use, internal api.
Definition n_network.c:1368
#define NETWORK_IPALL
Flag for auto detection by OS of ip version to use.
Definition n_network.h:48
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:2116
int netw_close(NETWORK **netw)
Closing a specified Network, destroy queues, free the structure.
Definition n_network.c:3367
int netw_connect(NETWORK **netw, char *host, char *port, int ip_version)
Use this to connect a NETWORK to any listening one, unrestricted send/recv lists.
Definition n_network.c:2898
Structure of a NETWORK.
Definition n_network.h:375
Common headers and low-level functions & define.
Generic log system.
Network Engine.
N_STR and string function declaration.
X.509 helpers: self-signed CA generation and per-host leaf minting.