Nilorea Library
C utilities for networking, threading, graphics
Loading...
Searching...
No Matches
ex_network_ssl_hostname.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
40
41#include "nilorea/n_log.h"
42#include "nilorea/n_network.h"
43#include "nilorea/n_str.h"
44#include "nilorea/n_x509.h"
45
46#include <pthread.h>
47#include <stdio.h>
48#include <string.h>
49
51#define SSL_HOSTNAME_PORT "27433"
53#define SSL_HOSTNAME_RIGHT "right.test"
55#define SSL_HOSTNAME_WRONG "wrong.test"
56
58typedef struct {
61 int ready;
62} SRV_ARG;
63
72static int pick_cert(const char* sni, N_STR** cert_pem, N_STR** key_pem, void* user_data) {
73 SRV_ARG* sa = (SRV_ARG*)user_data;
74 (void)sni;
75 *cert_pem = nstrdup(sa->cert);
76 *key_pem = nstrdup(sa->key);
77 return (*cert_pem && *key_pem) ? 0 : -1;
78}
79
85static void* server_thread(void* a) {
86 SRV_ARG* sa = (SRV_ARG*)a;
87 NETWORK* listen = NULL;
88 if (netw_make_listening(&listen, "127.0.0.1", (char*)SSL_HOSTNAME_PORT, 4, NETWORK_IPV4) == FALSE) {
89 sa->ready = -1;
90 return NULL;
91 }
92 sa->ready = 1;
93 while (sa->ready == 1) {
95 if (peer)
96 netw_close(&peer);
97 }
98 netw_close(&listen);
99 return NULL;
100}
101
110static int try_connect(const char* ask, const char* ca_path, int verify, int* out_verify_ok) {
111 NETWORK* cl = NULL;
112 char err[256] = "";
113 int shook;
114 *out_verify_ok = 0;
115 if (netw_ssl_connect_client_to(&cl, "127.0.0.1", (char*)SSL_HOSTNAME_PORT, NETWORK_IPV4, 2000) == FALSE)
116 return 0;
117 netw_ssl_set_ca(cl, ca_path, NULL);
118 netw_ssl_set_verify(cl, verify);
119 shook = (netw_ssl_do_handshake(cl, ask) == TRUE) ? 1 : 0;
120 if (shook)
121 *out_verify_ok = (netw_ssl_get_verify_result(cl, ask, err, sizeof(err)) == TRUE) ? 1 : 0;
122 netw_close(&cl);
123 return shook;
124}
125
126int main(void) {
127 N_STR* ca_cert = NULL;
128 N_STR* ca_key = NULL;
129 N_STR* leaf_cert = NULL;
130 N_STR* leaf_key = NULL;
131 SRV_ARG sa;
132 pthread_t th;
133 const char* ca_path = "/tmp/nilorea_ex_ssl_hostname_ca.pem";
134 int errors = 0;
135 int shook, vok;
136
138 if (n_x509_generate_ca("Nilorea Test CA", 1, &ca_cert, &ca_key) != 0 ||
139 n_x509_mint_host_cert(SSL_HOSTNAME_RIGHT, ca_cert, ca_key, 1, &leaf_cert, &leaf_key) != 0) {
140 n_log(LOG_ERR, "could not mint the test certificates");
141 return 1;
142 }
143 {
144 FILE* cf = fopen(ca_path, "wb");
145 if (!cf || fwrite(ca_cert->data, 1, (size_t)ca_cert->written, cf) != (size_t)ca_cert->written) {
146 if (cf)
147 fclose(cf);
148 n_log(LOG_ERR, "could not write the test CA to %s", ca_path);
149 return 1;
150 }
151 fclose(cf);
152 }
153 memset(&sa, 0, sizeof(sa));
154 sa.cert = leaf_cert;
155 sa.key = leaf_key;
156 if (pthread_create(&th, NULL, server_thread, &sa) != 0) {
157 n_log(LOG_ERR, "could not start the listener");
158 return 1;
159 }
160 while (sa.ready == 0)
161 u_sleep(1000);
162 if (sa.ready < 0) {
163 n_log(LOG_ERR, "listener could not bind " SSL_HOSTNAME_PORT);
164 return 1;
165 }
166
167 /* The contract under test is the HANDSHAKE outcome. With SSL_VERIFY_PEER and the
168 peer identity set, a completed handshake means the chain AND the name were both
169 accepted, so no separate identity query is needed (and netw_ssl_get_verify_result
170 cannot be used for it here: it builds its own store from the system default
171 paths, so a private test CA never validates through it). */
172 shook = try_connect(SSL_HOSTNAME_RIGHT, ca_path, 1, &vok);
173 if (shook) {
174 n_log(LOG_INFO, "PASS(verify on, right name): the handshake completed");
175 } else {
176 n_log(LOG_ERR, "FAIL(verify on, right name): a certificate issued for " SSL_HOSTNAME_RIGHT " was refused for " SSL_HOSTNAME_RIGHT);
177 errors++;
178 }
179 shook = try_connect(SSL_HOSTNAME_WRONG, ca_path, 1, &vok);
180 if (!shook) {
181 n_log(LOG_INFO, "PASS(verify on, wrong name): the handshake was refused");
182 } else {
183 n_log(LOG_ERR, "FAIL(verify on, wrong name): a certificate for " SSL_HOSTNAME_RIGHT " was accepted for " SSL_HOSTNAME_WRONG);
184 errors++;
185 }
186
187 /* verification OFF is unaffected: the handshake still completes, so a caller that
188 deliberately does not verify (a reachability probe, a MITM proxy's upstream leg)
189 keeps exactly its previous behaviour */
190 shook = try_connect(SSL_HOSTNAME_WRONG, ca_path, 0, &vok);
191 if (shook) {
192 n_log(LOG_INFO, "PASS(verify off, wrong name): the handshake still completed");
193 } else {
194 n_log(LOG_ERR, "FAIL(verify off, wrong name): setting the identity broke a non-verifying caller");
195 errors++;
196 }
197
198 sa.ready = 2;
199 {
200 NETWORK* poke = NULL;
201 if (netw_connect_to(&poke, "127.0.0.1", (char*)SSL_HOSTNAME_PORT, NETWORK_IPV4, 500) == TRUE)
202 netw_close(&poke);
203 }
204 pthread_join(th, NULL);
205 free_nstr(&ca_cert);
206 free_nstr(&ca_key);
207 free_nstr(&leaf_cert);
208 free_nstr(&leaf_key);
209 remove(ca_path);
210 netw_unload_openssl(); /* frees the one-time locking state netw_init_openssl took */
211 if (errors > 0) {
212 n_log(LOG_ERR, "ex_network_ssl_hostname: %d check(s) failed", errors);
213 exit(1);
214 }
215 n_log(LOG_INFO, "ex_network_ssl_hostname: all checks passed");
216 exit(0);
217}
int main(void)
Definition ex_atlas.c:117
static void * server_thread(void *arg)
Thread function that runs the server accept loop.
#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.
#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.