diff --git a/NEWS b/NEWS index a76771e7..a6e4240a 100644 --- a/NEWS +++ b/NEWS @@ -17,6 +17,10 @@ v0.8.21-head 2016-xx-xx The Irssi team + autolog_ignore_targets and activity_hide_targets learn a new syntax tag/* and * to ignore whole networks or everything. + /hilight got a -matchcase flag to hilight case sensitively (#421). + + TLS: Rename SSL to TLS throughout the code and command interfaces. + + TLS: Automatically connect using TLS for known IRC networks that supports encrypted connections. + + TLS: OpenSSL is now a required dependency. + + TLS: Add certificate and public key pinning support. - IP addresses are no longer stored when resolve_reverse_lookup is used. - /names and $[...] now uses utf8 string operations (#40, #411). diff --git a/configure.ac b/configure.ac index ee03eb82..629dd590 100644 --- a/configure.ac +++ b/configure.ac @@ -153,14 +153,6 @@ AC_ARG_ENABLE(true-color, fi, want_truecolor=no) -dnl ** -dnl ** SSL Library checks (OpenSSL) -dnl ** - -AC_ARG_ENABLE(ssl, -[ --disable-ssl Disable Secure Sockets Layer support],, - enable_ssl=yes) - dnl ** dnl ** just some generic stuff... dnl ** @@ -238,6 +230,11 @@ if test "x$want_socks" = "xyes"; then ]) fi +dnl ** +dnl ** OpenSSL checks +dnl ** +AC_CHECK_LIB([ssl], [SSL_library_init]) + dnl ** dnl ** fe-text checks dnl ** @@ -279,27 +276,7 @@ if test -z "$GLIB_LIBS"; then AC_ERROR([GLIB is required to build irssi.]) fi -LIBS="$LIBS $GLIB_LIBS" - -have_openssl=no -if test "$enable_ssl" = "yes"; then - PKG_CHECK_MODULES(SSL, openssl, :, :) - if test "$SSL_LIBS"; then - CFLAGS="$CFLAGS $SSL_CFLAGS" - have_openssl=yes - else - AC_CHECK_LIB(ssl, SSL_read, [ - AC_CHECK_HEADERS(openssl/ssl.h openssl/err.h, [ - SSL_LIBS="-lssl -lcrypto" - have_openssl=yes - ]) - ],, -lcrypto) - fi - if test "$have_openssl" = "yes"; then - AC_DEFINE(HAVE_OPENSSL,, Build with OpenSSL support) - LIBS="$LIBS $SSL_LIBS" - fi -fi +LIBS="$LIBS $GLIB_LIBS -lssl -lcrypto" dnl ** dnl ** curses checks @@ -569,12 +546,6 @@ if test "x$want_dane" = "xyes"; then AC_DEFINE([HAVE_DANE], [], [DANE support]) have_dane=yes ], [], [-lssl -lcrypto -lsres -lpthread]) - - if test x$have_dane = "xyes" ; then - if test x$have_openssl = "xno" ; then - AC_ERROR([SSL is required to build Irssi with DANE support enabled.]) - fi - fi fi if test "x$want_truecolor" = "xyes"; then @@ -695,16 +666,6 @@ echo "Install prefix ................... : $prefix" echo -echo "Building with SSL support ........ : $have_openssl" -if test "x$have_openssl" = "xno" -a "x$enable_ssl" = "xyes"; then - if test -f /etc/debian_version; then - echo " - Try: sudo apt-get install libssl-dev" - elif test -f /etc/redhat-release; then - echo " - Try installing openssl-devel" - else - echo " - Try installing OpenSSL development headers" - fi -fi echo "Building with 64bit DCC support .. : $offt_64bit" echo "Building with DANE support ....... : $have_dane" echo "Building with true color support.. : $want_truecolor" diff --git a/docs/help/in/connect.in b/docs/help/in/connect.in index df50d1b9..e861ad74 100644 --- a/docs/help/in/connect.in +++ b/docs/help/in/connect.in @@ -5,23 +5,24 @@ %9Parameters:%9 - -4: Connects using IPv4. - -6: Connects using IPv6. - -ssl: Connects using SSL encryption. - -ssl_cert: The SSL client certificate file. - -ssl_pkey: The SSL client private key, if not included in the - certificate file. - -ssl_pass: The password for the SSL client private key or certificate. - -ssl_verify: Verifies the SSL certificate of the server. - -ssl_cafile: The file with the list of CA certificates. - -ssl_capath: The directory which contains the CA certificates. - -ssl_ciphers: SSL cipher suite preference lists. - -noproxy: Ignores the global proxy configuration. - -network: The network this connection belongs to. - -host: The hostname you would like to connect from. - -rawlog: Immediately open rawlog after connecting. - -!: Doesn't autojoin channels. - -noautosendcmd: Doesn't execute autosendcmd. + -4: Connects using IPv4. + -6: Connects using IPv6. + -tls: Connects using TLS encryption. + -tls_cert: The TLS client certificate file. + -tls_pkey: The TLS client private key, if not included in the certificate file. + -tls_pass: The password for the TLS client private key or certificate. + -tls_verify: Verifies the TLS certificate of the server. + -tls_cafile: The file with the list of CA certificates. + -tls_capath: The directory which contains the CA certificates. + -tls_ciphers: TLS cipher suite preference lists. + -tls_pinned_cert: Pinned x509 certificate fingerprint. + -tls_pinned_pubkey: Pinned public key fingerprint. + -noproxy: Ignores the global proxy configuration. + -network: The network this connection belongs to. + -host: The hostname you would like to connect from. + -rawlog: Immediately open rawlog after connecting. + -!: Doesn't autojoin channels. + -noautosendcmd: Doesn't execute autosendcmd. A network or server to connect to; you can optionally specify a custom port, password and nickname. diff --git a/docs/help/in/server.in b/docs/help/in/server.in index 68a62e2d..60870111 100644 --- a/docs/help/in/server.in +++ b/docs/help/in/server.in @@ -5,45 +5,47 @@ %9Parameters:%9 - LIST: Displays the list of servers you are connected to. - CONNECT: Connects to the given server. - ADD: Adds a server to your configuration. - MODIFY: Modifies a server in your configuration. - REMOVE: Removes a server from your configuration. - PURGE: Purges the commands queued to be sent to the server. + LIST: Displays the list of servers you are connected to. + CONNECT: Connects to the given server. + ADD: Adds a server to your configuration. + MODIFY: Modifies a server in your configuration. + REMOVE: Removes a server from your configuration. + PURGE: Purges the commands queued to be sent to the server. - -!: Doesn't autojoin the channels. - -4: Connects using IPv4. - -6: Connects using IPv6. - -ssl: Connects using SSL encryption. - -ssl_cert: The SSL client certificate file. - -ssl_pkey: The SSL client private key, if not included in the - certificate file. - -ssl_pass: The password for the SSL client private key or certificate. - -ssl_verify: Verifies the SSL certificate of the server. - -ssl_cafile: The file with the list of CA certificates. - -ssl_capath: The directory which contains the CA certificates. - -ssl_ciphers: SSL cipher suite preference lists. - -auto: Automatically connects to the server on startup. - -noauto: Doesn't connect to the server on startup. - -network: The network the server belongs to. - -host: The hostname you would like to connect from. - -cmdspeed: Specifies the minimum amount of time, expressed in - milliseconds, that the client must wait before sending - additional commands to the server. - -cmdmax: Specifies the maximum number of commands to perform - before starting the internal flood protection. - -port: Specifies the port to connect to the server. - -noproxy: Ignores the global proxy configuration. - -rawlog: Immediately open rawlog after connecting. - -noautosendcmd: Doesn't execute autosendcmd. + -!: Doesn't autojoin the channels. + -4: Connects using IPv4. + -6: Connects using IPv6. + -tls: Connects using TLS encryption. + -tls_cert: The TLS client certificate file. + -tls_pkey: The TLS client private key, if not included in the + certificate file. + -tls_pass: The password for the TLS client private key or certificate. + -tls_verify: Verifies the TLS certificate of the server. + -tls_cafile: The file with the list of CA certificates. + -tls_capath: The directory which contains the CA certificates. + -tls_ciphers: TLS cipher suite preference lists. + -tls_pinned_cert: Pinned x509 certificate fingerprint. + -tls_pinned_pubkey: Pinned public key fingerprint. + -auto: Automatically connects to the server on startup. + -noauto: Doesn't connect to the server on startup. + -network: The network the server belongs to. + -host: The hostname you would like to connect from. + -cmdspeed: Specifies the minimum amount of time, expressed in + milliseconds, that the client must wait before sending + additional commands to the server. + -cmdmax: Specifies the maximum number of commands to perform + before starting the internal flood protection. + -port: Specifies the port to connect to the server. + -noproxy: Ignores the global proxy configuration. + -rawlog: Immediately open rawlog after connecting. + -noautosendcmd: Doesn't execute autosendcmd. The server, port and network to add, modify or remove; if no argument is given, the list of servers you are connected to will be returned. %9Description:%9 - Displays, adds, modifies or removes the network configuration of IRC + Displays, adds, modifies or removes the network configuration of IRC servers. When using the ADD parameter on a server that already exists, the diff --git a/docs/signals.txt b/docs/signals.txt index 5e03d901..47db3575 100644 --- a/docs/signals.txt +++ b/docs/signals.txt @@ -59,6 +59,7 @@ network-openssl.c: "tlsa available", SERVER_REC "tlsa verification success", SERVER_REC "tlsa verification failed", SERVER_REC + "tls handshake finished", SERVER_REC, TLS_REC nicklist.c: "nicklist new", CHANNEL_REC, NICK_REC diff --git a/irssi.conf b/irssi.conf index ba159f34..f19b0823 100644 --- a/irssi.conf +++ b/irssi.conf @@ -2,13 +2,13 @@ servers = ( { address = "irc.dal.net"; chatnet = "DALnet"; port = "6667"; }, { address = "irc.efnet.org"; chatnet = "EFNet"; port = "6667"; }, { address = "irc.esper.net"; chatnet = "EsperNet"; port = "6667"; }, - { address = "chat.freenode.net"; chatnet = "Freenode"; port = "6667"; }, + { address = "chat.freenode.net"; chatnet = "Freenode"; port = "6697"; use_tls = "yes"; tls_verify = "yes"; }, { address = "irc.gamesurge.net"; chatnet = "GameSurge"; port = "6667"; }, - { address = "eu.irc6.net"; chatnet = "IRCnet"; port = "6667"; }, + { address = "eu.irc6.net"; chatnet = "IRCnet"; port = "6667"; use_tls = "yes"; }, { address = "open.ircnet.net"; chatnet = "IRCnet"; port = "6667"; }, { address = "irc.ircsource.net"; chatnet = "IRCSource"; port = "6667"; }, { address = "irc.netfuze.net"; chatnet = "NetFuze"; port = "6667"; }, - { address = "irc.oftc.net"; chatnet = "OFTC"; port = "6667"; }, + { address = "irc.oftc.net"; chatnet = "OFTC"; port = "6697"; use_tls = "yes"; }, { address = "irc.quakenet.org"; chatnet = "QuakeNet"; port = "6667"; }, { address = "irc.rizon.net"; chatnet = "Rizon"; port = "6667"; }, { address = "silc.silcnet.org"; chatnet = "SILC"; port = "706"; }, @@ -21,7 +21,7 @@ chatnets = { max_kicks = "4"; max_msgs = "20"; max_whois = "30"; - }; + }; EFNet = { type = "IRC"; max_kicks = "1"; @@ -64,7 +64,7 @@ chatnets = { max_msgs = "1"; max_whois = "1"; }; - OFTC = { + OFTC = { type = "IRC"; max_kicks = "1"; max_msgs = "1"; @@ -264,7 +264,7 @@ aliases = { statusbar = { items = { - + barstart = "{sbstart}"; barend = "{sbend}"; @@ -276,10 +276,10 @@ statusbar = { window = "{sb $winref:$tag/$itemname{sbmode $M}}"; window_empty = "{sb $winref{sbservertag $tag}}"; - + prompt = "{prompt $[.15]itemname}"; prompt_empty = "{prompt $winname}"; - + topic = " $topic"; topic_empty = " Irssi v$J - http://www.irssi.org"; @@ -328,7 +328,7 @@ statusbar = { }; prompt = { - + type = "root"; placement = "bottom"; position = "100"; diff --git a/src/core/Makefile.am b/src/core/Makefile.am index af323234..846c4889 100644 --- a/src/core/Makefile.am +++ b/src/core/Makefile.am @@ -17,8 +17,8 @@ libcore_a_SOURCES = \ chatnets.c \ core.c \ expandos.c \ - ignore.c \ - levels.c \ + ignore.c \ + levels.c \ line-split.c \ log.c \ log-away.c \ @@ -46,6 +46,7 @@ libcore_a_SOURCES = \ special-vars.c \ utf8.c \ wcwidth.c \ + tls.c \ write-buffer.c structure_headers = \ @@ -68,8 +69,8 @@ pkginc_core_HEADERS = \ chatnets.h \ core.h \ expandos.h \ - ignore.h \ - levels.h \ + ignore.h \ + levels.h \ line-split.h \ log.h \ masks.h \ @@ -97,5 +98,6 @@ pkginc_core_HEADERS = \ special-vars.h \ utf8.h \ window-item-def.h \ + tls.h \ write-buffer.h \ $(structure_headers) diff --git a/src/core/chat-commands.c b/src/core/chat-commands.c index a9404fa3..0f40869b 100644 --- a/src/core/chat-commands.c +++ b/src/core/chat-commands.c @@ -99,27 +99,31 @@ static SERVER_CONNECT_REC *get_server_connect(const char *data, int *plus_addr, else if (g_hash_table_lookup(optlist, "4") != NULL) conn->family = AF_INET; - if (g_hash_table_lookup(optlist, "ssl") != NULL) - conn->use_ssl = TRUE; - if ((tmp = g_hash_table_lookup(optlist, "ssl_cert")) != NULL) - conn->ssl_cert = g_strdup(tmp); - if ((tmp = g_hash_table_lookup(optlist, "ssl_pkey")) != NULL) - conn->ssl_pkey = g_strdup(tmp); - if ((tmp = g_hash_table_lookup(optlist, "ssl_pass")) != NULL) - conn->ssl_pass = g_strdup(tmp); - if (g_hash_table_lookup(optlist, "ssl_verify") != NULL) - conn->ssl_verify = TRUE; - if ((tmp = g_hash_table_lookup(optlist, "ssl_cafile")) != NULL) - conn->ssl_cafile = g_strdup(tmp); - if ((tmp = g_hash_table_lookup(optlist, "ssl_capath")) != NULL) - conn->ssl_capath = g_strdup(tmp); - if ((tmp = g_hash_table_lookup(optlist, "ssl_ciphers")) != NULL) - conn->ssl_ciphers = g_strdup(tmp); - if ((conn->ssl_capath != NULL && conn->ssl_capath[0] != '\0') - || (conn->ssl_cafile != NULL && conn->ssl_cafile[0] != '\0')) - conn->ssl_verify = TRUE; - if ((conn->ssl_cert != NULL && conn->ssl_cert[0] != '\0') || conn->ssl_verify) - conn->use_ssl = TRUE; + if (g_hash_table_lookup(optlist, "tls") != NULL || g_hash_table_lookup(optlist, "ssl") != NULL) + conn->use_tls = TRUE; + if ((tmp = g_hash_table_lookup(optlist, "tls_cert")) != NULL || (tmp = g_hash_table_lookup(optlist, "ssl_cert")) != NULL) + conn->tls_cert = g_strdup(tmp); + if ((tmp = g_hash_table_lookup(optlist, "tls_pkey")) != NULL || (tmp = g_hash_table_lookup(optlist, "ssl_pkey")) != NULL) + conn->tls_pkey = g_strdup(tmp); + if ((tmp = g_hash_table_lookup(optlist, "tls_pass")) != NULL || (tmp = g_hash_table_lookup(optlist, "ssl_pass")) != NULL) + conn->tls_pass = g_strdup(tmp); + if (g_hash_table_lookup(optlist, "tls_verify") != NULL || g_hash_table_lookup(optlist, "ssl_verify") != NULL) + conn->tls_verify = TRUE; + if ((tmp = g_hash_table_lookup(optlist, "tls_cafile")) != NULL || (tmp = g_hash_table_lookup(optlist, "ssl_cafile")) != NULL) + conn->tls_cafile = g_strdup(tmp); + if ((tmp = g_hash_table_lookup(optlist, "tls_capath")) != NULL || (tmp = g_hash_table_lookup(optlist, "ssl_capath")) != NULL) + conn->tls_capath = g_strdup(tmp); + if ((tmp = g_hash_table_lookup(optlist, "tls_ciphers")) != NULL || (tmp = g_hash_table_lookup(optlist, "ssl_ciphers")) != NULL) + conn->tls_ciphers = g_strdup(tmp); + if ((tmp = g_hash_table_lookup(optlist, "tls_pinned_cert")) != NULL || (tmp = g_hash_table_lookup(optlist, "ssl_pinned_cert")) != NULL) + conn->tls_pinned_cert = g_strdup(tmp); + if ((tmp = g_hash_table_lookup(optlist, "tls_pinned_pubkey")) != NULL || (tmp = g_hash_table_lookup(optlist, "ssl_pinned_pubkey")) != NULL) + conn->tls_pinned_pubkey = g_strdup(tmp); + if ((conn->tls_capath != NULL && conn->tls_capath[0] != '\0') + || (conn->tls_cafile != NULL && conn->tls_cafile[0] != '\0')) + conn->tls_verify = TRUE; + if ((conn->tls_cert != NULL && conn->tls_cert[0] != '\0') || conn->tls_verify) + conn->use_tls = TRUE; if (g_hash_table_lookup(optlist, "!") != NULL) conn->no_autojoin_channels = TRUE; @@ -145,13 +149,13 @@ static SERVER_CONNECT_REC *get_server_connect(const char *data, int *plus_addr, return conn; } -/* SYNTAX: CONNECT [-4 | -6] [-ssl] [-ssl_cert ] [-ssl_pkey ] [-ssl_pass ] - [-ssl_verify] [-ssl_cafile ] [-ssl_capath ] - [-ssl_ciphers ] +/* SYNTAX: CONNECT [-4 | -6] + [-ssl] [-ssl_cert ] [-ssl_pkey ] [-ssl_pass ] + [-ssl_verify] [-ssl_cafile ] [-ssl_capath ] [-ssl_ciphers ] [-!] [-noautosendcmd] - [-noproxy] [-network ] [-host ] - [-rawlog ] -
| [ [ []]] */ + [-noproxy] [-network ] [-host ] + [-rawlog ] +
| [ [ []]] */ /* NOTE: -network replaces the old -ircnet flag. */ static void cmd_connect(const char *data) { @@ -494,7 +498,7 @@ void chat_commands_init(void) signal_add("default command server", (SIGNAL_FUNC) sig_default_command_server); signal_add("server sendmsg", (SIGNAL_FUNC) sig_server_sendmsg); - command_set_options("connect", "4 6 !! -network ssl +ssl_cert +ssl_pkey +ssl_pass ssl_verify +ssl_cafile +ssl_capath +ssl_ciphers +host noproxy -rawlog noautosendcmd"); + command_set_options("connect", "4 6 !! -network ssl +ssl_cert +ssl_pkey +ssl_pass ssl_verify +ssl_cafile +ssl_capath +ssl_ciphers +ssl_pinned_cert +ssl_pinned_pubkey tls +tls_cert +tls_pkey +tls_pass tls_verify +tls_cafile +tls_capath +tls_ciphers +tls_pinned_cert +tls_pinned_pubkey +host noproxy -rawlog noautosendcmd"); command_set_options("msg", "channel nick"); } diff --git a/src/core/misc.c b/src/core/misc.c index bc9f504e..38c82989 100644 --- a/src/core/misc.c +++ b/src/core/misc.c @@ -930,3 +930,23 @@ char **strsplit_len(const char *str, int len, gboolean onspace) return ret; } + +char *binary_to_hex(unsigned char *buffer, size_t size) +{ + static const char hex[] = "0123456789ABCDEF"; + char *result = NULL; + int i; + + if (buffer == NULL || size == 0) + return NULL; + + result = g_malloc(2 * size + size); + + for (i = 0; i < size; i++) { + result[i * 3 + 0] = hex[(buffer[i] >> 4) & 0xf]; + result[i * 3 + 1] = hex[(buffer[i] >> 0) & 0xf]; + result[i * 3 + 2] = i == size - 1 ? '\0' : ':'; + } + + return result; +} diff --git a/src/core/misc.h b/src/core/misc.h index c095e131..2e1243cd 100644 --- a/src/core/misc.h +++ b/src/core/misc.h @@ -109,4 +109,7 @@ int find_substr(const char *list, const char *item); /* split `str' into `len' sized substrings */ char **strsplit_len(const char *str, int len, gboolean onspace); +/* Convert a given buffer to hex and return a pointer to the newly allocated hex buffer */ +char *binary_to_hex(unsigned char *buffer, size_t size); + #endif diff --git a/src/core/network-openssl.c b/src/core/network-openssl.c index a18e6fc7..09939fab 100644 --- a/src/core/network-openssl.c +++ b/src/core/network-openssl.c @@ -22,8 +22,8 @@ #include "network.h" #include "misc.h" #include "servers.h" - -#ifdef HAVE_OPENSSL +#include "signals.h" +#include "tls.h" #include #include @@ -203,9 +203,10 @@ static gboolean irssi_ssl_verify_hostname(X509 *cert, const char *hostname) return matched; } -static gboolean irssi_ssl_verify(SSL *ssl, SSL_CTX *ctx, const char* hostname, int port, X509 *cert, SERVER_REC *server) +static gboolean irssi_ssl_verify(SSL *ssl, SSL_CTX *ctx, const char* hostname, int port, X509 *cert, SERVER_REC *server, TLS_REC *tls_rec) { long result; + #ifdef HAVE_DANE int dane_ret; struct val_daneparams daneparams; @@ -242,39 +243,7 @@ static gboolean irssi_ssl_verify(SSL *ssl, SSL_CTX *ctx, const char* hostname, i result = SSL_get_verify_result(ssl); if (result != X509_V_OK) { - unsigned char md[EVP_MAX_MD_SIZE]; - unsigned int n; - char *str; - - g_warning("Could not verify SSL servers certificate: %s", - X509_verify_cert_error_string(result)); - if ((str = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0)) == NULL) - g_warning(" Could not get subject-name from peer certificate"); - else { - g_warning(" Subject : %s", str); - free(str); - } - if ((str = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0)) == NULL) - g_warning(" Could not get issuer-name from peer certificate"); - else { - g_warning(" Issuer : %s", str); - free(str); - } - if (! X509_digest(cert, EVP_md5(), md, &n)) - g_warning(" Could not get fingerprint from peer certificate"); - else { - char hex[] = "0123456789ABCDEF"; - char fp[EVP_MAX_MD_SIZE*3]; - if (n < sizeof(fp)) { - unsigned int i; - for (i = 0; i < n; i++) { - fp[i*3+0] = hex[(md[i] >> 4) & 0xF]; - fp[i*3+1] = hex[(md[i] >> 0) & 0xF]; - fp[i*3+2] = i == n - 1 ? '\0' : ':'; - } - g_warning(" MD5 Fingerprint : %s", fp); - } - } + g_warning("Could not verify SSL servers certificate: %s", X509_verify_cert_error_string(result)); return FALSE; } else if (! irssi_ssl_verify_hostname(cert, hostname)){ return FALSE; @@ -428,7 +397,6 @@ static gboolean irssi_ssl_init(void) ssl_inited = TRUE; return TRUE; - } static int get_pem_password_callback(char *buffer, int max_length, int rwflag, void *pass) @@ -457,13 +425,13 @@ static GIOChannel *irssi_ssl_get_iochannel(GIOChannel *handle, int port, SERVER_ SSL *ssl; SSL_CTX *ctx = NULL; - const char *mycert = server->connrec->ssl_cert; - const char *mypkey = server->connrec->ssl_pkey; - const char *mypass = server->connrec->ssl_pass; - const char *cafile = server->connrec->ssl_cafile; - const char *capath = server->connrec->ssl_capath; - const char *ciphers = server->connrec->ssl_ciphers; - gboolean verify = server->connrec->ssl_verify; + const char *mycert = server->connrec->tls_cert; + const char *mypkey = server->connrec->tls_pkey; + const char *mypass = server->connrec->tls_pass; + const char *cafile = server->connrec->tls_cafile; + const char *capath = server->connrec->tls_capath; + const char *ciphers = server->connrec->tls_ciphers; + gboolean verify = server->connrec->tls_verify; g_return_val_if_fail(handle != NULL, NULL); @@ -482,7 +450,8 @@ static GIOChannel *irssi_ssl_get_iochannel(GIOChannel *handle, int port, SERVER_ SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3); SSL_CTX_set_default_passwd_cb(ctx, get_pem_password_callback); SSL_CTX_set_default_passwd_cb_userdata(ctx, (void *)mypass); - if (ciphers && *ciphers) { + + if (ciphers != NULL && ciphers[0] != '\0') { if (SSL_CTX_set_cipher_list(ctx, ciphers) != 1) g_warning("No valid SSL cipher suite could be selected"); } @@ -544,8 +513,7 @@ static GIOChannel *irssi_ssl_get_iochannel(GIOChannel *handle, int port, SERVER_ SSL_set_tlsext_host_name(ssl, server->connrec->address); #endif - SSL_set_mode(ssl, SSL_MODE_ENABLE_PARTIAL_WRITE | - SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); + SSL_set_mode(ssl, SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); chan = g_new0(GIOSSLChannel, 1); chan->fd = fd; @@ -565,6 +533,192 @@ static GIOChannel *irssi_ssl_get_iochannel(GIOChannel *handle, int port, SERVER_ return gchan; } +static void set_cipher_info(TLS_REC *tls, SSL *ssl) +{ + g_assert(tls != NULL); + g_assert(ssl != NULL); + + tls_rec_set_protocol_version(tls, SSL_get_version(ssl)); + + tls_rec_set_cipher(tls, SSL_CIPHER_get_name(SSL_get_current_cipher(ssl))); + tls_rec_set_cipher_size(tls, SSL_get_cipher_bits(ssl, NULL)); +} + +static void set_pubkey_info(TLS_REC *tls, X509 *cert, unsigned char *cert_fingerprint, size_t cert_fingerprint_size, unsigned char *public_key_fingerprint, size_t public_key_fingerprint_size) +{ + g_assert(tls != NULL); + g_assert(cert != NULL); + + EVP_PKEY *pubkey = NULL; + char *cert_fingerprint_hex = NULL; + char *public_key_fingerprint_hex = NULL; + + BIO *bio = NULL; + char buffer[128]; + size_t length; + + pubkey = X509_get_pubkey(cert); + + cert_fingerprint_hex = binary_to_hex(cert_fingerprint, cert_fingerprint_size); + tls_rec_set_certificate_fingerprint(tls, cert_fingerprint_hex); + tls_rec_set_certificate_fingerprint_algorithm(tls, "SHA256"); + + // Show algorithm. + switch (EVP_PKEY_id(pubkey)) { + case EVP_PKEY_RSA: + tls_rec_set_public_key_algorithm(tls, "RSA"); + break; + + case EVP_PKEY_DSA: + tls_rec_set_public_key_algorithm(tls, "DSA"); + break; + + case EVP_PKEY_EC: + tls_rec_set_public_key_algorithm(tls, "EC"); + break; + + default: + tls_rec_set_public_key_algorithm(tls, "Unknown"); + break; + } + + public_key_fingerprint_hex = binary_to_hex(public_key_fingerprint, public_key_fingerprint_size); + tls_rec_set_public_key_fingerprint(tls, public_key_fingerprint_hex); + tls_rec_set_public_key_size(tls, EVP_PKEY_bits(pubkey)); + tls_rec_set_public_key_fingerprint_algorithm(tls, "SHA256"); + + // Read the NotBefore timestamp. + bio = BIO_new(BIO_s_mem()); + ASN1_TIME_print(bio, X509_get_notBefore(cert)); + length = BIO_read(bio, buffer, sizeof(buffer)); + buffer[length] = '\0'; + BIO_free(bio); + tls_rec_set_not_before(tls, buffer); + + // Read the NotAfter timestamp. + bio = BIO_new(BIO_s_mem()); + ASN1_TIME_print(bio, X509_get_notAfter(cert)); + length = BIO_read(bio, buffer, sizeof(buffer)); + buffer[length] = '\0'; + BIO_free(bio); + tls_rec_set_not_after(tls, buffer); + + g_free(cert_fingerprint_hex); + g_free(public_key_fingerprint_hex); + EVP_PKEY_free(pubkey); +} + +static void set_peer_cert_chain_info(TLS_REC *tls, SSL *ssl) +{ + g_assert(tls != NULL); + g_assert(ssl != NULL); + + STACK_OF(X509) *chain = NULL; + int i; + + chain = SSL_get_peer_cert_chain(ssl); + + if (chain == NULL) + return; + + for (i = 0; i < sk_X509_num(chain); i++) { + TLS_CERT_REC *cert_rec = NULL; + X509_NAME *name = NULL; + + int j; + int nid; + + char *key = NULL; + char *value = NULL; + + cert_rec = tls_cert_create_rec(); + + // Subject. + name = X509_get_subject_name(sk_X509_value(chain, i)); + + for (j = 0; j < X509_NAME_entry_count(name); j++) { + X509_NAME_ENTRY *entry = NULL; + TLS_CERT_ENTRY_REC *tls_cert_entry_rec = NULL; + + entry = X509_NAME_get_entry(name, j); + + nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(entry)); + key = (char *)OBJ_nid2sn(nid); + + if (key == NULL) + key = (char *)OBJ_nid2ln(nid); + + ASN1_STRING *d = X509_NAME_ENTRY_get_data(entry); + value = (char *)ASN1_STRING_data(d); + + tls_cert_entry_rec = tls_cert_entry_create_rec(key, value); + tls_cert_rec_append_subject_entry(cert_rec, tls_cert_entry_rec); + } + + // Issuer. + name = X509_get_issuer_name(sk_X509_value(chain, i)); + + for (j = 0; j < X509_NAME_entry_count(name); j++) { + X509_NAME_ENTRY *entry = NULL; + TLS_CERT_ENTRY_REC *tls_cert_entry_rec = NULL; + + entry = X509_NAME_get_entry(name, j); + + nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(entry)); + key = (char *)OBJ_nid2sn(nid); + + if (key == NULL) + key = (char *)OBJ_nid2ln(nid); + + ASN1_STRING *d = X509_NAME_ENTRY_get_data(entry); + value = (char *)ASN1_STRING_data(d); + + tls_cert_entry_rec = tls_cert_entry_create_rec(key, value); + tls_cert_rec_append_issuer_entry(cert_rec, tls_cert_entry_rec); + } + + tls_rec_append_cert(tls, cert_rec); + } +} + +static void set_server_temporary_key_info(TLS_REC *tls, SSL *ssl) +{ + g_assert(tls != NULL); + g_assert(ssl != NULL); + +#ifdef SSL_get_server_tmp_key + // Show ephemeral key information. + EVP_PKEY *ephemeral_key = NULL; + + if (SSL_get_server_tmp_key(ssl, &ephemeral_key)) { + switch (EVP_PKEY_id(ephemeral_key)) { + case EVP_PKEY_DH: + tls_rec_set_ephemeral_key_algorithm(tls, "DH"); + tls_rec_set_ephemeral_key_size(tls, EVP_PKEY_bits(ephemeral_key)); + break; + + case EVP_PKEY_EC: + { + EC_KEY *ec = EVP_PKEY_get1_EC_KEY(ephemeral_key); + int nid; + const char *cname; + nid = EC_GROUP_get_curve_name(EC_KEY_get0_group(ec)); + EC_KEY_free(ec); + cname = OBJ_nid2sn(nid); + char *ephemeral_key_algorithm = g_strdup_printf("ECDH: %s", cname); + + tls_rec_set_ephemeral_key_algorithm(tls, ephemeral_key_algorithm); + tls_rec_set_ephemeral_key_size(tls, EVP_PKEY_bits(ephemeral_key)); + + g_free_and_null(ephemeral_key_algorithm); + } + } + + EVP_PKEY_free(ephemeral_key); + } +#endif // SSL_get_server_tmp_key. +} + GIOChannel *net_connect_ip_ssl(IPADDR *ip, int port, IPADDR *my_ip, SERVER_REC *server) { GIOChannel *handle, *ssl_handle; @@ -582,8 +736,19 @@ int irssi_ssl_handshake(GIOChannel *handle) { GIOSSLChannel *chan = (GIOSSLChannel *)handle; int ret, err; - X509 *cert; - const char *errstr; + const char *errstr = NULL; + X509 *cert = NULL; + X509_PUBKEY *pubkey = NULL; + int pubkey_size = 0; + unsigned char *pubkey_der = NULL; + unsigned char *pubkey_der_tmp = NULL; + unsigned char pubkey_fingerprint[EVP_MAX_MD_SIZE]; + unsigned int pubkey_fingerprint_size; + unsigned char cert_fingerprint[EVP_MAX_MD_SIZE]; + unsigned int cert_fingerprint_size; + const char *pinned_cert_fingerprint = chan->server->connrec->tls_pinned_cert; + const char *pinned_pubkey_fingerprint = chan->server->connrec->tls_pinned_pubkey; + TLS_REC *tls = NULL; ERR_clear_error(); ret = SSL_connect(chan->ssl); @@ -611,22 +776,73 @@ int irssi_ssl_handshake(GIOChannel *handle) } cert = SSL_get_peer_certificate(chan->ssl); + pubkey = X509_get_X509_PUBKEY(cert); + if (cert == NULL) { g_warning("SSL server supplied no certificate"); return -1; } - ret = !chan->verify || irssi_ssl_verify(chan->ssl, chan->ctx, chan->server->connrec->address, chan->port, cert, chan->server); + + if (pubkey == NULL) { + g_warning("SSL server supplied no certificate public key"); + return -1; + } + + if (! X509_digest(cert, EVP_sha256(), cert_fingerprint, &cert_fingerprint_size)) { + g_warning("Unable to generate certificate fingerprint"); + X509_free(cert); + return -1; + } + + pubkey_size = i2d_X509_PUBKEY(pubkey, NULL); + pubkey_der = pubkey_der_tmp = g_new(unsigned char, pubkey_size); + i2d_X509_PUBKEY(pubkey, &pubkey_der_tmp); + + EVP_Digest(pubkey_der, pubkey_size, pubkey_fingerprint, &pubkey_fingerprint_size, EVP_sha256(), 0); + + tls = tls_create_rec(); + set_cipher_info(tls, chan->ssl); + set_pubkey_info(tls, cert, cert_fingerprint, cert_fingerprint_size, pubkey_fingerprint, pubkey_fingerprint_size); + set_peer_cert_chain_info(tls, chan->ssl); + set_server_temporary_key_info(tls, chan->ssl); + + ret = 1; + + do { + if (pinned_cert_fingerprint != NULL && pinned_cert_fingerprint[0] != '\0') { + ret = g_ascii_strcasecmp(pinned_cert_fingerprint, tls->certificate_fingerprint) == 0; + + if (! ret) { + g_warning(" Pinned certificate mismatch"); + continue; + } + } + + if (pinned_pubkey_fingerprint != NULL && pinned_pubkey_fingerprint[0] != '\0') { + ret = g_ascii_strcasecmp(pinned_pubkey_fingerprint, tls->public_key_fingerprint) == 0; + + if (! ret) { + g_warning(" Pinned public key mismatch"); + continue; + } + } + + if (chan->verify) { + ret = irssi_ssl_verify(chan->ssl, chan->ctx, chan->server->connrec->address, chan->port, cert, chan->server, tls); + + if (! ret) { + // irssi_ssl_verify emits a warning itself. + continue; + } + } + } while (0); + + // Emit the TLS rec. + signal_emit("tls handshake finished", 2, chan->server, tls); + + tls_rec_free(tls); X509_free(cert); + g_free(pubkey_der); + return ret ? 0 : -1; } - -#else /* HAVE_OPENSSL */ - -GIOChannel *net_connect_ip_ssl(IPADDR *ip, int port, IPADDR *my_ip, SERVER_REC *server) -{ - g_warning("Connection failed: SSL support not enabled in this build."); - errno = ENOSYS; - return NULL; -} - -#endif /* ! HAVE_OPENSSL */ diff --git a/src/core/server-connect-rec.h b/src/core/server-connect-rec.h index 80c5761b..fa348769 100644 --- a/src/core/server-connect-rec.h +++ b/src/core/server-connect-rec.h @@ -23,12 +23,14 @@ char *nick; char *username; char *realname; -char *ssl_cert; -char *ssl_pkey; -char *ssl_pass; -char *ssl_cafile; -char *ssl_capath; -char *ssl_ciphers; +char *tls_cert; +char *tls_pkey; +char *tls_pass; +char *tls_cafile; +char *tls_capath; +char *tls_ciphers; +char *tls_pinned_cert; +char *tls_pinned_pubkey; GIOChannel *connect_handle; /* connect using this handle */ @@ -38,8 +40,8 @@ unsigned int reconnecting:1; /* we're trying to reconnect any connection */ unsigned int no_autojoin_channels:1; /* don't autojoin any channels */ unsigned int no_autosendcmd:1; /* don't execute autosendcmd */ unsigned int unix_socket:1; /* Connect using named unix socket */ -unsigned int use_ssl:1; /* this connection uses SSL */ -unsigned int ssl_verify:1; +unsigned int use_tls:1; /* this connection uses TLS */ +unsigned int tls_verify:1; unsigned int no_connect:1; /* don't connect() at all, it's done by plugin */ char *channels; char *away_reason; diff --git a/src/core/server-rec.h b/src/core/server-rec.h index 69e990c6..775538ce 100644 --- a/src/core/server-rec.h +++ b/src/core/server-rec.h @@ -59,11 +59,9 @@ int (*ischannel)(SERVER_REC *server, const char *data); of them aren't supported '\0' can be used. */ const char *(*get_nick_flags)(SERVER_REC *server); /* send public or private message to server */ -void (*send_message)(SERVER_REC *server, const char *target, - const char *msg, int target_type); +void (*send_message)(SERVER_REC *server, const char *target, const char *msg, int target_type); /* split message in case it is too long for the server to receive */ -char **(*split_message)(SERVER_REC *server, const char *target, - const char *msg); +char **(*split_message)(SERVER_REC *server, const char *target, const char *msg); /* -- Default implementations are used if NULL -- */ CHANNEL_REC *(*channel_find_func)(SERVER_REC *server, const char *name); diff --git a/src/core/server-setup-rec.h b/src/core/server-setup-rec.h index 2c9614c7..e6b0431c 100644 --- a/src/core/server-setup-rec.h +++ b/src/core/server-setup-rec.h @@ -11,12 +11,14 @@ char *password; int sasl_mechanism; char *sasl_password; -char *ssl_cert; -char *ssl_pkey; -char *ssl_pass; -char *ssl_cafile; -char *ssl_capath; -char *ssl_ciphers; +char *tls_cert; +char *tls_pkey; +char *tls_pass; +char *tls_cafile; +char *tls_capath; +char *tls_ciphers; +char *tls_pinned_cert; +char *tls_pinned_pubkey; char *own_host; /* address to use when connecting this server */ IPADDR *own_ip4, *own_ip6; /* resolved own_address if not NULL */ @@ -28,7 +30,7 @@ unsigned int no_proxy:1; unsigned int last_failed:1; /* if last connection attempt failed */ unsigned int banned:1; /* if we're banned from this server */ unsigned int dns_error:1; /* DNS said the host doesn't exist */ -unsigned int use_ssl:1; /* this connection uses SSL */ -unsigned int ssl_verify:1; +unsigned int use_tls:1; /* this connection uses TLS */ +unsigned int tls_verify:1; GHashTable *module_data; diff --git a/src/core/servers-reconnect.c b/src/core/servers-reconnect.c index 58c9dd09..1727704c 100644 --- a/src/core/servers-reconnect.c +++ b/src/core/servers-reconnect.c @@ -192,13 +192,15 @@ server_connect_copy_skeleton(SERVER_CONNECT_REC *src, int connect_info) dest->no_autosendcmd = src->no_autosendcmd; dest->unix_socket = src->unix_socket; - dest->use_ssl = src->use_ssl; - dest->ssl_cert = g_strdup(src->ssl_cert); - dest->ssl_pkey = g_strdup(src->ssl_pkey); - dest->ssl_verify = src->ssl_verify; - dest->ssl_cafile = g_strdup(src->ssl_cafile); - dest->ssl_capath = g_strdup(src->ssl_capath); - dest->ssl_ciphers = g_strdup(src->ssl_ciphers); + dest->use_tls = src->use_tls; + dest->tls_cert = g_strdup(src->tls_cert); + dest->tls_pkey = g_strdup(src->tls_pkey); + dest->tls_verify = src->tls_verify; + dest->tls_cafile = g_strdup(src->tls_cafile); + dest->tls_capath = g_strdup(src->tls_capath); + dest->tls_ciphers = g_strdup(src->tls_ciphers); + dest->tls_pinned_cert = g_strdup(src->tls_pinned_cert); + dest->tls_pinned_pubkey = g_strdup(src->tls_pinned_pubkey); return dest; } diff --git a/src/core/servers-setup.c b/src/core/servers-setup.c index 0cecfece..9492c58c 100644 --- a/src/core/servers-setup.c +++ b/src/core/servers-setup.c @@ -167,20 +167,24 @@ static void server_setup_fill_server(SERVER_CONNECT_REC *conn, if (sserver->port > 0 && conn->port <= 0) conn->port = sserver->port; - conn->use_ssl = sserver->use_ssl; - if (conn->ssl_cert == NULL && sserver->ssl_cert != NULL && sserver->ssl_cert[0] != '\0') - conn->ssl_cert = g_strdup(sserver->ssl_cert); - if (conn->ssl_pkey == NULL && sserver->ssl_pkey != NULL && sserver->ssl_pkey[0] != '\0') - conn->ssl_pkey = g_strdup(sserver->ssl_pkey); - if (conn->ssl_pass == NULL && sserver->ssl_pass != NULL && sserver->ssl_pass[0] != '\0') - conn->ssl_pass = g_strdup(sserver->ssl_pass); - conn->ssl_verify = sserver->ssl_verify; - if (conn->ssl_cafile == NULL && sserver->ssl_cafile != NULL && sserver->ssl_cafile[0] != '\0') - conn->ssl_cafile = g_strdup(sserver->ssl_cafile); - if (conn->ssl_capath == NULL && sserver->ssl_capath != NULL && sserver->ssl_capath[0] != '\0') - conn->ssl_capath = g_strdup(sserver->ssl_capath); - if (conn->ssl_ciphers == NULL && sserver->ssl_ciphers != NULL && sserver->ssl_ciphers[0] != '\0') - conn->ssl_ciphers = g_strdup(sserver->ssl_ciphers); + conn->use_tls = sserver->use_tls; + if (conn->tls_cert == NULL && sserver->tls_cert != NULL && sserver->tls_cert[0] != '\0') + conn->tls_cert = g_strdup(sserver->tls_cert); + if (conn->tls_pkey == NULL && sserver->tls_pkey != NULL && sserver->tls_pkey[0] != '\0') + conn->tls_pkey = g_strdup(sserver->tls_pkey); + if (conn->tls_pass == NULL && sserver->tls_pass != NULL && sserver->tls_pass[0] != '\0') + conn->tls_pass = g_strdup(sserver->tls_pass); + conn->tls_verify = sserver->tls_verify; + if (conn->tls_cafile == NULL && sserver->tls_cafile != NULL && sserver->tls_cafile[0] != '\0') + conn->tls_cafile = g_strdup(sserver->tls_cafile); + if (conn->tls_capath == NULL && sserver->tls_capath != NULL && sserver->tls_capath[0] != '\0') + conn->tls_capath = g_strdup(sserver->tls_capath); + if (conn->tls_ciphers == NULL && sserver->tls_ciphers != NULL && sserver->tls_ciphers[0] != '\0') + conn->tls_ciphers = g_strdup(sserver->tls_ciphers); + if (conn->tls_pinned_cert == NULL && sserver->tls_pinned_cert != NULL && sserver->tls_pinned_cert[0] != '\0') + conn->tls_pinned_cert = g_strdup(sserver->tls_pinned_cert); + if (conn->tls_pinned_pubkey == NULL && sserver->tls_pinned_pubkey != NULL && sserver->tls_pinned_pubkey[0] != '\0') + conn->tls_pinned_pubkey = g_strdup(sserver->tls_pinned_pubkey); server_setup_fill_reconn(conn, sserver); @@ -362,9 +366,10 @@ SERVER_SETUP_REC *server_setup_find(const char *address, int port, static SERVER_SETUP_REC *server_setup_read(CONFIG_NODE *node) { SERVER_SETUP_REC *rec; - CHATNET_REC *chatnetrec; + CHATNET_REC *chatnetrec; char *server, *chatnet, *family; int port; + char *value = NULL; g_return_val_if_fail(node != NULL, NULL); @@ -390,7 +395,7 @@ static SERVER_SETUP_REC *server_setup_read(CONFIG_NODE *node) chatnet_create(chatnetrec); } - family = config_node_get_str(node, "family", ""); + family = config_node_get_str(node, "family", ""); rec = CHAT_PROTOCOL(chatnetrec)->create_server_setup(); rec->type = module_get_uniq_id("SERVER SETUP", 0); @@ -400,18 +405,55 @@ static SERVER_SETUP_REC *server_setup_read(CONFIG_NODE *node) (g_ascii_strcasecmp(family, "inet") == 0 ? AF_INET : 0); rec->address = g_strdup(server); rec->password = g_strdup(config_node_get_str(node, "password", NULL)); - rec->use_ssl = config_node_get_bool(node, "use_ssl", FALSE); - rec->ssl_cert = g_strdup(config_node_get_str(node, "ssl_cert", NULL)); - rec->ssl_pkey = g_strdup(config_node_get_str(node, "ssl_pkey", NULL)); - rec->ssl_pass = g_strdup(config_node_get_str(node, "ssl_pass", NULL)); - rec->ssl_verify = config_node_get_bool(node, "ssl_verify", FALSE); - rec->ssl_cafile = g_strdup(config_node_get_str(node, "ssl_cafile", NULL)); - rec->ssl_capath = g_strdup(config_node_get_str(node, "ssl_capath", NULL)); - rec->ssl_ciphers = g_strdup(config_node_get_str(node, "ssl_ciphers", NULL)); - if (rec->ssl_cafile || rec->ssl_capath) - rec->ssl_verify = TRUE; - if (rec->ssl_cert != NULL || rec->ssl_verify) - rec->use_ssl = TRUE; + + rec->use_tls = config_node_get_bool(node, "use_tls", FALSE) || config_node_get_bool(node, "use_ssl", FALSE); + rec->tls_verify = config_node_get_bool(node, "tls_verify", FALSE) || config_node_get_bool(node, "ssl_verify", FALSE); + + value = config_node_get_str(node, "tls_cert", NULL); + if (value == NULL) + value = config_node_get_str(node, "ssl_cert", NULL); + rec->tls_cert = g_strdup(value); + + value = config_node_get_str(node, "tls_pkey", NULL); + if (value == NULL) + value = config_node_get_str(node, "ssl_pkey", NULL); + rec->tls_pkey = g_strdup(value); + + value = config_node_get_str(node, "tls_pass", NULL); + if (value == NULL) + value = config_node_get_str(node, "ssl_pass", NULL); + rec->tls_pass = g_strdup(value); + + value = config_node_get_str(node, "tls_cafile", NULL); + if (value == NULL) + value = config_node_get_str(node, "ssl_cafile", NULL); + rec->tls_cafile = g_strdup(value); + + value = config_node_get_str(node, "tls_capath", NULL); + if (value == NULL) + value = config_node_get_str(node, "ssl_capath", NULL); + rec->tls_capath = g_strdup(value); + + value = config_node_get_str(node, "tls_ciphers", NULL); + if (value == NULL) + value = config_node_get_str(node, "ssl_ciphers", NULL); + rec->tls_ciphers = g_strdup(value); + + value = config_node_get_str(node, "tls_pinned_cert", NULL); + if (value == NULL) + value = config_node_get_str(node, "ssl_pinned_cert", NULL); + rec->tls_pinned_cert = g_strdup(value); + + value = config_node_get_str(node, "tls_pinned_pubkey", NULL); + if (value == NULL) + value = config_node_get_str(node, "ssl_pinned_pubkey", NULL); + rec->tls_pinned_pubkey = g_strdup(value); + + if (rec->tls_cafile || rec->tls_capath) + rec->tls_verify = TRUE; + if (rec->tls_cert != NULL || rec->tls_verify) + rec->use_tls = TRUE; + rec->port = port; rec->autoconnect = config_node_get_bool(node, "autoconnect", FALSE); rec->no_proxy = config_node_get_bool(node, "no_proxy", FALSE); @@ -463,14 +505,18 @@ static void server_setup_save(SERVER_SETUP_REC *rec) iconfig_node_set_int(node, "port", rec->port); iconfig_node_set_str(node, "password", rec->password); - iconfig_node_set_bool(node, "use_ssl", rec->use_ssl); - iconfig_node_set_str(node, "ssl_cert", rec->ssl_cert); - iconfig_node_set_str(node, "ssl_pkey", rec->ssl_pkey); - iconfig_node_set_str(node, "ssl_pass", rec->ssl_pass); - iconfig_node_set_bool(node, "ssl_verify", rec->ssl_verify); - iconfig_node_set_str(node, "ssl_cafile", rec->ssl_cafile); - iconfig_node_set_str(node, "ssl_capath", rec->ssl_capath); - iconfig_node_set_str(node, "ssl_ciphers", rec->ssl_ciphers); + + iconfig_node_set_bool(node, "use_tls", rec->use_tls); + iconfig_node_set_str(node, "tls_cert", rec->tls_cert); + iconfig_node_set_str(node, "tls_pkey", rec->tls_pkey); + iconfig_node_set_str(node, "tls_pass", rec->tls_pass); + iconfig_node_set_bool(node, "tls_verify", rec->tls_verify); + iconfig_node_set_str(node, "tls_cafile", rec->tls_cafile); + iconfig_node_set_str(node, "tls_capath", rec->tls_capath); + iconfig_node_set_str(node, "tls_ciphers", rec->tls_ciphers); + iconfig_node_set_str(node, "tls_pinned_cert", rec->tls_pinned_cert); + iconfig_node_set_str(node, "tls_pinned_pubkey", rec->tls_pinned_pubkey); + iconfig_node_set_str(node, "own_host", rec->own_host); iconfig_node_set_str(node, "family", @@ -514,12 +560,14 @@ static void server_setup_destroy(SERVER_SETUP_REC *rec) g_free_not_null(rec->own_ip6); g_free_not_null(rec->chatnet); g_free_not_null(rec->password); - g_free_not_null(rec->ssl_cert); - g_free_not_null(rec->ssl_pkey); - g_free_not_null(rec->ssl_pass); - g_free_not_null(rec->ssl_cafile); - g_free_not_null(rec->ssl_capath); - g_free_not_null(rec->ssl_ciphers); + g_free_not_null(rec->tls_cert); + g_free_not_null(rec->tls_pkey); + g_free_not_null(rec->tls_pass); + g_free_not_null(rec->tls_cafile); + g_free_not_null(rec->tls_capath); + g_free_not_null(rec->tls_ciphers); + g_free_not_null(rec->tls_pinned_cert); + g_free_not_null(rec->tls_pinned_pubkey); g_free(rec->address); g_free(rec); } diff --git a/src/core/servers.c b/src/core/servers.c index d6297c4d..b9faab81 100644 --- a/src/core/servers.c +++ b/src/core/servers.c @@ -167,7 +167,6 @@ static void server_connect_callback_init(SERVER_REC *server, GIOChannel *handle) server_connect_finished(server); } -#ifdef HAVE_OPENSSL static void server_connect_callback_init_ssl(SERVER_REC *server, GIOChannel *handle) { int error; @@ -198,7 +197,6 @@ static void server_connect_callback_init_ssl(SERVER_REC *server, GIOChannel *han server_connect_finished(server); } -#endif static void server_real_connect(SERVER_REC *server, IPADDR *ip, const char *unix_socket) @@ -221,7 +219,7 @@ static void server_real_connect(SERVER_REC *server, IPADDR *ip, own_ip = IPADDR_IS_V6(ip) ? server->connrec->own_ip6 : server->connrec->own_ip4; port = server->connrec->proxy != NULL ? server->connrec->proxy_port : server->connrec->port; - handle = server->connrec->use_ssl ? + handle = server->connrec->use_tls ? net_connect_ip_ssl(ip, port, own_ip, server) : net_connect_ip(ip, port, own_ip); } else { handle = net_connect_unix(unix_socket); @@ -239,7 +237,7 @@ static void server_real_connect(SERVER_REC *server, IPADDR *ip, } server->no_reconnect = TRUE; } - if (server->connrec->use_ssl && errno == ENOSYS) + if (server->connrec->use_tls && errno == ENOSYS) server->no_reconnect = TRUE; server->connection_lost = TRUE; @@ -247,11 +245,9 @@ static void server_real_connect(SERVER_REC *server, IPADDR *ip, g_free(errmsg2); } else { server->handle = net_sendbuffer_create(handle, 0); -#ifdef HAVE_OPENSSL - if (server->connrec->use_ssl) + if (server->connrec->use_tls) server_connect_callback_init_ssl(server, handle); else -#endif server->connect_tag = g_input_add(handle, G_INPUT_WRITE | G_INPUT_READ, (GInputFunction) @@ -626,22 +622,24 @@ void server_connect_unref(SERVER_CONNECT_REC *conn) g_free_not_null(conn->own_ip4); g_free_not_null(conn->own_ip6); - g_free_not_null(conn->password); - g_free_not_null(conn->nick); - g_free_not_null(conn->username); + g_free_not_null(conn->password); + g_free_not_null(conn->nick); + g_free_not_null(conn->username); g_free_not_null(conn->realname); - g_free_not_null(conn->ssl_cert); - g_free_not_null(conn->ssl_pkey); - g_free_not_null(conn->ssl_pass); - g_free_not_null(conn->ssl_cafile); - g_free_not_null(conn->ssl_capath); - g_free_not_null(conn->ssl_ciphers); + g_free_not_null(conn->tls_cert); + g_free_not_null(conn->tls_pkey); + g_free_not_null(conn->tls_pass); + g_free_not_null(conn->tls_cafile); + g_free_not_null(conn->tls_capath); + g_free_not_null(conn->tls_ciphers); + g_free_not_null(conn->tls_pinned_cert); + g_free_not_null(conn->tls_pinned_pubkey); g_free_not_null(conn->channels); - g_free_not_null(conn->away_reason); + g_free_not_null(conn->away_reason); - conn->type = 0; + conn->type = 0; g_free(conn); } diff --git a/src/core/session.c b/src/core/session.c index 17d80076..34190c52 100644 --- a/src/core/session.c +++ b/src/core/session.c @@ -150,8 +150,7 @@ static void session_save_server(SERVER_REC *server, CONFIG_REC *config, node = config_node_section(config, node, NULL, NODE_TYPE_BLOCK); - config_node_set_str(config, node, "chat_type", - chat_protocol_find_id(server->chat_type)->name); + config_node_set_str(config, node, "chat_type", chat_protocol_find_id(server->chat_type)->name); config_node_set_str(config, node, "address", server->connrec->address); config_node_set_int(config, node, "port", server->connrec->port); config_node_set_str(config, node, "chatnet", server->connrec->chatnet); @@ -159,13 +158,15 @@ static void session_save_server(SERVER_REC *server, CONFIG_REC *config, config_node_set_str(config, node, "nick", server->nick); config_node_set_str(config, node, "version", server->version); - config_node_set_bool(config, node, "use_ssl", server->connrec->use_ssl); - config_node_set_str(config, node, "ssl_cert", server->connrec->ssl_cert); - config_node_set_str(config, node, "ssl_pkey", server->connrec->ssl_pkey); - config_node_set_bool(config, node, "ssl_verify", server->connrec->ssl_verify); - config_node_set_str(config, node, "ssl_cafile", server->connrec->ssl_cafile); - config_node_set_str(config, node, "ssl_capath", server->connrec->ssl_capath); - config_node_set_str(config, node, "ssl_ciphers", server->connrec->ssl_ciphers); + config_node_set_bool(config, node, "use_tls", server->connrec->use_tls); + config_node_set_str(config, node, "tls_cert", server->connrec->tls_cert); + config_node_set_str(config, node, "tls_pkey", server->connrec->tls_pkey); + config_node_set_bool(config, node, "tls_verify", server->connrec->tls_verify); + config_node_set_str(config, node, "tls_cafile", server->connrec->tls_cafile); + config_node_set_str(config, node, "tls_capath", server->connrec->tls_capath); + config_node_set_str(config, node, "tls_ciphers", server->connrec->tls_ciphers); + config_node_set_str(config, node, "tls_pinned_cert", server->connrec->tls_pinned_cert); + config_node_set_str(config, node, "tls_pinned_pubkey", server->connrec->tls_pinned_pubkey); handle = g_io_channel_unix_get_fd(net_sendbuffer_handle(server->handle)); config_node_set_int(config, node, "handle", handle); diff --git a/src/core/tls.c b/src/core/tls.c new file mode 100644 index 00000000..5610d4f5 --- /dev/null +++ b/src/core/tls.c @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2015 Alexander Færøy + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301,USA + */ + +#include "module.h" + +#include "tls.h" + +TLS_REC *tls_create_rec() +{ + TLS_REC *rec = g_new0(TLS_REC, 1); + g_assert(rec != NULL); + + return rec; +} + +void tls_rec_free(TLS_REC *tls_rec) +{ + if (tls_rec == NULL) + return; + + g_free_and_null(tls_rec->protocol_version); + g_free_and_null(tls_rec->cipher); + g_free_and_null(tls_rec->public_key_algorithm); + g_free_and_null(tls_rec->public_key_fingerprint); + g_free_and_null(tls_rec->public_key_fingerprint_algorithm); + g_free_and_null(tls_rec->certificate_fingerprint); + g_free_and_null(tls_rec->certificate_fingerprint_algorithm); + g_free_and_null(tls_rec->not_after); + g_free_and_null(tls_rec->not_before); + +#ifdef SSL_get_server_tmp_key + g_free_and_null(tls_rec->ephemeral_key_algorithm); +#endif + + if (tls_rec->certs != NULL) { + g_slist_foreach(tls_rec->certs, (GFunc)tls_cert_rec_free, NULL); + g_slist_free(tls_rec->certs); + tls_rec->certs = NULL; + } + + g_free(tls_rec); +} + +void tls_rec_set_protocol_version(TLS_REC *tls_rec, const char *protocol_version) +{ + g_assert(tls_rec != NULL); + + tls_rec->protocol_version = g_strdup(protocol_version); +} + +void tls_rec_set_cipher(TLS_REC *tls_rec, const char *cipher) +{ + g_assert(tls_rec != NULL); + + tls_rec->cipher = g_strdup(cipher); +} + +void tls_rec_set_cipher_size(TLS_REC *tls_rec, size_t size) +{ + g_assert(tls_rec != NULL); + + tls_rec->cipher_size = size; +} + +void tls_rec_set_public_key_algorithm(TLS_REC *tls_rec, const char *algorithm) +{ + g_assert(tls_rec != NULL); + + tls_rec->public_key_algorithm = g_strdup(algorithm); +} + +void tls_rec_set_public_key_fingerprint(TLS_REC *tls_rec, const char *fingerprint) +{ + g_assert(tls_rec != NULL); + + tls_rec->public_key_fingerprint = g_strdup(fingerprint); +} + +void tls_rec_set_public_key_fingerprint_algorithm(TLS_REC *tls_rec, const char *algorithm) +{ + g_assert(tls_rec != NULL); + + tls_rec->public_key_fingerprint_algorithm = g_strdup(algorithm); +} + +void tls_rec_set_public_key_size(TLS_REC *tls_rec, size_t size) +{ + g_assert(tls_rec != NULL); + tls_rec->public_key_size = size; +} + +void tls_rec_set_certificate_fingerprint(TLS_REC *tls_rec, const char *fingerprint) +{ + g_assert(tls_rec != NULL); + + tls_rec->certificate_fingerprint = g_strdup(fingerprint); +} + +void tls_rec_set_certificate_fingerprint_algorithm(TLS_REC *tls_rec, const char *algorithm) +{ + g_assert(tls_rec != NULL); + + tls_rec->certificate_fingerprint_algorithm = g_strdup(algorithm); +} + +void tls_rec_set_not_after(TLS_REC *tls_rec, const char *not_after) +{ + g_assert(tls_rec != NULL); + tls_rec->not_after = g_strdup(not_after); +} + +void tls_rec_set_not_before(TLS_REC *tls_rec, const char *not_before) +{ + g_assert(tls_rec != NULL); + tls_rec->not_before = g_strdup(not_before); +} + +#ifdef SSL_get_server_tmp_key +void tls_rec_set_ephemeral_key_algorithm(TLS_REC *tls_rec, const char *algorithm) +{ + g_assert(tls_rec != NULL); + tls_rec->ephemeral_key_algorithm = g_strdup(algorithm); +} + +void tls_rec_set_ephemeral_key_size(TLS_REC *tls_rec, size_t size) +{ + g_assert(tls_rec != NULL); + tls_rec->ephemeral_key_size = size; +} +#endif + +void tls_rec_append_cert(TLS_REC *tls_rec, TLS_CERT_REC *tls_cert_rec) +{ + g_assert(tls_rec != NULL); + g_assert(tls_cert_rec != NULL); + + tls_rec->certs = g_slist_append(tls_rec->certs, tls_cert_rec); +} + +TLS_CERT_REC *tls_cert_create_rec() +{ + TLS_CERT_REC *rec = g_new0(TLS_CERT_REC, 1); + g_assert(rec != NULL); + + return rec; +} + +void tls_cert_rec_append_subject_entry(TLS_CERT_REC *tls_cert_rec, TLS_CERT_ENTRY_REC *tls_cert_entry_rec) +{ + g_assert(tls_cert_rec != NULL); + g_assert(tls_cert_entry_rec != NULL); + + tls_cert_rec->subject = g_slist_append(tls_cert_rec->subject, tls_cert_entry_rec); +} + +void tls_cert_rec_append_issuer_entry(TLS_CERT_REC *tls_cert_rec, TLS_CERT_ENTRY_REC *tls_cert_entry_rec) +{ + g_assert(tls_cert_rec != NULL); + g_assert(tls_cert_entry_rec != NULL); + + tls_cert_rec->issuer = g_slist_append(tls_cert_rec->issuer, tls_cert_entry_rec); +} + +void tls_cert_rec_free(TLS_CERT_REC *tls_cert_rec) +{ + if (tls_cert_rec == NULL) + return; + + if (tls_cert_rec->subject != NULL) { + g_slist_foreach(tls_cert_rec->subject, (GFunc)tls_cert_entry_rec_free, NULL); + g_slist_free(tls_cert_rec->subject); + tls_cert_rec->subject = NULL; + } + + if (tls_cert_rec->issuer != NULL) { + g_slist_foreach(tls_cert_rec->issuer, (GFunc)tls_cert_entry_rec_free, NULL); + g_slist_free(tls_cert_rec->issuer); + tls_cert_rec->issuer = NULL; + } + + g_free(tls_cert_rec); +} + +TLS_CERT_ENTRY_REC *tls_cert_entry_create_rec(const char *name, const char *value) +{ + TLS_CERT_ENTRY_REC *rec = g_new0(TLS_CERT_ENTRY_REC, 1); + g_assert(rec != NULL); + + rec->name = g_strdup(name); + rec->value = g_strdup(value); + + return rec; +} + +void tls_cert_entry_rec_free(TLS_CERT_ENTRY_REC *tls_cert_entry) +{ + if (tls_cert_entry == NULL) + return; + + g_free_and_null(tls_cert_entry->name); + g_free_and_null(tls_cert_entry->value); + + g_free(tls_cert_entry); +} diff --git a/src/core/tls.h b/src/core/tls.h new file mode 100644 index 00000000..fd3bb751 --- /dev/null +++ b/src/core/tls.h @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2015 Alexander Færøy + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301,USA + */ + +#ifndef __TLS_H +#define __TLS_H + +#include + +#include + +typedef struct _TLS_REC TLS_REC; +typedef struct _TLS_CERT_REC TLS_CERT_REC; +typedef struct _TLS_CERT_ENTRY_REC TLS_CERT_ENTRY_REC; + +struct _TLS_REC { + char *protocol_version; + char *cipher; + size_t cipher_size; + + char *public_key_algorithm; + char *public_key_fingerprint; + char *public_key_fingerprint_algorithm; + size_t public_key_size; + + char *certificate_fingerprint; + char *certificate_fingerprint_algorithm; + + char *not_after; + char *not_before; + +#ifdef SSL_get_server_tmp_key + char *ephemeral_key_algorithm; + size_t ephemeral_key_size; +#endif + + GSList *certs; + + bool success; + char *error; +}; + +struct _TLS_CERT_REC { + GSList *subject; + GSList *issuer; +}; + +struct _TLS_CERT_ENTRY_REC { + char *name; + char *value; +}; + +TLS_REC *tls_create_rec(); +void tls_rec_free(TLS_REC *tls_rec); + +void tls_rec_set_protocol_version(TLS_REC *tls_rec, const char *protocol_version); +void tls_rec_set_cipher(TLS_REC *tls_rec, const char *cipher); +void tls_rec_set_cipher_size(TLS_REC *tls_rec, size_t size); +void tls_rec_set_public_key_algorithm(TLS_REC *tls_rec, const char *algorithm); +void tls_rec_set_public_key_fingerprint(TLS_REC *tls_rec, const char *fingerprint); +void tls_rec_set_public_key_fingerprint_algorithm(TLS_REC *tls_rec, const char *algorithm); +void tls_rec_set_public_key_size(TLS_REC *tls_rec, size_t size); +void tls_rec_set_certificate_fingerprint(TLS_REC *tls_rec, const char *fingerprint); +void tls_rec_set_certificate_fingerprint_algorithm(TLS_REC *tls_rec, const char *algorithm); +void tls_rec_set_not_after(TLS_REC *tls_rec, const char *not_after); +void tls_rec_set_not_before(TLS_REC *tls_rec, const char *not_before); + +#ifdef SSL_get_server_tmp_key +void tls_rec_set_ephemeral_key_algorithm(TLS_REC *tls_rec, const char *algorithm); +void tls_rec_set_ephemeral_key_size(TLS_REC *tls_rec, size_t size); +#endif + +void tls_rec_append_cert(TLS_REC *tls_rec, TLS_CERT_REC *tls_cert_rec); + +TLS_CERT_REC *tls_cert_create_rec(); +void tls_cert_rec_free(TLS_CERT_REC *tls_cert_rec); + +void tls_cert_rec_append_subject_entry(TLS_CERT_REC *tls_cert_rec, TLS_CERT_ENTRY_REC *tls_cert_entry_rec); +void tls_cert_rec_append_issuer_entry(TLS_CERT_REC *tls_cert_rec, TLS_CERT_ENTRY_REC *tls_cert_entry_rec); + +TLS_CERT_ENTRY_REC *tls_cert_entry_create_rec(const char *name, const char *value); +void tls_cert_entry_rec_free(TLS_CERT_ENTRY_REC *tls_cert_entry); + +#endif diff --git a/src/fe-common/core/Makefile.am b/src/fe-common/core/Makefile.am index 63f91fa6..6efff411 100644 --- a/src/fe-common/core/Makefile.am +++ b/src/fe-common/core/Makefile.am @@ -24,6 +24,7 @@ libfe_common_core_a_SOURCES = \ fe-queries.c \ fe-server.c \ fe-settings.c \ + fe-tls.c \ formats.c \ hilight-text.c \ keyboard.c \ @@ -48,6 +49,7 @@ pkginc_fe_common_core_HEADERS = \ fe-exec.h \ fe-messages.h \ fe-queries.h \ + fe-tls.h \ formats.h \ hilight-text.h \ keyboard.h \ diff --git a/src/fe-common/core/fe-common-core.c b/src/fe-common/core/fe-common-core.c index 1b2ab1e2..cf588e71 100644 --- a/src/fe-common/core/fe-common-core.c +++ b/src/fe-common/core/fe-common-core.c @@ -88,6 +88,9 @@ void fe_server_deinit(void); void fe_settings_init(void); void fe_settings_deinit(void); +void fe_tls_init(void); +void fe_tls_deinit(void); + void window_commands_init(void); void window_commands_deinit(void); @@ -161,21 +164,24 @@ void fe_common_core_init(void) g_get_charset(&str); settings_add_str("lookandfeel", "term_charset", str); themes_init(); - theme_register(fecommon_core_formats); + theme_register(fecommon_core_formats); command_history_init(); completion_init(); keyboard_init(); printtext_init(); formats_init(); - fe_exec_init(); - fe_expandos_init(); +#ifndef WIN32 + fe_exec_init(); +#endif + fe_expandos_init(); fe_help_init(); fe_ignore_init(); fe_log_init(); fe_modules_init(); fe_server_init(); fe_settings_init(); + fe_tls_init(); windows_init(); window_activity_init(); window_commands_init(); @@ -183,8 +189,8 @@ void fe_common_core_init(void) windows_layout_init(); fe_core_commands_init(); - fe_channels_init(); - fe_queries_init(); + fe_channels_init(); + fe_queries_init(); fe_messages_init(); hilight_text_init(); @@ -193,10 +199,10 @@ void fe_common_core_init(void) settings_check(); - signal_add_first("server connected", (SIGNAL_FUNC) sig_connected); - signal_add_last("server disconnected", (SIGNAL_FUNC) sig_disconnected); - signal_add_first("channel created", (SIGNAL_FUNC) sig_channel_created); - signal_add_last("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed); + signal_add_first("server connected", (SIGNAL_FUNC) sig_connected); + signal_add_last("server disconnected", (SIGNAL_FUNC) sig_disconnected); + signal_add_first("channel created", (SIGNAL_FUNC) sig_channel_created); + signal_add_last("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed); module_register("core", "fe"); } @@ -209,14 +215,17 @@ void fe_common_core_deinit(void) keyboard_deinit(); printtext_deinit(); formats_deinit(); - fe_exec_deinit(); - fe_expandos_deinit(); +#ifndef WIN32 + fe_exec_deinit(); +#endif + fe_expandos_deinit(); fe_help_deinit(); fe_ignore_deinit(); fe_log_deinit(); fe_modules_deinit(); fe_server_deinit(); fe_settings_deinit(); + fe_tls_deinit(); windows_deinit(); window_activity_deinit(); window_commands_deinit(); @@ -224,21 +233,21 @@ void fe_common_core_deinit(void) windows_layout_deinit(); fe_core_commands_deinit(); - fe_channels_deinit(); - fe_queries_deinit(); + fe_channels_deinit(); + fe_queries_deinit(); fe_messages_deinit(); fe_ignore_messages_deinit(); fe_recode_deinit(); - theme_unregister(); + theme_unregister(); themes_deinit(); - signal_remove("setup changed", (SIGNAL_FUNC) sig_setup_changed); - signal_remove("server connected", (SIGNAL_FUNC) sig_connected); - signal_remove("server disconnected", (SIGNAL_FUNC) sig_disconnected); - signal_remove("channel created", (SIGNAL_FUNC) sig_channel_created); - signal_remove("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed); + signal_remove("setup changed", (SIGNAL_FUNC) sig_setup_changed); + signal_remove("server connected", (SIGNAL_FUNC) sig_connected); + signal_remove("server disconnected", (SIGNAL_FUNC) sig_disconnected); + signal_remove("channel created", (SIGNAL_FUNC) sig_channel_created); + signal_remove("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed); } void glog_func(const char *log_domain, GLogLevelFlags log_level, diff --git a/src/fe-common/core/fe-server.c b/src/fe-common/core/fe-server.c index 468cb707..5184d9d8 100644 --- a/src/fe-common/core/fe-server.c +++ b/src/fe-common/core/fe-server.c @@ -154,42 +154,66 @@ static void cmd_server_add_modify(const char *data, gboolean add) else if (g_hash_table_lookup(optlist, "4")) rec->family = AF_INET; - if (g_hash_table_lookup(optlist, "ssl")) - rec->use_ssl = TRUE; + if (g_hash_table_lookup(optlist, "tls") || g_hash_table_lookup(optlist, "ssl")) + rec->use_tls = TRUE; - value = g_hash_table_lookup(optlist, "ssl_cert"); + value = g_hash_table_lookup(optlist, "tls_cert"); + if (value == NULL) + value = g_hash_table_lookup(optlist, "ssl_cert"); if (value != NULL && *value != '\0') - rec->ssl_cert = g_strdup(value); + rec->tls_cert = g_strdup(value); - value = g_hash_table_lookup(optlist, "ssl_pkey"); + value = g_hash_table_lookup(optlist, "tls_pkey"); + if (value == NULL) + value = g_hash_table_lookup(optlist, "ssl_pkey"); if (value != NULL && *value != '\0') - rec->ssl_pkey = g_strdup(value); + rec->tls_pkey = g_strdup(value); - value = g_hash_table_lookup(optlist, "ssl_pass"); + value = g_hash_table_lookup(optlist, "tls_pass"); + if (value == NULL) + value = g_hash_table_lookup(optlist, "ssl_pass"); if (value != NULL && *value != '\0') - rec->ssl_pass = g_strdup(value); + rec->tls_pass = g_strdup(value); - if (g_hash_table_lookup(optlist, "ssl_verify")) - rec->ssl_verify = TRUE; + if (g_hash_table_lookup(optlist, "tls_verify") || g_hash_table_lookup(optlist, "ssl_verify")) + rec->tls_verify = TRUE; - value = g_hash_table_lookup(optlist, "ssl_cafile"); + value = g_hash_table_lookup(optlist, "tls_cafile"); + if (value == NULL) + value = g_hash_table_lookup(optlist, "ssl_cafile"); if (value != NULL && *value != '\0') - rec->ssl_cafile = g_strdup(value); + rec->tls_cafile = g_strdup(value); - value = g_hash_table_lookup(optlist, "ssl_capath"); + value = g_hash_table_lookup(optlist, "tls_capath"); + if (value == NULL) + value = g_hash_table_lookup(optlist, "ssl_capath"); if (value != NULL && *value != '\0') - rec->ssl_capath = g_strdup(value); + rec->tls_capath = g_strdup(value); - value = g_hash_table_lookup(optlist, "ssl_ciphers"); + value = g_hash_table_lookup(optlist, "tls_ciphers"); + if (value == NULL) + value = g_hash_table_lookup(optlist, "ssl_ciphers"); if (value != NULL && *value != '\0') - rec->ssl_ciphers = g_strdup(value); + rec->tls_ciphers = g_strdup(value); - if ((rec->ssl_cafile != NULL && rec->ssl_cafile[0] != '\0') - || (rec->ssl_capath != NULL && rec->ssl_capath[0] != '\0')) - rec->ssl_verify = TRUE; + value = g_hash_table_lookup(optlist, "tls_pinned_cert"); + if (value == NULL) + value = g_hash_table_lookup(optlist, "ssl_pinned_cert"); + if (value != NULL && *value != '\0') + rec->tls_pinned_cert = g_strdup(value); - if ((rec->ssl_cert != NULL && rec->ssl_cert[0] != '\0') || rec->ssl_verify == TRUE) - rec->use_ssl = TRUE; + value = g_hash_table_lookup(optlist, "tls_pinned_pubkey"); + if (value == NULL) + value = g_hash_table_lookup(optlist, "ssl_pinned_pubkey"); + if (value != NULL && *value != '\0') + rec->tls_pinned_pubkey = g_strdup(value); + + if ((rec->tls_cafile != NULL && rec->tls_cafile[0] != '\0') + || (rec->tls_capath != NULL && rec->tls_capath[0] != '\0')) + rec->tls_verify = TRUE; + + if ((rec->tls_cert != NULL && rec->tls_cert[0] != '\0') || rec->tls_verify == TRUE) + rec->use_tls = TRUE; if (g_hash_table_lookup(optlist, "auto")) rec->autoconnect = TRUE; if (g_hash_table_lookup(optlist, "noauto")) rec->autoconnect = FALSE; @@ -331,8 +355,7 @@ static void sig_server_connected(SERVER_REC *server) { g_return_if_fail(server != NULL); - printformat(server, NULL, MSGLEVEL_CLIENTNOTICE, - TXT_CONNECTION_ESTABLISHED, server->connrec->address); + printformat(server, NULL, MSGLEVEL_CLIENTNOTICE, TXT_CONNECTION_ESTABLISHED, server->connrec->address); } static void sig_connect_failed(SERVER_REC *server, gchar *msg) @@ -409,8 +432,9 @@ void fe_server_init(void) command_bind("server remove", NULL, (SIGNAL_FUNC) cmd_server_remove); command_bind_first("server", NULL, (SIGNAL_FUNC) server_command); command_bind_first("disconnect", NULL, (SIGNAL_FUNC) server_command); - command_set_options("server add", "4 6 !! ssl +ssl_cert +ssl_pkey +ssl_pass ssl_verify +ssl_cafile +ssl_capath +ssl_ciphers auto noauto proxy noproxy -host -port noautosendcmd"); - command_set_options("server modify", "4 6 !! ssl +ssl_cert +ssl_pkey +ssl_pass ssl_verify +ssl_cafile +ssl_capath +ssl_ciphers auto noauto proxy noproxy -host -port noautosendcmd"); + + command_set_options("server add", "4 6 !! ssl +ssl_cert +ssl_pkey +ssl_pass ssl_verify +ssl_cafile +ssl_capath +ssl_ciphers +ssl_fingerprint tls +tls_cert +tls_pkey +tls_pass tls_verify +tls_cafile +tls_capath +tls_ciphers +tls_pinned_cert +tls_pinned_pubkey auto noauto proxy noproxy -host -port noautosendcmd"); + command_set_options("server modify", "4 6 !! ssl +ssl_cert +ssl_pkey +ssl_pass ssl_verify +ssl_cafile +ssl_capath +ssl_ciphers +ssl_fingerprint tls +tls_cert +tls_pkey +tls_pass tls_verify +tls_cafile +tls_capath +tls_ciphers +tls_pinned_cert +tls_pinned_pubkey auto noauto proxy noproxy -host -port noautosendcmd"); signal_add("server looking", (SIGNAL_FUNC) sig_server_looking); signal_add("server connecting", (SIGNAL_FUNC) sig_server_connecting); diff --git a/src/fe-common/core/fe-tls.c b/src/fe-common/core/fe-tls.c new file mode 100644 index 00000000..32321e0d --- /dev/null +++ b/src/fe-common/core/fe-tls.c @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2015 Alexander Færøy + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301,USA + */ + +#include "module.h" +#include "signals.h" +#include "settings.h" +#include "levels.h" +#include "tls.h" + +#include "module-formats.h" +#include "printtext.h" + +#include "fe-tls.h" + +static void tls_handshake_finished(SERVER_REC *server, TLS_REC *tls) +{ + if (! settings_get_bool("tls_verbose_connect")) + return; + + printformat(server, NULL, MSGLEVEL_CLIENTNOTICE, TXT_TLS_CERT_HEADER); + + GSList *certs = NULL; + for (certs = tls->certs; certs != NULL; certs = certs->next) { + TLS_CERT_REC *tls_cert_rec = certs->data; + + printformat(server, NULL, MSGLEVEL_CLIENTNOTICE, TXT_TLS_CERT_SUBJECT_HEADER); + + GSList *subject = NULL; + for (subject = tls_cert_rec->subject; subject != NULL; subject = subject->next) { + TLS_CERT_ENTRY_REC *subject_data = subject->data; + printformat(server, NULL, MSGLEVEL_CLIENTNOTICE, TXT_TLS_CERT_NAMED_ENTRY, subject_data->name, subject_data->value); + } + + printformat(server, NULL, MSGLEVEL_CLIENTNOTICE, TXT_TLS_CERT_ISSUER_HEADER); + + GSList *issuer = NULL; + for (issuer = tls_cert_rec->issuer; issuer != NULL; issuer = issuer->next) { + TLS_CERT_ENTRY_REC *issuer_data = issuer->data; + printformat(server, NULL, MSGLEVEL_CLIENTNOTICE, TXT_TLS_CERT_NAMED_ENTRY, issuer_data->name, issuer_data->value); + } + } + + printformat(server, NULL, MSGLEVEL_CLIENTNOTICE, TXT_TLS_PROTOCOL_VERSION, tls->protocol_version, tls->cipher_size, tls->cipher); + +#ifdef SSL_get_server_tmp_key + if (tls->ephemeral_key_algorithm != NULL) + printformat(server, NULL, MSGLEVEL_CLIENTNOTICE, TXT_TLS_EPHEMERAL_KEY, tls->ephemeral_key_size, tls->ephemeral_key_algorithm); + else + printformat(server, NULL, MSGLEVEL_CLIENTNOTICE, TXT_TLS_EPHEMERAL_KEY_UNAVAILBLE); +#endif + + printformat(server, NULL, MSGLEVEL_CLIENTNOTICE, TXT_TLS_PUBLIC_KEY, tls->public_key_size, tls->public_key_algorithm, tls->not_before, tls->not_after); + printformat(server, NULL, MSGLEVEL_CLIENTNOTICE, TXT_TLS_PUBLIC_KEY_FINGERPRINT, tls->public_key_fingerprint, tls->public_key_fingerprint_algorithm); + printformat(server, NULL, MSGLEVEL_CLIENTNOTICE, TXT_TLS_CERTIFICATE_FINGERPRINT, tls->certificate_fingerprint, tls->certificate_fingerprint_algorithm); +} + +void fe_tls_init(void) +{ + settings_add_bool("lookandfeel", "tls_verbose_connect", TRUE); + + signal_add("tls handshake finished", (SIGNAL_FUNC)tls_handshake_finished); +} + +void fe_tls_deinit(void) +{ + signal_remove("tls handshake finished", (SIGNAL_FUNC)tls_handshake_finished); +} diff --git a/src/fe-common/core/fe-tls.h b/src/fe-common/core/fe-tls.h new file mode 100644 index 00000000..f0082477 --- /dev/null +++ b/src/fe-common/core/fe-tls.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2015 Alexander Færøy + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301,USA + */ + +#ifndef __FE_TLS_H +#define __FE_TLS_H + +void fe_tls_init(void); +void fe_tls_deinit(void); + +#endif diff --git a/src/fe-common/core/module-formats.c b/src/fe-common/core/module-formats.c index 5c07f14c..c181b468 100644 --- a/src/fe-common/core/module-formats.c +++ b/src/fe-common/core/module-formats.c @@ -291,5 +291,19 @@ FORMAT_REC fecommon_core_formats[] = { { "completion_line", "%#$[10]0 $[!40]1 $2", 3, { 0, 0, 0 } }, { "completion_footer", "", 0 }, + /* ---- */ + { NULL, "TLS", 0 }, + + { "tls_ephemeral_key", "EDH Key: {hilight $0} bit {hilight $1}", 2, { 1, 0 } }, + { "tls_ephemeral_key_unavailable", "EDH Key: {error N/A}", 0 }, + { "tls_public_key", "Public Key: {hilight $0} bit {hilight $1}, valid from {hilight $2} to {hilight $3}", 4, { 1, 0, 0, 0 } }, + { "tls_cert_header", "Certificate Chain:", 0 }, + { "tls_cert_subject_header", " Subject:", 0 }, + { "tls_cert_issuer_header", " Issuer:", 0 }, + { "tls_cert_named_entry", " $[-2]0: {hilight $1}", 2, { 0, 0 } }, + { "tls_public_key_fingerprint", "Public Key Fingerprint: {hilight $0} ({hilight $1})", 2, { 0, 0 } }, + { "tls_certificate_fingerprint", "Certificate Fingerprint: {hilight $0} ({hilight $1})", 2, { 0, 0 } }, + { "tls_protocol_version", "Protocol: {hilight $0} ({hilight $1} bit, {hilight $2})", 3, { 0, 1, 0 } }, + { NULL, NULL, 0 } }; diff --git a/src/fe-common/core/module-formats.h b/src/fe-common/core/module-formats.h index 2b45ff6b..6a8135f6 100644 --- a/src/fe-common/core/module-formats.h +++ b/src/fe-common/core/module-formats.h @@ -48,8 +48,8 @@ enum { TXT_LOOKING_UP, TXT_CONNECTING, - TXT_RECONNECTING, - TXT_CONNECTION_ESTABLISHED, + TXT_RECONNECTING, + TXT_CONNECTION_ESTABLISHED, TXT_CANT_CONNECT, TXT_CONNECTION_LOST, TXT_LAG_DISCONNECTED, @@ -254,7 +254,20 @@ enum { TXT_COMPLETION_REMOVED, TXT_COMPLETION_HEADER, TXT_COMPLETION_LINE, - TXT_COMPLETION_FOOTER + TXT_COMPLETION_FOOTER, + + TLS_FILL_15, + + TXT_TLS_EPHEMERAL_KEY, + TXT_TLS_EPHEMERAL_KEY_UNAVAILBLE, + TXT_TLS_PUBLIC_KEY, + TXT_TLS_CERT_HEADER, + TXT_TLS_CERT_SUBJECT_HEADER, + TXT_TLS_CERT_ISSUER_HEADER, + TXT_TLS_CERT_NAMED_ENTRY, + TXT_TLS_PUBLIC_KEY_FINGERPRINT, + TXT_TLS_CERTIFICATE_FINGERPRINT, + TXT_TLS_PROTOCOL_VERSION }; extern FORMAT_REC fecommon_core_formats[]; diff --git a/src/fe-common/irc/fe-irc-server.c b/src/fe-common/irc/fe-irc-server.c index 2e22d6f2..c4435d8f 100644 --- a/src/fe-common/irc/fe-irc-server.c +++ b/src/fe-common/irc/fe-irc-server.c @@ -108,23 +108,27 @@ static void cmd_server_list(const char *data) g_string_append(str, "autoconnect, "); if (rec->no_proxy) g_string_append(str, "noproxy, "); - if (rec->use_ssl) { - g_string_append(str, "ssl, "); - if (rec->ssl_cert) { - g_string_append_printf(str, "ssl_cert: %s, ", rec->ssl_cert); - if (rec->ssl_pkey) - g_string_append_printf(str, "ssl_pkey: %s, ", rec->ssl_pkey); - if (rec->ssl_pass) + if (rec->use_tls) { + g_string_append(str, "tls, "); + if (rec->tls_cert) { + g_string_append_printf(str, "tls_cert: %s, ", rec->tls_cert); + if (rec->tls_pkey) + g_string_append_printf(str, "tls_pkey: %s, ", rec->tls_pkey); + if (rec->tls_pass) g_string_append_printf(str, "(pass), "); } - if (rec->ssl_verify) - g_string_append(str, "ssl_verify, "); - if (rec->ssl_cafile) - g_string_append_printf(str, "ssl_cafile: %s, ", rec->ssl_cafile); - if (rec->ssl_capath) - g_string_append_printf(str, "ssl_capath: %s, ", rec->ssl_capath); - if (rec->ssl_ciphers) - g_string_append_printf(str, "ssl_ciphers: %s, ", rec->ssl_ciphers); + if (rec->tls_verify) + g_string_append(str, "tls_verify, "); + if (rec->tls_cafile) + g_string_append_printf(str, "tls_cafile: %s, ", rec->tls_cafile); + if (rec->tls_capath) + g_string_append_printf(str, "tls_capath: %s, ", rec->tls_capath); + if (rec->tls_ciphers) + g_string_append_printf(str, "tls_ciphers: %s, ", rec->tls_ciphers); + if (rec->tls_pinned_cert) + g_string_append_printf(str, "tls_pinned_cert: %s, ", rec->tls_pinned_cert); + if (rec->tls_pinned_pubkey) + g_string_append_printf(str, "tls_pinned_pubkey: %s, ", rec->tls_pinned_pubkey); } if (rec->max_cmds_at_once > 0) diff --git a/src/irc/core/irc-servers.c b/src/irc/core/irc-servers.c index 79aeb227..3117e345 100644 --- a/src/irc/core/irc-servers.c +++ b/src/irc/core/irc-servers.c @@ -310,7 +310,7 @@ SERVER_REC *irc_server_init_connect(SERVER_CONNECT_REC *conn) if (server->connrec->port <= 0) { server->connrec->port = - server->connrec->use_ssl ? 6697 : 6667; + server->connrec->use_tls ? 6697 : 6667; } server->cmd_queue_speed = ircconn->cmd_queue_speed > 0 ? @@ -328,7 +328,7 @@ SERVER_REC *irc_server_init_connect(SERVER_CONNECT_REC *conn) ircconn->max_whois : DEFAULT_MAX_WHOIS; server->max_msgs_in_cmd = ircconn->max_msgs > 0 ? ircconn->max_msgs : DEFAULT_MAX_MSGS; - server->connrec->use_ssl = conn->use_ssl; + server->connrec->use_tls = conn->use_tls; modes_server_init(server); diff --git a/src/perl/get-signals.pl b/src/perl/get-signals.pl index 529d35b1..806bcafa 100755 --- a/src/perl/get-signals.pl +++ b/src/perl/get-signals.pl @@ -37,6 +37,7 @@ while () { RAWLOG_REC => 'Irssi::Rawlog', IGNORE_REC => 'Irssi::Ignore', MODULE_REC => 'Irssi::Module', + TLS_REC => 'iobject', # irc BAN_REC => 'Irssi::Irc::Ban', diff --git a/src/perl/perl-common.c b/src/perl/perl-common.c index b641867f..1d08319f 100644 --- a/src/perl/perl-common.c +++ b/src/perl/perl-common.c @@ -301,7 +301,8 @@ void perl_connect_fill_hash(HV *hv, SERVER_CONNECT_REC *conn) (void) hv_store(hv, "no_autojoin_channels", 20, newSViv(conn->no_autojoin_channels), 0); (void) hv_store(hv, "no_autosendcmd", 14, newSViv(conn->no_autosendcmd), 0); (void) hv_store(hv, "unix_socket", 11, newSViv(conn->unix_socket), 0); - (void) hv_store(hv, "use_ssl", 7, newSViv(conn->use_ssl), 0); + (void) hv_store(hv, "use_ssl", 7, newSViv(conn->use_tls), 0); + (void) hv_store(hv, "use_tls", 7, newSViv(conn->use_tls), 0); (void) hv_store(hv, "no_connect", 10, newSViv(conn->no_connect), 0); }