From patchwork Thu Jun 13 03:48:29 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Arne Schwabe X-Patchwork-Id: 742 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 OCTaN3xUAl24XQAAIUCqbw for ; Thu, 13 Jun 2019 09:49:49 -0400 Received: from proxy18.mail.iad3b.rsapps.net ([172.31.255.6]) by director10.mail.ord1d.rsapps.net with LMTP id IKzWNHxUAl3iaAAApN4f7A ; Thu, 13 Jun 2019 09:49:48 -0400 Received: from smtp17.gate.iad3b ([172.31.255.6]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy18.mail.iad3b.rsapps.net with LMTP id WMYILnxUAl2YAQAA3NpJmQ ; Thu, 13 Jun 2019 09:49:48 -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: smtp17.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: 1b7b8f7e-8de2-11e9-8083-52540094e46f-1-1 Received: from [216.105.38.7] ([216.105.38.7:56574] helo=lists.sourceforge.net) by smtp17.gate.iad3b.rsapps.net (envelope-from ) (ecelerity 4.2.38.62370 r(:)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id 47/D9-14677-C74520D5; Thu, 13 Jun 2019 09:49:48 -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 1hbQ65-0000NE-Hq; Thu, 13 Jun 2019 13:48:57 +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 1hbQ64-0000Mf-AU for openvpn-devel@lists.sourceforge.net; Thu, 13 Jun 2019 13:48:56 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=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:In-Reply-To:References:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=AdimWJH8zlRy+ByXwpbPvjpdANGBf4a+OsDccA985iU=; b=LqKFJiQAVtBmUa47O6m5qqqgQ9 R49coG8Io1911Ea7deFMd6slWwdsmSi+1/Xg13/0BN85yhQZFMW0tgtrQPPoLSGTcAx6iMXHZbqzT YrIQKhqRj5HJA2YCFhIucE/JCXBeWgxIepQzypNxmYBIWlzvvDSQJdEWt/Q04eCe4pNQ=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=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: In-Reply-To:References:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=AdimWJH8zlRy+ByXwpbPvjpdANGBf4a+OsDccA985iU=; b=Awl4mVp3X7d6gOjvHWpj6N/TGF D5YVVfNb0ZDBmQMmoEa+DS5zW/K355qWW9GZWI1tu/K4i0jr8m3WKoVxHRV5JkRjkxsW/KyTAx0sI Og/B+kGtiZIHNw5hnfPNmbVpBbstQggfNnmJWq//oOdhrKMzoTTbzCHw1EhoU4oihzmA=; Received: from [192.26.174.232] (helo=mail.blinkt.de) by sfi-mx-3.v28.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.90_1) id 1hbQ5y-00ByOQ-Qb for openvpn-devel@lists.sourceforge.net; Thu, 13 Jun 2019 13:48:55 +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 1hbQ5i-000O12-9F for openvpn-devel@lists.sourceforge.net; Thu, 13 Jun 2019 15:48:34 +0200 Received: (nullmailer pid 5754 invoked by uid 10006); Thu, 13 Jun 2019 13:48:34 -0000 From: Arne Schwabe To: openvpn-devel@lists.sourceforge.net Date: Thu, 13 Jun 2019 15:48:29 +0200 Message-Id: <20190613134834.5709-1-arne@rfc2549.org> X-Mailer: git-send-email 2.17.1 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: t_lpback.sh] 0.2 HEADER_FROM_DIFFERENT_DOMAINS From and EnvelopeFrom 2nd level mail domains are different 0.0 SPF_NONE SPF: sender does not publish an SPF Record 0.0 SPF_HELO_NONE SPF: HELO does not publish an SPF Record 1.0 RDNS_NONE Delivered to internal network by a host with no rDNS X-Headers-End: 1hbQ5y-00ByOQ-Qb Subject: [Openvpn-devel] [PATCH v4 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. PATCH V4: Introduce warning/error when using --secret with --genkey Update non code usages to use new --genkey syntax Acked-by: David Sommerseth --- INSTALL | 2 +- doc/openvpn.8 | 81 ++++++++++++++------------ sample/sample-config-files/server.conf | 2 +- sample/sample-keys/gen-sample-keys.sh | 2 +- sample/sample-windows/sample.ovpn | 2 +- src/openvpn/crypto.c | 2 +- src/openvpn/init.c | 71 ++++++++++++++-------- src/openvpn/options.c | 67 +++++++++++++-------- src/openvpn/options.h | 11 +++- tests/t_lpback.sh | 8 +-- 10 files changed, 151 insertions(+), 97 deletions(-) diff --git a/INSTALL b/INSTALL index 0ba3bba6..b82cda19 100644 --- a/INSTALL +++ b/INSTALL @@ -145,7 +145,7 @@ make check (Run all tests below) Test Crypto: -./openvpn --genkey --secret key +./openvpn --genkey secret key ./openvpn --test-crypto --secret key Test SSL/TLS negotiations (runs for 2 minutes): diff --git a/doc/openvpn.8 b/doc/openvpn.8 index ce440447..25195fd4 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 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 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. @@ -7185,7 +7192,7 @@ First build a static key on bob. .IP .B openvpn \-\-genkey \-\-secret key .LP -This command will build a random key file called +This command will build a key file called .B key (in ascii format). Now copy diff --git a/sample/sample-config-files/server.conf b/sample/sample-config-files/server.conf index 1dd477bd..e7020639 100644 --- a/sample/sample-config-files/server.conf +++ b/sample/sample-config-files/server.conf @@ -235,7 +235,7 @@ keepalive 10 120 # to help block DoS attacks and UDP port flooding. # # Generate with: -# openvpn --genkey --secret ta.key +# openvpn --genkey tls-auth ta.key # # The server and each client must have # a copy of this key. diff --git a/sample/sample-keys/gen-sample-keys.sh b/sample/sample-keys/gen-sample-keys.sh index 920513a1..fda4ffe6 100755 --- a/sample/sample-keys/gen-sample-keys.sh +++ b/sample/sample-keys/gen-sample-keys.sh @@ -15,7 +15,7 @@ then fi # Generate static key for tls-auth (or static key mode) -$(dirname ${0})/../../src/openvpn/openvpn --genkey --secret ta.key +$(dirname ${0})/../../src/openvpn/openvpn --genkey tls-auth ta.key # Create required directories and files mkdir -p sample-ca diff --git a/sample/sample-windows/sample.ovpn b/sample/sample-windows/sample.ovpn index 5accd573..51e32744 100755 --- a/sample/sample-windows/sample.ovpn +++ b/sample/sample-windows/sample.ovpn @@ -68,7 +68,7 @@ ifconfig 10.3.0.1 255.255.255.0 # # You can also generate key.txt manually # with the following command: -# openvpn --genkey --secret key.txt +# openvpn --genkey secret key.txt # # key must match on both ends of the connection, # so you should generate it on one machine and diff --git a/src/openvpn/crypto.c b/src/openvpn/crypto.c index eb56421b..9a150fa2 100644 --- a/src/openvpn/crypto.c +++ b/src/openvpn/crypto.c @@ -1493,7 +1493,7 @@ must_have_n_keys(const char *filename, const char *option, const struct key2 *ke #ifdef ENABLE_SMALL msg(M_FATAL, "Key file '%s' used in --%s contains insufficient key material [keys found=%d required=%d]", filename, option, key2->n, n); #else - msg(M_FATAL, "Key file '%s' used in --%s contains insufficient key material [keys found=%d required=%d] -- try generating a new key file with '" PACKAGE " --genkey --secret [file]', or use the existing key file in bidirectional mode by specifying --%s without a key direction parameter", filename, option, key2->n, n, option); + msg(M_FATAL, "Key file '%s' used in --%s contains insufficient key material [keys found=%d required=%d] -- try generating a new key file with '" PACKAGE " --genkey secret [file]', or use the existing key file in bidirectional mode by specifying --%s without a key direction parameter", filename, option, key2->n, n, option); #endif } } diff --git a/src/openvpn/init.c b/src/openvpn/init.c index 647f5336..87976290 100644 --- a/src/openvpn/init.c +++ b/src/openvpn/init.c @@ -1053,18 +1053,43 @@ 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) + + /* + * We do not want user to use --genkey with --secret. In the transistion + * phase we for secret. + */ + if (options->genkey && options->genkey_type != GENKEY_SECRET + && options->shared_secret_file) + { + msg(M_USAGE, "Using --genkey type with --secret filename is " + "not supported. Use --genkey type filename instead."); + } + if (options->genkey && options->genkey_type == GENKEY_SECRET) { int nbits_written; + const char *genkey_filename = options->genkey_filename; + if (options->shared_secret_file && options->genkey_filename) + { + msg(M_USAGE, "You must provide a filename to either --genkey " + "or --secret, not both"); + } - notnull(options->shared_secret_file, - "shared secret output file (--secret)"); + /* + * Copy filename from shared_secret_file to genkey_filename to support + * the old --genkey --secret foo.file syntax. + */ + if (options->shared_secret_file) + { + msg(M_WARN, "WARNING: Using --genkey --secret filename is " + "DEPRECATED. Use --genkey secret filename instead."); + genkey_filename = options->shared_secret_file; + } - nbits_written = write_key_file(2, 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 +1100,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 7ced4607..a37b7146 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"); @@ -7521,13 +7519,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]) @@ -8125,16 +8152,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 fb2d84a1..63f0f4cb 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; diff --git a/tests/t_lpback.sh b/tests/t_lpback.sh index 3b1e73a8..d8512896 100755 --- a/tests/t_lpback.sh +++ b/tests/t_lpback.sh @@ -38,7 +38,7 @@ CIPHERS=$(echo "$CIPHERS" | egrep -v '^(DES-EDE3-CFB1|DES-CFB1|RC5-)' ) # Also test cipher 'none' CIPHERS=${CIPHERS}$(printf "\nnone") -"${top_builddir}/src/openvpn/openvpn" --genkey --secret key.$$ +"${top_builddir}/src/openvpn/openvpn" --genkey secret key.$$ set +e e=0 @@ -57,7 +57,7 @@ done echo -n "Testing tls-crypt-v2 server key generation..." "${top_builddir}/src/openvpn/openvpn" \ - --tls-crypt-v2-genkey server tc-server-key.$$ >log.$$ 2>&1 + --genkey tls-crypt-v2-server tc-server-key.$$ >log.$$ 2>&1 if [ $? != 0 ] ; then echo "FAILED" cat log.$$ @@ -68,7 +68,7 @@ fi echo -n "Testing tls-crypt-v2 key generation (no metadata)..." "${top_builddir}/src/openvpn/openvpn" --tls-crypt-v2 tc-server-key.$$ \ - --tls-crypt-v2-genkey client tc-client-key.$$ >log.$$ 2>&1 + --genkey tls-crypt-v2-client tc-client-key.$$ >log.$$ 2>&1 if [ $? != 0 ] ; then echo "FAILED" cat log.$$ @@ -86,7 +86,7 @@ while [ $i -lt 732 ]; do done echo -n "Testing tls-crypt-v2 key generation (max length metadata)..." "${top_builddir}/src/openvpn/openvpn" --tls-crypt-v2 tc-server-key.$$ \ - --tls-crypt-v2-genkey client tc-client-key.$$ "${METADATA}" \ + --genkey tls-crypt-v2-client tc-client-key.$$ "${METADATA}" \ >log.$$ 2>&1 if [ $? != 0 ] ; then echo "FAILED" From patchwork Thu Jun 13 03:48:30 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Arne Schwabe X-Patchwork-Id: 745 X-Patchwork-Delegate: davids@openvpn.net Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director7.mail.ord1d.rsapps.net ([172.31.255.6]) by backend30.mail.ord1d.rsapps.net with LMTP id AJyXIY9UAl3SZwAAIUCqbw for ; Thu, 13 Jun 2019 09:50:07 -0400 Received: from proxy2.mail.iad3b.rsapps.net ([172.31.255.6]) by director7.mail.ord1d.rsapps.net with LMTP id qCGkHo9UAl3XbgAAovjBpQ ; Thu, 13 Jun 2019 09:50:07 -0400 Received: from smtp25.gate.iad3b ([172.31.255.6]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy2.mail.iad3b.rsapps.net with LMTP id yBDJF49UAl2RXwAAvAZTew ; Thu, 13 Jun 2019 09:50:07 -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: smtp25.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: 26d7dfee-8de2-11e9-95c3-52540030a522-1-1 Received: from [216.105.38.7] ([216.105.38.7:46068] helo=lists.sourceforge.net) by smtp25.gate.iad3b.rsapps.net (envelope-from ) (ecelerity 4.2.38.62370 r(:)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id 44/C7-06961-F84520D5; Thu, 13 Jun 2019 09:50:07 -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 1hbQ67-0004ZB-II; Thu, 13 Jun 2019 13:48:59 +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 1hbQ63-0004Yl-Mm for openvpn-devel@lists.sourceforge.net; Thu, 13 Jun 2019 13:48:55 +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=A80MV15cVPsN00fnPgT4Aw1b5aAOe795zyaFGj0sq04=; b=klTz2fpqYYVmpeYgK8wV/Rp1Yc e+/9voJgYxWNj7Mm+AGowK4aJEammCPmBzPrepKR+MFiwDAZvCP8DddXTOk9FL7tUlw/71x1zlnCQ FRpAqVaiycbO2Wjv3hpZKbUpsHMExpXVEido+IIiiDyaK1laMUAaJW50DL8tjmDcz/Fc=; 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=A80MV15cVPsN00fnPgT4Aw1b5aAOe795zyaFGj0sq04=; b=hI2zhEbC6u8TauUxIYeIX8PFav NHmqENDhMMRTP50DlxofANEPnk8AP/C5nZm6a5NtdP69FF40T8/Rcge/TXNfLKGk99EP+hKiwWYqc XXBIwnwihhi9tLLMbV9Nf1hYx05GxB1WuY6xO9kQDLK0Yn+bRGAiy3I/2Nxeb2RLWg6U=; Received: from [192.26.174.232] (helo=mail.blinkt.de) by sfi-mx-4.v28.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.90_1) id 1hbQ5z-00BZVJ-0O for openvpn-devel@lists.sourceforge.net; Thu, 13 Jun 2019 13:48:54 +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 1hbQ5i-000O15-C3; Thu, 13 Jun 2019 15:48:34 +0200 Received: (nullmailer pid 5757 invoked by uid 10006); Thu, 13 Jun 2019 13:48:34 -0000 From: Arne Schwabe To: openvpn-devel@lists.sourceforge.net Date: Thu, 13 Jun 2019 15:48:30 +0200 Message-Id: <20190613134834.5709-2-arne@rfc2549.org> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20190613134834.5709-1-arne@rfc2549.org> References: <20190613134834.5709-1-arne@rfc2549.org> X-Spam-Report: Spam Filtering performed by mx.sourceforge.net. See http://spamassassin.org/tag/ for more details. 0.2 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.0 SPF_NONE SPF: sender does not publish an SPF Record 0.0 SPF_HELO_NONE SPF: HELO does not publish an SPF Record 1.0 RDNS_NONE Delivered to internal network by a host with no rDNS X-Headers-End: 1hbQ5z-00BZVJ-0O Subject: [Openvpn-devel] [PATCH v4 3/7] Add generate_ephemeral_key that allows a random ephermal 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 Patch V4: Move wrapper functionality to caller and leave only generate epehermal key functionality in the new function Acked-By: David Sommerseth --- src/openvpn/crypto.c | 14 ++++++++++++++ src/openvpn/crypto.h | 12 +++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/openvpn/crypto.c b/src/openvpn/crypto.c index 9a150fa2..69877d1d 100644 --- a/src/openvpn/crypto.c +++ b/src/openvpn/crypto.c @@ -1892,6 +1892,20 @@ cleanup: return; } +bool +generate_ephemeral_key(struct buffer *key, const char *key_name) +{ + msg(M_INFO, "Using random %s.", key_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; +} + 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..72244997 100644 --- a/src/openvpn/crypto.h +++ b/src/openvpn/crypto.h @@ -428,7 +428,17 @@ unsigned int crypto_max_overhead(void); * @param pem_name The name to use in the PEM header/footer. */ void -write_pem_key_file(const char *filename, const char *pem_name); +write_pem_key_file(const char *filename, const char *key_name); + +/** + * Generate ephermal key material into the key structure or if + * + * @param key the key structure that will hold the key material + * @param pem_name the name used for logging + * @return true if key generation was successful + */ +bool +generate_ephemeral_key(struct buffer *key, const char *pem_name); /** * Read key material from a PEM encoded files into the key structure From patchwork Thu Jun 13 03:48:31 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Arne Schwabe X-Patchwork-Id: 744 X-Patchwork-Delegate: davids@openvpn.net Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director7.mail.ord1d.rsapps.net ([172.31.255.6]) by backend30.mail.ord1d.rsapps.net with LMTP id EMF5LYNUAl0oDQAAIUCqbw for ; Thu, 13 Jun 2019 09:49:55 -0400 Received: from proxy19.mail.iad3b.rsapps.net ([172.31.255.6]) by director7.mail.ord1d.rsapps.net with LMTP id eF19KoNUAl1CbwAAovjBpQ ; Thu, 13 Jun 2019 09:49:55 -0400 Received: from smtp2.gate.iad3b ([172.31.255.6]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy19.mail.iad3b.rsapps.net with LMTP id YFjnI4NUAl0CRgAAIG4riQ ; Thu, 13 Jun 2019 09:49:55 -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: smtp2.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: 1f5f60de-8de2-11e9-a924-5254000fbace-1-1 Received: from [216.105.38.7] ([216.105.38.7:45996] helo=lists.sourceforge.net) by smtp2.gate.iad3b.rsapps.net (envelope-from ) (ecelerity 4.2.38.62370 r(:)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id 1E/53-18201-284520D5; Thu, 13 Jun 2019 09:49:55 -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 1hbQ67-0004ZM-Oe; Thu, 13 Jun 2019 13:48:59 +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 1hbQ65-0004Z2-8A for openvpn-devel@lists.sourceforge.net; Thu, 13 Jun 2019 13:48:57 +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=NqBgo3bUJfi2XHZ2Rc5n8Ge/TZaqoIfS9Sya1euwG1I=; b=j1TwPHHHOPBem8Yv9sCjfojtU7 P4yNWEY+KWWtD9x6v7f+aY0vqfzFvq3b8ypk0TULg5qWAKnd/qqQA7olDJ0Ygcw5vga7kA2klXNTE IKcIoyxURxanO0Hy5PNa8gOgs/fpBLffo8UnSjQvJO3GYtqNnleighsnNowLCAu8cc3Q=; 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=NqBgo3bUJfi2XHZ2Rc5n8Ge/TZaqoIfS9Sya1euwG1I=; b=areH262+KjZ+ze2PhTI9P1Ijql KplGGBcZ3VAHBbg9ARhhERsVFShW5UEDQdeqBSZn69KzkdpnEWRsYIbN+5v51FYeNuQPVDVXLt7Un vEyjpO9SwisduN29X6vPCbwziTkMnc9f0YQcgV+AtpX+NDh7a2Ljqmbmdj0XG5k2onlU=; 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 1hbQ5y-0058lD-Rz for openvpn-devel@lists.sourceforge.net; Thu, 13 Jun 2019 13:48:55 +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 1hbQ5i-000O19-EI for openvpn-devel@lists.sourceforge.net; Thu, 13 Jun 2019 15:48:34 +0200 Received: (nullmailer pid 5760 invoked by uid 10006); Thu, 13 Jun 2019 13:48:34 -0000 From: Arne Schwabe To: openvpn-devel@lists.sourceforge.net Date: Thu, 13 Jun 2019 15:48:31 +0200 Message-Id: <20190613134834.5709-3-arne@rfc2549.org> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20190613134834.5709-1-arne@rfc2549.org> References: <20190613134834.5709-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.2 HEADER_FROM_DIFFERENT_DOMAINS From and EnvelopeFrom 2nd level mail domains are different 0.0 SPF_NONE SPF: sender does not publish an SPF Record 0.0 SPF_HELO_NONE SPF: HELO does not publish an SPF Record 1.0 RDNS_NONE Delivered to internal network by a host with no rDNS X-Headers-End: 1hbQ5y-0058lD-Rz Subject: [Openvpn-devel] [PATCH v4 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 Patch V4: Use ephermal_generate_key --- doc/openvpn.8 | 25 ++++ src/openvpn/Makefile.am | 1 + src/openvpn/auth_token.c | 272 +++++++++++++++++++++++++++++++++++++++ src/openvpn/auth_token.h | 116 +++++++++++++++++ src/openvpn/init.c | 30 ++++- 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, 639 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 25195fd4..cb63e218 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 30caa01f..3754fd95 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..0c515fce --- /dev/null +++ b/src/openvpn/auth_token.c @@ -0,0 +1,272 @@ +#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); + + bool key_loaded = false; + if (key_file) + { + key_loaded = read_pem_key_file(&server_secret_key, + auth_token_pem_name, + key_file, key_inline); + } + else + { + key_loaded = generate_ephemeral_key(&server_secret_key, + auth_token_pem_name); + } + + if (!key_loaded) + { + 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 87976290..3fe746ad 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" @@ -1118,6 +1119,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; @@ -2522,7 +2528,6 @@ init_crypto_pre(struct context *c, const unsigned int flags) rand_ctx_enable_prediction_resistance(); } #endif - } /* @@ -2646,6 +2651,22 @@ 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. @@ -2698,6 +2719,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) { @@ -2871,6 +2895,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; @@ -4486,6 +4511,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 fca33f25..7b01de16 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 a37b7146..b9e39157 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) @@ -6771,6 +6776,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); @@ -7546,6 +7562,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 63f0f4cb..d885f4d9 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 640808f9..73c07595 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 0312c1f8..5e19ab56 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; @@ -369,10 +374,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 @@ -540,7 +541,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; @@ -548,13 +563,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 Thu Jun 13 03:48:32 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Arne Schwabe X-Patchwork-Id: 743 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 WLQXLH1UAl24XQAAIUCqbw for ; Thu, 13 Jun 2019 09:49:49 -0400 Received: from proxy19.mail.iad3b.rsapps.net ([172.31.255.6]) by director11.mail.ord1d.rsapps.net with LMTP id mOLoKH1UAl1FawAAvGGmqA ; Thu, 13 Jun 2019 09:49:49 -0400 Received: from smtp15.gate.iad3b ([172.31.255.6]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy19.mail.iad3b.rsapps.net with LMTP id sMECIn1UAl0GRgAAIG4riQ ; Thu, 13 Jun 2019 09:49:49 -0400 X-Spam-Threshold: 95 X-Spam-Score: 0 X-Spam-Flag: NO X-Virus-Scanned: OK X-Orig-To: openvpnslackdevel@openvpn.net X-Originating-Ip: [216.105.38.7] Authentication-Results: smtp15.gate.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: 1bf05db8-8de2-11e9-ac95-5254003d6d3a-1-1 Received: from [216.105.38.7] ([216.105.38.7:60074] helo=lists.sourceforge.net) by smtp15.gate.iad3b.rsapps.net (envelope-from ) (ecelerity 4.2.38.62370 r(:)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id 6D/AA-06514-C74520D5; Thu, 13 Jun 2019 09:49:49 -0400 Received: from [127.0.0.1] (helo=sfs-ml-2.v29.lw.sourceforge.com) by sfs-ml-2.v29.lw.sourceforge.com with esmtp (Exim 4.90_1) (envelope-from ) id 1hbQ64-0004GP-K7; Thu, 13 Jun 2019 13:48:56 +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 1hbQ62-0004GC-Qr for openvpn-devel@lists.sourceforge.net; Thu, 13 Jun 2019 13:48:54 +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=4PV/q0533H86YW5YOewvt20VPb1LoJOdjfpIoganUZM=; b=YpRHf+c4ks63oIk6p05uOFvws/ WrDCEuWGp6bsOvCuNsV5LG/lRocVccEr4Mjpf1ALWprqlW5+ZP1dxoABVSmn2W0vEhWRKcMm2+nZ7 nxXBY4Vm4s3Ky+MeHOzk4Zh2eEBfYrh5GO4IqWdEonsZJ8Er/kz2N9R+R7zAfo+XRqLE=; 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=4PV/q0533H86YW5YOewvt20VPb1LoJOdjfpIoganUZM=; b=aTTx7ZyWPPQdtc4DZSz7HkpRg3 81pgwBsBfsnqnwuvexxmoZmEkdXO+wt5kcFzr481vFVGX3mmYROqGfu29ZBeR5wAn7M7h8tuMe9lE bMR/evbVDZ27gIkqqz3TpF5c39I79+jMQdt/I2avgFkKyZPpiYjsCPAG4gSfZe5B/Qug=; 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 1hbQ5z-0058lE-2k for openvpn-devel@lists.sourceforge.net; Thu, 13 Jun 2019 13:48:54 +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 1hbQ5i-000O1D-Gz; Thu, 13 Jun 2019 15:48:34 +0200 Received: (nullmailer pid 5763 invoked by uid 10006); Thu, 13 Jun 2019 13:48:34 -0000 From: Arne Schwabe To: openvpn-devel@lists.sourceforge.net Date: Thu, 13 Jun 2019 15:48:32 +0200 Message-Id: <20190613134834.5709-4-arne@rfc2549.org> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20190613134834.5709-1-arne@rfc2549.org> References: <20190613134834.5709-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.2 HEADER_FROM_DIFFERENT_DOMAINS From and EnvelopeFrom 2nd level mail domains are different 0.0 SPF_NONE SPF: sender does not publish an SPF Record 0.0 SPF_HELO_NONE SPF: HELO does not publish an SPF Record 1.0 RDNS_NONE Delivered to internal network by a host with no rDNS X-Headers-End: 1hbQ5z-0058lE-2k Subject: [Openvpn-devel] [PATCH v4 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' Patch V4: Rebase on current master --- 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 cb63e218..7ec287df 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 0c515fce..8387a7c8 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) { @@ -96,6 +179,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 @@ -108,27 +193,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))); @@ -147,12 +263,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) { @@ -180,9 +297,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)", @@ -192,8 +311,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))); @@ -204,6 +323,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)", @@ -246,7 +370,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 3fe746ad..5df0db15 100644 --- a/src/openvpn/init.c +++ b/src/openvpn/init.c @@ -2895,6 +2895,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 b9e39157..d2a37f11 100644 --- a/src/openvpn/options.c +++ b/src/openvpn/options.c @@ -6770,11 +6770,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 d885f4d9..8f722497 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 5e19ab56..047aa59b 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; @@ -470,7 +471,6 @@ struct tls_session */ #define KEY_SCAN_SIZE 3 - /** * Security parameter state for a single VPN tunnel. * @ingroup control_processor @@ -553,9 +553,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 Thu Jun 13 03:48:33 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Arne Schwabe X-Patchwork-Id: 747 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 iBITKpBUAl0oDQAAIUCqbw for ; Thu, 13 Jun 2019 09:50:08 -0400 Received: from proxy2.mail.iad3b.rsapps.net ([172.31.255.6]) by director11.mail.ord1d.rsapps.net with LMTP id kPziJpBUAl2taQAAvGGmqA ; Thu, 13 Jun 2019 09:50:08 -0400 Received: from smtp7.gate.iad3b ([172.31.255.6]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy2.mail.iad3b.rsapps.net with LMTP id EHrEH5BUAl13XwAAvAZTew ; Thu, 13 Jun 2019 09:50:08 -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: smtp7.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: 276e91e6-8de2-11e9-bb53-525400e292e5-1-1 Received: from [216.105.38.7] ([216.105.38.7:60136] helo=lists.sourceforge.net) by smtp7.gate.iad3b.rsapps.net (envelope-from ) (ecelerity 4.2.38.62370 r(:)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id 82/BB-32251-F84520D5; Thu, 13 Jun 2019 09:50:08 -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 1hbQ64-0004Ga-N9; Thu, 13 Jun 2019 13:48:56 +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 1hbQ63-0004GI-4k for openvpn-devel@lists.sourceforge.net; Thu, 13 Jun 2019 13:48:55 +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=Bs37d6E3Ssz30J4meiKEQvGrVjL34PI0XNdgiXqGrwg=; b=UzazPnS5yEhMXMKlo3W0ujW5iB j+NLRNrD/wRNDoUqaiJGy1zaOfcJa8LU11nPq4kYUbcRCc7uw0zlJSEQC+781ATH7l8Gixy0zr7Rt leIxns7eZTldqVr/nydmr7+SwpcNEIxw678Hk7hxqK3kzIqOcI0Wpd4IZMkY4pqESwi4=; 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=Bs37d6E3Ssz30J4meiKEQvGrVjL34PI0XNdgiXqGrwg=; b=SbZiul1z90z/Bk82i9LkElYe9f C/jiVuuNK8ZSkVyZpw9gulqfnx81JeCUfDES+0EkIQUVdyvvmG6JH8ZNlx11zZAo/rieoXIrZhINB CqE2rIAJJWDsTbGGkm4o18Kz/hhgHBld2+wF/WExObg9pS+hEWGnAakTmZAUNCrl2bIY=; Received: from [192.26.174.232] (helo=mail.blinkt.de) by sfi-mx-3.v28.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.90_1) id 1hbQ5z-00ByOT-27 for openvpn-devel@lists.sourceforge.net; Thu, 13 Jun 2019 13:48:55 +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 1hbQ5i-000O1K-J6; Thu, 13 Jun 2019 15:48:34 +0200 Received: (nullmailer pid 5766 invoked by uid 10006); Thu, 13 Jun 2019 13:48:34 -0000 From: Arne Schwabe To: openvpn-devel@lists.sourceforge.net Date: Thu, 13 Jun 2019 15:48:33 +0200 Message-Id: <20190613134834.5709-5-arne@rfc2549.org> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20190613134834.5709-1-arne@rfc2549.org> References: <20190613134834.5709-1-arne@rfc2549.org> X-Spam-Report: Spam Filtering performed by mx.sourceforge.net. See http://spamassassin.org/tag/ for more details. 0.2 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.0 SPF_NONE SPF: sender does not publish an SPF Record 0.0 SPF_HELO_NONE SPF: HELO does not publish an SPF Record 1.0 RDNS_NONE Delivered to internal network by a host with no rDNS X-Headers-End: 1hbQ5z-00ByOT-27 Subject: [Openvpn-devel] [PATCH v4 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); Patch V4: Rebase on master Acked-by: David Sommerseth --- 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 8387a7c8..6c287236 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"; @@ -368,6 +369,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 73c07595..b993df9f 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 047aa59b..aad6af2f 100644 --- a/src/openvpn/ssl_common.h +++ b/src/openvpn/ssl_common.h @@ -526,16 +526,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 Thu Jun 13 03:48:34 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Arne Schwabe X-Patchwork-Id: 746 X-Patchwork-Delegate: davids@openvpn.net Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director9.mail.ord1d.rsapps.net ([172.31.255.6]) by backend30.mail.ord1d.rsapps.net with LMTP id CKIUMo9UAl3ICAAAIUCqbw for ; Thu, 13 Jun 2019 09:50:07 -0400 Received: from proxy4.mail.iad3b.rsapps.net ([172.31.255.6]) by director9.mail.ord1d.rsapps.net with LMTP id mAITL49UAl3OYQAAalYnBA ; Thu, 13 Jun 2019 09:50:07 -0400 Received: from smtp24.gate.iad3b ([172.31.255.6]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy4.mail.iad3b.rsapps.net with LMTP id WDgaKI9UAl1CQAAA9crAow ; Thu, 13 Jun 2019 09:50:07 -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: 26f15e74-8de2-11e9-9c33-525400892b35-1-1 Received: from [216.105.38.7] ([216.105.38.7:57004] 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 4C/E1-22065-F84520D5; Thu, 13 Jun 2019 09:50:07 -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 1hbQ64-0000Me-CU; Thu, 13 Jun 2019 13:48:56 +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 1hbQ63-0000ML-Gd for openvpn-devel@lists.sourceforge.net; Thu, 13 Jun 2019 13:48:55 +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=guM5vPCQdZkL3xIeW0Z+uPyU9XKyiEDh78aj1yx2QHw=; b=A+49wt2oDPngGKXOQBlXlQPnTt FDTKpNMJcte1YsFIfhKN4xpE8cyPhwifW3gwVArVzO7iaVl9TeZ300R7ATtbBmWC0RWJGR/C3Vjlo 7R0ylxDlvQToxs0XLtqX7f3gu9Jeb94rwalPROOLyEiSsjqQ4pz4YElOke6KyJUxOR0k=; 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=guM5vPCQdZkL3xIeW0Z+uPyU9XKyiEDh78aj1yx2QHw=; b=QQ/TKmz/8DsWcLZ+N54GaxfDca LsW6mdYVselY82I59zJT3+TuHSAdvnxzaNo0pgINgA9o8RAW5/0j8aLhvN5VldweclUafeMoG7Ys/ GCv1BeKE7vuJfMoG3HJHslY2QZL6wPYVvxaUkFAoi3oM5oNAEyN9r2GZrgKZzwsYiMdo=; Received: from [192.26.174.232] (helo=mail.blinkt.de) by sfi-mx-4.v28.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.90_1) id 1hbQ5z-00BZVL-3n for openvpn-devel@lists.sourceforge.net; Thu, 13 Jun 2019 13:48:54 +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 1hbQ5i-000O1P-LM; Thu, 13 Jun 2019 15:48:34 +0200 Received: (nullmailer pid 5769 invoked by uid 10006); Thu, 13 Jun 2019 13:48:34 -0000 From: Arne Schwabe To: openvpn-devel@lists.sourceforge.net Date: Thu, 13 Jun 2019 15:48:34 +0200 Message-Id: <20190613134834.5709-6-arne@rfc2549.org> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20190613134834.5709-1-arne@rfc2549.org> References: <20190613134834.5709-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.2 HEADER_FROM_DIFFERENT_DOMAINS From and EnvelopeFrom 2nd level mail domains are different 0.0 SPF_NONE SPF: sender does not publish an SPF Record 0.0 SPF_HELO_NONE SPF: HELO does not publish an SPF Record 1.0 RDNS_NONE Delivered to internal network by a host with no rDNS X-Headers-End: 1hbQ5z-00BZVL-3n Subject: [Openvpn-devel] [PATCH v4 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 Patch V4: Resolve rebase conflicts --- tests/unit_tests/openvpn/Makefile.am | 20 +- tests/unit_tests/openvpn/test_auth_token.c | 375 +++++++++++++++++++++ 2 files changed, 394 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 c6da91a8..e941f488 100644 --- a/tests/unit_tests/openvpn/Makefile.am +++ b/tests/unit_tests/openvpn/Makefile.am @@ -6,7 +6,7 @@ if HAVE_LD_WRAP_SUPPORT test_binaries += argv_testdriver buffer_testdriver endif -test_binaries += crypto_testdriver packet_id_testdriver +test_binaries += crypto_testdriver packet_id_testdriver auth_token_testdriver if HAVE_LD_WRAP_SUPPORT test_binaries += tls_crypt_testdriver endif @@ -92,3 +92,21 @@ networking_testdriver_SOURCES = test_networking.c mock_msg.c \ $(openvpn_srcdir)/otime.c \ $(openvpn_srcdir)/packet_id.c \ $(openvpn_srcdir)/platform.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)/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) +{ + +}