Nilorea Library
C utilities for networking, threading, graphics
Loading...
Searching...
No Matches
ex_network_sni.c
Go to the documentation of this file.
1/*
2 * Nilorea Library
3 * Copyright (C) 2005-2026 Castagnier Mickael
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
14 * implied. See the License for the specific language governing
15 * permissions and limitations under the License.
16 *
17 * SPDX-License-Identifier: Apache-2.0
18 */
19
26
27#include "nilorea/n_common.h"
28#include "nilorea/n_log.h"
29#include "nilorea/n_network.h"
30#include "nilorea/n_str.h"
31#include "nilorea/n_x509.h"
32
33#include <pthread.h>
34#include <stdio.h>
35#include <stdlib.h>
36#include <string.h>
37#include <unistd.h>
38
39#include <openssl/ssl.h>
40#include <openssl/x509.h>
41
43#define SNI_HOST "test.host.example"
45#define MSG_LEN 18
46
48typedef struct CA_CTX {
49 N_STR* cert;
50 N_STR* key;
51} CA_CTX;
52
54typedef struct SRV {
55 NETWORK* listen;
56 CA_CTX* ca;
57 int ok;
58} SRV;
59
60/* Parse the -V LOG_LEVEL verbosity argument. */
61void process_args(int argc, char** argv) {
62 int opt = 0;
63 while ((opt = getopt(argc, argv, "hvV:")) != EOF) {
64 switch (opt) {
65 case 'V':
66 if (!strncmp("LOG_NULL", optarg, 8))
68 else if (!strncmp("LOG_NOTICE", optarg, 10))
70 else if (!strncmp("LOG_INFO", optarg, 8))
72 else if (!strncmp("LOG_ERR", optarg, 7))
74 else if (!strncmp("LOG_DEBUG", optarg, 9))
76 else {
77 fprintf(stderr, "Unknown log level %s\n", optarg);
78 exit(1);
79 }
80 break;
81 case 'v':
82 fprintf(stderr, "ex_network_sni\n");
83 exit(1);
84 case 'h':
85 default:
86 fprintf(stderr, "usage: %s [-V LOG_LEVEL]\n", argv[0]);
87 break;
88 }
89 }
90}
91
92/* SNI pick callback: mint a leaf for the requested host signed by the CA. */
93static int pick_cb(const char* sni, N_STR** cert_pem, N_STR** key_pem, void* user_data) {
94 CA_CTX* ca = (CA_CTX*)user_data;
95 const char* host = (sni && sni[0]) ? sni : "localhost";
96 n_log(LOG_DEBUG, "SNI pick for host '%s'", host);
97 return n_x509_mint_host_cert(host, ca->cert, ca->key, 825, cert_pem, key_pem);
98}
99
100/* Server thread: accept one SNI connection, echo one message, close. */
101static void* server_fn(void* p) {
102 SRV* s = (SRV*)p;
104 if (c) {
105 char buf[64] = "";
106 char reply[] = "hello-from-server";
107 /* read exactly MSG_LEN, then reply MSG_LEN: recv_ssl_data waits for the
108 * full count, so both sides must agree on the size or they deadlock */
109 if (recv_ssl_data(c, buf, (uint32_t)MSG_LEN) > 0)
110 n_log(LOG_NOTICE, "server received: %s", buf);
111 send_ssl_data(c, reply, (uint32_t)MSG_LEN);
112 s->ok = 1;
113 netw_close(&c);
114 }
115 return NULL;
116}
117
118/* Bind a loopback listening socket on the first free port in a range. */
119static NETWORK* bind_free(char* port_out, size_t port_out_len) {
120 int p;
121 for (p = 18300; p < 18400; p++) {
122 NETWORK* l = NULL;
123 char ps[16];
124 snprintf(ps, sizeof(ps), "%d", p);
125 if (netw_make_listening(&l, "127.0.0.1", ps, 5, NETWORK_IPALL) == TRUE && l) {
126 snprintf(port_out, port_out_len, "%s", ps);
127 return l;
128 }
129 if (l)
130 netw_close(&l);
131 }
132 return NULL;
133}
134
135int main(int argc, char** argv) {
136 CA_CTX ca = {NULL, NULL};
137 NETWORK* listen = NULL;
138 NETWORK* cli = NULL;
139 SRV srv;
140 pthread_t th;
141 char port[16] = "";
142 int failures = 0;
143
145 process_args(argc, argv);
146
147 /* initialize OpenSSL once in the main thread: the server and client threads
148 * both use TLS, and the lazy init guard is not synchronized */
150
151 if (n_x509_generate_ca("Nilorea SNI Test CA", 3650, &ca.cert, &ca.key) != 0 || !ca.cert || !ca.key) {
152 n_log(LOG_ERR, "CA generation failed");
153 return 1;
154 }
155
156 listen = bind_free(port, sizeof(port));
157 if (!listen) {
158 n_log(LOG_ERR, "could not bind a loopback port");
159 free_nstr(&ca.cert);
160 free_nstr(&ca.key);
161 return 1;
162 }
163
164 srv.listen = listen;
165 srv.ca = &ca;
166 srv.ok = 0;
167 pthread_create(&th, NULL, server_fn, &srv);
168
169 if (netw_ssl_connect_client_to(&cli, "127.0.0.1", port, NETWORK_IPALL, 5000) != TRUE || !cli) {
170 n_log(LOG_ERR, "client connect failed");
171 failures++;
172 } else {
173 netw_ssl_set_verify(cli, 0);
174 /* TLS version and cipher policy: a security tool has to be able to say which
175 * versions and ciphers it will speak, and OpenSSL keeps the TLS<=1.2 cipher list
176 * and the TLS1.3 ciphersuites as SEPARATE knobs, so setting one leaves the other
177 * at its default. Both directions are checked here: a bound OpenSSL accepts, and
178 * a cipher string nothing matches must be REFUSED rather than silently ignored,
179 * which is how "I restricted the ciphers" becomes a handshake that used something
180 * else entirely. */
181 if (netw_ssl_set_versions(cli, TLS1_2_VERSION, 0) != TRUE) {
182 n_log(LOG_ERR, "netw_ssl_set_versions(TLS1_2, 0) should succeed");
183 failures++;
184 }
185 if (netw_ssl_set_ciphers(cli, "HIGH:!aNULL", "TLS_AES_256_GCM_SHA384") != TRUE) {
186 n_log(LOG_ERR, "netw_ssl_set_ciphers with a valid list should succeed");
187 failures++;
188 }
189 if (netw_ssl_set_ciphers(cli, "THIS-CIPHER-DOES-NOT-EXIST", NULL) != FALSE) {
190 n_log(LOG_ERR, "netw_ssl_set_ciphers must refuse a list that matches nothing");
191 failures++;
192 }
193 if (netw_ssl_set_versions(NULL, TLS1_2_VERSION, 0) != FALSE ||
194 netw_ssl_set_ciphers(NULL, "HIGH", NULL) != FALSE) {
195 n_log(LOG_ERR, "the TLS policy setters must refuse a NULL network");
196 failures++;
197 }
198 if (netw_ssl_do_handshake(cli, SNI_HOST) != TRUE) {
199 n_log(LOG_ERR, "client TLS handshake failed");
200 failures++;
201 } else {
202 char msg[] = "hello-from-client";
203 char buf[64] = "";
204 X509* peer = NULL;
205 send_ssl_data(cli, msg, (uint32_t)MSG_LEN);
206 if (recv_ssl_data(cli, buf, (uint32_t)MSG_LEN) > 0)
207 n_log(LOG_NOTICE, "client received: %s", buf);
208
209 peer = SSL_get1_peer_certificate(cli->ssl);
210 if (!peer) {
211 n_log(LOG_ERR, "no server certificate presented");
212 failures++;
213 } else {
214 char cn[256] = "";
215 X509_NAME_get_text_by_NID(X509_get_subject_name(peer), NID_commonName, cn, sizeof(cn));
216 n_log(LOG_NOTICE, "server presented a certificate with CN=%s", cn);
217 if (strcmp(cn, SNI_HOST) != 0) {
218 n_log(LOG_ERR, "certificate CN does not match the requested SNI host");
219 failures++;
220 }
221 X509_free(peer);
222 }
223 {
224 char vrerr[128] = "";
225 int trusted = netw_ssl_get_verify_result(cli, SNI_HOST, vrerr, sizeof(vrerr));
226 /* the leaf is signed by a private test CA absent from the system
227 * trust store, so a trust evaluation must fail with a reason */
228 n_log(LOG_NOTICE, "verify result: trusted=%d reason='%s'", trusted, vrerr);
229 if (trusted != FALSE) {
230 n_log(LOG_ERR, "private-CA leaf was unexpectedly trusted");
231 failures++;
232 }
233 if (vrerr[0] == '\0') {
234 n_log(LOG_ERR, "verify failure did not report a reason");
235 failures++;
236 }
237 }
238 }
239 }
240
241 netw_close(&cli);
242 pthread_join(th, NULL);
243 if (!srv.ok) {
244 n_log(LOG_ERR, "server did not complete the exchange");
245 failures++;
246 }
247 netw_close(&listen);
248 free_nstr(&ca.cert);
249 free_nstr(&ca.key);
251
252 if (failures) {
253 n_log(LOG_ERR, "ex_network_sni: %d failure(s)", failures);
254 return 1;
255 }
256 n_log(LOG_NOTICE, "ex_network_sni: all checks passed");
257 return 0;
258}
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 * server_fn(void *p)
static NETWORK * bind_free(char *port_out, size_t port_out_len)
static int pick_cb(const char *sni, N_STR **cert_pem, N_STR **key_pem, void *user_data)
#define MSG_LEN
fixed exchange message length (both messages are 17 chars + NUL)
#define SNI_HOST
the host name the client requests via SNI
N_STR * key
the CA private key PEM
N_STR * cert
the CA certificate PEM
CA used by the server to mint per-host leaves.
char * cert
char * port
NETWORK * listen
the listening socket
int ok
set to 1 once the server completed the exchange
CA_CTX * ca
the CA to mint leaves with
server worker context
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_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
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
SSL * ssl
SSL handle.
Definition n_network.h:446
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
ssize_t send_ssl_data(void *netw, char *buf, uint32_t n)
send data onto the socket
Definition n_network.c:5063
int netw_ssl_set_verify(NETWORK *netw, int enable)
enable or disable SSL peer certificate verification
Definition n_network.c:2053
#define SSL_get1_peer_certificate(ssl)
Definition n_network.h:247
int netw_init_openssl(void)
Do not directly use, internal api.
Definition n_network.c:1335
ssize_t recv_ssl_data(void *netw, char *buf, uint32_t n)
recv data from the socket
Definition n_network.c:5161
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
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
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_ssl_set_ciphers(NETWORK *netw, const char *cipher_list, const char *ciphersuites)
set the cipher list (TLS 1.2 and below) and/or the ciphersuites (TLS 1.3)
Definition n_network.c:2135
int netw_ssl_set_versions(NETWORK *netw, int min_version, int max_version)
set the minimum (and optionally maximum) TLS protocol version on a connection's SSL context
Definition n_network.c:2085
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.