Newer
Older
SSL_set_tlsext_status_type(connssl->handle, TLSEXT_STATUSTYPE_ocsp);
Daniel Stenberg
committed
SSL_set_connect_state(connssl->handle);
Daniel Stenberg
committed
connssl->server_cert = 0x0;
#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
if((0 == Curl_inet_pton(AF_INET, conn->host.name, &addr)) &&
#ifdef ENABLE_IPV6
(0 == Curl_inet_pton(AF_INET6, conn->host.name, &addr)) &&
sni &&
!SSL_set_tlsext_host_name(connssl->handle, conn->host.name))
infof(data, "WARNING: failed to configure server name indication (SNI) "
"TLS extension\n");
#endif
Daniel Stenberg
committed
/* 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! */
Daniel Stenberg
committed
if(!SSL_set_session(connssl->handle, ssl_sessionid)) {
Daniel Stenberg
committed
failf(data, "SSL: SSL_set_session failed: %s",
ERR_error_string(ERR_get_error(), NULL));
Daniel Stenberg
committed
return CURLE_SSL_CONNECT_ERROR;
}
Daniel Stenberg
committed
/* Informational message */
infof (data, "SSL re-using session ID\n");
}
/* pass the raw socket into the SSL layers */
if(!SSL_set_fd(connssl->handle, (int)sockfd)) {
failf(data, "SSL: SSL_set_fd failed: %s",
ERR_error_string(ERR_get_error(), NULL));
return CURLE_SSL_CONNECT_ERROR;
Daniel Stenberg
committed
connssl->connecting_state = ssl_connect_2;
Daniel Stenberg
committed
return CURLE_OK;
}
static CURLcode ossl_connect_step2(struct connectdata *conn, int sockindex)
Daniel Stenberg
committed
{
struct SessionHandle *data = conn->data;
int err;
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
DEBUGASSERT(ssl_connect_2 == connssl->connecting_state
Daniel Stenberg
committed
|| ssl_connect_2_reading == connssl->connecting_state
|| ssl_connect_2_writing == connssl->connecting_state);
Daniel Stenberg
committed
ERR_clear_error();
Daniel Stenberg
committed
err = SSL_connect(connssl->handle);
Daniel Stenberg
committed
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) {
int detail = SSL_get_error(connssl->handle, err);
Daniel Stenberg
committed
Daniel Stenberg
committed
if(SSL_ERROR_WANT_READ == detail) {
connssl->connecting_state = ssl_connect_2_reading;
return CURLE_OK;
Daniel Stenberg
committed
}
Daniel Stenberg
committed
else if(SSL_ERROR_WANT_WRITE == detail) {
connssl->connecting_state = ssl_connect_2_writing;
return CURLE_OK;
}
else {
/* untreated error */
unsigned long errdetail;
char error_buffer[256]=""; /* OpenSSL documents that this must be at
least 256 bytes long. */
int lib;
int reason;
Daniel Stenberg
committed
/* the connection failed, we're not waiting for anything else. */
connssl->connecting_state = ssl_connect_2;
/* Get the earliest error code from the thread's error queue and removes
the entry. */
errdetail = ERR_get_error();
/* Extract which lib and reason */
lib = ERR_GET_LIB(errdetail);
reason = ERR_GET_REASON(errdetail);
if((lib == ERR_LIB_SSL) &&
(reason == SSL_R_CERTIFICATE_VERIFY_FAILED)) {
lerr = SSL_get_verify_result(connssl->handle);
if(lerr != X509_V_OK) {
snprintf(error_buffer, sizeof(error_buffer),
"SSL certificate problem: %s",
X509_verify_cert_error_string(lerr));
}
else
/* strcpy() is fine here as long as the string fits within
error_buffer */
strcpy(error_buffer, "SSL certificate verification failed");
}
else {
SSL_strerror(errdetail, error_buffer, sizeof(error_buffer));
Daniel Stenberg
committed
}
Daniel Stenberg
committed
/* detail is already set to the SSL error above */
/* 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 == result && errdetail == 0) {
conn->host.name, conn->remote_port);
Daniel Stenberg
committed
}
Daniel Stenberg
committed
failf(data, "%s", error_buffer);
Daniel Stenberg
committed
}
}
else {
/* we have been connected fine, we're not waiting for anything else. */
connssl->connecting_state = ssl_connect_3;
/* Informational message */
infof(data, "SSL connection using %s / %s\n",
get_ssl_version_txt(connssl->handle),
Daniel Stenberg
committed
#ifdef HAS_ALPN
/* Sets data and len to negotiated protocol, len is 0 if no protocol was
* negotiated
*/
if(data->set.ssl_enable_alpn) {
const unsigned char* neg_protocol;
unsigned int len;
SSL_get0_alpn_selected(connssl->handle, &neg_protocol, &len);
if(len != 0) {
infof(data, "ALPN, server accepted to use %.*s\n", len, neg_protocol);
#ifdef USE_NGHTTP2
if(len == NGHTTP2_PROTO_VERSION_ID_LEN &&
!memcmp(NGHTTP2_PROTO_VERSION_ID, neg_protocol, len)) {
conn->negnpn = CURL_HTTP_VERSION_2;
else
#endif
if(len == ALPN_HTTP_1_1_LENGTH &&
!memcmp(ALPN_HTTP_1_1, neg_protocol, ALPN_HTTP_1_1_LENGTH)) {
conn->negnpn = CURL_HTTP_VERSION_1_1;
infof(data, "ALPN, server did not agree to a protocol\n");
Daniel Stenberg
committed
return CURLE_OK;
}
}
Daniel Stenberg
committed
static int asn1_object_dump(ASN1_OBJECT *a, char *buf, size_t len)
{
int i, ilen;
if((ilen = (int)len) < 0)
return 1; /* buffer too big */
i = i2t_ASN1_OBJECT(buf, ilen, a);
if(i >= ilen)
return 1; /* buffer too small */
Daniel Stenberg
committed
return 0;
}
#define push_certinfo(_label, _num) \
do { \
long info_len = BIO_get_mem_data(mem, &ptr); \
Curl_ssl_push_certinfo_len(data, _num, _label, ptr, info_len); \
if(1!=BIO_reset(mem)) \
break; \
} WHILE_FALSE
Daniel Stenberg
committed
static void pubkey_show(struct SessionHandle *data,
BIO *mem,
Daniel Stenberg
committed
int num,
const char *type,
const char *name,
BIGNUM *bn)
Daniel Stenberg
committed
{
char *ptr;
Daniel Stenberg
committed
char namebuf[32];
snprintf(namebuf, sizeof(namebuf), "%s(%s)", type, name);
BN_print(mem, bn);
Daniel Stenberg
committed
}
#define print_pubkey_BN(_type, _name, _num) \
do { \
if(pubkey->pkey._type->_name) { \
pubkey_show(data, mem, _num, #_type, #_name, pubkey->pkey._type->_name); \
Daniel Stenberg
committed
} \
Daniel Stenberg
committed
static int X509V3_ext(struct SessionHandle *data,
int certnum,
STACK_OF(X509_EXTENSION) *exts)
{
int i;
size_t j;
Daniel Stenberg
committed
if((int)sk_X509_EXTENSION_num(exts) <= 0)
Daniel Stenberg
committed
/* no extensions, bail out */
return 1;
for(i=0; i < (int)sk_X509_EXTENSION_num(exts); i++) {
Daniel Stenberg
committed
ASN1_OBJECT *obj;
X509_EXTENSION *ext = sk_X509_EXTENSION_value(exts, i);
BUF_MEM *biomem;
char buf[512];
char *ptr=buf;
char namebuf[128];
Daniel Stenberg
committed
BIO *bio_out = BIO_new(BIO_s_mem());
if(!bio_out)
return 1;
Daniel Stenberg
committed
obj = X509_EXTENSION_get_object(ext);
asn1_object_dump(obj, namebuf, sizeof(namebuf));
if(!X509V3_EXT_print(bio_out, ext, 0, 0))
ASN1_STRING_print(bio_out, (ASN1_STRING *)X509_EXTENSION_get_data(ext));
Daniel Stenberg
committed
BIO_get_mem_ptr(bio_out, &biomem);
for(j = 0; j < (size_t)biomem->length; j++) {
Daniel Stenberg
committed
const char *sep="";
if(biomem->data[j] == '\n') {
sep=", ";
j++; /* skip the newline */
};
while((j<(size_t)biomem->length) && (biomem->data[j] == ' '))
Daniel Stenberg
committed
j++;
if(j<(size_t)biomem->length)
ptr+=snprintf(ptr, sizeof(buf)-(ptr-buf), "%s%c", sep,
biomem->data[j]);
Daniel Stenberg
committed
}
Curl_ssl_push_certinfo(data, certnum, namebuf, buf);
Daniel Stenberg
committed
BIO_free(bio_out);
}
return 0; /* all is fine */
}
static CURLcode get_cert_chain(struct connectdata *conn,
struct ssl_connect_data *connssl)
{
CURLcode result;
Daniel Stenberg
committed
STACK_OF(X509) *sk;
int i;
struct SessionHandle *data = conn->data;
int numcerts;
BIO *mem;
Daniel Stenberg
committed
sk = SSL_get_peer_cert_chain(connssl->handle);
if(!sk) {
Daniel Stenberg
committed
return CURLE_OUT_OF_MEMORY;
Daniel Stenberg
committed
numcerts = sk_X509_num(sk);
result = Curl_ssl_init_certinfo(data, numcerts);
if(result) {
return result;
Daniel Stenberg
committed
mem = BIO_new(BIO_s_mem());
for(i = 0; i < numcerts; i++) {
Daniel Stenberg
committed
ASN1_INTEGER *num;
X509 *x = sk_X509_value(sk, i);
EVP_PKEY *pubkey=NULL;
int j;
char *ptr;
ASN1_BIT_STRING *psig;
Daniel Stenberg
committed
X509_NAME_print_ex(mem, X509_get_subject_name(x), 0, XN_FLAG_ONELINE);
Daniel Stenberg
committed
X509_NAME_print_ex(mem, X509_get_issuer_name(x), 0, XN_FLAG_ONELINE);
Daniel Stenberg
committed
BIO_printf(mem, "%lx", X509_get_version(x));
push_certinfo("Version", i);
Daniel Stenberg
committed
num = X509_get_serialNumber(x);
if(num->type == V_ASN1_NEG_INTEGER)
BIO_puts(mem, "-");
for(j = 0; j < num->length; j++)
BIO_printf(mem, "%02x", num->data[j]);
push_certinfo("Serial Number", i);
Daniel Stenberg
committed
#if defined(HAVE_X509_GET0_SIGNATURE) && defined(HAVE_X509_GET0_EXTENSIONS)
{
X509_ALGOR *palg;
ASN1_STRING *a = ASN1_STRING_new();
if(a) {
X509_get0_signature(&psig, &palg, x);
X509_signature_print(mem, palg, a);
ASN1_STRING_free(a);
}
i2a_ASN1_OBJECT(mem, palg->algorithm);
push_certinfo("Public Key Algorithm", i);
Daniel Stenberg
committed
X509V3_ext(data, i, X509_get0_extensions(x));
}
#else
{
/* before OpenSSL 1.0.2 */
X509_CINF *cinf = x->cert_info;
i2a_ASN1_OBJECT(mem, cinf->signature->algorithm);
push_certinfo("Signature Algorithm", i);
i2a_ASN1_OBJECT(mem, cinf->key->algor->algorithm);
push_certinfo("Public Key Algorithm", i);
X509V3_ext(data, i, cinf->extensions);
psig = x->signature;
}
#endif
Daniel Stenberg
committed
ASN1_TIME_print(mem, X509_get_notBefore(x));
push_certinfo("Start date", i);
Daniel Stenberg
committed
ASN1_TIME_print(mem, X509_get_notAfter(x));
push_certinfo("Expire date", i);
Daniel Stenberg
committed
pubkey = X509_get_pubkey(x);
if(!pubkey)
infof(data, " Unable to load public key\n");
else {
switch(pubkey->type) {
case EVP_PKEY_RSA:
BIO_printf(mem, "%d", BN_num_bits(pubkey->pkey.rsa->n));
push_certinfo("RSA Public Key", i);
Daniel Stenberg
committed
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
print_pubkey_BN(rsa, n, i);
print_pubkey_BN(rsa, e, i);
print_pubkey_BN(rsa, d, i);
print_pubkey_BN(rsa, p, i);
print_pubkey_BN(rsa, q, i);
print_pubkey_BN(rsa, dmp1, i);
print_pubkey_BN(rsa, dmq1, i);
print_pubkey_BN(rsa, iqmp, i);
break;
case EVP_PKEY_DSA:
print_pubkey_BN(dsa, p, i);
print_pubkey_BN(dsa, q, i);
print_pubkey_BN(dsa, g, i);
print_pubkey_BN(dsa, priv_key, i);
print_pubkey_BN(dsa, pub_key, i);
break;
case EVP_PKEY_DH:
print_pubkey_BN(dh, p, i);
print_pubkey_BN(dh, g, i);
print_pubkey_BN(dh, priv_key, i);
print_pubkey_BN(dh, pub_key, i);
break;
#if 0
case EVP_PKEY_EC: /* symbol not present in OpenSSL 0.9.6 */
/* left TODO */
break;
#endif
Daniel Stenberg
committed
}
EVP_PKEY_free(pubkey);
Daniel Stenberg
committed
}
for(j = 0; j < psig->length; j++)
BIO_printf(mem, "%02x:", psig->data[j]);
push_certinfo("Signature", i);
PEM_write_bio_X509(mem, x);
Daniel Stenberg
committed
}
BIO_free(mem);
Daniel Stenberg
committed
return CURLE_OK;
}
/*
* Heavily modified from:
* https://www.owasp.org/index.php/Certificate_and_Public_Key_Pinning#OpenSSL
*/
static CURLcode pkp_pin_peer_pubkey(struct SessionHandle *data, X509* cert,
const char *pinnedpubkey)
{
/* Scratch */
int len1 = 0, len2 = 0;
Patrick Monnerat
committed
unsigned char *buff1 = NULL, *temp = NULL;
/* Result is returned to caller */
Patrick Monnerat
committed
CURLcode result = CURLE_SSL_PINNEDPUBKEYNOTMATCH;
/* if a path wasn't specified, don't pin */
do {
/* Begin Gyrations to get the subjectPublicKeyInfo */
/* Thanks to Viktor Dukhovni on the OpenSSL mailing list */
/* https://groups.google.com/group/mailing.openssl.users/browse_thread
/thread/d61858dae102c6c7 */
len1 = i2d_X509_PUBKEY(X509_get_X509_PUBKEY(cert), NULL);
if(len1 < 1)
break; /* failed */
/* https://www.openssl.org/docs/crypto/buffer.html */
buff1 = temp = OPENSSL_malloc(len1);
/* https://www.openssl.org/docs/crypto/d2i_X509.html */
len2 = i2d_X509_PUBKEY(X509_get_X509_PUBKEY(cert), &temp);
/*
* These checks are verifying we got back the same values as when we
* sized the buffer. It's pretty weak since they should always be the
* same. But it gives us something to test.
*/
if((len1 != len2) || !temp || ((temp - buff1) != len1))
break; /* failed */
/* End Gyrations */
/* The one good exit point */
result = Curl_pin_peer_pubkey(data, pinnedpubkey, buff1, len1);
/* https://www.openssl.org/docs/crypto/buffer.html */
OPENSSL_free(buff1);
return result;
}
Daniel Stenberg
committed
/*
* Get the server cert, verify it and show it etc, only call failf() if the
* 'strict' argument is TRUE as otherwise all this is for informational
* purposes only!
*
* We check certificates to authenticate the server; otherwise we risk
* man-in-the-middle attack.
*/
static CURLcode servercert(struct connectdata *conn,
struct ssl_connect_data *connssl,
bool strict)
Daniel Stenberg
committed
{
Daniel Stenberg
committed
int rc;
long lerr, len;
Daniel Stenberg
committed
struct SessionHandle *data = conn->data;
Daniel Stenberg
committed
X509 *issuer;
FILE *fp;
char *buffer = data->state.buffer;
Patrick Monnerat
committed
const char *ptr;
BIO *mem = BIO_new(BIO_s_mem());
Daniel Stenberg
committed
if(data->set.ssl.certinfo)
/* we've been asked to gather certificate info! */
(void)get_cert_chain(conn, connssl);
Daniel Stenberg
committed
Daniel Stenberg
committed
connssl->server_cert = SSL_get_peer_certificate(connssl->handle);
if(!connssl->server_cert) {
if(!strict)
return CURLE_OK;
failf(data, "SSL: couldn't get peer certificate!");
return CURLE_PEER_FAILED_VERIFICATION;
infof(data, "Server certificate:\n");
Daniel Stenberg
committed
Daniel Stenberg
committed
rc = x509_name_oneline(X509_get_subject_name(connssl->server_cert),
infof(data, "\t subject: %s\n", rc?"[NONE]":buffer);
ASN1_TIME_print(mem, X509_get_notBefore(connssl->server_cert));
len = BIO_get_mem_data(mem, (char **) &ptr);
infof(data, "\t start date: %.*s\n", len, ptr);
rc = BIO_reset(mem);
ASN1_TIME_print(mem, X509_get_notAfter(connssl->server_cert));
len = BIO_get_mem_data(mem, (char **) &ptr);
infof(data, "\t expire date: %.*s\n", len, ptr);
rc = BIO_reset(mem);
BIO_free(mem);
if(data->set.ssl.verifyhost) {
result = verifyhost(conn, connssl->server_cert);
if(result) {
Daniel Stenberg
committed
X509_free(connssl->server_cert);
connssl->server_cert = NULL;
}
Daniel Stenberg
committed
}
Daniel Stenberg
committed
rc = x509_name_oneline(X509_get_issuer_name(connssl->server_cert),
buffer, BUFSIZE);
Daniel Stenberg
committed
if(rc) {
Daniel Stenberg
committed
if(strict)
failf(data, "SSL: couldn't get X509-issuer name!");
else {
Daniel Stenberg
committed
infof(data, "\t issuer: %s\n", buffer);
/* We could do all sorts of certificate verification stuff here before
deallocating the certificate. */
Daniel Stenberg
committed
Daniel Stenberg
committed
/* e.g. match issuer name with provided issuer certificate */
if(data->set.str[STRING_SSL_ISSUERCERT]) {
fp = fopen(data->set.str[STRING_SSL_ISSUERCERT], FOPEN_READTEXT);
if(!fp) {
if(strict)
failf(data, "SSL: Unable to open issuer cert (%s)",
Daniel Stenberg
committed
data->set.str[STRING_SSL_ISSUERCERT]);
X509_free(connssl->server_cert);
connssl->server_cert = NULL;
return CURLE_SSL_ISSUER_ERROR;
Daniel Stenberg
committed
}
issuer = PEM_read_X509(fp, NULL, ZERO_NULL, NULL);
if(!issuer) {
if(strict)
failf(data, "SSL: Unable to read issuer cert (%s)",
Daniel Stenberg
committed
data->set.str[STRING_SSL_ISSUERCERT]);
X509_free(connssl->server_cert);
X509_free(issuer);
fclose(fp);
return CURLE_SSL_ISSUER_ERROR;
Daniel Stenberg
committed
}
Daniel Stenberg
committed
fclose(fp);
if(X509_check_issued(issuer, connssl->server_cert) != X509_V_OK) {
failf(data, "SSL: Certificate issuer check failed (%s)",
Daniel Stenberg
committed
data->set.str[STRING_SSL_ISSUERCERT]);
X509_free(connssl->server_cert);
X509_free(issuer);
connssl->server_cert = NULL;
Daniel Stenberg
committed
return CURLE_SSL_ISSUER_ERROR;
}
Daniel Stenberg
committed
infof(data, "\t SSL certificate issuer check ok (%s)\n",
Daniel Stenberg
committed
data->set.str[STRING_SSL_ISSUERCERT]);
Daniel Stenberg
committed
X509_free(issuer);
}
Daniel Stenberg
committed
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
Daniel Stenberg
committed
and we return earlier if verifypeer is set? */
Daniel Stenberg
committed
if(strict)
failf(data, "SSL certificate verify result: %s (%ld)",
X509_verify_cert_error_string(lerr), lerr);
}
else
Daniel Stenberg
committed
infof(data, "\t SSL certificate verify result: %s (%ld),"
Daniel Stenberg
committed
" continuing anyway.\n",
X509_verify_cert_error_string(lerr), lerr);
Daniel Stenberg
committed
}
else
Daniel Stenberg
committed
infof(data, "\t SSL certificate verify ok.\n");
#if (OPENSSL_VERSION_NUMBER >= 0x0090808fL) && !defined(OPENSSL_NO_TLSEXT) && \
if(data->set.ssl.verifystatus) {
result = verifystatus(conn, connssl);
if(result) {
X509_free(connssl->server_cert);
connssl->server_cert = NULL;
return result;
}
}
if(!strict)
/* when not strict, we don't bother about the verify cert problems */
result = CURLE_OK;
Patrick Monnerat
committed
ptr = data->set.str[STRING_SSL_PINNEDPUBLICKEY];
result = pkp_pin_peer_pubkey(data, connssl->server_cert, ptr);
Patrick Monnerat
committed
failf(data, "SSL: public key does not match pinned public key!");
Daniel Stenberg
committed
X509_free(connssl->server_cert);
connssl->server_cert = NULL;
Daniel Stenberg
committed
connssl->connecting_state = ssl_connect_done;
Daniel Stenberg
committed
Daniel Stenberg
committed
}
static CURLcode ossl_connect_step3(struct connectdata *conn, int sockindex)
Daniel Stenberg
committed
{
CURLcode result = CURLE_OK;
void *old_ssl_sessionid = NULL;
Daniel Stenberg
committed
struct SessionHandle *data = conn->data;
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
SSL_SESSION *our_ssl_sessionid;
Daniel Stenberg
committed
DEBUGASSERT(ssl_connect_3 == connssl->connecting_state);
our_ssl_sessionid = SSL_get1_session(connssl->handle);
Daniel Stenberg
committed
/* SSL_get1_session() will increment the reference count and the session
will stay in memory until explicitly freed with SSL_SESSION_free(3),
regardless of its state. */
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;
}
}
result = Curl_ssl_addsessionid(conn, our_ssl_sessionid,
0 /* unknown size */);
Daniel Stenberg
committed
failf(data, "failed to store ssl session");
Daniel Stenberg
committed
}
}
else {
/* Session was incache, so refcount already incremented earlier.
* Avoid further increments with each SSL_get1_session() call.
* This does not free the session as refcount remains > 0
*/
SSL_SESSION_free(our_ssl_sessionid);
}
Daniel Stenberg
committed
/*
* We check certificates to authenticate the server; otherwise we risk
* man-in-the-middle attack; NEVERTHELESS, if we're told explicitly not to
* verify the peer ignore faults and failures from the server cert
* operations.
*/
result = servercert(conn, connssl,
(data->set.ssl.verifypeer || data->set.ssl.verifyhost));
Daniel Stenberg
committed
Daniel Stenberg
committed
connssl->connecting_state = ssl_connect_done;
Daniel Stenberg
committed
static Curl_recv ossl_recv;
static Curl_send ossl_send;
static CURLcode ossl_connect_common(struct connectdata *conn,
int sockindex,
bool nonblocking,
bool *done)
Daniel Stenberg
committed
{
Daniel Stenberg
committed
struct SessionHandle *data = conn->data;
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
curl_socket_t sockfd = conn->sock[sockindex];
long timeout_ms;
Daniel Stenberg
committed
/* 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;
}
result = ossl_connect_step1(conn, sockindex);
if(result)
return result;
Daniel Stenberg
committed
}
Daniel Stenberg
committed
while(ssl_connect_2 == connssl->connecting_state ||
ssl_connect_2_reading == connssl->connecting_state ||
ssl_connect_2_writing == connssl->connecting_state) {
Daniel Stenberg
committed
/* 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;
}
Daniel Stenberg
committed
/* 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) {
Daniel Stenberg
committed
Daniel Stenberg
committed
connssl->connecting_state?sockfd:CURL_SOCKET_BAD;
Daniel Stenberg
committed
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;
Daniel Stenberg
committed
}
else {
/* timeout */
failf(data, "SSL connection timeout");
return CURLE_OPERATION_TIMEDOUT;
Daniel Stenberg
committed
}
}
/* socket is readable or writable */
Daniel Stenberg
committed
}
/* Run transaction, and return to the caller if it failed or if this
* connection is done nonblocking 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.
result = ossl_connect_step2(conn, sockindex);
if(result || (nonblocking &&
(ssl_connect_2 == connssl->connecting_state ||
ssl_connect_2_reading == connssl->connecting_state ||
ssl_connect_2_writing == connssl->connecting_state)))
return result;
Daniel Stenberg
committed
} /* repeat step2 until all transactions are done. */
if(ssl_connect_3 == connssl->connecting_state) {
result = ossl_connect_step3(conn, sockindex);
if(result)
return result;
Daniel Stenberg
committed
}
if(ssl_connect_done == connssl->connecting_state) {
Daniel Stenberg
committed
connssl->state = ssl_connection_complete;
conn->recv[sockindex] = ossl_recv;
conn->send[sockindex] = ossl_send;
Daniel Stenberg
committed
*done = TRUE;
}
Daniel Stenberg
committed
else
Daniel Stenberg
committed
*done = FALSE;
Daniel Stenberg
committed
/* Reset our connect state machine */
connssl->connecting_state = ssl_connect_1;
Daniel Stenberg
committed
return CURLE_OK;
}
CURLcode Curl_ossl_connect_nonblocking(struct connectdata *conn,
int sockindex,
bool *done)
Daniel Stenberg
committed
{
return ossl_connect_common(conn, sockindex, TRUE, done);
Daniel Stenberg
committed
}
CURLcode Curl_ossl_connect(struct connectdata *conn, int sockindex)
Daniel Stenberg
committed
{
Daniel Stenberg
committed
bool done = FALSE;
result = ossl_connect_common(conn, sockindex, FALSE, &done);
if(result)
return result;
Daniel Stenberg
committed
DEBUGASSERT(done);
Daniel Stenberg
committed
return CURLE_OK;
}
bool Curl_ossl_data_pending(const struct connectdata *conn, int connindex)
Daniel Stenberg
committed
{
if(conn->ssl[connindex].handle)
/* SSL is in use */
return (0 != SSL_pending(conn->ssl[connindex].handle)) ? TRUE : FALSE;
Daniel Stenberg
committed
else
return FALSE;
}
static ssize_t ossl_send(struct connectdata *conn,
int sockindex,
const void *mem,
size_t len,
CURLcode *curlcode)
Daniel Stenberg
committed
{
/* SSL_write() is said to return 'int' while write() and send() returns
'size_t' */
int err;
char error_buffer[120]; /* OpenSSL documents that this must be at least 120
bytes long. */
unsigned long sslerror;
Yang Tse
committed
int memlen;
int rc;
ERR_clear_error();
Yang Tse
committed
memlen = (len > (size_t)INT_MAX) ? INT_MAX : (int)len;
rc = SSL_write(conn->ssl[sockindex].handle, mem, memlen);
Daniel Stenberg
committed
Daniel Stenberg
committed
err = SSL_get_error(conn->ssl[sockindex].handle, rc);
switch(err) {
case SSL_ERROR_WANT_READ:
case SSL_ERROR_WANT_WRITE:
/* The operation did not complete; the same TLS/SSL I/O function
should be called again later. This is basically an EWOULDBLOCK
Daniel Stenberg
committed
equivalent. */
*curlcode = CURLE_AGAIN;
Daniel Stenberg
committed
case SSL_ERROR_SYSCALL:
Daniel Stenberg
committed
failf(conn->data, "SSL_write() returned SYSCALL, errno = %d",
SOCKERRNO);
*curlcode = CURLE_SEND_ERROR;
Daniel Stenberg
committed
return -1;
case SSL_ERROR_SSL:
/* A failure in the SSL library occurred, usually a protocol error.
The OpenSSL error queue contains more information on the error. */
sslerror = ERR_get_error();
Daniel Stenberg
committed
failf(conn->data, "SSL_write() error: %s",
Daniel Stenberg
committed
ERR_error_string(sslerror, error_buffer));
*curlcode = CURLE_SEND_ERROR;
Daniel Stenberg
committed
return -1;
}
/* a true error */
Daniel Stenberg
committed
failf(conn->data, "SSL_write() return error %d", err);
*curlcode = CURLE_SEND_ERROR;
Daniel Stenberg
committed
return -1;
}
Daniel Stenberg
committed
return (ssize_t)rc; /* number of bytes */
Daniel Stenberg
committed
}
static ssize_t ossl_recv(struct connectdata *conn, /* connection data */
int num, /* socketindex */
char *buf, /* store read data here */
size_t buffersize, /* max amount to read */
CURLcode *curlcode)
Daniel Stenberg
committed
{
char error_buffer[120]; /* OpenSSL documents that this must be at
least 120 bytes long. */
unsigned long sslerror;
Yang Tse
committed
ssize_t nread;
int buffsize;
ERR_clear_error();
Yang Tse
committed
buffsize = (buffersize > (size_t)INT_MAX) ? INT_MAX : (int)buffersize;
nread = (ssize_t)SSL_read(conn->ssl[num].handle, buf, buffsize);
Daniel Stenberg
committed
/* failed SSL_read */
int err = SSL_get_error(conn->ssl[num].handle, (int)nread);
switch(err) {
case SSL_ERROR_NONE: /* this is not an error */
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;
Daniel Stenberg
committed
default:
/* openssl/ssl.h for SSL_ERROR_SYSCALL says "look at error stack/return
value/errno" */
/* https://www.openssl.org/docs/crypto/ERR_get_error.html */
Daniel Stenberg
committed
sslerror = ERR_get_error();
if((nread < 0) || sslerror) {
/* If the return code was negative or there actually is an error in the
queue */
failf(conn->data, "SSL read: %s, errno %d",
ERR_error_string(sslerror, error_buffer),
SOCKERRNO);
*curlcode = CURLE_RECV_ERROR;
return -1;
}
Daniel Stenberg
committed
}
}
return nread;
}
size_t Curl_ossl_version(char *buffer, size_t size)
{
#ifdef OPENSSL_IS_BORINGSSL
return snprintf(buffer, size, "BoringSSL");
#else /* OPENSSL_IS_BORINGSSL */
char sub[3];
unsigned long ssleay_value;
sub[2]='\0';
sub[1]='\0';
ssleay_value=SSLeay();
if(ssleay_value < 0x906000) {
ssleay_value=SSLEAY_VERSION_NUMBER;
sub[0]='\0';
Daniel Stenberg
committed
}
else {
if(ssleay_value&0xff0) {
int minor_ver = (ssleay_value >> 4) & 0xff;
if(minor_ver > 26) {
/* handle extended version introduced for 0.9.8za */
sub[1] = (char) ((minor_ver - 1) % 26 + 'a' + 1);
sub[0] = 'z';
}
else {
sub[0]=(char)(((ssleay_value>>4)&0xff) + 'a' -1);
}
Daniel Stenberg
committed
}
else
sub[0]='\0';
}
Daniel Stenberg
committed
return snprintf(buffer, size, "%s/%lx.%lx.%lx%s",
#ifdef LIBRESSL_VERSION_NUMBER
"LibreSSL"
#else
"OpenSSL"
#endif
, (ssleay_value>>28)&0xf,
(ssleay_value>>20)&0xff,
(ssleay_value>>12)&0xff,
sub);
Daniel Stenberg
committed
}
/* can be called with data == NULL */
int Curl_ossl_random(struct SessionHandle *data, unsigned char *entropy,
size_t length)
Curl_ossl_seed(data); /* Initiate the seed if not already done */
RAND_bytes(entropy, curlx_uztosi(length));
return 0; /* 0 as in no problem */
}
void Curl_ossl_md5sum(unsigned char *tmp, /* input */
size_t tmplen,
unsigned char *md5sum /* output */,
size_t unused)
{
MD5_CTX MD5pw;
(void)unused;
MD5_Init(&MD5pw);
MD5_Update(&MD5pw, tmp, tmplen);