123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640 |
- /***************************************************************************
- * _ _ ____ _
- * Project ___| | | | _ \| |
- * / __| | | | |_) | |
- * | (__| |_| | _ <| |___
- * \___|\___/|_| \_\_____|
- *
- * Copyright (C) 1998 - 2007, 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
- * are also available at http://curl.haxx.se/docs/copyright.html.
- *
- * You may opt to use, copy, modify, merge, publish, distribute and/or sell
- * copies of the Software, and permit persons to whom the Software is
- * furnished to do so, under the terms of the COPYING file.
- *
- * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
- * KIND, either express or implied.
- *
- * $Id$
- ***************************************************************************/
- /*
- * Source file for all GnuTLS-specific code for the TLS/SSL layer. No code
- * but sslgen.c should ever call or use these functions.
- *
- * Note: don't use the GnuTLS' *_t variable type names in this source code,
- * since they were not present in 1.0.X.
- */
- #include "setup.h"
- #ifdef USE_GNUTLS
- #include <gnutls/gnutls.h>
- #include <gnutls/x509.h>
- #include <string.h>
- #include <stdlib.h>
- #include <ctype.h>
- #ifdef HAVE_SYS_TYPES_H
- #include <sys/types.h>
- #endif
- #ifdef HAVE_SYS_SOCKET_H
- #include <sys/socket.h>
- #endif
- #include "urldata.h"
- #include "sendf.h"
- #include "gtls.h"
- #include "sslgen.h"
- #include "parsedate.h"
- #include "connect.h" /* for the connect timeout */
- #include "select.h"
- #define _MPRINTF_REPLACE /* use our functions only */
- #include <curl/mprintf.h>
- #include "memory.h"
- /* The last #include file should be: */
- #include "memdebug.h"
- /* Enable GnuTLS debugging by defining GTLSDEBUG */
- /*#define GTLSDEBUG */
- #ifdef GTLSDEBUG
- static void tls_log_func(int level, const char *str)
- {
- fprintf(stderr, "|<%d>| %s", level, str);
- }
- #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).
- * 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.
- */
- static ssize_t Curl_gtls_push(void *s, const void *buf, size_t len)
- {
- return swrite(s, buf, len);
- }
- static ssize_t Curl_gtls_pull(void *s, void *buf, size_t len)
- {
- return sread(s, buf, len);
- }
- /* Global GnuTLS init, called from Curl_ssl_init() */
- int Curl_gtls_init(void)
- {
- gnutls_global_init();
- #ifdef GTLSDEBUG
- gnutls_global_set_log_function(tls_log_func);
- gnutls_global_set_log_level(2);
- #endif
- return 1;
- }
- int Curl_gtls_cleanup(void)
- {
- gnutls_global_deinit();
- return 1;
- }
- 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
- snprintf(data->state.buffer,
- BUFSIZE,
- "\t %s: %s, %02d %s %4d %02d:%02d:%02d GMT\n",
- text,
- Curl_wkday[tm->tm_wday?tm->tm_wday-1:6],
- tm->tm_mday,
- Curl_month[tm->tm_mon],
- tm->tm_year + 1900,
- tm->tm_hour,
- tm->tm_min,
- tm->tm_sec);
- infof(data, "%s", data->state.buffer);
- }
- /* this function does a BLOCKING SSL/TLS (re-)handshake */
- static CURLcode handshake(struct connectdata *conn,
- gnutls_session session,
- int sockindex,
- bool duringconnect)
- {
- struct SessionHandle *data = conn->data;
- int rc;
- do {
- rc = gnutls_handshake(session);
- if((rc == GNUTLS_E_AGAIN) || (rc == GNUTLS_E_INTERRUPTED)) {
- long timeout_ms = DEFAULT_CONNECT_TIMEOUT;
- long has_passed;
- if(duringconnect && data->set.connecttimeout)
- timeout_ms = data->set.connecttimeout*1000;
- if(data->set.timeout) {
- /* get the strictest timeout of the ones converted to milliseconds */
- if((data->set.timeout*1000) < timeout_ms)
- timeout_ms = data->set.timeout*1000;
- }
- /* Evaluate in milliseconds how much time that has passed */
- has_passed = Curl_tvdiff(Curl_tvnow(), data->progress.t_startsingle);
- /* subtract the passed time */
- timeout_ms -= has_passed;
- if(timeout_ms < 0) {
- /* a precaution, no need to continue if time already is up */
- failf(data, "SSL connection timeout");
- return CURLE_OPERATION_TIMEOUTED;
- }
- rc = Curl_select(conn->sock[sockindex],
- conn->sock[sockindex], (int)timeout_ms);
- if(rc > 0)
- /* reabable or writable, go loop*/
- continue;
- else if(0 == rc) {
- /* timeout */
- failf(data, "SSL connection timeout");
- return CURLE_OPERATION_TIMEDOUT;
- }
- else {
- /* anything that gets here is fatally bad */
- failf(data, "select on SSL socket, errno: %d", Curl_sockerrno());
- return CURLE_SSL_CONNECT_ERROR;
- }
- }
- else
- break;
- } while(1);
- if (rc < 0) {
- failf(data, "gnutls_handshake() failed: %s", gnutls_strerror(rc));
- return CURLE_SSL_CONNECT_ERROR;
- }
- return CURLE_OK;
- }
- static gnutls_x509_crt_fmt do_file_type(const char *type)
- {
- if(!type || !type[0])
- return GNUTLS_X509_FMT_PEM;
- if(curl_strequal(type, "PEM"))
- return GNUTLS_X509_FMT_PEM;
- if(curl_strequal(type, "DER"))
- return GNUTLS_X509_FMT_DER;
- return -1;
- }
- /*
- * This function is called after the TCP connect has completed. Setup the TLS
- * layer and do all necessary magic.
- */
- CURLcode
- Curl_gtls_connect(struct connectdata *conn,
- int sockindex)
- {
- const int cert_type_priority[] = { GNUTLS_CRT_X509, 0 };
- struct SessionHandle *data = conn->data;
- gnutls_session session;
- int rc;
- unsigned int cert_list_size;
- const gnutls_datum *chainp;
- unsigned int verify_status;
- gnutls_x509_crt x509_cert;
- char certbuf[256]; /* big enough? */
- size_t size;
- unsigned int algo;
- unsigned int bits;
- time_t clock;
- const char *ptr;
- void *ssl_sessionid;
- size_t ssl_idsize;
- /* GnuTLS only supports TLSv1 (and SSLv3?) */
- if(data->set.ssl.version == CURL_SSLVERSION_SSLv2) {
- failf(data, "GnuTLS does not support SSLv2");
- return CURLE_SSL_CONNECT_ERROR;
- }
- /* allocate a cred struct */
- rc = gnutls_certificate_allocate_credentials(&conn->ssl[sockindex].cred);
- if(rc < 0) {
- failf(data, "gnutls_cert_all_cred() failed: %s", gnutls_strerror(rc));
- return CURLE_SSL_CONNECT_ERROR;
- }
- if(data->set.ssl.CAfile) {
- /* set the trusted CA cert bundle file */
- gnutls_certificate_set_verify_flags(conn->ssl[sockindex].cred,
- GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT);
- rc = gnutls_certificate_set_x509_trust_file(conn->ssl[sockindex].cred,
- data->set.ssl.CAfile,
- GNUTLS_X509_FMT_PEM);
- if(rc < 0) {
- infof(data, "error reading ca cert file %s (%s)\n",
- data->set.ssl.CAfile, gnutls_strerror(rc));
- if (data->set.ssl.verifypeer)
- return CURLE_SSL_CACERT_BADFILE;
- }
- else
- infof(data, "found %d certificates in %s\n",
- rc, data->set.ssl.CAfile);
- }
- /* Initialize TLS session as a client */
- rc = gnutls_init(&conn->ssl[sockindex].session, GNUTLS_CLIENT);
- if(rc) {
- failf(data, "gnutls_init() failed: %d", rc);
- return CURLE_SSL_CONNECT_ERROR;
- }
- /* convenient assign */
- session = conn->ssl[sockindex].session;
- /* Use default priorities */
- rc = gnutls_set_default_priority(session);
- if(rc < 0)
- return CURLE_SSL_CONNECT_ERROR;
- /* 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 < 0)
- return CURLE_SSL_CONNECT_ERROR;
- if(data->set.cert) {
- if( gnutls_certificate_set_x509_key_file(
- conn->ssl[sockindex].cred, data->set.cert,
- data->set.key != 0 ? data->set.key : data->set.cert,
- do_file_type(data->set.cert_type) ) ) {
- failf(data, "error reading X.509 key or certificate file");
- return CURLE_SSL_CONNECT_ERROR;
- }
- }
- /* put the credentials to the current session */
- 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,
- (gnutls_transport_ptr)conn->sock[sockindex]);
- /* register callback functions to send and receive data. */
- gnutls_transport_set_push_function(session, Curl_gtls_push);
- gnutls_transport_set_pull_function(session, Curl_gtls_pull);
- /* lowat must be set to zero when using custom push and pull functions. */
- gnutls_transport_set_lowat(session, 0);
- /* This might be a reconnect, so we check for a session ID in the cache
- to speed up things */
- if(!Curl_ssl_getsessionid(conn, &ssl_sessionid, &ssl_idsize)) {
- /* we got a session id, use it! */
- gnutls_session_set_data(session, ssl_sessionid, ssl_idsize);
- /* Informational message */
- infof (data, "SSL re-using session ID\n");
- }
- rc = handshake(conn, session, sockindex, TRUE);
- if(rc)
- /* handshake() sets its own error message with failf() */
- return rc;
- /* This function will return the peer's raw certificate (chain) as sent by
- the peer. These certificates are in raw format (DER encoded for
- X.509). In case of a X.509 then a certificate list may be present. The
- first certificate in the list is the peer's certificate, following the
- issuer's certificate, then the issuer's issuer etc. */
- chainp = gnutls_certificate_get_peers(session, &cert_list_size);
- if(!chainp) {
- if(data->set.ssl.verifyhost) {
- failf(data, "failed to get server cert");
- return CURLE_SSL_PEER_CERTIFICATE;
- }
- infof(data, "\t common name: WARNING couldn't obtain\n");
- }
- /* This function will try to verify the peer's certificate and return its
- status (trusted, invalid etc.). The value of status should be one or more
- of the gnutls_certificate_status_t enumerated elements bitwise or'd. To
- avoid denial of service attacks some default upper limits regarding the
- certificate key size and chain size are set. To override them use
- gnutls_certificate_set_verify_limits(). */
- rc = gnutls_certificate_verify_peers2(session, &verify_status);
- if (rc < 0) {
- failf(data, "server cert verify failed: %d", rc);
- return CURLE_SSL_CONNECT_ERROR;
- }
- /* verify_status is a bitmask of gnutls_certificate_status bits */
- if(verify_status & GNUTLS_CERT_INVALID) {
- if (data->set.ssl.verifypeer) {
- failf(data, "server certificate verification failed. CAfile: %s",
- data->set.ssl.CAfile?data->set.ssl.CAfile:"none");
- return CURLE_SSL_CACERT;
- }
- else
- infof(data, "\t server certificate verification FAILED\n");
- }
- else
- infof(data, "\t server certificate verification OK\n");
- /* initialize an X.509 certificate structure. */
- gnutls_x509_crt_init(&x509_cert);
- /* convert the given DER or PEM encoded Certificate to the native
- gnutls_x509_crt_t format */
- gnutls_x509_crt_import(x509_cert, chainp, GNUTLS_X509_FMT_DER);
- size=sizeof(certbuf);
- rc = gnutls_x509_crt_get_dn_by_oid(x509_cert, GNUTLS_OID_X520_COMMON_NAME,
- 0, /* the first and only one */
- FALSE,
- certbuf,
- &size);
- if(rc) {
- infof(data, "error fetching CN from cert:%s\n",
- gnutls_strerror(rc));
- }
- /* This function will check if the given certificate's subject matches the
- given hostname. This is a basic implementation of the matching described
- in RFC2818 (HTTPS), which takes into account wildcards, and the subject
- alternative name PKIX extension. Returns non zero on success, and zero on
- failure. */
- rc = gnutls_x509_crt_check_hostname(x509_cert, conn->host.name);
- if(!rc) {
- if (data->set.ssl.verifyhost > 1) {
- failf(data, "SSL: certificate subject name (%s) does not match "
- "target host name '%s'", certbuf, conn->host.dispname);
- gnutls_x509_crt_deinit(x509_cert);
- return CURLE_SSL_PEER_CERTIFICATE;
- }
- else
- infof(data, "\t common name: %s (does not match '%s')\n",
- certbuf, conn->host.dispname);
- }
- else
- infof(data, "\t common name: %s (matched)\n", certbuf);
- /* Show:
- - ciphers used
- - subject
- - start date
- - expire date
- - common name
- - issuer
- */
- /* public key algorithm's parameters */
- algo = gnutls_x509_crt_get_pk_algorithm(x509_cert, &bits);
- infof(data, "\t certificate public key: %s\n",
- gnutls_pk_algorithm_get_name(algo));
- /* version of the X.509 certificate. */
- infof(data, "\t certificate version: #%d\n",
- gnutls_x509_crt_get_version(x509_cert));
- size = sizeof(certbuf);
- gnutls_x509_crt_get_dn(x509_cert, certbuf, &size);
- infof(data, "\t subject: %s\n", certbuf);
- clock = gnutls_x509_crt_get_activation_time(x509_cert);
- showtime(data, "start date", clock);
- clock = gnutls_x509_crt_get_expiration_time(x509_cert);
- showtime(data, "expire date", clock);
- size = sizeof(certbuf);
- gnutls_x509_crt_get_issuer_dn(x509_cert, certbuf, &size);
- infof(data, "\t issuer: %s\n", certbuf);
- gnutls_x509_crt_deinit(x509_cert);
- /* compression algorithm (if any) */
- ptr = gnutls_compression_get_name(gnutls_compression_get(session));
- /* the *_get_name() says "NULL" if GNUTLS_COMP_NULL is returned */
- infof(data, "\t compression: %s\n", ptr);
- /* the name of the cipher used. ie 3DES. */
- ptr = gnutls_cipher_get_name(gnutls_cipher_get(session));
- infof(data, "\t cipher: %s\n", ptr);
- /* the MAC algorithms name. ie SHA1 */
- ptr = gnutls_mac_get_name(gnutls_mac_get(session));
- infof(data, "\t MAC: %s\n", ptr);
- if(!ssl_sessionid) {
- /* this session was not previously in the cache, add it now */
- /* get the session ID data size */
- gnutls_session_get_data(session, NULL, &ssl_idsize);
- ssl_sessionid = malloc(ssl_idsize); /* get a buffer for it */
- if(ssl_sessionid) {
- /* extract session ID to the allocated buffer */
- gnutls_session_get_data(session, ssl_sessionid, &ssl_idsize);
- /* store this session id */
- return Curl_ssl_addsessionid(conn, ssl_sessionid, ssl_idsize);
- }
- }
- return CURLE_OK;
- }
- /* return number of sent (non-SSL) bytes */
- ssize_t Curl_gtls_send(struct connectdata *conn,
- int sockindex,
- void *mem,
- size_t len)
- {
- ssize_t rc = gnutls_record_send(conn->ssl[sockindex].session, mem, len);
- if(rc < 0 ) {
- if(rc == GNUTLS_E_AGAIN)
- return 0; /* EWOULDBLOCK equivalent */
- rc = -1; /* generic error code for send failure */
- }
- return rc;
- }
- void Curl_gtls_close_all(struct SessionHandle *data)
- {
- /* FIX: make the OpenSSL code more generic and use parts of it here */
- (void)data;
- }
- static void close_one(struct connectdata *conn,
- int index)
- {
- if(conn->ssl[index].session) {
- gnutls_bye(conn->ssl[index].session, GNUTLS_SHUT_RDWR);
- gnutls_deinit(conn->ssl[index].session);
- }
- gnutls_certificate_free_credentials(conn->ssl[index].cred);
- }
- void Curl_gtls_close(struct connectdata *conn)
- {
- if(conn->ssl[0].use)
- close_one(conn, 0);
- if(conn->ssl[1].use)
- close_one(conn, 1);
- }
- /*
- * This function is called to shut down the SSL layer but keep the
- * socket open (CCC - Clear Command Channel)
- */
- int Curl_gtls_shutdown(struct connectdata *conn, int sockindex)
- {
- int result;
- int retval = 0;
- struct SessionHandle *data = conn->data;
- int done = 0;
- ssize_t nread;
- char buf[120];
- /* This has only been tested on the proftpd server, and the mod_tls code
- sends a close notify alert without waiting for a close notify alert in
- response. Thus we wait for a close notify alert from the server, but
- we do not send one. Let's hope other servers do the same... */
- if(conn->ssl[sockindex].session) {
- while(!done) {
- int what = Curl_select(conn->sock[sockindex],
- 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 */
- result = gnutls_record_recv(conn->ssl[sockindex].session,
- buf, sizeof(buf));
- switch(result) {
- case 0:
- /* This is the expected response. There was no data but only
- the close notify alert */
- done = 1;
- break;
- case GNUTLS_E_AGAIN:
- case GNUTLS_E_INTERRUPTED:
- infof(data, "GNUTLS_E_AGAIN || GNUTLS_E_INTERRUPTED\n");
- break;
- default:
- retval = -1;
- done = 1;
- break;
- }
- }
- else if(0 == what) {
- /* timeout */
- failf(data, "SSL shutdown timeout");
- done = 1;
- break;
- }
- else {
- /* anything that gets here is fatally bad */
- failf(data, "select on SSL socket, errno: %d", Curl_sockerrno());
- retval = -1;
- done = 1;
- }
- }
- gnutls_deinit(conn->ssl[sockindex].session);
- }
- gnutls_certificate_free_credentials(conn->ssl[sockindex].cred);
- conn->ssl[sockindex].session = NULL;
- conn->ssl[sockindex].use = FALSE;
- return retval;
- }
- /*
- * If the read would block we return -1 and set 'wouldblock' to TRUE.
- * Otherwise we return the amount of data read. Other errors should return -1
- * and set 'wouldblock' to FALSE.
- */
- ssize_t Curl_gtls_recv(struct connectdata *conn, /* connection data */
- int num, /* socketindex */
- char *buf, /* store read data here */
- size_t buffersize, /* max amount to read */
- bool *wouldblock)
- {
- ssize_t ret;
- ret = gnutls_record_recv(conn->ssl[num].session, buf, buffersize);
- if((ret == GNUTLS_E_AGAIN) || (ret == GNUTLS_E_INTERRUPTED)) {
- *wouldblock = TRUE;
- return -1;
- }
- if(ret == GNUTLS_E_REHANDSHAKE) {
- /* BLOCKING call, this is bad but a work-around for now. Fixing this "the
- proper way" takes a whole lot of work. */
- CURLcode rc = handshake(conn, conn->ssl[num].session, num, FALSE);
- if(rc)
- /* handshake() writes error message on its own */
- return rc;
- *wouldblock = TRUE; /* then return as if this was a wouldblock */
- return -1;
- }
- *wouldblock = FALSE;
- if (!ret) {
- failf(conn->data, "Peer closed the TLS connection");
- return -1;
- }
- if (ret < 0) {
- failf(conn->data, "GnuTLS recv error (%d): %s",
- (int)ret, gnutls_strerror(ret));
- return -1;
- }
- return ret;
- }
- void Curl_gtls_session_free(void *ptr)
- {
- free(ptr);
- }
- size_t Curl_gtls_version(char *buffer, size_t size)
- {
- return snprintf(buffer, size, " GnuTLS/%s", gnutls_check_version(NULL));
- }
- #endif /* USE_GNUTLS */
|