Newer
Older
Daniel Stenberg
committed
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
case SSL2_MT_SERVER_VERIFY:
return "Server verify";
case SSL2_MT_SERVER_FINISHED:
return "Server finished";
case SSL2_MT_REQUEST_CERTIFICATE:
return "Request CERT";
case SSL2_MT_CLIENT_CERTIFICATE:
return "Client CERT";
}
}
else if (ssl_ver == SSL3_VERSION_MAJOR) {
switch (msg) {
case SSL3_MT_HELLO_REQUEST:
return "Hello request";
case SSL3_MT_CLIENT_HELLO:
return "Client hello";
case SSL3_MT_SERVER_HELLO:
return "Server hello";
case SSL3_MT_CERTIFICATE:
return "CERT";
case SSL3_MT_SERVER_KEY_EXCHANGE:
return "Server key exchange";
case SSL3_MT_CLIENT_KEY_EXCHANGE:
return "Client key exchange";
case SSL3_MT_CERTIFICATE_REQUEST:
return "Request CERT";
case SSL3_MT_SERVER_DONE:
return "Server finished";
case SSL3_MT_CERTIFICATE_VERIFY:
return "CERT verify";
case SSL3_MT_FINISHED:
return "Finished";
}
}
return "Unknown";
}
static const char *tls_rt_type(int type)
{
return (
type == SSL3_RT_CHANGE_CIPHER_SPEC ? "TLS change cipher, " :
type == SSL3_RT_ALERT ? "TLS alert, " :
type == SSL3_RT_HANDSHAKE ? "TLS handshake, " :
type == SSL3_RT_APPLICATION_DATA ? "TLS app data, " :
"TLS Unknown, ");
}
/*
* Our callback from the SSL/TLS layers.
*/
static void ssl_tls_trace(int direction, int ssl_ver, int content_type,
const void *buf, size_t len, const SSL *ssl,
struct connectdata *conn)
{
struct SessionHandle *data = conn->data;
const char *msg_name, *tls_rt_name;
char ssl_buf[1024];
int ver, msg_type, txt_len;
if (!conn || !conn->data || !conn->data->set.fdebug ||
(direction != 0 && direction != 1))
return;
data = conn->data;
ssl_ver >>= 8;
ver = (ssl_ver == SSL2_VERSION_MAJOR ? '2' :
ssl_ver == SSL3_VERSION_MAJOR ? '3' : '?');
/* SSLv2 doesn't seem to have TLS record-type headers, so OpenSSL
* always pass-up content-type as 0. But the interesting message-type
Daniel Stenberg
committed
* is at 'buf[0]'.
*/
if (ssl_ver == SSL3_VERSION_MAJOR && content_type != 0)
tls_rt_name = tls_rt_type(content_type);
else
tls_rt_name = "";
msg_type = *(char*)buf;
msg_name = ssl_msg_type(ssl_ver, msg_type);
txt_len = 1 + snprintf(ssl_buf, sizeof(ssl_buf), "SSLv%c, %s%s (%d):\n",
ver, tls_rt_name, msg_name, msg_type);
Daniel Stenberg
committed
Curl_debug(data, CURLINFO_TEXT, ssl_buf, txt_len, NULL);
Curl_debug(data, (direction == 1) ? CURLINFO_SSL_DATA_OUT :
CURLINFO_SSL_DATA_IN, (char *)buf, len, NULL);
Daniel Stenberg
committed
(void) ssl;
}
#endif
/* ====================================================== */
Daniel Stenberg
committed
CURLcode
Daniel Stenberg
committed
Curl_SSLConnect(struct connectdata *conn,
int sockindex)
Daniel Stenberg
committed
CURLcode retcode = CURLE_OK;
Daniel Stenberg
committed
struct SessionHandle *data = conn->data;
Daniel Stenberg
committed
long lerr;
Daniel Stenberg
committed
int what;
char * str;
SSL_METHOD *req_method;
SSL_SESSION *ssl_sessionid=NULL;
ASN1_TIME *certdate;
curl_socket_t sockfd = conn->sock[sockindex];
Daniel Stenberg
committed
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
/* mark this is being ssl enabled from here on out. */
Daniel Stenberg
committed
connssl->use = TRUE;
Daniel Stenberg
committed
if(!ssl_seeded || data->set.ssl.random_file || data->set.ssl.egdsocket) {
Daniel Stenberg
committed
/* Make funny stuff to get random input */
random_the_seed(data);
ssl_seeded = TRUE;
}
/* check to see if we've been told to use an explicit SSL/TLS version */
Daniel Stenberg
committed
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_SSLv2:
req_method = SSLv2_client_method();
break;
case CURL_SSLVERSION_SSLv3:
req_method = SSLv3_client_method();
break;
}
Daniel Stenberg
committed
Daniel Stenberg
committed
connssl->ctx = SSL_CTX_new(req_method);
Daniel Stenberg
committed
if(!connssl->ctx) {
failf(data, "SSL: couldn't create a context!");
Daniel Stenberg
committed
return CURLE_OUT_OF_MEMORY;
Daniel Stenberg
committed
#ifdef SSL_CTRL_SET_MSG_CALLBACK
if (data->set.fdebug) {
SSL_CTX_callback_ctrl(connssl->ctx, SSL_CTRL_SET_MSG_CALLBACK,
ssl_tls_trace);
SSL_CTX_ctrl(connssl->ctx, SSL_CTRL_SET_MSG_CALLBACK_ARG, 0, conn);
}
#endif
/* OpenSSL contains code to work-around lots of bugs and flaws in various
SSL-implementations. SSL_CTX_set_options() is used to enabled those
work-arounds. The man page for this option states that SSL_OP_ALL enables
ll the work-arounds and that "It is usually safe to use SSL_OP_ALL to
enable the bug workaround options if compatibility with somewhat broken
implementations is desired."
*/
Daniel Stenberg
committed
SSL_CTX_set_options(connssl->ctx, SSL_OP_ALL);
Daniel Stenberg
committed
Daniel Stenberg
committed
#if 0
/*
* Not sure it's needed to tell SSL_connect() that socket is
* non-blocking. It doesn't seem to care, but just return with
* SSL_ERROR_WANT_x.
*/
if (data->state.used_interface == Curl_if_multi)
SSL_CTX_ctrl(connssl->ctx, BIO_C_SET_NBIO, 1, NULL);
#endif
Daniel Stenberg
committed
if(data->set.cert) {
if(!cert_stuff(conn,
Daniel Stenberg
committed
connssl->ctx,
data->set.cert,
data->set.cert_type,
data->set.key,
data->set.key_type)) {
/* failf() is already done in cert_stuff() */
return CURLE_SSL_CERTPROBLEM;
if(data->set.ssl.cipher_list) {
Daniel Stenberg
committed
if(!SSL_CTX_set_cipher_list(connssl->ctx,
data->set.ssl.cipher_list)) {
failf(data, "failed setting cipher list");
return CURLE_SSL_CIPHER;
if (data->set.ssl.CAfile || data->set.ssl.CApath) {
/* tell SSL where to find CA certificates that are used to verify
the servers certificate. */
Daniel Stenberg
committed
if (!SSL_CTX_load_verify_locations(connssl->ctx, data->set.ssl.CAfile,
data->set.ssl.CApath)) {
if (data->set.ssl.verifypeer) {
/* Fail if we insist on successfully verifying the server. */
failf(data,"error setting certificate verify locations:\n"
" CAfile: %s\n CApath: %s\n",
data->set.ssl.CAfile ? data->set.ssl.CAfile : "none",
data->set.ssl.CApath ? data->set.ssl.CApath : "none");
return CURLE_SSL_CACERT;
}
else {
/* Just continue with a warning if no strict certificate verification
is required. */
Daniel Stenberg
committed
infof(data, "error setting certificate verify locations,"
" continuing anyway:\n");
}
}
else {
/* Everything is fine. */
Daniel Stenberg
committed
infof(data, "successfully set certificate verify locations:\n");
Daniel Stenberg
committed
}
Daniel Stenberg
committed
infof(data,
" CAfile: %s\n"
" CApath: %s\n",
data->set.ssl.CAfile ? data->set.ssl.CAfile : "none",
data->set.ssl.CApath ? data->set.ssl.CApath : "none");
/* 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. */
Daniel Stenberg
committed
SSL_CTX_set_verify(connssl->ctx,
data->set.ssl.verifypeer?SSL_VERIFY_PEER:SSL_VERIFY_NONE,
cert_verify_callback);
Daniel Stenberg
committed
/* give application a chance to interfere with SSL set up. */
if(data->set.ssl.fsslctx) {
Daniel Stenberg
committed
retcode = (*data->set.ssl.fsslctx)(data, connssl->ctx,
data->set.ssl.fsslctxp);
if(retcode) {
failf(data,"error signaled by ssl ctx callback");
return retcode;
}
}
/* Lets make an SSL structure */
Daniel Stenberg
committed
connssl->handle = SSL_new(connssl->ctx);
SSL_set_connect_state(connssl->handle);
Daniel Stenberg
committed
connssl->server_cert = 0x0;
if(!conn->bits.reuse) {
/* We're not re-using a connection, check if there's a cached ID we
can/should use here! */
if(!Get_SSL_Session(conn, &ssl_sessionid)) {
/* we got a session id, use it! */
Daniel Stenberg
committed
SSL_set_session(connssl->handle, ssl_sessionid);
/* Informational message */
infof (data, "SSL re-using session ID\n");
}
}
/* pass the raw socket into the SSL layers */
Daniel Stenberg
committed
SSL_set_fd(connssl->handle, sockfd);
while(1) {
fd_set writefd;
fd_set readfd;
struct timeval interval;
long timeout_ms;
/* Find out if any timeout is set. If not, use 300 seconds.
Otherwise, figure out the most strict timeout of the two possible one
and then how much time that has elapsed to know how much time we
allow for the connect call */
if(data->set.timeout || data->set.connecttimeout) {
long has_passed;
/* Evaluate in milliseconds how much time that has passed */
has_passed = Curl_tvdiff(Curl_tvnow(), data->progress.start);
/* get the most strict timeout of the ones converted to milliseconds */
if(data->set.timeout &&
(data->set.timeout>data->set.connecttimeout))
timeout_ms = data->set.timeout*1000;
else
timeout_ms = data->set.connecttimeout*1000;
Daniel Stenberg
committed
/* subtract the passed time */
timeout_ms -= has_passed;
Daniel Stenberg
committed
if(timeout_ms < 0) {
/* a precaution, no need to continue if time already is up */
failf(data, "SSL connection timeout");
return CURLE_OPERATION_TIMEOUTED;
}
}
else
/* no particular time-out has been set */
timeout_ms= DEFAULT_CONNECT_TIMEOUT;
Daniel Stenberg
committed
FD_ZERO(&writefd);
FD_ZERO(&readfd);
Daniel Stenberg
committed
err = SSL_connect(connssl->handle);
Daniel Stenberg
committed
/* 1 is fine
0 is "not successful but was shut down controlled"
<0 is "handshake was not successful, because a fatal error occurred" */
if(1 != err) {
Daniel Stenberg
committed
int detail = SSL_get_error(connssl->handle, err);
Daniel Stenberg
committed
if(SSL_ERROR_WANT_READ == detail)
Daniel Stenberg
committed
FD_SET(sockfd, &readfd);
Daniel Stenberg
committed
else if(SSL_ERROR_WANT_WRITE == detail)
Daniel Stenberg
committed
FD_SET(sockfd, &writefd);
Daniel Stenberg
committed
else {
/* untreated error */
unsigned long errdetail;
Daniel Stenberg
committed
char error_buffer[120]; /* OpenSSL documents that this must be at least
120 bytes long. */
Daniel Stenberg
committed
CURLcode rc;
const char *cert_problem = NULL;
errdetail = ERR_get_error(); /* Gets the earliest error code from the
thread's error queue and removes the
entry. */
switch(errdetail) {
case 0x1407E086:
/* 1407E086:
SSL routines:
SSL2_SET_CERTIFICATE:
certificate verify failed */
Daniel Stenberg
committed
/* fall-through */
case 0x14090086:
/* 14090086:
SSL routines:
SSL3_GET_SERVER_CERTIFICATE:
certificate verify failed */
Daniel Stenberg
committed
cert_problem = "SSL certificate problem, verify that the CA cert is"
" OK. Details:\n";
rc = CURLE_SSL_CACERT;
break;
default:
Daniel Stenberg
committed
rc = CURLE_SSL_CONNECT_ERROR;
break;
}
/* detail is already set to the SSL error above */
Daniel Stenberg
committed
/* If we e.g. use SSLv2 request-method and the server doesn't like us
* (RST connection etc.), OpenSSL gives no explanation whatsoever and
* the SO_ERROR is also lost.
*/
if (CURLE_SSL_CONNECT_ERROR == rc && errdetail == 0) {
failf(data, "Unknown SSL protocol error in connection to %s:%d ",
conn->host.name, conn->port);
return rc;
}
/* Could be a CERT problem */
#ifdef HAVE_ERR_ERROR_STRING_N
/* OpenSSL 0.9.6 and later has a function named
ERRO_error_string_n() that takes the size of the buffer as a
third argument */
ERR_error_string_n(errdetail, error_buffer, sizeof(error_buffer));
#else
ERR_error_string(errdetail, error_buffer);
#endif
Daniel Stenberg
committed
failf(data, "%s%s", cert_problem ? cert_problem : "", error_buffer);
return rc;
Daniel Stenberg
committed
}
}
else
/* we have been connected fine, get out of the connect loop */
break;
interval.tv_sec = (int)(timeout_ms/1000);
timeout_ms -= interval.tv_sec*1000;
interval.tv_usec = timeout_ms*1000;
Daniel Stenberg
committed
while(1) {
what = select(sockfd+1, &readfd, &writefd, NULL, &interval);
if(what > 0)
/* reabable or writable, go loop in the outer loop */
break;
else if(0 == what) {
/* timeout */
failf(data, "SSL connection timeout");
return CURLE_OPERATION_TIMEDOUT;
}
else {
#if !defined(WIN32) && defined(EINTR)
Daniel Stenberg
committed
/* For platforms without EINTR all errnos are bad */
if (errno == EINTR)
continue; /* retry the select() */
#endif
/* anything other than the unimportant EINTR is fatally bad */
failf(data, "select on SSL socket, errno: %d", Curl_ourerrno());
return CURLE_SSL_CONNECT_ERROR;
}
} /* while()-loop for the select() */
} /* while()-loop for the SSL_connect() */
/* Informational message */
infof (data, "SSL connection using %s\n",
Daniel Stenberg
committed
SSL_get_cipher(connssl->handle));
if(!ssl_sessionid) {
/* Since this is not a cached session ID, then we want to stach this one
in the cache! */
Daniel Stenberg
committed
Store_SSL_Session(conn, connssl);
}
Daniel Stenberg
committed
/* Get server's certificate (note: beware of dynamic allocation) - opt */
/* major serious hack alert -- we should check certificates
* to authenticate the server; otherwise we risk man-in-the-middle
* attack
*/
Daniel Stenberg
committed
connssl->server_cert = SSL_get_peer_certificate(connssl->handle);
if(!connssl->server_cert) {
failf(data, "SSL: couldn't get peer certificate!");
Daniel Stenberg
committed
return CURLE_SSL_PEER_CERTIFICATE;
}
infof (data, "Server certificate:\n");
Daniel Stenberg
committed
Daniel Stenberg
committed
str = X509_NAME_oneline(X509_get_subject_name(connssl->server_cert),
NULL, 0);
if(!str) {
failf(data, "SSL: couldn't get X509-subject!");
Daniel Stenberg
committed
X509_free(connssl->server_cert);
Daniel Stenberg
committed
return CURLE_SSL_CONNECT_ERROR;
}
infof(data, "\t subject: %s\n", str);
CRYPTO_free(str);
Daniel Stenberg
committed
certdate = X509_get_notBefore(connssl->server_cert);
Curl_ASN1_UTCTIME_output(conn, "\t start date: ", certdate);
Daniel Stenberg
committed
certdate = X509_get_notAfter(connssl->server_cert);
Curl_ASN1_UTCTIME_output(conn, "\t expire date: ", certdate);
if(data->set.ssl.verifyhost) {
Daniel Stenberg
committed
retcode = verifyhost(conn, connssl->server_cert);
if(retcode) {
Daniel Stenberg
committed
X509_free(connssl->server_cert);
return retcode;
}
Daniel Stenberg
committed
}
Daniel Stenberg
committed
str = X509_NAME_oneline(X509_get_issuer_name(connssl->server_cert),
NULL, 0);
if(!str) {
failf(data, "SSL: couldn't get X509-issuer name!");
retcode = CURLE_SSL_CONNECT_ERROR;
else {
infof(data, "\t issuer: %s\n", str);
CRYPTO_free(str);
/* We could do all sorts of certificate verification stuff here before
deallocating the certificate. */
Daniel Stenberg
committed
Daniel Stenberg
committed
lerr = data->set.ssl.certverifyresult=
SSL_get_verify_result(connssl->handle);
if(data->set.ssl.certverifyresult != X509_V_OK) {
if(data->set.ssl.verifypeer) {
/* We probably never reach this, because SSL_connect() will fail
and we return earlyer if verifypeer is set? */
Daniel Stenberg
committed
failf(data, "SSL certificate verify result: %s (%ld)",
X509_verify_cert_error_string(lerr), lerr);
retcode = CURLE_SSL_PEER_CERTIFICATE;
}
else
Daniel Stenberg
committed
infof(data, "SSL certificate verify result: %s (%ld),"
" continuing anyway.\n",
X509_verify_cert_error_string(err), lerr);
Daniel Stenberg
committed
}
else
infof(data, "SSL certificate verify ok.\n");
Daniel Stenberg
committed
X509_free(connssl->server_cert);
Daniel Stenberg
committed
return retcode;