From patchwork Tue Jan 16 10:15:56 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Gert Doering X-Patchwork-Id: 3568 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a05:7300:a213:b0:100:d2e5:60d with SMTP id bs19csp4089466dyb; Tue, 16 Jan 2024 02:16:52 -0800 (PST) X-Google-Smtp-Source: AGHT+IHXfcuVsLnrfS6Cashp7M7Wuc5Rfh85omaYzj5/HFGwxGw1ppXWIpDbOye+B61bQ9FH3T8Q X-Received: by 2002:a05:6a21:8199:b0:18c:198a:469b with SMTP id pd25-20020a056a21819900b0018c198a469bmr11232029pzb.6.1705400211659; Tue, 16 Jan 2024 02:16:51 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1705400211; cv=none; d=google.com; s=arc-20160816; b=zfexO806P7S2tVY+N5qGKEd1tnj6xT0IQFE50D/JSYGWVSADXcvwbSG3l6cm+8AU+X fLCPclmXGfDPXqgBtevlkskBO7oMOCzHFIChKgCN3222ibM8dSPGOkYDQpURbTQKC7hK VKA8zk1jO4WFXf4XwShyRRYbvkfHcFhM5mjuxy1Vhh5vm+TI9SnhQUXW47mIvGSJH2Os G8ltRFyuJEjZXd1ER+/i1CkXhF5Piq/JwZOAGPlux7CCm1JbEeDU7v/6BsaKKsuZcuU6 9O0m4uG6wB/nVvhawu09OcDvomETBQ767tGWLoWb++ngjljlsIuC7IDneqmqpmVUcQ9/ 7cRA== 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=eQKyFf/kfq+5OT/cPaZ44nGauPIrGt+TWq+e0O7FbT8=; fh=4NbAC/LsuMLI0S0hprUlLSLCiHwg6SCAifhH718Jh0Q=; b=uCE7YjeTSsKoCsoP3BFet2Dj9R4IQJS+PhZzEC/g62CV17Uykb57BsX9kzV/cTcaxX SAUj+2YT5VMBJaM/lDrs1pu4S1cjzSAE4ZNuYGageOyI/SZx6Ot+fJxe1Dqif4fvhcXQ tK5kOGblRn8PgjZavqEF2wsd3bosaVs0UBdONnXRcZJLZ9Yjy7WV4/IWANqFm9WfDUVW nou26GpdtbkkzklIdojJ5+jTFsHVWA6aTfy5pTz50G1yqtP6X27FiLx0HlBDMNpw5IkH xl8UzRmEoLHe3B+DqW1CqVegx9eO9S3gEz43tkE2+NdVRE4/XwPZgIaA/yAk0ulynPVe aGGg== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=AoaoGeBg; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=MH3YOupo; 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 z3-20020a170903018300b001d3f44dd4e9si11711513plg.9.2024.01.16.02.16.51 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Tue, 16 Jan 2024 02:16:51 -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=AoaoGeBg; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=MH3YOupo; 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-4.v29.lw.sourceforge.com) by sfs-ml-4.v29.lw.sourceforge.com with esmtp (Exim 4.95) (envelope-from ) id 1rPgUO-0001wO-R0; Tue, 16 Jan 2024 10:16:12 +0000 Received: from [172.30.20.202] (helo=mx.sourceforge.net) by sfs-ml-4.v29.lw.sourceforge.com with esmtps (TLS1.2) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.95) (envelope-from ) id 1rPgUM-0001wH-5s for openvpn-devel@lists.sourceforge.net; Tue, 16 Jan 2024 10:16:10 +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=j+q61wW/JvPWlgmu0yroEQFLMznJONUkwPIUExSUHM4=; b=AoaoGeBgzjhUEaPwV1Zc1ASema h2OeYB/cTFQWteEa6bD3HjpmlmO0MFUwBkrydtEqKD5sLnulq8i4UTWYMNkuJvwIFrg56rWe8KY2x nLX/YSFzfGZJT3EyTdXmNm3piQtS6fAnQFfp342v4OxOb5r/PA4yXyTEyGZJNuv0RWmw=; 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=j+q61wW/JvPWlgmu0yroEQFLMznJONUkwPIUExSUHM4=; b=MH3YOupoGS7s0z1PKY45CsG83j MGRFL7TO0QlnysiRX7FGCKBoPmIKrF0dGWtbJT/BPqCukUkGfBKr/eyqeitF01nwYhxyR/JQBS8Ua GcaEcIz9yImjoIbcqN3Q+yQsSxiev/U7FkLjATKisCyOEwphaiqaotBfBQpMPuNlSekA=; 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 1rPgUJ-0004ks-9c for openvpn-devel@lists.sourceforge.net; Tue, 16 Jan 2024 10:16:10 +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 40GAFvh7002267 for ; Tue, 16 Jan 2024 11:15:57 +0100 Received: (from gert@localhost) by blue.greenie.muc.de (8.17.1.9/8.17.1.9/Submit) id 40GAFvOU002266 for openvpn-devel@lists.sourceforge.net; Tue, 16 Jan 2024 11:15:57 +0100 From: Gert Doering To: openvpn-devel@lists.sourceforge.net Date: Tue, 16 Jan 2024 11:15:56 +0100 Message-ID: <20240116101556.2257-1-gert@greenie.muc.de> X-Mailer: git-send-email 2.43.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_PASS SPF: sender matches SPF record -0.0 SPF_HELO_PASS SPF: HELO matches SPF record -0.0 T_SCC_BODY_TEXT_LINE No description available. X-Headers-End: 1rPgUJ-0004ks-9c Subject: [Openvpn-devel] [PATCH v12] 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?1788241732273399157?= 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 12 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..75a4b2e 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,30 @@ 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"); + if (pem_export_fname) + { + unlink(pem_export_fname); + } +} + /* * call --tls-verify plug-in(s) */ @@ -572,18 +597,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 +732,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 +796,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) {