forked from enlightenment/efl
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:
parent
9a13816fb3
commit
f4198f022a
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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"
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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]]
|
||||
}
|
Loading…
Reference in New Issue