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-2024 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, write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 */
23
24#ifdef HAVE_CONFIG_H
25#include "config.h"
26#endif
27
28#include "syshead.h"
29
30#include <stdio.h>
31#include <stdlib.h>
32#include <stdarg.h>
33#include <string.h>
34#include <setjmp.h>
35#include <cmocka.h>
36
37#include "ssl_ncp.c"
38#include "test_common.h"
39
40/* Defines for use in the tests and the mock parse_line() */
41
42const char *bf_chacha = "BF-CBC:CHACHA20-POLY1305";
43const char *aes_chacha = "AES-128-CBC:CHACHA20-POLY1305";
44const char *aes_ciphers = "AES-256-GCM:AES-128-GCM";
45
46
47/* Define this function here as dummy since including the ssl_*.c files
48 * leads to having to include even more unrelated code */
49bool
51 const char *label, size_t label_size,
52 void *ekm, size_t ekm_size)
53{
54 ASSERT(0);
55}
56
57
58/* Define a dummy dco cipher option to avoid linking against all the DCO
59 * units */
60#if defined(ENABLE_DCO)
61const char *
63{
64 return "AES-192-GCM:AES-128-CBC:AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305";
65}
66#endif
67
68static void
70{
71 struct gc_arena gc = gc_new();
72 bool have_chacha = cipher_valid("CHACHA20-POLY1305");
73 bool have_blowfish = cipher_valid("BF-CBC");
74
75 assert_string_equal(mutate_ncp_cipher_list("none", &gc), "none");
76 assert_string_equal(mutate_ncp_cipher_list("AES-256-GCM:none", &gc),
77 "AES-256-GCM:none");
78
79 assert_string_equal(mutate_ncp_cipher_list(aes_ciphers, &gc), aes_ciphers);
80
81 if (have_chacha)
82 {
83 assert_string_equal(mutate_ncp_cipher_list(aes_chacha, &gc), aes_chacha);
84 }
85
86 if (have_chacha && have_blowfish)
87 {
88 assert_string_equal(mutate_ncp_cipher_list(bf_chacha, &gc), bf_chacha);
89 assert_string_equal(mutate_ncp_cipher_list("BF-CBC:CHACHA20-POLY1305", &gc),
90 bf_chacha);
91 }
92 else
93 {
94 assert_ptr_equal(mutate_ncp_cipher_list(bf_chacha, &gc), NULL);
95 }
96
97 /* Check that optional ciphers work */
98 assert_string_equal(mutate_ncp_cipher_list("AES-256-GCM:?vollbit:AES-128-GCM", &gc),
100
101 /* Check that optional ciphers work */
102 assert_string_equal(mutate_ncp_cipher_list("?AES-256-GCM:?AES-128-GCM", &gc),
104
105 /* All unsupported should still yield an empty list */
106 assert_ptr_equal(mutate_ncp_cipher_list("?kugelfisch:?grasshopper", &gc), NULL);
107
108 /* If the last is optional, previous invalid ciphers should be ignored */
109 assert_ptr_equal(mutate_ncp_cipher_list("Vollbit:Littlebit:AES-256-CBC:BF-CBC:?nixbit", &gc), NULL);
110
111 /* We do not support CCM ciphers */
112 assert_ptr_equal(mutate_ncp_cipher_list("AES-256-GCM:AES-128-CCM", &gc), NULL);
113
114 assert_string_equal(mutate_ncp_cipher_list("AES-256-GCM:?AES-128-CCM:AES-128-GCM", &gc),
116
117 /* For testing that with OpenSSL 1.1.0+ that also accepts ciphers in
118 * a different spelling the normalised cipher output is the same */
119 bool have_chacha_mixed_case = cipher_valid("ChaCha20-Poly1305");
120 if (have_chacha_mixed_case)
121 {
122 assert_string_equal(mutate_ncp_cipher_list("AES-128-CBC:ChaCha20-Poly1305", &gc),
123 aes_chacha);
124 }
125
126 assert_ptr_equal(mutate_ncp_cipher_list("vollbit", &gc), NULL);
127 assert_ptr_equal(mutate_ncp_cipher_list("AES-256-GCM:vollbit", &gc), NULL);
128 assert_ptr_equal(mutate_ncp_cipher_list("", &gc), NULL);
129
130 assert_ptr_equal(mutate_ncp_cipher_list(
131 "ChaCha20-Poly1305:ChaCha20-Poly1305:ChaCha20-Poly1305:"
132 "ChaCha20-Poly1305:ChaCha20-Poly1305:ChaCha20-Poly1305:"
133 "ChaCha20-Poly1305", &gc), NULL);
134
135#ifdef ENABLE_CRYPTO_OPENSSL
136 assert_string_equal(mutate_ncp_cipher_list("id-aes128-GCM:id-aes256-GCM",
137 &gc), "AES-128-GCM:AES-256-GCM");
138#else
139 if (have_blowfish)
140 {
141 assert_string_equal(mutate_ncp_cipher_list("BLOWFISH-CBC",
142 &gc), "BF-CBC");
143 }
144#endif
145 gc_free(&gc);
146}
147
148static void
150{
151 struct gc_arena gc = gc_new();
152 const char *client_peer_info;
153 const char *peer_list;
154
155 client_peer_info = "foo=bar\nIV_foo=y\nIV_NCP=2";
156 peer_list = tls_peer_ncp_list(client_peer_info, &gc);
157 assert_string_equal(aes_ciphers, peer_list);
158 assert_true(tls_peer_supports_ncp(client_peer_info));
159
160 client_peer_info = "foo=bar\nIV_foo=y\nIV_NCP=2\nIV_CIPHERS=BF-CBC";
161 peer_list = tls_peer_ncp_list(client_peer_info, &gc);
162 assert_string_equal("BF-CBC", peer_list);
163 assert_true(tls_peer_supports_ncp(client_peer_info));
164
165 client_peer_info = "IV_NCP=2\nIV_CIPHERS=BF-CBC:FOO-BAR\nIV_BAR=7";
166 peer_list = tls_peer_ncp_list(client_peer_info, &gc);
167 assert_string_equal("BF-CBC:FOO-BAR", peer_list);
168 assert_true(tls_peer_supports_ncp(client_peer_info));
169
170 client_peer_info = "IV_CIPHERS=BF-CBC:FOO-BAR\nIV_BAR=7";
171 peer_list = tls_peer_ncp_list(client_peer_info, &gc);
172 assert_string_equal("BF-CBC:FOO-BAR", peer_list);
173 assert_true(tls_peer_supports_ncp(client_peer_info));
174
175 client_peer_info = "IV_YOLO=NO\nIV_BAR=7";
176 peer_list = tls_peer_ncp_list(client_peer_info, &gc);
177 assert_string_equal("", peer_list);
178 assert_false(tls_peer_supports_ncp(client_peer_info));
179
180 peer_list = tls_peer_ncp_list(NULL, &gc);
181 assert_string_equal("", peer_list);
182 assert_false(tls_peer_supports_ncp(client_peer_info));
183
184 gc_free(&gc);
185}
186
187static void
188test_poor_man(void **state)
189{
190 struct gc_arena gc = gc_new();
191 char *best_cipher;
192
193 const char *serverlist = "CHACHA20_POLY1305:AES-128-GCM";
194 const char *serverlistbfcbc = "CHACHA20_POLY1305:AES-128-GCM:BF-CBC:none";
195
196 best_cipher = ncp_get_best_cipher(serverlist,
197 "IV_YOLO=NO\nIV_BAR=7",
198 "BF-CBC", &gc);
199
200 assert_ptr_equal(best_cipher, NULL);
201
202
203 best_cipher = ncp_get_best_cipher(serverlistbfcbc,
204 "IV_YOLO=NO\nIV_BAR=7",
205 "BF-CBC", &gc);
206
207 assert_string_equal(best_cipher, "BF-CBC");
208
209
210 best_cipher = ncp_get_best_cipher(serverlist,
211 "IV_NCP=1\nIV_BAR=7",
212 "AES-128-GCM", &gc);
213
214 assert_string_equal(best_cipher, "AES-128-GCM");
215
216 best_cipher = ncp_get_best_cipher(serverlist, NULL,
217 "AES-128-GCM", &gc);
218
219 assert_string_equal(best_cipher, "AES-128-GCM");
220
221 best_cipher = ncp_get_best_cipher(serverlist, NULL,
222 "none", &gc);
223 assert_ptr_equal(best_cipher, NULL);
224
225 best_cipher = ncp_get_best_cipher(serverlistbfcbc, NULL,
226 "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,
245 "IV_YOLO=NO\nIV_NCP=2\nIV_BAR=7",
246 "BF-CBC", &gc);
247
248 assert_string_equal(best_cipher, "AES-128-GCM");
249
250 /* Best cipher is in --cipher of client */
251 best_cipher = ncp_get_best_cipher(serverlist, "IV_NCP=2\nIV_BAR=7",
252 "CHACHA20_POLY1305", &gc);
253
254 assert_string_equal(best_cipher, "CHACHA20_POLY1305");
255
256 /* Best cipher is in --cipher of client */
257 best_cipher = ncp_get_best_cipher(serverlist, "IV_CIPHERS=AES-128-GCM",
258 "AES-256-CBC", &gc);
259
260
261 assert_string_equal(best_cipher, "AES-128-GCM");
262
263 /* IV_NCP=2 should be ignored if IV_CIPHERS is sent */
264 best_cipher = ncp_get_best_cipher(serverlist,
265 "IV_FOO=7\nIV_CIPHERS=AES-256-GCM\nIV_NCP=2",
266 "AES-256-CBC", &gc);
267
268 assert_string_equal(best_cipher, "AES-256-GCM");
269
270
271 gc_free(&gc);
272}
273
274static void
275test_ncp_default(void **state)
276{
277 bool have_chacha = cipher_valid("CHACHA20-POLY1305");
278
279 struct options o = { 0 };
280
281 o.gc = gc_new();
282
283 /* no user specified string */
284 o.ncp_ciphers = NULL;
286
287 if (have_chacha)
288 {
289 assert_string_equal(o.ncp_ciphers, "AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305");
290 }
291 else
292 {
293 assert_string_equal(o.ncp_ciphers, "AES-256-GCM:AES-128-GCM");
294 }
295 assert_string_equal(o.ncp_ciphers_conf, "DEFAULT");
296
297 /* check that a default string is replaced with DEFAULT */
298 if (have_chacha)
299 {
300 o.ncp_ciphers = "AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305";
301 }
302 else
303 {
304 o.ncp_ciphers = "AES-256-GCM:AES-128-GCM";
305 }
306
308 assert_string_equal(o.ncp_ciphers_conf, "DEFAULT");
309
310 /* test default in the middle of the string */
311 o.ncp_ciphers = "BF-CBC:DEFAULT:AES-128-CBC:AES-256-CBC";
313
314 if (have_chacha)
315 {
316 assert_string_equal(o.ncp_ciphers, "BF-CBC:AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305:AES-128-CBC:AES-256-CBC");
317 }
318 else
319 {
320 assert_string_equal(o.ncp_ciphers, "BF-CBC:AES-256-GCM:AES-128-GCM:AES-128-CBC:AES-256-CBC");
321 }
322 assert_string_equal(o.ncp_ciphers_conf, "BF-CBC:DEFAULT:AES-128-CBC:AES-256-CBC");
323
324 /* string at the beginning */
325 o.ncp_ciphers = "DEFAULT:AES-128-CBC:AES-192-CBC";
327
328 if (have_chacha)
329 {
330 assert_string_equal(o.ncp_ciphers, "AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305:AES-128-CBC:AES-192-CBC");
331 }
332 else
333 {
334 assert_string_equal(o.ncp_ciphers, "AES-256-GCM:AES-128-GCM:AES-128-CBC:AES-192-CBC");
335 }
336 assert_string_equal(o.ncp_ciphers_conf, "DEFAULT:AES-128-CBC:AES-192-CBC");
337
338 /* DEFAULT at the end */
339 o.ncp_ciphers = "AES-192-GCM:AES-128-CBC:DEFAULT";
341
342 if (have_chacha)
343 {
344 assert_string_equal(o.ncp_ciphers, "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:1033
static struct gc_arena gc_new(void)
Definition buffer.h:1025
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:392
#define ASSERT(x)
Definition error.h:195
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:250
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:229
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:591
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:634
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:80
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:98
Garbage collection arena used to keep track of dynamically allocated memory.
Definition buffer.h:117
const char * ncp_ciphers_conf
The original ncp_ciphers specified by the user in the configuration.
Definition options.h:577
const char * ncp_ciphers
Definition options.h:578
struct gc_arena gc
Definition options.h:251
Security parameter state of a single session within a VPN tunnel.
Definition ssl_common.h:483
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:36
const char * aes_ciphers
Definition test_ncp.c:44
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:50
const char * bf_chacha
Definition test_ncp.c:42
static void test_extract_client_ciphers(void **state)
Definition test_ncp.c:149
static void test_ncp_default(void **state)
Definition test_ncp.c:275
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:69
const char * aes_chacha
Definition test_ncp.c:43
int main(void)
Definition test_ncp.c:407
static void test_poor_man(void **state)
Definition test_ncp.c:188
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:155