efl_net_socket_ssl: initial SSL wrapper.

This is the first step towards SSL connections on top of sockets, with
an example on how to upgrade a dialer and a server client using TCP.
This commit is contained in:
Gustavo Sverzut Barbieri 2016-10-28 22:48:19 -02:00
parent 9a13816fb3
commit f4198f022a
19 changed files with 3802 additions and 4 deletions

View File

@ -20,6 +20,8 @@ ecore_con_eolian_files = \
lib/ecore_con/efl_net_server_tcp.eo \
lib/ecore_con/efl_net_server_udp.eo \
lib/ecore_con/efl_net_server_udp_client.eo \
lib/ecore_con/efl_net_socket_ssl.eo \
lib/ecore_con/efl_net_ssl_context.eo \
lib/ecore_con/ecore_con_eet_base.eo \
lib/ecore_con/ecore_con_eet_server_obj.eo \
lib/ecore_con/ecore_con_eet_client_obj.eo \
@ -34,7 +36,8 @@ ecore_con_eolian_files += \
endif
ecore_con_eolian_type_files = \
lib/ecore_con/efl_net_http_types.eot
lib/ecore_con/efl_net_http_types.eot \
lib/ecore_con/efl_net_ssl_types.eot
ecore_con_eolian_c = $(ecore_con_eolian_files:%.eo=%.eo.c)
@ -95,10 +98,24 @@ lib/ecore_con/efl_net_server.c \
lib/ecore_con/efl_net_server_fd.c \
lib/ecore_con/efl_net_server_tcp.c \
lib/ecore_con/efl_net_server_udp.c \
lib/ecore_con/efl_net_server_udp_client.c
lib/ecore_con/efl_net_server_udp_client.c \
lib/ecore_con/efl_net_socket_ssl.c \
lib/ecore_con/efl_net_ssl_context.c
EXTRA_DIST2 += lib/ecore_con/ecore_con_legacy.c
# these are included rather than compiled out
# so the structures can be embedded into the
# object Private Data and allows functions to
# be all static
EXTRA_DIST2 += \
lib/ecore_con/efl_net_ssl_conn-openssl.c \
lib/ecore_con/efl_net_ssl_conn-gnutls.c \
lib/ecore_con/efl_net_ssl_conn-none.c \
lib/ecore_con/efl_net_ssl_ctx-openssl.c \
lib/ecore_con/efl_net_ssl_ctx-gnutls.c \
lib/ecore_con/efl_net_ssl_ctx-none.c
if HAVE_WINDOWS
lib_ecore_con_libecore_con_la_SOURCES += lib/ecore_con/ecore_con_local_win32.c
else

View File

@ -57,3 +57,5 @@
/efl_net_dialer_udp_example
/efl_net_dialer_unix_example
/ecore_evas_vnc
/efl_net_socket_ssl_dialer_example
/efl_net_socket_ssl_server_example

View File

@ -85,7 +85,10 @@ efl_net_server_example \
efl_net_dialer_http_example \
efl_net_dialer_websocket_example \
efl_net_dialer_websocket_autobahntestee \
efl_net_dialer_udp_example
efl_net_dialer_udp_example \
efl_net_socket_ssl_dialer_example \
efl_net_socket_ssl_server_example
ECORE_COMMON_LDADD = \
$(top_builddir)/src/lib/ecore/libecore.la \
@ -320,6 +323,12 @@ efl_net_dialer_unix_example_SOURCES = efl_net_dialer_unix_example.c
efl_net_dialer_unix_example_LDADD = $(ECORE_CON_COMMON_LDADD)
endif
efl_net_socket_ssl_dialer_example_SOURCES = efl_net_socket_ssl_dialer_example.c
efl_net_socket_ssl_dialer_example_LDADD = $(ECORE_CON_COMMON_LDADD)
efl_net_socket_ssl_server_example_SOURCES = efl_net_socket_ssl_server_example.c
efl_net_socket_ssl_server_example_LDADD = $(ECORE_CON_COMMON_LDADD)
SRCS = \
ecore_animator_example.c \
ecore_buffer_example.c \
@ -374,7 +383,9 @@ efl_net_server_example.c \
efl_net_dialer_http_example.c \
efl_net_dialer_websocket_example.c \
efl_net_dialer_websocket_autobahntestee.c \
efl_net_dialer_udp_example.c
efl_net_dialer_udp_example.c \
efl_net_socket_ssl_dialer_example.c \
efl_net_socket_ssl_server_example.c
DATA_FILES = red.png Makefile.examples

View File

@ -0,0 +1,512 @@
#define EFL_BETA_API_SUPPORT 1
#define EFL_EO_API_SUPPORT 1
#include <Ecore.h>
#include <Ecore_Con.h>
#include <Ecore_Getopt.h>
#include <fcntl.h>
#include <ctype.h>
static int retval = EXIT_SUCCESS;
static Eina_List *pending_send = NULL;
static size_t pending_send_offset = 0;
static Eo *ssl_ctx;
static void
_ssl_ready(void *data EINA_UNUSED, const Efl_Event *event EINA_UNUSED)
{
fprintf(stderr, "INFO: SSL ready!\n");
}
static void
_ssl_error(void *data EINA_UNUSED, const Efl_Event *event)
{
const Eina_Error *perr = event->info;
fprintf(stderr, "INFO: SSL error: %d '%s'\n", *perr, eina_error_msg_get(*perr));
retval = EXIT_FAILURE;
}
static void
_ssl_can_read(void *data EINA_UNUSED, const Efl_Event *event)
{
char buf[63]; /* INFO: SSL read '...' will fit in 80 columns */
Eina_Error err;
Eina_Bool can_read = efl_io_reader_can_read_get(event->object);
/* NOTE: this message may appear with can read=0 BEFORE
* "read '...'" because efl_io_readr_read() will change the status
* of can_read to FALSE prior to return so we can print it!
*/
fprintf(stderr, "INFO: SSL can read=%d\n", can_read);
if (!can_read) return;
do
{
Eina_Rw_Slice rw_slice;
/* reset on every read, as rw_slice will be modified! */
rw_slice.len = sizeof(buf);
rw_slice.mem = buf;
err = efl_io_reader_read(event->object, &rw_slice);
if (err)
{
if (err == EAGAIN) return;
fprintf(stderr, "ERROR: could not read: %s\n", eina_error_msg_get(err));
retval = EXIT_FAILURE;
ecore_main_loop_quit();
return;
}
fprintf(stderr, "INFO: SSL read '" EINA_SLICE_STR_FMT "'\n", EINA_SLICE_STR_PRINT(rw_slice));
}
while (efl_io_reader_can_read_get(event->object));
}
static void
_ssl_eos(void *data EINA_UNUSED, const Efl_Event *event EINA_UNUSED)
{
fprintf(stderr, "INFO: SSL eos\n");
ecore_main_loop_quit();
}
static void
_ssl_can_write(void *data EINA_UNUSED, const Efl_Event *event)
{
Eina_Bool can_write = efl_io_writer_can_write_get(event->object);
size_t len;
/* NOTE: this message may appear with can write=0 BEFORE
* "wrote '...'" because efl_io_writer_write() will change the status
* of can_write to FALSE prior to return so we can print it!
*/
fprintf(stderr, "INFO: SSL can write=%d (needed writes=%u offset=%zd)\n", can_write, eina_list_count(pending_send), pending_send_offset);
if (!can_write) return;
if (!pending_send) return;
do
{
Eina_Slice slice;
Eina_Error err;
slice.bytes = pending_send->data;
slice.bytes += pending_send_offset;
slice.len = len = strlen(slice.mem);
err = efl_io_writer_write(event->object, &slice, NULL);
if (err)
{
if (err == EAGAIN) return;
fprintf(stderr, "ERROR: could not write: %s\n", eina_error_msg_get(err));
retval = EXIT_FAILURE;
ecore_main_loop_quit();
return;
}
fprintf(stderr, "INFO: SSL wrote '" EINA_SLICE_STR_FMT "'\n", EINA_SLICE_STR_PRINT(slice));
pending_send_offset += slice.len;
if (pending_send_offset == strlen(pending_send->data))
{
free(pending_send->data);
pending_send = eina_list_remove_list(pending_send, pending_send);
pending_send_offset = 0;
}
}
while ((pending_send) && (efl_io_writer_can_write_get(event->object)));
}
static void
_ssl_closed(void *data EINA_UNUSED, const Efl_Event *event)
{
fprintf(stderr, "INFO: SSL closed\n");
efl_del(event->object);
}
EFL_CALLBACKS_ARRAY_DEFINE(ssl_cbs,
{ EFL_NET_SOCKET_SSL_EVENT_SSL_READY, _ssl_ready },
{ EFL_NET_SOCKET_SSL_EVENT_SSL_ERROR, _ssl_error },
{ EFL_IO_READER_EVENT_CAN_READ_CHANGED, _ssl_can_read },
{ EFL_IO_READER_EVENT_EOS, _ssl_eos },
{ EFL_IO_WRITER_EVENT_CAN_WRITE_CHANGED, _ssl_can_write },
{ EFL_IO_CLOSER_EVENT_CLOSED, _ssl_closed });
static void
_connected(void *data EINA_UNUSED, const Efl_Event *event)
{
Eo *ssl;
const char *hostname;
fprintf(stderr,
"INFO: connected to '%s' (%s)\n"
"INFO: - local address=%s\n",
efl_net_dialer_address_dial_get(event->object),
efl_net_socket_address_remote_get(event->object),
efl_net_socket_address_local_get(event->object));
ssl = efl_add(EFL_NET_SOCKET_SSL_CLASS, efl_parent_get(event->object),
efl_net_socket_ssl_adopt(efl_added, event->object, ssl_ctx),
efl_event_callback_array_add(efl_added, ssl_cbs(), NULL));
if (!ssl)
{
fprintf(stderr, "ERROR: failed to wrap dialer=%p in SSL\n", event->object);
retval = EXIT_FAILURE;
ecore_main_loop_quit();
return;
}
hostname = efl_net_socket_ssl_hostname_override_get(ssl);
if (!hostname) hostname = "<none>";
fprintf(stderr,
"INFO: - verify-mode=%d\n"
"INFO: - hostname-verify=%d\n"
"INFO: - hostname-override='%s'\n",
efl_net_socket_ssl_verify_mode_get(ssl),
efl_net_socket_ssl_hostname_verify_get(ssl),
hostname);
}
static void
_resolved(void *data EINA_UNUSED, const Efl_Event *event)
{
fprintf(stderr, "INFO: resolved %s => %s\n",
efl_net_dialer_address_dial_get(event->object),
efl_net_socket_address_remote_get(event->object));
}
static void
_error(void *data EINA_UNUSED, const Efl_Event *event)
{
const Eina_Error *perr = event->info;
fprintf(stderr, "INFO: error: %d '%s'\n", *perr, eina_error_msg_get(*perr));
retval = EXIT_FAILURE;
}
EFL_CALLBACKS_ARRAY_DEFINE(dialer_cbs,
{ EFL_NET_DIALER_EVENT_CONNECTED, _connected },
{ EFL_NET_DIALER_EVENT_RESOLVED, _resolved },
{ EFL_NET_DIALER_EVENT_ERROR, _error });
static char *
_unescape(const char *str)
{
char *ret = strdup(str);
char *c, *w;
Eina_Bool escaped = EINA_FALSE;
for (c = ret, w = ret; *c != '\0'; c++)
{
if (escaped)
{
escaped = EINA_FALSE;
switch (*c)
{
case 'n': *w = '\n'; break;
case 'r': *w = '\r'; break;
case 't': *w = '\t'; break;
default: w++; /* no change */
}
w++;
}
else
{
if (*c == '\\')
escaped = EINA_TRUE;
else
w++;
}
}
*w = '\0';
return ret;
}
/*
* Define USE_DEFAULT_CONTEXT will remove all context-setup functions
* and use a default context for dialers, what most applications
* should use.
*/
//#define USE_DEFAULT_CONTEXT 1
#ifndef USE_DEFAULT_CONTEXT
static const char *verify_mode_strs[] = {
"none",
"optional",
"required",
NULL
};
static const char *ciphers_strs[] = {
"auto",
"sslv3",
"tlsv1",
"tlsv1.1",
"tlsv1.2",
NULL
};
#endif
static const Ecore_Getopt options = {
"efl_net_socket_ssl_dialer_example", /* program name */
NULL, /* usage line */
"1", /* version */
"(C) 2016 Enlightenment Project", /* copyright */
"BSD 2-Clause", /* license */
/* long description, may be multiline and contain \n */
"Example of 'upgrading' a regular Efl.Net.Dialer.Tcp to a SSL socket.",
EINA_FALSE,
{
ECORE_GETOPT_STORE_STR('d', "line-delimiter",
"Changes the line delimiter to be used in both send and receive. Defaults to \\r\\n"),
ECORE_GETOPT_APPEND('s', "send", "send the given string to the server once connected.", ECORE_GETOPT_TYPE_STR),
#ifndef USE_DEFAULT_CONTEXT
ECORE_GETOPT_CHOICE('c', "cipher", "Cipher to use, defaults to 'auto'", ciphers_strs),
ECORE_GETOPT_APPEND(0, "certificate", "certificate path to use.", ECORE_GETOPT_TYPE_STR),
ECORE_GETOPT_APPEND(0, "private-key", "private key path to use.", ECORE_GETOPT_TYPE_STR),
ECORE_GETOPT_APPEND(0, "crl", "certificate revogation list to use.", ECORE_GETOPT_TYPE_STR),
ECORE_GETOPT_APPEND(0, "ca", "certificate authorities path to use.", ECORE_GETOPT_TYPE_STR),
ECORE_GETOPT_STORE_FALSE(0, "no-default-paths", "Do not use default certificate paths from your system."),
ECORE_GETOPT_CHOICE(0, "verify-mode", "One of: none (do not verify), optional (verify if provided), required (require and verify). Defaults to required", verify_mode_strs),
ECORE_GETOPT_STORE_FALSE(0, "no-hostname-verify", "Do not Verify hostname"),
ECORE_GETOPT_STORE_STR(0, "hostname-override", "Use this hostname instead of server provided one"),
#endif
ECORE_GETOPT_VERSION('V', "version"),
ECORE_GETOPT_COPYRIGHT('C', "copyright"),
ECORE_GETOPT_LICENSE('L', "license"),
ECORE_GETOPT_HELP('h', "help"),
ECORE_GETOPT_STORE_METAVAR_STR(0, NULL,
"The address (URL) to dial", "address"),
ECORE_GETOPT_SENTINEL
}
};
int
main(int argc, char **argv)
{
char *address = NULL;
char *line_delimiter_str = NULL;
char *str;
Eina_List *to_send = NULL;
#ifndef USE_DEFAULT_CONTEXT
Eina_Iterator *it;
char *verify_mode_choice = "required";
char *cipher_choice = "auto";
Eina_List *certificates = NULL;
Eina_List *private_keys = NULL;
Eina_List *crls = NULL;
Eina_List *cas = NULL;
Eina_Bool default_paths_load = EINA_TRUE;
Efl_Net_Ssl_Verify_Mode verify_mode = EFL_NET_SSL_VERIFY_MODE_OPTIONAL;
Efl_Net_Ssl_Cipher cipher = EFL_NET_SSL_CIPHER_AUTO;
Eina_Bool hostname_verify = EINA_TRUE;
char *hostname_override = NULL;
#endif
Eina_Bool quit_option = EINA_FALSE;
double timeout_dial = 30.0;
Ecore_Getopt_Value values[] = {
ECORE_GETOPT_VALUE_STR(line_delimiter_str),
ECORE_GETOPT_VALUE_LIST(to_send),
#ifndef USE_DEFAULT_CONTEXT
ECORE_GETOPT_VALUE_STR(cipher_choice),
ECORE_GETOPT_VALUE_LIST(certificates),
ECORE_GETOPT_VALUE_LIST(private_keys),
ECORE_GETOPT_VALUE_LIST(crls),
ECORE_GETOPT_VALUE_LIST(cas),
ECORE_GETOPT_VALUE_BOOL(default_paths_load),
ECORE_GETOPT_VALUE_STR(verify_mode_choice),
ECORE_GETOPT_VALUE_BOOL(hostname_verify),
ECORE_GETOPT_VALUE_STR(hostname_override),
#endif
/* standard block to provide version, copyright, license and help */
ECORE_GETOPT_VALUE_BOOL(quit_option), /* -V/--version quits */
ECORE_GETOPT_VALUE_BOOL(quit_option), /* -C/--copyright quits */
ECORE_GETOPT_VALUE_BOOL(quit_option), /* -L/--license quits */
ECORE_GETOPT_VALUE_BOOL(quit_option), /* -h/--help quits */
/* positional argument */
ECORE_GETOPT_VALUE_STR(address),
ECORE_GETOPT_VALUE_NONE /* sentinel */
};
int args;
Eo *dialer, *loop;
Eina_Error err;
ecore_init();
ecore_con_init();
args = ecore_getopt_parse(&options, values, argc, argv);
if (args < 0)
{
fputs("ERROR: Could not parse command line options.\n", stderr);
retval = EXIT_FAILURE;
goto end;
}
if (quit_option) goto end;
loop = ecore_main_loop_get();
args = ecore_getopt_parse_positional(&options, values, argc, argv, args);
if (args < 0)
{
fputs("ERROR: Could not parse positional arguments.\n", stderr);
retval = EXIT_FAILURE;
goto end;
}
#ifndef USE_DEFAULT_CONTEXT
if (verify_mode_choice)
{
if (strcmp(verify_mode_choice, "none") == 0)
verify_mode = EFL_NET_SSL_VERIFY_MODE_NONE;
else if (strcmp(verify_mode_choice, "optional") == 0)
verify_mode = EFL_NET_SSL_VERIFY_MODE_OPTIONAL;
else if (strcmp(verify_mode_choice, "required") == 0)
verify_mode = EFL_NET_SSL_VERIFY_MODE_REQUIRED;
}
if (cipher_choice)
{
if (strcmp(cipher_choice, "auto") == 0)
cipher = EFL_NET_SSL_CIPHER_AUTO;
else if (strcmp(cipher_choice, "sslv3") == 0)
cipher = EFL_NET_SSL_CIPHER_SSLV3;
else if (strcmp(cipher_choice, "tlsv1") == 0)
cipher = EFL_NET_SSL_CIPHER_TLSV1;
else if (strcmp(cipher_choice, "tlsv1.1") == 0)
cipher = EFL_NET_SSL_CIPHER_TLSV1_1;
else if (strcmp(cipher_choice, "tlsv1.2") == 0)
cipher = EFL_NET_SSL_CIPHER_TLSV1_2;
}
#endif
if (to_send)
{
line_delimiter_str = _unescape(line_delimiter_str ? line_delimiter_str : "\\r\\n");
if (line_delimiter_str[0] == '\0')
{
pending_send = to_send;
to_send = NULL;
}
else
{
EINA_LIST_FREE(to_send, str)
{
/* ignore empty sends, but add line delimiter, so we can do HTTP's last line :-) */
if (str[0] == '\0')
free(str);
else
pending_send = eina_list_append(pending_send, str);
if (line_delimiter_str[0])
pending_send = eina_list_append(pending_send, strdup(line_delimiter_str));
}
}
free(line_delimiter_str);
line_delimiter_str = NULL;
}
/* create a new SSL context with command line configurations.
* another option would be to use the default dialer context */
#ifndef USE_DEFAULT_CONTEXT
ssl_ctx = efl_add(EFL_NET_SSL_CONTEXT_CLASS, NULL,
efl_net_ssl_context_certificates_set(efl_added, eina_list_iterator_new(certificates)),
efl_net_ssl_context_private_keys_set(efl_added, eina_list_iterator_new(private_keys)),
efl_net_ssl_context_certificate_revogation_lists_set(efl_added, eina_list_iterator_new(crls)),
efl_net_ssl_context_certificate_authorities_set(efl_added, eina_list_iterator_new(cas)),
efl_net_ssl_context_default_paths_load_set(efl_added, default_paths_load),
efl_net_ssl_context_verify_mode_set(efl_added, verify_mode),
efl_net_ssl_context_hostname_verify_set(efl_added, hostname_verify),
efl_net_ssl_context_hostname_set(efl_added, hostname_override),
efl_net_ssl_context_setup(efl_added, cipher, EINA_TRUE));
#else
ssl_ctx = efl_net_ssl_context_default_dialer_get(EFL_NET_SSL_CONTEXT_CLASS);
fprintf(stderr, "INFO: using default context for dialers.\n");
#endif
if (!ssl_ctx)
{
fprintf(stderr, "ERROR: could not create the SSL context!\n");
retval = EXIT_FAILURE;
goto no_ssl_ctx;
}
/* no point in printing default context, it's all empty */
#ifndef USE_DEFAULT_CONTEXT
fprintf(stderr, "INFO: - certificates in use:\n");
it = efl_net_ssl_context_certificates_get(ssl_ctx);
EINA_ITERATOR_FOREACH(it, str)
fprintf(stderr, "INFO: * %s\n", str);
eina_iterator_free(it);
fprintf(stderr, "INFO: - private keys in use:\n");
it = efl_net_ssl_context_private_keys_get(ssl_ctx);
EINA_ITERATOR_FOREACH(it, str)
fprintf(stderr, "INFO: * %s\n", str);
eina_iterator_free(it);
fprintf(stderr, "INFO: - certificate revogation lists in use:\n");
it = efl_net_ssl_context_certificate_revogation_lists_get(ssl_ctx);
EINA_ITERATOR_FOREACH(it, str)
fprintf(stderr, "INFO: * %s\n", str);
eina_iterator_free(it);
fprintf(stderr, "INFO: - certificate authorities in use:\n");
it = efl_net_ssl_context_certificate_authorities_get(ssl_ctx);
EINA_ITERATOR_FOREACH(it, str)
fprintf(stderr, "INFO: * %s\n", str);
eina_iterator_free(it);
#endif
fprintf(stderr,
"INFO: - verify-mode=%d\n",
efl_net_ssl_context_verify_mode_get(ssl_ctx));
dialer = efl_add(EFL_NET_DIALER_TCP_CLASS, loop,
efl_name_set(efl_added, "dialer"),
efl_net_dialer_timeout_dial_set(efl_added, timeout_dial),
efl_event_callback_array_add(efl_added, dialer_cbs(), NULL));
err = efl_net_dialer_dial(dialer, address);
if (err != 0)
{
fprintf(stderr, "ERROR: could not dial '%s': %s",
address, eina_error_msg_get(err));
retval = EXIT_FAILURE;
goto no_mainloop;
}
ecore_main_loop_begin();
fprintf(stderr, "INFO: main loop finished.\n");
no_mainloop:
efl_io_closer_close(dialer); /* just del won't do as ssl has an extra ref */
efl_del(dialer);
no_ssl_ctx:
efl_del(ssl_ctx);
end:
EINA_LIST_FREE(pending_send, str) free(str);
#ifndef USE_DEFAULT_CONTEXT
EINA_LIST_FREE(certificates, str) free(str);
EINA_LIST_FREE(private_keys, str) free(str);
EINA_LIST_FREE(crls, str) free(str);
EINA_LIST_FREE(cas, str) free(str);
#endif
ecore_con_shutdown();
ecore_shutdown();
return retval;
}

View File

@ -0,0 +1,351 @@
#define EFL_BETA_API_SUPPORT 1
#define EFL_EO_API_SUPPORT 1
#include <Ecore.h>
#include <Ecore_Con.h>
#include <Ecore_Getopt.h>
#include <fcntl.h>
static int retval = EXIT_SUCCESS;
static double timeout = 30.0;
static Eo *ssl_ctx = NULL;
/* NOTE: client i/o events are only used as debug, you can omit these */
static void
_ssl_can_read_changed(void *data EINA_UNUSED, const Efl_Event *event)
{
fprintf(stderr, "INFO: ssl %s can_read=%d\n",
efl_net_socket_address_remote_get(event->object),
efl_io_reader_can_read_get(event->object));
}
static void
_ssl_can_write_changed(void *data EINA_UNUSED, const Efl_Event *event)
{
fprintf(stderr, "INFO: ssl %s can_write=%d\n",
efl_net_socket_address_remote_get(event->object),
efl_io_writer_can_write_get(event->object));
}
static void
_ssl_eos(void *data EINA_UNUSED, const Efl_Event *event)
{
fprintf(stderr, "INFO: ssl %s eos.\n",
efl_net_socket_address_remote_get(event->object));
}
static void
_ssl_closed(void *data EINA_UNUSED, const Efl_Event *event)
{
fprintf(stderr, "INFO: ssl %s closed.\n",
efl_net_socket_address_remote_get(event->object));
}
EFL_CALLBACKS_ARRAY_DEFINE(ssl_cbs,
{ EFL_IO_READER_EVENT_CAN_READ_CHANGED, _ssl_can_read_changed },
{ EFL_IO_READER_EVENT_EOS, _ssl_eos },
{ EFL_IO_WRITER_EVENT_CAN_WRITE_CHANGED, _ssl_can_write_changed },
{ EFL_IO_CLOSER_EVENT_CLOSED, _ssl_closed });
/* copier events are of interest, you should hook to at least "done"
* and "error"
*/
/* echo copier is about the same socket, you can close it right away */
static void
_echo_copier_done(void *data EINA_UNUSED, const Efl_Event *event)
{
Eo *copier = event->object;
fprintf(stderr, "INFO: echo copier done, close and del %p\n", copier);
efl_del(copier); /* set to close_on_destructor, will auto close copier and ssl */
}
static void
_echo_copier_error(void *data EINA_UNUSED, const Efl_Event *event)
{
Eo *copier = event->object;
const Eina_Error *perr = event->info;
if (*perr == ETIMEDOUT)
{
Eo *ssl = efl_io_copier_source_get(copier);
fprintf(stderr, "INFO: ssl '%s' timed out, delete it.\n",
efl_net_socket_address_remote_get(ssl));
efl_del(copier);
return;
}
retval = EXIT_FAILURE;
fprintf(stderr, "ERROR: echo copier %p failed %d '%s', close and del.\n",
copier, *perr, eina_error_msg_get(*perr));
efl_del(copier);
}
EFL_CALLBACKS_ARRAY_DEFINE(echo_copier_cbs,
{ EFL_IO_COPIER_EVENT_DONE, _echo_copier_done },
{ EFL_IO_COPIER_EVENT_ERROR, _echo_copier_error});
/* server events are mandatory, afterall you need to define what's
* going to happen after a client socket is connected. This is the
* "client,add" event.
*/
static void
_server_client_add(void *data EINA_UNUSED, const Efl_Event *event)
{
Efl_Net_Socket *client = event->info;
Efl_Net_Socket_Ssl *ssl;
Eo *echo_copier;
fprintf(stderr, "INFO: accepted client %s\n",
efl_net_socket_address_remote_get(client));
/* to use a client, you must efl_ref() it. Here we're not doing it
* explicitly because Efl.Net.Socket.Ssl do take a reference.
*/
ssl = efl_add(EFL_NET_SOCKET_SSL_CLASS, efl_loop_get(client),
efl_net_socket_ssl_adopt(efl_added, client, ssl_ctx), /* mandatory inside efl_add() */
efl_event_callback_array_add(efl_added, ssl_cbs(), NULL) /* optional, for debug purposes */
);
if (!ssl)
{
fprintf(stderr, "ERROR: failed to wrap client=%p in SSL\n", client);
retval = EXIT_FAILURE;
ecore_main_loop_quit();
return;
}
/*
* SSL will do a handshake and keep can_read/can_write as false
* until it's finished, thus we can create the echo copier right
* away.
*
* remember to NEVER add a copier, read or write from wrapped
* socket, doing that will bypass the SSL layer and thus result in
* incorrect operation. You can forget about it
*/
echo_copier = efl_add(EFL_IO_COPIER_CLASS, efl_parent_get(ssl),
efl_io_copier_source_set(efl_added, ssl),
efl_io_copier_destination_set(efl_added, ssl),
efl_io_copier_inactivity_timeout_set(efl_added, timeout),
efl_event_callback_array_add(efl_added, echo_copier_cbs(), ssl),
efl_io_closer_close_on_destructor_set(efl_added, EINA_TRUE) /* we want to auto-close as we have a single copier */
);
fprintf(stderr, "INFO: using an echo copier=%p for ssl %s\n",
echo_copier, efl_net_socket_address_remote_get(ssl));
}
static void
_server_error(void *data EINA_UNUSED, const Efl_Event *event)
{
const Eina_Error *perr = event->info;
fprintf(stderr, "ERROR: %d '%s'\n", *perr, eina_error_msg_get(*perr));
retval = EXIT_FAILURE;
ecore_main_loop_quit();
}
static void
_server_serving(void *data EINA_UNUSED, const Efl_Event *event)
{
fprintf(stderr, "INFO: serving at %s\n",
efl_net_server_address_get(event->object));
}
EFL_CALLBACKS_ARRAY_DEFINE(server_cbs,
{ EFL_NET_SERVER_EVENT_CLIENT_ADD, _server_client_add },
{ EFL_NET_SERVER_EVENT_ERROR, _server_error },
{ EFL_NET_SERVER_EVENT_SERVING, _server_serving });
static const char *ciphers_strs[] = {
"auto",
"sslv3",
"tlsv1",
"tlsv1.1",
"tlsv1.2",
NULL
};
static const Ecore_Getopt options = {
"efl_net_socket_ssl_server_example", /* program name */
NULL, /* usage line */
"1", /* version */
"(C) 2016 Enlightenment Project", /* copyright */
"BSD 2-Clause", /* license */
/* long description, may be multiline and contain \n */
"Example of 'upgrading' a regular Efl.Net.Socket received from an Efl.Net.Server.Tcp to a SSL socket, then serving as 'echo' server.",
EINA_FALSE,
{
ECORE_GETOPT_CHOICE('c', "cipher", "Cipher to use, defaults to 'auto'", ciphers_strs),
ECORE_GETOPT_APPEND(0, "certificate", "certificate path to use.", ECORE_GETOPT_TYPE_STR),
ECORE_GETOPT_APPEND(0, "private-key", "private key path to use.", ECORE_GETOPT_TYPE_STR),
ECORE_GETOPT_APPEND(0, "crl", "certificate revogation list to use.", ECORE_GETOPT_TYPE_STR),
ECORE_GETOPT_APPEND(0, "ca", "certificate authorities path to use.", ECORE_GETOPT_TYPE_STR),
ECORE_GETOPT_VERSION('V', "version"),
ECORE_GETOPT_COPYRIGHT('C', "copyright"),
ECORE_GETOPT_LICENSE('L', "license"),
ECORE_GETOPT_HELP('h', "help"),
ECORE_GETOPT_STORE_METAVAR_STR(0, NULL,
"The server address to listen, such as "
"IPv4:PORT, [IPv6]:PORT, Unix socket path...",
"address"),
ECORE_GETOPT_SENTINEL
}
};
int
main(int argc, char **argv)
{
char *address = NULL;
char *cipher_choice = "auto";
char *str;
Eina_List *certificates = NULL;
Eina_List *private_keys = NULL;
Eina_List *crls = NULL;
Eina_List *cas = NULL;
Efl_Net_Ssl_Cipher cipher = EFL_NET_SSL_CIPHER_AUTO;
Eina_Bool quit_option = EINA_FALSE;
Ecore_Getopt_Value values[] = {
ECORE_GETOPT_VALUE_STR(cipher_choice),
ECORE_GETOPT_VALUE_LIST(certificates),
ECORE_GETOPT_VALUE_LIST(private_keys),
ECORE_GETOPT_VALUE_LIST(crls),
ECORE_GETOPT_VALUE_LIST(cas),
/* standard block to provide version, copyright, license and help */
ECORE_GETOPT_VALUE_BOOL(quit_option), /* -V/--version quits */
ECORE_GETOPT_VALUE_BOOL(quit_option), /* -C/--copyright quits */
ECORE_GETOPT_VALUE_BOOL(quit_option), /* -L/--license quits */
ECORE_GETOPT_VALUE_BOOL(quit_option), /* -h/--help quits */
/* positional argument */
ECORE_GETOPT_VALUE_STR(address),
ECORE_GETOPT_VALUE_NONE /* sentinel */
};
int args;
Eina_Iterator *it;
Eo *server;
Eina_Error err;
ecore_init();
ecore_con_init();
args = ecore_getopt_parse(&options, values, argc, argv);
if (args < 0)
{
fputs("ERROR: Could not parse command line options.\n", stderr);
retval = EXIT_FAILURE;
goto end;
}
if (quit_option) goto end;
args = ecore_getopt_parse_positional(&options, values, argc, argv, args);
if (args < 0)
{
fputs("ERROR: Could not parse positional arguments.\n", stderr);
retval = EXIT_FAILURE;
goto end;
}
if (cipher_choice)
{
if (strcmp(cipher_choice, "auto") == 0)
cipher = EFL_NET_SSL_CIPHER_AUTO;
else if (strcmp(cipher_choice, "sslv3") == 0)
cipher = EFL_NET_SSL_CIPHER_SSLV3;
else if (strcmp(cipher_choice, "tlsv1") == 0)
cipher = EFL_NET_SSL_CIPHER_TLSV1;
else if (strcmp(cipher_choice, "tlsv1.1") == 0)
cipher = EFL_NET_SSL_CIPHER_TLSV1_1;
else if (strcmp(cipher_choice, "tlsv1.2") == 0)
cipher = EFL_NET_SSL_CIPHER_TLSV1_2;
}
ssl_ctx = efl_add(EFL_NET_SSL_CONTEXT_CLASS, NULL,
efl_net_ssl_context_certificates_set(efl_added, eina_list_iterator_new(certificates)),
efl_net_ssl_context_private_keys_set(efl_added, eina_list_iterator_new(private_keys)),
efl_net_ssl_context_certificate_revogation_lists_set(efl_added, eina_list_iterator_new(crls)),
efl_net_ssl_context_certificate_authorities_set(efl_added, eina_list_iterator_new(cas)),
efl_net_ssl_context_setup(efl_added, cipher, EINA_FALSE /* a server! */));
if (!ssl_ctx)
{
fprintf(stderr, "ERROR: could not create the SSL context!\n");
retval = EXIT_FAILURE;
goto end;
}
fprintf(stderr, "INFO: - certificates in use:\n");
it = efl_net_ssl_context_certificates_get(ssl_ctx);
EINA_ITERATOR_FOREACH(it, str)
fprintf(stderr, "INFO: * %s\n", str);
eina_iterator_free(it);
fprintf(stderr, "INFO: - private keys in use:\n");
it = efl_net_ssl_context_private_keys_get(ssl_ctx);
EINA_ITERATOR_FOREACH(it, str)
fprintf(stderr, "INFO: * %s\n", str);
eina_iterator_free(it);
fprintf(stderr, "INFO: - certificate revogation lists in use:\n");
it = efl_net_ssl_context_certificate_revogation_lists_get(ssl_ctx);
EINA_ITERATOR_FOREACH(it, str)
fprintf(stderr, "INFO: * %s\n", str);
eina_iterator_free(it);
fprintf(stderr, "INFO: - certificate authorities in use:\n");
it = efl_net_ssl_context_certificate_authorities_get(ssl_ctx);
EINA_ITERATOR_FOREACH(it, str)
fprintf(stderr, "INFO: * %s\n", str);
eina_iterator_free(it);
server = efl_add(EFL_NET_SERVER_TCP_CLASS, ecore_main_loop_get(), /* it's mandatory to use a main loop provider as the server parent */
efl_net_server_tcp_ipv6_only_set(efl_added, EINA_FALSE), /* optional, but helps testing IPv4 on IPv6 servers */
efl_net_server_fd_close_on_exec_set(efl_added, EINA_TRUE), /* recommended */
efl_net_server_fd_reuse_address_set(efl_added, EINA_TRUE), /* optional, but nice for testing */
efl_net_server_fd_reuse_port_set(efl_added, EINA_TRUE), /* optional, but nice for testing... not secure unless you know what you're doing */
efl_event_callback_array_add(efl_added, server_cbs(), NULL)); /* mandatory to have "client,add" in order to be useful */
if (!server)
{
fprintf(stderr, "ERROR: could not create class Efl.Net.Server.Tcp\n");
goto end_ctx;
}
err = efl_net_server_serve(server, address);
if (err)
{
fprintf(stderr, "ERROR: could not serve(%s): %s\n",
address, eina_error_msg_get(err));
goto end_server;
}
ecore_main_loop_begin();
end_server:
efl_del(server);
server = NULL;
end_ctx:
efl_del(ssl_ctx);
end:
EINA_LIST_FREE(certificates, str) free(str);
EINA_LIST_FREE(private_keys, str) free(str);
EINA_LIST_FREE(crls, str) free(str);
EINA_LIST_FREE(cas, str) free(str);
ecore_con_shutdown();
ecore_shutdown();
return retval;
}

View File

@ -31,3 +31,9 @@
#include "efl_net_dialer_http.eo.h"
#include "efl_net_dialer_websocket.eo.h"
#include "efl_net_ssl_types.eot.h"
#include "efl_net_ssl_context.eo.h"
#include "efl_net_socket_ssl.eo.h"

View File

@ -184,6 +184,9 @@ EWAPI Eina_Error EFL_NET_DIALER_ERROR_PROXY_AUTHENTICATION_FAILED = 0;
EWAPI Eina_Error EFL_NET_SERVER_ERROR_COULDNT_RESOLVE_HOST = 0;
EWAPI Eina_Error EFL_NET_SOCKET_SSL_ERROR_HANDSHAKE = 0;
EWAPI Eina_Error EFL_NET_SOCKET_SSL_ERROR_CERTIFICATE_VERIFY_FAILED = 0;
static Eina_List *servers = NULL;
static int _ecore_con_init_count = 0;
static int _ecore_con_event_count = 0;
@ -241,6 +244,9 @@ ecore_con_init(void)
EFL_NET_SERVER_ERROR_COULDNT_RESOLVE_HOST = eina_error_msg_static_register("Couldn't resolve host name");
EFL_NET_SOCKET_SSL_ERROR_HANDSHAKE = eina_error_msg_static_register("Failed SSL handshake");
EFL_NET_SOCKET_SSL_ERROR_CERTIFICATE_VERIFY_FAILED = eina_error_msg_static_register("Failed to verify peer's certificate");
eina_magic_string_set(ECORE_MAGIC_CON_SERVER, "Ecore_Con_Server");
eina_magic_string_set(ECORE_MAGIC_CON_CLIENT, "Ecore_Con_Client");
eina_magic_string_set(ECORE_MAGIC_CON_URL, "Ecore_Con_Url");

View File

@ -674,4 +674,8 @@ Eina_Error efl_net_multicast_loopback_get(SOCKET fd, int family, Eina_Bool *loop
*/
size_t efl_net_udp_datagram_size_query(SOCKET fd);
/* SSL abstraction API */
extern void *efl_net_ssl_context_connection_new(Efl_Net_Ssl_Context *context);
#endif

View File

@ -0,0 +1,611 @@
#define EFL_NET_SOCKET_SSL_PROTECTED 1
#define EFL_IO_READER_PROTECTED 1
#define EFL_IO_WRITER_PROTECTED 1
#define EFL_IO_CLOSER_PROTECTED 1
#define EFL_NET_SOCKET_PROTECTED 1
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "Ecore.h"
#include "Ecore_Con.h"
#include "ecore_con_private.h"
typedef struct _Efl_Net_Ssl_Conn Efl_Net_Ssl_Conn;
/**
* Setups the SSL context
*
* Update the given lists, removing invalid entries. If all entries
* failed in a list, return EINVAL.
*
* @internal
*/
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);
/**
* Cleans up the SSL associated to this context.
* @internal
*/
static void efl_net_ssl_conn_teardown(Efl_Net_Ssl_Conn *conn);
/**
* Send data to remote peer.
*
* This should be called once handshake is finished, otherwise it may
* lead the handshake to fail.
*
* @param slice[inout] takes the amount of bytes to write and
* source memory, will store written length in slice->len.
*
* @internal
*/
static Eina_Error efl_net_ssl_conn_write(Efl_Net_Ssl_Conn *conn, Eina_Slice *slice);
/**
* Receive data from remote peer.
*
* This should be called once handshake is finished, otherwise it may
* lead the handshake to fail.
*
* Note that even if the socket 'can_read', eventually it couldn't
* decipher a byte and it will return slice->len == 0 with EAGAIN as
* error.
*
* @param slice[inout] takes the amount of bytes to read and
* destination memory, will store read length in slice->len.
*
* @internal
*/
static Eina_Error efl_net_ssl_conn_read(Efl_Net_Ssl_Conn *conn, Eina_Rw_Slice *slice);
/**
* Attempt to finish the handshake.
*
* This should not block, if it's not finished yet, just set done =
* false.
*
* Errors, such as failed handshake, should be returned as Eina_Error.
*
* @internal
*/
static Eina_Error efl_net_ssl_conn_handshake(Efl_Net_Ssl_Conn *conn, Eina_Bool *done);
/**
* Configure how to verify peer.
*
* @internal
*/
static Eina_Error efl_net_ssl_conn_verify_mode_set(Efl_Net_Ssl_Conn *conn, Efl_Net_Ssl_Verify_Mode verify_mode);
/**
* Configure whenever to check for hostname.
*
* @internal
*/
static Eina_Error efl_net_ssl_conn_hostname_verify_set(Efl_Net_Ssl_Conn *conn, Eina_Bool hostname_verify);
/**
* Overrides the hostname to use.
*
* @note duplicate hostname if needed!
*
* @internal
*/
static Eina_Error efl_net_ssl_conn_hostname_override_set(Efl_Net_Ssl_Conn *conn, const char *hostname);
#if HAVE_OPENSSL
#include "efl_net_ssl_conn-openssl.c"
#elif HAVE_GNUTLS
#include "efl_net_ssl_conn-gnutls.c"
#else
#include "efl_net_ssl_conn-none.c"
#endif
#define MY_CLASS EFL_NET_SOCKET_SSL_CLASS
typedef struct _Efl_Net_Socket_Ssl_Data
{
Eo *sock;
Efl_Net_Ssl_Context *context;
const char *hostname_override;
Efl_Net_Ssl_Conn ssl_conn;
Efl_Net_Ssl_Verify_Mode verify_mode;
Eina_Bool hostname_verify;
Eina_Bool did_handshake;
Eina_Bool can_read;
Eina_Bool eos;
Eina_Bool can_write;
} Efl_Net_Socket_Ssl_Data;
static void
efl_net_socket_ssl_sock_eos(void *data, const Efl_Event *event EINA_UNUSED)
{
Eo *o = data;
efl_io_reader_eos_set(o, EINA_TRUE);
}
static void
efl_net_socket_ssl_handshake_try(Eo *o, Efl_Net_Socket_Ssl_Data *pd)
{
Eina_Error err;
DBG("SSL=%p handshake...", o);
err = efl_net_ssl_conn_handshake(&pd->ssl_conn, &pd->did_handshake);
if (err)
{
WRN("SSL=%p failed handshake: %s", o, eina_error_msg_get(err));
efl_event_callback_call(o, EFL_NET_SOCKET_SSL_EVENT_SSL_ERROR, &err);
efl_io_closer_close(o);
return;
}
if (!pd->did_handshake) return;
DBG("SSL=%p finished handshake", o);
efl_io_reader_can_read_set(o, efl_io_reader_can_read_get(pd->sock));
efl_io_writer_can_write_set(o, efl_io_writer_can_write_get(pd->sock));
efl_event_callback_call(o, EFL_NET_SOCKET_SSL_EVENT_SSL_READY, NULL);
}
static void
efl_net_socket_ssl_sock_can_read_changed(void *data, const Efl_Event *event EINA_UNUSED)
{
Eo *o = data;
Efl_Net_Socket_Ssl_Data *pd = efl_data_scope_get(o, MY_CLASS);
efl_ref(o); /* we're emitting callbacks then continuing the workflow */
if (!efl_io_reader_can_read_get(pd->sock))
{
// TODO: stop jobs?
goto end;
}
if (pd->did_handshake)
efl_io_reader_can_read_set(o, EINA_TRUE);
else
efl_net_socket_ssl_handshake_try(o, pd);
end:
efl_unref(o);
}
static void
efl_net_socket_ssl_sock_can_write_changed(void *data, const Efl_Event *event EINA_UNUSED)
{
Eo *o = data;
Efl_Net_Socket_Ssl_Data *pd = efl_data_scope_get(o, MY_CLASS);
efl_ref(o); /* we're emitting callbacks then continuing the workflow */
if (!efl_io_writer_can_write_get(pd->sock))
{
// TODO: stop jobs?
goto end;
}
if (pd->did_handshake)
efl_io_writer_can_write_set(o, EINA_TRUE);
else
efl_net_socket_ssl_handshake_try(o, pd);
end:
efl_unref(o);
}
static void
efl_net_socket_ssl_sock_closed(void *data, const Efl_Event *event EINA_UNUSED)
{
Eo *o = data;
efl_event_callback_call(o, EFL_IO_CLOSER_EVENT_CLOSED, NULL);
}
static void
efl_net_socket_ssl_sock_del(void *data, const Efl_Event *event EINA_UNUSED)
{
Eo *o = data;
Efl_Net_Socket_Ssl_Data *pd = efl_data_scope_get(o, MY_CLASS);
pd->sock = NULL;
efl_net_ssl_conn_teardown(&pd->ssl_conn);
}
EFL_CALLBACKS_ARRAY_DEFINE(efl_net_socket_ssl_sock_cbs,
{EFL_IO_READER_EVENT_EOS, efl_net_socket_ssl_sock_eos},
{EFL_IO_READER_EVENT_CAN_READ_CHANGED, efl_net_socket_ssl_sock_can_read_changed},
{EFL_IO_WRITER_EVENT_CAN_WRITE_CHANGED, efl_net_socket_ssl_sock_can_write_changed},
{EFL_IO_CLOSER_EVENT_CLOSED, efl_net_socket_ssl_sock_closed},
{EFL_EVENT_DEL, efl_net_socket_ssl_sock_del});
static void
efl_net_socket_ssl_sock_connected(void *data, const Efl_Event *event EINA_UNUSED)
{
Eo *o = data;
Efl_Net_Socket_Ssl_Data *pd = efl_data_scope_get(o, MY_CLASS);
Eina_Error err;
efl_ref(o); /* we're emitting callbacks then continuing the workflow */
err = efl_net_ssl_conn_handshake(&pd->ssl_conn, &pd->did_handshake);
if (err)
{
WRN("SSL=%p failed handshake: %s", o, eina_error_msg_get(err));
efl_io_closer_close(o);
return;
}
efl_unref(o);
}
static void
_efl_net_socket_ssl_context_del(void *data, const Efl_Event *event EINA_UNUSED)
{
Eo *o = data;
Efl_Net_Socket_Ssl_Data *pd = efl_data_scope_get(o, MY_CLASS);
pd->context = NULL;
}
EOLIAN static void
_efl_net_socket_ssl_adopt(Eo *o, Efl_Net_Socket_Ssl_Data *pd, Efl_Net_Socket *sock, Efl_Net_Ssl_Context *context)
{
Eina_Error err;
char *tmp = NULL;
const char *hostname;
Eina_Bool is_dialer;
EINA_SAFETY_ON_TRUE_RETURN(pd->sock != NULL);
EINA_SAFETY_ON_FALSE_RETURN(efl_isa(sock, EFL_NET_SOCKET_INTERFACE));
EINA_SAFETY_ON_FALSE_RETURN(efl_isa(context, EFL_NET_SSL_CONTEXT_CLASS));
EINA_SAFETY_ON_TRUE_RETURN(efl_finalized_get(o));
is_dialer = efl_isa(sock, EFL_NET_DIALER_INTERFACE);
err = efl_net_ssl_conn_setup(&pd->ssl_conn, is_dialer, sock, context);
if (err)
{
ERR("ssl=%p failed to adopt socket (is_dialer=%d) sock=%p", o, is_dialer, sock);
return;
}
pd->context = efl_ref(context);
efl_event_callback_add(context, EFL_EVENT_DEL, _efl_net_socket_ssl_context_del, o);
DBG("ssl=%p adopted socket (is_dialer=%d) sock=%p, ssl_conn=%p", o, is_dialer, sock, &pd->ssl_conn);
if (pd->hostname_verify == 0xff)
pd->hostname_verify = efl_net_ssl_context_hostname_verify_get(context);
hostname = pd->hostname_override;
if (!hostname)
hostname = pd->hostname_override = eina_stringshare_ref(efl_net_ssl_context_hostname_get(context));
if (!hostname)
{
const char *remote_address = (is_dialer ?
efl_net_dialer_address_dial_get(sock) :
efl_net_socket_address_remote_get(sock));
if (remote_address)
{
const char *host, *port;
tmp = strdup(remote_address);
EINA_SAFETY_ON_NULL_RETURN(tmp);
if (efl_net_ip_port_split(tmp, &host, &port))
hostname = host;
}
}
if ((uint8_t)pd->verify_mode == 0xff) pd->verify_mode = efl_net_ssl_context_verify_mode_get(context);
efl_net_ssl_conn_verify_mode_set(&pd->ssl_conn, pd->verify_mode);
efl_net_ssl_conn_hostname_verify_set(&pd->ssl_conn, pd->hostname_verify);
efl_net_ssl_conn_hostname_override_set(&pd->ssl_conn, hostname);
free(tmp);
pd->sock = efl_ref(sock);
efl_event_callback_array_add(sock, efl_net_socket_ssl_sock_cbs(), o);
if (efl_isa(sock, EFL_NET_DIALER_INTERFACE))
efl_event_callback_add(sock, EFL_NET_DIALER_EVENT_CONNECTED, efl_net_socket_ssl_sock_connected, o);
efl_net_socket_ssl_sock_can_read_changed(o, NULL);
efl_net_socket_ssl_sock_can_write_changed(o, NULL);
if (efl_io_closer_closed_get(sock))
efl_event_callback_call(o, EFL_IO_CLOSER_EVENT_CLOSED, NULL);
}
static Efl_Net_Ssl_Verify_Mode
_efl_net_socket_ssl_verify_mode_get(Eo *o EINA_UNUSED, Efl_Net_Socket_Ssl_Data *pd)
{
return pd->verify_mode;
}
static void
_efl_net_socket_ssl_verify_mode_set(Eo *o EINA_UNUSED, Efl_Net_Socket_Ssl_Data *pd, Efl_Net_Ssl_Verify_Mode verify_mode)
{
pd->verify_mode = verify_mode;
if (!efl_finalized_get(o)) return;
efl_net_ssl_conn_verify_mode_set(&pd->ssl_conn, pd->verify_mode);
}
static Eina_Bool
_efl_net_socket_ssl_hostname_verify_get(Eo *o EINA_UNUSED, Efl_Net_Socket_Ssl_Data *pd)
{
return pd->hostname_verify;
}
static void
_efl_net_socket_ssl_hostname_verify_set(Eo *o EINA_UNUSED, Efl_Net_Socket_Ssl_Data *pd, Eina_Bool hostname_verify)
{
pd->hostname_verify = hostname_verify;
if (!efl_finalized_get(o)) return;
efl_net_ssl_conn_hostname_verify_set(&pd->ssl_conn, pd->hostname_verify);
}
static const char *
_efl_net_socket_ssl_hostname_override_get(Eo *o EINA_UNUSED, Efl_Net_Socket_Ssl_Data *pd)
{
return pd->hostname_override;
}
static void
_efl_net_socket_ssl_hostname_override_set(Eo *o EINA_UNUSED, Efl_Net_Socket_Ssl_Data *pd, const char* hostname_override)
{
char *tmp = NULL;
const char *hostname;
eina_stringshare_replace(&pd->hostname_override, hostname_override);
hostname = pd->hostname_override;
if (!hostname)
{
const char *remote_address = (efl_isa(pd->sock, EFL_NET_DIALER_INTERFACE) ?
efl_net_dialer_address_dial_get(pd->sock) :
efl_net_socket_address_remote_get(pd->sock));
if (remote_address)
{
const char *host, *port;
tmp = strdup(remote_address);
EINA_SAFETY_ON_NULL_RETURN(tmp);
if (efl_net_ip_port_split(tmp, &host, &port))
hostname = host;
}
}
efl_net_ssl_conn_hostname_override_set(&pd->ssl_conn, hostname);
free(tmp);
}
EOLIAN static Efl_Object *
_efl_net_socket_ssl_efl_object_finalize(Eo *o, Efl_Net_Socket_Ssl_Data *pd EINA_UNUSED)
{
Eina_Error err;
o = efl_finalize(efl_super(o, MY_CLASS));
if (!o) return NULL;
if (!pd->sock)
{
ERR("no Efl.Net.Socket was adopted by this SSL=%p", o);
return NULL;
}
if (efl_isa(pd->sock, EFL_NET_DIALER_INTERFACE))
{
if (!efl_net_dialer_connected_get(pd->sock))
return o;
}
err = efl_net_ssl_conn_handshake(&pd->ssl_conn, &pd->did_handshake);
if (err)
{
WRN("SSL=%p failed handshake", o);
return NULL;
}
return o;
}
EOLIAN static Eo *
_efl_net_socket_ssl_efl_object_constructor(Eo *o, Efl_Net_Socket_Ssl_Data *pd)
{
pd->hostname_verify = 0xff;
pd->verify_mode = 0xff;
return efl_constructor(efl_super(o, MY_CLASS));
}
EOLIAN static void
_efl_net_socket_ssl_efl_object_destructor(Eo *o, Efl_Net_Socket_Ssl_Data *pd)
{
if (efl_io_closer_close_on_destructor_get(o) &&
(!efl_io_closer_closed_get(o)))
efl_io_closer_close(o);
efl_destructor(efl_super(o, MY_CLASS));
efl_net_ssl_conn_teardown(&pd->ssl_conn);
if (pd->sock)
{
efl_event_callback_array_del(pd->sock, efl_net_socket_ssl_sock_cbs(), o);
if (efl_isa(pd->sock, EFL_NET_DIALER_INTERFACE))
{
efl_event_callback_del(pd->sock, EFL_NET_DIALER_EVENT_CONNECTED, efl_net_socket_ssl_sock_connected, o);
}
efl_unref(pd->sock);
pd->sock = NULL;
}
if (pd->context)
{
efl_event_callback_del(pd->context, EFL_EVENT_DEL, _efl_net_socket_ssl_context_del, o);
efl_unref(pd->context);
pd->context = NULL;
}
eina_stringshare_replace(&pd->hostname_override, NULL);
}
EOLIAN static Eina_Error
_efl_net_socket_ssl_efl_io_closer_close(Eo *o, Efl_Net_Socket_Ssl_Data *pd)
{
EINA_SAFETY_ON_NULL_RETURN_VAL(pd->sock, EBADF);
efl_io_reader_can_read_set(o, EINA_FALSE);
efl_io_reader_eos_set(o, EINA_TRUE);
efl_net_ssl_conn_teardown(&pd->ssl_conn);
if (efl_io_closer_closed_get(pd->sock))
return 0;
return efl_io_closer_close(pd->sock);
}
EOLIAN static Eina_Bool
_efl_net_socket_ssl_efl_io_closer_closed_get(Eo *o EINA_UNUSED, Efl_Net_Socket_Ssl_Data *pd)
{
return (!pd->sock) || efl_io_closer_closed_get(pd->sock);
}
EOLIAN static Eina_Error
_efl_net_socket_ssl_efl_io_reader_read(Eo *o, Efl_Net_Socket_Ssl_Data *pd, Eina_Rw_Slice *rw_slice)
{
Eina_Error err;
EINA_SAFETY_ON_NULL_RETURN_VAL(rw_slice, EINVAL);
if (!pd->did_handshake)
{
rw_slice->mem = NULL;
rw_slice->len = 0;
return EAGAIN;
}
err = efl_net_ssl_conn_read(&pd->ssl_conn, rw_slice);
if (rw_slice->len == 0)
{
efl_io_reader_can_read_set(o, EINA_FALSE);
if (err == 0)
efl_io_reader_eos_set(o, EINA_TRUE);
}
return err;
}
EOLIAN static void
_efl_net_socket_ssl_efl_io_reader_can_read_set(Eo *o, Efl_Net_Socket_Ssl_Data *pd, Eina_Bool can_read)
{
EINA_SAFETY_ON_NULL_RETURN(pd->sock);
if (pd->can_read == can_read) return;
pd->can_read = can_read;
efl_event_callback_call(o, EFL_IO_READER_EVENT_CAN_READ_CHANGED, NULL);
}
EOLIAN static Eina_Bool
_efl_net_socket_ssl_efl_io_reader_can_read_get(Eo *o EINA_UNUSED, Efl_Net_Socket_Ssl_Data *pd)
{
return pd->can_read;
}
EOLIAN static Eina_Bool
_efl_net_socket_ssl_efl_io_reader_eos_get(Eo *o EINA_UNUSED, Efl_Net_Socket_Ssl_Data *pd)
{
return pd->eos;
}
EOLIAN static void
_efl_net_socket_ssl_efl_io_reader_eos_set(Eo *o, Efl_Net_Socket_Ssl_Data *pd, Eina_Bool is_eos)
{
EINA_SAFETY_ON_NULL_RETURN(pd->sock);
if (pd->eos == is_eos) return;
pd->eos = is_eos;
if (is_eos)
efl_event_callback_call(o, EFL_IO_READER_EVENT_EOS, NULL);
}
EOLIAN static Eina_Error
_efl_net_socket_ssl_efl_io_writer_write(Eo *o, Efl_Net_Socket_Ssl_Data *pd, Eina_Slice *ro_slice, Eina_Slice *remaining)
{
Eina_Error err;
EINA_SAFETY_ON_NULL_RETURN_VAL(ro_slice, EINVAL);
if (!pd->did_handshake)
{
if (remaining) *remaining = *ro_slice;
ro_slice->mem = NULL;
ro_slice->len = 0;
return EAGAIN;
}
if (remaining) *remaining = *ro_slice;
err = efl_net_ssl_conn_write(&pd->ssl_conn, ro_slice);
if (remaining)
{
remaining->bytes += ro_slice->len;
remaining->len -= ro_slice->len;
}
if (ro_slice->len == 0)
efl_io_writer_can_write_set(o, EINA_FALSE);
return err;
}
EOLIAN static void
_efl_net_socket_ssl_efl_io_writer_can_write_set(Eo *o, Efl_Net_Socket_Ssl_Data *pd, Eina_Bool can_write)
{
EINA_SAFETY_ON_NULL_RETURN(pd->sock);
if (pd->can_write == can_write) return;
pd->can_write = can_write;
efl_event_callback_call(o, EFL_IO_WRITER_EVENT_CAN_WRITE_CHANGED, NULL);
}
EOLIAN static Eina_Bool
_efl_net_socket_ssl_efl_io_writer_can_write_get(Eo *o EINA_UNUSED, Efl_Net_Socket_Ssl_Data *pd)
{
return pd->can_write;
}
EOLIAN static Eina_Bool
_efl_net_socket_ssl_efl_io_closer_close_on_exec_set(Eo *o EINA_UNUSED, Efl_Net_Socket_Ssl_Data *pd, Eina_Bool close_on_exec)
{
if (pd->sock) efl_io_closer_close_on_exec_set(pd->sock, close_on_exec);
return EINA_TRUE;
}
EOLIAN static Eina_Bool
_efl_net_socket_ssl_efl_io_closer_close_on_exec_get(Eo *o EINA_UNUSED, Efl_Net_Socket_Ssl_Data *pd)
{
return pd->sock && efl_io_closer_close_on_exec_get(pd->sock);
}
EOLIAN static void
_efl_net_socket_ssl_efl_io_closer_close_on_destructor_set(Eo *o EINA_UNUSED, Efl_Net_Socket_Ssl_Data *pd, Eina_Bool close_on_destructor)
{
if (pd->sock) efl_io_closer_close_on_destructor_set(pd->sock, close_on_destructor);
}
EOLIAN static Eina_Bool
_efl_net_socket_ssl_efl_io_closer_close_on_destructor_get(Eo *o EINA_UNUSED, Efl_Net_Socket_Ssl_Data *pd)
{
return pd->sock && efl_io_closer_close_on_destructor_get(pd->sock);
}
EOLIAN static const char *
_efl_net_socket_ssl_efl_net_socket_address_local_get(Eo *o EINA_UNUSED, Efl_Net_Socket_Ssl_Data *pd)
{
if (!pd->sock) return "unbound";
return efl_net_socket_address_local_get(pd->sock);
}
EOLIAN static const char *
_efl_net_socket_ssl_efl_net_socket_address_remote_get(Eo *o EINA_UNUSED, Efl_Net_Socket_Ssl_Data *pd)
{
if (!pd->sock) return "unbound";
return efl_net_socket_address_remote_get(pd->sock);
}
#include "efl_net_socket_ssl.eo.c"

View File

@ -0,0 +1,90 @@
var Efl.Net.Socket.Ssl.Error.HANDSHAKE: Eina.Error; [[Failed SSL handshake]]
var Efl.Net.Socket.Ssl.Error.CERTIFICATE_VERIFY_FAILED: Eina.Error; [[Failed to verify peer's certificate]]
class Efl.Net.Socket.Ssl (Efl.Object, Efl.Net.Socket) {
[[A wrapper socket doing SSL (Secure Sockets Layer).
Use this wrapper around an existing socket to do secure
communication, a common use is to apply it to TCP sockets
created with @Efl.Net.Dialer.Tcp or @Efl.Net.Server.Tcp created
with "client,add" event.
@since 1.19
]]
events {
ssl,ready; [[Notifies the SSL handshake was done and the socket is now able to communicate]]
ssl,error: Eina.Error; [[an error such as @Efl.Net.Socket.Ssl.Error.HANDSHAKE]]
}
methods {
adopt {
[[Adopt an Efl.Net.Dialer or regular Efl.Net.Socket that will be used for the actual communication.
If used with an Efl.Net.Dialer object, it will assume
the 'connect' role, otherwise will use 'accept'.
This is a constructor only method and should be called
before @Efl.Object.finalize.
]]
params {
efl_net_socket: Efl.Net.Socket; [[The socket to adopt]]
ctx: Efl.Net.Ssl.Context; [[The SSL context to use when adopting the socket]]
}
}
@property verify_mode {
[[How to verify the remote peer.]]
values {
verify_mode: Efl.Net.Ssl.Verify_Mode;
}
}
@property hostname_verify {
[[Define if hostname should be verified.
This will check the socket hostname (without the port in
case of an IP) or the overriden value from
@.hostname_override.
]]
values {
hostname_verify: bool;
}
}
@property hostname_override {
[[Overrides the hostname to use for this socket.
Most of time this is useful if you're using an IP
address but the server certificate only specifies DNS
(names).
If NULL, then it will fetch from socket using
@Efl.Net.Socket.address_remote or
@Efl.Net.Dialer.address_dial.
It's only used if @.hostname_verify is $true.
]]
values {
hostname_override: string @nullable;
}
}
}
implements {
Efl.Object.constructor;
Efl.Object.destructor;
Efl.Object.finalize;
Efl.Io.Closer.close;
Efl.Io.Closer.closed.get;
Efl.Io.Closer.close_on_exec;
Efl.Io.Closer.close_on_destructor;
Efl.Io.Reader.read;
Efl.Io.Reader.can_read;
Efl.Io.Reader.eos;
Efl.Io.Writer.write;
Efl.Io.Writer.can_write;
Efl.Net.Socket.address_remote.get;
Efl.Net.Socket.address_local.get;
}
}

View File

@ -0,0 +1,362 @@
#include <gnutls/gnutls.h>
#include <gnutls/x509.h>
struct _Efl_Net_Ssl_Conn {
gnutls_session_t session;
gnutls_datum_t ticket;
Eo *sock;
const char *hostname;
Efl_Net_Ssl_Verify_Mode verify_mode;
Eina_Bool hostname_verify;
Eina_Bool is_dialer;
};
static ssize_t
_efl_net_ssl_conn_write(gnutls_transport_ptr_t transp, const void *buf, size_t len)
{
Eina_Slice slice = {
.mem = buf,
.len = len
};
Efl_Net_Ssl_Conn *conn = transp;
Eina_Error err;
if ((!buf) || (len == 0)) return 0;
if (!conn) return 0;
if (!efl_io_writer_can_write_get(conn->sock))
{
DBG("socket=%p would block if written!", conn->sock);
gnutls_transport_set_errno(conn->session, EAGAIN);
return -1;
}
err = efl_io_writer_write(conn->sock, &slice, NULL);
if (err)
{
gnutls_transport_set_errno(conn->session, err);
return -1;
}
gnutls_transport_set_errno(conn->session, 0);
return slice.len;
}
static ssize_t
_efl_net_ssl_conn_read(gnutls_transport_ptr_t transp, void *buf, size_t len)
{
Eina_Rw_Slice slice = {
.mem = buf,
.len = len
};
Efl_Net_Ssl_Conn *conn = transp;
Eina_Error err;
if ((!buf) || (len == 0)) return 0;
if (!conn) return 0;
if (!efl_io_reader_can_read_get(conn->sock))
{
DBG("socket=%p would block if read!", conn->sock);
gnutls_transport_set_errno(conn->session, EAGAIN);
return -1;
}
err = efl_io_reader_read(conn->sock, &slice);
if (err)
{
gnutls_transport_set_errno(conn->session, err);
return -1;
}
gnutls_transport_set_errno(conn->session, 0);
return slice.len;
}
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)
{
gnutls_certificate_request_t req;
int r;
EINA_SAFETY_ON_TRUE_RETURN_VAL(conn->session != NULL, EALREADY);
conn->is_dialer = is_dialer;
conn->session = efl_net_ssl_context_connection_new(context);
EINA_SAFETY_ON_NULL_RETURN_VAL(conn->session, ENOSYS);
gnutls_handshake_set_private_extensions(conn->session, 1);
switch (conn->verify_mode)
{
case EFL_NET_SSL_VERIFY_MODE_NONE:
req = GNUTLS_CERT_IGNORE;
break;
case EFL_NET_SSL_VERIFY_MODE_OPTIONAL:
req = GNUTLS_CERT_REQUEST;
break;
case EFL_NET_SSL_VERIFY_MODE_REQUIRED:
default:
req = GNUTLS_CERT_REQUIRE;
}
gnutls_certificate_server_set_request(conn->session, req);
if (is_dialer)
{
r = gnutls_session_ticket_enable_client(conn->session);
if (r < 0)
{
ERR("ssl_conn=%p could not enable session's ticket client: %s", conn, gnutls_strerror(r));
goto error;
}
}
else
{
r = gnutls_session_ticket_key_generate(&conn->ticket);
if (r < 0)
{
ERR("ssl_conn=%p could not generate session ticket: %s", conn, gnutls_strerror(r));
goto error;
}
r = gnutls_session_ticket_enable_server(conn->session, &conn->ticket);
if (r < 0)
{
ERR("ssl_conn=%p could not enable session's ticket server: %s", conn, gnutls_strerror(r));
goto error_ticket;
}
}
conn->sock = sock;
gnutls_transport_set_ptr(conn->session, conn);
gnutls_transport_set_push_function(conn->session, _efl_net_ssl_conn_write);
gnutls_transport_set_pull_function(conn->session, _efl_net_ssl_conn_read);
return 0;
error_ticket:
gnutls_free(conn->ticket.data);
conn->ticket.data = NULL;
error:
gnutls_deinit(conn->session);
conn->session = NULL;
return ENOSYS;
}
static void
efl_net_ssl_conn_teardown(Efl_Net_Ssl_Conn *conn)
{
if (conn->session)
{
gnutls_bye(conn->session, GNUTLS_SHUT_RDWR);
gnutls_deinit(conn->session);
conn->session = NULL;
}
if (conn->ticket.data)
{
gnutls_free(conn->ticket.data);
conn->ticket.data = NULL;
}
eina_stringshare_replace(&conn->hostname, NULL);
}
static Eina_Error
efl_net_ssl_conn_write(Efl_Net_Ssl_Conn *conn, Eina_Slice *slice)
{
ssize_t r = gnutls_record_send(conn->session, slice->mem, slice->len);
if (r < 0)
{
slice->len = 0;
if (gnutls_error_is_fatal(r))
{
ERR("ssl_conn=%p could not send %zd bytes: %s", conn, slice->len, gnutls_strerror(r));
return EINVAL;
}
DBG("ssl_conn=%p could not send %zd bytes: %s", conn, slice->len, gnutls_strerror(r));
return EAGAIN;
}
slice->len = r;
return 0;
}
static Eina_Error
efl_net_ssl_conn_read(Efl_Net_Ssl_Conn *conn, Eina_Rw_Slice *slice)
{
ssize_t r = gnutls_record_recv(conn->session, slice->mem, slice->len);
if (r < 0)
{
slice->len = 0;
if (gnutls_error_is_fatal(r))
{
ERR("ssl_conn=%p could not receive %zd bytes: %s", conn, slice->len, gnutls_strerror(r));
return EINVAL;
}
DBG("ssl_conn=%p could not receive %zd bytes: %s", conn, slice->len, gnutls_strerror(r));
return EAGAIN;
}
slice->len = r;
return 0;
}
static Eina_Error
_efl_net_ssl_conn_verify(Efl_Net_Ssl_Conn *conn)
{
unsigned status = 0;
int r;
r = gnutls_certificate_verify_peers2(conn->session, &status);
if (r < 0)
{
ERR("ssl_conn=%p could not verify peer: %s", conn, gnutls_strerror(r));
return EFL_NET_SOCKET_SSL_ERROR_HANDSHAKE;
}
if (!status) return 0;
if (status & GNUTLS_CERT_INVALID)
WRN("ssl_conn=%p The certificate is not trusted.", conn);
if (status & GNUTLS_CERT_SIGNER_NOT_FOUND)
WRN("ssl_conn=%p The certificate hasn't got a known issuer.", conn);
if (status & GNUTLS_CERT_REVOKED)
WRN("ssl_conn=%p The certificate has been revoked.", conn);
if (status & GNUTLS_CERT_EXPIRED)
WRN("ssl_conn=%p The certificate has expired", conn);
if (status & GNUTLS_CERT_NOT_ACTIVATED)
WRN("ssl_conn=%p The certificate is not yet activated", conn);
return EFL_NET_SOCKET_SSL_ERROR_CERTIFICATE_VERIFY_FAILED;
}
static Eina_Error
_efl_net_ssl_conn_hostname_verify(Efl_Net_Ssl_Conn *conn)
{
const gnutls_datum_t *list;
unsigned int size;
gnutls_x509_crt_t cert = NULL;
int r;
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;
}
if (gnutls_certificate_type_get(conn->session) != GNUTLS_CRT_X509)
{
ERR("ssl_conn=%p PGP certificates are not yet supported!", conn);
return EFL_NET_SOCKET_SSL_ERROR_CERTIFICATE_VERIFY_FAILED;
}
list = gnutls_certificate_get_peers(conn->session, &size);
if (!list)
{
ERR("ssl_conn=%p no peer certificate!", conn);
return EFL_NET_SOCKET_SSL_ERROR_HANDSHAKE;
}
r = gnutls_x509_crt_init(&cert);
EINA_SAFETY_ON_TRUE_RETURN_VAL(r < 0, EFL_NET_SOCKET_SSL_ERROR_CERTIFICATE_VERIFY_FAILED);
r = gnutls_x509_crt_import(cert, &list[0], GNUTLS_X509_FMT_DER);
if (r < 0)
{
ERR("ssl_conn=%p could not import x509 certificate to verify: %s", conn, gnutls_strerror(r));
gnutls_x509_crt_deinit(cert);
return EFL_NET_SOCKET_SSL_ERROR_CERTIFICATE_VERIFY_FAILED;
}
r = gnutls_x509_crt_check_hostname(cert, conn->hostname);
gnutls_x509_crt_deinit(cert);
if (r == 1)
return 0;
ERR("ssl_conn=%p hostname='%s' doesn't match certificate.",
conn, conn->hostname);
return EFL_NET_SOCKET_SSL_ERROR_CERTIFICATE_VERIFY_FAILED;
}
static Eina_Error
efl_net_ssl_conn_handshake(Efl_Net_Ssl_Conn *conn, Eina_Bool *done)
{
int r = gnutls_handshake(conn->session);
if (r < 0)
{
*done = EINA_FALSE;
if (gnutls_error_is_fatal(r))
{
ERR("ssl_conn=%p could not handshake: %s", conn, gnutls_strerror(r));
return EFL_NET_SOCKET_SSL_ERROR_HANDSHAKE;
}
DBG("ssl_conn=%p did not finish handshake: %s", conn, gnutls_strerror(r));
return 0;
}
if (conn->verify_mode != EFL_NET_SSL_VERIFY_MODE_NONE)
{
Eina_Error err = _efl_net_ssl_conn_verify(conn);
if (err)
return err;
}
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;
}
static Eina_Error
efl_net_ssl_conn_verify_mode_set(Efl_Net_Ssl_Conn *conn, Efl_Net_Ssl_Verify_Mode verify_mode)
{
gnutls_certificate_request_t req;
conn->verify_mode = verify_mode;
switch (conn->verify_mode)
{
case EFL_NET_SSL_VERIFY_MODE_NONE:
req = GNUTLS_CERT_IGNORE;
break;
case EFL_NET_SSL_VERIFY_MODE_OPTIONAL:
req = GNUTLS_CERT_REQUEST;
break;
case EFL_NET_SSL_VERIFY_MODE_REQUIRED:
default:
req = GNUTLS_CERT_REQUIRE;
}
gnutls_certificate_server_set_request(conn->session, req);
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)
{
int r;
eina_stringshare_replace(&conn->hostname, hostname);
if (!hostname) hostname = "";
r = gnutls_server_name_set(conn->session, GNUTLS_NAME_DNS, hostname, strlen(hostname));
if (r < 0)
{
ERR("ssl_conn=%p could not set server name '%s': %s", conn, hostname, gnutls_strerror(r));
return EINVAL;
}
return 0;
}

View File

@ -0,0 +1,50 @@
struct _Efl_Net_Ssl_Conn {
};
static Eina_Error
efl_net_ssl_conn_setup(Efl_Net_Ssl_Conn *conn EINA_UNUSED, Eina_Bool is_dialer EINA_UNUSED, Efl_Net_Socket *sock EINA_UNUSED, Efl_Net_Ssl_Context *context EINA_UNUSED)
{
ERR("EFL compiled with --with-crypto=none");
return ENOSYS;
}
static void
efl_net_ssl_conn_teardown(Efl_Net_Ssl_Conn *conn EINA_UNUSED)
{
}
static Eina_Error
efl_net_ssl_conn_write(Efl_Net_Ssl_Conn *conn EINA_UNUSED, Eina_Slice *slice EINA_UNUSED)
{
return ENOSYS;
}
static Eina_Error
efl_net_ssl_conn_read(Efl_Net_Ssl_Conn *conn EINA_UNUSED, Eina_Rw_Slice *slice EINA_UNUSED)
{
return ENOSYS;
}
static Eina_Error
efl_net_ssl_conn_handshake(Efl_Net_Ssl_Conn *conn EINA_UNUSED, Eina_Bool *done EINA_UNUSED)
{
return ENOSYS;
}
static Eina_Error
efl_net_ssl_conn_verify_mode_set(Efl_Net_Ssl_Conn *conn EINA_UNUSED, Efl_Net_Ssl_Verify_Mode verify_mode EINA_UNUSED)
{
return ENOSYS;
}
static Eina_Error
efl_net_ssl_conn_hostname_verify_set(Efl_Net_Ssl_Conn *conn EINA_UNUSED, Eina_Bool hostname_verify EINA_UNUSED)
{
return ENOSYS;
}
static Eina_Error
efl_net_ssl_conn_hostname_override_set(Efl_Net_Ssl_Conn *conn EINA_UNUSED, const char *hostname EINA_UNUSED)
{
return ENOSYS;
}

View File

@ -0,0 +1,520 @@
#include <openssl/x509v3.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/dh.h>
#ifdef HAVE_ARPA_INET_H
# include <arpa/inet.h>
#endif
/* OpenSSL's BIO is the abstraction for I/O, provide one for Efl.Io.* */
static int
efl_net_socket_bio_create(BIO *b)
{
b->init = 1;
b->num = 0;
b->ptr = NULL;
b->flags = 0;
return 1;
}
static int
efl_net_socket_bio_destroy(BIO *b)
{
if (!b) return 0;
b->init = 0;
b->ptr = NULL;
b->flags = 0;
return 1;
}
static int
efl_net_socket_bio_read(BIO *b, char *buf, int len)
{
Eina_Rw_Slice slice = {
.mem = buf,
.len = len
};
Eo *sock = b->ptr;
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
};
Eo *sock = b->ptr;
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 = {
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
};
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[] = {
{SSL3_VERSION, "SSLv3.0"},
{TLS1_VERSION, "TLSv1.0"},
{TLS1_1_VERSION, "TLSv1.1"},
{TLS1_2_VERSION, "TLSv1.2"},
{DTLS1_VERSION, "DTLSv1.0"},
{DTLS1_2_VERSION, "DTLSv1.2"},
{DTLS1_BAD_VER, "DTLSv1.0"},
{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);
EINA_SAFETY_ON_NULL_GOTO(conn->bio, error_bio);
conn->bio->ptr = sock;
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 = 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 = 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;
}
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;
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;
}
if (strchr(conn->hostname, ':')) family = AF_INET6;
if (inet_pton(family, conn->hostname, &addr) == 1)
{
label = "IP address";
r = X509_check_ip_asc(x509, conn->hostname, 0);
}
else
{
label = "hostname";
r = 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)
{
int r = SSL_do_handshake(conn->ssl);
long err_ssl;
const char *err_file;
const char *err_data;
int err_line, err_flags;
*done = EINA_FALSE;
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;
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;
}

View File

@ -0,0 +1,373 @@
#define EFL_NET_SSL_CONTEXT_PROTECTED 1
#define EFL_IO_READER_PROTECTED 1
#define EFL_IO_WRITER_PROTECTED 1
#define EFL_IO_CLOSER_PROTECTED 1
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "Ecore.h"
#include "Ecore_Con.h"
#include "ecore_con_private.h"
#include "Emile.h"
/**
* This function is used by efl_net_socket_ssl to retrieve a new
* connection based on the implementation-depentent context.
*
* @internal
*/
void *efl_net_ssl_context_connection_new(Efl_Net_Ssl_Context *context);
typedef struct _Efl_Net_Ssl_Ctx Efl_Net_Ssl_Ctx;
typedef struct _Efl_Net_Ssl_Ctx_Config {
Efl_Net_Ssl_Cipher cipher;
Eina_Bool is_dialer;
Eina_Bool load_defaults;
Eina_List **certificates;
Eina_List **private_keys;
Eina_List **certificate_revogation_lists;
Eina_List **certificate_authorities;
} Efl_Net_Ssl_Ctx_Config;
/**
* Returns the platform dependent context to efl_net_socket_ssl
* wrapper.
*
* @internal
*/
static void *efl_net_ssl_ctx_connection_new(Efl_Net_Ssl_Ctx *ctx);
/**
* Setups the SSL context
*
* Update the given lists, removing invalid entries. If all entries
* failed in a list, return EINVAL.
*
* @internal
*/
static Eina_Error efl_net_ssl_ctx_setup(Efl_Net_Ssl_Ctx *ctx, Efl_Net_Ssl_Ctx_Config cfg);
/**
* Cleans up the SSL associated to this context.
* @internal
*/
static void efl_net_ssl_ctx_teardown(Efl_Net_Ssl_Ctx *ctx);
/**
* Configure how to verify peer.
*
* @internal
*/
static Eina_Error efl_net_ssl_ctx_verify_mode_set(Efl_Net_Ssl_Ctx *ctx, Efl_Net_Ssl_Verify_Mode verify_mode);
/**
* Configure whenever to check for hostname.
*
* @internal
*/
static Eina_Error efl_net_ssl_ctx_hostname_verify_set(Efl_Net_Ssl_Ctx *ctx, Eina_Bool hostname_verify);
/**
* Configure the hostname to use.
*
* @note duplicate hostname if needed!
*
* @internal
*/
static Eina_Error efl_net_ssl_ctx_hostname_set(Efl_Net_Ssl_Ctx *ctx, const char *hostname);
#if HAVE_OPENSSL
#include "efl_net_ssl_ctx-openssl.c"
#elif HAVE_GNUTLS
#include "efl_net_ssl_ctx-gnutls.c"
#else
#include "efl_net_ssl_ctx-none.c"
#endif
#define MY_CLASS EFL_NET_SSL_CONTEXT_CLASS
typedef struct _Efl_Net_Ssl_Context_Data
{
Efl_Net_Ssl_Ctx ssl_ctx;
Eina_List *certificates;
Eina_List *private_keys;
Eina_List *certificate_revogation_lists;
Eina_List *certificate_authorities;
const char *hostname;
Efl_Net_Ssl_Cipher cipher;
Eina_Bool is_dialer;
Efl_Net_Ssl_Verify_Mode verify_mode;
Eina_Bool load_defaults;
Eina_Bool hostname_verify;
Eina_Bool did_handshake;
Eina_Bool can_read;
Eina_Bool eos;
Eina_Bool can_write;
} Efl_Net_Ssl_Context_Data;
void *
efl_net_ssl_context_connection_new(Efl_Net_Ssl_Context *context)
{
Efl_Net_Ssl_Context_Data *pd = efl_data_scope_get(context, MY_CLASS);
EINA_SAFETY_ON_NULL_RETURN_VAL(pd, NULL);
return efl_net_ssl_ctx_connection_new(&pd->ssl_ctx);
}
EOLIAN static void
_efl_net_ssl_context_setup(Eo *o, Efl_Net_Ssl_Context_Data *pd, Efl_Net_Ssl_Cipher cipher, Eina_Bool is_dialer)
{
EINA_SAFETY_ON_TRUE_RETURN(efl_finalized_get(o));
EINA_SAFETY_ON_TRUE_RETURN(cipher > EFL_NET_SSL_CIPHER_TLSV1_2);
pd->cipher = cipher;
pd->is_dialer = is_dialer;
}
static Eina_List *
_efl_net_ssl_context_string_iter_to_list(Eina_Iterator *it)
{
Eina_List *lst = NULL;
const char *str;
EINA_ITERATOR_FOREACH(it, str)
{
if (!str) continue;
lst = eina_list_append(lst, eina_stringshare_add(str));
}
eina_iterator_free(it);
return lst;
}
static void
_efl_net_ssl_context_string_list_free(Eina_List **p_lst)
{
const char *str;
EINA_LIST_FREE(*p_lst, str)
eina_stringshare_del(str);
}
static Eina_Iterator *
_efl_net_ssl_context_certificates_get(Eo *o EINA_UNUSED, Efl_Net_Ssl_Context_Data *pd)
{
return eina_list_iterator_new(pd->certificates);
}
static void
_efl_net_ssl_context_certificates_set(Eo *o EINA_UNUSED, Efl_Net_Ssl_Context_Data *pd, Eina_Iterator *it)
{
_efl_net_ssl_context_string_list_free(&pd->certificates);
pd->certificates = _efl_net_ssl_context_string_iter_to_list(it);
}
static Eina_Iterator *
_efl_net_ssl_context_private_keys_get(Eo *o EINA_UNUSED, Efl_Net_Ssl_Context_Data *pd)
{
return eina_list_iterator_new(pd->private_keys);
}
static void
_efl_net_ssl_context_private_keys_set(Eo *o EINA_UNUSED, Efl_Net_Ssl_Context_Data *pd, Eina_Iterator *it)
{
_efl_net_ssl_context_string_list_free(&pd->private_keys);
pd->private_keys = _efl_net_ssl_context_string_iter_to_list(it);
}
static Eina_Iterator *
_efl_net_ssl_context_certificate_revogation_lists_get(Eo *o EINA_UNUSED, Efl_Net_Ssl_Context_Data *pd)
{
return eina_list_iterator_new(pd->certificate_revogation_lists);
}
static void
_efl_net_ssl_context_certificate_revogation_lists_set(Eo *o EINA_UNUSED, Efl_Net_Ssl_Context_Data *pd, Eina_Iterator *it)
{
_efl_net_ssl_context_string_list_free(&pd->certificate_revogation_lists);
pd->certificate_revogation_lists = _efl_net_ssl_context_string_iter_to_list(it);
}
static Eina_Iterator *
_efl_net_ssl_context_certificate_authorities_get(Eo *o EINA_UNUSED, Efl_Net_Ssl_Context_Data *pd)
{
return eina_list_iterator_new(pd->certificate_authorities);
}
static void
_efl_net_ssl_context_certificate_authorities_set(Eo *o EINA_UNUSED, Efl_Net_Ssl_Context_Data *pd, Eina_Iterator *it)
{
_efl_net_ssl_context_string_list_free(&pd->certificate_authorities);
pd->certificate_authorities = _efl_net_ssl_context_string_iter_to_list(it);
}
static Eina_Bool
_efl_net_ssl_context_default_paths_load_get(Eo *o EINA_UNUSED, Efl_Net_Ssl_Context_Data *pd)
{
return pd->load_defaults;
}
static void
_efl_net_ssl_context_default_paths_load_set(Eo *o EINA_UNUSED, Efl_Net_Ssl_Context_Data *pd, Eina_Bool load_defaults)
{
pd->load_defaults = load_defaults;
}
static Efl_Net_Ssl_Verify_Mode
_efl_net_ssl_context_verify_mode_get(Eo *o EINA_UNUSED, Efl_Net_Ssl_Context_Data *pd)
{
return pd->verify_mode;
}
static void
_efl_net_ssl_context_verify_mode_set(Eo *o, Efl_Net_Ssl_Context_Data *pd, Efl_Net_Ssl_Verify_Mode verify_mode)
{
pd->verify_mode = verify_mode;
if (!efl_finalized_get(o)) return;
efl_net_ssl_ctx_verify_mode_set(&pd->ssl_ctx, pd->verify_mode);
}
static Eina_Bool
_efl_net_ssl_context_hostname_verify_get(Eo *o EINA_UNUSED, Efl_Net_Ssl_Context_Data *pd)
{
return pd->hostname_verify;
}
static void
_efl_net_ssl_context_hostname_verify_set(Eo *o EINA_UNUSED, Efl_Net_Ssl_Context_Data *pd, Eina_Bool hostname_verify)
{
pd->hostname_verify = hostname_verify;
if (!efl_finalized_get(o)) return;
efl_net_ssl_ctx_hostname_verify_set(&pd->ssl_ctx, pd->hostname_verify);
}
static const char *
_efl_net_ssl_context_hostname_get(Eo *o EINA_UNUSED, Efl_Net_Ssl_Context_Data *pd)
{
return pd->hostname;
}
static void
_efl_net_ssl_context_hostname_set(Eo *o EINA_UNUSED, Efl_Net_Ssl_Context_Data *pd, const char* hostname)
{
eina_stringshare_replace(&pd->hostname, hostname);
if (!efl_finalized_get(o)) return;
efl_net_ssl_ctx_hostname_set(&pd->ssl_ctx, pd->hostname);
}
EOLIAN static Efl_Object *
_efl_net_ssl_context_efl_object_finalize(Eo *o, Efl_Net_Ssl_Context_Data *pd)
{
Eina_Error err;
Efl_Net_Ssl_Ctx_Config cfg;
o = efl_finalize(efl_super(o, MY_CLASS));
if (!o) return NULL;
if (!emile_cipher_init())
{
ERR("could not initialize cipher subsystem.");
return NULL;
}
if (pd->is_dialer)
{
if ((uint8_t)pd->verify_mode == 0xff)
pd->verify_mode = EFL_NET_SSL_VERIFY_MODE_REQUIRED;
if (pd->hostname_verify == 0xff)
pd->hostname_verify = EINA_TRUE;
if (pd->load_defaults == 0xff)
pd->load_defaults = EINA_TRUE;
}
else
{
cfg.is_dialer = EINA_FALSE;
if ((uint8_t)pd->verify_mode == 0xff)
pd->verify_mode = EFL_NET_SSL_VERIFY_MODE_NONE;
if (pd->hostname_verify == 0xff)
pd->hostname_verify = EINA_FALSE;
if (pd->load_defaults == 0xff)
pd->load_defaults = EINA_FALSE;
}
cfg.cipher = pd->cipher;
cfg.is_dialer = pd->is_dialer;
cfg.load_defaults = pd->load_defaults;
cfg.certificates = &pd->certificates;
cfg.private_keys = &pd->private_keys;
cfg.certificate_revogation_lists = &pd->certificate_revogation_lists;
cfg.certificate_authorities = &pd->certificate_authorities;
cfg.load_defaults = pd->load_defaults;
err = efl_net_ssl_ctx_setup(&pd->ssl_ctx, cfg);
if (err)
{
ERR("o=%p failed to setup context (is_dialer=%d)", o, cfg.is_dialer);
return NULL;
}
DBG("o=%p setup context (is_dialer=%d) ssl_ctx=%p", o, cfg.is_dialer, &pd->ssl_ctx);
efl_net_ssl_ctx_verify_mode_set(&pd->ssl_ctx, pd->verify_mode);
efl_net_ssl_ctx_hostname_verify_set(&pd->ssl_ctx, pd->hostname_verify);
efl_net_ssl_ctx_hostname_set(&pd->ssl_ctx, pd->hostname);
return o;
}
EOLIAN static Eo *
_efl_net_ssl_context_efl_object_constructor(Eo *o, Efl_Net_Ssl_Context_Data *pd)
{
pd->cipher = EFL_NET_SSL_CIPHER_AUTO;
pd->is_dialer = EINA_TRUE;
pd->load_defaults = 0xff;
pd->hostname_verify = 0xff;
pd->verify_mode = 0xff;
return efl_constructor(efl_super(o, MY_CLASS));
}
EOLIAN static void
_efl_net_ssl_context_efl_object_destructor(Eo *o, Efl_Net_Ssl_Context_Data *pd)
{
efl_destructor(efl_super(o, MY_CLASS));
efl_net_ssl_ctx_teardown(&pd->ssl_ctx);
_efl_net_ssl_context_string_list_free(&pd->certificates);
_efl_net_ssl_context_string_list_free(&pd->private_keys);
_efl_net_ssl_context_string_list_free(&pd->certificate_revogation_lists);
_efl_net_ssl_context_string_list_free(&pd->certificate_authorities);
eina_stringshare_replace(&pd->hostname, NULL);
}
static Efl_Net_Ssl_Context *_efl_net_ssl_context_default_dialer = NULL;
static void
_efl_net_ssl_context_default_dialer_del(void *data EINA_UNUSED, const Efl_Event *event EINA_UNUSED)
{
_efl_net_ssl_context_default_dialer = NULL;
}
EOLIAN static Efl_Net_Ssl_Context *
_efl_net_ssl_context_default_dialer_get(Efl_Class *klass, void *pd EINA_UNUSED)
{
if (!_efl_net_ssl_context_default_dialer)
{
_efl_net_ssl_context_default_dialer = efl_add(klass, NULL,
efl_net_ssl_context_verify_mode_set(efl_added, EFL_NET_SSL_VERIFY_MODE_REQUIRED),
efl_net_ssl_context_hostname_verify_set(efl_added, EINA_TRUE),
efl_net_ssl_context_default_paths_load_set(efl_added, EINA_TRUE),
efl_net_ssl_context_setup(efl_added, EFL_NET_SSL_CIPHER_AUTO, EINA_TRUE));
efl_event_callback_add(_efl_net_ssl_context_default_dialer,
EFL_EVENT_DEL,
_efl_net_ssl_context_default_dialer_del,
NULL);
}
return _efl_net_ssl_context_default_dialer;
}
#include "efl_net_ssl_context.eo.c"

View File

@ -0,0 +1,122 @@
import efl_net_ssl_types;
class Efl.Net.Ssl.Context (Efl.Object) {
[[A SSL Context that is used to start a SSL socket wrapper.
The context will contain common configurations such as
certificates, private keys, certificate revogation lists (CRLs),
certificate authorities (CAs) and so on.
The method @.setup must be called once before
@Efl.Object.finalize in order to define the mandatory
operational parameters.
\@note All setter methods must be called before @Efl.Object.finalize.
@since 1.19
]]
methods {
@property default_dialer @class {
[[The default context for dialers.
It will start with:
- default_paths_load = true
- cipher = auto
- verify_mode = required
- verify_hostname = true
]]
get { }
values {
default_client_context: Efl.Net.Ssl.Context;
}
}
setup {
[[Defines the context mandatory operation parameters]]
params {
cipher: Efl.Net.Ssl.Cipher; [[Cipher to use, prefer @Efl.Net.Ssl.Cipher.auto]]
is_dialer: bool; [[If $true, this SSL context is targeted at dialers connecting to a remote serer]]
}
}
@property certificates {
[[The list of paths to certificates to use.]]
values {
paths: free(own(iterator<string>), eina_iterator_free);
}
}
@property private_keys {
[[The list of paths to private keys to use.]]
values {
paths: free(own(iterator<string>), eina_iterator_free);
}
}
@property certificate_revogation_lists {
[[The list of paths to CRL (certificate revogation list) to use.]]
values {
paths: free(own(iterator<string>), eina_iterator_free);
}
}
@property certificate_authorities {
[[The list of paths to CA (certificate authoritie) to use.]]
values {
paths: free(own(iterator<string>), eina_iterator_free);
}
}
@property default_paths_load {
[[If $true, will use system's default certificate storage]]
values {
default_paths_load: bool;
}
}
@property verify_mode {
[[How to verify the remote peer.]]
values {
verify_mode: Efl.Net.Ssl.Verify_Mode;
}
}
@property hostname_verify {
[[Define if hostname should be verified.
This will check the socket hostname (without the port in
case of an IP) or the overriden value from
@.hostname.
]]
values {
hostname_verify: bool;
}
}
@property hostname {
[[Defines the hostname to use for sockets.
This is useful to avoid replicating a hostname in all
socket wrapper with hostname_override.
If NULL, then sockets wrappers will will fetch from
adopted socket using address_remote or
address_dial.
It's only used if @.hostname_verify is $true.
]]
values {
hostname: string @nullable;
}
}
}
implements {
Efl.Object.constructor;
Efl.Object.destructor;
Efl.Object.finalize;
}
}

View File

@ -0,0 +1,310 @@
#include <gnutls/gnutls.h>
struct _Efl_Net_Ssl_Ctx {
gnutls_certificate_credentials_t x509_cred;
gnutls_priority_t priority;
Eina_Bool is_dialer;
};
static Eina_Error
_efl_net_ssl_ctx_load_lists(Efl_Net_Ssl_Ctx *ctx, Efl_Net_Ssl_Ctx_Config cfg)
{
Eina_List *n, *n_next, *pk_node;
const char *path;
unsigned certificates_count = eina_list_count(*cfg.certificates);
unsigned private_keys_count = eina_list_count(*cfg.private_keys);
unsigned certificate_revogation_lists_count = eina_list_count(*cfg.certificate_revogation_lists);
unsigned certificate_authorities_count = eina_list_count(*cfg.certificate_authorities);
int r;
ctx->is_dialer = cfg.is_dialer;
if (cfg.load_defaults)
{
r = gnutls_certificate_set_x509_system_trust(ctx->x509_cred);
if (r < 0)
{
ERR("ssl_ctx=%p could not load default paths: %s", ctx, gnutls_strerror(r));
return ENOSYS;
}
DBG("ssl_ctx=%p loaded default paths", ctx);
}
else
DBG("ssl_ctx=%p did not load default paths", ctx);
/* GNUTLS needs certificate-key pairs, so we do:
*
* - if no private keys, use certificate as its own key;
*
* - if a private keys, walk the list alongside certificates, but
* do NOT delete elements if list sizes are different. Stop at
* last private key, allowing a single private key for multiple
* certificates.
*/
pk_node = *cfg.private_keys;
EINA_LIST_FOREACH_SAFE(*cfg.certificates, n, n_next, path)
{
const char *key = pk_node ? pk_node->data : path;
r = gnutls_certificate_set_x509_key_file(ctx->x509_cred, path, key, GNUTLS_X509_FMT_PEM);
if (r < 0)
{
ERR("ssl_ctx=%p could not use certificate from '%s' with key '%s': %s",
ctx, path, key, gnutls_strerror(r));
if (pk_node)
{
if (eina_list_count(*cfg.private_keys) == eina_list_count(*cfg.certificates))
{
pk_node = pk_node->next;
eina_stringshare_del(key);
*cfg.private_keys = eina_list_remove_list(*cfg.private_keys, pk_node->prev);
}
else if (pk_node->next) pk_node = pk_node->next;
}
eina_stringshare_del(path);
*cfg.certificates = eina_list_remove_list(*cfg.certificates, n);
continue;
}
else
{
if (pk_node->next) pk_node = pk_node->next;
}
DBG("ssl_ctx=%p loaded certificate '%s' with key '%s'", ctx, path, key);
}
if (certificates_count && !*cfg.certificates)
{
ERR("ssl_ctx=%p none of the required certificates were loaded!", ctx);
return EINVAL;
}
if (private_keys_count && !*cfg.private_keys)
{
ERR("ssl_ctx=%p none of the required private keys were loaded!", ctx);
return EINVAL;
}
else if (pk_node != eina_list_last(*cfg.private_keys))
{
do
{
n = pk_node->next;
path = n->data;
ERR("ssl_ctx=%p extra private key is unused '%s'", ctx, path);
eina_stringshare_del(path);
*cfg.private_keys = eina_list_remove_list(*cfg.private_keys, n);
}
while (pk_node->next);
}
EINA_LIST_FOREACH_SAFE(*cfg.certificate_revogation_lists, n, n_next, path)
{
r = gnutls_certificate_set_x509_crl_file(ctx->x509_cred, path, GNUTLS_X509_FMT_PEM);
if (r < 0)
{
ERR("ssl_ctx=%p could not use certificate revogation lists from %s: %s",
ctx, path, gnutls_strerror(r));
eina_stringshare_del(path);
*cfg.certificate_revogation_lists = eina_list_remove_list(*cfg.certificate_revogation_lists, n);
continue;
}
DBG("ssl_ctx=%p loaded certificate revogation lists '%s'", ctx, path);
}
if (certificate_revogation_lists_count && !*cfg.certificate_revogation_lists)
{
ERR("ssl_ctx=%p none of the required certificate revogation lists were loaded!", ctx);
return EINVAL;
}
EINA_LIST_FOREACH_SAFE(*cfg.certificate_authorities, n, n_next, path)
{
struct stat st;
r = 0;
if (stat(path, &st) != 0)
{
ERR("ssl_ctx=%p could not load certificate authorities from '%s': %s", ctx, path, strerror(errno));
eina_stringshare_del(path);
*cfg.certificate_authorities = eina_list_remove_list(*cfg.certificate_authorities, n);
continue;
}
else if (S_ISDIR(st.st_mode))
r = gnutls_certificate_set_x509_trust_dir(ctx->x509_cred, path, GNUTLS_X509_FMT_PEM);
else
r = gnutls_certificate_set_x509_trust_file(ctx->x509_cred, path, GNUTLS_X509_FMT_PEM);
if (r < 0)
{
ERR("ssl_ctx=%p could not use certificate authorities from '%s': %s", ctx, path, gnutls_strerror(r));
eina_stringshare_del(path);
*cfg.certificate_authorities = eina_list_remove_list(*cfg.certificate_authorities, n);
continue;
}
DBG("ssl_ctx=%p loaded certificate authorities '%s'", ctx, path);
}
if (certificate_authorities_count && !*cfg.certificate_authorities)
{
ERR("ssl_ctx=%p none of the required certificate authorities were loaded!", ctx);
return EINVAL;
}
return 0;
}
static void *
efl_net_ssl_ctx_connection_new(Efl_Net_Ssl_Ctx *ctx)
{
gnutls_session_t session;
int r;
r = gnutls_init(&session, ctx->is_dialer ? GNUTLS_CLIENT : GNUTLS_SERVER);
if (r < 0)
{
ERR("ssl_ctx=%p could not create %s session: %s",
ctx, ctx->is_dialer ? "dialer" : "server", gnutls_strerror(r));
return NULL;
}
if (!ctx->priority)
{
r = gnutls_set_default_priority(session);
if (r < 0)
{
ERR("ssl_ctx=%p could not set default cipher priority: %s", ctx, gnutls_strerror(r));
goto error;
}
}
else
{
r = gnutls_priority_set(session, ctx->priority);
if (r < 0)
{
ERR("ssl_ctx=%p could not set cipher priority: %s", ctx, gnutls_strerror(r));
goto error;
}
}
r = gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, ctx->x509_cred);
if (r < 0)
{
ERR("ssl_ctx=%p could not set session credentials: %s", ctx, gnutls_strerror(r));
goto error;
}
return session;
error:
gnutls_deinit(session);
return NULL;
}
static Eina_Error
efl_net_ssl_ctx_setup(Efl_Net_Ssl_Ctx *ctx, Efl_Net_Ssl_Ctx_Config cfg)
{
Eina_Error err;
const char *priority;
int r;
EINA_SAFETY_ON_TRUE_RETURN_VAL(ctx->x509_cred != NULL, EALREADY);
switch (cfg.cipher)
{
case EFL_NET_SSL_CIPHER_AUTO:
priority = NULL;
break;
case EFL_NET_SSL_CIPHER_SSLV3:
priority = "NORMAL:%VERIFY_ALLOW_X509_V1_CA_CRT:!VERS-TLS1.0:!VERS-TLS1.1:!VERS-TLS1.2";
break;
case EFL_NET_SSL_CIPHER_TLSV1:
priority = "NORMAL:%VERIFY_ALLOW_X509_V1_CA_CRT:!VERS-SSL3.0!VERS-TLS1.1:!VERS-TLS1.2";
break;
case EFL_NET_SSL_CIPHER_TLSV1_1:
priority = "NORMAL:%VERIFY_ALLOW_X509_V1_CA_CRT:!VERS-SSL3.0:!VERS-TLS1.0:!VERS-TLS1.2";
break;
case EFL_NET_SSL_CIPHER_TLSV1_2:
priority = "NORMAL:%VERIFY_ALLOW_X509_V1_CA_CRT:!VERS-SSL3.0:!VERS-TLS1.0:!VERS-TLS1.1";
break;
default:
ERR("ssl_ctx=%p unsupported cipher %d", ctx, cfg.cipher);
return EINVAL;
}
if (priority)
{
const char *err_pos = NULL;
r = gnutls_priority_init(&ctx->priority, priority, &err_pos);
if (r < 0)
{
size_t off = err_pos - priority;
if (r == GNUTLS_E_INVALID_REQUEST)
{
ERR("ssl_ctx=%p invalid syntax on GNUTLS priority string offset %zd: '%s'", ctx, off, priority);
return EINVAL;
}
ERR("ssl_ctx=%p could not set GNUTLS priority offset %zd '%s': %s", ctx, off, priority, gnutls_strerror(r));
return EINVAL;
}
}
r = gnutls_certificate_allocate_credentials(&ctx->x509_cred);
if (r < 0)
{
ERR("ssl_ctx=%p could not allocate X509 credentials: %s", ctx, gnutls_strerror(r));
err = ENOSYS;
goto err_cert_alloc;
}
err = _efl_net_ssl_ctx_load_lists(ctx, cfg);
if (err)
{
ERR("ssl_ctx=%p failed to load certificate, private keys, CRL or CA", ctx);
goto err_load;
}
return 0;
err_load:
gnutls_certificate_free_credentials(ctx->x509_cred);
ctx->x509_cred = NULL;
err_cert_alloc:
gnutls_priority_deinit(ctx->priority);
ctx->priority = NULL;
return err;
}
static void
efl_net_ssl_ctx_teardown(Efl_Net_Ssl_Ctx *ctx)
{
if (ctx->x509_cred)
{
gnutls_certificate_free_credentials(ctx->x509_cred);
ctx->x509_cred = NULL;
}
if (ctx->priority)
{
gnutls_priority_deinit(ctx->priority);
ctx->priority = NULL;
}
}
static Eina_Error
efl_net_ssl_ctx_verify_mode_set(Efl_Net_Ssl_Ctx *ctx EINA_UNUSED, Efl_Net_Ssl_Verify_Mode verify_mode EINA_UNUSED)
{
return 0;
}
static Eina_Error
efl_net_ssl_ctx_hostname_verify_set(Efl_Net_Ssl_Ctx *ctx EINA_UNUSED, Eina_Bool hostname_verify EINA_UNUSED)
{
return 0;
}
static Eina_Error
efl_net_ssl_ctx_hostname_set(Efl_Net_Ssl_Ctx *ctx EINA_UNUSED, const char *hostname EINA_UNUSED)
{
return 0;
}

View File

@ -0,0 +1,38 @@
struct _Efl_Net_Ssl_Ctx {
};
static void *
efl_net_ssl_ctx_connection_new(Efl_Net_Ssl_Ctx *ctx EINA_UNUSED)
{
return NULL;
}
static Eina_Error
efl_net_ssl_ctx_setup(Efl_Net_Ssl_Ctx *ctx EINA_UNUSED, Efl_Net_Ssl_Ctx_Config cfg EINA_UNUSED)
{
ERR("EFL compiled with --with-crypto=none");
return ENOSYS;
}
static void
efl_net_ssl_ctx_teardown(Efl_Net_Ssl_Ctx *ctx EINA_UNUSED)
{
}
static Eina_Error
efl_net_ssl_ctx_verify_mode_set(Efl_Net_Ssl_Ctx *ctx EINA_UNUSED, Efl_Net_Ssl_Verify_Mode verify_mode EINA_UNUSED)
{
return ENOSYS;
}
static Eina_Error
efl_net_ssl_ctx_hostname_verify_set(Efl_Net_Ssl_Ctx *ctx EINA_UNUSED, Eina_Bool hostname_verify EINA_UNUSED)
{
return ENOSYS;
}
static Eina_Error
efl_net_ssl_ctx_hostname_set(Efl_Net_Ssl_Ctx *ctx EINA_UNUSED, const char *hostname EINA_UNUSED)
{
return ENOSYS;
}

View File

@ -0,0 +1,387 @@
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/dh.h>
struct _Efl_Net_Ssl_Ctx
{
SSL_CTX *ssl_ctx;
Eina_Bool did_certificates;
Eina_Bool is_dialer;
};
#define EFL_NET_SSL_CONTEXT_CIPHERS "aRSA+HIGH:+kEDH:+kRSA:!kSRP:!kPSK:+3DES:!MD5"
#define _efl_net_ssl_ctx_check_errors() \
__efl_net_ssl_ctx_check_errors(__FILE__, __LINE__, __FUNCTION__)
static unsigned long
__efl_net_ssl_ctx_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_ctx_load_lists(Efl_Net_Ssl_Ctx *ctx, Efl_Net_Ssl_Ctx_Config cfg)
{
Eina_List *n, *n_next;
const char *path;
unsigned certificates_count = eina_list_count(*cfg.certificates);
unsigned private_keys_count = eina_list_count(*cfg.private_keys);
unsigned certificate_revogation_lists_count = eina_list_count(*cfg.certificate_revogation_lists);
unsigned certificate_authorities_count = eina_list_count(*cfg.certificate_authorities);
long err_ssl;
const char *err_file;
const char *err_data;
int err_line, err_flags;
X509_STORE *x509_store;
X509_LOOKUP *x509_lookup;
unsigned long x509_store_flags = X509_V_FLAG_TRUSTED_FIRST;
if (cfg.load_defaults)
{
if (SSL_CTX_set_default_verify_paths(ctx->ssl_ctx) != 1)
{
_efl_net_ssl_ctx_check_errors();
ERR("ssl_ctx=%p could not load default paths", ctx);
return ENOSYS;
}
DBG("ssl_ctx=%p loaded default paths", ctx);
}
else
DBG("ssl_ctx=%p did not load default paths", ctx);
EINA_LIST_FOREACH_SAFE(*cfg.certificates, n, n_next, path)
{
if ((SSL_CTX_use_certificate_file(ctx->ssl_ctx, path, SSL_FILETYPE_PEM) != 1) &&
(SSL_CTX_use_certificate_file(ctx->ssl_ctx, path, SSL_FILETYPE_ASN1) != 1))
{
err_ssl = ERR_peek_error_line_data(&err_file, &err_line, &err_data, &err_flags);
_efl_net_ssl_ctx_check_errors();
_efl_net_ssl_ctx_check_errors();
ERR("ssl_ctx=%p could not use certificate from %s [%s:%d%s%s '%s']",
ctx, path,
err_file, err_line,
(err_flags & ERR_TXT_STRING) ? " " : "",
(err_flags & ERR_TXT_STRING) ? err_data : "",
ERR_reason_error_string(err_ssl));
eina_stringshare_del(path);
*cfg.certificates = eina_list_remove_list(*cfg.certificates, n);
continue;
}
DBG("ssl_ctx=%p loaded certificate '%s'", ctx, path);
ctx->did_certificates = EINA_TRUE;
}
if (certificates_count && !*cfg.certificates)
{
ERR("ssl_ctx=%p none of the required certificates were loaded!", ctx);
return EINVAL;
}
EINA_LIST_FOREACH_SAFE(*cfg.private_keys, n, n_next, path)
{
if ((SSL_CTX_use_PrivateKey_file(ctx->ssl_ctx, path, SSL_FILETYPE_PEM) != 1) &&
(SSL_CTX_use_PrivateKey_file(ctx->ssl_ctx, path, SSL_FILETYPE_ASN1) != 1))
{
err_ssl = ERR_peek_error_line_data(&err_file, &err_line, &err_data, &err_flags);
_efl_net_ssl_ctx_check_errors();
ERR("ssl_ctx=%p could not use private key from %s [%s:%d%s%s '%s']",
ctx, path,
err_file, err_line,
(err_flags & ERR_TXT_STRING) ? " " : "",
(err_flags & ERR_TXT_STRING) ? err_data : "",
ERR_reason_error_string(err_ssl));
eina_stringshare_del(path);
*cfg.private_keys = eina_list_remove_list(*cfg.private_keys, n);
continue;
}
if (SSL_CTX_check_private_key(ctx->ssl_ctx) != 1)
{
err_ssl = ERR_peek_error_line_data(&err_file, &err_line, &err_data, &err_flags);
_efl_net_ssl_ctx_check_errors();
ERR("ssl_ctx=%p could not check private key from %s [%s:%d%s%s '%s']",
ctx, path,
err_file, err_line,
(err_flags & ERR_TXT_STRING) ? " " : "",
(err_flags & ERR_TXT_STRING) ? err_data : "",
ERR_reason_error_string(err_ssl));
continue;
}
DBG("ssl_ctx=%p loaded private key '%s'", ctx, path);
}
if (private_keys_count && !*cfg.private_keys)
{
ERR("ssl_ctx=%p none of the required private keys were loaded!", ctx);
return EINVAL;
}
x509_store = SSL_CTX_get_cert_store(ctx->ssl_ctx);
if (!x509_store)
{
_efl_net_ssl_ctx_check_errors();
ERR("ssl_ctx=%p SSL has no X509 certificate store", ctx);
return ENOSYS;
}
x509_lookup = X509_STORE_add_lookup(x509_store, X509_LOOKUP_file());
if (!x509_lookup)
{
_efl_net_ssl_ctx_check_errors();
ERR("ssl_ctx=%p could not add X509 file lookup", ctx);
return ENOSYS;
}
EINA_LIST_FOREACH_SAFE(*cfg.certificate_revogation_lists, n, n_next, path)
{
if ((X509_load_crl_file(x509_lookup, path, X509_FILETYPE_PEM) != 1) &&
(X509_load_crl_file(x509_lookup, path, X509_FILETYPE_ASN1) != 1))
{
err_ssl = ERR_peek_error_line_data(&err_file, &err_line, &err_data, &err_flags);
_efl_net_ssl_ctx_check_errors();
ERR("ssl_ctx=%p could not use certificate revogation lists from %s [%s:%d%s%s '%s']",
ctx, path,
err_file, err_line,
(err_flags & ERR_TXT_STRING) ? " " : "",
(err_flags & ERR_TXT_STRING) ? err_data : "",
ERR_reason_error_string(err_ssl));
eina_stringshare_del(path);
*cfg.certificate_revogation_lists = eina_list_remove_list(*cfg.certificate_revogation_lists, n);
continue;
}
DBG("ssl_ctx=%p loaded certificate revogation lists '%s'", ctx, path);
x509_store_flags |= X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL;
}
if (certificate_revogation_lists_count && !*cfg.certificate_revogation_lists)
{
ERR("ssl_ctx=%p none of the required certificate revogation lists were loaded!", ctx);
return EINVAL;
}
X509_STORE_set_flags(x509_store, x509_store_flags);
EINA_LIST_FOREACH_SAFE(*cfg.certificate_authorities, n, n_next, path)
{
struct stat st;
const char *cafile = NULL, *cadir = NULL;
if (stat(path, &st) != 0)
{
ERR("ssl_ctx=%p could not load certificate authorities from '%s': %s", ctx, path, strerror(errno));
eina_stringshare_del(path);
*cfg.certificate_authorities = eina_list_remove_list(*cfg.certificate_authorities, n);
continue;
}
else if (S_ISDIR(st.st_mode)) cadir = path;
else cafile = path;
if (SSL_CTX_load_verify_locations(ctx->ssl_ctx, cafile, cadir) != 1)
{
err_ssl = ERR_peek_error_line_data(&err_file, &err_line, &err_data, &err_flags);
_efl_net_ssl_ctx_check_errors();
ERR("ssl_ctx=%p could not use certificate authorities from %s [%s:%d%s%s '%s']",
ctx, path,
err_file, err_line,
(err_flags & ERR_TXT_STRING) ? " " : "",
(err_flags & ERR_TXT_STRING) ? err_data : "",
ERR_reason_error_string(err_ssl));
eina_stringshare_del(path);
*cfg.certificate_authorities = eina_list_remove_list(*cfg.certificate_authorities, n);
continue;
}
DBG("ssl_ctx=%p loaded certificate authorities '%s'", ctx, path);
}
if (certificate_authorities_count && !*cfg.certificate_authorities)
{
ERR("ssl_ctx=%p none of the required certificate authorities were loaded!", ctx);
return EINVAL;
}
if (!ctx->did_certificates)
{
if (!SSL_CTX_set_cipher_list(ctx->ssl_ctx, EFL_NET_SSL_CONTEXT_CIPHERS))
{
err_ssl = ERR_peek_error_line_data(&err_file, &err_line, &err_data, &err_flags);
_efl_net_ssl_ctx_check_errors();
ERR("ssl_ctx=%p Could not set ciphers '%s' [%s:%d%s%s '%s']",
ctx, EFL_NET_SSL_CONTEXT_CIPHERS,
err_file, err_line,
(err_flags & ERR_TXT_STRING) ? " " : "",
(err_flags & ERR_TXT_STRING) ? err_data : "",
ERR_reason_error_string(err_ssl));
return EINVAL;
}
}
return 0;
}
static void *
efl_net_ssl_ctx_connection_new(Efl_Net_Ssl_Ctx *ctx)
{
return SSL_new(ctx->ssl_ctx);
}
static Eina_Error
efl_net_ssl_ctx_setup(Efl_Net_Ssl_Ctx *ctx, Efl_Net_Ssl_Ctx_Config cfg)
{
Eina_Error err;
unsigned long options;
EINA_SAFETY_ON_TRUE_RETURN_VAL(ctx->ssl_ctx != NULL, EALREADY);
ctx->is_dialer = cfg.is_dialer;
if (ctx->is_dialer)
{
switch (cfg.cipher)
{
case EFL_NET_SSL_CIPHER_AUTO:
ctx->ssl_ctx = SSL_CTX_new(SSLv23_client_method());
break;
case EFL_NET_SSL_CIPHER_SSLV3:
#ifndef OPENSSL_NO_SSL3_METHOD
ctx->ssl_ctx = SSL_CTX_new(SSLv3_client_method());
#else
ERR("ssl_ctx=%p SSLv3 is disabled in your OpenSSL build", ctx);
#endif
break;
case EFL_NET_SSL_CIPHER_TLSV1:
ctx->ssl_ctx = SSL_CTX_new(TLSv1_client_method());
break;
case EFL_NET_SSL_CIPHER_TLSV1_1:
ctx->ssl_ctx = SSL_CTX_new(TLSv1_1_client_method());
break;
case EFL_NET_SSL_CIPHER_TLSV1_2:
ctx->ssl_ctx = SSL_CTX_new(TLSv1_2_client_method());
break;
default:
ERR("ssl_ctx=%p unsupported cipher %d", ctx, cfg.cipher);
return EINVAL;
}
EINA_SAFETY_ON_NULL_RETURN_VAL(ctx->ssl_ctx, ENOSYS);
}
else
{
switch (cfg.cipher)
{
case EFL_NET_SSL_CIPHER_AUTO:
ctx->ssl_ctx = SSL_CTX_new(SSLv23_server_method());
break;
case EFL_NET_SSL_CIPHER_SSLV3:
#ifndef OPENSSL_NO_SSL3_METHOD
ctx->ssl_ctx = SSL_CTX_new(SSLv3_server_method());
#else
ERR("ssl_ctx=%p SSLv3 is disabled in your OpenSSL build", ctx);
#endif
break;
case EFL_NET_SSL_CIPHER_TLSV1:
ctx->ssl_ctx = SSL_CTX_new(TLSv1_server_method());
break;
case EFL_NET_SSL_CIPHER_TLSV1_1:
ctx->ssl_ctx = SSL_CTX_new(TLSv1_1_server_method());
break;
case EFL_NET_SSL_CIPHER_TLSV1_2:
ctx->ssl_ctx = SSL_CTX_new(TLSv1_2_server_method());
break;
default:
ERR("ssl_ctx=%p unsupported cipher %d", ctx, cfg.cipher);
return EINVAL;
}
EINA_SAFETY_ON_NULL_RETURN_VAL(ctx->ssl_ctx, ENOSYS);
}
options = SSL_CTX_get_options(ctx->ssl_ctx);
options |= SSL_OP_NO_SSLv2;
options |= SSL_OP_SINGLE_DH_USE;
if (cfg.cipher != EFL_NET_SSL_CIPHER_SSLV3)
options |= SSL_OP_NO_SSLv3;
SSL_CTX_set_options(ctx->ssl_ctx, options);
err = _efl_net_ssl_ctx_load_lists(ctx, cfg);
if (err)
{
ERR("ssl_ctx=%p failed to load certificate, private keys, CRL or CA", ctx);
goto error;
}
return 0;
error:
SSL_CTX_free(ctx->ssl_ctx);
ctx->ssl_ctx = NULL;
return err;
}
static void
efl_net_ssl_ctx_teardown(Efl_Net_Ssl_Ctx *ctx)
{
if (ctx->ssl_ctx)
{
SSL_CTX_free(ctx->ssl_ctx);
ctx->ssl_ctx = NULL;
}
}
static Eina_Error
efl_net_ssl_ctx_verify_mode_set(Efl_Net_Ssl_Ctx *ctx, Efl_Net_Ssl_Verify_Mode verify_mode)
{
int ssl_mode;
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_CTX_set_verify(ctx->ssl_ctx, ssl_mode, SSL_CTX_get_verify_callback(ctx->ssl_ctx));
return 0;
}
static Eina_Error
efl_net_ssl_ctx_hostname_verify_set(Efl_Net_Ssl_Ctx *ctx EINA_UNUSED, Eina_Bool hostname_verify EINA_UNUSED)
{
return 0;
}
static Eina_Error
efl_net_ssl_ctx_hostname_set(Efl_Net_Ssl_Ctx *ctx EINA_UNUSED, const char *hostname EINA_UNUSED)
{
return 0;
}

View File

@ -0,0 +1,26 @@
enum Efl.Net.Ssl.Verify_Mode {
[[Defines how remote peers should be verified.
@since 1.19
]]
none, [[Do not verify peer]]
optional, [[If provided, verify. Otherwise proceed]]
required, [[Always verify and fail if certificate wasn't provided]]
}
enum Efl.Net.Ssl.Cipher {
[[Defines the SSL/TLS version to use.
Prefer 'auto' or one of the TLS variants.
\@note since it's very insecure, SSLv2 is not present. SSLv3
support depends on being available on the platform.
@since 1.19
]]
auto, [[The default. Use the best your system supports, disables dangerous ciphers]]
sslv3, [[SSLv3, insecure and unsupported - DANGEROUS]]
tlsv1, [[TLSv1, secure and widely available]]
tlsv1_1, [[TLSv1.1, secure]]
tlsv1_2, [[TLSv1.2, secure]]
}