spnego_gssapi: implement TLS channel bindings for openssl

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
This commit is contained in:
Max Faxälv 2024-02-29 09:12:59 +01:00 коммит произвёл Daniel Stenberg
Родитель 9dfdc6ff42
Коммит 0a5ea09a91
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 5CC908FDB71E12C2
14 изменённых файлов: 153 добавлений и 1 удалений

Просмотреть файл

@ -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);

Просмотреть файл

@ -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

Просмотреть файл

@ -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,

Просмотреть файл

@ -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 */

Просмотреть файл

@ -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 */

Просмотреть файл

@ -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 */

Просмотреть файл

@ -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 */

Просмотреть файл

@ -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 */

Просмотреть файл

@ -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 */

Просмотреть файл

@ -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__

Просмотреть файл

@ -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 =

Просмотреть файл

@ -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,

Просмотреть файл

@ -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;

Просмотреть файл

@ -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