OpenVPN
test_ncp.c
Go to the documentation of this file.
1/*
2 * OpenVPN -- An application to securely tunnel IP networks
3 * over a single UDP port, with support for SSL/TLS-based
4 * session authentication and key exchange,
5 * packet encryption, packet authentication, and
6 * packet compression.
7 *
8 * Copyright (C) 2019-2025 Arne Schwabe <arne@rfc2549.org>
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License version 2
12 * as published by the Free Software Foundation.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License along
20 * with this program; if not, see <https://www.gnu.org/licenses/>.
21 */
22
23#ifdef HAVE_CONFIG_H
24#include "config.h"
25#endif
26
27#include "syshead.h"
28
29#include <stdio.h>
30#include <stdlib.h>
31#include <stdarg.h>
32#include <string.h>
33#include <setjmp.h>
34#include <cmocka.h>
35
36#include "ssl_ncp.c"
37#include "test_common.h"
38
39/* Defines for use in the tests and the mock parse_line() */
40
41const char *bf_chacha = "BF-CBC:CHACHA20-POLY1305";
42const char *aes_chacha = "AES-128-CBC:CHACHA20-POLY1305";
43const char *aes_ciphers = "AES-256-GCM:AES-128-GCM";
44
45
46/* Define this function here as dummy since including the ssl_*.c files
47 * leads to having to include even more unrelated code */
48bool
49key_state_export_keying_material(struct tls_session *session, const char *label, size_t label_size,
50 void *ekm, size_t ekm_size)
51{
52 ASSERT(0);
53}
54
55
56/* Define a dummy dco cipher option to avoid linking against all the DCO
57 * units */
58#if defined(ENABLE_DCO)
59const char *
61{
62 return "AES-192-GCM:AES-128-CBC:AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305";
63}
64#endif
65
66static void
68{
69 struct gc_arena gc = gc_new();
70 bool have_chacha = cipher_valid("CHACHA20-POLY1305");
71 bool have_blowfish = cipher_valid("BF-CBC");
72
73 assert_string_equal(mutate_ncp_cipher_list("none", &gc), "none");
74 assert_string_equal(mutate_ncp_cipher_list("AES-256-GCM:none", &gc), "AES-256-GCM:none");
75
76 assert_string_equal(mutate_ncp_cipher_list(aes_ciphers, &gc), aes_ciphers);
77
78 if (have_chacha)
79 {
80 assert_string_equal(mutate_ncp_cipher_list(aes_chacha, &gc), aes_chacha);
81 }
82
83 if (have_chacha && have_blowfish)
84 {
85 assert_string_equal(mutate_ncp_cipher_list(bf_chacha, &gc), bf_chacha);
86 assert_string_equal(mutate_ncp_cipher_list("BF-CBC:CHACHA20-POLY1305", &gc), bf_chacha);
87 }
88 else
89 {
90 assert_ptr_equal(mutate_ncp_cipher_list(bf_chacha, &gc), NULL);
91 }
92
93 /* Check that optional ciphers work */
94 assert_string_equal(mutate_ncp_cipher_list("AES-256-GCM:?vollbit:AES-128-GCM", &gc),
96
97 /* Check that optional ciphers work */
98 assert_string_equal(mutate_ncp_cipher_list("?AES-256-GCM:?AES-128-GCM", &gc), aes_ciphers);
99
100 /* All unsupported should still yield an empty list */
101 assert_ptr_equal(mutate_ncp_cipher_list("?kugelfisch:?grasshopper", &gc), NULL);
102
103 /* If the last is optional, previous invalid ciphers should be ignored */
104 assert_ptr_equal(mutate_ncp_cipher_list("Vollbit:Littlebit:AES-256-CBC:BF-CBC:?nixbit", &gc),
105 NULL);
106
107 /* We do not support CCM ciphers */
108 assert_ptr_equal(mutate_ncp_cipher_list("AES-256-GCM:AES-128-CCM", &gc), NULL);
109
110 assert_string_equal(mutate_ncp_cipher_list("AES-256-GCM:?AES-128-CCM:AES-128-GCM", &gc),
112
113 /* For testing that with OpenSSL 1.1.0+ that also accepts ciphers in
114 * a different spelling the normalised cipher output is the same */
115 bool have_chacha_mixed_case = cipher_valid("ChaCha20-Poly1305");
116 if (have_chacha_mixed_case)
117 {
118 assert_string_equal(mutate_ncp_cipher_list("AES-128-CBC:ChaCha20-Poly1305", &gc),
119 aes_chacha);
120 }
121
122 assert_ptr_equal(mutate_ncp_cipher_list("vollbit", &gc), NULL);
123 assert_ptr_equal(mutate_ncp_cipher_list("AES-256-GCM:vollbit", &gc), NULL);
124 assert_ptr_equal(mutate_ncp_cipher_list("", &gc), NULL);
125
126 assert_ptr_equal(mutate_ncp_cipher_list("ChaCha20-Poly1305:ChaCha20-Poly1305:ChaCha20-Poly1305:"
127 "ChaCha20-Poly1305:ChaCha20-Poly1305:ChaCha20-Poly1305:"
128 "ChaCha20-Poly1305",
129 &gc),
130 NULL);
131
132#ifdef ENABLE_CRYPTO_OPENSSL
133 assert_string_equal(mutate_ncp_cipher_list("id-aes128-GCM:id-aes256-GCM", &gc),
134 "AES-128-GCM:AES-256-GCM");
135#else
136 if (have_blowfish)
137 {
138 assert_string_equal(mutate_ncp_cipher_list("BLOWFISH-CBC", &gc), "BF-CBC");
139 }
140#endif
141 gc_free(&gc);
142}
143
144static void
146{
147 struct gc_arena gc = gc_new();
148 const char *client_peer_info;
149 const char *peer_list;
150
151 client_peer_info = "foo=bar\nIV_foo=y\nIV_NCP=2";
152 peer_list = tls_peer_ncp_list(client_peer_info, &gc);
153 assert_string_equal(aes_ciphers, peer_list);
154 assert_true(tls_peer_supports_ncp(client_peer_info));
155
156 client_peer_info = "foo=bar\nIV_foo=y\nIV_NCP=2\nIV_CIPHERS=BF-CBC";
157 peer_list = tls_peer_ncp_list(client_peer_info, &gc);
158 assert_string_equal("BF-CBC", peer_list);
159 assert_true(tls_peer_supports_ncp(client_peer_info));
160
161 client_peer_info = "IV_NCP=2\nIV_CIPHERS=BF-CBC:FOO-BAR\nIV_BAR=7";
162 peer_list = tls_peer_ncp_list(client_peer_info, &gc);
163 assert_string_equal("BF-CBC:FOO-BAR", peer_list);
164 assert_true(tls_peer_supports_ncp(client_peer_info));
165
166 client_peer_info = "IV_CIPHERS=BF-CBC:FOO-BAR\nIV_BAR=7";
167 peer_list = tls_peer_ncp_list(client_peer_info, &gc);
168 assert_string_equal("BF-CBC:FOO-BAR", peer_list);
169 assert_true(tls_peer_supports_ncp(client_peer_info));
170
171 client_peer_info = "IV_YOLO=NO\nIV_BAR=7";
172 peer_list = tls_peer_ncp_list(client_peer_info, &gc);
173 assert_string_equal("", peer_list);
174 assert_false(tls_peer_supports_ncp(client_peer_info));
175
176 peer_list = tls_peer_ncp_list(NULL, &gc);
177 assert_string_equal("", peer_list);
178 assert_false(tls_peer_supports_ncp(client_peer_info));
179
180 gc_free(&gc);
181}
182
183static void
184test_poor_man(void **state)
185{
186 struct gc_arena gc = gc_new();
187 char *best_cipher;
188
189 const char *serverlist = "CHACHA20_POLY1305:AES-128-GCM";
190 const char *serverlistbfcbc = "CHACHA20_POLY1305:AES-128-GCM:BF-CBC:none";
191
192 best_cipher = ncp_get_best_cipher(serverlist, "IV_YOLO=NO\nIV_BAR=7", "BF-CBC", &gc);
193
194 assert_ptr_equal(best_cipher, NULL);
195
196
197 best_cipher = ncp_get_best_cipher(serverlistbfcbc, "IV_YOLO=NO\nIV_BAR=7", "BF-CBC", &gc);
198
199 assert_string_equal(best_cipher, "BF-CBC");
200
201
202 best_cipher = ncp_get_best_cipher(serverlist, "IV_NCP=1\nIV_BAR=7", "AES-128-GCM", &gc);
203
204 assert_string_equal(best_cipher, "AES-128-GCM");
205
206 best_cipher = ncp_get_best_cipher(serverlist, NULL, "AES-128-GCM", &gc);
207
208 assert_string_equal(best_cipher, "AES-128-GCM");
209
210 best_cipher = ncp_get_best_cipher(serverlist, NULL, "none", &gc);
211 assert_ptr_equal(best_cipher, NULL);
212
213 best_cipher = ncp_get_best_cipher(serverlistbfcbc, NULL, "none", &gc);
214 assert_string_equal(best_cipher, "none");
215
216 best_cipher = ncp_get_best_cipher(serverlist, NULL, NULL, &gc);
217 assert_ptr_equal(best_cipher, NULL);
218
219 gc_free(&gc);
220}
221
222
223static void
224test_ncp_best(void **state)
225{
226 struct gc_arena gc = gc_new();
227 char *best_cipher;
228
229 const char *serverlist = "CHACHA20_POLY1305:AES-128-GCM:AES-256-GCM";
230
231 best_cipher = ncp_get_best_cipher(serverlist, "IV_YOLO=NO\nIV_NCP=2\nIV_BAR=7", "BF-CBC", &gc);
232
233 assert_string_equal(best_cipher, "AES-128-GCM");
234
235 /* Best cipher is in --cipher of client */
236 best_cipher = ncp_get_best_cipher(serverlist, "IV_NCP=2\nIV_BAR=7", "CHACHA20_POLY1305", &gc);
237
238 assert_string_equal(best_cipher, "CHACHA20_POLY1305");
239
240 /* Best cipher is in --cipher of client */
241 best_cipher = ncp_get_best_cipher(serverlist, "IV_CIPHERS=AES-128-GCM", "AES-256-CBC", &gc);
242
243
244 assert_string_equal(best_cipher, "AES-128-GCM");
245
246 /* IV_NCP=2 should be ignored if IV_CIPHERS is sent */
247 best_cipher = ncp_get_best_cipher(serverlist, "IV_FOO=7\nIV_CIPHERS=AES-256-GCM\nIV_NCP=2",
248 "AES-256-CBC", &gc);
249
250 assert_string_equal(best_cipher, "AES-256-GCM");
251
252
253 gc_free(&gc);
254}
255
256static void
257test_ncp_default(void **state)
258{
259 bool have_chacha = cipher_valid("CHACHA20-POLY1305");
260
261 struct options o = { 0 };
262
263 o.gc = gc_new();
264
265 /* no user specified string */
266 o.ncp_ciphers = NULL;
268
269 if (have_chacha)
270 {
271 assert_string_equal(o.ncp_ciphers, "AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305");
272 }
273 else
274 {
275 assert_string_equal(o.ncp_ciphers, "AES-256-GCM:AES-128-GCM");
276 }
277 assert_string_equal(o.ncp_ciphers_conf, "DEFAULT");
278
279 /* check that a default string is replaced with DEFAULT */
280 if (have_chacha)
281 {
282 o.ncp_ciphers = "AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305";
283 }
284 else
285 {
286 o.ncp_ciphers = "AES-256-GCM:AES-128-GCM";
287 }
288
290 assert_string_equal(o.ncp_ciphers_conf, "DEFAULT");
291
292 /* test default in the middle of the string */
293 o.ncp_ciphers = "BF-CBC:DEFAULT:AES-128-CBC:AES-256-CBC";
295
296 if (have_chacha)
297 {
298 assert_string_equal(
299 o.ncp_ciphers,
300 "BF-CBC:AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305:AES-128-CBC:AES-256-CBC");
301 }
302 else
303 {
304 assert_string_equal(o.ncp_ciphers,
305 "BF-CBC:AES-256-GCM:AES-128-GCM:AES-128-CBC:AES-256-CBC");
306 }
307 assert_string_equal(o.ncp_ciphers_conf, "BF-CBC:DEFAULT:AES-128-CBC:AES-256-CBC");
308
309 /* string at the beginning */
310 o.ncp_ciphers = "DEFAULT:AES-128-CBC:AES-192-CBC";
312
313 if (have_chacha)
314 {
315 assert_string_equal(o.ncp_ciphers,
316 "AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305:AES-128-CBC:AES-192-CBC");
317 }
318 else
319 {
320 assert_string_equal(o.ncp_ciphers, "AES-256-GCM:AES-128-GCM:AES-128-CBC:AES-192-CBC");
321 }
322 assert_string_equal(o.ncp_ciphers_conf, "DEFAULT:AES-128-CBC:AES-192-CBC");
323
324 /* DEFAULT at the end */
325 o.ncp_ciphers = "AES-192-GCM:AES-128-CBC:DEFAULT";
327
328 if (have_chacha)
329 {
330 assert_string_equal(o.ncp_ciphers,
331 "AES-192-GCM:AES-128-CBC:AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305");
332 }
333 else
334 {
335 assert_string_equal(o.ncp_ciphers, "AES-192-GCM:AES-128-CBC:AES-256-GCM:AES-128-GCM");
336 }
337 assert_string_equal(o.ncp_ciphers_conf, "AES-192-GCM:AES-128-CBC:DEFAULT");
338
339 gc_free(&o.gc);
340}
341
342static void
343test_ncp_expand(void **state)
344{
345 bool have_chacha = cipher_valid("CHACHA20-POLY1305");
346 struct options o = { 0 };
347
348 o.gc = gc_new();
349 struct gc_arena gc = gc_new();
350
351 /* no user specified string */
352 o.ncp_ciphers = NULL;
354
355 const char *expanded = ncp_expanded_ciphers(&o, &gc);
356
357 /* user specificed string with DEFAULT in it */
358 o.ncp_ciphers = "AES-192-GCM:DEFAULT";
360 const char *expanded2 = ncp_expanded_ciphers(&o, &gc);
361
362 if (have_chacha)
363 {
364 assert_string_equal(expanded, " (AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305)");
365 assert_string_equal(expanded2, " (AES-192-GCM:AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305)");
366 }
367 else
368 {
369 assert_string_equal(expanded, " (AES-256-GCM:AES-128-GCM)");
370 assert_string_equal(expanded2, " (AES-192-GCM:AES-256-GCM:AES-128-GCM)");
371 }
372
373 o.ncp_ciphers = "AES-192-GCM:BF-CBC";
375
376 assert_string_equal(ncp_expanded_ciphers(&o, &gc), "");
377
378 gc_free(&o.gc);
379 gc_free(&gc);
380}
381
382
383const struct CMUnitTest ncp_tests[] = {
384 cmocka_unit_test(test_check_ncp_ciphers_list),
385 cmocka_unit_test(test_extract_client_ciphers),
386 cmocka_unit_test(test_poor_man),
387 cmocka_unit_test(test_ncp_best),
388 cmocka_unit_test(test_ncp_default),
389 cmocka_unit_test(test_ncp_expand),
390};
391
392
393int
394main(void)
395{
397#if defined(ENABLE_CRYPTO_OPENSSL)
398 OpenSSL_add_all_algorithms();
399#endif
400 return cmocka_run_group_tests(ncp_tests, NULL, NULL);
401}
static void gc_free(struct gc_arena *a)
Definition buffer.h:1015
static struct gc_arena gc_new(void)
Definition buffer.h:1007
static bool cipher_valid(const char *ciphername)
Returns if the cipher is valid, based on the given cipher name.
static const char * dco_get_supported_ciphers(void)
Definition dco.h:383
#define ASSERT(x)
Definition error.h:217
Control Channel SSL/Data dynamic negotiation Module This file is split from ssl.c to be able to unit ...
char * ncp_get_best_cipher(const char *server_list, const char *peer_info, const char *remote_cipher, struct gc_arena *gc)
Iterates through the ciphers in server_list and return the first cipher that is also supported by the...
Definition ssl_ncp.c:246
const char * tls_peer_ncp_list(const char *peer_info, struct gc_arena *gc)
Returns the support cipher list from the peer according to the IV_NCP and IV_CIPHER values in peer_in...
Definition ssl_ncp.c:225
void options_postprocess_setdefault_ncpciphers(struct options *o)
Checks for availibility of Chacha20-Poly1305 and sets the ncp_cipher to either AES-256-GCM:AES-128-GC...
Definition ssl_ncp.c:575
const char * ncp_expanded_ciphers(struct options *o, struct gc_arena *gc)
returns the o->ncp_ciphers in brackets, e.g.
Definition ssl_ncp.c:618
bool tls_peer_supports_ncp(const char *peer_info)
Returns whether the client supports NCP either by announcing IV_NCP>=2 or the IV_CIPHERS list.
Definition ssl_ncp.c:79
char * mutate_ncp_cipher_list(const char *list, struct gc_arena *gc)
Check whether the ciphers in the supplied list are supported.
Definition ssl_ncp.c:96
Garbage collection arena used to keep track of dynamically allocated memory.
Definition buffer.h:116
const char * ncp_ciphers_conf
The original ncp_ciphers specified by the user in the configuration.
Definition options.h:580
const char * ncp_ciphers
Definition options.h:581
struct gc_arena gc
Definition options.h:253
Security parameter state of a single session within a VPN tunnel.
Definition ssl_common.h:490
static void openvpn_unit_test_setup(void)
Sets up the environment for unit tests like making both stderr and stdout non-buffered to avoid messa...
Definition test_common.h:35
const char * aes_ciphers
Definition test_ncp.c:43
bool key_state_export_keying_material(struct tls_session *session, const char *label, size_t label_size, void *ekm, size_t ekm_size)
Keying Material Exporters [RFC 5705] allows additional keying material to be derived from existing TL...
Definition test_ncp.c:49
const char * bf_chacha
Definition test_ncp.c:41
static void test_extract_client_ciphers(void **state)
Definition test_ncp.c:145
static void test_ncp_default(void **state)
Definition test_ncp.c:257
static void test_ncp_expand(void **state)
Definition test_ncp.c:343
static void test_check_ncp_ciphers_list(void **state)
Definition test_ncp.c:67
const char * aes_chacha
Definition test_ncp.c:42
int main(void)
Definition test_ncp.c:394
static void test_poor_man(void **state)
Definition test_ncp.c:184
static void test_ncp_best(void **state)
Definition test_ncp.c:224
const struct CMUnitTest ncp_tests[]
Definition test_ncp.c:383
struct gc_arena gc
Definition test_ssl.c:154