From patchwork Wed Jul 4 07:53:56 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Steffan Karger X-Patchwork-Id: 400 Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director7.mail.ord1d.rsapps.net ([172.27.255.53]) by backend30.mail.ord1d.rsapps.net (Dovecot) with LMTP id 0wrMEhYKPVvCIQAAIUCqbw for ; Wed, 04 Jul 2018 13:55:34 -0400 Received: from proxy10.mail.iad3a.rsapps.net ([172.27.255.53]) by director7.mail.ord1d.rsapps.net (Dovecot) with LMTP id oceYAhYKPVtlaQAAovjBpQ ; Wed, 04 Jul 2018 13:55:34 -0400 Received: from smtp40.gate.iad3a ([172.27.255.53]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy10.mail.iad3a.rsapps.net with LMTP id 8Eo0CRYKPVuJKQAAnQ/bqA ; Wed, 04 Jul 2018 13:55:34 -0400 X-Spam-Threshold: 95 X-Spam-Score: 0 X-Spam-Flag: NO X-Virus-Scanned: OK X-Orig-To: openvpnslackdevel@openvpn.net X-Originating-Ip: [216.105.38.7] Authentication-Results: smtp40.gate.iad3a.rsapps.net; iprev=pass policy.iprev="216.105.38.7"; spf=pass smtp.mailfrom="openvpn-devel-bounces@lists.sourceforge.net" smtp.helo="lists.sourceforge.net"; dkim=fail (signature verification failed) header.d=sourceforge.net; dkim=fail (signature verification failed) header.d=sf.net; dkim=fail (signature verification failed) header.d=karger-me.20150623.gappssmtp.com; dmarc=none (p=nil; dis=none) header.from=karger.me X-Suspicious-Flag: YES X-Classification-ID: 724008f0-7fb3-11e8-bb4b-5254003a14f9-1-1 Received: from [216.105.38.7] ([216.105.38.7:20770] helo=lists.sourceforge.net) by smtp40.gate.iad3a.rsapps.net (envelope-from ) (ecelerity 4.2.1.56364 r(Core:4.2.1.14)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id 37/9B-23169-51A0D3B5; Wed, 04 Jul 2018 13:55:33 -0400 Received: from [127.0.0.1] (helo=sfs-ml-4.v29.lw.sourceforge.com) by sfs-ml-4.v29.lw.sourceforge.com with esmtp (Exim 4.90_1) (envelope-from ) id 1falzL-0001Pb-Lz; Wed, 04 Jul 2018 17:54:47 +0000 Received: from [172.30.20.202] (helo=mx.sourceforge.net) by sfs-ml-4.v29.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.90_1) (envelope-from ) id 1falzG-0001PH-S3 for openvpn-devel@lists.sourceforge.net; Wed, 04 Jul 2018 17:54:42 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=References:In-Reply-To:Message-Id:Date:Subject:Cc: To:From:Sender:Reply-To:MIME-Version:Content-Type:Content-Transfer-Encoding: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=tRBxEsqAf8VJXH7g6B3ZWfep007e/7QUYFvKvmttdLg=; b=C9wiwVdOngvhHhJXL9AwIEXEt8 TIZNvRtcNBMHZaRD9uiviJBbgTrtuHvspINPX5Ar6joD8nt69ry/06EDkKoI9tw1Km678KQxyGdLO ediOB56JDhSCLWSETtsy8ug7lmVI7JT5JWTcyXVGvEaXceaOJw1zsXC0gD1J0rlUQr8Y=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=References:In-Reply-To:Message-Id:Date:Subject:Cc:To:From:Sender:Reply-To :MIME-Version:Content-Type:Content-Transfer-Encoding:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=tRBxEsqAf8VJXH7g6B3ZWfep007e/7QUYFvKvmttdLg=; b=HOLjmPjnGfPeRgrYrhmJex28Zw 3nqnIBZij+wr4DhYX6XvYhVoAoISK5p53vUFTLG/jIR6uKhay+9XyzDhxd2Lzw5v+W5xLTflno/nz 0YyUWUNOpPYP2PKqXDK747u4F5rkSKfiVrOxxIkhHF27VP8v7jREzg+NG8SHfDI/Ypg0=; Received: from mail-ed1-f51.google.com ([209.85.208.51]) by sfi-mx-1.v28.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES128-GCM-SHA256:128) (Exim 4.90_1) id 1falzD-00HLKe-Jn for openvpn-devel@lists.sourceforge.net; Wed, 04 Jul 2018 17:54:42 +0000 Received: by mail-ed1-f51.google.com with SMTP id v22-v6so4572224edq.4 for ; Wed, 04 Jul 2018 10:54:39 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=karger-me.20150623.gappssmtp.com; s=20150623; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=tRBxEsqAf8VJXH7g6B3ZWfep007e/7QUYFvKvmttdLg=; b=nJ6ibNLWnw2yKnEc4JUv3CGocxBlqiNCSZhtyp1CyGGrO2OUW2hVwHDBOPQNS8QMNu gWAQdvcqExCpkljmvFZ1hKhLJsbss28TXhJm0ezCR0vLNoQ3wX+a7QfnHbAsbcrMmPWG PkILSORGG+7Dx5aTXVgCvp4dcZqUtfU+gW7laTWhi07Wbt3K9e4xTSqrJ+45aH6n389p tfelODQx5yNhrZw++b65IsGOLvMp5Yt0rjm+A3+yuCPSmSEIozOxJAeLHG4VP602z4zv uzJSv33ayZeDCYpRyW0DSP0NUn8zsPQ6wrLPZFTYX6H+GUZ3NoiaSIKLfj0gC+fVvZji NS7g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=tRBxEsqAf8VJXH7g6B3ZWfep007e/7QUYFvKvmttdLg=; b=uXkllyUI8cAYLF6KWWQia193DxFkwW2u45JbCVYml6+uQHTGYtjPXS1Rl2eWDKEtFf J6XACS0tyJkXdVhb1x+N+8UB5IlQcIKDo2hdfCrtOzFw1S06N5JbxjdMv2mC2WScZNCc VkPi2oyskUfZoSyKe8hTfNzPqEfYYqboS0jLWLjZEbmCLeC0TIm+UBKdsODc2EqY0DEF Sv8fu+zk2nn36pvqNo2zzVYs8BY3n0AGrQorIZU14t0CbrDArx48FcavxCwXtGDcvfmz bysOaU7tv+mP+7W/MSpfQ/oCFgD2o9k93mPFlX8wb8Nr7j+tRu0dRlxmVIqxrcIhmurD JqwQ== X-Gm-Message-State: APt69E0NJW5gTBsIDXPZ42HqDKlsThbxwxXllfYHif5PfLTdgnKyq/kN JUkY6bkKcZDdcwW/p+DAC796nUnt+Uw= X-Google-Smtp-Source: AAOMgpeuESev9LM8JGie4/rParosvJgo78EC3I+koSxeE49ODv3JLorPgLzIWUphlAWBhL3XjvEiQQ== X-Received: by 2002:a50:f577:: with SMTP id w52-v6mr3818522edm.230.1530726872399; Wed, 04 Jul 2018 10:54:32 -0700 (PDT) Received: from vesta.fritz.box ([2001:985:e54:1:f598:331e:3cdf:2649]) by smtp.gmail.com with ESMTPSA id o2-v6sm1948961edd.84.2018.07.04.10.54.31 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Wed, 04 Jul 2018 10:54:31 -0700 (PDT) From: Steffan Karger To: openvpn-devel@lists.sourceforge.net Date: Wed, 4 Jul 2018 19:53:56 +0200 Message-Id: <20180704175404.22371-1-steffan@karger.me> X-Mailer: git-send-email 2.17.1 In-Reply-To: <1512734870-17133-1-git-send-email-steffan.karger@fox-it.com> References: <1512734870-17133-1-git-send-email-steffan.karger@fox-it.com> X-Spam-Report: Spam Filtering performed by mx.sourceforge.net. See http://spamassassin.org/tag/ for more details. -0.0 RCVD_IN_DNSWL_NONE RBL: Sender listed at http://www.dnswl.org/, no trust [209.85.208.51 listed in list.dnswl.org] -0.0 RCVD_IN_MSPIKE_H2 RBL: Average reputation (+2) [209.85.208.51 listed in wl.mailspike.net] -0.0 SPF_PASS SPF: sender matches SPF record 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature -0.0 T_DKIMWL_WL_MED DKIMwl.org - Whitelisted Medium sender X-Headers-End: 1falzD-00HLKe-Jn Subject: [Openvpn-devel] [PATCH v2 1/9] Move file-related functions from misc.c to platform.c X-BeenThere: openvpn-devel@lists.sourceforge.net X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox From: Steffan Karger To avoid having to include misc.c - which is a dependency mess - in the tls-crypt unit tests, move file-handing related functions to platform.c (which is where other file-related functions already reside). Note that platform_create_temp_file() needs random. To avoid including misc.c in other tests that use platform.c, add a mock get_random(). (Almost every test includes platform.c, because buffer.c depends on it. That smells like it needs cleanup too, but not in this patch set.) Signed-off-by: Steffan Karger Acked-by: Gert Doering --- src/openvpn/init.c | 2 +- src/openvpn/misc.c | 148 --------------------- src/openvpn/misc.h | 12 -- src/openvpn/multi.c | 25 ++-- src/openvpn/pf.c | 4 +- src/openvpn/platform.c | 148 +++++++++++++++++++++ src/openvpn/platform.h | 18 +++ src/openvpn/plugin.c | 6 +- src/openvpn/ssl_verify.c | 12 +- tests/unit_tests/openvpn/Makefile.am | 4 + tests/unit_tests/openvpn/mock_get_random.c | 36 +++++ 11 files changed, 233 insertions(+), 182 deletions(-) create mode 100644 tests/unit_tests/openvpn/mock_get_random.c diff --git a/src/openvpn/init.c b/src/openvpn/init.c index b748357d..f6c8f08e 100644 --- a/src/openvpn/init.c +++ b/src/openvpn/init.c @@ -807,7 +807,7 @@ init_static(void) #ifdef STATUS_PRINTF_TEST { struct gc_arena gc = gc_new(); - const char *tmp_file = create_temp_file("/tmp", "foo", &gc); + const char *tmp_file = platform_create_temp_file("/tmp", "foo", &gc); struct status_output *so = status_open(tmp_file, 0, -1, NULL, STATUS_OUTPUT_WRITE); status_printf(so, "%s", "foo"); status_printf(so, "%s", "bar"); diff --git a/src/openvpn/misc.c b/src/openvpn/misc.c index fc68027f..85cdc95d 100644 --- a/src/openvpn/misc.c +++ b/src/openvpn/misc.c @@ -313,87 +313,6 @@ openvpn_popen(const struct argv *a, const struct env_set *es) return ret; } - -/* return true if filename can be opened for read */ -bool -test_file(const char *filename) -{ - bool ret = false; - if (filename) - { - FILE *fp = platform_fopen(filename, "r"); - if (fp) - { - fclose(fp); - ret = true; - } - else - { - if (openvpn_errno() == EACCES) - { - msg( M_WARN | M_ERRNO, "Could not access file '%s'", filename); - } - } - } - - dmsg(D_TEST_FILE, "TEST FILE '%s' [%d]", - filename ? filename : "UNDEF", - ret); - - return ret; -} - -/* create a temporary filename in directory */ -const char * -create_temp_file(const char *directory, const char *prefix, struct gc_arena *gc) -{ - int fd; - const char *retfname = NULL; - unsigned int attempts = 0; - char fname[256] = { 0 }; - const char *fname_fmt = PACKAGE "_%.*s_%08lx%08lx.tmp"; - const int max_prefix_len = sizeof(fname) - (sizeof(PACKAGE) + 7 + (2 * 8)); - - while (attempts < 6) - { - ++attempts; - - if (!openvpn_snprintf(fname, sizeof(fname), fname_fmt, max_prefix_len, - prefix, (unsigned long) get_random(), - (unsigned long) get_random())) - { - msg(M_WARN, "ERROR: temporary filename too long"); - return NULL; - } - - retfname = gen_path(directory, fname, gc); - if (!retfname) - { - msg(M_WARN, "Failed to create temporary filename and path"); - return NULL; - } - - /* Atomically create the file. Errors out if the file already - * exists. */ - fd = platform_open(retfname, O_CREAT | O_EXCL | O_WRONLY, S_IRUSR | S_IWUSR); - if (fd != -1) - { - close(fd); - return retfname; - } - else if (fd == -1 && errno != EEXIST) - { - /* Something else went wrong, no need to retry. */ - msg(M_WARN | M_ERRNO, "Could not create temporary file '%s'", - retfname); - return NULL; - } - } - - msg(M_WARN, "Failed to create temporary file after %i attempts", attempts); - return NULL; -} - /* * Prepend a random string to hostname to prevent DNS caching. * For example, foo.bar.gov would be modified to .foo.bar.gov. @@ -415,73 +334,6 @@ hostname_randomize(const char *hostname, struct gc_arena *gc) #undef n_rnd_bytes } -/* - * Put a directory and filename together. - */ -const char * -gen_path(const char *directory, const char *filename, struct gc_arena *gc) -{ -#ifdef _WIN32 - const int CC_PATH_RESERVED = CC_LESS_THAN|CC_GREATER_THAN|CC_COLON - |CC_DOUBLE_QUOTE|CC_SLASH|CC_BACKSLASH|CC_PIPE|CC_QUESTION_MARK|CC_ASTERISK; -#else - const int CC_PATH_RESERVED = CC_SLASH; -#endif - - if (!gc) - { - return NULL; /* Would leak memory otherwise */ - } - - const char *safe_filename = string_mod_const(filename, CC_PRINT, CC_PATH_RESERVED, '_', gc); - - if (safe_filename - && strcmp(safe_filename, ".") - && strcmp(safe_filename, "..") -#ifdef _WIN32 - && win_safe_filename(safe_filename) -#endif - ) - { - const size_t outsize = strlen(safe_filename) + (directory ? strlen(directory) : 0) + 16; - struct buffer out = alloc_buf_gc(outsize, gc); - char dirsep[2]; - - dirsep[0] = OS_SPECIFIC_DIRSEP; - dirsep[1] = '\0'; - - if (directory) - { - buf_printf(&out, "%s%s", directory, dirsep); - } - buf_printf(&out, "%s", safe_filename); - - return BSTR(&out); - } - else - { - return NULL; - } -} - -bool -absolute_pathname(const char *pathname) -{ - if (pathname) - { - const int c = pathname[0]; -#ifdef _WIN32 - return c == '\\' || (isalpha(c) && pathname[1] == ':' && pathname[2] == '\\'); -#else - return c == '/'; -#endif - } - else - { - return false; - } -} - /* * Get and store a username/password */ diff --git a/src/openvpn/misc.h b/src/openvpn/misc.h index 5680c274..c23d4cd1 100644 --- a/src/openvpn/misc.h +++ b/src/openvpn/misc.h @@ -79,18 +79,6 @@ const char **make_extended_arg_array(char **p, struct gc_arena *gc); /* an analogue to the random() function, but use OpenSSL functions if available */ long int get_random(void); -/* return true if filename can be opened for read */ -bool test_file(const char *filename); - -/* create a temporary file in directory, returns the filename of the created file */ -const char *create_temp_file(const char *directory, const char *prefix, struct gc_arena *gc); - -/* put a directory and filename together */ -const char *gen_path(const char *directory, const char *filename, struct gc_arena *gc); - -/* return true if pathname is absolute */ -bool absolute_pathname(const char *pathname); - /* prepend a random prefix to hostname */ const char *hostname_randomize(const char *hostname, struct gc_arena *gc); diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c index 3da8c110..2944eef2 100644 --- a/src/openvpn/multi.c +++ b/src/openvpn/multi.c @@ -1642,7 +1642,7 @@ multi_client_connect_post(struct multi_context *m, unsigned int *option_types_found) { /* Did script generate a dynamic config file? */ - if (test_file(dc_file)) + if (platform_test_file(dc_file)) { options_server_import(&mi->context.options, dc_file, @@ -1829,12 +1829,13 @@ multi_connection_established(struct multi_context *m, struct multi_instance *mi) { const char *ccd_file; - ccd_file = gen_path(mi->context.options.client_config_dir, - tls_common_name(mi->context.c2.tls_multi, false), - &gc); + ccd_file = platform_gen_path(mi->context.options.client_config_dir, + tls_common_name(mi->context.c2.tls_multi, + false), + &gc); /* try common-name file */ - if (test_file(ccd_file)) + if (platform_test_file(ccd_file)) { options_server_import(&mi->context.options, ccd_file, @@ -1845,11 +1846,11 @@ multi_connection_established(struct multi_context *m, struct multi_instance *mi) } else /* try default file */ { - ccd_file = gen_path(mi->context.options.client_config_dir, - CCD_DEFAULT, - &gc); + ccd_file = platform_gen_path(mi->context.options.client_config_dir, + CCD_DEFAULT, + &gc); - if (test_file(ccd_file)) + if (platform_test_file(ccd_file)) { options_server_import(&mi->context.options, ccd_file, @@ -1879,7 +1880,8 @@ multi_connection_established(struct multi_context *m, struct multi_instance *mi) if (plugin_defined(mi->context.plugins, OPENVPN_PLUGIN_CLIENT_CONNECT)) { struct argv argv = argv_new(); - const char *dc_file = create_temp_file(mi->context.options.tmp_dir, "cc", &gc); + const char *dc_file = platform_create_temp_file(mi->context.options.tmp_dir, + "cc", &gc); if (!dc_file) { @@ -1941,7 +1943,8 @@ script_depr_failed: setenv_str(mi->context.c2.es, "script_type", "client-connect"); - dc_file = create_temp_file(mi->context.options.tmp_dir, "cc", &gc); + dc_file = platform_create_temp_file(mi->context.options.tmp_dir, + "cc", &gc); if (!dc_file) { cc_succeeded = false; diff --git a/src/openvpn/pf.c b/src/openvpn/pf.c index cb22a1cb..d4e405a8 100644 --- a/src/openvpn/pf.c +++ b/src/openvpn/pf.c @@ -621,8 +621,8 @@ pf_init_context(struct context *c) #ifdef PLUGIN_PF if (plugin_defined(c->plugins, OPENVPN_PLUGIN_ENABLE_PF)) { - c->c2.pf.filename = create_temp_file(c->options.tmp_dir, "pf", - &c->c2.gc); + c->c2.pf.filename = platform_create_temp_file(c->options.tmp_dir, "pf", + &c->c2.gc); if (c->c2.pf.filename) { setenv_str(c->c2.es, "pf_file", c->c2.pf.filename); diff --git a/src/openvpn/platform.c b/src/openvpn/platform.c index fbffd0f0..5ecccf1c 100644 --- a/src/openvpn/platform.c +++ b/src/openvpn/platform.c @@ -31,6 +31,7 @@ #include "buffer.h" #include "error.h" +#include "misc.h" #include "win32.h" #include "memdbg.h" @@ -335,3 +336,150 @@ platform_stat(const char *path, platform_stat_t *buf) #endif } +/* create a temporary filename in directory */ +const char * +platform_create_temp_file(const char *directory, const char *prefix, struct gc_arena *gc) +{ + int fd; + const char *retfname = NULL; + unsigned int attempts = 0; + char fname[256] = { 0 }; + const char *fname_fmt = PACKAGE "_%.*s_%08lx%08lx.tmp"; + const int max_prefix_len = sizeof(fname) - (sizeof(PACKAGE) + 7 + (2 * 8)); + + while (attempts < 6) + { + ++attempts; + + if (!openvpn_snprintf(fname, sizeof(fname), fname_fmt, max_prefix_len, + prefix, (unsigned long) get_random(), + (unsigned long) get_random())) + { + msg(M_WARN, "ERROR: temporary filename too long"); + return NULL; + } + + retfname = platform_gen_path(directory, fname, gc); + if (!retfname) + { + msg(M_WARN, "Failed to create temporary filename and path"); + return NULL; + } + + /* Atomically create the file. Errors out if the file already + * exists. */ + fd = platform_open(retfname, O_CREAT | O_EXCL | O_WRONLY, S_IRUSR | S_IWUSR); + if (fd != -1) + { + close(fd); + return retfname; + } + else if (fd == -1 && errno != EEXIST) + { + /* Something else went wrong, no need to retry. */ + msg(M_WARN | M_ERRNO, "Could not create temporary file '%s'", + retfname); + return NULL; + } + } + + msg(M_WARN, "Failed to create temporary file after %i attempts", attempts); + return NULL; +} + +/* + * Put a directory and filename together. + */ +const char * +platform_gen_path(const char *directory, const char *filename, + struct gc_arena *gc) +{ +#ifdef _WIN32 + const int CC_PATH_RESERVED = CC_LESS_THAN|CC_GREATER_THAN|CC_COLON + |CC_DOUBLE_QUOTE|CC_SLASH|CC_BACKSLASH|CC_PIPE|CC_QUESTION_MARK|CC_ASTERISK; +#else + const int CC_PATH_RESERVED = CC_SLASH; +#endif + + if (!gc) + { + return NULL; /* Would leak memory otherwise */ + } + + const char *safe_filename = string_mod_const(filename, CC_PRINT, CC_PATH_RESERVED, '_', gc); + + if (safe_filename + && strcmp(safe_filename, ".") + && strcmp(safe_filename, "..") +#ifdef _WIN32 + && win_safe_filename(safe_filename) +#endif + ) + { + const size_t outsize = strlen(safe_filename) + (directory ? strlen(directory) : 0) + 16; + struct buffer out = alloc_buf_gc(outsize, gc); + char dirsep[2]; + + dirsep[0] = OS_SPECIFIC_DIRSEP; + dirsep[1] = '\0'; + + if (directory) + { + buf_printf(&out, "%s%s", directory, dirsep); + } + buf_printf(&out, "%s", safe_filename); + + return BSTR(&out); + } + else + { + return NULL; + } +} + +bool +platform_absolute_pathname(const char *pathname) +{ + if (pathname) + { + const int c = pathname[0]; +#ifdef _WIN32 + return c == '\\' || (isalpha(c) && pathname[1] == ':' && pathname[2] == '\\'); +#else + return c == '/'; +#endif + } + else + { + return false; + } +} + +/* return true if filename can be opened for read */ +bool +platform_test_file(const char *filename) +{ + bool ret = false; + if (filename) + { + FILE *fp = platform_fopen(filename, "r"); + if (fp) + { + fclose(fp); + ret = true; + } + else + { + if (openvpn_errno() == EACCES) + { + msg( M_WARN | M_ERRNO, "Could not access file '%s'", filename); + } + } + } + + dmsg(D_TEST_FILE, "TEST FILE '%s' [%d]", + filename ? filename : "UNDEF", + ret); + + return ret; +} diff --git a/src/openvpn/platform.h b/src/openvpn/platform.h index 288937df..091fc9c4 100644 --- a/src/openvpn/platform.h +++ b/src/openvpn/platform.h @@ -49,6 +49,7 @@ #endif #include "basic.h" +#include "buffer.h" /* Get/Set UID of process */ @@ -143,4 +144,21 @@ typedef struct stat platform_stat_t; #endif int platform_stat(const char *path, platform_stat_t *buf); +/** + * Create a temporary file in directory, returns the filename of the created + * file. + */ +const char *platform_create_temp_file(const char *directory, const char *prefix, + struct gc_arena *gc); + +/** Put a directory and filename together. */ +const char *platform_gen_path(const char *directory, const char *filename, + struct gc_arena *gc); + +/** Return true if pathname is absolute. */ +bool platform_absolute_pathname(const char *pathname); + +/** Return true if filename can be opened for read. */ +bool platform_test_file(const char *filename); + #endif /* ifndef PLATFORM_H */ diff --git a/src/openvpn/plugin.c b/src/openvpn/plugin.c index 0f091ef5..3e7d2b2a 100644 --- a/src/openvpn/plugin.c +++ b/src/openvpn/plugin.c @@ -249,7 +249,7 @@ plugin_init_item(struct plugin *p, const struct plugin_option *o) * was parsed. * */ - if (!absolute_pathname(p->so_pathname) + if (!platform_absolute_pathname(p->so_pathname) && p->so_pathname[0] != '.') { char full[PATH_MAX]; @@ -259,7 +259,7 @@ plugin_init_item(struct plugin *p, const struct plugin_option *o) } else { - rel = !absolute_pathname(p->so_pathname); + rel = !platform_absolute_pathname(p->so_pathname); p->handle = dlopen(p->so_pathname, RTLD_NOW); } if (!p->handle) @@ -271,7 +271,7 @@ plugin_init_item(struct plugin *p, const struct plugin_option *o) #else /* ifndef _WIN32 */ - rel = !absolute_pathname(p->so_pathname); + rel = !platform_absolute_pathname(p->so_pathname); p->module = LoadLibraryW(wide_string(p->so_pathname, &gc)); if (!p->module) { diff --git a/src/openvpn/ssl_verify.c b/src/openvpn/ssl_verify.c index 25395b27..a3699252 100644 --- a/src/openvpn/ssl_verify.c +++ b/src/openvpn/ssl_verify.c @@ -547,7 +547,7 @@ verify_cert_export_cert(openvpn_x509_cert_t *peercert, const char *tmp_dir, stru /* create tmp file to store peer cert */ if (!tmp_dir - || !(peercert_filename = create_temp_file(tmp_dir, "pcf", gc))) + || !(peercert_filename = platform_create_temp_file(tmp_dir, "pcf", gc))) { msg(M_NONFATAL, "Failed to create peer cert file"); return NULL; @@ -890,7 +890,7 @@ key_state_gen_auth_control_file(struct key_state *ks, const struct tls_options * struct gc_arena gc = gc_new(); key_state_rm_auth_control_file(ks); - const char *acf = create_temp_file(opt->tmp_dir, "acf", &gc); + const char *acf = platform_create_temp_file(opt->tmp_dir, "acf", &gc); if (acf) { ks->auth_control_file = string_alloc(acf, NULL); @@ -1103,7 +1103,8 @@ verify_user_pass_script(struct tls_session *session, const struct user_pass *up) { struct status_output *so; - tmp_file = create_temp_file(session->opt->tmp_dir, "up", &gc); + tmp_file = platform_create_temp_file(session->opt->tmp_dir, "up", + &gc); if (tmp_file) { so = status_open(tmp_file, 0, -1, NULL, STATUS_OUTPUT_WRITE); @@ -1513,8 +1514,9 @@ verify_final_auth_checks(struct tls_multi *multi, struct tls_session *session) struct gc_arena gc = gc_new(); const char *cn = session->common_name; - const char *path = gen_path(session->opt->client_config_dir_exclusive, cn, &gc); - if (!cn || !strcmp(cn, CCD_DEFAULT) || !test_file(path)) + const char *path = platform_gen_path(session->opt->client_config_dir_exclusive, + cn, &gc); + if (!cn || !strcmp(cn, CCD_DEFAULT) || !platform_test_file(path)) { ks->authenticated = false; wipe_auth_token(multi); diff --git a/tests/unit_tests/openvpn/Makefile.am b/tests/unit_tests/openvpn/Makefile.am index 23d758b7..db4d46e1 100644 --- a/tests/unit_tests/openvpn/Makefile.am +++ b/tests/unit_tests/openvpn/Makefile.am @@ -19,6 +19,7 @@ argv_testdriver_CFLAGS = @TEST_CFLAGS@ -I$(openvpn_srcdir) -I$(compat_srcdir) \ argv_testdriver_LDFLAGS = @TEST_LDFLAGS@ -L$(openvpn_srcdir) -Wl,--wrap=parse_line \ $(OPTIONAL_CRYPTO_LIBS) argv_testdriver_SOURCES = test_argv.c mock_msg.c \ + mock_get_random.c \ $(openvpn_srcdir)/platform.c \ $(openvpn_srcdir)/buffer.c \ $(openvpn_srcdir)/argv.c @@ -26,6 +27,7 @@ argv_testdriver_SOURCES = test_argv.c mock_msg.c \ buffer_testdriver_CFLAGS = @TEST_CFLAGS@ -I$(openvpn_srcdir) -I$(compat_srcdir) buffer_testdriver_LDFLAGS = @TEST_LDFLAGS@ -L$(openvpn_srcdir) -Wl,--wrap=parse_line buffer_testdriver_SOURCES = test_buffer.c mock_msg.c \ + mock_get_random.c \ $(openvpn_srcdir)/buffer.c \ $(openvpn_srcdir)/platform.c @@ -35,6 +37,7 @@ packet_id_testdriver_CFLAGS = @TEST_CFLAGS@ \ packet_id_testdriver_LDFLAGS = @TEST_LDFLAGS@ \ $(OPTIONAL_CRYPTO_LIBS) packet_id_testdriver_SOURCES = test_packet_id.c mock_msg.c \ + mock_get_random.c \ $(openvpn_srcdir)/buffer.c \ $(openvpn_srcdir)/otime.c \ $(openvpn_srcdir)/packet_id.c \ @@ -46,6 +49,7 @@ tls_crypt_testdriver_CFLAGS = @TEST_CFLAGS@ \ tls_crypt_testdriver_LDFLAGS = @TEST_LDFLAGS@ \ $(OPTIONAL_CRYPTO_LIBS) tls_crypt_testdriver_SOURCES = test_tls_crypt.c mock_msg.c \ + $(openvpn_srcdir)/base64.c \ $(openvpn_srcdir)/buffer.c \ $(openvpn_srcdir)/crypto.c \ $(openvpn_srcdir)/crypto_mbedtls.c \ diff --git a/tests/unit_tests/openvpn/mock_get_random.c b/tests/unit_tests/openvpn/mock_get_random.c new file mode 100644 index 00000000..da92a9bb --- /dev/null +++ b/tests/unit_tests/openvpn/mock_get_random.c @@ -0,0 +1,36 @@ +/* + * OpenVPN -- An application to securely tunnel IP networks + * over a single UDP port, with support for SSL/TLS-based + * session authentication and key exchange, + * packet encryption, packet authentication, and + * packet compression. + * + * Copyright (C) 2017 Fox Crypto B.V. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include +#include +#include + +unsigned long +get_random(void) +{ + /* rand() is not very random, but it's C99 and this is just for testing */ + return rand(); +} From patchwork Wed Jul 4 07:53:57 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Steffan Karger X-Patchwork-Id: 401 Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director9.mail.ord1d.rsapps.net ([172.27.255.57]) by backend30.mail.ord1d.rsapps.net (Dovecot) with LMTP id 5K+wAxYKPVtncQAAIUCqbw for ; Wed, 04 Jul 2018 13:55:34 -0400 Received: from proxy19.mail.iad3a.rsapps.net ([172.27.255.57]) by director9.mail.ord1d.rsapps.net (Dovecot) with LMTP id E8J9FBYKPVucLgAAalYnBA ; Wed, 04 Jul 2018 13:55:34 -0400 Received: from smtp8.gate.iad3a ([172.27.255.57]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy19.mail.iad3a.rsapps.net with LMTP id 0A2NEhYKPVs3fAAAXy6Yeg ; Wed, 04 Jul 2018 13:55:34 -0400 X-Spam-Threshold: 95 X-Spam-Score: 0 X-Spam-Flag: NO X-Virus-Scanned: OK X-Orig-To: openvpnslackdevel@openvpn.net X-Originating-Ip: [216.105.38.7] Authentication-Results: smtp8.gate.iad3a.rsapps.net; iprev=pass policy.iprev="216.105.38.7"; spf=pass smtp.mailfrom="openvpn-devel-bounces@lists.sourceforge.net" smtp.helo="lists.sourceforge.net"; dkim=fail (signature verification failed) header.d=sourceforge.net; dkim=fail (signature verification failed) header.d=sf.net; dkim=fail (signature verification failed) header.d=karger-me.20150623.gappssmtp.com; dmarc=none (p=nil; dis=none) header.from=karger.me X-Suspicious-Flag: YES X-Classification-ID: 71ec2302-7fb3-11e8-80a8-525400b8fe03-1-1 Received: from [216.105.38.7] ([216.105.38.7:7785] helo=lists.sourceforge.net) by smtp8.gate.iad3a.rsapps.net (envelope-from ) (ecelerity 4.2.1.56364 r(Core:4.2.1.14)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id 7A/6F-23534-41A0D3B5; Wed, 04 Jul 2018 13:55:33 -0400 Received: from [127.0.0.1] (helo=sfs-ml-2.v29.lw.sourceforge.com) by sfs-ml-2.v29.lw.sourceforge.com with esmtp (Exim 4.90_1) (envelope-from ) id 1falzI-0003Rr-2P; Wed, 04 Jul 2018 17:54:44 +0000 Received: from [172.30.20.202] (helo=mx.sourceforge.net) by sfs-ml-2.v29.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.90_1) (envelope-from ) id 1falzH-0003Rf-0v for openvpn-devel@lists.sourceforge.net; Wed, 04 Jul 2018 17:54:43 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=References:In-Reply-To:Message-Id:Date:Subject:Cc: To:From:Sender:Reply-To:MIME-Version:Content-Type:Content-Transfer-Encoding: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=4pXVBmKFU9uuJAfAKatsZltjKuoIM+2wNrQvOZjEmCY=; b=PJNT3jgj8vqJwn6WvvJeAYF5Iw EW5Y2vt3YeMvbgD6YiFhMF6LPctHHhkQzaN+64iAKBaP37NCLMDS/R6Kp5Nza4UhND3pGACS2Olzh 9w2VCOK0pIZv6pNzpXkpqtPOKPB5KKnFw5aD44/YsQMhP2ip/Figa50RPzNuhBXJIiU0=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=References:In-Reply-To:Message-Id:Date:Subject:Cc:To:From:Sender:Reply-To :MIME-Version:Content-Type:Content-Transfer-Encoding:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=4pXVBmKFU9uuJAfAKatsZltjKuoIM+2wNrQvOZjEmCY=; b=Q1nAr8VLTiU/VfsoI3J+Jtp2l7 H4ff6JNRq2hNNZR4LugbRvBl7lAPug4lOtmHeL8bsBqolTz8Ts+oXX972gE8qXpYlNp193ys+1oCc Ncf1d4UPHTA2LKSewpoJEoMHg8wQ9VlQ9UvzJGjpir8VJtKXRnFIS9a+t9hFMH6f9rSo=; Received: from mail-ed1-f66.google.com ([209.85.208.66]) by sfi-mx-2.v28.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES128-GCM-SHA256:128) (Exim 4.90_1) id 1falzE-00GkHu-Dw for openvpn-devel@lists.sourceforge.net; Wed, 04 Jul 2018 17:54:42 +0000 Received: by mail-ed1-f66.google.com with SMTP id g15-v6so4561457edr.12 for ; Wed, 04 Jul 2018 10:54:40 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=karger-me.20150623.gappssmtp.com; s=20150623; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=4pXVBmKFU9uuJAfAKatsZltjKuoIM+2wNrQvOZjEmCY=; b=Ojb8Hsr2eaW2NhMBxafXEViiZQDW0DFckOrt5jso+WtXMYgdshUuIodYeq7M409lmQ f6SpxjX3AanUAWLa5RK/OSz9Prm9WpC0o08L18o0gxnSvUCxm/HsksMcFhSTQDRMPHj1 HyCkDhNsIDxwIPvhXZP2pVWAaqdzdWUsAGzH78Zx9XvdAWBZuw6vKCnjROcCWLfhwUI7 aP73iXN8l02qKBCov/K9IUoGN0qwMVxsgRQwLh0DzPIAOgkWt6zf/4N1S0Q5kEElDPUZ y6Wz+CL4zWKAw+upt18ZGj+CtuhflZcc7+7jPeobO51EvFZERc5rRdG3t8sdW0xN2sb1 hdqQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=4pXVBmKFU9uuJAfAKatsZltjKuoIM+2wNrQvOZjEmCY=; b=T5z91MRswuQBFKDNlF11ITmDLtDWGt009n1gXsQPKhsz11GJzdda1ZRsOrQA1H5Iov HZ6oUjm0IFcLqxkIOAANSAHXzIhLFGVhRrhUBZN6eK1VMiOh19apEiGnvqu9xDKqeDIl 74fJNsdqI5DAfP+xHr2w/ulLv+sJ8y0fI1pPNoHCpzfi5uHk0Us15xFRNq6sUQVVzxX+ G3iBhFqC/w60/JExTxaD01Wm0DX91+FAhB0kM0s83AOMdOg580YpCkraHgPrOoms0Gsp 9RCaZFAyZQr8WUQfpEAD/mDlGucQfn+ymPzbURPNZNEwnnfSRb7ODNPLT9m3MzGImaj5 TmrA== X-Gm-Message-State: APt69E2fkJ0L5/5TwXrlWMtaV1xqjG29clcKG14FbVGeQvHkHyHsU6DX YpkgDh1O0juOkua/bV28nEVtcPRguHo= X-Google-Smtp-Source: AAOMgpeN9R+y3L39fdBaio/W7+Dbrk8VEWBSs4cPYdUgXOoAuV3aVX5O50/ScSJuXQy10+tUijK45g== X-Received: by 2002:a50:a106:: with SMTP id 6-v6mr3853368edj.12.1530726873265; Wed, 04 Jul 2018 10:54:33 -0700 (PDT) Received: from vesta.fritz.box ([2001:985:e54:1:f598:331e:3cdf:2649]) by smtp.gmail.com with ESMTPSA id o2-v6sm1948961edd.84.2018.07.04.10.54.32 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Wed, 04 Jul 2018 10:54:32 -0700 (PDT) From: Steffan Karger To: openvpn-devel@lists.sourceforge.net Date: Wed, 4 Jul 2018 19:53:57 +0200 Message-Id: <20180704175404.22371-2-steffan@karger.me> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20180704175404.22371-1-steffan@karger.me> References: <1512734870-17133-1-git-send-email-steffan.karger@fox-it.com> <20180704175404.22371-1-steffan@karger.me> X-Spam-Report: Spam Filtering performed by mx.sourceforge.net. See http://spamassassin.org/tag/ for more details. -0.0 RCVD_IN_MSPIKE_H2 RBL: Average reputation (+2) [209.85.208.66 listed in wl.mailspike.net] -0.0 RCVD_IN_DNSWL_NONE RBL: Sender listed at http://www.dnswl.org/, no trust [209.85.208.66 listed in list.dnswl.org] -0.0 SPF_PASS SPF: sender matches SPF record 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature -0.0 T_DKIMWL_WL_MED DKIMwl.org - Whitelisted Medium sender X-Headers-End: 1falzE-00GkHu-Dw Subject: [Openvpn-devel] [PATCH v2 2/9] Move execve/run_script helper functions to run_command.c X-BeenThere: openvpn-devel@lists.sourceforge.net X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox To avoid having to include misc.c - which is a dependency mess - in the tls-crypt unit tests, move the command execution helper functions to a new run_command.c module. While at it, abstract away the script_security global variable. Signed-off-by: Steffan Karger Acked-by: Antonio Quartulli --- src/openvpn/Makefile.am | 1 + src/openvpn/env_set.c | 6 +- src/openvpn/init.c | 5 +- src/openvpn/lladdr.c | 1 + src/openvpn/misc.c | 218 ------------------------------- src/openvpn/misc.h | 32 ----- src/openvpn/multi.c | 2 +- src/openvpn/options.c | 3 +- src/openvpn/route.c | 2 +- src/openvpn/run_command.c | 267 ++++++++++++++++++++++++++++++++++++++ src/openvpn/run_command.h | 63 +++++++++ src/openvpn/socket.c | 1 + src/openvpn/ssl_verify.c | 4 +- src/openvpn/tun.c | 2 +- src/openvpn/win32.c | 4 +- 15 files changed, 348 insertions(+), 263 deletions(-) create mode 100644 src/openvpn/run_command.c create mode 100644 src/openvpn/run_command.h diff --git a/src/openvpn/Makefile.am b/src/openvpn/Makefile.am index eda08351..66410611 100644 --- a/src/openvpn/Makefile.am +++ b/src/openvpn/Makefile.am @@ -102,6 +102,7 @@ openvpn_SOURCES = \ pushlist.h \ reliable.c reliable.h \ route.c route.h \ + run_command.c run_command.h \ schedule.c schedule.h \ session_id.c session_id.h \ shaper.c shaper.h \ diff --git a/src/openvpn/env_set.c b/src/openvpn/env_set.c index 17675625..e7fb2d83 100644 --- a/src/openvpn/env_set.c +++ b/src/openvpn/env_set.c @@ -32,10 +32,10 @@ #include "syshead.h" -#include "misc.h" - #include "env_set.h" +#include "run_command.h" + /* * Set environmental variable (int or string). * @@ -414,7 +414,7 @@ setenv_str_i(struct env_set *es, const char *name, const char *value, const int bool env_allowed(const char *str) { - return (script_security >= SSEC_PW_ENV || !is_password_env_var(str)); + return (script_security() >= SSEC_PW_ENV || !is_password_env_var(str)); } /* Make arrays of strings */ diff --git a/src/openvpn/init.c b/src/openvpn/init.c index f6c8f08e..d28d1fd2 100644 --- a/src/openvpn/init.c +++ b/src/openvpn/init.c @@ -35,6 +35,7 @@ #include "win32.h" #include "init.h" +#include "run_command.h" #include "sig.h" #include "occ.h" #include "list.h" @@ -3095,11 +3096,11 @@ do_option_warnings(struct context *c) /* If a script is used, print appropiate warnings */ if (o->user_script_used) { - if (script_security >= SSEC_SCRIPTS) + if (script_security() >= SSEC_SCRIPTS) { msg(M_WARN, "NOTE: the current --script-security setting may allow this configuration to call user-defined scripts"); } - else if (script_security >= SSEC_PW_ENV) + else if (script_security() >= SSEC_PW_ENV) { msg(M_WARN, "WARNING: the current --script-security setting may allow passwords to be passed to scripts via environmental variables"); } diff --git a/src/openvpn/lladdr.c b/src/openvpn/lladdr.c index ff71e48c..f24596b5 100644 --- a/src/openvpn/lladdr.c +++ b/src/openvpn/lladdr.c @@ -11,6 +11,7 @@ #include "syshead.h" #include "error.h" #include "misc.h" +#include "run_command.h" int set_lladdr(const char *ifname, const char *lladdr, diff --git a/src/openvpn/misc.c b/src/openvpn/misc.c index 85cdc95d..71fa2135 100644 --- a/src/openvpn/misc.c +++ b/src/openvpn/misc.c @@ -51,9 +51,6 @@ const char *iproute_path = IPROUTE_PATH; /* GLOBAL */ #endif -/* contains an SSEC_x value defined in misc.h */ -int script_security = SSEC_BUILT_IN; /* GLOBAL */ - /* * Set standard file descriptors to /dev/null */ @@ -98,221 +95,6 @@ save_inetd_socket_descriptor(void) #endif } -/* - * Print an error message based on the status code returned by system(). - */ -const char * -system_error_message(int stat, struct gc_arena *gc) -{ - struct buffer out = alloc_buf_gc(256, gc); -#ifdef _WIN32 - if (stat == -1) - { - buf_printf(&out, "external program did not execute -- "); - } - buf_printf(&out, "returned error code %d", stat); -#else /* ifdef _WIN32 */ - if (stat == -1) - { - buf_printf(&out, "external program fork failed"); - } - else if (!WIFEXITED(stat)) - { - buf_printf(&out, "external program did not exit normally"); - } - else - { - const int cmd_ret = WEXITSTATUS(stat); - if (!cmd_ret) - { - buf_printf(&out, "external program exited normally"); - } - else if (cmd_ret == 127) - { - buf_printf(&out, "could not execute external program"); - } - else - { - buf_printf(&out, "external program exited with error status: %d", cmd_ret); - } - } -#endif /* ifdef _WIN32 */ - return (const char *)out.data; -} - -/* - * Wrapper around openvpn_execve - */ -bool -openvpn_execve_check(const struct argv *a, const struct env_set *es, const unsigned int flags, const char *error_message) -{ - struct gc_arena gc = gc_new(); - const int stat = openvpn_execve(a, es, flags); - int ret = false; - - if (platform_system_ok(stat)) - { - ret = true; - } - else - { - if (error_message) - { - msg(((flags & S_FATAL) ? M_FATAL : M_WARN), "%s: %s", - error_message, - system_error_message(stat, &gc)); - } - } - gc_free(&gc); - return ret; -} - -bool -openvpn_execve_allowed(const unsigned int flags) -{ - if (flags & S_SCRIPT) - { - return script_security >= SSEC_SCRIPTS; - } - else - { - return script_security >= SSEC_BUILT_IN; - } -} - - -#ifndef _WIN32 -/* - * Run execve() inside a fork(). Designed to replicate the semantics of system() but - * in a safer way that doesn't require the invocation of a shell or the risks - * assocated with formatting and parsing a command line. - */ -int -openvpn_execve(const struct argv *a, const struct env_set *es, const unsigned int flags) -{ - struct gc_arena gc = gc_new(); - int ret = -1; - static bool warn_shown = false; - - if (a && a->argv[0]) - { -#if defined(ENABLE_FEATURE_EXECVE) - if (openvpn_execve_allowed(flags)) - { - const char *cmd = a->argv[0]; - char *const *argv = a->argv; - char *const *envp = (char *const *)make_env_array(es, true, &gc); - pid_t pid; - - pid = fork(); - if (pid == (pid_t)0) /* child side */ - { - execve(cmd, argv, envp); - exit(127); - } - else if (pid < (pid_t)0) /* fork failed */ - { - msg(M_ERR, "openvpn_execve: unable to fork"); - } - else /* parent side */ - { - if (waitpid(pid, &ret, 0) != pid) - { - ret = -1; - } - } - } - else if (!warn_shown && (script_security < SSEC_SCRIPTS)) - { - msg(M_WARN, SCRIPT_SECURITY_WARNING); - warn_shown = true; - } -#else /* if defined(ENABLE_FEATURE_EXECVE) */ - msg(M_WARN, "openvpn_execve: execve function not available"); -#endif /* if defined(ENABLE_FEATURE_EXECVE) */ - } - else - { - msg(M_FATAL, "openvpn_execve: called with empty argv"); - } - - gc_free(&gc); - return ret; -} -#endif /* ifndef _WIN32 */ - -/* - * Run execve() inside a fork(), duping stdout. Designed to replicate the semantics of popen() but - * in a safer way that doesn't require the invocation of a shell or the risks - * assocated with formatting and parsing a command line. - */ -int -openvpn_popen(const struct argv *a, const struct env_set *es) -{ - struct gc_arena gc = gc_new(); - int ret = -1; - static bool warn_shown = false; - - if (a && a->argv[0]) - { -#if defined(ENABLE_FEATURE_EXECVE) - if (script_security >= SSEC_BUILT_IN) - { - const char *cmd = a->argv[0]; - char *const *argv = a->argv; - char *const *envp = (char *const *)make_env_array(es, true, &gc); - pid_t pid; - int pipe_stdout[2]; - - if (pipe(pipe_stdout) == 0) - { - pid = fork(); - if (pid == (pid_t)0) /* child side */ - { - close(pipe_stdout[0]); /* Close read end */ - dup2(pipe_stdout[1],1); - execve(cmd, argv, envp); - exit(127); - } - else if (pid > (pid_t)0) /* parent side */ - { - int status = 0; - - close(pipe_stdout[1]); /* Close write end */ - waitpid(pid, &status, 0); - ret = pipe_stdout[0]; - } - else /* fork failed */ - { - close(pipe_stdout[0]); - close(pipe_stdout[1]); - msg(M_ERR, "openvpn_popen: unable to fork %s", cmd); - } - } - else - { - msg(M_WARN, "openvpn_popen: unable to create stdout pipe for %s", cmd); - ret = -1; - } - } - else if (!warn_shown && (script_security < SSEC_SCRIPTS)) - { - msg(M_WARN, SCRIPT_SECURITY_WARNING); - warn_shown = true; - } -#else /* if defined(ENABLE_FEATURE_EXECVE) */ - msg(M_WARN, "openvpn_popen: execve function not available"); -#endif /* if defined(ENABLE_FEATURE_EXECVE) */ - } - else - { - msg(M_FATAL, "openvpn_popen: called with empty argv"); - } - - gc_free(&gc); - return ret; -} - /* * Prepend a random string to hostname to prevent DNS caching. * For example, foo.bar.gov would be modified to .foo.bar.gov. diff --git a/src/openvpn/misc.h b/src/openvpn/misc.h index c23d4cd1..14abb0f3 100644 --- a/src/openvpn/misc.h +++ b/src/openvpn/misc.h @@ -38,30 +38,6 @@ /* forward declarations */ struct plugin_list; -/* system flags */ -#define S_SCRIPT (1<<0) -#define S_FATAL (1<<1) - -const char *system_error_message(int, struct gc_arena *gc); - -/* wrapper around the execve() call */ -int openvpn_popen(const struct argv *a, const struct env_set *es); - -int openvpn_execve(const struct argv *a, const struct env_set *es, const unsigned int flags); - -bool openvpn_execve_check(const struct argv *a, const struct env_set *es, const unsigned int flags, const char *error_message); - -bool openvpn_execve_allowed(const unsigned int flags); - -static inline bool -openvpn_run_script(const struct argv *a, const struct env_set *es, const unsigned int flags, const char *hook) -{ - char msg[256]; - - openvpn_snprintf(msg, sizeof(msg), "WARNING: Failed running command (%s)", hook); - return openvpn_execve_check(a, es, flags | S_SCRIPT, msg); -} - /* Set standard file descriptors to /dev/null */ void set_std_files_to_null(bool stdin_only); @@ -198,14 +174,6 @@ void get_user_pass_auto_userid(struct user_pass *up, const char *tag); extern const char *iproute_path; #endif -/* Script security */ -#define SSEC_NONE 0 /* strictly no calling of external programs */ -#define SSEC_BUILT_IN 1 /* only call built-in programs such as ifconfig, route, netsh, etc.*/ -#define SSEC_SCRIPTS 2 /* allow calling of built-in programs and user-defined scripts */ -#define SSEC_PW_ENV 3 /* allow calling of built-in programs and user-defined scripts that may receive a password as an environmental variable */ -extern int script_security; /* GLOBAL */ - - #define COMPAT_FLAG_QUERY 0 /** compat_flags operator: Query for a flag */ #define COMPAT_FLAG_SET (1<<0) /** compat_flags operator: Set a compat flag */ #define COMPAT_NAMES (1<<1) /** compat flag: --compat-names set */ diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c index 2944eef2..db32500d 100644 --- a/src/openvpn/multi.c +++ b/src/openvpn/multi.c @@ -38,7 +38,7 @@ #include "multi.h" #include "push.h" -#include "misc.h" +#include "run_command.h" #include "otime.h" #include "gremlin.h" #include "mstats.h" diff --git a/src/openvpn/options.c b/src/openvpn/options.c index 426057ab..b89f4ba2 100644 --- a/src/openvpn/options.c +++ b/src/openvpn/options.c @@ -41,6 +41,7 @@ #include "buffer.h" #include "error.h" #include "common.h" +#include "run_command.h" #include "shaper.h" #include "crypto.h" #include "ssl.h" @@ -6379,7 +6380,7 @@ add_option(struct options *options, else if (streq(p[0], "script-security") && p[1] && !p[2]) { VERIFY_PERMISSION(OPT_P_GENERAL); - script_security = atoi(p[1]); + script_security_set(atoi(p[1])); } else if (streq(p[0], "mssfix") && !p[2]) { diff --git a/src/openvpn/route.c b/src/openvpn/route.c index f6962848..ff392308 100644 --- a/src/openvpn/route.c +++ b/src/openvpn/route.c @@ -36,7 +36,7 @@ #include "common.h" #include "error.h" #include "route.h" -#include "misc.h" +#include "run_command.h" #include "socket.h" #include "manage.h" #include "win32.h" diff --git a/src/openvpn/run_command.c b/src/openvpn/run_command.c new file mode 100644 index 00000000..4e198676 --- /dev/null +++ b/src/openvpn/run_command.c @@ -0,0 +1,267 @@ +/* + * OpenVPN -- An application to securely tunnel IP networks + * over a single TCP/UDP port, with support for SSL/TLS-based + * session authentication and key exchange, + * packet encryption, packet authentication, and + * packet compression. + * + * Copyright (C) 2002-2017 OpenVPN Technologies, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#elif defined(_MSC_VER) +#include "config-msvc.h" +#endif + +#include "syshead.h" + +#include "buffer.h" +#include "error.h" +#include "platform.h" +#include "win32.h" + +#include "memdbg.h" + +#include "run_command.h" + +/* contains an SSEC_x value defined in platform.h */ +static int script_security_level = SSEC_BUILT_IN; /* GLOBAL */ + +int script_security(void) +{ + return script_security_level; +} + +void script_security_set(int level) +{ + script_security_level = level; +} + +/* + * Print an error message based on the status code returned by system(). + */ +static const char * +system_error_message(int stat, struct gc_arena *gc) +{ + struct buffer out = alloc_buf_gc(256, gc); +#ifdef _WIN32 + if (stat == -1) + { + buf_printf(&out, "external program did not execute -- "); + } + buf_printf(&out, "returned error code %d", stat); +#else /* ifdef _WIN32 */ + if (stat == -1) + { + buf_printf(&out, "external program fork failed"); + } + else if (!WIFEXITED(stat)) + { + buf_printf(&out, "external program did not exit normally"); + } + else + { + const int cmd_ret = WEXITSTATUS(stat); + if (!cmd_ret) + { + buf_printf(&out, "external program exited normally"); + } + else if (cmd_ret == 127) + { + buf_printf(&out, "could not execute external program"); + } + else + { + buf_printf(&out, "external program exited with error status: %d", cmd_ret); + } + } +#endif /* ifdef _WIN32 */ + return (const char *)out.data; +} + +bool +openvpn_execve_allowed(const unsigned int flags) +{ + if (flags & S_SCRIPT) + { + return script_security() >= SSEC_SCRIPTS; + } + else + { + return script_security() >= SSEC_BUILT_IN; + } +} + + +#ifndef _WIN32 +/* + * Run execve() inside a fork(). Designed to replicate the semantics of system() but + * in a safer way that doesn't require the invocation of a shell or the risks + * assocated with formatting and parsing a command line. + */ +int +openvpn_execve(const struct argv *a, const struct env_set *es, const unsigned int flags) +{ + struct gc_arena gc = gc_new(); + int ret = -1; + static bool warn_shown = false; + + if (a && a->argv[0]) + { +#if defined(ENABLE_FEATURE_EXECVE) + if (openvpn_execve_allowed(flags)) + { + const char *cmd = a->argv[0]; + char *const *argv = a->argv; + char *const *envp = (char *const *)make_env_array(es, true, &gc); + pid_t pid; + + pid = fork(); + if (pid == (pid_t)0) /* child side */ + { + execve(cmd, argv, envp); + exit(127); + } + else if (pid < (pid_t)0) /* fork failed */ + { + msg(M_ERR, "openvpn_execve: unable to fork"); + } + else /* parent side */ + { + if (waitpid(pid, &ret, 0) != pid) + { + ret = -1; + } + } + } + else if (!warn_shown && (script_security() < SSEC_SCRIPTS)) + { + msg(M_WARN, SCRIPT_SECURITY_WARNING); + warn_shown = true; + } +#else /* if defined(ENABLE_FEATURE_EXECVE) */ + msg(M_WARN, "openvpn_execve: execve function not available"); +#endif /* if defined(ENABLE_FEATURE_EXECVE) */ + } + else + { + msg(M_FATAL, "openvpn_execve: called with empty argv"); + } + + gc_free(&gc); + return ret; +} +#endif /* ifndef _WIN32 */ + +/* + * Wrapper around openvpn_execve + */ +bool +openvpn_execve_check(const struct argv *a, const struct env_set *es, const unsigned int flags, const char *error_message) +{ + struct gc_arena gc = gc_new(); + const int stat = openvpn_execve(a, es, flags); + int ret = false; + + if (platform_system_ok(stat)) + { + ret = true; + } + else + { + if (error_message) + { + msg(((flags & S_FATAL) ? M_FATAL : M_WARN), "%s: %s", + error_message, + system_error_message(stat, &gc)); + } + } + gc_free(&gc); + return ret; +} + +/* + * Run execve() inside a fork(), duping stdout. Designed to replicate the semantics of popen() but + * in a safer way that doesn't require the invocation of a shell or the risks + * assocated with formatting and parsing a command line. + */ +int +openvpn_popen(const struct argv *a, const struct env_set *es) +{ + struct gc_arena gc = gc_new(); + int ret = -1; + static bool warn_shown = false; + + if (a && a->argv[0]) + { +#if defined(ENABLE_FEATURE_EXECVE) + if (script_security() >= SSEC_BUILT_IN) + { + const char *cmd = a->argv[0]; + char *const *argv = a->argv; + char *const *envp = (char *const *)make_env_array(es, true, &gc); + pid_t pid; + int pipe_stdout[2]; + + if (pipe(pipe_stdout) == 0) + { + pid = fork(); + if (pid == (pid_t)0) /* child side */ + { + close(pipe_stdout[0]); /* Close read end */ + dup2(pipe_stdout[1],1); + execve(cmd, argv, envp); + exit(127); + } + else if (pid > (pid_t)0) /* parent side */ + { + int status = 0; + + close(pipe_stdout[1]); /* Close write end */ + waitpid(pid, &status, 0); + ret = pipe_stdout[0]; + } + else /* fork failed */ + { + close(pipe_stdout[0]); + close(pipe_stdout[1]); + msg(M_ERR, "openvpn_popen: unable to fork %s", cmd); + } + } + else + { + msg(M_WARN, "openvpn_popen: unable to create stdout pipe for %s", cmd); + ret = -1; + } + } + else if (!warn_shown && (script_security() < SSEC_SCRIPTS)) + { + msg(M_WARN, SCRIPT_SECURITY_WARNING); + warn_shown = true; + } +#else /* if defined(ENABLE_FEATURE_EXECVE) */ + msg(M_WARN, "openvpn_popen: execve function not available"); +#endif /* if defined(ENABLE_FEATURE_EXECVE) */ + } + else + { + msg(M_FATAL, "openvpn_popen: called with empty argv"); + } + + gc_free(&gc); + return ret; +} diff --git a/src/openvpn/run_command.h b/src/openvpn/run_command.h new file mode 100644 index 00000000..4501a5cc --- /dev/null +++ b/src/openvpn/run_command.h @@ -0,0 +1,63 @@ +/* + * OpenVPN -- An application to securely tunnel IP networks + * over a single TCP/UDP port, with support for SSL/TLS-based + * session authentication and key exchange, + * packet encryption, packet authentication, and + * packet compression. + * + * Copyright (C) 2002-2017 OpenVPN Technologies, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef RUN_COMMAND_H +#define RUN_COMMAND_H + +#include "basic.h" +#include "env_set.h" + +/* Script security */ +#define SSEC_NONE 0 /* strictly no calling of external programs */ +#define SSEC_BUILT_IN 1 /* only call built-in programs such as ifconfig, route, netsh, etc.*/ +#define SSEC_SCRIPTS 2 /* allow calling of built-in programs and user-defined scripts */ +#define SSEC_PW_ENV 3 /* allow calling of built-in programs and user-defined scripts that may receive a password as an environmental variable */ + +int script_security(void); + +void script_security_set(int level); + +/* openvpn_execve flags */ +#define S_SCRIPT (1<<0) +#define S_FATAL (1<<1) + +/* wrapper around the execve() call */ +int openvpn_popen(const struct argv *a, const struct env_set *es); + +bool openvpn_execve_allowed(const unsigned int flags); + +bool openvpn_execve_check(const struct argv *a, const struct env_set *es, + const unsigned int flags, const char *error_message); + +static inline bool +openvpn_run_script(const struct argv *a, const struct env_set *es, + const unsigned int flags, const char *hook) +{ + char msg[256]; + + openvpn_snprintf(msg, sizeof(msg), + "WARNING: Failed running command (%s)", hook); + return openvpn_execve_check(a, es, flags | S_SCRIPT, msg); +} + +#endif /* ifndef RUN_COMMAND_H */ diff --git a/src/openvpn/socket.c b/src/openvpn/socket.c index 211e7441..911b2335 100644 --- a/src/openvpn/socket.c +++ b/src/openvpn/socket.c @@ -35,6 +35,7 @@ #include "gremlin.h" #include "plugin.h" #include "ps.h" +#include "run_command.h" #include "manage.h" #include "misc.h" #include "manage.h" diff --git a/src/openvpn/ssl_verify.c b/src/openvpn/ssl_verify.c index a3699252..61872251 100644 --- a/src/openvpn/ssl_verify.c +++ b/src/openvpn/ssl_verify.c @@ -34,10 +34,10 @@ #include "syshead.h" -#include "misc.h" +#include "base64.h" #include "manage.h" #include "otime.h" -#include "base64.h" +#include "run_command.h" #include "ssl_verify.h" #include "ssl_verify_backend.h" diff --git a/src/openvpn/tun.c b/src/openvpn/tun.c index 26baa206..1c7e51d9 100644 --- a/src/openvpn/tun.c +++ b/src/openvpn/tun.c @@ -40,7 +40,7 @@ #include "tun.h" #include "fdmisc.h" #include "common.h" -#include "misc.h" +#include "run_command.h" #include "socket.h" #include "manage.h" #include "route.h" diff --git a/src/openvpn/win32.c b/src/openvpn/win32.c index 29bbb841..3905524a 100644 --- a/src/openvpn/win32.c +++ b/src/openvpn/win32.c @@ -39,9 +39,9 @@ #include "buffer.h" #include "error.h" #include "mtu.h" +#include "run_command.h" #include "sig.h" #include "win32.h" -#include "misc.h" #include "openvpn-msg.h" #include "memdbg.h" @@ -1137,7 +1137,7 @@ openvpn_execve(const struct argv *a, const struct env_set *es, const unsigned in free(env); gc_free(&gc); } - else if (!exec_warn && (script_security < SSEC_SCRIPTS)) + else if (!exec_warn && (script_security() < SSEC_SCRIPTS)) { msg(M_WARN, SCRIPT_SECURITY_WARNING); exec_warn = true; From patchwork Wed Jul 4 07:53:58 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Steffan Karger X-Patchwork-Id: 397 Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director12.mail.ord1d.rsapps.net ([172.27.255.53]) by backend30.mail.ord1d.rsapps.net (Dovecot) with LMTP id C18FABQKPVvCIQAAIUCqbw for ; Wed, 04 Jul 2018 13:55:32 -0400 Received: from proxy12.mail.iad3a.rsapps.net ([172.27.255.53]) by director12.mail.ord1d.rsapps.net (Dovecot) with LMTP id VbOLDBQKPVt5eQAAIasKDg ; Wed, 04 Jul 2018 13:55:32 -0400 Received: from smtp1.gate.iad3a ([172.27.255.53]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy12.mail.iad3a.rsapps.net with LMTP id kBtlDRQKPVuNOAAAh9K5Vw ; Wed, 04 Jul 2018 13:55:32 -0400 X-Spam-Threshold: 95 X-Spam-Score: 0 X-Spam-Flag: NO X-Virus-Scanned: OK X-Orig-To: openvpnslackdevel@openvpn.net X-Originating-Ip: [216.105.38.7] Authentication-Results: smtp1.gate.iad3a.rsapps.net; iprev=pass policy.iprev="216.105.38.7"; spf=pass smtp.mailfrom="openvpn-devel-bounces@lists.sourceforge.net" smtp.helo="lists.sourceforge.net"; dkim=fail (signature verification failed) header.d=sourceforge.net; dkim=fail (signature verification failed) header.d=sf.net; dkim=fail (signature verification failed) header.d=karger-me.20150623.gappssmtp.com; dmarc=none (p=nil; dis=none) header.from=karger.me X-Suspicious-Flag: YES X-Classification-ID: 71205f74-7fb3-11e8-af4a-52540091dea5-1-1 Received: from [216.105.38.7] ([216.105.38.7:30804] helo=lists.sourceforge.net) by smtp1.gate.iad3a.rsapps.net (envelope-from ) (ecelerity 4.2.1.56364 r(Core:4.2.1.14)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id 64/FC-32593-31A0D3B5; Wed, 04 Jul 2018 13:55:31 -0400 Received: from [127.0.0.1] (helo=sfs-ml-2.v29.lw.sourceforge.com) by sfs-ml-2.v29.lw.sourceforge.com with esmtp (Exim 4.90_1) (envelope-from ) id 1falzK-0003SL-8z; Wed, 04 Jul 2018 17:54:46 +0000 Received: from [172.30.20.202] (helo=mx.sourceforge.net) by sfs-ml-2.v29.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.90_1) (envelope-from ) id 1falzI-0003S0-4t for openvpn-devel@lists.sourceforge.net; Wed, 04 Jul 2018 17:54:44 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=References:In-Reply-To:Message-Id:Date:Subject:Cc: To:From:Sender:Reply-To:MIME-Version:Content-Type:Content-Transfer-Encoding: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=tVZTZdTesssfltS5P9CAnwNvmkPcCdzXprZzvuTkvgc=; b=OvszAdNmP2M0cveic4uI3CqkZx TtN7V2+bL7eS+Cv8VoA80J9oQmff1GlXYzS30VrpMyp6pPelbKJGGbaCTYtZAOzHyRivLEJd5rNuZ fS21q3/F6WKs84OBbk+OG6Lvz07alLQykB8m0uCCiKx/lye4AHdc0uL8DbZP67XiJXsg=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=References:In-Reply-To:Message-Id:Date:Subject:Cc:To:From:Sender:Reply-To :MIME-Version:Content-Type:Content-Transfer-Encoding:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=tVZTZdTesssfltS5P9CAnwNvmkPcCdzXprZzvuTkvgc=; b=eWmP5dxZnEPGUo4uHAJz9ewlYL YcpRQcBHdFgsRhDHe24iKDYUGbgZomvOLQMtBerz2tyt3WZJVp51I+B3nAtvsm73wiW508THcjz7d +qRaDWqpmuoh/PY8d5D13F6DTBD7GGk2HFiBzpP19aZltdP89MIBve5wUAmC/HG1JZ3U=; Received: from mail-ed1-f65.google.com ([209.85.208.65]) by sfi-mx-4.v28.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES128-GCM-SHA256:128) (Exim 4.90_1) id 1falzE-00CwKc-PL for openvpn-devel@lists.sourceforge.net; Wed, 04 Jul 2018 17:54:44 +0000 Received: by mail-ed1-f65.google.com with SMTP id r17-v6so4563335edo.13 for ; Wed, 04 Jul 2018 10:54:40 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=karger-me.20150623.gappssmtp.com; s=20150623; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=tVZTZdTesssfltS5P9CAnwNvmkPcCdzXprZzvuTkvgc=; b=BE6I6g/7saVOKKH5F/8Vj+KB9+e25CH+hVzxsz9dJRV6QrJzQ9RjYoQKDbEECp3Pqy 80THSFPYrbWYPpNkRXr4wnnE/yG/x0qcakW/a0oDlLP+r1W8DBphPYHrObPmInAEC0XH RggDCSICawSRbgoWzfp9TlZsnc5XgUMDVe+BsWmagY3b76LqeV0oEvS2j6skQZd5GCEw YWjJED3NdOtkNps33TQpzcMH5/x87x8q5R9O4Cyj9SQAFg9+IlzSW/uGUKV1MQEgLgRy cVWNkkFc34dQukFfiLhGnHv9ytNO8rbEeav9StRyVQzuBvrh/6gFFM9oE6sQrT5Na6+r fwXg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=tVZTZdTesssfltS5P9CAnwNvmkPcCdzXprZzvuTkvgc=; b=ODx6Wog9CG/yK+eTlxWLS1QcWYwttCuIsYJAqAhe5OfzZmPpV3zw2UzBPJ93p9H758 KPI9u0mkYxjEwr5UW2y5oLLnPNZcdoD3/TDFpJuxOQfWk4tgaHESBe2JlrTb1J4bokXG 5Df3pcCH2uJmo8sbXQpnd56pF/OY4H2WGQbCzzGvCmBK9ptlZ0WnSAE6aH2khd1+Q7Hs 9rvXntd0My6ITuXF1MvEWHhX4cQW7Y5qwkPY26xVY+jD5+9V2tJccEyJ4/C5hje0V6dt OwTQRTpYXqxszZw8VfY0u0MwZJq6mm/nMSILrVG0XtVoCLKHpDzNCr1HaCrrRTtxwGvs ipkA== X-Gm-Message-State: APt69E3YRVmmVMxtZFZ56nUhUcp6HWSsD4WXOlY4q4mnnUtVEXOiDrxR 0WJAuc3vmMVHby0ES3sILjgIKlOy9ao= X-Google-Smtp-Source: AAOMgpdbNp7vgyfFNtSRTmf87XZa5UNshHd9oxyAvKXpTGWnuHheJ1CGynKZSshY6aaP0iaT0Se5MQ== X-Received: by 2002:a50:eacb:: with SMTP id u11-v6mr3795407edp.7.1530726873965; Wed, 04 Jul 2018 10:54:33 -0700 (PDT) Received: from vesta.fritz.box ([2001:985:e54:1:f598:331e:3cdf:2649]) by smtp.gmail.com with ESMTPSA id o2-v6sm1948961edd.84.2018.07.04.10.54.33 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Wed, 04 Jul 2018 10:54:33 -0700 (PDT) From: Steffan Karger To: openvpn-devel@lists.sourceforge.net Date: Wed, 4 Jul 2018 19:53:58 +0200 Message-Id: <20180704175404.22371-3-steffan@karger.me> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20180704175404.22371-1-steffan@karger.me> References: <1512734870-17133-1-git-send-email-steffan.karger@fox-it.com> <20180704175404.22371-1-steffan@karger.me> X-Spam-Report: Spam Filtering performed by mx.sourceforge.net. See http://spamassassin.org/tag/ for more details. -0.0 RCVD_IN_DNSWL_NONE RBL: Sender listed at http://www.dnswl.org/, no trust [209.85.208.65 listed in list.dnswl.org] -0.0 SPF_PASS SPF: sender matches SPF record -0.0 RCVD_IN_MSPIKE_H2 RBL: Average reputation (+2) [209.85.208.65 listed in wl.mailspike.net] 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature -0.0 T_DKIMWL_WL_MED DKIMwl.org - Whitelisted Medium sender 0.0 AWL AWL: Adjusted score from AWL reputation of From: address X-Headers-End: 1falzE-00CwKc-PL Subject: [Openvpn-devel] [PATCH v2 3/9] Add crypto_pem_{encode,decode}() X-BeenThere: openvpn-devel@lists.sourceforge.net X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox From: Steffan Karger Needed for tls-crypt-v2, but isolated enough to be reviewed as a separate patch. The encode API allocates memory, because it fits our typical gc-oriented code pattern and the caller does not have to do multiple calls or calculations to determine the required destination buffer size. The decode API does not allocate memory, because the required destination buffer is always smaller than the input buffer (so is easy to manage by the caller) and does not force the caller to use the heap. Signed-off-by: Steffan Karger Acked-by: Antonio Quartulli --- src/openvpn/crypto_backend.h | 29 +++++++++ src/openvpn/crypto_mbedtls.c | 75 ++++++++++++++++++++++ src/openvpn/crypto_openssl.c | 82 ++++++++++++++++++++++++ tests/unit_tests/openvpn/Makefile.am | 16 ++++- tests/unit_tests/openvpn/test_crypto.c | 88 ++++++++++++++++++++++++++ 5 files changed, 289 insertions(+), 1 deletion(-) create mode 100644 tests/unit_tests/openvpn/test_crypto.c diff --git a/src/openvpn/crypto_backend.h b/src/openvpn/crypto_backend.h index bb53b8c9..f917c8d7 100644 --- a/src/openvpn/crypto_backend.h +++ b/src/openvpn/crypto_backend.h @@ -36,6 +36,7 @@ #include "crypto_mbedtls.h" #endif #include "basic.h" +#include "buffer.h" /* TLS uses a tag of 128 bytes, let's do the same for OpenVPN */ #define OPENVPN_AEAD_TAG_LENGTH 16 @@ -105,6 +106,34 @@ void show_available_digests(void); void show_available_engines(void); +/** + * Encode binary data as PEM. + * + * @param name The name to use in the PEM header/footer. + * @param dst Destination buffer for PEM-encoded data. Must be a valid + * pointer to an uninitialized buffer structure. Iff this + * function returns true, the buffer will contain memory + * allocated through the supplied gc. + * @param src Source buffer. + * @param gc The garbage collector to use when allocating memory for dst. + * + * @return true iff PEM encode succeeded. + */ +bool crypto_pem_encode(const char *name, struct buffer *dst, + const struct buffer *src, struct gc_arena *gc); + +/** + * Decode a PEM buffer to binary data. + * + * @param name The name expected in the PEM header/footer. + * @param dst Destination buffer for decoded data. + * @param src Source buffer (PEM data). + * + * @return true iff PEM decode succeeded. + */ +bool crypto_pem_decode(const char *name, struct buffer *dst, + const struct buffer *src); + /* * * Random number functions, used in cases where we want diff --git a/src/openvpn/crypto_mbedtls.c b/src/openvpn/crypto_mbedtls.c index 8ff6704d..82f4e574 100644 --- a/src/openvpn/crypto_mbedtls.c +++ b/src/openvpn/crypto_mbedtls.c @@ -44,11 +44,13 @@ #include "otime.h" #include "misc.h" +#include #include #include #include #include #include +#include #include @@ -229,6 +231,79 @@ show_available_engines(void) "available\n"); } +bool +crypto_pem_encode(const char *name, struct buffer *dst, + const struct buffer *src, struct gc_arena *gc) +{ + /* 1000 chars is the PEM line length limit (+1 for tailing NUL) */ + char header[1000+1] = { 0 }; + char footer[1000+1] = { 0 }; + + if (!openvpn_snprintf(header, sizeof(header), "-----BEGIN %s-----\n", name)) + { + return false; + } + if (!openvpn_snprintf(footer, sizeof(footer), "-----END %s-----\n", name)) + { + return false; + } + + size_t out_len = 0; + if (MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL != + mbedtls_pem_write_buffer(header, footer, BPTR(src), BLEN(src), + NULL, 0, &out_len)) + { + return false; + } + + *dst = alloc_buf_gc(out_len, gc); + if (!mbed_ok(mbedtls_pem_write_buffer(header, footer, BPTR(src), BLEN(src), + BPTR(dst), BCAP(dst), &out_len)) + || !buf_inc_len(dst, out_len)) + { + CLEAR(*dst); + return false; + } + + return true; +} + +bool +crypto_pem_decode(const char *name, struct buffer *dst, + const struct buffer *src) +{ + /* 1000 chars is the PEM line length limit (+1 for tailing NUL) */ + char header[1000+1] = { 0 }; + char footer[1000+1] = { 0 }; + + if (*(BLAST(src)) != '\0') + { + msg(M_WARN, "PEM decode error: source buffer not null-terminated"); + return false; + } + if (!openvpn_snprintf(header, sizeof(header), "-----BEGIN %s-----", name)) + { + return false; + } + if (!openvpn_snprintf(footer, sizeof(footer), "-----END %s-----", name)) + { + return false; + } + + size_t use_len = 0; + mbedtls_pem_context ctx = { 0 }; + bool ret = mbed_ok(mbedtls_pem_read_buffer(&ctx, header, footer, BPTR(src), + NULL, 0, &use_len)); + if (ret && !buf_write(dst, ctx.buf, ctx.buflen)) + { + ret = false; + msg(M_WARN, "PEM decode error: destination buffer too small"); + } + + mbedtls_pem_free(&ctx); + return ret; +} + /* * * Random number functions, used in cases where we want diff --git a/src/openvpn/crypto_openssl.c b/src/openvpn/crypto_openssl.c index 4fb2f6d6..32b8b695 100644 --- a/src/openvpn/crypto_openssl.c +++ b/src/openvpn/crypto_openssl.c @@ -387,6 +387,88 @@ show_available_engines(void) #endif } + +bool +crypto_pem_encode(const char *name, struct buffer *dst, + const struct buffer *src, struct gc_arena *gc) +{ + bool ret = false; + BIO *bio = BIO_new(BIO_s_mem()); + if (!bio || !PEM_write_bio(bio, name, "", BPTR(src), BLEN(src))) + { + ret = false; + goto cleanup; + } + + BUF_MEM *bptr; + BIO_get_mem_ptr(bio, &bptr); + + *dst = alloc_buf_gc(bptr->length, gc); + ASSERT(buf_write(dst, bptr->data, bptr->length)); + + ret = true; +cleanup: + if (!BIO_free(bio)) + { + ret = false;; + } + + return ret; +} + +bool +crypto_pem_decode(const char *name, struct buffer *dst, + const struct buffer *src) +{ + bool ret = false; + BIO *bio; + + if (!(bio = BIO_new_mem_buf((char *)BPTR(src), BLEN(src)))) + { + crypto_msg(M_FATAL, "Cannot open memory BIO for PEM decode"); + } + + char *name_read = NULL; + char *header_read = NULL; + uint8_t *data_read = NULL; + long data_read_len = 0; + if (!PEM_read_bio(bio, &name_read, &header_read, &data_read, + &data_read_len)) + { + dmsg(D_CRYPT_ERRORS, "%s: PEM decode failed", __func__); + goto cleanup; + } + + if (strcmp(name, name_read)) + { + dmsg(D_CRYPT_ERRORS, + "%s: unexpected PEM name (got '%s', expected '%s')", + __func__, name_read, name); + goto cleanup; + } + + uint8_t *dst_data = buf_write_alloc(dst, data_read_len); + if (!dst_data) + { + dmsg(D_CRYPT_ERRORS, "%s: dst too small (%i, needs %li)", __func__, + BCAP(dst), data_read_len); + goto cleanup; + } + memcpy(dst_data, data_read, data_read_len); + + ret = true; +cleanup: + OPENSSL_free(name_read); + OPENSSL_free(header_read); + OPENSSL_free(data_read); + if (!BIO_free(bio)) + { + ret = false;; + } + + return ret; +} + /* * * Random number functions, used in cases where we want diff --git a/tests/unit_tests/openvpn/Makefile.am b/tests/unit_tests/openvpn/Makefile.am index db4d46e1..1ff62615 100644 --- a/tests/unit_tests/openvpn/Makefile.am +++ b/tests/unit_tests/openvpn/Makefile.am @@ -6,7 +6,7 @@ if HAVE_LD_WRAP_SUPPORT check_PROGRAMS += argv_testdriver buffer_testdriver endif -check_PROGRAMS += packet_id_testdriver tls_crypt_testdriver +check_PROGRAMS += crypto_testdriver packet_id_testdriver tls_crypt_testdriver TESTS = $(check_PROGRAMS) @@ -31,6 +31,20 @@ buffer_testdriver_SOURCES = test_buffer.c mock_msg.c \ $(openvpn_srcdir)/buffer.c \ $(openvpn_srcdir)/platform.c +crypto_testdriver_CFLAGS = @TEST_CFLAGS@ \ + -I$(openvpn_includedir) -I$(compat_srcdir) -I$(openvpn_srcdir) \ + $(OPTIONAL_CRYPTO_CFLAGS) +crypto_testdriver_LDFLAGS = @TEST_LDFLAGS@ \ + $(OPTIONAL_CRYPTO_LIBS) +crypto_testdriver_SOURCES = test_crypto.c mock_msg.c \ + $(openvpn_srcdir)/buffer.c \ + $(openvpn_srcdir)/crypto.c \ + $(openvpn_srcdir)/crypto_mbedtls.c \ + $(openvpn_srcdir)/crypto_openssl.c \ + $(openvpn_srcdir)/otime.c \ + $(openvpn_srcdir)/packet_id.c \ + $(openvpn_srcdir)/platform.c + packet_id_testdriver_CFLAGS = @TEST_CFLAGS@ \ -I$(openvpn_includedir) -I$(compat_srcdir) -I$(openvpn_srcdir) \ $(OPTIONAL_CRYPTO_CFLAGS) diff --git a/tests/unit_tests/openvpn/test_crypto.c b/tests/unit_tests/openvpn/test_crypto.c new file mode 100644 index 00000000..7027d3da --- /dev/null +++ b/tests/unit_tests/openvpn/test_crypto.c @@ -0,0 +1,88 @@ +/* + * OpenVPN -- An application to securely tunnel IP networks + * over a single UDP port, with support for SSL/TLS-based + * session authentication and key exchange, + * packet encryption, packet authentication, and + * packet compression. + * + * Copyright (C) 2016-2018 Fox Crypto B.V. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#elif defined(_MSC_VER) +#include "config-msvc.h" +#endif + +#include "syshead.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "crypto.h" + +#include "mock_msg.h" + +static const char testtext[] = "Dummy text to test PEM encoding"; + +static void +crypto_pem_encode_decode_loopback(void **state) { + struct gc_arena gc = gc_new(); + struct buffer src_buf; + buf_set_read(&src_buf, (void *)testtext, sizeof(testtext)); + + uint8_t dec[sizeof(testtext)]; + struct buffer dec_buf; + buf_set_write(&dec_buf, dec, sizeof(dec)); + + struct buffer pem_buf; + + assert_true(crypto_pem_encode("TESTKEYNAME", &pem_buf, &src_buf, &gc)); + assert_true(BLEN(&src_buf) < BLEN(&pem_buf)); + + /* Wrong key name */ + assert_false(crypto_pem_decode("WRONGNAME", &dec_buf, &pem_buf)); + + assert_true(crypto_pem_decode("TESTKEYNAME", &dec_buf, &pem_buf)); + assert_int_equal(BLEN(&src_buf), BLEN(&dec_buf)); + assert_memory_equal(BPTR(&src_buf), BPTR(&dec_buf), BLEN(&src_buf)); + + gc_free(&gc); +} + +int +main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test(crypto_pem_encode_decode_loopback), + }; + +#if defined(ENABLE_CRYPTO_OPENSSL) + OpenSSL_add_all_algorithms(); +#endif + + int ret = cmocka_run_group_tests_name("crypto tests", tests, NULL, NULL); + +#if defined(ENABLE_CRYPTO_OPENSSL) + EVP_cleanup(); +#endif + + return ret; +} From patchwork Wed Jul 4 07:53:59 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Steffan Karger X-Patchwork-Id: 404 Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director8.mail.ord1d.rsapps.net ([172.27.255.56]) by backend30.mail.ord1d.rsapps.net (Dovecot) with LMTP id 1ExDAicKPVuSBwAAIUCqbw for ; Wed, 04 Jul 2018 13:55:51 -0400 Received: from proxy3.mail.iad3a.rsapps.net ([172.27.255.56]) by director8.mail.ord1d.rsapps.net (Dovecot) with LMTP id Y1frEScKPVsyJgAAfY0hYg ; Wed, 04 Jul 2018 13:55:51 -0400 Received: from smtp22.gate.iad3a ([172.27.255.56]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy3.mail.iad3a.rsapps.net with LMTP id SIfGDycKPVutTAAAYaqY3Q ; Wed, 04 Jul 2018 13:55:51 -0400 X-Spam-Threshold: 95 X-Spam-Score: 0 X-Spam-Flag: NO X-Virus-Scanned: OK X-Orig-To: openvpnslackdevel@openvpn.net X-Originating-Ip: [216.105.38.7] Authentication-Results: smtp22.gate.iad3a.rsapps.net; iprev=pass policy.iprev="216.105.38.7"; spf=pass smtp.mailfrom="openvpn-devel-bounces@lists.sourceforge.net" smtp.helo="lists.sourceforge.net"; dkim=fail (signature verification failed) header.d=sourceforge.net; dkim=fail (signature verification failed) header.d=sf.net; dkim=fail (signature verification failed) header.d=karger-me.20150623.gappssmtp.com; dmarc=none (p=nil; dis=none) header.from=karger.me X-Suspicious-Flag: YES X-Classification-ID: 7c9ed57e-7fb3-11e8-8018-5254005ae9fe-1-1 Received: from [216.105.38.7] ([216.105.38.7:43554] helo=lists.sourceforge.net) by smtp22.gate.iad3a.rsapps.net (envelope-from ) (ecelerity 4.2.1.56364 r(Core:4.2.1.14)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id 71/B1-30269-62A0D3B5; Wed, 04 Jul 2018 13:55:50 -0400 Received: from [127.0.0.1] (helo=sfs-ml-2.v29.lw.sourceforge.com) by sfs-ml-2.v29.lw.sourceforge.com with esmtp (Exim 4.90_1) (envelope-from ) id 1falzK-0003SA-6O; Wed, 04 Jul 2018 17:54:46 +0000 Received: from [172.30.20.202] (helo=mx.sourceforge.net) by sfs-ml-2.v29.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.90_1) (envelope-from ) id 1falzH-0003Rl-H9 for openvpn-devel@lists.sourceforge.net; Wed, 04 Jul 2018 17:54:43 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=References:In-Reply-To:Message-Id:Date:Subject:Cc: To:From:Sender:Reply-To:MIME-Version:Content-Type:Content-Transfer-Encoding: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=tULkX6Errkm2MLeV5W55UUoGVvQOFeM3zBxiUvlSn50=; b=gzokjPpGgoW3AB0t4njcmfmsno tQ97HgJCtvCYw6lw57RdaJvYqF/0WZw7kQ0VXdJFnhpODjWbwRDb46tdrDT/sLHoKN8//2NnOgxZx +AlqSQZ668H3OZ13yh4SI2LoBI/uc+gwzKda2LKmyms1pHsXXsSe56DBlMPl8R860O3Q=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=References:In-Reply-To:Message-Id:Date:Subject:Cc:To:From:Sender:Reply-To :MIME-Version:Content-Type:Content-Transfer-Encoding:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=tULkX6Errkm2MLeV5W55UUoGVvQOFeM3zBxiUvlSn50=; b=C9PNsxMYw1GiyNVFmn7i/3nVYt 0fzJyJpv5K692z7nkbtmiiw/gkmkc9BanUU+Q/adJ3IMFAnVA/TaO7UKpWZCAk1ZrIOoIk+qc36DL xt71xWelzwLANiBXgCweDKBh+ts8O5cDUXZff9uRAKd3r4+j3ngq4O/+3Hw26qB0Q4dw=; Received: from mail-ed1-f54.google.com ([209.85.208.54]) by sfi-mx-2.v28.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES128-GCM-SHA256:128) (Exim 4.90_1) id 1falzF-00GkHw-LJ for openvpn-devel@lists.sourceforge.net; Wed, 04 Jul 2018 17:54:43 +0000 Received: by mail-ed1-f54.google.com with SMTP id g15-v6so4561491edr.12 for ; Wed, 04 Jul 2018 10:54:41 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=karger-me.20150623.gappssmtp.com; s=20150623; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=tULkX6Errkm2MLeV5W55UUoGVvQOFeM3zBxiUvlSn50=; b=pKfSSmhFS5wu0ckJgAzvlMV+57/jgnLohay8yr8pI39QRGx1LYTrP7e7150YNTyCMm XpBQ2RezyAujeWRC2C3E8o9PdcGQ03CeBL7UnOXWrQ5CpqR9h5SlQJU5OZAsXpAwgQos lJW5k+GglVNI97CSP6fy0UBpFwzqm9IHXJpbvTi0hUivq+0h9DtJ3/qOpWHVYs2wgW4M SGNxUTWpr+cvIdFiqhnsNZyhKGiAWBjPQdpD0Q3O2f+2F4x9AUr935G/EwUYI3xJF5Ut yNRAffN4h8J5tFTVdBoNwW0D5tb/KwZhmwkokQKPOdm17M4o5BeHA51oDJSvs4fYb4OL /vMA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=tULkX6Errkm2MLeV5W55UUoGVvQOFeM3zBxiUvlSn50=; b=eUcIsYjr/KbzAsROxwjAuedyjEGiuHcgN/WsUwhWugk5By7pFJdBHB1ug6vH3vwWS/ j2RmRk362jHWeYClGoS0mRlD7SGSTxatnuHEPMNSSe5Za6ir0iHv+8zg7wYL0JOGY+8U RBnLMPnN3Z8JhIwD/Wo4sh46BNZAjR6o9j/p1eNH3kbGCNZEApC6eaaARH1AIzQECEju iWr0no8lkthA3t7ImybSFEda0oGmbzF3h/cQcQccYDdFOOlXRf33wfCvBP5ffxSMEkV7 Be72Dax5vdob6RAjT1gM/OfoMaePEz2FZtob4/zstGwPlr69zcF6+8hMZVbHAB0rX6Tv lMqw== X-Gm-Message-State: APt69E11E7tT7Vnh3kpkEsIX8TCHYodO6IlnEGWj5DUEpXHX8S+9Qi7L SjJGK17X+NrBH9Vs+x9P9AqtZFoAyWI= X-Google-Smtp-Source: AAOMgpfFe1CY2HZGtsEYFOkERXgp+gKvV6dlbCY8PYid3ZGOMeE5lxQOJjf0dPaJYyzjPuKeREWJFw== X-Received: by 2002:a50:adaa:: with SMTP id a39-v6mr3808300edd.194.1530726874813; Wed, 04 Jul 2018 10:54:34 -0700 (PDT) Received: from vesta.fritz.box ([2001:985:e54:1:f598:331e:3cdf:2649]) by smtp.gmail.com with ESMTPSA id o2-v6sm1948961edd.84.2018.07.04.10.54.34 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Wed, 04 Jul 2018 10:54:34 -0700 (PDT) From: Steffan Karger To: openvpn-devel@lists.sourceforge.net Date: Wed, 4 Jul 2018 19:53:59 +0200 Message-Id: <20180704175404.22371-4-steffan@karger.me> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20180704175404.22371-1-steffan@karger.me> References: <1512734870-17133-1-git-send-email-steffan.karger@fox-it.com> <20180704175404.22371-1-steffan@karger.me> X-Spam-Report: Spam Filtering performed by mx.sourceforge.net. See http://spamassassin.org/tag/ for more details. -0.0 RCVD_IN_MSPIKE_H2 RBL: Average reputation (+2) [209.85.208.54 listed in wl.mailspike.net] -0.0 RCVD_IN_DNSWL_NONE RBL: Sender listed at http://www.dnswl.org/, no trust [209.85.208.54 listed in list.dnswl.org] -0.0 SPF_PASS SPF: sender matches SPF record 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature -0.0 T_DKIMWL_WL_MED DKIMwl.org - Whitelisted Medium sender X-Headers-End: 1falzF-00GkHw-LJ Subject: [Openvpn-devel] [PATCH v2 4/9] tls-crypt-v2: add specification to doc/ X-BeenThere: openvpn-devel@lists.sourceforge.net X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox From: Steffan Karger This is a preliminary description of tls-crypt-v2. It should give a good impression about the reasoning and design behind tls-crypt-v2, but might need some polishing and updating. Signed-off-by: Steffan Karger --- doc/tls-crypt-v2.txt | 164 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 doc/tls-crypt-v2.txt diff --git a/doc/tls-crypt-v2.txt b/doc/tls-crypt-v2.txt new file mode 100644 index 00000000..578b2f9b --- /dev/null +++ b/doc/tls-crypt-v2.txt @@ -0,0 +1,164 @@ +Client-specific tls-crypt keys (--tls-crypt-v2) +=============================================== + +This document describes the ``--tls-crypt-v2`` option, which enables OpenVPN +to use client-specific ``--tls-crypt`` keys. + +Rationale +--------- + +``--tls-auth`` and ``tls-crypt`` use a pre-shared group key, which is shared +among all clients and servers in an OpenVPN deployment. If any client or +server is compromised, the attacker will have access to this shared key, and it +will no longer provide any security. To reduce the risk of loosing pre-shared +keys, ``tls-crypt-v2`` adds the ability to supply each client with a unique +tls-crypt key. This allows large organisations and VPN providers to profit +from the same DoS and TLS stack protection that small deployments can already +achieve using ``tls-auth`` or ``tls-crypt``. + +Also, for ``tls-crypt``, even if all these peers succeed in keeping the key +secret, the key lifetime is limited to roughly 8000 years, divided by the +number of clients (see the ``--tls-crypt`` section of the man page). Using +client-specific keys, we lift this lifetime requirement to roughly 8000 years +for each client key (which "Should Be Enough For Everybody (tm)"). + + +Introduction +------------ + +``tls-crypt-v2`` uses an encrypted cookie mechanism to introduce +client-specific tls-crypt keys without introducing a lot of server-side state. +The client-specific key is encrypted using a server key. The server key is the +same for all servers in a group. When a client connects, it first sends the +encrypted key to the server, such that the server can decrypt the key and all +messages can thereafter be encrypted using the client-specific key. + +A wrapped (encrypted and authenticated) client-specific key can also contain +metadata. The metadata is wrapped together with the key, and can be used to +allow servers to identify clients and/or key validity. This allows the server +to abort the connection immediately after receiving the first packet, rather +than performing an entire TLS handshake. Aborting the connection this early +greatly improves the DoS resilience and reduces attack service against +malicious clients that have the ``tls-crypt`` or ``tls-auth`` key. This is +particularly relevant for large deployments (think lost key or disgruntled +employee) and VPN providers (clients are not trusted). + +To allow for a smooth transition, ``tls-crypt-v2`` is designed such that a +server can enable both ``tls-crypt-v2`` and either ``tls-crypt`` or +``tls-auth``. This is achieved by introducing a P_CONTROL_HARD_RESET_CLIENT_V3 +opcode, that indicates that the client wants to use ``tls-crypt-v2`` for the +current connection. + +For an exact specification and more details, read the Implementation section. + + +Implementation +-------------- + +When setting up a tls-crypt-v2 group (similar to generating a tls-crypt or +tls-auth key previously): + +1. Generate a tls-crypt-v2 server key using OpenVPN's ``--genkey``. This key + contains 4 512-bit keys, of which we use: + + * the first 256 bits of key 1 as AES-256-CTR encryption key ``Ke`` + * the first 256 bits of key 2 as HMAC-SHA-256 authentication key ``Ka`` + +2. Add the tls-crypt-v2 server key to all server configs + (``tls-crypt-v2 /path/to/server.key``) + + +When provisioning a client, create a client-specific tls-crypt key: + +1. Generate 2048 bits client-specific key ``Kc`` +2. Optionally generate metadata +3. Create a wrapped client key ``WKc``, using the same nonce-misuse-resistant + SIV conruction we use for tls-crypt: + + ``T = HMAC-SHA256(Ka, Kc || metadata)`` + + ``IV = 128 most significant bits of T`` + + ``WKc = T || AES-256-CTR(Ke, IV, Kc || metadata)`` + +4. Create a tls-crypt-v2 client key: PEM-encode ``Kc || WKc`` and store in a + file, using the header ``-----BEGIN OpenVPN tls-crypt-v2 client key-----`` + and the footer ``-----END OpenVPN tls-crypt-v2 client key-----``. (The PEM + format is simple, and following PEM allows us to use the crypto lib function + for en/decoding.) +5. Add the tls-crypt-v2 client key to the client config + (``tls-crypt-v2 /path/to/client-specific.key``) + + +When setting up the openvpn connection: + +1. The client reads the tls-crypt-v2 key from its config, and: + + 1. loads ``Kc`` as its tls-crypt key, + 2. stores ``WKc`` in memory for sending to the server. + +2. To start the connection, the client creates a P_CONTROL_HARD_RESET_CLIENT_V3 + message without payload, wraps it with tls-crypt using ``Kc`` as the key, + and appends ``WKc``. (``WKc`` must not be encrypted, to prevent a + chicken-and-egg problem.) + +3. The server receives the P_CONTROL_HARD_RESET_CLIENT_V3 message, and + + a. unwraps ``WKc`` and strips ``WKc`` from the message. + b. uses unwrapped ``Kc`` to verify the remaining + P_CONTROL_HARD_RESET_CLIENT_V3 message's authentication. + + The message is dropped and no error response is sent when either a or b + fails (DoS protection). + +4. Server optionally checks metadata using a --tls-crypt-v2-verify script + + Metadata could for example contain the users certificate serial, such that + the incoming connection can be verified against a CRL, or a notAfter + timestamp that limits the key's validity period. + + This allows early abort of connection, *before* we expose any of the + notoriously dangerous TLS, X.509 and ASN.1 parsers and thereby reduces the + attack surface of the server. + + The metadata is checked *after* the OpenVPN three-way handshake has + completed, to prevent DoS attacks. (That is, once the client has proved to + the server that it possesses Kc, by authenticating a packet that contains the + session ID picked by the server.) + + RFC: should the server send a 'key rejected' message if the key is e.g. + revoked or expired? That allows for better client-side error reporting, but + also reduces the DoS resilience. + +6. Client and server use ``Kc`` for (un)wrapping any following control channel + messages. + + +Considerations +-------------- + +To allow for a smooth transition, the server implementation allows +``tls-crypt`` or ``tls-auth`` to be used simultaneously with ``tls-crypt-v2``. +This specification does not allow simultaneously using ``tls-crypt-v2`` and +connections without any control channel wrapping, because that would break DoS +resilience. RFC: should we add an option along the lines of +--tls-crypt-v2-allow-insecure-fallback to allow admins to enable this anyway? +It might help with transitioning. + +``tls-crypt-v2`` uses fixed crypto algorithms, because: + + * The crypto is used before we can do any negotiation, so the algorithms have + to be predefined. + * The crypto primitives are chosen conservatively, making problems with these + primitives unlikely. + * Making anything configurable adds complexity, both in implementation and + usage. We should not add anymore complexity than is absolutely necessary. + +Potential ``tls-crypt-v2`` risks: + + * Slightly more work on first connection (``WKc`` unwrap + hard reset unwrap) + than with ``tls-crypt`` (hard reset unwrap) or ``tls-auth`` (hard reset auth). + * Flexible metadata allow mistakes + (So we should make it easy to do it right. Provide tooling to create client + keys based on cert serial + CA fingerprint, provide script that uses CRL (if + available) to drop revoked keys.) From patchwork Wed Jul 4 07:54:00 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Steffan Karger X-Patchwork-Id: 405 Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director8.mail.ord1d.rsapps.net ([172.27.255.59]) by backend30.mail.ord1d.rsapps.net (Dovecot) with LMTP id E5Z/BygKPVuSBwAAIUCqbw for ; Wed, 04 Jul 2018 13:55:52 -0400 Received: from proxy5.mail.iad3a.rsapps.net ([172.27.255.59]) by director8.mail.ord1d.rsapps.net (Dovecot) with LMTP id 4wbRASgKPVsfJgAAfY0hYg ; Wed, 04 Jul 2018 13:55:52 -0400 Received: from smtp12.gate.iad3a ([172.27.255.59]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy5.mail.iad3a.rsapps.net with LMTP id mKCBOycKPVvpdAAAhn5joQ ; Wed, 04 Jul 2018 13:55:52 -0400 X-Spam-Threshold: 95 X-Spam-Score: 0 X-Spam-Flag: NO X-Virus-Scanned: OK X-Orig-To: openvpnslackdevel@openvpn.net X-Originating-Ip: [216.105.38.7] Authentication-Results: smtp12.gate.iad3a.rsapps.net; iprev=pass policy.iprev="216.105.38.7"; spf=pass smtp.mailfrom="openvpn-devel-bounces@lists.sourceforge.net" smtp.helo="lists.sourceforge.net"; dkim=fail (signature verification failed) header.d=sourceforge.net; dkim=fail (signature verification failed) header.d=sf.net; dkim=fail (signature verification failed) header.d=karger-me.20150623.gappssmtp.com; dmarc=none (p=nil; dis=none) header.from=karger.me X-Suspicious-Flag: YES X-Classification-ID: 7cc3b394-7fb3-11e8-b316-525400068c1c-1-1 Received: from [216.105.38.7] ([216.105.38.7:14150] helo=lists.sourceforge.net) by smtp12.gate.iad3a.rsapps.net (envelope-from ) (ecelerity 4.2.1.56364 r(Core:4.2.1.14)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id 05/9A-23881-62A0D3B5; Wed, 04 Jul 2018 13:55:51 -0400 Received: from [127.0.0.1] (helo=sfs-ml-4.v29.lw.sourceforge.com) by sfs-ml-4.v29.lw.sourceforge.com with esmtp (Exim 4.90_1) (envelope-from ) id 1falzL-0001Pm-P4; Wed, 04 Jul 2018 17:54:47 +0000 Received: from [172.30.20.202] (helo=mx.sourceforge.net) by sfs-ml-4.v29.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.90_1) (envelope-from ) id 1falzI-0001PP-Rt for openvpn-devel@lists.sourceforge.net; Wed, 04 Jul 2018 17:54:44 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=References:In-Reply-To:Message-Id:Date:Subject:Cc: To:From:Sender:Reply-To:MIME-Version:Content-Type:Content-Transfer-Encoding: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=VOOZZpzDTVFrK+NtB8bDKRm+XWejRfj2G12SKfQhyLs=; b=VjCHelFYBoMdMFu/G7oM/r6bHB guDjm+R7Cw+ncPV80SdEia8Cq+eL0wQ+DSYo2rE7ir/wrNSjzUwFx1rDvpSNKBEdtHvzD169WUhRe Q5z/ud+3kc+gNPPKIq0lrYi8qMXdClS38v+lTiKnZGqNJ6T1iict4/mPoNZoVKuG049w=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=References:In-Reply-To:Message-Id:Date:Subject:Cc:To:From:Sender:Reply-To :MIME-Version:Content-Type:Content-Transfer-Encoding:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=VOOZZpzDTVFrK+NtB8bDKRm+XWejRfj2G12SKfQhyLs=; b=bVC4f0vsZge9BEfeI6LJJ7MOtD 08iAAOydnIqGFDaq9FnVxF7WePdjlJVN+sCvbfvtIfuSI/lrw4V6Y4fDjeth/xMjd7aRiMWvXCOTM 9Z/Pl1scMQsc1AImhUlRIq09qo690NZv7tEp3Z5tIrJADDmUKeigsnUA3TILHNpBQF2w=; Received: from mail-ed1-f65.google.com ([209.85.208.65]) by sfi-mx-1.v28.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES128-GCM-SHA256:128) (Exim 4.90_1) id 1falzG-00HLKl-PD for openvpn-devel@lists.sourceforge.net; Wed, 04 Jul 2018 17:54:44 +0000 Received: by mail-ed1-f65.google.com with SMTP id e19-v6so4568235edq.7 for ; Wed, 04 Jul 2018 10:54:42 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=karger-me.20150623.gappssmtp.com; s=20150623; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=VOOZZpzDTVFrK+NtB8bDKRm+XWejRfj2G12SKfQhyLs=; b=FvxPb+/BBPuPB7h8SYIoAslvM2w4l9QWGd4MQbKfaE+BY0Emt5Bj3KCAPzkAsbQuMl WDOZEZkEol0kjgR68HMDLeAq/gQE36zBoGB1JASEBh/OGnlLGggING9/2xwfaBXI/WtN JYuuGG+Q1m/gMyOgbQdvWTG1lUC4bqqtPNrdNK3UbUIaDtCNAxoTlzq3G4+Jwg9hzIWY aHYzLe5VijzVXXPicclkKRqrL9cY83BopMDAFXB8i6CqvhyJ0lmjj4Y2JwKfqnKgGVMG HZhhJeJFu7qqd+9zCaozyu7aPRby0WVO/f1Pkp/3plrwHs/WBQYYj83OJfdqEh59wNj5 yvdg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=VOOZZpzDTVFrK+NtB8bDKRm+XWejRfj2G12SKfQhyLs=; b=iDd8PrpjUAv1DBGWGpgdo0b/n7ZZaVZWMxozlkUSL3YBSw+GKdt7dtOzANxX9vxZo0 iM9neegOF3DVDsBp81ohNnwQpZYUsD65NHWOMjEh0mv6fE0rJiXO8ggNmyA2yLud8bd7 U9lTBj44xbG58ffP0FU9VWoG3YnUaR/rW4MVkGERpbK1UsplXQfcNusVikOYDO2jvjbf TWbXQ5nEAQcmgb8sz3iec2l+hKX5z+iXaZtai8b9KNtj9S3W74Knj2on2Ayh64jYL/P8 PTBrTPfQgaNW/Yd2pV6/8yOWZBXLPQcYMKJlTHSTU3uQaVSu5cY7XwuWSgzYiDlZ4rSL XhOw== X-Gm-Message-State: APt69E1SfeNPHxumnakUR0wPwQidOaXt05YBybosGHwZ9KFVgPiaZMF7 9g88KlgpOfuplLZ6w/JURVd1zXqUI/c= X-Google-Smtp-Source: AAOMgpe2R26kRqqZqnxttTm7xxDP8l2JQfLnnmPrerwOq6s5x65qhYGALSZ8jSFU4ecqDNcPAqsPqg== X-Received: by 2002:a50:8b66:: with SMTP id l93-v6mr3698629edl.44.1530726875709; Wed, 04 Jul 2018 10:54:35 -0700 (PDT) Received: from vesta.fritz.box ([2001:985:e54:1:f598:331e:3cdf:2649]) by smtp.gmail.com with ESMTPSA id o2-v6sm1948961edd.84.2018.07.04.10.54.34 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Wed, 04 Jul 2018 10:54:35 -0700 (PDT) From: Steffan Karger To: openvpn-devel@lists.sourceforge.net Date: Wed, 4 Jul 2018 19:54:00 +0200 Message-Id: <20180704175404.22371-5-steffan@karger.me> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20180704175404.22371-1-steffan@karger.me> References: <1512734870-17133-1-git-send-email-steffan.karger@fox-it.com> <20180704175404.22371-1-steffan@karger.me> X-Spam-Report: Spam Filtering performed by mx.sourceforge.net. See http://spamassassin.org/tag/ for more details. -0.0 RCVD_IN_DNSWL_NONE RBL: Sender listed at http://www.dnswl.org/, no trust [209.85.208.65 listed in list.dnswl.org] -0.0 RCVD_IN_MSPIKE_H2 RBL: Average reputation (+2) [209.85.208.65 listed in wl.mailspike.net] -0.0 SPF_PASS SPF: sender matches SPF record 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature -0.0 T_DKIMWL_WL_MED DKIMwl.org - Whitelisted Medium sender X-Headers-End: 1falzG-00HLKl-PD Subject: [Openvpn-devel] [PATCH v2 5/9] tls-crypt-v2: generate client keys X-BeenThere: openvpn-devel@lists.sourceforge.net X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox From: Steffan Karger As a first step towards a full tls-crypt-v2 implementation, add functionality to generate tls-crypt-v2 client keys. Signed-off-by: Steffan Karger --- doc/openvpn.8 | 51 ++++++++ src/openvpn/buffer.c | 63 +++++++++ src/openvpn/buffer.h | 6 + src/openvpn/init.c | 35 ++++- src/openvpn/integer.h | 10 ++ src/openvpn/options.c | 37 ++++++ src/openvpn/options.h | 9 ++ src/openvpn/tls_crypt.c | 284 ++++++++++++++++++++++++++++++++++++++++ src/openvpn/tls_crypt.h | 81 ++++++++++-- tests/t_lpback.sh | 40 +++++- 10 files changed, 600 insertions(+), 16 deletions(-) diff --git a/doc/openvpn.8 b/doc/openvpn.8 index 46ea58b6..abefa86a 100644 --- a/doc/openvpn.8 +++ b/doc/openvpn.8 @@ -5245,6 +5245,57 @@ degrading to the same security as using That is, the control channel still benefits from the extra protection against active man\-in\-the\-middle\-attacks and DoS attacks, but may no longer offer extra privacy and post\-quantum security on top of what TLS itself offers. + +For large setups or setups where clients are not trusted, consider using +.B \-\-tls\-crypt\-v2 +instead. That uses per\-client unique keys, and thereby improves the bounds to +\fR'rotate a client key at least once per 8000 years'. +.\"********************************************************* +.TP +.B \-\-tls\-crypt\-v2 keyfile + +Use client\-specific tls\-crypt keys. + +For clients, +.B keyfile +is a client\-specific tls\-crypt key. Such a key can be generated using the +.B \-\-tls\-crypt\-v2\-genkey +option. + +For servers, +.B keyfile +is used to unwrap client\-specific keys supplied by the client during connection +setup. This key must be the same as the key used to generate the +client\-specific key (see +.B \-\-tls\-crypt\-v2\-genkey\fR). + +On servers, this option can be used together with the +.B \-\-tls\-auth +or +.B \-\-tls\-crypt +option. In that case, the server will detect whether the client is using +client\-specific keys, and automatically select the right mode. +.\"********************************************************* +.TP +.B \-\-tls\-crypt\-v2\-genkey client|server keyfile [metadata] + +If the first parameter equals "server", generate a \-\-tls\-crypt\-v2 server +key and store the key in +.B keyfile\fR. + + +If the first parameter equals "client", generate a \-\-tls\-crypt\-v2 client +key, and store the key in +.B keyfile\fR. + +If supplied, include the supplied +.B metadata +in the wrapped client key. This metadata must be supplied in base64\-encoded +form. The metadata must be at most 735 bytes long (980 bytes in base64). + +.B TODO +Metadata handling is not yet implemented. This text will be updated by the +commit that introduces metadata handling. .\"********************************************************* .TP .B \-\-askpass [file] diff --git a/src/openvpn/buffer.c b/src/openvpn/buffer.c index becfeb93..ca32618b 100644 --- a/src/openvpn/buffer.c +++ b/src/openvpn/buffer.c @@ -315,6 +315,69 @@ convert_to_one_line(struct buffer *buf) } } +bool +buffer_write_file(const char *filename, const struct buffer *buf) +{ + bool ret = false; + int fd = platform_open(filename, O_CREAT | O_TRUNC | O_WRONLY, + S_IRUSR | S_IWUSR); + if (fd == -1) + { + msg(M_ERRNO, "Cannot open file '%s' for write", filename); + goto cleanup; + } + + const int size = write(fd, BPTR(buf), BLEN(buf)); + if (size != BLEN(buf)) + { + msg(M_ERRNO, "Write error on file '%s'", filename); + goto cleanup; + } + + ret = true; +cleanup: + if (close(fd)) + { + msg(M_ERRNO, "Close error on file %s", filename); + ret = false; + } + return ret; +} + +struct buffer +buffer_read_from_file(const char *filename, struct gc_arena *gc) +{ + struct buffer ret = { 0 }; + + platform_stat_t file_stat = {0}; + if (platform_stat(filename, &file_stat) < 0) + { + return ret; + } + + FILE *fp = platform_fopen(filename, "r"); + if (!fp) + { + return ret; + } + + const size_t size = file_stat.st_size; + ret = alloc_buf_gc(size + 1, gc); /* Space for trailing \0 */ + ssize_t read_size = fread(BPTR(&ret), 1, size, fp); + if (read_size < 0) + { + free_buf(&ret); + goto cleanup; + } + ASSERT(buf_inc_len(&ret, read_size)); + buf_null_terminate(&ret); + +cleanup: + fclose(fp); + return ret; +} + + /* NOTE: requires that string be null terminated */ void buf_write_string_file(const struct buffer *buf, const char *filename, int fd) diff --git a/src/openvpn/buffer.h b/src/openvpn/buffer.h index d848490a..9ab60420 100644 --- a/src/openvpn/buffer.h +++ b/src/openvpn/buffer.h @@ -469,6 +469,12 @@ const char *skip_leading_whitespace(const char *str); void string_null_terminate(char *str, int len, int capacity); +/** Write buffer contents to file */ +bool buffer_write_file(const char *filename, const struct buffer *buf); + +/** Read file contents. Returns invalid buffer on error. */ +struct buffer buffer_read_from_file(const char *filename, struct gc_arena *gc); + /* * Write string in buf to file descriptor fd. * NOTE: requires that string be null terminated. diff --git a/src/openvpn/init.c b/src/openvpn/init.c index d28d1fd2..c1454f36 100644 --- a/src/openvpn/init.c +++ b/src/openvpn/init.c @@ -1028,6 +1028,11 @@ print_openssl_info(const struct options *options) bool do_genkey(const struct options *options) { + /* should we disable paging? */ + if (options->mlock && (options->genkey || options->tls_crypt_v2_genkey_file)) + { + platform_mlockall(true); + } if (options->genkey) { int nbits_written; @@ -1035,11 +1040,6 @@ do_genkey(const struct options *options) notnull(options->shared_secret_file, "shared secret output file (--secret)"); - if (options->mlock) /* should we disable paging? */ - { - platform_mlockall(true); - } - nbits_written = write_key_file(2, options->shared_secret_file); msg(D_GENKEY | M_NOPREFIX, @@ -1047,6 +1047,31 @@ do_genkey(const struct options *options) options->shared_secret_file); return true; } + if (options->tls_crypt_v2_genkey_type) + { + if(!strcmp(options->tls_crypt_v2_genkey_type, "server")) + { + tls_crypt_v2_write_server_key_file(options->tls_crypt_v2_genkey_file); + return true; + } + else if (options->tls_crypt_v2_genkey_type + && !strcmp(options->tls_crypt_v2_genkey_type, "client")) + { + if (!options->tls_crypt_v2_file) + { + msg(M_USAGE, "--tls-crypt-v2-gen-client-key requires a server key to be set via --tls-crypt-v2"); + } + + tls_crypt_v2_write_client_key_file(options->tls_crypt_v2_genkey_file, + options->tls_crypt_v2_metadata, options->tls_crypt_v2_file, + options->tls_crypt_v2_inline); + return true; + } + else + { + msg(M_USAGE, "--tls-crypt-v2-genkey type should be \"client\" or \"server\""); + } + } return false; } diff --git a/src/openvpn/integer.h b/src/openvpn/integer.h index a7e19d35..b1ae0eda 100644 --- a/src/openvpn/integer.h +++ b/src/openvpn/integer.h @@ -26,6 +26,16 @@ #include "error.h" +#ifndef htonll +#define htonll(x) ((1==htonl(1)) ? (x) : \ + ((uint64_t)htonl((x) & 0xFFFFFFFF) << 32) | htonl((x) >> 32)) +#endif + +#ifndef ntohll +#define ntohll(x) ((1==ntohl(1)) ? (x) : \ + ((uint64_t)ntohl((x) & 0xFFFFFFFF) << 32) | ntohl((x) >> 32)) +#endif + /* * min/max functions */ diff --git a/src/openvpn/options.c b/src/openvpn/options.c index b89f4ba2..c1d53a96 100644 --- a/src/openvpn/options.c +++ b/src/openvpn/options.c @@ -622,6 +622,13 @@ static const char usage_message[] = " attacks on the TLS stack and DoS attacks.\n" " key (required) provides the pre-shared key file.\n" " see --secret option for more info.\n" + "--tls-crypt-v2 key : For clients: use key as a client-specific tls-crypt key.\n" + " For servers: use key to decrypt client-specific keys. For\n" + " key generation (--tls-crypt-v2-genkey): use key to\n" + " encrypt generated client-specific key. (See --tls-crypt.)\n" + "--tls-crypt-v2-genkey client|server keyfile [base64 metadata]: Generate a\n" + " fresh tls-crypt-v2 client or server key, and store to\n" + " keyfile. If supplied, include metadata in wrapped key.\n" "--askpass [file]: Get PEM password from controlling tty before we daemonize.\n" "--auth-nocache : Don't cache --askpass or --auth-user-pass passwords.\n" "--crl-verify crl ['dir']: Check peer certificate against a CRL.\n" @@ -1789,6 +1796,10 @@ show_settings(const struct options *o) SHOW_STR(tls_auth_file); SHOW_STR(tls_crypt_file); + SHOW_STR(tls_crypt_v2_file); + SHOW_STR(tls_crypt_v2_genkey_type); + SHOW_STR(tls_crypt_v2_genkey_file); + SHOW_STR(tls_crypt_v2_metadata); #ifdef ENABLE_PKCS11 { @@ -2762,6 +2773,7 @@ options_postprocess_verify_ce(const struct options *options, const struct connec MUST_BE_UNDEF(transition_window); MUST_BE_UNDEF(tls_auth_file); MUST_BE_UNDEF(tls_crypt_file); + MUST_BE_UNDEF(tls_crypt_v2_file); MUST_BE_UNDEF(single_session); MUST_BE_UNDEF(push_peer_info); MUST_BE_UNDEF(tls_exit); @@ -3290,6 +3302,12 @@ options_postprocess_filechecks(struct options *options) options->tls_auth_file, R_OK, "--tls-auth"); errs |= check_file_access(CHKACC_FILE|CHKACC_INLINE|CHKACC_PRIVATE, options->tls_crypt_file, R_OK, "--tls-crypt"); + errs |= check_file_access(CHKACC_FILE|CHKACC_INLINE|CHKACC_PRIVATE, + options->tls_crypt_v2_file, R_OK, + "--tls-crypt-v2"); + errs |= check_file_access(CHKACC_FILE|CHKACC_INLINE|CHKACC_PRIVATE, + options->tls_crypt_v2_genkey_file, R_OK, + "--tls-crypt-v2-genkey"); errs |= check_file_access(CHKACC_FILE|CHKACC_INLINE|CHKACC_PRIVATE, options->shared_secret_file, R_OK, "--secret"); errs |= check_file_access(CHKACC_DIRPATH|CHKACC_FILEXSTWR, @@ -8023,6 +8041,25 @@ add_option(struct options *options, } options->tls_crypt_file = p[1]; } + else if (streq(p[0], "tls-crypt-v2") && p[1] && !p[3]) + { + VERIFY_PERMISSION(OPT_P_GENERAL); + if (streq(p[1], INLINE_FILE_TAG) && p[2]) + { + options->tls_crypt_v2_inline = p[2]; + } + options->tls_crypt_v2_file = p[1]; + } + else if (streq(p[0], "tls-crypt-v2-genkey") && p[2] && !p[4]) + { + VERIFY_PERMISSION(OPT_P_GENERAL); + options->tls_crypt_v2_genkey_type = p[1]; + options->tls_crypt_v2_genkey_file = p[2]; + if (p[3]) + { + options->tls_crypt_v2_metadata = p[3]; + } + } else if (streq(p[0], "key-method") && p[1] && !p[2]) { int key_method; diff --git a/src/openvpn/options.h b/src/openvpn/options.h index 3a6c33f8..d5407b5b 100644 --- a/src/openvpn/options.h +++ b/src/openvpn/options.h @@ -567,6 +567,15 @@ struct options const char *tls_crypt_file; const char *tls_crypt_inline; + /* Client-specific secret or server key used for TLS control channel + * authenticated encryption v2 */ + const char *tls_crypt_v2_file; + const char *tls_crypt_v2_inline; + + const char *tls_crypt_v2_genkey_type; + const char *tls_crypt_v2_genkey_file; + const char *tls_crypt_v2_metadata; + /* Allow only one session */ bool single_session; diff --git a/src/openvpn/tls_crypt.c b/src/openvpn/tls_crypt.c index 36ead84d..6f9e02ef 100644 --- a/src/openvpn/tls_crypt.c +++ b/src/openvpn/tls_crypt.c @@ -29,11 +29,21 @@ #include "syshead.h" +#include "base64.h" #include "crypto.h" +#include "platform.h" #include "session_id.h" #include "tls_crypt.h" +const char *tls_crypt_v2_cli_pem_name = "OpenVPN tls-crypt-v2 client key"; +const char *tls_crypt_v2_srv_pem_name = "OpenVPN tls-crypt-v2 server key"; + +/** Metadata contains user-specified data */ +static const uint8_t TLS_CRYPT_METADATA_TYPE_USER = 0x00; +/** Metadata contains a 64-bit unix timestamp in network byte order */ +static const uint8_t TLS_CRYPT_METADATA_TYPE_TIMESTAMP = 0x01; + static struct key_type tls_crypt_kt(void) { @@ -264,3 +274,277 @@ error_exit: gc_free(&gc); return false; } + +static inline bool +tls_crypt_v2_read_keyfile(struct buffer *key, const char *pem_name, + const char *key_file, const char *key_inline) +{ + bool ret = false; + struct buffer key_pem = { 0 }; + struct gc_arena gc = gc_new(); + + if (strcmp(key_file, INLINE_FILE_TAG)) + { + key_pem = buffer_read_from_file(key_file, &gc); + if (!buf_valid(&key_pem)) + { + msg(M_WARN, "ERROR: failed to read tls-crypt-v2 key file (%s)", + key_file); + goto cleanup; + } + } + else + { + buf_set_read(&key_pem, (const void *)key_inline, strlen(key_inline)); + } + + if (!crypto_pem_decode(pem_name, key, &key_pem)) + { + msg(M_WARN, "ERROR: tls-crypt-v2 pem decode failed"); + goto cleanup; + } + + ret = true; +cleanup: + if (strcmp(key_file, INLINE_FILE_TAG)) + { + buf_clear(&key_pem); + } + gc_free(&gc); + return ret; +} + +static inline void +tls_crypt_v2_load_client_key(struct key_ctx_bi *key, const struct key2 *key2, + bool tls_server) +{ + const int key_direction = tls_server ? + KEY_DIRECTION_NORMAL : KEY_DIRECTION_INVERSE; + struct key_type kt = tls_crypt_kt(); + if (!kt.cipher || !kt.digest) + { + msg (M_FATAL, "ERROR: --tls-crypt not supported"); + } + init_key_ctx_bi(key, key2, key_direction, &kt, + "Control Channel Encryption"); +} + +void +tls_crypt_v2_init_client_key(struct key_ctx_bi *key, struct buffer *wkc_buf, + const char *key_file, const char *key_inline) +{ + struct buffer client_key = alloc_buf(TLS_CRYPT_V2_CLIENT_KEY_LEN + + TLS_CRYPT_V2_MAX_WKC_LEN); + + if (!tls_crypt_v2_read_keyfile(&client_key, tls_crypt_v2_cli_pem_name, + key_file, key_inline)) + { + msg(M_FATAL, "ERROR: invalid tls-crypt-v2 client key format"); + } + + struct key2 key2; + if (!buf_read(&client_key, &key2.keys, sizeof(key2.keys))) + { + msg (M_FATAL, "ERROR: not enough data in tls-crypt-v2 client key"); + } + + tls_crypt_v2_load_client_key(key, &key2, false); + secure_memzero(&key2, sizeof(key2)); + + *wkc_buf = client_key; +} + +void +tls_crypt_v2_init_server_key(struct key_ctx *key_ctx, bool encrypt, + const char *key_file, const char *key_inline) +{ + struct key srv_key; + struct buffer srv_key_buf; + + buf_set_write(&srv_key_buf, (void *) &srv_key, sizeof(srv_key)); + if (!tls_crypt_v2_read_keyfile(&srv_key_buf, tls_crypt_v2_srv_pem_name, + key_file, key_inline)) + { + msg(M_FATAL, "ERROR: invalid tls-crypt-v2 server key format"); + } + + struct key_type kt = tls_crypt_kt(); + if (!kt.cipher || !kt.digest) + { + msg (M_FATAL, "ERROR: --tls-crypt not supported"); + } + init_key_ctx(key_ctx, &srv_key, &kt, encrypt, "tls-crypt-v2 server key"); + secure_memzero(&srv_key, sizeof(srv_key)); +} + +static bool +tls_crypt_v2_wrap_client_key(struct buffer *wkc, + const struct key2 *src_key, + const struct buffer *src_metadata, + struct key_ctx *server_key, struct gc_arena *gc) +{ + cipher_ctx_t *cipher_ctx = server_key->cipher; + struct buffer work = alloc_buf_gc(TLS_CRYPT_V2_MAX_WKC_LEN + + cipher_ctx_block_size(cipher_ctx), gc); + + /* Calculate auth tag and synthetic IV */ + uint8_t *tag = buf_write_alloc(&work, TLS_CRYPT_TAG_SIZE); + if (!tag) + { + msg (M_WARN, "ERROR: could not write tag"); + return false; + } + hmac_ctx_t *hmac_ctx = server_key->hmac; + hmac_ctx_reset(hmac_ctx); + hmac_ctx_update(hmac_ctx, (void*)src_key->keys, sizeof(src_key->keys)); + hmac_ctx_update(hmac_ctx, BPTR(src_metadata), BLEN(src_metadata)); + hmac_ctx_final(hmac_ctx, tag); + + dmsg(D_CRYPTO_DEBUG, "TLS-CRYPT WRAP TAG: %s", + format_hex(tag, TLS_CRYPT_TAG_SIZE, 0, gc)); + + /* Use the 128 most significant bits of the tag as IV */ + ASSERT(cipher_ctx_reset(cipher_ctx, tag)); + + /* Overflow check (OpenSSL requires an extra block in the dst buffer) */ + if (buf_forward_capacity(&work) < (sizeof(src_key->keys) + + BLEN(src_metadata) + + cipher_ctx_block_size(cipher_ctx))) + { + msg (M_WARN, "ERROR: could not crypt: insufficient space in dst"); + return false; + } + + /* Encrypt */ + int outlen = 0; + ASSERT(cipher_ctx_update(cipher_ctx, BEND(&work), &outlen, + (void*)src_key->keys, sizeof(src_key->keys))); + ASSERT(buf_inc_len(&work, outlen)); + ASSERT(cipher_ctx_update(cipher_ctx, BEND(&work), &outlen, + BPTR(src_metadata), BLEN(src_metadata))); + ASSERT(buf_inc_len(&work, outlen)); + ASSERT(cipher_ctx_final(cipher_ctx, BEND(&work), &outlen)); + ASSERT(buf_inc_len(&work, outlen)); + + return buf_copy(wkc, &work); +} + +void +tls_crypt_v2_write_server_key_file(const char *filename) +{ + struct gc_arena gc = gc_new(); + struct key server_key = { 0 }; + struct buffer server_key_buf = clear_buf(); + struct buffer server_key_pem = clear_buf(); + + if (!rand_bytes((void *)&server_key, sizeof(server_key))) + { + msg(M_NONFATAL, "ERROR: could not generate random key"); + goto cleanup; + } + buf_set_read(&server_key_buf, (void *) &server_key, sizeof(server_key)); + if (!crypto_pem_encode(tls_crypt_v2_srv_pem_name, &server_key_pem, + &server_key_buf, &gc)) + { + msg(M_WARN, "ERROR: could not PEM-encode client key"); + goto cleanup; + } + + if (!buffer_write_file(filename, &server_key_pem)) + { + msg(M_ERR, "ERROR: could not write server key file"); + goto cleanup; + } + +cleanup: + secure_memzero(&server_key, sizeof(server_key)); + buf_clear(&server_key_pem); + gc_free(&gc); + return; +} + +void +tls_crypt_v2_write_client_key_file(const char *filename, const char *b64_metadata, + const char *server_key_file, + const char *server_key_inline) +{ + struct gc_arena gc = gc_new(); + struct key_ctx server_key = { 0 }; + struct buffer client_key_pem = { 0 }; + struct buffer dst = alloc_buf_gc(TLS_CRYPT_V2_CLIENT_KEY_LEN + + TLS_CRYPT_V2_MAX_WKC_LEN, &gc); + + struct key2 client_key = { 2 }; + if (!rand_bytes((void*)client_key.keys, sizeof(client_key.keys))) + { + msg(M_FATAL, "ERROR: could not generate random key"); + goto cleanup; + } + ASSERT(buf_write(&dst, client_key.keys, sizeof(client_key.keys))); + + struct buffer metadata = alloc_buf_gc(TLS_CRYPT_V2_MAX_METADATA_LEN, &gc); + if (b64_metadata) + { + if (TLS_CRYPT_V2_MAX_B64_METADATA_LEN < strlen(b64_metadata)) + { + msg(M_FATAL, + "ERROR: metadata too long (%d bytes, max %u bytes)", + (int) strlen(b64_metadata), TLS_CRYPT_V2_MAX_B64_METADATA_LEN); + } + ASSERT(buf_write(&metadata, &TLS_CRYPT_METADATA_TYPE_USER, 1)); + int decoded_len = openvpn_base64_decode(b64_metadata, BPTR(&metadata), + BCAP(&metadata)); + if (decoded_len < 0) + { + msg(M_FATAL, "ERROR: failed to base64 decode provided metadata"); + goto cleanup; + } + ASSERT(buf_inc_len(&metadata, decoded_len)); + } + else + { + int64_t timestamp = htonll(now); + ASSERT(buf_write(&metadata, &TLS_CRYPT_METADATA_TYPE_TIMESTAMP, 1)); + ASSERT(buf_write(&metadata, ×tamp, sizeof(timestamp))); + } + + tls_crypt_v2_init_server_key(&server_key, true, server_key_file, + server_key_inline); + if (!tls_crypt_v2_wrap_client_key(&dst, &client_key, &metadata, &server_key, + &gc)) + { + msg (M_FATAL, "ERROR: could not wrap generated client key"); + goto cleanup; + } + + /* PEM-encode Kc || WKc */ + if (!crypto_pem_encode(tls_crypt_v2_cli_pem_name, &client_key_pem, &dst, + &gc)) + { + msg(M_FATAL, "ERROR: could not PEM-encode client key"); + goto cleanup; + } + + if (!buffer_write_file(filename, &client_key_pem)) + { + msg(M_FATAL, "ERROR: could not write client key file"); + goto cleanup; + } + + /* Sanity check: load client key (as "client") */ + struct key_ctx_bi test_client_key; + struct buffer test_wrapped_client_key; + msg (D_GENKEY, "Testing client-side key loading..."); + tls_crypt_v2_init_client_key(&test_client_key, &test_wrapped_client_key, + filename, NULL); + free_key_ctx_bi(&test_client_key); + free_buf(&test_wrapped_client_key); + +cleanup: + secure_memzero(&client_key, sizeof(client_key)); + free_key_ctx(&server_key); + buf_clear(&client_key_pem); + buf_clear(&dst); + + gc_free(&gc); +} diff --git a/src/openvpn/tls_crypt.h b/src/openvpn/tls_crypt.h index 067758ca..746f3be8 100644 --- a/src/openvpn/tls_crypt.h +++ b/src/openvpn/tls_crypt.h @@ -22,15 +22,13 @@ */ /** - * @defgroup tls_crypt Control channel encryption (--tls-crypt) + * @defgroup tls_crypt Control channel encryption (--tls-crypt, --tls-crypt-v2) * @ingroup control_tls * @{ * - * @par * Control channel encryption uses a pre-shared static key (like the --tls-auth * key) to encrypt control channel packets. * - * @par * Encrypting control channel packets has three main advantages: * - It provides more privacy by hiding the certificate used for the TLS * connection. @@ -38,11 +36,20 @@ * - It provides "poor-man's" post-quantum security, against attackers who * will never know the pre-shared key (i.e. no forward secrecy). * - * @par Specification + * --tls-crypt uses a tls-auth-style group key, where all servers and clients + * share the same group key. --tls-crypt-v2 adds support for client-specific + * keys, where all servers share the same client-key encryption key, and each + * clients receives a unique client key, both in plaintext and in encrypted + * form. When connecting to a server, the client sends the encrypted key to + * the server in the first packet (P_CONTROL_HARD_RESET_CLIENT_V3). The server + * then decrypts that key, and both parties can use the same client-specific + * key for tls-crypt packets. See doc/tls-crypt-v2.txt for more details. + * + * @par On-the-wire tls-crypt packet specification + * @parblock * Control channel encryption is based on the SIV construction [0], to achieve * nonce misuse-resistant authenticated encryption: * - * @par * \code{.unparsed} * msg = control channel plaintext * header = opcode (1 byte) || session_id (8 bytes) || packet_id (8 bytes) @@ -57,18 +64,17 @@ * output = Header || Tag || Ciph * \endcode * - * @par * This boils down to the following on-the-wire packet format: * - * @par * \code{.unparsed} * - opcode - || - session_id - || - packet_id - || auth_tag || * payload * * \endcode * - * @par * Where * - XXX - means authenticated, and * * XXX * means authenticated and encrypted. + * + * @endparblock */ #ifndef TLSCRYPT_H @@ -86,6 +92,15 @@ #define TLS_CRYPT_OFF_TAG (TLS_CRYPT_OFF_PID + TLS_CRYPT_PID_SIZE) #define TLS_CRYPT_OFF_CT (TLS_CRYPT_OFF_TAG + TLS_CRYPT_TAG_SIZE) +#define TLS_CRYPT_V2_MAX_WKC_LEN (1024) +#define TLS_CRYPT_V2_CLIENT_KEY_LEN (2048/8) +#define TLS_CRYPT_V2_SERVER_KEY_LEN (sizeof(struct key)) +#define TLS_CRYPT_V2_TAG_SIZE (TLS_CRYPT_TAG_SIZE) +#define TLS_CRYPT_V2_MAX_METADATA_LEN (TLS_CRYPT_V2_MAX_WKC_LEN \ + - (TLS_CRYPT_V2_CLIENT_KEY_LEN + TLS_CRYPT_V2_TAG_SIZE)) +#define TLS_CRYPT_V2_MAX_B64_METADATA_LEN \ + ((((TLS_CRYPT_V2_MAX_METADATA_LEN - 1) * 8) + 5) / 6) + /** * Initialize a key_ctx_bi structure for use with --tls-crypt. * @@ -138,6 +153,56 @@ bool tls_crypt_wrap(const struct buffer *src, struct buffer *dst, bool tls_crypt_unwrap(const struct buffer *src, struct buffer *dst, struct crypto_options *opt); +/** + * Initialize a tls-crypt-v2 server key (used to encrypt/decrypt client keys). + * + * @param key Key structure to be initialized. Must be non-NULL. + * @parem encrypt If true, initialize the key structure for encryption, + * otherwise for decryption. + * @param key_file File path of the key file to load, or INLINE tag. + * @param key_inline Inline key file contents (or NULL if not inline). + */ +void tls_crypt_v2_init_server_key(struct key_ctx *key_ctx, bool encrypt, + const char *key_file, const char *key_inline); + +/** + * Initialize a tls-crypt-v2 client key. + * + * @param key Key structure to be initialized with the client + * key. + * @param wrapped_key_buf Returns buffer containing the wrapped key that will + * be sent to the server when connecting. Caller must + * free this buffer when no longer neede. + * @param key_file File path of the key file to load, or INLINE tag. + * @param key_inline Inline key file contents (or NULL if not inline). + */ +void tls_crypt_v2_init_client_key(struct key_ctx_bi *key, + struct buffer *wrapped_key_buf, + const char *key_file, + const char *key_inline); + +/** + * Generate a tls-crypt-v2 server key, and write to file. + * + * @param filename Filename of the server key file to create. + */ +void tls_crypt_v2_write_server_key_file(const char *filename); + +/** + * Generate a tls-crypt-v2 client key, and write to file. + * + * @param filename Filename of the client key file to create. + * @param b64_metadata Base64 metadata to be included in the client key. + * @param server_key_file File path of the server key to use for wrapping the + * client key, or INLINE tag. + * @param server_key_inline Inline server key file contents (or NULL if not + * inline). + */ +void tls_crypt_v2_write_client_key_file(const char *filename, + const char *b64_metadata, + const char *key_file, + const char *key_inline); + /** @} */ #endif /* TLSCRYPT_H */ diff --git a/tests/t_lpback.sh b/tests/t_lpback.sh index 2052c626..107e7c1e 100755 --- a/tests/t_lpback.sh +++ b/tests/t_lpback.sh @@ -21,8 +21,8 @@ set -eu top_builddir="${top_builddir:-..}" -trap "rm -f key.$$ log.$$ ; trap 0 ; exit 77" 1 2 15 -trap "rm -f key.$$ log.$$ ; exit 1" 0 3 +trap "rm -f key.$$ tc-server-key.$$ tc-client-key.$$ log.$$ ; trap 0 ; exit 77" 1 2 15 +trap "rm -f key.$$ tc-server-key.$$ tc-client-key.$$ log.$$ ; exit 1" 0 3 # Get list of supported ciphers from openvpn --show-ciphers output CIPHERS=$(${top_builddir}/src/openvpn/openvpn --show-ciphers | \ @@ -55,6 +55,40 @@ do fi done -rm key.$$ log.$$ +echo -n "Testing tls-crypt-v2 server key generation..." +"${top_builddir}/src/openvpn/openvpn" \ + --tls-crypt-v2-genkey server tc-server-key.$$ >log.$$ 2>&1 +if [ $? != 0 ] ; then + echo "FAILED" + cat log.$$ + e=1 +else + echo "OK" +fi + +echo -n "Testing tls-crypt-v2 key generation (no metadata)..." +"${top_builddir}/src/openvpn/openvpn" --tls-crypt-v2 tc-server-key.$$ \ + --tls-crypt-v2-genkey client tc-client-key.$$ >log.$$ 2>&1 +if [ $? != 0 ] ; then + echo "FAILED" + cat log.$$ + e=1 +else + echo "OK" +fi + +echo -n "Testing tls-crypt-v2 key generation (max length metadata)..." +"${top_builddir}/src/openvpn/openvpn" --tls-crypt-v2 tc-server-key.$$ \ + --tls-crypt-v2-genkey client tc-client-key.$$ \ + $(head -c735 /dev/zero | base64 -w0) >log.$$ 2>&1 +if [ $? != 0 ] ; then + echo "FAILED" + cat log.$$ + e=1 +else + echo "OK" +fi + +rm key.$$ tc-server-key.$$ tc-client-key.$$ log.$$ trap 0 exit $e From patchwork Wed Jul 4 07:54:01 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Steffan Karger X-Patchwork-Id: 398 Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director8.mail.ord1d.rsapps.net ([172.27.255.52]) by backend30.mail.ord1d.rsapps.net (Dovecot) with LMTP id rXjJCxQKPVtncQAAIUCqbw for ; Wed, 04 Jul 2018 13:55:32 -0400 Received: from proxy21.mail.iad3a.rsapps.net ([172.27.255.52]) by director8.mail.ord1d.rsapps.net (Dovecot) with LMTP id axXgEBQKPVsmJgAAfY0hYg ; Wed, 04 Jul 2018 13:55:32 -0400 Received: from smtp15.gate.iad3a ([172.27.255.52]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy21.mail.iad3a.rsapps.net with LMTP id SFv9DhQKPVsMCgAASBQwCQ ; Wed, 04 Jul 2018 13:55:32 -0400 X-Spam-Threshold: 95 X-Spam-Score: 0 X-Spam-Flag: NO X-Virus-Scanned: OK X-Orig-To: openvpnslackdevel@openvpn.net X-Originating-Ip: [216.105.38.7] Authentication-Results: smtp15.gate.iad3a.rsapps.net; iprev=pass policy.iprev="216.105.38.7"; spf=pass smtp.mailfrom="openvpn-devel-bounces@lists.sourceforge.net" smtp.helo="lists.sourceforge.net"; dkim=fail (signature verification failed) header.d=sourceforge.net; dkim=fail (signature verification failed) header.d=sf.net; dkim=fail (signature verification failed) header.d=karger-me.20150623.gappssmtp.com; dmarc=none (p=nil; dis=none) header.from=karger.me X-Suspicious-Flag: YES X-Classification-ID: 7124fa98-7fb3-11e8-9b7d-525400f46865-1-1 Received: from [216.105.38.7] ([216.105.38.7:43064] helo=lists.sourceforge.net) by smtp15.gate.iad3a.rsapps.net (envelope-from ) (ecelerity 4.2.1.56364 r(Core:4.2.1.14)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id 6D/D8-08160-31A0D3B5; Wed, 04 Jul 2018 13:55:31 -0400 Received: from [127.0.0.1] (helo=sfs-ml-1.v29.lw.sourceforge.com) by sfs-ml-1.v29.lw.sourceforge.com with esmtp (Exim 4.90_1) (envelope-from ) id 1falzK-00004t-Gm; Wed, 04 Jul 2018 17:54:46 +0000 Received: from [172.30.20.202] (helo=mx.sourceforge.net) by sfs-ml-1.v29.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.90_1) (envelope-from ) id 1falzJ-0008WQ-4D for openvpn-devel@lists.sourceforge.net; Wed, 04 Jul 2018 17:54:45 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=References:In-Reply-To:Message-Id:Date:Subject:Cc: To:From:Sender:Reply-To:MIME-Version:Content-Type:Content-Transfer-Encoding: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=g2SXjCDivdW11/hYBzxxi1+oc7t2YK6eKuiAd01i28I=; b=eqjFTIp2XBjJgEBGab9OsYJDsN X2p3blKXK+iOTC9eK2Ai9gMmspzaabZjc3mYQqu86vQvteNVmdWMsK7wqU+zRQU4F6sCkIHAO8dUy XrEJlIHuiCht0V5L6gskmehw/Ylczj7uXVlP3/LWlhEPyXboDvVdlM7gBUqjWvSFcv9Y=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=References:In-Reply-To:Message-Id:Date:Subject:Cc:To:From:Sender:Reply-To :MIME-Version:Content-Type:Content-Transfer-Encoding:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=g2SXjCDivdW11/hYBzxxi1+oc7t2YK6eKuiAd01i28I=; b=ZUNY7iwuR/4T0zWPsKZ/PCFlhN KePW7aVBQyRmiZYybgkUV9qiha71rnE6He9ikBK4q4YnVHDcuwnrP9kz6NSX9hhrqUevSIJO0LCQu Jdhv6JMYVqXePZAdcl45fXIH3rLUSE662QudoMmyBsxF9D7Bddyaej7CRy2yGBSucF7U=; Received: from mail-ed1-f66.google.com ([209.85.208.66]) by sfi-mx-2.v28.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES128-GCM-SHA256:128) (Exim 4.90_1) id 1falzH-00GkI1-Bt for openvpn-devel@lists.sourceforge.net; Wed, 04 Jul 2018 17:54:45 +0000 Received: by mail-ed1-f66.google.com with SMTP id a5-v6so4576022edt.5 for ; Wed, 04 Jul 2018 10:54:43 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=karger-me.20150623.gappssmtp.com; s=20150623; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=g2SXjCDivdW11/hYBzxxi1+oc7t2YK6eKuiAd01i28I=; b=zN6fMKv/Hj0cSPF78+Dt1GU+AreEfQk4W1wVnVtFWuITwfUV0mGjdcbxwojOubHfIb iL6jVvjDMusAGba4MmTnNoQzaslDHZSI1yzSy3poi+gp7MHemLb/gLXNEAYdAaC5AiVT UgY0JTSWSgAsK7AP76ZnQyUQSjPSBzN5Lgo/Q7TxnCjI/R8CD9HI6iko17Khx0kAhIaN xhO4+JPcyEvbYkrcP/gGw0ATQ8XbTlNSrjQ1BsPDUNoSy0O+Z6UFbTPJ9bPPOwFgGSHE G45Djos3hT0H/YLtGgkfbbGsi8Umt8DQcJjav8en0hDy9dIxk1Pg2L3pRvTHlityBsLF uykA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=g2SXjCDivdW11/hYBzxxi1+oc7t2YK6eKuiAd01i28I=; b=Ml83M1GIis/V1eCBd8iGR/xYv04JcSG6e2XG7xjQCc1qdktalrRYcRkV4zGzDkzbOY pbKvtR++3qZDNBT7qp1WluFIWAWd2612J+hyZ9AcNbo9IB0FFRbWNdQQGNey0mYEPb1e 9rZL3sj2/XZnCQr6f9SWjAjDCxU0RxlC3WmCapdy57Xb2AoRlFTWJWI2boODoOG8c7qq lMIHPWUHS08BwQ9eLc1eYUjbGqxvc5jhDz4e8egOsD+b5B6WUPzlHoZLwVfiifmBJszJ M32TeNd8jyh3ux+GSQKQJBWw3/PHDQoT7Lmr0IJBgKyhbX0sIs+nESyV88jxhXi8D4PF j3UA== X-Gm-Message-State: APt69E3qx30PwwScdQE0QBh/3iAduGgY1/I2WcXn3k49TjkLkP/aJZLE enE9ouGdyYpY4FmncbobokqGhwxlyNQ= X-Google-Smtp-Source: AAOMgpcmSWWEGgF09gGsEWVx/jTgfbAyxpVvlHyhKONAV4qeKLIxaqyNLvwuXtmsiO6nEl9YkEYlHg== X-Received: by 2002:a50:cac7:: with SMTP id f7-v6mr3925306edi.22.1530726876445; Wed, 04 Jul 2018 10:54:36 -0700 (PDT) Received: from vesta.fritz.box ([2001:985:e54:1:f598:331e:3cdf:2649]) by smtp.gmail.com with ESMTPSA id o2-v6sm1948961edd.84.2018.07.04.10.54.35 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Wed, 04 Jul 2018 10:54:35 -0700 (PDT) From: Steffan Karger To: openvpn-devel@lists.sourceforge.net Date: Wed, 4 Jul 2018 19:54:01 +0200 Message-Id: <20180704175404.22371-6-steffan@karger.me> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20180704175404.22371-1-steffan@karger.me> References: <1512734870-17133-1-git-send-email-steffan.karger@fox-it.com> <20180704175404.22371-1-steffan@karger.me> X-Spam-Report: Spam Filtering performed by mx.sourceforge.net. See http://spamassassin.org/tag/ for more details. -0.0 RCVD_IN_DNSWL_NONE RBL: Sender listed at http://www.dnswl.org/, no trust [209.85.208.66 listed in list.dnswl.org] -0.0 RCVD_IN_MSPIKE_H2 RBL: Average reputation (+2) [209.85.208.66 listed in wl.mailspike.net] -0.0 SPF_PASS SPF: sender matches SPF record 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature -0.0 T_DKIMWL_WL_MED DKIMwl.org - Whitelisted Medium sender X-Headers-End: 1falzH-00GkI1-Bt Subject: [Openvpn-devel] [PATCH v2 6/9] tls-crypt-v2: add unwrap_client_key X-BeenThere: openvpn-devel@lists.sourceforge.net X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox From: Steffan Karger Add helper functions to unwrap tls-crypt-v2 client keys. Signed-off-by: Steffan Karger --- src/openvpn/buffer.h | 7 + src/openvpn/tls_crypt.c | 102 +++++++++ tests/unit_tests/openvpn/test_tls_crypt.c | 253 ++++++++++++++++++++-- 3 files changed, 342 insertions(+), 20 deletions(-) diff --git a/src/openvpn/buffer.h b/src/openvpn/buffer.h index 9ab60420..ce36279a 100644 --- a/src/openvpn/buffer.h +++ b/src/openvpn/buffer.h @@ -839,6 +839,13 @@ buf_read_u32(struct buffer *buf, bool *good) } } +/** Return true if buffer contents are equal */ +static inline bool +buf_equal(const struct buffer *a, const struct buffer *b) +{ + return BLEN(a) == BLEN(b) && 0 == memcmp(BPTR(a), BPTR(b), BLEN(a)); +} + /** * Compare src buffer contents with match. * *NOT* constant time. Do not use when comparing HMACs. diff --git a/src/openvpn/tls_crypt.c b/src/openvpn/tls_crypt.c index 6f9e02ef..cb0d5faf 100644 --- a/src/openvpn/tls_crypt.c +++ b/src/openvpn/tls_crypt.c @@ -429,6 +429,96 @@ tls_crypt_v2_wrap_client_key(struct buffer *wkc, return buf_copy(wkc, &work); } +static bool +tls_crypt_v2_unwrap_client_key(struct key2 *client_key, struct buffer *metadata, + struct buffer wrapped_client_key, + struct key_ctx *server_key) +{ + const char *error_prefix = __func__; + bool ret = false; + struct gc_arena gc = gc_new(); + /* The crypto API requires one extra cipher block of buffer head room when + * decrypting, which nicely matches the tag size of WKc. So + * TLS_CRYPT_V2_MAX_WKC_LEN is always large enough for the plaintext. */ + uint8_t plaintext_buf_data[TLS_CRYPT_V2_MAX_WKC_LEN] = { 0 }; + struct buffer plaintext = { 0 }; + + dmsg(D_TLS_DEBUG_MED, "%s: unwrapping client key (len=%d): %s", __func__, + BLEN(&wrapped_client_key), format_hex(BPTR(&wrapped_client_key), + BLEN(&wrapped_client_key), + 0, &gc)); + + if (TLS_CRYPT_V2_MAX_WKC_LEN < BLEN(&wrapped_client_key)) + { + CRYPT_ERROR("wrapped client key too big"); + } + + /* Decrypt client key and metadata */ + const uint8_t *tag = BPTR(&wrapped_client_key); + if (!buf_advance(&wrapped_client_key, TLS_CRYPT_TAG_SIZE)) + { + CRYPT_ERROR("failed to read tag"); + } + + if (!cipher_ctx_reset(server_key->cipher, tag)) + { + CRYPT_ERROR("failed to initialize IV"); + } + buf_set_write(&plaintext, plaintext_buf_data, sizeof(plaintext_buf_data)); + int outlen = 0; + if (!cipher_ctx_update(server_key->cipher, BPTR(&plaintext), &outlen, + BPTR(&wrapped_client_key), + BLEN(&wrapped_client_key))) + { + CRYPT_ERROR("could not decrypt client key"); + } + ASSERT(buf_inc_len(&plaintext, outlen)); + + if (!cipher_ctx_final(server_key->cipher, BEND(&plaintext), &outlen)) + { + CRYPT_ERROR("cipher final failed"); + } + ASSERT(buf_inc_len(&plaintext, outlen)); + + /* Check authentication */ + uint8_t tag_check[TLS_CRYPT_TAG_SIZE] = { 0 }; + hmac_ctx_reset(server_key->hmac); + hmac_ctx_update(server_key->hmac, BPTR(&plaintext), + BLEN(&plaintext)); + hmac_ctx_final(server_key->hmac, tag_check); + + if (memcmp_constant_time(tag, tag_check, sizeof(tag_check))) + { + dmsg(D_CRYPTO_DEBUG, "tag : %s", + format_hex(tag, sizeof(tag_check), 0, &gc)); + dmsg(D_CRYPTO_DEBUG, "tag_check: %s", + format_hex(tag_check, sizeof(tag_check), 0, &gc)); + CRYPT_ERROR("client key authentication error"); + } + + if (buf_len(&plaintext) < sizeof(client_key->keys)) + { + CRYPT_ERROR("failed to read client key"); + } + memcpy(&client_key->keys, BPTR(&plaintext), sizeof(client_key->keys)); + ASSERT(buf_advance(&plaintext, sizeof(client_key->keys))); + + if(!buf_copy(metadata, &plaintext)) + { + CRYPT_ERROR("metadata too large for supplied buffer"); + } + + ret = true; +error_exit: + if (!ret) + { + secure_memzero(client_key, sizeof(*client_key)); + } + buf_clear(&plaintext); + gc_free(&gc); + return ret; +} + void tls_crypt_v2_write_server_key_file(const char *filename) { @@ -538,6 +628,18 @@ tls_crypt_v2_write_client_key_file(const char *filename, const char *b64_metadat tls_crypt_v2_init_client_key(&test_client_key, &test_wrapped_client_key, filename, NULL); free_key_ctx_bi(&test_client_key); + + /* Sanity check: unwrap and load client key (as "server") */ + struct buffer test_metadata = alloc_buf_gc(TLS_CRYPT_V2_MAX_METADATA_LEN, + &gc); + struct key2 test_client_key2 = { 0 }; + free_key_ctx(&server_key); + tls_crypt_v2_init_server_key(&server_key, false, server_key_file, + server_key_inline); + msg (D_GENKEY, "Testing server-side key loading..."); + ASSERT(tls_crypt_v2_unwrap_client_key(&test_client_key2, &test_metadata, + test_wrapped_client_key, &server_key)); + secure_memzero(&test_client_key2, sizeof(test_client_key2)); free_buf(&test_wrapped_client_key); cleanup: diff --git a/tests/unit_tests/openvpn/test_tls_crypt.c b/tests/unit_tests/openvpn/test_tls_crypt.c index f3228adc..f53255cd 100644 --- a/tests/unit_tests/openvpn/test_tls_crypt.c +++ b/tests/unit_tests/openvpn/test_tls_crypt.c @@ -45,7 +45,7 @@ const char plaintext_short[1]; -struct test_context { +struct test_tls_crypt_context { struct crypto_options co; struct key_type kt; struct buffer source; @@ -54,8 +54,8 @@ struct test_context { }; static int -setup(void **state) { - struct test_context *ctx = calloc(1, sizeof(*ctx)); +test_tls_crypt_setup(void **state) { + struct test_tls_crypt_context *ctx = calloc(1, sizeof(*ctx)); *state = ctx; struct key key = { 0 }; @@ -84,8 +84,8 @@ setup(void **state) { } static int -teardown(void **state) { - struct test_context *ctx = (struct test_context *) *state; +test_tls_crypt_teardown(void **state) { + struct test_tls_crypt_context *ctx = (struct test_tls_crypt_context *) *state; free_buf(&ctx->source); free_buf(&ctx->ciphertext); @@ -98,7 +98,7 @@ teardown(void **state) { return 0; } -static void skip_if_tls_crypt_not_supported(struct test_context *ctx) +static void skip_if_tls_crypt_not_supported(struct test_tls_crypt_context *ctx) { if (!ctx->kt.cipher || !ctx->kt.digest) { @@ -111,7 +111,7 @@ static void skip_if_tls_crypt_not_supported(struct test_context *ctx) */ static void tls_crypt_loopback(void **state) { - struct test_context *ctx = (struct test_context *) *state; + struct test_tls_crypt_context *ctx = (struct test_tls_crypt_context *) *state; skip_if_tls_crypt_not_supported(ctx); @@ -128,7 +128,7 @@ tls_crypt_loopback(void **state) { */ static void tls_crypt_loopback_zero_len(void **state) { - struct test_context *ctx = (struct test_context *) *state; + struct test_tls_crypt_context *ctx = (struct test_tls_crypt_context *) *state; skip_if_tls_crypt_not_supported(ctx); @@ -147,7 +147,7 @@ tls_crypt_loopback_zero_len(void **state) { */ static void tls_crypt_loopback_max_len(void **state) { - struct test_context *ctx = (struct test_context *) *state; + struct test_tls_crypt_context *ctx = (struct test_tls_crypt_context *) *state; skip_if_tls_crypt_not_supported(ctx); @@ -168,7 +168,7 @@ tls_crypt_loopback_max_len(void **state) { */ static void tls_crypt_fail_msg_too_long(void **state) { - struct test_context *ctx = (struct test_context *) *state; + struct test_tls_crypt_context *ctx = (struct test_tls_crypt_context *) *state; skip_if_tls_crypt_not_supported(ctx); @@ -184,7 +184,7 @@ tls_crypt_fail_msg_too_long(void **state) { */ static void tls_crypt_fail_invalid_key(void **state) { - struct test_context *ctx = (struct test_context *) *state; + struct test_tls_crypt_context *ctx = (struct test_tls_crypt_context *) *state; skip_if_tls_crypt_not_supported(ctx); @@ -203,7 +203,7 @@ tls_crypt_fail_invalid_key(void **state) { */ static void tls_crypt_fail_replay(void **state) { - struct test_context *ctx = (struct test_context *) *state; + struct test_tls_crypt_context *ctx = (struct test_tls_crypt_context *) *state; skip_if_tls_crypt_not_supported(ctx); @@ -222,7 +222,7 @@ tls_crypt_fail_replay(void **state) { */ static void tls_crypt_ignore_replay(void **state) { - struct test_context *ctx = (struct test_context *) *state; + struct test_tls_crypt_context *ctx = (struct test_tls_crypt_context *) *state; skip_if_tls_crypt_not_supported(ctx); @@ -236,22 +236,235 @@ tls_crypt_ignore_replay(void **state) { assert_true(tls_crypt_unwrap(&ctx->ciphertext, &ctx->unwrapped, &ctx->co)); } +struct test_tls_crypt_v2_context { + struct gc_arena gc; + struct key2 server_key2; + struct key_ctx_bi server_keys; + struct key2 client_key2; + struct key_ctx_bi client_key; + struct buffer metadata; + struct buffer unwrapped_metadata; + struct buffer wkc; +}; + +static int +test_tls_crypt_v2_setup(void **state) { + struct test_tls_crypt_v2_context *ctx = calloc(1, sizeof(*ctx)); + *state = ctx; + + ctx->gc = gc_new(); + + /* Sligthly longer buffers to be able to test too-long data */ + ctx->metadata = alloc_buf_gc(TLS_CRYPT_V2_MAX_METADATA_LEN+16, &ctx->gc); + ctx->unwrapped_metadata = alloc_buf_gc(TLS_CRYPT_V2_MAX_METADATA_LEN+16, + &ctx->gc); + ctx->wkc = alloc_buf_gc(TLS_CRYPT_V2_MAX_WKC_LEN+16, &ctx->gc); + + /* Generate server key */ + rand_bytes((void *)ctx->server_key2.keys, sizeof(ctx->server_key2.keys)); + ctx->server_key2.n = 2; + struct key_type kt = tls_crypt_kt(); + init_key_ctx_bi(&ctx->server_keys, &ctx->server_key2, + KEY_DIRECTION_BIDIRECTIONAL, &kt, + "tls-crypt-v2 server key"); + + /* Generate client key */ + rand_bytes((void *)ctx->client_key2.keys, sizeof(ctx->client_key2.keys)); + ctx->client_key2.n = 2; + + return 0; +} + +static int +test_tls_crypt_v2_teardown(void **state) { + struct test_tls_crypt_v2_context *ctx = + (struct test_tls_crypt_v2_context *) *state; + + free_key_ctx_bi(&ctx->server_keys); + free_key_ctx_bi(&ctx->client_key); + + gc_free(&ctx->gc); + + free(ctx); + + return 0; +} + +/** + * Check wrapping and unwrapping a tls-crypt-v2 client key without metadata. + */ +static void +tls_crypt_v2_wrap_unwrap_no_metadata(void **state) { + struct test_tls_crypt_v2_context *ctx = + (struct test_tls_crypt_v2_context *) *state; + + struct buffer wrapped_client_key = alloc_buf_gc(TLS_CRYPT_V2_MAX_WKC_LEN, + &ctx->gc); + assert_true(tls_crypt_v2_wrap_client_key(&wrapped_client_key, + &ctx->client_key2, + &ctx->metadata, + &ctx->server_keys.encrypt, + &ctx->gc)); + + struct buffer unwrap_metadata = alloc_buf_gc(TLS_CRYPT_V2_MAX_METADATA_LEN, + &ctx->gc); + struct key2 unwrapped_client_key2 = { 0 }; + assert_true(tls_crypt_v2_unwrap_client_key(&unwrapped_client_key2, + &unwrap_metadata, + wrapped_client_key, + &ctx->server_keys.decrypt)); + + assert_true(0 == memcmp(ctx->client_key2.keys, unwrapped_client_key2.keys, + sizeof(ctx->client_key2.keys))); +} + +/** + * Check wrapping and unwrapping a tls-crypt-v2 client key with maximum length + * metadata. + */ +static void +tls_crypt_v2_wrap_unwrap_max_metadata(void **state) { + struct test_tls_crypt_v2_context *ctx = + (struct test_tls_crypt_v2_context *) *state; + + uint8_t* metadata = + buf_write_alloc(&ctx->metadata, TLS_CRYPT_V2_MAX_METADATA_LEN); + assert_true(rand_bytes(metadata, TLS_CRYPT_V2_MAX_METADATA_LEN)); + assert_true(tls_crypt_v2_wrap_client_key(&ctx->wkc, &ctx->client_key2, + &ctx->metadata, + &ctx->server_keys.encrypt, + &ctx->gc)); + + struct buffer unwrap_metadata = alloc_buf_gc(TLS_CRYPT_V2_MAX_METADATA_LEN, + &ctx->gc); + struct key2 unwrapped_client_key2 = { 0 }; + assert_true(tls_crypt_v2_unwrap_client_key(&unwrapped_client_key2, + &unwrap_metadata, ctx->wkc, + &ctx->server_keys.decrypt)); + + assert_true(0 == memcmp(ctx->client_key2.keys, unwrapped_client_key2.keys, + sizeof(ctx->client_key2.keys))); + assert_true(buf_equal(&ctx->metadata, &unwrap_metadata)); +} + +/** + * Check that wrapping a tls-crypt-v2 client key with too long metadata fails + * as expected. + */ +static void +tls_crypt_v2_wrap_too_long_metadata(void **state) { + struct test_tls_crypt_v2_context *ctx = + (struct test_tls_crypt_v2_context *) *state; + + assert_true(buf_inc_len(&ctx->metadata, TLS_CRYPT_V2_MAX_METADATA_LEN+1)); + assert_false(tls_crypt_v2_wrap_client_key(&ctx->wkc, &ctx->client_key2, + &ctx->metadata, + &ctx->server_keys.encrypt, + &ctx->gc)); +} + +/** + * Check that unwrapping a tls-crypt-v2 client key with the wrong server key + * fails as expected. + */ +static void +tls_crypt_v2_wrap_unwrap_wrong_key(void **state) { + struct test_tls_crypt_v2_context *ctx = + (struct test_tls_crypt_v2_context *) *state; + + assert_true(tls_crypt_v2_wrap_client_key(&ctx->wkc, &ctx->client_key2, + &ctx->metadata, + &ctx->server_keys.encrypt, + &ctx->gc)); + + /* Change server key */ + struct key_type kt = tls_crypt_kt(); + free_key_ctx_bi(&ctx->server_keys); + memset(&ctx->server_key2.keys, 0, sizeof(ctx->server_key2.keys)); + init_key_ctx_bi(&ctx->server_keys, &ctx->server_key2, + KEY_DIRECTION_BIDIRECTIONAL, &kt, + "wrong tls-crypt-v2 server key"); + + + struct key2 unwrapped_client_key2 = { 0 }; + assert_false(tls_crypt_v2_unwrap_client_key(&unwrapped_client_key2, + &ctx->unwrapped_metadata, + ctx->wkc, + &ctx->server_keys.decrypt)); + + const struct key2 zero = { 0 }; + assert_true(0 == memcmp(&unwrapped_client_key2, &zero, sizeof(zero))); + assert_true(0 == BLEN(&ctx->unwrapped_metadata)); +} + +/** + * Check that unwrapping a tls-crypt-v2 client key to a too small metadata + * buffer fails as expected. + */ +static void +tls_crypt_v2_wrap_unwrap_dst_too_small(void **state) { + struct test_tls_crypt_v2_context *ctx = + (struct test_tls_crypt_v2_context *) *state; + + uint8_t* metadata = + buf_write_alloc(&ctx->metadata, TLS_CRYPT_V2_MAX_METADATA_LEN); + assert_true(rand_bytes(metadata, TLS_CRYPT_V2_MAX_METADATA_LEN)); + assert_true(tls_crypt_v2_wrap_client_key(&ctx->wkc, &ctx->client_key2, + &ctx->metadata, + &ctx->server_keys.encrypt, + &ctx->gc)); + + struct key2 unwrapped_client_key2 = { 0 }; + struct buffer unwrapped_metadata = + alloc_buf_gc(TLS_CRYPT_V2_MAX_METADATA_LEN-1, &ctx->gc); + assert_false(tls_crypt_v2_unwrap_client_key(&unwrapped_client_key2, + &unwrapped_metadata, ctx->wkc, + &ctx->server_keys.decrypt)); + + const struct key2 zero = { 0 }; + assert_true(0 == memcmp(&unwrapped_client_key2, &zero, sizeof(zero))); + assert_true(0 == BLEN(&ctx->unwrapped_metadata)); +} + int main(void) { const struct CMUnitTest tests[] = { - cmocka_unit_test_setup_teardown(tls_crypt_loopback, setup, teardown), + cmocka_unit_test_setup_teardown(tls_crypt_loopback, + test_tls_crypt_setup, + test_tls_crypt_teardown), cmocka_unit_test_setup_teardown(tls_crypt_loopback_zero_len, - setup, teardown), + test_tls_crypt_setup, + test_tls_crypt_teardown), cmocka_unit_test_setup_teardown(tls_crypt_loopback_max_len, - setup, teardown), + test_tls_crypt_setup, + test_tls_crypt_teardown), cmocka_unit_test_setup_teardown(tls_crypt_fail_msg_too_long, - setup, teardown), + test_tls_crypt_setup, + test_tls_crypt_teardown), cmocka_unit_test_setup_teardown(tls_crypt_fail_invalid_key, - setup, teardown), + test_tls_crypt_setup, + test_tls_crypt_teardown), cmocka_unit_test_setup_teardown(tls_crypt_fail_replay, - setup, teardown), + test_tls_crypt_setup, + test_tls_crypt_teardown), cmocka_unit_test_setup_teardown(tls_crypt_ignore_replay, - setup, teardown), + test_tls_crypt_setup, + test_tls_crypt_teardown), + cmocka_unit_test_setup_teardown(tls_crypt_v2_wrap_unwrap_no_metadata, + test_tls_crypt_v2_setup, + test_tls_crypt_v2_teardown), + cmocka_unit_test_setup_teardown(tls_crypt_v2_wrap_unwrap_max_metadata, + test_tls_crypt_v2_setup, + test_tls_crypt_v2_teardown), + cmocka_unit_test_setup_teardown(tls_crypt_v2_wrap_too_long_metadata, + test_tls_crypt_v2_setup, + test_tls_crypt_v2_teardown), + cmocka_unit_test_setup_teardown(tls_crypt_v2_wrap_unwrap_wrong_key, + test_tls_crypt_v2_setup, + test_tls_crypt_v2_teardown), + cmocka_unit_test_setup_teardown(tls_crypt_v2_wrap_unwrap_dst_too_small, + test_tls_crypt_v2_setup, + test_tls_crypt_v2_teardown), }; #if defined(ENABLE_CRYPTO_OPENSSL) From patchwork Wed Jul 4 07:54:02 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Steffan Karger X-Patchwork-Id: 399 Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director7.mail.ord1d.rsapps.net ([172.27.255.57]) by backend30.mail.ord1d.rsapps.net (Dovecot) with LMTP id FF8FABQKPVvCIQAAIUCqbw for ; Wed, 04 Jul 2018 13:55:32 -0400 Received: from proxy3.mail.iad3a.rsapps.net ([172.27.255.57]) by director7.mail.ord1d.rsapps.net (Dovecot) with LMTP id 28bGFxQKPVtoaQAAovjBpQ ; Wed, 04 Jul 2018 13:55:32 -0400 Received: from smtp21.gate.iad3a ([172.27.255.57]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy3.mail.iad3a.rsapps.net with LMTP id QGPEFRQKPVulTAAAYaqY3Q ; Wed, 04 Jul 2018 13:55:32 -0400 X-Spam-Threshold: 95 X-Spam-Score: 0 X-Spam-Flag: NO X-Virus-Scanned: OK X-Orig-To: openvpnslackdevel@openvpn.net X-Originating-Ip: [216.105.38.7] Authentication-Results: smtp21.gate.iad3a.rsapps.net; iprev=pass policy.iprev="216.105.38.7"; spf=pass smtp.mailfrom="openvpn-devel-bounces@lists.sourceforge.net" smtp.helo="lists.sourceforge.net"; dkim=fail (signature verification failed) header.d=sourceforge.net; dkim=fail (signature verification failed) header.d=sf.net; dkim=fail (signature verification failed) header.d=karger-me.20150623.gappssmtp.com; dmarc=none (p=nil; dis=none) header.from=karger.me X-Suspicious-Flag: YES X-Classification-ID: 713aa7da-7fb3-11e8-8503-525400e75841-1-1 Received: from [216.105.38.7] ([216.105.38.7:29709] helo=lists.sourceforge.net) by smtp21.gate.iad3a.rsapps.net (envelope-from ) (ecelerity 4.2.1.56364 r(Core:4.2.1.14)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id A7/0C-31792-31A0D3B5; Wed, 04 Jul 2018 13:55:31 -0400 Received: from [127.0.0.1] (helo=sfs-ml-4.v29.lw.sourceforge.com) by sfs-ml-4.v29.lw.sourceforge.com with esmtp (Exim 4.90_1) (envelope-from ) id 1falzL-0001Px-U7; Wed, 04 Jul 2018 17:54:47 +0000 Received: from [172.30.20.202] (helo=mx.sourceforge.net) by sfs-ml-4.v29.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.90_1) (envelope-from ) id 1falzJ-0001PV-Io for openvpn-devel@lists.sourceforge.net; Wed, 04 Jul 2018 17:54:45 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=References:In-Reply-To:Message-Id:Date:Subject:Cc: To:From:Sender:Reply-To:MIME-Version:Content-Type:Content-Transfer-Encoding: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=FLWm3VDSOL+Sxe1w9eRoKMgC/Nf2xhGCExwFqsxdZqM=; b=OON4vtmf5d0FnUDrWF0ULcdGrh pBO6DLBUXPgOkDm/8OK+YzY68uj6qLAGdxVWq9Tt0RhCHn70ESolbS7mIMR7pswXtMNHDyk5gZsDv Ru0Ln60jh/jrRLTL3GvYqoQv2zSSgh1TDXkpJ0PjLlrrCXIB4g3JLd+47cKj56YalzzA=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=References:In-Reply-To:Message-Id:Date:Subject:Cc:To:From:Sender:Reply-To :MIME-Version:Content-Type:Content-Transfer-Encoding:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=FLWm3VDSOL+Sxe1w9eRoKMgC/Nf2xhGCExwFqsxdZqM=; b=XsE8yqyFw5lnlmjUyfv4HGTGwZ m7QLF+cPxGEVLhzEQlqg01OxeaJbhZuC4MgVg3XrUs1Qs6luouUTtO9vaZ/7rEoUh429EToTzmWR9 cGAhICfm8Lxop8UUcULw9JCKj2xtkITS9KuJf6o5cjk+ubbzmNPOj8mPUQaRI3G1fzVM=; Received: from mail-ed1-f41.google.com ([209.85.208.41]) by sfi-mx-4.v28.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES128-GCM-SHA256:128) (Exim 4.90_1) id 1falzI-00CwKj-6f for openvpn-devel@lists.sourceforge.net; Wed, 04 Jul 2018 17:54:45 +0000 Received: by mail-ed1-f41.google.com with SMTP id r17-v6so4563418edo.13 for ; Wed, 04 Jul 2018 10:54:43 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=karger-me.20150623.gappssmtp.com; s=20150623; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=FLWm3VDSOL+Sxe1w9eRoKMgC/Nf2xhGCExwFqsxdZqM=; b=DKin4uZ3PM82kT7LsNAU0VlHVZf5t+JsWhyapO0rdbseo2GX5Tx/MvrQBHC9eYO5VL /F5tm9/3iK4JaLFkqF6qjpHc4t06hnR34YukSpfnCEJU3o9q/pbPgHb4KBPYPGtrqHea W58rAOcOLBVKbVtNuCZTFq7l4m4XOvnEPPobLdufay7qo+IiPOhTP7u3s167CRT9KGDA yVj606KOgcUHlcKYYQ+FBQC73GsXQY6yWJ0iGOTBbOk6eyywXZf/K6gckR3/Cn5glbMd GkL5YCi1yBPGg5QqCH6Q3EVBwr4x8wKPviiJWLvdZpvsj+GPGUqqpDzJE06Xzr9hGIur SKOg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=FLWm3VDSOL+Sxe1w9eRoKMgC/Nf2xhGCExwFqsxdZqM=; b=bQjn70OwyDhinyBFWetiUekDjR3sDFhLLUvEOhBh1n84HVf8Nqm1eFM2LAutHmsif1 BT5d2qN3BU9VLYmYM+flBKUnzbwdJo9bf0UoI4FM5XkfKUueY48swh7sait5JE4psic+ +u1Mk4AbSC/WM9WOWO/yaHcL7HAzO1mIUb8MQVJpBFlRjCN4a2kj80Fj0B5BV+Inqy2B E3T1c6KD8k9lYZyywHuXlJpJoNPfrkdVDztRvy59wlm4aPnxvqiA3RxCiXrssj5Af7o0 0wX1+1wXqmPDTb37/O2peTSalp4Xjs4RfEqDjcsbKbbc5c2GF5+WsWGFaFra/e/GIcdL 2dYg== X-Gm-Message-State: APt69E221ZqWPbSlLuHt8t3dhfwYLEG53P6DN/LK86Ymp+bLIMIvcd4W 9Q9Z1Mq2aJpN+8SDpBJU+xJ/V8Qm7iY= X-Google-Smtp-Source: AAOMgpfqcJz387ip0ZPe97Edu6ZTrCYUUqSQu8+o0fq3VbNd5oz1J0TFwDwN5NmXnPeGYMfE5nnq3g== X-Received: by 2002:a50:d2d7:: with SMTP id q23-v6mr3784090edg.183.1530726877396; Wed, 04 Jul 2018 10:54:37 -0700 (PDT) Received: from vesta.fritz.box ([2001:985:e54:1:f598:331e:3cdf:2649]) by smtp.gmail.com with ESMTPSA id o2-v6sm1948961edd.84.2018.07.04.10.54.36 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Wed, 04 Jul 2018 10:54:36 -0700 (PDT) From: Steffan Karger To: openvpn-devel@lists.sourceforge.net Date: Wed, 4 Jul 2018 19:54:02 +0200 Message-Id: <20180704175404.22371-7-steffan@karger.me> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20180704175404.22371-1-steffan@karger.me> References: <1512734870-17133-1-git-send-email-steffan.karger@fox-it.com> <20180704175404.22371-1-steffan@karger.me> X-Spam-Report: Spam Filtering performed by mx.sourceforge.net. See http://spamassassin.org/tag/ for more details. -0.0 SPF_PASS SPF: sender matches SPF record -0.0 RCVD_IN_MSPIKE_H2 RBL: Average reputation (+2) [209.85.208.41 listed in wl.mailspike.net] -0.0 RCVD_IN_DNSWL_NONE RBL: Sender listed at http://www.dnswl.org/, no trust [209.85.208.41 listed in list.dnswl.org] 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature -0.0 T_DKIMWL_WL_MED DKIMwl.org - Whitelisted Medium sender 0.0 AWL AWL: Adjusted score from AWL reputation of From: address X-Headers-End: 1falzI-00CwKj-6f Subject: [Openvpn-devel] [PATCH v2 7/9] tls-crypt-v2: add P_CONTROL_HARD_RESET_CLIENT_V3 opcode X-BeenThere: openvpn-devel@lists.sourceforge.net X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox From: Steffan Karger Not used yet, but prepare for sending and receiving tls-crypt-v2 handshake messages. Signed-off-by: Steffan Karger --- src/openvpn/ps.c | 3 ++- src/openvpn/ssl.c | 23 ++++++++++++++++++----- src/openvpn/ssl.h | 5 ++++- src/openvpn/ssl_common.h | 2 ++ 4 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/openvpn/ps.c b/src/openvpn/ps.c index 25ab3749..adec0724 100644 --- a/src/openvpn/ps.c +++ b/src/openvpn/ps.c @@ -985,7 +985,8 @@ is_openvpn_protocol(const struct buffer *buf) { return p[0] == 0 && p[1] >= 14 - && p[2] == (P_CONTROL_HARD_RESET_CLIENT_V2<= 2) { diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c index 669f941b..bfe37fcd 100644 --- a/src/openvpn/ssl.c +++ b/src/openvpn/ssl.c @@ -792,6 +792,9 @@ packet_opcode_name(int op) case P_CONTROL_HARD_RESET_SERVER_V2: return "P_CONTROL_HARD_RESET_SERVER_V2"; + case P_CONTROL_HARD_RESET_CLIENT_V3: + return "P_CONTROL_HARD_RESET_CLIENT_V3"; + case P_CONTROL_SOFT_RESET_V1: return "P_CONTROL_SOFT_RESET_V1"; @@ -864,7 +867,8 @@ is_hard_reset(int op, int key_method) if (!key_method || key_method >= 2) { - if (op == P_CONTROL_HARD_RESET_CLIENT_V2 || op == P_CONTROL_HARD_RESET_SERVER_V2) + if (op == P_CONTROL_HARD_RESET_CLIENT_V2 || op == P_CONTROL_HARD_RESET_SERVER_V2 + || op == P_CONTROL_HARD_RESET_CLIENT_V3) { return true; } @@ -1095,8 +1099,15 @@ tls_session_init(struct tls_multi *multi, struct tls_session *session) } else /* session->opt->key_method >= 2 */ { - session->initial_opcode = session->opt->server ? - P_CONTROL_HARD_RESET_SERVER_V2 : P_CONTROL_HARD_RESET_CLIENT_V2; + if (session->opt->server) + { + session->initial_opcode = P_CONTROL_HARD_RESET_SERVER_V2; + } + else + { + session->initial_opcode = session->opt->tls_crypt_v2 ? + P_CONTROL_HARD_RESET_CLIENT_V3 : P_CONTROL_HARD_RESET_CLIENT_V2; + } } /* Initialize control channel authentication parameters */ @@ -3427,7 +3438,8 @@ tls_pre_decrypt(struct tls_multi *multi, { /* verify client -> server or server -> client connection */ if (((op == P_CONTROL_HARD_RESET_CLIENT_V1 - || op == P_CONTROL_HARD_RESET_CLIENT_V2) && !multi->opt.server) + || op == P_CONTROL_HARD_RESET_CLIENT_V2 + || op == P_CONTROL_HARD_RESET_CLIENT_V3) && !multi->opt.server) || ((op == P_CONTROL_HARD_RESET_SERVER_V1 || op == P_CONTROL_HARD_RESET_SERVER_V2) && multi->opt.server)) { @@ -3812,7 +3824,8 @@ tls_pre_decrypt_lite(const struct tls_auth_standalone *tas, /* this packet is from an as-yet untrusted source, so * scrutinize carefully */ - if (op != P_CONTROL_HARD_RESET_CLIENT_V2) + if (op != P_CONTROL_HARD_RESET_CLIENT_V2 + && op != P_CONTROL_HARD_RESET_CLIENT_V3) { /* * This can occur due to bogus data or DoS packets. diff --git a/src/openvpn/ssl.h b/src/openvpn/ssl.h index a2501c9b..bbc84192 100644 --- a/src/openvpn/ssl.h +++ b/src/openvpn/ssl.h @@ -63,9 +63,12 @@ #define P_CONTROL_HARD_RESET_CLIENT_V2 7 /* initial key from client, forget previous state */ #define P_CONTROL_HARD_RESET_SERVER_V2 8 /* initial key from server, forget previous state */ +/* indicates key_method >= 2 and client-specific tls-crypt key */ +#define P_CONTROL_HARD_RESET_CLIENT_V3 10 /* initial key from client, forget previous state */ + /* define the range of legal opcodes */ #define P_FIRST_OPCODE 1 -#define P_LAST_OPCODE 9 +#define P_LAST_OPCODE 10 /* * Set the max number of acknowledgments that can "hitch a ride" on an outgoing diff --git a/src/openvpn/ssl_common.h b/src/openvpn/ssl_common.h index 08ef6ffa..e3c852af 100644 --- a/src/openvpn/ssl_common.h +++ b/src/openvpn/ssl_common.h @@ -286,6 +286,8 @@ struct tls_options const char *config_authname; bool ncp_enabled; + bool tls_crypt_v2; + /** TLS handshake wrapping state */ struct tls_wrap_ctx tls_wrap; From patchwork Wed Jul 4 07:54:03 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Steffan Karger X-Patchwork-Id: 403 Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director11.mail.ord1d.rsapps.net ([172.27.255.59]) by backend30.mail.ord1d.rsapps.net (Dovecot) with LMTP id k66XCyYKPVuSBwAAIUCqbw for ; Wed, 04 Jul 2018 13:55:50 -0400 Received: from proxy11.mail.iad3a.rsapps.net ([172.27.255.59]) by director11.mail.ord1d.rsapps.net (Dovecot) with LMTP id SycYBSYKPVuSIAAAvGGmqA ; Wed, 04 Jul 2018 13:55:50 -0400 Received: from smtp37.gate.iad3a ([172.27.255.59]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy11.mail.iad3a.rsapps.net with LMTP id 2HjUAiYKPVuOHQAAxCvdqw ; Wed, 04 Jul 2018 13:55:50 -0400 X-Spam-Threshold: 95 X-Spam-Score: 0 X-Spam-Flag: NO X-Virus-Scanned: OK X-Orig-To: openvpnslackdevel@openvpn.net X-Originating-Ip: [216.105.38.7] Authentication-Results: smtp37.gate.iad3a.rsapps.net; iprev=pass policy.iprev="216.105.38.7"; spf=pass smtp.mailfrom="openvpn-devel-bounces@lists.sourceforge.net" smtp.helo="lists.sourceforge.net"; dkim=fail (signature verification failed) header.d=sourceforge.net; dkim=fail (signature verification failed) header.d=sf.net; dkim=fail (signature verification failed) header.d=karger-me.20150623.gappssmtp.com; dmarc=none (p=nil; dis=none) header.from=karger.me X-Suspicious-Flag: YES X-Classification-ID: 7bcf1f28-7fb3-11e8-a01c-525400dc5f6a-1-1 Received: from [216.105.38.7] ([216.105.38.7:40331] helo=lists.sourceforge.net) by smtp37.gate.iad3a.rsapps.net (envelope-from ) (ecelerity 4.2.1.56364 r(Core:4.2.1.14)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id 59/FC-26805-52A0D3B5; Wed, 04 Jul 2018 13:55:49 -0400 Received: from [127.0.0.1] (helo=sfs-ml-2.v29.lw.sourceforge.com) by sfs-ml-2.v29.lw.sourceforge.com with esmtp (Exim 4.90_1) (envelope-from ) id 1falzM-0003TB-NS; Wed, 04 Jul 2018 17:54:48 +0000 Received: from [172.30.20.202] (helo=mx.sourceforge.net) by sfs-ml-2.v29.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.90_1) (envelope-from ) id 1falzL-0003SW-1B for openvpn-devel@lists.sourceforge.net; Wed, 04 Jul 2018 17:54:47 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=References:In-Reply-To:Message-Id:Date:Subject:Cc: To:From:Sender:Reply-To:MIME-Version:Content-Type:Content-Transfer-Encoding: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=0/997kEdK75nvwdoo9i3oTxCofLcPjQiKuEObV+ZcsY=; b=I4GJ8KjqkFCnelT7TIQmKww5B1 XybKYzSKsvC4Ak0ghK/RggsSwEzzEAoVRSI1/L6TQIHiNQid7iHlF0AGAK3YrnMzec8oDBjuiEYKM IcFy+Kmn/Alvgiiby8w+DNjTcANPYOZqFMbA/ijNDzYYnPCaMOTAqaEvC6shwr2imX3U=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=References:In-Reply-To:Message-Id:Date:Subject:Cc:To:From:Sender:Reply-To :MIME-Version:Content-Type:Content-Transfer-Encoding:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=0/997kEdK75nvwdoo9i3oTxCofLcPjQiKuEObV+ZcsY=; b=ROlV553oYUu1mMMThda628dOpX BncOw2+fhpwo1ZMjy88XoUsMCjsHZ5kq9ZYOlqjMfvUprMK0F9UTJRCfPY+ECViNq3gjjxPcsqwm1 OaKAaSnX1+QXZotZRqYhRNJVHvmxITIqYqcGU/SEFAA4jgLiJ+oTc90S1Gbfu5KZ1PzA=; Received: from mail-ed1-f47.google.com ([209.85.208.47]) by sfi-mx-4.v28.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES128-GCM-SHA256:128) (Exim 4.90_1) id 1falzJ-00CwKk-Aw for openvpn-devel@lists.sourceforge.net; Wed, 04 Jul 2018 17:54:46 +0000 Received: by mail-ed1-f47.google.com with SMTP id x5-v6so894277edr.0 for ; Wed, 04 Jul 2018 10:54:45 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=karger-me.20150623.gappssmtp.com; s=20150623; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=0/997kEdK75nvwdoo9i3oTxCofLcPjQiKuEObV+ZcsY=; b=J/do7atxzdTUra/i3XhdLuinGCfrp5uaLf5KXrc2ep9v81JLrTKL2NJb1rBd33JOIA ZY+TU0WWjjZ3LYRQl0I7ihJsfDJJTyIZvQKoXJ1PxUc1ndeyXAbUe5bxRPvtz6M6iBAC UN7N3Hk6hRau93iQ+86Ychw4v1UKxe3eengrvdcS0VvDfY90zPOXG/IBwHkZU9l429o1 L/gdvmNgiYM2lCauj91PQ91ZMV7/nTIdl0X42Ny6ko6BL/XxMjjx1K43bSoLubnN5D+T jRNQwEW/1Ynn+C1Vw+DabUs9ojATPDnZj+1rSu6YG70VpEPmd9DZIhiGZegtz9u03SAL 7ebw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=0/997kEdK75nvwdoo9i3oTxCofLcPjQiKuEObV+ZcsY=; b=MftzUx+EZeYasFKZiMUwKe9DnRk3wjxbR85R5+ckvNiOBc3eojZU+eGCKAIJ3xPOjA WCK0SIi7RejMFch0STkkMixYgZgduLDsq8QMtV3VFg6aWG4VcNTwDRbx+cej98LGd4M2 VkDsY5zQGnqJnbyL6HuUKw/sbHrt/15c5fv4hXT41eDVBSRXUbXvPZKHm3b9vZZ2lYii xhH6W0FwIN+vo/WkI9xSQs3H2ozWFth4iwYe4/KPiw1F9lU9Yn2wRZG6PpBbbyIsYuwN t2MUOzTFoOgKQqaSDScBSZRsHi4vMT/4OtMSbuRMT4IhD8cb4GrJ8kGNlqs1TdGC2wos J+Ug== X-Gm-Message-State: APt69E22Qe1jXLYwhS2Q9/05VNfjDMDMLSxYCY9EVaohSfEL5ax8ArBw rFzwg9LjCO09y4CscqXG48HMTIRLEsw= X-Google-Smtp-Source: AAOMgpcjIPkr+wBPI+G0quvpa7xCBx6TCYjhNrTrZKsJmYRThh9HVuA2CEO4GhF0iuURNi0B3y7RzQ== X-Received: by 2002:a50:b178:: with SMTP id l53-v6mr3669530edd.306.1530726878276; Wed, 04 Jul 2018 10:54:38 -0700 (PDT) Received: from vesta.fritz.box ([2001:985:e54:1:f598:331e:3cdf:2649]) by smtp.gmail.com with ESMTPSA id o2-v6sm1948961edd.84.2018.07.04.10.54.37 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Wed, 04 Jul 2018 10:54:37 -0700 (PDT) From: Steffan Karger To: openvpn-devel@lists.sourceforge.net Date: Wed, 4 Jul 2018 19:54:03 +0200 Message-Id: <20180704175404.22371-8-steffan@karger.me> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20180704175404.22371-1-steffan@karger.me> References: <1512734870-17133-1-git-send-email-steffan.karger@fox-it.com> <20180704175404.22371-1-steffan@karger.me> X-Spam-Report: Spam Filtering performed by mx.sourceforge.net. See http://spamassassin.org/tag/ for more details. -0.0 RCVD_IN_DNSWL_NONE RBL: Sender listed at http://www.dnswl.org/, no trust [209.85.208.47 listed in list.dnswl.org] -0.0 SPF_PASS SPF: sender matches SPF record -0.0 RCVD_IN_MSPIKE_H2 RBL: Average reputation (+2) [209.85.208.47 listed in wl.mailspike.net] 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature -0.0 T_DKIMWL_WL_MED DKIMwl.org - Whitelisted Medium sender X-Headers-End: 1falzJ-00CwKk-Aw Subject: [Openvpn-devel] [PATCH v2 8/9] tls-crypt-v2: implement tls-crypt-v2 handshake X-BeenThere: openvpn-devel@lists.sourceforge.net X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox From: Steffan Karger This makes clients send-and-use, and servers receive-unwrap-and-use tls-crypt-v2 client keys, which completes the on-the-wire work. Signed-off-by: Steffan Karger --- src/openvpn/init.c | 39 +++++++++++++- src/openvpn/openvpn.h | 2 + src/openvpn/options.c | 5 ++ src/openvpn/ssl.c | 79 ++++++++++++++++++++++------ src/openvpn/ssl_common.h | 6 +++ src/openvpn/tls_crypt.c | 50 ++++++++++++++++++ src/openvpn/tls_crypt.h | 14 +++++ tests/unit_tests/openvpn/Makefile.am | 2 +- 8 files changed, 179 insertions(+), 18 deletions(-) diff --git a/src/openvpn/init.c b/src/openvpn/init.c index c1454f36..0874edff 100644 --- a/src/openvpn/init.c +++ b/src/openvpn/init.c @@ -2436,6 +2436,9 @@ key_schedule_free(struct key_schedule *ks, bool free_ssl_ctx) { tls_ctx_free(&ks->ssl_ctx); free_key_ctx_bi(&ks->tls_wrap_key); + free_key_ctx(&ks->tls_crypt_v2_server_key); + buf_clear(&ks->tls_crypt_v2_wkc); + free_buf(&ks->tls_crypt_v2_wkc); } CLEAR(*ks); } @@ -2604,6 +2607,24 @@ do_init_crypto_tls_c1(struct context *c) options->tls_crypt_inline, options->tls_server); } + /* tls-crypt with client-specific keys (--tls-crypt-v2) */ + if (options->tls_crypt_v2_file) + { + if (options->tls_server) + { + tls_crypt_v2_init_server_key(&c->c1.ks.tls_crypt_v2_server_key, + true, options->tls_crypt_v2_file, + options->tls_crypt_v2_inline); + } + else + { + tls_crypt_v2_init_client_key(&c->c1.ks.tls_wrap_key, + &c->c1.ks.tls_crypt_v2_wkc, + options->tls_crypt_v2_file, + options->tls_crypt_v2_inline); + } + } + c->c1.ciphername = options->ciphername; c->c1.authname = options->authname; c->c1.keysize = options->keysize; @@ -2823,13 +2844,28 @@ do_init_crypto_tls(struct context *c, const unsigned int flags) } /* TLS handshake encryption (--tls-crypt) */ - if (options->tls_crypt_file) + if (options->tls_crypt_file + || (options->tls_crypt_v2_file && options->tls_client)) { to.tls_wrap.mode = TLS_WRAP_CRYPT; to.tls_wrap.opt.key_ctx_bi = c->c1.ks.tls_wrap_key; to.tls_wrap.opt.pid_persist = &c->c1.pid_persist; to.tls_wrap.opt.flags |= CO_PACKET_ID_LONG_FORM; tls_crypt_adjust_frame_parameters(&to.frame); + + if (options->tls_crypt_v2_file) + { + to.tls_wrap.tls_crypt_v2_wkc = &c->c1.ks.tls_crypt_v2_wkc; + } + } + + if (options->tls_crypt_v2_file) + { + to.tls_crypt_v2 = true; + if (options->tls_server) + { + to.tls_wrap.tls_crypt_v2_server_key = c->c1.ks.tls_crypt_v2_server_key; + } } /* If we are running over TCP, allow for @@ -4359,6 +4395,7 @@ inherit_context_child(struct context *dest, dest->c1.ks.ssl_ctx = src->c1.ks.ssl_ctx; dest->c1.ks.tls_wrap_key = src->c1.ks.tls_wrap_key; dest->c1.ks.tls_auth_key_type = src->c1.ks.tls_auth_key_type; + dest->c1.ks.tls_crypt_v2_server_key = src->c1.ks.tls_crypt_v2_server_key; /* inherit pre-NCP ciphers */ dest->c1.ciphername = src->c1.ciphername; dest->c1.authname = src->c1.authname; diff --git a/src/openvpn/openvpn.h b/src/openvpn/openvpn.h index c5c767c0..96cff870 100644 --- a/src/openvpn/openvpn.h +++ b/src/openvpn/openvpn.h @@ -66,6 +66,8 @@ struct key_schedule /* optional TLS control channel wrapping */ struct key_type tls_auth_key_type; struct key_ctx_bi tls_wrap_key; + struct key_ctx tls_crypt_v2_server_key; + struct buffer tls_crypt_v2_wkc; /**< Wrapped client key */ }; /* diff --git a/src/openvpn/options.c b/src/openvpn/options.c index c1d53a96..60be7d15 100644 --- a/src/openvpn/options.c +++ b/src/openvpn/options.c @@ -2739,6 +2739,11 @@ options_postprocess_verify_ce(const struct options *options, const struct connec { msg(M_USAGE, "--tls-auth and --tls-crypt are mutually exclusive"); } + if (options->client && options->tls_crypt_v2_file + && (options->tls_auth_file || options->tls_crypt_file)) + { + msg(M_USAGE, "--tls-crypt-v2, --tls-auth and --tls-crypt are mutually exclusive in client mode"); + } } else { diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c index bfe37fcd..9fc1efa3 100644 --- a/src/openvpn/ssl.c +++ b/src/openvpn/ssl.c @@ -1055,6 +1055,23 @@ tls_session_user_pass_enabled(struct tls_session *session) /** @addtogroup control_processor * @{ */ +/** Free the elements of a tls_wrap_ctx structure */ +static void tls_wrap_free(struct tls_wrap_ctx *tls_wrap) +{ + if (packet_id_initialized(&tls_wrap->opt.packet_id)) + { + packet_id_free(&tls_wrap->opt.packet_id); + } + + if (tls_wrap->cleanup_key_ctx) + { + free_key_ctx_bi(&tls_wrap->opt.key_ctx_bi); + } + + free_buf(&tls_wrap->tls_crypt_v2_metadata); + free_buf(&tls_wrap->work); +} + /** @name Functions for initialization and cleanup of tls_session structures * @{ */ @@ -1147,16 +1164,9 @@ tls_session_init(struct tls_multi *multi, struct tls_session *session) static void tls_session_free(struct tls_session *session, bool clear) { - int i; - - if (packet_id_initialized(&session->tls_wrap.opt.packet_id)) - { - packet_id_free(&session->tls_wrap.opt.packet_id); - } + tls_wrap_free(&session->tls_wrap); - free_buf(&session->tls_wrap.work); - - for (i = 0; i < KS_SIZE; ++i) + for (size_t i = 0; i < KS_SIZE; ++i) { key_state_free(&session->key[i], false); } @@ -1482,6 +1492,8 @@ write_control_auth(struct tls_session *session, ASSERT(reliable_ack_write (ks->rec_ack, buf, &ks->session_id_remote, max_ack, prepend_ack)); + msg(D_TLS_DEBUG, "%s(): %s", __func__, packet_opcode_name(opcode)); + if (session->tls_wrap.mode == TLS_WRAP_AUTH || session->tls_wrap.mode == TLS_WRAP_NONE) { @@ -1499,17 +1511,26 @@ write_control_auth(struct tls_session *session, ASSERT(buf_init(&session->tls_wrap.work, buf->offset)); ASSERT(buf_write(&session->tls_wrap.work, &header, sizeof(header))); ASSERT(session_id_write(&session->session_id, &session->tls_wrap.work)); - if (tls_crypt_wrap(buf, &session->tls_wrap.work, &session->tls_wrap.opt)) - { - /* Don't change the original data in buf, it's used by the reliability - * layer to resend on failure. */ - *buf = session->tls_wrap.work; - } - else + if (!tls_crypt_wrap(buf, &session->tls_wrap.work, &session->tls_wrap.opt)) { buf->len = 0; return; } + + if (opcode == P_CONTROL_HARD_RESET_CLIENT_V3) + { + if (!buf_copy(&session->tls_wrap.work, + session->tls_wrap.tls_crypt_v2_wkc)) + { + msg(D_TLS_ERRORS, "Could not append tls-crypt-v2 client key"); + buf->len = 0; + return; + } + } + + /* Don't change the original data in buf, it's used by the reliability + * layer to resend on failure. */ + *buf = session->tls_wrap.work; } *to_link_addr = &ks->remote_addr; } @@ -1525,6 +1546,16 @@ read_control_auth(struct buffer *buf, struct gc_arena gc = gc_new(); bool ret = false; + const uint8_t opcode = *(BPTR(buf)) >> P_OPCODE_SHIFT; + if (opcode == P_CONTROL_HARD_RESET_CLIENT_V3 + && !tls_crypt_v2_extract_client_key(buf, ctx)) + { + msg (D_TLS_ERRORS, + "TLS Error: can not extract tls-crypt-v2 client key from %s", + print_link_socket_actual(from, &gc)); + goto cleanup; + } + if (ctx->mode == TLS_WRAP_AUTH) { struct buffer null = clear_buf(); @@ -1564,6 +1595,18 @@ read_control_auth(struct buffer *buf, ASSERT(buf_copy(buf, &tmp)); buf_clear(&tmp); } + else if (ctx->tls_crypt_v2_server_key.cipher) + { + /* If tls-crypt-v2 is enabled, require *some* wrapping */ + msg(D_TLS_ERRORS, "TLS Error: could not determine wrapping from %s", + print_link_socket_actual(from, &gc)); + /* TODO Do we want to support using tls-crypt-v2 and no control channel + * wrapping at all simultaneously? That would allow server admins to + * upgrade clients one-by-one without running a second instance, but we + * should not enable it by default because it breaks DoS-protection. + * So, add something like --tls-crypt-v2-allow-insecure-fallback ? */ + goto cleanup; + } if (ctx->mode == TLS_WRAP_NONE || ctx->mode == TLS_WRAP_AUTH) { @@ -3864,6 +3907,10 @@ tls_pre_decrypt_lite(const struct tls_auth_standalone *tas, /* HMAC test, if --tls-auth was specified */ status = read_control_auth(&newbuf, &tls_wrap_tmp, from); free_buf(&newbuf); + if (tls_wrap_tmp.cleanup_key_ctx) + { + free_key_ctx_bi(&tls_wrap_tmp.opt.key_ctx_bi); + } if (!status) { goto error; diff --git a/src/openvpn/ssl_common.h b/src/openvpn/ssl_common.h index e3c852af..d744881c 100644 --- a/src/openvpn/ssl_common.h +++ b/src/openvpn/ssl_common.h @@ -213,6 +213,12 @@ struct tls_wrap_ctx } mode; /**< Control channel wrapping mode */ struct crypto_options opt; /**< Crypto state */ struct buffer work; /**< Work buffer (only for --tls-crypt) */ + struct key_ctx tls_crypt_v2_server_key; /**< Decrypts client keys */ + const struct buffer *tls_crypt_v2_wkc; /**< Wrapped client key, + sent to server */ + struct buffer tls_crypt_v2_metadata; /**< Received from client */ + bool cleanup_key_ctx; /**< opt.key_ctx_bi is owned by + this context */ }; /* diff --git a/src/openvpn/tls_crypt.c b/src/openvpn/tls_crypt.c index cb0d5faf..0180a753 100644 --- a/src/openvpn/tls_crypt.c +++ b/src/openvpn/tls_crypt.c @@ -33,6 +33,7 @@ #include "crypto.h" #include "platform.h" #include "session_id.h" +#include "ssl.h" #include "tls_crypt.h" @@ -519,6 +520,55 @@ error_exit: return ret; } +bool +tls_crypt_v2_extract_client_key(struct buffer *buf, + struct tls_wrap_ctx *ctx) +{ + static const int hard_reset_length = + TLS_CRYPT_OFF_CT + sizeof(uint8_t) + sizeof(packet_id_type); + + if (!ctx->tls_crypt_v2_server_key.cipher) + { + msg (D_TLS_ERRORS, + "Client want tls-crypt-v2, but no server key present."); + return false; + } + + msg (D_HANDSHAKE, "Control Channel: using tls-crypt-v2 key"); + + struct buffer wrapped_client_key = *buf; + if (!buf_advance(&wrapped_client_key, hard_reset_length)) + { + msg (D_TLS_ERRORS, "Can not locate tls-crypt-v2 client key"); + return false; + } + + struct key2 client_key = { 0 }; + ctx->tls_crypt_v2_metadata = alloc_buf(TLS_CRYPT_V2_MAX_METADATA_LEN); + if (!tls_crypt_v2_unwrap_client_key(&client_key, + &ctx->tls_crypt_v2_metadata, + wrapped_client_key, + &ctx->tls_crypt_v2_server_key)) + { + msg (D_TLS_ERRORS, "Can not unwrap tls-crypt-v2 client key"); + secure_memzero(&client_key, sizeof(client_key)); + return false; + } + + /* Load the decrypted key */ + ctx->mode = TLS_WRAP_CRYPT; + ctx->cleanup_key_ctx = true; + ctx->opt.flags |= CO_PACKET_ID_LONG_FORM; + memset(&ctx->opt.key_ctx_bi, 0, sizeof(ctx->opt.key_ctx_bi)); + tls_crypt_v2_load_client_key(&ctx->opt.key_ctx_bi, &client_key, true); + secure_memzero(&client_key, sizeof(client_key)); + + /* Remove client key from buffer so tls-crypt code can unwrap message */ + ASSERT(buf_inc_len(buf, -(BLEN(&wrapped_client_key)))); + + return true; +} + void tls_crypt_v2_write_server_key_file(const char *filename) { diff --git a/src/openvpn/tls_crypt.h b/src/openvpn/tls_crypt.h index 746f3be8..6521144e 100644 --- a/src/openvpn/tls_crypt.h +++ b/src/openvpn/tls_crypt.h @@ -83,6 +83,7 @@ #include "buffer.h" #include "crypto.h" #include "session_id.h" +#include "ssl_common.h" #define TLS_CRYPT_TAG_SIZE (256/8) #define TLS_CRYPT_PID_SIZE (sizeof(packet_id_type) + sizeof(net_time_t)) @@ -181,6 +182,19 @@ void tls_crypt_v2_init_client_key(struct key_ctx_bi *key, const char *key_file, const char *key_inline); +/** + * Extract a tls-crypt-v2 client key from a P_CONTROL_HARD_RESET_CLIENT_V3 + * message, and load the key into the supplied tls wrap context. + * + * @param buf Buffer containing a received P_CONTROL_HARD_RESET_CLIENT_V3 + * message. + * @param ctx tls-wrap context to be initialized with the client key. + * + * @returns true if a key was successfully extracted. + */ +bool tls_crypt_v2_extract_client_key(struct buffer *buf, + struct tls_wrap_ctx *ctx); + /** * Generate a tls-crypt-v2 server key, and write to file. * diff --git a/tests/unit_tests/openvpn/Makefile.am b/tests/unit_tests/openvpn/Makefile.am index 1ff62615..b51973fa 100644 --- a/tests/unit_tests/openvpn/Makefile.am +++ b/tests/unit_tests/openvpn/Makefile.am @@ -59,7 +59,7 @@ packet_id_testdriver_SOURCES = test_packet_id.c mock_msg.c \ tls_crypt_testdriver_CFLAGS = @TEST_CFLAGS@ \ -I$(openvpn_includedir) -I$(compat_srcdir) -I$(openvpn_srcdir) \ - $(OPTIONAL_CRYPTO_CFLAGS) + $(OPTIONAL_CRYPTO_CFLAGS) $(OPTIONAL_PKCS11_HELPER_CFLAGS) tls_crypt_testdriver_LDFLAGS = @TEST_LDFLAGS@ \ $(OPTIONAL_CRYPTO_LIBS) tls_crypt_testdriver_SOURCES = test_tls_crypt.c mock_msg.c \ From patchwork Wed Jul 4 07:54:04 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Steffan Karger X-Patchwork-Id: 402 Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director10.mail.ord1d.rsapps.net ([172.27.255.51]) by backend30.mail.ord1d.rsapps.net (Dovecot) with LMTP id 3ArMEhYKPVvCIQAAIUCqbw for ; Wed, 04 Jul 2018 13:55:34 -0400 Received: from proxy7.mail.iad3a.rsapps.net ([172.27.255.51]) by director10.mail.ord1d.rsapps.net (Dovecot) with LMTP id W3mKFxYKPVvIHwAApN4f7A ; Wed, 04 Jul 2018 13:55:34 -0400 Received: from smtp15.gate.iad3a ([172.27.255.51]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy7.mail.iad3a.rsapps.net with LMTP id wPh3FRYKPVvhJQAAnPvY+A ; Wed, 04 Jul 2018 13:55:34 -0400 X-Spam-Threshold: 95 X-Spam-Score: 0 X-Spam-Flag: NO X-Virus-Scanned: OK X-Orig-To: openvpnslackdevel@openvpn.net X-Originating-Ip: [216.105.38.7] Authentication-Results: smtp15.gate.iad3a.rsapps.net; iprev=pass policy.iprev="216.105.38.7"; spf=pass smtp.mailfrom="openvpn-devel-bounces@lists.sourceforge.net" smtp.helo="lists.sourceforge.net"; dkim=fail (signature verification failed) header.d=sourceforge.net; dkim=fail (signature verification failed) header.d=sf.net; dkim=fail (signature verification failed) header.d=karger-me.20150623.gappssmtp.com; dmarc=none (p=nil; dis=none) header.from=karger.me X-Suspicious-Flag: YES X-Classification-ID: 72b0b6e0-7fb3-11e8-9b7d-525400f46865-1-1 Received: from [216.105.38.7] ([216.105.38.7:51522] helo=lists.sourceforge.net) by smtp15.gate.iad3a.rsapps.net (envelope-from ) (ecelerity 4.2.1.56364 r(Core:4.2.1.14)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id C2/E8-08160-61A0D3B5; Wed, 04 Jul 2018 13:55:34 -0400 Received: from [127.0.0.1] (helo=sfs-ml-4.v29.lw.sourceforge.com) by sfs-ml-4.v29.lw.sourceforge.com with esmtp (Exim 4.90_1) (envelope-from ) id 1falzR-0001RC-4k; Wed, 04 Jul 2018 17:54:53 +0000 Received: from [172.30.20.202] (helo=mx.sourceforge.net) by sfs-ml-4.v29.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.90_1) (envelope-from ) id 1falzP-0001Qv-Kh for openvpn-devel@lists.sourceforge.net; Wed, 04 Jul 2018 17:54:51 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=References:In-Reply-To:Message-Id:Date:Subject:Cc: To:From:Sender:Reply-To:MIME-Version:Content-Type:Content-Transfer-Encoding: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=tmuh4xWOxihf289vDt3cxwLkaNmDeXNX3n0I6VLyKAY=; b=MqVOIhReyCV+sLQG8ugFeiXhph v7ZusR+dO9nli1IT8pNLQ3LWpyA0UD24VVhIwgIIY5STA2v0dbDVg7+QndlBMCOVhWdBjYb6zejk5 cQ/FIFEokzEnDFTgl8fAsznDXktZQLZzgD/UHc0wwslk1Er4+qFo4DEgZtch+bb+/xHQ=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=References:In-Reply-To:Message-Id:Date:Subject:Cc:To:From:Sender:Reply-To :MIME-Version:Content-Type:Content-Transfer-Encoding:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=tmuh4xWOxihf289vDt3cxwLkaNmDeXNX3n0I6VLyKAY=; b=RAUXRX3UUwMPaPErC9ln67SMIi GjkZWGc0UZf/o3VSw7XQNLwMv4BjtyEevfE8lHoGm+Bj0WosIo7ccBax3d2r7MXLfMl5lp4Y0UU4s CMa8LmsQ36MORQajKji4LJMewHRnjorMQ2JmyGk+cWeK0r5qgQAX7wmwt2FuHQiglDjw=; Received: from mail-ed1-f67.google.com ([209.85.208.67]) by sfi-mx-2.v28.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES128-GCM-SHA256:128) (Exim 4.90_1) id 1falzJ-00GkI4-Os for openvpn-devel@lists.sourceforge.net; Wed, 04 Jul 2018 17:54:51 +0000 Received: by mail-ed1-f67.google.com with SMTP id b12-v6so4573115edt.8 for ; Wed, 04 Jul 2018 10:54:45 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=karger-me.20150623.gappssmtp.com; s=20150623; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=tmuh4xWOxihf289vDt3cxwLkaNmDeXNX3n0I6VLyKAY=; b=ooZwVfLwFTMOmU7nDAoFsih/5Ew96Qju1cFVJDgt0LL+YAUBOuDj1R5me+gX/rHs8G gbEb7dV/rI9KooU7gjur/kzyqmsRnZTI/O1WoiCcOeyrV3UPZy23lY9a20GHvOH/wY2S pXQZNyciXkkd9a4FWOHzTgjeAnST+7P0sI1TtsjdWxnT82VHlp4gonCKB6O2rCYIgm61 FzneqaCCddJgr50+65tZScaOT2saFsl+H8mrjmVeEaTrjoxtmCrmXzFoS/+kvlUPk0Gz qls75Ol7+TtAWi4cTxCFbxiHGg8pBkUB7SOlsJZ87qUGgjCWPv3sBg8kN8TYoL5vZfBf 9OqQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=tmuh4xWOxihf289vDt3cxwLkaNmDeXNX3n0I6VLyKAY=; b=bENTqEnuGlT4xgEWS/0j4RRnLVV4Rft7/mKjEqxb5J13IfRTbKLPZLrzKsc3g1eewq aXE1OeePM3LcTLiEICbUbR7PevI2+FxKduAeZPZSFLllmIKJTG3kNI4vw3WlKeMl7FGf 1jZG2BLeu8/d/kcCGZ8km3iu5YUjebk3Hu3yZUcZIhugu6wW61dc5tRiE+s4tzeFMhGK O70bRsFeVc02PGGIvtsaOiRODv/WOr5JdrIDdWBBml7wpca0IZWM5J9wQKw6UQ6XCH+K /b2zfW9zbxxlVc0XuEjgRDAVxsswur5FgZFQhkcdeE86V9r8C0sK13/vaeo0ey6w3Iet Gi9g== X-Gm-Message-State: APt69E2GA0DYzh/XRKEbogdg+p9a/i1CwjmUpF4/MBk3wEEcB/ytG4NA QfWwI7VbcXdkcPqguL4NAUmDPUKKpNY= X-Google-Smtp-Source: AAOMgpdUXe741gsTWe4nQ+fz6QSwv0rkEht6z6+sSx9f6/7g37tcws48r+4sGsTu4Z+9q2htp1w4ZA== X-Received: by 2002:a50:9943:: with SMTP id l3-v6mr3636134edb.272.1530726878942; Wed, 04 Jul 2018 10:54:38 -0700 (PDT) Received: from vesta.fritz.box ([2001:985:e54:1:f598:331e:3cdf:2649]) by smtp.gmail.com with ESMTPSA id o2-v6sm1948961edd.84.2018.07.04.10.54.38 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Wed, 04 Jul 2018 10:54:38 -0700 (PDT) From: Steffan Karger To: openvpn-devel@lists.sourceforge.net Date: Wed, 4 Jul 2018 19:54:04 +0200 Message-Id: <20180704175404.22371-9-steffan@karger.me> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20180704175404.22371-1-steffan@karger.me> References: <1512734870-17133-1-git-send-email-steffan.karger@fox-it.com> <20180704175404.22371-1-steffan@karger.me> X-Spam-Report: Spam Filtering performed by mx.sourceforge.net. See http://spamassassin.org/tag/ for more details. -0.0 RCVD_IN_DNSWL_NONE RBL: Sender listed at http://www.dnswl.org/, no trust [209.85.208.67 listed in list.dnswl.org] -0.0 RCVD_IN_MSPIKE_H2 RBL: Average reputation (+2) [209.85.208.67 listed in wl.mailspike.net] -0.0 SPF_PASS SPF: sender matches SPF record 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature -0.0 T_DKIMWL_WL_MED DKIMwl.org - Whitelisted Medium sender X-Headers-End: 1falzJ-00GkI4-Os Subject: [Openvpn-devel] [PATCH v2 9/9] tls-crypt-v2: add script hook to verify metadata X-BeenThere: openvpn-devel@lists.sourceforge.net X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Antonio Quartulli MIME-Version: 1.0 Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox From: Steffan Karger To allow rejecting incoming connections very early in the handshake, add a --tls-crypt-v2-verify option that allows administators to run an external command to verify the metadata from the client key. See doc/tls-crypt-v2.txt for more details. Because of the extra dependencies, this requires adding a mock parse_line() to the tls-crypt unit tests. Signed-off-by: Antonio Quartulli Signed-off-by: Steffan Karger --- Changes.rst | 12 ++++ doc/openvpn.8 | 35 +++++++++++- src/openvpn/init.c | 1 + src/openvpn/options.c | 7 +++ src/openvpn/options.h | 2 + src/openvpn/ssl.c | 16 ++++-- src/openvpn/ssl_common.h | 1 + src/openvpn/tls_crypt.c | 69 ++++++++++++++++++++++- src/openvpn/tls_crypt.h | 3 +- tests/unit_tests/openvpn/Makefile.am | 12 +++- tests/unit_tests/openvpn/test_tls_crypt.c | 16 ++++++ 11 files changed, 160 insertions(+), 14 deletions(-) diff --git a/Changes.rst b/Changes.rst index a6090cf5..e77b3d79 100644 --- a/Changes.rst +++ b/Changes.rst @@ -1,3 +1,15 @@ +Overview of changes in 2.5 +========================== + +New features +------------ +Client-specific tls-crypt keys (``--tls-crypt-v2``) + ``tls-crypt-v2`` adds the ability to supply each client with a unique + tls-crypt key. This allows large organisations and VPN providers to profit + from the same DoS and TLS stack protection that small deployments can + already achieve using ``tls-auth`` or ``tls-crypt``. + + Overview of changes in 2.4 ========================== diff --git a/doc/openvpn.8 b/doc/openvpn.8 index abefa86a..497f6ca3 100644 --- a/doc/openvpn.8 +++ b/doc/openvpn.8 @@ -5293,9 +5293,38 @@ If supplied, include the supplied in the wrapped client key. This metadata must be supplied in base64\-encoded form. The metadata must be at most 735 bytes long (980 bytes in base64). -.B TODO -Metadata handling is not yet implemented. This text will be updated by the -commit that introduces metadata handling. +If no metadata is supplied, OpenVPN will use a 64-bit unix timestamp +representing the current time in UTC, encoded in network order, as metadata for +the generated key. + +Servers can use +.B \-\-tls\-crypt\-v2\-verify +to specify a metadata verification command. +.\"********************************************************* +.TP +.B \-\-tls\-crypt\-v2\-verify cmd + +Run command +.B cmd +to verify the metadata of the client-specific tls-crypt-v2 key of a connecting +client. This allows server administrators to reject client connections, before +exposing the TLS stack (including the notoriously dangerous X.509 and ASN.1 +stacks) to the connecting client. + +OpenVPN supplies the following env vars to the command: +.RS +.IP \[bu] 2 +script_type is set to "tls-crypt-v2-verify" +.IP \[bu] +metadata_type is set to "0" is the metadata was user supplied, or "1" if it's a +64-bit unix timestamp representing the key creation time. +.IP \[bu] +metadata_file contains the filename of a temporary file that contains the client +metadata. +.RE + +.IP +The command can reject the connection by exitingwith a non-zero exit code. .\"********************************************************* .TP .B \-\-askpass [file] diff --git a/src/openvpn/init.c b/src/openvpn/init.c index 0874edff..9da1f218 100644 --- a/src/openvpn/init.c +++ b/src/openvpn/init.c @@ -2865,6 +2865,7 @@ do_init_crypto_tls(struct context *c, const unsigned int flags) if (options->tls_server) { to.tls_wrap.tls_crypt_v2_server_key = c->c1.ks.tls_crypt_v2_server_key; + to.tls_crypt_v2_verify_script = c->options.tls_crypt_v2_verify_script; } } diff --git a/src/openvpn/options.c b/src/openvpn/options.c index 60be7d15..19ac470f 100644 --- a/src/openvpn/options.c +++ b/src/openvpn/options.c @@ -629,6 +629,8 @@ static const char usage_message[] = "--tls-crypt-v2-genkey client|server keyfile [base64 metadata]: Generate a\n" " fresh tls-crypt-v2 client or server key, and store to\n" " keyfile. If supplied, include metadata in wrapped key.\n" + "--tls-crypt-v2-verify cmd : Run command cmd to verify the metadata of the\n" + " client-supplied tls-crypt-v2 client key\n" "--askpass [file]: Get PEM password from controlling tty before we daemonize.\n" "--auth-nocache : Don't cache --askpass or --auth-user-pass passwords.\n" "--crl-verify crl ['dir']: Check peer certificate against a CRL.\n" @@ -8065,6 +8067,11 @@ add_option(struct options *options, options->tls_crypt_v2_metadata = p[3]; } } + else if (streq(p[0], "tls-crypt-v2-verify") && p[1] && !p[2]) + { + VERIFY_PERMISSION(OPT_P_GENERAL); + options->tls_crypt_v2_verify_script = p[1]; + } else if (streq(p[0], "key-method") && p[1] && !p[2]) { int key_method; diff --git a/src/openvpn/options.h b/src/openvpn/options.h index d5407b5b..2f93e44f 100644 --- a/src/openvpn/options.h +++ b/src/openvpn/options.h @@ -576,6 +576,8 @@ struct options const char *tls_crypt_v2_genkey_file; const char *tls_crypt_v2_metadata; + const char *tls_crypt_v2_verify_script; + /* Allow only one session */ bool single_session; diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c index 9fc1efa3..d3951f68 100644 --- a/src/openvpn/ssl.c +++ b/src/openvpn/ssl.c @@ -1541,14 +1541,15 @@ write_control_auth(struct tls_session *session, static bool read_control_auth(struct buffer *buf, struct tls_wrap_ctx *ctx, - const struct link_socket_actual *from) + const struct link_socket_actual *from, + const struct tls_options *opt) { struct gc_arena gc = gc_new(); bool ret = false; const uint8_t opcode = *(BPTR(buf)) >> P_OPCODE_SHIFT; if (opcode == P_CONTROL_HARD_RESET_CLIENT_V3 - && !tls_crypt_v2_extract_client_key(buf, ctx)) + && !tls_crypt_v2_extract_client_key(buf, ctx, opt)) { msg (D_TLS_ERRORS, "TLS Error: can not extract tls-crypt-v2 client key from %s", @@ -3629,7 +3630,8 @@ tls_pre_decrypt(struct tls_multi *multi, goto error; } - if (!read_control_auth(buf, &session->tls_wrap, from)) + if (!read_control_auth(buf, &session->tls_wrap, from, + session->opt)) { goto error; } @@ -3682,7 +3684,8 @@ tls_pre_decrypt(struct tls_multi *multi, if (op == P_CONTROL_SOFT_RESET_V1 && DECRYPT_KEY_ENABLED(multi, ks)) { - if (!read_control_auth(buf, &session->tls_wrap, from)) + if (!read_control_auth(buf, &session->tls_wrap, from, + session->opt)) { goto error; } @@ -3703,7 +3706,8 @@ tls_pre_decrypt(struct tls_multi *multi, do_burst = true; } - if (!read_control_auth(buf, &session->tls_wrap, from)) + if (!read_control_auth(buf, &session->tls_wrap, from, + session->opt)) { goto error; } @@ -3905,7 +3909,7 @@ tls_pre_decrypt_lite(const struct tls_auth_standalone *tas, bool status; /* HMAC test, if --tls-auth was specified */ - status = read_control_auth(&newbuf, &tls_wrap_tmp, from); + status = read_control_auth(&newbuf, &tls_wrap_tmp, from, NULL); free_buf(&newbuf); if (tls_wrap_tmp.cleanup_key_ctx) { diff --git a/src/openvpn/ssl_common.h b/src/openvpn/ssl_common.h index d744881c..923cd959 100644 --- a/src/openvpn/ssl_common.h +++ b/src/openvpn/ssl_common.h @@ -293,6 +293,7 @@ struct tls_options bool ncp_enabled; bool tls_crypt_v2; + const char *tls_crypt_v2_verify_script; /** TLS handshake wrapping state */ struct tls_wrap_ctx tls_wrap; diff --git a/src/openvpn/tls_crypt.c b/src/openvpn/tls_crypt.c index 0180a753..3cdf2d01 100644 --- a/src/openvpn/tls_crypt.c +++ b/src/openvpn/tls_crypt.c @@ -29,9 +29,11 @@ #include "syshead.h" +#include "argv.h" #include "base64.h" #include "crypto.h" #include "platform.h" +#include "run_command.h" #include "session_id.h" #include "ssl.h" @@ -520,9 +522,69 @@ error_exit: return ret; } +static bool +tls_crypt_v2_verify_metadata(const struct tls_wrap_ctx *ctx, + const struct tls_options *opt) +{ + bool ret = false; + struct gc_arena gc = gc_new(); + const char *tmp_file = NULL; + struct buffer metadata = ctx->tls_crypt_v2_metadata; + int metadata_type = buf_read_u8(&metadata); + if (metadata_type < 0) + { + msg(M_WARN, "ERROR: no metadata type"); + goto cleanup; + } + + tmp_file = platform_create_temp_file(opt->tmp_dir, "tls_crypt_v2_metadata_", + &gc); + if (!tmp_file || !buffer_write_file(tmp_file, &metadata)) + { + msg(M_WARN, "ERROR: could not write metadata to file"); + goto cleanup; + } + + char metadata_type_str[4] = { 0 }; /* Max value: 255 */ + openvpn_snprintf(metadata_type_str, sizeof(metadata_type_str), + "%i", metadata_type); + struct env_set *es = env_set_create(NULL); + setenv_str(es, "script_type", "tls-crypt-v2-verify"); + setenv_str(es, "metadata_type", metadata_type_str); + setenv_str(es, "metadata_file", tmp_file); + + struct argv argv = argv_new(); + argv_parse_cmd(&argv, opt->tls_crypt_v2_verify_script); + argv_msg_prefix(D_TLS_DEBUG, &argv, "Executing tls-crypt-v2-verify"); + + ret = openvpn_run_script(&argv, es, 0, "--tls-crypt-v2-verify"); + + argv_reset(&argv); + env_set_destroy(es); + + if (!platform_unlink(tmp_file)) + { + msg(M_WARN, "WARNING: failed to remove temp file '%s", tmp_file); + } + + if (ret) + { + msg(D_HANDSHAKE, "TLS CRYPT V2 VERIFY SCRIPT OK"); + } + else + { + msg(D_HANDSHAKE, "TLS CRYPT V2 VERIFY SCRIPT ERROR"); + } + +cleanup: + gc_free(&gc); + return ret; +} + bool tls_crypt_v2_extract_client_key(struct buffer *buf, - struct tls_wrap_ctx *ctx) + struct tls_wrap_ctx *ctx, + const struct tls_options *opt) { static const int hard_reset_length = TLS_CRYPT_OFF_CT + sizeof(uint8_t) + sizeof(packet_id_type); @@ -566,6 +628,11 @@ tls_crypt_v2_extract_client_key(struct buffer *buf, /* Remove client key from buffer so tls-crypt code can unwrap message */ ASSERT(buf_inc_len(buf, -(BLEN(&wrapped_client_key)))); + if (opt && opt->tls_crypt_v2_verify_script) + { + return tls_crypt_v2_verify_metadata(ctx, opt); + } + return true; } diff --git a/src/openvpn/tls_crypt.h b/src/openvpn/tls_crypt.h index 6521144e..5d9d29d6 100644 --- a/src/openvpn/tls_crypt.h +++ b/src/openvpn/tls_crypt.h @@ -193,7 +193,8 @@ void tls_crypt_v2_init_client_key(struct key_ctx_bi *key, * @returns true if a key was successfully extracted. */ bool tls_crypt_v2_extract_client_key(struct buffer *buf, - struct tls_wrap_ctx *ctx); + struct tls_wrap_ctx *ctx, + const struct tls_options *opt); /** * Generate a tls-crypt-v2 server key, and write to file. diff --git a/tests/unit_tests/openvpn/Makefile.am b/tests/unit_tests/openvpn/Makefile.am index b51973fa..80eca766 100644 --- a/tests/unit_tests/openvpn/Makefile.am +++ b/tests/unit_tests/openvpn/Makefile.am @@ -6,7 +6,10 @@ if HAVE_LD_WRAP_SUPPORT check_PROGRAMS += argv_testdriver buffer_testdriver endif -check_PROGRAMS += crypto_testdriver packet_id_testdriver tls_crypt_testdriver +check_PROGRAMS += crypto_testdriver packet_id_testdriver +if HAVE_LD_WRAP_SUPPORT +check_PROGRAMS += tls_crypt_testdriver +endif TESTS = $(check_PROGRAMS) @@ -60,14 +63,17 @@ packet_id_testdriver_SOURCES = test_packet_id.c mock_msg.c \ tls_crypt_testdriver_CFLAGS = @TEST_CFLAGS@ \ -I$(openvpn_includedir) -I$(compat_srcdir) -I$(openvpn_srcdir) \ $(OPTIONAL_CRYPTO_CFLAGS) $(OPTIONAL_PKCS11_HELPER_CFLAGS) -tls_crypt_testdriver_LDFLAGS = @TEST_LDFLAGS@ \ +tls_crypt_testdriver_LDFLAGS = @TEST_LDFLAGS@ -Wl,--wrap=parse_line \ $(OPTIONAL_CRYPTO_LIBS) tls_crypt_testdriver_SOURCES = test_tls_crypt.c mock_msg.c \ + $(openvpn_srcdir)/argv.c \ $(openvpn_srcdir)/base64.c \ $(openvpn_srcdir)/buffer.c \ $(openvpn_srcdir)/crypto.c \ $(openvpn_srcdir)/crypto_mbedtls.c \ $(openvpn_srcdir)/crypto_openssl.c \ + $(openvpn_srcdir)/env_set.c \ $(openvpn_srcdir)/otime.c \ $(openvpn_srcdir)/packet_id.c \ - $(openvpn_srcdir)/platform.c + $(openvpn_srcdir)/platform.c \ + $(openvpn_srcdir)/run_command.c diff --git a/tests/unit_tests/openvpn/test_tls_crypt.c b/tests/unit_tests/openvpn/test_tls_crypt.c index f53255cd..b7a5359f 100644 --- a/tests/unit_tests/openvpn/test_tls_crypt.c +++ b/tests/unit_tests/openvpn/test_tls_crypt.c @@ -43,8 +43,24 @@ #define TESTBUF_SIZE 128 +/* Defines for use in the tests and the mock parse_line() */ +#define PATH1 "/s p a c e" +#define PATH2 "/foo bar/baz" +#define PARAM1 "param1" +#define PARAM2 "param two" + const char plaintext_short[1]; +int +__wrap_parse_line(const char *line, char **p, const int n, const char *file, + const int line_num, int msglevel, struct gc_arena *gc) +{ + p[0] = PATH1 PATH2; + p[1] = PARAM1; + p[2] = PARAM2; + return 3; +} + struct test_tls_crypt_context { struct crypto_options co; struct key_type kt;