diff --git a/NEWS b/NEWS index 4dad1712..e4431839 100644 --- a/NEWS +++ b/NEWS @@ -25,6 +25,27 @@ v0.8.21-head 2016-xx-xx The Irssi team configuration. + Display TLS connection information upon connect. You can disable this by setting tls_verbose_connect to FALSE. + + Add -tls_pinned_cert and -tls_pinned_pubkey for x509 and public key pinning. + + The values needed for -tls_pinned_cert and -tls_pinned_pubkey is shown + when connecting to a TLS enabled IRC server, but you can also find the + values like this: Start by downloading the certificate from a given IRC + server: + + $ openssl s_client -connect chat.freenode.net:6697 < /dev/null 2>/dev/null | \ + openssl x509 > freenode.cert + + Find the value for -tls_pinned_cert: + + $ openssl x509 -in freenode.cert -fingerprint -sha256 -noout + + Find the value for -tls_pinned_pubkey: + + $ openssl x509 -in freenode.cert -pubkey -noout | \ + openssl pkey -pubin -outform der | \ + openssl dgst -sha256 -c | \ + tr a-z A-Z + - IP addresses are no longer stored when resolve_reverse_lookup is used. - /names and $[...] now uses utf8 string operations (#40, #411). diff --git a/docs/help/in/connect.in b/docs/help/in/connect.in index a0d793d2..e861ad74 100644 --- a/docs/help/in/connect.in +++ b/docs/help/in/connect.in @@ -15,6 +15,8 @@ -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. diff --git a/docs/help/in/server.in b/docs/help/in/server.in index ee1a30e1..60870111 100644 --- a/docs/help/in/server.in +++ b/docs/help/in/server.in @@ -24,6 +24,8 @@ -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. diff --git a/src/core/chat-commands.c b/src/core/chat-commands.c index db60e46f..c737b810 100644 --- a/src/core/chat-commands.c +++ b/src/core/chat-commands.c @@ -115,6 +115,10 @@ static SERVER_CONNECT_REC *get_server_connect(const char *data, int *plus_addr, 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; @@ -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 tls +tls_cert +tls_pkey +tls_pass tls_verify +tls_cafile +tls_capath +tls_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/network-openssl.c b/src/core/network-openssl.c index c221624f..13db6e25 100644 --- a/src/core/network-openssl.c +++ b/src/core/network-openssl.c @@ -753,6 +753,8 @@ int irssi_ssl_handshake(GIOChannel *handle) 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(); @@ -814,6 +816,24 @@ int irssi_ssl_handshake(GIOChannel *handle) 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); diff --git a/src/core/server-connect-rec.h b/src/core/server-connect-rec.h index 35577fd4..fa348769 100644 --- a/src/core/server-connect-rec.h +++ b/src/core/server-connect-rec.h @@ -29,6 +29,8 @@ 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 */ diff --git a/src/core/server-setup-rec.h b/src/core/server-setup-rec.h index 22876d4e..e6b0431c 100644 --- a/src/core/server-setup-rec.h +++ b/src/core/server-setup-rec.h @@ -17,6 +17,8 @@ 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 */ diff --git a/src/core/servers-reconnect.c b/src/core/servers-reconnect.c index 16ec1fac..1727704c 100644 --- a/src/core/servers-reconnect.c +++ b/src/core/servers-reconnect.c @@ -199,6 +199,8 @@ server_connect_copy_skeleton(SERVER_CONNECT_REC *src, int connect_info) 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 01a36e1c..9492c58c 100644 --- a/src/core/servers-setup.c +++ b/src/core/servers-setup.c @@ -181,6 +181,10 @@ static void server_setup_fill_server(SERVER_CONNECT_REC *conn, 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); @@ -435,6 +439,16 @@ static SERVER_SETUP_REC *server_setup_read(CONFIG_NODE *node) 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) @@ -500,6 +514,8 @@ static void server_setup_save(SERVER_SETUP_REC *rec) 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); @@ -550,6 +566,8 @@ static void server_setup_destroy(SERVER_SETUP_REC *rec) 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 2a14d510..b9faab81 100644 --- a/src/core/servers.c +++ b/src/core/servers.c @@ -633,6 +633,8 @@ void server_connect_unref(SERVER_CONNECT_REC *conn) 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); diff --git a/src/core/session.c b/src/core/session.c index 5b3303bb..34190c52 100644 --- a/src/core/session.c +++ b/src/core/session.c @@ -165,6 +165,8 @@ static void session_save_server(SERVER_REC *server, CONFIG_REC *config, 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/fe-common/core/fe-server.c b/src/fe-common/core/fe-server.c index b9522bc1..f4c1d3ee 100644 --- a/src/fe-common/core/fe-server.c +++ b/src/fe-common/core/fe-server.c @@ -196,6 +196,17 @@ static void cmd_server_add_modify(const char *data, gboolean add) if (value != NULL && *value != '\0') rec->tls_ciphers = g_strdup(value); + 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); + + 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')) @@ -423,8 +434,8 @@ void fe_server_init(void) 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 +ssl_fingerprint tls +tls_cert +tls_pkey +tls_pass tls_verify +tls_cafile +tls_capath +tls_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 +ssl_fingerprint tls +tls_cert +tls_pkey +tls_pass tls_verify +tls_cafile +tls_capath +tls_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/irc/fe-irc-server.c b/src/fe-common/irc/fe-irc-server.c index 36ed2bdc..c4435d8f 100644 --- a/src/fe-common/irc/fe-irc-server.c +++ b/src/fe-common/irc/fe-irc-server.c @@ -125,6 +125,10 @@ static void cmd_server_list(const char *data) 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)