]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
spnego_gssapi: implement TLS channel bindings for openssl
authorMax Faxälv <max.faxalv@sony.com>
Thu, 29 Feb 2024 08:12:59 +0000 (09:12 +0100)
committerDaniel Stenberg <daniel@haxx.se>
Mon, 12 Aug 2024 17:16:54 +0000 (19:16 +0200)
Channel Bindings are used to tie the session context to a specific TLS
channel. This is to provide additional proof of valid identity,
mitigating authentication relay attacks.

Major web servers have the ability to require (None/Accept/Require)
GSSAPI channel binding, rendering Curl unable to connect to such
websites unless support for channel bindings is implemented.

IIS calls this feature Extended Protection (EPA), which is used in
Enterprise environments using Kerberos for authentication.

This change require krb5 >= 1.19, otherwise channel bindings won't be
forwarded through SPNEGO.

Co-Authored-By: Steffen Kieß <947515+steffen-kiess@users.noreply.github.com>
Closes #13098

14 files changed:
lib/http_negotiate.c
lib/urldata.h
lib/vauth/spnego_gssapi.c
lib/vtls/bearssl.c
lib/vtls/gtls.c
lib/vtls/mbedtls.c
lib/vtls/openssl.c
lib/vtls/rustls.c
lib/vtls/schannel.c
lib/vtls/sectransp.c
lib/vtls/vtls.c
lib/vtls/vtls.h
lib/vtls/vtls_int.h
lib/vtls/wolfssl.c

index 629de834f12a46c05df49de37b93808fff25b771..26e475c27342b118dcc9a9c3de4b8a58ea56011a 100644 (file)
@@ -30,6 +30,7 @@
 #include "sendf.h"
 #include "http_negotiate.h"
 #include "vauth/vauth.h"
+#include "vtls/vtls.h"
 
 /* The last 3 #include files should be in this order */
 #include "curl_printf.h"
@@ -106,11 +107,27 @@ CURLcode Curl_input_negotiate(struct Curl_easy *data, struct connectdata *conn,
 #if defined(USE_WINDOWS_SSPI) && defined(SECPKG_ATTR_ENDPOINT_BINDINGS)
   neg_ctx->sslContext = conn->sslContext;
 #endif
+  /* Check if the connection is using SSL and get the channel binding data */
+#ifdef HAVE_GSSAPI
+  if(conn->handler->flags & PROTOPT_SSL) {
+    Curl_dyn_init(&neg_ctx->channel_binding_data, SSL_CB_MAX_SIZE);
+    result = Curl_ssl_get_channel_binding(
+      data, FIRSTSOCKET, &neg_ctx->channel_binding_data);
+    if(result) {
+      Curl_http_auth_cleanup_negotiate(conn);
+      return result;
+    }
+  }
+#endif
 
   /* Initialize the security context and decode our challenge */
   result = Curl_auth_decode_spnego_message(data, userp, passwdp, service,
                                            host, header, neg_ctx);
 
+#ifdef HAVE_GSSAPI
+  Curl_dyn_free(&neg_ctx->channel_binding_data);
+#endif
+
   if(result)
     Curl_http_auth_cleanup_negotiate(conn);
 
index 2e71abdaf723229d190091b3e41eeee8b07a0772..bc7ee989ad0b39cc3377aca91c0ae48c9e279dad 100644 (file)
@@ -455,6 +455,7 @@ struct negotiatedata {
   gss_ctx_id_t context;
   gss_name_t spn;
   gss_buffer_desc output_token;
+  struct dynbuf channel_binding_data;
 #else
 #ifdef USE_WINDOWS_SSPI
 #ifdef SECPKG_ATTR_ENDPOINT_BINDINGS
index 23822838be3f2b267af89fb0eaeceb90228bcac3..f48d9b7000b257937b850b6b137fec11cff8b020 100644 (file)
@@ -91,6 +91,8 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data,
   gss_buffer_desc spn_token = GSS_C_EMPTY_BUFFER;
   gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
   gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
+  gss_channel_bindings_t chan_bindings = GSS_C_NO_CHANNEL_BINDINGS;
+  struct gss_channel_bindings_struct chan;
 
   (void) user;
   (void) password;
@@ -148,13 +150,21 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data,
     input_token.length = chlglen;
   }
 
+  /* Set channel binding data if available */
+  if(nego->channel_binding_data.leng > 0) {
+    memset(&chan, 0, sizeof(struct gss_channel_bindings_struct));
+    chan.application_data.length = nego->channel_binding_data.leng;
+    chan.application_data.value = nego->channel_binding_data.bufr;
+    chan_bindings = &chan;
+  }
+
   /* Generate our challenge-response message */
   major_status = Curl_gss_init_sec_context(data,
                                            &minor_status,
                                            &nego->context,
                                            nego->spn,
                                            &Curl_spnego_mech_oid,
-                                           GSS_C_NO_CHANNEL_BINDINGS,
+                                           chan_bindings,
                                            &input_token,
                                            &output_token,
                                            TRUE,
index edd6ca90cc26213b49b78b13cf892a56c3f42b34..ad70dcbcae8021ef01ba3ef1907306c86249c7ac 100644 (file)
@@ -1144,6 +1144,7 @@ const struct Curl_ssl Curl_ssl_bearssl = {
   NULL,                            /* disassociate_connection */
   bearssl_recv,                    /* recv decrypted data */
   bearssl_send,                    /* send data to encrypt */
+  NULL,                            /* get_channel_binding */
 };
 
 #endif /* USE_BEARSSL */
index fb496955d52accb1f21e8bd8d75314b58fd80b18..89c497c475787095b9fe08412008ef3c495102b6 100644 (file)
@@ -2020,6 +2020,7 @@ const struct Curl_ssl Curl_ssl_gnutls = {
   NULL,                          /* disassociate_connection */
   gtls_recv,                     /* recv decrypted data */
   gtls_send,                     /* send data to encrypt */
+  NULL,                          /* get_channel_binding */
 };
 
 #endif /* USE_GNUTLS */
index 741cc7c7c64a4a6ee9ce3bb49b3cff18838ea007..782ded29cfb227ee0ee502db7364784675b9a528 100644 (file)
@@ -1754,6 +1754,7 @@ const struct Curl_ssl Curl_ssl_mbedtls = {
   NULL,                             /* disassociate_connection */
   mbed_recv,                        /* recv decrypted data */
   mbed_send,                        /* send data to encrypt */
+  NULL,                             /* get_channel_binding */
 };
 
 #endif /* USE_MBEDTLS */
index 671299d43d5fb9365909813427efe1d729ef3924..9ac967620e47c98ba0c21bd420d44ae0225d4e3c 100644 (file)
@@ -5055,6 +5055,91 @@ out:
   return nread;
 }
 
+static CURLcode ossl_get_channel_binding(struct Curl_easy *data, int sockindex,
+                                         struct dynbuf *binding)
+{
+  /* required for X509_get_signature_nid support */
+#if OPENSSL_VERSION_NUMBER > 0x10100000L
+  X509 *cert;
+  int algo_nid;
+  const EVP_MD *algo_type;
+  const char *algo_name;
+  unsigned int length;
+  unsigned char buf[EVP_MAX_MD_SIZE];
+
+  const char prefix[] = "tls-server-end-point:";
+  struct connectdata *conn = data->conn;
+  struct Curl_cfilter *cf = conn->cfilter[sockindex];
+  struct ossl_ctx *octx = NULL;
+
+  do {
+    const struct Curl_cftype *cft = cf->cft;
+    struct ssl_connect_data *connssl = cf->ctx;
+
+    if(cft->name && !strcmp(cft->name, "SSL")) {
+      octx = (struct ossl_ctx *)connssl->backend;
+      break;
+    }
+
+    if(cf->next)
+      cf = cf->next;
+
+  } while(cf->next);
+
+  if(!octx) {
+    failf(data,
+          "Failed to find SSL backend for endpoint");
+    return CURLE_SSL_ENGINE_INITFAILED;
+  }
+
+  cert = SSL_get1_peer_certificate(octx->ssl);
+  if(!cert) {
+    /* No server certificate, don't do channel binding */
+    return CURLE_OK;
+  }
+
+  if(!OBJ_find_sigid_algs(X509_get_signature_nid(cert), &algo_nid, NULL)) {
+    failf(data,
+          "Unable to find digest NID for certificate signature algorithm");
+    return CURLE_SSL_INVALIDCERTSTATUS;
+  }
+
+  /* https://datatracker.ietf.org/doc/html/rfc5929#section-4.1 */
+  if(algo_nid == NID_md5 || algo_nid == NID_sha1) {
+    algo_type = EVP_sha256();
+  }
+  else {
+    algo_type = EVP_get_digestbynid(algo_nid);
+    if(!algo_type) {
+      algo_name = OBJ_nid2sn(algo_nid);
+      failf(data, "Could not find digest algorithm %s (NID %d)",
+            algo_name ? algo_name : "(null)", algo_nid);
+      return CURLE_SSL_INVALIDCERTSTATUS;
+    }
+  }
+
+  if(!X509_digest(cert, algo_type, buf, &length)) {
+    failf(data, "X509_digest() failed");
+    return CURLE_SSL_INVALIDCERTSTATUS;
+  }
+
+  /* Append "tls-server-end-point:" */
+  if(Curl_dyn_addn(binding, prefix, sizeof(prefix) - 1) != CURLE_OK)
+    return CURLE_OUT_OF_MEMORY;
+  /* Append digest */
+  if(Curl_dyn_addn(binding, buf, length))
+    return CURLE_OUT_OF_MEMORY;
+
+  return CURLE_OK;
+#else
+  /* No X509_get_signature_nid support */
+  (void)data; /* unused */
+  (void)sockindex; /* unused */
+  (void)binding; /* unused */
+  return CURLE_OK;
+#endif
+}
+
 static size_t ossl_version(char *buffer, size_t size)
 {
 #ifdef LIBRESSL_VERSION_NUMBER
@@ -5244,6 +5329,7 @@ const struct Curl_ssl Curl_ssl_openssl = {
   NULL,                     /* remote of data from this connection */
   ossl_recv,                /* recv decrypted data */
   ossl_send,                /* send data to encrypt */
+  ossl_get_channel_binding  /* get_channel_binding */
 };
 
 #endif /* USE_OPENSSL */
index 153aa4d175b8030418014eb162be0d44fa68ab1a..d86c81754aa54c750a3bb13c2eac32cf78bb786f 100644 (file)
@@ -873,6 +873,7 @@ const struct Curl_ssl Curl_ssl_rustls = {
   NULL,                            /* disassociate_connection */
   cr_recv,                         /* recv decrypted data */
   cr_send,                         /* send data to encrypt */
+  NULL,                            /* get_channel_binding */
 };
 
 #endif /* USE_RUSTLS */
index f6c17406a25543a4439f246cb8ab767303aedc0f..df248782b44668c36c563c3de2d20d59c054a7c6 100644 (file)
@@ -2997,6 +2997,7 @@ const struct Curl_ssl Curl_ssl_schannel = {
   NULL,                              /* disassociate_connection */
   schannel_recv,                     /* recv decrypted data */
   schannel_send,                     /* send data to encrypt */
+  NULL,                              /* get_channel_binding */
 };
 
 #endif /* USE_SCHANNEL */
index b7e6f7e2bd6e7355fbb1bc7ee4d48fc4a1ecfa32..9de1a4f9d38f147da72afb3056176decdba1d8db 100644 (file)
@@ -2916,6 +2916,7 @@ const struct Curl_ssl Curl_ssl_sectransp = {
   NULL,                               /* disassociate_connection */
   sectransp_recv,                     /* recv decrypted data */
   sectransp_send,                     /* send data to encrypt */
+  NULL,                               /* get_channel_binding */
 };
 
 #ifdef __GNUC__
index e778464cf6279881d0e245ffa9079a5bd8b67f34..bbb964af2a00c283d9338c44ff33e270368c9038 100644 (file)
@@ -749,6 +749,14 @@ out:
   return CURLE_OK;
 }
 
+CURLcode Curl_ssl_get_channel_binding(struct Curl_easy *data, int sockindex,
+                                       struct dynbuf *binding)
+{
+  if(Curl_ssl->get_channel_binding)
+    return Curl_ssl->get_channel_binding(data, sockindex, binding);
+  return CURLE_OK;
+}
+
 void Curl_ssl_close_all(struct Curl_easy *data)
 {
   /* kill the session ID cache if not shared */
@@ -1338,6 +1346,7 @@ static const struct Curl_ssl Curl_ssl_multi = {
   NULL,                              /* disassociate_connection */
   multissl_recv_plain,               /* recv decrypted data */
   multissl_send_plain,               /* send data to encrypt */
+  NULL,                              /* get_channel_binding */
 };
 
 const struct Curl_ssl *Curl_ssl =
index 2f6ed6b538c6ef9da912178ea70e21c79bcb0acf..1c2538b2e89bd65fac2f5d0e9fd0f5aa6269607b 100644 (file)
@@ -183,6 +183,25 @@ bool Curl_ssl_cert_status_request(void);
 
 bool Curl_ssl_false_start(struct Curl_easy *data);
 
+/* The maximum size of the SSL channel binding is 85 bytes, as defined in
+ * RFC 5929, Section 4.1. The 'tls-server-end-point:' prefix is 21 bytes long,
+ * and SHA-512 is the longest supported hash algorithm, with a digest length of
+ * 64 bytes.
+ * The maximum size of the channel binding is therefore 21 + 64 = 85 bytes.
+ */
+#define SSL_CB_MAX_SIZE 85
+
+/* Return the tls-server-end-point channel binding, including the
+ * 'tls-server-end-point:' prefix.
+ * If successful, the data is written to the dynbuf, and CURLE_OK is returned.
+ * The dynbuf MUST HAVE a minimum toobig size of SSL_CB_MAX_SIZE.
+ * If the dynbuf is too small, CURLE_OUT_OF_MEMORY is returned.
+ * If channel binding is not supported, binding stays empty and CURLE_OK is
+ * returned.
+ */
+CURLcode Curl_ssl_get_channel_binding(struct Curl_easy *data, int sockindex,
+                                       struct dynbuf *binding);
+
 #define SSL_SHUTDOWN_TIMEOUT 10000 /* ms */
 
 CURLcode Curl_ssl_cfilter_add(struct Curl_easy *data,
index 1472a0ca5cfcd94efa71616b371036b769249209..8d6df46947bd7fbe4d9abb0c50e02067015c6ef1 100644 (file)
@@ -158,6 +158,9 @@ struct Curl_ssl {
   ssize_t (*send_plain)(struct Curl_cfilter *cf, struct Curl_easy *data,
                         const void *mem, size_t len, CURLcode *code);
 
+  CURLcode (*get_channel_binding)(struct Curl_easy *data, int sockindex,
+                                  struct dynbuf *binding);
+
 };
 
 extern const struct Curl_ssl *Curl_ssl;
index dbe5bb3520bf0a20c2372191acd1daf3a7974c09..8334d1e5ccc302ad26fed799fda12992576d2880 100644 (file)
@@ -1949,6 +1949,7 @@ const struct Curl_ssl Curl_ssl_wolfssl = {
   NULL,                            /* disassociate_connection */
   wolfssl_recv,                    /* recv decrypted data */
   wolfssl_send,                    /* send data to encrypt */
+  NULL,                            /* get_channel_binding */
 };
 
 #endif