| /*************************************************************************** |
| * _ _ ____ _ |
| * Project ___| | | | _ \| | |
| * / __| | | | |_) | | |
| * | (__| |_| | _ <| |___ |
| * \___|\___/|_| \_\_____| |
| * |
| * 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 |
| * 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. |
| * |
| ***************************************************************************/ |
| |
| /* |
| * Source file for all CyaSSL-specific code for the TLS/SSL layer. No code |
| * but sslgen.c should ever call or use these functions. |
| * |
| */ |
| |
| #include "setup.h" |
| |
| #ifdef USE_CYASSL |
| |
| #ifdef HAVE_LIMITS_H |
| #include <limits.h> |
| #endif |
| |
| #ifdef HAVE_SYS_SOCKET_H |
| #include <sys/socket.h> |
| #endif |
| |
| #include "urldata.h" |
| #include "sendf.h" |
| #include "inet_pton.h" |
| #include "cyassl.h" |
| #include "sslgen.h" |
| #include "parsedate.h" |
| #include "connect.h" /* for the connect timeout */ |
| #include "select.h" |
| #include "rawstr.h" |
| |
| #define _MPRINTF_REPLACE /* use our functions only */ |
| #include <curl/mprintf.h> |
| #include "curl_memory.h" |
| /* The last #include file should be: */ |
| #include "memdebug.h" |
| |
| |
| static Curl_recv cyassl_recv; |
| static Curl_send cyassl_send; |
| |
| |
| static int do_file_type(const char *type) |
| { |
| if(!type || !type[0]) |
| return SSL_FILETYPE_PEM; |
| if(Curl_raw_equal(type, "PEM")) |
| return SSL_FILETYPE_PEM; |
| if(Curl_raw_equal(type, "DER")) |
| return SSL_FILETYPE_ASN1; |
| return -1; |
| } |
| |
| /* |
| * This function loads all the client/CA certificates and CRLs. Setup the TLS |
| * layer and do all necessary magic. |
| */ |
| static CURLcode |
| cyassl_connect_step1(struct connectdata *conn, |
| int sockindex) |
| { |
| struct SessionHandle *data = conn->data; |
| struct ssl_connect_data* conssl = &conn->ssl[sockindex]; |
| SSL_METHOD* req_method = NULL; |
| void* ssl_sessionid = NULL; |
| curl_socket_t sockfd = conn->sock[sockindex]; |
| |
| if(conssl->state == ssl_connection_complete) |
| return CURLE_OK; |
| |
| /* CyaSSL doesn't support SSLv2 */ |
| if(data->set.ssl.version == CURL_SSLVERSION_SSLv2) { |
| failf(data, "CyaSSL does not support SSLv2"); |
| return CURLE_SSL_CONNECT_ERROR; |
| } |
| |
| /* check to see if we've been told to use an explicit SSL/TLS version */ |
| switch(data->set.ssl.version) { |
| case CURL_SSLVERSION_DEFAULT: |
| /* we try to figure out version */ |
| req_method = SSLv23_client_method(); |
| break; |
| case CURL_SSLVERSION_TLSv1: |
| req_method = TLSv1_client_method(); |
| break; |
| case CURL_SSLVERSION_SSLv3: |
| req_method = SSLv3_client_method(); |
| break; |
| default: |
| req_method = TLSv1_client_method(); |
| } |
| |
| if(!req_method) { |
| failf(data, "SSL: couldn't create a method!"); |
| return CURLE_OUT_OF_MEMORY; |
| } |
| |
| if(conssl->ctx) |
| SSL_CTX_free(conssl->ctx); |
| conssl->ctx = SSL_CTX_new(req_method); |
| |
| if(!conssl->ctx) { |
| failf(data, "SSL: couldn't create a context!"); |
| return CURLE_OUT_OF_MEMORY; |
| } |
| |
| #ifndef NO_FILESYSTEM |
| /* load trusted cacert */ |
| if(data->set.str[STRING_SSL_CAFILE]) { |
| if(!SSL_CTX_load_verify_locations(conssl->ctx, |
| data->set.str[STRING_SSL_CAFILE], |
| data->set.str[STRING_SSL_CAPATH])) { |
| if(data->set.ssl.verifypeer) { |
| /* Fail if we insiste on successfully verifying the server. */ |
| failf(data,"error setting certificate verify locations:\n" |
| " CAfile: %s\n CApath: %s\n", |
| data->set.str[STRING_SSL_CAFILE]? |
| data->set.str[STRING_SSL_CAFILE]: "none", |
| data->set.str[STRING_SSL_CAPATH]? |
| data->set.str[STRING_SSL_CAPATH] : "none"); |
| return CURLE_SSL_CACERT_BADFILE; |
| } |
| else { |
| /* Just continue with a warning if no strict certificate |
| verification is required. */ |
| infof(data, "error setting certificate verify locations," |
| " continuing anyway:\n"); |
| } |
| } |
| else { |
| /* Everything is fine. */ |
| infof(data, "successfully set certificate verify locations:\n"); |
| } |
| infof(data, |
| " CAfile: %s\n" |
| " CApath: %s\n", |
| data->set.str[STRING_SSL_CAFILE] ? data->set.str[STRING_SSL_CAFILE]: |
| "none", |
| data->set.str[STRING_SSL_CAPATH] ? data->set.str[STRING_SSL_CAPATH]: |
| "none"); |
| } |
| |
| /* Load the client certificate, and private key */ |
| if(data->set.str[STRING_CERT] && data->set.str[STRING_KEY]) { |
| int file_type = do_file_type(data->set.str[STRING_CERT_TYPE]); |
| |
| if(SSL_CTX_use_certificate_file(conssl->ctx, data->set.str[STRING_CERT], |
| file_type) != 1) { |
| failf(data, "unable to use client certificate (no key or wrong pass" |
| " phrase?)"); |
| return CURLE_SSL_CONNECT_ERROR; |
| } |
| |
| file_type = do_file_type(data->set.str[STRING_KEY_TYPE]); |
| if(SSL_CTX_use_PrivateKey_file(conssl->ctx, data->set.str[STRING_KEY], |
| file_type) != 1) { |
| failf(data, "unable to set private key"); |
| return CURLE_SSL_CONNECT_ERROR; |
| } |
| } |
| #else |
| if(CyaSSL_no_filesystem_verify(conssl->ctx)!= SSL_SUCCESS) { |
| return CURLE_SSL_CONNECT_ERROR; |
| } |
| #endif /* NO_FILESYSTEM */ |
| |
| /* SSL always tries to verify the peer, this only says whether it should |
| * fail to connect if the verification fails, or if it should continue |
| * anyway. In the latter case the result of the verification is checked with |
| * SSL_get_verify_result() below. */ |
| SSL_CTX_set_verify(conssl->ctx, |
| data->set.ssl.verifypeer?SSL_VERIFY_PEER:SSL_VERIFY_NONE, |
| NULL); |
| |
| /* Let's make an SSL structure */ |
| if(conssl->handle) |
| SSL_free(conssl->handle); |
| conssl->handle = SSL_new(conssl->ctx); |
| if(!conssl->handle) { |
| failf(data, "SSL: couldn't create a context (handle)!"); |
| return CURLE_OUT_OF_MEMORY; |
| } |
| |
| /* Check if there's a cached ID we can/should use here! */ |
| if(!Curl_ssl_getsessionid(conn, &ssl_sessionid, NULL)) { |
| /* we got a session id, use it! */ |
| if(!SSL_set_session(conssl->handle, ssl_sessionid)) { |
| failf(data, "SSL: SSL_set_session failed: %s", |
| ERR_error_string(SSL_get_error(conssl->handle, 0),NULL)); |
| return CURLE_SSL_CONNECT_ERROR; |
| } |
| /* Informational message */ |
| infof (data, "SSL re-using session ID\n"); |
| } |
| |
| /* pass the raw socket into the SSL layer */ |
| if(!SSL_set_fd(conssl->handle, (int)sockfd)) { |
| failf(data, "SSL: SSL_set_fd failed"); |
| return CURLE_SSL_CONNECT_ERROR; |
| } |
| |
| conssl->connecting_state = ssl_connect_2; |
| return CURLE_OK; |
| } |
| |
| |
| static CURLcode |
| cyassl_connect_step2(struct connectdata *conn, |
| int sockindex) |
| { |
| int ret = -1; |
| struct SessionHandle *data = conn->data; |
| struct ssl_connect_data* conssl = &conn->ssl[sockindex]; |
| |
| infof(data, "CyaSSL: Connecting to %s:%d\n", |
| conn->host.name, conn->remote_port); |
| |
| conn->recv[sockindex] = cyassl_recv; |
| conn->send[sockindex] = cyassl_send; |
| |
| ret = SSL_connect(conssl->handle); |
| if(ret != 1) { |
| char error_buffer[80]; |
| int detail = SSL_get_error(conssl->handle, ret); |
| |
| if(SSL_ERROR_WANT_READ == detail) { |
| conssl->connecting_state = ssl_connect_2_reading; |
| return CURLE_OK; |
| } |
| |
| if(SSL_ERROR_WANT_WRITE == detail) { |
| conssl->connecting_state = ssl_connect_2_writing; |
| return CURLE_OK; |
| } |
| |
| failf(data, "SSL_connect failed with error %d: %s", detail, |
| ERR_error_string(detail, error_buffer)); |
| return CURLE_SSL_CONNECT_ERROR; |
| } |
| |
| conssl->connecting_state = ssl_connect_3; |
| infof(data, "SSL connected\n"); |
| |
| return CURLE_OK; |
| } |
| |
| |
| static CURLcode |
| cyassl_connect_step3(struct connectdata *conn, |
| int sockindex) |
| { |
| CURLcode retcode = CURLE_OK; |
| void *old_ssl_sessionid=NULL; |
| struct SessionHandle *data = conn->data; |
| struct ssl_connect_data *connssl = &conn->ssl[sockindex]; |
| int incache; |
| SSL_SESSION *our_ssl_sessionid; |
| |
| DEBUGASSERT(ssl_connect_3 == connssl->connecting_state); |
| |
| our_ssl_sessionid = SSL_get_session(connssl->handle); |
| |
| incache = !(Curl_ssl_getsessionid(conn, &old_ssl_sessionid, NULL)); |
| if(incache) { |
| if(old_ssl_sessionid != our_ssl_sessionid) { |
| infof(data, "old SSL session ID is stale, removing\n"); |
| Curl_ssl_delsessionid(conn, old_ssl_sessionid); |
| incache = FALSE; |
| } |
| } |
| if(!incache) { |
| retcode = Curl_ssl_addsessionid(conn, our_ssl_sessionid, |
| 0 /* unknown size */); |
| if(retcode) { |
| failf(data, "failed to store ssl session"); |
| return retcode; |
| } |
| } |
| |
| connssl->connecting_state = ssl_connect_done; |
| |
| return retcode; |
| } |
| |
| |
| static ssize_t cyassl_send(struct connectdata *conn, |
| int sockindex, |
| const void *mem, |
| size_t len, |
| CURLcode *curlcode) |
| { |
| char error_buffer[80]; |
| int memlen = (len > (size_t)INT_MAX) ? INT_MAX : (int)len; |
| int rc = SSL_write(conn->ssl[sockindex].handle, mem, memlen); |
| |
| if(rc < 0) { |
| int err = SSL_get_error(conn->ssl[sockindex].handle, rc); |
| |
| switch(err) { |
| case SSL_ERROR_WANT_READ: |
| case SSL_ERROR_WANT_WRITE: |
| /* there's data pending, re-invoke SSL_write() */ |
| *curlcode = CURLE_AGAIN; |
| return -1; |
| default: |
| failf(conn->data, "SSL write: %s, errno %d", |
| ERR_error_string(err, error_buffer), |
| SOCKERRNO); |
| *curlcode = CURLE_SEND_ERROR; |
| return -1; |
| } |
| } |
| return rc; |
| } |
| |
| void Curl_cyassl_close_all(struct SessionHandle *data) |
| { |
| (void)data; |
| } |
| |
| void Curl_cyassl_close(struct connectdata *conn, int sockindex) |
| { |
| struct ssl_connect_data *conssl = &conn->ssl[sockindex]; |
| |
| if(conssl->handle) { |
| (void)SSL_shutdown(conssl->handle); |
| SSL_free (conssl->handle); |
| conssl->handle = NULL; |
| } |
| if(conssl->ctx) { |
| SSL_CTX_free (conssl->ctx); |
| conssl->ctx = NULL; |
| } |
| } |
| |
| static ssize_t cyassl_recv(struct connectdata *conn, |
| int num, |
| char *buf, |
| size_t buffersize, |
| CURLcode *curlcode) |
| { |
| char error_buffer[80]; |
| int buffsize = (buffersize > (size_t)INT_MAX) ? INT_MAX : (int)buffersize; |
| int nread = SSL_read(conn->ssl[num].handle, buf, buffsize); |
| |
| if(nread < 0) { |
| int err = SSL_get_error(conn->ssl[num].handle, nread); |
| |
| switch(err) { |
| case SSL_ERROR_ZERO_RETURN: /* no more data */ |
| break; |
| case SSL_ERROR_WANT_READ: |
| case SSL_ERROR_WANT_WRITE: |
| /* there's data pending, re-invoke SSL_read() */ |
| *curlcode = CURLE_AGAIN; |
| return -1; |
| default: |
| failf(conn->data, "SSL read: %s, errno %d", |
| ERR_error_string(err, error_buffer), |
| SOCKERRNO); |
| *curlcode = CURLE_RECV_ERROR; |
| return -1; |
| } |
| } |
| return nread; |
| } |
| |
| |
| void Curl_cyassl_session_free(void *ptr) |
| { |
| (void)ptr; |
| /* CyaSSL reuses sessions on own, no free */ |
| } |
| |
| |
| size_t Curl_cyassl_version(char *buffer, size_t size) |
| { |
| #ifdef CYASSL_VERSION |
| return snprintf(buffer, size, "CyaSSL/%s", CYASSL_VERSION); |
| #else |
| return snprintf(buffer, size, "CyaSSL/%s", "<1.8.8"); |
| #endif |
| } |
| |
| |
| int Curl_cyassl_init(void) |
| { |
| if(CyaSSL_Init() == 0) |
| return 1; |
| |
| return -1; |
| } |
| |
| |
| bool Curl_cyassl_data_pending(const struct connectdata* conn, int connindex) |
| { |
| if(conn->ssl[connindex].handle) /* SSL is in use */ |
| return (0 != SSL_pending(conn->ssl[connindex].handle)) ? TRUE : FALSE; |
| else |
| return FALSE; |
| } |
| |
| |
| /* |
| * This function is called to shut down the SSL layer but keep the |
| * socket open (CCC - Clear Command Channel) |
| */ |
| int Curl_cyassl_shutdown(struct connectdata *conn, int sockindex) |
| { |
| int retval = 0; |
| struct ssl_connect_data *connssl = &conn->ssl[sockindex]; |
| |
| if(connssl->handle) { |
| SSL_free (connssl->handle); |
| connssl->handle = NULL; |
| } |
| return retval; |
| } |
| |
| |
| static CURLcode |
| cyassl_connect_common(struct connectdata *conn, |
| int sockindex, |
| bool nonblocking, |
| bool *done) |
| { |
| CURLcode retcode; |
| struct SessionHandle *data = conn->data; |
| struct ssl_connect_data *connssl = &conn->ssl[sockindex]; |
| curl_socket_t sockfd = conn->sock[sockindex]; |
| long timeout_ms; |
| int what; |
| |
| /* check if the connection has already been established */ |
| if(ssl_connection_complete == connssl->state) { |
| *done = TRUE; |
| return CURLE_OK; |
| } |
| |
| if(ssl_connect_1==connssl->connecting_state) { |
| /* Find out how much more time we're allowed */ |
| timeout_ms = Curl_timeleft(data, NULL, TRUE); |
| |
| if(timeout_ms < 0) { |
| /* no need to continue if time already is up */ |
| failf(data, "SSL connection timeout"); |
| return CURLE_OPERATION_TIMEDOUT; |
| } |
| retcode = cyassl_connect_step1(conn, sockindex); |
| if(retcode) |
| return retcode; |
| } |
| |
| while(ssl_connect_2 == connssl->connecting_state || |
| ssl_connect_2_reading == connssl->connecting_state || |
| ssl_connect_2_writing == connssl->connecting_state) { |
| |
| /* check allowed time left */ |
| timeout_ms = Curl_timeleft(data, NULL, TRUE); |
| |
| if(timeout_ms < 0) { |
| /* no need to continue if time already is up */ |
| failf(data, "SSL connection timeout"); |
| return CURLE_OPERATION_TIMEDOUT; |
| } |
| |
| /* if ssl is expecting something, check if it's available. */ |
| if(connssl->connecting_state == ssl_connect_2_reading |
| || connssl->connecting_state == ssl_connect_2_writing) { |
| |
| curl_socket_t writefd = ssl_connect_2_writing== |
| connssl->connecting_state?sockfd:CURL_SOCKET_BAD; |
| curl_socket_t readfd = ssl_connect_2_reading== |
| connssl->connecting_state?sockfd:CURL_SOCKET_BAD; |
| |
| what = Curl_socket_ready(readfd, writefd, nonblocking?0:timeout_ms); |
| if(what < 0) { |
| /* fatal error */ |
| failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); |
| return CURLE_SSL_CONNECT_ERROR; |
| } |
| else if(0 == what) { |
| if(nonblocking) { |
| *done = FALSE; |
| return CURLE_OK; |
| } |
| else { |
| /* timeout */ |
| failf(data, "SSL connection timeout"); |
| return CURLE_OPERATION_TIMEDOUT; |
| } |
| } |
| /* socket is readable or writable */ |
| } |
| |
| /* Run transaction, and return to the caller if it failed or if |
| * this connection is part of a multi handle and this loop would |
| * execute again. This permits the owner of a multi handle to |
| * abort a connection attempt before step2 has completed while |
| * ensuring that a client using select() or epoll() will always |
| * have a valid fdset to wait on. |
| */ |
| retcode = cyassl_connect_step2(conn, sockindex); |
| if(retcode || (nonblocking && |
| (ssl_connect_2 == connssl->connecting_state || |
| ssl_connect_2_reading == connssl->connecting_state || |
| ssl_connect_2_writing == connssl->connecting_state))) |
| return retcode; |
| |
| } /* repeat step2 until all transactions are done. */ |
| |
| if(ssl_connect_3==connssl->connecting_state) { |
| retcode = cyassl_connect_step3(conn, sockindex); |
| if(retcode) |
| return retcode; |
| } |
| |
| if(ssl_connect_done==connssl->connecting_state) { |
| connssl->state = ssl_connection_complete; |
| conn->recv[sockindex] = cyassl_recv; |
| conn->send[sockindex] = cyassl_send; |
| *done = TRUE; |
| } |
| else |
| *done = FALSE; |
| |
| /* Reset our connect state machine */ |
| connssl->connecting_state = ssl_connect_1; |
| |
| return CURLE_OK; |
| } |
| |
| |
| CURLcode |
| Curl_cyassl_connect_nonblocking(struct connectdata *conn, |
| int sockindex, |
| bool *done) |
| { |
| return cyassl_connect_common(conn, sockindex, TRUE, done); |
| } |
| |
| |
| CURLcode |
| Curl_cyassl_connect(struct connectdata *conn, |
| int sockindex) |
| { |
| CURLcode retcode; |
| bool done = FALSE; |
| |
| retcode = cyassl_connect_common(conn, sockindex, FALSE, &done); |
| if(retcode) |
| return retcode; |
| |
| DEBUGASSERT(done); |
| |
| return CURLE_OK; |
| } |
| |
| #endif |