From patchwork Mon Jan 14 04:48:14 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Arne Schwabe X-Patchwork-Id: 660 Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director12.mail.ord1d.rsapps.net ([172.27.255.57]) by backend30.mail.ord1d.rsapps.net with LMTP id WFSkLGC5PFwRZQAAIUCqbw for ; Mon, 14 Jan 2019 11:31:28 -0500 Received: from proxy7.mail.iad3a.rsapps.net ([172.27.255.57]) by director12.mail.ord1d.rsapps.net with LMTP id KBo+KmC5PFxcFQAAIasKDg ; Mon, 14 Jan 2019 11:31:28 -0500 Received: from smtp7.gate.iad3a ([172.27.255.57]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy7.mail.iad3a.rsapps.net with LMTP id sIZiJWC5PFxXMgAAnPvY+A ; Mon, 14 Jan 2019 11:31:28 -0500 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.iad3a.rsapps.net; iprev=pass policy.iprev="216.105.38.7"; spf=pass smtp.mailfrom="openvpn-devel-bounces@lists.sourceforge.net" smtp.helo="lists.sourceforge.net"; dkim=fail (signature verification failed) header.d=sourceforge.net; dkim=fail (signature verification failed) header.d=sf.net; dmarc=none (p=nil; dis=none) header.from=rfc2549.org X-Suspicious-Flag: YES X-Classification-ID: d6fdb9be-1819-11e9-9ed1-525400bbebb8-1-1 Received: from [216.105.38.7] ([216.105.38.7:32795] helo=lists.sourceforge.net) by smtp7.gate.iad3a.rsapps.net (envelope-from ) (ecelerity 4.2.38.62370 r(:)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id 5C/AC-08319-F59BC3C5; Mon, 14 Jan 2019 11:31:27 -0500 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 1gj58b-0006Tz-23; Mon, 14 Jan 2019 16:30:57 +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 1gj58a-0006Tm-1P for openvpn-devel@lists.sourceforge.NET; Mon, 14 Jan 2019 16:30: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: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:In-Reply-To:References:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=ULpd+qmftjGeVIR72xx6RpzIFlPm8l8s1ER7O1QdQnE=; b=C3Qo0bEKZn6KdX4ZtE2Wc3D+Bq SS0czKoXznxDwcMYPFzDv4Mdm0mrcQDEl6U4NwcllhRKzeE81dSNjNmBq80wuR53deVflIBzZkA0W X9OlLla6zHlhwIlHZ1LOq3XgknuphEjtMtyBoZKXFTn+TaX+UBaS+pt0vbAcDSi/ah+8=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=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: In-Reply-To:References:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=ULpd+qmftjGeVIR72xx6RpzIFlPm8l8s1ER7O1QdQnE=; b=A0AN3GWnuE+iGangPRek9flIGk MXkGOlFsrsazycwRvd+PYaLYgHphaUy+hlM7vx9y0UNQUThGW+lgSDDjgfciOgQQ9KvkbOwI5Ar76 HeoPBk45N/wdsUPYaDb9aKVdJWxfGUQPpSrakgZCevoXBZubmLaiwMBU3k5WqooVaxUg=; Received: from mail.blinkt.de ([192.26.174.232]) by sfi-mx-3.v28.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.90_1) id 1gj58Y-001TlY-Ma for openvpn-devel@lists.sourceforge.NET; Mon, 14 Jan 2019 16:30: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 1gj4TL-000MiN-Ht; Mon, 14 Jan 2019 16:48:19 +0100 Received: (nullmailer pid 6109 invoked by uid 10006); Mon, 14 Jan 2019 15:48:19 -0000 From: Arne Schwabe To: openvpn-devel@lists.sourceforge.net Date: Mon, 14 Jan 2019 16:48:14 +0100 Message-Id: <20190114154819.6064-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 HEADER_FROM_DIFFERENT_DOMAINS From and EnvelopeFrom 2nd level mail domains are different X-Headers-End: 1gj58Y-001TlY-Ma Subject: [Openvpn-devel] [PATCH 1/6] Fix loading inline tls-crypt-v2 keys with mbed TLS 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 Using a tls-crypt-v2 key with mbed TLS inline results in PEM decode error: source buffer not null-terminated This is because the mbed TLS decode PEM function excepts the last byte in the buffer to be 0x00. When constructing the buffer we only made as big as strlen, which does not include the 0x00 byte of a string. Add an extra byte to ensure also the null byte is included in the buffer. Acked-by: Steffan Karger --- src/openvpn/tls_crypt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/openvpn/tls_crypt.c b/src/openvpn/tls_crypt.c index 85495d7f..6bc2b7f8 100644 --- a/src/openvpn/tls_crypt.c +++ b/src/openvpn/tls_crypt.c @@ -298,7 +298,7 @@ tls_crypt_v2_read_keyfile(struct buffer *key, const char *pem_name, } else { - buf_set_read(&key_pem, (const void *)key_inline, strlen(key_inline)); + buf_set_read(&key_pem, (const void *)key_inline, strlen(key_inline) + 1); } if (!crypto_pem_decode(pem_name, key, &key_pem)) From patchwork Mon Jan 14 04:48:15 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Arne Schwabe X-Patchwork-Id: 658 Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director7.mail.ord1d.rsapps.net ([172.27.255.52]) by backend30.mail.ord1d.rsapps.net with LMTP id UND/JVe5PFxvRwAAIUCqbw for ; Mon, 14 Jan 2019 11:31:19 -0500 Received: from proxy4.mail.iad3a.rsapps.net ([172.27.255.52]) by director7.mail.ord1d.rsapps.net with LMTP id 0P2CIle5PFxOLgAAovjBpQ ; Mon, 14 Jan 2019 11:31:19 -0500 Received: from smtp9.gate.iad3a ([172.27.255.52]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy4.mail.iad3a.rsapps.net with LMTP id 2IsmG1e5PFzlHgAA8Zvu4w ; Mon, 14 Jan 2019 11:31:19 -0500 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: smtp9.gate.iad3a.rsapps.net; iprev=pass policy.iprev="216.105.38.7"; spf=pass smtp.mailfrom="openvpn-devel-bounces@lists.sourceforge.net" smtp.helo="lists.sourceforge.net"; dkim=fail (signature verification failed) header.d=sourceforge.net; dkim=fail (signature verification failed) header.d=sf.net; dmarc=none (p=nil; dis=none) header.from=rfc2549.org X-Suspicious-Flag: YES X-Classification-ID: d1673958-1819-11e9-b63c-52540097fc8c-1-1 Received: from [216.105.38.7] ([216.105.38.7:1997] helo=lists.sourceforge.net) by smtp9.gate.iad3a.rsapps.net (envelope-from ) (ecelerity 4.2.38.62370 r(:)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id 0F/85-32398-659BC3C5; Mon, 14 Jan 2019 11:31:18 -0500 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 1gj58Q-0006Sx-VP; Mon, 14 Jan 2019 16:30:46 +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 1gj58Q-0006Sr-BO for openvpn-devel@lists.sourceforge.NET; Mon, 14 Jan 2019 16:30:46 +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=Yr1P5d1IPx3y4u8MOl3P/ePc5HmoD4HgHwW1154SINA=; b=Z0Qia9tAuwOLpAKHEZLmAO0RPH UrP+xe4OGVvVk/1iQmWMTtRGEdsELSMV/wBaT8jC4ihyx4Pb7jysGYq9dZvZnoB/rcBBQene9f89H aZYGYTEtSo+kfiMeiYDbb7rhcyAz2kwrfkXKttlWvVmDzpep7pzpsHsOK2Eby1KajuuY=; 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=Yr1P5d1IPx3y4u8MOl3P/ePc5HmoD4HgHwW1154SINA=; b=CV9ClQb3vyzNCfQzalnG57giaR q8sc11+M+fvoHSnI4oWzv3hCPsTWYVOUIpyHgyAntZx2tPomZLNqcVuCRrBNE+QZRgC6qpfYJ290w 2ci6u+iV3oSlVLylDOpv2Cr8i/yEZ+LWjRMWPkduN5TPNITAL1RdQaxKtvJR2OARkw/8=; Received: from mail.blinkt.de ([192.26.174.232]) by sfi-mx-4.v28.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.90_1) id 1gj58M-001qoX-Oi for openvpn-devel@lists.sourceforge.NET; Mon, 14 Jan 2019 16:30:46 +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 1gj4TL-000MiQ-Jf; Mon, 14 Jan 2019 16:48:19 +0100 Received: (nullmailer pid 6112 invoked by uid 10006); Mon, 14 Jan 2019 15:48:19 -0000 From: Arne Schwabe To: openvpn-devel@lists.sourceforge.net Date: Mon, 14 Jan 2019 16:48:15 +0100 Message-Id: <20190114154819.6064-2-arne@rfc2549.org> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20190114154819.6064-1-arne@rfc2549.org> References: <20190114154819.6064-1-arne@rfc2549.org> X-Spam-Report: Spam Filtering performed by mx.sourceforge.net. See http://spamassassin.org/tag/ for more details. 0.0 HEADER_FROM_DIFFERENT_DOMAINS From and EnvelopeFrom 2nd level mail domains are different X-Headers-End: 1gj58M-001qoX-Oi Subject: [Openvpn-devel] [PATCH 2/6] Refactor tls_crypt_v2_write_server_key_file into crypto.c X-BeenThere: openvpn-devel@lists.sourceforge.net X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , 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 the method to be resued for generating other types of keys that should also not be reused as tls-crypt/tls-auth keys. Acked-by: Steffan Karger --- src/openvpn/crypto.c | 34 ++++++++++++++++++++++++++++++++++ src/openvpn/crypto.h | 10 ++++++++++ src/openvpn/tls_crypt.c | 30 +----------------------------- 3 files changed, 45 insertions(+), 29 deletions(-) diff --git a/src/openvpn/crypto.c b/src/openvpn/crypto.c index df6f36ca..19136799 100644 --- a/src/openvpn/crypto.c +++ b/src/openvpn/crypto.c @@ -1848,3 +1848,37 @@ translate_cipher_name_to_openvpn(const char *cipher_name) return pair->openvpn_name; } + +void +write_pem_key_file(const char *filename, const char *pem_name) +{ + struct gc_arena gc = gc_new(); + struct key server_key = { 0 }; + struct buffer server_key_buf = clear_buf(); + struct buffer server_key_pem = clear_buf(); + + if (!rand_bytes((void *)&server_key, sizeof(server_key))) + { + msg(M_NONFATAL, "ERROR: could not generate random key"); + goto cleanup; + } + buf_set_read(&server_key_buf, (void *)&server_key, sizeof(server_key)); + if (!crypto_pem_encode(pem_name, &server_key_pem, + &server_key_buf, &gc)) + { + msg(M_WARN, "ERROR: could not PEM-encode key"); + goto cleanup; + } + + if (!buffer_write_file(filename, &server_key_pem)) + { + msg(M_ERR, "ERROR: could not write key file"); + goto cleanup; + } + +cleanup: + secure_memzero(&server_key, sizeof(server_key)); + buf_clear(&server_key_pem); + gc_free(&gc); + return; +} diff --git a/src/openvpn/crypto.h b/src/openvpn/crypto.h index 1edde2e3..c0574ff6 100644 --- a/src/openvpn/crypto.h +++ b/src/openvpn/crypto.h @@ -420,6 +420,16 @@ void crypto_adjust_frame_parameters(struct frame *frame, /** Return the worst-case OpenVPN crypto overhead (in bytes) */ unsigned int crypto_max_overhead(void); +/** + * Generate a server key with enough randomness to fill a key struct + * and write to file. + * + * @param filename Filename of the server key file to create. + * @param pem_name The name to use in the PEM header/footer. + */ +void +write_pem_key_file(const char *filename, const char *pem_name); + /* Minimum length of the nonce used by the PRNG */ #define NONCE_SECRET_LEN_MIN 16 diff --git a/src/openvpn/tls_crypt.c b/src/openvpn/tls_crypt.c index 6bc2b7f8..eeac794b 100644 --- a/src/openvpn/tls_crypt.c +++ b/src/openvpn/tls_crypt.c @@ -670,35 +670,7 @@ tls_crypt_v2_extract_client_key(struct buffer *buf, void tls_crypt_v2_write_server_key_file(const char *filename) { - struct gc_arena gc = gc_new(); - struct key server_key = { 0 }; - struct buffer server_key_buf = clear_buf(); - struct buffer server_key_pem = clear_buf(); - - if (!rand_bytes((void *)&server_key, sizeof(server_key))) - { - msg(M_NONFATAL, "ERROR: could not generate random key"); - goto cleanup; - } - buf_set_read(&server_key_buf, (void *)&server_key, sizeof(server_key)); - if (!crypto_pem_encode(tls_crypt_v2_srv_pem_name, &server_key_pem, - &server_key_buf, &gc)) - { - msg(M_WARN, "ERROR: could not PEM-encode server key"); - goto cleanup; - } - - if (!buffer_write_file(filename, &server_key_pem)) - { - msg(M_ERR, "ERROR: could not write server key file"); - goto cleanup; - } - -cleanup: - secure_memzero(&server_key, sizeof(server_key)); - buf_clear(&server_key_pem); - gc_free(&gc); - return; + write_pem_key_file(filename, tls_crypt_v2_srv_pem_name); } void From patchwork Mon Jan 14 04:48:16 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Arne Schwabe X-Patchwork-Id: 661 Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director11.mail.ord1d.rsapps.net ([172.27.255.54]) by backend30.mail.ord1d.rsapps.net with LMTP id CNhNJ3W5PFyeMwAAIUCqbw for ; Mon, 14 Jan 2019 11:31:49 -0500 Received: from proxy3.mail.iad3a.rsapps.net ([172.27.255.54]) by director11.mail.ord1d.rsapps.net with LMTP id MPIcJXW5PFxmPQAAvGGmqA ; Mon, 14 Jan 2019 11:31:49 -0500 Received: from smtp23.gate.iad3a ([172.27.255.54]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy3.mail.iad3a.rsapps.net with LMTP id uAcRIHW5PFxOdwAAYaqY3Q ; Mon, 14 Jan 2019 11:31:49 -0500 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: smtp23.gate.iad3a.rsapps.net; iprev=pass policy.iprev="216.105.38.7"; spf=pass smtp.mailfrom="openvpn-devel-bounces@lists.sourceforge.net" smtp.helo="lists.sourceforge.net"; dkim=fail (signature verification failed) header.d=sourceforge.net; dkim=fail (signature verification failed) header.d=sf.net; dmarc=none (p=nil; dis=none) header.from=rfc2549.org X-Suspicious-Flag: YES X-Classification-ID: e3cab8b8-1819-11e9-8b08-52540033eb40-1-1 Received: from [216.105.38.7] ([216.105.38.7:21847] helo=lists.sourceforge.net) by smtp23.gate.iad3a.rsapps.net (envelope-from ) (ecelerity 4.2.38.62370 r(:)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id F2/5F-32171-579BC3C5; Mon, 14 Jan 2019 11:31:49 -0500 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 1gj58j-0004nF-Io; Mon, 14 Jan 2019 16:31:05 +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 1gj58i-0004n0-0h for openvpn-devel@lists.sourceforge.NET; Mon, 14 Jan 2019 16:31:04 +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=aRIOdB8MJSldEbwkIkdwhvGQ/xtQ16UJISJ+xhUY/10=; b=hA176GmeqSS6+dTQSVlbkyTRok cQm6mnYvqG9tBoTiH3QX66doKnix6KBHd9SE+X000oiAnhRV9jDfm9CYuaTHxqknHUUfzR/vBLwDq oNuaWShqeAV1rlrns6jB5lpCBJVUhK+gdN1Slx3izjMaNxblO83bUePlTMgg6IKqDO4Q=; 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=aRIOdB8MJSldEbwkIkdwhvGQ/xtQ16UJISJ+xhUY/10=; b=S1y2ViwSoRWtHWf5In33IV+9cN SAB6XAC9LA1YJU6v6UhkLSAobiN9NT0LL50oMIkHXX737A6vvrr5gCbjJQQTNzpl4vTXtQ01kc8wD iyqmmKCv628NYz3KHmzH7d8V5z9zDcG4fVZvS5fSFEnj45mGapjlDOxdVF3PD338//z8=; Received: from mail.blinkt.de ([192.26.174.232]) by sfi-mx-1.v28.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.90_1) id 1gj58g-0045JC-L1 for openvpn-devel@lists.sourceforge.NET; Mon, 14 Jan 2019 16:31:03 +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 1gj4TL-000MiS-KU; Mon, 14 Jan 2019 16:48:19 +0100 Received: (nullmailer pid 6114 invoked by uid 10006); Mon, 14 Jan 2019 15:48:19 -0000 From: Arne Schwabe To: openvpn-devel@lists.sourceforge.net Date: Mon, 14 Jan 2019 16:48:16 +0100 Message-Id: <20190114154819.6064-3-arne@rfc2549.org> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20190114154819.6064-1-arne@rfc2549.org> References: <20190114154819.6064-1-arne@rfc2549.org> X-Spam-Report: Spam Filtering performed by mx.sourceforge.net. See http://spamassassin.org/tag/ for more details. 0.0 HEADER_FROM_DIFFERENT_DOMAINS From and EnvelopeFrom 2nd level mail domains are different X-Headers-End: 1gj58g-0045JC-L1 Subject: [Openvpn-devel] [PATCH 3/6] Add send_control_channel_string_dowork variant 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 The send_control_channel_string_dowork variant does not schedule the sending of the actual and can be used where struct context is not available. Acked-by: Gert Doering --- src/openvpn/forward.c | 43 +++++++++++++++++++++++-------------------- src/openvpn/forward.h | 31 ++++++++++++++++++++++++++++++- 2 files changed, 53 insertions(+), 21 deletions(-) diff --git a/src/openvpn/forward.c b/src/openvpn/forward.c index 0a90fff0..4076f647 100644 --- a/src/openvpn/forward.c +++ b/src/openvpn/forward.c @@ -464,42 +464,45 @@ check_connection_established_dowork(struct context *c) } } -/* - * Send a string to remote over the TLS control channel. - * Used for push/pull messages, passing username/password, - * etc. - */ +bool +send_control_channel_string_dowork(struct tls_multi *multi, + const char *str, int msglevel) +{ + struct gc_arena gc = gc_new(); + bool stat; + + /* buffered cleartext write onto TLS control channel */ + stat = tls_send_payload(multi, (uint8_t *) str, strlen(str) + 1); + + msg(msglevel, "SENT CONTROL [%s]: '%s' (status=%d)", + tls_common_name(multi, false), + sanitize_control_message(str, &gc), + (int) stat); + + gc_free(&gc); + return stat; +} + bool send_control_channel_string(struct context *c, const char *str, int msglevel) { if (c->c2.tls_multi) { - struct gc_arena gc = gc_new(); - bool stat; - - /* buffered cleartext write onto TLS control channel */ - stat = tls_send_payload(c->c2.tls_multi, (uint8_t *) str, strlen(str) + 1); - + bool ret = send_control_channel_string_dowork(c->c2.tls_multi, + str, msglevel); /* * Reschedule tls_multi_process. * NOTE: in multi-client mode, usually the below two statements are * insufficient to reschedule the client instance object unless * multi_schedule_context_wakeup(m, mi) is also called. */ + interval_action(&c->c2.tmp_int); context_immediate_reschedule(c); /* ZERO-TIMEOUT */ - - msg(msglevel, "SENT CONTROL [%s]: '%s' (status=%d)", - tls_common_name(c->c2.tls_multi, false), - sanitize_control_message(str, &gc), - (int) stat); - - gc_free(&gc); - return stat; + return ret; } return true; } - /* * Add routes. */ diff --git a/src/openvpn/forward.h b/src/openvpn/forward.h index f97b0e2e..48202c07 100644 --- a/src/openvpn/forward.h +++ b/src/openvpn/forward.h @@ -286,7 +286,36 @@ void process_outgoing_tun(struct context *c); /**************************************************************************/ -bool send_control_channel_string(struct context *c, const char *str, int msglevel); +/* + * Send a string to remote over the TLS control channel. + * Used for push/pull messages, passing username/password, + * etc. + * @param c - The context structure of the VPN tunnel associated with + * the packet. + * @param str - The message to be sent + * @param msglevel - Message level to use for logging + */ +bool +send_control_channel_string(struct context *c, const char *str, int msglevel); + +/* + * Send a string to remote over the TLS control channel. + * Used for push/pull messages, passing username/password, + * etc. + * + * This variant does not schedule the actual sending of the message + * The caller needs to ensure that it is scheduled or call + * send_control_channel_string + * + * @param multi - The tls_multi structure of the VPN tunnel associated + * with the packet. + * @param str - The message to be sent + * @param msglevel - Message level to use for logging + */ + +bool +send_control_channel_string_dowork(struct tls_multi *multi, + const char *str, int msglevel); #define PIPV4_PASSTOS (1<<0) #define PIP_MSSFIX (1<<1) /* v4 and v6 */ From patchwork Mon Jan 14 04:48:17 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Arne Schwabe X-Patchwork-Id: 659 Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director8.mail.ord1d.rsapps.net ([172.27.255.7]) by backend30.mail.ord1d.rsapps.net with LMTP id oFZcB1m5PFwVWQAAIUCqbw for ; Mon, 14 Jan 2019 11:31:21 -0500 Received: from proxy19.mail.iad3a.rsapps.net ([172.27.255.7]) by director8.mail.ord1d.rsapps.net with LMTP id iAB9BFm5PFwLKgAAfY0hYg ; Mon, 14 Jan 2019 11:31:21 -0500 Received: from smtp12.gate.iad3a ([172.27.255.7]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy19.mail.iad3a.rsapps.net with LMTP id 2C1MOli5PFyZHgAAXy6Yeg ; Mon, 14 Jan 2019 11:31:20 -0500 X-Spam-Threshold: 95 X-Spam-Score: 0 X-Spam-Flag: NO X-Virus-Scanned: OK X-Orig-To: openvpnslackdevel@openvpn.net X-Originating-Ip: [216.105.38.7] Authentication-Results: smtp12.gate.iad3a.rsapps.net; iprev=pass policy.iprev="216.105.38.7"; spf=pass smtp.mailfrom="openvpn-devel-bounces@lists.sourceforge.net" smtp.helo="lists.sourceforge.net"; dkim=fail (signature verification failed) header.d=sourceforge.net; dkim=fail (signature verification failed) header.d=sf.net; dmarc=none (p=nil; dis=none) header.from=rfc2549.org X-Suspicious-Flag: YES X-Classification-ID: d25d7b56-1819-11e9-b674-525400068c1c-1-1 Received: from [216.105.38.7] ([216.105.38.7:58659] helo=lists.sourceforge.net) by smtp12.gate.iad3a.rsapps.net (envelope-from ) (ecelerity 4.2.38.62370 r(:)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id D4/D3-07916-759BC3C5; Mon, 14 Jan 2019 11:31:20 -0500 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 1gj58H-0004ks-8J; Mon, 14 Jan 2019 16:30:37 +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 1gj58G-0004kf-3C for openvpn-devel@lists.sourceforge.NET; Mon, 14 Jan 2019 16:30:36 +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=9Y4Lh7Z74+J0RYcLDHIT8/zbk0Ony3np1/o4Nwn4kDQ=; b=KuQs9YPCcIN3DEWS2IVcOFX2uw vIxEZSRFxoS/yI9fZOQvydeb+X9yYk/P4lbT1Mtz/Cgi7/2++O1LkBydSBh8z+/nD+fAwWZ0SUJ6W dFwJ+Wv5hO97WUfEr00SjzskFk93siTjJgyQVzKVdItoLTzoRnrZqMRo8EeR97tY7JCY=; 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=9Y4Lh7Z74+J0RYcLDHIT8/zbk0Ony3np1/o4Nwn4kDQ=; b=NC5lhxA9GNafWBldP1nFK+rj71 qSiTdpEtWALU+kECgAeKJQnrZVWJPclFvyzDEv2lNHqrGz1ULSxfBQ7ICnsPVCY7MEO27mJWPOAOW 84lA9zp9ZktxhVVI20/uCfkdDkTduBxCo0UowpjRm2lA17RNTlG/AfKRCP+6TvArysLc=; Received: from mail.blinkt.de ([192.26.174.232]) by sfi-mx-4.v28.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.90_1) id 1gj58B-001qnU-L3 for openvpn-devel@lists.sourceforge.NET; Mon, 14 Jan 2019 16:30:35 +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 1gj4TL-000MiW-Mz for openvpn-devel@lists.sourceforge.net; Mon, 14 Jan 2019 16:48:19 +0100 Received: (nullmailer pid 6118 invoked by uid 10006); Mon, 14 Jan 2019 15:48:19 -0000 From: Arne Schwabe To: openvpn-devel@lists.sourceforge.net Date: Mon, 14 Jan 2019 16:48:17 +0100 Message-Id: <20190114154819.6064-4-arne@rfc2549.org> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20190114154819.6064-1-arne@rfc2549.org> References: <20190114154819.6064-1-arne@rfc2549.org> X-Spam-Report: Spam Filtering performed by mx.sourceforge.net. See http://spamassassin.org/tag/ for more details. 0.0 HEADER_FROM_DIFFERENT_DOMAINS From and EnvelopeFrom 2nd level mail domains are different X-Headers-End: 1gj58B-001qnU-L3 Subject: [Openvpn-devel] [PATCH 4/6] 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. --- src/openvpn/Makefile.am | 1 + src/openvpn/auth_token.c | 333 +++++++++++++++++++++++++++++++++++++++ src/openvpn/auth_token.h | 101 ++++++++++++ src/openvpn/init.c | 34 +++- src/openvpn/openvpn.h | 1 + src/openvpn/options.c | 24 ++- 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 | 30 ++-- src/openvpn/ssl_verify.c | 74 ++------- 12 files changed, 594 insertions(+), 93 deletions(-) create mode 100644 src/openvpn/auth_token.c create mode 100644 src/openvpn/auth_token.h diff --git a/src/openvpn/Makefile.am b/src/openvpn/Makefile.am index 197e62ba..78f94762 100644 --- a/src/openvpn/Makefile.am +++ b/src/openvpn/Makefile.am @@ -39,6 +39,7 @@ sbin_PROGRAMS = openvpn openvpn_SOURCES = \ argv.c argv.h \ + auth_token.c auth_token.h \ base64.c base64.h \ basic.h \ buffer.c buffer.h \ diff --git a/src/openvpn/auth_token.c b/src/openvpn/auth_token.c new file mode 100644 index 00000000..5ba31042 --- /dev/null +++ b/src/openvpn/auth_token.c @@ -0,0 +1,333 @@ +#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" + +const char *auth_token_pem_name = "OpenVPN auth-token server key"; + +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); +} + +static inline bool +auth_token_load_key(struct buffer *key, const char *key_file, + const char *key_inline) +{ + struct buffer key_pem = { 0 }; + struct gc_arena gc = gc_new(); + bool ret = false; + /* + * User did not specify a permanent key file, generate key data + * on the fly + */ + if (key_file == NULL) + { + msg(M_INFO, "Using random auth-token HMAC key."); + uint8_t rand[BCAP(key)]; + if (!rand_bytes(rand, BCAP(key))) + { + msg(M_WARN, "ERROR: could not generate random key"); + } + else + { + buf_write(key, rand, BCAP(key)); + ret = true; + } + goto cleanup; + } + else if (strcmp(key_file, INLINE_FILE_TAG)) + { + key_pem = buffer_read_from_file(key_file, &gc); + if (!buf_valid(&key_pem)) + { + msg(M_WARN, "ERROR: failed to read auth-token-secret file (%s)", + key_file); + goto cleanup; + } + } + else + { + buf_set_read(&key_pem, (const void *)key_inline, strlen(key_inline) +1); + } + + if (!crypto_pem_decode(auth_token_pem_name, key, &key_pem)) + { + msg(M_WARN, "ERROR: auth-token pem decode failed"); + goto cleanup; + } + ret = true; +cleanup: + if (key_file && strcmp(key_file, INLINE_FILE_TAG)) + { + buf_clear(&key_pem); + } + gc_free(&gc); + return ret; +} + +void +auth_token_init_secret(struct key_ctx *key_ctx, const char *key_file, + const char *key_inline) +{ + struct key_type kt = auth_token_kt(); + + struct buffer server_secret_key = alloc_buf(2048); + + if (!auth_token_load_key(&server_secret_key, key_file, key_inline)) + { + msg(M_FATAL, "ERROR: Cannot load auth-token secret"); + } + + struct key key; + + if (!buf_read(&server_secret_key, &key, sizeof(key))) + { + msg(M_FATAL, "ERROR: not enough data in auth-token secret"); + } + init_key_ctx(key_ctx, &key, &kt, false, "auth-token secret"); + + free_buf(&server_secret_key); +} + +void +generate_auth_token(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); +} + +bool +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 + */ + char 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 */ + int expected_len = 2 * sizeof(int64_t) + 32; + + if (decoded_len != expected_len) + { + msg(M_WARN, "ERROR: --auth-token wrong size (%d!=%d)", decoded_len, expected_len); + return false; + } + + hmac_ctx_t *ctx = multi->opt.auth_token_key.hmac; + ASSERT(hmac_ctx_size(ctx) == 256/8); + + hmac_ctx_reset(ctx); + hmac_ctx_update(ctx, (uint8_t *) up->username, strlen(up->username)); + hmac_ctx_update(ctx, (uint8_t *) b64decoded, sizeof(int64_t)); + + uint8_t hmac_output[256/8]; + hmac_ctx_final(ctx, hmac_output); + + const uint8_t *tstamp_initial = b64decoded; + const uint8_t *tstamp = tstamp_initial + sizeof(int64_t); + const uint8_t *hmac = tstamp + sizeof(int64_t); + + uint64_t timestamp = ntohll(*((uint64_t *)(tstamp))); + uint64_t timestamp_initial = ntohll(*((uint64_t *)(tstamp_initial))); + + int validhmac = memcmp_constant_time(&hmac_output, hmac, 32); + + if (validhmac != 0) + { + msg(M_WARN, "--auth-token-gen: HMAC on token from client failed"); + return false; + } + + bool ret = false; + + /* Accept session tokens that not expired are in the acceptable range + * for renogiations */ + if (now >= timestamp + && now < timestamp + 2 * session->opt->renegotiate_seconds) + { + ret = true; + } + + /* We could still have a client that does not update + * its auth-token, so also allow the initial auth-token */ + if (!ret && multi->auth_token_initial + && memcmp_constant_time(up->password, multi->auth_token_initial, + strlen(multi->auth_token_initial)) == 0) + { + ret = true; + } + + /* 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 = false; + } + + if (multi->opt.auth_token_lifetime + && now > timestamp_initial + multi->opt.auth_token_lifetime) + { + /* auth token lifetime exceeded */ + ret = false; + } + + if (!ret) + { + msg(M_INFO, "--auth-token-gen: auth-token from client expired"); + } + + + /* Generate a new auth token to be sent to the client */ + if (ret) + { + /* If we accepted a token without prior session, i.e. + * initial auth via token on new connection, we need + * to store the auth-token in multi->auth_token, so + * the initial timestamp can be extracted from it + */ + if (!multi->auth_token) + { + multi->auth_token = strdup(up->password); + } + + generate_auth_token(up, multi); + /* Auth token already sent to client, update auth-token */ + if (multi->auth_token_initial) + { + /* + * 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); + } + } + 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..0a75ea22 --- /dev/null +++ b/src/openvpn/auth_token.h @@ -0,0 +1,101 @@ +/* + * 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(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 + */ +bool +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_" + +#endif /* AUTH_TOKEN_H */ diff --git a/src/openvpn/init.c b/src/openvpn/init.c index 560d87db..983b49e4 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" @@ -1098,6 +1099,17 @@ do_genkey(const struct options *options) msg(M_USAGE, "--tls-crypt-v2-genkey type should be \"client\" or \"server\""); } + + if (options->auth_token_gen_secret_file) + { + if (!options->auth_token_secret_file) + { + msg(M_USAGE, "--auth-gen-token-secret-genkey requires a server key " + "to be set via --auth-gen-token-secret to create a shared secret"); + } + auth_token_write_server_key_file(options->auth_token_secret_file); + return true; + } return false; } @@ -2490,7 +2502,6 @@ init_crypto_pre(struct context *c, const unsigned int flags) rand_ctx_enable_prediction_resistance(); } #endif - } /* @@ -2614,6 +2625,20 @@ do_init_tls_wrap_key(struct context *c) } +/* + * Initialise the auth-token key context + */ +static void +do_init_auth_token_key(struct context *c) +{ + if (!c->options.auth_token_generate) + return; + + auth_token_init_secret(&c->c1.ks.auth_token_key, + c->options.auth_token_secret_file, + c->options.auth_token_secret_file_inline); +} + /* * Initialize the persistent component of OpenVPN's TLS mode, * which is preserved across SIGUSR1 resets. @@ -2666,6 +2691,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) { @@ -2838,6 +2866,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; @@ -4451,6 +4480,9 @@ inherit_context_child(struct context *dest, dest->c1.authname = src->c1.authname; dest->c1.keysize = src->c1.keysize; + /* inherit auth-token */ + dest->c1.ks.auth_token_key = src->c1.ks.auth_token_key; + /* options */ dest->options = src->options; options_detach(&dest->options); diff --git a/src/openvpn/openvpn.h b/src/openvpn/openvpn.h index d11f61df..30b5aeb4 100644 --- a/src/openvpn/openvpn.h +++ b/src/openvpn/openvpn.h @@ -68,6 +68,7 @@ struct key_schedule struct key_ctx_bi tls_wrap_key; struct key_ctx tls_crypt_v2_server_key; struct buffer tls_crypt_v2_wkc; /**< Wrapped client key */ + struct key_ctx auth_token_key; }; /* diff --git a/src/openvpn/options.c b/src/openvpn/options.c index 0cf8db76..87632551 100644 --- a/src/openvpn/options.c +++ b/src/openvpn/options.c @@ -1286,6 +1286,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); @@ -2334,7 +2335,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) @@ -6769,6 +6774,23 @@ 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], "auth-gen-token-secret-genkey") && !p[1]) + { + VERIFY_PERMISSION(OPT_P_GENERAL); + options->auth_token_gen_secret_file = true; + } + else if (streq(p[0], "client-connect") && p[1]) { VERIFY_PERMISSION(OPT_P_SCRIPT); diff --git a/src/openvpn/options.h b/src/openvpn/options.h index e2b38939..0e0217a1 100644 --- a/src/openvpn/options.h +++ b/src/openvpn/options.h @@ -459,7 +459,11 @@ struct options const char *auth_user_pass_verify_script; bool auth_user_pass_verify_script_via_file; bool auth_token_generate; + bool auth_token_gen_secret_file; 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..8c886657 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 e9927eb8..fb557e37 100644 --- a/src/openvpn/ssl.c +++ b/src/openvpn/ssl.c @@ -59,6 +59,7 @@ #include "ssl.h" #include "ssl_verify.h" #include "ssl_backend.h" +#include "auth_token.h" #include "memdbg.h" @@ -1368,11 +1369,7 @@ tls_multi_free(struct tls_multi *multi, bool clear) cert_hash_free(multi->locked_cert_hash_set); - if (multi->auth_token) - { - secure_memzero(multi->auth_token, AUTH_TOKEN_SIZE); - free(multi->auth_token); - } + wipe_auth_token(multi); free(multi->remote_ciphername); diff --git a/src/openvpn/ssl_common.h b/src/openvpn/ssl_common.h index 410b2163..0e18bf2f 100644 --- a/src/openvpn/ssl_common.h +++ b/src/openvpn/ssl_common.h @@ -298,7 +298,6 @@ struct tls_options /** TLS handshake wrapping state */ struct tls_wrap_ctx tls_wrap; - /* frame parameters for TLS control channel */ struct frame frame; /* used for username/password authentication */ @@ -306,10 +305,16 @@ struct tls_options bool auth_user_pass_verify_script_via_file; const char *tmp_dir; const char *auth_user_pass_file; - bool auth_token_generate; /**< Generate auth-tokens on successful user/pass auth, - * set via options->auth_token_generate. */ + +#ifdef P2MP_SERVER + bool auth_token_generate; /**< Generate auth-tokens on successful + * user/pass auth,seet via + * options->auth_token_generate. */ unsigned int auth_token_lifetime; + struct key_ctx auth_token_key; +#endif + /* use the client-config-dir as a positive authenticator */ const char *client_config_dir_exclusive; @@ -368,10 +373,6 @@ struct tls_options /** @} name Index of key_state objects within a tls_session structure */ /** @} addtogroup control_processor */ -#define AUTH_TOKEN_SIZE 32 /**< Size of server side generated auth tokens. - * 32 bytes == 256 bits - */ - /** * Security parameter state of a single session within a VPN tunnel. * @ingroup control_processor @@ -539,6 +540,14 @@ 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) + */ #endif /* For P_DATA_V2 */ @@ -547,13 +556,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..ac6ade4e 100644 --- a/src/openvpn/ssl_verify.c +++ b/src/openvpn/ssl_verify.c @@ -44,6 +44,7 @@ #ifdef ENABLE_CRYPTO_OPENSSL #include "ssl_verify_openssl.h" #endif +#include "auth_token.h" /** Maximum length of common name */ #define TLS_USERNAME_LEN 64 @@ -63,28 +64,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 +1232,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,13 +1257,14 @@ 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 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 && + memcmp_constant_time(SESSION_ID_PREFIX, up->password, + strlen(SESSION_ID_PREFIX))==0) { unsigned int ssl_flags = session->opt->ssl_flags; @@ -1295,29 +1276,14 @@ verify_user_pass(struct user_pass *up, struct tls_multi *multi, return; } - /* 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) + if (!verify_auth_token(up, multi,session)) { - 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; } - - /* The core authentication of the token itself */ - if (memcmp_constant_time(multi->auth_token, up->password, - strlen(multi->auth_token)) != 0) - { - 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]" : ""); - } else { ks->authenticated = true; @@ -1326,7 +1292,7 @@ verify_user_pass(struct user_pass *up, struct tls_multi *multi, { set_common_name(session, up->username); } - msg(D_HANDSHAKE, "TLS: Username/auth-token authentication " + msg(M_WARN, "TLS: Username/auth-token authentication " "succeeded for username '%s' %s", up->username, (ssl_flags & SSLF_USERNAME_AS_COMMON_NAME) ? "[CN SET]" : ""); @@ -1382,27 +1348,13 @@ verify_user_pass(struct user_pass *up, struct tls_multi *multi, } #endif - 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. */ - uint8_t tok[AUTH_TOKEN_SIZE]; - - if (!rand_bytes(tok, AUTH_TOKEN_SIZE)) - { - msg( M_FATAL, "Failed to get enough randomness for " - "authentication token"); - } - /* The token should be longer than the input when - * being base64 encoded - */ - 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)) From patchwork Mon Jan 14 04:48:18 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Arne Schwabe X-Patchwork-Id: 662 Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director7.mail.ord1d.rsapps.net ([172.27.255.1]) by backend30.mail.ord1d.rsapps.net with LMTP id mPZzNHq5PFxvRwAAIUCqbw for ; Mon, 14 Jan 2019 11:31:54 -0500 Received: from proxy11.mail.iad3a.rsapps.net ([172.27.255.1]) by director7.mail.ord1d.rsapps.net with LMTP id +D22MXq5PFzcLgAAovjBpQ ; Mon, 14 Jan 2019 11:31:54 -0500 Received: from smtp13.gate.iad3a ([172.27.255.1]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy11.mail.iad3a.rsapps.net with LMTP id gEvbK3q5PFwFOgAAxCvdqw ; Mon, 14 Jan 2019 11:31:54 -0500 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: smtp13.gate.iad3a.rsapps.net; iprev=pass policy.iprev="216.105.38.7"; spf=pass smtp.mailfrom="openvpn-devel-bounces@lists.sourceforge.net" smtp.helo="lists.sourceforge.net"; dkim=fail (signature verification failed) header.d=sourceforge.net; dkim=fail (signature verification failed) header.d=sf.net; dmarc=none (p=nil; dis=none) header.from=rfc2549.org X-Suspicious-Flag: YES X-Classification-ID: e658cf20-1819-11e9-aa4c-5254004b83b1-1-1 Received: from [216.105.38.7] ([216.105.38.7:57833] helo=lists.sourceforge.net) by smtp13.gate.iad3a.rsapps.net (envelope-from ) (ecelerity 4.2.38.62370 r(:)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id 09/43-14360-979BC3C5; Mon, 14 Jan 2019 11:31:54 -0500 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 1gj58s-0004oj-Lg; Mon, 14 Jan 2019 16:31:14 +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 1gj58r-0004oX-49 for openvpn-devel@lists.sourceforge.NET; Mon, 14 Jan 2019 16:31:13 +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=7voRwAH2Zy1Mw1BaUYnmXoaRWKtdBS4/B6g+4WNJp6Q=; b=elIfCqQLAwjlwnhFcBMvTodISA EXLXsTEapIbNlyeaNQVZyUshuYFK27PFiuqkBrhBsK8PbT1VwJHRjts4uXsImiPE95DdxNsBPkYb3 cziQNrSctQezun6tMWYh+3Aj0E5C/TUPAkEV3crUgcFMTH3RbQjqM93BMu7rCkCmALFc=; 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=7voRwAH2Zy1Mw1BaUYnmXoaRWKtdBS4/B6g+4WNJp6Q=; b=TbyDHn2OXiBnu/CpMDW9nEcEj5 GFcCji+txkSIFN3MbX+TSVDu6May/2QDH7oRRESTNuaQ2dU6zoFLvaEKTPNmFxaX77vwWxhoyKsky ADympSga+wtJzFqS7ojNQKi0JJA6UVZiBl9y1PSAO7DGasBBurUUuEn17JMQuZWUsQUA=; Received: from mail.blinkt.de ([192.26.174.232]) by sfi-mx-3.v28.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.90_1) id 1gj58o-001TnZ-OJ for openvpn-devel@lists.sourceforge.NET; Mon, 14 Jan 2019 16:31:13 +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 1gj4TL-000MiZ-P7; Mon, 14 Jan 2019 16:48:19 +0100 Received: (nullmailer pid 6121 invoked by uid 10006); Mon, 14 Jan 2019 15:48:19 -0000 From: Arne Schwabe To: openvpn-devel@lists.sourceforge.net Date: Mon, 14 Jan 2019 16:48:18 +0100 Message-Id: <20190114154819.6064-5-arne@rfc2549.org> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20190114154819.6064-1-arne@rfc2549.org> References: <20190114154819.6064-1-arne@rfc2549.org> X-Spam-Report: Spam Filtering performed by mx.sourceforge.net. See http://spamassassin.org/tag/ for more details. 0.0 HEADER_FROM_DIFFERENT_DOMAINS From and EnvelopeFrom 2nd level mail domains are different X-Headers-End: 1gj58o-001TnZ-OJ Subject: [Openvpn-devel] [PATCH 5/6] 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. --- doc/openvpn.8 | 10 +++- src/openvpn/auth_token.c | 105 ++++++++++++++++++++++++++++++++------- src/openvpn/auth_token.h | 17 ++++++- src/openvpn/init.c | 1 + src/openvpn/manage.c | 4 +- src/openvpn/options.c | 14 +++++- src/openvpn/options.h | 3 +- src/openvpn/ssl_common.h | 1 + src/openvpn/ssl_verify.c | 78 ++++++++++++++++++----------- 9 files changed, 180 insertions(+), 53 deletions(-) diff --git a/doc/openvpn.8 b/doc/openvpn.8 index 7abcaf1e..671c0a01 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 @@ -3730,6 +3730,14 @@ 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 auth keyword is present the normal authentication +method will be called even if auth-token succeeds. This allows +the normal to still check the validity of the account and do other +checks. The normal auth method can infer that the auth-token has been +successful from the password starting with SESS_ID_ +(invalid auth-token fail early and the normal authentication will not +be called) and the presence of a 'session_id' environment variable. .\"********************************************************* .TP .B \-\-opt\-verify diff --git a/src/openvpn/auth_token.c b/src/openvpn/auth_token.c index 5ba31042..03e661e6 100644 --- a/src/openvpn/auth_token.c +++ b/src/openvpn/auth_token.c @@ -17,6 +17,11 @@ 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 + static struct key_type auth_token_kt(void) { @@ -37,7 +42,47 @@ auth_token_kt(void) } -void auth_token_write_server_key_file(const char *filename) + +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; + } + + /* If reach reach this point with a SESS_ID prefixed password + * we passed auth-token checks */ + if (memcmp_constant_time(SESSION_ID_PREFIX, up->password, + strlen(SESSION_ID_PREFIX))==0) + { + setenv_str(session->opt->es, "session_state", "Authenticated"); + ASSERT(multi->auth_token); + } + else + { + setenv_str(session->opt->es, "session_state", "Initial"); + /* Generate initial token to have it for the management interface */ + if (!multi->auth_token) + { + generate_auth_token(up, multi); + } + } + /* + * 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, multi->auth_token + 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) { write_pem_key_file(filename, auth_token_pem_name); } @@ -123,7 +168,7 @@ auth_token_init_secret(struct key_ctx *key_ctx, const char *key_file, } void -generate_auth_token(struct user_pass *up, struct tls_multi *multi) +generate_auth_token(const struct user_pass *up, struct tls_multi *multi) { struct gc_arena gc = gc_new(); @@ -134,6 +179,8 @@ generate_auth_token(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 @@ -146,36 +193,54 @@ generate_auth_token(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)); + 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))); - char* b64output; + 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); + 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))); @@ -185,8 +250,8 @@ generate_auth_token(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); } @@ -199,13 +264,14 @@ verify_auth_token(struct user_pass *up, struct tls_multi *multi, * Base64 is <= input and input is < USER_PASS_LEN, so using USER_PASS_LEN * is safe here but a bit overkill */ - char b64decoded[USER_PASS_LEN]; + uint8_t b64decoded[USER_PASS_LEN]; int decoded_len = openvpn_base64_decode(up->password + strlen(SESSION_ID_PREFIX), - b64decoded, USER_PASS_LEN); + b64decoded, USER_PASS_LEN); /* Ensure that the decoded data is at least the size of the * timestamp + hmac */ - int expected_len = 2 * sizeof(int64_t) + 32; + + int expected_len = 2 * sizeof(int64_t) + AUTH_TOKEN_SESSION_ID_LEN + 32; if (decoded_len != expected_len) { @@ -216,14 +282,15 @@ verify_auth_token(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); - hmac_ctx_reset(ctx); - hmac_ctx_update(ctx, (uint8_t *) up->username, strlen(up->username)); - hmac_ctx_update(ctx, (uint8_t *) b64decoded, sizeof(int64_t)); - 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, b64decoded, expected_len - 256/8); hmac_ctx_final(ctx, hmac_output); - 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); const uint8_t *hmac = tstamp + sizeof(int64_t); @@ -234,7 +301,8 @@ verify_auth_token(struct user_pass *up, struct tls_multi *multi, if (validhmac != 0) { - msg(M_WARN, "--auth-token-gen: HMAC on token from client failed"); + msg(M_WARN, "--auth-token-gen: HMAC on token from client failed (%s), %s", + up->password, up->username); return false; } @@ -285,7 +353,7 @@ verify_auth_token(struct user_pass *up, struct tls_multi *multi, /* If we accepted a token without prior session, i.e. * initial auth via token on new connection, we need * to store the auth-token in multi->auth_token, so - * the initial timestamp can be extracted from it + * the initial timestamp and session id can be extracted from it */ if (!multi->auth_token) { @@ -293,6 +361,7 @@ verify_auth_token(struct user_pass *up, struct tls_multi *multi, } generate_auth_token(up, multi); + /* Auth token already sent to client, update auth-token */ if (multi->auth_token_initial) { diff --git a/src/openvpn/auth_token.h b/src/openvpn/auth_token.h index 0a75ea22..9f6a84ef 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,13 +45,17 @@ * 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 * * we prepend the session id with SESS_ID_ before sending it to the client */ void -generate_auth_token(struct user_pass *up, struct tls_multi *multi); +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 @@ -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 983b49e4..1bcd239c 100644 --- a/src/openvpn/init.c +++ b/src/openvpn/init.c @@ -2866,6 +2866,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 87632551..5f7263ac 100644 --- a/src/openvpn/options.c +++ b/src/openvpn/options.c @@ -6768,11 +6768,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], "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 0e0217a1..79d5af14 100644 --- a/src/openvpn/options.h +++ b/src/openvpn/options.h @@ -460,7 +460,8 @@ struct options bool auth_user_pass_verify_script_via_file; bool auth_token_generate; bool auth_token_gen_secret_file; - unsigned int auth_token_lifetime; + 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 0e18bf2f..2e4ee20c 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; diff --git a/src/openvpn/ssl_verify.c b/src/openvpn/ssl_verify.c index ac6ade4e..0a7147c8 100644 --- a/src/openvpn/ssl_verify.c +++ b/src/openvpn/ssl_verify.c @@ -1051,7 +1051,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(); @@ -1100,6 +1101,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); @@ -1133,7 +1137,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 @@ -1153,6 +1158,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)) @@ -1196,7 +1203,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 */ @@ -1214,6 +1223,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); @@ -1257,7 +1271,14 @@ 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 the client sends + /* + * 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. @@ -1266,8 +1287,6 @@ verify_user_pass(struct user_pass *up, struct tls_multi *multi, memcmp_constant_time(SESSION_ID_PREFIX, up->password, strlen(SESSION_ID_PREFIX))==0) { - unsigned int ssl_flags = session->opt->ssl_flags; - /* Ensure that the username has not changed */ if (!tls_lock_username(multi, up->username)) { @@ -1286,40 +1305,41 @@ verify_user_pass(struct user_pass *up, struct tls_multi *multi, } else { - ks->authenticated = true; - - if (ssl_flags & SSLF_USERNAME_AS_COMMON_NAME) - { - set_common_name(session, up->username); - } msg(M_WARN, "TLS: Username/auth-token authentication " - "succeeded for username '%s' %s", - up->username, - (ssl_flags & SSLF_USERNAME_AS_COMMON_NAME) ? "[CN SET]" : ""); + "succeeded for username '%s'", + up->username); + if (!session->opt->auth_token_call_auth) + skip_auth = true; } - return; } /* call plugin(s) and/or script */ -#ifdef MANAGEMENT_DEF_AUTH - if (man_def_auth == KMDA_DEF) + if (!skip_auth) { - man_def_auth = verify_user_pass_management(session, up); - } +#ifdef MANAGEMENT_DEF_AUTH + if (man_def_auth==KMDA_DEF) + { + 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); - } - if (session->opt->auth_user_pass_verify_script) - { - s2 = verify_user_pass_script(session, up); + if (plugin_defined(session->opt->plugins, OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY)) + { + s1 = verify_user_pass_plugin(session, multi, up); + } + + if (session->opt->auth_user_pass_verify_script) + { + 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) + 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); + 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; } From patchwork Mon Jan 14 04:48:19 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Arne Schwabe X-Patchwork-Id: 657 Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director8.mail.ord1d.rsapps.net ([172.27.255.56]) by backend30.mail.ord1d.rsapps.net with LMTP id WAhfCVe5PFwNWQAAIUCqbw for ; Mon, 14 Jan 2019 11:31:19 -0500 Received: from proxy3.mail.iad3a.rsapps.net ([172.27.255.56]) by director8.mail.ord1d.rsapps.net with LMTP id UK+GBle5PFzuKwAAfY0hYg ; Mon, 14 Jan 2019 11:31:19 -0500 Received: from smtp27.gate.iad3a ([172.27.255.56]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy3.mail.iad3a.rsapps.net with LMTP id gBbXAFe5PFxOdwAAYaqY3Q ; Mon, 14 Jan 2019 11:31:19 -0500 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: smtp27.gate.iad3a.rsapps.net; iprev=pass policy.iprev="216.105.38.7"; spf=pass smtp.mailfrom="openvpn-devel-bounces@lists.sourceforge.net" smtp.helo="lists.sourceforge.net"; dkim=fail (signature verification failed) header.d=sourceforge.net; dkim=fail (signature verification failed) header.d=sf.net; dmarc=none (p=nil; dis=none) header.from=rfc2549.org X-Suspicious-Flag: YES X-Classification-ID: d169274a-1819-11e9-afbf-525400358560-1-1 Received: from [216.105.38.7] ([216.105.38.7:3185] helo=lists.sourceforge.net) by smtp27.gate.iad3a.rsapps.net (envelope-from ) (ecelerity 4.2.38.62370 r(:)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id B9/D1-14302-659BC3C5; Mon, 14 Jan 2019 11:31:18 -0500 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 1gj587-00041k-3x; Mon, 14 Jan 2019 16:30:27 +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 1gj584-00041c-Uy for openvpn-devel@lists.sourceforge.NET; Mon, 14 Jan 2019 16:30:24 +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=iapOrHmHF498k4Rso/rphDveQAYIx7akzmRwt9nKCSQ=; b=QjkhwRAhESZqH9Vj1FJsqNk3Ni hCTNLJiYCa8MU29oci33ODvcsY4vtHssqujTCRXJ2oHjmDuEjhVh1PgnQiwQcA8oU/aQuj+HPKfmb W/HdGGsHnLxp1CbVf+vJ/YLSpFV0ZUs2TyTruh1bTpWeh455U22BZ+XDbCqWGHCMdQqE=; 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=iapOrHmHF498k4Rso/rphDveQAYIx7akzmRwt9nKCSQ=; b=MVD7I4Apdpp5ZMal+nU+4nufdS Gpwn5j9Vz8pvt9x6h61o+ZMDZskdeUyajw1U64FGowykWhwX4Dgna5au/vfXcov9UKTVfaNoRJeH8 wH6nWK2bKQ6E2Nd7cOPlpRuBHaUTSsPHPRs9Vtj7FVkbfHSfwRFQ0IGIbNs9+yYb32l0=; Received: from mail.blinkt.de ([192.26.174.232]) by sfi-mx-4.v28.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.90_1) id 1gj57p-001qiq-DD for openvpn-devel@lists.sourceforge.NET; Mon, 14 Jan 2019 16:30:24 +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 1gj4TL-000Mil-RT; Mon, 14 Jan 2019 16:48:19 +0100 Received: (nullmailer pid 6124 invoked by uid 10006); Mon, 14 Jan 2019 15:48:19 -0000 From: Arne Schwabe To: openvpn-devel@lists.sourceforge.net Date: Mon, 14 Jan 2019 16:48:19 +0100 Message-Id: <20190114154819.6064-6-arne@rfc2549.org> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20190114154819.6064-1-arne@rfc2549.org> References: <20190114154819.6064-1-arne@rfc2549.org> X-Spam-Report: Spam Filtering performed by mx.sourceforge.net. See http://spamassassin.org/tag/ for more details. 0.0 HEADER_FROM_DIFFERENT_DOMAINS From and EnvelopeFrom 2nd level mail domains are different 0.0 TIME_LIMIT_EXCEEDED Exceeded time limit / deadline X-Headers-End: 1gj57p-001qiq-DD Subject: [Openvpn-devel] [PATCH 6/6] Implement unit tests for 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 --- tests/unit_tests/openvpn/Makefile.am | 18 +- tests/unit_tests/openvpn/test_auth_token.c | 309 +++++++++++++++++++++ 2 files changed, 326 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 b4304e35..f50c7e06 100644 --- a/tests/unit_tests/openvpn/Makefile.am +++ b/tests/unit_tests/openvpn/Makefile.am @@ -6,7 +6,7 @@ if HAVE_LD_WRAP_SUPPORT check_PROGRAMS += argv_testdriver buffer_testdriver endif -check_PROGRAMS += crypto_testdriver packet_id_testdriver +check_PROGRAMS += crypto_testdriver packet_id_testdriver auth_token_testdriver if HAVE_LD_WRAP_SUPPORT check_PROGRAMS += tls_crypt_testdriver endif @@ -68,3 +68,19 @@ tls_crypt_testdriver_SOURCES = test_tls_crypt.c mock_msg.c \ $(openvpn_srcdir)/packet_id.c \ $(openvpn_srcdir)/platform.c \ $(openvpn_srcdir)/run_command.c + +auth_token_testdriver_CFLAGS = @TEST_CFLAGS@ \ + -I$(openvpn_includedir) -I$(compat_srcdir) -I$(openvpn_srcdir) \ + $(OPTIONAL_CRYPTO_CFLAGS) +auth_token_testdriver_LDFLAGS = @TEST_LDFLAGS@ \ + $(OPTIONAL_CRYPTO_LIBS) + +auth_token_testdriver_SOURCES = test_auth_token.c mock_msg.c \ + $(openvpn_srcdir)/buffer.c \ + $(openvpn_srcdir)/crypto.c \ + $(openvpn_srcdir)/crypto_mbedtls.c \ + $(openvpn_srcdir)/crypto_openssl.c \ + $(openvpn_srcdir)/otime.c \ + $(openvpn_srcdir)/packet_id.c \ + $(openvpn_srcdir)/platform.c \ + $(openvpn_srcdir)/base64.c diff --git a/tests/unit_tests/openvpn/test_auth_token.c b/tests/unit_tests/openvpn/test_auth_token.c new file mode 100644 index 00000000..1ac4b7ed --- /dev/null +++ b/tests/unit_tests/openvpn/test_auth_token.c @@ -0,0 +1,309 @@ +/* + * 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" + +#define TESTBUF_SIZE 128 + +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_true(verify_auth_token(&ctx->up, &ctx->multi, &ctx->session)); +} + +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_true(verify_auth_token(&ctx->up, &ctx->multi, &ctx->session)); + + /* 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_false(verify_auth_token(&ctx->up, &ctx->multi, &ctx->session)); + + /* 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_true(verify_auth_token(&ctx->up, &ctx->multi, &ctx->session)); + +} + +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_true(verify_auth_token(&ctx->up, &ctx->multi, &ctx->session)); + + /* Token before validity, should be rejected */ + now = 100000 - 100; + assert_false(verify_auth_token(&ctx->up, &ctx->multi, &ctx->session)); + + /* Token still in validity, should be accepted */ + now = 100000 + 2*ctx->session.opt->renegotiate_seconds - 20; + assert_true(verify_auth_token(&ctx->up, &ctx->multi, &ctx->session)); + + /* Token past validity, should be rejected */ + now = 100000 + 2*ctx->session.opt->renegotiate_seconds + 20; + assert_false(verify_auth_token(&ctx->up, &ctx->multi, &ctx->session)); + + /* Check if the mode for a client that never updates its token works */ + ctx->multi.auth_token_initial = strdup(ctx->up.password); + assert_true(verify_auth_token(&ctx->up, &ctx->multi, &ctx->session)); + + /* But not when we reached our timeout */ + now = 100000 + ctx->session.opt->auth_token_lifetime + 1; + assert_false(verify_auth_token(&ctx->up, &ctx->multi, &ctx->session)); + + 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) + { + /*printf("now: %u, %s\n", (unsigned int) now, ctx->up.password); */ + assert_true(verify_auth_token(&ctx->up, &ctx->multi, &ctx->session)); + strcpy(ctx->up.password, ctx->multi.auth_token); + now += ctx->session.opt->renegotiate_seconds; + } + + + assert_false(verify_auth_token(&ctx->up, &ctx->multi, &ctx->session)); + ctx->multi.opt.auth_token_lifetime = 0; + + /* Non expiring token should be fine */ + assert_true(verify_auth_token(&ctx->up, &ctx->multi, &ctx->session)); +} + +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_true(verify_auth_token(&ctx->up, &ctx->multi, &ctx->session)); +} + + +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_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 +setenv_str(struct env_set *es, const char *name, const char *value) +{ +} \ No newline at end of file