Browse code

Allow DEFAULT in data-ciphers and report both expanded and user set option

This adds support for parsing DEFAULT in data-ciphers, the idea is that people
can modify the default without repeating the default ciphers.

In the past we have seem that people will use data-ciphers BF-CBC or
data-ciphers AES-128-CBC when getting the warning that the cipher is not
supported by the server. This commit aims to provide a better way for
these situation as we still want people to rely on default cipher selection
from OpenVPN when possible.

Change-Id: Ia1c5209022d3ab4c0dac6438c41891c7d059f812
Signed-off-by: Arne Schwabe <arne@rfc2549.org>
Acked-by: Frank Lichtenheld <frank@lichtenheld.com>
Message-Id: <20241227124632.110920-1-frank@lichtenheld.com>
URL: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg30245.html
Signed-off-by: Gert Doering <gert@greenie.muc.de>

Arne Schwabe authored on 2024/12/27 21:46:32
Showing 8 changed files
... ...
@@ -30,6 +30,12 @@ Enforcement of AES-GCM usage limit
30 30
 
31 31
     https://datatracker.ietf.org/doc/draft-irtf-cfrg-aead-limits/
32 32
 
33
+Default ciphers in ``--data-ciphers``
34
+    Ciphers in ``--data-ciphers`` can contain the string DEFAULT that is
35
+    replaced by the default ciphers used by OpenVPN, making it easier to
36
+    add an allowed cipher without having to spell out the default ciphers.
37
+
38
+
33 39
 Deprecated features
34 40
 -------------------
35 41
 ``secret`` support has been removed by default.
... ...
@@ -178,6 +178,12 @@ configured in a compatible way between both the local and remote side.
178 178
   Chacha20-Poly1305 if the underlying SSL library (and its configuration)
179 179
   supports it.
180 180
 
181
+  Starting with OpenVPN 2.7 the special keyword :code:`DEFAULT` can be used
182
+  in the string and is replaced by the default ciphers.  This can be used to
183
+  add an additional allowed cipher to the allowed ciphers, e.g.
184
+  :code:`DEFAULT:AES-192-CBC` to use the default ciphers but also allow
185
+  :code:`AES-192-CBC`.
186
+
181 187
   Cipher negotiation is enabled in client-server mode only. I.e. if
182 188
   ``--mode`` is set to `server` (server-side, implied by setting
183 189
   ``--server`` ), or if ``--pull`` is specified (client-side, implied by
... ...
@@ -1897,14 +1897,15 @@ multi_client_set_protocol_options(struct context *c)
1897 1897
     if (strlen(peer_ciphers) > 0)
1898 1898
     {
1899 1899
         msg(M_INFO, "PUSH: No common cipher between server and client. "
1900
-            "Server data-ciphers: '%s', client supported ciphers '%s'",
1901
-            o->ncp_ciphers, peer_ciphers);
1900
+            "Server data-ciphers: '%s'%s, client supported ciphers '%s'",
1901
+            o->ncp_ciphers_conf, ncp_expanded_ciphers(o, &gc), peer_ciphers);
1902 1902
     }
1903 1903
     else if (tls_multi->remote_ciphername)
1904 1904
     {
1905 1905
         msg(M_INFO, "PUSH: No common cipher between server and client. "
1906
-            "Server data-ciphers: '%s', client supports cipher '%s'",
1907
-            o->ncp_ciphers, tls_multi->remote_ciphername);
1906
+            "Server data-ciphers: '%s'%s, client supports cipher '%s'",
1907
+            o->ncp_ciphers_conf, ncp_expanded_ciphers(o, &gc),
1908
+            tls_multi->remote_ciphername);
1908 1909
     }
1909 1910
     else
1910 1911
     {
... ...
@@ -3486,40 +3486,6 @@ options_postprocess_verify(const struct options *o)
3486 3486
     }
3487 3487
 }
3488 3488
 
3489
-
3490
-/**
3491
- * Checks for availibility of Chacha20-Poly1305 and sets
3492
- * the ncp_cipher to either AES-256-GCM:AES-128-GCM or
3493
- * AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305.
3494
- */
3495
-static void
3496
-options_postprocess_setdefault_ncpciphers(struct options *o)
3497
-{
3498
-    if (o->ncp_ciphers)
3499
-    {
3500
-        /* custom --data-ciphers set, keep list */
3501
-        return;
3502
-    }
3503
-
3504
-    /* check if crypto library supports chacha */
3505
-    bool can_do_chacha = cipher_valid("CHACHA20-POLY1305");
3506
-
3507
-    if (can_do_chacha && dco_enabled(o))
3508
-    {
3509
-        /* also make sure that dco supports chacha */
3510
-        can_do_chacha = tls_item_in_cipher_list("CHACHA20-POLY1305", dco_get_supported_ciphers());
3511
-    }
3512
-
3513
-    if (can_do_chacha)
3514
-    {
3515
-        o->ncp_ciphers = "AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305";
3516
-    }
3517
-    else
3518
-    {
3519
-        o->ncp_ciphers = "AES-256-GCM:AES-128-GCM";
3520
-    }
3521
-}
3522
-
3523 3489
 static void
3524 3490
 options_postprocess_cipher(struct options *o)
3525 3491
 {
... ...
@@ -3550,7 +3516,8 @@ options_postprocess_cipher(struct options *o)
3550 3550
             "defaulted to BF-CBC as fallback when cipher negotiation "
3551 3551
             "failed in this case. If you need this fallback please add "
3552 3552
             "'--data-ciphers-fallback BF-CBC' to your configuration "
3553
-            "and/or add BF-CBC to --data-ciphers.");
3553
+            "and/or add BF-CBC to --data-ciphers. E.g. "
3554
+            "--data-ciphers %s:BF-CBC", o->ncp_ciphers_conf);
3554 3555
     }
3555 3556
     else if (!o->enable_ncp_fallback
3556 3557
              && !tls_item_in_cipher_list(o->ciphername, o->ncp_ciphers))
... ...
@@ -3558,7 +3525,7 @@ options_postprocess_cipher(struct options *o)
3558 3558
         msg(M_WARN, "DEPRECATED OPTION: --cipher set to '%s' but missing in "
3559 3559
             "--data-ciphers (%s). OpenVPN ignores --cipher for cipher "
3560 3560
             "negotiations. ",
3561
-            o->ciphername, o->ncp_ciphers);
3561
+            o->ciphername, o->ncp_ciphers_conf);
3562 3562
     }
3563 3563
 }
3564 3564
 
... ...
@@ -559,6 +559,8 @@ struct options
559 559
     const char *ciphername;
560 560
     bool enable_ncp_fallback;      /**< If defined fall back to
561 561
                                    * ciphername if NCP fails */
562
+    /** The original ncp_ciphers specified by the user in the configuration*/
563
+    const char *ncp_ciphers_conf;
562 564
     const char *ncp_ciphers;
563 565
     const char *authname;
564 566
     const char *engine;
... ...
@@ -40,6 +40,8 @@
40 40
 #include "config.h"
41 41
 #endif
42 42
 
43
+#include <string.h>
44
+
43 45
 #include "syshead.h"
44 46
 #include "win32.h"
45 47
 
... ...
@@ -223,7 +225,6 @@ tls_item_in_cipher_list(const char *item, const char *list)
223 223
 
224 224
     return token != NULL;
225 225
 }
226
-
227 226
 const char *
228 227
 tls_peer_ncp_list(const char *peer_info, struct gc_arena *gc)
229 228
 {
... ...
@@ -335,15 +336,16 @@ check_pull_client_ncp(struct context *c, const int found)
335 335
         return true;
336 336
     }
337 337
 
338
-    /* We failed negotiation, give appropiate error message */
338
+    /* We failed negotiation, give appropriate error message */
339 339
     if (c->c2.tls_multi->remote_ciphername)
340 340
     {
341 341
         msg(D_TLS_ERRORS, "OPTIONS ERROR: failed to negotiate "
342 342
             "cipher with server.  Add the server's "
343
-            "cipher ('%s') to --data-ciphers (currently '%s') if "
344
-            "you want to connect to this server.",
343
+            "cipher ('%s') to --data-ciphers (currently '%s'), e.g."
344
+            "--data-ciphers %s:%s if you want to connect to this server.",
345 345
             c->c2.tls_multi->remote_ciphername,
346
-            c->options.ncp_ciphers);
346
+            c->options.ncp_ciphers_conf, c->options.ncp_ciphers_conf,
347
+            c->c2.tls_multi->remote_ciphername);
347 348
         return false;
348 349
 
349 350
     }
... ...
@@ -517,10 +519,13 @@ check_session_cipher(struct tls_session *session, struct options *options)
517 517
     if (!session->opt->server && !cipher_allowed_as_fallback
518 518
         && !tls_item_in_cipher_list(options->ciphername, options->ncp_ciphers))
519 519
     {
520
-        msg(D_TLS_ERRORS, "Error: negotiated cipher not allowed - %s not in %s",
521
-            options->ciphername, options->ncp_ciphers);
520
+        struct gc_arena gc = gc_new();
521
+        msg(D_TLS_ERRORS, "Error: negotiated cipher not allowed - %s not in %s%s",
522
+            options->ciphername, options->ncp_ciphers_conf,
523
+            ncp_expanded_ciphers(options, &gc));
522 524
         /* undo cipher push, abort connection setup */
523 525
         options->ciphername = session->opt->config_ciphername;
526
+        gc_free(&gc);
524 527
         return false;
525 528
     }
526 529
     else
... ...
@@ -528,3 +533,100 @@ check_session_cipher(struct tls_session *session, struct options *options)
528 528
         return true;
529 529
     }
530 530
 }
531
+
532
+/**
533
+ * Replaces the string DEFAULT with the string \c replace.
534
+ *
535
+ * @param o         Options struct to modify and to use the gc from
536
+ * @param replace   string used to replace the DEFAULT string
537
+ */
538
+static void
539
+replace_default_in_ncp_ciphers_option(struct options *o, const char *replace)
540
+{
541
+    const char *search = "DEFAULT";
542
+    const int ncp_ciphers_len = strlen(o->ncp_ciphers) + strlen(replace) - strlen(search) + 1;
543
+
544
+    uint8_t *ncp_ciphers = gc_malloc(ncp_ciphers_len, true, &o->gc);
545
+
546
+    struct buffer ncp_ciphers_buf;
547
+    buf_set_write(&ncp_ciphers_buf, ncp_ciphers, ncp_ciphers_len);
548
+
549
+    const char *def = strstr(o->ncp_ciphers, search);
550
+
551
+    /* Copy everything before the DEFAULT string */
552
+    buf_write(&ncp_ciphers_buf, o->ncp_ciphers, def - o->ncp_ciphers);
553
+
554
+    /* copy the default string. */
555
+    buf_write(&ncp_ciphers_buf, replace, strlen(replace));
556
+
557
+    /* copy the rest of the ncp cipher string */
558
+    const char *after_default = def + strlen(search);
559
+    buf_write(&ncp_ciphers_buf, after_default, strlen(after_default));
560
+
561
+    o->ncp_ciphers = (char *) ncp_ciphers;
562
+}
563
+
564
+/**
565
+ * Checks for availibility of Chacha20-Poly1305 and sets
566
+ * the ncp_cipher to either AES-256-GCM:AES-128-GCM or
567
+ * AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305.
568
+ */
569
+void
570
+options_postprocess_setdefault_ncpciphers(struct options *o)
571
+{
572
+    bool default_in_cipher_list = o->ncp_ciphers
573
+                                  && tls_item_in_cipher_list("DEFAULT", o->ncp_ciphers);
574
+
575
+    /* preserve the values that the user put into the configuration */
576
+    o->ncp_ciphers_conf = o->ncp_ciphers;
577
+
578
+    /* check if crypto library supports chacha */
579
+    bool can_do_chacha = cipher_valid("CHACHA20-POLY1305");
580
+
581
+    if (can_do_chacha && dco_enabled(o))
582
+    {
583
+        /* also make sure that dco supports chacha */
584
+        can_do_chacha = tls_item_in_cipher_list("CHACHA20-POLY1305", dco_get_supported_ciphers());
585
+    }
586
+
587
+    const char *default_ciphers = "AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305";
588
+
589
+    if (!can_do_chacha)
590
+    {
591
+        default_ciphers = "AES-256-GCM:AES-128-GCM";
592
+    }
593
+
594
+    /* want to rather print DEFAULT instead of a manually set default list */
595
+    if (!o->ncp_ciphers_conf || !strcmp(default_ciphers, o->ncp_ciphers_conf))
596
+    {
597
+        o->ncp_ciphers = default_ciphers;
598
+        o->ncp_ciphers_conf = "DEFAULT";
599
+    }
600
+    else if (!default_in_cipher_list)
601
+    {
602
+        /* custom cipher list without DEFAULT string in it,
603
+         * nothing to replace/mutate */
604
+        return;
605
+    }
606
+    else
607
+    {
608
+        replace_default_in_ncp_ciphers_option(o, default_ciphers);
609
+    }
610
+}
611
+
612
+const char *
613
+ncp_expanded_ciphers(struct options *o, struct gc_arena *gc)
614
+{
615
+    if (!strcmp(o->ncp_ciphers, o->ncp_ciphers_conf))
616
+    {
617
+        /* expanded ciphers and user set ciphers are identical, no need to
618
+         * add an expanded version */
619
+        return "";
620
+    }
621
+
622
+    /* two extra brackets, one space, NUL byte */
623
+    struct buffer expanded_ciphers_buf = alloc_buf_gc(strlen(o->ncp_ciphers) + 4, gc);
624
+
625
+    buf_printf(&expanded_ciphers_buf, " (%s)", o->ncp_ciphers);
626
+    return BSTR(&expanded_ciphers_buf);
627
+}
... ...
@@ -157,4 +157,23 @@ get_p2p_ncp_cipher(struct tls_session *session, const char *peer_info,
157 157
 bool
158 158
 check_session_cipher(struct tls_session *session, struct options *options);
159 159
 
160
+/**
161
+ * Checks for availability of Chacha20-Poly1305 and sets
162
+ * the ncp_cipher to either AES-256-GCM:AES-128-GCM or
163
+ * AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305 if not set.
164
+ *
165
+ * If DEFAULT is in the ncp_cipher string, it will be replaced
166
+ * by the default cipher string as defined above.
167
+ */
168
+void
169
+options_postprocess_setdefault_ncpciphers(struct options *o);
170
+
171
+/** returns the o->ncp_ciphers in brackets, e.g.
172
+ *  (AES-256-GCM:CHACHA20-POLY1305) if o->ncp_ciphers_conf
173
+ *  and o->ncp_ciphers differ, otherwise an empty string
174
+ *
175
+ *  The returned string will be allocated in the passed \c gc
176
+ */
177
+const char *
178
+ncp_expanded_ciphers(struct options *o, struct gc_arena *gc);
160 179
 #endif /* ifndef OPENVPN_SSL_NCP_H */
... ...
@@ -54,6 +54,17 @@ key_state_export_keying_material(struct tls_session *session,
54 54
     ASSERT(0);
55 55
 }
56 56
 
57
+
58
+/* Define a dummy dco cipher option to avoid linking against all the DCO
59
+ * units */
60
+#if defined(ENABLE_DCO)
61
+const char *
62
+dco_get_supported_ciphers(void)
63
+{
64
+    return "AES-192-GCM:AES-128-CBC:AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305";
65
+}
66
+#endif
67
+
57 68
 static void
58 69
 test_check_ncp_ciphers_list(void **state)
59 70
 {
... ...
@@ -260,13 +271,135 @@ test_ncp_best(void **state)
260 260
     gc_free(&gc);
261 261
 }
262 262
 
263
+static void
264
+test_ncp_default(void **state)
265
+{
266
+    bool have_chacha = cipher_valid("CHACHA20-POLY1305");
267
+
268
+    struct options o = { 0 };
269
+
270
+    o.gc = gc_new();
271
+
272
+    /* no user specified string */
273
+    o.ncp_ciphers = NULL;
274
+    options_postprocess_setdefault_ncpciphers(&o);
275
+
276
+    if (have_chacha)
277
+    {
278
+        assert_string_equal(o.ncp_ciphers, "AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305");
279
+    }
280
+    else
281
+    {
282
+        assert_string_equal(o.ncp_ciphers, "AES-256-GCM:AES-128-GCM");
283
+    }
284
+    assert_string_equal(o.ncp_ciphers_conf, "DEFAULT");
285
+
286
+    /* check that a default string is replaced with DEFAULT */
287
+    if (have_chacha)
288
+    {
289
+        o.ncp_ciphers = "AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305";
290
+    }
291
+    else
292
+    {
293
+        o.ncp_ciphers = "AES-256-GCM:AES-128-GCM";
294
+    }
295
+
296
+    options_postprocess_setdefault_ncpciphers(&o);
297
+    assert_string_equal(o.ncp_ciphers_conf, "DEFAULT");
298
+
299
+    /* test default in the middle of the string */
300
+    o.ncp_ciphers = "BF-CBC:DEFAULT:AES-128-CBC:AES-256-CBC";
301
+    options_postprocess_setdefault_ncpciphers(&o);
302
+
303
+    if (have_chacha)
304
+    {
305
+        assert_string_equal(o.ncp_ciphers, "BF-CBC:AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305:AES-128-CBC:AES-256-CBC");
306
+    }
307
+    else
308
+    {
309
+        assert_string_equal(o.ncp_ciphers, "BF-CBC:AES-256-GCM:AES-128-GCM:AES-128-CBC:AES-256-CBC");
310
+    }
311
+    assert_string_equal(o.ncp_ciphers_conf, "BF-CBC:DEFAULT:AES-128-CBC:AES-256-CBC");
312
+
313
+    /* string at the beginning */
314
+    o.ncp_ciphers = "DEFAULT:AES-128-CBC:AES-192-CBC";
315
+    options_postprocess_setdefault_ncpciphers(&o);
316
+
317
+    if (have_chacha)
318
+    {
319
+        assert_string_equal(o.ncp_ciphers, "AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305:AES-128-CBC:AES-192-CBC");
320
+    }
321
+    else
322
+    {
323
+        assert_string_equal(o.ncp_ciphers, "AES-256-GCM:AES-128-GCM:AES-128-CBC:AES-192-CBC");
324
+    }
325
+    assert_string_equal(o.ncp_ciphers_conf, "DEFAULT:AES-128-CBC:AES-192-CBC");
326
+
327
+    /* DEFAULT at the end */
328
+    o.ncp_ciphers = "AES-192-GCM:AES-128-CBC:DEFAULT";
329
+    options_postprocess_setdefault_ncpciphers(&o);
330
+
331
+    if (have_chacha)
332
+    {
333
+        assert_string_equal(o.ncp_ciphers, "AES-192-GCM:AES-128-CBC:AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305");
334
+    }
335
+    else
336
+    {
337
+        assert_string_equal(o.ncp_ciphers, "AES-192-GCM:AES-128-CBC:AES-256-GCM:AES-128-GCM");
338
+    }
339
+    assert_string_equal(o.ncp_ciphers_conf, "AES-192-GCM:AES-128-CBC:DEFAULT");
340
+
341
+    gc_free(&o.gc);
342
+}
343
+
344
+static void
345
+test_ncp_expand(void **state)
346
+{
347
+    bool have_chacha = cipher_valid("CHACHA20-POLY1305");
348
+    struct options o = {0};
349
+
350
+    o.gc = gc_new();
351
+    struct gc_arena gc = gc_new();
352
+
353
+    /* no user specified string */
354
+    o.ncp_ciphers = NULL;
355
+    options_postprocess_setdefault_ncpciphers(&o);
356
+
357
+    const char *expanded = ncp_expanded_ciphers(&o, &gc);
358
+
359
+    /* user specificed string with DEFAULT in it */
360
+    o.ncp_ciphers = "AES-192-GCM:DEFAULT";
361
+    options_postprocess_setdefault_ncpciphers(&o);
362
+    const char *expanded2 = ncp_expanded_ciphers(&o, &gc);
363
+
364
+    if (have_chacha)
365
+    {
366
+        assert_string_equal(expanded, " (AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305)");
367
+        assert_string_equal(expanded2, " (AES-192-GCM:AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305)");
368
+    }
369
+    else
370
+    {
371
+        assert_string_equal(expanded, " (AES-256-GCM:AES-128-GCM)");
372
+        assert_string_equal(expanded2, " (AES-192-GCM:AES-256-GCM:AES-128-GCM)");
373
+    }
374
+
375
+    o.ncp_ciphers = "AES-192-GCM:BF-CBC";
376
+    options_postprocess_setdefault_ncpciphers(&o);
377
+
378
+    assert_string_equal(ncp_expanded_ciphers(&o, &gc), "");
379
+
380
+    gc_free(&o.gc);
381
+    gc_free(&gc);
382
+}
263 383
 
264 384
 
265 385
 const struct CMUnitTest ncp_tests[] = {
266 386
     cmocka_unit_test(test_check_ncp_ciphers_list),
267 387
     cmocka_unit_test(test_extract_client_ciphers),
268 388
     cmocka_unit_test(test_poor_man),
269
-    cmocka_unit_test(test_ncp_best)
389
+    cmocka_unit_test(test_ncp_best),
390
+    cmocka_unit_test(test_ncp_default),
391
+    cmocka_unit_test(test_ncp_expand),
270 392
 };
271 393
 
272 394