forked from enlightenment/efl
658 lines
18 KiB
C
658 lines
18 KiB
C
#include <openssl/x509v3.h>
|
|
#include <openssl/ssl.h>
|
|
#include <openssl/err.h>
|
|
#include <openssl/dh.h>
|
|
|
|
#ifdef HAVE_SYS_SOCKET_H
|
|
# include <sys/socket.h>
|
|
#endif
|
|
#ifdef HAVE_NETINET_IN_H
|
|
# include <netinet/in.h>
|
|
#endif
|
|
#ifdef HAVE_ARPA_INET_H
|
|
# include <arpa/inet.h>
|
|
#endif
|
|
|
|
#if defined HAVE_DLOPEN && ! defined _WIN32
|
|
# include <dlfcn.h>
|
|
#endif
|
|
|
|
#ifdef _WIN32
|
|
# include <evil_private.h> /* dlsym */
|
|
#endif
|
|
|
|
#include "ecore_con_private.h"
|
|
|
|
/* OpenSSL's BIO is the abstraction for I/O, provide one for Efl.Io.* */
|
|
static int
|
|
efl_net_socket_bio_create(BIO *b)
|
|
{
|
|
#if (LIBRESSL_VERSION_NUMBER >= 0x3050000fL) || ((OPENSSL_VERSION_NUMBER >= 0x10100000L) && !defined(LIBRESSL_VERSION_NUMBER))
|
|
BIO_set_init(b, 1);
|
|
BIO_set_data(b, NULL);
|
|
BIO_set_flags(b, 0);
|
|
#else
|
|
b->init = 1;
|
|
b->num = 0;
|
|
b->ptr = NULL;
|
|
b->flags = 0;
|
|
#endif
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
efl_net_socket_bio_destroy(BIO *b)
|
|
{
|
|
if (!b) return 0;
|
|
#if (LIBRESSL_VERSION_NUMBER >= 0x3050000fL) || ((OPENSSL_VERSION_NUMBER >= 0x10100000L) && !defined(LIBRESSL_VERSION_NUMBER))
|
|
BIO_set_init(b, 0);
|
|
BIO_set_data(b, NULL);
|
|
BIO_set_flags(b, 0);
|
|
#else
|
|
b->init = 0;
|
|
b->ptr = NULL;
|
|
b->flags = 0;
|
|
#endif
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
efl_net_socket_bio_read(BIO *b, char *buf, int len)
|
|
{
|
|
Eina_Rw_Slice slice = {
|
|
.mem = buf,
|
|
.len = len
|
|
};
|
|
#if (LIBRESSL_VERSION_NUMBER >= 0x3050000fL) || ((OPENSSL_VERSION_NUMBER >= 0x10100000L) && !defined(LIBRESSL_VERSION_NUMBER))
|
|
Eo *sock = BIO_get_data(b);
|
|
#else
|
|
Eo *sock = b->ptr;
|
|
#endif
|
|
Eina_Error err;
|
|
|
|
if ((!buf) || (len <= 0)) return 0;
|
|
if (!sock) return 0;
|
|
|
|
if (!efl_io_reader_can_read_get(sock))
|
|
{
|
|
DBG("socket=%p would block if read!", sock);
|
|
BIO_set_retry_read(b);
|
|
return -1;
|
|
}
|
|
|
|
err = efl_io_reader_read(sock, &slice);
|
|
BIO_clear_retry_flags(b);
|
|
if (err)
|
|
{
|
|
if (err == EAGAIN)
|
|
BIO_set_retry_write(b);
|
|
return -1;
|
|
}
|
|
|
|
return slice.len;
|
|
}
|
|
|
|
static int
|
|
efl_net_socket_bio_write(BIO *b, const char *buf, int len)
|
|
{
|
|
Eina_Slice slice = {
|
|
.mem = buf,
|
|
.len = len
|
|
};
|
|
#if (LIBRESSL_VERSION_NUMBER >= 0x3050000fL) || ((OPENSSL_VERSION_NUMBER >= 0x10100000L) && !defined(LIBRESSL_VERSION_NUMBER))
|
|
Eo *sock = BIO_get_data(b);
|
|
#else
|
|
Eo *sock = b->ptr;
|
|
#endif
|
|
Eina_Error err;
|
|
|
|
if ((!buf) || (len <= 0)) return 0;
|
|
if (!sock) return 0;
|
|
|
|
if (!efl_io_writer_can_write_get(sock))
|
|
{
|
|
DBG("socket=%p would block if written!", sock);
|
|
BIO_set_retry_write(b);
|
|
return -1;
|
|
}
|
|
|
|
err = efl_io_writer_write(sock, &slice, NULL);
|
|
BIO_clear_retry_flags(b);
|
|
if (err)
|
|
{
|
|
if (err == EAGAIN)
|
|
BIO_set_retry_write(b);
|
|
return -1;
|
|
}
|
|
|
|
return slice.len;
|
|
}
|
|
|
|
static long
|
|
efl_net_socket_bio_ctrl(BIO *b EINA_UNUSED, int cmd, long num EINA_UNUSED, void *ptr EINA_UNUSED)
|
|
{
|
|
if (cmd == BIO_CTRL_FLUSH)
|
|
/* looks mandatory, but doesn't have a meaning here */
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
efl_net_socket_bio_puts(BIO *b, const char *str)
|
|
{
|
|
return efl_net_socket_bio_write(b, str, strlen(str));
|
|
}
|
|
|
|
static BIO_METHOD *
|
|
__efl_net_socket_bio_get(void)
|
|
{
|
|
#if (LIBRESSL_VERSION_NUMBER >= 0x3050000fL) || ((OPENSSL_VERSION_NUMBER >= 0x10100000L) && !defined(LIBRESSL_VERSION_NUMBER))
|
|
static BIO_METHOD *efl_net_socket_bio = NULL;
|
|
|
|
if (efl_net_socket_bio) return efl_net_socket_bio;
|
|
efl_net_socket_bio = BIO_meth_new(0x400 /* 0x400 means source & sink */,
|
|
"efl_net_socket wrapper");
|
|
if (!efl_net_socket_bio) return NULL;
|
|
if (!BIO_meth_set_write(efl_net_socket_bio, efl_net_socket_bio_write)
|
|
|| !BIO_meth_set_read(efl_net_socket_bio, efl_net_socket_bio_read)
|
|
|| !BIO_meth_set_puts(efl_net_socket_bio, efl_net_socket_bio_puts)
|
|
|| !BIO_meth_set_ctrl(efl_net_socket_bio, efl_net_socket_bio_ctrl)
|
|
|| !BIO_meth_set_create(efl_net_socket_bio, efl_net_socket_bio_create)
|
|
|| !BIO_meth_set_destroy(efl_net_socket_bio, efl_net_socket_bio_destroy))
|
|
{
|
|
BIO_meth_free(efl_net_socket_bio);
|
|
efl_net_socket_bio = NULL;
|
|
return NULL;
|
|
}
|
|
// FIXME: some day we need to clean up, but for now a singleton alloc is ok
|
|
// BIO_meth_free(efl_net_socket_bio);
|
|
return efl_net_socket_bio;
|
|
#else
|
|
static BIO_METHOD efl_net_socket_bio = {
|
|
0x400, /* 0x400 means source & sink */
|
|
"efl_net_socket wrapper",
|
|
efl_net_socket_bio_write,
|
|
efl_net_socket_bio_read,
|
|
efl_net_socket_bio_puts,
|
|
NULL, /* no gets */
|
|
efl_net_socket_bio_ctrl,
|
|
efl_net_socket_bio_create,
|
|
efl_net_socket_bio_destroy
|
|
};
|
|
return &efl_net_socket_bio;
|
|
#endif
|
|
}
|
|
|
|
struct _Efl_Net_Ssl_Conn
|
|
{
|
|
SSL *ssl;
|
|
BIO *bio;
|
|
const char *hostname;
|
|
Eina_Bool hostname_verify;
|
|
Eina_Bool did_certificates;
|
|
};
|
|
|
|
#define EFL_NET_SOCKET_SSL_CIPHERS "aRSA+HIGH:+kEDH:+kRSA:!kSRP:!kPSK:+3DES:!MD5"
|
|
|
|
#define _efl_net_ssl_conn_session_debug(conn) \
|
|
__efl_net_ssl_conn_session_debug(__FILE__, __LINE__, __func__, conn)
|
|
static void
|
|
__efl_net_ssl_conn_session_debug(const char *file, int line, const char *fname, Efl_Net_Ssl_Conn *conn)
|
|
{
|
|
STACK_OF(X509) * sk_X509;
|
|
STACK_OF(SSL_CIPHER) *sk_CIPHER;
|
|
|
|
if (!eina_log_domain_level_check(_ecore_con_log_dom, EINA_LOG_LEVEL_DBG)) return;
|
|
|
|
sk_X509 = SSL_get_peer_cert_chain(conn->ssl);
|
|
if (!sk_X509)
|
|
eina_log_print(_ecore_con_log_dom, EINA_LOG_LEVEL_DBG, file, fname, line,
|
|
"ssl_conn=%p No peer certificate chain", conn);
|
|
else
|
|
{
|
|
char subject[4096], issuer[4096];
|
|
int i;
|
|
|
|
eina_log_print(_ecore_con_log_dom, EINA_LOG_LEVEL_DBG, file, fname, line,
|
|
"ssl_conn=%p Peer Certificates:", conn);
|
|
for (i = 0; i < sk_X509_num(sk_X509); i++)
|
|
{
|
|
X509 *item = sk_X509_value(sk_X509, i);
|
|
eina_log_print(_ecore_con_log_dom, EINA_LOG_LEVEL_DBG, file, fname, line,
|
|
" #%02d %s (Issuer: %s)",
|
|
i,
|
|
X509_NAME_oneline(X509_get_subject_name(item), subject, sizeof(subject)),
|
|
X509_NAME_oneline(X509_get_issuer_name(item), issuer, sizeof(issuer)));
|
|
if (eina_log_domain_level_check(_ecore_con_log_dom, EINA_LOG_LEVEL_DBG + 1))
|
|
X509_print_fp(stderr, item);
|
|
}
|
|
}
|
|
|
|
sk_CIPHER = SSL_get_ciphers(conn->ssl);
|
|
if (!sk_CIPHER)
|
|
eina_log_print(_ecore_con_log_dom, EINA_LOG_LEVEL_DBG, file, fname, line,
|
|
"ssl_conn=%p No ciphers", conn);
|
|
else
|
|
{
|
|
char shared[8192];
|
|
const SSL_CIPHER *cipher;
|
|
char *p;
|
|
int i;
|
|
|
|
eina_log_print(_ecore_con_log_dom, EINA_LOG_LEVEL_DBG, file, fname, line,
|
|
"ssl_conn=%p Ciphers:", conn);
|
|
for (i = 0; i < sk_SSL_CIPHER_num(sk_CIPHER); i++)
|
|
{
|
|
cipher = sk_SSL_CIPHER_value(sk_CIPHER, i);
|
|
eina_log_print(_ecore_con_log_dom, EINA_LOG_LEVEL_DBG, file, fname, line,
|
|
" #%03d %s", i, SSL_CIPHER_get_name(cipher));
|
|
}
|
|
|
|
p = SSL_get_shared_ciphers(conn->ssl, shared, sizeof(shared));
|
|
if (!p)
|
|
eina_log_print(_ecore_con_log_dom, EINA_LOG_LEVEL_DBG, file, fname, line,
|
|
"ssl_conn=%p No Client (Shared) Ciphers", conn);
|
|
else
|
|
{
|
|
eina_log_print(_ecore_con_log_dom, EINA_LOG_LEVEL_DBG, file, fname, line,
|
|
"ssl_conn=%p Client (Shared) Ciphers:", conn);
|
|
i = 0;
|
|
do
|
|
{
|
|
char *n = strchr(p, ':');
|
|
if (n)
|
|
{
|
|
*n = '\0';
|
|
n++;
|
|
}
|
|
|
|
eina_log_print(_ecore_con_log_dom, EINA_LOG_LEVEL_DBG, file, fname, line,
|
|
" #%03d %s", i, p);
|
|
p = n;
|
|
i++;
|
|
}
|
|
while (p);
|
|
}
|
|
|
|
cipher = SSL_get_current_cipher(conn->ssl);
|
|
if (!cipher)
|
|
eina_log_print(_ecore_con_log_dom, EINA_LOG_LEVEL_DBG, file, fname, line,
|
|
"ssl_conn=%p No cipher in use", conn);
|
|
else
|
|
eina_log_print(_ecore_con_log_dom, EINA_LOG_LEVEL_DBG, file, fname, line,
|
|
"ssl_conn=%p Current Cipher: %s (%s)",
|
|
conn,
|
|
SSL_CIPHER_get_version(cipher),
|
|
SSL_CIPHER_get_name(cipher));
|
|
}
|
|
|
|
if (eina_log_domain_level_check(_ecore_con_log_dom, EINA_LOG_LEVEL_DBG + 1))
|
|
SSL_SESSION_print_fp(stderr, SSL_get_session(conn->ssl));
|
|
|
|
eina_log_print(_ecore_con_log_dom, EINA_LOG_LEVEL_DBG, file, fname, line,
|
|
"ssl_conn=%p end of SSL session information", conn);
|
|
}
|
|
|
|
#define _efl_net_ssl_conn_check_errors() \
|
|
__efl_net_ssl_conn_check_errors(__FILE__, __LINE__, __func__)
|
|
static unsigned long
|
|
__efl_net_ssl_conn_check_errors(const char *file, int line, const char *fname)
|
|
{
|
|
unsigned long first = 0;
|
|
do
|
|
{
|
|
const char *_ssl_err_file, *_ssl_err_data;
|
|
int _ssl_err_line, _ssl_err_flags;
|
|
unsigned long _ssl_err = ERR_get_error_line_data(&_ssl_err_file, &_ssl_err_line, &_ssl_err_data, &_ssl_err_flags);
|
|
if (!_ssl_err) break;
|
|
if (!first) first = _ssl_err;
|
|
eina_log_print(_ecore_con_log_dom, EINA_LOG_LEVEL_ERR, file, fname, line,
|
|
"OpenSSL error %s:%d%s%s: %s",
|
|
_ssl_err_file, _ssl_err_line,
|
|
(_ssl_err_flags & ERR_TXT_STRING) ? " " : "",
|
|
(_ssl_err_flags & ERR_TXT_STRING) ? _ssl_err_data : "",
|
|
ERR_reason_error_string(_ssl_err));
|
|
}
|
|
while (1);
|
|
return first;
|
|
}
|
|
|
|
static Eina_Error
|
|
efl_net_ssl_conn_setup(Efl_Net_Ssl_Conn *conn, Eina_Bool is_dialer, Efl_Net_Socket *sock, Efl_Net_Ssl_Context *context)
|
|
{
|
|
char vbuf[32];
|
|
const char *ssl_ver_str = NULL;
|
|
int ssl_ver;
|
|
static const struct {
|
|
int ver;
|
|
const char *str;
|
|
} *ssl_ver_itr, ssl_ver_map[] = {
|
|
#ifdef SSL3_VERSION
|
|
{SSL3_VERSION, "SSLv3.0"},
|
|
#endif
|
|
#ifdef TLS1_VERSION
|
|
{TLS1_VERSION, "TLSv1.0"},
|
|
#endif
|
|
#ifdef TLS1_1_VERSION
|
|
{TLS1_1_VERSION, "TLSv1.1"},
|
|
#endif
|
|
#ifdef TLS1_2_VERSION
|
|
{TLS1_2_VERSION, "TLSv1.2"},
|
|
#endif
|
|
#ifdef DTLS1_VERSION
|
|
{DTLS1_VERSION, "DTLSv1.0"},
|
|
#endif
|
|
#ifdef DTLS1_2_VERSION
|
|
{DTLS1_2_VERSION, "DTLSv1.2"},
|
|
#endif
|
|
#ifdef DTLS1_BAD_VER
|
|
{DTLS1_BAD_VER, "DTLSv1.0"},
|
|
#endif
|
|
{0, NULL}
|
|
};
|
|
|
|
EINA_SAFETY_ON_TRUE_RETURN_VAL(conn->ssl != NULL, EALREADY);
|
|
|
|
conn->ssl = efl_net_ssl_context_connection_new(context);
|
|
EINA_SAFETY_ON_NULL_RETURN_VAL(conn->ssl, ENOSYS);
|
|
|
|
conn->bio = BIO_new(__efl_net_socket_bio_get());
|
|
EINA_SAFETY_ON_NULL_GOTO(conn->bio, error_bio);
|
|
|
|
#if (LIBRESSL_VERSION_NUMBER >= 0x3050000fL) || ((OPENSSL_VERSION_NUMBER >= 0x10100000L) && !defined(LIBRESSL_VERSION_NUMBER))
|
|
BIO_set_data(conn->bio, sock);
|
|
#else
|
|
conn->bio->ptr = sock;
|
|
#endif
|
|
|
|
SSL_set_bio(conn->ssl, conn->bio, conn->bio);
|
|
if (is_dialer)
|
|
SSL_set_connect_state(conn->ssl);
|
|
else
|
|
SSL_set_accept_state(conn->ssl);
|
|
|
|
ssl_ver = SSL_version(conn->ssl);
|
|
for (ssl_ver_itr = ssl_ver_map; ssl_ver_itr->str != NULL; ssl_ver_itr++)
|
|
{
|
|
if (ssl_ver_itr->ver == ssl_ver)
|
|
{
|
|
ssl_ver_str = ssl_ver_itr->str;
|
|
break;
|
|
}
|
|
}
|
|
if (!ssl_ver_str)
|
|
{
|
|
snprintf(vbuf, sizeof(vbuf), "%#x", ssl_ver);
|
|
ssl_ver_str = vbuf;
|
|
}
|
|
DBG("Using SSL %s", ssl_ver_str);
|
|
|
|
return 0;
|
|
|
|
error_bio:
|
|
SSL_shutdown(conn->ssl);
|
|
SSL_free(conn->ssl);
|
|
conn->ssl = NULL;
|
|
return ENOSYS;
|
|
}
|
|
|
|
static void
|
|
efl_net_ssl_conn_teardown(Efl_Net_Ssl_Conn *conn)
|
|
{
|
|
if (conn->bio)
|
|
{
|
|
/* NOTE: no BIO_free() as it's done by SSL_free(). */
|
|
}
|
|
|
|
if (conn->ssl)
|
|
{
|
|
if (!SSL_shutdown(conn->ssl))
|
|
SSL_shutdown(conn->ssl);
|
|
|
|
SSL_free(conn->ssl);
|
|
conn->ssl = NULL;
|
|
}
|
|
|
|
eina_stringshare_replace(&conn->hostname, NULL);
|
|
}
|
|
|
|
static Eina_Error
|
|
efl_net_ssl_conn_write(Efl_Net_Ssl_Conn *conn, Eina_Slice *slice)
|
|
{
|
|
int r;
|
|
|
|
EINA_SAFETY_ON_NULL_RETURN_VAL(conn->ssl, EINVAL);
|
|
|
|
r = SSL_write(conn->ssl, slice->mem, slice->len);
|
|
if (r < 0)
|
|
{
|
|
int ssl_err = SSL_get_error(conn->ssl, r);
|
|
|
|
slice->len = 0;
|
|
if (ssl_err == SSL_ERROR_WANT_WRITE) return EAGAIN;
|
|
_efl_net_ssl_conn_check_errors();
|
|
ERR("ssl_conn=%p could not write", conn);
|
|
return EINVAL;
|
|
}
|
|
slice->len = r;
|
|
return 0;
|
|
}
|
|
|
|
static Eina_Error
|
|
efl_net_ssl_conn_read(Efl_Net_Ssl_Conn *conn, Eina_Rw_Slice *slice)
|
|
{
|
|
int r;
|
|
|
|
EINA_SAFETY_ON_NULL_RETURN_VAL(conn->ssl, EINVAL);
|
|
|
|
r = SSL_read(conn->ssl, slice->mem, slice->len);
|
|
if (r < 0)
|
|
{
|
|
int ssl_err = SSL_get_error(conn->ssl, r);
|
|
|
|
slice->len = 0;
|
|
if (ssl_err == SSL_ERROR_WANT_READ) return EAGAIN;
|
|
_efl_net_ssl_conn_check_errors();
|
|
ERR("ssl_conn=%p could not read", conn);
|
|
return EINVAL;
|
|
}
|
|
slice->len = r;
|
|
return 0;
|
|
}
|
|
|
|
/* OpenSSL 1.0.2 introduced X509_check_host() and X509_check_ip_asc()
|
|
* and with them the X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT define.
|
|
*/
|
|
static int
|
|
_replace_X509_check_host(X509 *x EINA_UNUSED,
|
|
const char *chk EINA_UNUSED,
|
|
size_t chklen EINA_UNUSED,
|
|
unsigned int flags EINA_UNUSED,
|
|
char **peername EINA_UNUSED)
|
|
{
|
|
ERR("your OpenSSL do not support X509_check_ip_asc() - no verification can be done");
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
_replace_X509_check_ip_asc(X509 *x EINA_UNUSED,
|
|
const char *ipasc EINA_UNUSED,
|
|
unsigned int flags EINA_UNUSED)
|
|
{
|
|
ERR("your OpenSSL do not support X509_check_ip_asc() - no verification can be done");
|
|
return 0;
|
|
}
|
|
|
|
static int (*_sym_X509_check_host) (X509 *x, const char *chk, size_t chklen, unsigned int flags, char **peername) = NULL;
|
|
static int (*_sym_X509_check_ip_asc) (X509 *x, const char *ipasc, unsigned int flags) = NULL;
|
|
|
|
static inline void
|
|
_X509_check_init(void)
|
|
{
|
|
if (_sym_X509_check_host) return;
|
|
#ifdef HAVE_DLOPEN
|
|
_sym_X509_check_host = dlsym(NULL, "X509_check_host");
|
|
_sym_X509_check_ip_asc = dlsym(NULL, "X509_check_ip_asc");
|
|
if (_sym_X509_check_host && _sym_X509_check_ip_asc) return;
|
|
#endif
|
|
_sym_X509_check_host = _replace_X509_check_host;
|
|
_sym_X509_check_ip_asc = _replace_X509_check_ip_asc;
|
|
}
|
|
|
|
static Eina_Error
|
|
_efl_net_ssl_conn_hostname_verify(Efl_Net_Ssl_Conn *conn)
|
|
{
|
|
X509 *x509;
|
|
struct sockaddr_storage addr;
|
|
const char *label;
|
|
int family = AF_INET;
|
|
int r;
|
|
|
|
EINA_SAFETY_ON_NULL_RETURN_VAL(conn->ssl, EINVAL);
|
|
|
|
if ((!conn->hostname) || (conn->hostname[0] == '\0'))
|
|
{
|
|
ERR("ssl_conn=%p no hostname, cannot verify", conn);
|
|
return EFL_NET_SOCKET_SSL_ERROR_CERTIFICATE_VERIFY_FAILED;
|
|
}
|
|
|
|
x509 = SSL_get_peer_certificate(conn->ssl);
|
|
if (!x509)
|
|
{
|
|
ERR("ssl_conn=%p no peer certificate!", conn);
|
|
return EFL_NET_SOCKET_SSL_ERROR_HANDSHAKE;
|
|
}
|
|
|
|
_X509_check_init();
|
|
|
|
if (strchr(conn->hostname, ':')) family = AF_INET6;
|
|
if (inet_pton(family, conn->hostname, &addr) == 1)
|
|
{
|
|
label = "IP address";
|
|
r = _sym_X509_check_ip_asc(x509, conn->hostname, 0);
|
|
}
|
|
else
|
|
{
|
|
label = "hostname";
|
|
r = _sym_X509_check_host(x509, conn->hostname, 0, 0, NULL);
|
|
}
|
|
|
|
if (r != 1)
|
|
{
|
|
ERR("ssl_conn=%p %s='%s' doesn't match certificate.",
|
|
conn, label, conn->hostname);
|
|
return EFL_NET_SOCKET_SSL_ERROR_CERTIFICATE_VERIFY_FAILED;
|
|
}
|
|
|
|
DBG("ssl_conn=%p %s='%s' matches certificate.", conn, label, conn->hostname);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static Eina_Error
|
|
efl_net_ssl_conn_handshake(Efl_Net_Ssl_Conn *conn, Eina_Bool *done)
|
|
{
|
|
long err_ssl;
|
|
const char *err_file;
|
|
const char *err_data;
|
|
int r, err_line, err_flags;
|
|
|
|
*done = EINA_FALSE;
|
|
|
|
EINA_SAFETY_ON_NULL_RETURN_VAL(conn->ssl, EINVAL);
|
|
|
|
r = SSL_do_handshake(conn->ssl);
|
|
if (r == 1)
|
|
{
|
|
_efl_net_ssl_conn_session_debug(conn);
|
|
|
|
if (conn->hostname_verify)
|
|
{
|
|
Eina_Error err = _efl_net_ssl_conn_hostname_verify(conn);
|
|
if (err)
|
|
return err;
|
|
}
|
|
|
|
*done = EINA_TRUE;
|
|
DBG("ssl_conn=%p handshake finished!", conn);
|
|
return 0;
|
|
}
|
|
|
|
r = SSL_get_error(conn->ssl, r);
|
|
if ((r == SSL_ERROR_WANT_READ) || (r == SSL_ERROR_WANT_WRITE))
|
|
{
|
|
DBG("ssl_conn=%p handshake needs more data...", conn);
|
|
return 0;
|
|
}
|
|
|
|
err_ssl = ERR_peek_error_line_data(&err_file, &err_line, &err_data, &err_flags);
|
|
_efl_net_ssl_conn_check_errors();
|
|
|
|
if (!err_ssl)
|
|
DBG("ssl_conn=%p handshake error=%#x (SSL_ERROR_SSL=%#x)", conn, r, SSL_ERROR_SSL);
|
|
else
|
|
DBG("ssl_conn=%p handshake error=%#x (SSL_ERROR_SSL=%#x) [%s:%d%s%s %#lx='%s']",
|
|
conn, r, SSL_ERROR_SSL,
|
|
err_file, err_line,
|
|
(err_flags & ERR_TXT_STRING) ? " " : "",
|
|
(err_flags & ERR_TXT_STRING) ? err_data : "",
|
|
err_ssl, ERR_reason_error_string(err_ssl));
|
|
if (r == SSL_ERROR_SSL)
|
|
{
|
|
_efl_net_ssl_conn_session_debug(conn);
|
|
|
|
if ((ERR_GET_LIB(err_ssl) == ERR_LIB_SSL) &&
|
|
(ERR_GET_REASON(err_ssl) == SSL_R_CERTIFICATE_VERIFY_FAILED))
|
|
{
|
|
WRN("ssl_conn=%p certificate verification failed, handshake failed", conn);
|
|
return EFL_NET_SOCKET_SSL_ERROR_CERTIFICATE_VERIFY_FAILED;
|
|
}
|
|
WRN("ssl_conn=%p handshake failed: %s", conn, ERR_reason_error_string(err_ssl));
|
|
return EFL_NET_SOCKET_SSL_ERROR_HANDSHAKE;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static Eina_Error
|
|
efl_net_ssl_conn_verify_mode_set(Efl_Net_Ssl_Conn *conn, Efl_Net_Ssl_Verify_Mode verify_mode)
|
|
{
|
|
int ssl_mode;
|
|
|
|
EINA_SAFETY_ON_NULL_RETURN_VAL(conn->ssl, EINVAL);
|
|
|
|
switch (verify_mode)
|
|
{
|
|
case EFL_NET_SSL_VERIFY_MODE_NONE:
|
|
ssl_mode = SSL_VERIFY_NONE;
|
|
break;
|
|
case EFL_NET_SSL_VERIFY_MODE_OPTIONAL:
|
|
ssl_mode = SSL_VERIFY_PEER;
|
|
break;
|
|
case EFL_NET_SSL_VERIFY_MODE_REQUIRED:
|
|
ssl_mode = SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
|
|
break;
|
|
default:
|
|
ERR("unknown verify_mode=%d", verify_mode);
|
|
return EINVAL;
|
|
}
|
|
|
|
SSL_set_verify(conn->ssl, ssl_mode, SSL_get_verify_callback(conn->ssl));
|
|
return 0;
|
|
}
|
|
|
|
static Eina_Error
|
|
efl_net_ssl_conn_hostname_verify_set(Efl_Net_Ssl_Conn *conn, Eina_Bool hostname_verify)
|
|
{
|
|
conn->hostname_verify = hostname_verify;
|
|
return 0;
|
|
}
|
|
|
|
static Eina_Error
|
|
efl_net_ssl_conn_hostname_override_set(Efl_Net_Ssl_Conn *conn, const char *hostname)
|
|
{
|
|
eina_stringshare_replace(&conn->hostname, hostname);
|
|
if (hostname && (!conn->hostname)) return ENOMEM;
|
|
return 0;
|
|
}
|