diff --git a/lib/http_negotiate.c b/lib/http_negotiate.c index 629de834f1..26e475c273 100644 --- a/lib/http_negotiate.c +++ b/lib/http_negotiate.c @@ -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); diff --git a/lib/urldata.h b/lib/urldata.h index 2e71abdaf7..bc7ee989ad 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -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 diff --git a/lib/vauth/spnego_gssapi.c b/lib/vauth/spnego_gssapi.c index 23822838be..f48d9b7000 100644 --- a/lib/vauth/spnego_gssapi.c +++ b/lib/vauth/spnego_gssapi.c @@ -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, diff --git a/lib/vtls/bearssl.c b/lib/vtls/bearssl.c index edd6ca90cc..ad70dcbcae 100644 --- a/lib/vtls/bearssl.c +++ b/lib/vtls/bearssl.c @@ -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 */ diff --git a/lib/vtls/gtls.c b/lib/vtls/gtls.c index fb496955d5..89c497c475 100644 --- a/lib/vtls/gtls.c +++ b/lib/vtls/gtls.c @@ -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 */ diff --git a/lib/vtls/mbedtls.c b/lib/vtls/mbedtls.c index 741cc7c7c6..782ded29cf 100644 --- a/lib/vtls/mbedtls.c +++ b/lib/vtls/mbedtls.c @@ -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 */ diff --git a/lib/vtls/openssl.c b/lib/vtls/openssl.c index 671299d43d..9ac967620e 100644 --- a/lib/vtls/openssl.c +++ b/lib/vtls/openssl.c @@ -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 */ diff --git a/lib/vtls/rustls.c b/lib/vtls/rustls.c index 153aa4d175..d86c81754a 100644 --- a/lib/vtls/rustls.c +++ b/lib/vtls/rustls.c @@ -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 */ diff --git a/lib/vtls/schannel.c b/lib/vtls/schannel.c index f6c17406a2..df248782b4 100644 --- a/lib/vtls/schannel.c +++ b/lib/vtls/schannel.c @@ -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 */ diff --git a/lib/vtls/sectransp.c b/lib/vtls/sectransp.c index b7e6f7e2bd..9de1a4f9d3 100644 --- a/lib/vtls/sectransp.c +++ b/lib/vtls/sectransp.c @@ -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__ diff --git a/lib/vtls/vtls.c b/lib/vtls/vtls.c index e778464cf6..bbb964af2a 100644 --- a/lib/vtls/vtls.c +++ b/lib/vtls/vtls.c @@ -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 = diff --git a/lib/vtls/vtls.h b/lib/vtls/vtls.h index 2f6ed6b538..1c2538b2e8 100644 --- a/lib/vtls/vtls.h +++ b/lib/vtls/vtls.h @@ -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, diff --git a/lib/vtls/vtls_int.h b/lib/vtls/vtls_int.h index 1472a0ca5c..8d6df46947 100644 --- a/lib/vtls/vtls_int.h +++ b/lib/vtls/vtls_int.h @@ -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; diff --git a/lib/vtls/wolfssl.c b/lib/vtls/wolfssl.c index dbe5bb3520..8334d1e5cc 100644 --- a/lib/vtls/wolfssl.c +++ b/lib/vtls/wolfssl.c @@ -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