GnuTLS: make the connection phase non-blocking
When multi interface is used, the SSL handshake is no longer blocking when GnuTLS is used.
This commit is contained in:

committed by
Daniel Stenberg

parent
6632d957e7
commit
c2888604d7
214
lib/gtls.c
214
lib/gtls.c
@@ -182,54 +182,76 @@ static void unload_file(gnutls_datum data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* this function does a BLOCKING SSL/TLS (re-)handshake */
|
/* this function does a SSL/TLS (re-)handshake */
|
||||||
static CURLcode handshake(struct connectdata *conn,
|
static CURLcode handshake(struct connectdata *conn,
|
||||||
gnutls_session session,
|
|
||||||
int sockindex,
|
int sockindex,
|
||||||
bool duringconnect)
|
bool duringconnect,
|
||||||
|
bool nonblocking)
|
||||||
{
|
{
|
||||||
struct SessionHandle *data = conn->data;
|
struct SessionHandle *data = conn->data;
|
||||||
|
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
|
||||||
|
gnutls_session session = conn->ssl[sockindex].session;
|
||||||
|
curl_socket_t sockfd = conn->sock[sockindex];
|
||||||
|
long timeout_ms;
|
||||||
int rc;
|
int rc;
|
||||||
if(!gtls_inited)
|
int what;
|
||||||
Curl_gtls_init();
|
|
||||||
do {
|
|
||||||
rc = gnutls_handshake(session);
|
|
||||||
|
|
||||||
if((rc == GNUTLS_E_AGAIN) || (rc == GNUTLS_E_INTERRUPTED)) {
|
while(1) {
|
||||||
long timeout_ms = Curl_timeleft(conn, NULL, duringconnect);
|
/* check allowed time left */
|
||||||
|
timeout_ms = Curl_timeleft(conn, NULL, duringconnect);
|
||||||
|
|
||||||
if(timeout_ms < 0) {
|
if(timeout_ms < 0) {
|
||||||
/* a precaution, no need to continue if time already is up */
|
/* no need to continue if time already is up */
|
||||||
failf(data, "SSL connection timeout");
|
failf(data, "SSL connection timeout");
|
||||||
return CURLE_OPERATION_TIMEDOUT;
|
return CURLE_OPERATION_TIMEDOUT;
|
||||||
}
|
}
|
||||||
|
|
||||||
rc = Curl_socket_ready(conn->sock[sockindex],
|
/* if ssl is expecting something, check if it's available. */
|
||||||
conn->sock[sockindex], (int)timeout_ms);
|
if(connssl->connecting_state == ssl_connect_2_reading
|
||||||
if(rc > 0)
|
|| connssl->connecting_state == ssl_connect_2_writing) {
|
||||||
/* reabable or writable, go loop*/
|
|
||||||
continue;
|
curl_socket_t writefd = ssl_connect_2_writing==
|
||||||
else if(0 == rc) {
|
connssl->connecting_state?sockfd:CURL_SOCKET_BAD;
|
||||||
/* timeout */
|
curl_socket_t readfd = ssl_connect_2_reading==
|
||||||
failf(data, "SSL connection timeout");
|
connssl->connecting_state?sockfd:CURL_SOCKET_BAD;
|
||||||
return CURLE_OPERATION_TIMEDOUT;
|
|
||||||
}
|
what = Curl_socket_ready(readfd, writefd,
|
||||||
else {
|
nonblocking?0:(int)timeout_ms);
|
||||||
/* anything that gets here is fatally bad */
|
if(what < 0) {
|
||||||
|
/* fatal error */
|
||||||
failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO);
|
failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO);
|
||||||
return CURLE_SSL_CONNECT_ERROR;
|
return CURLE_SSL_CONNECT_ERROR;
|
||||||
}
|
}
|
||||||
|
else if(0 == what) {
|
||||||
|
if(nonblocking) {
|
||||||
|
return CURLE_OK;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
/* timeout */
|
||||||
|
failf(data, "SSL connection timeout");
|
||||||
|
return CURLE_OPERATION_TIMEDOUT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* socket is readable or writable */
|
||||||
}
|
}
|
||||||
else
|
|
||||||
break;
|
|
||||||
} while(1);
|
|
||||||
|
|
||||||
if(rc < 0) {
|
rc = gnutls_handshake(session);
|
||||||
failf(data, "gnutls_handshake() failed: %s", gnutls_strerror(rc));
|
|
||||||
return CURLE_SSL_CONNECT_ERROR;
|
if((rc == GNUTLS_E_AGAIN) || (rc == GNUTLS_E_INTERRUPTED)) {
|
||||||
|
connssl->connecting_state =
|
||||||
|
gnutls_record_get_direction(session)?
|
||||||
|
ssl_connect_2_writing:ssl_connect_2_reading;
|
||||||
|
if(nonblocking) {
|
||||||
|
return CURLE_OK;
|
||||||
|
}
|
||||||
|
} else if (rc < 0) {
|
||||||
|
failf(data, "gnutls_handshake() failed: %s", gnutls_strerror(rc));
|
||||||
|
} else {
|
||||||
|
/* Reset our connect state machine */
|
||||||
|
connssl->connecting_state = ssl_connect_1;
|
||||||
|
return CURLE_OK;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return CURLE_OK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static gnutls_x509_crt_fmt do_file_type(const char *type)
|
static gnutls_x509_crt_fmt do_file_type(const char *type)
|
||||||
@@ -243,31 +265,14 @@ static gnutls_x509_crt_fmt do_file_type(const char *type)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static CURLcode
|
||||||
/*
|
gtls_connect_step1(struct connectdata *conn,
|
||||||
* This function is called after the TCP connect has completed. Setup the TLS
|
int sockindex)
|
||||||
* layer and do all necessary magic.
|
|
||||||
*/
|
|
||||||
CURLcode
|
|
||||||
Curl_gtls_connect(struct connectdata *conn,
|
|
||||||
int sockindex)
|
|
||||||
|
|
||||||
{
|
{
|
||||||
static const int cert_type_priority[] = { GNUTLS_CRT_X509, 0 };
|
static const int cert_type_priority[] = { GNUTLS_CRT_X509, 0 };
|
||||||
struct SessionHandle *data = conn->data;
|
struct SessionHandle *data = conn->data;
|
||||||
gnutls_session session;
|
gnutls_session session;
|
||||||
int rc;
|
int rc;
|
||||||
unsigned int cert_list_size;
|
|
||||||
const gnutls_datum *chainp;
|
|
||||||
unsigned int verify_status;
|
|
||||||
gnutls_x509_crt x509_cert,x509_issuer;
|
|
||||||
gnutls_datum issuerp;
|
|
||||||
char certbuf[256]; /* big enough? */
|
|
||||||
size_t size;
|
|
||||||
unsigned int algo;
|
|
||||||
unsigned int bits;
|
|
||||||
time_t certclock;
|
|
||||||
const char *ptr;
|
|
||||||
void *ssl_sessionid;
|
void *ssl_sessionid;
|
||||||
size_t ssl_idsize;
|
size_t ssl_idsize;
|
||||||
bool sni = TRUE; /* default is SNI enabled */
|
bool sni = TRUE; /* default is SNI enabled */
|
||||||
@@ -411,10 +416,29 @@ Curl_gtls_connect(struct connectdata *conn,
|
|||||||
infof (data, "SSL re-using session ID\n");
|
infof (data, "SSL re-using session ID\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
rc = handshake(conn, session, sockindex, TRUE);
|
return CURLE_OK;
|
||||||
if(rc)
|
}
|
||||||
/* handshake() sets its own error message with failf() */
|
|
||||||
return rc;
|
static CURLcode
|
||||||
|
gtls_connect_step3(struct connectdata *conn,
|
||||||
|
int sockindex)
|
||||||
|
{
|
||||||
|
unsigned int cert_list_size;
|
||||||
|
const gnutls_datum *chainp;
|
||||||
|
unsigned int verify_status;
|
||||||
|
gnutls_x509_crt x509_cert,x509_issuer;
|
||||||
|
gnutls_datum issuerp;
|
||||||
|
char certbuf[256]; /* big enough? */
|
||||||
|
size_t size;
|
||||||
|
unsigned int algo;
|
||||||
|
unsigned int bits;
|
||||||
|
time_t certclock;
|
||||||
|
const char *ptr;
|
||||||
|
struct SessionHandle *data = conn->data;
|
||||||
|
gnutls_session session = conn->ssl[sockindex].session;
|
||||||
|
int rc;
|
||||||
|
int incache;
|
||||||
|
void *ssl_sessionid;
|
||||||
|
|
||||||
/* This function will return the peer's raw certificate (chain) as sent by
|
/* This function will return the peer's raw certificate (chain) as sent by
|
||||||
the peer. These certificates are in raw format (DER encoded for
|
the peer. These certificates are in raw format (DER encoded for
|
||||||
@@ -623,21 +647,89 @@ Curl_gtls_connect(struct connectdata *conn,
|
|||||||
/* extract session ID to the allocated buffer */
|
/* extract session ID to the allocated buffer */
|
||||||
gnutls_session_get_data(session, connect_sessionid, &connect_idsize);
|
gnutls_session_get_data(session, connect_sessionid, &connect_idsize);
|
||||||
|
|
||||||
if(ssl_sessionid)
|
incache = !(Curl_ssl_getsessionid(conn, &ssl_sessionid, NULL));
|
||||||
|
if (incache) {
|
||||||
/* there was one before in the cache, so instead of risking that the
|
/* there was one before in the cache, so instead of risking that the
|
||||||
previous one was rejected, we just kill that and store the new */
|
previous one was rejected, we just kill that and store the new */
|
||||||
Curl_ssl_delsessionid(conn, ssl_sessionid);
|
Curl_ssl_delsessionid(conn, ssl_sessionid);
|
||||||
|
}
|
||||||
|
|
||||||
/* store this session id */
|
/* store this session id */
|
||||||
return Curl_ssl_addsessionid(conn, connect_sessionid, connect_idsize);
|
return Curl_ssl_addsessionid(conn, connect_sessionid, connect_idsize);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return CURLE_OK;
|
return CURLE_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This function is called after the TCP connect has completed. Setup the TLS
|
||||||
|
* layer and do all necessary magic.
|
||||||
|
*/
|
||||||
|
/* We use connssl->connecting_state to keep track of the connection status;
|
||||||
|
there are three states: 'ssl_connect_1' (not started yet or complete),
|
||||||
|
'ssl_connect_2_reading' (waiting for data from server), and
|
||||||
|
'ssl_connect_2_writing' (waiting to be able to write).
|
||||||
|
*/
|
||||||
|
static CURLcode
|
||||||
|
gtls_connect_common(struct connectdata *conn,
|
||||||
|
int sockindex,
|
||||||
|
bool nonblocking,
|
||||||
|
bool *done)
|
||||||
|
{
|
||||||
|
int rc;
|
||||||
|
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
|
||||||
|
|
||||||
|
/* Initiate the connection, if not already done */
|
||||||
|
if(ssl_connect_1==connssl->connecting_state) {
|
||||||
|
rc = gtls_connect_step1 (conn, sockindex);
|
||||||
|
if(rc)
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc = handshake(conn, sockindex, TRUE, nonblocking);
|
||||||
|
if(rc)
|
||||||
|
/* handshake() sets its own error message with failf() */
|
||||||
|
return rc;
|
||||||
|
|
||||||
|
/* Finish connecting once the handshake is done */
|
||||||
|
if(ssl_connect_1==connssl->connecting_state) {
|
||||||
|
rc = gtls_connect_step3(conn, sockindex);
|
||||||
|
if(rc)
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
*done = ssl_connect_1==connssl->connecting_state;
|
||||||
|
|
||||||
|
return CURLE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
CURLcode
|
||||||
|
Curl_gtls_connect_nonblocking(struct connectdata *conn,
|
||||||
|
int sockindex,
|
||||||
|
bool *done)
|
||||||
|
{
|
||||||
|
return gtls_connect_common(conn, sockindex, TRUE, done);
|
||||||
|
}
|
||||||
|
|
||||||
|
CURLcode
|
||||||
|
Curl_gtls_connect(struct connectdata *conn,
|
||||||
|
int sockindex)
|
||||||
|
|
||||||
|
{
|
||||||
|
CURLcode retcode;
|
||||||
|
bool done = FALSE;
|
||||||
|
|
||||||
|
retcode = gtls_connect_common(conn, sockindex, FALSE, &done);
|
||||||
|
if(retcode)
|
||||||
|
return retcode;
|
||||||
|
|
||||||
|
DEBUGASSERT(done);
|
||||||
|
|
||||||
|
return CURLE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
/* for documentation see Curl_ssl_send() in sslgen.h */
|
/* for documentation see Curl_ssl_send() in sslgen.h */
|
||||||
ssize_t Curl_gtls_send(struct connectdata *conn,
|
ssize_t Curl_gtls_send(struct connectdata *conn,
|
||||||
int sockindex,
|
int sockindex,
|
||||||
@@ -769,7 +861,7 @@ ssize_t Curl_gtls_recv(struct connectdata *conn, /* connection data */
|
|||||||
if(ret == GNUTLS_E_REHANDSHAKE) {
|
if(ret == GNUTLS_E_REHANDSHAKE) {
|
||||||
/* BLOCKING call, this is bad but a work-around for now. Fixing this "the
|
/* BLOCKING call, this is bad but a work-around for now. Fixing this "the
|
||||||
proper way" takes a whole lot of work. */
|
proper way" takes a whole lot of work. */
|
||||||
CURLcode rc = handshake(conn, conn->ssl[num].session, num, FALSE);
|
CURLcode rc = handshake(conn, num, FALSE, FALSE);
|
||||||
if(rc)
|
if(rc)
|
||||||
/* handshake() writes error message on its own */
|
/* handshake() writes error message on its own */
|
||||||
*curlcode = rc;
|
*curlcode = rc;
|
||||||
|
@@ -27,6 +27,9 @@
|
|||||||
int Curl_gtls_init(void);
|
int Curl_gtls_init(void);
|
||||||
int Curl_gtls_cleanup(void);
|
int Curl_gtls_cleanup(void);
|
||||||
CURLcode Curl_gtls_connect(struct connectdata *conn, int sockindex);
|
CURLcode Curl_gtls_connect(struct connectdata *conn, int sockindex);
|
||||||
|
CURLcode Curl_gtls_connect_nonblocking(struct connectdata *conn,
|
||||||
|
int sockindex,
|
||||||
|
bool *done);
|
||||||
|
|
||||||
/* tell GnuTLS to close down all open information regarding connections (and
|
/* tell GnuTLS to close down all open information regarding connections (and
|
||||||
thus session ID caching etc) */
|
thus session ID caching etc) */
|
||||||
@@ -52,6 +55,7 @@ int Curl_gtls_seed(struct SessionHandle *data);
|
|||||||
#define curlssl_init Curl_gtls_init
|
#define curlssl_init Curl_gtls_init
|
||||||
#define curlssl_cleanup Curl_gtls_cleanup
|
#define curlssl_cleanup Curl_gtls_cleanup
|
||||||
#define curlssl_connect Curl_gtls_connect
|
#define curlssl_connect Curl_gtls_connect
|
||||||
|
#define curlssl_connect_nonblocking Curl_gtls_connect_nonblocking
|
||||||
#define curlssl_session_free(x) Curl_gtls_session_free(x)
|
#define curlssl_session_free(x) Curl_gtls_session_free(x)
|
||||||
#define curlssl_close_all Curl_gtls_close_all
|
#define curlssl_close_all Curl_gtls_close_all
|
||||||
#define curlssl_close Curl_gtls_close
|
#define curlssl_close Curl_gtls_close
|
||||||
|
18
lib/http.c
18
lib/http.c
@@ -1817,9 +1817,9 @@ static CURLcode https_connecting(struct connectdata *conn, bool *done)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_SSLEAY
|
#if defined(USE_SSLEAY) || defined(USE_GNUTLS)
|
||||||
/* This function is OpenSSL-specific. It should be made to query the generic
|
/* This function is for OpenSSL and GnuTLS only. It should be made to query
|
||||||
SSL layer instead. */
|
the generic SSL layer instead. */
|
||||||
static int https_getsock(struct connectdata *conn,
|
static int https_getsock(struct connectdata *conn,
|
||||||
curl_socket_t *socks,
|
curl_socket_t *socks,
|
||||||
int numsocks)
|
int numsocks)
|
||||||
@@ -1844,17 +1844,6 @@ static int https_getsock(struct connectdata *conn,
|
|||||||
return CURLE_OK;
|
return CURLE_OK;
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
#ifdef USE_GNUTLS
|
|
||||||
static int https_getsock(struct connectdata *conn,
|
|
||||||
curl_socket_t *socks,
|
|
||||||
int numsocks)
|
|
||||||
{
|
|
||||||
(void)conn;
|
|
||||||
(void)socks;
|
|
||||||
(void)numsocks;
|
|
||||||
return GETSOCK_BLANK;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
#ifdef USE_NSS
|
#ifdef USE_NSS
|
||||||
static int https_getsock(struct connectdata *conn,
|
static int https_getsock(struct connectdata *conn,
|
||||||
curl_socket_t *socks,
|
curl_socket_t *socks,
|
||||||
@@ -1879,7 +1868,6 @@ static int https_getsock(struct connectdata *conn,
|
|||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Curl_http_done() gets called from Curl_done() after a single HTTP request
|
* Curl_http_done() gets called from Curl_done() after a single HTTP request
|
||||||
|
@@ -231,6 +231,7 @@ struct ssl_connect_data {
|
|||||||
#ifdef USE_GNUTLS
|
#ifdef USE_GNUTLS
|
||||||
gnutls_session session;
|
gnutls_session session;
|
||||||
gnutls_certificate_credentials cred;
|
gnutls_certificate_credentials cred;
|
||||||
|
ssl_connect_state connecting_state;
|
||||||
#endif /* USE_GNUTLS */
|
#endif /* USE_GNUTLS */
|
||||||
#ifdef USE_NSS
|
#ifdef USE_NSS
|
||||||
PRFileDesc *handle;
|
PRFileDesc *handle;
|
||||||
|
Reference in New Issue
Block a user