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-2026 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 const char long_string[MAX_NCP_CIPHERS_LENGTH] =
127 "CHACHA20-POLY1305:CHACHA20-POLY1305:CHACHA20-POLY1305:"
128 "CHACHA20-POLY1305:CHACHA20-POLY1305:CHACHA20-POLY1305:"
129 "CHACHA20-POLY1305";
130 const char longer_string[MAX_NCP_CIPHERS_LENGTH + 1] =
131 "CHACHA20-POLY1305:CHACHA20-POLY1305:CHACHA20-POLY1305:"
132 "CHACHA20-POLY1305:CHACHA20-POLY1305:CHACHA20-POLY1305:"
133 "CHACHA20-POLY1305:";
134 const char longest_string[] =
135 "CHACHA20-POLY1305:CHACHA20-POLY1305:CHACHA20-POLY1305:"
136 "CHACHA20-POLY1305:CHACHA20-POLY1305:CHACHA20-POLY1305:"
137 "CHACHA20-POLY1305:CHACHA20-POLY1305";
138 assert_string_equal(mutate_ncp_cipher_list(long_string, &gc),
139 long_string);
140 assert_string_equal(mutate_ncp_cipher_list(longer_string, &gc),
141 long_string);
142 assert_ptr_equal(mutate_ncp_cipher_list(longest_string, &gc),
143 NULL);
144
145#ifdef ENABLE_CRYPTO_OPENSSL
146 assert_string_equal(mutate_ncp_cipher_list("id-aes128-GCM:id-aes256-GCM", &gc),
147 "AES-128-GCM:AES-256-GCM");
148#else
149 if (have_blowfish)
150 {
151 assert_string_equal(mutate_ncp_cipher_list("BLOWFISH-CBC", &gc), "BF-CBC");
152 }
153#endif
154 gc_free(&gc);
155}
156
157static void
159{
160 struct gc_arena gc = gc_new();
161 const char *client_peer_info;
162 const char *peer_list;
163
164 client_peer_info = "foo=bar\nIV_foo=y\nIV_NCP=2";
165 peer_list = tls_peer_ncp_list(client_peer_info, &gc);
166 assert_string_equal(aes_ciphers, peer_list);
167 assert_true(tls_peer_supports_ncp(client_peer_info));
168
169 client_peer_info = "foo=bar\nIV_foo=y\nIV_NCP=2\nIV_CIPHERS=BF-CBC";
170 peer_list = tls_peer_ncp_list(client_peer_info, &gc);
171 assert_string_equal("BF-CBC", peer_list);
172 assert_true(tls_peer_supports_ncp(client_peer_info));
173
174 client_peer_info = "IV_NCP=2\nIV_CIPHERS=BF-CBC:FOO-BAR\nIV_BAR=7";
175 peer_list = tls_peer_ncp_list(client_peer_info, &gc);
176 assert_string_equal("BF-CBC:FOO-BAR", peer_list);
177 assert_true(tls_peer_supports_ncp(client_peer_info));
178
179 client_peer_info = "IV_CIPHERS=BF-CBC:FOO-BAR\nIV_BAR=7";
180 peer_list = tls_peer_ncp_list(client_peer_info, &gc);
181 assert_string_equal("BF-CBC:FOO-BAR", peer_list);
182 assert_true(tls_peer_supports_ncp(client_peer_info));
183
184 client_peer_info = "IV_YOLO=NO\nIV_BAR=7";
185 peer_list = tls_peer_ncp_list(client_peer_info, &gc);
186 assert_string_equal("", peer_list);
187 assert_false(tls_peer_supports_ncp(client_peer_info));
188
189 peer_list = tls_peer_ncp_list(NULL, &gc);
190 assert_string_equal("", peer_list);
191 assert_false(tls_peer_supports_ncp(client_peer_info));
192
193 gc_free(&gc);
194}
195
196static void
197test_poor_man(void **state)
198{
199 struct gc_arena gc = gc_new();
200 char *best_cipher;
201
202 const char *serverlist = "CHACHA20_POLY1305:AES-128-GCM";
203 const char *serverlistbfcbc = "CHACHA20_POLY1305:AES-128-GCM:BF-CBC:none";
204
205 best_cipher = ncp_get_best_cipher(serverlist, "IV_YOLO=NO\nIV_BAR=7", "BF-CBC", &gc);
206
207 assert_ptr_equal(best_cipher, NULL);
208
209
210 best_cipher = ncp_get_best_cipher(serverlistbfcbc, "IV_YOLO=NO\nIV_BAR=7", "BF-CBC", &gc);
211
212 assert_string_equal(best_cipher, "BF-CBC");
213
214
215 best_cipher = ncp_get_best_cipher(serverlist, "IV_NCP=1\nIV_BAR=7", "AES-128-GCM", &gc);
216
217 assert_string_equal(best_cipher, "AES-128-GCM");
218
219 best_cipher = ncp_get_best_cipher(serverlist, NULL, "AES-128-GCM", &gc);
220
221 assert_string_equal(best_cipher, "AES-128-GCM");
222
223 best_cipher = ncp_get_best_cipher(serverlist, NULL, "none", &gc);
224 assert_ptr_equal(best_cipher, NULL);
225
226 best_cipher = ncp_get_best_cipher(serverlistbfcbc, NULL, "none", &gc);
227 assert_string_equal(best_cipher, "none");
228
229 best_cipher = ncp_get_best_cipher(serverlist, NULL, NULL, &gc);
230 assert_ptr_equal(best_cipher, NULL);
231
232 gc_free(&gc);
233}
234
235
236static void
237test_ncp_best(void **state)
238{
239 struct gc_arena gc = gc_new();
240 char *best_cipher;
241
242 const char *serverlist = "CHACHA20_POLY1305:AES-128-GCM:AES-256-GCM";
243
244 best_cipher = ncp_get_best_cipher(serverlist, "IV_YOLO=NO\nIV_NCP=2\nIV_BAR=7", "BF-CBC", &gc);
245
246 assert_string_equal(best_cipher, "AES-128-GCM");
247
248 /* Best cipher is in --cipher of client */
249 best_cipher = ncp_get_best_cipher(serverlist, "IV_NCP=2\nIV_BAR=7", "CHACHA20_POLY1305", &gc);
250
251 assert_string_equal(best_cipher, "CHACHA20_POLY1305");
252
253 /* Best cipher is in --cipher of client */
254 best_cipher = ncp_get_best_cipher(serverlist, "IV_CIPHERS=AES-128-GCM", "AES-256-CBC", &gc);
255
256
257 assert_string_equal(best_cipher, "AES-128-GCM");
258
259 /* IV_NCP=2 should be ignored if IV_CIPHERS is sent */
260 best_cipher = ncp_get_best_cipher(serverlist, "IV_FOO=7\nIV_CIPHERS=AES-256-GCM\nIV_NCP=2",
261 "AES-256-CBC", &gc);
262
263 assert_string_equal(best_cipher, "AES-256-GCM");
264
265
266 gc_free(&gc);
267}
268
269static void
270test_ncp_default(void **state)
271{
272 bool have_chacha = cipher_valid("CHACHA20-POLY1305");
273
274 struct options o = { 0 };
275
276 o.gc = gc_new();
277
278 /* no user specified string */
279 o.ncp_ciphers = NULL;
281
282 if (have_chacha)
283 {
284 assert_string_equal(o.ncp_ciphers, "AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305");
285 }
286 else
287 {
288 assert_string_equal(o.ncp_ciphers, "AES-256-GCM:AES-128-GCM");
289 }
290 assert_string_equal(o.ncp_ciphers_conf, "DEFAULT");
291
292 /* check that a default string is replaced with DEFAULT */
293 if (have_chacha)
294 {
295 o.ncp_ciphers = "AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305";
296 }
297 else
298 {
299 o.ncp_ciphers = "AES-256-GCM:AES-128-GCM";
300 }
301
303 assert_string_equal(o.ncp_ciphers_conf, "DEFAULT");
304
305 /* test default in the middle of the string */
306 o.ncp_ciphers = "BF-CBC:DEFAULT:AES-128-CBC:AES-256-CBC";
308
309 if (have_chacha)
310 {
311 assert_string_equal(
312 o.ncp_ciphers,
313 "BF-CBC:AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305:AES-128-CBC:AES-256-CBC");
314 }
315 else
316 {
317 assert_string_equal(o.ncp_ciphers,
318 "BF-CBC:AES-256-GCM:AES-128-GCM:AES-128-CBC:AES-256-CBC");
319 }
320 assert_string_equal(o.ncp_ciphers_conf, "BF-CBC:DEFAULT:AES-128-CBC:AES-256-CBC");
321
322 /* string at the beginning */
323 o.ncp_ciphers = "DEFAULT:AES-128-CBC:AES-192-CBC";
325
326 if (have_chacha)
327 {
328 assert_string_equal(o.ncp_ciphers,
329 "AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305:AES-128-CBC:AES-192-CBC");
330 }
331 else
332 {
333 assert_string_equal(o.ncp_ciphers, "AES-256-GCM:AES-128-GCM:AES-128-CBC:AES-192-CBC");
334 }
335 assert_string_equal(o.ncp_ciphers_conf, "DEFAULT:AES-128-CBC:AES-192-CBC");
336
337 /* DEFAULT at the end */
338 o.ncp_ciphers = "AES-192-GCM:AES-128-CBC:DEFAULT";
340
341 if (have_chacha)
342 {
343 assert_string_equal(o.ncp_ciphers,
344 "AES-192-GCM:AES-128-CBC:AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305");
345 }
346 else
347 {
348 assert_string_equal(o.ncp_ciphers, "AES-192-GCM:AES-128-CBC:AES-256-GCM:AES-128-GCM");
349 }
350 assert_string_equal(o.ncp_ciphers_conf, "AES-192-GCM:AES-128-CBC:DEFAULT");
351
352 gc_free(&o.gc);
353}
354
355static void
356test_ncp_expand(void **state)
357{
358 bool have_chacha = cipher_valid("CHACHA20-POLY1305");
359 struct options o = { 0 };
360
361 o.gc = gc_new();
362 struct gc_arena gc = gc_new();
363
364 /* no user specified string */
365 o.ncp_ciphers = NULL;
367
368 const char *expanded = ncp_expanded_ciphers(&o, &gc);
369
370 /* user specificed string with DEFAULT in it */
371 o.ncp_ciphers = "AES-192-GCM:DEFAULT";
373 const char *expanded2 = ncp_expanded_ciphers(&o, &gc);
374
375 if (have_chacha)
376 {
377 assert_string_equal(expanded, " (AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305)");
378 assert_string_equal(expanded2, " (AES-192-GCM:AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305)");
379 }
380 else
381 {
382 assert_string_equal(expanded, " (AES-256-GCM:AES-128-GCM)");
383 assert_string_equal(expanded2, " (AES-192-GCM:AES-256-GCM:AES-128-GCM)");
384 }
385
386 o.ncp_ciphers = "AES-192-GCM:BF-CBC";
388
389 assert_string_equal(ncp_expanded_ciphers(&o, &gc), "");
390
391 gc_free(&o.gc);
392 gc_free(&gc);
393}
394
395
396const struct CMUnitTest ncp_tests[] = {
397 cmocka_unit_test(test_check_ncp_ciphers_list),
398 cmocka_unit_test(test_extract_client_ciphers),
399 cmocka_unit_test(test_poor_man),
400 cmocka_unit_test(test_ncp_best),
401 cmocka_unit_test(test_ncp_default),
402 cmocka_unit_test(test_ncp_expand),
403};
404
405
406int
407main(void)
408{
410#if defined(ENABLE_CRYPTO_OPENSSL)
411 OpenSSL_add_all_algorithms();
412#endif
413 return cmocka_run_group_tests(ncp_tests, NULL, NULL);
414}
static void gc_free(struct gc_arena *a)
Definition buffer.h:1049
static struct gc_arena gc_new(void)
Definition buffer.h:1041
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:381
#define ASSERT(x)
Definition error.h:219
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:576
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:619
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
#define MAX_NCP_CIPHERS_LENGTH
The maximum length of a ncp-cipher string that is accepted.
Definition ssl_ncp.h:123
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:576
const char * ncp_ciphers
Definition options.h:577
struct gc_arena gc
Definition options.h:256
Security parameter state of a single session within a VPN tunnel.
Definition ssl_common.h:489
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:61
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:158
static void test_ncp_default(void **state)
Definition test_ncp.c:270
static void test_ncp_expand(void **state)
Definition test_ncp.c:356
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:407
static void test_poor_man(void **state)
Definition test_ncp.c:197
static void test_ncp_best(void **state)
Definition test_ncp.c:237
const struct CMUnitTest ncp_tests[]
Definition test_ncp.c:396
struct gc_arena gc
Definition test_ssl.c:131