#include "setup.h"
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#include "urldata.h"
#include "sendf.h"
#include "formdata.h"
#include "url.h"
#include "inet_pton.h"
#include "ssluse.h"
#include "connect.h"
#include "strequal.h"
#include "select.h"
#include "sslgen.h"
#define _MPRINTF_REPLACE
#include <curl/mprintf.h>
#ifdef USE_SSLEAY
#ifdef USE_OPENSSL
#include <openssl/rand.h>
#include <openssl/x509v3.h>
#else
#include <rand.h>
#include <x509v3.h>
#endif
#include "memory.h"
#include "easyif.h"
#include "memdebug.h"
#ifndef min
#define min(a, b) ((a) < (b) ? (a) : (b))
#endif
#if OPENSSL_VERSION_NUMBER >= 0x0090581fL
#define HAVE_SSL_GET1_SESSION 1
#else
#undef HAVE_SSL_GET1_SESSION
#endif
#if OPENSSL_VERSION_NUMBER >= 0x00904100L
#define HAVE_USERDATA_IN_PWD_CALLBACK 1
#else
#undef HAVE_USERDATA_IN_PWD_CALLBACK
#endif
#if OPENSSL_VERSION_NUMBER >= 0x00907001L
#define HAVE_ENGINE_LOAD_FOUR_ARGS
#else
#undef HAVE_ENGINE_LOAD_FOUR_ARGS
#endif
#if (OPENSSL_VERSION_NUMBER >= 0x00903001L) && defined(HAVE_OPENSSL_PKCS12_H)
#define HAVE_PKCS12_SUPPORT
#else
#undef HAVE_PKCS12_SUPPORT
#endif
#if OPENSSL_VERSION_NUMBER >= 0x00906001L
#define HAVE_ERR_ERROR_STRING_N 1
#endif
#if OPENSSL_VERSION_NUMBER >= 0x00909000L
#define SSL_METHOD_QUAL const
#else
#define SSL_METHOD_QUAL
#endif
#define RAND_LOAD_LENGTH 1024
#ifndef HAVE_USERDATA_IN_PWD_CALLBACK
static char global_passwd[64];
#endif
static int passwd_callback(char *buf, int num, int verify
#if HAVE_USERDATA_IN_PWD_CALLBACK
, void *global_passwd
#endif
)
{
if(verify)
fprintf(stderr, "%s\n", buf);
else {
if(num > (int)strlen((char *)global_passwd)) {
strcpy(buf, global_passwd);
return (int)strlen(buf);
}
}
return 0;
}
#ifdef HAVE_RAND_STATUS
#define seed_enough(x) rand_enough()
static bool rand_enough(void)
{
return (bool)(0 != RAND_status());
}
#else
#define seed_enough(x) rand_enough(x)
static bool rand_enough(int nread)
{
return (bool)(nread > 500);
}
#endif
static int ossl_seed(struct SessionHandle *data)
{
char *buf = data->state.buffer;
int nread=0;
#ifndef RANDOM_FILE
if(data->set.ssl.random_file)
#define RANDOM_FILE ""
#endif
{
nread += RAND_load_file((data->set.ssl.random_file?
data->set.ssl.random_file:RANDOM_FILE),
RAND_LOAD_LENGTH);
if(seed_enough(nread))
return nread;
}
#if defined(HAVE_RAND_EGD)
#ifndef EGD_SOCKET
if(data->set.ssl.egdsocket)
#define EGD_SOCKET ""
#endif
{
int ret = RAND_egd(data->set.ssl.egdsocket?
data->set.ssl.egdsocket:EGD_SOCKET);
if(-1 != ret) {
nread += ret;
if(seed_enough(nread))
return nread;
}
}
#endif
#ifdef HAVE_RAND_SCREEN
RAND_screen();
nread = 100;
#else
{
int len;
char *area;
do {
area = Curl_FormBoundary();
if(!area)
return 3;
len = (int)strlen(area);
RAND_add(area, len, (len >> 1));
free(area);
} while (!RAND_status());
}
#endif
buf[0]=0;
RAND_file_name(buf, BUFSIZE);
if(buf[0]) {
nread += RAND_load_file(buf, RAND_LOAD_LENGTH);
if(seed_enough(nread))
return nread;
}
infof(data, "libcurl is now using a weak random seed!\n");
return nread;
}
int Curl_ossl_seed(struct SessionHandle *data)
{
static bool ssl_seeded = FALSE;
if(!ssl_seeded || data->set.ssl.random_file || data->set.ssl.egdsocket) {
ossl_seed(data);
ssl_seeded = TRUE;
}
return 0;
}
#ifndef SSL_FILETYPE_ENGINE
#define SSL_FILETYPE_ENGINE 42
#endif
#ifndef SSL_FILETYPE_PKCS12
#define SSL_FILETYPE_PKCS12 43
#endif
static int do_file_type(const char *type)
{
if(!type || !type[0])
return SSL_FILETYPE_PEM;
if(curl_strequal(type, "PEM"))
return SSL_FILETYPE_PEM;
if(curl_strequal(type, "DER"))
return SSL_FILETYPE_ASN1;
if(curl_strequal(type, "ENG"))
return SSL_FILETYPE_ENGINE;
if(curl_strequal(type, "P12"))
return SSL_FILETYPE_PKCS12;
return -1;
}
static
int cert_stuff(struct connectdata *conn,
SSL_CTX* ctx,
char *cert_file,
const char *cert_type,
char *key_file,
const char *key_type)
{
struct SessionHandle *data = conn->data;
int file_type;
if(cert_file != NULL) {
SSL *ssl;
X509 *x509;
int cert_done = 0;
if(data->set.key_passwd) {
#ifndef HAVE_USERDATA_IN_PWD_CALLBACK
size_t len = strlen(data->set.key_passwd);
if(len < sizeof(global_passwd))
memcpy(global_passwd, data->set.key_passwd, len+1);
#else
SSL_CTX_set_default_passwd_cb_userdata(ctx,
data->set.key_passwd);
#endif
SSL_CTX_set_default_passwd_cb(ctx, passwd_callback);
}
file_type = do_file_type(cert_type);
#define SSL_CLIENT_CERT_ERR \
"unable to use client certificate (no key found or wrong pass phrase?)"
switch(file_type) {
case SSL_FILETYPE_PEM:
if(SSL_CTX_use_certificate_chain_file(ctx,
cert_file) != 1) {
failf(data, SSL_CLIENT_CERT_ERR);
return 0;
}
break;
case SSL_FILETYPE_ASN1:
if(SSL_CTX_use_certificate_file(ctx,
cert_file,
file_type) != 1) {
failf(data, SSL_CLIENT_CERT_ERR);
return 0;
}
break;
case SSL_FILETYPE_ENGINE:
failf(data, "file type ENG for certificate not implemented");
return 0;
case SSL_FILETYPE_PKCS12:
{
#ifdef HAVE_PKCS12_SUPPORT
FILE *f;
PKCS12 *p12;
EVP_PKEY *pri;
f = fopen(cert_file,"rb");
if (!f) {
failf(data, "could not open PKCS12 file '%s'", cert_file);
return 0;
}
p12 = d2i_PKCS12_fp(f, NULL);
fclose(f);
PKCS12_PBE_add();
if (!PKCS12_parse(p12, data->set.key_passwd, &pri, &x509, NULL)) {
failf(data,
"could not parse PKCS12 file, check password, OpenSSL error %s",
ERR_error_string(ERR_get_error(), NULL) );
PKCS12_free(p12);
return 0;
}
PKCS12_free(p12);
if(SSL_CTX_use_certificate(ctx, x509) != 1) {
failf(data, SSL_CLIENT_CERT_ERR);
EVP_PKEY_free(pri);
X509_free(x509);
return 0;
}
if(SSL_CTX_use_PrivateKey(ctx, pri) != 1) {
failf(data, "unable to use private key from PKCS12 file '%s'",
cert_file);
EVP_PKEY_free(pri);
X509_free(x509);
return 0;
}
EVP_PKEY_free(pri);
X509_free(x509);
cert_done = 1;
break;
#else
failf(data, "file type P12 for certificate not supported");
return 0;
#endif
}
default:
failf(data, "not supported file type '%s' for certificate", cert_type);
return 0;
}
file_type = do_file_type(key_type);
switch(file_type) {
case SSL_FILETYPE_PEM:
if(cert_done)
break;
if(key_file == NULL)
key_file=cert_file;
case SSL_FILETYPE_ASN1:
if(SSL_CTX_use_PrivateKey_file(ctx, key_file, file_type) != 1) {
failf(data, "unable to set private key file: '%s' type %s\n",
key_file, key_type?key_type:"PEM");
return 0;
}
break;
case SSL_FILETYPE_ENGINE:
#ifdef HAVE_OPENSSL_ENGINE_H
{
EVP_PKEY *priv_key = NULL;
if(data->state.engine) {
#ifdef HAVE_ENGINE_LOAD_FOUR_ARGS
UI_METHOD *ui_method = UI_OpenSSL();
#endif
if(!key_file || !key_file[0]) {
failf(data, "no key set to load from crypto engine\n");
return 0;
}
priv_key = (EVP_PKEY *)
ENGINE_load_private_key(data->state.engine,key_file,
#ifdef HAVE_ENGINE_LOAD_FOUR_ARGS
ui_method,
#endif
data->set.key_passwd);
if(!priv_key) {
failf(data, "failed to load private key from crypto engine\n");
return 0;
}
if(SSL_CTX_use_PrivateKey(ctx, priv_key) != 1) {
failf(data, "unable to set private key\n");
EVP_PKEY_free(priv_key);
return 0;
}
EVP_PKEY_free(priv_key);
}
else {
failf(data, "crypto engine not set, can't load private key\n");
return 0;
}
}
break;
#else
failf(data, "file type ENG for private key not supported\n");
return 0;
#endif
case SSL_FILETYPE_PKCS12:
if(!cert_done) {
failf(data, "file type P12 for private key not supported\n");
return 0;
}
break;
default:
failf(data, "not supported file type for private key\n");
return 0;
}
ssl=SSL_new(ctx);
if (NULL == ssl) {
failf(data,"unable to create an SSL structure\n");
return 0;
}
x509=SSL_get_certificate(ssl);
if(x509 != NULL) {
EVP_PKEY *pktmp = X509_get_pubkey(x509);
EVP_PKEY_copy_parameters(pktmp,SSL_get_privatekey(ssl));
EVP_PKEY_free(pktmp);
}
SSL_free(ssl);
if(!SSL_CTX_check_private_key(ctx)) {
failf(data, "Private key does not match the certificate public key");
return(0);
}
#ifndef HAVE_USERDATA_IN_PWD_CALLBACK
memset(global_passwd, 0, sizeof(global_passwd));
#endif
}
return(1);
}
static
int cert_verify_callback(int ok, X509_STORE_CTX *ctx)
{
X509 *err_cert;
char buf[256];
err_cert=X509_STORE_CTX_get_current_cert(ctx);
X509_NAME_oneline(X509_get_subject_name(err_cert), buf, sizeof(buf));
return ok;
}
static char *SSL_strerror(unsigned long error, char *buf, size_t size)
{
#ifdef HAVE_ERR_ERROR_STRING_N
ERR_error_string_n(error, buf, size);
#else
(void) size;
ERR_error_string(error, buf);
#endif
return (buf);
}
#endif
#ifdef USE_SSLEAY
int Curl_ossl_init(void)
{
#ifdef HAVE_ENGINE_LOAD_BUILTIN_ENGINES
ENGINE_load_builtin_engines();
#endif
SSL_load_error_strings();
if (!SSLeay_add_ssl_algorithms())
return 0;
return 1;
}
#endif
#ifdef USE_SSLEAY
void Curl_ossl_cleanup(void)
{
ERR_free_strings();
EVP_cleanup();
#ifdef HAVE_ENGINE_cleanup
ENGINE_cleanup();
#endif
#ifdef HAVE_CRYPTO_CLEANUP_ALL_EX_DATA
CRYPTO_cleanup_all_ex_data();
#endif
}
int Curl_ossl_check_cxn(struct connectdata *conn)
{
int rc;
char buf;
rc = SSL_peek(conn->ssl[FIRSTSOCKET].handle, (void*)&buf, 1);
if (rc > 0)
return 1;
if (rc == 0)
return 0;
return -1;
}
#endif
CURLcode Curl_ossl_set_engine(struct SessionHandle *data, const char *engine)
{
#if defined(USE_SSLEAY) && defined(HAVE_OPENSSL_ENGINE_H)
ENGINE *e = ENGINE_by_id(engine);
if (!e) {
failf(data, "SSL Engine '%s' not found", engine);
return (CURLE_SSL_ENGINE_NOTFOUND);
}
if (data->state.engine) {
ENGINE_finish(data->state.engine);
ENGINE_free(data->state.engine);
data->state.engine = NULL;
}
if (!ENGINE_init(e)) {
char buf[256];
ENGINE_free(e);
failf(data, "Failed to initialise SSL Engine '%s':\n%s",
engine, SSL_strerror(ERR_get_error(), buf, sizeof(buf)));
return (CURLE_SSL_ENGINE_INITFAILED);
}
data->state.engine = e;
return (CURLE_OK);
#else
(void)engine;
failf(data, "SSL Engine not supported");
return (CURLE_SSL_ENGINE_NOTFOUND);
#endif
}
#ifdef USE_SSLEAY
CURLcode Curl_ossl_set_engine_default(struct SessionHandle *data)
{
#ifdef HAVE_OPENSSL_ENGINE_H
if (data->state.engine) {
if (ENGINE_set_default(data->state.engine, ENGINE_METHOD_ALL) > 0) {
infof(data,"set default crypto engine '%s'\n", ENGINE_get_id(data->state.engine));
}
else {
failf(data, "set default crypto engine '%s' failed", ENGINE_get_id(data->state.engine));
return CURLE_SSL_ENGINE_SETFAILED;
}
}
#else
(void) data;
#endif
return CURLE_OK;
}
#endif
struct curl_slist *Curl_ossl_engines_list(struct SessionHandle *data)
{
struct curl_slist *list = NULL;
#if defined(USE_SSLEAY) && defined(HAVE_OPENSSL_ENGINE_H)
struct curl_slist *beg = NULL;
ENGINE *e;
for (e = ENGINE_get_first(); e; e = ENGINE_get_next(e)) {
list = curl_slist_append(list, ENGINE_get_id(e));
if (list == NULL) {
curl_slist_free_all(beg);
return NULL;
}
else if (beg == NULL) {
beg = list;
}
}
#endif
(void) data;
return (list);
}
#ifdef USE_SSLEAY
void Curl_ossl_close(struct connectdata *conn)
{
int i;
ERR_remove_state(0);
for(i=0; i<2; i++) {
struct ssl_connect_data *connssl = &conn->ssl[i];
if(connssl->handle) {
(void)SSL_shutdown(connssl->handle);
SSL_set_connect_state(connssl->handle);
SSL_free (connssl->handle);
connssl->handle = NULL;
}
if(connssl->ctx) {
SSL_CTX_free (connssl->ctx);
connssl->ctx = NULL;
}
connssl->use = FALSE;
}
}
int Curl_ossl_shutdown(struct connectdata *conn, int sockindex)
{
int retval = 0;
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
struct SessionHandle *data = conn->data;
char buf[120];
unsigned long sslerror;
ssize_t nread;
int err;
int done = 0;
if(data->set.ftp_ccc == CURLFTPSSL_CCC_ACTIVE)
(void)SSL_shutdown(connssl->handle);
if(connssl->handle) {
while(!done) {
int what = Curl_socket_ready(conn->sock[sockindex],
CURL_SOCKET_BAD, SSL_SHUTDOWN_TIMEOUT);
if(what > 0) {
nread = (ssize_t)SSL_read(conn->ssl[sockindex].handle, buf,
sizeof(buf));
err = SSL_get_error(conn->ssl[sockindex].handle, (int)nread);
switch(err) {
case SSL_ERROR_NONE:
case SSL_ERROR_ZERO_RETURN:
done = 1;
break;
case SSL_ERROR_WANT_READ:
infof(data, "SSL_ERROR_WANT_READ\n");
break;
case SSL_ERROR_WANT_WRITE:
infof(data, "SSL_ERROR_WANT_WRITE\n");
done = 1;
break;
default:
sslerror = ERR_get_error();
failf(conn->data, "SSL read: %s, errno %d",
ERR_error_string(sslerror, buf),
SOCKERRNO);
done = 1;
break;
}
}
else if(0 == what) {
failf(data, "SSL shutdown timeout");
done = 1;
break;
}
else {
failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO);
retval = -1;
done = 1;
}
}
if(data->set.verbose) {
#ifdef HAVE_SSL_GET_SHUTDOWN
switch(SSL_get_shutdown(connssl->handle)) {
case SSL_SENT_SHUTDOWN:
infof(data, "SSL_get_shutdown() returned SSL_SENT_SHUTDOWN\n");
break;
case SSL_RECEIVED_SHUTDOWN:
infof(data, "SSL_get_shutdown() returned SSL_RECEIVED_SHUTDOWN\n");
break;
case SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN:
infof(data, "SSL_get_shutdown() returned SSL_SENT_SHUTDOWN|"
"SSL_RECEIVED__SHUTDOWN\n");
break;
}
#endif
}
connssl->use = FALSE;
SSL_free (connssl->handle);
connssl->handle = NULL;
}
return retval;
}
void Curl_ossl_session_free(void *ptr)
{
SSL_SESSION_free(ptr);
}
int Curl_ossl_close_all(struct SessionHandle *data)
{
#ifdef HAVE_OPENSSL_ENGINE_H
if(data->state.engine) {
ENGINE_finish(data->state.engine);
ENGINE_free(data->state.engine);
data->state.engine = NULL;
}
#else
(void)data;
#endif
return 0;
}
static int Curl_ASN1_UTCTIME_output(struct connectdata *conn,
const char *prefix,
ASN1_UTCTIME *tm)
{
char *asn1_string;
int gmt=FALSE;
int i;
int year=0,month=0,day=0,hour=0,minute=0,second=0;
struct SessionHandle *data = conn->data;
#ifdef CURL_DISABLE_VERBOSE_STRINGS
(void)prefix;
#endif
if(!data->set.verbose)
return 0;
i=tm->length;
asn1_string=(char *)tm->data;
if(i < 10)
return 1;
if(asn1_string[i-1] == 'Z')
gmt=TRUE;
for (i=0; i<10; i++)
if((asn1_string[i] > '9') || (asn1_string[i] < '0'))
return 2;
year= (asn1_string[0]-'0')*10+(asn1_string[1]-'0');
if(year < 50)
year+=100;
month= (asn1_string[2]-'0')*10+(asn1_string[3]-'0');
if((month > 12) || (month < 1))
return 3;
day= (asn1_string[4]-'0')*10+(asn1_string[5]-'0');
hour= (asn1_string[6]-'0')*10+(asn1_string[7]-'0');
minute= (asn1_string[8]-'0')*10+(asn1_string[9]-'0');
if((asn1_string[10] >= '0') && (asn1_string[10] <= '9') &&
(asn1_string[11] >= '0') && (asn1_string[11] <= '9'))
second= (asn1_string[10]-'0')*10+(asn1_string[11]-'0');
infof(data,
"%s%04d-%02d-%02d %02d:%02d:%02d %s\n",
prefix, year+1900, month, day, hour, minute, second, (gmt?"GMT":""));
return 0;
}
#endif
#ifdef USE_SSLEAY
#define HOST_NOMATCH 0
#define HOST_MATCH 1
static int hostmatch(const char *hostname, const char *pattern)
{
while (1) {
int c = *pattern++;
if (c == '\0')
return (*hostname ? HOST_NOMATCH : HOST_MATCH);
if (c == '*') {
c = *pattern;
if (c == '\0')
return HOST_MATCH;
while (*hostname) {
if (hostmatch(hostname++,pattern) == HOST_MATCH)
return HOST_MATCH;
}
break;
}
if (toupper(c) != toupper(*hostname++))
break;
}
return HOST_NOMATCH;
}
static int
cert_hostcheck(const char *match_pattern, const char *hostname)
{
if (!match_pattern || !*match_pattern ||
!hostname || !*hostname)
return 0;
if(curl_strequal(hostname,match_pattern))
return 1;
if (hostmatch(hostname,match_pattern) == HOST_MATCH)
return 1;
return 0;
}
static CURLcode verifyhost(struct connectdata *conn,
X509 *server_cert)
{
bool matched = FALSE;
int target = GEN_DNS;
int addrlen = 0;
struct SessionHandle *data = conn->data;
STACK_OF(GENERAL_NAME) *altnames;
#ifdef ENABLE_IPV6
struct in6_addr addr;
#else
struct in_addr addr;
#endif
CURLcode res = CURLE_OK;
#ifdef ENABLE_IPV6
if(conn->bits.ipv6_ip &&
Curl_inet_pton(AF_INET6, conn->host.name, &addr)) {
target = GEN_IPADD;
addrlen = sizeof(struct in6_addr);
}
else
#endif
if(Curl_inet_pton(AF_INET, conn->host.name, &addr)) {
target = GEN_IPADD;
addrlen = sizeof(struct in_addr);
}
altnames = X509_get_ext_d2i(server_cert, NID_subject_alt_name, NULL, NULL);
if(altnames) {
int numalts;
int i;
numalts = sk_GENERAL_NAME_num(altnames);
for (i=0; (i<numalts) && !matched; i++) {
const GENERAL_NAME *check = sk_GENERAL_NAME_value(altnames, i);
if(check->type == target) {
const char *altptr = (char *)ASN1_STRING_data(check->d.ia5);
int altlen;
switch(target) {
case GEN_DNS:
if (cert_hostcheck(altptr, conn->host.name))
matched = TRUE;
break;
case GEN_IPADD:
altlen = ASN1_STRING_length(check->d.ia5);
if((altlen == addrlen) && !memcmp(altptr, &addr, altlen))
matched = TRUE;
break;
}
}
}
GENERAL_NAMES_free(altnames);
}
if(matched)
infof(data, "\t subjectAltName: %s matched\n", conn->host.dispname);
else {
int j,i=-1 ;
unsigned char *nulstr = (unsigned char *)"";
unsigned char *peer_CN = nulstr;
X509_NAME *name = X509_get_subject_name(server_cert) ;
if (name)
while ((j=X509_NAME_get_index_by_NID(name,NID_commonName,i))>=0)
i=j;
if (i>=0) {
ASN1_STRING *tmp = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(name,i));
if (tmp && ASN1_STRING_type(tmp) == V_ASN1_UTF8STRING) {
j = ASN1_STRING_length(tmp);
if (j >= 0) {
peer_CN = OPENSSL_malloc(j+1);
if (peer_CN) {
memcpy(peer_CN, ASN1_STRING_data(tmp), j);
peer_CN[j] = '\0';
}
}
}
else
j = ASN1_STRING_to_UTF8(&peer_CN, tmp);
}
if (peer_CN == nulstr)
peer_CN = NULL;
#ifdef CURL_DOES_CONVERSIONS
else {
size_t rc;
rc = Curl_convert_from_utf8(data, peer_CN, strlen(peer_CN));
if (rc != CURLE_OK) {
OPENSSL_free(peer_CN);
return rc;
}
}
#endif
if (!peer_CN) {
if(data->set.ssl.verifyhost > 1) {
failf(data,
"SSL: unable to obtain common name from peer certificate");
return CURLE_SSL_PEER_CERTIFICATE;
}
else {
infof(data, "\t common name: WARNING couldn't obtain\n");
}
}
else if(!cert_hostcheck((const char *)peer_CN, conn->host.name)) {
if(data->set.ssl.verifyhost > 1) {
failf(data, "SSL: certificate subject name '%s' does not match "
"target host name '%s'", peer_CN, conn->host.dispname);
res = CURLE_SSL_PEER_CERTIFICATE;
}
else
infof(data, "\t common name: %s (does not match '%s')\n",
peer_CN, conn->host.dispname);
}
else {
infof(data, "\t common name: %s (matched)\n", peer_CN);
}
if(peer_CN)
OPENSSL_free(peer_CN);
}
return res;
}
#endif
#ifdef SSL_CTRL_SET_MSG_CALLBACK
static const char *ssl_msg_type(int ssl_ver, int msg)
{
if (ssl_ver == SSL2_VERSION_MAJOR) {
switch (msg) {
case SSL2_MT_ERROR:
return "Error";
case SSL2_MT_CLIENT_HELLO:
return "Client hello";
case SSL2_MT_CLIENT_MASTER_KEY:
return "Client key";
case SSL2_MT_CLIENT_FINISHED:
return "Client finished";
case SSL2_MT_SERVER_HELLO:
return "Server hello";
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, ");
}
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;
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' : '?');
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 = snprintf(ssl_buf, sizeof(ssl_buf), "SSLv%c, %s%s (%d):\n",
ver, tls_rt_name, msg_name, msg_type);
Curl_debug(data, CURLINFO_TEXT, ssl_buf, (size_t)txt_len, NULL);
Curl_debug(data, (direction == 1) ? CURLINFO_SSL_DATA_OUT :
CURLINFO_SSL_DATA_IN, (char *)buf, len, NULL);
(void) ssl;
}
#endif
#ifdef USE_SSLEAY
static CURLcode
Curl_ossl_connect_step1(struct connectdata *conn,
int sockindex)
{
CURLcode retcode = CURLE_OK;
struct SessionHandle *data = conn->data;
SSL_METHOD_QUAL SSL_METHOD *req_method=NULL;
void *ssl_sessionid=NULL;
curl_socket_t sockfd = conn->sock[sockindex];
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
DEBUGASSERT(ssl_connect_1 == connssl->connecting_state);
Curl_ossl_seed(data);
switch(data->set.ssl.version) {
default:
case CURL_SSLVERSION_DEFAULT:
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;
}
if (connssl->ctx)
SSL_CTX_free(connssl->ctx);
connssl->ctx = SSL_CTX_new(req_method);
if(!connssl->ctx) {
failf(data, "SSL: couldn't create a context!");
return CURLE_OUT_OF_MEMORY;
}
#ifdef SSL_CTRL_SET_MSG_CALLBACK
if (data->set.fdebug && data->set.verbose) {
if (!SSL_CTX_callback_ctrl(connssl->ctx, SSL_CTRL_SET_MSG_CALLBACK,
(void (*)(void))ssl_tls_trace)) {
infof(data, "SSL: couldn't set callback!\n");
}
else if (!SSL_CTX_ctrl(connssl->ctx, SSL_CTRL_SET_MSG_CALLBACK_ARG, 0,
conn)) {
infof(data, "SSL: couldn't set callback argument!\n");
}
}
#endif
SSL_CTX_set_options(connssl->ctx, SSL_OP_ALL);
#if 0
if (data->state.used_interface == Curl_if_multi)
SSL_CTX_ctrl(connssl->ctx, BIO_C_SET_NBIO, 1, NULL);
#endif
if(data->set.cert) {
if(!cert_stuff(conn,
connssl->ctx,
data->set.cert,
data->set.cert_type,
data->set.key,
data->set.key_type)) {
return CURLE_SSL_CERTPROBLEM;
}
}
if(data->set.ssl.cipher_list) {
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) {
if (!SSL_CTX_load_verify_locations(connssl->ctx, data->set.ssl.CAfile,
data->set.ssl.CApath)) {
if (data->set.ssl.verifypeer) {
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_BADFILE;
}
else {
infof(data, "error setting certificate verify locations,"
" continuing anyway:\n");
}
}
else {
infof(data, "successfully set certificate verify locations:\n");
}
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_CTX_set_verify(connssl->ctx,
data->set.ssl.verifypeer?SSL_VERIFY_PEER:SSL_VERIFY_NONE,
cert_verify_callback);
if(data->set.ssl.fsslctx) {
retcode = (*data->set.ssl.fsslctx)(data, connssl->ctx,
data->set.ssl.fsslctxp);
if(retcode) {
failf(data,"error signaled by ssl ctx callback");
return retcode;
}
}
if (connssl->handle)
SSL_free(connssl->handle);
connssl->handle = SSL_new(connssl->ctx);
if (!connssl->handle) {
failf(data, "SSL: couldn't create a context (handle)!");
return CURLE_OUT_OF_MEMORY;
}
SSL_set_connect_state(connssl->handle);
connssl->server_cert = 0x0;
if(!Curl_ssl_getsessionid(conn, &ssl_sessionid, NULL)) {
if (!SSL_set_session(connssl->handle, ssl_sessionid)) {
failf(data, "SSL: SSL_set_session failed: %s",
ERR_error_string(ERR_get_error(),NULL));
return CURLE_SSL_CONNECT_ERROR;
}
infof (data, "SSL re-using session ID\n");
}
if (!SSL_set_fd(connssl->handle, sockfd)) {
failf(data, "SSL: SSL_set_fd failed: %s",
ERR_error_string(ERR_get_error(),NULL));
return CURLE_SSL_CONNECT_ERROR;
}
connssl->connecting_state = ssl_connect_2;
return CURLE_OK;
}
static CURLcode
Curl_ossl_connect_step2(struct connectdata *conn,
int sockindex, long *timeout_ms)
{
struct SessionHandle *data = conn->data;
int err;
long has_passed;
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
DEBUGASSERT(ssl_connect_2 == connssl->connecting_state
|| ssl_connect_2_reading == connssl->connecting_state
|| ssl_connect_2_writing == connssl->connecting_state);
if(data->set.timeout && data->set.connecttimeout) {
if(data->set.timeout<data->set.connecttimeout)
*timeout_ms = data->set.timeout;
else
*timeout_ms = data->set.connecttimeout;
}
else if(data->set.timeout)
*timeout_ms = data->set.timeout;
else if(data->set.connecttimeout)
*timeout_ms = data->set.connecttimeout;
else
*timeout_ms = DEFAULT_CONNECT_TIMEOUT;
has_passed = Curl_tvdiff(Curl_tvnow(), data->progress.t_startsingle);
*timeout_ms -= has_passed;
if(*timeout_ms < 0) {
failf(data, "SSL connection timeout");
return CURLE_OPERATION_TIMEOUTED;
}
err = SSL_connect(connssl->handle);
if(1 != err) {
int detail = SSL_get_error(connssl->handle, err);
if(SSL_ERROR_WANT_READ == detail) {
connssl->connecting_state = ssl_connect_2_reading;
return CURLE_OK;
}
else if(SSL_ERROR_WANT_WRITE == detail) {
connssl->connecting_state = ssl_connect_2_writing;
return CURLE_OK;
}
else {
unsigned long errdetail;
char error_buffer[256];
CURLcode rc;
const char *cert_problem = NULL;
connssl->connecting_state = ssl_connect_2;
errdetail = ERR_get_error();
switch(errdetail) {
case 0x1407E086:
case 0x14090086:
cert_problem = "SSL certificate problem, verify that the CA cert is"
" OK. Details:\n";
rc = CURLE_SSL_CACERT;
break;
default:
rc = CURLE_SSL_CONNECT_ERROR;
break;
}
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;
}
SSL_strerror(errdetail, error_buffer, sizeof(error_buffer));
failf(data, "%s%s", cert_problem ? cert_problem : "", error_buffer);
return rc;
}
}
else {
connssl->connecting_state = ssl_connect_3;
infof (data, "SSL connection using %s\n",
SSL_get_cipher(connssl->handle));
return CURLE_OK;
}
}
static CURLcode
Curl_ossl_connect_step3(struct connectdata *conn,
int sockindex)
{
CURLcode retcode = CURLE_OK;
char * str;
long lerr;
ASN1_TIME *certdate;
void *ssl_sessionid=NULL;
struct SessionHandle *data = conn->data;
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
DEBUGASSERT(ssl_connect_3 == connssl->connecting_state);
if(Curl_ssl_getsessionid(conn, &ssl_sessionid, NULL)) {
SSL_SESSION *our_ssl_sessionid;
#ifdef HAVE_SSL_GET1_SESSION
our_ssl_sessionid = SSL_get1_session(connssl->handle);
#else
our_ssl_sessionid = SSL_get_session(connssl->handle);
#endif
retcode = Curl_ssl_addsessionid(conn, our_ssl_sessionid,
0 );
if(retcode) {
failf(data, "failed to store ssl session");
return retcode;
}
}
connssl->server_cert = SSL_get_peer_certificate(connssl->handle);
if(!connssl->server_cert) {
failf(data, "SSL: couldn't get peer certificate!");
return CURLE_SSL_PEER_CERTIFICATE;
}
infof (data, "Server certificate:\n");
str = X509_NAME_oneline(X509_get_subject_name(connssl->server_cert),
NULL, 0);
if(!str) {
failf(data, "SSL: couldn't get X509-subject!");
X509_free(connssl->server_cert);
connssl->server_cert = NULL;
return CURLE_SSL_CONNECT_ERROR;
}
infof(data, "\t subject: %s\n", str);
CRYPTO_free(str);
certdate = X509_get_notBefore(connssl->server_cert);
Curl_ASN1_UTCTIME_output(conn, "\t start date: ", certdate);
certdate = X509_get_notAfter(connssl->server_cert);
Curl_ASN1_UTCTIME_output(conn, "\t expire date: ", certdate);
if(data->set.ssl.verifyhost) {
retcode = verifyhost(conn, connssl->server_cert);
if(retcode) {
X509_free(connssl->server_cert);
connssl->server_cert = NULL;
return retcode;
}
}
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);
lerr = data->set.ssl.certverifyresult=
SSL_get_verify_result(connssl->handle);
if(data->set.ssl.certverifyresult != X509_V_OK) {
if(data->set.ssl.verifypeer) {
failf(data, "SSL certificate verify result: %s (%ld)",
X509_verify_cert_error_string(lerr), lerr);
retcode = CURLE_SSL_PEER_CERTIFICATE;
}
else
infof(data, "SSL certificate verify result: %s (%ld),"
" continuing anyway.\n",
X509_verify_cert_error_string(lerr), lerr);
}
else
infof(data, "SSL certificate verify ok.\n");
}
X509_free(connssl->server_cert);
connssl->server_cert = NULL;
connssl->connecting_state = ssl_connect_done;
return retcode;
}
static CURLcode
Curl_ossl_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;
if (ssl_connect_1==connssl->connecting_state) {
retcode = Curl_ossl_connect_step1(conn, sockindex);
if (retcode)
return retcode;
}
timeout_ms = 0;
while (ssl_connect_2 == connssl->connecting_state ||
ssl_connect_2_reading == connssl->connecting_state ||
ssl_connect_2_writing == connssl->connecting_state) {
if (connssl->connecting_state == ssl_connect_2_reading
|| connssl->connecting_state == ssl_connect_2_writing) {
int writefd = ssl_connect_2_writing==
connssl->connecting_state?sockfd:CURL_SOCKET_BAD;
int readfd = ssl_connect_2_reading==
connssl->connecting_state?sockfd:CURL_SOCKET_BAD;
while(1) {
int what = Curl_socket_ready(readfd, writefd, nonblocking?0:(int)timeout_ms);
if(what > 0)
break;
else if(0 == what) {
if (nonblocking) {
*done = FALSE;
return CURLE_OK;
}
else {
failf(data, "SSL connection timeout");
return CURLE_OPERATION_TIMEDOUT;
}
}
else {
failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO);
return CURLE_SSL_CONNECT_ERROR;
}
}
}
retcode = Curl_ossl_connect_step2(conn, sockindex, &timeout_ms);
if (retcode)
return retcode;
}
if (ssl_connect_3==connssl->connecting_state) {
retcode = Curl_ossl_connect_step3(conn, sockindex);
if (retcode)
return retcode;
}
if (ssl_connect_done==connssl->connecting_state) {
*done = TRUE;
}
else {
*done = FALSE;
}
connssl->connecting_state = ssl_connect_1;
return CURLE_OK;
}
CURLcode
Curl_ossl_connect_nonblocking(struct connectdata *conn,
int sockindex,
bool *done)
{
return Curl_ossl_connect_common(conn, sockindex, TRUE, done);
}
CURLcode
Curl_ossl_connect(struct connectdata *conn,
int sockindex)
{
CURLcode retcode;
bool done = FALSE;
retcode = Curl_ossl_connect_common(conn, sockindex, FALSE, &done);
if (retcode)
return retcode;
DEBUGASSERT(done);
return CURLE_OK;
}
ssize_t Curl_ossl_send(struct connectdata *conn,
int sockindex,
void *mem,
size_t len)
{
int err;
char error_buffer[120];
unsigned long sslerror;
int rc = SSL_write(conn->ssl[sockindex].handle, mem, (int)len);
if(rc < 0) {
err = SSL_get_error(conn->ssl[sockindex].handle, rc);
switch(err) {
case SSL_ERROR_WANT_READ:
case SSL_ERROR_WANT_WRITE:
return 0;
case SSL_ERROR_SYSCALL:
failf(conn->data, "SSL_write() returned SYSCALL, errno = %d\n",
SOCKERRNO);
return -1;
case SSL_ERROR_SSL:
sslerror = ERR_get_error();
failf(conn->data, "SSL_write() error: %s\n",
ERR_error_string(sslerror, error_buffer));
return -1;
}
failf(conn->data, "SSL_write() return error %d\n", err);
return -1;
}
return (ssize_t)rc;
}
ssize_t Curl_ossl_recv(struct connectdata *conn,
int num,
char *buf,
size_t buffersize,
bool *wouldblock)
{
char error_buffer[120];
unsigned long sslerror;
ssize_t nread = (ssize_t)SSL_read(conn->ssl[num].handle, buf,
(int)buffersize);
*wouldblock = FALSE;
if(nread < 0) {
int err = SSL_get_error(conn->ssl[num].handle, (int)nread);
switch(err) {
case SSL_ERROR_NONE:
case SSL_ERROR_ZERO_RETURN:
break;
case SSL_ERROR_WANT_READ:
case SSL_ERROR_WANT_WRITE:
*wouldblock = TRUE;
return -1;
default:
sslerror = ERR_get_error();
failf(conn->data, "SSL read: %s, errno %d",
ERR_error_string(sslerror, error_buffer),
SOCKERRNO);
return -1;
}
}
return nread;
}
size_t Curl_ossl_version(char *buffer, size_t size)
{
#ifdef YASSL_VERSION
return snprintf(buffer, size, " yassl/%s", YASSL_VERSION);
#else
#if (SSLEAY_VERSION_NUMBER >= 0x905000)
{
char sub[2];
unsigned long ssleay_value;
sub[1]='\0';
ssleay_value=SSLeay();
if(ssleay_value < 0x906000) {
ssleay_value=SSLEAY_VERSION_NUMBER;
sub[0]='\0';
}
else {
if(ssleay_value&0xff0) {
sub[0]=(char)(((ssleay_value>>4)&0xff) + 'a' -1);
}
else
sub[0]='\0';
}
return snprintf(buffer, size, " OpenSSL/%lx.%lx.%lx%s",
(ssleay_value>>28)&0xf,
(ssleay_value>>20)&0xff,
(ssleay_value>>12)&0xff,
sub);
}
#else
#if (SSLEAY_VERSION_NUMBER >= 0x900000)
return snprintf(buffer, size, " OpenSSL/%lx.%lx.%lx",
(SSLEAY_VERSION_NUMBER>>28)&0xff,
(SSLEAY_VERSION_NUMBER>>20)&0xff,
(SSLEAY_VERSION_NUMBER>>12)&0xf);
#else
{
char sub[2];
sub[1]='\0';
if(SSLEAY_VERSION_NUMBER&0x0f) {
sub[0]=(SSLEAY_VERSION_NUMBER&0x0f) + 'a' -1;
}
else
sub[0]='\0';
return snprintf(buffer, size, " SSL/%x.%x.%x%s",
(SSLEAY_VERSION_NUMBER>>12)&0xff,
(SSLEAY_VERSION_NUMBER>>8)&0xf,
(SSLEAY_VERSION_NUMBER>>4)&0xf, sub);
}
#endif
#endif
#endif
}
#endif