[Openvpn-devel,1/1] Rework mbedtls CRL handling

Message ID E1lUDfq-000423-Dl@sfs-ml-1.v29.lw.sourceforge.com
State Changes Requested
Headers show
Series [Openvpn-devel,1/1] Rework mbedtls CRL handling | expand

Commit Message

Maximilian Fillinger April 7, 2021, 9:15 a.m. UTC
This commit fixes the following two issues:

The config belonging to a mbedtls_ssl_ctx struct is not supposed to be
changed after mbedtls_ssl_setup() has been called. Previously, we
modified the CRL structure in place when a new CRL was loaded, but a
pointer to this struct appears in configs that are currently in use.

This commit uses reference counting to ensure that CRLs remain in memory
while the mbedtls_ssl_ctx struct and config remain.

The other issue fixed by this commit is that, if OpenVPN failed to read
the CRL in init_ssl(), but succeeded in reading it later, it would not
actually use that CRL.
---
 src/openvpn/ssl.c                |   8 +++
 src/openvpn/ssl_mbedtls.c        | 103 ++++++++++++++++++++++++++++++++++-----
 src/openvpn/ssl_mbedtls.h        |  25 +++++++++-
 src/openvpn/ssl_verify_mbedtls.c |   2 +-
 4 files changed, 125 insertions(+), 13 deletions(-)

Patch

diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c
index d8662d00..0f2cb62d 100644
--- a/src/openvpn/ssl.c
+++ b/src/openvpn/ssl.c
@@ -2737,6 +2737,14 @@  tls_process(struct tls_multi *multi,
             {
                 tls_ctx_reload_crl(&session->opt->ssl_ctx,
                                    session->opt->crl_file, session->opt->crl_file_inline);
+
+#ifdef ENABLE_CRYPTO_MBEDTLS
+                /* With mbedtls, the new CRL doesn't end up in the key_state
+                 * automatically, so we need to put it there and recreate the
+                 * mbedtls_ssl_context. */
+                update_key_state_crl(&ks->ks_ssl, &session->opt->ssl_ctx);
+#endif
+
             }
 
             /* New connection, remove any old X509 env variables */
diff --git a/src/openvpn/ssl_mbedtls.c b/src/openvpn/ssl_mbedtls.c
index 8917fb18..87a4b8df 100644
--- a/src/openvpn/ssl_mbedtls.c
+++ b/src/openvpn/ssl_mbedtls.c
@@ -105,6 +105,45 @@  tls_clear_error(void)
 {
 }
 
+/* Functions to handle reference counting for CRLs.
+ *
+ * The config of an mbedtls_ssl_context is not supposed to be changed
+ * after mbedtls_ssl_setup() has been called. Therefore, we need to
+ * leave previous CRLs in memory. They will be freed when they are no
+ * longer needed.
+ */
+static struct refcounted_crl *
+init_refcounted_crl(void)
+{
+    struct refcounted_crl *ref_crl;
+    ALLOC_OBJ(ref_crl, struct refcounted_crl);
+    mbedtls_x509_crl_init(&ref_crl->crl);
+    ref_crl->refcount = 1;
+
+    return ref_crl;
+}
+
+static struct refcounted_crl *
+get_refcounted_crl(struct refcounted_crl *ref_crl)
+{
+    ASSERT(ref_crl->refcount < SIZE_MAX);
+    ref_crl->refcount += 1;
+    return ref_crl;
+}
+
+static void
+release_refcounted_crl(struct refcounted_crl *ref_crl)
+{
+    if (ref_crl != NULL) {
+        ASSERT(ref_crl->refcount > 0);
+        if (--ref_crl->refcount == 0)
+        {
+            mbedtls_x509_crl_free(&ref_crl->crl);
+            free(ref_crl);
+        }
+    }
+}
+
 void
 tls_ctx_server_new(struct tls_root_ctx *ctx)
 {
@@ -149,8 +188,7 @@  tls_ctx_free(struct tls_root_ctx *ctx)
         mbedtls_dhm_free(ctx->dhm_ctx);
         free(ctx->dhm_ctx);
 
-        mbedtls_x509_crl_free(ctx->crl);
-        free(ctx->crl);
+        release_refcounted_crl(ctx->ref_crl);
 
 #if defined(ENABLE_PKCS11)
         pkcs11h_certificate_freeCertificate(ctx->pkcs11_cert);
@@ -1013,15 +1051,12 @@  backend_tls_ctx_reload_crl(struct tls_root_ctx *ctx, const char *crl_file,
 {
     ASSERT(crl_file);
 
-    if (ctx->crl == NULL)
-    {
-        ALLOC_OBJ_CLEAR(ctx->crl, mbedtls_x509_crl);
-    }
-    mbedtls_x509_crl_free(ctx->crl);
+    release_refcounted_crl(ctx->ref_crl);
+    ctx->ref_crl = init_refcounted_crl();
 
     if (crl_inline)
     {
-        if (!mbed_ok(mbedtls_x509_crl_parse(ctx->crl,
+        if (!mbed_ok(mbedtls_x509_crl_parse(&ctx->ref_crl->crl,
                                             (const unsigned char *)crl_file,
                                             strlen(crl_file) + 1)))
         {
@@ -1031,7 +1066,7 @@  backend_tls_ctx_reload_crl(struct tls_root_ctx *ctx, const char *crl_file,
     }
     else
     {
-        if (!mbed_ok(mbedtls_x509_crl_parse_file(ctx->crl, crl_file)))
+        if (!mbed_ok(mbedtls_x509_crl_parse_file(&ctx->ref_crl->crl, crl_file)))
         {
             msg(M_WARN, "CRL: cannot read CRL from file %s", crl_file);
             goto err;
@@ -1040,7 +1075,9 @@  backend_tls_ctx_reload_crl(struct tls_root_ctx *ctx, const char *crl_file,
     return;
 
 err:
-    mbedtls_x509_crl_free(ctx->crl);
+    /* Leaving the CRL in the freed state causes all connection attempts to
+     * be rejected. */
+    mbedtls_x509_crl_free(&ctx->ref_crl->crl);
 }
 
 void
@@ -1121,8 +1158,17 @@  key_state_ssl_init(struct key_state_ssl *ks_ssl,
     }
     mbedtls_ssl_conf_verify(ks_ssl->ssl_config, verify_callback, session);
 
+    if (ssl_ctx->ref_crl)
+    {
+        ks_ssl->ref_crl = get_refcounted_crl(ssl_ctx->ref_crl);
+    }
+    else
+    {
+        ks_ssl->ref_crl = NULL;
+    }
+
     /* TODO: mbed TLS does not currently support sending the CA chain to the client */
-    mbedtls_ssl_conf_ca_chain(ks_ssl->ssl_config, ssl_ctx->ca_chain, ssl_ctx->crl);
+    mbedtls_ssl_conf_ca_chain(ks_ssl->ssl_config, ssl_ctx->ca_chain, &ks_ssl->ref_crl->crl);
 
     /* Initialize minimum TLS version */
     {
@@ -1196,10 +1242,45 @@  key_state_ssl_free(struct key_state_ssl *ks_ssl)
             buf_free_entries(&ks_ssl->bio_ctx->out);
             free(ks_ssl->bio_ctx);
         }
+        release_refcounted_crl(ks_ssl->ref_crl);
         CLEAR(*ks_ssl);
     }
 }
 
+void
+update_key_state_crl(struct key_state_ssl *ks_ssl, struct tls_root_ctx *ctx)
+{
+    ASSERT(ks_ssl != NULL);
+    ASSERT(ks_ssl->ctx != NULL);
+    ASSERT(ks_ssl->bio_ctx != NULL);
+
+    /* Free the old mbedtls_ssl_context struct. The config should not be
+     * updated while it is in use. */
+    mbedtls_ssl_free(ks_ssl->ctx);
+
+    release_refcounted_crl(ks_ssl->ref_crl);
+    if (ctx->ref_crl != NULL)
+    {
+        ks_ssl->ref_crl = get_refcounted_crl(ctx->ref_crl);
+    }
+    else
+    {
+        ks_ssl->ref_crl = NULL;
+    }
+
+    /* Set new CRL in ssl_config. */
+    mbedtls_ssl_conf_ca_chain(ks_ssl->ssl_config, ctx->ca_chain, &ks_ssl->ref_crl->crl);
+
+    /* Initialize new mbedtls_ssl_context. */
+    mbedtls_ssl_init(ks_ssl->ctx);
+    mbedtls_ssl_setup(ks_ssl->ctx, ks_ssl->ssl_config);
+
+    buf_free_entries(&ks_ssl->bio_ctx->in);
+    buf_free_entries(&ks_ssl->bio_ctx->out);
+    mbedtls_ssl_set_bio(ks_ssl->ctx, ks_ssl->bio_ctx, ssl_bio_write,
+                        ssl_bio_read, NULL);
+}
+
 int
 key_state_write_plaintext(struct key_state_ssl *ks, struct buffer *buf)
 {
diff --git a/src/openvpn/ssl_mbedtls.h b/src/openvpn/ssl_mbedtls.h
index ff64e17c..1be5c749 100644
--- a/src/openvpn/ssl_mbedtls.h
+++ b/src/openvpn/ssl_mbedtls.h
@@ -96,6 +96,17 @@  struct tls_key_cache { };
 #endif
 
 /**
+ * This structure wraps a CRL and counts how many structures currently
+ * have a pointer to it. The config of a mbedtls_ssl_context is not
+ * supposed to be changed after changed after setup, so we have to leave
+ * old CRLs in memory after loading a new one.
+ */
+struct refcounted_crl {
+    mbedtls_x509_crl crl;
+    size_t refcount;
+};
+
+/**
  * Structure that wraps the TLS context. Contents differ depending on the
  * SSL library used.
  *
@@ -110,7 +121,7 @@  struct tls_root_ctx {
     mbedtls_x509_crt *crt_chain;        /**< Local Certificate chain */
     mbedtls_x509_crt *ca_chain;         /**< CA chain for remote verification */
     mbedtls_pk_context *priv_key;       /**< Local private key */
-    mbedtls_x509_crl *crl;              /**< Certificate Revocation List */
+    struct refcounted_crl *ref_crl;     /**< Certificate Revocation List */
     time_t crl_last_mtime;              /**< CRL last modification time */
     off_t crl_last_size;                /**< size of last loaded CRL */
 #ifdef ENABLE_PKCS11
@@ -125,6 +136,7 @@  struct tls_root_ctx {
 struct key_state_ssl {
     mbedtls_ssl_config *ssl_config;     /**< mbedTLS global ssl config */
     mbedtls_ssl_context *ctx;           /**< mbedTLS connection context */
+    struct refcounted_crl *ref_crl;     /**< CRL belonging to ssl_config and ctx */
     bio_ctx *bio_ctx;
 
     struct tls_key_cache tls_key_cache;
@@ -144,4 +156,15 @@  int tls_ctx_use_external_signing_func(struct tls_root_ctx *ctx,
                                       external_sign_func sign_func,
                                       void *sign_ctx);
 
+/**
+ * Update the Certificate Revocation List in ks_ssl.
+ *
+ * This involves creating a new mbedtls_ssl_context in ks_ssl and should only
+ * be called if that context has not been used yet.
+ *
+ * @param ks_ssl        The SSL channel's state info
+ * @param ctx           The TLS context with the new CRL
+ */
+void update_key_state_crl(struct key_state_ssl *ks_ssl, struct tls_root_ctx *ctx);
+
 #endif /* SSL_MBEDTLS_H_ */
diff --git a/src/openvpn/ssl_verify_mbedtls.c b/src/openvpn/ssl_verify_mbedtls.c
index ef3847bb..03ae9355 100644
--- a/src/openvpn/ssl_verify_mbedtls.c
+++ b/src/openvpn/ssl_verify_mbedtls.c
@@ -560,7 +560,7 @@  bool
 tls_verify_crl_missing(const struct tls_options *opt)
 {
     if (opt->crl_file && !(opt->ssl_flags & SSLF_CRL_VERIFY_DIR)
-        && (opt->ssl_ctx.crl == NULL || opt->ssl_ctx.crl->version == 0))
+        && (opt->ssl_ctx.ref_crl == NULL || opt->ssl_ctx.ref_crl->crl.version == 0))
     {
         return true;
     }