From: Max Fillinger Date: Wed, 19 Nov 2025 14:01:43 +0000 (+0100) Subject: Add option to check tls-crypt-v2 key timestamps X-Git-Url: https://git.feebdaed.xyz/?a=commitdiff_plain;h=20b234b23d483cecfb5dec07ae5f9d8b393847a7;p=0xmirror%2Fopenvpn.git Add option to check tls-crypt-v2 key timestamps This commit adds the option --tls-crypt-v2-max-age n. When a client key is older than n days or has no timestamp, the server rejects it. Based on work by Rein van Baaren for Sentyron. Co-authored-by: Rein van Baaren Change-Id: I0579d18c784e2ac16973d5553992c28f281a0900 Signed-off-by: Max Fillinger Acked-by: Arne Schwabe Gerrit URL: https://gerrit.openvpn.net/c/openvpn/+/1304 Message-Id: <20251119140149.31867-1-gert@greenie.muc.de> URL: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg34545.html Signed-off-by: Gert Doering --- diff --git a/doc/man-sections/tls-options.rst b/doc/man-sections/tls-options.rst index db107e66..63cb32f9 100644 --- a/doc/man-sections/tls-options.rst +++ b/doc/man-sections/tls-options.rst @@ -568,6 +568,10 @@ certificates and keys: https://github.com/OpenVPN/easy-rsa The command can reject the connection by exiting with a non-zero exit code. +--tls-crypt-v2-max-age n + Reject tls-crypt-v2 client keys that are older than n days or have + no timestamp. + --tls-exit Exit on TLS negotiation failure. This option can be useful when you only want to make one attempt at connecting, e.g. in a test or monitoring script. diff --git a/doc/tls-crypt-v2.txt b/doc/tls-crypt-v2.txt index 7dcd0415..c2e9debc 100644 --- a/doc/tls-crypt-v2.txt +++ b/doc/tls-crypt-v2.txt @@ -139,7 +139,10 @@ When setting up the openvpn connection: The message is dropped and no error response is sent when either 3.1, 3.2 or 3.3 fails (DoS protection). -4. Server optionally checks metadata using a --tls-crypt-v2-verify script +4. The server optionally checks if the client key contains a timestamp that is + below a maximum age configured with the --tls-crypt-v2-max-age option. + +5. Server optionally checks metadata using a --tls-crypt-v2-verify script This allows early abort of connection, *before* we expose any of the notoriously dangerous TLS, X.509 and ASN.1 parsers and thereby reduces the diff --git a/src/openvpn/init.c b/src/openvpn/init.c index 8d95d5c1..fc079e11 100644 --- a/src/openvpn/init.c +++ b/src/openvpn/init.c @@ -3418,6 +3418,7 @@ do_init_crypto_tls(struct context *c, const unsigned int flags) { to.tls_wrap.tls_crypt_v2_server_key = c->c1.ks.tls_crypt_v2_server_key; to.tls_crypt_v2_verify_script = c->options.tls_crypt_v2_verify_script; + to.tls_crypt_v2_max_age = c->options.tls_crypt_v2_max_age; if (options->ce.tls_crypt_v2_force_cookie) { to.tls_wrap.opt.flags |= CO_FORCE_TLSCRYPTV2_COOKIE; diff --git a/src/openvpn/options.c b/src/openvpn/options.c index 683543a8..4794315c 100644 --- a/src/openvpn/options.c +++ b/src/openvpn/options.c @@ -648,6 +648,8 @@ static const char usage_message[] = " fresh tls-crypt-v2 server key, and store to keyfile\n" "--tls-crypt-v2-verify cmd : Run command cmd to verify the metadata of the\n" " client-supplied tls-crypt-v2 client key\n" + "--tls-crypt-v2-max-age n : Only accept tls-crypt-v2 client keys that have a\n" + " timestamp which is at most n days old.\n" "--askpass [file]: Get PEM password from controlling tty before we daemonize.\n" "--auth-nocache : Don't cache --askpass or --auth-user-pass passwords.\n" "--crl-verify crl ['dir']: Check peer certificate against a CRL.\n" @@ -9079,6 +9081,14 @@ add_option(struct options *options, char *p[], bool is_inline, const char *file, VERIFY_PERMISSION(OPT_P_GENERAL); options->tls_crypt_v2_verify_script = p[1]; } + else if (streq(p[0], "tls-crypt-v2-max-age") && p[1]) + { + VERIFY_PERMISSION(OPT_P_GENERAL); + if (!atoi_constrained(p[1], &options->tls_crypt_v2_max_age, "tls-crypt-v2-max-age", 1, INT_MAX, msglevel)) + { + goto err; + } + } else if (streq(p[0], "x509-track") && p[1] && !p[2]) { VERIFY_PERMISSION(OPT_P_GENERAL); diff --git a/src/openvpn/options.h b/src/openvpn/options.h index 9d2ff9fd..42db9cae 100644 --- a/src/openvpn/options.h +++ b/src/openvpn/options.h @@ -678,6 +678,8 @@ struct options const char *tls_crypt_v2_verify_script; + int tls_crypt_v2_max_age; + /* Allow only one session */ bool single_session; diff --git a/src/openvpn/ssl_common.h b/src/openvpn/ssl_common.h index 23da8cf5..3129299b 100644 --- a/src/openvpn/ssl_common.h +++ b/src/openvpn/ssl_common.h @@ -383,6 +383,7 @@ struct tls_options bool tls_crypt_v2; const char *tls_crypt_v2_verify_script; + int tls_crypt_v2_max_age; /** TLS handshake wrapping state */ struct tls_wrap_ctx tls_wrap; diff --git a/src/openvpn/tls_crypt.c b/src/openvpn/tls_crypt.c index ab719b38..318c9392 100644 --- a/src/openvpn/tls_crypt.c +++ b/src/openvpn/tls_crypt.c @@ -29,6 +29,7 @@ #include "argv.h" #include "base64.h" #include "crypto.h" +#include "integer.h" #include "platform.h" #include "run_command.h" #include "session_id.h" @@ -519,6 +520,34 @@ error_exit: return ret; } +static bool +tls_crypt_v2_check_client_key_age(const struct tls_wrap_ctx *ctx, int max_days) +{ + if (ctx->tls_crypt_v2_metadata.len < 1 + sizeof(int64_t)) + { + msg(M_WARN, "ERROR: Client key metadata is too small to contain a timestamp."); + return false; + } + + const uint8_t *metadata = ctx->tls_crypt_v2_metadata.data; + if (*metadata != TLS_CRYPT_METADATA_TYPE_TIMESTAMP) + { + msg(M_WARN, "ERROR: Client key does not have a timestamp."); + return false; + } + + int64_t timestamp; + memcpy(×tamp, metadata + 1, sizeof(int64_t)); + timestamp = (int64_t)ntohll((uint64_t)timestamp); + int64_t max_age_in_seconds = max_days * 24 * 60 * 60; + if (now - timestamp > max_age_in_seconds) + { + msg(M_WARN, "ERROR: Client key is too old."); + return false; + } + return true; +} + static bool tls_crypt_v2_verify_metadata(const struct tls_wrap_ctx *ctx, const struct tls_options *opt) { @@ -634,6 +663,12 @@ tls_crypt_v2_extract_client_key(struct buffer *buf, struct tls_wrap_ctx *ctx, return false; } + if (opt && opt->tls_crypt_v2_max_age > 0 && !tls_crypt_v2_check_client_key_age(ctx, opt->tls_crypt_v2_max_age)) + { + secure_memzero(&ctx->original_wrap_keydata, sizeof(ctx->original_wrap_keydata)); + return false; + } + if (opt && opt->tls_crypt_v2_verify_script && !tls_crypt_v2_verify_metadata(ctx, opt)) { secure_memzero(&ctx->original_wrap_keydata, sizeof(ctx->original_wrap_keydata));