Newer
Older
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",
Daniel Stenberg
committed
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");
Daniel Stenberg
committed
if(data->set.str[STRING_SSL_CRLFILE]) {
Daniel Stenberg
committed
/* tell SSL where to find CRL file that is used to check certificate
* revocation */
lookup=X509_STORE_add_lookup(SSL_CTX_get_cert_store(connssl->ctx),
X509_LOOKUP_file());
(!X509_load_crl_file(lookup, data->set.str[STRING_SSL_CRLFILE],
X509_FILETYPE_PEM)) ) {
failf(data, "error loading CRL file: %s",
data->set.str[STRING_SSL_CRLFILE]);
Daniel Stenberg
committed
return CURLE_SSL_CRL_BADFILE;
}
else {
/* Everything is fine. */
infof(data, "successfully load CRL file:\n");
X509_STORE_set_flags(SSL_CTX_get_cert_store(connssl->ctx),
Daniel Stenberg
committed
X509_V_FLAG_CRL_CHECK|X509_V_FLAG_CRL_CHECK_ALL);
Daniel Stenberg
committed
}
infof(data,
" CRLfile: %s\n", data->set.str[STRING_SSL_CRLFILE] ?
Daniel Stenberg
committed
data->set.str[STRING_SSL_CRLFILE]: "none");
Daniel Stenberg
committed
}
/* 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) {
result = (*data->set.ssl.fsslctx)(data, connssl->ctx,
data->set.ssl.fsslctxp);
if(result) {
failf(data, "error signaled by ssl ctx callback");
}
}
/* Lets make an SSL structure */
Daniel Stenberg
committed
if(connssl->handle)
Daniel Stenberg
committed
SSL_free(connssl->handle);
Daniel Stenberg
committed
connssl->handle = SSL_new(connssl->ctx);
Daniel Stenberg
committed
if(!connssl->handle) {
failf(data, "SSL: couldn't create a context (handle)!");
return CURLE_OUT_OF_MEMORY;
}
#if (OPENSSL_VERSION_NUMBER >= 0x0090808fL) && !defined(OPENSSL_NO_TLSEXT) && \
!defined(OPENSSL_IS_BORINGSSL)
if(data->set.ssl.verifystatus)
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. */
Daniel Stenberg
committed
const char *cert_problem = NULL;
Daniel Stenberg
committed
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
connssl->connecting_state = ssl_connect_2; /* the connection failed,
we're not waiting for
anything else. */
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 */
/* fall-through */
case 0x14090086:
/* 14090086:
SSL routines:
SSL3_GET_SERVER_CERTIFICATE:
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
cert_problem = "SSL certificate problem, verify that the CA cert is"
" OK.";
Daniel Stenberg
committed
break;
default:
SSL_strerror(errdetail, error_buffer, sizeof(error_buffer));
Daniel Stenberg
committed
break;
}
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
Daniel Stenberg
committed
failf(data, "%s%s", cert_problem ? cert_problem : "", 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_0;
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;
}
static void pubkey_show(struct SessionHandle *data,
int num,
const char *type,
const char *name,
unsigned char *raw,
int len)
{
Daniel Stenberg
committed
int i;
char namebuf[32];
char *buffer;
buffer = malloc(left);
if(buffer) {
char *ptr=buffer;
snprintf(namebuf, sizeof(namebuf), "%s(%s)", type, name);
for(i=0; i< len; i++) {
snprintf(ptr, left, "%02x:", raw[i]);
ptr += 3;
left -= 3;
}
infof(data, " %s: %s\n", namebuf, buffer);
Curl_ssl_push_certinfo(data, num, namebuf, buffer);
Daniel Stenberg
committed
}
}
#define print_pubkey_BN(_type, _name, _num) \
do { \
if(pubkey->pkey._type->_name) { \
int len = BN_num_bytes(pubkey->pkey._type->_name); \
if(len < CERTBUFFERSIZE) { \
BN_bn2bin(pubkey->pkey._type->_name, (unsigned char*)bufp); \
pubkey_show(data, _num, #_type, #_name, (unsigned char*)bufp, len); \
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(sk_X509_EXTENSION_num(exts) <= 0)
/* no extensions, bail out */
return 1;
for(i=0; i<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));
infof(data, "%s: %s\n", namebuf,
X509_EXTENSION_get_critical(ext)?"(critical)":"");
if(!X509V3_EXT_print(bio_out, ext, 0, 0))
M_ASN1_OCTET_STRING_print(bio_out, ext->value);
BIO_get_mem_ptr(bio_out, &biomem);
/* biomem->length bytes at biomem->data, this little loop here is only
done for the infof() call, we send the "raw" data to the certinfo
function */
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
}
infof(data, " %s\n", buf);
Curl_ssl_push_certinfo(data, certnum, namebuf, buf);
Daniel Stenberg
committed
BIO_free(bio_out);
}
return 0; /* all is fine */
}
static void X509_signature(struct SessionHandle *data,
int numcert,
ASN1_STRING *sig)
{
char buf[1024];
char *ptr = buf;
int i;
for(i=0; i<sig->length; i++)
Daniel Stenberg
committed
ptr+=snprintf(ptr, sizeof(buf)-(ptr-buf), "%02x:", sig->data[i]);
infof(data, " Signature: %s\n", buf);
Curl_ssl_push_certinfo(data, numcert, "Signature", buf);
Daniel Stenberg
committed
}
static void dumpcert(struct SessionHandle *data, X509 *x, int numcert)
{
BIO *bio_out = BIO_new(BIO_s_mem());
BUF_MEM *biomem;
/* this outputs the cert in this 64 column wide style with newlines and
-----BEGIN CERTIFICATE----- texts and more */
PEM_write_bio_X509(bio_out, x);
BIO_get_mem_ptr(bio_out, &biomem);
Curl_ssl_push_certinfo_len(data, numcert,
"Cert", biomem->data, biomem->length);
Daniel Stenberg
committed
BIO_free(bio_out);
}
/*
* This size was previously 512 which has been reported "too small" without
* any specifics, so it was enlarged to allow more data to get shown uncut.
* The "perfect" size is yet to figure out.
*/
#define CERTBUFFERSIZE 8192
Daniel Stenberg
committed
static CURLcode get_cert_chain(struct connectdata *conn,
struct ssl_connect_data *connssl)
{
CURLcode result;
Daniel Stenberg
committed
STACK_OF(X509) *sk;
int i;
Daniel Stenberg
committed
struct SessionHandle *data = conn->data;
int numcerts;
bufp = malloc(CERTBUFFERSIZE);
if(!bufp)
return CURLE_OUT_OF_MEMORY;
Daniel Stenberg
committed
sk = SSL_get_peer_cert_chain(connssl->handle);
if(!sk) {
free(bufp);
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
infof(data, "--- Certificate chain\n");
for(i=0; i<numcerts; i++) {
Daniel Stenberg
committed
long value;
ASN1_INTEGER *num;
ASN1_TIME *certdate;
/* get the certs in "importance order" */
#if 0
X509 *x = sk_X509_value(sk, numcerts - i - 1);
#else
X509 *x = sk_X509_value(sk, i);
#endif
X509_CINF *cinf;
EVP_PKEY *pubkey=NULL;
int j;
char *ptr;
(void)x509_name_oneline(X509_get_subject_name(x), bufp, CERTBUFFERSIZE);
infof(data, "%2d Subject: %s\n", i, bufp);
Curl_ssl_push_certinfo(data, i, "Subject", bufp);
Daniel Stenberg
committed
(void)x509_name_oneline(X509_get_issuer_name(x), bufp, CERTBUFFERSIZE);
infof(data, " Issuer: %s\n", bufp);
Curl_ssl_push_certinfo(data, i, "Issuer", bufp);
Daniel Stenberg
committed
value = X509_get_version(x);
infof(data, " Version: %lu (0x%lx)\n", value+1, value);
snprintf(bufp, CERTBUFFERSIZE, "%lx", value);
Curl_ssl_push_certinfo(data, i, "Version", bufp); /* hex */
Daniel Stenberg
committed
num=X509_get_serialNumber(x);
if(num->length <= 4) {
Daniel Stenberg
committed
value = ASN1_INTEGER_get(num);
infof(data, " Serial Number: %ld (0x%lx)\n", value, value);
snprintf(bufp, CERTBUFFERSIZE, "%lx", value);
Daniel Stenberg
committed
}
else {
Daniel Stenberg
committed
Daniel Stenberg
committed
*ptr++ = 0;
if(num->type == V_ASN1_NEG_INTEGER)
*ptr++='-';
for(j=0; (j<num->length) && (left>=4); j++) {
Daniel Stenberg
committed
/* TODO: length restrictions */
snprintf(ptr, 3, "%02x%c",num->data[j],
((j+1 == num->length)?'\n':':'));
ptr += 3;
Daniel Stenberg
committed
}
if(num->length)
infof(data, " Serial Number: %s\n", bufp);
Daniel Stenberg
committed
else
Daniel Stenberg
committed
}
Curl_ssl_push_certinfo(data, i, "Serial Number", bufp); /* hex */
Daniel Stenberg
committed
cinf = x->cert_info;
j = asn1_object_dump(cinf->signature->algorithm, bufp, CERTBUFFERSIZE);
Daniel Stenberg
committed
if(!j) {
infof(data, " Signature Algorithm: %s\n", bufp);
Curl_ssl_push_certinfo(data, i, "Signature Algorithm", bufp);
Daniel Stenberg
committed
}
certdate = X509_get_notBefore(x);
asn1_output(certdate, bufp, CERTBUFFERSIZE);
infof(data, " Start date: %s\n", bufp);
Curl_ssl_push_certinfo(data, i, "Start date", bufp);
Daniel Stenberg
committed
certdate = X509_get_notAfter(x);
asn1_output(certdate, bufp, CERTBUFFERSIZE);
infof(data, " Expire date: %s\n", bufp);
Curl_ssl_push_certinfo(data, i, "Expire date", bufp);
Daniel Stenberg
committed
j = asn1_object_dump(cinf->key->algor->algorithm, bufp, CERTBUFFERSIZE);
Daniel Stenberg
committed
if(!j) {
infof(data, " Public Key Algorithm: %s\n", bufp);
Curl_ssl_push_certinfo(data, i, "Public Key Algorithm", bufp);
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:
infof(data, " RSA Public Key (%d bits)\n",
BN_num_bits(pubkey->pkey.rsa->n));
snprintf(bufp, CERTBUFFERSIZE, "%d", BN_num_bits(pubkey->pkey.rsa->n));
Curl_ssl_push_certinfo(data, i, "RSA Public Key", bufp);
Daniel Stenberg
committed
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
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
}
EVP_PKEY_free(pubkey);
Daniel Stenberg
committed
}
X509V3_ext(data, i, cinf->extensions);
X509_signature(data, i, x->signature);
dumpcert(data, x, i);
}
Daniel Stenberg
committed
return CURLE_OK;
}
/*
* Heavily modified from:
* https://www.owasp.org/index.php/Certificate_and_Public_Key_Pinning#OpenSSL
*/
Patrick Monnerat
committed
static CURLcode pkp_pin_peer_pubkey(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 */
/* http://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 */
/* http://www.openssl.org/docs/crypto/buffer.html */
buff1 = temp = OPENSSL_malloc(len1);
break; /* failed */
/* http://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.Its 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 */
Patrick Monnerat
committed
result = Curl_pin_peer_pubkey(pinnedpubkey, buff1, len1);
} while(0);
/* http://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;
Daniel Stenberg
committed
long lerr;
ASN1_TIME *certdate;
struct SessionHandle *data = conn->data;
Daniel Stenberg
committed
X509 *issuer;
FILE *fp;
char *buffer = data->state.buffer;
Patrick Monnerat
committed
const char *ptr;
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) {
Daniel Stenberg
committed
if(strict)
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);
Daniel Stenberg
committed
certdate = X509_get_notBefore(connssl->server_cert);
asn1_output(certdate, buffer, BUFSIZE);
Daniel Stenberg
committed
infof(data, "\t start date: %s\n", buffer);
Daniel Stenberg
committed
certdate = X509_get_notAfter(connssl->server_cert);
asn1_output(certdate, buffer, BUFSIZE);
Daniel Stenberg
committed
infof(data, "\t expire date: %s\n", buffer);
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], "r");
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) && \
!defined(OPENSSL_IS_BORINGSSL)
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];
if(!result && ptr) {
result = pkp_pin_peer_pubkey(connssl->server_cert, ptr);
if(result)
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);
#ifdef HAVE_SSL_GET1_SESSION
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.
This function was introduced in openssl 0.9.5a. */
Daniel Stenberg
committed
#else
our_ssl_sessionid = SSL_get_session(connssl->handle);
Daniel Stenberg
committed
/* if SSL_get1_session() is unavailable, use SSL_get_session().
This is an inferior option because the session can be flushed
at any time by openssl. It is included only so curl compiles
under versions of openssl < 0.9.5a.
Daniel Stenberg
committed
WARNING: How curl behaves if it's session is flushed is
untested.
*/
Daniel Stenberg
committed
#endif
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
}
}
#ifdef HAVE_SSL_GET1_SESSION
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);
}
#endif
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)