| 
      
      
      From: MaxF (C. Review) <ge...@op...> - 2025-10-26 16:45:10
      
     | 
| Attention is currently required from: flichtenheld, plaisthos.
Hello flichtenheld, plaisthos, 
I'd like you to reexamine a change. Please visit
    http://gerrit.openvpn.net/c/openvpn/+/1304?usp=email
to look at the new patch set (#2).
Change subject: Add option to check tls-crypt-v2 key timestamps
......................................................................
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 <rev...@pr...>
Change-Id: I0579d18c784e2ac16973d5553992c28f281a0900
---
M doc/man-sections/tls-options.rst
M doc/tls-crypt-v2.txt
M src/openvpn/init.c
M src/openvpn/options.c
M src/openvpn/options.h
M src/openvpn/ssl_common.h
M src/openvpn/tls_crypt.c
7 files changed, 49 insertions(+), 1 deletion(-)
  git pull ssh://gerrit.openvpn.net:29418/openvpn refs/changes/04/1304/2
diff --git a/doc/man-sections/tls-options.rst b/doc/man-sections/tls-options.rst
index db107e6..63cb32f 100644
--- a/doc/man-sections/tls-options.rst
+++ b/doc/man-sections/tls-options.rst
@@ -568,6 +568,10 @@
   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 7dcd041..c2e9deb 100644
--- a/doc/tls-crypt-v2.txt
+++ b/doc/tls-crypt-v2.txt
@@ -139,7 +139,10 @@
    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 aa2611d..a6331dc 100644
--- a/src/openvpn/init.c
+++ b/src/openvpn/init.c
@@ -3442,6 +3442,7 @@
         {
             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 65c6b3b..2cdf58f 100644
--- a/src/openvpn/options.c
+++ b/src/openvpn/options.c
@@ -653,6 +653,8 @@
     "                  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"
@@ -9087,6 +9089,14 @@
         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 009904a..9329030 100644
--- a/src/openvpn/options.h
+++ b/src/openvpn/options.h
@@ -682,6 +682,8 @@
 
     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 de89d30..0402a6a 100644
--- a/src/openvpn/ssl_common.h
+++ b/src/openvpn/ssl_common.h
@@ -383,6 +383,7 @@
 
     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 51b4eb3..d4c516c 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"
@@ -528,6 +529,27 @@
 }
 
 static bool
+tls_crypt_v2_check_client_key_age(const struct tls_wrap_ctx *ctx, int max_days)
+{
+    const uint8_t *metadata = ctx->tls_crypt_v2_metadata.data;
+    if (*metadata != TLS_CRYPT_METADATA_TYPE_TIMESTAMP)
+    {
+        msg(M_WARN, "ERROR: Client key doesn't 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)
 {
     bool ret = false;
@@ -652,6 +674,11 @@
     /* Remove client key from buffer so tls-crypt code can unwrap message */
     ASSERT(buf_inc_len(buf, -(BLEN(&wrapped_client_key))));
 
+    if (opt && opt->tls_crypt_v2_max_age > 0)
+    {
+        return tls_crypt_v2_check_client_key_age(ctx, opt->tls_crypt_v2_max_age);
+    }
+
     if (opt && opt->tls_crypt_v2_verify_script)
     {
         return tls_crypt_v2_verify_metadata(ctx, opt);
-- 
To view, visit http://gerrit.openvpn.net/c/openvpn/+/1304?usp=email
To unsubscribe, or for help writing mail filters, visit http://gerrit.openvpn.net/settings?usp=email
Gerrit-MessageType: newpatchset
Gerrit-Project: openvpn
Gerrit-Branch: master
Gerrit-Change-Id: I0579d18c784e2ac16973d5553992c28f281a0900
Gerrit-Change-Number: 1304
Gerrit-PatchSet: 2
Gerrit-Owner: MaxF <ma...@ma...>
Gerrit-Reviewer: flichtenheld <fr...@li...>
Gerrit-Reviewer: plaisthos <arn...@rf...>
Gerrit-CC: openvpn-devel <ope...@li...>
Gerrit-Attention: plaisthos <arn...@rf...>
Gerrit-Attention: flichtenheld <fr...@li...>
 |