#include #include #include #include #ifdef HAVE_SYS_SOCKET_H # include #endif #ifdef HAVE_NETINET_IN_H # include #endif #ifdef HAVE_ARPA_INET_H # include #endif #if defined HAVE_DLOPEN && ! defined _WIN32 # include #endif #ifdef _WIN32 # include /* 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 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 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 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 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 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"); 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); // 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__, __FUNCTION__, 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__, __FUNCTION__) 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 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; }