diff options
Diffstat (limited to 'lib/gtls.c')
-rw-r--r-- | lib/gtls.c | 278 |
1 files changed, 219 insertions, 59 deletions
diff --git a/lib/gtls.c b/lib/gtls.c index 845dbbb12..700e46a9d 100644 --- a/lib/gtls.c +++ b/lib/gtls.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2010, Daniel Stenberg, <daniel@haxx.se>, et al. + * Copyright (C) 1998 - 2012, Daniel Stenberg, <daniel@haxx.se>, et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms @@ -28,17 +28,18 @@ * since they were not present in 1.0.X. */ -#include "setup.h" +#include "curl_setup.h" + #ifdef USE_GNUTLS + #include <gnutls/gnutls.h> #include <gnutls/x509.h> -#include <gcrypt.h> -#include <string.h> -#include <stdlib.h> -#include <ctype.h> -#ifdef HAVE_SYS_SOCKET_H -#include <sys/socket.h> +#ifdef USE_GNUTLS_NETTLE +#include <gnutls/crypto.h> +#include <nettle/md5.h> +#else +#include <gcrypt.h> #endif #include "urldata.h" @@ -79,10 +80,22 @@ static void tls_log_func(int level, const char *str) #endif static bool gtls_inited = FALSE; +#if defined(GNUTLS_VERSION_NUMBER) +# if (GNUTLS_VERSION_NUMBER >= 0x020c00) +# undef gnutls_transport_set_lowat +# define gnutls_transport_set_lowat(A,B) Curl_nop_stmt +# define USE_GNUTLS_PRIORITY_SET_DIRECT 1 +# endif +# if (GNUTLS_VERSION_NUMBER >= 0x020c03) +# define GNUTLS_MAPS_WINSOCK_ERRORS 1 +# endif +#endif + /* * Custom push and pull callback functions used by GNU TLS to read and write * to the socket. These functions are simple wrappers to send() and recv() - * (although here using the sread/swrite macros as defined by setup_once.h). + * (although here using the sread/swrite macros as defined by + * curl_setup_once.h). * We use custom functions rather than the GNU TLS defaults because it allows * us to get specific about the fourth "flags" argument, and to use arbitrary * private data with gnutls_transport_set_ptr if we wish. @@ -96,9 +109,13 @@ static bool gtls_inited = FALSE; * resort global errno variable using gnutls_transport_set_global_errno, * with a transport agnostic error value. This implies that some winsock * error translation must take place in these callbacks. + * + * Paragraph above applies to GNU TLS versions older than 2.12.3, since + * this version GNU TLS does its own internal winsock error translation + * using system_errno() function. */ -#ifdef USE_WINSOCK +#if defined(USE_WINSOCK) && !defined(GNUTLS_MAPS_WINSOCK_ERRORS) # define gtls_EINTR 4 # define gtls_EIO 5 # define gtls_EAGAIN 11 @@ -119,7 +136,7 @@ static int gtls_mapped_sockerrno(void) static ssize_t Curl_gtls_push(void *s, const void *buf, size_t len) { ssize_t ret = swrite(GNUTLS_POINTER_TO_INT_CAST(s), buf, len); -#ifdef USE_WINSOCK +#if defined(USE_WINSOCK) && !defined(GNUTLS_MAPS_WINSOCK_ERRORS) if(ret < 0) gnutls_transport_set_global_errno(gtls_mapped_sockerrno()); #endif @@ -129,7 +146,7 @@ static ssize_t Curl_gtls_push(void *s, const void *buf, size_t len) static ssize_t Curl_gtls_pull(void *s, void *buf, size_t len) { ssize_t ret = sread(GNUTLS_POINTER_TO_INT_CAST(s), buf, len); -#ifdef USE_WINSOCK +#if defined(USE_WINSOCK) && !defined(GNUTLS_MAPS_WINSOCK_ERRORS) if(ret < 0) gnutls_transport_set_global_errno(gtls_mapped_sockerrno()); #endif @@ -170,13 +187,12 @@ static void showtime(struct SessionHandle *data, const char *text, time_t stamp) { - struct tm *tm; -#ifdef HAVE_GMTIME_R struct tm buffer; - tm = (struct tm *)gmtime_r(&stamp, &buffer); -#else - tm = gmtime(&stamp); -#endif + const struct tm *tm = &buffer; + CURLcode result = Curl_gmtime(stamp, &buffer); + if(result) + return; + snprintf(data->state.buffer, BUFSIZE, "\t %s: %s, %02d %s %4d %02d:%02d:%02d GMT\n", @@ -188,7 +204,7 @@ static void showtime(struct SessionHandle *data, tm->tm_hour, tm->tm_min, tm->tm_sec); - infof(data, "%s", data->state.buffer); + infof(data, "%s\n", data->state.buffer); } static gnutls_datum load_file (const char *file) @@ -198,14 +214,14 @@ static gnutls_datum load_file (const char *file) long filelen; void *ptr; - if (!(f = fopen(file, "r"))) + if(!(f = fopen(file, "r"))) return loaded_file; - if (fseek(f, 0, SEEK_END) != 0 - || (filelen = ftell(f)) < 0 - || fseek(f, 0, SEEK_SET) != 0 - || !(ptr = malloc((size_t)filelen))) + if(fseek(f, 0, SEEK_END) != 0 + || (filelen = ftell(f)) < 0 + || fseek(f, 0, SEEK_SET) != 0 + || !(ptr = malloc((size_t)filelen))) goto out; - if (fread(ptr, 1, (size_t)filelen, f) < (size_t)filelen) { + if(fread(ptr, 1, (size_t)filelen, f) < (size_t)filelen) { free(ptr); goto out; } @@ -238,7 +254,7 @@ static CURLcode handshake(struct connectdata *conn, for(;;) { /* check allowed time left */ - timeout_ms = Curl_timeleft(conn, NULL, duringconnect); + timeout_ms = Curl_timeleft(data, NULL, duringconnect); if(timeout_ms < 0) { /* no need to continue if time already is up */ @@ -256,7 +272,8 @@ static CURLcode handshake(struct connectdata *conn, connssl->connecting_state?sockfd:CURL_SOCKET_BAD; what = Curl_socket_ready(readfd, writefd, - nonblocking?0:(int)timeout_ms?1000:timeout_ms); + nonblocking?0: + timeout_ms?timeout_ms:1000); if(what < 0) { /* fatal error */ failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); @@ -280,18 +297,41 @@ static CURLcode handshake(struct connectdata *conn, connssl->connecting_state = gnutls_record_get_direction(session)? ssl_connect_2_writing:ssl_connect_2_reading; + continue; if(nonblocking) return CURLE_OK; } - else if (rc < 0) { - failf(data, "gnutls_handshake() failed: %s", gnutls_strerror(rc)); - return CURLE_SSL_CONNECT_ERROR; + else if((rc < 0) && !gnutls_error_is_fatal(rc)) { + const char *strerr = NULL; + + if(rc == GNUTLS_E_WARNING_ALERT_RECEIVED) { + int alert = gnutls_alert_get(session); + strerr = gnutls_alert_get_name(alert); + } + + if(strerr == NULL) + strerr = gnutls_strerror(rc); + + failf(data, "gnutls_handshake() warning: %s", strerr); } - else { - /* Reset our connect state machine */ - connssl->connecting_state = ssl_connect_1; - return CURLE_OK; + else if(rc < 0) { + const char *strerr = NULL; + + if(rc == GNUTLS_E_FATAL_ALERT_RECEIVED) { + int alert = gnutls_alert_get(session); + strerr = gnutls_alert_get_name(alert); + } + + if(strerr == NULL) + strerr = gnutls_strerror(rc); + + failf(data, "gnutls_handshake() failed: %s", strerr); + return CURLE_SSL_CONNECT_ERROR; } + + /* Reset our connect state machine */ + connssl->connecting_state = ssl_connect_1; + return CURLE_OK; } } @@ -310,7 +350,9 @@ static CURLcode gtls_connect_step1(struct connectdata *conn, int sockindex) { +#ifndef USE_GNUTLS_PRIORITY_SET_DIRECT static const int cert_type_priority[] = { GNUTLS_CRT_X509, 0 }; +#endif struct SessionHandle *data = conn->data; gnutls_session session; int rc; @@ -346,6 +388,30 @@ gtls_connect_step1(struct connectdata *conn, return CURLE_SSL_CONNECT_ERROR; } +#ifdef USE_TLS_SRP + if(data->set.ssl.authtype == CURL_TLSAUTH_SRP) { + infof(data, "Using TLS-SRP username: %s\n", data->set.ssl.username); + + rc = gnutls_srp_allocate_client_credentials( + &conn->ssl[sockindex].srp_client_cred); + if(rc != GNUTLS_E_SUCCESS) { + failf(data, "gnutls_srp_allocate_client_cred() failed: %s", + gnutls_strerror(rc)); + return CURLE_OUT_OF_MEMORY; + } + + rc = gnutls_srp_set_client_credentials(conn->ssl[sockindex]. + srp_client_cred, + data->set.ssl.username, + data->set.ssl.password); + if(rc != GNUTLS_E_SUCCESS) { + failf(data, "gnutls_srp_set_client_cred() failed: %s", + gnutls_strerror(rc)); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + } +#endif + if(data->set.ssl.CAfile) { /* set the trusted CA cert bundle file */ gnutls_certificate_set_verify_flags(conn->ssl[sockindex].cred, @@ -371,7 +437,7 @@ gtls_connect_step1(struct connectdata *conn, data->set.ssl.CRLfile, GNUTLS_X509_FMT_PEM); if(rc < 0) { - failf(data, "error reading crl file %s (%s)\n", + failf(data, "error reading crl file %s (%s)", data->set.ssl.CRLfile, gnutls_strerror(rc)); return CURLE_SSL_CRL_BADFILE; } @@ -390,13 +456,13 @@ gtls_connect_step1(struct connectdata *conn, /* convenient assign */ session = conn->ssl[sockindex].session; - if ((0 == Curl_inet_pton(AF_INET, conn->host.name, &addr)) && + if((0 == Curl_inet_pton(AF_INET, conn->host.name, &addr)) && #ifdef ENABLE_IPV6 - (0 == Curl_inet_pton(AF_INET6, conn->host.name, &addr)) && + (0 == Curl_inet_pton(AF_INET6, conn->host.name, &addr)) && #endif - sni && - (gnutls_server_name_set(session, GNUTLS_NAME_DNS, conn->host.name, - strlen(conn->host.name)) < 0)) + sni && + (gnutls_server_name_set(session, GNUTLS_NAME_DNS, conn->host.name, + strlen(conn->host.name)) < 0)) infof(data, "WARNING: failed to configure server name indication (SNI) " "TLS extension\n"); @@ -406,34 +472,58 @@ gtls_connect_step1(struct connectdata *conn, return CURLE_SSL_CONNECT_ERROR; if(data->set.ssl.version == CURL_SSLVERSION_SSLv3) { +#ifndef USE_GNUTLS_PRIORITY_SET_DIRECT static const int protocol_priority[] = { GNUTLS_SSL3, 0 }; - gnutls_protocol_set_priority(session, protocol_priority); + rc = gnutls_protocol_set_priority(session, protocol_priority); +#else + const char *err; + /* the combination of the cipher ARCFOUR with SSL 3.0 and TLS 1.0 is not + vulnerable to attacks such as the BEAST, why this code now explicitly + asks for that + */ + rc = gnutls_priority_set_direct(session, + "NORMAL:-VERS-TLS-ALL:+VERS-SSL3.0:" + "-CIPHER-ALL:+ARCFOUR-128", + &err); +#endif if(rc != GNUTLS_E_SUCCESS) return CURLE_SSL_CONNECT_ERROR; } +#ifndef USE_GNUTLS_PRIORITY_SET_DIRECT /* Sets the priority on the certificate types supported by gnutls. Priority is higher for types specified before others. After specifying the types you want, you must append a 0. */ rc = gnutls_certificate_type_set_priority(session, cert_type_priority); if(rc != GNUTLS_E_SUCCESS) return CURLE_SSL_CONNECT_ERROR; +#endif if(data->set.str[STRING_CERT]) { - if( gnutls_certificate_set_x509_key_file( - conn->ssl[sockindex].cred, - data->set.str[STRING_CERT], - data->set.str[STRING_KEY] ? - data->set.str[STRING_KEY] : data->set.str[STRING_CERT], - do_file_type(data->set.str[STRING_CERT_TYPE]) ) != GNUTLS_E_SUCCESS) { + if(gnutls_certificate_set_x509_key_file( + conn->ssl[sockindex].cred, + data->set.str[STRING_CERT], + data->set.str[STRING_KEY] ? + data->set.str[STRING_KEY] : data->set.str[STRING_CERT], + do_file_type(data->set.str[STRING_CERT_TYPE]) ) != + GNUTLS_E_SUCCESS) { failf(data, "error reading X.509 key or certificate file"); return CURLE_SSL_CONNECT_ERROR; } } +#ifdef USE_TLS_SRP /* put the credentials to the current session */ - rc = gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, - conn->ssl[sockindex].cred); + if(data->set.ssl.authtype == CURL_TLSAUTH_SRP) { + rc = gnutls_credentials_set(session, GNUTLS_CRD_SRP, + conn->ssl[sockindex].srp_client_cred); + if(rc != GNUTLS_E_SUCCESS) + failf(data, "gnutls_credentials_set() failed: %s", gnutls_strerror(rc)); + } + else +#endif + rc = gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, + conn->ssl[sockindex].cred); /* set the connection handle (file descriptor for the socket) */ gnutls_transport_set_ptr(session, @@ -483,6 +573,7 @@ gtls_connect_step3(struct connectdata *conn, int rc; int incache; void *ssl_sessionid; + CURLcode result = CURLE_OK; /* This function will return the peer's raw certificate (chain) as sent by the peer. These certificates are in raw format (DER encoded for @@ -495,8 +586,21 @@ gtls_connect_step3(struct connectdata *conn, if(data->set.ssl.verifypeer || data->set.ssl.verifyhost || data->set.ssl.issuercert) { - failf(data, "failed to get server cert"); - return CURLE_PEER_FAILED_VERIFICATION; +#ifdef USE_TLS_SRP + if(data->set.ssl.authtype == CURL_TLSAUTH_SRP + && data->set.ssl.username != NULL + && !data->set.ssl.verifypeer + && gnutls_cipher_get(session)) { + /* no peer cert, but auth is ok if we have SRP user and cipher and no + peer verify */ + } + else { +#endif + failf(data, "failed to get server cert"); + return CURLE_PEER_FAILED_VERIFICATION; +#ifdef USE_TLS_SRP + } +#endif } infof(data, "\t common name: WARNING couldn't obtain\n"); } @@ -529,8 +633,10 @@ gtls_connect_step3(struct connectdata *conn, else infof(data, "\t server certificate verification OK\n"); } - else + else { infof(data, "\t server certificate verification SKIPPED\n"); + goto after_server_cert_verification; + } /* initialize an X.509 certificate structure. */ gnutls_x509_crt_init(&x509_cert); @@ -539,13 +645,13 @@ gtls_connect_step3(struct connectdata *conn, gnutls_x509_crt_t format */ gnutls_x509_crt_import(x509_cert, chainp, GNUTLS_X509_FMT_DER); - if (data->set.ssl.issuercert) { + if(data->set.ssl.issuercert) { gnutls_x509_crt_init(&x509_issuer); issuerp = load_file(data->set.ssl.issuercert); gnutls_x509_crt_import(x509_issuer, &issuerp, GNUTLS_X509_FMT_PEM); rc = gnutls_x509_crt_check_issuer(x509_cert,x509_issuer); unload_file(issuerp); - if (rc <= 0) { + if(rc <= 0) { failf(data, "server certificate issuer check failed (IssuerCert: %s)", data->set.ssl.issuercert?data->set.ssl.issuercert:"none"); return CURLE_SSL_ISSUER_ERROR; @@ -573,7 +679,7 @@ gtls_connect_step3(struct connectdata *conn, rc = gnutls_x509_crt_check_hostname(x509_cert, conn->host.name); if(!rc) { - if(data->set.ssl.verifyhost > 1) { + if(data->set.ssl.verifyhost) { failf(data, "SSL: certificate subject name (%s) does not match " "target host name '%s'", certbuf, conn->host.dispname); gnutls_x509_crt_deinit(x509_cert); @@ -660,6 +766,8 @@ gtls_connect_step3(struct connectdata *conn, gnutls_x509_crt_deinit(x509_cert); +after_server_cert_verification: + /* compression algorithm (if any) */ ptr = gnutls_compression_get_name(gnutls_compression_get(session)); /* the *_get_name() says "NULL" if GNUTLS_COMP_NULL is returned */ @@ -694,18 +802,24 @@ gtls_connect_step3(struct connectdata *conn, gnutls_session_get_data(session, connect_sessionid, &connect_idsize); incache = !(Curl_ssl_getsessionid(conn, &ssl_sessionid, NULL)); - if (incache) { + if(incache) { /* 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 */ Curl_ssl_delsessionid(conn, ssl_sessionid); } /* store this session id */ - return Curl_ssl_addsessionid(conn, connect_sessionid, connect_idsize); + result = Curl_ssl_addsessionid(conn, connect_sessionid, connect_idsize); + if(result) { + free(connect_sessionid); + result = CURLE_OUT_OF_MEMORY; + } } + else + result = CURLE_OUT_OF_MEMORY; } - return CURLE_OK; + return result; } @@ -813,6 +927,12 @@ static void close_one(struct connectdata *conn, gnutls_certificate_free_credentials(conn->ssl[idx].cred); conn->ssl[idx].cred = NULL; } +#ifdef USE_TLS_SRP + if(conn->ssl[idx].srp_client_cred) { + gnutls_srp_free_client_credentials(conn->ssl[idx].srp_client_cred); + conn->ssl[idx].srp_client_cred = NULL; + } +#endif } void Curl_gtls_close(struct connectdata *conn, int sockindex) @@ -843,7 +963,7 @@ int Curl_gtls_shutdown(struct connectdata *conn, int sockindex) if(conn->ssl[sockindex].session) { while(!done) { int what = Curl_socket_ready(conn->sock[sockindex], - CURL_SOCKET_BAD, SSL_SHUTDOWN_TIMEOUT); + CURL_SOCKET_BAD, SSL_SHUTDOWN_TIMEOUT); if(what > 0) { /* Something to read, let's do it and hope that it is the close notify alert from the server */ @@ -882,6 +1002,12 @@ int Curl_gtls_shutdown(struct connectdata *conn, int sockindex) } gnutls_certificate_free_credentials(conn->ssl[sockindex].cred); +#ifdef USE_TLS_SRP + if(data->set.ssl.authtype == CURL_TLSAUTH_SRP + && data->set.ssl.username != NULL) + gnutls_srp_free_client_credentials(conn->ssl[sockindex].srp_client_cred); +#endif + conn->ssl[sockindex].cred = NULL; conn->ssl[sockindex].session = NULL; @@ -941,7 +1067,9 @@ int Curl_gtls_seed(struct SessionHandle *data) static bool ssl_seeded = FALSE; /* Quickly add a bit of entropy */ +#ifndef USE_GNUTLS_NETTLE gcry_fast_random_poll(); +#endif if(!ssl_seeded || data->set.str[STRING_SSL_RANDOM_FILE] || data->set.str[STRING_SSL_EGDSOCKET]) { @@ -956,4 +1084,36 @@ int Curl_gtls_seed(struct SessionHandle *data) return 0; } +void Curl_gtls_random(struct SessionHandle *data, + unsigned char *entropy, + size_t length) +{ +#if defined(USE_GNUTLS_NETTLE) + (void)data; + gnutls_rnd(GNUTLS_RND_RANDOM, entropy, length); +#elif defined(USE_GNUTLS) + Curl_gtls_seed(data); /* Initiate the seed if not already done */ + gcry_randomize(entropy, length, GCRY_STRONG_RANDOM); +#endif +} + +void Curl_gtls_md5sum(unsigned char *tmp, /* input */ + size_t tmplen, + unsigned char *md5sum, /* output */ + size_t md5len) +{ +#if defined(USE_GNUTLS_NETTLE) + struct md5_ctx MD5pw; + md5_init(&MD5pw); + md5_update(&MD5pw, tmplen, tmp); + md5_digest(&MD5pw, md5len, md5sum); +#elif defined(USE_GNUTLS) + gcry_md_hd_t MD5pw; + gcry_md_open(&MD5pw, GCRY_MD_MD5, 0); + gcry_md_write(MD5pw, tmp, tmplen); + memcpy(md5sum, gcry_md_read (MD5pw, 0), md5len); + gcry_md_close(MD5pw); +#endif +} + #endif /* USE_GNUTLS */ |