Skip to content

Commit

Permalink
Implement the --tls-export-cert feature
Browse files Browse the repository at this point in the history
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 <[email protected]>
Acked-by: Gert Doering <[email protected]>
Message-Id: <[email protected]>
URL: https://www.mail-archive.com/[email protected]/msg28014.html
Signed-off-by: Gert Doering <[email protected]>
  • Loading branch information
schwabe authored and cron2 committed Jan 16, 2024
1 parent f0a17ed commit c58c7c3
Show file tree
Hide file tree
Showing 9 changed files with 145 additions and 5 deletions.
14 changes: 14 additions & 0 deletions doc/man-sections/script-options.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions src/openvpn/init.c
Original file line number Diff line number Diff line change
Expand Up @@ -3336,6 +3336,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;
Expand Down
14 changes: 14 additions & 0 deletions src/openvpn/options.c
Original file line number Diff line number Diff line change
Expand Up @@ -1995,6 +1995,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);
Expand Down Expand Up @@ -3062,6 +3063,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);
Expand Down Expand Up @@ -4092,6 +4094,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)
{
Expand Down Expand Up @@ -9041,6 +9050,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);
Expand Down
1 change: 1 addition & 0 deletions src/openvpn/options.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions src/openvpn/ssl_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,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;

Expand Down
50 changes: 45 additions & 5 deletions src/openvpn/ssl_verify.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#endif

#include "syshead.h"
#include <string.h>

#include "base64.h"
#include "manage.h"
Expand Down Expand Up @@ -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)
*/
Expand Down Expand Up @@ -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 "
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down
11 changes: 11 additions & 0 deletions src/openvpn/ssl_verify_backend.h
Original file line number Diff line number Diff line change
Expand Up @@ -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:
*
Expand Down
35 changes: 35 additions & 0 deletions src/openvpn/ssl_verify_mbedtls.c
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,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)
Expand Down
23 changes: 23 additions & 0 deletions src/openvpn/ssl_verify_openssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down

0 comments on commit c58c7c3

Please sign in to comment.