Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

spnego_gssapi: implement TLS channel bindings for openssl #13098

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
17 changes: 17 additions & 0 deletions lib/http_negotiate.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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) {
result = Curl_ssl_get_tls_server_end_point(
data, FIRSTSOCKET, &neg_ctx->channel_binding_data,
&neg_ctx->channel_binding_data_len);
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
free(neg_ctx->channel_binding_data);
#endif

if(result)
Curl_http_auth_cleanup_negotiate(conn);

Expand Down
2 changes: 2 additions & 0 deletions lib/urldata.h
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,8 @@ struct negotiatedata {
gss_ctx_id_t context;
gss_name_t spn;
gss_buffer_desc output_token;
char *channel_binding_data;
size_t channel_binding_data_len;
#else
#ifdef USE_WINDOWS_SSPI
#ifdef SECPKG_ATTR_ENDPOINT_BINDINGS
Expand Down
12 changes: 11 additions & 1 deletion lib/vauth/spnego_gssapi.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -148,13 +150,21 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data,
input_token.length = chlglen;
}

/* Set channel binding data */
if(nego->channel_binding_data) {
memset(&chan, 0, sizeof(struct gss_channel_bindings_struct));
chan.application_data.length = nego->channel_binding_data_len;
chan.application_data.value = nego->channel_binding_data;
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,
Expand Down
1 change: 1 addition & 0 deletions lib/vtls/bearssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -1220,6 +1220,7 @@ const struct Curl_ssl Curl_ssl_bearssl = {
NULL, /* free_multi_ssl_backend_data */
bearssl_recv, /* recv decrypted data */
bearssl_send, /* send data to encrypt */
NULL, /* get_tls_server_end_point */
};

#endif /* USE_BEARSSL */
1 change: 1 addition & 0 deletions lib/vtls/gtls.c
Original file line number Diff line number Diff line change
Expand Up @@ -1859,6 +1859,7 @@ const struct Curl_ssl Curl_ssl_gnutls = {
NULL, /* free_multi_ssl_backend_data */
gtls_recv, /* recv decrypted data */
gtls_send, /* send data to encrypt */
NULL, /* get_tls_server_end_point */
};

#endif /* USE_GNUTLS */
1 change: 1 addition & 0 deletions lib/vtls/mbedtls.c
Original file line number Diff line number Diff line change
Expand Up @@ -1465,6 +1465,7 @@ const struct Curl_ssl Curl_ssl_mbedtls = {
NULL, /* free_multi_ssl_backend_data */
mbed_recv, /* recv decrypted data */
mbed_send, /* send data to encrypt */
NULL, /* get_tls_server_end_point */
};

#endif /* USE_MBEDTLS */
106 changes: 106 additions & 0 deletions lib/vtls/openssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -5096,6 +5096,111 @@ static ssize_t ossl_recv(struct Curl_cfilter *cf,
return nread;
}

static CURLcode ossl_get_tls_server_end_point(struct Curl_easy *data,
int sockindex, char **binding,
size_t *len)
{
/* 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 */
*binding = NULL;
*len = 0;
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_name = "SHA256";
}
else {
algo_name = OBJ_nid2sn(algo_nid);
if(!algo_name) {
failf(data, "Could not find digest algorithm name for NID %d",
algo_nid);
return CURLE_SSL_INVALIDCERTSTATUS;
}
}

#if OPENSSL_VERSION_NUMBER >= 0x30000000L
algo_type = EVP_MD_fetch(NULL, algo_name, NULL);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It’s best to cache the result of this, for performance reasons.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @DemiMarie! Thank you for continued review.
Where would it be best to cache this? I am honestly not too familiar with this level of curl internals. I am primarily working this as a continuation of the great work done by @steffe-kiess (#7196) to add support for SPNEGO GSSAPI, as it is something we critically need from command-line usage due to some web servers now enforcing this. Any pointer in right direction of caching architecture would be greatly appreciated.

I have found little material online regarding the performance impact of repeatedly calling EVP_MD_fetch too much. The explicit fetching will only be done once per connection, during initial connect, so for most cases this function will only be called once. This is a also a completely new feature, as curl has been unable to ever connect to servers requiring secure channel binding. Therefore wondering if caching is something which could be done in a future patch if it turns out to be too much work.

Finally, regarding Explicit Fetching, I have not found any other usage of EVP_*_fetch in the entire curl source code... All other places use EVP_sha256() and similar implicit functions. I agree it could be a good idea to support explicit fetching, but I'd ideally not want this change to be a testing ground for this migration. Rather reverting back to the implicit-only implementation and then adding OpenSSL 3.x Explicit Fetching support in a separate PR as it touches many more areas outside of this scope. Thoughts?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, separate PR.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK! Do you wanna do explicit+caching in a separate PR, or only caching?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’m not a curl developer, just someone who noticed this PR while reading about the mess that OpenSSL 3.x is causing for HAProxy.

#else
/* OpenSSL 1.1.0 to 1.1.1 must use implicit fetching */
algo_type = EVP_get_digestbyname(algo_name);
#endif
if(!algo_type) {
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;
}

#if OPENSSL_VERSION_NUMBER >= 0x30000000L
/* Free the explicitly fetched algorithm object */
EVP_MD_free((EVP_MD *)algo_type);
#endif

*binding = malloc(sizeof(prefix) - 1 + length);
if(!*binding)
return CURLE_OUT_OF_MEMORY;
memcpy(*binding, prefix, sizeof(prefix) - 1);
memcpy(*binding + sizeof(prefix) - 1, buf, length);
*len = sizeof(prefix) - 1 + length;

return CURLE_OK;
#else
/* No X509_get_signature_nid support */
(void)data; /* unused */
(void)sockindex; /* unused */
*binding = NULL;
*len = 0;
return CURLE_OK;
#endif
}

static size_t ossl_version(char *buffer, size_t size)
{
#ifdef LIBRESSL_VERSION_NUMBER
Expand Down Expand Up @@ -5298,6 +5403,7 @@ const struct Curl_ssl Curl_ssl_openssl = {
ossl_free_multi_ssl_backend_data, /* free_multi_ssl_backend_data */
ossl_recv, /* recv decrypted data */
ossl_send, /* send data to encrypt */
ossl_get_tls_server_end_point /* get_tls_server_end_point */
};

#endif /* USE_OPENSSL */
1 change: 1 addition & 0 deletions lib/vtls/rustls.c
Original file line number Diff line number Diff line change
Expand Up @@ -747,6 +747,7 @@ const struct Curl_ssl Curl_ssl_rustls = {
NULL, /* free_multi_ssl_backend_data */
cr_recv, /* recv decrypted data */
cr_send, /* send data to encrypt */
NULL, /* get_tls_server_end_point */
};

#endif /* USE_RUSTLS */
1 change: 1 addition & 0 deletions lib/vtls/schannel.c
Original file line number Diff line number Diff line change
Expand Up @@ -2928,6 +2928,7 @@ const struct Curl_ssl Curl_ssl_schannel = {
schannel_free_multi_ssl_backend_data, /* free_multi_ssl_backend_data */
schannel_recv, /* recv decrypted data */
schannel_send, /* send data to encrypt */
NULL, /* get_tls_server_end_point */
};

#endif /* USE_SCHANNEL */
1 change: 1 addition & 0 deletions lib/vtls/sectransp.c
Original file line number Diff line number Diff line change
Expand Up @@ -3479,6 +3479,7 @@ const struct Curl_ssl Curl_ssl_sectransp = {
NULL, /* free_multi_ssl_backend_data */
sectransp_recv, /* recv decrypted data */
sectransp_send, /* send data to encrypt */
NULL, /* get_tls_server_end_point */
};

#ifdef __GNUC__
Expand Down
12 changes: 12 additions & 0 deletions lib/vtls/vtls.c
Original file line number Diff line number Diff line change
Expand Up @@ -759,6 +759,17 @@ void Curl_free_multi_ssl_backend_data(struct multi_ssl_backend_data *mbackend)
Curl_ssl->free_multi_ssl_backend_data(mbackend);
}

CURLcode Curl_ssl_get_tls_server_end_point(struct Curl_easy *data,
int sockindex, char **binding,
size_t *len)
{
if(Curl_ssl->get_tls_server_end_point)
return Curl_ssl->get_tls_server_end_point(data, sockindex, binding, len);
*binding = NULL;
*len = 0;
return CURLE_OK;
}

void Curl_ssl_close_all(struct Curl_easy *data)
{
/* kill the session ID cache if not shared */
Expand Down Expand Up @@ -1342,6 +1353,7 @@ static const struct Curl_ssl Curl_ssl_multi = {
NULL, /* free_multi_ssl_backend_data */
multissl_recv_plain, /* recv decrypted data */
multissl_send_plain, /* send data to encrypt */
NULL, /* get_tls_server_end_point */
};

const struct Curl_ssl *Curl_ssl =
Expand Down
11 changes: 11 additions & 0 deletions lib/vtls/vtls.h
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,17 @@ bool Curl_ssl_false_start(struct Curl_easy *data);

void Curl_free_multi_ssl_backend_data(struct multi_ssl_backend_data *mbackend);

/* Return the tls-server-end-point channel binding, including the
* 'tls-server-end-point:' prefix.
* If successful, a pointer to the data is stored in *binding and the length of
* the data is stored in *len. The caller must free the data with free().
* If getting the channel binding is not supported, *binding will be set to
* NULL.
*/
CURLcode Curl_ssl_get_tls_server_end_point(struct Curl_easy *data,
int sockindex, char **binding,
size_t *len);

#define SSL_SHUTDOWN_TIMEOUT 10000 /* ms */

CURLcode Curl_ssl_cfilter_add(struct Curl_easy *data,
Expand Down
3 changes: 3 additions & 0 deletions lib/vtls/vtls_int.h
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,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_tls_server_end_point)(struct Curl_easy *data, int sockindex,
char **binding, size_t *len);

};

extern const struct Curl_ssl *Curl_ssl;
Expand Down
1 change: 1 addition & 0 deletions lib/vtls/wolfssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -1521,6 +1521,7 @@ const struct Curl_ssl Curl_ssl_wolfssl = {
NULL, /* free_multi_ssl_backend_data */
wolfssl_recv, /* recv decrypted data */
wolfssl_send, /* send data to encrypt */
NULL, /* get_tls_server_end_point */
};

#endif