From patchwork Fri May 10 02:11:08 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Arne Schwabe X-Patchwork-Id: 731 X-Patchwork-Delegate: davids@openvpn.net Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director12.mail.ord1d.rsapps.net ([172.31.255.6]) by backend30.mail.ord1d.rsapps.net with LMTP id aKZIDSFv1VywZgAAIUCqbw for ; Fri, 10 May 2019 08:31:29 -0400 Received: from proxy9.mail.iad3b.rsapps.net ([172.31.255.6]) by director12.mail.ord1d.rsapps.net with LMTP id UHIKCyFv1VwtPwAAIasKDg ; Fri, 10 May 2019 08:31:29 -0400 Received: from smtp26.gate.iad3b ([172.31.255.6]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy9.mail.iad3b.rsapps.net with LMTP id 4PyQBSFv1VwXBAAAC4PSzw ; Fri, 10 May 2019 08:31:29 -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: smtp26.gate.iad3b.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; dmarc=none (p=nil; dis=none) header.from=rfc2549.org X-Suspicious-Flag: YES X-Classification-ID: 887f5160-731f-11e9-b216-5254001088d3-1-1 Received: from [216.105.38.7] ([216.105.38.7:49376] helo=lists.sourceforge.net) by smtp26.gate.iad3b.rsapps.net (envelope-from ) (ecelerity 4.2.38.62370 r(:)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id 15/E7-03413-02F65DC5; Fri, 10 May 2019 08:31:28 -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 1hP4fl-0005ve-FL; Fri, 10 May 2019 12:30:45 +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 1hP4fk-0005vS-E9 for openvpn-devel@lists.sourceforge.net; Fri, 10 May 2019 12:30: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:To: From:Sender:Reply-To:Cc: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=/cz309KnGFUYxUdKGg/ZfI16g7o0BqlOFHdlRbmJtWA=; b=lu6FCcpZx+Hrm6TnxxQwj4Vf3G J0ps91pwfNGbsYnutBKM9Et95AjzYNu4A3Tj8th6q9uxz9QjnIjYzjiDcIOl/gtgz0MNpR5rP6EW0 9GjAUkx202WXpwNLoLzXEbwyBQiIessLxj/sGlDciApGH0Tymy9pm74FIgAe7Lt4USxU=; 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:To:From:Sender:Reply-To:Cc :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=/cz309KnGFUYxUdKGg/ZfI16g7o0BqlOFHdlRbmJtWA=; b=ebCjMRy4GpJE/PSsKW8ETStEyv 19gvLyn3KgTRTAWSDvTRDCq1UTHvE19x5fE7a4ATwdSJBHUnk/PQIYlHBW87z2mpDB/DdPDEPfmYy PUS3ZXN51juI+HkGvNvCT6gO3N471IqACCe0pojK9WhlNrIw3RXxlTSpzjKuG+nlVx68=; Received: from mail.blinkt.de ([192.26.174.232]) by sfi-mx-1.v28.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.90_1) id 1hP4fi-00FeDX-W3 for openvpn-devel@lists.sourceforge.net; Fri, 10 May 2019 12:30:44 +0000 Received: from kamera.blinkt.de ([2001:638:502:390:20c:29ff:fec8:535c]) by mail.blinkt.de with smtp (Exim 4.91 (FreeBSD)) (envelope-from ) id 1hP4Mt-000B4j-1Y for openvpn-devel@lists.sourceforge.net; Fri, 10 May 2019 14:11:15 +0200 Received: (nullmailer pid 30516 invoked by uid 10006); Fri, 10 May 2019 12:11:14 -0000 From: Arne Schwabe To: openvpn-devel@lists.sourceforge.net Date: Fri, 10 May 2019 14:11:08 +0200 Message-Id: <20190510121114.30468-2-arne@rfc2549.org> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20190510121114.30468-1-arne@rfc2549.org> References: <20190510121114.30468-1-arne@rfc2549.org> X-Spam-Report: Spam Filtering performed by mx.sourceforge.net. See http://spamassassin.org/tag/ for more details. 0.1 HEADER_FROM_DIFFERENT_DOMAINS From and EnvelopeFrom 2nd level mail domains are different 0.2 AWL AWL: Adjusted score from AWL reputation of From: address X-Headers-End: 1hP4fi-00FeDX-W3 Subject: [Openvpn-devel] [PATCH v3 1/7] Write key to stdout if filename is not given 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 This change is preperation for changing the way --genkey works. Acked-By: David Sommerseth --- src/openvpn/crypto.c | 13 +++++++++++-- src/openvpn/tls_crypt.c | 13 +++++++++++-- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/openvpn/crypto.c b/src/openvpn/crypto.c index ff9dbfdc..eb56421b 100644 --- a/src/openvpn/crypto.c +++ b/src/openvpn/crypto.c @@ -1465,8 +1465,13 @@ write_key_file(const int nkeys, const char *filename) buf_printf(&out, "%s\n", static_key_foot); + /* write key file to stdout if no filename given */ + if (!filename || strcmp(filename, "")==0) + { + printf("%s\n", BPTR(&out)); + } /* write key file, now formatted in out, to file */ - if (!buffer_write_file(filename, &out)) + else if (!buffer_write_file(filename, &out)) { nbits = -1; } @@ -1870,7 +1875,11 @@ write_pem_key_file(const char *filename, const char *pem_name) goto cleanup; } - if (!buffer_write_file(filename, &server_key_pem)) + if (!filename || strcmp(filename, "")==0) + { + printf("%s\n", BPTR(&server_key_pem)); + } + else if (!buffer_write_file(filename, &server_key_pem)) { msg(M_ERR, "ERROR: could not write key file"); goto cleanup; diff --git a/src/openvpn/tls_crypt.c b/src/openvpn/tls_crypt.c index d6a82252..e8889e93 100644 --- a/src/openvpn/tls_crypt.c +++ b/src/openvpn/tls_crypt.c @@ -697,7 +697,16 @@ tls_crypt_v2_write_client_key_file(const char *filename, goto cleanup; } - if (!buffer_write_file(filename, &client_key_pem)) + const char *client_filename = filename; + const char *client_inline = NULL; + + if (!filename || streq(filename, "")) + { + printf("%s\n", BPTR(&client_key_pem)); + client_filename = INLINE_FILE_TAG; + client_inline = (const char *)BPTR(&client_key_pem); + } + else if (!buffer_write_file(filename, &client_key_pem)) { msg(M_FATAL, "ERROR: could not write client key file"); goto cleanup; @@ -708,7 +717,7 @@ tls_crypt_v2_write_client_key_file(const char *filename, 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); + client_filename, client_inline); free_key_ctx_bi(&test_client_key); /* Sanity check: unwrap and load client key (as "server") */ From patchwork Fri May 10 02:11:09 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Arne Schwabe X-Patchwork-Id: 730 X-Patchwork-Delegate: davids@openvpn.net Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director10.mail.ord1d.rsapps.net ([172.31.255.6]) by backend30.mail.ord1d.rsapps.net with LMTP id yI78Cxlv1Vy7JAAAIUCqbw for ; Fri, 10 May 2019 08:31:21 -0400 Received: from proxy15.mail.iad3b.rsapps.net ([172.31.255.6]) by director10.mail.ord1d.rsapps.net with LMTP id YP6XCRlv1VxtXAAApN4f7A ; Fri, 10 May 2019 08:31:21 -0400 Received: from smtp11.gate.iad3b ([172.31.255.6]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy15.mail.iad3b.rsapps.net with LMTP id AN1VBBlv1Vy5FgAAhyf7VQ ; Fri, 10 May 2019 08:31:21 -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: smtp11.gate.iad3b.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; dmarc=none (p=nil; dis=none) header.from=rfc2549.org X-Suspicious-Flag: YES X-Classification-ID: 8325be34-731f-11e9-a9a8-52540070b5bb-1-1 Received: from [216.105.38.7] ([216.105.38.7:60293] helo=lists.sourceforge.net) by smtp11.gate.iad3b.rsapps.net (envelope-from ) (ecelerity 4.2.38.62370 r(:)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id 69/60-08385-71F65DC5; Fri, 10 May 2019 08:31:20 -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 1hP4fY-00049B-TV; Fri, 10 May 2019 12:30:32 +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 1hP4fX-000490-3h for openvpn-devel@lists.sourceforge.net; Fri, 10 May 2019 12:30:31 +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:To: From:Sender:Reply-To:Cc: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=a4KcmmniY8cN0tTVyWy67BOHHErDSv+UnoUSUqoIKzA=; b=X6AnKnUP9xUAbNsiwyqpw22w+V k3HNzyl/voTUdZ844bCguxZFXABEnUtaWnAKH4CyiLVGujFMdKugm1tCODhavfrhNQXth5Wdht0ac A1wmA43dWj9xhgLf2cFjIvYLOMHW9yP1cfLbILfrDM/XhsF5tiFuXpz80m7uXlWS0LDA=; 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:To:From:Sender:Reply-To:Cc :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=a4KcmmniY8cN0tTVyWy67BOHHErDSv+UnoUSUqoIKzA=; b=TlxbTH5Q9AFjBGqJvgBafCcgbt ZzKL81numCNC/Chfm4DLigrJb4xedyP6xBN0GQiPAUCxdKHm2VC4vCKzr5/0l7I49RU42fMFLL5aS ryCTUZBEQeGW2RyKktxoxo/heVPfvTvCE+OOxX7bk6i3ckW2SthFNqUOVjLWPhbSRKZM=; Received: from mail.blinkt.de ([192.26.174.232]) by sfi-mx-4.v28.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.90_1) id 1hP4fV-00BUGG-I9 for openvpn-devel@lists.sourceforge.net; Fri, 10 May 2019 12:30:31 +0000 Received: from kamera.blinkt.de ([2001:638:502:390:20c:29ff:fec8:535c]) by mail.blinkt.de with smtp (Exim 4.91 (FreeBSD)) (envelope-from ) id 1hP4Mt-000B4o-3h for openvpn-devel@lists.sourceforge.net; Fri, 10 May 2019 14:11:15 +0200 Received: (nullmailer pid 30519 invoked by uid 10006); Fri, 10 May 2019 12:11:15 -0000 From: Arne Schwabe To: openvpn-devel@lists.sourceforge.net Date: Fri, 10 May 2019 14:11:09 +0200 Message-Id: <20190510121114.30468-3-arne@rfc2549.org> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20190510121114.30468-1-arne@rfc2549.org> References: <20190510121114.30468-1-arne@rfc2549.org> X-Spam-Report: Spam Filtering performed by mx.sourceforge.net. See http://spamassassin.org/tag/ for more details. 0.1 HEADER_FROM_DIFFERENT_DOMAINS From and EnvelopeFrom 2nd level mail domains are different 0.5 AWL AWL: Adjusted score from AWL reputation of From: address X-Headers-End: 1hP4fV-00BUGG-I9 Subject: [Openvpn-devel] [PATCH v3 2/7] Implement --genkey type keyfile syntax and migrate tls-crypt-v2 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 This unifies our key generation and also migrates the generation of the tls-crypt-v2 keys. Since tls-crypt-v2 is not included in any released version, we remove the the old syntax without compatibility. --- doc/openvpn.8 | 79 +++++++++++++++++++++++-------------------- src/openvpn/init.c | 61 ++++++++++++++++++++------------- src/openvpn/options.c | 67 ++++++++++++++++++++++-------------- src/openvpn/options.h | 11 ++++-- 4 files changed, 131 insertions(+), 87 deletions(-) diff --git a/doc/openvpn.8 b/doc/openvpn.8 index ce440447..90a6be91 100644 --- a/doc/openvpn.8 +++ b/doc/openvpn.8 @@ -5242,7 +5242,7 @@ 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 +.B \-\-genkey tls\-crypt\-v2\-client option. For servers, @@ -5250,7 +5250,7 @@ For servers, 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). +.B \-\-genkey tls\-crypt\-v2\-client\fR). On servers, this option can be used together with the .B \-\-tls\-auth @@ -5260,36 +5260,6 @@ 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). - -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. - -A tls\-crypt\-v2 client key is wrapped using a server key. To generate a -client key, the user must therefore supply the server key using the -.B \-\-tls\-crypt\-v2 -option. - -Servers can use -.B \-\-tls\-crypt\-v2\-verify -to specify a metadata verification command. -.\"********************************************************* -.TP .B \-\-tls\-crypt\-v2\-verify cmd Run command @@ -5741,13 +5711,18 @@ Show all available elliptic curves to use with the .B \-\-ecdh\-curve option. .\"********************************************************* -.SS Generate a random key: -Used only for non\-TLS static key encryption mode. +.SS Generating random key material: .\"********************************************************* .TP -.B \-\-genkey file +.B \-\-genkey keytype keyfile (Standalone) -Generate a random key to be used as a shared secret, for use with the +Generate a random key to be used of the type keytype. if keyfile is left out or empty +the key will be output on stdout. See the following sections for the different keytypes. + +.\"********************************************************* +.TP +.B \-\-genkey secret|tls-crypt|tls-auth keyfile +Generate a shared secret, for use with the .B \-\-secret , .B \-\-tls-auth @@ -5755,6 +5730,8 @@ or .B \-\-tls-crypt options. Stores the key in .B file\fR. +All three variants (secret, tls-crypt, and tls-auth) generate the same type of +key. The aliases are added for convience. If using this for .B \-\-secret @@ -5762,6 +5739,36 @@ If using this for such as .BR scp (1)\fR. .\"********************************************************* +.TP +.B \-\-genkey tls\-crypt\-v2-server keyfile + +Generate a \-\-tls\-crypt\-v2 server key and store the key in +.B keyfile\fR. + +.TP +.B \-\-genkey tls\-crypt\-v2-client keyfile [metadata] + +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). + +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. + +A tls\-crypt\-v2 client key is wrapped using a server key. To generate a +client key, the user must therefore supply the server key using the +.B \-\-tls\-crypt\-v2 +option. + +Servers can use +.B \-\-tls\-crypt\-v2\-verify +to specify a metadata verification command. +.\"********************************************************* .SS TUN/TAP persistent tunnel config mode: Available with Linux 2.4.7+. These options comprise a standalone mode of OpenVPN which can be used to create and delete persistent tunnels. diff --git a/src/openvpn/init.c b/src/openvpn/init.c index 3c449678..9260067f 100644 --- a/src/openvpn/init.c +++ b/src/openvpn/init.c @@ -1053,18 +1053,33 @@ bool do_genkey(const struct options *options) { /* should we disable paging? */ - if (options->mlock && (options->genkey || options->tls_crypt_v2_genkey_file)) + if (options->mlock && (options->genkey)) { platform_mlockall(true); } - if (options->genkey) + if (options->genkey && options->genkey_type == GENKEY_SECRET) { int nbits_written; - notnull(options->shared_secret_file, - "shared secret output file (--secret)"); + if (options->shared_secret_file && options->genkey_filename) + { + msg(M_USAGE, "You must provide a filename to either --genkey or --secret, not both"); + } + + + const char* genkey_filename = options->genkey_filename; - nbits_written = write_key_file(2, options->shared_secret_file); + /* + * Copy filename from shared_secret_file to genkey_filenmae to support the old + * --genkey --secret foo.file syntax. The previous check ensure only one of them + * is defined. + */ + if (options->shared_secret_file) + { + genkey_filename = options->shared_secret_file; + } + + nbits_written = write_key_file(2, genkey_filename); if (nbits_written < 0) { msg(M_FATAL, "Failed to write key file"); @@ -1075,30 +1090,28 @@ do_genkey(const struct options *options) options->shared_secret_file); return true; } - if (options->tls_crypt_v2_genkey_type) + else if (options->genkey && options->genkey_type == GENKEY_TLS_CRYPTV2_SERVER) { - if (!strcmp(options->tls_crypt_v2_genkey_type, "server")) - { - tls_crypt_v2_write_server_key_file(options->tls_crypt_v2_genkey_file); - return true; - } - if (options->tls_crypt_v2_genkey_type - && !strcmp(options->tls_crypt_v2_genkey_type, "client")) + tls_crypt_v2_write_server_key_file(options->genkey_filename); + return true; + } + else if (options->genkey && options->genkey_type == GENKEY_TLS_CRYPTV2_CLIENT) + { + if (!options->tls_crypt_v2_file) { - if (!options->tls_crypt_v2_file) - { - msg(M_USAGE, "--tls-crypt-v2-genkey requires a server key to be set via --tls-crypt-v2 to create a client key"); - } - - 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; + msg(M_USAGE, + "--genkey tls-crypt-v2-client requires a server key to be set via --tls-crypt-v2 to create a client key"); } - msg(M_USAGE, "--tls-crypt-v2-genkey type should be \"client\" or \"server\""); + tls_crypt_v2_write_client_key_file(options->genkey_filename, + options->genkey_extra_data, options->tls_crypt_v2_file, + options->tls_crypt_v2_inline); + return true; + } + else + { + return false; } - return false; } /* diff --git a/src/openvpn/options.c b/src/openvpn/options.c index e34b65b1..ced5e987 100644 --- a/src/openvpn/options.c +++ b/src/openvpn/options.c @@ -632,9 +632,11 @@ static const char usage_message[] = " 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" + "--genkey tls-crypt-v2-client [keyfile] [base64 metadata]: Generate a\n" + " fresh tls-crypt-v2 client key, and store to\n" " keyfile. If supplied, include metadata in wrapped key.\n" + "--genkey tls-crypt-v2-server [keyfile] [base64 metadata]: Generate a\n" + " fresh tls-crypt-v2 server key, and store to keyfile\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" @@ -754,8 +756,9 @@ static const char usage_message[] = " to access TAP adapter.\n" #endif /* ifdef _WIN32 */ "\n" - "Generate a new key (for use with --secret, --tls-auth or --tls-crypt):\n" - "--genkey file : Generate a new random key and write to file.\n" + "Generate a new key :\n" + "--genkey secret file : Generate a new random key of type and write to file\n" + " (for use with --secret, --tls-auth or --tls-crypt)." #ifdef ENABLE_FEATURE_TUN_PERSIST "\n" "Tun/tap config mode (available with linux 2.4+):\n" @@ -1526,6 +1529,7 @@ show_settings(const struct options *o) SHOW_BOOL(show_digests); SHOW_BOOL(show_engines); SHOW_BOOL(genkey); + SHOW_STR(genkey_filename); SHOW_STR(key_pass_file); SHOW_BOOL(show_tls_ciphers); @@ -1746,8 +1750,6 @@ show_settings(const struct options *o) SHOW_BOOL(push_peer_info); SHOW_BOOL(tls_exit); - SHOW_STR(tls_crypt_v2_genkey_type); - SHOW_STR(tls_crypt_v2_genkey_file); SHOW_STR(tls_crypt_v2_metadata); #ifdef ENABLE_PKCS11 @@ -2689,10 +2691,6 @@ options_postprocess_verify_ce(const struct options *options, const struct connec { msg(M_USAGE, "--tls-crypt-v2, --tls-auth and --tls-crypt are mutually exclusive in client mode"); } - if (options->genkey && options->tls_crypt_v2_genkey_type) - { - msg(M_USAGE, "--genkey and --tls-crypt-v2-genkey are mutually exclusive"); - } } else { @@ -3320,8 +3318,8 @@ options_postprocess_filechecks(struct options *options) } errs |= check_file_access(CHKACC_FILE|CHKACC_INLINE|CHKACC_PRIVATE, - options->tls_crypt_v2_genkey_file, R_OK, - "--tls-crypt-v2-genkey"); + options->genkey_filename, R_OK, + "--genkey"); errs |= check_file_access(CHKACC_FILE|CHKACC_INLINE|CHKACC_PRIVATE, options->shared_secret_file, R_OK, "--secret"); @@ -7517,13 +7515,42 @@ add_option(struct options *options, } options->shared_secret_file = p[1]; } - else if (streq(p[0], "genkey") && !p[2]) + else if (streq(p[0], "genkey") && !p[4]) { VERIFY_PERMISSION(OPT_P_GENERAL); options->genkey = true; - if (p[1]) + if (!p[1]) + { + options->genkey_type = GENKEY_SECRET; + } + else + { + if (streq(p[1], "secret") || streq(p[1], "tls-auth") || + streq(p[1], "tls-crypt")) + { + options->genkey_type = GENKEY_SECRET; + } + else if (streq(p[1], "tls-crypt-v2-server")) + { + options->genkey_type = GENKEY_TLS_CRYPTV2_SERVER; + } + else if (streq(p[1], "tls-crypt-v2-client")) + { + options->genkey_type = GENKEY_TLS_CRYPTV2_CLIENT; + if (p[3]) + { + options->genkey_extra_data = p[3]; + } + } + else + { + msg(msglevel, "unknown --genkey type: %s", p[1]); + } + + } + if (p[2]) { - options->shared_secret_file = p[1]; + options->genkey_filename = p[2]; } } else if (streq(p[0], "auth") && p[1] && !p[2]) @@ -8121,16 +8148,6 @@ add_option(struct options *options, options->ce.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], "tls-crypt-v2-verify") && p[1] && !p[2]) { VERIFY_PERMISSION(OPT_P_GENERAL); diff --git a/src/openvpn/options.h b/src/openvpn/options.h index e2b38939..d8e7efa4 100644 --- a/src/openvpn/options.h +++ b/src/openvpn/options.h @@ -177,6 +177,12 @@ struct remote_host_store char port[RH_PORT_LEN]; }; +enum genkey_type { + GENKEY_SECRET, + GENKEY_TLS_CRYPTV2_CLIENT, + GENKEY_TLS_CRYPTV2_SERVER, +}; + /* Command line options */ struct options { @@ -207,6 +213,9 @@ struct options bool show_tls_ciphers; bool show_curves; bool genkey; + enum genkey_type genkey_type; + const char* genkey_filename; + const char* genkey_extra_data; /* Networking parms */ int connect_retry_max; @@ -589,8 +598,6 @@ struct options 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; const char *tls_crypt_v2_verify_script; From patchwork Fri May 10 02:11:10 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Arne Schwabe X-Patchwork-Id: 733 X-Patchwork-Delegate: davids@openvpn.net Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director11.mail.ord1d.rsapps.net ([172.31.255.6]) by backend30.mail.ord1d.rsapps.net with LMTP id EJCRGTJv1VywZgAAIUCqbw for ; Fri, 10 May 2019 08:31:46 -0400 Received: from proxy20.mail.iad3b.rsapps.net ([172.31.255.6]) by director11.mail.ord1d.rsapps.net with LMTP id 2ESsGDJv1VydWQAAvGGmqA ; Fri, 10 May 2019 08:31:46 -0400 Received: from smtp14.gate.iad3b ([172.31.255.6]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy20.mail.iad3b.rsapps.net with LMTP id AKYlETJv1VzEMQAAcDxLoQ ; Fri, 10 May 2019 08:31:46 -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: smtp14.gate.iad3b.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; dmarc=none (p=nil; dis=none) header.from=rfc2549.org X-Suspicious-Flag: YES X-Classification-ID: 92ab135e-731f-11e9-9658-52540057873d-1-1 Received: from [216.105.38.7] ([216.105.38.7:58674] helo=lists.sourceforge.net) by smtp14.gate.iad3b.rsapps.net (envelope-from ) (ecelerity 4.2.38.62370 r(:)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id E0/40-08826-13F65DC5; Fri, 10 May 2019 08:31:46 -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 1hP4g2-0001iX-2e; Fri, 10 May 2019 12:31:02 +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 1hP4g0-0001i0-9c for openvpn-devel@lists.sourceforge.net; Fri, 10 May 2019 12:31:00 +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=w+TDuAXefIA6kx05qvz7lJIobj1bUxbn+y7QV8uee7c=; b=IrN8PkwFT0swN791jUGWdb0iFM w8fDDO66xp4OdETTdkUMfyUrj6TmMtWeiEfvWhm7m6YgU8BipGRHpSUBdxRSv85lBOQtxSVc5vhrP 9bFD1zooFFc0D8Kk0+v1p+oq92o6L0ZiKuP8/pNrNqYDHNps9UMYEgWTU7bQe0nZhLYY=; 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=w+TDuAXefIA6kx05qvz7lJIobj1bUxbn+y7QV8uee7c=; b=LHDGbiYJB5e3NbqhXaU6b6vI71 QOLObgC6SWEadTx/pIqp8J/Hyi8IJWbekQYzh+QEV6dQFeI8KzHoiGLIJMa3yuIfqNBA8mh9FkYRV GtWwgxMjiTJAIUANP8wcEaMn+ulXB3jHULC1DAiaJyZJ1P++FiCKyeP5Qi/thN37SzAY=; Received: from mail.blinkt.de ([192.26.174.232]) by sfi-mx-3.v28.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.90_1) id 1hP4fz-00BysK-6V for openvpn-devel@lists.sourceforge.net; Fri, 10 May 2019 12:31:00 +0000 Received: from kamera.blinkt.de ([2001:638:502:390:20c:29ff:fec8:535c]) by mail.blinkt.de with smtp (Exim 4.91 (FreeBSD)) (envelope-from ) id 1hP4Mt-000B4s-5h; Fri, 10 May 2019 14:11:15 +0200 Received: (nullmailer pid 30522 invoked by uid 10006); Fri, 10 May 2019 12:11:15 -0000 From: Arne Schwabe To: openvpn-devel@lists.sourceforge.net Date: Fri, 10 May 2019 14:11:10 +0200 Message-Id: <20190510121114.30468-4-arne@rfc2549.org> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20190510121114.30468-1-arne@rfc2549.org> References: <20190510121114.30468-1-arne@rfc2549.org> X-Spam-Report: Spam Filtering performed by mx.sourceforge.net. See http://spamassassin.org/tag/ for more details. 0.0 URIBL_BLOCKED ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [URIs: openvpn.net] 0.1 HEADER_FROM_DIFFERENT_DOMAINS From and EnvelopeFrom 2nd level mail domains are different 0.1 AWL AWL: Adjusted score from AWL reputation of From: address X-Headers-End: 1hP4fz-00BysK-6V Subject: [Openvpn-devel] [PATCH v3 3/7] Add pem_read_key_file variant that allows a random 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: , Cc: Arne Schwabe MIME-Version: 1.0 Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox From: Arne Schwabe This is useful for features that can use enither a persistent or an ephemeral key. Patch V2: Move the functionality of generating a random key into a separate function that acts as wrapper for pem_read_key_file --- src/openvpn/crypto.c | 22 ++++++++++++++++++++++ src/openvpn/crypto.h | 15 +++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/src/openvpn/crypto.c b/src/openvpn/crypto.c index eb56421b..0e17c351 100644 --- a/src/openvpn/crypto.c +++ b/src/openvpn/crypto.c @@ -1892,6 +1892,28 @@ cleanup: return; } +bool +load_pem_file_or_ephemeral_key(struct buffer *key, const char *pem_name, + const char *key_file, const char *key_inline) +{ + if (key_file == NULL) + { + msg(M_INFO, "Using random %s.", pem_name); + uint8_t rand[BCAP(key)]; + if (!rand_bytes(rand, BCAP(key))) + { + msg(M_WARN, "ERROR: could not generate random key"); + return false; + } + buf_write(key, rand, BCAP(key)); + return true; + } + else + { + return read_pem_key_file(key, pem_name, key_file, key_inline); + } +} + bool read_pem_key_file(struct buffer *key, const char *pem_name, const char *key_file, const char *key_inline) diff --git a/src/openvpn/crypto.h b/src/openvpn/crypto.h index c5947483..4ae02571 100644 --- a/src/openvpn/crypto.h +++ b/src/openvpn/crypto.h @@ -430,6 +430,21 @@ unsigned int crypto_max_overhead(void); void write_pem_key_file(const char *filename, const char *pem_name); +/** + * Read key material from a PEM encoded files into the key structure or if + * the filename is not passed generate a ephemeral key + * + * @param key the key structure that will hold the key material + * @param pem_name the name used in the pem encoding start/end lines + * @param key_file name of the file to read or NULL if an ephermal key + * should be generated + * @param key_inline a string holding the data in case of an inline key + * @return true if reading into key was successful or generated + */ +bool +load_pem_file_or_ephemeral_key(struct buffer *key, const char *pem_name, + const char *key_file, const char *key_inline); + /** * Read key material from a PEM encoded files into the key structure * @param key the key structure that will hold the key material From patchwork Fri May 10 02:11:11 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Arne Schwabe X-Patchwork-Id: 734 X-Patchwork-Delegate: davids@openvpn.net Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director11.mail.ord1d.rsapps.net ([172.31.255.6]) by backend30.mail.ord1d.rsapps.net with LMTP id qOVlKkRv1VxvSAAAIUCqbw for ; Fri, 10 May 2019 08:32:04 -0400 Received: from proxy16.mail.iad3b.rsapps.net ([172.31.255.6]) by director11.mail.ord1d.rsapps.net with LMTP id KELkKURv1VwAUgAAvGGmqA ; Fri, 10 May 2019 08:32:04 -0400 Received: from smtp35.gate.iad3b ([172.31.255.6]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy16.mail.iad3b.rsapps.net with LMTP id wK7cIkRv1VzUegAAPj+4aA ; Fri, 10 May 2019 08:32:04 -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: smtp35.gate.iad3b.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; dmarc=none (p=nil; dis=none) header.from=rfc2549.org X-Suspicious-Flag: YES X-Classification-ID: 9d3dd0cc-731f-11e9-aaa6-525400503131-1-1 Received: from [216.105.38.7] ([216.105.38.7:39536] helo=lists.sourceforge.net) by smtp35.gate.iad3b.rsapps.net (envelope-from ) (ecelerity 4.2.38.62370 r(:)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id A7/D3-30699-34F65DC5; Fri, 10 May 2019 08:32:03 -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 1hP4gB-0005y8-KD; Fri, 10 May 2019 12:31:11 +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 1hP4g9-0005xu-UZ for openvpn-devel@lists.sourceforge.net; Fri, 10 May 2019 12:31:09 +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:To: From:Sender:Reply-To:Cc: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=JLRd6kxqrJaeIs4plBJvUFdA00v0xVhbmjHw7DR+VZY=; b=k6XRU0NWBf/M5jOSM2k3uGmU18 cMaOBT3ZipOsmwVWFtXrhMjEzhDUlczJE2JH8QDJUI/UG6id9jAZ6ypCFAztM7C8WZJ1HQTdhg8C+ cIzIT/9XrNwKbWLDNPUevakFGdmRJr26YvKvZhxLUgDNpOZTMWSM9Guv7HCvewozGesc=; 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:To:From:Sender:Reply-To:Cc :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=JLRd6kxqrJaeIs4plBJvUFdA00v0xVhbmjHw7DR+VZY=; b=Cx2pEvUq5a9+QTBrFpX7bmNBCU ExXOdQ3Js1Ac/5r40ZY+Ji3QSRnGK9rNpw5ci4RVfOXdjFHCayOmkg4PwL74wsPQ9hIBccuD5sd2f mnqt0V3iNMQ6S4w4Ly/kVmUiiltJ1YgecFRSqO5jJaYw1djE9DlLVA8BT1mAzuBI1jh0=; Received: from mail.blinkt.de ([192.26.174.232]) by sfi-mx-1.v28.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.90_1) id 1hP4g7-00FeFA-0p for openvpn-devel@lists.sourceforge.net; Fri, 10 May 2019 12:31:09 +0000 Received: from kamera.blinkt.de ([2001:638:502:390:20c:29ff:fec8:535c]) by mail.blinkt.de with smtp (Exim 4.91 (FreeBSD)) (envelope-from ) id 1hP4Mt-000B4v-7j for openvpn-devel@lists.sourceforge.net; Fri, 10 May 2019 14:11:15 +0200 Received: (nullmailer pid 30525 invoked by uid 10006); Fri, 10 May 2019 12:11:15 -0000 From: Arne Schwabe To: openvpn-devel@lists.sourceforge.net Date: Fri, 10 May 2019 14:11:11 +0200 Message-Id: <20190510121114.30468-5-arne@rfc2549.org> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20190510121114.30468-1-arne@rfc2549.org> References: <20190510121114.30468-1-arne@rfc2549.org> X-Spam-Report: Spam Filtering performed by mx.sourceforge.net. See http://spamassassin.org/tag/ for more details. 0.0 URIBL_BLOCKED ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [URIs: makefile.am] 0.1 HEADER_FROM_DIFFERENT_DOMAINS From and EnvelopeFrom 2nd level mail domains are different 0.1 AWL AWL: Adjusted score from AWL reputation of From: address X-Headers-End: 1hP4g7-00FeFA-0p Subject: [Openvpn-devel] [PATCH v3 4/7] Rewrite auth-token-gen to be based on HMAC based tokens 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 The previous auth-token implementation had a serious problem, especially when paired with an unpatched OpenVPN client that keeps trying the auth-token (commit e61b401a). The auth-token-gen implementation forgot the auth-token on reconnect, this lead to reconnect with auth-token never working. This new implementation implements the auth-token in a stateles variant. By using HMAC to sign the auth-token the server can verify if a token has been authenticated and by checking the embedded timestamp in the token it can also verify that the auth-token is still valid. Using the new config directive auth-gen-token-secret instead of extending auth-gen-token (--auth-gen-token [lifetime] [secret-key]) was chosen to allow inlinening the secret key. Patch V2: cleaned up code, use refactored read_pem_key_file function Patch V3: clarify some design decision in the commit message --- doc/openvpn.8 | 25 ++++ src/openvpn/Makefile.am | 1 + src/openvpn/auth_token.c | 261 +++++++++++++++++++++++++++++++++++++++ src/openvpn/auth_token.h | 116 +++++++++++++++++ src/openvpn/init.c | 28 ++++- src/openvpn/openvpn.h | 1 + src/openvpn/options.c | 22 +++- src/openvpn/options.h | 4 + src/openvpn/push.c | 70 +++++++++-- src/openvpn/push.h | 8 ++ src/openvpn/ssl.c | 7 +- src/openvpn/ssl_common.h | 36 +++--- src/openvpn/ssl_verify.c | 182 ++++++++++++--------------- 13 files changed, 626 insertions(+), 135 deletions(-) create mode 100644 src/openvpn/auth_token.c create mode 100644 src/openvpn/auth_token.h diff --git a/doc/openvpn.8 b/doc/openvpn.8 index 90a6be91..0ae617a3 100644 --- a/doc/openvpn.8 +++ b/doc/openvpn.8 @@ -3720,6 +3720,9 @@ the token authentication internally and it will NOT do any additional authentications against configured external user/password authentication mechanisms. +The tokens implemented by this mechanism include a initial timestamp +and a renew timestamp and are secured by HMAC. + The .B lifetime argument defines how long the generated token is valid. The @@ -3730,6 +3733,16 @@ This feature is useful for environments which is configured to use One Time Passwords (OTP) as part of the user/password authentications and that authentication mechanism does not implement any auth\-token support. +.\"********************************************************* +.TP +.B \-\-auth\-gen\-token\-secret [file] +Specifies a file that hold a secret for the HMAC used in +.B \-\-auth\-gen\-token +If not present OpenVPN will generate a random secret on startup. This file +should be used if auth-token should valid after restarting a server or if +client should be able to roam between multiple OpenVPN server with their +auth\-token. + .\"********************************************************* .TP .B \-\-opt\-verify @@ -5769,6 +5782,17 @@ Servers can use .B \-\-tls\-crypt\-v2\-verify to specify a metadata verification command. .\"********************************************************* +.TP +.B \-\-genkey auth\-token [keyfile] +Generate a new secret that can be used +with +.B \-\-auth\-gen\-token\-secret + +.B Note: +this file should be kept secret to the server as anyone +that access to this file will be to generate auth tokens +that the OpenVPN server will accept as valid. +.\"********************************************************* .SS TUN/TAP persistent tunnel config mode: Available with Linux 2.4.7+. These options comprise a standalone mode of OpenVPN which can be used to create and delete persistent tunnels. @@ -6980,6 +7004,7 @@ X509_1_C=KG OpenVPN allows including files in the main configuration for the .B \-\-ca, \-\-cert, \-\-dh, \-\-extra\-certs, \-\-key, \-\-pkcs12, \-\-secret, .B \-\-crl\-verify, \-\-http\-proxy\-user\-pass, \-\-tls\-auth, +.B \-\-auth\-gen\-token\-secret .B \-\-tls\-crypt, and .B \-\-tls\-crypt-v2 diff --git a/src/openvpn/Makefile.am b/src/openvpn/Makefile.am index 197e62ba..78f94762 100644 --- a/src/openvpn/Makefile.am +++ b/src/openvpn/Makefile.am @@ -39,6 +39,7 @@ sbin_PROGRAMS = openvpn openvpn_SOURCES = \ argv.c argv.h \ + auth_token.c auth_token.h \ base64.c base64.h \ basic.h \ buffer.c buffer.h \ diff --git a/src/openvpn/auth_token.c b/src/openvpn/auth_token.c new file mode 100644 index 00000000..dc8d68f6 --- /dev/null +++ b/src/openvpn/auth_token.c @@ -0,0 +1,261 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#elif defined(_MSC_VER) +#include "config-msvc.h" +#endif + +#include "syshead.h" + +#include "base64.h" +#include "buffer.h" +#include "crypto.h" +#include "openvpn.h" +#include "ssl_common.h" +#include "auth_token.h" +#include "push.h" +#include "integer.h" +#include "ssl.h" + +const char *auth_token_pem_name = "OpenVPN auth-token server key"; + + +/* Size of the data of the token (not b64 encoded and without prefix) */ +#define TOKEN_DATA_LEN (2 * sizeof(int64_t) + 32) + +static struct key_type +auth_token_kt(void) +{ + struct key_type kt; + /* We do not encrypt our session tokens */ + kt.cipher = NULL; + kt.digest = md_kt_get("SHA256"); + + if (!kt.digest) + { + msg(M_WARN, "ERROR: --tls-crypt requires HMAC-SHA-256 support."); + return (struct key_type) { 0 }; + } + + kt.hmac_length = md_kt_size(kt.digest); + + return kt; +} + + +void +auth_token_write_server_key_file(const char *filename) +{ + write_pem_key_file(filename, auth_token_pem_name); +} + +void +auth_token_init_secret(struct key_ctx *key_ctx, const char *key_file, + const char *key_inline) +{ + struct key_type kt = auth_token_kt(); + + struct buffer server_secret_key = alloc_buf(2048); + + if (!load_pem_file_or_ephemeral_key(&server_secret_key, + auth_token_pem_name, key_file, + key_inline)) + { + msg(M_FATAL, "ERROR: Cannot load auth-token secret"); + } + + struct key key; + + if (!buf_read(&server_secret_key, &key, sizeof(key))) + { + msg(M_FATAL, "ERROR: not enough data in auth-token secret"); + } + init_key_ctx(key_ctx, &key, &kt, false, "auth-token secret"); + + free_buf(&server_secret_key); +} + +void +generate_auth_token(const struct user_pass *up, struct tls_multi *multi) +{ + struct gc_arena gc = gc_new(); + + int64_t timestamp = htonll((uint64_t)now); + int64_t initial_timestamp = timestamp; + + hmac_ctx_t *ctx = multi->opt.auth_token_key.hmac; + ASSERT(hmac_ctx_size(ctx) == 256/8); + + if (multi->auth_token) + { + /* Just enough space to fit 8 bytes+ 1 extra to decode a non padded + * base64 string (multiple of 3 bytes). 9 bytes => 12 bytes base64 + * bytes + */ + char old_tstamp_decode[9]; + + /* + * reuse the same session id and timestamp and null terminate it at + * for base64 decode it only decodes the session id part of it + */ + char *old_tsamp_initial = multi->auth_token + strlen(SESSION_ID_PREFIX); + + old_tsamp_initial[12] = '\0'; + ASSERT(openvpn_base64_decode(old_tsamp_initial, old_tstamp_decode, 9) == 9); + initial_timestamp = *((uint64_t *)(old_tstamp_decode)); + + /* free the auth-token, we will replace it with a new one */ + free(multi->auth_token); + } + uint8_t hmac_output[256/8]; + + hmac_ctx_reset(ctx); + hmac_ctx_update(ctx, (uint8_t *) up->username, (int)strlen(up->username)); + hmac_ctx_update(ctx, (uint8_t *) &initial_timestamp, sizeof(initial_timestamp)); + hmac_ctx_update(ctx, (uint8_t *) ×tamp, sizeof(timestamp)); + hmac_ctx_final(ctx, hmac_output); + + /* Construct the unencoded session token */ + struct buffer token = alloc_buf_gc( + 2*sizeof(uint64_t) + 256/8, &gc); + + ASSERT(buf_write(&token, &initial_timestamp, sizeof(initial_timestamp))); + ASSERT(buf_write(&token, ×tamp, sizeof(timestamp))); + ASSERT(buf_write(&token, hmac_output, sizeof(hmac_output))); + + char *b64output; + openvpn_base64_encode(BPTR(&token), BLEN(&token), &b64output); + + struct buffer session_token = alloc_buf_gc( + strlen(SESSION_ID_PREFIX) + strlen(b64output) + 1, &gc); + + ASSERT(buf_write(&session_token, SESSION_ID_PREFIX, strlen(SESSION_ID_PREFIX))); + ASSERT(buf_write(&session_token, b64output, (int)strlen(b64output))); + ASSERT(buf_write_u8(&session_token, 0)); + + free(b64output); + + multi->auth_token = strdup((char *)BPTR(&session_token)); + + dmsg(D_SHOW_KEYS, "Generated token for client: %s", + multi->auth_token); + + gc_free(&gc); +} + +static bool +check_hmac_token(hmac_ctx_t *ctx, const uint8_t *b64decoded, const char *username) +{ + ASSERT(hmac_ctx_size(ctx) == 256/8); + + uint8_t hmac_output[256/8]; + + hmac_ctx_reset(ctx); + hmac_ctx_update(ctx, (uint8_t *) username, (int)strlen(username)); + hmac_ctx_update(ctx, b64decoded, TOKEN_DATA_LEN - 256/8); + hmac_ctx_final(ctx, hmac_output); + + const uint8_t *hmac = b64decoded + TOKEN_DATA_LEN - 256/8; + return memcmp_constant_time(&hmac_output, hmac, 32) == 0; +} + +unsigned int +verify_auth_token(struct user_pass *up, struct tls_multi *multi, + struct tls_session *session) +{ + /* + * Base64 is <= input and input is < USER_PASS_LEN, so using USER_PASS_LEN + * is safe here but a bit overkill + */ + uint8_t b64decoded[USER_PASS_LEN]; + int decoded_len = openvpn_base64_decode(up->password + strlen(SESSION_ID_PREFIX), + b64decoded, USER_PASS_LEN); + /* Ensure that the decoded data is at least the size of the + * timestamp + hmac */ + + if (decoded_len != TOKEN_DATA_LEN) + { + msg(M_WARN, "ERROR: --auth-token wrong size (%d!=%d)", + decoded_len, (int) TOKEN_DATA_LEN); + return 0; + } + + unsigned int ret = 0; + + + const uint8_t *tstamp_initial = b64decoded; + const uint8_t *tstamp = tstamp_initial + sizeof(int64_t); + + uint64_t timestamp = ntohll(*((uint64_t *) (tstamp))); + uint64_t timestamp_initial = ntohll(*((uint64_t *) (tstamp_initial))); + + hmac_ctx_t *ctx = multi->opt.auth_token_key.hmac; + if (check_hmac_token(ctx, b64decoded, up->username)) + { + ret |= AUTH_TOKEN_HMAC_OK; + } + else + { + msg(M_WARN, "--auth-token-gen: HMAC on token from client failed (%s)", + up->username); + return 0; + } + + /* Accept session tokens that not expired are in the acceptable range + * for renogiations */ + bool in_renog_time = now >= timestamp + && now < timestamp + 2 * session->opt->renegotiate_seconds; + + /* We could still have a client that does not update + * its auth-token, so also allow the initial auth-token */ + bool initialtoken = multi->auth_token_initial + && memcmp_constant_time(up->password, multi->auth_token_initial, + strlen(multi->auth_token_initial)) == 0; + + if (!in_renog_time && !initialtoken) + { + ret |= AUTH_TOKEN_EXPIRED; + } + + /* Sanity check the initial timestamp */ + if (timestamp < timestamp_initial) + { + msg(M_WARN, "Initial timestamp (%lld) in token from client earlier than " + "current timestamp (%lld). Broken/unsynchronised clock?", + timestamp_initial, timestamp); + ret |= AUTH_TOKEN_EXPIRED; + } + + if (multi->opt.auth_token_lifetime + && now > timestamp_initial + multi->opt.auth_token_lifetime) + { + ret |= AUTH_TOKEN_EXPIRED; + } + + if (ret & AUTH_TOKEN_EXPIRED) + { + msg(M_INFO, "--auth-token-gen: auth-token from client expired"); + } + + return ret; +} + +void +wipe_auth_token(struct tls_multi *multi) +{ + if (multi) + { + if (multi->auth_token) + { + secure_memzero(multi->auth_token, strlen(multi->auth_token)); + free(multi->auth_token); + } + if (multi->auth_token_initial) + { + secure_memzero(multi->auth_token_initial, + strlen(multi->auth_token_initial)); + free(multi->auth_token_initial); + } + multi->auth_token = NULL; + multi->auth_token_initial = NULL; + } +} diff --git a/src/openvpn/auth_token.h b/src/openvpn/auth_token.h new file mode 100644 index 00000000..600ac29f --- /dev/null +++ b/src/openvpn/auth_token.h @@ -0,0 +1,116 @@ +/* + * 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-2018 OpenVPN 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 AUTH_TOKEN_H +#define AUTH_TOKEN_H + +/** + * Generate an auth token based on username and timestamp + * + * The idea of auth token is to be stateless, so that we can verify use it + * even after we have forgotten about it or server has been restarted. + * + * To achieve this even though we cannot trust the client we use HMAC + * to be able to verify the information. + * + * Format of the auth-token (before base64 encode) + * + * uint64 timestamp (4 bytes)|uint64 timestamp (4 bytes)|sha256-hmac(32 bytes) + * + * The first timestamp is the time the token was initially created and is used to + * determine the maximum renewable time of the token. We always include this even + * if tokens do not expire (this value is not used) to keep the code cleaner. + * + * The second timestamp is the time the token was renewed/regenerated and is used + * to determine if this token has been renewed in the acceptable time range + * (2 * renogiation timeout) + * + * The hmac is calculated over the username contactinated with the + * raw auth-token bytes to include authentication of the username in the token + * + * we prepend the session id with SESS_ID_ before sending it to the client + */ +void +generate_auth_token(const struct user_pass *up, struct tls_multi *multi); + +/** + * Verifies the auth token to be in the format that generate_auth_token + * create and checks if the token is valid. + * + * Also calls generate_auth_token to update the auth-token to extend + * its validity + */ +unsigned +verify_auth_token(struct user_pass *up, struct tls_multi *multi, + struct tls_session *session); + + + +/** + * Loads an HMAC secret from a file or if no file is present generates a + * epheremal secret for the run time of the server and stores it into ctx + */ +void +auth_token_init_secret(struct key_ctx *key_ctx, const char *key_file, + const char *key_inline); + + +/** + * Generate a auth-token server secret key, and write to file. + * + * @param filename Filename of the server key file to create. + */ +void auth_token_write_server_key_file(const char *filename); + + +/** + * Wipes the authentication token out of the memory, frees and cleans up + * related buffers and flags + * + * @param multi Pointer to a multi object holding the auth_token variables + */ +void wipe_auth_token(struct tls_multi *multi); + +/** + * The prefix given to auth tokens start with, this prefix is special + * cased to not show up in log files in OpenVPN 2 and 3 + * + * We also prefix this with _AT_ to only act on auth token generated by us. + */ +#define SESSION_ID_PREFIX "SESS_ID_AT_" + +/** + * Return if the password string has the format of a password. + * + * This fuction will always read as many bytes as SESSION_ID_PREFIX is longer + * the caller needs ensure that password memory is at least that long (true for + * calling with struct user_pass) + * @param password + * @return whether the password string starts with the session token prefix + */ +static inline bool +is_auth_token(const char *password) +{ + return (memcmp_constant_time(SESSION_ID_PREFIX, password, + strlen(SESSION_ID_PREFIX)) == 0); +} +#endif /* AUTH_TOKEN_H */ diff --git a/src/openvpn/init.c b/src/openvpn/init.c index 9260067f..1732312f 100644 --- a/src/openvpn/init.c +++ b/src/openvpn/init.c @@ -51,6 +51,7 @@ #include "ssl_verify.h" #include "tls_crypt.h" #include "forward.h" +#include "auth_token.h" #include "memdbg.h" @@ -1108,6 +1109,11 @@ do_genkey(const struct options *options) options->tls_crypt_v2_inline); return true; } + else if (options->genkey && options->genkey_type == GENKEY_AUTH_TOKEN) + { + auth_token_write_server_key_file(options->genkey_filename); + return true; + } else { return false; @@ -2503,7 +2509,6 @@ init_crypto_pre(struct context *c, const unsigned int flags) rand_ctx_enable_prediction_resistance(); } #endif - } /* @@ -2627,6 +2632,20 @@ do_init_tls_wrap_key(struct context *c) } +/* + * Initialise the auth-token key context + */ +static void +do_init_auth_token_key(struct context *c) +{ + if (!c->options.auth_token_generate) + return; + + auth_token_init_secret(&c->c1.ks.auth_token_key, + c->options.auth_token_secret_file, + c->options.auth_token_secret_file_inline); +} + /* * Initialize the persistent component of OpenVPN's TLS mode, * which is preserved across SIGUSR1 resets. @@ -2679,6 +2698,9 @@ do_init_crypto_tls_c1(struct context *c) /* initialize tls-auth/crypt/crypt-v2 key */ do_init_tls_wrap_key(c); + /* initialise auth-token crypto support */ + do_init_auth_token_key(c); + #if 0 /* was: #if ENABLE_INLINE_FILES -- Note that enabling this code will break restarts */ if (options->priv_key_file_inline) { @@ -2851,6 +2873,7 @@ do_init_crypto_tls(struct context *c, const unsigned int flags) to.auth_user_pass_file = options->auth_user_pass_file; to.auth_token_generate = options->auth_token_generate; to.auth_token_lifetime = options->auth_token_lifetime; + to.auth_token_key = c->c1.ks.auth_token_key; #endif to.x509_track = options->x509_track; @@ -4464,6 +4487,9 @@ inherit_context_child(struct context *dest, dest->c1.authname = src->c1.authname; dest->c1.keysize = src->c1.keysize; + /* inherit auth-token */ + dest->c1.ks.auth_token_key = src->c1.ks.auth_token_key; + /* options */ dest->options = src->options; options_detach(&dest->options); diff --git a/src/openvpn/openvpn.h b/src/openvpn/openvpn.h index d11f61df..30b5aeb4 100644 --- a/src/openvpn/openvpn.h +++ b/src/openvpn/openvpn.h @@ -68,6 +68,7 @@ struct key_schedule struct key_ctx_bi tls_wrap_key; struct key_ctx tls_crypt_v2_server_key; struct buffer tls_crypt_v2_wkc; /**< Wrapped client key */ + struct key_ctx auth_token_key; }; /* diff --git a/src/openvpn/options.c b/src/openvpn/options.c index ced5e987..3dac1e7a 100644 --- a/src/openvpn/options.c +++ b/src/openvpn/options.c @@ -1289,6 +1289,7 @@ show_p2mp_parms(const struct options *o) SHOW_BOOL(auth_user_pass_verify_script_via_file); SHOW_BOOL(auth_token_generate); SHOW_INT(auth_token_lifetime); + SHOW_STR(auth_token_secret_file); #if PORT_SHARE SHOW_STR(port_share_host); SHOW_STR(port_share_port); @@ -2336,7 +2337,11 @@ options_postprocess_verify_ce(const struct options *options, const struct connec { msg(M_USAGE, "--mode server requires --key-method 2"); } - + if (options->auth_token_generate && !options->renegotiate_seconds) + { + msg(M_USAGE, "--auth-gen-token needs a non-infinite " + "--renegotiate_seconds setting"); + } { const bool ccnr = (options->auth_user_pass_verify_script || PLUGIN_OPTION_LIST(options) @@ -6767,6 +6772,17 @@ add_option(struct options *options, options->auth_token_generate = true; options->auth_token_lifetime = p[1] ? positive_atoi(p[1]) : 0; } + else if (streq(p[0], "auth-gen-token-secret") && p[1] && (!p[2] + || (p[2] && streq(p[1], INLINE_FILE_TAG)))) + { + VERIFY_PERMISSION(OPT_P_GENERAL); + options->auth_token_secret_file = p[1]; + + if (streq(p[1], INLINE_FILE_TAG) && p[2]) + { + options->auth_token_secret_file_inline = p[2]; + } + } else if (streq(p[0], "client-connect") && p[1]) { VERIFY_PERMISSION(OPT_P_SCRIPT); @@ -7542,6 +7558,10 @@ add_option(struct options *options, options->genkey_extra_data = p[3]; } } + else if (streq(p[1], "auth-token")) + { + options->genkey_type = GENKEY_AUTH_TOKEN; + } else { msg(msglevel, "unknown --genkey type: %s", p[1]); diff --git a/src/openvpn/options.h b/src/openvpn/options.h index d8e7efa4..cad5be81 100644 --- a/src/openvpn/options.h +++ b/src/openvpn/options.h @@ -181,6 +181,7 @@ enum genkey_type { GENKEY_SECRET, GENKEY_TLS_CRYPTV2_CLIENT, GENKEY_TLS_CRYPTV2_SERVER, + GENKEY_AUTH_TOKEN }; /* Command line options */ @@ -469,6 +470,9 @@ struct options bool auth_user_pass_verify_script_via_file; bool auth_token_generate; unsigned int auth_token_lifetime; + const char *auth_token_secret_file; + const char *auth_token_secret_file_inline; + #if PORT_SHARE char *port_share_host; char *port_share_port; diff --git a/src/openvpn/push.c b/src/openvpn/push.c index 8befc6f5..45ef0c4f 100644 --- a/src/openvpn/push.c +++ b/src/openvpn/push.c @@ -324,6 +324,37 @@ send_push_request(struct context *c) } #if P2MP_SERVER +/** + * Prepare push option for auth-token + * @param tls_multi tls multi context of VPN tunnel + * @param gc gc arena for allocating push options + * @param push_list push list to where options are added + * + * @return true on success, false on failure. + */ +void +prepare_auth_token_push_reply(struct tls_multi *tls_multi, struct gc_arena *gc, + struct push_list *push_list) +{ + /* + * If server uses --auth-gen-token and we have an auth token + * to send to the client + */ + if (tls_multi->auth_token) + { + push_option_fmt(gc, push_list, M_USAGE, + "auth-token %s", + tls_multi->auth_token); + if (!tls_multi->auth_token_initial) + { + /* + * Save the initial auth token for clients that ignore + * the updates to the token + */ + tls_multi->auth_token_initial = strdup(tls_multi->auth_token); + } + } +} /** * Prepare push options, based on local options and available peer info. @@ -334,7 +365,7 @@ send_push_request(struct context *c) * * @return true on success, false on failure. */ -static bool +bool prepare_push_reply(struct context *c, struct gc_arena *gc, struct push_list *push_list) { @@ -382,6 +413,11 @@ prepare_push_reply(struct context *c, struct gc_arena *gc, tls_multi->use_peer_id = true; } } + /* + * If server uses --auth-gen-token and we have an auth token + * to send to the client + */ + prepare_auth_token_push_reply(tls_multi, gc, push_list); /* Push cipher if client supports Negotiable Crypto Parameters */ if (tls_peer_info_ncp_ver(peer_info) >= 2 && o->ncp_enabled) @@ -412,15 +448,6 @@ prepare_push_reply(struct context *c, struct gc_arena *gc, tls_poor_mans_ncp(o, tls_multi->remote_ciphername); } - /* If server uses --auth-gen-token and we have an auth token - * to send to the client - */ - if (false == tls_multi->auth_token_sent && NULL != tls_multi->auth_token) - { - push_option_fmt(gc, push_list, M_USAGE, - "auth-token %s", tls_multi->auth_token); - tls_multi->auth_token_sent = true; - } return true; } @@ -431,6 +458,7 @@ send_push_options(struct context *c, struct buffer *buf, { struct push_entry *e = push_list->head; + e = push_list->head; while (e) { if (e->enable) @@ -463,7 +491,27 @@ send_push_options(struct context *c, struct buffer *buf, return true; } -static bool +void +send_push_reply_auth_token(struct tls_multi *multi) +{ + struct gc_arena gc = gc_new(); + + + struct push_list push_list = {}; + prepare_auth_token_push_reply(multi, &gc, &push_list); + + /* prepare auth token should always add the auth-token option */ + struct push_entry *e = push_list.head; + ASSERT(e && e->enable); + + /* Construct a mimimal control channel push reply message */ + struct buffer buf = alloc_buf_gc(PUSH_BUNDLE_SIZE, &gc); + buf_printf(&buf, "%s, %s", push_reply_cmd, e->option); + send_control_channel_string_dowork(multi, BSTR(&buf), D_PUSH); + gc_free(&gc); +} + +bool send_push_reply(struct context *c, struct push_list *per_client_push_list) { struct gc_arena gc = gc_new(); diff --git a/src/openvpn/push.h b/src/openvpn/push.h index 5f6181e7..070782dd 100644 --- a/src/openvpn/push.h +++ b/src/openvpn/push.h @@ -69,6 +69,14 @@ void send_auth_failed(struct context *c, const char *client_reason); void send_restart(struct context *c, const char *kill_msg); +/** + * Sends a push reply message only containin the auth-token to update + * the auth-token on the client + * + * @param multi - The tls_multi structure belonging to the instance to push to + */ +void send_push_reply_auth_token(struct tls_multi *multi); + #endif #endif /* if P2MP */ #endif /* ifndef PUSH_H */ diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c index 4c81ae72..69d7be57 100644 --- a/src/openvpn/ssl.c +++ b/src/openvpn/ssl.c @@ -59,6 +59,7 @@ #include "ssl.h" #include "ssl_verify.h" #include "ssl_backend.h" +#include "auth_token.h" #include "memdbg.h" @@ -1368,11 +1369,7 @@ tls_multi_free(struct tls_multi *multi, bool clear) cert_hash_free(multi->locked_cert_hash_set); - if (multi->auth_token) - { - secure_memzero(multi->auth_token, AUTH_TOKEN_SIZE); - free(multi->auth_token); - } + wipe_auth_token(multi); free(multi->remote_ciphername); diff --git a/src/openvpn/ssl_common.h b/src/openvpn/ssl_common.h index 410b2163..ff8ffacc 100644 --- a/src/openvpn/ssl_common.h +++ b/src/openvpn/ssl_common.h @@ -298,7 +298,6 @@ struct tls_options /** TLS handshake wrapping state */ struct tls_wrap_ctx tls_wrap; - /* frame parameters for TLS control channel */ struct frame frame; /* used for username/password authentication */ @@ -306,10 +305,16 @@ struct tls_options bool auth_user_pass_verify_script_via_file; const char *tmp_dir; const char *auth_user_pass_file; - bool auth_token_generate; /**< Generate auth-tokens on successful user/pass auth, - * set via options->auth_token_generate. */ + +#ifdef P2MP_SERVER + bool auth_token_generate; /**< Generate auth-tokens on successful + * user/pass auth,seet via + * options->auth_token_generate. */ unsigned int auth_token_lifetime; + struct key_ctx auth_token_key; +#endif + /* use the client-config-dir as a positive authenticator */ const char *client_config_dir_exclusive; @@ -368,10 +373,6 @@ struct tls_options /** @} name Index of key_state objects within a tls_session structure */ /** @} addtogroup control_processor */ -#define AUTH_TOKEN_SIZE 32 /**< Size of server side generated auth tokens. - * 32 bytes == 256 bits - */ - /** * Security parameter state of a single session within a VPN tunnel. * @ingroup control_processor @@ -539,7 +540,21 @@ struct tls_multi * over control channel. */ char *peer_info; + char *auth_token; /**< If server sends a generated auth-token, + * this is the token to use for future + * user/pass authentications in this session. + */ + char *auth_token_initial; + /**< The first auth-token we sent to a client, for clients that do + * not update their auth-token (older OpenVPN3 core versions) + */ +#define AUTH_TOKEN_HMAC_OK (1<<0) + /**< Auth-token sent from client has valid hmac */ +#define AUTH_TOKEN_EXPIRED (1<<1) + /**< Auth-token sent from client has expired */ #endif + int auth_token_state_flags; + /**< The state of the auth-token sent from the client last time */ /* For P_DATA_V2 */ uint32_t peer_id; @@ -547,13 +562,6 @@ struct tls_multi char *remote_ciphername; /**< cipher specified in peer's config file */ - char *auth_token; /**< If server sends a generated auth-token, - * this is the token to use for future - * user/pass authentications in this session. - */ - time_t auth_token_tstamp; /**< timestamp of the generated token */ - bool auth_token_sent; /**< If server uses --auth-gen-token and - * token has been sent to client */ /* * Our session objects. */ diff --git a/src/openvpn/ssl_verify.c b/src/openvpn/ssl_verify.c index a7f51751..b392b0ac 100644 --- a/src/openvpn/ssl_verify.c +++ b/src/openvpn/ssl_verify.c @@ -44,6 +44,8 @@ #ifdef ENABLE_CRYPTO_OPENSSL #include "ssl_verify_openssl.h" #endif +#include "auth_token.h" +#include "push.h" /** Maximum length of common name */ #define TLS_USERNAME_LEN 64 @@ -63,28 +65,6 @@ setenv_untrusted(struct tls_session *session) setenv_link_socket_actual(session->opt->es, "untrusted", &session->untrusted_addr, SA_IP_PORT); } - -/** - * Wipes the authentication token out of the memory, frees and cleans up related buffers and flags - * - * @param multi Pointer to a multi object holding the auth_token variables - */ -static void -wipe_auth_token(struct tls_multi *multi) -{ - if (multi) - { - if (multi->auth_token) - { - secure_memzero(multi->auth_token, AUTH_TOKEN_SIZE); - free(multi->auth_token); - } - multi->auth_token = NULL; - multi->auth_token_sent = false; - } -} - - /* * Remove authenticated state from all sessions in the given tunnel */ @@ -1253,6 +1233,7 @@ verify_user_pass_management(struct tls_session *session, const struct user_pass } #endif /* ifdef MANAGEMENT_DEF_AUTH */ + /* * Main username/password verification entry point */ @@ -1277,86 +1258,67 @@ verify_user_pass(struct user_pass *up, struct tls_multi *multi, string_mod_remap_name(up->username); string_mod(up->password, CC_PRINT, CC_CRLF, '_'); - /* If server is configured with --auth-gen-token and we have an - * authentication token for this client, this authentication + /* + * If auth token succeeds we skip the auth + * methods unless otherwise specified + */ + bool skip_auth = false; + + /* + * If server is configured with --auth-gen-token and the client sends + * something that looks like an authentication token, this * round will be done internally using the token instead of * calling any external authentication modules. */ - if (session->opt->auth_token_generate && multi->auth_token_sent - && NULL != multi->auth_token) + if (session->opt->auth_token_generate && is_auth_token(up->password)) { - unsigned int ssl_flags = session->opt->ssl_flags; - - /* Ensure that the username has not changed */ - if (!tls_lock_username(multi, up->username)) + multi->auth_token_state_flags = verify_auth_token(up, multi,session); + if (multi->auth_token_state_flags == AUTH_TOKEN_HMAC_OK) { - /* auth-token cleared in tls_lock_username() on failure */ - ks->authenticated = false; - return; + /* + * We do not want the EXPIRED flag here so check + * for equality with AUTH_TOKEN_HMAC_OK + */ + msg(M_WARN, "TLS: Username/auth-token authentication " + "succeeded for username '%s'", + up->username); + skip_auth = true; } - - /* If auth-token lifetime has been enabled, - * ensure the token has not expired - */ - if (session->opt->auth_token_lifetime > 0 - && (multi->auth_token_tstamp + session->opt->auth_token_lifetime) < now) + else { - msg(D_HANDSHAKE, "Auth-token for client expired\n"); wipe_auth_token(multi); ks->authenticated = false; + msg(M_WARN, "TLS: Username/auth-token authentication " + "failed for username '%s'", up->username); return; } + } + if (!skip_auth) + { - /* The core authentication of the token itself */ - if (memcmp_constant_time(multi->auth_token, up->password, - strlen(multi->auth_token)) != 0) + /* call plugin(s) and/or script */ +#ifdef MANAGEMENT_DEF_AUTH + if (man_def_auth == KMDA_DEF) { - ks->authenticated = false; - tls_deauthenticate(multi); - - msg(D_TLS_ERRORS, "TLS Auth Error: Auth-token verification " - "failed for username '%s' %s", up->username, - (ssl_flags & SSLF_USERNAME_AS_COMMON_NAME) ? "[CN SET]" : ""); + man_def_auth = verify_user_pass_management(session, up); } - else +#endif + if (plugin_defined(session->opt->plugins, OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY)) { - ks->authenticated = true; - - if (ssl_flags & SSLF_USERNAME_AS_COMMON_NAME) - { - set_common_name(session, up->username); - } - msg(D_HANDSHAKE, "TLS: Username/auth-token authentication " - "succeeded for username '%s' %s", - up->username, - (ssl_flags & SSLF_USERNAME_AS_COMMON_NAME) ? "[CN SET]" : ""); + s1 = verify_user_pass_plugin(session, up); + } + if (session->opt->auth_user_pass_verify_script) + { + s2 = verify_user_pass_script(session, up); } - return; - } - - /* call plugin(s) and/or script */ -#ifdef MANAGEMENT_DEF_AUTH - if (man_def_auth == KMDA_DEF) - { - man_def_auth = verify_user_pass_management(session, up); - } -#endif - if (plugin_defined(session->opt->plugins, OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY)) - { - s1 = verify_user_pass_plugin(session, up); - } - if (session->opt->auth_user_pass_verify_script) - { - s2 = verify_user_pass_script(session, up); - } - /* check sizing of username if it will become our common name */ - if ((session->opt->ssl_flags & SSLF_USERNAME_AS_COMMON_NAME) && strlen(up->username) > TLS_USERNAME_LEN) - { - msg(D_TLS_ERRORS, "TLS Auth Error: --username-as-common name specified and username is longer than the maximum permitted Common Name length of %d characters", TLS_USERNAME_LEN); - s1 = OPENVPN_PLUGIN_FUNC_ERROR; + /* check sizing of username if it will become our common name */ + if ((session->opt->ssl_flags & SSLF_USERNAME_AS_COMMON_NAME) && strlen(up->username) > TLS_USERNAME_LEN) + { + msg(D_TLS_ERRORS, "TLS Auth Error: --username-as-common name specified and username is longer than the maximum permitted Common Name length of %d characters", TLS_USERNAME_LEN); + s1 = OPENVPN_PLUGIN_FUNC_ERROR; + } } - /* auth succeeded? */ if ((s1 == OPENVPN_PLUGIN_FUNC_SUCCESS #ifdef PLUGIN_DEF_AUTH @@ -1381,35 +1343,49 @@ verify_user_pass(struct user_pass *up, struct tls_multi *multi, ks->auth_deferred = true; } #endif + if ((session->opt->ssl_flags & SSLF_USERNAME_AS_COMMON_NAME)) + { + set_common_name(session, up->username); + } - if ((session->opt->auth_token_generate) && (NULL == multi->auth_token)) + if ((session->opt->auth_token_generate)) { - /* Server is configured with --auth-gen-token but no token has yet - * been generated for this client. Generate one and save it. + /* + * If we accepted a (not expired) token, i.e. + * initial auth via token on new connection, we need + * to store the auth-token in multi->auth_token, so + * the initial timestamp and session id can be extracted from it */ - uint8_t tok[AUTH_TOKEN_SIZE]; - - if (!rand_bytes(tok, AUTH_TOKEN_SIZE)) + if (multi->auth_token && (multi->auth_token_state_flags & AUTH_TOKEN_HMAC_OK) + && !(multi->auth_token_state_flags & AUTH_TOKEN_EXPIRED)) { - msg( M_FATAL, "Failed to get enough randomness for " - "authentication token"); + multi->auth_token = strdup(up->password); } - /* The token should be longer than the input when - * being base64 encoded + /* + * Server is configured with --auth-gen-token but no token has yet + * been generated for this client. Generate one and save it. */ - ASSERT(openvpn_base64_encode(tok, AUTH_TOKEN_SIZE, - &multi->auth_token) > AUTH_TOKEN_SIZE); - multi->auth_token_tstamp = now; - dmsg(D_SHOW_KEYS, "Generated token for client: %s", - multi->auth_token); + generate_auth_token(up, multi); } - - if ((session->opt->ssl_flags & SSLF_USERNAME_AS_COMMON_NAME)) + /* + * Auth token already sent to client, update auth-token on client. + * The initial auth-token is sent as part of the push message, for this + * update we need to schedule an extra push message. + */ + if (multi->auth_token_initial) { - set_common_name(session, up->username); + /* + * We do not explicitly schedule the sending of the + * control message here but control message are only + * postponed when the control channel is not yet fully + * established and furthermore since this is called in + * the middle of authentication, there are other messages + * (new data channel keys) that are sent anyway and will + * trigger schedueling + */ + send_push_reply_auth_token(multi); } - #ifdef ENABLE_DEF_AUTH msg(D_HANDSHAKE, "TLS: Username/Password authentication %s for username '%s' %s", ks->auth_deferred ? "deferred" : "succeeded", From patchwork Fri May 10 02:11:12 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Arne Schwabe X-Patchwork-Id: 729 X-Patchwork-Delegate: davids@openvpn.net Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director10.mail.ord1d.rsapps.net ([172.31.255.6]) by backend30.mail.ord1d.rsapps.net with LMTP id KOiOEBZv1Vy7JAAAIUCqbw for ; Fri, 10 May 2019 08:31:18 -0400 Received: from proxy16.mail.iad3b.rsapps.net ([172.31.255.6]) by director10.mail.ord1d.rsapps.net with LMTP id MIToDRZv1VyWWwAApN4f7A ; Fri, 10 May 2019 08:31:18 -0400 Received: from smtp24.gate.iad3b ([172.31.255.6]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy16.mail.iad3b.rsapps.net with LMTP id wBbHBxZv1VzkegAAPj+4aA ; Fri, 10 May 2019 08:31:18 -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: smtp24.gate.iad3b.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; dmarc=none (p=nil; dis=none) header.from=rfc2549.org X-Suspicious-Flag: YES X-Classification-ID: 81c9429a-731f-11e9-85af-525400892b35-1-1 Received: from [216.105.38.7] ([216.105.38.7:30342] helo=lists.sourceforge.net) by smtp24.gate.iad3b.rsapps.net (envelope-from ) (ecelerity 4.2.38.62370 r(:)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id D2/3E-28739-51F65DC5; Fri, 10 May 2019 08:31:17 -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 1hP4fQ-00048H-Pq; Fri, 10 May 2019 12:30:24 +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 1hP4fO-00048A-TD for openvpn-devel@lists.sourceforge.net; Fri, 10 May 2019 12:30:22 +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=c3xQCwpli6zITe+kaqhoLzzuvfUDdbAq9Sp19zlPTP0=; b=jtGN+XqkmLMdlaGvFs9Qf5saMJ bNUA62pt7f7UvsE+ICtZn+BaGWqrLM9AT+K2zkbJYF4qER0jaKHIfC04lWcuX+w1QaCfue27b9+TO IdFPnB+XesexE2dAJYk7ICQUtzg/4UOav+lhQNZoGmTj2Bbeb/faZMiDk/EvIXMFITpU=; 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=c3xQCwpli6zITe+kaqhoLzzuvfUDdbAq9Sp19zlPTP0=; b=AaccB1u2VdQ29cF+mc+b236L5x rYUQ03ROy1dJhKOmvi6DNsoLrBm9gmT1BAiWEM2TOrrINT0Hb8ya0X4d0yZBllyTAE4+iibKfO/IX 9p8WrQzLmFvvVmgct0VnxAYwptImMRXnX5IzjaDf8WpzVKF9I5Tg0igPZijiVyLhguUk=; Received: from [192.26.174.232] (helo=mail.blinkt.de) by sfi-mx-1.v28.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.90_1) id 1hP4fK-00FeAh-Q7 for openvpn-devel@lists.sourceforge.net; Fri, 10 May 2019 12:30:22 +0000 Received: from kamera.blinkt.de ([2001:638:502:390:20c:29ff:fec8:535c]) by mail.blinkt.de with smtp (Exim 4.91 (FreeBSD)) (envelope-from ) id 1hP4Mt-000B4z-9v; Fri, 10 May 2019 14:11:15 +0200 Received: (nullmailer pid 30528 invoked by uid 10006); Fri, 10 May 2019 12:11:15 -0000 From: Arne Schwabe To: openvpn-devel@lists.sourceforge.net Date: Fri, 10 May 2019 14:11:12 +0200 Message-Id: <20190510121114.30468-6-arne@rfc2549.org> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20190510121114.30468-1-arne@rfc2549.org> References: <20190510121114.30468-1-arne@rfc2549.org> X-Spam-Report: Spam Filtering performed by mx.sourceforge.net. See http://spamassassin.org/tag/ for more details. 0.1 HEADER_FROM_DIFFERENT_DOMAINS From and EnvelopeFrom 2nd level mail domains are different 0.0 URIBL_BLOCKED ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [URIs: openvpn.net] 1.0 RDNS_NONE Delivered to internal network by a host with no rDNS X-Headers-End: 1hP4fK-00FeAh-Q7 Subject: [Openvpn-devel] [PATCH v3 5/7] Implement a permanent session id in auth-token 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: Arne Schwabe MIME-Version: 1.0 Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox From: Arne Schwabe This allows an external authentication method (e.g. management interface) to track the connection and distinguish a reconnection from multiple connections. Addtionally this now also checks to workaround a problem with OpenVPN 3 core that sometimes uses a username hint from the config instead of using an empty username if the token would be valid with an empty username. Accepting such token can be only done explicitly when the external-auth keyword to auth-gen-token is present. Patch V2: Add Empty variants to work around behaviour in openvpn 3 Patch V3: document the behaviour of external-auth better in the man page, rename 'auth' parameter to 'external-auth' --- doc/openvpn.8 | 37 +++++++++- src/openvpn/auth_token.c | 145 ++++++++++++++++++++++++++++++++++++--- src/openvpn/auth_token.h | 15 +++- src/openvpn/init.c | 1 + src/openvpn/manage.c | 4 +- src/openvpn/options.c | 14 +++- src/openvpn/options.h | 4 +- src/openvpn/ssl_common.h | 12 +++- src/openvpn/ssl_verify.c | 67 ++++++++++++------ 9 files changed, 260 insertions(+), 39 deletions(-) diff --git a/doc/openvpn.8 b/doc/openvpn.8 index 0ae617a3..8c16a272 100644 --- a/doc/openvpn.8 +++ b/doc/openvpn.8 @@ -3710,7 +3710,7 @@ For a sample script that performs PAM authentication, see in the OpenVPN source distribution. .\"********************************************************* .TP -.B \-\-auth\-gen\-token [lifetime] +.B \-\-auth\-gen\-token [lifetime] [auth] After successful user/password authentication, the OpenVPN server will with this option generate a temporary authentication token and push that to client. On the following @@ -3733,6 +3733,41 @@ This feature is useful for environments which is configured to use One Time Passwords (OTP) as part of the user/password authentications and that authentication mechanism does not implement any auth\-token support. + +When the external-auth keyword is present the normal authentication +method will always be called even if auth-token succeeds. Normally other +authentications method are skipped if auth-token verification suceeds or +fails. + +This option postpones this decision to the external authentication methods +and check the validity of the account and do other checks. + +In this mode the environment will have a session_id variable +that hold the session id from auth-gen-token. Also a environment +variable session_state is present. This variable tells whether the +auth-token has succeeded or not. It can have the following values: + + - Initial: No token from client. + - Authenticated: Token is valid and not expired + - Expired: Token is valid but has expired + - Invalid Token is invalid (failed HMAC or wrong length) + - AuthenticatedEmptyUser/ExpiredEmptyUser + The token is not valid with the username send from the client + but would be valid (or expired) if we assume an empty username was used + instead. These two cases are a workaround for behaviour in OpenVPN3. If + this workaround is not needed these two cases should be handled in the + same way as Invalid. + +.B Warning: +Use this feature only if you want your authentication method called on +every verification. Since the external authentication is called it needs +to also indicate a success or failure of the authentication. It is strongly +recommended to return an authentication failure in the case of the +Invalid/Expired auth-token with the external-auth option unless the client +could authenticate in another acceptable way (e.g. client certificate), +otherwise returning success will lead to authentication bypass (as does +returning success on a wrong password from a script). + .\"********************************************************* .TP .B \-\-auth\-gen\-token\-secret [file] diff --git a/src/openvpn/auth_token.c b/src/openvpn/auth_token.c index dc8d68f6..f28008d3 100644 --- a/src/openvpn/auth_token.c +++ b/src/openvpn/auth_token.c @@ -18,9 +18,13 @@ const char *auth_token_pem_name = "OpenVPN auth-token server key"; +#define AUTH_TOKEN_SESSION_ID_LEN 12 +#if AUTH_TOKEN_SESSION_ID_LEN % 3 +#error AUTH_TOKEN_SESSION_ID_LEN needs to be multiple a 3 +#endif /* Size of the data of the token (not b64 encoded and without prefix) */ -#define TOKEN_DATA_LEN (2 * sizeof(int64_t) + 32) +#define TOKEN_DATA_LEN (2 * sizeof(int64_t) + AUTH_TOKEN_SESSION_ID_LEN + 32) static struct key_type auth_token_kt(void) @@ -42,6 +46,85 @@ auth_token_kt(void) } +void +add_session_token_env(struct tls_session *session, struct tls_multi *multi, + const struct user_pass *up) +{ + if (!multi->opt.auth_token_generate) + { + return; + } + + + const char *state; + + if (!is_auth_token(up->password)) + { + state = "Initial"; + } + else if (multi->auth_token_state_flags & AUTH_TOKEN_HMAC_OK) + { + switch (multi->auth_token_state_flags & (AUTH_TOKEN_VALID_EMPTYUSER|AUTH_TOKEN_EXPIRED)) + { + case 0: + state = "Authenticated"; + break; + + case AUTH_TOKEN_EXPIRED: + state = "Expired"; + break; + + case AUTH_TOKEN_VALID_EMPTYUSER: + state = "AuthenticatedEmptyUser"; + break; + + case AUTH_TOKEN_VALID_EMPTYUSER | AUTH_TOKEN_EXPIRED: + state = "ExpiredEmptyUser"; + break; + + default: + /* Silence compiler warning, all four possible combinations are covered */ + ASSERT(0); + } + } + else + { + state = "Invalid"; + } + + setenv_str(session->opt->es, "session_state", state); + + /* We had a valid session id before */ + const char *session_id_source; + if (multi->auth_token_state_flags & AUTH_TOKEN_HMAC_OK + &!(multi->auth_token_state_flags & AUTH_TOKEN_EXPIRED)) + { + session_id_source = up->password; + } + else + { + /* + * No session before, generate a new session token for the new session + */ + if (!multi->auth_token) + { + generate_auth_token(up, multi); + } + session_id_source = multi->auth_token; + } + /* + * In the auth-token the auth token is already base64 encoded + * and being a multiple of 4 ensure that it a multiple of bytes + * in the encoding + */ + + char session_id[AUTH_TOKEN_SESSION_ID_LEN*2] = {0}; + memcpy(session_id, session_id_source + sizeof(SESSION_ID_PREFIX), + AUTH_TOKEN_SESSION_ID_LEN*8/6); + + setenv_str(session->opt->es, "session_id", session_id); +} + void auth_token_write_server_key_file(const char *filename) { @@ -85,6 +168,8 @@ generate_auth_token(const struct user_pass *up, struct tls_multi *multi) hmac_ctx_t *ctx = multi->opt.auth_token_key.hmac; ASSERT(hmac_ctx_size(ctx) == 256/8); + uint8_t sessid[AUTH_TOKEN_SESSION_ID_LEN]; + if (multi->auth_token) { /* Just enough space to fit 8 bytes+ 1 extra to decode a non padded @@ -97,27 +182,58 @@ generate_auth_token(const struct user_pass *up, struct tls_multi *multi) * reuse the same session id and timestamp and null terminate it at * for base64 decode it only decodes the session id part of it */ - char *old_tsamp_initial = multi->auth_token + strlen(SESSION_ID_PREFIX); + char *old_sessid = multi->auth_token + strlen(SESSION_ID_PREFIX); + char *old_tsamp_initial = old_sessid + AUTH_TOKEN_SESSION_ID_LEN*8/6; old_tsamp_initial[12] = '\0'; ASSERT(openvpn_base64_decode(old_tsamp_initial, old_tstamp_decode, 9) == 9); initial_timestamp = *((uint64_t *)(old_tstamp_decode)); + old_tsamp_initial[0] = '\0'; + ASSERT(openvpn_base64_decode(old_sessid, sessid, AUTH_TOKEN_SESSION_ID_LEN)==AUTH_TOKEN_SESSION_ID_LEN); + + /* free the auth-token, we will replace it with a new one */ free(multi->auth_token); } + else if (!rand_bytes(sessid, AUTH_TOKEN_SESSION_ID_LEN)) + { + msg( M_FATAL, "Failed to get enough randomness for " + "authentication token"); + } + + /* Calculate the HMAC */ + /* We enforce up->username to be \0 terminated in ssl.c.. Allowing username + * with \0 in them is asking for troubles in so many ways anyway that we + * ignore that corner case here + */ uint8_t hmac_output[256/8]; hmac_ctx_reset(ctx); - hmac_ctx_update(ctx, (uint8_t *) up->username, (int)strlen(up->username)); + + /* + * If the token was only valid for the empty user, also generate + * a new token with the empty username since we do not want to loose + * the information that the username cannot be trusted + */ + if (multi->auth_token_state_flags & AUTH_TOKEN_VALID_EMPTYUSER) + { + hmac_ctx_update(ctx, (const uint8_t *) "", 0); + } + else + { + hmac_ctx_update(ctx, (uint8_t *) up->username, (int) strlen(up->username)); + } + hmac_ctx_update(ctx, sessid, AUTH_TOKEN_SESSION_ID_LEN); hmac_ctx_update(ctx, (uint8_t *) &initial_timestamp, sizeof(initial_timestamp)); hmac_ctx_update(ctx, (uint8_t *) ×tamp, sizeof(timestamp)); hmac_ctx_final(ctx, hmac_output); /* Construct the unencoded session token */ struct buffer token = alloc_buf_gc( - 2*sizeof(uint64_t) + 256/8, &gc); + 2*sizeof(uint64_t) + AUTH_TOKEN_SESSION_ID_LEN + 256/8, &gc); + ASSERT(buf_write(&token, sessid, sizeof(sessid))); ASSERT(buf_write(&token, &initial_timestamp, sizeof(initial_timestamp))); ASSERT(buf_write(&token, ×tamp, sizeof(timestamp))); ASSERT(buf_write(&token, hmac_output, sizeof(hmac_output))); @@ -136,12 +252,13 @@ generate_auth_token(const struct user_pass *up, struct tls_multi *multi) multi->auth_token = strdup((char *)BPTR(&session_token)); - dmsg(D_SHOW_KEYS, "Generated token for client: %s", - multi->auth_token); + dmsg(D_SHOW_KEYS, "Generated token for client: %s (%s)", + multi->auth_token, up->username); gc_free(&gc); } + static bool check_hmac_token(hmac_ctx_t *ctx, const uint8_t *b64decoded, const char *username) { @@ -169,9 +286,11 @@ verify_auth_token(struct user_pass *up, struct tls_multi *multi, uint8_t b64decoded[USER_PASS_LEN]; int decoded_len = openvpn_base64_decode(up->password + strlen(SESSION_ID_PREFIX), b64decoded, USER_PASS_LEN); - /* Ensure that the decoded data is at least the size of the - * timestamp + hmac */ + /* + * Ensure that the decoded data is the size of the + * timestamp + hmac + session id + */ if (decoded_len != TOKEN_DATA_LEN) { msg(M_WARN, "ERROR: --auth-token wrong size (%d!=%d)", @@ -181,8 +300,8 @@ verify_auth_token(struct user_pass *up, struct tls_multi *multi, unsigned int ret = 0; - - const uint8_t *tstamp_initial = b64decoded; + const uint8_t *sessid = b64decoded; + const uint8_t *tstamp_initial = sessid + AUTH_TOKEN_SESSION_ID_LEN; const uint8_t *tstamp = tstamp_initial + sizeof(int64_t); uint64_t timestamp = ntohll(*((uint64_t *) (tstamp))); @@ -193,6 +312,11 @@ verify_auth_token(struct user_pass *up, struct tls_multi *multi, { ret |= AUTH_TOKEN_HMAC_OK; } + else if (check_hmac_token(ctx, b64decoded, "")) + { + ret |= AUTH_TOKEN_HMAC_OK; + ret |= AUTH_TOKEN_VALID_EMPTYUSER; + } else { msg(M_WARN, "--auth-token-gen: HMAC on token from client failed (%s)", @@ -235,7 +359,6 @@ verify_auth_token(struct user_pass *up, struct tls_multi *multi, { msg(M_INFO, "--auth-token-gen: auth-token from client expired"); } - return ret; } diff --git a/src/openvpn/auth_token.h b/src/openvpn/auth_token.h index 600ac29f..c10afde9 100644 --- a/src/openvpn/auth_token.h +++ b/src/openvpn/auth_token.h @@ -34,7 +34,8 @@ * * Format of the auth-token (before base64 encode) * - * uint64 timestamp (4 bytes)|uint64 timestamp (4 bytes)|sha256-hmac(32 bytes) + * session id(12 bytes)|uint64 timestamp (4 bytes)| + * uint64 timestamp (4 bytes)|sha256-hmac(32 bytes) * * The first timestamp is the time the token was initially created and is used to * determine the maximum renewable time of the token. We always include this even @@ -44,6 +45,10 @@ * to determine if this token has been renewed in the acceptable time range * (2 * renogiation timeout) * + * The session is a random string of 12 byte (or 16 in base64) that is not used by + * OpenVPN itself but kept intact so that external logging/managment can track the + * session multiple reconnects/servers + * * The hmac is calculated over the username contactinated with the * raw auth-token bytes to include authentication of the username in the token * @@ -82,6 +87,14 @@ auth_token_init_secret(struct key_ctx *key_ctx, const char *key_file, void auth_token_write_server_key_file(const char *filename); +/** + * Put the session id, and auth token status into the environment + * if auth-token is enabled + * + */ +void add_session_token_env(struct tls_session *session, struct tls_multi *multi, + const struct user_pass *up); + /** * Wipes the authentication token out of the memory, frees and cleans up * related buffers and flags diff --git a/src/openvpn/init.c b/src/openvpn/init.c index 1732312f..a3cc1f37 100644 --- a/src/openvpn/init.c +++ b/src/openvpn/init.c @@ -2873,6 +2873,7 @@ do_init_crypto_tls(struct context *c, const unsigned int flags) to.auth_user_pass_file = options->auth_user_pass_file; to.auth_token_generate = options->auth_token_generate; to.auth_token_lifetime = options->auth_token_lifetime; + to.auth_token_call_auth = options->auth_token_call_auth; to.auth_token_key = c->c1.ks.auth_token_key; #endif diff --git a/src/openvpn/manage.c b/src/openvpn/manage.c index 2d86dad4..1d97c2b6 100644 --- a/src/openvpn/manage.c +++ b/src/openvpn/manage.c @@ -2736,7 +2736,9 @@ env_filter_match(const char *env_str, const int env_filter_level) "ifconfig_pool_netmask=", "time_duration=", "bytes_sent=", - "bytes_received=" + "bytes_received=", + "session_id=", + "session_state=" }; if (env_filter_level == 0) diff --git a/src/openvpn/options.c b/src/openvpn/options.c index 3dac1e7a..9993063a 100644 --- a/src/openvpn/options.c +++ b/src/openvpn/options.c @@ -6766,11 +6766,23 @@ add_option(struct options *options, &options->auth_user_pass_verify_script, p[1], "auth-user-pass-verify", true); } - else if (streq(p[0], "auth-gen-token")) + else if (streq(p[0], "auth-gen-token") && !p[3]) { VERIFY_PERMISSION(OPT_P_GENERAL); options->auth_token_generate = true; options->auth_token_lifetime = p[1] ? positive_atoi(p[1]) : 0; + if (p[2]) + { + if (streq(p[2], "external-auth")) + { + options->auth_token_call_auth = true; + } + else + { + msg(msglevel, "Invalid argument to auth-gen_token: %s", p[2]); + } + } + } else if (streq(p[0], "auth-gen-token-secret") && p[1] && (!p[2] || (p[2] && streq(p[1], INLINE_FILE_TAG)))) diff --git a/src/openvpn/options.h b/src/openvpn/options.h index cad5be81..4b3883e6 100644 --- a/src/openvpn/options.h +++ b/src/openvpn/options.h @@ -469,7 +469,9 @@ struct options const char *auth_user_pass_verify_script; bool auth_user_pass_verify_script_via_file; bool auth_token_generate; - unsigned int auth_token_lifetime; + bool auth_token_gen_secret_file; + bool auth_token_call_auth; + int auth_token_lifetime; const char *auth_token_secret_file; const char *auth_token_secret_file_inline; diff --git a/src/openvpn/ssl_common.h b/src/openvpn/ssl_common.h index ff8ffacc..8cdb7dce 100644 --- a/src/openvpn/ssl_common.h +++ b/src/openvpn/ssl_common.h @@ -310,6 +310,7 @@ struct tls_options bool auth_token_generate; /**< Generate auth-tokens on successful * user/pass auth,seet via * options->auth_token_generate. */ + bool auth_token_call_auth; /**< always call normal authentication */ unsigned int auth_token_lifetime; struct key_ctx auth_token_key; @@ -469,7 +470,6 @@ struct tls_session */ #define KEY_SCAN_SIZE 3 - /** * Security parameter state for a single VPN tunnel. * @ingroup control_processor @@ -552,9 +552,17 @@ struct tls_multi /**< Auth-token sent from client has valid hmac */ #define AUTH_TOKEN_EXPIRED (1<<1) /**< Auth-token sent from client has expired */ -#endif +#define AUTH_TOKEN_VALID_EMPTYUSER (1<<2) + /**< + * Auth-token is only valid for an empty username + * and not the username actually supplied from the client + * + * OpenVPN 3 clients sometimes the empty username with a + * username hint from their config. + */ int auth_token_state_flags; /**< The state of the auth-token sent from the client last time */ +#endif /* For P_DATA_V2 */ uint32_t peer_id; diff --git a/src/openvpn/ssl_verify.c b/src/openvpn/ssl_verify.c index b392b0ac..6535ad8d 100644 --- a/src/openvpn/ssl_verify.c +++ b/src/openvpn/ssl_verify.c @@ -1052,7 +1052,8 @@ tls_authenticate_key(struct tls_multi *multi, const unsigned int mda_key_id, con * Verify the user name and password using a script */ static bool -verify_user_pass_script(struct tls_session *session, const struct user_pass *up) +verify_user_pass_script(struct tls_session *session, struct tls_multi *multi, + const struct user_pass *up) { struct gc_arena gc = gc_new(); struct argv argv = argv_new(); @@ -1101,6 +1102,9 @@ verify_user_pass_script(struct tls_session *session, const struct user_pass *up) /* setenv client real IP address */ setenv_untrusted(session); + /* add auth-token environment */ + add_session_token_env(session, multi, up); + /* format command line */ argv_parse_cmd(&argv, session->opt->auth_user_pass_verify_script); argv_printf_cat(&argv, "%s", tmp_file); @@ -1134,7 +1138,8 @@ done: * Verify the username and password using a plugin */ static int -verify_user_pass_plugin(struct tls_session *session, const struct user_pass *up) +verify_user_pass_plugin(struct tls_session *session, struct tls_multi *multi, + const struct user_pass *up) { int retval = OPENVPN_PLUGIN_FUNC_ERROR; #ifdef PLUGIN_DEF_AUTH @@ -1154,6 +1159,8 @@ verify_user_pass_plugin(struct tls_session *session, const struct user_pass *up) /* setenv client real IP address */ setenv_untrusted(session); + /* add auth-token environment */ + add_session_token_env(session, multi, up); #ifdef PLUGIN_DEF_AUTH /* generate filename for deferred auth control file */ if (!key_state_gen_auth_control_file(ks, session->opt)) @@ -1197,7 +1204,9 @@ cleanup: #define KMDA_DEF 3 static int -verify_user_pass_management(struct tls_session *session, const struct user_pass *up) +verify_user_pass_management(struct tls_session *session, + struct tls_multi* multi, + const struct user_pass *up) { int retval = KMDA_ERROR; struct key_state *ks = &session->key[KS_PRIMARY]; /* primary key */ @@ -1215,6 +1224,11 @@ verify_user_pass_management(struct tls_session *session, const struct user_pass /* setenv client real IP address */ setenv_untrusted(session); + /* + * if we are using auth-gen-token, send also the session id of auth gen token to + * allow the management to figure out if it is a new session or a continued one + */ + add_session_token_env(session, multi, up); if (management) { management_notify_client_needing_auth(management, ks->mda_key_id, session->opt->mda_context, session->opt->es); @@ -1272,17 +1286,25 @@ verify_user_pass(struct user_pass *up, struct tls_multi *multi, */ if (session->opt->auth_token_generate && is_auth_token(up->password)) { - multi->auth_token_state_flags = verify_auth_token(up, multi,session); - if (multi->auth_token_state_flags == AUTH_TOKEN_HMAC_OK) + multi->auth_token_state_flags = verify_auth_token(up, multi, session); + if (session->opt->auth_token_call_auth) + { + /* + * we do not care about the result here because it is + * the responsibility of the external authentication to + * decide what to do with the result + */ + } + else if (multi->auth_token_state_flags == AUTH_TOKEN_HMAC_OK) { /* - * We do not want the EXPIRED flag here so check + * We do not want the EXPIRED or EMPTY USER flags here so check * for equality with AUTH_TOKEN_HMAC_OK */ msg(M_WARN, "TLS: Username/auth-token authentication " - "succeeded for username '%s'", + "succeeded for username '%s'", up->username); - skip_auth = true; + skip_auth = true; } else { @@ -1293,31 +1315,34 @@ verify_user_pass(struct user_pass *up, struct tls_multi *multi, return; } } + /* call plugin(s) and/or script */ if (!skip_auth) { - - /* call plugin(s) and/or script */ #ifdef MANAGEMENT_DEF_AUTH - if (man_def_auth == KMDA_DEF) + if (man_def_auth==KMDA_DEF) { - man_def_auth = verify_user_pass_management(session, up); + man_def_auth = verify_user_pass_management(session, multi, up); } #endif if (plugin_defined(session->opt->plugins, OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY)) { - s1 = verify_user_pass_plugin(session, up); + s1 = verify_user_pass_plugin(session, multi, up); } + if (session->opt->auth_user_pass_verify_script) { - s2 = verify_user_pass_script(session, up); + s2 = verify_user_pass_script(session, multi, up); } + } - /* check sizing of username if it will become our common name */ - if ((session->opt->ssl_flags & SSLF_USERNAME_AS_COMMON_NAME) && strlen(up->username) > TLS_USERNAME_LEN) - { - msg(D_TLS_ERRORS, "TLS Auth Error: --username-as-common name specified and username is longer than the maximum permitted Common Name length of %d characters", TLS_USERNAME_LEN); - s1 = OPENVPN_PLUGIN_FUNC_ERROR; - } + /* check sizing of username if it will become our common name */ + if ((session->opt->ssl_flags & SSLF_USERNAME_AS_COMMON_NAME) && + strlen(up->username)>TLS_USERNAME_LEN) + { + msg(D_TLS_ERRORS, + "TLS Auth Error: --username-as-common name specified and username is longer than the maximum permitted Common Name length of %d characters", + TLS_USERNAME_LEN); + s1 = OPENVPN_PLUGIN_FUNC_ERROR; } /* auth succeeded? */ if ((s1 == OPENVPN_PLUGIN_FUNC_SUCCESS @@ -1357,7 +1382,7 @@ verify_user_pass(struct user_pass *up, struct tls_multi *multi, * the initial timestamp and session id can be extracted from it */ if (multi->auth_token && (multi->auth_token_state_flags & AUTH_TOKEN_HMAC_OK) - && !(multi->auth_token_state_flags & AUTH_TOKEN_EXPIRED)) + && !(multi->auth_token_state_flags & AUTH_TOKEN_EXPIRED)) { multi->auth_token = strdup(up->password); } From patchwork Fri May 10 02:11:13 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Arne Schwabe X-Patchwork-Id: 735 X-Patchwork-Delegate: davids@openvpn.net Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director12.mail.ord1d.rsapps.net ([172.31.255.6]) by backend30.mail.ord1d.rsapps.net with LMTP id UOfbGUZv1VwVNgAAIUCqbw for ; Fri, 10 May 2019 08:32:06 -0400 Received: from proxy5.mail.iad3b.rsapps.net ([172.31.255.6]) by director12.mail.ord1d.rsapps.net with LMTP id sNQsF0Zv1VwuOQAAIasKDg ; Fri, 10 May 2019 08:32:06 -0400 Received: from smtp6.gate.iad3b ([172.31.255.6]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy5.mail.iad3b.rsapps.net with LMTP id 6DkOEUZv1VylEwAA13hMnw ; Fri, 10 May 2019 08:32:06 -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: smtp6.gate.iad3b.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; dmarc=none (p=nil; dis=none) header.from=rfc2549.org X-Suspicious-Flag: YES X-Classification-ID: 9e8dcd10-731f-11e9-a619-5254000d607e-1-1 Received: from [216.105.38.7] ([216.105.38.7:14971] helo=lists.sourceforge.net) by smtp6.gate.iad3b.rsapps.net (envelope-from ) (ecelerity 4.2.38.62370 r(:)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id A4/A0-24456-54F65DC5; Fri, 10 May 2019 08:32:05 -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 1hP4gN-0004EX-2q; Fri, 10 May 2019 12:31:23 +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 1hP4gM-0004E6-3c for openvpn-devel@lists.sourceforge.net; Fri, 10 May 2019 12:31:22 +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=RGSn3slR/EKlIF4lgtr/ATo4F12fcO3tyqqMeL2DnIo=; b=izHlhqE5689a31D0p2jwYa9an9 iMHTc9fSnF/gGlRWWe+fn0ar9c6JSvMadvMko1jFcvtFbbv38h7BZ/bbedFlXnkUf3G4e7/OF4qw7 xkDWRbNnMFpouuodnWNTOJfj+bJDbRFPoBego+AF3lqGXp+tqOunmr66ukRaiq6xd7yg=; 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=RGSn3slR/EKlIF4lgtr/ATo4F12fcO3tyqqMeL2DnIo=; b=Y82Q4TCEK+9H0Z2nxHZ/B3Cd0c seaIpPo1R6mTZMAgxZlIT56w5AnEmF17DYX5jXQkL/wdWPvNM2BhIIh9PM87fFjsZj3hE3VN/qIX4 bzFCjZ8d6MCWjj8pC0R7lJObk/itfUOCGZgbSgzmmUNJ9hsY4ujFdQ5dmqHq6OQrnj6Q=; Received: from mail.blinkt.de ([192.26.174.232]) by sfi-mx-4.v28.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.90_1) id 1hP4gG-00BUJI-GW for openvpn-devel@lists.sourceforge.net; Fri, 10 May 2019 12:31:17 +0000 Received: from kamera.blinkt.de ([2001:638:502:390:20c:29ff:fec8:535c]) by mail.blinkt.de with smtp (Exim 4.91 (FreeBSD)) (envelope-from ) id 1hP4Mt-000B56-CH; Fri, 10 May 2019 14:11:15 +0200 Received: (nullmailer pid 30531 invoked by uid 10006); Fri, 10 May 2019 12:11:15 -0000 From: Arne Schwabe To: openvpn-devel@lists.sourceforge.net Date: Fri, 10 May 2019 14:11:13 +0200 Message-Id: <20190510121114.30468-7-arne@rfc2549.org> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20190510121114.30468-1-arne@rfc2549.org> References: <20190510121114.30468-1-arne@rfc2549.org> X-Spam-Report: Spam Filtering performed by mx.sourceforge.net. See http://spamassassin.org/tag/ for more details. 0.1 HEADER_FROM_DIFFERENT_DOMAINS From and EnvelopeFrom 2nd level mail domains are different 0.0 URIBL_BLOCKED ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [URIs: openvpn.net] 0.1 AWL AWL: Adjusted score from AWL reputation of From: address X-Headers-End: 1hP4gG-00BUJI-GW Subject: [Openvpn-devel] [PATCH v3 6/7] Sent indication that a session is expired to clients 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: Arne Schwabe MIME-Version: 1.0 Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox From: Arne Schwabe This allows OpenVPN 3 core to fall back to the original authentication method. This commit changes man_def_auth_set_client_reason to auth_set_client_reason since it now used in more contexts. Also remove a FIXME about client_reason not being freed, as it is freed in tls_multi_free with auth_set_client_reason(multi, NULL); --- src/openvpn/auth_token.c | 3 +++ src/openvpn/ssl.c | 6 ++---- src/openvpn/ssl_common.h | 10 +++++----- src/openvpn/ssl_verify.c | 8 ++++---- src/openvpn/ssl_verify.h | 15 ++++++++++----- 5 files changed, 24 insertions(+), 18 deletions(-) diff --git a/src/openvpn/auth_token.c b/src/openvpn/auth_token.c index f28008d3..63ec3145 100644 --- a/src/openvpn/auth_token.c +++ b/src/openvpn/auth_token.c @@ -15,6 +15,7 @@ #include "push.h" #include "integer.h" #include "ssl.h" +#include "ssl_verify.h" const char *auth_token_pem_name = "OpenVPN auth-token server key"; @@ -357,6 +358,8 @@ verify_auth_token(struct user_pass *up, struct tls_multi *multi, if (ret & AUTH_TOKEN_EXPIRED) { + /* Tell client that the session token is expired */ + auth_set_client_reason(multi, "SESSION: token expired"); msg(M_INFO, "--auth-token-gen: auth-token from client expired"); } return ret; diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c index 69d7be57..c615a6d8 100644 --- a/src/openvpn/ssl.c +++ b/src/openvpn/ssl.c @@ -1349,11 +1349,9 @@ tls_multi_free(struct tls_multi *multi, bool clear) ASSERT(multi); -#ifdef MANAGEMENT_DEF_AUTH - man_def_auth_set_client_reason(multi, NULL); - -#endif #if P2MP_SERVER + auth_set_client_reason(multi, NULL); + free(multi->peer_info); #endif diff --git a/src/openvpn/ssl_common.h b/src/openvpn/ssl_common.h index 8cdb7dce..df320abb 100644 --- a/src/openvpn/ssl_common.h +++ b/src/openvpn/ssl_common.h @@ -525,16 +525,16 @@ struct tls_multi struct cert_hash_set *locked_cert_hash_set; #ifdef ENABLE_DEF_AUTH - /* - * An error message to send to client on AUTH_FAILED - */ - char *client_reason; - /* Time of last call to tls_authentication_status */ time_t tas_last; #endif #if P2MP_SERVER + /* + * An error message to send to client on AUTH_FAILED + */ + char *client_reason; + /* * A multi-line string of general-purpose info received from peer * over control channel. diff --git a/src/openvpn/ssl_verify.c b/src/openvpn/ssl_verify.c index 6535ad8d..f61c621b 100644 --- a/src/openvpn/ssl_verify.c +++ b/src/openvpn/ssl_verify.c @@ -803,9 +803,8 @@ cleanup: #define ACF_FAILED 3 #endif -#ifdef MANAGEMENT_DEF_AUTH void -man_def_auth_set_client_reason(struct tls_multi *multi, const char *client_reason) +auth_set_client_reason(struct tls_multi* multi, const char* client_reason) { if (multi->client_reason) { @@ -814,11 +813,12 @@ man_def_auth_set_client_reason(struct tls_multi *multi, const char *client_reaso } if (client_reason && strlen(client_reason)) { - /* FIXME: Last alloc will never be freed */ multi->client_reason = string_alloc(client_reason, NULL); } } +#ifdef MANAGEMENT_DEF_AUTH + static inline unsigned int man_def_auth_test(const struct key_state *ks) { @@ -1022,7 +1022,7 @@ tls_authenticate_key(struct tls_multi *multi, const unsigned int mda_key_id, con if (multi) { int i; - man_def_auth_set_client_reason(multi, client_reason); + auth_set_client_reason(multi, client_reason); for (i = 0; i < KEY_SCAN_SIZE; ++i) { struct key_state *ks = multi->key_scan[i]; diff --git a/src/openvpn/ssl_verify.h b/src/openvpn/ssl_verify.h index 64f27efb..c54b89a6 100644 --- a/src/openvpn/ssl_verify.h +++ b/src/openvpn/ssl_verify.h @@ -224,18 +224,23 @@ struct x509_track #ifdef MANAGEMENT_DEF_AUTH bool tls_authenticate_key(struct tls_multi *multi, const unsigned int mda_key_id, const bool auth, const char *client_reason); -void man_def_auth_set_client_reason(struct tls_multi *multi, const char *client_reason); +#endif +#ifdef P2MP_SERVER +/** + * Sets the reason why authentication of a client failed. This be will send to the client + * when the AUTH_FAILED message is sent + * An example would be "SESSION: Token expired" + * @param multi The multi tls struct + * @param client_reason The string to send to the client as part of AUTH_FAILED + */ +void auth_set_client_reason(struct tls_multi* multi, const char* client_reason); #endif static inline const char * tls_client_reason(struct tls_multi *multi) { -#ifdef ENABLE_DEF_AUTH return multi->client_reason; -#else - return NULL; -#endif } /** Remove any X509_ env variables from env_set es */ From patchwork Fri May 10 02:11:14 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Arne Schwabe X-Patchwork-Id: 736 X-Patchwork-Delegate: davids@openvpn.net Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director8.mail.ord1d.rsapps.net ([172.31.255.6]) by backend30.mail.ord1d.rsapps.net with LMTP id gDayKE9v1VwfQgAAIUCqbw for ; Fri, 10 May 2019 08:32:15 -0400 Received: from proxy10.mail.iad3b.rsapps.net ([172.31.255.6]) by director8.mail.ord1d.rsapps.net with LMTP id 4LPrJU9v1VyyRwAAfY0hYg ; Fri, 10 May 2019 08:32:15 -0400 Received: from smtp39.gate.iad3b ([172.31.255.6]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy10.mail.iad3b.rsapps.net with LMTP id IKqKHk9v1VwCTQAA/F5p9A ; Fri, 10 May 2019 08:32:15 -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: smtp39.gate.iad3b.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; dmarc=none (p=nil; dis=none) header.from=rfc2549.org X-Suspicious-Flag: YES X-Classification-ID: a415285a-731f-11e9-b814-5254002be87c-1-1 Received: from [216.105.38.7] ([216.105.38.7:53843] helo=lists.sourceforge.net) by smtp39.gate.iad3b.rsapps.net (envelope-from ) (ecelerity 4.2.38.62370 r(:)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id EA/4B-28825-F4F65DC5; Fri, 10 May 2019 08:32:15 -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 1hP4gR-00060E-GQ; Fri, 10 May 2019 12:31:27 +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 1hP4gQ-000602-6u for openvpn-devel@lists.sourceforge.net; Fri, 10 May 2019 12:31:26 +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=sVn9ufxdwW2SyM5QRAVOvLu9uJbqHsOo/wbnoNlEY3U=; b=Zb8Fa70TAIq3i775o7zHFNiam3 SrrJmONCQXLLJjoskpHnr2H41hSAyVvOnzQHbmoRkYMiRJN9YJy8uxyHpA2Ol5afEfYp64oWQAvS7 AXZBHQQGgZSrlsVclHUrq7SYwd1Eanke59huT7nxUywiv8+W6/kt9xTqw7C7rIMsfLq4=; 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=sVn9ufxdwW2SyM5QRAVOvLu9uJbqHsOo/wbnoNlEY3U=; b=OXs6dB4UaTym8Tr2VYJuNz5DWC qlD7xPbyvYiqkq0//yG82IEeK2iQpHf50rZhFGxzwUl9lKzQFIe4s/4/nWpwNn0yZvLkgeyqMxZ56 vneMqD2W35H4HRAOW2fvQlqM7+RMZrMFeuaW8QR9RmBT5dKXKt5a4kqxs83Ko8B8xhW0=; Received: from mail.blinkt.de ([192.26.174.232]) by sfi-mx-3.v28.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.90_1) id 1hP4gO-00Byui-D1 for openvpn-devel@lists.sourceforge.net; Fri, 10 May 2019 12:31:26 +0000 Received: from kamera.blinkt.de ([2001:638:502:390:20c:29ff:fec8:535c]) by mail.blinkt.de with smtp (Exim 4.91 (FreeBSD)) (envelope-from ) id 1hP4Mt-000B5B-Eq; Fri, 10 May 2019 14:11:15 +0200 Received: (nullmailer pid 30534 invoked by uid 10006); Fri, 10 May 2019 12:11:15 -0000 From: Arne Schwabe To: openvpn-devel@lists.sourceforge.net Date: Fri, 10 May 2019 14:11:14 +0200 Message-Id: <20190510121114.30468-8-arne@rfc2549.org> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20190510121114.30468-1-arne@rfc2549.org> References: <20190510121114.30468-1-arne@rfc2549.org> X-Spam-Report: Spam Filtering performed by mx.sourceforge.net. See http://spamassassin.org/tag/ for more details. 0.0 URIBL_BLOCKED ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [URIs: makefile.am] 0.1 HEADER_FROM_DIFFERENT_DOMAINS From and EnvelopeFrom 2nd level mail domains are different 0.1 AWL AWL: Adjusted score from AWL reputation of From: address X-Headers-End: 1hP4gO-00Byui-D1 Subject: [Openvpn-devel] [PATCH v3 7/7] Implement unit tests for auth-gen-token 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: Arne Schwabe MIME-Version: 1.0 Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox From: Arne Schwabe Patch V2: adapt unit tests to other V2 patches --- tests/unit_tests/openvpn/Makefile.am | 18 +- tests/unit_tests/openvpn/test_auth_token.c | 375 +++++++++++++++++++++ 2 files changed, 392 insertions(+), 1 deletion(-) create mode 100644 tests/unit_tests/openvpn/test_auth_token.c diff --git a/tests/unit_tests/openvpn/Makefile.am b/tests/unit_tests/openvpn/Makefile.am index 657957e5..cf55bfaf 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 += crypto_testdriver packet_id_testdriver +check_PROGRAMS += crypto_testdriver packet_id_testdriver auth_token_testdriver if HAVE_LD_WRAP_SUPPORT check_PROGRAMS += tls_crypt_testdriver endif @@ -72,3 +72,19 @@ tls_crypt_testdriver_SOURCES = test_tls_crypt.c mock_msg.c mock_msg.h \ $(openvpn_srcdir)/packet_id.c \ $(openvpn_srcdir)/platform.c \ $(openvpn_srcdir)/run_command.c + +auth_token_testdriver_CFLAGS = @TEST_CFLAGS@ \ + -I$(openvpn_includedir) -I$(compat_srcdir) -I$(openvpn_srcdir) \ + $(OPTIONAL_CRYPTO_CFLAGS) +auth_token_testdriver_LDFLAGS = @TEST_LDFLAGS@ \ + $(OPTIONAL_CRYPTO_LIBS) + +auth_token_testdriver_SOURCES = test_auth_token.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 \ + $(openvpn_srcdir)/base64.c diff --git a/tests/unit_tests/openvpn/test_auth_token.c b/tests/unit_tests/openvpn/test_auth_token.c new file mode 100644 index 00000000..a3591b4a --- /dev/null +++ b/tests/unit_tests/openvpn/test_auth_token.c @@ -0,0 +1,375 @@ +/* + * 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 "auth_token.c" + +#include "mock_msg.h" + +struct test_context { + struct tls_multi multi; + struct key_type kt; + struct user_pass up; + struct tls_session session; +}; + +static const char *now0key0 = "SESS_ID_AT_0123456789abcdefAAAAAAAAAAAAAAAAAAAAAE5JsQJOVfo8jnI3RL3tBaR5NkE4yPfcylFUHmHSc5Bu"; + +static const char *zeroinline = "-----BEGIN OpenVPN auth-token server key-----\n" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\n" + "-----END OpenVPN auth-token server key-----"; + +static const char *allx01inline = "-----BEGIN OpenVPN auth-token server key-----\n" + "AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEB\n" + "AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEB\n" + "AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE=\n" + "-----END OpenVPN auth-token server key-----"; + +static const char *random_key = "-----BEGIN OpenVPN auth-token server key-----\n" + "+mmmf7IQ5cymtMVjKYTWk8IOcYanRlpQmV9Tb3EjkHYxueBVDg3yqRgzeBlVGzNLD//rAPiOVhau\n" + "3NDBjNOQB8951bfs7Cc2mYfay92Bh2gRJ5XEM/DMfzCWN+7uU6NWoTTHr4FuojnIQtjtqVAj/JS9\n" + "w+dTSp/vYHl+c7uHd19uVRu/qLqV85+rm4tUGIjO7FfYuwyPqwmhuIsi3hs9QkSimh888FmBpoKY\n" + "/tbKVTJZmSERKti9KEwtV2eVAR0znN5KW7lCB3mHVAhN7bUpcoDjfCzYIFARxwswTFu9gFkwqUMY\n" + "I1KUOgIsVNs4llACioeXplYekWETR+YkJwDc/A==\n" + "-----END OpenVPN auth-token server key-----"; + +static const char *random_token = "SESS_ID_AT_ThhRItzOKNKrh3dfAAAAAFwzHpwAAAAAXDMenDdrq0RoH3dkA1f7O3wO+7kZcx2DusVZrRmFlWQM9HOb"; + + +static int +setup(void **state) +{ + struct test_context *ctx = calloc(1, sizeof(*ctx)); + *state = ctx; + + struct key key = { 0 }; + + ctx->kt = auth_token_kt(); + if (!ctx->kt.digest) + { + return 0; + } + ctx->multi.opt.auth_token_generate = true; + ctx->multi.opt.auth_token_lifetime = 3000; + + ctx->session.opt = calloc(1, sizeof(struct tls_options)); + ctx->session.opt->renegotiate_seconds = 120; + ctx->session.opt->auth_token_lifetime = 3000; + + strcpy(ctx->up.username, "test user name"); + strcpy(ctx->up.password, "ignored"); + + init_key_ctx(&ctx->multi.opt.auth_token_key, &key, &ctx->kt, false, "TEST"); + + now = 0; + return 0; +} + +static int +teardown(void **state) +{ + struct test_context *ctx = (struct test_context *) *state; + + free_key_ctx(&ctx->multi.opt.auth_token_key); + wipe_auth_token(&ctx->multi); + + free(ctx->session.opt); + free(ctx); + + return 0; +} + +static void +auth_token_basic_test(void **state) +{ + struct test_context *ctx = (struct test_context *) *state; + + generate_auth_token(&ctx->up, &ctx->multi); + strcpy(ctx->up.password, ctx->multi.auth_token); + assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, &ctx->session), AUTH_TOKEN_HMAC_OK); +} + +static void +auth_token_fail_invalid_key(void **state) +{ + struct test_context *ctx = (struct test_context *) *state; + + generate_auth_token(&ctx->up, &ctx->multi); + strcpy(ctx->up.password, ctx->multi.auth_token); + assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, &ctx->session), AUTH_TOKEN_HMAC_OK); + + /* Change auth-token key */ + struct key key; + memset(&key, '1', sizeof(key)); + free_key_ctx(&ctx->multi.opt.auth_token_key); + init_key_ctx(&ctx->multi.opt.auth_token_key, &key, &ctx->kt, false, "TEST"); + + assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, &ctx->session), 0); + + /* Load original test key again */ + memset(&key, 0, sizeof(key)); + free_key_ctx(&ctx->multi.opt.auth_token_key); + init_key_ctx(&ctx->multi.opt.auth_token_key, &key, &ctx->kt, false, "TEST"); + assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, &ctx->session), AUTH_TOKEN_HMAC_OK); + +} + +static void +auth_token_test_timeout(void **state) +{ + struct test_context *ctx = (struct test_context *) *state; + + now = 100000; + generate_auth_token(&ctx->up, &ctx->multi); + strcpy(ctx->up.password, ctx->multi.auth_token); + + /* No time has passed */ + assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, &ctx->session), AUTH_TOKEN_HMAC_OK); + + /* Token before validity, should be rejected */ + now = 100000 - 100; + assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, &ctx->session), AUTH_TOKEN_HMAC_OK|AUTH_TOKEN_EXPIRED); + + /* Token still in validity, should be accepted */ + now = 100000 + 2*ctx->session.opt->renegotiate_seconds - 20; + assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, &ctx->session), AUTH_TOKEN_HMAC_OK); + + /* Token past validity, should be rejected */ + now = 100000 + 2*ctx->session.opt->renegotiate_seconds + 20; + assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, &ctx->session), AUTH_TOKEN_HMAC_OK|AUTH_TOKEN_EXPIRED); + + /* Check if the mode for a client that never updates its token works */ + ctx->multi.auth_token_initial = strdup(ctx->up.password); + assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, &ctx->session), AUTH_TOKEN_HMAC_OK); + + /* But not when we reached our timeout */ + now = 100000 + ctx->session.opt->auth_token_lifetime + 1; + assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, &ctx->session), AUTH_TOKEN_HMAC_OK|AUTH_TOKEN_EXPIRED); + + free(ctx->multi.auth_token_initial); + ctx->multi.auth_token_initial = NULL; + + /* regenerate the token util it hits the expiry */ + now = 100000; + while (now < 100000 + ctx->session.opt->auth_token_lifetime + 1) + { + assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, &ctx->session), AUTH_TOKEN_HMAC_OK); + generate_auth_token(&ctx->up, &ctx->multi); + strcpy(ctx->up.password, ctx->multi.auth_token); + now += ctx->session.opt->renegotiate_seconds; + } + + + assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, &ctx->session), AUTH_TOKEN_HMAC_OK|AUTH_TOKEN_EXPIRED); + ctx->multi.opt.auth_token_lifetime = 0; + + /* Non expiring token should be fine */ + assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, &ctx->session), AUTH_TOKEN_HMAC_OK); +} + +static void +zerohmac(char *token) +{ + char *hmacstart = token + AUTH_TOKEN_SESSION_ID_LEN + + strlen(SESSION_ID_PREFIX) + 2*sizeof(uint64_t); + memset(hmacstart, 0x8d, strlen(hmacstart)); +} + +static void +auth_token_test_known_keys(void **state) +{ + struct test_context *ctx = (struct test_context *) *state; + + now = 0; + /* Preload the session id so the same session id is used here */ + ctx->multi.auth_token = strdup(now0key0); + + /* Zero the hmac part to ensure we have a newly generated token */ + zerohmac(ctx->multi.auth_token); + + generate_auth_token(&ctx->up, &ctx->multi); + + assert_string_equal(now0key0, ctx->multi.auth_token); + + strcpy(ctx->up.password, ctx->multi.auth_token); + assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, &ctx->session), AUTH_TOKEN_HMAC_OK); +} + +static const char *lastsesion_statevalue; +void +setenv_str(struct env_set *es, const char *name, const char *value) +{ + if (streq(name, "session_state")) + { + lastsesion_statevalue = value; + } +} + +static void +auth_token_test_empty_user(void **state) +{ + struct test_context *ctx = (struct test_context *) *state; + + CLEAR(ctx->up.username); + now = 0; + + generate_auth_token(&ctx->up, &ctx->multi); + strcpy(ctx->up.password, ctx->multi.auth_token); + assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, &ctx->session), AUTH_TOKEN_HMAC_OK); + + now = 100000; + assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, &ctx->session), AUTH_TOKEN_HMAC_OK|AUTH_TOKEN_EXPIRED); + strcpy(ctx->up.username, "test user name"); + + now = 0; + assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, &ctx->session), AUTH_TOKEN_HMAC_OK|AUTH_TOKEN_VALID_EMPTYUSER); + + now = 100000; + assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, &ctx->session), AUTH_TOKEN_HMAC_OK|AUTH_TOKEN_EXPIRED|AUTH_TOKEN_VALID_EMPTYUSER); + + zerohmac(ctx->up.password); + assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, &ctx->session), 0); +} + +static void +auth_token_test_env(void **state) +{ + struct test_context *ctx = (struct test_context *) *state; + + ctx->multi.auth_token_state_flags = 0; + ctx->multi.auth_token = NULL; + add_session_token_env(&ctx->session, &ctx->multi, &ctx->up); + assert_string_equal(lastsesion_statevalue, "Initial"); + + ctx->multi.auth_token_state_flags = 0; + strcpy(ctx->up.password, now0key0); + add_session_token_env(&ctx->session, &ctx->multi, &ctx->up); + assert_string_equal(lastsesion_statevalue, "Invalid"); + + ctx->multi.auth_token_state_flags = AUTH_TOKEN_HMAC_OK; + add_session_token_env(&ctx->session, &ctx->multi, &ctx->up); + assert_string_equal(lastsesion_statevalue, "Authenticated"); + + ctx->multi.auth_token_state_flags = AUTH_TOKEN_HMAC_OK|AUTH_TOKEN_EXPIRED; + add_session_token_env(&ctx->session, &ctx->multi, &ctx->up); + assert_string_equal(lastsesion_statevalue, "Expired"); + + ctx->multi.auth_token_state_flags = AUTH_TOKEN_HMAC_OK|AUTH_TOKEN_VALID_EMPTYUSER; + add_session_token_env(&ctx->session, &ctx->multi, &ctx->up); + assert_string_equal(lastsesion_statevalue, "AuthenticatedEmptyUser"); + + ctx->multi.auth_token_state_flags = AUTH_TOKEN_HMAC_OK|AUTH_TOKEN_EXPIRED|AUTH_TOKEN_VALID_EMPTYUSER; + add_session_token_env(&ctx->session, &ctx->multi, &ctx->up); + assert_string_equal(lastsesion_statevalue, "ExpiredEmptyUser"); +} + +static void +auth_token_test_random_keys(void **state) +{ + struct test_context *ctx = (struct test_context *) *state; + + now = 0x5c331e9c; + /* Preload the session id so the same session id is used here */ + ctx->multi.auth_token = strdup(random_token); + + free_key_ctx(&ctx->multi.opt.auth_token_key); + auth_token_init_secret(&ctx->multi.opt.auth_token_key, INLINE_FILE_TAG, random_key); + + /* Zero the hmac part to ensure we have a newly generated token */ + zerohmac(ctx->multi.auth_token); + + generate_auth_token(&ctx->up, &ctx->multi); + + assert_string_equal(random_token, ctx->multi.auth_token); + + strcpy(ctx->up.password, ctx->multi.auth_token); + assert_true(verify_auth_token(&ctx->up, &ctx->multi, &ctx->session)); +} + + +static void +auth_token_test_key_load(void **state) +{ + struct test_context *ctx = (struct test_context *) *state; + + free_key_ctx(&ctx->multi.opt.auth_token_key); + auth_token_init_secret(&ctx->multi.opt.auth_token_key, INLINE_FILE_TAG, zeroinline); + strcpy(ctx->up.password, now0key0); + assert_true(verify_auth_token(&ctx->up, &ctx->multi, &ctx->session)); + + free_key_ctx(&ctx->multi.opt.auth_token_key); + auth_token_init_secret(&ctx->multi.opt.auth_token_key, INLINE_FILE_TAG, allx01inline); + assert_false(verify_auth_token(&ctx->up, &ctx->multi, &ctx->session)); +} + +int +main(void) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(auth_token_basic_test, setup, teardown), + cmocka_unit_test_setup_teardown(auth_token_fail_invalid_key, setup, teardown), + cmocka_unit_test_setup_teardown(auth_token_test_known_keys, setup, teardown), + cmocka_unit_test_setup_teardown(auth_token_test_empty_user, setup, teardown), + cmocka_unit_test_setup_teardown(auth_token_test_env, setup, teardown), + cmocka_unit_test_setup_teardown(auth_token_test_random_keys, setup, teardown), + cmocka_unit_test_setup_teardown(auth_token_test_key_load, setup, teardown), + cmocka_unit_test_setup_teardown(auth_token_test_timeout, setup, teardown), + }; + +#if defined(ENABLE_CRYPTO_OPENSSL) + OpenSSL_add_all_algorithms(); +#endif + + int ret = cmocka_run_group_tests_name("auth-token tests", tests, NULL, NULL); + + return ret; +} + +/* Dummy functions that do nothing to mock the functionality */ +void +send_push_reply_auth_token(struct tls_multi *multi) +{ +} + +void +auth_set_client_reason(struct tls_multi *multi, const char *reason) +{ + +}