From patchwork Fri Jan 12 18:19:22 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Gert Doering X-Patchwork-Id: 3567 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a05:7300:a213:b0:100:d2e5:60d with SMTP id bs19csp2227119dyb; Fri, 12 Jan 2024 10:20:05 -0800 (PST) X-Google-Smtp-Source: AGHT+IGmScAWSe3j81Wb3/aro8iKI+OJMpW67+TQXS5AGsljELYxhfgEPAhsQKh9MzQ5pmZJUAxY X-Received: by 2002:aa7:9a08:0:b0:6d9:6081:602d with SMTP id w8-20020aa79a08000000b006d96081602dmr3566883pfj.3.1705083605153; Fri, 12 Jan 2024 10:20:05 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1705083605; cv=none; d=google.com; s=arc-20160816; b=giiJjKOByw5Hq2Uz/zJm3czRRoQ+DDFvTgM3Qlki/xlGYmfpxBrmeBoq0ywyLgU8QP +sCRp8hkZoGpL6psxctOztCydgAVXMMq3ylMR/xe7HZgDyhBspJ/hvHysT+sIXv9DnVn VBGtjjRTlyoF3LnOnQoVcMUUh5gd1r9GJC8Ai2qSSvm+3/UdlFS5JvwDpRaWbzJt5qTz CgRuM/g837YZfM3Qx70i7xQ7jFxWHjFrGaIfYj3s19c2dxCHNnEJfOVJd9vXjOxwtUsC vR8ii5EUQ0UmHFWboAciodeIW5s/xqXTTXyPynyMeI+wi6Svvbs+10NfttlTDsPXR3/y c7QA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=errors-to:content-transfer-encoding:list-subscribe:list-help :list-post:list-archive:list-unsubscribe:list-id:precedence:subject :mime-version:references:in-reply-to:message-id:date:to:from :dkim-signature:dkim-signature; bh=1yK2UytzNuw8fkkf8n/hPd9yz/JVJc+RwPyk3aR4Jyw=; fh=4NbAC/LsuMLI0S0hprUlLSLCiHwg6SCAifhH718Jh0Q=; b=wOuzD7+m3wEIie9Op/nF8S2b/I59EHmVrQDeOmtA5OiEbLUzkDfpOpG1cM6mS8up9Z V7tVCpdWD7t1njulbJJdBorvbbwW9w5cCNsyhzweY9lh/h6tghLOljSPlg1yK+PXjfS7 GB/Z68a3kONOpRupTIi8k7tlVbW3SXqeqRSYpsB+Hyr+HHe29xSsO0Gfcmmr3KNlpMYD druNPQpTWloxZvlhciQ8k+Vf+br0Iv8PEnKrjBbXT0nIaIEgLWLOTmRT5rUiGaO8DoBn rvB45WMka3rmGZAUg32QSsJ6EYiFAWHsnnv4YexoiEa4gHrIhlJD6squWapjDxBfWMzq J6nw== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=JPde0qtm; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=FKDkexQh; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=muc.de Received: from lists.sourceforge.net (lists.sourceforge.net. [216.105.38.7]) by mx.google.com with ESMTPS id lp22-20020a056a003d5600b006d9bc9235b9si3846439pfb.130.2024.01.12.10.20.05 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Fri, 12 Jan 2024 10:20:05 -0800 (PST) Received-SPF: pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) client-ip=216.105.38.7; Authentication-Results: mx.google.com; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=JPde0qtm; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=FKDkexQh; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=muc.de 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.95) (envelope-from ) id 1rOM83-00019i-T4; Fri, 12 Jan 2024 18:19:39 +0000 Received: from [172.30.20.202] (helo=mx.sourceforge.net) by sfs-ml-1.v29.lw.sourceforge.com with esmtps (TLS1.2) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.95) (envelope-from ) id 1rOM81-00019c-Mu for openvpn-devel@lists.sourceforge.net; Fri, 12 Jan 2024 18:19:37 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=Content-Transfer-Encoding:MIME-Version:References: In-Reply-To:Message-ID:Date:Subject:To:From:Sender:Reply-To:Cc:Content-Type: 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=FwlYP9zGNL+KYG3evBRw/vWD6BD8ZnuVBQLwYoUy8eU=; b=JPde0qtmN8eBW7mNqV2ZRlF6Wg VSz9LGkmPRDGhvF8QLjhn1dNeP6Hx0FrlBN9DGXLtniscDF/EkhKpxFaR1S1Cst6XTQJBmXpQUFDJ 0kbRqgK3ogzhO1KJdL9qE8OvW3HFp0WLHlFvHGRFCQCrtgsrIwlygMmT/2voziOV4nS0=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=Content-Transfer-Encoding:MIME-Version:References:In-Reply-To:Message-ID: Date:Subject:To:From:Sender:Reply-To:Cc:Content-Type: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=FwlYP9zGNL+KYG3evBRw/vWD6BD8ZnuVBQLwYoUy8eU=; b=FKDkexQhJGIQsRg0/tnT9mWSpT MSaWzP+SJ+DnErQoNETv0Qs5mwhfM7X3JMKCgFZuxaG6Cvin0fPL736TVaBXpyJIApBc2MSU1SCCi yaNzAwWvRtXxu0/Ee8HEsufo9y92zVUPxpSToCVS8cocGVZkTxYAaDqKgR5sZztLh5mA=; Received: from dhcp-174.greenie.muc.de ([193.149.48.174] helo=blue.greenie.muc.de) by sfi-mx-2.v28.lw.sourceforge.com with esmtps (TLS1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.95) id 1rOM7x-0004o6-Al for openvpn-devel@lists.sourceforge.net; Fri, 12 Jan 2024 18:19:37 +0000 Received: from blue.greenie.muc.de (localhost [127.0.0.1]) by blue.greenie.muc.de (8.17.1.9/8.17.1.9) with ESMTP id 40CIJNuK000498 for ; Fri, 12 Jan 2024 19:19:23 +0100 Received: (from gert@localhost) by blue.greenie.muc.de (8.17.1.9/8.17.1.9/Submit) id 40CIJNZF000497 for openvpn-devel@lists.sourceforge.net; Fri, 12 Jan 2024 19:19:23 +0100 From: Gert Doering To: openvpn-devel@lists.sourceforge.net Date: Fri, 12 Jan 2024 19:19:22 +0100 Message-ID: <20240112181922.488-1-gert@greenie.muc.de> X-Mailer: git-send-email 2.41.0 In-Reply-To: References: MIME-Version: 1.0 X-Spam-Score: -0.0 (/) X-Spam-Report: Spam detection software, running on the system "util-spamd-1.v13.lw.sourceforge.com", has NOT identified this incoming email as spam. The original message has been attached to this so you can view it or label similar future email. If you have any questions, see the administrator of that system for details. Content preview: From: Arne Schwabe This is a re-implementation of the --tls-export-cert feature. This was necessary to due to missing approval to re-license the old (now removed) code. The re-implementation is based on the following de [...] Content analysis details: (-0.0 points, 6.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- -0.0 SPF_HELO_PASS SPF: HELO matches SPF record -0.0 SPF_PASS SPF: sender matches SPF record -0.0 T_SCC_BODY_TEXT_LINE No description available. X-Headers-End: 1rOM7x-0004o6-Al Subject: [Openvpn-devel] [PATCH v11] Implement the --tls-export-cert feature 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: , Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox X-GMAIL-THRID: =?utf-8?q?1785255908351193265?= X-GMAIL-MSGID: =?utf-8?q?1787909746408347230?= From: Arne Schwabe This is a re-implementation of the --tls-export-cert feature. This was necessary to due to missing approval to re-license the old (now removed) code. The re-implementation is based on the following description of the feature provided by David: Add an option to export certificate in PEM format of the remote peer to a given directory. For example: --tls-export-cert /var/tmp This option should use a randomised filename, which is provided via a "peer_cert" environment variable for the --tls-verify script or the OPENVPN_PLUGIN_TLS_VERIFY plug-in hook. Once the script or plugin call has completed, OpenVPN should delete this file. Change-Id: Ia9b3f1813d2d0d492d17c87348b4cebd0bf19ce2 Signed-off-by: Arne Schwabe Acked-by: Gert Doering --- This change was reviewed on Gerrit and approved by at least one developer. I request to merge it to master. Gerrit URL: https://gerrit.openvpn.net/c/openvpn/+/466 This mail reflects revision 11 of this Change. Acked-by according to Gerrit (reflected above): Gert Doering diff --git a/doc/man-sections/script-options.rst b/doc/man-sections/script-options.rst index 6f90e14..e05100a 100644 --- a/doc/man-sections/script-options.rst +++ b/doc/man-sections/script-options.rst @@ -423,6 +423,14 @@ See the `Environmental Variables`_ section below for additional parameters passed as environmental variables. +--tls-export-cert dir + Adds an environment variable ``peer_cert`` when calling the + ``--tls-verify`` script or executing the OPENVPN_PLUGIN_TLS_VERIFY plugin + hook to verify the certificate. + + The environment variable contains the path to a PEM encoded certificate + of the current peer certificate in the directory ``dir``. + --up cmd Run command ``cmd`` after successful TUN/TAP device open (pre ``--user`` UID change). @@ -633,6 +641,7 @@ Name of first ``--config`` file. Set on program initiation and reset on SIGHUP. + :code:`daemon` Set to "1" if the ``--daemon`` directive is specified, or "0" otherwise. Set on program initiation and reset on SIGHUP. @@ -763,6 +772,11 @@ modifier is specified, and deleted from the environment after the script returns. +:code:`peer_cert` + If the option ``--tls-export-cert`` is enabled, this option contains + the path to the current peer certificate to be verified in PEM format. + See also the argument certificate_depth to the ``--tls-verify`` command. + :code:`proto` The ``--proto`` parameter. Set on program initiation and reset on SIGHUP. diff --git a/src/openvpn/init.c b/src/openvpn/init.c index 9e2b3845..c5cc154 100644 --- a/src/openvpn/init.c +++ b/src/openvpn/init.c @@ -3336,6 +3336,7 @@ to.auth_user_pass_verify_script_via_file = options->auth_user_pass_verify_script_via_file; to.client_crresponse_script = options->client_crresponse_script; to.tmp_dir = options->tmp_dir; + to.export_peer_cert_dir = options->tls_export_peer_cert_dir; if (options->ccd_exclusive) { to.client_config_dir_exclusive = options->client_config_dir; diff --git a/src/openvpn/options.c b/src/openvpn/options.c index f54f276..6975cbe 100644 --- a/src/openvpn/options.c +++ b/src/openvpn/options.c @@ -1995,6 +1995,7 @@ SHOW_STR(cipher_list_tls13); SHOW_STR(tls_cert_profile); SHOW_STR(tls_verify); + SHOW_STR(tls_export_peer_cert_dir); SHOW_INT(verify_x509_type); SHOW_STR(verify_x509_name); SHOW_STR_INLINE(crl_file); @@ -3062,6 +3063,7 @@ MUST_BE_UNDEF(cipher_list_tls13); MUST_BE_UNDEF(tls_cert_profile); MUST_BE_UNDEF(tls_verify); + MUST_BE_UNDEF(tls_export_peer_cert_dir); MUST_BE_UNDEF(verify_x509_name); MUST_BE_UNDEF(tls_timeout); MUST_BE_UNDEF(renegotiate_bytes); @@ -4092,6 +4094,13 @@ R_OK, "--crl-verify"); } + if (options->tls_export_peer_cert_dir) + { + errs |= check_file_access_chroot(options->chroot_dir, CHKACC_FILE, + options->tls_export_peer_cert_dir, + W_OK, "--tls-export-cert"); + } + ASSERT(options->connection_list); for (int i = 0; i < options->connection_list->len; ++i) { @@ -9041,6 +9050,11 @@ string_substitute(p[1], ',', ' ', &options->gc), "tls-verify", true); } + else if (streq(p[0], "tls-export-cert") && p[1] && !p[2]) + { + VERIFY_PERMISSION(OPT_P_SCRIPT); + options->tls_export_peer_cert_dir = p[1]; + } else if (streq(p[0], "compat-names")) { VERIFY_PERMISSION(OPT_P_GENERAL); diff --git a/src/openvpn/options.h b/src/openvpn/options.h index cbfff18..85de887 100644 --- a/src/openvpn/options.h +++ b/src/openvpn/options.h @@ -592,6 +592,7 @@ const char *tls_cert_profile; const char *ecdh_curve; const char *tls_verify; + const char *tls_export_peer_cert_dir; int verify_x509_type; const char *verify_x509_name; const char *crl_file; diff --git a/src/openvpn/ssl_common.h b/src/openvpn/ssl_common.h index 925660b..f085e0d 100644 --- a/src/openvpn/ssl_common.h +++ b/src/openvpn/ssl_common.h @@ -374,6 +374,7 @@ const char *client_crresponse_script; bool auth_user_pass_verify_script_via_file; const char *tmp_dir; + const char *export_peer_cert_dir; const char *auth_user_pass_file; bool auth_user_pass_file_inline; diff --git a/src/openvpn/ssl_verify.c b/src/openvpn/ssl_verify.c index bd7e512..55b3cf0 100644 --- a/src/openvpn/ssl_verify.c +++ b/src/openvpn/ssl_verify.c @@ -31,6 +31,7 @@ #endif #include "syshead.h" +#include #include "base64.h" #include "manage.h" @@ -457,6 +458,27 @@ gc_free(&gc); } +/** + * Exports the certificate in \c peer_cert into the environment and adds + * the filname + */ +static bool +verify_cert_cert_export_env(struct env_set *es, openvpn_x509_cert_t *peer_cert, + const char *pem_export_fname) +{ + /* export the path to the current certificate in pem file format */ + setenv_str(es, "peer_cert", pem_export_fname); + + return backend_x509_write_pem(peer_cert, pem_export_fname) == SUCCESS; +} + +static void +verify_cert_cert_delete_env(struct env_set *es, const char *pem_export_fname) +{ + env_set_del(es, "peer_cert"); + unlink(pem_export_fname); +} + /* * call --tls-verify plug-in(s) */ @@ -572,18 +594,19 @@ result_t verify_cert(struct tls_session *session, openvpn_x509_cert_t *cert, int cert_depth) { + /* need to define these variables here so goto cleanup will always have + * them defined */ result_t ret = FAILURE; - char *subject = NULL; - const struct tls_options *opt; struct gc_arena gc = gc_new(); + const char *pem_export_fname = NULL; - opt = session->opt; + const struct tls_options *opt = session->opt; ASSERT(opt); session->verified = false; /* get the X509 name */ - subject = x509_get_subject(cert, &gc); + char *subject = x509_get_subject(cert, &gc); if (!subject) { msg(D_TLS_ERRORS, "VERIFY ERROR: depth=%d, could not extract X509 " @@ -706,6 +729,19 @@ session->verify_maxlevel = max_int(session->verify_maxlevel, cert_depth); + if (opt->export_peer_cert_dir) + { + pem_export_fname = platform_create_temp_file(opt->export_peer_cert_dir, + "pef", &gc); + + if (!pem_export_fname + || !verify_cert_cert_export_env(opt->es, cert, pem_export_fname)) + { + msg(D_TLS_ERRORS, "TLS Error: Failed to export certificate for " + "--tls-export-cert in %s", opt->export_peer_cert_dir); + goto cleanup; + } + } /* export certificate values to the environment */ verify_cert_set_env(opt->es, cert, cert_depth, subject, common_name, opt->x509_track); @@ -757,12 +793,13 @@ ret = SUCCESS; cleanup: - + verify_cert_cert_delete_env(opt->es, pem_export_fname); if (ret != SUCCESS) { tls_clear_error(); /* always? */ session->verified = false; /* double sure? */ } + gc_free(&gc); return ret; diff --git a/src/openvpn/ssl_verify_backend.h b/src/openvpn/ssl_verify_backend.h index d402b1f..5301a51 100644 --- a/src/openvpn/ssl_verify_backend.h +++ b/src/openvpn/ssl_verify_backend.h @@ -161,6 +161,17 @@ struct gc_arena *gc); /* + * Write the certificate to the file in PEM format. + * + * + * @param cert Certificate to serialise. + * + * @return \c FAILURE, \c or SUCCESS + */ +result_t backend_x509_write_pem(openvpn_x509_cert_t *cert, + const char *filename); + +/* * Save X509 fields to environment, using the naming convention: * * X509_{cert_depth}_{name}={value} diff --git a/src/openvpn/ssl_verify_mbedtls.c b/src/openvpn/ssl_verify_mbedtls.c index 5612139..24a89c3 100644 --- a/src/openvpn/ssl_verify_mbedtls.c +++ b/src/openvpn/ssl_verify_mbedtls.c @@ -218,6 +218,41 @@ return buf; } +result_t +backend_x509_write_pem(openvpn_x509_cert_t *cert, const char *filename) +{ + /* mbed TLS does not make it easy to write a certificate in PEM format. + * The only way is to directly access the DER encoded raw certificate + * and PEM encode it ourselves */ + + struct gc_arena gc = gc_new(); + /* just do a very loose upper bound for the base64 based PEM encoding + * using 3 times the space for the base64 and 100 bytes for the + * headers and footer */ + struct buffer pem = alloc_buf_gc(cert->raw.len * 3 + 100, &gc); + + struct buffer der = {}; + buf_set_read(&der, cert->raw.p, cert->raw.len); + + if (!crypto_pem_encode("CERTIFICATE", &pem, &der, &gc)) + { + goto err; + } + + if (!buffer_write_file(filename, &pem)) + { + goto err; + } + + gc_free(&gc); + return SUCCESS; +err: + msg(D_TLS_DEBUG_LOW, "Error writing X509 certificate to file %s", + filename); + gc_free(&gc); + return FAILURE; +} + static struct buffer x509_get_fingerprint(const mbedtls_md_info_t *md_info, mbedtls_x509_crt *cert, struct gc_arena *gc) diff --git a/src/openvpn/ssl_verify_openssl.c b/src/openvpn/ssl_verify_openssl.c index 5afffc1..00fdec3 100644 --- a/src/openvpn/ssl_verify_openssl.c +++ b/src/openvpn/ssl_verify_openssl.c @@ -320,6 +320,29 @@ return format_hex_ex(asn1_i->data, asn1_i->length, 0, 1, ":", gc); } +result_t +backend_x509_write_pem(openvpn_x509_cert_t *cert, const char *filename) +{ + BIO *out = BIO_new_file(filename, "w"); + if (!out) + { + goto err; + } + + if (!PEM_write_bio_X509(out, cert)) + { + goto err; + } + BIO_free(out); + + return SUCCESS; +err: + BIO_free(out); + crypto_msg(D_TLS_DEBUG_LOW, "Error writing X509 certificate to file %s", + filename); + return FAILURE; +} + struct buffer x509_get_sha1_fingerprint(X509 *cert, struct gc_arena *gc) {