From patchwork Sun Mar 21 03:25:38 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Arne Schwabe X-Patchwork-Id: 1640 X-Patchwork-Delegate: a@unstable.cc Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director9.mail.ord1d.rsapps.net ([172.30.191.6]) by backend30.mail.ord1d.rsapps.net with LMTP id mGa8F8NXV2BUdQAAIUCqbw (envelope-from ) for ; Sun, 21 Mar 2021 10:27:15 -0400 Received: from proxy5.mail.ord1d.rsapps.net ([172.30.191.6]) by director9.mail.ord1d.rsapps.net with LMTP id uEqkF8NXV2CdRwAAalYnBA (envelope-from ) for ; Sun, 21 Mar 2021 10:27:15 -0400 Received: from smtp9.gate.ord1c ([172.30.191.6]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy5.mail.ord1d.rsapps.net with LMTPS id uHxyF8NXV2C8MQAA8Zzt7w (envelope-from ) for ; Sun, 21 Mar 2021 10:27:15 -0400 X-Spam-Threshold: 95 X-Spam-Score: 0 X-Spam-Flag: NO X-Virus-Scanned: OK X-Orig-To: openvpnslackdevel@openvpn.net X-Originating-Ip: [216.105.38.7] Authentication-Results: smtp9.gate.ord1c.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: 876bd596-8a51-11eb-b8f1-0026b95bddb7-1-1 Received: from [216.105.38.7] ([216.105.38.7:59204] helo=lists.sourceforge.net) by smtp9.gate.ord1c.rsapps.net (envelope-from ) (ecelerity 4.2.38.62370 r(:)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id C2/A6-15241-1C757506; Sun, 21 Mar 2021 10:27:14 -0400 Received: from [127.0.0.1] (helo=sfs-ml-4.v29.lw.sourceforge.com) by sfs-ml-4.v29.lw.sourceforge.com with esmtp (Exim 4.90_1) (envelope-from ) id 1lNz1t-00023k-8n; Sun, 21 Mar 2021 14:26:09 +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 1lNz1p-00023Y-HO for openvpn-devel@lists.sourceforge.net; Sun, 21 Mar 2021 14:26:05 +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=zkBnjZH77ewBHQb2m0Uh+62lozvOB25MtRpVITTZFnI=; b=TTYjnmvR77jo8LTA470QoYt32/ xnaRvpNA1dkRbjvJQSggwvk5cU6teKDAtuOvw9u1xFvhTYS2Yvto0nUl67cRxXwEvsRq25ipEuyhc ZxSqsWy3MhEDeAj0qa5y9QYvl9xeXUn96OLckgmvcUMfi2/PyB7asER0rTvHnGs9eoMU=; 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=zkBnjZH77ewBHQb2m0Uh+62lozvOB25MtRpVITTZFnI=; b=bo3EaqXxROtMGEc+N1Rf1GfhZJ VBEdR1yhY9wS7qRWlgOGH25lgy7j+ZrG4wYPHLKHDW469RzVKXaNmbRWExdI6YHmbS+YmO9EjyMaN zSO9azdO6OPjJrDcm4UGOPwcnEJvVlYwY/sOIYkeTKjM0FewOoXyFr1Vq2MdJMrBDYLM=; Received: from mail.blinkt.de ([192.26.174.232]) by sfi-mx-2.v28.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.92.3) id 1lNz1d-0006Uu-4R for openvpn-devel@lists.sourceforge.net; Sun, 21 Mar 2021 14:26:03 +0000 Received: from kamera.blinkt.de ([2001:638:502:390:20c:29ff:fec8:535c]) by mail.blinkt.de with smtp (Exim 4.94 (FreeBSD)) (envelope-from ) id 1lNz1O-00031X-Kq for openvpn-devel@lists.sourceforge.net; Sun, 21 Mar 2021 15:25:38 +0100 Received: (nullmailer pid 1708 invoked by uid 10006); Sun, 21 Mar 2021 14:25:38 -0000 From: Arne Schwabe To: openvpn-devel@lists.sourceforge.net Date: Sun, 21 Mar 2021 15:25:38 +0100 Message-Id: <20210321142538.1656-1-arne@rfc2549.org> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20210319142001.2201-1-arne@rfc2549.org> References: <20210319142001.2201-1-arne@rfc2549.org> X-Spam-Report: Spam Filtering performed by mx.sourceforge.net. See http://spamassassin.org/tag/ for more details. 0.0 URIBL_BLOCKED ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [URIs: rfc2549.org] 0.2 HEADER_FROM_DIFFERENT_DOMAINS From and EnvelopeFrom 2nd level mail domains are different 0.0 SPF_NONE SPF: sender does not publish an SPF Record 0.0 SPF_HELO_NONE SPF: HELO does not publish an SPF Record X-Headers-End: 1lNz1d-0006Uu-4R Subject: [Openvpn-devel] [PATCH v3] Extend verify-hash to allow multiple hashes X-BeenThere: openvpn-devel@lists.sourceforge.net X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox This patch introduces support for verify-hash inlining. When inlined, this options now allows to specify multiple fingerprints, one per line. Since this is a new syntax, there is no backwards compatibility to take care of, therefore we can drop support for SHA1. Inlined fingerprints are assumed be to SHA-256 only. Also print a warning about SHA1 hash being deprecated to verify certificates as it is not "industry standard" anymore. Patch v2: fix/clarify various comments, fix a few minor problems, allow the option to be specified multiple times and have that added to the list. Patch v3: Remove leftover variable, always call parse_hash_fingerprint_multiline, add comments clarifying list appending Signed-off-by: Arne Schwabe Acked-by: Antonio Quartulli --- doc/man-sections/inline-files.rst | 4 +- doc/man-sections/tls-options.rst | 10 +++ src/openvpn/options.c | 102 ++++++++++++++++++++++++++---- src/openvpn/options.h | 10 ++- src/openvpn/ssl_common.h | 2 +- src/openvpn/ssl_verify.c | 16 ++++- 6 files changed, 127 insertions(+), 17 deletions(-) diff --git a/doc/man-sections/inline-files.rst b/doc/man-sections/inline-files.rst index 819bd3c8..303bb3c8 100644 --- a/doc/man-sections/inline-files.rst +++ b/doc/man-sections/inline-files.rst @@ -4,8 +4,8 @@ INLINE FILE SUPPORT OpenVPN allows including files in the main configuration for the ``--ca``, ``--cert``, ``--dh``, ``--extra-certs``, ``--key``, ``--pkcs12``, ``--secret``, ``--crl-verify``, ``--http-proxy-user-pass``, ``--tls-auth``, -``--auth-gen-token-secret``, ``--tls-crypt`` and ``--tls-crypt-v2`` -options. +``--auth-gen-token-secret``, ``--tls-crypt``, ``--tls-crypt-v2`` and +``--verify-hash`` options. Each inline file started by the line ```` diff --git a/doc/man-sections/tls-options.rst b/doc/man-sections/tls-options.rst index e13fb3c8..d8f9800e 100644 --- a/doc/man-sections/tls-options.rst +++ b/doc/man-sections/tls-options.rst @@ -582,6 +582,16 @@ certificates and keys: https://github.com/OpenVPN/easy-rsa The ``algo`` flag can be either :code:`SHA1` or :code:`SHA256`. If not provided, it defaults to :code:`SHA1`. + This option can also be inlined + :: + + + 00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff + 11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00 + + +If the option is inlined, ``algo`` is always :code:`SHA256`. + --verify-x509-name args Accept connections only if a host's X.509 name is equal to **name.** The remote host must also pass all other tests of verification. diff --git a/src/openvpn/options.c b/src/openvpn/options.c index 86e78b05..3b1c69ba 100644 --- a/src/openvpn/options.c +++ b/src/openvpn/options.c @@ -1078,12 +1078,24 @@ string_substitute(const char *src, int from, int to, struct gc_arena *gc) return ret; } -static uint8_t * +/** + * Parses a hexstring and checks if the string has the correct length. Return + * a verify_hash_list containing the parsed hash string. + * + * @param str String to check/parse + * @param nbytes Number of bytes expected in the hexstr (e.g. 20 for SHA1) + * @param msglevel message level to use when printing warnings/errors + * @param gc The returned object will be allocated in this gc + */ +static struct verify_hash_list * parse_hash_fingerprint(const char *str, int nbytes, int msglevel, struct gc_arena *gc) { int i; const char *cp = str; - uint8_t *ret = (uint8_t *) gc_malloc(nbytes, true, gc); + + struct verify_hash_list *ret; + ALLOC_OBJ_CLEAR_GC(ret, struct verify_hash_list, gc); + char term = 1; int byte; char bs[3]; @@ -1102,7 +1114,7 @@ parse_hash_fingerprint(const char *str, int nbytes, int msglevel, struct gc_aren { msg(msglevel, "format error in hash fingerprint hex byte: %s", str); } - ret[i] = (uint8_t)byte; + ret->hash[i] = (uint8_t)byte; term = *cp++; if (term != ':' && term != 0) { @@ -1116,10 +1128,55 @@ parse_hash_fingerprint(const char *str, int nbytes, int msglevel, struct gc_aren if (term != 0 || i != nbytes-1) { msg(msglevel, "hash fingerprint is different length than expected (%d bytes): %s", nbytes, str); + return NULL; } return ret; } +/** + * Parses a string consisting of multiple lines of hexstrings and checks if each + * string has the correct length. Empty lines are ignored. Returns + * a linked list of (possibly) multiple verify_hash_list objects. + * + * @param str String to check/parse + * @param nbytes Number of bytes expected in the hexstring (e.g. 20 for SHA1) + * @param msglevel message level to use when printing warnings/errors + * @param gc The returned list items will be allocated in this gc + */ +static struct verify_hash_list * +parse_hash_fingerprint_multiline(const char *str, int nbytes, int msglevel, + struct gc_arena *gc) +{ + struct gc_arena gc_temp = gc_new(); + char *lines = string_alloc(str, &gc_temp); + + struct verify_hash_list *ret = NULL; + + const char *line; + while ((line = strsep(&lines, "\n"))) + { + /* skip empty lines */ + if (strlen(line) == 0) + { + continue; + } + + struct verify_hash_list *hash = parse_hash_fingerprint(line, nbytes, + msglevel, gc); + + if (!hash) + { + gc_free(&gc_temp); + return NULL; + } + + hash->next = ret; + ret = hash; + } + gc_free(&gc_temp); + + return ret; +} #ifdef _WIN32 #ifndef ENABLE_SMALL @@ -8063,22 +8120,45 @@ add_option(struct options *options, } else if (streq(p[0], "verify-hash") && p[1] && !p[3]) { - VERIFY_PERMISSION(OPT_P_GENERAL); + VERIFY_PERMISSION(OPT_P_GENERAL|OPT_P_INLINE); + options->verify_hash_algo = MD_SHA256; + + int digest_len = SHA256_DIGEST_LENGTH; - if (!p[2] || (p[2] && streq(p[2], "SHA1"))) + if ((!p[2] && !is_inline) || (p[2] && streq(p[2], "SHA1"))) { - options->verify_hash = parse_hash_fingerprint(p[1], SHA_DIGEST_LENGTH, msglevel, &options->gc); options->verify_hash_algo = MD_SHA1; + msg(M_WARN, "DEPRECATED FEATURE: Usage of SHA1 fingerprints for " + "verify-hash is deprecated. You should switch to SHA256."); + options->verify_hash_algo = MD_SHA1; + digest_len = SHA_DIGEST_LENGTH; } - else if (p[2] && streq(p[2], "SHA256")) + else if (p[2] && !streq(p[2], "SHA256")) { - options->verify_hash = parse_hash_fingerprint(p[1], SHA256_DIGEST_LENGTH, msglevel, &options->gc); - options->verify_hash_algo = MD_SHA256; + msg(msglevel, "invalid or unsupported hashing algorithm: %s " + "(only SHA1 and SHA256 are valid)", p[2]); + goto err; + } + + struct verify_hash_list *newlist; + newlist = parse_hash_fingerprint_multiline(p[1], digest_len, + msglevel, &options->gc); + + /* Append the new list to the end of our current list */ + if (!options->verify_hash) + { + options->verify_hash = newlist; } else { - msg(msglevel, "invalid or unsupported hashing algorithm: %s (only SHA1 and SHA256 are valid)", p[2]); - goto err; + /* since both the old and new list can have multiple entries + * we need to go to the end of one of them to concatenate them */ + struct verify_hash_list *listend = options->verify_hash; + while (listend->next) + { + listend = listend->next; + } + listend->next = newlist; } } #ifdef ENABLE_CRYPTOAPI diff --git a/src/openvpn/options.h b/src/openvpn/options.h index 56228668..a7b3174f 100644 --- a/src/openvpn/options.h +++ b/src/openvpn/options.h @@ -191,6 +191,14 @@ enum genkey_type { GENKEY_AUTH_TOKEN }; +struct verify_hash_list +{ + /* We support SHA256 and SHA1 fingerpint. In the case of using the + * deprecated SHA1, only the first 20 bytes of each list item are used */ + uint8_t hash[SHA256_DIGEST_LENGTH]; + struct verify_hash_list *next; +}; + /* Command line options */ struct options { @@ -550,7 +558,7 @@ struct options int ns_cert_type; /* set to 0, NS_CERT_CHECK_SERVER, or NS_CERT_CHECK_CLIENT */ unsigned remote_cert_ku[MAX_PARMS]; const char *remote_cert_eku; - uint8_t *verify_hash; + struct verify_hash_list *verify_hash; hash_algo_type verify_hash_algo; unsigned int ssl_flags; /* set to SSLF_x flags from ssl.h */ diff --git a/src/openvpn/ssl_common.h b/src/openvpn/ssl_common.h index 2e3c98db..f6aaae98 100644 --- a/src/openvpn/ssl_common.h +++ b/src/openvpn/ssl_common.h @@ -283,7 +283,7 @@ struct tls_options int ns_cert_type; unsigned remote_cert_ku[MAX_PARMS]; const char *remote_cert_eku; - uint8_t *verify_hash; + struct verify_hash_list *verify_hash; hash_algo_type verify_hash_algo; #ifdef ENABLE_X509ALTUSERNAME char *x509_username_field[MAX_PARMS]; diff --git a/src/openvpn/ssl_verify.c b/src/openvpn/ssl_verify.c index 4b120f7b..06de0f5f 100644 --- a/src/openvpn/ssl_verify.c +++ b/src/openvpn/ssl_verify.c @@ -748,9 +748,21 @@ verify_cert(struct tls_session *session, openvpn_x509_cert_t *cert, int cert_dep goto cleanup; } - if (memcmp(BPTR(&ca_hash), opt->verify_hash, BLEN(&ca_hash))) + struct verify_hash_list *current_hash = opt->verify_hash; + + while (current_hash) + { + if (memcmp_constant_time(BPTR(&ca_hash), current_hash->hash, + BLEN(&ca_hash)) == 0) + { + break; + } + current_hash = current_hash->next; + } + + if (!current_hash) { - msg(D_TLS_ERRORS, "TLS Error: level-1 certificate hash verification failed"); + msg(D_TLS_ERRORS, "TLS Error: --tls-verify certificate hash verification failed"); goto cleanup; } }