#include "setup.h"
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#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"
#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 "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
#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 RAND_status()?TRUE:FALSE;
}
#else
#define seed_enough(x) rand_enough(x)
static bool rand_enough(int nread)
{
return (nread > 500)?TRUE:FALSE;
}
#endif
static
int random_the_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;
}
#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) );
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(conn && conn->data && conn->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(conn->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);
}
static int init_ssl=0;
static bool ssl_seeded = FALSE;
#endif
int Curl_SSL_init(void)
{
#ifdef USE_SSLEAY
if(init_ssl)
return 1;
#ifdef HAVE_ENGINE_LOAD_BUILTIN_ENGINES
ENGINE_load_builtin_engines();
#endif
SSL_load_error_strings();
if (!SSLeay_add_ssl_algorithms())
return 0;
init_ssl++;
#else
#endif
return 1;
}
void Curl_SSL_cleanup(void)
{
#ifdef USE_SSLEAY
if(init_ssl) {
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
init_ssl=0;
}
#else
#endif
}
#ifndef USE_SSLEAY
void Curl_SSL_Close(struct connectdata *conn)
{
(void)conn;
}
#endif
CURLcode Curl_SSL_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
}
CURLcode Curl_SSL_set_engine_default(struct SessionHandle *data)
{
#if defined(USE_SSLEAY) && defined(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", data->state.engine);
}
else {
failf(data, "set default crypto engine %s failed", data->state.engine);
return CURLE_SSL_ENGINE_SETFAILED;
}
}
#else
(void) data;
#endif
return (CURLE_OK);
}
struct curl_slist *Curl_SSL_engines_list(struct SessionHandle *data)
{
struct curl_slist *list = NULL;
#if defined(USE_SSLEAY) && defined(HAVE_OPENSSL_ENGINE_H)
ENGINE *e;
for (e = ENGINE_get_first(); e; e = ENGINE_get_next(e))
list = curl_slist_append(list, ENGINE_get_id(e));
#endif
(void) data;
return (list);
}
#ifdef USE_SSLEAY
void Curl_SSL_Close(struct connectdata *conn)
{
if(conn->ssl[FIRSTSOCKET].use) {
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;
}
}
}
CURLcode Curl_SSL_InitSessions(struct SessionHandle *data, long amount)
{
struct curl_ssl_session *session;
if(data->state.session)
return CURLE_OK;
session = (struct curl_ssl_session *)
malloc(amount * sizeof(struct curl_ssl_session));
if(!session)
return CURLE_OUT_OF_MEMORY;
memset(session, 0, amount * sizeof(struct curl_ssl_session));
data->set.ssl.numsessions = amount;
data->state.session = session;
data->state.sessionage = 1;
return CURLE_OK;
}
static int Get_SSL_Session(struct connectdata *conn,
SSL_SESSION **ssl_sessionid)
{
struct curl_ssl_session *check;
struct SessionHandle *data = conn->data;
long i;
for(i=0; i< data->set.ssl.numsessions; i++) {
check = &data->state.session[i];
if(!check->sessionid)
continue;
if(curl_strequal(conn->host.name, check->name) &&
(conn->remote_port == check->remote_port) &&
Curl_ssl_config_matches(&conn->ssl_config, &check->ssl_config)) {
data->state.sessionage++;
check->age = data->state.sessionage;
*ssl_sessionid = check->sessionid;
return FALSE;
}
}
*ssl_sessionid = (SSL_SESSION *)NULL;
return TRUE;
}
static int Kill_Single_Session(struct curl_ssl_session *session)
{
if(session->sessionid) {
SSL_SESSION_free(session->sessionid);
session->sessionid=NULL;
session->age = 0;
Curl_free_ssl_config(&session->ssl_config);
Curl_safefree(session->name);
session->name = NULL;
return 0;
}
else
return 1;
}
int Curl_SSL_Close_All(struct SessionHandle *data)
{
int i;
if(data->state.session) {
for(i=0; i< data->set.ssl.numsessions; i++)
Kill_Single_Session(&data->state.session[i]);
free(data->state.session);
data->state.session = NULL;
}
#ifdef HAVE_OPENSSL_ENGINE_H
if(data->state.engine) {
ENGINE_finish(data->state.engine);
ENGINE_free(data->state.engine);
data->state.engine = NULL;
}
#endif
return 0;
}
static CURLcode Store_SSL_Session(struct connectdata *conn,
struct ssl_connect_data *ssl)
{
SSL_SESSION *ssl_sessionid;
int i;
struct SessionHandle *data=conn->data;
struct curl_ssl_session *store = &data->state.session[0];
long oldest_age=data->state.session[0].age;
char *clone_host;
clone_host = strdup(conn->host.name);
if(!clone_host)
return CURLE_OUT_OF_MEMORY;
#ifdef HAVE_SSL_GET1_SESSION
ssl_sessionid = SSL_get1_session(ssl->handle);
#else
ssl_sessionid = SSL_get_session(ssl->handle);
#endif
for(i=1; (i<data->set.ssl.numsessions) &&
data->state.session[i].sessionid; i++) {
if(data->state.session[i].age < oldest_age) {
oldest_age = data->state.session[i].age;
store = &data->state.session[i];
}
}
if(i == data->set.ssl.numsessions)
Kill_Single_Session(store);
else
store = &data->state.session[i];
store->sessionid = ssl_sessionid;
store->age = data->state.sessionage;
store->name = clone_host;
store->remote_port = conn->remote_port;
if (!Curl_clone_ssl_config(&conn->ssl_config, &store->ssl_config))
return CURLE_OUT_OF_MEMORY;
return CURLE_OK;
}
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;
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;
}
return HOST_NOMATCH;
}
if (toupper(c) != toupper(*hostname++))
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;
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 = 1 + 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, txt_len, NULL);
Curl_debug(data, (direction == 1) ? CURLINFO_SSL_DATA_OUT :
CURLINFO_SSL_DATA_IN, (char *)buf, len, NULL);
(void) ssl;
}
#endif
CURLcode
Curl_SSLConnect(struct connectdata *conn,
int sockindex)
{
CURLcode retcode = CURLE_OK;
#ifdef USE_SSLEAY
struct SessionHandle *data = conn->data;
int err;
long lerr;
int what;
char * str;
SSL_METHOD *req_method;
SSL_SESSION *ssl_sessionid=NULL;
ASN1_TIME *certdate;
curl_socket_t sockfd = conn->sock[sockindex];
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
connssl->use = TRUE;
if(!ssl_seeded || data->set.ssl.random_file || data->set.ssl.egdsocket) {
random_the_seed(data);
ssl_seeded = TRUE;
}
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;
}
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) {
if (!SSL_CTX_callback_ctrl(connssl->ctx, SSL_CTRL_SET_MSG_CALLBACK,
ssl_tls_trace)) {
failf(data, "SSL: couldn't set callback!");
return CURLE_SSL_CONNECT_ERROR;
}
if (!SSL_CTX_ctrl(connssl->ctx, SSL_CTRL_SET_MSG_CALLBACK_ARG, 0, conn)) {
failf(data, "SSL: couldn't set callback argument!");
return CURLE_SSL_CONNECT_ERROR;
}
}
#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;
}
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;
}
}
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(!conn->bits.reuse) {
if(!Get_SSL_Session(conn, &ssl_sessionid)) {
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;
}
while(1) {
int writefd;
int readfd;
long timeout_ms;
if(data->set.timeout || data->set.connecttimeout) {
long has_passed;
has_passed = Curl_tvdiff(Curl_tvnow(), data->progress.t_startsingle);
if(data->set.timeout &&
(data->set.timeout>data->set.connecttimeout))
timeout_ms = data->set.timeout*1000;
else
timeout_ms = data->set.connecttimeout*1000;
timeout_ms -= has_passed;
if(timeout_ms < 0) {
failf(data, "SSL connection timeout");
return CURLE_OPERATION_TIMEOUTED;
}
}
else
timeout_ms= DEFAULT_CONNECT_TIMEOUT;
readfd = CURL_SOCKET_BAD;
writefd = CURL_SOCKET_BAD;
err = SSL_connect(connssl->handle);
if(1 != err) {
int detail = SSL_get_error(connssl->handle, err);
if(SSL_ERROR_WANT_READ == detail)
readfd = sockfd;
else if(SSL_ERROR_WANT_WRITE == detail)
writefd = sockfd;
else {
unsigned long errdetail;
char error_buffer[120];
CURLcode rc;
const char *cert_problem = NULL;
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
break;
while(1) {
what = Curl_select(readfd, writefd, (int)timeout_ms);
if(what > 0)
break;
else if(0 == what) {
failf(data, "SSL connection timeout");
return CURLE_OPERATION_TIMEDOUT;
}
else {
#if !defined(WIN32) && defined(EINTR)
if (errno == EINTR)
continue;
#endif
failf(data, "select on SSL socket, errno: %d", Curl_ourerrno());
return CURLE_SSL_CONNECT_ERROR;
}
}
}
infof (data, "SSL connection using %s\n",
SSL_get_cipher(connssl->handle));
if(!ssl_sessionid) {
retcode = Store_SSL_Session(conn, connssl);
if(retcode) {
failf(data,"failure 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(err), lerr);
}
else
infof(data, "SSL certificate verify ok.\n");
}
X509_free(connssl->server_cert);
connssl->server_cert = NULL;
#else
(void)conn;
(void)sockindex;
#endif
return retcode;
}