From d27cb14891f3ac40e86062c475df139bbe2c6066 Mon Sep 17 00:00:00 2001 From: Arne Schwabe Date: Tue, 16 Jan 2024 11:15:56 +0100 Subject: [PATCH] Implement the --tls-export-cert feature 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 Message-Id: <20240116101556.2257-1-gert@greenie.muc.de> URL: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg28014.html Signed-off-by: Gert Doering (cherry picked from commit c58c7c3c669461805956dabc703c1279fe58eeee) --- doc/man-sections/script-options.rst | 14 ++++++++ src/openvpn/init.c | 1 + src/openvpn/options.c | 14 ++++++++ src/openvpn/options.h | 1 + src/openvpn/ssl_common.h | 1 + src/openvpn/ssl_verify.c | 50 ++++++++++++++++++++++++++--- src/openvpn/ssl_verify_backend.h | 11 +++++++ src/openvpn/ssl_verify_mbedtls.c | 35 ++++++++++++++++++++ src/openvpn/ssl_verify_openssl.c | 23 +++++++++++++ 9 files changed, 145 insertions(+), 5 deletions(-) diff --git a/doc/man-sections/script-options.rst b/doc/man-sections/script-options.rst index 6f90e14dc5f..e05100a27f7 100644 --- a/doc/man-sections/script-options.rst +++ b/doc/man-sections/script-options.rst @@ -423,6 +423,14 @@ SCRIPT HOOKS 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 @@ instances. 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 @@ instances. 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 e1b313a2948..aecbf613e52 100644 --- a/src/openvpn/init.c +++ b/src/openvpn/init.c @@ -3356,6 +3356,7 @@ do_init_crypto_tls(struct context *c, const unsigned int flags) 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 26b56484a95..05209327c52 100644 --- a/src/openvpn/options.c +++ b/src/openvpn/options.c @@ -1996,6 +1996,7 @@ show_settings(const struct options *o) 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); @@ -3063,6 +3064,7 @@ options_postprocess_verify_ce(const struct options *options, 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); @@ -4093,6 +4095,13 @@ options_postprocess_filechecks(struct options *options) 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) { @@ -9030,6 +9039,11 @@ add_option(struct options *options, 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 2aacfea6b8c..8c75a85f9a8 100644 --- a/src/openvpn/options.h +++ b/src/openvpn/options.h @@ -592,6 +592,7 @@ struct options 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 8d8668a9251..62947016b63 100644 --- a/src/openvpn/ssl_common.h +++ b/src/openvpn/ssl_common.h @@ -375,6 +375,7 @@ struct tls_options 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 bd7e5125136..75a4b2ec19a 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 @@ verify_cert_set_env(struct env_set *es, openvpn_x509_cert_t *peer_cert, int cert 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 @@ verify_check_crl_dir(const char *crl_dir, openvpn_x509_cert_t *cert, 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 @@ verify_cert(struct tls_session *session, openvpn_x509_cert_t *cert, int cert_dep 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 @@ verify_cert(struct tls_session *session, openvpn_x509_cert_t *cert, int cert_dep 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 d402b1f2404..5301a51d9fe 100644 --- a/src/openvpn/ssl_verify_backend.h +++ b/src/openvpn/ssl_verify_backend.h @@ -160,6 +160,17 @@ char *backend_x509_get_serial(openvpn_x509_cert_t *cert, struct gc_arena *gc); char *backend_x509_get_serial_hex(openvpn_x509_cert_t *cert, 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: * diff --git a/src/openvpn/ssl_verify_mbedtls.c b/src/openvpn/ssl_verify_mbedtls.c index 4596843c0ca..212a3c6cde1 100644 --- a/src/openvpn/ssl_verify_mbedtls.c +++ b/src/openvpn/ssl_verify_mbedtls.c @@ -217,6 +217,41 @@ backend_x509_get_serial_hex(mbedtls_x509_crt *cert, struct gc_arena *gc) 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 5afffc1fdac..00fdec31d61 100644 --- a/src/openvpn/ssl_verify_openssl.c +++ b/src/openvpn/ssl_verify_openssl.c @@ -320,6 +320,29 @@ backend_x509_get_serial_hex(openvpn_x509_cert_t *cert, struct gc_arena *gc) 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) {