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:
Jerome Vouillon
2010-04-16 22:43:01 +02:00
committed by Daniel Stenberg
parent 6632d957e7
commit c2888604d7
4 changed files with 161 additions and 76 deletions

View File

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

View File

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

View File

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

View File

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